From 30af43b84105f5fcb1a6903f6c3557ab9e39dc03 Mon Sep 17 00:00:00 2001 From: Martin Walsh Date: Tue, 10 Jan 2017 14:28:02 +0000 Subject: [PATCH 1/3] Added Error support to CDNLoader callback Added ConnectionError Presenter/View Added Option to disable full screen critical errors Added ConnectionError Route Updated CDNLoader callback tests Added Tests for new Presenter/View --- Lock.xcodeproj/project.pbxproj | 20 +++++ Lock/CDNLoaderInteractor.swift | 33 ++++++--- Lock/ConnectionErrorPresenter.swift | 43 +++++++++++ Lock/ConnectionErrorView.swift | 73 +++++++++++++++++++ Lock/ConnectionLoadingPresenter.swift | 12 ++- Lock/LockOptions.swift | 1 + Lock/OptionBuildable.swift | 3 + Lock/Options.swift | 1 + Lock/RemoteConnectionLoader.swift | 4 +- Lock/RemoteConnectionLoaderError.swift | 49 +++++++++++++ Lock/Router.swift | 9 ++- Lock/Routes.swift | 5 +- .../Interactors/CDNLoaderInteractorSpec.swift | 34 ++++++++- LockTests/OptionsSpec.swift | 9 +++ .../ConnectionErrorPresenterSpec.swift | 70 ++++++++++++++++++ .../ConnectionLoadingPresenterSpec.swift | 4 +- .../RemoteConnectionLoaderErrorSpec.swift | 66 +++++++++++++++++ LockTests/Router/RouterSpec.swift | 11 +++ LockTests/Utils/Mocks.swift | 5 +- 19 files changed, 431 insertions(+), 21 deletions(-) create mode 100644 Lock/ConnectionErrorPresenter.swift create mode 100644 Lock/ConnectionErrorView.swift create mode 100644 Lock/RemoteConnectionLoaderError.swift create mode 100644 LockTests/Presenters/ConnectionErrorPresenterSpec.swift create mode 100644 LockTests/RemoteConnectionLoaderErrorSpec.swift diff --git a/Lock.xcodeproj/project.pbxproj b/Lock.xcodeproj/project.pbxproj index 873ddc312..a408b396a 100644 --- a/Lock.xcodeproj/project.pbxproj +++ b/Lock.xcodeproj/project.pbxproj @@ -19,6 +19,11 @@ 5B4DE0151DD66DE1004C8AC2 /* EnterpriseDomainInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B4DE0141DD66DE1004C8AC2 /* EnterpriseDomainInteractor.swift */; }; 5B4DE0171DD67064004C8AC2 /* EnterpriseActiveAuthPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B4DE0161DD67064004C8AC2 /* EnterpriseActiveAuthPresenter.swift */; }; 5B4DE0191DD670F7004C8AC2 /* EnterpriseActiveAuthView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B4DE0181DD670F7004C8AC2 /* EnterpriseActiveAuthView.swift */; }; + 5B54CDC41E25200900F0AFFF /* ConnectionErrorPresenterSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B54CDC31E25200900F0AFFF /* ConnectionErrorPresenterSpec.swift */; }; + 5B55F3C91E24273D00B75CF5 /* ConnectionErrorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B55F3C81E24273D00B75CF5 /* ConnectionErrorView.swift */; }; + 5B55F3CB1E242A2E00B75CF5 /* ConnectionErrorPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B55F3CA1E242A2E00B75CF5 /* ConnectionErrorPresenter.swift */; }; + 5B55F3D11E244B8700B75CF5 /* RemoteConnectionLoaderError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B55F3D01E244B8700B75CF5 /* RemoteConnectionLoaderError.swift */; }; + 5B55F3D41E24FFD000B75CF5 /* RemoteConnectionLoaderErrorSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B55F3D21E24F3F000B75CF5 /* RemoteConnectionLoaderErrorSpec.swift */; }; 5B6631531DDB9B28001CB043 /* EnterpriseDomainInteractorSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6631511DDB990B001CB043 /* EnterpriseDomainInteractorSpec.swift */; }; 5BA563F11DD117550002D3AB /* EnterpriseActiveAuthInteractorSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA563EF1DD1171F0002D3AB /* EnterpriseActiveAuthInteractorSpec.swift */; }; 5BB4A7C11DF9A38E008E8C37 /* DatabaseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BB4A7C01DF9A38E008E8C37 /* DatabaseView.swift */; }; @@ -209,6 +214,11 @@ 5B4DE0141DD66DE1004C8AC2 /* EnterpriseDomainInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = EnterpriseDomainInteractor.swift; path = Lock/EnterpriseDomainInteractor.swift; sourceTree = SOURCE_ROOT; }; 5B4DE0161DD67064004C8AC2 /* EnterpriseActiveAuthPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = EnterpriseActiveAuthPresenter.swift; path = Lock/EnterpriseActiveAuthPresenter.swift; sourceTree = SOURCE_ROOT; }; 5B4DE0181DD670F7004C8AC2 /* EnterpriseActiveAuthView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = EnterpriseActiveAuthView.swift; path = Lock/EnterpriseActiveAuthView.swift; sourceTree = SOURCE_ROOT; }; + 5B54CDC31E25200900F0AFFF /* ConnectionErrorPresenterSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionErrorPresenterSpec.swift; sourceTree = ""; }; + 5B55F3C81E24273D00B75CF5 /* ConnectionErrorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionErrorView.swift; sourceTree = ""; }; + 5B55F3CA1E242A2E00B75CF5 /* ConnectionErrorPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionErrorPresenter.swift; sourceTree = ""; }; + 5B55F3D01E244B8700B75CF5 /* RemoteConnectionLoaderError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RemoteConnectionLoaderError.swift; sourceTree = ""; }; + 5B55F3D21E24F3F000B75CF5 /* RemoteConnectionLoaderErrorSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RemoteConnectionLoaderErrorSpec.swift; sourceTree = ""; }; 5B6631511DDB990B001CB043 /* EnterpriseDomainInteractorSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnterpriseDomainInteractorSpec.swift; sourceTree = ""; }; 5BA563EF1DD1171F0002D3AB /* EnterpriseActiveAuthInteractorSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnterpriseActiveAuthInteractorSpec.swift; sourceTree = ""; }; 5BB4A7C01DF9A38E008E8C37 /* DatabaseView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatabaseView.swift; sourceTree = ""; }; @@ -377,6 +387,7 @@ 5B0CF2D41DE9ADF900F82BF4 /* InputValidationErrorSpec.swift */, 5B0CF2D71DE9B14000F82BF4 /* DatabaseAuthenticatableErrorSpec.swift */, 5B0CF2DC1DE9B47C00F82BF4 /* DatabaseUserCreatorErrorSpec.swift */, + 5B55F3D21E24F3F000B75CF5 /* RemoteConnectionLoaderErrorSpec.swift */, ); name = Errors; sourceTree = ""; @@ -392,6 +403,7 @@ isa = PBXGroup; children = ( 5F2496B51D665AA800A1C6E2 /* InputValidationError.swift */, + 5B55F3D01E244B8700B75CF5 /* RemoteConnectionLoaderError.swift */, 5F2496B91D665AE900A1C6E2 /* DatabaseAuthenticatableError.swift */, 5F2496BB1D665AF700A1C6E2 /* DatabaseUserCreatorError.swift */, ); @@ -451,6 +463,7 @@ children = ( 5BB4A7C01DF9A38E008E8C37 /* DatabaseView.swift */, 5F1C49921D8360DF005B74FC /* LoadingView.swift */, + 5B55F3C81E24273D00B75CF5 /* ConnectionErrorView.swift */, 5F73CDD31D3073BE00D8D8D1 /* DatabaseForgotPasswordView.swift */, 5B0971811DC8FAC5003AA88F /* EnterpriseDomainView.swift */, 5B4DE0181DD670F7004C8AC2 /* EnterpriseActiveAuthView.swift */, @@ -482,6 +495,7 @@ 5FBE5CC91D3EA1380038536D /* MultifactorPresenterSpec.swift */, 5F57DFCD1D4FBE5A00C54DA8 /* AuthPresenterSpec.swift */, 5FE50DBE1D7A254000D82290 /* ConnectionLoadingPresenterSpec.swift */, + 5B54CDC31E25200900F0AFFF /* ConnectionErrorPresenterSpec.swift */, 5FB06A411E1329FA00E62F36 /* BannerMessagePresenterSpec.swift */, ); path = Presenters; @@ -581,6 +595,7 @@ isa = PBXGroup; children = ( 5F1C498F1D8360BF005B74FC /* ConnectionLoadingPresenter.swift */, + 5B55F3CA1E242A2E00B75CF5 /* ConnectionErrorPresenter.swift */, 5B09717F1DC8F5C4003AA88F /* EnterpriseDomainPresenter.swift */, 5F73CDD51D30790500D8D8D1 /* DatabaseForgotPasswordPresenter.swift */, 5B4DE0161DD67064004C8AC2 /* EnterpriseActiveAuthPresenter.swift */, @@ -961,6 +976,7 @@ 5F1C499B1D836190005B74FC /* CustomTextField.swift in Sources */, 5BB4A7C11DF9A38E008E8C37 /* DatabaseView.swift in Sources */, 5FB06A401E131D0300E62F36 /* BannerMessagePresenter.swift in Sources */, + 5B55F3D11E244B8700B75CF5 /* RemoteConnectionLoaderError.swift in Sources */, 5F70F1E71D790773004698DA /* OptionBuildable.swift in Sources */, 5FBE5CB81D3D8F030038536D /* User.swift in Sources */, 5FF0B2281E1726C400A73257 /* CredentialAuth.swift in Sources */, @@ -983,6 +999,7 @@ 5B0971801DC8F5C4003AA88F /* EnterpriseDomainPresenter.swift in Sources */, 5FC434861D1DF769005188BC /* View.swift in Sources */, 5FDB41CE1D2C79FD00166B67 /* Operations.swift in Sources */, + 5B55F3C91E24273D00B75CF5 /* ConnectionErrorView.swift in Sources */, 5B09717C1DC8F229003AA88F /* EnterpriseDomain.swift in Sources */, 5F51EE681D1C88FC0024BCD6 /* SignUpView.swift in Sources */, 5F57DFD41D4FE64700C54DA8 /* Auth0OAuth2Interactor.swift in Sources */, @@ -995,6 +1012,7 @@ 5F70F1E11D790500004698DA /* Connections.swift in Sources */, 5F92C68F1D50EAC200CCE6C0 /* LazyImage.swift in Sources */, 5F2496BA1D665AE900A1C6E2 /* DatabaseAuthenticatableError.swift in Sources */, + 5B55F3CB1E242A2E00B75CF5 /* ConnectionErrorPresenter.swift in Sources */, 5F57DFC61D4F79DD00C54DA8 /* AuthStyle.swift in Sources */, 5B0971821DC8FAC5003AA88F /* EnterpriseDomainView.swift in Sources */, 5B4DE0191DD670F7004C8AC2 /* EnterpriseActiveAuthView.swift in Sources */, @@ -1042,6 +1060,7 @@ 5F92C68B1D4FE90F00CCE6C0 /* Auth0OAuth2InteractorSpec.swift in Sources */, 5F5090081D1DE7BA00EAA650 /* NetworkStub.swift in Sources */, 5F0FCF921E201CF300E3D53B /* ObserverStoreSpec.swift in Sources */, + 5B55F3D41E24FFD000B75CF5 /* RemoteConnectionLoaderErrorSpec.swift in Sources */, 5F73CDDC1D309BE900D8D8D1 /* DatabasePasswordInteractorSpec.swift in Sources */, 5F2496AF1D66210500A1C6E2 /* LockViewControllerSpec.swift in Sources */, 5FA250501D48E2A200C544FA /* OptionsSpec.swift in Sources */, @@ -1051,6 +1070,7 @@ 5F508FFB1D1DB1E700EAA650 /* DatabaseInteractorSpec.swift in Sources */, 5F5F98D41D21E3890016FC22 /* DatabasePresenterSpec.swift in Sources */, 5FA250521D48F08200C544FA /* LockSpec.swift in Sources */, + 5B54CDC41E25200900F0AFFF /* ConnectionErrorPresenterSpec.swift in Sources */, 5F5F98DC1D22F0B40016FC22 /* RouterSpec.swift in Sources */, 5FBE5CCD1D3EDF960038536D /* UserSpec.swift in Sources */, 5BCDE1361DDDF17F00AA2A6C /* EnterpriseActiveAuthPresenterSpec.swift in Sources */, diff --git a/Lock/CDNLoaderInteractor.swift b/Lock/CDNLoaderInteractor.swift index 63b9b64fa..56eb6d499 100644 --- a/Lock/CDNLoaderInteractor.swift +++ b/Lock/CDNLoaderInteractor.swift @@ -33,17 +33,25 @@ struct CDNLoaderInteractor: RemoteConnectionLoader, Loggable { self.url = URL(string: "client/\(clientId).js", relativeTo: cdnURL(from: baseURL))! } - func load(_ callback: @escaping (Connections?) -> ()) { + func load(_ callback: @escaping (Connections?, RemoteConnectionLoaderError?) -> ()) { + let sessionConfiguration = URLSessionConfiguration.default + sessionConfiguration.timeoutIntervalForRequest = 30 // Default 60 + let session = URLSession(configuration: sessionConfiguration) + self.logger.info("Loading client info from \(self.url)") - let task = URLSession.shared.dataTask(with: self.url, completionHandler: { (data, response, error) in + let task = session.dataTask(with: self.url, completionHandler: { (data, response, error) in guard error == nil else { self.logger.error("Failed to load with error \(error!)") - callback(nil) + if let error = error, error._code == NSURLErrorTimedOut { + callback(nil, .connectionTimeout) + } else { + callback(nil, .requestIssue) + } return } guard let response = response as? HTTPURLResponse else { self.logger.error("Response was not NSHTTURLResponse") - return callback(nil) + return callback(nil, RemoteConnectionLoaderError.responseIssue) } let payload: String? @@ -54,12 +62,15 @@ struct CDNLoaderInteractor: RemoteConnectionLoader, Loggable { } guard 200...299 ~= response.statusCode else { self.logger.error("HTTP response was not successful. HTTP \(response.statusCode) <\(payload ?? "No Body")>") - return callback(nil) + if response.statusCode == 403 { + return callback(nil, RemoteConnectionLoaderError.invalidClient) + } + return callback(nil, RemoteConnectionLoaderError.responseIssue) } guard var jsonp = payload else { self.logger.error("HTTP response had no jsonp \(payload ?? "No Body")") - return callback(nil) + return callback(nil, RemoteConnectionLoaderError.invalidClientInfo) } self.logger.verbose("Received jsonp \(jsonp)") @@ -93,10 +104,14 @@ struct CDNLoaderInteractor: RemoteConnectionLoader, Loggable { info.oauth2.forEach { strategy in strategy.connections.forEach { connections.social(name: $0.name, style: AuthStyle.style(forStrategy: strategy.name, connectionName: $0.name)) } } - callback(connections) + if connections.isEmpty { + callback(connections, .noConnections) + } else { + callback(connections, nil) + } } catch let e { self.logger.error("Failed to parse \(jsonp) with error \(e)") - return callback(nil) + return callback(nil, .invalidClientInfo) } }) task.resume() @@ -143,7 +158,7 @@ private struct ClientInfo { "waad", "adfs", "ad" - ] + ] } diff --git a/Lock/ConnectionErrorPresenter.swift b/Lock/ConnectionErrorPresenter.swift new file mode 100644 index 000000000..f5228c74b --- /dev/null +++ b/Lock/ConnectionErrorPresenter.swift @@ -0,0 +1,43 @@ +// ConnectionErrorPresenter.swift +// +// Copyright (c) 2016 Auth0 (http://auth0.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Foundation + +class ConnectionErrorPresenter: Presentable, Loggable { + let navigator: Navigable + let error: RemoteConnectionLoaderError + + var messagePresenter: MessagePresenter? + + init(error: RemoteConnectionLoaderError, navigator: Navigable) { + self.navigator = navigator + self.error = error + } + + var view: View { + let view = ConnectionErrorView(message: self.error.localizableMessage) + view.primaryButton?.onPress = { _ in + self.navigator.navigate(.root) + } + return view + } +} diff --git a/Lock/ConnectionErrorView.swift b/Lock/ConnectionErrorView.swift new file mode 100644 index 000000000..df54f4118 --- /dev/null +++ b/Lock/ConnectionErrorView.swift @@ -0,0 +1,73 @@ +// ConnectionErrorView.swift +// +// Copyright (c) 2017 Auth0 (http://auth0.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import UIKit + +class ConnectionErrorView: UIView, View { + + weak var primaryButton: PrimaryButton? + weak var label: UILabel? + + init(message: String) { + let primaryButton = PrimaryButton() + let center = UILayoutGuide() + let label = UILabel() + + self.primaryButton = primaryButton + self.label = label + + super.init(frame: CGRect.zero) + + self.addSubview(primaryButton) + self.addSubview(label) + self.addLayoutGuide(center) + + constraintEqual(anchor: center.leftAnchor, toAnchor: self.leftAnchor, constant: 20) + constraintEqual(anchor: center.topAnchor, toAnchor: self.topAnchor) + constraintEqual(anchor: center.rightAnchor, toAnchor: self.rightAnchor, constant: -20) + constraintEqual(anchor: center.bottomAnchor, toAnchor: primaryButton.topAnchor) + + constraintEqual(anchor: label.leftAnchor, toAnchor: center.leftAnchor) + constraintEqual(anchor: label.rightAnchor, toAnchor: center.rightAnchor) + constraintEqual(anchor: label.centerYAnchor, toAnchor: center.centerYAnchor, constant: -20) + label.translatesAutoresizingMaskIntoConstraints = false + + constraintEqual(anchor: primaryButton.leftAnchor, toAnchor: self.leftAnchor) + constraintEqual(anchor: primaryButton.rightAnchor, toAnchor: self.rightAnchor) + constraintEqual(anchor: primaryButton.bottomAnchor, toAnchor: self.bottomAnchor) + primaryButton.translatesAutoresizingMaskIntoConstraints = false + + label.text = message + label.textAlignment = .center + label.numberOfLines = 3 + label.font = mediumSystemFont(size: 16) + primaryButton.title = "Retry".i18n(key: "com.auth0.lock.submit.retry", comment: "Retry") + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func apply(style: Style) { + self.primaryButton?.apply(style: style) + } +} diff --git a/Lock/ConnectionLoadingPresenter.swift b/Lock/ConnectionLoadingPresenter.swift index 626768274..543ac19d2 100644 --- a/Lock/ConnectionLoadingPresenter.swift +++ b/Lock/ConnectionLoadingPresenter.swift @@ -26,14 +26,22 @@ class ConnectionLoadingPresenter: Presentable, Loggable { var messagePresenter: MessagePresenter? let loader: RemoteConnectionLoader let navigator: Navigable + let options: Options - init(loader: RemoteConnectionLoader, navigator: Navigable) { + init(loader: RemoteConnectionLoader, navigator: Navigable, options: Options) { self.loader = loader self.navigator = navigator + self.options = options } var view: View { - self.loader.load { connections in + self.loader.load { connections, error in + guard error == nil || !self.options.presentCriticalErrors else { + Queue.main.async { + self.navigator.navigate(.connectionError(error: error!)) + } + return + } guard let connections = connections, !connections.isEmpty else { return self.navigator.exit(withError: UnrecoverableError.clientWithNoConnections) } Queue.main.async { self.logger.debug("Loaded connections. Moving to root view") diff --git a/Lock/LockOptions.swift b/Lock/LockOptions.swift index cb707ff27..904b6633e 100644 --- a/Lock/LockOptions.swift +++ b/Lock/LockOptions.swift @@ -25,6 +25,7 @@ import Auth0 struct LockOptions: OptionBuildable { var closable: Bool = false + var presentCriticalErrors: Bool = true var termsOfServiceURL: URL = URL(string: "https://auth0.com/terms")! var privacyPolicyURL: URL = URL(string: "https://auth0.com/privacy")! var logLevel: LoggerLevel = .off diff --git a/Lock/OptionBuildable.swift b/Lock/OptionBuildable.swift index 00e53799c..ad532cd33 100644 --- a/Lock/OptionBuildable.swift +++ b/Lock/OptionBuildable.swift @@ -30,6 +30,9 @@ public protocol OptionBuildable: Options { /// Allows Lock to be dismissed. By default is false. var closable: Bool { get set } + /// Allows Lock to present critical errors. By default is true + var presentCriticalErrors: Bool { get set } + /// ToS URL. By default is Auth0's. var termsOfServiceURL: URL { get set } diff --git a/Lock/Options.swift b/Lock/Options.swift index 8f95fae0f..a1cc0c6aa 100644 --- a/Lock/Options.swift +++ b/Lock/Options.swift @@ -24,6 +24,7 @@ import Foundation public protocol Options { var closable: Bool { get } + var presentCriticalErrors: Bool { get } var termsOfServiceURL: URL { get } var privacyPolicyURL: URL { get } diff --git a/Lock/RemoteConnectionLoader.swift b/Lock/RemoteConnectionLoader.swift index 670d5f06c..db7064a14 100644 --- a/Lock/RemoteConnectionLoader.swift +++ b/Lock/RemoteConnectionLoader.swift @@ -23,7 +23,5 @@ import Foundation protocol RemoteConnectionLoader { - - func load(_ callback: @escaping (Connections?) -> ()) - + func load(_ callback: @escaping (Connections?, RemoteConnectionLoaderError?) -> ()) } diff --git a/Lock/RemoteConnectionLoaderError.swift b/Lock/RemoteConnectionLoaderError.swift new file mode 100644 index 000000000..9ccdc247c --- /dev/null +++ b/Lock/RemoteConnectionLoaderError.swift @@ -0,0 +1,49 @@ +// ConnectionLoadingError.swift +// +// Copyright (c) 2017 Auth0 (http://auth0.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Foundation + +enum RemoteConnectionLoaderError: Error, LocalizableError { + case connectionTimeout + case invalidClient + case invalidClientInfo + case noConnections + case requestIssue + case responseIssue + + var localizableMessage: String { + switch self { + case .invalidClient: + return "No client information found, please check your Auth0 client credentials.".i18n(key: "com.auth0.lock.critical.credentials", comment: "Invalid client credentials") + case .invalidClientInfo: + return "Unable to retrieve client information, please try again later.".i18n(key: "com.auth0.lock.critical.clientinfo", comment: "Invalid client info") + case .noConnections: + return "No connections available for this client, please check your Auth0 client setup.".i18n(key: "com.auth0.lock.critical.noconnections", comment: "No connections available.") + default: + return "Could not connect to the server, please try again later.".i18n(key: "com.auth0.lock.critical.timeout", comment: "Connection timeout") + } + } + + var userVisible: Bool { + return true + } +} diff --git a/Lock/Router.swift b/Lock/Router.swift index 4da1bf35c..cdc9e1b08 100644 --- a/Lock/Router.swift +++ b/Lock/Router.swift @@ -53,7 +53,7 @@ struct Router: Navigable { guard !connections.isEmpty else { self.lock.logger.debug("No connections configured. Loading client info from Auth0...") let interactor = CDNLoaderInteractor(baseURL: self.lock.authentication.url, clientId: self.lock.authentication.clientId) - return ConnectionLoadingPresenter(loader: interactor, navigator: self) + return ConnectionLoadingPresenter(loader: interactor, navigator: self, options: self.lock.options) } if let database = connections.database { guard self.lock.options.allow != [.ResetPassword] && self.lock.options.initialScreen != .resetPassword else { return forgotPassword } @@ -127,6 +127,11 @@ struct Router: Navigable { return presenter } + func connectionError(_ error: RemoteConnectionLoaderError) -> Presentable? { + let presenter = ConnectionErrorPresenter(error: error, navigator: self) + return presenter + } + var showBack: Bool { guard let routes = self.controller?.routes else { return false } return !routes.history.isEmpty @@ -170,6 +175,8 @@ struct Router: Navigable { presentable = self.multifactor case .enterpriseActiveAuth(let connection): presentable = self.EnterpriseActiveAuth(connection) + case .connectionError(let error): + presentable = self.connectionError(error) default: self.lock.logger.warn("Ignoring navigation \(route)") return diff --git a/Lock/Routes.swift b/Lock/Routes.swift index 548c7b901..866ebea22 100644 --- a/Lock/Routes.swift +++ b/Lock/Routes.swift @@ -49,6 +49,7 @@ enum Route: Equatable { case forgotPassword case multifactor case enterpriseActiveAuth(connection: EnterpriseConnection) + case connectionError(error: RemoteConnectionLoaderError) func title(withStyle style: Style) -> String? { switch self { @@ -58,7 +59,7 @@ enum Route: Equatable { return "Two Step Verification".i18n(key: "com.auth0.lock.multifactor.title", comment: "Multifactor title") case .enterpriseActiveAuth: return "Corporate Login".i18n(key: "com.auth0.lock.corporate.title", comment: "Corporate Login title") - case .root: + case .root, .connectionError: return style.hideTitle ? nil : style.title } } @@ -70,6 +71,8 @@ func == (lhs: Route, rhs: Route) -> Bool { return true case (.enterpriseActiveAuth(let lhsConnection), .enterpriseActiveAuth(let rhsConnection)): return lhsConnection.name == rhsConnection.name + case (.connectionError(let lhsError), .connectionError(let rhsError)): + return lhsError == rhsError default: return false } diff --git a/LockTests/Interactors/CDNLoaderInteractorSpec.swift b/LockTests/Interactors/CDNLoaderInteractorSpec.swift index c1f9d3acd..835d5ec10 100644 --- a/LockTests/Interactors/CDNLoaderInteractorSpec.swift +++ b/LockTests/Interactors/CDNLoaderInteractorSpec.swift @@ -63,12 +63,15 @@ class CDNLoaderInteractorSpec: QuickSpec { var loader: CDNLoaderInteractor! var connections: Connections? - var callback: ((Connections?) -> ())! + var error: RemoteConnectionLoaderError? + var callback: ((Connections?, RemoteConnectionLoaderError?) -> ())! beforeEach { loader = CDNLoaderInteractor(baseURL: URL(string: "https://overmind.auth0.com")!, clientId: clientId) - callback = { connections = $0 } + callback = { connections = $0 + error = $1 } connections = nil + error = nil } context("failure") { @@ -101,6 +104,33 @@ class CDNLoaderInteractorSpec: QuickSpec { } } + context("remote connection errors") { + + it("should return invalid client error") { + stub(condition: isCDN(forClientId: clientId)) { _ in OHHTTPStubsResponse(data: Data(), statusCode: 403, headers: [:]) } + loader.load(callback) + expect(error).toEventually(beError(error: RemoteConnectionLoaderError.invalidClient)) + } + + it("should return invalid client info error") { + stub(condition: isCDN(forClientId: clientId)) { _ in OHHTTPStubsResponse(data: "not a json object".data(using: String.Encoding.utf8)!, statusCode: 200, headers: [:]) } + loader.load(callback) + expect(error).toEventually(beError(error: RemoteConnectionLoaderError.invalidClientInfo)) + } + + it("should return connection timeout error") { + loader.load(callback) + expect(error).toEventually(beError(error: RemoteConnectionLoaderError.connectionTimeout)) + } + + it("should return no connections error") { + stub(condition: isCDN(forClientId: clientId)) { _ in Auth0Stubs.strategiesFromCDN([[:]]) } + loader.load(callback) + expect(error).toEventually(beError(error: RemoteConnectionLoaderError.noConnections)) + } + + } + let databaseConnection = "DB Connection" it("should load empty strategies") { diff --git a/LockTests/OptionsSpec.swift b/LockTests/OptionsSpec.swift index 94caec5ab..c4550b9d3 100644 --- a/LockTests/OptionsSpec.swift +++ b/LockTests/OptionsSpec.swift @@ -41,6 +41,10 @@ class OptionsSpec: QuickSpec { expect(options.closable) == false } + it("should present critical errors") { + expect(options.presentCriticalErrors) == true + } + it("should have Auth0 tos as String") { expect(options.termsOfService) == "https://auth0.com/terms" } @@ -133,6 +137,11 @@ class OptionsSpec: QuickSpec { expect(options.closable) == true } + it("should set presentable critical errors") { + options.presentCriticalErrors = false + expect(options.presentCriticalErrors) == false + } + it("should set OIDC Conformant") { options.oidcConformant = true expect(options.oidcConformant) == true diff --git a/LockTests/Presenters/ConnectionErrorPresenterSpec.swift b/LockTests/Presenters/ConnectionErrorPresenterSpec.swift new file mode 100644 index 000000000..8cde32ec6 --- /dev/null +++ b/LockTests/Presenters/ConnectionErrorPresenterSpec.swift @@ -0,0 +1,70 @@ +// ConnectionErrorPresenterSpec.swift + +// +// Copyright (c) 2016 Auth0 (http://auth0.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Quick +import Nimble + +@testable import Lock + +class ConnectionErrorPresenterSpec: QuickSpec { + + override func spec() { + + var presenter: ConnectionErrorPresenter! + var navigator: MockNavigator! + var error: RemoteConnectionLoaderError! + var view: ConnectionErrorView! + + beforeEach { + error = RemoteConnectionLoaderError.connectionTimeout + navigator = MockNavigator() + presenter = ConnectionErrorPresenter(error: error, navigator: navigator) + view = presenter.view as? ConnectionErrorView + } + + describe("init") { + + it("should build ConnectionErrorView") { + expect(presenter.view as? ConnectionErrorView).toNot(beNil()) + } + + it("should have button title") { + expect(view.primaryButton?.title) == "Retry" + } + + it("should diplay relevant error message") { + expect(view.label?.text) == error.localizableMessage + } + } + + describe("action") { + + it("should trigger retry on button press") { + view.primaryButton?.onPress(view.primaryButton!) + expect(navigator.route) == Route.root + } + + } + + } +} diff --git a/LockTests/Presenters/ConnectionLoadingPresenterSpec.swift b/LockTests/Presenters/ConnectionLoadingPresenterSpec.swift index ed57b8505..7bd8efeba 100644 --- a/LockTests/Presenters/ConnectionLoadingPresenterSpec.swift +++ b/LockTests/Presenters/ConnectionLoadingPresenterSpec.swift @@ -33,12 +33,14 @@ class ConnectionLoadingPresenterSpec: QuickSpec { var presenter: ConnectionLoadingPresenter! var messagePresenter: MockMessagePresenter! var navigator: MockNavigator! + var options: OptionBuildable! beforeEach { + options = LockOptions() messagePresenter = MockMessagePresenter() interactor = MockConnectionsLoader() navigator = MockNavigator() - presenter = ConnectionLoadingPresenter(loader: interactor, navigator: navigator) + presenter = ConnectionLoadingPresenter(loader: interactor, navigator: navigator, options: options) presenter.messagePresenter = messagePresenter } diff --git a/LockTests/RemoteConnectionLoaderErrorSpec.swift b/LockTests/RemoteConnectionLoaderErrorSpec.swift new file mode 100644 index 000000000..2e9f48760 --- /dev/null +++ b/LockTests/RemoteConnectionLoaderErrorSpec.swift @@ -0,0 +1,66 @@ +// RemoteConnectionLoaderErrorSpec.swift +// +// Copyright (c) 2017 Auth0 (http://auth0.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Quick +import Nimble +@testable import Lock + +class RemoteConnectionLoaderErrorSpec: QuickSpec { + + override func spec() { + + describe("localised message response") { + + it(".invalidClient should return relevant string") { + let error = RemoteConnectionLoaderError.invalidClient + expect(error.localizableMessage).to(contain("No client information found")) + } + + it(".invalidClientInfo should return relevant string") { + let error = RemoteConnectionLoaderError.invalidClientInfo + expect(error.localizableMessage).to(contain("Unable to retrieve client information")) + } + + it(".noConnections should return relevant string") { + let error = RemoteConnectionLoaderError.noConnections + expect(error.localizableMessage).to(contain("No connections available")) + } + + it(".connectionTimeout should return default string") { + let error = RemoteConnectionLoaderError.connectionTimeout + expect(error.localizableMessage).to(contain("Could not connect to the server")) + } + + it(".requestIssue should return default string") { + let error = RemoteConnectionLoaderError.requestIssue + expect(error.localizableMessage).to(contain("Could not connect to the server")) + } + + it(".responseIssue should return default string") { + let error = RemoteConnectionLoaderError.responseIssue + expect(error.localizableMessage).to(contain("Could not connect to the server")) + } + + } + + } +} diff --git a/LockTests/Router/RouterSpec.swift b/LockTests/Router/RouterSpec.swift index cc5fa17b6..9ba86bab9 100644 --- a/LockTests/Router/RouterSpec.swift +++ b/LockTests/Router/RouterSpec.swift @@ -245,6 +245,11 @@ class RouterSpec: QuickSpec { expect(controller.headerView.title) == "Corporate Login" } + it("should show connection error screen") { + router.navigate(.connectionError(error: RemoteConnectionLoaderError.connectionTimeout)) + expect(controller.presentable as? ConnectionErrorPresenter).toNot(beNil()) + } + context("no connection") { beforeEach { lock = Lock(authentication: Auth0.authentication(clientId: "CLIENT_ID", domain: "samples.auth0.com"), webAuth: MockWebAuth()) @@ -347,6 +352,12 @@ class RouterSpec: QuickSpec { expect(match).to(beTrue()) } + it("connectionError should should be equatable with connectionError") { + let error = RemoteConnectionLoaderError.connectionTimeout + let match = Route.connectionError(error: error) == Route.connectionError(error: error) + expect(match).to(beTrue()) + } + it("root should should not be equatable with Multifactor") { let match = Route.root == Route.multifactor expect(match).to(beFalse()) diff --git a/LockTests/Utils/Mocks.swift b/LockTests/Utils/Mocks.swift index e787d87c6..848ce188e 100644 --- a/LockTests/Utils/Mocks.swift +++ b/LockTests/Utils/Mocks.swift @@ -205,9 +205,10 @@ class MockDBInteractor: DatabaseAuthenticatable, DatabaseUserCreator { class MockConnectionsLoader: RemoteConnectionLoader { var connections: Connections? = nil + var error: RemoteConnectionLoaderError? = nil - func load(_ callback: @escaping (Connections?) -> ()) { - callback(connections) + func load(_ callback: @escaping (Connections?, RemoteConnectionLoaderError?) -> ()) { + callback(connections, error) } } From 5b9fefec26ef0e753f9313ed6a1cfb230fb05a1d Mon Sep 17 00:00:00 2001 From: Martin Walsh Date: Wed, 11 Jan 2017 13:29:33 +0000 Subject: [PATCH 2/3] Unified RemoteConnectionError into UnrecoverableError Soft errors now default, option removed. Added more tests, updated existing tests General code style improvements --- Lock.xcodeproj/project.pbxproj | 44 ++++----- Lock/CDNLoaderInteractor.swift | 38 +++---- Lock/ConnectionLoadingPresenter.swift | 9 +- Lock/Lock.swift | 7 -- Lock/LockOptions.swift | 1 - Lock/OptionBuildable.swift | 3 - Lock/Options.swift | 1 - Lock/RemoteConnectionLoader.swift | 2 +- Lock/RemoteConnectionLoaderError.swift | 49 --------- Lock/Router.swift | 8 +- Lock/Routes.swift | 6 +- Lock/UnrecoverableError.swift | 68 +++++++++++++ ...wift => UnrecoverableErrorPresenter.swift} | 10 +- ...iew.swift => UnrecoverableErrorView.swift} | 4 +- .../Interactors/CDNLoaderInteractorSpec.swift | 16 +-- LockTests/OptionsSpec.swift | 9 -- ... => UnrecoverableErrorPresenterSpec.swift} | 20 ++-- .../RemoteConnectionLoaderErrorSpec.swift | 66 ------------- LockTests/Router/RouterSpec.swift | 10 +- LockTests/UnrecoverableErrorSpec.swift | 99 +++++++++++++++++++ LockTests/Utils/Mocks.swift | 6 +- 21 files changed, 248 insertions(+), 228 deletions(-) delete mode 100644 Lock/RemoteConnectionLoaderError.swift create mode 100644 Lock/UnrecoverableError.swift rename Lock/{ConnectionErrorPresenter.swift => UnrecoverableErrorPresenter.swift} (83%) rename Lock/{ConnectionErrorView.swift => UnrecoverableErrorView.swift} (97%) rename LockTests/Presenters/{ConnectionErrorPresenterSpec.swift => UnrecoverableErrorPresenterSpec.swift} (76%) delete mode 100644 LockTests/RemoteConnectionLoaderErrorSpec.swift create mode 100644 LockTests/UnrecoverableErrorSpec.swift diff --git a/Lock.xcodeproj/project.pbxproj b/Lock.xcodeproj/project.pbxproj index a408b396a..172b4bdce 100644 --- a/Lock.xcodeproj/project.pbxproj +++ b/Lock.xcodeproj/project.pbxproj @@ -19,11 +19,11 @@ 5B4DE0151DD66DE1004C8AC2 /* EnterpriseDomainInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B4DE0141DD66DE1004C8AC2 /* EnterpriseDomainInteractor.swift */; }; 5B4DE0171DD67064004C8AC2 /* EnterpriseActiveAuthPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B4DE0161DD67064004C8AC2 /* EnterpriseActiveAuthPresenter.swift */; }; 5B4DE0191DD670F7004C8AC2 /* EnterpriseActiveAuthView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B4DE0181DD670F7004C8AC2 /* EnterpriseActiveAuthView.swift */; }; - 5B54CDC41E25200900F0AFFF /* ConnectionErrorPresenterSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B54CDC31E25200900F0AFFF /* ConnectionErrorPresenterSpec.swift */; }; - 5B55F3C91E24273D00B75CF5 /* ConnectionErrorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B55F3C81E24273D00B75CF5 /* ConnectionErrorView.swift */; }; - 5B55F3CB1E242A2E00B75CF5 /* ConnectionErrorPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B55F3CA1E242A2E00B75CF5 /* ConnectionErrorPresenter.swift */; }; - 5B55F3D11E244B8700B75CF5 /* RemoteConnectionLoaderError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B55F3D01E244B8700B75CF5 /* RemoteConnectionLoaderError.swift */; }; - 5B55F3D41E24FFD000B75CF5 /* RemoteConnectionLoaderErrorSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B55F3D21E24F3F000B75CF5 /* RemoteConnectionLoaderErrorSpec.swift */; }; + 5B54CDC41E25200900F0AFFF /* UnrecoverableErrorPresenterSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B54CDC31E25200900F0AFFF /* UnrecoverableErrorPresenterSpec.swift */; }; + 5B55F3C91E24273D00B75CF5 /* UnrecoverableErrorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B55F3C81E24273D00B75CF5 /* UnrecoverableErrorView.swift */; }; + 5B55F3CB1E242A2E00B75CF5 /* UnrecoverableErrorPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B55F3CA1E242A2E00B75CF5 /* UnrecoverableErrorPresenter.swift */; }; + 5B55F3D11E244B8700B75CF5 /* UnrecoverableError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B55F3D01E244B8700B75CF5 /* UnrecoverableError.swift */; }; + 5B55F3D41E24FFD000B75CF5 /* UnrecoverableErrorSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B55F3D21E24F3F000B75CF5 /* UnrecoverableErrorSpec.swift */; }; 5B6631531DDB9B28001CB043 /* EnterpriseDomainInteractorSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6631511DDB990B001CB043 /* EnterpriseDomainInteractorSpec.swift */; }; 5BA563F11DD117550002D3AB /* EnterpriseActiveAuthInteractorSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA563EF1DD1171F0002D3AB /* EnterpriseActiveAuthInteractorSpec.swift */; }; 5BB4A7C11DF9A38E008E8C37 /* DatabaseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BB4A7C01DF9A38E008E8C37 /* DatabaseView.swift */; }; @@ -214,11 +214,11 @@ 5B4DE0141DD66DE1004C8AC2 /* EnterpriseDomainInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = EnterpriseDomainInteractor.swift; path = Lock/EnterpriseDomainInteractor.swift; sourceTree = SOURCE_ROOT; }; 5B4DE0161DD67064004C8AC2 /* EnterpriseActiveAuthPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = EnterpriseActiveAuthPresenter.swift; path = Lock/EnterpriseActiveAuthPresenter.swift; sourceTree = SOURCE_ROOT; }; 5B4DE0181DD670F7004C8AC2 /* EnterpriseActiveAuthView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = EnterpriseActiveAuthView.swift; path = Lock/EnterpriseActiveAuthView.swift; sourceTree = SOURCE_ROOT; }; - 5B54CDC31E25200900F0AFFF /* ConnectionErrorPresenterSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionErrorPresenterSpec.swift; sourceTree = ""; }; - 5B55F3C81E24273D00B75CF5 /* ConnectionErrorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionErrorView.swift; sourceTree = ""; }; - 5B55F3CA1E242A2E00B75CF5 /* ConnectionErrorPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionErrorPresenter.swift; sourceTree = ""; }; - 5B55F3D01E244B8700B75CF5 /* RemoteConnectionLoaderError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RemoteConnectionLoaderError.swift; sourceTree = ""; }; - 5B55F3D21E24F3F000B75CF5 /* RemoteConnectionLoaderErrorSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RemoteConnectionLoaderErrorSpec.swift; sourceTree = ""; }; + 5B54CDC31E25200900F0AFFF /* UnrecoverableErrorPresenterSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnrecoverableErrorPresenterSpec.swift; sourceTree = ""; }; + 5B55F3C81E24273D00B75CF5 /* UnrecoverableErrorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnrecoverableErrorView.swift; sourceTree = ""; }; + 5B55F3CA1E242A2E00B75CF5 /* UnrecoverableErrorPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = UnrecoverableErrorPresenter.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; + 5B55F3D01E244B8700B75CF5 /* UnrecoverableError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnrecoverableError.swift; sourceTree = ""; }; + 5B55F3D21E24F3F000B75CF5 /* UnrecoverableErrorSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnrecoverableErrorSpec.swift; sourceTree = ""; }; 5B6631511DDB990B001CB043 /* EnterpriseDomainInteractorSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnterpriseDomainInteractorSpec.swift; sourceTree = ""; }; 5BA563EF1DD1171F0002D3AB /* EnterpriseActiveAuthInteractorSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnterpriseActiveAuthInteractorSpec.swift; sourceTree = ""; }; 5BB4A7C01DF9A38E008E8C37 /* DatabaseView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatabaseView.swift; sourceTree = ""; }; @@ -231,7 +231,7 @@ 5F1C498F1D8360BF005B74FC /* ConnectionLoadingPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ConnectionLoadingPresenter.swift; path = Lock/ConnectionLoadingPresenter.swift; sourceTree = SOURCE_ROOT; }; 5F1C49921D8360DF005B74FC /* LoadingView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = LoadingView.swift; path = Lock/LoadingView.swift; sourceTree = SOURCE_ROOT; }; 5F1C49941D8360F5005B74FC /* RemoteConnectionLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = RemoteConnectionLoader.swift; path = Lock/RemoteConnectionLoader.swift; sourceTree = SOURCE_ROOT; }; - 5F1C49951D8360F5005B74FC /* CDNLoaderInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CDNLoaderInteractor.swift; path = Lock/CDNLoaderInteractor.swift; sourceTree = SOURCE_ROOT; }; + 5F1C49951D8360F5005B74FC /* CDNLoaderInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; name = CDNLoaderInteractor.swift; path = Lock/CDNLoaderInteractor.swift; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 5F1C499A1D836190005B74FC /* CustomTextField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CustomTextField.swift; path = Lock/CustomTextField.swift; sourceTree = SOURCE_ROOT; }; 5F2037C11D5D02880005D2E2 /* Matchers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Matchers.swift; sourceTree = ""; }; 5F2496AE1D66210500A1C6E2 /* LockViewControllerSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LockViewControllerSpec.swift; sourceTree = ""; }; @@ -321,7 +321,7 @@ 5FDB41CD1D2C79FD00166B67 /* Operations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Operations.swift; path = Lock/Operations.swift; sourceTree = SOURCE_ROOT; }; 5FDB41CF1D2C95B100166B67 /* MessageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MessageView.swift; path = Lock/MessageView.swift; sourceTree = SOURCE_ROOT; }; 5FDC876C1D46DAF200D28596 /* Queue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Queue.swift; path = Lock/Queue.swift; sourceTree = SOURCE_ROOT; }; - 5FE50DBC1D79B8AD00D82290 /* CDNLoaderInteractorSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CDNLoaderInteractorSpec.swift; sourceTree = ""; }; + 5FE50DBC1D79B8AD00D82290 /* CDNLoaderInteractorSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = CDNLoaderInteractorSpec.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 5FE50DBE1D7A254000D82290 /* ConnectionLoadingPresenterSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionLoadingPresenterSpec.swift; sourceTree = ""; }; 5FE50DC01D7DED8C00D82290 /* OfflineConnectionsSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OfflineConnectionsSpec.swift; sourceTree = ""; }; 5FEADCF41D1A7EBC0032D810 /* LockApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = LockApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -387,7 +387,7 @@ 5B0CF2D41DE9ADF900F82BF4 /* InputValidationErrorSpec.swift */, 5B0CF2D71DE9B14000F82BF4 /* DatabaseAuthenticatableErrorSpec.swift */, 5B0CF2DC1DE9B47C00F82BF4 /* DatabaseUserCreatorErrorSpec.swift */, - 5B55F3D21E24F3F000B75CF5 /* RemoteConnectionLoaderErrorSpec.swift */, + 5B55F3D21E24F3F000B75CF5 /* UnrecoverableErrorSpec.swift */, ); name = Errors; sourceTree = ""; @@ -403,7 +403,7 @@ isa = PBXGroup; children = ( 5F2496B51D665AA800A1C6E2 /* InputValidationError.swift */, - 5B55F3D01E244B8700B75CF5 /* RemoteConnectionLoaderError.swift */, + 5B55F3D01E244B8700B75CF5 /* UnrecoverableError.swift */, 5F2496B91D665AE900A1C6E2 /* DatabaseAuthenticatableError.swift */, 5F2496BB1D665AF700A1C6E2 /* DatabaseUserCreatorError.swift */, ); @@ -463,7 +463,7 @@ children = ( 5BB4A7C01DF9A38E008E8C37 /* DatabaseView.swift */, 5F1C49921D8360DF005B74FC /* LoadingView.swift */, - 5B55F3C81E24273D00B75CF5 /* ConnectionErrorView.swift */, + 5B55F3C81E24273D00B75CF5 /* UnrecoverableErrorView.swift */, 5F73CDD31D3073BE00D8D8D1 /* DatabaseForgotPasswordView.swift */, 5B0971811DC8FAC5003AA88F /* EnterpriseDomainView.swift */, 5B4DE0181DD670F7004C8AC2 /* EnterpriseActiveAuthView.swift */, @@ -495,7 +495,7 @@ 5FBE5CC91D3EA1380038536D /* MultifactorPresenterSpec.swift */, 5F57DFCD1D4FBE5A00C54DA8 /* AuthPresenterSpec.swift */, 5FE50DBE1D7A254000D82290 /* ConnectionLoadingPresenterSpec.swift */, - 5B54CDC31E25200900F0AFFF /* ConnectionErrorPresenterSpec.swift */, + 5B54CDC31E25200900F0AFFF /* UnrecoverableErrorPresenterSpec.swift */, 5FB06A411E1329FA00E62F36 /* BannerMessagePresenterSpec.swift */, ); path = Presenters; @@ -595,7 +595,7 @@ isa = PBXGroup; children = ( 5F1C498F1D8360BF005B74FC /* ConnectionLoadingPresenter.swift */, - 5B55F3CA1E242A2E00B75CF5 /* ConnectionErrorPresenter.swift */, + 5B55F3CA1E242A2E00B75CF5 /* UnrecoverableErrorPresenter.swift */, 5B09717F1DC8F5C4003AA88F /* EnterpriseDomainPresenter.swift */, 5F73CDD51D30790500D8D8D1 /* DatabaseForgotPasswordPresenter.swift */, 5B4DE0161DD67064004C8AC2 /* EnterpriseActiveAuthPresenter.swift */, @@ -976,7 +976,7 @@ 5F1C499B1D836190005B74FC /* CustomTextField.swift in Sources */, 5BB4A7C11DF9A38E008E8C37 /* DatabaseView.swift in Sources */, 5FB06A401E131D0300E62F36 /* BannerMessagePresenter.swift in Sources */, - 5B55F3D11E244B8700B75CF5 /* RemoteConnectionLoaderError.swift in Sources */, + 5B55F3D11E244B8700B75CF5 /* UnrecoverableError.swift in Sources */, 5F70F1E71D790773004698DA /* OptionBuildable.swift in Sources */, 5FBE5CB81D3D8F030038536D /* User.swift in Sources */, 5FF0B2281E1726C400A73257 /* CredentialAuth.swift in Sources */, @@ -999,7 +999,7 @@ 5B0971801DC8F5C4003AA88F /* EnterpriseDomainPresenter.swift in Sources */, 5FC434861D1DF769005188BC /* View.swift in Sources */, 5FDB41CE1D2C79FD00166B67 /* Operations.swift in Sources */, - 5B55F3C91E24273D00B75CF5 /* ConnectionErrorView.swift in Sources */, + 5B55F3C91E24273D00B75CF5 /* UnrecoverableErrorView.swift in Sources */, 5B09717C1DC8F229003AA88F /* EnterpriseDomain.swift in Sources */, 5F51EE681D1C88FC0024BCD6 /* SignUpView.swift in Sources */, 5F57DFD41D4FE64700C54DA8 /* Auth0OAuth2Interactor.swift in Sources */, @@ -1012,7 +1012,7 @@ 5F70F1E11D790500004698DA /* Connections.swift in Sources */, 5F92C68F1D50EAC200CCE6C0 /* LazyImage.swift in Sources */, 5F2496BA1D665AE900A1C6E2 /* DatabaseAuthenticatableError.swift in Sources */, - 5B55F3CB1E242A2E00B75CF5 /* ConnectionErrorPresenter.swift in Sources */, + 5B55F3CB1E242A2E00B75CF5 /* UnrecoverableErrorPresenter.swift in Sources */, 5F57DFC61D4F79DD00C54DA8 /* AuthStyle.swift in Sources */, 5B0971821DC8FAC5003AA88F /* EnterpriseDomainView.swift in Sources */, 5B4DE0191DD670F7004C8AC2 /* EnterpriseActiveAuthView.swift in Sources */, @@ -1060,7 +1060,7 @@ 5F92C68B1D4FE90F00CCE6C0 /* Auth0OAuth2InteractorSpec.swift in Sources */, 5F5090081D1DE7BA00EAA650 /* NetworkStub.swift in Sources */, 5F0FCF921E201CF300E3D53B /* ObserverStoreSpec.swift in Sources */, - 5B55F3D41E24FFD000B75CF5 /* RemoteConnectionLoaderErrorSpec.swift in Sources */, + 5B55F3D41E24FFD000B75CF5 /* UnrecoverableErrorSpec.swift in Sources */, 5F73CDDC1D309BE900D8D8D1 /* DatabasePasswordInteractorSpec.swift in Sources */, 5F2496AF1D66210500A1C6E2 /* LockViewControllerSpec.swift in Sources */, 5FA250501D48E2A200C544FA /* OptionsSpec.swift in Sources */, @@ -1070,7 +1070,7 @@ 5F508FFB1D1DB1E700EAA650 /* DatabaseInteractorSpec.swift in Sources */, 5F5F98D41D21E3890016FC22 /* DatabasePresenterSpec.swift in Sources */, 5FA250521D48F08200C544FA /* LockSpec.swift in Sources */, - 5B54CDC41E25200900F0AFFF /* ConnectionErrorPresenterSpec.swift in Sources */, + 5B54CDC41E25200900F0AFFF /* UnrecoverableErrorPresenterSpec.swift in Sources */, 5F5F98DC1D22F0B40016FC22 /* RouterSpec.swift in Sources */, 5FBE5CCD1D3EDF960038536D /* UserSpec.swift in Sources */, 5BCDE1361DDDF17F00AA2A6C /* EnterpriseActiveAuthPresenterSpec.swift in Sources */, diff --git a/Lock/CDNLoaderInteractor.swift b/Lock/CDNLoaderInteractor.swift index 56eb6d499..68bacdc20 100644 --- a/Lock/CDNLoaderInteractor.swift +++ b/Lock/CDNLoaderInteractor.swift @@ -33,25 +33,18 @@ struct CDNLoaderInteractor: RemoteConnectionLoader, Loggable { self.url = URL(string: "client/\(clientId).js", relativeTo: cdnURL(from: baseURL))! } - func load(_ callback: @escaping (Connections?, RemoteConnectionLoaderError?) -> ()) { - let sessionConfiguration = URLSessionConfiguration.default - sessionConfiguration.timeoutIntervalForRequest = 30 // Default 60 - let session = URLSession(configuration: sessionConfiguration) - + func load(_ callback: @escaping (UnrecoverableError?, Connections?) -> ()) { self.logger.info("Loading client info from \(self.url)") - let task = session.dataTask(with: self.url, completionHandler: { (data, response, error) in + let task = URLSession.shared.dataTask(with: self.url, completionHandler: { (data, response, error) in + guard error?._code != NSURLErrorTimedOut else { return callback(.connectionTimeout, nil) } guard error == nil else { self.logger.error("Failed to load with error \(error!)") - if let error = error, error._code == NSURLErrorTimedOut { - callback(nil, .connectionTimeout) - } else { - callback(nil, .requestIssue) - } - return + return callback(.requestIssue, nil) } + guard let response = response as? HTTPURLResponse else { self.logger.error("Response was not NSHTTURLResponse") - return callback(nil, RemoteConnectionLoaderError.responseIssue) + return callback(.requestIssue, nil) } let payload: String? @@ -60,17 +53,16 @@ struct CDNLoaderInteractor: RemoteConnectionLoader, Loggable { } else { payload = nil } + guard 200...299 ~= response.statusCode else { self.logger.error("HTTP response was not successful. HTTP \(response.statusCode) <\(payload ?? "No Body")>") - if response.statusCode == 403 { - return callback(nil, RemoteConnectionLoaderError.invalidClient) - } - return callback(nil, RemoteConnectionLoaderError.responseIssue) + guard response.statusCode != 403 else { return callback(.invalidClientOrDomain, nil) } + return callback(.requestIssue, nil) } guard var jsonp = payload else { self.logger.error("HTTP response had no jsonp \(payload ?? "No Body")") - return callback(nil, RemoteConnectionLoaderError.invalidClientInfo) + return callback(.invalidClientOrDomain, nil) } self.logger.verbose("Received jsonp \(jsonp)") @@ -104,14 +96,12 @@ struct CDNLoaderInteractor: RemoteConnectionLoader, Loggable { info.oauth2.forEach { strategy in strategy.connections.forEach { connections.social(name: $0.name, style: AuthStyle.style(forStrategy: strategy.name, connectionName: $0.name)) } } - if connections.isEmpty { - callback(connections, .noConnections) - } else { - callback(connections, nil) - } + + guard !connections.isEmpty else { return callback(.clientWithNoConnections, connections) } + callback(nil, connections) } catch let e { self.logger.error("Failed to parse \(jsonp) with error \(e)") - return callback(nil, .invalidClientInfo) + return callback(.invalidClientOrDomain, nil) } }) task.resume() diff --git a/Lock/ConnectionLoadingPresenter.swift b/Lock/ConnectionLoadingPresenter.swift index 543ac19d2..543b1e5db 100644 --- a/Lock/ConnectionLoadingPresenter.swift +++ b/Lock/ConnectionLoadingPresenter.swift @@ -35,12 +35,11 @@ class ConnectionLoadingPresenter: Presentable, Loggable { } var view: View { - self.loader.load { connections, error in - guard error == nil || !self.options.presentCriticalErrors else { - Queue.main.async { - self.navigator.navigate(.connectionError(error: error!)) + self.loader.load { error, connections in + guard error == nil else { + return Queue.main.async { + self.navigator.navigate(.unrecoverableError(error: error!)) } - return } guard let connections = connections, !connections.isEmpty else { return self.navigator.exit(withError: UnrecoverableError.clientWithNoConnections) } Queue.main.async { diff --git a/Lock/Lock.swift b/Lock/Lock.swift index 1320fd2b4..5e3ba4238 100644 --- a/Lock/Lock.swift +++ b/Lock/Lock.swift @@ -259,13 +259,6 @@ struct ConnectionProvider { var connections: Connections { return local.select(byNames: allowed) } } -public enum UnrecoverableError: Error { - case invalidClientOrDomain - case clientWithNoConnections - case missingDatabaseConnection - case invalidOptions(cause: String) -} - private func telemetryFor(authentication: Authentication, webAuth: WebAuth) -> (Authentication, WebAuth) { var authentication = authentication var webAuth = webAuth diff --git a/Lock/LockOptions.swift b/Lock/LockOptions.swift index 904b6633e..cb707ff27 100644 --- a/Lock/LockOptions.swift +++ b/Lock/LockOptions.swift @@ -25,7 +25,6 @@ import Auth0 struct LockOptions: OptionBuildable { var closable: Bool = false - var presentCriticalErrors: Bool = true var termsOfServiceURL: URL = URL(string: "https://auth0.com/terms")! var privacyPolicyURL: URL = URL(string: "https://auth0.com/privacy")! var logLevel: LoggerLevel = .off diff --git a/Lock/OptionBuildable.swift b/Lock/OptionBuildable.swift index ad532cd33..00e53799c 100644 --- a/Lock/OptionBuildable.swift +++ b/Lock/OptionBuildable.swift @@ -30,9 +30,6 @@ public protocol OptionBuildable: Options { /// Allows Lock to be dismissed. By default is false. var closable: Bool { get set } - /// Allows Lock to present critical errors. By default is true - var presentCriticalErrors: Bool { get set } - /// ToS URL. By default is Auth0's. var termsOfServiceURL: URL { get set } diff --git a/Lock/Options.swift b/Lock/Options.swift index a1cc0c6aa..8f95fae0f 100644 --- a/Lock/Options.swift +++ b/Lock/Options.swift @@ -24,7 +24,6 @@ import Foundation public protocol Options { var closable: Bool { get } - var presentCriticalErrors: Bool { get } var termsOfServiceURL: URL { get } var privacyPolicyURL: URL { get } diff --git a/Lock/RemoteConnectionLoader.swift b/Lock/RemoteConnectionLoader.swift index db7064a14..aff9dd1d3 100644 --- a/Lock/RemoteConnectionLoader.swift +++ b/Lock/RemoteConnectionLoader.swift @@ -23,5 +23,5 @@ import Foundation protocol RemoteConnectionLoader { - func load(_ callback: @escaping (Connections?, RemoteConnectionLoaderError?) -> ()) + func load(_ callback: @escaping (UnrecoverableError?, Connections?) -> ()) } diff --git a/Lock/RemoteConnectionLoaderError.swift b/Lock/RemoteConnectionLoaderError.swift deleted file mode 100644 index 9ccdc247c..000000000 --- a/Lock/RemoteConnectionLoaderError.swift +++ /dev/null @@ -1,49 +0,0 @@ -// ConnectionLoadingError.swift -// -// Copyright (c) 2017 Auth0 (http://auth0.com) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import Foundation - -enum RemoteConnectionLoaderError: Error, LocalizableError { - case connectionTimeout - case invalidClient - case invalidClientInfo - case noConnections - case requestIssue - case responseIssue - - var localizableMessage: String { - switch self { - case .invalidClient: - return "No client information found, please check your Auth0 client credentials.".i18n(key: "com.auth0.lock.critical.credentials", comment: "Invalid client credentials") - case .invalidClientInfo: - return "Unable to retrieve client information, please try again later.".i18n(key: "com.auth0.lock.critical.clientinfo", comment: "Invalid client info") - case .noConnections: - return "No connections available for this client, please check your Auth0 client setup.".i18n(key: "com.auth0.lock.critical.noconnections", comment: "No connections available.") - default: - return "Could not connect to the server, please try again later.".i18n(key: "com.auth0.lock.critical.timeout", comment: "Connection timeout") - } - } - - var userVisible: Bool { - return true - } -} diff --git a/Lock/Router.swift b/Lock/Router.swift index cdc9e1b08..d4c30dba5 100644 --- a/Lock/Router.swift +++ b/Lock/Router.swift @@ -127,8 +127,8 @@ struct Router: Navigable { return presenter } - func connectionError(_ error: RemoteConnectionLoaderError) -> Presentable? { - let presenter = ConnectionErrorPresenter(error: error, navigator: self) + func unrecoverableError(_ error: UnrecoverableError) -> Presentable? { + let presenter = UnrecoverableErrorPresenter(error: error, navigator: self) return presenter } @@ -175,8 +175,8 @@ struct Router: Navigable { presentable = self.multifactor case .enterpriseActiveAuth(let connection): presentable = self.EnterpriseActiveAuth(connection) - case .connectionError(let error): - presentable = self.connectionError(error) + case .unrecoverableError(let error): + presentable = self.unrecoverableError(error) default: self.lock.logger.warn("Ignoring navigation \(route)") return diff --git a/Lock/Routes.swift b/Lock/Routes.swift index 866ebea22..198ac9f7a 100644 --- a/Lock/Routes.swift +++ b/Lock/Routes.swift @@ -49,7 +49,7 @@ enum Route: Equatable { case forgotPassword case multifactor case enterpriseActiveAuth(connection: EnterpriseConnection) - case connectionError(error: RemoteConnectionLoaderError) + case unrecoverableError(error: UnrecoverableError) func title(withStyle style: Style) -> String? { switch self { @@ -59,7 +59,7 @@ enum Route: Equatable { return "Two Step Verification".i18n(key: "com.auth0.lock.multifactor.title", comment: "Multifactor title") case .enterpriseActiveAuth: return "Corporate Login".i18n(key: "com.auth0.lock.corporate.title", comment: "Corporate Login title") - case .root, .connectionError: + case .root, .unrecoverableError: return style.hideTitle ? nil : style.title } } @@ -71,7 +71,7 @@ func == (lhs: Route, rhs: Route) -> Bool { return true case (.enterpriseActiveAuth(let lhsConnection), .enterpriseActiveAuth(let rhsConnection)): return lhsConnection.name == rhsConnection.name - case (.connectionError(let lhsError), .connectionError(let rhsError)): + case (.unrecoverableError(let lhsError), .unrecoverableError(let rhsError)): return lhsError == rhsError default: return false diff --git a/Lock/UnrecoverableError.swift b/Lock/UnrecoverableError.swift new file mode 100644 index 000000000..f6af669cd --- /dev/null +++ b/Lock/UnrecoverableError.swift @@ -0,0 +1,68 @@ +// ConnectionLoadingError.swift +// +// Copyright (c) 2017 Auth0 (http://auth0.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Foundation + +enum UnrecoverableError: Equatable, Error, LocalizableError { + case connectionTimeout + case invalidClientOrDomain + case clientWithNoConnections + case requestIssue + case missingDatabaseConnection + case invalidOptions(cause: String) + + var localizableMessage: String { + switch self { + case .invalidClientOrDomain: + return "No client information found, please check your Auth0 client credentials.".i18n(key: "com.auth0.lock.error.invalidclient", comment: "Invalid client") + case .clientWithNoConnections: + return "No connections available for this client, please check your Auth0 client setup.".i18n(key: "com.auth0.lock.error.noconnections", comment: "No client connections") + case .connectionTimeout, .requestIssue: + return "There was a problem with your request, please try again later.".i18n(key: "com.auth0.lock.error.requestfailed", comment: "Generic request failure") + case .missingDatabaseConnection: + return "No database connection was found, please check your Auth0 client setup".i18n(key: "com.auth0.lock.error.missingdatabase", comment: "No database connection") + case .invalidOptions(let cause): + return "Option configuration issue: \(cause)".i18n(key: "com.auth0.lock.error.options", comment: "Option configuration issue.") + } + } + + var userVisible: Bool { + switch self { + case .missingDatabaseConnection, .invalidOptions: + return false + default: + return true + } + } +} + +func == (lhs: UnrecoverableError, rhs: UnrecoverableError) -> Bool { + switch((lhs, rhs)) { + case (.connectionTimeout, .connectionTimeout), (.invalidClientOrDomain, .invalidClientOrDomain), (.clientWithNoConnections, .clientWithNoConnections), + (.requestIssue, .requestIssue), (.missingDatabaseConnection, .missingDatabaseConnection): + return true + case (.invalidOptions(let lhsCause), .invalidOptions(let rhsCause)): + return lhsCause == rhsCause + default: + return false + } +} diff --git a/Lock/ConnectionErrorPresenter.swift b/Lock/UnrecoverableErrorPresenter.swift similarity index 83% rename from Lock/ConnectionErrorPresenter.swift rename to Lock/UnrecoverableErrorPresenter.swift index f5228c74b..340395b4c 100644 --- a/Lock/ConnectionErrorPresenter.swift +++ b/Lock/UnrecoverableErrorPresenter.swift @@ -1,4 +1,4 @@ -// ConnectionErrorPresenter.swift +// UnrecoverableErrorPresenter.swift // // Copyright (c) 2016 Auth0 (http://auth0.com) // @@ -22,19 +22,19 @@ import Foundation -class ConnectionErrorPresenter: Presentable, Loggable { +class UnrecoverableErrorPresenter: Presentable, Loggable { let navigator: Navigable - let error: RemoteConnectionLoaderError + let error: UnrecoverableError var messagePresenter: MessagePresenter? - init(error: RemoteConnectionLoaderError, navigator: Navigable) { + init(error: UnrecoverableError, navigator: Navigable) { self.navigator = navigator self.error = error } var view: View { - let view = ConnectionErrorView(message: self.error.localizableMessage) + let view = UnrecoverableErrorView(message: self.error.localizableMessage) view.primaryButton?.onPress = { _ in self.navigator.navigate(.root) } diff --git a/Lock/ConnectionErrorView.swift b/Lock/UnrecoverableErrorView.swift similarity index 97% rename from Lock/ConnectionErrorView.swift rename to Lock/UnrecoverableErrorView.swift index df54f4118..cda47c60d 100644 --- a/Lock/ConnectionErrorView.swift +++ b/Lock/UnrecoverableErrorView.swift @@ -1,4 +1,4 @@ -// ConnectionErrorView.swift +// UnrecoverableErrorView.swift // // Copyright (c) 2017 Auth0 (http://auth0.com) // @@ -22,7 +22,7 @@ import UIKit -class ConnectionErrorView: UIView, View { +class UnrecoverableErrorView: UIView, View { weak var primaryButton: PrimaryButton? weak var label: UILabel? diff --git a/LockTests/Interactors/CDNLoaderInteractorSpec.swift b/LockTests/Interactors/CDNLoaderInteractorSpec.swift index 835d5ec10..7455ed4cb 100644 --- a/LockTests/Interactors/CDNLoaderInteractorSpec.swift +++ b/LockTests/Interactors/CDNLoaderInteractorSpec.swift @@ -63,13 +63,13 @@ class CDNLoaderInteractorSpec: QuickSpec { var loader: CDNLoaderInteractor! var connections: Connections? - var error: RemoteConnectionLoaderError? - var callback: ((Connections?, RemoteConnectionLoaderError?) -> ())! + var error: UnrecoverableError? + var callback: ((UnrecoverableError?, Connections?) -> ())! beforeEach { loader = CDNLoaderInteractor(baseURL: URL(string: "https://overmind.auth0.com")!, clientId: clientId) - callback = { connections = $0 - error = $1 } + callback = { error = $0 + connections = $1 } connections = nil error = nil } @@ -109,24 +109,24 @@ class CDNLoaderInteractorSpec: QuickSpec { it("should return invalid client error") { stub(condition: isCDN(forClientId: clientId)) { _ in OHHTTPStubsResponse(data: Data(), statusCode: 403, headers: [:]) } loader.load(callback) - expect(error).toEventually(beError(error: RemoteConnectionLoaderError.invalidClient)) + expect(error).toEventually(beError(error: UnrecoverableError.invalidClientOrDomain)) } it("should return invalid client info error") { stub(condition: isCDN(forClientId: clientId)) { _ in OHHTTPStubsResponse(data: "not a json object".data(using: String.Encoding.utf8)!, statusCode: 200, headers: [:]) } loader.load(callback) - expect(error).toEventually(beError(error: RemoteConnectionLoaderError.invalidClientInfo)) + expect(error).toEventually(beError(error: UnrecoverableError.invalidClientOrDomain)) } it("should return connection timeout error") { loader.load(callback) - expect(error).toEventually(beError(error: RemoteConnectionLoaderError.connectionTimeout)) + expect(error).toEventually(beError(error: UnrecoverableError.connectionTimeout)) } it("should return no connections error") { stub(condition: isCDN(forClientId: clientId)) { _ in Auth0Stubs.strategiesFromCDN([[:]]) } loader.load(callback) - expect(error).toEventually(beError(error: RemoteConnectionLoaderError.noConnections)) + expect(error).toEventually(beError(error: UnrecoverableError.clientWithNoConnections)) } } diff --git a/LockTests/OptionsSpec.swift b/LockTests/OptionsSpec.swift index c4550b9d3..94caec5ab 100644 --- a/LockTests/OptionsSpec.swift +++ b/LockTests/OptionsSpec.swift @@ -41,10 +41,6 @@ class OptionsSpec: QuickSpec { expect(options.closable) == false } - it("should present critical errors") { - expect(options.presentCriticalErrors) == true - } - it("should have Auth0 tos as String") { expect(options.termsOfService) == "https://auth0.com/terms" } @@ -137,11 +133,6 @@ class OptionsSpec: QuickSpec { expect(options.closable) == true } - it("should set presentable critical errors") { - options.presentCriticalErrors = false - expect(options.presentCriticalErrors) == false - } - it("should set OIDC Conformant") { options.oidcConformant = true expect(options.oidcConformant) == true diff --git a/LockTests/Presenters/ConnectionErrorPresenterSpec.swift b/LockTests/Presenters/UnrecoverableErrorPresenterSpec.swift similarity index 76% rename from LockTests/Presenters/ConnectionErrorPresenterSpec.swift rename to LockTests/Presenters/UnrecoverableErrorPresenterSpec.swift index 8cde32ec6..1083d398c 100644 --- a/LockTests/Presenters/ConnectionErrorPresenterSpec.swift +++ b/LockTests/Presenters/UnrecoverableErrorPresenterSpec.swift @@ -1,4 +1,4 @@ -// ConnectionErrorPresenterSpec.swift +// UnrecoverableErrorPresenterSpec.swift // // Copyright (c) 2016 Auth0 (http://auth0.com) @@ -26,26 +26,26 @@ import Nimble @testable import Lock -class ConnectionErrorPresenterSpec: QuickSpec { +class UnrecoverableErrorPresenterSpec: QuickSpec { override func spec() { - var presenter: ConnectionErrorPresenter! + var presenter: UnrecoverableErrorPresenter! var navigator: MockNavigator! - var error: RemoteConnectionLoaderError! - var view: ConnectionErrorView! + var error: UnrecoverableError! + var view: UnrecoverableErrorView! beforeEach { - error = RemoteConnectionLoaderError.connectionTimeout + error = UnrecoverableError.connectionTimeout navigator = MockNavigator() - presenter = ConnectionErrorPresenter(error: error, navigator: navigator) - view = presenter.view as? ConnectionErrorView + presenter = UnrecoverableErrorPresenter(error: error, navigator: navigator) + view = presenter.view as? UnrecoverableErrorView } describe("init") { - it("should build ConnectionErrorView") { - expect(presenter.view as? ConnectionErrorView).toNot(beNil()) + it("should build UnrecoverableErrorView") { + expect(presenter.view as? UnrecoverableErrorView).toNot(beNil()) } it("should have button title") { diff --git a/LockTests/RemoteConnectionLoaderErrorSpec.swift b/LockTests/RemoteConnectionLoaderErrorSpec.swift deleted file mode 100644 index 2e9f48760..000000000 --- a/LockTests/RemoteConnectionLoaderErrorSpec.swift +++ /dev/null @@ -1,66 +0,0 @@ -// RemoteConnectionLoaderErrorSpec.swift -// -// Copyright (c) 2017 Auth0 (http://auth0.com) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import Quick -import Nimble -@testable import Lock - -class RemoteConnectionLoaderErrorSpec: QuickSpec { - - override func spec() { - - describe("localised message response") { - - it(".invalidClient should return relevant string") { - let error = RemoteConnectionLoaderError.invalidClient - expect(error.localizableMessage).to(contain("No client information found")) - } - - it(".invalidClientInfo should return relevant string") { - let error = RemoteConnectionLoaderError.invalidClientInfo - expect(error.localizableMessage).to(contain("Unable to retrieve client information")) - } - - it(".noConnections should return relevant string") { - let error = RemoteConnectionLoaderError.noConnections - expect(error.localizableMessage).to(contain("No connections available")) - } - - it(".connectionTimeout should return default string") { - let error = RemoteConnectionLoaderError.connectionTimeout - expect(error.localizableMessage).to(contain("Could not connect to the server")) - } - - it(".requestIssue should return default string") { - let error = RemoteConnectionLoaderError.requestIssue - expect(error.localizableMessage).to(contain("Could not connect to the server")) - } - - it(".responseIssue should return default string") { - let error = RemoteConnectionLoaderError.responseIssue - expect(error.localizableMessage).to(contain("Could not connect to the server")) - } - - } - - } -} diff --git a/LockTests/Router/RouterSpec.swift b/LockTests/Router/RouterSpec.swift index 9ba86bab9..b1ec0e891 100644 --- a/LockTests/Router/RouterSpec.swift +++ b/LockTests/Router/RouterSpec.swift @@ -246,8 +246,8 @@ class RouterSpec: QuickSpec { } it("should show connection error screen") { - router.navigate(.connectionError(error: RemoteConnectionLoaderError.connectionTimeout)) - expect(controller.presentable as? ConnectionErrorPresenter).toNot(beNil()) + router.navigate(.unrecoverableError(error: UnrecoverableError.connectionTimeout)) + expect(controller.presentable as? UnrecoverableErrorPresenter).toNot(beNil()) } context("no connection") { @@ -352,9 +352,9 @@ class RouterSpec: QuickSpec { expect(match).to(beTrue()) } - it("connectionError should should be equatable with connectionError") { - let error = RemoteConnectionLoaderError.connectionTimeout - let match = Route.connectionError(error: error) == Route.connectionError(error: error) + it("UnrecoverableError should should be equatable with UnrecoverableError") { + let error = UnrecoverableError.connectionTimeout + let match = Route.unrecoverableError(error: error) == Route.unrecoverableError(error: error) expect(match).to(beTrue()) } diff --git a/LockTests/UnrecoverableErrorSpec.swift b/LockTests/UnrecoverableErrorSpec.swift new file mode 100644 index 000000000..d446eccbb --- /dev/null +++ b/LockTests/UnrecoverableErrorSpec.swift @@ -0,0 +1,99 @@ +// UnrecoverableErrorSpec.swift +// +// Copyright (c) 2017 Auth0 (http://auth0.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Quick +import Nimble +@testable import Lock + +class UnrecoverableErrorSpec: QuickSpec { + + override func spec() { + + describe("localised message response") { + + it(".invalidClientOrDomain should return relevant string") { + let error = UnrecoverableError.invalidClientOrDomain + expect(error.localizableMessage).to(contain("No client information found")) + } + + it(".clientWithNoConnections should return relevant string") { + let error = UnrecoverableError.clientWithNoConnections + expect(error.localizableMessage).to(contain("No connections available")) + } + + it(".connectionTimeout should return default string") { + let error = UnrecoverableError.connectionTimeout + expect(error.localizableMessage).to(contain("problem with your request")) + } + + it(".requestIssue should return default string") { + let error = UnrecoverableError.requestIssue + expect(error.localizableMessage).to(contain("problem with your request")) + } + + it(".missingDatabaseConnection should return relevant string") { + let error = UnrecoverableError.missingDatabaseConnection + expect(error.localizableMessage).to(contain("No database connection was found")) + } + + it(".invalidOptions should return relevant string") { + let error = UnrecoverableError.invalidOptions(cause: "bad options") + expect(error.localizableMessage).to(contain("bad options")) + } + + } + + describe("equatable") { + + it("invalidClientOrDomain should should be equatable with itself") { + let match = UnrecoverableError.invalidClientOrDomain == UnrecoverableError.invalidClientOrDomain + expect(match).to(beTrue()) + } + + it("clientWithNoConnections should should be equatable with itself") { + let match = UnrecoverableError.clientWithNoConnections == UnrecoverableError.clientWithNoConnections + expect(match).to(beTrue()) + } + + it("connectionTimeout should should be equatable with itself") { + let match = UnrecoverableError.connectionTimeout == UnrecoverableError.connectionTimeout + expect(match).to(beTrue()) + } + + it("requestIssue should should be equatable with itself") { + let match = UnrecoverableError.requestIssue == UnrecoverableError.requestIssue + expect(match).to(beTrue()) + } + + it("missingDatabaseConnection should should be equatable with itself") { + let match = UnrecoverableError.missingDatabaseConnection == UnrecoverableError.missingDatabaseConnection + expect(match).to(beTrue()) + } + + it("invalidOptions should should be equatable with itself") { + let match = UnrecoverableError.invalidOptions(cause: "bad options") == UnrecoverableError.invalidOptions(cause: "bad options") + expect(match).to(beTrue()) + } + } + + } +} diff --git a/LockTests/Utils/Mocks.swift b/LockTests/Utils/Mocks.swift index 848ce188e..280b39e34 100644 --- a/LockTests/Utils/Mocks.swift +++ b/LockTests/Utils/Mocks.swift @@ -205,10 +205,10 @@ class MockDBInteractor: DatabaseAuthenticatable, DatabaseUserCreator { class MockConnectionsLoader: RemoteConnectionLoader { var connections: Connections? = nil - var error: RemoteConnectionLoaderError? = nil + var error: UnrecoverableError? = nil - func load(_ callback: @escaping (Connections?, RemoteConnectionLoaderError?) -> ()) { - callback(connections, error) + func load(_ callback: @escaping (UnrecoverableError?, Connections?) -> ()) { + callback(error, connections) } } From 17928036e89b09989d4d44a0761f982d03842472 Mon Sep 17 00:00:00 2001 From: Martin Walsh Date: Wed, 11 Jan 2017 23:00:18 +0000 Subject: [PATCH 3/3] Update UnrecoverableError messages Update UnrecoverableError Tests --- Lock/UnrecoverableError.swift | 19 ++++--------------- LockTests/UnrecoverableErrorSpec.swift | 24 ++++++++++++------------ 2 files changed, 16 insertions(+), 27 deletions(-) diff --git a/Lock/UnrecoverableError.swift b/Lock/UnrecoverableError.swift index f6af669cd..e3f948cf1 100644 --- a/Lock/UnrecoverableError.swift +++ b/Lock/UnrecoverableError.swift @@ -32,26 +32,15 @@ enum UnrecoverableError: Equatable, Error, LocalizableError { var localizableMessage: String { switch self { - case .invalidClientOrDomain: - return "No client information found, please check your Auth0 client credentials.".i18n(key: "com.auth0.lock.error.invalidclient", comment: "Invalid client") case .clientWithNoConnections: - return "No connections available for this client, please check your Auth0 client setup.".i18n(key: "com.auth0.lock.error.noconnections", comment: "No client connections") - case .connectionTimeout, .requestIssue: - return "There was a problem with your request, please try again later.".i18n(key: "com.auth0.lock.error.requestfailed", comment: "Generic request failure") - case .missingDatabaseConnection: - return "No database connection was found, please check your Auth0 client setup".i18n(key: "com.auth0.lock.error.missingdatabase", comment: "No database connection") - case .invalidOptions(let cause): - return "Option configuration issue: \(cause)".i18n(key: "com.auth0.lock.error.options", comment: "Option configuration issue.") + return "No authentication methods found for this client. Please check your client setup.".i18n(key: "com.auth0.lock.error.unrecoverable.no_connections", comment: "No connections") + case .invalidClientOrDomain, .connectionTimeout, .requestIssue, .missingDatabaseConnection, .invalidOptions: + return "Something went wrong.\nPlease contact technical support.".i18n(key: "com.auth0.lock.error.unrecoverable.default", comment: "Default error") } } var userVisible: Bool { - switch self { - case .missingDatabaseConnection, .invalidOptions: - return false - default: - return true - } + return true } } diff --git a/LockTests/UnrecoverableErrorSpec.swift b/LockTests/UnrecoverableErrorSpec.swift index d446eccbb..36a9a3fc0 100644 --- a/LockTests/UnrecoverableErrorSpec.swift +++ b/LockTests/UnrecoverableErrorSpec.swift @@ -30,34 +30,34 @@ class UnrecoverableErrorSpec: QuickSpec { describe("localised message response") { - it(".invalidClientOrDomain should return relevant string") { + it(".invalidClientOrDomain should default error message") { let error = UnrecoverableError.invalidClientOrDomain - expect(error.localizableMessage).to(contain("No client information found")) + expect(error.localizableMessage).to(contain("Something went wrong")) } - it(".clientWithNoConnections should return relevant string") { + it(".clientWithNoConnections should return relevant error message") { let error = UnrecoverableError.clientWithNoConnections - expect(error.localizableMessage).to(contain("No connections available")) + expect(error.localizableMessage).to(contain("No authentication methods found")) } - it(".connectionTimeout should return default string") { + it(".connectionTimeout should return default error message") { let error = UnrecoverableError.connectionTimeout - expect(error.localizableMessage).to(contain("problem with your request")) + expect(error.localizableMessage).to(contain("Something went wrong")) } - it(".requestIssue should return default string") { + it(".requestIssue should return default error message") { let error = UnrecoverableError.requestIssue - expect(error.localizableMessage).to(contain("problem with your request")) + expect(error.localizableMessage).to(contain("Something went wrong")) } - it(".missingDatabaseConnection should return relevant string") { + it(".missingDatabaseConnection should return default error message") { let error = UnrecoverableError.missingDatabaseConnection - expect(error.localizableMessage).to(contain("No database connection was found")) + expect(error.localizableMessage).to(contain("Something went wrong")) } - it(".invalidOptions should return relevant string") { + it(".invalidOptions should return default error message") { let error = UnrecoverableError.invalidOptions(cause: "bad options") - expect(error.localizableMessage).to(contain("bad options")) + expect(error.localizableMessage).to(contain("Something went wrong")) } }