Skip to content

Commit f3e647c

Browse files
Add end-to-end OAuth tests (M2M and U2M)\n\nTask ID: task-4.3-end-to-end-tests
1 parent 7933bf4 commit f3e647c

File tree

6 files changed

+568
-23
lines changed

6 files changed

+568
-23
lines changed

rust/CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ All files must have Apache 2.0 license headers:
174174
- Unit tests go in `#[cfg(test)] mod tests { }` at the bottom of each file
175175
- Integration tests go in `tests/` directory
176176
- Test names: `test_<function>_<scenario>`
177+
- E2E tests that require real Databricks connection should be marked with `#[ignore]`
177178

178179
### Error Handling
179180

rust/docs/designs/oauth-u2m-m2m-design.md

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -726,8 +726,27 @@ Tests in `client/http.rs` `#[cfg(test)]` verifying the `OnceLock`-based auth pro
726726

727727
### End-to-End Tests
728728

729-
- `test_m2m_end_to_end` -- real Databricks workspace with service principal credentials (requires env vars, `#[ignore]` by default)
730-
- `test_u2m_end_to_end` -- manual test only (`#[ignore]`), requires interactive browser
729+
End-to-end tests in `tests/oauth_e2e.rs` verify the complete OAuth implementation against real Databricks workspaces:
730+
731+
**M2M (Service Principal) Test:**
732+
- `test_m2m_end_to_end` -- Connects to real Databricks workspace using service principal credentials
733+
- Requires environment variables: `DATABRICKS_HOST`, `DATABRICKS_CLIENT_ID`, `DATABRICKS_CLIENT_SECRET`, `DATABRICKS_WAREHOUSE_ID`
734+
- Marked with `#[ignore]` to prevent running in CI by default
735+
- Verifies: (1) Database config with mechanism=11, flow=1, (2) OIDC discovery, (3) Client credentials token exchange, (4) Connection creation, (5) Query execution (SELECT 1), (6) Result validation
736+
- Run with: `cargo test --test oauth_e2e test_m2m_end_to_end -- --ignored --nocapture`
737+
738+
**U2M (Browser-Based) Test:**
739+
- `test_u2m_end_to_end` -- Manual test requiring interactive browser authentication
740+
- Requires environment variables: `DATABRICKS_HOST`, `DATABRICKS_WAREHOUSE_ID`, `DATABRICKS_CLIENT_ID` (optional, defaults to "databricks-cli")
741+
- Always marked with `#[ignore]` (manual test only)
742+
- Verifies: (1) Database config with mechanism=11, flow=2, (2) OIDC discovery, (3) Token cache check, (4) Browser launch (if needed), (5) Authorization code exchange, (6) Connection creation, (7) Query execution (SELECT 1), (8) Result validation, (9) Token caching to disk
743+
- Run with: `cargo test --test oauth_e2e test_u2m_end_to_end -- --ignored --nocapture`
744+
- Note: Will launch default web browser for user to complete authentication
745+
746+
**Configuration Validation Test:**
747+
- `test_oauth_config_validation` -- Verifies proper error messages for missing/invalid OAuth configuration
748+
- Runs in standard test suite (not ignored)
749+
- Tests: missing auth.flow when mechanism=OAuth, missing client_secret for M2M, invalid mechanism/flow values
731750

732751
---
733752

rust/src/auth/oauth/m2m.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,8 @@ impl ClientCredentialsProvider {
188188
token_endpoint_override: Option<String>,
189189
) -> Result<Self> {
190190
// Discover OIDC endpoints or use override
191-
let (token_endpoint, auth_endpoint) = if let Some(token_endpoint) = token_endpoint_override {
191+
let (token_endpoint, auth_endpoint) = if let Some(token_endpoint) = token_endpoint_override
192+
{
192193
// Use override for token endpoint, still discover auth endpoint
193194
// (auth endpoint is not typically overridden for M2M since it's not used)
194195
let endpoints = OidcEndpoints::discover(host, &http_client).await?;

rust/src/database.rs

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -554,25 +554,34 @@ impl adbc_core::Database for Database {
554554
}
555555
AuthFlow::ClientCredentials => {
556556
// Client credentials flow (M2M) - create ClientCredentialsProvider
557-
let client_id = self.auth_client_id.as_ref()
557+
let client_id = self
558+
.auth_config
559+
.client_id
560+
.as_ref()
558561
.expect("client_id should be validated above");
559-
let client_secret = self.auth_client_secret.as_ref()
562+
let client_secret = self
563+
.auth_config
564+
.client_secret
565+
.as_ref()
560566
.expect("client_secret should be validated above");
561567

562568
// Default scope for M2M is "all-apis" (no offline_access since M2M has no refresh token)
563-
let scopes_str = self.auth_scopes.as_deref().unwrap_or("all-apis");
564-
let scopes: Vec<String> = scopes_str.split_whitespace().map(String::from).collect();
569+
let scopes_str = self.auth_config.scopes.as_deref().unwrap_or("all-apis");
570+
let scopes: Vec<String> =
571+
scopes_str.split_whitespace().map(String::from).collect();
565572

566573
// Create the provider (async operation - needs runtime)
567574
let provider = runtime
568-
.block_on(crate::auth::ClientCredentialsProvider::new_with_full_config(
569-
host,
570-
client_id,
571-
client_secret,
572-
http_client.clone(),
573-
scopes,
574-
self.auth_token_endpoint.clone(),
575-
))
575+
.block_on(
576+
crate::auth::ClientCredentialsProvider::new_with_full_config(
577+
host,
578+
client_id,
579+
client_secret,
580+
http_client.clone(),
581+
scopes,
582+
self.auth_config.token_endpoint.clone(),
583+
),
584+
)
576585
.map_err(|e| e.to_adbc())?;
577586

578587
Arc::new(provider)
@@ -1572,10 +1581,13 @@ mod tests {
15721581
.unwrap();
15731582

15741583
// Verify config is stored correctly
1575-
assert_eq!(db.auth_mechanism, Some(AuthMechanism::OAuth));
1576-
assert_eq!(db.auth_flow, Some(AuthFlow::ClientCredentials));
1577-
assert_eq!(db.auth_client_id, Some("test-client-id".to_string()));
1578-
assert_eq!(db.auth_client_secret, Some("test-secret".to_string()));
1584+
assert_eq!(db.auth_config.mechanism, Some(AuthMechanism::OAuth));
1585+
assert_eq!(db.auth_config.flow, Some(AuthFlow::ClientCredentials));
1586+
assert_eq!(db.auth_config.client_id, Some("test-client-id".to_string()));
1587+
assert_eq!(
1588+
db.auth_config.client_secret,
1589+
Some("test-secret".to_string())
1590+
);
15791591
}
15801592

15811593
#[test]
@@ -1620,7 +1632,7 @@ mod tests {
16201632

16211633
// Verify custom scopes are stored
16221634
assert_eq!(
1623-
db.auth_scopes,
1635+
db.auth_config.scopes,
16241636
Some("custom-scope-1 custom-scope-2".to_string())
16251637
);
16261638
}
@@ -1667,7 +1679,7 @@ mod tests {
16671679

16681680
// Verify token_endpoint override is stored
16691681
assert_eq!(
1670-
db.auth_token_endpoint,
1682+
db.auth_config.token_endpoint,
16711683
Some("https://custom.endpoint/token".to_string())
16721684
);
16731685
}

0 commit comments

Comments
 (0)