mirror of
https://github.com/vector-im/element-ios.git
synced 2024-09-28 23:32:41 +00:00
Add tests for FlowResult.
This commit is contained in:
parent
a8f8d36314
commit
badd44f426
6 changed files with 345 additions and 100 deletions
|
@ -37,6 +37,7 @@ targets:
|
||||||
- target: DesignKit
|
- target: DesignKit
|
||||||
- target: CommonKit
|
- target: CommonKit
|
||||||
- package: Mapbox
|
- package: Mapbox
|
||||||
|
- package: OrderedCollections
|
||||||
|
|
||||||
configFiles:
|
configFiles:
|
||||||
Debug: Debug.xcconfig
|
Debug: Debug.xcconfig
|
||||||
|
|
|
@ -15,91 +15,7 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import OrderedCollections
|
||||||
/// The parameters used for registration requests.
|
|
||||||
struct RegistrationParameters: Codable {
|
|
||||||
/// Authentication parameters
|
|
||||||
var auth: AuthenticationParameters?
|
|
||||||
|
|
||||||
/// The account username
|
|
||||||
var username: String?
|
|
||||||
|
|
||||||
/// The account password
|
|
||||||
var password: String?
|
|
||||||
|
|
||||||
/// Device name
|
|
||||||
var initialDeviceDisplayName: String?
|
|
||||||
|
|
||||||
/// Temporary flag to notify the server that we support MSISDN flow. Used to prevent old app
|
|
||||||
/// versions to end up in fallback because the HS returns the MSISDN flow which they don't support
|
|
||||||
var xShowMSISDN: Bool?
|
|
||||||
|
|
||||||
enum CodingKeys: String, CodingKey {
|
|
||||||
case auth
|
|
||||||
case username
|
|
||||||
case password
|
|
||||||
case initialDeviceDisplayName = "initial_device_display_name"
|
|
||||||
case xShowMSISDN = "x_show_msisdn"
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The parameters as a JSON dictionary for use in MXRestClient.
|
|
||||||
func dictionary() throws -> [String: Any] {
|
|
||||||
let jsonData = try JSONEncoder().encode(self)
|
|
||||||
let object = try JSONSerialization.jsonObject(with: jsonData)
|
|
||||||
guard let dictionary = object as? [String: Any] else {
|
|
||||||
MXLog.error("[RegistrationParameters] dictionary: Unexpected type decoded \(type(of: object)). Expected a Dictionary.")
|
|
||||||
throw AuthenticationError.dictionaryError
|
|
||||||
}
|
|
||||||
|
|
||||||
return dictionary
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The data passed to the `auth` parameter in authentication requests.
|
|
||||||
struct AuthenticationParameters: Codable {
|
|
||||||
/// The type of authentication taking place. The identifier from `MXLoginFlowType`.
|
|
||||||
let type: String
|
|
||||||
|
|
||||||
/// Note: session can be null for reset password request
|
|
||||||
var session: String?
|
|
||||||
|
|
||||||
/// parameter for "m.login.recaptcha" type
|
|
||||||
var captchaResponse: String?
|
|
||||||
|
|
||||||
/// parameter for "m.login.email.identity" type
|
|
||||||
var threePIDCredentials: ThreePIDCredentials?
|
|
||||||
|
|
||||||
enum CodingKeys: String, CodingKey {
|
|
||||||
case type
|
|
||||||
case session
|
|
||||||
case captchaResponse = "response"
|
|
||||||
case threePIDCredentials = "threepid_creds"
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates the authentication parameters for a captcha step.
|
|
||||||
static func captchaParameters(session: String, captchaResponse: String) -> AuthenticationParameters {
|
|
||||||
AuthenticationParameters(type: kMXLoginFlowTypeRecaptcha, session: session, captchaResponse: captchaResponse)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates the authentication parameters for a third party ID step using an email address.
|
|
||||||
static func emailIdentityParameters(session: String, threePIDCredentials: ThreePIDCredentials) -> AuthenticationParameters {
|
|
||||||
AuthenticationParameters(type: kMXLoginFlowTypeEmailIdentity, session: session, threePIDCredentials: threePIDCredentials)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note that there is a bug in Synapse (needs investigation), but if we pass .msisdn,
|
|
||||||
// the homeserver answer with the login flow with MatrixError fields and not with a simple MatrixError 401.
|
|
||||||
/// Creates the authentication parameters for a third party ID step using a phone number.
|
|
||||||
static func msisdnIdentityParameters(session: String, threePIDCredentials: ThreePIDCredentials) -> AuthenticationParameters {
|
|
||||||
AuthenticationParameters(type: kMXLoginFlowTypeMSISDN, session: session, threePIDCredentials: threePIDCredentials)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates the authentication parameters for a password reset step.
|
|
||||||
static func resetPasswordParameters(clientSecret: String, sessionID: String) -> AuthenticationParameters {
|
|
||||||
AuthenticationParameters(type: kMXLoginFlowTypeEmailIdentity,
|
|
||||||
session: nil,
|
|
||||||
threePIDCredentials: ThreePIDCredentials(clientSecret: clientSecret, sessionID: sessionID))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The result from a registration screen's coordinator
|
/// The result from a registration screen's coordinator
|
||||||
enum AuthenticationRegistrationStageResult {
|
enum AuthenticationRegistrationStageResult {
|
||||||
|
@ -126,24 +42,24 @@ struct FlowResult {
|
||||||
|
|
||||||
/// A stage in the authentication flow.
|
/// A stage in the authentication flow.
|
||||||
enum Stage {
|
enum Stage {
|
||||||
/// The stage with the type `m.login.recaptcha`.
|
|
||||||
case reCaptcha(isMandatory: Bool, siteKey: String)
|
|
||||||
|
|
||||||
/// The stage with the type `m.login.email.identity`.
|
/// The stage with the type `m.login.email.identity`.
|
||||||
case email(isMandatory: Bool)
|
case email(isMandatory: Bool)
|
||||||
|
|
||||||
/// The stage with the type `m.login.msisdn`.
|
/// The stage with the type `m.login.msisdn`.
|
||||||
case msisdn(isMandatory: Bool)
|
case msisdn(isMandatory: Bool)
|
||||||
|
|
||||||
|
/// The stage with the type `m.login.terms`.
|
||||||
|
case terms(isMandatory: Bool, terms: MXLoginTerms?)
|
||||||
|
|
||||||
|
/// The stage with the type `m.login.recaptcha`.
|
||||||
|
case reCaptcha(isMandatory: Bool, siteKey: String)
|
||||||
|
|
||||||
/// The stage with the type `m.login.dummy`.
|
/// The stage with the type `m.login.dummy`.
|
||||||
///
|
///
|
||||||
/// This stage can be mandatory if there is no other stages. In this case the account cannot
|
/// This stage can be mandatory if there is no other stages. In this case the account cannot
|
||||||
/// be created by just sending a username and a password, the dummy stage has to be completed.
|
/// be created by just sending a username and a password, the dummy stage has to be completed.
|
||||||
case dummy(isMandatory: Bool)
|
case dummy(isMandatory: Bool)
|
||||||
|
|
||||||
/// The stage with the type `m.login.terms`.
|
|
||||||
case terms(isMandatory: Bool, terms: MXLoginTerms?)
|
|
||||||
|
|
||||||
/// A stage of an unknown type.
|
/// A stage of an unknown type.
|
||||||
case other(isMandatory: Bool, type: String, params: [AnyHashable: Any])
|
case other(isMandatory: Bool, type: String, params: [AnyHashable: Any])
|
||||||
|
|
||||||
|
@ -172,8 +88,8 @@ struct FlowResult {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Determines the next stage to be completed in the flow.
|
/// Determines the next stage to be completed in the flow, following the order Email → Terms → ReCaptcha.
|
||||||
var nextUncompletedStage: Stage? {
|
var nextUncompletedStageOrdered: Stage? {
|
||||||
if let emailStage = missingStages.first(where: { if case .email = $0 { return true } else { return false } }) {
|
if let emailStage = missingStages.first(where: { if case .email = $0 { return true } else { return false } }) {
|
||||||
return emailStage
|
return emailStage
|
||||||
}
|
}
|
||||||
|
@ -183,16 +99,23 @@ struct FlowResult {
|
||||||
if let reCaptchaStage = missingStages.first(where: { if case .reCaptcha = $0 { return true } else { return false } }) {
|
if let reCaptchaStage = missingStages.first(where: { if case .reCaptcha = $0 { return true } else { return false } }) {
|
||||||
return reCaptchaStage
|
return reCaptchaStage
|
||||||
}
|
}
|
||||||
if let msisdnStage = missingStages.first(where: { if case .msisdn = $0 { return true } else { return false } }) {
|
|
||||||
return msisdnStage
|
|
||||||
}
|
|
||||||
|
|
||||||
MXLog.failure("[FlowResult.Stage] nextUncompletedStage: The dummy stage should be handled silently and any other stages should trigger the fallback flow.")
|
return nextUncompletedStage
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Determines the next stage to be completed in the flow honouring the server's ordering.
|
||||||
|
/// This ordering is slightly broken when the are multiple flows as mandatory stages are
|
||||||
|
/// shown first and then optional ones afterwards.
|
||||||
|
var nextUncompletedStage: Stage? {
|
||||||
|
if let mandatoryStage = missingStages.filter(\.isMandatory).first {
|
||||||
|
return mandatoryStage
|
||||||
|
}
|
||||||
return missingStages.first
|
return missingStages.first
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether fallback registration should be used due to unsupported stages.
|
||||||
var needsFallback : Bool {
|
var needsFallback : Bool {
|
||||||
missingStages.filter { $0.isMandatory }.contains { stage in
|
missingStages.filter(\.isMandatory).contains { stage in
|
||||||
if case .other = stage { return true } else { return false }
|
if case .other = stage { return true } else { return false }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -201,7 +124,7 @@ struct FlowResult {
|
||||||
extension MXAuthenticationSession {
|
extension MXAuthenticationSession {
|
||||||
/// The flows from the session mapped as a `FlowResult` value.
|
/// The flows from the session mapped as a `FlowResult` value.
|
||||||
var flowResult: FlowResult {
|
var flowResult: FlowResult {
|
||||||
let allFlowTypes = Set(flows.flatMap { $0.stages ?? [] }) // Using a Set here loses the order, but an order is forced during presentation anyway.
|
let allFlowTypes = OrderedSet(flows.flatMap { $0.stages ?? [] })
|
||||||
var missingStages = [FlowResult.Stage]()
|
var missingStages = [FlowResult.Stage]()
|
||||||
var completedStages = [FlowResult.Stage]()
|
var completedStages = [FlowResult.Stage]()
|
||||||
|
|
||||||
|
@ -239,3 +162,28 @@ extension MXAuthenticationSession {
|
||||||
return FlowResult(missingStages: missingStages, completedStages: completedStages)
|
return FlowResult(missingStages: missingStages, completedStages: completedStages)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Equatable
|
||||||
|
|
||||||
|
extension FlowResult.Stage: Equatable {
|
||||||
|
// The [AnyHashable: Any] dictionary breaks automatic conformance, so add manually (but ignore this value).
|
||||||
|
static func == (lhs: FlowResult.Stage, rhs: FlowResult.Stage) -> Bool {
|
||||||
|
switch (lhs, rhs) {
|
||||||
|
case (.email(let lhsMandatory), .email(let rhsMandatory)):
|
||||||
|
return lhsMandatory == rhsMandatory
|
||||||
|
case (.msisdn(let lhsMandatory), .msisdn(let rhsMandatory)):
|
||||||
|
return lhsMandatory == rhsMandatory
|
||||||
|
case (.terms(let lhsMandatory, let lhsTerms), .terms(let rhsMandatory, let rhsTerms)):
|
||||||
|
// TODO: Add comprehensive Equatable conformance on MXLoginTerms
|
||||||
|
return lhsMandatory == rhsMandatory && lhsTerms?.policies == rhsTerms?.policies
|
||||||
|
case (.reCaptcha(let lhsMandatory, let lhsSiteKey), .reCaptcha(let rhsMandatory, let rhsSiteKey)):
|
||||||
|
return lhsMandatory == rhsMandatory && lhsSiteKey == rhsSiteKey
|
||||||
|
case (.dummy(let lhsMandatory), .dummy(let rhsMandatory)):
|
||||||
|
return lhsMandatory == rhsMandatory
|
||||||
|
case (.other(let lhsMandatory, let lhsType, _), .other(let rhsMandatory, let rhsType, _)):
|
||||||
|
return lhsMandatory == rhsMandatory && lhsType == rhsType
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,102 @@
|
||||||
|
//
|
||||||
|
// Copyright 2022 New Vector Ltd
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
/// The parameters used for registration requests.
|
||||||
|
struct RegistrationParameters: Codable {
|
||||||
|
/// Authentication parameters
|
||||||
|
var auth: AuthenticationParameters?
|
||||||
|
|
||||||
|
/// The account username
|
||||||
|
var username: String?
|
||||||
|
|
||||||
|
/// The account password
|
||||||
|
var password: String?
|
||||||
|
|
||||||
|
/// Device name
|
||||||
|
var initialDeviceDisplayName: String?
|
||||||
|
|
||||||
|
/// Temporary flag to notify the server that we support MSISDN flow. Used to prevent old app
|
||||||
|
/// versions to end up in fallback because the HS returns the MSISDN flow which they don't support
|
||||||
|
var xShowMSISDN: Bool?
|
||||||
|
|
||||||
|
enum CodingKeys: String, CodingKey {
|
||||||
|
case auth
|
||||||
|
case username
|
||||||
|
case password
|
||||||
|
case initialDeviceDisplayName = "initial_device_display_name"
|
||||||
|
case xShowMSISDN = "x_show_msisdn"
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The parameters as a JSON dictionary for use in MXRestClient.
|
||||||
|
func dictionary() throws -> [String: Any] {
|
||||||
|
let jsonData = try JSONEncoder().encode(self)
|
||||||
|
let object = try JSONSerialization.jsonObject(with: jsonData)
|
||||||
|
guard let dictionary = object as? [String: Any] else {
|
||||||
|
MXLog.error("[RegistrationParameters] dictionary: Unexpected type decoded \(type(of: object)). Expected a Dictionary.")
|
||||||
|
throw AuthenticationError.dictionaryError
|
||||||
|
}
|
||||||
|
|
||||||
|
return dictionary
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The data passed to the `auth` parameter in registration requests.
|
||||||
|
struct AuthenticationParameters: Codable {
|
||||||
|
/// The type of authentication taking place. The identifier from `MXLoginFlowType`.
|
||||||
|
let type: String
|
||||||
|
|
||||||
|
/// Note: session can be null for reset password request
|
||||||
|
var session: String?
|
||||||
|
|
||||||
|
/// parameter for "m.login.recaptcha" type
|
||||||
|
var captchaResponse: String?
|
||||||
|
|
||||||
|
/// parameter for "m.login.email.identity" type
|
||||||
|
var threePIDCredentials: ThreePIDCredentials?
|
||||||
|
|
||||||
|
enum CodingKeys: String, CodingKey {
|
||||||
|
case type
|
||||||
|
case session
|
||||||
|
case captchaResponse = "response"
|
||||||
|
case threePIDCredentials = "threepid_creds"
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates the authentication parameters for a captcha step.
|
||||||
|
static func captchaParameters(session: String, captchaResponse: String) -> AuthenticationParameters {
|
||||||
|
AuthenticationParameters(type: kMXLoginFlowTypeRecaptcha, session: session, captchaResponse: captchaResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates the authentication parameters for a third party ID step using an email address.
|
||||||
|
static func emailIdentityParameters(session: String, threePIDCredentials: ThreePIDCredentials) -> AuthenticationParameters {
|
||||||
|
AuthenticationParameters(type: kMXLoginFlowTypeEmailIdentity, session: session, threePIDCredentials: threePIDCredentials)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note that there is a bug in Synapse (needs investigation), but if we pass .msisdn,
|
||||||
|
// the homeserver answer with the login flow with MatrixError fields and not with a simple MatrixError 401.
|
||||||
|
/// Creates the authentication parameters for a third party ID step using a phone number.
|
||||||
|
static func msisdnIdentityParameters(session: String, threePIDCredentials: ThreePIDCredentials) -> AuthenticationParameters {
|
||||||
|
AuthenticationParameters(type: kMXLoginFlowTypeMSISDN, session: session, threePIDCredentials: threePIDCredentials)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates the authentication parameters for a password reset step.
|
||||||
|
static func resetPasswordParameters(clientSecret: String, sessionID: String) -> AuthenticationParameters {
|
||||||
|
AuthenticationParameters(type: kMXLoginFlowTypeEmailIdentity,
|
||||||
|
session: nil,
|
||||||
|
threePIDCredentials: ThreePIDCredentials(clientSecret: clientSecret, sessionID: sessionID))
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,7 +18,6 @@ import XCTest
|
||||||
|
|
||||||
@testable import Riot
|
@testable import Riot
|
||||||
|
|
||||||
@available(iOS 14.0, *)
|
|
||||||
class AuthenticationServiceTests: XCTestCase {
|
class AuthenticationServiceTests: XCTestCase {
|
||||||
func testRegistrationWizardWhenStartingLoginFlow() async throws {
|
func testRegistrationWizardWhenStartingLoginFlow() async throws {
|
||||||
// Given a fresh service.
|
// Given a fresh service.
|
191
RiotTests/Modules/Authentication/RegistrationTests.swift
Normal file
191
RiotTests/Modules/Authentication/RegistrationTests.swift
Normal file
|
@ -0,0 +1,191 @@
|
||||||
|
//
|
||||||
|
// Copyright 2022 New Vector Ltd
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
//
|
||||||
|
|
||||||
|
import XCTest
|
||||||
|
|
||||||
|
@testable import Riot
|
||||||
|
|
||||||
|
class RegistrationTests: XCTestCase {
|
||||||
|
/// Makes an authentication session that mimics the matrix.org flow.
|
||||||
|
func makeSession() -> MXAuthenticationSession {
|
||||||
|
let flow = MXLoginFlow()
|
||||||
|
flow.stages = [kMXLoginFlowTypeRecaptcha, kMXLoginFlowTypeTerms, kMXLoginFlowTypeEmailIdentity]
|
||||||
|
|
||||||
|
let session = MXAuthenticationSession()
|
||||||
|
session.flows = [flow]
|
||||||
|
session.params = [:]
|
||||||
|
return session
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Makes an authentication session that has two flows.
|
||||||
|
func makeSessionWithTwoFlows() -> MXAuthenticationSession {
|
||||||
|
let flow1 = MXLoginFlow()
|
||||||
|
flow1.stages = [kMXLoginFlowTypeMSISDN, kMXLoginFlowTypeTerms, kMXLoginFlowTypeRecaptcha]
|
||||||
|
|
||||||
|
let flow2 = MXLoginFlow()
|
||||||
|
flow2.stages = [kMXLoginFlowTypeEmailIdentity, kMXLoginFlowTypeTerms, kMXLoginFlowTypeRecaptcha]
|
||||||
|
|
||||||
|
let session = MXAuthenticationSession()
|
||||||
|
session.flows = [flow1, flow2]
|
||||||
|
session.params = [:]
|
||||||
|
return session
|
||||||
|
}
|
||||||
|
|
||||||
|
func testRegistrationResultForNewSession() {
|
||||||
|
// Given a fresh session.
|
||||||
|
let session = makeSession()
|
||||||
|
|
||||||
|
// Then the result should have no completed stages.
|
||||||
|
let flowResult = session.flowResult
|
||||||
|
XCTAssertEqual(flowResult.completedStages.count, 0,
|
||||||
|
"There should be no completed stages for a new session.")
|
||||||
|
XCTAssertEqual(flowResult.missingStages.count, 3,
|
||||||
|
"The result should have 3 missing stages.")
|
||||||
|
XCTAssertEqual(flowResult.nextUncompletedStage, .reCaptcha(isMandatory: true, siteKey: ""),
|
||||||
|
"The first stage should match the order in the session.")
|
||||||
|
XCTAssertEqual(flowResult.nextUncompletedStageOrdered, .email(isMandatory: true),
|
||||||
|
"The first stage when ordered should be Email for a new session.")
|
||||||
|
XCTAssertFalse(flowResult.needsFallback,
|
||||||
|
"Fallback shouldn't be needed when the stages are all supported.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testRegistrationResultAfterEmail() {
|
||||||
|
// Given a fresh session.
|
||||||
|
let session = makeSession()
|
||||||
|
|
||||||
|
// When completing the email stage.
|
||||||
|
session.completed = [kMXLoginFlowTypeEmailIdentity]
|
||||||
|
|
||||||
|
// Then the result should reflect the first stage has been completed.
|
||||||
|
let flowResult = session.flowResult
|
||||||
|
XCTAssertEqual(flowResult.completedStages.count, 1,
|
||||||
|
"The result should have 1 completed stage.")
|
||||||
|
XCTAssertEqual(flowResult.missingStages.count, 2,
|
||||||
|
"The result should have 2 missing stages.")
|
||||||
|
XCTAssertEqual(flowResult.nextUncompletedStage, .reCaptcha(isMandatory: true, siteKey: ""),
|
||||||
|
"The next stage should be the ReCaptcha stage.")
|
||||||
|
XCTAssertEqual(flowResult.nextUncompletedStageOrdered, .terms(isMandatory: true, terms: MXLoginTerms(fromJSON: [:])),
|
||||||
|
"The next stage when ordered should be the Terms stage.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testRegistrationResultAfterEmailAndTerms() {
|
||||||
|
// Given a fresh session.
|
||||||
|
let session = makeSession()
|
||||||
|
|
||||||
|
// When completing the email and terms stages.
|
||||||
|
session.completed = [kMXLoginFlowTypeEmailIdentity, kMXLoginFlowTypeTerms]
|
||||||
|
|
||||||
|
// Then the result should reflect the first 2 stages have been completed.
|
||||||
|
let flowResult = session.flowResult
|
||||||
|
XCTAssertEqual(flowResult.completedStages.count, 2,
|
||||||
|
"The result should have 2 completed stages.")
|
||||||
|
XCTAssertEqual(flowResult.missingStages.count, 1,
|
||||||
|
"The result should have 1 missing stage.")
|
||||||
|
XCTAssertEqual(flowResult.nextUncompletedStage, .reCaptcha(isMandatory: true, siteKey: ""),
|
||||||
|
"The next stage should be the ReCaptcha stage.")
|
||||||
|
XCTAssertEqual(flowResult.nextUncompletedStageOrdered, .reCaptcha(isMandatory: true, siteKey: ""),
|
||||||
|
"The next stage when ordered should be the ReCaptcha stage.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testRegistrationResultAfterAllStages() {
|
||||||
|
// Given a fresh session.
|
||||||
|
let session = makeSession()
|
||||||
|
|
||||||
|
// When completing all of the stages.
|
||||||
|
session.completed = [kMXLoginFlowTypeEmailIdentity, kMXLoginFlowTypeTerms, kMXLoginFlowTypeRecaptcha]
|
||||||
|
|
||||||
|
// Then the result shouldn't have any missing stages.
|
||||||
|
let flowResult = session.flowResult
|
||||||
|
XCTAssertEqual(flowResult.completedStages.count, 3,
|
||||||
|
"The result should have all completed stages.")
|
||||||
|
XCTAssertEqual(flowResult.missingStages.count, 0,
|
||||||
|
"The result should have no missing stages.")
|
||||||
|
XCTAssertNil(flowResult.nextUncompletedStage,
|
||||||
|
"There shouldn't be any more stages to complete.")
|
||||||
|
XCTAssertNil(flowResult.nextUncompletedStageOrdered,
|
||||||
|
"There shouldn't be any more stages to complete.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testRegistrationResultCustomStage() {
|
||||||
|
// Given a session that contains a single flow with a custom stage.
|
||||||
|
let session = makeSession()
|
||||||
|
session.flows.first?.stages.append("test.flow")
|
||||||
|
|
||||||
|
// Then the result should indicate that fallback authentication should be used.
|
||||||
|
let flowResult = session.flowResult
|
||||||
|
XCTAssertTrue(flowResult.needsFallback, "Fallback should be required when a custom stage is present.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testRegistrationResultTwoFlows() {
|
||||||
|
// Given a session with two flows.
|
||||||
|
let session = makeSessionWithTwoFlows()
|
||||||
|
|
||||||
|
// Then the result should know the mandatory/optional stages and start with the mandatory stages unless ordered
|
||||||
|
let flowResult = session.flowResult
|
||||||
|
XCTAssertFalse(flowResult.needsFallback,
|
||||||
|
"Fallback shouldn't be needed when the stages are all supported.")
|
||||||
|
XCTAssertEqual(flowResult.nextUncompletedStage, .terms(isMandatory: true, terms: MXLoginTerms(fromJSON: [:])),
|
||||||
|
"The first stage should be the Terms stage.")
|
||||||
|
XCTAssertEqual(flowResult.nextUncompletedStageOrdered, .email(isMandatory: false),
|
||||||
|
"The first stage when ordered should be the Email stage.")
|
||||||
|
|
||||||
|
flowResult.missingStages.forEach { stage in
|
||||||
|
switch stage {
|
||||||
|
case .email(let isMandatory):
|
||||||
|
XCTAssertFalse(isMandatory, "The Email stage should be optional.")
|
||||||
|
case .msisdn(let isMandatory):
|
||||||
|
XCTAssertFalse(isMandatory, "The MSISDN stage should be optional.")
|
||||||
|
case .terms(let isMandatory, _):
|
||||||
|
XCTAssertTrue(isMandatory, "The Terms stage should be mandatory.")
|
||||||
|
case .reCaptcha(let isMandatory, _):
|
||||||
|
XCTAssertTrue(isMandatory, "The ReCaptcha stage should be mandatory.")
|
||||||
|
default:
|
||||||
|
XCTFail("There shouldn't be any other types of stage in the result.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testRegistrationResultTwoFlowsAfterMandatoryStages() {
|
||||||
|
// Given a session with two flows.
|
||||||
|
let session = makeSessionWithTwoFlows()
|
||||||
|
|
||||||
|
// When completing the terms and recaptcha stages.
|
||||||
|
session.completed = [kMXLoginFlowTypeTerms, kMXLoginFlowTypeRecaptcha]
|
||||||
|
|
||||||
|
// Then the result should have the optional stages remaining.
|
||||||
|
let flowResult = session.flowResult
|
||||||
|
XCTAssertEqual(flowResult.completedStages.count, 2,
|
||||||
|
"The result should have 2 completed stages.")
|
||||||
|
XCTAssertEqual(flowResult.missingStages.count, 2,
|
||||||
|
"The result should have 2 missing stage.")
|
||||||
|
XCTAssertEqual(flowResult.nextUncompletedStage, .msisdn(isMandatory: false),
|
||||||
|
"The next stage should be the MSISDN stage.")
|
||||||
|
XCTAssertEqual(flowResult.nextUncompletedStageOrdered, .email(isMandatory: false),
|
||||||
|
"The next stage when ordered should be the Email stage.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testRegistrationResultTwoFlowsCustomStage() {
|
||||||
|
// Given a session with a custom stage in a second flow.
|
||||||
|
let session = makeSession()
|
||||||
|
let flow = MXLoginFlow()
|
||||||
|
flow.stages = ["test.flow"]
|
||||||
|
session.flows.append(flow)
|
||||||
|
|
||||||
|
// Then the session shouldn't need fallback.
|
||||||
|
let flowResult = session.flowResult
|
||||||
|
XCTAssertFalse(flowResult.needsFallback, "Fallback shouldn't be required when a custom stage is optional.")
|
||||||
|
}
|
||||||
|
}
|
|
@ -44,3 +44,7 @@ packages:
|
||||||
url: https://github.com/maplibre/maplibre-gl-native-distribution
|
url: https://github.com/maplibre/maplibre-gl-native-distribution
|
||||||
minVersion: 5.12.2
|
minVersion: 5.12.2
|
||||||
maxVersion: 5.13.0
|
maxVersion: 5.13.0
|
||||||
|
OrderedCollections:
|
||||||
|
url: https://github.com/apple/swift-collections
|
||||||
|
minVersion: 1.0.2
|
||||||
|
maxVersion: 2.0.0
|
||||||
|
|
Loading…
Reference in a new issue