Skip to content

fix: Don't encode unmodified email when updating a user #241

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Sep 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 = "[email protected]"
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 = "[email protected]"
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 = "[email protected]"
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 = "[email protected]"
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 = "[email protected]"
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