148 lines
5.9 KiB
TypeScript
148 lines
5.9 KiB
TypeScript
import type { PlatformLeadSourceAdminDto } from "@hivegrid/db/platform-leads-admin.server";
|
|
import { describe, expect, it, vi } from "vitest";
|
|
|
|
import {
|
|
isSuccessfulPlatformLeadSourceBootstrapManifest,
|
|
planPlatformLeadSourceBootstrap,
|
|
runPlatformLeadSourceBootstrap,
|
|
type PlatformLeadSourceBootstrapService,
|
|
} from "./source-bootstrap.js";
|
|
|
|
function source(overrides: Partial<PlatformLeadSourceAdminDto> & Pick<PlatformLeadSourceAdminDto, "id" | "key">): PlatformLeadSourceAdminDto {
|
|
return {
|
|
id: overrides.id,
|
|
key: overrides.key,
|
|
displayName: overrides.displayName ?? overrides.key,
|
|
createdAt: overrides.createdAt ?? "2026-07-16T00:00:00.000Z",
|
|
updatedAt: overrides.updatedAt ?? "2026-07-16T00:00:00.000Z",
|
|
enabled: overrides.enabled ?? false,
|
|
defaultAssigneeAdminUserId: overrides.defaultAssigneeAdminUserId ?? null,
|
|
notifyAdminUserIds: overrides.notifyAdminUserIds ?? [],
|
|
rateLimitPerMinute: overrides.rateLimitPerMinute ?? 60,
|
|
leadCount: overrides.leadCount ?? 0,
|
|
metadata: overrides.metadata ?? null,
|
|
credentials: overrides.credentials ?? [],
|
|
};
|
|
}
|
|
|
|
function clock(...values: string[]): () => Date {
|
|
const dates = values.map((value) => new Date(value));
|
|
return () => dates.shift() ?? new Date("2026-07-16T12:00:00.000Z");
|
|
}
|
|
|
|
describe("platform lead source bootstrap", () => {
|
|
it("plans all three sources and configures the current website for the supplied operator only", () => {
|
|
const plan = planPlatformLeadSourceBootstrap([], "haris_user_id");
|
|
|
|
expect(plan.hash).toMatch(/^[a-f0-9]{64}$/u);
|
|
expect(plan.actions).toEqual([
|
|
{
|
|
kind: "create",
|
|
key: "hivegrid-website",
|
|
input: expect.objectContaining({ enabled: true, defaultAssigneeAdminUserId: "haris_user_id", notifyAdminUserIds: ["haris_user_id"] }),
|
|
},
|
|
{
|
|
kind: "create",
|
|
key: "hivegrid-legacy-cloud",
|
|
input: expect.objectContaining({ enabled: false, defaultAssigneeAdminUserId: null, notifyAdminUserIds: [] }),
|
|
},
|
|
{
|
|
kind: "create",
|
|
key: "lead-inbox-legacy",
|
|
input: expect.objectContaining({ enabled: false, defaultAssigneeAdminUserId: null, notifyAdminUserIds: [] }),
|
|
},
|
|
]);
|
|
});
|
|
|
|
it("preserves extra website recipients while disabling import-only legacy sources", () => {
|
|
const existing = [
|
|
source({
|
|
id: "source_hivegrid",
|
|
key: "hivegrid-website",
|
|
enabled: false,
|
|
defaultAssigneeAdminUserId: "other_operator",
|
|
notifyAdminUserIds: ["other_operator"],
|
|
}),
|
|
source({ id: "source_cloud", key: "hivegrid-legacy-cloud", enabled: true, notifyAdminUserIds: ["other_operator"] }),
|
|
source({ id: "source_inbox", key: "lead-inbox-legacy", enabled: false }),
|
|
];
|
|
|
|
const plan = planPlatformLeadSourceBootstrap(existing, "haris");
|
|
expect(plan.actions).toEqual([
|
|
{
|
|
kind: "update",
|
|
key: "hivegrid-website",
|
|
input: {
|
|
sourceId: "source_hivegrid",
|
|
enabled: true,
|
|
defaultAssigneeAdminUserId: "haris",
|
|
notifyAdminUserIds: ["haris", "other_operator"],
|
|
},
|
|
},
|
|
{ kind: "update", key: "hivegrid-legacy-cloud", input: { sourceId: "source_cloud", enabled: false } },
|
|
{ kind: "unchanged", key: "lead-inbox-legacy" },
|
|
]);
|
|
});
|
|
|
|
it("returns a PII-free dry-run manifest and issues no credentials", async () => {
|
|
const manifest = await runPlatformLeadSourceBootstrap({
|
|
mode: "dry-run",
|
|
runId: "bootstrap-dry-1",
|
|
actorAdminUserId: "operator_admin",
|
|
routingAdminUserId: "haris_private_identifier",
|
|
existingSources: [],
|
|
clock: clock("2026-07-16T10:00:00.000Z", "2026-07-16T10:00:01.000Z"),
|
|
});
|
|
|
|
expect(manifest).toMatchObject({
|
|
outcome: "validated",
|
|
counts: { createPlanned: 3, updatePlanned: 0, unchanged: 0, created: 0, updated: 0, failed: 0 },
|
|
credentialsIssued: 0,
|
|
notificationsTriggered: 0,
|
|
});
|
|
expect(manifest.operatorBindingSha256).toMatch(/^[a-f0-9]{64}$/u);
|
|
expect(JSON.stringify(manifest)).not.toContain("haris_private_identifier");
|
|
expect(isSuccessfulPlatformLeadSourceBootstrapManifest(manifest)).toBe(true);
|
|
});
|
|
|
|
it("blocks apply on plan drift before any source mutation", async () => {
|
|
const createSource = vi.fn();
|
|
const updateSource = vi.fn();
|
|
const manifest = await runPlatformLeadSourceBootstrap({
|
|
mode: "apply",
|
|
runId: "bootstrap-apply-1",
|
|
actorAdminUserId: "operator_admin",
|
|
routingAdminUserId: "haris",
|
|
existingSources: [],
|
|
expectedPlanSha256: "0".repeat(64),
|
|
service: { createSource, updateSource } as PlatformLeadSourceBootstrapService,
|
|
clock: clock("2026-07-16T10:00:00.000Z", "2026-07-16T10:00:01.000Z"),
|
|
});
|
|
|
|
expect(manifest.outcome).toBe("blocked");
|
|
expect(createSource).not.toHaveBeenCalled();
|
|
expect(updateSource).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("applies the confirmed plan through audited source service calls", async () => {
|
|
const plan = planPlatformLeadSourceBootstrap([], "haris");
|
|
const createSource = vi.fn(async (input) => source({ id: `created_${input.key}`, key: input.key, enabled: input.enabled }));
|
|
const updateSource = vi.fn();
|
|
const manifest = await runPlatformLeadSourceBootstrap({
|
|
mode: "apply",
|
|
runId: "bootstrap-apply-2",
|
|
actorAdminUserId: "operator_admin",
|
|
routingAdminUserId: "haris",
|
|
existingSources: [],
|
|
expectedPlanSha256: plan.hash,
|
|
service: { createSource, updateSource } as PlatformLeadSourceBootstrapService,
|
|
clock: clock("2026-07-16T10:00:00.000Z", "2026-07-16T10:00:01.000Z"),
|
|
});
|
|
|
|
expect(createSource).toHaveBeenCalledTimes(3);
|
|
expect(createSource).toHaveBeenCalledWith(expect.objectContaining({ actorAdminUserId: "operator_admin", requestId: "bootstrap-apply-2" }));
|
|
expect(updateSource).not.toHaveBeenCalled();
|
|
expect(manifest).toMatchObject({ outcome: "applied", counts: { created: 3, updated: 0, failed: 0 } });
|
|
});
|
|
});
|