Files
mesh-controller/internal/link/contracts_test.go
T
jochen 1cc6a2d759 Keep what each machine says of what it runs, raise it, and gate on it (hq ADR 0240, to-be 48 Phase A)
The gate judged a module by what the mesh saw from outside, so a container that
crash-looped after it applied passed it. Each machine's node-engine now states
the health of every long-running resource it runs; the controller keeps the
newest statement per machine, raises module.<module>.<machine>.unhealthy on the
second statement in a row, clears it on the first that does not say it, and the
gate passes a module only when every long-running resource of it is stated
healthy since the send. An engine that states nothing is judged as before.
2026-10-07 02:28:16 +02:00

85 lines
2.5 KiB
Go

package link
import (
"go/parser"
"go/token"
"io/fs"
"path/filepath"
"strings"
"testing"
"github.com/novox/mesh-controller/internal/broker"
)
// Every subject the controller consumes is a kind with a contract, and every test a contract names
// exists (novox/hq to-be 45 Phase 2, ADR 0227 rule 2).
func TestEveryConsumedKindHasAContract(t *testing.T) {
// What the controller can be handed, from the subjects it is granted and the ones it derives.
subjects := []string{EnrolSubject, BuiltSubject, ReportSubject("anchor"), AliveSubject("anchor"),
ToolsAliveSubject("anchor"), HealthSubject("anchor"), "mesh.mod.postgres.event.provisioner.failing"}
subjects = append(subjects, broker.ControllerFollows...)
kinds := map[string]bool{}
for _, s := range subjects {
kind, known := kindOfSubject(s)
if !known {
if strings.Contains(s, "*") {
continue // a pattern among the follows; its concrete subject is listed above
}
t.Errorf("the controller follows %s and has no kind for it", s)
continue
}
kinds[kind] = true
}
for kind := range kinds {
c, ok := Contracts[kind]
switch {
case !ok:
t.Errorf("the controller consumes %s and no contract says how an older one is told from a newer", kind)
case (c.Ordered == "") == (c.Unordered == ""):
t.Errorf("%s's contract says neither, or both, how it is ordered and why it need not be: %+v", kind, c)
case c.Ordered != "" && len(c.Tests) == 0:
t.Errorf("%s is ordered and no test delivers the newer and then the older", kind)
}
}
for kind := range Contracts {
if !kinds[kind] {
t.Errorf("a contract for %s, which the controller does not consume", kind)
}
}
// Every test named exists in this repository.
exists := map[string]bool{}
fset := token.NewFileSet()
err := filepath.WalkDir("../..", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() && (d.Name() == "vendor" || d.Name() == ".git") {
return filepath.SkipDir
}
if d.IsDir() || !strings.HasSuffix(path, "_test.go") {
return nil
}
f, err := parser.ParseFile(fset, path, nil, 0)
if err != nil {
return err
}
for name, obj := range f.Scope.Objects {
if obj.Kind.String() == "func" && strings.HasPrefix(name, "Test") {
exists[name] = true
}
}
return nil
})
if err != nil {
t.Fatal(err)
}
for kind, c := range Contracts {
for _, name := range c.Tests {
if !exists[name] {
t.Errorf("%s's contract names %s, which no test in this repository is", kind, name)
}
}
}
}