RenaiApp/src/main/modules/logger/logger.spec.ts

150 lines
5.6 KiB
TypeScript

import { createInterface, Interface } from 'readline';
import chai, { expect } from 'chai';
import 'mocha';
import chaiFs from 'chai-fs';
import fc from 'fast-check';
import fs from 'fs-extra';
import { container } from '../../core/container';
import { setDev } from '../../core/env.spec';
import { LogLevel } from './log-level';
chai.use(chaiFs);
describe('Logger Service', () => {
function getLastLine(rl: Interface): Promise<string> {
return new Promise((resolve) => {
let lastLine = '';
rl.on('line', (line) => {
lastLine = line;
});
rl.on('close', () => {
resolve(lastLine);
});
});
}
function getDefaultLogReadLineInterface(logger: LoggerInterface) {
return createInterface(fs.createReadStream(logger.getLogFile()));
}
// no debug because it gets tested separately
const logLevels = ['fatal', 'error', 'warning', 'notice', 'info'];
const logLevelsNumber = [0, 1, 2, 3, 4];
// hard-coded because it is hardcoded in the logger as well and therefore can be tested to be this way, in bytes
const maxLogSize = 50000;
type LogLevelArbitrary = fc.Arbitrary<'fatal' | 'error' | 'warning' | 'notice' | 'info'>;
const logLevelArbitrary = fc.constantFrom(...logLevels);
const logLevelNumberArbitrary = fc.constantFrom(...logLevelsNumber);
it('creates log files', () => {
const logger: LoggerInterface = container.get('logger');
expect(logger.getLogFile()).path('log file is not created');
expect(logger.getExceptionsLogFile()).path('exception log file is not created');
});
it('logs exceptions', async () => {
const logger: LoggerInterface = container.get('logger');
await logger.exception(new Error('this is an error'));
});
it("default log file doesn't get bigger than 50KB @slow", async () => {
const logger: LoggerInterface = container.get('logger');
let prevLogFileSize = (await fs.stat(logger.getLogFile())).size;
let minNumberOfLines = maxLogSize;
const sizeIncreases = [];
for (let i = 0; i <= minNumberOfLines; i++) {
await logger.log(4, 'your waifu is trash');
const currLogFileSize = (await fs.stat(logger.getLogFile())).size;
const sizeIncrease = currLogFileSize - prevLogFileSize;
if (sizeIncrease) {
sizeIncreases.push(sizeIncrease);
minNumberOfLines = maxLogSize / (sizeIncreases.reduce((sum, e) => sum + e, 0) / sizeIncreases.length);
}
prevLogFileSize = currLogFileSize;
}
const logFileStats = await fs.stat(logger.getLogFile());
expect(logFileStats.size).lessThan(maxLogSize, 'log is bigger than its max size');
}).timeout(15000);
it("exception log file doesn't get bigger than 50KB @slow", async () => {
const logger: LoggerInterface = container.get('logger');
let prevLogFileSize = (await fs.stat(logger.getExceptionsLogFile())).size;
let minNumberOfLines = maxLogSize;
const sizeIncreases = [];
for (let i = 0; i <= minNumberOfLines; i++) {
await logger.exception(new Error('your waifu is trash'));
const currLogFileSize = (await fs.stat(logger.getExceptionsLogFile())).size;
const sizeIncrease = currLogFileSize - prevLogFileSize;
if (sizeIncrease) {
sizeIncreases.push(sizeIncrease);
minNumberOfLines = maxLogSize / (sizeIncreases.reduce((sum, e) => sum + e, 0) / sizeIncreases.length);
}
prevLogFileSize = currLogFileSize;
}
const logFileStats = await fs.stat(logger.getExceptionsLogFile());
expect(logFileStats.size).lessThan(maxLogSize, 'exception log is bigger than its max size');
}).timeout(15000);
it('logs different levels directly', () => {
const logger: LoggerInterface = container.get('logger');
return fc.assert(
fc.asyncProperty(logLevelArbitrary as LogLevelArbitrary, fc.string(), async (logLevel, message) => {
await logger[logLevel](message);
const lastLine = await getLastLine(getDefaultLogReadLineInterface(logger));
expect(lastLine).contains(message, 'the log line does not contain the message');
expect(lastLine).contains(logLevel, `the log line does not contain the '${logLevel}' keyword`);
}),
{
numRuns: 50,
}
);
});
it('logs different levels indirectly via the generic log function', () => {
const logger: LoggerInterface = container.get('logger');
return fc.assert(
fc.asyncProperty(logLevelNumberArbitrary, fc.string(), async (logLevelNumber, message) => {
await logger.log(logLevelNumber, message);
const lastLine = await getLastLine(getDefaultLogReadLineInterface(logger));
expect(lastLine).contains(message, 'the log line does not contain the message');
expect(lastLine).contains(
logLevels[logLevelNumber],
`the log line does not contain the '${logLevels[logLevelNumber]}' keyword`
);
}),
{
numRuns: 50,
}
);
});
it('logs debug only in dev mode', async () => {
const logger: LoggerInterface = container.get('logger');
setDev();
await logger.debug('this is a development message');
const lastLine = await getLastLine(getDefaultLogReadLineInterface(logger));
expect(lastLine).contains('this is a development message', 'the dev log line does not contain the message');
expect(lastLine).contains('debug', `the dev log line does not contain the 'debug' keyword`);
setDev(false);
await logger.log(LogLevel.warning, 'this is a warning');
await logger.debug('this is a second development message, should not be here');
expect(lastLine).not.contain(
'this is a second development message, should not be here',
'debug is logged even in non-dev mode'
);
});
});