package delivery import ( "encoding/json" "reflect" "strings" "testing" "time" ) // The packages state, the build end's imports and the kept imports (ADR 0593 ยง2) round-trip, and a reader refuses what it // could not take, naming it. func TestPublishedPackagesAndImportsRoundTripAndRefuse(t *testing.T) { commit := strings.Repeat("a", 40) sdk := "git.novox.be/novox/mesh-sdk/go" good := PublishedPackages{Module: sdk, Repository: "novox/mesh-sdk", Dir: "go", Commit: commit, Requires: "sha256:01", Packages: []PackageDigest{{Package: sdk, Digest: "d0"}, {Package: sdk + "/announce", Digest: "d1"}, {Package: sdk + "/internal/token", Digest: "d2"}}, At: time.Date(2026, 11, 2, 18, 0, 0, 0, time.UTC)} raw, err := EncodePublishedPackages(good) if err != nil { t.Fatal(err) } back, err := DecodePublishedPackages(raw) good.V = Version if err != nil || !reflect.DeepEqual(back, good) { t.Fatalf("round trip: %v, %+v", err, back) } if d, ok := back.DigestOf(sdk + "/announce"); !ok || d != "d1" { t.Fatalf("DigestOf announce = %q, %v", d, ok) } if _, ok := back.DigestOf(sdk + "/buildstep"); ok { t.Fatal("DigestOf finds a package the value does not hold") } if key, err := PackagesKey(sdk); err != nil || key != "git=2enovox=2ebe/novox/mesh-sdk/go" { t.Fatalf("PackagesKey = %q, %v", key, err) } if _, err := PackagesKey("github.com/x/y"); err == nil { t.Fatal("PackagesKey takes a path that is no mesh Go module's") } for name, mutate := range map[string]func(p *PublishedPackages){ "no mesh module": func(p *PublishedPackages) { p.Module = "github.com/x/y" }, "other repository": func(p *PublishedPackages) { p.Repository = "novox/mesh-host" }, "short commit": func(p *PublishedPackages) { p.Commit = "abc" }, "no requires": func(p *PublishedPackages) { p.Requires = "" }, "package elsewhere": func(p *PublishedPackages) { p.Packages = []PackageDigest{{Package: "git.novox.be/novox/mesh-host/x", Digest: "d"}} }, "unsorted": func(p *PublishedPackages) { p.Packages = []PackageDigest{p.Packages[1], p.Packages[0]} }, "no digest": func(p *PublishedPackages) { p.Packages = []PackageDigest{{Package: sdk}} }, } { p := good p.Packages = slicesClone(good.Packages) mutate(&p) if _, err := EncodePublishedPackages(p); err == nil { t.Errorf("%s: encoded", name) } } // The indexer's series value (design 51 *The versions*), as the indexer writes it: read back whole, and refused // where a reader could not take it. written := `{"v":1,"name":"` + sdk + `","package":"mesh-sdk/go","series":"0.1","seat":"go-package-registry","go-module":"` + sdk + `","newest":{"version":"0.1.15","commit":"` + commit + `","digest":"sha256:0f","fingerprint":"fp1","contents":` + string(raw) + `},"numbered":{"version":"0.1.16","commit":"` + commit + `"},"at":"2026-11-04T14:00:00Z"}` series, err := DecodeSeriesPackage([]byte(written)) if err != nil || series.Name != sdk || series.Package != "mesh-sdk/go" || series.Newest.Contents == nil || series.Numbered.Version != "0.1.16" { t.Fatalf("a series value as the indexer writes it reads %+v, %v", series, err) } if again, err := EncodeSeriesPackage(series); err != nil || string(again) != written { t.Fatalf("a series value writes back as\n%s, %v, not\n%s", again, err, written) } if key := SeriesPackageKey(sdk, "0.1"); key != "git=2enovox=2ebe/novox/mesh-sdk/go.0.1" || !IsSeriesPackageKey(key) || IsSeriesPackageKey(EscapeName(sdk)) { t.Fatalf("the series key %q is not told apart from a per-module key", key) } for name, mutate := range map[string]func(s *SeriesPackage){ "a series with no name": func(s *SeriesPackage) { s.Name = "" }, "a series that is no series": func(s *SeriesPackage) { s.Series = "0.1.15" }, "built from no artifact": func(s *SeriesPackage) { s.Package = "mesh-sdk" }, "a numbered version of nothing": func(s *SeriesPackage) { s.Numbered = &SeriesVersion{} }, } { s := series mutate(&s) if _, err := EncodeSeriesPackage(s); err == nil { t.Errorf("%s: encoded", name) } } imports := []string{sdk, sdk + "/announce"} outOfStep := []string{"go/seats/meshbroker/contract.go", "ts/seats/meshbroker/src/index.ts"} end := BuildLogEntry{Event: BuildEndedEvent, Module: "netcheck", FP: "src2:x", Repository: "novox/mesh-catalog", Commit: commit, Node: "ace", At: good.At, Outcome: "done", Imports: imports, OutOfStep: outOfStep} raw, err = EncodeBuildLogEntry(end) if err != nil { t.Fatal(err) } if e, err := DecodeBuildLogEntry(raw); err != nil || !reflect.DeepEqual(e.Imports, imports) || !reflect.DeepEqual(e.OutOfStep, outOfStep) || !strings.Contains(string(raw), `"out-of-step":["go/seats/meshbroker/contract.go",`) { t.Fatalf("build end round trip: %v, %v, %v in %s", err, e.Imports, e.OutOfStep, raw) } start := end start.Event, start.Outcome, start.Imports = BuildStartedEvent, "", nil if _, err := EncodeBuildLogEntry(start); err == nil { t.Error("a build's start with files out of step encoded") } start.Imports, start.OutOfStep = imports, nil if _, err := EncodeBuildLogEntry(start); err == nil { t.Error("a build's start with imports encoded") } end.Imports = []string{sdk + "/announce", sdk} if _, err := EncodeBuildLogEntry(end); err == nil { t.Error("unsorted imports encoded") } // A build narrates between its start and its end (build.said): its module, fingerprint, line and why, nothing of an // end; a start or end carries no line. said := BuildLogEntry{Event: BuildSaidEvent, Module: "netcheck", FP: "src2:x", At: good.At, Line: "waiting on container-image-registry", Why: "waiting"} if raw, err := EncodeBuildLogEntry(said); err != nil { t.Errorf("a narration refused: %v", err) } else if e, err := DecodeBuildLogEntry(raw); err != nil || e.Line != said.Line || e.Why != "waiting" { t.Errorf("a narration's round trip: %+v, %v", e, err) } for name, mutate := range map[string]func(e *BuildLogEntry){ "a narration with no line": func(e *BuildLogEntry) { e.Line = "" }, "a narration with an outcome": func(e *BuildLogEntry) { e.Outcome = "done" }, "a narration out of step": func(e *BuildLogEntry) { e.OutOfStep = outOfStep }, "an end with a line": func(e *BuildLogEntry) { e.Event, e.Outcome, e.Node, e.Repository, e.Commit = BuildEndedEvent, "done", "ace", "novox/mesh-catalog", commit }, } { e := said mutate(&e) if _, err := EncodeBuildLogEntry(e); err == nil { t.Errorf("%s encoded", name) } } var answer BuildAnswer if err := json.Unmarshal([]byte(`{"artifacts":[],"imports":["`+sdk+`"],"out-of-step":["go/seats/x/contract.go"]}`), &answer); err != nil || len(answer.Imports) != 1 || !reflect.DeepEqual(answer.OutOfStep, []string{"go/seats/x/contract.go"}) { t.Fatalf("build answer imports and files out of step: %v, %v, %v", err, answer.Imports, answer.OutOfStep) } // The builder's version.numbered, as a build module consumes it: the builder's own body whole, and its subject. numbered := VersionNumbered{Name: "mesh-sdk/go", Kind: "package", Version: "0.1.7", Module: "mesh-sdk", Fingerprint: "src2:x", Commit: commit, Digest: "sha256:d", Seat: "go-package-registry", Head: commit, Again: "2026-11-04T15:00:00Z"} if raw, err := EncodeVersionNumbered(numbered); err != nil { t.Errorf("numbered refused: %v", err) } else if back, err := DecodeVersionNumbered(raw); err != nil || back != numbered || !strings.Contains(string(raw), `"fingerprint":"src2:x"`) || VersionNumberedSubject != "mesh.mod.mesh-delivery-builder.event.version.numbered" { t.Errorf("numbered round trip: %+v, %v, %s", back, err, raw) } if _, err := DecodeVersionNumbered([]byte(`{"name":"mesh-sdk/go","version":"0.1.7"}`)); err == nil { t.Error("a numbered version naming no build is read") } if err := CheckImports([]string{"fmt"}); err == nil { t.Error("CheckImports takes a standard package") } } func slicesClone(d []PackageDigest) []PackageDigest { return append([]PackageDigest(nil), d...) }