Files
listify/listify-api/src/mcp/list-suggestion-agent.service.spec.ts
Bastian Wagner c8603be226 mcp support
2026-06-11 11:19:07 +02:00

144 lines
3.8 KiB
TypeScript

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<ListsService, 'listLists' | 'createList'>;
let listTemplatesService: Pick<ListTemplatesService, 'listTemplates'>;
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();
}