The group is read from the gate's merges state the bridge already reads; the end comes only once every approved member is merged and its merged message written, never on a refusal, with a message id the stream's duplicate window holds to one. Claude-Session: https://claude.ai/code/session_01JG73KG7AfSdh6FP8VrBqCn
95 lines
4.4 KiB
Go
95 lines
4.4 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"log"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/delivery"
|
|
)
|
|
|
|
// idPub records the subjects, bodies and message ids it is handed.
|
|
type idPub struct {
|
|
subjects, ids []string
|
|
data [][]byte
|
|
}
|
|
|
|
func (p *idPub) send(_ context.Context, subject string, data []byte, id string) error {
|
|
p.subjects, p.data, p.ids = append(p.subjects, subject), append(p.data, data), append(p.ids, id)
|
|
return nil
|
|
}
|
|
|
|
func TestAGroupsMembersNameItAndItsEndIsSaidOnce(t *testing.T) { // 1585/T16, T17 (ADR 0633 §4)
|
|
ctx := context.Background()
|
|
h1, h2 := strings.Repeat("1", 40), strings.Repeat("2", 40)
|
|
c1, c2 := strings.Repeat("c", 40), strings.Repeat("d", 40)
|
|
merge := func(states ...delivery.MergePull) []byte {
|
|
v, err := delivery.EncodeMerge(delivery.Merge{Group: "feat/take-in", Ask: "ask", Pulls: states,
|
|
Approved: map[string]string{"novox/mesh-sdk@37": h1, "novox/mesh-delivery@9": h2}})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return v
|
|
}
|
|
sdk := func(state delivery.MergeState, commit, words string) delivery.MergePull {
|
|
return delivery.MergePull{Head: delivery.Head{Repository: "novox/mesh-sdk", Number: 37, Head: h1}, State: state, Commit: commit, Words: words}
|
|
}
|
|
dlv := func(state delivery.MergeState, commit string) delivery.MergePull {
|
|
return delivery.MergePull{Head: delivery.Head{Repository: "novox/mesh-delivery", Number: 9, Head: h2}, State: state, Commit: commit}
|
|
}
|
|
gate := map[string][]byte{}
|
|
var logs bytes.Buffer
|
|
pub, done := &idPub{}, memRevisions{}
|
|
g := &grouper{send: pub.send, done: done, logger: log.New(&logs, "", 0),
|
|
merges: func(_ context.Context, key string) ([]byte, uint64, bool, error) {
|
|
v, ok := gate[key]
|
|
return v, 7, ok, nil
|
|
}}
|
|
mergedOf := func(repo string, number int, head, commit string) []byte {
|
|
data, err := delivery.EncodeForge(delivery.Forge{Kind: delivery.ForgeMerged, Repository: repo, Branch: "feat/take-in", Number: number, Head: head, Merge: commit, Root: "r"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return data
|
|
}
|
|
// A lone pull request's merged message carries no group.
|
|
if _, f, ok := g.member(ctx, mergedOf("novox/hq", 4, h1, c1)); ok || f.Group != "" {
|
|
t.Errorf("a lone pull request names the group %q", f.Group)
|
|
}
|
|
// The first member: its message names the group and both members; the group is not ended.
|
|
gate["feat/take-in"] = merge(sdk(delivery.MergeMerged, c1, ""), dlv(delivery.MergeMerging, ""))
|
|
data, f, ok := g.member(ctx, mergedOf("novox/mesh-sdk", 37, h1, c1))
|
|
if back, err := delivery.DecodeForge(data); !ok || err != nil || back.Group != "feat/take-in" || len(back.Members) != 2 {
|
|
t.Fatalf("a member's message: %+v %v", back, err)
|
|
}
|
|
g.published(ctx, f)
|
|
// A refusal of the other member is no end (ADR 0441 §3: the group merges on from it later).
|
|
gate["feat/take-in"] = merge(sdk(delivery.MergeMerged, c1, ""), delivery.MergePull{Head: delivery.Head{Repository: "novox/mesh-delivery", Number: 9, Head: h2}, State: delivery.MergeRefused, Words: "405"})
|
|
g.ended(ctx, "feat/take-in", 8, gate["feat/take-in"])
|
|
// Merged on at the gate before its merged message is written: still no end; then its message ends the group.
|
|
gate["feat/take-in"] = merge(sdk(delivery.MergeMerged, c1, ""), dlv(delivery.MergeMerged, c2))
|
|
g.ended(ctx, "feat/take-in", 9, gate["feat/take-in"])
|
|
if len(pub.subjects) != 0 {
|
|
t.Fatalf("an end before every member's merged message is written: %v", pub.subjects)
|
|
}
|
|
_, f, _ = g.member(ctx, mergedOf("novox/mesh-delivery", 9, h2, c2))
|
|
g.published(ctx, f)
|
|
g.ended(ctx, "feat/take-in", 10, gate["feat/take-in"]) // a later write of the gate says no second end
|
|
if len(pub.subjects) != 1 || pub.subjects[0] != "mesh.stream.mesh-delivery-forge-bridge.event.forge.group.merged" || pub.ids[0] != "group-end.feat/take-in."+c2 {
|
|
t.Fatalf("ends %v ids %v, want one forge.group.merged with id group-end.feat/take-in.%s", pub.subjects, pub.ids, c2)
|
|
}
|
|
end, err := delivery.DecodeForgeGroupEnd(pub.data[0])
|
|
if err != nil || len(end.Members) != 2 || end.Members[0].Merge != c1 || end.Members[1].Merge != c2 {
|
|
t.Errorf("the end = %+v %v, want both members in their merge order", end, err)
|
|
}
|
|
if rev, ok := done[groupsKey("feat/take-in")]; !ok || rev != 7 {
|
|
t.Errorf("performed %v: want groups.feat/take-in kept", done)
|
|
}
|
|
// Again by hand: written once more, its id not the first's.
|
|
if !g.end(ctx, "feat/take-in", 10, gate["feat/take-in"], "20261104T2300") || pub.ids[1] == pub.ids[0] {
|
|
t.Errorf("the end again by hand: ids %v", pub.ids)
|
|
}
|
|
}
|