|
| 1 | +import Foundation |
| 2 | +import Testing |
| 3 | +import ZeroTesting |
| 4 | + |
| 5 | +@testable import Zero |
| 6 | + |
| 7 | +/// Two clients of one client group in one process, checked against the JS |
| 8 | +/// client (`packages/zero-client` active clients). |
| 9 | +@Suite(.serialized) @MainActor struct SweepActiveClientsTests { |
| 10 | + static let create = defineMutator("issue.create") { (tx, id: String) in |
| 11 | + try tx.mutate.issue.insert([ |
| 12 | + "id": .string(id), "title": "", "description": "", "closed": false, "ownerId": .null, |
| 13 | + "createdAt": 1, |
| 14 | + ]) |
| 15 | + } |
| 16 | + |
| 17 | + func makeZero( |
| 18 | + storageKey: String, sockets: MockSocketFactory, scheduler: ManualScheduler |
| 19 | + ) throws -> Zero<Void> { |
| 20 | + var options = ZeroOptions( |
| 21 | + cacheURL: "http://zero.test", userID: "u", storageKey: storageKey, storage: .memory) |
| 22 | + options.logSink = RecordingLogSink() |
| 23 | + return try Zero( |
| 24 | + schema: .test, mutators: [Self.create], options: options, scheduler: scheduler, |
| 25 | + socketFactory: sockets) |
| 26 | + } |
| 27 | + |
| 28 | + // MARK: - Two clients in one client group |
| 29 | + |
| 30 | + /// Opens the newest socket and acknowledges it. |
| 31 | + func connect(_ sockets: MockSocketFactory, _ scheduler: ManualScheduler) throws -> MockSocket { |
| 32 | + scheduler.runDueTimers() |
| 33 | + let socket = try #require(sockets.sockets.last) |
| 34 | + socket.receive(text: #"["connected",{"wsid":"w"}]"#) |
| 35 | + scheduler.runDueTimers() |
| 36 | + return socket |
| 37 | + } |
| 38 | + |
| 39 | + /// JS lists every client of the group running in the process |
| 40 | + /// (`ActiveClientsManager`; without `navigator.locks` its fallback lock |
| 41 | + /// manager is process-wide). zero-cache deletes the clients of the group |
| 42 | + /// that are not in `activeClients` (`view-syncer.ts`), so listing only |
| 43 | + /// itself makes the server delete the other client's queries while it |
| 44 | + /// runs. |
| 45 | + @Test func initConnectionListsTheOtherActiveClientOfTheGroup() throws { |
| 46 | + let scheduler = ManualScheduler(now: 1_700_000_000_000) |
| 47 | + let sockets = MockSocketFactory() |
| 48 | + let key = randomID() |
| 49 | + let first = try makeZero(storageKey: key, sockets: sockets, scheduler: scheduler) |
| 50 | + _ = try connect(sockets, scheduler) |
| 51 | + let second = try makeZero(storageKey: key, sockets: sockets, scheduler: scheduler) |
| 52 | + #expect(second.clientGroupID == first.clientGroupID) |
| 53 | + let socket = try connect(sockets, scheduler) |
| 54 | + let initConnection = try SecProtocols.decode(#require(socket.secProtocol))[ |
| 55 | + "initConnectionMessage"]?[1] |
| 56 | + let active = Set(initConnection?["activeClients"]?.arrayValue?.compactMap(\.stringValue) ?? []) |
| 57 | + #expect(active == [first.clientID, second.clientID]) |
| 58 | + first.close() |
| 59 | + second.close() |
| 60 | + } |
| 61 | + |
| 62 | + /// JS: "We should send a deleteClient when a Zero instance is closed" |
| 63 | + /// (`zero.test.ts`): the other client of the group sends `deleteClients` |
| 64 | + /// for the closed one, once. |
| 65 | + @Test func closingAClientMakesTheOtherOfTheGroupDeleteIt() throws { |
| 66 | + let scheduler = ManualScheduler(now: 1_700_000_000_000) |
| 67 | + let sockets = MockSocketFactory() |
| 68 | + let key = randomID() |
| 69 | + let first = try makeZero(storageKey: key, sockets: sockets, scheduler: scheduler) |
| 70 | + _ = try connect(sockets, scheduler) |
| 71 | + let second = try makeZero(storageKey: key, sockets: sockets, scheduler: scheduler) |
| 72 | + let socket = try connect(sockets, scheduler) |
| 73 | + #expect(second.clientGroupID == first.clientGroupID) |
| 74 | + |
| 75 | + first.close() |
| 76 | + scheduler.runDueTimers() |
| 77 | + let deletes = socket.messages.filter { $0.hasPrefix(#"["deleteClients""#) } |
| 78 | + #expect(deletes == [#"["deleteClients",{"clientIDs":["\#(first.clientID)"]}]"#]) |
| 79 | + second.close() |
| 80 | + } |
| 81 | +} |
0 commit comments