mirror of
https://github.com/proffesor-for-testing/agentic-qe.git
synced 2026-09-19 08:45:47 +08:00
2253d05618
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>
259 lines
9.4 KiB
TypeScript
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');
|
|
}
|
|
});
|
|
});
|