Files
proffesor-for-testing__agen…/tests/e2e/opencode-aqe-smoke.test.ts
Profa 2253d05618 feat: flatten project structure — archive v2, promote v3 to root
Remove the nested v3/ subdirectory by moving all active source code,
tests, configs, and assets directly to the project root. Archive v2/
(dead code, 847MB) to v2-archive.tar.gz.

Key changes:
- Move v3/src/ → src/, v3/tests/ → tests/, v3/packages/ → packages/
- Move v3/tsconfig.json, v3/vitest.config.ts to root
- Merge v3/package.json into root package.json (ESM, scripts, deps, exports)
- Merge v3/scripts/ into scripts/, v3/implementation/ into docs/implementation/
- Update all CI workflows to remove "cd v3 &&" and v3/ path prefixes
- Fix all init installer path resolution (3-level-up → 2-level-up)
- Fix hooks to use "node ./dist/cli/bundle.js" instead of bare "aqe"
- Update settings.json ADR/DDD directories to docs/implementation/
- Fix helper scripts (statusline, adr-compliance, ddd-tracker, guidance-hooks)
- Update 58 agent definitions: aqe/v3/ → aqe/ memory namespaces
- Update 15 guidance shards: v3/src/ → src/ path references
- Fix sync interfaces: remove v3/ and v2/ path references
- Fix test path calculations (process.cwd() + '..' no longer needed)
- Fix flaky perf test threshold (500ms → 1000ms for CI environments)
- Fix complexity-analyzer test to match ADR-051 keyword-based eligibility

Build passes, 17,901 tests pass, CLI and MCP bundles verified.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 11:35:41 +00:00

259 lines
9.4 KiB
TypeScript

