Files
mesh-sdk/go/delivery/source_test.go
T
jschoubben 4580d80a32
mesh/repo-check head 4580d80a32d9 merged into trunk 2db8ef5079f5 beside mesh-controller#400@a0c971ce — pass: its merge-check.sh passed, each part in its to…
mesh/merge-gate head 4580d80a32d9 merged into trunk 2db8ef5079f5 — pass: group fix/1279-consumers-from-now composed: every machine composes with the change…
mesh/delivery delivered
mesh/delivery-group group fix/1279-consumers-from-now delivered: every member is delivered
Add the forge kind open: every open pull request into trunk, so the indexer can drop the rest (issue 1279, ADR 0443)
2026-10-19 21:50:03 +02:00

377 lines
13 KiB
Go

package delivery
import (
"encoding/json"
"errors"
"reflect"
"strings"
"testing"
)
func TestSourceKeys(t *testing.T) { // 1200/AC1
if k, err := CandidatesKey("feat/delivery-source-keys"); err != nil || k != "feat/delivery-source-keys" {
t.Fatalf("candidates key = %q, %v", k, err)
}
if g, err := ParseCandidatesKey("feat/x"); err != nil || g != "feat/x" {
t.Fatalf("parse candidates = %q, %v", g, err)
}
if k, err := ModulesKey("mesh-mail"); err != nil || k != "mesh-mail" {
t.Fatalf("modules key = %q, %v", k, err)
}
if m, err := ParseModulesKey("mesh-mail"); err != nil || m != "mesh-mail" {
t.Fatalf("parse modules = %q, %v", m, err)
}
for _, bad := range []string{"", "a.b", "a*b", "a>b", "a b", "a\tb"} {
if _, err := CandidatesKey(bad); err == nil {
t.Errorf("candidates key %q accepted", bad)
}
if _, err := ModulesKey(bad); err == nil {
t.Errorf("modules key %q accepted", bad)
}
if _, err := ParseCandidatesKey(bad); err == nil {
t.Errorf("parse candidates %q accepted", bad)
}
if _, err := ParseModulesKey(bad); err == nil {
t.Errorf("parse modules %q accepted", bad)
}
}
ev, err := ForgeEvent("novox/mesh-sdk", ForgeMerged)
if err != nil || ev != "forge.novox.mesh-sdk.merged" {
t.Fatalf("forge event = %q, %v", ev, err)
}
repo, kind, err := ParseForgeEvent(ev)
if err != nil || repo != "novox/mesh-sdk" || kind != ForgeMerged {
t.Fatalf("parse forge event = %q %q %v", repo, kind, err)
}
for _, k := range []string{ForgePull, ForgeMerged, ForgeRefused} {
if _, err := ForgeEvent("a/b", k); err != nil {
t.Errorf("kind %s refused: %v", k, err)
}
}
for _, bad := range []struct{ repo, kind string }{
{"", "pull"}, {"novox", "pull"}, {"a/b/c", "pull"}, {"/b", "pull"}, {"a/", "pull"},
{"a.b/c", "pull"}, {"a/b c", "pull"}, {"a/b", "closed"}, {"a/b", ""},
} {
if _, err := ForgeEvent(bad.repo, bad.kind); err == nil {
t.Errorf("forge event %q %q accepted", bad.repo, bad.kind)
} else if bad.kind == "closed" && !strings.Contains(err.Error(), "closed") {
t.Errorf("error does not name the kind: %v", err)
}
}
for _, bad := range []string{"", "forge.a.b", "forge.a.b.closed", "x.a.b.pull", "forge.a.b.pull.x", "forge..b.pull"} {
if _, _, err := ParseForgeEvent(bad); err == nil {
t.Errorf("parse forge event %q accepted", bad)
}
}
}
func TestForgeOpenKind(t *testing.T) { // 1279/AC5
ev := OpenEvent()
if ev != "forge.open" {
t.Fatalf("open event = %q", ev)
}
repo, kind, err := ParseForgeEvent(ev)
if err != nil || repo != "" || kind != ForgeOpen {
t.Fatalf("parse open event = %q %q %v", repo, kind, err)
}
// An empty list is a message too: nothing is open.
data, err := EncodeForge(Forge{Kind: ForgeOpen, Open: []OpenPull{}})
if err != nil {
t.Fatalf("empty open: %v", err)
}
if f, err := DecodeForge(data); err != nil || f.Kind != ForgeOpen || len(f.Open) != 0 {
t.Errorf("empty open round trip: %+v %v", f, err)
}
for name, f := range map[string]Forge{
"a repository": {Kind: ForgeOpen, Repository: "a/b", Open: []OpenPull{{Repository: "a/b", Number: 1}}},
"a bad entry": {Kind: ForgeOpen, Open: []OpenPull{{Repository: "a", Number: 1}}},
"a zero number": {Kind: ForgeOpen, Open: []OpenPull{{Repository: "a/b"}}},
"a head": {Kind: ForgeOpen, Head: "x", Open: []OpenPull{{Repository: "a/b", Number: 1}}},
} {
if _, err := EncodeForge(f); err == nil {
t.Errorf("open with %s accepted", name)
}
}
if _, err := EncodeForge(Forge{Kind: ForgePull, Repository: "a/b", Number: 1, Head: "h", Action: "updated", Open: []OpenPull{{Repository: "a/b", Number: 1}}}); err == nil {
t.Error("a pull message naming open pull requests accepted")
}
if _, err := ForgeEvent("a/b", ForgeOpen); err == nil {
t.Error("an open event for a repository accepted")
}
}
func TestHeadsHash(t *testing.T) { // 1200/AC2
a := Head{Repository: "novox/mesh-sdk", Number: 36, Head: "aaaa"}
b := Head{Repository: "novox/mesh-control", Number: 7, Head: "bbbb"}
h1, err := HeadsHash([]Head{a, b})
if err != nil {
t.Fatal(err)
}
h2, _ := HeadsHash([]Head{b, a})
if h1 != h2 {
t.Errorf("order changes the hash: %s %s", h1, h2)
}
if len(h1) != 12 || strings.Trim(h1, "0123456789abcdef") != "" {
t.Errorf("hash %q is not 12 hex digits", h1)
}
for _, changed := range []Head{
{Repository: "novox/mesh-sdk", Number: 36, Head: "aaab"},
{Repository: "novox/mesh-sdk", Number: 37, Head: "aaaa"},
{Repository: "novox/other", Number: 36, Head: "aaaa"},
} {
h3, _ := HeadsHash([]Head{changed, b})
if h3 == h1 {
t.Errorf("changing %v keeps the hash", changed)
}
}
if _, err := HeadsHash(nil); err == nil {
t.Error("an empty list is accepted")
}
}
func sampleSource() Source {
return Source{Group: "feat/x", Heads: map[string]string{"novox/mesh-sdk": "abc123", "novox/mesh-control": "def456"}}
}
func sampleCandidate() Candidate {
pulls := []CandidatePull{
{Head: Head{Repository: "novox/mesh-sdk", Number: 36, Head: "aaaa"}, Modules: []string{"mesh-mail"}},
{Head: Head{Repository: "novox/mesh-control", Number: 7, Head: "bbbb"}, Modules: []string{"mesh-controller"}},
}
hh, _ := HeadsHash([]Head{pulls[0].Head, pulls[1].Head})
return Candidate{Group: "feat/x", Base: 12, HeadsHash: hh, Pulls: pulls}
}
func sampleForges() []Forge {
return []Forge{
{Kind: ForgePull, Repository: "novox/mesh-sdk", Branch: "feat/x", Number: 36, Head: "aaaa", Action: "opened"},
{Kind: ForgeMerged, Repository: "novox/mesh-sdk", Number: 36, Head: "aaaa", Merge: "cccc",
Trees: map[string]string{"modules/a": "t1"}, Root: "t0"},
{Kind: ForgeRefused, Repository: "novox/mesh-sdk", Act: "novox/mesh-sdk.36.aaaa.view", Words: "403 forbidden"},
{Kind: ForgeOpen, Open: []OpenPull{{Repository: "novox/mesh-sdk", Number: 36}, {Repository: "novox/mesh-control", Number: 7}}},
}
}
func TestSourceValuesRoundTrip(t *testing.T) { // 1200/AC3
check := func(name string, data []byte, err error) {
t.Helper()
if err != nil {
t.Fatalf("%s: %v", name, err)
}
var probe map[string]any
if err := json.Unmarshal(data, &probe); err != nil || probe["v"] != float64(1) {
t.Fatalf("%s: json %s does not carry v:1", name, data)
}
}
s := sampleSource()
data, err := EncodeSource(s)
check("source", data, err)
s.V = Version
if got, err := DecodeSource(data); err != nil || !SameSource(got, s) {
t.Errorf("source round trip: %+v %v", got, err)
}
c := sampleCandidate()
data, err = EncodeCandidate(c)
check("candidate", data, err)
c.V = Version
if got, err := DecodeCandidate(data); err != nil || !SameCandidate(got, c) {
t.Errorf("candidate round trip: %+v %v", got, err)
}
for _, m := range []Module{
{FP: "src1:abc", Src: 5, Repository: "novox/mesh-sdk", Commit: "cccc"},
{None: "a build that resolves packages when built", Repository: "novox/mesh-sdk", Commit: "cccc"},
} {
data, err = EncodeModule(m)
check("module", data, err)
m.V = Version
if got, err := DecodeModule(data); err != nil || !SameModule(got, m) {
t.Errorf("module round trip: %+v %v", got, err)
}
}
for _, f := range sampleForges() {
data, err = EncodeForge(f)
check("forge "+f.Kind, data, err)
f.V = Version
if got, err := DecodeForge(data); err != nil || !SameForge(got, f) {
t.Errorf("forge %s round trip: %+v %v", f.Kind, got, err)
}
}
for name, dec := range map[string]func([]byte) error{
"source": func(b []byte) error { _, err := DecodeSource(b); return err },
"candidate": func(b []byte) error { _, err := DecodeCandidate(b); return err },
"module": func(b []byte) error { _, err := DecodeModule(b); return err },
"forge": func(b []byte) error { _, err := DecodeForge(b); return err },
} {
for _, in := range []string{`{"group":"g"}`, `{"v":2,"group":"g"}`} {
if err := dec([]byte(in)); !errors.Is(err, ErrUnknownVersion) {
t.Errorf("%s decode of %s = %v, want ErrUnknownVersion", name, in, err)
}
}
}
}
func TestSourceValuesRefused(t *testing.T) { // 1200/AC4
wantErr := func(name string, err error, words ...string) {
t.Helper()
if err == nil {
t.Errorf("%s accepted", name)
return
}
for _, w := range words {
if !strings.Contains(err.Error(), w) {
t.Errorf("%s: error %q does not name %q", name, err, w)
}
}
}
_, err := EncodeSource(Source{Group: "g"})
wantErr("source without repositories", err, "repositor")
_, err = EncodeSource(Source{Group: "g", Heads: map[string]string{"a/b": ""}})
wantErr("source with an empty head", err, "a/b")
_, err = EncodeSource(Source{Group: "g", Heads: map[string]string{"": "x"}})
wantErr("source with an empty repository", err, "repositor")
c := sampleCandidate()
c.HeadsHash = "000000000000"
_, err = EncodeCandidate(c)
wantErr("candidate with a stale hash", err, "heads hash")
c = sampleCandidate()
c.Pulls = nil
_, err = EncodeCandidate(c)
wantErr("candidate without pulls", err, "pull")
_, err = EncodeModule(Module{FP: "src1:x", None: "why", Repository: "a/b", Commit: "c"})
wantErr("module with both", err, "both")
_, err = EncodeModule(Module{Repository: "a/b", Commit: "c"})
wantErr("module with neither", err, "fingerprint", "none")
_, err = EncodeModule(Module{FP: "abc", Repository: "a/b", Commit: "c"})
wantErr("module with a bad fingerprint", err, "src1:")
f := sampleForges()[1]
f.Merge = ""
_, err = EncodeForge(f)
wantErr("merged without merge commit", err, "merge")
f = sampleForges()[1]
f.Root = ""
_, err = EncodeForge(f)
wantErr("merged without root", err, "root")
f = sampleForges()[1]
f.Trees = nil
_, err = EncodeForge(f)
wantErr("merged without trees", err, "tree")
f = sampleForges()[1]
f.Trees = map[string]string{"modules/a": ""}
_, err = EncodeForge(f)
wantErr("merged with an empty tree", err, "modules/a")
f = sampleForges()[0]
f.Action = "reopened"
_, err = EncodeForge(f)
wantErr("pull with an unknown action", err, "reopened")
f = sampleForges()[0]
f.Head = ""
_, err = EncodeForge(f)
wantErr("pull without head", err, "head")
f = sampleForges()[2]
f.Words = ""
_, err = EncodeForge(f)
wantErr("refused without words", err, "words")
f = sampleForges()[2]
f.Act = ""
_, err = EncodeForge(f)
wantErr("refused without act", err, "act")
_, err = EncodeForge(Forge{Kind: "closed", Repository: "a/b"})
wantErr("unknown kind", err, "closed")
_, err = EncodeForge(Forge{Kind: ForgePull, Repository: "nope", Number: 1, Head: "h", Action: "opened"})
wantErr("bad repository", err, "nope")
}
func TestSourceSame(t *testing.T) { // 1200/AC5
if !SameSource(sampleSource(), sampleSource()) {
t.Error("equal sources differ")
}
s := sampleSource()
s.Heads["novox/mesh-sdk"] = "zzz"
if SameSource(s, sampleSource()) {
t.Error("different sources are the same")
}
if !SameCandidate(sampleCandidate(), sampleCandidate()) {
t.Error("equal candidates differ")
}
c := sampleCandidate()
c.Pulls[0].Modules = []string{"other"}
if SameCandidate(c, sampleCandidate()) {
t.Error("different candidates are the same")
}
m := Module{FP: "src1:a", Repository: "a/b", Commit: "c"}
if !SameModule(m, m) || SameModule(m, Module{FP: "src1:b", Repository: "a/b", Commit: "c"}) {
t.Error("SameModule wrong")
}
f := sampleForges()[1]
g := sampleForges()[1]
if !SameForge(f, g) {
t.Error("equal forge messages differ")
}
g.Trees["modules/a"] = "other"
if SameForge(f, g) {
t.Error("different forge messages are the same")
}
}
func TestSourceBuckets(t *testing.T) {
if SourceBucket != "mesh-delivery-gate_source" || CandidatesBucket != "mesh-delivery-indexer_candidates" ||
ModulesBucket != "mesh-delivery-indexer_modules" || SourceMainKey != "main" ||
SourceHistory != 64 || CandidatesHistory != 10 || ModulesHistory != 20 {
t.Error("a bucket or history differs from the decided layout")
}
}
func TestForgeDirs(t *testing.T) {
pull := func(dirs []string, known bool) Forge {
return Forge{Kind: ForgePull, Repository: "novox/mesh-sdk", Number: 36, Head: "aaaa", Action: "updated", Dirs: dirs, DirsKnown: known}
}
round := func(f Forge) Forge {
t.Helper()
data, err := EncodeForge(f)
if err != nil {
t.Fatal(err)
}
got, err := DecodeForge(data)
if err != nil {
t.Fatal(err)
}
return got
}
// 1233/AC2: the directories come back equal and in order.
got := round(pull([]string{"modules/a", "modules/b"}, true))
if !reflect.DeepEqual(got.Dirs, []string{"modules/a", "modules/b"}) || !got.DirsKnown {
t.Errorf("dirs = %v known %v", got.Dirs, got.DirsKnown)
}
// 1233/AC3: a head changing no module directory is told apart from one whose directories could not be said.
none, unknown := round(pull(nil, true)), round(pull(nil, false))
if !none.DirsKnown || len(none.Dirs) != 0 {
t.Errorf("known and empty: dirs %v known %v", none.Dirs, none.DirsKnown)
}
if unknown.DirsKnown || len(unknown.Dirs) != 0 {
t.Errorf("not known: dirs %v known %v", unknown.Dirs, unknown.DirsKnown)
}
// 1233/AC4: a merged or a refused message carrying directories is refused.
m := sampleForges()[1]
m.Dirs, m.DirsKnown = []string{"modules/a"}, true
if _, err := EncodeForge(m); err == nil {
t.Error("merged message with dirs accepted")
}
r := sampleForges()[2]
r.Dirs, r.DirsKnown = []string{"modules/a"}, true
if _, err := EncodeForge(r); err == nil {
t.Error("refused message with dirs accepted")
}
r = sampleForges()[2]
r.DirsKnown = true
if _, err := EncodeForge(r); err == nil {
t.Error("refused message with dirs-known accepted")
}
// directories on a pull message that does not say it knows them are refused too.
if _, err := EncodeForge(pull([]string{"modules/a"}, false)); err == nil {
t.Error("dirs without dirs-known accepted")
}
}