Files
mesh-controller/internal/inventory/chain_builds_test.go
T
jschoubben f89d870308 Keep builds in the builds bucket and move the build rows there at start (issue 1090)
Once chain_moved records the kind, RecordBuild and every build read go through the chain (reuse key and published added); before that the tables are read as before. The serving controller copies the rows at its start, holding the lease: idempotent per key, rows counted against keys, the largest value measured, a refusal (urgent condition, nothing switched) on a count that differs or a value over the bus's largest, and a re-read when the table took a row meanwhile. ForTest attaches the chain in memory with builds moved; ForTestBefore leaves it unmoved. Build requests, gates and the other kinds are still in their tables.
2026-10-18 02:49:51 +02:00

101 lines
4.4 KiB
Go

package inventory
import (
"encoding/json"
"reflect"
"testing"
"time"
"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)
}
}