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)
}
}