7
7
"net/http"
8
8
"net/http/httptest"
9
9
"os"
10
+ "strconv"
10
11
"strings"
11
12
"testing"
12
13
"time"
@@ -228,7 +229,7 @@ func TestRest_Find(t *testing.T) {
228
229
assert .Equal (t , id2 , comments .Comments [0 ].ID )
229
230
230
231
// get in tree mode
231
- tree := service. Tree {}
232
+ tree := treeWithInfo {}
232
233
res , code = get (t , ts .URL + "/api/v1/find?site=remark42&url=https://radio-t.com/blah1&format=tree" )
233
234
assert .Equal (t , http .StatusOK , code )
234
235
err = json .Unmarshal ([]byte (res ), & tree )
@@ -254,7 +255,7 @@ func TestRest_FindAge(t *testing.T) {
254
255
_ , err = srv .DataService .Create (c2 )
255
256
require .NoError (t , err )
256
257
257
- tree := service. Tree {}
258
+ tree := treeWithInfo {}
258
259
259
260
res , code := get (t , ts .URL + "/api/v1/find?site=remark42&url=https://radio-t.com/blah1&format=tree" )
260
261
assert .Equal (t , http .StatusOK , code )
@@ -297,15 +298,15 @@ func TestRest_FindReadOnly(t *testing.T) {
297
298
require .NoError (t , err )
298
299
require .NoError (t , resp .Body .Close ())
299
300
300
- tree := service. Tree {}
301
+ tree := treeWithInfo {}
301
302
res , code := get (t , ts .URL + "/api/v1/find?site=remark42&url=https://radio-t.com/blah1&format=tree" )
302
303
assert .Equal (t , http .StatusOK , code )
303
304
err = json .Unmarshal ([]byte (res ), & tree )
304
305
require .NoError (t , err )
305
306
assert .Equal (t , "https://radio-t.com/blah1" , tree .Info .URL )
306
307
assert .True (t , tree .Info .ReadOnly , "post is ro" )
307
308
308
- tree = service. Tree {}
309
+ tree = treeWithInfo {}
309
310
res , code = get (t , ts .URL + "/api/v1/find?site=remark42&url=https://radio-t.com/blah2&format=tree" )
310
311
assert .Equal (t , http .StatusOK , code )
311
312
err = json .Unmarshal ([]byte (res ), & tree )
@@ -533,6 +534,159 @@ func TestRest_FindUserComments_CWE_918(t *testing.T) {
533
534
assert .Equal (t , arbitraryServer .URL , resp .Comments [0 ].Locator .URL , "arbitrary URL provided by the request" )
534
535
}
535
536
537
+ func TestPublic_FindCommentsCtrl_ConsistentCount (t * testing.T ) {
538
+ // test that comment counting is consistent between tree and plain formats
539
+ ts , srv , teardown := startupT (t )
540
+ defer teardown ()
541
+
542
+ commentLocator := store.Locator {URL : "test-url" , SiteID : "remark42" }
543
+
544
+ // vote for comment multiple times
545
+ setScore := func (locator store.Locator , id string , val int ) {
546
+ abs := func (x int ) int {
547
+ if x < 0 {
548
+ return - x
549
+ }
550
+ return x
551
+ }
552
+ for i := 0 ; i < abs (val ); i ++ {
553
+ _ , err := srv .DataService .Vote (service.VoteReq {
554
+ Locator : locator ,
555
+ CommentID : id ,
556
+ // unique user ID is needed for correct counting of controversial votes
557
+ UserID : "user" + strconv .Itoa (val ) + strconv .Itoa (i ),
558
+ Val : val > 0 ,
559
+ })
560
+ require .NoError (t , err )
561
+ }
562
+ }
563
+
564
+ // Adding initial comments (8 to test-url and 1 to another-url) and voting, and delete two of comments to the first post.
565
+ // With sleep so that at least few millisecond pass between each comment
566
+ // and later we would be able to use that in "since" filter with millisecond precision
567
+ ids := make ([]string , 9 )
568
+ timestamps := make ([]time.Time , 9 )
569
+ c1 := store.Comment {Text : "top-level comment 1" , Locator : commentLocator }
570
+ ids [0 ], timestamps [0 ] = addCommentGetCreatedTime (t , c1 , ts )
571
+ // #3 by score
572
+ setScore (commentLocator , ids [0 ], 1 )
573
+ time .Sleep (time .Millisecond * 5 )
574
+
575
+ c2 := store.Comment {Text : "top-level comment 2" , Locator : commentLocator }
576
+ ids [1 ], timestamps [1 ] = addCommentGetCreatedTime (t , c2 , ts )
577
+ // #2 by score
578
+ setScore (commentLocator , ids [1 ], 2 )
579
+ time .Sleep (time .Millisecond * 5 )
580
+
581
+ c3 := store.Comment {Text : "second-level comment 1" , ParentID : ids [0 ], Locator : commentLocator }
582
+ ids [2 ], timestamps [2 ] = addCommentGetCreatedTime (t , c3 , ts )
583
+ // #1 by score
584
+ setScore (commentLocator , ids [2 ], 10 )
585
+ time .Sleep (time .Millisecond * 5 )
586
+
587
+ c4 := store.Comment {Text : "third-level comment 1" , ParentID : ids [2 ], Locator : commentLocator }
588
+ ids [3 ], timestamps [3 ] = addCommentGetCreatedTime (t , c4 , ts )
589
+ // #5 by score, #1 by controversy
590
+ setScore (commentLocator , ids [3 ], 4 )
591
+ setScore (commentLocator , ids [3 ], - 4 )
592
+ time .Sleep (time .Millisecond * 5 )
593
+
594
+ c5 := store.Comment {Text : "second-level comment 2" , ParentID : ids [1 ], Locator : commentLocator }
595
+ ids [4 ], timestamps [4 ] = addCommentGetCreatedTime (t , c5 , ts )
596
+ // #5 by score, #2 by controversy
597
+ setScore (commentLocator , ids [4 ], 2 )
598
+ setScore (commentLocator , ids [4 ], - 3 )
599
+ time .Sleep (time .Millisecond * 5 )
600
+
601
+ c6 := store.Comment {Text : "third-level comment 2" , ParentID : ids [4 ], Locator : commentLocator }
602
+ ids [5 ], timestamps [5 ] = addCommentGetCreatedTime (t , c6 , ts )
603
+ // deleted later so not visible in site-wide requests
604
+ setScore (commentLocator , ids [5 ], 10 )
605
+ setScore (commentLocator , ids [5 ], - 10 )
606
+ time .Sleep (time .Millisecond * 5 )
607
+
608
+ c7 := store.Comment {Text : "top-level comment 3" , Locator : commentLocator }
609
+ ids [6 ], timestamps [6 ] = addCommentGetCreatedTime (t , c7 , ts )
610
+ // #6 by score, #4 by controversy
611
+ setScore (commentLocator , ids [6 ], - 3 )
612
+ setScore (commentLocator , ids [6 ], 1 )
613
+ time .Sleep (time .Millisecond * 5 )
614
+
615
+ c8 := store.Comment {Text : "second-level comment 3" , ParentID : ids [6 ], Locator : commentLocator }
616
+ ids [7 ], timestamps [7 ] = addCommentGetCreatedTime (t , c8 , ts )
617
+ // deleted later so not visible in site-wide requests
618
+ setScore (commentLocator , ids [7 ], - 20 )
619
+
620
+ c9 := store.Comment {Text : "comment to post 2" , Locator : store.Locator {URL : "another-url" , SiteID : "remark42" }}
621
+ ids [8 ], timestamps [8 ] = addCommentGetCreatedTime (t , c9 , ts )
622
+ // #7 by score
623
+ setScore (store.Locator {URL : "another-url" , SiteID : "remark42" }, ids [8 ], - 25 )
624
+
625
+ // delete two comments bringing the total from 9 to 6
626
+ err := srv .DataService .Delete (commentLocator , ids [7 ], store .SoftDelete )
627
+ assert .NoError (t , err )
628
+ err = srv .DataService .Delete (commentLocator , ids [5 ], store .HardDelete )
629
+ assert .NoError (t , err )
630
+ srv .Cache .Flush (cache.FlusherRequest {})
631
+
632
+ sinceTenSecondsAgo := strconv .FormatInt (time .Now ().Add (- time .Second * 10 ).UnixNano ()/ 1000000 , 10 )
633
+ sinceTS := make ([]string , 9 )
634
+ formattedTS := make ([]string , 9 )
635
+ for i , created := range timestamps {
636
+ sinceTS [i ] = strconv .FormatInt (created .UnixNano ()/ 1000000 , 10 )
637
+ formattedTS [i ] = created .Format (time .RFC3339Nano )
638
+ }
639
+ t .Logf ("last timestamp: %v" , timestamps [7 ])
640
+
641
+ testCases := []struct {
642
+ params string
643
+ expectedBody string
644
+ }{
645
+ {"" , fmt .Sprintf (`"info":{"count":7,"first_time":%q,"last_time":%q}` , formattedTS [0 ], formattedTS [8 ])},
646
+ {"url=test-url" , fmt .Sprintf (`"info":{"url":"test-url","count":6,"first_time":%q,"last_time":%q}` , formattedTS [0 ], formattedTS [7 ])},
647
+ {"format=plain" , fmt .Sprintf (`"info":{"count":7,"first_time":%q,"last_time":%q}` , formattedTS [0 ], formattedTS [8 ])},
648
+ {"format=plain&url=test-url" , fmt .Sprintf (`"info":{"url":"test-url","count":6,"first_time":%q,"last_time":%q}` , formattedTS [0 ], formattedTS [7 ])},
649
+ {"since=" + sinceTenSecondsAgo , fmt .Sprintf (`"info":{"count":7,"first_time":%q,"last_time":%q}` , formattedTS [0 ], formattedTS [8 ])},
650
+ {"url=test-url&since=" + sinceTenSecondsAgo , fmt .Sprintf (`"info":{"url":"test-url","count":6,"first_time":%q,"last_time":%q}` , formattedTS [0 ], formattedTS [7 ])},
651
+ {"since=" + sinceTS [0 ], fmt .Sprintf (`"info":{"count":7,"first_time":%q,"last_time":%q}` , formattedTS [0 ], formattedTS [8 ])},
652
+ {"url=test-url&since=" + sinceTS [0 ], fmt .Sprintf (`"info":{"url":"test-url","count":6,"first_time":%q,"last_time":%q}` , formattedTS [0 ], formattedTS [7 ])},
653
+ {"since=" + sinceTS [1 ], fmt .Sprintf (`"info":{"count":6,"first_time":%q,"last_time":%q}` , formattedTS [0 ], formattedTS [8 ])},
654
+ {"url=test-url&since=" + sinceTS [1 ], fmt .Sprintf (`"info":{"url":"test-url","count":5,"first_time":%q,"last_time":%q}` , formattedTS [0 ], formattedTS [7 ])},
655
+ {"since=" + sinceTS [4 ], fmt .Sprintf (`"info":{"count":3,"first_time":%q,"last_time":%q}` , formattedTS [0 ], formattedTS [8 ])},
656
+ {"url=test-url&since=" + sinceTS [4 ], fmt .Sprintf (`"info":{"url":"test-url","count":2,"first_time":%q,"last_time":%q}` , formattedTS [0 ], formattedTS [7 ])},
657
+ {"format=tree" , `"info":{"count":7` },
658
+ {"format=tree&url=test-url" , `"info":{"url":"test-url","count":6` },
659
+ {"format=tree&sort=+time" , `"info":{"count":7` },
660
+ {"format=tree&url=test-url&sort=+time" , `"info":{"url":"test-url","count":6` },
661
+ {"format=tree&sort=-score" , `"info":{"count":7` },
662
+ {"format=tree&url=test-url&sort=-score" , `"info":{"url":"test-url","count":6` },
663
+ {"sort=+time" , fmt .Sprintf (`"score":-25,"vote":0,"time":%q}],"info":{"count":7` , formattedTS [8 ])},
664
+ {"sort=-time" , fmt .Sprintf (`"score":1,"vote":0,"time":%q}],"info":{"count":7` , formattedTS [0 ])},
665
+ {"sort=+score" , fmt .Sprintf (`"score":10,"vote":0,"time":%q}],"info":{"count":7` , formattedTS [2 ])},
666
+ {"sort=+score&url=test-url" , fmt .Sprintf (`"score":10,"vote":0,"time":%q}],"info":{"url":"test-url","count":6` , formattedTS [2 ])},
667
+ {"sort=-score" , fmt .Sprintf (`"score":-25,"vote":0,"time":%q}],"info":{"count":7` , formattedTS [8 ])},
668
+ {"sort=-score&url=test-url" , fmt .Sprintf (`"score":-2,"vote":0,"controversy":1.5874010519681994,"time":%q}],"info":{"url":"test-url","count":6` , formattedTS [6 ])},
669
+ {"sort=-time&since=" + sinceTS [4 ], fmt .Sprintf (`"score":-1,"vote":0,"controversy":2.924017738212866,"time":%q}],"info":{"count":3` , formattedTS [4 ])},
670
+ {"sort=-score&since=" + sinceTS [3 ], fmt .Sprintf (`"score":-25,"vote":0,"time":%q}],"info":{"count":4` , formattedTS [8 ])},
671
+ {"sort=-score&url=test-url&since=" + sinceTS [3 ], fmt .Sprintf (`"score":-2,"vote":0,"controversy":1.5874010519681994,"time":%q}],"info":{"url":"test-url","count":3` , formattedTS [6 ])},
672
+ {"sort=+controversy&url=test-url&since=" + sinceTS [5 ], fmt .Sprintf (`"score":-2,"vote":0,"controversy":1.5874010519681994,"time":%q}],"info":{"url":"test-url","count":1` , formattedTS [6 ])},
673
+ // three comments of which last one deleted and doesn't have controversy so returned last
674
+ {"sort=-controversy&url=test-url&since=" + sinceTS [5 ], fmt .Sprintf (`"score":0,"vote":0,"time":%q,"delete":true}],"info":{"url":"test-url","count":1` , formattedTS [7 ])},
675
+ }
676
+
677
+ for _ , tc := range testCases {
678
+ t .Run (tc .params , func (t * testing.T ) {
679
+ url := fmt .Sprintf (ts .URL + "/api/v1/find?site=remark42&%s" , tc .params )
680
+ body , code := get (t , url )
681
+ assert .Equal (t , http .StatusOK , code )
682
+ assert .Contains (t , body , tc .expectedBody )
683
+ t .Log (body )
684
+ // prevent hit limiter from engaging
685
+ time .Sleep (50 * time .Millisecond )
686
+ })
687
+ }
688
+ }
689
+
536
690
func TestRest_UserInfo (t * testing.T ) {
537
691
ts , _ , teardown := startupT (t )
538
692
defer teardown ()
0 commit comments