Added support for Buildkite's JSON errors, and wrote more tests for pagination
This commit is contained in:
parent
2865672821
commit
9481c26290
2
LICENSE
2
LICENSE
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 build’s "Block pipeline" job. The job’s `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 job’s 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 job’s 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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])
|
||||
}
|
||||
}
|
|
@ -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: [])
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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""#])!
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue