Skip to content

Commit

Permalink
Added some more tests and niceties to GraphQL.Content
Browse files Browse the repository at this point in the history
  • Loading branch information
aaronsky committed May 28, 2020
1 parent e679c00 commit 321115c
Show file tree
Hide file tree
Showing 6 changed files with 196 additions and 51 deletions.
45 changes: 30 additions & 15 deletions Sources/Buildkite/Resources/GraphQL.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,32 +22,29 @@ public struct GraphQL<T: Decodable>: Resource, HasResponseBody, HasRequestBody {

// Making a design decision here to forbid supplying data and errors
// simultaneously. The GraphQL spec permits it but multiple implementations,
// seemingly including Buildkite's, choose not to.
public enum Content: Error, Decodable {
// including Buildkite's, choose not to.
public enum Content: Decodable {
case data(T)
case errors([Error], type: String?)

case errors(Errors)
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if let errors = try container.decodeIfPresent([Error].self, forKey: .errors) {
let type = try container.decodeIfPresent(String.self, forKey: .type)
self = .errors(errors, type: type)
self = .errors(Errors(errors: errors, type: type))
} else if let data = try container.decodeIfPresent(T.self, forKey: .data) {
self = .data(data)
} else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "The GraphQL response does not contain either errors or data. One is required. If errors are present, they will be considered instead of any data that may have also been sent."))
}
}

