Files
opencode-mobile/src/lib/logbuffer.test.ts
engineer 3499167b60 test(lib): cover request-header building + log ring buffer (4→20 tests)
Extract the auth + directory-header encoding from sdk.ts into a pure, dep-free
headers.ts (sdk.ts now delegates — behavior unchanged) so the connection-critical
logic is unit-testable without expo/fetch. Add 8 header tests (ASCII passthrough,
non-ASCII/CJK percent-encoding stays header-safe, Basic auth, empty-dir handling)
and 8 logbuffer tests (serialization incl. circular-ref fallback, 200-entry ring
cap, copy semantics, formatting). typecheck clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 13:55:31 -07:00

88 lines
3.0 KiB
TypeScript

import { test } from "node:test"
import assert from "node:assert/strict"
import { log, getLogEntries, formatLogLines, clearLog, type LogEntry } from "./logbuffer.ts"
// The log buffer backs diagnostic reports. A regression in its ring-buffer cap,
// message serialization, or formatting silently corrupts the attached logs, so
// these pin the observable behavior.
test("records entries with level, tag, and joined message", () => {
clearLog()
log.info("net", "connected to", "server")
const entries = getLogEntries()
assert.equal(entries.length, 1)
assert.equal(entries[0].level, "info")
assert.equal(entries[0].tag, "net")
assert.equal(entries[0].message, "connected to server")
assert.equal(typeof entries[0].ts, "number")
})
test("serializes non-string parts as JSON, falling back to String() when unserializable", () => {
clearLog()
log.debug("t", "obj", { a: 1 }, 42, true)
assert.equal(getLogEntries()[0].message, 'obj {"a":1} 42 true')
clearLog()
const circular: Record<string, unknown> = {}
circular.self = circular // JSON.stringify throws -> String() fallback
log.warn("t", circular)
// Should not throw and should produce a string (the [object Object] fallback).
assert.equal(typeof getLogEntries()[0].message, "string")
assert.equal(getLogEntries().length, 1)
})
test("each level helper is recorded with the matching level", () => {
clearLog()
log.debug("t", "d")
log.info("t", "i")
log.warn("t", "w")
log.error("t", "e")
assert.deepEqual(
getLogEntries().map((e) => e.level),
["debug", "info", "warn", "error"],
)
})
test("ring buffer is capped at 200 entries, keeping the most recent", () => {
clearLog()
for (let i = 0; i < 250; i++) log.info("t", `msg${i}`)
const entries = getLogEntries()
assert.equal(entries.length, 200)
// Oldest 50 dropped: first kept is msg50, last is msg249.
assert.equal(entries[0].message, "msg50")
assert.equal(entries[entries.length - 1].message, "msg249")
})
test("getLogEntries returns a copy — mutating it does not affect the buffer", () => {
clearLog()
log.info("t", "a")
const snapshot = getLogEntries()
snapshot.push({ ts: 0, level: "info", tag: "x", message: "injected" } as LogEntry)
assert.equal(getLogEntries().length, 1)
})
test("clearLog empties the buffer", () => {
clearLog()
log.info("t", "a")
log.info("t", "b")
assert.equal(getLogEntries().length, 2)
clearLog()
assert.equal(getLogEntries().length, 0)
})
test("formatLogLines renders ISO time, padded level, tag, and message", () => {
const entries: LogEntry[] = [
{ ts: Date.UTC(2026, 0, 2, 3, 4, 5, 678), level: "info", tag: "net", message: "hello" },
{ ts: Date.UTC(2026, 0, 2, 3, 4, 6, 0), level: "error", tag: "db", message: "boom" },
]
const out = formatLogLines(entries)
const lines = out.split("\n")
assert.equal(lines.length, 2)
assert.equal(lines[0], "03:04:05.678 INFO [net] hello")
assert.equal(lines[1], "03:04:06.000 ERROR [db] boom")
})
test("formatLogLines of an empty list is an empty string", () => {
assert.equal(formatLogLines([]), "")
})