Cover high-priority untested modules: - util: base32, result, refs-field, storage-root, log-tag - util-agent: storage (normalizeWorkflowConfig, resolveStorageRoot), run (parseArgv) - agent-builtin: tools (read-file, write-file, run-command), session, detail 627 → 719 tests (+92), all passing. Refs #35
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
CROCKFORD_BASE32_ALPHABET,
|
||||
encodeCrockfordBase32Bits,
|
||||
decodeCrockfordBase32Bits,
|
||||
encodeUint64AsCrockford,
|
||||
decodeCrockfordToUint64,
|
||||
} from "../src/base32.js";
|
||||
|
||||
describe("CROCKFORD_BASE32_ALPHABET", () => {
|
||||
it("has exactly 32 characters", () => {
|
||||
expect(CROCKFORD_BASE32_ALPHABET).toHaveLength(32);
|
||||
});
|
||||
|
||||
it("excludes I, L, O, U", () => {
|
||||
expect(CROCKFORD_BASE32_ALPHABET).not.toContain("I");
|
||||
expect(CROCKFORD_BASE32_ALPHABET).not.toContain("L");
|
||||
expect(CROCKFORD_BASE32_ALPHABET).not.toContain("O");
|
||||
expect(CROCKFORD_BASE32_ALPHABET).not.toContain("U");
|
||||
});
|
||||
});
|
||||
|
||||
describe("encodeCrockfordBase32Bits / decodeCrockfordBase32Bits", () => {
|
||||
it("roundtrips zero with bitLength=5", () => {
|
||||
const encoded = encodeCrockfordBase32Bits(0n, 5);
|
||||
expect(encoded).toBe("0");
|
||||
const decoded = decodeCrockfordBase32Bits(encoded, 5);
|
||||
expect(decoded).toEqual({ ok: true, value: 0n });
|
||||
});
|
||||
|
||||
it("roundtrips value 31 with bitLength=5", () => {
|
||||
const encoded = encodeCrockfordBase32Bits(31n, 5);
|
||||
expect(encoded).toBe("Z");
|
||||
const decoded = decodeCrockfordBase32Bits(encoded, 5);
|
||||
expect(decoded).toEqual({ ok: true, value: 31n });
|
||||
});
|
||||
|
||||
it("roundtrips with bitLength=10", () => {
|
||||
const encoded = encodeCrockfordBase32Bits(1023n, 10);
|
||||
expect(encoded).toBe("ZZ");
|
||||
const decoded = decodeCrockfordBase32Bits(encoded, 10);
|
||||
expect(decoded).toEqual({ ok: true, value: 1023n });
|
||||
});
|
||||
|
||||
it("roundtrips with non-multiple-of-5 bitLength", () => {
|
||||
const value = 255n; // 8 bits
|
||||
const encoded = encodeCrockfordBase32Bits(value, 8);
|
||||
expect(encoded).toHaveLength(2); // 8 bits -> 10 bits padded -> 2 chars
|
||||
const decoded = decodeCrockfordBase32Bits(encoded, 8);
|
||||
expect(decoded).toEqual({ ok: true, value });
|
||||
});
|
||||
|
||||
it("roundtrips large value", () => {
|
||||
const value = (1n << 64n) - 1n;
|
||||
const encoded = encodeCrockfordBase32Bits(value, 64);
|
||||
const decoded = decodeCrockfordBase32Bits(encoded, 64);
|
||||
expect(decoded).toEqual({ ok: true, value });
|
||||
});
|
||||
|
||||
it("throws on bitLength <= 0", () => {
|
||||
expect(() => encodeCrockfordBase32Bits(0n, 0)).toThrow("bitLength must be positive");
|
||||
expect(() => encodeCrockfordBase32Bits(0n, -1)).toThrow("bitLength must be positive");
|
||||
});
|
||||
|
||||
it("returns error on decode with bitLength <= 0", () => {
|
||||
const result = decodeCrockfordBase32Bits("0", 0);
|
||||
expect(result.ok).toBe(false);
|
||||
});
|
||||
|
||||
it("returns error on invalid character", () => {
|
||||
const result = decodeCrockfordBase32Bits("U", 5);
|
||||
expect(result.ok).toBe(false);
|
||||
});
|
||||
|
||||
it("returns error on wrong encoded length", () => {
|
||||
const result = decodeCrockfordBase32Bits("00", 5);
|
||||
expect(result.ok).toBe(false);
|
||||
});
|
||||
|
||||
it("handles lowercase input on decode", () => {
|
||||
const encoded = encodeCrockfordBase32Bits(10n, 5);
|
||||
const decoded = decodeCrockfordBase32Bits(encoded.toLowerCase(), 5);
|
||||
expect(decoded).toEqual({ ok: true, value: 10n });
|
||||
});
|
||||
});
|
||||
|
||||
describe("encodeUint64AsCrockford / decodeCrockfordToUint64", () => {
|
||||
it("encodes to 13 characters", () => {
|
||||
expect(encodeUint64AsCrockford(0n)).toHaveLength(13);
|
||||
expect(encodeUint64AsCrockford(1n)).toHaveLength(13);
|
||||
});
|
||||
|
||||
it("roundtrips 0n", () => {
|
||||
const encoded = encodeUint64AsCrockford(0n);
|
||||
expect(encoded).toBe("0000000000000");
|
||||
const decoded = decodeCrockfordToUint64(encoded);
|
||||
expect(decoded).toEqual({ ok: true, value: 0n });
|
||||
});
|
||||
|
||||
it("roundtrips max uint64", () => {
|
||||
const max = (1n << 64n) - 1n;
|
||||
const encoded = encodeUint64AsCrockford(max);
|
||||
const decoded = decodeCrockfordToUint64(encoded);
|
||||
expect(decoded).toEqual({ ok: true, value: max });
|
||||
});
|
||||
|
||||
it("roundtrips arbitrary value", () => {
|
||||
const value = 0xDEAD_BEEF_CAFE_BABEn;
|
||||
const encoded = encodeUint64AsCrockford(value);
|
||||
const decoded = decodeCrockfordToUint64(encoded);
|
||||
expect(decoded).toEqual({ ok: true, value });
|
||||
});
|
||||
|
||||
it("masks values beyond 64 bits", () => {
|
||||
const over = (1n << 64n) + 42n;
|
||||
const encoded = encodeUint64AsCrockford(over);
|
||||
const decoded = decodeCrockfordToUint64(encoded);
|
||||
expect(decoded).toEqual({ ok: true, value: 42n });
|
||||
});
|
||||
|
||||
it("returns error for invalid input", () => {
|
||||
const result = decodeCrockfordToUint64("!!!");
|
||||
expect(result.ok).toBe(false);
|
||||
});
|
||||
|
||||
it("returns error for wrong length", () => {
|
||||
const result = decodeCrockfordToUint64("000");
|
||||
expect(result.ok).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,38 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { assertValidLogTag } from '../src/process-logger/log-tag.js';
|
||||
|
||||
describe('assertValidLogTag', () => {
|
||||
it('accepts valid 8-char Crockford Base32 tags', () => {
|
||||
expect(() => assertValidLogTag('0123ABCD')).not.toThrow();
|
||||
expect(() => assertValidLogTag('VWXYZ789')).not.toThrow();
|
||||
expect(() => assertValidLogTag('00000000')).not.toThrow();
|
||||
expect(() => assertValidLogTag('ZZZZZZZZ')).not.toThrow();
|
||||
});
|
||||
|
||||
it('accepts lowercase (converted via toUpperCase)', () => {
|
||||
expect(() => assertValidLogTag('abcdefgh')).not.toThrow();
|
||||
expect(() => assertValidLogTag('0a1b2c3d')).not.toThrow();
|
||||
});
|
||||
|
||||
it('throws on too short', () => {
|
||||
expect(() => assertValidLogTag('1234567')).toThrow();
|
||||
expect(() => assertValidLogTag('')).toThrow();
|
||||
});
|
||||
|
||||
it('throws on too long', () => {
|
||||
expect(() => assertValidLogTag('123456789')).toThrow();
|
||||
});
|
||||
|
||||
it('throws on invalid chars I, L, O, U', () => {
|
||||
expect(() => assertValidLogTag('IIIIIIII')).toThrow();
|
||||
expect(() => assertValidLogTag('LLLLLLLL')).toThrow();
|
||||
expect(() => assertValidLogTag('OOOOOOOO')).toThrow();
|
||||
expect(() => assertValidLogTag('UUUUUUUU')).toThrow();
|
||||
});
|
||||
|
||||
it('throws on special characters', () => {
|
||||
expect(() => assertValidLogTag('1234567!')).toThrow();
|
||||
expect(() => assertValidLogTag('ABCD-EFG')).toThrow();
|
||||
expect(() => assertValidLogTag('ABCD EFG')).toThrow();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,40 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { mergeRefsWithContentHash, normalizeRefsField } from '../src/refs-field.js';
|
||||
|
||||
describe('mergeRefsWithContentHash', () => {
|
||||
it('appends a new content hash', () => {
|
||||
expect(mergeRefsWithContentHash(['a', 'b'], 'c')).toEqual(['a', 'b', 'c']);
|
||||
});
|
||||
|
||||
it('skips duplicate content hash', () => {
|
||||
expect(mergeRefsWithContentHash(['a', 'b'], 'b')).toEqual(['a', 'b']);
|
||||
});
|
||||
|
||||
it('preserves order', () => {
|
||||
expect(mergeRefsWithContentHash(['x', 'y'], 'z')).toEqual(['x', 'y', 'z']);
|
||||
});
|
||||
|
||||
it('handles empty refs', () => {
|
||||
expect(mergeRefsWithContentHash([], 'a')).toEqual(['a']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('normalizeRefsField', () => {
|
||||
it('returns empty array for non-array', () => {
|
||||
expect(normalizeRefsField(null)).toEqual([]);
|
||||
expect(normalizeRefsField(undefined)).toEqual([]);
|
||||
expect(normalizeRefsField(42)).toEqual([]);
|
||||
});
|
||||
|
||||
it('passes through string array', () => {
|
||||
expect(normalizeRefsField(['a', 'b'])).toEqual(['a', 'b']);
|
||||
});
|
||||
|
||||
it('filters non-strings from mixed array', () => {
|
||||
expect(normalizeRefsField(['a', 1, 'b', null])).toEqual(['a', 'b']);
|
||||
});
|
||||
|
||||
it('handles empty array', () => {
|
||||
expect(normalizeRefsField([])).toEqual([]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,51 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { ok, err } from '../src/result.js';
|
||||
|
||||
describe('result', () => {
|
||||
describe('ok', () => {
|
||||
it('wraps a value', () => {
|
||||
const r = ok(42);
|
||||
expect(r).toEqual({ ok: true, value: 42 });
|
||||
});
|
||||
|
||||
it('wraps a string value', () => {
|
||||
const r = ok('hello');
|
||||
expect(r.ok).toBe(true);
|
||||
if (r.ok) expect(r.value).toBe('hello');
|
||||
});
|
||||
});
|
||||
|
||||
describe('err', () => {
|
||||
it('wraps an error', () => {
|
||||
const r = err('fail');
|
||||
expect(r).toEqual({ ok: false, error: 'fail' });
|
||||
});
|
||||
|
||||
it('wraps an Error object', () => {
|
||||
const e = new Error('boom');
|
||||
const r = err(e);
|
||||
expect(r.ok).toBe(false);
|
||||
if (!r.ok) expect(r.error).toBe(e);
|
||||
});
|
||||
});
|
||||
|
||||
describe('type narrowing', () => {
|
||||
it('narrows ok result', () => {
|
||||
const r = ok(10) as ReturnType<typeof ok<number>> | ReturnType<typeof err<string>>;
|
||||
if (r.ok) {
|
||||
expect(r.value).toBe(10);
|
||||
} else {
|
||||
expect.unreachable();
|
||||
}
|
||||
});
|
||||
|
||||
it('narrows err result', () => {
|
||||
const r = err('bad') as ReturnType<typeof ok<number>> | ReturnType<typeof err<string>>;
|
||||
if (!r.ok) {
|
||||
expect(r.error).toBe('bad');
|
||||
} else {
|
||||
expect.unreachable();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,25 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { homedir } from 'node:os';
|
||||
import { getDefaultStorageRoot, getDefaultWorkflowStorageRoot, getGlobalCasDir } from '../src/storage-root.js';
|
||||
|
||||
describe('getDefaultStorageRoot', () => {
|
||||
it('returns homedir + /.uwf', () => {
|
||||
expect(getDefaultStorageRoot()).toBe(homedir() + '/.uwf');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getDefaultWorkflowStorageRoot', () => {
|
||||
it('returns same as getDefaultStorageRoot (deprecated alias)', () => {
|
||||
expect(getDefaultWorkflowStorageRoot()).toBe(getDefaultStorageRoot());
|
||||
});
|
||||
});
|
||||
|
||||
describe('getGlobalCasDir', () => {
|
||||
it('appends /cas to given storage root', () => {
|
||||
expect(getGlobalCasDir('/tmp/test')).toBe('/tmp/test/cas');
|
||||
});
|
||||
|
||||
it('falls back to default when undefined', () => {
|
||||
expect(getGlobalCasDir(undefined)).toBe(homedir() + '/.uwf/cas');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user