mirror of
https://github.com/zkldi/Tachi.git
synced 2026-10-09 15:19:50 +03:00
test: add tests for migrations
This commit is contained in:
@@ -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();
|
||||
});
|
||||
@@ -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 @@
|
||||
[]
|
||||
@@ -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 }),
|
||||
};
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
Reference in New Issue
Block a user