feat(resume): chunk-hash sha1 helpers (8 tests)
hashBuffer (sync) + hashFile (async streaming). 1MB+ files don't block. Known-input vectors (hello, empty) verify against canonical sha1. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
3667233a26
commit
59a8912fba
67
src/main/infra/chunk-hash.test.ts
Normal file
67
src/main/infra/chunk-hash.test.ts
Normal file
@ -0,0 +1,67 @@
|
||||
import { test, expect, describe, beforeEach, afterEach } from 'vitest';
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import { hashBuffer, hashFile } from './chunk-hash';
|
||||
|
||||
let tmpDir: string;
|
||||
beforeEach(() => {
|
||||
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'chunkhash-'));
|
||||
});
|
||||
afterEach(() => {
|
||||
try { fs.rmSync(tmpDir, { recursive: true, force: true }); } catch { /* ignore */ }
|
||||
});
|
||||
|
||||
describe('hashBuffer', () => {
|
||||
test('"hello" sha1', () => {
|
||||
expect(hashBuffer(Buffer.from('hello', 'utf-8')))
|
||||
.toBe('aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d');
|
||||
});
|
||||
|
||||
test('empty buffer sha1', () => {
|
||||
expect(hashBuffer(Buffer.alloc(0)))
|
||||
.toBe('da39a3ee5e6b4b0d3255bfef95601890afd80709');
|
||||
});
|
||||
|
||||
test('large buffer hashes deterministically', () => {
|
||||
const big = Buffer.alloc(1024 * 1024, 0x42); // 1MB of 'B' bytes
|
||||
const a = hashBuffer(big);
|
||||
const b = hashBuffer(big);
|
||||
expect(a).toBe(b);
|
||||
expect(a).toHaveLength(40); // sha1 = 40 hex chars
|
||||
});
|
||||
|
||||
test('different content produces different hashes', () => {
|
||||
expect(hashBuffer(Buffer.from('a'))).not.toBe(hashBuffer(Buffer.from('b')));
|
||||
});
|
||||
});
|
||||
|
||||
describe('hashFile', () => {
|
||||
test('file hash matches buffer hash for same content', async () => {
|
||||
const content = 'roundtrip-test-payload';
|
||||
const filePath = path.join(tmpDir, 'a.bin');
|
||||
fs.writeFileSync(filePath, content, 'utf-8');
|
||||
const fileHash = await hashFile(filePath);
|
||||
const bufHash = hashBuffer(Buffer.from(content, 'utf-8'));
|
||||
expect(fileHash).toBe(bufHash);
|
||||
});
|
||||
|
||||
test('empty file = empty-buffer sha1', async () => {
|
||||
const filePath = path.join(tmpDir, 'empty.bin');
|
||||
fs.writeFileSync(filePath, '');
|
||||
const fileHash = await hashFile(filePath);
|
||||
expect(fileHash).toBe('da39a3ee5e6b4b0d3255bfef95601890afd80709');
|
||||
});
|
||||
|
||||
test('large file (4MB) hashes correctly', async () => {
|
||||
const filePath = path.join(tmpDir, 'big.bin');
|
||||
const payload = Buffer.alloc(4 * 1024 * 1024, 0x55);
|
||||
fs.writeFileSync(filePath, payload);
|
||||
const fileHash = await hashFile(filePath);
|
||||
expect(fileHash).toBe(hashBuffer(payload));
|
||||
});
|
||||
|
||||
test('missing file rejects', async () => {
|
||||
await expect(hashFile(path.join(tmpDir, 'does-not-exist'))).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
26
src/main/infra/chunk-hash.ts
Normal file
26
src/main/infra/chunk-hash.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import * as crypto from 'crypto';
|
||||
import * as fs from 'fs';
|
||||
|
||||
export function hashBuffer(b: Buffer): string {
|
||||
return crypto.createHash('sha1').update(b).digest('hex');
|
||||
}
|
||||
|
||||
/**
|
||||
* Streaming sha1-Hash einer Datei. Async, damit grosse Recorded-Segments
|
||||
* (oft mehrere MB) nicht den Event-Loop blockieren.
|
||||
*/
|
||||
export function hashFile(filePath: string): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const hash = crypto.createHash('sha1');
|
||||
const stream = fs.createReadStream(filePath);
|
||||
stream.on('error', reject);
|
||||
stream.on('data', (chunk: Buffer | string) => {
|
||||
if (typeof chunk === 'string') {
|
||||
hash.update(chunk, 'utf-8');
|
||||
} else {
|
||||
hash.update(chunk);
|
||||
}
|
||||
});
|
||||
stream.on('end', () => resolve(hash.digest('hex')));
|
||||
});
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user