import test from 'node:test'; import assert from 'node:assert/strict'; import { bucketForBristolType, buildTimmySummary, detectUrgentFlags, exportLedger, photoQualityMessage, sanitizeEntry, } from '../src/domain.js'; test('maps Bristol types to clinically grounded buckets', () => { assert.equal(bucketForBristolType(1), 'constipation'); assert.equal(bucketForBristolType(2), 'constipation'); assert.equal(bucketForBristolType(3), 'typical'); assert.equal(bucketForBristolType(4), 'typical'); assert.equal(bucketForBristolType(5), 'loose'); assert.equal(bucketForBristolType(7), 'loose'); assert.equal(bucketForBristolType(0), 'unknown'); }); test('escalates reported blood, black stool, severe pain, vomiting, fever, or inability to pass gas', () => { const result = detectUrgentFlags({ blood: true, blackOrDarkRed: false, severePain: true, vomiting: false, fever: false, cannotPassGas: false, }); assert.equal(result.urgent, true); assert.deepEqual(result.flags, ['blood', 'severePain']); assert.match(result.message, /medical care/i); }); test('does not invent reassurance when no urgent flags are reported', () => { const result = detectUrgentFlags({}); assert.equal(result.urgent, false); assert.deepEqual(result.flags, []); assert.match(result.message, /not a diagnosis/i); }); test('Timmy summary reports patterns without clearing food or diagnosing disease', () => { const entries = [ { bristolType: 3, occurredAt: '2026-08-15T08:00:00.000Z' }, { bristolType: 4, occurredAt: '2026-08-16T08:00:00.000Z' }, { bristolType: 6, occurredAt: '2026-08-17T08:00:00.000Z' }, ]; const summary = buildTimmySummary(entries); assert.match(summary, /3 logs/); assert.match(summary, /2 typical/); assert.doesNotMatch(summary, /safe|diagnos|Taco Bell|clear/i); }); test('sanitizes a user entry to the MVP data contract', () => { const entry = sanitizeEntry({ id: 'abc', occurredAt: '2026-08-17T12:00:00.000Z', bristolType: 4, color: 'brown', urgency: 2, discomfort: 1, note: 'After lunch', photoDataUrl: 'data:image/jpeg;base64,abc', unexpected: 'drop me', }); assert.deepEqual(Object.keys(entry).sort(), [ 'bristolType', 'color', 'discomfort', 'id', 'note', 'occurredAt', 'photoDataUrl', 'symptoms', 'urgency' ].sort()); assert.equal(entry.unexpected, undefined); }); test('photo quality guidance is deterministic and does not claim visual diagnosis', () => { assert.match(photoQualityMessage({ width: 300, height: 300, brightness: 0.5 }), /closer/i); assert.match(photoQualityMessage({ width: 1200, height: 900, brightness: 0.02 }), /light/i); assert.match(photoQualityMessage({ width: 1200, height: 900, brightness: 0.5 }), /review/i); assert.doesNotMatch(photoQualityMessage({ width: 1200, height: 900, brightness: 0.5 }), /type [1-7]|disease|diagnos/i); }); test('export ledger is portable JSON with version and entries', () => { const text = exportLedger([{ id: 'a', bristolType: 4 }], '2026-08-18T00:00:00.000Z'); const parsed = JSON.parse(text); assert.equal(parsed.schemaVersion, 1); assert.equal(parsed.exportedAt, '2026-08-18T00:00:00.000Z'); assert.equal(parsed.entries.length, 1); });