372 lines
12 KiB
JavaScript
372 lines
12 KiB
JavaScript
/**
|
|
* Integration Tests - Backward Compatibility
|
|
* Tests for handling old manifest formats and migrations
|
|
* File: test/integration/backward-compatibility.test.js
|
|
*/
|
|
|
|
const path = require('node:path');
|
|
const fs = require('fs-extra');
|
|
const yaml = require('js-yaml');
|
|
const { Installer } = require('../../tools/cli/installers/lib/core/installer');
|
|
|
|
describe('Backward Compatibility', () => {
|
|
let tempDir;
|
|
let installer;
|
|
|
|
beforeEach(() => {
|
|
tempDir = path.join(__dirname, '../fixtures/temp', `compat-${Date.now()}`);
|
|
fs.ensureDirSync(tempDir);
|
|
installer = new Installer();
|
|
});
|
|
|
|
afterEach(() => {
|
|
if (fs.existsSync(tempDir)) {
|
|
fs.removeSync(tempDir);
|
|
}
|
|
});
|
|
|
|
describe('Old Manifest Format Support', () => {
|
|
// Test 8.1: Handle Old Manifest Format
|
|
it('should handle manifest from v4.30.0', async () => {
|
|
const projectDir = tempDir;
|
|
const bmadDir = path.join(projectDir, '.bmad-core');
|
|
fs.ensureDirSync(bmadDir);
|
|
|
|
// Old format manifest
|
|
const oldManifest = {
|
|
version: '4.30.0',
|
|
installed_at: '2025-01-01T00:00:00.000Z',
|
|
install_type: 'full',
|
|
// Note: Might be missing newer fields
|
|
};
|
|
|
|
const manifestPath = path.join(bmadDir, 'install-manifest.yaml');
|
|
fs.writeFileSync(manifestPath, yaml.dump(oldManifest));
|
|
|
|
const config = await installer.loadConfigForProject(projectDir);
|
|
|
|
expect(config).toBeDefined();
|
|
expect(config.getConfig('version')).toBe('4.30.0');
|
|
expect(config.getConfig('install_type')).toBe('full');
|
|
});
|
|
|
|
it('should handle v3.x manifest format', async () => {
|
|
const projectDir = tempDir;
|
|
const bmadDir = path.join(projectDir, '.bmad-core');
|
|
fs.ensureDirSync(bmadDir);
|
|
|
|
// V3 format manifest
|
|
const v3Manifest = {
|
|
version: '3.5.0',
|
|
installed_at: '2024-06-01T00:00:00.000Z',
|
|
installation_type: 'full', // Different field name
|
|
};
|
|
|
|
const manifestPath = path.join(bmadDir, 'install-manifest.yaml');
|
|
fs.writeFileSync(manifestPath, yaml.dump(v3Manifest));
|
|
|
|
const config = await installer.loadConfigForProject(projectDir);
|
|
|
|
// Should handle old field names with migration
|
|
expect(config).toBeDefined();
|
|
expect(config.getConfig('version')).toBe('3.5.0');
|
|
});
|
|
|
|
it('should migrate between format versions', async () => {
|
|
const oldManifest = {
|
|
version: '4.30.0',
|
|
installed_at: '2025-01-01T00:00:00Z',
|
|
install_type: 'full',
|
|
};
|
|
|
|
const migrator = installer.getMigrator();
|
|
const migratedManifest = migrator.migrate(oldManifest, '4.36.2');
|
|
|
|
expect(migratedManifest.version).toBe('4.36.2');
|
|
expect(migratedManifest.installed_at).toBeDefined();
|
|
expect(migratedManifest.install_type).toBe('full');
|
|
});
|
|
});
|
|
|
|
describe('Missing Optional Fields', () => {
|
|
// Test 8.2: Missing Optional Fields Handled
|
|
it('should handle manifest without ides_setup', async () => {
|
|
const projectDir = tempDir;
|
|
const bmadDir = path.join(projectDir, '.bmad-core');
|
|
fs.ensureDirSync(bmadDir);
|
|
|
|
const manifest = {
|
|
version: '4.32.0',
|
|
installed_at: '2025-03-01T00:00:00.000Z',
|
|
install_type: 'full',
|
|
// ides_setup is missing (added in later version)
|
|
};
|
|
|
|
const manifestPath = path.join(bmadDir, 'install-manifest.yaml');
|
|
fs.writeFileSync(manifestPath, yaml.dump(manifest));
|
|
|
|
const config = await installer.loadConfigForProject(projectDir);
|
|
|
|
expect(config).toBeDefined();
|
|
expect(config.getConfig('ides_setup', [])).toEqual([]);
|
|
expect(config.getConfig('version')).toBe('4.32.0');
|
|
});
|
|
|
|
// Test 8.3: Missing expansion_packs Field
|
|
it('should handle manifest without expansion_packs', async () => {
|
|
const projectDir = tempDir;
|
|
const bmadDir = path.join(projectDir, '.bmad-core');
|
|
fs.ensureDirSync(bmadDir);
|
|
|
|
const manifest = {
|
|
version: '4.34.0',
|
|
installed_at: '2025-05-01T00:00:00.000Z',
|
|
install_type: 'full',
|
|
ides_setup: ['claude-code'],
|
|
// expansion_packs is missing
|
|
};
|
|
|
|
const manifestPath = path.join(bmadDir, 'install-manifest.yaml');
|
|
fs.writeFileSync(manifestPath, yaml.dump(manifest));
|
|
|
|
const config = await installer.loadConfigForProject(projectDir);
|
|
|
|
expect(config).toBeDefined();
|
|
expect(config.getConfig('expansion_packs', [])).toEqual([]);
|
|
expect(config.getConfig('ides_setup')).toEqual(['claude-code']);
|
|
});
|
|
|
|
it('should provide safe defaults for missing fields', async () => {
|
|
const manifest = {
|
|
version: '4.33.0',
|
|
installed_at: '2025-04-01T00:00:00Z',
|
|
install_type: 'full',
|
|
};
|
|
|
|
const config = {
|
|
getConfig: (key, defaultValue) => manifest[key] ?? defaultValue,
|
|
};
|
|
|
|
const defaults = {
|
|
ides_setup: config.getConfig('ides_setup', []),
|
|
expansion_packs: config.getConfig('expansion_packs', []),
|
|
doc_organization: config.getConfig('doc_organization', 'by-module'),
|
|
};
|
|
|
|
expect(defaults.ides_setup).toEqual([]);
|
|
expect(defaults.expansion_packs).toEqual([]);
|
|
expect(defaults.doc_organization).toBe('by-module');
|
|
});
|
|
});
|
|
|
|
describe('Version Comparison Backward Compat', () => {
|
|
// Test 8.4: Version Comparison Backward Compat
|
|
it('should handle pre-release version formats', async () => {
|
|
const projectDir = tempDir;
|
|
const bmadDir = path.join(projectDir, '.bmad-core');
|
|
fs.ensureDirSync(bmadDir);
|
|
|
|
const manifest = {
|
|
version: '4.36.2-beta1',
|
|
installed_at: '2025-08-01T00:00:00.000Z',
|
|
install_type: 'full',
|
|
};
|
|
|
|
const manifestPath = path.join(bmadDir, 'install-manifest.yaml');
|
|
fs.writeFileSync(manifestPath, yaml.dump(manifest));
|
|
|
|
const mode = installer.detectInstallMode(projectDir, '4.36.2');
|
|
|
|
// Beta version < release version = update
|
|
expect(mode).toBe('update');
|
|
});
|
|
|
|
it('should handle alpha/beta/rc versions', async () => {
|
|
const versionCases = [
|
|
{ installed: '4.36.0-alpha', current: '4.36.0', mode: 'update' },
|
|
{ installed: '4.36.0-beta', current: '4.36.0', mode: 'update' },
|
|
{ installed: '4.36.0-rc1', current: '4.36.0', mode: 'update' },
|
|
{ installed: '4.36.0-rc1', current: '4.36.0-rc2', mode: 'update' },
|
|
{ installed: '4.36.0-rc1', current: '4.36.0-rc1', mode: 'reinstall' },
|
|
];
|
|
|
|
for (const { installed, current, mode: expectedMode } of versionCases) {
|
|
fs.removeSync(bmadDir);
|
|
const bmadDir = path.join(projectDir, '.bmad-core');
|
|
fs.ensureDirSync(bmadDir);
|
|
|
|
const manifest = {
|
|
version: installed,
|
|
installed_at: '2025-08-01T00:00:00Z',
|
|
install_type: 'full',
|
|
};
|
|
|
|
const manifestPath = path.join(bmadDir, 'install-manifest.yaml');
|
|
fs.writeFileSync(manifestPath, yaml.dump(manifest));
|
|
|
|
const mode = installer.detectInstallMode(projectDir, current);
|
|
expect(mode).toBe(expectedMode);
|
|
}
|
|
});
|
|
|
|
it('should handle versions with different segment counts', async () => {
|
|
const testCases = [
|
|
{ v1: '4.36', v2: '4.36.0', compatible: true },
|
|
{ v1: '4', v2: '4.36.2', compatible: true },
|
|
{ v1: '4.36.2.1', v2: '4.36.2', compatible: true },
|
|
];
|
|
|
|
for (const { v1, v2, compatible } of testCases) {
|
|
const detector = installer.getDetector();
|
|
const result = detector.canCompareVersions(v1, v2);
|
|
expect(result || !compatible).toBeDefined();
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('Field Name Migration', () => {
|
|
it('should handle renamed configuration fields', async () => {
|
|
const oldManifest = {
|
|
version: '4.20.0',
|
|
installed_at: '2024-01-01T00:00:00Z',
|
|
installation_mode: 'full', // Old name
|
|
};
|
|
|
|
const migrator = installer.getMigrator();
|
|
const migratedManifest = migrator.migrateFields(oldManifest);
|
|
|
|
expect(migratedManifest.install_type || migratedManifest.installation_mode).toBeDefined();
|
|
});
|
|
|
|
it('should preserve unknown fields during migration', async () => {
|
|
const oldManifest = {
|
|
version: '4.30.0',
|
|
installed_at: '2025-01-01T00:00Z',
|
|
install_type: 'full',
|
|
custom_field: 'custom_value',
|
|
user_preference: 'should-preserve',
|
|
};
|
|
|
|
const migrator = installer.getMigrator();
|
|
const migratedManifest = migrator.migrate(oldManifest, '4.36.2');
|
|
|
|
expect(migratedManifest.custom_field).toBe('custom_value');
|
|
expect(migratedManifest.user_preference).toBe('should-preserve');
|
|
});
|
|
});
|
|
|
|
describe('Installation Type Variations', () => {
|
|
it('should handle various installation type values', async () => {
|
|
const installTypes = ['full', 'minimal', 'custom', 'lite', 'pro', 'enterprise'];
|
|
|
|
for (const type of installTypes) {
|
|
const manifest = {
|
|
version: '4.36.2',
|
|
installed_at: '2025-08-12T00:00:00Z',
|
|
install_type: type,
|
|
};
|
|
|
|
const config = {
|
|
getConfig: (key) => manifest[key],
|
|
};
|
|
|
|
expect(config.getConfig('install_type')).toBe(type);
|
|
}
|
|
});
|
|
|
|
it('should handle custom installation profiles', async () => {
|
|
const manifest = {
|
|
version: '4.36.2',
|
|
installed_at: '2025-08-12T00:00:00Z',
|
|
install_type: 'custom',
|
|
custom_profile: {
|
|
agents: ['agent1', 'agent2'],
|
|
modules: ['module1', 'module2'],
|
|
},
|
|
};
|
|
|
|
const config = {
|
|
getConfig: (key) => manifest[key],
|
|
};
|
|
|
|
expect(config.getConfig('custom_profile')).toBeDefined();
|
|
expect(config.getConfig('custom_profile').agents).toEqual(['agent1', 'agent2']);
|
|
});
|
|
});
|
|
|
|
describe('IDE Configuration Compatibility', () => {
|
|
it('should recognize old IDE names and map to new ones', async () => {
|
|
const oldManifest = {
|
|
version: '4.25.0',
|
|
installed_at: '2024-12-01T00:00:00Z',
|
|
install_type: 'full',
|
|
ides: ['claude-code-v1', 'github-copilot-v2'], // Old IDE names
|
|
};
|
|
|
|
const migrator = installer.getMigrator();
|
|
const migratedManifest = migrator.migrateIdeNames(oldManifest);
|
|
|
|
// Should be converted to new names or handled gracefully
|
|
expect(migratedManifest.ides_setup).toBeDefined();
|
|
});
|
|
|
|
it('should handle unknown IDE names gracefully', async () => {
|
|
const manifest = {
|
|
version: '4.36.2',
|
|
installed_at: '2025-08-12T00:00:00Z',
|
|
install_type: 'full',
|
|
ides_setup: ['claude-code', 'unknown-ide', 'cline'],
|
|
};
|
|
|
|
const config = {
|
|
getConfig: (key) => manifest[key],
|
|
};
|
|
|
|
const ides = config.getConfig('ides_setup', []);
|
|
expect(ides).toContain('claude-code');
|
|
expect(ides).toContain('unknown-ide');
|
|
expect(ides).toContain('cline');
|
|
});
|
|
});
|
|
|
|
describe('Installation Timestamp Handling', () => {
|
|
it('should preserve installation timestamp during update', async () => {
|
|
const originalInstallTime = '2025-01-15T10:30:00.000Z';
|
|
|
|
const oldManifest = {
|
|
version: '4.30.0',
|
|
installed_at: originalInstallTime,
|
|
install_type: 'full',
|
|
};
|
|
|
|
const migrator = installer.getMigrator();
|
|
const preserveTimestamp = !migrator.shouldUpdateTimestamp('update');
|
|
|
|
if (preserveTimestamp) {
|
|
expect(oldManifest.installed_at).toBe(originalInstallTime);
|
|
}
|
|
});
|
|
|
|
it('should update modification timestamp on update', async () => {
|
|
const manifest = {
|
|
version: '4.30.0',
|
|
installed_at: '2025-01-15T10:30:00Z',
|
|
install_type: 'full',
|
|
modified_at: '2025-01-15T10:30:00Z', // Optional field
|
|
};
|
|
|
|
const config = {
|
|
getConfig: (key) => manifest[key],
|
|
setConfig: (key, value) => {
|
|
manifest[key] = value;
|
|
},
|
|
};
|
|
|
|
// Update modification time
|
|
config.setConfig('modified_at', new Date().toISOString());
|
|
|
|
expect(config.getConfig('modified_at')).not.toBe(manifest.installed_at);
|
|
});
|
|
});
|
|
});
|