Files
steipete--codexbar/Tests/CodexBarTests/AlibabaCodingPlanProviderTests.swift
T
2026-07-13 12:22:33 +08:00

1144 lines
43 KiB
Swift

import Foundation
import Testing
@testable import CodexBarCore
struct AlibabaCodingPlanSettingsReaderTests {
@Test
func `api token reads from environment`() {
let token = AlibabaCodingPlanSettingsReader.apiToken(environment: ["ALIBABA_CODING_PLAN_API_KEY": "abc123"])
#expect(token == "abc123")
}
@Test
func `api token reads qwen alias from environment`() {
let token = AlibabaCodingPlanSettingsReader.apiToken(environment: ["ALIBABA_QWEN_API_KEY": "qwen123"])
#expect(token == "qwen123")
}
@Test
func `api token reads dashscope alias from environment`() {
let token = AlibabaCodingPlanSettingsReader.apiToken(environment: ["DASHSCOPE_API_KEY": "dashscope123"])
#expect(token == "dashscope123")
}
@Test
func `api token prefers coding plan key over aliases`() {
let token = AlibabaCodingPlanSettingsReader.apiToken(environment: [
"ALIBABA_CODING_PLAN_API_KEY": "coding-plan",
"ALIBABA_QWEN_API_KEY": "qwen",
"DASHSCOPE_API_KEY": "dashscope",
])
#expect(token == "coding-plan")
}
@Test
func `api token strips quotes`() {
let token = AlibabaCodingPlanSettingsReader
.apiToken(environment: ["ALIBABA_CODING_PLAN_API_KEY": "\"token-xyz\""])
#expect(token == "token-xyz")
}
@Test
func `quota URL infers scheme`() {
let url = AlibabaCodingPlanSettingsReader
.quotaURL(environment: [AlibabaCodingPlanSettingsReader
.quotaURLKey: "modelstudio.console.alibabacloud.com/data/api.json"])
#expect(url?.absoluteString == "https://modelstudio.console.alibabacloud.com/data/api.json")
}
@Test
func `endpoint overrides allow custom https hosts by default`() {
let env = [
AlibabaCodingPlanSettingsReader.hostKey: "https://attacker.example",
AlibabaCodingPlanSettingsReader.quotaURLKey: "https://attacker.example/data/api.json",
]
#expect(AlibabaCodingPlanSettingsReader.hostOverride(environment: env) == "attacker.example")
#expect(AlibabaCodingPlanSettingsReader.quotaURL(environment: env)?.host == "attacker.example")
#expect(AlibabaCodingPlanSettingsReader.rejectedEndpointOverrideKey(environment: env) == nil)
}
@Test
func `host endpoint overrides preserve explicit port`() {
let env = [AlibabaCodingPlanSettingsReader.hostKey: "proxy.example.test:8443"]
#expect(AlibabaCodingPlanSettingsReader.hostOverride(environment: env) == "proxy.example.test:8443")
#expect(
AlibabaCodingPlanUsageFetcher.resolveQuotaURL(region: .international, environment: env).absoluteString ==
"https://proxy.example.test:8443/data/api.json?action=zeldaEasy.broadscope-bailian.codingPlan.queryCodingPlanInstanceInfoV2&product=broadscope-bailian&api=queryCodingPlanInstanceInfoV2&currentRegionId=ap-southeast-1")
#expect(
AlibabaCodingPlanUsageFetcher.resolveConsoleDashboardURL(region: .international, environment: env)
.absoluteString
.hasPrefix("https://proxy.example.test:8443/") == true)
}
@Test
func `endpoint overrides reject encoded host delimiters before suffix matching`() {
let encodedSlash = "https://attacker.example%2f.modelstudio.console.alibabacloud.com"
let doubleEncodedSlash = "https://attacker.example%252f.modelstudio.console.alibabacloud.com"
let env = [
AlibabaCodingPlanSettingsReader.hostKey: encodedSlash,
AlibabaCodingPlanSettingsReader.quotaURLKey: "\(encodedSlash)/data/api.json",
]
#expect(AlibabaCodingPlanSettingsReader.hostOverride(environment: env) == nil)
#expect(AlibabaCodingPlanSettingsReader.quotaURL(environment: env) == nil)
#expect(AlibabaCodingPlanSettingsReader.hostOverride(environment: [
AlibabaCodingPlanSettingsReader.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(AlibabaCodingPlanSettingsReader.hostOverride(environment: [
AlibabaCodingPlanSettingsReader.hostKey: host,
]) == nil)
#expect(AlibabaCodingPlanSettingsReader.quotaURL(environment: [
AlibabaCodingPlanSettingsReader.quotaURLKey: "\(host)/data/api.json",
]) == nil)
}
}
@Test
func `endpoint overrides require https and no userinfo`() {
#expect(AlibabaCodingPlanSettingsReader.hostOverride(environment: [
AlibabaCodingPlanSettingsReader.hostKey: "http://modelstudio.console.alibabacloud.com",
]) == nil)
#expect(AlibabaCodingPlanSettingsReader.quotaURL(environment: [
AlibabaCodingPlanSettingsReader.quotaURLKey:
"https://user:pass@modelstudio.console.alibabacloud.com/data/api.json",
]) == nil)
}
@Test
func `strict provider endpoint mode rejects custom hosts`() {
let env = [
AlibabaCodingPlanSettingsReader.requireProviderEndpointOverridesKey: "true",
AlibabaCodingPlanSettingsReader.hostKey: "proxy.example.test",
AlibabaCodingPlanSettingsReader.quotaURLKey: "https://proxy.example.test/data/api.json",
]
#expect(AlibabaCodingPlanSettingsReader.hostOverride(environment: env) == nil)
#expect(AlibabaCodingPlanSettingsReader.quotaURL(environment: env) == nil)
#expect(AlibabaCodingPlanSettingsReader
.rejectedEndpointOverrideKey(environment: env) == AlibabaCodingPlanSettingsReader.hostKey)
}
@Test
func `strict provider endpoint mode rejects customer controlled Alibaba Cloud hosts`() {
let env = [
AlibabaCodingPlanSettingsReader.requireProviderEndpointOverridesKey: "true",
AlibabaCodingPlanSettingsReader.hostKey: "tenant.cn-beijing.fc.aliyuncs.com",
]
#expect(AlibabaCodingPlanSettingsReader.hostOverride(environment: env) == nil)
#expect(AlibabaCodingPlanSettingsReader
.rejectedEndpointOverrideKey(environment: env) == AlibabaCodingPlanSettingsReader.hostKey)
}
@Test
func `strict provider endpoint mode accepts known Coding Plan hosts`() {
let env = [
AlibabaCodingPlanSettingsReader.requireProviderEndpointOverridesKey: "true",
AlibabaCodingPlanSettingsReader.hostKey: "bailian-beijing-cs.aliyuncs.com",
]
#expect(
AlibabaCodingPlanSettingsReader.hostOverride(environment: env) ==
"bailian-beijing-cs.aliyuncs.com")
#expect(AlibabaCodingPlanSettingsReader.rejectedEndpointOverrideKey(environment: env) == nil)
}
@Test
func `custom https compatibility mode still rejects http and userinfo`() {
#expect(AlibabaCodingPlanSettingsReader.hostOverride(environment: [
AlibabaCodingPlanSettingsReader.hostKey: "http://proxy.example.test",
]) == nil)
#expect(AlibabaCodingPlanSettingsReader.rejectedEndpointOverrideKey(environment: [
AlibabaCodingPlanSettingsReader.quotaURLKey: "https://user:pass@proxy.example.test/data/api.json",
]) == AlibabaCodingPlanSettingsReader.quotaURLKey)
}
@Test
func `missing cookie error includes access hint when present`() {
let error = AlibabaCodingPlanSettingsError
.missingCookie(details: "Safari cookie file exists but is not readable.")
#expect(error.errorDescription?.contains("Safari cookie file exists but is not readable.") == true)
}
}
struct AlibabaCodingPlanUsageSnapshotTests {
@Test
func `maps usage snapshot windows`() {
let now = Date(timeIntervalSince1970: 1_700_000_000)
let reset5h = Date(timeIntervalSince1970: 1_700_000_300)
let resetWeek = Date(timeIntervalSince1970: 1_700_010_000)
let resetMonth = Date(timeIntervalSince1970: 1_700_100_000)
let snapshot = AlibabaCodingPlanUsageSnapshot(
planName: "Pro",
fiveHourUsedQuota: 20,
fiveHourTotalQuota: 100,
fiveHourNextRefreshTime: reset5h,
weeklyUsedQuota: 120,
weeklyTotalQuota: 400,
weeklyNextRefreshTime: resetWeek,
monthlyUsedQuota: 500,
monthlyTotalQuota: 2000,
monthlyNextRefreshTime: resetMonth,
updatedAt: now)
let usage = snapshot.toUsageSnapshot()
#expect(usage.primary?.usedPercent == 20)
#expect(usage.primary?.windowMinutes == 300)
#expect(usage.secondary?.usedPercent == 30)
#expect(usage.secondary?.windowMinutes == 10080)
#expect(usage.tertiary?.usedPercent == 25)
#expect(usage.tertiary?.windowMinutes == 43200)
#expect(usage.loginMethod(for: .alibaba) == "Pro")
}
@Test
func `shifts primary reset forward when backend reset is not future`() {
let now = Date(timeIntervalSince1970: 1_700_000_000)
let stalePrimaryReset = Date(timeIntervalSince1970: 1_699_999_900)
let snapshot = AlibabaCodingPlanUsageSnapshot(
planName: "Lite",
fiveHourUsedQuota: 70,
fiveHourTotalQuota: 1200,
fiveHourNextRefreshTime: stalePrimaryReset,
weeklyUsedQuota: 80,
weeklyTotalQuota: 9000,
weeklyNextRefreshTime: Date(timeIntervalSince1970: 1_700_010_000),
monthlyUsedQuota: 80,
monthlyTotalQuota: 18000,
monthlyNextRefreshTime: Date(timeIntervalSince1970: 1_700_100_000),
updatedAt: now)
let usage = snapshot.toUsageSnapshot()
#expect(usage.primary?.resetsAt == stalePrimaryReset.addingTimeInterval(TimeInterval(5 * 60 * 60)))
}
}
struct AlibabaCodingPlanUsageParsingTests {
@Test
func `parses quota payload`() throws {
let now = Date(timeIntervalSince1970: 1_700_000_000)
let json = """
{
"data": {
"codingPlanInstanceInfos": [
{ "planName": "Alibaba Coding Plan Pro" }
],
"codingPlanQuotaInfo": {
"per5HourUsedQuota": 52,
"per5HourTotalQuota": 1000,
"per5HourQuotaNextRefreshTime": 1700000300000,
"perWeekUsedQuota": 800,
"perWeekTotalQuota": 5000,
"perWeekQuotaNextRefreshTime": 1700100000000,
"perBillMonthUsedQuota": 1200,
"perBillMonthTotalQuota": 20000,
"perBillMonthQuotaNextRefreshTime": 1701000000000
}
},
"status_code": 0
}
"""
let snapshot = try AlibabaCodingPlanUsageFetcher.parseUsageSnapshot(from: Data(json.utf8), now: now)
#expect(snapshot.planName == "Alibaba Coding Plan Pro")
#expect(snapshot.fiveHourUsedQuota == 52)
#expect(snapshot.fiveHourTotalQuota == 1000)
#expect(snapshot.weeklyTotalQuota == 5000)
#expect(snapshot.monthlyTotalQuota == 20000)
#expect(snapshot.fiveHourNextRefreshTime == Date(timeIntervalSince1970: 1_700_000_300))
}
@Test
func `multi instance quota payload uses selected active instance plan name`() throws {
let now = Date(timeIntervalSince1970: 1_700_000_000)
let json = """
{
"data": {
"codingPlanInstanceInfos": [
{
"planName": "Expired Starter",
"status": "EXPIRED",
"endTime": "2025-04-01 17:00",
"codingPlanQuotaInfo": {
"per5HourUsedQuota": 7,
"per5HourTotalQuota": 100,
"per5HourQuotaNextRefreshTime": 1700000100000
}
},
{
"planName": "Active Pro",
"status": "VALID",
"codingPlanQuotaInfo": {
"per5HourUsedQuota": 52,
"per5HourTotalQuota": 1000,
"per5HourQuotaNextRefreshTime": 1700000300000
}
}
]
},
"status_code": 0
}
"""
let snapshot = try AlibabaCodingPlanUsageFetcher.parseUsageSnapshot(from: Data(json.utf8), now: now)
#expect(snapshot.planName == "Active Pro")
#expect(snapshot.fiveHourUsedQuota == 52)
#expect(snapshot.fiveHourTotalQuota == 1000)
#expect(snapshot.fiveHourNextRefreshTime == Date(timeIntervalSince1970: 1_700_000_300))
}
@Test
func `missing quota data without positive active signal fails`() {
let json = """
{
"data": {
"codingPlanInstanceInfos": [
{ "planName": "Alibaba Coding Plan Pro" }
]
},
"status_code": 0
}
"""
#expect(throws: AlibabaCodingPlanUsageError.self) {
try AlibabaCodingPlanUsageFetcher.parseUsageSnapshot(from: Data(json.utf8))
}
}
@Test
func `plan usage without positive active proof fails`() {
let json = """
{
"data": {
"codingPlanInstanceInfos": [
{
"planName": "Alibaba Coding Plan Pro",
"planUsage": "18%"
}
]
},
"status_code": 0
}
"""
#expect(throws: AlibabaCodingPlanUsageError.self) {
try AlibabaCodingPlanUsageFetcher.parseUsageSnapshot(from: Data(json.utf8))
}
}
@Test
func `parses wrapped JSON string payload`() throws {
let now = Date(timeIntervalSince1970: 1_700_000_000)
let inner = """
{
"data": {
"codingPlanInstanceInfos": [
{
"planName": "Coding Plan Lite",
"status": "VALID",
"codingPlanQuotaInfo": {
"per5HourUsedQuota": 0,
"per5HourTotalQuota": 1000,
"per5HourQuotaNextRefreshTime": 1700000300000
}
}
]
},
"statusCode": 200
}
"""
.replacingOccurrences(of: "\n", with: "")
.replacingOccurrences(of: " ", with: "")
.replacingOccurrences(of: "\"", with: "\\\"")
let wrapped = """
{
"successResponse": {
"body": "\(inner)"
}
}
"""
let snapshot = try AlibabaCodingPlanUsageFetcher.parseUsageSnapshot(from: Data(wrapped.utf8), now: now)
#expect(snapshot.planName == "Coding Plan Lite")
#expect(snapshot.fiveHourTotalQuota == 1000)
#expect(snapshot.fiveHourUsedQuota == 0)
}
@Test
func `plan usage fallback stays visible but non quantitative`() throws {
let now = Date(timeIntervalSince1970: 1_700_000_000)
let json = """
{
"data": {
"codingPlanInstanceInfos": [
{
"planName": "Coding Plan Lite",
"status": "VALID",
"planUsage": "0%",
"endTime": "2026-04-01 17:00"
}
]
},
"status_code": 0
}
"""
let snapshot = try AlibabaCodingPlanUsageFetcher.parseUsageSnapshot(from: Data(json.utf8), now: now)
#expect(snapshot.planName == "Coding Plan Lite")
#expect(snapshot.fiveHourUsedQuota == nil)
#expect(snapshot.fiveHourTotalQuota == nil)
#expect(snapshot.fiveHourNextRefreshTime == nil)
let usage = snapshot.toUsageSnapshot()
#expect(usage.primary == nil)
#expect(usage.loginMethod(for: .alibaba) == "Coding Plan Lite")
}
@Test
func `falls back to active plan when quota and usage missing`() throws {
let now = Date(timeIntervalSince1970: 1_700_000_000)
let json = """
{
"data": {
"codingPlanInstanceInfos": [
{
"planName": "Coding Plan Lite",
"status": "VALID",
"endTime": "2026-04-01 17:00"
}
]
},
"status_code": 0
}
"""
let snapshot = try AlibabaCodingPlanUsageFetcher.parseUsageSnapshot(from: Data(json.utf8), now: now)
#expect(snapshot.planName == "Coding Plan Lite")
#expect(snapshot.fiveHourUsedQuota == nil)
#expect(snapshot.fiveHourTotalQuota == nil)
let usage = snapshot.toUsageSnapshot()
#expect(usage.primary == nil)
#expect(usage.loginMethod(for: .alibaba) == "Coding Plan Lite")
}
@Test
func `future end time counts as positive active signal`() throws {
let now = Date(timeIntervalSince1970: 1_700_000_000)
let json = """
{
"data": {
"codingPlanInstanceInfos": [
{
"planName": "Coding Plan Lite",
"endTime": "2030-04-01 17:00"
}
]
},
"status_code": 0
}
"""
let snapshot = try AlibabaCodingPlanUsageFetcher.parseUsageSnapshot(from: Data(json.utf8), now: now)
#expect(snapshot.planName == "Coding Plan Lite")
#expect(snapshot.fiveHourUsedQuota == nil)
#expect(snapshot.weeklyTotalQuota == nil)
let usage = snapshot.toUsageSnapshot()
#expect(usage.primary == nil)
#expect(usage.loginMethod(for: .alibaba) == "Coding Plan Lite")
}
@Test
func `multi instance fallback uses selected active instance plan name`() throws {
let now = Date(timeIntervalSince1970: 1_700_000_000)
let json = """
{
"data": {
"codingPlanInstanceInfos": [
{
"planName": "Expired Starter",
"status": "EXPIRED",
"endTime": "2025-04-01 17:00"
},
{
"planName": "Active Pro",
"status": "VALID",
"planUsage": "42%"
}
]
},
"status_code": 0
}
"""
let snapshot = try AlibabaCodingPlanUsageFetcher.parseUsageSnapshot(from: Data(json.utf8), now: now)
#expect(snapshot.planName == "Active Pro")
#expect(snapshot.fiveHourUsedQuota == nil)
#expect(snapshot.fiveHourTotalQuota == nil)
let usage = snapshot.toUsageSnapshot()
#expect(usage.primary == nil)
#expect(usage.loginMethod(for: .alibaba) == "Active Pro")
}
@Test
func `active instance without quota does not borrow quota from another instance`() throws {
let now = Date(timeIntervalSince1970: 1_700_000_000)
let json = """
{
"data": {
"codingPlanInstanceInfos": [
{
"planName": "Expired Starter",
"status": "EXPIRED",
"endTime": "2025-04-01 17:00",
"codingPlanQuotaInfo": {
"per5HourUsedQuota": 7,
"per5HourTotalQuota": 100,
"per5HourQuotaNextRefreshTime": 1700000100000
}
},
{
"planName": "Active Pro",
"status": "VALID"
}
]
},
"status_code": 0
}
"""
let snapshot = try AlibabaCodingPlanUsageFetcher.parseUsageSnapshot(from: Data(json.utf8), now: now)
#expect(snapshot.planName == "Active Pro")
#expect(snapshot.fiveHourUsedQuota == nil)
#expect(snapshot.fiveHourTotalQuota == nil)
#expect(snapshot.fiveHourNextRefreshTime == nil)
}
@Test
func `payload level active proof does not label first instance when no instance is active`() {
let now = Date(timeIntervalSince1970: 1_700_000_000)
let json = """
{
"data": {
"status": "VALID",
"codingPlanInstanceInfos": [
{
"planName": "Expired Starter",
"status": "EXPIRED",
"endTime": "2025-04-01 17:00"
},
{
"planName": "No Proof Pro"
}
]
},
"status_code": 0
}
"""
#expect(throws: AlibabaCodingPlanUsageError.self) {
try AlibabaCodingPlanUsageFetcher.parseUsageSnapshot(from: Data(json.utf8), now: now)
}
}
@Test
func `does not fallback for inactive plan without quota`() {
let json = """
{
"data": {
"codingPlanInstanceInfos": [
{
"planName": "Coding Plan Lite",
"status": "EXPIRED"
}
]
},
"status_code": 0
}
"""
#expect(throws: AlibabaCodingPlanUsageError.self) {
try AlibabaCodingPlanUsageFetcher.parseUsageSnapshot(from: Data(json.utf8))
}
}
@Test
func `console need login payload maps to login required`() {
let json = """
{
"code": "ConsoleNeedLogin",
"message": "You need to log in.",
"requestId": "abc",
"successResponse": false
}
"""
#expect(throws: AlibabaCodingPlanUsageError.loginRequired) {
try AlibabaCodingPlanUsageFetcher.parseUsageSnapshot(from: Data(json.utf8))
}
}
@Test
func `console need login payload maps to unavailable API key mode`() {
let json = """
{
"code": "ConsoleNeedLogin",
"message": "You need to log in.",
"requestId": "abc",
"successResponse": false
}
"""
#expect(throws: AlibabaCodingPlanUsageError.apiKeyUnavailableInRegion) {
try AlibabaCodingPlanUsageFetcher.parseUsageSnapshot(
from: Data(json.utf8),
authMode: .apiKey)
}
}
}
@Suite(.serialized)
struct AlibabaCodingPlanFallbackTests {
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
}
}
private func makeContext(
sourceMode: ProviderSourceMode,
settings: ProviderSettingsSnapshot? = nil,
env: [String: String] = [:]) -> ProviderFetchContext
{
let browserDetection = BrowserDetection(cacheTTL: 0)
return ProviderFetchContext(
runtime: .cli,
sourceMode: sourceMode,
includeCredits: false,
webTimeout: 1,
webDebugDumpHTML: false,
verbose: false,
env: env,
settings: settings,
fetcher: UsageFetcher(environment: env),
claudeFetcher: StubClaudeFetcher(),
browserDetection: browserDetection)
}
@Test
func `falls back on TLS failure in auto mode`() {
let strategy = AlibabaCodingPlanWebFetchStrategy()
let context = self.makeContext(sourceMode: .auto)
#expect(strategy.shouldFallback(on: URLError(.secureConnectionFailed), context: context))
}
@Test
func `does not fallback on TLS failure when source forced to web`() {
let strategy = AlibabaCodingPlanWebFetchStrategy()
let context = self.makeContext(sourceMode: .web)
#expect(strategy.shouldFallback(on: URLError(.secureConnectionFailed), context: context) == false)
}
@Test
func `auto mode does not borrow manual cookie authority when browser import fails`() {
let strategy = AlibabaCodingPlanWebFetchStrategy()
let settings = ProviderSettingsSnapshot.make(
alibaba: ProviderSettingsSnapshot.AlibabaCodingPlanProviderSettings(
cookieSource: .auto,
manualCookieHeader: "session=manual-cookie",
apiRegion: .international))
let context = self.makeContext(sourceMode: .auto, settings: settings)
CookieHeaderCache.clear(provider: .alibaba)
AlibabaCodingPlanCookieImporter.importSessionOverrideForTesting = { _, _ in
throw AlibabaCodingPlanSettingsError.missingCookie()
}
defer {
AlibabaCodingPlanCookieImporter.importSessionOverrideForTesting = nil
}
do {
_ = try AlibabaCodingPlanWebFetchStrategy.resolveCookieHeader(context: context, allowCached: false)
Issue.record("Expected auto mode to fail instead of borrowing the manual cookie header")
} catch let error as AlibabaCodingPlanSettingsError {
guard case .missingCookie = error else {
Issue.record("Expected missingCookie, got \(error)")
return
}
#expect(strategy.shouldFallback(on: error, context: context))
} catch {
Issue.record("Expected AlibabaCodingPlanSettingsError, got \(error)")
}
}
@Test
func `auto mode skips web when no alibaba session is available`() async {
let strategy = AlibabaCodingPlanWebFetchStrategy()
let settings = ProviderSettingsSnapshot.make(
alibaba: ProviderSettingsSnapshot.AlibabaCodingPlanProviderSettings(
cookieSource: .auto,
manualCookieHeader: nil,
apiRegion: .international))
let context = self.makeContext(
sourceMode: .auto,
settings: settings,
env: [AlibabaCodingPlanSettingsReader.apiTokenKey: "token-abc"])
CookieHeaderCache.clear(provider: .alibaba)
AlibabaCodingPlanCookieImporter.importSessionOverrideForTesting = { _, _ in
throw AlibabaCodingPlanSettingsError.missingCookie()
}
defer {
AlibabaCodingPlanCookieImporter.importSessionOverrideForTesting = nil
}
#expect(await strategy.isAvailable(context) == false)
}
}
struct AlibabaCodingPlanRegionTests {
@Test
func `defaults to international endpoint`() {
let url = AlibabaCodingPlanUsageFetcher.resolveQuotaURL(region: .international, environment: [:])
#expect(url.host == "modelstudio.console.alibabacloud.com")
#expect(url.path == "/data/api.json")
}
@Test
func `uses china mainland host`() {
let url = AlibabaCodingPlanUsageFetcher.resolveQuotaURL(region: .chinaMainland, environment: [:])
#expect(url.host == "bailian.console.aliyun.com")
}
@Test
func `host override wins for quota URL`() {
let env = [AlibabaCodingPlanSettingsReader.hostKey: "custom.aliyun.com"]
let url = AlibabaCodingPlanUsageFetcher.resolveQuotaURL(region: .international, environment: env)
#expect(url.host == "custom.aliyun.com")
#expect(url.path == "/data/api.json")
}
@Test
func `host override uses selected region for quota URL`() {
let env = [AlibabaCodingPlanSettingsReader.hostKey: "custom.aliyun.com"]
let url = AlibabaCodingPlanUsageFetcher.resolveQuotaURL(region: .chinaMainland, environment: env)
#expect(url.host == "custom.aliyun.com")
let components = URLComponents(url: url, resolvingAgainstBaseURL: false)
let currentRegion = components?.queryItems?.first(where: { $0.name == "currentRegionId" })?.value
#expect(currentRegion == AlibabaCodingPlanAPIRegion.chinaMainland.currentRegionID)
}
@Test
func `bare host override builds console dashboard URL`() {
let env = [AlibabaCodingPlanSettingsReader.hostKey: "custom.aliyun.com"]
let url = AlibabaCodingPlanUsageFetcher.resolveConsoleDashboardURL(region: .international, environment: env)
#expect(url.scheme == "https")
#expect(url.host == "custom.aliyun.com")
#expect(url.path == AlibabaCodingPlanAPIRegion.international.dashboardURL.path)
let components = URLComponents(url: url, resolvingAgainstBaseURL: false)
let tab = components?.queryItems?.first(where: { $0.name == "tab" })?.value
#expect(tab == "coding-plan")
}
@Test
func `quota url override beats host`() {
let env = [
AlibabaCodingPlanSettingsReader.quotaURLKey:
"https://modelstudio.console.alibabacloud.com/custom/quota",
]
let url = AlibabaCodingPlanUsageFetcher.resolveQuotaURL(region: .international, environment: env)
#expect(url.absoluteString == "https://modelstudio.console.alibabacloud.com/custom/quota")
}
@Test
func `custom quota url override is preserved by default`() {
let env = [AlibabaCodingPlanSettingsReader.quotaURLKey: "https://attacker.example/custom/quota"]
let url = AlibabaCodingPlanUsageFetcher.resolveQuotaURL(region: .international, environment: env)
#expect(url.host == "attacker.example")
}
@Test
func `strict provider endpoint mode falls back to provider endpoint`() {
let env = [
AlibabaCodingPlanSettingsReader.requireProviderEndpointOverridesKey: "true",
AlibabaCodingPlanSettingsReader.quotaURLKey: "https://attacker.example/custom/quota",
]
let url = AlibabaCodingPlanUsageFetcher.resolveQuotaURL(region: .international, environment: env)
#expect(url.host == AlibabaCodingPlanAPIRegion.international.quotaURL.host)
}
@Test
func `explicit endpoint override rejects invalid api scheme before network`() async {
await #expect(throws: ProviderEndpointOverrideError.alibabaCodingPlan(
AlibabaCodingPlanSettingsReader.quotaURLKey))
{
_ = try await AlibabaCodingPlanUsageFetcher.fetchUsage(
apiKey: "cpk-test",
environment: [AlibabaCodingPlanSettingsReader
.quotaURLKey: "http://modelstudio.console.alibabacloud.com/custom/quota"])
}
}
@Test
func `explicit endpoint override rejects invalid cookie scheme before network`() async {
await #expect(throws: ProviderEndpointOverrideError.alibabaCodingPlan(
AlibabaCodingPlanSettingsReader.quotaURLKey))
{
_ = try await AlibabaCodingPlanUsageFetcher.fetchUsage(
cookieHeader: "login_aliyunid_ticket=ticket; login_aliyunid_pk=user",
environment: [AlibabaCodingPlanSettingsReader
.quotaURLKey: "http://modelstudio.console.alibabacloud.com/custom/quota"])
}
}
}
@Suite(.serialized)
struct AlibabaCodingPlanUsageFetcherRequestTests {
@Test
func `api401 maps to invalid credentials`() async throws {
let transport = ProviderHTTPTransportHandler { request in
guard let url = request.url else { throw URLError(.badURL) }
let (response, data) = Self.makeResponse(
url: url,
body: #"{"message":"unauthorized"}"#,
statusCode: 401)
return (data, response)
}
await #expect(throws: AlibabaCodingPlanUsageError.invalidCredentials) {
_ = try await AlibabaCodingPlanUsageFetcher.fetchUsage(
apiKey: "cpk-test",
region: .chinaMainland,
environment: [
AlibabaCodingPlanSettingsReader.quotaURLKey: "https://bailian.console.aliyun.com/data/api.json",
],
transport: transport)
}
}
@Test
func `cookie SEC token fallback survives user info request failure`() async throws {
let registered = URLProtocol.registerClass(AlibabaConsoleSECTokenStubURLProtocol.self)
defer {
if registered {
URLProtocol.unregisterClass(AlibabaConsoleSECTokenStubURLProtocol.self)
}
AlibabaConsoleSECTokenStubURLProtocol.handler = nil
}
AlibabaConsoleSECTokenStubURLProtocol.handler = { request in
guard let url = request.url else { throw URLError(.badURL) }
if url.host == "modelstudio.console.alibabacloud.com", request.httpMethod == "GET" {
return Self.makeResponse(url: url, body: "<html></html>", statusCode: 200)
}
if url.host == "modelstudio.console.alibabacloud.com", url.path == "/tool/user/info.json" {
throw URLError(.timedOut)
}
if url.host == "bailian-singapore-cs.alibabacloud.com", request.httpMethod == "POST" {
let body = Self.requestBodyString(from: request)
#expect(body.contains("sec_token=cookie-sec-token"))
let json = """
{
"data": {
"codingPlanInstanceInfos": [
{ "planName": "Alibaba Coding Plan Pro", "status": "VALID" }
],
"codingPlanQuotaInfo": {
"per5HourUsedQuota": 52,
"per5HourTotalQuota": 1000,
"per5HourQuotaNextRefreshTime": 1700000300000
}
},
"status_code": 0
}
"""
return Self.makeResponse(url: url, body: json, statusCode: 200)
}
throw URLError(.unsupportedURL)
}
let snapshot = try await AlibabaCodingPlanUsageFetcher.fetchUsage(
cookieHeader: "sec_token=cookie-sec-token; login_aliyunid_ticket=ticket; login_aliyunid_pk=user",
region: .international,
environment: [:],
now: Date(timeIntervalSince1970: 1_700_000_000))
#expect(snapshot.planName == "Alibaba Coding Plan Pro")
#expect(snapshot.fiveHourUsedQuota == 52)
#expect(snapshot.fiveHourTotalQuota == 1000)
}
@Test
func `host override applies to user info SEC token fallback`() async throws {
let registered = URLProtocol.registerClass(AlibabaConsoleSECTokenStubURLProtocol.self)
defer {
if registered {
URLProtocol.unregisterClass(AlibabaConsoleSECTokenStubURLProtocol.self)
}
AlibabaConsoleSECTokenStubURLProtocol.handler = nil
}
AlibabaConsoleSECTokenStubURLProtocol.handler = { request in
guard let url = request.url else { throw URLError(.badURL) }
#expect(url.host == "modelstudio.console.alibabacloud.com")
if request.httpMethod == "GET", url.path == AlibabaCodingPlanAPIRegion.international.dashboardURL.path {
return Self.makeResponse(url: url, body: "<html></html>", statusCode: 200)
}
if request.httpMethod == "GET", url.path == "/tool/user/info.json" {
return Self.makeResponse(
url: url,
body: #"{"data":{"secToken":"override-sec-token"}}"#,
statusCode: 200)
}
if request.httpMethod == "POST", url.path == "/data/api.json" {
let body = Self.requestBodyString(from: request)
#expect(body.contains("sec_token=override-sec-token"))
let json = """
{
"data": {
"codingPlanInstanceInfos": [
{ "planName": "Alibaba Coding Plan Pro", "status": "VALID" }
],
"codingPlanQuotaInfo": {
"per5HourUsedQuota": 21,
"per5HourTotalQuota": 1000,
"per5HourQuotaNextRefreshTime": 1700000300000
}
},
"status_code": 0
}
"""
return Self.makeResponse(url: url, body: json, statusCode: 200)
}
throw URLError(.unsupportedURL)
}
let snapshot = try await AlibabaCodingPlanUsageFetcher.fetchUsage(
cookieHeader: "sec_token=cookie-sec-token; login_aliyunid_ticket=ticket; login_aliyunid_pk=user",
region: .international,
environment: [AlibabaCodingPlanSettingsReader.hostKey: "https://modelstudio.console.alibabacloud.com"],
now: Date(timeIntervalSince1970: 1_700_000_000))
#expect(snapshot.planName == "Alibaba Coding Plan Pro")
#expect(snapshot.fiveHourUsedQuota == 21)
#expect(snapshot.fiveHourTotalQuota == 1000)
}
@Test
func `console request body uses region specific metadata`() async throws {
let registered = URLProtocol.registerClass(AlibabaConsoleSECTokenStubURLProtocol.self)
defer {
if registered {
URLProtocol.unregisterClass(AlibabaConsoleSECTokenStubURLProtocol.self)
}
AlibabaConsoleSECTokenStubURLProtocol.handler = nil
}
AlibabaConsoleSECTokenStubURLProtocol.handler = { request in
guard let url = request.url else { throw URLError(.badURL) }
if request.httpMethod == "GET", url.path == AlibabaCodingPlanAPIRegion.chinaMainland.dashboardURL.path {
return Self.makeResponse(url: url, body: "<html></html>", statusCode: 200)
}
if request.httpMethod == "GET", url.path == "/tool/user/info.json" {
return Self.makeResponse(url: url, body: #"{"data":{"secToken":"cn-sec-token"}}"#, statusCode: 200)
}
if request.httpMethod == "POST", url.path == "/data/api.json" {
let body = Self.requestBodyString(from: request)
let params = try #require(Self.requestParamsDictionary(from: body))
let data = try #require(params["Data"] as? [String: Any])
let cornerstone = try #require(data["cornerstoneParam"] as? [String: Any])
#expect(cornerstone["domain"] as? String == AlibabaCodingPlanAPIRegion.chinaMainland.consoleDomain)
#expect(cornerstone["consoleSite"] as? String == AlibabaCodingPlanAPIRegion.chinaMainland.consoleSite)
#expect(
cornerstone["feURL"] as? String
== AlibabaCodingPlanAPIRegion.chinaMainland.dashboardURL.absoluteString)
let json = """
{
"data": {
"codingPlanInstanceInfos": [
{ "planName": "Alibaba Coding Plan Pro", "status": "VALID" }
],
"codingPlanQuotaInfo": {
"per5HourUsedQuota": 21,
"per5HourTotalQuota": 1000,
"per5HourQuotaNextRefreshTime": 1700000300000
}
},
"status_code": 0
}
"""
return Self.makeResponse(url: url, body: json, statusCode: 200)
}
throw URLError(.unsupportedURL)
}
let snapshot = try await AlibabaCodingPlanUsageFetcher.fetchUsage(
cookieHeader: "sec_token=cookie-sec-token; login_aliyunid_ticket=ticket; login_aliyunid_pk=user",
region: .chinaMainland,
environment: [:],
now: Date(timeIntervalSince1970: 1_700_000_000))
#expect(snapshot.planName == "Alibaba Coding Plan Pro")
#expect(snapshot.fiveHourUsedQuota == 21)
#expect(snapshot.fiveHourTotalQuota == 1000)
}
private static func makeResponse(url: URL, body: String, statusCode: Int) -> (HTTPURLResponse, Data) {
let response = HTTPURLResponse(
url: url,
statusCode: statusCode,
httpVersion: "HTTP/1.1",
headerFields: ["Content-Type": "application/json"])!
return (response, Data(body.utf8))
}
private static func requestBodyString(from request: URLRequest) -> String {
if let data = request.httpBody {
return String(data: data, encoding: .utf8) ?? ""
}
guard let stream = request.httpBodyStream else {
return ""
}
stream.open()
defer { stream.close() }
var data = Data()
let bufferSize = 4096
let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: bufferSize)
defer { buffer.deallocate() }
while stream.hasBytesAvailable {
let count = stream.read(buffer, maxLength: bufferSize)
if count <= 0 {
break
}
data.append(buffer, count: count)
}
return String(data: data, encoding: .utf8) ?? ""
}
private static func requestParamsDictionary(from body: String) -> [String: Any]? {
guard let components = URLComponents(string: "https://example.invalid/?\(body)"),
let params = components.queryItems?.first(where: { $0.name == "params" })?.value,
let data = params.data(using: .utf8)
else {
return nil
}
let object = try? JSONSerialization.jsonObject(with: data, options: [])
return object as? [String: Any]
}
}
final class AlibabaUsageFetcherStubURLProtocol: URLProtocol {
nonisolated(unsafe) static var handler: ((URLRequest) throws -> (HTTPURLResponse, Data))?
override static func canInit(with request: URLRequest) -> Bool {
request.url?.host == "bailian.console.aliyun.com"
}
override static func canonicalRequest(for request: URLRequest) -> URLRequest {
request
}
override func startLoading() {
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() {}
}
final class AlibabaConsoleSECTokenStubURLProtocol: URLProtocol {
nonisolated(unsafe) static var handler: ((URLRequest) throws -> (HTTPURLResponse, Data))?
override static func canInit(with request: URLRequest) -> Bool {
guard let host = request.url?.host else { return false }
return [
"modelstudio.console.alibabacloud.com",
"bailian-singapore-cs.alibabacloud.com",
"bailian.console.aliyun.com",
"bailian-cs.console.aliyun.com",
"bailian-beijing-cs.aliyuncs.com",
].contains(host)
}
override static func canonicalRequest(for request: URLRequest) -> URLRequest {
request
}
override func startLoading() {
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() {}
}