engagement_test.go
⎇
Raw
1package service
2
3import (
4 "reflect"
5 "testing"
6
7 "vidarchive/internal/models"
8)
9
10func TestGetEngagement(t *testing.T) {
11 svc, dir := newLibrary(t)
12 info := `{"comments":[{"author":"Alice","text":"hello <world>","timestamp":1700000000,"like_count":3,"author_is_uploader":true},{"channel":"Bob","text":"second","time_text":"yesterday"}],"heatmap":[{"start_time":0,"end_time":10,"value":0.5},{"start_time":10,"end_time":20,"value":1.0}]}`
13 writeItem(t, dir, "engagement", `name = "Engagement"`, map[string]string{
14 "video.mp4": "dummy",
15 "info.json": info,
16 })
17
18 comments, heatmap, err := svc.GetEngagement("engagement")
19 if err != nil {
20 t.Fatalf("GetEngagement: %v", err)
21 }
22 if len(comments) != 2 || comments[0].Author != "Alice" || comments[0].LikeCount != 3 || !comments[0].AuthorIsUploader {
23 t.Fatalf("unexpected comments: %+v", comments)
24 }
25 if comments[0].TimeText != "2023-11-14 22:13" {
26 t.Errorf("timestamp fallback = %q, want UTC time", comments[0].TimeText)
27 }
28 if comments[1].Author != "Bob" || comments[1].TimeText != "yesterday" {
29 t.Fatalf("fallback comment fields not parsed: %+v", comments[1])
30 }
31 if len(heatmap) != 2 || heatmap[0].Width != 50 || heatmap[1].Height != 100 {
32 t.Fatalf("unexpected heatmap: %+v", heatmap)
33 }
34}
35
36func TestGetEngagementFiltersAndSortsHeatmap(t *testing.T) {
37 svc, dir := newLibrary(t)
38 writeItem(t, dir, "heatmap-edge", `name = "Heatmap edge"`, map[string]string{
39 "video.mp4": "dummy",
40 "info.json": `{"heatmap":[{"start_time":10,"end_time":20,"value":0.5},{"start_time":2,"end_time":4,"value":1},{"start_time":5,"end_time":4,"value":1},{"start_time":20,"end_time":21,"value":-1}]}`,
41 })
42 _, heatmap, err := svc.GetEngagement("heatmap-edge")
43 if err != nil {
44 t.Fatalf("GetEngagement: %v", err)
45 }
46 if len(heatmap) != 2 {
47 t.Fatalf("valid heatmap segment count = %d, want 2: %+v", len(heatmap), heatmap)
48 }
49 if heatmap[0].StartTime != 2 || heatmap[1].StartTime != 10 {
50 t.Errorf("heatmap order = %v, want ascending start time", heatmap)
51 }
52 for _, segment := range heatmap {
53 if segment.Width <= 0 || segment.Height < 8 || segment.Height > 100 {
54 t.Errorf("invalid normalized heatmap segment: %+v", segment)
55 }
56 }
57}
58
59func TestOrderCommentsNestsRepliesAndCapsDepth(t *testing.T) {
60 comments := []models.Comment{
61 {ID: "root", Parent: "root", Author: "root"},
62 {ID: "reply", Parent: "root", Author: "reply"},
63 {ID: "deep-1", Parent: "reply", Author: "deep-1"},
64 {ID: "deep-2", Parent: "deep-1", Author: "deep-2"},
65 {ID: "deep-3", Parent: "deep-2", Author: "deep-3"},
66 {ID: "deep-4", Parent: "deep-3", Author: "deep-4"},
67 {ID: "deep-5", Parent: "deep-4", Author: "deep-5"},
68 }
69
70 ordered := orderComments(comments)
71 if len(ordered) != len(comments) {
72 t.Fatalf("orderComments dropped comments: %d of %d", len(ordered), len(comments))
73 }
74 for i, want := range []string{"root", "reply", "deep-1", "deep-2", "deep-3", "deep-4", "deep-5"} {
75 if ordered[i].ID != want {
76 t.Errorf("comment %d = %q, want %q", i, ordered[i].ID, want)
77 }
78 }
79 if ordered[1].ReplyTo != "root" || ordered[len(ordered)-1].ReplyTo != "deep-4" {
80 t.Errorf("reply targets = %q, %q; want root and deep-4", ordered[1].ReplyTo, ordered[len(ordered)-1].ReplyTo)
81 }
82 if ordered[len(ordered)-1].Depth != 4 {
83 t.Errorf("deep reply depth = %d, want capped depth 4", ordered[len(ordered)-1].Depth)
84 }
85}
86
87func TestOrderCommentsHandlesMissingParentsCyclesAndSiblings(t *testing.T) {
88 comments := []models.Comment{
89 {ID: "root", Parent: "root", Author: "root"},
90 {ID: "second", Parent: " root ", Author: "second"},
91 {ID: "first", Parent: "root", Author: "first"},
92 {ID: "orphan", Parent: "missing", Author: "orphan"},
93 {ID: "cycle-a", Parent: "cycle-b", Author: "a"},
94 {ID: "cycle-b", Parent: "cycle-a", Author: "b"},
95 }
96
97 ordered := orderComments(comments)
98 if len(ordered) != len(comments) {
99 t.Fatalf("orderComments dropped malformed-tree comments: %d of %d", len(ordered), len(comments))
100 }
101 var ids []string
102 for _, comment := range ordered {
103 ids = append(ids, comment.ID)
104 }
105 if !reflect.DeepEqual(ids[:3], []string{"root", "second", "first"}) {
106 t.Errorf("sibling/root order = %v, want root then source-order siblings", ids)
107 }
108 for _, comment := range ordered {
109 if comment.ID == "orphan" && comment.Depth != 0 {
110 t.Errorf("orphan depth = %d, want top-level", comment.Depth)
111 }
112 }
113}
114
115func TestGetEngagementMalformedSidecarReturnsError(t *testing.T) {
116 svc, dir := newLibrary(t)
117 writeItem(t, dir, "bad-engagement", `name = "Bad engagement"`, map[string]string{
118 "video.mp4": "dummy",
119 "info.json": `{"comments":[`,
120 })
121 if _, _, err := svc.GetEngagement("bad-engagement"); err == nil {
122 t.Fatal("expected malformed engagement JSON to return an error")
123 }
124}
125
126func TestGetEngagementMissingSidecarIsEmpty(t *testing.T) {
127 svc, dir := newLibrary(t)
128 writeItem(t, dir, "no-engagement", `name = "No engagement"`, map[string]string{"video.mp4": "dummy"})
129 comments, heatmap, err := svc.GetEngagement("no-engagement")
130 if err != nil {
131 t.Fatalf("GetEngagement: %v", err)
132 }
133 if len(comments) != 0 || len(heatmap) != 0 {
134 t.Fatalf("expected empty engagement, got comments=%v heatmap=%v", comments, heatmap)
135 }
136}
137