Builds of one module in flight together finish in any order, and the mesh took whatever it heard last as what the module is: RegisterModule overwrote the module's manifest unconditionally, and Held/BuiltAgainst/ReadRepositories ordered builds by when they were recorded. A postgres build asked before the mesh-tools runtime fix finished after the one asked after it, and the next push deployed the stale image (novox/hq issue 219). A build is now ordered by when it was asked, read from the build-<nanos> id the controller writes: build.asked and module.built_asked (migration 0055). A registration from an earlier request than the module's current one is recorded and refused as superseded. A plan takes as its outcome only a build asked at or after its own ask, so an earlier plan's leftover build cannot settle a later plan. Ids of any other shape keep the old order.
153 lines
6.4 KiB
Go
153 lines
6.4 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/novox/mesh-controller/internal/inventory"
|
|
"github.com/novox/mesh-controller/internal/link"
|
|
)
|
|
|
|
// A build's outcome is taken in the same way whoever hears it (novox/hq issue 176): recorded, and
|
|
// the module registered with its source as the seat and path when the request said so — never the
|
|
// URL. A definition naming an installation is recorded and not registered; a failure is recorded
|
|
// and said.
|
|
func TestABuildHeardIsRecordedAndRegistered(t *testing.T) {
|
|
open := aMesh(t)
|
|
ctx := t.Context()
|
|
manifest, _ := json.Marshal(map[string]any{"module": "shop", "version": "3"})
|
|
m, _, err := takeIn(ctx, open.inventory, link.BuildResult{
|
|
ID: "b-1", Repository: "http://forge.internal:20000/novox/shop.git", Path: "modules/shop",
|
|
Ref: "main", On: "anchor", Commit: "abcdef0123", Manifest: manifest,
|
|
Source: &link.SourceOnSeat{Seat: "git", Repository: "novox/shop"},
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if m.Module != "shop" {
|
|
t.Fatalf("registered %q", m.Module)
|
|
}
|
|
shelf, err := open.inventory.Catalogue(ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, held := shelf["shop"]; !held {
|
|
t.Fatal("the module a heard build produced is not in the catalogue")
|
|
}
|
|
src, err := open.inventory.SourceOf(ctx, "shop")
|
|
if err != nil || src.Seat != "git" || src.Repository != "novox/shop" || src.BuiltFrom != "abcdef0123" {
|
|
t.Fatalf("the source is the seat and the path, never the URL: %+v %v", src, err)
|
|
}
|
|
builds, err := open.inventory.Builds(ctx, "shop", 5)
|
|
if err != nil || len(builds) != 1 || builds[0].ID != "b-1" {
|
|
t.Fatalf("the build is not recorded once: %v %v", builds, err)
|
|
}
|
|
|
|
named, _ := json.Marshal(map[string]any{"module": "idp", "version": "1", "resources": []any{
|
|
map[string]any{"id": "server", "type": "container", "image": "x@sha256:aa",
|
|
"env": map[string]any{"KC_HOSTNAME": "https://login.mesh-one.be"}}}})
|
|
_, _, err = takeIn(ctx, open.inventory, link.BuildResult{
|
|
ID: "b-2", Repository: "/r", On: "anchor", Commit: "0123456789", Manifest: named})
|
|
if err == nil || !strings.Contains(err.Error(), "does not register it") {
|
|
t.Fatalf("a definition naming an installation was taken in: %v", err)
|
|
}
|
|
if shelf, _ := open.inventory.Catalogue(ctx); shelf["idp"].Module != "" {
|
|
t.Fatal("the refused module was registered anyway")
|
|
}
|
|
if builds, _ := open.inventory.Builds(ctx, "idp", 5); len(builds) != 1 {
|
|
t.Fatalf("the refused build was not recorded: %v", builds)
|
|
}
|
|
|
|
_, _, err = takeIn(ctx, open.inventory, link.BuildResult{ID: "b-3", Repository: "/r", On: "anchor", Failed: "no compiler"})
|
|
if err == nil || !strings.Contains(err.Error(), "no compiler") {
|
|
t.Fatalf("a failure is said in the builder's words: %v", err)
|
|
}
|
|
}
|
|
|
|
// novox/hq 04-ISSUES/215: a build asked at a commit is recorded as built from that commit, and the
|
|
// module keeps following the branch it followed — a new one, the default branch.
|
|
func TestABuildAtACommitKeepsTheBranchTheModuleFollows(t *testing.T) {
|
|
open := aMesh(t)
|
|
ctx := t.Context()
|
|
manifest, _ := json.Marshal(map[string]any{"module": "unifi", "version": "1"})
|
|
result := func(id, ref, commit string) link.BuildResult {
|
|
return link.BuildResult{ID: id, Repository: "http://forge.internal:20000/novox/mesh-catalog.git",
|
|
Path: "modules/unifi", Ref: ref, On: "anchor", Commit: commit, Manifest: manifest,
|
|
Source: &link.SourceOnSeat{Seat: "git", Repository: "novox/mesh-catalog"}}
|
|
}
|
|
if _, _, err := takeIn(ctx, open.inventory, result("b-1", "main", "1111111aaaa")); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, _, err := takeIn(ctx, open.inventory, result("b-2", "9c97a8a", "9c97a8a1d2c3")); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
src, err := open.inventory.SourceOf(ctx, "unifi")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if src.Ref != "main" || src.BuiltFrom != "9c97a8a1d2c3" {
|
|
t.Errorf("after a build at a commit the module follows %q, built from %q; want main, 9c97a8a1d2c3", src.Ref, src.BuiltFrom)
|
|
}
|
|
|
|
// One new to the catalogue, first built at a commit, follows the default branch.
|
|
other, _ := json.Marshal(map[string]any{"module": "letta", "version": "1"})
|
|
r := result("b-3", "deadbeef", "deadbeefcafe")
|
|
r.Manifest, r.Path = other, "modules/letta"
|
|
if _, _, err := takeIn(ctx, open.inventory, r); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if src, _ := open.inventory.SourceOf(ctx, "letta"); src.Ref != "" {
|
|
t.Errorf("a module first built at a commit follows %q, want the default branch", src.Ref)
|
|
}
|
|
}
|
|
|
|
// novox/hq 04-ISSUES/219: an older request heard after a newer one is recorded and not registered,
|
|
// so a push sends what the newer request built.
|
|
func TestAnOlderBuildHeardLaterDoesNotReplaceTheNewer(t *testing.T) {
|
|
open := aMesh(t)
|
|
ctx := t.Context()
|
|
older := time.Date(2026, 10, 3, 21, 33, 45, 0, time.UTC)
|
|
newer := time.Date(2026, 10, 3, 21, 51, 57, 0, time.UTC)
|
|
result := func(asked time.Time, image string) link.BuildResult {
|
|
manifest, _ := json.Marshal(map[string]any{"module": "postgres", "version": image})
|
|
return link.BuildResult{ID: link.NewBuildID(asked), Repository: "http://forge.internal:20000/novox/mesh-catalog.git",
|
|
Path: "modules/postgres", Ref: "main", On: "anchor", Commit: "efff5415", Manifest: manifest,
|
|
Source: &link.SourceOnSeat{Seat: "git", Repository: "novox/mesh-catalog"}}
|
|
}
|
|
if _, _, err := takeIn(ctx, open.inventory, result(newer, "4bcd5f73")); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
_, _, err := takeIn(ctx, open.inventory, result(older, "0ab07fa9"))
|
|
if !errors.Is(err, inventory.ErrSuperseded) {
|
|
t.Fatalf("the older request's outcome was taken in as current: %v", err)
|
|
}
|
|
shelf, err := open.inventory.Catalogue(ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := shelf["postgres"].Version; got != "4bcd5f73" {
|
|
t.Errorf("postgres is %q; want the newer request's 4bcd5f73", got)
|
|
}
|
|
if builds, _ := open.inventory.Builds(ctx, "postgres", 5); len(builds) != 2 {
|
|
t.Errorf("the late build was not recorded: %v", builds)
|
|
}
|
|
}
|
|
|
|
func TestABuildIDSaysWhenItWasAsked(t *testing.T) {
|
|
at := time.Date(2026, 10, 3, 21, 51, 57, 392539762, time.UTC)
|
|
if got, ok := link.BuildAskedAt(link.NewBuildID(at)); !ok || !got.Equal(at) {
|
|
t.Errorf("read back %v %v; want %v", got, ok, at)
|
|
}
|
|
if got, ok := link.BuildAskedAt("build-1791064317392539762"); !ok || got.Format(time.TimeOnly) != "21:51:57" {
|
|
t.Errorf("the incident's id reads as %v %v", got, ok)
|
|
}
|
|
for _, id := range []string{"b-1", "build-2", "build-", "build-x", ""} {
|
|
if _, ok := link.BuildAskedAt(id); ok {
|
|
t.Errorf("%q read as a request time", id)
|
|
}
|
|
}
|
|
}
|