'use strict'; // Mock the Anthropic SDK before any module that requires it is loaded. let mockCreate; jest.mock('@anthropic-ai/sdk', () => { mockCreate = jest.fn(); return jest.fn().mockImplementation(() => ({ messages: { create: mockCreate }, })); }); jest.mock('../config', () => ({ anthropicApiKey: 'test-key', })); const { classifyMessage } = require('../core/taskClassifier'); function makeResponse(jsonPayload) { return { content: [{ text: JSON.stringify(jsonPayload) }] }; } const SAMPLE_PROFILE = { name: 'Alice', os_type: 'Windows', vocabulary_level: 'basic', comfort_level: 2, }; beforeEach(() => { jest.clearAllMocks(); }); describe('classifyMessage — correct task type classification', () => { test('returns learn_skill for learning requests', async () => { mockCreate.mockResolvedValue(makeResponse({ taskType: 'learn_skill', topic: 'sending email', urgency: 'low' })); const result = await classifyMessage('How do I send an email?', SAMPLE_PROFILE); expect(result.taskType).toBe('learn_skill'); }); test('returns troubleshoot for error/problem messages', async () => { mockCreate.mockResolvedValue(makeResponse({ taskType: 'troubleshoot', topic: 'computer not turning on', urgency: 'high' })); const result = await classifyMessage("My computer won't turn on.", SAMPLE_PROFILE); expect(result.taskType).toBe('troubleshoot'); }); test('returns follow_up for "I did that" type messages', async () => { mockCreate.mockResolvedValue(makeResponse({ taskType: 'follow_up', topic: 'follow-up step', urgency: 'low' })); const result = await classifyMessage('I did that, now what?', SAMPLE_PROFILE); expect(result.taskType).toBe('follow_up'); }); test('returns accessibility for accessibility requests', async () => { mockCreate.mockResolvedValue(makeResponse({ taskType: 'accessibility', topic: 'text too small', urgency: 'medium' })); const result = await classifyMessage('The text is too small for me to read.', SAMPLE_PROFILE); expect(result.taskType).toBe('accessibility'); }); test('returns unknown for unrelated messages', async () => { mockCreate.mockResolvedValue(makeResponse({ taskType: 'unknown', topic: 'unrelated topic', urgency: 'low' })); const result = await classifyMessage('What is the weather like today?', SAMPLE_PROFILE); expect(result.taskType).toBe('unknown'); }); }); describe('classifyMessage — topic and urgency passthrough', () => { test('returns the topic', async () => { mockCreate.mockResolvedValue(makeResponse({ taskType: 'learn_skill', topic: 'printing a document', urgency: 'low' })); const result = await classifyMessage('How do I print?', SAMPLE_PROFILE); expect(result.topic).toBe('printing a document'); }); test('returns the urgency', async () => { mockCreate.mockResolvedValue(makeResponse({ taskType: 'troubleshoot', topic: 'screen blank', urgency: 'high' })); const result = await classifyMessage('My screen is blank!', SAMPLE_PROFILE); expect(result.urgency).toBe('high'); }); }); describe('classifyMessage — error handling', () => { test('returns fallback when API throws', async () => { mockCreate.mockRejectedValue(new Error('Network error')); const result = await classifyMessage('Something is wrong.', SAMPLE_PROFILE); expect(result).toEqual({ taskType: 'unknown', topic: 'unclassified', urgency: 'low' }); }); test('returns fallback for malformed JSON', async () => { mockCreate.mockResolvedValue({ content: [{ text: 'not json' }] }); const result = await classifyMessage('Help me.', SAMPLE_PROFILE); expect(result).toEqual({ taskType: 'unknown', topic: 'unclassified', urgency: 'low' }); }); test('returns fallback for missing fields', async () => { mockCreate.mockResolvedValue({ content: [{ text: '{"foo": "bar"}' }] }); const result = await classifyMessage('Help me.', SAMPLE_PROFILE); expect(result.taskType).toBe('unknown'); }); test('returns fallback for empty content', async () => { mockCreate.mockResolvedValue({ content: [] }); const result = await classifyMessage('Help me.', SAMPLE_PROFILE); expect(result).toEqual({ taskType: 'unknown', topic: 'unclassified', urgency: 'low' }); }); }); describe('classifyMessage — validation', () => { const VALID_TYPES = ['learn_skill', 'troubleshoot', 'follow_up', 'accessibility', 'unknown']; test.each(VALID_TYPES)('accepts valid taskType "%s"', async (type) => { mockCreate.mockResolvedValue(makeResponse({ taskType: type, topic: 'test', urgency: 'low' })); const result = await classifyMessage('Test.', SAMPLE_PROFILE); expect(result.taskType).toBe(type); }); test('maps unrecognized taskType to unknown', async () => { mockCreate.mockResolvedValue(makeResponse({ taskType: 'invented', topic: 'test', urgency: 'low' })); const result = await classifyMessage('Test.', SAMPLE_PROFILE); expect(result.taskType).toBe('unknown'); }); test('maps unrecognized urgency to low', async () => { mockCreate.mockResolvedValue(makeResponse({ taskType: 'troubleshoot', topic: 'test', urgency: 'critical' })); const result = await classifyMessage('Test.', SAMPLE_PROFILE); expect(result.urgency).toBe('low'); }); }); describe('classifyMessage — null/undefined profile', () => { test('works with null profile', async () => { mockCreate.mockResolvedValue(makeResponse({ taskType: 'learn_skill', topic: 'email', urgency: 'low' })); const result = await classifyMessage('How do I send an email?', null); expect(result.taskType).toBe('learn_skill'); }); test('works with undefined profile', async () => { mockCreate.mockResolvedValue(makeResponse({ taskType: 'troubleshoot', topic: 'printer', urgency: 'medium' })); const result = await classifyMessage('My printer is broken.', undefined); expect(result.taskType).toBe('troubleshoot'); }); }); describe('classifyMessage — markdown stripping', () => { test('handles ```json fences', async () => { const payload = { taskType: 'troubleshoot', topic: 'wifi', urgency: 'high' }; mockCreate.mockResolvedValue({ content: [{ text: '```json\n' + JSON.stringify(payload) + '\n```' }] }); const result = await classifyMessage('WiFi dropping.', SAMPLE_PROFILE); expect(result.taskType).toBe('troubleshoot'); }); test('handles plain ``` fences', async () => { const payload = { taskType: 'learn_skill', topic: 'zoom', urgency: 'low' }; mockCreate.mockResolvedValue({ content: [{ text: '```\n' + JSON.stringify(payload) + '\n```' }] }); const result = await classifyMessage('How do I use Zoom?', SAMPLE_PROFILE); expect(result.taskType).toBe('learn_skill'); }); });