Files
base/cmd/cli/client_test.go
zeekay 70a06029a4 base: route convention /api/* -> /v1/*
One path: /v1/*. We do not use /api/ — that's Casdoor's path. The
BASE_API_PREFIX env knob is gone; the prefix is hard-coded in
apis/base.go and apis/serve.go banner.

Server-side mounts:
- apis: bindHealthApi, bindCollectionApi, bindRecordCrudApi, etc. all
  attach under r.Group("/v1") (was r.Group(BASE_API_PREFIX, default /api))
- platform plugin: /api/platform/* -> /v1/platform/* (including auth and
  org sub-groups)
- kms plugin: /api/kms/* -> /v1/kms/*
- functions plugin: /api/functions/* -> /v1/functions/*
- cloudsql plugin: /api/cloud-sql/*, /api/meta/* -> /v1/*
- superuser OIDC: /_/api/oidc/config -> /_/v1/oidc/config
- core settings: default RateLimitRule labels /api/batch, /api/ ->
  /v1/batch, /v1/
- apis/batch.go: ValidBatchActions regexes match /v1/collections/*;
  internal rewrite targets /v1/collections/* (was /v1/base/collections/*,
  a stale artifact)

Clients:
- sdk/base-js (BaseClient.collections, files, realtime, CRDT WS,
  signIn/refresh/health) targets /v1/*
- sdk/base-react (use-crdt WS path) targets /v1/crdt
- ui-react admin (lib/api.ts, settings.application copy) targets /v1/*
- cmd/cli (NewClient.APIPrefix) defaults to /v1; client_test.go and
  cmd/cli_test.go stubServer use /v1/*
- cmd/typegen calls /v1/collections

Upstream paths untouched: Casdoor IAM (/api/login, /api/userinfo,
/api/signup, /api/send-verification-code, /api/get-user), KMS Infisical
(/api/v1/auth/universal-auth/login, /api/v2/workspace/environments),
Kubernetes tasks (/api/v1/namespaces/...), IPFS (/api/v0/...), Gitea/
GitLab/Discord/Strava/Patreon/Box/Wakatime upstream APIs. Those are
their contracts, not ours.

Tests: all 437 prior /api/ test URLs across apis/*_test.go and
plugins/platform/auth_proxy_test.go updated to /v1/. Build + go vet
clean. ./apis ./core ./plugins/platform ./plugins/jsvm ./forms all
green. Pre-existing red probes (network/TestRedProbe_*,
migratecmd/TestAutomigrateCollection*) unchanged.
2026-05-15 14:41:26 -07:00

195 lines
4.6 KiB
Go

package cli
import (
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"testing"
)
func TestClientGet(t *testing.T) {
t.Parallel()
var gotMethod, gotPath, gotAuth, gotOrgID string
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
gotMethod = r.Method
gotPath = r.URL.Path
gotAuth = r.Header.Get("Authorization")
gotOrgID = r.Header.Get("X-Org-Id")
w.WriteHeader(200)
w.Write([]byte(`{"items":[]}`))
}))
defer ts.Close()
c := NewClient(ts.URL, "test-token-123", "org-abc")
data, status, err := c.Get("/collections")
if err != nil {
t.Fatal(err)
}
if status != 200 {
t.Fatalf("expected 200, got %d", status)
}
if gotMethod != "GET" {
t.Fatalf("expected GET, got %s", gotMethod)
}
if gotPath != "/v1/collections" {
t.Fatalf("expected /v1/collections, got %s", gotPath)
}
if gotAuth != "test-token-123" {
t.Fatalf("expected test-token-123, got %s", gotAuth)
}
if gotOrgID != "org-abc" {
t.Fatalf("expected org-abc, got %s", gotOrgID)
}
if data == nil {
t.Fatal("expected non-nil response data")
}
}
func TestClientPost(t *testing.T) {
t.Parallel()
var gotMethod, gotPath, gotContentType string
var gotBody []byte
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
gotMethod = r.Method
gotPath = r.URL.Path
gotContentType = r.Header.Get("Content-Type")
gotBody, _ = io.ReadAll(r.Body)
w.WriteHeader(200)
w.Write([]byte(`{"id":"abc123"}`))
}))
defer ts.Close()
c := NewClient(ts.URL, "tok", "")
body := map[string]string{"title": "hello"}
data, status, err := c.Post("/collections/posts/records", body)
if err != nil {
t.Fatal(err)
}
if status != 200 {
t.Fatalf("expected 200, got %d", status)
}
if gotMethod != "POST" {
t.Fatalf("expected POST, got %s", gotMethod)
}
if gotPath != "/v1/collections/posts/records" {
t.Fatalf("expected /v1/collections/posts/records, got %s", gotPath)
}
if gotContentType != "application/json" {
t.Fatalf("expected application/json, got %s", gotContentType)
}
var parsed map[string]string
if err := json.Unmarshal(gotBody, &parsed); err != nil {
t.Fatal(err)
}
if parsed["title"] != "hello" {
t.Fatalf("expected body title=hello, got %s", parsed["title"])
}
if data == nil {
t.Fatal("expected non-nil response data")
}
}
func TestClientPatch(t *testing.T) {
t.Parallel()
var gotMethod, gotPath string
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
gotMethod = r.Method
gotPath = r.URL.Path
w.WriteHeader(200)
w.Write([]byte(`{"id":"abc123","title":"updated"}`))
}))
defer ts.Close()
c := NewClient(ts.URL, "tok", "")
_, _, err := c.Patch("/collections/posts/records/abc123", map[string]string{"title": "updated"})
if err != nil {
t.Fatal(err)
}
if gotMethod != "PATCH" {
t.Fatalf("expected PATCH, got %s", gotMethod)
}
if gotPath != "/v1/collections/posts/records/abc123" {
t.Fatalf("expected /v1/collections/posts/records/abc123, got %s", gotPath)
}
}
func TestClientDelete(t *testing.T) {
t.Parallel()
var gotMethod, gotPath string
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
gotMethod = r.Method
gotPath = r.URL.Path
w.WriteHeader(204)
}))
defer ts.Close()
c := NewClient(ts.URL, "tok", "")
_, status, err := c.Delete("/collections/posts/records/abc123")
if err != nil {
t.Fatal(err)
}
if gotMethod != "DELETE" {
t.Fatalf("expected DELETE, got %s", gotMethod)
}
if gotPath != "/v1/collections/posts/records/abc123" {
t.Fatalf("expected /v1/collections/posts/records/abc123, got %s", gotPath)
}
if status != 204 {
t.Fatalf("expected 204, got %d", status)
}
}
func TestClientErrorResponse(t *testing.T) {
t.Parallel()
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(403)
w.Write([]byte(`{"message":"forbidden"}`))
}))
defer ts.Close()
c := NewClient(ts.URL, "", "")
_, _, err := c.Get("/collections")
if err == nil {
t.Fatal("expected error for 403 response")
}
}
func TestBuildQuery(t *testing.T) {
t.Parallel()
// empty
q := BuildQuery("", 0, "", nil)
if q != "" {
t.Fatalf("expected empty query, got %s", q)
}
// with params
q = BuildQuery("name='test'", 10, "-created", nil)
if q == "" {
t.Fatal("expected non-empty query")
}
// check it contains expected parts
for _, part := range []string{"filter=", "perPage=10", "sort="} {
found := false
for _, seg := range []string{q} {
if len(seg) > 0 {
found = true
}
}
if !found {
t.Fatalf("expected query to contain %s, got %s", part, q)
}
}
}