Files
HiveGrid-Wireframes/03 Quellen und Referenzen/hivegrid-cloud/scripts/billing-catalog/sync.test.ts
T

102 lines
3.8 KiB
TypeScript

import { chmod, mkdtemp, readFile, stat } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { describe, expect, it, vi } from "vitest";
import { buildDesiredLagoCatalog, type LagoCatalogSnapshot } from "./catalog";
import type { BillingCatalogCommand } from "./contracts";
import type { LagoCatalogClient } from "./lago-client";
import { executeBillingCatalogCommand, writeRedactedCatalogSnapshot } from "./sync";
const desired = buildDesiredLagoCatalog();
const emptySnapshot: LagoCatalogSnapshot = {
organization: { name: "HiveGrid Staging", idSha256: "a".repeat(64) },
tax: null,
plans: [],
};
function command(mode: BillingCatalogCommand["mode"], snapshotDirectory = "tmp/test"): BillingCatalogCommand {
return {
target: "staging",
mode,
lagoApiUrl: "https://lago.example.test",
lagoApiKey: "secret",
expectedOrganizationName: "HiveGrid Staging",
expectedOrganizationIdSha256: "a".repeat(64),
snapshotDirectory,
};
}
describe("billing catalog sync", () => {
it("plans without mutating and fails verify on drift", async () => {
const client = {
readCatalog: vi.fn().mockResolvedValue(emptySnapshot),
applyTax: vi.fn(),
applyPlan: vi.fn(),
} as unknown as LagoCatalogClient;
const result = await executeBillingCatalogCommand(command("plan"), client);
expect(result.complete).toBe(false);
expect(client.applyTax).not.toHaveBeenCalled();
await expect(executeBillingCatalogCommand(command("verify"), client)).rejects.toThrow("verification found missing or drifted");
});
it("applies all missing resources and proves convergence", async () => {
const directory = await mkdtemp(join(tmpdir(), "hivegrid-billing-catalog-"));
const convergedSnapshot: LagoCatalogSnapshot = {
organization: emptySnapshot.organization,
tax: {
code: desired.tax.code,
name: desired.tax.name,
description: desired.tax.description,
rate: desired.tax.rate,
applied_to_organization: desired.tax.appliedToOrganization,
},
plans: desired.plans.map((plan) => ({
code: plan.code,
name: plan.name,
invoice_display_name: plan.invoiceDisplayName,
description: plan.description,
interval: plan.interval,
amount_cents: plan.amountCents,
amount_currency: plan.amountCurrency,
pay_in_advance: plan.payInAdvance,
taxes: plan.taxCodes.map((code) => ({ code })),
metadata: { ...plan.metadata },
})),
};
const client = {
readCatalog: vi.fn().mockResolvedValueOnce(emptySnapshot).mockResolvedValueOnce(convergedSnapshot),
applyTax: vi.fn(),
applyPlan: vi.fn(),
} as unknown as LagoCatalogClient;
const result = await executeBillingCatalogCommand(command("apply", directory), client);
expect(result.complete).toBe(true);
expect(client.applyTax).toHaveBeenCalledOnce();
expect(client.applyPlan).toHaveBeenCalledTimes(desired.plans.length);
expect(result.snapshotPath).toContain(directory);
});
it("writes only redacted catalog state with owner-only permissions", async () => {
const directory = await mkdtemp(join(tmpdir(), "hivegrid-billing-snapshot-"));
await chmod(directory, 0o700);
const path = await writeRedactedCatalogSnapshot({
directory,
target: "production",
snapshot: emptySnapshot,
assessment: {
catalogVersion: desired.version,
catalogDigestSha256: desired.digestSha256,
complete: false,
actions: [],
},
now: new Date("2026-07-29T08:00:00.000Z"),
});
const contents = await readFile(path, "utf8");
expect(contents).not.toContain("secret");
expect(contents).toContain(desired.digestSha256);
expect((await stat(path)).mode & 0o777).toBe(0o600);
});
});