All checks were successful
Quality gates / quality (pull_request) Successful in 1m20s
110 lines
4.1 KiB
JavaScript
110 lines
4.1 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
|
|
import {
|
|
bucketForBristolType,
|
|
buildTimmySummary,
|
|
detectUrgentFlags,
|
|
detectUrgentText,
|
|
exportLedger,
|
|
hasUrgentLedgerContext,
|
|
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('detects common urgent symptom language without matching unrelated blood wording', () => {
|
|
for (const message of [
|
|
'I have rectal bleeding',
|
|
'There is blood in my stool',
|
|
'My stool is black',
|
|
'I have severe stomach pain',
|
|
'I am throwing up and have a fever',
|
|
'I threw up',
|
|
'I am unable to pass gas',
|
|
]) assert.equal(detectUrgentText(message).urgent, true, message);
|
|
assert.equal(detectUrgentText('My blood pressure was checked').urgent, false);
|
|
});
|
|
|
|
test('detects urgent flags or language in confirmed ledger context', () => {
|
|
assert.equal(hasUrgentLedgerContext([{ symptoms: { cannotPassGas: true }, note: '' }]), true);
|
|
assert.equal(hasUrgentLedgerContext([{ symptoms: {}, note: 'I have rectal bleeding' }]), true);
|
|
assert.equal(hasUrgentLedgerContext([{ symptoms: {}, note: 'ordinary entry' }]), false);
|
|
});
|
|
|
|
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);
|
|
});
|