package app import ( "context" "encoding/base64" "encoding/json" "fmt" "net/http" "net/http/httptest" "strings" "testing" ) func TestWordPressPublisherCategoriesUsesRESTAPIAndBasicAuth(t *testing.T) { const username = "editor@example.com" const applicationPassword = "abcd efgh ijkl mnop" var gotAuth string var gotPath string var gotQuery string server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { gotAuth = r.Header.Get("Authorization") gotPath = r.URL.Path gotQuery = r.URL.RawQuery w.Header().Set("Content-Type", "application/json") w.Header().Set("X-WP-TotalPages", "1") _, _ = w.Write([]byte(`[ {"id": 12, "name": "案例", "slug": "case", "parent": 0, "count": 3, "link": "https://example.test/category/case/"} ]`)) })) defer server.Close() publisher := newWordPressPublisher(server.Client()) items, raw, err := publisher.Categories(context.Background(), enterpriseSiteCredential{ SiteURL: server.URL, AppID: username, Secret: applicationPassword, }) if err != nil { t.Fatalf("sync wordpress categories: %v", err) } wantAuth := "Basic " + base64.StdEncoding.EncodeToString([]byte(username+":"+applicationPassword)) if gotAuth != wantAuth { t.Fatalf("authorization = %q, want %q", gotAuth, wantAuth) } if gotPath != "/index.php" { t.Fatalf("path = %q, want wordpress categories endpoint", gotPath) } if !strings.Contains(gotQuery, "rest_route=%2Fwp%2Fv2%2Fcategories") || !strings.Contains(gotQuery, "per_page=100") || !strings.Contains(gotQuery, "hide_empty=false") { t.Fatalf("query = %q, want wordpress rest_route, per_page and hide_empty", gotQuery) } if len(items) != 1 || items[0].RemoteID != "12" || items[0].Name != "案例" { t.Fatalf("items = %#v, want parsed wordpress category", items) } if len(raw) != 1 || raw[0]["slug"] != "case" { t.Fatalf("raw = %#v, want preserved category payload", raw) } } func TestWordPressPublisherPingReturnsSafeRawSummary(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") switch { case strings.Contains(r.URL.RawQuery, "rest_route=%2Fwp%2Fv2%2Fusers%2Fme"): _, _ = w.Write([]byte(`{"id": 7, "name": "admin", "email": "admin@example.test", "capabilities": {"administrator": true}}`)) default: _, _ = w.Write([]byte(`{"name": "Example", "home": "https://example.test", "routes": {"/wp/v2/users/me": {"sensitive": true}}}`)) } })) defer server.Close() publisher := newWordPressPublisher(server.Client()) capability, raw, err := publisher.Ping(context.Background(), enterpriseSiteCredential{ SiteURL: server.URL, AppID: "editor", Secret: "application-password", }) if err != nil { t.Fatalf("ping wordpress: %v", err) } if capability.SiteName == nil || *capability.SiteName != "Example" || !capability.APIAuth { t.Fatalf("capability = %#v, want sanitized wordpress capability", capability) } if raw["api_auth"] != true || raw["authenticated"] != true { t.Fatalf("raw = %#v, want safe auth summary", raw) } if _, ok := raw["routes"]; ok { t.Fatalf("raw leaked wordpress index routes: %#v", raw) } if user, ok := raw["authenticated_user"].(map[string]any); ok || user != nil { t.Fatalf("raw leaked authenticated user body: %#v", raw) } if strings.Contains(fmt.Sprintf("%v", raw), "admin@example.test") { t.Fatalf("raw leaked authenticated user email: %#v", raw) } } func TestWordPressPublisherPublishCreatesPost(t *testing.T) { var gotPath string var gotQuery string var gotPayload map[string]any server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { gotPath = r.URL.Path gotQuery = r.URL.RawQuery if r.Method != http.MethodPost { t.Fatalf("method = %s, want POST", r.Method) } if _, _, ok := r.BasicAuth(); !ok { t.Fatalf("missing basic auth") } if err := json.NewDecoder(r.Body).Decode(&gotPayload); err != nil { t.Fatalf("decode request body: %v", err) } w.Header().Set("Content-Type", "application/json") _, _ = w.Write([]byte(`{"id": 88, "link": "https://example.test/post/88/", "status": "publish"}`)) })) defer server.Close() publisher := newWordPressPublisher(server.Client()) result, raw, err := publisher.Publish(context.Background(), enterpriseSiteCredential{ SiteURL: server.URL, AppID: "editor", Secret: "application-password", }, cmsPublishRequest{ Title: "测试标题", ContentHTML: "

正文

", CategoryID: "12", Description: "摘要", PublishType: "draft", }) if err != nil { t.Fatalf("publish wordpress post: %v", err) } if gotPath != "/index.php" { t.Fatalf("path = %q, want wordpress posts endpoint", gotPath) } if !strings.Contains(gotQuery, "rest_route=%2Fwp%2Fv2%2Fposts") { t.Fatalf("query = %q, want wordpress posts rest_route", gotQuery) } if gotPayload == nil { t.Fatalf("missing request payload") } if gotPayload["title"] != "测试标题" || gotPayload["content"] != "

正文

" || gotPayload["status"] != "draft" { t.Fatalf("payload = %#v, want wordpress post body", gotPayload) } categories, ok := gotPayload["categories"].([]any) if !ok || len(categories) != 1 { t.Fatalf("categories = %#v, want [12]", gotPayload["categories"]) } categoryID, ok := categories[0].(float64) if !ok || categoryID != 12 { t.Fatalf("category id = %#v, want 12", categories[0]) } if result.RemoteID != "88" || result.URL != "https://example.test/post/88/" { t.Fatalf("result = %#v, want wordpress post result", result) } if raw["id"] != int64(88) { t.Fatalf("raw = %#v, want saved wordpress response", raw) } }