import { test } from 'node:test'; import assert from 'node:assert/strict'; import fs from 'fs'; import os from 'os'; import path from 'path'; // db/client opens (and migrates) the SQLite file under DATA_DIR at import // time, so point it at a throwaway directory before loading anything that // imports it. process.env.DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), 'mkb-ancestors-')); const { db } = require('../db/client') as typeof import('../db/client'); const { rabbits } = require('../db/schema') as typeof import('../db/schema'); const { materializeAncestors, findExistingSubject, mergeIntoExisting } = require('./ancestors') as typeof import('./ancestors'); const { eq } = require('drizzle-orm') as typeof import('drizzle-orm'); type RabbitInsert = typeof rabbits.$inferInsert; const manual = (name: string, extra: Record = {}) => JSON.stringify({ name, farm: 'Test Farm', ear_number: '', weight_lbs: 3, dob: 'Unknown', ...extra }); function save(values: RabbitInsert) { return db.transaction((tx) => { materializeAncestors(tx, values); return tx.insert(rabbits).values(values).returning().get(); }); } // Mirrors POST /api/rabbits: top up an existing rabbit, else create. function submit(values: RabbitInsert) { return db.transaction((tx) => { const existing = findExistingSubject(tx, values); if (existing) return { id: existing.id, report: mergeIntoExisting(tx, existing, values) }; materializeAncestors(tx, values); return { id: tx.insert(rabbits).values(values).returning().get().id, report: null }; }); } const byId = (id: number) => db.select().from(rabbits).where(eq(rabbits.id, id)).get()!; test('typed ancestors become linked reference rabbits', () => { const subject = save({ name: 'Kit One', sireNameText: manual('Big Buck', { ear_number: 'bb1', weight_lbs: 9.5, dob: '2022-01-02' }), damNameText: manual('Nice Doe', { ear_number: 'ND1' }), paternalGrandsireNameText: manual('Grandpa', { ear_number: 'GP1' }), ppGranddamNameText: manual('Great Gran', { ear_number: 'GG1' }), }); assert.ok(subject.sireId && subject.damId); assert.equal(subject.sireNameText, null); assert.equal(subject.paternalGrandsireNameText, null); assert.equal(subject.ppGranddamNameText, null); const sire = byId(subject.sireId!); assert.equal(sire.name, 'Big Buck'); assert.equal(sire.sex, 'buck'); assert.equal(sire.status, 'other'); assert.equal(sire.earNumber, 'BB1'); assert.equal(sire.weightLbs, 9.5); assert.equal(sire.dob, '2022-01-02'); assert.equal(sire.damId, null); const grandpa = byId(sire.sireId!); assert.equal(grandpa.name, 'Grandpa'); assert.equal(byId(grandpa.damId!).name, 'Great Gran'); assert.equal(byId(grandpa.damId!).sex, 'doe'); const dam = byId(subject.damId!); assert.equal(dam.weightLbs, 3); assert.equal(dam.dob, null); // "Unknown" isn't a date }); test('a sibling reuses the ancestors already created', () => { const before = db.select().from(rabbits).all().length; const sibling = save({ name: 'Kit Two', sireNameText: manual('Big Buck', { ear_number: 'BB1' }), damNameText: manual('Nice Doe', { ear_number: 'nd1' }), }); assert.equal(db.select().from(rabbits).all().length, before + 1); // only the sibling itself const first = db.select().from(rabbits).where(eq(rabbits.name, 'Kit One')).get()!; assert.equal(sibling.sireId, first.sireId); assert.equal(sibling.damId, first.damId); }); test('a line-bred pedigree repeating an animal creates it once', () => { const subject = save({ name: 'Line Bred', sireNameText: manual('Son', { ear_number: 'S1' }), damNameText: manual('Daughter', { ear_number: 'D1' }), paternalGrandsireNameText: manual('Patriarch', { ear_number: 'PAT' }), maternalGrandsireNameText: manual('Patriarch', { ear_number: 'PAT' }), }); const sire = byId(subject.sireId!); const dam = byId(subject.damId!); assert.equal(sire.sireId, dam.sireId); }); test('name + farm matches only without a conflicting ear number; linked parents are untouched', () => { const sameName = save({ name: 'Child A', sireNameText: manual('Common Name', { ear_number: 'X1' }) }); const otherEar = save({ name: 'Child B', sireNameText: manual('Common Name', { ear_number: 'X2' }) }); assert.notEqual(sameName.sireId, otherEar.sireId); const linked = save({ name: 'Child C', sireId: sameName.sireId, sireNameText: manual('Ignored') }); assert.equal(linked.sireId, sameName.sireId); assert.equal(db.select().from(rabbits).where(eq(rabbits.name, 'Ignored')).all().length, 0); }); test('an Unknown parent keeps grandparent text on the rabbit', () => { const subject = save({ name: 'Partial', paternalGrandsireNameText: manual('Lonely Grandsire') }); assert.equal(subject.sireId ?? null, null); assert.ok(subject.paternalGrandsireNameText); }); test('adding an existing rabbit again fills gaps and adds missing ancestors, never overwrites', () => { const first = submit({ name: 'Repeat', farm: 'Home', earNumber: 'RP1', breed: 'Holland Lop', sex: 'doe' }); assert.equal(first.report, null); const again = submit({ name: 'Repeat', farm: 'Home', earNumber: 'rp1', breed: 'Mini Rex', // already set: must not change colorName: 'Broken Black', weightLbs: 3.5, sireNameText: manual('Repeat Sire', { ear_number: 'RS1' }), paternalGrandsireNameText: manual('Repeat Grandsire', { ear_number: 'RG1' }), }); assert.equal(again.id, first.id); const row = byId(first.id); assert.equal(row.breed, 'Holland Lop'); assert.equal(row.colorName, 'Broken Black'); assert.equal(row.weightLbs, 3.5); assert.equal(byId(row.sireId!).name, 'Repeat Sire'); assert.equal(byId(byId(row.sireId!).sireId!).name, 'Repeat Grandsire'); assert.equal(again.report!.ancestorsAdded, 2); assert.deepEqual(again.report!.filled.sort(), ['colorName', 'weightLbs']); // A third pass adds the sire's dam to the existing sire record. const third = submit({ name: 'Repeat', earNumber: 'RP1', farm: 'Home', sireNameText: manual('Repeat Sire', { ear_number: 'RS1', dob: '2021-05-06' }), paternalGranddamNameText: manual('Repeat Granddam', { ear_number: 'RD1' }), }); const sire = byId(byId(first.id).sireId!); assert.equal(sire.dob, '2021-05-06'); assert.equal(byId(sire.damId!).name, 'Repeat Granddam'); assert.equal(third.report!.ancestorsUpdated, 1); }); test('adding a rabbit that exists only as a reference ancestor promotes it', () => { const kit = submit({ name: 'Promo Kit', damNameText: manual('Promo Doe', { ear_number: 'PD1' }) }); const damId = byId(kit.id).damId!; assert.equal(byId(damId).status, 'other'); const added = submit({ name: 'Promo Doe', farm: 'Test Farm', earNumber: 'PD1', status: 'active', sex: 'doe' }); assert.equal(added.id, damId); assert.equal(byId(damId).status, 'active'); }); test('a different existing parent is kept and reported as a conflict', () => { submit({ name: 'Conflicted', earNumber: 'CF1', sireNameText: manual('Real Sire', { ear_number: 'RSX' }) }); const again = submit({ name: 'Conflicted', earNumber: 'CF1', sireNameText: manual('Other Sire', { ear_number: 'OSX' }) }); assert.equal(byId(byId(again.id).sireId!).name, 'Real Sire'); assert.equal(again.report!.conflicts.length, 1); }); test('when an ear number is shared, the herd rabbit wins over a reference record', () => { const ref = db.insert(rabbits).values({ name: 'Dup', earNumber: 'DUP1', status: 'other' }).returning().get(); const herd = db.insert(rabbits).values({ name: 'Dup', earNumber: 'DUP1', status: 'active' }).returning().get(); const kit = submit({ name: 'Dup Kit', sireNameText: manual('Dup', { ear_number: 'DUP1' }) }); assert.equal(byId(kit.id).sireId, herd.id); assert.notEqual(byId(kit.id).sireId, ref.id); }); test('same name and farm but a different DOB is a different rabbit', () => { const a = submit({ name: 'Doe 1', farm: 'Kits', dob: '2025-01-01' }); const b = submit({ name: 'Doe 1', farm: 'Kits', dob: '2025-06-01' }); assert.notEqual(a.id, b.id); });