# Databricks Isv Testing

> How to run and structure auth tests for Databricks ISV integrations: clean env per test, runner script patterns, U2M browser flows, and language-specific considerations.

- Skill: `databricks-solutions/databricks-isv-testing` (Agent Skill)
- Install (CLI): `npx skillmds@latest add databricks-solutions/databricks-isv-testing`
- Raw SKILL.md: https://api.skillmd.com/api/skills/databricks-solutions/databricks-isv-testing/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: databricks-solutions (https://skillmd.com/u/databricks-solutions)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/databricks-solutions/databricks-isv-testing

---


<!-- skill-version: 1.0.0 -->

# Testing Auth for ISV Integrations

Use this skill when **running or designing tests** for Databricks partner authentication (PAT, OAuth M2M, U2M).

## Core Principle: Clean Environment Per Test

**Never run multiple auth types in one process** with mixed environment variables. SDKs and drivers error with "more than one authorization method configured" if conflicting credentials are set.

### Option A: `env -i` (Automated Test Runners)

```bash
env -i PATH="$PATH" HOME="$HOME" \
  DATABRICKS_HOST="https://myworkspace.cloud.databricks.com" \
  DATABRICKS_TOKEN="dapi..." \
  python your_pat_example.py
```

**Caveat:** In long interactive sessions, `env -i` may strip needed vars like `PYTHONPATH`. Use Option B for manual testing.

### Option B: Fresh Bash Subshell (Interactive Testing)

```bash
/bin/bash -c '
export DATABRICKS_HOST="https://myworkspace.cloud.databricks.com"
export DATABRICKS_TOKEN="dapi..."
unset DATABRICKS_CLIENT_ID DATABRICKS_CLIENT_SECRET
export PYTHONUNBUFFERED=1
python your_pat_example.py
'
```

Starts a completely clean child process. More reliable for interactive manual testing.

---

## Environment Variables by Auth Type

| Auth Type | Selector Value | Required Variables | Must NOT Be Set |
|-----------|----------------|--------------------|-----------------| 
| PAT | `pat` | `DATABRICKS_HOST`, `DATABRICKS_TOKEN` | `CLIENT_ID`, `CLIENT_SECRET` |
| OAuth M2M | `oauth_m2m` | `DATABRICKS_HOST`, `DATABRICKS_CLIENT_ID`, `DATABRICKS_CLIENT_SECRET` | `DATABRICKS_TOKEN` |
| U2M External Browser | `u2m_external_browser` | `DATABRICKS_HOST` | `CLIENT_ID`, `CLIENT_SECRET`, `TOKEN` |
| U2M Custom OAuth App | `u2m_custom_oauth_app` | `DATABRICKS_HOST`, `DATABRICKS_U2M_CLIENT_ID` | `DATABRICKS_CLIENT_ID` |
| U2M Token-Env | `u2m_token_env` | `DATABRICKS_HOST`, `DATABRICKS_ACCESS_TOKEN` | `CLIENT_ID`, `CLIENT_SECRET` |

**Optional for all:** `DATABRICKS_HTTP_PATH` (SQL drivers), `DATABRICKS_WAREHOUSE_ID` (Statement Execution API).

**Optional for U2M Custom OAuth App:** `DATABRICKS_U2M_CLIENT_SECRET` (if confidential app), `DATABRICKS_REDIRECT_URI` (default varies by connector).

### CLIENT_ID Distinction (Critical)

| Env Var | Auth Type | What It Is |
|---------|-----------|------------|
| `DATABRICKS_CLIENT_ID` | `oauth_m2m` | M2M Service Principal UUID |
| `DATABRICKS_U2M_CLIENT_ID` | `u2m_custom_oauth_app` | Custom OAuth App from App Connections |

Using the wrong one causes `"OAuth application with client_id not available"`.

---

## Test Runner Script Pattern

A well-structured test runner:

1. **Sources credentials once** from an env file
2. **Runs each test with isolated env** using `env -i` plus only required vars
3. **Records pass/fail** per test
4. **Prints summary** at the end

### Example Runner Structure

