@@ -241,6 +241,63 @@ func TestLifecycle_Chart_AddSaveLoadExtract(t *testing.T) {
241241 }
242242}
243243
244+ // TestLifecycle_DigestOnlyImage_AddSaveLoad exercises the full save/load round-trip
245+ // for an image added by digest only (no tag). Before fix #642, the image disappeared
246+ // from index.json after LoadCmd because CopyAll appended a double-@ digest.
247+ func TestLifecycle_DigestOnlyImage_AddSaveLoad (t * testing.T ) {
248+ ctx := newTestContext (t )
249+
250+ // Step 1: seed a tagged image so we can get its digest
251+ srcHost , srcOpts := newLocalhostRegistry (t )
252+ srcImg := seedImage (t , srcHost , "lifecycle/digestonly" , "v1" , srcOpts ... )
253+ hash , err := srcImg .Digest ()
254+ if err != nil {
255+ t .Fatalf ("srcImg.Digest: %v" , err )
256+ }
257+
258+ // Step 2: add BY DIGEST (not tag) into store A
259+ storeA := newTestStore (t )
260+ rso := defaultRootOpts (storeA .Root )
261+ ro := defaultCliOpts ()
262+ digestRef := srcHost + "/lifecycle/digestonly@" + hash .String ()
263+ if err := storeImage (ctx , storeA , v1.Image {Name : digestRef }, "" , false , rso , ro , "" ); err != nil {
264+ t .Fatalf ("storeImage by digest: %v" , err )
265+ }
266+ // The image should be findable by its digest hex
267+ assertArtifactInStore (t , storeA , hash .Hex )
268+
269+ // Flush index.json for SaveCmd
270+ if err := storeA .SaveIndex (); err != nil {
271+ t .Fatalf ("SaveIndex: %v" , err )
272+ }
273+
274+ // Step 3: SaveCmd -> archive
275+ archivePath := filepath .Join (t .TempDir (), "lifecycle-digestonly.tar.zst" )
276+ saveOpts := newSaveOpts (storeA .Root , archivePath )
277+ if err := SaveCmd (ctx , saveOpts , defaultRootOpts (storeA .Root ), defaultCliOpts ()); err != nil {
278+ t .Fatalf ("SaveCmd: %v" , err )
279+ }
280+
281+ // Step 4: LoadCmd -> fresh store B
282+ storeBDir := t .TempDir ()
283+ loadOpts := & flags.LoadOpts {
284+ StoreRootOpts : defaultRootOpts (storeBDir ),
285+ FileName : []string {archivePath },
286+ }
287+ if err := LoadCmd (ctx , loadOpts , defaultRootOpts (storeBDir ), defaultCliOpts ()); err != nil {
288+ t .Fatalf ("LoadCmd: %v" , err )
289+ }
290+
291+ storeB , err := store .NewLayout (storeBDir )
292+ if err != nil {
293+ t .Fatalf ("store.NewLayout(storeB): %v" , err )
294+ }
295+
296+ // Regression assertion: the digest-only image must survive the save/load round-trip.
297+ // Before fix 1, the image disappears from the loaded store's index.json.
298+ assertArtifactInStore (t , storeB , hash .Hex )
299+ }
300+
244301// TestLifecycle_Remove_ThenSave verifies that removing one artifact from a store
245302// with two file artifacts, then saving/loading, results in only the retained
246303// artifact being present.
0 commit comments