From c7ef4cf66d449ffcc30a7c2735ed28c88533f6a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ulas=CC=A7=20Sancak?= Date: Mon, 20 Jun 2022 20:54:40 +0300 Subject: [PATCH 1/6] feat: add Spotify login to 3rd party login providers --- ParseSwift.xcodeproj/project.pbxproj | 38 +++++ .../ParseSpotify/ParseSpotify+async.swift | 87 ++++++++++ .../ParseSpotify/ParseSpotify+combine.swift | 84 +++++++++ .../3rd Party/ParseSpotify/ParseSpotify.swift | 160 ++++++++++++++++++ 4 files changed, 369 insertions(+) create mode 100644 Sources/ParseSwift/Authentication/3rd Party/ParseSpotify/ParseSpotify+async.swift create mode 100644 Sources/ParseSwift/Authentication/3rd Party/ParseSpotify/ParseSpotify+combine.swift create mode 100644 Sources/ParseSwift/Authentication/3rd Party/ParseSpotify/ParseSpotify.swift diff --git a/ParseSwift.xcodeproj/project.pbxproj b/ParseSwift.xcodeproj/project.pbxproj index 3eba5962a..b48825038 100644 --- a/ParseSwift.xcodeproj/project.pbxproj +++ b/ParseSwift.xcodeproj/project.pbxproj @@ -760,6 +760,18 @@ 7C4C0947285EA60E00F202C6 /* ParseInstagramAsyncTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C4C0946285EA60E00F202C6 /* ParseInstagramAsyncTests.swift */; }; 7C4C0948285EA60E00F202C6 /* ParseInstagramAsyncTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C4C0946285EA60E00F202C6 /* ParseInstagramAsyncTests.swift */; }; 7C4C0949285EA60E00F202C6 /* ParseInstagramAsyncTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C4C0946285EA60E00F202C6 /* ParseInstagramAsyncTests.swift */; }; + 7C55F9E72860CD6B002A352D /* ParseSpotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C55F9E62860CD6B002A352D /* ParseSpotify.swift */; }; + 7C55F9E82860CD6B002A352D /* ParseSpotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C55F9E62860CD6B002A352D /* ParseSpotify.swift */; }; + 7C55F9E92860CD6B002A352D /* ParseSpotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C55F9E62860CD6B002A352D /* ParseSpotify.swift */; }; + 7C55F9EA2860CD6B002A352D /* ParseSpotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C55F9E62860CD6B002A352D /* ParseSpotify.swift */; }; + 7C55F9EC2860CEA6002A352D /* ParseSpotify+async.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C55F9EB2860CEA6002A352D /* ParseSpotify+async.swift */; }; + 7C55F9ED2860CEA6002A352D /* ParseSpotify+async.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C55F9EB2860CEA6002A352D /* ParseSpotify+async.swift */; }; + 7C55F9EE2860CEA6002A352D /* ParseSpotify+async.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C55F9EB2860CEA6002A352D /* ParseSpotify+async.swift */; }; + 7C55F9EF2860CEA6002A352D /* ParseSpotify+async.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C55F9EB2860CEA6002A352D /* ParseSpotify+async.swift */; }; + 7C55F9F12860CEEF002A352D /* ParseSpotify+combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C55F9F02860CEEF002A352D /* ParseSpotify+combine.swift */; }; + 7C55F9F22860CEEF002A352D /* ParseSpotify+combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C55F9F02860CEEF002A352D /* ParseSpotify+combine.swift */; }; + 7C55F9F32860CEEF002A352D /* ParseSpotify+combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C55F9F02860CEEF002A352D /* ParseSpotify+combine.swift */; }; + 7C55F9F42860CEEF002A352D /* ParseSpotify+combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C55F9F02860CEEF002A352D /* ParseSpotify+combine.swift */; }; 7FFF552E2217E72A007C3B4E /* AnyEncodableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FFF552B2217E729007C3B4E /* AnyEncodableTests.swift */; }; 7FFF552F2217E72A007C3B4E /* AnyCodableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FFF552C2217E729007C3B4E /* AnyCodableTests.swift */; }; 7FFF55302217E72A007C3B4E /* AnyDecodableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FFF552D2217E729007C3B4E /* AnyDecodableTests.swift */; }; @@ -1311,6 +1323,9 @@ 7C4C093E285EA3A000F202C6 /* ParseInstagramTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ParseInstagramTests.swift; sourceTree = ""; }; 7C4C0942285EA56E00F202C6 /* ParseInstagramCombineTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ParseInstagramCombineTests.swift; sourceTree = ""; }; 7C4C0946285EA60E00F202C6 /* ParseInstagramAsyncTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ParseInstagramAsyncTests.swift; sourceTree = ""; }; + 7C55F9E62860CD6B002A352D /* ParseSpotify.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ParseSpotify.swift; sourceTree = ""; }; + 7C55F9EB2860CEA6002A352D /* ParseSpotify+async.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ParseSpotify+async.swift"; sourceTree = ""; }; + 7C55F9F02860CEEF002A352D /* ParseSpotify+combine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ParseSpotify+combine.swift"; sourceTree = ""; }; 7FFF552B2217E729007C3B4E /* AnyEncodableTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnyEncodableTests.swift; sourceTree = ""; }; 7FFF552C2217E729007C3B4E /* AnyCodableTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnyCodableTests.swift; sourceTree = ""; }; 7FFF552D2217E729007C3B4E /* AnyDecodableTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnyDecodableTests.swift; sourceTree = ""; }; @@ -1907,6 +1922,7 @@ 70F03A212780B8D000E5AFB4 /* ParseGoogle */, 703B096226BF486C005A112F /* ParseLDAP */, 70F03A322780C7EB00E5AFB4 /* ParseLinkedIn */, + 7C55F9E52860CD48002A352D /* ParseSpotify */, 703B096326BF487E005A112F /* ParseTwitter */, ); path = "3rd Party"; @@ -1978,6 +1994,16 @@ path = ParseInstagram; sourceTree = ""; }; + 7C55F9E52860CD48002A352D /* ParseSpotify */ = { + isa = PBXGroup; + children = ( + 7C55F9E62860CD6B002A352D /* ParseSpotify.swift */, + 7C55F9EB2860CEA6002A352D /* ParseSpotify+async.swift */, + 7C55F9F02860CEEF002A352D /* ParseSpotify+combine.swift */, + ); + path = ParseSpotify; + sourceTree = ""; + }; 7FFF552A2217E729007C3B4E /* AnyCodableTests */ = { isa = PBXGroup; children = ( @@ -2578,6 +2604,7 @@ buildActionMask = 2147483647; files = ( F97B463724D9C74400F4A88B /* Responses.swift in Sources */, + 7C55F9EC2860CEA6002A352D /* ParseSpotify+async.swift in Sources */, 916786E2259B7DDA00BB5B4E /* ParseCloud.swift in Sources */, 70CE0AC6285FD5A800DAEA86 /* ParseHookFunctionable+combine.swift in Sources */, 91F346B9269B766C005727B6 /* CloudViewModel.swift in Sources */, @@ -2652,6 +2679,7 @@ 70CE0A9E28592A2B00DAEA86 /* ParseCloudUser.swift in Sources */, 705A9A2F25991C1400B3547F /* Fileable.swift in Sources */, 89899D342603CF36002E2043 /* ParseTwitter.swift in Sources */, + 7C55F9F12860CEEF002A352D /* ParseSpotify+combine.swift in Sources */, 70B4E0BC2762F1D5004C9757 /* QueryConstraint.swift in Sources */, 70C167B427304F09009F4E30 /* Pointer+async.swift in Sources */, 705025AE28456106008D6624 /* ParsePushStatusable.swift in Sources */, @@ -2663,6 +2691,7 @@ 700396F825A394AE0052CB31 /* ParseLiveQueryDelegate.swift in Sources */, F97B465A24D9C78C00F4A88B /* Increment.swift in Sources */, 7045769326BD8F8100F86F71 /* ParseInstallation+async.swift in Sources */, + 7C55F9E72860CD6B002A352D /* ParseSpotify.swift in Sources */, 7003960925A184EF0052CB31 /* ParseLiveQuery.swift in Sources */, 7044C17525C4ECFF0011F6E7 /* ParseCloud+combine.swift in Sources */, 705025B32845C302008D6624 /* ParsePushStatus.swift in Sources */, @@ -2874,6 +2903,7 @@ buildActionMask = 2147483647; files = ( F97B463824D9C74400F4A88B /* Responses.swift in Sources */, + 7C55F9ED2860CEA6002A352D /* ParseSpotify+async.swift in Sources */, 916786E3259B7DDA00BB5B4E /* ParseCloud.swift in Sources */, 70CE0AC7285FD5A800DAEA86 /* ParseHookFunctionable+combine.swift in Sources */, 91F346BA269B766D005727B6 /* CloudViewModel.swift in Sources */, @@ -2948,6 +2978,7 @@ 70CE0A9F28592A2B00DAEA86 /* ParseCloudUser.swift in Sources */, 89899D332603CF36002E2043 /* ParseTwitter.swift in Sources */, 70B4E0BD2762F1D5004C9757 /* QueryConstraint.swift in Sources */, + 7C55F9F22860CEEF002A352D /* ParseSpotify+combine.swift in Sources */, 70C167B527304F09009F4E30 /* Pointer+async.swift in Sources */, F97B464B24D9C78B00F4A88B /* Delete.swift in Sources */, 705025AF28456106008D6624 /* ParsePushStatusable.swift in Sources */, @@ -2959,6 +2990,7 @@ F97B465B24D9C78C00F4A88B /* Increment.swift in Sources */, 7045769426BD8F8100F86F71 /* ParseInstallation+async.swift in Sources */, 7003960A25A184EF0052CB31 /* ParseLiveQuery.swift in Sources */, + 7C55F9E82860CD6B002A352D /* ParseSpotify.swift in Sources */, 7044C17625C4ECFF0011F6E7 /* ParseCloud+combine.swift in Sources */, F97B45E324D9C6F200F4A88B /* AnyEncodable.swift in Sources */, 705025B42845C302008D6624 /* ParsePushStatus.swift in Sources */, @@ -3295,6 +3327,7 @@ buildActionMask = 2147483647; files = ( F97B45D524D9C6F200F4A88B /* AnyDecodable.swift in Sources */, + 7C55F9EF2860CEA6002A352D /* ParseSpotify+async.swift in Sources */, 916786E5259B7DDA00BB5B4E /* ParseCloud.swift in Sources */, 70CE0AC9285FD5A800DAEA86 /* ParseHookFunctionable+combine.swift in Sources */, 91F346BC269B766D005727B6 /* CloudViewModel.swift in Sources */, @@ -3369,6 +3402,7 @@ 70CE0AA128592A2B00DAEA86 /* ParseCloudUser.swift in Sources */, 705A9A3225991C1400B3547F /* Fileable.swift in Sources */, 70B4E0BF2762F1D5004C9757 /* QueryConstraint.swift in Sources */, + 7C55F9F42860CEEF002A352D /* ParseSpotify+combine.swift in Sources */, 89899D282603CF35002E2043 /* ParseTwitter.swift in Sources */, 70C167B727304F09009F4E30 /* Pointer+async.swift in Sources */, 705025B128456106008D6624 /* ParsePushStatusable.swift in Sources */, @@ -3380,6 +3414,7 @@ 700396FB25A394AE0052CB31 /* ParseLiveQueryDelegate.swift in Sources */, F97B463A24D9C74400F4A88B /* Responses.swift in Sources */, 7045769626BD8F8100F86F71 /* ParseInstallation+async.swift in Sources */, + 7C55F9EA2860CD6B002A352D /* ParseSpotify.swift in Sources */, 7003960C25A184EF0052CB31 /* ParseLiveQuery.swift in Sources */, 7044C17825C4ECFF0011F6E7 /* ParseCloud+combine.swift in Sources */, 705025B62845C302008D6624 /* ParsePushStatus.swift in Sources */, @@ -3475,6 +3510,7 @@ buildActionMask = 2147483647; files = ( F97B45D424D9C6F200F4A88B /* AnyDecodable.swift in Sources */, + 7C55F9EE2860CEA6002A352D /* ParseSpotify+async.swift in Sources */, 916786E4259B7DDA00BB5B4E /* ParseCloud.swift in Sources */, 70CE0AC8285FD5A800DAEA86 /* ParseHookFunctionable+combine.swift in Sources */, 91F346BB269B766D005727B6 /* CloudViewModel.swift in Sources */, @@ -3549,6 +3585,7 @@ 70CE0AA028592A2B00DAEA86 /* ParseCloudUser.swift in Sources */, 705A9A3125991C1400B3547F /* Fileable.swift in Sources */, 70B4E0BE2762F1D5004C9757 /* QueryConstraint.swift in Sources */, + 7C55F9F32860CEEF002A352D /* ParseSpotify+combine.swift in Sources */, 89899D322603CF35002E2043 /* ParseTwitter.swift in Sources */, 70C167B627304F09009F4E30 /* Pointer+async.swift in Sources */, 705025B028456106008D6624 /* ParsePushStatusable.swift in Sources */, @@ -3560,6 +3597,7 @@ 700396FA25A394AE0052CB31 /* ParseLiveQueryDelegate.swift in Sources */, F97B463924D9C74400F4A88B /* Responses.swift in Sources */, 7045769526BD8F8100F86F71 /* ParseInstallation+async.swift in Sources */, + 7C55F9E92860CD6B002A352D /* ParseSpotify.swift in Sources */, 7003960B25A184EF0052CB31 /* ParseLiveQuery.swift in Sources */, 7044C17725C4ECFF0011F6E7 /* ParseCloud+combine.swift in Sources */, 705025B52845C302008D6624 /* ParsePushStatus.swift in Sources */, diff --git a/Sources/ParseSwift/Authentication/3rd Party/ParseSpotify/ParseSpotify+async.swift b/Sources/ParseSwift/Authentication/3rd Party/ParseSpotify/ParseSpotify+async.swift new file mode 100644 index 000000000..29fcade1b --- /dev/null +++ b/Sources/ParseSwift/Authentication/3rd Party/ParseSpotify/ParseSpotify+async.swift @@ -0,0 +1,87 @@ +// +// ParseSpotify+async.swift +// ParseSwift +// +// Created by Ulaş Sancak on 06/20/22. +// Copyright © 2022 Parse Community. All rights reserved. +// + +#if compiler(>=5.5.2) && canImport(_Concurrency) +import Foundation + +public extension ParseSpotify { + // MARK: Async/Await + + /** + Login a `ParseUser` *asynchronously* using Spotify authentication. + - parameter id: The **Spotify profile id** from **Spotify**. + - parameter accessToken: Required **access_token** from **Spotify**. + - parameter options: A set of header options sent to the server. Defaults to an empty set. + - returns: An instance of the logged in `ParseUser`. + - throws: An error of type `ParseError`. + */ + func login(id: String, + accessToken: String, + options: API.Options = []) async throws -> AuthenticatedUser { + try await withCheckedThrowingContinuation { continuation in + self.login(id: id, + accessToken: accessToken, + options: options, + completion: continuation.resume) + } + } + + /** + Login a `ParseUser` *asynchronously* using Spotify authentication. + - parameter authData: Dictionary containing key/values. + - returns: An instance of the logged in `ParseUser`. + - throws: An error of type `ParseError`. + */ + func login(authData: [String: String], + options: API.Options = []) async throws -> AuthenticatedUser { + try await withCheckedThrowingContinuation { continuation in + self.login(authData: authData, + options: options, + completion: continuation.resume) + } + } +} + +public extension ParseSpotify { + + /** + Link the *current* `ParseUser` *asynchronously* using Spotify authentication. + - parameter id: The **Spotify profile id** from **Spotify**. + - parameter accessToken: Required **access_token** from **Spotify**. + - parameter options: A set of header options sent to the server. Defaults to an empty set. + - returns: An instance of the logged in `ParseUser`. + - throws: An error of type `ParseError`. + */ + func link(id: String, + accessToken: String, + options: API.Options = []) async throws -> AuthenticatedUser { + try await withCheckedThrowingContinuation { continuation in + self.link(id: id, + accessToken: accessToken, + options: options, + completion: continuation.resume) + } + } + + /** + Link the *current* `ParseUser` *asynchronously* using Spotify authentication. + - parameter authData: Dictionary containing key/values. + - parameter options: A set of header options sent to the server. Defaults to an empty set. + - returns: An instance of the logged in `ParseUser`. + - throws: An error of type `ParseError`. + */ + func link(authData: [String: String], + options: API.Options = []) async throws -> AuthenticatedUser { + try await withCheckedThrowingContinuation { continuation in + self.link(authData: authData, + options: options, + completion: continuation.resume) + } + } +} +#endif diff --git a/Sources/ParseSwift/Authentication/3rd Party/ParseSpotify/ParseSpotify+combine.swift b/Sources/ParseSwift/Authentication/3rd Party/ParseSpotify/ParseSpotify+combine.swift new file mode 100644 index 000000000..f2c20319c --- /dev/null +++ b/Sources/ParseSwift/Authentication/3rd Party/ParseSpotify/ParseSpotify+combine.swift @@ -0,0 +1,84 @@ +// +// ParseSpotify+combine.swift +// ParseSwift +// +// Created by Ulaş Sancak on 06/20/22. +// Copyright © 2022 Parse Community. All rights reserved. +// + +#if canImport(Combine) +import Foundation +import Combine + +public extension ParseSpotify { + // MARK: Combine + /** + Login a `ParseUser` *asynchronously* using Spotify authentication. Publishes when complete. + - parameter id: The **Spotify profile id** from **Spotify**. + - parameter accessToken: Required **access_token** from **Spotify**. + - parameter options: A set of header options sent to the server. Defaults to an empty set. + - returns: A publisher that eventually produces a single value and then finishes or fails. + */ + func loginPublisher(id: String, + accessToken: String, + options: API.Options = []) -> Future { + Future { promise in + self.login(id: id, + accessToken: accessToken, + options: options, + completion: promise) + } + } + + /** + Login a `ParseUser` *asynchronously* using Spotify authentication. Publishes when complete. + - parameter authData: Dictionary containing key/values. + - returns: A publisher that eventually produces a single value and then finishes or fails. + */ + func loginPublisher(authData: [String: String], + options: API.Options = []) -> Future { + Future { promise in + self.login(authData: authData, + options: options, + completion: promise) + } + } +} + +public extension ParseSpotify { + /** + Link the *current* `ParseUser` *asynchronously* using Spotify authentication. + Publishes when complete. + - parameter id: The **Spotify profile id** from **Spotify**. + - parameter accessToken: Required **access_token** from **Spotify**. + - parameter options: A set of header options sent to the server. Defaults to an empty set. + - returns: A publisher that eventually produces a single value and then finishes or fails. + */ + func linkPublisher(id: String, + accessToken: String, + options: API.Options = []) -> Future { + Future { promise in + self.link(id: id, + accessToken: accessToken, + options: options, + completion: promise) + } + } + + /** + Link the *current* `ParseUser` *asynchronously* using Spotify authentication. + Publishes when complete. + - parameter authData: Dictionary containing key/values. + - returns: A publisher that eventually produces a single value and then finishes or fails. + */ + func linkPublisher(authData: [String: String], + options: API.Options = []) -> Future { + Future { promise in + self.link(authData: authData, + options: options, + completion: promise) + } + } +} + +#endif diff --git a/Sources/ParseSwift/Authentication/3rd Party/ParseSpotify/ParseSpotify.swift b/Sources/ParseSwift/Authentication/3rd Party/ParseSpotify/ParseSpotify.swift new file mode 100644 index 000000000..f3cd0c278 --- /dev/null +++ b/Sources/ParseSwift/Authentication/3rd Party/ParseSpotify/ParseSpotify.swift @@ -0,0 +1,160 @@ +// +// ParseSpotify.swift +// ParseSwift +// +// Created by Ulaş Sancak on 06/20/22. +// Copyright © 2022 Parse Community. All rights reserved. +// + +import Foundation + +// swiftlint:disable line_length + +/** + Provides utility functions for working with Spotify User Authentication and `ParseUser`'s. + Be sure your Parse Server is configured for [sign in with Spotify](https://docs.parseplatform.org/parse-server/guide/#spotify-authdata) + For information on acquiring Spotify sign-in credentials to use with `ParseSpotify`, refer to [Spotify's Documentation](https://developer.spotify.com/documentation/general/guides/authorization/) + */ +public struct ParseSpotify: ParseAuthentication { + + /// Authentication keys required for Spotify authentication. + enum AuthenticationKeys: String, Codable { + case id + case accessToken = "access_token" + + /// Properly makes an authData dictionary with the required keys. + /// - parameter id: Required id for the user. + /// - parameter accessToken: Required access token for Spotify. + /// - returns: authData dictionary. + func makeDictionary(id: String, + accessToken: String) -> [String: String] { + + let returnDictionary = [ + AuthenticationKeys.id.rawValue: id, + AuthenticationKeys.accessToken.rawValue: accessToken + ] + return returnDictionary + } + + /// Verifies all mandatory keys are in authData. + /// - parameter authData: Dictionary containing key/values. + /// - returns: **true** if all the mandatory keys are present, **false** otherwise. + func verifyMandatoryKeys(authData: [String: String]) -> Bool { + guard authData[AuthenticationKeys.id.rawValue] != nil, + authData[AuthenticationKeys.accessToken.rawValue] != nil else { + return false + } + return true + } + } + + public static var __type: String { // swiftlint:disable:this identifier_name + "spotify" + } + + public init() { } +} + +// MARK: Login +public extension ParseSpotify { + + /** + Login a `ParseUser` *asynchronously* using Spotify authentication. + - parameter id: The **Spotify profile id** from **Spotify**. + - parameter accessToken: Required **access_token** from **Spotify**. + - parameter options: A set of header options sent to the server. Defaults to an empty set. + - parameter callbackQueue: The queue to return to after completion. Default value of .main. + - parameter completion: The block to execute. + */ + func login(id: String, + accessToken: String, + options: API.Options = [], + callbackQueue: DispatchQueue = .main, + completion: @escaping (Result) -> Void) { + + let spotifyAuthData = AuthenticationKeys.id + .makeDictionary(id: id, + accessToken: accessToken) +// print(spotifyAuthData) + login(authData: spotifyAuthData, + options: options, + callbackQueue: callbackQueue, + completion: completion) + } + + func login(authData: [String: String], + options: API.Options = [], + callbackQueue: DispatchQueue = .main, + completion: @escaping (Result) -> Void) { + guard AuthenticationKeys.id.verifyMandatoryKeys(authData: authData) else { + callbackQueue.async { + completion(.failure(.init(code: .unknownError, + message: "Should have authData in consisting of keys \"id\" and \"accessToken\"."))) + } + return + } + AuthenticatedUser.login(Self.__type, + authData: authData, + options: options, + callbackQueue: callbackQueue, + completion: completion) + } +} + +// MARK: Link +public extension ParseSpotify { + + /** + Link the *current* `ParseUser` *asynchronously* using Spotify authentication. + - parameter id: The **Spotify profile id** from **Spotify**. + - parameter accessToken: Required **access_token** from **Spotify**. + - parameter options: A set of header options sent to the server. Defaults to an empty set. + - parameter callbackQueue: The queue to return to after completion. Default value of .main. + - parameter completion: The block to execute. + */ + func link(id: String, + accessToken: String, + options: API.Options = [], + callbackQueue: DispatchQueue = .main, + completion: @escaping (Result) -> Void) { + let spotifyAuthData = AuthenticationKeys.id + .makeDictionary(id: id, + accessToken: accessToken) + link(authData: spotifyAuthData, + options: options, + callbackQueue: callbackQueue, + completion: completion) + } + + func link(authData: [String: String], + options: API.Options = [], + callbackQueue: DispatchQueue = .main, + completion: @escaping (Result) -> Void) { + guard AuthenticationKeys.id.verifyMandatoryKeys(authData: authData) else { + callbackQueue.async { + completion(.failure(.init(code: .unknownError, + message: "Should have authData in consisting of keys \"id\" and \"accessToken\"."))) + } + return + } + AuthenticatedUser.link(Self.__type, + authData: authData, + options: options, + callbackQueue: callbackQueue, + completion: completion) + } +} + +// MARK: 3rd Party Authentication - ParseSpotify +public extension ParseUser { + + /// A Spotify `ParseUser`. + static var spotify: ParseSpotify { + ParseSpotify() + } + + /// An Spotify `ParseUser`. + var spotify: ParseSpotify { + Self.spotify + } +} From f99fd7ab4febbddd43ac8d7b42c3a9c7946b3924 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ulas=CC=A7=20Sancak?= Date: Tue, 21 Jun 2022 15:19:35 +0300 Subject: [PATCH 2/6] feat: add optional parameters to Spotify login --- .../ParseSpotify/ParseSpotify+async.swift | 18 +++++++ .../ParseSpotify/ParseSpotify+combine.swift | 18 +++++++ .../3rd Party/ParseSpotify/ParseSpotify.swift | 50 ++++++++++++++++--- 3 files changed, 80 insertions(+), 6 deletions(-) diff --git a/Sources/ParseSwift/Authentication/3rd Party/ParseSpotify/ParseSpotify+async.swift b/Sources/ParseSwift/Authentication/3rd Party/ParseSpotify/ParseSpotify+async.swift index 29fcade1b..c55acb10b 100644 --- a/Sources/ParseSwift/Authentication/3rd Party/ParseSpotify/ParseSpotify+async.swift +++ b/Sources/ParseSwift/Authentication/3rd Party/ParseSpotify/ParseSpotify+async.swift @@ -16,16 +16,25 @@ public extension ParseSpotify { Login a `ParseUser` *asynchronously* using Spotify authentication. - parameter id: The **Spotify profile id** from **Spotify**. - parameter accessToken: Required **access_token** from **Spotify**. + - parameter clientId: Optional **client_id** from **Spotify**. + - parameter expiresIn: Optional **expires_in** in seconds from **Spotify**. + - parameter refreshToken: Optional **refresh_token** from **Spotify**. - parameter options: A set of header options sent to the server. Defaults to an empty set. - returns: An instance of the logged in `ParseUser`. - throws: An error of type `ParseError`. */ func login(id: String, accessToken: String, + clientId: String? = nil, + expiresIn: Int? = nil, + refreshToken: String? = nil, options: API.Options = []) async throws -> AuthenticatedUser { try await withCheckedThrowingContinuation { continuation in self.login(id: id, accessToken: accessToken, + clientId: clientId, + expiresIn: expiresIn, + refreshToken: refreshToken, options: options, completion: continuation.resume) } @@ -53,16 +62,25 @@ public extension ParseSpotify { Link the *current* `ParseUser` *asynchronously* using Spotify authentication. - parameter id: The **Spotify profile id** from **Spotify**. - parameter accessToken: Required **access_token** from **Spotify**. + - parameter clientId: Optional **client_id** from **Spotify**. + - parameter expiresIn: Optional **expires_in** in seconds from **Spotify**. + - parameter refreshToken: Optional **refresh_token** from **Spotify**. - parameter options: A set of header options sent to the server. Defaults to an empty set. - returns: An instance of the logged in `ParseUser`. - throws: An error of type `ParseError`. */ func link(id: String, accessToken: String, + clientId: String? = nil, + expiresIn: Int? = nil, + refreshToken: String? = nil, options: API.Options = []) async throws -> AuthenticatedUser { try await withCheckedThrowingContinuation { continuation in self.link(id: id, accessToken: accessToken, + clientId: clientId, + expiresIn: expiresIn, + refreshToken: refreshToken, options: options, completion: continuation.resume) } diff --git a/Sources/ParseSwift/Authentication/3rd Party/ParseSpotify/ParseSpotify+combine.swift b/Sources/ParseSwift/Authentication/3rd Party/ParseSpotify/ParseSpotify+combine.swift index f2c20319c..88e64ef98 100644 --- a/Sources/ParseSwift/Authentication/3rd Party/ParseSpotify/ParseSpotify+combine.swift +++ b/Sources/ParseSwift/Authentication/3rd Party/ParseSpotify/ParseSpotify+combine.swift @@ -16,15 +16,24 @@ public extension ParseSpotify { Login a `ParseUser` *asynchronously* using Spotify authentication. Publishes when complete. - parameter id: The **Spotify profile id** from **Spotify**. - parameter accessToken: Required **access_token** from **Spotify**. + - parameter clientId: Optional **client_id** from **Spotify**. + - parameter expiresIn: Optional **expires_in** in seconds from **Spotify**. + - parameter refreshToken: Optional **refresh_token** from **Spotify**. - parameter options: A set of header options sent to the server. Defaults to an empty set. - returns: A publisher that eventually produces a single value and then finishes or fails. */ func loginPublisher(id: String, accessToken: String, + clientId: String? = nil, + expiresIn: Int? = nil, + refreshToken: String? = nil, options: API.Options = []) -> Future { Future { promise in self.login(id: id, accessToken: accessToken, + clientId: clientId, + expiresIn: expiresIn, + refreshToken: refreshToken, options: options, completion: promise) } @@ -51,15 +60,24 @@ public extension ParseSpotify { Publishes when complete. - parameter id: The **Spotify profile id** from **Spotify**. - parameter accessToken: Required **access_token** from **Spotify**. + - parameter clientId: Optional **client_id** from **Spotify**. + - parameter expiresIn: Optional **expires_in** in seconds from **Spotify**. + - parameter refreshToken: Optional **refresh_token** from **Spotify**. - parameter options: A set of header options sent to the server. Defaults to an empty set. - returns: A publisher that eventually produces a single value and then finishes or fails. */ func linkPublisher(id: String, accessToken: String, + clientId: String? = nil, + expiresIn: Int? = nil, + refreshToken: String? = nil, options: API.Options = []) -> Future { Future { promise in self.link(id: id, accessToken: accessToken, + clientId: clientId, + expiresIn: expiresIn, + refreshToken: refreshToken, options: options, completion: promise) } diff --git a/Sources/ParseSwift/Authentication/3rd Party/ParseSpotify/ParseSpotify.swift b/Sources/ParseSwift/Authentication/3rd Party/ParseSpotify/ParseSpotify.swift index f3cd0c278..36165af43 100644 --- a/Sources/ParseSwift/Authentication/3rd Party/ParseSpotify/ParseSpotify.swift +++ b/Sources/ParseSwift/Authentication/3rd Party/ParseSpotify/ParseSpotify.swift @@ -21,18 +21,39 @@ public struct ParseSpotify: ParseAuthentication { enum AuthenticationKeys: String, Codable { case id case accessToken = "access_token" - + case clientId = "client_id" + case expiresIn = "expires_in" + case refreshToken = "refresh_token" /// Properly makes an authData dictionary with the required keys. /// - parameter id: Required id for the user. /// - parameter accessToken: Required access token for Spotify. + /// - parameter clientId: Optional client id for Spotify. + /// - parameter expiresIn: Optional expiration in seconds for Spotify. + /// - parameter refreshToken: Optional refresh token for Spotify. /// - returns: authData dictionary. func makeDictionary(id: String, - accessToken: String) -> [String: String] { + accessToken: String, + clientId: String? = nil, + expiresIn: Int? = nil, + refreshToken: String? = nil) -> [String: String] { - let returnDictionary = [ + var returnDictionary = [ AuthenticationKeys.id.rawValue: id, AuthenticationKeys.accessToken.rawValue: accessToken ] + if let clientId = clientId { + returnDictionary[AuthenticationKeys.clientId.rawValue] = clientId + } + if let expiresIn = expiresIn, + let expirationDate = Calendar.current.date(byAdding: .second, + value: expiresIn, + to: Date()) { + let dateString = ParseCoding.dateFormatter.string(from: expirationDate) + returnDictionary[AuthenticationKeys.expiresIn.rawValue] = dateString + } + if let refreshToken = refreshToken { + returnDictionary[AuthenticationKeys.refreshToken.rawValue] = refreshToken + } return returnDictionary } @@ -62,20 +83,28 @@ public extension ParseSpotify { Login a `ParseUser` *asynchronously* using Spotify authentication. - parameter id: The **Spotify profile id** from **Spotify**. - parameter accessToken: Required **access_token** from **Spotify**. + - parameter clientId: Optional **client_id** from **Spotify**. + - parameter expiresIn: Optional **expires_in** in seconds from **Spotify**. + - parameter refreshToken: Optional **refresh_token** from **Spotify**. - parameter options: A set of header options sent to the server. Defaults to an empty set. - parameter callbackQueue: The queue to return to after completion. Default value of .main. - parameter completion: The block to execute. */ func login(id: String, accessToken: String, + clientId: String? = nil, + expiresIn: Int? = nil, + refreshToken: String? = nil, options: API.Options = [], callbackQueue: DispatchQueue = .main, completion: @escaping (Result) -> Void) { let spotifyAuthData = AuthenticationKeys.id .makeDictionary(id: id, - accessToken: accessToken) -// print(spotifyAuthData) + accessToken: accessToken, + clientId: clientId, + expiresIn: expiresIn, + refreshToken: refreshToken) login(authData: spotifyAuthData, options: options, callbackQueue: callbackQueue, @@ -108,18 +137,27 @@ public extension ParseSpotify { Link the *current* `ParseUser` *asynchronously* using Spotify authentication. - parameter id: The **Spotify profile id** from **Spotify**. - parameter accessToken: Required **access_token** from **Spotify**. + - parameter clientId: Optional **client_id** from **Spotify**. + - parameter expiresIn: Optional **expires_in** in seconds from **Spotify**. + - parameter refreshToken: Optional **refresh_token** from **Spotify**. - parameter options: A set of header options sent to the server. Defaults to an empty set. - parameter callbackQueue: The queue to return to after completion. Default value of .main. - parameter completion: The block to execute. */ func link(id: String, accessToken: String, + clientId: String? = nil, + expiresIn: Int? = nil, + refreshToken: String? = nil, options: API.Options = [], callbackQueue: DispatchQueue = .main, completion: @escaping (Result) -> Void) { let spotifyAuthData = AuthenticationKeys.id .makeDictionary(id: id, - accessToken: accessToken) + accessToken: accessToken, + clientId: clientId, + expiresIn: expiresIn, + refreshToken: refreshToken) link(authData: spotifyAuthData, options: options, callbackQueue: callbackQueue, From 15f4423be2080325b80b0cfe1f5b8d64f34a1299 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ulas=CC=A7=20Sancak?= Date: Tue, 21 Jun 2022 16:46:31 +0300 Subject: [PATCH 3/6] test: add test cases for Spotify login --- ParseSwift.xcodeproj/project.pbxproj | 24 + .../3rd Party/ParseSpotify/ParseSpotify.swift | 4 +- .../ParseSpotifyAsyncTests.swift | 276 +++++++++ .../ParseSpotifyCombineTests.swift | 352 ++++++++++++ Tests/ParseSwiftTests/ParseSpotifyTests.swift | 542 ++++++++++++++++++ 5 files changed, 1196 insertions(+), 2 deletions(-) create mode 100644 Tests/ParseSwiftTests/ParseSpotifyAsyncTests.swift create mode 100644 Tests/ParseSwiftTests/ParseSpotifyCombineTests.swift create mode 100644 Tests/ParseSwiftTests/ParseSpotifyTests.swift diff --git a/ParseSwift.xcodeproj/project.pbxproj b/ParseSwift.xcodeproj/project.pbxproj index b48825038..a63834ab2 100644 --- a/ParseSwift.xcodeproj/project.pbxproj +++ b/ParseSwift.xcodeproj/project.pbxproj @@ -772,6 +772,15 @@ 7C55F9F22860CEEF002A352D /* ParseSpotify+combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C55F9F02860CEEF002A352D /* ParseSpotify+combine.swift */; }; 7C55F9F32860CEEF002A352D /* ParseSpotify+combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C55F9F02860CEEF002A352D /* ParseSpotify+combine.swift */; }; 7C55F9F42860CEEF002A352D /* ParseSpotify+combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C55F9F02860CEEF002A352D /* ParseSpotify+combine.swift */; }; + 7C995D252861F8330077805A /* ParseSpotifyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C995D242861F8330077805A /* ParseSpotifyTests.swift */; }; + 7C995D262861F8330077805A /* ParseSpotifyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C995D242861F8330077805A /* ParseSpotifyTests.swift */; }; + 7C995D272861F8330077805A /* ParseSpotifyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C995D242861F8330077805A /* ParseSpotifyTests.swift */; }; + 7C995D292861FA0B0077805A /* ParseSpotifyAsyncTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C995D282861FA0B0077805A /* ParseSpotifyAsyncTests.swift */; }; + 7C995D2A2861FA0B0077805A /* ParseSpotifyAsyncTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C995D282861FA0B0077805A /* ParseSpotifyAsyncTests.swift */; }; + 7C995D2B2861FA0B0077805A /* ParseSpotifyAsyncTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C995D282861FA0B0077805A /* ParseSpotifyAsyncTests.swift */; }; + 7C995D2D2861FAE40077805A /* ParseSpotifyCombineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C995D2C2861FAE40077805A /* ParseSpotifyCombineTests.swift */; }; + 7C995D2E2861FAE40077805A /* ParseSpotifyCombineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C995D2C2861FAE40077805A /* ParseSpotifyCombineTests.swift */; }; + 7C995D2F2861FAE40077805A /* ParseSpotifyCombineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C995D2C2861FAE40077805A /* ParseSpotifyCombineTests.swift */; }; 7FFF552E2217E72A007C3B4E /* AnyEncodableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FFF552B2217E729007C3B4E /* AnyEncodableTests.swift */; }; 7FFF552F2217E72A007C3B4E /* AnyCodableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FFF552C2217E729007C3B4E /* AnyCodableTests.swift */; }; 7FFF55302217E72A007C3B4E /* AnyDecodableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FFF552D2217E729007C3B4E /* AnyDecodableTests.swift */; }; @@ -1326,6 +1335,9 @@ 7C55F9E62860CD6B002A352D /* ParseSpotify.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ParseSpotify.swift; sourceTree = ""; }; 7C55F9EB2860CEA6002A352D /* ParseSpotify+async.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ParseSpotify+async.swift"; sourceTree = ""; }; 7C55F9F02860CEEF002A352D /* ParseSpotify+combine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ParseSpotify+combine.swift"; sourceTree = ""; }; + 7C995D242861F8330077805A /* ParseSpotifyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ParseSpotifyTests.swift; sourceTree = ""; }; + 7C995D282861FA0B0077805A /* ParseSpotifyAsyncTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ParseSpotifyAsyncTests.swift; sourceTree = ""; }; + 7C995D2C2861FAE40077805A /* ParseSpotifyCombineTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ParseSpotifyCombineTests.swift; sourceTree = ""; }; 7FFF552B2217E729007C3B4E /* AnyEncodableTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnyEncodableTests.swift; sourceTree = ""; }; 7FFF552C2217E729007C3B4E /* AnyCodableTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnyCodableTests.swift; sourceTree = ""; }; 7FFF552D2217E729007C3B4E /* AnyDecodableTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnyDecodableTests.swift; sourceTree = ""; }; @@ -1623,6 +1635,9 @@ 7050259C2843F0CF008D6624 /* ParseSchemaAsyncTests.swift */, 705025A02843F0E7008D6624 /* ParseSchemaCombineTests.swift */, 705025A4284407C4008D6624 /* ParseSchemaTests.swift */, + 7C995D282861FA0B0077805A /* ParseSpotifyAsyncTests.swift */, + 7C995D2C2861FAE40077805A /* ParseSpotifyCombineTests.swift */, + 7C995D242861F8330077805A /* ParseSpotifyTests.swift */, 70C5504525B40D5200B5DBC2 /* ParseSessionTests.swift */, 917BA4512703F55700F8D747 /* ParseTwitterAsyncTests.swift */, 89899D9E26045998002E2043 /* ParseTwitterCombineTests.swift */, @@ -2789,6 +2804,7 @@ 918CED5E268618C600CFDC83 /* ParseLiveQueryCombineTests.swift in Sources */, 91679D6D268F261800F71809 /* ParseVersionTests.swift in Sources */, 917BA44E2703F2B400F8D747 /* ParseFacebookAsyncTests.swift in Sources */, + 7C995D292861FA0B0077805A /* ParseSpotifyAsyncTests.swift in Sources */, 911DB13624C4FC100027F3C7 /* ParseObjectTests.swift in Sources */, 70C167B927305101009F4E30 /* ParsePointerAsyncTests.swift in Sources */, 70F03A662780EAFA00E5AFB4 /* ParseLinkedInTests.swift in Sources */, @@ -2807,6 +2823,7 @@ 70732C5A2606CCAD000CAB81 /* ParseObjectCustomObjectIdTests.swift in Sources */, 911DB12C24C3F7720027F3C7 /* MockURLResponse.swift in Sources */, 7044C24325C5EA360011F6E7 /* ParseAppleCombineTests.swift in Sources */, + 7C995D2D2861FAE40077805A /* ParseSpotifyCombineTests.swift in Sources */, 70DFEA8A2618E77800F8EB4B /* InitializeSDKTests.swift in Sources */, 91285B2126991EE80051B544 /* ParsePolygonTests.swift in Sources */, 70170A4E2656EBA50070C905 /* ParseAnalyticsTests.swift in Sources */, @@ -2832,6 +2849,7 @@ 70F03A562780E8E300E5AFB4 /* ParseGoogleCombineTests.swift in Sources */, 7C4C0947285EA60E00F202C6 /* ParseInstagramAsyncTests.swift in Sources */, 917BA4422703EAC700F8D747 /* ParseLiveQueryAsyncTests.swift in Sources */, + 7C995D252861F8330077805A /* ParseSpotifyTests.swift in Sources */, 7016ED4025C4A25A00038648 /* ParseUserCombineTests.swift in Sources */, 91F346C3269B88F7005727B6 /* ParseCloudViewModelTests.swift in Sources */, 917BA4262703DB4600F8D747 /* ParseQueryAsyncTests.swift in Sources */, @@ -3097,6 +3115,7 @@ 918CED60268618C600CFDC83 /* ParseLiveQueryCombineTests.swift in Sources */, 91679D6F268F261A00F71809 /* ParseVersionTests.swift in Sources */, 917BA4502703F2B400F8D747 /* ParseFacebookAsyncTests.swift in Sources */, + 7C995D2B2861FA0B0077805A /* ParseSpotifyAsyncTests.swift in Sources */, 709B98512556ECAA00507778 /* ParseEncoderExtraTests.swift in Sources */, 70C167BB27305101009F4E30 /* ParsePointerAsyncTests.swift in Sources */, 70F03A682780EAFA00E5AFB4 /* ParseLinkedInTests.swift in Sources */, @@ -3115,6 +3134,7 @@ 70732C5C2606CCAD000CAB81 /* ParseObjectCustomObjectIdTests.swift in Sources */, 709B984D2556ECAA00507778 /* AnyDecodableTests.swift in Sources */, 7044C24525C5EA360011F6E7 /* ParseAppleCombineTests.swift in Sources */, + 7C995D2F2861FAE40077805A /* ParseSpotifyCombineTests.swift in Sources */, 70DFEA8C2618E77800F8EB4B /* InitializeSDKTests.swift in Sources */, 91285B2326991EE80051B544 /* ParsePolygonTests.swift in Sources */, 70170A502656EBA50070C905 /* ParseAnalyticsTests.swift in Sources */, @@ -3140,6 +3160,7 @@ 70F03A582780E8E300E5AFB4 /* ParseGoogleCombineTests.swift in Sources */, 7C4C0949285EA60E00F202C6 /* ParseInstagramAsyncTests.swift in Sources */, 917BA4442703EAC700F8D747 /* ParseLiveQueryAsyncTests.swift in Sources */, + 7C995D272861F8330077805A /* ParseSpotifyTests.swift in Sources */, 7016ED4225C4A25A00038648 /* ParseUserCombineTests.swift in Sources */, 91F346C5269B88F7005727B6 /* ParseCloudViewModelTests.swift in Sources */, 917BA4282703DB4600F8D747 /* ParseQueryAsyncTests.swift in Sources */, @@ -3213,6 +3234,7 @@ 918CED5F268618C600CFDC83 /* ParseLiveQueryCombineTests.swift in Sources */, 91679D6E268F261900F71809 /* ParseVersionTests.swift in Sources */, 917BA44F2703F2B400F8D747 /* ParseFacebookAsyncTests.swift in Sources */, + 7C995D2A2861FA0B0077805A /* ParseSpotifyAsyncTests.swift in Sources */, 70F2E2B6254F283000B2EA5C /* ParseACLTests.swift in Sources */, 70C167BA27305101009F4E30 /* ParsePointerAsyncTests.swift in Sources */, 70F03A672780EAFA00E5AFB4 /* ParseLinkedInTests.swift in Sources */, @@ -3231,6 +3253,7 @@ 70732C5B2606CCAD000CAB81 /* ParseObjectCustomObjectIdTests.swift in Sources */, 70F2E2C2254F283000B2EA5C /* APICommandTests.swift in Sources */, 7044C24425C5EA360011F6E7 /* ParseAppleCombineTests.swift in Sources */, + 7C995D2E2861FAE40077805A /* ParseSpotifyCombineTests.swift in Sources */, 70DFEA8B2618E77800F8EB4B /* InitializeSDKTests.swift in Sources */, 91285B2226991EE80051B544 /* ParsePolygonTests.swift in Sources */, 70170A4F2656EBA50070C905 /* ParseAnalyticsTests.swift in Sources */, @@ -3256,6 +3279,7 @@ 70F03A572780E8E300E5AFB4 /* ParseGoogleCombineTests.swift in Sources */, 7C4C0948285EA60E00F202C6 /* ParseInstagramAsyncTests.swift in Sources */, 917BA4432703EAC700F8D747 /* ParseLiveQueryAsyncTests.swift in Sources */, + 7C995D262861F8330077805A /* ParseSpotifyTests.swift in Sources */, 7016ED4125C4A25A00038648 /* ParseUserCombineTests.swift in Sources */, 91F346C4269B88F7005727B6 /* ParseCloudViewModelTests.swift in Sources */, 917BA4272703DB4600F8D747 /* ParseQueryAsyncTests.swift in Sources */, diff --git a/Sources/ParseSwift/Authentication/3rd Party/ParseSpotify/ParseSpotify.swift b/Sources/ParseSwift/Authentication/3rd Party/ParseSpotify/ParseSpotify.swift index 36165af43..fd66a0af4 100644 --- a/Sources/ParseSwift/Authentication/3rd Party/ParseSpotify/ParseSpotify.swift +++ b/Sources/ParseSwift/Authentication/3rd Party/ParseSpotify/ParseSpotify.swift @@ -22,7 +22,7 @@ public struct ParseSpotify: ParseAuthentication { case id case accessToken = "access_token" case clientId = "client_id" - case expiresIn = "expires_in" + case expirationDate = "expiration_date" case refreshToken = "refresh_token" /// Properly makes an authData dictionary with the required keys. /// - parameter id: Required id for the user. @@ -49,7 +49,7 @@ public struct ParseSpotify: ParseAuthentication { value: expiresIn, to: Date()) { let dateString = ParseCoding.dateFormatter.string(from: expirationDate) - returnDictionary[AuthenticationKeys.expiresIn.rawValue] = dateString + returnDictionary[AuthenticationKeys.expirationDate.rawValue] = dateString } if let refreshToken = refreshToken { returnDictionary[AuthenticationKeys.refreshToken.rawValue] = refreshToken diff --git a/Tests/ParseSwiftTests/ParseSpotifyAsyncTests.swift b/Tests/ParseSwiftTests/ParseSpotifyAsyncTests.swift new file mode 100644 index 000000000..119381e22 --- /dev/null +++ b/Tests/ParseSwiftTests/ParseSpotifyAsyncTests.swift @@ -0,0 +1,276 @@ +// +// ParseSpotifyAsyncTests.swift +// ParseSwift +// +// Created by Ulaş Sancak on 06/21/22. +// Copyright © 2022 Parse Community. All rights reserved. +// + +#if compiler(>=5.5.2) && canImport(_Concurrency) +import Foundation +#if canImport(FoundationNetworking) +import FoundationNetworking +#endif +import XCTest +@testable import ParseSwift + +class ParseSpotifyAsyncTests: XCTestCase { // swiftlint:disable:this type_body_length + struct User: ParseUser { + + //: These are required by ParseObject + var objectId: String? + var createdAt: Date? + var updatedAt: Date? + var ACL: ParseACL? + var originalData: Data? + + // These are required by ParseUser + var username: String? + var email: String? + var emailVerified: Bool? + var password: String? + var authData: [String: [String: String]?]? + } + + struct LoginSignupResponse: ParseUser { + + var objectId: String? + var createdAt: Date? + var sessionToken: String + var updatedAt: Date? + var ACL: ParseACL? + var originalData: Data? + + // These are required by ParseUser + var username: String? + var email: String? + var emailVerified: Bool? + var password: String? + var authData: [String: [String: String]?]? + + // Your custom keys + var customKey: String? + + init() { + let date = Date() + self.createdAt = date + self.updatedAt = date + self.objectId = "yarr" + self.ACL = nil + self.customKey = "blah" + self.sessionToken = "myToken" + self.username = "hello10" + self.email = "hello@parse.com" + } + } + + override func setUpWithError() throws { + try super.setUpWithError() + guard let url = URL(string: "http://localhost:1337/1") else { + XCTFail("Should create valid URL") + return + } + ParseSwift.initialize(applicationId: "applicationId", + clientKey: "clientKey", + masterKey: "masterKey", + serverURL: url, + testing: true) + } + + override func tearDownWithError() throws { + try super.tearDownWithError() + MockURLProtocol.removeAll() + #if !os(Linux) && !os(Android) && !os(Windows) + try KeychainStore.shared.deleteAll() + #endif + try ParseStorage.shared.deleteAll() + } + + @MainActor + func testLogin() async throws { + var serverResponse = LoginSignupResponse() + let authData = ParseAnonymous.AuthenticationKeys.id.makeDictionary() + serverResponse.username = "hello" + serverResponse.password = "world" + serverResponse.objectId = "yarr" + serverResponse.sessionToken = "myToken" + serverResponse.authData = [serverResponse.spotify.__type: authData] + serverResponse.createdAt = Date() + serverResponse.updatedAt = serverResponse.createdAt?.addingTimeInterval(+300) + + var userOnServer: User! + + let encoded: Data! + do { + encoded = try serverResponse.getEncoder().encode(serverResponse, skipKeys: .none) + //Get dates in correct format from ParseDecoding strategy + userOnServer = try serverResponse.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 user = try await User.spotify.login(id: "testing", accessToken: "access_token") + XCTAssertEqual(user, User.current) + XCTAssertEqual(user, userOnServer) + XCTAssertEqual(user.username, "hello") + XCTAssertEqual(user.password, "world") + XCTAssertTrue(user.spotify.isLinked) + } + + @MainActor + func testLoginAuthData() async throws { + var serverResponse = LoginSignupResponse() + let authData = ParseAnonymous.AuthenticationKeys.id.makeDictionary() + serverResponse.username = "hello" + serverResponse.password = "world" + serverResponse.objectId = "yarr" + serverResponse.sessionToken = "myToken" + serverResponse.authData = [serverResponse.spotify.__type: authData] + serverResponse.createdAt = Date() + serverResponse.updatedAt = serverResponse.createdAt?.addingTimeInterval(+300) + + var userOnServer: User! + + let encoded: Data! + do { + encoded = try serverResponse.getEncoder().encode(serverResponse, skipKeys: .none) + //Get dates in correct format from ParseDecoding strategy + userOnServer = try serverResponse.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 user = try await User.spotify.login(authData: ["id": "testing", + "access_token": "access_token"]) + XCTAssertEqual(user, User.current) + XCTAssertEqual(user, userOnServer) + XCTAssertEqual(user.username, "hello") + XCTAssertEqual(user.password, "world") + XCTAssertTrue(user.spotify.isLinked) + } + + func loginNormally() throws -> User { + 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 + } + } + return try User.login(username: "parse", password: "user") + } + + @MainActor + func testLink() async throws { + _ = try loginNormally() + MockURLProtocol.removeAll() + + var serverResponse = LoginSignupResponse() + serverResponse.updatedAt = Date() + + var userOnServer: User! + + let encoded: Data! + do { + encoded = try serverResponse.getEncoder().encode(serverResponse, skipKeys: .none) + //Get dates in correct format from ParseDecoding strategy + userOnServer = try serverResponse.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 user = try await User.spotify.link(id: "testing", accessToken: "access_token") + XCTAssertEqual(user, User.current) + XCTAssertEqual(user.updatedAt, userOnServer.updatedAt) + XCTAssertEqual(user.username, "hello10") + XCTAssertNil(user.password) + XCTAssertTrue(user.spotify.isLinked) + XCTAssertFalse(user.anonymous.isLinked) + } + + @MainActor + func testLinkAuthData() async throws { + _ = try loginNormally() + MockURLProtocol.removeAll() + + var serverResponse = LoginSignupResponse() + serverResponse.updatedAt = Date() + + var userOnServer: User! + + let encoded: Data! + do { + encoded = try serverResponse.getEncoder().encode(serverResponse, skipKeys: .none) + //Get dates in correct format from ParseDecoding strategy + userOnServer = try serverResponse.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 user = try await User.spotify.link(authData: ["id": "testing", + "access_token": "access_token"]) + XCTAssertEqual(user, User.current) + XCTAssertEqual(user.updatedAt, userOnServer.updatedAt) + XCTAssertEqual(user.username, "hello10") + XCTAssertNil(user.password) + XCTAssertTrue(user.spotify.isLinked) + XCTAssertFalse(user.anonymous.isLinked) + } + + @MainActor + func testUnlink() async throws { + _ = try loginNormally() + MockURLProtocol.removeAll() + + let authData = ParseSpotify + .AuthenticationKeys.id.makeDictionary(id: "testing", + accessToken: "access_token") + User.current?.authData = [User.spotify.__type: authData] + XCTAssertTrue(User.spotify.isLinked) + + var serverResponse = LoginSignupResponse() + serverResponse.updatedAt = Date() + + var userOnServer: User! + + let encoded: Data! + do { + encoded = try serverResponse.getEncoder().encode(serverResponse, skipKeys: .none) + //Get dates in correct format from ParseDecoding strategy + userOnServer = try serverResponse.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 user = try await User.spotify.unlink() + XCTAssertEqual(user, User.current) + XCTAssertEqual(user.updatedAt, userOnServer.updatedAt) + XCTAssertEqual(user.username, "hello10") + XCTAssertNil(user.password) + XCTAssertFalse(user.spotify.isLinked) + } +} +#endif diff --git a/Tests/ParseSwiftTests/ParseSpotifyCombineTests.swift b/Tests/ParseSwiftTests/ParseSpotifyCombineTests.swift new file mode 100644 index 000000000..40c18e235 --- /dev/null +++ b/Tests/ParseSwiftTests/ParseSpotifyCombineTests.swift @@ -0,0 +1,352 @@ +// +// ParseSpotifyCombineTests.swift +// ParseSwift +// +// Created by Ulaş Sancak on 06/21/22. +// Copyright © 2022 Parse Community. All rights reserved. +// + +#if canImport(Combine) + +import Foundation +import XCTest +import Combine +@testable import ParseSwift + +class ParseSpotifyCombineTests: XCTestCase { // swiftlint:disable:this type_body_length + + struct User: ParseUser { + + //: These are required by ParseObject + var objectId: String? + var createdAt: Date? + var updatedAt: Date? + var ACL: ParseACL? + var originalData: Data? + + // These are required by ParseUser + var username: String? + var email: String? + var emailVerified: Bool? + var password: String? + var authData: [String: [String: String]?]? + } + + struct LoginSignupResponse: ParseUser { + + var objectId: String? + var createdAt: Date? + var sessionToken: String + var updatedAt: Date? + var ACL: ParseACL? + var originalData: Data? + + // These are required by ParseUser + var username: String? + var email: String? + var emailVerified: Bool? + var password: String? + var authData: [String: [String: String]?]? + + // Your custom keys + var customKey: String? + + init() { + let date = Date() + self.createdAt = date + self.updatedAt = date + self.objectId = "yarr" + self.ACL = nil + self.customKey = "blah" + self.sessionToken = "myToken" + self.username = "hello10" + self.email = "hello@parse.com" + } + } + + override func setUpWithError() throws { + try super.setUpWithError() + guard let url = URL(string: "http://localhost:1337/1") else { + XCTFail("Should create valid URL") + return + } + ParseSwift.initialize(applicationId: "applicationId", + clientKey: "clientKey", + masterKey: "masterKey", + serverURL: url, + testing: true) + } + + override func tearDownWithError() throws { + try super.tearDownWithError() + MockURLProtocol.removeAll() + #if !os(Linux) && !os(Android) && !os(Windows) + try KeychainStore.shared.deleteAll() + #endif + try ParseStorage.shared.deleteAll() + } + + func testLogin() { + var subscriptions = Set() + let expectation1 = XCTestExpectation(description: "Save") + + var serverResponse = LoginSignupResponse() + let authData = ParseAnonymous.AuthenticationKeys.id.makeDictionary() + serverResponse.username = "hello" + serverResponse.password = "world" + serverResponse.objectId = "yarr" + serverResponse.sessionToken = "myToken" + serverResponse.authData = [serverResponse.spotify.__type: authData] + serverResponse.createdAt = Date() + serverResponse.updatedAt = serverResponse.createdAt?.addingTimeInterval(+300) + + var userOnServer: User! + + let encoded: Data! + do { + encoded = try serverResponse.getEncoder().encode(serverResponse, skipKeys: .none) + //Get dates in correct format from ParseDecoding strategy + userOnServer = try serverResponse.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 publisher = User.spotify.loginPublisher(id: "testing", accessToken: "access_token") + .sink(receiveCompletion: { result in + + if case let .failure(error) = result { + XCTFail(error.localizedDescription) + } + expectation1.fulfill() + + }, receiveValue: { user in + + XCTAssertEqual(user, User.current) + XCTAssertEqual(user, userOnServer) + XCTAssertEqual(user.username, "hello") + XCTAssertEqual(user.password, "world") + XCTAssertTrue(user.spotify.isLinked) + }) + publisher.store(in: &subscriptions) + + wait(for: [expectation1], timeout: 20.0) + } + + func testLoginAuthData() { + var subscriptions = Set() + let expectation1 = XCTestExpectation(description: "Save") + + var serverResponse = LoginSignupResponse() + let authData = ParseAnonymous.AuthenticationKeys.id.makeDictionary() + serverResponse.username = "hello" + serverResponse.password = "world" + serverResponse.objectId = "yarr" + serverResponse.sessionToken = "myToken" + serverResponse.authData = [serverResponse.spotify.__type: authData] + serverResponse.createdAt = Date() + serverResponse.updatedAt = serverResponse.createdAt?.addingTimeInterval(+300) + + var userOnServer: User! + + let encoded: Data! + do { + encoded = try serverResponse.getEncoder().encode(serverResponse, skipKeys: .none) + //Get dates in correct format from ParseDecoding strategy + userOnServer = try serverResponse.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 publisher = User.spotify.loginPublisher(authData: ["id": "testing", + "access_token": "access_token"]) + .sink(receiveCompletion: { result in + + if case let .failure(error) = result { + XCTFail(error.localizedDescription) + } + expectation1.fulfill() + + }, receiveValue: { user in + + XCTAssertEqual(user, User.current) + XCTAssertEqual(user, userOnServer) + XCTAssertEqual(user.username, "hello") + XCTAssertEqual(user.password, "world") + XCTAssertTrue(user.spotify.isLinked) + }) + publisher.store(in: &subscriptions) + + wait(for: [expectation1], timeout: 20.0) + } + + func loginNormally() throws -> User { + 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 + } + } + return try User.login(username: "parse", password: "user") + } + + func testLink() throws { + var subscriptions = Set() + let expectation1 = XCTestExpectation(description: "Save") + + _ = try loginNormally() + MockURLProtocol.removeAll() + + var serverResponse = LoginSignupResponse() + serverResponse.updatedAt = Date() + + var userOnServer: User! + + let encoded: Data! + do { + encoded = try serverResponse.getEncoder().encode(serverResponse, skipKeys: .none) + //Get dates in correct format from ParseDecoding strategy + userOnServer = try serverResponse.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 publisher = User.spotify.linkPublisher(id: "testing", accessToken: "access_token") + .sink(receiveCompletion: { result in + + if case let .failure(error) = result { + XCTFail(error.localizedDescription) + } + expectation1.fulfill() + + }, receiveValue: { user in + + XCTAssertEqual(user, User.current) + XCTAssertEqual(user.updatedAt, userOnServer.updatedAt) + XCTAssertEqual(user.username, "hello10") + XCTAssertNil(user.password) + XCTAssertTrue(user.spotify.isLinked) + XCTAssertFalse(user.anonymous.isLinked) + }) + publisher.store(in: &subscriptions) + + wait(for: [expectation1], timeout: 20.0) + } + + func testLinkAuthData() throws { + var subscriptions = Set() + let expectation1 = XCTestExpectation(description: "Save") + + _ = try loginNormally() + MockURLProtocol.removeAll() + + var serverResponse = LoginSignupResponse() + serverResponse.updatedAt = Date() + + var userOnServer: User! + + let encoded: Data! + do { + encoded = try serverResponse.getEncoder().encode(serverResponse, skipKeys: .none) + //Get dates in correct format from ParseDecoding strategy + userOnServer = try serverResponse.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 publisher = User.spotify.linkPublisher(authData: ["id": "testing", + "access_token": "access_token"]) + .sink(receiveCompletion: { result in + + if case let .failure(error) = result { + XCTFail(error.localizedDescription) + } + expectation1.fulfill() + + }, receiveValue: { user in + + XCTAssertEqual(user, User.current) + XCTAssertEqual(user.updatedAt, userOnServer.updatedAt) + XCTAssertEqual(user.username, "hello10") + XCTAssertNil(user.password) + XCTAssertTrue(user.spotify.isLinked) + XCTAssertFalse(user.anonymous.isLinked) + }) + publisher.store(in: &subscriptions) + + wait(for: [expectation1], timeout: 20.0) + } + + func testUnlink() throws { + var subscriptions = Set() + let expectation1 = XCTestExpectation(description: "Save") + + _ = try loginNormally() + MockURLProtocol.removeAll() + + let authData = ParseSpotify + .AuthenticationKeys.id.makeDictionary(id: "testing", + accessToken: "access_token") + User.current?.authData = [User.spotify.__type: authData] + XCTAssertTrue(User.spotify.isLinked) + + var serverResponse = LoginSignupResponse() + serverResponse.updatedAt = Date() + + var userOnServer: User! + + let encoded: Data! + do { + encoded = try serverResponse.getEncoder().encode(serverResponse, skipKeys: .none) + //Get dates in correct format from ParseDecoding strategy + userOnServer = try serverResponse.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 publisher = User.spotify.unlinkPublisher() + .sink(receiveCompletion: { result in + + if case let .failure(error) = result { + XCTFail(error.localizedDescription) + } + expectation1.fulfill() + + }, receiveValue: { user in + + XCTAssertEqual(user, User.current) + XCTAssertEqual(user.updatedAt, userOnServer.updatedAt) + XCTAssertEqual(user.username, "hello10") + XCTAssertNil(user.password) + XCTAssertFalse(user.spotify.isLinked) + }) + publisher.store(in: &subscriptions) + + wait(for: [expectation1], timeout: 20.0) + } +} + +#endif diff --git a/Tests/ParseSwiftTests/ParseSpotifyTests.swift b/Tests/ParseSwiftTests/ParseSpotifyTests.swift new file mode 100644 index 000000000..a6bbfe769 --- /dev/null +++ b/Tests/ParseSwiftTests/ParseSpotifyTests.swift @@ -0,0 +1,542 @@ +// +// ParseSpotifyTests.swift +// ParseSwift +// +// Created by Corey Baker on 06/21/22. +// Copyright © 2022 Parse Community. All rights reserved. +// + +import Foundation +import XCTest +@testable import ParseSwift + +class ParseSpotifyTests: XCTestCase { + struct User: ParseUser { + + //: These are required by ParseObject + var objectId: String? + var createdAt: Date? + var updatedAt: Date? + var ACL: ParseACL? + var originalData: Data? + + // These are required by ParseUser + var username: String? + var email: String? + var emailVerified: Bool? + var password: String? + var authData: [String: [String: String]?]? + } + + struct LoginSignupResponse: ParseUser { + + var objectId: String? + var createdAt: Date? + var sessionToken: String? + var updatedAt: Date? + var ACL: ParseACL? + var originalData: Data? + + // These are required by ParseUser + var username: String? + var email: String? + var emailVerified: Bool? + var password: String? + var authData: [String: [String: String]?]? + + // Your custom keys + var customKey: String? + + init() { + let date = Date() + self.createdAt = date + self.updatedAt = date + self.objectId = "yarr" + self.ACL = nil + self.customKey = "blah" + self.sessionToken = "myToken" + self.username = "hello10" + self.email = "hello@parse.com" + } + } + + override func setUpWithError() throws { + try super.setUpWithError() + guard let url = URL(string: "http://localhost:1337/1") else { + XCTFail("Should create valid URL") + return + } + ParseSwift.initialize(applicationId: "applicationId", + clientKey: "clientKey", + masterKey: "masterKey", + serverURL: url, + testing: true) + + } + + override func tearDownWithError() throws { + try super.tearDownWithError() + MockURLProtocol.removeAll() + #if !os(Linux) && !os(Android) && !os(Windows) + try KeychainStore.shared.deleteAll() + #endif + try ParseStorage.shared.deleteAll() + } + + func loginNormally() throws -> User { + 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 + } + } + return try User.login(username: "parse", password: "user") + } + + func testAuthenticationKeys() throws { + let authData = ParseSpotify + .AuthenticationKeys.id.makeDictionary(id: "testing", + accessToken: "access_token") + XCTAssertEqual(authData, ["id": "testing", "access_token": "access_token"]) + } + + func testAuthenticationWithOptinalKeys() throws { + let authData = ParseSpotify + .AuthenticationKeys.id.makeDictionary(id: "testing", + accessToken: "access_token", + clientId: "client_id", + expiresIn: 10, + refreshToken: "refresh_token") + guard let dateString = authData["expiration_date"] else { + XCTFail("Should have found date") + return + } + XCTAssertEqual(authData, ["id": "testing", + "access_token": "access_token", + "client_id": "client_id", + "expiration_date": dateString, + "refresh_token": "refresh_token"]) + } + + func testVerifyMandatoryKeys() throws { + let authData = ["id": "testing", "access_token": "access_token"] + let authDataWrong = ["id": "testing", "hello": "test"] + XCTAssertTrue(ParseSpotify + .AuthenticationKeys.id.verifyMandatoryKeys(authData: authData)) + XCTAssertFalse(ParseSpotify + .AuthenticationKeys.id.verifyMandatoryKeys(authData: authDataWrong)) + } + + func testLogin() throws { + var serverResponse = LoginSignupResponse() + + let authData = ParseSpotify + .AuthenticationKeys.id.makeDictionary(id: "testing", + accessToken: "access_token") + serverResponse.username = "hello" + serverResponse.password = "world" + serverResponse.objectId = "yarr" + serverResponse.sessionToken = "myToken" + serverResponse.authData = [serverResponse.spotify.__type: authData] + serverResponse.createdAt = Date() + serverResponse.updatedAt = serverResponse.createdAt?.addingTimeInterval(+300) + + var userOnServer: User! + + let encoded: Data! + do { + encoded = try serverResponse.getEncoder().encode(serverResponse, skipKeys: .none) + //Get dates in correct format from ParseDecoding strategy + userOnServer = try serverResponse.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: "Login") + + User.spotify.login(id: "testing", accessToken: "access_token") { result in + switch result { + + case .success(let user): + XCTAssertEqual(user, User.current) + XCTAssertEqual(user, userOnServer) + XCTAssertEqual(user.username, "hello") + XCTAssertEqual(user.password, "world") + XCTAssertTrue(user.spotify.isLinked) + + //Test stripping + user.spotify.strip() + XCTAssertFalse(user.spotify.isLinked) + case .failure(let error): + XCTFail(error.localizedDescription) + } + expectation1.fulfill() + } + wait(for: [expectation1], timeout: 20.0) + } + + func testLoginAuthData() throws { + var serverResponse = LoginSignupResponse() + + let authData = ParseSpotify + .AuthenticationKeys.id.makeDictionary(id: "testing", + accessToken: "access_token") + serverResponse.username = "hello" + serverResponse.password = "world" + serverResponse.objectId = "yarr" + serverResponse.sessionToken = "myToken" + serverResponse.authData = [serverResponse.spotify.__type: authData] + serverResponse.createdAt = Date() + serverResponse.updatedAt = serverResponse.createdAt?.addingTimeInterval(+300) + + var userOnServer: User! + + let encoded: Data! + do { + encoded = try serverResponse.getEncoder().encode(serverResponse, skipKeys: .none) + //Get dates in correct format from ParseDecoding strategy + userOnServer = try serverResponse.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: "Login") + + User.spotify.login(authData: authData) { result in + switch result { + + case .success(let user): + XCTAssertEqual(user, User.current) + XCTAssertEqual(user, userOnServer) + XCTAssertEqual(user.username, "hello") + XCTAssertEqual(user.password, "world") + XCTAssertTrue(user.spotify.isLinked) + + //Test stripping + user.spotify.strip() + XCTAssertFalse(user.spotify.isLinked) + case .failure(let error): + XCTFail(error.localizedDescription) + } + expectation1.fulfill() + } + wait(for: [expectation1], timeout: 20.0) + } + + func testLoginWrongKeys() throws { + _ = try loginNormally() + MockURLProtocol.removeAll() + + let expectation1 = XCTestExpectation(description: "Login") + + User.spotify.login(authData: ["hello": "world"]) { result in + + if case let .failure(error) = result { + XCTAssertTrue(error.message.contains("consisting of keys")) + } else { + XCTFail("Should have returned error") + } + expectation1.fulfill() + } + wait(for: [expectation1], timeout: 20.0) + } + + func loginAnonymousUser() throws { + let authData = ["id": "yolo"] + + //: Convert the anonymous user to a real new user. + var serverResponse = LoginSignupResponse() + serverResponse.username = "hello" + serverResponse.password = "world" + serverResponse.objectId = "yarr" + serverResponse.sessionToken = "myToken" + serverResponse.authData = [serverResponse.anonymous.__type: authData] + serverResponse.createdAt = Date() + serverResponse.updatedAt = serverResponse.createdAt?.addingTimeInterval(+300) + + var userOnServer: User! + + let encoded: Data! + do { + encoded = try serverResponse.getEncoder().encode(serverResponse, skipKeys: .none) + //Get dates in correct format from ParseDecoding strategy + userOnServer = try serverResponse.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 user = try User.anonymous.login() + XCTAssertEqual(user, User.current) + XCTAssertEqual(user, userOnServer) + XCTAssertEqual(user.username, "hello") + XCTAssertEqual(user.password, "world") + XCTAssertTrue(user.anonymous.isLinked) + } + + func testReplaceAnonymousWithSpotify() throws { + try loginAnonymousUser() + MockURLProtocol.removeAll() + + let authData = ParseSpotify + .AuthenticationKeys.id.makeDictionary(id: "testing", + accessToken: "access_token") + + var serverResponse = LoginSignupResponse() + serverResponse.username = "hello" + serverResponse.password = "world" + serverResponse.objectId = "yarr" + serverResponse.sessionToken = "myToken" + serverResponse.authData = [serverResponse.spotify.__type: authData, + serverResponse.anonymous.__type: nil] + serverResponse.createdAt = Date() + serverResponse.updatedAt = serverResponse.createdAt?.addingTimeInterval(+300) + + var userOnServer: User! + + let encoded: Data! + do { + encoded = try serverResponse.getEncoder().encode(serverResponse, skipKeys: .none) + //Get dates in correct format from ParseDecoding strategy + userOnServer = try serverResponse.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: "Login") + + User.spotify.login(id: "testing", accessToken: "access_token") { result in + switch result { + + case .success(let user): + XCTAssertEqual(user, User.current) + XCTAssertEqual(user.authData, userOnServer.authData) + XCTAssertEqual(user.username, "hello") + XCTAssertEqual(user.password, "world") + XCTAssertTrue(user.spotify.isLinked) + XCTAssertFalse(user.anonymous.isLinked) + case .failure(let error): + XCTFail(error.localizedDescription) + } + expectation1.fulfill() + } + wait(for: [expectation1], timeout: 20.0) + } + + func testReplaceAnonymousWithLinkedSpotify() throws { + try loginAnonymousUser() + MockURLProtocol.removeAll() + var serverResponse = LoginSignupResponse() + serverResponse.updatedAt = Date() + + var userOnServer: User! + + let encoded: Data! + do { + encoded = try serverResponse.getEncoder().encode(serverResponse, skipKeys: .none) + //Get dates in correct format from ParseDecoding strategy + userOnServer = try serverResponse.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: "Login") + + User.spotify.link(id: "testing", accessToken: "access_token") { result in + switch result { + + case .success(let user): + XCTAssertEqual(user, User.current) + XCTAssertEqual(user.updatedAt, userOnServer.updatedAt) + XCTAssertEqual(user.username, "hello") + XCTAssertEqual(user.password, "world") + XCTAssertTrue(user.spotify.isLinked) + XCTAssertFalse(user.anonymous.isLinked) + case .failure(let error): + XCTFail(error.localizedDescription) + } + expectation1.fulfill() + } + wait(for: [expectation1], timeout: 20.0) + } + + func testLinkLoggedInUserWithSpotify() throws { + _ = try loginNormally() + MockURLProtocol.removeAll() + + var serverResponse = LoginSignupResponse() + serverResponse.sessionToken = nil + serverResponse.updatedAt = Date() + + var userOnServer: User! + + let encoded: Data! + do { + encoded = try serverResponse.getEncoder().encode(serverResponse, skipKeys: .none) + //Get dates in correct format from ParseDecoding strategy + userOnServer = try serverResponse.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: "Login") + + User.spotify.link(id: "testing", accessToken: "access_token") { result in + switch result { + + case .success(let user): + XCTAssertEqual(user, User.current) + XCTAssertEqual(user.updatedAt, userOnServer.updatedAt) + XCTAssertEqual(user.username, "hello10") + XCTAssertNil(user.password) + XCTAssertTrue(user.spotify.isLinked) + XCTAssertFalse(user.anonymous.isLinked) + XCTAssertEqual(User.current?.sessionToken, "myToken") + case .failure(let error): + XCTFail(error.localizedDescription) + } + expectation1.fulfill() + } + wait(for: [expectation1], timeout: 20.0) + } + + func testLinkLoggedInAuthData() throws { + _ = try loginNormally() + MockURLProtocol.removeAll() + + var serverResponse = LoginSignupResponse() + serverResponse.sessionToken = nil + serverResponse.updatedAt = Date() + + var userOnServer: User! + + let encoded: Data! + do { + encoded = try serverResponse.getEncoder().encode(serverResponse, skipKeys: .none) + //Get dates in correct format from ParseDecoding strategy + userOnServer = try serverResponse.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: "Login") + + let authData = ParseSpotify + .AuthenticationKeys.id.makeDictionary(id: "testing", + accessToken: "access_token") + + User.spotify.link(authData: authData) { result in + switch result { + + case .success(let user): + XCTAssertEqual(user, User.current) + XCTAssertEqual(user.updatedAt, userOnServer.updatedAt) + XCTAssertEqual(user.username, "hello10") + XCTAssertNil(user.password) + XCTAssertTrue(user.spotify.isLinked) + XCTAssertFalse(user.anonymous.isLinked) + XCTAssertEqual(User.current?.sessionToken, "myToken") + case .failure(let error): + XCTFail(error.localizedDescription) + } + expectation1.fulfill() + } + wait(for: [expectation1], timeout: 20.0) + } + + func testLinkLoggedInUserWrongKeys() throws { + _ = try loginNormally() + MockURLProtocol.removeAll() + + let expectation1 = XCTestExpectation(description: "Login") + + User.spotify.link(authData: ["hello": "world"]) { result in + + if case let .failure(error) = result { + XCTAssertTrue(error.message.contains("consisting of keys")) + } else { + XCTFail("Should have returned error") + } + expectation1.fulfill() + } + wait(for: [expectation1], timeout: 20.0) + } + + func testUnlink() throws { + _ = try loginNormally() + MockURLProtocol.removeAll() + + let authData = ParseSpotify + .AuthenticationKeys.id.makeDictionary(id: "testing", + accessToken: "access_token") + User.current?.authData = [User.spotify.__type: authData] + XCTAssertTrue(User.spotify.isLinked) + + var serverResponse = LoginSignupResponse() + serverResponse.updatedAt = Date() + + var userOnServer: User! + + let encoded: Data! + do { + encoded = try serverResponse.getEncoder().encode(serverResponse, skipKeys: .none) + //Get dates in correct format from ParseDecoding strategy + userOnServer = try serverResponse.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: "Login") + + User.spotify.unlink { result in + switch result { + + case .success(let user): + XCTAssertEqual(user, User.current) + XCTAssertEqual(user.updatedAt, userOnServer.updatedAt) + XCTAssertEqual(user.username, "hello10") + XCTAssertNil(user.password) + XCTAssertFalse(user.spotify.isLinked) + case .failure(let error): + XCTFail(error.localizedDescription) + } + expectation1.fulfill() + } + wait(for: [expectation1], timeout: 20.0) + } +} From 213964ee47718e3333a6236893201b9893d8aa13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ula=C5=9F=20Sancak?= Date: Sun, 26 Jun 2022 15:47:40 +0300 Subject: [PATCH 4/6] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ef9936008..5c7fe3445 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ __New features__ [Full Changelog](https://github.com/parse-community/Parse-Swift/compare/4.5.0...4.6.0) __New features__ +- Add ParseSpotify authentication ([#375](https://github.com/parse-community/Parse-Swift/pull/375)), thanks to [Ulaş Sancak](https://github.com/rocxteady). - Add the ability to use Parse Hooks and Triggers ([#373](https://github.com/parse-community/Parse-Swift/pull/373)), thanks to [Corey Baker](https://github.com/cbaker6). - Add ParseInstagram authentication ([#372](https://github.com/parse-community/Parse-Swift/pull/372)), thanks to [Ulaş Sancak](https://github.com/rocxteady). - Add the ability to send APN and FCM push notifications. Also adds the ability to query _PushStatus ([#371](https://github.com/parse-community/Parse-Swift/pull/371)), thanks to [Corey Baker](https://github.com/cbaker6). From 31944f3842f54ac4d5a08d0e6e428cae6e0d5737 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ula=C5=9F=20Sancak?= Date: Mon, 27 Jun 2022 03:09:34 +0300 Subject: [PATCH 5/6] Update CHANGELOG.md --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c7fe3445..59a8a0d8a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ [Full Changelog](https://github.com/parse-community/Parse-Swift/compare/4.7.0...main) * _Contributing to this repo? Add info about your change here to be included in the next release_ +__New features__ +- Add ParseSpotify authentication ([#375](https://github.com/parse-community/Parse-Swift/pull/375)), thanks to [Ulaş Sancak](https://github.com/rocxteady). + __Fixes__ - Use select for ParseLiveQuery when fields are not present ([#376](https://github.com/parse-community/Parse-Swift/pull/376)), thanks to [Corey Baker](https://github.com/cbaker6). @@ -18,7 +21,6 @@ __New features__ [Full Changelog](https://github.com/parse-community/Parse-Swift/compare/4.5.0...4.6.0) __New features__ -- Add ParseSpotify authentication ([#375](https://github.com/parse-community/Parse-Swift/pull/375)), thanks to [Ulaş Sancak](https://github.com/rocxteady). - Add the ability to use Parse Hooks and Triggers ([#373](https://github.com/parse-community/Parse-Swift/pull/373)), thanks to [Corey Baker](https://github.com/cbaker6). - Add ParseInstagram authentication ([#372](https://github.com/parse-community/Parse-Swift/pull/372)), thanks to [Ulaş Sancak](https://github.com/rocxteady). - Add the ability to send APN and FCM push notifications. Also adds the ability to query _PushStatus ([#371](https://github.com/parse-community/Parse-Swift/pull/371)), thanks to [Corey Baker](https://github.com/cbaker6). From db0b7e0c69dbfaf3edc809819e7f7711cacee7c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ulas=CC=A7=20Sancak?= Date: Sat, 9 Jul 2022 02:58:19 +0300 Subject: [PATCH 6/6] fix: Remove clientId from authentication data --- .../3rd Party/ParseSpotify/ParseSpotify+async.swift | 6 ------ .../ParseSpotify/ParseSpotify+combine.swift | 6 ------ .../3rd Party/ParseSpotify/ParseSpotify.swift | 12 ------------ Tests/ParseSwiftTests/ParseSpotifyTests.swift | 2 -- 4 files changed, 26 deletions(-) diff --git a/Sources/ParseSwift/Authentication/3rd Party/ParseSpotify/ParseSpotify+async.swift b/Sources/ParseSwift/Authentication/3rd Party/ParseSpotify/ParseSpotify+async.swift index c55acb10b..2508767f2 100644 --- a/Sources/ParseSwift/Authentication/3rd Party/ParseSpotify/ParseSpotify+async.swift +++ b/Sources/ParseSwift/Authentication/3rd Party/ParseSpotify/ParseSpotify+async.swift @@ -16,7 +16,6 @@ public extension ParseSpotify { Login a `ParseUser` *asynchronously* using Spotify authentication. - parameter id: The **Spotify profile id** from **Spotify**. - parameter accessToken: Required **access_token** from **Spotify**. - - parameter clientId: Optional **client_id** from **Spotify**. - parameter expiresIn: Optional **expires_in** in seconds from **Spotify**. - parameter refreshToken: Optional **refresh_token** from **Spotify**. - parameter options: A set of header options sent to the server. Defaults to an empty set. @@ -25,14 +24,12 @@ public extension ParseSpotify { */ func login(id: String, accessToken: String, - clientId: String? = nil, expiresIn: Int? = nil, refreshToken: String? = nil, options: API.Options = []) async throws -> AuthenticatedUser { try await withCheckedThrowingContinuation { continuation in self.login(id: id, accessToken: accessToken, - clientId: clientId, expiresIn: expiresIn, refreshToken: refreshToken, options: options, @@ -62,7 +59,6 @@ public extension ParseSpotify { Link the *current* `ParseUser` *asynchronously* using Spotify authentication. - parameter id: The **Spotify profile id** from **Spotify**. - parameter accessToken: Required **access_token** from **Spotify**. - - parameter clientId: Optional **client_id** from **Spotify**. - parameter expiresIn: Optional **expires_in** in seconds from **Spotify**. - parameter refreshToken: Optional **refresh_token** from **Spotify**. - parameter options: A set of header options sent to the server. Defaults to an empty set. @@ -71,14 +67,12 @@ public extension ParseSpotify { */ func link(id: String, accessToken: String, - clientId: String? = nil, expiresIn: Int? = nil, refreshToken: String? = nil, options: API.Options = []) async throws -> AuthenticatedUser { try await withCheckedThrowingContinuation { continuation in self.link(id: id, accessToken: accessToken, - clientId: clientId, expiresIn: expiresIn, refreshToken: refreshToken, options: options, diff --git a/Sources/ParseSwift/Authentication/3rd Party/ParseSpotify/ParseSpotify+combine.swift b/Sources/ParseSwift/Authentication/3rd Party/ParseSpotify/ParseSpotify+combine.swift index 88e64ef98..e8e866a52 100644 --- a/Sources/ParseSwift/Authentication/3rd Party/ParseSpotify/ParseSpotify+combine.swift +++ b/Sources/ParseSwift/Authentication/3rd Party/ParseSpotify/ParseSpotify+combine.swift @@ -16,7 +16,6 @@ public extension ParseSpotify { Login a `ParseUser` *asynchronously* using Spotify authentication. Publishes when complete. - parameter id: The **Spotify profile id** from **Spotify**. - parameter accessToken: Required **access_token** from **Spotify**. - - parameter clientId: Optional **client_id** from **Spotify**. - parameter expiresIn: Optional **expires_in** in seconds from **Spotify**. - parameter refreshToken: Optional **refresh_token** from **Spotify**. - parameter options: A set of header options sent to the server. Defaults to an empty set. @@ -24,14 +23,12 @@ public extension ParseSpotify { */ func loginPublisher(id: String, accessToken: String, - clientId: String? = nil, expiresIn: Int? = nil, refreshToken: String? = nil, options: API.Options = []) -> Future { Future { promise in self.login(id: id, accessToken: accessToken, - clientId: clientId, expiresIn: expiresIn, refreshToken: refreshToken, options: options, @@ -60,7 +57,6 @@ public extension ParseSpotify { Publishes when complete. - parameter id: The **Spotify profile id** from **Spotify**. - parameter accessToken: Required **access_token** from **Spotify**. - - parameter clientId: Optional **client_id** from **Spotify**. - parameter expiresIn: Optional **expires_in** in seconds from **Spotify**. - parameter refreshToken: Optional **refresh_token** from **Spotify**. - parameter options: A set of header options sent to the server. Defaults to an empty set. @@ -68,14 +64,12 @@ public extension ParseSpotify { */ func linkPublisher(id: String, accessToken: String, - clientId: String? = nil, expiresIn: Int? = nil, refreshToken: String? = nil, options: API.Options = []) -> Future { Future { promise in self.link(id: id, accessToken: accessToken, - clientId: clientId, expiresIn: expiresIn, refreshToken: refreshToken, options: options, diff --git a/Sources/ParseSwift/Authentication/3rd Party/ParseSpotify/ParseSpotify.swift b/Sources/ParseSwift/Authentication/3rd Party/ParseSpotify/ParseSpotify.swift index fd66a0af4..0f86fb5df 100644 --- a/Sources/ParseSwift/Authentication/3rd Party/ParseSpotify/ParseSpotify.swift +++ b/Sources/ParseSwift/Authentication/3rd Party/ParseSpotify/ParseSpotify.swift @@ -21,19 +21,16 @@ public struct ParseSpotify: ParseAuthentication { enum AuthenticationKeys: String, Codable { case id case accessToken = "access_token" - case clientId = "client_id" case expirationDate = "expiration_date" case refreshToken = "refresh_token" /// Properly makes an authData dictionary with the required keys. /// - parameter id: Required id for the user. /// - parameter accessToken: Required access token for Spotify. - /// - parameter clientId: Optional client id for Spotify. /// - parameter expiresIn: Optional expiration in seconds for Spotify. /// - parameter refreshToken: Optional refresh token for Spotify. /// - returns: authData dictionary. func makeDictionary(id: String, accessToken: String, - clientId: String? = nil, expiresIn: Int? = nil, refreshToken: String? = nil) -> [String: String] { @@ -41,9 +38,6 @@ public struct ParseSpotify: ParseAuthentication { AuthenticationKeys.id.rawValue: id, AuthenticationKeys.accessToken.rawValue: accessToken ] - if let clientId = clientId { - returnDictionary[AuthenticationKeys.clientId.rawValue] = clientId - } if let expiresIn = expiresIn, let expirationDate = Calendar.current.date(byAdding: .second, value: expiresIn, @@ -83,7 +77,6 @@ public extension ParseSpotify { Login a `ParseUser` *asynchronously* using Spotify authentication. - parameter id: The **Spotify profile id** from **Spotify**. - parameter accessToken: Required **access_token** from **Spotify**. - - parameter clientId: Optional **client_id** from **Spotify**. - parameter expiresIn: Optional **expires_in** in seconds from **Spotify**. - parameter refreshToken: Optional **refresh_token** from **Spotify**. - parameter options: A set of header options sent to the server. Defaults to an empty set. @@ -92,7 +85,6 @@ public extension ParseSpotify { */ func login(id: String, accessToken: String, - clientId: String? = nil, expiresIn: Int? = nil, refreshToken: String? = nil, options: API.Options = [], @@ -102,7 +94,6 @@ public extension ParseSpotify { let spotifyAuthData = AuthenticationKeys.id .makeDictionary(id: id, accessToken: accessToken, - clientId: clientId, expiresIn: expiresIn, refreshToken: refreshToken) login(authData: spotifyAuthData, @@ -137,7 +128,6 @@ public extension ParseSpotify { Link the *current* `ParseUser` *asynchronously* using Spotify authentication. - parameter id: The **Spotify profile id** from **Spotify**. - parameter accessToken: Required **access_token** from **Spotify**. - - parameter clientId: Optional **client_id** from **Spotify**. - parameter expiresIn: Optional **expires_in** in seconds from **Spotify**. - parameter refreshToken: Optional **refresh_token** from **Spotify**. - parameter options: A set of header options sent to the server. Defaults to an empty set. @@ -146,7 +136,6 @@ public extension ParseSpotify { */ func link(id: String, accessToken: String, - clientId: String? = nil, expiresIn: Int? = nil, refreshToken: String? = nil, options: API.Options = [], @@ -155,7 +144,6 @@ public extension ParseSpotify { let spotifyAuthData = AuthenticationKeys.id .makeDictionary(id: id, accessToken: accessToken, - clientId: clientId, expiresIn: expiresIn, refreshToken: refreshToken) link(authData: spotifyAuthData, diff --git a/Tests/ParseSwiftTests/ParseSpotifyTests.swift b/Tests/ParseSwiftTests/ParseSpotifyTests.swift index a6bbfe769..a03e87fed 100644 --- a/Tests/ParseSwiftTests/ParseSpotifyTests.swift +++ b/Tests/ParseSwiftTests/ParseSpotifyTests.swift @@ -108,7 +108,6 @@ class ParseSpotifyTests: XCTestCase { let authData = ParseSpotify .AuthenticationKeys.id.makeDictionary(id: "testing", accessToken: "access_token", - clientId: "client_id", expiresIn: 10, refreshToken: "refresh_token") guard let dateString = authData["expiration_date"] else { @@ -117,7 +116,6 @@ class ParseSpotifyTests: XCTestCase { } XCTAssertEqual(authData, ["id": "testing", "access_token": "access_token", - "client_id": "client_id", "expiration_date": dateString, "refresh_token": "refresh_token"]) }