Files
story/web/test/home-preparation.test.mjs
T
Codex Local bf1233d5df
ci / verify (push) Canceled after 0s
feat: expand companion platform, device controls, and memoir workflows
Add customer and operator login, BLE provisioning, device fleet controls, call memory, memoir generation, and refreshed family and maintenance portals. Include API contracts, migrations, firmware sources, tests, assets, and deployment documentation.
2026-10-08 10:06:44 +08:00

102 lines
5.1 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import { homePreparation } from '../src/components/home/home-model.ts';
const check = (code, status, blocking = true, action) => ({ code, status, blocking, action, title: code, detail: `${code} details` });
const readiness = (checks, extra = {}) => ({ subject_id: 'elder-a', ready: !checks.some(item => item.blocking && item.status !== 'ready'), checks, devices: [], evaluated_at: '2026-10-05T00:00:00Z', start_time_checks: ['usage_quota', 'runtime_routing', 'no_active_conversation'], ...extra });
test('an elder is required before subject-scoped preparation can be checked', () => {
const result = homePreparation(undefined, readiness([check('VOICE_CONSENT', 'ready')]));
assert.equal(result.status, 'needs_subject');
assert.equal(result.destination, 'subjects');
assert.equal(result.checks.length, 0);
});
test('missing, failed and refreshing data never become unfinished setup', () => {
const previous = readiness([check('VOICE_CONSENT', 'blocked', true, 'MANAGE_CONSENTS')]);
for (const [data, state] of [[undefined, undefined], [previous, 'error'], [previous, 'loading']]) {
const result = homePreparation('elder-a', data, state);
assert.equal(result.status, 'unknown');
assert.equal(result.pending, 0);
assert.equal(result.completed, 0);
assert.equal(result.destination, 'retry');
}
});
test('changing elder does not reuse another elder preparation', () => {
const result = homePreparation('elder-b', readiness([check('SUBJECT_ACTIVE', 'ready')]));
assert.equal(result.status, 'unknown');
assert.equal(result.checks.length, 0);
});
test('voice authorization has a direct privacy destination even with a ready device', () => {
const result = homePreparation('elder-a', readiness([
check('DEVICE_BOUND', 'ready'), check('DEVICE_RUNTIME', 'ready'),
check('VOICE_CONSENT', 'blocked', true, 'MANAGE_CONSENTS'),
]));
assert.equal(result.destination, 'privacy');
assert.equal(result.next.code, 'VOICE_CONSENT');
assert.equal(result.pending, 1);
assert.equal(result.completed, 2);
});
test('next action follows remaining server blockers instead of a fixed device-first sequence', () => {
const profile = check('PROFILE_BACKGROUND', 'blocked', true, 'EDIT_PROFILE');
const device = check('DEVICE_BOUND', 'blocked', true, 'BIND_DEVICE');
assert.equal(homePreparation('elder-a', readiness([profile, device])).destination, 'subjects');
assert.equal(homePreparation('elder-a', readiness([device, profile])).destination, 'devices');
const result = homePreparation('elder-a', readiness([device, profile]));
assert.deepEqual(result.checks.map(item => item.destination), ['devices', 'subjects']);
});
test('web binding guidance matches the existing Bluetooth add and assignment flow', () => {
const result = homePreparation('elder-a', readiness([{ ...check('DEVICE_BOUND', 'blocked', true, 'BIND_DEVICE'), detail: '请使用设备屏幕显示的一次性绑定码完成绑定。' }]));
assert.match(result.next.detail, /蓝牙/);
assert.match(result.next.detail, /已添加/);
assert.doesNotMatch(result.next.detail, /绑定码|小程序/);
assert.equal(result.destination, 'devices');
});
test('attention checks remain optional and do not count as blockers', () => {
const result = homePreparation('elder-a', readiness([
check('SUBJECT_ACTIVE', 'ready'), check('VOICE_CONSENT', 'ready'),
check('CONFIG_SYNC', 'attention', false, 'OPEN_DEVICES'),
check('RECENT_CONTACT', 'attention', false, 'OPEN_DEVICES'),
]));
assert.equal(result.status, 'ready');
assert.equal(result.pending, 0);
assert.equal(result.suggestions, 2);
assert.equal(result.next.blocking, false);
assert.equal(result.destination, 'devices');
});
test('a blocker is handled before an earlier optional suggestion', () => {
const result = homePreparation('elder-a', readiness([
check('CONFIG_SYNC', 'attention', false, 'OPEN_DEVICES'),
check('CONVERSATION_GOAL', 'blocked', true, 'EDIT_PROFILE'),
]));
assert.equal(result.next.code, 'CONVERSATION_GOAL');
assert.equal(result.destination, 'subjects');
});
test('completed checks lead to records without inventing memory or recording consent', () => {
const result = homePreparation('elder-a', readiness([check('VOICE_CONSENT', 'ready'), check('DEVICE_BOUND', 'ready')]));
assert.equal(result.status, 'ready');
assert.equal(result.destination, 'conversations');
assert.equal(result.next, undefined);
assert.equal(result.checks.length, 2);
});
test('empty or contradictory payloads do not claim completion', () => {
for (const data of [readiness([]), readiness([check('VOICE_CONSENT', 'ready')], { ready: false }), readiness([check('VOICE_CONSENT', 'blocked')], { ready: true })]) {
assert.equal(homePreparation('elder-a', data).status, 'unknown');
}
});
test('an unknown blocking action does not guess a settings destination', () => {
const result = homePreparation('elder-a', readiness([check('VOICE_CONSENT', 'blocked', true)]));
assert.equal(result.status, 'blocked');
assert.equal(result.destination, 'retry');
assert.equal(result.next.detail, 'VOICE_CONSENT details');
});