The merge gate, the build seat's check and the facts writer import the snapshot's type from mesh-sdk (go/facts), whose reader takes formats 1 and 2 and refuses a newer one. The writer keeps writing format 1 (writtenFormat); moving it to 2 is a release of its own (novox/hq ADR 0456). A test shows the writer writes format 1, carries no grants or bindings, and the reader takes both formats and refuses a newer one.
149 lines
5.5 KiB
Go
149 lines
5.5 KiB
Go
//go:build integration
|
|
|
|
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
|
snapshot "git.novox.be/novox/mesh-sdk/go/facts"
|
|
"github.com/novox/mesh-controller/internal/inventory"
|
|
)
|
|
|
|
func templatedMesh(t *testing.T) (snapshot.Facts, map[string]string) {
|
|
t.Helper()
|
|
open := aMesh(t)
|
|
ctx := t.Context()
|
|
manifests := map[string]string{
|
|
"lemurs": `{"module":"lemurs","version":"1","capabilities":["systemd"],
|
|
"resources":[{"id":"service","type":"service","unit":"lemurs.service","state":"running","boot":"enabled"}]}`,
|
|
"notifier": inlineNotifier,
|
|
}
|
|
for name, raw := range manifests {
|
|
m, err := catalogue.ParseManifest([]byte(raw))
|
|
if err != nil {
|
|
t.Fatalf("%s: %v", name, err)
|
|
}
|
|
if err := open.inventory.RegisterModule(ctx, m, inventory.Source{Repository: "novox/mesh-catalog",
|
|
Path: "modules/" + name, BuiltFrom: "c0ffee"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := assign(ctx, open, "laptop", name); err != nil {
|
|
t.Fatalf("assign %s: %v", name, err)
|
|
}
|
|
}
|
|
f, err := gatherFacts(ctx, open, "2.11.17")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return f, manifests
|
|
}
|
|
|
|
func TestTheMergeGateJudgesAChangeToATemplateAlone(t *testing.T) {
|
|
f, manifests := templatedMesh(t)
|
|
|
|
// The module moved to its template, the same text: nothing changes on any machine.
|
|
v := gateJudged(t, f, aTemplatedTree(t, manifests, "volume = 3\n", true), "modules/notifier/module.json",
|
|
notifierTemplate)
|
|
if v.Verdict == "fail" || v.Modules["notifier"] != "" {
|
|
t.Fatalf("the module moved to its template with the same text reads as a change (%v):\n%s", v.Modules, v.Report())
|
|
}
|
|
|
|
// The template alone changed: its module changed, and composes.
|
|
v = gateJudged(t, f, aTemplatedTree(t, manifests, "volume = 4\n", true), notifierTemplate)
|
|
if v.Verdict == "fail" || v.Modules["notifier"] != "changed" {
|
|
t.Fatalf("a change to the template alone is not a change to its module (%v):\n%s", v.Modules, v.Report())
|
|
}
|
|
if v.Width == nil || strings.Join(v.Width.Modules, ",") != "notifier" {
|
|
t.Fatalf("a change to the template rebuilds %+v, not its module", v.Width)
|
|
}
|
|
|
|
// The template alone changed to text the controller refuses: the pull request fails, naming the module.
|
|
v = gateJudged(t, f, aTemplatedTree(t, manifests, "store = ${dir:nowhere}\n", true), notifierTemplate)
|
|
if v.Verdict != "fail" || !strings.Contains(v.Report(), "notifier") {
|
|
t.Fatalf("a template the controller refuses passed the gate:\n%s", v.Report())
|
|
}
|
|
|
|
// A symlinked files/ fails: nothing is read from outside the tree.
|
|
tree := aTree(t, withEdit(manifests, "notifier", ""))
|
|
elsewhere := t.TempDir()
|
|
if err := os.WriteFile(filepath.Join(elsewhere, "notifier.conf"), []byte("volume = 3\n"), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
aTemplatedModule(t, filepath.Join(tree, "modules", "notifier"), templatedNotifier, nil)
|
|
if err := os.Symlink(elsewhere, filepath.Join(tree, "modules", "notifier", "files")); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
v = gateJudged(t, f, tree, notifierTemplate)
|
|
if v.Verdict != "fail" || !strings.Contains(v.Report(), "files/ is a symlink") {
|
|
t.Fatalf("a symlinked files/ passed the gate:\n%s", v.Report())
|
|
}
|
|
|
|
// And the template removed from the tree fails, naming it.
|
|
v = gateJudged(t, f, aTemplatedTree(t, manifests, "", false), notifierTemplate)
|
|
if v.Verdict != "fail" || !strings.Contains(v.Report(), "files/notifier.conf") {
|
|
t.Fatalf("a missing template passed the gate:\n%s", v.Report())
|
|
}
|
|
}
|
|
|
|
func TestTheMergeGateResolvesATemplatedModuleToItsInlineTwin(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := aTemplatedModule(t, dir, templatedNotifier, map[string]string{"files/notifier.conf": "volume = 3\n"})
|
|
raw, err := os.ReadFile(path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
m, err := catalogue.ParseManifest(raw)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
texts, err := catalogue.ReadTemplates(m, dir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
resolved, err := standInBuild(m, texts)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
inline, err := catalogue.ParseManifest([]byte(inlineNotifier))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if builtAlike(resolved) != builtAlike(inline) {
|
|
got, _ := json.Marshal(resolved)
|
|
t.Fatalf("the templated module resolves to %s, not its inline twin", got)
|
|
}
|
|
}
|
|
|
|
func TestReplay876(t *testing.T) {
|
|
moved := strings.Replace(templatedNotifier, `"version":"1",`, `"version":"1","moved":true,`, 1)
|
|
judge := func(manifest string, files map[string]string) (map[string]catalogue.Manifest, []string) {
|
|
tree := t.TempDir()
|
|
aTemplatedModule(t, filepath.Join(tree, "modules", "notifier"), manifest, files)
|
|
out, failures, err := shelfWithChange(map[string]catalogue.Manifest{}, nil, snapshot.Facts{},
|
|
"novox/mesh-catalog", tree, map[string]string{}, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return out, failures
|
|
}
|
|
out, failures := judge(moved, map[string]string{"files/notifier.conf": "volume = 3\n"})
|
|
if len(failures) > 0 {
|
|
t.Fatalf("a moved module naming its templates was refused: %v", failures)
|
|
}
|
|
if h := out["notifier"].Handed; h == nil || h.Templates["conf"] != "notifier.conf" {
|
|
t.Fatalf("the gate did not hand the moved module its templates: %+v", h)
|
|
}
|
|
|
|
// A moved module naming no template is still refused.
|
|
none := strings.Replace(strings.Replace(moved, `"template":"files/notifier.conf"`,
|
|
`"content":"volume = 3\n"`, 1), `"build":{"artifacts":[{"name":"files","kind":"archive","from":"files"}]},`, "", 1)
|
|
if _, failures := judge(none, nil); len(failures) != 1 || !strings.Contains(failures[0], "names no template") {
|
|
t.Fatalf("a moved module naming no template was not refused: %v", failures)
|
|
}
|
|
}
|