Skip to content

Commit

Permalink
Cover changes introduced by Web synchronization with unit tests
Browse files Browse the repository at this point in the history
With changes added by enabling visitor messages to be delivered via socket and using send message payload API, new logic for message processing has been introduced, to handle messages delivered via web-socket and via REST API. These tests cover situations were message via socket gets delivered before REST API and vice versa, to ensure that no visitor message duplications occur.

MOB-2638
  • Loading branch information
igorkravchenko committed Sep 13, 2023
1 parent 25e53f7 commit 71d7bc7
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 1 deletion.
2 changes: 1 addition & 1 deletion GliaWidgets/Sources/Interactor/Interactor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ class Interactor {
}
}
}
private var environment: Environment
var environment: Environment

init(
configuration: Configuration,
Expand Down
10 changes: 10 additions & 0 deletions GliaWidgets/Sources/ViewModel/Chat/ChatViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -930,3 +930,13 @@ extension ChatViewModel {
}
}
}

#if DEBUG
extension ChatViewModel {
/// Sets text and immediately sends it. Used for testing.
func invokeSetTextAndSendMessage(text: String) {
self.messageText = text
self.sendMessage()
}
}
#endif
122 changes: 122 additions & 0 deletions GliaWidgetsTests/Sources/ChatViewModel/ChatViewModelTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -579,6 +579,128 @@ class ChatViewModelTests: XCTestCase {
}
XCTAssertEqual(viewModel.receivedMessageIds, [messageId.uppercased()])
}

func test_messageReceivedFromSocketWithSameIdIsDiscarded() {
var viewModelEnv = ChatViewModel.Environment.failing()
viewModelEnv.createFileUploadListModel = { _ in .mock() }
viewModelEnv.fileManager.urlsForDirectoryInDomainMask = { _, _ in [.mock] }
viewModelEnv.fileManager.createDirectoryAtUrlWithIntermediateDirectories = { _, _, _ in }
viewModelEnv.loadChatMessagesFromHistory = { true }
viewModelEnv.fetchSiteConfigurations = { _ in }
let expectedMessageId = "expected_message_id"
viewModelEnv.fetchChatHistory = { callback in
callback(.success([]))
}
let viewModel: ChatViewModel = .mock(environment: viewModelEnv)
viewModel.isViewLoaded = true
viewModel.start()

viewModel.interactorEvent(.receivedMessage(.mock(id: expectedMessageId)))
viewModel.interactorEvent(.receivedMessage(.mock(id: expectedMessageId)))
XCTAssertEqual(viewModel.messagesSection.items.count, 1)
XCTAssertEqual(viewModel.receivedMessageIds, [expectedMessageId.uppercased()])
}

func test_messageReceivedFirstFromRestDiscardsSameOneFromSocket() throws {
var viewModelEnv = ChatViewModel.Environment.failing()
viewModelEnv.createFileUploadListModel = { _ in .mock() }
viewModelEnv.fileManager.urlsForDirectoryInDomainMask = { _, _ in [.mock] }
viewModelEnv.fileManager.createDirectoryAtUrlWithIntermediateDirectories = { _, _, _ in }
viewModelEnv.loadChatMessagesFromHistory = { true }
viewModelEnv.fetchSiteConfigurations = { _ in }
let messageIdSuffix = "1D_123"
let expectedMessageId = "expected_message_id"
viewModelEnv.fetchChatHistory = { callback in
callback(.success([]))
}
viewModelEnv.createSendMessagePayload = {
.mock(messageIdSuffix: messageIdSuffix, content: $0, attachment: $1)
}

let interactor = Interactor.failing
interactor.environment.gcd.mainQueue.asyncIfNeeded = { $0() }
interactor.environment.coreSdk.configureWithInteractor = { _ in }
interactor.environment.coreSdk.configureWithConfiguration = { _, callback in callback?() }
interactor.environment.coreSdk.sendMessagePreview = { _, _ in }
interactor.environment.coreSdk.sendMessageWithMessagePayload = { _, completion in
completion(.success(.mock(id: expectedMessageId)))
}
interactor.environment.coreSdk.queueForEngagement = { _, _, _, _, _, completion in
completion(.mock, nil)
}

let viewModel: ChatViewModel = .mock(interactor: interactor, environment: viewModelEnv)
viewModel.isViewLoaded = true
viewModel.start()
interactor.state = .engaged(.mock())
viewModel.invokeSetTextAndSendMessage(text: "Mock send message.")

viewModel.interactorEvent(.receivedMessage(.mock(id: expectedMessageId)))

let lastMessage = viewModel.messagesSection.items.last?.kind
var receivedMessage: ChatMessage?
switch try XCTUnwrap(lastMessage) {
case let .visitorMessage(message, _):
receivedMessage = message
default:
XCTFail("Expected visitor message. Got \(String(describing: lastMessage)) instead.")
}

XCTAssertEqual(viewModel.messagesSection.items.count, 2)
XCTAssertEqual(viewModel.receivedMessageIds, [expectedMessageId.uppercased()])
XCTAssertEqual(try XCTUnwrap(receivedMessage).id, expectedMessageId)
}

