diff --git a/Networking/Networking.xcodeproj/project.pbxproj b/Networking/Networking.xcodeproj/project.pbxproj index 2103da20d02..9ca45e24fcb 100644 --- a/Networking/Networking.xcodeproj/project.pbxproj +++ b/Networking/Networking.xcodeproj/project.pbxproj @@ -722,6 +722,8 @@ DEC51AFB2769C66B009F3DF4 /* SystemStatusMapperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEC51AFA2769C66B009F3DF4 /* SystemStatusMapperTests.swift */; }; DEC51B02276AFB35009F3DF4 /* SystemStatus+DropinMustUsePlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEC51B01276AFB34009F3DF4 /* SystemStatus+DropinMustUsePlugin.swift */; }; DEF13C5029629EEA0024A02B /* user-complete-without-data.json in Resources */ = {isa = PBXBuildFile; fileRef = DEF13C4F29629EEA0024A02B /* user-complete-without-data.json */; }; + DEF13C562965689F0024A02B /* LeaderboardListMapperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEF13C552965689F0024A02B /* LeaderboardListMapperTests.swift */; }; + DEF13C5A296571150024A02B /* leaderboards-year-without-data.json in Resources */ = {isa = PBXBuildFile; fileRef = DEF13C59296571150024A02B /* leaderboards-year-without-data.json */; }; DEFBA74E29485A7600C35BA9 /* RESTRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEFBA74D29485A7600C35BA9 /* RESTRequest.swift */; }; DEFBA7542949CE6600C35BA9 /* RequestProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEFBA7532949CE6600C35BA9 /* RequestProcessor.swift */; }; DEFBA7562949D17400C35BA9 /* DefaultRequestAuthenticatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEFBA7552949D17300C35BA9 /* DefaultRequestAuthenticatorTests.swift */; }; @@ -1515,6 +1517,8 @@ DEC51AFA2769C66B009F3DF4 /* SystemStatusMapperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemStatusMapperTests.swift; sourceTree = ""; }; DEC51B01276AFB34009F3DF4 /* SystemStatus+DropinMustUsePlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SystemStatus+DropinMustUsePlugin.swift"; sourceTree = ""; }; DEF13C4F29629EEA0024A02B /* user-complete-without-data.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "user-complete-without-data.json"; sourceTree = ""; }; + DEF13C552965689F0024A02B /* LeaderboardListMapperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LeaderboardListMapperTests.swift; sourceTree = ""; }; + DEF13C59296571150024A02B /* leaderboards-year-without-data.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "leaderboards-year-without-data.json"; sourceTree = ""; }; DEFBA74D29485A7600C35BA9 /* RESTRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RESTRequest.swift; sourceTree = ""; }; DEFBA7532949CE6600C35BA9 /* RequestProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestProcessor.swift; sourceTree = ""; }; DEFBA7552949D17300C35BA9 /* DefaultRequestAuthenticatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultRequestAuthenticatorTests.swift; sourceTree = ""; }; @@ -2167,6 +2171,7 @@ E16C59B828F927CA007D55BB /* iap-order-create.json */, 26B2F74624C55A6E0065CCC8 /* leaderboards-year.json */, 268B68FC24C87E37007EBF1D /* leaderboards-year-alt.json */, + DEF13C59296571150024A02B /* leaderboards-year-without-data.json */, 02EF1671292F0D1900D90AD6 /* load-plan-success.json */, B505F6D420BEE4E600BB1B69 /* me.json */, 93D8BBFE226BC1DA00AD2EB3 /* me-settings.json */, @@ -2521,6 +2526,7 @@ isa = PBXGroup; children = ( B505F6D220BEE3A500BB1B69 /* AccountMapperTests.swift */, + DEF13C552965689F0024A02B /* LeaderboardListMapperTests.swift */, DE2E8EAA2954170D002E4B14 /* WordPressSiteMapperTests.swift */, 9387A6EF226E3F15001B53D7 /* AccountSettingsMapperTests.swift */, 077F39DB26A58F4800ABEADC /* SystemPluginMapperTests.swift */, @@ -2938,6 +2944,7 @@ 265BCA02243056E3004E53EE /* categories-all.json in Resources */, D8FBFF2422D52815006E3336 /* order-stats-v4-daily.json in Resources */, CEC4BF91234E40B5008D9195 /* refund-single.json in Resources */, + DEF13C5A296571150024A02B /* leaderboards-year-without-data.json in Resources */, 2683D70E24456DB8002A1589 /* categories-empty.json in Resources */, D865CE67278CA225002C8520 /* stripe-payment-intent-succeeded.json in Resources */, DE74F2A027E3137F0002FE59 /* setting-analytics.json in Resources */, @@ -3525,6 +3532,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + DEF13C562965689F0024A02B /* LeaderboardListMapperTests.swift in Sources */, 45551F142523E7FF007EF104 /* UserAgentTests.swift in Sources */, 03EB998A2906AB0C00F06A39 /* JustInTimeMessagesRemoteTests.swift in Sources */, DE34051D28BDF1C900CF0D97 /* JetpackConnectionURLMapperTests.swift in Sources */, diff --git a/Networking/Networking/Mapper/LeaderboardListMapper.swift b/Networking/Networking/Mapper/LeaderboardListMapper.swift index 0c91d133d44..f9cdac6ee64 100644 --- a/Networking/Networking/Mapper/LeaderboardListMapper.swift +++ b/Networking/Networking/Mapper/LeaderboardListMapper.swift @@ -8,7 +8,11 @@ struct LeaderboardListMapper: Mapper { /// func map(response: Data) throws -> [Leaderboard] { let decoder = JSONDecoder() - return try decoder.decode(LeaderboardsEnvelope.self, from: response).data + do { + return try decoder.decode(LeaderboardsEnvelope.self, from: response).data + } catch { + return try decoder.decode([Leaderboard].self, from: response) + } } } diff --git a/Networking/Networking/Remote/LeaderboardsRemote.swift b/Networking/Networking/Remote/LeaderboardsRemote.swift index 4a08dd2dc51..579081f2219 100644 --- a/Networking/Networking/Remote/LeaderboardsRemote.swift +++ b/Networking/Networking/Remote/LeaderboardsRemote.swift @@ -30,7 +30,12 @@ public class LeaderboardsRemote: Remote { ParameterKeys.forceRefresh: forceRefresh ] - let request = JetpackRequest(wooApiVersion: .wcAnalytics, method: .get, siteID: siteID, path: Constants.path, parameters: parameters) + let request = JetpackRequest(wooApiVersion: .wcAnalytics, + method: .get, + siteID: siteID, + path: Constants.path, + parameters: parameters, + availableAsRESTRequest: true) let mapper = LeaderboardListMapper() enqueue(request, mapper: mapper, completion: completion) } @@ -62,7 +67,12 @@ public class LeaderboardsRemote: Remote { ParameterKeys.forceRefresh: forceRefresh ] - let request = JetpackRequest(wooApiVersion: .wcAnalytics, method: .get, siteID: siteID, path: Constants.pathDeprecated, parameters: parameters) + let request = JetpackRequest(wooApiVersion: .wcAnalytics, + method: .get, + siteID: siteID, + path: Constants.pathDeprecated, + parameters: parameters, + availableAsRESTRequest: true) let mapper = LeaderboardListMapper() enqueue(request, mapper: mapper, completion: completion) } diff --git a/Networking/NetworkingTests/Mapper/LeaderboardListMapperTests.swift b/Networking/NetworkingTests/Mapper/LeaderboardListMapperTests.swift new file mode 100644 index 00000000000..e970c010954 --- /dev/null +++ b/Networking/NetworkingTests/Mapper/LeaderboardListMapperTests.swift @@ -0,0 +1,54 @@ +import XCTest +@testable import Networking + +final class LeaderboardListMapperTests: XCTestCase { + + /// Verifies that leaderboard list is parsed. + /// + func test_mapper_parses_leaderboard_list_in_response_with_data_envelope() throws { + // Given + let list = try mapLeaderboardListResponse() + + // Then + XCTAssertFalse(list.isEmpty) + } + + /// Verifies that the leaderboard list is parsed when the response has no data envelope. + /// + func test_mapper_parses_leaderboard_list_in_response_without_data_envelope() throws { + // Given + let list = try mapLeaderboardListResponseWithoutDataEnvelope() + + // Then + XCTAssertFalse(list.isEmpty) + } +} + +// MARK: - Test Helpers +/// +private extension LeaderboardListMapperTests { + + /// Returns the LeaderboardListMapper output upon receiving `filename` (Data Encoded) + /// + func mapLeaderboardList(from filename: String) throws -> [Leaderboard] { + guard let response = Loader.contentsOf(filename) else { + throw FileNotFoundError() + } + + return try LeaderboardListMapper().map(response: response) + } + + /// Returns the LeaderboardListMapper output from `products.json` + /// + func mapLeaderboardListResponse() throws -> [Leaderboard] { + return try mapLeaderboardList(from: "leaderboards-year") + } + + /// Returns the LeaderboardListMapper output from `leaderboards-products-without-data.json` + /// + func mapLeaderboardListResponseWithoutDataEnvelope() throws -> [Leaderboard] { + return try mapLeaderboardList(from: "leaderboards-year-without-data") + } + + struct FileNotFoundError: Error {} +} diff --git a/Networking/NetworkingTests/Responses/leaderboards-year-without-data.json b/Networking/NetworkingTests/Responses/leaderboards-year-without-data.json new file mode 100644 index 00000000000..802ad26b625 --- /dev/null +++ b/Networking/NetworkingTests/Responses/leaderboards-year-without-data.json @@ -0,0 +1,47 @@ +[ + { + "id": "products", + "label": "Top Products - Items Sold", + "headers": [ + { + "label": "Product" + }, + { + "label": "Items Sold" + }, + { + "label": "Net Sales" + } + ], + "rows": [ + [ + { + "display": "Aljafor<\/a>", + "value": "Aljafor" + }, + { + "display": "4", + "value": 4 + }, + { + "display": "$<\/span>20,000.00<\/span>", + "value": 20000 + } + ], + [ + { + "display": "Album<\/a>", + "value": "Album" + }, + { + "display": "1", + "value": 1 + }, + { + "display": "$<\/span>15.00<\/span>", + "value": 15.99 + } + ] + ] + } +]