2019-11-08 18:47:28 -08:00
|
|
|
//
|
|
|
|
|
// ResourceObjectDecodingErrorTests.swift
|
|
|
|
|
//
|
|
|
|
|
//
|
|
|
|
|
// Created by Mathew Polzin on 11/8/19.
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
import XCTest
|
|
|
|
|
@testable import JSONAPI
|
|
|
|
|
|
|
|
|
|
// MARK: - Relationships
|
|
|
|
|
final class ResourceObjectDecodingErrorTests: XCTestCase {
|
|
|
|
|
func test_missingRelationshipsObject() {
|
|
|
|
|
XCTAssertThrowsError(try testDecoder.decode(
|
|
|
|
|
TestEntity.self,
|
|
|
|
|
from: entity_relationships_entirely_missing
|
|
|
|
|
)) { error in
|
|
|
|
|
XCTAssertEqual(
|
|
|
|
|
error as? ResourceObjectDecodingError,
|
|
|
|
|
ResourceObjectDecodingError(
|
|
|
|
|
subjectName: ResourceObjectDecodingError.entireObject,
|
|
|
|
|
cause: .keyNotFound,
|
|
|
|
|
location: .relationships
|
|
|
|
|
)
|
|
|
|
|
)
|
2019-11-09 00:33:42 -08:00
|
|
|
|
|
|
|
|
XCTAssertEqual(
|
|
|
|
|
(error as? ResourceObjectDecodingError)?.description,
|
|
|
|
|
"relationships object is required and missing."
|
|
|
|
|
)
|
2019-11-08 18:47:28 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func test_required_relationship() {
|
|
|
|
|
XCTAssertThrowsError(try testDecoder.decode(
|
|
|
|
|
TestEntity.self,
|
|
|
|
|
from: entity_required_relationship_is_omitted
|
|
|
|
|
)) { error in
|
|
|
|
|
XCTAssertEqual(
|
|
|
|
|
error as? ResourceObjectDecodingError,
|
|
|
|
|
ResourceObjectDecodingError(
|
|
|
|
|
subjectName: "required",
|
|
|
|
|
cause: .keyNotFound,
|
|
|
|
|
location: .relationships
|
|
|
|
|
)
|
|
|
|
|
)
|
2019-11-09 00:33:42 -08:00
|
|
|
|
|
|
|
|
XCTAssertEqual(
|
|
|
|
|
(error as? ResourceObjectDecodingError)?.description,
|
|
|
|
|
"'required' relationship is required and missing."
|
|
|
|
|
)
|
2019-11-08 18:47:28 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func test_NonNullable_relationship() {
|
|
|
|
|
XCTAssertThrowsError(try testDecoder.decode(
|
|
|
|
|
TestEntity.self,
|
|
|
|
|
from: entity_nonNullable_relationship_is_null
|
|
|
|
|
)) { error in
|
|
|
|
|
XCTAssertEqual(
|
|
|
|
|
error as? ResourceObjectDecodingError,
|
|
|
|
|
ResourceObjectDecodingError(
|
|
|
|
|
subjectName: "required",
|
|
|
|
|
cause: .valueNotFound,
|
|
|
|
|
location: .relationships
|
|
|
|
|
)
|
|
|
|
|
)
|
2019-11-09 00:33:42 -08:00
|
|
|
|
|
|
|
|
XCTAssertEqual(
|
|
|
|
|
(error as? ResourceObjectDecodingError)?.description,
|
2019-11-12 18:34:34 -08:00
|
|
|
"'required' relationship is not nullable but null was found."
|
2019-11-09 00:33:42 -08:00
|
|
|
)
|
2019-11-08 18:47:28 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func test_NonNullable_relationship2() {
|
|
|
|
|
XCTAssertThrowsError(try testDecoder.decode(
|
|
|
|
|
TestEntity.self,
|
|
|
|
|
from: entity_nonNullable_relationship_is_null2
|
|
|
|
|
)) { error in
|
|
|
|
|
XCTAssertEqual(
|
|
|
|
|
error as? ResourceObjectDecodingError,
|
|
|
|
|
ResourceObjectDecodingError(
|
|
|
|
|
subjectName: "required",
|
|
|
|
|
cause: .valueNotFound,
|
|
|
|
|
location: .relationships
|
|
|
|
|
)
|
|
|
|
|
)
|
2019-11-09 00:33:42 -08:00
|
|
|
|
|
|
|
|
XCTAssertEqual(
|
|
|
|
|
(error as? ResourceObjectDecodingError)?.description,
|
2019-11-12 18:34:34 -08:00
|
|
|
"'required' relationship is not nullable but null was found."
|
2019-11-09 00:33:42 -08:00
|
|
|
)
|
2019-11-08 18:47:28 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func test_oneTypeVsAnother_relationship() {
|
|
|
|
|
XCTAssertThrowsError(try testDecoder.decode(
|
|
|
|
|
TestEntity.self,
|
|
|
|
|
from: entity_relationship_is_wrong_type
|
|
|
|
|
)) { error in
|
|
|
|
|
XCTAssertEqual(
|
|
|
|
|
error as? ResourceObjectDecodingError,
|
|
|
|
|
ResourceObjectDecodingError(
|
|
|
|
|
subjectName: "required",
|
|
|
|
|
cause: .jsonTypeMismatch(expectedType: "thirteenth_test_entities", foundType: "not_the_same"),
|
|
|
|
|
location: .relationships
|
|
|
|
|
)
|
|
|
|
|
)
|
2019-11-09 00:33:42 -08:00
|
|
|
|
|
|
|
|
XCTAssertEqual(
|
|
|
|
|
(error as? ResourceObjectDecodingError)?.description,
|
|
|
|
|
#"'required' relationship is of JSON:API type "not_the_same" but it was expected to be "thirteenth_test_entities""#
|
|
|
|
|
)
|
2019-11-08 18:47:28 -08:00
|
|
|
}
|
|
|
|
|
}
|
2019-11-09 00:33:42 -08:00
|
|
|
|
|
|
|
|
func test_twoOneVsToMany_relationship() {
|
2019-11-10 20:46:35 -08:00
|
|
|
|
|
|
|
|
XCTAssertThrowsError(try testDecoder.decode(
|
|
|
|
|
TestEntity.self,
|
|
|
|
|
from: entity_single_relationship_is_many
|
|
|
|
|
)) { error in
|
|
|
|
|
XCTAssertEqual(
|
|
|
|
|
error as? ResourceObjectDecodingError,
|
|
|
|
|
ResourceObjectDecodingError(
|
|
|
|
|
subjectName: "required",
|
|
|
|
|
cause: .quantityMismatch(expected: .one),
|
|
|
|
|
location: .relationships
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
XCTAssertEqual(
|
|
|
|
|
(error as? ResourceObjectDecodingError)?.description,
|
|
|
|
|
"'required' relationship should contain one value but found many"
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
XCTAssertThrowsError(try testDecoder.decode(
|
|
|
|
|
TestEntity.self,
|
|
|
|
|
from: entity_many_relationship_is_single
|
|
|
|
|
)) { error in
|
|
|
|
|
XCTAssertEqual(
|
|
|
|
|
error as? ResourceObjectDecodingError,
|
|
|
|
|
ResourceObjectDecodingError(
|
|
|
|
|
subjectName: "omittable",
|
|
|
|
|
cause: .quantityMismatch(expected: .many),
|
|
|
|
|
location: .relationships
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
XCTAssertEqual(
|
|
|
|
|
(error as? ResourceObjectDecodingError)?.description,
|
|
|
|
|
"'omittable' relationship should contain many values but found one"
|
|
|
|
|
)
|
|
|
|
|
}
|
2019-11-09 00:33:42 -08:00
|
|
|
}
|
2019-11-08 18:47:28 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// MARK: - Attributes
|
|
|
|
|
extension ResourceObjectDecodingErrorTests {
|
2019-11-09 00:33:42 -08:00
|
|
|
func test_missingAttributesObject() {
|
|
|
|
|
XCTAssertThrowsError(try testDecoder.decode(
|
|
|
|
|
TestEntity2.self,
|
|
|
|
|
from: entity_attributes_entirely_missing
|
|
|
|
|
)) { error in
|
|
|
|
|
XCTAssertEqual(
|
|
|
|
|
error as? ResourceObjectDecodingError,
|
|
|
|
|
ResourceObjectDecodingError(
|
|
|
|
|
subjectName: ResourceObjectDecodingError.entireObject,
|
|
|
|
|
cause: .keyNotFound,
|
|
|
|
|
location: .attributes
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
XCTAssertEqual(
|
|
|
|
|
(error as? ResourceObjectDecodingError)?.description,
|
|
|
|
|
"attributes object is required and missing."
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func test_required_attribute() {
|
|
|
|
|
XCTAssertThrowsError(try testDecoder.decode(
|
|
|
|
|
TestEntity2.self,
|
|
|
|
|
from: entity_required_attribute_is_omitted
|
|
|
|
|
)) { error in
|
|
|
|
|
XCTAssertEqual(
|
|
|
|
|
error as? ResourceObjectDecodingError,
|
|
|
|
|
ResourceObjectDecodingError(
|
|
|
|
|
subjectName: "required",
|
|
|
|
|
cause: .keyNotFound,
|
|
|
|
|
location: .attributes
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
XCTAssertEqual(
|
|
|
|
|
(error as? ResourceObjectDecodingError)?.description,
|
|
|
|
|
"'required' attribute is required and missing."
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func test_NonNullable_attribute() {
|
|
|
|
|
XCTAssertThrowsError(try testDecoder.decode(
|
|
|
|
|
TestEntity2.self,
|
|
|
|
|
from: entity_nonNullable_attribute_is_null
|
|
|
|
|
)) { error in
|
|
|
|
|
XCTAssertEqual(
|
|
|
|
|
error as? ResourceObjectDecodingError,
|
|
|
|
|
ResourceObjectDecodingError(
|
|
|
|
|
subjectName: "required",
|
|
|
|
|
cause: .valueNotFound,
|
|
|
|
|
location: .attributes
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
XCTAssertEqual(
|
|
|
|
|
(error as? ResourceObjectDecodingError)?.description,
|
2019-11-12 18:34:34 -08:00
|
|
|
"'required' attribute is not nullable but null was found."
|
2019-11-09 00:33:42 -08:00
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func test_oneTypeVsAnother_attribute() {
|
|
|
|
|
XCTAssertThrowsError(try testDecoder.decode(
|
|
|
|
|
TestEntity2.self,
|
|
|
|
|
from: entity_attribute_is_wrong_type
|
|
|
|
|
)) { error in
|
|
|
|
|
XCTAssertEqual(
|
|
|
|
|
error as? ResourceObjectDecodingError,
|
|
|
|
|
ResourceObjectDecodingError(
|
|
|
|
|
subjectName: "required",
|
|
|
|
|
cause: .typeMismatch(expectedTypeName: String(describing: String.self)),
|
|
|
|
|
location: .attributes
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
XCTAssertEqual(
|
|
|
|
|
(error as? ResourceObjectDecodingError)?.description,
|
|
|
|
|
"'required' attribute is not a String as expected."
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func test_oneTypeVsAnother_attribute2() {
|
|
|
|
|
XCTAssertThrowsError(try testDecoder.decode(
|
|
|
|
|
TestEntity2.self,
|
|
|
|
|
from: entity_attribute_is_wrong_type2
|
|
|
|
|
)) { error in
|
|
|
|
|
XCTAssertEqual(
|
|
|
|
|
error as? ResourceObjectDecodingError,
|
|
|
|
|
ResourceObjectDecodingError(
|
|
|
|
|
subjectName: "other",
|
|
|
|
|
cause: .typeMismatch(expectedTypeName: String(describing: Int.self)),
|
|
|
|
|
location: .attributes
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
XCTAssertEqual(
|
|
|
|
|
(error as? ResourceObjectDecodingError)?.description,
|
|
|
|
|
"'other' attribute is not a Int as expected."
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func test_oneTypeVsAnother_attribute3() {
|
|
|
|
|
XCTAssertThrowsError(try testDecoder.decode(
|
|
|
|
|
TestEntity2.self,
|
|
|
|
|
from: entity_attribute_is_wrong_type3
|
|
|
|
|
)) { error in
|
|
|
|
|
XCTAssertEqual(
|
|
|
|
|
error as? ResourceObjectDecodingError,
|
|
|
|
|
ResourceObjectDecodingError(
|
|
|
|
|
subjectName: "yetAnother",
|
|
|
|
|
cause: .typeMismatch(expectedTypeName: String(describing: Bool.self)),
|
|
|
|
|
location: .attributes
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
XCTAssertEqual(
|
|
|
|
|
(error as? ResourceObjectDecodingError)?.description,
|
|
|
|
|
"'yetAnother' attribute is not a Bool as expected."
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-11-12 18:34:34 -08:00
|
|
|
|
|
|
|
|
func test_transformed_attribute() {
|
|
|
|
|
XCTAssertThrowsError(try testDecoder.decode(
|
|
|
|
|
TestEntity2.self,
|
|
|
|
|
from: entity_attribute_is_wrong_type4
|
|
|
|
|
)) { error in
|
|
|
|
|
XCTAssertEqual(
|
|
|
|
|
error as? ResourceObjectDecodingError,
|
|
|
|
|
ResourceObjectDecodingError(
|
|
|
|
|
subjectName: "transformed",
|
|
|
|
|
cause: .typeMismatch(expectedTypeName: String(describing: Int.self)),
|
|
|
|
|
location: .attributes
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
XCTAssertEqual(
|
|
|
|
|
(error as? ResourceObjectDecodingError)?.description,
|
|
|
|
|
"'transformed' attribute is not a Int as expected."
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func test_transformed_attribute2() {
|
|
|
|
|
XCTAssertThrowsError(try testDecoder.decode(
|
|
|
|
|
TestEntity2.self,
|
|
|
|
|
from: entity_attribute_always_fails
|
|
|
|
|
)) { error in
|
|
|
|
|
XCTAssertEqual(
|
|
|
|
|
String(describing: error),
|
|
|
|
|
"Error: Always Fails"
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-11-08 18:47:28 -08:00
|
|
|
}
|
|
|
|
|
|
2019-11-10 23:02:26 -08:00
|
|
|
// MARK: - JSON:API Type
|
|
|
|
|
extension ResourceObjectDecodingErrorTests {
|
2019-11-12 18:34:34 -08:00
|
|
|
func test_wrongJSONAPIType() {
|
2019-11-10 23:02:26 -08:00
|
|
|
XCTAssertThrowsError(try testDecoder.decode(
|
|
|
|
|
TestEntity2.self,
|
|
|
|
|
from: entity_is_wrong_type
|
|
|
|
|
)) { error in
|
|
|
|
|
XCTAssertEqual(
|
|
|
|
|
error as? ResourceObjectDecodingError,
|
|
|
|
|
ResourceObjectDecodingError(
|
|
|
|
|
subjectName: "self",
|
|
|
|
|
cause: .jsonTypeMismatch(expectedType: "fourteenth_test_entities", foundType: "not_correct_type"),
|
|
|
|
|
location: .type
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
XCTAssertEqual(
|
|
|
|
|
(error as? ResourceObjectDecodingError)?.description,
|
|
|
|
|
#"found JSON:API type "not_correct_type" but expected "fourteenth_test_entities""#
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-11-12 18:34:34 -08:00
|
|
|
|
|
|
|
|
func test_wrongDecodedType() {
|
|
|
|
|
XCTAssertThrowsError(try testDecoder.decode(
|
|
|
|
|
TestEntity2.self,
|
|
|
|
|
from: entity_type_is_wrong_type
|
|
|
|
|
)) { error in
|
|
|
|
|
XCTAssertEqual(
|
|
|
|
|
error as? ResourceObjectDecodingError,
|
|
|
|
|
ResourceObjectDecodingError(
|
|
|
|
|
subjectName: "type",
|
|
|
|
|
cause: .typeMismatch(expectedTypeName: String(describing: String.self)),
|
|
|
|
|
location: .type
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
XCTAssertEqual(
|
|
|
|
|
(error as? ResourceObjectDecodingError)?.description,
|
|
|
|
|
#"'type' (a.k.a. the JSON:API type name) is not a String as expected."#
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func test_type_missing() {
|
|
|
|
|
XCTAssertThrowsError(try testDecoder.decode(
|
|
|
|
|
TestEntity2.self,
|
|
|
|
|
from: entity_type_is_missing
|
|
|
|
|
)) { error in
|
|
|
|
|
XCTAssertEqual(
|
|
|
|
|
error as? ResourceObjectDecodingError,
|
|
|
|
|
ResourceObjectDecodingError(
|
|
|
|
|
subjectName: "type",
|
|
|
|
|
cause: .keyNotFound,
|
|
|
|
|
location: .type
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
XCTAssertEqual(
|
|
|
|
|
(error as? ResourceObjectDecodingError)?.description,
|
|
|
|
|
#"'type' (a.k.a. JSON:API type name) is required and missing."#
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func test_type_null() {
|
|
|
|
|
XCTAssertThrowsError(try testDecoder.decode(
|
|
|
|
|
TestEntity2.self,
|
|
|
|
|
from: entity_type_is_null
|
|
|
|
|
)) { error in
|
|
|
|
|
XCTAssertEqual(
|
|
|
|
|
error as? ResourceObjectDecodingError,
|
|
|
|
|
ResourceObjectDecodingError(
|
|
|
|
|
subjectName: "type",
|
|
|
|
|
cause: .valueNotFound,
|
|
|
|
|
location: .type
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
XCTAssertEqual(
|
|
|
|
|
(error as? ResourceObjectDecodingError)?.description,
|
|
|
|
|
#"'type' (a.k.a. JSON:API type name) is not nullable but null was found."#
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-11-10 23:02:26 -08:00
|
|
|
}
|
|
|
|
|
|
2019-11-08 18:47:28 -08:00
|
|
|
// MARK: - Test Types
|
|
|
|
|
extension ResourceObjectDecodingErrorTests {
|
|
|
|
|
enum TestEntityType: ResourceObjectDescription {
|
|
|
|
|
public static var jsonType: String { return "thirteenth_test_entities" }
|
|
|
|
|
|
|
|
|
|
typealias Attributes = NoAttributes
|
|
|
|
|
|
|
|
|
|
public struct Relationships: JSONAPI.Relationships {
|
|
|
|
|
|
|
|
|
|
let required: ToOneRelationship<TestEntity, NoMetadata, NoLinks>
|
2019-11-10 20:46:35 -08:00
|
|
|
let omittable: ToManyRelationship<TestEntity, NoMetadata, NoLinks>?
|
2019-11-08 18:47:28 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
typealias TestEntity = BasicEntity<TestEntityType>
|
2019-11-09 00:33:42 -08:00
|
|
|
|
|
|
|
|
enum TestEntityType2: ResourceObjectDescription {
|
2019-11-10 20:46:35 -08:00
|
|
|
public static var jsonType: String { return "fourteenth_test_entities" }
|
2019-11-09 00:33:42 -08:00
|
|
|
|
|
|
|
|
public struct Attributes: JSONAPI.Attributes {
|
|
|
|
|
|
|
|
|
|
let required: Attribute<String>
|
|
|
|
|
let other: Attribute<Int>?
|
|
|
|
|
let yetAnother: Attribute<Bool?>?
|
2019-11-12 18:34:34 -08:00
|
|
|
let transformed: TransformedAttribute<Int, IntToString>?
|
|
|
|
|
let transformed2: TransformedAttribute<String, AlwaysFails>?
|
2019-11-09 00:33:42 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
typealias Relationships = NoRelationships
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
typealias TestEntity2 = BasicEntity<TestEntityType2>
|
2019-11-12 18:34:34 -08:00
|
|
|
|
|
|
|
|
enum IntToString: Transformer {
|
|
|
|
|
static func transform(_ value: Int) throws -> String {
|
|
|
|
|
return "\(value)"
|
|
|
|
|
}
|
|
|
|
|
typealias From = Int
|
|
|
|
|
typealias To = String
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
enum AlwaysFails: Transformer {
|
|
|
|
|
static func transform(_ value: String) throws -> String {
|
|
|
|
|
throw Error()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct Error: Swift.Error, CustomStringConvertible {
|
|
|
|
|
let description: String = "Error: Always Fails"
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-11-08 18:47:28 -08:00
|
|
|
}
|