internal/push/push_test.go
112 lines · 2978 bytes
5 symbols in this file
1package push
2
3import (
4 "context"
5 "net/http"
6 "testing"
7 "time"
8
9 "gitbay.org/gitbay/internal/store"
10)
11
12// testStoreWithQueuedPush opens an in-memory store, creates a user with
13// push enabled, registers one device and enqueues one push for it.
14func testStoreWithQueuedPush(t *testing.T, token string) *store.Store {
15 t.Helper()
16 st, err := store.Open(":memory:")
17 if err != nil {
18 t.Fatal(err)
19 }
20 t.Cleanup(func() { st.Close() })
21 if err := st.MigrateUp(); err != nil {
22 t.Fatal(err)
23 }
24 uid, err := st.CreateUser("alice", false)
25 if err != nil {
26 t.Fatal(err)
27 }
28 if err := st.SetPushEnabled(uid, true); err != nil {
29 t.Fatal(err)
30 }
31 if _, err := st.AddPushDevice(uid, token, "iphone"); err != nil {
32 t.Fatal(err)
33 }
34 if err := st.EnqueuePush(uid, "krz/gitbay", "cmc opened issue #12", "krz/gitbay/issues/12"); err != nil {
35 t.Fatal(err)
36 }
37 return st
38}
39
40// countPushDevices counts the test user's own devices. testStoreWithQueuedPush
41// always creates "alice", so looking her up here keeps the helper's signature
42// matching the brief's test code, which passes no user id.
43func countPushDevices(t *testing.T, st *store.Store) int {
44 t.Helper()
45 u, err := st.UserByUsername("alice")
46 if err != nil {
47 t.Fatal(err)
48 }
49 devices, err := st.PushDevices(u.ID)
50 if err != nil {
51 t.Fatal(err)
52 }
53 return len(devices)
54}
55
56func TestDrainSendsAndMarks(t *testing.T) {
57 var hits int
58 c, _ := fakeAPNs(t, func(w http.ResponseWriter, r *http.Request) {
59 hits++
60 w.WriteHeader(200)
61 })
62 st := testStoreWithQueuedPush(t, "tok-a")
63 d := &Deliverer{St: st, Cl: c, RetryBase: time.Millisecond, MaxAttempts: 5}
64
65 d.drain(context.Background())
66
67 if hits != 1 {
68 t.Fatalf("sent %d times, want 1", hits)
69 }
70 if due, _ := st.DuePush(20); len(due) != 0 {
71 t.Fatalf("row still due after a 200")
72 }
73}
74
75func TestDrainReapsADeadToken(t *testing.T) {
76 c, _ := fakeAPNs(t, func(w http.ResponseWriter, r *http.Request) {
77 w.WriteHeader(410)
78 w.Write([]byte(`{"reason":"Unregistered"}`))
79 })
80 st := testStoreWithQueuedPush(t, "tok-a")
81 d := &Deliverer{St: st, Cl: c, RetryBase: time.Millisecond, MaxAttempts: 5}
82
83 d.drain(context.Background())
84
85 if due, _ := st.DuePush(20); len(due) != 0 {
86 t.Fatalf("queue survived the reap")
87 }
88 // The device is gone, not merely its queue row.
89 if n := countPushDevices(t, st); n != 0 {
90 t.Fatalf("%d devices left after 410", n)
91 }
92}
93
94func TestDrainBacksOffThenDeadLetters(t *testing.T) {
95 c, _ := fakeAPNs(t, func(w http.ResponseWriter, r *http.Request) {
96 w.WriteHeader(503)
97 })
98 st := testStoreWithQueuedPush(t, "tok-a")
99 d := &Deliverer{St: st, Cl: c, RetryBase: time.Nanosecond, MaxAttempts: 3}
100
101 // Three passes: two back off, the third gives up.
102 for i := 0; i < 3; i++ {
103 d.drain(context.Background())
104 }
105 if due, _ := st.DuePush(20); len(due) != 0 {
106 t.Fatalf("row still due after MaxAttempts")
107 }
108 // A transient failure must not take the device with it.
109 if n := countPushDevices(t, st); n != 1 {
110 t.Fatalf("device reaped on a 503")
111 }
112}