diff --git a/internal/client/client.go b/internal/client/client.go index 6f04287..c517dc6 100644 --- a/internal/client/client.go +++ b/internal/client/client.go @@ -115,22 +115,35 @@ func (c *Client) Do(method, path string, body interface{}, query url.Values) (*o } // Check GitLink error-in-body pattern + // Support both {"status":N, "message":"..."} and gateway {"code":N, "msg":"..."} + var bodyCode float64 + var bodyMsg string if status, ok := raw["status"]; ok { - var statusCode float64 switch v := status.(type) { case float64: - statusCode = v + bodyCode = v case int: - statusCode = float64(v) + bodyCode = float64(v) } - if statusCode != 0 && statusCode != 200 && statusCode != 1 { - msg, _ := raw["message"].(string) - suggestion := suggestFix(int(statusCode)) - return output.ErrorEnvelope(int(statusCode), msg, suggestion), &APIError{ - StatusCode: int(statusCode), - Code: int(statusCode), - Message: msg, - } + bodyMsg, _ = raw["message"].(string) + } else if code, ok := raw["code"]; ok { + switch v := code.(type) { + case float64: + bodyCode = v + case int: + bodyCode = float64(v) + } + bodyMsg, _ = raw["msg"].(string) + if bodyMsg == "" { + bodyMsg, _ = raw["message"].(string) + } + } + if bodyCode != 0 && bodyCode != 200 && bodyCode != 201 && bodyCode != 204 && bodyCode != 1 { + suggestion := suggestFix(int(bodyCode)) + return output.ErrorEnvelope(int(bodyCode), bodyMsg, suggestion), &APIError{ + StatusCode: int(bodyCode), + Code: int(bodyCode), + Message: bodyMsg, } } @@ -175,6 +188,10 @@ func shouldAppendJSONSuffix(path string) bool { return false } } + // Wiki open API endpoints do not use .json suffix + if len(parts) >= 3 && parts[0] == "wiki" && parts[1] == "open" { + return false + } return true } diff --git a/internal/client/client_test.go b/internal/client/client_test.go index 9a55bcb..c46226b 100644 --- a/internal/client/client_test.go +++ b/internal/client/client_test.go @@ -169,6 +169,45 @@ func TestClientDoStatusError(t *testing.T) { } } +func TestClientDoGatewayCodeError(t *testing.T) { + // Gateway returns {"code":N, "msg":"..."} instead of {"status":N, "message":"..."} + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.Write([]byte(`{"code":400,"msg":"Bad Request"}`)) + })) + defer server.Close() + + c := &Client{HTTP: server.Client(), BaseURL: server.URL} + env, err := c.Do("GET", "/api/test", nil, nil) + if err == nil { + t.Fatal("expected error for code=400") + } + if env == nil { + t.Fatal("expected envelope for code error") + } + if env.OK { + t.Fatal("expected OK=false for code=400") + } +} + +func TestClientDoGatewayCode201Success(t *testing.T) { + // Gateway returns code=201 with JSON string data — should be treated as success + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.Write([]byte(`{"code":201,"msg":"","data":"{\"title\":\"test\"}"}`)) + })) + defer server.Close() + + c := &Client{HTTP: server.Client(), BaseURL: server.URL} + env, err := c.Do("POST", "/api/test", map[string]string{"title": "test"}, nil) + if err != nil { + t.Fatalf("unexpected error for code=201: %v", err) + } + if !env.OK { + t.Fatal("expected OK=true for code=201") + } +} + func TestClientDoStatusZero(t *testing.T) { // status=0, 200, 1 are treated as success server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -525,3 +564,18 @@ func TestShouldAppendJSONSuffixSkipsExistingJSONPath(t *testing.T) { t.Fatal("existing .json path should not get another suffix") } } + +func TestShouldAppendJSONSuffixSkipsWikiOpenPaths(t *testing.T) { + paths := []string{ + "/wiki/open/createWiki", + "/wiki/open/getWiki", + "/wiki/open/updateWiki", + "/wiki/open/deleteWiki", + "/wiki/open/wikiPages", + } + for _, p := range paths { + if shouldAppendJSONSuffix(p) { + t.Errorf("wiki/open path %q should not get .json suffix", p) + } + } +}