import { describe, it, expect, beforeEach } from 'vitest' import { mkdtempSync } from 'fs' import { tmpdir } from 'os' import { join } from 'path' import { openSessionsDb, type SessionsDb, type WorklogCommentRecord } from '../services/sessions/db.js' import { parseWorklogHtml } from '../services/sessions/worklog-import.js' import { detectActionsNeverExecuted, weekRange } from '../services/sessions/patterns.js' const SAMPLE_H4 = `

2026-04-15 10:30 - Refactor API sessions

Projecto: DashDescomplicar

Tarefa: #2059 - Observabilidade Espelho

Duração: ~2h 15m

Trabalho Realizado

Ficheiros Modificados

Problemas / Soluções

Padrões Detectados

Acções Sugeridas

` const SAMPLE_H2 = `

2026-01-31 - Estratégia Stack

Duração: ~2h

Trabalho Realizado

Insights

` const SAMPLE_D33 = `

Origem: Sessão 2026-02-02

Prioridade: P1

` describe('parseWorklogHtml', () => { it('extrai campos de comentário formato

', () => { const parsed = parseWorklogHtml(SAMPLE_H4, { id: 100, discussion_id: 31, created_at: '' }) expect(parsed.id).toBe(100) expect(parsed.title).toMatch(/2026-04-15/) expect(parsed.task_ref).toBe('#2059') expect(parsed.duration_sec).toBe(2 * 3600 + 15 * 60) expect(parsed.work_items.length).toBe(2) expect(parsed.files_modified.length).toBe(2) expect(parsed.patterns_text.length).toBe(1) expect(parsed.actions.length).toBe(1) expect(parsed.actions[0].tipo).toBe('Refactor') expect(parsed.actions[0].prioridade).toBe('P2') expect(parsed.created_at.startsWith('2026-04-15')).toBe(true) }) it('extrai campos de comentário formato

/

(legacy)', () => { const parsed = parseWorklogHtml(SAMPLE_H2, { id: 64, discussion_id: 31, created_at: '' }) expect(parsed.title).toMatch(/2026-01-31/) expect(parsed.work_items.length).toBeGreaterThanOrEqual(1) expect(parsed.duration_sec).toBe(2 * 3600) expect(parsed.created_at.startsWith('2026-01-31')).toBe(true) }) it('extrai acções em formato discussão #33 (lista crua)', () => { const parsed = parseWorklogHtml(SAMPLE_D33, { id: 200, discussion_id: 33, created_at: '2026-02-02T00:00:00Z' }) expect(parsed.actions.length).toBe(1) expect(parsed.actions[0].tipo).toBe('MCP') }) }) describe('upsertWorklogComment idempotência', () => { let db: SessionsDb beforeEach(() => { const dir = mkdtempSync(join(tmpdir(), 'obs-wl-')) db = openSessionsDb(join(dir, 'sessions.db')) }) it('insert primeiro, update depois', () => { const base: WorklogCommentRecord = { id: 42, discussion_id: 31, created_at: '2026-04-15T10:30:00Z', staff_id: 25, title: 'Test', task_ref: '#100', duration_sec: 600, work_items: ['a'], files_modified: [], problems: [], patterns_text: [], actions: [], raw_html: '

Test

', imported_at: '2026-04-23T00:00:00Z', } const r1 = db.upsertWorklogComment(base) expect(r1.inserted).toBe(true) expect(db.countWorklogComments()).toBe(1) const r2 = db.upsertWorklogComment({ ...base, title: 'Updated' }) expect(r2.inserted).toBe(false) expect(db.countWorklogComments()).toBe(1) const list = db.listWorklogComments({ discussion_id: 31 }) expect(list[0].title).toBe('Updated') }) }) describe('detectActionsNeverExecuted', () => { let db: SessionsDb beforeEach(() => { const dir = mkdtempSync(join(tmpdir(), 'obs-act-')) db = openSessionsDb(join(dir, 'sessions.db')) }) it('sinaliza acções P1/P2 antigas sem execução', () => { const old = new Date('2026-03-01T00:00:00Z').toISOString() for (let i = 0; i < 4; i++) { db.upsertWorklogComment({ id: 300 + i, discussion_id: 33, created_at: old, staff_id: 25, title: `Acção ${i}`, task_ref: `#${1000 + i}`, duration_sec: null, work_items: [], files_modified: [], problems: [], patterns_text: [], actions: [{ tipo: 'MCP', descricao: `Corrigir bug X${i}`, prioridade: i % 2 ? 'P1' : 'P2' }], raw_html: '', imported_at: '2026-04-23T00:00:00Z', }) } const range = weekRange(new Date('2026-04-22T00:00:00Z')) const patterns = detectActionsNeverExecuted({ db: db.rawDb(), weekStartIso: range.start.toISOString(), weekEndIso: range.end.toISOString(), }) expect(patterns.length).toBe(1) expect(patterns[0].pattern_key).toBe('actions_never_executed') expect(patterns[0].affected_count).toBeGreaterThanOrEqual(3) }) it('não sinaliza se acções recentes (<14 dias)', () => { const recent = new Date().toISOString() for (let i = 0; i < 5; i++) { db.upsertWorklogComment({ id: 400 + i, discussion_id: 33, created_at: recent, staff_id: 25, title: null, task_ref: null, duration_sec: null, work_items: [], files_modified: [], problems: [], patterns_text: [], actions: [{ tipo: 'MCP', descricao: 'x', prioridade: 'P1' }], raw_html: '', imported_at: recent, }) } const range = weekRange(new Date()) const patterns = detectActionsNeverExecuted({ db: db.rawDb(), weekStartIso: range.start.toISOString(), weekEndIso: range.end.toISOString(), }) expect(patterns.length).toBe(0) }) })