From 845d0854555117ff9802cf0ad0a027915e55963b Mon Sep 17 00:00:00 2001 From: Mathew Polzin Date: Mon, 21 Jan 2019 15:32:38 -0800 Subject: [PATCH] Fill out a fair bit of OpenAPI Entity testing --- .../JSONAPIOpenAPI/OpenAPITypes+Codable.swift | 10 +- Sources/JSONAPIOpenAPI/OpenAPITypes.swift | 14 +- .../JSONAPIAttributeOpenAPITests.swift | 2 +- .../JSONAPIEntityOpenAPITests.swift | 260 +++++++++++++++++- .../JSONAPIOpenAPITests/XCTestManifests.swift | 2 + 5 files changed, 271 insertions(+), 17 deletions(-) diff --git a/Sources/JSONAPIOpenAPI/OpenAPITypes+Codable.swift b/Sources/JSONAPIOpenAPI/OpenAPITypes+Codable.swift index 7b04957..6d0aced 100644 --- a/Sources/JSONAPIOpenAPI/OpenAPITypes+Codable.swift +++ b/Sources/JSONAPIOpenAPI/OpenAPITypes+Codable.swift @@ -110,7 +110,7 @@ extension JSONNode.ArrayContext: Encodable { } } -extension JSONNode.ObjectContext : Encodable{ +extension JSONNode.ObjectContext : Encodable { private enum CodingKeys: String, CodingKey { case maxProperties case minProperties @@ -132,13 +132,9 @@ extension JSONNode.ObjectContext : Encodable{ try container.encode(additionalProperties, forKey: .additionalProperties) } - let required = properties.filter { (name, node) in - node.required - }.keys + try container.encode(requiredProperties, forKey: .required) - try container.encode(Array(required), forKey: .required) - - try container.encode(max(minProperties, required.count), forKey: .minProperties) + try container.encode(minProperties, forKey: .minProperties) } } diff --git a/Sources/JSONAPIOpenAPI/OpenAPITypes.swift b/Sources/JSONAPIOpenAPI/OpenAPITypes.swift index 79bf009..033a3bd 100644 --- a/Sources/JSONAPIOpenAPI/OpenAPITypes.swift +++ b/Sources/JSONAPIOpenAPI/OpenAPITypes.swift @@ -371,7 +371,7 @@ public enum JSONNode: Equatable { public struct ObjectContext: Equatable { public let maxProperties: Int? - public let minProperties: Int + let _minProperties: Int public let properties: [String: JSONNode] public let additionalProperties: [String: JSONNode]? @@ -379,8 +379,16 @@ public enum JSONNode: Equatable { // NOTE that an object's required properties // array is determined by looking at its properties' // required Bool. - public let required: [String] */ + public var requiredProperties: [String] { + return Array(properties.filter { (name, node) in + node.required + }.keys) + } + + public var minProperties: Int { + return max(_minProperties, requiredProperties.count) + } public init(properties: [String: JSONNode], additionalProperties: [String: JSONNode]? = nil, @@ -389,7 +397,7 @@ public enum JSONNode: Equatable { self.properties = properties self.additionalProperties = additionalProperties self.maxProperties = maxProperties - self.minProperties = minProperties + self._minProperties = minProperties } } diff --git a/Tests/JSONAPIOpenAPITests/JSONAPIAttributeOpenAPITests.swift b/Tests/JSONAPIOpenAPITests/JSONAPIAttributeOpenAPITests.swift index 726f99a..642b95e 100644 --- a/Tests/JSONAPIOpenAPITests/JSONAPIAttributeOpenAPITests.swift +++ b/Tests/JSONAPIOpenAPITests/JSONAPIAttributeOpenAPITests.swift @@ -429,7 +429,7 @@ extension JSONAPIAttributeOpenAPITests { extension JSONAPIAttributeOpenAPITests { func test_EnumAttribute() { let node = try! Attribute.rawOpenAPINode() - print(EnumAttribute.allCases) + XCTAssertTrue(node.required) XCTAssertEqual(node.jsonTypeFormat, .string(.generic)) diff --git a/Tests/JSONAPIOpenAPITests/JSONAPIEntityOpenAPITests.swift b/Tests/JSONAPIOpenAPITests/JSONAPIEntityOpenAPITests.swift index 298100c..329e0fb 100644 --- a/Tests/JSONAPIOpenAPITests/JSONAPIEntityOpenAPITests.swift +++ b/Tests/JSONAPIOpenAPITests/JSONAPIEntityOpenAPITests.swift @@ -8,12 +8,115 @@ import XCTest import JSONAPI import JSONAPIOpenAPI +import AnyCodable class JSONAPIEntityOpenAPITests: XCTestCase { func test_EmptyEntity() { let node = try! TestType1.openAPINode() - // TODO: Write test + XCTAssertTrue(node.required) + XCTAssertEqual(node.jsonTypeFormat, .object(.generic)) + + guard case let .object(contextA, objectContext1) = node else { + XCTFail("Expected Object node") + return + } + + XCTAssertEqual(contextA, .init(format: .generic, + required: true, + nullable: false, + allowedValues: nil)) + + XCTAssertEqual(objectContext1.minProperties, 2) + XCTAssertEqual(Set(objectContext1.requiredProperties), Set(["id", "type"])) + XCTAssertEqual(Set(objectContext1.properties.keys), Set(["id", "type"])) + XCTAssertEqual(objectContext1.properties["id"], .string(.init(format: .generic, + required: true), + .init())) + XCTAssertEqual(objectContext1.properties["type"], .string(.init(format: .generic, + required: true), + .init())) + } + + func test_AttributesEntity() { + let node = try! TestType2.openAPINode() + + XCTAssertTrue(node.required) + XCTAssertEqual(node.jsonTypeFormat, .object(.generic)) + + guard case let .object(contextA, objectContext1) = node else { + XCTFail("Expected Object node") + return + } + + XCTAssertEqual(contextA, .init(format: .generic, + required: true, + nullable: false, + allowedValues: nil)) + + XCTAssertEqual(objectContext1.minProperties, 3) + XCTAssertEqual(Set(objectContext1.requiredProperties), Set(["id", "type", "attributes"])) + XCTAssertEqual(Set(objectContext1.properties.keys), Set(["id", "type", "attributes"])) + + XCTAssertEqual(objectContext1.properties["id"], .string(.init(format: .generic, + required: true), + .init())) + XCTAssertEqual(objectContext1.properties["type"], .string(.init(format: .generic, + required: true), + .init())) + + let attributesNode = objectContext1.properties["attributes"] + + XCTAssertNotNil(attributesNode) + XCTAssertTrue(attributesNode?.required ?? false) + XCTAssertEqual(attributesNode?.jsonTypeFormat, .object(.generic)) + + guard case let .object(contextB, attributesContext)? = attributesNode else { + XCTFail("Expected Object node for attributes") + return + } + + XCTAssertEqual(contextB, .init(format: .generic, + required: true, + nullable: false, + allowedValues: nil)) + + XCTAssertEqual(attributesContext.minProperties, 3) + XCTAssertEqual(Set(attributesContext.requiredProperties), Set(["stringProperty", "enumProperty", "nullableProperty"])) + XCTAssertEqual(Set(attributesContext.properties.keys), Set(["stringProperty", "enumProperty", "optionalProperty", "nullableProperty", "nullableOptionalProperty"])) + + XCTAssertEqual(attributesContext.properties["stringProperty"], + .string(.init(format: .generic, + required: true), + .init())) + + XCTAssertEqual(attributesContext.properties["enumProperty"], + .string(.init(format: .generic, + required: true, + nullable: false, + allowedValues: ["one", "two"].map(AnyCodable.init)), + .init())) + + XCTAssertEqual(attributesContext.properties["optionalProperty"], + .string(.init(format: .generic, + required: false, + nullable: false, + allowedValues: nil), + .init())) + + XCTAssertEqual(attributesContext.properties["nullableProperty"], + .string(.init(format: .generic, + required: true, + nullable: true, + allowedValues: nil), + .init())) + + XCTAssertEqual(attributesContext.properties["nullableOptionalProperty"], + .string(.init(format: .generic, + required: false, + nullable: true, + allowedValues: nil), + .init())) let encoder = JSONEncoder() encoder.outputFormatting = .prettyPrinted @@ -21,15 +124,125 @@ class JSONAPIEntityOpenAPITests: XCTestCase { print(string) } - func test_AttributesEntity() { - let node = try! TestType2.openAPINode() + func test_RelationshipsEntity() { + let node = try! TestType3.openAPINode() - // TODO: Write test + XCTAssertTrue(node.required) + XCTAssertEqual(node.jsonTypeFormat, .object(.generic)) + + guard case let .object(contextA, objectContext1) = node else { + XCTFail("Expected Object node") + return + } + + XCTAssertEqual(contextA, .init(format: .generic, + required: true, + nullable: false, + allowedValues: nil)) + + XCTAssertEqual(objectContext1.minProperties, 3) + XCTAssertEqual(Set(objectContext1.requiredProperties), Set(["id", "type", "relationships"])) + XCTAssertEqual(Set(objectContext1.properties.keys), Set(["id", "type", "relationships"])) + + XCTAssertEqual(objectContext1.properties["id"], .string(.init(format: .generic, + required: true), + .init())) + XCTAssertEqual(objectContext1.properties["type"], .string(.init(format: .generic, + required: true), + .init())) + + let relationshipsNode = objectContext1.properties["relationships"] + + XCTAssertNotNil(relationshipsNode) + XCTAssertTrue(relationshipsNode?.required ?? false) + XCTAssertEqual(relationshipsNode?.jsonTypeFormat, .object(.generic)) + + guard case let .object(contextB, relationshipsContext)? = relationshipsNode else { + XCTFail("Expected Object node for relationships") + return + } + + XCTAssertEqual(contextB, .init(format: .generic, + required: true, + nullable: false, + allowedValues: nil)) + + XCTAssertEqual(relationshipsContext.minProperties, 3) + XCTAssertEqual(Set(relationshipsContext.requiredProperties), Set(["toOne", "nullableToOne", "toMany"])) + XCTAssertEqual(Set(relationshipsContext.properties.keys), Set(["toOne", "optionalTooOne", "nullableToOne", "nullableOptionalToOne", "toMany", "optionalToMany"])) + + let pointerDataContext = JSONNode.ObjectContext(properties: ["id": .string(.init(format: .generic, + required: true), + .init()), + "type": .string(.init(format: .generic, + required: true), + .init())]) + + let pointerContext = JSONNode.ObjectContext(properties: ["data": .object(.init(format: .generic, + required: true), + pointerDataContext)]) + + let nullablePointerContext = JSONNode.ObjectContext(properties: ["data": .object(.init(format: .generic, + required: true, + nullable: true), + pointerDataContext)]) + + let manyPointerContext = JSONNode.ObjectContext(properties: ["data": .array(.init(format: .generic, + required: true), + .init(items: .object(.init(format: .generic, + required: true), + pointerDataContext)))]) + + XCTAssertEqual(relationshipsContext.properties["toOne"], + .object(.init(format: .generic, + required: true), + pointerContext)) + + XCTAssertEqual(relationshipsContext.properties["optionalTooOne"], + .object(.init(format: .generic, + required: false, + nullable: false, + allowedValues: nil), + pointerContext)) + + XCTAssertEqual(relationshipsContext.properties["nullableToOne"], + .object(.init(format: .generic, + required: true, + nullable: false, + allowedValues: nil), + nullablePointerContext)) + + XCTAssertEqual(relationshipsContext.properties["nullableOptionalToOne"], + .object(.init(format: .generic, + required: false, + nullable: false, + allowedValues: nil), + nullablePointerContext)) + + XCTAssertEqual(relationshipsContext.properties["toMany"], + .object(.init(format: .generic, + required: true), + manyPointerContext)) + + XCTAssertEqual(relationshipsContext.properties["optionalToMany"], + .object(.init(format: .generic, + required: false, + nullable: false, + allowedValues: nil), + manyPointerContext)) + } + + func test_AttributesAndRelationshipsEntity() { + // TODO: write test + + /* let encoder = JSONEncoder() encoder.outputFormatting = .prettyPrinted let string = String(data: try! encoder.encode(node), encoding: .utf8)! print(string) + + */ } } @@ -46,7 +259,7 @@ extension JSONAPIEntityOpenAPITests { typealias TestType1 = BasicEntity enum TestType2Description: EntityDescription { - public static var jsonType: String { return "test1" } + public static var jsonType: String { return "test2" } public enum EnumType: String, CaseIterable, Codable, Equatable { case one @@ -56,13 +269,19 @@ extension JSONAPIEntityOpenAPITests { public struct Attributes: JSONAPI.Attributes, Sampleable { let stringProperty: Attribute let enumProperty: Attribute + let optionalProperty: Attribute? + let nullableProperty: Attribute + let nullableOptionalProperty: Attribute? var computedProperty: Attribute { return enumProperty } public static var sample: Attributes { return Attributes(stringProperty: .init(value: "hello"), - enumProperty: .init(value: .one)) + enumProperty: .init(value: .one), + optionalProperty: nil, + nullableProperty: .init(value: nil), + nullableOptionalProperty: nil) } } @@ -70,4 +289,33 @@ extension JSONAPIEntityOpenAPITests { } typealias TestType2 = BasicEntity + + enum TestType3Description: EntityDescription { + public static var jsonType: String { return "test3" } + + public typealias Attributes = NoAttributes + + public struct Relationships: JSONAPI.Relationships, Sampleable { + public let toOne: ToOneRelationship + public let optionalTooOne: ToOneRelationship? + public let nullableToOne: ToOneRelationship + public let nullableOptionalToOne: ToOneRelationship? + + public let toMany: ToManyRelationship + public let optionalToMany: ToManyRelationship? + // Note there is no such thing as nullable to-many relationships (Just use + // an empty array) + + public static var sample: Relationships { + return Relationships(toOne: .init(id: .init(rawValue: "1")), + optionalTooOne: nil, + nullableToOne: .init(id: nil), + nullableOptionalToOne: nil, + toMany: .init(ids: [.init(rawValue: "1")]), + optionalToMany: nil) + } + } + } + + typealias TestType3 = BasicEntity } diff --git a/Tests/JSONAPIOpenAPITests/XCTestManifests.swift b/Tests/JSONAPIOpenAPITests/XCTestManifests.swift index b05e1af..a2dcf48 100644 --- a/Tests/JSONAPIOpenAPITests/XCTestManifests.swift +++ b/Tests/JSONAPIOpenAPITests/XCTestManifests.swift @@ -32,8 +32,10 @@ extension JSONAPIAttributeOpenAPITests { extension JSONAPIEntityOpenAPITests { static let __allTests = [ + ("test_AttributesAndRelationshipsEntity", test_AttributesAndRelationshipsEntity), ("test_AttributesEntity", test_AttributesEntity), ("test_EmptyEntity", test_EmptyEntity), + ("test_RelationshipsEntity", test_RelationshipsEntity), ] }