User report: in session mode the upload-log lines split across two files — the first few before the auto-persist fallback fired, the rest after into a path with the session-stamp DOUBLED. Root cause in main.js _persistFallbackLogPath: 1. The strip was gated on the legacy `sessionLog` boolean, which 3.3.35 retired in favour of `logMode`. So in session/daily mode the gate was false and the resolved path got persisted with its stamp intact. 2. Even when the gate triggered, its regex matched only the daily YYYY-MM-DD suffix, not the session "session-YYYY-MM-DD_HH-MM-SS-pid" suffix. The next getLogFilePath() call read that saved path as the "base", treated the already-stamped filename as the base name, and re-applied another stamp on top. First flush hit the original session file; everything after hit a doubly- stamped one — exactly the symptom (top file: 2 lines, bottom file: the rest). - lib/log-mode.js: new pure stripModeStampFromFileName helper that removes both the daily and the session suffix patterns. Anchored to $, no nested quantifiers (linear). - main.js: gate on logMode (not sessionLog) and call the helper for daily AND session, so logFilePath always persists as a bare base. - Tests: 4 new — strip behaviour + an idempotence regression that locks in "resolve → strip → resolve = same path" so this can't silently come back. 204/200. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
141 lines
5.9 KiB
JavaScript
141 lines
5.9 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'
|
|
);
|
|
});
|
|
|
|
// --- stripModeStampFromFileName ---
|
|
|
|
const { stripModeStampFromFileName } = require('../lib/log-mode');
|
|
|
|
test('stripModeStampFromFileName: leaves bare names alone', () => {
|
|
assert.equal(stripModeStampFromFileName('fileuploader.log'), 'fileuploader.log');
|
|
assert.equal(stripModeStampFromFileName('fileuploader'), 'fileuploader');
|
|
});
|
|
|
|
test('stripModeStampFromFileName: strips a daily YYYY-MM-DD suffix', () => {
|
|
assert.equal(stripModeStampFromFileName('fileuploader-2026-06-03.log'), 'fileuploader.log');
|
|
});
|
|
|
|
test('stripModeStampFromFileName: strips a session-stamp suffix (with and without pid)', () => {
|
|
assert.equal(
|
|
stripModeStampFromFileName('fileuploader-session-2026-06-03_18-16-20-8132.log'),
|
|
'fileuploader.log'
|
|
);
|
|
assert.equal(
|
|
stripModeStampFromFileName('fileuploader-session-2026-06-03_18-16-20.log'),
|
|
'fileuploader.log'
|
|
);
|
|
});
|
|
|
|
test('regression: resolveLogFileName(stripModeStampFromFileName(...)) is idempotent — persisting then re-resolving never compounds stamps', () => {
|
|
// This is the exact bug shape: persist the resolved path, then on next call
|
|
// re-resolve from the saved base — must produce the same file, not a doubled
|
|
// session-stamped one. The fix is the strip; this test guards against
|
|
// regressing _persistFallbackLogPath into the 3.3.35 bug.
|
|
const sessionId = '2026-06-03_18-16-20-8132';
|
|
const dailyDate = new Date(2026, 5, 3);
|
|
for (const mode of ['daily', 'session']) {
|
|
const date = mode === 'daily' ? dailyDate : new Date();
|
|
const initial = resolveLogFileName({ baseName: 'fileuploader', ext: '.log', mode, date, sessionId });
|
|
const stripped = stripModeStampFromFileName(initial);
|
|
// After strip, the base should be back to the bare name.
|
|
assert.equal(stripped, 'fileuploader.log', `${mode}: strip should produce bare base`);
|
|
// Re-resolving from the bare base gives the same final filename — no doubling.
|
|
const reBase = stripped.replace(/\.log$/, '');
|
|
const second = resolveLogFileName({ baseName: reBase, ext: '.log', mode, date, sessionId });
|
|
assert.equal(second, initial, `${mode}: round-trip must be idempotent`);
|
|
}
|
|
});
|
|
|
|
// --- 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');
|
|
});
|