/**
* E2E Smoke Test: OpenCode-AQE Integration
*
* Validates end-to-end compatibility between AQE MCP server and OpenCode:
* - MCP tool listing and schema validation
* - Core tool invocation (fleet_health, test_generate, coverage, quality)
* - Response structure and metadata
* - Bridge initialization
* - OpenCode config validation (agents + skills YAML)
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { MCPServer, createMCPServer } from '../../src/mcp/server';
import * as fs from 'fs';
import * as path from 'path';
import YAML from 'yaml';
const OPENCODE_DIR = path.resolve(__dirname, '../../../../.opencode');
const AGENTS_DIR = path.join(OPENCODE_DIR, 'agents');
const SKILLS_DIR = path.join(OPENCODE_DIR, 'skills');
describe('OpenCode-AQE E2E Smoke Test', () => {
let server: MCPServer;
beforeEach(async () => {
server = createMCPServer();
await server.initialize();
});
afterEach(async () => {
await server.dispose();
});
// -------------------------------------------------------------------------
// 1. MCP tool listing
// -------------------------------------------------------------------------
it('should list 30+ tools with OpenCode-compatible schemas', async () => {
const tools = server.getTools();
expect(tools.length).toBeGreaterThanOrEqual(30);
for (const tool of tools) {
expect(tool.name).toBeDefined();
expect(typeof tool.name).toBe('string');
expect(tool.description).toBeDefined();
expect(typeof tool.description).toBe('string');
expect(tool.parameters).toBeDefined();
expect(Array.isArray(tool.parameters)).toBe(true);
}
});
// -------------------------------------------------------------------------
// 2. fleet_health invocation
// -------------------------------------------------------------------------
it('should invoke fleet_health and get structured response', async () => {
try {
const result = await server.invoke('mcp__agentic_qe__fleet_health', {});
expect(result).toBeDefined();
// Result should be an object with status information
if (result && typeof result === 'object') {
expect(result).toHaveProperty('status');
}
} catch (err) {
// Fleet may not be initialized — verify structured error
expect(err).toBeInstanceOf(Error);
expect((err as Error).message).toBeDefined();
}
});
// -------------------------------------------------------------------------
// 3. test_generate_enhanced invocation
// -------------------------------------------------------------------------
it('should invoke test_generate_enhanced with fixture code', async () => {
const MAX_OUTPUT_CHARS = 140_000; // ~35k tokens
try {
const result = await server.invoke('mcp__agentic_qe__test_generate_enhanced', {
sourceCode: 'function add(a: number, b: number): number { return a + b; }',
testType: 'unit',
language: 'typescript',
});
expect(result).toBeDefined();
const serialized = JSON.stringify(result);
expect(serialized.length).toBeLessThan(MAX_OUTPUT_CHARS);
} catch (err) {
// Test generation may require additional setup
expect(err).toBeInstanceOf(Error);
}
});
// -------------------------------------------------------------------------
// 4. coverage_analyze_sublinear invocation
// -------------------------------------------------------------------------
it('should invoke coverage_analyze_sublinear and get result structure', async () => {
try {
const result = await server.invoke('mcp__agentic_qe__coverage_analyze_sublinear', {
target: 'src/',
detectGaps: true,
});
expect(result).toBeDefined();
} catch (err) {
expect(err).toBeInstanceOf(Error);
}
});
// -------------------------------------------------------------------------
// 5. quality_assess invocation
// -------------------------------------------------------------------------
it('should invoke quality_assess and get quality gate response', async () => {
try {
const result = await server.invoke('mcp__agentic_qe__quality_assess', {
runGate: false,
});
expect(result).toBeDefined();
} catch (err) {
expect(err).toBeInstanceOf(Error);
}
});
// -------------------------------------------------------------------------
// 6. Tool descriptions under 200 chars
// -------------------------------------------------------------------------
it('should have tool descriptions under 200 characters for OpenCode display', async () => {
const tools = server.getTools();
const tooLong: string[] = [];
for (const tool of tools) {
if (tool.description && tool.description.length > 200) {
tooLong.push(`${tool.name} (${tool.description.length} chars)`);
}
}
// Allow some tolerance — flag but don't fail hard
if (tooLong.length > 0) {
console.warn(`Tools with descriptions > 200 chars:\n ${tooLong.join('\n ')}`);
}
// At least 80% of tools should be under 200 chars
const complianceRate = (tools.length - tooLong.length) / tools.length;
expect(complianceRate).toBeGreaterThanOrEqual(0.8);
});
// -------------------------------------------------------------------------
// 7. Bridge initBridge
// -------------------------------------------------------------------------
it('should initialize bridge and return valid status', async () => {
try {
const { initBridge } = await import(
'../../../packages/aqe-opencode-bridge/src/index'
);
const status = await initBridge();
expect(status).toBeDefined();
expect(status.initialized).toBe(true);
expect(status.skillCount).toBeGreaterThan(0);
expect(status.tierBreakdown).toBeDefined();
expect(status.tierBreakdown.tier1).toBeGreaterThan(0);
expect(status.tierBreakdown.tier2).toBeGreaterThan(0);
expect(status.tierBreakdown.tier3).toBeGreaterThan(0);
} catch (err) {
// Bridge may have import issues in test env — structured error is OK
expect(err).toBeInstanceOf(Error);
}
});
// -------------------------------------------------------------------------
// 8. Agent YAML configs are parseable
// -------------------------------------------------------------------------
it('should have all .opencode/agents/ YAML configs parseable', () => {
if (!fs.existsSync(AGENTS_DIR)) {
// Acceptable if not yet created
return;
}
const files = fs.readdirSync(AGENTS_DIR).filter((f) => f.endsWith('.yaml'));
expect(files.length).toBeGreaterThanOrEqual(10);
for (const file of files) {
const content = fs.readFileSync(path.join(AGENTS_DIR, file), 'utf-8');
let parsed: unknown;
try {
parsed = YAML.parse(content);
} catch (err) {
throw new Error(`Invalid YAML in agents/${file}: ${(err as Error).message}`);
}
expect(parsed).toBeDefined();
expect(typeof parsed).toBe('object');
const agent = parsed as Record<string, unknown>;
expect(agent.name, `${file}: missing name`).toBeDefined();
expect(agent.description, `${file}: missing description`).toBeDefined();
expect(agent.systemPrompt, `${file}: missing systemPrompt`).toBeDefined();
}
});
// -------------------------------------------------------------------------
// 9. Skill YAML configs are parseable
// -------------------------------------------------------------------------
it('should have all .opencode/skills/ YAML configs parseable', () => {
if (!fs.existsSync(SKILLS_DIR)) {
return;
}
const files = fs.readdirSync(SKILLS_DIR).filter((f) => f.endsWith('.yaml'));
expect(files.length).toBeGreaterThanOrEqual(15);
for (const file of files) {
const content = fs.readFileSync(path.join(SKILLS_DIR, file), 'utf-8');
let parsed: unknown;
try {
parsed = YAML.parse(content);
} catch (err) {
throw new Error(`Invalid YAML in skills/${file}: ${(err as Error).message}`);
}
expect(parsed).toBeDefined();
expect(typeof parsed).toBe('object');
const skill = parsed as Record<string, unknown>;
expect(skill.name, `${file}: missing name`).toBeDefined();
expect(skill.description, `${file}: missing description`).toBeDefined();
expect(skill.steps, `${file}: missing steps`).toBeDefined();
expect(Array.isArray(skill.steps), `${file}: steps must be array`).toBe(true);
}
});
// -------------------------------------------------------------------------
// 10. Permissions file exists and is valid
// -------------------------------------------------------------------------
it('should have valid permissions.yaml', () => {
const permPath = path.join(OPENCODE_DIR, 'permissions.yaml');
if (!fs.existsSync(permPath)) {
// Acceptable if not yet created
return;
}
const content = fs.readFileSync(permPath, 'utf-8');
const parsed = YAML.parse(content) as Record<string, unknown>;
expect(parsed.defaults).toBeDefined();
expect(typeof parsed.defaults).toBe('object');
if (parsed.agents) {
expect(typeof parsed.agents).toBe('object');
}
});
});