package main import ( "bytes" "encoding/json" "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]{}) { 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.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) } } 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) } }