Files
mesh-delivery/modules/mesh-delivery-builder/internal/build/plan_test.go
T
jschoubben 4332246f79 U2a: the builder plans a Go package on the Go build seat instead of refusing it
A module whose only artifact is a Go package (mesh-wireguard's go-overlay) never had a
done build: PlanOf refused every package of language go. The Go build module already packs
and publishes a Go package; the builder now asks it.

Builds: design 47 G §370; design 51 *Publishing*

Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 16:13:13 +01:00

123 lines
5.7 KiB
Go

package build
import (
"errors"
"strings"
"testing"
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun"
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
"git.novox.be/novox/mesh-sdk/go/catalogue"
"git.novox.be/novox/mesh-sdk/go/seatdef"
)
// manifestOf is a manifest holding these artifacts: the plan reads only their name, kind and language.
func manifestOf(artifacts ...catalogue.Artifact) catalogue.Manifest {
return catalogue.Manifest{Module: "m", Build: &catalogue.Build{Artifacts: artifacts}}
}
func TestPlanRoutesEachKindToItsBuildModule(t *testing.T) { // 1607/AC2
m := manifestOf(
catalogue.Artifact{Name: "tool", Kind: "bundle", Language: "go"},
catalogue.Artifact{Name: "web", Kind: "bundle", Language: "typescript"},
catalogue.Artifact{Name: "files", Kind: "archive"},
catalogue.Artifact{Name: "go-overlay", Kind: "package", Language: "go"}) // design 47 G §370: a Go package is built
p, err := PlanOf(m, testSeats())
if err != nil {
t.Fatal(err)
}
want := []Task{
{Artifact: "files", Kind: "archive", Module: testGo, Language: "go"},
{Artifact: "go-overlay", Kind: "package", Module: testGo, Language: "go"},
{Artifact: "tool", Kind: "bundle", Module: testGo, Language: "go"},
{Artifact: "web", Kind: "bundle", Module: testNode, Language: "typescript"},
}
if len(p.Tasks) != len(want) {
t.Fatalf("tasks = %+v, want %+v", p.Tasks, want)
}
for i := range want {
if p.Tasks[i] != want[i] {
t.Errorf("task %d = %+v, want %+v", i, p.Tasks[i], want[i])
}
}
if modules := p.Modules(); len(modules) != 2 || modules[0] != testGo || modules[1] != testNode {
t.Errorf("modules = %v", modules)
}
}
const pinned = "nats:2.11.17-alpine@sha256:3333333333333333333333333333333333333333333333333333333333333333"
func TestPlanSendsAPinnedUpstreamToTheImageBuildModulesCopyAndRefusesAnUnpinnedOne(t *testing.T) { // B3, ADR 0498 clause 4
p, err := PlanOf(manifestOf(catalogue.Artifact{Name: "ok", Kind: "archive"}, catalogue.Artifact{Name: "up", Kind: "upstream", From: pinned}), testSeats())
if err != nil || len(p.Tasks) != 2 {
t.Fatalf("plan %+v, error %v", p, err)
}
if up := p.Tasks[1]; up.Artifact != "up" || up.Kind != "upstream" || up.Module != testImage || up.From != pinned || up.Language != "" {
t.Errorf("task = %+v, want the upstream sent to %s with its pinned reference and no toolchain", up, testImage)
}
if p.NeedsToolchain(testImage) {
t.Error("a copy needs no toolchain")
}
_, err = PlanOf(manifestOf(catalogue.Artifact{Name: "up", Kind: "upstream", From: "nats:2.11.17-alpine"}), testSeats())
if err == nil || !strings.HasPrefix(err.Error(), builds.NotPinned+"up") || !builds.FailedUntilChanged(err.Error()) {
t.Errorf("an upstream with no digest: error = %v, want it to open %q naming the artifact", err, builds.NotPinned)
}
}
func TestPlanRefusesAPackageOfAnotherLanguageByName(t *testing.T) { // ADR 0498 clause 5
_, err := PlanOf(manifestOf(catalogue.Artifact{Name: "ok", Kind: "archive"}, catalogue.Artifact{Name: "pkg", Kind: "package", Language: "python"}), testSeats())
var r *Refused
if err == nil || !asRefused(err, &r) || !strings.HasPrefix(err.Error(), builds.RefusedKind) || !strings.Contains(err.Error(), "python") || !strings.Contains(err.Error(), "pkg") {
t.Errorf("error = %v, want it to open %q naming the artifact and its language", err, builds.RefusedKind)
}
}
func TestPlanRefusesABundleOfAnotherLanguageAndAModuleWithNoArtifact(t *testing.T) {
if _, err := PlanOf(manifestOf(catalogue.Artifact{Name: "x", Kind: "bundle", Language: "python"}), testSeats()); err == nil || !strings.HasPrefix(err.Error(), builds.RefusedKind) {
t.Errorf("a python bundle: error = %v, want a refusal", err)
}
p, err := PlanOf(manifestOf(), testSeats())
if err != nil || len(p.Tasks) != 0 {
t.Errorf("no artifact: plan %+v error %v, want an empty plan", p, err)
}
}
func asRefused(err error, target **Refused) bool { return errors.As(err, target) }
func TestPlanSendsAnImageToMeshBuildImageWithNoToolchain(t *testing.T) { // 1705a, ADR 0487 clause 5
p, err := PlanOf(manifestOf(catalogue.Artifact{Name: "img", Kind: "image"}, catalogue.Artifact{Name: "files", Kind: "archive"}), testSeats())
if err != nil || len(p.Tasks) != 2 {
t.Fatalf("plan %+v, error %v", p, err)
}
if p.Tasks[1].Artifact != "img" || p.Tasks[1].Module != testImage {
t.Errorf("task = %+v, want the image on %s", p.Tasks[1], testImage)
}
if p.NeedsToolchain(testImage) {
t.Error("an image needs no toolchain")
}
}
// A plan's task names the holder of the build seat the seat list and the catalog half give, and a plan made before they name one
// is no refusal and is not kept (design 47 G A-82).
func TestAPlanNamesTheHoldersOfTheBuildSeats(t *testing.T) {
m := manifestOf(catalogue.Artifact{Name: "tool", Kind: "bundle", Language: "go"}, catalogue.Artifact{Name: "img", Kind: "image"})
defs := map[string]seatdef.Definition{
"node-build-go": {Acts: []seatdef.ActingRule{{Step: seatdef.StepBuild, On: "go"}}},
"node-build-image": {Acts: []seatdef.ActingRule{{Step: seatdef.StepBuild, On: seatdef.OnKindImage}}},
}
holders := map[string]string{}
seats := buildrun.NewBuildSeats(func() map[string]seatdef.Definition { return defs }, func(s string) (string, bool) { h, ok := holders[s]; return h, ok })
_, err := PlanOf(m, seats)
var none *NoBuildModule
if !errors.As(err, &none) {
t.Fatalf("with no holder: %v, want NoBuildModule", err)
}
holders["node-build-go"], holders["node-build-image"] = "acme-go", "acme-img"
p, err := PlanOf(m, seats)
if err != nil || len(p.Tasks) != 2 || p.Tasks[0].Module != "acme-img" || p.Tasks[1].Module != "acme-go" || p.Tasks[1].Language != "go" || p.Tasks[0].Language != "" {
t.Fatalf("plan = %+v, %v", p, err)
}
}