183 lines
5.6 KiB
TypeScript
183 lines
5.6 KiB
TypeScript
// @vitest-environment node
|
|
import { describe, expect, it, vi } from "vitest";
|
|
|
|
import {
|
|
confirmAdminSalesContactsImport,
|
|
prepareAdminSalesContactsImport,
|
|
requestAdminSalesContactsExport,
|
|
type AdminSalesDataOperatorContext,
|
|
} from "./admin-sales-data.server";
|
|
|
|
function context() {
|
|
return {
|
|
adminUserId: "admin_1",
|
|
email: "sales@hivegrid.de",
|
|
canWrite: true,
|
|
canExport: true,
|
|
actor: (reason: string) => ({
|
|
adminUserId: "admin_1",
|
|
email: "sales@hivegrid.de",
|
|
reason,
|
|
requestId: "request_1",
|
|
ip: null,
|
|
userAgent: null,
|
|
}),
|
|
service: {
|
|
createImportJob: vi.fn(async (input) => ({
|
|
id: "import_1",
|
|
fileName: input.fileName,
|
|
sourceBucket: input.sourceBucket,
|
|
sourceKey: input.sourceKey,
|
|
sourceHeaders: input.sourceHeaders,
|
|
fieldMapping: input.fieldMapping,
|
|
requestedByAdminUserId: "admin_1",
|
|
})),
|
|
getImportJobForQueue: vi.fn(async () => ({
|
|
id: "import_1",
|
|
requestedByAdminUserId: "admin_1",
|
|
})),
|
|
markImportJobQueued: vi.fn(async () => ({ id: "import_1" })),
|
|
createExportJob: vi.fn(async () => ({
|
|
id: "export_1",
|
|
requestedByAdminUserId: "admin_1",
|
|
})),
|
|
markExportJobQueued: vi.fn(async () => ({ id: "export_1" })),
|
|
},
|
|
};
|
|
}
|
|
|
|
describe("admin sales data web service", () => {
|
|
it("scans and stores CSV bytes before creating a pending preview job", async () => {
|
|
const operator = context();
|
|
const store = vi.fn(async () => "stored");
|
|
const remove = vi.fn(async () => undefined);
|
|
const result = await prepareAdminSalesContactsImport(
|
|
{
|
|
file: new File(
|
|
["Vorname,Nachname,E-Mail\nAda,Lovelace,ada@example.com\n"],
|
|
"kontakte.csv",
|
|
{ type: "text/csv" }
|
|
),
|
|
reason: "Kontaktimport prüfen",
|
|
context: operator as unknown as AdminSalesDataOperatorContext,
|
|
},
|
|
{
|
|
bucket: () => "hivegrid-files",
|
|
store,
|
|
remove,
|
|
id: () => "upload_1",
|
|
}
|
|
);
|
|
|
|
expect(store).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
bucket: "hivegrid-files",
|
|
id: "admin-sales/imports/admin_1/upload_1.csv",
|
|
throwOnFailure: true,
|
|
scan: expect.objectContaining({
|
|
context: "formUpload",
|
|
requireMalwareScan: true,
|
|
filename: "kontakte.csv",
|
|
}),
|
|
})
|
|
);
|
|
expect(operator.service.createImportJob).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
sourceHeaders: ["Vorname", "Nachname", "E-Mail"],
|
|
fieldMapping: [
|
|
"Vorname=firstName",
|
|
"Nachname=lastName",
|
|
"E-Mail=email",
|
|
],
|
|
})
|
|
);
|
|
expect(result.preview.rowCount).toBe(1);
|
|
expect(remove).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("removes the scanned object if pending-job persistence fails", async () => {
|
|
const operator = context();
|
|
operator.service.createImportJob.mockRejectedValueOnce(
|
|
new Error("database unavailable")
|
|
);
|
|
const remove = vi.fn(async () => undefined);
|
|
await expect(
|
|
prepareAdminSalesContactsImport(
|
|
{
|
|
file: new File(
|
|
["Vorname,Nachname\nAda,Lovelace\n"],
|
|
"kontakte.csv"
|
|
),
|
|
reason: "Kontaktimport prüfen",
|
|
context: operator as unknown as AdminSalesDataOperatorContext,
|
|
},
|
|
{
|
|
bucket: () => "hivegrid-files",
|
|
store: vi.fn(async () => "stored"),
|
|
remove,
|
|
id: () => "upload_1",
|
|
}
|
|
)
|
|
).rejects.toThrow("database unavailable");
|
|
expect(remove).toHaveBeenCalledWith({
|
|
bucket: "hivegrid-files",
|
|
key: "admin-sales/imports/admin_1/upload_1.csv",
|
|
});
|
|
});
|
|
|
|
it("queues only an owned reviewed import and marks it queued afterward", async () => {
|
|
const operator = context();
|
|
const add = vi.fn(async () => ({ id: "queue_1" }));
|
|
await confirmAdminSalesContactsImport({
|
|
importJobId: "import_1",
|
|
reason: "Geprüften Import starten",
|
|
context: operator as unknown as AdminSalesDataOperatorContext,
|
|
createQueue: async () => ({ add } as never),
|
|
});
|
|
expect(operator.service.getImportJobForQueue).toHaveBeenCalledWith(
|
|
expect.objectContaining({ jobId: "import_1" })
|
|
);
|
|
expect(add).toHaveBeenCalledWith(
|
|
"run-import",
|
|
expect.objectContaining({
|
|
kind: "admin-sales.contacts-import",
|
|
importJobId: "import_1",
|
|
}),
|
|
expect.objectContaining({ priority: 1 })
|
|
);
|
|
expect(
|
|
operator.service.markImportJobQueued.mock.invocationCallOrder[0]
|
|
).toBeGreaterThan(add.mock.invocationCallOrder[0]!);
|
|
});
|
|
|
|
it("uses the same worker queue for bounded contact exports", async () => {
|
|
const operator = context();
|
|
const add = vi.fn(async () => ({ id: "queue_1" }));
|
|
await requestAdminSalesContactsExport({
|
|
contactIds: ["contact_1"],
|
|
reason: "Kontakte exportieren",
|
|
requestId: "export_request_1",
|
|
context: operator as unknown as AdminSalesDataOperatorContext,
|
|
createQueue: async () => ({ add } as never),
|
|
});
|
|
expect(operator.service.createExportJob).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
selectedContactIds: ["contact_1"],
|
|
idempotencyKey:
|
|
"admin-sales-export:admin_1:export_request_1",
|
|
})
|
|
);
|
|
expect(add).toHaveBeenCalledWith(
|
|
"run-import",
|
|
expect.objectContaining({
|
|
kind: "admin-sales.contacts-export",
|
|
exportJobId: "export_1",
|
|
}),
|
|
expect.any(Object)
|
|
);
|
|
expect(
|
|
operator.service.markExportJobQueued.mock.invocationCallOrder[0]
|
|
).toBeGreaterThan(add.mock.invocationCallOrder[0]!);
|
|
});
|
|
});
|