Skip to content

Commit baedaeb

Browse files
committed
clean up environment patching
1 parent 194752b commit baedaeb

File tree

2 files changed

+355
-30
lines changed

2 files changed

+355
-30
lines changed

src/client/environment.test.ts

Lines changed: 287 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,287 @@
1+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2+
import {
3+
patchMath,
4+
createDeterministicDate,
5+
createConsole,
6+
} from "./environment.js";
7+
8+
describe("environment patching units", () => {
9+
describe("patchMath", () => {
10+
it("should preserve all Math methods except random", () => {
11+
const originalMath = Math;
12+
const patchedMath = patchMath(originalMath);
13+
14+
// Should preserve all other methods
15+
expect(patchedMath.abs).toBe(originalMath.abs);
16+
expect(patchedMath.sin).toBe(originalMath.sin);
17+
expect(patchedMath.cos).toBe(originalMath.cos);
18+
expect(patchedMath.PI).toBe(originalMath.PI);
19+
expect(patchedMath.E).toBe(originalMath.E);
20+
});
21+
22+
it("should replace Math.random with function that throws", () => {
23+
const originalMath = Math;
24+
const patchedMath = patchMath(originalMath);
25+
26+
expect(() => patchedMath.random()).toThrow(
27+
"Math.random() isn't currently supported within workflows",
28+
);
29+
});
30+
31+
it("should not mutate the original Math object", () => {
32+
const originalMath = Math;
33+
const originalRandom = Math.random;
34+
35+
patchMath(originalMath);
36+
37+
// Original Math should be unchanged
38+
expect(Math.random).toBe(originalRandom);
39+
});
40+
});
41+
42+
describe("createDeterministicDate", () => {
43+
const mockGetGenerationState = vi.fn();
44+
45+
beforeEach(() => {
46+
mockGetGenerationState.mockReturnValue({
47+
now: 1234567890000,
48+
latest: true,
49+
});
50+
});
51+
52+
afterEach(() => {
53+
mockGetGenerationState.mockReset();
54+
});
55+
56+
it("should create Date that uses generation state for Date.now()", () => {
57+
const testTime = 9876543210000;
58+
mockGetGenerationState.mockReturnValue({ now: testTime, latest: true });
59+
60+
const DeterministicDate = createDeterministicDate(
61+
Date,
62+
mockGetGenerationState,
63+
);
64+
65+
expect(DeterministicDate.now()).toBe(testTime);
66+
expect(mockGetGenerationState).toHaveBeenCalled();
67+
});
68+
69+
it("should create new Date with current timestamp when no args", () => {
70+
const testTime = 1111111111111;
71+
mockGetGenerationState.mockReturnValue({ now: testTime, latest: true });
72+
73+
const DeterministicDate = createDeterministicDate(
74+
Date,
75+
mockGetGenerationState,
76+
);
77+
const date = new DeterministicDate();
78+
79+
expect(date.getTime()).toBe(testTime);
80+
});
81+
82+
it("should create Date with provided args", () => {
83+
const DeterministicDate = createDeterministicDate(
84+
Date,
85+
mockGetGenerationState,
86+
);
87+
const date = new DeterministicDate(2023, 0, 1);
88+
89+
expect(date.getFullYear()).toBe(2023);
90+
expect(date.getMonth()).toBe(0);
91+
expect(date.getDate()).toBe(1);
92+
});
93+
94+
it("should return string when called without new", () => {
95+
const DeterministicDate = createDeterministicDate(
96+
Date,
97+
mockGetGenerationState,
98+
);
99+
100+
const dateString = (DeterministicDate as unknown as () => string)();
101+
expect(typeof dateString).toBe("string");
102+
});
103+
104+
it("should preserve original Date static methods", () => {
105+
const originalDate = Date;
106+
const DeterministicDate = createDeterministicDate(
107+
originalDate,
108+
mockGetGenerationState,
109+
);
110+
111+
expect(DeterministicDate.parse).toBe(originalDate.parse);
112+
expect(DeterministicDate.UTC).toBe(originalDate.UTC);
113+
expect(DeterministicDate.prototype).toBe(originalDate.prototype);
114+
});
115+
116+
it("should not affect the original Date constructor", () => {
117+
const originalNow = Date.now;
118+
119+
createDeterministicDate(Date, mockGetGenerationState);
120+
121+
// Original Date should be unchanged
122+
expect(Date.now).toBe(originalNow);
123+
});
124+
});
125+
126+
describe("createConsole", () => {
127+
const mockGetGenerationState = vi.fn();
128+
let mockConsole: {
129+
log: ReturnType<typeof vi.fn>;
130+
info: ReturnType<typeof vi.fn>;
131+
warn: ReturnType<typeof vi.fn>;
132+
error: ReturnType<typeof vi.fn>;
133+
debug: ReturnType<typeof vi.fn>;
134+
group: ReturnType<typeof vi.fn>;
135+
groupEnd: ReturnType<typeof vi.fn>;
136+
};
137+
138+
beforeEach(() => {
139+
mockConsole = {
140+
log: vi.fn(),
141+
info: vi.fn(),
142+
warn: vi.fn(),
143+
error: vi.fn(),
144+
debug: vi.fn(),
145+
group: vi.fn(),
146+
groupEnd: vi.fn(),
147+
};
148+
});
149+
150+
afterEach(() => {
151+
mockGetGenerationState.mockReset();
152+
});
153+
154+
it("should allow console methods when latest is true", () => {
155+
mockGetGenerationState.mockReturnValue({ now: 1000, latest: true });
156+
157+
const proxiedConsole = createConsole(
158+
mockConsole as unknown as Console,
159+
mockGetGenerationState,
160+
);
161+
162+
proxiedConsole.log("test");
163+
proxiedConsole.info("test");
164+
proxiedConsole.warn("test");
165+
proxiedConsole.error("test");
166+
167+
expect(mockConsole.log).toHaveBeenCalledWith("test");
168+
expect(mockConsole.info).toHaveBeenCalledWith("test");
169+
expect(mockConsole.warn).toHaveBeenCalledWith("test");
170+
expect(mockConsole.error).toHaveBeenCalledWith("test");
171+
});
172+
173+
it("should return noop function when latest is false", () => {
174+
mockGetGenerationState.mockReturnValue({ now: 1000, latest: false });
175+
176+
const proxiedConsole = createConsole(
177+
mockConsole as unknown as Console,
178+
mockGetGenerationState,
179+
);
180+
181+
// Methods should be functions (noop) but not call the original
182+
expect(typeof proxiedConsole.log).toBe("function");
183+
expect(typeof proxiedConsole.info).toBe("function");
184+
185+
proxiedConsole.log("test");
186+
proxiedConsole.info("test");
187+
188+
expect(mockConsole.log).not.toHaveBeenCalled();
189+
expect(mockConsole.info).not.toHaveBeenCalled();
190+
});
191+
192+
it("should throw error for console.Console access", () => {
193+
mockGetGenerationState.mockReturnValue({ now: 1000, latest: true });
194+
195+
const proxiedConsole = createConsole(
196+
mockConsole as unknown as Console,
197+
mockGetGenerationState,
198+
);
199+
200+
expect(() => proxiedConsole.Console).toThrow(
201+
"console.Console() is not supported within workflows",
202+
);
203+
});
204+
205+
it("should handle console.count with state tracking", () => {
206+
mockGetGenerationState.mockReturnValue({ now: 1000, latest: true });
207+
208+
const proxiedConsole = createConsole(
209+
mockConsole as unknown as Console,
210+
mockGetGenerationState,
211+
);
212+
213+
proxiedConsole.count("test");
214+
proxiedConsole.count("test");
215+
proxiedConsole.count(); // default label
216+
217+
expect(mockConsole.info).toHaveBeenCalledWith("test: 1");
218+
expect(mockConsole.info).toHaveBeenCalledWith("test: 2");
219+
expect(mockConsole.info).toHaveBeenCalledWith("default: 1");
220+
});
221+
222+
it("should handle console.countReset", () => {
223+
mockGetGenerationState.mockReturnValue({ now: 1000, latest: true });
224+
225+
const proxiedConsole = createConsole(
226+
mockConsole as unknown as Console,
227+
mockGetGenerationState,
228+
);
229+
230+
proxiedConsole.count("test");
231+
proxiedConsole.count("test");
232+
proxiedConsole.countReset("test");
233+
proxiedConsole.count("test");
234+
235+
expect(mockConsole.info).toHaveBeenCalledWith("test: 1");
236+
expect(mockConsole.info).toHaveBeenCalledWith("test: 2");
237+
expect(mockConsole.info).toHaveBeenCalledWith("test: 1");
238+
});
239+
240+
it("should always pass through groupEnd", () => {
241+
mockGetGenerationState.mockReturnValue({ now: 1000, latest: false });
242+
243+
const proxiedConsole = createConsole(
244+
mockConsole as unknown as Console,
245+
mockGetGenerationState,
246+
);
247+
248+
proxiedConsole.groupEnd();
249+
expect(mockConsole.groupEnd).toHaveBeenCalled();
250+
});
251+
252+
it("should handle time/timeEnd with generation state", () => {
253+
const startTime = 1000;
254+
const endTime = 1500;
255+
256+
// Mock different return values for different calls
257+
mockGetGenerationState
258+
.mockReturnValueOnce({ now: startTime, latest: false }) // for time()
259+
.mockReturnValueOnce({ now: endTime, latest: true }); // for timeEnd()
260+
261+
const proxiedConsole = createConsole(
262+
mockConsole as unknown as Console,
263+
mockGetGenerationState,
264+
);
265+
266+
proxiedConsole.time("test");
267+
proxiedConsole.timeEnd("test");
268+
269+
expect(mockConsole.info).toHaveBeenCalledWith("test: 500ms");
270+
});
271+
272+
it("should not call console methods for count when latest is false", () => {
273+
mockGetGenerationState.mockReturnValue({ now: 1000, latest: false });
274+
275+
const proxiedConsole = createConsole(
276+
mockConsole as unknown as Console,
277+
mockGetGenerationState,
278+
);
279+
280+
proxiedConsole.count("test");
281+
proxiedConsole.count("test");
282+
283+
// Should not call info when latest is false
284+
expect(mockConsole.info).not.toHaveBeenCalled();
285+
});
286+
});
287+
});

0 commit comments

Comments
 (0)