feat: initial open marketplace with llm-security, config-audit, ultraplan-local
This commit is contained in:
commit
f93d6abdae
380 changed files with 65935 additions and 0 deletions
252
plugins/config-audit/tests/lib/report-generator.test.mjs
Normal file
252
plugins/config-audit/tests/lib/report-generator.test.mjs
Normal file
|
|
@ -0,0 +1,252 @@
|
|||
import { describe, it } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import {
|
||||
generatePostureReport,
|
||||
generateDriftReport,
|
||||
generatePluginHealthReport,
|
||||
generateFullReport,
|
||||
} from '../../scanners/lib/report-generator.mjs';
|
||||
|
||||
// --- Helpers ---
|
||||
|
||||
function makePostureResult(overrides = {}) {
|
||||
return {
|
||||
utilization: { score: 65, overhang: 35 },
|
||||
maturity: { level: 2, name: 'Structured', description: 'Rules, skills, hooks' },
|
||||
segment: { segment: 'Strong', description: 'Well-configured' },
|
||||
areas: [
|
||||
{ name: 'CLAUDE.md', grade: 'A', score: 95, findingCount: 0 },
|
||||
{ name: 'Settings', grade: 'B', score: 80, findingCount: 2 },
|
||||
{ name: 'Hooks', grade: 'C', score: 60, findingCount: 4 },
|
||||
],
|
||||
overallGrade: 'B',
|
||||
topActions: ['Add MCP server', 'Configure hooks diversity', 'Add custom skills'],
|
||||
scannerEnvelope: {
|
||||
meta: { target: '/test/project', timestamp: '2026-04-03T12:00:00.000Z', version: '2.0.0', tool: 'config-audit' },
|
||||
scanners: [
|
||||
{ scanner: 'CML', findings: [], counts: { critical: 0, high: 0, medium: 0, low: 0, info: 0 } },
|
||||
{ scanner: 'SET', findings: [
|
||||
{ severity: 'medium', title: 'Unknown key', file: 'settings.json' },
|
||||
{ severity: 'low', title: 'Missing schema', file: 'settings.json' },
|
||||
], counts: { critical: 0, high: 0, medium: 1, low: 1, info: 0 } },
|
||||
],
|
||||
},
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function makeDiffResult(overrides = {}) {
|
||||
return {
|
||||
summary: { totalBefore: 10, totalAfter: 8, newCount: 1, resolvedCount: 3, trend: 'improving' },
|
||||
scoreChange: {
|
||||
before: { score: 60, grade: 'C' },
|
||||
after: { score: 75, grade: 'B' },
|
||||
delta: 15,
|
||||
},
|
||||
newFindings: [
|
||||
{ severity: 'medium', title: 'New finding', file: 'test.json' },
|
||||
],
|
||||
resolvedFindings: [
|
||||
{ severity: 'high', title: 'Fixed issue' },
|
||||
{ severity: 'medium', title: 'Another fixed' },
|
||||
{ severity: 'low', title: 'Minor fix' },
|
||||
],
|
||||
areaChanges: [
|
||||
{ name: 'Settings', before: { score: 60, grade: 'C' }, after: { score: 80, grade: 'B' }, delta: 20 },
|
||||
{ name: 'Hooks', before: { score: 70, grade: 'B' }, after: { score: 70, grade: 'B' }, delta: 0 },
|
||||
],
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function makePluginResults() {
|
||||
return [
|
||||
{ name: 'plugin-a', findings: [], commandCount: 5, agentCount: 2 },
|
||||
{ name: 'plugin-b', findings: [
|
||||
{ severity: 'medium', title: 'Missing frontmatter' },
|
||||
{ severity: 'low', title: 'No README' },
|
||||
], commandCount: 3, agentCount: 1 },
|
||||
];
|
||||
}
|
||||
|
||||
function makeScanResult(crossPluginFindings = []) {
|
||||
return {
|
||||
scanner: 'PLH',
|
||||
status: 'ok',
|
||||
findings: [...crossPluginFindings],
|
||||
counts: { critical: 0, high: 0, medium: 0, low: 0, info: 0 },
|
||||
};
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// generatePostureReport
|
||||
// ========================================
|
||||
describe('generatePostureReport', () => {
|
||||
it('returns markdown with health header and grade', () => {
|
||||
const report = generatePostureReport(makePostureResult());
|
||||
assert.ok(report.includes('## Health Assessment'));
|
||||
assert.ok(report.includes('Health Grade'));
|
||||
assert.ok(report.includes('**B**'));
|
||||
});
|
||||
|
||||
it('includes area breakdown table (quality areas only)', () => {
|
||||
const report = generatePostureReport(makePostureResult());
|
||||
assert.ok(report.includes('| CLAUDE.md |'));
|
||||
assert.ok(report.includes('| Settings |'));
|
||||
assert.ok(report.includes('| Hooks |'));
|
||||
});
|
||||
|
||||
it('excludes Feature Coverage from area breakdown', () => {
|
||||
const result = makePostureResult({
|
||||
areas: [
|
||||
{ name: 'CLAUDE.md', grade: 'A', score: 95, findingCount: 0 },
|
||||
{ name: 'Feature Coverage', grade: 'F', score: 20, findingCount: 15 },
|
||||
],
|
||||
});
|
||||
const report = generatePostureReport(result);
|
||||
assert.ok(!report.includes('| Feature Coverage |'));
|
||||
assert.ok(report.includes('| CLAUDE.md |'));
|
||||
});
|
||||
|
||||
it('shows opportunity count when present', () => {
|
||||
const report = generatePostureReport(makePostureResult({ opportunityCount: 5 }));
|
||||
assert.ok(report.includes('5 features available'));
|
||||
});
|
||||
|
||||
it('does not show legacy metrics (utilization, maturity, segment)', () => {
|
||||
const report = generatePostureReport(makePostureResult());
|
||||
assert.ok(!report.includes('Utilization'));
|
||||
assert.ok(!report.includes('Maturity'));
|
||||
assert.ok(!report.includes('Segment'));
|
||||
});
|
||||
|
||||
it('includes scanner findings in collapsed details', () => {
|
||||
const report = generatePostureReport(makePostureResult());
|
||||
assert.ok(report.includes('<details>'));
|
||||
assert.ok(report.includes('SET'));
|
||||
assert.ok(report.includes('Unknown key'));
|
||||
});
|
||||
|
||||
it('skips scanners with no findings', () => {
|
||||
const report = generatePostureReport(makePostureResult());
|
||||
// CML has 0 findings, should not appear in details
|
||||
assert.ok(!report.includes('<summary>CML'));
|
||||
});
|
||||
|
||||
it('handles empty areas', () => {
|
||||
const report = generatePostureReport(makePostureResult({ areas: [], topActions: [] }));
|
||||
assert.ok(report.includes('## Health Assessment'));
|
||||
});
|
||||
});
|
||||
|
||||
// ========================================
|
||||
// generateDriftReport
|
||||
// ========================================
|
||||
describe('generateDriftReport', () => {
|
||||
it('returns markdown with baseline info', () => {
|
||||
const report = generateDriftReport(makeDiffResult(), 'my-baseline');
|
||||
assert.ok(report.includes('## Drift Report'));
|
||||
assert.ok(report.includes('my-baseline'));
|
||||
});
|
||||
|
||||
it('shows trend indicator', () => {
|
||||
const report = generateDriftReport(makeDiffResult(), 'default');
|
||||
assert.ok(report.includes('Improving'));
|
||||
});
|
||||
|
||||
it('shows score delta', () => {
|
||||
const report = generateDriftReport(makeDiffResult(), 'default');
|
||||
assert.ok(report.includes('+15'));
|
||||
});
|
||||
|
||||
it('shows new findings table', () => {
|
||||
const report = generateDriftReport(makeDiffResult(), 'default');
|
||||
assert.ok(report.includes('New Findings'));
|
||||
assert.ok(report.includes('New finding'));
|
||||
});
|
||||
|
||||
it('shows resolved findings table', () => {
|
||||
const report = generateDriftReport(makeDiffResult(), 'default');
|
||||
assert.ok(report.includes('Resolved Findings'));
|
||||
assert.ok(report.includes('Fixed issue'));
|
||||
});
|
||||
|
||||
it('shows area changes (only non-zero delta)', () => {
|
||||
const report = generateDriftReport(makeDiffResult(), 'default');
|
||||
assert.ok(report.includes('Settings'));
|
||||
// Hooks has delta 0, should not appear
|
||||
assert.ok(!report.includes('| Hooks |'));
|
||||
});
|
||||
});
|
||||
|
||||
// ========================================
|
||||
// generatePluginHealthReport
|
||||
// ========================================
|
||||
describe('generatePluginHealthReport', () => {
|
||||
it('returns markdown with plugin table', () => {
|
||||
const report = generatePluginHealthReport(makeScanResult(), makePluginResults());
|
||||
assert.ok(report.includes('## Plugin Health'));
|
||||
assert.ok(report.includes('plugin-a'));
|
||||
assert.ok(report.includes('plugin-b'));
|
||||
});
|
||||
|
||||
it('shows grades and counts', () => {
|
||||
const report = generatePluginHealthReport(makeScanResult(), makePluginResults());
|
||||
assert.ok(report.includes('| 5 |'));
|
||||
assert.ok(report.includes('| 3 |'));
|
||||
});
|
||||
|
||||
it('includes per-plugin findings', () => {
|
||||
const report = generatePluginHealthReport(makeScanResult(), makePluginResults());
|
||||
assert.ok(report.includes('Missing frontmatter'));
|
||||
});
|
||||
|
||||
it('handles no plugins', () => {
|
||||
const report = generatePluginHealthReport(makeScanResult(), []);
|
||||
assert.ok(report.includes('No plugins found'));
|
||||
});
|
||||
|
||||
it('shows cross-plugin issues', () => {
|
||||
const crossFindings = [
|
||||
{ severity: 'high', title: 'Cross-plugin conflict', description: 'Command name clash' },
|
||||
];
|
||||
const report = generatePluginHealthReport(makeScanResult(crossFindings), makePluginResults());
|
||||
assert.ok(report.includes('Cross-Plugin Issues'));
|
||||
assert.ok(report.includes('Command name clash'));
|
||||
});
|
||||
});
|
||||
|
||||
// ========================================
|
||||
// generateFullReport
|
||||
// ========================================
|
||||
describe('generateFullReport', () => {
|
||||
it('combines all sections', () => {
|
||||
const report = generateFullReport(
|
||||
makePostureResult(),
|
||||
{ diff: makeDiffResult(), baselineName: 'default' },
|
||||
{ scanResult: makeScanResult(), pluginResults: makePluginResults() },
|
||||
);
|
||||
assert.ok(report.includes('# Config-Audit Report'));
|
||||
assert.ok(report.includes('## Health Assessment'));
|
||||
assert.ok(report.includes('## Drift Report'));
|
||||
assert.ok(report.includes('## Plugin Health'));
|
||||
});
|
||||
|
||||
it('skips null sections', () => {
|
||||
const report = generateFullReport(makePostureResult(), null, null);
|
||||
assert.ok(report.includes('## Health Assessment'));
|
||||
assert.ok(!report.includes('## Drift Report'));
|
||||
assert.ok(!report.includes('## Plugin Health'));
|
||||
});
|
||||
|
||||
it('handles all null inputs', () => {
|
||||
const report = generateFullReport(null, null, null);
|
||||
assert.ok(report.includes('No data provided'));
|
||||
});
|
||||
|
||||
it('stays under 500 lines', () => {
|
||||
const report = generateFullReport(makePostureResult(), null, null);
|
||||
const lineCount = report.split('\n').length;
|
||||
assert.ok(lineCount <= 502); // 500 + truncation notice
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue