187 lines
6.7 KiB
TypeScript
187 lines
6.7 KiB
TypeScript
import Database from 'better-sqlite3';
|
|
import { execFileSync } from 'node:child_process';
|
|
import { mkdtempSync, readdirSync, readFileSync, rmSync } from 'node:fs';
|
|
import { tmpdir } from 'node:os';
|
|
import path from 'node:path';
|
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
|
|
const temporaryDirectories: string[] = [];
|
|
|
|
afterEach(async () => {
|
|
const { closeDatabase } = await import('./database.js');
|
|
closeDatabase();
|
|
delete process.env.SENDEWERK_DB_PATH;
|
|
delete process.env.SENDEWERK_UPLOAD_DIR;
|
|
for (const directory of temporaryDirectories.splice(0)) {
|
|
rmSync(directory, { recursive: true, force: true });
|
|
}
|
|
vi.resetModules();
|
|
});
|
|
|
|
describe('database setup', () => {
|
|
it('runs migrations and the seed repeatedly in an isolated database', () => {
|
|
const directory = mkdtempSync(path.join(tmpdir(), 'sendewerk-test-'));
|
|
temporaryDirectories.push(directory);
|
|
const databasePath = path.join(directory, 'test.sqlite');
|
|
const environment = {
|
|
...process.env,
|
|
SENDEWERK_DB_PATH: databasePath,
|
|
SENDEWERK_UPLOAD_DIR: path.join(directory, 'uploads')
|
|
};
|
|
|
|
for (let run = 0; run < 2; run += 1) {
|
|
execFileSync(process.execPath, ['scripts/migrate.mjs'], {
|
|
cwd: process.cwd(),
|
|
env: environment
|
|
});
|
|
execFileSync(process.execPath, ['scripts/seed.mjs'], {
|
|
cwd: process.cwd(),
|
|
env: environment
|
|
});
|
|
}
|
|
|
|
const database = new Database(databasePath, { readonly: true });
|
|
const counts = database
|
|
.prepare(
|
|
`SELECT
|
|
(SELECT count(*) FROM schema_migrations) AS migrations,
|
|
(SELECT count(*) FROM pieces) AS pieces,
|
|
(SELECT count(*) FROM shows) AS shows,
|
|
(SELECT count(*) FROM piece_usages) AS usages,
|
|
(SELECT count(*) FROM show_templates) AS templates,
|
|
(SELECT count(*) FROM show_sections WHERE is_reserve_bank = 1) AS reserve_banks`
|
|
)
|
|
.get() as {
|
|
migrations: number;
|
|
pieces: number;
|
|
shows: number;
|
|
usages: number;
|
|
templates: number;
|
|
reserve_banks: number;
|
|
};
|
|
database.close();
|
|
|
|
expect(counts).toEqual({
|
|
migrations: 20,
|
|
pieces: 6,
|
|
shows: 2,
|
|
usages: 5,
|
|
templates: 2,
|
|
reserve_banks: 2
|
|
});
|
|
expect(databasePath).not.toBe(path.resolve('.data/sendewerk.sqlite'));
|
|
});
|
|
|
|
it('does not count reserve pieces or explicit sections twice', async () => {
|
|
const directory = mkdtempSync(path.join(tmpdir(), 'sendewerk-duration-test-'));
|
|
temporaryDirectories.push(directory);
|
|
process.env.SENDEWERK_DB_PATH = path.join(directory, 'test.sqlite');
|
|
process.env.SENDEWERK_UPLOAD_DIR = path.join(directory, 'uploads');
|
|
|
|
execFileSync(process.execPath, ['scripts/migrate.mjs'], {
|
|
cwd: process.cwd(),
|
|
env: process.env
|
|
});
|
|
execFileSync(process.execPath, ['scripts/seed.mjs'], { cwd: process.cwd(), env: process.env });
|
|
const { getShow } = await import('./previews.js');
|
|
const show = getShow();
|
|
|
|
expect(show.sections.map((section) => section.durationSeconds)).toEqual([300, 1200, 3600]);
|
|
expect(show.totalDurationSeconds).toBe(5100);
|
|
expect(show.sections[1].usages.find((usage) => usage.isReserve)?.durationSeconds).toBe(540);
|
|
});
|
|
|
|
it('upgrades existing completed shows into stable finalizations exactly once', () => {
|
|
const directory = mkdtempSync(path.join(tmpdir(), 'sendewerk-upgrade-test-'));
|
|
temporaryDirectories.push(directory);
|
|
const databasePath = path.join(directory, 'test.sqlite');
|
|
const environment = {
|
|
...process.env,
|
|
SENDEWERK_DB_PATH: databasePath,
|
|
SENDEWERK_UPLOAD_DIR: path.join(directory, 'uploads')
|
|
};
|
|
const database = new Database(databasePath);
|
|
database.pragma('foreign_keys = ON');
|
|
database.exec(
|
|
'CREATE TABLE schema_migrations (name TEXT PRIMARY KEY, applied_at_utc TEXT NOT NULL)'
|
|
);
|
|
const migrationDirectory = path.resolve('migrations');
|
|
const oldMigrations = readdirSync(migrationDirectory)
|
|
.filter(
|
|
(name) =>
|
|
/^\d+.+\.sql$/.test(name) && !name.startsWith('0015_') && !name.startsWith('0016_')
|
|
)
|
|
.sort();
|
|
for (const name of oldMigrations) {
|
|
database.exec(readFileSync(path.join(migrationDirectory, name), 'utf8'));
|
|
database
|
|
.prepare('INSERT INTO schema_migrations (name, applied_at_utc) VALUES (?, ?)')
|
|
.run(name, new Date().toISOString());
|
|
}
|
|
database.close();
|
|
execFileSync(process.execPath, ['scripts/seed.mjs'], { cwd: process.cwd(), env: environment });
|
|
|
|
const legacy = new Database(databasePath);
|
|
legacy.pragma('foreign_keys = ON');
|
|
const usage = legacy
|
|
.prepare(
|
|
`SELECT u.id FROM piece_usages u JOIN show_sections s ON s.id = u.show_section_id
|
|
WHERE s.show_id = 'show-october' AND s.is_reserve_bank = 0 LIMIT 1`
|
|
)
|
|
.get() as { id: string };
|
|
legacy.prepare("UPDATE piece_usages SET status = 'done' WHERE id = ?").run(usage.id);
|
|
legacy.prepare("UPDATE shows SET status = 'completed' WHERE id = 'show-october'").run();
|
|
const completionMigration = '0015_completion_archive_and_shares.sql';
|
|
legacy.exec(readFileSync(path.join(migrationDirectory, completionMigration), 'utf8'));
|
|
legacy
|
|
.prepare('INSERT INTO schema_migrations (name, applied_at_utc) VALUES (?, ?)')
|
|
.run(completionMigration, new Date().toISOString());
|
|
const now = new Date().toISOString();
|
|
legacy
|
|
.prepare(
|
|
`INSERT INTO users (id, email, email_normalized, display_name, password_hash, role,
|
|
password_changed_at_utc, created_at_utc, updated_at_utc)
|
|
VALUES ('upgrade-barkeeper', 'upgrade@example.test', 'upgrade@example.test',
|
|
'Upgrade', 'unused', 'barkeeper', ?, ?, ?)`
|
|
)
|
|
.run(now, now, now);
|
|
legacy
|
|
.prepare(
|
|
`INSERT INTO show_shares
|
|
(id, show_id, token_hash, scope, password_hash, expires_at_utc, revoked_at_utc,
|
|
created_by_user_id, created_at_utc)
|
|
VALUES ('upgrade-share', 'show-october', 'upgrade-token-hash', 'rundown', NULL,
|
|
NULL, NULL, 'upgrade-barkeeper', ?)`
|
|
)
|
|
.run(now);
|
|
legacy
|
|
.prepare(
|
|
`INSERT INTO show_share_sessions (token_hash, share_id, created_at_utc, expires_at_utc)
|
|
VALUES ('upgrade-session-hash', 'upgrade-share', ?, ?)`
|
|
)
|
|
.run(now, new Date(Date.now() + 60_000).toISOString());
|
|
legacy.close();
|
|
|
|
for (let run = 0; run < 2; run += 1) {
|
|
execFileSync(process.execPath, ['scripts/migrate.mjs'], {
|
|
cwd: process.cwd(),
|
|
env: environment
|
|
});
|
|
}
|
|
const upgraded = new Database(databasePath, { readonly: true });
|
|
expect(
|
|
upgraded
|
|
.prepare(
|
|
`SELECT
|
|
(SELECT count(*) FROM show_finalizations WHERE show_id = 'show-october') AS finalizations,
|
|
(SELECT count(*) FROM piece_usage_history WHERE show_id = 'show-october') AS history,
|
|
(SELECT count(*) FROM show_shares WHERE id = 'upgrade-share') AS shares,
|
|
(SELECT count(*) FROM show_share_sessions WHERE token_hash = 'upgrade-session-hash') AS sessions`
|
|
)
|
|
.get()
|
|
).toEqual({ finalizations: 1, history: 1, shares: 1, sessions: 1 });
|
|
expect(upgraded.pragma('foreign_key_check')).toEqual([]);
|
|
upgraded.close();
|
|
});
|
|
});
|