import { describe, it, expect } from 'vitest' import { parseSessionFile } from '../services/sessions/parser.js' import { mkdtempSync, writeFileSync } from 'fs' import { tmpdir } from 'os' import { join } from 'path' function writeJsonl(lines: object[]): string { const dir = mkdtempSync(join(tmpdir(), 'obs-test-')) const path = join(dir, 'session-test.jsonl') writeFileSync(path, lines.map((l) => JSON.stringify(l)).join('\n')) return path } describe('parseSessionFile', () => { it('extrai metadata básica de sessão mínima', async () => { const path = writeJsonl([ { type: 'permission-mode', permissionMode: 'default', sessionId: 's1' }, { type: 'user', timestamp: '2026-04-23T10:00:00Z', message: { role: 'user', content: [{ type: 'text', text: 'olá mundo' }] }, }, { type: 'assistant', timestamp: '2026-04-23T10:00:30Z', message: { role: 'assistant', content: [{ type: 'text', text: 'olá' }] }, }, ]) const result = await parseSessionFile(path) expect(result.meta.session_id).toBe('s1') expect(result.meta.user_messages).toBe(1) expect(result.meta.assistant_msgs).toBe(1) expect(result.meta.tool_calls).toBe(0) expect(result.meta.first_prompt).toBe('olá mundo') expect(result.meta.permission_mode).toBe('default') expect(result.meta.outcome).toBe('completed') }) it('conta tool_calls e recolhe tools_used', async () => { const path = writeJsonl([ { type: 'assistant', timestamp: '2026-04-23T10:00:00Z', message: { role: 'assistant', content: [ { type: 'tool_use', name: 'Bash', input: { command: 'ls' } }, { type: 'tool_use', name: 'Read', input: { file_path: '/tmp/x' } }, ], }, }, ]) const result = await parseSessionFile(path) expect(result.meta.tool_calls).toBe(2) expect(result.meta.tools_used).toEqual(expect.arrayContaining(['Bash', 'Read'])) }) it('detecta skill invocation em system-reminder', async () => { const path = writeJsonl([ { type: 'system', timestamp: '2026-04-23T10:00:00Z', message: { role: 'system', content: [{ type: 'text', text: 'Launching skill: superpowers:brainstorming' }] }, }, ]) const result = await parseSessionFile(path) expect(result.meta.skills_invoked).toContain('superpowers:brainstorming') }) it('ignora linhas JSON inválidas silenciosamente', async () => { const path = writeJsonl([ { type: 'user', message: { role: 'user', content: [{ type: 'text', text: 'válido' }] } }, ]) const { writeFileSync } = await import('fs') writeFileSync(path, 'linha inválida\n' + JSON.stringify({ type: 'user', message: { role: 'user', content: [{ type: 'text', text: 'válido' }] } })) const result = await parseSessionFile(path) expect(result.meta.user_messages).toBe(1) }) it('devolve duration_sec null quando timestamps são inválidos', async () => { const path = writeJsonl([ { type: 'user', timestamp: 'not-a-date', message: { role: 'user', content: [{ type: 'text', text: 'a' }] } }, { type: 'assistant', timestamp: 'also-not-a-date', message: { role: 'assistant', content: [{ type: 'text', text: 'b' }] } }, ]) const result = await parseSessionFile(path) expect(result.meta.duration_sec).toBeNull() }) it('classifica max_tokens como interrupted', async () => { const path = writeJsonl([ { type: 'assistant', timestamp: '2026-04-23T10:00:00Z', message: { role: 'assistant', content: [{ type: 'text', text: 'resposta cortada' }], stop_reason: 'max_tokens' }, }, ]) const result = await parseSessionFile(path) expect(result.meta.outcome).toBe('interrupted') }) })