Files
Dragan Spiridonov e82a7240ec feat: implement CC-internals improvements IMP-00 through IMP-10 (Waves 0-4)
11 improvements across 4 priority tiers, all integrated into production
code paths with 416 new tests across 33 test files. Each wave passed
a brutal honesty audit with all findings resolved.

Wave 0-2 (P0/P1): Middleware chain, microcompact, tool concurrency,
retry engine, session durability, prompt cache latch, startup fast
paths, hook security hardening.

Wave 3 (P2): 4-tier context compaction pipeline, plugin architecture
with local/GitHub/npm sources.

Wave 4 (P3): QE Quality Daemon — priority queue, git watcher (with
Linux polling fallback), coverage delta analyzer, CI monitor, test
suggester, nightly consolidation, notification service with SSRF guard,
persistent SQLite-backed memory, CLI commands, MCP startup integration.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 17:32:50 +00:00

148 lines
4.9 KiB
TypeScript

/**
* Tests for Plugin Manifest Validation (IMP-09)
*/
import { describe, it, expect } from 'vitest';
import { validateManifest, parseManifest, type QEPluginManifest } from '../../src/plugins/manifest';
function validManifest(overrides?: Partial<QEPluginManifest>): QEPluginManifest {
return {
name: 'my-test-plugin',
version: '1.0.0',
description: 'A test plugin for unit tests',
author: 'Test Author',
domains: ['test-generation' as never],
entryPoint: 'dist/index.js',
...overrides,
};
}
describe('validateManifest', () => {
describe('valid manifests', () => {
it('should accept a valid minimal manifest', () => {
const result = validateManifest(validManifest());
expect(result.valid).toBe(true);
expect(result.errors).toHaveLength(0);
});
it('should accept manifest with all optional fields', () => {
const result = validateManifest(validManifest({
dependencies: { 'other-plugin': '^1.0.0' },
hooks: { 'pre-test': 'hooks/pre-test.js' },
minAqeVersion: '3.8.0',
permissions: ['fs:read'],
}));
expect(result.valid).toBe(true);
});
});
describe('required field validation', () => {
it('should reject null/undefined input', () => {
expect(validateManifest(null).valid).toBe(false);
expect(validateManifest(undefined).valid).toBe(false);
});
it('should reject missing name', () => {
const m = validManifest();
delete (m as Record<string, unknown>).name;
const result = validateManifest(m);
expect(result.valid).toBe(false);
expect(result.errors[0]).toContain('name');
});
it('should reject missing version', () => {
const m = validManifest();
delete (m as Record<string, unknown>).version;
expect(validateManifest(m).valid).toBe(false);
});
it('should reject missing entryPoint', () => {
const m = validManifest();
delete (m as Record<string, unknown>).entryPoint;
expect(validateManifest(m).valid).toBe(false);
});
it('should reject empty domains array', () => {
expect(validateManifest(validManifest({ domains: [] as never })).valid).toBe(false);
});
});
describe('name validation', () => {
it('should reject names with uppercase', () => {
expect(validateManifest(validManifest({ name: 'MyPlugin' })).valid).toBe(false);
});
it('should reject names starting with numbers', () => {
expect(validateManifest(validManifest({ name: '1plugin' })).valid).toBe(false);
});
it('should reject names with spaces', () => {
expect(validateManifest(validManifest({ name: 'my plugin' })).valid).toBe(false);
});
it('should reject reserved prefixes', () => {
expect(validateManifest(validManifest({ name: 'aqe-core-auth' })).valid).toBe(false);
});
it('should accept valid hyphenated names', () => {
expect(validateManifest(validManifest({ name: 'my-custom-plugin' })).valid).toBe(true);
});
});
describe('version validation', () => {
it('should reject non-semver versions', () => {
expect(validateManifest(validManifest({ version: '1.0' })).valid).toBe(false);
expect(validateManifest(validManifest({ version: 'latest' })).valid).toBe(false);
});
it('should accept pre-release versions', () => {
expect(validateManifest(validManifest({ version: '1.0.0-beta.1' })).valid).toBe(true);
});
});
describe('entryPoint validation', () => {
it('should reject path traversal', () => {
expect(validateManifest(validManifest({ entryPoint: '../../../evil.js' })).valid).toBe(false);
});
it('should reject absolute paths', () => {
expect(validateManifest(validManifest({ entryPoint: '/usr/bin/evil.js' })).valid).toBe(false);
});
it('should reject non-js/ts extensions', () => {
expect(validateManifest(validManifest({ entryPoint: 'index.py' })).valid).toBe(false);
});
it('should accept .mjs entry points', () => {
expect(validateManifest(validManifest({ entryPoint: 'dist/index.mjs' })).valid).toBe(true);
});
});
describe('hooks validation', () => {
it('should reject hook paths with traversal', () => {
const result = validateManifest(validManifest({
hooks: { 'pre-test': '../../../etc/passwd' },
}));
expect(result.valid).toBe(false);
expect(result.errors.some(e => e.includes('..'))).toBe(true);
});
});
});
describe('parseManifest', () => {
it('should parse valid JSON string', () => {
const json = JSON.stringify(validManifest());
const manifest = parseManifest(json);
expect(manifest.name).toBe('my-test-plugin');
});
it('should parse valid object', () => {
const manifest = parseManifest(validManifest());
expect(manifest.name).toBe('my-test-plugin');
});
it('should throw on invalid manifest', () => {
expect(() => parseManifest({ name: 123 })).toThrow('Invalid plugin manifest');
});
});