Skip to content

Commit 087227e

Browse files
authored
fix: Don't encode unmodified email when updating a user (#241)
* Don't encode emailVerified when saving user to server * Don't encode email if user didn't change it * Add testcases * Improve testcases
1 parent ddcf35f commit 087227e

File tree

5 files changed

+258
-18
lines changed

5 files changed

+258
-18
lines changed

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/1.9.10...main)
55
* _Contributing to this repo? Add info about your change here to be included in the next release_
66

7+
__Fixes__
8+
- 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).
9+
710
### 1.9.10
811
[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/1.9.9...1.9.10)
912
__Fixes__

Sources/ParseSwift/Objects/ParseUser.swift

+7-1
Original file line numberDiff line numberDiff line change
@@ -944,12 +944,18 @@ extension ParseUser {
944944
}
945945

946946
private func updateCommand() -> API.Command<Self, Self> {
947+
var mutableSelf = self
948+
if let currentUser = Self.current,
949+
currentUser.hasSameObjectId(as: mutableSelf) == true,
950+
currentUser.email == mutableSelf.email {
951+
mutableSelf.email = nil
952+
}
947953
let mapper = { (data) -> Self in
948954
try ParseCoding.jsonDecoder().decode(UpdateResponse.self, from: data).apply(to: self)
949955
}
950956
return API.Command<Self, Self>(method: .PUT,
951957
path: endpoint,
952-
body: self,
958+
body: mutableSelf,
953959
mapper: mapper)
954960
}
955961
}

Sources/ParseSwift/Types/CloudViewModel.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ open class CloudViewModel<T: ParseCloud>: CloudObservable {
2929
}
3030

