Files
claude-task-master/packages/tm-core/src/config/services/config-merger.service.spec.ts
Ralph Khreish 0f3ab00f26 feat: create tm-core and apps/cli (#1093)
- add typescript
- add npm workspaces
2025-09-09 03:32:48 +02:00

238 lines
5.6 KiB
TypeScript

/**
* @fileoverview Unit tests for ConfigMerger service
*/
import { describe, it, expect, beforeEach } from 'vitest';
import { ConfigMerger, CONFIG_PRECEDENCE } from './config-merger.service.js';
describe('ConfigMerger', () => {
let merger: ConfigMerger;
beforeEach(() => {
merger = new ConfigMerger();
});
describe('addSource', () => {
it('should add configuration source', () => {
const source = {
name: 'test',
config: { test: true },
precedence: 1
};
merger.addSource(source);
const sources = merger.getSources();
expect(sources).toHaveLength(1);
expect(sources[0]).toEqual(source);
});
it('should add multiple sources', () => {
merger.addSource({ name: 'source1', config: {}, precedence: 1 });
merger.addSource({ name: 'source2', config: {}, precedence: 2 });
expect(merger.getSources()).toHaveLength(2);
});
});
describe('clearSources', () => {
it('should remove all configuration sources', () => {
merger.addSource({ name: 'test', config: {}, precedence: 1 });
merger.clearSources();
expect(merger.getSources()).toHaveLength(0);
});
});
describe('merge', () => {
it('should merge configurations based on precedence', () => {
merger.addSource({
name: 'low',
config: { a: 1, b: 2 },
precedence: 1
});
merger.addSource({
name: 'high',
config: { a: 3, c: 4 },
precedence: 2
});
const result = merger.merge();
expect(result).toEqual({
a: 3, // High precedence wins
b: 2, // Only in low
c: 4 // Only in high
});
});
it('should deep merge nested objects', () => {
merger.addSource({
name: 'base',
config: {
models: { main: 'model1', fallback: 'model2' },
storage: { type: 'file' as const }
},
precedence: 1
});
merger.addSource({
name: 'override',
config: {
models: { main: 'model3' },
storage: { encoding: 'utf8' as const }
},
precedence: 2
});
const result = merger.merge();
expect(result).toEqual({
models: {
main: 'model3', // Overridden
fallback: 'model2' // Preserved
},
storage: {
type: 'file', // Preserved
encoding: 'utf8' // Added
}
});
});
it('should handle arrays by replacement', () => {
merger.addSource({
name: 'base',
config: { items: [1, 2, 3] },
precedence: 1
});
merger.addSource({
name: 'override',
config: { items: [4, 5] },
precedence: 2
});
const result = merger.merge();
expect(result.items).toEqual([4, 5]); // Arrays are replaced, not merged
});
it('should ignore null and undefined values', () => {
merger.addSource({
name: 'base',
config: { a: 1, b: 2 },
precedence: 1
});
merger.addSource({
name: 'override',
config: { a: null, b: undefined, c: 3 } as any,
precedence: 2
});
const result = merger.merge();
expect(result).toEqual({
a: 1, // null ignored
b: 2, // undefined ignored
c: 3 // new value added
});
});
it('should return empty object when no sources', () => {
const result = merger.merge();
expect(result).toEqual({});
});
it('should use CONFIG_PRECEDENCE constants correctly', () => {
merger.addSource({
name: 'defaults',
config: { level: 'default' },
precedence: CONFIG_PRECEDENCE.DEFAULTS
});
merger.addSource({
name: 'local',
config: { level: 'local' },
precedence: CONFIG_PRECEDENCE.LOCAL
});
merger.addSource({
name: 'environment',
config: { level: 'env' },
precedence: CONFIG_PRECEDENCE.ENVIRONMENT
});
const result = merger.merge();
expect(result.level).toBe('env'); // Highest precedence wins
});
});
describe('getSources', () => {
it('should return sources sorted by precedence (highest first)', () => {
merger.addSource({ name: 'low', config: {}, precedence: 1 });
merger.addSource({ name: 'high', config: {}, precedence: 3 });
merger.addSource({ name: 'medium', config: {}, precedence: 2 });
const sources = merger.getSources();
expect(sources[0].name).toBe('high');
expect(sources[1].name).toBe('medium');
expect(sources[2].name).toBe('low');
});
it('should return a copy of sources array', () => {
merger.addSource({ name: 'test', config: {}, precedence: 1 });
const sources1 = merger.getSources();
const sources2 = merger.getSources();
expect(sources1).not.toBe(sources2); // Different array instances
expect(sources1).toEqual(sources2); // Same content
});
});
describe('hasSource', () => {
it('should return true when source exists', () => {
merger.addSource({ name: 'test', config: {}, precedence: 1 });
expect(merger.hasSource('test')).toBe(true);
});
it('should return false when source does not exist', () => {
expect(merger.hasSource('nonexistent')).toBe(false);
});
});
describe('removeSource', () => {
it('should remove source by name and return true', () => {
merger.addSource({ name: 'test', config: {}, precedence: 1 });
merger.addSource({ name: 'keep', config: {}, precedence: 2 });
const removed = merger.removeSource('test');
expect(removed).toBe(true);
expect(merger.hasSource('test')).toBe(false);
expect(merger.hasSource('keep')).toBe(true);
});
it('should return false when source does not exist', () => {
const removed = merger.removeSource('nonexistent');
expect(removed).toBe(false);
});
it('should handle removing all sources', () => {
merger.addSource({ name: 'test1', config: {}, precedence: 1 });
merger.addSource({ name: 'test2', config: {}, precedence: 2 });
merger.removeSource('test1');
merger.removeSource('test2');
expect(merger.getSources()).toHaveLength(0);
});
});
});