import { SkillRegistry, DEFAULT_SKILLS } from '@/lib/assistant/skills/SkillRegistry'; /** * The SkillRegistry joins the rules (code) with the settings (the backend * agent registry, /admin/ai/skills). Rows below are shaped exactly like that * endpoint's response. */ const row = (skillid, enabled, thresholds = {}) => ({ skillid, enabled, thresholds }); describe('SkillRegistry', () => { beforeEach(() => { SkillRegistry.clearRegistryConfig(); }); it('registers the eight built-in logistics skills', () => { const ids = SkillRegistry.getAllSkills().map((s) => s.id); expect(ids).toHaveLength(8); expect(ids).toEqual(DEFAULT_SKILLS.map((s) => s.id)); expect(ids).toContain('skill_sla_guardian'); expect(ids).toContain('skill_rider_battery_safety'); }); it('runs on code defaults until the registry answers, and says so', () => { expect(SkillRegistry.getSource()).toBe('defaults'); const sla = SkillRegistry.getSkill('skill_sla_guardian'); expect(sla.enabled).toBe(true); expect(sla.effectiveThresholds.slaRiskWindowMin.currentValue).toBe(45); }); it('takes enabled and thresholds from the registry', () => { SkillRegistry.applyRegistryConfig([ row('skill_fleet_balancer', false), row('skill_sla_guardian', true, { slaRiskWindowMin: 30 }) ]); expect(SkillRegistry.getSource()).toBe('registry'); expect(SkillRegistry.getSkill('skill_fleet_balancer').enabled).toBe(false); expect(SkillRegistry.getSkill('skill_sla_guardian').effectiveThresholds.slaRiskWindowMin.currentValue).toBe(30); // A threshold the registry did not send keeps its default. expect(SkillRegistry.getSkill('skill_sla_guardian').effectiveThresholds.unassignedAgingMin.currentValue).toBe(60); }); it('can switch on a skill that is off by default', () => { SkillRegistry.applyRegistryConfig([row('skill_cash_exposure', true)]); expect(SkillRegistry.getSkill('skill_cash_exposure').enabled).toBe(true); }); it('ignores an out-of-range stored value rather than running on it', () => { SkillRegistry.applyRegistryConfig([row('skill_doorstep_stall', true, { arrivedStalledMin: 999 })]); expect(SkillRegistry.getSkill('skill_doorstep_stall').effectiveThresholds.arrivedStalledMin.currentValue).toBe(20); }); it('ignores registry rows for skills this console does not implement', () => { SkillRegistry.applyRegistryConfig([row('stall_response', false), row('custom_night_shift_watch', true)]); expect(SkillRegistry.getAllSkills()).toHaveLength(8); expect(SkillRegistry.getSkill('stall_response')).toBeUndefined(); }); it('goes back to defaults when the registry is lost', () => { SkillRegistry.applyRegistryConfig([row('skill_sla_guardian', false, { slaRiskWindowMin: 15 })]); SkillRegistry.clearRegistryConfig(); const sla = SkillRegistry.getSkill('skill_sla_guardian'); expect(SkillRegistry.getSource()).toBe('defaults'); expect(sla.enabled).toBe(true); expect(sla.effectiveThresholds.slaRiskWindowMin.currentValue).toBe(45); }); it('keeps nothing in browser storage', () => { localStorage.clear(); SkillRegistry.applyRegistryConfig([row('skill_sla_guardian', false)]); expect(localStorage.length).toBe(0); }); it('notifies subscribers when the registry settings change', () => { const listener = jest.fn(); const unsubscribe = SkillRegistry.subscribe(listener); SkillRegistry.applyRegistryConfig([row('skill_doorstep_stall', false)]); expect(listener).toHaveBeenCalledTimes(1); expect(listener.mock.calls[0][0].find((s) => s.id === 'skill_doorstep_stall').enabled).toBe(false); unsubscribe(); }); });