180 lines
8.1 KiB
TypeScript
180 lines
8.1 KiB
TypeScript
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();
|
|
});
|
|
});
|