From 74ef85d03cce1e91ef915f32e059a2b9283e3232 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ulas=CC=A7=20Sancak?= Date: Sun, 19 Jun 2022 04:17:44 +0300 Subject: [PATCH 1/7] feat: add Instagram login --- ParseSwift.xcodeproj/project.pbxproj | 62 +++ .../ParseInstagram/ParseInstagram+async.swift | 93 ++++ .../ParseInstagram+combine.swift | 90 ++++ .../ParseInstagram/ParseInstagram.swift | 174 +++++++ .../ParseInstagramAsyncTests.swift | 290 ++++++++++++ .../ParseInstagramCombineTests.swift | 361 ++++++++++++++ .../ParseSwiftTests/ParseInstagramTests.swift | 443 ++++++++++++++++++ 7 files changed, 1513 insertions(+) create mode 100644 Sources/ParseSwift/Authentication/3rd Party/ParseInstagram/ParseInstagram+async.swift create mode 100644 Sources/ParseSwift/Authentication/3rd Party/ParseInstagram/ParseInstagram+combine.swift create mode 100644 Sources/ParseSwift/Authentication/3rd Party/ParseInstagram/ParseInstagram.swift create mode 100644 Tests/ParseSwiftTests/ParseInstagramAsyncTests.swift create mode 100644 Tests/ParseSwiftTests/ParseInstagramCombineTests.swift create mode 100644 Tests/ParseSwiftTests/ParseInstagramTests.swift diff --git a/ParseSwift.xcodeproj/project.pbxproj b/ParseSwift.xcodeproj/project.pbxproj index e9cf102ff..bf83f7275 100644 --- a/ParseSwift.xcodeproj/project.pbxproj +++ b/ParseSwift.xcodeproj/project.pbxproj @@ -648,6 +648,27 @@ 70F79A732639DEA000731C46 /* ParseHealthTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70F79A4F2639DE6900731C46 /* ParseHealthTests.swift */; }; 70F79A7D2639DEA100731C46 /* ParseHealthTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70F79A4F2639DE6900731C46 /* ParseHealthTests.swift */; }; 70F79A872639DEA200731C46 /* ParseHealthTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70F79A4F2639DE6900731C46 /* ParseHealthTests.swift */; }; + 7C4C092B285E746800F202C6 /* ParseInstagram.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C4C092A285E746800F202C6 /* ParseInstagram.swift */; }; + 7C4C092C285E746800F202C6 /* ParseInstagram.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C4C092A285E746800F202C6 /* ParseInstagram.swift */; }; + 7C4C092D285E746800F202C6 /* ParseInstagram.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C4C092A285E746800F202C6 /* ParseInstagram.swift */; }; + 7C4C092E285E746800F202C6 /* ParseInstagram.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C4C092A285E746800F202C6 /* ParseInstagram.swift */; }; + 7C4C0930285E986F00F202C6 /* ParseInstagram+async.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C4C092F285E986F00F202C6 /* ParseInstagram+async.swift */; }; + 7C4C0931285E986F00F202C6 /* ParseInstagram+async.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C4C092F285E986F00F202C6 /* ParseInstagram+async.swift */; }; + 7C4C0932285E986F00F202C6 /* ParseInstagram+async.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C4C092F285E986F00F202C6 /* ParseInstagram+async.swift */; }; + 7C4C0933285E986F00F202C6 /* ParseInstagram+async.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C4C092F285E986F00F202C6 /* ParseInstagram+async.swift */; }; + 7C4C093A285E9A3700F202C6 /* ParseInstagram+combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C4C0939285E9A3700F202C6 /* ParseInstagram+combine.swift */; }; + 7C4C093B285E9A3700F202C6 /* ParseInstagram+combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C4C0939285E9A3700F202C6 /* ParseInstagram+combine.swift */; }; + 7C4C093C285E9A3700F202C6 /* ParseInstagram+combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C4C0939285E9A3700F202C6 /* ParseInstagram+combine.swift */; }; + 7C4C093D285E9A3700F202C6 /* ParseInstagram+combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C4C0939285E9A3700F202C6 /* ParseInstagram+combine.swift */; }; + 7C4C093F285EA3A000F202C6 /* ParseInstagramTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C4C093E285EA3A000F202C6 /* ParseInstagramTests.swift */; }; + 7C4C0940285EA3A000F202C6 /* ParseInstagramTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C4C093E285EA3A000F202C6 /* ParseInstagramTests.swift */; }; + 7C4C0941285EA3A000F202C6 /* ParseInstagramTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C4C093E285EA3A000F202C6 /* ParseInstagramTests.swift */; }; + 7C4C0943285EA56E00F202C6 /* ParseInstagramCombineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C4C0942285EA56E00F202C6 /* ParseInstagramCombineTests.swift */; }; + 7C4C0944285EA56E00F202C6 /* ParseInstagramCombineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C4C0942285EA56E00F202C6 /* ParseInstagramCombineTests.swift */; }; + 7C4C0945285EA56E00F202C6 /* ParseInstagramCombineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C4C0942285EA56E00F202C6 /* ParseInstagramCombineTests.swift */; }; + 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 */; }; 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 */; }; @@ -1168,6 +1189,12 @@ 70F79A182639CE6F00731C46 /* ParseHealth.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseHealth.swift; sourceTree = ""; }; 70F79A4F2639DE6900731C46 /* ParseHealthTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseHealthTests.swift; sourceTree = ""; }; 70F79A662639DE9700731C46 /* ParseHealthCombineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseHealthCombineTests.swift; sourceTree = ""; }; + 7C4C092A285E746800F202C6 /* ParseInstagram.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ParseInstagram.swift; sourceTree = ""; }; + 7C4C092F285E986F00F202C6 /* ParseInstagram+async.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ParseInstagram+async.swift"; sourceTree = ""; }; + 7C4C0939285E9A3700F202C6 /* ParseInstagram+combine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ParseInstagram+combine.swift"; sourceTree = ""; }; + 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 = ""; }; 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 = ""; }; @@ -1423,6 +1450,9 @@ 70386A4525D99C8B0048EC1B /* ParseLDAPTests.swift */, 70F03A612780EADD00E5AFB4 /* ParseLinkedInCombineTests.swift */, 70F03A652780EAFA00E5AFB4 /* ParseLinkedInTests.swift */, + 7C4C0946285EA60E00F202C6 /* ParseInstagramAsyncTests.swift */, + 7C4C0942285EA56E00F202C6 /* ParseInstagramCombineTests.swift */, + 7C4C093E285EA3A000F202C6 /* ParseInstagramTests.swift */, 917BA4412703EAC700F8D747 /* ParseLiveQueryAsyncTests.swift */, 918CED5D268618C600CFDC83 /* ParseLiveQueryCombineTests.swift */, 7003963A25A288100052CB31 /* ParseLiveQueryTests.swift */, @@ -1730,6 +1760,7 @@ 70A2D81325B358FA001BEB7D /* 3rd Party */ = { isa = PBXGroup; children = ( + 7C4C0929285E745400F202C6 /* ParseInstagram */, 703B096126BF484E005A112F /* ParseApple */, 703B096426BF4896005A112F /* ParseFacebook */, 70F03A312780C7DD00E5AFB4 /* ParseGithub */, @@ -1797,6 +1828,16 @@ path = ParseSwiftTestsmacOS; sourceTree = ""; }; + 7C4C0929285E745400F202C6 /* ParseInstagram */ = { + isa = PBXGroup; + children = ( + 7C4C092A285E746800F202C6 /* ParseInstagram.swift */, + 7C4C092F285E986F00F202C6 /* ParseInstagram+async.swift */, + 7C4C0939285E9A3700F202C6 /* ParseInstagram+combine.swift */, + ); + path = ParseInstagram; + sourceTree = ""; + }; 7FFF552A2217E729007C3B4E /* AnyCodableTests */ = { isa = PBXGroup; children = ( @@ -2416,6 +2457,7 @@ 70110D52250680140091CC1D /* ParseConstants.swift in Sources */, 91B79AC326EE3A4E00073F2C /* API+NonParseBodyCommand.swift in Sources */, 70D1BDBA25BB17A600A42E7C /* ParseConfig.swift in Sources */, + 7C4C092B285E746800F202C6 /* ParseInstagram.swift in Sources */, 703B08FD26BD953B005A112F /* ParseHealth+async.swift in Sources */, 7085DDA326CC8A470033B977 /* ParseHealth+combine.swift in Sources */, F97B465224D9C78C00F4A88B /* AddUnique.swift in Sources */, @@ -2427,6 +2469,7 @@ 705025EB285153BC008D6624 /* ParsePushApplePayloadable.swift in Sources */, 705025A928441C96008D6624 /* ParseFieldOptions.swift in Sources */, F97B45D624D9C6F200F4A88B /* ParseEncoder.swift in Sources */, + 7C4C093A285E9A3700F202C6 /* ParseInstagram+combine.swift in Sources */, 700395A325A119430052CB31 /* Operations.swift in Sources */, 91BB8FCF2690BA70005A6BA5 /* QueryObservable.swift in Sources */, 70F03A232780BDE200E5AFB4 /* ParseGoogle.swift in Sources */, @@ -2441,6 +2484,7 @@ 70F03A252780BDF700E5AFB4 /* ParseGoogle+async.swift in Sources */, F97B45F224D9C6F200F4A88B /* Pointer.swift in Sources */, 703B095826BF480D005A112F /* ParseFacebook+combine.swift in Sources */, + 7C4C0930285E986F00F202C6 /* ParseInstagram+async.swift in Sources */, 705025D6284D0C1D008D6624 /* ParsePushPayloadApple.swift in Sources */, 70510AAC259EE25E00FEA700 /* LiveQuerySocket.swift in Sources */, 70C167AF27304EE4009F4E30 /* Pointer+combine.swift in Sources */, @@ -2566,6 +2610,7 @@ 89899D592603CF3E002E2043 /* ParseTwitterTests.swift in Sources */, 70CE1D892545BF730018D572 /* ParsePointerTests.swift in Sources */, 89899D772603CF66002E2043 /* ParseFacebookTests.swift in Sources */, + 7C4C0943285EA56E00F202C6 /* ParseInstagramCombineTests.swift in Sources */, 70386A4625D99C8B0048EC1B /* ParseLDAPTests.swift in Sources */, 709B40C1268F999000ED2EAC /* IOS13Tests.swift in Sources */, 91285B182698E66D0051B544 /* ParseBytesTests.swift in Sources */, @@ -2578,6 +2623,7 @@ 70DFEA8A2618E77800F8EB4B /* InitializeSDKTests.swift in Sources */, 91285B2126991EE80051B544 /* ParsePolygonTests.swift in Sources */, 70170A4E2656EBA50070C905 /* ParseAnalyticsTests.swift in Sources */, + 7C4C093F285EA3A000F202C6 /* ParseInstagramTests.swift in Sources */, 703B091F26BDE78D005A112F /* ParseObjectAsyncTests.swift in Sources */, 917BA4362703E4CB00F8D747 /* ParseFileAsyncTests.swift in Sources */, 7044C1DF25C5C70D0011F6E7 /* ParseObjectCombineTests.swift in Sources */, @@ -2593,6 +2639,7 @@ 70C5504625B40D5200B5DBC2 /* ParseSessionTests.swift in Sources */, 70110D5C2506ED0E0091CC1D /* ParseInstallationTests.swift in Sources */, 70F03A562780E8E300E5AFB4 /* ParseGoogleCombineTests.swift in Sources */, + 7C4C0947285EA60E00F202C6 /* ParseInstagramAsyncTests.swift in Sources */, 917BA4422703EAC700F8D747 /* ParseLiveQueryAsyncTests.swift in Sources */, 7016ED4025C4A25A00038648 /* ParseUserCombineTests.swift in Sources */, 91F346C3269B88F7005727B6 /* ParseCloudViewModelTests.swift in Sources */, @@ -2681,6 +2728,7 @@ 70110D53250680140091CC1D /* ParseConstants.swift in Sources */, 91B79AC426EE3A4E00073F2C /* API+NonParseBodyCommand.swift in Sources */, 70D1BDBB25BB17A600A42E7C /* ParseConfig.swift in Sources */, + 7C4C092C285E746800F202C6 /* ParseInstagram.swift in Sources */, 703B08FE26BD953B005A112F /* ParseHealth+async.swift in Sources */, 7085DDA426CC8A470033B977 /* ParseHealth+combine.swift in Sources */, F97B465324D9C78C00F4A88B /* AddUnique.swift in Sources */, @@ -2692,6 +2740,7 @@ 705025EC285153BC008D6624 /* ParsePushApplePayloadable.swift in Sources */, 705025AA28441C96008D6624 /* ParseFieldOptions.swift in Sources */, F97B45D724D9C6F200F4A88B /* ParseEncoder.swift in Sources */, + 7C4C093B285E9A3700F202C6 /* ParseInstagram+combine.swift in Sources */, 700395A425A119430052CB31 /* Operations.swift in Sources */, 91BB8FD02690BA70005A6BA5 /* QueryObservable.swift in Sources */, 7045769926BD917500F86F71 /* Query+async.swift in Sources */, @@ -2706,6 +2755,7 @@ 703B095926BF480D005A112F /* ParseFacebook+combine.swift in Sources */, 70510AAD259EE25E00FEA700 /* LiveQuerySocket.swift in Sources */, 70C167B027304EE4009F4E30 /* Pointer+combine.swift in Sources */, + 7C4C0931285E986F00F202C6 /* ParseInstagram+async.swift in Sources */, 705025D7284D0C1D008D6624 /* ParsePushPayloadApple.swift in Sources */, 7044C19225C4F5B60011F6E7 /* ParseFile+combine.swift in Sources */, 70F79A1A2639CE6F00731C46 /* ParseHealth.swift in Sources */, @@ -2840,6 +2890,7 @@ 89899D642603CF3F002E2043 /* ParseTwitterTests.swift in Sources */, 709B98532556ECAA00507778 /* ParsePointerTests.swift in Sources */, 89899D822603CF67002E2043 /* ParseFacebookTests.swift in Sources */, + 7C4C0945285EA56E00F202C6 /* ParseInstagramCombineTests.swift in Sources */, 70386A4825D99C8B0048EC1B /* ParseLDAPTests.swift in Sources */, 709B40C3268F999000ED2EAC /* IOS13Tests.swift in Sources */, 91285B1A2698E66D0051B544 /* ParseBytesTests.swift in Sources */, @@ -2852,6 +2903,7 @@ 70DFEA8C2618E77800F8EB4B /* InitializeSDKTests.swift in Sources */, 91285B2326991EE80051B544 /* ParsePolygonTests.swift in Sources */, 70170A502656EBA50070C905 /* ParseAnalyticsTests.swift in Sources */, + 7C4C0941285EA3A000F202C6 /* ParseInstagramTests.swift in Sources */, 703B092126BDE790005A112F /* ParseObjectAsyncTests.swift in Sources */, 917BA4382703E4CB00F8D747 /* ParseFileAsyncTests.swift in Sources */, 7044C1E125C5C70D0011F6E7 /* ParseObjectCombineTests.swift in Sources */, @@ -2867,6 +2919,7 @@ 70C5504825B40D5200B5DBC2 /* ParseSessionTests.swift in Sources */, 709B98572556ECAA00507778 /* ParseACLTests.swift in Sources */, 70F03A582780E8E300E5AFB4 /* ParseGoogleCombineTests.swift in Sources */, + 7C4C0949285EA60E00F202C6 /* ParseInstagramAsyncTests.swift in Sources */, 917BA4442703EAC700F8D747 /* ParseLiveQueryAsyncTests.swift in Sources */, 7016ED4225C4A25A00038648 /* ParseUserCombineTests.swift in Sources */, 91F346C5269B88F7005727B6 /* ParseCloudViewModelTests.swift in Sources */, @@ -2944,6 +2997,7 @@ 89899D632603CF3E002E2043 /* ParseTwitterTests.swift in Sources */, 70F2E2B7254F283000B2EA5C /* ParsePointerTests.swift in Sources */, 89899D812603CF67002E2043 /* ParseFacebookTests.swift in Sources */, + 7C4C0944285EA56E00F202C6 /* ParseInstagramCombineTests.swift in Sources */, 70386A4725D99C8B0048EC1B /* ParseLDAPTests.swift in Sources */, 709B40C2268F999000ED2EAC /* IOS13Tests.swift in Sources */, 91285B192698E66D0051B544 /* ParseBytesTests.swift in Sources */, @@ -2956,6 +3010,7 @@ 70DFEA8B2618E77800F8EB4B /* InitializeSDKTests.swift in Sources */, 91285B2226991EE80051B544 /* ParsePolygonTests.swift in Sources */, 70170A4F2656EBA50070C905 /* ParseAnalyticsTests.swift in Sources */, + 7C4C0940285EA3A000F202C6 /* ParseInstagramTests.swift in Sources */, 703B092026BDE78F005A112F /* ParseObjectAsyncTests.swift in Sources */, 917BA4372703E4CB00F8D747 /* ParseFileAsyncTests.swift in Sources */, 7044C1E025C5C70D0011F6E7 /* ParseObjectCombineTests.swift in Sources */, @@ -2971,6 +3026,7 @@ 70C5504725B40D5200B5DBC2 /* ParseSessionTests.swift in Sources */, 70F2E2BC254F283000B2EA5C /* ParseObjectTests.swift in Sources */, 70F03A572780E8E300E5AFB4 /* ParseGoogleCombineTests.swift in Sources */, + 7C4C0948285EA60E00F202C6 /* ParseInstagramAsyncTests.swift in Sources */, 917BA4432703EAC700F8D747 /* ParseLiveQueryAsyncTests.swift in Sources */, 7016ED4125C4A25A00038648 /* ParseUserCombineTests.swift in Sources */, 91F346C4269B88F7005727B6 /* ParseCloudViewModelTests.swift in Sources */, @@ -3059,6 +3115,7 @@ 70170A472656B02D0070C905 /* ParseAnalytics.swift in Sources */, F97B45E124D9C6F200F4A88B /* AnyCodable.swift in Sources */, 91B79AC626EE3A4E00073F2C /* API+NonParseBodyCommand.swift in Sources */, + 7C4C092E285E746800F202C6 /* ParseInstagram.swift in Sources */, 70D1BDBD25BB17A600A42E7C /* ParseConfig.swift in Sources */, 703B090026BD953B005A112F /* ParseHealth+async.swift in Sources */, 7085DDA626CC8A470033B977 /* ParseHealth+combine.swift in Sources */, @@ -3070,6 +3127,7 @@ 705025EE285153BC008D6624 /* ParsePushApplePayloadable.swift in Sources */, 705025AC28441C96008D6624 /* ParseFieldOptions.swift in Sources */, 7085DD9726CBF3A70033B977 /* Documentation.docc in Sources */, + 7C4C093D285E9A3700F202C6 /* ParseInstagram+combine.swift in Sources */, F97B465D24D9C78C00F4A88B /* Increment.swift in Sources */, 700395A625A119430052CB31 /* Operations.swift in Sources */, 91BB8FD22690BA70005A6BA5 /* QueryObservable.swift in Sources */, @@ -3084,6 +3142,7 @@ F97B45FD24D9C6F200F4A88B /* ParseACL.swift in Sources */, 703B095B26BF480D005A112F /* ParseFacebook+combine.swift in Sources */, 70510AAF259EE25E00FEA700 /* LiveQuerySocket.swift in Sources */, + 7C4C0933285E986F00F202C6 /* ParseInstagram+async.swift in Sources */, 705025D9284D0C1D008D6624 /* ParsePushPayloadApple.swift in Sources */, 70C167B227304EE4009F4E30 /* Pointer+combine.swift in Sources */, 7044C19425C4F5B60011F6E7 /* ParseFile+combine.swift in Sources */, @@ -3220,6 +3279,7 @@ 70170A462656B02D0070C905 /* ParseAnalytics.swift in Sources */, F97B45E024D9C6F200F4A88B /* AnyCodable.swift in Sources */, 91B79AC526EE3A4E00073F2C /* API+NonParseBodyCommand.swift in Sources */, + 7C4C092D285E746800F202C6 /* ParseInstagram.swift in Sources */, 70D1BDBC25BB17A600A42E7C /* ParseConfig.swift in Sources */, 703B08FF26BD953B005A112F /* ParseHealth+async.swift in Sources */, 7085DDA526CC8A470033B977 /* ParseHealth+combine.swift in Sources */, @@ -3231,6 +3291,7 @@ 705025ED285153BC008D6624 /* ParsePushApplePayloadable.swift in Sources */, 705025AB28441C96008D6624 /* ParseFieldOptions.swift in Sources */, 7085DD9626CBF3A70033B977 /* Documentation.docc in Sources */, + 7C4C093C285E9A3700F202C6 /* ParseInstagram+combine.swift in Sources */, F97B465C24D9C78C00F4A88B /* Increment.swift in Sources */, 700395A525A119430052CB31 /* Operations.swift in Sources */, 91BB8FD12690BA70005A6BA5 /* QueryObservable.swift in Sources */, @@ -3245,6 +3306,7 @@ F97B45FC24D9C6F200F4A88B /* ParseACL.swift in Sources */, 703B095A26BF480D005A112F /* ParseFacebook+combine.swift in Sources */, 70510AAE259EE25E00FEA700 /* LiveQuerySocket.swift in Sources */, + 7C4C0932285E986F00F202C6 /* ParseInstagram+async.swift in Sources */, 705025D8284D0C1D008D6624 /* ParsePushPayloadApple.swift in Sources */, 70C167B127304EE4009F4E30 /* Pointer+combine.swift in Sources */, 7044C19325C4F5B60011F6E7 /* ParseFile+combine.swift in Sources */, diff --git a/Sources/ParseSwift/Authentication/3rd Party/ParseInstagram/ParseInstagram+async.swift b/Sources/ParseSwift/Authentication/3rd Party/ParseInstagram/ParseInstagram+async.swift new file mode 100644 index 000000000..942288c13 --- /dev/null +++ b/Sources/ParseSwift/Authentication/3rd Party/ParseInstagram/ParseInstagram+async.swift @@ -0,0 +1,93 @@ +// +// ParseInstagram+async.swift +// ParseSwift +// +// Created by Ulaş Sancak on 06/19/22. +// Copyright © 2022 Parse Community. All rights reserved. +// + +#if compiler(>=5.5.2) && canImport(_Concurrency) +import Foundation + +public extension ParseInstagram { + // MARK: Async/Await + + /** + Login a `ParseUser` *asynchronously* using Instagram authentication. + - parameter id: The `Instagram profile id` from **Instagram**. + - parameter accessToken: Required **access_token** from **Instagram**. + - parameter apiURL: The `Instagram's most recent graph api url` from **Instagram**. + - 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, + apiURL: String = Self.graphAPIBaseURL, + options: API.Options = []) async throws -> AuthenticatedUser { + try await withCheckedThrowingContinuation { continuation in + self.login(id: id, + accessToken: accessToken, + apiURL: apiURL, + options: options, + completion: continuation.resume) + } + } + + /** + Login a `ParseUser` *asynchronously* using Instagram 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 ParseInstagram { + + /** + Link the *current* `ParseUser` *asynchronously* using Instagram authentication. + - parameter id: The `Instagram profile id` from **Instagram**. + - parameter accessToken: Required **access_token** from **Instagram**. + - parameter apiURL: The `Instagram's most recent graph api url` from **Instagram**. + - 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, + apiURL: String = Self.graphAPIBaseURL, + options: API.Options = []) async throws -> AuthenticatedUser { + try await withCheckedThrowingContinuation { continuation in + self.link(id: id, + accessToken: accessToken, + apiURL: apiURL, + options: options, + completion: continuation.resume) + } + } + + /** + Link the *current* `ParseUser` *asynchronously* using Instagram 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/ParseInstagram/ParseInstagram+combine.swift b/Sources/ParseSwift/Authentication/3rd Party/ParseInstagram/ParseInstagram+combine.swift new file mode 100644 index 000000000..e9c67f4d0 --- /dev/null +++ b/Sources/ParseSwift/Authentication/3rd Party/ParseInstagram/ParseInstagram+combine.swift @@ -0,0 +1,90 @@ +// +// ParseInstagram+combine.swift +// ParseSwift +// +// Created by Ulaş Sancak on 06/19/22. +// Copyright © 2022 Parse Community. All rights reserved. +// + +#if canImport(Combine) +import Foundation +import Combine + +public extension ParseInstagram { + // MARK: Combine + /** + Login a `ParseUser` *asynchronously* using Instagram authentication. Publishes when complete. + - parameter id: The `Instagram profile id` from **Instagram**. + - parameter accessToken: Required **access_token** from **Instagram**. + - parameter apiURL: The `Instagram's most recent graph api url` from **Instagram**. + - 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, + apiURL: String = Self.graphAPIBaseURL, + options: API.Options = []) -> Future { + Future { promise in + self.login(id: id, + accessToken: accessToken, + apiURL: apiURL, + options: options, + completion: promise) + } + } + + /** + Login a `ParseUser` *asynchronously* using Instagram 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 ParseInstagram { + /** + Link the *current* `ParseUser` *asynchronously* using Instagram authentication. + Publishes when complete. + - parameter id: The `Instagram profile id` from **Instagram**. + - parameter accessToken: Required **access_token** from **Instagram**. + - parameter apiURL: The `Instagram's most recent graph api url` from **Instagram**. + - 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, + apiURL: String = Self.graphAPIBaseURL, + options: API.Options = []) -> Future { + Future { promise in + self.link(id: id, + accessToken: accessToken, + apiURL: apiURL, + options: options, + completion: promise) + } + } + + /** + Link the *current* `ParseUser` *asynchronously* using Instagram 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/ParseInstagram/ParseInstagram.swift b/Sources/ParseSwift/Authentication/3rd Party/ParseInstagram/ParseInstagram.swift new file mode 100644 index 000000000..719543c21 --- /dev/null +++ b/Sources/ParseSwift/Authentication/3rd Party/ParseInstagram/ParseInstagram.swift @@ -0,0 +1,174 @@ +// +// ParseInstagram.swift +// ParseSwift +// +// Created by Ulaş Sancak on 06/19/22. +// Copyright © 2022 Parse Community. All rights reserved. +// + +import Foundation + +// swiftlint:disable line_length + +/** + Provides utility functions for working with Instagram User Authentication and `ParseUser`'s. + Be sure your Parse Server is configured for [sign in with Instagram](https://docs.parseplatform.org/parse-server/guide/#instagram-authdata). + For information on acquiring Instagram sign-in credentials to use with `ParseInstagram`, refer to [Facebook's Documentation](https://developers.facebook.com/docs/instagram-basic-display-api/overview). + */ +public struct ParseInstagram: ParseAuthentication { + + public static var graphAPIBaseURL: String { + "https://graph.instagram.com/" + } + + /// Authentication keys required for Instagram authentication. + enum AuthenticationKeys: String, Codable { + case id + case accessToken = "access_token" + case apiURL + + /// Properly makes an authData dictionary with the required keys. + /// - parameter id: Required id for the user. + /// - parameter accessToken: Required access token for Instagram. + /// - returns: authData dictionary. + func makeDictionary(id: String, + accessToken: String, + apiURL: String) -> [String: String] { + + let returnDictionary = [ + AuthenticationKeys.id.rawValue: id, + AuthenticationKeys.accessToken.rawValue: accessToken, + AuthenticationKeys.apiURL.rawValue: apiURL + ] + 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, + authData[AuthenticationKeys.apiURL.rawValue] != nil else { + return false + } + return true + } + } + + public static var __type: String { // swiftlint:disable:this identifier_name + "instagram" + } + + public init() { } +} + +// MARK: Login +public extension ParseInstagram { + + /** + Login a `ParseUser` *asynchronously* using Instagram authentication. + - parameter id: The `Instagram profile id` from **Instagram**. + - parameter accessToken: Required **access_token** from **Instagram**. + - parameter apiURL: The `Instagram's most recent graph api url` from **Instagram**. + - 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, + apiURL: String = Self.graphAPIBaseURL, + options: API.Options = [], + callbackQueue: DispatchQueue = .main, + completion: @escaping (Result) -> Void) { + + let instagramAuthData = AuthenticationKeys.id + .makeDictionary(id: id, + accessToken: accessToken, + apiURL: apiURL) + print(instagramAuthData) + login(authData: instagramAuthData, + 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\", \"accessToken\", and \"isMobileSDK\"."))) + } + return + } + AuthenticatedUser.login(Self.__type, + authData: authData, + options: options, + callbackQueue: callbackQueue, + completion: completion) + } +} + +// MARK: Link +public extension ParseInstagram { + + /** + Link the *current* `ParseUser` *asynchronously* using Instagram authentication. + - parameter id: The `Instagram profile id` from **Instagram**. + - parameter accessToken: Required **access_token** from **Instagram**. + - parameter apiURL: The `Instagram's most recent graph api url` from **Instagram**. + - 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, + apiURL: String = Self.graphAPIBaseURL, + options: API.Options = [], + callbackQueue: DispatchQueue = .main, + completion: @escaping (Result) -> Void) { + let instagramAuthData = AuthenticationKeys.id + .makeDictionary(id: id, + accessToken: accessToken, + apiURL: apiURL) + link(authData: instagramAuthData, + 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\", \"accessToken\", and \"isMobileSDK\"."))) + } + return + } + AuthenticatedUser.link(Self.__type, + authData: authData, + options: options, + callbackQueue: callbackQueue, + completion: completion) + } +} + +// MARK: 3rd Party Authentication - ParseInstagram +public extension ParseUser { + + /// A Instagram `ParseUser`. + static var instagram: ParseInstagram { + ParseInstagram() + } + + /// An Instagram `ParseUser`. + var instagram: ParseInstagram { + Self.instagram + } +} diff --git a/Tests/ParseSwiftTests/ParseInstagramAsyncTests.swift b/Tests/ParseSwiftTests/ParseInstagramAsyncTests.swift new file mode 100644 index 000000000..dfcf4cbc7 --- /dev/null +++ b/Tests/ParseSwiftTests/ParseInstagramAsyncTests.swift @@ -0,0 +1,290 @@ +// +// ParseInstagramAsyncTests.swift +// ParseSwift +// +// Created by Ulaş Sancak on 06/19/22. +// Copyright © 2021 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 ParseInstagramAsyncTests: 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:4242/biseycal") else { + XCTFail("Should create valid URL") + return + } + ParseSwift.initialize(applicationId: "me.biseycal", + clientKey: "DA6VtYV9LeKyUOc0Bg77rSkoSQWgPWax", + masterKey: "fApDOwXl9WEKMctzQ7G2rLzyIkQrYGdT", + 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.instagram.__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.instagram.login(id: "testing", + accessToken: "access_token", + apiURL: "apiURL") + XCTAssertEqual(user, User.current) + XCTAssertEqual(user, userOnServer) + XCTAssertEqual(user.username, "hello") + XCTAssertEqual(user.password, "world") + XCTAssertTrue(user.instagram.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.instagram.__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.instagram.login(authData: (["id": "testing", + "access_token": "access_token", + "apiURL": "apiURL"])) + XCTAssertEqual(user, User.current) + XCTAssertEqual(user, userOnServer) + XCTAssertEqual(user.username, "hello") + XCTAssertEqual(user.password, "world") + XCTAssertTrue(user.instagram.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.instagram.link(id: "testing", + accessToken: "access_token", + apiURL: "apiURL") + XCTAssertEqual(user, User.current) + XCTAssertEqual(user.updatedAt, userOnServer.updatedAt) + XCTAssertEqual(user.username, "hello10") + XCTAssertNil(user.password) + XCTAssertTrue(user.instagram.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 authData = ParseInstagram + .AuthenticationKeys.id.makeDictionary(id: "testing", + accessToken: "access_token", + apiURL: "apiURL") + + let user = try await User.instagram.link(authData: authData) + XCTAssertEqual(user, User.current) + XCTAssertEqual(user.updatedAt, userOnServer.updatedAt) + XCTAssertEqual(user.username, "hello10") + XCTAssertNil(user.password) + XCTAssertTrue(user.instagram.isLinked) + XCTAssertFalse(user.anonymous.isLinked) + } + + @MainActor + func testUnlink() async throws { + + _ = try loginNormally() + MockURLProtocol.removeAll() + + let authData = ParseInstagram + .AuthenticationKeys.id.makeDictionary(id: "testing", + accessToken: "access_token", + apiURL: "apiURL") + User.current?.authData = [User.instagram.__type: authData] + XCTAssertTrue(User.instagram.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.instagram.unlink() + XCTAssertEqual(user, User.current) + XCTAssertEqual(user.updatedAt, userOnServer.updatedAt) + XCTAssertEqual(user.username, "hello10") + XCTAssertNil(user.password) + XCTAssertFalse(user.instagram.isLinked) + } +} +#endif diff --git a/Tests/ParseSwiftTests/ParseInstagramCombineTests.swift b/Tests/ParseSwiftTests/ParseInstagramCombineTests.swift new file mode 100644 index 000000000..0dcb6d901 --- /dev/null +++ b/Tests/ParseSwiftTests/ParseInstagramCombineTests.swift @@ -0,0 +1,361 @@ +// +// ParseInstagramCombineTests.swift +// ParseSwift +// +// Created by Ulaş Sancak on 06/19/22. +// Copyright © 2022 Parse Community. All rights reserved. +// + +#if canImport(Combine) + +import Foundation +import XCTest +import Combine +@testable import ParseSwift + +class ParseInstagramCombineTests: 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:4242/biseycal") else { + XCTFail("Should create valid URL") + return + } + ParseSwift.initialize(applicationId: "me.biseycal", + clientKey: "DA6VtYV9LeKyUOc0Bg77rSkoSQWgPWax", + masterKey: "fApDOwXl9WEKMctzQ7G2rLzyIkQrYGdT", + 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.instagram.__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.instagram.loginPublisher(id: "testing", + accessToken: "access_token", + apiURL: "apiURL") + .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.instagram.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.instagram.__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.instagram.loginPublisher(authData: (["id": "testing", + "access_token": "access_token", + "apiURL": "apiURL"])) + .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.instagram.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.instagram.linkPublisher(id: "testing", + accessToken: "access_token", + apiURL: "apiURL") + .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.instagram.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 authData = ParseInstagram + .AuthenticationKeys.id.makeDictionary(id: "testing", + accessToken: "access_token", + apiURL: "apiURL") + let publisher = User.instagram.linkPublisher(authData: authData) + .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.instagram.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 = ParseInstagram + .AuthenticationKeys.id.makeDictionary(id: "testing", + accessToken: "access_token", + apiURL: "apiURL") + User.current?.authData = [User.instagram.__type: authData] + XCTAssertTrue(User.instagram.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.instagram.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.instagram.isLinked) + }) + publisher.store(in: &subscriptions) + + wait(for: [expectation1], timeout: 20.0) + } +} + +#endif diff --git a/Tests/ParseSwiftTests/ParseInstagramTests.swift b/Tests/ParseSwiftTests/ParseInstagramTests.swift new file mode 100644 index 000000000..bf2b9f85e --- /dev/null +++ b/Tests/ParseSwiftTests/ParseInstagramTests.swift @@ -0,0 +1,443 @@ +// +// ParseInstagramTests.swift +// ParseSwift +// +// Created by Ulaş Sancak on 06/19/22. +// Copyright © 2022 Parse Community. All rights reserved. +// + +import Foundation +#if canImport(FoundationNetworking) +import FoundationNetworking +#endif +import XCTest +@testable import ParseSwift + +class ParseInstagramTests: 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:4242/biseycal") else { + XCTFail("Should create valid URL") + return + } + ParseSwift.initialize(applicationId: "me.biseycal", + clientKey: "DA6VtYV9LeKyUOc0Bg77rSkoSQWgPWax", + masterKey: "fApDOwXl9WEKMctzQ7G2rLzyIkQrYGdT", + 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 loginAnonymousUser() throws { + let authData = ["id": "yolo"] + + //: Convert the anonymous user to a real new user. + var serverResponse = LoginSignupResponse() + serverResponse.username = "hello" + 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") + XCTAssertNil(user.password) + XCTAssertTrue(user.anonymous.isLinked) + } + + func testAuthenticationKeys() throws { + let authData = ParseInstagram + .AuthenticationKeys.id.makeDictionary(id: "testing", + accessToken: "access_token", + apiURL: "apiURL") + XCTAssertEqual(authData, ["id": "testing", + "access_token": "access_token", + "apiURL": "apiURL"]) + } + + func testVerifyMandatoryKeys() throws { + let authData = ["id": "testing", "access_token": "access_token", "apiURL": "apiURL"] + let authDataWrong = ["id": "testing", "hello": "test"] + XCTAssertTrue(ParseInstagram + .AuthenticationKeys.id.verifyMandatoryKeys(authData: authData)) + XCTAssertFalse(ParseInstagram + .AuthenticationKeys.id.verifyMandatoryKeys(authData: authDataWrong)) + } + +#if compiler(>=5.5.2) && canImport(_Concurrency) + @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.instagram.__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.instagram.login(id: "testing", + accessToken: "access_token", + apiURL: "apiURL") + XCTAssertEqual(user, User.current) + XCTAssertEqual(user, userOnServer) + XCTAssertEqual(user.username, "hello") + XCTAssertEqual(user.password, "world") + XCTAssertTrue(user.instagram.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.instagram.__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.instagram.login(authData: (["id": "testing", + "access_token": "access_token", + "apiURL": "apiURL"])) + XCTAssertEqual(user, User.current) + XCTAssertEqual(user, userOnServer) + XCTAssertEqual(user.username, "hello") + XCTAssertEqual(user.password, "world") + XCTAssertTrue(user.instagram.isLinked) + } + + @MainActor + func testLoginAuthDataBadAuth() async throws { + do { + _ = try await User.instagram.login(authData: (["id": "testing", + "bad": "token"])) + } catch { + guard let parseError = error.containedIn([.unknownError]) else { + XCTFail("Should have casted") + return + } + XCTAssertTrue(parseError.message.contains("consisting of keys")) + } + } + + @MainActor + func testReplaceAnonymousWithLoggedIn() async throws { + try loginAnonymousUser() + MockURLProtocol.removeAll() + var serverResponse = LoginSignupResponse() + serverResponse.updatedAt = Date() + serverResponse.password = nil + + 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.instagram.login(id: "testing", + accessToken: "access_token", + apiURL: "apiURL") + XCTAssertEqual(user, User.current) + XCTAssertEqual(user.updatedAt, userOnServer.updatedAt) + XCTAssertEqual(user.username, "hello") + XCTAssertNil(user.password) + XCTAssertTrue(user.instagram.isLinked) + XCTAssertFalse(user.anonymous.isLinked) + } + + @MainActor + func testReplaceAnonymousWithLinked() async throws { + try loginAnonymousUser() + MockURLProtocol.removeAll() + var serverResponse = LoginSignupResponse() + serverResponse.updatedAt = Date() + serverResponse.password = nil + + 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.instagram.link(id: "testing", + accessToken: "access_token", + apiURL: "apiURL") + XCTAssertEqual(user, User.current) + XCTAssertEqual(user.updatedAt, userOnServer.updatedAt) + XCTAssertEqual(user.username, "hello") + XCTAssertNil(user.password) + XCTAssertTrue(user.instagram.isLinked) + XCTAssertFalse(user.anonymous.isLinked) + } + + @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.instagram.link(id: "testing", + accessToken: "access_token", + apiURL: "apiURL") + XCTAssertEqual(user, User.current) + XCTAssertEqual(user.updatedAt, userOnServer.updatedAt) + XCTAssertEqual(user.username, "hello10") + XCTAssertNil(user.password) + XCTAssertTrue(user.instagram.isLinked) + XCTAssertFalse(user.anonymous.isLinked) + } + + @MainActor + func testLinkLoggedInAuthData() async throws { + + _ = try loginNormally() + MockURLProtocol.removeAll() + + var serverResponse = LoginSignupResponse() + serverResponse.updatedAt = Date() + serverResponse.sessionToken = nil + + 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 authData = ParseInstagram + .AuthenticationKeys.id.makeDictionary(id: "testing", + accessToken: "access_token", + apiURL: "apiURL") + + let user = try await User.instagram.link(authData: authData) + XCTAssertEqual(user, User.current) + XCTAssertEqual(user.updatedAt, userOnServer.updatedAt) + XCTAssertEqual(user.username, "hello10") + XCTAssertNil(user.password) + XCTAssertTrue(user.instagram.isLinked) + XCTAssertFalse(user.anonymous.isLinked) + } + + @MainActor + func testLinkLoggedInUserWrongKeys() async throws { + _ = try loginNormally() + MockURLProtocol.removeAll() + do { + _ = try await User.instagram.link(authData: ["hello": "world"]) + } catch { + guard let parseError = error.containedIn([.unknownError]) else { + XCTFail("Should have casted") + return + } + XCTAssertTrue(parseError.message.contains("consisting of keys")) + } + } + + @MainActor + func testUnlink() async throws { + + _ = try loginNormally() + MockURLProtocol.removeAll() + + let authData = ParseInstagram + .AuthenticationKeys.id.makeDictionary(id: "testing", + accessToken: "access_token", + apiURL: "apiURL") + User.current?.authData = [User.instagram.__type: authData] + XCTAssertTrue(User.instagram.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.instagram.unlink() + XCTAssertEqual(user, User.current) + XCTAssertEqual(user.updatedAt, userOnServer.updatedAt) + XCTAssertEqual(user.username, "hello10") + XCTAssertNil(user.password) + XCTAssertFalse(user.instagram.isLinked) + } +#endif +} From 8c4d8bca57988d600ca1688c4e284db95a30590b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ulas=CC=A7=20Sancak?= Date: Sun, 19 Jun 2022 16:45:08 +0300 Subject: [PATCH 2/7] fix: swiftlint warnings. --- .../3rd Party/ParseInstagram/ParseInstagram.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/ParseSwift/Authentication/3rd Party/ParseInstagram/ParseInstagram.swift b/Sources/ParseSwift/Authentication/3rd Party/ParseInstagram/ParseInstagram.swift index 719543c21..c461ef00e 100644 --- a/Sources/ParseSwift/Authentication/3rd Party/ParseInstagram/ParseInstagram.swift +++ b/Sources/ParseSwift/Authentication/3rd Party/ParseInstagram/ParseInstagram.swift @@ -16,11 +16,11 @@ import Foundation For information on acquiring Instagram sign-in credentials to use with `ParseInstagram`, refer to [Facebook's Documentation](https://developers.facebook.com/docs/instagram-basic-display-api/overview). */ public struct ParseInstagram: ParseAuthentication { - + public static var graphAPIBaseURL: String { "https://graph.instagram.com/" } - + /// Authentication keys required for Instagram authentication. enum AuthenticationKeys: String, Codable { case id From 09402949f95c32cc76e280872b8e1f579bd624c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ulas=CC=A7=20Sancak?= Date: Sun, 19 Jun 2022 18:05:32 +0300 Subject: [PATCH 3/7] test: add test for using default api url for Instagram login --- .../ParseInstagramAsyncTests.swift | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/Tests/ParseSwiftTests/ParseInstagramAsyncTests.swift b/Tests/ParseSwiftTests/ParseInstagramAsyncTests.swift index dfcf4cbc7..682617046 100644 --- a/Tests/ParseSwiftTests/ParseInstagramAsyncTests.swift +++ b/Tests/ParseSwiftTests/ParseInstagramAsyncTests.swift @@ -123,6 +123,43 @@ class ParseInstagramAsyncTests: XCTestCase { // swiftlint:disable:this type_body XCTAssertEqual(user.password, "world") XCTAssertTrue(user.instagram.isLinked) } + + @MainActor + func testLoginWithDefaultApiURL() 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.instagram.__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.instagram.login(id: "testing", + accessToken: "access_token") + XCTAssertEqual(user, User.current) + XCTAssertEqual(user, userOnServer) + XCTAssertEqual(user.username, "hello") + XCTAssertEqual(user.password, "world") + XCTAssertTrue(user.instagram.isLinked) + } @MainActor func testLoginAuthData() async throws { From 460923c4c04d7ec443cc17a65ff5a7417b3e440f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ulas=CC=A7=20Sancak?= Date: Sun, 19 Jun 2022 19:18:09 +0300 Subject: [PATCH 4/7] docs: fix documentation for id parameter --- .../3rd Party/ParseInstagram/ParseInstagram+async.swift | 4 ++-- .../3rd Party/ParseInstagram/ParseInstagram+combine.swift | 4 ++-- .../3rd Party/ParseInstagram/ParseInstagram.swift | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Sources/ParseSwift/Authentication/3rd Party/ParseInstagram/ParseInstagram+async.swift b/Sources/ParseSwift/Authentication/3rd Party/ParseInstagram/ParseInstagram+async.swift index 942288c13..c3ec524c7 100644 --- a/Sources/ParseSwift/Authentication/3rd Party/ParseInstagram/ParseInstagram+async.swift +++ b/Sources/ParseSwift/Authentication/3rd Party/ParseInstagram/ParseInstagram+async.swift @@ -14,7 +14,7 @@ public extension ParseInstagram { /** Login a `ParseUser` *asynchronously* using Instagram authentication. - - parameter id: The `Instagram profile id` from **Instagram**. + - parameter id: The **Instagram profile id** from **Instagram**. - parameter accessToken: Required **access_token** from **Instagram**. - parameter apiURL: The `Instagram's most recent graph api url` from **Instagram**. - parameter options: A set of header options sent to the server. Defaults to an empty set. @@ -54,7 +54,7 @@ public extension ParseInstagram { /** Link the *current* `ParseUser` *asynchronously* using Instagram authentication. - - parameter id: The `Instagram profile id` from **Instagram**. + - parameter id: The **Instagram profile id** from **Instagram**. - parameter accessToken: Required **access_token** from **Instagram**. - parameter apiURL: The `Instagram's most recent graph api url` from **Instagram**. - parameter options: A set of header options sent to the server. Defaults to an empty set. diff --git a/Sources/ParseSwift/Authentication/3rd Party/ParseInstagram/ParseInstagram+combine.swift b/Sources/ParseSwift/Authentication/3rd Party/ParseInstagram/ParseInstagram+combine.swift index e9c67f4d0..612aea94c 100644 --- a/Sources/ParseSwift/Authentication/3rd Party/ParseInstagram/ParseInstagram+combine.swift +++ b/Sources/ParseSwift/Authentication/3rd Party/ParseInstagram/ParseInstagram+combine.swift @@ -14,7 +14,7 @@ public extension ParseInstagram { // MARK: Combine /** Login a `ParseUser` *asynchronously* using Instagram authentication. Publishes when complete. - - parameter id: The `Instagram profile id` from **Instagram**. + - parameter id: The **Instagram profile id** from **Instagram**. - parameter accessToken: Required **access_token** from **Instagram**. - parameter apiURL: The `Instagram's most recent graph api url` from **Instagram**. - parameter options: A set of header options sent to the server. Defaults to an empty set. @@ -52,7 +52,7 @@ public extension ParseInstagram { /** Link the *current* `ParseUser` *asynchronously* using Instagram authentication. Publishes when complete. - - parameter id: The `Instagram profile id` from **Instagram**. + - parameter id: The **Instagram profile id** from **Instagram**. - parameter accessToken: Required **access_token** from **Instagram**. - parameter apiURL: The `Instagram's most recent graph api url` from **Instagram**. - parameter options: A set of header options sent to the server. Defaults to an empty set. diff --git a/Sources/ParseSwift/Authentication/3rd Party/ParseInstagram/ParseInstagram.swift b/Sources/ParseSwift/Authentication/3rd Party/ParseInstagram/ParseInstagram.swift index c461ef00e..7d4f1dce2 100644 --- a/Sources/ParseSwift/Authentication/3rd Party/ParseInstagram/ParseInstagram.swift +++ b/Sources/ParseSwift/Authentication/3rd Party/ParseInstagram/ParseInstagram.swift @@ -68,7 +68,7 @@ public extension ParseInstagram { /** Login a `ParseUser` *asynchronously* using Instagram authentication. - - parameter id: The `Instagram profile id` from **Instagram**. + - parameter id: The **Instagram profile id** from **Instagram**. - parameter accessToken: Required **access_token** from **Instagram**. - parameter apiURL: The `Instagram's most recent graph api url` from **Instagram**. - parameter options: A set of header options sent to the server. Defaults to an empty set. @@ -117,7 +117,7 @@ public extension ParseInstagram { /** Link the *current* `ParseUser` *asynchronously* using Instagram authentication. - - parameter id: The `Instagram profile id` from **Instagram**. + - parameter id: The **Instagram profile id** from **Instagram**. - parameter accessToken: Required **access_token** from **Instagram**. - parameter apiURL: The `Instagram's most recent graph api url` from **Instagram**. - parameter options: A set of header options sent to the server. Defaults to an empty set. From 3f7258f95124fa590f4316616b754c1f7451dcd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ula=C5=9F=20Sancak?= Date: Sun, 19 Jun 2022 19:56:30 +0300 Subject: [PATCH 5/7] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1792905a0..ead545c50 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ __New features__ - 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). - Add ParseSchema, ParseCLP, and ParseFieldOptions. Should only be used when using the Swift SDK on a secured server ([#370](https://github.com/parse-community/Parse-Swift/pull/370)), 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). ### 4.5.0 [Full Changelog](https://github.com/parse-community/Parse-Swift/compare/4.4.0...4.5.0) From 153ec9bdce0ccef67a7bdce32a0bb126007a83fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ulas=CC=A7=20Sancak?= Date: Sun, 19 Jun 2022 20:29:24 +0300 Subject: [PATCH 6/7] fix: swiftlint warnings. --- Tests/ParseSwiftTests/ParseInstagramAsyncTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/ParseSwiftTests/ParseInstagramAsyncTests.swift b/Tests/ParseSwiftTests/ParseInstagramAsyncTests.swift index 682617046..61d91c617 100644 --- a/Tests/ParseSwiftTests/ParseInstagramAsyncTests.swift +++ b/Tests/ParseSwiftTests/ParseInstagramAsyncTests.swift @@ -123,7 +123,7 @@ class ParseInstagramAsyncTests: XCTestCase { // swiftlint:disable:this type_body XCTAssertEqual(user.password, "world") XCTAssertTrue(user.instagram.isLinked) } - + @MainActor func testLoginWithDefaultApiURL() async throws { From fd4c32dacd83c971f00641c61b4d6057776e0a5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ulas=CC=A7=20Sancak?= Date: Mon, 20 Jun 2022 17:34:24 +0300 Subject: [PATCH 7/7] test: fix test implementations --- .../ParseInstagram/ParseInstagram.swift | 2 +- .../ParseInstagramAsyncTests.swift | 72 +--- .../ParseInstagramCombineTests.swift | 8 +- .../ParseSwiftTests/ParseInstagramTests.swift | 367 +++++++++++------- 4 files changed, 251 insertions(+), 198 deletions(-) diff --git a/Sources/ParseSwift/Authentication/3rd Party/ParseInstagram/ParseInstagram.swift b/Sources/ParseSwift/Authentication/3rd Party/ParseInstagram/ParseInstagram.swift index 7d4f1dce2..11635f8fa 100644 --- a/Sources/ParseSwift/Authentication/3rd Party/ParseInstagram/ParseInstagram.swift +++ b/Sources/ParseSwift/Authentication/3rd Party/ParseInstagram/ParseInstagram.swift @@ -33,7 +33,7 @@ public struct ParseInstagram: ParseAuthentication /// - returns: authData dictionary. func makeDictionary(id: String, accessToken: String, - apiURL: String) -> [String: String] { + apiURL: String = ParseInstagram.graphAPIBaseURL) -> [String: String] { let returnDictionary = [ AuthenticationKeys.id.rawValue: id, diff --git a/Tests/ParseSwiftTests/ParseInstagramAsyncTests.swift b/Tests/ParseSwiftTests/ParseInstagramAsyncTests.swift index 61d91c617..974f6f49c 100644 --- a/Tests/ParseSwiftTests/ParseInstagramAsyncTests.swift +++ b/Tests/ParseSwiftTests/ParseInstagramAsyncTests.swift @@ -66,13 +66,13 @@ class ParseInstagramAsyncTests: XCTestCase { // swiftlint:disable:this type_body override func setUpWithError() throws { try super.setUpWithError() - guard let url = URL(string: "http://localhost:4242/biseycal") else { + guard let url = URL(string: "http://localhost:1337/1") else { XCTFail("Should create valid URL") return } - ParseSwift.initialize(applicationId: "me.biseycal", - clientKey: "DA6VtYV9LeKyUOc0Bg77rSkoSQWgPWax", - masterKey: "fApDOwXl9WEKMctzQ7G2rLzyIkQrYGdT", + ParseSwift.initialize(applicationId: "applicationId", + clientKey: "clientKey", + masterKey: "masterKey", serverURL: url, testing: true) } @@ -88,7 +88,6 @@ class ParseInstagramAsyncTests: XCTestCase { // swiftlint:disable:this type_body @MainActor func testLogin() async throws { - var serverResponse = LoginSignupResponse() let authData = ParseAnonymous.AuthenticationKeys.id.makeDictionary() serverResponse.username = "hello" @@ -114,46 +113,7 @@ class ParseInstagramAsyncTests: XCTestCase { // swiftlint:disable:this type_body return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) } - let user = try await User.instagram.login(id: "testing", - accessToken: "access_token", - apiURL: "apiURL") - XCTAssertEqual(user, User.current) - XCTAssertEqual(user, userOnServer) - XCTAssertEqual(user.username, "hello") - XCTAssertEqual(user.password, "world") - XCTAssertTrue(user.instagram.isLinked) - } - - @MainActor - func testLoginWithDefaultApiURL() 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.instagram.__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.instagram.login(id: "testing", - accessToken: "access_token") + let user = try await User.instagram.login(id: "testing", accessToken: "access_token", apiURL: "apiURL") XCTAssertEqual(user, User.current) XCTAssertEqual(user, userOnServer) XCTAssertEqual(user.username, "hello") @@ -163,7 +123,6 @@ class ParseInstagramAsyncTests: XCTestCase { // swiftlint:disable:this type_body @MainActor func testLoginAuthData() async throws { - var serverResponse = LoginSignupResponse() let authData = ParseAnonymous.AuthenticationKeys.id.makeDictionary() serverResponse.username = "hello" @@ -189,9 +148,9 @@ class ParseInstagramAsyncTests: XCTestCase { // swiftlint:disable:this type_body return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) } - let user = try await User.instagram.login(authData: (["id": "testing", - "access_token": "access_token", - "apiURL": "apiURL"])) + let user = try await User.instagram.login(authData: ["id": "testing", + "access_token": "access_token", + "apiURL": "apiURL"]) XCTAssertEqual(user, User.current) XCTAssertEqual(user, userOnServer) XCTAssertEqual(user.username, "hello") @@ -215,7 +174,6 @@ class ParseInstagramAsyncTests: XCTestCase { // swiftlint:disable:this type_body @MainActor func testLink() async throws { - _ = try loginNormally() MockURLProtocol.removeAll() @@ -237,9 +195,7 @@ class ParseInstagramAsyncTests: XCTestCase { // swiftlint:disable:this type_body return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) } - let user = try await User.instagram.link(id: "testing", - accessToken: "access_token", - apiURL: "apiURL") + let user = try await User.instagram.link(id: "testing", accessToken: "access_token", apiURL: "apiURL") XCTAssertEqual(user, User.current) XCTAssertEqual(user.updatedAt, userOnServer.updatedAt) XCTAssertEqual(user.username, "hello10") @@ -250,7 +206,6 @@ class ParseInstagramAsyncTests: XCTestCase { // swiftlint:disable:this type_body @MainActor func testLinkAuthData() async throws { - _ = try loginNormally() MockURLProtocol.removeAll() @@ -271,12 +226,10 @@ class ParseInstagramAsyncTests: XCTestCase { // swiftlint:disable:this type_body MockURLProtocol.mockRequests { _ in return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) } - let authData = ParseInstagram - .AuthenticationKeys.id.makeDictionary(id: "testing", - accessToken: "access_token", - apiURL: "apiURL") - let user = try await User.instagram.link(authData: authData) + let user = try await User.instagram.link(authData: ["id": "testing", + "access_token": "access_token", + "apiURL": "apiURL"]) XCTAssertEqual(user, User.current) XCTAssertEqual(user.updatedAt, userOnServer.updatedAt) XCTAssertEqual(user.username, "hello10") @@ -287,7 +240,6 @@ class ParseInstagramAsyncTests: XCTestCase { // swiftlint:disable:this type_body @MainActor func testUnlink() async throws { - _ = try loginNormally() MockURLProtocol.removeAll() diff --git a/Tests/ParseSwiftTests/ParseInstagramCombineTests.swift b/Tests/ParseSwiftTests/ParseInstagramCombineTests.swift index 0dcb6d901..5f70da208 100644 --- a/Tests/ParseSwiftTests/ParseInstagramCombineTests.swift +++ b/Tests/ParseSwiftTests/ParseInstagramCombineTests.swift @@ -66,13 +66,13 @@ class ParseInstagramCombineTests: XCTestCase { // swiftlint:disable:this type_bo override func setUpWithError() throws { try super.setUpWithError() - guard let url = URL(string: "http://localhost:4242/biseycal") else { + guard let url = URL(string: "http://localhost:1337/1") else { XCTFail("Should create valid URL") return } - ParseSwift.initialize(applicationId: "me.biseycal", - clientKey: "DA6VtYV9LeKyUOc0Bg77rSkoSQWgPWax", - masterKey: "fApDOwXl9WEKMctzQ7G2rLzyIkQrYGdT", + ParseSwift.initialize(applicationId: "applicationId", + clientKey: "clientKey", + masterKey: "masterKey", serverURL: url, testing: true) } diff --git a/Tests/ParseSwiftTests/ParseInstagramTests.swift b/Tests/ParseSwiftTests/ParseInstagramTests.swift index bf2b9f85e..97bf329e3 100644 --- a/Tests/ParseSwiftTests/ParseInstagramTests.swift +++ b/Tests/ParseSwiftTests/ParseInstagramTests.swift @@ -13,7 +13,7 @@ import FoundationNetworking import XCTest @testable import ParseSwift -class ParseInstagramTests: XCTestCase { // swiftlint:disable:this type_body_length +class ParseInstagramTests: XCTestCase { struct User: ParseUser { //: These are required by ParseObject @@ -65,15 +65,16 @@ class ParseInstagramTests: XCTestCase { // swiftlint:disable:this type_body_leng override func setUpWithError() throws { try super.setUpWithError() - guard let url = URL(string: "http://localhost:4242/biseycal") else { + guard let url = URL(string: "http://localhost:1337/1") else { XCTFail("Should create valid URL") return } - ParseSwift.initialize(applicationId: "me.biseycal", - clientKey: "DA6VtYV9LeKyUOc0Bg77rSkoSQWgPWax", - masterKey: "fApDOwXl9WEKMctzQ7G2rLzyIkQrYGdT", + ParseSwift.initialize(applicationId: "applicationId", + clientKey: "clientKey", + masterKey: "masterKey", serverURL: url, testing: true) + } override func tearDownWithError() throws { @@ -99,15 +100,46 @@ class ParseInstagramTests: XCTestCase { // swiftlint:disable:this type_body_leng return try User.login(username: "parse", password: "user") } - func loginAnonymousUser() throws { - let authData = ["id": "yolo"] + func testAuthenticationKeys() throws { + let authData = ParseInstagram + .AuthenticationKeys.id.makeDictionary(id: "testing", + accessToken: "access_token", + apiURL: "apiURL") + XCTAssertEqual(authData, ["id": "testing", + "access_token": "access_token", + "apiURL": "apiURL"]) + } - //: Convert the anonymous user to a real new user. + func testAuthenticationKeysWithDefaultApiURL() throws { + let authData = ParseInstagram + .AuthenticationKeys.id.makeDictionary(id: "testing", + accessToken: "access_token") + XCTAssertEqual(authData, ["id": "testing", + "access_token": "access_token", + "apiURL": "https://graph.instagram.com/"]) + } + + func testVerifyMandatoryKeys() throws { + let authData = ["id": "testing", "access_token": "access_token", "apiURL": "apiURL"] + let authDataWrong = ["id": "testing", "hello": "test"] + XCTAssertTrue(ParseInstagram + .AuthenticationKeys.id.verifyMandatoryKeys(authData: authData)) + XCTAssertFalse(ParseInstagram + .AuthenticationKeys.id.verifyMandatoryKeys(authData: authDataWrong)) + } + + func testLogin() throws { var serverResponse = LoginSignupResponse() + + let authData = ParseInstagram + .AuthenticationKeys.id.makeDictionary(id: "testing", + accessToken: "access_token", + apiURL: "apiURL") serverResponse.username = "hello" + serverResponse.password = "world" serverResponse.objectId = "yarr" serverResponse.sessionToken = "myToken" - serverResponse.authData = [serverResponse.anonymous.__type: authData] + serverResponse.authData = [serverResponse.instagram.__type: authData] serverResponse.createdAt = Date() serverResponse.updatedAt = serverResponse.createdAt?.addingTimeInterval(+300) @@ -126,39 +158,36 @@ class ParseInstagramTests: XCTestCase { // swiftlint:disable:this type_body_leng 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") - XCTAssertNil(user.password) - XCTAssertTrue(user.anonymous.isLinked) + let expectation1 = XCTestExpectation(description: "Login") + + User.instagram.login(id: "testing", accessToken: "access_token", apiURL: "apiURL") { 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.instagram.isLinked) + + //Test stripping + user.instagram.strip() + XCTAssertFalse(user.instagram.isLinked) + case .failure(let error): + XCTFail(error.localizedDescription) + } + expectation1.fulfill() + } + wait(for: [expectation1], timeout: 20.0) } - func testAuthenticationKeys() throws { + func testLoginAuthData() throws { + var serverResponse = LoginSignupResponse() + let authData = ParseInstagram .AuthenticationKeys.id.makeDictionary(id: "testing", accessToken: "access_token", apiURL: "apiURL") - XCTAssertEqual(authData, ["id": "testing", - "access_token": "access_token", - "apiURL": "apiURL"]) - } - - func testVerifyMandatoryKeys() throws { - let authData = ["id": "testing", "access_token": "access_token", "apiURL": "apiURL"] - let authDataWrong = ["id": "testing", "hello": "test"] - XCTAssertTrue(ParseInstagram - .AuthenticationKeys.id.verifyMandatoryKeys(authData: authData)) - XCTAssertFalse(ParseInstagram - .AuthenticationKeys.id.verifyMandatoryKeys(authData: authDataWrong)) - } - -#if compiler(>=5.5.2) && canImport(_Concurrency) - @MainActor - func testLogin() async throws { - - var serverResponse = LoginSignupResponse() - let authData = ParseAnonymous.AuthenticationKeys.id.makeDictionary() serverResponse.username = "hello" serverResponse.password = "world" serverResponse.objectId = "yarr" @@ -182,26 +211,57 @@ class ParseInstagramTests: XCTestCase { // swiftlint:disable:this type_body_leng return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) } - let user = try await User.instagram.login(id: "testing", - accessToken: "access_token", - apiURL: "apiURL") - XCTAssertEqual(user, User.current) - XCTAssertEqual(user, userOnServer) - XCTAssertEqual(user.username, "hello") - XCTAssertEqual(user.password, "world") - XCTAssertTrue(user.instagram.isLinked) + let expectation1 = XCTestExpectation(description: "Login") + + User.instagram.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.instagram.isLinked) + + //Test stripping + user.instagram.strip() + XCTAssertFalse(user.instagram.isLinked) + case .failure(let error): + XCTFail(error.localizedDescription) + } + expectation1.fulfill() + } + wait(for: [expectation1], timeout: 20.0) } - @MainActor - func testLoginAuthData() async throws { + func testLoginWrongKeys() throws { + _ = try loginNormally() + MockURLProtocol.removeAll() + + let expectation1 = XCTestExpectation(description: "Login") + + User.instagram.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() - let authData = ParseAnonymous.AuthenticationKeys.id.makeDictionary() serverResponse.username = "hello" serverResponse.password = "world" serverResponse.objectId = "yarr" serverResponse.sessionToken = "myToken" - serverResponse.authData = [serverResponse.instagram.__type: authData] + serverResponse.authData = [serverResponse.anonymous.__type: authData] serverResponse.createdAt = Date() serverResponse.updatedAt = serverResponse.createdAt?.addingTimeInterval(+300) @@ -220,37 +280,32 @@ class ParseInstagramTests: XCTestCase { // swiftlint:disable:this type_body_leng return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) } - let user = try await User.instagram.login(authData: (["id": "testing", - "access_token": "access_token", - "apiURL": "apiURL"])) + let user = try User.anonymous.login() XCTAssertEqual(user, User.current) XCTAssertEqual(user, userOnServer) XCTAssertEqual(user.username, "hello") XCTAssertEqual(user.password, "world") - XCTAssertTrue(user.instagram.isLinked) - } - - @MainActor - func testLoginAuthDataBadAuth() async throws { - do { - _ = try await User.instagram.login(authData: (["id": "testing", - "bad": "token"])) - } catch { - guard let parseError = error.containedIn([.unknownError]) else { - XCTFail("Should have casted") - return - } - XCTAssertTrue(parseError.message.contains("consisting of keys")) - } + XCTAssertTrue(user.anonymous.isLinked) } - @MainActor - func testReplaceAnonymousWithLoggedIn() async throws { + func testReplaceAnonymousWithInstagram() throws { try loginAnonymousUser() MockURLProtocol.removeAll() + + let authData = ParseInstagram + .AuthenticationKeys.id.makeDictionary(id: "testing", + accessToken: "access_token", + apiURL: "apiURL") + var serverResponse = LoginSignupResponse() - serverResponse.updatedAt = Date() - serverResponse.password = nil + serverResponse.username = "hello" + serverResponse.password = "world" + serverResponse.objectId = "yarr" + serverResponse.sessionToken = "myToken" + serverResponse.authData = [serverResponse.instagram.__type: authData, + serverResponse.anonymous.__type: nil] + serverResponse.createdAt = Date() + serverResponse.updatedAt = serverResponse.createdAt?.addingTimeInterval(+300) var userOnServer: User! @@ -267,24 +322,31 @@ class ParseInstagramTests: XCTestCase { // swiftlint:disable:this type_body_leng return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) } - let user = try await User.instagram.login(id: "testing", - accessToken: "access_token", - apiURL: "apiURL") - XCTAssertEqual(user, User.current) - XCTAssertEqual(user.updatedAt, userOnServer.updatedAt) - XCTAssertEqual(user.username, "hello") - XCTAssertNil(user.password) - XCTAssertTrue(user.instagram.isLinked) - XCTAssertFalse(user.anonymous.isLinked) + let expectation1 = XCTestExpectation(description: "Login") + + User.instagram.login(id: "testing", accessToken: "access_token", apiURL: "apiURL") { 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.instagram.isLinked) + XCTAssertFalse(user.anonymous.isLinked) + case .failure(let error): + XCTFail(error.localizedDescription) + } + expectation1.fulfill() + } + wait(for: [expectation1], timeout: 20.0) } - @MainActor - func testReplaceAnonymousWithLinked() async throws { + func testReplaceAnonymousWithLinkedInstagram() throws { try loginAnonymousUser() MockURLProtocol.removeAll() var serverResponse = LoginSignupResponse() serverResponse.updatedAt = Date() - serverResponse.password = nil var userOnServer: User! @@ -301,24 +363,32 @@ class ParseInstagramTests: XCTestCase { // swiftlint:disable:this type_body_leng return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) } - let user = try await User.instagram.link(id: "testing", - accessToken: "access_token", - apiURL: "apiURL") - XCTAssertEqual(user, User.current) - XCTAssertEqual(user.updatedAt, userOnServer.updatedAt) - XCTAssertEqual(user.username, "hello") - XCTAssertNil(user.password) - XCTAssertTrue(user.instagram.isLinked) - XCTAssertFalse(user.anonymous.isLinked) - } + let expectation1 = XCTestExpectation(description: "Login") - @MainActor - func testLink() async throws { + User.instagram.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.instagram.isLinked) + XCTAssertFalse(user.anonymous.isLinked) + case .failure(let error): + XCTFail(error.localizedDescription) + } + expectation1.fulfill() + } + wait(for: [expectation1], timeout: 20.0) + } + + func testLinkLoggedInUserWithInstagram() throws { _ = try loginNormally() MockURLProtocol.removeAll() var serverResponse = LoginSignupResponse() + serverResponse.sessionToken = nil serverResponse.updatedAt = Date() var userOnServer: User! @@ -336,26 +406,34 @@ class ParseInstagramTests: XCTestCase { // swiftlint:disable:this type_body_leng return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) } - let user = try await User.instagram.link(id: "testing", - accessToken: "access_token", - apiURL: "apiURL") - XCTAssertEqual(user, User.current) - XCTAssertEqual(user.updatedAt, userOnServer.updatedAt) - XCTAssertEqual(user.username, "hello10") - XCTAssertNil(user.password) - XCTAssertTrue(user.instagram.isLinked) - XCTAssertFalse(user.anonymous.isLinked) + let expectation1 = XCTestExpectation(description: "Login") + + User.instagram.login(id: "testing", accessToken: "access_token", apiURL: "apiURL") { 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.instagram.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) } - @MainActor - func testLinkLoggedInAuthData() async throws { - + func testLinkLoggedInAuthData() throws { _ = try loginNormally() MockURLProtocol.removeAll() var serverResponse = LoginSignupResponse() - serverResponse.updatedAt = Date() serverResponse.sessionToken = nil + serverResponse.updatedAt = Date() var userOnServer: User! @@ -372,38 +450,51 @@ class ParseInstagramTests: XCTestCase { // swiftlint:disable:this type_body_leng return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) } + let expectation1 = XCTestExpectation(description: "Login") + let authData = ParseInstagram .AuthenticationKeys.id.makeDictionary(id: "testing", accessToken: "access_token", apiURL: "apiURL") - let user = try await User.instagram.link(authData: authData) - XCTAssertEqual(user, User.current) - XCTAssertEqual(user.updatedAt, userOnServer.updatedAt) - XCTAssertEqual(user.username, "hello10") - XCTAssertNil(user.password) - XCTAssertTrue(user.instagram.isLinked) - XCTAssertFalse(user.anonymous.isLinked) + User.instagram.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.instagram.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) } - @MainActor - func testLinkLoggedInUserWrongKeys() async throws { + func testLinkLoggedInUserWrongKeys() throws { _ = try loginNormally() MockURLProtocol.removeAll() - do { - _ = try await User.instagram.link(authData: ["hello": "world"]) - } catch { - guard let parseError = error.containedIn([.unknownError]) else { - XCTFail("Should have casted") - return + + let expectation1 = XCTestExpectation(description: "Login") + + User.instagram.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") } - XCTAssertTrue(parseError.message.contains("consisting of keys")) + expectation1.fulfill() } + wait(for: [expectation1], timeout: 20.0) } - @MainActor - func testUnlink() async throws { - + func testUnlink() throws { _ = try loginNormally() MockURLProtocol.removeAll() @@ -432,12 +523,22 @@ class ParseInstagramTests: XCTestCase { // swiftlint:disable:this type_body_leng return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) } - let user = try await User.instagram.unlink() - XCTAssertEqual(user, User.current) - XCTAssertEqual(user.updatedAt, userOnServer.updatedAt) - XCTAssertEqual(user.username, "hello10") - XCTAssertNil(user.password) - XCTAssertFalse(user.instagram.isLinked) + let expectation1 = XCTestExpectation(description: "Login") + + User.instagram.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.instagram.isLinked) + case .failure(let error): + XCTFail(error.localizedDescription) + } + expectation1.fulfill() + } + wait(for: [expectation1], timeout: 20.0) } -#endif }