Repository navigation
Expand file tree
/
Copy pathsummary_snooze_test.go
More file actions
112 lines (108 loc) · 4.82 KB
/
Copy pathsummary_snooze_test.go
File metadata and controls
112 lines (108 loc) · 4.82 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
package main
import (
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
"time"
)
func snoozeFixture(t *testing.T, lastActive time.Time) *summarizer {
t.Helper()
at := lastActive.Add(time.Hour)
s := &summarizer{enabled: true, provider: "claude", stateDir: t.TempDir(), now: func() time.Time { return at }}
s.latest.Store(&summarySnapshot{At: at, Provider: "claude", Projects: []*summarySnapshotProject{{Project: "acme/api", Name: "Acme API", Topics: []*summarySnapshotTopic{
{summaryTopic: summaryTopic{ID: "keys", Name: "Rotate API keys", Status: "Waiting on review", LastActive: lastActive}},
{summaryTopic: summaryTopic{ID: "docs", Name: "Write the API guide", LastActive: lastActive}},
}}}})
return s
}
func TestSummarySnoozeUntilActiveAgain(t *testing.T) {
active := time.Date(2026, 11, 6, 9, 0, 0, 0, time.UTC)
s := snoozeFixture(t, active)
if err := s.setSnooze("invented", true); err != errSummaryTopicUnknown {
t.Fatalf("unknown topic: %v", err)
}
if err := s.setSnooze("keys", true); err != nil {
t.Fatal(err)
}
fi, err := os.Stat(filepath.Join(s.stateDir, "snoozed.json"))
if err != nil || fi.Mode().Perm() != 0600 {
t.Fatalf("snooze file privacy: %v %v", fi, err)
}
view, snoozed := summaryWithoutSnoozed(s.latest.Load(), s.snoozes())
if len(view.Projects[0].Topics) != 1 || view.Projects[0].Topics[0].ID != "docs" || len(snoozed) != 1 || snoozed[0].Topic.ID != "keys" || snoozed[0].ProjectName != "Acme API" || !snoozed[0].Snooze.Through.Equal(active) {
t.Fatalf("snoozed view: %+v %+v", view.Projects[0].Topics, snoozed)
}
if len(s.latest.Load().Projects[0].Topics) != 2 {
t.Fatal("the saved snapshot itself changed")
}
// New activity in a later summary brings the topic back.
later := snoozeFixture(t, active.Add(time.Minute))
view, snoozed = summaryWithoutSnoozed(later.latest.Load(), s.snoozes())
if len(view.Projects[0].Topics) != 2 || len(snoozed) != 0 {
t.Fatalf("active topic stayed snoozed: %+v %+v", view.Projects[0].Topics, snoozed)
}
if err := s.setSnooze("keys", false); err != nil {
t.Fatal(err)
}
if _, snoozed = summaryWithoutSnoozed(s.latest.Load(), s.snoozes()); len(snoozed) != 0 {
t.Fatal("unsnooze kept the topic snoozed")
}
}
func TestSummarySnoozeRoutes(t *testing.T) {
logs := captureLog(t)
s := snoozeFixture(t, time.Now().Add(-time.Hour))
h := serve(&scanner{}, &prTracker{}, &sessionIndex{}, nil, s)
body := get(t, h, "GET", "/summary", local).Body.String()
if !strings.Contains(body, `data-summary-snooze="keys" data-snooze="true"`) || strings.Contains(body, `aria-label="Summary views"`) {
t.Fatal("no Snooze button, or tabs shown with nothing snoozed")
}
token := pageToken(t, h, local)
post := func(remote, origin, token, body string) int {
req := httptest.NewRequest("POST", "/api/summary/snooze", strings.NewReader(body))
req.RemoteAddr, req.Host = remote, "localhost:4777"
if origin != "" {
req.Header.Set("Origin", origin)
}
if token != "" {
req.Header.Set(removeTokenHeader, token)
}
rec := httptest.NewRecorder()
h.ServeHTTP(rec, req)
return rec.Code
}
for _, tc := range []struct {
name, remote, origin, token, body string
code int
}{
{"LAN", lanPeer, pageOrigin, token, `{"topic":"keys","snooze":true}`, 403},
{"no origin", local, "", token, `{"topic":"keys","snooze":true}`, 403},
{"foreign origin", local, "https://evil.example", token, `{"topic":"keys","snooze":true}`, 403},
{"wrong token", local, pageOrigin, token + "x", `{"topic":"keys","snooze":true}`, 403},
{"bad body", local, pageOrigin, token, `nope`, 400},
{"unknown topic", local, pageOrigin, token, `{"topic":"invented","snooze":true}`, 404},
{"snooze", local, pageOrigin, token, `{"topic":"keys","snooze":true}`, 200},
} {
if code := post(tc.remote, tc.origin, tc.token, tc.body); code != tc.code {
t.Fatalf("%s: %d, want %d", tc.name, code, tc.code)
}
}
body = get(t, h, "GET", "/summary", local).Body.String()
if strings.Contains(body, "Rotate API keys") || !strings.Contains(body, `href="/summary?view=snoozed"`) || !strings.Contains(body, "Write the API guide") {
t.Fatal("snoozed topic still listed, or no Snoozed tab")
}
body = get(t, h, "GET", "/summary?view=snoozed", local).Body.String()
if !strings.Contains(body, "Rotate API keys") || !strings.Contains(body, `data-snooze="false"`) || strings.Contains(body, "Write the API guide") || !strings.Contains(body, "Acme API") {
t.Fatal("snoozed view does not list the topic with Unsnooze")
}
if code := post(local, pageOrigin, token, `{"topic":"keys","snooze":false}`); code != 200 {
t.Fatalf("unsnooze: %d", code)
}
if body = get(t, h, "GET", "/summary", local).Body.String(); !strings.Contains(body, "Rotate API keys") {
t.Fatal("unsnoozed topic did not come back")
}
if logs.Len() > 0 && strings.Contains(logs.String(), "template") {
t.Fatalf("template errors: %s", logs)
}
}