@@ -151,32 +151,128 @@ 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 ( ) ;
175194 } ) ;
176- expect ( testRunDeleteMock ) . toHaveBeenCalledWith ( build . testRuns [ 0 ] . id ) ;
177- expect ( eventBuildDeletedMock ) . toHaveBeenCalledWith ( new BuildDto ( build ) ) ;
178- expect ( buildDeleteMock ) . toHaveBeenCalledWith ( {
179- where : { id : build . id } ,
195+ } ) ;
196+
197+ describe ( 'deleteOldBuilds' , ( ) => {
198+ const projectId = 'someProjectId' ;
199+ const build1 = { ...buildDto , id : 'build1' , createdAt : new Date ( Date . now ( ) - 10000 ) } ;
200+ const build2 = { ...buildDto , id : 'build2' , createdAt : new Date ( Date . now ( ) - 5000 ) } ;
201+
202+ it ( 'should delete old builds and keep the specified number' , async ( ) => {
203+ const buildFindManyMock = jest . fn ( ) . mockResolvedValueOnce ( [ build1 , build2 ] ) ;
204+ const buildCountMock = jest . fn ( ) . mockResolvedValueOnce ( 2 ) ;
205+ const buildFindUniqueMock = jest . fn ( ) . mockResolvedValueOnce ( build1 ) . mockResolvedValueOnce ( build2 ) ;
206+ mocked ( BuildDto )
207+ . mockReturnValueOnce ( build1 as MockedObject < BuildDto > )
208+ . mockReturnValueOnce ( build2 as MockedObject < BuildDto > ) ;
209+ const testRunFindManyMock = jest . fn ( ) . mockResolvedValue ( [ ] ) ;
210+ const removeMock = jest . fn ( ) . mockResolvedValue ( undefined ) ;
211+ const loggerDebugMock = jest . fn ( ) ;
212+
213+ service = await initService ( { buildFindManyMock, buildCountMock, buildFindUniqueMock, testRunFindManyMock } ) ;
214+ service . remove = removeMock ;
215+ ( service as any ) . logger = { debug : loggerDebugMock } ;
216+
217+ await service . deleteOldBuilds ( projectId , 2 ) ;
218+
219+ expect ( buildFindManyMock ) . toHaveBeenCalledWith ( {
220+ where : { projectId } ,
221+ take : undefined ,
222+ skip : 1 ,
223+ orderBy : { createdAt : 'desc' } ,
224+ } ) ;
225+ expect ( removeMock ) . toHaveBeenCalledTimes ( 2 ) ;
226+ expect ( removeMock ) . toHaveBeenCalledWith ( build1 . id ) ;
227+ expect ( removeMock ) . toHaveBeenCalledWith ( build2 . id ) ;
228+ expect ( loggerDebugMock ) . toHaveBeenCalledWith (
229+ `Starting to delete old builds for project ${ projectId } , keeping 2 builds.`
230+ ) ;
231+ expect ( loggerDebugMock ) . toHaveBeenCalledWith ( `Finished deleting old builds for project ${ projectId } .` ) ;
232+ } ) ;
233+
234+ it ( 'should handle concurrent calls for the same project by returning the existing promise' , async ( ) => {
235+ const buildFindManyMock = jest . fn ( ) . mockResolvedValueOnce ( [ build1 , build2 ] ) ;
236+ const buildCountMock = jest . fn ( ) . mockResolvedValueOnce ( 2 ) ;
237+ const removeMock = jest . fn ( ) . mockResolvedValue ( undefined ) ;
238+ const testRunFindManyMock = jest . fn ( ) . mockResolvedValue ( [ ] ) ;
239+ const buildFindUniqueMock = jest . fn ( ) . mockResolvedValueOnce ( build1 ) . mockResolvedValueOnce ( build2 ) ;
240+ mocked ( BuildDto )
241+ . mockReturnValueOnce ( build1 as MockedObject < BuildDto > )
242+ . mockReturnValueOnce ( build2 as MockedObject < BuildDto > ) ;
243+ const loggerDebugMock = jest . fn ( ) ;
244+
245+ service = await initService ( { buildFindManyMock, buildCountMock, testRunFindManyMock, buildFindUniqueMock } ) ;
246+ service . remove = removeMock ;
247+ ( service as any ) . logger = { debug : loggerDebugMock } ;
248+
249+ const promise1 = service . deleteOldBuilds ( projectId , 2 ) ;
250+ const promise2 = service . deleteOldBuilds ( projectId , 2 ) ;
251+
252+ expect ( promise1 ) . toStrictEqual ( promise2 ) ;
253+ expect ( loggerDebugMock ) . toHaveBeenCalledWith (
254+ `Deletion for project ${ projectId } is already in progress. Returning existing promise.`
255+ ) ;
256+
257+ await promise1 ; // Wait for the deletion to complete
258+
259+ expect ( buildFindManyMock ) . toHaveBeenCalledTimes ( 1 ) ; // Only called once
260+ expect ( removeMock ) . toHaveBeenCalledTimes ( 2 ) ;
261+ expect ( loggerDebugMock ) . toHaveBeenCalledWith ( `Finished deleting old builds for project ${ projectId } .` ) ;
262+ } ) ;
263+
264+ it ( 'should remove the promise from the map after completion (success)' , async ( ) => {
265+ const buildFindManyMock = jest . fn ( ) . mockResolvedValueOnce ( [ build1 ] ) ;
266+ const buildCountMock = jest . fn ( ) . mockResolvedValueOnce ( 1 ) ;
267+ const removeMock = jest . fn ( ) . mockResolvedValue ( undefined ) ;
268+
269+ service = await initService ( { buildFindManyMock, buildCountMock } ) ;
270+ service . remove = removeMock ;
271+
272+ const projectId = 'testProject' ;
273+ await service . deleteOldBuilds ( projectId , 0 ) ;
274+
275+ expect ( service [ 'ongoingDeletions' ] . has ( projectId ) ) . toBe ( false ) ;
180276 } ) ;
181277 } ) ;
182278
0 commit comments