From 956e92a9aca3cef35a81eb3de566245d92ca6d52 Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Thu, 5 Jan 2023 18:06:39 +0200 Subject: [PATCH] Add support for manually starting SaS verification flows and accepting remotely started ones --- .../en.lproj/Untranslated.strings | 2 -- .../Generated/Strings+Untranslated.swift | 4 --- .../SessionVerificationModels.swift | 22 ++++++------- .../SessionVerificationStateMachine.swift | 28 +++++++++++++++-- .../SessionVerificationViewModel.swift | 24 ++++++++++++-- .../View/SessionVerificationScreen.swift | 31 ++++++++++++------- ...ckSessionVerificationControllerProxy.swift | 16 +++++++++- .../SessionVerificationControllerProxy.swift | 27 ++++++++++++++++ ...nVerificationControllerProxyProtocol.swift | 5 +++ .../Sources/SessionVerificationUITests.swift | 30 ++++++++++++++---- ...SessionVerificationStateMachineTests.swift | 18 +++++++++++ .../SessionVerificationViewModelTests.swift | 23 ++++++++++---- changelog.d/pr-408.feature | 1 + 13 files changed, 185 insertions(+), 46 deletions(-) create mode 100644 changelog.d/pr-408.feature diff --git a/ElementX/Resources/Localizations/en.lproj/Untranslated.strings b/ElementX/Resources/Localizations/en.lproj/Untranslated.strings index 791b6d667..ad710534a 100644 --- a/ElementX/Resources/Localizations/en.lproj/Untranslated.strings +++ b/ElementX/Resources/Localizations/en.lproj/Untranslated.strings @@ -23,8 +23,6 @@ "session_verification_banner_title" = "Help keep your messages secure"; "session_verification_banner_message" = "Looks like you’re using a new device. Verify its you."; -"session_verification_screen_emojis_title" = "Lets check if these"; -"session_verification_screen_emojis_message" = "Open Element on one of your other sessions to compare."; "server_selection_server_footer" = "You can only connect to a server that has already been set up"; diff --git a/ElementX/Sources/Generated/Strings+Untranslated.swift b/ElementX/Sources/Generated/Strings+Untranslated.swift index 634564dec..d5393aa83 100644 --- a/ElementX/Sources/Generated/Strings+Untranslated.swift +++ b/ElementX/Sources/Generated/Strings+Untranslated.swift @@ -58,10 +58,6 @@ extension ElementL10n { public static let sessionVerificationBannerMessage = ElementL10n.tr("Untranslated", "session_verification_banner_message") /// Help keep your messages secure public static let sessionVerificationBannerTitle = ElementL10n.tr("Untranslated", "session_verification_banner_title") - /// Open Element on one of your other sessions to compare. - public static let sessionVerificationScreenEmojisMessage = ElementL10n.tr("Untranslated", "session_verification_screen_emojis_message") - /// Lets check if these - public static let sessionVerificationScreenEmojisTitle = ElementL10n.tr("Untranslated", "session_verification_screen_emojis_title") /// Appearance public static let settingsAppearance = ElementL10n.tr("Untranslated", "settings_appearance") /// Timeline Style diff --git a/ElementX/Sources/Screens/SessionVerification/SessionVerificationModels.swift b/ElementX/Sources/Screens/SessionVerification/SessionVerificationModels.swift index 95ce88815..8e49db30d 100644 --- a/ElementX/Sources/Screens/SessionVerification/SessionVerificationModels.swift +++ b/ElementX/Sources/Screens/SessionVerification/SessionVerificationModels.swift @@ -22,22 +22,19 @@ enum SessionVerificationViewModelAction { struct SessionVerificationViewState: BindableState { var verificationState: SessionVerificationStateMachine.State = .initial - - var title: String? { - switch verificationState { - case .showingChallenge: - return ElementL10n.sessionVerificationScreenEmojisTitle - default: - return nil - } - } - + var message: String { switch verificationState { case .initial: return ElementL10n.verificationOpenOtherToVerify case .requestingVerification: return ElementL10n.verificationRequestWaiting + case .verificationRequestAccepted: + return ElementL10n.verificationEmojiNotice + case .startingSasVerification: + return ElementL10n.verificationRequestWaiting + case .sasVerificationStarted: + return ElementL10n.verificationRequestWaiting case .acceptingChallenge: return ElementL10n.verificationRequestWaiting case .decliningChallenge: @@ -45,7 +42,7 @@ struct SessionVerificationViewState: BindableState { case .cancelling: return ElementL10n.verificationRequestWaiting case .showingChallenge: - return ElementL10n.sessionVerificationScreenEmojisMessage + return ElementL10n.verificationCodeNotice case .verified: return ElementL10n.verificationConclusionOkSelfNotice case .cancelled: @@ -55,7 +52,8 @@ struct SessionVerificationViewState: BindableState { } enum SessionVerificationViewAction { - case start + case requestVerification + case startSasVerification case restart case accept case decline diff --git a/ElementX/Sources/Screens/SessionVerification/SessionVerificationStateMachine.swift b/ElementX/Sources/Screens/SessionVerification/SessionVerificationStateMachine.swift index 490f8d8b6..a029ec371 100644 --- a/ElementX/Sources/Screens/SessionVerification/SessionVerificationStateMachine.swift +++ b/ElementX/Sources/Screens/SessionVerification/SessionVerificationStateMachine.swift @@ -24,6 +24,12 @@ class SessionVerificationStateMachine { case initial /// Waiting for verification acceptance case requestingVerification + /// Verification request accepted. Waiting for start + case verificationRequestAccepted + /// Waiting for SaS verification start + case startingSasVerification + /// A SaS verification flow has been started + case sasVerificationStarted /// Verification accepted and emojis received case showingChallenge(emojis: [SessionVerificationEmoji]) /// Emojis match locally @@ -42,6 +48,12 @@ class SessionVerificationStateMachine { enum Event: EventType { /// Request verification case requestVerification + /// The current verification request has been accepted + case didAcceptVerificationRequest + /// Start a SaS verification flow + case startSasVerification + /// Started a SaS verification flow + case didStartSasVerification /// Has received emojis case didReceiveChallenge(emojis: [SessionVerificationEmoji]) /// Emojis match @@ -69,15 +81,19 @@ class SessionVerificationStateMachine { init() { stateMachine = StateMachine(state: .initial) { machine in machine.addRoutes(event: .requestVerification, transitions: [.initial => .requestingVerification]) + machine.addRoutes(event: .didAcceptVerificationRequest, transitions: [.requestingVerification => .verificationRequestAccepted]) + machine.addRoutes(event: .startSasVerification, transitions: [.verificationRequestAccepted => .startingSasVerification]) machine.addRoutes(event: .didFail, transitions: [.requestingVerification => .initial]) machine.addRoutes(event: .restart, transitions: [.cancelled => .initial]) // Transitions with associated values need to be handled through `addRouteMapping` machine.addRouteMapping { event, fromState, _ in switch (event, fromState) { - case (.didReceiveChallenge(let emojis), .requestingVerification): + case (.didStartSasVerification, _): + return .sasVerificationStarted + + case (.didReceiveChallenge(let emojis), .sasVerificationStarted): return .showingChallenge(emojis: emojis) - case (.acceptChallenge, .showingChallenge(let emojis)): return .acceptingChallenge(emojis: emojis) case (.didFail, .acceptingChallenge(let emojis)): @@ -100,6 +116,14 @@ class SessionVerificationStateMachine { return nil } } + + addTransitionHandler { context in + if let event = context.event { + MXLog.info("Transitioning from `\(context.fromState)` to `\(context.toState)` with event `\(event)`") + } else { + MXLog.info("Transitioning from \(context.fromState)` to `\(context.toState)`") + } + } } } diff --git a/ElementX/Sources/Screens/SessionVerification/SessionVerificationViewModel.swift b/ElementX/Sources/Screens/SessionVerification/SessionVerificationViewModel.swift index 45f810e5a..59d6c312c 100644 --- a/ElementX/Sources/Screens/SessionVerification/SessionVerificationViewModel.swift +++ b/ElementX/Sources/Screens/SessionVerification/SessionVerificationViewModel.swift @@ -41,8 +41,12 @@ class SessionVerificationViewModel: SessionVerificationViewModelType, SessionVer guard let self else { return } switch callback { + case .acceptedVerificationRequest: + self.stateMachine.processEvent(.didAcceptVerificationRequest) + case .startedSasVerification: + self.stateMachine.processEvent(.didStartSasVerification) case .receivedVerificationData(let emojis): - guard self.stateMachine.state == .requestingVerification else { + guard self.stateMachine.state == .sasVerificationStarted else { MXLog.warning("Callbacks: Ignoring receivedVerificationData due to invalid state.") return } @@ -61,8 +65,10 @@ class SessionVerificationViewModel: SessionVerificationViewModelType, SessionVer override func process(viewAction: SessionVerificationViewAction) async { switch viewAction { - case .start: + case .requestVerification: stateMachine.processEvent(.requestVerification) + case .startSasVerification: + stateMachine.processEvent(.startSasVerification) case .restart: stateMachine.processEvent(.restart) case .close: @@ -92,6 +98,8 @@ class SessionVerificationViewModel: SessionVerificationViewModelType, SessionVer switch (context.fromState, context.event, context.toState) { case (.initial, .requestVerification, .requestingVerification): self.requestVerification() + case (.verificationRequestAccepted, .startSasVerification, .startingSasVerification): + self.startSasVerification() case (.showingChallenge, .acceptChallenge, .acceptingChallenge): self.acceptChallenge() case (.showingChallenge, .declineChallenge, .decliningChallenge): @@ -131,6 +139,18 @@ class SessionVerificationViewModel: SessionVerificationViewModelType, SessionVer } } + private func startSasVerification() { + Task { + switch await sessionVerificationControllerProxy.startSasVerification() { + case .success: + // Need to wait for the callback from the remote + break + case .failure: + stateMachine.processEvent(.didFail) + } + } + } + private func acceptChallenge() { Task { switch await sessionVerificationControllerProxy.approveVerification() { diff --git a/ElementX/Sources/Screens/SessionVerification/View/SessionVerificationScreen.swift b/ElementX/Sources/Screens/SessionVerification/View/SessionVerificationScreen.swift index 0b8243a03..928db4c73 100644 --- a/ElementX/Sources/Screens/SessionVerification/View/SessionVerificationScreen.swift +++ b/ElementX/Sources/Screens/SessionVerification/View/SessionVerificationScreen.swift @@ -23,13 +23,6 @@ struct SessionVerificationScreen: View { NavigationView { ScrollView { VStack(spacing: 32.0) { - if let title = context.viewState.title { - Text(title) - .font(.element.headlineBold) - .foregroundColor(.element.systemPrimaryLabel) - .multilineTextAlignment(.center) - } - Text(context.viewState.message) .font(.body) .multilineTextAlignment(.center) @@ -66,6 +59,15 @@ struct SessionVerificationScreen: View { case .requestingVerification: ProgressView() .accessibilityIdentifier("requestingVerificationProgressView") + + case .verificationRequestAccepted: + StateIcon(systemName: "lock.shield") + case .startingSasVerification: + ProgressView() + .accessibilityIdentifier("startingSasVerification") + case .sasVerificationStarted: + ProgressView() + .accessibilityIdentifier("startedSasVerification") case .cancelling: ProgressView() .accessibilityIdentifier("cancellingVerificationProgressView") @@ -98,10 +100,10 @@ struct SessionVerificationScreen: View { switch context.viewState.verificationState { case .initial: Button(ElementL10n.startVerification) { - context.send(viewAction: .start) + context.send(viewAction: .requestVerification) } .buttonStyle(.elementAction(.xLarge)) - .accessibilityIdentifier("startButton") + .accessibilityIdentifier("requestVerificationButton") case .cancelled: Button(ElementL10n.globalRetry) { @@ -109,16 +111,23 @@ struct SessionVerificationScreen: View { } .buttonStyle(.elementAction(.xLarge)) .accessibilityIdentifier("restartButton") + + case .verificationRequestAccepted: + Button(ElementL10n.startVerification) { + context.send(viewAction: .startSasVerification) + } + .buttonStyle(.elementAction(.xLarge)) + .accessibilityIdentifier("sasVerificationStartButton") case .showingChallenge: VStack(spacing: 30) { Button { context.send(viewAction: .accept) } label: { - Label(ElementL10n.actionMatch, systemImage: "checkmark") + Label(ElementL10n.verificationSasMatch, systemImage: "checkmark") } .buttonStyle(.elementAction(.xLarge)) .accessibilityLabel("challengeAcceptButton") - Button(ElementL10n.no) { + Button(ElementL10n.verificationSasDoNotMatch) { context.send(viewAction: .decline) } .font(.element.bodyBold) diff --git a/ElementX/Sources/Services/SessionVerification/MockSessionVerificationControllerProxy.swift b/ElementX/Sources/Services/SessionVerification/MockSessionVerificationControllerProxy.swift index 607ec0026..5487c31ea 100644 --- a/ElementX/Sources/Services/SessionVerification/MockSessionVerificationControllerProxy.swift +++ b/ElementX/Sources/Services/SessionVerification/MockSessionVerificationControllerProxy.swift @@ -27,7 +27,21 @@ struct MockSessionVerificationControllerProxy: SessionVerificationControllerProx func requestVerification() async -> Result { Task.detached { try await Task.sleep(for: requestDelay) - callbacks.send(.receivedVerificationData(Self.emojis)) + callbacks.send(.acceptedVerificationRequest) + } + + return .success(()) + } + + func startSasVerification() async -> Result { + Task.detached { + try await Task.sleep(for: requestDelay) + callbacks.send(.startedSasVerification) + + Task.detached { + try await Task.sleep(for: requestDelay) + callbacks.send(.receivedVerificationData(Self.emojis)) + } } return .success(()) diff --git a/ElementX/Sources/Services/SessionVerification/SessionVerificationControllerProxy.swift b/ElementX/Sources/Services/SessionVerification/SessionVerificationControllerProxy.swift index 8e74d8d5f..db1fa8df4 100644 --- a/ElementX/Sources/Services/SessionVerification/SessionVerificationControllerProxy.swift +++ b/ElementX/Sources/Services/SessionVerification/SessionVerificationControllerProxy.swift @@ -31,6 +31,14 @@ private class WeakSessionVerificationControllerProxy: SessionVerificationControl proxy?.didReceiveData(data) } + func didAcceptVerificationRequest() { + proxy?.didAcceptVerificationRequest() + } + + func didStartSasVerification() { + proxy?.didStartSasVerification() + } + func didFail() { proxy?.didFail() } @@ -73,6 +81,17 @@ class SessionVerificationControllerProxy: SessionVerificationControllerProxyProt } } + func startSasVerification() async -> Result { + await Task.dispatch(on: .global()) { + do { + try self.sessionVerificationController.startSasVerification() + return .success(()) + } catch { + return .failure(.failedStartingSasVerification) + } + } + } + func approveVerification() async -> Result { await Task.dispatch(on: .global()) { do { @@ -108,6 +127,14 @@ class SessionVerificationControllerProxy: SessionVerificationControllerProxyProt // MARK: - Private + fileprivate func didAcceptVerificationRequest() { + callbacks.send(.acceptedVerificationRequest) + } + + fileprivate func didStartSasVerification() { + callbacks.send(.startedSasVerification) + } + fileprivate func didReceiveData(_ data: [MatrixRustSDK.SessionVerificationEmoji]) { callbacks.send(.receivedVerificationData(data.map { emoji in SessionVerificationEmoji(symbol: emoji.symbol(), description: emoji.description()) diff --git a/ElementX/Sources/Services/SessionVerification/SessionVerificationControllerProxyProtocol.swift b/ElementX/Sources/Services/SessionVerification/SessionVerificationControllerProxyProtocol.swift index 0859b0bf4..3cec7c22d 100644 --- a/ElementX/Sources/Services/SessionVerification/SessionVerificationControllerProxyProtocol.swift +++ b/ElementX/Sources/Services/SessionVerification/SessionVerificationControllerProxyProtocol.swift @@ -19,12 +19,15 @@ import Foundation enum SessionVerificationControllerProxyError: Error { case failedRequestingVerification + case failedStartingSasVerification case failedApprovingVerification case failedDecliningVerification case failedCancellingVerification } enum SessionVerificationControllerProxyCallback { + case acceptedVerificationRequest + case startedSasVerification case receivedVerificationData([SessionVerificationEmoji]) case finished case cancelled @@ -43,6 +46,8 @@ protocol SessionVerificationControllerProxyProtocol { func requestVerification() async -> Result + func startSasVerification() async -> Result + func approveVerification() async -> Result func declineVerification() async -> Result diff --git a/UITests/Sources/SessionVerificationUITests.swift b/UITests/Sources/SessionVerificationUITests.swift index f34f237a9..df39a0743 100644 --- a/UITests/Sources/SessionVerificationUITests.swift +++ b/UITests/Sources/SessionVerificationUITests.swift @@ -22,16 +22,22 @@ class SessionVerificationUITests: XCTestCase { let app = Application.launch() app.goToScreenWithIdentifier(.sessionVerification) - XCTAssert(app.buttons["startButton"].exists) + XCTAssert(app.buttons["requestVerificationButton"].exists) XCTAssert(app.buttons["closeButton"].exists) XCTAssert(app.staticTexts["titleLabel"].exists) app.assertScreenshot(.sessionVerification) - app.buttons["startButton"].tap() + app.buttons["requestVerificationButton"].tap() XCTAssert(app.activityIndicators["requestingVerificationProgressView"].exists) + XCTAssert(app.buttons["sasVerificationStartButton"].waitForExistence(timeout: 5.0)) + app.buttons["sasVerificationStartButton"].tap() + + XCTAssert(app.activityIndicators["startingSasVerification"].waitForExistence(timeout: 5.0)) + XCTAssert(app.activityIndicators["startedSasVerification"].waitForExistence(timeout: 5.0)) + XCTAssert(app.buttons["challengeAcceptButton"].waitForExistence(timeout: 5.0)) XCTAssert(app.buttons["challengeDeclineButton"].waitForExistence(timeout: 5.0)) @@ -50,14 +56,20 @@ class SessionVerificationUITests: XCTestCase { let app = Application.launch() app.goToScreenWithIdentifier(.sessionVerification) - XCTAssert(app.buttons["startButton"].exists) + XCTAssert(app.buttons["requestVerificationButton"].exists) XCTAssert(app.buttons["closeButton"].exists) XCTAssert(app.staticTexts["titleLabel"].exists) - app.buttons["startButton"].tap() + app.buttons["requestVerificationButton"].tap() XCTAssert(app.activityIndicators["requestingVerificationProgressView"].exists) + XCTAssert(app.buttons["sasVerificationStartButton"].waitForExistence(timeout: 5.0)) + app.buttons["sasVerificationStartButton"].tap() + + XCTAssert(app.activityIndicators["startingSasVerification"].waitForExistence(timeout: 5.0)) + XCTAssert(app.activityIndicators["startedSasVerification"].waitForExistence(timeout: 5.0)) + XCTAssert(app.buttons["challengeAcceptButton"].waitForExistence(timeout: 5.0)) XCTAssert(app.buttons["challengeDeclineButton"].waitForExistence(timeout: 5.0)) @@ -74,14 +86,20 @@ class SessionVerificationUITests: XCTestCase { let app = Application.launch() app.goToScreenWithIdentifier(.sessionVerification) - XCTAssert(app.buttons["startButton"].exists) + XCTAssert(app.buttons["requestVerificationButton"].exists) XCTAssert(app.buttons["closeButton"].exists) XCTAssert(app.staticTexts["titleLabel"].exists) - app.buttons["startButton"].tap() + app.buttons["requestVerificationButton"].tap() XCTAssert(app.activityIndicators["requestingVerificationProgressView"].waitForExistence(timeout: 1)) + XCTAssert(app.buttons["sasVerificationStartButton"].waitForExistence(timeout: 5.0)) + app.buttons["sasVerificationStartButton"].tap() + + XCTAssert(app.activityIndicators["startingSasVerification"].waitForExistence(timeout: 5.0)) + XCTAssert(app.activityIndicators["startedSasVerification"].waitForExistence(timeout: 5.0)) + app.buttons["closeButton"].tap() XCTAssert(app.images["sessionVerificationFailedIcon"].waitForExistence(timeout: 1)) diff --git a/UnitTests/Sources/SessionVerificationStateMachineTests.swift b/UnitTests/Sources/SessionVerificationStateMachineTests.swift index dec258de2..f62eee464 100644 --- a/UnitTests/Sources/SessionVerificationStateMachineTests.swift +++ b/UnitTests/Sources/SessionVerificationStateMachineTests.swift @@ -33,6 +33,12 @@ class SessionVerificationStateMachineTests: XCTestCase { stateMachine.processEvent(.requestVerification) XCTAssertEqual(stateMachine.state, .requestingVerification) + stateMachine.processEvent(.didAcceptVerificationRequest) + XCTAssertEqual(stateMachine.state, .verificationRequestAccepted) + + stateMachine.processEvent(.didStartSasVerification) + XCTAssertEqual(stateMachine.state, .sasVerificationStarted) + stateMachine.processEvent(.didReceiveChallenge(emojis: MockSessionVerificationControllerProxy.emojis)) XCTAssertEqual(stateMachine.state, .showingChallenge(emojis: MockSessionVerificationControllerProxy.emojis)) @@ -49,6 +55,12 @@ class SessionVerificationStateMachineTests: XCTestCase { stateMachine.processEvent(.requestVerification) XCTAssertEqual(stateMachine.state, .requestingVerification) + stateMachine.processEvent(.didAcceptVerificationRequest) + XCTAssertEqual(stateMachine.state, .verificationRequestAccepted) + + stateMachine.processEvent(.didStartSasVerification) + XCTAssertEqual(stateMachine.state, .sasVerificationStarted) + stateMachine.processEvent(.didReceiveChallenge(emojis: MockSessionVerificationControllerProxy.emojis)) XCTAssertEqual(stateMachine.state, .showingChallenge(emojis: MockSessionVerificationControllerProxy.emojis)) @@ -84,6 +96,12 @@ class SessionVerificationStateMachineTests: XCTestCase { stateMachine.processEvent(.requestVerification) XCTAssertEqual(stateMachine.state, .requestingVerification) + stateMachine.processEvent(.didAcceptVerificationRequest) + XCTAssertEqual(stateMachine.state, .verificationRequestAccepted) + + stateMachine.processEvent(.didStartSasVerification) + XCTAssertEqual(stateMachine.state, .sasVerificationStarted) + stateMachine.processEvent(.didReceiveChallenge(emojis: MockSessionVerificationControllerProxy.emojis)) XCTAssertEqual(stateMachine.state, .showingChallenge(emojis: MockSessionVerificationControllerProxy.emojis)) diff --git a/UnitTests/Sources/SessionVerificationViewModelTests.swift b/UnitTests/Sources/SessionVerificationViewModelTests.swift index dc463e17a..7b28e6e67 100644 --- a/UnitTests/Sources/SessionVerificationViewModelTests.swift +++ b/UnitTests/Sources/SessionVerificationViewModelTests.swift @@ -35,7 +35,7 @@ class SessionVerificationViewModelTests: XCTestCase { func testRequestVerification() async { XCTAssertEqual(context.viewState.verificationState, .initial) - context.send(viewAction: .start) + context.send(viewAction: .requestVerification) await Task.yield() @@ -45,7 +45,7 @@ class SessionVerificationViewModelTests: XCTestCase { func testVerificationCancellation() async throws { XCTAssertEqual(context.viewState.verificationState, .initial) - context.send(viewAction: .start) + context.send(viewAction: .requestVerification) context.send(viewAction: .close) @@ -125,14 +125,20 @@ class SessionVerificationViewModelTests: XCTestCase { // MARK: - Private private func setupChallengeReceived() { - let expectation = XCTestExpectation(description: "Wait for challenge") + let requestAcceptanceExpectation = XCTestExpectation(description: "Wait for request acceptance") + let sasVerificationStartExpectation = XCTestExpectation(description: "Wait for SaS verification start") + let verificationDataReceivalExpectation = XCTestExpectation(description: "Wait for Emoji data") let cancellable = sessionVerificationController.callbacks .debounce(for: .seconds(2.0), scheduler: DispatchQueue.main) .sink { callback in switch callback { + case .acceptedVerificationRequest: + requestAcceptanceExpectation.fulfill() + case .startedSasVerification: + sasVerificationStartExpectation.fulfill() case .receivedVerificationData: - expectation.fulfill() + verificationDataReceivalExpectation.fulfill() default: break } @@ -142,10 +148,15 @@ class SessionVerificationViewModelTests: XCTestCase { cancellable.cancel() } - context.send(viewAction: .start) + context.send(viewAction: .requestVerification) + wait(for: [requestAcceptanceExpectation], timeout: 10.0) + XCTAssertEqual(context.viewState.verificationState, .verificationRequestAccepted) - wait(for: [expectation], timeout: 10.0) + context.send(viewAction: .startSasVerification) + wait(for: [sasVerificationStartExpectation], timeout: 10.0) + XCTAssertEqual(context.viewState.verificationState, .sasVerificationStarted) + wait(for: [verificationDataReceivalExpectation], timeout: 10.0) XCTAssertEqual(context.viewState.verificationState, .showingChallenge(emojis: MockSessionVerificationControllerProxy.emojis)) } } diff --git a/changelog.d/pr-408.feature b/changelog.d/pr-408.feature new file mode 100644 index 000000000..6ea441d4c --- /dev/null +++ b/changelog.d/pr-408.feature @@ -0,0 +1 @@ +Add support for manually starting SaS verification flows and accepting remotely started ones \ No newline at end of file