package inventory import ( "context" "strings" "testing" ) // A build result was answered to whoever asked and kept nowhere, so "when did this last build", // "why did it fail" and "which machine built what is running" had no answer at all. func aBuild(id, module, failed string) Build { b := Build{ ID: id, Repository: "https://forge.invalid/" + strings.TrimSuffix(module, "?") + ".git", Module: module, On: "a-build-machine", Failed: failed, } if failed == "" { b.Commit = "c0ffee" + id b.Made = []Artifact{{Name: "config", Kind: "archive", Reference: "…/blobs/sha256:…"}} } return b } func TestAFailedBuildIsARowLikeAnyOther(t *testing.T) { // One that leaves no trace is indistinguishable from a build nobody asked for, and the // difference is the whole of whether somebody should be looking at something. inv := fresh(t) ctx := context.Background() if err := inv.RecordBuild(ctx, aBuild("1", "", "cannot clone: no such repository")); err != nil { t.Fatal(err) } got, err := inv.Builds(ctx, "", 10) if err != nil { t.Fatal(err) } if len(got) != 1 { t.Fatalf("got %d builds", len(got)) } if got[0].Worked() { t.Fatal("a failure was recorded as a success") } if !strings.Contains(got[0].Failed, "no such repository") { t.Fatalf("the builder's own words were not kept: %q", got[0].Failed) } // And it kept what was asked for, which is the only thing a person can go and look at when // the build never learned what it was building. if got[0].Module != "" || got[0].Repository == "" { t.Fatalf("got %+v", got[0]) } } func TestOneResultRecordedTwiceIsOneBuild(t *testing.T) { // A result can arrive twice: as the answer to whoever asked, and on the exchange when nobody // was. Two rows would show one build as two, and which is real is not answerable afterwards. inv := fresh(t) ctx := context.Background() for i := 0; i < 2; i++ { if err := inv.RecordBuild(ctx, aBuild("same", "shell", "")); err != nil { t.Fatal(err) } } got, err := inv.Builds(ctx, "", 10) if err != nil { t.Fatal(err) } if len(got) != 1 { t.Fatalf("one build was recorded %d times", len(got)) } } func TestBuildsComeBackNewestFirstAndCanBeAskedPerModule(t *testing.T) { inv := fresh(t) ctx := context.Background() for _, b := range []Build{ aBuild("1", "shell", ""), aBuild("2", "meshboard", ""), aBuild("3", "shell", "the tests failed"), } { if err := inv.RecordBuild(ctx, b); err != nil { t.Fatal(err) } } all, err := inv.Builds(ctx, "", 10) if err != nil { t.Fatal(err) } if len(all) != 3 || all[0].ID != "3" { t.Fatalf("newest is not first: %v", ids(all)) } // Per module, because "what has happened to this" is asked when deciding whether to trust it. shell, err := inv.Builds(ctx, "shell", 10) if err != nil { t.Fatal(err) } if len(shell) != 2 { t.Fatalf("shell has %d builds: %v", len(shell), ids(shell)) } for _, b := range shell { if b.Module != "shell" { t.Fatalf("asked for shell and got %q", b.Module) } } } func TestWhatWasPublishedIsKeptWithTheBuild(t *testing.T) { // So a digest can be traced back to the build that made it, without keeping the manifest a // second time in a place that can disagree with the first. inv := fresh(t) ctx := context.Background() if err := inv.RecordBuild(ctx, aBuild("1", "shell", "")); err != nil { t.Fatal(err) } got, _ := inv.Builds(ctx, "", 10) if len(got[0].Made) != 1 || got[0].Made[0].Kind != "archive" { t.Fatalf("what was published was not kept: %+v", got[0].Made) } } func TestAskingForMoreThanThereIsIsNotAnError(t *testing.T) { inv := fresh(t) got, err := inv.Builds(context.Background(), "", 100) if err != nil { t.Fatal(err) } if len(got) != 0 { t.Fatalf("got %d", len(got)) } } func ids(builds []Build) []string { var out []string for _, b := range builds { out = append(out, b.ID) } return out } // What a build read besides its module's own repository comes back for the newest build of each // module, and only for builds that worked. Nothing recorded is absent rather than empty, which is how // a build made before the mesh kept this is told from one that read nothing (novox/hq 04-ISSUES/131). func TestWhatABuildReadComesBackForTheNewestBuildOfEachModule(t *testing.T) { inv := fresh(t) ctx := context.Background() older := aBuild("older", "builder", "") older.Read = []ReadRepository{{Repository: "novox/mesh-controller", Ref: "release"}} newer := aBuild("newer", "builder", "") newer.Read = []ReadRepository{{Repository: "novox/mesh-controller", Ref: "main"}} plain := aBuild("plain", "gitea", "") failed := aBuild("failed", "route-proxy", "cannot clone") failed.Read = []ReadRepository{{Repository: "novox/mesh-controller", Ref: "main"}} for _, b := range []Build{older, newer, plain, failed} { if err := inv.RecordBuild(ctx, b); err != nil { t.Fatal(err) } } read, err := inv.ReadRepositories(ctx) if err != nil { t.Fatal(err) } if len(read["builder"]) != 1 || read["builder"][0].Ref != "main" { t.Fatalf("the newest build's reading is %+v", read["builder"]) } if _, has := read["gitea"]; has { t.Fatalf("a build that read nothing but its own repository reads as %+v", read["gitea"]) } if _, has := read["route-proxy"]; has { t.Fatal("a failed build's reading was kept as what that module reads") } }