Files
MiragenFlow/server/tests/auth-task.test.ts
T

283 lines
35 KiB
TypeScript

import test from "node:test";
import assert from "node:assert/strict";
import { createHash, createHmac } from "node:crypto";
import { createHttpServer } from "../src/app/http.ts";
import { createStore } from "../src/store.ts";
function totpCode(secret: string, now = Date.now()) {
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
let buffer = 0; let bits = 0; const bytes: number[] = [];
for (const character of secret) {
buffer = (buffer << 5) | alphabet.indexOf(character); 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");
}
test("registration, verification, recharge and task idempotency keep one charge", async () => {
const server = createHttpServer(createStore(), { host: "127.0.0.1", port: 0, accessTokenSecret: "test", accessTokenTtlSeconds: 900, refreshCookieName: "refresh", adminEmail: "admin@example.com", adminPassword: "change-me-now", adminMfaRequired: true });
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}`;
const register = await fetch(`${base}/api/v1/auth/register`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email: "test@example.com", password: "password123" }) });
const registered = (await register.json()) as { data: { userId: string; devVerificationCode: string } };
const verify = 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(verify.status, 200);
const login = await fetch(`${base}/api/v1/auth/login`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email: "test@example.com", password: "password123" }) });
const session = (await login.json()) as { data: { accessToken: string } };
const auth = { Authorization: `Bearer ${session.data.accessToken}`, "Content-Type": "application/json" };
const recharge = await fetch(`${base}/api/v1/recharge/orders`, { method: "POST", headers: { ...auth, "Idempotency-Key": "recharge" }, body: JSON.stringify({ amount: 100 }) });
const rechargePayload = await recharge.json() as { data: Record<string, unknown> };
assert.equal(recharge.status, 201);
assert.equal(Object.prototype.hasOwnProperty.call(rechargePayload.data, "provider"), false);
const rechargeOrders = await fetch(`${base}/api/v1/recharge/orders`, { headers: auth });
const rechargeOrdersPayload = await rechargeOrders.json() as { data: { items: Array<Record<string, unknown>> } };
assert.equal(Object.prototype.hasOwnProperty.call(rechargeOrdersPayload.data.items[0], "provider"), false);
const first = await fetch(`${base}/api/v1/tasks/image`, { method: "POST", headers: { ...auth, "Idempotency-Key": "task" }, body: JSON.stringify({ modelProductId: "basic-image", prompt: "fixture" }) });
const firstPayload = (await first.json()) as { data: { id: string } };
const second = await fetch(`${base}/api/v1/tasks/image`, { method: "POST", headers: { ...auth, "Idempotency-Key": "task" }, body: JSON.stringify({ modelProductId: "basic-image", prompt: "fixture" }) });
const secondPayload = (await second.json()) as { data: { id: string } };
assert.equal(firstPayload.data.id, secondPayload.data.id);
await new Promise((resolve) => setTimeout(resolve, 500));
const balance = await fetch(`${base}/api/v1/balance`, { headers: auth });
const balancePayload = (await balance.json()) as { data: { available: number; reserved: number } };
assert.deepEqual(balancePayload.data, { available: 90, reserved: 0, unit: { name: "金币", shortCode: "COIN", icon: "coins", precision: 0, version: 1 } });
await new Promise<void>((resolve, reject) => server.close((error) => (error ? reject(error) : resolve())));
});
test("adapter payment failure does not credit a recharge order", async () => {
const previous = process.env.NODE_ENV; process.env.NODE_ENV = "development";
const store = createStore(); store.paymentProviders.clear(); store.paymentProviders.set("adapter", { id: "adapter", enabled: true, mode: "live" });
const server = createHttpServer(store, { host: "127.0.0.1", port: 0, accessTokenSecret: "test", accessTokenTtlSeconds: 900, refreshCookieName: "refresh", adminEmail: "admin@example.com", adminPassword: "change-me-now", adminMfaRequired: true, paymentWebhookSecret: "webhook-secret" });
await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", resolve));
try {
const address = server.address(); assert.ok(address && typeof address !== "string"); const base = `http://127.0.0.1:${address.port}`;
const register = await fetch(`${base}/api/v1/auth/register`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email: "adapter-failure@example.com", password: "password123" }) });
const registered = await register.json() as { data: { userId: string; devVerificationCode: string } };
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 }) });
const login = await fetch(`${base}/api/v1/auth/login`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email: "adapter-failure@example.com", password: "password123" }) });
const session = await login.json() as { data: { accessToken: string } }; const auth = { Authorization: `Bearer ${session.data.accessToken}`, "Content-Type": "application/json", "Idempotency-Key": "adapter-recharge" };
const orderResponse = await fetch(`${base}/api/v1/recharge/orders`, { method: "POST", headers: auth, body: JSON.stringify({ amount: 100 }) }); const order = await orderResponse.json() as { data: { orderId: string } };
const payload = { orderId: order.data.orderId, eventId: "adapter-failure-event", status: "failed", amount: 100, currency: "CNY" };
const signature = createHmac("sha256", "webhook-secret").update(createHash("sha256").update(JSON.stringify(payload)).digest("hex")).digest("hex");
const webhook = await fetch(`${base}/api/v1/payment/webhooks/adapter`, { method: "POST", headers: { "content-type": "application/json", "x-miragenflow-signature": signature }, body: JSON.stringify(payload) });
assert.equal(webhook.status, 200); const balance = await fetch(`${base}/api/v1/balance`, { headers: auth }); const balancePayload = await balance.json() as { data: { available: number } }; assert.equal(balancePayload.data.available, 0);
} finally { await new Promise<void>((resolve, reject) => server.close((error) => error ? reject(error) : resolve())); if (previous === undefined) delete process.env.NODE_ENV; else process.env.NODE_ENV = previous; }
});
test("adapter refund reverses recharge credit once", async () => {
const previous = process.env.NODE_ENV; process.env.NODE_ENV = "development";
const store = createStore(); store.paymentProviders.clear(); store.paymentProviders.set("adapter", { id: "adapter", enabled: true, mode: "live" });
const server = createHttpServer(store, { host: "127.0.0.1", port: 0, accessTokenSecret: "test", accessTokenTtlSeconds: 900, refreshCookieName: "refresh", adminEmail: "admin@example.com", adminPassword: "change-me-now", adminMfaRequired: true, paymentWebhookSecret: "webhook-secret" });
await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", resolve));
try {
const address = server.address(); assert.ok(address && typeof address !== "string"); const base = `http://127.0.0.1:${address.port}`;
const register = await fetch(`${base}/api/v1/auth/register`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email: "adapter-refund@example.com", password: "password123" }) }); const registered = await register.json() as { data: { userId: string; devVerificationCode: string } };
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 }) });
const login = await fetch(`${base}/api/v1/auth/login`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email: "adapter-refund@example.com", password: "password123" }) }); const session = await login.json() as { data: { accessToken: string } }; const auth = { Authorization: `Bearer ${session.data.accessToken}`, "Content-Type": "application/json", "Idempotency-Key": "adapter-refund-order" };
const orderResponse = await fetch(`${base}/api/v1/recharge/orders`, { method: "POST", headers: auth, body: JSON.stringify({ amount: 100 }) }); const order = await orderResponse.json() as { data: { orderId: string } };
const paidBody = { orderId: order.data.orderId, eventId: "adapter-paid-event", status: "paid", amount: 100, currency: "CNY" }; const sign = (body: unknown) => createHmac("sha256", "webhook-secret").update(createHash("sha256").update(JSON.stringify(body)).digest("hex")).digest("hex");
assert.equal((await fetch(`${base}/api/v1/payment/webhooks/adapter`, { method: "POST", headers: { "content-type": "application/json", "x-miragenflow-signature": sign(paidBody) }, body: JSON.stringify(paidBody) })).status, 200);
const refundBody = { ...paidBody, eventId: "adapter-refund-event", status: "refunded" };
const orderRecord = store.rechargeOrders.get(order.data.orderId)!; const bucket = (store.buckets.get(registered.data.userId) || []).find((item) => item.id === orderRecord.creditBucketId)!; const account = store.balances.get(registered.data.userId)!;
bucket.remaining -= 1; account.available -= 1;
assert.equal((await fetch(`${base}/api/v1/payment/webhooks/adapter`, { method: "POST", headers: { "content-type": "application/json", "x-miragenflow-signature": sign(refundBody) }, body: JSON.stringify(refundBody) })).status, 409);
assert.equal(orderRecord.status, "paid"); assert.equal(store.ledger.some((entry) => entry.referenceId === order.data.orderId && entry.type === "refund"), false);
bucket.remaining += 1; account.available += 1;
assert.equal((await fetch(`${base}/api/v1/payment/webhooks/adapter`, { method: "POST", headers: { "content-type": "application/json", "x-miragenflow-signature": sign(refundBody) }, body: JSON.stringify(refundBody) })).status, 200);
const repeated = await fetch(`${base}/api/v1/payment/webhooks/adapter`, { method: "POST", headers: { "content-type": "application/json", "x-miragenflow-signature": sign(refundBody) }, body: JSON.stringify(refundBody) }); assert.equal(repeated.status, 200);
const balance = await fetch(`${base}/api/v1/balance`, { headers: auth }); const balancePayload = await balance.json() as { data: { available: number } }; assert.equal(balancePayload.data.available, 0);
const ledger = await fetch(`${base}/api/v1/balance/ledger`, { headers: auth }); const ledgerPayload = await ledger.json() as { data: { items: Array<{ type: string; referenceId?: string; bucketId?: string }> } }; const orderEntries = ledgerPayload.data.items.filter((item) => item.referenceId === order.data.orderId); assert.equal(orderEntries.filter((item) => item.type === "refund").length, 1); assert.ok(orderEntries.every((item) => Boolean(item.bucketId)));
} finally { await new Promise<void>((resolve, reject) => server.close((error) => error ? reject(error) : resolve())); if (previous === undefined) delete process.env.NODE_ENV; else process.env.NODE_ENV = previous; }
});
test("channel failures move through the ordered group and release on exhaustion", async () => {
const previous = process.env.MIRAGENFLOW_FIXTURE_FAIL_CHANNELS;
process.env.MIRAGENFLOW_FIXTURE_FAIL_CHANNELS = "channel-a,channel-b,channel-c";
const server = createHttpServer(createStore(), { host: "127.0.0.1", port: 0, accessTokenSecret: "test", accessTokenTtlSeconds: 900, refreshCookieName: "refresh", adminEmail: "admin@example.com", adminPassword: "change-me-now", adminMfaRequired: true });
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}`;
const register = await fetch(`${base}/api/v1/auth/register`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email: "failover@example.com", password: "password123" }) });
const registered = (await register.json()) as { data: { userId: string; devVerificationCode: string } };
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 }) });
const login = await fetch(`${base}/api/v1/auth/login`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email: "failover@example.com", password: "password123" }) });
const session = (await login.json()) as { data: { accessToken: string } };
const auth = { Authorization: `Bearer ${session.data.accessToken}`, "Content-Type": "application/json" };
await fetch(`${base}/api/v1/recharge/orders`, { method: "POST", headers: { ...auth, "Idempotency-Key": "recharge" }, body: JSON.stringify({ amount: 100 }) });
const created = await fetch(`${base}/api/v1/tasks/image`, { method: "POST", headers: { ...auth, "Idempotency-Key": "failover" }, body: JSON.stringify({ modelProductId: "basic-image", prompt: "fixture" }) });
const task = (await created.json()) as { data: { id: string } };
await new Promise((resolve) => setTimeout(resolve, 300));
const events = await fetch(`${base}/api/v1/tasks/${task.data.id}/events`, { headers: auth });
const eventPayload = (await events.json()) as { data: { items: Array<{ type: string; payload: Record<string, unknown> }> } };
const attempts = eventPayload.data.items.filter((event) => event.type === "task.attempt");
assert.equal(attempts.length, 4);
const terminal = await fetch(`${base}/api/v1/tasks/${task.data.id}`, { headers: auth });
const terminalPayload = (await terminal.json()) as { data: { status: string; attempts: Array<{ status: string }> } };
assert.equal(terminalPayload.data.status, "succeeded");
assert.equal(terminalPayload.data.attempts.filter((attempt) => attempt.status === "failed").length, 3);
await new Promise<void>((resolve, reject) => server.close((error) => (error ? reject(error) : resolve())));
if (previous === undefined) delete process.env.MIRAGENFLOW_FIXTURE_FAIL_CHANNELS;
else process.env.MIRAGENFLOW_FIXTURE_FAIL_CHANNELS = previous;
});
test("plan purchase mock webhook credits once and hides provider details", async () => {
const server = createHttpServer(createStore(), { host: "127.0.0.1", port: 0, accessTokenSecret: "test", accessTokenTtlSeconds: 900, refreshCookieName: "refresh", adminEmail: "admin@example.com", adminPassword: "change-me-now", adminMfaRequired: true });
await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", resolve));
try {
const address = server.address(); assert.ok(address && typeof address !== "string"); const base = `http://127.0.0.1:${address.port}`;
const register = await fetch(`${base}/api/v1/auth/register`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email: "plan-purchase@example.com", password: "password123" }) });
const registered = await register.json() as { data: { userId: string; devVerificationCode: string } };
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 }) });
const login = await fetch(`${base}/api/v1/auth/login`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email: "plan-purchase@example.com", password: "password123" }) });
const session = await login.json() as { data: { accessToken: string } }; const auth = { Authorization: `Bearer ${session.data.accessToken}`, "Content-Type": "application/json", "Idempotency-Key": "plan-purchase" };
const purchase = await fetch(`${base}/api/v1/plans/starter/purchase`, { method: "POST", headers: auth, body: "{}" }); const payload = await purchase.json() as { data: Record<string, unknown> };
assert.equal(purchase.status, 202); assert.equal(payload.data.status, "paid");
for (const field of ["userId", "provider", "idempotencyKey", "requestFingerprint", "creditBucketId", "entitlementId", "planSnapshot"]) assert.equal(Object.prototype.hasOwnProperty.call(payload.data, field), false);
const plan = await fetch(`${base}/api/v1/catalog/plans/starter`); const planPayload = await plan.json() as { data: { item: { allowedModelProductIds: string[] } } }; assert.equal(plan.status, 200); assert.ok(planPayload.data.item.allowedModelProductIds.every((value) => value.endsWith("-v1")));
const entitlements = await fetch(`${base}/api/v1/me/entitlements`, { headers: auth }); const entitlementPayload = await entitlements.json() as { data: { items: Array<{ allowedModelProductIds: string[] }> } }; assert.equal(entitlements.status, 200); assert.ok(entitlementPayload.data.items[0].allowedModelProductIds.every((value) => value.endsWith("-v1")));
const balance = await fetch(`${base}/api/v1/balance`, { headers: auth }); const balancePayload = await balance.json() as { data: { available: number; reserved: number } }; assert.equal(balancePayload.data.available, 100); assert.equal(balancePayload.data.reserved, 0);
const repeated = await fetch(`${base}/api/v1/plans/starter/purchase`, { method: "POST", headers: auth, body: "{}" }); const repeatedPayload = await repeated.json() as { data: { id: string } }; assert.equal(repeatedPayload.data.id, payload.data.id);
const ledger = await fetch(`${base}/api/v1/balance/ledger`, { headers: auth }); const ledgerPayload = await ledger.json() as { data: { items: Array<{ referenceId?: string; bucketId?: string }> } }; const purchaseEntries = ledgerPayload.data.items.filter((item) => item.referenceId === payload.data.id); assert.equal(purchaseEntries.length, 1); assert.ok(purchaseEntries.every((item) => Boolean(item.bucketId)));
} finally { await new Promise<void>((resolve, reject) => server.close((error) => error ? reject(error) : resolve())); }
});
test("queued cancellation releases the reserved balance exactly once", async () => {
const server = createHttpServer(createStore(), { host: "127.0.0.1", port: 0, accessTokenSecret: "test", accessTokenTtlSeconds: 900, refreshCookieName: "refresh", adminEmail: "admin@example.com", adminPassword: "change-me-now", adminMfaRequired: true });
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}`;
const register = await fetch(`${base}/api/v1/auth/register`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email: "cancel@example.com", password: "password123" }) });
const registered = (await register.json()) as { data: { userId: string; devVerificationCode: string } };
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 }) });
const login = await fetch(`${base}/api/v1/auth/login`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email: "cancel@example.com", password: "password123" }) });
const session = (await login.json()) as { data: { accessToken: string } };
const auth = { Authorization: `Bearer ${session.data.accessToken}`, "Content-Type": "application/json" };
await fetch(`${base}/api/v1/recharge/orders`, { method: "POST", headers: { ...auth, "Idempotency-Key": "recharge" }, body: JSON.stringify({ amount: 100 }) });
const created = await fetch(`${base}/api/v1/tasks/image`, { method: "POST", headers: { ...auth, "Idempotency-Key": "cancel" }, body: JSON.stringify({ modelProductId: "basic-image", prompt: "fixture" }) });
const task = (await created.json()) as { data: { id: string } };
const canceled = await fetch(`${base}/api/v1/tasks/${task.data.id}/cancel`, { method: "POST", headers: auth });
assert.equal(canceled.status, 200);
await new Promise((resolve) => setTimeout(resolve, 100));
const balance = await fetch(`${base}/api/v1/balance`, { headers: auth });
const balancePayload = (await balance.json()) as { data: { available: number; reserved: number } };
assert.equal(balancePayload.data.available, 100);
assert.equal(balancePayload.data.reserved, 0);
await new Promise<void>((resolve, reject) => server.close((error) => (error ? reject(error) : resolve())));
});
test("public task responses do not expose provider route fields and 3D remains disabled", async () => {
const store = createStore();
const server = createHttpServer(store, { host: "127.0.0.1", port: 0, accessTokenSecret: "test", accessTokenTtlSeconds: 900, refreshCookieName: "refresh", adminEmail: "admin@example.com", adminPassword: "change-me-now", adminMfaRequired: true });
await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", resolve));
try {
const address = server.address(); assert.ok(address && typeof address !== "string"); const base = `http://127.0.0.1:${address.port}`;
const register = await fetch(`${base}/api/v1/auth/register`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email: "public-contract@example.com", password: "password123" }) });
const registered = (await register.json()) as { data: { userId: string; devVerificationCode: string } };
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 }) });
const login = await fetch(`${base}/api/v1/auth/login`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email: "public-contract@example.com", password: "password123" }) });
const session = (await login.json()) as { data: { accessToken: string } }; const auth = { Authorization: `Bearer ${session.data.accessToken}` };
const catalog = await fetch(`${base}/api/v1/catalog/models`, { headers: auth }); const catalogPayload = await catalog.json() as { data: { items: Array<Record<string, unknown>> } };
assert.equal(Object.prototype.hasOwnProperty.call(catalogPayload.data.items[0], "channelGroupId"), false);
const task = "provider-task"; const now = new Date().toISOString(); store.tasks.set(task, { id: task, ownerId: registered.data.userId, taskType: "image", modelProductId: "basic-image", status: "queued", estimatedCost: 1, reservedCost: 0, createdAt: now, updatedAt: now, eventSequence: 0, routeSnapshot: { groupId: "image-default", groupVersion: 1, publicModelId: "basic-image-v1", routeVersion: 1, orderedCandidates: [{ channelId: "channel-a", priorityTier: 1, orderWithinTier: 1, retryBudget: 0, requestModelId: "basic-image-v1" }], strategy: "strict", totalRetryBudget: 0 }, modelSnapshot: { publicModelId: "basic-image-v1", modelVersion: 1, name: "基础模型", tier: "basic", capabilities: ["image"] }, pricingSnapshot: { ruleId: "price-basic-image", ruleVersion: 1, publicModelId: "basic-image-v1", resolutionId: "low", unitPrice: 1, quantity: 1, billingUnit: "output", multiplier: 1, balanceUnitVersion: 1 }, planSnapshot: { queuePriority: 0, maxConcurrent: 1 }, reserveExpiresAt: now, idempotencyKey: "private-task-key", requestFingerprint: "private-fingerprint", attempts: [{ id: "attempt", channelId: "channel-a", sequence: 1, status: "started", providerRequestId: "provider-secret", platformIdempotencyKey: "provider-idempotency", leaseExpiresAt: now, startedAt: now }], outputs: [] });
const hidden = JSON.stringify(store.tasks.get(task)); assert.equal(hidden.includes("provider-secret"), true);
const taskResponse = await fetch(`${base}/api/v1/tasks/${task}`, { headers: auth }); const taskPayload = await taskResponse.json() as { data: Record<string, unknown> & { attempts?: Array<Record<string, unknown>> } };
assert.equal(taskResponse.status, 200);
for (const field of ["ownerId", "routeSnapshot", "pricingSnapshot", "planSnapshot", "leaseExpiresAt", "leaseToken", "providerSubmitted", "preferredReserveBucketId", "idempotencyKey", "requestFingerprint", "reserveExpiresAt"]) assert.equal(Object.prototype.hasOwnProperty.call(taskPayload.data, field), false);
for (const field of ["channelId", "providerRequestId", "errorCode", "platformIdempotencyKey", "leaseExpiresAt"]) assert.equal(Object.prototype.hasOwnProperty.call(taskPayload.data.attempts?.[0] || {}, field), false);
const disabled3d = await fetch(`${base}/api/v1/tasks/3d`, { method: "POST", headers: { ...auth, "content-type": "application/json" }, body: JSON.stringify({ modelProductId: "basic-image", prompt: "fixture" }) }); const disabledPayload = await disabled3d.json() as { error?: { code?: string } }; assert.ok([400, 422].includes(disabled3d.status)); assert.equal(disabledPayload.error?.code, "CAPABILITY_NOT_ENABLED");
} finally {
await new Promise<void>((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
}
});
test("task creation accepts the published public model id without exposing route details", async () => {
const server = createHttpServer(createStore(), { host: "127.0.0.1", port: 0, accessTokenSecret: "test", accessTokenTtlSeconds: 900, refreshCookieName: "refresh", adminEmail: "admin@example.com", adminPassword: "change-me-now", adminMfaRequired: true });
await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", resolve));
try {
const address = server.address(); assert.ok(address && typeof address !== "string"); const base = `http://127.0.0.1:${address.port}`;
const register = await fetch(`${base}/api/v1/auth/register`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email: "public-id@example.com", password: "password123" }) });
const registered = await register.json() as { data: { userId: string; devVerificationCode: string } };
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 }) });
const login = await fetch(`${base}/api/v1/auth/login`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email: "public-id@example.com", password: "password123" }) });
const session = await login.json() as { data: { accessToken: string } }; const auth = { Authorization: `Bearer ${session.data.accessToken}`, "Content-Type": "application/json", "Idempotency-Key": "public-model-id" };
const response = await fetch(`${base}/api/v1/recharge/orders`, { method: "POST", headers: { ...auth, "Idempotency-Key": "public-id-recharge" }, body: JSON.stringify({ amount: 100 }) });
assert.equal(response.status, 201);
const task = await fetch(`${base}/api/v1/tasks/image`, { method: "POST", headers: auth, body: JSON.stringify({ modelProductId: "basic-image-v1", prompt: "fixture" }) });
assert.equal(task.status, 202);
const payload = await task.json() as { data: { modelSnapshot?: { publicModelId?: string }; modelProductId: string } };
assert.equal(payload.data.modelSnapshot?.publicModelId, "basic-image-v1");
} finally { await new Promise<void>((resolve, reject) => server.close((error) => error ? reject(error) : resolve())); }
});
test("published catalog never exposes a 3D capability in V1", async () => {
const store = createStore();
store.products[0].capabilities = [...store.products[0].capabilities, "3d"];
const server = createHttpServer(store, { host: "127.0.0.1", port: 0, accessTokenSecret: "test", accessTokenTtlSeconds: 900, refreshCookieName: "refresh", adminEmail: "admin@example.com", adminPassword: "change-me-now", adminMfaRequired: true });
await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", resolve));
try {
const address = server.address(); assert.ok(address && typeof address !== "string");
const response = await fetch(`http://127.0.0.1:${address.port}/api/v1/catalog/models`);
const payload = await response.json() as { data: { items: Array<{ capabilities: string[] }> } };
assert.equal(response.status, 200);
assert.equal(payload.data.items.some((item) => item.capabilities.includes("3d")), false);
} finally { await new Promise<void>((resolve, reject) => server.close((error) => error ? reject(error) : resolve())); }
});
test("user MFA recovery codes are one-time credentials", async () => {
const server = createHttpServer(createStore(), { host: "127.0.0.1", port: 0, accessTokenSecret: "test", accessTokenTtlSeconds: 900, refreshCookieName: "refresh", adminEmail: "admin@example.com", adminPassword: "change-me-now", adminMfaRequired: true, userMfaRequired: true });
await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", resolve));
try {
const address = server.address(); assert.ok(address && typeof address !== "string"); const base = `http://127.0.0.1:${address.port}`;
const register = await fetch(`${base}/api/v1/auth/register`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email: "mfa-recovery@example.com", password: "password123" }) });
const registered = await register.json() as { data: { userId: string; devVerificationCode: string } };
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 }) });
const enrollment = await fetch(`${base}/api/v1/auth/login`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email: "mfa-recovery@example.com", password: "password123" }) });
const enrollmentPayload = await enrollment.json() as { data: { challengeId: string; challengeType: string; mfaEnrollment: { secret: string; recoveryCodes: string[] } } };
assert.equal(enrollmentPayload.data.challengeType, "setup");
assert.ok(enrollmentPayload.data.mfaEnrollment.recoveryCodes.length > 0);
const confirmed = await fetch(`${base}/api/v1/auth/mfa/verify`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ challengeId: enrollmentPayload.data.challengeId, code: totpCode(enrollmentPayload.data.mfaEnrollment.secret) }) });
assert.equal(confirmed.status, 200);
const firstLogin = await fetch(`${base}/api/v1/auth/login`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email: "mfa-recovery@example.com", password: "password123" }) });
const firstChallenge = await firstLogin.json() as { data: { challengeId: string; challengeType: string } };
assert.equal(firstChallenge.data.challengeType, "verify");
const recoveryCode = enrollmentPayload.data.mfaEnrollment.recoveryCodes[0];
const recovered = await fetch(`${base}/api/v1/auth/mfa/recovery`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ challengeId: firstChallenge.data.challengeId, recoveryCode }) });
assert.equal(recovered.status, 200);
const secondLogin = await fetch(`${base}/api/v1/auth/login`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email: "mfa-recovery@example.com", password: "password123" }) });
const secondChallenge = await secondLogin.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: secondChallenge.data.challengeId, recoveryCode }) });
assert.equal(repeated.status, 401);
} finally {
await new Promise<void>((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
}
});
test("voluntary user MFA remains active when the global policy is disabled", async () => {
const server = createHttpServer(createStore(), { host: "127.0.0.1", port: 0, accessTokenSecret: "test", accessTokenTtlSeconds: 900, refreshCookieName: "refresh", adminEmail: "admin@example.com", adminPassword: "change-me-now", adminMfaRequired: true, userMfaRequired: false });
await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", resolve));
try {
const address = server.address(); assert.ok(address && typeof address !== "string"); const base = `http://127.0.0.1:${address.port}`;
const register = await fetch(`${base}/api/v1/auth/register`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email: "mfa-policy@example.com", password: "password123" }) });
const registered = await register.json() as { data: { userId: string; devVerificationCode: string } };
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 }) });
const firstLogin = await fetch(`${base}/api/v1/auth/login`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email: "mfa-policy@example.com", password: "password123" }) });
const firstSession = await firstLogin.json() as { data: { accessToken: string } };
const setup = await fetch(`${base}/api/v1/auth/mfa/setup`, { method: "POST", headers: { Authorization: `Bearer ${firstSession.data.accessToken}`, "content-type": "application/json" }, body: "{}" });
const setupPayload = await setup.json() as { data: { challengeId: string; mfaEnrollment: { secret: string; recoveryCodes: string[] } } };
assert.ok(setupPayload.data.mfaEnrollment.recoveryCodes.length > 0);
const confirmed = await fetch(`${base}/api/v1/auth/mfa/verify`, { method: "POST", headers: { Authorization: `Bearer ${firstSession.data.accessToken}`, "content-type": "application/json" }, body: JSON.stringify({ challengeId: setupPayload.data.challengeId, code: totpCode(setupPayload.data.mfaEnrollment.secret) }) });
assert.equal(confirmed.status, 200);
const login = await fetch(`${base}/api/v1/auth/login`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email: "mfa-policy@example.com", password: "password123" }) });
const loginPayload = await login.json() as { data?: { accessToken?: string; mfaRequired?: boolean; challengeType?: string } };
assert.equal(login.status, 200); assert.equal(loginPayload.data?.accessToken, undefined); assert.equal(loginPayload.data?.mfaRequired, true); assert.equal(loginPayload.data?.challengeType, "verify");
} finally { await new Promise<void>((resolve, reject) => server.close((error) => error ? reject(error) : resolve())); }
});