import test from 'node:test'; import assert from 'node:assert/strict'; import { mkdtemp, readFile, rm } from 'node:fs/promises'; import os from 'node:os'; import path from 'node:path'; import zlib from 'node:zlib'; import diagnosticsModule from '../src/main/diagnostics-service.cjs'; const { DiagnosticsService } = diagnosticsModule; function unzipLocalEntries(buffer) { const entries = new Map(); let offset = 0; while (offset + 4 <= buffer.length && buffer.readUInt32LE(offset) === 0x04034b50) { const method = buffer.readUInt16LE(offset + 8); const compressedSize = buffer.readUInt32LE(offset + 18); const nameLength = buffer.readUInt16LE(offset + 26); const extraLength = buffer.readUInt16LE(offset + 28); const nameStart = offset + 30; const dataStart = nameStart + nameLength + extraLength; const name = buffer.subarray(nameStart, nameStart + nameLength).toString('utf8'); const compressed = buffer.subarray(dataStart, dataStart + compressedSize); entries.set(name, method === 8 ? zlib.inflateRawSync(compressed) : compressed); offset = dataStart + compressedSize; } return entries; } test('writes structured local diagnostics and exports a secret-free support bundle', async (t) => { const root = await mkdtemp(path.join(os.tmpdir(), 'forgeflow-diagnostics-')); t.after(() => rm(root, { recursive: true, force: true })); const secret = ['gitea', 'TEST', 'ONLY', 'ULTRA', 'SECRET', '1234567890'].join('_'); const service = new DiagnosticsService({ userDataPath: root, appInfo: { name: 'ForgeFlow', version: '0.3.0-test' }, secretProvider: () => [secret], preferencesProvider: () => ({ diagnosticsEnabled: true, diagnosticLevel: 'debug', logRetentionDays: 14, maxLogFileMb: 8 }) }); await service.initialize(); await service.error('test.failure', { authorization: `token ${secret}`, password: 'unsafe-password', message: `request failed with ${secret}`, path: path.join(os.homedir(), 'private', 'repository') }); await service.flush(); const status = await service.getStatus(); assert.equal(status.enabled, true); assert.ok(status.fileCount >= 1); const raw = (await Promise.all((await service.listLogFiles()).map((file) => readFile(file.path, 'utf8')))).join('\n'); assert.doesNotMatch(raw, new RegExp(secret)); assert.doesNotMatch(raw, /unsafe-password/); assert.doesNotMatch(raw, new RegExp(os.homedir().replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))); const destination = path.join(root, 'support.zip'); const result = await service.exportSupportBundle({ destinationPath: destination, privacyMode: 'strict', publicState: { gitea: { baseUrl: 'https://gitea.example.test', hasToken: true, encryptedToken: 'ciphertext' }, preferences: {} }, repositories: [{ id: 1, fullName: 'jens/private-repo', localPath: path.join(os.homedir(), 'private-repo'), localStatus: { head: 'a'.repeat(40), branch: { head: 'main' }, counts: {}, clean: true } }], operations: [{ repository: 'jens/private-repo', status: 'failed', runnerLog: `Authorization: token ${secret}` }], preflight: { checks: [] } }); assert.equal(service.isKnownBundlePath(result.path), true); const entries = unzipLocalEntries(await readFile(destination)); const bundleText = [...entries.values()].map((value) => value.toString('utf8')).join('\n'); assert.doesNotMatch(bundleText, new RegExp(secret)); assert.doesNotMatch(bundleText, /ciphertext|unsafe-password|jens\/private-repo/); assert.match(entries.get('manifest.json').toString(), /"containsSecrets": false/); assert.match(entries.get('repositories-sanitized.json').toString(), /fullname-[a-f0-9]{12}/); const cleared = await service.clear(); assert.ok(cleared.fileCount >= 1, 'clear writes a new safe session marker'); });