diff --git a/Lock/CredentialView.swift b/Lock/CredentialView.swift index 807ce8a84..809f6eed9 100644 --- a/Lock/CredentialView.swift +++ b/Lock/CredentialView.swift @@ -22,7 +22,7 @@ import UIKit -public class CredentialView: UIView, Form { +class CredentialView: UIView, Form { var identityField: InputField var passwordField: InputField @@ -50,18 +50,18 @@ public class CredentialView: UIView, Form { // MARK: - Initialisers - public convenience init() { + convenience init() { self.init(frame: CGRect.zero) } - required override public init(frame: CGRect) { + required override init(frame: CGRect) { self.identityField = InputField() self.passwordField = InputField() super.init(frame: frame) self.layoutForm() } - public required init?(coder aDecoder: NSCoder) { + required init?(coder aDecoder: NSCoder) { self.identityField = InputField() self.passwordField = InputField() super.init(coder: aDecoder) diff --git a/Lock/DatabaseForgotPasswordPresenter.swift b/Lock/DatabaseForgotPasswordPresenter.swift index 39b214f1a..77ad96544 100644 --- a/Lock/DatabaseForgotPasswordPresenter.swift +++ b/Lock/DatabaseForgotPasswordPresenter.swift @@ -41,7 +41,7 @@ class DatabaseForgotPasswordPresenter: Presentable, Loggable { var view: View { let email = self.interactor.validEmail ? self.interactor.email : nil - let view = DatabaseForgotPasswordView(email: email, options: options) + let view = DatabaseForgotPasswordView(email: email) let form = view.form view.form?.onValueChange = { input in @@ -73,6 +73,8 @@ class DatabaseForgotPasswordPresenter: Presentable, Loggable { } 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) + guard self.options.allow.contains(.Login) else { return } + self.navigator.navigate(.root) } } } diff --git a/Lock/DatabaseForgotPasswordView.swift b/Lock/DatabaseForgotPasswordView.swift index 9de379e46..6401b8cf8 100644 --- a/Lock/DatabaseForgotPasswordView.swift +++ b/Lock/DatabaseForgotPasswordView.swift @@ -27,7 +27,7 @@ class DatabaseForgotPasswordView: UIView, View { weak var form: Form? weak var primaryButton: PrimaryButton? - init(email: String?, options: Options) { + init(email: String?) { let primaryButton = PrimaryButton() let forgotView = SingleInputView() let center = UILayoutGuide() diff --git a/Lock/DatabaseModeSwitcher.swift b/Lock/DatabaseModeSwitcher.swift index 8bdadb776..9b2f43d10 100644 --- a/Lock/DatabaseModeSwitcher.swift +++ b/Lock/DatabaseModeSwitcher.swift @@ -22,13 +22,13 @@ import UIKit -public class DatabaseModeSwitcher: UIView { +class DatabaseModeSwitcher: UIView { weak var segmentedControl: UISegmentedControl? var onSelectionChange: (DatabaseModeSwitcher) -> () = { _ in } - public enum Mode: Int { + enum Mode: Int { case login = 0 case signup @@ -57,16 +57,16 @@ public class DatabaseModeSwitcher: UIView { // MARK: - Initialisers - public convenience init() { + convenience init() { self.init(frame: CGRect.zero) } - required override public init(frame: CGRect) { + required override init(frame: CGRect) { super.init(frame: frame) self.layoutSwitcher() } - public required init?(coder aDecoder: NSCoder) { + required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) self.layoutSwitcher() } @@ -109,7 +109,7 @@ public class DatabaseModeSwitcher: UIView { self.selected = .login } - public override var intrinsicContentSize : CGSize { + override var intrinsicContentSize : CGSize { return CGSize(width: UIViewNoIntrinsicMetric, height: 55) } diff --git a/Lock/DatabasePresenter.swift b/Lock/DatabasePresenter.swift index 9ff904c06..34629e86d 100644 --- a/Lock/DatabasePresenter.swift +++ b/Lock/DatabasePresenter.swift @@ -114,17 +114,13 @@ class DatabasePresenter: Presentable, Loggable { } } - // Enterprise Authentication if let connection = self.enterpriseInteractor?.connection { - // Credential Auth if self.options.enterpriseConnectionUsingActiveAuth.contains(connection.name) { self.navigator.navigate(.enterpriseActiveAuth(connection: connection)) } else { - // OAuth self.enterpriseInteractor?.login(errorHandler) } } else { - // Database Authentication self.authenticator.login(errorHandler) } @@ -164,7 +160,7 @@ class DatabasePresenter: Presentable, Loggable { guard createError != nil || loginError != nil else { if !self.options.loginAfterSignup { let message = "Thanks for signing up.".i18n(key: "com.auth0.lock.database.signup.success.message", comment: "User signed up") - if let databaseView = self.databaseView { + if let databaseView = self.databaseView, self.options.allow.contains(.Login) { self.showLogin(inView: databaseView, identifier: self.creator.identifier) } self.messagePresenter?.showSuccess(message) @@ -224,7 +220,7 @@ class DatabasePresenter: Presentable, Loggable { guard let attr = attribute else { return } do { try self.authenticator.update(attr, value: input.text) - // Check for Entperise domain match in login view + if self.enterpriseInteractor?.matchDomain(input.text) != nil, let mode = self.databaseView?.switcher?.selected, mode == .login { try self.enterpriseInteractor?.updateEmail(input.text) self.logger.verbose("Enterprise connection detected: \(self.enterpriseInteractor?.connection)") diff --git a/Lock/EnterpriseActiveAuthPresenter.swift b/Lock/EnterpriseActiveAuthPresenter.swift index 6445c8aae..a8b413d11 100644 --- a/Lock/EnterpriseActiveAuthPresenter.swift +++ b/Lock/EnterpriseActiveAuthPresenter.swift @@ -44,7 +44,7 @@ class EnterpriseActiveAuthPresenter: Presentable, Loggable { identifier = username } - let view = EnterpriseActiveAuthView(identifer: identifier, identifierAttribute: self.interactor.identifierAttribute, options: self.options) + let view = EnterpriseActiveAuthView(identifer: identifier, identifierAttribute: self.interactor.identifierAttribute) let form = view.form view.ssoBar?.title = self.interactor.connection.domains.first diff --git a/Lock/EnterpriseActiveAuthView.swift b/Lock/EnterpriseActiveAuthView.swift index ce5e53745..2a9ef1d47 100644 --- a/Lock/EnterpriseActiveAuthView.swift +++ b/Lock/EnterpriseActiveAuthView.swift @@ -30,7 +30,7 @@ class EnterpriseActiveAuthView: UIView, View { private weak var container: UIStackView? - init(identifer: String?, identifierAttribute:UserAttribute, options: Options) { + init(identifer: String?, identifierAttribute:UserAttribute) { let primaryButton = PrimaryButton() let credentialView = CredentialView() let container = UIStackView() diff --git a/Lock/EnterpriseDomainInteractor.swift b/Lock/EnterpriseDomainInteractor.swift index c751700ce..5d7060128 100644 --- a/Lock/EnterpriseDomainInteractor.swift +++ b/Lock/EnterpriseDomainInteractor.swift @@ -37,7 +37,6 @@ struct EnterpriseDomainInteractor: HRDAuthenticatable { self.connections = connections.enterprise self.authenticator = authentication - // Single Enterprise, defaulting connection if self.connections.count == 1 && connections.oauth2.isEmpty && connections.database == nil { self.connection = self.connections.first } @@ -57,7 +56,6 @@ struct EnterpriseDomainInteractor: HRDAuthenticatable { throw error } validEmail = true - connection = matchDomain(value) } diff --git a/Lock/EnterpriseDomainPresenter.swift b/Lock/EnterpriseDomainPresenter.swift index 8aa5be084..f975fb291 100644 --- a/Lock/EnterpriseDomainPresenter.swift +++ b/Lock/EnterpriseDomainPresenter.swift @@ -28,8 +28,6 @@ class EnterpriseDomainPresenter: Presentable, Loggable { var customLogger: Logger? var user: User var options: Options - - // Social connections var authPresenter: AuthPresenter? init(interactor: EnterpriseDomainInteractor, navigator: Navigable, user: User, options: Options) { @@ -46,7 +44,6 @@ class EnterpriseDomainPresenter: Presentable, Loggable { let email = self.interactor.validEmail ? self.interactor.email : nil let authCollectionView = self.authPresenter?.newViewToEmbed(withInsets: UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0), isLogin: true) - // Single Enterprise Domain if let enterpriseButton = EnterpriseButton(forConnections: interactor.connections, customStyle: [:], isLogin: true, onAction: { self.interactor.login { error in Queue.main.async { @@ -57,7 +54,6 @@ class EnterpriseDomainPresenter: Presentable, Loggable { self.logger.debug("Enterprise authenticator launched") } } - }}) { let view = EnterpriseDomainView(authButton: enterpriseButton, authCollectionView: authCollectionView) return view @@ -86,7 +82,6 @@ class EnterpriseDomainPresenter: Presentable, Loggable { } let action = { [weak form] (button: PrimaryButton) in - // Check for credential auth if let connection = self.interactor.connection, self.options.enterpriseConnectionUsingActiveAuth.contains(connection.name) { guard self.navigator?.navigate(.enterpriseActiveAuth(connection: connection)) == nil else { return } } @@ -106,9 +101,7 @@ class EnterpriseDomainPresenter: Presentable, Loggable { self.logger.debug("Enterprise authenticator launched") } } - } - } view.primaryButton?.onPress = action diff --git a/Lock/EnterpriseDomainView.swift b/Lock/EnterpriseDomainView.swift index ef8b76bca..1bb8b60dc 100644 --- a/Lock/EnterpriseDomainView.swift +++ b/Lock/EnterpriseDomainView.swift @@ -145,7 +145,7 @@ private func strutView(withHeight height: CGFloat = 50) -> UIView { return view } -public class EnterpriseSingleInputView : SingleInputView { +class EnterpriseSingleInputView : SingleInputView { public override var intrinsicContentSize : CGSize { return CGSize(width: UIViewNoIntrinsicMetric, height: 50) diff --git a/Lock/InfoBarView.swift b/Lock/InfoBarView.swift index 58492ff55..3239cfc65 100644 --- a/Lock/InfoBarView.swift +++ b/Lock/InfoBarView.swift @@ -22,7 +22,7 @@ import UIKit -public class InfoBarView: UIView { +class InfoBarView: UIView { weak var container: UIView? weak var iconView: UIImageView? @@ -38,16 +38,16 @@ public class InfoBarView: UIView { } } - public convenience init() { + convenience init() { self.init(frame: CGRect.zero) } - required override public init(frame: CGRect) { + required override init(frame: CGRect) { super.init(frame: frame) self.layoutHeader() } - public required init?(coder aDecoder: NSCoder) { + required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) self.layoutHeader() } @@ -91,7 +91,7 @@ public class InfoBarView: UIView { self.iconView?.tintColor = UIColor ( red: 0.5725, green: 0.5804, blue: 0.5843, alpha: 1.0 ) } - public override var intrinsicContentSize : CGSize { + override var intrinsicContentSize : CGSize { return CGSize(width: UIViewNoIntrinsicMetric, height: 35) } diff --git a/Lock/InputField.swift b/Lock/InputField.swift index f93db92a4..252a1d572 100644 --- a/Lock/InputField.swift +++ b/Lock/InputField.swift @@ -22,7 +22,7 @@ import UIKit -public class InputField: UIView, UITextFieldDelegate { +class InputField: UIView, UITextFieldDelegate { weak var containerView: UIView? weak var textField: UITextField? @@ -77,16 +77,16 @@ public class InputField: UIView, UITextFieldDelegate { // MARK: - Initialisers - public convenience init() { + convenience init() { self.init(frame: CGRect.zero) } - required override public init(frame: CGRect) { + required override init(frame: CGRect) { super.init(frame: frame) self.layoutField() } - public required init?(coder aDecoder: NSCoder) { + required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) self.layoutField() } @@ -183,7 +183,7 @@ public class InputField: UIView, UITextFieldDelegate { self.containerView?.layer.borderColor = State.valid.color.cgColor } - public override var intrinsicContentSize : CGSize { + override var intrinsicContentSize : CGSize { return CGSize(width: 230, height: 50) } @@ -221,15 +221,15 @@ public class InputField: UIView, UITextFieldDelegate { } } - public func textFieldDidBeginEditing(_ textField: UITextField) { + func textFieldDidBeginEditing(_ textField: UITextField) { self.onBeginEditing(self) } - public func textFieldDidEndEditing(_ textField: UITextField) { + func textFieldDidEndEditing(_ textField: UITextField) { self.onEndEditing(self) } - public func textFieldShouldReturn(_ textField: UITextField) -> Bool { + func textFieldShouldReturn(_ textField: UITextField) -> Bool { self.onReturn(self) if let field = self.nextField?.textField { Queue.main.async { @@ -248,7 +248,7 @@ public class InputField: UIView, UITextFieldDelegate { } // MARK: - Types - public enum InputType { + enum InputType { case email case username case emailOrUsername diff --git a/Lock/MessageView.swift b/Lock/MessageView.swift index 18fbbc788..17216346c 100644 --- a/Lock/MessageView.swift +++ b/Lock/MessageView.swift @@ -22,7 +22,7 @@ import UIKit -public class MessageView: UIView { +class MessageView: UIView { weak var messageLabel: UILabel? @@ -42,7 +42,7 @@ public class MessageView: UIView { } } - public enum Flavor { + enum Flavor { case success case failure @@ -60,16 +60,16 @@ public class MessageView: UIView { } } - required override public init(frame: CGRect) { + required override init(frame: CGRect) { super.init(frame: frame) self.layoutMessage() } - public convenience init() { + convenience init() { self.init(frame: CGRect.zero) } - public required convenience init?(coder aDecoder: NSCoder) { + required convenience init?(coder aDecoder: NSCoder) { self.init(frame: CGRect.zero) } diff --git a/Lock/Options.swift b/Lock/Options.swift index 8f95fae0f..39a821ac2 100644 --- a/Lock/Options.swift +++ b/Lock/Options.swift @@ -40,7 +40,6 @@ public protocol Options { var customSignupFields: [CustomTextField] { get } var loginAfterSignup: Bool { get } - // Enterprise var activeDirectoryEmailAsUsername: Bool { get } var enterpriseConnectionUsingActiveAuth: [String] { get } diff --git a/Lock/PrimaryButton.swift b/Lock/PrimaryButton.swift index 6ed183088..2247e6d06 100644 --- a/Lock/PrimaryButton.swift +++ b/Lock/PrimaryButton.swift @@ -22,7 +22,7 @@ import UIKit -public class PrimaryButton: UIView { +class PrimaryButton: UIView { weak var button: UIButton? weak var indicator: UIActivityIndicatorView? @@ -57,16 +57,16 @@ public class PrimaryButton: UIView { } } - public convenience init() { + convenience init() { self.init(frame: CGRect.zero) } - required override public init(frame: CGRect) { + required override init(frame: CGRect) { super.init(frame: frame) self.layoutButton() } - public required init?(coder aDecoder: NSCoder) { + required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) self.layoutButton() } @@ -127,7 +127,7 @@ public class PrimaryButton: UIView { button.setAttributedTitle(NSAttributedString(), for: .disabled) } - public override var intrinsicContentSize : CGSize { + override var intrinsicContentSize : CGSize { return CGSize(width: UIViewNoIntrinsicMetric, height: 95) } diff --git a/Lock/Router.swift b/Lock/Router.swift index d4c30dba5..437620538 100644 --- a/Lock/Router.swift +++ b/Lock/Router.swift @@ -60,12 +60,10 @@ struct Router: Navigable { let authentication = self.lock.authentication let interactor = DatabaseInteractor(connection: database, authentication: authentication, user: self.user, options: self.lock.options, dispatcher: lock.observerStore) let presenter = DatabasePresenter(interactor: interactor, connection: database, navigator: self, options: self.lock.options) - // Add Social if !connections.oauth2.isEmpty { let interactor = Auth0OAuth2Interactor(webAuth: self.lock.webAuth, dispatcher: lock.observerStore, options: self.lock.options) presenter.authPresenter = AuthPresenter(connections: connections, interactor: interactor, customStyle: self.lock.style.oauth2) } - // Add Enterprise if !connections.enterprise.isEmpty { let authInteractor = Auth0OAuth2Interactor(webAuth: self.lock.webAuth, dispatcher: lock.observerStore, options: self.lock.options) let interactor = EnterpriseDomainInteractor(connections: connections, authentication: authInteractor) @@ -76,9 +74,8 @@ struct Router: Navigable { if !connections.enterprise.isEmpty { let authInteractor = Auth0OAuth2Interactor(webAuth: self.lock.webAuth, dispatcher: lock.observerStore, options: self.lock.options) let interactor = EnterpriseDomainInteractor(connections: connections, authentication: authInteractor) - // Single enterprise in active auth mode if let connection = interactor.connection, self.lock.options.enterpriseConnectionUsingActiveAuth.contains(connection.name) { - return EnterpriseActiveAuth(connection) + return enterpriseActiveAuth(connection) } let presenter = EnterpriseDomainPresenter(interactor: interactor, navigator: self, user: self.user, options: self.lock.options) if !connections.oauth2.isEmpty { @@ -119,7 +116,7 @@ struct Router: Navigable { return presenter } - func EnterpriseActiveAuth(_ connection: EnterpriseConnection) -> Presentable? { + func enterpriseActiveAuth(_ connection: EnterpriseConnection) -> Presentable? { let authentication = self.lock.authentication let interactor = EnterpriseActiveAuthInteractor(connection: connection, authentication: authentication, user: self.user, options: self.lock.options, dispatcher: lock.observerStore) let presenter = EnterpriseActiveAuthPresenter(interactor: interactor, options: self.lock.options) @@ -174,7 +171,7 @@ struct Router: Navigable { case .multifactor: presentable = self.multifactor case .enterpriseActiveAuth(let connection): - presentable = self.EnterpriseActiveAuth(connection) + presentable = self.enterpriseActiveAuth(connection) case .unrecoverableError(let error): presentable = self.unrecoverableError(error) default: diff --git a/Lock/SecondaryButton.swift b/Lock/SecondaryButton.swift index 25b460fea..7401706ee 100644 --- a/Lock/SecondaryButton.swift +++ b/Lock/SecondaryButton.swift @@ -22,7 +22,7 @@ import UIKit -public class SecondaryButton: UIView { +class SecondaryButton: UIView { weak var button: UIButton? @@ -44,16 +44,16 @@ public class SecondaryButton: UIView { } // MARK: - Initialisers - public convenience init() { + convenience init() { self.init(frame: CGRect.zero) } - required override public init(frame: CGRect) { + required override init(frame: CGRect) { super.init(frame: frame) self.layoutButton() } - public required init?(coder aDecoder: NSCoder) { + required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) self.layoutButton() } @@ -80,7 +80,7 @@ public class SecondaryButton: UIView { self.button = button } - public override var intrinsicContentSize : CGSize { + override var intrinsicContentSize : CGSize { return CGSize(width: UIViewNoIntrinsicMetric, height: 76) } diff --git a/Lock/SignUpView.swift b/Lock/SignUpView.swift index 26d09ed1e..f93314c1b 100644 --- a/Lock/SignUpView.swift +++ b/Lock/SignUpView.swift @@ -22,7 +22,7 @@ import UIKit -public class SignUpView: UIView, Form { +class SignUpView: UIView, Form { var emailField: InputField var passwordField: InputField var usernameField: InputField? @@ -66,7 +66,7 @@ public class SignUpView: UIView, Form { // MARK: - Initialisers - public init(additionalFields: [CustomTextField]) { + init(additionalFields: [CustomTextField]) { self.emailField = inputField(withType: .email) self.passwordField = inputField(withType: .password) var fields = [emailField, passwordField] @@ -76,7 +76,7 @@ public class SignUpView: UIView, Form { self.layoutForm() } - required override public init(frame: CGRect) { + required override init(frame: CGRect) { self.emailField = inputField(withType: .email) self.passwordField = inputField(withType: .password) self.stackView = UIStackView(arrangedSubviews: [emailField, passwordField]) @@ -84,7 +84,7 @@ public class SignUpView: UIView, Form { self.layoutForm() } - public required init?(coder aDecoder: NSCoder) { + required init?(coder aDecoder: NSCoder) { self.emailField = inputField(withType: .email) self.passwordField = inputField(withType: .password) self.stackView = UIStackView(arrangedSubviews: [emailField, passwordField]) diff --git a/Lock/SingleInputView.swift b/Lock/SingleInputView.swift index 70fe13d8b..39bc60304 100644 --- a/Lock/SingleInputView.swift +++ b/Lock/SingleInputView.swift @@ -22,7 +22,7 @@ import UIKit -public class SingleInputView: UIView, Form { +class SingleInputView: UIView, Form { private var inputField: InputField private var titleView: UILabel private var messageView: UILabel @@ -79,7 +79,7 @@ public class SingleInputView: UIView, Form { // MARK: - Initialisers - required override public init(frame: CGRect) { + required override init(frame: CGRect) { self.inputField = InputField() self.titleView = UILabel() self.messageView = UILabel() @@ -88,11 +88,11 @@ public class SingleInputView: UIView, Form { self.layoutForm() } - public convenience init() { + convenience init() { self.init(frame: CGRect.zero) } - public required convenience init?(coder aDecoder: NSCoder) { + required convenience init?(coder aDecoder: NSCoder) { self.init(frame: CGRect.zero) } @@ -121,7 +121,7 @@ public class SingleInputView: UIView, Form { inputField.returnKey = self.returnKey } - public override var intrinsicContentSize : CGSize { + override var intrinsicContentSize : CGSize { return CGSize(width: UIViewNoIntrinsicMetric, height: 244) } } diff --git a/LockTests/Presenters/DatabaseForgotPasswordPresenterSpec.swift b/LockTests/Presenters/DatabaseForgotPasswordPresenterSpec.swift index 76001ef80..a60e8bf50 100644 --- a/LockTests/Presenters/DatabaseForgotPasswordPresenterSpec.swift +++ b/LockTests/Presenters/DatabaseForgotPasswordPresenterSpec.swift @@ -196,6 +196,34 @@ class DatabaseForgotPasswordPresenterSpec: QuickSpec { } + describe("navigation on success") { + + it("should navigate to .root") { + let button = view.primaryButton! + interactor.onRequest = { + return nil + } + button.onPress(button) + expect(navigator.route).toEventually(equal(Route.root)) + } + + it("should not navigate to .root") { + options.allow = .ResetPassword + navigator.route = .forgotPassword + + presenter = DatabaseForgotPasswordPresenter(interactor: interactor, connections: connections, navigator: navigator, options: options) + view = presenter.view as! DatabaseForgotPasswordView + + let button = view.primaryButton! + interactor.onRequest = { + return nil + } + button.onPress(button) + expect(navigator.route).toEventuallyNot(equal(Route.root)) + } + + } + } } diff --git a/LockTests/Presenters/DatabasePresenterSpec.swift b/LockTests/Presenters/DatabasePresenterSpec.swift index a27d1e608..fd5871903 100644 --- a/LockTests/Presenters/DatabasePresenterSpec.swift +++ b/LockTests/Presenters/DatabasePresenterSpec.swift @@ -523,6 +523,40 @@ class DatabasePresenterSpec: QuickSpec { button.onPress(button) expect(button.inProgress).toEventually(beFalse()) } + + context("no login after signup") { + + beforeEach { + options.loginAfterSignup = false + } + + it("should switch to login on success") { + presenter = DatabasePresenter(authenticator: interactor, creator: interactor, connection: DatabaseConnection(name: connection, requiresUsername: true), navigator: navigator, options: options) + view = presenter.view as! DatabaseOnlyView + + let button = view.primaryButton! + interactor.onSignUp = { + return nil + } + button.onPress(button) + expect(view.switcher!.selected).toEventually(equal(DatabaseModeSwitcher.Mode.login)) + } + + it("should remain on signup on success") { + options.allow = .Signup + presenter = DatabasePresenter(authenticator: interactor, creator: interactor, connection: DatabaseConnection(name: connection, requiresUsername: true), navigator: navigator, options: options) + view = presenter.view as! DatabaseOnlyView + + let button = view.primaryButton! + interactor.onSignUp = { + return nil + } + button.onPress(button) + expect(button.title).toEventually(contain("Sign up")) + } + + + } } describe("tos action") {