feat: complete TASK-WP2-04 credit ledger
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
import { mkdirSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { createServer, type ViteDevServer } from "vite";
|
||||
|
||||
let vite: ViteDevServer;
|
||||
let webUrl: string;
|
||||
|
||||
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());
|
||||
|
||||
const userId = "00000000-0000-4000-8000-000000000601";
|
||||
const adminId = "00000000-0000-4000-8000-000000000602";
|
||||
const session = {
|
||||
audience: "user", authenticated: true, credits: { available_balance: -5, reserved_balance: 1 },
|
||||
csrf_token: "csrf-credit-fixture-0000000000000000000000000000000000",
|
||||
expires_at: "2026-09-02T12:00:00.000Z",
|
||||
user: { creator_name: "Credit User", role: "user", social_id: "@credit", status: "active", user_id: userId },
|
||||
};
|
||||
|
||||
test("TDD-WP2-CRED-001-insufficient shows negative available, reserved and no purchase entry", async ({ page }) => {
|
||||
await page.route("**/api/v1/auth/session", (route) => route.fulfill({ body: JSON.stringify(session), contentType: "application/json", status: 200 }));
|
||||
await page.route("**/api/v1/me/credits", (route) => route.fulfill({
|
||||
body: JSON.stringify({ available_balance: -5, reserved_balance: 1, updated_at: "2026-08-02T12:00:00.000Z" }), contentType: "application/json", status: 200,
|
||||
}));
|
||||
await page.route("**/api/v1/me/credit-ledger**", (route) => route.fulfill({
|
||||
body: JSON.stringify({
|
||||
credits: { available_balance: -5, reserved_balance: 1 },
|
||||
entries: [{
|
||||
amount: -5, available_after: -5, available_before: 0, created_at: "2026-08-02T12:00:00.000Z",
|
||||
entry_id: "00000000-0000-4000-8000-000000000603", entry_type: "admin_adjustment",
|
||||
model_id: null, reason: "人工测试额度校正", reference_id: "00000000-0000-4000-8000-000000000604",
|
||||
reference_type: "admin_adjustment", reserved_after: 1, reserved_before: 1, status: "succeeded",
|
||||
}], next_cursor: null, updated_at: "2026-08-02T12:00:00.000Z",
|
||||
}), contentType: "application/json", status: 200,
|
||||
}));
|
||||
await page.goto(`${webUrl}/app/credits`);
|
||||
await expect(page.getByRole("heading", { name: "点数明细" })).toBeVisible();
|
||||
await expect(page.getByText("-5", { exact: true })).toBeVisible();
|
||||
await expect(page.getByText("已冻结 1 点")).toBeVisible();
|
||||
await expect(page.getByText("可用点数不足时,请联系管理员调整点数。" )).toBeVisible();
|
||||
await expect(page.getByText(/购买|充值/)).toHaveCount(0);
|
||||
const root = process.env.DADA_EVIDENCE_DIR_CREDITS;
|
||||
if (root) {
|
||||
const directory = resolve(root, "TDD-WP2-CRED-001-insufficient", "screenshots");
|
||||
mkdirSync(directory, { recursive: true });
|
||||
await page.screenshot({ fullPage: true, path: resolve(directory, "insufficient.png") });
|
||||
}
|
||||
});
|
||||
|
||||
test("TDD-WP2-CRED-002-admin-adjustment previews and submits available-only negative balance", async ({ page }) => {
|
||||
let adjusted = false;
|
||||
const adminSession = {
|
||||
acknowledged_private_content_notice_version: null,
|
||||
admin: { role: "super_admin", status: "active", user_id: adminId }, audience: "admin", authenticated: true,
|
||||
csrf_token: "csrf-admin-credit-fixture-0000000000000000000000000000000",
|
||||
current_private_content_notice_version: null, expires_at: "2026-09-02T12:00:00.000Z", notice_acknowledged: false,
|
||||
};
|
||||
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/users/${userId}/credits`, (route) => route.fulfill({
|
||||
body: JSON.stringify({ available_balance: adjusted ? -5 : 0, reserved_balance: 1, updated_at: "2026-08-02T12:00:00.000Z" }), contentType: "application/json", status: 200,
|
||||
}));
|
||||
await page.route(`**/api/v1/admin/users/${userId}/credit-adjustments`, (route) => {
|
||||
adjusted = true;
|
||||
const body = route.request().postDataJSON() as { adjustment_id: string };
|
||||
return route.fulfill({ body: JSON.stringify({ adjustment_id: body.adjustment_id, available_balance: -5, reserved_balance: 1, status: "adjusted" }), contentType: "application/json", status: 200 });
|
||||
});
|
||||
await page.goto(`${webUrl}/admin/users?userId=${userId}`);
|
||||
await page.getByRole("button", { name: "调整点数" }).click();
|
||||
await page.getByRole("radio", { name: "扣减" }).check();
|
||||
await page.getByLabel("调整数量").fill("5");
|
||||
await page.getByLabel("调整原因").fill("人工测试额度校正");
|
||||
await expect(page.getByText("调整后可用点数 -5")).toBeVisible();
|
||||
await expect(page.getByText("冻结点数保持 1")).toBeVisible();
|
||||
const root = process.env.DADA_EVIDENCE_DIR_CREDITS;
|
||||
if (root) {
|
||||
const directory = resolve(root, "TDD-WP2-CRED-002-admin-adjustment", "screenshots");
|
||||
mkdirSync(directory, { recursive: true });
|
||||
await page.screenshot({ fullPage: true, path: resolve(directory, "negative-balance.png") });
|
||||
}
|
||||
await page.getByRole("button", { name: "确认调整" }).click();
|
||||
await expect(page.getByText("点数调整已完成并记录审计")).toBeVisible();
|
||||
expect(adjusted).toBe(true);
|
||||
});
|
||||
Reference in New Issue
Block a user