package inventory import ( "context" "fmt" "os" "slices" "strings" "testing" "github.com/novox/mesh-controller/internal/catalogue" ) // A base a build copies into the store is recorded where it is copied, and kept while a kept build stood // on it (novox/hq ADR 0257). func mirrorRef(repository string, n int) string { return fmt.Sprintf("%s%s@sha256:%064x", catalogue.ArtifactStoreScheme, repository, n) } // builtOn records a successful build of a module that made one image and stood on these bases. func builtOn(t *testing.T, inv *Inventory, id, module string, n int, mirrored []string, against ...string) { t.Helper() b := aBuild(id, module, "") b.Made = []Artifact{{Name: "app", Kind: "image", Reference: ref(module, "app", n)}} b.Against = against b.Mirrored = mirrored if err := inv.RecordBuild(context.Background(), b); err != nil { t.Fatal(err) } } func stateOf(t *testing.T, inv *Inventory, reference string) ArtifactState { t.Helper() states, err := inv.Artifacts(context.Background()) if err != nil { t.Fatal(err) } for _, s := range states { if s.Reference == reference { return s } } return ArtifactState{Reference: reference, State: "unrecorded"} } func TestACopyAFailedBuildMadeIsRecordedAndNothingKeepsIt(t *testing.T) { inv := fresh(t) golang := mirrorRef("upstream/docker.io/library/golang", 1) failed := aBuild("f1", "", "the recipe fetched an undeclared image") failed.Mirrored = []string{golang} if err := inv.RecordBuild(context.Background(), failed); err != nil { t.Fatal(err) } if s := stateOf(t, inv, golang); s.State != ArtifactEligible { t.Fatalf("a copy only a failed build made reads %+v; want eligible", s) } } func TestACopyIsKeptByTheKeptBuildsThatStoodOnItAndByNothingElse(t *testing.T) { inv := fresh(t) ctx := context.Background() holding(t, inv, "proxy") old := mirrorRef("upstream/docker.io/library/golang", 1) current := mirrorRef("upstream/docker.io/library/golang", 2) // The first build stood on the old copy; six more on the current one. The old copy falls out of // the five kept builds; the current one is stood on by all five. builtOn(t, inv, "b01", "proxy", 1, []string{old}, old) for i := 2; i <= 7; i++ { builtOn(t, inv, fmt.Sprintf("b%02d", i), "proxy", i, []string{current}, current) } if s := stateOf(t, inv, old); s.State != ArtifactEligible { t.Fatalf("a copy no kept build stood on reads %+v", s) } if s := stateOf(t, inv, current); s.State != ArtifactKept || !slices.Equal(s.Why, []string{KeptStoodOn}) { t.Fatalf("a copy the kept builds stood on reads %+v", s) } // A definition naming the image upstream, by the digest every copy shares, keeps no copy. m := catalogue.Manifest{Module: "proxy", Version: "2", Build: &catalogue.Build{On: []catalogue.BuildsOn{{Arg: "GO_BASE", Image: "golang@" + strings.SplitN(old, "@", 2)[1]}}}} if err := inv.RegisterModule(ctx, m, Source{Repository: "https://forge.invalid/proxy.git"}); err != nil { t.Fatal(err) } if s := stateOf(t, inv, old); s.State != ArtifactEligible { t.Fatalf("a definition naming the upstream image kept the old copy: %+v", s) } go_, err := inv.ToCollect(ctx) if err != nil { t.Fatal(err) } if !slices.Contains(go_, old) || slices.Contains(go_, current) { t.Fatalf("offered %v to collect", go_) } // Once let go of, a copy stays let go of, even should a kept build still name it. if err := inv.MarkCollected(ctx, []string{current}); err != nil { t.Fatal(err) } if s := stateOf(t, inv, current); s.State != ArtifactCollected { t.Fatalf("a collected copy reads %+v", s) } } func TestAModulesBaseIsNotKeptAsACopy(t *testing.T) { // A base that is another module's artifact is kept by that module's own builds, as before: the // stood-on reason is a copy's alone. inv := fresh(t) holding(t, inv, "app") base := ref("go-base", "build", 1) b := aBuild("g1", "go-base", "") b.Made = []Artifact{{Name: "build", Kind: "image", Reference: base}} if err := inv.RecordBuild(context.Background(), b); err != nil { t.Fatal(err) } builtOn(t, inv, "a1", "app", 1, nil, base) if s := stateOf(t, inv, base); s.State != ArtifactEligible { t.Fatalf("a forgotten module's artifact read %+v because a build stood on it", s) } } func TestAPersonRecordsACopyAndTheFirstRecordStands(t *testing.T) { inv := fresh(t) ctx := context.Background() copyRef := mirrorRef("route-proxy/on-go_base", 3) if err := inv.RecordMirrored(ctx, "", "copied before copies were recorded", []string{copyRef}); err != nil { t.Fatal(err) } if err := inv.RecordMirrored(ctx, "b9", "", []string{copyRef}); err != nil { t.Fatal(err) } known, err := inv.Mirrored(ctx) if err != nil || !known[copyRef] { t.Fatalf("recorded copies %v %v", known, err) } var why string if err := inv.store.Pool().QueryRow(ctx, `select why from artifact_mirrored where reference = $1`, copyRef).Scan(&why); err != nil { t.Fatal(err) } if why != "copied before copies were recorded" { t.Fatalf("the first record did not stand: %q", why) } if s := stateOf(t, inv, copyRef); s.State != ArtifactEligible { t.Fatalf("a copy a person recorded reads %+v", s) } } // The migration records the copies the build records already name: only a module's own on- // repository, and only once, by the earliest build naming it. func TestTheCopiesTheRecordsAlreadyNameAreRecorded(t *testing.T) { inv := fresh(t) ctx := context.Background() copyRef := mirrorRef("route-proxy/on-go_base", 1) notACopy := ref("go-base", "build", 2) builtOn(t, inv, "b1", "route-proxy", 1, nil, copyRef, notACopy) builtOn(t, inv, "b2", "route-proxy", 2, nil, copyRef) // The rows a mesh had before the table: written by builds that did not say what they copied. if _, err := inv.store.Pool().Exec(ctx, `truncate artifact_mirrored`); err != nil { t.Fatal(err) } sql, err := os.ReadFile("migrations/0081-a-mirrored-base-is-recorded.sql") if err != nil { t.Fatal(err) } _, backfill, _ := strings.Cut(string(sql), "insert into artifact_mirrored") if _, err := inv.store.Pool().Exec(ctx, "insert into artifact_mirrored"+backfill); err != nil { t.Fatal(err) } var reference, build string var n int if err := inv.store.Pool().QueryRow(ctx, `select count(*) from artifact_mirrored`).Scan(&n); err != nil || n != 1 { t.Fatalf("%d copies recorded (%v); want the one", n, err) } if err := inv.store.Pool().QueryRow(ctx, `select reference, build from artifact_mirrored`).Scan(&reference, &build); err != nil { t.Fatal(err) } if reference != copyRef || build != "b1" { t.Fatalf("recorded %s by %s", reference, build) } } // A copy let go of and copied again by a later build is held again: its record is back, and the sweep // decides about it as before. func TestACopyLetGoOfAndCopiedAgainIsHeldAgain(t *testing.T) { inv := fresh(t) ctx := context.Background() holding(t, inv, "proxy") golang := mirrorRef("upstream/docker.io/library/golang", 1) builtOn(t, inv, "b01", "proxy", 1, []string{golang}, golang) if err := inv.MarkCollected(ctx, []string{golang}); err != nil { t.Fatal(err) } if s := stateOf(t, inv, golang); s.State != ArtifactCollected { t.Fatalf("a let-go copy reads %+v", s) } builtOn(t, inv, "b02", "proxy", 2, []string{golang}, golang) if s := stateOf(t, inv, golang); s.State != ArtifactKept || !slices.Equal(s.Why, []string{KeptStoodOn}) { t.Fatalf("a copy copied again reads %+v; want kept again", s) } // A person's record never undoes a collection: only a build that copied it says it is there. if err := inv.MarkCollected(ctx, []string{golang}); err != nil { t.Fatal(err) } if err := inv.RecordMirrored(ctx, "", "again", []string{golang}); err != nil { t.Fatal(err) } if s := stateOf(t, inv, golang); s.State != ArtifactCollected { t.Fatalf("a person's record brought a collected copy back: %+v", s) } }