Files
TREK/server/tests/unit/db/seeders-insert-ignore.test.ts
T
Maurice 39bb8f9619 build(server): hold SQLite-only SQL spellings to a shrinking baseline
ESLint keeps raw SQL out of the repositories, but nothing looked at the rest
of src/: the entities, the domain services, the src/db lifecycle files or a
migration added from now on. scripts/dialect-lint.mjs (npm run lint:dialect)
reads every string and template literal under src/, skipping comments, and
counts INSERT OR IGNORE/REPLACE, datetime(), strftime, json_extract, GLOB,
last_insert_rowid, AUTOINCREMENT, PRAGMA outside the lifecycle files, ||
concatenation in SQL and .insertId reads. scripts/dialect-baseline.json
holds today's thirteen hits; the counts may only go down, like lint:size.
The dialect layer is not scanned, and neither are the migrations that have
shipped.

The four seeders that still spelled INSERT OR IGNORE (addons, photo and
document providers, the schema_version marker) are Kysely inserts that do
nothing on conflict now, so a re-run still leaves an operator's rows alone.
They leave the SQL-string exemption list in eslint.config.mjs and their pins
in the selector test.
2026-10-08 18:39:33 +02:00

83 lines
3.6 KiB
TypeScript

/**
* The addon, photo-provider and document-provider seeders run again on every
* schema bootstrap and on a backup restore. They used to spell SQLite's
* `INSERT OR IGNORE`; they are query-builder inserts that do nothing on
* conflict now, and these tests pin the behaviour that has to survive that:
* a second run adds nothing, and a row an operator changed keeps the change.
*/
import { AddonSeeder } from '../../../src/db/seeders/AddonSeeder';
import { DocumentProviderSeeder } from '../../../src/db/seeders/DocumentProviderSeeder';
import { PhotoProviderSeeder } from '../../../src/db/seeders/PhotoProviderSeeder';
import { createMigrationOrm, migratorOf, rawExec, rawQuery } from '../../helpers/migration-step';
import type { MikroORM } from '@mikro-orm/sqlite';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
describe('seeders that leave existing rows alone', () => {
let orm: MikroORM;
beforeEach(async () => {
orm = await createMigrationOrm();
await migratorOf(orm).up();
});
afterEach(async () => {
await orm.close(true);
});
const count = async (table: string) =>
(await rawQuery<{ n: number }>(orm, `SELECT COUNT(*) AS n FROM ${table}`))[0].n;
it('SEEDIGN-001: AddonSeeder adds nothing the second time and keeps an operator toggle', async () => {
await new AddonSeeder().run(orm.em);
const seeded = await count('addons');
expect(seeded).toBeGreaterThan(0);
await rawExec(orm, "UPDATE addons SET enabled = 1 - enabled, sort_order = 99 WHERE id = 'mcp'");
const [before] = await rawQuery<{ enabled: number }>(orm, "SELECT enabled FROM addons WHERE id = 'mcp'");
await new AddonSeeder().run(orm.em);
expect(await count('addons')).toBe(seeded);
expect(await rawQuery(orm, "SELECT enabled, sort_order FROM addons WHERE id = 'mcp'")).toEqual([
{ enabled: before.enabled, sort_order: 99 },
]);
});
it('SEEDIGN-002: AddonSeeder restores a deleted default addon', async () => {
await new AddonSeeder().run(orm.em);
await rawExec(orm, "DELETE FROM addons WHERE id = 'collab'");
await new AddonSeeder().run(orm.em);
expect(await rawQuery(orm, "SELECT id, type, icon FROM addons WHERE id = 'collab'")).toEqual([
{ id: 'collab', type: 'trip', icon: 'Users' },
]);
});
it('SEEDIGN-003: PhotoProviderSeeder seeds providers and fields once, keeping a renamed provider', async () => {
await new PhotoProviderSeeder().run(orm.em);
const providers = await count('photo_providers');
const fields = await count('photo_provider_fields');
expect(providers).toBeGreaterThan(0);
expect(fields).toBeGreaterThan(0);
await rawExec(orm, "UPDATE photo_providers SET name = 'Renamed' WHERE id = 'synologyphotos'");
await new PhotoProviderSeeder().run(orm.em);
expect(await count('photo_providers')).toBe(providers);
expect(await count('photo_provider_fields')).toBe(fields);
expect(await rawQuery(orm, "SELECT name FROM photo_providers WHERE id = 'synologyphotos'")).toEqual([{ name: 'Renamed' }]);
});
it('SEEDIGN-004: DocumentProviderSeeder seeds providers disabled and fields once', async () => {
await new DocumentProviderSeeder().run(orm.em);
const providers = await count('document_providers');
const fields = await count('document_provider_fields');
expect(providers).toBeGreaterThan(0);
expect(await rawQuery(orm, 'SELECT DISTINCT enabled FROM document_providers')).toEqual([{ enabled: 0 }]);
await new DocumentProviderSeeder().run(orm.em);
expect(await count('document_providers')).toBe(providers);
expect(await count('document_provider_fields')).toBe(fields);
});
});