Adds a third choice next to the existing single-file and per-day modes: a new log file is created at every app start (process boot) and used until the app is closed. A close → reopen of the app starts a new session, hence a new file. File pattern: fileuploader-session-YYYY-MM-DD_HH-MM-SS-<pid>.log. The boolean sessionLog field — misnamed: it actually toggled daily mode — is replaced by a logMode enum: "single" | "daily" | "session". The misnomer made the migration the trap to watch: existing users with sessionLog:true must land on "daily", NOT "session". normalizeLogMode handles this and is unit-tested. - lib/log-mode.js (new, pure, dual CJS/window export): normalizeLogMode + resolveLogFileName + format helpers. No fs, no Date.now() at call time. - config-store.js: normalize at the single load() boundary so downstream readers consume logMode only. logMode is deliberately NOT seeded in DEFAULTS (would beat the legacy migration after merge). - main.js: stamp SESSION_ID once at process start (with pid hedge against same-second restart collisions); getLogFilePath and buildFallbackLogName switch on mode via the lib. _resolveUploadLogTarget cache key is now just the primary path, which already encodes mode/date/session — self-invalidates. - renderer: <select> with three German labels replaces the old checkbox; saveSettings writes logMode; index.html loads the lib so window.LogMode is available in renderSettings. - Tests: 14 log-mode tests (incl. legacy-migration regression), 3 config-store tests (defaults, legacy migration, round-trip all three values). 200/200. End-to-end simulated locally: two launches → two distinct session files; PID hedge produces distinct names even within the same second. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
97 lines
3.8 KiB
JavaScript
97 lines
3.8 KiB
JavaScript
const { test } = require('node:test');
|
|
const assert = require('node:assert');
|
|
const { normalizeLogMode, resolveLogFileName, formatDateStamp, formatSessionStamp } = require('../lib/log-mode');
|
|
|
|
// --- normalizeLogMode ---
|
|
|
|
test('normalizeLogMode: default for empty/null/undefined is "single"', () => {
|
|
assert.equal(normalizeLogMode(), 'single');
|
|
assert.equal(normalizeLogMode(null), 'single');
|
|
assert.equal(normalizeLogMode({}), 'single');
|
|
});
|
|
|
|
test('normalizeLogMode: explicit logMode wins for all three valid values', () => {
|
|
assert.equal(normalizeLogMode({ logMode: 'single' }), 'single');
|
|
assert.equal(normalizeLogMode({ logMode: 'daily' }), 'daily');
|
|
assert.equal(normalizeLogMode({ logMode: 'session' }), 'session');
|
|
});
|
|
|
|
test('regression: legacy sessionLog:true maps to "daily", NOT "session"', () => {
|
|
// The legacy boolean field was named after a misnomer — it actually toggled
|
|
// per-day logging. Mapping it to "session" would silently flip every existing
|
|
// per-day user onto per-session, which is exactly the bug the migration trap
|
|
// exists to prevent.
|
|
assert.equal(normalizeLogMode({ sessionLog: true }), 'daily');
|
|
});
|
|
|
|
test('normalizeLogMode: sessionLog:false / missing maps to "single"', () => {
|
|
assert.equal(normalizeLogMode({ sessionLog: false }), 'single');
|
|
});
|
|
|
|
test('normalizeLogMode: explicit logMode beats the legacy sessionLog field', () => {
|
|
// Once a user picks a mode in 3.3.35+, the legacy boolean must NOT override.
|
|
assert.equal(normalizeLogMode({ logMode: 'session', sessionLog: true }), 'session');
|
|
assert.equal(normalizeLogMode({ logMode: 'single', sessionLog: true }), 'single');
|
|
});
|
|
|
|
test('normalizeLogMode: invalid logMode strings fall through to single (or legacy if present)', () => {
|
|
assert.equal(normalizeLogMode({ logMode: 'lolnope' }), 'single');
|
|
assert.equal(normalizeLogMode({ logMode: '' }), 'single');
|
|
assert.equal(normalizeLogMode({ logMode: 'lolnope', sessionLog: true }), 'daily');
|
|
});
|
|
|
|
// --- resolveLogFileName ---
|
|
|
|
test('resolveLogFileName: single mode → bare basename + ext', () => {
|
|
assert.equal(
|
|
resolveLogFileName({ baseName: 'fileuploader', ext: '.log', mode: 'single' }),
|
|
'fileuploader.log'
|
|
);
|
|
});
|
|
|
|
test('resolveLogFileName: daily mode → fileuploader-YYYY-MM-DD.log', () => {
|
|
const d = new Date(2026, 4, 28); // May 28, 2026 — month is 0-indexed
|
|
assert.equal(
|
|
resolveLogFileName({ baseName: 'fileuploader', ext: '.log', mode: 'daily', date: d }),
|
|
'fileuploader-2026-05-28.log'
|
|
);
|
|
});
|
|
|
|
test('resolveLogFileName: session mode → fileuploader-session-<id>.log', () => {
|
|
assert.equal(
|
|
resolveLogFileName({ baseName: 'fileuploader', ext: '.log', mode: 'session', sessionId: '2026-05-28_22-44-52-12345' }),
|
|
'fileuploader-session-2026-05-28_22-44-52-12345.log'
|
|
);
|
|
});
|
|
|
|
test('resolveLogFileName: session mode with missing sessionId falls back to single (never emits malformed name)', () => {
|
|
assert.equal(
|
|
resolveLogFileName({ baseName: 'fileuploader', ext: '.log', mode: 'session' }),
|
|
'fileuploader.log'
|
|
);
|
|
});
|
|
|
|
test('resolveLogFileName: unknown mode is treated as single', () => {
|
|
assert.equal(
|
|
resolveLogFileName({ baseName: 'fileuploader', ext: '.log', mode: 'lolnope' }),
|
|
'fileuploader.log'
|
|
);
|
|
});
|
|
|
|
// --- format helpers ---
|
|
|
|
test('formatDateStamp: zero-pads month and day', () => {
|
|
assert.equal(formatDateStamp(new Date(2026, 0, 3)), '2026-01-03');
|
|
assert.equal(formatDateStamp(new Date(2026, 11, 31)), '2026-12-31');
|
|
});
|
|
|
|
test('formatSessionStamp: produces YYYY-MM-DD_HH-MM-SS-pid', () => {
|
|
const d = new Date(2026, 4, 28, 7, 9, 5);
|
|
assert.equal(formatSessionStamp(d, 12345), '2026-05-28_07-09-05-12345');
|
|
});
|
|
|
|
test('formatSessionStamp: omits the pid suffix when none provided', () => {
|
|
const d = new Date(2026, 4, 28, 22, 44, 52);
|
|
assert.equal(formatSessionStamp(d), '2026-05-28_22-44-52');
|
|
});
|