Skip to content

Commit 0c8b4ee

Browse files
committed
Fix MiMo auth redirect fallback
1 parent 6d37bbc commit 0c8b4ee

2 files changed

Lines changed: 89 additions & 0 deletions

File tree

Sources/CodexBarCore/Providers/MiMo/MiMoUsageFetcher.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,9 @@ public enum MiMoUsageFetcher {
168168
switch response.statusCode {
169169
case 200:
170170
break
171+
// Expired browser sessions can redirect API requests to the login flow.
172+
case 300 ..< 400:
173+
throw MiMoUsageError.loginRequired
171174
case 401:
172175
throw MiMoUsageError.loginRequired
173176
case 403:

Tests/CodexBarTests/MiMoProviderTests.swift

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -561,6 +561,25 @@ struct MiMoProviderTests {
561561

562562
#expect(elapsed < .seconds(1), "Required failure was delayed by optional requests: \(elapsed)")
563563
}
564+
565+
@Test
566+
func `fetch usage treats auth redirect as login required`() async throws {
567+
let transport = ProviderHTTPTransportStub { request in
568+
let url = try #require(request.url)
569+
let (response, data) = Self.makeResponse(url: url, body: "", statusCode: 302)
570+
return (data, response)
571+
}
572+
573+
do {
574+
_ = try await MiMoUsageFetcher.fetchUsage(
575+
cookieHeader: "userId=123; api-platform_serviceToken=expired-token",
576+
environment: ["MIMO_API_URL": "https://mimo.test/api/v1"],
577+
session: transport)
578+
Issue.record("Expected MiMo auth redirect to require login")
579+
} catch MiMoUsageError.loginRequired {
580+
// Expected.
581+
}
582+
}
564583
}
565584

566585
private actor MiMoOptionalRequestGate {
@@ -942,6 +961,73 @@ extension MiMoProviderTests {
942961
#expect(CookieHeaderCache.load(provider: .mimo)?.sourceLabel == "Active Chrome")
943962
}
944963

964+
@Test
965+
func `mimo web strategy retries safari after stale chrome auth redirect`() async throws {
966+
KeychainCacheStore.setTestStoreForTesting(true)
967+
defer { KeychainCacheStore.setTestStoreForTesting(false) }
968+
let registered = URLProtocol.registerClass(MiMoStubURLProtocol.self)
969+
defer {
970+
if registered {
971+
URLProtocol.unregisterClass(MiMoStubURLProtocol.self)
972+
}
973+
MiMoStubURLProtocol.handler = nil
974+
MiMoCookieImporter.importSessionsOverrideForTesting = nil
975+
CookieHeaderCache.clear(provider: .mimo)
976+
}
977+
978+
CookieHeaderCache.clear(provider: .mimo)
979+
CookieHeaderCache.store(
980+
provider: .mimo,
981+
cookieHeader: "api-platform_serviceToken=stale-chrome-token; userId=111",
982+
sourceLabel: "Chrome")
983+
984+
MiMoCookieImporter.importSessionsOverrideForTesting = { _, _ in
985+
[
986+
.init(
987+
cookieHeader: "api-platform_serviceToken=stale-chrome-token; userId=111",
988+
sourceLabel: "Chrome"),
989+
.init(
990+
cookieHeader: "api-platform_serviceToken=valid-safari-token; userId=222",
991+
sourceLabel: "Safari"),
992+
]
993+
}
994+
995+
let lock = NSLock()
996+
var requestedCookies: [String] = []
997+
MiMoStubURLProtocol.handler = { request in
998+
guard let url = request.url else { throw URLError(.badURL) }
999+
let cookie = request.value(forHTTPHeaderField: "Cookie") ?? ""
1000+
lock.withLock {
1001+
requestedCookies.append(cookie)
1002+
}
1003+
1004+
if cookie.contains("stale-chrome-token") {
1005+
return Self.makeResponse(url: url, body: "", statusCode: 302)
1006+
}
1007+
1008+
let body = """
1009+
{
1010+
"code": 0,
1011+
"message": "",
1012+
"data": {
1013+
"balance": "25.51",
1014+
"currency": "USD"
1015+
}
1016+
}
1017+
"""
1018+
return Self.makeResponse(url: url, body: body)
1019+
}
1020+
1021+
let strategy = MiMoWebFetchStrategy()
1022+
let result = try await strategy
1023+
.fetch(self.makeContext(environment: ["MIMO_API_URL": "https://mimo.test/api/v1"]))
1024+
1025+
#expect(requestedCookies.contains(where: { $0.contains("stale-chrome-token") }))
1026+
#expect(requestedCookies.contains(where: { $0.contains("valid-safari-token") }))
1027+
#expect(result.usage.mimoUsage?.balanceDetail == "$25.51")
1028+
#expect(CookieHeaderCache.load(provider: .mimo)?.sourceLabel == "Safari")
1029+
}
1030+
9451031
#if os(macOS)
9461032
@Test
9471033
func `mimo importer merges profile stores before validating auth cookies`() {

0 commit comments

Comments
 (0)