package delivery import ( "encoding/json" "errors" "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 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"}, } } 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") } }