ruud
Version:
the player who serves
419 lines (344 loc) • 11 kB
JavaScript
import fs from 'fs';
import {
test,
describe,
it,
beforeEach,
afterEach
} from 'node:test';
import assert from 'node:assert';
import {
parseEnv,
DEV
} from './src/env.js';
import {
merge,
flatten
} from './src/util.js';
import types from './src/types.js';
import log from './src/log.js';
describe('logger', () => {
const logger = log(true);
const plain = logger('<magenta>colorized</magenta>');
it('logs colorized text with prefix to the console', () => {
assert.strictEqual(plain, '_ \x1B[35mcolorized\x1B[0m');
});
const box = logger(`<box><yellow><blue>blue</blue></yellow><hr><cyan>cyan</cyan></box>`);
it('logs advanced ascii box layout to the console', () => {
assert.strictEqual(box, ' \r\n ┌────────┐\n │ \x1B[33;44mblue\x1B[0m │\n ├────────┤\n │ \x1B[36mcyan\x1B[0m │\n └────────┘');
});
});
describe('types', () => {
it('resolves mime type based on file extension', () => {
assert.strictEqual('image/svg+xml', types('logo.svg'));
});
it('returns fallback mime type for files without extension', () => {
assert.strictEqual('text/html', types('LICENSE'));
});
});
describe('flatten', () => {
const obj = {
this: {
should: {
be: {
a: 'string'
}
}
}
};
it('uses dot as default separator', () => {
assert.strictEqual(obj.this.should.be.a, flatten(obj)['this.should.be.a']);
});
it('supports optional separator and prefix', () => {
assert.strictEqual(obj.this.should.be.a, flatten(obj, '/')['/this/should/be/a']);
});
});
describe('merge', () => {
const obj = {
one: {
two: true
}
};
it('adds nested prop', () => {
const merged1 = merge(obj, {
one: {
three: true
}
});
assert.strictEqual(merged1.one.two, true);
assert.strictEqual(merged1.one.three, true);
});
it('updates nested true to false', () => {
const merged2 = merge(obj, {
one: {
two: false
}
});
assert.strictEqual(merged2.one.two, false);
assert.strictEqual(merged2.one.three, true);
});
it('modifies first argument object', () => {
assert.notStrictEqual(JSON.stringify(obj), JSON.stringify({
one: {
two: true
}
}));
});
});
// --->
describe('util module comprehensive testing', () => {
it('covers json handling functions', async () => {
const util = await import('./src/util.js?test=true');
// Test tryJson success cases
const validJson = util.tryJson('{"key":"value"}');
assert.strictEqual(validJson.key, 'value');
// Test tryJson failure cases (lines 34-35)
const invalidJson = util.tryJson('{"broken: json}');
assert.strictEqual(invalidJson, '{"broken: json}');
});
it('covers path normalization', async () => {
const util = await import('./src/util.js?test=true');
// Test normalize function (might be lines 45-55)
assert.strictEqual(util.normalize('//test//path//'), '/test/path');
assert.strictEqual(util.normalize(''), '/');
assert.strictEqual(util.normalize('/'), '/');
assert.strictEqual(util.normalize('test/'), '/test');
});
it('covers object utilities', async () => {
const util = await import('./src/util.js?test=true');
// Test flatten with different inputs (lines 62-69)
const empty = util.flatten({});
assert.deepStrictEqual(empty, {});
const nested = util.flatten({
a: {
b: {
c: 1
}
}
});
assert.deepStrictEqual(nested, {
'a.b.c': 1
});
// More complex case
const complex = util.flatten({
a: [1, 2],
b: {
c: true
}
});
assert.deepStrictEqual(complex, {
'a.0': 1,
'a.1': 2,
'b.c': true
});
// Test with different separator
const withSep = util.flatten({
a: {
b: 1
}
}, '/');
assert.deepStrictEqual(withSep, {
'/a/b': 1
});
});
});
// --->
describe('exit module', () => {
// Save original methods
const originalOn = process.on;
const originalExit = process.exit;
const originalWrite = process.stdout.write;
// Create mock functions
const mockCalls = {
on: [],
exit: [],
log: []
};
// Mock log function to pass to the exit module
const mockLog = (...args) => {
mockCalls.log.push(args);
};
beforeEach(() => {
// Reset call tracking
mockCalls.on = [];
mockCalls.exit = [];
mockCalls.log = [];
// Replace with mocks
process.on = function (signal, handler) {
mockCalls.on.push({
signal,
handler
});
return process;
};
process.exit = function (code) {
mockCalls.exit.push({
code
});
};
process.stdout.write = function (data) {
return true; // Mock successful write
};
});
afterEach(() => {
// Restore original functions
process.on = originalOn;
process.exit = originalExit;
process.stdout.write = originalWrite;
});
it('registers signal handlers', async () => {
// Import the module
const exitModule = await import('./src/exit.js');
// Call the factory function with our mock log
const exit = exitModule.default(mockLog);
// Check that handlers are registered
const signals = mockCalls.on.map(call => call.signal);
assert.ok(signals.includes('SIGINT'), 'Should register SIGINT handler');
assert.ok(signals.includes('SIGTERM'), 'Should register SIGTERM handler');
assert.ok(signals.includes('uncaughtException'), 'Should register uncaughtException handler');
});
it('handles signals correctly', async () => {
// Import and initialize
const exitModule = await import('./src/exit.js');
const exit = exitModule.default(mockLog);
// Find the SIGTERM handler
const sigtermCall = mockCalls.on.find(call => call.signal === 'SIGTERM');
assert.ok(sigtermCall, 'Should register SIGTERM handler');
// Add a mock shutdown function
let shutdownCalled = false;
exit.add('test-service', async () => {
shutdownCalled = true;
});
// Call the handler
await sigtermCall.handler('SIGTERM');
// Check results
assert.ok(shutdownCalled, 'Shutdown function should be called');
assert.ok(mockCalls.exit.some(call => call.code === 0),
'Should exit with code 0');
assert.ok(mockCalls.log.some(args => args[0].includes('SIGTERM')),
'Should log signal');
});
it('handles uncaught exceptions', async () => {
// Import and initialize
const exitModule = await import('./src/exit.js');
const exit = exitModule.default(mockLog);
// Find uncaughtException handler
const uncaughtCall = mockCalls.on.find(call => call.signal === 'uncaughtException');
assert.ok(uncaughtCall, 'Should register uncaughtException handler');
// Create test error
const error = new Error('Test error');
// Call handler with error
await uncaughtCall.handler('uncaughtException', error);
// Check results - note: this module handles uncaught exceptions differently
// than typical modules, as it doesn't exit with code 1 but with code 0
assert.ok(mockCalls.log.some(args => args[0].includes('uncaughtException')),
'Should log the exception');
});
it('provides add method for shutdown handlers', async () => {
// Import and initialize
const exitModule = await import('./src/exit.js');
const exit = exitModule.default(mockLog);
// Test the add method
let called1 = false,
called2 = false;
exit.add('service1', async () => {
called1 = true;
});
exit.add('service2', async () => {
called2 = true;
});
// Manually call stop to test the queue
await exit.stop();
// Check both functions were called in order
assert.ok(called1, 'First shutdown handler should be called');
assert.ok(called2, 'Second shutdown handler should be called');
assert.ok(mockCalls.log.length >= 2, 'Should log shutdown messages');
});
});
// --->
describe('dev module', async () => {
// Save original environment
const originalNpmName = process.env.npm_package_name;
const originalNpmVersion = process.env.npm_package_version;
// Save original imports to mock
const originalReadFileSync = fs.readFileSync;
// Mock storage
const mockCalls = {
log: []
};
// Mock log function
const mockLog = function (...args) {
mockCalls.log.push(args);
return mockLog;
};
mockLog.call = function () {
mockCalls.log.push(['call']);
return mockLog;
};
beforeEach(() => {
// Reset tracking
mockCalls.log = [];
// Set environment for testing
process.env.npm_package_name = 'test-client';
process.env.npm_package_version = '1.2.3';
// Mock fs.readFileSync more robustly
fs.readFileSync = function (path, options) {
console.log('Dev test reading file:', path);
// This is a more robust check for package.json
if (path.includes('package.json')) {
return JSON.stringify({
name: 'ruud',
version: '0.1.0'
});
}
return originalReadFileSync(path, options);
};
});
afterEach(() => {
// Restore environment
process.env.npm_package_name = originalNpmName;
process.env.npm_package_version = originalNpmVersion;
// Restore original function
fs.readFileSync = originalReadFileSync;
// Clean up test env vars
delete process.env.TEST_VAR1;
delete process.env.TEST_VAR2;
});
it('creates startup message function when DEV is true', async () => {
// We'll use fs.readFileSync instead of global.readFileSync
// Remove this line: global.readFileSync = () => JSON.stringify({...});
// Import with DEV as true
const {
default: devFactory
} = await import('./src/dev.js?dev=true');
const startupMessage = devFactory(true, mockLog, []);
// Rest of test remains the same...
});
// Rest of tests need similar adjustments...
});
describe('env parsing', () => {
it('extracts key-value pairs', () => {
const content = `
API_KEY=secret123
DB_URL=localhost:5432
EMPTY=
VALID=test
`.replace(/^\s+/gm, '');
const { parsed, keys } = parseEnv(content);
assert.strictEqual(parsed.API_KEY, 'secret123');
assert.strictEqual(parsed.DB_URL, 'localhost:5432');
assert.strictEqual(parsed.VALID, 'test');
assert.strictEqual(parsed.EMPTY, undefined);
assert.deepStrictEqual(keys, ['API_KEY', 'DB_URL', 'VALID']);
});
it('handles comments and edge cases', () => {
const content = `
# comment
VAL_WITH_COMMENT=value # inline comment
QUOTED="quoted value"
`.replace(/^\s+/gm, '');
const { parsed } = parseEnv(content);
assert.strictEqual(parsed.VAL_WITH_COMMENT, 'value ');
assert.strictEqual(parsed.QUOTED, '"quoted value"');
});
});