Skip to content

Commit a7b0bd3

Browse files
authored
fix(deps): upgrade vitest to ^4.1.0 to patch critical Vitest UI advisory (GHSA-5xrq-8626-4rwp) (#4837)
* fix(deps): upgrade vitest to ^4.1.0 to patch critical Vitest UI advisory (GHSA-5xrq-8626-4rwp) - Bump vitest and @vitest/coverage-v8 to ^4.1.0 across all workspaces (only patched release for the critical 'Vitest UI server arbitrary file read/execute' advisory; no 3.x backport exists) - Widen @sim/testing peer range to ^3.0.0 || ^4.0.0 - Migrate constructor mocks to class expressions: vitest 4 uses Reflect.construct for mocks invoked with new, and arrow/function implementations are not constructable (function expressions also get reverted to arrows by biome's useArrowFunction) - Remove deprecated test.poolOptions from apps/sim/vitest.config.ts (options are now top-level in vitest 4) * fix(deps): exclude vulnerable vitest 4.0.x from @sim/testing peer range Tighten the v4 arm of the peer range to >=4.1.0 <5.0.0 so the peer requirement cannot be satisfied by the unpatched 4.0.x builds that GHSA-5xrq-8626-4rwp affects. * fix(testing): make vitest 4 constructor mocks type-check cleanly - logging-session & mcp-oauth mocks: a class passed to mockImplementation has a construct signature that isn't assignable to its (...args) => any parameter, failing tsc. Use named function declarations instead (constructable via Reflect.construct, assignable to mockImplementation, and not rewritten to arrows by biome's useArrowFunction). - database.mock.ts: vitest 4's generic vi.fn typings no longer break the self-referential cycle on the transaction callback's tx param; loosen tx and annotate the callback's return type to resolve the implicit-any errors. * test(isolated-vm): de-flake queue-capacity scheduler tests The 'queue is full' and 'per-owner queued limit' tests relied on 'await sleep(1)' to assume the first request had reached the queue before submitting the overflow request. The first request only enqueues after an async spawn-failure chain (acquireWorker -> spawn exit -> resolve null -> enqueue), which isn't guaranteed within 1ms under CI load — the overflow request then found an empty queue and hit the 200ms queue-wait timeout instead of the capacity rejection. Replace the wall-clock barrier with a deterministic, event-driven one: hold the single global concurrency slot (IVM_MAX_CONCURRENT=1) with an active worker and await an explicit 'dispatched' signal (fired when the worker receives its execute message, after the scheduler counts it active). The follow-up requests then deterministically hit the synchronous enqueue path. Also drops the queue-wait timeout from 200ms to 50ms, so the tests run faster.
1 parent 9bed841 commit a7b0bd3

37 files changed

Lines changed: 562 additions & 353 deletions

File tree

apps/realtime/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,6 @@
4343
"@types/node": "24.2.1",
4444
"socket.io-client": "4.8.1",
4545
"typescript": "^5.7.3",
46-
"vitest": "^3.0.8"
46+
"vitest": "^4.1.0"
4747
}
4848
}

apps/sim/app/api/copilot/checkpoints/revert/route.test.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -94,16 +94,23 @@ describe('Copilot Checkpoints Revert API Route', () => {
9494
vi.spyOn(Date, 'now').mockReturnValue(1640995200000)
9595

9696
const originalDate = Date
97-
vi.spyOn(global, 'Date').mockImplementation(((...args: any[]) => {
97+
const buildDate = (args: any[]): Date => {
9898
if (args.length === 0) {
99-
const mockDate = new originalDate('2024-01-01T00:00:00.000Z')
100-
return mockDate
99+
return new originalDate('2024-01-01T00:00:00.000Z')
101100
}
102101
if (args.length === 1) {
103102
return new originalDate(args[0])
104103
}
105104
return new originalDate(args[0], args[1], args[2], args[3], args[4], args[5], args[6])
106-
}) as any)
105+
}
106+
vi.spyOn(global, 'Date').mockImplementation(
107+
class {
108+
constructor(...args: any[]) {
109+
// biome-ignore lint/correctness/noConstructorReturn: vitest 4 constructs mocks via Reflect.construct; returning a real Date overrides the instance so `new Date(...)` yields a genuine Date the route can call .toISOString()/.getTime() on
110+
return buildDate(args)
111+
}
112+
} as any
113+
)
107114
})
108115

109116
afterEach(() => {

apps/sim/lib/copilot/request/lifecycle/start.test.ts

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -65,31 +65,33 @@ vi.mock('@/lib/copilot/request/session', () => ({
6565
startAbortPoller: vi.fn().mockReturnValue(setInterval(() => {}, 999999)),
6666
isExplicitStopReason: vi.fn().mockReturnValue(false),
6767
SSE_RESPONSE_HEADERS: {},
68-
StreamWriter: vi.fn().mockImplementation(() => ({
69-
attach: vi.fn().mockImplementation((ctrl: ReadableStreamDefaultController) => {
70-
mockPublisherController = ctrl
71-
}),
72-
startKeepalive: vi.fn(),
73-
stopKeepalive: vi.fn(),
74-
flush: vi.fn(),
75-
close: vi.fn().mockImplementation(() => {
76-
try {
77-
mockPublisherController?.close()
78-
} catch {
79-
// already closed
68+
StreamWriter: vi.fn().mockImplementation(
69+
class {
70+
attach = vi.fn().mockImplementation((ctrl: ReadableStreamDefaultController) => {
71+
mockPublisherController = ctrl
72+
})
73+
startKeepalive = vi.fn()
74+
stopKeepalive = vi.fn()
75+
flush = vi.fn()
76+
close = vi.fn().mockImplementation(() => {
77+
try {
78+
mockPublisherController?.close()
79+
} catch {
80+
// already closed
81+
}
82+
})
83+
markDisconnected = vi.fn()
84+
publish = vi.fn().mockImplementation(async (event: Record<string, unknown>) => {
85+
appendEvent(event)
86+
})
87+
get clientDisconnected() {
88+
return false
89+
}
90+
get sawComplete() {
91+
return false
8092
}
81-
}),
82-
markDisconnected: vi.fn(),
83-
publish: vi.fn().mockImplementation(async (event: Record<string, unknown>) => {
84-
appendEvent(event)
85-
}),
86-
get clientDisconnected() {
87-
return false
88-
},
89-
get sawComplete() {
90-
return false
91-
},
92-
})),
93+
}
94+
),
9395
}))
9496
vi.mock('@/lib/copilot/request/session/sse', () => ({
9597
SSE_RESPONSE_HEADERS: {},

apps/sim/lib/core/config/redis.test.ts

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,13 @@ const { MockRedisConstructor } = vi.hoisted(() => ({
66
}))
77

88
const mockRedisInstance = createMockRedis()
9-
MockRedisConstructor.mockImplementation(() => mockRedisInstance)
9+
MockRedisConstructor.mockImplementation(
10+
class {
11+
constructor() {
12+
Object.assign(this, mockRedisInstance)
13+
}
14+
}
15+
)
1016

1117
vi.mock('@/lib/core/config/env', () => createEnvMock({ REDIS_URL: 'redis://localhost:6379' }))
1218
vi.mock('ioredis', () => ({
@@ -26,7 +32,13 @@ describe('redis config', () => {
2632
vi.clearAllMocks()
2733
vi.useFakeTimers()
2834
resetForTesting()
29-
MockRedisConstructor.mockImplementation(() => mockRedisInstance)
35+
MockRedisConstructor.mockImplementation(
36+
class {
37+
constructor() {
38+
Object.assign(this, mockRedisInstance)
39+
}
40+
}
41+
)
3042
})
3143

3244
afterEach(() => {
@@ -197,10 +209,14 @@ describe('redis config', () => {
197209
describe('retryStrategy', () => {
198210
function captureRetryStrategy(): (times: number) => number {
199211
let capturedConfig: Record<string, unknown> = {}
200-
MockRedisConstructor.mockImplementation((_url: string, config: Record<string, unknown>) => {
201-
capturedConfig = config
202-
return { ping: vi.fn(), on: vi.fn() }
203-
})
212+
MockRedisConstructor.mockImplementation(
213+
class {
214+
constructor(_url: string, config: Record<string, unknown>) {
215+
capturedConfig = config
216+
Object.assign(this, { ping: vi.fn(), on: vi.fn() })
217+
}
218+
}
219+
)
204220

205221
getRedisClient()
206222

apps/sim/lib/core/rate-limiter/storage/factory.test.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,19 @@ vi.mock('@/lib/core/storage', () => ({
1919
}))
2020

2121
vi.mock('@/lib/core/rate-limiter/storage/db-token-bucket', () => ({
22-
DbTokenBucket: vi.fn(() => ({ type: 'db' })),
22+
DbTokenBucket: vi.fn().mockImplementation(
23+
class {
24+
type = 'db'
25+
}
26+
),
2327
}))
2428

2529
vi.mock('@/lib/core/rate-limiter/storage/redis-token-bucket', () => ({
26-
RedisTokenBucket: vi.fn(() => ({ type: 'redis' })),
30+
RedisTokenBucket: vi.fn().mockImplementation(
31+
class {
32+
type = 'redis'
33+
}
34+
),
2735
}))
2836

2937
import { createStorageAdapter, resetStorageAdapter } from '@/lib/core/rate-limiter/storage/factory'

apps/sim/lib/data-drains/destinations/azure_blob.test.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,12 @@ const { mockUpload, mockDeleteIfExists, BlobServiceClientCtor, StorageSharedKeyC
1212
return {
1313
mockUpload,
1414
mockDeleteIfExists,
15-
BlobServiceClientCtor: vi.fn(() => ({ getContainerClient: vi.fn(() => containerClient) })),
16-
StorageSharedKeyCredentialCtor: vi.fn(),
15+
BlobServiceClientCtor: vi.fn().mockImplementation(
16+
class {
17+
getContainerClient = vi.fn(() => containerClient)
18+
}
19+
),
20+
StorageSharedKeyCredentialCtor: vi.fn().mockImplementation(class {}),
1721
}
1822
})
1923

apps/sim/lib/data-drains/destinations/bigquery.test.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,11 @@ const { mockGetAccessToken, JWTCtor, loggerInstance } = vi.hoisted(() => {
1717
}
1818
return {
1919
mockGetAccessToken,
20-
JWTCtor: vi.fn(() => ({ getAccessToken: mockGetAccessToken })),
20+
JWTCtor: vi.fn().mockImplementation(
21+
class {
22+
getAccessToken = mockGetAccessToken
23+
}
24+
),
2125
loggerInstance,
2226
}
2327
})

apps/sim/lib/data-drains/destinations/gcs.test.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@ const { mockGetAccessToken, JWTCtor } = vi.hoisted(() => {
77
const mockGetAccessToken = vi.fn(async () => ({ token: 'fake-access-token' }))
88
return {
99
mockGetAccessToken,
10-
JWTCtor: vi.fn(() => ({ getAccessToken: mockGetAccessToken })),
10+
JWTCtor: vi.fn().mockImplementation(
11+
class {
12+
getAccessToken = mockGetAccessToken
13+
}
14+
),
1115
}
1216
})
1317

apps/sim/lib/data-drains/destinations/s3.test.ts

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,30 @@ const { mockSend, mockDestroy, S3ClientCtor, PutObjectCommandCtor, DeleteObjectC
1010
return {
1111
mockSend,
1212
mockDestroy,
13-
S3ClientCtor: vi.fn(() => ({ send: mockSend, destroy: mockDestroy })),
14-
PutObjectCommandCtor: vi.fn((args: unknown) => ({ __cmd: 'put', args })),
15-
DeleteObjectCommandCtor: vi.fn((args: unknown) => ({ __cmd: 'delete', args })),
13+
S3ClientCtor: vi.fn().mockImplementation(
14+
class {
15+
send = mockSend
16+
destroy = mockDestroy
17+
}
18+
),
19+
PutObjectCommandCtor: vi.fn().mockImplementation(
20+
class {
21+
__cmd = 'put'
22+
args: unknown
23+
constructor(args: unknown) {
24+
this.args = args
25+
}
26+
}
27+
),
28+
DeleteObjectCommandCtor: vi.fn().mockImplementation(
29+
class {
30+
__cmd = 'delete'
31+
args: unknown
32+
constructor(args: unknown) {
33+
this.args = args
34+
}
35+
}
36+
),
1637
}
1738
})
1839

0 commit comments

Comments
 (0)