Files
pikasTech-HWLAB/internal/cloud/user-billing-integration.test.ts
T
2026-06-14 01:33:26 +08:00

347 lines
15 KiB
TypeScript

import assert from "node:assert/strict";
import { mkdtemp, rm } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { test } from "bun:test";
import { createCloudApiServer } from "./server.ts";
import { codexStdioChatFixture, codexStdioReadyFixture } from "./server-test-helpers.ts";
test("cloud api accepts user-billing API keys and records Code Agent billing usage", async () => {
const calls = [];
const userBillingClient = {
configured: true,
async introspect(token) {
calls.push({ op: "introspect", tokenPrefix: token.slice(0, 8) });
assert.equal(token, "hwl_user_billing_test_secret");
return {
ok: true,
status: 200,
body: {
active: true,
principal: {
userId: "usr_user_billing_agent",
email: "agent@hwlab.local",
username: "billing-agent",
role: "user",
scopes: ["api"],
authType: "api-key",
keyId: "key_user_billing_agent"
}
}
};
},
async billingPreflight(body) {
calls.push({ op: "preflight", body });
assert.equal(body.apiKey, "hwl_user_billing_test_secret");
assert.equal(body.serviceId, "hwlab-code-agent");
assert.equal(body.estimatedCredits, 1);
assert.equal(body.idempotencyKey, "code-agent:trc_user_billing_agent:preflight");
assert.equal(body.metadata.resource, "code-agent-turn");
return { ok: true, status: 200, body: { allowed: true, reservationId: "res_user_billing_agent", estimatedCredits: 1, expiresAt: "2026-06-13T12:00:00.000Z" } };
},
async billingRecord(body) {
calls.push({ op: "record", body });
assert.equal(body.reservationId, "res_user_billing_agent");
assert.equal(body.serviceId, "hwlab-code-agent");
assert.equal(body.usedCredits, 1);
assert.equal(body.idempotencyKey, "code-agent:trc_user_billing_agent:record");
return { ok: true, status: 200, body: { recordId: "use_user_billing_agent", credits: 1, balance: 99 } };
},
async billingSummary(token, options = {}) {
calls.push({ op: "summary", tokenPrefix: token.slice(0, 8), options });
assert.equal(token, "hwl_user_billing_test_secret");
assert.equal(options.limit, 20);
return {
ok: true,
status: 200,
body: {
contractVersion: "user-billing-summary-v1",
status: "ok",
credits: { balance: 99, reserved: 0, available: 99 },
usage: { totalCredits: 1, totalQuantity: 1200, recordCount: 1, byService: [{ serviceId: "hwlab-code-agent", credits: 1, quantity: 1200, recordCount: 1, lastUsedAt: "2026-06-13T12:00:00.000Z" }] },
ledger: { count: 1, limit: 20, rows: [{ id: "led_user_billing_agent", kind: "usage", reason: "hwlab-code-agent", deltaCredits: -1, balanceAfter: 99, createdAt: "2026-06-13T12:00:00.000Z" }] },
reservations: { activeCount: 0, active: [] },
state: { stateless: true, stateAuthority: "pk01-postgres", redis: { role: "cache-only" } },
valuesRedacted: true
}
};
},
async adminBillingSummary(options = {}) {
calls.push({ op: "adminSummary", options });
assert.equal(options.limit, 50);
return {
ok: true,
status: 200,
body: {
contractVersion: "user-billing-admin-summary-v1",
status: "ok",
count: 1,
limit: 50,
totals: { users: 1, activeUsers: 1, balanceCredits: 99, usageCredits: 1, activeReservations: 0 },
users: [{
user: { id: "usr_user_billing_agent", email: "agent@hwlab.local", username: "billing-agent", displayName: "Billing Agent", role: "user", status: "active" },
planId: "default",
credits: { balance: 99, reserved: 0, available: 99 },
usage: { totalCredits: 1, totalQuantity: 1200, recordCount: 1, lastUsedAt: "2026-06-13T12:00:00.000Z" },
apiKeys: { activeCount: 1, totalCount: 1, lastUsedAt: "2026-06-13T12:00:00.000Z" },
reservations: { activeCount: 0, active: [] }
}],
state: { stateless: true, stateAuthority: "pk01-postgres", redis: { role: "cache-only" } },
valuesRedacted: true
}
};
}
};
const workspace = await mkdtemp(path.join(os.tmpdir(), "hwlab-user-billing-agent-"));
const codexHome = await mkdtemp(path.join(os.tmpdir(), "hwlab-user-billing-agent-codex-"));
const server = createCloudApiServer({
env: {
PATH: process.env.PATH,
CODEX_HOME: codexHome,
HWLAB_ACCESS_CONTROL_REQUIRED: "1",
HWLAB_USER_BILLING_CODE_AGENT_ENABLED: "1",
HWLAB_CODE_AGENT_CODEX_WORKSPACE: workspace,
HWLAB_CODE_AGENT_CODEX_SANDBOX: "danger-full-access",
HWLAB_CODE_AGENT_PROVIDER: "codex-stdio",
HWLAB_CODE_AGENT_MODEL: "gpt-test",
OPENAI_API_KEY: "test-openai-key-material",
HWLAB_CODE_AGENT_OPENAI_BASE_URL: "http://127.0.0.1:65535/v1/responses"
},
userBillingClient,
codexStdioManager: {
describe() { return { ...codexStdioReadyFixture({ workspace, codexHome }), sandbox: "danger-full-access" }; },
async probe() { return { ...codexStdioReadyFixture({ workspace, codexHome }), sandbox: "danger-full-access" }; },
async chat(params = {}) {
return {
...codexStdioChatFixture({ workspace, codexHome, params }),
usage: { totalTokens: 1200 },
sandbox: "danger-full-access"
};
},
cancel() {},
reapIdle() {}
}
});
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
try {
const { port } = server.address();
const authHeader = { authorization: "Bearer hwl_user_billing_test_secret" };
const me = await fetch(`http://127.0.0.1:${port}/v1/users/me`, { headers: authHeader });
assert.equal(me.status, 200);
const meBody = await me.json();
assert.equal(meBody.actor.id, "usr_user_billing_agent");
assert.equal(meBody.authMethod, "user-billing-api-key");
const sessionResponse = await fetch(`http://127.0.0.1:${port}/v1/agent/sessions`, {
method: "POST",
headers: {
"content-type": "application/json",
...authHeader
},
body: JSON.stringify({
conversationId: "cnv_user_billing_agent",
sessionId: "ses_user_billing_agent",
providerProfile: "deepseek"
})
});
assert.equal(sessionResponse.status, 201);
const manual = await sessionResponse.json();
assert.equal(manual.ok, true);
assert.equal(manual.session.sessionId, "ses_user_billing_agent");
const submit = await fetch(`http://127.0.0.1:${port}/v1/agent/chat`, {
method: "POST",
headers: {
"content-type": "application/json",
authorization: "Bearer hwl_user_billing_test_secret",
"x-trace-id": "trc_user_billing_agent",
prefer: "respond-async"
},
body: JSON.stringify({
conversationId: manual.session.conversationId,
sessionId: manual.session.sessionId,
shortConnection: true,
message: "billing integration smoke"
})
});
assert.equal(submit.status, 202);
const result = await pollUserBillingAgentResult(port, "trc_user_billing_agent", authHeader);
assert.equal(result.status, "completed");
assert.equal(result.billing.recorded, true);
assert.equal(result.billing.reservationId, "res_user_billing_agent");
assert.equal(result.billing.recordId, "use_user_billing_agent");
const summaryResponse = await fetch(`http://127.0.0.1:${port}/v1/usage/summary`, { headers: authHeader });
assert.equal(summaryResponse.status, 200);
const summary = await summaryResponse.json();
assert.equal(summary.contractVersion, "user-billing-summary-v1");
assert.equal(summary.credits.available, 99);
assert.equal(summary.usage.byService[0].serviceId, "hwlab-code-agent");
assert.equal(summary.state.stateAuthority, "pk01-postgres");
assert.equal(summary.state.redis.role, "cache-only");
assert.equal(summary.proxy.source, "hwlab-user-billing");
assert.equal(JSON.stringify(summary).includes("hwl_user_billing_test_secret"), false);
const adminSummaryDenied = await fetch(`http://127.0.0.1:${port}/v1/admin/billing/summary`, { headers: authHeader });
assert.equal(adminSummaryDenied.status, 403);
const adminStatusDenied = await fetch(`http://127.0.0.1:${port}/v1/admin/billing/users/usr_user_billing_agent/status`, {
method: "PATCH",
headers: { "content-type": "application/json", ...authHeader },
body: JSON.stringify({ status: "disabled" })
});
assert.equal(adminStatusDenied.status, 403);
assert.deepEqual(calls.map((call) => call.op), ["introspect", "introspect", "introspect", "preflight", "record", "introspect", "introspect", "summary", "introspect", "introspect"]);
assert.equal(JSON.stringify(result).includes("hwl_user_billing_test_secret"), false);
} finally {
await new Promise((resolve, reject) => server.close((error) => (error ? reject(error) : resolve())));
await rm(workspace, { recursive: true, force: true });
await rm(codexHome, { recursive: true, force: true });
}
});
async function pollUserBillingAgentResult(port, traceId, headers) {
let last = null;
for (let attempt = 0; attempt < 20; attempt += 1) {
const response = await fetch(`http://127.0.0.1:${port}/v1/agent/chat/result/${encodeURIComponent(traceId)}`, { headers });
last = {
status: response.status,
body: await response.json()
};
if (response.status === 200) return last.body;
await new Promise((resolve) => setTimeout(resolve, 10));
}
throw new Error(`Code Agent result did not complete: ${JSON.stringify(last)}`);
}
test("cloud api proxies admin billing summary for web admins without exposing user-billing secrets", async () => {
const calls = [];
const userBillingClient = {
configured: true,
async adminBillingSummary(options = {}) {
calls.push({ op: "adminSummary", options });
assert.equal(options.limit, 50);
return {
ok: true,
status: 200,
body: {
contractVersion: "user-billing-admin-summary-v1",
status: "ok",
count: 1,
limit: 50,
totals: { users: 1, activeUsers: 1, balanceCredits: 25, reservedCredits: 5, availableCredits: 20, usageCredits: 3, activeReservations: 1 },
users: [{
user: { id: "usr_billing_admin_subject", email: "subject@hwlab.local", username: "billing-subject", displayName: "Billing Subject", role: "user", status: "active" },
planId: "default",
credits: { balance: 25, reserved: 5, available: 20 },
usage: { totalCredits: 3, totalQuantity: 2048, recordCount: 2, lastUsedAt: "2026-06-13T13:00:00.000Z" },
apiKeys: { activeCount: 1, totalCount: 2, lastUsedAt: "2026-06-13T12:30:00.000Z" },
reservations: { activeCount: 1, active: [{ reservationId: "res_admin_billing", serviceId: "hwlab-hwpod", estimatedCredits: 5, estimatedTokens: 0, status: "reserved", expiresAt: "2026-06-13T13:30:00.000Z", createdAt: "2026-06-13T13:00:00.000Z" }] }
}],
state: { stateless: true, stateAuthority: "pk01-postgres", redis: { role: "cache-only" } },
valuesRedacted: true
}
};
}
};
const server = createCloudApiServer({
env: {
HWLAB_ACCESS_CONTROL_REQUIRED: "1",
HWLAB_BOOTSTRAP_ADMIN_USERNAME: "admin",
HWLAB_BOOTSTRAP_ADMIN_PASSWORD: "admin-password-1127"
},
userBillingClient
});
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
try {
const { port } = server.address();
const login = await fetch(`http://127.0.0.1:${port}/auth/login`, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ username: "admin", password: "admin-password-1127" })
});
assert.equal(login.status, 200);
const cookie = login.headers.get("set-cookie")?.split(";")[0] ?? "";
assert.match(cookie, /^hwlab_session=/u);
const response = await fetch(`http://127.0.0.1:${port}/v1/admin/billing/summary`, { headers: { cookie } });
assert.equal(response.status, 200);
const summary = await response.json();
assert.equal(summary.contractVersion, "user-billing-admin-summary-v1");
assert.equal(summary.users[0].user.id, "usr_billing_admin_subject");
assert.equal(summary.users[0].credits.available, 20);
assert.equal(summary.users[0].reservations.active[0].serviceId, "hwlab-hwpod");
assert.equal(summary.state.stateAuthority, "pk01-postgres");
assert.equal(summary.state.redis.role, "cache-only");
assert.equal(summary.proxy.source, "hwlab-user-billing");
assert.equal(JSON.stringify(summary).includes("admin-password-1127"), false);
assert.deepEqual(calls.map((call) => call.op), ["adminSummary"]);
} finally {
await new Promise((resolve, reject) => server.close((error) => (error ? reject(error) : resolve())));
}
});
test("cloud api proxies admin user status updates for web admins", async () => {
const calls = [];
const userBillingClient = {
configured: true,
async updateAdminUserStatus(userId, status) {
calls.push({ op: "adminUserStatus", userId, status });
assert.equal(userId, "usr_billing_admin_subject");
assert.equal(status, "disabled");
return {
ok: true,
status: 200,
body: {
userId,
status
}
};
}
};
const server = createCloudApiServer({
env: {
HWLAB_ACCESS_CONTROL_REQUIRED: "1",
HWLAB_BOOTSTRAP_ADMIN_USERNAME: "admin",
HWLAB_BOOTSTRAP_ADMIN_PASSWORD: "admin-password-1127"
},
userBillingClient
});
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
try {
const { port } = server.address();
const login = await fetch(`http://127.0.0.1:${port}/auth/login`, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ username: "admin", password: "admin-password-1127" })
});
assert.equal(login.status, 200);
const cookie = login.headers.get("set-cookie")?.split(";")[0] ?? "";
assert.match(cookie, /^hwlab_session=/u);
const invalid = await fetch(`http://127.0.0.1:${port}/v1/admin/billing/users/usr_billing_admin_subject/status`, {
method: "PATCH",
headers: { "content-type": "application/json", cookie },
body: JSON.stringify({ status: "archived" })
});
assert.equal(invalid.status, 400);
const response = await fetch(`http://127.0.0.1:${port}/v1/admin/billing/users/usr_billing_admin_subject/status`, {
method: "PATCH",
headers: { "content-type": "application/json", cookie },
body: JSON.stringify({ status: "disabled" })
});
assert.equal(response.status, 200);
const payload = await response.json();
assert.equal(payload.userId, "usr_billing_admin_subject");
assert.equal(payload.status, "disabled");
assert.equal(payload.proxy.source, "hwlab-user-billing");
assert.equal(payload.proxy.route, "/v1/admin/billing/users/{userId}/status");
assert.equal(payload.valuesRedacted, true);
assert.equal(JSON.stringify(payload).includes("admin-password-1127"), false);
assert.deepEqual(calls, [{ op: "adminUserStatus", userId: "usr_billing_admin_subject", status: "disabled" }]);
} finally {
await new Promise((resolve, reject) => server.close((error) => (error ? reject(error) : resolve())));
}
});