```bash
#!/usr/bin/env bash
set -euo pipefail

# Load credentials
source "${1:-credentials.env}"

PASSED=0
FAILED=0

run_test() {
    local name="$1"
    local cmd="$2"
    
    echo "Running: $name"
    if eval "$cmd" > /dev/null 2>&1; then
        echo "  PASS"
        ((PASSED++))
    else
        echo "  FAIL"
        ((FAILED++))
    fi
}

# PAT Test
run_test "PAT" "env -i PATH=\"$PATH\" HOME=\"$HOME\" \
    DATABRICKS_HOST=\"$DATABRICKS_HOST\" \
    DATABRICKS_TOKEN=\"$DATABRICKS_TOKEN\" \
    python your_pat_example.py"

# OAuth M2M Test  
run_test "OAuth M2M" "env -i PATH=\"$PATH\" HOME=\"$HOME\" \
    DATABRICKS_HOST=\"$DATABRICKS_HOST\" \
    DATABRICKS_CLIENT_ID=\"$DATABRICKS_CLIENT_ID\" \
    DATABRICKS_CLIENT_SECRET=\"$DATABRICKS_CLIENT_SECRET\" \
    python your_m2m_example.py"

# Summary
echo ""
echo "Results: $PASSED passed, $FAILED failed"
```

---

## U2M Browser Test Considerations

### Built-in OAuth App (External Browser)

- Do NOT pass M2M credentials (`DATABRICKS_CLIENT_ID`/`DATABRICKS_CLIENT_SECRET`)
- The SDK uses the built-in `databricks-cli` OAuth app
- Redirect URI is typically `http://localhost:8020`

```bash
env -i PATH="$PATH" HOME="$HOME" \
    DATABRICKS_HOST="$DATABRICKS_HOST" \
    USER="$USER" DISPLAY="${DISPLAY:-}" \
    python your_u2m_browser_example.py
```

### Custom OAuth App (PKCE)

- Use `DATABRICKS_U2M_CLIENT_ID` (NOT `DATABRICKS_CLIENT_ID`)
- Optionally set `DATABRICKS_U2M_CLIENT_SECRET` and `DATABRICKS_REDIRECT_URI`
- Default redirect URI is typically `http://localhost:8040/callback`

```bash
env -i PATH="$PATH" HOME="$HOME" \
    DATABRICKS_HOST="$DATABRICKS_HOST" \
    DATABRICKS_U2M_CLIENT_ID="$DATABRICKS_U2M_CLIENT_ID" \
    DATABRICKS_U2M_CLIENT_SECRET="$DATABRICKS_U2M_CLIENT_SECRET" \
    DATABRICKS_REDIRECT_URI="http://localhost:8040/callback" \
    USER="$USER" DISPLAY="${DISPLAY:-}" \
    python your_u2m_custom_app_example.py
```

### Port Cleanup Before U2M Tests

Before running custom OAuth app tests, kill any process holding the redirect port:

```bash
lsof -ti:8040 | xargs kill -9 2>/dev/null || true
```

A previous hung test can leave the port bound.

---

## HTTPServer Pitfalls (PKCE Flow Implementation)

When implementing the local callback server for PKCE:

### Python Implementation Pattern

```python
import threading
from http.server import HTTPServer, BaseHTTPRequestHandler

class CallbackHandler(BaseHTTPRequestHandler):
    auth_code = None
    callback_received = threading.Event()
    
    def do_GET(self):
        if "/callback" in self.path:
            # Extract code from query params
            from urllib.parse import urlparse, parse_qs
            query = parse_qs(urlparse(self.path).query)
            CallbackHandler.auth_code = query.get("code", [None])[0]
            
            self.send_response(200)
            self.end_headers()
            self.wfile.write(b"Authentication complete. You can close this tab.")
            CallbackHandler.callback_received.set()
        else:
            # Ignore other requests (favicon, etc.)
            self.send_response(204)
            self.end_headers()
    
    def log_message(self, format, *args):
        pass  # Suppress logging

def run_callback_server(port=8040, timeout=120):
    server = HTTPServer(("localhost", port), CallbackHandler)
    server.timeout = timeout  # CRITICAL: Set timeout
    
    # Handle requests until callback received or timeout
    while not CallbackHandler.callback_received.is_set():
        server.handle_request()
    
    server.server_close()  # Use server_close(), NOT shutdown()
    return CallbackHandler.auth_code
```

**Key Points:**
- **Always set `server.timeout`** — Without it, `handle_request()` blocks forever
- **Use `server.server_close()`** — NOT `server.shutdown()` (which hangs with `handle_request()`)
- **Handle multiple requests** — Browser may send favicon requests before the callback
- **Use `threading.Event`** — To signal when the actual callback is received

---

## Language-Specific Notes

### Python

