diff --git a/src/lib/server/reference/schema.ts b/src/lib/server/reference/schema.ts index 745ef6b..cf1ee3a 100644 --- a/src/lib/server/reference/schema.ts +++ b/src/lib/server/reference/schema.ts @@ -202,6 +202,7 @@ export function createReferenceSchema(db: Database): void { CREATE INDEX systems_name_idx ON systems(name); CREATE INDEX gates_destination_idx ON gates(destination_system_id); CREATE INDEX stations_system_idx ON stations(system_id); + CREATE INDEX stations_source_verified_idx ON stations(source_verified, station_id); `); } @@ -209,7 +210,7 @@ export function createReferenceSchema(db: Database): void { export function assertReferenceReaderSchema(db: Database): void { const version = Number((db.query<{ user_version: number }, []>('PRAGMA user_version').get() as { user_version: number } | null)?.user_version ?? 0); const row = db.query<{ present: number }, []>("SELECT COUNT(*) AS present FROM sqlite_master WHERE type='table' AND name IN ('types','type_search','type_search_fts','categories','groups','regions','constellations','systems','stations')").get(); - const index = db.query<{ present: number }, []>("SELECT COUNT(*) AS present FROM sqlite_master WHERE type='index' AND name='type_search_normalized_name_idx'").get(); + const indexes = db.query<{ present: number }, []>("SELECT COUNT(*) AS present FROM sqlite_master WHERE type='index' AND name IN ('type_search_normalized_name_idx','stations_source_verified_idx')").get(); const requiredColumns: Record = { types: ['type_id', 'group_id', 'category_id', 'name_json', 'published', 'volume'], type_search: ['type_id', 'language', 'name', 'normalized_name'], @@ -225,7 +226,7 @@ export function assertReferenceReaderSchema(db: Database): void { const present = new Set(db.query<{ name: string }, []>(`PRAGMA table_info(${table})`).all().map((column) => column.name)); return columns.every((column) => present.has(column)); }); - if (version !== SCHEMA_VERSION || !row || Number(row.present) !== 9 || !index || Number(index.present) !== 1 || !columnsValid) throw new Error(`active reference database has an incompatible schema version (expected ${SCHEMA_VERSION})`); + if (version !== SCHEMA_VERSION || !row || Number(row.present) !== 9 || !indexes || Number(indexes.present) !== 2 || !columnsValid) throw new Error(`active reference database has an incompatible schema version (expected ${SCHEMA_VERSION})`); } const bool = (value: boolean | undefined): number | null => value === undefined ? null : value ? 1 : 0; diff --git a/tests/reference-readers.test.ts b/tests/reference-readers.test.ts index 02e1831..0466e78 100644 --- a/tests/reference-readers.test.ts +++ b/tests/reference-readers.test.ts @@ -1,5 +1,6 @@ import { expect, test } from 'bun:test'; import { cp, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'; +import { Database } from 'bun:sqlite'; import { join } from 'node:path'; import { tmpdir } from 'node:os'; import { ReferenceCompiler, SqliteReferenceRepository, readReferenceManifest } from '../src/lib/server/reference'; @@ -15,8 +16,8 @@ test('reference readers search normalized types and expose group/category/system const universe = JSON.parse(await readFile(universePath, 'utf8')) as Record; universe.regions = { '10000001': { region_id: 10000001, name: 'The Forge' } }; universe.constellations = { '20000020': { constellation_id: 20000020, name: 'Kimotoro', region_id: 10000001 } }; - universe.stations = { '60003760': { station_id: 60003760, system_id: 30000142, name: 'Jita IV - Moon 4 - Caldari Navy Assembly Plant', type_id: 587, owner_id: 1000035, position: { x: 1, y: 2, z: 3 } } }; - (universe.systems as Record>)['30000142'].stations = [60003761]; + universe.stations = { '60003761': { station_id: 60003761, system_id: 30000142, name: 'Jita IV - Moon 4 - Caldari Navy Assembly Plant', type_id: 587, owner_id: 1000035, position: { x: 1, y: 2, z: 3 } } }; + (universe.systems as Record>)['30000142'].stations = [60003760]; await writeFile(universePath, JSON.stringify(universe)); const result = await new ReferenceCompiler({ directory, profile: 'fixture', sources: fixtureSources(sources) }).compile(); @@ -32,10 +33,13 @@ test('reference readers search normalized types and expose group/category/system const stations = await reader.stations({ systemId: 30000142 }); expect(stations).toHaveLength(2); expect(stations).toEqual([ - { id: 60003760, systemId: 30000142, systemName: 'Jita', regionId: 10000001, evidence: 'verified', sourceVerified: true, name: 'Jita IV - Moon 4 - Caldari Navy Assembly Plant', typeId: 587, ownerId: 1000035, x: 1, y: 2, z: 3 }, - { id: 60003761, systemId: 30000142, systemName: 'Jita', regionId: 10000001, evidence: 'system_listed', sourceVerified: false, name: null, typeId: null, ownerId: null, x: null, y: null, z: null }, + { id: 60003760, systemId: 30000142, systemName: 'Jita', regionId: 10000001, evidence: 'system_listed', sourceVerified: false, name: null, typeId: null, ownerId: null, x: null, y: null, z: null }, + { id: 60003761, systemId: 30000142, systemName: 'Jita', regionId: 10000001, evidence: 'verified', sourceVerified: true, name: 'Jita IV - Moon 4 - Caldari Navy Assembly Plant', typeId: 587, ownerId: 1000035, x: 1, y: 2, z: 3 }, ]); - expect(await reader.stations({ query: 'assembly' })).toEqual([stations[0]]); + const db = new Database(reader.databasePath, { readonly: true, strict: true }); + try { expect(db.query<{ station_id: number }, []>('SELECT station_id FROM stations WHERE source_verified = 1 ORDER BY station_id LIMIT 1').get()?.station_id).toBe(60003761); } + finally { db.close(); } + expect(await reader.stations({ query: 'assembly' })).toEqual([stations[1]]); const pinned = reader.pin(); try { expect((await pinned.searchTypesByName('pyerite'))[0]?.id).toBe(35); } finally { pinned.close(); } diff --git a/workers/check-reference.ts b/workers/check-reference.ts index 7692f7c..a77d2dc 100644 --- a/workers/check-reference.ts +++ b/workers/check-reference.ts @@ -27,8 +27,10 @@ try { assert(verifiedStations > 0); const joinedStations = Number((db.query<{ n: number }, []>('SELECT COUNT(*) AS n FROM stations st JOIN systems s ON s.system_id = st.system_id JOIN constellations c ON c.constellation_id = s.constellation_id JOIN regions r ON r.region_id = c.region_id').get() as { n: number }).n); assert.equal(joinedStations, counts.stations, 'every station must join through system, constellation, and region'); - const verifiedStation = (await repository.stations({ limit: 1 })).find((station) => station.sourceVerified); - assert(verifiedStation, 'reader must expose a verified station row'); + const verifiedStationId = db.query<{ station_id: number }, []>('SELECT station_id FROM stations WHERE source_verified = 1 ORDER BY station_id LIMIT 1').get()?.station_id; + assert(verifiedStationId !== undefined, 'published artifact must contain a verified station row'); + const verifiedStation = (await repository.stations([verifiedStationId]))[0]; + assert(verifiedStation?.sourceVerified, 'reader must expose the selected verified station row'); assert.deepEqual(await repository.stations([Number.MAX_SAFE_INTEGER]), [], 'unknown station IDs must remain absent rather than fabricated'); const integrity = db.query<{ integrity_check: string }, []>('PRAGMA integrity_check').all(); assert.deepEqual(integrity.map(row => row.integrity_check), ['ok']);