this repo has no description
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}