@@ -81,4 +81,198 @@ describe('WorkspaceConfigManager', () => {
8181 expect . stringContaining ( '"new-store": "stores/123"' )
8282 ) ;
8383 } ) ;
84+
85+ it ( 'should return default config and warn when file is corrupt' , ( ) => {
86+ const warnSpy = jest . spyOn ( console , 'warn' ) . mockImplementation ( ( ) => { } ) ;
87+ mockExistsSync . mockReturnValue ( true ) ;
88+ mockReadFileSync . mockReturnValue ( '{ invalid json }' ) ;
89+
90+ const config = WorkspaceConfigManager . load ( ) ;
91+
92+ expect ( config ) . toEqual ( { researchIds : [ ] , fileSearchStores : { } , uploadOperations : { } } ) ;
93+ expect ( warnSpy ) . toHaveBeenCalledWith (
94+ expect . stringContaining ( 'Failed to load workspace config' ) ,
95+ expect . anything ( )
96+ ) ;
97+ warnSpy . mockRestore ( ) ;
98+ } ) ;
99+
100+ it ( 'should return default config and warn when schema validation fails' , ( ) => {
101+ const warnSpy = jest . spyOn ( console , 'warn' ) . mockImplementation ( ( ) => { } ) ;
102+ mockExistsSync . mockReturnValue ( true ) ;
103+ // Valid JSON but invalid schema: researchIds should be an array, not a string
104+ mockReadFileSync . mockReturnValue ( JSON . stringify ( { researchIds : 'not-an-array' } ) ) ;
105+
106+ const config = WorkspaceConfigManager . load ( ) ;
107+
108+ expect ( config ) . toEqual ( { researchIds : [ ] , fileSearchStores : { } , uploadOperations : { } } ) ;
109+ expect ( warnSpy ) . toHaveBeenCalledWith (
110+ expect . stringContaining ( 'Failed to load workspace config' ) ,
111+ expect . anything ( )
112+ ) ;
113+ warnSpy . mockRestore ( ) ;
114+ } ) ;
115+
116+ it ( 'should not add duplicate research ID' , ( ) => {
117+ mockExistsSync . mockReturnValue ( true ) ;
118+ mockReadFileSync . mockReturnValue ( JSON . stringify ( { researchIds : [ 'existing-id' ] , fileSearchStores : { } , uploadOperations : { } } ) ) ;
119+
120+ WorkspaceConfigManager . addResearchId ( 'existing-id' ) ;
121+
122+ // Should not call save since ID already exists
123+ expect ( mockWriteFileSync ) . not . toHaveBeenCalled ( ) ;
124+ } ) ;
125+
126+ it ( 'should get upload operation by id' , ( ) => {
127+ const mockOperation = {
128+ id : 'op-123' ,
129+ status : 'completed' ,
130+ path : '/test/path' ,
131+ storeName : 'store-1' ,
132+ smartSync : false ,
133+ totalFiles : 10 ,
134+ completedFiles : 10 ,
135+ skippedFiles : 0 ,
136+ failedFiles : 0 ,
137+ failedFilesList : [ ] ,
138+ startedAt : '2024-01-01T00:00:00Z' ,
139+ } ;
140+ mockExistsSync . mockReturnValue ( true ) ;
141+ mockReadFileSync . mockReturnValue ( JSON . stringify ( {
142+ researchIds : [ ] ,
143+ fileSearchStores : { } ,
144+ uploadOperations : { 'op-123' : mockOperation }
145+ } ) ) ;
146+
147+ const operation = WorkspaceConfigManager . getUploadOperation ( 'op-123' ) ;
148+
149+ expect ( operation ) . toEqual ( mockOperation ) ;
150+ } ) ;
151+
152+ it ( 'should return undefined for non-existent upload operation' , ( ) => {
153+ mockExistsSync . mockReturnValue ( true ) ;
154+ mockReadFileSync . mockReturnValue ( JSON . stringify ( { researchIds : [ ] , fileSearchStores : { } , uploadOperations : { } } ) ) ;
155+
156+ const operation = WorkspaceConfigManager . getUploadOperation ( 'non-existent' ) ;
157+
158+ expect ( operation ) . toBeUndefined ( ) ;
159+ } ) ;
160+
161+ it ( 'should set upload operation' , ( ) => {
162+ const mockOperation = {
163+ id : 'op-456' ,
164+ status : 'in_progress' as const ,
165+ path : '/new/path' ,
166+ storeName : 'store-2' ,
167+ smartSync : true ,
168+ totalFiles : 5 ,
169+ completedFiles : 2 ,
170+ skippedFiles : 1 ,
171+ failedFiles : 0 ,
172+ failedFilesList : [ ] ,
173+ startedAt : '2024-01-01T00:00:00Z' ,
174+ } ;
175+ mockExistsSync . mockReturnValue ( true ) ;
176+ mockReadFileSync . mockReturnValue ( JSON . stringify ( { researchIds : [ ] , fileSearchStores : { } , uploadOperations : { } } ) ) ;
177+
178+ WorkspaceConfigManager . setUploadOperation ( 'op-456' , mockOperation ) ;
179+
180+ expect ( mockWriteFileSync ) . toHaveBeenCalledWith (
181+ mockConfigPath ,
182+ expect . stringContaining ( '"op-456"' )
183+ ) ;
184+ } ) ;
185+
186+ it ( 'should get all upload operations' , ( ) => {
187+ const mockOperations = {
188+ 'op-1' : { id : 'op-1' , status : 'completed' , path : '/path1' , storeName : 'store' , smartSync : false , totalFiles : 1 , completedFiles : 1 , skippedFiles : 0 , failedFiles : 0 , failedFilesList : [ ] , startedAt : '2024-01-01' } ,
189+ 'op-2' : { id : 'op-2' , status : 'pending' , path : '/path2' , storeName : 'store' , smartSync : true , totalFiles : 0 , completedFiles : 0 , skippedFiles : 0 , failedFiles : 0 , failedFilesList : [ ] , startedAt : '2024-01-02' } ,
190+ } ;
191+ mockExistsSync . mockReturnValue ( true ) ;
192+ mockReadFileSync . mockReturnValue ( JSON . stringify ( {
193+ researchIds : [ ] ,
194+ fileSearchStores : { } ,
195+ uploadOperations : mockOperations
196+ } ) ) ;
197+
198+ const operations = WorkspaceConfigManager . getAllUploadOperations ( ) ;
199+
200+ expect ( operations ) . toEqual ( mockOperations ) ;
201+ } ) ;
202+ } ) ;
203+
204+ // Dynamic import WorkspaceOperationStorage
205+ const { WorkspaceOperationStorage } = await import ( './WorkspaceConfig' ) ;
206+
207+ describe ( 'WorkspaceOperationStorage' , ( ) => {
208+ beforeEach ( ( ) => {
209+ jest . clearAllMocks ( ) ;
210+ } ) ;
211+
212+ it ( 'should get operation via WorkspaceConfigManager' , ( ) => {
213+ const mockOperation = {
214+ id : 'op-789' ,
215+ status : 'completed' ,
216+ path : '/test' ,
217+ storeName : 'store' ,
218+ smartSync : false ,
219+ totalFiles : 1 ,
220+ completedFiles : 1 ,
221+ skippedFiles : 0 ,
222+ failedFiles : 0 ,
223+ failedFilesList : [ ] ,
224+ startedAt : '2024-01-01' ,
225+ } ;
226+ mockExistsSync . mockReturnValue ( true ) ;
227+ mockReadFileSync . mockReturnValue ( JSON . stringify ( {
228+ researchIds : [ ] ,
229+ fileSearchStores : { } ,
230+ uploadOperations : { 'op-789' : mockOperation }
231+ } ) ) ;
232+
233+ const storage = new WorkspaceOperationStorage ( ) ;
234+ const operation = storage . get ( 'op-789' ) ;
235+
236+ expect ( operation ) . toEqual ( mockOperation ) ;
237+ } ) ;
238+
239+ it ( 'should set operation via WorkspaceConfigManager' , ( ) => {
240+ const mockOperation = {
241+ id : 'op-new' ,
242+ status : 'pending' as const ,
243+ path : '/new' ,
244+ storeName : 'store' ,
245+ smartSync : false ,
246+ totalFiles : 0 ,
247+ completedFiles : 0 ,
248+ skippedFiles : 0 ,
249+ failedFiles : 0 ,
250+ failedFilesList : [ ] ,
251+ startedAt : '2024-01-01' ,
252+ } ;
253+ mockExistsSync . mockReturnValue ( true ) ;
254+ mockReadFileSync . mockReturnValue ( JSON . stringify ( { researchIds : [ ] , fileSearchStores : { } , uploadOperations : { } } ) ) ;
255+
256+ const storage = new WorkspaceOperationStorage ( ) ;
257+ storage . set ( 'op-new' , mockOperation ) ;
258+
259+ expect ( mockWriteFileSync ) . toHaveBeenCalled ( ) ;
260+ } ) ;
261+
262+ it ( 'should get all operations via WorkspaceConfigManager' , ( ) => {
263+ const mockOperations = {
264+ 'op-a' : { id : 'op-a' , status : 'completed' , path : '/a' , storeName : 'store' , smartSync : false , totalFiles : 1 , completedFiles : 1 , skippedFiles : 0 , failedFiles : 0 , failedFilesList : [ ] , startedAt : '2024-01-01' } ,
265+ } ;
266+ mockExistsSync . mockReturnValue ( true ) ;
267+ mockReadFileSync . mockReturnValue ( JSON . stringify ( {
268+ researchIds : [ ] ,
269+ fileSearchStores : { } ,
270+ uploadOperations : mockOperations
271+ } ) ) ;
272+
273+ const storage = new WorkspaceOperationStorage ( ) ;
274+ const operations = storage . getAll ( ) ;
275+
276+ expect ( operations ) . toEqual ( mockOperations ) ;
277+ } ) ;
84278} ) ;
0 commit comments