import * as path from 'path'; import { MigrationInterface, QueryRunner } from 'typeorm'; import { InitSchema1700000000000 } from '../../backend/src/database/migrations/1700000000000-InitSchema'; import { SeedSystemData1700000000100 } from '../../backend/src/database/migrations/1700000000100-SeedSystemData'; import { AddCategoryTimestampsAndUniqueAbbrev1700000000200 } from '../../backend/src/database/migrations/1700000000200-AddCategoryTimestampsAndUniqueAbbrev'; import { UpdateSystemCategoryKeys1700000000300 } from '../../backend/src/database/migrations/1700000000300-UpdateSystemCategoryKeys'; import { DataSource } from 'typeorm'; import { UserEntity } from '../../backend/src/database/entities/user.entity'; import { SettingEntity } from '../../backend/src/database/entities/setting.entity'; import { CategoryEntity } from '../../backend/src/database/entities/category.entity'; import { ChallengeEntity } from '../../backend/src/database/entities/challenge.entity'; import { ChallengeFileEntity } from '../../backend/src/database/entities/challenge-file.entity'; import { SolveEntity } from '../../backend/src/database/entities/solve.entity'; import { RefreshTokenEntity } from '../../backend/src/database/entities/refresh-token.entity'; import { BlogPostEntity } from '../../backend/src/database/entities/blog-post.entity'; describe('Migrations', () => { let dataSource: DataSource; beforeAll(async () => { dataSource = new DataSource({ type: 'better-sqlite3', database: ':memory:', entities: [UserEntity, SettingEntity, CategoryEntity, ChallengeEntity, ChallengeFileEntity, SolveEntity, RefreshTokenEntity, BlogPostEntity], migrations: [ InitSchema1700000000000, SeedSystemData1700000000100, AddCategoryTimestampsAndUniqueAbbrev1700000000200, UpdateSystemCategoryKeys1700000000300, ], migrationsRun: true, synchronize: false, logging: false, }); await dataSource.initialize(); }); afterAll(async () => { await dataSource.destroy(); }); it('creates all required tables', async () => { const tables = await dataSource.query("SELECT name FROM sqlite_master WHERE type='table'"); const names = tables.map((t: any) => t.name); expect(names).toEqual(expect.arrayContaining([ 'user', 'setting', 'category', 'challenge', 'challenge_file', 'solve', 'refresh_token', 'blog_post', ])); }); it('seeds 6 system categories with unique system_keys', async () => { const cats = await dataSource.getRepository(CategoryEntity).find(); const systemCats = cats.filter((c) => c.systemKey); expect(systemCats.length).toBeGreaterThanOrEqual(6); const keys = systemCats.map((c) => c.systemKey); expect(new Set(keys).size).toBe(keys.length); }); it('seeds default settings', async () => { const settings = await dataSource.getRepository(SettingEntity).find(); const keys = settings.map((s) => s.key); expect(keys).toEqual(expect.arrayContaining(['pageTitle', 'themeKey', 'eventStartUtc', 'eventEndUtc'])); }); it('enforces UNIQUE(challengeId, userId) on solve', async () => { const indexes = await dataSource.query("SELECT name, sql FROM sqlite_master WHERE type='index' AND tbl_name='solve'"); const uq = indexes.find((i: any) => /UNIQUE.*challenge_id.*user_id/i.test(i.sql || '')); expect(uq).toBeDefined(); }); it('enforces ON DELETE RESTRICT FK on solve', async () => { const fks = await dataSource.query("PRAGMA foreign_key_list('solve')"); const restrict = fks.filter((f: any) => f.on_delete === 'RESTRICT'); expect(restrict.length).toBeGreaterThanOrEqual(2); }); });