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

216 lines
10 KiB
Go

package main
import (
"strings"
"testing"
"git.novox.be/novox/mesh-sdk/go/asks"
sdk "git.novox.be/novox/mesh-sdk/go/delivery"
)
// **The gate asks through its bucket asks and merges through the bridge** (novox/hq ADR 0437 §6, §8, ADR 0441 §1 to §5,
// ADR 0450 §3, issue 1348). Fixture sources: the key and the id of an ask and the layout of a merge are mesh-sdk go/delivery's
// (MergeAskKey, AskID, Merge); the act an ask binds is spelled as mesh-controller cmd/mesh-controller/moved.go:98-106 verifies
// it and as the teller checks it (mesh-delivery modules/mesh-delivery-teller teller.go mergeAct, headsOf); the bridge's
// messages are the ones mesh-delivery modules/mesh-delivery-forge-bridge internal/bridge acts.go writes (RefusedMessage) and
// bridge.go merged writes, and the fake bridge here (bridge_test.go) writes them in that layout.
// AC1: a ready group writes one ask key, with today's words, and the gate calls no router.
func TestAReadyGroupWritesOneAskKeyWithTodaysWordsAndCallsNoRouter(t *testing.T) {
w := approvedGroup(t)
allGreen(w)
if w.op.count() != 1 {
t.Fatalf("%d asks written, wanted one", w.op.count())
}
key := w.op.open()[0]
kind, group, hash, err := sdk.ParseAskKey(key)
if err != nil || kind != "merge" || group != "feat/g" {
t.Fatalf("the key %q is not merge.<group>.<heads hash>: %q %q %v", key, kind, group, err)
}
ask := w.op.byKey[key]
if ask.Group != "feat/g" || len(ask.Heads) != 2 {
t.Fatalf("the ask value %+v does not name the group and both heads", ask)
}
if want, err := sdk.MergeAskKey("feat/g", ask.Heads); err != nil || want != key || !strings.HasSuffix(key, hash) {
t.Fatalf("the heads %v give the key %q, the ask is under %q (%v)", ask.Heads, want, key, err)
}
if ask.Ask.ID != sdk.AskID(key) {
t.Fatalf("the router's id of the ask is %q, wanted %q", ask.Ask.ID, sdk.AskID(key))
}
// Today's words (approval.go composeMergeAsk): the headline names the group, the details its members.
if !strings.Contains(ask.Ask.Headline, "group feat") || !strings.Contains(ask.Ask.Details, "mesh-controller#1") ||
!strings.Contains(ask.Ask.Details, "mesh-catalog#2") || len(ask.Ask.Options) != 2 || ask.Ask.Who != asks.Operator {
t.Fatalf("the ask is not today's merge ask: %+v", ask.Ask)
}
// The act the Approve binds is the one the teller checks and the controller verifies: this ask's router id, the option, and
// the heads as `owner/repo@<first 12 characters>` to the whole commit.
approve, _ := ask.Ask.Option(OptionApprove)
heads := map[string]string{IDOf(gCtl, gHc): gHc, IDOf(gCat, gHk): gHk}
want, _ := asks.ActDigest(asks.Act{"verb": "mesh-delivery.merge", "ask": sdk.AskID(key), "option": "approve",
"heads": "novox/mesh-catalog@d2d2d2d2d2d2=" + gHk + ",novox/mesh-controller@c1c1c1c1c1c1=" + gHc})
if approve.Binds != want {
t.Fatalf("the Approve binds %s, wanted the act %v of %v", approve.Binds, mergeAct(sdk.AskID(key), "approve", heads), heads)
}
if len(w.forge.calls) != 0 || len(w.merges.puts) != 0 || len(w.forge.merges) != 0 {
t.Fatalf("something was written beside the ask: forge %v, merges %d", w.forge.calls, len(w.merges.puts))
}
}
// answeredFirst is a group approved by the teller's answer, with the bridge saying nothing yet.
func answeredFirst(t *testing.T) *world {
w := approvedGroup(t)
allGreen(w)
w.merges.manual = true
w.asked = w.op.open()[0]
w.op.answer(w.asked, OptionApprove)
w.settleAll()
return w
}
// AC2: an answer naming the heads asked now hands the first member only; the bridge's merged hands the next; its refused
// hands none.
func TestAnAnswerHandsTheFirstMemberOnlyAndTheBridgesMergedHandsTheNext(t *testing.T) {
w := answeredFirst(t)
if len(w.merges.puts) != 1 {
t.Fatalf("the gate wrote %d merges, wanted the first member handed once", len(w.merges.puts))
}
first := w.merges.last("feat/g")
if len(first.Pulls) != 1 || first.Pulls[0].Repository != gCat || first.Pulls[0].State != sdk.MergeMerging || first.Pulls[0].Head.Head != gHk {
t.Fatalf("the first member is not handed alone, merging, at its head: %+v", first.Pulls)
}
if first.Approved[sdk.ApprovedKey(gCat, 2)] != gHk || first.Approved[sdk.ApprovedKey(gCtl, 1)] != gHc || len(first.Approved) != 2 {
t.Fatalf("the merge does not carry the Approve's heads of both members: %v", first.Approved)
}
if first.Ask != w.asked {
t.Fatalf("the merge names no ask: %+v", first)
}
w.settleAll()
if len(w.merges.puts) != 1 {
t.Fatalf("the next member was handed before the bridge said the first merged: %d merges written", len(w.merges.puts))
}
w.merges.say(sdk.Forge{Kind: sdk.ForgeMerged, Repository: gCat, Number: 2, Head: gHk, Merge: strings.Repeat("a", 40), Root: "r", Trees: map[string]string{"x": "t"}})
w.settleAll()
if len(w.merges.puts) != 2 {
t.Fatalf("the second member was not handed after the first merged: %d merges written", len(w.merges.puts))
}
second := w.merges.last("feat/g")
if len(second.Pulls) != 2 || second.Pulls[0].State != sdk.MergeMerged || second.Pulls[0].Commit != strings.Repeat("a", 40) ||
second.Pulls[1].Repository != gCtl || second.Pulls[1].State != sdk.MergeMerging {
t.Fatalf("the next merge is not the first merged and the second merging: %+v", second.Pulls)
}
}
// AC2: the bridge's refused hands no member, and the group is said urgent, naming the member and the forge's words.
func TestTheBridgesRefusalHandsNoMemberAndIsSaidUrgent(t *testing.T) {
w := answeredFirst(t)
w.merges.say(sdk.Forge{Kind: sdk.ForgeRefused, Repository: gCat, Number: 2, Head: gHk, Act: "merge",
Words: "the forge refused gitea_merge_pull_request: mesh/repo-check (missing)"})
w.later(time30s)
w.settleAll()
if len(w.merges.puts) != 2 {
t.Fatalf("%d merges written; wanted the first handed and then written refused, and none handed after", len(w.merges.puts))
}
refused := w.merges.last("feat/g")
if len(refused.Pulls) != 1 || refused.Pulls[0].State != sdk.MergeRefused || !strings.Contains(refused.Pulls[0].Words, "mesh/repo-check (missing)") {
t.Fatalf("the refusal is not written into the merge: %+v", refused.Pulls)
}
lines := urgentLines(w)
if len(lines) != 1 || lines[0].ID != IDOf(gCat, gHk) || !strings.Contains(lines[0].Says, "feat/g") ||
!strings.Contains(lines[0].Says, "mesh/repo-check (missing)") {
t.Fatalf("the refusal is not said as one urgent line: %+v", lines)
}
}
// ADR 0441 §2: before a member is handed, every member's required statuses are read again; one that is not a success holds
// the group, even after the first member merged.
func TestTheNextMemberIsNotHandedWhileARequiredStatusOfTheGroupIsNotASuccess(t *testing.T) {
w := answeredFirst(t)
w.merges.say(sdk.Forge{Kind: sdk.ForgeMerged, Repository: gCat, Number: 2, Head: gHk, Merge: strings.Repeat("a", 40), Root: "r", Trees: map[string]string{"x": "t"}})
setChecks(w, gCtl, gHc, "success", "") // the second member's repository check went missing meanwhile
w.settleAll()
if len(w.merges.puts) != 1 {
t.Fatalf("the second member was handed with a required status missing: %d merges written", len(w.merges.puts))
}
if lines := urgentLines(w); len(lines) != 1 || !strings.Contains(lines[0].Says, "mesh/repo-check (missing)") {
t.Fatalf("the held group is not said urgent: %+v", lines)
}
}
// AC4: once every member is merged the gate writes source.main once, and each merge is announced with the ask, its asker the
// teller, and the heads.
func TestAGroupMergedWholeMovesSourceMainOnceAndAnnouncesWithTheTeller(t *testing.T) {
w := approvedGroup(t)
allGreen(w)
w.op.answer(w.op.open()[0], OptionApprove)
w.settleAll()
w.settleAll()
if len(w.forge.merges) != 2 {
t.Fatalf("merges %v", w.forge.merges)
}
if len(w.source.moves) != 1 {
t.Fatalf("source.main moved %d times, wanted once", len(w.source.moves))
}
main := w.source.moves[0]
commit := strings.Repeat("c", 40)
if main.Group != "feat/g" || main.Heads[gCat] != commit || main.Heads[gCtl] != commit || len(main.Heads) != 2 {
t.Fatalf("source.main is %+v; wanted both repositories at their merge commits, moved by the group", main)
}
final := w.merges.last("feat/g")
if len(final.Pulls) != 2 || final.Pulls[0].State != sdk.MergeMerged || final.Pulls[1].State != sdk.MergeMerged {
t.Fatalf("the merge is not written as all merged: %+v", final.Pulls)
}
say := mergedSaid(w)
if len(say) != 2 {
t.Fatalf("announced %d merges, wanted each member's once: %v", len(say), say)
}
for _, m := range say {
if m["asker"] != sdk.TellerModule || !strings.HasPrefix(m["ask_id"].(string), "merge-feat_g-") || m["option"] != OptionApprove {
t.Fatalf("the announcement does not name the ask and its asker the teller: %v", m)
}
}
w.settleAll()
if len(w.source.moves) != 1 {
t.Fatalf("source.main moved again: %d", len(w.source.moves))
}
}
// 1106: the same refusal again and again with the trunk not moved is not asked on without end. Three refusals in a row with
// the same cause end the asking by a verdict, a status or a composed check, and say it urgent; a trunk's move, or the
// operator's Approve, starts it again.
func TestARefusalThatRepeatsWithNoTrunkMoveIsNotAskedAgainWithoutEnd(t *testing.T) {
w := answeredFirst(t)
d := w.h.deliveries[IDOf(gCat, gHk)]
refuse := func(took string) {
w.h.ForgeRefused(sdk.Forge{Kind: sdk.ForgeRefused, Repository: gCat, Number: 2, Head: gHk, Act: "merge",
Words: "novox/mesh-catalog#2 is not merged: its verdict names no trunk commit it was judged against, and main is now f70a99dbf485. " +
"The check of head d2d2d2d2d2d2 merged into trunk f70a99dbf485 was asked; the last check took " + took + ". Ask again once both statuses pass"})
}
tried := w.now
for i, took := range []string{"4m1s", "4m9s"} {
refuse(took)
d.Ready.LastTry = tried
w.h.mergeAgainNow(d)
if !d.Ready.LastTry.IsZero() {
t.Fatalf("refusal %d: the merge was not asked again at once on a verdict, as after a first refusal", i+1)
}
}
refuse("4m5s") // the third in a row, with only the check's duration changed
if d.Ready.Repeats != 3 {
t.Fatalf("%d refusals counted for the same cause, wanted 3", d.Ready.Repeats)
}
d.Ready.LastTry = tried
w.h.mergeAgainNow(d) // the composed check or a verdict answers: nothing is asked
if d.Ready.LastTry.IsZero() {
t.Fatal("the merge was asked again after the same refusal three times with the trunk not moved")
}
if lines := urgentLines(w); len(lines) != 1 || !strings.Contains(lines[0].Says, "names no trunk commit") {
t.Fatalf("the repeated refusal is not said urgent: %+v", lines)
}
// The trunk moves: the merge is asked again.
d.TrunkNow = "a1a1a1a1a1a1a1a1"
w.h.mergeAgainNow(d)
if !d.Ready.LastTry.IsZero() {
t.Fatal("a trunk's move did not let the merge be asked again")
}
}