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:
Sucukdeluxe
2026-08-26 12:27:13 +02:00
parent 3f9f7c212f
commit d21b721de4
3 changed files with 178 additions and 105 deletions
+97 -47
View File
@@ -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');
});