bramble-parser
Version:
Bramble is a lightweight recursive descent parser that processes .havenfs files, returning a structured Json tree that can be used to construct an in-memory FS representation. The parser is based on line-based grammar, chunk headers, and metadata declarat
126 lines (92 loc) • 3.82 kB
text/typescript
import { describe, test, expect, vi, beforeEach } from 'bun:test';
import { BrambleLexer } from '../../src/lexer/brambleLexer';
import * as fs from 'fs';
import { ChunkParser } from '../../src/parser/chunkParser';
import { errorManager } from '../../src/errors/errorManager';
describe('Hash verification between files', () => {
beforeEach(() => {
vi.restoreAllMocks();
errorManager.clear();
});
test('Does not report an error if FILE and META have the same hash', () => {
const fakeContent = `
#CHUNK files
FILE f1a7e parent=92e1f name=logo.png
META f1a7e modified=1723472381 created=1723472370 mimetype=image/png
`.trim();
vi.spyOn(fs, 'readFileSync').mockReturnValue(fakeContent);
const lexer = new BrambleLexer('./test/test.example.havenfs');
lexer.tokenize();
lexer.groupTokensByLine();
lexer.groupByChunkContext();
lexer.checkHashReferencesBetweenFiles();
expect(errorManager.getAll().length).toBe(0);
});
test('Reports an error if FILE and META have different hashes', () => {
const fakeContent = `
#CHUNK files
FILE f1a7e parent=92e1f name=logo.png
META xx999 modified=1723472381 created=1723472370 mimetype=image/png
`.trim();
vi.spyOn(fs, 'readFileSync').mockReturnValue(fakeContent);
const lexer = new BrambleLexer('./test/test.example.havenfs');
lexer.tokenize();
lexer.groupTokensByLine();
lexer.groupByChunkContext();
lexer.checkHashReferencesBetweenFiles();
const errors = errorManager.getAll();
expect(errors.length).toBeGreaterThan(0);
expect(errors[0].message).toMatch(/Mismatch between FILE and META hashes/);
});
test('Reports an error if META is missing after FILE', () => {
const fakeContent = `
#CHUNK files
FILE f1a7e parent=92e1f name=logo.png
`.trim();
vi.spyOn(fs, 'readFileSync').mockReturnValue(fakeContent);
const lexer = new BrambleLexer('./test/test.example.havenfs');
lexer.tokenize();
lexer.groupTokensByLine();
lexer.groupByChunkContext();
lexer.checkHashReferencesBetweenFiles();
const errors = errorManager.getAll();
expect(errors.length).toBeGreaterThan(0);
expect(errors[0].message).toMatch(/Missing META for file f1a7e/);
});
test('Does not report an error if the history chunk has the correct hash', () => {
const fakeContent = `
#CHUNK history f1a7e
HIST f1a7e 20250625T1230 user=ellie action=created hash=abc123
`.trim();
vi.spyOn(fs, 'readFileSync').mockReturnValue(fakeContent);
const lexer = new BrambleLexer('./test/test.example.havenfs');
lexer.tokenize();
lexer.groupTokensByLine();
lexer.groupByChunkContext();
lexer.checkHashReferencesBetweenFiles();
expect(errorManager.getAll().length).toBe(0);
});
test('reports an error if token at index is missing', () => {
const tokens: ILexerToken[] = [];
const result = ChunkParser['getStringTokenAt'](tokens, 0, 'empty tokens');
expect(result).not.toBeNull();
const errors = errorManager.getAll();
expect(errors.length).toBeGreaterThan(0);
expect(errors[0].message).toBe('Missing token at index 0 in empty tokens');
});
test('Reports an error if the history chunk has an incorrect hash', () => {
const fakeContent = `
#CHUNK history f1a7e
HIST xxxxxx 20250625T1230 user=ellie action=created hash=abc123
`.trim();
vi.spyOn(fs, 'readFileSync').mockReturnValue(fakeContent);
const lexer = new BrambleLexer('./test/test.example.havenfs');
lexer.tokenize();
lexer.groupTokensByLine();
lexer.groupByChunkContext();
lexer.checkHashReferencesBetweenFiles();
const errors = errorManager.getAll();
expect(errors.length).toBeGreaterThan(0);
expect(errors[0].message).toMatch(/Expected ID token at index 2 in hash references, but got STRING: xxxxxx/);
});
});