96 lines
5.2 KiB
TypeScript
96 lines
5.2 KiB
TypeScript
import { expect, test } from "@playwright/test";
|
|
import { createServer, type ViteDevServer } from "vite";
|
|
import { resolve } from "node:path";
|
|
|
|
import { adminDiagnosticsFixture, adminServicesStorageFixture } from "../fixtures/wp6-05-state.js";
|
|
|
|
let vite: ViteDevServer;
|
|
let webUrl: string;
|
|
|
|
const adminSession = {
|
|
admin: { role: "super_admin", status: "active", user_id: "00000000-0000-4000-8000-000000000605" },
|
|
audience: "admin",
|
|
authenticated: true,
|
|
expires_at: "2026-09-02T09:30:00.000Z",
|
|
};
|
|
|
|
test.beforeAll(async () => {
|
|
vite = await createServer({ configFile: resolve("apps/web/vite.config.ts"), root: resolve("apps/web"), server: { host: "127.0.0.1", port: 0 } });
|
|
await vite.listen();
|
|
const address = vite.httpServer?.address();
|
|
if (!address || typeof address === "string") throw new Error("Vite did not expose a test port.");
|
|
webUrl = `http://127.0.0.1:${address.port}`;
|
|
});
|
|
|
|
test.afterAll(async () => vite.close());
|
|
|
|
test("TDD-WP6-DIAG-001-redacted-diagnostics renders state and diagnostics without sensitive fields", async ({ page }) => {
|
|
await page.route("**/api/v1/admin-auth/session", (route) => route.fulfill({ body: JSON.stringify(adminSession), contentType: "application/json", status: 200 }));
|
|
await page.route("**/api/v1/admin/services-storage", (route) => route.fulfill({ body: JSON.stringify(adminServicesStorageFixture), contentType: "application/json", status: 200 }));
|
|
await page.route("**/api/v1/admin/diagnostics", (route) => route.fulfill({ body: JSON.stringify(adminDiagnosticsFixture), contentType: "application/json", status: 200 }));
|
|
await page.goto(`${webUrl}/admin/services-storage`);
|
|
|
|
await expect(page.getByRole("heading", { level: 2, name: "服务与存储" })).toBeVisible();
|
|
await expect(page.getByText("Resend", { exact: true })).toBeVisible();
|
|
await expect(page.getByText("额度暂停", { exact: true })).toBeVisible();
|
|
await expect(page.getByText("4.25 GB", { exact: true })).toBeVisible();
|
|
await expect(page.getByRole("button", { name: "复制诊断" })).toBeEnabled();
|
|
await expect(page.locator("body")).not.toContainText(/secret|password|example\.invalid|C:\\Users/i);
|
|
});
|
|
|
|
test("TDD-WP6-STATE-001-three-model-states refetches REST truth after a runtime event", async ({ page }) => {
|
|
await page.addInitScript(() => {
|
|
class FakeEventSource {
|
|
static instance: FakeEventSource | undefined;
|
|
onmessage: ((event: MessageEvent) => void) | null = null;
|
|
constructor() { FakeEventSource.instance = this; }
|
|
close() { if (FakeEventSource.instance === this) FakeEventSource.instance = undefined; }
|
|
}
|
|
Object.defineProperty(window, "EventSource", { configurable: true, value: FakeEventSource });
|
|
(window as unknown as { emitDadaEvent: () => void }).emitDadaEvent = () => FakeEventSource.instance?.onmessage?.({ data: "{}" } as MessageEvent);
|
|
});
|
|
await page.route("**/api/v1/admin-auth/session", (route) => route.fulfill({ body: JSON.stringify({ ...adminSession, csrf_token: "csrf-admin-model-fixture-000000000000000000000000000000000" }), contentType: "application/json", status: 200 }));
|
|
let runtimeAvailable = false;
|
|
let modelCalls = 0;
|
|
const configuration = () => {
|
|
const ids = ["gemini-3.1-flash-image-preview", "gemini-3-pro-image-preview", "gpt-image-2"];
|
|
return {
|
|
config_set_version: 1,
|
|
configured_default_model_id: ids[0],
|
|
recommended_model_id: runtimeAvailable ? ids[1] : null,
|
|
models: ids.map((modelId, index) => ({
|
|
config_version: 1,
|
|
contract_evidence_ref: null,
|
|
contract_validation_status: "verified",
|
|
credit_cost: 1,
|
|
display_name: ["Gemini 3.1 Flash Image Preview", "Gemini 3 Pro Image Preview", "GPT Image 2"][index],
|
|
enabled: true,
|
|
error_mapping_profile: { timeout: "upstream_timeout" },
|
|
gateway_account_ref: "mock-gateway",
|
|
is_default: index === 0,
|
|
model_id: modelId,
|
|
prompt_max_length: 1_000,
|
|
recommendation_priority: index + 1,
|
|
reference_limits: { max_file_bytes: 1_024, max_files: 2, max_total_bytes: 2_048 },
|
|
route_profile: { endpoint: "https://mock.invalid/v1/images" },
|
|
runtime_availability: { available_for_new_jobs: runtimeAvailable, checked_at: "2026-08-02T15:00:00.000Z", reason: runtimeAvailable ? "available" : "gateway_balance_insufficient" },
|
|
safety_source: "provider",
|
|
supported_ratios: ["3:4", "1:1", "4:3", "9:16"],
|
|
})),
|
|
};
|
|
};
|
|
await page.route("**/api/v1/models", (route) => {
|
|
modelCalls += 1;
|
|
return route.fulfill({ body: JSON.stringify(configuration()), contentType: "application/json", status: 200 });
|
|
});
|
|
|
|
await page.goto(`${webUrl}/admin/models`);
|
|
await expect(page.getByText("当前推荐").locator("..").getByText("无", { exact: true })).toBeVisible();
|
|
expect(modelCalls).toBeGreaterThanOrEqual(1);
|
|
runtimeAvailable = true;
|
|
await page.evaluate(() => (window as unknown as { emitDadaEvent: () => void }).emitDadaEvent());
|
|
await expect(page.getByText("当前推荐").locator("..").getByText("gemini-3-pro-image-preview", { exact: true })).toBeVisible();
|
|
await expect(page.getByText("配置默认").locator("..").getByText("gemini-3.1-flash-image-preview", { exact: true })).toBeVisible();
|
|
expect(modelCalls).toBeGreaterThanOrEqual(2);
|
|
});
|