Skip to content

Commit bde8111

Browse files
committed
Add tests for MetadataHandler
1 parent 0a18247 commit bde8111

File tree

5 files changed

+275
-8
lines changed

5 files changed

+275
-8
lines changed

.fernignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ src/utils
1919

2020
# Modified due to issues with OTEL
2121
tests/unit/fetcher/stream-wrappers/webpack.test.ts
22+
tests/unit/internal/
2223
tests/integration/
2324

2425
# CI Action

src/evals/run.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -637,7 +637,6 @@ async function resolveFile<I extends Record<string, unknown> & { message?: any[]
637637
"File does not exist on Humanloop. Please provide a `file.path` and a version to create a new version.",
638638
);
639639
}
640-
Logger.log(`UPSERTING FILE ${JSON.stringify(fileConfig, null, 2)}`);
641640
return [await upsertFile(client, fileConfig), callable];
642641
}
643642

src/sync/MetadataHandler.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import fs from "fs";
2-
import path from "path";
1+
import * as fs from "fs";
2+
import * as path from "path";
33

44
/**
55
* Interface for operation data
@@ -33,7 +33,7 @@ interface LogOperationParams {
3333
successfulFiles?: string[];
3434
failedFiles?: string[];
3535
error?: string;
36-
startTime: number;
36+
duration_ms: number;
3737
}
3838

3939
/**
@@ -107,7 +107,6 @@ export default class MetadataHandler {
107107
*/
108108
public logOperation(params: LogOperationParams): void {
109109
const currentTime = new Date().toISOString();
110-
const duration = Date.now() - params.startTime;
111110

112111
const operationData: OperationData = {
113112
timestamp: currentTime,
@@ -117,7 +116,7 @@ export default class MetadataHandler {
117116
successful_files: params.successfulFiles || [],
118117
failed_files: params.failedFiles || [],
119118
error: params.error,
120-
duration_ms: duration,
119+
duration_ms: params.duration_ms,
121120
};
122121

123122
const metadata = this.readMetadata();

src/sync/SyncClient.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -316,18 +316,19 @@ export default class SyncClient {
316316
path: normalizedPath || "",
317317
environment,
318318
successfulFiles,
319-
startTime,
319+
duration_ms: duration,
320320
});
321321

322322
return successfulFiles;
323323
} catch (error) {
324+
const duration = Date.now() - startTime;
324325
// Log the failed operation
325326
this.metadata.logOperation({
326327
operationType: "pull",
327328
path: normalizedPath || "",
328329
environment,
329330
error: String(error),
330-
startTime,
331+
duration_ms: duration,
331332
});
332333
throw error;
333334
}
Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
import * as fs from "fs";
2+
import * as path from "path";
3+
4+
import MetadataHandler from "../../../../src/sync/MetadataHandler";
5+
6+
// Mock fs module
7+
jest.mock("fs");
8+
9+
describe("MetadataHandler", () => {
10+
let metadataHandler: MetadataHandler;
11+
const testBaseDir = "/test/dir";
12+
const mockMetadataFile = path.join(testBaseDir, ".sync_metadata.json");
13+
14+
beforeEach(() => {
15+
// Clear all mocks before each test
16+
jest.clearAllMocks();
17+
18+
// Mock fs.existsSync to return false initially (file doesn't exist)
19+
(fs.existsSync as jest.Mock).mockReturnValue(false);
20+
21+
// Mock fs.mkdirSync to do nothing
22+
(fs.mkdirSync as jest.Mock).mockImplementation(() => {});
23+
24+
// Mock fs.writeFileSync to do nothing
25+
(fs.writeFileSync as jest.Mock).mockImplementation(() => {});
26+
27+
// Mock fs.readFileSync to return initial state
28+
(fs.readFileSync as jest.Mock).mockReturnValue(
29+
JSON.stringify({ last_operation: null, history: [] }),
30+
);
31+
});
32+
33+
describe("initialization", () => {
34+
it("should create metadata file if it does not exist", () => {
35+
metadataHandler = new MetadataHandler(testBaseDir);
36+
expect(fs.existsSync).toHaveBeenCalledWith(mockMetadataFile);
37+
expect(fs.writeFileSync).toHaveBeenCalledWith(
38+
mockMetadataFile,
39+
JSON.stringify({ last_operation: null, history: [] }, null, 2),
40+
);
41+
});
42+
43+
it("should not create metadata file if it already exists", () => {
44+
// Reset mocks
45+
jest.clearAllMocks();
46+
47+
// Mock that file exists
48+
(fs.existsSync as jest.Mock).mockReturnValue(true);
49+
50+
// Mock readFileSync to return existing data
51+
(fs.readFileSync as jest.Mock).mockReturnValue(
52+
JSON.stringify({ last_operation: null, history: [] }),
53+
);
54+
55+
metadataHandler = new MetadataHandler(testBaseDir);
56+
57+
expect(fs.writeFileSync).not.toHaveBeenCalled();
58+
});
59+
});
60+
61+
describe("logOperation", () => {
62+
it("should log operation with all fields", () => {
63+
metadataHandler = new MetadataHandler(testBaseDir);
64+
const params = {
65+
operationType: "pull",
66+
path: "test/path",
67+
environment: "dev",
68+
successfulFiles: ["test/path/file1.prompt", "test/path/file2.prompt"],
69+
failedFiles: ["test/path/file3.prompt"],
70+
error: "Test error",
71+
duration_ms: 1000,
72+
};
73+
74+
metadataHandler.logOperation(params);
75+
76+
expect(fs.writeFileSync).toHaveBeenCalledWith(
77+
mockMetadataFile,
78+
expect.stringContaining('"operation_type": "pull"'),
79+
);
80+
expect(fs.writeFileSync).toHaveBeenCalledWith(
81+
mockMetadataFile,
82+
expect.stringContaining('"path": "test/path"'),
83+
);
84+
expect(fs.writeFileSync).toHaveBeenCalledWith(
85+
mockMetadataFile,
86+
expect.stringContaining('"environment": "dev"'),
87+
);
88+
});
89+
90+
it("should maintain history within max size", () => {
91+
// First, initialize the MetadataHandler
92+
metadataHandler = new MetadataHandler(testBaseDir);
93+
94+
// Clear the writeFileSync mock to reset the call history after initialization
95+
(fs.writeFileSync as jest.Mock).mockClear();
96+
97+
// Create a history array with 5 items
98+
const existingHistory = Array(5)
99+
.fill(null)
100+
.map(() => ({
101+
timestamp: new Date().toISOString(),
102+
operation_type: "pull",
103+
path: "/test/path",
104+
successful_files: [],
105+
failed_files: [],
106+
duration_ms: 1000,
107+
}));
108+
109+
// NOW set up the stateful mock AFTER initialization
110+
// This prevents the ensureMetadataFile from overwriting our test data
111+
(fs.readFileSync as jest.Mock).mockReturnValue(
112+
JSON.stringify({
113+
last_operation: existingHistory[0],
114+
history: existingHistory,
115+
}),
116+
);
117+
118+
const params = {
119+
operationType: "pull",
120+
path: "/new/path",
121+
duration_ms: 750,
122+
};
123+
124+
metadataHandler.logOperation(params);
125+
126+
// Get the last call to writeFileSync
127+
const lastCallArgs = (fs.writeFileSync as jest.Mock).mock.calls[0];
128+
const writtenData = JSON.parse(lastCallArgs[1]);
129+
130+
// The history should still have 5 items (max size) after adding a new one
131+
expect(writtenData.history.length).toBe(5);
132+
133+
// The new operation should be at the start of the array
134+
expect(writtenData.history[0].operation_type).toBe("pull");
135+
expect(writtenData.history[0].path).toBe("/new/path");
136+
expect(writtenData.history[0].duration_ms).toBe(750);
137+
});
138+
139+
it("should handle empty history array", () => {
140+
metadataHandler = new MetadataHandler(testBaseDir);
141+
142+
const params = {
143+
operationType: "pull",
144+
path: "test/path",
145+
duration_ms: 500,
146+
};
147+
148+
metadataHandler.logOperation(params);
149+
150+
const lastCallArgs = (fs.writeFileSync as jest.Mock).mock.calls.slice(
151+
-1,
152+
)[0];
153+
const writtenData = JSON.parse(lastCallArgs[1]);
154+
155+
expect(writtenData.history.length).toBe(1);
156+
expect(writtenData.history[0].operation_type).toBe("pull");
157+
expect(writtenData.history[0].path).toBe("test/path");
158+
});
159+
160+
it("should handle missing optional fields", () => {
161+
metadataHandler = new MetadataHandler(testBaseDir);
162+
163+
const params = {
164+
operationType: "pull",
165+
path: "test/path",
166+
duration_ms: 500,
167+
};
168+
169+
metadataHandler.logOperation(params);
170+
171+
const lastCallArgs = (fs.writeFileSync as jest.Mock).mock.calls.slice(
172+
-1,
173+
)[0];
174+
const writtenData = JSON.parse(lastCallArgs[1]);
175+
176+
expect(writtenData.history[0].successful_files).toEqual([]);
177+
expect(writtenData.history[0].failed_files).toEqual([]);
178+
expect(writtenData.history[0].environment).toBeUndefined();
179+
expect(writtenData.history[0].error).toBeUndefined();
180+
});
181+
});
182+
183+
describe("getLastOperation", () => {
184+
it("should return the last operation", () => {
185+
const operation = {
186+
timestamp: new Date().toISOString(),
187+
operation_type: "pull",
188+
path: "test/path",
189+
successful_files: [],
190+
failed_files: [],
191+
duration_ms: 1000,
192+
};
193+
194+
// Mock readFileSync to return a state with a last operation
195+
(fs.readFileSync as jest.Mock).mockReturnValue(
196+
JSON.stringify({
197+
last_operation: operation,
198+
history: [operation],
199+
}),
200+
);
201+
202+
metadataHandler = new MetadataHandler(testBaseDir);
203+
const result = metadataHandler.getLastOperation();
204+
205+
expect(result).toEqual(operation);
206+
});
207+
208+
it("should return null if no operations exist", () => {
209+
// Mock readFileSync to return empty state
210+
(fs.readFileSync as jest.Mock).mockReturnValue(
211+
JSON.stringify({
212+
last_operation: null,
213+
history: [],
214+
}),
215+
);
216+
217+
metadataHandler = new MetadataHandler(testBaseDir);
218+
const result = metadataHandler.getLastOperation();
219+
220+
expect(result).toBeNull();
221+
});
222+
});
223+
224+
describe("getHistory", () => {
225+
it("should return the operation history", () => {
226+
const operations = Array(3)
227+
.fill(null)
228+
.map((_, i) => ({
229+
timestamp: new Date().toISOString(),
230+
operation_type: "pull",
231+
path: `test/path${i}`,
232+
successful_files: [],
233+
failed_files: [],
234+
duration_ms: 1000 + i,
235+
}));
236+
237+
// Mock readFileSync to return a state with history
238+
(fs.readFileSync as jest.Mock).mockReturnValue(
239+
JSON.stringify({
240+
last_operation: operations[0],
241+
history: operations,
242+
}),
243+
);
244+
245+
metadataHandler = new MetadataHandler(testBaseDir);
246+
const result = metadataHandler.getHistory();
247+
248+
expect(result).toEqual(operations);
249+
expect(result.length).toBe(3);
250+
});
251+
252+
it("should return empty array if no history exists", () => {
253+
// Mock readFileSync to return empty state
254+
(fs.readFileSync as jest.Mock).mockReturnValue(
255+
JSON.stringify({
256+
last_operation: null,
257+
history: [],
258+
}),
259+
);
260+
261+
metadataHandler = new MetadataHandler(testBaseDir);
262+
const result = metadataHandler.getHistory();
263+
264+
expect(result).toEqual([]);
265+
});
266+
});
267+
});

0 commit comments

Comments
 (0)