package inventory import ( "context" "encoding/json" "fmt" "reflect" "testing" "time" "github.com/nats-io/nats.go" "github.com/novox/mesh-controller/internal/chain" ) // **A build keeps its reuse key and what it published, and comes back whole from the bucket** (novox/hq ADR 0408 §1, // issue 1090): an artifact by the digest of what it made, a package release by name and version. func TestABuildKeepsItsReuseKeyAndWhatItPublishedWhenKeptOnTheBus(t *testing.T) { at := time.Date(2026, 10, 18, 3, 0, 0, 0, time.UTC) b := Build{ID: "build-1", Repository: "novox/notes", Ref: "main", Module: "notes", Commit: "c1", On: "ace", Path: "modules/notes", Manifest: []byte(`{"module":"notes"}`), Against: []string{"artifact-store://base@sha256:aa"}, Read: []ReadRepository{{Repository: "novox/other", Ref: "main"}}, Sources: []BuildSource{{Paths: []string{"modules/notes/module.json"}}}, SourceFingerprint: "fp", Asked: at, At: at.Add(time.Minute), ReuseKey: "rk-1", Made: []Artifact{{Name: "notes", Kind: "image", Reference: "artifact-store://notes@sha256:bb"}, {Name: "sdk", Kind: "package", Reference: "mesh-sdk@1.4.0"}}} kept := toChainBuild(b) if kept.ReuseKey != "rk-1" { t.Errorf("the reuse key is %q", kept.ReuseKey) } if kept.Published == nil || kept.Published.Artifact != "sha256:bb" || kept.Published.Package == nil || kept.Published.Package.Name != "mesh-sdk" || kept.Published.Package.Version != "1.4.0" { t.Errorf("it published %+v", kept.Published) } // Through the bucket's bytes and back: nothing is lost. raw, err := json.Marshal(kept) if err != nil { t.Fatal(err) } var back chain.Build if err := json.Unmarshal(raw, &back); err != nil { t.Fatal(err) } got := fromChainBuild(back) b.Published = kept.Published if !reflect.DeepEqual(got, b) { t.Fatalf("the build came back as\n %+v\nnot\n %+v", got, b) } } func TestWhatABuildPublishedIsReadFromWhatItMade(t *testing.T) { for name, tc := range map[string]struct { made []Artifact want *chain.Published }{ "nothing": {nil, nil}, "an archive with no digest": {[]Artifact{{Name: "config", Kind: "archive", Reference: "…/blobs/sha256:…"}}, nil}, "an image": {[]Artifact{{Name: "a", Kind: "image", Reference: "artifact-store://a@sha256:11"}}, &chain.Published{Artifact: "sha256:11"}}, "the first digest stands": {[]Artifact{{Kind: "image", Reference: "x@sha256:11"}, {Kind: "archive", Reference: "y@sha256:22"}}, &chain.Published{Artifact: "sha256:11"}}, "a package": {[]Artifact{{Name: "sdk", Kind: "package", Reference: "mesh-sdk@2.0.1"}}, &chain.Published{Package: &chain.Package{Name: "mesh-sdk", Version: "2.0.1"}}}, "a package with no version": {[]Artifact{{Name: "sdk", Kind: "package", Reference: "mesh-sdk"}}, nil}, } { if got := publishedOf(tc.made); !reflect.DeepEqual(got, tc.want) { t.Errorf("%s: published %+v, not %+v", name, got, tc.want) } } } // **The same content hashes alike, however the record spells it** (issue 280's fingerprints, now over the bucket's bytes // and not the store's jsonb text): key order and white space of a manifest change nothing. func TestTheSameBuildHashesAlikeWhateverTheSpellingOfItsRecord(t *testing.T) { a := Build{Manifest: []byte(`{"module": "notes", "version": "1"}`), Made: []Artifact{{Name: "n", Kind: "image", Reference: "r"}}} b := Build{Manifest: []byte("{\"version\":\"1\",\n \"module\":\"notes\"}"), Made: []Artifact{{Name: "n", Kind: "image", Reference: "r"}}} if chainFingerprint(a) != chainFingerprint(b) { t.Fatal("two spellings of one manifest hash apart") } b.Made[0].Reference = "other" if chainFingerprint(a) == chainFingerprint(b) { t.Fatal("two artifacts hash alike") } } // **Newest request first, as the store's ordering was**: the newest ask, then the newest record, then the newest id. func TestBuildsAreOrderedNewestRequestFirst(t *testing.T) { t0 := time.Date(2026, 10, 18, 3, 0, 0, 0, time.UTC) builds := []Build{ {ID: "old", Asked: t0, At: t0.Add(time.Hour)}, {ID: "unasked", At: t0.Add(2 * time.Hour)}, {ID: "new", Asked: t0.Add(3 * time.Hour), At: t0.Add(3*time.Hour + time.Minute)}, {ID: "same-a", Asked: t0.Add(4 * time.Hour), At: t0.Add(5 * time.Hour)}, {ID: "same-b", Asked: t0.Add(4 * time.Hour), At: t0.Add(5 * time.Hour)}, } newestRequestOrder(builds) var got []string for _, b := range builds { got = append(got, b.ID) } if want := []string{"same-b", "same-a", "new", "unasked", "old"}; !reflect.DeepEqual(got, want) { t.Fatalf("ordered %v, not %v", got, want) } } // **A bus that does not answer, a lease lost and a key written first are "not now"** (issue 1092): a merge or a report whose // write failed so is held and handed over again, as it is for a store that is away. func TestTheBusBeingAwayIsNotNowLikeTheStoreBeingAway(t *testing.T) { for name, err := range map[string]error{ "a timeout": fmt.Errorf("the plan is not written: %w", nats.ErrTimeout), "no responders": fmt.Errorf("build 1: %w", nats.ErrNoResponders), "a closed bus": nats.ErrConnectionClosed, "the lease lost": fmt.Errorf("%w: another controller holds it", chain.ErrNotHeld), "a key moved": chain.ErrMoved, "a deadline": context.DeadlineExceeded, } { if !Unreachable(err) { t.Errorf("%s is read as an answer", name) } } for name, err := range map[string]error{ "a value too large": chain.ErrTooLarge, "a key not taken": chain.ErrBadKey, "a plan moved": ErrPlanMoved, "a key that exists": chain.ErrExists, } { if Unreachable(err) { t.Errorf("%s is read as a bus being away: asking again gets the same answer", name) } } }