234 lines
8.5 KiB
Swift
234 lines
8.5 KiB
Swift
import CodexBarCore
|
|
import Foundation
|
|
import Testing
|
|
|
|
@Suite(.serialized)
|
|
struct MiniMaxAPITokenFetchTests {
|
|
@Test
|
|
func `retries china host when global rejects token`() async throws {
|
|
defer {
|
|
MiniMaxAPITokenStubURLProtocol.handler = nil
|
|
MiniMaxAPITokenStubURLProtocol.requests = []
|
|
}
|
|
MiniMaxAPITokenStubURLProtocol.requests = []
|
|
|
|
MiniMaxAPITokenStubURLProtocol.handler = { request in
|
|
guard let url = request.url else { throw URLError(.badURL) }
|
|
let host = url.host ?? ""
|
|
if host == "api.minimax.io" {
|
|
return Self.makeResponse(url: url, body: "{}", statusCode: 401)
|
|
}
|
|
if host == "api.minimaxi.com" {
|
|
let start = 1_700_000_000_000
|
|
let end = start + 5 * 60 * 60 * 1000
|
|
let body = """
|
|
{
|
|
"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
|
|
}
|
|
]
|
|
}
|
|
"""
|
|
return Self.makeResponse(url: url, body: body)
|
|
}
|
|
return Self.makeResponse(url: url, body: "{}", statusCode: 404)
|
|
}
|
|
|
|
let now = Date(timeIntervalSince1970: 1_700_000_000)
|
|
let snapshot = try await MiniMaxUsageFetcher.fetchUsage(
|
|
apiToken: "sk-cp-test",
|
|
region: .global,
|
|
now: now,
|
|
session: Self.makeSession())
|
|
|
|
#expect(snapshot.planName == "Max")
|
|
#expect(MiniMaxAPITokenStubURLProtocol.requests.map { $0.url?.host } == [
|
|
"api.minimax.io",
|
|
"api.minimax.io",
|
|
"api.minimaxi.com",
|
|
])
|
|
#expect(MiniMaxAPITokenStubURLProtocol.requests.map { $0.url?.path } == [
|
|
"/v1/token_plan/remains",
|
|
"/v1/api/openplatform/coding_plan/remains",
|
|
"/v1/token_plan/remains",
|
|
])
|
|
}
|
|
|
|
@Test
|
|
func `preserves invalid credentials when china retry fails transport`() async throws {
|
|
defer {
|
|
MiniMaxAPITokenStubURLProtocol.handler = nil
|
|
MiniMaxAPITokenStubURLProtocol.requests = []
|
|
}
|
|
MiniMaxAPITokenStubURLProtocol.requests = []
|
|
|
|
MiniMaxAPITokenStubURLProtocol.handler = { request in
|
|
guard let url = request.url else { throw URLError(.badURL) }
|
|
let host = url.host ?? ""
|
|
if host == "api.minimax.io" {
|
|
return Self.makeResponse(url: url, body: "{}", statusCode: 401)
|
|
}
|
|
if host == "api.minimaxi.com" {
|
|
throw URLError(.cannotFindHost)
|
|
}
|
|
return Self.makeResponse(url: url, body: "{}", statusCode: 404)
|
|
}
|
|
|
|
let now = Date(timeIntervalSince1970: 1_700_000_000)
|
|
await #expect(throws: MiniMaxUsageError.invalidCredentials) {
|
|
_ = try await MiniMaxUsageFetcher.fetchUsage(
|
|
apiToken: "sk-cp-test",
|
|
region: .global,
|
|
now: now,
|
|
session: Self.makeSession())
|
|
}
|
|
|
|
#expect(MiniMaxAPITokenStubURLProtocol.requests.map { $0.url?.host } == [
|
|
"api.minimax.io",
|
|
"api.minimax.io",
|
|
"api.minimaxi.com",
|
|
"api.minimaxi.com",
|
|
])
|
|
#expect(MiniMaxAPITokenStubURLProtocol.requests.map { $0.url?.path } == [
|
|
"/v1/token_plan/remains",
|
|
"/v1/api/openplatform/coding_plan/remains",
|
|
"/v1/token_plan/remains",
|
|
"/v1/api/openplatform/coding_plan/remains",
|
|
])
|
|
}
|
|
|
|
@Test
|
|
func `explicit china region preserves structured invalid credentials across legacy fallback`() async throws {
|
|
defer {
|
|
MiniMaxAPITokenStubURLProtocol.handler = nil
|
|
MiniMaxAPITokenStubURLProtocol.requests = []
|
|
}
|
|
MiniMaxAPITokenStubURLProtocol.requests = []
|
|
|
|
MiniMaxAPITokenStubURLProtocol.handler = { request in
|
|
guard let url = request.url else { throw URLError(.badURL) }
|
|
if url.path == "/v1/token_plan/remains" {
|
|
return Self.makeResponse(
|
|
url: url,
|
|
body: #"{"base_resp":{"status_code":1004,"status_msg":"invalid api key"}}"#)
|
|
}
|
|
return Self.makeResponse(url: url, body: "{}", statusCode: 404)
|
|
}
|
|
|
|
await #expect(throws: MiniMaxUsageError.invalidCredentials) {
|
|
_ = try await MiniMaxUsageFetcher.fetchUsage(
|
|
apiToken: "sk-cp-test",
|
|
region: .chinaMainland,
|
|
session: Self.makeSession())
|
|
}
|
|
|
|
#expect(MiniMaxAPITokenStubURLProtocol.requests.map { $0.url?.path } == [
|
|
"/v1/token_plan/remains",
|
|
"/v1/api/openplatform/coding_plan/remains",
|
|
])
|
|
}
|
|
|
|
@Test
|
|
func `does not retry when region is china mainland`() async throws {
|
|
defer {
|
|
MiniMaxAPITokenStubURLProtocol.handler = nil
|
|
MiniMaxAPITokenStubURLProtocol.requests = []
|
|
}
|
|
MiniMaxAPITokenStubURLProtocol.requests = []
|
|
|
|
MiniMaxAPITokenStubURLProtocol.handler = { request in
|
|
guard let url = request.url else { throw URLError(.badURL) }
|
|
let host = url.host ?? ""
|
|
if host == "api.minimaxi.com" {
|
|
let start = 1_700_000_000_000
|
|
let end = start + 5 * 60 * 60 * 1000
|
|
let body = """
|
|
{
|
|
"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
|
|
}
|
|
]
|
|
}
|
|
"""
|
|
return Self.makeResponse(url: url, body: body)
|
|
}
|
|
return Self.makeResponse(url: url, body: "{}", statusCode: 401)
|
|
}
|
|
|
|
let now = Date(timeIntervalSince1970: 1_700_000_000)
|
|
_ = try await MiniMaxUsageFetcher.fetchUsage(
|
|
apiToken: "sk-cp-test",
|
|
region: .chinaMainland,
|
|
now: now,
|
|
session: Self.makeSession())
|
|
|
|
#expect(MiniMaxAPITokenStubURLProtocol.requests.count == 1)
|
|
#expect(MiniMaxAPITokenStubURLProtocol.requests.first?.url?.host == "api.minimaxi.com")
|
|
}
|
|
|
|
private static func makeSession() -> URLSession {
|
|
let config = URLSessionConfiguration.ephemeral
|
|
config.protocolClasses = [MiniMaxAPITokenStubURLProtocol.self]
|
|
return URLSession(configuration: config)
|
|
}
|
|
|
|
private static func makeResponse(
|
|
url: URL,
|
|
body: String,
|
|
statusCode: Int = 200) -> (HTTPURLResponse, Data)
|
|
{
|
|
let response = HTTPURLResponse(
|
|
url: url,
|
|
statusCode: statusCode,
|
|
httpVersion: "HTTP/1.1",
|
|
headerFields: ["Content-Type": "application/json"])!
|
|
return (response, Data(body.utf8))
|
|
}
|
|
}
|
|
|
|
final class MiniMaxAPITokenStubURLProtocol: URLProtocol {
|
|
nonisolated(unsafe) static var handler: ((URLRequest) throws -> (HTTPURLResponse, Data))?
|
|
nonisolated(unsafe) static var requests: [URLRequest] = []
|
|
|
|
override static func canInit(with request: URLRequest) -> Bool {
|
|
guard let host = request.url?.host else { return false }
|
|
return host == "api.minimax.io" || host == "api.minimaxi.com"
|
|
}
|
|
|
|
override static func canonicalRequest(for request: URLRequest) -> URLRequest {
|
|
request
|
|
}
|
|
|
|
override func startLoading() {
|
|
Self.requests.append(self.request)
|
|
guard let handler = Self.handler else {
|
|
self.client?.urlProtocol(self, didFailWithError: URLError(.badServerResponse))
|
|
return
|
|
}
|
|
do {
|
|
let (response, data) = try handler(self.request)
|
|
self.client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed)
|
|
self.client?.urlProtocol(self, didLoad: data)
|
|
self.client?.urlProtocolDidFinishLoading(self)
|
|
} catch {
|
|
self.client?.urlProtocol(self, didFailWithError: error)
|
|
}
|
|
}
|
|
|
|
override func stopLoading() {}
|
|
}
|