@@ -34,6 +34,7 @@ import (
3434 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3535 "k8s.io/apimachinery/pkg/runtime"
3636 "k8s.io/apimachinery/pkg/types"
37+ "k8s.io/apimachinery/pkg/watch"
3738)
3839
3940func TestCachedRepoRefresh (t * testing.T ) {
@@ -250,3 +251,111 @@ func TestHandleRunOnceAt(t *testing.T) {
250251 assert .NotNil (t , status , "Expected repository status to be updated" )
251252 assert .Contains (t , []string {"Ready" , "Error" , "Reconciling" }, status .Conditions [0 ].Reason )
252253}
254+
255+ func TestCRDeleteLatestRevision (t * testing.T ) {
256+ mockRepo := mockrepo .NewMockRepository (t )
257+ mockMeta := mockmeta .NewMockMetadataStore (t )
258+ mockNotifier := mockcachetypes .NewMockRepoPRChangeNotifier (t )
259+
260+ repoSpec := configapi.Repository {
261+ ObjectMeta : metav1.ObjectMeta {
262+ Name : repoName ,
263+ Namespace : namespace ,
264+ },
265+ }
266+
267+ repoKey := repository.RepositoryKey {
268+ Namespace : namespace ,
269+ Name : repoName ,
270+ }
271+
272+ // Create two package revisions for the same package
273+ prKey1 := repository.PackageRevisionKey {
274+ PkgKey : repository.PackageKey {
275+ RepoKey : repoKey ,
276+ Path : "" ,
277+ Package : "test-package" ,
278+ },
279+ WorkspaceName : "v1" ,
280+ Revision : 1 ,
281+ }
282+
283+ prKey2 := repository.PackageRevisionKey {
284+ PkgKey : repository.PackageKey {
285+ RepoKey : repoKey ,
286+ Path : "" ,
287+ Package : "test-package" ,
288+ },
289+ WorkspaceName : "v2" ,
290+ Revision : 2 ,
291+ }
292+
293+ fpr1 := & fake.FakePackageRevision {
294+ PrKey : prKey1 ,
295+ PackageLifecycle : porchapi .PackageRevisionLifecyclePublished ,
296+ }
297+
298+ fpr2 := & fake.FakePackageRevision {
299+ PrKey : prKey2 ,
300+ PackageLifecycle : porchapi .PackageRevisionLifecyclePublished ,
301+ }
302+
303+ // Setup mocks
304+ mockRepo .On ("Key" ).Return (repoKey ).Maybe ()
305+ mockRepo .EXPECT ().DeletePackageRevision (mock .Anything , mock .Anything ).Return (nil ).Maybe ()
306+ mockMeta .EXPECT ().Delete (mock .Anything , mock .Anything , mock .Anything ).Return (metav1.ObjectMeta {}, nil ).Maybe ()
307+
308+ // Expect exactly 2 notifications: one for deletion, one for async notification
309+ mockNotifier .EXPECT ().NotifyPackageRevisionChange (watch .Deleted , mock .Anything ).Return (1 ).Once ()
310+ mockNotifier .EXPECT ().NotifyPackageRevisionChange (watch .Modified , mock .Anything ).Return (1 ).Once ()
311+
312+ // Create repository manually to avoid sync manager
313+ cr := & cachedRepository {
314+ key : repoKey ,
315+ repoSpec : & repoSpec ,
316+ repo : mockRepo ,
317+ metadataStore : mockMeta ,
318+ repoPRChangeNotifier : mockNotifier ,
319+ cachedPackages : make (map [repository.PackageKey ]* cachedPackage ), // Initialize to enable deletion
320+ }
321+
322+ // Initialize cache with two revisions
323+ cr .cachedPackageRevisions = make (map [repository.PackageRevisionKey ]* cachedPackageRevision )
324+ cr .cachedPackageRevisions [prKey1 ] = & cachedPackageRevision {
325+ PackageRevision : fpr1 ,
326+ metadataStore : mockMeta ,
327+ isLatestRevision : false ,
328+ }
329+ cr .cachedPackageRevisions [prKey2 ] = & cachedPackageRevision {
330+ PackageRevision : fpr2 ,
331+ metadataStore : mockMeta ,
332+ isLatestRevision : false ,
333+ }
334+
335+ // Identify latest revisions (revision 2 should be latest)
336+ identifyLatestRevisions (context .TODO (), cr .cachedPackageRevisions )
337+
338+ // Verify revision 2 is marked as latest before deletion
339+ assert .True (t , cr .cachedPackageRevisions [prKey2 ].IsLatestRevision ())
340+ assert .False (t , cr .cachedPackageRevisions [prKey1 ].IsLatestRevision ())
341+
342+ // Delete the latest revision (revision 2)
343+ err := cr .DeletePackageRevision (context .TODO (), cr .cachedPackageRevisions [prKey2 ])
344+ assert .NoError (t , err )
345+
346+ // Give time for async notification to complete
347+ time .Sleep (100 * time .Millisecond )
348+
349+ // Now delete pkg1 - should only send 1 notification (deletion) since no more revisions remain
350+ mockNotifier .EXPECT ().NotifyPackageRevisionChange (watch .Deleted , mock .Anything ).Return (1 ).Once ()
351+ // No async notification expected since no revisions remain
352+
353+ err = cr .DeletePackageRevision (context .TODO (), cr .cachedPackageRevisions [prKey1 ])
354+ assert .NoError (t , err )
355+
356+ // Give time for any potential async notification
357+ time .Sleep (100 * time .Millisecond )
358+
359+ // Verify all expected mock calls were made
360+ mockNotifier .AssertExpectations (t )
361+ }
0 commit comments