import assert from 'node:assert/strict'; import test from 'node:test'; import { Sequelize, DataTypes } from 'sequelize'; import migration from '../migrations-active/20261104000001-add-character-appearance.cjs'; import developmentMigration from '../migrations-active/20261104000000-add-character-development-ages.cjs'; // Only the explicitly supplied disposable database may be changed. test('appearance migration persists colors for existing characters, Sequelize and raw daemon inserts', { skip: !process.env.TEST_DATABASE_URL, }, async () => { const db = new Sequelize(process.env.TEST_DATABASE_URL, { logging: false }); try { const [[{ name }]] = await db.query('SELECT current_database() AS name'); assert.equal(name, 'yourpart_development_test', 'Use the isolated development test database'); await db.query(` DROP SCHEMA IF EXISTS falukant_data CASCADE; CREATE SCHEMA falukant_data; CREATE TABLE falukant_data.character ( id SERIAL PRIMARY KEY, gender TEXT, health INT DEFAULT 100, hair_color VARCHAR(7), updated_at TIMESTAMPTZ DEFAULT NOW() ); INSERT INTO falukant_data.character (gender, hair_color) VALUES ('female', '#AbC123'), ('male', NULL); `); const qi = db.getQueryInterface(); await developmentMigration.up(qi); const [developmentBefore] = await db.query('SELECT id, health, puberty_start_age, fertile_from_age, updated_at FROM falukant_data.character ORDER BY id'); await migration.up(qi); const [developmentAfter] = await db.query('SELECT id, health, puberty_start_age, fertile_from_age, updated_at FROM falukant_data.character ORDER BY id'); assert.deepEqual(developmentAfter, developmentBefore, 'Existing character development remains intact'); const [before] = await db.query('SELECT * FROM falukant_data.character ORDER BY id'); assert.equal(before[0].hair_color, '#AbC123', 'Keep manually prepared values'); for (const row of before) { assert.match(row.hair_color, /^#[0-9a-fA-F]{6}$/); assert.match(row.eye_color, /^#[0-9a-fA-F]{6}$/); } await migration.up(qi); await db.query(`UPDATE falukant_data.character SET hair_color = NULL, eye_color = NULL`); const [after] = await db.query('SELECT * FROM falukant_data.character ORDER BY id'); assert.deepEqual(after, before, 'Repeated migrations and omitted colors keep existing assignments'); // NPCs and newborns created with raw Rust SQL also get stable colors. const [generated] = await db.query(`INSERT INTO falukant_data.character (gender) SELECT CASE WHEN i % 2 = 0 THEN 'female' ELSE 'male' END FROM generate_series(1, 1000) i RETURNING hair_color, eye_color`); assert.equal(new Set(generated.map(c => c.hair_color)).size, 7); assert.equal(new Set(generated.map(c => c.eye_color)).size, 6); assert.ok(generated.every(c => /^#[0-9a-fA-F]{6}$/.test(c.hair_color) && /^#[0-9a-fA-F]{6}$/.test(c.eye_color))); const Character = db.define('AppearanceTestCharacter', { id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true }, gender: DataTypes.STRING, hairColor: { type: DataTypes.STRING(7), field: 'hair_color' }, eyeColor: { type: DataTypes.STRING(7), field: 'eye_color' }, }, { schema: 'falukant_data', tableName: 'character', timestamps: false }); const character = await Character.create({ gender: 'female', eyeColor: '#123456' }); assert.match(character.hairColor, /^#[0-9a-fA-F]{6}$/); assert.equal(character.eyeColor, '#123456'); const assigned = character.hairColor; await character.update({ gender: 'male' }); await character.reload(); assert.equal(character.hairColor, assigned); await character.update({ hairColor: '#654321' }); await character.reload(); assert.equal(character.hairColor, '#654321'); assert.equal(character.eyeColor, '#123456'); for (const color of ['red', '#fff', '#12345z', '']) { await assert.rejects(db.query('UPDATE falukant_data.character SET eye_color = :color WHERE id = 1', { replacements: { color }, })); } const [[{ count: oldCount }]] = await db.query('SELECT count(*)::int FROM falukant_data.character'); await migration.down(qi); const [[{ count: newCount }]] = await db.query('SELECT count(*)::int FROM falukant_data.character'); assert.equal(newCount, oldCount, 'Rollback retains characters'); await migration.up(qi); } finally { await db.close(); } });