Files
creator-hub/internal/platform/xiaohongshu/collector_test.go
T

127 lines
6.2 KiB
Go

package xiaohongshu
import (
"context"
"errors"
"net/url"
"testing"
"git.ipao.vip/rogee/creator-hub/internal/creator"
)
type fakeBrowser struct {
getURL string
postURL string
getBody []byte
postBody []byte
getResp Response
postResp Response
resolvedURL string
resolveInput string
}
func (f *fakeBrowser) Get(_ context.Context, target string) (Response, error) {
f.getURL = target
return f.getResp, nil
}
func (f *fakeBrowser) Post(_ context.Context, target string, body []byte) (Response, error) {
f.postURL, f.postBody = target, body
return f.postResp, nil
}
func (f *fakeBrowser) Resolve(_ context.Context, target string) (string, error) {
f.resolveInput = target
return f.resolvedURL, nil
}
func TestIdentityRequiresMatchingUser(t *testing.T) {
browser := &fakeBrowser{getResp: Response{Status: 200, Body: []byte(`{"success":true,"data":{"user_id":"u-1","nickname":"作者"}}`)}}
collector := &Collector{Browser: browser}
identity, err := collector.Identity(context.Background(), "u-1")
if err != nil || identity.UserID != "u-1" {
t.Fatalf("identity = %#v, err = %v", identity, err)
}
if _, err := collector.Identity(context.Background(), "u-2"); !errors.Is(err, creator.ErrConflict) {
t.Fatalf("expected identity conflict, got %v", err)
}
}
func TestListWorksCapturesContextForComments(t *testing.T) {
browser := &fakeBrowser{getResp: Response{Status: 200, Body: []byte(`{"success":true,"data":{"cursor":"next","has_more":true,"notes":[{"note_id":"n-1","title":"标题","desc":"正文","time":1710000000,"user":{"user_id":"u-1","nickname":"作者"},"interact_info":{"liked_count":"2","comment_count":3,"shared_count":4},"xsec_token":"token"}]}}`)}}
collector := &Collector{Browser: browser, AccountKey: "u-1", SourceType: creator.SourceOwned, SourceID: "source-1"}
page, err := collector.ListWorks(context.Background(), "ignored", "")
if err != nil || len(page.Items) != 1 || page.NextCursor != "next" || !page.HasMore || page.Items[0].RawPayload == "" {
t.Fatalf("page = %#v, err = %v", page, err)
}
parsed, err := url.Parse(browser.getURL)
if err != nil || parsed.Query().Get("user_id") != "u-1" {
t.Fatalf("request URL = %s", browser.getURL)
}
browser.getResp = Response{Status: 200, Body: []byte(`{"success":true,"data":{"cursor":"","has_more":false,"comments":[{"id":"c-1","content":"评论","create_time":1710000000,"user_info":{"user_id":"u-2","nickname":"读者"}}]}}`)}
comments, err := collector.ListTopLevelComments(context.Background(), "n-1", "")
if err != nil || len(comments.Items) != 1 || comments.Items[0].CommentType != "top_level" || comments.Items[0].RawPayload == "" {
t.Fatalf("comments = %#v, err = %v", comments, err)
}
commentURL, err := url.Parse(browser.getURL)
if err != nil || commentURL.Query().Get("xsec_token") != "token" {
t.Fatalf("comment request URL = %s", browser.getURL)
}
}
func TestSearchUsesBoundedPostAndRejectsMalformedPage(t *testing.T) {
browser := &fakeBrowser{postResp: Response{Status: 200, Body: []byte(`{"success":true,"data":{"cursor":"","has_more":false,"notes":[]}}`)}}
collector := &Collector{Browser: browser}
page, err := collector.SearchNotes(context.Background(), "关键词", 1)
if err != nil || page.HasMore || len(page.Items) != 0 || browser.postURL != SearchOrigin+SearchNotesPath {
t.Fatalf("page = %#v, URL = %s, err = %v", page, browser.postURL, err)
}
browser.postResp = Response{Status: 200, Body: []byte(`{"success":true,"data":{"has_more":true,"notes":[]}}`)}
if _, err := collector.SearchNotes(context.Background(), "关键词", 1); !errors.Is(err, creator.ErrInvalid) {
t.Fatalf("expected malformed response error, got %v", err)
}
}
func TestHomepageContextIsUsedWithoutInventingDefaults(t *testing.T) {
browser := &fakeBrowser{getResp: Response{Status: 200, Body: []byte(`{"success":true,"data":{"cursor":"","has_more":false,"notes":[]}}`)}}
collector := &Collector{Browser: browser, AccountKey: "u-1", HomepageURL: "https://www.xiaohongshu.com/user/profile/u-1?xsec_token=home-token&xsec_source=pc_search"}
if _, err := collector.ListWorks(context.Background(), "ignored", ""); err != nil {
t.Fatalf("list works: %v", err)
}
requestURL, err := url.Parse(browser.getURL)
if err != nil || requestURL.Query().Get("xsec_token") != "home-token" || requestURL.Query().Get("xsec_source") != "pc_search" {
t.Fatalf("homepage context was not forwarded: %s", browser.getURL)
}
}
func TestSourceURLParsingAndShareResolution(t *testing.T) {
homepage, err := ParseSourceURL("https://www.xiaohongshu.com/user/profile/u-1?xsec_source=pc_search")
if err != nil || homepage.Kind != LinkHomepage || homepage.AccountKey != "u-1" {
t.Fatalf("homepage = %#v, err = %v", homepage, err)
}
share, err := ParseSourceURL("https://xhslink.com/a/abc")
if err != nil || share.Kind != LinkShare {
t.Fatalf("share = %#v, err = %v", share, err)
}
browser := &fakeBrowser{resolvedURL: "https://www.xiaohongshu.com/explore/n-1?xsec_token=tok-1&xsec_source=pc_search", postResp: Response{Status: 200, Body: []byte(`{"success":true,"data":{"items":[{"note_id":"n-1","title":"详情","time":1710000000,"interact_info":{}}]}}`)}}
collector := &Collector{Browser: browser}
item, err := collector.GetNoteDetail(context.Background(), "https://xhslink.com/a/abc")
if err != nil || item.WorkKey != "n-1" || browser.resolveInput != "https://xhslink.com/a/abc" {
t.Fatalf("resolved detail = %#v, err = %v, input = %s", item, err, browser.resolveInput)
}
}
func TestResponseErrorClassifiesRejectedPlatformPayload(t *testing.T) {
if err := responseError(Response{Status: 200, Body: []byte(`{"success":false,"code":"LOGIN_REQUIRED","msg":"请重新登录"}`)}, "works"); !errors.Is(err, creator.ErrConflict) {
t.Fatalf("expected conflict, got %v", err)
}
if err := responseError(Response{Status: 200, Body: []byte(`{"success":false,"code":"RATE_LIMIT","msg":"请求过于频繁"}`)}, "works"); !errors.Is(err, creator.ErrUnavailable) {
t.Fatalf("expected unavailable, got %v", err)
}
}
func TestSetWorkContextRejectsWrongHost(t *testing.T) {
collector := &Collector{}
if err := collector.SetWorkContext("n-1", "https://evil.example/explore/n-1"); !errors.Is(err, creator.ErrInvalid) {
t.Fatalf("expected invalid URL, got %v", err)
}
}