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 "net/http"
5 "net/http/httptest"
6 "net/url"
7 "regexp"
8 "strings"
9 "testing"
10 "time"
11
12 "amadan.net/oficina/calls/callscore"
13)
14
15type nativeRoundTripper func(*http.Request) (*http.Response, error)
16
17func (f nativeRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) { return f(r) }
18
19// The real shared Go client consumes a session produced by the existing Home
20// assertion and explicit linking ceremony. No test-only auth route is mounted.
21func TestNativeCoreUsesHomeSessionAndResumesCall(t *testing.T) {
22 f := newHomeRouterFixture(t)
23 f.link(t)
24 started := f.do("POST", "/meetings/start", "")
25 match := regexp.MustCompile(`href="/m/([A-Za-z0-9]+)/join"`).FindStringSubmatch(started.Body.String())
26 if len(match) != 2 {
27 t.Fatal("missing new meeting")
28 }
29 room := match[1]
30 f.app.ttl.Store(int64(100 * time.Millisecond))
31 server := httptest.NewServer(f.handler)
32 defer server.Close()
33 target, _ := url.Parse(server.URL)
34 cookies := []*http.Cookie{}
35 for _, c := range f.cookies {
36 cookies = append(cookies, c)
37 }
38 client := &http.Client{Transport: nativeRoundTripper(func(req *http.Request) (*http.Response, error) {
39 copy := req.Clone(req.Context())
40 u := *req.URL
41 copy.URL = &u
42 copy.Host = req.URL.Host
43 copy.URL.Host = target.Host
44 copy.URL.Scheme = target.Scheme
45 for _, c := range cookies {
46 copy.AddCookie(c)
47 }
48 return http.DefaultTransport.RoundTrip(copy)
49 })}
50 events := make(chan callscore.Event, 128)
51 native, err := callscore.New(homeTestOrigin, room, client, func(e callscore.Event) { events <- e })
52 if err != nil {
53 t.Fatal(err)
54 }
55 if err := native.Start(); err != nil {
56 t.Fatal(err)
57 }
58 defer native.Close()
59 deadline := time.NewTimer(5 * time.Second)
60 defer deadline.Stop()
61 peer := ""
62 rosters := 0
63 gotICE := false
64 for rosters < 2 || !gotICE {
65 select {
66 case e := <-events:
67 if e.Type == "ice" {
68 gotICE = true
69 }
70 if e.Message != nil && e.Message.Type == "roster" {
71 rosters++
72 s := native.Snapshot()
73 if peer != "" && peer != string(s.Peer) {
74 t.Fatal("resume replaced participation")
75 }
76 peer = string(s.Peer)
77 }
78 case <-deadline.C:
79 t.Fatal("Home native client failed to join and resume")
80 }
81 }
82 if w := f.do("POST", "/meetings/"+room+"/end", ""); w.Code != 303 {
83 t.Fatalf("end: %d", w.Code)
84 }
85 for {
86 select {
87 case e := <-events:
88 if e.State != nil && e.State.Phase == "ended" {
89 if e.State.Reason != "ended" || len(e.State.Peers) != 0 {
90 t.Fatalf("bad terminal state: %+v", e.State)
91 }
92 return
93 }
94 case <-deadline.C:
95 t.Fatal("native client did not observe ended meeting")
96 }
97 }
98}
99
100// Paul's "This meeting is open elsewhere" (Calls 0.3.1): an app that quit
101// or crashed mid-call leaves its participation inside the thirty-second
102// resume window, and the next run of the app, on the same kept session,
103// was refused its fresh stream with 409 — which the core read as
104// "replaced" before the call had begun. The core now takes its own place
105// back through the web's takeover, on the real Home-mode router with its
106// CSRF middleware, and joins.
107func TestNativeCoreTakesBackItsOwnGhostParticipation(t *testing.T) {
108 f := newHomeRouterFixture(t)
109 f.link(t)
110 started := f.do("POST", "/meetings/start", "")
111 match := regexp.MustCompile(`href="/m/([A-Za-z0-9]+)/join"`).FindStringSubmatch(started.Body.String())
112 if len(match) != 2 {
113 t.Fatal("missing new meeting")
114 }
115 room := match[1]
116 server := httptest.NewServer(f.handler)
117 defer server.Close()
118 target, _ := url.Parse(server.URL)
119 cookies := []*http.Cookie{}
120 for _, c := range f.cookies {
121 cookies = append(cookies, c)
122 }
123 client := &http.Client{Transport: nativeRoundTripper(func(req *http.Request) (*http.Response, error) {
124 copy := req.Clone(req.Context())
125 u := *req.URL
126 copy.URL = &u
127 copy.Host = req.URL.Host
128 copy.URL.Host = target.Host
129 copy.URL.Scheme = target.Scheme
130 for _, c := range cookies {
131 copy.AddCookie(c)
132 }
133 return http.DefaultTransport.RoundTrip(copy)
134 })}
135 admitted := func(native *callscore.Room, events chan callscore.Event) string {
136 t.Helper()
137 deadline := time.NewTimer(5 * time.Second)
138 defer deadline.Stop()
139 for {
140 select {
141 case e := <-events:
142 if e.State == nil {
143 continue
144 }
145 if e.State.Phase == "ended" {
146 t.Fatalf("call ended before it began: %q", e.State.Reason)
147 }
148 if e.State.Phase == "admitted" && e.State.Peer != "" {
149 return string(e.State.Peer)
150 }
151 case <-deadline.C:
152 t.Fatal("native client was never admitted")
153 }
154 }
155 }
156 firstEvents := make(chan callscore.Event, 128)
157 first, err := callscore.New(homeTestOrigin, room, client, func(e callscore.Event) {
158 select {
159 case firstEvents <- e:
160 default:
161 }
162 })
163 if err != nil {
164 t.Fatal(err)
165 }
166 if err := first.Start(); err != nil {
167 t.Fatal(err)
168 }
169 ghost := admitted(first, firstEvents)
170 // The app goes away without leaving: no leave signal, the stream
171 // simply drops, as it does when the process ends.
172 first.Close()
173
174 events := make(chan callscore.Event, 128)
175 second, err := callscore.New(homeTestOrigin, room, client, func(e callscore.Event) { events <- e })
176 if err != nil {
177 t.Fatal(err)
178 }
179 if err := second.Start(); err != nil {
180 t.Fatal(err)
181 }
182 defer second.Close()
183 peer := admitted(second, events)
184 if peer == ghost {
185 t.Fatal("the new run should have a participation of its own")
186 }
187 for _, p := range second.Snapshot().Peers {
188 if string(p.Peer) == ghost {
189 t.Fatal("the ghost participation is still in the room")
190 }
191 }
192}
193
194func TestNativeCoreCannotUseAgentBearerAsHumanSession(t *testing.T) {
195 f := newHomeRouterFixture(t)
196 f.link(t)
197 started := f.do("POST", "/meetings/start", "")
198 match := regexp.MustCompile(`href="(/m/[A-Za-z0-9]+)/join"`).FindStringSubmatch(started.Body.String())
199 if len(match) != 2 {
200 t.Fatal("missing meeting")
201 }
202 created := f.do("POST", "/settings/agents", "name=native-test")
203 token := regexp.MustCompile(`value="(mt_[A-Za-z0-9_-]+)"`).FindStringSubmatch(created.Body.String())
204 if len(token) != 2 {
205 t.Fatal("missing agent")
206 }
207 r := httptest.NewRequest("GET", homeTestOrigin+match[1]+"/stream", nil)
208 r.Header.Set("Authorization", "Bearer "+token[1])
209 r.Header.Set("Accept", "text/event-stream")
210 w := httptest.NewRecorder()
211 f.handler.ServeHTTP(w, r)
212 if w.Code == 200 || strings.Contains(w.Body.String(), "event: hello") {
213 t.Fatal("agent entered human call")
214 }
215}
216