```bash
# PAT
env -i PATH="$PATH" HOME="$HOME" PYTHONUNBUFFERED=1 \
    DATABRICKS_HOST="$DATABRICKS_HOST" \
    DATABRICKS_TOKEN="$DATABRICKS_TOKEN" \
    python your_example.py

# OAuth M2M
env -i PATH="$PATH" HOME="$HOME" PYTHONUNBUFFERED=1 \
    DATABRICKS_HOST="$DATABRICKS_HOST" \
    DATABRICKS_CLIENT_ID="$DATABRICKS_CLIENT_ID" \
    DATABRICKS_CLIENT_SECRET="$DATABRICKS_CLIENT_SECRET" \
    python your_example.py

# U2M (browser)
env -i PATH="$PATH" HOME="$HOME" USER="$USER" DISPLAY="${DISPLAY:-}" \
    DATABRICKS_HOST="$DATABRICKS_HOST" \
    DATABRICKS_U2M_CLIENT_ID="$DATABRICKS_U2M_CLIENT_ID" \
    python your_example.py
```

### Java

- **Environment isolation:** Run with `env -i` to prevent Java SDK auto-reading conflicting env vars
- **Java 17+ JDBC:** Add `--add-opens=java.base/java.nio=ALL-UNNAMED` to JVM args for Arrow-based OSS JDBC driver
- **SDK Workarounds (U2M):** Must call `config.setScopes(Arrays.asList("all-apis"))` and `config.resolve()` before `config.authenticate()`

```bash
export MAVEN_OPTS="--add-opens=java.base/java.nio=ALL-UNNAMED"

# PAT
env -i PATH="$PATH" HOME="$HOME" JAVA_HOME="$JAVA_HOME" \
    DATABRICKS_HOST="$DATABRICKS_HOST" \
    DATABRICKS_TOKEN="$DATABRICKS_TOKEN" \
    mvn -q exec:java -Dexec.mainClass="com.example.PatExample"

# OAuth M2M
env -i PATH="$PATH" HOME="$HOME" JAVA_HOME="$JAVA_HOME" \
    DATABRICKS_HOST="$DATABRICKS_HOST" \
    DATABRICKS_CLIENT_ID="$DATABRICKS_CLIENT_ID" \
    DATABRICKS_CLIENT_SECRET="$DATABRICKS_CLIENT_SECRET" \
    mvn -q exec:java -Dexec.mainClass="com.example.M2MExample"
```

### Go

- **Go SDK uses `APP_AUTH_TYPE`:** The SDK reads `DATABRICKS_AUTH_TYPE` internally, so use `APP_AUTH_TYPE` for your auth selector
- **Go SQL Driver uses `DATABRICKS_AUTH_TYPE`:** Safe to use as the selector
- **Build once, run with `env -i`:** Build the binary first, then run with isolated env

```bash
# Build once (example for Go SDK with build tags)
go build -tags all_auth_example -o ./my_example ./

# PAT (Go SDK uses APP_AUTH_TYPE)
env -i PATH="$PATH" HOME="$HOME" \
    APP_AUTH_TYPE=pat \
    DATABRICKS_HOST="$DATABRICKS_HOST" \
    DATABRICKS_TOKEN="$DATABRICKS_TOKEN" \
    ./my_example

# OAuth M2M
env -i PATH="$PATH" HOME="$HOME" \
    APP_AUTH_TYPE=oauth_m2m \
    DATABRICKS_HOST="$DATABRICKS_HOST" \
    DATABRICKS_CLIENT_ID="$DATABRICKS_CLIENT_ID" \
    DATABRICKS_CLIENT_SECRET="$DATABRICKS_CLIENT_SECRET" \
    ./my_example

# U2M (browser) - needs USER and DISPLAY
env -i PATH="$PATH" HOME="$HOME" USER="$USER" DISPLAY="${DISPLAY:-}" \
    APP_AUTH_TYPE=u2m_custom_oauth_app \
    DATABRICKS_HOST="$DATABRICKS_HOST" \
    DATABRICKS_U2M_CLIENT_ID="$DATABRICKS_U2M_CLIENT_ID" \
    ./my_example
```

### Node.js

```bash
# PAT
env -i PATH="$PATH" HOME="$HOME" \
    DATABRICKS_HOST="$DATABRICKS_HOST" \
    DATABRICKS_HTTP_PATH="$DATABRICKS_HTTP_PATH" \
    DATABRICKS_TOKEN="$DATABRICKS_TOKEN" \
    node your_example.js

# OAuth M2M
env -i PATH="$PATH" HOME="$HOME" \
    DATABRICKS_HOST="$DATABRICKS_HOST" \
    DATABRICKS_HTTP_PATH="$DATABRICKS_HTTP_PATH" \
    DATABRICKS_CLIENT_ID="$DATABRICKS_CLIENT_ID" \
    DATABRICKS_CLIENT_SECRET="$DATABRICKS_CLIENT_SECRET" \
    node your_example.js
```

