Files
proffesor-for-testing__agen…/tests/integration/cli/migrate-file-operations.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

223 lines
9.3 KiB
TypeScript

/**
* Integration tests for migrate CLI file operations
*
* These tests verify that the migrate command actually performs
* file operations correctly (not just unit testing the mappings).
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
describe('Migrate File Operations', () => {
let tempDir: string;
let claudeAgentsDir: string;
// Sample v2 agent content
const createV2AgentContent = (name: string) => `---
name: ${name}
description: Test agent for migration testing
---
<qe_agent_definition>
<identity>
You are the ${name.replace('qe-', '').split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ')} Agent.
Mission: Test the migration functionality.
</identity>
<capabilities>
- ${name} specific capability
- Another capability for ${name}
</capabilities>
</qe_agent_definition>
`;
beforeEach(() => {
// Create temp directory structure
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'aqe-migrate-test-'));
claudeAgentsDir = path.join(tempDir, '.claude', 'agents');
fs.mkdirSync(claudeAgentsDir, { recursive: true });
});
afterEach(() => {
// Clean up temp directory
if (tempDir && fs.existsSync(tempDir)) {
fs.rmSync(tempDir, { recursive: true, force: true });
}
});
describe('Agent file migration', () => {
it('should rename v2 agent file to v3 name', async () => {
// Create a v2 agent file
const v2Name = 'qe-test-generator';
const v2FilePath = path.join(claudeAgentsDir, `${v2Name}.md`);
fs.writeFileSync(v2FilePath, createV2AgentContent(v2Name));
// Import and run migration logic directly
const { resolveAgentName } = await import('../../../src/migration/agent-compat.js');
const v3Name = resolveAgentName(v2Name);
expect(v3Name).toBe('qe-test-architect');
// Simulate the migration file operation
const content = fs.readFileSync(v2FilePath, 'utf-8');
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
expect(frontmatterMatch).not.toBeNull();
const frontmatter = frontmatterMatch![1];
const bodyStart = content.indexOf('---', 4) + 4;
let body = content.slice(bodyStart);
// Update frontmatter
let newFrontmatter = frontmatter.replace(/^name:\s*.+$/m, `name: ${v3Name}`);
newFrontmatter += `\nv2_compat:\n name: ${v2Name}\n deprecated_in: "3.0.0"\n removed_in: "4.0.0"`;
// Update body content
const toTitleCase = (s: string) => s.replace('qe-', '').split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ');
body = body.replace(new RegExp(toTitleCase(v2Name), 'g'), toTitleCase(v3Name));
body = body.replace(new RegExp(v2Name, 'g'), v3Name);
const newContent = `---\n${newFrontmatter}\n---${body}`;
// Write new file
const v3FilePath = path.join(claudeAgentsDir, `${v3Name}.md`);
fs.writeFileSync(v3FilePath, newContent);
// Create deprecated directory and move old file
const deprecatedDir = path.join(claudeAgentsDir, 'deprecated');
fs.mkdirSync(deprecatedDir, { recursive: true });
fs.renameSync(v2FilePath, path.join(deprecatedDir, `${v2Name}.md.v2`));
// Verify results
expect(fs.existsSync(v3FilePath)).toBe(true);
expect(fs.existsSync(v2FilePath)).toBe(false);
expect(fs.existsSync(path.join(deprecatedDir, `${v2Name}.md.v2`))).toBe(true);
});
it('should update frontmatter with v2_compat field', async () => {
const v2Name = 'qe-coverage-analyzer';
const v2FilePath = path.join(claudeAgentsDir, `${v2Name}.md`);
fs.writeFileSync(v2FilePath, createV2AgentContent(v2Name));
const { resolveAgentName } = await import('../../../src/migration/agent-compat.js');
const v3Name = resolveAgentName(v2Name);
const content = fs.readFileSync(v2FilePath, 'utf-8');
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
let newFrontmatter = frontmatterMatch![1].replace(/^name:\s*.+$/m, `name: ${v3Name}`);
newFrontmatter += `\nv2_compat:\n name: ${v2Name}\n deprecated_in: "3.0.0"\n removed_in: "4.0.0"`;
const v3FilePath = path.join(claudeAgentsDir, `${v3Name}.md`);
const bodyStart = content.indexOf('---', 4) + 4;
const body = content.slice(bodyStart);
fs.writeFileSync(v3FilePath, `---\n${newFrontmatter}\n---${body}`);
// Verify frontmatter
const migratedContent = fs.readFileSync(v3FilePath, 'utf-8');
expect(migratedContent).toContain('name: qe-coverage-specialist');
expect(migratedContent).toContain('v2_compat:');
expect(migratedContent).toContain('name: qe-coverage-analyzer');
expect(migratedContent).toContain('deprecated_in: "3.0.0"');
expect(migratedContent).toContain('removed_in: "4.0.0"');
});
it('should replace agent name in body content', async () => {
const v2Name = 'qe-test-generator';
const v2FilePath = path.join(claudeAgentsDir, `${v2Name}.md`);
fs.writeFileSync(v2FilePath, createV2AgentContent(v2Name));
const { resolveAgentName } = await import('../../../src/migration/agent-compat.js');
const v3Name = resolveAgentName(v2Name);
const content = fs.readFileSync(v2FilePath, 'utf-8');
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
const bodyStart = content.indexOf('---', 4) + 4;
let body = content.slice(bodyStart);
// Apply body replacement
const toTitleCase = (s: string) => s.replace('qe-', '').split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ');
const v2Display = toTitleCase(v2Name); // "Test Generator"
const v3Display = toTitleCase(v3Name); // "Test Architect"
body = body.replace(new RegExp(v2Display, 'g'), v3Display);
body = body.replace(new RegExp(v2Name, 'g'), v3Name);
const v3FilePath = path.join(claudeAgentsDir, `${v3Name}.md`);
let newFrontmatter = frontmatterMatch![1].replace(/^name:\s*.+$/m, `name: ${v3Name}`);
fs.writeFileSync(v3FilePath, `---\n${newFrontmatter}\n---${body}`);
// Verify body content was updated
const migratedContent = fs.readFileSync(v3FilePath, 'utf-8');
expect(migratedContent).toContain('Test Architect Agent');
expect(migratedContent).not.toContain('Test Generator Agent');
expect(migratedContent).toContain('qe-test-architect specific capability');
expect(migratedContent).not.toContain('qe-test-generator specific capability');
});
it('should handle consolidated agents (multiple v2 → one v3)', async () => {
// Both qe-gap-detector and qe-coverage-analyzer map to qe-coverage-specialist
const { resolveAgentName, getV2Names } = await import('../../../src/migration/agent-compat.js');
expect(resolveAgentName('qe-gap-detector')).toBe('qe-coverage-specialist');
expect(resolveAgentName('qe-coverage-analyzer')).toBe('qe-coverage-specialist');
const v2Names = getV2Names('qe-coverage-specialist');
expect(v2Names).toContain('qe-gap-detector');
expect(v2Names).toContain('qe-coverage-analyzer');
});
it('should preserve deprecated folder structure', async () => {
const v2Name = 'qe-parallel-executor';
const v2FilePath = path.join(claudeAgentsDir, `${v2Name}.md`);
fs.writeFileSync(v2FilePath, createV2AgentContent(v2Name));
const deprecatedDir = path.join(claudeAgentsDir, 'deprecated');
fs.mkdirSync(deprecatedDir, { recursive: true });
// Move to deprecated
const deprecatedPath = path.join(deprecatedDir, `${v2Name}.md.v2`);
fs.renameSync(v2FilePath, deprecatedPath);
// Verify
expect(fs.existsSync(deprecatedPath)).toBe(true);
expect(fs.existsSync(v2FilePath)).toBe(false);
// Content should be preserved
const content = fs.readFileSync(deprecatedPath, 'utf-8');
expect(content).toContain('name: qe-parallel-executor');
});
});
describe('Edge cases', () => {
it('should skip files without frontmatter', async () => {
const invalidContent = `# No frontmatter here
Just regular markdown content.
`;
const filePath = path.join(claudeAgentsDir, 'qe-test-generator.md');
fs.writeFileSync(filePath, invalidContent);
const content = fs.readFileSync(filePath, 'utf-8');
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
expect(frontmatterMatch).toBeNull();
});
it('should handle special regex characters in agent names', async () => {
// Test that regex replacement doesn't break on special chars
const toTitleCase = (s: string) => s.replace('qe-', '').split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ');
const v2Name = 'qe-api-contract-validator';
const v3Name = 'qe-contract-testing';
const body = `Testing ${toTitleCase(v2Name)} capabilities with ${v2Name}`;
const updatedBody = body
.replace(new RegExp(toTitleCase(v2Name), 'g'), toTitleCase(v3Name))
.replace(new RegExp(v2Name, 'g'), v3Name);
expect(updatedBody).toBe('Testing Contract Testing capabilities with qe-contract-testing');
});
});
});