Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
14a943616This file is pinned to the selected release. Download the original to open it in your design tool.
import { mkdtempSync, mkdirSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { inflateRawSync } from 'node:zlib'
import { describe, expect, it } from 'vitest'
import { buildLogBundle, bundleFileName, redactSecretsInText } from './logBundle.js'
import { crc32, writeZip } from './zipWriter.js'
/** Enough of a ZIP reader to check what writeZip produced: walks the local headers. */
function readZip(zip: Buffer): Map<string, Buffer> {
const out = new Map<string, Buffer>()
let at = 0
while (zip.readUInt32LE(at) === 0x04034b50) {
const method = zip.readUInt16LE(at + 8)
const crc = zip.readUInt32LE(at + 14)
const csize = zip.readUInt32LE(at + 18)
const nlen = zip.readUInt16LE(at + 26)
const name = zip.subarray(at + 30, at + 30 + nlen).toString('utf8')
const body = zip.subarray(at + 30 + nlen, at + 30 + nlen + csize)
const data = method === 8 ? inflateRawSync(body) : Buffer.from(body)
expect(crc32(data)).toBe(crc)
out.set(name, data)
at += 30 + nlen + csize
}
expect(zip.readUInt32LE(at)).toBe(0x02014b50) // the central directory follows the last entry
return out
}
describe('zipWriter', () => {
it('round-trips deflated and stored entries', () => {
const text = Buffer.from('hello hello hello hello hello hello\n'.repeat(50))
const noise = Buffer.from(Array.from({ length: 300 }, (_, i) => (i * 7919) % 256))
const zip = writeZip([{ name: 'a/text.log', data: text }, { name: 'noise.bin', data: noise }])
const files = readZip(zip)
expect(files.get('a/text.log')?.equals(text)).toBe(true)
expect(files.get('noise.bin')?.equals(noise)).toBe(true)
expect(zip.length).toBeLessThan(text.length) // the repeated text really was compressed
})
it('computes the standard CRC-32', () => {
expect(crc32(Buffer.from('123456789'))).toBe(0xcbf43926)
})
})
describe('buildLogBundle', () => {
it('takes the last week of dated logs, the daemon log tail, and redacts', () => {
const root = mkdtempSync(join(tmpdir(), 'bundle-'))
const logs = join(root, 'logs'), data = join(root, 'data')
mkdirSync(logs); mkdirSync(data)
const now = new Date(2026, 8, 15, 10, 30)
writeFileSync(join(logs, 'dial-20260915.log'), 'I (1) touch: press\nsession_token: abcdefgh12345\n')
writeFileSync(join(logs, 'app-20260910.log'), 'in window\n')
writeFileSync(join(logs, 'app-20260908.log'), 'too old\n')
writeFileSync(join(logs, 'cli-20260915.log'), 'Bearer abcdefghijklmnop\n')
writeFileSync(join(logs, 'unrelated.txt'), 'no\n')
writeFileSync(join(data, 'harness.log'), 'x'.repeat(100) + 'THE END\n')
const { zip, included } = buildLogBundle({
logsDir: logs, dataDir: data, now, version: '0.2.21', redact: redactSecretsInText,
harnessLogTailBytes: 8, notes: ['app: 1.1.22'],
})
const files = readZip(zip)
expect([...files.keys()]).toEqual(['README.txt', 'logs/app-20260910.log', 'logs/cli-20260915.log', 'logs/dial-20260915.log', 'harness.log'])
expect(files.get('logs/dial-20260915.log')?.toString()).toBe('I (1) touch: press\nsession_token: <redacted>\n')
expect(files.get('logs/cli-20260915.log')?.toString()).toBe('Bearer <redacted>\n')
expect(files.get('harness.log')?.toString()).toBe('THE END\n')
expect(files.get('README.txt')?.toString()).toContain('app: 1.1.22')
expect(included).toContain('dial-20260915.log')
expect(bundleFileName(now)).toBe('harness-logs-20260915-1030.zip')
})
it('still produces a bundle with a README when there is nothing to include', () => {
const root = mkdtempSync(join(tmpdir(), 'bundle-'))
const { zip, included } = buildLogBundle({ logsDir: join(root, 'none'), dataDir: root, version: '0', redact: (s) => s })
expect(included).toEqual([])
expect(readZip(zip).get('README.txt')?.toString()).toContain('nothing')
})
})