CLI app for developers prototyping atproto functionality
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

test(oauth-client): add metadata stage integration tests with AC2 coverage

Implement 10+ integration tests covering AC2.1–AC2.9 plus loopback-skip
and discovery-failure-blocks-metadata scenarios. Tests cover:

- AC2.1: confidential web client (happy path)
- AC2.2: public web client (happy path)
- AC2.3: native client (happy path)
- AC2.4: dpop_bound_access_tokens required
- AC2.5: confidential requires jwks/jwks_uri
- AC2.6: public client forbids token_endpoint_auth_method != "none"
- AC2.7: native redirect_uri scheme must match reverse-domain
- AC2.8: scope grammar validation with miette spans
- AC2.9: loopback clients skip all metadata checks
- discovery failure blocks metadata checks

Fixtures include minimal valid metadata documents for confidential/public
web clients and native clients, plus violation cases (missing fields,
invalid values, grammar errors). All snapshots verified and accepted.

Updated discovery test fixture to comply with Phase 4 metadata validation
constraints. All metadata check IDs appear in test snapshots.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

+856 -16
+14 -4
tests/fixtures/oauth_client/discovery/https_confidential_happy/metadata.json
··· 1 1 { 2 2 "client_id": "https://client.example.com/metadata.json", 3 3 "application_type": "web", 4 - "redirect_uris": ["http://localhost/callback"], 4 + "redirect_uris": ["https://client.example.com/callback"], 5 5 "grant_types": ["authorization_code"], 6 6 "response_types": ["code"], 7 - "scope": "openid", 8 - "dpop_bound_access_tokens": false, 7 + "scope": "atproto", 8 + "dpop_bound_access_tokens": true, 9 9 "token_endpoint_auth_method": "private_key_jwt", 10 10 "jwks": { 11 - "keys": [] 11 + "keys": [ 12 + { 13 + "kty": "EC", 14 + "crv": "P-256", 15 + "alg": "ES256", 16 + "use": "sig", 17 + "kid": "k1", 18 + "x": "f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU", 19 + "y": "x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0" 20 + } 21 + ] 12 22 } 13 23 }
+23
tests/fixtures/oauth_client/metadata/confidential_happy/metadata.json
··· 1 + { 2 + "client_id": "https://client.example.com/metadata.json", 3 + "application_type": "web", 4 + "response_types": ["code"], 5 + "grant_types": ["authorization_code"], 6 + "scope": "atproto", 7 + "redirect_uris": ["https://client.example.com/cb"], 8 + "dpop_bound_access_tokens": true, 9 + "token_endpoint_auth_method": "private_key_jwt", 10 + "jwks": { 11 + "keys": [ 12 + { 13 + "kty": "EC", 14 + "crv": "P-256", 15 + "alg": "ES256", 16 + "use": "sig", 17 + "kid": "k1", 18 + "x": "f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU", 19 + "y": "x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0" 20 + } 21 + ] 22 + } 23 + }
+10
tests/fixtures/oauth_client/metadata/confidential_missing_jwks/metadata.json
··· 1 + { 2 + "client_id": "https://client.example.com/metadata.json", 3 + "application_type": "web", 4 + "response_types": ["code"], 5 + "grant_types": ["authorization_code"], 6 + "scope": "atproto", 7 + "redirect_uris": ["https://client.example.com/cb"], 8 + "dpop_bound_access_tokens": true, 9 + "token_endpoint_auth_method": "private_key_jwt" 10 + }
+23
tests/fixtures/oauth_client/metadata/dpop_bound_false/metadata.json
··· 1 + { 2 + "client_id": "https://client.example.com/metadata.json", 3 + "application_type": "web", 4 + "response_types": ["code"], 5 + "grant_types": ["authorization_code"], 6 + "scope": "atproto", 7 + "redirect_uris": ["https://client.example.com/cb"], 8 + "dpop_bound_access_tokens": false, 9 + "token_endpoint_auth_method": "private_key_jwt", 10 + "jwks": { 11 + "keys": [ 12 + { 13 + "kty": "EC", 14 + "crv": "P-256", 15 + "alg": "ES256", 16 + "use": "sig", 17 + "kid": "k1", 18 + "x": "f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU", 19 + "y": "x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0" 20 + } 21 + ] 22 + } 23 + }
+10
tests/fixtures/oauth_client/metadata/native_happy/metadata.json
··· 1 + { 2 + "client_id": "https://app.example.com/oauth-client-metadata.json", 3 + "application_type": "native", 4 + "response_types": ["code"], 5 + "grant_types": ["authorization_code"], 6 + "scope": "atproto", 7 + "redirect_uris": ["com.example.app:/cb"], 8 + "dpop_bound_access_tokens": true, 9 + "token_endpoint_auth_method": "none" 10 + }
+10
tests/fixtures/oauth_client/metadata/native_redirect_scheme_mismatch/metadata.json
··· 1 + { 2 + "client_id": "https://app.example.com/oauth-client-metadata.json", 3 + "application_type": "native", 4 + "response_types": ["code"], 5 + "grant_types": ["authorization_code"], 6 + "scope": "atproto", 7 + "redirect_uris": ["app.wrong.example:/cb"], 8 + "dpop_bound_access_tokens": true, 9 + "token_endpoint_auth_method": "none" 10 + }
+10
tests/fixtures/oauth_client/metadata/public_happy/metadata.json
··· 1 + { 2 + "client_id": "https://client.example.com/metadata.json", 3 + "application_type": "web", 4 + "response_types": ["code"], 5 + "grant_types": ["authorization_code"], 6 + "scope": "atproto", 7 + "redirect_uris": ["https://client.example.com/cb"], 8 + "dpop_bound_access_tokens": true, 9 + "token_endpoint_auth_method": "none" 10 + }
+10
tests/fixtures/oauth_client/metadata/public_with_token_endpoint_auth/metadata.json
··· 1 + { 2 + "client_id": "https://client.example.com/metadata.json", 3 + "application_type": "web", 4 + "response_types": ["code"], 5 + "grant_types": ["authorization_code"], 6 + "scope": "atproto", 7 + "redirect_uris": ["https://client.example.com/cb"], 8 + "dpop_bound_access_tokens": true, 9 + "token_endpoint_auth_method": "client_secret_basic" 10 + }
+23
tests/fixtures/oauth_client/metadata/scope_grammar_invalid/metadata.json
··· 1 + { 2 + "client_id": "https://client.example.com/metadata.json", 3 + "application_type": "web", 4 + "response_types": ["code"], 5 + "grant_types": ["authorization_code"], 6 + "scope": "atproto invalid-scope-token", 7 + "redirect_uris": ["https://client.example.com/cb"], 8 + "dpop_bound_access_tokens": true, 9 + "token_endpoint_auth_method": "private_key_jwt", 10 + "jwks": { 11 + "keys": [ 12 + { 13 + "kty": "EC", 14 + "crv": "P-256", 15 + "alg": "ES256", 16 + "use": "sig", 17 + "kid": "k1", 18 + "x": "f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU", 19 + "y": "x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0" 20 + } 21 + ] 22 + } 23 + }
+293
tests/oauth_client_metadata.rs
··· 1 + //! Integration tests for the oauth client metadata stage using snapshot tests. 2 + 3 + mod common; 4 + 5 + use atproto_devtool::commands::test::oauth::client::pipeline::{ 6 + OauthClientOptions, OauthClientReport, parse_target, run_pipeline, 7 + }; 8 + use atproto_devtool::common::report::RenderConfig; 9 + use url::Url; 10 + 11 + /// Helper to render a report to a string. 12 + fn render_report_to_string(report: &OauthClientReport) -> String { 13 + let mut buf = Vec::new(); 14 + report 15 + .render(&mut buf, &RenderConfig { no_color: true }) 16 + .expect("render failed"); 17 + String::from_utf8(buf).expect("invalid utf-8") 18 + } 19 + 20 + // ============================================================================= 21 + // AC2.1: Confidential web client happy path 22 + // ============================================================================= 23 + 24 + #[tokio::test] 25 + async fn confidential_happy() { 26 + let http = common::FakeHttpClient::new(); 27 + let metadata = 28 + include_bytes!("fixtures/oauth_client/metadata/confidential_happy/metadata.json"); 29 + http.add_response( 30 + &Url::parse("https://client.example.com/metadata.json").unwrap(), 31 + 200, 32 + metadata.to_vec(), 33 + ); 34 + 35 + let target = parse_target("https://client.example.com/metadata.json").expect("parse failed"); 36 + let opts = OauthClientOptions { 37 + http: &http, 38 + verbose: false, 39 + }; 40 + 41 + let report = run_pipeline(target, opts).await; 42 + assert_eq!(report.exit_code(), 0, "Expected all checks to pass"); 43 + 44 + let rendered = render_report_to_string(&report); 45 + insta::assert_snapshot!(rendered); 46 + } 47 + 48 + // ============================================================================= 49 + // AC2.2: Public web client happy path 50 + // ============================================================================= 51 + 52 + #[tokio::test] 53 + async fn public_happy() { 54 + let http = common::FakeHttpClient::new(); 55 + let metadata = include_bytes!("fixtures/oauth_client/metadata/public_happy/metadata.json"); 56 + http.add_response( 57 + &Url::parse("https://client.example.com/metadata.json").unwrap(), 58 + 200, 59 + metadata.to_vec(), 60 + ); 61 + 62 + let target = parse_target("https://client.example.com/metadata.json").expect("parse failed"); 63 + let opts = OauthClientOptions { 64 + http: &http, 65 + verbose: false, 66 + }; 67 + 68 + let report = run_pipeline(target, opts).await; 69 + assert_eq!(report.exit_code(), 0, "Expected all checks to pass"); 70 + 71 + let rendered = render_report_to_string(&report); 72 + insta::assert_snapshot!(rendered); 73 + } 74 + 75 + // ============================================================================= 76 + // AC2.3: Native client happy path 77 + // ============================================================================= 78 + 79 + #[tokio::test] 80 + async fn native_happy() { 81 + let http = common::FakeHttpClient::new(); 82 + let metadata = include_bytes!("fixtures/oauth_client/metadata/native_happy/metadata.json"); 83 + http.add_response( 84 + &Url::parse("https://app.example.com/oauth-client-metadata.json").unwrap(), 85 + 200, 86 + metadata.to_vec(), 87 + ); 88 + 89 + let target = 90 + parse_target("https://app.example.com/oauth-client-metadata.json").expect("parse failed"); 91 + let opts = OauthClientOptions { 92 + http: &http, 93 + verbose: false, 94 + }; 95 + 96 + let report = run_pipeline(target, opts).await; 97 + assert_eq!(report.exit_code(), 0, "Expected all checks to pass"); 98 + 99 + let rendered = render_report_to_string(&report); 100 + insta::assert_snapshot!(rendered); 101 + } 102 + 103 + // ============================================================================= 104 + // AC2.4: DPoP bound required 105 + // ============================================================================= 106 + 107 + #[tokio::test] 108 + async fn dpop_bound_false() { 109 + let http = common::FakeHttpClient::new(); 110 + let metadata = include_bytes!("fixtures/oauth_client/metadata/dpop_bound_false/metadata.json"); 111 + http.add_response( 112 + &Url::parse("https://client.example.com/metadata.json").unwrap(), 113 + 200, 114 + metadata.to_vec(), 115 + ); 116 + 117 + let target = parse_target("https://client.example.com/metadata.json").expect("parse failed"); 118 + let opts = OauthClientOptions { 119 + http: &http, 120 + verbose: false, 121 + }; 122 + 123 + let report = run_pipeline(target, opts).await; 124 + assert_eq!(report.exit_code(), 1, "Expected SpecViolation exit code"); 125 + 126 + let rendered = render_report_to_string(&report); 127 + insta::assert_snapshot!(rendered); 128 + } 129 + 130 + // ============================================================================= 131 + // AC2.5: Confidential requires JWKS 132 + // ============================================================================= 133 + 134 + #[tokio::test] 135 + async fn confidential_missing_jwks() { 136 + let http = common::FakeHttpClient::new(); 137 + let metadata = 138 + include_bytes!("fixtures/oauth_client/metadata/confidential_missing_jwks/metadata.json"); 139 + http.add_response( 140 + &Url::parse("https://client.example.com/metadata.json").unwrap(), 141 + 200, 142 + metadata.to_vec(), 143 + ); 144 + 145 + let target = parse_target("https://client.example.com/metadata.json").expect("parse failed"); 146 + let opts = OauthClientOptions { 147 + http: &http, 148 + verbose: false, 149 + }; 150 + 151 + let report = run_pipeline(target, opts).await; 152 + assert_eq!(report.exit_code(), 1, "Expected SpecViolation exit code"); 153 + 154 + let rendered = render_report_to_string(&report); 155 + insta::assert_snapshot!(rendered); 156 + } 157 + 158 + // ============================================================================= 159 + // AC2.6: Public client forbids token endpoint auth method other than none 160 + // ============================================================================= 161 + 162 + #[tokio::test] 163 + async fn public_with_token_endpoint_auth() { 164 + let http = common::FakeHttpClient::new(); 165 + let metadata = include_bytes!( 166 + "fixtures/oauth_client/metadata/public_with_token_endpoint_auth/metadata.json" 167 + ); 168 + http.add_response( 169 + &Url::parse("https://client.example.com/metadata.json").unwrap(), 170 + 200, 171 + metadata.to_vec(), 172 + ); 173 + 174 + let target = parse_target("https://client.example.com/metadata.json").expect("parse failed"); 175 + let opts = OauthClientOptions { 176 + http: &http, 177 + verbose: false, 178 + }; 179 + 180 + let report = run_pipeline(target, opts).await; 181 + assert_eq!(report.exit_code(), 1, "Expected SpecViolation exit code"); 182 + 183 + let rendered = render_report_to_string(&report); 184 + insta::assert_snapshot!(rendered); 185 + } 186 + 187 + // ============================================================================= 188 + // AC2.7: Native redirect scheme must match reverse-domain of client_id 189 + // ============================================================================= 190 + 191 + #[tokio::test] 192 + async fn native_redirect_scheme_mismatch() { 193 + let http = common::FakeHttpClient::new(); 194 + let metadata = include_bytes!( 195 + "fixtures/oauth_client/metadata/native_redirect_scheme_mismatch/metadata.json" 196 + ); 197 + http.add_response( 198 + &Url::parse("https://app.example.com/oauth-client-metadata.json").unwrap(), 199 + 200, 200 + metadata.to_vec(), 201 + ); 202 + 203 + let target = 204 + parse_target("https://app.example.com/oauth-client-metadata.json").expect("parse failed"); 205 + let opts = OauthClientOptions { 206 + http: &http, 207 + verbose: false, 208 + }; 209 + 210 + let report = run_pipeline(target, opts).await; 211 + assert_eq!(report.exit_code(), 1, "Expected SpecViolation exit code"); 212 + 213 + let rendered = render_report_to_string(&report); 214 + insta::assert_snapshot!(rendered); 215 + } 216 + 217 + // ============================================================================= 218 + // AC2.8: Scope grammar invalid 219 + // ============================================================================= 220 + 221 + #[tokio::test] 222 + async fn scope_grammar_invalid() { 223 + let http = common::FakeHttpClient::new(); 224 + let metadata = 225 + include_bytes!("fixtures/oauth_client/metadata/scope_grammar_invalid/metadata.json"); 226 + http.add_response( 227 + &Url::parse("https://client.example.com/metadata.json").unwrap(), 228 + 200, 229 + metadata.to_vec(), 230 + ); 231 + 232 + let target = parse_target("https://client.example.com/metadata.json").expect("parse failed"); 233 + let opts = OauthClientOptions { 234 + http: &http, 235 + verbose: false, 236 + }; 237 + 238 + let report = run_pipeline(target, opts).await; 239 + assert_eq!(report.exit_code(), 1, "Expected SpecViolation exit code"); 240 + 241 + let rendered = render_report_to_string(&report); 242 + insta::assert_snapshot!(rendered); 243 + } 244 + 245 + // ============================================================================= 246 + // AC2.9: Loopback skips all metadata checks 247 + // ============================================================================= 248 + 249 + #[tokio::test] 250 + async fn loopback_skips_all_metadata_checks() { 251 + let http = common::FakeHttpClient::new(); 252 + // No seeded responses needed for loopback 253 + 254 + let target = parse_target("http://localhost/").expect("parse failed"); 255 + let opts = OauthClientOptions { 256 + http: &http, 257 + verbose: false, 258 + }; 259 + 260 + let report = run_pipeline(target, opts).await; 261 + assert_eq!(report.exit_code(), 0, "Expected all checks to pass or skip"); 262 + 263 + let rendered = render_report_to_string(&report); 264 + insta::assert_snapshot!(rendered); 265 + } 266 + 267 + // ============================================================================= 268 + // Additional test: Discovery failure blocks metadata 269 + // ============================================================================= 270 + 271 + #[tokio::test] 272 + async fn discovery_failure_blocks_metadata() { 273 + let http = common::FakeHttpClient::new(); 274 + // Seed a 404 response 275 + http.add_response( 276 + &Url::parse("https://client.example.com/metadata.json").unwrap(), 277 + 404, 278 + b"not found".to_vec(), 279 + ); 280 + 281 + let target = parse_target("https://client.example.com/metadata.json").expect("parse failed"); 282 + let opts = OauthClientOptions { 283 + http: &http, 284 + verbose: false, 285 + }; 286 + 287 + let report = run_pipeline(target, opts).await; 288 + // 404 is a network error (exit code 2), not a spec violation (exit code 1). 289 + assert_eq!(report.exit_code(), 2, "Expected network error exit code"); 290 + 291 + let rendered = render_report_to_string(&report); 292 + insta::assert_snapshot!(rendered); 293 + }
+17 -2
tests/snapshots/oauth_client_discovery__https_404_produces_network_error.snap
··· 1 1 --- 2 2 source: tests/oauth_client_discovery.rs 3 - assertion_line: 64 4 3 expression: rendered 5 4 --- 6 5 Target: https://client.example.com/missing.json ··· 13 12 14 13 × Metadata document fetch returned HTTP 404: https://client.example.com/missing.json 15 14 [SKIP] Metadata is valid JSON — blocked by oauth_client::discovery::metadata_document_fetchable 15 + == Metadata == 16 + [SKIP] Metadata document deserializes — blocked by oauth_client::discovery::metadata_document_fetchable 17 + [SKIP] Metadata `client_id` matches fetched URL — blocked by oauth_client::discovery::metadata_document_fetchable 18 + [SKIP] `application_type` field is present — blocked by oauth_client::discovery::metadata_document_fetchable 19 + [SKIP] `application_type` is `web` or `native` — blocked by oauth_client::discovery::metadata_document_fetchable 20 + [SKIP] `response_types` is `["code"]` — blocked by oauth_client::discovery::metadata_document_fetchable 21 + [SKIP] `grant_types` includes `authorization_code` — blocked by oauth_client::discovery::metadata_document_fetchable 22 + [SKIP] `dpop_bound_access_tokens` is `true` — blocked by oauth_client::discovery::metadata_document_fetchable 23 + [SKIP] `redirect_uris` is non-empty — blocked by oauth_client::discovery::metadata_document_fetchable 24 + [SKIP] Every `redirect_uri` has the right shape for the client kind — blocked by oauth_client::discovery::metadata_document_fetchable 25 + [SKIP] `token_endpoint_auth_method` matches client kind — blocked by oauth_client::discovery::metadata_document_fetchable 26 + [SKIP] Confidential client provides exactly one of `jwks`/`jwks_uri` — blocked by oauth_client::discovery::metadata_document_fetchable 27 + [SKIP] Public/native client does not provide `jwks` or `jwks_uri` — blocked by oauth_client::discovery::metadata_document_fetchable 28 + [SKIP] `scope` field is present — blocked by oauth_client::discovery::metadata_document_fetchable 29 + [SKIP] `scope` includes the `atproto` token — blocked by oauth_client::discovery::metadata_document_fetchable 30 + [SKIP] `scope` parses against the atproto permission grammar — blocked by oauth_client::discovery::metadata_document_fetchable 16 31 17 - Summary: 1 passed, 0 failed (spec), 1 network errors, 0 advisories, 1 skipped. Exit code: 2 32 + Summary: 1 passed, 0 failed (spec), 1 network errors, 0 advisories, 16 skipped. Exit code: 2
+16 -1
tests/snapshots/oauth_client_discovery__https_confidential_happy_discovery.snap
··· 9 9 [OK] Client ID well-formed 10 10 [OK] Metadata document fetchable 11 11 [OK] Metadata is valid JSON 12 + == Metadata == 13 + [OK] Metadata document deserializes 14 + [OK] Metadata `client_id` matches fetched URL 15 + [OK] `application_type` field is present 16 + [OK] `application_type` is `web` or `native` 17 + [OK] `response_types` is `["code"]` 18 + [OK] `grant_types` includes `authorization_code` 19 + [OK] `dpop_bound_access_tokens` is `true` 20 + [OK] `redirect_uris` is non-empty 21 + [OK] Every `redirect_uri` has the right shape for the client kind 22 + [OK] `token_endpoint_auth_method` matches client kind 23 + [OK] Confidential client provides exactly one of `jwks`/`jwks_uri` 24 + [OK] `scope` field is present 25 + [OK] `scope` parses against the atproto permission grammar 26 + [OK] `scope` includes the `atproto` token 12 27 13 - Summary: 3 passed, 0 failed (spec), 0 network errors, 0 advisories, 0 skipped. Exit code: 0 28 + Summary: 17 passed, 0 failed (spec), 0 network errors, 0 advisories, 0 skipped. Exit code: 0
+17 -2
tests/snapshots/oauth_client_discovery__https_not_json_produces_spec_violation.snap
··· 1 1 --- 2 2 source: tests/oauth_client_discovery.rs 3 - assertion_line: 106 4 3 expression: rendered 5 4 --- 6 5 Target: https://client.example.com/metadata.json ··· 17 16 1 │ not valid json at all 18 17 · ─ 19 18 ╰──── 19 + == Metadata == 20 + [SKIP] Metadata document deserializes — blocked by oauth_client::discovery::metadata_document_fetchable 21 + [SKIP] Metadata `client_id` matches fetched URL — blocked by oauth_client::discovery::metadata_document_fetchable 22 + [SKIP] `application_type` field is present — blocked by oauth_client::discovery::metadata_document_fetchable 23 + [SKIP] `application_type` is `web` or `native` — blocked by oauth_client::discovery::metadata_document_fetchable 24 + [SKIP] `response_types` is `["code"]` — blocked by oauth_client::discovery::metadata_document_fetchable 25 + [SKIP] `grant_types` includes `authorization_code` — blocked by oauth_client::discovery::metadata_document_fetchable 26 + [SKIP] `dpop_bound_access_tokens` is `true` — blocked by oauth_client::discovery::metadata_document_fetchable 27 + [SKIP] `redirect_uris` is non-empty — blocked by oauth_client::discovery::metadata_document_fetchable 28 + [SKIP] Every `redirect_uri` has the right shape for the client kind — blocked by oauth_client::discovery::metadata_document_fetchable 29 + [SKIP] `token_endpoint_auth_method` matches client kind — blocked by oauth_client::discovery::metadata_document_fetchable 30 + [SKIP] Confidential client provides exactly one of `jwks`/`jwks_uri` — blocked by oauth_client::discovery::metadata_document_fetchable 31 + [SKIP] Public/native client does not provide `jwks` or `jwks_uri` — blocked by oauth_client::discovery::metadata_document_fetchable 32 + [SKIP] `scope` field is present — blocked by oauth_client::discovery::metadata_document_fetchable 33 + [SKIP] `scope` includes the `atproto` token — blocked by oauth_client::discovery::metadata_document_fetchable 34 + [SKIP] `scope` parses against the atproto permission grammar — blocked by oauth_client::discovery::metadata_document_fetchable 20 35 21 - Summary: 2 passed, 1 failed (spec), 0 network errors, 0 advisories, 0 skipped. Exit code: 1 36 + Summary: 2 passed, 1 failed (spec), 0 network errors, 0 advisories, 15 skipped. Exit code: 1
+17 -2
tests/snapshots/oauth_client_discovery__https_not_json_with_content_type_produces_spec_violation_with_ct.snap
··· 1 1 --- 2 2 source: tests/oauth_client_discovery.rs 3 - assertion_line: 131 4 3 expression: rendered 5 4 --- 6 5 Target: https://client.example.com/metadata.json ··· 17 16 1 │ not valid json at all 18 17 · ─ 19 18 ╰──── 19 + == Metadata == 20 + [SKIP] Metadata document deserializes — blocked by oauth_client::discovery::metadata_document_fetchable 21 + [SKIP] Metadata `client_id` matches fetched URL — blocked by oauth_client::discovery::metadata_document_fetchable 22 + [SKIP] `application_type` field is present — blocked by oauth_client::discovery::metadata_document_fetchable 23 + [SKIP] `application_type` is `web` or `native` — blocked by oauth_client::discovery::metadata_document_fetchable 24 + [SKIP] `response_types` is `["code"]` — blocked by oauth_client::discovery::metadata_document_fetchable 25 + [SKIP] `grant_types` includes `authorization_code` — blocked by oauth_client::discovery::metadata_document_fetchable 26 + [SKIP] `dpop_bound_access_tokens` is `true` — blocked by oauth_client::discovery::metadata_document_fetchable 27 + [SKIP] `redirect_uris` is non-empty — blocked by oauth_client::discovery::metadata_document_fetchable 28 + [SKIP] Every `redirect_uri` has the right shape for the client kind — blocked by oauth_client::discovery::metadata_document_fetchable 29 + [SKIP] `token_endpoint_auth_method` matches client kind — blocked by oauth_client::discovery::metadata_document_fetchable 30 + [SKIP] Confidential client provides exactly one of `jwks`/`jwks_uri` — blocked by oauth_client::discovery::metadata_document_fetchable 31 + [SKIP] Public/native client does not provide `jwks` or `jwks_uri` — blocked by oauth_client::discovery::metadata_document_fetchable 32 + [SKIP] `scope` field is present — blocked by oauth_client::discovery::metadata_document_fetchable 33 + [SKIP] `scope` includes the `atproto` token — blocked by oauth_client::discovery::metadata_document_fetchable 34 + [SKIP] `scope` parses against the atproto permission grammar — blocked by oauth_client::discovery::metadata_document_fetchable 20 35 21 - Summary: 2 passed, 1 failed (spec), 0 network errors, 0 advisories, 0 skipped. Exit code: 1 36 + Summary: 2 passed, 1 failed (spec), 0 network errors, 0 advisories, 15 skipped. Exit code: 1
+17 -2
tests/snapshots/oauth_client_discovery__https_unreachable_produces_network_error.snap
··· 1 1 --- 2 2 source: tests/oauth_client_discovery.rs 3 - assertion_line: 83 4 3 expression: rendered 5 4 --- 6 5 Target: https://client.example.com/metadata.json ··· 13 12 14 13 × Failed to fetch metadata from https://client.example.com/metadata.json: connection refused 15 14 [SKIP] Metadata is valid JSON — blocked by oauth_client::discovery::metadata_document_fetchable 15 + == Metadata == 16 + [SKIP] Metadata document deserializes — blocked by oauth_client::discovery::metadata_document_fetchable 17 + [SKIP] Metadata `client_id` matches fetched URL — blocked by oauth_client::discovery::metadata_document_fetchable 18 + [SKIP] `application_type` field is present — blocked by oauth_client::discovery::metadata_document_fetchable 19 + [SKIP] `application_type` is `web` or `native` — blocked by oauth_client::discovery::metadata_document_fetchable 20 + [SKIP] `response_types` is `["code"]` — blocked by oauth_client::discovery::metadata_document_fetchable 21 + [SKIP] `grant_types` includes `authorization_code` — blocked by oauth_client::discovery::metadata_document_fetchable 22 + [SKIP] `dpop_bound_access_tokens` is `true` — blocked by oauth_client::discovery::metadata_document_fetchable 23 + [SKIP] `redirect_uris` is non-empty — blocked by oauth_client::discovery::metadata_document_fetchable 24 + [SKIP] Every `redirect_uri` has the right shape for the client kind — blocked by oauth_client::discovery::metadata_document_fetchable 25 + [SKIP] `token_endpoint_auth_method` matches client kind — blocked by oauth_client::discovery::metadata_document_fetchable 26 + [SKIP] Confidential client provides exactly one of `jwks`/`jwks_uri` — blocked by oauth_client::discovery::metadata_document_fetchable 27 + [SKIP] Public/native client does not provide `jwks` or `jwks_uri` — blocked by oauth_client::discovery::metadata_document_fetchable 28 + [SKIP] `scope` field is present — blocked by oauth_client::discovery::metadata_document_fetchable 29 + [SKIP] `scope` includes the `atproto` token — blocked by oauth_client::discovery::metadata_document_fetchable 30 + [SKIP] `scope` parses against the atproto permission grammar — blocked by oauth_client::discovery::metadata_document_fetchable 16 31 17 - Summary: 1 passed, 0 failed (spec), 1 network errors, 0 advisories, 1 skipped. Exit code: 2 32 + Summary: 1 passed, 0 failed (spec), 1 network errors, 0 advisories, 16 skipped. Exit code: 2
+17 -1
tests/snapshots/oauth_client_discovery__loopback_127_0_0_1.snap
··· 9 9 [OK] Client ID well-formed 10 10 [SKIP] Metadata document fetchable — metadata is implicit for loopback clients 11 11 [SKIP] Metadata is valid JSON — metadata is implicit for loopback clients 12 + == Metadata == 13 + [SKIP] Metadata document deserializes — metadata is implicit for loopback clients 14 + [SKIP] Metadata `client_id` matches fetched URL — metadata is implicit for loopback clients 15 + [SKIP] `application_type` field is present — metadata is implicit for loopback clients 16 + [SKIP] `application_type` is `web` or `native` — metadata is implicit for loopback clients 17 + [SKIP] `response_types` is `["code"]` — metadata is implicit for loopback clients 18 + [SKIP] `grant_types` includes `authorization_code` — metadata is implicit for loopback clients 19 + [SKIP] `dpop_bound_access_tokens` is `true` — metadata is implicit for loopback clients 20 + [SKIP] `redirect_uris` is non-empty — metadata is implicit for loopback clients 21 + [SKIP] Every `redirect_uri` has the right shape for the client kind — metadata is implicit for loopback clients 22 + [SKIP] `token_endpoint_auth_method` matches client kind — metadata is implicit for loopback clients 23 + [SKIP] Confidential client provides exactly one of `jwks`/`jwks_uri` — metadata is implicit for loopback clients 24 + [SKIP] Public/native client does not provide `jwks` or `jwks_uri` — metadata is implicit for loopback clients 25 + [SKIP] `scope` field is present — metadata is implicit for loopback clients 26 + [SKIP] `scope` includes the `atproto` token — metadata is implicit for loopback clients 27 + [SKIP] `scope` parses against the atproto permission grammar — metadata is implicit for loopback clients 12 28 13 - Summary: 1 passed, 0 failed (spec), 0 network errors, 0 advisories, 2 skipped. Exit code: 0 29 + Summary: 1 passed, 0 failed (spec), 0 network errors, 0 advisories, 17 skipped. Exit code: 0
+17 -1
tests/snapshots/oauth_client_discovery__loopback_root_produces_skip_rows.snap
··· 9 9 [OK] Client ID well-formed 10 10 [SKIP] Metadata document fetchable — metadata is implicit for loopback clients 11 11 [SKIP] Metadata is valid JSON — metadata is implicit for loopback clients 12 + == Metadata == 13 + [SKIP] Metadata document deserializes — metadata is implicit for loopback clients 14 + [SKIP] Metadata `client_id` matches fetched URL — metadata is implicit for loopback clients 15 + [SKIP] `application_type` field is present — metadata is implicit for loopback clients 16 + [SKIP] `application_type` is `web` or `native` — metadata is implicit for loopback clients 17 + [SKIP] `response_types` is `["code"]` — metadata is implicit for loopback clients 18 + [SKIP] `grant_types` includes `authorization_code` — metadata is implicit for loopback clients 19 + [SKIP] `dpop_bound_access_tokens` is `true` — metadata is implicit for loopback clients 20 + [SKIP] `redirect_uris` is non-empty — metadata is implicit for loopback clients 21 + [SKIP] Every `redirect_uri` has the right shape for the client kind — metadata is implicit for loopback clients 22 + [SKIP] `token_endpoint_auth_method` matches client kind — metadata is implicit for loopback clients 23 + [SKIP] Confidential client provides exactly one of `jwks`/`jwks_uri` — metadata is implicit for loopback clients 24 + [SKIP] Public/native client does not provide `jwks` or `jwks_uri` — metadata is implicit for loopback clients 25 + [SKIP] `scope` field is present — metadata is implicit for loopback clients 26 + [SKIP] `scope` includes the `atproto` token — metadata is implicit for loopback clients 27 + [SKIP] `scope` parses against the atproto permission grammar — metadata is implicit for loopback clients 12 28 13 - Summary: 1 passed, 0 failed (spec), 0 network errors, 0 advisories, 2 skipped. Exit code: 0 29 + Summary: 1 passed, 0 failed (spec), 0 network errors, 0 advisories, 17 skipped. Exit code: 0
+17 -1
tests/snapshots/oauth_client_discovery__loopback_with_port_produces_same_skip_rows.snap
··· 9 9 [OK] Client ID well-formed 10 10 [SKIP] Metadata document fetchable — metadata is implicit for loopback clients 11 11 [SKIP] Metadata is valid JSON — metadata is implicit for loopback clients 12 + == Metadata == 13 + [SKIP] Metadata document deserializes — metadata is implicit for loopback clients 14 + [SKIP] Metadata `client_id` matches fetched URL — metadata is implicit for loopback clients 15 + [SKIP] `application_type` field is present — metadata is implicit for loopback clients 16 + [SKIP] `application_type` is `web` or `native` — metadata is implicit for loopback clients 17 + [SKIP] `response_types` is `["code"]` — metadata is implicit for loopback clients 18 + [SKIP] `grant_types` includes `authorization_code` — metadata is implicit for loopback clients 19 + [SKIP] `dpop_bound_access_tokens` is `true` — metadata is implicit for loopback clients 20 + [SKIP] `redirect_uris` is non-empty — metadata is implicit for loopback clients 21 + [SKIP] Every `redirect_uri` has the right shape for the client kind — metadata is implicit for loopback clients 22 + [SKIP] `token_endpoint_auth_method` matches client kind — metadata is implicit for loopback clients 23 + [SKIP] Confidential client provides exactly one of `jwks`/`jwks_uri` — metadata is implicit for loopback clients 24 + [SKIP] Public/native client does not provide `jwks` or `jwks_uri` — metadata is implicit for loopback clients 25 + [SKIP] `scope` field is present — metadata is implicit for loopback clients 26 + [SKIP] `scope` includes the `atproto` token — metadata is implicit for loopback clients 27 + [SKIP] `scope` parses against the atproto permission grammar — metadata is implicit for loopback clients 12 28 13 - Summary: 1 passed, 0 failed (spec), 0 network errors, 0 advisories, 2 skipped. Exit code: 0 29 + Summary: 1 passed, 0 failed (spec), 0 network errors, 0 advisories, 17 skipped. Exit code: 0
+28
tests/snapshots/oauth_client_metadata__confidential_happy.snap
··· 1 + --- 2 + source: tests/oauth_client_metadata.rs 3 + expression: rendered 4 + --- 5 + Target: https://client.example.com/metadata.json 6 + elapsed: 0ms 7 + 8 + == Discovery == 9 + [OK] Client ID well-formed 10 + [OK] Metadata document fetchable 11 + [OK] Metadata is valid JSON 12 + == Metadata == 13 + [OK] Metadata document deserializes 14 + [OK] Metadata `client_id` matches fetched URL 15 + [OK] `application_type` field is present 16 + [OK] `application_type` is `web` or `native` 17 + [OK] `response_types` is `["code"]` 18 + [OK] `grant_types` includes `authorization_code` 19 + [OK] `dpop_bound_access_tokens` is `true` 20 + [OK] `redirect_uris` is non-empty 21 + [OK] Every `redirect_uri` has the right shape for the client kind 22 + [OK] `token_endpoint_auth_method` matches client kind 23 + [OK] Confidential client provides exactly one of `jwks`/`jwks_uri` 24 + [OK] `scope` field is present 25 + [OK] `scope` parses against the atproto permission grammar 26 + [OK] `scope` includes the `atproto` token 27 + 28 + Summary: 17 passed, 0 failed (spec), 0 network errors, 0 advisories, 0 skipped. Exit code: 0
+28
tests/snapshots/oauth_client_metadata__confidential_missing_jwks.snap
··· 1 + --- 2 + source: tests/oauth_client_metadata.rs 3 + expression: rendered 4 + --- 5 + Target: https://client.example.com/metadata.json 6 + elapsed: 0ms 7 + 8 + == Discovery == 9 + [OK] Client ID well-formed 10 + [OK] Metadata document fetchable 11 + [OK] Metadata is valid JSON 12 + == Metadata == 13 + [OK] Metadata document deserializes 14 + [OK] Metadata `client_id` matches fetched URL 15 + [OK] `application_type` field is present 16 + [OK] `application_type` is `web` or `native` 17 + [OK] `response_types` is `["code"]` 18 + [OK] `grant_types` includes `authorization_code` 19 + [OK] `dpop_bound_access_tokens` is `true` 20 + [OK] `redirect_uris` is non-empty 21 + [OK] Every `redirect_uri` has the right shape for the client kind 22 + [OK] `token_endpoint_auth_method` matches client kind 23 + [FAIL] Confidential client provides exactly one of `jwks`/`jwks_uri` 24 + [OK] `scope` field is present 25 + [OK] `scope` parses against the atproto permission grammar 26 + [OK] `scope` includes the `atproto` token 27 + 28 + Summary: 16 passed, 1 failed (spec), 0 network errors, 0 advisories, 0 skipped. Exit code: 1
+32
tests/snapshots/oauth_client_metadata__discovery_failure_blocks_metadata.snap
··· 1 + --- 2 + source: tests/oauth_client_metadata.rs 3 + expression: rendered 4 + --- 5 + Target: https://client.example.com/metadata.json 6 + elapsed: 0ms 7 + 8 + == Discovery == 9 + [OK] Client ID well-formed 10 + [NET] Metadata document unreachable 11 + oauth_client::discovery::metadata_document_fetchable 12 + 13 + × Metadata document fetch returned HTTP 404: https://client.example.com/metadata.json 14 + [SKIP] Metadata is valid JSON — blocked by oauth_client::discovery::metadata_document_fetchable 15 + == Metadata == 16 + [SKIP] Metadata document deserializes — blocked by oauth_client::discovery::metadata_document_fetchable 17 + [SKIP] Metadata `client_id` matches fetched URL — blocked by oauth_client::discovery::metadata_document_fetchable 18 + [SKIP] `application_type` field is present — blocked by oauth_client::discovery::metadata_document_fetchable 19 + [SKIP] `application_type` is `web` or `native` — blocked by oauth_client::discovery::metadata_document_fetchable 20 + [SKIP] `response_types` is `["code"]` — blocked by oauth_client::discovery::metadata_document_fetchable 21 + [SKIP] `grant_types` includes `authorization_code` — blocked by oauth_client::discovery::metadata_document_fetchable 22 + [SKIP] `dpop_bound_access_tokens` is `true` — blocked by oauth_client::discovery::metadata_document_fetchable 23 + [SKIP] `redirect_uris` is non-empty — blocked by oauth_client::discovery::metadata_document_fetchable 24 + [SKIP] Every `redirect_uri` has the right shape for the client kind — blocked by oauth_client::discovery::metadata_document_fetchable 25 + [SKIP] `token_endpoint_auth_method` matches client kind — blocked by oauth_client::discovery::metadata_document_fetchable 26 + [SKIP] Confidential client provides exactly one of `jwks`/`jwks_uri` — blocked by oauth_client::discovery::metadata_document_fetchable 27 + [SKIP] Public/native client does not provide `jwks` or `jwks_uri` — blocked by oauth_client::discovery::metadata_document_fetchable 28 + [SKIP] `scope` field is present — blocked by oauth_client::discovery::metadata_document_fetchable 29 + [SKIP] `scope` includes the `atproto` token — blocked by oauth_client::discovery::metadata_document_fetchable 30 + [SKIP] `scope` parses against the atproto permission grammar — blocked by oauth_client::discovery::metadata_document_fetchable 31 + 32 + Summary: 1 passed, 0 failed (spec), 1 network errors, 0 advisories, 16 skipped. Exit code: 2
+28
tests/snapshots/oauth_client_metadata__dpop_bound_false.snap
··· 1 + --- 2 + source: tests/oauth_client_metadata.rs 3 + expression: rendered 4 + --- 5 + Target: https://client.example.com/metadata.json 6 + elapsed: 0ms 7 + 8 + == Discovery == 9 + [OK] Client ID well-formed 10 + [OK] Metadata document fetchable 11 + [OK] Metadata is valid JSON 12 + == Metadata == 13 + [OK] Metadata document deserializes 14 + [OK] Metadata `client_id` matches fetched URL 15 + [OK] `application_type` field is present 16 + [OK] `application_type` is `web` or `native` 17 + [OK] `response_types` is `["code"]` 18 + [OK] `grant_types` includes `authorization_code` 19 + [FAIL] `dpop_bound_access_tokens` is `true` 20 + [OK] `redirect_uris` is non-empty 21 + [OK] Every `redirect_uri` has the right shape for the client kind 22 + [OK] `token_endpoint_auth_method` matches client kind 23 + [OK] Confidential client provides exactly one of `jwks`/`jwks_uri` 24 + [OK] `scope` field is present 25 + [OK] `scope` parses against the atproto permission grammar 26 + [OK] `scope` includes the `atproto` token 27 + 28 + Summary: 16 passed, 1 failed (spec), 0 network errors, 0 advisories, 0 skipped. Exit code: 1
+29
tests/snapshots/oauth_client_metadata__loopback_skips_all_metadata_checks.snap
··· 1 + --- 2 + source: tests/oauth_client_metadata.rs 3 + expression: rendered 4 + --- 5 + Target: http://localhost/ 6 + elapsed: 0ms 7 + 8 + == Discovery == 9 + [OK] Client ID well-formed 10 + [SKIP] Metadata document fetchable — metadata is implicit for loopback clients 11 + [SKIP] Metadata is valid JSON — metadata is implicit for loopback clients 12 + == Metadata == 13 + [SKIP] Metadata document deserializes — metadata is implicit for loopback clients 14 + [SKIP] Metadata `client_id` matches fetched URL — metadata is implicit for loopback clients 15 + [SKIP] `application_type` field is present — metadata is implicit for loopback clients 16 + [SKIP] `application_type` is `web` or `native` — metadata is implicit for loopback clients 17 + [SKIP] `response_types` is `["code"]` — metadata is implicit for loopback clients 18 + [SKIP] `grant_types` includes `authorization_code` — metadata is implicit for loopback clients 19 + [SKIP] `dpop_bound_access_tokens` is `true` — metadata is implicit for loopback clients 20 + [SKIP] `redirect_uris` is non-empty — metadata is implicit for loopback clients 21 + [SKIP] Every `redirect_uri` has the right shape for the client kind — metadata is implicit for loopback clients 22 + [SKIP] `token_endpoint_auth_method` matches client kind — metadata is implicit for loopback clients 23 + [SKIP] Confidential client provides exactly one of `jwks`/`jwks_uri` — metadata is implicit for loopback clients 24 + [SKIP] Public/native client does not provide `jwks` or `jwks_uri` — metadata is implicit for loopback clients 25 + [SKIP] `scope` field is present — metadata is implicit for loopback clients 26 + [SKIP] `scope` includes the `atproto` token — metadata is implicit for loopback clients 27 + [SKIP] `scope` parses against the atproto permission grammar — metadata is implicit for loopback clients 28 + 29 + Summary: 1 passed, 0 failed (spec), 0 network errors, 0 advisories, 17 skipped. Exit code: 0
+28
tests/snapshots/oauth_client_metadata__native_happy.snap
··· 1 + --- 2 + source: tests/oauth_client_metadata.rs 3 + expression: rendered 4 + --- 5 + Target: https://app.example.com/oauth-client-metadata.json 6 + elapsed: 0ms 7 + 8 + == Discovery == 9 + [OK] Client ID well-formed 10 + [OK] Metadata document fetchable 11 + [OK] Metadata is valid JSON 12 + == Metadata == 13 + [OK] Metadata document deserializes 14 + [OK] Metadata `client_id` matches fetched URL 15 + [OK] `application_type` field is present 16 + [OK] `application_type` is `web` or `native` 17 + [OK] `response_types` is `["code"]` 18 + [OK] `grant_types` includes `authorization_code` 19 + [OK] `dpop_bound_access_tokens` is `true` 20 + [OK] `redirect_uris` is non-empty 21 + [OK] Every `redirect_uri` has the right shape for the client kind 22 + [OK] `token_endpoint_auth_method` matches client kind 23 + [OK] Public/native client does not provide `jwks` or `jwks_uri` 24 + [OK] `scope` field is present 25 + [OK] `scope` parses against the atproto permission grammar 26 + [OK] `scope` includes the `atproto` token 27 + 28 + Summary: 17 passed, 0 failed (spec), 0 network errors, 0 advisories, 0 skipped. Exit code: 0
+28
tests/snapshots/oauth_client_metadata__native_redirect_scheme_mismatch.snap
··· 1 + --- 2 + source: tests/oauth_client_metadata.rs 3 + expression: rendered 4 + --- 5 + Target: https://app.example.com/oauth-client-metadata.json 6 + elapsed: 0ms 7 + 8 + == Discovery == 9 + [OK] Client ID well-formed 10 + [OK] Metadata document fetchable 11 + [OK] Metadata is valid JSON 12 + == Metadata == 13 + [OK] Metadata document deserializes 14 + [OK] Metadata `client_id` matches fetched URL 15 + [OK] `application_type` field is present 16 + [OK] `application_type` is `web` or `native` 17 + [OK] `response_types` is `["code"]` 18 + [OK] `grant_types` includes `authorization_code` 19 + [OK] `dpop_bound_access_tokens` is `true` 20 + [OK] `redirect_uris` is non-empty 21 + [FAIL] Every `redirect_uri` has the right shape for the client kind 22 + [OK] `token_endpoint_auth_method` matches client kind 23 + [OK] Public/native client does not provide `jwks` or `jwks_uri` 24 + [OK] `scope` field is present 25 + [OK] `scope` parses against the atproto permission grammar 26 + [OK] `scope` includes the `atproto` token 27 + 28 + Summary: 16 passed, 1 failed (spec), 0 network errors, 0 advisories, 0 skipped. Exit code: 1
+28
tests/snapshots/oauth_client_metadata__public_happy.snap
··· 1 + --- 2 + source: tests/oauth_client_metadata.rs 3 + expression: rendered 4 + --- 5 + Target: https://client.example.com/metadata.json 6 + elapsed: 0ms 7 + 8 + == Discovery == 9 + [OK] Client ID well-formed 10 + [OK] Metadata document fetchable 11 + [OK] Metadata is valid JSON 12 + == Metadata == 13 + [OK] Metadata document deserializes 14 + [OK] Metadata `client_id` matches fetched URL 15 + [OK] `application_type` field is present 16 + [OK] `application_type` is `web` or `native` 17 + [OK] `response_types` is `["code"]` 18 + [OK] `grant_types` includes `authorization_code` 19 + [OK] `dpop_bound_access_tokens` is `true` 20 + [OK] `redirect_uris` is non-empty 21 + [OK] Every `redirect_uri` has the right shape for the client kind 22 + [OK] `token_endpoint_auth_method` matches client kind 23 + [OK] Public/native client does not provide `jwks` or `jwks_uri` 24 + [OK] `scope` field is present 25 + [OK] `scope` parses against the atproto permission grammar 26 + [OK] `scope` includes the `atproto` token 27 + 28 + Summary: 17 passed, 0 failed (spec), 0 network errors, 0 advisories, 0 skipped. Exit code: 0
+28
tests/snapshots/oauth_client_metadata__public_with_token_endpoint_auth.snap
··· 1 + --- 2 + source: tests/oauth_client_metadata.rs 3 + expression: rendered 4 + --- 5 + Target: https://client.example.com/metadata.json 6 + elapsed: 0ms 7 + 8 + == Discovery == 9 + [OK] Client ID well-formed 10 + [OK] Metadata document fetchable 11 + [OK] Metadata is valid JSON 12 + == Metadata == 13 + [OK] Metadata document deserializes 14 + [OK] Metadata `client_id` matches fetched URL 15 + [OK] `application_type` field is present 16 + [OK] `application_type` is `web` or `native` 17 + [OK] `response_types` is `["code"]` 18 + [OK] `grant_types` includes `authorization_code` 19 + [OK] `dpop_bound_access_tokens` is `true` 20 + [OK] `redirect_uris` is non-empty 21 + [OK] Every `redirect_uri` has the right shape for the client kind 22 + [FAIL] `token_endpoint_auth_method` matches client kind 23 + [OK] Public/native client does not provide `jwks` or `jwks_uri` 24 + [OK] `scope` field is present 25 + [OK] `scope` parses against the atproto permission grammar 26 + [OK] `scope` includes the `atproto` token 27 + 28 + Summary: 16 passed, 1 failed (spec), 0 network errors, 0 advisories, 0 skipped. Exit code: 1
+38
tests/snapshots/oauth_client_metadata__scope_grammar_invalid.snap
··· 1 + --- 2 + source: tests/oauth_client_metadata.rs 3 + expression: rendered 4 + --- 5 + Target: https://client.example.com/metadata.json 6 + elapsed: 0ms 7 + 8 + == Discovery == 9 + [OK] Client ID well-formed 10 + [OK] Metadata document fetchable 11 + [OK] Metadata is valid JSON 12 + == Metadata == 13 + [OK] Metadata document deserializes 14 + [OK] Metadata `client_id` matches fetched URL 15 + [OK] `application_type` field is present 16 + [OK] `application_type` is `web` or `native` 17 + [OK] `response_types` is `["code"]` 18 + [OK] `grant_types` includes `authorization_code` 19 + [OK] `dpop_bound_access_tokens` is `true` 20 + [OK] `redirect_uris` is non-empty 21 + [OK] Every `redirect_uri` has the right shape for the client kind 22 + [OK] `token_endpoint_auth_method` matches client kind 23 + [OK] Confidential client provides exactly one of `jwks`/`jwks_uri` 24 + [OK] `scope` field is present 25 + [FAIL] `scope` parses against the atproto permission grammar 26 + oauth_client::metadata::scope_grammar 27 + 28 + × scope grammar error: unknown resource name 29 + ╭─[metadata document:27:21] 30 + 26 │ ], 31 + 27 │ "scope": "atproto invalid-scope-token", 32 + · ─────────┬───────── 33 + · ╰── invalid token 34 + 28 │ "token_endpoint_auth_method": "private_key_jwt" 35 + ╰──── 36 + [SKIP] `scope` includes the `atproto` token — blocked by oauth_client::metadata::scope_grammar 37 + 38 + Summary: 15 passed, 1 failed (spec), 0 network errors, 0 advisories, 1 skipped. Exit code: 1