327 lines
7.6 KiB
TypeScript
327 lines
7.6 KiB
TypeScript
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
import { readFileSync } from 'fs';
|
|
import { loadConfig, validateConfig, Config } from './config';
|
|
|
|
// Mock fs module
|
|
vi.mock('fs');
|
|
const mockedReadFileSync = vi.mocked(readFileSync);
|
|
|
|
describe('Configuration Management', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
afterEach(() => {
|
|
// Clear environment variables
|
|
delete process.env.GITHUB_TOKEN;
|
|
delete process.env.PORT;
|
|
delete process.env.HOST;
|
|
});
|
|
|
|
describe('loadConfig', () => {
|
|
it('should load configuration from file', () => {
|
|
const mockConfig = {
|
|
github: {
|
|
token: 'test-token',
|
|
repositories: [
|
|
{ owner: 'test-owner', name: 'test-repo' }
|
|
]
|
|
},
|
|
server: {
|
|
port: 3001,
|
|
host: '0.0.0.0'
|
|
},
|
|
cache: {
|
|
timeoutSeconds: 300
|
|
}
|
|
};
|
|
|
|
mockedReadFileSync.mockReturnValueOnce(JSON.stringify(mockConfig));
|
|
|
|
const result = loadConfig();
|
|
|
|
expect(result).toEqual(mockConfig);
|
|
expect(mockedReadFileSync).toHaveBeenCalledWith(
|
|
expect.stringContaining('config.json'),
|
|
'utf8'
|
|
);
|
|
});
|
|
|
|
it('should use environment variables when config file is not found', () => {
|
|
process.env.GITHUB_TOKEN = 'env-token';
|
|
process.env.PORT = '8080';
|
|
process.env.HOST = '127.0.0.1';
|
|
|
|
mockedReadFileSync.mockImplementationOnce(() => {
|
|
throw new Error('ENOENT: no such file or directory');
|
|
});
|
|
|
|
const result = loadConfig();
|
|
|
|
expect(result).toEqual({
|
|
github: {
|
|
token: 'env-token',
|
|
repositories: []
|
|
},
|
|
server: {
|
|
port: 8080,
|
|
host: '127.0.0.1'
|
|
},
|
|
cache: {
|
|
timeoutSeconds: 300
|
|
}
|
|
});
|
|
});
|
|
|
|
it('should merge file config with environment variables', () => {
|
|
process.env.GITHUB_TOKEN = 'env-token';
|
|
process.env.PORT = '8080';
|
|
|
|
const mockConfig = {
|
|
github: {
|
|
repositories: [
|
|
{ owner: 'test-owner', name: 'test-repo' }
|
|
]
|
|
},
|
|
server: {
|
|
host: '0.0.0.0'
|
|
},
|
|
cache: {
|
|
timeoutSeconds: 600
|
|
}
|
|
};
|
|
|
|
mockedReadFileSync.mockReturnValueOnce(JSON.stringify(mockConfig));
|
|
|
|
const result = loadConfig();
|
|
|
|
expect(result).toEqual({
|
|
github: {
|
|
token: 'env-token', // From environment
|
|
repositories: [
|
|
{ owner: 'test-owner', name: 'test-repo' }
|
|
]
|
|
},
|
|
server: {
|
|
port: 8080, // From environment
|
|
host: '0.0.0.0' // From file
|
|
},
|
|
cache: {
|
|
timeoutSeconds: 600 // From file
|
|
}
|
|
});
|
|
});
|
|
|
|
it('should use default values when neither file nor env vars are provided', () => {
|
|
mockedReadFileSync.mockImplementationOnce(() => {
|
|
throw new Error('ENOENT: no such file or directory');
|
|
});
|
|
|
|
const result = loadConfig();
|
|
|
|
expect(result).toEqual({
|
|
github: {
|
|
token: '',
|
|
repositories: []
|
|
},
|
|
server: {
|
|
port: 3001,
|
|
host: '0.0.0.0'
|
|
},
|
|
cache: {
|
|
timeoutSeconds: 300
|
|
}
|
|
});
|
|
});
|
|
|
|
it('should handle invalid JSON in config file', () => {
|
|
mockedReadFileSync.mockReturnValueOnce('invalid json');
|
|
|
|
const result = loadConfig();
|
|
|
|
// Should fallback to defaults
|
|
expect(result).toEqual({
|
|
github: {
|
|
token: '',
|
|
repositories: []
|
|
},
|
|
server: {
|
|
port: 3001,
|
|
host: '0.0.0.0'
|
|
},
|
|
cache: {
|
|
timeoutSeconds: 300
|
|
}
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('validateConfig', () => {
|
|
it('should validate correct configuration', () => {
|
|
const config: Config = {
|
|
github: {
|
|
token: 'test-token',
|
|
repositories: [
|
|
{ owner: 'test-owner', name: 'test-repo' }
|
|
]
|
|
},
|
|
server: {
|
|
port: 3001,
|
|
host: '0.0.0.0'
|
|
},
|
|
cache: {
|
|
timeoutSeconds: 300
|
|
}
|
|
};
|
|
|
|
expect(() => validateConfig(config)).not.toThrow();
|
|
});
|
|
|
|
it('should throw error when GitHub token is missing', () => {
|
|
const config: Config = {
|
|
github: {
|
|
token: '',
|
|
repositories: [
|
|
{ owner: 'test-owner', name: 'test-repo' }
|
|
]
|
|
},
|
|
server: {
|
|
port: 3001,
|
|
host: '0.0.0.0'
|
|
},
|
|
cache: {
|
|
timeoutSeconds: 300
|
|
}
|
|
};
|
|
|
|
expect(() => validateConfig(config)).toThrow('GitHub token is required');
|
|
});
|
|
|
|
it('should throw error when no repositories are configured', () => {
|
|
const config: Config = {
|
|
github: {
|
|
token: 'test-token',
|
|
repositories: []
|
|
},
|
|
server: {
|
|
port: 3001,
|
|
host: '0.0.0.0'
|
|
},
|
|
cache: {
|
|
timeoutSeconds: 300
|
|
}
|
|
};
|
|
|
|
expect(() => validateConfig(config)).toThrow('At least one repository is required');
|
|
});
|
|
|
|
it('should throw error for invalid repository configuration', () => {
|
|
const config: Config = {
|
|
github: {
|
|
token: 'test-token',
|
|
repositories: [
|
|
{ owner: '', name: 'test-repo' }
|
|
]
|
|
},
|
|
server: {
|
|
port: 3001,
|
|
host: '0.0.0.0'
|
|
},
|
|
cache: {
|
|
timeoutSeconds: 300
|
|
}
|
|
};
|
|
|
|
expect(() => validateConfig(config)).toThrow('Invalid repository configuration');
|
|
});
|
|
|
|
it('should throw error for repository missing name', () => {
|
|
const config: Config = {
|
|
github: {
|
|
token: 'test-token',
|
|
repositories: [
|
|
{ owner: 'test-owner', name: '' }
|
|
]
|
|
},
|
|
server: {
|
|
port: 3001,
|
|
host: '0.0.0.0'
|
|
},
|
|
cache: {
|
|
timeoutSeconds: 300
|
|
}
|
|
};
|
|
|
|
expect(() => validateConfig(config)).toThrow('Invalid repository configuration');
|
|
});
|
|
|
|
it('should validate multiple repositories', () => {
|
|
const config: Config = {
|
|
github: {
|
|
token: 'test-token',
|
|
repositories: [
|
|
{ owner: 'owner1', name: 'repo1' },
|
|
{ owner: 'owner2', name: 'repo2' },
|
|
{ owner: 'owner3', name: 'repo3' }
|
|
]
|
|
},
|
|
server: {
|
|
port: 3001,
|
|
host: '0.0.0.0'
|
|
},
|
|
cache: {
|
|
timeoutSeconds: 300
|
|
}
|
|
};
|
|
|
|
expect(() => validateConfig(config)).not.toThrow();
|
|
});
|
|
});
|
|
|
|
describe('cache configuration', () => {
|
|
it('should use default cache timeout when not specified', () => {
|
|
const mockConfig = {
|
|
github: {
|
|
token: 'test-token',
|
|
repositories: [
|
|
{ owner: 'test-owner', name: 'test-repo' }
|
|
]
|
|
},
|
|
server: {
|
|
port: 3001,
|
|
host: '0.0.0.0'
|
|
}
|
|
};
|
|
|
|
mockedReadFileSync.mockReturnValueOnce(JSON.stringify(mockConfig));
|
|
|
|
const result = loadConfig();
|
|
|
|
expect(result.cache?.timeoutSeconds).toBe(300);
|
|
});
|
|
|
|
it('should use custom cache timeout when specified', () => {
|
|
const mockConfig = {
|
|
github: {
|
|
token: 'test-token',
|
|
repositories: [
|
|
{ owner: 'test-owner', name: 'test-repo' }
|
|
]
|
|
},
|
|
server: {
|
|
port: 3001,
|
|
host: '0.0.0.0'
|
|
},
|
|
cache: {
|
|
timeoutSeconds: 600
|
|
}
|
|
};
|
|
|
|
mockedReadFileSync.mockReturnValueOnce(JSON.stringify(mockConfig));
|
|
|
|
const result = loadConfig();
|
|
|
|
expect(result.cache?.timeoutSeconds).toBe(600);
|
|
});
|
|
});
|
|
}); |