From a38db6b427f0863628d87d94790c87a724c41754 Mon Sep 17 00:00:00 2001 From: Martin Walsh Date: Thu, 12 Jan 2017 18:32:07 +0000 Subject: [PATCH 1/5] Dismiss Lock on .Signup when .Login not present Dismiss Lock on .ResetPassword when .Login not present Added ForgetPassword event handling to Dispatcher Added Dispatch/Dismiss tests to Database Password Interactor --- Lock/DatabaseInteractor.swift | 2 +- Lock/DatabasePasswordInteractor.swift | 7 +- Lock/ObserverStore.swift | 13 ++-- Lock/Router.swift | 2 +- .../Interactors/DatabaseInteractorSpec.swift | 40 +++++++++++- .../DatabasePasswordInteractorSpec.swift | 64 +++++++++++++++++-- LockTests/Models/ObserverStoreSpec.swift | 18 +++++- 7 files changed, 129 insertions(+), 17 deletions(-) diff --git a/Lock/DatabaseInteractor.swift b/Lock/DatabaseInteractor.swift index f2f72a4b4..42c16a9cb 100644 --- a/Lock/DatabaseInteractor.swift +++ b/Lock/DatabaseInteractor.swift @@ -135,7 +135,7 @@ struct DatabaseInteractor: DatabaseAuthenticatable, DatabaseUserCreator, Loggabl "verified": user.verified ] extra["username"] = user.username - self.dispatcher.dispatch(result: .signUp(user.email, extra)) + self.dispatcher.dispatch(result: .signUp(user.email, extra, !self.options.allow.contains(.Login))) callback(nil, nil) } case .failure(let cause as AuthenticationError) where cause.isPasswordNotStrongEnough: diff --git a/Lock/DatabasePasswordInteractor.swift b/Lock/DatabasePasswordInteractor.swift index 3b6755ac3..17695624e 100644 --- a/Lock/DatabasePasswordInteractor.swift +++ b/Lock/DatabasePasswordInteractor.swift @@ -26,18 +26,22 @@ import Auth0 struct DatabasePasswordInteractor: PasswordRecoverable { private var user: DatabaseUser + private let dispatcher: Dispatcher var email: String? { return self.user.email } var validEmail: Bool { return self.user.validEmail } let authentication: Authentication + let options: Options let connections: Connections let emailValidator: InputValidator = EmailValidator() - init(connections: Connections, authentication: Authentication, user: DatabaseUser) { + init(connections: Connections, authentication: Authentication, user: DatabaseUser, options: Options, dispatcher: Dispatcher) { self.authentication = authentication self.connections = connections self.user = user + self.options = options + self.dispatcher = dispatcher } mutating func updateEmail(_ value: String?) throws { @@ -55,6 +59,7 @@ struct DatabasePasswordInteractor: PasswordRecoverable { .resetPassword(email: email, connection: connection) .start { guard case .success = $0 else { return callback(.emailNotSent) } + self.dispatcher.dispatch(result: .forgotPassword(email, !self.options.allow.contains(.Login))) callback(nil) } } diff --git a/Lock/ObserverStore.swift b/Lock/ObserverStore.swift index ac3e7fcba..f992aac95 100644 --- a/Lock/ObserverStore.swift +++ b/Lock/ObserverStore.swift @@ -28,6 +28,7 @@ struct ObserverStore: Dispatcher { var onFailure: (Error) -> () = { _ in } var onCancel: () -> () = { } var onSignUp: (String, [String: Any]) -> () = { _ in } + var onForgetPassword: (String) -> () = { _ in } weak var controller: UIViewController? @@ -40,10 +41,10 @@ struct ObserverStore: Dispatcher { closure = { self.onFailure(error) } case .cancel: closure = dismiss(from: controller?.presentingViewController, completion: { self.onCancel() }) - case .signUp(let email, let attributes): - closure = { self.onSignUp(email, attributes) } - default: - closure = {} + case .signUp(let email, let attributes, let dismissLock): + closure = dismissLock ? dismiss(from: controller?.presentingViewController, completion: { self.onSignUp(email, attributes) }) : { self.onSignUp(email, attributes) } + case .forgotPassword(let email, let dismissLock): + closure = dismissLock ? dismiss(from: controller?.presentingViewController, completion: { self.onForgetPassword(email) }) : { self.onForgetPassword(email) } } Queue.main.async(closure) } @@ -58,8 +59,8 @@ enum Result { case auth(Credentials) case error(Error) case cancel - case signUp(String, [String: Any]) - case forgotPassword(String) + case signUp(String, [String: Any], Bool) + case forgotPassword(String, Bool) } protocol Dispatcher { diff --git a/Lock/Router.swift b/Lock/Router.swift index 437620538..310ff20f0 100644 --- a/Lock/Router.swift +++ b/Lock/Router.swift @@ -97,7 +97,7 @@ struct Router: Navigable { exit(withError: UnrecoverableError.clientWithNoConnections) return nil } - let interactor = DatabasePasswordInteractor(connections: connections, authentication: self.lock.authentication, user: self.user) + let interactor = DatabasePasswordInteractor(connections: connections, authentication: self.lock.authentication, user: self.user, options: self.lock.options, dispatcher: lock.observerStore) let presenter = DatabaseForgotPasswordPresenter(interactor: interactor, connections: connections, navigator: self, options: self.lock.options) presenter.customLogger = self.lock.logger return presenter diff --git a/LockTests/Interactors/DatabaseInteractorSpec.swift b/LockTests/Interactors/DatabaseInteractorSpec.swift index cc503619f..85be73334 100644 --- a/LockTests/Interactors/DatabaseInteractorSpec.swift +++ b/LockTests/Interactors/DatabaseInteractorSpec.swift @@ -733,7 +733,6 @@ class DatabaseInteractorSpec: QuickSpec { } } } - } // MARK: - Signup @@ -1024,16 +1023,28 @@ class DatabaseInteractorSpec: QuickSpec { var options: LockOptions! var newUserEmail: String? + var controller: MockLockController! + var presenter: MockController! + var dispatcher: ObserverStore! + var db: DatabaseConnection! beforeEach { + options = LockOptions() + dispatcher = ObserverStore() + presenter = MockController() + controller = MockLockController(lock: Lock()) + presenter.presented = controller + controller.presenting = presenter + dispatcher.controller = controller + newUserEmail = nil options = LockOptions() options.loginAfterSignup = false - var dispatcher = ObserverStore() + dispatcher.onSignUp = { email, _ in newUserEmail = email } - let db = DatabaseConnection(name: connection, requiresUsername: true, usernameValidator: UsernameValidator(withLength: 1...15, characterSet: UsernameValidator.auth0), passwordValidator: PasswordPolicyValidator(policy: .good)) + db = DatabaseConnection(name: connection, requiresUsername: true, usernameValidator: UsernameValidator(withLength: 1...15, characterSet: UsernameValidator.auth0), passwordValidator: PasswordPolicyValidator(policy: .good)) database = DatabaseInteractor(connection: db, authentication: authentication, user: user, options: options, dispatcher: dispatcher) stub(condition: databaseLogin(identifier: email, password: password, connection: connection)) { _ in return Auth0Stubs.failure() } stub(condition: databaseSignUp(email: email, username: username, password: password, connection: connection)) { _ in return Auth0Stubs.createdUser(email) } @@ -1058,6 +1069,29 @@ class DatabaseInteractorSpec: QuickSpec { try! database.update(.password(enforcePolicy: false), value: password) database.create { _ in } expect(newUserEmail).toEventually(equal(email)) + expect(presenter.presented).toEventuallyNot(beNil(), timeout: 2) + } + + it("should dispatch signup event and dimiss Lock") { + options.allow = .Signup + database = DatabaseInteractor(connection: db, authentication: authentication, user: user, options: options, dispatcher: dispatcher) + try! database.update(.email, value: email) + try! database.update(.username, value: username) + try! database.update(.password(enforcePolicy: false), value: password) + database.create { _ in } + expect(newUserEmail).toEventually(equal(email)) + expect(presenter.presented).toEventually(beNil(), timeout: 2) + } + + it("should dispatch signup event and dimiss Lock") { + options.allow = [.Signup, .ResetPassword] + database = DatabaseInteractor(connection: db, authentication: authentication, user: user, options: options, dispatcher: dispatcher) + try! database.update(.email, value: email) + try! database.update(.username, value: username) + try! database.update(.password(enforcePolicy: false), value: password) + database.create { _ in } + expect(newUserEmail).toEventually(equal(email)) + expect(presenter.presented).toEventually(beNil(), timeout: 2) } } diff --git a/LockTests/Interactors/DatabasePasswordInteractorSpec.swift b/LockTests/Interactors/DatabasePasswordInteractorSpec.swift index 76b546ea5..8ce2b7474 100644 --- a/LockTests/Interactors/DatabasePasswordInteractorSpec.swift +++ b/LockTests/Interactors/DatabasePasswordInteractorSpec.swift @@ -39,7 +39,7 @@ class DatabasePasswordInteractorSpec: QuickSpec { } describe("init") { - let database = DatabasePasswordInteractor(connections: OfflineConnections(), authentication: authentication, user: User()) + let database = DatabasePasswordInteractor(connections: OfflineConnections(), authentication: authentication, user: User(), options: LockOptions(), dispatcher: ObserverStore()) it("should build with authentication") { expect(database).toNot(beNil()) @@ -54,12 +54,16 @@ class DatabasePasswordInteractorSpec: QuickSpec { var user: User! var connections: OfflineConnections! var forgot: DatabasePasswordInteractor! + var options: OptionBuildable! + var dispatcher: Dispatcher! beforeEach { + options = LockOptions() + dispatcher = ObserverStore() connections = OfflineConnections() connections.database(name: connection, requiresUsername: true) user = User() - forgot = DatabasePasswordInteractor(connections: connections, authentication: authentication, user: user) + forgot = DatabasePasswordInteractor(connections: connections, authentication: authentication, user: user, options: options, dispatcher: dispatcher) } describe("updateEmail") { @@ -102,7 +106,7 @@ class DatabasePasswordInteractorSpec: QuickSpec { describe("request email") { it("should fail if no db connection is found") { - forgot = DatabasePasswordInteractor(connections: OfflineConnections(), authentication: authentication, user: user) + forgot = DatabasePasswordInteractor(connections: OfflineConnections(), authentication: authentication, user: user, options: options, dispatcher: dispatcher) try! forgot.updateEmail(email) waitUntil(timeout: 2) { done in forgot.requestEmail { error in @@ -143,7 +147,59 @@ class DatabasePasswordInteractorSpec: QuickSpec { } } + context("on success") { + + var userEmail: String? + var controller: MockLockController! + var presenter: MockController! + var dispatcher: ObserverStore! + + beforeEach { + dispatcher = ObserverStore() + presenter = MockController() + controller = MockLockController(lock: Lock()) + presenter.presented = controller + controller.presenting = presenter + dispatcher.controller = controller + + userEmail = nil + options = LockOptions() + dispatcher.onForgetPassword = { email in + userEmail = email + } + forgot = DatabasePasswordInteractor(connections: connections, authentication: authentication, user: user, options: options, dispatcher: dispatcher) + stub(condition: databaseForgotPassword(email: email, connection: connection)) { _ in return Auth0Stubs.forgotEmailSent() } + } + + it("should dispatch forgotPassword event") { + try! forgot.updateEmail(email) + forgot.requestEmail { _ in } + expect(userEmail).toEventually(equal(email)) + expect(presenter.presented).toEventuallyNot(beNil(), timeout: 4) + } + + it("should dispatch forgotPassword event and dismiss lock") { + options.allow = .ResetPassword + forgot = DatabasePasswordInteractor(connections: connections, authentication: authentication, user: user, options: options, dispatcher: dispatcher) + + try! forgot.updateEmail(email) + forgot.requestEmail { _ in } + expect(userEmail).toEventually(equal(email)) + expect(presenter.presented).toEventually(beNil(), timeout: 4) + } + + it("should dispatch forgotPassword event and dismiss lock") { + options.allow = [.ResetPassword, .Signup] + forgot = DatabasePasswordInteractor(connections: connections, authentication: authentication, user: user, options: options, dispatcher: dispatcher) + + try! forgot.updateEmail(email) + forgot.requestEmail { _ in } + expect(userEmail).toEventually(equal(email)) + expect(presenter.presented).toEventually(beNil(), timeout: 4) + } + + } } } - + } diff --git a/LockTests/Models/ObserverStoreSpec.swift b/LockTests/Models/ObserverStoreSpec.swift index 8c661c6ef..6c9703d53 100644 --- a/LockTests/Models/ObserverStoreSpec.swift +++ b/LockTests/Models/ObserverStoreSpec.swift @@ -49,6 +49,7 @@ class ObserverStoreSpec: QuickSpec { store.onAuth = { credentials = $0 } store.onCancel = { closed = true } store.onSignUp = { newEmail = $0; newAttributes = $1 } + store.onForgetPassword = { newEmail = $0 } dispatcher = store } @@ -69,11 +70,16 @@ class ObserverStoreSpec: QuickSpec { } it("should disptach when user signs up") { - dispatcher.dispatch(result: .signUp(email, ["username": username])) + dispatcher.dispatch(result: .signUp(email, ["username": username], false)) expect(newEmail).toEventually(equal(email)) expect(newAttributes?["username"] as? String).toEventually(equal(username)) } + it("should disptach when user requests password") { + dispatcher.dispatch(result: .forgotPassword(email, false)) + expect(newEmail).toEventually(equal(email)) + } + pending("controller displayed") { // TODO: Check why it fails only in travis @@ -98,7 +104,17 @@ class ObserverStoreSpec: QuickSpec { dispatcher.dispatch(result: .cancel) expect(presenter.presented).toEventually(beNil(), timeout: 2) } + + it("should dismiss onSignUp") { + dispatcher.dispatch(result: .signUp(email, ["username": username], true)) + expect(newEmail).toEventually(equal(email)) + expect(presenter.presented).toEventually(beNil(), timeout: 2) + } + it("should dismiss forgotPassword") { + dispatcher.dispatch(result: .forgotPassword(email, true)) + expect(presenter.presented).toEventually(beNil(), timeout: 2) + } } } } From c996a6d5b06ce2b3a5775b0d120556ecb8f70383 Mon Sep 17 00:00:00 2001 From: Martin Walsh Date: Fri, 13 Jan 2017 11:22:10 +0000 Subject: [PATCH 2/5] Refactor dismissLock flag into dispatch func Remove success banner display when auto closing Added autoClose to options for specifying mode auto closing Added validation for 'impossible to close Lock' miss configurations Added more dispatch tests in DatabaseInteractor and DatabasePasswordInteractor Login will always auto close for all connection types --- Lock/Auth0OAuth2Interactor.swift | 2 +- Lock/DatabaseForgotPasswordPresenter.swift | 4 +- Lock/DatabaseInteractor.swift | 4 +- Lock/DatabasePasswordInteractor.swift | 2 +- Lock/DatabasePresenter.swift | 4 +- Lock/EnterpriseActiveAuthInteractor.swift | 2 +- Lock/LockOptions.swift | 1 + Lock/LockViewController.swift | 2 +- Lock/MultifactorInteractor.swift | 2 +- Lock/ObserverStore.swift | 23 +- Lock/OptionBuildable.swift | 4 + Lock/Options.swift | 1 + .../Interactors/DatabaseInteractorSpec.swift | 210 ++++++++++++------ .../DatabasePasswordInteractorSpec.swift | 63 ++++-- LockTests/Models/ObserverStoreSpec.swift | 20 +- LockTests/OptionsSpec.swift | 28 +++ .../DatabaseForgotPasswordPresenterSpec.swift | 34 +++ .../Presenters/DatabasePresenterSpec.swift | 27 +++ 18 files changed, 313 insertions(+), 120 deletions(-) diff --git a/Lock/Auth0OAuth2Interactor.swift b/Lock/Auth0OAuth2Interactor.swift index 385eb2e3d..2b938d7ab 100644 --- a/Lock/Auth0OAuth2Interactor.swift +++ b/Lock/Auth0OAuth2Interactor.swift @@ -45,7 +45,7 @@ struct Auth0OAuth2Interactor: OAuth2Authenticatable { .start { result in switch result { case .success(let credentials): - self.dispatcher.dispatch(result: .auth(credentials)) + self.dispatcher.dispatch(result: .auth(credentials), dismissLock: true) callback(nil) case .failure(WebAuthError.userCancelled): callback(.cancelled) diff --git a/Lock/DatabaseForgotPasswordPresenter.swift b/Lock/DatabaseForgotPasswordPresenter.swift index 77ad96544..b76a3867d 100644 --- a/Lock/DatabaseForgotPasswordPresenter.swift +++ b/Lock/DatabaseForgotPasswordPresenter.swift @@ -72,7 +72,9 @@ class DatabaseForgotPasswordPresenter: Presentable, Loggable { self.logger.error("Failed with error \(error)") } else { let message = "We've just sent you an email to reset your password".i18n(key: "com.auth0.lock.database.forgot.success.message", comment: "forgot password email sent") - self.messagePresenter?.showSuccess(message) + if self.options.allow.contains(.Login) || !self.options.autoClose.contains(.ResetPassword) { + self.messagePresenter?.showSuccess(message) + } guard self.options.allow.contains(.Login) else { return } self.navigator.navigate(.root) } diff --git a/Lock/DatabaseInteractor.swift b/Lock/DatabaseInteractor.swift index 42c16a9cb..08e5dd5bd 100644 --- a/Lock/DatabaseInteractor.swift +++ b/Lock/DatabaseInteractor.swift @@ -135,7 +135,7 @@ struct DatabaseInteractor: DatabaseAuthenticatable, DatabaseUserCreator, Loggabl "verified": user.verified ] extra["username"] = user.username - self.dispatcher.dispatch(result: .signUp(user.email, extra, !self.options.allow.contains(.Login))) + self.dispatcher.dispatch(result: .signUp(user.email, extra), dismissLock: !self.options.allow.contains(.Login) && self.options.autoClose.contains(.Signup)) callback(nil, nil) } case .failure(let cause as AuthenticationError) where cause.isPasswordNotStrongEnough: @@ -204,7 +204,7 @@ struct DatabaseInteractor: DatabaseAuthenticatable, DatabaseUserCreator, Loggabl case .success(let credentials): self.logger.info("Authenticated user <\(self.identifier)>") callback(nil) - self.dispatcher.dispatch(result: .auth(credentials)) + self.dispatcher.dispatch(result: .auth(credentials), dismissLock: true) } } } diff --git a/Lock/DatabasePasswordInteractor.swift b/Lock/DatabasePasswordInteractor.swift index 17695624e..117f64a09 100644 --- a/Lock/DatabasePasswordInteractor.swift +++ b/Lock/DatabasePasswordInteractor.swift @@ -59,7 +59,7 @@ struct DatabasePasswordInteractor: PasswordRecoverable { .resetPassword(email: email, connection: connection) .start { guard case .success = $0 else { return callback(.emailNotSent) } - self.dispatcher.dispatch(result: .forgotPassword(email, !self.options.allow.contains(.Login))) + self.dispatcher.dispatch(result: .forgotPassword(email), dismissLock: !self.options.allow.contains(.Login) && self.options.autoClose.contains(.ResetPassword)) callback(nil) } } diff --git a/Lock/DatabasePresenter.swift b/Lock/DatabasePresenter.swift index 701cf8601..23dc22f52 100644 --- a/Lock/DatabasePresenter.swift +++ b/Lock/DatabasePresenter.swift @@ -162,7 +162,9 @@ class DatabasePresenter: Presentable, Loggable { if let databaseView = self.databaseView, self.options.allow.contains(.Login) { self.showLogin(inView: databaseView, identifier: self.creator.identifier) } - self.messagePresenter?.showSuccess(message) + if self.options.allow.contains(.Login) || !self.options.autoClose.contains(.Signup) { + self.messagePresenter?.showSuccess(message) + } } return } diff --git a/Lock/EnterpriseActiveAuthInteractor.swift b/Lock/EnterpriseActiveAuthInteractor.swift index 4189c48c4..c448d9101 100644 --- a/Lock/EnterpriseActiveAuthInteractor.swift +++ b/Lock/EnterpriseActiveAuthInteractor.swift @@ -151,7 +151,7 @@ struct EnterpriseActiveAuthInteractor: DatabaseAuthenticatable, Loggable { case .success(let credentials): self.logger.info("Authenticated user <\(self.identifier)>") callback(nil) - self.dispatcher.dispatch(result: .auth(credentials)) + self.dispatcher.dispatch(result: .auth(credentials), dismissLock: true) } } diff --git a/Lock/LockOptions.swift b/Lock/LockOptions.swift index cb707ff27..9feb5a3ad 100644 --- a/Lock/LockOptions.swift +++ b/Lock/LockOptions.swift @@ -33,6 +33,7 @@ struct LockOptions: OptionBuildable { var scope: String = "openid" var parameters: [String : Any] = [:] var allow: DatabaseMode = [.Login, .Signup, .ResetPassword] + var autoClose: DatabaseMode = [.Login, .Signup, .ResetPassword] var initialScreen: DatabaseScreen = .login var usernameStyle: DatabaseIdentifierStyle = [.Username, .Email] var customSignupFields: [CustomTextField] = [] diff --git a/Lock/LockViewController.swift b/Lock/LockViewController.swift index f31e220b8..8bffc3dfb 100644 --- a/Lock/LockViewController.swift +++ b/Lock/LockViewController.swift @@ -73,7 +73,7 @@ public class LockViewController: UIViewController { header.showClose = self.router.lock.options.closable header.onClosePressed = { [weak self] in - self?.router.lock.observerStore.dispatch(result: .cancel) + self?.router.lock.observerStore.dispatch(result: .cancel, dismissLock: true) } header.apply(style: style) diff --git a/Lock/MultifactorInteractor.swift b/Lock/MultifactorInteractor.swift index e0902240a..452084c35 100644 --- a/Lock/MultifactorInteractor.swift +++ b/Lock/MultifactorInteractor.swift @@ -86,7 +86,7 @@ struct MultifactorInteractor: MultifactorAuthenticatable { callback(.couldNotLogin) case .success(let credentials): callback(nil) - self.dispatcher.dispatch(result: .auth(credentials)) + self.dispatcher.dispatch(result: .auth(credentials), dismissLock: true) } } } diff --git a/Lock/ObserverStore.swift b/Lock/ObserverStore.swift index f992aac95..67873e14c 100644 --- a/Lock/ObserverStore.swift +++ b/Lock/ObserverStore.swift @@ -32,21 +32,22 @@ struct ObserverStore: Dispatcher { weak var controller: UIViewController? - func dispatch(result: Result) { + func dispatch(result: Result, dismissLock: Bool) { let closure: () -> () switch result { case .auth(let credentials): - closure = dismiss(from: controller?.presentingViewController, completion: { self.onAuth(credentials) }) + closure = { self.onAuth(credentials) } case .error(let error): closure = { self.onFailure(error) } case .cancel: - closure = dismiss(from: controller?.presentingViewController, completion: { self.onCancel() }) - case .signUp(let email, let attributes, let dismissLock): - closure = dismissLock ? dismiss(from: controller?.presentingViewController, completion: { self.onSignUp(email, attributes) }) : { self.onSignUp(email, attributes) } - case .forgotPassword(let email, let dismissLock): - closure = dismissLock ? dismiss(from: controller?.presentingViewController, completion: { self.onForgetPassword(email) }) : { self.onForgetPassword(email) } + closure = { self.onCancel() } + case .signUp(let email, let attributes): + closure = { self.onSignUp(email, attributes) } + case .forgotPassword(let email): + closure = { self.onForgetPassword(email) } } - Queue.main.async(closure) + + Queue.main.async(dismissLock ? dismiss(from: controller?.presentingViewController, completion: closure) : closure) } private func dismiss(from controller: UIViewController?, completion: @escaping () -> ()) -> () -> () { @@ -59,10 +60,10 @@ enum Result { case auth(Credentials) case error(Error) case cancel - case signUp(String, [String: Any], Bool) - case forgotPassword(String, Bool) + case signUp(String, [String: Any]) + case forgotPassword(String) } protocol Dispatcher { - func dispatch(result: Result) + func dispatch(result: Result, dismissLock: Bool) } diff --git a/Lock/OptionBuildable.swift b/Lock/OptionBuildable.swift index 00e53799c..34fd7e467 100644 --- a/Lock/OptionBuildable.swift +++ b/Lock/OptionBuildable.swift @@ -54,6 +54,9 @@ public protocol OptionBuildable: Options { /// What database modes are allowed and must be at least one. By default all modes are allowed. var allow: DatabaseMode { get set } + /// What database modes can be automatically closed upon success. By default all modes are auto closable. + var autoClose: DatabaseMode { get set } + /// Initial screen displayed by Lock when a database connection is available. By default is Login var initialScreen: DatabaseScreen { get set } @@ -87,6 +90,7 @@ internal extension OptionBuildable { func validate() -> UnrecoverableError? { guard !self.allow.isEmpty else { return UnrecoverableError.invalidOptions(cause: "Must allow at least one database mode") } guard !self.usernameStyle.isEmpty else { return UnrecoverableError.invalidOptions(cause: "Must specify at least one username style") } + guard self.allow.contains(.Login) || self.closable || self.autoClose.contains(self.allow) else { return UnrecoverableError.invalidOptions(cause: "Must enable autoclose for database mode or enable closable") } return nil } } diff --git a/Lock/Options.swift b/Lock/Options.swift index 39a821ac2..d39e74e15 100644 --- a/Lock/Options.swift +++ b/Lock/Options.swift @@ -35,6 +35,7 @@ public protocol Options { var scope: String { get } var parameters: [String: Any] { get } var allow: DatabaseMode { get } + var autoClose: DatabaseMode { get } var initialScreen: DatabaseScreen { get } var usernameStyle: DatabaseIdentifierStyle { get } var customSignupFields: [CustomTextField] { get } diff --git a/LockTests/Interactors/DatabaseInteractorSpec.swift b/LockTests/Interactors/DatabaseInteractorSpec.swift index 85be73334..e82b5cdd2 100644 --- a/LockTests/Interactors/DatabaseInteractorSpec.swift +++ b/LockTests/Interactors/DatabaseInteractorSpec.swift @@ -559,7 +559,48 @@ class DatabaseInteractorSpec: QuickSpec { } } } - + + // TODO: Check why it fails only in travis + pending("login dispatch") { + + var options: LockOptions! + var auth: Credentials? + var controller: MockLockController! + var presenter: MockController! + var dispatcher: ObserverStore! + var db: DatabaseConnection! + + beforeEach { + options = LockOptions() + dispatcher = ObserverStore() + presenter = MockController() + controller = MockLockController(lock: Lock()) + presenter.presented = controller + controller.presenting = presenter + dispatcher.controller = controller + + auth = nil + options = LockOptions() + + dispatcher.onAuth = { credentials in + auth = credentials + } + db = DatabaseConnection(name: connection, requiresUsername: true, usernameValidator: UsernameValidator(withLength: 1...15, characterSet: UsernameValidator.auth0), passwordValidator: PasswordPolicyValidator(policy: .good)) + database = DatabaseInteractor(connection: db, authentication: authentication, user: user, options: options, dispatcher: dispatcher) + stub(condition: databaseLogin(identifier: email, password: password, connection: connection)) { _ in return Auth0Stubs.authentication() } + } + + + it("should dispatch on auth event and dismiss lock") { + try! database.update(.email, value: email) + try! database.update(.password(enforcePolicy: false), value: password) + database.login { _ in } + expect(auth?.accessToken).toEventually(equal("token")) + expect(presenter.presented).toEventually(beNil(), timeout: 2) + } + + } + } describe("login OIDC Conformant") { @@ -795,7 +836,7 @@ class DatabaseInteractorSpec: QuickSpec { } } - context("Auto log in after sign up") { + context("auto log in after sign up") { var options = LockOptions() @@ -1017,83 +1058,106 @@ class DatabaseInteractorSpec: QuickSpec { } } } - } - context("auto login disabled") { + // TODO: Check why it fails only in travis + pending("auto login disabled") { - var options: LockOptions! - var newUserEmail: String? - var controller: MockLockController! - var presenter: MockController! - var dispatcher: ObserverStore! - var db: DatabaseConnection! + var options: LockOptions! + var newUserEmail: String? + var controller: MockLockController! + var presenter: MockController! + var dispatcher: ObserverStore! + var db: DatabaseConnection! - beforeEach { - options = LockOptions() - dispatcher = ObserverStore() - presenter = MockController() - controller = MockLockController(lock: Lock()) - presenter.presented = controller - controller.presenting = presenter - dispatcher.controller = controller - - newUserEmail = nil - options = LockOptions() - options.loginAfterSignup = false - - dispatcher.onSignUp = { email, _ in - newUserEmail = email + beforeEach { + options = LockOptions() + dispatcher = ObserverStore() + presenter = MockController() + controller = MockLockController(lock: Lock()) + presenter.presented = controller + controller.presenting = presenter + dispatcher.controller = controller + + newUserEmail = nil + options = LockOptions() + options.loginAfterSignup = false + + dispatcher.onSignUp = { email, _ in + newUserEmail = email + } + db = DatabaseConnection(name: connection, requiresUsername: true, usernameValidator: UsernameValidator(withLength: 1...15, characterSet: UsernameValidator.auth0), passwordValidator: PasswordPolicyValidator(policy: .good)) + database = DatabaseInteractor(connection: db, authentication: authentication, user: user, options: options, dispatcher: dispatcher) + + stub(condition: databaseSignUp(email: email, username: username, password: password, connection: connection)) { _ in return Auth0Stubs.createdUser(email) } } - db = DatabaseConnection(name: connection, requiresUsername: true, usernameValidator: UsernameValidator(withLength: 1...15, characterSet: UsernameValidator.auth0), passwordValidator: PasswordPolicyValidator(policy: .good)) - database = DatabaseInteractor(connection: db, authentication: authentication, user: user, options: options, dispatcher: dispatcher) - stub(condition: databaseLogin(identifier: email, password: password, connection: connection)) { _ in return Auth0Stubs.failure() } - stub(condition: databaseSignUp(email: email, username: username, password: password, connection: connection)) { _ in return Auth0Stubs.createdUser(email) } - } - it("should only signup user") { - try! database.update(.email, value: email) - try! database.update(.username, value: username) - try! database.update(.password(enforcePolicy: false), value: password) - waitUntil(timeout: 2) { done in - database.create { create, login in - expect(create).to(beNil()) - expect(login).to(beNil()) - done() + it("should dispatch signup event") { + try! database.update(.email, value: email) + try! database.update(.username, value: username) + try! database.update(.password(enforcePolicy: false), value: password) + database.create { _ in } + expect(newUserEmail).toEventually(equal(email)) + expect(presenter.presented).toEventuallyNot(beNil(), timeout: 2) + } + + context("auto close disabled") { + + it("should dispatch signup event and stay on screen") { + options.autoClose = [.Signup] + database = DatabaseInteractor(connection: db, authentication: authentication, user: user, options: options, dispatcher: dispatcher) + try! database.update(.email, value: email) + try! database.update(.username, value: username) + try! database.update(.password(enforcePolicy: false), value: password) + database.create { _ in } + expect(newUserEmail).toEventually(equal(email)) + expect(presenter.presented).toEventuallyNot(beNil(), timeout: 2) + } + + } + + context("no login screen") { + + it("should dispatch signup event and dimiss Lock") { + options.allow = .Signup + database = DatabaseInteractor(connection: db, authentication: authentication, user: user, options: options, dispatcher: dispatcher) + try! database.update(.email, value: email) + try! database.update(.username, value: username) + try! database.update(.password(enforcePolicy: false), value: password) + database.create { _ in } + expect(newUserEmail).toEventually(equal(email)) + expect(presenter.presented).toEventually(beNil(), timeout: 2) + } + + it("should dispatch signup event and dimiss Lock") { + options.allow = [.Signup, .ResetPassword] + database = DatabaseInteractor(connection: db, authentication: authentication, user: user, options: options, dispatcher: dispatcher) + try! database.update(.email, value: email) + try! database.update(.username, value: username) + try! database.update(.password(enforcePolicy: false), value: password) + database.create { _ in } + expect(newUserEmail).toEventually(equal(email)) + expect(presenter.presented).toEventually(beNil(), timeout: 2) + } + + context("auto close disabled") { + + it("should dispatch signup event and stay on screen") { + options.allow = .Signup + options.autoClose = [] + database = DatabaseInteractor(connection: db, authentication: authentication, user: user, options: options, dispatcher: dispatcher) + try! database.update(.email, value: email) + try! database.update(.username, value: username) + try! database.update(.password(enforcePolicy: false), value: password) + database.create { _ in } + expect(newUserEmail).toEventually(equal(email)) + expect(presenter.presented).toEventuallyNot(beNil(), timeout: 2) + } + } + } + } - - it("should dispatch signup event") { - try! database.update(.email, value: email) - try! database.update(.username, value: username) - try! database.update(.password(enforcePolicy: false), value: password) - database.create { _ in } - expect(newUserEmail).toEventually(equal(email)) - expect(presenter.presented).toEventuallyNot(beNil(), timeout: 2) - } - - it("should dispatch signup event and dimiss Lock") { - options.allow = .Signup - database = DatabaseInteractor(connection: db, authentication: authentication, user: user, options: options, dispatcher: dispatcher) - try! database.update(.email, value: email) - try! database.update(.username, value: username) - try! database.update(.password(enforcePolicy: false), value: password) - database.create { _ in } - expect(newUserEmail).toEventually(equal(email)) - expect(presenter.presented).toEventually(beNil(), timeout: 2) - } - - it("should dispatch signup event and dimiss Lock") { - options.allow = [.Signup, .ResetPassword] - database = DatabaseInteractor(connection: db, authentication: authentication, user: user, options: options, dispatcher: dispatcher) - try! database.update(.email, value: email) - try! database.update(.username, value: username) - try! database.update(.password(enforcePolicy: false), value: password) - database.create { _ in } - expect(newUserEmail).toEventually(equal(email)) - expect(presenter.presented).toEventually(beNil(), timeout: 2) - } - } describe("signup OIDC Conformnant") { @@ -1287,7 +1351,7 @@ class DatabaseInteractorSpec: QuickSpec { } } } - + it("should send parameters on login") { let state = UUID().uuidString var options = LockOptions() @@ -1306,7 +1370,7 @@ class DatabaseInteractorSpec: QuickSpec { } } } - + } } diff --git a/LockTests/Interactors/DatabasePasswordInteractorSpec.swift b/LockTests/Interactors/DatabasePasswordInteractorSpec.swift index 8ce2b7474..bce9d8998 100644 --- a/LockTests/Interactors/DatabasePasswordInteractorSpec.swift +++ b/LockTests/Interactors/DatabasePasswordInteractorSpec.swift @@ -147,7 +147,8 @@ class DatabasePasswordInteractorSpec: QuickSpec { } } - context("on success") { + // TODO: Check why it fails only in travis + pending("controller displayed") { var userEmail: String? var controller: MockLockController! @@ -178,24 +179,52 @@ class DatabasePasswordInteractorSpec: QuickSpec { expect(presenter.presented).toEventuallyNot(beNil(), timeout: 4) } - it("should dispatch forgotPassword event and dismiss lock") { - options.allow = .ResetPassword - forgot = DatabasePasswordInteractor(connections: connections, authentication: authentication, user: user, options: options, dispatcher: dispatcher) - - try! forgot.updateEmail(email) - forgot.requestEmail { _ in } - expect(userEmail).toEventually(equal(email)) - expect(presenter.presented).toEventually(beNil(), timeout: 4) - } + context("no login screen") { - it("should dispatch forgotPassword event and dismiss lock") { - options.allow = [.ResetPassword, .Signup] - forgot = DatabasePasswordInteractor(connections: connections, authentication: authentication, user: user, options: options, dispatcher: dispatcher) + it("should dispatch forgotPassword event and dismiss lock") { + options.allow = .ResetPassword + forgot = DatabasePasswordInteractor(connections: connections, authentication: authentication, user: user, options: options, dispatcher: dispatcher) + + try! forgot.updateEmail(email) + forgot.requestEmail { _ in } + expect(userEmail).toEventually(equal(email)) + expect(presenter.presented).toEventually(beNil(), timeout: 2) + } + + it("should dispatch forgotPassword event and dismiss lock") { + options.allow = [.ResetPassword, .Signup] + forgot = DatabasePasswordInteractor(connections: connections, authentication: authentication, user: user, options: options, dispatcher: dispatcher) + + try! forgot.updateEmail(email) + forgot.requestEmail { _ in } + expect(userEmail).toEventually(equal(email)) + expect(presenter.presented).toEventually(beNil(), timeout: 2) + } + + context("disable auto close") { + + it("should dispatch forgotPassword and stay on screen") { + options.autoClose = [] + forgot = DatabasePasswordInteractor(connections: connections, authentication: authentication, user: user, options: options, dispatcher: dispatcher) + + try! forgot.updateEmail(email) + forgot.requestEmail { _ in } + expect(userEmail).toEventually(equal(email)) + expect(presenter.presented).toEventuallyNot(beNil(), timeout: 2) + } + + it("should dispatch forgotPassword event and stay on screen") { + options.allow = [.ResetPassword] + options.autoClose = [] + forgot = DatabasePasswordInteractor(connections: connections, authentication: authentication, user: user, options: options, dispatcher: dispatcher) + + try! forgot.updateEmail(email) + forgot.requestEmail { _ in } + expect(userEmail).toEventually(equal(email)) + expect(presenter.presented).toEventuallyNot(beNil(), timeout: 2) + } + } - try! forgot.updateEmail(email) - forgot.requestEmail { _ in } - expect(userEmail).toEventually(equal(email)) - expect(presenter.presented).toEventually(beNil(), timeout: 4) } } diff --git a/LockTests/Models/ObserverStoreSpec.swift b/LockTests/Models/ObserverStoreSpec.swift index 6c9703d53..d14e9599e 100644 --- a/LockTests/Models/ObserverStoreSpec.swift +++ b/LockTests/Models/ObserverStoreSpec.swift @@ -54,35 +54,35 @@ class ObserverStoreSpec: QuickSpec { } it("should dispatch errors") { - dispatcher.dispatch(result: .error(UnrecoverableError.invalidClientOrDomain)) + dispatcher.dispatch(result: .error(UnrecoverableError.invalidClientOrDomain), dismissLock: false) expect(error).toEventuallyNot(beNil()) } it("should dispatch credentials") { let value = mockCredentials() - dispatcher.dispatch(result: .auth(value)) + dispatcher.dispatch(result: .auth(value), dismissLock: false) expect(credentials).toEventually(equal(value)) } it("should dispatch when lock is dismissed") { - dispatcher.dispatch(result: .cancel) + dispatcher.dispatch(result: .cancel, dismissLock: false) expect(closed).toEventually(beTrue()) } it("should disptach when user signs up") { - dispatcher.dispatch(result: .signUp(email, ["username": username], false)) + dispatcher.dispatch(result: .signUp(email, ["username": username]), dismissLock: false) expect(newEmail).toEventually(equal(email)) expect(newAttributes?["username"] as? String).toEventually(equal(username)) } it("should disptach when user requests password") { - dispatcher.dispatch(result: .forgotPassword(email, false)) + dispatcher.dispatch(result: .forgotPassword(email), dismissLock: false) expect(newEmail).toEventually(equal(email)) } + // TODO: Check why it fails only in travis pending("controller displayed") { - // TODO: Check why it fails only in travis var controller: MockLockController! var presenter: MockController! @@ -96,23 +96,23 @@ class ObserverStoreSpec: QuickSpec { it("should dismiss onAuth") { let value = mockCredentials() - dispatcher.dispatch(result: .auth(value)) + dispatcher.dispatch(result: .auth(value), dismissLock: true) expect(presenter.presented).toEventually(beNil(), timeout: 2) } it("should dismiss onCancel") { - dispatcher.dispatch(result: .cancel) + dispatcher.dispatch(result: .cancel, dismissLock: true) expect(presenter.presented).toEventually(beNil(), timeout: 2) } it("should dismiss onSignUp") { - dispatcher.dispatch(result: .signUp(email, ["username": username], true)) + dispatcher.dispatch(result: .signUp(email, ["username": username]), dismissLock: true) expect(newEmail).toEventually(equal(email)) expect(presenter.presented).toEventually(beNil(), timeout: 2) } it("should dismiss forgotPassword") { - dispatcher.dispatch(result: .forgotPassword(email, true)) + dispatcher.dispatch(result: .forgotPassword(email), dismissLock: true) expect(presenter.presented).toEventually(beNil(), timeout: 2) } } diff --git a/LockTests/OptionsSpec.swift b/LockTests/OptionsSpec.swift index 94caec5ab..703e88770 100644 --- a/LockTests/OptionsSpec.swift +++ b/LockTests/OptionsSpec.swift @@ -88,6 +88,10 @@ class OptionsSpec: QuickSpec { it("should have no audience") { expect(options.audience).to(beNil()) } + + it("should have all database modes auto close enabled") { + expect(options.autoClose) == [.Login, .Signup, .ResetPassword] + } } describe("validation") { @@ -124,6 +128,30 @@ class OptionsSpec: QuickSpec { expect(options.validate()).toNot(beNil()) } + context("auto close") { + + it("should fail when autoclose is empty and no .Login allowed") { + options.autoClose = [] + options.allow = [.Signup, .ResetPassword] + expect(options.validate()).toNot(beNil()) + } + + it("should pass when autoclose has single screen and matching allowed screen") { + options.autoClose = [.ResetPassword] + options.allow = [.ResetPassword] + expect(options.validate()).to(beNil()) + } + + it("should pass when autoclose empty, no login, but closeable set") { + options.closable = true + options.autoClose = [] + options.allow = [.Signup, .ResetPassword] + expect(options.validate()).to(beNil()) + } + + } + + } describe("builder") { diff --git a/LockTests/Presenters/DatabaseForgotPasswordPresenterSpec.swift b/LockTests/Presenters/DatabaseForgotPasswordPresenterSpec.swift index a60e8bf50..5ad00556a 100644 --- a/LockTests/Presenters/DatabaseForgotPasswordPresenterSpec.swift +++ b/LockTests/Presenters/DatabaseForgotPasswordPresenterSpec.swift @@ -194,6 +194,40 @@ class DatabaseForgotPasswordPresenterSpec: QuickSpec { expect(button.inProgress).toEventually(beFalse()) } + context("no login screen") { + + beforeEach { + options.allow = .ResetPassword + presenter = DatabaseForgotPasswordPresenter(interactor: interactor, connections: connections, navigator: navigator, options: options) + presenter.messagePresenter = messagePresenter + view = presenter.view as! DatabaseForgotPasswordView + } + + it("should not show global success message") { + interactor.onRequest = { + return nil + } + view.primaryButton?.onPress(view.primaryButton!) + expect(messagePresenter.message).toEventually(beNil()) + } + + it("should show global success message") { + options.allow = .ResetPassword + options.autoClose = [] + presenter = DatabaseForgotPasswordPresenter(interactor: interactor, connections: connections, navigator: navigator, options: options) + presenter.messagePresenter = messagePresenter + view = presenter.view as! DatabaseForgotPasswordView + + interactor.onRequest = { + return nil + } + view.primaryButton?.onPress(view.primaryButton!) + expect(messagePresenter.message).toEventuallyNot(beNil()) + } + + } + + } describe("navigation on success") { diff --git a/LockTests/Presenters/DatabasePresenterSpec.swift b/LockTests/Presenters/DatabasePresenterSpec.swift index fd5871903..fe19cd986 100644 --- a/LockTests/Presenters/DatabasePresenterSpec.swift +++ b/LockTests/Presenters/DatabasePresenterSpec.swift @@ -471,6 +471,14 @@ class DatabasePresenterSpec: QuickSpec { expect(messagePresenter.error).toEventually(beNil()) expect(messagePresenter.message).toEventually(beNil()) } + + it("should show no success message") { + interactor.onSignUp = { + return nil + } + view.primaryButton?.onPress(view.primaryButton!) + expect(messagePresenter.message).toEventually(beNil()) + } it("should show global error message") { interactor.onSignUp = { @@ -555,7 +563,26 @@ class DatabasePresenterSpec: QuickSpec { expect(button.title).toEventually(contain("Sign up")) } + context("no auto login or auto close") { + + beforeEach { + options.loginAfterSignup = false + options.allow = [.Signup] + options.autoClose = [] + presenter = DatabasePresenter(authenticator: interactor, creator: interactor, connection: DatabaseConnection(name: connection, requiresUsername: true), navigator: navigator, options: options) + presenter.messagePresenter = messagePresenter + view = presenter.view as! DatabaseOnlyView + } + it("should show no success message") { + interactor.onSignUp = { + return nil + } + view.primaryButton?.onPress(view.primaryButton!) + expect(messagePresenter.error).toEventually(beNil()) + expect(messagePresenter.message).toEventuallyNot(beNil()) + } + } } } From 0db93b531bc1a1bdd531614a71dfa74e26707799 Mon Sep 17 00:00:00 2001 From: Martin Walsh Date: Fri, 13 Jan 2017 19:57:58 +0000 Subject: [PATCH 3/5] Dispatch Timeout Travis Test --- LockTests/Interactors/DatabaseInteractorSpec.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/LockTests/Interactors/DatabaseInteractorSpec.swift b/LockTests/Interactors/DatabaseInteractorSpec.swift index e82b5cdd2..109d527b1 100644 --- a/LockTests/Interactors/DatabaseInteractorSpec.swift +++ b/LockTests/Interactors/DatabaseInteractorSpec.swift @@ -561,7 +561,7 @@ class DatabaseInteractorSpec: QuickSpec { } // TODO: Check why it fails only in travis - pending("login dispatch") { + describe("login dispatch") { var options: LockOptions! var auth: Credentials? @@ -596,7 +596,7 @@ class DatabaseInteractorSpec: QuickSpec { try! database.update(.password(enforcePolicy: false), value: password) database.login { _ in } expect(auth?.accessToken).toEventually(equal("token")) - expect(presenter.presented).toEventually(beNil(), timeout: 2) + expect(presenter.presented).toEventually(beNil(), timeout: 10) } } From c0706e83b00102cbe83581c9ef7dc4240e96a751 Mon Sep 17 00:00:00 2001 From: Martin Walsh Date: Mon, 16 Jan 2017 11:08:30 +0000 Subject: [PATCH 4/5] Refactored ObserverStore dismissLock management Updated Tests --- Lock.xcodeproj/project.pbxproj | 2 +- Lock/Auth0OAuth2Interactor.swift | 2 +- Lock/DatabaseInteractor.swift | 4 +-- Lock/DatabasePasswordInteractor.swift | 2 +- Lock/EnterpriseActiveAuthInteractor.swift | 2 +- Lock/Lock.swift | 3 +- Lock/LockViewController.swift | 2 +- Lock/MultifactorInteractor.swift | 2 +- Lock/ObserverStore.swift | 19 ++++++++++--- .../Interactors/DatabaseInteractorSpec.swift | 17 ++++++++--- .../DatabasePasswordInteractorSpec.swift | 8 ++++-- LockTests/Models/ObserverStoreSpec.swift | 28 +++++++++---------- 12 files changed, 58 insertions(+), 33 deletions(-) diff --git a/Lock.xcodeproj/project.pbxproj b/Lock.xcodeproj/project.pbxproj index 172b4bdce..5dd74572d 100644 --- a/Lock.xcodeproj/project.pbxproj +++ b/Lock.xcodeproj/project.pbxproj @@ -225,7 +225,7 @@ 5BCDE1341DDDF12100AA2A6C /* EnterpriseActiveAuthPresenterSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnterpriseActiveAuthPresenterSpec.swift; sourceTree = ""; }; 5BCED4C51DD1FCF200E2CE8A /* EnterpriseDomainPresenterSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnterpriseDomainPresenterSpec.swift; sourceTree = ""; }; 5F0FCF8F1E20117E00E3D53B /* ObserverStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObserverStore.swift; sourceTree = ""; }; - 5F0FCF911E201CF300E3D53B /* ObserverStoreSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObserverStoreSpec.swift; sourceTree = ""; }; + 5F0FCF911E201CF300E3D53B /* ObserverStoreSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = ObserverStoreSpec.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 5F1456591D5130E80085DF9C /* Colors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Colors.swift; path = Lock/Colors.swift; sourceTree = SOURCE_ROOT; }; 5F1C498D1D8360AA005B74FC /* Style.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Style.swift; path = Lock/Style.swift; sourceTree = SOURCE_ROOT; }; 5F1C498F1D8360BF005B74FC /* ConnectionLoadingPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ConnectionLoadingPresenter.swift; path = Lock/ConnectionLoadingPresenter.swift; sourceTree = SOURCE_ROOT; }; diff --git a/Lock/Auth0OAuth2Interactor.swift b/Lock/Auth0OAuth2Interactor.swift index 2b938d7ab..385eb2e3d 100644 --- a/Lock/Auth0OAuth2Interactor.swift +++ b/Lock/Auth0OAuth2Interactor.swift @@ -45,7 +45,7 @@ struct Auth0OAuth2Interactor: OAuth2Authenticatable { .start { result in switch result { case .success(let credentials): - self.dispatcher.dispatch(result: .auth(credentials), dismissLock: true) + self.dispatcher.dispatch(result: .auth(credentials)) callback(nil) case .failure(WebAuthError.userCancelled): callback(.cancelled) diff --git a/Lock/DatabaseInteractor.swift b/Lock/DatabaseInteractor.swift index 08e5dd5bd..f2f72a4b4 100644 --- a/Lock/DatabaseInteractor.swift +++ b/Lock/DatabaseInteractor.swift @@ -135,7 +135,7 @@ struct DatabaseInteractor: DatabaseAuthenticatable, DatabaseUserCreator, Loggabl "verified": user.verified ] extra["username"] = user.username - self.dispatcher.dispatch(result: .signUp(user.email, extra), dismissLock: !self.options.allow.contains(.Login) && self.options.autoClose.contains(.Signup)) + self.dispatcher.dispatch(result: .signUp(user.email, extra)) callback(nil, nil) } case .failure(let cause as AuthenticationError) where cause.isPasswordNotStrongEnough: @@ -204,7 +204,7 @@ struct DatabaseInteractor: DatabaseAuthenticatable, DatabaseUserCreator, Loggabl case .success(let credentials): self.logger.info("Authenticated user <\(self.identifier)>") callback(nil) - self.dispatcher.dispatch(result: .auth(credentials), dismissLock: true) + self.dispatcher.dispatch(result: .auth(credentials)) } } } diff --git a/Lock/DatabasePasswordInteractor.swift b/Lock/DatabasePasswordInteractor.swift index 117f64a09..6ea98d3b3 100644 --- a/Lock/DatabasePasswordInteractor.swift +++ b/Lock/DatabasePasswordInteractor.swift @@ -59,7 +59,7 @@ struct DatabasePasswordInteractor: PasswordRecoverable { .resetPassword(email: email, connection: connection) .start { guard case .success = $0 else { return callback(.emailNotSent) } - self.dispatcher.dispatch(result: .forgotPassword(email), dismissLock: !self.options.allow.contains(.Login) && self.options.autoClose.contains(.ResetPassword)) + self.dispatcher.dispatch(result: .forgotPassword(email)) callback(nil) } } diff --git a/Lock/EnterpriseActiveAuthInteractor.swift b/Lock/EnterpriseActiveAuthInteractor.swift index c448d9101..4189c48c4 100644 --- a/Lock/EnterpriseActiveAuthInteractor.swift +++ b/Lock/EnterpriseActiveAuthInteractor.swift @@ -151,7 +151,7 @@ struct EnterpriseActiveAuthInteractor: DatabaseAuthenticatable, Loggable { case .success(let credentials): self.logger.info("Authenticated user <\(self.identifier)>") callback(nil) - self.dispatcher.dispatch(result: .auth(credentials), dismissLock: true) + self.dispatcher.dispatch(result: .auth(credentials)) } } diff --git a/Lock/Lock.swift b/Lock/Lock.swift index 7f0412908..271bc82ea 100644 --- a/Lock/Lock.swift +++ b/Lock/Lock.swift @@ -37,7 +37,7 @@ public class Lock: NSObject { var optionsBuilder: OptionBuildable = LockOptions() var options: Options { return self.optionsBuilder } - var observerStore = ObserverStore() + var observerStore = ObserverStore(options: LockOptions()) var style: Style = Style() @@ -147,6 +147,7 @@ public class Lock: NSObject { var builder: OptionBuildable = self.optionsBuilder closure(&builder) self.optionsBuilder = builder + self.observerStore.options = self.options _ = self.authentication.logging(enabled: self.options.logHttpRequest) _ = self.webAuth.logging(enabled: self.options.logHttpRequest) return self diff --git a/Lock/LockViewController.swift b/Lock/LockViewController.swift index 8bffc3dfb..f31e220b8 100644 --- a/Lock/LockViewController.swift +++ b/Lock/LockViewController.swift @@ -73,7 +73,7 @@ public class LockViewController: UIViewController { header.showClose = self.router.lock.options.closable header.onClosePressed = { [weak self] in - self?.router.lock.observerStore.dispatch(result: .cancel, dismissLock: true) + self?.router.lock.observerStore.dispatch(result: .cancel) } header.apply(style: style) diff --git a/Lock/MultifactorInteractor.swift b/Lock/MultifactorInteractor.swift index 452084c35..e0902240a 100644 --- a/Lock/MultifactorInteractor.swift +++ b/Lock/MultifactorInteractor.swift @@ -86,7 +86,7 @@ struct MultifactorInteractor: MultifactorAuthenticatable { callback(.couldNotLogin) case .success(let credentials): callback(nil) - self.dispatcher.dispatch(result: .auth(credentials), dismissLock: true) + self.dispatcher.dispatch(result: .auth(credentials)) } } } diff --git a/Lock/ObserverStore.swift b/Lock/ObserverStore.swift index 67873e14c..82d40d632 100644 --- a/Lock/ObserverStore.swift +++ b/Lock/ObserverStore.swift @@ -28,23 +28,34 @@ struct ObserverStore: Dispatcher { var onFailure: (Error) -> () = { _ in } var onCancel: () -> () = { } var onSignUp: (String, [String: Any]) -> () = { _ in } - var onForgetPassword: (String) -> () = { _ in } + var onForgotPassword: (String) -> () = { _ in } + var options: Options weak var controller: UIViewController? - func dispatch(result: Result, dismissLock: Bool) { + init(options: Options = LockOptions()) { + self.options = options + } + + func dispatch(result: Result) { + let dismissLock: Bool let closure: () -> () switch result { case .auth(let credentials): + dismissLock = true closure = { self.onAuth(credentials) } case .error(let error): + dismissLock = false closure = { self.onFailure(error) } case .cancel: + dismissLock = true closure = { self.onCancel() } case .signUp(let email, let attributes): + dismissLock = !self.options.allow.contains(.Login) && self.options.autoClose.contains(.Signup) closure = { self.onSignUp(email, attributes) } case .forgotPassword(let email): - closure = { self.onForgetPassword(email) } + dismissLock = !self.options.allow.contains(.Login) && self.options.autoClose.contains(.ResetPassword) + closure = { self.onForgotPassword(email) } } Queue.main.async(dismissLock ? dismiss(from: controller?.presentingViewController, completion: closure) : closure) @@ -65,5 +76,5 @@ enum Result { } protocol Dispatcher { - func dispatch(result: Result, dismissLock: Bool) + func dispatch(result: Result) } diff --git a/LockTests/Interactors/DatabaseInteractorSpec.swift b/LockTests/Interactors/DatabaseInteractorSpec.swift index 109d527b1..f44bd1612 100644 --- a/LockTests/Interactors/DatabaseInteractorSpec.swift +++ b/LockTests/Interactors/DatabaseInteractorSpec.swift @@ -38,13 +38,16 @@ class DatabaseInteractorSpec: QuickSpec { describe("init") { + let options = LockOptions() + let dispatcher = ObserverStore() + it("should build with authentication") { - let database = DatabaseInteractor(connection: DatabaseConnection(name: "db", requiresUsername: true), authentication: authentication, user: User(), options: LockOptions(), dispatcher: ObserverStore()) + let database = DatabaseInteractor(connection: DatabaseConnection(name: "db", requiresUsername: true), authentication: authentication, user: User(), options: options, dispatcher: dispatcher) expect(database).toNot(beNil()) } it("should have authentication object") { - let database = DatabaseInteractor(connection: DatabaseConnection(name: "db", requiresUsername: true), authentication: authentication, user: User(), options: LockOptions(), dispatcher: ObserverStore()) + let database = DatabaseInteractor(connection: DatabaseConnection(name: "db", requiresUsername: true), authentication: authentication, user: User(), options: options, dispatcher: dispatcher) expect(database.credentialAuth.authentication.clientId) == "CLIENT_ID" expect(database.credentialAuth.authentication.url.host) == "samples.auth0.com" expect(database.credentialAuth.oidc) == false @@ -56,12 +59,16 @@ class DatabaseInteractorSpec: QuickSpec { var database: DatabaseInteractor! var user: User! var options: OptionBuildable! + var dispatcher: ObserverStore! beforeEach { + options = LockOptions() + dispatcher = ObserverStore() + Auth0Stubs.failUnknown() user = User() let db = DatabaseConnection(name: connection, requiresUsername: true, usernameValidator: UsernameValidator(withLength: 1...15, characterSet: UsernameValidator.auth0), passwordValidator: PasswordPolicyValidator(policy: .good)) - database = DatabaseInteractor(connection: db, authentication: authentication, user: user, options: LockOptions(), dispatcher: ObserverStore()) + database = DatabaseInteractor(connection: db, authentication: authentication, user: user, options: options, dispatcher: dispatcher) } describe("updateAttribute") { @@ -561,7 +568,7 @@ class DatabaseInteractorSpec: QuickSpec { } // TODO: Check why it fails only in travis - describe("login dispatch") { + pending("login dispatch") { var options: LockOptions! var auth: Credentials? @@ -1119,6 +1126,7 @@ class DatabaseInteractorSpec: QuickSpec { it("should dispatch signup event and dimiss Lock") { options.allow = .Signup + dispatcher.options = options database = DatabaseInteractor(connection: db, authentication: authentication, user: user, options: options, dispatcher: dispatcher) try! database.update(.email, value: email) try! database.update(.username, value: username) @@ -1130,6 +1138,7 @@ class DatabaseInteractorSpec: QuickSpec { it("should dispatch signup event and dimiss Lock") { options.allow = [.Signup, .ResetPassword] + dispatcher.options = options database = DatabaseInteractor(connection: db, authentication: authentication, user: user, options: options, dispatcher: dispatcher) try! database.update(.email, value: email) try! database.update(.username, value: username) diff --git a/LockTests/Interactors/DatabasePasswordInteractorSpec.swift b/LockTests/Interactors/DatabasePasswordInteractorSpec.swift index bce9d8998..d040d5e17 100644 --- a/LockTests/Interactors/DatabasePasswordInteractorSpec.swift +++ b/LockTests/Interactors/DatabasePasswordInteractorSpec.swift @@ -156,6 +156,7 @@ class DatabasePasswordInteractorSpec: QuickSpec { var dispatcher: ObserverStore! beforeEach { + options = LockOptions() dispatcher = ObserverStore() presenter = MockController() controller = MockLockController(lock: Lock()) @@ -164,8 +165,7 @@ class DatabasePasswordInteractorSpec: QuickSpec { dispatcher.controller = controller userEmail = nil - options = LockOptions() - dispatcher.onForgetPassword = { email in + dispatcher.onForgotPassword = { email in userEmail = email } forgot = DatabasePasswordInteractor(connections: connections, authentication: authentication, user: user, options: options, dispatcher: dispatcher) @@ -183,6 +183,7 @@ class DatabasePasswordInteractorSpec: QuickSpec { it("should dispatch forgotPassword event and dismiss lock") { options.allow = .ResetPassword + dispatcher.options = options forgot = DatabasePasswordInteractor(connections: connections, authentication: authentication, user: user, options: options, dispatcher: dispatcher) try! forgot.updateEmail(email) @@ -193,6 +194,7 @@ class DatabasePasswordInteractorSpec: QuickSpec { it("should dispatch forgotPassword event and dismiss lock") { options.allow = [.ResetPassword, .Signup] + dispatcher.options = options forgot = DatabasePasswordInteractor(connections: connections, authentication: authentication, user: user, options: options, dispatcher: dispatcher) try! forgot.updateEmail(email) @@ -205,6 +207,7 @@ class DatabasePasswordInteractorSpec: QuickSpec { it("should dispatch forgotPassword and stay on screen") { options.autoClose = [] + dispatcher.options = options forgot = DatabasePasswordInteractor(connections: connections, authentication: authentication, user: user, options: options, dispatcher: dispatcher) try! forgot.updateEmail(email) @@ -216,6 +219,7 @@ class DatabasePasswordInteractorSpec: QuickSpec { it("should dispatch forgotPassword event and stay on screen") { options.allow = [.ResetPassword] options.autoClose = [] + dispatcher.options = options forgot = DatabasePasswordInteractor(connections: connections, authentication: authentication, user: user, options: options, dispatcher: dispatcher) try! forgot.updateEmail(email) diff --git a/LockTests/Models/ObserverStoreSpec.swift b/LockTests/Models/ObserverStoreSpec.swift index d14e9599e..99652c88d 100644 --- a/LockTests/Models/ObserverStoreSpec.swift +++ b/LockTests/Models/ObserverStoreSpec.swift @@ -49,34 +49,34 @@ class ObserverStoreSpec: QuickSpec { store.onAuth = { credentials = $0 } store.onCancel = { closed = true } store.onSignUp = { newEmail = $0; newAttributes = $1 } - store.onForgetPassword = { newEmail = $0 } + store.onForgotPassword = { newEmail = $0 } dispatcher = store } it("should dispatch errors") { - dispatcher.dispatch(result: .error(UnrecoverableError.invalidClientOrDomain), dismissLock: false) + dispatcher.dispatch(result: .error(UnrecoverableError.invalidClientOrDomain)) expect(error).toEventuallyNot(beNil()) } it("should dispatch credentials") { let value = mockCredentials() - dispatcher.dispatch(result: .auth(value), dismissLock: false) + dispatcher.dispatch(result: .auth(value)) expect(credentials).toEventually(equal(value)) } it("should dispatch when lock is dismissed") { - dispatcher.dispatch(result: .cancel, dismissLock: false) + dispatcher.dispatch(result: .cancel) expect(closed).toEventually(beTrue()) } it("should disptach when user signs up") { - dispatcher.dispatch(result: .signUp(email, ["username": username]), dismissLock: false) + dispatcher.dispatch(result: .signUp(email, ["username": username])) expect(newEmail).toEventually(equal(email)) expect(newAttributes?["username"] as? String).toEventually(equal(username)) } it("should disptach when user requests password") { - dispatcher.dispatch(result: .forgotPassword(email), dismissLock: false) + dispatcher.dispatch(result: .forgotPassword(email)) expect(newEmail).toEventually(equal(email)) } @@ -96,24 +96,24 @@ class ObserverStoreSpec: QuickSpec { it("should dismiss onAuth") { let value = mockCredentials() - dispatcher.dispatch(result: .auth(value), dismissLock: true) + dispatcher.dispatch(result: .auth(value)) expect(presenter.presented).toEventually(beNil(), timeout: 2) } it("should dismiss onCancel") { - dispatcher.dispatch(result: .cancel, dismissLock: true) + dispatcher.dispatch(result: .cancel) expect(presenter.presented).toEventually(beNil(), timeout: 2) } - it("should dismiss onSignUp") { - dispatcher.dispatch(result: .signUp(email, ["username": username]), dismissLock: true) + it("should not dismiss onSignUp") { + dispatcher.dispatch(result: .signUp(email, ["username": username])) expect(newEmail).toEventually(equal(email)) - expect(presenter.presented).toEventually(beNil(), timeout: 2) + expect(presenter.presented).toEventuallyNot(beNil(), timeout: 2) } - it("should dismiss forgotPassword") { - dispatcher.dispatch(result: .forgotPassword(email), dismissLock: true) - expect(presenter.presented).toEventually(beNil(), timeout: 2) + it("should not dismiss forgotPassword") { + dispatcher.dispatch(result: .forgotPassword(email)) + expect(presenter.presented).toEventuallyNot(beNil(), timeout: 2) } } } From 8c75bce83a1a7a12ca43152df9298d51b64f8622 Mon Sep 17 00:00:00 2001 From: Martin Walsh Date: Mon, 16 Jan 2017 21:51:12 +0000 Subject: [PATCH 5/5] Moved dismissLock logic into ObserverStore case Changed autoClose flag to Bool Updated Tests Removed Options from DatabasePasswordInteractor Login now follows autoClose rules Login success message added DatabaseInteractor tests Added Dispatcher related tests unified into ObserverStore tests --- Lock/DatabaseForgotPasswordPresenter.swift | 2 +- Lock/DatabasePasswordInteractor.swift | 4 +- Lock/DatabasePresenter.swift | 6 +- Lock/Lock.swift | 2 +- Lock/LockOptions.swift | 2 +- Lock/ObserverStore.swift | 34 +++-- Lock/OptionBuildable.swift | 6 +- Lock/Options.swift | 2 +- Lock/Router.swift | 2 +- .../Interactors/DatabaseInteractorSpec.swift | 144 ------------------ .../DatabasePasswordInteractorSpec.swift | 94 +----------- LockTests/Models/ObserverStoreSpec.swift | 50 +++++- LockTests/OptionsSpec.swift | 14 +- .../DatabaseForgotPasswordPresenterSpec.swift | 2 +- .../Presenters/DatabasePresenterSpec.swift | 27 +++- 15 files changed, 117 insertions(+), 274 deletions(-) diff --git a/Lock/DatabaseForgotPasswordPresenter.swift b/Lock/DatabaseForgotPasswordPresenter.swift index b76a3867d..bd85708eb 100644 --- a/Lock/DatabaseForgotPasswordPresenter.swift +++ b/Lock/DatabaseForgotPasswordPresenter.swift @@ -72,7 +72,7 @@ class DatabaseForgotPasswordPresenter: Presentable, Loggable { self.logger.error("Failed with error \(error)") } else { let message = "We've just sent you an email to reset your password".i18n(key: "com.auth0.lock.database.forgot.success.message", comment: "forgot password email sent") - if self.options.allow.contains(.Login) || !self.options.autoClose.contains(.ResetPassword) { + if self.options.allow.contains(.Login) || !self.options.autoClose { self.messagePresenter?.showSuccess(message) } guard self.options.allow.contains(.Login) else { return } diff --git a/Lock/DatabasePasswordInteractor.swift b/Lock/DatabasePasswordInteractor.swift index 6ea98d3b3..8c20d3e3c 100644 --- a/Lock/DatabasePasswordInteractor.swift +++ b/Lock/DatabasePasswordInteractor.swift @@ -32,15 +32,13 @@ struct DatabasePasswordInteractor: PasswordRecoverable { var validEmail: Bool { return self.user.validEmail } let authentication: Authentication - let options: Options let connections: Connections let emailValidator: InputValidator = EmailValidator() - init(connections: Connections, authentication: Authentication, user: DatabaseUser, options: Options, dispatcher: Dispatcher) { + init(connections: Connections, authentication: Authentication, user: DatabaseUser, dispatcher: Dispatcher) { self.authentication = authentication self.connections = connections self.user = user - self.options = options self.dispatcher = dispatcher } diff --git a/Lock/DatabasePresenter.swift b/Lock/DatabasePresenter.swift index 23dc22f52..01f7115f0 100644 --- a/Lock/DatabasePresenter.swift +++ b/Lock/DatabasePresenter.swift @@ -102,6 +102,10 @@ class DatabasePresenter: Presentable, Loggable { button.inProgress = false guard let error = error else { self.logger.debug("Logged in!") + let message = "You have logged in successfully.".i18n(key: "com.auth0.lock.database.login.success.message", comment: "User logged in") + if !self.options.autoClose { + self.messagePresenter?.showSuccess(message) + } return } if case DatabaseAuthenticatableError.multifactorRequired = error { @@ -162,7 +166,7 @@ class DatabasePresenter: Presentable, Loggable { if let databaseView = self.databaseView, self.options.allow.contains(.Login) { self.showLogin(inView: databaseView, identifier: self.creator.identifier) } - if self.options.allow.contains(.Login) || !self.options.autoClose.contains(.Signup) { + if self.options.allow.contains(.Login) || !self.options.autoClose { self.messagePresenter?.showSuccess(message) } } diff --git a/Lock/Lock.swift b/Lock/Lock.swift index 271bc82ea..f7ee3f1ff 100644 --- a/Lock/Lock.swift +++ b/Lock/Lock.swift @@ -37,7 +37,7 @@ public class Lock: NSObject { var optionsBuilder: OptionBuildable = LockOptions() var options: Options { return self.optionsBuilder } - var observerStore = ObserverStore(options: LockOptions()) + var observerStore = ObserverStore() var style: Style = Style() diff --git a/Lock/LockOptions.swift b/Lock/LockOptions.swift index 9feb5a3ad..04230e356 100644 --- a/Lock/LockOptions.swift +++ b/Lock/LockOptions.swift @@ -33,7 +33,7 @@ struct LockOptions: OptionBuildable { var scope: String = "openid" var parameters: [String : Any] = [:] var allow: DatabaseMode = [.Login, .Signup, .ResetPassword] - var autoClose: DatabaseMode = [.Login, .Signup, .ResetPassword] + var autoClose: Bool = true var initialScreen: DatabaseScreen = .login var usernameStyle: DatabaseIdentifierStyle = [.Username, .Email] var customSignupFields: [CustomTextField] = [] diff --git a/Lock/ObserverStore.swift b/Lock/ObserverStore.swift index 82d40d632..7ba0f862d 100644 --- a/Lock/ObserverStore.swift +++ b/Lock/ObserverStore.swift @@ -29,36 +29,38 @@ struct ObserverStore: Dispatcher { var onCancel: () -> () = { } var onSignUp: (String, [String: Any]) -> () = { _ in } var onForgotPassword: (String) -> () = { _ in } - var options: Options + var options: Options = LockOptions() weak var controller: UIViewController? - init(options: Options = LockOptions()) { - self.options = options - } - func dispatch(result: Result) { - let dismissLock: Bool let closure: () -> () switch result { case .auth(let credentials): - dismissLock = true - closure = { self.onAuth(credentials) } + if self.options.autoClose { + closure = dismiss(from: controller?.presentingViewController, completion: { self.onAuth(credentials) }) + } else { + closure = { self.onAuth(credentials) } + } case .error(let error): - dismissLock = false closure = { self.onFailure(error) } case .cancel: - dismissLock = true - closure = { self.onCancel() } + closure = dismiss(from: controller?.presentingViewController, completion: { self.onCancel() }) case .signUp(let email, let attributes): - dismissLock = !self.options.allow.contains(.Login) && self.options.autoClose.contains(.Signup) - closure = { self.onSignUp(email, attributes) } + if !self.options.allow.contains(.Login) && self.options.autoClose { + closure = dismiss(from: controller?.presentingViewController, completion: { self.onSignUp(email, attributes) }) + } else { + closure = { self.onSignUp(email, attributes) } + } case .forgotPassword(let email): - dismissLock = !self.options.allow.contains(.Login) && self.options.autoClose.contains(.ResetPassword) - closure = { self.onForgotPassword(email) } + if !self.options.allow.contains(.Login) && self.options.autoClose { + closure = dismiss(from: controller?.presentingViewController, completion: { self.onForgotPassword(email) }) + } else { + closure = { self.onForgotPassword(email) } + } } - Queue.main.async(dismissLock ? dismiss(from: controller?.presentingViewController, completion: closure) : closure) + Queue.main.async(closure) } private func dismiss(from controller: UIViewController?, completion: @escaping () -> ()) -> () -> () { diff --git a/Lock/OptionBuildable.swift b/Lock/OptionBuildable.swift index 34fd7e467..1a1fc90b0 100644 --- a/Lock/OptionBuildable.swift +++ b/Lock/OptionBuildable.swift @@ -54,8 +54,8 @@ public protocol OptionBuildable: Options { /// What database modes are allowed and must be at least one. By default all modes are allowed. var allow: DatabaseMode { get set } - /// What database modes can be automatically closed upon success. By default all modes are auto closable. - var autoClose: DatabaseMode { get set } + /// Should Lock close if only mode available. By default is true + var autoClose: Bool { get set } /// Initial screen displayed by Lock when a database connection is available. By default is Login var initialScreen: DatabaseScreen { get set } @@ -90,7 +90,7 @@ internal extension OptionBuildable { func validate() -> UnrecoverableError? { guard !self.allow.isEmpty else { return UnrecoverableError.invalidOptions(cause: "Must allow at least one database mode") } guard !self.usernameStyle.isEmpty else { return UnrecoverableError.invalidOptions(cause: "Must specify at least one username style") } - guard self.allow.contains(.Login) || self.closable || self.autoClose.contains(self.allow) else { return UnrecoverableError.invalidOptions(cause: "Must enable autoclose for database mode or enable closable") } + guard self.allow.contains(.Login) || self.closable || self.autoClose else { return UnrecoverableError.invalidOptions(cause: "Must enable autoclose or enable closable") } return nil } } diff --git a/Lock/Options.swift b/Lock/Options.swift index d39e74e15..a1f8b333f 100644 --- a/Lock/Options.swift +++ b/Lock/Options.swift @@ -35,7 +35,7 @@ public protocol Options { var scope: String { get } var parameters: [String: Any] { get } var allow: DatabaseMode { get } - var autoClose: DatabaseMode { get } + var autoClose: Bool { get } var initialScreen: DatabaseScreen { get } var usernameStyle: DatabaseIdentifierStyle { get } var customSignupFields: [CustomTextField] { get } diff --git a/Lock/Router.swift b/Lock/Router.swift index 310ff20f0..7779256a1 100644 --- a/Lock/Router.swift +++ b/Lock/Router.swift @@ -97,7 +97,7 @@ struct Router: Navigable { exit(withError: UnrecoverableError.clientWithNoConnections) return nil } - let interactor = DatabasePasswordInteractor(connections: connections, authentication: self.lock.authentication, user: self.user, options: self.lock.options, dispatcher: lock.observerStore) + let interactor = DatabasePasswordInteractor(connections: connections, authentication: self.lock.authentication, user: self.user, dispatcher: lock.observerStore) let presenter = DatabaseForgotPasswordPresenter(interactor: interactor, connections: connections, navigator: self, options: self.lock.options) presenter.customLogger = self.lock.logger return presenter diff --git a/LockTests/Interactors/DatabaseInteractorSpec.swift b/LockTests/Interactors/DatabaseInteractorSpec.swift index f44bd1612..c96314c17 100644 --- a/LockTests/Interactors/DatabaseInteractorSpec.swift +++ b/LockTests/Interactors/DatabaseInteractorSpec.swift @@ -566,48 +566,6 @@ class DatabaseInteractorSpec: QuickSpec { } } } - - // TODO: Check why it fails only in travis - pending("login dispatch") { - - var options: LockOptions! - var auth: Credentials? - var controller: MockLockController! - var presenter: MockController! - var dispatcher: ObserverStore! - var db: DatabaseConnection! - - beforeEach { - options = LockOptions() - dispatcher = ObserverStore() - presenter = MockController() - controller = MockLockController(lock: Lock()) - presenter.presented = controller - controller.presenting = presenter - dispatcher.controller = controller - - auth = nil - options = LockOptions() - - dispatcher.onAuth = { credentials in - auth = credentials - } - db = DatabaseConnection(name: connection, requiresUsername: true, usernameValidator: UsernameValidator(withLength: 1...15, characterSet: UsernameValidator.auth0), passwordValidator: PasswordPolicyValidator(policy: .good)) - database = DatabaseInteractor(connection: db, authentication: authentication, user: user, options: options, dispatcher: dispatcher) - stub(condition: databaseLogin(identifier: email, password: password, connection: connection)) { _ in return Auth0Stubs.authentication() } - } - - - it("should dispatch on auth event and dismiss lock") { - try! database.update(.email, value: email) - try! database.update(.password(enforcePolicy: false), value: password) - database.login { _ in } - expect(auth?.accessToken).toEventually(equal("token")) - expect(presenter.presented).toEventually(beNil(), timeout: 10) - } - - } - } describe("login OIDC Conformant") { @@ -1065,108 +1023,6 @@ class DatabaseInteractorSpec: QuickSpec { } } } - - // TODO: Check why it fails only in travis - pending("auto login disabled") { - - var options: LockOptions! - var newUserEmail: String? - var controller: MockLockController! - var presenter: MockController! - var dispatcher: ObserverStore! - var db: DatabaseConnection! - - beforeEach { - options = LockOptions() - dispatcher = ObserverStore() - presenter = MockController() - controller = MockLockController(lock: Lock()) - presenter.presented = controller - controller.presenting = presenter - dispatcher.controller = controller - - newUserEmail = nil - options = LockOptions() - options.loginAfterSignup = false - - dispatcher.onSignUp = { email, _ in - newUserEmail = email - } - db = DatabaseConnection(name: connection, requiresUsername: true, usernameValidator: UsernameValidator(withLength: 1...15, characterSet: UsernameValidator.auth0), passwordValidator: PasswordPolicyValidator(policy: .good)) - database = DatabaseInteractor(connection: db, authentication: authentication, user: user, options: options, dispatcher: dispatcher) - - stub(condition: databaseSignUp(email: email, username: username, password: password, connection: connection)) { _ in return Auth0Stubs.createdUser(email) } - } - - it("should dispatch signup event") { - try! database.update(.email, value: email) - try! database.update(.username, value: username) - try! database.update(.password(enforcePolicy: false), value: password) - database.create { _ in } - expect(newUserEmail).toEventually(equal(email)) - expect(presenter.presented).toEventuallyNot(beNil(), timeout: 2) - } - - context("auto close disabled") { - - it("should dispatch signup event and stay on screen") { - options.autoClose = [.Signup] - database = DatabaseInteractor(connection: db, authentication: authentication, user: user, options: options, dispatcher: dispatcher) - try! database.update(.email, value: email) - try! database.update(.username, value: username) - try! database.update(.password(enforcePolicy: false), value: password) - database.create { _ in } - expect(newUserEmail).toEventually(equal(email)) - expect(presenter.presented).toEventuallyNot(beNil(), timeout: 2) - } - - } - - context("no login screen") { - - it("should dispatch signup event and dimiss Lock") { - options.allow = .Signup - dispatcher.options = options - database = DatabaseInteractor(connection: db, authentication: authentication, user: user, options: options, dispatcher: dispatcher) - try! database.update(.email, value: email) - try! database.update(.username, value: username) - try! database.update(.password(enforcePolicy: false), value: password) - database.create { _ in } - expect(newUserEmail).toEventually(equal(email)) - expect(presenter.presented).toEventually(beNil(), timeout: 2) - } - - it("should dispatch signup event and dimiss Lock") { - options.allow = [.Signup, .ResetPassword] - dispatcher.options = options - database = DatabaseInteractor(connection: db, authentication: authentication, user: user, options: options, dispatcher: dispatcher) - try! database.update(.email, value: email) - try! database.update(.username, value: username) - try! database.update(.password(enforcePolicy: false), value: password) - database.create { _ in } - expect(newUserEmail).toEventually(equal(email)) - expect(presenter.presented).toEventually(beNil(), timeout: 2) - } - - context("auto close disabled") { - - it("should dispatch signup event and stay on screen") { - options.allow = .Signup - options.autoClose = [] - database = DatabaseInteractor(connection: db, authentication: authentication, user: user, options: options, dispatcher: dispatcher) - try! database.update(.email, value: email) - try! database.update(.username, value: username) - try! database.update(.password(enforcePolicy: false), value: password) - database.create { _ in } - expect(newUserEmail).toEventually(equal(email)) - expect(presenter.presented).toEventuallyNot(beNil(), timeout: 2) - } - - } - - } - - } } describe("signup OIDC Conformnant") { diff --git a/LockTests/Interactors/DatabasePasswordInteractorSpec.swift b/LockTests/Interactors/DatabasePasswordInteractorSpec.swift index d040d5e17..c8c31d1b5 100644 --- a/LockTests/Interactors/DatabasePasswordInteractorSpec.swift +++ b/LockTests/Interactors/DatabasePasswordInteractorSpec.swift @@ -39,7 +39,7 @@ class DatabasePasswordInteractorSpec: QuickSpec { } describe("init") { - let database = DatabasePasswordInteractor(connections: OfflineConnections(), authentication: authentication, user: User(), options: LockOptions(), dispatcher: ObserverStore()) + let database = DatabasePasswordInteractor(connections: OfflineConnections(), authentication: authentication, user: User(), dispatcher: ObserverStore()) it("should build with authentication") { expect(database).toNot(beNil()) @@ -54,16 +54,14 @@ class DatabasePasswordInteractorSpec: QuickSpec { var user: User! var connections: OfflineConnections! var forgot: DatabasePasswordInteractor! - var options: OptionBuildable! var dispatcher: Dispatcher! beforeEach { - options = LockOptions() dispatcher = ObserverStore() connections = OfflineConnections() connections.database(name: connection, requiresUsername: true) user = User() - forgot = DatabasePasswordInteractor(connections: connections, authentication: authentication, user: user, options: options, dispatcher: dispatcher) + forgot = DatabasePasswordInteractor(connections: connections, authentication: authentication, user: user, dispatcher: dispatcher) } describe("updateEmail") { @@ -106,7 +104,7 @@ class DatabasePasswordInteractorSpec: QuickSpec { describe("request email") { it("should fail if no db connection is found") { - forgot = DatabasePasswordInteractor(connections: OfflineConnections(), authentication: authentication, user: user, options: options, dispatcher: dispatcher) + forgot = DatabasePasswordInteractor(connections: OfflineConnections(), authentication: authentication, user: user, dispatcher: dispatcher) try! forgot.updateEmail(email) waitUntil(timeout: 2) { done in forgot.requestEmail { error in @@ -146,92 +144,6 @@ class DatabasePasswordInteractorSpec: QuickSpec { } } } - - // TODO: Check why it fails only in travis - pending("controller displayed") { - - var userEmail: String? - var controller: MockLockController! - var presenter: MockController! - var dispatcher: ObserverStore! - - beforeEach { - options = LockOptions() - dispatcher = ObserverStore() - presenter = MockController() - controller = MockLockController(lock: Lock()) - presenter.presented = controller - controller.presenting = presenter - dispatcher.controller = controller - - userEmail = nil - dispatcher.onForgotPassword = { email in - userEmail = email - } - forgot = DatabasePasswordInteractor(connections: connections, authentication: authentication, user: user, options: options, dispatcher: dispatcher) - stub(condition: databaseForgotPassword(email: email, connection: connection)) { _ in return Auth0Stubs.forgotEmailSent() } - } - - it("should dispatch forgotPassword event") { - try! forgot.updateEmail(email) - forgot.requestEmail { _ in } - expect(userEmail).toEventually(equal(email)) - expect(presenter.presented).toEventuallyNot(beNil(), timeout: 4) - } - - context("no login screen") { - - it("should dispatch forgotPassword event and dismiss lock") { - options.allow = .ResetPassword - dispatcher.options = options - forgot = DatabasePasswordInteractor(connections: connections, authentication: authentication, user: user, options: options, dispatcher: dispatcher) - - try! forgot.updateEmail(email) - forgot.requestEmail { _ in } - expect(userEmail).toEventually(equal(email)) - expect(presenter.presented).toEventually(beNil(), timeout: 2) - } - - it("should dispatch forgotPassword event and dismiss lock") { - options.allow = [.ResetPassword, .Signup] - dispatcher.options = options - forgot = DatabasePasswordInteractor(connections: connections, authentication: authentication, user: user, options: options, dispatcher: dispatcher) - - try! forgot.updateEmail(email) - forgot.requestEmail { _ in } - expect(userEmail).toEventually(equal(email)) - expect(presenter.presented).toEventually(beNil(), timeout: 2) - } - - context("disable auto close") { - - it("should dispatch forgotPassword and stay on screen") { - options.autoClose = [] - dispatcher.options = options - forgot = DatabasePasswordInteractor(connections: connections, authentication: authentication, user: user, options: options, dispatcher: dispatcher) - - try! forgot.updateEmail(email) - forgot.requestEmail { _ in } - expect(userEmail).toEventually(equal(email)) - expect(presenter.presented).toEventuallyNot(beNil(), timeout: 2) - } - - it("should dispatch forgotPassword event and stay on screen") { - options.allow = [.ResetPassword] - options.autoClose = [] - dispatcher.options = options - forgot = DatabasePasswordInteractor(connections: connections, authentication: authentication, user: user, options: options, dispatcher: dispatcher) - - try! forgot.updateEmail(email) - forgot.requestEmail { _ in } - expect(userEmail).toEventually(equal(email)) - expect(presenter.presented).toEventuallyNot(beNil(), timeout: 2) - } - } - - } - - } } } diff --git a/LockTests/Models/ObserverStoreSpec.swift b/LockTests/Models/ObserverStoreSpec.swift index 99652c88d..df440f932 100644 --- a/LockTests/Models/ObserverStoreSpec.swift +++ b/LockTests/Models/ObserverStoreSpec.swift @@ -100,21 +100,69 @@ class ObserverStoreSpec: QuickSpec { expect(presenter.presented).toEventually(beNil(), timeout: 2) } + it("should not dismiss onAuth when autoclose disabled") { + let value = mockCredentials() + var options = LockOptions() + options.autoClose = false + dispatcher.options = options + dispatcher.dispatch(result: .auth(value)) + expect(presenter.presented).toEventuallyNot(beNil(), timeout: 2) + } + it("should dismiss onCancel") { dispatcher.dispatch(result: .cancel) expect(presenter.presented).toEventually(beNil(), timeout: 2) } - + it("should not dismiss onSignUp") { dispatcher.dispatch(result: .signUp(email, ["username": username])) expect(newEmail).toEventually(equal(email)) expect(presenter.presented).toEventuallyNot(beNil(), timeout: 2) } + it("should dismiss onSignUp when single screen") { + var options = LockOptions() + options.allow = [.Signup] + dispatcher.options = options + dispatcher.dispatch(result: .signUp(email, ["username": username])) + expect(newEmail).toEventually(equal(email)) + expect(presenter.presented).toEventually(beNil(), timeout: 2) + } + + it("should not dismiss onSignUp when single screen but autoclose disabled") { + var options = LockOptions() + options.allow = [.Signup] + options.autoClose = false + dispatcher.options = options + dispatcher.dispatch(result: .signUp(email, ["username": username])) + expect(newEmail).toEventually(equal(email)) + expect(presenter.presented).toEventuallyNot(beNil(), timeout: 2) + } + it("should not dismiss forgotPassword") { dispatcher.dispatch(result: .forgotPassword(email)) expect(presenter.presented).toEventuallyNot(beNil(), timeout: 2) } + + it("should dismiss forgotPassword when single screen") { + var options = LockOptions() + options.allow = [.ResetPassword] + dispatcher.options = options + dispatcher.dispatch(result: .forgotPassword(email)) + expect(newEmail).toEventually(equal(email)) + expect(presenter.presented).toEventually(beNil(), timeout: 2) + } + + it("should not dismiss forgotPassword when single screen but autoclose disabled") { + var options = LockOptions() + options.allow = [.ResetPassword] + options.autoClose = false + dispatcher.options = options + dispatcher.dispatch(result: .forgotPassword(email)) + expect(newEmail).toEventually(equal(email)) + expect(presenter.presented).toEventuallyNot(beNil(), timeout: 2) + } + } } } diff --git a/LockTests/OptionsSpec.swift b/LockTests/OptionsSpec.swift index 703e88770..30d3f174a 100644 --- a/LockTests/OptionsSpec.swift +++ b/LockTests/OptionsSpec.swift @@ -89,8 +89,8 @@ class OptionsSpec: QuickSpec { expect(options.audience).to(beNil()) } - it("should have all database modes auto close enabled") { - expect(options.autoClose) == [.Login, .Signup, .ResetPassword] + it("should be auto closeable") { + expect(options.autoClose) == true } } @@ -131,20 +131,20 @@ class OptionsSpec: QuickSpec { context("auto close") { it("should fail when autoclose is empty and no .Login allowed") { - options.autoClose = [] + options.autoClose = false options.allow = [.Signup, .ResetPassword] expect(options.validate()).toNot(beNil()) } - it("should pass when autoclose has single screen and matching allowed screen") { - options.autoClose = [.ResetPassword] + it("should fail autoclose disabled and single screen") { + options.autoClose = false options.allow = [.ResetPassword] - expect(options.validate()).to(beNil()) + expect(options.validate()).toNot(beNil()) } it("should pass when autoclose empty, no login, but closeable set") { options.closable = true - options.autoClose = [] + options.autoClose = false options.allow = [.Signup, .ResetPassword] expect(options.validate()).to(beNil()) } diff --git a/LockTests/Presenters/DatabaseForgotPasswordPresenterSpec.swift b/LockTests/Presenters/DatabaseForgotPasswordPresenterSpec.swift index 5ad00556a..3364017bf 100644 --- a/LockTests/Presenters/DatabaseForgotPasswordPresenterSpec.swift +++ b/LockTests/Presenters/DatabaseForgotPasswordPresenterSpec.swift @@ -213,7 +213,7 @@ class DatabaseForgotPasswordPresenterSpec: QuickSpec { it("should show global success message") { options.allow = .ResetPassword - options.autoClose = [] + options.autoClose = false presenter = DatabaseForgotPasswordPresenter(interactor: interactor, connections: connections, navigator: navigator, options: options) presenter.messagePresenter = messagePresenter view = presenter.view as! DatabaseForgotPasswordView diff --git a/LockTests/Presenters/DatabasePresenterSpec.swift b/LockTests/Presenters/DatabasePresenterSpec.swift index fe19cd986..ac90902fa 100644 --- a/LockTests/Presenters/DatabasePresenterSpec.swift +++ b/LockTests/Presenters/DatabasePresenterSpec.swift @@ -307,7 +307,16 @@ class DatabasePresenterSpec: QuickSpec { view.primaryButton?.onPress(view.primaryButton!) expect(messagePresenter.error).toEventually(beError(error: DatabaseAuthenticatableError.couldNotLogin)) } - + + it("should show no success message") { + interactor.onLogin = { + return nil + } + view.primaryButton?.onPress(view.primaryButton!) + expect(messagePresenter.error).toEventually(beNil()) + expect(messagePresenter.message).toEventually(beNil()) + } + it("should navigate to multifactor required screen") { interactor.onLogin = { return .multifactorRequired @@ -349,6 +358,20 @@ class DatabasePresenterSpec: QuickSpec { button.onPress(button) expect(navigator.route).toEventually(equal(Route.forgotPassword)) } + + it("should always show terms button in signup") { + var options = LockOptions() + options.autoClose = false + presenter = DatabasePresenter(authenticator: interactor, creator: interactor, connection: DatabaseConnection(name: connection, requiresUsername: true), navigator: navigator, options: options) + presenter.messagePresenter = messagePresenter + view = presenter.view as! DatabaseOnlyView + interactor.onLogin = { + return nil + } + view.primaryButton?.onPress(view.primaryButton!) + expect(messagePresenter.error).toEventually(beNil()) + expect(messagePresenter.message).toEventuallyNot(beNil()) + } } } @@ -568,7 +591,7 @@ class DatabasePresenterSpec: QuickSpec { beforeEach { options.loginAfterSignup = false options.allow = [.Signup] - options.autoClose = [] + options.autoClose = false presenter = DatabasePresenter(authenticator: interactor, creator: interactor, connection: DatabaseConnection(name: connection, requiresUsername: true), navigator: navigator, options: options) presenter.messagePresenter = messagePresenter view = presenter.view as! DatabaseOnlyView