package api
import (
"context"
"errors"
"strings"
"testing"
"craftdesign.group/quarry/internal/ops"
)
func landingListRegistry(t *testing.T, settled string) *ops.Registry {
t.Helper()
r := ops.NewRegistry()
ix := landingIndex(t, "bob", "feature-x", "trunk", settled)
if err := RegisterAll(r, Deps{Version: "v", Repos: testRepos(ix)}); err != nil {
t.Fatal(err)
}
return r
}
func landings(t *testing.T, r *ops.Registry, state string) (landingListOut, error) {
t.Helper()
got, err := r.Invoke(context.Background(), "landing.list",
landingListIn{Repo: "test", State: state})
if err != nil {
return landingListOut{}, err
}
return got.(landingListOut), nil
}
// `state=all` means every state, because that is the value the web
// page's own filter links to.
//
// It used to be passed to the query as a literal state, matching
// nothing, so the op answered 200 with an empty list — the same answer
// a repository with no landings gives. A client written by reading this
// product's UI asked for the one value the op did not accept and was
// told there was nothing there (G-132).
func TestLandingStateAllMeansEveryState(t *testing.T) {
r := landingListRegistry(t, "accept")
every, err := landings(t, r, "")
if err != nil {
t.Fatal(err)
}
all, err := landings(t, r, "all")
if err != nil {
t.Fatalf("FAIL: state=all was refused: %v", err)
}
if len(every.Landings) == 0 {
t.Fatal("FAIL: the fixture has no landings to compare")
}
if len(all.Landings) != len(every.Landings) {
t.Fatalf("FAIL: state=all listed %d, no filter listed %d; they must agree",
len(all.Landings), len(every.Landings))
}
t.Logf("ok: state=all and no filter both list %d", len(all.Landings))
}
// A state the op does not know is refused by name, as ticket.state
// already did. Same concept, same parameter name — they used to get
// opposite treatment, which was the sharpest line in the sweep that
// found this.
func TestAnUnknownLandingStateIsRefused(t *testing.T) {
// ops.Validate is the mechanism, and the router is the only caller
// of it — the same seam that already refuses an unknown
// ticket.state. Asserted here rather than over HTTP so the test
// names what enforces the rule.
if err := ops.Validate(landingListIn{Repo: "test", State: "zzz-not-a-state"}); !errors.Is(err, ops.ErrInvalid) {
t.Fatalf("FAIL: err = %v, want ErrInvalid", err)
} else {
t.Logf("ok: %v", err)
}
// The words that DO work keep working, including the empty one,
// and they work end to end rather than only passing validation.
r := landingListRegistry(t, "accept")
for _, s := range []string{"", "open", "accepted", "rejected", "all"} {
if err := ops.Validate(landingListIn{Repo: "test", State: s}); err != nil {
t.Errorf("FAIL: state %q did not validate: %v", s, err)
continue
}
if _, err := landings(t, r, s); err != nil {
t.Errorf("FAIL: state %q was refused by the op: %v", s, err)
}
}
t.Log("ok: open, accepted, rejected, all and the empty default are accepted")
}
// The refusal names what would have worked. "no such value" alone costs
// the caller a second guess, and the guess is what caused this.
func TestTheLandingStateRefusalNamesTheValues(t *testing.T) {
err := ops.Validate(landingListIn{Repo: "test", State: "closed"})
if err == nil {
t.Fatal("FAIL: a state the op does not know was accepted")
}
for _, want := range []string{"open", "accepted", "rejected", "all"} {
if !strings.Contains(err.Error(), want) {
t.Errorf("FAIL: the refusal does not mention %q: %v", want, err)
}
}
t.Logf("ok: %v", err)
}