11// Log tail tests cover reading, parsing, and limiting recent log entries.
22import fs from "node:fs/promises" ;
3- import os from "node:os" ;
43import path from "node:path" ;
54import { afterEach , describe , expect , it , vi } from "vitest" ;
5+ import { useAutoCleanupTempDirTracker } from "../../test/helpers/temp-dir.js" ;
66import { resetLogger , setLoggerOverride } from "../logging.js" ;
77
8+ const tempDirs = useAutoCleanupTempDirTracker ( afterEach ) ;
9+
810const resolvedRedaction = { mode : "tools" as const , patterns : [ / c u s t o m - s e c r e t - [ a - z ] + / g] } ;
11+ type PositionalRead = (
12+ buffer : Buffer ,
13+ offset : number ,
14+ length : number ,
15+ position : number | null ,
16+ ) => Promise < { bytesRead : number ; buffer : Buffer } > ;
917
1018const { redactSensitiveLinesMock, resolveRedactOptionsMock } = vi . hoisted ( ( ) => ( {
1119 redactSensitiveLinesMock : vi . fn ( ( lines : string [ ] , options ?: unknown ) =>
@@ -28,6 +36,7 @@ vi.mock("./redact.js", async () => {
2836
2937describe ( "readConfiguredLogTail" , ( ) => {
3038 afterEach ( ( ) => {
39+ vi . restoreAllMocks ( ) ;
3140 resolveRedactOptionsMock . mockClear ( ) ;
3241 redactSensitiveLinesMock . mockClear ( ) ;
3342 resetLogger ( ) ;
@@ -36,7 +45,7 @@ describe("readConfiguredLogTail", () => {
3645
3746 it ( "applies redaction once per request across all returned lines" , async ( ) => {
3847 const { readConfiguredLogTail } = await import ( "./log-tail.js" ) ;
39- const tempDir = await fs . mkdtemp ( path . join ( os . tmpdir ( ) , "openclaw-log-tail-" ) ) ;
48+ const tempDir = tempDirs . make ( "openclaw-log-tail-" ) ;
4049 const file = path . join ( tempDir , "openclaw-2026-01-22.log" ) ;
4150
4251 await fs . writeFile ( file , "custom-secret-abcdefghijklmnopqrstuvwxyz\nsecond line\n" ) ;
@@ -51,7 +60,28 @@ describe("readConfiguredLogTail", () => {
5160 resolvedRedaction ,
5261 ) ;
5362 expect ( result . lines ) . toEqual ( [ "custom…wxyz" , "second line" ] ) ;
63+ } ) ;
64+
65+ it ( "fills short positional reads before splitting log lines" , async ( ) => {
66+ const { readConfiguredLogTail } = await import ( "./log-tail.js" ) ;
67+ const tempDir = tempDirs . make ( "openclaw-log-tail-" ) ;
68+ const file = path . join ( tempDir , "openclaw-2026-01-22.log" ) ;
69+ const realOpen = fs . open . bind ( fs ) ;
70+ vi . spyOn ( fs , "open" ) . mockImplementation ( async ( ...args ) => {
71+ const handle = await realOpen ( ...args ) ;
72+ const realRead = handle . read . bind ( handle ) as PositionalRead ;
73+ const shortRead = vi . fn < PositionalRead > ( ( buffer , offset , length , position ) =>
74+ realRead ( buffer , offset , Math . min ( length , 4 ) , position ) ,
75+ ) ;
76+ Object . defineProperty ( handle , "read" , { configurable : true , value : shortRead } ) ;
77+ return handle ;
78+ } ) ;
79+
80+ await fs . writeFile ( file , "old line\nrecent one\nrecent two\n" ) ;
81+ setLoggerOverride ( { file } ) ;
82+
83+ const result = await readConfiguredLogTail ( ) ;
5484
55- await fs . rm ( tempDir , { recursive : true , force : true } ) ;
85+ expect ( result . lines ) . toEqual ( [ "old line" , "recent one" , "recent two" ] ) ;
5686 } ) ;
5787} ) ;
0 commit comments