3131
/// Updates and notifies when there is an error retrieving the results.
32-
open var error: ParseError? = nil {
32+
open var error: ParseError? {
3333
willSet {
3434
if newValue != nil {
3535
self.results = nil

Sources/ParseSwift/Types/QueryViewModel.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ open class QueryViewModel<T: ParseObject>: QueryObservable {
3838
}
3939

4040
/// Updates and notifies when there is an error retrieving the results.
41-
open var error: ParseError? = nil {
41+
open var error: ParseError? {
4242
willSet {
4343
if newValue != nil {
4444
results.removeAll()

Tests/ParseSwiftTests/ParseUserTests.swift

+246-15
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,16 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length
6161
self.email = "[email protected]"
6262
self.emailVerified = false
6363
}
64+
65+
func createUser() -> User {
66+
var user = User()
67+
user.objectId = objectId
68+
user.ACL = ACL
69+
user.customKey = customKey
70+
user.username = username
71+
user.email = email
72+
return user
73+
}
6474
}
6575

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

457-
func testSaveAndUpdateCurrentUser() { // swiftlint:disable:this function_body_length
458-
XCTAssertNil(User.current?.objectId)
459-
testLogin()
467+
func userSignUp() throws {
468+
let loginResponse = LoginSignupResponse()
469+
470+
MockURLProtocol.mockRequests { _ in
471+
do {
472+
let encoded = try loginResponse.getEncoder().encode(loginResponse, skipKeys: .none)
473+
return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0)
474+
} catch {
475+
return nil
476+
}
477+
}
478+
_ = try loginResponse.createUser().signup()
460479
MockURLProtocol.removeAll()
480+
guard let currentUser = User.current else {
481+
XCTFail("Should have a current user after signup")
482+
return
483+
}
484+
XCTAssertEqual(currentUser.objectId, loginResponse.objectId)
485+
XCTAssertEqual(currentUser.username, loginResponse.username)
486+
XCTAssertEqual(currentUser.email, loginResponse.email)
487+
XCTAssertEqual(currentUser.ACL, loginResponse.ACL)
488+
XCTAssertEqual(currentUser.customKey, loginResponse.customKey)
489+
}
490+
491+
func testUpdateCommandUnmodifiedEmail() throws {
492+
try userSignUp()
493+
guard let user = User.current,
494+
let objectId = user.objectId else {
495+
XCTFail("Should have current user.")
496+
return
497+
}
498+
XCTAssertNotNil(user.email)
499+
let command = try user.saveCommand()
500+
XCTAssertNotNil(command)
501+
XCTAssertEqual(command.path.urlComponent, "/users/\(objectId)")
502+
XCTAssertEqual(command.method, API.Method.PUT)
503+
XCTAssertNil(command.params)
504+
XCTAssertNotNil(command.body)
505+
XCTAssertNil(command.body?.email)
506+
}
507+
508+
func testUpdateCommandModifiedEmail() throws {
509+
try userSignUp()
510+
guard var user = User.current,
511+
let objectId = user.objectId else {
512+
XCTFail("Should have current user.")
513+
return
514+
}
515+
let email = "[email protected]"
516+
user.email = email
517+
XCTAssertNotNil(user.email)
518+
let command = try user.saveCommand()
519+
XCTAssertNotNil(command)
520+
XCTAssertEqual(command.path.urlComponent, "/users/\(objectId)")
521+
XCTAssertEqual(command.method, API.Method.PUT)
522+
XCTAssertNil(command.params)
523+
XCTAssertNotNil(command.body)
524+
XCTAssertEqual(command.body?.email, email)
525+
}
526+
527+
func testUpdateCommandNotCurrentModifiedEmail() throws {
528+
try userSignUp()
529+
var user = User()
530+
let objectId = "yarr"
531+
user.objectId = objectId
532+
let email = "[email protected]"
533+
user.email = email
534+
XCTAssertNotNil(user.email)
535+
let command = try user.saveCommand()
536+
XCTAssertNotNil(command)
537+
XCTAssertEqual(command.path.urlComponent, "/users/\(objectId)")
538+
XCTAssertEqual(command.method, API.Method.PUT)
539+
XCTAssertNil(command.params)
540+
XCTAssertNotNil(command.body)
541+
XCTAssertEqual(command.body?.email, email)
542+
}
543+
544+
func testSaveAndUpdateCurrentUser() throws { // swiftlint:disable:this function_body_length
545+
XCTAssertNil(User.current?.objectId)
546+
try userSignUp()
461547
XCTAssertNotNil(User.current?.objectId)
462548

463549
guard let user = User.current else {
464550
XCTFail("Should unwrap")
465551
return
466552
}
553+
XCTAssertNotNil(user.email)
554+
var userOnServer = user
555+
userOnServer.createdAt = User.current?.createdAt
556+
userOnServer.updatedAt = User.current?.updatedAt?.addingTimeInterval(+300)
557+
558+
let encoded: Data!
559+
do {
560+
encoded = try userOnServer.getEncoder().encode(userOnServer, skipKeys: .none)
561+
//Get dates in correct format from ParseDecoding strategy
562+
userOnServer = try userOnServer.getDecoder().decode(User.self, from: encoded)
563+
} catch {
564+
XCTFail("Should encode/decode. Error \(error)")
565+
return
566+
}
567+
MockURLProtocol.mockRequests { _ in
568+
return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0)
569+
}
570+
571+
do {
572+
let saved = try user.save(options: [.useMasterKey])
573+
XCTAssert(saved.hasSameObjectId(as: userOnServer))
574+
XCTAssertEqual(saved.email, user.email)
575+
guard let savedCreatedAt = saved.createdAt,
576+
let savedUpdatedAt = saved.updatedAt else {
577+
XCTFail("Should unwrap dates")
578+
return
579+
}
580+
guard let originalCreatedAt = user.createdAt,
581+
let originalUpdatedAt = user.updatedAt else {
582+
XCTFail("Should unwrap dates")
583+
return
584+
}
585+
XCTAssertEqual(savedCreatedAt, originalCreatedAt)
586+
XCTAssertGreaterThan(savedUpdatedAt, originalUpdatedAt)
587+
XCTAssertNil(saved.ACL)
588+
589+
//Should be updated in memory
590+
XCTAssertEqual(User.current?.updatedAt, savedUpdatedAt)
591+
XCTAssertEqual(User.current?.email, user.email)
467592

593+
#if !os(Linux) && !os(Android)
594+
//Should be updated in Keychain
595+
guard let keychainUser: CurrentUserContainer<BaseParseUser>
596+
= try? KeychainStore.shared.get(valueFor: ParseStorage.Keys.currentUser) else {
597+
XCTFail("Should get object from Keychain")
598+
return
599+
}
600+
XCTAssertEqual(keychainUser.currentUser?.updatedAt, savedUpdatedAt)
601+
XCTAssertEqual(keychainUser.currentUser?.email, user.email)
602+
#endif
603+
604+
} catch {
605+
XCTFail(error.localizedDescription)
606+
}
607+
}
608+
609+
func testSaveAndUpdateCurrentUserModifiedEmail() throws { // swiftlint:disable:this function_body_length
610+
XCTAssertNil(User.current?.objectId)
611+
try userSignUp()
612+
XCTAssertNotNil(User.current?.objectId)
613+
614+
guard var user = User.current else {
615+
XCTFail("Should unwrap")
616+
return
617+
}
618+
user.email = "[email protected]"
619+
XCTAssertNotEqual(User.current?.email, user.email)
468620
var userOnServer = user
469621
userOnServer.createdAt = User.current?.createdAt
470622
userOnServer.updatedAt = User.current?.updatedAt?.addingTimeInterval(+300)
@@ -485,6 +637,7 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length
485637
do {
486638
let saved = try user.save(options: [.useMasterKey])
487639
XCTAssert(saved.hasSameObjectId(as: userOnServer))
640+
XCTAssertEqual(saved.email, user.email)
488641
guard let savedCreatedAt = saved.createdAt,
489642
let savedUpdatedAt = saved.updatedAt else {
490643
XCTFail("Should unwrap dates")
@@ -501,6 +654,7 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length
501654

502655
//Should be updated in memory
503656
XCTAssertEqual(User.current?.updatedAt, savedUpdatedAt)
657+
XCTAssertEqual(User.current?.email, user.email)
504658

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

515670
} catch {
516671
XCTFail(error.localizedDescription)
517672
}
518673
}
519674

520-
func testSaveAsyncAndUpdateCurrentUser() { // swiftlint:disable:this function_body_length
675+
func testSaveAsyncAndUpdateCurrentUser() throws { // swiftlint:disable:this function_body_length
521676
XCTAssertNil(User.current?.objectId)
522-
testLogin()
523-
MockURLProtocol.removeAll()
677+
try userSignUp()
524678
XCTAssertNotNil(User.current?.objectId)
525679

526680
guard let user = User.current else {
527681
XCTFail("Should unwrap")
528682
return
529683
}
684+
XCTAssertNotNil(user.email)
685+
var userOnServer = user
686+
userOnServer.createdAt = User.current?.createdAt
687+
userOnServer.updatedAt = User.current?.updatedAt?.addingTimeInterval(+300)
688+
689+
let encoded: Data!
690+
do {
691+
encoded = try userOnServer.getEncoder().encode(userOnServer, skipKeys: .none)
692+
//Get dates in correct format from ParseDecoding strategy
693+
userOnServer = try userOnServer.getDecoder().decode(User.self, from: encoded)
694+
} catch {
695+
XCTFail("Should encode/decode. Error \(error)")
696+
return
697+
}
698+
MockURLProtocol.mockRequests { _ in
699+
return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0)
700+
}
530701

702+
let expectation1 = XCTestExpectation(description: "Fetch user1")
703+
user.save(options: [], callbackQueue: .global(qos: .background)) { result in
704+
705+
switch result {
706+
case .success(let saved):
707+
XCTAssert(saved.hasSameObjectId(as: userOnServer))
708+
XCTAssertEqual(saved.email, user.email)
709+
guard let savedCreatedAt = saved.createdAt,
710+
let savedUpdatedAt = saved.updatedAt else {
711+
XCTFail("Should unwrap dates")
712+
expectation1.fulfill()
713+
return
714+
}
715+
guard let originalCreatedAt = user.createdAt,
716+
let originalUpdatedAt = user.updatedAt else {
717+
XCTFail("Should unwrap dates")
718+
expectation1.fulfill()
719+
return
720+
}
721+
XCTAssertEqual(savedCreatedAt, originalCreatedAt)
722+
XCTAssertGreaterThan(savedUpdatedAt, originalUpdatedAt)
723+
XCTAssertNil(saved.ACL)
724+
725+
//Should be updated in memory
726+
XCTAssertEqual(User.current?.updatedAt, savedUpdatedAt)
727+
XCTAssertEqual(User.current?.email, user.email)
728+
729+
#if !os(Linux) && !os(Android)
730+
//Should be updated in Keychain
731+
guard let keychainUser: CurrentUserContainer<BaseParseUser>
732+
= try? KeychainStore.shared.get(valueFor: ParseStorage.Keys.currentUser) else {
733+
XCTFail("Should get object from Keychain")
734+
return
735+
}
736+
XCTAssertEqual(keychainUser.currentUser?.updatedAt, savedUpdatedAt)
737+
XCTAssertEqual(keychainUser.currentUser?.email, user.email)
738+
#endif
739+
740+
case .failure(let error):
741+
XCTFail(error.localizedDescription)
742+
}
743+
expectation1.fulfill()
744+
}
745+
wait(for: [expectation1], timeout: 20.0)
746+
}
747+
748+
func testSaveAsyncAndUpdateCurrentUserModifiedEmail() throws { // swiftlint:disable:this function_body_length
749+
XCTAssertNil(User.current?.objectId)
750+
try userSignUp()
751+
XCTAssertNotNil(User.current?.objectId)
752+
753+
guard var user = User.current else {
754+
XCTFail("Should unwrap")
755+
return
756+
}
757+
user.email = "[email protected]"
758+
XCTAssertNotEqual(User.current?.email, user.email)
531759
var userOnServer = user
532760
userOnServer.createdAt = User.current?.createdAt
533761
userOnServer.updatedAt = User.current?.updatedAt?.addingTimeInterval(+300)
@@ -549,10 +777,11 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length
549777
user.save(options: [], callbackQueue: .global(qos: .background)) { result in
550778

551779
switch result {
552-
case .success(let fetched):
553-
XCTAssert(fetched.hasSameObjectId(as: userOnServer))
554-
guard let fetchedCreatedAt = fetched.createdAt,
555-
let fetchedUpdatedAt = fetched.updatedAt else {
780+
case .success(let saved):
781+
XCTAssert(saved.hasSameObjectId(as: userOnServer))
782+
XCTAssertEqual(saved.email, user.email)
783+
guard let savedCreatedAt = saved.createdAt,
784+
let savedUpdatedAt = saved.updatedAt else {
556785
XCTFail("Should unwrap dates")
557786
expectation1.fulfill()
558787
return
@@ -563,12 +792,13 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length
563792
expectation1.fulfill()
564793
return
565794
}
566-
XCTAssertEqual(fetchedCreatedAt, originalCreatedAt)
567-
XCTAssertGreaterThan(fetchedUpdatedAt, originalUpdatedAt)
568-
XCTAssertNil(fetched.ACL)
795+
XCTAssertEqual(savedCreatedAt, originalCreatedAt)
796+
XCTAssertGreaterThan(savedUpdatedAt, originalUpdatedAt)
797+
XCTAssertNil(saved.ACL)
569798

570799
//Should be updated in memory
571-
XCTAssertEqual(User.current?.updatedAt, fetchedUpdatedAt)
800+
XCTAssertEqual(User.current?.updatedAt, savedUpdatedAt)
801+
XCTAssertEqual(User.current?.email, user.email)
572802

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

583814
case .failure(let error):

0 commit comments

Comments
 (0)