BMAD-METHOD/.patch/478/test-unit-find-installation...

457 lines
15 KiB
JavaScript

/**
* Unit Tests for Installation Detection/Search Functionality
* Tests the behavior of finding BMAD installations in directory trees
* Issue #478: Status command not detecting BMAD installations
*/
const path = require('node:path');
const fs = require('fs-extra');
const { Installer } = require('../../../tools/cli/installers/lib/core/installer');
const { Detector } = require('../../../tools/cli/installers/lib/core/detector');
describe('Installation Detection - find-installation.test.js', () => {
let testDir;
let installer;
let detector;
beforeEach(async () => {
// Create temporary test directory
testDir = path.join(__dirname, '..', 'fixtures', `bmad-test-${Date.now()}`);
await fs.ensureDir(testDir);
installer = new Installer();
detector = new Detector();
});
afterEach(async () => {
// Clean up test directory
if (await fs.pathExists(testDir)) {
await fs.remove(testDir);
}
});
// ==========================================
// SUITE 1: Current Behavior (Expected Failures)
// ==========================================
describe('Current getStatus() Behavior - BASELINE', () => {
test('should detect installation when bmad/ folder exists at exact path', async () => {
// Setup: Create bmad folder at root
const bmadPath = path.join(testDir, 'bmad');
await fs.ensureDir(bmadPath);
// Create minimal config to mark as installed
const coreDir = path.join(bmadPath, 'core');
await fs.ensureDir(coreDir);
// Execute
const status = await installer.getStatus(testDir);
// Assert
expect(status.installed).toBe(true);
});
test('FAILS: should detect installation in parent directory', async () => {
// Setup: Create nested directory structure
// testDir/
// ├── bmad/
// └── src/
// └── components/
const bmadPath = path.join(testDir, 'bmad');
const nestedDir = path.join(testDir, 'src', 'components');
await fs.ensureDir(bmadPath);
await fs.ensureDir(nestedDir);
// Create minimal installation marker
const coreDir = path.join(bmadPath, 'core');
await fs.ensureDir(coreDir);
// Execute: call getStatus from nested directory
const status = await installer.getStatus(nestedDir);
// Assert: CURRENTLY FAILS - should find installation in parent
// This is the BUG - it returns installed: false when it should be true
expect(status.installed).toBe(true); // ❌ EXPECTED TO FAIL
});
test('FAILS: should detect legacy .bmad-core/ folder', async () => {
// Setup: Create legacy folder instead of modern bmad/
const legacyPath = path.join(testDir, '.bmad-core');
await fs.ensureDir(legacyPath);
// Execute
const status = await installer.getStatus(testDir);
// Assert: CURRENTLY FAILS - legacy folders not checked
expect(status.installed).toBe(true); // ❌ EXPECTED TO FAIL
});
test('FAILS: should detect legacy .bmad-method/ folder', async () => {
// Setup: Create legacy folder
const legacyPath = path.join(testDir, '.bmad-method');
await fs.ensureDir(legacyPath);
// Execute
const status = await installer.getStatus(testDir);
// Assert: CURRENTLY FAILS
expect(status.installed).toBe(true); // ❌ EXPECTED TO FAIL
});
test('should return not installed when no bmad folder exists', async () => {
// Setup: Empty directory
// Execute
const status = await installer.getStatus(testDir);
// Assert
expect(status.installed).toBe(false);
});
});
// ==========================================
// SUITE 2: Directory Search Test Cases
// ==========================================
describe('Directory Tree Search Behavior - ISSUE #478', () => {
test('REPRODUCES BUG: status from nested directory should find parent bmad/', async () => {
// Setup: Simulate a real project structure
// project/
// ├── bmad/ ← BMAD installation here
// │ ├── core/
// │ └── agents/
// ├── src/
// │ └── components/ ← Run status command from here
// └── package.json
const projectRoot = testDir;
const bmadPath = path.join(projectRoot, 'bmad');
const srcPath = path.join(projectRoot, 'src', 'components');
// Create structure
await fs.ensureDir(bmadPath);
await fs.ensureDir(path.join(bmadPath, 'core'));
await fs.ensureDir(srcPath);
await fs.writeJSON(path.join(projectRoot, 'package.json'), { name: 'test-project' });
// Execute: Run status from nested directory
const status = await installer.getStatus(srcPath);
// Assert: This demonstrates the bug
// Currently: installed = false (WRONG)
// After fix: installed = true (CORRECT)
expect(status.installed).toBe(true); // ❌ FAILS WITH CURRENT CODE
});
test('REPRODUCES BUG: deeply nested directory should find ancestor bmad/', async () => {
// Setup: Multiple levels deep
// project/
// ├── bmad/
// └── a/
// └── b/
// └── c/
// └── d/ ← 4 levels deep
const projectRoot = testDir;
const bmadPath = path.join(projectRoot, 'bmad');
const deepPath = path.join(projectRoot, 'a', 'b', 'c', 'd');
await fs.ensureDir(bmadPath);
await fs.ensureDir(path.join(bmadPath, 'core'));
await fs.ensureDir(deepPath);
// Execute
const status = await installer.getStatus(deepPath);
// Assert
expect(status.installed).toBe(true); // ❌ FAILS WITH CURRENT CODE
});
test('REPRODUCES BUG: should find closest installation when multiple exist', async () => {
// Setup: Multiple BMAD installations at different levels
// root/
// ├── bmad/ ← First (ancestor)
// └── projects/
// ├── bmad/ ← Second (closest, should prefer this)
// └── myapp/
const root = testDir;
const ancestorBmad = path.join(root, 'bmad');
const projectBmad = path.join(root, 'projects', 'bmad');
const myappPath = path.join(root, 'projects', 'myapp');
// Create both installations
await fs.ensureDir(ancestorBmad);
await fs.ensureDir(path.join(ancestorBmad, 'core'));
await fs.writeJSON(path.join(ancestorBmad, 'install-manifest.yaml'), { version: '1.0.0' });
await fs.ensureDir(projectBmad);
await fs.ensureDir(path.join(projectBmad, 'core'));
await fs.writeJSON(path.join(projectBmad, 'install-manifest.yaml'), { version: '2.0.0' });
await fs.ensureDir(myappPath);
// Execute
const status = await installer.getStatus(myappPath);
// Assert: Should find closest (version 2.0.0)
expect(status.installed).toBe(true); // ❌ FAILS WITH CURRENT CODE
// After fix, would also verify: expect(status.version).toBe('2.0.0');
});
test('REPRODUCES BUG: should handle search reaching filesystem root', async () => {
// Setup: Create directory with no BMAD installation anywhere
const orphanDir = path.join(testDir, 'orphan-project');
await fs.ensureDir(orphanDir);
// Execute
const status = await installer.getStatus(orphanDir);
// Assert: Should return not installed (not crash or hang)
expect(status.installed).toBe(false);
});
});
// ==========================================
// SUITE 3: Legacy Folder Detection
// ==========================================
describe('Legacy Folder Support', () => {
test('REPRODUCES BUG: should find .bmad-core/ as installation', async () => {
// Setup
const legacyPath = path.join(testDir, '.bmad-core');
await fs.ensureDir(path.join(legacyPath, 'agents'));
// Execute
const status = await installer.getStatus(testDir);
// Assert
expect(status.installed).toBe(true); // ❌ FAILS - not checked
});
test('REPRODUCES BUG: should find .bmad-method/ as installation', async () => {
// Setup
const legacyPath = path.join(testDir, '.bmad-method');
await fs.ensureDir(legacyPath);
// Execute
const status = await installer.getStatus(testDir);
// Assert
expect(status.installed).toBe(true); // ❌ FAILS
});
test('REPRODUCES BUG: should find .bmm/ as installation', async () => {
// Setup
const legacyPath = path.join(testDir, '.bmm');
await fs.ensureDir(legacyPath);
// Execute
const status = await installer.getStatus(testDir);
// Assert
expect(status.installed).toBe(true); // ❌ FAILS
});
test('REPRODUCES BUG: should find .cis/ as installation', async () => {
// Setup
const legacyPath = path.join(testDir, '.cis');
await fs.ensureDir(legacyPath);
// Execute
const status = await installer.getStatus(testDir);
// Assert
expect(status.installed).toBe(true); // ❌ FAILS
});
test('REPRODUCES BUG: should search parents for legacy folders', async () => {
// Setup: Legacy folder in parent, code in child
const legacyPath = path.join(testDir, '.bmad-core');
const childDir = path.join(testDir, 'src');
await fs.ensureDir(legacyPath);
await fs.ensureDir(childDir);
// Execute
const status = await installer.getStatus(childDir);
// Assert
expect(status.installed).toBe(true); // ❌ FAILS - doesn't search
});
});
// ==========================================
// SUITE 4: Edge Cases
// ==========================================
describe('Edge Cases', () => {
test('REPRODUCES BUG: should handle relative paths', async () => {
// Setup
const bmadPath = path.join(testDir, 'bmad');
await fs.ensureDir(bmadPath);
await fs.ensureDir(path.join(bmadPath, 'core'));
const subdirPath = path.join(testDir, 'src');
await fs.ensureDir(subdirPath);
// Execute: Pass relative path
const status = await installer.getStatus('./src');
// Assert: Current code may fail with relative paths
// After fix: should work consistently
expect(status.installed).toBe(false); // Current behavior (may vary)
});
test('REPRODUCES BUG: should skip hidden system directories', async () => {
// Setup: Create system directories that should be ignored
const gitDir = path.join(testDir, '.git');
const nodeModulesDir = path.join(testDir, 'node_modules');
const vsCodeDir = path.join(testDir, '.vscode');
const ideaDir = path.join(testDir, '.idea');
await fs.ensureDir(gitDir);
await fs.ensureDir(nodeModulesDir);
await fs.ensureDir(vsCodeDir);
await fs.ensureDir(ideaDir);
// Execute
const status = await installer.getStatus(testDir);
// Assert
expect(status.installed).toBe(false); // Should not detect these
});
test('REPRODUCES BUG: should work with absolute paths', async () => {
// Setup
const bmadPath = path.join(testDir, 'bmad');
await fs.ensureDir(bmadPath);
await fs.ensureDir(path.join(bmadPath, 'core'));
// Execute: Absolute path
const status = await installer.getStatus(testDir);
// Assert
expect(status.installed).toBe(true); // Should work
});
test('REPRODUCES BUG: should prioritize modern bmad/ over legacy folders', async () => {
// Setup: Both modern and legacy exist
const modernBmad = path.join(testDir, 'bmad');
const legacyBmad = path.join(testDir, '.bmad-core');
await fs.ensureDir(modernBmad);
await fs.ensureDir(path.join(modernBmad, 'core'));
await fs.ensureDir(legacyBmad);
// Execute
const status = await installer.getStatus(testDir);
// Assert: Should detect installation (either one is fine)
expect(status.installed).toBe(true);
// After fix could verify: expect(status.path).toContain('bmad'); // modern preferred
});
});
// ==========================================
// SUITE 5: Detector Class Tests
// ==========================================
describe('Detector Class - detect() Method', () => {
test('should work correctly when given exact bmad directory path', async () => {
// Setup: Create proper installation
const bmadPath = path.join(testDir, 'bmad');
const corePath = path.join(bmadPath, 'core');
await fs.ensureDir(corePath);
// Create minimal config
const configPath = path.join(corePath, 'config.yaml');
await fs.writeFile(configPath, 'version: 1.0.0\n');
// Execute: Pass exact path to detector
const result = await detector.detect(bmadPath);
// Assert
expect(result.installed).toBe(true);
expect(result.hasCore).toBe(true);
});
test('should return not installed for empty directory', async () => {
// Setup: Empty directory
const emptyPath = path.join(testDir, 'empty');
await fs.ensureDir(emptyPath);
// Execute
const result = await detector.detect(emptyPath);
// Assert
expect(result.installed).toBe(false);
});
test('should parse manifest correctly when present', async () => {
// Setup
const bmadPath = path.join(testDir, 'bmad');
await fs.ensureDir(bmadPath);
// Create manifest
const manifestPath = path.join(bmadPath, '.install-manifest.yaml');
const manifestContent = `
version: 1.2.3
installed_at: "2025-01-01T00:00:00Z"
ides:
- vscode
- claude-code
`;
await fs.writeFile(manifestPath, manifestContent);
// Execute
const result = await detector.detect(bmadPath);
// Assert
expect(result.version).toBe('1.2.3');
expect(result.manifest).toBeDefined();
});
});
// ==========================================
// TEST SUMMARY
// ==========================================
/*
* EXPECTED TEST RESULTS:
*
* Suite 1 (Current Behavior):
* ✓ Exact path detection - PASS
* ✗ Parent directory detection - FAIL (BUG)
* ✗ Legacy .bmad-core - FAIL (BUG)
* ✗ Legacy .bmad-method - FAIL (BUG)
* ✓ No installation - PASS
*
* Suite 2 (Directory Search):
* ✗ Nested directory - FAIL (BUG)
* ✗ Deeply nested - FAIL (BUG)
* ✗ Multiple installations - FAIL (BUG)
* ✓ Orphan directory - PASS
*
* Suite 3 (Legacy Folders):
* ✗ .bmad-core detection - FAIL (BUG)
* ✗ .bmad-method detection - FAIL (BUG)
* ✗ .bmm detection - FAIL (BUG)
* ✗ .cis detection - FAIL (BUG)
* ✗ Legacy parent search - FAIL (BUG)
*
* Suite 4 (Edge Cases):
* ? Relative paths - VARIES
* ✓ Skip system dirs - PASS
* ✓ Absolute paths - PASS
* ✓ Modern/legacy priority - PASS
*
* Suite 5 (Detector Tests):
* ✓ Exact path - PASS
* ✓ Empty directory - PASS
* ✓ Manifest parsing - PASS
*
* SUMMARY: ~12 tests expected to FAIL, demonstrating Issue #478
*/
});