Skip to content

Commit 5dfc17c

Browse files
:sparkles feat(migrations): scaffold compass migrations (#1034)
* :sparkles feat(migrations): scaffold compass migrations * :sparkles feat(migrations): scaffold rudimentary scripts tests
1 parent f1b0e19 commit 5dfc17c

File tree

10 files changed

+696
-56
lines changed

10 files changed

+696
-56
lines changed

jest.config.js

Lines changed: 53 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,42 @@
11
/*
22
* For a detailed explanation regarding each configuration property, visit:
33
* https://jestjs.io/docs/configuration
4+
*
45
*/
6+
7+
/** @type { Exclude<Exclude<import("jest").Config["projects"], undefined>[number], string>} */
8+
const backendProject = {
9+
displayName: "backend",
10+
moduleNameMapper: {
11+
"^@core(/(.*)$)?": "<rootDir>/packages/core/src/$1",
12+
"^@backend/auth(/(.*)$)?": "<rootDir>/packages/backend/src/auth/$1",
13+
"^@backend/calendar(/(.*)$)?": "<rootDir>/packages/backend/src/calendar/$1",
14+
"^@backend/common(/(.*)$)?": "<rootDir>/packages/backend/src/common/$1",
15+
"^@backend/dev(/(.*)$)?": "<rootDir>/packages/backend/src/dev/$1",
16+
"^@backend/email(/(.*)$)?": "<rootDir>/packages/backend/src/email/$1",
17+
"^@backend/event(/(.*)$)?": "<rootDir>/packages/backend/src/event/$1",
18+
"^@backend/priority(/(.*)$)?": "<rootDir>/packages/backend/src/priority/$1",
19+
"^@backend/servers(/(.*)$)?": "<rootDir>/packages/backend/src/servers/$1",
20+
"^@backend/sync(/(.*)$)?": "<rootDir>/packages/backend/src/sync/$1",
21+
"^@backend/user(/(.*)$)?": "<rootDir>/packages/backend/src/user/$1",
22+
"^@backend/waitlist(/(.*)$)?": "<rootDir>/packages/backend/src/waitlist/$1",
23+
"^@backend/__tests__(/(.*)$)?":
24+
"<rootDir>/packages/backend/src/__tests__/$1",
25+
},
26+
27+
setupFiles: ["<rootDir>/packages/core/src/__tests__/core.test.init.ts"],
28+
setupFilesAfterEnv: [
29+
// backend init intentionally here to accommodate @shelf/mongodb preset
30+
"<rootDir>/packages/backend/src/__tests__/backend.test.init.ts",
31+
"<rootDir>/packages/backend/src/__tests__/backend.test.start.ts",
32+
],
33+
testMatch: ["<rootDir>/packages/backend/**/?(*.)+(spec|test).[tj]s?(x)"],
34+
// A preset that is used as a base for Jest's configuration
35+
preset: "@shelf/jest-mongodb", // https://jestjs.io/docs/mongodb,
36+
};
37+
538
/** @type { import("jest").Config } */
6-
module.exports = {
39+
const config = {
740
// All imported modules in your tests should be mocked automatically
841
// automock: false,
942

@@ -85,7 +118,7 @@ module.exports = {
85118
// moduleNameMapper: {}
86119

87120
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
88-
// modulePathIgnorePatterns: [],
121+
modulePathIgnorePatterns: ["<rootDir>/build"],
89122

90123
// Activates notifications for test results
91124
// notify: false,
@@ -144,36 +177,24 @@ module.exports = {
144177
"/node_modules/(?!react-dnd|dnd-core|@react-dnd)",
145178
],
146179
},
180+
backendProject,
147181
{
148-
displayName: "backend",
182+
displayName: "scripts",
149183
moduleNameMapper: {
150-
"^@core(/(.*)$)?": "<rootDir>/packages/core/src/$1",
151-
"^@backend/auth(/(.*)$)?": "<rootDir>/packages/backend/src/auth/$1",
152-
"^@backend/calendar(/(.*)$)?":
153-
"<rootDir>/packages/backend/src/calendar/$1",
154-
"^@backend/common(/(.*)$)?": "<rootDir>/packages/backend/src/common/$1",
155-
"^@backend/dev(/(.*)$)?": "<rootDir>/packages/backend/src/dev/$1",
156-
"^@backend/email(/(.*)$)?": "<rootDir>/packages/backend/src/email/$1",
157-
"^@backend/event(/(.*)$)?": "<rootDir>/packages/backend/src/event/$1",
158-
"^@backend/priority(/(.*)$)?":
159-
"<rootDir>/packages/backend/src/priority/$1",
160-
"^@backend/servers(/(.*)$)?":
161-
"<rootDir>/packages/backend/src/servers/$1",
162-
"^@backend/sync(/(.*)$)?": "<rootDir>/packages/backend/src/sync/$1",
163-
"^@backend/user(/(.*)$)?": "<rootDir>/packages/backend/src/user/$1",
164-
"^@backend/waitlist(/(.*)$)?":
165-
"<rootDir>/packages/backend/src/waitlist/$1",
166-
"^@backend/__tests__(/(.*)$)?":
167-
"<rootDir>/packages/backend/src/__tests__/$1",
184+
...backendProject.moduleNameMapper,
185+
"^@scripts(/(.*)$)?": "<rootDir>/packages/scripts/src/$1",
186+
"^@scripts/commands(/(.*)$)?":
187+
"<rootDir>/packages/scripts/src/commands/$1",
188+
"^@scripts/common(/(.*)$)?": "<rootDir>/packages/scripts/src/common/$1",
189+
"^@scripts/migrations(/(.*)$)?":
190+
"<rootDir>/packages/scripts/src/migrations/$1",
191+
"^@scripts/seeders(/(.*)$)?":
192+
"<rootDir>/packages/scripts/src/seeders/$1",
168193
},
169194

170-
setupFiles: ["<rootDir>/packages/core/src/__tests__/core.test.init.ts"],
171-
setupFilesAfterEnv: [
172-
// backend init intentionally here to accommodate @shelf/mongodb preset
173-
"<rootDir>/packages/backend/src/__tests__/backend.test.init.ts",
174-
"<rootDir>/packages/backend/src/__tests__/backend.test.start.ts",
175-
],
176-
testMatch: ["<rootDir>/packages/backend/**/?(*.)+(spec|test).[tj]s?(x)"],
195+
setupFiles: [...backendProject.setupFiles],
196+
setupFilesAfterEnv: [...backendProject.setupFilesAfterEnv],
197+
testMatch: ["<rootDir>/packages/scripts/**/?(*.)+(spec|test).[tj]s?(x)"],
177198
// A preset that is used as a base for Jest's configuration
178199
preset: "@shelf/jest-mongodb", // https://jestjs.io/docs/mongodb,
179200
},
@@ -197,6 +218,7 @@ module.exports = {
197218
// rootDir: 'packages/web/',
198219
// rootDir: 'packages/backend/',
199220
rootDir: "./",
221+
passWithNoTests: true,
200222

201223
// A list of paths to directories that Jest should use to search for files in
202224
// roots: [
@@ -272,3 +294,6 @@ module.exports = {
272294
// Whether to use watchman for file crawling
273295
// watchman: true,
274296
};
297+
298+
// eslint-disable-next-line no-undef
299+
module.exports = config;

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@
3030
"test": "cross-env-shell TZ=Etc/UTC yarn jest",
3131
"test:backend": "yarn test backend",
3232
"test:core": "yarn test core",
33-
"test:web": "yarn test web"
33+
"test:web": "yarn test web",
34+
"test:scripts": "yarn test scripts"
3435
},
3536
"dependencies": {
3637
"@compass/backend": "*",

packages/scripts/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
"dotenv": "^16.0.1",
1212
"inquirer": "^8.0.0",
1313
"lodash.uniqby": "^4.7.0",
14-
"shelljs": "^0.8.5"
14+
"shelljs": "^0.8.5",
15+
"umzug": "^3.8.2"
1516
},
1617
"devDependencies": {
1718
"@types/inquirer": "^9.0.1",

packages/scripts/src/cli.test.ts

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import CompassCLI from "@scripts/cli";
2+
import { runBuild } from "@scripts/commands/build";
3+
import { startDeleteFlow } from "@scripts/commands/delete";
4+
import { inviteWaitlist } from "@scripts/commands/invite";
5+
import { NodeEnv } from "../../core/src/constants/core.constants";
6+
import { MigratorType } from "./common/cli.types";
7+
8+
const mockGetCliOptions = jest.fn();
9+
const mockValidateBuild = jest.fn();
10+
const mockValidateDelete = jest.fn();
11+
const mockExitHelpfully = jest.fn();
12+
const mockRunMigrator = jest.fn();
13+
14+
jest.mock("@scripts/cli.validator", () => {
15+
return {
16+
CliValidator: jest.fn().mockImplementation(() => ({
17+
getCliOptions: mockGetCliOptions,
18+
validateBuild: mockValidateBuild,
19+
validateDelete: mockValidateDelete,
20+
exitHelpfully: mockExitHelpfully,
21+
})),
22+
};
23+
});
24+
25+
jest.mock("@scripts/commands/build.util");
26+
jest.mock("@scripts/commands/build");
27+
jest.mock("@scripts/commands/delete");
28+
jest.mock("@scripts/commands/invite");
29+
30+
jest.mock("@scripts/commands/migrate", () => ({
31+
runMigrator: jest
32+
.fn()
33+
.mockImplementation((...args) => mockRunMigrator(...args)),
34+
}));
35+
36+
describe("CompassCLI", () => {
37+
beforeEach(() => jest.clearAllMocks());
38+
39+
it("runs build command and calls validateBuild and runBuild", async () => {
40+
mockGetCliOptions.mockReturnValue({ force: false, user: undefined });
41+
42+
const cli = new CompassCLI([
43+
"node",
44+
"cli",
45+
"build",
46+
"nodePckgs",
47+
"-e",
48+
NodeEnv.Staging,
49+
]);
50+
51+
await cli.run();
52+
53+
expect(mockValidateBuild).toHaveBeenCalled();
54+
expect(runBuild).toHaveBeenCalled();
55+
});
56+
57+
it("runs delete command and calls validateDelete and startDeleteFlow", async () => {
58+
mockGetCliOptions.mockReturnValue({
59+
force: true,
60+
user: "user@example.com",
61+
});
62+
63+
const cli = new CompassCLI(["node", "cli", "delete"]);
64+
65+
await cli.run();
66+
67+
expect(mockValidateDelete).toHaveBeenCalled();
68+
expect(startDeleteFlow).toHaveBeenCalledWith("user@example.com", true);
69+
});
70+
71+
it("runs invite command and calls inviteWaitlist", async () => {
72+
mockGetCliOptions.mockReturnValue({});
73+
74+
const cli = new CompassCLI(["node", "cli", "invite"]);
75+
76+
await cli.run();
77+
78+
expect(inviteWaitlist).toHaveBeenCalled();
79+
});
80+
81+
it("runs migrate command and does not throw", async () => {
82+
mockGetCliOptions.mockReturnValue({});
83+
84+
const cli = new CompassCLI(["node", "cli", "migrate", "--help"]);
85+
86+
await cli.run();
87+
88+
expect(mockRunMigrator).toHaveBeenCalledWith(MigratorType.MIGRATION);
89+
});
90+
91+
it("runs seed command and does not throw", async () => {
92+
mockGetCliOptions.mockReturnValue({});
93+
94+
const cli = new CompassCLI(["node", "cli", "seed"]);
95+
96+
await cli.run();
97+
98+
expect(mockRunMigrator).toHaveBeenCalledWith(MigratorType.SEEDER);
99+
});
100+
101+
it("calls exitHelpfully for unsupported command", async () => {
102+
mockGetCliOptions.mockReturnValue({});
103+
104+
const exitSpy = jest
105+
.spyOn(process, "exit")
106+
.mockImplementation(jest.fn() as never);
107+
108+
const cli = new CompassCLI(["node", "cli", "unknown"]);
109+
110+
await cli.run();
111+
112+
expect(mockExitHelpfully).toHaveBeenCalledWith(
113+
"root",
114+
"unknown is not a supported cmd",
115+
);
116+
117+
expect(exitSpy).toHaveBeenCalledWith(1);
118+
});
119+
});

packages/scripts/src/cli.ts

Lines changed: 37 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
// sort-imports-ignore
2-
import { Command } from "commander";
3-
import "./init";
2+
import "@scripts/init";
43

5-
import { CliValidator } from "./cli.validator";
6-
import { runBuild } from "./commands/build";
7-
import { startDeleteFlow } from "./commands/delete";
8-
import { inviteWaitlist } from "./commands/invite";
9-
import { runSeed } from "./commands/seed";
10-
import { ALL_PACKAGES, CATEGORY_VM } from "./common/cli.constants";
4+
import { CliValidator } from "@scripts/cli.validator";
5+
import { runBuild } from "@scripts/commands/build";
6+
import { startDeleteFlow } from "@scripts/commands/delete";
7+
import { inviteWaitlist } from "@scripts/commands/invite";
8+
import { runMigrator } from "@scripts/commands/migrate";
9+
import { ALL_PACKAGES, CATEGORY_VM } from "@scripts/common/cli.constants";
10+
import { MigratorType } from "@scripts/common/cli.types";
11+
import { Command } from "commander";
1112

12-
class CompassCli {
13+
export default class CompassCLI {
1314
private program: Command;
1415
private validator: CliValidator;
1516

@@ -21,7 +22,6 @@ class CompassCli {
2122

2223
public async run() {
2324
const options = this.validator.getCliOptions();
24-
const { force, user } = options;
2525
const cmd = this.program.args[0];
2626

2727
switch (true) {
@@ -31,6 +31,7 @@ class CompassCli {
3131
break;
3232
}
3333
case cmd === "delete": {
34+
const { force, user } = options;
3435
this.validator.validateDelete(options);
3536
await startDeleteFlow(user as string, force);
3637
break;
@@ -39,9 +40,11 @@ class CompassCli {
3940
await inviteWaitlist();
4041
break;
4142
}
43+
case cmd === "migrate":
44+
await runMigrator(MigratorType.MIGRATION);
45+
break;
4246
case cmd === "seed": {
43-
this.validator.validateSeed(options);
44-
await runSeed(user as string, force);
47+
await runMigrator(MigratorType.SEEDER);
4548
break;
4649
}
4750
default:
@@ -84,15 +87,30 @@ class CompassCli {
8487
program.command("invite").description("invite users from the waitlist");
8588

8689
program
90+
.enablePositionalOptions(true)
91+
.passThroughOptions(true)
92+
.command("migrate")
93+
.helpOption(false)
94+
.allowUnknownOption(true)
95+
.description("run database schema migrations");
96+
97+
program
98+
.enablePositionalOptions(true)
99+
.passThroughOptions(true)
87100
.command("seed")
88-
.description("seed the database with events")
89-
.option("-u, --user <id>", "specify which user to seed events for");
101+
.helpOption(false)
102+
.allowUnknownOption(true)
103+
.description("run seed migrations to populate the database with data");
104+
90105
return program;
91106
}
92107
}
93108

94-
const cli = new CompassCli(process.argv);
95-
cli.run().catch((err) => {
96-
console.log(err);
97-
process.exit(1);
98-
});
109+
if (require.main === module) {
110+
const cli = new CompassCLI(process.argv);
111+
112+
cli.run().catch((err) => {
113+
console.log(err);
114+
process.exit(1);
115+
});
116+
}

0 commit comments

Comments
 (0)