Files
mesh-catalog/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/issue1349_test.go
T

197 lines
7.5 KiB
Go

package main
import (
"encoding/json"
"go/ast"
"go/parser"
"go/token"
"path/filepath"
"slices"
"strconv"
"strings"
"sync"
"testing"
sdk "git.novox.be/novox/mesh-sdk/go/delivery"
)
// **The gate writes nothing to the forge: its statuses, views, notes and comments are owed keys, and its plan is read from
// `plans`** (novox/hq ADR 0437 §2, §8, ADR 0450 §2, §4, §7, issue 1349; the empty check answer is the mesh-delivery half of
// issue 1311). Fixture sources: the key and the value of an owed act are mesh-sdk go/delivery's (OwedKey, Owed); the arguments
// of `plans` are mesh-controller cmd/mesh-controller/seatverbs.go's (repository, paths, module-dirs, head, base, removed, data)
// and its answer the document of deliveryPlanDocument (plan, reach, mesh, gated).
// AC1: a transition that today sets a status, a view, a note or a comment writes the matching owed key.
func TestATransitionWritesItsStatusViewAndNoteAsOwedKeys(t *testing.T) {
w := newWorld(t)
id := IDOf("novox/app", head)
w.h.PullUpdated(pr("novox/app", 7, head, "feat/x", "src/a.go"))
w.settleAll()
page := "https://forge.invalid/novox/app/pulls/7"
statusKey, _ := sdk.OwedKey("novox/app", 7, head, "status=mesh/delivery")
viewKey, _ := sdk.OwedKey("novox/app", 7, head, "view")
noteKey, _ := sdk.OwedKey("novox/app", 7, head, "note")
for _, key := range []string{statusKey, viewKey, noteKey} {
if _, there := w.owed.keys[key]; !there {
t.Fatalf("the key %s was not written; the keys are %v", key, w.owed.writes)
}
}
if s := w.owed.keys[statusKey]; s.Act != sdk.OwedStatus || s.Context != "mesh/delivery" || s.State != "pending" || s.Target != page || s.Text == "" {
t.Fatalf("the owed status is %+v; wanted mesh/delivery pending, pointing at %s", s, page)
}
if v := w.owed.keys[viewKey]; v.Act != sdk.OwedView || !strings.Contains(v.Text, id) {
t.Fatalf("the owed view is %+v", v)
}
if n := w.owed.keys[noteKey]; n.Act != sdk.OwedNote || n.Ref != "mesh-plan" || !strings.Contains(n.Text, "proposed") {
t.Fatalf("the owed note is %+v", n)
}
// What the bridge would perform is what the forge holds.
if len(w.forge.notesOn(head)) == 0 || !strings.HasPrefix(statusOn(w, "novox/app", head), "pending") {
t.Fatalf("the fake bridge did not perform what was owed: %v", w.forge.notesOn(head))
}
}
// AC1: a note on the merge commit, which no pull request names, is keyed with the number 0.
func TestANoteOnTheMergeCommitIsOwedWithNumberZero(t *testing.T) {
w := newWorld(t)
w.h.PullUpdated(pr("novox/app", 7, head, "feat/x", "src/a.go"))
w.h.Checked(verdict("novox/app", 7, head, "pass"))
w.h.PullMerged(merged("novox/app", 7, head, merge))
w.h.WalkMoved(w.ctl.put(aWalk("plan-1", "novox/app", merge, true)))
w.settleAll()
key, _ := sdk.OwedKey("novox/app", 0, merge, "note")
if n, there := w.owed.keys[key]; !there || n.Act != sdk.OwedNote {
t.Fatalf("no note was owed on the merge commit under %s; the keys are %v", key, w.owed.writes)
}
}
// AC1: two comments on one head are two acts: the sequence number is in the act.
func TestTwoCommentsOnOneHeadAreTwoOwedKeys(t *testing.T) {
w := newWorld(t)
id := IDOf("novox/app", head)
w.h.PullUpdated(pr("novox/app", 7, head, "feat/x", "src/a.go"))
w.h.mu.Lock()
d := w.h.deliveries[id]
w.h.sayOn(d, "the first thing said")
w.h.sayOn(d, "the second thing said")
w.h.mu.Unlock()
w.settleAll()
one, _ := sdk.OwedKey("novox/app", 7, head, "comment.1")
two, _ := sdk.OwedKey("novox/app", 7, head, "comment.2")
if w.owed.keys[one].Text != "the first thing said" || w.owed.keys[two].Text != "the second thing said" {
t.Fatalf("the comments are %+v and %+v", w.owed.keys[one], w.owed.keys[two])
}
if len(w.forge.comments) != 2 {
t.Fatalf("the bridge would say %d comments: %v", len(w.forge.comments), w.forge.comments)
}
}
// AC2: the gate's code calls no gitea write tool and no `delivery-plan`, and no controller verb beyond the ones its manifest
// names (ADR 0450 §7). Tests are left out: they name the words to prove them gone.
func TestTheGatesCodeCallsNoForgeWriteToolAndNoDeliveryPlan(t *testing.T) {
files, err := filepath.Glob("*.go")
if err != nil {
t.Fatal(err)
}
forbidden := []string{"delivery-plan", "gitea_commit_status\"", "gitea_delivery_view", "gitea_note_append", "gitea_add_comment",
"gitea_merge_pull_request\"", "gitea_open_pulls_write"}
// The controller's verbs the gate calls: ADR 0450 §7, less the checks and the queue, which the controller no longer
// keeps (issue 1318, ADR 0524 §5).
allowed := []string{"deliver", "delivery-stop", "delivery-backups-again", "delivery-walks", "plans"}
called := map[string]bool{}
fset := token.NewFileSet()
for _, f := range files {
if strings.HasSuffix(f, "_test.go") {
continue
}
file, err := parser.ParseFile(fset, f, nil, 0)
if err != nil {
t.Fatal(err)
}
ast.Inspect(file, func(n ast.Node) bool {
switch x := n.(type) {
case *ast.BasicLit:
if x.Kind == token.STRING {
for _, word := range forbidden {
if strings.Contains(x.Value, word) {
t.Errorf("%s holds the string %s", f, x.Value)
}
}
}
case *ast.CallExpr:
if sel, ok := x.Fun.(*ast.SelectorExpr); ok && sel.Sel.Name == "verb" && len(x.Args) > 0 {
if lit, ok := x.Args[0].(*ast.BasicLit); ok {
verb, _ := strconv.Unquote(lit.Value)
called[verb] = true
if !slices.Contains(allowed, verb) {
t.Errorf("%s calls the controller's verb %q", f, verb)
}
}
}
}
return true
})
}
for _, verb := range []string{"plans"} {
if !called[verb] {
t.Errorf("the gate calls no %s: the test reads nothing", verb)
}
}
}
// stubController is a controller that answers every verb with one document, and keeps what it was asked.
type stubController struct {
mu sync.Mutex
keys []string
bodies []map[string]any
answer string
}
func (s *stubController) ask(key string, body any) (json.RawMessage, error) {
s.mu.Lock()
defer s.mu.Unlock()
s.keys = append(s.keys, key)
b, _ := body.(map[string]any)
s.bodies = append(s.bodies, b)
return json.RawMessage(s.answer), nil
}
func (s *stubController) called(key string) []map[string]any {
s.mu.Lock()
defer s.mu.Unlock()
var out []map[string]any
for i, k := range s.keys {
if k == key {
out = append(out, s.bodies[i])
}
}
return out
}
// AC3: a delivery with no plan gets it from `plans`, asked as data, with the change's diffset.
func TestADeliveryWithNoPlanGetsItFromPlansAsData(t *testing.T) {
w := newWorld(t)
stub := &stubController{answer: `{"ok":true,"output":"","answer":{"asked":"check-1","held":true,"walks":[],
"plan":{"repository":"novox/app","base":"main","head":"` + head + `","moved":["app"],"summary":"builds app, sent to the node"}}}`}
w.h.Controller = seatController{ask: stub.ask}
w.h.PullUpdated(pr("novox/app", 7, head, "feat/x", "src/a.go"))
c := verdict("novox/app", 7, head, "pass")
c.Plan = nil
w.h.Checked(c)
w.settleAll()
asked := stub.called("seat:mesh-controller.plans")
if len(asked) == 0 {
t.Fatalf("`plans` was not asked; the controller was asked %v", stub.keys)
}
if len(stub.called("seat:mesh-controller.delivery-plan")) != 0 {
t.Fatal("delivery-plan was asked")
}
got := asked[0]
if got["repository"] != "novox/app" || got["paths"] != "src/a.go" || got["data"] != "true" || got["head"] != head || got["base"] != "main" {
t.Fatalf("plans was asked %v; wanted the repository, the paths, the head, the base and data", got)
}
if d := w.delivery(IDOf("novox/app", head)); d.Plan == nil || d.Plan.Summary != "builds app, sent to the node" {
t.Fatalf("the delivery's plan is %+v", d.Plan)
}
}