test(billing): cover durable report settlement
This commit is contained in:
@@ -15,6 +15,7 @@ import {
|
||||
PersonalReportWorkerError,
|
||||
createPersonalReportWorker,
|
||||
} from "../src/lib/personal-report-worker-core.ts";
|
||||
import type { ReportBillingPort } from "../src/lib/personal-report-billing.ts";
|
||||
|
||||
const USER_ID = "11111111-1111-4111-8111-111111111111";
|
||||
const REQUEST_ID = "22222222-2222-4222-8222-222222222222";
|
||||
@@ -89,6 +90,7 @@ function createHarness(input: Readonly<{
|
||||
generate?: () => Promise<GeneratePersonalReportResult>;
|
||||
loseLeaseBeforeCompletion?: boolean;
|
||||
markReadyError?: PersonalReportJobServiceError;
|
||||
billing?: ReportBillingPort;
|
||||
}>) {
|
||||
let job = input.job ?? jobRecord();
|
||||
let report = input.report === undefined ? reportRecord() : input.report;
|
||||
@@ -97,6 +99,8 @@ function createHarness(input: Readonly<{
|
||||
let generateCount = 0;
|
||||
let completeCount = 0;
|
||||
let reportFailureCount = 0;
|
||||
let billingCompleteCount = 0;
|
||||
const billingReleaseReasons: string[] = [];
|
||||
|
||||
function requireLease(jobId: string, leaseToken: string): PersonalReportJobRecord {
|
||||
assert.equal(jobId, job.id);
|
||||
@@ -327,6 +331,19 @@ function createHarness(input: Readonly<{
|
||||
generateCount += 1;
|
||||
return input.generate?.() ?? { status: "ready", document: READY_DOCUMENT, evidenceHash: "c".repeat(64) };
|
||||
},
|
||||
billing: input.billing
|
||||
? {
|
||||
reserve: input.billing.reserve,
|
||||
complete: async (completeInput: Parameters<ReportBillingPort["complete"]>[0]) => {
|
||||
billingCompleteCount += 1;
|
||||
return input.billing!.complete(completeInput);
|
||||
},
|
||||
release: async (releaseInput: Parameters<ReportBillingPort["release"]>[0]) => {
|
||||
billingReleaseReasons.push(releaseInput.reason);
|
||||
return input.billing!.release(releaseInput);
|
||||
},
|
||||
}
|
||||
: undefined,
|
||||
now: () => NOW,
|
||||
retryDelayMs: () => 1_000,
|
||||
heartbeatIntervalMs: 60_000,
|
||||
@@ -340,6 +357,8 @@ function createHarness(input: Readonly<{
|
||||
get generateCount() { return generateCount; },
|
||||
get completeCount() { return completeCount; },
|
||||
get reportFailureCount() { return reportFailureCount; },
|
||||
get billingCompleteCount() { return billingCompleteCount; },
|
||||
billingReleaseReasons,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -537,6 +556,86 @@ test("retryable failures use the bounded job retry budget and fail the report on
|
||||
assert.equal(harness.reportFailureCount, 1);
|
||||
});
|
||||
|
||||
test("successful durable generation completes the reserved report usage", async () => {
|
||||
const billing: ReportBillingPort = {
|
||||
async reserve() { throw new Error("not used"); },
|
||||
async complete(input) {
|
||||
assert.equal(input.userId, USER_ID);
|
||||
assert.equal(input.requestId, REQUEST_ID);
|
||||
assert.deepEqual(input.usage, {
|
||||
actualModelId: "model-a",
|
||||
modelConfigVersion: 7,
|
||||
inputTokens: 120,
|
||||
outputTokens: 45,
|
||||
durationMs: 0,
|
||||
});
|
||||
return true;
|
||||
},
|
||||
async release() { throw new Error("release must not run"); },
|
||||
};
|
||||
const harness = createHarness({
|
||||
billing,
|
||||
generate: async () => ({
|
||||
status: "ready",
|
||||
document: READY_DOCUMENT,
|
||||
evidenceHash: "c".repeat(64),
|
||||
usage: { inputTokens: 120, outputTokens: 45, actualModelId: "model-a", modelConfigVersion: 7 },
|
||||
}),
|
||||
});
|
||||
|
||||
const result = await harness.worker.tick();
|
||||
|
||||
assert.equal(result.outcome, "ready");
|
||||
assert.equal(harness.billingCompleteCount, 1);
|
||||
assert.deepEqual(harness.billingReleaseReasons, []);
|
||||
});
|
||||
|
||||
test("terminal durable generation failure releases the reserved report usage", async () => {
|
||||
const billing: ReportBillingPort = {
|
||||
async reserve() { throw new Error("not used"); },
|
||||
async complete() { throw new Error("complete must not run"); },
|
||||
async release(input) {
|
||||
assert.equal(input.userId, USER_ID);
|
||||
assert.equal(input.requestId, REQUEST_ID);
|
||||
return true;
|
||||
},
|
||||
};
|
||||
const harness = createHarness({
|
||||
billing,
|
||||
generate: async () => ({
|
||||
status: "failed",
|
||||
failureCode: "report_schema_invalid",
|
||||
innerReason: "agent_output_invalid",
|
||||
}),
|
||||
});
|
||||
|
||||
const result = await harness.worker.tick();
|
||||
|
||||
assert.equal(result.outcome, "failed");
|
||||
assert.deepEqual(harness.billingReleaseReasons, ["report_schema_invalid"]);
|
||||
assert.equal(harness.billingCompleteCount, 0);
|
||||
});
|
||||
|
||||
test("retryable durable generation failure does not release before retry exhaustion", async () => {
|
||||
const billing: ReportBillingPort = {
|
||||
async reserve() { throw new Error("not used"); },
|
||||
async complete() { throw new Error("complete must not run"); },
|
||||
async release() { return true; },
|
||||
};
|
||||
const harness = createHarness({
|
||||
billing,
|
||||
job: jobRecord({ maxAttempts: 2 }),
|
||||
generate: async () => {
|
||||
throw new PersonalReportWorkerError("calculation_unavailable", true);
|
||||
},
|
||||
});
|
||||
|
||||
const first = await harness.worker.tick();
|
||||
|
||||
assert.equal(first.outcome, "retry_scheduled");
|
||||
assert.deepEqual(harness.billingReleaseReasons, []);
|
||||
});
|
||||
|
||||
test("instrumentation retains the Skill guard and starts a singleton Node worker loop", () => {
|
||||
const instrumentation = readFileSync(new URL("../src/instrumentation.ts", import.meta.url), "utf8");
|
||||
const productionAdapter = readFileSync(new URL("../src/lib/personal-report-worker.ts", import.meta.url), "utf8");
|
||||
|
||||
Reference in New Issue
Block a user