docs: enterprise refactor plan with ralph specs
This commit is contained in:
133
tests/plugins/exporters/MarkdownExporter.test.ts
Normal file
133
tests/plugins/exporters/MarkdownExporter.test.ts
Normal file
@@ -0,0 +1,133 @@
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import { MarkdownExporter } from '../../../src/plugins/exporters/MarkdownExporter';
|
||||
import { IAnomaly, IAction } from '../../../src/core/interfaces';
|
||||
|
||||
function makeAnomaly(overrides: Partial<IAnomaly> = {}): IAnomaly {
|
||||
const action: IAction = {
|
||||
id: 'act-1',
|
||||
type: 'navigate',
|
||||
url: 'http://localhost:3000/register',
|
||||
timestamp: 1700000000000,
|
||||
seed: 42,
|
||||
stateId: 's1',
|
||||
};
|
||||
return {
|
||||
id: 'anom-md-001',
|
||||
type: 'http_error',
|
||||
severity: 'high',
|
||||
observationId: 'obs-1',
|
||||
actionTrace: [action],
|
||||
description: 'Form submission returns HTTP 500 on empty email field',
|
||||
evidence: {
|
||||
screenshotPath: 'anom-md-001/screenshot.png',
|
||||
domSnapshotPath: 'anom-md-001/dom.html',
|
||||
httpLog: [{ url: '/api/register', status: 500, method: 'POST', durationMs: 234 }],
|
||||
rawErrors: ['POST /api/register → 500 (234ms)'],
|
||||
},
|
||||
timestamp: 1700000000000,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe('MarkdownExporter', () => {
|
||||
let tmpDir: string;
|
||||
|
||||
beforeEach(() => {
|
||||
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'abe-md-'));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fs.rmSync(tmpDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it('creates report.md in the output directory', async () => {
|
||||
const exporter = new MarkdownExporter();
|
||||
const outputDir = path.join(tmpDir, 'anom-md-001');
|
||||
const filePath = await exporter.export(makeAnomaly(), outputDir);
|
||||
expect(fs.existsSync(filePath)).toBe(true);
|
||||
expect(filePath.endsWith('report.md')).toBe(true);
|
||||
});
|
||||
|
||||
it('includes anomaly type and date in title', async () => {
|
||||
const exporter = new MarkdownExporter();
|
||||
const outputDir = path.join(tmpDir, 'title-test');
|
||||
const filePath = await exporter.export(makeAnomaly(), outputDir);
|
||||
const content = fs.readFileSync(filePath, 'utf8');
|
||||
expect(content).toContain('# Bug Report');
|
||||
expect(content).toContain('http_error');
|
||||
});
|
||||
|
||||
it('includes severity section', async () => {
|
||||
const exporter = new MarkdownExporter();
|
||||
const outputDir = path.join(tmpDir, 'severity-test');
|
||||
const filePath = await exporter.export(makeAnomaly(), outputDir);
|
||||
const content = fs.readFileSync(filePath, 'utf8');
|
||||
expect(content).toContain('## Severity');
|
||||
expect(content).toContain('high');
|
||||
});
|
||||
|
||||
it('includes reproduction steps with navigate action', async () => {
|
||||
const exporter = new MarkdownExporter();
|
||||
const outputDir = path.join(tmpDir, 'steps-test');
|
||||
const filePath = await exporter.export(makeAnomaly(), outputDir);
|
||||
const content = fs.readFileSync(filePath, 'utf8');
|
||||
expect(content).toContain('## Reproduction Steps');
|
||||
expect(content).toContain('Navigate to');
|
||||
expect(content).toContain('http://localhost:3000/register');
|
||||
});
|
||||
|
||||
it('includes seed and replay command', async () => {
|
||||
const exporter = new MarkdownExporter();
|
||||
const outputDir = path.join(tmpDir, 'seed-test');
|
||||
const filePath = await exporter.export(makeAnomaly(), outputDir);
|
||||
const content = fs.readFileSync(filePath, 'utf8');
|
||||
expect(content).toContain('Seed used');
|
||||
expect(content).toContain('42');
|
||||
expect(content).toContain('Replay command');
|
||||
expect(content).toContain('npm run replay');
|
||||
});
|
||||
|
||||
it('includes evidence section with screenshot and dom paths', async () => {
|
||||
const exporter = new MarkdownExporter();
|
||||
const outputDir = path.join(tmpDir, 'evidence-test');
|
||||
const filePath = await exporter.export(makeAnomaly(), outputDir);
|
||||
const content = fs.readFileSync(filePath, 'utf8');
|
||||
expect(content).toContain('## Evidence');
|
||||
expect(content).toContain('screenshot.png');
|
||||
expect(content).toContain('dom.html');
|
||||
});
|
||||
|
||||
it('includes HTTP table when responses present', async () => {
|
||||
const exporter = new MarkdownExporter();
|
||||
const outputDir = path.join(tmpDir, 'http-test');
|
||||
const filePath = await exporter.export(makeAnomaly(), outputDir);
|
||||
const content = fs.readFileSync(filePath, 'utf8');
|
||||
expect(content).toContain('500');
|
||||
expect(content).toContain('/api/register');
|
||||
expect(content).toContain('POST');
|
||||
});
|
||||
|
||||
it('includes raw errors section', async () => {
|
||||
const exporter = new MarkdownExporter();
|
||||
const outputDir = path.join(tmpDir, 'errors-test');
|
||||
const filePath = await exporter.export(makeAnomaly(), outputDir);
|
||||
const content = fs.readFileSync(filePath, 'utf8');
|
||||
expect(content).toContain('## Raw Errors');
|
||||
expect(content).toContain('POST /api/register');
|
||||
});
|
||||
|
||||
it('has format = markdown', () => {
|
||||
expect(new MarkdownExporter().format).toBe('markdown');
|
||||
});
|
||||
|
||||
it('handles anomaly with no action trace', async () => {
|
||||
const exporter = new MarkdownExporter();
|
||||
const outputDir = path.join(tmpDir, 'no-trace-test');
|
||||
const anomaly = makeAnomaly({ actionTrace: [] });
|
||||
const filePath = await exporter.export(anomaly, outputDir);
|
||||
const content = fs.readFileSync(filePath, 'utf8');
|
||||
expect(content).toContain('No steps recorded');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user