internal/store/retention_test.go
151 lines · 4689 bytes
5 symbols in this file
1package store
2
3import (
4 "testing"
5 "time"
6)
7
8func retentionFixture(t *testing.T) (*Store, int64) {
9 t.Helper()
10 s := open(t)
11 if err := s.MigrateUp(); err != nil {
12 t.Fatal(err)
13 }
14 uid, err := s.CreateUser("cmc", true)
15 if err != nil {
16 t.Fatal(err)
17 }
18 return s, uid
19}
20
21func TestSweepRemovesExpiredSessionsAndTokens(t *testing.T) {
22 s, uid := retentionFixture(t)
23 if err := s.CreateWebSession("live", uid, time.Hour); err != nil {
24 t.Fatal(err)
25 }
26 if err := s.CreateWebSession("dead", uid, -time.Hour); err != nil {
27 t.Fatal(err)
28 }
29 if err := s.CreateLoginToken(uid, "stale", -time.Minute); err != nil {
30 t.Fatal(err)
31 }
32
33 // Retention is unset: expired rows still go, since nothing keeps them
34 // meaningful once they cannot authenticate.
35 got, err := s.Sweep(Retention{}, time.Now())
36 if err != nil {
37 t.Fatal(err)
38 }
39 if got["web_sessions"] != 1 || got["login_tokens"] != 1 {
40 t.Fatalf("swept %v", got)
41 }
42 var n int
43 s.DB.QueryRow("SELECT COUNT(*) FROM web_sessions").Scan(&n)
44 if n != 1 {
45 t.Fatalf("%d sessions remain, want the live one", n)
46 }
47 if _, err := s.WebSessionUser("live"); err != nil {
48 t.Fatalf("live session swept: %v", err)
49 }
50}
51
52// Zero retention keeps forever: deleting an audit trail is not a default.
53func TestSweepKeepsWhenUnconfigured(t *testing.T) {
54 s, uid := retentionFixture(t)
55 s.Audit(uid, "cmd repo create", map[string]any{"argv": []string{"a/b"}})
56 s.DB.Exec("UPDATE audit_log SET created_at = '2020-01-01T00:00:00.000Z'")
57
58 if _, err := s.Sweep(Retention{}, time.Now()); err != nil {
59 t.Fatal(err)
60 }
61 var n int
62 s.DB.QueryRow("SELECT COUNT(*) FROM audit_log").Scan(&n)
63 if n != 1 {
64 t.Fatalf("audit row swept with retention unset (%d rows)", n)
65 }
66
67 if _, err := s.Sweep(Retention{Audit: 24 * time.Hour}, time.Now()); err != nil {
68 t.Fatal(err)
69 }
70 s.DB.QueryRow("SELECT COUNT(*) FROM audit_log").Scan(&n)
71 if n != 0 {
72 t.Fatalf("audit row survived its retention (%d rows)", n)
73 }
74}
75
76// A delivery still being retried is live state, however old its first
77// attempt; only finished ones age out.
78func TestSweepKeepsUnfinishedWork(t *testing.T) {
79 s, uid := retentionFixture(t)
80 repoID, err := s.CreateRepo("user", uid, "lib", "public")
81 if err != nil {
82 t.Fatal(err)
83 }
84 if _, err := s.AddWebhook(repoID, "https://example.test/h", "", "*"); err != nil {
85 t.Fatal(err)
86 }
87 for i := 0; i < 3; i++ {
88 if err := s.RecordEvent(repoID, uid, "push", "{}"); err != nil {
89 t.Fatal(err)
90 }
91 }
92 s.DB.Exec("UPDATE webhook_deliveries SET created_at = '2020-01-01T00:00:00.000Z'")
93 s.DB.Exec("UPDATE webhook_deliveries SET delivered_at = '2020-01-02T00:00:00.000Z' WHERE id = 1")
94 s.DB.Exec("UPDATE webhook_deliveries SET failed_at = '2020-01-02T00:00:00.000Z' WHERE id = 2")
95
96 got, err := s.Sweep(Retention{WebhookDeliveries: time.Hour}, time.Now())
97 if err != nil {
98 t.Fatal(err)
99 }
100 if got["webhook_deliveries"] != 2 {
101 t.Fatalf("swept %v, want the delivered and the failed one", got)
102 }
103 var n int
104 s.DB.QueryRow("SELECT COUNT(*) FROM webhook_deliveries").Scan(&n)
105 if n != 1 {
106 t.Fatalf("%d deliveries remain, want the one still being retried", n)
107 }
108}
109
110// events.id is the parent of webhook_deliveries.event_id under ON DELETE
111// CASCADE, so sweeping events would take a queued delivery with it. The
112// sweep skips any event that still has one.
113func TestSweepEventsSpareQueuedDeliveries(t *testing.T) {
114 s, uid := retentionFixture(t)
115 repoID, err := s.CreateRepo("user", uid, "lib", "public")
116 if err != nil {
117 t.Fatal(err)
118 }
119 if _, err := s.AddWebhook(repoID, "https://example.test/h", "", "*"); err != nil {
120 t.Fatal(err)
121 }
122 if err := s.RecordEvent(repoID, uid, "push", "{}"); err != nil {
123 t.Fatal(err)
124 }
125 s.DB.Exec("UPDATE events SET created_at = '2020-01-01T00:00:00.000Z'")
126 s.DB.Exec("UPDATE webhook_deliveries SET created_at = '2020-01-01T00:00:00.000Z'")
127
128 // The delivery has neither delivered_at nor failed_at: still queued.
129 got, err := s.Sweep(Retention{Events: time.Hour, WebhookDeliveries: time.Hour}, time.Now())
130 if err != nil {
131 t.Fatal(err)
132 }
133 if got["events"] != 0 || got["webhook_deliveries"] != 0 {
134 t.Fatalf("swept %v, want nothing while the delivery is queued", got)
135 }
136 var n int
137 s.DB.QueryRow("SELECT COUNT(*) FROM webhook_deliveries").Scan(&n)
138 if n != 1 {
139 t.Fatal("queued delivery cascaded away with its event")
140 }
141
142 // Once it finishes, both go.
143 s.DB.Exec("UPDATE webhook_deliveries SET delivered_at = '2020-01-02T00:00:00.000Z'")
144 if _, err := s.Sweep(Retention{Events: time.Hour, WebhookDeliveries: time.Hour}, time.Now()); err != nil {
145 t.Fatal(err)
146 }
147 s.DB.QueryRow("SELECT COUNT(*) FROM events").Scan(&n)
148 if n != 0 {
149 t.Fatalf("%d events remain after the delivery finished", n)
150 }
151}