import { BadRequestException } from '@nestjs/common'; import { ListTemplate, UserList, } from '../list-templates/list-template.types'; import { ListTemplatesService } from '../list-templates/list-templates.service'; import { ListsService } from '../lists/lists.service'; import { ListSuggestionAgentService } from './list-suggestion-agent.service'; describe('ListSuggestionAgentService', () => { let listsService: Pick; let listTemplatesService: Pick; let service: ListSuggestionAgentService; beforeEach(() => { listsService = { listLists: jest.fn(), createList: jest.fn(), }; listTemplatesService = { listTemplates: jest.fn(), }; service = new ListSuggestionAgentService( listsService as ListsService, listTemplatesService as ListTemplatesService, ); }); it('suggests read-only list ideas from matching templates', async () => { jest.mocked(listsService.listLists).mockResolvedValue([ list({ name: 'Urlaub: Sommerurlaub' }), ]); jest.mocked(listTemplatesService.listTemplates).mockResolvedValue([ template({ id: 'template-1', name: 'Urlaub', kind: 'packing', items: ['Pass', 'Tickets', 'Ladegeraete'], }), template({ id: 'template-2', name: 'Wocheneinkauf', kind: 'shopping', items: ['Milch'], }), ]); const result = await service.suggestLists('user-1', { goal: 'Sommerurlaub', constraints: ['Handgepaeck beachten'], }); expect(result.suggestions[0]).toEqual( expect.objectContaining({ name: 'Urlaub: Sommerurlaub 2', kind: 'packing', sourceTemplateId: 'template-1', }), ); expect(result.suggestions[0].items[0]).toEqual( expect.objectContaining({ title: 'Handgepaeck beachten', required: true, }), ); expect(listsService.createList).not.toHaveBeenCalled(); }); it('falls back to inferred kind when no template matches', async () => { jest.mocked(listsService.listLists).mockResolvedValue([]); jest.mocked(listTemplatesService.listTemplates).mockResolvedValue([]); const result = await service.suggestLists('user-1', { goal: 'Projektplanung fuer Release', }); expect(result.suggestions[0]).toEqual( expect.objectContaining({ name: 'Projektplanung fuer Release', kind: 'todo', }), ); expect(result.suggestions[0].items).toEqual( expect.arrayContaining([ expect.objectContaining({ title: 'Ziel klaeren' }), ]), ); }); it('rejects empty goals and invalid constraints', async () => { await expect(service.suggestLists('user-1', { goal: ' ' })).rejects.toThrow( BadRequestException, ); await expect( service.suggestLists('user-1', { goal: 'Liste', constraints: 'bad' as never, }), ).rejects.toThrow('Constraints must be an array.'); }); }); function template(options: { id: string; name: string; kind: ListTemplate['kind']; items: string[]; }): ListTemplate { return { id: options.id, ownerId: 'user-1', name: options.name, kind: options.kind, items: options.items.map((title, position) => ({ id: `${options.id}-item-${position}`, title, required: true, position, createdAt: now(), updatedAt: now(), })), createdAt: now(), updatedAt: now(), }; } function list(options: { name: string }): UserList { return { id: 'list-1', ownerId: 'user-1', accessRole: 'owner', name: options.name, kind: 'packing', items: [], collaborators: [], createdAt: now(), updatedAt: now(), }; } function now(): string { return new Date(0).toISOString(); }