@grouparoo/core
Version:
The Grouparoo Core
457 lines (421 loc) • 13.6 kB
text/typescript
import { helper } from "@grouparoo/spec-helper";
import { specHelper, Connection } from "actionhero";
import { Option, Source, App, GrouparooModel, Property } from "../../../src";
import { SessionCreate } from "../../../src/actions/session";
import {
SourceBootstrapUniqueProperty,
SourceConnectionApps,
SourceConnectionOptions,
SourceCreate,
SourceDefaultPropertyOptions,
SourceDestroy,
SourceEdit,
SourcePreview,
SourcesList,
SourceView,
} from "../../../src/actions/sources";
import { PropertyDestroy } from "../../../src/actions/properties";
import { ConfigWriter } from "../../../src/modules/configWriter";
describe("actions/sources", () => {
helper.grouparooTestServer({ truncate: true, enableTestPlugin: true });
const configSpy = jest.spyOn(ConfigWriter, "run");
let model: GrouparooModel;
let app: App;
let id: string;
let propertyId: string;
beforeAll(async () => {
await specHelper.runAction("team:initialize", {
firstName: "Mario",
lastName: "Mario",
password: "P@ssw0rd!",
email: "mario@example.com",
});
model = await helper.factories.model();
app = await helper.factories.app();
await app.update({ name: "test app" });
});
afterEach(() => {
configSpy.mockClear();
});
describe("administrator signed in", () => {
let connection: Connection;
let csrfToken: string;
beforeAll(async () => {
connection = await specHelper.buildConnection();
connection.params = { email: "mario@example.com", password: "P@ssw0rd!" };
const sessionResponse = await specHelper.runAction<SessionCreate>(
"session:create",
connection
);
csrfToken = sessionResponse.csrfToken;
});
test("an administrator can create a new source from an app", async () => {
connection.params = {
csrfToken,
name: "test source",
type: "test-plugin-import",
appId: app.id,
modelId: model.id,
options: { table: "users" },
};
const { error, source } = await specHelper.runAction<SourceCreate>(
"source:create",
connection
);
expect(error).toBeUndefined();
expect(source.id).toBeTruthy();
expect(source.app.id).toBe(app.id);
expect(source.app.name).toBe("test app");
expect(source.state).toBe("draft");
expect(configSpy).toBeCalledTimes(1);
id = source.id;
});
test("an administrator can list all the sources", async () => {
connection.params = {
csrfToken,
};
const { error, sources, total } = await specHelper.runAction<SourcesList>(
"sources:list",
connection
);
expect(error).toBeUndefined();
expect(sources.length).toBe(1);
expect(sources[0].app.name).toBe("test app");
expect(total).toBe(1);
});
test("a source can be bootstrapped with a property", async () => {
connection.params = {
csrfToken,
id,
key: "userId",
type: "integer",
mappedColumn: "id",
};
const { property, error } =
await specHelper.runAction<SourceBootstrapUniqueProperty>(
"source:bootstrapUniqueProperty",
connection
);
expect(error).toBeUndefined();
expect(property.id).toBeTruthy();
propertyId = property.id;
});
test("an administrator can list the connection methods and app pairs available for a new connection", async () => {
connection.params = {
csrfToken,
id,
};
const { error, connectionApps } =
await specHelper.runAction<SourceConnectionApps>(
"sources:connectionApps",
connection
);
expect(error).toBeUndefined();
expect(connectionApps[0].app.id).toBe(app.id);
expect(connectionApps[0].connection.apps).toEqual(["test-plugin-app"]);
expect(connectionApps[0].connection.methods).toEqual([
"sourceOptions",
"sourcePreview",
"propertyOptions",
"scheduleOptions",
"uniquePropertyBootstrapOptions",
"sourceFilters",
"importRecords",
"recordProperty",
"recordProperties",
]);
});
describe("options from environment variables", () => {
beforeAll(() => {
process.env.GROUPAROO_OPTION__SOURCE__TEST_OPTION = "abc123";
});
test("options for a new source will include the names of options included in environment variables", async () => {
connection.params = { csrfToken };
const { environmentVariableOptions } =
await specHelper.runAction<SourceConnectionApps>(
"sources:connectionApps",
connection
);
expect(environmentVariableOptions).toEqual(["TEST_OPTION"]);
});
afterAll(() => {
process.env.GROUPAROO_OPTION__APP__TEST_OPTION = undefined;
});
});
test("an administrator can enumerate the source connection options", async () => {
connection.params = {
csrfToken,
id,
};
const { error, options } =
await specHelper.runAction<SourceConnectionOptions>(
"source:connectionOptions",
connection
);
expect(error).toBeUndefined();
expect(options).toEqual({
tableWithOptions: {
options: ["users", "admins", "purchases", "products"],
type: "list",
},
});
});
test("a source with no options will see an empty preview", async () => {
const options = await Option.findAll({ where: { ownerId: id } });
for (const option of options) await option.destroy();
connection.params = {
csrfToken,
id,
};
const { error, preview } = await specHelper.runAction<SourcePreview>(
"source:preview",
connection
);
expect(error).toBeUndefined();
expect(preview).toEqual([]);
});
test("a source can provide options to a preview and column speculation", async () => {
connection.params = {
csrfToken,
id,
options: { table: "admins", tableWithOptions: "admins" },
};
const { error, preview, columnSpeculation } =
await specHelper.runAction<SourcePreview>("source:preview", connection);
expect(error).toBeUndefined();
expect(preview).toEqual([
{ id: 1, fname: "mario", lname: "mario" },
{ id: 2, fname: "luigi", lname: "mario" },
{ id: 3, fname: "peach", lname: "toadstool" },
]);
expect(columnSpeculation).toEqual({
id: { isUnique: true, type: "integer", suggestedPropertyKey: "id" },
fname: {
isUnique: false,
type: "string",
suggestedPropertyKey: "fname",
},
lname: {
isUnique: false,
type: "string",
suggestedPropertyKey: "lname",
},
});
});
test("a source can have options set", async () => {
connection.params = {
csrfToken,
id,
options: { table: "admins", tableWithOptions: "admins" },
};
const { error, source } = await specHelper.runAction<SourceEdit>(
"source:edit",
connection
);
expect(error).toBeUndefined();
expect(source.options).toEqual({
table: "admins",
tableWithOptions: "admins",
});
expect(configSpy).toBeCalledTimes(1);
});
test("a source can provide default property options", async () => {
connection.params = {
csrfToken,
id,
};
const { error, defaultPropertyOptions } =
await specHelper.runAction<SourceDefaultPropertyOptions>(
"source:defaultPropertyOptions",
connection
);
expect(error).toBeUndefined();
expect(defaultPropertyOptions).toEqual([
{
description: "the column to choose",
displayName: undefined,
key: "column",
options: [
{ key: "id", examples: [1, 2, 3] },
{ key: "fname", examples: ["mario", "luigi", "peach"] },
{
key: "lname",
examples: ["mario", "mario", "toadstool"],
},
],
required: true,
type: "list",
},
{
description: "how things are combined",
displayName: undefined,
key: "aggregationMethod",
options: [
{ key: "exact", default: true },
{ key: "count" },
{ key: "min" },
{ key: "max" },
],
required: false,
type: "list",
},
{
description: "some text you want to set just because",
displayName: undefined,
key: "arbitraryText",
options: [],
primary: undefined,
required: false,
type: "text",
},
]);
});
test("a source with options set will return a preview", async () => {
connection.params = {
csrfToken,
id,
};
const { error, preview } = await specHelper.runAction<SourcePreview>(
"source:preview",
connection
);
expect(error).toBeUndefined();
expect(preview).toEqual([
{ id: 1, fname: "mario", lname: "mario" },
{ id: 2, fname: "luigi", lname: "mario" },
{ id: 3, fname: "peach", lname: "toadstool" },
]);
});
test("a source can have mapping set", async () => {
connection.params = {
csrfToken,
id,
mapping: { id: "userId" },
};
const { error, source } = await specHelper.runAction<SourceEdit>(
"source:edit",
connection
);
expect(error).toBeUndefined();
expect(source.mapping).toEqual({ id: "userId" });
expect(configSpy).toBeCalledTimes(1);
});
test("a source can be made ready", async () => {
connection.params = {
csrfToken,
id,
state: "ready",
};
const { error, source } = await specHelper.runAction<SourceEdit>(
"source:edit",
connection
);
expect(error).toBeUndefined();
expect(source.state).toBe("ready");
});
describe("a source with column(s) that conflict with an existing property", () => {
let source: Source;
let otherSource: Source;
let otherProperty: Property;
beforeAll(async () => {
source = await Source.findOne();
otherSource = await helper.factories.source();
await otherSource.setOptions({ table: "users" });
await otherSource.setMapping({ id: "userId" });
await otherSource.update({ state: "ready" });
otherProperty = await helper.factories.property(
otherSource,
{
key: "Fname",
},
{ column: "Fname" }
);
});
afterAll(async () => {
await otherProperty.destroy();
await otherSource.destroy();
});
test("can provide suggested property keys", async () => {
connection.params = {
csrfToken,
id,
options: { table: "admins", tableWithOptions: "admins" },
};
const { error, preview, columnSpeculation } =
await specHelper.runAction<SourcePreview>(
"source:preview",
connection
);
expect(error).toBeUndefined();
expect(preview).toEqual([
{ id: 1, fname: "mario", lname: "mario" },
{ id: 2, fname: "luigi", lname: "mario" },
{ id: 3, fname: "peach", lname: "toadstool" },
]);
expect(columnSpeculation).toEqual({
id: { isUnique: true, type: "integer", suggestedPropertyKey: "id" },
// Check for rename in the case of a conflict
fname: {
isUnique: false,
type: "string",
suggestedPropertyKey: "profiles_fname",
},
lname: {
isUnique: false,
type: "string",
suggestedPropertyKey: "lname",
},
});
});
});
test("an administrator can view a source", async () => {
connection.params = {
csrfToken,
id,
};
const { error, source } = await specHelper.runAction<SourceView>(
"source:view",
connection
);
expect(error).toBeUndefined();
expect(source.id).toBeTruthy();
expect(source.app.name).toBe("test app");
});
test("an administrator can destroy a source", async () => {
connection.params = {
csrfToken,
id,
mapping: {},
};
const editResponse = await specHelper.runAction<SourceEdit>(
"source:edit",
connection
);
expect(editResponse.error).toBeUndefined();
expect(configSpy).toBeCalledTimes(1);
connection.params = {
csrfToken,
id: propertyId,
};
const deleteRuleResponse = await specHelper.runAction<PropertyDestroy>(
"property:destroy",
connection
);
expect(deleteRuleResponse.error).toBeUndefined();
expect(configSpy).toBeCalledTimes(2);
connection.params = {
csrfToken,
id,
};
const destroyResponse = await specHelper.runAction<SourceDestroy>(
"source:destroy",
connection
);
expect(destroyResponse.error).toBeUndefined();
expect(destroyResponse.success).toBe(true);
const count = await Source.count();
expect(count).toBe(0);
expect(configSpy).toBeCalledTimes(3);
});
});
});