oficina / meet Public

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

Plain git — no account needed to clone.

Download

Download this file

1package callscore
2
3import (
4 "encoding/json"
5 "net/http"
6 "net/http/httptest"
7 "strings"
8 "sync/atomic"
9 "testing"
10)
11
12func TestStartMeetingSendsBothLocksAndAnswersTheSqid(t *testing.T) {
13 var origin, contentType, method, path atomic.Value
14 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
15 origin.Store(r.Header.Get("Origin"))
16 contentType.Store(r.Header.Get("Content-Type"))
17 method.Store(r.Method)
18 path.Store(r.URL.Path)
19 json.NewEncoder(w).Encode(map[string]string{"sqid": "abc123", "title": "Call, 17 Sep 11:23"})
20 }))
21 defer server.Close()
22 sqid, err := StartMeeting(t.Context(), server.URL, server.Client())
23 if err != nil {
24 t.Fatal(err)
25 }
26 if sqid != "abc123" {
27 t.Fatal("wrong meeting", sqid)
28 }
29 if method.Load() != "POST" || path.Load() != "/native/meetings/start" {
30 t.Fatal("wrong request", method.Load(), path.Load())
31 }
32 // csrf.Protect refuses a POST that carries neither, and it is worth a
33 // test because the failure is a 403 on a live server and nothing a
34 // unit test of the handler would ever see.
35 if origin.Load() != server.URL {
36 t.Fatal("origin not sent", origin.Load())
37 }
38 if contentType.Load() != "application/json" {
39 t.Fatal("content type not sent", contentType.Load())
40 }
41}
42
43func TestStartMeetingRefusesAnAnswerItCannotOpen(t *testing.T) {
44 for _, body := range []string{`{"sqid":""}`, `{"sqid":"../admin"}`, `{"sqid":"has space"}`, `{"sqid":"` + strings.Repeat("a", 33) + `"}`, `not json`, `{}`} {
45 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
46 w.Write([]byte(body))
47 }))
48 if _, err := StartMeeting(t.Context(), server.URL, server.Client()); err == nil {
49 t.Errorf("accepted %q", body)
50 }
51 server.Close()
52 }
53}
54
55func TestStartMeetingNeverFollowsARedirectWithTheSession(t *testing.T) {
56 // A session that went comes back as the sign-in page's redirect. Follow
57 // it and the app carries a credential to wherever it points, then
58 // decodes a page as JSON.
59 var calls atomic.Int32
60 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
61 calls.Add(1)
62 http.Redirect(w, r, "/signin", http.StatusSeeOther)
63 }))
64 defer server.Close()
65 _, err := StartMeeting(t.Context(), server.URL, server.Client())
66 if err == nil {
67 t.Fatal("redirect accepted")
68 }
69 if !strings.Contains(err.Error(), "sign in") {
70 t.Fatal("unhelpful error", err)
71 }
72 if calls.Load() != 1 {
73 t.Fatal("followed the redirect", calls.Load())
74 }
75}
76
77func TestStartMeetingRefusesAnUnsafeOriginBeforeSendingAnything(t *testing.T) {
78 var calls atomic.Int32
79 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { calls.Add(1) }))
80 defer server.Close()
81 for _, origin := range []string{"http://example.test", "https://alice:secret@example.test", "https://example.test?x=1", "https://example.test/m/abc", "", "not a url"} {
82 if _, err := StartMeeting(t.Context(), origin, server.Client()); err == nil {
83 t.Errorf("accepted %q", origin)
84 }
85 }
86 if calls.Load() != 0 {
87 t.Fatal("a refused origin still reached a server", calls.Load())
88 }
89 if _, err := StartMeeting(t.Context(), server.URL, nil); err == nil {
90 t.Fatal("accepted a client with no session")
91 }
92}
93
94func TestStartMeetingNamesTheRefusalItCanDoSomethingAbout(t *testing.T) {
95 for _, c := range []struct {
96 status int
97 want string
98 }{
99 {http.StatusUnauthorized, "sign in"},
100 {http.StatusForbidden, "cannot start meetings"},
101 {http.StatusNotFound, "cannot start meetings"},
102 {http.StatusInternalServerError, "just now"},
103 } {
104 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
105 http.Error(w, "no", c.status)
106 }))
107 _, err := StartMeeting(t.Context(), server.URL, server.Client())
108 if err == nil || !strings.Contains(err.Error(), c.want) {
109 t.Errorf("status %d gave %v, wanted something about %q", c.status, err, c.want)
110 }
111 server.Close()
112 }
113}
114
115func TestRegisterDeviceSendsTheTokenAndBothLocks(t *testing.T) {
116 var body, origin, contentType atomic.Value
117 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
118 origin.Store(r.Header.Get("Origin"))
119 contentType.Store(r.Header.Get("Content-Type"))
120 var in map[string]string
121 json.NewDecoder(r.Body).Decode(&in)
122 body.Store(in)
123 w.WriteHeader(http.StatusNoContent)
124 }))
125 defer server.Close()
126 if err := RegisterDevice(t.Context(), server.URL, server.Client(), "aabbcc", "macos"); err != nil {
127 t.Fatal(err)
128 }
129 in := body.Load().(map[string]string)
130 if in["token"] != "aabbcc" || in["platform"] != "macos" {
131 t.Fatal("wrong body", in)
132 }
133 if origin.Load() != server.URL || contentType.Load() != "application/json" {
134 t.Fatal("missing a lock", origin.Load(), contentType.Load())
135 }
136}
137
138func TestRegisterDeviceRefusesAnUnknownPlatformWithoutCallingTheServer(t *testing.T) {
139 var calls atomic.Int32
140 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { calls.Add(1) }))
141 defer server.Close()
142 if err := RegisterDevice(t.Context(), server.URL, server.Client(), "aabb", "android"); err == nil {
143 t.Fatal("accepted an unknown platform")
144 }
145 if calls.Load() != 0 {
146 t.Fatal("called the server anyway")
147 }
148}
149
150func TestRegisterDeviceOnAnOlderServerIsAPlainRefusal(t *testing.T) {
151 // A server without the endpoint has no push. That is a fact about
152 // the server, not a failure worth showing anyone.
153 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
154 http.NotFound(w, r)
155 }))
156 defer server.Close()
157 err := RegisterDevice(t.Context(), server.URL, server.Client(), "aabb", "ios")
158 if err == nil || !strings.Contains(err.Error(), "does not take device registrations") {
159 t.Fatal("unhelpful error", err)
160 }
161}
162