oficina / meet Public

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

Plain git — no account needed to clone.

Download

Download this file

1import Foundation
2import XCTest
3
4@testable import CallsKit
5
6/// A handoff the browser never sees: it says what it was begun with, and
7/// hands back `result` when the test lets it.
8final class FakeHandoff: Handoff, @unchecked Sendable {
9 private let gate = DispatchSemaphore(value: 0)
10 private let result: String
11 private(set) var cancelled = false
12 init(result: String) { self.result = result }
13 func browserURL() -> String { "https://calls.oficina.works/native/start?id=x" }
14 func confirmationCode() -> String { "ABCD-1234" }
15 func wait() throws -> String {
16 gate.wait()
17 if cancelled { throw NSError(domain: "test", code: 1) }
18 return result
19 }
20 func approve() { gate.signal() }
21 func cancel() {
22 cancelled = true
23 gate.signal()
24 }
25}
26
27final class BeginLog: @unchecked Sendable {
28 private let lock = NSLock()
29 private var calls: [String] = []
30 var log: [String] { lock.withLock { calls } }
31 func add(_ s: String) { lock.withLock { calls.append(s) } }
32}
33
34@MainActor private func settle(_ done: @MainActor () -> Bool) async {
35 let deadline = Date().addingTimeInterval(5)
36 while !done(), Date() < deadline { try? await Task.sleep(for: .milliseconds(10)) }
37}
38
39private let offer = String(repeating: "A", count: 42) + "w"
40/// A server that cannot be reached (.invalid never resolves), so that a
41/// test that ends in a join sends nothing anywhere.
42private let origin = "https://calls.invalid"
43private let kept = StoredSession(
44 origin: origin, cookies: #"[{"name":"__Host-rastrillo_session","value":"kept"}]"#)
45
46@MainActor private func makeModel(_ store: SessionStore, _ remote: SessionRemote) -> CallsModel {
47 let defaults = UserDefaults(suiteName: "AppLinkTests")!
48 defaults.removePersistentDomain(forName: "AppLinkTests")
49 defaults.set(origin, forKey: CallsServer.key)
50 return CallsModel(store: store, remote: remote, defaults: defaults)
51}
52
53private func link(_ path: String, offer: String? = nil) -> URL {
54 var url = "oficina-calls://join?url=https%3A%2F%2Fcalls.invalid%2F"
55 + path.replacingOccurrences(of: "/", with: "%2F")
56 if let offer { url += "&offer=" + offer }
57 return URL(string: url)!
58}
59
60/// The web's "Join in the app" (2026-10-02): reported by a Calls user as
61/// "App link doesn't work if you're signed in". The link named the
62/// meeting and nothing else, and the app has a session of its own or none,
63/// so a guest signed in on the web — or anyone whose default browser was
64/// not the one they were signed in to — could not get there.
65final class AppLinkTests: XCTestCase {
66 private func remote(_ server: ScriptedRemote, _ begun: BeginLog, _ handoff: FakeHandoff)
67 -> SessionRemote
68 {
69 var remote = server.remote
70 remote.begin = { address, offer in
71 begun.add("\(address) \(offer ?? "-")")
72 return handoff
73 }
74 return remote
75 }
76
77 /// Signed out, from the web: the offer is taken up at once, no browser
78 /// is opened (the one the person came from is waiting), and once it
79 /// confirms, the app joins that meeting as that person.
80 @MainActor func testAnOfferFromTheWebIsTakenUpAndJoins() async {
81 // Asked once, when the sign-in is adopted: a guest's is not kept.
82 let server = ScriptedRemote(checks: [.success("participant")])
83 let begun = BeginLog()
84 let handoff = FakeHandoff(
85 result:
86 #"{"origin":"https://calls.invalid","room":"abc123","cookies":[{"name":"__Host-rastrillo_session","value":"guest"}]}"#
87 )
88 let model = makeModel(MemorySessionStore(), remote(server, begun, handoff))
89 model.openMeetingURL(link("m/abc123", offer: offer))
90 XCTAssertTrue(model.authenticating)
91 XCTAssertTrue(model.handingOff, "confirmed in the browser it came from")
92 XCTAssertEqual(model.pendingLink, "", "started, not offered")
93 await settle { !model.confirmationCode.isEmpty }
94 XCTAssertEqual(begun.log, ["https://calls.invalid/m/abc123 \(offer)"])
95 XCTAssertEqual(model.confirmationCode, "ABCD-1234")
96 handoff.approve()
97 // The join is real and calls.invalid does not resolve, so it is over
98 // quickly: joined, or tried and said why it could not.
99 await settle { model.joining || !model.error.isEmpty }
100 XCTAssertTrue(model.signedIn)
101 XCTAssertEqual(model.room, "abc123")
102 XCTAssertTrue(model.joining || !model.error.isEmpty, "the meeting they asked for, joined")
103 XCTAssertFalse(model.authenticating)
104 model.leave()
105 }
106
107 /// Signed in, as somebody that meeting is not for, and no offer: the
108 /// app says so and offers a sign-in for that meeting only, rather than
109 /// joining and failing.
110 @MainActor func testASignInThatCannotJoinSaysSoAndOffersAVisit() async {
111 let server = ScriptedRemote(checks: [.success("member")], opens: [#"{"room":"","noAccess":true}"#])
112 let begun = BeginLog()
113 let model = makeModel(MemorySessionStore(kept), remote(server, begun, FakeHandoff(result: "")))
114 model.restore()
115 await settle { model.signedIn && !model.restoring }
116 model.openMeetingURL(link("m/abc123"))
117 await settle { !model.opening && !model.pendingLink.isEmpty }
118 XCTAssertEqual(model.pendingLink, "https://calls.invalid/m/abc123")
119 XCTAssertTrue(model.pendingNoAccess)
120 XCTAssertFalse(model.joining)
121 XCTAssertTrue(model.signedIn, "the app's own sign-in stays")
122 XCTAssertTrue(begun.log.isEmpty, "offered, not started")
123 }
124
125 /// Signed in, as somebody that meeting is not for, from the web: the
126 /// person in that browser is handed over for it, as a visit.
127 @MainActor func testASignInThatCannotJoinTakesUpTheWebsOffer() async {
128 let server = ScriptedRemote(checks: [.success("member")], opens: [#"{"room":"","noAccess":true}"#])
129 let begun = BeginLog()
130 let handoff = FakeHandoff(result: "")
131 let model = makeModel(MemorySessionStore(kept), remote(server, begun, handoff))
132 model.restore()
133 await settle { model.signedIn && !model.restoring }
134 model.openMeetingURL(link("m/abc123", offer: offer))
135 await settle { !begun.log.isEmpty }
136 XCTAssertEqual(begun.log, ["https://calls.invalid/m/abc123 \(offer)"])
137 XCTAssertTrue(model.handingOff)
138 model.cancelSignIn()
139 }
140
141 /// Signed in, as someone who may join: the app's own session opens it,
142 /// with no browser, offer or not — the case that always worked.
143 @MainActor func testASignInThatCanJoinNeedsNoBrowser() async {
144 let server = ScriptedRemote(checks: [.success("member")], opens: [#"{"room":"abc123"}"#])
145 let begun = BeginLog()
146 let model = makeModel(MemorySessionStore(kept), remote(server, begun, FakeHandoff(result: "")))
147 model.restore()
148 await settle { model.signedIn && !model.restoring }
149 model.openMeetingURL(link("m/abc123", offer: offer))
150 await settle { model.joining }
151 XCTAssertEqual(model.room, "abc123")
152 XCTAssertTrue(begun.log.isEmpty)
153 model.leave()
154 }
155
156 /// The link launched the app with nothing kept: the offer waits for the
157 /// check of the kept sign-in, then is taken up.
158 @MainActor func testAnOfferThatLaunchedTheAppIsTakenUpAfterTheCheck() async {
159 let server = ScriptedRemote()
160 let begun = BeginLog()
161 let handoff = FakeHandoff(result: "")
162 let model = makeModel(MemorySessionStore(), remote(server, begun, handoff))
163 model.restore()
164 model.openMeetingURL(link("m/abc123", offer: offer))
165 await settle { !begun.log.isEmpty }
166 XCTAssertEqual(begun.log, ["https://calls.invalid/m/abc123 \(offer)"])
167 model.cancelSignIn()
168 }
169
170 /// A link on another server, with nobody able to take it up, says which
171 /// server it is on before anything opens.
172 @MainActor func testALinkOnAnotherServerSaysWhichServer() {
173 let model = makeModel(MemorySessionStore(), ScriptedRemote().remote)
174 model.openMeetingURL(
175 URL(string: "oficina-calls://join?url=https%3A%2F%2Fcalls.example.org%2Fm%2Fabc123")!)
176 XCTAssertEqual(model.pendingLink, "https://calls.example.org/m/abc123")
177 XCTAssertFalse(model.pendingOnServer)
178 XCTAssertEqual(model.pendingHost, "calls.example.org")
179 XCTAssertFalse(model.authenticating)
180 }
181
182 func testOnlyTheOffersTheServerMakesAreTaken() {
183 XCTAssertTrue(CallsModel.validOffer(offer))
184 XCTAssertTrue(CallsModel.validOffer(String(repeating: "_-", count: 21) + "a"))
185 for bad in ["", "short", String(repeating: "A", count: 44), String(repeating: "A", count: 42) + "/",
186 String(repeating: "A", count: 42) + "é"]
187 {
188 XCTAssertFalse(CallsModel.validOffer(bad), bad)
189 }
190 }
191}
192