@@ -27,30 +27,40 @@ function createPageMock(evaluateResult) {
2727}
2828describe ( 'xiaohongshu comments' , ( ) => {
2929 const command = getRegistry ( ) . get ( 'xiaohongshu/comments' ) ;
30- it ( 'returns ranked comment rows' , async ( ) => {
30+ it ( 'returns ranked comment rows for signed full URLs ' , async ( ) => {
3131 const page = createPageMock ( {
3232 loginWall : false ,
3333 results : [
3434 { author : 'Alice' , text : 'Great note!' , likes : 10 , time : '2024-01-01' , is_reply : false , reply_to : '' } ,
3535 { author : 'Bob' , text : 'Very helpful' , likes : 0 , time : '2024-01-02' , is_reply : false , reply_to : '' } ,
3636 ] ,
3737 } ) ;
38- const result = ( await command . func ( page , { 'note-id' : '69aadbcb000000002202f131' , limit : 5 } ) ) ;
39- expect ( page . goto . mock . calls [ 0 ] [ 0 ] ) . toContain ( '/search_result/69aadbcb000000002202f131' ) ;
38+ const signedUrl = 'https://www.xiaohongshu.com/search_result/69aadbcb000000002202f131?xsec_token=abc&xsec_source=pc_search' ;
39+ const result = ( await command . func ( page , { 'note-id' : signedUrl , limit : 5 } ) ) ;
40+ expect ( page . goto . mock . calls [ 0 ] [ 0 ] ) . toBe ( signedUrl ) ;
4041 expect ( result ) . toHaveLength ( 2 ) ;
4142 expect ( result [ 0 ] ) . toMatchObject ( { rank : 1 , author : 'Alice' , text : 'Great note!' , likes : 10 } ) ;
4243 expect ( result [ 1 ] ) . toMatchObject ( { rank : 2 , author : 'Bob' , text : 'Very helpful' , likes : 0 } ) ;
4344 } ) ;
44- it ( 'preserves full /explore/ URL as-is for navigation' , async ( ) => {
45+ it ( 'rejects bare note IDs before browser navigation' , async ( ) => {
46+ const page = createPageMock ( { loginWall : false , results : [ ] } ) ;
47+ await expect ( command . func ( page , { 'note-id' : '69aadbcb000000002202f131' , limit : 5 } ) ) . rejects . toMatchObject ( {
48+ code : 'ARGUMENT' ,
49+ message : expect . stringContaining ( 'signed URL' ) ,
50+ hint : expect . stringContaining ( 'xsec_token' ) ,
51+ } ) ;
52+ expect ( page . goto ) . not . toHaveBeenCalled ( ) ;
53+ } ) ;
54+ it ( 'preserves signed /explore/ URL as-is for navigation' , async ( ) => {
4555 const page = createPageMock ( {
4656 loginWall : false ,
4757 results : [ { author : 'Alice' , text : 'Nice' , likes : 1 , time : '2024-01-01' , is_reply : false , reply_to : '' } ] ,
4858 } ) ;
4959 await command . func ( page , {
50- 'note-id' : 'https://www.xiaohongshu.com/explore/69aadbcb000000002202f131' ,
60+ 'note-id' : 'https://www.xiaohongshu.com/explore/69aadbcb000000002202f131?xsec_token=abc&xsec_source=pc_search ' ,
5161 limit : 5 ,
5262 } ) ;
53- expect ( page . goto . mock . calls [ 0 ] [ 0 ] ) . toContain ( '/explore/69aadbcb000000002202f131' ) ;
63+ expect ( page . goto . mock . calls [ 0 ] [ 0 ] ) . toContain ( '/explore/69aadbcb000000002202f131?xsec_token=abc ' ) ;
5464 } ) ;
5565 it ( 'preserves full search_result URL with xsec_token for navigation' , async ( ) => {
5666 const page = createPageMock ( {
@@ -61,22 +71,21 @@ describe('xiaohongshu comments', () => {
6171 await command . func ( page , { 'note-id' : fullUrl , limit : 5 } ) ;
6272 expect ( page . goto . mock . calls [ 0 ] [ 0 ] ) . toBe ( fullUrl ) ;
6373 } ) ;
64- it ( 'throws AuthRequiredError when login wall is detected' , async ( ) => {
65- const page = createPageMock ( { loginWall : true , results : [ ] } ) ;
66- await expect ( command . func ( page , { 'note-id' : 'abc123' , limit : 5 } ) ) . rejects . toThrow ( 'Note comments require login' ) ;
67- } ) ;
68- it ( 'throws SECURITY_BLOCK with bare-id guidance when risk control blocks the comments page' , async ( ) => {
74+ it ( 'preserves signed /user/profile/<user>/<note> URLs for navigation' , async ( ) => {
6975 const page = createPageMock ( {
70- pageUrl : 'https://www.xiaohongshu.com/website-login/error?error_code=300017' ,
71- securityBlock : true ,
7276 loginWall : false ,
73- results : [ ] ,
74- } ) ;
75- await expect ( command . func ( page , { 'note-id' : 'abc123' , limit : 5 } ) ) . rejects . toMatchObject ( {
76- code : 'SECURITY_BLOCK' ,
77- hint : expect . stringContaining ( 'xsec_token' ) ,
77+ results : [ { author : 'Alice' , text : 'Nice' , likes : 1 , time : '2024-01-01' , is_reply : false , reply_to : '' } ] ,
7878 } ) ;
79- expect ( page . wait ) . toHaveBeenCalledWith ( expect . objectContaining ( { time : expect . any ( Number ) } ) ) ;
79+ const fullUrl = 'https://www.xiaohongshu.com/user/profile/user123/69aadbcb000000002202f131?xsec_token=abc&xsec_source=pc_user' ;
80+ await command . func ( page , { 'note-id' : fullUrl , limit : 5 } ) ;
81+ expect ( page . goto . mock . calls [ 0 ] [ 0 ] ) . toBe ( fullUrl ) ;
82+ } ) ;
83+ it ( 'throws AuthRequiredError when login wall is detected' , async ( ) => {
84+ const page = createPageMock ( { loginWall : true , results : [ ] } ) ;
85+ await expect ( command . func ( page , {
86+ 'note-id' : 'https://www.xiaohongshu.com/search_result/abc123?xsec_token=tok' ,
87+ limit : 5 ,
88+ } ) ) . rejects . toThrow ( 'Note comments require login' ) ;
8089 } ) ;
8190 it ( 'throws SECURITY_BLOCK with retry guidance when a full URL comments page is blocked' , async ( ) => {
8291 const page = createPageMock ( {
@@ -95,11 +104,17 @@ describe('xiaohongshu comments', () => {
95104 } ) ;
96105 it ( 'returns empty array when no comments are found' , async ( ) => {
97106 const page = createPageMock ( { loginWall : false , results : [ ] } ) ;
98- await expect ( command . func ( page , { 'note-id' : 'abc123' , limit : 5 } ) ) . resolves . toEqual ( [ ] ) ;
107+ await expect ( command . func ( page , {
108+ 'note-id' : 'https://www.xiaohongshu.com/search_result/abc123?xsec_token=tok' ,
109+ limit : 5 ,
110+ } ) ) . resolves . toEqual ( [ ] ) ;
99111 } ) ;
100112 it ( 'uses condition-based comment scrolling instead of a fixed blind loop' , async ( ) => {
101113 const page = createPageMock ( { loginWall : false , results : [ ] } ) ;
102- await command . func ( page , { 'note-id' : 'abc123' , limit : 5 } ) ;
114+ await command . func ( page , {
115+ 'note-id' : 'https://www.xiaohongshu.com/search_result/abc123?xsec_token=tok' ,
116+ limit : 5 ,
117+ } ) ;
103118 const script = page . evaluate . mock . calls [ 0 ] [ 0 ] ;
104119 expect ( script ) . toContain ( "const beforeCount = scroller.querySelectorAll('.parent-comment').length" ) ;
105120 expect ( script ) . toContain ( "const afterCount = scroller.querySelectorAll('.parent-comment').length" ) ;
@@ -115,7 +130,10 @@ describe('xiaohongshu comments', () => {
115130 reply_to : '' ,
116131 } ) ) ;
117132 const page = createPageMock ( { loginWall : false , results : manyComments } ) ;
118- const result = ( await command . func ( page , { 'note-id' : 'abc123' , limit : 3 } ) ) ;
133+ const result = ( await command . func ( page , {
134+ 'note-id' : 'https://www.xiaohongshu.com/search_result/abc123?xsec_token=tok' ,
135+ limit : 3 ,
136+ } ) ) ;
119137 expect ( result ) . toHaveLength ( 3 ) ;
120138 expect ( result [ 0 ] . rank ) . toBe ( 1 ) ;
121139 expect ( result [ 2 ] . rank ) . toBe ( 3 ) ;
@@ -128,7 +146,10 @@ describe('xiaohongshu comments', () => {
128146 { author : 'Bob' , text : 'Very helpful' , likes : 0 , time : '2024-01-02' , is_reply : false , reply_to : '' } ,
129147 ] ,
130148 } ) ;
131- const result = ( await command . func ( page , { 'note-id' : 'abc123' , limit : - 3 } ) ) ;
149+ const result = ( await command . func ( page , {
150+ 'note-id' : 'https://www.xiaohongshu.com/search_result/abc123?xsec_token=tok' ,
151+ limit : - 3 ,
152+ } ) ) ;
132153 expect ( result ) . toHaveLength ( 1 ) ;
133154 expect ( result [ 0 ] ) . toMatchObject ( { rank : 1 , author : 'Alice' } ) ;
134155 } ) ;
@@ -143,7 +164,7 @@ describe('xiaohongshu comments', () => {
143164 ] ,
144165 } ) ;
145166 const result = ( await command . func ( page , {
146- 'note-id' : 'abc123' , limit : 50 , 'with-replies' : true ,
167+ 'note-id' : 'https://www.xiaohongshu.com/search_result/ abc123?xsec_token=tok ' , limit : 50 , 'with-replies' : true ,
147168 } ) ) ;
148169 expect ( result ) . toHaveLength ( 3 ) ;
149170 expect ( result [ 0 ] ) . toMatchObject ( { author : 'Alice' , is_reply : false , reply_to : '' } ) ;
@@ -166,7 +187,7 @@ describe('xiaohongshu comments', () => {
166187 } ) ;
167188 // Limit to 2 top-level comments — should include A + 2 replies + B = 4 rows
168189 const result = ( await command . func ( page , {
169- 'note-id' : 'abc123' , limit : 2 , 'with-replies' : true ,
190+ 'note-id' : 'https://www.xiaohongshu.com/search_result/ abc123?xsec_token=tok ' , limit : 2 , 'with-replies' : true ,
170191 } ) ) ;
171192 expect ( result ) . toHaveLength ( 4 ) ;
172193 expect ( result . map ( ( r ) => r . author ) ) . toEqual ( [ 'A' , 'A1' , 'A2' , 'B' ] ) ;
0 commit comments