927 lines
37 KiB
Swift
927 lines
37 KiB
Swift
import Foundation
|
|
import Testing
|
|
@testable import CodexBarCore
|
|
|
|
struct AlibabaTokenPlanSettingsReaderTests {
|
|
@Test
|
|
func `cookie reads from environment`() {
|
|
let cookie = AlibabaTokenPlanSettingsReader.cookieHeader(environment: [
|
|
AlibabaTokenPlanSettingsReader.cookieHeaderKey: "\"login_aliyunid_ticket=ticket\"",
|
|
])
|
|
#expect(cookie == "login_aliyunid_ticket=ticket")
|
|
}
|
|
|
|
@Test
|
|
func `quota URL infers HTTPS scheme`() {
|
|
let url = AlibabaTokenPlanSettingsReader.quotaURL(environment: [
|
|
AlibabaTokenPlanSettingsReader.quotaURLKey: "quota.token-plan.test/data/api.json",
|
|
])
|
|
|
|
#expect(url?.scheme == "https")
|
|
#expect(url?.host == "quota.token-plan.test")
|
|
}
|
|
|
|
@Test
|
|
func `quota URL rejects non HTTPS schemes`() {
|
|
let httpURL = AlibabaTokenPlanSettingsReader.quotaURL(environment: [
|
|
AlibabaTokenPlanSettingsReader.quotaURLKey: "http://quota.token-plan.test/data/api.json",
|
|
])
|
|
let ftpURL = AlibabaTokenPlanSettingsReader.quotaURL(environment: [
|
|
AlibabaTokenPlanSettingsReader.quotaURLKey: "ftp://quota.token-plan.test/data/api.json",
|
|
])
|
|
|
|
#expect(httpURL == nil)
|
|
#expect(ftpURL == nil)
|
|
}
|
|
|
|
@Test
|
|
func `host override rejects non HTTPS schemes`() {
|
|
let httpHost = AlibabaTokenPlanSettingsReader.hostOverride(environment: [
|
|
AlibabaTokenPlanSettingsReader.hostKey: "http://dashboard.token-plan.test",
|
|
])
|
|
let httpsHost = AlibabaTokenPlanSettingsReader.hostOverride(environment: [
|
|
AlibabaTokenPlanSettingsReader.hostKey: "https://dashboard.token-plan.test",
|
|
])
|
|
let bareHost = AlibabaTokenPlanSettingsReader.hostOverride(environment: [
|
|
AlibabaTokenPlanSettingsReader.hostKey: "dashboard.token-plan.test",
|
|
])
|
|
|
|
#expect(httpHost == nil)
|
|
#expect(httpsHost == "dashboard.token-plan.test")
|
|
#expect(bareHost == "dashboard.token-plan.test")
|
|
}
|
|
|
|
@Test
|
|
func `default quota URL targets subscription summary API`() {
|
|
let url = AlibabaTokenPlanUsageFetcher.defaultQuotaURL
|
|
#expect(url.host == "modelstudio.console.alibabacloud.com")
|
|
#expect(url.absoluteString.contains("GetSubscriptionSummary"))
|
|
#expect(url.absoluteString.contains("BssOpenAPI-V3"))
|
|
}
|
|
|
|
@Test
|
|
func `default quota URL for china mainland targets bailian`() {
|
|
let url = AlibabaTokenPlanUsageFetcher.defaultQuotaURL(region: .chinaMainland)
|
|
#expect(url.host == "bailian.console.aliyun.com")
|
|
#expect(url.absoluteString.contains("GetSubscriptionSummary"))
|
|
#expect(url.absoluteString.contains("BssOpenAPI-V3"))
|
|
}
|
|
}
|
|
|
|
struct AlibabaTokenPlanCookieHeaderTests {
|
|
@Test
|
|
func `builds URL scoped headers for API and dashboard`() throws {
|
|
let cookies = [
|
|
self.cookie(name: "login_aliyunid_ticket", value: "ticket", domain: ".alibabacloud.com"),
|
|
self.cookie(name: "login_current_pk", value: "account", domain: ".alibabacloud.com"),
|
|
self.cookie(name: "sec_token", value: "shared", domain: ".console.alibabacloud.com"),
|
|
self.cookie(name: "sec_token", value: "dashboard", domain: "modelstudio.console.alibabacloud.com"),
|
|
self.cookie(name: "bailian_only", value: "bailian", domain: "bailian.console.aliyun.com"),
|
|
]
|
|
|
|
let headers = try #require(AlibabaTokenPlanCookieHeader.headers(from: cookies))
|
|
|
|
#expect(headers.apiCookieHeader.contains("login_aliyunid_ticket=ticket"))
|
|
#expect(headers.apiCookieHeader.contains("login_current_pk=account"))
|
|
#expect(headers.apiCookieHeader.contains("sec_token=dashboard"))
|
|
#expect(!headers.apiCookieHeader.contains("bailian_only=bailian"))
|
|
#expect(headers.dashboardCookieHeader.contains("sec_token=dashboard"))
|
|
#expect(!headers.dashboardCookieHeader.contains("bailian_only=bailian"))
|
|
}
|
|
|
|
@Test
|
|
func `builds URL scoped headers for china mainland region`() throws {
|
|
let cookies = [
|
|
self.cookie(name: "login_aliyunid_ticket", value: "ticket", domain: ".aliyun.com"),
|
|
self.cookie(name: "login_current_pk", value: "account", domain: ".aliyun.com"),
|
|
self.cookie(name: "sec_token", value: "shared", domain: ".console.aliyun.com"),
|
|
self.cookie(name: "sec_token", value: "dashboard", domain: "bailian.console.aliyun.com"),
|
|
self.cookie(name: "modelstudio_only", value: "modelstudio", domain: "modelstudio.console.alibabacloud.com"),
|
|
]
|
|
|
|
let headers = try #require(AlibabaTokenPlanCookieHeader.headers(from: cookies, region: .chinaMainland))
|
|
|
|
#expect(headers.apiCookieHeader.contains("login_aliyunid_ticket=ticket"))
|
|
#expect(headers.apiCookieHeader.contains("login_current_pk=account"))
|
|
#expect(headers.apiCookieHeader.contains("sec_token=dashboard"))
|
|
#expect(!headers.apiCookieHeader.contains("modelstudio_only=modelstudio"))
|
|
#expect(headers.dashboardCookieHeader.contains("sec_token=dashboard"))
|
|
#expect(!headers.dashboardCookieHeader.contains("modelstudio_only=modelstudio"))
|
|
}
|
|
|
|
@Test
|
|
func `cached token plan headers preserve URL scoping`() throws {
|
|
let headers = AlibabaTokenPlanCookieHeaders(
|
|
apiCookieHeader: "login_aliyunid_ticket=ticket; api_only=api",
|
|
dashboardCookieHeader: "login_aliyunid_ticket=ticket; dashboard_only=dashboard")
|
|
|
|
let cached = try #require(AlibabaTokenPlanCookieHeaders(cachedHeader: headers.cacheCookieHeader))
|
|
|
|
#expect(cached.apiCookieHeader.contains("api_only=api"))
|
|
#expect(!cached.apiCookieHeader.contains("dashboard_only=dashboard"))
|
|
#expect(cached.dashboardCookieHeader.contains("dashboard_only=dashboard"))
|
|
#expect(!cached.dashboardCookieHeader.contains("api_only=api"))
|
|
}
|
|
|
|
@Test
|
|
func `builds headers from environment scoped URLs`() throws {
|
|
let cookies = [
|
|
self.cookie(name: "login_aliyunid_ticket", value: "ticket", domain: ".token-plan.test"),
|
|
self.cookie(name: "api_only", value: "api", domain: "quota.token-plan.test"),
|
|
self.cookie(name: "dashboard_only", value: "dashboard", domain: "dashboard.token-plan.test"),
|
|
self.cookie(name: "prod_api_only", value: "prod-api", domain: "modelstudio.console.alibabacloud.com"),
|
|
self.cookie(
|
|
name: "prod_dashboard_only",
|
|
value: "prod-dashboard",
|
|
domain: "modelstudio.console.alibabacloud.com"),
|
|
]
|
|
|
|
let headers = try #require(AlibabaTokenPlanCookieHeader.headers(
|
|
from: cookies,
|
|
environment: [
|
|
AlibabaTokenPlanSettingsReader.quotaURLKey: "https://quota.token-plan.test/data/api.json",
|
|
AlibabaTokenPlanSettingsReader.hostKey: "https://dashboard.token-plan.test",
|
|
]))
|
|
|
|
#expect(headers.apiCookieHeader.contains("login_aliyunid_ticket=ticket"))
|
|
#expect(headers.apiCookieHeader.contains("api_only=api"))
|
|
#expect(!headers.apiCookieHeader.contains("prod_api_only=prod-api"))
|
|
#expect(headers.dashboardCookieHeader.contains("dashboard_only=dashboard"))
|
|
#expect(!headers.dashboardCookieHeader.contains("prod_dashboard_only=prod-dashboard"))
|
|
}
|
|
|
|
private func cookie(
|
|
name: String,
|
|
value: String,
|
|
domain: String,
|
|
path: String = "/",
|
|
expires: Date = Date(timeIntervalSinceNow: 3600)) -> HTTPCookie
|
|
{
|
|
HTTPCookie(properties: [
|
|
.domain: domain,
|
|
.path: path,
|
|
.name: name,
|
|
.value: value,
|
|
.expires: expires,
|
|
.secure: true,
|
|
])!
|
|
}
|
|
}
|
|
|
|
struct AlibabaTokenPlanUsageSnapshotTests {
|
|
@Test
|
|
func `maps used and total quota to primary window`() {
|
|
let now = Date(timeIntervalSince1970: 1_700_000_000)
|
|
let reset = Date(timeIntervalSince1970: 1_700_100_000)
|
|
let snapshot = AlibabaTokenPlanUsageSnapshot(
|
|
planName: "TOKEN PLAN",
|
|
usedQuota: 250,
|
|
totalQuota: 1000,
|
|
remainingQuota: nil,
|
|
resetsAt: reset,
|
|
updatedAt: now)
|
|
|
|
let usage = snapshot.toUsageSnapshot()
|
|
|
|
#expect(usage.primary?.usedPercent == 25)
|
|
#expect(usage.primary?.resetsAt == reset)
|
|
#expect(usage.primary?.resetDescription == "250 / 1,000 credits used")
|
|
#expect(usage.loginMethod(for: .alibabatokenplan) == "TOKEN PLAN")
|
|
}
|
|
|
|
@Test
|
|
func `does not create primary window from balance only`() {
|
|
let snapshot = AlibabaTokenPlanUsageSnapshot(
|
|
planName: "TOKEN PLAN",
|
|
usedQuota: nil,
|
|
totalQuota: nil,
|
|
remainingQuota: 700,
|
|
resetsAt: nil,
|
|
updatedAt: Date(timeIntervalSince1970: 1_700_000_000))
|
|
|
|
let usage = snapshot.toUsageSnapshot()
|
|
|
|
#expect(usage.primary == nil)
|
|
#expect(usage.loginMethod(for: .alibabatokenplan) == "TOKEN PLAN")
|
|
}
|
|
}
|
|
|
|
@Suite(.serialized)
|
|
struct AlibabaTokenPlanUsageParsingTests {
|
|
@Test
|
|
func `parses subscription summary payload`() throws {
|
|
let now = Date(timeIntervalSince1970: 1_700_000_000)
|
|
let json = """
|
|
{
|
|
"Success": true,
|
|
"Data": {
|
|
"TotalCount": 1,
|
|
"TotalValue": 1000,
|
|
"TotalSurplusValue": 875,
|
|
"NearestExpireDate": 1701000000000
|
|
},
|
|
"Code": "200"
|
|
}
|
|
"""
|
|
|
|
let snapshot = try AlibabaTokenPlanUsageFetcher.parseUsageSnapshot(from: Data(json.utf8), now: now)
|
|
|
|
#expect(snapshot.planName == "TOKEN PLAN")
|
|
#expect(snapshot.usedQuota == 125)
|
|
#expect(snapshot.totalQuota == 1000)
|
|
#expect(snapshot.remainingQuota == 875)
|
|
#expect(snapshot.resetsAt == Date(timeIntervalSince1970: 1_701_000_000))
|
|
#expect(snapshot.toUsageSnapshot().primary?.usedPercent == 12.5)
|
|
}
|
|
|
|
@Test
|
|
func `parses nested subscription summary body`() throws {
|
|
let body = """
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"totalCount": 1,
|
|
"totalSurplusValue": 750,
|
|
"totalValue": 1000
|
|
}
|
|
}
|
|
"""
|
|
let payload = ["successResponse": ["body": body]]
|
|
let data = try JSONSerialization.data(withJSONObject: payload)
|
|
|
|
let snapshot = try AlibabaTokenPlanUsageFetcher.parseUsageSnapshot(from: data)
|
|
|
|
#expect(snapshot.planName == "TOKEN PLAN")
|
|
#expect(snapshot.usedQuota == 250)
|
|
#expect(snapshot.remainingQuota == 750)
|
|
#expect(snapshot.totalQuota == 1000)
|
|
#expect(snapshot.toUsageSnapshot().primary?.usedPercent == 25)
|
|
}
|
|
|
|
@Test
|
|
func `empty subscription summary stays visible without quota window`() throws {
|
|
let json = """
|
|
{
|
|
"Success": true,
|
|
"Data": {
|
|
"TotalCount": 0
|
|
}
|
|
}
|
|
"""
|
|
|
|
let snapshot = try AlibabaTokenPlanUsageFetcher.parseUsageSnapshot(from: Data(json.utf8))
|
|
|
|
#expect(snapshot.planName == nil)
|
|
#expect(snapshot.totalQuota == nil)
|
|
#expect(snapshot.toUsageSnapshot().primary == nil)
|
|
}
|
|
|
|
@Test
|
|
func `login payload maps to login required`() {
|
|
let json = """
|
|
{
|
|
"code": "ConsoleNeedLogin",
|
|
"message": "You need to log in.",
|
|
"successResponse": false
|
|
}
|
|
"""
|
|
|
|
#expect(throws: AlibabaTokenPlanUsageError.loginRequired) {
|
|
try AlibabaTokenPlanUsageFetcher.parseUsageSnapshot(from: Data(json.utf8))
|
|
}
|
|
}
|
|
|
|
@Test
|
|
func `post only token payload maps to login required`() {
|
|
let json = """
|
|
{
|
|
"code": "PostonlyOrTokenError",
|
|
"message": "Your request has expired. Please refresh the page.",
|
|
"successResponse": false
|
|
}
|
|
"""
|
|
|
|
#expect(throws: AlibabaTokenPlanUsageError.loginRequired) {
|
|
try AlibabaTokenPlanUsageFetcher.parseUsageSnapshot(from: Data(json.utf8))
|
|
}
|
|
}
|
|
|
|
@Test
|
|
func `nested unsuccessful subscription summary maps to API error`() throws {
|
|
let body = """
|
|
{
|
|
"success": false,
|
|
"message": "Subscription lookup failed"
|
|
}
|
|
"""
|
|
let payload = ["successResponse": ["body": body]]
|
|
let data = try JSONSerialization.data(withJSONObject: payload)
|
|
|
|
#expect(throws: AlibabaTokenPlanUsageError.apiError("Subscription lookup failed")) {
|
|
try AlibabaTokenPlanUsageFetcher.parseUsageSnapshot(from: data)
|
|
}
|
|
}
|
|
|
|
@Test
|
|
func `forbidden payload maps to invalid credentials`() {
|
|
let json = """
|
|
{
|
|
"statusCode": 403,
|
|
"message": "Forbidden"
|
|
}
|
|
"""
|
|
|
|
#expect(throws: AlibabaTokenPlanUsageError.invalidCredentials) {
|
|
try AlibabaTokenPlanUsageFetcher.parseUsageSnapshot(from: Data(json.utf8))
|
|
}
|
|
}
|
|
|
|
@Test
|
|
func `failed forbidden payload maps to invalid credentials`() {
|
|
let json = """
|
|
{
|
|
"successResponse": false,
|
|
"statusCode": 403,
|
|
"message": "Forbidden"
|
|
}
|
|
"""
|
|
|
|
#expect(throws: AlibabaTokenPlanUsageError.invalidCredentials) {
|
|
try AlibabaTokenPlanUsageFetcher.parseUsageSnapshot(from: Data(json.utf8))
|
|
}
|
|
}
|
|
|
|
@Test
|
|
func `html login payload maps to login required`() {
|
|
let html = """
|
|
<html>
|
|
<body>Please login to Alibaba Cloud</body>
|
|
</html>
|
|
"""
|
|
|
|
#expect(throws: AlibabaTokenPlanUsageError.loginRequired) {
|
|
try AlibabaTokenPlanUsageFetcher.parseUsageSnapshot(from: Data(html.utf8))
|
|
}
|
|
}
|
|
|
|
@Test
|
|
func `non json payload maps to parse failed`() {
|
|
#expect(throws: AlibabaTokenPlanUsageError.parseFailed("Invalid JSON response")) {
|
|
try AlibabaTokenPlanUsageFetcher.parseUsageSnapshot(from: Data("not-json".utf8))
|
|
}
|
|
}
|
|
|
|
@Test
|
|
func `SEC token preflight falls back to user info`() async throws {
|
|
defer {
|
|
AlibabaTokenPlanStubURLProtocol.handler = nil
|
|
}
|
|
|
|
let hostOverride = "https://alibaba-token-plan.test:9443"
|
|
let environment: [String: String] = [
|
|
AlibabaTokenPlanSettingsReader.hostKey: hostOverride,
|
|
]
|
|
let expectedReferer = AlibabaTokenPlanUsageFetcher.dashboardURL(
|
|
region: .international,
|
|
environment: environment).absoluteString
|
|
|
|
AlibabaTokenPlanStubURLProtocol.handler = { request in
|
|
guard let url = request.url else { throw URLError(.badURL) }
|
|
|
|
if url.host == "alibaba-token-plan.test",
|
|
url.path == "/ap-southeast-1/",
|
|
request.httpMethod == "GET"
|
|
{
|
|
#expect(url.port == 9443)
|
|
return Self.makeResponse(url: url, body: "<html></html>", statusCode: 200)
|
|
}
|
|
|
|
if url.host == "alibaba-token-plan.test",
|
|
url.path == "/tool/user/info.json",
|
|
request.httpMethod == "GET"
|
|
{
|
|
#expect(url.port == 9443)
|
|
#expect(request.value(forHTTPHeaderField: "Cookie") == "login_aliyunid_ticket=ticket; raw_only=keep")
|
|
#expect(request.value(forHTTPHeaderField: "Accept") == "application/json, text/plain, */*")
|
|
let json = """
|
|
{
|
|
"code": "200",
|
|
"data": {
|
|
"secToken": "user-info-token"
|
|
},
|
|
"successResponse": true
|
|
}
|
|
"""
|
|
return Self.makeResponse(url: url, body: json, statusCode: 200)
|
|
}
|
|
|
|
if url.host == "alibaba-token-plan.test", request.httpMethod == "POST" {
|
|
#expect(request.value(forHTTPHeaderField: "Cookie") == "login_aliyunid_ticket=ticket; raw_only=keep")
|
|
#expect(request.value(forHTTPHeaderField: "Origin") == "https://modelstudio.console.alibabacloud.com")
|
|
#expect(request.value(forHTTPHeaderField: "Referer") == expectedReferer)
|
|
let body = Self.requestBodyString(from: request)
|
|
#expect(body.contains("sec_token=user-info-token"))
|
|
#expect(body.contains("GetSubscriptionSummary"))
|
|
#expect(body.contains("BssOpenAPI-V3"))
|
|
#expect(body.contains("ProductCode"))
|
|
#expect(body.contains("sfm_tokenplanteams_dp_intl"))
|
|
let json = """
|
|
{
|
|
"Success": true,
|
|
"Data": {
|
|
"TotalCount": 1,
|
|
"TotalValue": 1000,
|
|
"TotalSurplusValue": 900
|
|
}
|
|
}
|
|
"""
|
|
return Self.makeResponse(url: url, body: json, statusCode: 200)
|
|
}
|
|
|
|
throw URLError(.unsupportedURL)
|
|
}
|
|
|
|
let configuration = URLSessionConfiguration.ephemeral
|
|
configuration.protocolClasses = [AlibabaTokenPlanStubURLProtocol.self]
|
|
let session = URLSession(configuration: configuration)
|
|
let snapshot = try await AlibabaTokenPlanUsageFetcher.fetchUsage(
|
|
apiCookieHeader: "login_aliyunid_ticket=ticket; raw_only=keep",
|
|
dashboardCookieHeader: "login_aliyunid_ticket=ticket; raw_only=keep",
|
|
environment: environment,
|
|
session: session)
|
|
|
|
#expect(snapshot.planName == "TOKEN PLAN")
|
|
}
|
|
|
|
@Test
|
|
func `SEC token preflight uses injected session`() async throws {
|
|
AlibabaTokenPlanStubURLProtocol.handler = { request in
|
|
guard let url = request.url else { throw URLError(.badURL) }
|
|
|
|
if url.host == "session-token.test", request.httpMethod == "GET" {
|
|
return Self.makeResponse(
|
|
url: url,
|
|
body: "<html><script>sec_token = \"session-html-token\";</script></html>",
|
|
statusCode: 200)
|
|
}
|
|
|
|
if url.host == "session-token.test", request.httpMethod == "POST" {
|
|
let body = Self.requestBodyString(from: request)
|
|
#expect(body.contains("sec_token=session-html-token"))
|
|
let json = """
|
|
{
|
|
"Success": true,
|
|
"Data": {
|
|
"TotalCount": 1,
|
|
"TotalValue": 1000,
|
|
"TotalSurplusValue": 900
|
|
}
|
|
}
|
|
"""
|
|
return Self.makeResponse(url: url, body: json, statusCode: 200)
|
|
}
|
|
|
|
throw URLError(.unsupportedURL)
|
|
}
|
|
defer {
|
|
AlibabaTokenPlanStubURLProtocol.handler = nil
|
|
}
|
|
|
|
let configuration = URLSessionConfiguration.ephemeral
|
|
configuration.protocolClasses = [AlibabaTokenPlanStubURLProtocol.self]
|
|
let session = URLSession(configuration: configuration)
|
|
let snapshot = try await AlibabaTokenPlanUsageFetcher.fetchUsage(
|
|
apiCookieHeader: "login_aliyunid_ticket=ticket",
|
|
dashboardCookieHeader: "login_aliyunid_ticket=ticket",
|
|
environment: [AlibabaTokenPlanSettingsReader.hostKey: "https://session-token.test"],
|
|
session: session)
|
|
|
|
#expect(snapshot.planName == "TOKEN PLAN")
|
|
}
|
|
|
|
@Test
|
|
func `redirect preserves cookie only for same host HTTPS requests`() throws {
|
|
let sourceURL = try #require(URL(string: "https://bailian.console.aliyun.com/data/api.json"))
|
|
let sameHostURL = try #require(URL(string: "https://bailian.console.aliyun.com/redirected"))
|
|
let crossHostURL = try #require(URL(string: "https://signin.aliyun.com/login"))
|
|
let insecureURL = try #require(URL(string: "http://bailian.console.aliyun.com/redirected"))
|
|
let response = try #require(HTTPURLResponse(
|
|
url: sourceURL,
|
|
statusCode: 302,
|
|
httpVersion: "HTTP/1.1",
|
|
headerFields: nil))
|
|
|
|
var sameHostRequest = URLRequest(url: sameHostURL)
|
|
sameHostRequest.setValue("old=value", forHTTPHeaderField: "Cookie")
|
|
let sameHostRedirect = try #require(AlibabaTokenPlanUsageFetcher.redirectedRequest(
|
|
response: response,
|
|
request: sameHostRequest,
|
|
cookieHeader: "login_aliyunid_ticket=ticket"))
|
|
#expect(sameHostRedirect.value(forHTTPHeaderField: "Cookie") == "login_aliyunid_ticket=ticket")
|
|
|
|
var crossHostRequest = URLRequest(url: crossHostURL)
|
|
crossHostRequest.setValue("old=value", forHTTPHeaderField: "Cookie")
|
|
let crossHostRedirect = try #require(AlibabaTokenPlanUsageFetcher.redirectedRequest(
|
|
response: response,
|
|
request: crossHostRequest,
|
|
cookieHeader: "login_aliyunid_ticket=ticket"))
|
|
#expect(crossHostRedirect.value(forHTTPHeaderField: "Cookie") == nil)
|
|
|
|
let insecureRedirect = AlibabaTokenPlanUsageFetcher.redirectedRequest(
|
|
response: response,
|
|
request: URLRequest(url: insecureURL),
|
|
cookieHeader: "login_aliyunid_ticket=ticket")
|
|
#expect(insecureRedirect == nil)
|
|
}
|
|
|
|
@Test
|
|
func `dashboard redirect preserves dashboard cookie header`() throws {
|
|
let sourceURL = try #require(URL(string: "https://bailian.console.aliyun.com/cn-beijing"))
|
|
let targetURL = try #require(URL(string: "https://bailian.console.aliyun.com/redirected"))
|
|
let response = try #require(HTTPURLResponse(
|
|
url: sourceURL,
|
|
statusCode: 302,
|
|
httpVersion: "HTTP/1.1",
|
|
headerFields: nil))
|
|
var request = URLRequest(url: targetURL)
|
|
request.setValue("api_only=wrong", forHTTPHeaderField: "Cookie")
|
|
|
|
let redirected = try #require(AlibabaTokenPlanUsageFetcher.redirectedRequest(
|
|
response: response,
|
|
request: request,
|
|
cookieHeader: "dashboard_only=keep"))
|
|
|
|
#expect(redirected.value(forHTTPHeaderField: "Cookie") == "dashboard_only=keep")
|
|
}
|
|
|
|
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) ?? ""
|
|
}
|
|
if let stream = request.httpBodyStream {
|
|
stream.open()
|
|
defer {
|
|
stream.close()
|
|
}
|
|
var data = Data()
|
|
var buffer = [UInt8](repeating: 0, count: 1024)
|
|
while stream.hasBytesAvailable {
|
|
let count = stream.read(&buffer, maxLength: buffer.count)
|
|
if count <= 0 {
|
|
break
|
|
}
|
|
data.append(buffer, count: count)
|
|
}
|
|
return String(data: data, encoding: .utf8) ?? ""
|
|
}
|
|
return ""
|
|
}
|
|
}
|
|
|
|
@Suite(.serialized)
|
|
struct AlibabaTokenPlanWebStrategyTests {
|
|
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 clearCookieCaches() {
|
|
CookieHeaderCache.clear(provider: .alibabatokenplan)
|
|
for region in AlibabaTokenPlanAPIRegion.allCases {
|
|
CookieHeaderCache.clear(provider: .alibabatokenplan, scope: region.cookieCacheScope)
|
|
}
|
|
}
|
|
|
|
@Test
|
|
func `auto web strategy surfaces cookie import errors`() async throws {
|
|
let strategy = AlibabaTokenPlanWebFetchStrategy()
|
|
let settings = ProviderSettingsSnapshot.make(
|
|
alibabaTokenPlan: ProviderSettingsSnapshot.AlibabaTokenPlanProviderSettings(
|
|
cookieSource: .auto,
|
|
manualCookieHeader: nil))
|
|
let context = ProviderFetchContext(
|
|
runtime: .cli,
|
|
sourceMode: .web,
|
|
includeCredits: false,
|
|
webTimeout: 1,
|
|
webDebugDumpHTML: false,
|
|
verbose: false,
|
|
env: [:],
|
|
settings: settings,
|
|
fetcher: UsageFetcher(environment: [:]),
|
|
claudeFetcher: StubClaudeFetcher(),
|
|
browserDetection: BrowserDetection(cacheTTL: 0))
|
|
|
|
self.clearCookieCaches()
|
|
AlibabaCodingPlanCookieImporter.importSessionOverrideForTesting = { _, _ in
|
|
throw AlibabaCodingPlanSettingsError.missingCookie(
|
|
details: "macOS Keychain denied access to Chrome Safe Storage.")
|
|
}
|
|
defer {
|
|
AlibabaCodingPlanCookieImporter.importSessionOverrideForTesting = nil
|
|
self.clearCookieCaches()
|
|
}
|
|
|
|
#expect(await strategy.isAvailable(context))
|
|
|
|
do {
|
|
_ = try AlibabaTokenPlanWebFetchStrategy.resolveCookieHeader(context: context, allowCached: false)
|
|
Issue.record("Expected cookie import failure to be surfaced")
|
|
} catch let error as AlibabaTokenPlanSettingsError {
|
|
guard case let .missingCookie(details) = error else {
|
|
Issue.record("Expected missingCookie, got \(error)")
|
|
return
|
|
}
|
|
#expect(details == "macOS Keychain denied access to Chrome Safe Storage.")
|
|
#expect(error.localizedDescription.contains("Alibaba Token Plan"))
|
|
#expect(!error.localizedDescription.contains("Alibaba Coding Plan"))
|
|
}
|
|
}
|
|
|
|
@Test
|
|
func `auto web strategy imports subscription scoped token plan cookies`() throws {
|
|
let strategy = AlibabaTokenPlanWebFetchStrategy()
|
|
let settings = ProviderSettingsSnapshot.make(
|
|
alibabaTokenPlan: ProviderSettingsSnapshot.AlibabaTokenPlanProviderSettings(
|
|
cookieSource: .auto,
|
|
manualCookieHeader: nil))
|
|
let context = ProviderFetchContext(
|
|
runtime: .cli,
|
|
sourceMode: .web,
|
|
includeCredits: false,
|
|
webTimeout: 1,
|
|
webDebugDumpHTML: false,
|
|
verbose: false,
|
|
env: [:],
|
|
settings: settings,
|
|
fetcher: UsageFetcher(environment: [:]),
|
|
claudeFetcher: StubClaudeFetcher(),
|
|
browserDetection: BrowserDetection(cacheTTL: 0))
|
|
|
|
self.clearCookieCaches()
|
|
AlibabaCodingPlanCookieImporter.importSessionOverrideForTesting = { _, _ in
|
|
AlibabaCodingPlanCookieImporter.SessionInfo(
|
|
cookies: [
|
|
self.cookie(name: "login_aliyunid_ticket", value: "ticket", domain: ".alibabacloud.com"),
|
|
self.cookie(name: "login_current_pk", value: "account", domain: ".alibabacloud.com"),
|
|
self.cookie(
|
|
name: "dashboard_only",
|
|
value: "dashboard",
|
|
domain: "modelstudio.console.alibabacloud.com"),
|
|
self.cookie(
|
|
name: "bailian_only",
|
|
value: "bailian",
|
|
domain: "bailian.console.aliyun.com"),
|
|
self.cookie(name: "aliyun_only", value: "aliyun", domain: ".aliyun.com"),
|
|
],
|
|
sourceLabel: "Chrome Default")
|
|
}
|
|
defer {
|
|
AlibabaCodingPlanCookieImporter.importSessionOverrideForTesting = nil
|
|
self.clearCookieCaches()
|
|
}
|
|
|
|
let headers = try AlibabaTokenPlanWebFetchStrategy.resolveCookieHeaders(context: context, allowCached: false)
|
|
|
|
#expect(headers.apiCookieHeader == headers.dashboardCookieHeader)
|
|
#expect(headers.apiCookieHeader.contains("dashboard_only=dashboard"))
|
|
#expect(!headers.apiCookieHeader.contains("bailian_only=bailian"))
|
|
#expect(!headers.apiCookieHeader.contains("aliyun_only=aliyun"))
|
|
#expect(headers.dashboardCookieHeader.contains("dashboard_only=dashboard"))
|
|
#expect(!headers.dashboardCookieHeader.contains("bailian_only=bailian"))
|
|
#expect(!headers.dashboardCookieHeader.contains("aliyun_only=aliyun"))
|
|
|
|
AlibabaCodingPlanCookieImporter.importSessionOverrideForTesting = { _, _ in
|
|
throw AlibabaCodingPlanSettingsError.missingCookie(details: "unexpected import")
|
|
}
|
|
let cachedHeaders = try AlibabaTokenPlanWebFetchStrategy.resolveCookieHeaders(
|
|
context: context,
|
|
allowCached: true)
|
|
#expect(cachedHeaders.apiCookieHeader == headers.apiCookieHeader)
|
|
#expect(cachedHeaders.dashboardCookieHeader == headers.dashboardCookieHeader)
|
|
#expect(strategy.id == "alibaba-token-plan.web")
|
|
}
|
|
|
|
@Test
|
|
func `auto web strategy scopes imported cookies to environment overrides`() throws {
|
|
let settings = ProviderSettingsSnapshot.make(
|
|
alibabaTokenPlan: ProviderSettingsSnapshot.AlibabaTokenPlanProviderSettings(
|
|
cookieSource: .auto,
|
|
manualCookieHeader: nil))
|
|
let environment = [
|
|
AlibabaTokenPlanSettingsReader.quotaURLKey: "https://quota.token-plan.test/data/api.json",
|
|
AlibabaTokenPlanSettingsReader.hostKey: "https://dashboard.token-plan.test",
|
|
]
|
|
let context = ProviderFetchContext(
|
|
runtime: .cli,
|
|
sourceMode: .web,
|
|
includeCredits: false,
|
|
webTimeout: 1,
|
|
webDebugDumpHTML: false,
|
|
verbose: false,
|
|
env: environment,
|
|
settings: settings,
|
|
fetcher: UsageFetcher(environment: environment),
|
|
claudeFetcher: StubClaudeFetcher(),
|
|
browserDetection: BrowserDetection(cacheTTL: 0))
|
|
|
|
self.clearCookieCaches()
|
|
AlibabaCodingPlanCookieImporter.importSessionOverrideForTesting = { _, _ in
|
|
AlibabaCodingPlanCookieImporter.SessionInfo(
|
|
cookies: [
|
|
self.cookie(name: "login_aliyunid_ticket", value: "ticket", domain: ".token-plan.test"),
|
|
self.cookie(name: "api_only", value: "api", domain: "quota.token-plan.test"),
|
|
self.cookie(name: "dashboard_only", value: "dashboard", domain: "dashboard.token-plan.test"),
|
|
self.cookie(name: "prod_api_only", value: "prod-api", domain: "bailian.console.aliyun.com"),
|
|
self.cookie(
|
|
name: "prod_dashboard_only",
|
|
value: "prod-dashboard",
|
|
domain: "bailian.console.aliyun.com"),
|
|
],
|
|
sourceLabel: "Chrome Default")
|
|
}
|
|
defer {
|
|
AlibabaCodingPlanCookieImporter.importSessionOverrideForTesting = nil
|
|
self.clearCookieCaches()
|
|
}
|
|
|
|
let headers = try AlibabaTokenPlanWebFetchStrategy.resolveCookieHeaders(context: context, allowCached: false)
|
|
|
|
#expect(headers.apiCookieHeader.contains("api_only=api"))
|
|
#expect(!headers.apiCookieHeader.contains("prod_api_only=prod-api"))
|
|
#expect(headers.dashboardCookieHeader.contains("dashboard_only=dashboard"))
|
|
#expect(!headers.dashboardCookieHeader.contains("prod_dashboard_only=prod-dashboard"))
|
|
}
|
|
|
|
@Test
|
|
func `cached browser cookies stay isolated by gateway region`() throws {
|
|
self.clearCookieCaches()
|
|
defer { self.clearCookieCaches() }
|
|
CookieHeaderCache.store(
|
|
provider: .alibabatokenplan,
|
|
scope: AlibabaTokenPlanAPIRegion.international.cookieCacheScope,
|
|
cookieHeader: "login_aliyunid_ticket=intl-ticket; gateway=intl",
|
|
sourceLabel: "International fixture")
|
|
CookieHeaderCache.store(
|
|
provider: .alibabatokenplan,
|
|
scope: AlibabaTokenPlanAPIRegion.chinaMainland.cookieCacheScope,
|
|
cookieHeader: "login_aliyunid_ticket=cn-ticket; gateway=cn",
|
|
sourceLabel: "China fixture")
|
|
AlibabaCodingPlanCookieImporter.importSessionOverrideForTesting = { _, _ in
|
|
throw AlibabaCodingPlanSettingsError.missingCookie(details: "unexpected import")
|
|
}
|
|
defer { AlibabaCodingPlanCookieImporter.importSessionOverrideForTesting = nil }
|
|
|
|
let context = self.context(region: .international)
|
|
let international = try AlibabaTokenPlanWebFetchStrategy.resolveCookieHeaders(
|
|
context: context,
|
|
allowCached: true,
|
|
region: .international)
|
|
let china = try AlibabaTokenPlanWebFetchStrategy.resolveCookieHeaders(
|
|
context: context,
|
|
allowCached: true,
|
|
region: .chinaMainland)
|
|
|
|
#expect(international.apiCookieHeader.contains("gateway=intl"))
|
|
#expect(!international.apiCookieHeader.contains("gateway=cn"))
|
|
#expect(china.apiCookieHeader.contains("gateway=cn"))
|
|
#expect(!china.apiCookieHeader.contains("gateway=intl"))
|
|
}
|
|
|
|
@Test
|
|
func `legacy unscoped cache migrates only to China gateway`() throws {
|
|
self.clearCookieCaches()
|
|
defer {
|
|
AlibabaCodingPlanCookieImporter.importSessionOverrideForTesting = nil
|
|
self.clearCookieCaches()
|
|
}
|
|
CookieHeaderCache.store(
|
|
provider: .alibabatokenplan,
|
|
cookieHeader: "login_aliyunid_ticket=legacy; gateway=legacy-cn",
|
|
sourceLabel: "Legacy fixture")
|
|
AlibabaCodingPlanCookieImporter.importSessionOverrideForTesting = { _, _ in
|
|
AlibabaCodingPlanCookieImporter.SessionInfo(
|
|
cookies: [
|
|
self.cookie(
|
|
name: "login_aliyunid_ticket",
|
|
value: "intl",
|
|
domain: ".alibabacloud.com"),
|
|
self.cookie(
|
|
name: "gateway",
|
|
value: "intl",
|
|
domain: "modelstudio.console.alibabacloud.com"),
|
|
],
|
|
sourceLabel: "International fixture")
|
|
}
|
|
let context = self.context(region: .international)
|
|
|
|
let international = try AlibabaTokenPlanWebFetchStrategy.resolveCookieHeaders(
|
|
context: context,
|
|
allowCached: true,
|
|
region: .international)
|
|
AlibabaCodingPlanCookieImporter.importSessionOverrideForTesting = { _, _ in
|
|
throw AlibabaCodingPlanSettingsError.missingCookie(details: "unexpected China import")
|
|
}
|
|
let china = try AlibabaTokenPlanWebFetchStrategy.resolveCookieHeaders(
|
|
context: context,
|
|
allowCached: true,
|
|
region: .chinaMainland)
|
|
|
|
#expect(international.apiCookieHeader.contains("gateway=intl"))
|
|
#expect(!international.apiCookieHeader.contains("gateway=legacy-cn"))
|
|
#expect(china.apiCookieHeader.contains("gateway=legacy-cn"))
|
|
#expect(CookieHeaderCache.load(provider: .alibabatokenplan) == nil)
|
|
#expect(CookieHeaderCache.load(
|
|
provider: .alibabatokenplan,
|
|
scope: AlibabaTokenPlanAPIRegion.chinaMainland.cookieCacheScope) != nil)
|
|
}
|
|
|
|
private func context(region: AlibabaTokenPlanAPIRegion) -> ProviderFetchContext {
|
|
let settings = ProviderSettingsSnapshot.make(
|
|
alibabaTokenPlan: ProviderSettingsSnapshot.AlibabaTokenPlanProviderSettings(
|
|
cookieSource: .auto,
|
|
manualCookieHeader: nil,
|
|
apiRegion: region))
|
|
return ProviderFetchContext(
|
|
runtime: .cli,
|
|
sourceMode: .web,
|
|
includeCredits: false,
|
|
webTimeout: 1,
|
|
webDebugDumpHTML: false,
|
|
verbose: false,
|
|
env: [:],
|
|
settings: settings,
|
|
fetcher: UsageFetcher(environment: [:]),
|
|
claudeFetcher: StubClaudeFetcher(),
|
|
browserDetection: BrowserDetection(cacheTTL: 0))
|
|
}
|
|
|
|
private func cookie(
|
|
name: String,
|
|
value: String,
|
|
domain: String,
|
|
path: String = "/",
|
|
expires: Date = Date(timeIntervalSinceNow: 3600)) -> HTTPCookie
|
|
{
|
|
HTTPCookie(properties: [
|
|
.domain: domain,
|
|
.path: path,
|
|
.name: name,
|
|
.value: value,
|
|
.expires: expires,
|
|
.secure: true,
|
|
])!
|
|
}
|
|
}
|
|
|
|
final class AlibabaTokenPlanStubURLProtocol: 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 host == "bailian.console.aliyun.com" ||
|
|
host == "alibaba-token-plan.test" ||
|
|
host == "session-token.test"
|
|
}
|
|
|
|
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() {}
|
|
}
|