this repo has no description
at oauth 178 lines 6.1 kB view raw
1// 2// ClientMetadataTests.swift 3// CoreATProtocol 4// 5// Created by Claude on 2026-01-02. 6// 7 8import Testing 9import Foundation 10@testable import CoreATProtocol 11 12@Suite("Client Metadata Tests") 13struct ClientMetadataTests { 14 15 @Test("Creates valid public client metadata") 16 func testPublicClientMetadata() throws { 17 let metadata = ATClientMetadata( 18 clientId: "https://example.com/client-metadata.json", 19 redirectUri: "com.example.app://oauth/callback", 20 clientName: "My AT Proto App" 21 ) 22 23 #expect(metadata.clientId == "https://example.com/client-metadata.json") 24 #expect(metadata.applicationType == .native) 25 #expect(metadata.tokenEndpointAuthMethod == "none") 26 #expect(metadata.dpopBoundAccessTokens == true) 27 #expect(metadata.grantTypes.contains("authorization_code")) 28 #expect(metadata.grantTypes.contains("refresh_token")) 29 #expect(metadata.responseTypes.contains("code")) 30 #expect(metadata.scope.contains("atproto")) 31 } 32 33 @Test("Creates valid confidential client metadata") 34 func testConfidentialClientMetadata() throws { 35 let metadata = ATClientMetadata( 36 clientId: "https://webapp.example.com/client-metadata.json", 37 redirectUri: "https://webapp.example.com/oauth/callback", 38 clientName: "My Web App", 39 jwksUri: "https://webapp.example.com/.well-known/jwks.json" 40 ) 41 42 #expect(metadata.applicationType == .web) 43 #expect(metadata.tokenEndpointAuthMethod == "private_key_jwt") 44 #expect(metadata.jwksUri == "https://webapp.example.com/.well-known/jwks.json") 45 } 46 47 @Test("Metadata validates HTTPS requirement") 48 func testHTTPSValidation() { 49 let invalidMetadata = ATClientMetadata( 50 clientId: "http://insecure.example.com/metadata.json", 51 redirectUri: "com.example.app://callback", 52 clientName: "Insecure App" 53 ) 54 55 #expect(throws: OAuthError.self) { 56 try invalidMetadata.validate() 57 } 58 } 59 60 @Test("Metadata allows localhost for development") 61 func testLocalhostAllowed() throws { 62 let metadata = ATClientMetadata( 63 clientId: "http://localhost/client-metadata.json", 64 redirectUri: "http://127.0.0.1/callback", 65 clientName: "Dev App" 66 ) 67 68 // Should not throw 69 try metadata.validate() 70 } 71 72 @Test("Metadata validates atproto scope requirement") 73 func testScopeValidation() { 74 // Create metadata with custom scope missing atproto 75 let json = """ 76 { 77 "client_id": "https://example.com/metadata.json", 78 "application_type": "native", 79 "grant_types": ["authorization_code", "refresh_token"], 80 "scope": "openid profile", 81 "response_types": ["code"], 82 "redirect_uris": ["com.example://callback"], 83 "dpop_bound_access_tokens": true, 84 "token_endpoint_auth_method": "none" 85 } 86 """.data(using: .utf8)! 87 88 do { 89 let metadata = try JSONDecoder().decode(ATClientMetadata.self, from: json) 90 91 #expect(throws: OAuthError.self) { 92 try metadata.validate() 93 } 94 } catch { 95 Issue.record("Failed to decode metadata: \(error)") 96 } 97 } 98 99 @Test("Metadata validates DPoP requirement") 100 func testDPoPValidation() { 101 let json = """ 102 { 103 "client_id": "https://example.com/metadata.json", 104 "application_type": "native", 105 "grant_types": ["authorization_code", "refresh_token"], 106 "scope": "atproto", 107 "response_types": ["code"], 108 "redirect_uris": ["com.example://callback"], 109 "dpop_bound_access_tokens": false, 110 "token_endpoint_auth_method": "none" 111 } 112 """.data(using: .utf8)! 113 114 do { 115 let metadata = try JSONDecoder().decode(ATClientMetadata.self, from: json) 116 117 #expect(throws: OAuthError.self) { 118 try metadata.validate() 119 } 120 } catch { 121 Issue.record("Failed to decode metadata: \(error)") 122 } 123 } 124 125 @Test("Metadata encodes to valid JSON") 126 func testJSONEncoding() throws { 127 let metadata = ATClientMetadata( 128 clientId: "https://myapp.example.com/client-metadata.json", 129 redirectUri: "com.myapp://oauth", 130 clientName: "My App", 131 scope: "atproto transition:generic", 132 logoUri: "https://myapp.example.com/logo.png", 133 clientUri: "https://myapp.example.com", 134 tosUri: "https://myapp.example.com/tos", 135 policyUri: "https://myapp.example.com/privacy" 136 ) 137 138 let jsonString = try metadata.toJSONString() 139 140 // Verify it's valid JSON by parsing it 141 let data = jsonString.data(using: .utf8)! 142 let parsed = try JSONDecoder().decode(ATClientMetadata.self, from: data) 143 144 #expect(parsed.clientId == metadata.clientId) 145 #expect(parsed.clientName == metadata.clientName) 146 #expect(parsed.logoUri == metadata.logoUri) 147 } 148 149 @Test("JWK creates ES256 public key correctly") 150 func testJWKCreation() { 151 let jwk = JWK.es256PublicKey( 152 x: "base64url-x-coordinate", 153 y: "base64url-y-coordinate", 154 kid: "key-1" 155 ) 156 157 #expect(jwk.kty == "EC") 158 #expect(jwk.crv == "P-256") 159 #expect(jwk.alg == "ES256") 160 #expect(jwk.use == "sig") 161 #expect(jwk.kid == "key-1") 162 } 163 164 @Test("JWKSet encodes correctly") 165 func testJWKSetEncoding() throws { 166 let jwkSet = JWKSet(keys: [ 167 JWK.es256PublicKey(x: "x1", y: "y1", kid: "key-1"), 168 JWK.es256PublicKey(x: "x2", y: "y2", kid: "key-2") 169 ]) 170 171 let encoded = try JSONEncoder().encode(jwkSet) 172 let decoded = try JSONDecoder().decode(JWKSet.self, from: encoded) 173 174 #expect(decoded.keys.count == 2) 175 #expect(decoded.keys[0].kid == "key-1") 176 #expect(decoded.keys[1].kid == "key-2") 177 } 178}