33import * as vscode from "vscode"
44
55import { FileWatcher } from "../file-watcher"
6- import type { PointStruct } from "../../interfaces"
7- import { createHash } from "crypto"
86
97import { clearAllMocks } from "../../../../test-utils/reset"
108
@@ -93,136 +91,6 @@ vi.mock("vscode", () => ({
9391} ) )
9492
9593describe ( "FileWatcher" , ( ) => {
96- const deferred = ( ) => {
97- let resolve ! : ( ) => void
98- const promise = new Promise < void > ( ( done ) => {
99- resolve = done
100- } )
101- return { promise, resolve }
102- }
103-
104- it ( "drains delayed persistence before concurrent restarts can write newer points and hashes" , async ( ) => {
105- await fileWatcher . initialize ( )
106- vi . mocked ( vscode . workspace . createFileSystemWatcher ) . mockClear ( )
107- const blocked = deferred ( )
108- const entered = deferred ( )
109- const persisted = new Map < string | number , PointStruct > ( )
110- const hashes = new Map < string , string > ( )
111- mockCacheManager . updateHash . mockImplementation ( ( path : string , hash : string ) => hashes . set ( path , hash ) )
112- mockVectorStore . upsertPoints . mockImplementation ( async ( points : PointStruct [ ] ) => {
113- entered . resolve ( )
114- await blocked . promise
115- for ( const point of points ) persisted . set ( point . id , point )
116- } )
117- const path = "/mock/workspace/same.ts"
118- await mockOnDidCreate ( vscode . Uri . file ( path ) )
119- await flushBatch ( )
120- await entered . promise
121- fileWatcher . dispose ( )
122- const restarted = vi . fn ( )
123- const restarts = [ fileWatcher . initialize ( ) . then ( restarted ) , fileWatcher . initialize ( ) ]
124- await Promise . resolve ( )
125- expect ( restarted ) . not . toHaveBeenCalled ( )
126- expect ( vscode . workspace . createFileSystemWatcher ) . not . toHaveBeenCalled ( )
127- expect ( hashes . size ) . toBe ( 0 )
128- blocked . resolve ( )
129- await Promise . all ( restarts )
130- expect ( vscode . workspace . createFileSystemWatcher ) . toHaveBeenCalledOnce ( )
131- vi . spyOn ( fileWatcher , "processFile" ) . mockResolvedValueOnce ( {
132- path,
133- status : "processed_for_batching" ,
134- newHash : "new-hash" ,
135- pointsToUpsert : [ ...persisted . values ( ) ] . map ( ( point ) => ( { ...point , vector : [ 9 , 9 , 9 ] } ) ) ,
136- } )
137- await mockOnDidChange ( vscode . Uri . file ( path ) )
138- await flushBatch ( )
139- await fileWatcher . waitForIdle ( )
140- expect ( [ ...persisted . values ( ) ] . map ( ( point ) => point . vector ) ) . toEqual ( [ [ 9 , 9 , 9 ] ] )
141- expect ( hashes . get ( path ) ) . toBe ( "new-hash" )
142- expect ( mockCacheManager . updateHash . mock . calls . map ( ( call : string [ ] ) => call [ 1 ] ) ) . toEqual ( [
143- createHash ( "sha256" ) . update ( "test content" ) . digest ( "hex" ) ,
144- "new-hash" ,
145- ] )
146- } )
147-
148- it ( "waits for every overlapping batch, not only the latest, and releases all idle callers" , async ( ) => {
149- await fileWatcher . initialize ( )
150- const first = deferred ( )
151- const second = deferred ( )
152- mockVectorStore . upsertPoints
153- . mockImplementationOnce ( ( ) => first . promise )
154- . mockImplementationOnce ( ( ) => second . promise )
155- await mockOnDidCreate ( vscode . Uri . file ( "/mock/workspace/first.ts" ) )
156- await flushBatch ( )
157- await mockOnDidCreate ( vscode . Uri . file ( "/mock/workspace/second.ts" ) )
158- await flushBatch ( )
159- expect ( mockVectorStore . upsertPoints ) . toHaveBeenCalledTimes ( 2 )
160- fileWatcher . dispose ( )
161- const idle = vi . fn ( )
162- const waiters = [ fileWatcher . waitForIdle ( ) . then ( idle ) , fileWatcher . waitForIdle ( ) . then ( idle ) ]
163- second . resolve ( )
164- await flushBatch ( )
165- expect ( idle ) . not . toHaveBeenCalled ( )
166- first . resolve ( )
167- await Promise . all ( waiters )
168- expect ( idle ) . toHaveBeenCalledTimes ( 2 )
169- expect ( fileWatcher [ "activeBatches" ] . size ) . toBe ( 0 )
170- await fileWatcher . waitForIdle ( )
171- } )
172-
173- it ( "does not revive a pending restart after another disposal or accept stale native callbacks" , async ( ) => {
174- await fileWatcher . initialize ( )
175- const staleCreate = mockOnDidCreate
176- const blocked = deferred ( )
177- mockVectorStore . upsertPoints . mockImplementationOnce ( ( ) => blocked . promise )
178- await mockOnDidCreate ( vscode . Uri . file ( "/mock/workspace/old.ts" ) )
179- await flushBatch ( )
180- fileWatcher . dispose ( )
181- vi . mocked ( vscode . workspace . createFileSystemWatcher ) . mockClear ( )
182- const restart = fileWatcher . initialize ( )
183- fileWatcher . dispose ( )
184- blocked . resolve ( )
185- await restart
186- expect ( vscode . workspace . createFileSystemWatcher ) . not . toHaveBeenCalled ( )
187- await fileWatcher . initialize ( )
188- await staleCreate ( vscode . Uri . file ( "/mock/workspace/stale.ts" ) )
189- await flushBatch ( )
190- expect ( mockVectorStore . upsertPoints ) . toHaveBeenCalledOnce ( )
191- expect ( vscode . workspace . createFileSystemWatcher ) . toHaveBeenCalledOnce ( )
192- } )
193-
194- it ( "registers work before a batch-start listener restarts the watcher" , async ( ) => {
195- await fileWatcher . initialize ( )
196- const blocked = deferred ( )
197- mockVectorStore . upsertPoints . mockImplementationOnce ( ( ) => blocked . promise )
198- let restart : Promise < void > | undefined
199- fileWatcher . onDidStartBatchProcessing ( ( ) => {
200- fileWatcher . dispose ( )
201- restart = fileWatcher . initialize ( )
202- } )
203- vi . mocked ( vscode . workspace . createFileSystemWatcher ) . mockClear ( )
204- await mockOnDidCreate ( vscode . Uri . file ( "/mock/workspace/old.ts" ) )
205- await flushBatch ( )
206- expect ( restart ) . toBeDefined ( )
207- expect ( vscode . workspace . createFileSystemWatcher ) . not . toHaveBeenCalled ( )
208- blocked . resolve ( )
209- await restart
210- expect ( vscode . workspace . createFileSystemWatcher ) . toHaveBeenCalledOnce ( )
211- } )
212-
213- it ( "handles timer batch rejection and removes rejected work from the drain set" , async ( ) => {
214- await fileWatcher . initialize ( )
215- const error = new Error ( "unexpected batch failure" )
216- const log = vi . spyOn ( console , "error" ) . mockImplementation ( ( ) => { } )
217- fileWatcher [ "processBatch" ] = vi . fn < FileWatcher [ "processBatch" ] > ( ) . mockRejectedValueOnce ( error )
218- await mockOnDidCreate ( vscode . Uri . file ( "/mock/workspace/error.ts" ) )
219- await flushBatch ( )
220- await fileWatcher . waitForIdle ( )
221- expect ( log ) . toHaveBeenCalledWith ( "[FileWatcher] Unhandled batch processing error:" , error )
222- expect ( fileWatcher [ "activeBatches" ] . size ) . toBe ( 0 )
223- log . mockRestore ( )
224- } )
225-
22694 it ( "does not deliver an old batch into subscriptions created after restart" , async ( ) => {
22795 await fileWatcher . initialize ( )
22896 let release ! : ( ) => void
@@ -242,13 +110,12 @@ describe("FileWatcher", () => {
242110 const processing = flushBatch ( )
243111 await started
244112 fileWatcher . dispose ( )
245- const restarting = fileWatcher . initialize ( )
246- release ( )
247- await restarting
113+ await fileWatcher . initialize ( )
248114 const progress = vi . fn ( )
249115 const finished = vi . fn ( )
250116 fileWatcher . onBatchProgressUpdate ( progress )
251117 fileWatcher . onDidFinishBatchProcessing ( finished )
118+ release ( )
252119 await processing
253120 expect ( progress ) . not . toHaveBeenCalled ( )
254121 expect ( finished ) . not . toHaveBeenCalled ( )
0 commit comments