BMAD-METHOD/.patch/478/test-integration-status-com...

470 lines
16 KiB
JavaScript

/**
* Integration Tests for Status Command with Installation Detection
* Tests the end-to-end behavior of the status command
* Issue #478: Status command not detecting BMAD installations
*/
const path = require('node:path');
const fs = require('fs-extra');
const { execSync } = require('node:child_process');
describe('Status Command Integration Tests - status-command-detection.test.js', () => {
let testProjectRoot;
let originalCwd;
beforeEach(async () => {
// Save original working directory
originalCwd = process.cwd();
// Create test project
testProjectRoot = path.join(__dirname, '..', 'fixtures', `status-test-${Date.now()}`);
await fs.ensureDir(testProjectRoot);
});
afterEach(async () => {
// Restore original working directory
process.chdir(originalCwd);
// Clean up test project
if (await fs.pathExists(testProjectRoot)) {
await fs.remove(testProjectRoot);
}
});
// ==========================================
// SUITE 1: Status Command from Project Root
// ==========================================
describe('Status Command from Project Root', () => {
test('REPRODUCES BUG: should detect status when run from project root', async () => {
// Setup: Create project with BMAD installation
const bmadPath = path.join(testProjectRoot, 'bmad');
await fs.ensureDir(bmadPath);
await fs.ensureDir(path.join(bmadPath, 'core'));
// Create minimal install manifest
const manifestPath = path.join(bmadPath, '.install-manifest.yaml');
await fs.writeFile(manifestPath, 'version: 1.0.0\ninstalled_at: "2025-01-01T00:00:00Z"\n');
// Change to project root
process.chdir(testProjectRoot);
// Execute: Run status command from project root
// Simulating: npx bmad-method status
const { Installer } = require('../../../tools/cli/installers/lib/core/installer');
const installer = new Installer();
const status = await installer.getStatus('.');
// Assert: Should detect installation
expect(status.installed).toBe(true);
// This might work or fail depending on cwd handling
});
test('should detect with explicit current directory', async () => {
// Setup
const bmadPath = path.join(testProjectRoot, 'bmad');
await fs.ensureDir(bmadPath);
await fs.ensureDir(path.join(bmadPath, 'core'));
process.chdir(testProjectRoot);
// Execute: Explicit current directory
const { Installer } = require('../../../tools/cli/installers/lib/core/installer');
const installer = new Installer();
const status = await installer.getStatus(process.cwd());
// Assert
expect(status.installed).toBe(true);
});
test('should work with absolute path to project root', async () => {
// Setup
const bmadPath = path.join(testProjectRoot, 'bmad');
await fs.ensureDir(bmadPath);
await fs.ensureDir(path.join(bmadPath, 'core'));
// Execute: Absolute path
const { Installer } = require('../../../tools/cli/installers/lib/core/installer');
const installer = new Installer();
const status = await installer.getStatus(testProjectRoot);
// Assert
expect(status.installed).toBe(true);
});
});
// ==========================================
// SUITE 2: Status Command from Subdirectory
// ==========================================
describe('Status Command from Subdirectory (Issue #478)', () => {
test('REPRODUCES BUG: should search up to find parent BMAD installation', async () => {
// Setup: Create typical project structure
// project/
// ├── bmad/ ← BMAD installed here
// ├── src/
// │ └── components/
// └── package.json
const bmadPath = path.join(testProjectRoot, 'bmad');
const srcPath = path.join(testProjectRoot, 'src', 'components');
await fs.ensureDir(bmadPath);
await fs.ensureDir(path.join(bmadPath, 'core'));
await fs.ensureDir(srcPath);
await fs.writeJSON(path.join(testProjectRoot, 'package.json'), { name: 'test-project' });
// Change to subdirectory
process.chdir(srcPath);
// Execute: Run status from subdirectory
const { Installer } = require('../../../tools/cli/installers/lib/core/installer');
const installer = new Installer();
const status = await installer.getStatus('.');
// Assert: Should find installation in parent directory
expect(status.installed).toBe(true); // ❌ FAILS - BUG #478
});
test('REPRODUCES BUG: should find installation 2 levels up', async () => {
// Setup: More deeply nested
// project/
// ├── bmad/
// └── src/
// └── app/
// └── utils/
const bmadPath = path.join(testProjectRoot, 'bmad');
const deepPath = path.join(testProjectRoot, 'src', 'app', 'utils');
await fs.ensureDir(bmadPath);
await fs.ensureDir(path.join(bmadPath, 'core'));
await fs.ensureDir(deepPath);
process.chdir(deepPath);
// Execute
const { Installer } = require('../../../tools/cli/installers/lib/core/installer');
const installer = new Installer();
const status = await installer.getStatus('.');
// Assert
expect(status.installed).toBe(true); // ❌ FAILS - BUG #478
});
test('REPRODUCES BUG: should find installation 3 levels up', async () => {
// Setup: Very deeply nested
const bmadPath = path.join(testProjectRoot, 'bmad');
const veryDeepPath = path.join(testProjectRoot, 'a', 'b', 'c', 'd');
await fs.ensureDir(bmadPath);
await fs.ensureDir(path.join(bmadPath, 'core'));
await fs.ensureDir(veryDeepPath);
process.chdir(veryDeepPath);
// Execute
const { Installer } = require('../../../tools/cli/installers/lib/core/installer');
const installer = new Installer();
const status = await installer.getStatus('.');
// Assert
expect(status.installed).toBe(true); // ❌ FAILS - BUG #478
});
test('REPRODUCES BUG: should work with relative paths from subdirectory', async () => {
// Setup
const bmadPath = path.join(testProjectRoot, 'bmad');
const srcPath = path.join(testProjectRoot, 'src');
await fs.ensureDir(bmadPath);
await fs.ensureDir(path.join(bmadPath, 'core'));
await fs.ensureDir(srcPath);
process.chdir(srcPath);
// Execute: Use relative path ..
const { Installer } = require('../../../tools/cli/installers/lib/core/installer');
const installer = new Installer();
const status = await installer.getStatus('..');
// Assert
expect(status.installed).toBe(true); // ❌ FAILS
});
});
// ==========================================
// SUITE 3: Status Command with Legacy Folders
// ==========================================
describe('Status Command with Legacy Folders', () => {
test('REPRODUCES BUG: should detect legacy .bmad-core installation', async () => {
// Setup: Legacy project structure
const legacyPath = path.join(testProjectRoot, '.bmad-core');
const agentsPath = path.join(legacyPath, 'agents');
await fs.ensureDir(agentsPath);
// Execute
const { Installer } = require('../../../tools/cli/installers/lib/core/installer');
const installer = new Installer();
const status = await installer.getStatus(testProjectRoot);
// Assert
expect(status.installed).toBe(true); // ❌ FAILS - BUG
});
test('REPRODUCES BUG: should detect legacy .bmad-method installation', async () => {
// Setup
const legacyPath = path.join(testProjectRoot, '.bmad-method');
await fs.ensureDir(legacyPath);
// Execute
const { Installer } = require('../../../tools/cli/installers/lib/core/installer');
const installer = new Installer();
const status = await installer.getStatus(testProjectRoot);
// Assert
expect(status.installed).toBe(true); // ❌ FAILS - BUG
});
test('REPRODUCES BUG: should search parents for legacy .bmad-core/', async () => {
// Setup
const legacyPath = path.join(testProjectRoot, '.bmad-core');
const childPath = path.join(testProjectRoot, 'src');
await fs.ensureDir(legacyPath);
await fs.ensureDir(childPath);
process.chdir(childPath);
// Execute
const { Installer } = require('../../../tools/cli/installers/lib/core/installer');
const installer = new Installer();
const status = await installer.getStatus('.');
// Assert
expect(status.installed).toBe(true); // ❌ FAILS - BUG
});
test('REPRODUCES BUG: should prefer modern bmad/ over legacy folders', async () => {
// Setup: Both exist
const modernPath = path.join(testProjectRoot, 'bmad');
const legacyPath = path.join(testProjectRoot, '.bmad-core');
await fs.ensureDir(modernPath);
await fs.ensureDir(path.join(modernPath, 'core'));
await fs.ensureDir(legacyPath);
// Execute
const { Installer } = require('../../../tools/cli/installers/lib/core/installer');
const installer = new Installer();
const status = await installer.getStatus(testProjectRoot);
// Assert
expect(status.installed).toBe(true);
// After fix: expect(status.path).toContain('bmad'); // prefer modern
});
});
// ==========================================
// SUITE 4: Status Command Output
// ==========================================
describe('Status Command Output Validation', () => {
test('should output correct installation info when found', async () => {
// Setup: Create installation with metadata
const bmadPath = path.join(testProjectRoot, 'bmad');
const corePath = path.join(bmadPath, 'core');
await fs.ensureDir(corePath);
// Create manifest with version and IDEs
const manifestPath = path.join(bmadPath, '.install-manifest.yaml');
await fs.writeFile(
manifestPath,
`
version: 1.2.3
installed_at: "2025-01-01T00:00:00Z"
ides:
- vscode
- claude-code
`,
);
// Execute
const { Installer } = require('../../../tools/cli/installers/lib/core/installer');
const installer = new Installer();
const status = await installer.getStatus(testProjectRoot);
// Assert: Verify returned status object has expected fields
expect(status).toHaveProperty('installed');
expect(status).toHaveProperty('path');
expect(status).toHaveProperty('version');
expect(status).toHaveProperty('hasCore');
expect(status.installed).toBe(true);
expect(status.version).toBe('1.2.3');
expect(status.hasCore).toBe(true);
});
test('should include IDE info in status output', async () => {
// Setup
const bmadPath = path.join(testProjectRoot, 'bmad');
await fs.ensureDir(bmadPath);
const manifestPath = path.join(bmadPath, '.install-manifest.yaml');
await fs.writeFile(
manifestPath,
`
version: 1.0.0
ides:
- vscode
- claude-code
- github-copilot
`,
);
// Execute
const { Installer } = require('../../../tools/cli/installers/lib/core/installer');
const installer = new Installer();
const status = await installer.getStatus(testProjectRoot);
// Assert
expect(status.ides).toBeDefined();
expect(status.ides.length).toBeGreaterThan(0);
});
test('should return sensible defaults when manifest missing', async () => {
// Setup: Installation folder exists but no manifest
const bmadPath = path.join(testProjectRoot, 'bmad');
const corePath = path.join(bmadPath, 'core');
await fs.ensureDir(corePath);
// Execute
const { Installer } = require('../../../tools/cli/installers/lib/core/installer');
const installer = new Installer();
const status = await installer.getStatus(testProjectRoot);
// Assert: Should still detect as installed, with defaults
expect(status.installed).toBe(true);
expect(status.version).toBeDefined(); // or null, but not undefined
});
});
// ==========================================
// SUITE 5: Error Handling
// ==========================================
describe('Status Command Error Handling', () => {
test('should return installed=false for non-existent directory', async () => {
// Setup: Directory doesn't exist
const nonExistent = path.join(testProjectRoot, 'does-not-exist');
// Execute: Should handle gracefully
const { Installer } = require('../../../tools/cli/installers/lib/core/installer');
const installer = new Installer();
const status = await installer.getStatus(nonExistent);
// Assert
expect(status.installed).toBe(false);
});
test('should not crash with permission denied', async () => {
// Setup: This test may be OS-specific
// Skip on systems where we can't set permissions
if (process.platform === 'win32') {
// Skip on Windows - permission model is different
expect(true).toBe(true);
return;
}
// Create protected directory
const protectedDir = path.join(testProjectRoot, 'protected');
await fs.ensureDir(protectedDir);
try {
// Remove read permissions
fs.chmodSync(protectedDir, 0o000);
// Execute: Should handle permission error gracefully
const { Installer } = require('../../../tools/cli/installers/lib/core/installer');
const installer = new Installer();
// Should not throw
const status = await installer.getStatus(protectedDir);
expect(status).toBeDefined();
} finally {
// Restore permissions for cleanup
fs.chmodSync(protectedDir, 0o755);
}
});
test('should handle symlinked directories', async () => {
// Setup: Create real directory and symlink
const realBmad = path.join(testProjectRoot, 'real-bmad');
const symlinkBmad = path.join(testProjectRoot, 'link-bmad');
await fs.ensureDir(realBmad);
await fs.ensureDir(path.join(realBmad, 'core'));
try {
fs.symlinkSync(realBmad, symlinkBmad, 'dir');
// Execute: Use symlink path
const { Installer } = require('../../../tools/cli/installers/lib/core/installer');
const installer = new Installer();
const status = await installer.getStatus(path.join(testProjectRoot, 'link-bmad'));
// Assert: Should resolve symlink and detect installation
expect(status.installed).toBe(true);
} catch (error) {
// Skip if symlinks not supported (Windows without admin)
if (error.code === 'EEXIST' || error.code === 'EACCES') {
expect(true).toBe(true);
} else {
throw error;
}
}
});
});
// ==========================================
// TEST SUMMARY
// ==========================================
/*
* EXPECTED TEST RESULTS:
*
* Suite 1 (From Project Root):
* ? Root detection - UNCLEAR (may work or fail)
* ✓ Explicit cwd - PASS (if passed correctly)
* ✓ Absolute path - PASS
*
* Suite 2 (From Subdirectory):
* ✗ Find parent 1 level - FAIL (BUG #478)
* ✗ Find parent 2 levels - FAIL (BUG #478)
* ✗ Find parent 3 levels - FAIL (BUG #478)
* ✗ Relative path .. - FAIL (BUG #478)
*
* Suite 3 (Legacy Folders):
* ✗ .bmad-core detection - FAIL (BUG)
* ✗ .bmad-method detection - FAIL (BUG)
* ✗ Legacy parent search - FAIL (BUG)
* ✓ Modern preference - PASS
*
* Suite 4 (Output Validation):
* ✓ Correct info output - PASS (if detected)
* ✓ IDE info - PASS
* ✓ Sensible defaults - PASS
*
* Suite 5 (Error Handling):
* ✓ Non-existent dir - PASS
* ? Permission denied - OS-DEPENDENT
* ? Symlinks - PLATFORM-DEPENDENT
*
* SUMMARY: ~8-10 tests expected to FAIL
*/
});