Skip to content

Commit 956f65c

Browse files
committed
Add current method.
1 parent 92d974d commit 956f65c

File tree

5 files changed

+93
-7
lines changed

5 files changed

+93
-7
lines changed

Example/AblyChatExample/Mocks/MockClients.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -563,6 +563,10 @@ class MockOccupancy: Occupancy {
563563
func get() async throws(ARTErrorInfo) -> OccupancyData {
564564
OccupancyData(connections: 10, presenceMembers: 5)
565565
}
566+
567+
func current() throws(ARTErrorInfo) -> AblyChat.OccupancyData? {
568+
OccupancyData(connections: 10, presenceMembers: 5)
569+
}
566570
}
567571

568572
class MockConnection: Connection {

Sources/AblyChat/DefaultOccupancy.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ internal final class DefaultOccupancy: Occupancy {
77
private let logger: InternalLogger
88
private let options: OccupancyOptions
99

10+
private var lastOccupancyData: OccupancyData?
11+
1012
internal init(channel: any InternalRealtimeChannelProtocol, chatAPI: ChatAPI, roomName: String, logger: InternalLogger, options: OccupancyOptions) {
1113
self.channel = channel
1214
self.chatAPI = chatAPI
@@ -40,6 +42,9 @@ internal final class DefaultOccupancy: Occupancy {
4042

4143
let occupancyData = OccupancyData(connections: connections, presenceMembers: presenceMembers)
4244
let occupancyEvent = OccupancyEvent(type: .updated, occupancy: occupancyData)
45+
46+
lastOccupancyData = occupancyData
47+
4348
logger.log(message: "Emitting occupancy event: \(occupancyEvent)", level: .debug)
4449
callback(occupancyEvent)
4550
}
@@ -61,4 +66,15 @@ internal final class DefaultOccupancy: Occupancy {
6166
throw error.toARTErrorInfo()
6267
}
6368
}
69+
70+
// (CHA-O7a) The current method should return the latest occupancy numbers received over the realtime connection in a [meta]occupancy event.
71+
internal func current() throws(ARTErrorInfo) -> OccupancyData? {
72+
// CHA-O7c
73+
if !options.enableEvents {
74+
throw ARTErrorInfo(chatError: .occupancyEventsNotEnabled)
75+
}
76+
// CHA-07a
77+
// CHA-07b
78+
return lastOccupancyData
79+
}
6480
}

Sources/AblyChat/Errors.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ internal enum ChatError {
177177
case clientIdRequired
178178
case attachSerialIsNotDefined
179179
case channelFailedToAttach(cause: ARTErrorInfo?)
180+
case occupancyEventsNotEnabled
180181

181182
internal var codeAndStatusCode: ErrorCodeAndStatusCode {
182183
switch self {
@@ -215,6 +216,8 @@ internal enum ChatError {
215216
.fixedStatusCode(.badRequest)
216217
case .channelFailedToAttach:
217218
.fixedStatusCode(.badRequest)
219+
case .occupancyEventsNotEnabled:
220+
.fixedStatusCode(.badRequest)
218221
}
219222
}
220223

@@ -275,6 +278,8 @@ internal enum ChatError {
275278
"Channel is attached, but attachSerial is not defined."
276279
case let .channelFailedToAttach(cause):
277280
"Channel failed to attach: \(String(describing: cause))"
281+
case .occupancyEventsNotEnabled:
282+
"Cannot perform operation because occupancy events are not enabled for this room."
278283
}
279284
}
280285

@@ -299,7 +304,8 @@ internal enum ChatError {
299304
.messageRejectedByBeforePublishRule,
300305
.messageRejectedByModeration,
301306
.clientIdRequired,
302-
.attachSerialIsNotDefined:
307+
.attachSerialIsNotDefined,
308+
.occupancyEventsNotEnabled:
303309
nil
304310
}
305311
}

Sources/AblyChat/Occupancy.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,15 @@ public protocol Occupancy: AnyObject, Sendable {
2727
* - Returns: A current occupancy of the chat room.
2828
*/
2929
func get() async throws(ARTErrorInfo) -> OccupancyData
30+
31+
/**
32+
* Get the latest occupancy data received from realtime events.
33+
*
34+
* - Returns: The latest occupancy data, or undefined if no realtime events have been received yet.
35+
*
36+
* - Throws: ``ARTErrorInfo`` if occupancy events are not enabled for this room.
37+
*/
38+
func current() throws(ARTErrorInfo) -> OccupancyData?
3039
}
3140

3241
/// `AsyncSequence` variant of receiving room occupancy events.

Tests/AblyChatTests/DefaultRoomOccupancyTests.swift

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ import Testing
55
@MainActor
66
struct DefaultRoomOccupancyTests {
77
// @spec CHA-O3
8+
// @spec CHA-O7b
89
@Test
9-
func requestOccupancyCheck() async throws {
10+
func occupancyGet() async throws {
1011
// Given
1112
let realtime = MockRealtime {
1213
MockHTTPPaginatedResponse(
@@ -25,7 +26,7 @@ struct DefaultRoomOccupancyTests {
2526
chatAPI: chatAPI,
2627
roomName: "basketball",
2728
logger: TestLogger(),
28-
options: .init()
29+
options: .init(enableEvents: true)
2930
)
3031

3132
// When
@@ -34,18 +35,38 @@ struct DefaultRoomOccupancyTests {
3435
// Then
3536
#expect(occupancyInfo.connections == 5)
3637
#expect(occupancyInfo.presenceMembers == 2)
38+
39+
let currentOccupancy = try defaultOccupancy.current()
40+
#expect(currentOccupancy == nil)
3741
}
3842

3943
// @specUntested CHA-O4e - We chose to implement this failure with an idiomatic fatalError instead of throwing, but we can’t test this.
4044

4145
// @spec CHA-O4a
4246
// @spec CHA-O4c
47+
// @spec CHA-O7a
4348
@Test
4449
func usersCanSubscribeToRealtimeOccupancyUpdates() async throws {
4550
// Given
4651
let realtime = MockRealtime()
4752
let chatAPI = ChatAPI(realtime: realtime)
48-
let channel = MockRealtimeChannel(name: "basketball::$chat")
53+
let channel = MockRealtimeChannel(
54+
name: "basketball::$chat",
55+
messageToEmitOnSubscribe: {
56+
let message = ARTMessage()
57+
message.action = .create // arbitrary
58+
message.serial = "" // arbitrary
59+
message.clientId = "" // arbitrary
60+
message.data = [
61+
"metrics": [
62+
"connections": 5, // arbitrary
63+
"presenceMembers": 2, // arbitrary
64+
],
65+
]
66+
message.version = "0" // arbitrary
67+
return message
68+
}()
69+
)
4970
let defaultOccupancy = DefaultOccupancy(
5071
channel: channel,
5172
chatAPI: chatAPI,
@@ -54,17 +75,19 @@ struct DefaultRoomOccupancyTests {
5475
options: .init(enableEvents: true)
5576
)
5677

57-
// CHA-O4a, CHA-O4c
78+
// CHA-O4a, CHA-O4c, CHA-O7a
5879

5980
// When
6081
let subscription = defaultOccupancy.subscribe()
61-
let occupancyData = OccupancyData(connections: 5, presenceMembers: 2)
62-
subscription.emit(OccupancyEvent(type: .updated, occupancy: occupancyData))
6382

6483
// Then
6584
let occupancyEvent = try #require(await subscription.first { @Sendable _ in true })
6685
#expect(occupancyEvent.occupancy.connections == 5)
6786
#expect(occupancyEvent.occupancy.presenceMembers == 2)
87+
88+
let currentOccupancy = try defaultOccupancy.current()
89+
#expect(currentOccupancy?.connections == 5)
90+
#expect(currentOccupancy?.presenceMembers == 2)
6891
}
6992

7093
// @spec CHA-O4g
@@ -98,4 +121,32 @@ struct DefaultRoomOccupancyTests {
98121
#expect(occupancyEvent.occupancy.connections == 0)
99122
#expect(occupancyEvent.occupancy.presenceMembers == 0)
100123
}
124+
125+
// @spec CHA-O7c
126+
@Test
127+
func occupancyCurrentThrowsError() async throws {
128+
// Given
129+
let realtime = MockRealtime()
130+
let chatAPI = ChatAPI(realtime: realtime)
131+
let channel = MockRealtimeChannel(name: "basketball::$chat")
132+
let defaultOccupancy = DefaultOccupancy(
133+
channel: channel,
134+
chatAPI: chatAPI,
135+
roomName: "basketball",
136+
logger: TestLogger(),
137+
// Wnen
138+
options: .init() // enableEvents: false
139+
)
140+
141+
// Then
142+
// TODO: avoids compiler crash (https://github.com/ably/ably-chat-swift/issues/233), revert once Xcode 16.3 released
143+
let doIt = {
144+
_ = try defaultOccupancy.current()
145+
}
146+
await #expect {
147+
try await doIt()
148+
} throws: { error in
149+
error as? ARTErrorInfo == ARTErrorInfo(chatError: .occupancyEventsNotEnabled)
150+
}
151+
}
101152
}

0 commit comments

Comments
 (0)