128 lines
3.4 KiB
TypeScript
128 lines
3.4 KiB
TypeScript
/// <reference types="bun-types" />
|
|
|
|
import { describe, expect, it } from "bun:test"
|
|
import { createTimestampTransformer, createTimestampedStdoutController } from "./timestamp-output"
|
|
|
|
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 = () => new Date("2026-02-19T12:34:56.000Z")
|
|
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 = () => new Date("2026-02-19T01:02:03.000Z")
|
|
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(() => new Date("2026-02-19T01:02:03.000Z"))
|
|
|
|
// 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$/)
|
|
})
|
|
})
|