Files
flipstone-radar/server/config.test.ts

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);
});
});
});