package catalogue import ( "encoding/json" "fmt" "strings" "testing" ) // A module version prepares its state before it runs (novox/hq ADR 0135). // // What the mesh derives is the module's own resource, run once with one word, placed immediately in // front of the thing it prepares for. What matters in these tests is that the derivation is a copy // rather than a second description: the failure it replaces was a hand-written step repeating six // fields of the resource it preceded, each free to drift from it. func aPreparingModule() Manifest { return Manifest{ Module: "gitea", Prepares: true, Resources: []map[string]any{ {"id": "state", "type": "directory", "path": "/var/lib/gitea", "mode": "0700"}, {"id": "server", "type": "container", "name": "mesh-gitea-server", "image": "gitea/gitea@" + digest, "ports": []any{"3000:3000"}}, {"id": "runtime", "type": "container", "name": "mesh-gitea", "image": ArtifactStoreScheme + "gitea/runtime@" + digest, "network": "host", "env": map[string]any{"MESH_GITEA_STATE_DIR": "/run/state"}, "volumes": []any{"/var/lib/gitea:/run/state:ro"}, "ports": []any{"9000:9000"}}, }, } } func declaredFor(t *testing.T, m Manifest) []map[string]any { t.Helper() // The manifest as the mesh holds it: a build resolved the module's own artifact into the // reference it recorded, which is also how the composition knows whose code a resource runs. out, err := Resolution{Node: "anchor", Modules: []Manifest{m}}.Declaration( Rendering{ArtifactStore: "anchor.internal:5100"}) if err != nil { t.Fatal(err) } return out } func idsOf(resources []map[string]any) []string { var ids []string for _, r := range resources { ids = append(ids, fmt.Sprint(r["id"])) } return ids } // The step runs the module's own code, and comes immediately before it — not before the upstream // server the module packages, which may be the very thing the state lives in. func TestThePreparationRunsTheModulesOwnCodeAndComesRightBeforeIt(t *testing.T) { out := declaredFor(t, aPreparingModule()) ids := idsOf(out) at := -1 for i, id := range ids { if id == "gitea.runtime-prepare" { at = i } } if at < 0 { t.Fatalf("nothing prepares this module's state: %v", ids) } // A module's name may contain a dot, so a resource's module is everything before the last one — // which the derived id must not add to, or a machine reads the wrong owner from it. if strings.Count("gitea.runtime-prepare", ".") != 1 { t.Fatal("the derived id adds a dot, so what owns it cannot be read from it") } if ids[at+1] != "gitea.runtime" { t.Fatalf("the preparation is not immediately before the module's own code: %v", ids) } for _, id := range ids[:at] { if id == "gitea.runtime" { t.Fatalf("the module's own code runs before its state is prepared: %v", ids) } } } // It is given exactly what the module's own code is given. Asserted field by field against the // resource it was derived from, because writing it twice is the fault this replaces. func TestThePreparationIsGivenWhatTheModuleIsGiven(t *testing.T) { out := declaredFor(t, aPreparingModule()) declared := byID(out) step, workload := declared["gitea.runtime-prepare"], declared["gitea.runtime"] if step == nil || workload == nil { t.Fatalf("expected both, got %v", idsOf(out)) } for _, field := range []string{"image", "network", "env", "volumes", "type"} { if fmt.Sprint(step[field]) != fmt.Sprint(workload[field]) { t.Errorf("the preparation's %s is %v and the module's is %v", field, step[field], workload[field]) } } if once, _ := step["run-once"].(bool); !once { t.Error("the preparation is not a step, so nothing waits for it and nothing is gated by it") } if fmt.Sprint(step["args"]) != fmt.Sprint([]any{PreparationArgument}) { t.Errorf("the preparation is asked for as %v", step["args"]) } if fmt.Sprint(step["name"]) == fmt.Sprint(workload["name"]) { t.Error("the preparation and the workload have one name, so one removes the other") } // A published port cannot be bound twice, and the version being replaced is still running. if _, published := step["ports"]; published { t.Errorf("the preparation publishes a port the running version holds: %v", step["ports"]) } } // A module that says nothing about preparing gets nothing, which is most modules. func TestAModuleThatPreparesNothingGetsNoStep(t *testing.T) { m := aPreparingModule() m.Prepares = false for _, id := range idsOf(declaredFor(t, m)) { if id == "gitea.runtime-prepare" { t.Fatal("a module that prepares nothing was given a preparation") } } } // A module whose own code the mesh cannot find has nothing to ask, and saying so where the manifest // is read beats a declaration that quietly prepares nothing. func TestAModuleThatPreparesAndRunsNoneOfItsOwnCodeIsRefused(t *testing.T) { m := Manifest{ Module: "gitea", Prepares: true, Resources: []map[string]any{ // Only the upstream server it packages: nothing here runs gitea's own code. {"id": "server", "type": "container", "name": "mesh-gitea-server", "image": "gitea/gitea@" + digest}, }, } raw, err := json.Marshal(m) if err != nil { t.Fatal(err) } if _, err := ParseManifest(raw); err == nil { t.Fatal("a module that prepares its state with nothing of its own to run was accepted") } }