Added support for Buildkite's JSON errors, and wrote more tests for pagination

This commit is contained in:
Aaron Sky 2020-05-08 10:06:46 -04:00
parent 2865672821
commit 9481c26290
11 changed files with 312 additions and 115 deletions

View File

@ -6,4 +6,4 @@ Redistribution and use in source and binary forms, with or without modification,
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -49,7 +49,7 @@ public final class Buildkite {
transport.send(request: request, completion: handleContentfulResponse(completion: completion))
}
public func send<R: Resource & HasResponseBody & Paginated>(_ resource: R, pageOptions: PageOptions? = nil, completion: @escaping (Result<Response<R.Content>, Error>) -> Void) {
public func send<R: Resource & Paginated>(_ resource: R, pageOptions: PageOptions? = nil, completion: @escaping (Result<Response<R.Content>, Error>) -> Void) {
let request = URLRequest(resource, configuration: configuration, pageOptions: pageOptions)
transport.send(request: request, completion: handleContentfulResponse(completion: completion))
}
@ -65,7 +65,7 @@ public final class Buildkite {
transport.send(request: request, completion: handleContentfulResponse(completion: completion))
}
public func send<R: Resource & HasRequestBody & HasResponseBody & Paginated>(_ resource: R, pageOptions: PageOptions? = nil, completion: @escaping (Result<Response<R.Content>, Error>) -> Void) {
public func send<R: Resource & HasRequestBody & Paginated>(_ resource: R, pageOptions: PageOptions? = nil, completion: @escaping (Result<Response<R.Content>, Error>) -> Void) {
let request: URLRequest
do {
request = try URLRequest(resource, configuration: configuration, encoder: encoder, pageOptions: pageOptions)
@ -92,17 +92,6 @@ public final class Buildkite {
transport.send(request: request, completion: handleEmptyResponse(completion: completion))
}
public func send<R: Resource & HasRequestBody & Paginated>(_ resource: R, pageOptions: PageOptions? = nil, completion: @escaping (Result<Response<Void>, Error>) -> Void) {
let request: URLRequest
do {
request = try URLRequest(resource, configuration: configuration, encoder: encoder, pageOptions: pageOptions)
} catch {
completion(.failure(error))
return
}
transport.send(request: request, completion: handleEmptyResponse(completion: completion))
}
private func handleContentfulResponse<Content: Decodable>(completion: @escaping (Result<Response<Content>, Error>) -> Void) -> Transport.Completion {
return { [weak self] result in
guard let self = self else {
@ -113,7 +102,7 @@ public final class Buildkite {
do {
let data: Data
(data, response) = try result.get()
try self.checkResponseForIssues(response)
try self.checkResponseForIssues(response, data: data)
content = try self.decoder.decode(Content.self, from: data)
} catch {
completion(.failure(error))
@ -140,13 +129,18 @@ public final class Buildkite {
}
}
private func checkResponseForIssues(_ response: URLResponse) throws {
private func checkResponseForIssues(_ response: URLResponse, data: Data? = nil) throws {
guard let response = response as? HTTPURLResponse,
let statusCode = StatusCode(rawValue: response.statusCode) else {
throw ResponseError.missingResponse
}
if !statusCode.isSuccess {
throw statusCode
if let data = data,
let errorIntermediary = try? decoder.decode(BuildkiteError.Intermediary.self, from: data) {
throw BuildkiteError(statusCode: statusCode, intermediary: errorIntermediary)
} else {
throw statusCode
}
}
}
}
@ -159,7 +153,7 @@ extension Buildkite {
public func sendPublisher<R: Resource & HasResponseBody>(_ resource: R) -> AnyPublisher<Response<R.Content>, Error> {
transport.sendPublisher(request: URLRequest(resource, configuration: configuration))
.tryMap {
try self.checkResponseForIssues($0.response)
try self.checkResponseForIssues($0.response, data: $0.data)
let content = try self.decoder.decode(R.Content.self, from: $0.data)
return Response(content: content, response: $0.response)
}
@ -169,7 +163,7 @@ extension Buildkite {
public func sendPublisher<R: Resource & HasResponseBody & Paginated>(_ resource: R, pageOptions: PageOptions? = nil) -> AnyPublisher<Response<R.Content>, Error> {
transport.sendPublisher(request: URLRequest(resource, configuration: configuration, pageOptions: pageOptions))
.tryMap {
try self.checkResponseForIssues($0.response)
try self.checkResponseForIssues($0.response, data: $0.data)
let content = try self.decoder.decode(R.Content.self, from: $0.data)
return Response(content: content, response: $0.response)
}
@ -181,19 +175,19 @@ extension Buildkite {
.publisher
.flatMap(transport.sendPublisher)
.tryMap {
try self.checkResponseForIssues($0.response)
try self.checkResponseForIssues($0.response, data: $0.data)
let content = try self.decoder.decode(R.Content.self, from: $0.data)
return Response(content: content, response: $0.response)
}
.eraseToAnyPublisher()
}
public func sendPublisher<R: Resource & HasRequestBody & HasResponseBody & Paginated>(_ resource: R, pageOptions: PageOptions? = nil) -> AnyPublisher<Response<R.Content>, Error> {
public func sendPublisher<R: Resource & HasRequestBody & Paginated>(_ resource: R, pageOptions: PageOptions? = nil) -> AnyPublisher<Response<R.Content>, Error> {
Result { try URLRequest(resource, configuration: configuration, encoder: encoder, pageOptions: pageOptions) }
.publisher
.flatMap(transport.sendPublisher)
.tryMap {
try self.checkResponseForIssues($0.response)
try self.checkResponseForIssues($0.response, data: $0.data)
let content = try self.decoder.decode(R.Content.self, from: $0.data)
return Response(content: content, response: $0.response)
}
@ -219,16 +213,5 @@ extension Buildkite {
}
.eraseToAnyPublisher()
}
func sendPublisher<R: Resource & HasRequestBody & Paginated>(_ resource: R, pageOptions: PageOptions? = nil) -> AnyPublisher<Response<Void>, Error> {
Result { try URLRequest(resource, configuration: configuration, encoder: encoder, pageOptions: pageOptions) }
.publisher
.flatMap(transport.sendPublisher)
.tryMap {
try self.checkResponseForIssues($0.response)
return Response(content: (), response: $0.response)
}
.eraseToAnyPublisher()
}
}
#endif

View File

@ -127,7 +127,7 @@ public enum Job: Codable, Equatable {
}
}
public struct LogOutput: Codable {
public struct LogOutput: Codable, Equatable {
public var url: URL // Resource<Job.Resources.GetLogOutput>
public var content: String
public var size: Int

View File

@ -17,6 +17,23 @@ enum ResponseError: Error {
case unexpectedlyNoContent
}
public struct BuildkiteError: Error {
public var statusCode: StatusCode
public var message: String
public var errors: [String]
init(statusCode: StatusCode, intermediary: Intermediary) {
self.statusCode = statusCode
self.message = intermediary.message ?? ""
self.errors = intermediary.errors ?? []
}
struct Intermediary: Codable {
var message: String?
var errors: [String]?
}
}
public struct Response<T> {
public let content: T
public let response: URLResponse

View File

@ -12,7 +12,7 @@ import Foundation
import FoundationNetworking
#endif
public enum StatusCode: Int, Error {
public enum StatusCode: Int, Error, Codable {
/// The request was successfully processed by Buildkite.
case ok = 200
@ -22,6 +22,9 @@ public enum StatusCode: Int, Error {
/// The request has been accepted, but not yet processed.
case accepted = 202
/// The request was found
case found = 302
/// The response to the request can be found under a different URL in the Location header and can be retrieved using a GET method on that resource.
case seeOther = 303
@ -49,8 +52,6 @@ public enum StatusCode: Int, Error {
case unprocessableEntity = 422
/// The requested shop is currently locked. Shops are locked if they repeatedly exceed their API request limit, or if there is an issue with the account, such as a detected compromise or fraud risk.
///
/// Contact support if your shop is locked.
case locked = 423
/// The request was not accepted because the application has exceeded the rate limit. See the API Call Limit documentation for a breakdown of Buildkite's rate-limiting mechanism.
@ -72,5 +73,6 @@ public enum StatusCode: Int, Error {
self == .ok
|| self == .created
|| self == .accepted
|| self == .found
}
}

View File

@ -30,11 +30,11 @@ extension Job.Resources {
public var build: Int
/// job ID
public var job: UUID
public var path: String {
"organizations/\(organization)/pipelines/\(pipeline)/builds/\(build)/jobs/\(job)/retry"
}
public func transformRequest(_ request: inout URLRequest) {
request.httpMethod = "PUT"
}
@ -46,7 +46,7 @@ extension Job.Resources {
self.job = job
}
}
/// Unblock a job
///
/// Unblocks a builds "Block pipeline" job. The jobs `unblockable` property indicates whether it is able to be unblocked, and the `unblock_url` property points to this endpoint.
@ -62,7 +62,7 @@ extension Job.Resources {
public var job: UUID
/// body of the request
public var body: Body
public struct Body: Codable {
public var unblocker: UUID?
public var fields: [String: String]
@ -72,7 +72,7 @@ extension Job.Resources {
self.fields = fields
}
}
public var path: String {
"organizations/\(organization)/pipelines/\(pipeline)/builds/\(build)/jobs/\(job)/unblock"
}
@ -84,15 +84,15 @@ extension Job.Resources {
self.job = job
self.body = body
}
public func transformRequest(_ request: inout URLRequest) {
request.httpMethod = "PUT"
}
}
/// Get a jobs log output
public struct LogOutput: Resource {
typealias Content = Job.LogOutput
public struct LogOutput: Resource, HasResponseBody {
public typealias Content = Job.LogOutput
/// organization slug
public var organization: String
/// pipeline slug
@ -101,7 +101,7 @@ extension Job.Resources {
public var build: Int
/// job ID
public var job: UUID
public var path: String {
"organizations/\(organization)/pipelines/\(pipeline)/builds/\(build)/jobs/\(job)/log"
}
@ -113,7 +113,7 @@ extension Job.Resources {
self.job = job
}
}
/// Delete a jobs log output
public struct DeleteLogOutput: Resource {
public typealias Content = Void
@ -125,7 +125,7 @@ extension Job.Resources {
public var build: Int
/// job ID
public var job: UUID
public var path: String {
"organizations/\(organization)/pipelines/\(pipeline)/builds/\(build)/jobs/\(job)/log"
}
@ -136,12 +136,12 @@ extension Job.Resources {
self.build = build
self.job = job
}
public func transformRequest(_ request: inout URLRequest) {
request.httpMethod = "DELETE"
}
}
/// Get a job's environment variables
public struct EnvironmentVariables: Resource, HasResponseBody {
public typealias Content = Job.EnvironmentVariables
@ -153,7 +153,7 @@ extension Job.Resources {
public var build: Int
/// job ID
public var job: UUID
public var path: String {
"organizations/\(organization)/pipelines/\(pipeline)/builds/\(build)/jobs/\(job)/env"
}
@ -166,3 +166,36 @@ extension Job.Resources {
}
}
}
extension Job.Resources.LogOutput {
public struct Alternative: Resource, HasResponseBody {
public enum Format: String {
case html
case plainText = "txt"
}
public typealias Content = String
/// organization slug
public var organization: String
/// pipeline slug
public var pipeline: String
/// build number
public var build: Int
/// job ID
public var job: UUID
public var format: Format
public var path: String {
"organizations/\(organization)/pipelines/\(pipeline)/builds/\(build)/jobs/\(job)/log.\(format)"
}
public init(organization: String, pipeline: String, build: Int, job: UUID, format: Format) {
self.organization = organization
self.pipeline = pipeline
self.build = build
self.job = job
self.format = format
}
}
}

View File

@ -21,7 +21,7 @@ extension Team.Resources {
public typealias Content = [Team]
/// organization slug
public var organization: String
/// Filters the results to teams that have the given user as a member.
public var userId: UUID?
public var path: String {

View File

@ -22,24 +22,33 @@ class BuildkiteTests: XCTestCase {
private struct TestData {
enum Case {
case success
case successPaginated
case successNoContent
case successHasBody
case successHasBodyPaginated
case badResponse
case unsuccessfulResponse
case noData
}
var configuration = Configuration.default
var client: Buildkite
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.baseURL)]
case .successPaginated:
responses = [try MockData.mockingSuccess(with: resources.paginatedContent, url: configuration.baseURL)]
case .successNoContent:
responses = [MockData.mockingSuccessNoContent(url: configuration.baseURL)]
case .successHasBody:
responses = [MockData.mockingSuccessNoContent(url: configuration.baseURL)]
case .successHasBodyPaginated:
responses = [try MockData.mockingSuccess(with: resources.bodyAndPaginatedContent, url: configuration.baseURL)]
case .badResponse:
responses = [MockData.mockingIncompatibleResponse(for: configuration.baseURL)]
case .unsuccessfulResponse:
@ -47,7 +56,7 @@ class BuildkiteTests: XCTestCase {
case .noData:
responses = []
}
client = Buildkite(configuration: configuration,
transport: MockTransport(responses: responses))
}
@ -59,7 +68,7 @@ class BuildkiteTests: XCTestCase {
extension BuildkiteTests {
func testClosureBasedRequest() throws {
let testData = try TestData(testCase: .success)
let expectation = XCTestExpectation()
testData.client.send(testData.resources.contentResource) { result in
do {
@ -72,17 +81,61 @@ extension BuildkiteTests {
}
wait(for: [expectation])
}
func testClosureBasedRequestNoContent() throws {
let testData = try TestData(testCase: .successNoContent)
func testClosureBasedRequestWithPagination() throws {
let testData = try TestData(testCase: .success)
let expectation = XCTestExpectation()
testData.client.send(testData.resources.noContentResource) { _ in
testData.client.send(testData.resources.paginatedContentResource, pageOptions: PageOptions(page: 1, perPage: 30)) { result in
do {
let response = try result.get()
XCTAssertEqual(testData.resources.paginatedContent, response.content)
XCTAssertNotNil(response.page)
} catch {
XCTFail(error.localizedDescription)
}
expectation.fulfill()
}
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 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 {
let response = try result.get()
XCTAssertEqual(testData.resources.bodyAndPaginatedContent, response.content)
XCTAssertNotNil(response.page)
} catch {
XCTFail(error.localizedDescription)
}
expectation.fulfill()
}
wait(for: [expectation])
}
func testClosureBasedRequestInvalidResponse() throws {
let testData = try TestData(testCase: .badResponse)
let expectation = XCTestExpectation()
@ -96,7 +149,7 @@ extension BuildkiteTests {
}
wait(for: [expectation])
}
func testClosureBasedRequestUnsuccessfulResponse() throws {
let testData = try TestData(testCase: .unsuccessfulResponse)
let expectation = XCTestExpectation()
@ -111,7 +164,7 @@ extension BuildkiteTests {
}
wait(for: [expectation])
}
func testFailureFromTransport() throws {
let testData = try TestData(testCase: .noData)
let expectation = XCTestExpectation()
@ -148,12 +201,31 @@ extension BuildkiteTests {
.store(in: &cancellables)
wait(for: [expectation])
}
func testPublisherBasedRequestWithPagination() throws {
let testData = try TestData(testCase: .success)
let expectation = XCTestExpectation()
var cancellables: Set<AnyCancellable> = []
testData.client.sendPublisher(testData.resources.paginatedContentResource, pageOptions: PageOptions(page: 1, perPage: 30))
.sink(receiveCompletion: {
if case let .failure(error) = $0 {
XCTFail(error.localizedDescription)
}
expectation.fulfill()
},
receiveValue: {
XCTAssertEqual(testData.resources.paginatedContent, $0.content)
XCTAssertNotNil($0.page)
})
.store(in: &cancellables)
wait(for: [expectation])
}
func testPublisherBasedRequestNoContent() throws {
let testData = try TestData(testCase: .success)
let expectation = XCTestExpectation()
var cancellables: Set<AnyCancellable> = []
testData.client.sendPublisher(testData.resources.noContentResource)
testData.client.sendPublisher(testData.resources.noContentNoBodyResource)
.sink(receiveCompletion: {
if case let .failure(error) = $0 {
XCTFail(error.localizedDescription)
@ -163,7 +235,41 @@ extension BuildkiteTests {
.store(in: &cancellables)
wait(for: [expectation])
}
func testPublisherBasedRequestHasBody() throws {
let testData = try TestData(testCase: .successHasBody)
let expectation = XCTestExpectation()
var cancellables: Set<AnyCancellable> = []
testData.client.sendPublisher(testData.resources.bodyResource)
.sink(receiveCompletion: {
if case let .failure(error) = $0 {
XCTFail(error.localizedDescription)
}
expectation.fulfill()
}, receiveValue: { _ in })
.store(in: &cancellables)
wait(for: [expectation])
}
func testPublisherBasedRequestHasBodyWithPagination() throws {
let testData = try TestData(testCase: .successHasBodyPaginated)
let expectation = XCTestExpectation()
var cancellables: Set<AnyCancellable> = []
testData.client.sendPublisher(testData.resources.bodyAndPaginatedResource, pageOptions: PageOptions(page: 1, perPage: 30))
.sink(receiveCompletion: {
if case let .failure(error) = $0 {
XCTFail(error.localizedDescription)
}
expectation.fulfill()
},
receiveValue: {
XCTAssertEqual(testData.resources.bodyAndPaginatedContent, $0.content)
XCTAssertNotNil($0.page)
})
.store(in: &cancellables)
wait(for: [expectation])
}
func testPublisherBasedRequestInvalidResponse() throws {
let testData = try TestData(testCase: .badResponse)
let expectation = XCTestExpectation()
@ -178,7 +284,7 @@ extension BuildkiteTests {
.store(in: &cancellables)
wait(for: [expectation])
}
func testPublisherBasedRequestUnsuccessfulResponse() throws {
let testData = try TestData(testCase: .unsuccessfulResponse)
let expectation = XCTestExpectation()

View File

@ -1,35 +0,0 @@
//
// PaginationTests.swift
// Buildkite
//
// Created by Aaron Sky on 5/7/20.
// Copyright © 2020 Aaron Sky. All rights reserved.
//
import Foundation
import XCTest
@testable import Buildkite
#if canImport(FoundationNetworking)
import FoundationNetworking
#endif
class PaginationTests: XCTestCase {
func testPagination() throws {
let expected = [Pipeline(), Pipeline()]
let context = try MockContext(content: expected)
let expectation = XCTestExpectation()
context.client.send(Pipeline.Resources.List(organization: "buildkite"), pageOptions: PageOptions(page: 1, perPage: 30)) { result in
do {
let response = try result.get()
XCTAssertEqual(expected, response.content)
} catch {
XCTFail(error.localizedDescription)
}
expectation.fulfill()
}
wait(for: [expectation])
}
}

View File

@ -55,13 +55,51 @@ class JobsTests: XCTestCase {
}
func testJobsLogOutput() throws {
let context = MockContext()
let expected = Job.LogOutput()
let context = try MockContext(content: expected)
let expectation = XCTestExpectation()
context.client.send(Job.Resources.LogOutput(organization: "buildkite", pipeline: "my-pipeline", build: 1, job: UUID())) { result in
do {
_ = try result.get()
let response = try result.get()
XCTAssertEqual(expected, response.content)
} catch {
XCTFail(error.localizedDescription)
}
expectation.fulfill()
}
wait(for: [expectation])
}
func testJobsLogOutputAlternativePlainText() throws {
let expected = "hello friends"
let context = try MockContext(content: expected)
let expectation = XCTestExpectation()
context.client.send(Job.Resources.LogOutput.Alternative(organization: "buildkite", pipeline: "my-pipeline", build: 1, job: UUID(), format: .plainText)) { result in
do {
let response = try result.get()
XCTAssertEqual(expected, response.content)
} catch {
XCTFail(error.localizedDescription)
}
expectation.fulfill()
}
wait(for: [expectation])
}
func testJobsLogOutputAlternativeHTML() throws {
let expected = "hello friends"
let context = try MockContext(content: expected)
let expectation = XCTestExpectation()
context.client.send(Job.Resources.LogOutput.Alternative(organization: "buildkite", pipeline: "my-pipeline", build: 1, job: UUID(), format: .html)) { result in
do {
let response = try result.get()
XCTAssertEqual(expected, response.content)
} catch {
XCTFail(error.localizedDescription)
}
@ -104,3 +142,12 @@ class JobsTests: XCTestCase {
wait(for: [expectation])
}
}
extension Job.LogOutput {
init() {
self.init(url: URL(),
content: "hello friends",
size: 13,
headerTimes: [])
}
}

View File

@ -14,6 +14,13 @@ import FoundationNetworking
#endif
struct MockResources {
struct NoContentNoBody: Resource {
typealias Content = Void
let path = "mock"
}
var noContentNoBodyResource = NoContentNoBody()
struct HasContent: Resource, HasResponseBody {
struct Content: Codable, Equatable {
var name: String
@ -21,16 +28,53 @@ struct MockResources {
}
let path = "mock"
}
var contentResource = HasContent()
var content = HasContent.Content(name: "Jeff", age: 35)
struct NoContent: Resource {
typealias Content = Void
struct HasPaginatedContent: Resource, Paginated {
struct Content: Codable, Equatable {
var name: String
var age: Int
}
let path = "mock"
}
var noContentResource = NoContent()
var paginatedContentResource = HasPaginatedContent()
var paginatedContent = HasPaginatedContent.Content(name: "Jeff", age: 35)
struct HasBody: Resource, HasRequestBody {
struct Body: Codable, Equatable {
var name: String
var age: Int
}
var body: Body
let path = "mock"
}
var bodyResource = HasBody(body: HasBody.Body(name: "Jeff", age: 35))
struct HasBodyAndPaginated: Resource, HasRequestBody, Paginated {
struct Body: Codable, Equatable {
var name: String
var age: Int
}
struct Content: Codable, Equatable {
var name: String
var age: Int
}
var body: Body
let path = "mock"
}
var bodyAndPaginatedResource = HasBodyAndPaginated(body: HasBodyAndPaginated.Body(name: "Jeff", age: 35))
var bodyAndPaginatedContent = HasBodyAndPaginated.Content(name: "Jeff", age: 35)
}
enum MockData {
@ -57,7 +101,7 @@ extension MockData {
}
static func mockingUnsuccessfulResponse(for url: URL) -> (Data, URLResponse) {
return (Data(), urlResponse(for: url, status: .notFound))
return (#"{"message":"not found","errors": ["go away"]}"#.data(using: .utf8)!, urlResponse(for: url, status: .notFound))
}
static func mockingSuccessNoContent(for request: URLRequest) throws -> (Data, URLResponse) {
@ -76,7 +120,7 @@ extension MockData {
HTTPURLResponse(url: url,
statusCode: status,
httpVersion: "HTTP/1.1",
headerFields: [:])!
headerFields: ["Link":#"<https://api.buildkite.com/v2/organizations/my-great-org/pipelines/my-pipeline/builds?api_key=f8582f070276d764ce3dd4c6d57be92574dccf86&page=3>; rel="prev",<https://api.buildkite.com/v2/organizations/my-great-org/pipelines/my-pipeline/builds?api_key=f8582f070276d764ce3dd4c6d57be92574dccf86&page=5>; rel="next",<https://api.buildkite.com/v2/organizations/my-great-org/pipelines/my-pipeline/builds?api_key=f8582f070276d764ce3dd4c6d57be92574dccf86&page=1>; rel="first", <https://api.buildkite.com/v2/organizations/my-great-org/pipelines/my-pipeline/builds?api_key=f8582f070276d764ce3dd4c6d57be92574dccf86&page=10>; rel="last""#])!
}
}