| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732 |
- package wikijs
- import (
- "context"
- "encoding/json"
- "errors"
- "fmt"
- "io"
- "net/http"
- "net/http/httptest"
- "strings"
- "testing"
- )
- func newTestClient(t *testing.T, handler http.HandlerFunc) *Client {
- t.Helper()
- srv := httptest.NewServer(handler)
- t.Cleanup(srv.Close)
- client, err := New(srv.URL, "test-key", WithUserAgent("wikijs-mcp/test"))
- if err != nil {
- t.Fatalf("New() error = %v", err)
- }
- return client
- }
- func decodeRequest(t *testing.T, r *http.Request) graphqlRequest {
- t.Helper()
- var req graphqlRequest
- if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
- t.Errorf("decode request: %v", err)
- }
- return req
- }
- func writeJSON(t *testing.T, w http.ResponseWriter, status int, body string) {
- t.Helper()
- w.Header().Set("Content-Type", "application/json")
- w.WriteHeader(status)
- if _, err := w.Write([]byte(body)); err != nil {
- t.Errorf("write response: %v", err)
- }
- }
- func TestNewNormalizesURL(t *testing.T) {
- tests := []struct {
- base string
- wantBase string
- wantEndpoint string
- }{
- {base: "https://wiki.example.com", wantBase: "https://wiki.example.com", wantEndpoint: "https://wiki.example.com/graphql"},
- {base: "https://wiki.example.com/", wantBase: "https://wiki.example.com", wantEndpoint: "https://wiki.example.com/graphql"},
- {base: "https://wiki.example.com/graphql", wantBase: "https://wiki.example.com", wantEndpoint: "https://wiki.example.com/graphql"},
- {base: "https://wiki.example.com/graphql/", wantBase: "https://wiki.example.com", wantEndpoint: "https://wiki.example.com/graphql"},
- {base: "https://wiki.example.com/wiki", wantBase: "https://wiki.example.com/wiki", wantEndpoint: "https://wiki.example.com/wiki/graphql"},
- }
- for _, tt := range tests {
- t.Run(tt.base, func(t *testing.T) {
- c, err := New(tt.base, "key")
- if err != nil {
- t.Fatalf("New() error = %v", err)
- }
- if c.baseURL != tt.wantBase {
- t.Errorf("baseURL = %q, want %q", c.baseURL, tt.wantBase)
- }
- if c.endpoint != tt.wantEndpoint {
- t.Errorf("endpoint = %q, want %q", c.endpoint, tt.wantEndpoint)
- }
- })
- }
- }
- func TestNewRejectsInvalidURL(t *testing.T) {
- for _, raw := range []string{"", "wiki.example.com", "ftp://wiki.example.com", "/relative", "https://wiki.example.com/?a=1", "https://wiki.example.com/#x"} {
- if _, err := New(raw, "key"); err == nil {
- t.Errorf("New(%q) error = nil, want error", raw)
- }
- }
- }
- func TestPageByID(t *testing.T) {
- client := newTestClient(t, func(w http.ResponseWriter, r *http.Request) {
- if got, want := r.Header.Get("Authorization"), "Bearer test-key"; got != want {
- t.Errorf("Authorization = %q, want %q", got, want)
- }
- if got, want := r.Header.Get("User-Agent"), "wikijs-mcp/test"; got != want {
- t.Errorf("User-Agent = %q, want %q", got, want)
- }
- if got, want := r.Header.Get("Content-Type"), "application/json"; got != want {
- t.Errorf("Content-Type = %q, want %q", got, want)
- }
- req := decodeRequest(t, r)
- if !strings.Contains(req.Query, "single(id: $id)") {
- t.Errorf("query does not contain single query: %s", req.Query)
- }
- if got := req.Variables["id"]; got != float64(42) {
- t.Errorf("variables[id] = %v, want 42", got)
- }
- writeJSON(t, w, http.StatusOK, `{"data":{"pages":{"single":{
- "id":42,"path":"/dev/go","title":"Go","description":"desc",
- "tags":[{"tag":"go","title":"Go"}],"content":"body",
- "createdAt":"2025-01-01T00:00:00Z","updatedAt":"2025-01-02T00:00:00Z",
- "locale":"de"}}}}`)
- })
- page, err := client.PageByID(context.Background(), 42)
- if err != nil {
- t.Fatalf("PageByID() error = %v", err)
- }
- if page == nil {
- t.Fatal("PageByID() = nil, want page")
- }
- if page.ID != 42 || page.Title != "Go" {
- t.Errorf("page = %+v", page)
- }
- if got, want := page.URL, client.baseURL+"/de/dev/go"; got != want {
- t.Errorf("URL = %q, want %q", got, want)
- }
- if len(page.Tags) != 1 || page.Tags[0].Tag != "go" {
- t.Errorf("Tags = %+v", page.Tags)
- }
- }
- func TestPageByIDEnglishHasNoLocalePrefix(t *testing.T) {
- client := newTestClient(t, func(w http.ResponseWriter, r *http.Request) {
- writeJSON(t, w, http.StatusOK, `{"data":{"pages":{"single":{
- "id":1,"path":"/start","title":"Start","description":"",
- "tags":null,"content":"","createdAt":"","updatedAt":"","locale":"en"}}}}`)
- })
- page, err := client.PageByID(context.Background(), 1)
- if err != nil {
- t.Fatalf("PageByID() error = %v", err)
- }
- if got, want := page.URL, client.baseURL+"/start"; got != want {
- t.Errorf("URL = %q, want %q", got, want)
- }
- if page.Tags == nil {
- t.Error("Tags = nil, want empty slice")
- }
- }
- func TestPageByIDNotFound(t *testing.T) {
- client := newTestClient(t, func(w http.ResponseWriter, r *http.Request) {
- writeJSON(t, w, http.StatusOK, `{"data":{"pages":{"single":null}}}`)
- })
- page, err := client.PageByID(context.Background(), 99)
- if err != nil {
- t.Fatalf("PageByID() error = %v", err)
- }
- if page != nil {
- t.Errorf("PageByID() = %+v, want nil", page)
- }
- }
- func TestPageByPath(t *testing.T) {
- client := newTestClient(t, func(w http.ResponseWriter, r *http.Request) {
- req := decodeRequest(t, r)
- if !strings.Contains(req.Query, "singleByPath") {
- t.Errorf("query = %s", req.Query)
- }
- if got := req.Variables["path"]; got != "/start" {
- t.Errorf("variables[path] = %v", got)
- }
- if got := req.Variables["locale"]; got != "de-DE" {
- t.Errorf("variables[locale] = %v", got)
- }
- writeJSON(t, w, http.StatusOK, `{"data":{"pages":{"singleByPath":{
- "id":7,"path":"/start","title":"Start","description":"desc",
- "tags":[],"content":"body","createdAt":"c","updatedAt":"u","locale":"de-DE"}}}}`)
- })
- page, err := client.PageByPath(context.Background(), "/start", "de-DE")
- if err != nil {
- t.Fatalf("PageByPath() error = %v", err)
- }
- if got, want := page.URL, client.baseURL+"/de-DE/start"; got != want {
- t.Errorf("URL = %q, want %q", got, want)
- }
- }
- func TestSearchPages(t *testing.T) {
- client := newTestClient(t, func(w http.ResponseWriter, r *http.Request) {
- req := decodeRequest(t, r)
- if got := req.Variables["query"]; got != "mcp" {
- t.Errorf("variables[query] = %v", got)
- }
- if got := req.Variables["path"]; got != "/dev" {
- t.Errorf("variables[path] = %v", got)
- }
- if _, ok := req.Variables["locale"]; ok {
- t.Errorf("variables[locale] = %v, want omitted", req.Variables["locale"])
- }
- writeJSON(t, w, http.StatusOK, `{"data":{"pages":{"search":{
- "results":[
- {"title":"A","description":"a","path":"/a","locale":"en"},
- null,
- {"title":"B","description":"b","path":"/b","locale":"de"}
- ],
- "suggestions":["s1"],"totalHits":3}}}}`)
- })
- res, err := client.SearchPages(context.Background(), "mcp", "/dev", "")
- if err != nil {
- t.Fatalf("SearchPages() error = %v", err)
- }
- if got, want := res.TotalHits, 3; got != want {
- t.Errorf("TotalHits = %d, want %d", got, want)
- }
- if len(res.Results) != 2 {
- t.Fatalf("Results = %d, want 2", len(res.Results))
- }
- if got, want := res.Results[0].URL, client.baseURL+"/a"; got != want {
- t.Errorf("URL = %q, want %q", got, want)
- }
- if got, want := res.Results[1].URL, client.baseURL+"/de/b"; got != want {
- t.Errorf("URL = %q, want %q", got, want)
- }
- if len(res.Suggestions) != 1 || res.Suggestions[0] != "s1" {
- t.Errorf("Suggestions = %v", res.Suggestions)
- }
- }
- func TestSearchPagesEmpty(t *testing.T) {
- client := newTestClient(t, func(w http.ResponseWriter, r *http.Request) {
- writeJSON(t, w, http.StatusOK, `{"data":{"pages":{"search":null}}}`)
- })
- res, err := client.SearchPages(context.Background(), "nothing", "", "")
- if err != nil {
- t.Fatalf("SearchPages() error = %v", err)
- }
- if res.Results == nil || res.Suggestions == nil {
- t.Errorf("empty slices must not be nil: %+v", res)
- }
- if res.TotalHits != 0 {
- t.Errorf("TotalHits = %d, want 0", res.TotalHits)
- }
- }
- func TestListPages(t *testing.T) {
- client := newTestClient(t, func(w http.ResponseWriter, r *http.Request) {
- req := decodeRequest(t, r)
- if got := req.Variables["limit"]; got != float64(10) {
- t.Errorf("variables[limit] = %v", got)
- }
- if got := req.Variables["locale"]; got != "de" {
- t.Errorf("variables[locale] = %v", got)
- }
- tags, ok := req.Variables["tags"].([]any)
- if !ok || len(tags) != 2 || tags[0] != "go" {
- t.Errorf("variables[tags] = %v", req.Variables["tags"])
- }
- writeJSON(t, w, http.StatusOK, `{"data":{"pages":{"list":[
- {"id":1,"path":"/a","locale":"de","title":"A","description":"a",
- "createdAt":"c","updatedAt":"u","tags":["go"]},
- null,
- {"id":2,"path":"/b","locale":"en","title":"B","description":"b",
- "createdAt":"c","updatedAt":"u","tags":null}
- ]}}}`)
- })
- limit := 10
- pages, err := client.ListPages(context.Background(), ListOptions{
- Limit: &limit,
- Locale: "de",
- Tags: []string{"go", "mcp"},
- })
- if err != nil {
- t.Fatalf("ListPages() error = %v", err)
- }
- if len(pages) != 2 {
- t.Fatalf("ListPages() returned %d pages, want 2", len(pages))
- }
- if got, want := pages[0].URL, client.baseURL+"/de/a"; got != want {
- t.Errorf("URL = %q, want %q", got, want)
- }
- if pages[1].Tags == nil {
- t.Error("Tags = nil, want empty slice")
- }
- }
- func TestListPagesOmitsEmptyFilters(t *testing.T) {
- client := newTestClient(t, func(w http.ResponseWriter, r *http.Request) {
- req := decodeRequest(t, r)
- for _, key := range []string{"limit", "locale", "tags"} {
- if _, ok := req.Variables[key]; ok {
- t.Errorf("variables[%s] present, want omitted", key)
- }
- }
- writeJSON(t, w, http.StatusOK, `{"data":{"pages":{"list":[]}}}`)
- })
- if _, err := client.ListPages(context.Background(), ListOptions{}); err != nil {
- t.Fatalf("ListPages() error = %v", err)
- }
- }
- func TestCreatePage(t *testing.T) {
- client := newTestClient(t, func(w http.ResponseWriter, r *http.Request) {
- req := decodeRequest(t, r)
- if !strings.Contains(req.Query, "mutation CreatePage") {
- t.Errorf("query = %s", req.Query)
- }
- if got := req.Variables["tags"].([]any); len(got) != 1 || got[0] != "go" {
- t.Errorf("variables[tags] = %v", req.Variables["tags"])
- }
- if got := req.Variables["isPublished"]; got != true {
- t.Errorf("variables[isPublished] = %v", got)
- }
- writeJSON(t, w, http.StatusOK, `{"data":{"pages":{"create":{"responseResult":{
- "succeeded":true,"errorCode":0,"slug":"new-page","message":"Page created"}}}}}`)
- })
- res, err := client.CreatePage(context.Background(), CreatePageInput{
- Title: "New",
- Content: "body",
- Description: "desc",
- Path: "new-page",
- Editor: "markdown",
- IsPublished: true,
- Locale: "en",
- Tags: []string{"go"},
- })
- if err != nil {
- t.Fatalf("CreatePage() error = %v", err)
- }
- if !res.Succeeded || res.Message != "Page created" || res.Slug != "new-page" {
- t.Errorf("result = %+v", res)
- }
- }
- func TestUpdatePageFailureIsReturned(t *testing.T) {
- client := newTestClient(t, func(w http.ResponseWriter, r *http.Request) {
- writeJSON(t, w, http.StatusOK, `{"data":{"pages":{"update":{"responseResult":{
- "succeeded":false,"errorCode":6001,"slug":"","message":"Page not found"}}}}}`)
- })
- res, err := client.UpdatePage(context.Background(), UpdatePageInput{ID: 1})
- if err != nil {
- t.Fatalf("UpdatePage() error = %v", err)
- }
- if res.Succeeded {
- t.Error("Succeeded = true, want false")
- }
- if res.ErrorCode != 6001 || res.Message != "Page not found" {
- t.Errorf("result = %+v", res)
- }
- }
- func TestGraphQLErrors(t *testing.T) {
- client := newTestClient(t, func(w http.ResponseWriter, r *http.Request) {
- writeJSON(t, w, http.StatusOK, `{"errors":[{"message":"Unauthorized"},{"message":"Forbidden"}]}`)
- })
- _, err := client.PageByID(context.Background(), 1)
- if err == nil {
- t.Fatal("PageByID() error = nil, want error")
- }
- var apiErr *APIError
- if !errors.As(err, &apiErr) {
- t.Fatalf("error type = %T, want *APIError", err)
- }
- if got := apiErr.Error(); !strings.Contains(got, "Unauthorized") || !strings.Contains(got, "Forbidden") {
- t.Errorf("error = %q", got)
- }
- }
- func TestHTTPError(t *testing.T) {
- client := newTestClient(t, func(w http.ResponseWriter, r *http.Request) {
- http.Error(w, "server exploded", http.StatusInternalServerError)
- })
- _, err := client.PageByID(context.Background(), 1)
- if err == nil {
- t.Fatal("PageByID() error = nil, want error")
- }
- var apiErr *APIError
- if !errors.As(err, &apiErr) {
- t.Fatalf("error type = %T, want *APIError", err)
- }
- if apiErr.Status != http.StatusInternalServerError {
- t.Errorf("Status = %d, want 500", apiErr.Status)
- }
- if !strings.Contains(err.Error(), "server exploded") {
- t.Errorf("error = %q", err)
- }
- }
- func TestMissingData(t *testing.T) {
- client := newTestClient(t, func(w http.ResponseWriter, r *http.Request) {
- writeJSON(t, w, http.StatusOK, `{"data":null}`)
- })
- _, err := client.PageByID(context.Background(), 1)
- if err == nil {
- t.Fatal("PageByID() error = nil, want error")
- }
- }
- func TestContextCancellation(t *testing.T) {
- client := newTestClient(t, func(w http.ResponseWriter, r *http.Request) {
- t.Error("handler must not be called")
- })
- ctx, cancel := context.WithCancel(context.Background())
- cancel()
- if _, err := client.PageByID(ctx, 1); err == nil {
- t.Fatal("PageByID() error = nil, want context error")
- }
- }
- func TestListAssetFoldersRoot(t *testing.T) {
- client := newTestClient(t, func(w http.ResponseWriter, r *http.Request) {
- req := decodeRequest(t, r)
- if !strings.Contains(req.Query, "folders(parentFolderId") {
- t.Errorf("query = %s", req.Query)
- }
- if got := req.Variables["parentFolderId"]; got != float64(0) {
- t.Errorf("variables[parentFolderId] = %v, want 0 (root)", got)
- }
- writeJSON(t, w, http.StatusOK, `{"data":{"assets":{"folders":[
- {"id":5,"slug":"images","name":"Images"},
- {"id":6,"slug":"docs","name":"Docs"}
- ]}}}`)
- })
- folders, err := client.ListAssetFolders(context.Background(), "")
- if err != nil {
- t.Fatalf("ListAssetFolders() error = %v", err)
- }
- if len(folders) != 2 {
- t.Fatalf("folders = %d, want 2", len(folders))
- }
- if got, want := folders[0].Path, "images"; got != want {
- t.Errorf("Path = %q, want %q", got, want)
- }
- }
- func TestResolveAssetFolderNested(t *testing.T) {
- var calls []map[string]any
- client := newTestClient(t, func(w http.ResponseWriter, r *http.Request) {
- req := decodeRequest(t, r)
- calls = append(calls, req.Variables)
- switch len(calls) {
- case 1:
- if got := req.Variables["parentFolderId"]; got != float64(0) {
- t.Errorf("call 1 parentFolderId = %v, want 0", got)
- }
- writeJSON(t, w, http.StatusOK, `{"data":{"assets":{"folders":[{"id":5,"slug":"images"}]}}}`)
- case 2:
- if got := req.Variables["parentFolderId"]; got != float64(5) {
- t.Errorf("call 2 parentFolderId = %v, want 5", got)
- }
- writeJSON(t, w, http.StatusOK, `{"data":{"assets":{"folders":[{"id":9,"slug":"screens"}]}}}`)
- case 3:
- if got := req.Variables["folderId"]; got != float64(9) {
- t.Errorf("call 3 folderId = %v, want 9", got)
- }
- if got := req.Variables["kind"]; got != "ALL" {
- t.Errorf("call 3 kind = %v, want ALL", got)
- }
- writeJSON(t, w, http.StatusOK, `{"data":{"assets":{"list":[
- {"id":1,"filename":"shot.png","ext":"png","kind":"IMAGE","mime":"image/png",
- "fileSize":12,"createdAt":"c","updatedAt":"u",
- "folder":{"id":9,"slug":"screens","name":"Screens"}}
- ]}}}`)
- default:
- t.Errorf("unexpected call %d", len(calls))
- }
- })
- assets, err := client.ListAssets(context.Background(), "images/screens")
- if err != nil {
- t.Fatalf("ListAssets() error = %v", err)
- }
- if len(assets) != 1 {
- t.Fatalf("assets = %d, want 1", len(assets))
- }
- if got, want := assets[0].Folder.Path, "images/screens"; got != want {
- t.Errorf("Folder.Path = %q, want %q", got, want)
- }
- if got, want := assets[0].URL, client.baseURL+"/images/screens/shot.png"; got != want {
- t.Errorf("URL = %q, want %q", got, want)
- }
- }
- func TestListAssetsRootOmitsFolderResolution(t *testing.T) {
- requests := 0
- client := newTestClient(t, func(w http.ResponseWriter, r *http.Request) {
- requests++
- req := decodeRequest(t, r)
- if got := req.Variables["folderId"]; got != float64(0) {
- t.Errorf("folderId = %v, want 0", got)
- }
- writeJSON(t, w, http.StatusOK, `{"data":{"assets":{"list":[
- {"id":2,"filename":"logo.png","ext":"png","kind":"IMAGE","mime":"image/png",
- "fileSize":9,"createdAt":"c","updatedAt":"u","folder":null}
- ]}}}`)
- })
- assets, err := client.ListAssets(context.Background(), "")
- if err != nil {
- t.Fatalf("ListAssets() error = %v", err)
- }
- if requests != 1 {
- t.Errorf("requests = %d, want 1", requests)
- }
- if got, want := assets[0].URL, client.baseURL+"/logo.png"; got != want {
- t.Errorf("URL = %q, want %q", got, want)
- }
- if assets[0].Folder == nil || assets[0].Folder.ID != 0 {
- t.Errorf("Folder = %+v, want root folder", assets[0].Folder)
- }
- }
- func TestResolveAssetFolderNotFound(t *testing.T) {
- client := newTestClient(t, func(w http.ResponseWriter, r *http.Request) {
- writeJSON(t, w, http.StatusOK, `{"data":{"assets":{"folders":[]}}}`)
- })
- if _, err := client.ListAssets(context.Background(), "nope"); err == nil {
- t.Fatal("ListAssets() error = nil, want error")
- } else if !strings.Contains(err.Error(), "not found") {
- t.Errorf("error = %q", err)
- }
- }
- func TestCreateAssetFolder(t *testing.T) {
- client := newTestClient(t, func(w http.ResponseWriter, r *http.Request) {
- req := decodeRequest(t, r)
- if !strings.Contains(req.Query, "mutation CreateAssetFolder") {
- t.Errorf("query = %s", req.Query)
- }
- if got := req.Variables["parentFolderId"]; got != float64(0) {
- t.Errorf("parentFolderId = %v, want 0", got)
- }
- if got, want := req.Variables["slug"], "vacation"; got != want {
- t.Errorf("slug = %v, want %q", got, want)
- }
- writeJSON(t, w, http.StatusOK, `{"data":{"assets":{"createFolder":{"responseResult":{
- "succeeded":true,"errorCode":0,"slug":"vacation","message":"ok"}}}}}`)
- })
- res, err := client.CreateAssetFolder(context.Background(), "", "vacation", "Vacation 2025")
- if err != nil {
- t.Fatalf("CreateAssetFolder() error = %v", err)
- }
- if !res.Succeeded {
- t.Errorf("result = %+v", res)
- }
- }
- func TestCreateAssetFolderFailure(t *testing.T) {
- client := newTestClient(t, func(w http.ResponseWriter, r *http.Request) {
- writeJSON(t, w, http.StatusOK, `{"data":{"assets":{"createFolder":{"responseResult":{
- "succeeded":false,"errorCode":2002,"slug":"","message":"AssetFolderExists"}}}}}`)
- })
- res, err := client.CreateAssetFolder(context.Background(), "", "vacation", "Vacation")
- if err != nil {
- t.Fatalf("CreateAssetFolder() error = %v", err)
- }
- if res.Succeeded || res.ErrorCode != 2002 {
- t.Errorf("result = %+v", res)
- }
- }
- func TestUploadAssetRoot(t *testing.T) {
- var meta, file string
- var filename, contentType, auth string
- var contents []byte
- client := newTestClient(t, func(w http.ResponseWriter, r *http.Request) {
- // Best-effort site config probe arrives before the multipart upload.
- if !strings.HasPrefix(r.Header.Get("Content-Type"), "multipart/form-data") {
- writeJSON(t, w, http.StatusOK, `{"data":{"site":{"config":{"uploadMaxFileSize":5242880}}}}`)
- return
- }
- auth = r.Header.Get("Authorization")
- mr, err := r.MultipartReader()
- if err != nil {
- t.Fatalf("multipart reader: %v", err)
- }
- for i := 0; ; i++ {
- part, err := mr.NextPart()
- if err == io.EOF {
- break
- }
- if err != nil {
- t.Fatalf("part %d: %v", i, err)
- }
- if got := part.FormName(); got != "mediaUpload" {
- t.Errorf("part %d FormName() = %q, want mediaUpload", i, got)
- }
- data, err := io.ReadAll(part)
- if err != nil {
- t.Fatalf("read part %d: %v", i, err)
- }
- if part.FileName() == "" {
- meta = string(data)
- } else {
- file = string(data)
- filename = part.FileName()
- contentType = part.Header.Get("Content-Type")
- contents = data
- }
- }
- _, _ = w.Write([]byte("ok"))
- })
- res, uploaded, err := client.UploadAsset(context.Background(), "", "Report 2025; Final.pdf", []byte("payload"), "")
- if err != nil {
- t.Fatalf("UploadAsset() error = %v", err)
- }
- if auth != "Bearer test-key" {
- t.Errorf("Authorization = %q", auth)
- }
- if meta != `{"folderId":0}` {
- t.Errorf("metadata = %q, want folderId 0", meta)
- }
- if file != "payload" || string(contents) != "payload" {
- t.Errorf("file part = %q", file)
- }
- if filename != "report_2025_final.pdf" {
- t.Errorf("filename = %q, want sanitized", filename)
- }
- if contentType != "application/pdf" {
- t.Errorf("Content-Type = %q, want guessed application/pdf", contentType)
- }
- if !res.Succeeded {
- t.Errorf("result = %+v", res)
- }
- if got, want := uploaded.Filename, filename; got != want {
- t.Errorf("uploaded.Filename = %q, want %q", got, want)
- }
- if got, want := uploaded.URL, client.baseURL+"/report_2025_final.pdf"; got != want {
- t.Errorf("uploaded.URL = %q, want %q", got, want)
- }
- }
- func TestUploadAssetNestedFolder(t *testing.T) {
- var calls []map[string]any
- client := newTestClient(t, func(w http.ResponseWriter, r *http.Request) {
- if strings.Contains(r.Header.Get("Content-Type"), "multipart/form-data") {
- _, _ = w.Write([]byte("ok"))
- return
- }
- req := decodeRequest(t, r)
- if strings.Contains(req.Query, "query SiteConfig") {
- writeJSON(t, w, http.StatusOK, `{"data":{"site":{"config":{"uploadMaxFileSize":5242880}}}}`)
- return
- }
- calls = append(calls, req.Variables)
- if len(calls) == 1 {
- writeJSON(t, w, http.StatusOK, `{"data":{"assets":{"folders":[{"id":5,"slug":"images"}]}}}`)
- return
- }
- writeJSON(t, w, http.StatusOK, `{"data":{"assets":{"folders":[{"id":9,"slug":"screens"}]}}}`)
- })
- res, uploaded, err := client.UploadAsset(
- context.Background(), "images/screens", "shot.png", []byte("png"), "image/png",
- )
- if err != nil {
- t.Fatalf("UploadAsset() error = %v", err)
- }
- if len(calls) != 2 {
- t.Errorf("folder calls = %d, want 2", len(calls))
- }
- if !res.Succeeded {
- t.Errorf("result = %+v", res)
- }
- if got, want := uploaded.URL, client.baseURL+"/images/screens/shot.png"; got != want {
- t.Errorf("uploaded.URL = %q, want %q", got, want)
- }
- }
- func TestUploadAssetForbidden(t *testing.T) {
- client := newTestClient(t, func(w http.ResponseWriter, r *http.Request) {
- w.Header().Set("Content-Type", "application/json")
- w.WriteHeader(http.StatusForbidden)
- fmt.Fprint(w, `{"succeeded":false,"message":"You are not authorized to upload files."}`)
- })
- res, _, err := client.UploadAsset(context.Background(), "", "x.png", []byte("png"), "")
- if err != nil {
- t.Fatalf("UploadAsset() error = %v", err)
- }
- if res.Succeeded {
- t.Error("Succeeded = true, want false")
- }
- if res.Message != "You are not authorized to upload files." {
- t.Errorf("Message = %q", res.Message)
- }
- }
- func TestUploadAssetUnexpectedResponse(t *testing.T) {
- client := newTestClient(t, func(w http.ResponseWriter, r *http.Request) {
- _, _ = w.Write([]byte("<html>proxy error</html>"))
- })
- if _, _, err := client.UploadAsset(context.Background(), "", "x.png", []byte("png"), ""); err == nil {
- t.Fatal("UploadAsset() error = nil, want error")
- }
- }
- func TestUploadAssetInvalidFilename(t *testing.T) {
- client := newTestClient(t, func(w http.ResponseWriter, r *http.Request) {
- t.Error("handler must not be called")
- })
- if _, _, err := client.UploadAsset(context.Background(), "", " ", []byte("png"), ""); err == nil {
- t.Fatal("UploadAsset() error = nil, want error")
- }
- }
- func TestSanitizeAssetFilename(t *testing.T) {
- tests := []struct{ in, want string }{
- {in: "Report 2025; Final.pdf", want: "report_2025_final.pdf"},
- {in: "a b.png", want: "a_b.png"},
- {in: " A/B\\C.png ", want: "abc.png"},
- {in: "a,;#b.txt", want: "a_b.txt"},
- {in: "images/shot.png", want: "imagesshot.png"},
- }
- for _, tt := range tests {
- if got := sanitizeAssetFilename(tt.in); got != tt.want {
- t.Errorf("sanitizeAssetFilename(%q) = %q, want %q", tt.in, got, tt.want)
- }
- }
- }
|