this repo has no description
at oauth 236 lines 8.1 kB view raw
1// 2// TokenStorageTests.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("Token Storage Tests") 13struct TokenStorageTests { 14 15 // MARK: - AuthenticationState Tests 16 17 @Test("AuthenticationState initializes correctly") 18 func testAuthStateInit() { 19 let state = AuthenticationState( 20 did: "did:plc:test123", 21 handle: "test.bsky.social", 22 pdsURL: "https://bsky.social", 23 authServerURL: "https://bsky.social", 24 accessToken: "access-token-value", 25 accessTokenExpiry: Date().addingTimeInterval(3600), 26 refreshToken: "refresh-token-value", 27 scope: "atproto transition:generic", 28 dpopPrivateKeyData: nil 29 ) 30 31 #expect(state.did == "did:plc:test123") 32 #expect(state.handle == "test.bsky.social") 33 #expect(state.accessToken == "access-token-value") 34 #expect(state.refreshToken == "refresh-token-value") 35 } 36 37 @Test("AuthenticationState detects expired access token") 38 func testAccessTokenExpiry() { 39 let expiredState = AuthenticationState( 40 did: "did:plc:test", 41 handle: nil, 42 pdsURL: "https://pds.example.com", 43 authServerURL: "https://auth.example.com", 44 accessToken: "expired", 45 accessTokenExpiry: Date().addingTimeInterval(-100), // Already expired 46 refreshToken: nil, 47 scope: nil, 48 dpopPrivateKeyData: nil 49 ) 50 51 #expect(expiredState.isAccessTokenExpired == true) 52 53 let validState = AuthenticationState( 54 did: "did:plc:test", 55 handle: nil, 56 pdsURL: "https://pds.example.com", 57 authServerURL: "https://auth.example.com", 58 accessToken: "valid", 59 accessTokenExpiry: Date().addingTimeInterval(3600), // Valid for 1 hour 60 refreshToken: nil, 61 scope: nil, 62 dpopPrivateKeyData: nil 63 ) 64 65 #expect(validState.isAccessTokenExpired == false) 66 } 67 68 @Test("AuthenticationState.canRefresh checks refresh token") 69 func testCanRefresh() { 70 let withRefresh = AuthenticationState( 71 did: "did:plc:test", 72 handle: nil, 73 pdsURL: "https://pds.example.com", 74 authServerURL: "https://auth.example.com", 75 accessToken: "access", 76 accessTokenExpiry: nil, 77 refreshToken: "refresh-token", 78 refreshTokenExpiry: Date().addingTimeInterval(86400), // Valid for 1 day 79 scope: nil, 80 dpopPrivateKeyData: nil 81 ) 82 83 #expect(withRefresh.canRefresh == true) 84 85 let withoutRefresh = AuthenticationState( 86 did: "did:plc:test", 87 handle: nil, 88 pdsURL: "https://pds.example.com", 89 authServerURL: "https://auth.example.com", 90 accessToken: "access", 91 accessTokenExpiry: nil, 92 refreshToken: nil, 93 scope: nil, 94 dpopPrivateKeyData: nil 95 ) 96 97 #expect(withoutRefresh.canRefresh == false) 98 } 99 100 @Test("AuthenticationState updates tokens correctly") 101 func testUpdateTokens() { 102 let original = AuthenticationState( 103 did: "did:plc:test", 104 handle: "test.user", 105 pdsURL: "https://pds.example.com", 106 authServerURL: "https://auth.example.com", 107 accessToken: "old-access", 108 accessTokenExpiry: Date(), 109 refreshToken: "old-refresh", 110 scope: "atproto", 111 dpopPrivateKeyData: nil 112 ) 113 114 let updated = original.withUpdatedTokens( 115 access: "new-access", 116 refresh: "new-refresh", 117 expiresIn: 1800 118 ) 119 120 #expect(updated.accessToken == "new-access") 121 #expect(updated.refreshToken == "new-refresh") 122 #expect(updated.did == original.did) // DID should not change 123 #expect(updated.handle == original.handle) // Handle should not change 124 #expect(updated.updatedAt > original.updatedAt) 125 } 126 127 // MARK: - InMemoryTokenStorage Tests 128 129 @Test("InMemoryTokenStorage stores and retrieves") 130 func testInMemoryStorage() async throws { 131 let storage = await InMemoryTokenStorage() 132 133 let state = AuthenticationState( 134 did: "did:plc:memory", 135 handle: "memory.test", 136 pdsURL: "https://pds.example.com", 137 authServerURL: "https://auth.example.com", 138 accessToken: "memory-token", 139 accessTokenExpiry: Date().addingTimeInterval(3600), 140 refreshToken: "memory-refresh", 141 scope: "atproto", 142 dpopPrivateKeyData: nil 143 ) 144 145 try await storage.store(state) 146 let retrieved = try await storage.retrieve() 147 148 #expect(retrieved != nil) 149 #expect(retrieved?.did == "did:plc:memory") 150 #expect(retrieved?.accessToken == "memory-token") 151 } 152 153 @Test("InMemoryTokenStorage clears correctly") 154 func testInMemoryClear() async throws { 155 let storage = await InMemoryTokenStorage() 156 157 let state = AuthenticationState( 158 did: "did:plc:clear", 159 handle: nil, 160 pdsURL: "https://pds.example.com", 161 authServerURL: "https://auth.example.com", 162 accessToken: "token", 163 accessTokenExpiry: nil, 164 refreshToken: nil, 165 scope: nil, 166 dpopPrivateKeyData: nil 167 ) 168 169 try await storage.store(state) 170 try await storage.clear() 171 let retrieved = try await storage.retrieve() 172 173 #expect(retrieved == nil) 174 } 175 176 @Test("InMemoryTokenStorage updates tokens") 177 func testInMemoryUpdate() async throws { 178 let storage = await InMemoryTokenStorage() 179 180 let state = AuthenticationState( 181 did: "did:plc:update", 182 handle: nil, 183 pdsURL: "https://pds.example.com", 184 authServerURL: "https://auth.example.com", 185 accessToken: "original", 186 accessTokenExpiry: Date(), 187 refreshToken: "original-refresh", 188 scope: nil, 189 dpopPrivateKeyData: nil 190 ) 191 192 try await storage.store(state) 193 try await storage.updateTokens(access: "updated", refresh: "updated-refresh", expiresIn: 3600) 194 195 let retrieved = try await storage.retrieve() 196 #expect(retrieved?.accessToken == "updated") 197 #expect(retrieved?.refreshToken == "updated-refresh") 198 } 199 200 @Test("InMemoryTokenStorage throws when updating without stored state") 201 func testInMemoryUpdateWithoutState() async { 202 let storage = await InMemoryTokenStorage() 203 204 await #expect(throws: OAuthError.self) { 205 try await storage.updateTokens(access: "new", refresh: nil, expiresIn: 3600) 206 } 207 } 208 209 // MARK: - AuthenticationState Codable Tests 210 211 @Test("AuthenticationState encodes and decodes") 212 func testAuthStateCodable() throws { 213 let original = AuthenticationState( 214 did: "did:plc:codable", 215 handle: "codable.test", 216 pdsURL: "https://pds.example.com", 217 authServerURL: "https://auth.example.com", 218 accessToken: "codable-access", 219 accessTokenExpiry: Date().addingTimeInterval(3600), 220 refreshToken: "codable-refresh", 221 refreshTokenExpiry: Date().addingTimeInterval(86400), 222 scope: "atproto transition:generic", 223 dpopPrivateKeyData: Data([1, 2, 3, 4]) 224 ) 225 226 let encoded = try JSONEncoder().encode(original) 227 let decoded = try JSONDecoder().decode(AuthenticationState.self, from: encoded) 228 229 #expect(decoded.did == original.did) 230 #expect(decoded.handle == original.handle) 231 #expect(decoded.accessToken == original.accessToken) 232 #expect(decoded.refreshToken == original.refreshToken) 233 #expect(decoded.scope == original.scope) 234 #expect(decoded.dpopPrivateKeyData == original.dpopPrivateKeyData) 235 } 236}