@@ -151,32 +151,137 @@ describe('BuildsService', () => {
151151 } ) ;
152152 } ) ;
153153
154- it ( 'delete' , async ( ) => {
155- const buildFindUniqueMock = jest . fn ( ) . mockResolvedValueOnce ( build ) ;
156- const buildFindManyMock = jest . fn ( ) . mockImplementation ( ( ) => Promise . resolve ( build ) ) ;
157- const buildDeleteMock = jest . fn ( ) . mockImplementation ( ( ) => Promise . resolve ( build ) ) ;
158- const testRunDeleteMock = jest . fn ( ) ;
159- const eventBuildDeletedMock = jest . fn ( ) ;
160- service = await initService ( {
161- buildFindUniqueMock,
162- buildDeleteMock,
163- testRunDeleteMock,
164- eventBuildDeletedMock,
165- buildFindManyMock,
154+ describe ( 'remove' , ( ) => {
155+ it ( 'should remove a build successfully' , async ( ) => {
156+ const buildFindUniqueMock = jest . fn ( ) . mockResolvedValueOnce ( build ) ;
157+ const buildFindManyMock = jest . fn ( ) . mockImplementation ( ( ) => Promise . resolve ( build ) ) ;
158+ const buildDeleteMock = jest . fn ( ) . mockImplementation ( ( ) => Promise . resolve ( build ) ) ;
159+ const testRunDeleteMock = jest . fn ( ) ;
160+ const eventBuildDeletedMock = jest . fn ( ) ;
161+ service = await initService ( {
162+ buildFindUniqueMock,
163+ buildDeleteMock,
164+ testRunDeleteMock,
165+ eventBuildDeletedMock,
166+ buildFindManyMock,
167+ } ) ;
168+
169+ await service . remove ( build . id ) ;
170+
171+ expect ( buildFindUniqueMock ) . toHaveBeenCalledWith ( {
172+ where : { id : build . id } ,
173+ include : {
174+ testRuns : true ,
175+ } ,
176+ } ) ;
177+ expect ( testRunDeleteMock ) . toHaveBeenCalledWith ( build . testRuns [ 0 ] . id ) ;
178+ expect ( eventBuildDeletedMock ) . toHaveBeenCalledWith ( new BuildDto ( build ) ) ;
179+ expect ( buildDeleteMock ) . toHaveBeenCalledWith ( {
180+ where : { id : build . id } ,
181+ } ) ;
166182 } ) ;
167183
168- await service . remove ( build . id ) ;
184+ it ( 'should handle undefined ID gracefully' , async ( ) => {
185+ const buildFindUniqueMock = jest . fn ( ) ;
186+ const loggerWarnMock = jest . fn ( ) ;
187+ service = await initService ( { buildFindUniqueMock } ) ;
188+ ( service as any ) . logger = { warn : loggerWarnMock } ; // Mock the logger
169189
170- expect ( buildFindUniqueMock ) . toHaveBeenCalledWith ( {
171- where : { id : build . id } ,
172- include : {
173- testRuns : true ,
174- } ,
190+ await service . remove ( undefined ) ;
191+
192+ expect ( loggerWarnMock ) . toHaveBeenCalledWith ( 'Attempted to remove build with undefined ID.' ) ;
193+ expect ( buildFindUniqueMock ) . not . toHaveBeenCalled ( ) ;
194+ } ) ;
195+ } ) ;
196+
197+ describe ( 'deleteOldBuilds' , ( ) => {
198+ const projectId = 'someProjectId' ;
199+ const build1 = { ...build , id : 'build1' , createdAt : new Date ( Date . now ( ) - 10000 ) } ;
200+ const build2 = { ...build , id : 'build2' , createdAt : new Date ( Date . now ( ) - 5000 ) } ;
201+ const build3 = { ...build , id : 'build3' , createdAt : new Date ( Date . now ( ) ) } ;
202+
203+ it ( 'should delete old builds and keep the specified number' , async ( ) => {
204+ const buildFindManyMock = jest . fn ( ) . mockResolvedValueOnce ( [ build1 , build2 , build3 ] ) ;
205+ const buildCountMock = jest . fn ( ) . mockResolvedValueOnce ( 3 ) ;
206+ const removeMock = jest . fn ( ) . mockResolvedValue ( undefined ) ;
207+ const loggerDebugMock = jest . fn ( ) ;
208+
209+ service = await initService ( { buildFindManyMock, buildCountMock } ) ;
210+ service . remove = removeMock ;
211+ ( service as any ) . logger = { debug : loggerDebugMock } ;
212+
213+ await service . deleteOldBuilds ( projectId , 2 ) ;
214+
215+ expect ( buildFindManyMock ) . toHaveBeenCalledWith ( {
216+ where : { projectId } ,
217+ take : undefined ,
218+ skip : 1 ,
219+ orderBy : { createdAt : 'desc' } ,
220+ } ) ;
221+ expect ( removeMock ) . toHaveBeenCalledTimes ( 2 ) ;
222+ expect ( removeMock ) . toHaveBeenCalledWith ( build1 . id ) ;
223+ expect ( removeMock ) . toHaveBeenCalledWith ( build2 . id ) ;
224+ expect ( loggerDebugMock ) . toHaveBeenCalledWith ( `Starting to delete old builds for project ${ projectId } , keeping 2 builds.` ) ;
225+ expect ( loggerDebugMock ) . toHaveBeenCalledWith ( `Finished deleting old builds for project ${ projectId } .` ) ;
226+ } ) ;
227+
228+ it ( 'should handle concurrent calls for the same project by returning the existing promise' , async ( ) => {
229+ const buildFindManyMock = jest . fn ( ) . mockResolvedValueOnce ( [ build1 , build2 , build3 ] ) ;
230+ const buildCountMock = jest . fn ( ) . mockResolvedValueOnce ( 3 ) ;
231+ const removeMock = jest . fn ( ) . mockResolvedValue ( undefined ) ;
232+ const loggerDebugMock = jest . fn ( ) ;
233+
234+ service = await initService ( { buildFindManyMock, buildCountMock } ) ;
235+ service . remove = removeMock ;
236+ ( service as any ) . logger = { debug : loggerDebugMock } ;
237+
238+ const promise1 = service . deleteOldBuilds ( projectId , 2 ) ;
239+ const promise2 = service . deleteOldBuilds ( projectId , 2 ) ;
240+
241+ expect ( promise1 ) . toBe ( promise2 ) ; // Should return the same promise
242+ expect ( loggerDebugMock ) . toHaveBeenCalledWith ( `Deletion for project ${ projectId } is already in progress. Returning existing promise.` ) ;
243+
244+ await promise1 ; // Wait for the deletion to complete
245+
246+ expect ( buildFindManyMock ) . toHaveBeenCalledTimes ( 1 ) ; // Only called once
247+ expect ( removeMock ) . toHaveBeenCalledTimes ( 2 ) ;
248+ expect ( loggerDebugMock ) . toHaveBeenCalledWith ( `Finished deleting old builds for project ${ projectId } .` ) ;
249+
250+ // After completion, a new call should initiate a new deletion
251+ const promise3 = service . deleteOldBuilds ( projectId , 2 ) ;
252+ expect ( promise3 ) . not . toBe ( promise1 ) ;
253+ await promise3 ;
254+ expect ( buildFindManyMock ) . toHaveBeenCalledTimes ( 2 ) ;
255+ } ) ;
256+
257+ it ( 'should remove the promise from the map after completion (success)' , async ( ) => {
258+ const buildFindManyMock = jest . fn ( ) . mockResolvedValueOnce ( [ build1 ] ) ;
259+ const buildCountMock = jest . fn ( ) . mockResolvedValueOnce ( 1 ) ;
260+ const removeMock = jest . fn ( ) . mockResolvedValue ( undefined ) ;
261+
262+ service = await initService ( { buildFindManyMock, buildCountMock } ) ;
263+ service . remove = removeMock ;
264+
265+ const projectId = 'testProject' ;
266+ await service . deleteOldBuilds ( projectId , 0 ) ;
267+
268+ // Check if the promise is removed from the internal map
269+ expect ( ( service as any ) . ongoingDeletions . has ( projectId ) ) . toBe ( false ) ;
175270 } ) ;
176- expect ( testRunDeleteMock ) . toHaveBeenCalledWith ( build . testRuns [ 0 ] . id ) ;
177- expect ( eventBuildDeletedMock ) . toHaveBeenCalledWith ( new BuildDto ( build ) ) ;
178- expect ( buildDeleteMock ) . toHaveBeenCalledWith ( {
179- where : { id : build . id } ,
271+
272+ it ( 'should remove the promise from the map after completion (failure)' , async ( ) => {
273+ const buildFindManyMock = jest . fn ( ) . mockRejectedValueOnce ( new Error ( 'DB error' ) ) ;
274+ const buildCountMock = jest . fn ( ) . mockResolvedValueOnce ( 1 ) ;
275+ const removeMock = jest . fn ( ) . mockResolvedValue ( undefined ) ;
276+
277+ service = await initService ( { buildFindManyMock, buildCountMock } ) ;
278+ service . remove = removeMock ;
279+
280+ const projectId = 'testProject' ;
281+ await expect ( service . deleteOldBuilds ( projectId , 0 ) ) . rejects . toThrow ( 'DB error' ) ;
282+
283+ // Check if the promise is removed from the internal map even after an error
284+ expect ( ( service as any ) . ongoingDeletions . has ( projectId ) ) . toBe ( false ) ;
180285 } ) ;
181286 } ) ;
182287
0 commit comments