@grouparoo/core
Version:
The Grouparoo Core
327 lines (281 loc) • 10.6 kB
text/typescript
import { helper, ImportWorkflow } from "@grouparoo/spec-helper";
import { api, task, specHelper } from "actionhero";
import {
GrouparooRecord,
Group,
Destination,
Export,
Run,
App,
plugin,
GrouparooModel,
ErrorWithRecordId,
} from "../../../src";
describe("tasks/export:sendBatch", () => {
let model: GrouparooModel;
helper.grouparooTestServer({ truncate: true, enableTestPlugin: true });
beforeAll(async () => {
({ model } = await helper.factories.properties());
});
test("can be enqueued", async () => {
await task.enqueue("export:sendBatch", {
destinationId: "abc123",
exportIds: ["abc123"],
});
const found = await specHelper.findEnqueuedTasks("export:sendBatch");
expect(found.length).toEqual(1);
});
test("does not throw if the destination or export cannot be found", async () => {
await specHelper.runTask("export:sendBatch", {
destinationId: "missing",
exportIds: ["missing"],
});
const destination = await helper.factories.destination();
await specHelper.runTask("export:sendBatch", {
destinationId: destination.id,
exportIds: ["missing"],
});
await destination.destroy();
});
describe("within an export workflow", () => {
let destination: Destination,
group: Group,
record: GrouparooRecord,
run: Run;
beforeAll(async () => {
record = await helper.factories.record();
await record.addOrUpdateProperties({ email: ["mario@example.com"] });
await record.import();
await record.update({ state: "ready" });
group = await helper.factories.group();
await group.setRules([
{ key: "grouparooId", operation: { op: "exists" } },
]);
destination = await helper.factories.destination(null, {
type: "test-plugin-export-batch",
});
await destination.updateTracking("group", group.id);
await api.resque.queue.connection.redis.flushdb();
await Run.truncate();
const destinationGroupMemberships: Record<string, string> = {};
destinationGroupMemberships[group.id] = group.name;
await destination.setDestinationGroupMemberships(
destinationGroupMemberships
);
await destination.update({ state: "ready" });
await destination.exportMembers();
run = await Run.findOne({
where: { creatorId: group.id },
});
await specHelper.runTask("group:run", { runId: run.id });
await run.updateTotals();
expect(run.importsCreated).toBe(1);
expect(run.recordsCreated).toBe(0);
expect(run.recordsImported).toBe(0);
});
test("the record can be imported and exported", async () => {
await ImportWorkflow();
});
test("export:sendBatch will be enqueued after a batch from the run", async () => {
let foundExportSendBatchTasks = await specHelper.findEnqueuedTasks(
"export:sendBatch"
);
expect(foundExportSendBatchTasks.length).toBe(0);
await specHelper.runTask("export:enqueue", {});
foundExportSendBatchTasks = await specHelper.findEnqueuedTasks(
"export:sendBatch"
);
expect(foundExportSendBatchTasks.length).toBe(1);
});
test("without the export being sent, the run cannot be completed", async () => {
await run.afterBatch();
await run.reload();
expect(run.state).toEqual("running");
});
test("the export can be sent", async () => {
const foundExportSendBatchTasks = await specHelper.findEnqueuedTasks(
"export:sendBatch"
);
await specHelper.runTask(
"export:sendBatch",
foundExportSendBatchTasks[0].args[0]
);
const _exports = await Export.findAll({
where: {
destinationId: destination.id,
},
});
expect(_exports.length).toBe(1);
expect(_exports[0].startedAt).toBeTruthy();
expect(_exports[0].completedAt).toBeTruthy();
});
describe("with custom destination", () => {
let app: App;
let destination: Destination;
let exportProfilesResponse = {
success: true,
errors: undefined as ErrorWithRecordId[],
retryDelay: undefined as number,
};
beforeAll(async () => {
plugin.registerPlugin({
name: "test-export-plugin",
apps: [
{
name: "test-template-app",
displayName: "test-template-app",
options: [{ key: "test_key", required: true }],
methods: {
test: async () => {
return { success: true };
},
},
},
],
connections: [
{
name: "export-from-test-app",
displayName: "export-from-test-app",
description: "a test app connection",
apps: ["test-template-app"],
direction: "export",
options: [],
methods: {
destinationOptions: async () => ({}),
destinationMappingOptions: async () => {
return {
labels: {
group: {
singular: "list",
plural: "lists",
},
property: {
singular: "var",
plural: "vars",
},
},
properties: {
required: [],
known: [],
allowOptionalFromProperties: true,
},
};
},
exportArrayProperties: async () => [],
exportRecords: async () => {
return exportProfilesResponse;
},
},
},
],
});
app = await App.create({
name: "test with real methods",
type: "test-template-app",
});
await app.setOptions({ test_key: "abc" });
await app.update({ state: "ready" });
destination = await Destination.create({
name: "test plugin destination",
type: "export-from-test-app",
appId: app.id,
modelId: model.id,
});
await destination.update({ state: "ready" });
await destination.updateTracking("group", group.id);
});
beforeEach(async () => {
await Export.truncate();
await api.resque.queue.connection.redis.flushdb();
});
test("if the export succeeds, the task will not be enqueued and the export will be marked as complete", async () => {
// this export will succeed
exportProfilesResponse = {
success: true,
errors: null,
retryDelay: null,
};
await destination.exportRecord(record);
const _export = await Export.findOne({
where: { destinationId: destination.id },
});
await specHelper.runTask("export:enqueue", {});
let foundSendTasks = await specHelper.findEnqueuedTasks(
"export:sendBatch"
);
expect(foundSendTasks.length).toBe(1);
await specHelper.runTask("export:sendBatch", foundSendTasks[0].args[0]);
// the task should not be re-enqueued
foundSendTasks = await specHelper.findEnqueuedTasks("export:sendBatch");
expect(foundSendTasks.length).toBe(1); // the original one
// the export should be complete
await _export.reload();
expect(_export.startedAt).not.toBeFalsy();
expect(_export.sendAt).not.toBeFalsy();
expect(_export.errorMessage).toBeNull();
expect(_export.errorLevel).toBeNull();
expect(_export.completedAt).not.toBeFalsy();
expect(_export.state).toBe("complete");
expect(_export.retryCount).toBe(0);
});
test("if the export fails with a retryDelay, the export will have the error message appended", async () => {
// this export will fail
const error = new Error("oh no!") as ErrorWithRecordId;
error["recordId"] = record.id;
exportProfilesResponse = {
success: false,
errors: [error],
retryDelay: 1000 * 5,
};
await destination.exportRecord(record);
const _export = await Export.findOne({
where: { destinationId: destination.id },
});
await specHelper.runTask("export:enqueue", {});
let foundSendTasks = await specHelper.findEnqueuedTasks(
"export:sendBatch"
);
expect(foundSendTasks.length).toBe(1);
await specHelper.runTask("export:sendBatch", foundSendTasks[0].args[0]);
// the export should be marked with the error
await _export.reload();
expect(_export.startedAt).toBeFalsy();
expect(_export.sendAt.getTime()).toBeGreaterThan(new Date().getTime());
expect(_export.errorMessage).toMatch(/oh no!/);
expect(_export.errorLevel).toBe("error");
expect(_export.completedAt).toBeFalsy();
expect(_export.state).toBe("pending");
expect(_export.retryCount).toBe(0);
});
test("if the export fails without a retryDelay, the export will have the error message appended", async () => {
// this export will fail
const error = new Error("oh no!") as ErrorWithRecordId;
error["recordId"] = record.id;
exportProfilesResponse = {
success: false,
errors: [error],
retryDelay: undefined,
};
await destination.exportRecord(record);
const _export = await Export.findOne({
where: { destinationId: destination.id },
});
await specHelper.runTask("export:enqueue", {});
let foundSendTasks = await specHelper.findEnqueuedTasks(
"export:sendBatch"
);
expect(foundSendTasks.length).toBe(1);
await specHelper.runTask("export:sendBatch", foundSendTasks[0].args[0]);
// the export should be marked with the error
await _export.reload();
expect(_export.startedAt).toBeFalsy();
expect(_export.sendAt.getTime()).toBeGreaterThan(new Date().getTime());
expect(_export.errorMessage).toMatch(/oh no!/);
expect(_export.errorLevel).toBe("error");
expect(_export.completedAt).toBeFalsy();
expect(_export.state).toBe("pending");
expect(_export.retryCount).toBe(1);
});
});
});
});