Skip to content

Commit

Permalink
Rework implementation and add unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Supereg committed Nov 20, 2023
1 parent 971005f commit a51a469
Show file tree
Hide file tree
Showing 13 changed files with 248 additions and 47 deletions.
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ let package = Package(
dependencies: [
.package(url: "https://github.com/StanfordSpezi/Spezi", .upToNextMinor(from: "0.8.0")),
.package(url: "https://github.com/StanfordSpezi/SpeziViews.git", .upToNextMinor(from: "0.6.1")),
.package(url: "https://github.com/StanfordSpezi/SpeziAccount", .upToNextMinor(from: "0.8.0")),
.package(url: "https://github.com/StanfordSpezi/SpeziAccount", branch: "fix/record-id-sendable"),
.package(url: "https://github.com/StanfordSpezi/SpeziStorage", .upToNextMinor(from: "0.5.0")),
.package(url: "https://github.com/firebase/firebase-ios-sdk", from: "10.13.0")
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,6 @@ extension FirebaseAccountService {
throw FirebaseAccountError.notSignedIn
}

var changes = false

do {
// if we modify sensitive credentials and require a recent login
if modifications.modifiedDetails.storage[UserIdKey.self] != nil || modifications.modifiedDetails.password != nil {
Expand All @@ -107,7 +105,6 @@ extension FirebaseAccountService {
if let userId = modifications.modifiedDetails.storage[UserIdKey.self] {
Self.logger.debug("updateEmail(to:) for user.")
try await currentUser.updateEmail(to: userId)
changes = true
}

if let password = modifications.modifiedDetails.password {
Expand All @@ -120,14 +117,10 @@ extension FirebaseAccountService {
let changeRequest = currentUser.createProfileChangeRequest()
changeRequest.displayName = name.formatted(.name(style: .long))
try await changeRequest.commitChanges()

changes = true
}

if changes {
// non of the above request will trigger our state change listener, therefore just call it manually.
try await context.notifyUserSignIn(user: currentUser, for: self)
}
// non of the above request will trigger our state change listener, therefore just call it manually.
try await context.notifyUserSignIn(user: currentUser, for: self)
} catch let error as NSError {
Self.logger.error("Received NSError on firebase dispatch: \(error)")
throw FirebaseAccountError(authErrorCode: AuthErrorCode(_nsError: error))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,7 @@ public actor FirestoreAccountStorage: Module, AccountStorageStandard {

private let collection: () -> CollectionReference


public init(storeIn collection: @autoclosure @escaping () -> CollectionReference) {
public init(storeIn collection: @Sendable @autoclosure @escaping () -> CollectionReference) {
self.collection = collection
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@


import FirebaseFirestore
import SpeziAccount
import OSLog
import SpeziAccount


private struct SingleKeyContainer<Value: Codable>: Codable {
Expand All @@ -18,10 +18,10 @@ private struct SingleKeyContainer<Value: Codable>: Codable {


class FirestoreEncodeVisitor: AccountValueVisitor {
private let logger = Logger(subsystem: "edu.stanford.spezi.firebase", category: "FirestoreEncode")

typealias Data = [String: Any]

private let logger = Logger(subsystem: "edu.stanford.spezi.firebase", category: "FirestoreEncode")

private var values: Data = [:]
private var errors: [String: Error] = [:]

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//
// This source file is part of the Stanford Spezi open-source project
//
// SPDX-FileCopyrightText: 2022 Stanford University and the project authors (see CONTRIBUTORS.md)
//
// SPDX-License-Identifier: MIT
//

import FirebaseFirestore
import Spezi
import SpeziAccount
import SpeziFirebaseAccountStorage


actor AccountStorageTestStandard: Standard, AccountStorageStandard {
static var collection: CollectionReference {
Firestore.firestore().collection("users")
}

@Dependency var storage = FirestoreAccountStorage(storeIn: collection)


func create(_ identifier: AdditionalRecordId, _ details: SignupDetails) async throws {
try await storage.create(identifier, details)
}

func load(_ identifier: AdditionalRecordId, _ keys: [any AccountKey.Type]) async throws -> PartialAccountDetails {
try await storage.load(identifier, keys)
}

func modify(_ identifier: AdditionalRecordId, _ modifications: SpeziAccount.AccountModifications) async throws {
try await storage.modify(identifier, modifications)
}

func clear(_ identifier: AdditionalRecordId) async {
await storage.clear(identifier)
}

func delete(_ identifier: AdditionalRecordId) async throws {
try await storage.delete(identifier)
}
}
51 changes: 51 additions & 0 deletions Tests/UITests/TestApp/FirebaseAccountStorage/BiographyKey.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
//
// This source file is part of the Spezi open-source project
//
// SPDX-FileCopyrightText: 2023 Stanford University and the project authors (see CONTRIBUTORS.md)
//
// SPDX-License-Identifier: MIT
//

import SpeziAccount
import SpeziValidation
import SwiftUI


struct BiographyKey: AccountKey {
typealias Value = String

static let name: LocalizedStringResource = "Biography" // we don't bother to translate
static let category: AccountKeyCategory = .personalDetails
}


extension AccountKeys {
var biography: BiographyKey.Type {
BiographyKey.self
}
}


extension AccountValues {
var biography: String? {
storage[BiographyKey.self]
}
}


extension BiographyKey {
public struct DataEntry: DataEntryView {
public typealias Key = BiographyKey

@Binding private var biography: Value

public init(_ value: Binding<Value>) {
self._biography = value
}

public var body: some View {
VerifiableTextField(Key.name, text: $biography)
.autocorrectionDisabled()
}
}
}
13 changes: 13 additions & 0 deletions Tests/UITests/TestApp/Shared/FeatureFlags.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// This source file is part of the Stanford Spezi open-source project
//
// SPDX-FileCopyrightText: 2023 Stanford University and the project authors (see CONTRIBUTORS.md)
//
// SPDX-License-Identifier: MIT
//

import Foundation

enum FeatureFlags {
static let accountStorageTests = ProcessInfo.processInfo.arguments.contains("--account-storage")
}
34 changes: 27 additions & 7 deletions Tests/UITests/TestApp/Shared/TestAppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,37 @@ import SwiftUI

class TestAppDelegate: SpeziAppDelegate {
override var configuration: Configuration {
Configuration {
if FeatureFlags.accountStorageTests {
return Configuration(standard: AccountStorageTestStandard(), configurationsClosure)
} else {
return Configuration(configurationsClosure)
}
}

var configurationsClosure: () -> ModuleCollection {
{
self.configurations
}
}

@ModuleBuilder var configurations: ModuleCollection {
if FeatureFlags.accountStorageTests {
AccountConfiguration(configuration: [
.requires(\.userId),
.requires(\.name),
.requires(\.biography)
])
} else {
AccountConfiguration(configuration: [
.requires(\.userId),
.collects(\.name)
])
Firestore(settings: .emulator)
FirebaseAccountConfiguration(
authenticationMethods: [.emailAndPassword, .signInWithApple],
emulatorSettings: (host: "localhost", port: 9099)
)
FirebaseStorageConfiguration(emulatorSettings: (host: "localhost", port: 9199))
}
Firestore(settings: .emulator)
FirebaseAccountConfiguration(
authenticationMethods: [.emailAndPassword, .signInWithApple],
emulatorSettings: (host: "localhost", port: 9099)
)
FirebaseStorageConfiguration(emulatorSettings: (host: "localhost", port: 9199))
}
}
59 changes: 59 additions & 0 deletions Tests/UITests/TestAppUITests/FirebaseAccountStorageTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
//
// This source file is part of the Stanford Spezi open-source project
//
// SPDX-FileCopyrightText: 2023 Stanford University and the project authors (see CONTRIBUTORS.md)
//
// SPDX-License-Identifier: MIT
//

import XCTest
import XCTestExtensions


final class FirebaseAccountStorageTests: XCTestCase {
@MainActor
override func setUp() async throws {
try await super.setUp()

continueAfterFailure = false

try disablePasswordAutofill()

try await FirebaseClient.deleteAllAccounts()
try await Task.sleep(for: .seconds(0.5))
}

@MainActor
func testAdditionalAccountStorage() async throws {
let app = XCUIApplication()
app.launchArguments = ["--account-storage"]
app.launch()

XCTAssert(app.buttons["FirebaseAccount"].waitForExistence(timeout: 10.0))
app.buttons["FirebaseAccount"].tap()

if app.buttons["Logout"].waitForExistence(timeout: 5.0) && app.buttons["Logout"].isHittable {
app.buttons["Logout"].tap()
}


try app.signup(username: "[email protected]", password: "TestPassword1", givenName: "Test1", familyName: "Username1", biography: "Hello Stanford")

Check failure on line 40 in Tests/UITests/TestAppUITests/FirebaseAccountStorageTests.swift

View workflow job for this annotation

GitHub Actions / SwiftLint / SwiftLint / SwiftLint

Line Length Violation: Line should be 150 characters or less; currently it has 155 characters (line_length)


XCTAssertTrue(app.buttons["Account Overview"].waitForExistence(timeout: 2.0))
app.buttons["Account Overview"].tap()
XCTAssertTrue(app.staticTexts["Biography, Hello Stanford"].waitForExistence(timeout: 2.0))


// NOW TEST ACCOUNT EDIT
XCTAssertTrue(app.navigationBars.buttons["Edit"].exists)
app.navigationBars.buttons["Edit"].tap()

try app.textFields["Biography"].enter(value: "2")

XCTAssertTrue(app.navigationBars.buttons["Done"].exists)
app.navigationBars.buttons["Done"].tap()

XCTAssertTrue(app.staticTexts["Biography, Hello Stanford2"].waitForExistence(timeout: 2.0))
}
}
29 changes: 8 additions & 21 deletions Tests/UITests/TestAppUITests/FirebaseAccountTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -451,23 +451,12 @@ final class FirebaseAccountTests: XCTestCase { // swiftlint:disable:this type_bo


extension XCUIApplication {
func extendedDismissKeyboard() {
let keyboard = keyboards.firstMatch

if keyboard.waitForExistence(timeout: 1.0) && keyboard.buttons["Done"].isHittable {
keyboard.buttons["Done"].tap()
}
}

fileprivate func login(username: String, password: String, close: Bool = true) throws {
func login(username: String, password: String, close: Bool = true) throws {
buttons["Account Setup"].tap()
XCTAssertTrue(self.buttons["Login"].waitForExistence(timeout: 2.0))

try textFields["E-Mail Address"].enter(value: username)
extendedDismissKeyboard()

try secureTextFields["Password"].enter(value: password)
extendedDismissKeyboard()

swipeUp()

Expand All @@ -478,34 +467,32 @@ extension XCUIApplication {
self.buttons["Close"].tap()
}
}


fileprivate func signup(username: String, password: String, givenName: String, familyName: String) throws {

func signup(username: String, password: String, givenName: String, familyName: String, biography: String? = nil) throws {
buttons["Account Setup"].tap()
buttons["Signup"].tap()

XCTAssertTrue(staticTexts["Please fill out the details below to create your new account."].waitForExistence(timeout: 6.0))
sleep(2)

try collectionViews.textFields["E-Mail Address"].enter(value: username)
extendedDismissKeyboard()

try collectionViews.secureTextFields["Password"].enter(value: password)
extendedDismissKeyboard()

swipeUp()

try textFields["enter first name"].enter(value: givenName)
extendedDismissKeyboard()
swipeUp()

try textFields["enter last name"].enter(value: familyName)
extendedDismissKeyboard()
swipeUp()

if let biography {
try textFields["Biography"].enter(value: biography)
}

collectionViews.buttons["Signup"].tap()

sleep(3)
buttons["Close"].tap()
}
} // swiftlint:disable:this file_length
}
Loading

0 comments on commit a51a469

Please sign in to comment.