| name | databricks-isv-testing |
| description | 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. |
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)
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)
/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:
- Sources credentials once from an env file
- Runs each test with isolated env using
env -i plus only required vars
- Records pass/fail per test
- Prints summary at the end
Example Runner Structure
#!/usr/bin/env bash
set -euo pipefail
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
}
run_test "PAT" "env -i PATH=\"$PATH\" HOME=\"$HOME\" \
DATABRICKS_HOST=\"$DATABRICKS_HOST\" \
DATABRICKS_TOKEN=\"$DATABRICKS_TOKEN\" \
python your_pat_example.py"
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"
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
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
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:
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
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:
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:
self.send_response(204)
self.end_headers()
def log_message(self, format, *args):
pass
def run_callback_server(port=8040, timeout=120):
server = HTTPServer(("localhost", port), CallbackHandler)
server.timeout = timeout
while not CallbackHandler.callback_received.is_set():
server.handle_request()
server.server_close()
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
env -i PATH="$PATH" HOME="$HOME" PYTHONUNBUFFERED=1 \
DATABRICKS_HOST="$DATABRICKS_HOST" \
DATABRICKS_TOKEN="$DATABRICKS_TOKEN" \
python your_example.py
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
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()
export MAVEN_OPTS="--add-opens=java.base/java.nio=ALL-UNNAMED"
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"
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
go build -tags all_auth_example -o ./my_example ./
env -i PATH="$PATH" HOME="$HOME" \
APP_AUTH_TYPE=pat \
DATABRICKS_HOST="$DATABRICKS_HOST" \
DATABRICKS_TOKEN="$DATABRICKS_TOKEN" \
./my_example
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
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
env -i PATH="$PATH" HOME="$HOME" \
DATABRICKS_HOST="$DATABRICKS_HOST" \
DATABRICKS_HTTP_PATH="$DATABRICKS_HTTP_PATH" \
DATABRICKS_TOKEN="$DATABRICKS_TOKEN" \
node your_example.js
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:
import os
auth_type = os.environ.get("APP_AUTH_TYPE", "oauth_m2m")
if auth_type == "pat":
token = os.environ["DATABRICKS_TOKEN"]
elif auth_type == "oauth_m2m":
client_id = os.environ["DATABRICKS_CLIENT_ID"]
client_secret = os.environ["DATABRICKS_CLIENT_SECRET"]
elif auth_type == "u2m_custom_oauth_app":
u2m_client_id = os.environ["DATABRICKS_U2M_CLIENT_ID"]
elif auth_type == "u2m_token_env":
token = os.environ.get("DATABRICKS_ACCESS_TOKEN") or os.environ.get("DATABRICKS_TOKEN")
else:
raise ValueError(f"Unknown auth type: {auth_type}")
Test each auth type:
APP_AUTH_TYPE=pat DATABRICKS_HOST=... DATABRICKS_TOKEN=... python all_auth_example.py
APP_AUTH_TYPE=oauth_m2m DATABRICKS_HOST=... DATABRICKS_CLIENT_ID=... DATABRICKS_CLIENT_SECRET=... python all_auth_example.py
APP_AUTH_TYPE=u2m_custom_oauth_app DATABRICKS_HOST=... DATABRICKS_U2M_CLIENT_ID=... python all_auth_example.py
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:
DATABRICKS_REDIRECT_URI="http://localhost:8040/callback" ./run_tests_python.sh &
DATABRICKS_REDIRECT_URI="http://localhost:8041/callback" ./run_tests_go.sh &
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
def validate_connection(client):
"""Verify auth works with a simple API call."""
try:
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
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 — Validation requirements
- U2M Patterns: u2m/SKILL.md — Detailed U2M implementation
- Connector Skills: Language-specific auth patterns in
skills/<connector>/SKILL.md