func test_messageReceivedFirstFromSocketDiscardsSameOneFromRest() throws {
var viewModelEnv = ChatViewModel.Environment.failing()
viewModelEnv.createFileUploadListModel = { _ in .mock() }
viewModelEnv.fileManager.urlsForDirectoryInDomainMask = { _, _ in [.mock] }
viewModelEnv.fileManager.createDirectoryAtUrlWithIntermediateDirectories = { _, _, _ in }
viewModelEnv.loadChatMessagesFromHistory = { true }
viewModelEnv.fetchSiteConfigurations = { _ in }
let messageIdSuffix = "1D_123"
let expectedMessageId = "expected_message_id"
viewModelEnv.fetchChatHistory = { callback in
callback(.success([]))
}
viewModelEnv.createSendMessagePayload = {
.mock(messageIdSuffix: messageIdSuffix, content: $0, attachment: $1)
}

let interactor = Interactor.failing
interactor.environment.gcd.mainQueue.asyncIfNeeded = { $0() }
interactor.environment.coreSdk.configureWithInteractor = { _ in }
interactor.environment.coreSdk.configureWithConfiguration = { _, callback in callback?() }
interactor.environment.coreSdk.sendMessagePreview = { _, _ in }
interactor.environment.coreSdk.sendMessageWithMessagePayload = { [weak viewModel] _, completion in
// Deliver message via socket before REST API response.
viewModel?.interactorEvent(.receivedMessage(.mock(id: expectedMessageId)))
completion(.success(.mock(id: expectedMessageId)))
}
interactor.environment.coreSdk.queueForEngagement = { _, _, _, _, _, completion in
completion(.mock, nil)
}

let viewModel: ChatViewModel = .mock(interactor: interactor, environment: viewModelEnv)
viewModel.isViewLoaded = true
viewModel.start()
interactor.state = .engaged(.mock())
viewModel.invokeSetTextAndSendMessage(text: "Mock send message.")
viewModel.interactorEvent(.receivedMessage(.mock(id: expectedMessageId)))

let lastMessage = viewModel.messagesSection.items.last?.kind
var receivedMessage: ChatMessage?
switch try XCTUnwrap(lastMessage) {
case let .visitorMessage(message, _):
receivedMessage = message
default:
XCTFail("Expected visitor message. Got \(String(describing: lastMessage)) instead.")
}

XCTAssertEqual(viewModel.messagesSection.items.count, 2)
XCTAssertEqual(viewModel.receivedMessageIds, [expectedMessageId.uppercased()])
XCTAssertEqual(try XCTUnwrap(receivedMessage).id, expectedMessageId)
}
}

extension ChatChoiceCardOption {
Expand Down

0 comments on commit 71d7bc7

Please sign in to comment.