Files
mesh-catalog/modules/distribution/cmd/distribution-tools/contract_test.go
T
jschoubben b3261ccc03 G-catalog-B 6: on registry 2.8.3 a shared-digest tagged version's remove answers and says kept
Saying removed for a version the store still holds told the builder something false. remove now answers
kept with the reason shared-digest and the other tags naming its digest, removed false, and says the
seat's event kept with them (mesh-contracts' shared registry part); the builder raises no retry.

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-04 18:15:24 +01:00

89 lines
3.8 KiB
Go

package main
import (
"bytes"
"encoding/json"
"errors"
"reflect"
"strings"
"testing"
reg "git.novox.be/novox/mesh-contracts/go/seats/containerimageregistry"
"distribution/internal/holder"
)
func jsonFields(v any) map[string]bool {
out := map[string]bool{}
t := reflect.TypeOf(v)
for i := 0; i < t.NumField(); i++ {
name, _, _ := strings.Cut(t.Field(i).Tag.Get("json"), ",")
out[name] = true
}
return out
}
// TestTheVerbsSpeakTheSeatsContract holds distribution's seat verbs to the contracts mesh-contracts generates from the
// two seats' definitions (ADR 0620 *How it is checked*): every argument a verb's tool takes is a field of the
// contract's input, and every event body decodes into the contract's type with no field left over.
func TestTheVerbsSpeakTheSeatsContract(t *testing.T) {
inputs := map[string]any{reg.VerbPublish: reg.PublishInput{}, reg.VerbPublishPart: reg.PublishPartInput{},
reg.VerbVersions: reg.VersionsInput{}, reg.VerbRemove: reg.RemoveInput{}, reg.VerbHold: reg.HoldInput{},
reg.VerbRelease: reg.ReleaseInput{}, reg.VerbMirror: reg.MirrorInput{}, reg.VerbTags: reg.TagsInput{}}
seen := 0
for _, tool := range tools(&holder.Verbs{}, kept[holder.Window]{}, func() (holder.Callers, error) { return holder.DefaultCallers(), nil }) {
seat, verb, ok := strings.Cut(tool.Name, ".")
in, isVerb := inputs[verb]
if !ok || !isVerb || (seat != holder.ImageSeat && seat != holder.ArtifactSeat) {
continue
}
seen++
fields := jsonFields(in)
props, _ := tool.Input["properties"].(map[string]any)
for p := range props {
if !fields[p] {
t.Errorf("%s takes %q, which the contract's input does not name", tool.Name, p)
}
}
}
if seen != 14 {
t.Errorf("%d seat verbs served, want 14 (8 on the image seat, 6 on the artifact seat)", seen)
}
for event, want := range map[string]any{holder.EventPublished: &reg.Outcome{}, holder.EventKept: &reg.KeptEvent{}, holder.EventHeld: &reg.Hold{}, holder.EventReleased: &reg.Hold{}} {
b, _ := json.Marshal(eventBody(event, holder.Event{Name: "m/a", Kind: holder.KindArchive, Digest: "sha256:aa", Holder: holder.Self, By: "x.artifact-store-tools"}))
dec := json.NewDecoder(bytes.NewReader(b))
dec.DisallowUnknownFields()
if err := dec.Decode(want); err != nil {
t.Errorf("%s's body %s is not the contract's: %v", event, b, err)
}
}
// The caller list is read on each call: one that cannot be read answers no verb, and registry_callers says it as read.
unread := func() (holder.Callers, error) { return nil, errors.New("config refused") }
for _, tool := range tools(&holder.Verbs{}, kept[holder.Window]{}, unread) {
if tool.Name == holder.ImageSeat+"."+holder.VerbVersions {
if _, err := tool.Run(map[string]any{"name": "m/a"}); err == nil || !strings.Contains(err.Error(), "caller list") {
t.Errorf("versions with an unread caller list = %v, want refused naming the caller list", err)
}
}
}
answered := false
for _, tool := range tools(&holder.Verbs{}, kept[holder.Window]{}, func() (holder.Callers, error) { return holder.Callers{holder.VerbPublish: {"mesh-build-go"}}, nil }) {
if tool.Name == holder.CallersTool {
answered = true
got, err := tool.Run(map[string]any{})
if b, _ := json.Marshal(got); err != nil || string(b) != `{"callers":{"publish":["mesh-build-go"]}}` {
t.Errorf("%s = %s, %v", holder.CallersTool, b, err)
}
}
}
if !answered {
t.Errorf("no tool %s: the program could not read the caller list", holder.CallersTool)
}
if a := publishAnswer(holder.PublishAnswer{Transfer: "t", Missing: []string{"sha256:bb"}}); !reflect.DeepEqual(a.Lacks, []string{"sha256:bb"}) || a.Held {
t.Errorf("publish answers %+v", a)
}
if r, err := partOf(reg.PublishPartInput{Transfer: "t", Part: "sha256:bb", Data: "aGk=", Last: true}); err != nil || string(r.Data) != "hi" || r.Digest != "sha256:bb" {
t.Errorf("publish-part read %+v, %v", r, err)
}
}