test: add tests for migrations

This commit is contained in:
zkldi
2022-04-22 02:12:32 +01:00
parent b70f02de01
commit 715906eefb
5 changed files with 166 additions and 15 deletions
+125
View File
@@ -0,0 +1,125 @@
import db from "external/mongo/db";
import t from "tap";
import ResetDBState from "test-utils/resets";
import { FAKE_MIGRATION } from "test-utils/test-data";
import { ApplyMigration, FindUnappliedMigrations } from "./migrations";
t.test("#FindUnappliedMigrations", (t) => {
t.beforeEach(ResetDBState);
t.test("Should find all unapplied migrations", async (t) => {
const res = await FindUnappliedMigrations();
t.equal(
res.includes("fake-migration"),
true,
"Should contain fake-migration since it isn't applied."
);
t.end();
});
t.test("Shouldn't return pending migrations", async (t) => {
await db.migrations.insert({
migrationID: "fake-migration",
status: "pending",
});
const res = await FindUnappliedMigrations();
t.equal(res.includes("fake-migration"), false);
t.end();
});
t.test("Shouldn't return applied migrations", async (t) => {
await db.migrations.insert({
migrationID: "fake-migration",
status: "applied",
appliedOn: 1000,
});
const res = await FindUnappliedMigrations();
t.equal(res.includes("fake-migration"), false);
t.end();
});
t.end();
});
t.test("#ApplyMigration", (t) => {
t.beforeEach(ResetDBState);
t.test("Should apply a migration.", async (t) => {
await ApplyMigration(FAKE_MIGRATION);
const dbRes = await db.users.findOne({
id: 1,
});
t.equal(
// @ts-expect-error Our migration is a fake one that adds __wasMigrated
// to all user documents. There's nothing special about this property name.
dbRes?.__wasMigrated,
true,
"Migration should be applied on the users."
);
t.end();
});
t.test("Should panic and reset if something goes wrong.", async (t) => {
try {
await ApplyMigration({
id: "bad-migration",
up: () => {
throw new Error("Failed to migrate.");
},
down: () =>
db.users.update({}, { $set: { __wasUnmigrated: true } }, { multi: true }),
});
} catch (e) {
const err = e as Error;
t.equal(err?.message, "Was going to exit with statusCode 1, but we're in testing.");
}
const dbRes = await db.users.findOne({
id: 1,
});
t.equal(
// @ts-expect-error Our un-migration adds this property to check if it worked.
dbRes?.__wasUnmigrated,
true,
"Migration should be applied on the users."
);
t.end();
});
t.test(
"Should panic and panic again if un-migration failed, with a scarier log message.",
async (t) => {
try {
await ApplyMigration({
id: "bad-migration",
up: () => {
throw new Error("Failed to migrate.");
},
down: () => {
throw new Error("Failed to undo migration.");
},
});
} catch (e) {
const err = e as Error;
t.equal(err?.message, "Was going to exit with statusCode 1, but we're in testing.");
}
t.end();
}
);
t.end();
});
+31 -14
View File
@@ -1,5 +1,7 @@
import db from "external/mongo/db";
import CreateLogCtx from "lib/logger/logger";
import { Environment } from "lib/setup/config";
import { FAKE_MIGRATION } from "test-utils/test-data";
import { Migration } from "utils/types";
import UGPTRivalsMigration from "./migrations/add-rivals-to-ugpt";
@@ -8,7 +10,10 @@ const logger = CreateLogCtx(__filename);
// Migrations are stored in an array because they have some concept of order
// That is, migrations should ideally be applied in a fixed order just to avoid
// any potential unsavoury interactions.
const REGISTERED_MIGRATIONS: Migration[] = [UGPTRivalsMigration];
const REGISTERED_MIGRATIONS: Migration[] =
// If we're testing, we should pull fake migrations instead to ensure the tests
// stay consistent
Environment.nodeEnv === "test" ? [FAKE_MIGRATION] : [UGPTRivalsMigration];
function CreateMigrationLookupMap(migrations: Migration[]) {
const map = new Map<string, Migration>();
@@ -18,6 +23,8 @@ function CreateMigrationLookupMap(migrations: Migration[]) {
logger.crit(
`Multiple migrations are registered for ${mig.id}. Cannot safely apply migrations.`
);
// note, we want to exit in testing here, this is fine.
process.exit(1);
}
@@ -77,15 +84,32 @@ export async function ApplyUnappliedMigrations() {
// Note that if any migration fails, this will exit at CRIT level
// and not continue.
// eslint-disable-next-line no-await-in-loop
await ApplyMigration(migrationID);
await ApplyMigrationByID(migrationID);
}
}
export function ApplyMigrationByID(migrationID: string) {
const migration = MIGRATION_LOOKUP.get(migrationID);
if (!migration) {
logger.error(
`Attempted to apply migration ${migrationID}, but that migration doesn't exist?`
);
throw new Error(
`Attempted to apply migration ${migrationID}, but that migration doesn't exist?`
);
}
return ApplyMigration(migration);
}
/**
* Applies a migration. Will **EXIT TACHI** if a migration fails to apply, as this implies
* database level inconsistencies which could cause severe problems.
*/
export async function ApplyMigration(migrationID: string) {
export async function ApplyMigration(migration: Migration) {
const migrationID = migration.id;
logger.info(`Recieved request to apply migration '${migrationID}'.`);
// Lock the migration here:
@@ -112,17 +136,6 @@ export async function ApplyMigration(migrationID: string) {
throw new Error(`Tried to apply migration ${migrationID}, but it was already applied.`);
}
const migration = MIGRATION_LOOKUP.get(migrationID);
if (!migration) {
logger.error(
`Attempted to apply migration ${migrationID}, but that migration doesn't exist?`
);
throw new Error(
`Attempted to apply migration ${migrationID}, but that migration doesn't exist?`
);
}
try {
await migration.up();
@@ -169,6 +182,10 @@ export async function ApplyMigration(migrationID: string) {
// remove the stale migration so it can be re-ran in the future.
await db.migrations.findOneAndDelete({ migrationID });
if (Environment.nodeEnv === "test") {
throw new Error("Was going to exit with statusCode 1, but we're in testing.");
}
process.exit(1);
}
}
@@ -0,0 +1 @@
[]
+7
View File
@@ -28,6 +28,7 @@ import {
UGPTSettings,
} from "tachi-common";
import { ApplyNTimes, RFA } from "utils/misc";
import { Migration } from "utils/types";
const file = (name: string) => path.join(__dirname, "/test-data", name);
@@ -637,3 +638,9 @@ export const FakeGameSettings: UGPTSettings = {
rivals: [],
userID: 1,
};
export const FAKE_MIGRATION: Migration = {
id: "fake-migration",
up: () => db.users.update({}, { $set: { __wasMigrated: true } }, { multi: true }),
down: () => db.users.update({}, { $unset: { __wasMigrated: 1 } }, { multi: true }),
};
+2 -1
View File
@@ -1,7 +1,8 @@
import { exec } from "child_process";
import crypto from "crypto";
import { ONE_HOUR } from "lib/constants/time";
import { TachiConfig } from "lib/setup/config";
import { Environment, TachiConfig } from "lib/setup/config";
import { exit } from "process";
import { Game, GamePTConfig, GetGameConfig, integer, Playtype, Playtypes } from "tachi-common";
import { URL } from "url";