mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-19 05:26:47 +00:00
724c88c201
A mechanical prettier --write over server/src, server/tests, server/scripts and shared/src with the workspaces' .prettierrc; nothing but layout and import order changes, and every file keeps the same set of imports. shared is formatted throughout and its format baseline is empty. The server baseline keeps the 35 files that Prettier would grow past their lint:size limit (they need a split first, which is not a formatting change), and the generated entity files stay as their generator writes them.
175 lines
6.6 KiB
TypeScript
175 lines
6.6 KiB
TypeScript
/**
|
|
* Unit tests for the backfill migration that normalises leading/trailing
|
|
* whitespace in stored usernames and emails.
|
|
* Tests TRIM-MIG-001 through TRIM-MIG-010.
|
|
*
|
|
* Ported off the legacy runner (Task 0/Task 5c triage: its production
|
|
* equivalent is a frozen numbered migration) onto the real
|
|
* `Migration20200101020800_trim_leading_trailing_whitespace_from_stored_usernames`
|
|
* (the legacy `trimUserWhitespace` export in `db/migrations.ts` was never
|
|
* called by any numbered migration — this one reimplements the same logic
|
|
* inline, deliberately, since the legacy file is not importable from there):
|
|
* migrate to the step immediately before it, seed rows with raw SQL, apply
|
|
* just that one migration, assert.
|
|
*/
|
|
import { createMigrationOrm, migrateTo, pendingNames, rawExec, rawQuery } from '../../helpers/migration-step';
|
|
import type { MikroORM } from '@mikro-orm/sqlite';
|
|
|
|
import { describe, it, expect, vi } from 'vitest';
|
|
|
|
const TARGET = 'Migration20200101020800_trim_leading_trailing_whitespace_from_stored_usernames';
|
|
|
|
async function ormBeforeTarget(): Promise<MikroORM> {
|
|
const orm = await createMigrationOrm();
|
|
const names = await pendingNames(orm);
|
|
const idx = names.indexOf(TARGET);
|
|
expect(idx).toBeGreaterThan(0);
|
|
await migrateTo(orm, names[idx - 1]);
|
|
return orm;
|
|
}
|
|
|
|
async function insert(orm: MikroORM, username: string, email: string): Promise<number> {
|
|
await rawExec(orm, "INSERT INTO users (username, email, password_hash) VALUES (?, ?, 'x')", [username, email]);
|
|
const rows = await rawQuery<{ id: number }>(orm, 'SELECT last_insert_rowid() as id');
|
|
return rows[0].id;
|
|
}
|
|
|
|
async function row(orm: MikroORM, id: number): Promise<{ username: string; email: string }> {
|
|
const rows = await rawQuery<{ username: string; email: string }>(
|
|
orm,
|
|
'SELECT username, email FROM users WHERE id = ?',
|
|
[id],
|
|
);
|
|
return rows[0];
|
|
}
|
|
|
|
describe('trimUserWhitespace — clean data (no-op)', () => {
|
|
it('TRIM-MIG-001 — leaves already-clean rows untouched', async () => {
|
|
const orm = await ormBeforeTarget();
|
|
try {
|
|
const id = await insert(orm, 'alice', 'alice@example.com');
|
|
await migrateTo(orm, TARGET);
|
|
expect(await row(orm, id)).toEqual({ username: 'alice', email: 'alice@example.com' });
|
|
} finally {
|
|
await orm.close(true);
|
|
}
|
|
}, 30000);
|
|
});
|
|
|
|
describe('trimUserWhitespace — non-colliding dirty rows', () => {
|
|
it('TRIM-MIG-002 — trims trailing whitespace from username', async () => {
|
|
const orm = await ormBeforeTarget();
|
|
try {
|
|
const id = await insert(orm, 'alice ', 'alice@example.com');
|
|
await migrateTo(orm, TARGET);
|
|
expect((await row(orm, id)).username).toBe('alice');
|
|
} finally {
|
|
await orm.close(true);
|
|
}
|
|
}, 30000);
|
|
|
|
it('TRIM-MIG-003 — trims leading whitespace from username', async () => {
|
|
const orm = await ormBeforeTarget();
|
|
try {
|
|
const id = await insert(orm, ' alice', 'alice@example.com');
|
|
await migrateTo(orm, TARGET);
|
|
expect((await row(orm, id)).username).toBe('alice');
|
|
} finally {
|
|
await orm.close(true);
|
|
}
|
|
}, 30000);
|
|
|
|
it('TRIM-MIG-004 — trims surrounding whitespace from email', async () => {
|
|
const orm = await ormBeforeTarget();
|
|
try {
|
|
const id = await insert(orm, 'alice', ' alice@example.com ');
|
|
await migrateTo(orm, TARGET);
|
|
expect((await row(orm, id)).email).toBe('alice@example.com');
|
|
} finally {
|
|
await orm.close(true);
|
|
}
|
|
}, 30000);
|
|
|
|
it('TRIM-MIG-005 — emits a console.warn for each trimmed row', async () => {
|
|
const orm = await ormBeforeTarget();
|
|
try {
|
|
await insert(orm, 'bob ', 'bob@example.com');
|
|
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
await migrateTo(orm, TARGET);
|
|
expect(warn).toHaveBeenCalledWith(expect.stringContaining('[migration] Trimmed username'));
|
|
warn.mockRestore();
|
|
} finally {
|
|
await orm.close(true);
|
|
}
|
|
}, 30000);
|
|
});
|
|
|
|
describe('trimUserWhitespace — username collision handling', () => {
|
|
it('TRIM-MIG-006 — renames the dirty row to <trimmed>__migrated_<id> on collision', async () => {
|
|
const orm = await ormBeforeTarget();
|
|
try {
|
|
await insert(orm, 'carol', 'carol@example.com');
|
|
const dirtyId = await insert(orm, 'carol ', 'carol2@example.com');
|
|
await migrateTo(orm, TARGET);
|
|
expect((await row(orm, dirtyId)).username).toBe(`carol__migrated_${dirtyId}`);
|
|
} finally {
|
|
await orm.close(true);
|
|
}
|
|
}, 30000);
|
|
|
|
it('TRIM-MIG-007 — emits a WHITESPACE COLLISION warning for username collision', async () => {
|
|
const orm = await ormBeforeTarget();
|
|
try {
|
|
await insert(orm, 'dan', 'dan@example.com');
|
|
await insert(orm, 'dan ', 'dan2@example.com');
|
|
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
await migrateTo(orm, TARGET);
|
|
expect(warn).toHaveBeenCalledWith(expect.stringContaining('WHITESPACE COLLISION username'));
|
|
warn.mockRestore();
|
|
} finally {
|
|
await orm.close(true);
|
|
}
|
|
}, 30000);
|
|
|
|
it('TRIM-MIG-008 — the renamed value does not conflict with the existing clean row', async () => {
|
|
const orm = await ormBeforeTarget();
|
|
try {
|
|
const cleanId = await insert(orm, 'eve', 'eve@example.com');
|
|
const dirtyId = await insert(orm, 'eve ', 'eve2@example.com');
|
|
await migrateTo(orm, TARGET);
|
|
expect((await row(orm, cleanId)).username).toBe('eve');
|
|
expect((await row(orm, dirtyId)).username).toBe(`eve__migrated_${dirtyId}`);
|
|
} finally {
|
|
await orm.close(true);
|
|
}
|
|
}, 30000);
|
|
});
|
|
|
|
describe('trimUserWhitespace — email collision handling', () => {
|
|
it('TRIM-MIG-009 — renames dirty email as <local>__migrated_<id>@<domain> on collision', async () => {
|
|
const orm = await ormBeforeTarget();
|
|
try {
|
|
await insert(orm, 'frank', 'frank@example.com');
|
|
const dirtyId = await insert(orm, 'frank2', ' frank@example.com ');
|
|
await migrateTo(orm, TARGET);
|
|
expect((await row(orm, dirtyId)).email).toBe(`frank__migrated_${dirtyId}@example.com`);
|
|
} finally {
|
|
await orm.close(true);
|
|
}
|
|
}, 30000);
|
|
|
|
it('TRIM-MIG-010 — emits a WHITESPACE COLLISION warning for email collision', async () => {
|
|
const orm = await ormBeforeTarget();
|
|
try {
|
|
await insert(orm, 'grace', 'grace@example.com');
|
|
await insert(orm, 'grace2', 'grace@example.com ');
|
|
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
await migrateTo(orm, TARGET);
|
|
expect(warn).toHaveBeenCalledWith(expect.stringContaining('WHITESPACE COLLISION email'));
|
|
warn.mockRestore();
|
|
} finally {
|
|
await orm.close(true);
|
|
}
|
|
}, 30000);
|
|
});
|