Select Git revision
index.test.ts
-
Sigmund, Dominik authoredSigmund, Dominik authored
index.test.ts 10.73 KiB
import { promises as fs } from 'fs';
import { createConfig } from './index';
// Test JSON data
const jsonDefaults = {
setting: 'defaultvalue',
another: {
setting: 'avalue',
},
};
const jsonLocals = {
setting: 'localvalue',
another: {
more: 'stuff',
},
even: {
deeper: {
key: 'sodeep',
},
},
};
describe('Config', () => {
it('should only have values from config.defaults.json', async () => {
await fs.writeFile('config.defaults.json', JSON.stringify(jsonDefaults));
const config = createConfig();
await fs.unlink('config.defaults.json');
expect(config.setting).toBe('defaultvalue');
expect(config.another.setting).toBe('avalue');
});
it('should only have values from config.json', async () => {
await fs.writeFile('config.json', JSON.stringify(jsonLocals));
const config = createConfig();
await fs.unlink('config.json');
expect(config.setting).toBe('localvalue');
expect(config.another.more).toBe('stuff');
});
it('should have both values with preference to config.json', async () => {
await fs.writeFile('config.defaults.json', JSON.stringify(jsonDefaults));
await fs.writeFile('config.json', JSON.stringify(jsonLocals));
const config = createConfig();
await fs.unlink('config.json');
await fs.unlink('config.defaults.json');
expect(config.setting).toBe('localvalue');
expect(config.another.more).toBe('stuff');
expect(config.another.setting).toBe('avalue');
});
it('should respect a given basePath', async () => {
// Create the tmp directory only if it doesn't exist
await fs.mkdir('tmp', { recursive: true });
await fs.writeFile('tmp/config.defaults.json', JSON.stringify(jsonDefaults));
await fs.writeFile('tmp/config.json', JSON.stringify(jsonLocals));
const config = createConfig('tmp');
await fs.unlink('tmp/config.json');
await fs.unlink('tmp/config.defaults.json');
// Remove the tmp directory after the test
await fs.rmdir('tmp', { recursive: true });
expect(config.setting).toBe('localvalue');
expect(config.another.more).toBe('stuff');
expect(config.another.setting).toBe('avalue');
});
it('should have all values with preference to env', async () => {
await fs.writeFile('config.defaults.json', JSON.stringify(jsonDefaults));
await fs.writeFile('config.json', JSON.stringify(jsonLocals));
process.env['SETTING'] = 'overwritten-by-env';
process.env['ANOTHER_MORE'] = 'false';
process.env['EVEN_DEEPER_KEY'] = 'true';
const config = createConfig();
await fs.unlink('config.json');
await fs.unlink('config.defaults.json');
delete process.env['SETTING'];
delete process.env['ANOTHER_MORE'];
delete process.env['EVEN_DEEPER_KEY'];
expect(config.setting).toBe('overwritten-by-env');
expect(config.another.more).toBe(false);
expect(config.even.deeper.key).toBe(true);
expect(config.another.setting).toBe('avalue');
});
it('should have all values with preference to env and prefix', async () => {
await fs.writeFile('config.defaults.json', JSON.stringify(jsonDefaults));
await fs.writeFile('config.json', JSON.stringify(jsonLocals));
process.env['P_SETTING'] = 'overwritten-by-env';
process.env['P_ANOTHER_MORE'] = 'false';
process.env['P_EVEN_DEEPER_KEY'] = 'true';
const config = createConfig(undefined, 'p');
await fs.unlink('config.json');
await fs.unlink('config.defaults.json');
delete process.env['P_SETTING'];
delete process.env['P_ANOTHER_MORE'];
delete process.env['P_EVEN_DEEPER_KEY'];
expect(config.setting).toBe('overwritten-by-env');
expect(config.another.more).toBe(false);
expect(config.even.deeper.key).toBe(true);
expect(config.another.setting).toBe('avalue');
});
it('should read in a file when given', async () => {
jsonLocals.setting = 'file:file.txt';
await fs.writeFile('config.defaults.json', JSON.stringify(jsonDefaults));
await fs.writeFile('config.json', JSON.stringify(jsonLocals));
await fs.writeFile('file.txt', 'value-from-file');
const config = createConfig();
await fs.unlink('config.json');
await fs.unlink('config.defaults.json');
await fs.unlink('file.txt');
jsonLocals.setting = 'localvalue';
expect(config.setting).toBe('value-from-file');
});
it('should show a message and keep the setting when file given but not found', async () => {
jsonLocals.setting = 'file:file.txt';
await fs.writeFile('config.defaults.json', JSON.stringify(jsonDefaults));
await fs.writeFile('config.json', JSON.stringify(jsonLocals));
const config = createConfig();
await fs.unlink('config.json');
await fs.unlink('config.defaults.json');
jsonLocals.setting = 'localvalue';
expect(config.setting).toBe('file:file.txt');
});
it('should _reload if asked', async () => {
await fs.writeFile('config.defaults.json', JSON.stringify(jsonDefaults));
await fs.writeFile('config.json', JSON.stringify(jsonLocals));
const config = createConfig();
expect(config.setting).toBe('localvalue');
jsonLocals.setting = 'reloaded-value';
await fs.writeFile('config.json', JSON.stringify(jsonLocals));
config._reload();
await fs.unlink('config.json');
await fs.unlink('config.defaults.json');
jsonLocals.setting = 'localvalue';
expect(config.setting).toBe('reloaded-value');
});
it('should redact passwords if using _show', async () => {
jsonLocals.even.deeper.key = 'password123';
await fs.writeFile('config.defaults.json', JSON.stringify(jsonDefaults));
await fs.writeFile('config.json', JSON.stringify(jsonLocals));
const config = createConfig();
await fs.unlink('config.json');
await fs.unlink('config.defaults.json');
jsonLocals.even.deeper.key = 'sodeep';
expect(config._show().even.deeper.key).toBe('REDACTED');
});
it('should handle missing config.defaults.json gracefully', async () => {
// Ensure config.defaults.json does not exist
await fs.unlink('config.defaults.json').catch(() => {}); // Ignore errors if the file doesn't exist
const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
const config = createConfig();
// Ensure no crash and empty config
expect(config.setting).toBeUndefined();
// Check if the missing file message was logged
expect(consoleSpy).toHaveBeenCalledWith(
expect.stringContaining(`Defaults file missing`)
);
consoleSpy.mockRestore();
});
it('should handle missing config.json gracefully', async () => {
// Create config.defaults.json but ensure config.json does not exist
await fs.writeFile('config.defaults.json', JSON.stringify(jsonDefaults));
await fs.unlink('config.json').catch(() => {});
const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
const config = createConfig();
await fs.unlink('config.defaults.json');
// Expect log message for the missing file
expect(consoleSpy).toHaveBeenCalledWith(
expect.stringContaining('local file missing')
);
// Defaults should still be loaded
expect(config.setting).toBe('defaultvalue');
expect(config.another.setting).toBe('avalue');
consoleSpy.mockRestore();
});
it('should handle invalid JSON content in config.defaults.json gracefully', async () => {
// Write invalid JSON to config.defaults.json
await fs.writeFile('config.defaults.json', '{ invalid json }');
await fs.writeFile('config.json', JSON.stringify(jsonLocals));
const consoleSpy = jest.spyOn(console, 'error').mockImplementation();
const config = createConfig();
// Expect a log message for invalid JSON
expect(consoleSpy).toHaveBeenCalledWith(
expect.stringContaining('Error reading defaults file')
);
// Clean up files
await fs.unlink('config.defaults.json');
await fs.unlink('config.json');
// Expect config.json to still load successfully
expect(config.setting).toBe('localvalue');
expect(config.another.more).toBe('stuff');
consoleSpy.mockRestore();
});
it('should handle invalid JSON content in config.json gracefully', async () => {
// Write invalid JSON to config.json
await fs.writeFile('config.defaults.json', JSON.stringify(jsonDefaults));
await fs.writeFile('config.json', '{ invalid json }');
const consoleSpy = jest.spyOn(console, 'error').mockImplementation();
const config = createConfig();
// Expect a log message for invalid JSON
expect(consoleSpy).toHaveBeenCalledWith(
expect.stringContaining('Error reading local config file')
);
// Clean up files
await fs.unlink('config.defaults.json');
await fs.unlink('config.json');
// Expect defaults to still load successfully
expect(config.setting).toBe('defaultvalue');
expect(config.another.setting).toBe('avalue');
consoleSpy.mockRestore();
});
it('should serialize only configData using toJSON()', async () => {
// Create test configuration files
await fs.writeFile('config.defaults.json', JSON.stringify(jsonDefaults));
await fs.writeFile('config.json', JSON.stringify(jsonLocals));
const config = createConfig();
// Serialize the config object to JSON
const serializedConfig = JSON.stringify(config);
// Clean up test files
await fs.unlink('config.defaults.json');
await fs.unlink('config.json');
// Parse the serialized JSON
const parsedConfig = JSON.parse(serializedConfig);
// Check that all expected configData properties are present
expect(parsedConfig.setting).toBe('localvalue');
expect(parsedConfig.another.setting).toBe('avalue'); // From defaults
expect(parsedConfig.another.more).toBe('stuff'); // From local
expect(parsedConfig.even.deeper.key).toBe('sodeep'); // From local
// Ensure internal properties are not present
expect(parsedConfig).not.toHaveProperty('configDefaults');
expect(parsedConfig).not.toHaveProperty('configLocal');
expect(parsedConfig).not.toHaveProperty('envPrefix');
});
it('should log an error if a file referenced by "file:" is missing', async () => {
// Write test configuration file
await fs.writeFile(
'config.json',
JSON.stringify({
setting: 'file:nonexistent-file.txt',
})
);
// Spy on console.error
const consoleSpy = jest.spyOn(console, 'error').mockImplementation();
const config = createConfig();
// Clean up test files
await fs.unlink('config.json');
// Ensure the original "file:" reference remains unchanged
expect(config.setting).toBe('file:nonexistent-file.txt');
// Ensure error is logged for the missing file
expect(consoleSpy).toHaveBeenCalledWith(
expect.stringContaining('An unknown error occurred')
);
consoleSpy.mockRestore();
});
});