diff --git a/MorphicCore/MorphicCore.xcodeproj/project.pbxproj b/MorphicCore/MorphicCore.xcodeproj/project.pbxproj index d617d549..a8f75bd4 100644 --- a/MorphicCore/MorphicCore.xcodeproj/project.pbxproj +++ b/MorphicCore/MorphicCore.xcodeproj/project.pbxproj @@ -18,6 +18,10 @@ 31A0A5AD2405B75500166668 /* UserDeafults+Morphic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31A0A5AC2405B75500166668 /* UserDeafults+Morphic.swift */; }; 31DDA6A32424130900F18A43 /* Storage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31DDA6A22424130900F18A43 /* Storage.swift */; }; 31DDA6A624241CF900F18A43 /* Record.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31DDA6A524241CF900F18A43 /* Record.swift */; }; + 8011AF9424C61EAE00A49F96 /* UserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8011AF9324C61EAE00A49F96 /* UserTests.swift */; }; + 8011AF9624C61FB200A49F96 /* ArbitraryKeysTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8011AF9524C61FB200A49F96 /* ArbitraryKeysTests.swift */; }; + 8011AF9824C6256400A49F96 /* PreferencesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8011AF9724C6256400A49F96 /* PreferencesTests.swift */; }; + 806C04D224CA08AF0022E647 /* StorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 806C04D124CA08AF0022E647 /* StorageTests.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -45,6 +49,10 @@ 31A0A5AC2405B75500166668 /* UserDeafults+Morphic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserDeafults+Morphic.swift"; sourceTree = ""; }; 31DDA6A22424130900F18A43 /* Storage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Storage.swift; sourceTree = ""; }; 31DDA6A524241CF900F18A43 /* Record.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Record.swift; sourceTree = ""; }; + 8011AF9324C61EAE00A49F96 /* UserTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserTests.swift; sourceTree = ""; }; + 8011AF9524C61FB200A49F96 /* ArbitraryKeysTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArbitraryKeysTests.swift; sourceTree = ""; }; + 8011AF9724C6256400A49F96 /* PreferencesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesTests.swift; sourceTree = ""; }; + 806C04D124CA08AF0022E647 /* StorageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageTests.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -104,8 +112,12 @@ 31A0A5602405965600166668 /* MorphicCoreTests */ = { isa = PBXGroup; children = ( + 8011AF9724C6256400A49F96 /* PreferencesTests.swift */, 31A0A5612405965600166668 /* MorphicCoreTests.swift */, 31A0A5632405965600166668 /* Info.plist */, + 8011AF9324C61EAE00A49F96 /* UserTests.swift */, + 8011AF9524C61FB200A49F96 /* ArbitraryKeysTests.swift */, + 806C04D124CA08AF0022E647 /* StorageTests.swift */, ); path = MorphicCoreTests; sourceTree = ""; @@ -167,7 +179,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1130; - LastUpgradeCheck = 1130; + LastUpgradeCheck = 1160; ORGANIZATIONNAME = "Raising the Floor"; TargetAttributes = { 31A0A5522405965600166668 = { @@ -235,7 +247,11 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 8011AF9424C61EAE00A49F96 /* UserTests.swift in Sources */, 31A0A5622405965600166668 /* MorphicCoreTests.swift in Sources */, + 8011AF9824C6256400A49F96 /* PreferencesTests.swift in Sources */, + 806C04D224CA08AF0022E647 /* StorageTests.swift in Sources */, + 8011AF9624C61FB200A49F96 /* ArbitraryKeysTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/MorphicCore/MorphicCore.xcodeproj/xcshareddata/xcschemes/MorphicCore.xcscheme b/MorphicCore/MorphicCore.xcodeproj/xcshareddata/xcschemes/MorphicCore.xcscheme index 822a6221..ba7d9090 100644 --- a/MorphicCore/MorphicCore.xcodeproj/xcshareddata/xcschemes/MorphicCore.xcscheme +++ b/MorphicCore/MorphicCore.xcodeproj/xcshareddata/xcschemes/MorphicCore.xcscheme @@ -1,6 +1,6 @@ (record: RecordType, completion: @escaping (_ status: LoadStatus, _ document: RecordType?) -> Void) throws where RecordType: Record { + guard let prefsUrl = url(for: record.identifier, type: RecordType.self) else { + os_log(.error, log: logger, "Could not obtain a valid file url for removing") + DispatchQueue.main.async { + completion(.fileUrlMissing, record) + } + return + } + // Same logic as contains() where file doesn't exist + // i.e. doesn't exist? "already" removed. + guard fileManager.fileExists(atPath: prefsUrl.path) else { + DispatchQueue.main.async { + completion(.success, record) + } + return + } + try fileManager.removeItem(at: prefsUrl) + DispatchQueue.main.async { + completion(.success, record) + } + } + /// Check if a record exists /// - parameters: /// - identifier: The identifier of the object to load diff --git a/MorphicCore/MorphicCore/User.swift b/MorphicCore/MorphicCore/User.swift index b143545b..6fa29bf4 100644 --- a/MorphicCore/MorphicCore/User.swift +++ b/MorphicCore/MorphicCore/User.swift @@ -70,6 +70,7 @@ public struct User: Codable, Record { try container.encode(preferencesId, forKey: .preferencesId) try container.encode(firstName, forKey: .firstName) try container.encode(lastName, forKey: .lastName) + try container.encode(email, forKey: .email) } // MARK: - Identification diff --git a/MorphicCore/MorphicCoreTests/ArbitraryKeysTests.swift b/MorphicCore/MorphicCoreTests/ArbitraryKeysTests.swift new file mode 100644 index 00000000..b68d7d31 --- /dev/null +++ b/MorphicCore/MorphicCoreTests/ArbitraryKeysTests.swift @@ -0,0 +1,54 @@ +// Copyright 2020 Raising the Floor - International +// Copyright 2020 OCAD University +// +// Licensed under the New BSD license. You may not use this file except in +// compliance with this License. +// +// You may obtain a copy of the License at +// https://github.com/GPII/universal/blob/master/LICENSE.txt +// +// The R&D leading to these results received funding from the: +// * Rehabilitation Services Administration, US Dept. of Education under +// grant H421A150006 (APCP) +// * National Institute on Disability, Independent Living, and +// Rehabilitation Research (NIDILRR) +// * Administration for Independent Living & Dept. of Education under grants +// H133E080022 (RERC-IT) and H133E130028/90RE5003-01-00 (UIITA-RERC) +// * European Union's Seventh Framework Programme (FP7/2007-2013) grant +// agreement nos. 289016 (Cloud4all) and 610510 (Prosperity4All) +// * William and Flora Hewlett Foundation +// * Ontario Ministry of Research and Innovation +// * Canadian Foundation for Innovation +// * Adobe Foundation +// * Consumer Electronics Association Foundation +// +// ArbitraryKeysTests.swift +// MorphicCoreTests + +import XCTest +@testable import MorphicCore + +class ArbitraryKeysTests: XCTestCase { + + var aKeyGivenInt: ArbitraryKeys! + var aKeyGivenString: ArbitraryKeys! + + let testInt = 42 + let testString = "stringKey" + + override func setUpWithError() throws { + try super.setUpWithError() + aKeyGivenInt = ArbitraryKeys(intValue: testInt) + aKeyGivenString = ArbitraryKeys(stringValue: testString) + } + override func tearDownWithError() throws { + try super.tearDownWithError() + aKeyGivenInt = nil + aKeyGivenString = nil + } + + func testInits() throws { + XCTAssertEqual(aKeyGivenInt.intValue, testInt, "ArbitraryKey created with integer") + XCTAssertEqual(aKeyGivenString.stringValue, testString, "Arbitrary key created with string") + } +} diff --git a/MorphicCore/MorphicCoreTests/PreferencesTests.swift b/MorphicCore/MorphicCoreTests/PreferencesTests.swift new file mode 100644 index 00000000..0463fe45 --- /dev/null +++ b/MorphicCore/MorphicCoreTests/PreferencesTests.swift @@ -0,0 +1,133 @@ +// Copyright 2020 Raising the Floor - International +// Copyright 2020 OCAD University +// +// Licensed under the New BSD license. You may not use this file except in +// compliance with this License. +// +// You may obtain a copy of the License at +// https://github.com/GPII/universal/blob/master/LICENSE.txt +// +// The R&D leading to these results received funding from the: +// * Rehabilitation Services Administration, US Dept. of Education under +// grant H421A150006 (APCP) +// * National Institute on Disability, Independent Living, and +// Rehabilitation Research (NIDILRR) +// * Administration for Independent Living & Dept. of Education under grants +// H133E080022 (RERC-IT) and H133E130028/90RE5003-01-00 (UIITA-RERC) +// * European Union's Seventh Framework Programme (FP7/2007-2013) grant +// agreement nos. 289016 (Cloud4all) and 610510 (Prosperity4All) +// * William and Flora Hewlett Foundation +// * Ontario Ministry of Research and Innovation +// * Canadian Foundation for Innovation +// * Adobe Foundation +// * Consumer Electronics Association Foundation +// +// PreferencesTests.swift +// MorphicCoreTests + +import XCTest +@testable import MorphicCore + +class PreferencesTests: XCTestCase { + + var carlaPrefs: Preferences! + let prefsId = UUID().uuidString + let carlaId = UUID().uuidString + + var magFactorKey: Preferences.Key! + let magnifierName = "Magnifier" + let magFactorPref = "magfactor" + let magFactorVal: Double = 2.0 + + var inverseVideoKey: Preferences.Key! + let inverseVideoPref = "inverse_video" + let inverseVideoVal: Bool = true + + var nonExistentKey: Preferences.Key! + let nonExistentSolution = "nonSolution" + let nonExistentPref = "nullPref" + let nonExistentVal: Int = 0 + + override func setUpWithError() throws { + carlaPrefs = Preferences(identifier: prefsId) + carlaPrefs.userId = carlaId + magFactorKey = Preferences.Key(solution: magnifierName, preference: magFactorPref) + inverseVideoKey = Preferences.Key(solution: magnifierName, preference: inverseVideoPref) + nonExistentKey = Preferences.Key(solution: nonExistentSolution, preference: nonExistentPref) + } + + override func tearDownWithError() throws { + carlaPrefs = nil + magFactorKey = nil + inverseVideoKey = nil + nonExistentKey = nil + } + + func testCreation() { + XCTAssertEqual(type(of: carlaPrefs).typeName, Preferences.typeName, "Check type name") + XCTAssertEqual(carlaPrefs.identifier, prefsId, "Check preferences id") + XCTAssertEqual(carlaPrefs.userId, carlaId, "Check user id") + XCTAssertNil(carlaPrefs.defaults, "Check empty default preferences values") + } + + func testSetGet() { + carlaPrefs.set(nil, for: magFactorKey) + XCTAssertNil(carlaPrefs.get(key: magFactorKey), "Test set()/get() with magnification factor of nil") + + carlaPrefs.set(magFactorVal, for: magFactorKey) + XCTAssertEqual(magFactorVal, carlaPrefs.get(key: magFactorKey) as! Double, "Test set()/get() with magnification factor") + + carlaPrefs.set(inverseVideoVal, for: inverseVideoKey) + XCTAssertEqual(inverseVideoVal, carlaPrefs.get(key: inverseVideoKey) as! Bool, "Test set()/get() with inverse video") + } + + func testRemove() { + // Check that there is no magnification factor, then initialize it and confirm that + // it is set. + XCTAssertNil(carlaPrefs.get(key: magFactorKey), "Magnification factor nil before set()") + carlaPrefs.set(magFactorVal, for: magFactorKey) + XCTAssertNotNil(carlaPrefs.get(key: magFactorKey), "Magnification factor set") + + // Test "removal" of non-existent preferences and that the mag factor is unaffected. + XCTAssertNil(carlaPrefs.get(key: nonExistentKey), "Test non-existent preference before removal") + carlaPrefs.remove(key: nonExistentKey) + XCTAssertNil(carlaPrefs.get(key: nonExistentKey), "Test non-existent preference after removal") + XCTAssertNotNil(carlaPrefs.get(key: magFactorKey), "Test magnification factor still present after removing non-existent preference") + + // Remove mag factor preference. + carlaPrefs.remove(key: magFactorKey) + XCTAssertNil(carlaPrefs.get(key: magFactorKey), "Magnification factor nil after remove()") + } + + func testKeyValueTuples() { + // At start, there should be no preferences + var prefsTuples = carlaPrefs.keyValueTuples() + XCTAssertTrue(prefsTuples.isEmpty, "Check for zero preferences") + + // Add two preferences + carlaPrefs.set(magFactorVal, for: magFactorKey) + carlaPrefs.set(inverseVideoVal, for: inverseVideoKey) + + // Check that the preferences were added + prefsTuples = carlaPrefs.keyValueTuples() + XCTAssert(2 == prefsTuples.count, "Check for two preferences") + XCTAssertTrue(containsTuple(magFactorKey, magFactorVal, prefsTuples), "Check presence of magnification factor preference") + XCTAssertTrue(containsTuple(inverseVideoKey, inverseVideoVal, prefsTuples), "Check presence of inverse video preference") + + // Check that non-existent preference is absent + XCTAssertFalse(containsTuple(nonExistentKey, nonExistentVal, prefsTuples), "Check absence of non-existent preference") + } + + func containsTuple(_ inKey: Preferences.Key, _ inValue: Interoperable, _ tuplesArray: [(Preferences.Key, Interoperable?)]) -> Bool { + guard !tuplesArray.isEmpty else { + return false + } + for (key, val) in tuplesArray { + if (inKey == key) && + (inValue != nil && val != nil) { + return true + } + } + return false + } +} diff --git a/MorphicCore/MorphicCoreTests/StorageTests.swift b/MorphicCore/MorphicCoreTests/StorageTests.swift new file mode 100644 index 00000000..94ef0249 --- /dev/null +++ b/MorphicCore/MorphicCoreTests/StorageTests.swift @@ -0,0 +1,133 @@ +// Copyright 2020 Raising the Floor - International +// Copyright 2020 OCAD University +// +// Licensed under the New BSD license. You may not use this file except in +// compliance with this License. +// +// You may obtain a copy of the License at +// https://github.com/GPII/universal/blob/master/LICENSE.txt +// +// The R&D leading to these results received funding from the: +// * Rehabilitation Services Administration, US Dept. of Education under +// grant H421A150006 (APCP) +// * National Institute on Disability, Independent Living, and +// Rehabilitation Research (NIDILRR) +// * Administration for Independent Living & Dept. of Education under grants +// H133E080022 (RERC-IT) and H133E130028/90RE5003-01-00 (UIITA-RERC) +// * European Union's Seventh Framework Programme (FP7/2007-2013) grant +// agreement nos. 289016 (Cloud4all) and 610510 (Prosperity4All) +// * William and Flora Hewlett Foundation +// * Ontario Ministry of Research and Innovation +// * Canadian Foundation for Innovation +// * Adobe Foundation +// * Consumer Electronics Association Foundation +// +// StorageTests.swift +// MorphicCoreTests + +import XCTest +@testable import MorphicCore + +class StorageTests: XCTestCase { + + public private(set) var storage = Storage.shared + + var prefsToStore: Preferences! + let prefsId = UUID().uuidString + let userId = UUID().uuidString + + let magnifierName = "Magnifier" + let magFactorPref = "magfactor" + let magFactorVal: Double = 2.5 + let magFactorKey = Preferences.Key(solution: "Magnifier", preference: "magFactor") + + let inverseVideoPref = "inverse_video" + let inverseVideoVal: Bool = true + let inverseVideoKey = Preferences.Key(solution: "Magnifier", preference: "inverse_video") + + let defaultsId = "__default__" + let defaultsUserId:String? = nil + + let noSuchPrefsId = "NoSuchPrefsIdZzz" + let fileManager:FileManager = .default + + override func setUpWithError() throws { + prefsToStore = Preferences(identifier: prefsId) + prefsToStore.userId = userId + prefsToStore.set(magFactorVal, for: magFactorKey) + prefsToStore.set(inverseVideoVal, for: inverseVideoKey) + } + + override func tearDownWithError() throws { + let removeExpect = XCTestExpectation(description: "Test removal of preferences") + try storage.remove(record: prefsToStore, completion: { (_ status:Storage.LoadStatus, _ prefs:Preferences? ) -> Void in + let actualPrefsId = prefs?.identifier ?? "" + XCTAssertEqual(status, Storage.LoadStatus.success, "Test remove preferences from Storage " + actualPrefsId) + removeExpect.fulfill() + }) + wait(for: [removeExpect], timeout: 10.0) + + prefsToStore.remove(key: magFactorKey) // necessary? + prefsToStore.remove(key: inverseVideoKey) // necessary? + prefsToStore = nil + } + + func testSaveReload() { + let saveExpect = XCTestExpectation(description: "Test saving of preferences") + let loadExpect = XCTestExpectation(description: "Test loading of just saved preferences") + storage.save(record: prefsToStore, completion: { (_ saveSuccessful: Bool) -> Void in + XCTAssertTrue(saveSuccessful, "Test storing preferences") + saveExpect.fulfill() + }) + storage.load(identifier: prefsId, completion: { (_ status:Storage.LoadStatus, _ actual: Preferences?) -> Void in + XCTAssertEqual(status, Storage.LoadStatus.success, "Test success status on load of preferences") + guard let actualPrefs = actual else { + XCTFail("Test loading preferences: failed to load") + loadExpect.fulfill() + return + } + XCTAssertEqual(actualPrefs.userId, self.userId, "Test loaded preferences user id") + + let loadedMagFactor: Double = actualPrefs.get(key: self.magFactorKey) as! Double + XCTAssertEqual(loadedMagFactor, self.magFactorVal, "Test loaded magnification factor") + + let loadedInverseVideo: Bool = actualPrefs.get(key: self.inverseVideoKey) as! Bool + XCTAssertEqual(loadedInverseVideo, self.inverseVideoVal, "Test loaded inverse video") + + loadExpect.fulfill() + }) + wait(for: [saveExpect, loadExpect], timeout: 10.0, enforceOrder: true) + } + + func testContains() { + let containExpect = XCTestExpectation(description: "Test store contains preferences") + storage.save(record: prefsToStore, completion: { (_ succeeded: Bool) -> Void in + if succeeded { + var isContained = self.storage.contains(identifier: self.prefsId, type: Preferences.self) + XCTAssertTrue(isContained, "Test store contains known preferences") + isContained = self.storage.contains(identifier: self.noSuchPrefsId, type: Preferences.self) + XCTAssertFalse(isContained, "Test store does not contains unknown preferences") + } else { + XCTFail("Test store for contains preferences: failure to save preferences") + } + containExpect.fulfill() + }) + wait(for: [containExpect], timeout: 10.0) + } + + func testLoadDefaults() { + let loadExpect = XCTestExpectation(description: "Test loading from default preferences") + storage.load(identifier: defaultsId, completion: { (_ status:Storage.LoadStatus, _ actual: Preferences?) -> Void in + XCTAssertEqual(status, Storage.LoadStatus.success, "Test success status on load of default preferences") + guard let actualPrefs = actual else { + XCTFail("Test loading default preferences: failed to load") + loadExpect.fulfill() + return + } + XCTAssertEqual(actualPrefs.identifier, self.defaultsId, "Test loaded default preferences identifier") + XCTAssertEqual(actualPrefs.userId, self.defaultsUserId, "Test loaded default preferences user id") + loadExpect.fulfill() + }) + wait(for: [loadExpect], timeout: 10.0) + } +} diff --git a/MorphicCore/MorphicCoreTests/UserTests.swift b/MorphicCore/MorphicCoreTests/UserTests.swift new file mode 100644 index 00000000..9905063b --- /dev/null +++ b/MorphicCore/MorphicCoreTests/UserTests.swift @@ -0,0 +1,99 @@ +// Copyright 2020 Raising the Floor - International +// Copyright 2020 OCAD University +// +// Licensed under the New BSD license. You may not use this file except in +// compliance with this License. +// +// You may obtain a copy of the License at +// https://github.com/GPII/universal/blob/master/LICENSE.txt +// +// The R&D leading to these results received funding from the: +// * Rehabilitation Services Administration, US Dept. of Education under +// grant H421A150006 (APCP) +// * National Institute on Disability, Independent Living, and +// Rehabilitation Research (NIDILRR) +// * Administration for Independent Living & Dept. of Education under grants +// H133E080022 (RERC-IT) and H133E130028/90RE5003-01-00 (UIITA-RERC) +// * European Union's Seventh Framework Programme (FP7/2007-2013) grant +// agreement nos. 289016 (Cloud4all) and 610510 (Prosperity4All) +// * William and Flora Hewlett Foundation +// * Ontario Ministry of Research and Innovation +// * Canadian Foundation for Innovation +// * Adobe Foundation +// * Consumer Electronics Association Foundation +// +// UserTests.swift +// MorphicCoreTests + +import XCTest +@testable import MorphicCore + +class UserTests: XCTestCase { + + var freshNewUser: User! + var knownUserNoPrefs: User! + var userFromJson: User! + var userToEncode: User! + let identifierString = UUID().uuidString + let userAsJsonString = """ + { + "id": "12345678-1234-4321-6789-123456789ABC", + "preferences_id": "87654321-1234-4321-6789-123456789ABC", + "first_name": "Pat", + "last_name": "Jones", + "email": "pjones@somewhere.com" + } + """ + + override func setUpWithError() throws { + try super.setUpWithError() + + freshNewUser = User() + knownUserNoPrefs = User(identifier: identifierString) + + let userAsJsonData = userAsJsonString.data(using: .utf8) + userFromJson = try JSONDecoder().decode(User.self, from: userAsJsonData!) + // userFromJson = User( + + userToEncode = User(identifier: "12345678-1234-4321-6789-123456789ABC") + userToEncode.firstName = "Sandy" + userToEncode.lastName = "Smith" + userToEncode.email = "sandys@thebeach.com" + } + + override func tearDownWithError() throws { + try super.tearDownWithError() + + freshNewUser = nil + knownUserNoPrefs = nil + userFromJson = nil + userToEncode = nil + } + + func testInits() throws { + XCTAssertNotNil(UUID(uuidString: freshNewUser.identifier), "Create new User, user ID") + XCTAssertNotNil(UUID(uuidString: freshNewUser.preferencesId), "Create new User, preferences ID") + + XCTAssertNotNil(UUID(uuidString: knownUserNoPrefs.identifier), "Create User with identifier only, user ID") + XCTAssertNil(knownUserNoPrefs.preferencesId, "Create User with identifier only, preferences ID") + + // test init(from: decoder) + let msg = "User decoded from JSON" + XCTAssertNotNil(UUID(uuidString: userFromJson.identifier), msg + " identifier") + XCTAssertNotNil(UUID(uuidString: userFromJson.preferencesId), msg + " preferencesId") + XCTAssertNotNil(userFromJson.firstName, msg + " firstName") + XCTAssertNotNil(userFromJson.lastName, msg + " lastName") + XCTAssertNotNil(userFromJson.email, msg + " email") + } + + func testEncode() throws { + let jsonData = try JSONEncoder().encode(userToEncode) + let decodedUser = try JSONDecoder().decode(User.self, from: jsonData) + let msg = "User encode()," + XCTAssertEqual(userToEncode.identifier, decodedUser.identifier, msg + " identifier") + XCTAssertEqual(userToEncode.preferencesId, decodedUser.preferencesId, msg + " preferencesId") + XCTAssertEqual(userToEncode.firstName, decodedUser.firstName, msg + " firstName") + XCTAssertEqual(userToEncode.lastName, decodedUser.lastName, msg + " lastName") + XCTAssertEqual(userToEncode.email, decodedUser.email, msg + " email") + } +}