Skip to content

Commit de514e1

Browse files
committed
Prepare for connection tests.
1 parent 3e0c731 commit de514e1

File tree

7 files changed

+95
-21
lines changed

7 files changed

+95
-21
lines changed

Sources/AblyChat/ChatClient.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ public actor DefaultChatClient: ChatClient {
7474
self.init(realtime: suppliedRealtime, clientOptions: clientOptions, internalRealtimeClientFactory: DefaultInternalRealtimeClientFactory())
7575
}
7676

77-
internal init(realtime suppliedRealtime: any SuppliedRealtimeClientProtocol, clientOptions: ChatClientOptions?, internalRealtimeClientFactory: any InternalRealtimeClientFactory) {
77+
internal init(realtime suppliedRealtime: any SuppliedRealtimeClientProtocol, clientOptions: ChatClientOptions?, internalRealtimeClientFactory: any InternalRealtimeClientFactory, timerManager: TimerManagerProtocol = TimerManager()) {
7878
self.realtime = suppliedRealtime
7979
self.clientOptions = clientOptions ?? .init()
8080

@@ -84,7 +84,7 @@ public actor DefaultChatClient: ChatClient {
8484
logger = DefaultInternalLogger(logHandler: self.clientOptions.logHandler, logLevel: self.clientOptions.logLevel)
8585
let roomFactory = DefaultRoomFactory()
8686
rooms = DefaultRooms(realtime: internalRealtime, clientOptions: self.clientOptions, logger: logger, roomFactory: roomFactory)
87-
connection = DefaultConnection(realtime: internalRealtime)
87+
connection = DefaultConnection(realtime: internalRealtime, timerManager: timerManager)
8888
}
8989

9090
public nonisolated var clientID: String {

Sources/AblyChat/DefaultConnection.swift

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,13 @@ internal final class DefaultConnection: Connection {
1313
}
1414

1515
private let realtime: any InternalRealtimeClientProtocol
16-
private let timerManager = TimerManager()
16+
private let timerManager: TimerManagerProtocol
1717
private let connectionStatusManager = ConnectionStatusManager()
1818

19-
internal init(realtime: any InternalRealtimeClientProtocol) {
19+
internal init(realtime: any InternalRealtimeClientProtocol, timerManager: TimerManagerProtocol) {
2020
// (CHA-CS3) The initial status and error of the connection will be whatever status the realtime client returns whilst the connection status object is constructed.
2121
self.realtime = realtime
22+
self.timerManager = timerManager
2223
Task {
2324
await connectionStatusManager.updateStatus(to: .init(from: realtime.connection.state))
2425
await connectionStatusManager.updateError(to: realtime.connection.errorReason)
@@ -87,7 +88,7 @@ internal final class DefaultConnection: Connection {
8788
}
8889

8990
// (CHA-CS5b) Not withstanding CHA-CS5a. If a connection state event is observed from the underlying realtime library, the client must emit a status change event. The current status of that event shall reflect the status change in the underlying realtime library, along with the accompanying error.
90-
subscription.emit(statusChange)
91+
// subscription.emit(statusChange) // this call shouldn't be here - "Not withstanding CHA-CS5a" means just that I guess.
9192
Task {
9293
// update local state and error
9394
await connectionStatusManager.updateError(to: stateChange.reason)

Sources/AblyChat/TimerManager.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
import Foundation
22

3-
internal final actor TimerManager {
3+
internal protocol TimerManagerProtocol: Actor {
4+
func setTimer(interval: TimeInterval, handler: @escaping @Sendable () -> Void)
5+
func cancelTimer()
6+
func hasRunningTask() -> Bool
7+
}
8+
9+
internal final actor TimerManager: TimerManagerProtocol {
410
private var currentTask: Task<Void, Never>?
511

612
internal func setTimer(interval: TimeInterval, handler: @escaping @Sendable () -> Void) {

Tests/AblyChatTests/Helpers/Helpers.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ func compareAny(_ any1: Any?, with any2: Any?) -> Bool {
9898
}
9999
if let any1 = any1 as? Int, let any2 = any2 as? Int {
100100
return any1 == any2
101+
} else if let any1 = any1 as? Double, let any2 = any2 as? Double {
102+
return any1 == any2
101103
} else if let any1 = any1 as? Bool, let any2 = any2 as? Bool {
102104
return any1 == any2
103105
} else if let any1 = any1 as? String, let any2 = any2 as? String {
@@ -159,6 +161,16 @@ class MockMethodCallRecorder: @unchecked Sendable {
159161
mutex.unlock()
160162
return result
161163
}
164+
165+
func waitUntil(hasMatching signature: String, arguments: [String: Any], forMaxTimeout timeout: TimeInterval = 1.0) -> Bool {
166+
let startedAt = Date()
167+
while !hasRecord(matching: signature, arguments: arguments) {
168+
if startedAt.distance(to: Date()) > timeout {
169+
return false
170+
}
171+
}
172+
return true
173+
}
162174
}
163175

164176
private extension [MockMethodCallRecorder.MethodArgument] {
Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,28 @@
11
import Ably
22
@testable import AblyChat
33

4-
final class MockConnection: NSObject, InternalConnectionProtocol {
4+
final class MockConnection: NSObject, InternalConnectionProtocol, @unchecked Sendable {
55
let state: ARTRealtimeConnectionState
66

77
let errorReason: ARTErrorInfo?
88

9+
private var stateCallback: ((ARTConnectionStateChange) -> Void)?
10+
911
init(state: ARTRealtimeConnectionState = .initialized, errorReason: ARTErrorInfo? = nil) {
1012
self.state = state
1113
self.errorReason = errorReason
1214
}
1315

14-
func on(_: @escaping (ARTConnectionStateChange) -> Void) -> ARTEventListener {
15-
fatalError("Not implemented")
16+
func on(_ callback: @escaping (ARTConnectionStateChange) -> Void) -> ARTEventListener {
17+
stateCallback = callback
18+
return ARTEventListener()
1619
}
1720

1821
func off(_: ARTEventListener) {
22+
stateCallback = nil
23+
}
24+
25+
func off() {
1926
fatalError("Not implemented")
2027
}
2128
}

Tests/AblyChatTests/Mocks/MockSuppliedRealtime.swift

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import Foundation
44

55
// This mock isn't used much in the tests, since inside the SDK we mainly use `InternalRealtimeClientProtocol` (whose mock is ``MockRealtime``).
66
final class MockSuppliedRealtime: NSObject, SuppliedRealtimeClientProtocol, @unchecked Sendable {
7-
let connection = Connection()
7+
let connection: Connection
88
let channels = Channels()
99
let createWrapperSDKProxyReturnValue: MockSuppliedRealtime?
1010

@@ -21,8 +21,10 @@ final class MockSuppliedRealtime: NSObject, SuppliedRealtimeClientProtocol, @unc
2121
}
2222

2323
init(
24+
connection: Connection = .init(),
2425
createWrapperSDKProxyReturnValue: MockSuppliedRealtime? = nil
2526
) {
27+
self.connection = connection
2628
self.createWrapperSDKProxyReturnValue = createWrapperSDKProxyReturnValue
2729
}
2830

@@ -336,24 +338,28 @@ final class MockSuppliedRealtime: NSObject, SuppliedRealtimeClientProtocol, @unc
336338
}
337339
}
338340

339-
final class Connection: NSObject, ConnectionProtocol {
340-
var id: String? {
341-
fatalError("Not implemented")
342-
}
341+
final class Connection: NSObject, ConnectionProtocol, @unchecked Sendable {
342+
let state: ARTRealtimeConnectionState
343343

344-
var key: String? {
345-
fatalError("Not implemented")
344+
let errorReason: ARTErrorInfo?
345+
346+
private let stateCallbackLock = NSLock()
347+
private var stateCallback: ((ARTConnectionStateChange) -> Void)?
348+
349+
init(state: ARTRealtimeConnectionState = .initialized, errorReason: ARTErrorInfo? = nil) {
350+
self.state = state
351+
self.errorReason = errorReason
346352
}
347353

348-
var maxMessageSize: Int {
354+
var id: String? {
349355
fatalError("Not implemented")
350356
}
351357

352-
var state: ARTRealtimeConnectionState {
358+
var key: String? {
353359
fatalError("Not implemented")
354360
}
355361

356-
var errorReason: ARTErrorInfo? {
362+
var maxMessageSize: Int {
357363
fatalError("Not implemented")
358364
}
359365

@@ -381,8 +387,11 @@ final class MockSuppliedRealtime: NSObject, SuppliedRealtimeClientProtocol, @unc
381387
fatalError("Not implemented")
382388
}
383389

384-
func on(_: @escaping (ARTConnectionStateChange) -> Void) -> ARTEventListener {
385-
fatalError("Not implemented")
390+
func on(_ callback: @escaping (ARTConnectionStateChange) -> Void) -> ARTEventListener {
391+
stateCallbackLock.withLock {
392+
stateCallback = callback
393+
}
394+
return ARTEventListener()
386395
}
387396

388397
func once(_: ARTRealtimeConnectionEvent, callback _: @escaping (ARTConnectionStateChange) -> Void) -> ARTEventListener {
@@ -404,5 +413,12 @@ final class MockSuppliedRealtime: NSObject, SuppliedRealtimeClientProtocol, @unc
404413
func off() {
405414
fatalError("Not implemented")
406415
}
416+
417+
func transitionToState(_ state: ARTRealtimeConnectionState, event: ARTRealtimeConnectionEvent, error: ARTErrorInfo? = nil) {
418+
let stateChange = ARTConnectionStateChange(current: state, previous: self.state, event: event, reason: error)
419+
stateCallbackLock.withLock {
420+
stateCallback?(stateChange)
421+
}
422+
}
407423
}
408424
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
@testable import AblyChat
2+
import Foundation
3+
4+
internal final actor MockTimerManager: TimerManagerProtocol {
5+
let callRecorder = MockMethodCallRecorder()
6+
7+
private var handler: (@Sendable () -> Void)?
8+
9+
internal func setTimer(interval: TimeInterval, handler: @escaping @Sendable () -> Void) {
10+
callRecorder.addRecord(
11+
signature: "setTimer(interval:handler:)",
12+
arguments: ["interval": interval]
13+
)
14+
self.handler = handler
15+
}
16+
17+
internal func cancelTimer() {
18+
handler = nil
19+
callRecorder.addRecord(
20+
signature: "cancelTimer",
21+
arguments: [:]
22+
)
23+
}
24+
25+
internal func hasRunningTask() -> Bool {
26+
handler != nil
27+
}
28+
29+
internal func expireTimer() {
30+
handler?()
31+
}
32+
}

0 commit comments

Comments
 (0)