-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
1 changed file
with
199 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,199 @@ | ||
import Ably | ||
@testable import AblyChat | ||
import Testing | ||
|
||
struct DefaultRoomTypingTests { | ||
// MARK: - Test helpers | ||
|
||
// @spec CHA-T2 | ||
@Test | ||
func retrieveCurrentlyTypingClientIDs() async throws { | ||
// Given | ||
let typingPresence = MockRealtimePresence(["client1", "client2"].map { .init(clientId: $0) }) | ||
let channel = MockRealtimeChannel(name: "basketball::$chat::$typingIndicators", mockPresence: typingPresence) | ||
let featureChannel = MockFeatureChannel(channel: channel, resultOfWaitToBeAblePerformPresenceOperations: .success(())) | ||
let defaultTyping = DefaultTyping(featureChannel: featureChannel, roomID: "basketball", clientID: "mockClientId", logger: TestLogger(), timeout: 5) | ||
|
||
// When | ||
let typingInfo = try await defaultTyping.get() | ||
|
||
// Then | ||
#expect(typingInfo.sorted() == ["client1", "client2"]) | ||
} | ||
|
||
// @specPartial CHA-T2c | ||
@Test | ||
func retrieveCurrentlyTypingClientIDsWhileAttaching() async throws { | ||
// Given: A DefaultRoomLifecycleManager, with an ATTACH operation in progress and hence in the ATTACHING status | ||
let contributor = RoomLifecycleHelper.createContributor(feature: .presence, attachBehavior: .completeAndChangeState(.success, newState: .attached, delayInMilliseconds: RoomLifecycleHelper.fakeNetworkDelay)) | ||
let lifecycleManager = await RoomLifecycleHelper.createManager(contributors: [contributor]) | ||
|
||
// Given: A DefaultPresence with DefaultFeatureChannel and MockRoomLifecycleContributor | ||
let realtimePresence = MockRealtimePresence(["client1"].map { .init(clientId: $0) }) | ||
let channel = MockRealtimeChannel(name: "basketball::$chat::$chatMessages", mockPresence: realtimePresence) | ||
let featureChannel = DefaultFeatureChannel(channel: channel, contributor: contributor, roomLifecycleManager: lifecycleManager) | ||
let defaultTyping = DefaultTyping(featureChannel: featureChannel, roomID: "basketball", clientID: "mockClientId", logger: TestLogger(), timeout: 5) | ||
|
||
let attachingStatusWaitSubscription = await lifecycleManager.testsOnly_subscribeToStatusChangeWaitEvents() | ||
|
||
// When: The room is in the attaching state | ||
let roomStatusSubscription = await lifecycleManager.onRoomStatusChange(bufferingPolicy: .unbounded) | ||
|
||
let attachOperationID = UUID() | ||
async let _ = lifecycleManager.performAttachOperation(testsOnly_forcingOperationID: attachOperationID) | ||
|
||
// Wait for room to become ATTACHING | ||
_ = try #require(await roomStatusSubscription.attachingElements().first { _ in true }) | ||
|
||
// When: And presence get is called | ||
_ = try await defaultTyping.get() | ||
|
||
// Then: The manager was waiting for its room status to change before presence `get` was called | ||
_ = try #require(await attachingStatusWaitSubscription.first { _ in true }) | ||
} | ||
|
||
// @specPartial CHA-T2c | ||
@Test | ||
func retrieveCurrentlyTypingClientIDsWhileAttachingWithFailure() async throws { | ||
// Given: attachment failure | ||
let attachError = ARTErrorInfo(domain: "SomeDomain", code: 123) | ||
|
||
// Given: A DefaultRoomLifecycleManager, with an ATTACH operation in progress and hence in the ATTACHING status | ||
let contributor = RoomLifecycleHelper.createContributor(feature: .presence, attachBehavior: .completeAndChangeState(.failure(attachError), newState: .failed, delayInMilliseconds: RoomLifecycleHelper.fakeNetworkDelay)) | ||
let lifecycleManager = await RoomLifecycleHelper.createManager(contributors: [contributor]) | ||
|
||
// Given: A DefaultPresence with DefaultFeatureChannel and MockRoomLifecycleContributor | ||
let realtimePresence = MockRealtimePresence(["client1"].map { .init(clientId: $0) }) | ||
let channel = MockRealtimeChannel(mockPresence: realtimePresence) | ||
let featureChannel = DefaultFeatureChannel(channel: channel, contributor: contributor, roomLifecycleManager: lifecycleManager) | ||
let defaultTyping = DefaultTyping(featureChannel: featureChannel, roomID: "basketball", clientID: "mockClientId", logger: TestLogger(), timeout: 5) | ||
|
||
let attachingStatusWaitSubscription = await lifecycleManager.testsOnly_subscribeToStatusChangeWaitEvents() | ||
|
||
// When: The room is in the attaching state | ||
let roomStatusSubscription = await lifecycleManager.onRoomStatusChange(bufferingPolicy: .unbounded) | ||
|
||
let attachOperationID = UUID() | ||
async let _ = lifecycleManager.performAttachOperation(testsOnly_forcingOperationID: attachOperationID) | ||
|
||
// Wait for room to become ATTACHING | ||
_ = try #require(await roomStatusSubscription.attachingElements().first { _ in true }) | ||
|
||
// When: And fails to attach | ||
await #expect(throws: ARTErrorInfo.self) { | ||
do { | ||
_ = try await defaultTyping.get() | ||
} catch { | ||
// Then: An exception with status code of 500 should be thrown | ||
let error = try #require(error as? ARTErrorInfo) | ||
#expect(error.statusCode == 500) | ||
#expect(error.code == ErrorCode.roomInInvalidState.rawValue) | ||
throw error | ||
} | ||
} | ||
// Then: The manager were waiting for its room status to change from attaching | ||
_ = try #require(await attachingStatusWaitSubscription.first { _ in true }) | ||
} | ||
|
||
// @spec CHA-T2g | ||
@Test | ||
func failToRetrieveCurrentlyTypingClientIDsWhenRoomInInvalidState() async throws { | ||
// Given | ||
let realtimePresence = MockRealtimePresence(["client1", "client2"].map { .init(clientId: $0) }) | ||
let channel = MockRealtimeChannel(name: "basketball::$chat::$chatMessages", mockPresence: realtimePresence) | ||
let error = ARTErrorInfo(chatError: .presenceOperationRequiresRoomAttach(feature: .presence)) | ||
let featureChannel = MockFeatureChannel(channel: channel, resultOfWaitToBeAblePerformPresenceOperations: .failure(error)) | ||
let defaultTyping = DefaultTyping(featureChannel: featureChannel, roomID: "basketball", clientID: "mockClientId", logger: TestLogger(), timeout: 5) | ||
|
||
// Then | ||
await #expect(throws: ARTErrorInfo.self) { | ||
do { | ||
_ = try await defaultTyping.get() | ||
} catch { | ||
let error = try #require(error as? ARTErrorInfo) | ||
#expect(error.statusCode == 400) | ||
#expect(error.localizedDescription.contains("attach")) | ||
throw error | ||
} | ||
} | ||
} | ||
|
||
// @spec CHA-T4 | ||
// @spec CHA-T5 | ||
@Test | ||
func usersMayIndicateThatTheyHaveStartedOrStoppedTyping() async throws { | ||
// Given | ||
let typingPresence = MockRealtimePresence([]) | ||
let channel = MockRealtimeChannel(name: "basketball::$chat::$typingIndicators", mockPresence: typingPresence) | ||
let featureChannel = MockFeatureChannel(channel: channel, resultOfWaitToBeAblePerformPresenceOperations: .success(())) | ||
let defaultTyping = DefaultTyping(featureChannel: featureChannel, roomID: "basketball", clientID: "client1", logger: TestLogger(), timeout: 5) | ||
|
||
// CHA-T4 | ||
|
||
// When | ||
try await defaultTyping.start() | ||
|
||
// Then | ||
var typingInfo = try await defaultTyping.get() | ||
#expect(typingInfo == ["client1"]) | ||
|
||
// CHA-T5 | ||
|
||
// When | ||
try await defaultTyping.stop() | ||
|
||
// Then | ||
typingInfo = try await defaultTyping.get() | ||
#expect(typingInfo.isEmpty) | ||
} | ||
|
||
// @spec CHA-T6a | ||
// @spec CHA-T6b | ||
@Test | ||
func usersMaySubscribeToTypingEvents() async throws { | ||
// Given | ||
let typingPresence = MockRealtimePresence([]) | ||
let channel = MockRealtimeChannel(name: "basketball::$chat::$typingIndicators", mockPresence: typingPresence) | ||
let featureChannel = MockFeatureChannel(channel: channel, resultOfWaitToBeAblePerformPresenceOperations: .success(())) | ||
let defaultTyping = DefaultTyping(featureChannel: featureChannel, roomID: "basketball", clientID: "client1", logger: TestLogger(), timeout: 5) | ||
|
||
// CHA-T6a | ||
|
||
// When | ||
let subscription = await defaultTyping.subscribe() | ||
subscription.emit(TypingEvent(currentlyTyping: ["client1"])) | ||
|
||
// Then | ||
let typingEvent = try #require(await subscription.first { _ in true }) | ||
#expect(typingEvent.currentlyTyping == ["client1"]) | ||
|
||
// CHA-T6b | ||
|
||
// When | ||
subscription.unsubscribe() | ||
subscription.emit(TypingEvent(currentlyTyping: ["client1"])) | ||
|
||
// Then | ||
let nilTypingEvento = await subscription.first { _ in true } | ||
#expect(nilTypingEvento == nil) | ||
} | ||
|
||
// @spec CHA-T7 | ||
@Test | ||
func onDiscontinuity() async throws { | ||
// Given | ||
let typingPresence = MockRealtimePresence([]) | ||
let channel = MockRealtimeChannel(mockPresence: typingPresence) | ||
let featureChannel = MockFeatureChannel(channel: channel, resultOfWaitToBeAblePerformPresenceOperations: .success(())) | ||
let defaultTyping = DefaultTyping(featureChannel: featureChannel, roomID: "basketball", clientID: "client1", logger: TestLogger(), timeout: 5) | ||
|
||
// When: The feature channel emits a discontinuity through `onDiscontinuity` | ||
let featureChannelDiscontinuity = DiscontinuityEvent(error: ARTErrorInfo.createUnknownError()) // arbitrary error | ||
let discontinuitySubscription = await defaultTyping.onDiscontinuity() | ||
await featureChannel.emitDiscontinuity(featureChannelDiscontinuity) | ||
|
||
// Then: The DefaultOccupancy instance emits this discontinuity through `onDiscontinuity` | ||
let discontinuity = try #require(await discontinuitySubscription.first { _ in true }) | ||
#expect(discontinuity == featureChannelDiscontinuity) | ||
} | ||
} |