Skip to content

Commit

Permalink
Fix the parsing of Profile responses (#95)
Browse files Browse the repository at this point in the history
* Add include to listBundleIds request

- Adds a test for this

* Add .DS_Store to .gitignore

* Set a default value for Certificate type

* Add ProfileRelationship and fix ProfileResponse

* Add additional test Models

* Improve APIProvider's error reporting

To make it easier to debug decoding failures

* Fix ProfilesResponse property types

* Expose jsonDecoder so it is usable in tests

The custom date decoding is useful for testing

* Add support for json fixture loading

* Add Profiles fixtures to test response decoding

* Add tests for decoding profiles and relationships

* Remove debugging code

* Fix lint issues

Co-authored-by: Antoine van der Lee <[email protected]>
  • Loading branch information
orj and AvdLee authored Apr 30, 2020
1 parent 49c4b1e commit 4f27f45
Show file tree
Hide file tree
Showing 19 changed files with 885 additions and 17 deletions.
Binary file removed .DS_Store
Binary file not shown.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# macOS
.DS_Store

# Xcode
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
Expand Down
54 changes: 52 additions & 2 deletions AppStoreConnect-Swift-SDK.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,12 @@
50996B3F241500190044308C /* JWTRequestsAuthenticatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50996AB0241500190044308C /* JWTRequestsAuthenticatorTests.swift */; };
50996B41241500190044308C /* QueryParameterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50996AB2241500190044308C /* QueryParameterTests.swift */; };
6D050F07241BE65700CBDABA /* ProfileCertificatesResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D050F06241BE65700CBDABA /* ProfileCertificatesResponse.swift */; };
A72A23D32449384C0093A0E9 /* ProfileRelationship.swift in Sources */ = {isa = PBXBuildFile; fileRef = A72A23D22449384C0093A0E9 /* ProfileRelationship.swift */; };
A72A23D5244938610093A0E9 /* ProfileRelationshipTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A72A23D4244938610093A0E9 /* ProfileRelationshipTests.swift */; };
A72A23DB2449469E0093A0E9 /* Fixture.swift in Sources */ = {isa = PBXBuildFile; fileRef = A72A23D92449469B0093A0E9 /* Fixture.swift */; };
A72A23E2244948200093A0E9 /* ProfilesResponseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A72A23E1244948200093A0E9 /* ProfilesResponseTests.swift */; };
A72A23E4244949530093A0E9 /* Fixtures.bundle in Resources */ = {isa = PBXBuildFile; fileRef = A72A23D62449406C0093A0E9 /* Fixtures.bundle */; };
A72A23E7244956260093A0E9 /* Bundle+Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A72A23E6244956260093A0E9 /* Bundle+Tests.swift */; };
A757C8702446F457000BE880 /* ListBundleIdsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = A757C86E2446F452000BE880 /* ListBundleIdsTest.swift */; };
OBJ_1049 /* AppStoreConnect_Swift_SDK.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = "AppStoreConnect-Swift-SDK::AppStoreConnect-Swift-SDK::Product" /* AppStoreConnect_Swift_SDK.framework */; };
OBJ_554 /* APIProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_8 /* APIProvider.swift */; };
Expand Down Expand Up @@ -641,6 +647,12 @@
50996AB0241500190044308C /* JWTRequestsAuthenticatorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = JWTRequestsAuthenticatorTests.swift; path = Tests/JWTRequestsAuthenticatorTests.swift; sourceTree = SOURCE_ROOT; };
50996AB2241500190044308C /* QueryParameterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = QueryParameterTests.swift; path = Tests/QueryParameterTests.swift; sourceTree = SOURCE_ROOT; };
6D050F06241BE65700CBDABA /* ProfileCertificatesResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProfileCertificatesResponse.swift; sourceTree = "<group>"; };
A72A23D22449384C0093A0E9 /* ProfileRelationship.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileRelationship.swift; sourceTree = "<group>"; };
A72A23D4244938610093A0E9 /* ProfileRelationshipTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileRelationshipTests.swift; sourceTree = "<group>"; };
A72A23D62449406C0093A0E9 /* Fixtures.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; name = Fixtures.bundle; path = Tests/Models/Fixtures.bundle; sourceTree = SOURCE_ROOT; };
A72A23D92449469B0093A0E9 /* Fixture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Fixture.swift; path = Tests/Models/Fixture.swift; sourceTree = SOURCE_ROOT; };
A72A23E1244948200093A0E9 /* ProfilesResponseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfilesResponseTests.swift; sourceTree = "<group>"; };
A72A23E6244956260093A0E9 /* Bundle+Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Bundle+Tests.swift"; sourceTree = "<group>"; };
A757C86E2446F452000BE880 /* ListBundleIdsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListBundleIdsTest.swift; sourceTree = "<group>"; };
"AppStoreConnect-Swift-SDK::AppStoreConnect-Swift-SDK::Product" /* AppStoreConnect_Swift_SDK.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = AppStoreConnect_Swift_SDK.framework; sourceTree = BUILT_PRODUCTS_DIR; };
"AppStoreConnect-Swift-SDK::AppStoreConnect-Swift-SDKTests::Product" /* AppStoreConnect-Swift-SDKTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; path = "AppStoreConnect-Swift-SDKTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
Expand Down Expand Up @@ -1292,16 +1304,34 @@
50996AA8241500190044308C /* Models */ = {
isa = PBXGroup;
children = (
A72A23E0244947FF0093A0E9 /* Responses */,
50996AA9241500190044308C /* BetaTesterRelationshipTests.swift */,
50996AAA241500190044308C /* BetaGroupRelationshipTests.swift */,
50996AAB241500190044308C /* PreReleaseVersionRelationshipTests.swift */,
50996AAC241500190044308C /* AppRelationshipTests.swift */,
50996AAD241500190044308C /* BuildRelationshipTests.swift */,
A72A23D4244938610093A0E9 /* ProfileRelationshipTests.swift */,
);
name = Models;
path = Tests/Models;
sourceTree = SOURCE_ROOT;
};
A72A23E0244947FF0093A0E9 /* Responses */ = {
isa = PBXGroup;
children = (
A72A23E1244948200093A0E9 /* ProfilesResponseTests.swift */,
);
path = Responses;
sourceTree = "<group>";
};
A72A23E5244955F20093A0E9 /* Helpers */ = {
isa = PBXGroup;
children = (
A72A23E6244956260093A0E9 /* Bundle+Tests.swift */,
);
path = Helpers;
sourceTree = "<group>";
};
A757C86D2446F41A000BE880 /* BundleIds */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -1693,6 +1723,7 @@
OBJ_364 /* UserVisibleAppsLinkagesRequest.swift */,
OBJ_365 /* UserVisibleAppsLinkagesResponse.swift */,
OBJ_366 /* UsersResponse.swift */,
A72A23D22449384C0093A0E9 /* ProfileRelationship.swift */,
);
path = Models;
sourceTree = "<group>";
Expand Down Expand Up @@ -1741,14 +1772,16 @@
children = (
50996AAF241500190044308C /* APIProviderTests.swift */,
50996A12241500190044308C /* Endpoints */,
A72A23D92449469B0093A0E9 /* Fixture.swift */,
A72A23D62449406C0093A0E9 /* Fixtures.bundle */,
A72A23E5244955F20093A0E9 /* Helpers */,
50996AB0241500190044308C /* JWTRequestsAuthenticatorTests.swift */,
50996AAE241500190044308C /* JWTTests.swift */,
50996AA8241500190044308C /* Models */,
50996A11241500190044308C /* Models+Tests.swift */,
50996AB2241500190044308C /* QueryParameterTests.swift */,
);
name = Tests;
path = "Example/CocoaPods-AppStoreConnect-Swift-SDK/Tests";
path = Tests;
sourceTree = SOURCE_ROOT;
};
OBJ_48 /* Sales and Finance Reports */ = {
Expand Down Expand Up @@ -1932,6 +1965,7 @@
buildPhases = (
OBJ_905 /* Sources */,
OBJ_1048 /* Frameworks */,
A72A23E3244949490093A0E9 /* Resources */,
);
buildRules = (
);
Expand Down Expand Up @@ -1975,6 +2009,17 @@
};
/* End PBXProject section */

