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