feat(POSTV1-03): 增加本机测试直达入口
Dada P0-A isolated Windows CI / validate-and-package (push) Waiting to run
Dada P0-A isolated Windows CI / validate-and-package (push) Waiting to run
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
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 { RegistrationService } from "../../apps/api/src/registration.js";
|
||||
import { MockResendAdapter } from "../../apps/api/src/resend-adapter.js";
|
||||
|
||||
const fixedNow = Date.parse("2026-08-05T06:00:00.000Z");
|
||||
const writeHeaders = { host: "127.0.0.1:43121", origin: "http://127.0.0.1:43121" };
|
||||
const roots: string[] = [];
|
||||
const services: RegistrationService[] = [];
|
||||
|
||||
function createRegistrationService() {
|
||||
const root = mkdtempSync(join(tmpdir(), "dada-local-test-session-"));
|
||||
roots.push(root);
|
||||
const registration = new RegistrationService({
|
||||
challengePepper: Buffer.alloc(32, 0x51),
|
||||
clock: () => fixedNow,
|
||||
currentPrivacyNoticeVersion: "p0a-notice-v1",
|
||||
databasePath: join(root, "dada.sqlite3"),
|
||||
invitePepper: Buffer.alloc(32, 0x52),
|
||||
resend: new MockResendAdapter(),
|
||||
sessionPepper: Buffer.alloc(32, 0x53),
|
||||
});
|
||||
services.push(registration);
|
||||
return registration;
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
for (const service of services.splice(0)) {
|
||||
try { service.close(); } catch { /* already closed by the test */ }
|
||||
}
|
||||
for (const root of roots.splice(0)) rmSync(root, { force: true, recursive: true });
|
||||
});
|
||||
|
||||
describe("POSTV1-03 local test session", () => {
|
||||
it("does not expose the local test route unless explicitly enabled", async () => {
|
||||
const registration = createRegistrationService();
|
||||
const app = await createApp({ browserGate: false, networkBoundary: { allowTestPort: true }, registration });
|
||||
|
||||
const status = await app.inject({ headers: writeHeaders, method: "GET", url: "/api/v1/auth/local-test" });
|
||||
const created = await app.inject({ headers: writeHeaders, method: "POST", url: "/api/v1/auth/local-test" });
|
||||
|
||||
expect(status.statusCode).toBe(404);
|
||||
expect(created.statusCode).toBe(404);
|
||||
await app.close();
|
||||
});
|
||||
|
||||
it("creates one isolated fixture account and restores it without duplicate credits", async () => {
|
||||
const registration = createRegistrationService();
|
||||
const app = await createApp({
|
||||
browserGate: false,
|
||||
localTestAuth: true,
|
||||
networkBoundary: { allowTestPort: true },
|
||||
registration,
|
||||
});
|
||||
|
||||
const status = await app.inject({ headers: writeHeaders, method: "GET", url: "/api/v1/auth/local-test" });
|
||||
expect(status.statusCode).toBe(200);
|
||||
expect(status.json()).toEqual({ available: true });
|
||||
|
||||
const first = await app.inject({ headers: writeHeaders, method: "POST", url: "/api/v1/auth/local-test" });
|
||||
expect(first.statusCode).toBe(200);
|
||||
expect(first.json()).toMatchObject({
|
||||
audience: "user",
|
||||
credits: { available_balance: 10, reserved_balance: 0 },
|
||||
status: "authenticated",
|
||||
user: { creator_name: "本机测试用户", role: "user", social_id: "@dada_local_test", status: "active" },
|
||||
});
|
||||
expect(first.headers["set-cookie"]).toContain("dada_session=");
|
||||
|
||||
const session = await app.inject({
|
||||
headers: { cookie: first.headers["set-cookie"], host: "127.0.0.1:43121" },
|
||||
method: "GET",
|
||||
url: "/api/v1/auth/session",
|
||||
});
|
||||
expect(session.statusCode).toBe(200);
|
||||
expect(session.json()).toMatchObject({ authenticated: true, credits: { available_balance: 10 } });
|
||||
|
||||
const second = await app.inject({ headers: writeHeaders, method: "POST", url: "/api/v1/auth/local-test" });
|
||||
expect(second.statusCode).toBe(200);
|
||||
expect(second.json().user.user_id).toBe(first.json().user.user_id);
|
||||
expect(registration.database.prepare("SELECT COUNT(*) AS count FROM users").get()).toEqual({ count: 1 });
|
||||
expect(registration.database.prepare("SELECT COUNT(*) AS count FROM credit_ledger").get()).toEqual({ count: 1 });
|
||||
expect(registration.database.prepare("SELECT counts_toward_stage_limit FROM users").get()).toEqual({ counts_toward_stage_limit: 0 });
|
||||
|
||||
const openapi = JSON.stringify(app.swagger());
|
||||
expect(openapi).not.toContain("/api/v1/auth/local-test");
|
||||
expect(openapi).not.toContain("local-test-user");
|
||||
await app.close();
|
||||
});
|
||||
});
|
||||
@@ -40,8 +40,23 @@ describe("TDD-WP7-EXT-003 production Amap adapter", () => {
|
||||
|
||||
const clients = initializeApiCredentialClients(credentials);
|
||||
expect(clients.amap).toBeInstanceOf(RealAmapAdapter);
|
||||
expect(clients.resendConfigured).toBe(true);
|
||||
expect(Object.values(credentials)).toEqual(["", "", ""]);
|
||||
clients.amap.dispose();
|
||||
clients.amap.dispose?.();
|
||||
clients.adminAllowlistPepper.fill(0);
|
||||
});
|
||||
|
||||
it("reports an empty Resend credential without retaining its value", () => {
|
||||
const credentials = {
|
||||
"Dada/P0A/admin/pepper": "fixture-admin-value",
|
||||
"Dada/P0A/api/amap": "",
|
||||
"Dada/P0A/api/resend": "",
|
||||
};
|
||||
|
||||
const clients = initializeApiCredentialClients(credentials);
|
||||
expect(clients.resendConfigured).toBe(false);
|
||||
expect(Object.values(credentials)).toEqual(["", "", ""]);
|
||||
clients.amap.dispose?.();
|
||||
clients.adminAllowlistPepper.fill(0);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -23,6 +23,29 @@ test.beforeAll(async () => {
|
||||
|
||||
test.afterAll(async () => vite.close());
|
||||
|
||||
test("POSTV1-03 enters the workspace through the local test session", async ({ page }) => {
|
||||
await page.route("**/api/v1/auth/local-test", (route) => {
|
||||
if (route.request().method() === "GET") {
|
||||
return route.fulfill({ contentType: "application/json", status: 200, body: JSON.stringify({ available: true }) });
|
||||
}
|
||||
expect(route.request().postData()).toBeNull();
|
||||
return route.fulfill({ contentType: "application/json", status: 200, body: JSON.stringify({ status: "authenticated" }) });
|
||||
});
|
||||
|
||||
await page.goto(webUrl);
|
||||
const button = page.getByRole("button", { name: "直接进入本机测试" });
|
||||
await expect(button).toBeVisible();
|
||||
await button.click();
|
||||
|
||||
await expect(page).toHaveURL(`${webUrl}/app`);
|
||||
});
|
||||
|
||||
test("POSTV1-03 hides the local test entry when the API does not enable it", async ({ page }) => {
|
||||
await page.route("**/api/v1/auth/local-test", (route) => route.fulfill({ status: 404, body: "" }));
|
||||
await page.goto(webUrl);
|
||||
await expect(page.getByRole("button", { name: "直接进入本机测试" })).toHaveCount(0);
|
||||
});
|
||||
|
||||
test("TDD-WP1-NOTICE-001 expands DVPM8 only after successful code delivery", async ({ page }) => {
|
||||
await page.route("**/api/v1/auth/register/send", (route) => route.fulfill({
|
||||
contentType: "application/json",
|
||||
|
||||
Reference in New Issue
Block a user