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>
327 lines
13 KiB
TypeScript
327 lines
13 KiB
TypeScript
/**
|
|
* E2E Test: Cross-Platform Init
|
|
*
|
|
* Tests the full platform initialization flow through the AssetsPhase,
|
|
* verifying that platform flags and auto-detection work correctly for
|
|
* all 8 coding agent platforms.
|
|
*
|
|
* Uses real temporary directories -- no mocking.
|
|
*/
|
|
|
|
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
import fs from 'fs';
|
|
import os from 'os';
|
|
import path from 'path';
|
|
|
|
import { PLATFORM_REGISTRY, type PlatformId } from '../../src/init/platform-config-generator.js';
|
|
import { createCopilotInstaller } from '../../src/init/copilot-installer.js';
|
|
import { createCursorInstaller } from '../../src/init/cursor-installer.js';
|
|
import { createClineInstaller } from '../../src/init/cline-installer.js';
|
|
import { createKiloCodeInstaller } from '../../src/init/kilocode-installer.js';
|
|
import { createRooCodeInstaller } from '../../src/init/roocode-installer.js';
|
|
import { createCodexInstaller } from '../../src/init/codex-installer.js';
|
|
import { createWindsurfInstaller } from '../../src/init/windsurf-installer.js';
|
|
import { createContinueDevInstaller } from '../../src/init/continuedev-installer.js';
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Helpers
|
|
// ---------------------------------------------------------------------------
|
|
|
|
let tempDir: string;
|
|
|
|
function createTempDir(): string {
|
|
return fs.mkdtempSync(path.join(os.tmpdir(), 'aqe-e2e-platform-'));
|
|
}
|
|
|
|
function fileExists(relativePath: string): boolean {
|
|
return fs.existsSync(path.join(tempDir, relativePath));
|
|
}
|
|
|
|
function readFileAt(relativePath: string): string {
|
|
return fs.readFileSync(path.join(tempDir, relativePath), 'utf-8');
|
|
}
|
|
|
|
const ALL_PLATFORMS: PlatformId[] = [
|
|
'copilot', 'cursor', 'cline', 'kilocode', 'roocode',
|
|
'codex', 'windsurf', 'continuedev',
|
|
];
|
|
|
|
/**
|
|
* Install a single platform by ID, returning the result.
|
|
*/
|
|
async function installPlatform(platformId: PlatformId, overwrite = false) {
|
|
const opts = { projectRoot: tempDir, overwrite };
|
|
switch (platformId) {
|
|
case 'copilot': return createCopilotInstaller(opts).install();
|
|
case 'cursor': return createCursorInstaller(opts).install();
|
|
case 'cline': return createClineInstaller(opts).install();
|
|
case 'kilocode': return createKiloCodeInstaller(opts).install();
|
|
case 'roocode': return createRooCodeInstaller(opts).install();
|
|
case 'codex': return createCodexInstaller(opts).install();
|
|
case 'windsurf': return createWindsurfInstaller(opts).install();
|
|
case 'continuedev': return createContinueDevInstaller(opts).install();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Install all platforms into the current tempDir.
|
|
*/
|
|
async function installAllPlatforms(overwrite = false) {
|
|
const results: Record<string, Awaited<ReturnType<typeof installPlatform>>> = {};
|
|
for (const p of ALL_PLATFORMS) {
|
|
results[p] = await installPlatform(p, overwrite);
|
|
}
|
|
return results;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Lifecycle
|
|
// ---------------------------------------------------------------------------
|
|
|
|
beforeEach(() => {
|
|
tempDir = createTempDir();
|
|
});
|
|
|
|
afterEach(() => {
|
|
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Tests
|
|
// ---------------------------------------------------------------------------
|
|
|
|
describe('Cross-Platform Init - E2E', () => {
|
|
|
|
// -------------------------------------------------------------------------
|
|
// 1. Init with all platforms
|
|
// -------------------------------------------------------------------------
|
|
|
|
describe('Init with all platforms', () => {
|
|
it('creates config and rules files for all 8 platforms', async () => {
|
|
const results = await installAllPlatforms();
|
|
|
|
for (const platformId of ALL_PLATFORMS) {
|
|
const def = PLATFORM_REGISTRY[platformId];
|
|
const result = results[platformId];
|
|
|
|
expect(result.success, `${platformId} install should succeed`).toBe(true);
|
|
expect(result.mcpConfigured, `${platformId} MCP should be configured`).toBe(true);
|
|
expect(result.errors, `${platformId} should have no errors`).toEqual([]);
|
|
|
|
// Config file exists and references agentic-qe
|
|
expect(fileExists(def.configPath), `${platformId} config at ${def.configPath}`).toBe(true);
|
|
const configContent = readFileAt(def.configPath);
|
|
expect(configContent).toContain('agentic-qe');
|
|
|
|
// Rules file exists
|
|
expect(fileExists(def.rulesPath), `${platformId} rules at ${def.rulesPath}`).toBe(true);
|
|
}
|
|
});
|
|
|
|
it('no two platforms overwrite each others config files', async () => {
|
|
await installAllPlatforms();
|
|
|
|
// Collect all config paths and rules paths, verify uniqueness
|
|
const configPaths = ALL_PLATFORMS.map(p => PLATFORM_REGISTRY[p].configPath);
|
|
const rulesPaths = ALL_PLATFORMS.map(p => PLATFORM_REGISTRY[p].rulesPath);
|
|
|
|
// Config paths should all be unique
|
|
expect(new Set(configPaths).size).toBe(configPaths.length);
|
|
|
|
// Note: rules paths may share a directory but files themselves should be unique
|
|
expect(new Set(rulesPaths).size).toBe(rulesPaths.length);
|
|
});
|
|
|
|
it('total file count matches expectations', async () => {
|
|
await installAllPlatforms();
|
|
|
|
// Each platform creates 2 files: config + rules = 16 files
|
|
let fileCount = 0;
|
|
for (const platformId of ALL_PLATFORMS) {
|
|
const def = PLATFORM_REGISTRY[platformId];
|
|
if (fileExists(def.configPath)) fileCount++;
|
|
if (fileExists(def.rulesPath)) fileCount++;
|
|
}
|
|
expect(fileCount).toBe(16);
|
|
});
|
|
});
|
|
|
|
// -------------------------------------------------------------------------
|
|
// 2. Init with a single platform
|
|
// -------------------------------------------------------------------------
|
|
|
|
describe('Init with single platform', () => {
|
|
it.each(ALL_PLATFORMS)('installing only %s creates only that platforms files', async (platformId) => {
|
|
await installPlatform(platformId);
|
|
|
|
const def = PLATFORM_REGISTRY[platformId];
|
|
|
|
// Target platform files exist
|
|
expect(fileExists(def.configPath)).toBe(true);
|
|
expect(fileExists(def.rulesPath)).toBe(true);
|
|
|
|
// Other platform files do not exist
|
|
for (const otherId of ALL_PLATFORMS) {
|
|
if (otherId === platformId) continue;
|
|
const otherDef = PLATFORM_REGISTRY[otherId];
|
|
expect(
|
|
fileExists(otherDef.configPath),
|
|
`${otherId} config should not exist when only ${platformId} installed`
|
|
).toBe(false);
|
|
}
|
|
});
|
|
});
|
|
|
|
// -------------------------------------------------------------------------
|
|
// 3. Auto-detection simulation
|
|
// -------------------------------------------------------------------------
|
|
|
|
describe('Auto-detection via directory presence', () => {
|
|
/**
|
|
* The AssetsPhase uses directory presence to auto-detect platforms.
|
|
* We simulate this by creating platform-specific directories, then
|
|
* verifying the installer succeeds when invoked (as the assets phase would).
|
|
*/
|
|
|
|
const autoDetectMappings: { dir: string; platformId: PlatformId }[] = [
|
|
{ dir: '.vscode', platformId: 'copilot' },
|
|
{ dir: '.cursor', platformId: 'cursor' },
|
|
{ dir: '.kilocode', platformId: 'kilocode' },
|
|
{ dir: '.roo', platformId: 'roocode' },
|
|
{ dir: '.codex', platformId: 'codex' },
|
|
{ dir: '.windsurf', platformId: 'windsurf' },
|
|
{ dir: '.continue', platformId: 'continuedev' },
|
|
];
|
|
|
|
it.each(autoDetectMappings)(
|
|
'detects $platformId when $dir directory exists',
|
|
async ({ dir, platformId }) => {
|
|
// Create the sentinel directory
|
|
fs.mkdirSync(path.join(tempDir, dir), { recursive: true });
|
|
|
|
// Verify directory exists (simulating autoMode detection)
|
|
expect(fs.existsSync(path.join(tempDir, dir))).toBe(true);
|
|
|
|
// Install the corresponding platform (as the assets phase would)
|
|
const result = await installPlatform(platformId);
|
|
const def = PLATFORM_REGISTRY[platformId];
|
|
|
|
expect(result.success).toBe(true);
|
|
expect(result.mcpConfigured).toBe(true);
|
|
expect(fileExists(def.configPath)).toBe(true);
|
|
expect(fileExists(def.rulesPath)).toBe(true);
|
|
}
|
|
);
|
|
|
|
it('multiple auto-detected directories trigger multiple installs', async () => {
|
|
// Create directories for copilot, cursor, and roocode
|
|
fs.mkdirSync(path.join(tempDir, '.vscode'), { recursive: true });
|
|
fs.mkdirSync(path.join(tempDir, '.cursor'), { recursive: true });
|
|
fs.mkdirSync(path.join(tempDir, '.roo'), { recursive: true });
|
|
|
|
// Install the detected platforms
|
|
const results = await Promise.all([
|
|
installPlatform('copilot'),
|
|
installPlatform('cursor'),
|
|
installPlatform('roocode'),
|
|
]);
|
|
|
|
for (const result of results) {
|
|
expect(result.success).toBe(true);
|
|
expect(result.mcpConfigured).toBe(true);
|
|
}
|
|
|
|
// Verify all three have configs
|
|
expect(fileExists(PLATFORM_REGISTRY.copilot.configPath)).toBe(true);
|
|
expect(fileExists(PLATFORM_REGISTRY.cursor.configPath)).toBe(true);
|
|
expect(fileExists(PLATFORM_REGISTRY.roocode.configPath)).toBe(true);
|
|
|
|
// Verify non-detected platforms are absent
|
|
expect(fileExists(PLATFORM_REGISTRY.codex.configPath)).toBe(false);
|
|
expect(fileExists(PLATFORM_REGISTRY.continuedev.configPath)).toBe(false);
|
|
});
|
|
});
|
|
|
|
// -------------------------------------------------------------------------
|
|
// 4. Full round-trip: install, verify, overwrite, verify merge
|
|
// -------------------------------------------------------------------------
|
|
|
|
describe('Full round-trip: install then overwrite', () => {
|
|
it('first install creates files, second install with overwrite merges', async () => {
|
|
// First pass: install all
|
|
const firstResults = await installAllPlatforms();
|
|
for (const platformId of ALL_PLATFORMS) {
|
|
expect(firstResults[platformId].success).toBe(true);
|
|
}
|
|
|
|
// Inject a custom server into copilot config
|
|
const copilotConfigPath = path.join(tempDir, PLATFORM_REGISTRY.copilot.configPath);
|
|
const copilotConfig = JSON.parse(fs.readFileSync(copilotConfigPath, 'utf-8'));
|
|
copilotConfig.servers['my-custom-server'] = { command: 'echo', args: ['hello'] };
|
|
fs.writeFileSync(copilotConfigPath, JSON.stringify(copilotConfig, null, 2));
|
|
|
|
// Second pass: overwrite all
|
|
const secondResults = await installAllPlatforms(true);
|
|
for (const platformId of ALL_PLATFORMS) {
|
|
expect(secondResults[platformId].success).toBe(true);
|
|
expect(secondResults[platformId].mcpConfigured).toBe(true);
|
|
}
|
|
|
|
// Verify copilot config merged (custom server preserved)
|
|
const mergedCopilot = JSON.parse(readFileAt(PLATFORM_REGISTRY.copilot.configPath));
|
|
expect(mergedCopilot.servers['my-custom-server']).toBeDefined();
|
|
expect(mergedCopilot.servers['agentic-qe']).toBeDefined();
|
|
});
|
|
|
|
it('second install without overwrite does not modify existing files', async () => {
|
|
// First install
|
|
await installAllPlatforms();
|
|
|
|
// Record content of all config files
|
|
const originalContents: Record<string, string> = {};
|
|
for (const platformId of ALL_PLATFORMS) {
|
|
originalContents[platformId] = readFileAt(PLATFORM_REGISTRY[platformId].configPath);
|
|
}
|
|
|
|
// Second install without overwrite
|
|
const secondResults = await installAllPlatforms(false);
|
|
|
|
for (const platformId of ALL_PLATFORMS) {
|
|
expect(secondResults[platformId].mcpConfigured).toBe(false);
|
|
// Content unchanged
|
|
const currentContent = readFileAt(PLATFORM_REGISTRY[platformId].configPath);
|
|
expect(currentContent).toBe(originalContents[platformId]);
|
|
}
|
|
});
|
|
});
|
|
|
|
// -------------------------------------------------------------------------
|
|
// 5. Cross-format consistency
|
|
// -------------------------------------------------------------------------
|
|
|
|
describe('Cross-format consistency', () => {
|
|
it('all configs reference the same MCP command and args', async () => {
|
|
await installAllPlatforms();
|
|
|
|
for (const platformId of ALL_PLATFORMS) {
|
|
const def = PLATFORM_REGISTRY[platformId];
|
|
const content = readFileAt(def.configPath);
|
|
|
|
// All platforms should reference the npx command and agentic-qe package
|
|
expect(content).toContain('npx');
|
|
expect(content).toContain('agentic-qe');
|
|
}
|
|
});
|
|
|
|
it('all configs reference AQE_V3_MODE environment variable', async () => {
|
|
await installAllPlatforms();
|
|
|
|
for (const platformId of ALL_PLATFORMS) {
|
|
const def = PLATFORM_REGISTRY[platformId];
|
|
const content = readFileAt(def.configPath);
|
|
expect(content, `${platformId} should set AQE_V3_MODE`).toContain('AQE_V3_MODE');
|
|
}
|
|
});
|
|
});
|
|
});
|