import CryptoKit import Foundation import Testing @testable import CoreATProtocol private struct DeterministicRandomGenerator: RandomDataGenerating { func data(count: Int) throws -> Data { Data(repeating: 0x42, count: count) } } @Test("Base64URL encodes without padding and decodes back") func base64URLRoundTrip() throws { let data = Data([0xde, 0xad, 0xbe, 0xef]) let encoded = Base64URL.encode(data) #expect(encoded.contains("=") == false) let decoded = try Base64URL.decode(encoded) #expect(decoded == data) } @Test("PKCE generator creates verifier within bounds and matching challenge") func pkceGeneratorProducesExpectedValues() throws { let generator = PKCEGenerator(randomGenerator: DeterministicRandomGenerator()) let values = try generator.makeValues() #expect(values.verifier.count >= 43) #expect(values.verifier.count <= 128) let expectedDigest = SHA256.hash(data: Data(values.verifier.utf8)) let expectedChallenge = Base64URL.encode(Data(expectedDigest)) #expect(values.challenge == expectedChallenge) } @Test("DPoP generator signs payload with expected claims") func dpopGeneratorProducesValidProof() async throws { let keyPair = DPoPKeyPair() let generator = await DPoPGenerator(clock: { Date(timeIntervalSince1970: 1_700_000_000) }) try await generator.updateKey(using: keyPair.export()) let url = URL(string: "https://example.com/resource")! let proof = try await generator.generateProof( method: "GET", url: url, nonce: "nonce-value", accessToken: "access-token" ) let components = proof.split(separator: ".") #expect(components.count == 3) let headerData = try Base64URL.decode(String(components[0])) let payloadData = try Base64URL.decode(String(components[1])) let signatureData = try Base64URL.decode(String(components[2])) let header = try JSONSerialization.jsonObject(with: headerData) as? [String: Any] let payload = try JSONSerialization.jsonObject(with: payloadData) as? [String: Any] #expect(header?["typ"] as? String == "dpop+jwt") #expect(header?["alg"] as? String == "ES256") let jwk = header?["jwk"] as? [String: String] #expect(jwk?["kty"] == "EC") #expect(jwk?["crv"] == "P-256") #expect(payload?["htm"] as? String == "GET") #expect(payload?["htu"] as? String == "https://example.com/resource") #expect(payload?["nonce"] as? String == "nonce-value") #expect(payload?["ath"] as? String == Base64URL.encode(Data(SHA256.hash(data: Data("access-token".utf8))))) if let iat = payload?["iat"] as? Int { #expect(iat == 1_700_000_000) } else { Issue.record("DPoP payload missing iat") } let signingInput = Data((components[0] + "." + components[1]).utf8) let signature = try P256.Signing.ECDSASignature(derRepresentation: signatureData) #expect(keyPair.privateKey.publicKey.isValidSignature(signature, for: signingInput)) } @Test("OAuth session refresh heuristics") func oauthSessionRefreshLogic() { let issuedAt = Date() let session = OAuthSession( did: "did:plc:example", pdsURL: URL(string: "https://pds.example.com")!, authorizationServer: URL(string: "https://auth.example.com")!, tokenEndpoint: URL(string: "https://auth.example.com/token")!, accessToken: "token", refreshToken: "refresh", tokenType: "DPoP", scope: "atproto", expiresIn: 3600, issuedAt: issuedAt ) #expect(session.isExpired(relativeTo: issuedAt.addingTimeInterval(3500)) == false) #expect(session.needsRefresh(relativeTo: issuedAt.addingTimeInterval(3300), threshold: 400)) #expect(session.isExpired(relativeTo: issuedAt.addingTimeInterval(3600))) }