public struct Error: Swift.Error, Equatable, Hashable, Decodable {
public var message: String
public var locations: [Location]?
public var path: [String]?
public var extensions: JSONValue?

public struct Location: Equatable, Hashable, Decodable {
public var line: Int
public var column: Int

public func get() throws -> T {
switch self {
case let .data(data):
return data
case let .errors(errors):
throw errors
}
}

Expand All @@ -73,7 +70,25 @@ public struct GraphQL<T: Decodable>: Resource, HasResponseBody, HasRequestBody {
public func transformRequest(_ request: inout URLRequest) {
request.httpMethod = "POST"
}

public struct Errors: Swift.Error, Equatable, Hashable {
var errors: [Error]
var type: String?
}

public struct Error: Swift.Error, Equatable, Hashable, Decodable {
public var message: String
public var locations: [Location]?
public var path: [String]?
public var extensions: JSONValue?

public struct Location: Equatable, Hashable, Decodable {
public var line: Int
public var column: Int
}
}
}

extension GraphQL.Content: Equatable where T: Equatable {}
extension GraphQL.Content: Hashable where T: Hashable {}

Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// BuildkiteTests.swift
// BuildkiteClientTests.swift
// Buildkite
//
// Created by Aaron Sky on 3/22/20.
Expand All @@ -18,26 +18,27 @@ import FoundationNetworking
import Combine
#endif

class BuildkiteTests: XCTestCase {
class BuildkiteClientTests: XCTestCase {
private struct TestData {
enum Case {
case success
case successPaginated
case successNoContent
case successHasBody
case successHasBodyAndContent
case successHasBodyPaginated
case badResponse
case unsuccessfulResponse
case noData
}

var configuration = Configuration.default
var client: BuildkiteClient
var resources = MockResources()

init(testCase: Case = .success) throws {
let responses: [(Data, URLResponse)]

switch testCase {
case .success:
responses = [try MockData.mockingSuccess(with: resources.content, url: configuration.version.baseURL)]
Expand All @@ -47,6 +48,8 @@ class BuildkiteTests: XCTestCase {
responses = [MockData.mockingSuccessNoContent(url: configuration.version.baseURL)]
case .successHasBody:
responses = [MockData.mockingSuccessNoContent(url: configuration.version.baseURL)]
case .successHasBodyAndContent:
responses = [try MockData.mockingSuccess(with: resources.bodyAndContent, url: configuration.version.baseURL)]
case .successHasBodyPaginated:
responses = [try MockData.mockingSuccess(with: resources.bodyAndPaginatedContent, url: configuration.version.baseURL)]
case .badResponse:
Expand All @@ -56,19 +59,39 @@ class BuildkiteTests: XCTestCase {
case .noData:
responses = []
}

client = BuildkiteClient(configuration: configuration,
transport: MockTransport(responses: responses))
transport: MockTransport(responses: responses))
client.token = "a valid token, i guess"
XCTAssertEqual(client.token, client.configuration.token)
}
}

func testResourceWithIncompatibleAPIVersion() throws {
let testData = try TestData(testCase: .success)
let resource = MockResources.IsAPIIncompatible()
let expectation = XCTestExpectation()
testData.client.send(resource) { result in
do {
_ = try result.get()
XCTFail("Expected to have failed with an error, but closure fulfilled normally")
} catch ResourceError.incompatibleVersion(resource.version) {

} catch {
XCTFail("Expected to have failed with an error, but not this one: \(error)")
}
expectation.fulfill()
}
wait(for: [expectation])
}
}

// MARK: Closure-based Requests

extension BuildkiteTests {
extension BuildkiteClientTests {
func testClosureBasedRequest() throws {
let testData = try TestData(testCase: .success)

let expectation = XCTestExpectation()
testData.client.send(testData.resources.contentResource) { result in
do {
Expand All @@ -81,10 +104,10 @@ extension BuildkiteTests {
}
wait(for: [expectation])
}

func testClosureBasedRequestWithPagination() throws {
let testData = try TestData(testCase: .success)

let expectation = XCTestExpectation()
testData.client.send(testData.resources.paginatedContentResource, pageOptions: PageOptions(page: 1, perPage: 30)) { result in
do {
Expand All @@ -98,30 +121,46 @@ extension BuildkiteTests {
}
wait(for: [expectation])
}

func testClosureBasedRequestNoContent() throws {
let testData = try TestData(testCase: .successNoContent)

let expectation = XCTestExpectation()
testData.client.send(testData.resources.noContentNoBodyResource) { _ in
expectation.fulfill()
}
wait(for: [expectation])
}

func testClosureBasedRequestHasBody() throws {
let testData = try TestData(testCase: .successHasBody)

let expectation = XCTestExpectation()
testData.client.send(testData.resources.bodyResource) { _ in
expectation.fulfill()
}
wait(for: [expectation])
}


func testClosureBasedRequestHasBodyWithContent() throws {
let testData = try TestData(testCase: .successHasBodyAndContent)

let expectation = XCTestExpectation()
testData.client.send(testData.resources.bodyAndContentResource) { result in
do {
let response = try result.get()
XCTAssertEqual(testData.resources.bodyAndContent, response.content)
} catch {
XCTFail(error.localizedDescription)
}
expectation.fulfill()
}
wait(for: [expectation])
}

func testClosureBasedRequestHasBodyWithPagination() throws {
let testData = try TestData(testCase: .successHasBodyPaginated)

let expectation = XCTestExpectation()
testData.client.send(testData.resources.bodyAndPaginatedResource, pageOptions: PageOptions(page: 1, perPage: 30)) { result in
do {
Expand All @@ -135,7 +174,7 @@ extension BuildkiteTests {
}
wait(for: [expectation])
}

func testClosureBasedRequestInvalidResponse() throws {
let testData = try TestData(testCase: .badResponse)
let expectation = XCTestExpectation()
Expand All @@ -149,7 +188,7 @@ extension BuildkiteTests {
}
wait(for: [expectation])
}

func testClosureBasedRequestUnsuccessfulResponse() throws {
let testData = try TestData(testCase: .unsuccessfulResponse)
let expectation = XCTestExpectation()
Expand All @@ -164,7 +203,7 @@ extension BuildkiteTests {
}
wait(for: [expectation])
}

func testFailureFromTransport() throws {
let testData = try TestData(testCase: .noData)
let expectation = XCTestExpectation()
Expand All @@ -185,7 +224,7 @@ extension BuildkiteTests {

#if canImport(Combine)
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
extension BuildkiteTests {
extension BuildkiteClientTests {
func testPublisherBasedRequest() throws {
let testData = try TestData(testCase: .success)
let expectation = XCTestExpectation()
Expand All @@ -201,7 +240,7 @@ extension BuildkiteTests {
.store(in: &cancellables)
wait(for: [expectation])
}

func testPublisherBasedRequestWithPagination() throws {
let testData = try TestData(testCase: .success)
let expectation = XCTestExpectation()
Expand All @@ -220,7 +259,7 @@ extension BuildkiteTests {
.store(in: &cancellables)
wait(for: [expectation])
}

func testPublisherBasedRequestNoContent() throws {
let testData = try TestData(testCase: .success)
let expectation = XCTestExpectation()
Expand All @@ -235,7 +274,7 @@ extension BuildkiteTests {
.store(in: &cancellables)
wait(for: [expectation])
}

func testPublisherBasedRequestHasBody() throws {
let testData = try TestData(testCase: .successHasBody)
let expectation = XCTestExpectation()
Expand All @@ -250,7 +289,25 @@ extension BuildkiteTests {
.store(in: &cancellables)
wait(for: [expectation])
}


func testPublisherBasedRequestHasBodyWithContent() throws {
let testData = try TestData(testCase: .successHasBodyAndContent)
let expectation = XCTestExpectation()
var cancellables: Set<AnyCancellable> = []
testData.client.sendPublisher(testData.resources.bodyAndContentResource)
.sink(receiveCompletion: {
if case let .failure(error) = $0 {
XCTFail(error.localizedDescription)
}
expectation.fulfill()
},
receiveValue: {
XCTAssertEqual(testData.resources.bodyAndContent, $0.content)
})
.store(in: &cancellables)
wait(for: [expectation])
}

func testPublisherBasedRequestHasBodyWithPagination() throws {
let testData = try TestData(testCase: .successHasBodyPaginated)
let expectation = XCTestExpectation()
Expand All @@ -269,7 +326,7 @@ extension BuildkiteTests {
.store(in: &cancellables)
wait(for: [expectation])
}

func testPublisherBasedRequestInvalidResponse() throws {
let testData = try TestData(testCase: .badResponse)
let expectation = XCTestExpectation()
Expand All @@ -284,7 +341,7 @@ extension BuildkiteTests {
.store(in: &cancellables)
wait(for: [expectation])
}

func testPublisherBasedRequestUnsuccessfulResponse() throws {
let testData = try TestData(testCase: .unsuccessfulResponse)
let expectation = XCTestExpectation()
Expand Down
4 changes: 2 additions & 2 deletions Tests/BuildkiteTests/Resources/BuildsTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ class BuildsTests: XCTestCase {

let expectation = XCTestExpectation()

context.client.send(Build.Resources.ListForPipeline(organization: "buildkite", pipeline: "my-pipeline")) { result in
context.client.send(Build.Resources.ListForPipeline(organization: "buildkite", pipeline: "my-pipeline", queryOptions: Build.Resources.QueryOptions())) { result in
do {
let response = try result.get()
XCTAssertEqual(expected, response.content)
Expand Down Expand Up @@ -150,7 +150,7 @@ class BuildsTests: XCTestCase {

let body = Build.Resources.Create.Body(commit: "HEAD",
branch: "master",
author: nil,
author: Build.Resources.Create.Body.Author(name: "", email: ""),
cleanCheckout: nil,
env: nil,
ignorePipelineBranchFilters: nil,
Expand Down
14 changes: 10 additions & 4 deletions Tests/BuildkiteTests/Resources/GraphQLTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class GraphQLTests: XCTestCase {
}

func testGraphQLErrors() throws {
let expectedErrors: [GraphQL<JSONValue>.Content.Error] = [
let expectedErrors: GraphQL<JSONValue>.Errors = .init(errors: [
.init(message: "Field 'id' doesn't exist on type 'Query'",
locations: [.init(line: 2, column: 3)],
path: ["query SimpleQuery", "id"],
Expand All @@ -44,10 +44,11 @@ class GraphQLTests: XCTestCase {
"typeName": "Query",
"fieldName": "id"
])
]
let expected: GraphQL<JSONValue>.Content = .errors(expectedErrors, type: nil)
], type: nil)

let expected: GraphQL<JSONValue>.Content = .errors(expectedErrors)
let content: JSONValue = [
"errors": .array(expectedErrors.map { error in
"errors": .array(expectedErrors.errors.map { error in
let messageJSON: JSONValue = .string(error.message)
let locationsJSON: JSONValue
if let locations = error.locations {
Expand Down Expand Up @@ -99,4 +100,9 @@ class GraphQLTests: XCTestCase {
}
wait(for: [expectation])
}

func testGraphQLContentGet() throws {
try XCTAssertNoThrow(GraphQL.Content.data("hello").get())
try XCTAssertThrowsError(GraphQL<String>.Content.errors(.init(errors: [], type: nil)).get())
}
}
Loading

0 comments on commit 321115c

Please sign in to comment.