/** * Integration test for PHP response shape extraction. * * Runs the full pipeline on a PHP fixture with json_encode() calls * and verifies that Route nodes have correct responseKeys/errorKeys. */ import { describe, it, expect, beforeAll } from 'vitest'; import path from 'path'; import { FIXTURES, getNodesByLabel, getNodesByLabelFull, getRelationships, runPipelineFromRepo, type PipelineResult, } from './helpers.js'; describe('PHP response shape extraction (pipeline)', () => { let result: PipelineResult; beforeAll(async () => { result = await runPipelineFromRepo(path.join(FIXTURES, 'php-response-shapes'), () => {}); }, 60000); it('creates Route nodes for PHP endpoints', () => { const routes = getNodesByLabel(result, 'Route'); expect(routes).toContain('/api/items'); expect(routes).toContain('/api/submit'); }); it('extracts responseKeys from json_encode success path (items.php)', () => { const routes = getNodesByLabelFull(result, 'Route'); const items = routes.find((r) => r.name === '/api/items'); expect(items).toBeDefined(); expect(items!.properties.responseKeys).toEqual(expect.arrayContaining(['data', 'total'])); }); it('extracts errorKeys from json_encode with http_response_code >= 400 (items.php)', () => { const routes = getNodesByLabelFull(result, 'Route'); const items = routes.find((r) => r.name === '/api/items'); expect(items).toBeDefined(); expect(items!.properties.errorKeys).toEqual(expect.arrayContaining(['error'])); }); it('keeps success and error keys separate (no cross-contamination)', () => { const routes = getNodesByLabelFull(result, 'Route'); const items = routes.find((r) => r.name === '/api/items'); expect(items).toBeDefined(); const successKeys = new Set(items!.properties.responseKeys ?? []); const errorKeys = new Set(items!.properties.errorKeys ?? []); expect(successKeys.has('error')).toBe(false); expect(errorKeys.has('data')).toBe(false); expect(errorKeys.has('total')).toBe(false); }); it('extracts responseKeys from submit.php success path', () => { const routes = getNodesByLabelFull(result, 'Route'); const submit = routes.find((r) => r.name === '/api/submit'); expect(submit).toBeDefined(); expect(submit!.properties.responseKeys).toEqual( expect.arrayContaining(['ok', 'id', 'created_at']), ); }); it('extracts errorKeys from submit.php error paths', () => { const routes = getNodesByLabelFull(result, 'Route'); const submit = routes.find((r) => r.name === '/api/submit'); expect(submit).toBeDefined(); expect(submit!.properties.errorKeys).toEqual(expect.arrayContaining(['error'])); }); it('respects exit(N) and die() as boundaries in submit.php', () => { const routes = getNodesByLabelFull(result, 'Route'); const submit = routes.find((r) => r.name === '/api/submit'); expect(submit).toBeDefined(); const successKeys = new Set(submit!.properties.responseKeys ?? []); expect(successKeys.has('ok')).toBe(true); expect(successKeys.has('id')).toBe(true); expect(successKeys.has('error')).toBe(false); }); it('creates HANDLES_ROUTE edges from PHP files to Route nodes', () => { const edges = getRelationships(result, 'HANDLES_ROUTE'); const itemsHandler = edges.find((e) => e.target === '/api/items'); expect(itemsHandler).toBeDefined(); expect(itemsHandler!.sourceFilePath).toContain('api/items.php'); const submitHandler = edges.find((e) => e.target === '/api/submit'); expect(submitHandler).toBeDefined(); expect(submitHandler!.sourceFilePath).toContain('api/submit.php'); }); });