Files
oh-my-opencode/src/cli/run/timestamp-output.test.ts
T
2026-03-29 00:25:29 -07:00

132 lines
3.5 KiB
TypeScript

/// <reference types="bun-types" />
import { describe, expect, it } from "bun:test"
import { createTimestampTransformer, createTimestampedStdoutController } from "./timestamp-output"
function createLocalDate(hours: number, minutes: number, seconds: number): Date {
return new Date(2026, 1, 19, hours, minutes, seconds)
}
interface MockWriteStream {
write: (
chunk: Uint8Array | string,
encodingOrCallback?: BufferEncoding | ((error?: Error | null) => void),
callback?: (error?: Error | null) => void,
) => boolean
writes: string[]
}
function createMockWriteStream(): MockWriteStream {
const writes: string[] = []
const write: MockWriteStream["write"] = (
chunk,
encodingOrCallback,
callback,
) => {
const text = typeof chunk === "string"
? chunk
: Buffer.from(chunk).toString(typeof encodingOrCallback === "string" ? encodingOrCallback : undefined)
writes.push(text)
if (typeof encodingOrCallback === "function") {
encodingOrCallback(null)
} else if (callback) {
callback(null)
}
return true
}
return { write, writes }
}
describe("createTimestampTransformer", () => {
it("prefixes each output line with timestamp", () => {
// given
const now = () => createLocalDate(12, 34, 56)
const transform = createTimestampTransformer(now)
// when
const output = transform("hello\nworld")
// then
expect(output).toBe("[12:34:56] hello\n[12:34:56] world")
})
it("keeps line-start state across chunk boundaries", () => {
// given
const now = () => createLocalDate(1, 2, 3)
const transform = createTimestampTransformer(now)
// when
const first = transform("hello")
const second = transform(" world")
const third = transform("\nnext")
// then
expect(first).toBe("[01:02:03] hello")
expect(second).toBe(" world")
expect(third).toBe("\n[01:02:03] next")
})
it("returns empty string for empty chunk", () => {
// given
const transform = createTimestampTransformer(() => createLocalDate(1, 2, 3))
// when
const output = transform("")
// then
expect(output).toBe("")
})
})
describe("createTimestampedStdoutController", () => {
it("prefixes stdout writes when enabled", () => {
// given
const stdout = createMockWriteStream()
const controller = createTimestampedStdoutController(stdout as unknown as NodeJS.WriteStream)
// when
controller.enable()
stdout.write("hello\nworld")
// then
expect(stdout.writes).toHaveLength(1)
expect(stdout.writes[0]!).toMatch(/^\[\d{2}:\d{2}:\d{2}\] hello\n\[\d{2}:\d{2}:\d{2}\] world$/)
})
it("restores original write function", () => {
// given
const stdout = createMockWriteStream()
const controller = createTimestampedStdoutController(stdout as unknown as NodeJS.WriteStream)
controller.enable()
// when
stdout.write("before restore")
controller.restore()
stdout.write("after restore")
// then
expect(stdout.writes).toHaveLength(2)
expect(stdout.writes[0]!).toMatch(/^\[\d{2}:\d{2}:\d{2}\] before restore$/)
expect(stdout.writes[1]).toBe("after restore")
})
it("supports Uint8Array chunks and encoding", () => {
// given
const stdout = createMockWriteStream()
const controller = createTimestampedStdoutController(stdout as unknown as NodeJS.WriteStream)
// when
controller.enable()
stdout.write(Buffer.from("byte line"), "utf8")
// then
expect(stdout.writes).toHaveLength(1)
expect(stdout.writes[0]!).toMatch(/^\[\d{2}:\d{2}:\d{2}\] byte line$/)
})
})