Add scheduled invoice delivery
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
/// <reference types="bun" />
|
||||
|
||||
import { afterEach, describe, expect, mock, test } from "bun:test";
|
||||
|
||||
import type { BackgroundJob } from "../../web/src/server/jobs/queue";
|
||||
import { sendScheduledInvoice } from "../src/send-invoice";
|
||||
|
||||
const originalFetch = globalThis.fetch;
|
||||
const originalAppUrl = process.env.APP_INTERNAL_URL;
|
||||
const originalSecret = process.env.CRON_SECRET;
|
||||
|
||||
function scheduledJob(): BackgroundJob {
|
||||
const now = new Date("2026-08-17T16:00:00.000Z");
|
||||
return {
|
||||
id: "job-1",
|
||||
type: "invoice.send_scheduled",
|
||||
payload: {
|
||||
invoiceId: "invoice-1",
|
||||
actorUserId: "user-1",
|
||||
customMessage: "Thanks!",
|
||||
timeZone: "America/New_York",
|
||||
},
|
||||
status: "processing",
|
||||
idempotencyKey: "invoice.send_scheduled:invoice-1:once",
|
||||
runAt: now,
|
||||
attempts: 1,
|
||||
maxAttempts: 5,
|
||||
lockedAt: now,
|
||||
lockedBy: "worker-1",
|
||||
lastError: null,
|
||||
completedAt: null,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
};
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
globalThis.fetch = originalFetch;
|
||||
if (originalAppUrl === undefined) delete process.env.APP_INTERNAL_URL;
|
||||
else process.env.APP_INTERNAL_URL = originalAppUrl;
|
||||
if (originalSecret === undefined) delete process.env.CRON_SECRET;
|
||||
else process.env.CRON_SECRET = originalSecret;
|
||||
});
|
||||
|
||||
describe("scheduled invoice delivery", () => {
|
||||
test("calls the internal app endpoint with auth and an idempotency key", async () => {
|
||||
process.env.APP_INTERNAL_URL = "http://app:3000/";
|
||||
process.env.CRON_SECRET = "worker-secret";
|
||||
const fetchMock = mock(
|
||||
async (_url: string | URL | Request, _request?: RequestInit) =>
|
||||
Response.json({ success: true, emailId: "email-1" }),
|
||||
);
|
||||
globalThis.fetch = fetchMock as unknown as typeof fetch;
|
||||
|
||||
await sendScheduledInvoice(scheduledJob());
|
||||
|
||||
expect(fetchMock).toHaveBeenCalledTimes(1);
|
||||
const [url, request] = fetchMock.mock.calls[0]!;
|
||||
expect(url).toBe("http://app:3000/api/internal/jobs/send-invoice");
|
||||
expect(request?.headers).toEqual({
|
||||
Authorization: "Bearer worker-secret",
|
||||
"Content-Type": "application/json",
|
||||
});
|
||||
expect(JSON.parse(String(request?.body))).toMatchObject({
|
||||
jobId: "job-1",
|
||||
invoiceId: "invoice-1",
|
||||
actorUserId: "user-1",
|
||||
idempotencyKey: "invoice.send_scheduled:invoice-1:once",
|
||||
timeZone: "America/New_York",
|
||||
});
|
||||
});
|
||||
|
||||
test("surfaces retryable delivery errors to the worker", async () => {
|
||||
process.env.APP_INTERNAL_URL = "http://app:3000";
|
||||
process.env.CRON_SECRET = "worker-secret";
|
||||
globalThis.fetch = mock(
|
||||
async (_url: string | URL | Request, _request?: RequestInit) =>
|
||||
Response.json(
|
||||
{ error: "Email service is unavailable" },
|
||||
{ status: 503 },
|
||||
),
|
||||
) as unknown as typeof fetch;
|
||||
|
||||
await expect(sendScheduledInvoice(scheduledJob())).rejects.toThrow(
|
||||
"Email service is unavailable",
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user