관리자 증거와 계정 흐름을 정돈
This commit is contained in:
parent
21bbe3f98d
commit
34aff65cb0
9 changed files with 1100 additions and 193 deletions
|
|
@ -3,6 +3,15 @@
|
|||
// route fixture로 상태를 고정해 검증한다. 실제 AI 엔진 턴 생성은 하지 않는다.
|
||||
import { expect, test, type Page } from "@playwright/test";
|
||||
|
||||
type UsageCostBasis =
|
||||
| "provider_estimate"
|
||||
| "provider_reported"
|
||||
| "reference_rate"
|
||||
| "reference_upper_bound"
|
||||
| "partial"
|
||||
| "partial_upper_bound"
|
||||
| "unavailable";
|
||||
|
||||
interface AdminUsageBreakdown {
|
||||
provider: string;
|
||||
model: string;
|
||||
|
|
@ -14,7 +23,7 @@ interface AdminUsageBreakdown {
|
|||
estimated_cost_usd: number;
|
||||
token_metered_turns: number;
|
||||
token_unmetered_turns: number;
|
||||
cost_basis: "provider_estimate" | "provider_reported" | "reference_rate" | "unavailable";
|
||||
cost_basis: UsageCostBasis;
|
||||
rate_label?: string | null;
|
||||
rate_source_url?: string | null;
|
||||
}
|
||||
|
|
@ -25,6 +34,7 @@ interface AdminUsageDailyCost {
|
|||
tokens_in: number;
|
||||
tokens_out: number;
|
||||
cost_usd: number;
|
||||
cost_basis?: UsageCostBasis;
|
||||
}
|
||||
|
||||
interface AdminUsageEvaluatorCache {
|
||||
|
|
@ -52,11 +62,13 @@ interface AdminUsageResponse {
|
|||
cost_usd: number;
|
||||
recorded_cost_usd: number;
|
||||
estimated_cost_usd: number;
|
||||
cost_basis?: UsageCostBasis;
|
||||
budget: {
|
||||
limit_usd: number;
|
||||
used_ratio: number;
|
||||
remaining_usd: number | null;
|
||||
status: "disabled" | "ok" | "warn" | "exceeded";
|
||||
status: "disabled" | "ok" | "warn" | "exceeded" | "indeterminate";
|
||||
cost_basis?: UsageCostBasis;
|
||||
};
|
||||
evaluator_cache?: AdminUsageEvaluatorCache;
|
||||
by_provider: AdminUsageBreakdown[];
|
||||
|
|
@ -90,10 +102,17 @@ function usageFixture(
|
|||
tokens_in: 0,
|
||||
tokens_out: 0,
|
||||
cost_usd: 0,
|
||||
cost_basis: "provider_reported",
|
||||
// 실 API는 cost_usd = recorded + estimated 로 집계한다. 기본은 전액 기록값으로 둔다.
|
||||
recorded_cost_usd: overrides.cost_usd ?? 0,
|
||||
estimated_cost_usd: 0,
|
||||
budget: { limit_usd: 0, used_ratio: 0, remaining_usd: null, status: "disabled" },
|
||||
budget: {
|
||||
limit_usd: 0,
|
||||
used_ratio: 0,
|
||||
remaining_usd: null,
|
||||
status: "disabled",
|
||||
cost_basis: "provider_reported",
|
||||
},
|
||||
evaluator_cache: {
|
||||
enabled: false,
|
||||
entries: 0,
|
||||
|
|
@ -482,7 +501,7 @@ test.describe("full sweep: admin ai operations", () => {
|
|||
await expect(chart.locator(".aic-chart__day")).toHaveCount(3);
|
||||
await expect(chart.locator(".aic-chart__day").first()).toHaveAttribute(
|
||||
"title",
|
||||
"2026-07-13 $1.42",
|
||||
"2026-07-13 $1.42 · Provider 보고",
|
||||
);
|
||||
await expect(chart.locator(".aic-chart__day").first()).toContainText("8턴");
|
||||
|
||||
|
|
@ -521,6 +540,70 @@ test.describe("full sweep: admin ai operations", () => {
|
|||
await expect(page.locator(".aic-table")).toHaveCount(0);
|
||||
});
|
||||
|
||||
test("does not present a partial upper bound as an exact cost", async ({ page }) => {
|
||||
const state = createMockState({
|
||||
usageForWindow: (days) =>
|
||||
usageFixture(days, {
|
||||
total_turns: 2,
|
||||
metered_turns: 2,
|
||||
token_metered_turns: 2,
|
||||
tokens_in: 2_000_000,
|
||||
tokens_out: 2_000_000,
|
||||
cost_usd: 4.5,
|
||||
recorded_cost_usd: 0,
|
||||
estimated_cost_usd: 4.5,
|
||||
cost_basis: "partial_upper_bound",
|
||||
budget: {
|
||||
limit_usd: 20,
|
||||
used_ratio: 0.225,
|
||||
remaining_usd: null,
|
||||
status: "indeterminate",
|
||||
cost_basis: "partial_upper_bound",
|
||||
},
|
||||
by_provider: [
|
||||
{
|
||||
provider: "agy_cli",
|
||||
model: "gemini-3.7-flash-high",
|
||||
turns: 2,
|
||||
token_metered_turns: 2,
|
||||
token_unmetered_turns: 0,
|
||||
tokens_in: 2_000_000,
|
||||
tokens_out: 2_000_000,
|
||||
cost_usd: 4.5,
|
||||
recorded_cost_usd: 0,
|
||||
estimated_cost_usd: 4.5,
|
||||
cost_basis: "partial_upper_bound",
|
||||
rate_label: "일부 호출 미산정 · 산정된 부분도 상한 추정",
|
||||
},
|
||||
],
|
||||
daily_cost: [
|
||||
{
|
||||
day: "2026-08-13",
|
||||
turns: 2,
|
||||
tokens_in: 2_000_000,
|
||||
tokens_out: 2_000_000,
|
||||
cost_usd: 4.5,
|
||||
cost_basis: "partial_upper_bound",
|
||||
},
|
||||
],
|
||||
}),
|
||||
});
|
||||
await mockAdminAiSession(page, state);
|
||||
|
||||
await page.goto("/admin/ai");
|
||||
|
||||
await expect(page.locator(".aic-ledger b").first()).toHaveText("미산정");
|
||||
await expect(page.locator(".aic-ledger article").last().locator("b")).toHaveText("—");
|
||||
await expect(page.locator(".aic-budget")).toContainText("예산 미확정");
|
||||
await expect(page.locator(".aic-budget")).toContainText("잔여 미산정 · 사용률 미산정");
|
||||
await expect(page.locator(".aic-chart__value")).toHaveText("미산정");
|
||||
const row = page.locator(".aic-table tbody tr");
|
||||
await expect(row.locator("td").nth(4)).toContainText("미산정");
|
||||
await expect(row.locator("td").nth(4)).toContainText("일부 상한");
|
||||
await expect(row.locator("td").nth(5)).toHaveText("—");
|
||||
await expect(row.locator("td").nth(6)).toHaveText("—");
|
||||
});
|
||||
|
||||
// checklist: admin-ai-engine-config-meta
|
||||
test("shows engine config metadata for durable DB and runtime-only sources", async ({ page }) => {
|
||||
const state = createMockState({
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue