import { describe, it, expect, mock } from 'bun:test'; // Mock ModeManager before importing parser (it's used at module load time) mock.module('../../src/services/domain/ModeManager.js', () => ({ ModeManager: { getInstance: () => ({ getActiveMode: () => ({ observation_types: [{ id: 'bugfix' }, { id: 'discovery' }, { id: 'refactor' }], }), }), }, })); import { parseObservations } from '../../src/sdk/parser.js'; describe('parseObservations', () => { it('returns a populated observation when title is present', () => { const xml = ` discovery Found a bug in auth module The token refresh logic skips expired tokens. `; const result = parseObservations(xml); expect(result).toHaveLength(1); expect(result[0].title).toBe('Found a bug in auth module'); expect(result[0].type).toBe('discovery'); expect(result[0].narrative).toBe('The token refresh logic skips expired tokens.'); }); it('returns a populated observation when only narrative is present (no title)', () => { const xml = ` bugfix Patched the null pointer dereference in session handler. `; const result = parseObservations(xml); expect(result).toHaveLength(1); expect(result[0].title).toBeNull(); expect(result[0].narrative).toBe('Patched the null pointer dereference in session handler.'); }); it('returns a populated observation when only facts are present', () => { const xml = ` discovery File limit is hardcoded to 5 `; const result = parseObservations(xml); expect(result).toHaveLength(1); expect(result[0].facts).toEqual(['File limit is hardcoded to 5']); }); it('returns a populated observation when only concepts are present', () => { const xml = ` refactor dependency-injection `; const result = parseObservations(xml); expect(result).toHaveLength(1); expect(result[0].concepts).toEqual(['dependency-injection']); }); // Regression test for issue #1625: // Ghost observations (all content fields null/empty) must be filtered out. it('filters out ghost observations where all content fields are null (#1625)', () => { const xml = ` bugfix `; const result = parseObservations(xml); expect(result).toHaveLength(0); }); it('filters out ghost observation with empty tags but no text content (#1625)', () => { const xml = ` discovery `; const result = parseObservations(xml); expect(result).toHaveLength(0); }); it('filters out multiple ghost observations while keeping valid ones (#1625)', () => { const xml = ` bugfix discovery Real observation refactor `; const result = parseObservations(xml); expect(result).toHaveLength(1); expect(result[0].title).toBe('Real observation'); }); // Subtitle alone is explicitly excluded from the content guard (see parser comment). // An observation with only a subtitle is too thin to be useful and must be filtered. it('filters out observation with only a subtitle (excluded from survival criteria) (#1625)', () => { const xml = ` discovery Only a subtitle, no real content `; const result = parseObservations(xml); expect(result).toHaveLength(0); }); it('uses first mode type as fallback when type is missing', () => { const xml = ` Missing type field `; const result = parseObservations(xml); expect(result).toHaveLength(1); // First type in mocked mode is 'bugfix' expect(result[0].type).toBe('bugfix'); }); it('returns empty array when no observation blocks are present', () => { const result = parseObservations('Some text without any observations.'); expect(result).toHaveLength(0); }); it('parses files_read and files_modified arrays correctly', () => { const xml = ` bugfix File read tracking src/utils.tssrc/parser.ts src/utils.ts `; const result = parseObservations(xml); expect(result).toHaveLength(1); expect(result[0].files_read).toEqual(['src/utils.ts', 'src/parser.ts']); expect(result[0].files_modified).toEqual(['src/utils.ts']); }); });