Skip to content

Commit

Permalink
fix: Don't encode unmodified email when updating a user (#241)
Browse files Browse the repository at this point in the history
* Don't encode emailVerified when saving user to server

* Don't encode email if user didn't change it

* Add testcases

* Improve testcases
  • Loading branch information
cbaker6 authored Sep 22, 2021
1 parent ddcf35f commit 087227e
Show file tree
Hide file tree
Showing 5 changed files with 258 additions and 18 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/1.9.10...main)
* _Contributing to this repo? Add info about your change here to be included in the next release_

__Fixes__
- ParseUser shouldn't send email if it hasn't been modified or else email verification is resent ([#241](https://github.com/parse-community/Parse-Swift/pull/241)), thanks to [Corey Baker](https://github.com/cbaker6).

### 1.9.10
[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/1.9.9...1.9.10)
__Fixes__
Expand Down
8 changes: 7 additions & 1 deletion Sources/ParseSwift/Objects/ParseUser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -944,12 +944,18 @@ extension ParseUser {
}

private func updateCommand() -> API.Command<Self, Self> {
var mutableSelf = self
if let currentUser = Self.current,
currentUser.hasSameObjectId(as: mutableSelf) == true,
currentUser.email == mutableSelf.email {
mutableSelf.email = nil
}
let mapper = { (data) -> Self in
try ParseCoding.jsonDecoder().decode(UpdateResponse.self, from: data).apply(to: self)
}
return API.Command<Self, Self>(method: .PUT,
path: endpoint,
body: self,
body: mutableSelf,
mapper: mapper)
}
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/ParseSwift/Types/CloudViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ open class CloudViewModel<T: ParseCloud>: CloudObservable {
}

/// Updates and notifies when there is an error retrieving the results.
open var error: ParseError? = nil {
open var error: ParseError? {
willSet {
if newValue != nil {
self.results = nil
Expand Down
2 changes: 1 addition & 1 deletion Sources/ParseSwift/Types/QueryViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ open class QueryViewModel<T: ParseObject>: QueryObservable {
}

/// Updates and notifies when there is an error retrieving the results.
open var error: ParseError? = nil {
open var error: ParseError? {
willSet {
if newValue != nil {
results.removeAll()
Expand Down
261 changes: 246 additions & 15 deletions Tests/ParseSwiftTests/ParseUserTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,16 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length
self.email = "hello@parse.com"
self.emailVerified = false
}

func createUser() -> User {
var user = User()
user.objectId = objectId
user.ACL = ACL
user.customKey = customKey
user.username = username
user.email = email
return user
}
}

let loginUserName = "hello10"
Expand Down Expand Up @@ -454,17 +464,159 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length
XCTAssertNotNil(command.body)
}

func testSaveAndUpdateCurrentUser() { // swiftlint:disable:this function_body_length
XCTAssertNil(User.current?.objectId)
testLogin()
func userSignUp() throws {
let loginResponse = LoginSignupResponse()

MockURLProtocol.mockRequests { _ in
do {
let encoded = try loginResponse.getEncoder().encode(loginResponse, skipKeys: .none)
return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0)
} catch {
return nil
}
}
_ = try loginResponse.createUser().signup()
MockURLProtocol.removeAll()
guard let currentUser = User.current else {
XCTFail("Should have a current user after signup")
return
}
XCTAssertEqual(currentUser.objectId, loginResponse.objectId)
XCTAssertEqual(currentUser.username, loginResponse.username)
XCTAssertEqual(currentUser.email, loginResponse.email)
XCTAssertEqual(currentUser.ACL, loginResponse.ACL)
XCTAssertEqual(currentUser.customKey, loginResponse.customKey)
}

func testUpdateCommandUnmodifiedEmail() throws {
try userSignUp()
guard let user = User.current,
let objectId = user.objectId else {
XCTFail("Should have current user.")
return
}
XCTAssertNotNil(user.email)
let command = try user.saveCommand()
XCTAssertNotNil(command)
XCTAssertEqual(command.path.urlComponent, "/users/\(objectId)")
XCTAssertEqual(command.method, API.Method.PUT)
XCTAssertNil(command.params)
XCTAssertNotNil(command.body)
XCTAssertNil(command.body?.email)
}

func testUpdateCommandModifiedEmail() throws {
try userSignUp()
guard var user = User.current,
let objectId = user.objectId else {
XCTFail("Should have current user.")
return
}
let email = "peace@parse.com"
user.email = email
XCTAssertNotNil(user.email)
let command = try user.saveCommand()
XCTAssertNotNil(command)
XCTAssertEqual(command.path.urlComponent, "/users/\(objectId)")
XCTAssertEqual(command.method, API.Method.PUT)
XCTAssertNil(command.params)
XCTAssertNotNil(command.body)
XCTAssertEqual(command.body?.email, email)
}

func testUpdateCommandNotCurrentModifiedEmail() throws {
try userSignUp()
var user = User()
let objectId = "yarr"
user.objectId = objectId
let email = "peace@parse.com"
user.email = email
XCTAssertNotNil(user.email)
let command = try user.saveCommand()
XCTAssertNotNil(command)
XCTAssertEqual(command.path.urlComponent, "/users/\(objectId)")
XCTAssertEqual(command.method, API.Method.PUT)
XCTAssertNil(command.params)
XCTAssertNotNil(command.body)
XCTAssertEqual(command.body?.email, email)
}

func testSaveAndUpdateCurrentUser() throws { // swiftlint:disable:this function_body_length
XCTAssertNil(User.current?.objectId)
try userSignUp()
XCTAssertNotNil(User.current?.objectId)

guard let user = User.current else {
XCTFail("Should unwrap")
return
}
XCTAssertNotNil(user.email)
var userOnServer = user
userOnServer.createdAt = User.current?.createdAt
userOnServer.updatedAt = User.current?.updatedAt?.addingTimeInterval(+300)

let encoded: Data!
do {
encoded = try userOnServer.getEncoder().encode(userOnServer, skipKeys: .none)
//Get dates in correct format from ParseDecoding strategy
userOnServer = try userOnServer.getDecoder().decode(User.self, from: encoded)
} catch {
XCTFail("Should encode/decode. Error \(error)")
return
}
MockURLProtocol.mockRequests { _ in
return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0)
}

do {
let saved = try user.save(options: [.useMasterKey])
XCTAssert(saved.hasSameObjectId(as: userOnServer))
XCTAssertEqual(saved.email, user.email)
guard let savedCreatedAt = saved.createdAt,
let savedUpdatedAt = saved.updatedAt else {
XCTFail("Should unwrap dates")
return
}
guard let originalCreatedAt = user.createdAt,
let originalUpdatedAt = user.updatedAt else {
XCTFail("Should unwrap dates")
return
}
XCTAssertEqual(savedCreatedAt, originalCreatedAt)
XCTAssertGreaterThan(savedUpdatedAt, originalUpdatedAt)
XCTAssertNil(saved.ACL)

//Should be updated in memory
XCTAssertEqual(User.current?.updatedAt, savedUpdatedAt)
XCTAssertEqual(User.current?.email, user.email)

#if !os(Linux) && !os(Android)
//Should be updated in Keychain
guard let keychainUser: CurrentUserContainer<BaseParseUser>
= try? KeychainStore.shared.get(valueFor: ParseStorage.Keys.currentUser) else {
XCTFail("Should get object from Keychain")
return
}
XCTAssertEqual(keychainUser.currentUser?.updatedAt, savedUpdatedAt)
XCTAssertEqual(keychainUser.currentUser?.email, user.email)
#endif

} catch {
XCTFail(error.localizedDescription)
}
}

func testSaveAndUpdateCurrentUserModifiedEmail() throws { // swiftlint:disable:this function_body_length
XCTAssertNil(User.current?.objectId)
try userSignUp()
XCTAssertNotNil(User.current?.objectId)

guard var user = User.current else {
XCTFail("Should unwrap")
return
}
user.email = "pease@parse.com"
XCTAssertNotEqual(User.current?.email, user.email)
var userOnServer = user
userOnServer.createdAt = User.current?.createdAt
userOnServer.updatedAt = User.current?.updatedAt?.addingTimeInterval(+300)
Expand All @@ -485,6 +637,7 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length
do {
let saved = try user.save(options: [.useMasterKey])
XCTAssert(saved.hasSameObjectId(as: userOnServer))
XCTAssertEqual(saved.email, user.email)
guard let savedCreatedAt = saved.createdAt,
let savedUpdatedAt = saved.updatedAt else {
XCTFail("Should unwrap dates")
Expand All @@ -501,6 +654,7 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length

//Should be updated in memory
XCTAssertEqual(User.current?.updatedAt, savedUpdatedAt)
XCTAssertEqual(User.current?.email, user.email)

#if !os(Linux) && !os(Android)
//Should be updated in Keychain
Expand All @@ -510,24 +664,98 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length
return
}
XCTAssertEqual(keychainUser.currentUser?.updatedAt, savedUpdatedAt)
XCTAssertEqual(keychainUser.currentUser?.email, user.email)
#endif

} catch {
XCTFail(error.localizedDescription)
}
}

func testSaveAsyncAndUpdateCurrentUser() { // swiftlint:disable:this function_body_length
func testSaveAsyncAndUpdateCurrentUser() throws { // swiftlint:disable:this function_body_length
XCTAssertNil(User.current?.objectId)
testLogin()
MockURLProtocol.removeAll()
try userSignUp()
XCTAssertNotNil(User.current?.objectId)

guard let user = User.current else {
XCTFail("Should unwrap")
return
}
XCTAssertNotNil(user.email)
var userOnServer = user
userOnServer.createdAt = User.current?.createdAt
userOnServer.updatedAt = User.current?.updatedAt?.addingTimeInterval(+300)

let encoded: Data!
do {
encoded = try userOnServer.getEncoder().encode(userOnServer, skipKeys: .none)
//Get dates in correct format from ParseDecoding strategy
userOnServer = try userOnServer.getDecoder().decode(User.self, from: encoded)
} catch {
XCTFail("Should encode/decode. Error \(error)")
return
}
MockURLProtocol.mockRequests { _ in
return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0)
}

let expectation1 = XCTestExpectation(description: "Fetch user1")
user.save(options: [], callbackQueue: .global(qos: .background)) { result in

switch result {
case .success(let saved):
XCTAssert(saved.hasSameObjectId(as: userOnServer))
XCTAssertEqual(saved.email, user.email)
guard let savedCreatedAt = saved.createdAt,
let savedUpdatedAt = saved.updatedAt else {
XCTFail("Should unwrap dates")
expectation1.fulfill()
return
}
guard let originalCreatedAt = user.createdAt,
let originalUpdatedAt = user.updatedAt else {
XCTFail("Should unwrap dates")
expectation1.fulfill()
return
}
XCTAssertEqual(savedCreatedAt, originalCreatedAt)
XCTAssertGreaterThan(savedUpdatedAt, originalUpdatedAt)
XCTAssertNil(saved.ACL)

//Should be updated in memory
XCTAssertEqual(User.current?.updatedAt, savedUpdatedAt)
XCTAssertEqual(User.current?.email, user.email)

#if !os(Linux) && !os(Android)
//Should be updated in Keychain
guard let keychainUser: CurrentUserContainer<BaseParseUser>
= try? KeychainStore.shared.get(valueFor: ParseStorage.Keys.currentUser) else {
XCTFail("Should get object from Keychain")
return
}
XCTAssertEqual(keychainUser.currentUser?.updatedAt, savedUpdatedAt)
XCTAssertEqual(keychainUser.currentUser?.email, user.email)
#endif

case .failure(let error):
XCTFail(error.localizedDescription)
}
expectation1.fulfill()
}
wait(for: [expectation1], timeout: 20.0)
}

func testSaveAsyncAndUpdateCurrentUserModifiedEmail() throws { // swiftlint:disable:this function_body_length
XCTAssertNil(User.current?.objectId)
try userSignUp()
XCTAssertNotNil(User.current?.objectId)

guard var user = User.current else {
XCTFail("Should unwrap")
return
}
user.email = "pease@parse.com"
XCTAssertNotEqual(User.current?.email, user.email)
var userOnServer = user
userOnServer.createdAt = User.current?.createdAt
userOnServer.updatedAt = User.current?.updatedAt?.addingTimeInterval(+300)
Expand All @@ -549,10 +777,11 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length
user.save(options: [], callbackQueue: .global(qos: .background)) { result in

switch result {
case .success(let fetched):
XCTAssert(fetched.hasSameObjectId(as: userOnServer))
guard let fetchedCreatedAt = fetched.createdAt,
let fetchedUpdatedAt = fetched.updatedAt else {
case .success(let saved):
XCTAssert(saved.hasSameObjectId(as: userOnServer))
XCTAssertEqual(saved.email, user.email)
guard let savedCreatedAt = saved.createdAt,
let savedUpdatedAt = saved.updatedAt else {
XCTFail("Should unwrap dates")
expectation1.fulfill()
return
Expand All @@ -563,12 +792,13 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length
expectation1.fulfill()
return
}
XCTAssertEqual(fetchedCreatedAt, originalCreatedAt)
XCTAssertGreaterThan(fetchedUpdatedAt, originalUpdatedAt)
XCTAssertNil(fetched.ACL)
XCTAssertEqual(savedCreatedAt, originalCreatedAt)
XCTAssertGreaterThan(savedUpdatedAt, originalUpdatedAt)
XCTAssertNil(saved.ACL)

//Should be updated in memory
XCTAssertEqual(User.current?.updatedAt, fetchedUpdatedAt)
XCTAssertEqual(User.current?.updatedAt, savedUpdatedAt)
XCTAssertEqual(User.current?.email, user.email)

#if !os(Linux) && !os(Android)
//Should be updated in Keychain
Expand All @@ -577,7 +807,8 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length
XCTFail("Should get object from Keychain")
return
}
XCTAssertEqual(keychainUser.currentUser?.updatedAt, fetchedUpdatedAt)
XCTAssertEqual(keychainUser.currentUser?.updatedAt, savedUpdatedAt)
XCTAssertEqual(keychainUser.currentUser?.email, user.email)
#endif

case .failure(let error):
Expand Down

0 comments on commit 087227e

Please sign in to comment.