import { describe, expect, test } from 'bun:test' function template(overrides: Record) { return { id: 'id', name: '', description: '', type: 'canvas', useCases: [], industries: [], utilities: [], isGlobal: false, slots: [], schema: null, graph: null, variables: null, configuration: null, previewImage: null, promptId: 'p', promptVersionId: 'pv', workspaceId: 'w', createdAt: '', updatedAt: '', ...overrides, } as never } const catalog = [ template({ id: 'doc', name: 'Leitura de documento', type: 'canvas', utilities: ['pdf-treatment'], industries: ['finance'], useCases: ['Extração de dados de documentos', 'Leitura de notas fiscais, contratos e formulários'] }), template({ id: 'route', name: 'Classificar e encaminhar', type: 'workflow', industries: ['operations'], useCases: ['Roteamento automático de solicitações'] }), template({ id: 'text', name: 'Texto padronizado', type: 'canvas', industries: ['legal'], useCases: ['Geração de comunicados e cartas padrão'] }), ] describe('recommendTemplates', () => { test('ranks the template whose use case matches the description', async () => { const { recommendTemplates } = await import('./templates.ts') const results = await recommendTemplates('extrair dados de notas fiscais', { templates: catalog }) expect(results[0]!.template.id).toBe('doc') expect(results[0]!.score).toBeGreaterThan(0) }) test('surfaces the matching use case as the reason', async () => { const { recommendTemplates } = await import('./templates.ts') const results = await recommendTemplates('roteamento de solicitações', { templates: catalog }) expect(results[0]!.template.id).toBe('route') expect(results[0]!.reason).toContain('Roteamento') }) test('filters by kind when the path is already decided', async () => { const { recommendTemplates } = await import('./templates.ts') const results = await recommendTemplates('classificar solicitações', { templates: catalog, kind: 'canvas' }) expect(results.every(r => r.template.type === 'canvas')).toBe(true) }) test('matches on utility tags', async () => { const { recommendTemplates } = await import('./templates.ts') const results = await recommendTemplates('preciso tratar pdf', { templates: catalog }) expect(results[0]!.template.id).toBe('doc') }) test('returns nothing when the description shares no vocabulary', async () => { const { recommendTemplates } = await import('./templates.ts') expect(await recommendTemplates('kubernetes cluster autoscaling', { templates: catalog })).toEqual([]) }) test('returns nothing for a description made only of stop words', async () => { const { recommendTemplates } = await import('./templates.ts') expect(await recommendTemplates('quero criar um de para', { templates: catalog })).toEqual([]) }) test('ignores accents so "extracao" matches "Extração"', async () => { const { recommendTemplates } = await import('./templates.ts') const results = await recommendTemplates('extracao de documentos', { templates: catalog }) expect(results[0]!.template.id).toBe('doc') }) test('caps the shortlist', async () => { const { recommendTemplates } = await import('./templates.ts') const results = await recommendTemplates('documentos solicitações cartas', { templates: catalog, limit: 2 }) expect(results.length).toBeLessThanOrEqual(2) }) }) describe('isAmbiguousRecommendation', () => { test('flags a near tie so the agent asks instead of guessing', async () => { const { isAmbiguousRecommendation } = await import('./templates.ts') expect(isAmbiguousRecommendation([ { template: template({}), score: 0.30, reason: '' }, { template: template({}), score: 0.28, reason: '' }, ])).toBe(true) }) test('does not flag a clear winner', async () => { const { isAmbiguousRecommendation } = await import('./templates.ts') expect(isAmbiguousRecommendation([ { template: template({}), score: 0.60, reason: '' }, { template: template({}), score: 0.20, reason: '' }, ])).toBe(false) }) test('a single candidate is never ambiguous', async () => { const { isAmbiguousRecommendation } = await import('./templates.ts') expect(isAmbiguousRecommendation([{ template: template({}), score: 0.1, reason: '' }])).toBe(false) }) }) describe('recommendTemplates weighting', () => { test('a use-case match outranks a bare tag match', async () => { const { recommendTemplates } = await import('./templates.ts') const results = await recommendTemplates('extração de documentos pdf', { templates: [ template({ id: 'tagonly', name: 'Outro', utilities: ['pdf-treatment'], useCases: ['Coisa sem relação'], industries: ['ops'] }), template({ id: 'usecase', name: 'Leitura', useCases: ['Extração de dados de documentos'], industries: ['ops'] }), ], }) expect(results[0]!.template.id).toBe('usecase') }) }) describe('describeTemplateSlots', () => { test('says so when a template takes no inputs', async () => { const { describeTemplateSlots } = await import('./templates.ts') expect(describeTemplateSlots(template({ name: 'Simples', slots: [] }))) .toContain('takes no inputs') }) test('describes a text slot with its requiredness', async () => { const { describeTemplateSlots } = await import('./templates.ts') const output = describeTemplateSlots(template({ name: 'T', slots: [{ key: 'regras', type: 'text', label: 'Regras', required: true, multiline: true, description: 'Uma por linha' }], })) expect(output).toContain('regras (required, multiline text)') expect(output).toContain('Uma por linha') }) test('describes a list slot with its columns', async () => { const { describeTemplateSlots } = await import('./templates.ts') const output = describeTemplateSlots(template({ name: 'T', slots: [{ key: 'campos', type: 'list', label: 'Campos', columns: [{ key: 'nome', label: 'Nome' }, { key: 'descricao', label: 'Descrição' }] }], })) expect(output).toContain('campos (optional, list of rows (nome, descricao))') }) })