import { connect, type Database } from '@tursodatabase/database'; import { readFile } from 'node:fs/promises'; import { afterEach, beforeEach, describe, expect, it } from 'vitest'; let database: Database; const applySchema = async (target: Database) => { const schema = await readFile(new URL('./schema.sql', import.meta.url), 'utf8'); for (const statement of schema .split(';') .map((value) => value.trim()) .filter(Boolean)) { await target.exec(statement); } }; const insertSource = async ( target: Database, id: string, ownerId: string, fingerprint: string, ) => { await ( await target.prepare( `INSERT INTO mechanic_authored_sources( id,owner_type,owner_id,template,authored_contract_version,source_fingerprint, talent_scope,talent_has_tier_ii,created_at,updated_at ) VALUES(?,?,?,?,?,?,?,?,?,?)`, ) ).run( id, 'talent', ownerId, 'Gain {1}', 1, fingerprint, 'standard', 1, '2026-08-27T00:00:00.000Z', '2026-08-27T00:00:00.000Z', ); }; describe('mechanic persistence schema', () => { beforeEach(async () => { database = await connect(':memory:'); await database.exec('PRAGMA foreign_keys=ON'); await applySchema(database); }); afterEach(async () => { await database.close(); }); it('installs the complete schema with clean foreign keys', async () => { const rows = await ( await database.prepare( "SELECT name FROM sqlite_master WHERE type='table' AND name LIKE 'mechanic_%' ORDER BY name", ) ).all(); expect(rows.map((row) => row.name)).toEqual([ 'mechanic_authored_slots', 'mechanic_authored_sources', 'mechanic_authored_variants', 'mechanic_model_revision_clauses', 'mechanic_model_revision_outcomes', 'mechanic_model_revision_pools', 'mechanic_model_revisions', 'mechanic_model_selections', 'mechanic_public_variants', ]); expect(await (await database.prepare('PRAGMA foreign_key_check')).all()).toEqual([]); }); it('enforces owner metadata, ordered authored children, and source cascades', async () => { const fingerprint = 'a'.repeat(64); await insertSource(database, 'source-1', 'talent-1', fingerprint); await expect( insertSource(database, 'source-2', 'talent-1', fingerprint), ).rejects.toThrow(); await expect( database.exec( `INSERT INTO mechanic_authored_sources( id,owner_type,owner_id,template,authored_contract_version,source_fingerprint, specialization_rank_description,specialization_effect_id,specialization_effect_template, created_at,updated_at ) VALUES('source-3','talent','talent-3','Gain',1,'${fingerprint}', 'rank','effect','template','now','now')`, ), ).rejects.toThrow(); await ( await database.prepare( `INSERT INTO mechanic_authored_variants(source_id,variant_key,materialized,sort_order) VALUES('source-1','I','Gain 1',0),('source-1','II','Gain 2',1)`, ) ).run(); await ( await database.prepare( `INSERT INTO mechanic_authored_slots( source_id,variant_key,slot_key,display_value,sort_order ) VALUES('source-1','I','1','1',0)`, ) ).run(); await expect( database.exec( `INSERT INTO mechanic_authored_slots( source_id,variant_key,slot_key,display_value,sort_order ) VALUES('source-1','missing','1','1',0)`, ), ).rejects.toThrow(); await expect( database.exec( `INSERT INTO mechanic_authored_slots( source_id,variant_key,slot_key,display_value,sort_order ) VALUES('source-1','II','1','10% Weapon Damage',0)`, ), ).rejects.toThrow(); await database.exec("DELETE FROM mechanic_authored_sources WHERE id='source-1'"); for (const table of [ 'mechanic_authored_variants', 'mechanic_authored_slots', ]) { expect( await ( await database.prepare(`SELECT COUNT(*) AS count FROM ${table}`) ).get(), ).toMatchObject({ count: 0 }); } }); it('allows deliberate duplicate graphs while proving selection ownership', async () => { const sourceFingerprint = 'a'.repeat(64); const programHash = 'b'.repeat(64); await insertSource(database, 'source-1', 'talent-1', sourceFingerprint); await ( await database.prepare( `INSERT INTO mechanic_authored_variants(source_id,variant_key,materialized,sort_order) VALUES('source-1','I','Gain 1',0)`, ) ).run(); await ( await database.prepare( `INSERT INTO mechanic_model_revisions( id,source_id,revision_number,source_fingerprint,mechanic_contract_version, program_hash,simulation_readiness,scenario_inputs_json,source_variants_json, program_name,origin,created_at ) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)`, ) ).run( 'revision-1', 'source-1', 1, sourceFingerprint, 1, programHash, 'ready', '[]', '["I"]', 'Model', 'manual', 'now', ); await ( await database.prepare( `INSERT INTO mechanic_model_revisions( id,source_id,revision_number,source_fingerprint,mechanic_contract_version, program_hash,simulation_readiness,scenario_inputs_json,source_variants_json, program_name,origin,created_at ) SELECT 'revision-2',source_id,2,source_fingerprint,mechanic_contract_version, program_hash,simulation_readiness,scenario_inputs_json,source_variants_json, program_name,origin,created_at FROM mechanic_model_revisions WHERE id='revision-1'`, ) ).run(); await expect( database.exec( `INSERT INTO mechanic_model_revisions( id,source_id,revision_number,source_fingerprint,mechanic_contract_version, program_hash,simulation_readiness,scenario_inputs_json,source_variants_json, program_name,origin,created_at ) SELECT 'revision-3',source_id,1,source_fingerprint,mechanic_contract_version, program_hash,simulation_readiness,scenario_inputs_json,source_variants_json, program_name,origin,created_at FROM mechanic_model_revisions WHERE id='revision-1'`, ), ).rejects.toThrow(); await ( await database.prepare( `INSERT INTO mechanic_model_selections(source_id,revision_id,updated_at) VALUES('source-1','revision-2','now')`, ) ).run(); await expect( database.exec( `INSERT INTO mechanic_model_selections(source_id,revision_id,updated_at) VALUES('source-1','not-this-source','now')`, ), ).rejects.toThrow(); await ( await database.prepare( `INSERT INTO mechanic_model_revision_pools( id,revision_id,pool_key,label,state_kind,state_scope,initial_value, minimum_value,maximum_value,best_case_value,duration_seconds, duration_variants_json,sort_order ) VALUES('pool-1','revision-1','stacks','Stacks','stack','agent',0,0,5,5,NULL,NULL,0)`, ) ).run(); await ( await database.prepare( `INSERT INTO mechanic_model_revision_clauses( id,revision_id,clause_key,label,event_kind,recipe_key,conditions_json, reducer_json,weight_expression_json,mutations_json,cooldown_seconds, proc_chance_percent,requires_assigned_ability_active,sort_order ) VALUES('clause-1','revision-1','on-hit','On hit','weapon_hit',NULL,NULL, '{"kind":"none"}',NULL,'[]',NULL,25,NULL,0)`, ) ).run(); await ( await database.prepare( `INSERT INTO mechanic_model_revision_outcomes( id,revision_id,clause_id,state_pool_id,effect_key,variant_key,label, outcome_kind,effect_scope,value,value_expression_json,value_basis, resource_kind,delivery_mode,delivery_duration_seconds,application_phase, combat_timing,stat_attribute_id,per_stack,min_state_value,max_state_value, targeting_json,probability_percent,sort_order ) VALUES('outcome-1','revision-1','clause-1','pool-1','mark','I','Mark', 'marker','self',1,NULL,'flat',NULL,'instant',NULL,'while_active','current', NULL,0,NULL,NULL,NULL,NULL,0)`, ) ).run(); await ( await database.prepare( `INSERT INTO mechanic_public_variants( source_id,variant_key,selected_revision_id,current_source_fingerprint, authored_contract_version,mechanic_contract_version,status,behavior_text, display_source,updated_at ) VALUES('source-1','I','revision-1',?,1,1,'modeled','Modeled behavior','model','now')`, ) ).run(sourceFingerprint); await database.exec("DELETE FROM mechanic_authored_sources WHERE id='source-1'"); for (const table of [ 'mechanic_authored_variants', 'mechanic_authored_slots', 'mechanic_model_revisions', 'mechanic_model_revision_pools', 'mechanic_model_revision_clauses', 'mechanic_model_revision_outcomes', 'mechanic_model_selections', 'mechanic_public_variants', ]) { expect( await ( await database.prepare(`SELECT COUNT(*) AS count FROM ${table}`) ).get(), ).toMatchObject({ count: 0 }); } }); });