oficina / meet Public

Clone
git clone https://amadan.net/oficina/meet

Plain git — no account needed to clone.

Download

Download this file

1package calls
2
3import (
4 "context"
5 "net/http"
6 "net/http/httptest"
7 "strings"
8 "sync"
9 "testing"
10
11 "amadan.net/oficina/calls/internal/calls/apns"
12)
13
14// recordingPusher stands in for Apple. The verdict is per token so one
15// test can have a live device and a dead one at the same time.
16type recordingPusher struct {
17 mu sync.Mutex
18 sent []string
19 verdicts map[string]apns.Verdict
20}
21
22func (p *recordingPusher) Push(ctx context.Context, token, platform, name string) (apns.Verdict, error) {
23 p.mu.Lock()
24 defer p.mu.Unlock()
25 p.sent = append(p.sent, token+"|"+platform+"|"+name)
26 if v, ok := p.verdicts[token]; ok {
27 if v == apns.Gone {
28 return v, context.Canceled
29 }
30 return v, nil
31 }
32 return apns.Delivered, nil
33}
34
35func (p *recordingPusher) all() []string {
36 p.mu.Lock()
37 defer p.mu.Unlock()
38 return append([]string(nil), p.sent...)
39}
40
41func deviceJSON(f *homeRouterFixture, body string) *httptest.ResponseRecorder {
42 r := httptest.NewRequest("POST", homeTestOrigin+"/native/devices", strings.NewReader(body))
43 r.Header.Set("Content-Type", "application/json")
44 r.Header.Set("Origin", homeTestOrigin)
45 for _, c := range f.cookies {
46 r.AddCookie(c)
47 }
48 w := httptest.NewRecorder()
49 f.handler.ServeHTTP(w, r)
50 return w
51}
52
53func TestRegisteringADeviceStoresItAndIsIdempotent(t *testing.T) {
54 f := newHomeRouterFixture(t)
55 f.local(t)
56 if w := deviceJSON(f, `{"token":"AABBCC0011","platform":"macos"}`); w.Code != http.StatusNoContent {
57 t.Fatal("refused", w.Code, w.Body.String())
58 }
59 var count int
60 if err := f.app.db.Writer().QueryRow("SELECT count(*) FROM device_tokens").Scan(&count); err != nil {
61 t.Fatal(err)
62 }
63 if count != 1 {
64 t.Fatal("expected one row", count)
65 }
66 // The app re-registers on every launch. That must move last_seen,
67 // not accumulate rows.
68 if w := deviceJSON(f, `{"token":"aabbcc0011","platform":"macos"}`); w.Code != http.StatusNoContent {
69 t.Fatal("re-registration refused", w.Code)
70 }
71 if err := f.app.db.Writer().QueryRow("SELECT count(*) FROM device_tokens").Scan(&count); err != nil {
72 t.Fatal(err)
73 }
74 if count != 1 {
75 t.Fatal("re-registration made a second row", count)
76 }
77 var stored string
78 f.app.db.Writer().QueryRow("SELECT token FROM device_tokens").Scan(&stored)
79 if stored != "aabbcc0011" {
80 t.Fatal("token not normalised to lower case", stored)
81 }
82}
83
84func TestADeviceRegistrationMustBeHexAndAKnownPlatform(t *testing.T) {
85 f := newHomeRouterFixture(t)
86 f.local(t)
87 for name, body := range map[string]string{
88 "not hex": `{"token":"zzzz","platform":"ios"}`,
89 "empty token": `{"token":"","platform":"ios"}`,
90 "no platform": `{"token":"aabb"}`,
91 "bad platform": `{"token":"aabb","platform":"android"}`,
92 "too long": `{"token":"` + strings.Repeat("ab", 200) + `","platform":"ios"}`,
93 "not json": `hello`,
94 } {
95 if w := deviceJSON(f, body); w.Code == http.StatusNoContent {
96 t.Errorf("accepted %s", name)
97 }
98 }
99 var count int
100 f.app.db.Writer().QueryRow("SELECT count(*) FROM device_tokens").Scan(&count)
101 if count != 0 {
102 t.Fatal("a refused registration still stored something", count)
103 }
104}
105
106func TestADeviceRegistrationNeedsBothLocksAndASession(t *testing.T) {
107 f := newHomeRouterFixture(t)
108 f.local(t)
109 // A form content type is the cross-site shape an HTML form can send.
110 form := httptest.NewRequest("POST", homeTestOrigin+"/native/devices", strings.NewReader(""))
111 form.Header.Set("Content-Type", "application/x-www-form-urlencoded")
112 form.Header.Set("Origin", homeTestOrigin)
113 for _, c := range f.cookies {
114 form.AddCookie(c)
115 }
116 w := httptest.NewRecorder()
117 f.handler.ServeHTTP(w, form)
118 if w.Code != http.StatusUnsupportedMediaType {
119 t.Fatal("form post accepted", w.Code)
120 }
121 // And no origin is csrf.Protect's refusal, before the handler.
122 bare := httptest.NewRequest("POST", homeTestOrigin+"/native/devices", strings.NewReader(`{"token":"aabb","platform":"ios"}`))
123 bare.Header.Set("Content-Type", "application/json")
124 for _, c := range f.cookies {
125 bare.AddCookie(c)
126 }
127 w = httptest.NewRecorder()
128 f.handler.ServeHTTP(w, bare)
129 if w.Code == http.StatusNoContent {
130 t.Fatal("origin-less registration accepted")
131 }
132 // Signed out entirely.
133 g := newHomeRouterFixture(t)
134 if w := deviceJSON(g, `{"token":"aabb","platform":"ios"}`); w.Code == http.StatusNoContent {
135 t.Fatal("signed-out registration accepted")
136 }
137}
138
139// The decision this whole feature turns on: who is told.
140func TestAKnockSkipsMembersWhoAreAlreadyLooking(t *testing.T) {
141 f := newHomeRouterFixture(t)
142 f.local(t)
143 if w := deviceJSON(f, `{"token":"aabbcc","platform":"macos"}`); w.Code != http.StatusNoContent {
144 t.Fatal(w.Code)
145 }
146 var personID int64
147 if err := f.app.db.Writer().QueryRow("SELECT person_id FROM device_tokens").Scan(&personID); err != nil {
148 t.Fatal(err)
149 }
150 pusher := &recordingPusher{}
151 f.app.push = pusher
152
153 // In the room: the queue is on their screen already, and a push
154 // would be a second telling of something they can see.
155 f.app.knocked("room1", []int64{personID}, "Bea")
156 if got := pusher.all(); len(got) != 0 {
157 t.Fatal("pushed to a member who was watching", got)
158 }
159
160 // Not in the room: this is the case the feature exists for, and the
161 // case that was impossible before it.
162 f.app.knocked("room1", nil, "Bea")
163 got := pusher.all()
164 if len(got) != 1 || got[0] != "aabbcc|macos|Bea" {
165 t.Fatal("absent member was not told", got)
166 }
167}
168
169func TestAKnockDeletesATokenAppleSaysIsGone(t *testing.T) {
170 f := newHomeRouterFixture(t)
171 f.local(t)
172 deviceJSON(f, `{"token":"deadbeef","platform":"ios"}`)
173 f.app.push = &recordingPusher{verdicts: map[string]apns.Verdict{"deadbeef": apns.Gone}}
174 f.app.knocked("room1", nil, "Bea")
175 var count int
176 f.app.db.Writer().QueryRow("SELECT count(*) FROM device_tokens").Scan(&count)
177 if count != 0 {
178 t.Fatal("a token Apple reported unregistered was kept", count)
179 }
180}
181
182func TestAKnockKeepsATokenWhenAppleIsMerelyUnreachable(t *testing.T) {
183 // The distinction that matters: an outage must never be read as "the
184 // device is gone", or one bad afternoon empties the table.
185 f := newHomeRouterFixture(t)
186 f.local(t)
187 deviceJSON(f, `{"token":"abcdef","platform":"ios"}`)
188 f.app.push = &recordingPusher{verdicts: map[string]apns.Verdict{"abcdef": apns.Retry}}
189 f.app.knocked("room1", nil, "Bea")
190 var count int
191 f.app.db.Writer().QueryRow("SELECT count(*) FROM device_tokens").Scan(&count)
192 if count != 1 {
193 t.Fatal("a retryable refusal deleted the token", count)
194 }
195}
196
197func TestNoPushKeyIsQuietRatherThanBroken(t *testing.T) {
198 // Nil push is every deployment without a key, and it must be a
199 // no-op: knocks still reach everyone in the room over their streams,
200 // exactly as before any of this existed.
201 f := newHomeRouterFixture(t)
202 f.local(t)
203 deviceJSON(f, `{"token":"aabb","platform":"ios"}`)
204 f.app.push = nil
205 f.app.knocked("room1", nil, "Bea")
206}
207