Files
mesh-delivery/modules/mesh-delivery-indexer/internal/index/open_asks_test.go
T
jschoubben 5b13cc7ab1 IX24: the indexer's four kept calls are asked again only while their ask is open
messageVia, stepsVia, sourceVia and versionsVia go through CallKeptWhileOpen: open while the commit's take-in
is pending, the candidate is held, the branch is watched, the module is in the newest view. A call that ends
with its ask closed is index.ErrAskClosed, not an unanswered call.

Builds: design 47 G §351, §367, §460, §531; A-190

Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 05:53:45 +01:00

112 lines
4.7 KiB
Go

package index
import (
"context"
"encoding/json"
"errors"
"fmt"
"strings"
"testing"
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshcatalog"
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/nodebuildgo"
"git.novox.be/novox/mesh-sdk/go/seatdef"
)
// A-190: a kept call is asked again only while what it serves still stands; each of the indexer's four has its predicate.
func TestKeptCallsOpenWhileTheirAskServesSomething(t *testing.T) {
ctx := context.Background()
t.Run("the take-in of a commit is pending while an entry names it or the take-in is made", func(t *testing.T) {
pending := newMem()
val, _ := encodePending(Pending{Seq: 1, Ref: &PendingRef{Repository: "novox/r", Merge: "c1"}})
if err := pending.Create(ctx, "m.1", val); err != nil {
t.Fatal(err)
}
ix := &Indexer{Pending: pending}
cancelled, cancel := context.WithCancel(ctx)
cancel()
for _, c := range []struct {
name string
ctx context.Context
repo, commit string
want bool
}{
{"held", ctx, "novox/r", "c1", true},
{"taken in or dropped", ctx, "novox/r", "c2", false},
{"in hand", forTakeIn(ctx, "novox/r", "c2"), "novox/r", "c2", true},
{"in hand for another commit", forTakeIn(ctx, "novox/r", "c3"), "novox/r", "c2", false},
{"the take-in's context ended", cancelled, "novox/r", "c1", false},
} {
if got, err := ix.TakeInPending(c.ctx, c.repo, c.commit); err != nil || got != c.want {
t.Errorf("%s: pending = %v, %v, want %v", c.name, got, err, c.want)
}
}
})
t.Run("the seed's candidate and the versions' module are in the newest trunk revision", func(t *testing.T) {
ix := &Indexer{Revisions: newMemObjects(1 << 20)}
keep := func(n int64, fp string) {
rev, objects, err := CatalogRevisionOf(meshcatalog.CatalogRevision{Revision: n, Kind: "trunk"}, map[string]meshcatalog.CatalogEntry{"avahi": {Module: "avahi", Fp: fp, Manifest: map[string]any{}}})
if err == nil {
err = KeepRevision(ctx, ix.Revisions, rev, objects)
}
if err != nil {
t.Fatal(err)
}
}
keep(1, "fp1")
if held, err := ix.SeedEntryHeld(ctx, "avahi", "fp1"); err != nil || !held {
t.Errorf("the entry at its fingerprint: %v, %v", held, err)
}
if in, err := ix.ModuleInNewestView(ctx, "avahi"); err != nil || !in {
t.Errorf("the module in the view: %v, %v", in, err)
}
if in, _ := ix.ModuleInNewestView(ctx, "gone"); in {
t.Error("a module the view does not hold is in it")
}
keep(2, "fp2")
if held, _ := ix.SeedEntryHeld(ctx, "avahi", "fp1"); held {
t.Error("a candidate the newest revision replaced is still held")
}
})
t.Run("a branch is watched while a registration names it", func(t *testing.T) {
g := newRegRig()
ix := &Indexer{Registry: g.r}
raw, _ := json.Marshal(meshcatalog.Registration{Module: "avahi", Branches: []string{"main", "feat"}})
if err := g.r.Registrations.Create(ctx, "avahi", raw); err != nil {
t.Fatal(err)
}
if w, err := ix.BranchWatched(ctx, TrunkBranch); err != nil || !w {
t.Errorf("the trunk: %v, %v", w, err)
}
if w, _ := ix.BranchWatched(ctx, "feat"); !w {
t.Error("a branch a registration names is not watched")
}
if w, _ := ix.BranchWatched(ctx, "other"); w {
t.Error("a branch no registration names is watched")
}
})
}
// A-190: a call that ends with its ask closed is not said unanswered: no finding, and the fill and the seed go on.
func TestAClosedAskIsNotUnanswered(t *testing.T) {
ctx := context.Background()
closed := fmt.Errorf("steps: %w", ErrAskClosed)
ix := &Indexer{Findings: newFindings(), Heard: newMem(), Revisions: newMemObjects(1 << 20), Versions: newMem()}
rev, objects, _ := CatalogRevisionOf(meshcatalog.CatalogRevision{Revision: 1, Kind: "trunk"}, map[string]meshcatalog.CatalogEntry{"avahi": {Module: "avahi", Fp: "fp", Manifest: map[string]any{},
Artifacts: []meshcatalog.Artifact{{Ref: "avahi:program", Language: "go"}}}})
if err := KeepRevision(ctx, ix.Revisions, rev, objects); err != nil {
t.Fatal(err)
}
defs := map[string]seatdef.Definition{"node-build-go": {Acts: []seatdef.ActingRule{{Step: seatdef.StepBuild, On: "go"}}}}
if _, err := ix.SeedModulesFrom(ctx, defs, func(context.Context, string, nodebuildgo.StepsInput) (nodebuildgo.StepsAnswer, error) {
return nodebuildgo.StepsAnswer{}, closed
}); err != nil || len(ix.stepsSince) != 0 {
t.Errorf("a closed steps ask counted unanswered: %v %v", err, ix.stepsSince)
}
if err := ix.FillVersions(ctx, func(context.Context, string, string) (BuilderVersionsAnswer, error) {
return BuilderVersionsAnswer{}, fmt.Errorf("builder_versions: %w", ErrAskClosed)
}); errors.Is(err, ErrAskClosed) || (err != nil && strings.Contains(err.Error(), "is not answered")) {
t.Errorf("a closed versions ask is unanswered: %v", err)
}
}