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
89 lines
3.8 KiB
Go
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: ®.Outcome{}, holder.EventKept: ®.KeptEvent{}, holder.EventHeld: ®.Hold{}, holder.EventReleased: ®.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)
|
|
}
|
|
}
|