Skip to content

Commit d624fb0

Browse files
Copilotgrdsdev
andauthored
test(auth): add missing SessionStateMachine tests for refresh cancellation and failure (#934)
* Initial plan * test(auth): add tests for refresh cancellation and failure in SessionStateMachine Co-authored-by: grdsdev <5923044+grdsdev@users.noreply.github.com> Agent-Logs-Url: https://github.com/supabase/supabase-swift/sessions/96bb2c8d-09ab-479b-89de-3bd718602e12 --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: grdsdev <5923044+grdsdev@users.noreply.github.com> Co-authored-by: Guilherme Souza <guilherme@supabase.io>
1 parent fe521df commit d624fb0

1 file changed

Lines changed: 80 additions & 0 deletions

File tree

Tests/AuthTests/SessionStateMachineTests.swift

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,86 @@ final class SessionStateMachineTests: XCTestCase {
7272
expectNoDifference(returnedSession, session)
7373
}
7474

75+
func testRefreshCancellation_shouldRestoreUsableState() async throws {
76+
// Store a session that will trigger a refresh when validSession() is called.
77+
let currentSession = Session.expiredSession
78+
Dependencies[clientID].sessionStorage.store(currentSession)
79+
80+
await http.when(
81+
{ $0.url.path.contains("/token") },
82+
return: { _ in
83+
// Block until the task is cancelled; Task.sleep throws CancellationError on cancellation.
84+
try await Task.sleep(nanoseconds: NSEC_PER_SEC * 60)
85+
return .stub(Session.validSession)
86+
}
87+
)
88+
89+
let sut = sut
90+
let refreshTask = Task {
91+
try await sut.validSession()
92+
}
93+
94+
await Task.yield()
95+
96+
// Cancel the in-flight refresh by removing the session.
97+
await sut.remove()
98+
99+
// The task should have failed (CancellationError).
100+
do {
101+
_ = try await refreshTask.value
102+
XCTFail("Expected failure after cancellation")
103+
} catch {
104+
// Expected: any error (CancellationError) is acceptable.
105+
}
106+
107+
// State must be cleanly unauthenticated – not stuck in .refreshing.
108+
do {
109+
_ = try await sut.validSession()
110+
XCTFail("Expected sessionMissing error after cancellation")
111+
} catch AuthError.sessionMissing {
112+
// Expected: state is clean.
113+
}
114+
115+
// Recovery: a new session can be stored and retrieved (auto-refresh can proceed).
116+
await sut.update(Session.validSession)
117+
let session = try await sut.validSession()
118+
expectNoDifference(session, Session.validSession)
119+
}
120+
121+
func testRefreshFailure_storageAndStateConsistentAndRecoverable() async throws {
122+
// Store a session that will trigger a refresh when validSession() is called.
123+
let currentSession = Session.expiredSession
124+
Dependencies[clientID].sessionStorage.store(currentSession)
125+
126+
struct RefreshError: Error {}
127+
await http.when(
128+
{ $0.url.path.contains("/token") },
129+
return: { _ in throw RefreshError() }
130+
)
131+
132+
// validSession() triggers a refresh that fails.
133+
do {
134+
_ = try await sut.validSession()
135+
XCTFail("Expected refresh error")
136+
} catch is RefreshError {
137+
// Expected: the refresh error is propagated.
138+
}
139+
140+
// State is unauthenticated after failure – validSession() throws sessionMissing.
141+
do {
142+
_ = try await sut.validSession()
143+
XCTFail("Expected sessionMissing after refresh failure")
144+
} catch AuthError.sessionMissing {
145+
// Expected: storage and state are consistent.
146+
}
147+
148+
// Auto-refresh recovery: update with a new session allows validSession() to succeed.
149+
let newSession = Session.validSession
150+
await sut.update(newSession)
151+
let session = try await sut.validSession()
152+
expectNoDifference(session, newSession)
153+
}
154+
75155
func testSession_shouldRefreshSession_whenCurrentSessionExpired() async throws {
76156
let currentSession = Session.expiredSession
77157
Dependencies[clientID].sessionStorage.store(currentSession)

0 commit comments

Comments
 (0)