// // ClientMetadataTests.swift // CoreATProtocol // // Created by Claude on 2026-01-02. // import Testing import Foundation @testable import CoreATProtocol @Suite("Client Metadata Tests") struct ClientMetadataTests { @Test("Creates valid public client metadata") func testPublicClientMetadata() throws { let metadata = ATClientMetadata( clientId: "https://example.com/client-metadata.json", redirectUri: "com.example.app://oauth/callback", clientName: "My AT Proto App" ) #expect(metadata.clientId == "https://example.com/client-metadata.json") #expect(metadata.applicationType == .native) #expect(metadata.tokenEndpointAuthMethod == "none") #expect(metadata.dpopBoundAccessTokens == true) #expect(metadata.grantTypes.contains("authorization_code")) #expect(metadata.grantTypes.contains("refresh_token")) #expect(metadata.responseTypes.contains("code")) #expect(metadata.scope.contains("atproto")) } @Test("Creates valid confidential client metadata") func testConfidentialClientMetadata() throws { let metadata = ATClientMetadata( clientId: "https://webapp.example.com/client-metadata.json", redirectUri: "https://webapp.example.com/oauth/callback", clientName: "My Web App", jwksUri: "https://webapp.example.com/.well-known/jwks.json" ) #expect(metadata.applicationType == .web) #expect(metadata.tokenEndpointAuthMethod == "private_key_jwt") #expect(metadata.jwksUri == "https://webapp.example.com/.well-known/jwks.json") } @Test("Metadata validates HTTPS requirement") func testHTTPSValidation() { let invalidMetadata = ATClientMetadata( clientId: "http://insecure.example.com/metadata.json", redirectUri: "com.example.app://callback", clientName: "Insecure App" ) #expect(throws: OAuthError.self) { try invalidMetadata.validate() } } @Test("Metadata allows localhost for development") func testLocalhostAllowed() throws { let metadata = ATClientMetadata( clientId: "http://localhost/client-metadata.json", redirectUri: "http://127.0.0.1/callback", clientName: "Dev App" ) // Should not throw try metadata.validate() } @Test("Metadata validates atproto scope requirement") func testScopeValidation() { // Create metadata with custom scope missing atproto let json = """ { "client_id": "https://example.com/metadata.json", "application_type": "native", "grant_types": ["authorization_code", "refresh_token"], "scope": "openid profile", "response_types": ["code"], "redirect_uris": ["com.example://callback"], "dpop_bound_access_tokens": true, "token_endpoint_auth_method": "none" } """.data(using: .utf8)! do { let metadata = try JSONDecoder().decode(ATClientMetadata.self, from: json) #expect(throws: OAuthError.self) { try metadata.validate() } } catch { Issue.record("Failed to decode metadata: \(error)") } } @Test("Metadata validates DPoP requirement") func testDPoPValidation() { let json = """ { "client_id": "https://example.com/metadata.json", "application_type": "native", "grant_types": ["authorization_code", "refresh_token"], "scope": "atproto", "response_types": ["code"], "redirect_uris": ["com.example://callback"], "dpop_bound_access_tokens": false, "token_endpoint_auth_method": "none" } """.data(using: .utf8)! do { let metadata = try JSONDecoder().decode(ATClientMetadata.self, from: json) #expect(throws: OAuthError.self) { try metadata.validate() } } catch { Issue.record("Failed to decode metadata: \(error)") } } @Test("Metadata encodes to valid JSON") func testJSONEncoding() throws { let metadata = ATClientMetadata( clientId: "https://myapp.example.com/client-metadata.json", redirectUri: "com.myapp://oauth", clientName: "My App", scope: "atproto transition:generic", logoUri: "https://myapp.example.com/logo.png", clientUri: "https://myapp.example.com", tosUri: "https://myapp.example.com/tos", policyUri: "https://myapp.example.com/privacy" ) let jsonString = try metadata.toJSONString() // Verify it's valid JSON by parsing it let data = jsonString.data(using: .utf8)! let parsed = try JSONDecoder().decode(ATClientMetadata.self, from: data) #expect(parsed.clientId == metadata.clientId) #expect(parsed.clientName == metadata.clientName) #expect(parsed.logoUri == metadata.logoUri) } @Test("JWK creates ES256 public key correctly") func testJWKCreation() { let jwk = JWK.es256PublicKey( x: "base64url-x-coordinate", y: "base64url-y-coordinate", kid: "key-1" ) #expect(jwk.kty == "EC") #expect(jwk.crv == "P-256") #expect(jwk.alg == "ES256") #expect(jwk.use == "sig") #expect(jwk.kid == "key-1") } @Test("JWKSet encodes correctly") func testJWKSetEncoding() throws { let jwkSet = JWKSet(keys: [ JWK.es256PublicKey(x: "x1", y: "y1", kid: "key-1"), JWK.es256PublicKey(x: "x2", y: "y2", kid: "key-2") ]) let encoded = try JSONEncoder().encode(jwkSet) let decoded = try JSONDecoder().decode(JWKSet.self, from: encoded) #expect(decoded.keys.count == 2) #expect(decoded.keys[0].kid == "key-1") #expect(decoded.keys[1].kid == "key-2") } }