// // TokenStorageTests.swift // CoreATProtocol // // Created by Claude on 2026-01-02. // import Testing import Foundation @testable import CoreATProtocol @Suite("Token Storage Tests") struct TokenStorageTests { // MARK: - AuthenticationState Tests @Test("AuthenticationState initializes correctly") func testAuthStateInit() { let state = AuthenticationState( did: "did:plc:test123", handle: "test.bsky.social", pdsURL: "https://bsky.social", authServerURL: "https://bsky.social", accessToken: "access-token-value", accessTokenExpiry: Date().addingTimeInterval(3600), refreshToken: "refresh-token-value", scope: "atproto transition:generic", dpopPrivateKeyData: nil ) #expect(state.did == "did:plc:test123") #expect(state.handle == "test.bsky.social") #expect(state.accessToken == "access-token-value") #expect(state.refreshToken == "refresh-token-value") } @Test("AuthenticationState detects expired access token") func testAccessTokenExpiry() { let expiredState = AuthenticationState( did: "did:plc:test", handle: nil, pdsURL: "https://pds.example.com", authServerURL: "https://auth.example.com", accessToken: "expired", accessTokenExpiry: Date().addingTimeInterval(-100), // Already expired refreshToken: nil, scope: nil, dpopPrivateKeyData: nil ) #expect(expiredState.isAccessTokenExpired == true) let validState = AuthenticationState( did: "did:plc:test", handle: nil, pdsURL: "https://pds.example.com", authServerURL: "https://auth.example.com", accessToken: "valid", accessTokenExpiry: Date().addingTimeInterval(3600), // Valid for 1 hour refreshToken: nil, scope: nil, dpopPrivateKeyData: nil ) #expect(validState.isAccessTokenExpired == false) } @Test("AuthenticationState.canRefresh checks refresh token") func testCanRefresh() { let withRefresh = AuthenticationState( did: "did:plc:test", handle: nil, pdsURL: "https://pds.example.com", authServerURL: "https://auth.example.com", accessToken: "access", accessTokenExpiry: nil, refreshToken: "refresh-token", refreshTokenExpiry: Date().addingTimeInterval(86400), // Valid for 1 day scope: nil, dpopPrivateKeyData: nil ) #expect(withRefresh.canRefresh == true) let withoutRefresh = AuthenticationState( did: "did:plc:test", handle: nil, pdsURL: "https://pds.example.com", authServerURL: "https://auth.example.com", accessToken: "access", accessTokenExpiry: nil, refreshToken: nil, scope: nil, dpopPrivateKeyData: nil ) #expect(withoutRefresh.canRefresh == false) } @Test("AuthenticationState updates tokens correctly") func testUpdateTokens() { let original = AuthenticationState( did: "did:plc:test", handle: "test.user", pdsURL: "https://pds.example.com", authServerURL: "https://auth.example.com", accessToken: "old-access", accessTokenExpiry: Date(), refreshToken: "old-refresh", scope: "atproto", dpopPrivateKeyData: nil ) let updated = original.withUpdatedTokens( access: "new-access", refresh: "new-refresh", expiresIn: 1800 ) #expect(updated.accessToken == "new-access") #expect(updated.refreshToken == "new-refresh") #expect(updated.did == original.did) // DID should not change #expect(updated.handle == original.handle) // Handle should not change #expect(updated.updatedAt > original.updatedAt) } // MARK: - InMemoryTokenStorage Tests @Test("InMemoryTokenStorage stores and retrieves") func testInMemoryStorage() async throws { let storage = await InMemoryTokenStorage() let state = AuthenticationState( did: "did:plc:memory", handle: "memory.test", pdsURL: "https://pds.example.com", authServerURL: "https://auth.example.com", accessToken: "memory-token", accessTokenExpiry: Date().addingTimeInterval(3600), refreshToken: "memory-refresh", scope: "atproto", dpopPrivateKeyData: nil ) try await storage.store(state) let retrieved = try await storage.retrieve() #expect(retrieved != nil) #expect(retrieved?.did == "did:plc:memory") #expect(retrieved?.accessToken == "memory-token") } @Test("InMemoryTokenStorage clears correctly") func testInMemoryClear() async throws { let storage = await InMemoryTokenStorage() let state = AuthenticationState( did: "did:plc:clear", handle: nil, pdsURL: "https://pds.example.com", authServerURL: "https://auth.example.com", accessToken: "token", accessTokenExpiry: nil, refreshToken: nil, scope: nil, dpopPrivateKeyData: nil ) try await storage.store(state) try await storage.clear() let retrieved = try await storage.retrieve() #expect(retrieved == nil) } @Test("InMemoryTokenStorage updates tokens") func testInMemoryUpdate() async throws { let storage = await InMemoryTokenStorage() let state = AuthenticationState( did: "did:plc:update", handle: nil, pdsURL: "https://pds.example.com", authServerURL: "https://auth.example.com", accessToken: "original", accessTokenExpiry: Date(), refreshToken: "original-refresh", scope: nil, dpopPrivateKeyData: nil ) try await storage.store(state) try await storage.updateTokens(access: "updated", refresh: "updated-refresh", expiresIn: 3600) let retrieved = try await storage.retrieve() #expect(retrieved?.accessToken == "updated") #expect(retrieved?.refreshToken == "updated-refresh") } @Test("InMemoryTokenStorage throws when updating without stored state") func testInMemoryUpdateWithoutState() async { let storage = await InMemoryTokenStorage() await #expect(throws: OAuthError.self) { try await storage.updateTokens(access: "new", refresh: nil, expiresIn: 3600) } } // MARK: - AuthenticationState Codable Tests @Test("AuthenticationState encodes and decodes") func testAuthStateCodable() throws { let original = AuthenticationState( did: "did:plc:codable", handle: "codable.test", pdsURL: "https://pds.example.com", authServerURL: "https://auth.example.com", accessToken: "codable-access", accessTokenExpiry: Date().addingTimeInterval(3600), refreshToken: "codable-refresh", refreshTokenExpiry: Date().addingTimeInterval(86400), scope: "atproto transition:generic", dpopPrivateKeyData: Data([1, 2, 3, 4]) ) let encoded = try JSONEncoder().encode(original) let decoded = try JSONDecoder().decode(AuthenticationState.self, from: encoded) #expect(decoded.did == original.did) #expect(decoded.handle == original.handle) #expect(decoded.accessToken == original.accessToken) #expect(decoded.refreshToken == original.refreshToken) #expect(decoded.scope == original.scope) #expect(decoded.dpopPrivateKeyData == original.dpopPrivateKeyData) } }