/** * Parser Regression Tests * Ensures v4.2.5 and v4.2.6 bugfixes remain stable */ import { parseObservations, parseSummary } from './parser.js'; // ANSI color codes for output const GREEN = '\x1b[32m'; const RED = '\x1b[31m'; const YELLOW = '\x1b[33m'; const RESET = '\x1b[0m'; let testsRun = 0; let testsPassed = 0; let testsFailed = 0; function assert(condition: boolean, testName: string, errorMsg?: string): void { testsRun++; if (condition) { testsPassed++; console.log(`${GREEN}✓${RESET} ${testName}`); } else { testsFailed++; console.log(`${RED}✗${RESET} ${testName}`); if (errorMsg) { console.log(` ${RED}${errorMsg}${RESET}`); } } } function assertEqual(actual: T, expected: T, testName: string): void { const isEqual = JSON.stringify(actual) === JSON.stringify(expected); if (!isEqual) { assert(false, testName, `Expected: ${JSON.stringify(expected)}, Got: ${JSON.stringify(actual)}`); } else { assert(true, testName); } } console.log('\n' + YELLOW + '='.repeat(60) + RESET); console.log(YELLOW + 'Parser Regression Tests (v4.2.5 & v4.2.6)' + RESET); console.log(YELLOW + '='.repeat(60) + RESET + '\n'); // ============================================================================ // v4.2.6: Observation Parsing - NEVER Skip Observations // ============================================================================ console.log(YELLOW + '\nv4.2.6: Observation Validation Fixes' + RESET); console.log('─'.repeat(60) + '\n'); // Test 1: Observation with missing title should be saved const missingTitleXml = ` feature Added new feature Implemented the feature successfully Created new file authentication src/app.ts `; const missingTitleResult = parseObservations(missingTitleXml); assert(missingTitleResult.length === 1, 'Should parse observation with missing title'); assert(missingTitleResult[0].title === null, 'Missing title should be null'); assertEqual(missingTitleResult[0].type, 'feature', 'Should preserve type when title missing'); // Test 2: Observation with missing subtitle should be saved const missingSubtitleXml = ` bugfix Fixed critical bug Resolved the issue `; const missingSubtitleResult = parseObservations(missingSubtitleXml); assert(missingSubtitleResult.length === 1, 'Should parse observation with missing subtitle'); assert(missingSubtitleResult[0].subtitle === null, 'Missing subtitle should be null'); assertEqual(missingSubtitleResult[0].title, 'Fixed critical bug', 'Should preserve title when subtitle missing'); // Test 3: Observation with missing narrative should be saved const missingNarrativeXml = ` refactor Code cleanup Improved structure Removed dead code `; const missingNarrativeResult = parseObservations(missingNarrativeXml); assert(missingNarrativeResult.length === 1, 'Should parse observation with missing narrative'); assert(missingNarrativeResult[0].narrative === null, 'Missing narrative should be null'); assertEqual(missingNarrativeResult[0].facts, ['Removed dead code'], 'Should preserve facts when narrative missing'); // Test 4: Observation with ALL fields missing (except type) should be saved const minimalObservationXml = ` change `; const minimalResult = parseObservations(minimalObservationXml); assert(minimalResult.length === 1, 'Should parse minimal observation with only type'); assertEqual(minimalResult[0].type, 'change', 'Should preserve type for minimal observation'); assert(minimalResult[0].title === null, 'Empty title should be null'); assert(minimalResult[0].subtitle === null, 'Empty subtitle should be null'); assert(minimalResult[0].narrative === null, 'Empty narrative should be null'); // Test 5: Observation with missing type should use "change" as fallback const missingTypeXml = ` Something happened Details here More info `; const missingTypeResult = parseObservations(missingTypeXml); assert(missingTypeResult.length === 1, 'Should parse observation with missing type'); assertEqual(missingTypeResult[0].type, 'change', 'Missing type should default to "change"'); // Test 6: Observation with invalid type should use "change" as fallback const invalidTypeXml = ` invalid_type_here Something happened Details here More info `; const invalidTypeResult = parseObservations(invalidTypeXml); assert(invalidTypeResult.length === 1, 'Should parse observation with invalid type'); assertEqual(invalidTypeResult[0].type, 'change', 'Invalid type should default to "change"'); // Test 7: Multiple observations with mixed completeness should all be saved const mixedObservationsXml = ` feature Full observation Complete All fields present Fact 1 concept1 bugfix Only subtitle and type Only title, no type `; const mixedResult = parseObservations(mixedObservationsXml); assertEqual(mixedResult.length, 3, 'Should parse all three observations regardless of completeness'); assertEqual(mixedResult[0].type, 'feature', 'First observation should have correct type'); assertEqual(mixedResult[1].type, 'bugfix', 'Second observation should have correct type'); assertEqual(mixedResult[2].type, 'change', 'Third observation should default to "change"'); // ============================================================================ // v4.2.5: Summary Parsing - NEVER Skip Summaries // ============================================================================ console.log(YELLOW + '\nv4.2.5: Summary Validation Fixes' + RESET); console.log('─'.repeat(60) + '\n'); // Test 8: Summary with missing request field should be saved const missingRequestXml = ` Looked into the codebase Found the issue Fixed the bug Deploy to production `; const missingRequestResult = parseSummary(missingRequestXml); assert(missingRequestResult !== null, 'Should parse summary with missing request'); assert(missingRequestResult!.request === null, 'Missing request should be null'); assertEqual(missingRequestResult!.investigated, 'Looked into the codebase', 'Should preserve other fields'); // Test 9: Summary with missing investigated field should be saved const missingInvestigatedXml = ` Fix the bug Root cause identified Applied the fix Monitor production `; const missingInvestigatedResult = parseSummary(missingInvestigatedXml); assert(missingInvestigatedResult !== null, 'Should parse summary with missing investigated'); assert(missingInvestigatedResult!.investigated === null, 'Missing investigated should be null'); // Test 10: Summary with missing learned field should be saved const missingLearnedXml = ` Add new feature Reviewed the requirements Implemented the feature Write tests `; const missingLearnedResult = parseSummary(missingLearnedXml); assert(missingLearnedResult !== null, 'Should parse summary with missing learned'); assert(missingLearnedResult!.learned === null, 'Missing learned should be null'); // Test 11: Summary with missing completed field should be saved const missingCompletedXml = ` Refactor code Analyzed the structure Found improvement opportunities Continue refactoring `; const missingCompletedResult = parseSummary(missingCompletedXml); assert(missingCompletedResult !== null, 'Should parse summary with missing completed'); assert(missingCompletedResult!.completed === null, 'Missing completed should be null'); // Test 12: Summary with missing next_steps field should be saved const missingNextStepsXml = ` Review code Examined all files Code quality is good Review complete `; const missingNextStepsResult = parseSummary(missingNextStepsXml); assert(missingNextStepsResult !== null, 'Should parse summary with missing next_steps'); assert(missingNextStepsResult!.next_steps === null, 'Missing next_steps should be null'); // Test 13: Summary with only notes field should be saved const onlyNotesXml = ` Some random notes `; const onlyNotesResult = parseSummary(onlyNotesXml); assert(onlyNotesResult !== null, 'Should parse summary with only notes field'); assertEqual(onlyNotesResult!.notes, 'Some random notes', 'Should preserve notes field'); // Test 14: Completely empty summary should be saved const emptySummaryXml = ` `; const emptySummaryResult = parseSummary(emptySummaryXml); assert(emptySummaryResult !== null, 'Should parse completely empty summary'); assert(emptySummaryResult!.request === null, 'Empty request should be null'); assert(emptySummaryResult!.investigated === null, 'Empty investigated should be null'); // Test 15: Summary with skip_summary should return null (valid use case) const skipSummaryXml = ` `; const skipSummaryResult = parseSummary(skipSummaryXml); assert(skipSummaryResult === null, 'Should return null for skip_summary directive'); // ============================================================================ // Edge Cases & Data Integrity // ============================================================================ console.log(YELLOW + '\nEdge Cases & Data Integrity' + RESET); console.log('─'.repeat(60) + '\n'); // Test 16: Observation with whitespace-only fields should be null const whitespaceObservationXml = ` change `; const whitespaceResult = parseObservations(whitespaceObservationXml); assert(whitespaceResult.length === 1, 'Should parse observation with whitespace fields'); assert(whitespaceResult[0].title === null || whitespaceResult[0].title!.trim() === '', 'Whitespace title should be null or empty'); // Test 17: Observation with concepts including type should filter out type const conceptsWithTypeXml = ` feature New feature Details Description feature authentication `; const conceptsWithTypeResult = parseObservations(conceptsWithTypeXml); assert(conceptsWithTypeResult.length === 1, 'Should parse observation with type in concepts'); assertEqual(conceptsWithTypeResult[0].concepts, ['authentication'], 'Should filter out type from concepts'); // Test 18: Observation with all valid types const validTypes = ['decision', 'bugfix', 'feature', 'refactor', 'discovery', 'change']; validTypes.forEach(type => { const typeXml = ` ${type} Test Test Test `; const result = parseObservations(typeXml); assertEqual(result[0].type, type, `Should accept valid type: ${type}`); }); // ============================================================================ // Results Summary // ============================================================================ console.log('\n' + YELLOW + '='.repeat(60) + RESET); console.log(YELLOW + 'Test Results Summary' + RESET); console.log(YELLOW + '='.repeat(60) + RESET + '\n'); console.log(`Total Tests: ${testsRun}`); console.log(`${GREEN}Passed: ${testsPassed}${RESET}`); console.log(`${RED}Failed: ${testsFailed}${RESET}`); if (testsFailed > 0) { console.log(`\n${RED}❌ TESTS FAILED${RESET}\n`); process.exit(1); } else { console.log(`\n${GREEN}✅ ALL TESTS PASSED${RESET}\n`); process.exit(0); }