import Foundation import Testing @testable import CodexBarCore struct MiniMaxAPISettingsReaderTests { @Test func `api token prefers coding plan specific environment key`() { let token = MiniMaxAPISettingsReader.apiToken(environment: [ "MINIMAX_API_KEY": "sk-api-standard", "MINIMAX_CODING_API_KEY": "sk-cp-coding-plan", ]) #expect(token == "sk-cp-coding-plan") #expect(MiniMaxAPISettingsReader.apiKeyKind(token: token) == .codingPlan) } @Test func `api token falls back to generic environment key`() { let token = MiniMaxAPISettingsReader.apiToken(environment: [ "MINIMAX_API_KEY": "\"sk-api-standard\"", ]) #expect(token == "sk-api-standard") #expect(MiniMaxAPISettingsReader.apiKeyKind(token: token) == .standard) } } struct MiniMaxEndpointOverrideSettingsTests { @Test func `strict endpoint overrides reject non MiniMax hosts`() { let env = [ MiniMaxSettingsReader.requireProviderEndpointOverridesKey: "true", MiniMaxSettingsReader.hostKey: "https://attacker.example", MiniMaxSettingsReader.codingPlanURLKey: "https://attacker.example/coding-plan", MiniMaxSettingsReader.remainsURLKey: "https://attacker.example/remains", MiniMaxSettingsReader.billingHistoryURLKey: "https://attacker.example/account/amount", ] #expect(MiniMaxSettingsReader.hostOverride(environment: env) == nil) #expect(MiniMaxSettingsReader.codingPlanURL(environment: env) == nil) #expect(MiniMaxSettingsReader.remainsURL(environment: env) == nil) #expect(MiniMaxSettingsReader.billingHistoryURL(environment: env) == nil) } @Test func `endpoint overrides reject encoded host delimiters before suffix matching`() { let encodedSlash = "https://attacker.example%2f.platform.minimax.io" let doubleEncodedSlash = "https://attacker.example%252f.platform.minimax.io" let env = [ MiniMaxSettingsReader.hostKey: encodedSlash, MiniMaxSettingsReader.codingPlanURLKey: "\(encodedSlash)/coding-plan", MiniMaxSettingsReader.remainsURLKey: "\(encodedSlash)/remains", MiniMaxSettingsReader.billingHistoryURLKey: "\(encodedSlash)/account/amount", ] #expect(MiniMaxSettingsReader.hostOverride(environment: env) == nil) #expect(MiniMaxSettingsReader.codingPlanURL(environment: env) == nil) #expect(MiniMaxSettingsReader.remainsURL(environment: env) == nil) #expect(MiniMaxSettingsReader.billingHistoryURL(environment: env) == nil) #expect(MiniMaxSettingsReader.hostOverride(environment: [ MiniMaxSettingsReader.hostKey: doubleEncodedSlash, ]) == nil) } @Test func `endpoint overrides reject whitespace and control characters in hosts`() { for host in ["https://bad host", "https://bad%20host", "https://bad%09host"] { #expect(MiniMaxSettingsReader.hostOverride(environment: [ MiniMaxSettingsReader.hostKey: host, ]) == nil) #expect(MiniMaxSettingsReader.remainsURL(environment: [ MiniMaxSettingsReader.remainsURLKey: "\(host)/remains", ]) == nil) } } @Test func `endpoint overrides require https and no userinfo`() { #expect(MiniMaxSettingsReader.hostOverride(environment: [ MiniMaxSettingsReader.hostKey: "http://platform.minimax.io", ]) == nil) #expect(MiniMaxSettingsReader.remainsURL(environment: [ MiniMaxSettingsReader.remainsURLKey: "https://user:pass@platform.minimax.io/remains", ]) == nil) #expect(MiniMaxSettingsReader.rejectedEndpointOverrideKey(environment: [ MiniMaxSettingsReader.hostKey: ":443", ]) == MiniMaxSettingsReader.hostKey) } @Test func `endpoint overrides allow MiniMax and custom https hosts`() { let env = [ MiniMaxSettingsReader.hostKey: "platform.minimaxi.com", MiniMaxSettingsReader.remainsURLKey: "https://platform.minimax.io/custom/remains", ] #expect(MiniMaxSettingsReader.hostOverride(environment: env) == "platform.minimaxi.com") #expect(MiniMaxSettingsReader.remainsURL(environment: env)?.host == "platform.minimax.io") let customEnv = [ MiniMaxSettingsReader.hostKey: "proxy.example.test", MiniMaxSettingsReader.remainsURLKey: "https://proxy.example.test/custom/remains", ] #expect(MiniMaxSettingsReader.hostOverride(environment: customEnv) == "proxy.example.test") #expect(MiniMaxSettingsReader.remainsURL(environment: customEnv)?.host == "proxy.example.test") #expect(MiniMaxSettingsReader.rejectedEndpointOverrideKey(environment: customEnv) == nil) } @Test func `host endpoint overrides preserve explicit port`() { let env = [MiniMaxSettingsReader.hostKey: "proxy.example.test:8443"] #expect(MiniMaxSettingsReader.hostOverride(environment: env) == "proxy.example.test:8443") #expect( MiniMaxUsageFetcher.resolveCodingPlanURL(region: .global, environment: env).absoluteString == "https://proxy.example.test:8443/user-center/payment/coding-plan?cycle_type=3") #expect( MiniMaxUsageFetcher.resolveRemainsURL(region: .global, environment: env).absoluteString == "https://proxy.example.test:8443/v1/api/openplatform/coding_plan/remains") } @Test func `subscription metadata accepts host names beginning with http`() throws { let url = try MiniMaxSubscriptionMetadataFetcher.resolveComboURL( region: .global, environment: [MiniMaxSettingsReader.hostKey: "https://http-proxy.example.test"]) #expect(url.host == "http-proxy.example.test") #expect(url.scheme == "https") } @Test func `scheme less endpoint preserves colon in path`() { let env = [MiniMaxSettingsReader.remainsURLKey: "proxy.example.test/api:v1"] #expect( MiniMaxSettingsReader.remainsURL(environment: env)?.absoluteString == "https://proxy.example.test/api:v1") } @Test func `custom https endpoints allow bracketed IPv6 literals`() { let env = [ MiniMaxSettingsReader.hostKey: "[::1]:8443", MiniMaxSettingsReader.remainsURLKey: "https://[::1]:8443/remains", ] #expect(MiniMaxSettingsReader.hostOverride(environment: env) == "[::1]:8443") #expect(MiniMaxSettingsReader.remainsURL(environment: env)?.host == "::1") #expect(MiniMaxSettingsReader.rejectedEndpointOverrideKey(environment: env) == nil) } @Test func `strict provider endpoint mode rejects custom hosts`() { let env = [ MiniMaxSettingsReader.requireProviderEndpointOverridesKey: "true", MiniMaxSettingsReader.hostKey: "proxy.example.test", MiniMaxSettingsReader.remainsURLKey: "https://proxy.example.test/custom/remains", ] #expect(MiniMaxSettingsReader.hostOverride(environment: env) == nil) #expect(MiniMaxSettingsReader.remainsURL(environment: env) == nil) #expect(MiniMaxSettingsReader.rejectedEndpointOverrideKey(environment: env) == MiniMaxSettingsReader.hostKey) } @Test func `custom https compatibility mode still rejects http and userinfo`() { #expect(MiniMaxSettingsReader.hostOverride(environment: [ MiniMaxSettingsReader.hostKey: "http://proxy.example.test", ]) == nil) #expect(MiniMaxSettingsReader.rejectedEndpointOverrideKey(environment: [ MiniMaxSettingsReader.remainsURLKey: "https://user:pass@proxy.example.test/remains", ]) == MiniMaxSettingsReader.remainsURLKey) } @Test func `explicit endpoint override rejects invalid scheme before network`() async { await #expect(throws: ProviderEndpointOverrideError.minimax(MiniMaxSettingsReader.codingPlanURLKey)) { _ = try await MiniMaxUsageFetcher.fetchUsage( cookieHeader: "session=abc123", environment: [MiniMaxSettingsReader.codingPlanURLKey: "http://platform.minimax.io/coding-plan"], includeBillingHistory: false) } } } struct MiniMaxProviderStrategyTests { private struct StubClaudeFetcher: ClaudeUsageFetching { func loadLatestUsage(model _: String) async throws -> ClaudeUsageSnapshot { throw ClaudeUsageError.parseFailed("stub") } func debugRawProbe(model _: String) async -> String { "stub" } func detectVersion() -> String? { nil } } @Test func `browser cookie import is user initiated app only`() { let appContext = self.makeContext(runtime: .app) let cliContext = self.makeContext(runtime: .cli) #expect(MiniMaxCodingPlanFetchStrategy.allowsBrowserCookieImport(context: appContext) == false) #expect(MiniMaxCodingPlanFetchStrategy.allowsBrowserCookieImport(context: cliContext) == false) ProviderInteractionContext.$current.withValue(.userInitiated) { #expect(MiniMaxCodingPlanFetchStrategy.allowsBrowserCookieImport(context: appContext)) #expect(MiniMaxCodingPlanFetchStrategy.allowsBrowserCookieImport(context: cliContext) == false) } } private func makeContext(runtime: ProviderRuntime) -> ProviderFetchContext { let env: [String: String] = [:] return ProviderFetchContext( runtime: runtime, sourceMode: .web, includeCredits: false, webTimeout: 1, webDebugDumpHTML: false, verbose: false, env: env, settings: nil, fetcher: UsageFetcher(environment: env), claudeFetcher: StubClaudeFetcher(), browserDetection: BrowserDetection(cacheTTL: 0)) } } struct MiniMaxCookieHeaderTests { @Test func `normalizes raw cookie header`() { let raw = "foo=bar; session=abc123" let normalized = MiniMaxCookieHeader.normalized(from: raw) #expect(normalized == "foo=bar; session=abc123") } @Test func `extracts from cookie header line`() { let raw = "Cookie: foo=bar; session=abc123" let normalized = MiniMaxCookieHeader.normalized(from: raw) #expect(normalized == "foo=bar; session=abc123") } @Test func `extracts from curl header`() { let raw = "curl https://platform.minimax.io -H 'Cookie: foo=bar; session=abc123' -H 'accept: */*'" let normalized = MiniMaxCookieHeader.normalized(from: raw) #expect(normalized == "foo=bar; session=abc123") } @Test func `extracts from curl cookie flag`() { let raw = "curl https://platform.minimax.io --cookie 'foo=bar; session=abc123'" let normalized = MiniMaxCookieHeader.normalized(from: raw) #expect(normalized == "foo=bar; session=abc123") } @Test func `extracts auth and group ID from curl`() { let raw = """ curl 'https://platform.minimax.io/v1/api/openplatform/coding_plan/remains?GroupId=123456' \ -H 'authorization: Bearer token123' \ -H 'Cookie: foo=bar; session=abc123' """ let override = MiniMaxCookieHeader.override(from: raw) #expect(override?.cookieHeader == "foo=bar; session=abc123") #expect(override?.authorizationToken == "token123") #expect(override?.groupID == "123456") } @Test func `extracts auth from uppercase header`() { let raw = """ curl 'https://platform.minimax.io/v1/api/openplatform/coding_plan/remains?GROUP_ID=98765' \ -H 'Authorization: Bearer token-abc' \ -H 'Cookie: foo=bar; session=abc123' """ let override = MiniMaxCookieHeader.override(from: raw) #expect(override?.authorizationToken == "token-abc") #expect(override?.groupID == "98765") } @Test func `extracts group ID from combo curl header and cookie`() { let raw = """ curl 'https://www.minimaxi.com/v1/api/openplatform/charge/combo/cycle_audio_resource_package' \ -b 'foo=bar; minimax_group_id_v2=2013894056999916075' \ -H 'x-group-id: 2013894056999916075' """ let override = MiniMaxCookieHeader.override(from: raw) #expect(override?.cookieHeader == "foo=bar; minimax_group_id_v2=2013894056999916075") #expect(override?.groupID == "2013894056999916075") } } struct MiniMaxUsageParserTests { @Test func `signed out check ignores login copy inside scripts`() { let html = """
Coding Plan
""" #expect(!MiniMaxUsageFetcher._looksSignedOutForTesting(html: html)) } @Test func `signed out check still detects visible login copy`() { let html = """
Log in
""" #expect(MiniMaxUsageFetcher._looksSignedOutForTesting(html: html)) } @Test func `parses planName from concrete fields in remains response`() throws { let now = Date(timeIntervalSince1970: 1_700_000_000) // 1. plan_name let jsonPlanName = """ { "base_resp": { "status_code": 0 }, "plan_name": "MiniMax Star", "model_remains": [{"model_name": "abab6.5"}] } """ let snapshot1 = try MiniMaxUsageParser.parseCodingPlanRemains(data: Data(jsonPlanName.utf8), now: now) #expect(snapshot1.planName == "MiniMax Star") // 2. current_plan_title let jsonCurrentPlan = """ { "base_resp": { "status_code": 0 }, "current_plan_title": "Coding Plan Pro", "model_remains": [{"model_name": "abab6.5"}] } """ let snapshot2 = try MiniMaxUsageParser.parseCodingPlanRemains(data: Data(jsonCurrentPlan.utf8), now: now) #expect(snapshot2.planName == "Coding Plan Pro") // 3. current_subscribe_title let jsonSubscribe = """ { "base_resp": { "status_code": 0 }, "current_subscribe_title": "Max", "model_remains": [{"model_name": "abab6.5"}] } """ let snapshot3 = try MiniMaxUsageParser.parseCodingPlanRemains(data: Data(jsonSubscribe.utf8), now: now) #expect(snapshot3.planName == "Max") // 4. combo_title let jsonCombo = """ { "base_resp": { "status_code": 0 }, "combo_title": "Combo Star", "model_remains": [{"model_name": "abab6.5"}] } """ let snapshot4 = try MiniMaxUsageParser.parseCodingPlanRemains(data: Data(jsonCombo.utf8), now: now) #expect(snapshot4.planName == "Combo Star") // 5. current_combo_card.title let jsonComboCard = """ { "base_resp": { "status_code": 0 }, "current_combo_card": { "title": "Card Title" }, "model_remains": [{"model_name": "abab6.5"}] } """ let snapshot5 = try MiniMaxUsageParser.parseCodingPlanRemains(data: Data(jsonComboCard.utf8), now: now) #expect(snapshot5.planName == "Card Title") } @Test func `toUsageSnapshot maps planName to loginMethod`() { let now = Date() let snapshot1 = MiniMaxUsageSnapshot( planName: "MiniMax Star", availablePrompts: nil, currentPrompts: nil, remainingPrompts: nil, windowMinutes: nil, usedPercent: nil, resetsAt: nil, updatedAt: now) let usage1 = snapshot1.toUsageSnapshot() #expect(usage1.identity?.loginMethod == "MiniMax Star") let snapshot2 = MiniMaxUsageSnapshot( planName: nil, availablePrompts: nil, currentPrompts: nil, remainingPrompts: nil, windowMinutes: nil, usedPercent: nil, resetsAt: nil, updatedAt: now) let usage2 = snapshot2.toUsageSnapshot() #expect(usage2.identity?.loginMethod == nil) } @Test func `parses coding plan snapshot`() throws { let now = Date(timeIntervalSince1970: 1_700_000_000) let html = """
Coding Plan
Max
Available usage: 1,000 prompts / 5 hours
Current Usage
0% Used
Resets in 4 min
""" let snapshot = try MiniMaxUsageParser.parse(html: html, now: now) #expect(snapshot.planName == "Max") #expect(snapshot.availablePrompts == 1000) #expect(snapshot.windowMinutes == 300) #expect(snapshot.usedPercent == 0) #expect(snapshot.resetsAt == now.addingTimeInterval(240)) let usage = snapshot.toUsageSnapshot() #expect(usage.primary?.resetDescription == "1000 prompts / 5 hours") } @Test func `parses coding plan remains response`() throws { let now = Date(timeIntervalSince1970: 1_700_000_000) let start = 1_700_000_000_000 let end = start + 5 * 60 * 60 * 1000 let json = """ { "base_resp": { "status_code": 0 }, "current_subscribe_title": "Max", "model_remains": [ { "current_interval_total_count": 1000, "current_interval_usage_count": 250, "start_time": \(start), "end_time": \(end), "remains_time": 240000 } ] } """ let snapshot = try MiniMaxUsageParser.parseCodingPlanRemains(data: Data(json.utf8), now: now) let expectedReset = Date(timeIntervalSince1970: TimeInterval(end) / 1000) #expect(snapshot.planName == "Max") #expect(snapshot.availablePrompts == 1000) #expect(snapshot.currentPrompts == 750) #expect(snapshot.remainingPrompts == 250) #expect(snapshot.windowMinutes == 300) #expect(snapshot.usedPercent == 75) #expect(snapshot.resetsAt == expectedReset) } @Test func `parses model remains services using used quota semantics`() throws { let now = Date(timeIntervalSince1970: 1_700_000_000) let start = 1_700_000_000_000 let end = start + 5 * 60 * 60 * 1000 let json = """ { "base_resp": { "status_code": 0 }, "current_subscribe_title": "Max", "model_remains": [ { "model_name": "M2.7-highspeed", "current_interval_total_count": 1000, "current_interval_usage_count": 250, "start_time": \(start), "end_time": \(end), "remains_time": 240000 } ] } """ let snapshot = try MiniMaxUsageParser.parseCodingPlanRemains(data: Data(json.utf8), now: now) let service = try #require(snapshot.services?.first) #expect(service.displayName == "Text Generation") #expect(service.usage == 750) #expect(service.remaining == 250) #expect(service.limit == 1000) #expect(service.percent == 75) #expect(snapshot.toUsageSnapshot().primary?.usedPercent == 75) } @Test func `text generation includes weekly window when provided`() throws { let now = Date(timeIntervalSince1970: 1_700_000_000) let start = 1_700_000_000_000 let end = start + 5 * 60 * 60 * 1000 let weekStart = start - 2 * 24 * 60 * 60 * 1000 let weekEnd = weekStart + 7 * 24 * 60 * 60 * 1000 let json = """ { "base_resp": { "status_code": 0 }, "model_remains": [ { "model_name": "MiniMax-M1", "current_interval_total_count": 1000, "current_interval_usage_count": 250, "start_time": \(start), "end_time": \(end), "current_weekly_total_count": 6000, "current_weekly_usage_count": 5376, "weekly_start_time": \(weekStart), "weekly_end_time": \(weekEnd) } ] } """ let snapshot = try MiniMaxUsageParser.parseCodingPlanRemains(data: Data(json.utf8), now: now) let services = try #require(snapshot.services) #expect(services.count == 2) #expect(services[0].serviceType == "Text Generation") #expect(services[0].windowType == "5 hours") #expect(services[1].serviceType == "Text Generation") #expect(services[1].windowType == "Weekly") #expect(services[1].usage == 624) #expect(services[1].limit == 6000) #expect(services[1].timeRange.contains("/")) #expect(services[1].timeRange.contains("UTC+8")) #expect(!services[1].timeRange.hasPrefix("10:00-10:00")) } @Test func `legacy plan hides weekly when weekly total is missing or zero`() throws { let now = Date(timeIntervalSince1970: 1_700_000_000) let start = 1_700_000_000_000 let end = start + 5 * 60 * 60 * 1000 let json = """ { "base_resp": { "status_code": 0 }, "model_remains": [ { "model_name": "MiniMax-M1", "current_interval_total_count": 1000, "current_interval_usage_count": 250, "start_time": \(start), "end_time": \(end), "current_weekly_total_count": 0, "current_weekly_usage_count": 0 } ] } """ let snapshot = try MiniMaxUsageParser.parseCodingPlanRemains(data: Data(json.utf8), now: now) let services = try #require(snapshot.services) #expect(services.count == 1) #expect(services[0].windowType == "5 hours") } @Test func `parses multi service payload and utc offset reset`() throws { var calendar = Calendar(identifier: .gregorian) calendar.timeZone = try #require(TimeZone(secondsFromGMT: 8 * 3600)) let now = try #require(calendar.date(from: DateComponents( year: 2026, month: 3, day: 25, hour: 11, minute: 0))) let expectedReset = try #require(calendar.date(from: DateComponents( year: 2026, month: 3, day: 25, hour: 15, minute: 0))) let json = """ { "data": { "services": [ { "service_type": "Text Generation", "window_type": "5 hours", "time_range": "10:00-15:00(UTC+8)", "usage": 2, "limit": 10 }, { "service_type": "Image", "window_type": "Today", "time_range": "2026/03/25 00:00 - 2026/03/26 00:00", "usage": "5", "limit": "50", "percent": "10" } ] } } """ let snapshot = try MiniMaxUsageParser.parseCodingPlanRemains(data: Data(json.utf8), now: now) let services = try #require(snapshot.services) #expect(services.count == 2) #expect(services[0].usage == 2) #expect(services[0].remaining == 8) #expect(services[0].percent == 20) #expect(services[0].resetsAt == expectedReset) #expect(services[1].usage == 5) #expect(services[1].remaining == 45) #expect(services[1].percent == 10) } @Test func `parses coding plan remains from data wrapper`() throws { let now = Date(timeIntervalSince1970: 1_700_000_100) let start = 1_700_000_000_000 let end = start + 5 * 60 * 60 * 1000 let json = """ { "base_resp": { "status_code": "0" }, "data": { "current_subscribe_title": "Max", "model_remains": [ { "current_interval_total_count": "15000", "current_interval_usage_count": "14989", "start_time": \(start), "end_time": \(end), "remains_time": 8941292 } ] } } """ let snapshot = try MiniMaxUsageParser.parseCodingPlanRemains(data: Data(json.utf8), now: now) let expectedUsed = Double(11) / Double(15000) * 100 let expectedReset = Date(timeIntervalSince1970: TimeInterval(end) / 1000) #expect(snapshot.planName == "Max") #expect(snapshot.availablePrompts == 15000) #expect(snapshot.currentPrompts == 11) #expect(snapshot.remainingPrompts == 14989) #expect(snapshot.windowMinutes == 300) #expect(abs((snapshot.usedPercent ?? 0) - expectedUsed) < 0.01) #expect(snapshot.resetsAt == expectedReset) } @Test func `parses coding plan from next data`() throws { let now = Date(timeIntervalSince1970: 1_700_000_000) let start = 1_700_000_000_000 let end = start + 5 * 60 * 60 * 1000 let json = """ { "props": { "pageProps": { "data": { "base_resp": { "status_code": 0 }, "current_subscribe_title": "Max", "model_remains": [ { "current_interval_total_count": 1000, "current_interval_usage_count": 250, "start_time": \(start), "end_time": \(end), "remains_time": 240000 } ] } } } } """ let html = """ """ let snapshot = try MiniMaxUsageParser.parse(html: html, now: now) let expectedReset = Date(timeIntervalSince1970: TimeInterval(end) / 1000) #expect(snapshot.planName == "Max") #expect(snapshot.availablePrompts == 1000) #expect(snapshot.currentPrompts == 750) #expect(snapshot.remainingPrompts == 250) #expect(snapshot.windowMinutes == 300) #expect(snapshot.usedPercent == 75) #expect(snapshot.resetsAt == expectedReset) } @Test func `parses HTML with used prefix and reset time`() throws { var calendar = Calendar(identifier: .gregorian) calendar.timeZone = TimeZone(identifier: "UTC") ?? .current let now = try #require(calendar.date(from: DateComponents(year: 2025, month: 1, day: 1, hour: 10, minute: 0))) let expectedReset = try #require(calendar.date(from: DateComponents( year: 2025, month: 1, day: 1, hour: 23, minute: 30))) let html = """
Coding Plan Pro
Available usage: 1,500 prompts / 1.5 hours
Used 75%
Resets at 23:30 (UTC)
""" let snapshot = try MiniMaxUsageParser.parse(html: html, now: now) #expect(snapshot.planName == "Pro") #expect(snapshot.availablePrompts == 1500) #expect(snapshot.windowMinutes == 90) #expect(snapshot.usedPercent == 75) #expect(snapshot.resetsAt == expectedReset) } @Test func `throws on missing cookie response`() { let json = """ { "base_resp": { "status_code": 1004, "status_msg": "cookie is missing, log in again" } } """ #expect(throws: MiniMaxUsageError.invalidCredentials) { try MiniMaxUsageParser.parseCodingPlanRemains(data: Data(json.utf8)) } } @Test func `throws on string status code when logged out`() { let json = """ { "base_resp": { "status_code": "1004", "status_msg": "login required" } } """ #expect(throws: MiniMaxUsageError.invalidCredentials) { try MiniMaxUsageParser.parseCodingPlanRemains(data: Data(json.utf8)) } } @Test func `throws on error in data wrapper`() { let json = """ { "data": { "base_resp": { "status_code": 1004, "status_msg": "unauthorized" } } } """ #expect(throws: MiniMaxUsageError.invalidCredentials) { try MiniMaxUsageParser.parseCodingPlanRemains(data: Data(json.utf8)) } } @Test func `billing history aggregates records locally`() throws { var calendar = Calendar(identifier: .gregorian) calendar.timeZone = try #require(TimeZone(secondsFromGMT: 0)) let now = try #require(ISO8601DateFormatter().date(from: "2026-05-17T12:00:00Z")) let json = """ { "base_resp": { "status_code": 0 }, "consume_token_sum": 999999, "total_cnt": 4, "charge_records": [ { "consume_token": 1000, "consume_cash_after_voucher": 1.25, "ymd": "2026-05-17", "method": "chat", "model": "MiniMax-M1" }, { "consume_token": "2000", "consume_cash": "2.50", "ymd": "2026-05-16", "method": "chat", "model": "MiniMax-M2" }, { "consume_input_token": 1200, "consume_output_token": 1800, "ymd": "2026-04-18", "method": "audio", "model": "speech-2.8" }, { "consume_token": 4000, "ymd": "2026-04-17", "method": "old", "model": "ignored" } ] } """ let summary = try MiniMaxBillingHistoryParser.parse( data: Data(json.utf8), now: now, calendar: calendar) #expect(summary.todayTokens == 1000) #expect(summary.last30DaysTokens == 6000) #expect(summary.todayCash == 1.25) #expect(summary.last30DaysCash == 3.75) #expect(summary.daily.map(\.day) == ["2026-04-18", "2026-05-16", "2026-05-17"]) #expect(summary.topMethods.first?.name == "audio") #expect(summary.topModels.first?.name == "speech-2.8") } @Test func `billing history preserves date only days in local calendar`() throws { var calendar = Calendar(identifier: .gregorian) calendar.timeZone = try #require(TimeZone(secondsFromGMT: -7 * 60 * 60)) let now = try #require(ISO8601DateFormatter().date(from: "2026-05-17T12:00:00Z")) let json = """ { "base_resp": { "status_code": 0 }, "total_cnt": 1, "charge_records": [ { "consume_token": 1234, "ymd": "2026-05-17", "method": "chat", "model": "MiniMax-M1" } ] } """ let summary = try MiniMaxBillingHistoryParser.parse( data: Data(json.utf8), now: now, calendar: calendar) #expect(summary.todayTokens == 1234) #expect(summary.daily.map(\.day) == ["2026-05-17"]) } @Test func `billing history filters failed records`() throws { var calendar = Calendar(identifier: .gregorian) calendar.timeZone = try #require(TimeZone(secondsFromGMT: 0)) let now = try #require(ISO8601DateFormatter().date(from: "2026-05-17T12:00:00Z")) let json = """ { "base_resp": { "status_code": 0 }, "total_cnt": 5, "charge_records": [ { "consume_token": 1000, "ymd": "2026-05-17", "method": "chat", "model": "MiniMax-M1", "result": "SUCCESS" }, { "consume_token": 2000, "ymd": "2026-05-17", "method": "chat", "model": "MiniMax-M1", "result": "FAILED" }, { "consume_token": 3000, "ymd": "2026-05-17", "method": "chat", "model": "MiniMax-M1", "status": "fail" }, { "consume_token": 4000, "ymd": "2026-05-17", "method": "audio", "model": "speech-2.8" }, { "consume_token": 5000, "ymd": "2026-05-17", "method": "video", "model": "video-1", "status": 0 } ] } """ let summary = try MiniMaxBillingHistoryParser.parse( data: Data(json.utf8), now: now, calendar: calendar) // Only SUCCESS (1000) and missing/empty result status (4000) should be included. // FAILED (2000), status "fail" (3000), and numeric status 0 (5000) should be skipped. #expect(summary.todayTokens == 5000) #expect(summary.last30DaysTokens == 5000) #expect(summary.daily.map(\.day) == ["2026-05-17"]) // Top methods should aggregate only SUCCESS/missing records. #expect(summary.topMethods.count == 2) #expect(summary.topMethods[0].name == "audio") #expect(summary.topMethods[0].tokens == 4000) #expect(summary.topMethods[1].name == "chat") #expect(summary.topMethods[1].tokens == 1000) } @Test func `web usage fetch attaches billing history when available`() async throws { let now = try #require(ISO8601DateFormatter().date(from: "2026-05-17T12:00:00Z")) let transport = ProviderHTTPTransportStub { request in let url = try #require(request.url) if url.path.contains("coding-plan") { return Self.httpResponse( url: url, body: Self.codingPlanJSON, contentType: "application/json") } #expect(url.path == "/account/amount") #expect(url.query?.contains("aggregate=false") == true) #expect(request.value(forHTTPHeaderField: "Cookie") == "HERTZ-SESSION=abc") let body = """ { "base_resp": { "status_code": 0 }, "total_cnt": 1, "charge_records": [ { "consume_token": 1234, "ymd": "2026-05-17", "method": "chat", "model": "MiniMax-M1" } ] } """ return Self.httpResponse(url: url, body: body, contentType: "application/json") } let snapshot = try await MiniMaxUsageFetcher.fetchUsage( cookieHeader: "HERTZ-SESSION=abc", region: .global, environment: [:], session: transport, now: now) #expect(snapshot.currentPrompts == 2) #expect(snapshot.billingSummary?.todayTokens == 1234) #expect(snapshot.billingSummary?.last30DaysTokens == 1234) } @Test func `web usage fetch keeps paginating billing history until 30 day cutoff`() async throws { let now = try #require(ISO8601DateFormatter().date(from: "2026-05-17T12:00:00Z")) let transport = ProviderHTTPTransportStub { request in let url = try #require(request.url) if url.path.contains("coding-plan") { return Self.httpResponse( url: url, body: Self.codingPlanJSON, contentType: "application/json") } let page = URLComponents(url: url, resolvingAgainstBaseURL: false)? .queryItems? .first { $0.name == "page" }? .value ?? "1" let recordDay = page == "3" ? "2026-04-17" : "2026-05-17" let records = (0..<100) .map { _ in """ {"consume_token":1,"ymd":"\(recordDay)","method":"chat","model":"MiniMax-M1"} """ } .joined(separator: ",") let body = """ { "base_resp": { "status_code": 0 }, "total_cnt": 250, "charge_records": [\(records)] } """ return Self.httpResponse(url: url, body: body, contentType: "application/json") } let snapshot = try await MiniMaxUsageFetcher.fetchUsage( cookieHeader: "HERTZ-SESSION=abc", region: .global, environment: [:], session: transport, now: now) let billingRequests = await transport.requests().filter { $0.url?.path == "/account/amount" } #expect(billingRequests.count == 3) #expect(snapshot.billingSummary?.last30DaysTokens == 200) } @Test func `web usage fetch skips billing history when optional usage is disabled`() async throws { let now = try #require(ISO8601DateFormatter().date(from: "2026-05-17T12:00:00Z")) let transport = ProviderHTTPTransportStub { request in let url = try #require(request.url) #expect(url.path.contains("coding-plan")) return Self.httpResponse( url: url, body: Self.codingPlanJSON, contentType: "application/json") } let snapshot = try await MiniMaxUsageFetcher.fetchUsage( cookieHeader: "HERTZ-SESSION=abc", region: .global, environment: [:], includeBillingHistory: false, session: transport, now: now) let requests = await transport.requests() #expect(snapshot.currentPrompts == 2) #expect(snapshot.billingSummary == nil) #expect(requests.count == 1) } @Test func `web usage fetch keeps quota when billing history is forbidden`() async throws { let now = try #require(ISO8601DateFormatter().date(from: "2026-05-17T12:00:00Z")) let transport = ProviderHTTPTransportStub { request in let url = try #require(request.url) if url.path.contains("coding-plan") { return Self.httpResponse( url: url, body: Self.codingPlanJSON, contentType: "application/json") } return Self.httpResponse(url: url, body: "{}", statusCode: 403, contentType: "application/json") } let snapshot = try await MiniMaxUsageFetcher.fetchUsage( cookieHeader: "HERTZ-SESSION=abc", region: .global, environment: [:], session: transport, now: now) #expect(snapshot.currentPrompts == 2) #expect(snapshot.billingSummary == nil) } @Test func `web usage fetch preserves stale bearer failure during billing history`() async throws { let now = try #require(ISO8601DateFormatter().date(from: "2026-05-17T12:00:00Z")) let transport = ProviderHTTPTransportStub { request in let url = try #require(request.url) if url.path.contains("coding-plan") { return Self.httpResponse( url: url, body: Self.codingPlanJSON, contentType: "application/json") } #expect(request.value(forHTTPHeaderField: "Authorization") == "Bearer stale") return Self.httpResponse(url: url, body: "{}", statusCode: 403, contentType: "application/json") } await #expect(throws: MiniMaxUsageError.invalidCredentials) { try await MiniMaxUsageFetcher.fetchUsage( cookieHeader: "HERTZ-SESSION=abc", authorizationToken: "stale", region: .global, environment: [:], session: transport, now: now) } } @Test func `web usage fetch preserves billing history cancellation`() async throws { let now = try #require(ISO8601DateFormatter().date(from: "2026-05-17T12:00:00Z")) let transport = ProviderHTTPTransportStub { request in let url = try #require(request.url) if url.path.contains("coding-plan") { return Self.httpResponse( url: url, body: Self.codingPlanJSON, contentType: "application/json") } throw CancellationError() } await #expect(throws: CancellationError.self) { try await MiniMaxUsageFetcher.fetchUsage( cookieHeader: "HERTZ-SESSION=abc", region: .global, environment: [:], session: transport, now: now) } } private static let codingPlanJSON = """ { "base_resp": { "status_code": 0 }, "data": { "plan_name": "Max", "model_remains": [ { "model_name": "MiniMax-M1", "current_interval_total_count": 10, "current_interval_usage_count": 8, "start_time": 1779019200, "end_time": 1779037200, "remains_time": 3600 } ] } } """ private static func httpResponse( url: URL, body: String, statusCode: Int = 200, contentType: String) -> (Data, URLResponse) { let response = HTTPURLResponse( url: url, statusCode: statusCode, httpVersion: "HTTP/1.1", headerFields: ["Content-Type": contentType])! return (Data(body.utf8), response) } } struct MiniMaxAPIRegionTests { @Test func `defaults to global hosts`() { let codingPlan = MiniMaxUsageFetcher.resolveCodingPlanURL(region: .global, environment: [:]) let remains = MiniMaxUsageFetcher.resolveRemainsURL(region: .global, environment: [:]) #expect(codingPlan.host == "platform.minimax.io") #expect(remains.host == "platform.minimax.io") } @Test func `uses china mainland hosts`() { let codingPlan = MiniMaxUsageFetcher.resolveCodingPlanURL(region: .chinaMainland, environment: [:]) let remains = MiniMaxUsageFetcher.resolveRemainsURL(region: .chinaMainland, environment: [:]) #expect(codingPlan.host == "platform.minimaxi.com") #expect(remains.host == "platform.minimaxi.com") #expect(codingPlan.query == "cycle_type=3") } @Test func `resolves web remains fallback hosts`() { let global = MiniMaxUsageFetcher.resolveRemainsURLs(region: .global, environment: [:]) let china = MiniMaxUsageFetcher.resolveRemainsURLs(region: .chinaMainland, environment: [:]) #expect(global.map(\.host).contains("platform.minimax.io")) #expect(global.map(\.host).contains("www.minimax.io")) #expect(china.map(\.host).contains("platform.minimaxi.com")) #expect(china.map(\.host).contains("www.minimaxi.com")) } @Test func `resolves official token plan remains URL`() { let url = MiniMaxUsageFetcher.resolveTokenPlanRemainsURL(region: .chinaMainland) #expect(url.host == "api.minimaxi.com") #expect(url.path == "/v1/token_plan/remains") } @Test func `host override wins for remains and coding plan`() { let env = [MiniMaxSettingsReader.hostKey: "api.minimaxi.com"] let codingPlan = MiniMaxUsageFetcher.resolveCodingPlanURL(region: .global, environment: env) let remains = MiniMaxUsageFetcher.resolveRemainsURL(region: .global, environment: env) #expect(codingPlan.host == "api.minimaxi.com") #expect(remains.host == "api.minimaxi.com") } @Test func `billing history url uses account amount endpoint`() { let url = MiniMaxUsageFetcher.resolveBillingHistoryURL(region: .chinaMainland, environment: [:], page: 2) #expect(url.host == "platform.minimaxi.com") #expect(url.path == "/account/amount") #expect(url.query?.contains("page=2") == true) #expect(url.query?.contains("limit=100") == true) #expect(url.query?.contains("aggregate=false") == true) } @Test func `remains url override beats host`() { let env = [MiniMaxSettingsReader.remainsURLKey: "https://platform.minimaxi.com/custom/remains"] let remains = MiniMaxUsageFetcher.resolveRemainsURL(region: .global, environment: env) #expect(remains.absoluteString == "https://platform.minimaxi.com/custom/remains") } @Test func `origin uses coding plan override host`() { let env = [MiniMaxSettingsReader.codingPlanURLKey: "https://api.minimaxi.com/custom/path?cycle_type=3"] let codingPlan = MiniMaxUsageFetcher.resolveCodingPlanURL(region: .global, environment: env) let origin = MiniMaxUsageFetcher.originURL(from: codingPlan) #expect(origin.absoluteString == "https://api.minimaxi.com") } @Test func `origin strips host override path`() { let env = [MiniMaxSettingsReader.hostKey: "https://api.minimaxi.com/custom/path"] let codingPlan = MiniMaxUsageFetcher.resolveCodingPlanURL(region: .global, environment: env) let origin = MiniMaxUsageFetcher.originURL(from: codingPlan) #expect(origin.absoluteString == "https://api.minimaxi.com") } }