From 450fe6c7a8c3f167276b51511da5094a2653560f Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Tue, 12 Sep 2023 14:47:57 -0700 Subject: [PATCH] [auth-swift] Sample app Settings screen 2 --- FirebaseAuth/Sources/Swift/Auth/Auth.swift | 23 +++-- .../project.pbxproj | 4 + .../SettingsViewController.swift | 92 +++++++++++++----- .../SettingsUITests.swift | 93 +++++++++++++++++++ 4 files changed, 182 insertions(+), 30 deletions(-) create mode 100644 FirebaseAuth/Tests/SampleSwift/AuthenticationExampleUITests/SettingsUITests.swift diff --git a/FirebaseAuth/Sources/Swift/Auth/Auth.swift b/FirebaseAuth/Sources/Swift/Auth/Auth.swift index 0445f7a8608..11669af76c0 100644 --- a/FirebaseAuth/Sources/Swift/Auth/Auth.swift +++ b/FirebaseAuth/Sources/Swift/Auth/Auth.swift @@ -169,12 +169,12 @@ extension Auth: AuthInterop { /** @property app @brief Gets the `FirebaseApp` object that this auth object is connected to. */ - @objc public weak var app: FirebaseApp? + @objc public internal(set) weak var app: FirebaseApp? /** @property currentUser @brief Synchronously gets the cached current user, or null if there is none. */ - @objc public var currentUser: User? + @objc public internal(set) var currentUser: User? /** @property languageCode @brief The current user language code. This property can be set to the app's current language by @@ -182,7 +182,16 @@ extension Auth: AuthInterop { @remarks The string used to set this property must be a language code that follows BCP 47. */ - @objc public var languageCode: String? + @objc public var languageCode: String? { + get { + return requestConfiguration.languageCode + } + set(val) { + kAuthGlobalWorkQueue.sync { + requestConfiguration.languageCode = val + } + } + } /** @property settings @brief Contains settings related to the auth object. @@ -192,7 +201,7 @@ extension Auth: AuthInterop { /** @property userAccessGroup @brief The current user access group that the Auth instance is using. Default is nil. */ - @objc public var userAccessGroup: String? + @objc public internal(set) var userAccessGroup: String? /** @property shareAuthStateAcrossDevices @brief Contains shareAuthStateAcrossDevices setting related to the auth object. @@ -1458,8 +1467,10 @@ extension Auth: AuthInterop { @brief Sets `languageCode` to the app's current language. */ @objc public func useAppLanguage() { - kAuthGlobalWorkQueue.async { - self.requestConfiguration.languageCode = Locale.preferredLanguages.first + kAuthGlobalWorkQueue.sync { + if let language = Locale.preferredLanguages.first { + self.requestConfiguration.languageCode = language + } } } diff --git a/FirebaseAuth/Tests/SampleSwift/AuthenticationExample.xcodeproj/project.pbxproj b/FirebaseAuth/Tests/SampleSwift/AuthenticationExample.xcodeproj/project.pbxproj index 09740adf4bd..dbbfd847ce5 100644 --- a/FirebaseAuth/Tests/SampleSwift/AuthenticationExample.xcodeproj/project.pbxproj +++ b/FirebaseAuth/Tests/SampleSwift/AuthenticationExample.xcodeproj/project.pbxproj @@ -26,6 +26,7 @@ DEC2E5DD2A95331E0090260A /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEC2E5DC2A95331D0090260A /* SettingsViewController.swift */; }; DEC2E5DF2A9583CA0090260A /* AppManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEC2E5DE2A9583CA0090260A /* AppManager.swift */; }; DEC2E5E42A966DE20090260A /* GoogleService-Info_multi.plist in Resources */ = {isa = PBXBuildFile; fileRef = DEC2E5E32A966DE20090260A /* GoogleService-Info_multi.plist */; }; + DED37F632AB0C4F7003A67E4 /* SettingsUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DED37F622AB0C4F7003A67E4 /* SettingsUITests.swift */; }; EA02F68524A000E00079D000 /* UserActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA02F68424A000E00079D000 /* UserActions.swift */; }; EA02F68D24A063E90079D000 /* LoginDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA02F68C24A063E90079D000 /* LoginDelegate.swift */; }; EA062D5D24A0FEB6006714D3 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = EA062D5C24A0FEB6006714D3 /* README.md */; }; @@ -102,6 +103,7 @@ DEC2E5DC2A95331D0090260A /* SettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = ""; }; DEC2E5DE2A9583CA0090260A /* AppManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppManager.swift; sourceTree = ""; }; DEC2E5E32A966DE20090260A /* GoogleService-Info_multi.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = "GoogleService-Info_multi.plist"; sourceTree = SOURCE_ROOT; }; + DED37F622AB0C4F7003A67E4 /* SettingsUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsUITests.swift; sourceTree = ""; }; EA02F68424A000E00079D000 /* UserActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserActions.swift; sourceTree = ""; }; EA02F68C24A063E90079D000 /* LoginDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginDelegate.swift; sourceTree = ""; }; EA062D5C24A0FEB6006714D3 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = SOURCE_ROOT; }; @@ -327,6 +329,7 @@ children = ( EAE4CBE624855E3E00245E92 /* AuthenticationExampleUITests.swift */, EAE4CBE824855E3E00245E92 /* Info.plist */, + DED37F622AB0C4F7003A67E4 /* SettingsUITests.swift */, ); path = AuthenticationExampleUITests; sourceTree = ""; @@ -555,6 +558,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + DED37F632AB0C4F7003A67E4 /* SettingsUITests.swift in Sources */, EAE4CBE724855E3E00245E92 /* AuthenticationExampleUITests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/FirebaseAuth/Tests/SampleSwift/AuthenticationExample/ViewControllers/SettingsViewController.swift b/FirebaseAuth/Tests/SampleSwift/AuthenticationExample/ViewControllers/SettingsViewController.swift index f50307a5aee..b66aa6e97cd 100644 --- a/FirebaseAuth/Tests/SampleSwift/AuthenticationExample/ViewControllers/SettingsViewController.swift +++ b/FirebaseAuth/Tests/SampleSwift/AuthenticationExample/ViewControllers/SettingsViewController.swift @@ -22,6 +22,9 @@ enum SettingsAction: String { case toggleSecureTokenAPI = "Secure Token" case toggleActiveApp = "Active App" case toggleAccessGroup = "Current Access Group" + case setAuthLanugage = "Auth Language" + case useAppLanguage = "Use App Language" + case togglePhoneAppVerification = "Disable App Verification (Phone)" } class SettingsViewController: UIViewController, DataSourceProviderDelegate { @@ -61,6 +64,7 @@ class SettingsViewController: UIViewController, DataSourceProviderDelegate { // The row tapped has no affiliated action. return } + let auth = AppManager.shared.auth() switch action { case .toggleSecureTokenAPI: @@ -71,6 +75,15 @@ class SettingsViewController: UIViewController, DataSourceProviderDelegate { AppManager.shared.toggle() case .toggleAccessGroup: toggleAccessGroup() + case .setAuthLanugage: + setAuthLanguage() + case .useAppLanguage: + auth.useAppLanguage() + case .togglePhoneAppVerification: + guard let settings = auth.settings else { + fatalError("Unset auth.settings") + } + settings.appVerificationDisabledForTesting = !settings.isAppVerificationDisabledForTesting } updateUI() } @@ -105,6 +118,19 @@ class SettingsViewController: UIViewController, DataSourceProviderDelegate { } } + private func setAuthLanguage() { + let prompt = UIAlertController(title: nil, message: "Enter Language Code For Auth:", + preferredStyle: .alert) + prompt.addTextField() + let okAction = UIAlertAction(title: "OK", style: .default) { action in + AppManager.shared.auth().languageCode = prompt.textFields?[0].text ?? "" + self.updateUI() + } + prompt.addAction(okAction) + + present(prompt, animated: true) + } + // MARK: - Private Helpers private func configureNavigationBar() { @@ -134,29 +160,6 @@ class SettingsViewController: UIViewController, DataSourceProviderDelegate { options: .transitionCrossDissolve, animations: { tableView.reloadData() }) } - - private func presentEditUserInfoController(for title: String, - to saveHandler: @escaping (String) -> Void) { - let editController = UIAlertController( - title: "Update \(title)", - message: nil, - preferredStyle: .alert - ) - editController.addTextField { $0.placeholder = "New \(title)" } - - let saveHandler1: (UIAlertAction) -> Void = { _ in - let text = editController.textFields!.first!.text! - saveHandler(text) - } - - let cancel: (UIAlertAction) -> Void = { _ in - saveHandler("") - } - - editController.addAction(UIAlertAction(title: "Save", style: .default, handler: saveHandler1)) - editController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: cancel)) - present(editController, animated: true, completion: nil) - } } // MARK: - Extending a `AuthSettings` to conform to `DataSourceProvidable` @@ -184,7 +187,48 @@ extension AuthSettings: DataSourceProvidable { return Section(headerDescription: "Keychain Access Groups", items: items) } + // TODO: Add ability to click and clear both of these fields. + private var phoneAuthSection: Section { + var tokenString = "No Token" + var credentialString = "No Credential" + if let token = AppManager.shared.auth().tokenManager.token { + let tokenType = token.type == .prod ? "Production" : "Sandbox" + tokenString = "token: \(token.string): type: \(tokenType)" + } + if let credential = AppManager.shared.auth().appCredentialManager.credential { + // TODO: Maybe use truncatedString like ObjC sample + credentialString = "\(credential.receipt)/\(credential.secret ?? "nil")" + } + let items = [Item(title: tokenString, detailTitle: "APNs Token"), + Item(title: credentialString, detailTitle: "App Credential")] + return Section(headerDescription: "Phone Auth - TODO toggle off", items: items) + } + + private var languageSection: Section { + let languageCode = AppManager.shared.auth().languageCode + let items = [Item(title: languageCode ?? "[none]", detailTitle: "Auth Language"), + Item(title: "Click to Use App Language", detailTitle: "Use App Language")] + return Section(headerDescription: "Language", items: items) + } + + private var disableSection: Section { + guard let settings = AppManager.shared.auth().settings else { + fatalError("Missing auth settings") + } + let disabling = settings.isAppVerificationDisabledForTesting ? "YES" : "NO" + let items = [Item(title: disabling, detailTitle: "Disable App Verification (Phone)")] + return Section(headerDescription: "Auth Settings", items: items) + } + var sections: [Section] { - [versionSection, apiHostSection, appsSection, keychainSection] + [ + versionSection, + apiHostSection, + appsSection, + keychainSection, + phoneAuthSection, + languageSection, + disableSection, + ] } } diff --git a/FirebaseAuth/Tests/SampleSwift/AuthenticationExampleUITests/SettingsUITests.swift b/FirebaseAuth/Tests/SampleSwift/AuthenticationExampleUITests/SettingsUITests.swift new file mode 100644 index 00000000000..3fd38b6f079 --- /dev/null +++ b/FirebaseAuth/Tests/SampleSwift/AuthenticationExampleUITests/SettingsUITests.swift @@ -0,0 +1,93 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import XCTest + +class SettingsUITests: XCTestCase { + var app: XCUIApplication! + + override func setUp() { + super.setUp() + + continueAfterFailure = false + + app = XCUIApplication() + app.launch() + } + + func testSettings() { + app.staticTexts["Settings"].tap() + + wait(forElement: app.navigationBars["Settings"], timeout: 5.0) + XCTAssertTrue(app.navigationBars["Settings"].exists) + + // Test Identity toolkit + let identityCell = app.cells.containing(.staticText, identifier: "Identity Toolkit").element + XCTAssertTrue(identityCell.staticTexts["www.googleapis.com"].exists) + identityCell.tap() + XCTAssertTrue(identityCell.staticTexts["staging-www.sandbox.googleapis.com"].exists) + identityCell.tap() + XCTAssertTrue(identityCell.staticTexts["www.googleapis.com"].exists) + + // Test Secure Token + let secureTokenCell = app.cells.containing(.staticText, identifier: "Secure Token").element + XCTAssertTrue(secureTokenCell.staticTexts["securetoken.googleapis.com"].exists) + secureTokenCell.tap() + XCTAssertTrue(secureTokenCell.staticTexts["staging-securetoken.sandbox.googleapis.com"].exists) + secureTokenCell.tap() + XCTAssertTrue(secureTokenCell.staticTexts["securetoken.googleapis.com"].exists) + + // Swap Firebase App + let appCell = app.cells.containing(.staticText, identifier: "Active App").element + XCTAssertTrue(appCell.staticTexts["gcip-ios-test"].exists) + appCell.tap() + XCTAssertTrue(appCell.staticTexts["fb-sa-upgraded"].exists) + appCell.tap() + XCTAssertTrue(appCell.staticTexts["gcip-ios-test"].exists) + + // Current Access Group + let accessCell = app.cells.containing(.staticText, identifier: "Current Access Group").element + XCTAssertTrue(accessCell.staticTexts["[none]"].exists) + accessCell.tap() + // XCTAssertTrue(accessCell.staticTexts["com.google.firebase.auth.keychainGroup1"].exists) + accessCell.tap() + XCTAssertTrue(accessCell.staticTexts["[none]"].exists) + + // Auth Language + let languageCell = app.cells.containing(.staticText, identifier: "Auth Language").element + XCTAssertTrue(languageCell.staticTexts["[none]"].exists) + languageCell.tap() + app.typeText("abc") + app.buttons["OK"].tap() + XCTAssertTrue(languageCell.staticTexts["abc"].exists) + + // TODO: PhoneAuth + + // Click to Use App Language + let appLanguageCell = app.cells.containing(.staticText, + identifier: "Click to Use App Language").element + appLanguageCell.tap() + XCTAssertTrue(languageCell.staticTexts["en"].exists) + + // Disable App Verification + let disabledCell = app.cells.containing(.staticText, + identifier: "Disable App Verification (Phone)") + .element + XCTAssertTrue(disabledCell.staticTexts["NO"].exists, "App verification should NOT be disabled") + disabledCell.tap() + XCTAssertTrue(disabledCell.staticTexts["YES"].exists, "App verification should NOW be disabled") + disabledCell.tap() + XCTAssertTrue(disabledCell.staticTexts["NO"].exists, "App verification should NOT be disabled") + } +}