127 lines
6.2 KiB
Go
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)
|
|
}
|
|
}
|