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.
101 lines
4.4 KiB
Go
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)
|
|
}
|
|
}
|