From de29c2613c46e4ef83f0ad8bbf3a97e260b759d8 Mon Sep 17 00:00:00 2001 From: Marat Al Date: Tue, 31 Dec 2024 21:50:38 +0100 Subject: [PATCH] Test for CHA-PR3d spec. --- Sources/AblyChat/Room.swift | 11 +++ .../DefaultRoomPresenceTests.swift | 68 +++++++++++++++++++ .../Mocks/MockRealtimeChannel.swift | 6 +- 3 files changed, 82 insertions(+), 3 deletions(-) diff --git a/Sources/AblyChat/Room.swift b/Sources/AblyChat/Room.swift index 80e168b..20bf57c 100644 --- a/Sources/AblyChat/Room.swift +++ b/Sources/AblyChat/Room.swift @@ -423,3 +423,14 @@ internal actor DefaultRoom } } } + +#if DEBUG +extension DefaultRoom { + var testsOnly_lifecycleManager: DefaultRoomLifecycleManager { + guard let lifecycleManager = lifecycleManager as? DefaultRoomLifecycleManager else { + preconditionFailure("DefaultRoomLifecycleManager is expected here.") + } + return lifecycleManager + } +} +#endif diff --git a/Tests/AblyChatTests/DefaultRoomPresenceTests.swift b/Tests/AblyChatTests/DefaultRoomPresenceTests.swift index 710f21f..faf1eb2 100644 --- a/Tests/AblyChatTests/DefaultRoomPresenceTests.swift +++ b/Tests/AblyChatTests/DefaultRoomPresenceTests.swift @@ -135,6 +135,74 @@ struct DefaultRoomPresenceTests { } } + // @specPartial CHA-PR3d + @Test + func usersMayEnterPresenceWhileAttaching() async throws { + // Given + let realtimePresence = MockRealtimePresence(["client1"].map { .init(clientId: $0) }) + let channelsList = [ + MockRealtimeChannel(name: "basketball::$chat::$chatMessages", attachResult: .success, mockPresence: realtimePresence), + ] + let channels = MockChannels(channels: channelsList) + let realtime = MockRealtime.create(channels: channels) + let room = try await DefaultRoom(realtime: realtime, chatAPI: ChatAPI(realtime: realtime), roomID: "basketball", options: .init(presence: .init()), logger: TestLogger(), lifecycleManagerFactory: DefaultRoomLifecycleManagerFactory()) + + let lifecycleManager = await room.testsOnly_lifecycleManager + let attachingStatusWaitSubscription = await lifecycleManager.testsOnly_subscribeToStatusChangeWaitEvents() + + // When: The room is in the attaching state and presence enter is called + Task { + try await lifecycleManager.performAttachOperation() + } + // When: And presence enter is called + try await room.presence.enter() + + // Then: The manager waits for its room status to change + _ = try #require(await attachingStatusWaitSubscription.first { _ in true }) + + // Then: Room eventually attached + #expect(await room.status == .attached) + } + + // @specPartial CHA-PR3d + @Test + func usersMayEnterPresenceWhileAttachingWithFailure() async throws { + // Given: attachment failure + let attachError = ARTErrorInfo(domain: "SomeDomain", code: 123) + + // Given + let realtimePresence = MockRealtimePresence(["client1"].map { .init(clientId: $0) }) + let channelsList = [ + MockRealtimeChannel(name: "basketball::$chat::$chatMessages", attachResult: .failure(attachError), mockPresence: realtimePresence), + ] + let channels = MockChannels(channels: channelsList) + let realtime = MockRealtime.create(channels: channels) + let room = try await DefaultRoom(realtime: realtime, chatAPI: ChatAPI(realtime: realtime), roomID: "basketball", options: .init(presence: .init()), logger: TestLogger(), lifecycleManagerFactory: DefaultRoomLifecycleManagerFactory()) + + let lifecycleManager = await room.testsOnly_lifecycleManager + let attachingStatusWaitSubscription = await lifecycleManager.testsOnly_subscribeToStatusChangeWaitEvents() + + // When: The room is in the attaching state + Task { + try await lifecycleManager.performAttachOperation() + } + + // When: And fails to attach + await #expect(throws: ARTErrorInfo.self) { + do { + try await room.presence.enter() + } 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-PR7a // @spec CHA-PR7b // @spec CHA-PR7c diff --git a/Tests/AblyChatTests/Mocks/MockRealtimeChannel.swift b/Tests/AblyChatTests/Mocks/MockRealtimeChannel.swift index 26026cc..cf64a24 100644 --- a/Tests/AblyChatTests/Mocks/MockRealtimeChannel.swift +++ b/Tests/AblyChatTests/Mocks/MockRealtimeChannel.swift @@ -72,7 +72,7 @@ final class MockRealtimeChannel: NSObject, RealtimeChannelProtocol { } var state: ARTRealtimeChannelState { - .attached + attachResult == .success ? .attached : .failed } var errorReason: ARTErrorInfo? { @@ -87,7 +87,7 @@ final class MockRealtimeChannel: NSObject, RealtimeChannelProtocol { fatalError("Not implemented") } - enum AttachOrDetachResult { + enum AttachOrDetachResult: Equatable { case success case failure(ARTErrorInfo) @@ -184,7 +184,7 @@ final class MockRealtimeChannel: NSObject, RealtimeChannelProtocol { } func on(_: @escaping (ARTChannelStateChange) -> Void) -> ARTEventListener { - fatalError("Not implemented") + ARTEventListener() } func once(_: ARTChannelEvent, callback _: @escaping (ARTChannelStateChange) -> Void) -> ARTEventListener {