Files
MiragenFlow/server/tests/v1-contract-regressions.test.ts
T

666 lines
53 KiB
TypeScript

import test from "node:test";
import assert from "node:assert/strict";
import { createHmac } from "node:crypto";
import { mkdtemp, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import type { GenerationTask } from "@miragenflow/contracts";
import { createHttpServer } from "../src/app/http.ts";
import { createTotpSecret, encryptSecret, hashChallenge } from "../src/shared/auth.ts";
import { createStore, reserveBalance } from "../src/store.ts";
const accessTokenSecret = "v1-regression-test-secret";
function config(overrides: Record<string, unknown> = {}) {
return {
host: "127.0.0.1",
port: 0,
accessTokenSecret,
accessTokenTtlSeconds: 900,
refreshCookieName: "refresh",
adminEmail: "admin@example.com",
adminPassword: "change-me-now",
adminMfaRequired: false,
userMfaRequired: false,
...overrides,
};
}
function totpCode(secret: string, now = Date.now()) {
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
let buffer = 0;
let bits = 0;
const bytes: number[] = [];
for (const character of secret) {
const value = alphabet.indexOf(character);
buffer = (buffer << 5) | value;
bits += 5;
if (bits >= 8) {
bytes.push((buffer >>> (bits - 8)) & 255);
bits -= 8;
}
}
const message = Buffer.alloc(8);
message.writeBigInt64BE(BigInt(Math.floor(now / 30_000)));
const digest = createHmac("sha1", Buffer.from(bytes)).update(message).digest();
const offset = digest[digest.length - 1] & 15;
const value = ((digest[offset] & 127) << 24) | (digest[offset + 1] << 16) | (digest[offset + 2] << 8) | digest[offset + 3];
return String(value % 1_000_000).padStart(6, "0");
}
async function start(overrides: Record<string, unknown> = {}) {
const store = createStore();
const server = createHttpServer(store, config(overrides));
await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", resolve));
const address = server.address();
assert.ok(address && typeof address !== "string");
return { store, server, base: `http://127.0.0.1:${address.port}` };
}
async function registerAndLogin(base: string, email: string, password = "password123", deviceId?: string) {
const registration = await fetch(`${base}/api/v1/auth/register`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email, password, deviceId }) });
const registered = await registration.json() as { data: { userId: string; devVerificationCode: string } };
assert.equal(registration.status, 201);
const verification = await fetch(`${base}/api/v1/auth/verify-email`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ userId: registered.data.userId, code: registered.data.devVerificationCode }) });
assert.equal(verification.status, 200);
const login = await fetch(`${base}/api/v1/auth/login`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email, password, deviceId }) });
return { registered: registered.data, login, payload: await login.json() as { data: { accessToken?: string; mfaRequired?: boolean; challengeId?: string; challengeType?: "verify" | "setup"; mfaEnrollment?: { secret: string; recoveryCodes: string[] } } } };
}
async function loginAdmin(base: string, email: string, password: string, secret: string, recoveryCode?: string) {
const first = await fetch(`${base}/api/v1/admin/auth/login`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email, password }) });
const firstPayload = await first.json() as { data: { accessToken?: string; challengeId?: string; mfaRequired?: boolean } };
assert.equal(firstPayload.data.mfaRequired, true);
const second = await fetch(`${base}/api/v1/admin/auth/login`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email, password, challengeId: firstPayload.data.challengeId, ...(recoveryCode ? { recoveryCode } : { mfaCode: totpCode(secret) }) }) });
const secondPayload = await second.json() as { data: { accessToken: string } };
assert.equal(second.status, 200);
return secondPayload.data.accessToken;
}
test("user MFA recovery endpoint consumes a recovery code once", async () => {
const { server, base } = await start({ userMfaRequired: true });
try {
const first = await registerAndLogin(base, "recovery-endpoint@example.com");
assert.equal(first.payload.data.challengeType, "setup");
const recoveryCode = first.payload.data.mfaEnrollment?.recoveryCodes[0];
assert.ok(recoveryCode);
assert.equal(first.payload.data.mfaRequired, true);
const confirmed = await fetch(`${base}/api/v1/auth/mfa/verify`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ challengeId: first.payload.data.challengeId, code: totpCode(first.payload.data.mfaEnrollment!.secret) }) });
assert.equal(confirmed.status, 200);
const secondLogin = await fetch(`${base}/api/v1/auth/login`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email: "recovery-endpoint@example.com", password: "password123" }) });
const second = { payload: await secondLogin.json() as { data: { accessToken?: string; mfaRequired?: boolean; challengeId?: string } } };
const recovered = await fetch(`${base}/api/v1/auth/mfa/recovery`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ challengeId: second.payload.data.challengeId, recoveryCode }) });
assert.equal(recovered.status, 200);
assert.ok((await recovered.json() as { data: { accessToken?: string } }).data.accessToken);
const thirdLogin = await fetch(`${base}/api/v1/auth/login`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email: "recovery-endpoint@example.com", password: "password123" }) });
const third = { payload: await thirdLogin.json() as { data: { challengeId?: string } } };
const repeated = await fetch(`${base}/api/v1/auth/mfa/recovery`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ challengeId: third.payload.data.challengeId, recoveryCode }) });
assert.equal(repeated.status, 401);
} finally {
await new Promise<void>((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
}
});
test("sessions, plan entitlements, and remaining plan balance are user-scoped", async () => {
const { server, base } = await start();
try {
const first = await registerAndLogin(base, "sessions-plans@example.com", "password123", "browser-a");
const second = await fetch(`${base}/api/v1/auth/login`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email: "sessions-plans@example.com", password: "password123", deviceId: "browser-b" }) });
const secondPayload = await second.json() as { data: { accessToken: string } };
const auth = (token: string) => ({ authorization: `Bearer ${token}`, "content-type": "application/json" });
const sessions = await fetch(`${base}/api/v1/me/sessions`, { headers: auth(first.payload.data.accessToken!) });
const sessionItems = (await sessions.json() as { data: { items: Array<{ id: string; deviceId?: string }> } }).data.items;
assert.equal(sessionItems.length, 2);
const browserA = sessionItems.find((item) => item.deviceId === "browser-a");
assert.ok(browserA);
const revoked = await fetch(`${base}/api/v1/me/sessions/${browserA.id}`, { method: "DELETE", headers: auth(first.payload.data.accessToken!) });
assert.equal(revoked.status, 200);
assert.equal((await fetch(`${base}/api/v1/me`, { headers: auth(first.payload.data.accessToken!) })).status, 401);
const remaining = await fetch(`${base}/api/v1/me/sessions`, { headers: auth(secondPayload.data.accessToken) });
assert.equal((await remaining.json() as { data: { items: unknown[] } }).data.items.length, 1);
const purchase = await fetch(`${base}/api/v1/plans/starter/purchase`, { method: "POST", headers: { ...auth(secondPayload.data.accessToken), "idempotency-key": "plan-regression" }, body: "{}" });
assert.equal(purchase.status, 202);
const plans = await fetch(`${base}/api/v1/me/plans`, { headers: auth(secondPayload.data.accessToken) });
const planPayload = await plans.json() as { data: { items: Array<{ plan?: { id: string }; remainingBalance: number }> } };
assert.equal(plans.status, 200);
assert.equal(planPayload.data.items[0].plan?.id, "starter");
assert.equal(planPayload.data.items[0].remainingBalance, 100);
const task = await fetch(`${base}/api/v1/tasks/image`, { method: "POST", headers: { ...auth(secondPayload.data.accessToken), "idempotency-key": "plan-bucket-task" }, body: JSON.stringify({ modelProductId: "basic-image-v1", prompt: "plan bucket" }) });
assert.equal(task.status, 202);
await new Promise((resolve) => setTimeout(resolve, 150));
const afterTask = await fetch(`${base}/api/v1/me/plans`, { headers: auth(secondPayload.data.accessToken) });
assert.equal((await afterTask.json() as { data: { items: Array<{ remainingBalance: number }> } }).data.items[0].remainingBalance, 90);
} finally {
await new Promise<void>((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
}
});
test("admin image model products publish capability parameters and require a resolution matrix", async () => {
const { server, base } = await start();
try {
const login = await fetch(`${base}/api/v1/admin/auth/login`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email: "admin@example.com", password: "change-me-now" }) });
const loginPayload = await login.json() as { data: { accessToken: string } };
const headers = { authorization: `Bearer ${loginPayload.data.accessToken}`, "content-type": "application/json" };
const created = await fetch(`${base}/api/v1/admin/model-products`, { method: "POST", headers: { ...headers, "idempotency-key": "model-capability-create" }, body: JSON.stringify({ id: "openai-image-product", publicModelId: "gpt-image-2", name: "OpenAI 图片模型", capabilities: ["image"], channelGroupId: "image-default", basePrice: 20, resolutions: [{ id: "1K", label: "1K", width: 1024, height: 1024, priceMultiplier: 1, ratios: ["1:1", "16:9"] }] }) });
assert.equal(created.status, 201);
const createdPayload = await created.json() as { data: { item: { capabilitiesSchema?: Array<{ capability: string; parameters?: Record<string, { enum?: string[] }> }> } } };
const imageSchema = createdPayload.data.item.capabilitiesSchema?.find((item) => item.capability === "image");
assert.ok(imageSchema);
assert.deepEqual(imageSchema.parameters?.ratio.enum, ["auto", "1:1", "16:9", "9:16", "3:4", "4:5", "3:2", "2:3", "4:3", "1.91:1", "2.35:1", "21:9"]);
assert.deepEqual(imageSchema.parameters?.resolution.enum, ["1K", "2K", "4K"]);
assert.deepEqual(imageSchema.parameters?.input_fidelity.enum, ["low", "high"]);
const invalid = await fetch(`${base}/api/v1/admin/model-products`, { method: "POST", headers: { ...headers, "idempotency-key": "model-capability-invalid" }, body: JSON.stringify({ id: "openai-image-invalid", publicModelId: "gpt-image-invalid", name: "无分辨率图片模型", capabilities: ["image"], channelGroupId: "image-default", basePrice: 20, resolutions: [] }) });
assert.equal(invalid.status, 422);
} finally {
await new Promise<void>((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
}
});
test("admin channel groups bind model products separately from request channels", async () => {
const { server, base, store } = await start();
try {
const login = await fetch(`${base}/api/v1/admin/auth/login`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email: "admin@example.com", password: "change-me-now" }) });
const loginPayload = await login.json() as { data: { accessToken: string } };
const headers = { authorization: `Bearer ${loginPayload.data.accessToken}`, "content-type": "application/json" };
const firstProduct = await fetch(`${base}/api/v1/admin/model-products`, { method: "POST", headers: { ...headers, "idempotency-key": "channel-group-product-first" }, body: JSON.stringify({ id: "group-basic-image", publicModelId: "group-basic-image-v1", name: "分组基础图片", capabilities: ["image"], enabled: false, basePrice: 10, resolutions: [{ id: "1K", label: "1K", width: 1024, height: 1024, priceMultiplier: 1, ratios: ["1:1"] }] }) });
assert.equal(firstProduct.status, 201);
const secondProduct = await fetch(`${base}/api/v1/admin/model-products`, { method: "POST", headers: { ...headers, "idempotency-key": "channel-group-product-second" }, body: JSON.stringify({ id: "group-advanced-image", publicModelId: "group-advanced-image-v1", name: "分组高级图片", capabilities: ["image"], enabled: false, basePrice: 25, resolutions: [{ id: "2K", label: "2K", width: 2048, height: 2048, priceMultiplier: 1.5, ratios: ["1:1"] }] }) });
assert.equal(secondProduct.status, 201);
const created = await fetch(`${base}/api/v1/admin/channel-groups`, { method: "POST", headers: { ...headers, "idempotency-key": "channel-group-product-bind" }, body: JSON.stringify({ id: "image-product-group", name: "图片产品组", capabilities: ["image"], productIds: ["group-basic-image"], channelIds: ["channel-a"], retryBudget: 2 }) });
assert.equal(created.status, 201);
const createdPayload = await created.json() as { data: { item: { productIds?: string[] } } };
assert.deepEqual(createdPayload.data.item.productIds, ["group-basic-image"]);
assert.equal(store.products.find((product) => product.id === "group-basic-image")?.channelGroupId, "image-product-group");
const listed = await fetch(`${base}/api/v1/admin/channel-groups`, { headers });
assert.equal(listed.status, 200);
const listedPayload = await listed.json() as { data: { items: Array<{ id: string; productIds?: string[]; products?: Array<{ productId: string }> }> } };
const listedGroup = listedPayload.data.items.find((item) => item.id === "image-product-group");
assert.deepEqual(listedGroup?.productIds, ["group-basic-image"]);
assert.deepEqual(listedGroup?.products?.map((product) => product.productId), ["group-basic-image"]);
const edited = await fetch(`${base}/api/v1/admin/channel-groups/image-product-group`, { method: "PATCH", headers: { ...headers, "If-Match": "1", "idempotency-key": "channel-group-product-rebind" }, body: JSON.stringify({ productIds: ["group-advanced-image"], capabilities: ["image"], channelIds: ["channel-a"], retryBudget: 2, enabled: true }) });
assert.equal(edited.status, 200);
assert.equal(store.products.find((product) => product.id === "group-basic-image")?.channelGroupId, undefined);
assert.equal(store.products.find((product) => product.id === "group-basic-image")?.enabled, false);
assert.equal(store.products.find((product) => product.id === "group-advanced-image")?.channelGroupId, "image-product-group");
} finally {
await new Promise<void>((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
}
});
test("admin channel groups reject incompatible or silently migrated model products", async () => {
const { server, base, store } = await start();
try {
const login = await fetch(`${base}/api/v1/admin/auth/login`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email: "admin@example.com", password: "change-me-now" }) });
const loginPayload = await login.json() as { data: { accessToken: string } };
const headers = { authorization: `Bearer ${loginPayload.data.accessToken}`, "content-type": "application/json" };
const imageGroup = await fetch(`${base}/api/v1/admin/channel-groups`, { method: "POST", headers: { ...headers, "idempotency-key": "channel-group-capability-image" }, body: JSON.stringify({ id: "capability-image-group", name: "图片能力组", capabilities: ["image"], productIds: [], channelIds: ["channel-a"], retryBudget: 1 }) });
assert.equal(imageGroup.status, 201);
const product = await fetch(`${base}/api/v1/admin/model-products`, { method: "POST", headers: { ...headers, "idempotency-key": "channel-group-capability-product" }, body: JSON.stringify({ id: "capability-image-product", publicModelId: "capability-image-v1", name: "能力校验图片产品", capabilities: ["image"], channelGroupId: "capability-image-group", basePrice: 5, resolutions: [{ id: "1K", label: "1K", width: 1024, height: 1024, priceMultiplier: 1, ratios: ["1:1"] }] }) });
assert.equal(product.status, 201);
const textGroup = await fetch(`${base}/api/v1/admin/channel-groups`, { method: "POST", headers: { ...headers, "idempotency-key": "channel-group-capability-text" }, body: JSON.stringify({ id: "capability-text-group", name: "文字能力组", capabilities: ["text"], productIds: [], channelIds: ["channel-a"], retryBudget: 1 }) });
assert.equal(textGroup.status, 201);
const incompatibleMode = await fetch(`${base}/api/v1/admin/channel-groups/capability-image-group`, { method: "PATCH", headers: { ...headers, "If-Match": "1", "idempotency-key": "channel-group-capability-change" }, body: JSON.stringify({ capabilities: ["text"] }) });
assert.equal(incompatibleMode.status, 422);
assert.deepEqual(store.channelGroups.find((group) => group.id === "capability-image-group")?.capabilities, ["image"]);
assert.equal(store.products.find((item) => item.id === "capability-image-product")?.channelGroupId, "capability-image-group");
const incompatibleProduct = await fetch(`${base}/api/v1/admin/model-products/capability-image-product`, { method: "PATCH", headers: { ...headers, "If-Match": "1", "idempotency-key": "channel-group-capability-product-change" }, body: JSON.stringify({ channelGroupId: "capability-text-group" }) });
assert.equal(incompatibleProduct.status, 422);
assert.equal(store.products.find((item) => item.id === "capability-image-product")?.channelGroupId, "capability-image-group");
const duplicateBinding = await fetch(`${base}/api/v1/admin/channel-groups`, { method: "POST", headers: { ...headers, "idempotency-key": "channel-group-ownership-conflict" }, body: JSON.stringify({ id: "capability-other-group", name: "重复绑定组", capabilities: ["image"], productIds: ["capability-image-product"], channelIds: ["channel-a"], retryBudget: 1 }) });
assert.equal(duplicateBinding.status, 409);
assert.equal(store.channelGroups.some((group) => group.id === "capability-other-group"), false);
} finally {
await new Promise<void>((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
}
});
test("message provider and template writes survive a file-store restart", async () => {
const directory = await mkdtemp(join(tmpdir(), "miragenflow-message-persistence-"));
const file = join(directory, "store.json");
const store = createStore(file);
const server = createHttpServer(store, config());
await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", resolve));
const address = server.address();
assert.ok(address && typeof address !== "string");
const base = `http://127.0.0.1:${address.port}`;
try {
const login = await fetch(`${base}/api/v1/admin/auth/login`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email: "admin@example.com", password: "change-me-now" }) });
const loginPayload = await login.json() as { data: { accessToken: string } };
const headers = { authorization: `Bearer ${loginPayload.data.accessToken}`, "content-type": "application/json" };
const provider = await fetch(`${base}/api/v1/admin/message-providers`, { method: "POST", headers: { ...headers, "idempotency-key": "persistent-message-provider-create" }, body: JSON.stringify({ id: "persistent-message-provider", name: "持久化消息适配器", channel: "email" }) });
assert.equal(provider.status, 201);
const template = await fetch(`${base}/api/v1/admin/message-templates`, { method: "POST", headers: { ...headers, "idempotency-key": "persistent-message-template-create" }, body: JSON.stringify({ purpose: "register", channel: "email", content: "验证码:{{code}}" }) });
assert.equal(template.status, 201);
} finally {
await new Promise<void>((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
}
const restored = createStore(file);
try {
assert.equal(restored.messageProviders.has("persistent-message-provider"), true);
assert.equal([...restored.messageTemplates.values()].some((item) => item.content === "验证码:{{code}}"), true);
} finally {
await rm(directory, { recursive: true, force: true });
}
});
test("admin unknown-task reconciliation stores real image metadata and clamps the charge", async () => {
const { store, server, base } = await start();
const taskId = "reconcile-image-task";
const ownerId = "reconcile-user";
const now = new Date().toISOString();
assert.equal(reserveBalance(store, ownerId, 10, taskId), false);
store.balances.set(ownerId, { available: 100, reserved: 0 });
assert.equal(reserveBalance(store, ownerId, 10, taskId), true);
store.tasks.set(taskId, {
id: taskId,
ownerId,
taskType: "image",
modelProductId: "basic-image",
status: "unknown",
estimatedCost: 10,
reservedCost: 10,
count: 2,
pricingSnapshot: { basePrice: 5, multiplier: 1, unitVersion: 1 },
createdAt: now,
updatedAt: now,
eventSequence: 0,
channelGroupId: "image-default",
routeSnapshotVersion: 1,
attempts: [{ id: "reconcile-attempt", channelId: "channel-a", sequence: 1, status: "unknown", startedAt: now, reconciliationStatus: "pending" }],
outputs: [],
inputSnapshot: { references: [], params: { resolution: "2K" } },
} satisfies GenerationTask);
try {
const login = await fetch(`${base}/api/v1/admin/auth/login`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email: "admin@example.com", password: "change-me-now" }) });
const loginPayload = await login.json() as { data: { accessToken: string } };
const invalidForm = new FormData(); invalidForm.set("outcome", "success"); invalidForm.append("file", new Blob([Buffer.from("not-an-image")], { type: "image/png" }), "invalid.png");
const invalid = await fetch(`${base}/api/v1/admin/tasks/${taskId}/reconcile`, { method: "POST", headers: { authorization: `Bearer ${loginPayload.data.accessToken}`, "idempotency-key": "reconcile-image-invalid", "if-match": "0" }, body: invalidForm });
assert.equal(invalid.status, 415);
assert.equal(store.tasks.get(taskId)?.status, "unknown");
assert.equal(store.balances.get(ownerId)?.reserved, 10);
const form = new FormData(); form.set("outcome", "success"); form.set("chargedAmount", "99"); form.append("file", new Blob([Buffer.from("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M/wHwAF/gL+XwAAAABJRU5ErkJggg==", "base64")], { type: "image/png" }), "result.png");
const response = await fetch(`${base}/api/v1/admin/tasks/${taskId}/reconcile`, { method: "POST", headers: { authorization: `Bearer ${loginPayload.data.accessToken}`, "idempotency-key": "reconcile-image-success", "if-match": "0" }, body: form });
assert.equal(response.status, 200);
const payload = await response.json() as { data: { item: GenerationTask } };
const settledEntries = store.ledger.filter((entry) => entry.type === "settle" && entry.referenceId === taskId).length;
const repeatedForm = new FormData(); repeatedForm.set("outcome", "success"); repeatedForm.set("chargedAmount", "99"); repeatedForm.append("file", new Blob([Buffer.from("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M/wHwAF/gL+XwAAAABJRU5ErkJggg==", "base64")], { type: "image/png" }), "result.png");
const repeated = await fetch(`${base}/api/v1/admin/tasks/${taskId}/reconcile`, { method: "POST", headers: { authorization: `Bearer ${loginPayload.data.accessToken}`, "idempotency-key": "reconcile-image-success", "if-match": "0" }, body: repeatedForm });
assert.equal(repeated.status, 200);
assert.equal(store.ledger.filter((entry) => entry.type === "settle" && entry.referenceId === taskId).length, settledEntries);
const conflicting = await fetch(`${base}/api/v1/admin/tasks/${taskId}/reconcile`, { method: "POST", headers: { authorization: `Bearer ${loginPayload.data.accessToken}`, "content-type": "application/json", "idempotency-key": "reconcile-image-success" }, body: JSON.stringify({ outcome: "failure" }) });
assert.equal(conflicting.status, 409);
const output = payload.data.item.outputs[0];
assert.equal(payload.data.item.status, "succeeded");
assert.equal(payload.data.item.reservedCost, 0);
assert.equal(output.metadata?.width, 1);
assert.equal(output.metadata?.height, 1);
assert.equal(output.metadata?.size, "1x1");
assert.equal(output.metadata?.format, "png");
assert.equal(output.metadata?.source, "base64");
assert.equal(output.metadata?.resolution, "2K");
assert.equal(output.unitPriceSnapshot, 5);
assert.equal(output.chargedAmount, 10);
assert.equal(store.balances.get(ownerId)?.reserved, 0);
assert.equal(store.ledger.some((entry) => entry.type === "settle" && entry.referenceId === taskId), true);
} finally {
await new Promise<void>((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
}
});
test("admin unknown-task failure reconciliation releases the full reservation", async () => {
const { store, server, base } = await start();
const taskId = "reconcile-failure-task";
const ownerId = "reconcile-failure-user";
const now = new Date().toISOString();
store.balances.set(ownerId, { available: 20, reserved: 0 });
assert.equal(reserveBalance(store, ownerId, 10, taskId), true);
store.tasks.set(taskId, {
id: taskId,
ownerId,
taskType: "image",
modelProductId: "basic-image",
status: "unknown",
estimatedCost: 10,
reservedCost: 10,
createdAt: now,
updatedAt: now,
eventSequence: 0,
channelGroupId: "image-default",
routeSnapshotVersion: 1,
attempts: [{ id: "reconcile-failure-attempt", channelId: "channel-a", sequence: 1, status: "unknown", startedAt: now, reconciliationStatus: "pending" }],
outputs: [],
} satisfies GenerationTask);
try {
const login = await fetch(`${base}/api/v1/admin/auth/login`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email: "admin@example.com", password: "change-me-now" }) });
const loginPayload = await login.json() as { data: { accessToken: string } };
const response = await fetch(`${base}/api/v1/admin/tasks/${taskId}/reconcile`, { method: "POST", headers: { authorization: `Bearer ${loginPayload.data.accessToken}`, "content-type": "application/json", "idempotency-key": "reconcile-failure", "if-match": "0" }, body: JSON.stringify({ outcome: "failure" }) });
assert.equal(response.status, 200);
assert.equal(store.tasks.get(taskId)?.status, "failed");
assert.equal(store.tasks.get(taskId)?.reservedCost, 0);
assert.deepEqual({ available: store.balances.get(ownerId)?.available, reserved: store.balances.get(ownerId)?.reserved }, { available: 20, reserved: 0 });
assert.equal(store.tasks.get(taskId)?.attempts[0]?.reconciliationStatus, "confirmed_failure");
assert.equal(store.ledger.some((entry) => entry.type === "release" && entry.referenceId === taskId), true);
} finally {
await new Promise<void>((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
}
});
test("finance administrators can list balance targets and require separate MFA approval", async () => {
const firstSecret = createTotpSecret();
const secondSecret = createTotpSecret();
const securitySecret = createTotpSecret();
const recoveryCode = "ADMINRECOVERY";
const { store, server, base } = await start({
adminMfaRequired: true,
adminAccounts: [
{ id: "admin:first", email: "first-admin@example.com", password: "first-admin-password", role: "finance", mfaSecret: firstSecret, mfaRequired: true },
{ id: "admin:second", email: "second-admin@example.com", password: "second-admin-password", role: "finance", mfaSecret: secondSecret, mfaRequired: true, recoveryCodeHashes: [hashChallenge(recoveryCode, accessTokenSecret)] },
{ id: "admin:security", email: "security-admin@example.com", password: "security-admin-password", role: "support", mfaSecret: securitySecret, mfaRequired: true },
],
});
try {
const user = await registerAndLogin(base, "approval-user@example.com");
const requester = await loginAdmin(base, "first-admin@example.com", "first-admin-password", firstSecret);
const approver = await loginAdmin(base, "second-admin@example.com", "second-admin-password", secondSecret, recoveryCode);
const securityOnly = await loginAdmin(base, "security-admin@example.com", "security-admin-password", securitySecret);
const targets = await fetch(`${base}/api/v1/admin/billing/users`, { headers: { authorization: `Bearer ${requester}` } });
assert.equal(targets.status, 200);
const target = ((await targets.json()) as { data: { items: Array<Record<string, unknown>> } }).data.items.find((item) => item.id === user.registered.userId);
assert.ok(target);
const allowedTargetFields = ["balance", "displayName", "email", "id", "phone", "status", "username"];
assert.deepEqual(Object.keys(target).filter((key) => !allowedTargetFields.includes(key)), []);
assert.ok("balance" in target);
assert.equal((await fetch(`${base}/api/v1/admin/users`, { headers: { authorization: `Bearer ${requester}` } })).status, 403);
const forbidden = await fetch(`${base}/api/v1/admin/users/${user.registered.userId}/balance-adjustments`, { method: "POST", headers: { authorization: `Bearer ${securityOnly}`, "content-type": "application/json", "idempotency-key": "security-balance-adjustment" }, body: JSON.stringify({ amount: 1, reason: "不应允许安全管理员调账" }) });
assert.equal(forbidden.status, 403);
const request = await fetch(`${base}/api/v1/admin/users/${user.registered.userId}/balance-adjustments`, { method: "POST", headers: { authorization: `Bearer ${requester}`, "content-type": "application/json", "idempotency-key": "balance-adjustment-request" }, body: JSON.stringify({ amount: 25, reason: "人工账本回归测试" }) });
assert.equal(request.status, 202);
assert.equal((await fetch(`${base}/api/v1/balance`, { headers: { authorization: `Bearer ${user.payload.data.accessToken!}` } })).status, 200);
const before = await fetch(`${base}/api/v1/balance`, { headers: { authorization: `Bearer ${user.payload.data.accessToken!}` } });
assert.equal((await before.json() as { data: { available: number } }).data.available, 0);
const approval = ((await request.json()) as { data: { item: { id: string } } }).data.item;
const execute = await fetch(`${base}/api/v1/admin/approvals/${approval.id}/approve`, { method: "POST", headers: { authorization: `Bearer ${approver}`, "content-type": "application/json", "idempotency-key": "balance-adjustment-approve", "if-match": "1" }, body: JSON.stringify({ mfaCode: totpCode(secondSecret) }) });
assert.equal(execute.status, 200);
assert.ok(store.adminAccounts.get("admin:second")?.mfaLastTotpCounter !== undefined);
const after = await fetch(`${base}/api/v1/balance`, { headers: { authorization: `Bearer ${user.payload.data.accessToken!}` } });
assert.equal((await after.json() as { data: { available: number } }).data.available, 25);
} finally {
await new Promise<void>((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
}
});
test("local super administrator can approve its own balance increase and deduction", async () => {
const { store, server, base } = await start({ adminSelfApprovalAllowed: true });
try {
const user = await registerAndLogin(base, "self-approval-balance@example.com");
store.balances.set(user.registered.userId, { available: 10, reserved: 0 });
const login = await fetch(`${base}/api/v1/admin/auth/login`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email: "admin@example.com", password: "change-me-now" }) });
const loginPayload = await login.json() as { data: { accessToken: string } };
const adminHeaders = (idempotencyKey: string) => ({ authorization: `Bearer ${loginPayload.data.accessToken}`, "content-type": "application/json", "idempotency-key": idempotencyKey });
const adjust = async (amount: number, key: string) => {
const request = await fetch(`${base}/api/v1/admin/users/${user.registered.userId}/balance-adjustments`, { method: "POST", headers: adminHeaders(`${key}-request`), body: JSON.stringify({ amount, reason: amount > 0 ? "本地增加金币" : "本地扣减金币" }) });
assert.equal(request.status, 202);
const approval = ((await request.json()) as { data: { item: { id: string; version: number } } }).data.item;
const execute = await fetch(`${base}/api/v1/admin/approvals/${approval.id}/approve`, { method: "POST", headers: { ...adminHeaders(`${key}-approve`), "if-match": String(approval.version) }, body: "{}" });
assert.equal(execute.status, 200);
};
await adjust(40, "self-balance-add");
assert.equal((await (await fetch(`${base}/api/v1/balance`, { headers: { authorization: `Bearer ${user.payload.data.accessToken!}` } })).json() as { data: { available: number } }).data.available, 50);
assert.equal((store.buckets.get(user.registered.userId) || []).reduce((total, bucket) => total + bucket.remaining, 0), 50);
await adjust(10, "self-balance-add-second-bucket");
assert.equal((store.buckets.get(user.registered.userId) || []).reduce((total, bucket) => total + bucket.remaining, 0), 60);
await adjust(-55, "self-balance-deduct-across-buckets");
assert.equal((await (await fetch(`${base}/api/v1/balance`, { headers: { authorization: `Bearer ${user.payload.data.accessToken!}` } })).json() as { data: { available: number } }).data.available, 5);
assert.deepEqual((store.buckets.get(user.registered.userId) || []).map((bucket) => bucket.remaining), [0, 0, 5]);
assert.ok(store.ledger.filter((entry) => entry.userId === user.registered.userId && entry.type === "adjustment").every((entry) => Boolean(entry.bucketId)));
assert.equal([...store.adminIdempotency.values()].every((record) => record.state === "completed"), true);
} finally {
await new Promise<void>((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
}
});
test("administrator initial plan grant creates matching balance bucket ledger and entitlement", async () => {
const { store, server, base } = await start();
try {
const plan = store.plans.find((item) => item.includedBalance > 0);
assert.ok(plan);
const login = await fetch(`${base}/api/v1/admin/auth/login`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email: "admin@example.com", password: "change-me-now" }) });
const accessToken = (await login.json() as { data: { accessToken: string } }).data.accessToken;
const headers = { authorization: `Bearer ${accessToken}`, "content-type": "application/json", "idempotency-key": "initial-plan-user" };
const body = JSON.stringify({ username: "initial-plan-user", email: "initial-plan@example.com", password: "password123", planId: plan.id, welcomeMessage: "欢迎使用" });
const response = await fetch(`${base}/api/v1/admin/users`, { method: "POST", headers, body });
assert.equal(response.status, 201);
const firstPayload = await response.json() as { data: { item: { id: string }; initialPassword?: string } };
const userId = firstPayload.data.item.id;
assert.equal(firstPayload.data.initialPassword, "password123");
const entitlement = [...store.entitlements.values()].find((item) => item.userId === userId);
assert.ok(entitlement?.creditBucketId);
const bucket = (store.buckets.get(userId) || []).find((item) => item.id === entitlement.creditBucketId);
assert.equal(bucket?.remaining, plan.includedBalance);
assert.equal(store.balances.get(userId)?.available, plan.includedBalance);
assert.equal(store.ledger.find((entry) => entry.referenceId === entitlement.id)?.bucketId, bucket?.id);
assert.ok([...store.messageOutbox.values()].some((item) => item.idempotencyKey === "initial-plan-user:welcome"));
assert.ok(store.audit.some((item) => item.action === "user.create" && item.objectId === userId));
const counts = { users: store.users.size, buckets: store.buckets.get(userId)?.length, ledger: store.ledger.length, entitlements: store.entitlements.size, messages: store.messageOutbox.size, audit: store.audit.length };
const replay = await fetch(`${base}/api/v1/admin/users`, { method: "POST", headers, body });
assert.equal(replay.status, 201);
const replayPayload = await replay.json() as { data: { item: { id: string }; initialPassword?: string } };
assert.equal(replayPayload.data.item.id, userId);
assert.equal(replayPayload.data.initialPassword, undefined);
assert.deepEqual({ users: store.users.size, buckets: store.buckets.get(userId)?.length, ledger: store.ledger.length, entitlements: store.entitlements.size, messages: store.messageOutbox.size, audit: store.audit.length }, counts);
const idempotency = [...store.adminIdempotency.values()].find((item) => item.statusCode === 201);
assert.equal(idempotency?.state, "completed");
assert.equal(JSON.stringify(idempotency?.data).includes("password123"), false);
} finally {
await new Promise<void>((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
}
});
test("admin user status mutations reject stale versions", async () => {
const { store, server, base } = await start();
try {
const user = await registerAndLogin(base, "user-version-conflict@example.com");
const login = await fetch(`${base}/api/v1/admin/auth/login`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email: "admin@example.com", password: "change-me-now" }) });
const accessToken = (await login.json() as { data: { accessToken: string } }).data.accessToken;
const auth = { authorization: `Bearer ${accessToken}`, "content-type": "application/json" };
const detail = await fetch(`${base}/api/v1/admin/users/${user.registered.userId}`, { headers: auth });
assert.equal(detail.status, 200);
const detailPayload = await detail.json() as { data: { item: { version: number; status: string } } };
assert.equal(detailPayload.data.item.version, 1);
const freeze = await fetch(`${base}/api/v1/admin/users/${user.registered.userId}/freeze`, { method: "POST", headers: { ...auth, "if-match": String(detailPayload.data.item.version), "idempotency-key": "user-version-freeze" }, body: "{}" });
assert.equal(freeze.status, 200);
const freezePayload = await freeze.json() as { data: { version: number; status: string } };
assert.equal(freezePayload.data.status, "frozen");
assert.equal(freezePayload.data.version, 2);
const stale = await fetch(`${base}/api/v1/admin/users/${user.registered.userId}`, { method: "PATCH", headers: { ...auth, "if-match": "1", "idempotency-key": "user-version-stale-update" }, body: JSON.stringify({ status: "active" }) });
assert.equal(stale.status, 409);
const stalePayload = await stale.json() as { error?: { code?: string } };
assert.equal(stalePayload.error?.code, "VERSION_CONFLICT");
assert.equal(store.users.get(user.registered.userId)?.status, "frozen");
assert.equal(store.users.get(user.registered.userId)?.version, 2);
const unfreeze = await fetch(`${base}/api/v1/admin/users/${user.registered.userId}/unfreeze`, { method: "POST", headers: { ...auth, "if-match": "2", "idempotency-key": "user-version-unfreeze" }, body: "{}" });
assert.equal(unfreeze.status, 200);
assert.equal((await unfreeze.json() as { data: { version: number; status: string } }).data.version, 3);
} finally {
await new Promise<void>((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
}
});
test("assets enforce ownership and deduplicate repeated object saves", async () => {
const { store, server, base } = await start();
try {
const owner = await registerAndLogin(base, "asset-owner@example.com");
const other = await registerAndLogin(base, "asset-other@example.com");
const objectId = "object-owner-1";
store.objects.set(objectId, { id: objectId, ownerId: owner.registered.userId, mimeType: "image/png", data: Buffer.from("x").toString("base64"), expiresAt: new Date(Date.now() + 60_000).toISOString() });
const auth = { authorization: `Bearer ${owner.payload.data.accessToken!}`, "content-type": "application/json" };
const first = await fetch(`${base}/api/v1/assets`, { method: "POST", headers: auth, body: JSON.stringify({ objectId, type: "image", title: "测试资产" }) });
const firstPayload = await first.json() as { data: { id: string } };
assert.equal(first.status, 201);
const second = await fetch(`${base}/api/v1/assets`, { method: "POST", headers: auth, body: JSON.stringify({ objectId, type: "image", title: "重复保存" }) });
const secondPayload = await second.json() as { data: { id: string } };
assert.equal(second.status, 200);
assert.equal(secondPayload.data.id, firstPayload.data.id);
const denied = await fetch(`${base}/api/v1/assets`, { method: "POST", headers: { authorization: `Bearer ${other.payload.data.accessToken!}`, "content-type": "application/json" }, body: JSON.stringify({ objectId, type: "image" }) });
assert.equal(denied.status, 403);
} finally {
await new Promise<void>((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
}
});
test("WebDAV manifest retention is capped and valid writes keep the local record", async () => {
const { store, server, base } = await start();
try {
const user = await registerAndLogin(base, "webdav-retention@example.com");
store.webdav.set(user.registered.userId, { userId: user.registered.userId, configured: true, directory: "miragenflow", state: "ready", retentionDays: 30, encryptedUrl: encryptSecret("https://127.0.0.1:1", accessTokenSecret), manifestRetentionExpiresAt: new Date().toISOString(), manifestExtensionDays: 0 });
const auth = { authorization: `Bearer ${user.payload.data.accessToken!}`, "content-type": "application/json" };
const extension = await fetch(`${base}/api/v1/me/webdav/manifest/retention`, { method: "POST", headers: auth, body: JSON.stringify({ days: 20 }) });
assert.equal(extension.status, 200);
assert.equal((await extension.json() as { data: { manifestExtensionDays: number } }).data.manifestExtensionDays, 20);
const overLimit = await fetch(`${base}/api/v1/me/webdav/manifest/retention`, { method: "POST", headers: auth, body: JSON.stringify({ days: 11 }) });
assert.equal(overLimit.status, 422);
const write = await fetch(`${base}/api/v1/me/webdav/file`, { method: "PUT", headers: auth, body: JSON.stringify({ path: "canvas/manifest.json", mimeType: "application/json", data: "eA==" }) });
assert.equal(write.status, 202);
assert.ok(store.webdavFiles.has(`${user.registered.userId}:canvas/manifest.json`));
} finally {
await new Promise<void>((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
}
});
test("expired buckets append one adjustment ledger entry before reservation", () => {
const store = createStore();
const userId = "expired-bucket-user";
const bucketId = "expired-bucket";
store.balances.set(userId, { available: 10, reserved: 0 });
store.buckets.set(userId, [{ id: bucketId, userId, source: "plan", remaining: 10, priority: 0, expiresAt: new Date(Date.now() - 1_000).toISOString() }]);
assert.equal(reserveBalance(store, userId, 1, "expired-reservation"), false);
assert.equal(store.buckets.get(userId)?.[0].remaining, 0);
assert.deepEqual({ available: store.balances.get(userId)?.available, reserved: store.balances.get(userId)?.reserved }, { available: 0, reserved: 0 });
assert.equal(store.ledger.filter((entry) => entry.idempotencyKey === `expire:${userId}:${bucketId}`).length, 1);
});
test("expired bucket adjustment never drives aggregate balance below zero", () => {
const store = createStore();
const userId = "expired-bucket-clamp";
store.balances.set(userId, { available: 3, reserved: 0 });
store.buckets.set(userId, [{ id: "expired-large", userId, source: "plan", remaining: 10, priority: 0, expiresAt: new Date(Date.now() - 1_000).toISOString() }]);
assert.equal(reserveBalance(store, userId, 1, "clamp-reservation"), false);
assert.deepEqual({ available: store.balances.get(userId)?.available, reserved: store.balances.get(userId)?.reserved }, { available: 0, reserved: 0 });
assert.equal(store.ledger.find((entry) => entry.idempotencyKey === `expire:${userId}:expired-large`)?.amount, -3);
});
test("task route snapshots follow channel-group priority order", async () => {
const { store, server, base } = await start();
try {
const user = await registerAndLogin(base, "channel-order@example.com");
store.balances.set(user.registered.userId, { available: 100, reserved: 0 });
const group = store.channelGroups.find((item) => item.id === "image-default");
assert.ok(group);
group.channelIds = ["channel-c", "channel-a", "channel-d", "channel-b"];
group.channelPriorities = { "channel-c": 0, "channel-a": 1, "channel-d": 2, "channel-b": 3 };
const response = await fetch(`${base}/api/v1/tasks/image`, { method: "POST", headers: { authorization: `Bearer ${user.payload.data.accessToken!}`, "content-type": "application/json", "idempotency-key": "channel-order-task" }, body: JSON.stringify({ modelProductId: "basic-image-v1", prompt: "route snapshot" }) });
assert.equal(response.status, 202);
const task = [...store.tasks.values()].find((item) => item.ownerId === user.registered.userId);
assert.deepEqual(task?.routeSnapshot?.channelIds, ["channel-c", "channel-a", "channel-d", "channel-b"]);
} finally {
await new Promise<void>((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
}
});
test("documented admin singleton and detail method aliases remain callable", async () => {
const { server, base } = await start();
try {
const login = await fetch(`${base}/api/v1/admin/auth/login`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email: "admin@example.com", password: "change-me-now" }) });
const loginPayload = await login.json() as { data: { accessToken: string } };
const headers = (key: string) => ({ authorization: `Bearer ${loginPayload.data.accessToken}`, "content-type": "application/json", "idempotency-key": key });
assert.equal((await fetch(`${base}/api/v1/admin/billing/units`, { headers: headers("alias-units-get") })).status, 200);
assert.equal((await fetch(`${base}/api/v1/admin/billing/units`, { method: "POST", headers: headers("alias-units-post"), body: JSON.stringify({ name: "金币" }) })).status, 201);
assert.equal((await fetch(`${base}/api/v1/admin/billing/conversion-rules`, { method: "POST", headers: headers("alias-conversion-post"), body: JSON.stringify({ conversionRate: 1, rounding: "round" }) })).status, 201);
assert.equal((await fetch(`${base}/api/v1/admin/pricing-rules`, { method: "POST", headers: headers("alias-pricing-post"), body: JSON.stringify({ modelProductId: "basic-image", basePrice: 10 }) })).status, 201);
assert.equal((await fetch(`${base}/api/v1/admin/storage-policies`, { method: "POST", headers: headers("alias-storage-post"), body: JSON.stringify({ retentionDays: 30 }) })).status, 201);
assert.equal((await fetch(`${base}/api/v1/admin/recharge-orders/unknown-order`, { headers: headers("alias-order-detail") })).status, 404);
} finally {
await new Promise<void>((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
}
});
test("audit actors use the user name and direct administrator email", async () => {
const { store, server, base } = await start();
try {
const user = await registerAndLogin(base, "audit-actor@example.com");
const userRecord = store.users.get(user.registered.userId);
assert.ok(userRecord);
userRecord.username = "日志用户";
const now = new Date().toISOString();
store.audit.push(
{ id: "audit-user-actor", actorId: userRecord.id, action: "user.password.change", objectType: "user", objectId: userRecord.id, requestId: "audit-user-request", createdAt: now },
{ id: "audit-admin-actor", actorId: "admin", action: "admin.login.succeeded", objectType: "admin", objectId: "admin", requestId: "audit-admin-request", createdAt: now },
);
const login = await fetch(`${base}/api/v1/admin/auth/login`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email: "admin@example.com", password: "change-me-now" }) });
const loginPayload = await login.json() as { data: { accessToken: string } };
const response = await fetch(`${base}/api/v1/admin/audit-logs`, { headers: { authorization: `Bearer ${loginPayload.data.accessToken}` } });
assert.equal(response.status, 200);
const items = (await response.json() as { data: { items: Array<{ id: string; actorName: string }> } }).data.items;
assert.equal(items.find((item) => item.id === "audit-user-actor")?.actorName, "日志用户");
assert.equal(items.find((item) => item.id === "audit-admin-actor")?.actorName, "admin@example.com");
} finally {
await new Promise<void>((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
}
});
test("admin system settings are independent, versioned, and apply auth switches immediately", async () => {
const { server, base } = await start();
try {
const login = await fetch(`${base}/api/v1/admin/auth/login`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email: "admin@example.com", password: "change-me-now" }) });
const loginPayload = await login.json() as { data: { accessToken: string } };
const authHeaders = { authorization: `Bearer ${loginPayload.data.accessToken}` };
const initial = await fetch(`${base}/api/v1/admin/settings`, { headers: authHeaders });
assert.equal(initial.status, 200);
const initialPayload = await initial.json() as { data: { items: Array<{ version: number; userVerificationRequired: boolean }> } };
const current = initialPayload.data.items[0];
assert.ok(current);
assert.equal(current.userVerificationRequired, true);
const updated = await fetch(`${base}/api/v1/admin/settings`, { method: "PATCH", headers: { ...authHeaders, "content-type": "application/json", "idempotency-key": "settings-update-1", "if-match": String(current.version) }, body: JSON.stringify({ adminMfaRequired: false, adminCaptchaRequired: false, userMfaRequired: false, userVerificationRequired: false, captchaRequired: false, rateLimitWindowMs: 120000, rateLimitMax: 20, queueMaxConcurrent: 8, auditRetentionDays: 180, auditMaxEntries: 5000 }) });
assert.equal(updated.status, 200);
const updatedPayload = await updated.json() as { data: { item: { version: number; userVerificationRequired: boolean; rateLimitWindowMs: number } } };
assert.equal(updatedPayload.data.item.version, current.version + 1);
assert.equal(updatedPayload.data.item.userVerificationRequired, false);
assert.equal(updatedPayload.data.item.rateLimitWindowMs, 120000);
const policy = await fetch(`${base}/api/v1/auth/policy`);
assert.deepEqual((await policy.json() as { data: unknown }).data, { captchaRequired: false, userVerificationRequired: false, userMfaRequired: false });
const stale = await fetch(`${base}/api/v1/admin/settings`, { method: "PATCH", headers: { ...authHeaders, "content-type": "application/json", "idempotency-key": "settings-update-stale", "if-match": String(current.version) }, body: JSON.stringify({ rateLimitMax: 21 }) });
assert.equal(stale.status, 409);
} finally {
await new Promise<void>((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
}
});
test("admin recharge order PATCH only uses guarded failure/refund transitions", async () => {
const { store, server, base } = await start();
try {
const user = await registerAndLogin(base, "admin-order-patch@example.com");
const login = await fetch(`${base}/api/v1/admin/auth/login`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email: "admin@example.com", password: "change-me-now" }) });
const admin = await login.json() as { data: { accessToken: string } };
const headers = { authorization: `Bearer ${admin.data.accessToken}`, "content-type": "application/json", "idempotency-key": "admin-order-patch" };
const orderId = "admin-order-fixture";
store.rechargeOrders.set(orderId, { id: orderId, userId: user.registered.userId, provider: "mock", status: "pending", fiatAmount: 10, balanceAmount: 10, unitVersion: 1, idempotencyKey: "fixture-order", createdAt: new Date().toISOString() });
const failed = await fetch(`${base}/api/v1/admin/recharge-orders/${orderId}`, { method: "PATCH", headers: { ...headers, "if-match": "1" }, body: JSON.stringify({ status: "failed" }) });
assert.equal(failed.status, 200);
const refund = await fetch(`${base}/api/v1/admin/recharge-orders/${orderId}`, { method: "PATCH", headers: { ...headers, "idempotency-key": "admin-order-refund", "if-match": "2" }, body: JSON.stringify({ status: "refunded" }) });
assert.equal(refund.status, 409);
assert.equal(store.balances.get(user.registered.userId)?.available, 0);
} finally {
await new Promise<void>((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
}
});