Files
pikasTech-apistate/src/account-score-native.test.ts
T
2026-07-17 13:10:39 +02:00

119 lines
5.6 KiB
TypeScript

import { describe, expect, test } from "bun:test";
import { aggregateNativeGroupScore, collectNativeScores } from "./account-score-native";
import type { Sub2ApiClient } from "./sub2api-client";
const availableOps = { status: "available" as const, data: {}, reason: null };
describe("aggregateNativeGroupScore", () => {
test("scores upstream failures and excludes client input locally", () => {
const group = { id: 2, name: "pool", platform: "openai", status: "active" };
const account = { id: 15, name: "primary 0.02", platform: "openai", status: "active", schedulable: true, priority: 1 };
const usage = Array.from({ length: 20 }, (_, index) => ({
id: index + 1,
account_id: 15,
group_id: 2,
model: "gpt-test",
stream: true,
input_tokens: 100,
output_tokens: 20,
actual_cost: 0.1,
duration_ms: 20_000,
first_token_ms: 5_000 + index,
created_at: "2026-07-17T09:00:00Z",
}));
const result = aggregateNativeGroupScore({
group,
accounts: [account],
usage,
requestErrors: [
{ id: 1, request_id: "req-fail", account_id: 15, status_code: 502, phase: "upstream", type: "upstream_error", message: "Upstream service temporarily unavailable" },
{ id: 2, request_id: "req-input", account_id: 15, status_code: 400, phase: "client", type: "invalid_request", message: "Input must be a list" },
],
systemLogs: [
{ id: 10, created_at: "2026-07-17T09:01:00Z", message: "openai.upstream_failover_switching", request_id: "req-fail", account_id: 15, extra: { group_id: 2 } },
{ id: 11, created_at: "2026-07-17T09:01:01Z", message: "http request completed", request_id: "req-fail", account_id: 15, extra: { group_id: 2, status_code: 200 } },
],
overview: {
request_count_total: 22,
error_count_total: 1,
upstream_error_count_excl_429_529: 1,
upstream_429_count: 0,
upstream_529_count: 0,
error_rate: 1 / 22,
upstream_error_rate: 1 / 22,
ttft: { p99_ms: 5019 },
},
availability: availableOps,
concurrency: availableOps,
});
const row = result.accounts[0]!;
expect(row.successRequests).toBe(20);
expect(row.failureRequests).toBe(1);
expect(row.scoreableUpstreamErrorRequests).toBe(1);
expect(row.excludedNonUpstreamErrorRequests).toBe(1);
expect(row.failoverRequests).toBe(1);
expect(row.failoverRecovered).toBe(1);
expect((row.usage as Record<string, unknown>).tokenCount).toBe(2400);
expect((row.usage as Record<string, unknown>).upstreamCostCny).toBe(0.04);
expect(result.collection.mode).toBe("nc01-native-api-local-aggregation");
});
test("does not attribute another group policy event", () => {
const result = aggregateNativeGroupScore({
group: { id: 2, name: "pool", platform: "openai", status: "active" },
accounts: [{ id: 15, name: "primary 0.0", platform: "openai", status: "active", schedulable: true, priority: 1 }],
usage: [],
requestErrors: [],
systemLogs: [{ id: 1, created_at: "2026-07-17T09:00:00Z", message: "openai.forward_failed", request_id: "req-other", account_id: 15, extra: { group_id: 3 } }],
overview: {},
availability: {
status: "available",
data: {
group: { "2": { total_accounts: 1, available_count: 0, rate_limit_count: 0, error_count: 1 } },
account: { "15": { account_id: 15, group_id: 2, is_available: false } },
},
reason: null,
},
concurrency: { status: "available", data: { group: { "2": { current_in_use: 0, max_capacity: 10, waiting_in_queue: 0 } } }, reason: null },
});
expect(result.accounts[0]!.forwardFailedRequests).toBe(0);
expect(result.accounts[0]!.currentlyAvailable).toBe(false);
expect(result.group.unavailableAccountCount).toBe(1);
expect(result.group.maxCapacity).toBe(10);
});
test("collects groups sequentially and deduplicates marker results", async () => {
const calls: string[] = [];
const fake = {
async listGroups() {
calls.push("groups");
return [
{ id: 2, name: "pool", platform: "openai", status: "active" },
{ id: 3, name: "self", platform: "openai", status: "active" },
];
},
async listGroupAccounts(groupId: number) { calls.push(`accounts:${groupId}`); return []; },
async getOpsOverview(groupId: number) { calls.push(`overview:${groupId}`); return {}; },
async getOpsAccountAvailability(groupId: number) { calls.push(`availability:${groupId}`); return {}; },
async getOpsConcurrency(groupId: number) { calls.push(`concurrency:${groupId}`); return {}; },
async listGroupUsage(groupId: number) { calls.push(`usage:${groupId}`); return []; },
async listRequestErrors(groupId: number) { calls.push(`errors:${groupId}`); return []; },
} as unknown as Sub2ApiClient;
const events = {
async collect() {
calls.push("events");
return {
events: [{ id: 1, created_at: "2026-07-17T09:00:00Z", message: "account_temp_unschedulable", account_id: 15 }],
evidence: { source: "test", eventCount: 1 },
};
},
};
const result = await collectNativeScores(fake, events, "8h", new Date("2026-07-17T10:00:00Z"));
expect(result.groups).toHaveLength(2);
expect(calls.filter((call) => call === "events")).toHaveLength(1);
expect(calls.indexOf("accounts:3")).toBeGreaterThan(calls.indexOf("errors:2"));
const collection = result.collection.groups as Array<Record<string, unknown>>;
expect(collection[0]!.policyEventRows).toBe(1);
});
});