---

## Multi-Auth Example Pattern

Support multiple auth types via an environment variable selector:

```python
import os

auth_type = os.environ.get("APP_AUTH_TYPE", "oauth_m2m")

if auth_type == "pat":
    # Use DATABRICKS_TOKEN
    token = os.environ["DATABRICKS_TOKEN"]
    # Connect with PAT...

elif auth_type == "oauth_m2m":
    # Use DATABRICKS_CLIENT_ID and DATABRICKS_CLIENT_SECRET
    client_id = os.environ["DATABRICKS_CLIENT_ID"]
    client_secret = os.environ["DATABRICKS_CLIENT_SECRET"]
    # Connect with M2M...

elif auth_type == "u2m_custom_oauth_app":
    # Use DATABRICKS_U2M_CLIENT_ID, run PKCE flow
    u2m_client_id = os.environ["DATABRICKS_U2M_CLIENT_ID"]
    # Run PKCE flow, get token, connect...

elif auth_type == "u2m_token_env":
    # Use pre-obtained token
    token = os.environ.get("DATABRICKS_ACCESS_TOKEN") or os.environ.get("DATABRICKS_TOKEN")
    # Connect with token...

else:
    raise ValueError(f"Unknown auth type: {auth_type}")
```

Test each auth type:

```bash
# PAT
APP_AUTH_TYPE=pat DATABRICKS_HOST=... DATABRICKS_TOKEN=... python all_auth_example.py

# OAuth M2M
APP_AUTH_TYPE=oauth_m2m DATABRICKS_HOST=... DATABRICKS_CLIENT_ID=... DATABRICKS_CLIENT_SECRET=... python all_auth_example.py

# U2M Custom OAuth App
APP_AUTH_TYPE=u2m_custom_oauth_app DATABRICKS_HOST=... DATABRICKS_U2M_CLIENT_ID=... python all_auth_example.py

# U2M Token-Env
APP_AUTH_TYPE=u2m_token_env DATABRICKS_HOST=... DATABRICKS_ACCESS_TOKEN=... python all_auth_example.py
```

---

## Parallel Test Execution

When running multiple tests in parallel (e.g., different connectors), assign unique callback ports to avoid conflicts:

```bash
# Runner 1 uses port 8040
DATABRICKS_REDIRECT_URI="http://localhost:8040/callback" ./run_tests_python.sh &

# Runner 2 uses port 8041
DATABRICKS_REDIRECT_URI="http://localhost:8041/callback" ./run_tests_go.sh &

# Runner 3 uses port 8042
DATABRICKS_REDIRECT_URI="http://localhost:8042/callback" ./run_tests_java.sh &

wait
```

---

## Validation Tests

Each test should verify both authentication and basic operations:

### Validation Pattern

```python
def validate_connection(client):
    """Verify auth works with a simple API call."""
    try:
        # For SDK: call any API
        user = client.current_user.me()
        print(f"Authenticated as: {user.user_name}")
        return True
    except Exception as e:
        print(f"Validation failed: {e}")
        return False
```

### SQL Driver Validation

```python
def validate_sql_connection(connection):
    """Verify SQL connection works."""
    cursor = connection.cursor()
    cursor.execute("SELECT 1 AS test")
    result = cursor.fetchone()
    print(f"Query result: {result}")
    cursor.close()
    return result[0] == 1
```

---

## Troubleshooting

| Error | Cause | Solution |
|-------|-------|----------|
| "more than one authorization method configured" | Multiple auth env vars set | Use `env -i` for clean environment |
| "OAuth application with client_id not available" | Using M2M client_id for U2M | Use `DATABRICKS_U2M_CLIENT_ID` for U2M |
| "redirect_uri not registered" | URI mismatch | Exact match required in App Connections |
| Port already in use | Previous test hung | `lsof -ti:8040 \| xargs kill -9` |
| PKCE flow times out | User didn't complete sign-in | Check browser popups, increase timeout |
| `NullPointerException` (Java SDK) | Missing workarounds | Call `setScopes()` and `resolve()` |

---

## Related Skills

- **Integration Checklist:** [integration-checklist.md](../connector-testing/integration-checklist.md) — Validation requirements
- **U2M Patterns:** [u2m/SKILL.md](../u2m/SKILL.md) — Detailed U2M implementation
- **Connector Skills:** Language-specific auth patterns in `skills/<connector>/SKILL.md`

