214 lines
8.6 KiB
Go
214 lines
8.6 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"log"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
|
|
seat "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery"
|
|
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-forge-bridge/forgemsg"
|
|
)
|
|
|
|
const (
|
|
headA = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
|
headB = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
|
|
)
|
|
|
|
type memRevisions map[string]uint64
|
|
|
|
func (m memRevisions) Get(_ context.Context, key string) (uint64, bool, error) {
|
|
r, ok := m[key]
|
|
return r, ok, nil
|
|
}
|
|
func (m memRevisions) Put(_ context.Context, key string, rev uint64) error { m[key] = rev; return nil }
|
|
|
|
type tcall struct {
|
|
key string
|
|
body string
|
|
}
|
|
|
|
// forgeFake records the calls and answers each from refuse, a map from a tool to its refusal.
|
|
type forgeFake struct {
|
|
calls []tcall
|
|
refuse map[string]string
|
|
}
|
|
|
|
func (f *forgeFake) call(_ context.Context, key string, body any) (json.RawMessage, error) {
|
|
b, _ := json.Marshal(body)
|
|
f.calls = append(f.calls, tcall{key, string(b)})
|
|
if w, ok := f.refuse[key]; ok {
|
|
return nil, errors.New(w)
|
|
}
|
|
return json.RawMessage(`{}`), nil
|
|
}
|
|
|
|
func newPerformer(f *forgeFake) (*performer, *fakePub, memRevisions, *bytes.Buffer) {
|
|
pub, done, logs := &fakePub{}, memRevisions{}, &bytes.Buffer{}
|
|
return &performer{call: f.call, pub: pub, done: done, logger: log.New(logs, "", 0)}, pub, done, logs
|
|
}
|
|
|
|
// The values are the SDK's own encoding (mesh-sdk go/delivery EncodeOwed, EncodeMerge), the keys its OwedKey.
|
|
func owedKV(t *testing.T, repository string, number int, act string, o seat.OwedByRepositoryNumberHeadActValue) (string, []byte) {
|
|
t.Helper()
|
|
key, err := meshdeliveryclient.OwedKey(repository, number, headA, act)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
v, err := meshdeliveryclient.EncodeOwed(o)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return key, v
|
|
}
|
|
|
|
func TestAnOwedStatusIsSetOnceForARevision(t *testing.T) { // 1341/AC1
|
|
t.Run("the names are the manifest's", theNamesAreTheManifests)
|
|
f := &forgeFake{}
|
|
p, _, _, _ := newPerformer(f)
|
|
key, v := owedKV(t, "novox/mesh-delivery", 12, "status=mesh/merge-gate",
|
|
seat.OwedByRepositoryNumberHeadActValue{Act: meshdeliveryclient.OwedStatus, Context: "mesh/merge-gate", State: "success", Text: "all green", Target: "https://forge.example/p/12"})
|
|
p.owed(context.Background(), key, 7, v)
|
|
if len(f.calls) != 1 || f.calls[0].key != "gitea.gitea_commit_status" ||
|
|
f.calls[0].body != `{"context":"mesh/merge-gate","description":"all green","owner":"novox","repo":"mesh-delivery","sha":"`+headA+`","state":"success","target_url":"https://forge.example/p/12"}` {
|
|
t.Fatalf("calls = %+v", f.calls)
|
|
}
|
|
p.owed(context.Background(), key, 7, v) // the same revision seen again (a restart's replay)
|
|
if len(f.calls) != 1 {
|
|
t.Errorf("the same revision called the tool again: %+v", f.calls)
|
|
}
|
|
p.owed(context.Background(), key, 8, v) // a new revision of the key is a new act
|
|
if len(f.calls) != 2 {
|
|
t.Errorf("a new revision was not performed: %+v", f.calls)
|
|
}
|
|
}
|
|
|
|
func TestViewNoteAndCommentEachCallTheirToolOnce(t *testing.T) { // 1341/AC2
|
|
f := &forgeFake{}
|
|
p, _, _, _ := newPerformer(f)
|
|
for _, c := range []struct {
|
|
act string
|
|
o seat.OwedByRepositoryNumberHeadActValue
|
|
}{
|
|
{"view", seat.OwedByRepositoryNumberHeadActValue{Act: meshdeliveryclient.OwedView, Text: "the view"}},
|
|
{"note", seat.OwedByRepositoryNumberHeadActValue{Act: meshdeliveryclient.OwedNote, Text: "a line", Ref: "mesh-plan"}},
|
|
{"comment.1", seat.OwedByRepositoryNumberHeadActValue{Act: meshdeliveryclient.OwedComment, Text: "hello"}},
|
|
} {
|
|
key, v := owedKV(t, "novox/mesh-delivery", 12, c.act, c.o)
|
|
p.owed(context.Background(), key, 1, v)
|
|
p.owed(context.Background(), key, 1, v)
|
|
}
|
|
var tools []string
|
|
for _, c := range f.calls {
|
|
tools = append(tools, c.key)
|
|
}
|
|
if got := strings.Join(tools, " "); got != "gitea.gitea_delivery_view gitea.gitea_note_append gitea.gitea_add_comment" {
|
|
t.Errorf("calls = %s", got)
|
|
}
|
|
}
|
|
|
|
func TestARefusedActIsWrittenOnForgeOnceAndNotRetried(t *testing.T) { // 1341/AC3
|
|
f := &forgeFake{refuse: map[string]string{"gitea.gitea_add_comment": "the pull request is locked"}}
|
|
p, pub, _, _ := newPerformer(f)
|
|
key, v := owedKV(t, "novox/mesh-delivery", 12, "comment.1", seat.OwedByRepositoryNumberHeadActValue{Act: meshdeliveryclient.OwedComment, Text: "hello"})
|
|
p.owed(context.Background(), key, 3, v)
|
|
p.owed(context.Background(), key, 3, v)
|
|
if len(f.calls) != 1 {
|
|
t.Errorf("the refused act was tried %d times at one revision", len(f.calls))
|
|
}
|
|
if len(pub.subjects) != 1 || pub.subjects[0] != "mesh.stream.mesh-delivery-forge-bridge.event.forge.novox.mesh-delivery.refused" {
|
|
t.Fatalf("published on %v", pub.subjects)
|
|
}
|
|
m, err := forgemsg.DecodeForge(pub.data[0])
|
|
if err != nil || m.Kind != forgemsg.ForgeRefused || m.Act != "comment.1" || m.Number != 12 || m.Head != headA || m.Words != "the pull request is locked" {
|
|
t.Errorf("message %+v err %v", m, err)
|
|
}
|
|
f.refuse = nil
|
|
p.owed(context.Background(), key, 4, v) // only a new revision tries again
|
|
if len(f.calls) != 2 {
|
|
t.Errorf("a new revision was not tried: %d calls", len(f.calls))
|
|
}
|
|
}
|
|
|
|
func TestAnOwedValueThatCannotBeReadIsLoggedAndNotRetried(t *testing.T) {
|
|
f := &forgeFake{}
|
|
p, pub, done, logs := newPerformer(f)
|
|
p.owed(context.Background(), "not-a-key", 2, []byte(`{}`))
|
|
if len(f.calls) != 0 || len(pub.subjects) != 0 {
|
|
t.Errorf("calls %v published %v", f.calls, pub.subjects)
|
|
}
|
|
if !strings.Contains(logs.String(), "not-a-key") {
|
|
t.Errorf("log %q does not name the key", logs.String())
|
|
}
|
|
if rev, ok := done["owed.not-a-key"]; !ok || rev != 2 {
|
|
t.Errorf("revision kept = %v %v", rev, ok)
|
|
}
|
|
}
|
|
|
|
func mergeKV(t *testing.T, m seat.MergesByGroupValue) []byte {
|
|
t.Helper()
|
|
v, err := meshdeliveryclient.EncodeMerge(m)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return v
|
|
}
|
|
|
|
func twoMembers(a, b meshdeliveryclient.MergeState) seat.MergesByGroupValue {
|
|
return seat.MergesByGroupValue{Group: "g", Ask: "merge.g.0123456789ab",
|
|
Approved: map[string]string{meshdeliveryclient.ApprovedKey("novox/mesh-sdk", 4): headB, meshdeliveryclient.ApprovedKey("novox/mesh-delivery", 12): headA},
|
|
Pulls: []seat.MergesByGroupValuePullsItem{
|
|
{Repository: "novox/mesh-delivery", Number: 12, Head: headA, State: string(a), Commit: map[bool]string{true: "c1"}[a == meshdeliveryclient.MergeMerged]},
|
|
{Repository: "novox/mesh-sdk", Number: 4, Head: headB, State: string(b), Commit: map[bool]string{true: "c2"}[b == meshdeliveryclient.MergeMerged]},
|
|
}}
|
|
}
|
|
|
|
func TestAMergeRevisionMergesOnlyTheMemberHanded(t *testing.T) { // 1342/AC1
|
|
f := &forgeFake{}
|
|
p, _, _, _ := newPerformer(f)
|
|
m := twoMembers(meshdeliveryclient.MergeMerging, meshdeliveryclient.MergeMerged)
|
|
p.merges(context.Background(), "g", 5, mergeKV(t, m))
|
|
want := `{"approved_heads":["novox/mesh-delivery@` + headA + `","novox/mesh-sdk@` + headB + `"],"head":"` + headA + `","number":12,"owner":"novox","repo":"mesh-delivery"}`
|
|
if len(f.calls) != 1 || f.calls[0].key != "gitea.gitea_merge_pull_request" || f.calls[0].body != want {
|
|
t.Fatalf("calls = %+v", f.calls)
|
|
}
|
|
p.merges(context.Background(), "g", 5, mergeKV(t, m))
|
|
if len(f.calls) != 1 {
|
|
t.Errorf("a revision merged twice: %+v", f.calls)
|
|
}
|
|
}
|
|
|
|
func TestARefusedMergeIsWrittenOnForgeOnceAndNotRetried(t *testing.T) { // 1342/AC2
|
|
f := &forgeFake{refuse: map[string]string{"gitea.gitea_merge_pull_request": "novox/mesh-delivery#12 is not merged: its head moved"}}
|
|
p, pub, _, _ := newPerformer(f)
|
|
v := mergeKV(t, twoMembers(meshdeliveryclient.MergeMerging, meshdeliveryclient.MergeMerged))
|
|
p.merges(context.Background(), "g", 5, v)
|
|
p.merges(context.Background(), "g", 5, v)
|
|
if len(f.calls) != 1 || len(pub.data) != 1 {
|
|
t.Fatalf("calls %d messages %d, want one each", len(f.calls), len(pub.data))
|
|
}
|
|
m, err := forgemsg.DecodeForge(pub.data[0])
|
|
if err != nil || m.Kind != forgemsg.ForgeRefused || m.Repository != "novox/mesh-delivery" || m.Number != 12 || m.Head != headA ||
|
|
m.Act != "merge" || m.Words != "novox/mesh-delivery#12 is not merged: its head moved" {
|
|
t.Errorf("message %+v err %v", m, err)
|
|
}
|
|
f.refuse = nil
|
|
p.merges(context.Background(), "g", 6, v)
|
|
if len(f.calls) != 2 {
|
|
t.Errorf("a new revision was not tried: %d calls", len(f.calls))
|
|
}
|
|
}
|
|
|
|
func TestAMergeWithTwoHandedStopsAtTheFirstRefusal(t *testing.T) { // ADR 0441 §3
|
|
f := &forgeFake{refuse: map[string]string{"gitea.gitea_merge_pull_request": "no"}}
|
|
p, pub, _, _ := newPerformer(f)
|
|
p.merges(context.Background(), "g", 1, mergeKV(t, twoMembers(meshdeliveryclient.MergeMerging, meshdeliveryclient.MergeMerging)))
|
|
if len(f.calls) != 1 || len(pub.data) != 1 {
|
|
t.Errorf("calls %d messages %d, want the first refusal to stop the pass", len(f.calls), len(pub.data))
|
|
}
|
|
}
|