fix: keep internal audit logs in user data
Route upload audit and account rotation output through one internal userData log resolver with a contained fallback directory. Report active writer paths to diagnostics and log reveal actions while preserving the configurable fileuploader.log fallback contract and leaving existing Desktop files untouched. Cover primary paths, fallback containment, rotation, active path reporting, log opening, and upload log isolation with focused tests.
This commit is contained in:
+97
-47
@@ -4,61 +4,111 @@ const fs = require('fs');
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
|
||||
test('internal audit records never contaminate the MDU session link log', async () => {
|
||||
let createUploadAuditWriter;
|
||||
try {
|
||||
({ createUploadAuditWriter } = require('../lib/upload-audit'));
|
||||
} catch {}
|
||||
assert.equal(typeof createUploadAuditWriter, 'function');
|
||||
const {
|
||||
createInternalLogPathResolver,
|
||||
createInternalLogWriter,
|
||||
createUploadAuditWriter,
|
||||
getLogOpenDirectory
|
||||
} = require('../lib/upload-audit');
|
||||
|
||||
const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'mhu-upload-audit-'));
|
||||
const sessionLog = path.join(directory, '13-08-2026-mdu-session-14-20-123456.log');
|
||||
const writer = createUploadAuditWriter({
|
||||
fs,
|
||||
path,
|
||||
resolveUploadLogTarget: () => ({ path: sessionLog, isFallback: false }),
|
||||
rotateLogFile: () => {},
|
||||
invalidateUploadLogTarget: () => {},
|
||||
reportError: () => {},
|
||||
retryDelays: [0]
|
||||
});
|
||||
function createTempDirectory(t, prefix) {
|
||||
const directory = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
|
||||
t.after(() => fs.rmSync(directory, { recursive: true, force: true }));
|
||||
return directory;
|
||||
}
|
||||
|
||||
await writer.append('# SOURCE-CLEANUP {"outcome":"deleted"}\r\n', 'source-cleanup');
|
||||
await writer.append('# UPLOAD-PLAN {"plannedUploadCount":4}\r\n', 'upload-plan');
|
||||
test('internal audit and account rotation paths share the userData logs directory', (t) => {
|
||||
const directory = createTempDirectory(t, 'mhu-internal-log-paths-');
|
||||
const userDataPath = path.join(directory, 'user-data');
|
||||
const resolveInternalLogPath = createInternalLogPathResolver({ fs, path, userDataPath });
|
||||
|
||||
const auditLog = path.join(directory, 'upload-audit.log');
|
||||
assert.equal(fs.existsSync(sessionLog), false);
|
||||
assert.equal(fs.readFileSync(auditLog, 'utf8'), '# SOURCE-CLEANUP {"outcome":"deleted"}\r\n# UPLOAD-PLAN {"plannedUploadCount":4}\r\n');
|
||||
fs.rmSync(directory, { recursive: true, force: true });
|
||||
assert.equal(resolveInternalLogPath('upload-audit.log'), path.join(userDataPath, 'logs', 'upload-audit.log'));
|
||||
assert.equal(resolveInternalLogPath('account-rotation.log'), path.join(userDataPath, 'logs', 'account-rotation.log'));
|
||||
});
|
||||
|
||||
test('audit writer reports the actual fallback file after a failed primary write', async () => {
|
||||
const { createUploadAuditWriter } = require('../lib/upload-audit');
|
||||
const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'mhu-upload-audit-fallback-'));
|
||||
const blockedParent = path.join(directory, 'blocked');
|
||||
const fallbackDirectory = path.join(directory, 'fallback');
|
||||
fs.writeFileSync(blockedParent, 'not a directory');
|
||||
let attempts = 0;
|
||||
const persistedFallbacks = [];
|
||||
const writer = createUploadAuditWriter({
|
||||
fs,
|
||||
test('internal log paths fall back to another directory below userData without touching Desktop files', (t) => {
|
||||
const directory = createTempDirectory(t, 'mhu-internal-log-fallback-');
|
||||
const userDataPath = path.join(directory, 'user-data');
|
||||
const desktopPath = path.join(directory, 'Desktop');
|
||||
const desktopAuditPath = path.join(desktopPath, 'upload-audit.log');
|
||||
const desktopRotationPath = path.join(desktopPath, 'account-rotation.log');
|
||||
fs.mkdirSync(userDataPath, { recursive: true });
|
||||
fs.mkdirSync(desktopPath, { recursive: true });
|
||||
fs.writeFileSync(path.join(userDataPath, 'logs'), 'blocked');
|
||||
fs.writeFileSync(desktopAuditPath, 'existing audit');
|
||||
fs.writeFileSync(desktopRotationPath, 'existing rotation');
|
||||
|
||||
const resolveInternalLogPath = createInternalLogPathResolver({ fs, path, userDataPath });
|
||||
|
||||
assert.equal(resolveInternalLogPath('upload-audit.log'), path.join(userDataPath, 'internal-logs', 'upload-audit.log'));
|
||||
assert.equal(resolveInternalLogPath('account-rotation.log'), path.join(userDataPath, 'internal-logs', 'account-rotation.log'));
|
||||
assert.equal(fs.readFileSync(desktopAuditPath, 'utf8'), 'existing audit');
|
||||
assert.equal(fs.readFileSync(desktopRotationPath, 'utf8'), 'existing rotation');
|
||||
});
|
||||
|
||||
test('internal rotation writer retries inside userData and reports the file that accepted the write', async (t) => {
|
||||
const directory = createTempDirectory(t, 'mhu-internal-log-writer-');
|
||||
const userDataPath = path.join(directory, 'user-data');
|
||||
const primaryPath = path.join(userDataPath, 'logs', 'account-rotation.log');
|
||||
const fallbackPath = path.join(userDataPath, 'internal-logs', 'account-rotation.log');
|
||||
const appendTargets = [];
|
||||
const rotationTargets = [];
|
||||
const testFs = {
|
||||
mkdirSync: fs.mkdirSync,
|
||||
promises: {
|
||||
appendFile: async (targetPath, ...args) => {
|
||||
appendTargets.push(targetPath);
|
||||
if (targetPath === primaryPath) throw Object.assign(new Error('blocked primary'), { code: 'EACCES' });
|
||||
return fs.promises.appendFile(targetPath, ...args);
|
||||
}
|
||||
}
|
||||
};
|
||||
const targets = [primaryPath, fallbackPath];
|
||||
const resolveInternalLogPath = (_fileName, excludedPaths = new Set()) => targets.find(targetPath => !excludedPaths.has(targetPath)) || null;
|
||||
const writer = createInternalLogWriter({
|
||||
fs: testFs,
|
||||
path,
|
||||
resolveUploadLogTarget: () => ({
|
||||
path: attempts++ === 0
|
||||
? path.join(blockedParent, 'fileuploader.log')
|
||||
: path.join(fallbackDirectory, 'fileuploader.log'),
|
||||
isFallback: attempts > 1
|
||||
}),
|
||||
rotateLogFile: () => {},
|
||||
invalidateUploadLogTarget: () => {},
|
||||
persistFallbackLogPath: async targetPath => { persistedFallbacks.push(targetPath); },
|
||||
fileName: 'account-rotation.log',
|
||||
resolveInternalLogPath,
|
||||
rotateLogFile: targetPath => rotationTargets.push(targetPath),
|
||||
reportError: () => {},
|
||||
retryDelays: [0, 0]
|
||||
});
|
||||
|
||||
assert.equal(await writer.append('# UPLOAD-PLAN {}\r\n', 'upload-plan'), true);
|
||||
assert.equal(writer.getActivePath(), path.join(fallbackDirectory, 'upload-audit.log'));
|
||||
assert.deepEqual(persistedFallbacks, [path.join(fallbackDirectory, 'fileuploader.log')]);
|
||||
assert.equal(fs.readFileSync(writer.getActivePath(), 'utf8'), '# UPLOAD-PLAN {}\r\n');
|
||||
fs.rmSync(directory, { recursive: true, force: true });
|
||||
assert.equal(await writer.append('[rotation]\n', 'rot-log'), true);
|
||||
assert.deepEqual(appendTargets, [primaryPath, fallbackPath]);
|
||||
assert.deepEqual(rotationTargets, [primaryPath, fallbackPath]);
|
||||
assert.equal(writer.getActivePath(), fallbackPath);
|
||||
assert.equal(fs.readFileSync(fallbackPath, 'utf8'), '[rotation]\n');
|
||||
});
|
||||
|
||||
test('upload audit writer leaves the configured fileuploader log contract unchanged', async (t) => {
|
||||
const directory = createTempDirectory(t, 'mhu-upload-log-contract-');
|
||||
const userDataPath = path.join(directory, 'user-data');
|
||||
const customUploadDirectory = path.join(directory, 'custom-upload-logs');
|
||||
const uploadLogPath = path.join(customUploadDirectory, '13-08-2026-mdu-session-14-20-123456.log');
|
||||
fs.mkdirSync(customUploadDirectory, { recursive: true });
|
||||
fs.writeFileSync(uploadLogPath, 'existing upload entry\n');
|
||||
const resolveInternalLogPath = () => path.join(userDataPath, 'logs', 'upload-audit.log');
|
||||
const writer = createUploadAuditWriter({
|
||||
fs,
|
||||
path,
|
||||
resolveInternalLogPath,
|
||||
rotateLogFile: () => {},
|
||||
reportError: () => {},
|
||||
retryDelays: [0]
|
||||
});
|
||||
|
||||
assert.equal(await writer.append('# UPLOAD-PLAN {"plannedUploadCount":4}\r\n', 'upload-plan'), true);
|
||||
assert.equal(writer.getActivePath(), path.join(userDataPath, 'logs', 'upload-audit.log'));
|
||||
assert.equal(fs.readFileSync(uploadLogPath, 'utf8'), 'existing upload entry\n');
|
||||
assert.equal(fs.readFileSync(writer.getActivePath(), 'utf8'), '# UPLOAD-PLAN {"plannedUploadCount":4}\r\n');
|
||||
});
|
||||
|
||||
test('log opening uses the reported internal file directory before the general fallback directory', () => {
|
||||
assert.equal(
|
||||
getLogOpenDirectory('C:\\AppData\\Multi-Hoster\\logs\\upload-audit.log', 'C:\\Program Files\\Multi-Hoster', path.win32),
|
||||
'C:\\AppData\\Multi-Hoster\\logs'
|
||||
);
|
||||
assert.equal(getLogOpenDirectory(null, 'C:\\Program Files\\Multi-Hoster', path.win32), 'C:\\Program Files\\Multi-Hoster');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user