Skip to content
quarry Artifact [36f800ae02]
Login

Artifact [36f800ae02]

Artifact 36f800ae0230f3fa752c5bda16eadf32421624b7f6f81af358a1781853d3433f:


package ops

import (
	"context"
	"strings"
	"testing"
)

type echoIn struct {
	Repo string `json:"repo"`
}

type echoOut struct {
	Repo  string `json:"repo"`
	Count int    `json:"count"`
}

func echoOp(name, route string) Op[echoIn, echoOut] {
	return Op[echoIn, echoOut]{
		Name:  name,
		Kind:  Query,
		Auth:  Anonymous,
		Route: route,
		Run: func(_ context.Context, in echoIn) (echoOut, error) {
			return echoOut{Repo: in.Repo, Count: 1}, nil
		},
	}
}

func TestRegisterAndInvoke(t *testing.T) {
	r := NewRegistry()
	if err := Register(r, echoOp("timeline.list", "GET /api/v1/r/{repo}/timeline")); err != nil {
		t.Fatal(err)
	}

	got, err := r.Invoke(context.Background(), "timeline.list", echoIn{Repo: "sqlite"})
	if err != nil {
		t.Fatal(err)
	}
	out, ok := got.(echoOut)
	if !ok {
		t.Fatalf("got %T, want echoOut", got)
	}
	if out.Repo != "sqlite" || out.Count != 1 {
		t.Fatalf("got %+v", out)
	}
}

func TestInvokeWrongInputType(t *testing.T) {
	r := NewRegistry()
	if err := Register(r, echoOp("timeline.list", "GET /timeline")); err != nil {
		t.Fatal(err)
	}
	if _, err := r.Invoke(context.Background(), "timeline.list", struct{ X int }{}); err == nil {
		t.Fatal("want type-mismatch error, got nil")
	}
}

func TestInvokeUnknownOp(t *testing.T) {
	r := NewRegistry()
	if _, err := r.Invoke(context.Background(), "nope", echoIn{}); err == nil {
		t.Fatal("want unknown-op error, got nil")
	}
}

func TestDuplicateNameRejected(t *testing.T) {
	r := NewRegistry()
	if err := Register(r, echoOp("dup", "GET /a")); err != nil {
		t.Fatal(err)
	}
	err := Register(r, echoOp("dup", "GET /b"))
	if err == nil || !strings.Contains(err.Error(), "duplicate op name") {
		t.Fatalf("want duplicate-name error, got %v", err)
	}
}

func TestDuplicateRouteRejected(t *testing.T) {
	r := NewRegistry()
	if err := Register(r, echoOp("a", "GET /same")); err != nil {
		t.Fatal(err)
	}
	err := Register(r, echoOp("b", "GET /same"))
	if err == nil || !strings.Contains(err.Error(), "already registered") {
		t.Fatalf("want duplicate-route error, got %v", err)
	}
}

func TestKindMethodMismatchRejected(t *testing.T) {
	r := NewRegistry()

	q := echoOp("q", "POST /q") // query must be GET
	if err := Register(r, q); err == nil {
		t.Fatal("want query/POST mismatch error, got nil")
	}

	c := echoOp("c", "GET /c")
	c.Kind = Command // command must be POST
	if err := Register(r, c); err == nil {
		t.Fatal("want command/GET mismatch error, got nil")
	}
}

func TestBadRoutesRejected(t *testing.T) {
	for _, route := range []string{"", "GET", "GET relative/path", "DELETE /x", "/x"} {
		r := NewRegistry()
		if err := Register(r, echoOp("x", route)); err == nil {
			t.Fatalf("route %q: want error, got nil", route)
		}
	}
}

func TestNonStructInRejected(t *testing.T) {
	r := NewRegistry()
	err := Register(r, Op[string, echoOut]{
		Name:  "bad",
		Kind:  Query,
		Route: "GET /bad",
		Run:   func(_ context.Context, _ string) (echoOut, error) { return echoOut{}, nil },
	})
	if err == nil || !strings.Contains(err.Error(), "In must be a struct") {
		t.Fatalf("want non-struct error, got %v", err)
	}
}

func TestNilRunRejected(t *testing.T) {
	r := NewRegistry()
	err := Register(r, Op[echoIn, echoOut]{Name: "nilrun", Kind: Query, Route: "GET /n"})
	if err == nil || !strings.Contains(err.Error(), "nil Run") {
		t.Fatalf("want nil-Run error, got %v", err)
	}
}

func TestAllSortedByName(t *testing.T) {
	r := NewRegistry()
	for _, n := range []string{"c.op", "a.op", "b.op"} {
		if err := Register(r, echoOp(n, "GET /"+n)); err != nil {
			t.Fatal(err)
		}
	}
	all := r.All()
	if len(all) != 3 || all[0].Name != "a.op" || all[1].Name != "b.op" || all[2].Name != "c.op" {
		names := make([]string, len(all))
		for i, d := range all {
			names[i] = d.Name
		}
		t.Fatalf("got %v", names)
	}
}