feat: add admin state and diagnostics (TASK-WP6-05)
Dada P0-A isolated Windows CI / validate-and-package (push) Successful in 12m4s

This commit is contained in:
suyx
2026-08-04 01:51:35 +08:00
parent 19212cc1b6
commit 5041dc03c3
15 changed files with 1374 additions and 8 deletions
+179
View File
@@ -0,0 +1,179 @@
import { randomUUID } from "node:crypto";
import { mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import { createApp } from "../../apps/api/src/app.js";
import { createAdminServicesStorageProvider } from "../../apps/api/src/admin-state.js";
import { ManagedStorage } from "../../apps/api/src/managed-storage.js";
import { ModelConfigurationService } from "../../apps/api/src/model-configuration.js";
import { MockResendAdapter } from "../../apps/api/src/resend-adapter.js";
import { RegistrationService } from "../../apps/api/src/registration.js";
import type { AdminDiagnosticsResponse, AdminServicesStorageResponse } from "@dada/shared-contracts";
const now = "2026-08-04T09:30:00.000Z";
const headers = { host: "127.0.0.1:43121", origin: "http://127.0.0.1:43121" };
const roots: string[] = [];
const registrations: RegistrationService[] = [];
const storages: ManagedStorage[] = [];
const servicesFixture: AdminServicesStorageResponse = {
generated_at: now,
services: [
{ checked_at: now, configured: true, impact_scope: "authentication", pause_reason: null, service_id: "resend", status: "active" },
{ checked_at: now, configured: true, impact_scope: "location", pause_reason: "quota_exhausted", service_id: "amap", status: "paused_quota" },
{ checked_at: now, configured: true, impact_scope: "generation", pause_reason: "balance_insufficient", service_id: "ai_gateway", status: "degraded" },
{ checked_at: now, configured: true, impact_scope: "generation", pause_reason: "worker_stopped", service_id: "worker", status: "degraded" },
{ checked_at: now, configured: true, impact_scope: "api", pause_reason: null, service_id: "api", status: "active" },
{ checked_at: now, configured: true, impact_scope: "storage", pause_reason: null, service_id: "asset_root", status: "active" },
],
storage: {
capacity_notice_level: "warning",
cleanup_pending_count: 2,
data_root_ref: "configured_local_data_root",
hard_limit_bytes: 5_368_709_120,
last_measured_at: now,
managed_content_bytes: 4_563_402_752,
remeasurement_required: false,
status: "active",
storage_backend: "local_filesystem",
},
};
const diagnosticsFixture: AdminDiagnosticsResponse = {
generated_at: now,
diagnostic_text: "Dada P0-A\napp_version=0.0.0\napi_status=ready\nworker_status=ready\nstorage_status=active",
services: servicesFixture,
system: {
api_status: "ready",
app_version: "0.0.0",
browser_support: [{ brand: "Google Chrome", major: 128 }, { brand: "Microsoft Edge", major: 128 }],
worker_status: "ready",
},
};
function createRegistration() {
const root = mkdtempSync(join(tmpdir(), "dada-wp6-05-api-"));
roots.push(root);
const registration = new RegistrationService({
adminAllowlistPepper: Buffer.alloc(32, 0xe1),
challengePepper: Buffer.alloc(32, 0xe2),
clock: () => Date.parse(now),
currentPrivacyNoticeVersion: "p0a-registration-notice-v1",
databasePath: join(root, "dada.sqlite3"),
invitePepper: Buffer.alloc(32, 0xe3),
resend: new MockResendAdapter(),
sessionPepper: Buffer.alloc(32, 0xe4),
});
registrations.push(registration);
return registration;
}
function seedAdmin(registration: RegistrationService) {
const userId = randomUUID();
registration.database.prepare(`
INSERT INTO users (user_id, normalized_email, role, status, counts_toward_stage_limit, registration_id, created_at)
VALUES (?, ?, 'super_admin', 'active', 0, ?, ?)
`).run(userId, `${userId}@example.invalid`, randomUUID(), Date.parse(now));
registration.database.prepare("INSERT INTO admin_access (user_id, allowed) VALUES (?, 1)").run(userId);
return registration.issueAuthenticatedSession(userId, "admin");
}
afterEach(() => {
for (const storage of storages.splice(0)) storage.close();
for (const registration of registrations.splice(0)) registration.close();
for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true });
});
describe("admin status aggregation", () => {
it("reads storage and runtime truth without fabricating provider readiness", () => {
const registration = createRegistration();
const root = roots[roots.length - 1]!;
const storage = new ManagedStorage({ dataRoot: root, databasePath: join(root, "dada.sqlite3") });
storages.push(storage);
const models = new ModelConfigurationService({ database: registration.database, clock: () => Date.parse(now) });
const state = createAdminServicesStorageProvider({ database: registration.database, models, storage, clock: () => Date.parse(now) })();
expect(new Set(state.services.map((service) => service.service_id)).size).toBe(6);
expect(state.services.find((service) => service.service_id === "worker")?.status).toBe("unavailable");
expect(state.services.find((service) => service.service_id === "ai_gateway")?.status).toBe("degraded");
expect(state.storage.storage_backend).toBe("local_filesystem");
expect(JSON.stringify(state)).not.toContain("http");
expect(JSON.stringify(state)).not.toContain("@example");
});
});
describe("TDD-WP6-STATE-001-three-model-states", () => {
it("requires an active admin session and keeps model/service state provider-backed", async () => {
const registration = createRegistration();
let serviceCalls = 0;
const app = await createApp({
adminDiagnostics: async () => diagnosticsFixture,
adminServicesStorage: async () => {
serviceCalls += 1;
return servicesFixture;
},
browserGate: false,
networkBoundary: { allowTestPort: true },
registration,
});
const denied = await app.inject({ headers, method: "GET", url: "/api/v1/admin/services-storage" });
expect(denied.statusCode).toBe(401);
expect(serviceCalls).toBe(0);
const session = seedAdmin(registration);
const authorizedHeaders = { ...headers, cookie: `dada_admin_session=${session.sessionToken}` };
const state = await app.inject({ headers: authorizedHeaders, method: "GET", url: "/api/v1/admin/services-storage" });
expect(state.statusCode).toBe(200);
expect(state.json()).toEqual(servicesFixture);
expect(serviceCalls).toBe(1);
expect(JSON.stringify(state.json()).toLowerCase()).not.toMatch(/secret|password|email|absolute/);
const diagnostic = await app.inject({ headers: authorizedHeaders, method: "GET", url: "/api/v1/admin/diagnostics" });
expect(diagnostic.statusCode).toBe(200);
expect(diagnostic.json()).toEqual(diagnosticsFixture);
await app.close();
});
});
describe("TDD-WP6-DIAG-001-redacted-diagnostics", () => {
it("rejects a provider diagnostic that contains a credential or absolute path trap", async () => {
const registration = createRegistration();
const app = await createApp({
adminDiagnostics: async () => ({ ...diagnosticsFixture, diagnostic_text: "api_key=trap C:\\Users\\dada\\secret.txt" }),
browserGate: false,
networkBoundary: { allowTestPort: true },
registration,
});
const session = seedAdmin(registration);
const response = await app.inject({
headers: { ...headers, cookie: `dada_admin_session=${session.sessionToken}` },
method: "GET",
url: "/api/v1/admin/diagnostics",
});
expect(response.statusCode).toBe(503);
await app.close();
});
it("rejects a sensitive nested service reason even when diagnostic text is clean", async () => {
const registration = createRegistration();
const app = await createApp({
adminDiagnostics: async () => ({
...diagnosticsFixture,
services: {
...servicesFixture,
services: servicesFixture.services.map((service) => service.service_id === "resend" ? { ...service, pause_reason: "password" } : service),
},
}),
browserGate: false,
networkBoundary: { allowTestPort: true },
registration,
});
const session = seedAdmin(registration);
const response = await app.inject({ headers: { ...headers, cookie: `dada_admin_session=${session.sessionToken}` }, method: "GET", url: "/api/v1/admin/diagnostics" });
expect(response.statusCode).toBe(503);
await app.close();
});
});
+95
View File
@@ -0,0 +1,95 @@
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);
});
+34
View File
@@ -0,0 +1,34 @@
export const adminServicesStorageFixture = {
generated_at: "2026-08-04T09:30:00.000Z",
services: [
{ checked_at: "2026-08-04T09:30:00.000Z", configured: true, impact_scope: "authentication" as const, pause_reason: null, service_id: "resend" as const, status: "active" as const },
{ checked_at: "2026-08-04T09:30:00.000Z", configured: true, impact_scope: "location" as const, pause_reason: "quota_exhausted", service_id: "amap" as const, status: "paused_quota" as const },
{ checked_at: "2026-08-04T09:30:00.000Z", configured: true, impact_scope: "generation" as const, pause_reason: "balance_insufficient", service_id: "ai_gateway" as const, status: "degraded" as const },
{ checked_at: "2026-08-04T09:30:00.000Z", configured: true, impact_scope: "generation" as const, pause_reason: "worker_stopped", service_id: "worker" as const, status: "degraded" as const },
{ checked_at: "2026-08-04T09:30:00.000Z", configured: true, impact_scope: "api" as const, pause_reason: null, service_id: "api" as const, status: "active" as const },
{ checked_at: "2026-08-04T09:30:00.000Z", configured: true, impact_scope: "storage" as const, pause_reason: null, service_id: "asset_root" as const, status: "active" as const },
],
storage: {
capacity_notice_level: "warning" as const,
cleanup_pending_count: 2,
data_root_ref: "configured_local_data_root" as const,
hard_limit_bytes: 5_368_709_120,
last_measured_at: "2026-08-04T09:30:00.000Z",
managed_content_bytes: 4_563_402_752,
remeasurement_required: false,
status: "active" as const,
storage_backend: "local_filesystem" as const,
},
};
export const adminDiagnosticsFixture = {
generated_at: "2026-08-04T09:30:00.000Z",
diagnostic_text: "Dada P0-A diagnostics\napp_version=0.0.0\napi_status=ready\nworker_status=ready\nstorage_status=active",
services: adminServicesStorageFixture,
system: {
api_status: "ready" as const,
app_version: "0.0.0",
browser_support: [{ brand: "Google Chrome" as const, major: 128 }],
worker_status: "ready" as const,
},
};