internal/push/push_test.go

f8b976a97290a20d552056a999511f5d27d8e8ec
gitbay/internal/push/push_test.go history · blame · raw

112 lines · 2978 bytes

  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}