/* Begin PBXResourcesBuildPhase section */
A72A23E3244949490093A0E9 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
A72A23E4244949530093A0E9 /* Fixtures.bundle in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */

/* Begin PBXSourcesBuildPhase section */
OBJ_553 /* Sources */ = {
isa = PBXSourcesBuildPhase;
Expand Down Expand Up @@ -2067,6 +2112,7 @@
OBJ_638 /* GetBetaTesterIDsInBetaGroup.swift in Sources */,
OBJ_639 /* GetBuildIDsInBetaGroup.swift in Sources */,
6D050F07241BE65700CBDABA /* ProfileCertificatesResponse.swift in Sources */,
A72A23D32449384C0093A0E9 /* ProfileRelationship.swift in Sources */,
OBJ_640 /* ListBetaGroups.swift in Sources */,
OBJ_641 /* ListBetaTestersInBetaGroup.swift in Sources */,
OBJ_642 /* ListBuildsForBetaGroup.swift in Sources */,
Expand Down Expand Up @@ -2349,7 +2395,9 @@
50996AD9241500190044308C /* RemoveIndividualTestersFromBuildTests.swift in Sources */,
50996ACD241500190044308C /* ReadAppEncryptionDeclarationInformation.swift in Sources */,
50996ABA241500190044308C /* CreateBetaTesterTests.swift in Sources */,
A72A23E2244948200093A0E9 /* ProfilesResponseTests.swift in Sources */,
50996B1E241500190044308C /* GetBuildIDsInBetaGroupTests.swift in Sources */,
A72A23DB2449469E0093A0E9 /* Fixture.swift in Sources */,
50996AD7241500190044308C /* ModifyBuildTests.swift in Sources */,
50996B30241500190044308C /* InviteUserTests.swift in Sources */,
50996ADA241500190044308C /* ReadBuildBetaDetailsInformationOfBuildTests.swift in Sources */,
Expand Down Expand Up @@ -2460,7 +2508,9 @@
50996AD8241500190044308C /* GetResourceIDsOfIndividualTestersForBuildTests.swift in Sources */,
50996ABF241500190044308C /* GetAppResourceIDsForBetaTesterTests.swift in Sources */,
50996B26241500190044308C /* GetAppResourceIDForBetaGroupTests.swift in Sources */,
A72A23D5244938610093A0E9 /* ProfileRelationshipTests.swift in Sources */,
50996B24241500190044308C /* ReadAppInformationOfBetaGroupTests.swift in Sources */,
A72A23E7244956260093A0E9 /* Bundle+Tests.swift in Sources */,
50996AD6241500190044308C /* ReadAppEncryptionDeclarationOfBuildTests.swift in Sources */,
50996ACA241500190044308C /* ListAppEncryptionDeclarations.swift in Sources */,
50996ABD241500190044308C /* GetBetaGroupIDsOfBetaTesterGroupsTests.swift in Sources */,
Expand Down
25 changes: 15 additions & 10 deletions Sources/APIProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ public final class APIProvider {
case requestGeneration
case unknownResponseType
case requestFailure(StatusCode, Data?)
case decodingError(Data)
case decodingError(Swift.Error, Data)
case dateDecodingError(String)
case requestExecutorError(Swift.Error)

public var debugDescription: String {
Expand All @@ -53,11 +54,13 @@ public final class APIProvider {
return "Request failed with status code \(statusCode) and response \(response))."
}
return "Request failed with status code \(statusCode)."
case .decodingError(let data):
if let error = String(data: data, encoding: .utf8) {
return "Failed to decode data: \(error)."
case .decodingError(let error, let data):
if let response = String(data: data, encoding: .utf8) {
return "Failed to decode response:\n\(response)\nError: \(error)."
}
return "Failed to decode data."
case .dateDecodingError(let date):
return "Failed to decode date: \(date)"
case .requestExecutorError(let error):
return "Failed to execute request \(error)."
}
Expand All @@ -67,7 +70,7 @@ public final class APIProvider {
public typealias StatusCode = Int

/// Contains a JSON Decoder which can be reused.
private let jsonDecoder: JSONDecoder = {
static let jsonDecoder: JSONDecoder = {
let decoder = JSONDecoder()
let formatter = DateFormatter()
decoder.dateDecodingStrategy = .custom({ (decoder) -> Date in
Expand All @@ -82,7 +85,7 @@ public final class APIProvider {
if let date = formatter.date(from: dateStr) {
return date
}
throw APIProvider.Error.decodingError(Data(dateStr.utf8))
throw APIProvider.Error.dateDecodingError(dateStr)
})
return decoder
}()
Expand Down Expand Up @@ -159,11 +162,13 @@ private extension APIProvider {
guard let data = response.data, 200..<300 ~= response.statusCode else {
return .failure(Error.requestFailure(response.statusCode, response.data))
}
guard let decodedValue = try? jsonDecoder.decode(T.self, from: data) else {
return .failure(Error.decodingError(data))
}

return .success(decodedValue)
do {
let decodedValue = try Self.jsonDecoder.decode(T.self, from: data)
return .success(decodedValue)
} catch {
return .failure(Error.decodingError(error, data))
}
case .failure(let error):
return .failure(Error.requestExecutorError(error))
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/Models/BundleIdResponse.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import Foundation

/// A response containing a single resource.
public struct BundleIdResponse: Decodable {
public struct BundleIdResponse: Codable {

/// The resource data.
public let data: BundleId
Expand Down
2 changes: 1 addition & 1 deletion Sources/Models/Certificate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public struct Certificate: Codable {
public let `id`: String

/// The resource type.
public let type: String
public let type: String = "certificates"

/// Navigational links that include the self-link.
public let links: PagedDocumentLinks
Expand Down
44 changes: 44 additions & 0 deletions Sources/Models/ProfileRelationship.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//
// ProfileRelationship.swift
// AppStoreConnect-Swift-SDK
//
// Created by Oliver Jones on 17/4/20.
//

import Foundation

public enum ProfileRelationship: Codable {
case bundleId(BundleId)
case certificate(Certificate)
case device(Device)

enum TypeKeys: String, CodingKey {
case type
}
enum CodingKeys: String, Decodable, CodingKey {
case bundleIds, certificates, devices
}

public init(from decoder: Decoder) throws {
let type = try decoder.container(keyedBy: TypeKeys.self).decode(CodingKeys.self, forKey: .type)
switch type {
case .bundleIds:
self = try .bundleId(BundleId(from: decoder))
case .certificates:
self = try .certificate(Certificate(from: decoder))
case .devices:
self = try .device(Device(from: decoder))
}
}

public func encode(to encoder: Encoder) throws {
switch self {
case .bundleId(let value):
try value.encode(to: encoder)
case .certificate(let value):
try value.encode(to: encoder)
case .device(let value):
try value.encode(to: encoder)
}
}
}
2 changes: 1 addition & 1 deletion Sources/Models/ProfileResponse.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,5 @@ public struct ProfileResponse: Codable {

/// The requested relationship data.
/// Possible types: BundleId, Device, Certificate
public let included: [AppRelationship]?
public let included: [ProfileRelationship]?
}
4 changes: 2 additions & 2 deletions Sources/Models/ProfilesResponse.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ public struct ProfilesResponse: Codable {
public let data: [Profile]

/// Navigational links that include the self-link.
public let links: DocumentLinks
public let links: PagedDocumentLinks

/// Paging information.
public let meta: PagingInformation?

/// The requested relationship data.
/// Possible types: BundleId, Device, Certificate
public let included: [AppRelationship]?
public let included: [ProfileRelationship]?
}
14 changes: 14 additions & 0 deletions Tests/Helpers/Bundle+Tests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//
// Bundle+Tests.swift
// AppStoreConnect-Swift-SDK
//
// Created by Oliver Jones on 17/4/20.
//

import Foundation

extension Bundle {
static let tests = Bundle(for: BundleTag.self)
}

private final class BundleTag {}
48 changes: 48 additions & 0 deletions Tests/Models+Tests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,54 @@ extension BetaTester {
links: .test)
}

extension Device.Attributes {
static var test = Device.Attributes(
deviceClass: nil,
model: nil,
name: nil,
platform: nil,
status: nil,
udid: nil,
addedDate: nil
)
}

extension Device {
static var test = Device(
attributes: .test,
id: "id",
links: .test)
}

extension Certificate.Attributes {
static var test = Certificate.Attributes(
certificateContent: nil,
displayName: nil,
expirationDate: nil,
name: nil,
platform: nil,
serialNumber: nil,
certificateType: nil
)
}

extension Certificate {
static var test = Certificate(
attributes: .test,
id: "id",
links: .test
)
}

extension BundleId {
static var test = BundleId(
attributes: nil,
id: "id",
relationships: nil,
links: .test
)
}

extension BuildBetaDetail {
static var test = BuildBetaDetail(
attributes: nil,
Expand Down
20 changes: 20 additions & 0 deletions Tests/Models/Fixture.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//
// Fixture.swift
// AppStoreConnect-Swift-SDK
//
// Created by Oliver Jones on 17/4/20.
//

import Foundation

struct Fixture {
var data: Data

init(named: String, in bundle: Bundle = .tests) throws {

guard let url = bundle.url(forResource: named, withExtension: "json", subdirectory: "Fixtures.bundle") else {
fatalError("Unable to find fixture named: \(named)")
}
try data = Data(contentsOf: url)
}
}
Loading

0 comments on commit 4f27f45

Please sign in to comment.