Initial commit

This commit is contained in:
Aaron Sky 2020-05-05 12:44:23 -04:00
commit 880af3d578
58 changed files with 4015 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
.DS_Store
/.build
/Packages
/*.xcodeproj
xcuserdata/

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "container:../..">
</FileRef>
</Workspace>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@ -0,0 +1,116 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1140"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "Buildkite"
BuildableName = "Buildkite"
BlueprintName = "Buildkite"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "NO"
buildForProfiling = "NO"
buildForArchiving = "NO"
buildForAnalyzing = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "BuildkiteTests"
BuildableName = "BuildkiteTests"
BlueprintName = "BuildkiteTests"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "NO"
buildForProfiling = "NO"
buildForArchiving = "NO"
buildForAnalyzing = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "BuildkiteIntegrationTests"
BuildableName = "BuildkiteIntegrationTests"
BlueprintName = "BuildkiteIntegrationTests"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
codeCoverageEnabled = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "BuildkiteTests"
BuildableName = "BuildkiteTests"
BlueprintName = "BuildkiteTests"
ReferencedContainer = "container:">
</BuildableReference>
</TestableReference>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "BuildkiteIntegrationTests"
BuildableName = "BuildkiteIntegrationTests"
BlueprintName = "BuildkiteIntegrationTests"
ReferencedContainer = "container:">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "Buildkite"
BuildableName = "Buildkite"
BlueprintName = "Buildkite"
ReferencedContainer = "container:">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

29
Example/Package.swift Normal file
View File

@ -0,0 +1,29 @@
// swift-tools-version:5.2
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "Example",
platforms: [
.iOS(.v13),
.macOS(.v10_15)
],
products: [
// Products define the executables and libraries produced by a package, and make them visible to other packages.
.executable(name: "Example",
targets: ["Example"])
],
dependencies: [
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
.package(path: "../"),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages which this package depends on.
.target(
name: "Example",
dependencies: ["Buildkite"]),
]
)

View File

@ -0,0 +1,27 @@
//
// main.swift
//
//
// Created by Aaron Sky on 5/5/20.
//
import Foundation
import Combine
import Buildkite
let client = Buildkite()
client.token = "..."
var cancellables: Set<AnyCancellable> = []
client.sendPublisher(Build.Resources.ListAll())
.map(\.content)
.sink(receiveCompletion: { result in
if case let .failure(error) = result {
print(error)
exit(1)
}
}) { agents in
print(agents)
exit(0)
}.store(in: &cancellables)
RunLoop.main.run()

32
Package.swift Normal file
View File

@ -0,0 +1,32 @@
// swift-tools-version:5.2
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "Buildkite",
platforms: [
.iOS(.v10),
.macOS(.v10_12)
],
products: [
// Products define the executables and libraries produced by a package, and make them visible to other packages.
.library(
name: "Buildkite",
targets: ["Buildkite"])
],
dependencies: [
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages which this package depends on.
.target(
name: "Buildkite",
dependencies: []),
.testTarget(
name: "BuildkiteTests",
dependencies: ["Buildkite"]),
]
)

3
README.md Normal file
View File

@ -0,0 +1,3 @@
# Buildkite
A description of this package.

View File

@ -0,0 +1,171 @@
//
// Buildkite.swift
//
//
// Created by Aaron Sky on 3/22/20.
//
import Foundation
#if canImport(FoundationNetworking)
import FoundationNetworking
#endif
public final class Buildkite {
let encoder: JSONEncoder = {
let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .custom(Formatters.encodeISO8601)
encoder.keyEncodingStrategy = .convertToSnakeCase
return encoder
}()
let decoder: JSONDecoder = {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .custom(Formatters.decodeISO8601)
decoder.keyDecodingStrategy = .convertFromSnakeCase
return decoder
}()
var configuration: Configuration
var transport: Transport
public var token: String? {
get {
configuration.token
}
set {
configuration.token = newValue
}
}
public init(configuration: Configuration = .default, transport: Transport = URLSession.shared) {
self.configuration = configuration
self.transport = transport
}
public func send<R: Resource & HasResponseBody>(_ resource: R, completion: @escaping (Result<Response<R.Content>, Error>) -> Void) {
let request = URLRequest(resource, configuration: configuration)
transport.send(request: request, completion: handleContentfulResponse(completion: completion))
}
public func send<R: Resource & HasRequestBody & HasResponseBody>(_ resource: R, completion: @escaping (Result<Response<R.Content>, Error>) -> Void) {
let request: URLRequest
do {
request = try URLRequest(resource, configuration: configuration, encoder: encoder)
} catch {
completion(.failure(error))
return
}
transport.send(request: request, completion: handleContentfulResponse(completion: completion))
}
public func send<R: Resource>(_ resource: R, completion: @escaping (Result<Response<Void>, Error>) -> Void) {
let request = URLRequest(resource, configuration: configuration)
transport.send(request: request, completion: handleEmptyResponse(completion: completion))
}
public func send<R: Resource & HasRequestBody>(_ resource: R, completion: @escaping (Result<Response<Void>, Error>) -> Void) {
let request: URLRequest
do {
request = try URLRequest(resource, configuration: configuration, encoder: encoder)
} 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) -> (Result<(data: Data, response: URLResponse), Error>) -> Void { { [weak self] result in
guard let self = self else {
return
}
let content: Content
let response: URLResponse
do {
let data: Data
(data, response) = try result.get()
try self.checkResponseForIssues(response)
content = try self.decoder.decode(Content.self, from: data)
} catch {
completion(.failure(error))
return
}
completion(.success(Response(content: content, response: response)))
}
}
private func handleEmptyResponse(completion: @escaping (Result<Response<Void>, Error>) -> Void) -> (Result<(data: Data, response: URLResponse), Error>) -> Void { { [weak self] result in
guard let self = self else {
return
}
var response: URLResponse
do {
(_, response) = try result.get()
try self.checkResponseForIssues(response)
} catch {
completion(.failure(error))
return
}
completion(.success(Response(content: (), response: response)))
}
}
private func checkResponseForIssues(_ response: URLResponse) throws {
guard let response = response as? HTTPURLResponse,
let statusCode = StatusCode(rawValue: response.statusCode) else {
throw ResponseError.missingResponse
}
if !statusCode.isSuccess {
throw statusCode
}
}
}
#if canImport(Combine)
import Combine
@available(iOS 13.0, macOS 10.15, *)
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)
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>(_ resource: R) -> AnyPublisher<Response<R.Content>, Error> {
Result { try URLRequest(resource, configuration: configuration, encoder: encoder) }
.publisher
.flatMap(transport.sendPublisher)
.tryMap {
try self.checkResponseForIssues($0.response)
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>(_ resource: R) -> AnyPublisher<Response<Void>, Error> {
transport.sendPublisher(request: URLRequest(resource, configuration: configuration))
.tryMap {
try self.checkResponseForIssues($0.response)
return Response(content: (), response: $0.response)
}
.eraseToAnyPublisher()
}
func sendPublisher<R: Resource & HasRequestBody>(_ resource: R) -> AnyPublisher<Response<Void>, Error> {
Result { try URLRequest(resource, configuration: configuration, encoder: encoder) }
.publisher
.flatMap(transport.sendPublisher)
.tryMap {
try self.checkResponseForIssues($0.response)
return Response(content: (), response: $0.response)
}
.eraseToAnyPublisher()
}
}
#endif

View File

@ -0,0 +1,14 @@
//
// AccessToken.swift
// Buildkite
//
// Created by Aaron Sky on 5/3/20.
// Copyright © 2020 Fangamer. All rights reserved.
//
import Foundation
public struct AccessToken: Codable, Equatable {
public var uuid: UUID
public var scopes: [String]
}

View File

@ -0,0 +1,27 @@
//
// Agent.swift
// Buildkite
//
// Created by Aaron Sky on 5/3/20.
// Copyright © 2020 Fangamer. All rights reserved.
//
import Foundation
public struct Agent: Codable, Equatable {
public var id: UUID
public var url: URL
public var webUrl: URL
public var name: String
public var connectionState: String
public var hostname: String
public var ipAddress: String
public var userAgent: String
public var version: String
public var creator: User?
public var createdAt: Date
public var job: Job?
public var lastJobFinishedAt: Date?
public var priority: Int?
public var metaData: [String]
}

View File

@ -0,0 +1,25 @@
//
// Annotation.swift
// Buildkite
//
// Created by Aaron Sky on 5/3/20.
// Copyright © 2020 Fangamer. All rights reserved.
//
import Foundation
public struct Annotation: Codable, Equatable {
public enum Context: String, Codable {
case success
case info
case warning
case error
}
public var id: UUID
public var context: String
public var style: Context
public var bodyHtml: String
public var createdAt: Date
public var updatedAt: Date
}

View File

@ -0,0 +1,34 @@
//
// Artifact.swift
// Buildkite
//
// Created by Aaron Sky on 5/3/20.
// Copyright © 2020 Fangamer. All rights reserved.
//
import Foundation
public struct Artifact: Codable, Equatable {
public enum State: String, Codable, Equatable {
case new
case error
case finished
case deleted
}
public var id: UUID
public var jobId: UUID
public var url: URL
public var downloadUrl: URL
public var state: State
public var path: String
public var dirname: String
public var filename: String
public var mimeType: String
public var fileSize: Int
public var sha1sum: String
public struct URLs: Codable {
public var url: URL
}
}

View File

@ -0,0 +1,32 @@
//
// Build.swift
// Buildkite
//
// Created by Aaron Sky on 5/3/20.
// Copyright © 2020 Fangamer. All rights reserved.
//
import Foundation
public struct Build: Codable, Equatable {
public var id: UUID
public var url: URL
public var webUrl: URL
public var number: Int
public var state: String
public var blocked: Bool
public var message: String?
public var commit: String
public var branch: String
public var env: [String: String]?
public var source: String
public var creator: User?
public var jobs: [Job]
public var createdAt: Date
public var scheduledAt: Date
public var startedAt: Date?
public var finishedAt: Date?
public var metaData: [String: String]
public var pullRequest: [String: String?]?
public var pipeline: Pipeline
}

View File

@ -0,0 +1,15 @@
//
// Emoji.swift
// Buildkite
//
// Created by Aaron Sky on 5/3/20.
// Copyright © 2020 Fangamer. All rights reserved.
//
import Foundation
public struct Emoji: Codable, Equatable {
public var name: String
public var url: URL
public var aliases: [String] = []
}

View File

@ -0,0 +1,67 @@
//
// Job.swift
// Buildkite
//
// Created by Aaron Sky on 5/3/20.
// Copyright © 2020 Fangamer. All rights reserved.
//
import Foundation
public struct Job: Codable, Equatable {
public struct Agent: Codable, Equatable {
public var id: UUID
public var url: URL
public var name: String
}
public var id: UUID
public var type: String
public var name: String?
public var stepKey: String?
public var agentQueryRules: [String]
public var state: String?
public var buildUrl: URL?
public var webUrl: URL?
public var logUrl: URL?
public var rawLogUrl: URL?
public var artifactsUrl: URL?
public var command: String?
public var softFailed: Bool
public var exitStatus: Int?
public var artifactPaths: String?
public var agent: Agent?
public var createdAt: Date
public var scheduledAt: Date
public var runnableAt: Date?
public var startedAt: Date?
public var finishedAt: Date?
public var retried: Bool
public var retriedInJobId: UUID?
public var retriesCount: Int?
public var parallelGroupIndex: Int?
public var parallelGroupTotal: Int?
public struct Unblocked: Codable, Equatable {
public var id: UUID
public var type: String
public var label: String
public var state: String
public var webUrl: URL?
public var unblockedBy: User
public var unblockedAt: Date
public var unblockable: Bool
public var unblockUrl: URL
}
public struct LogOutput: Codable {
public var url: URL
public var content: String
public var size: Int
public var headerTimes: [Int]
}
public struct EnvironmentVariables: Codable, Equatable {
public var env: [String: String]
}
}

View File

@ -0,0 +1,21 @@
//
// Organization.swift
// Buildkite
//
// Created by Aaron Sky on 5/3/20.
// Copyright © 2020 Fangamer. All rights reserved.
//
import Foundation
public struct Organization: Codable, Equatable {
public var id: UUID
public var url: URL
public var webUrl: URL
public var name: String
public var slug: String
public var pipelinesUrl: URL
public var agentsUrl: URL
public var emojisUrl: URL
public var createdAt: URL
}

View File

@ -0,0 +1,195 @@
//
// Pipeline.swift
// Buildkite
//
// Created by Aaron Sky on 5/3/20.
// Copyright © 2020 Fangamer. All rights reserved.
//
import Foundation
public struct Pipeline: Codable, Equatable {
public var id: UUID
public var url: URL
public var webUrl: URL
public var name: String
public var slug: String
public var repository: String
public var branchConfiguration: String?
public var defaultBranch: String?
public var provider: Provider
public var skipQueuedBranchBuilds: Bool
public var skipQueuedBranchBuildsFilter: String?
public var cancelRunningBranchBuilds: Bool
public var cancelRunningBranchBuildsFilter: String?
public var buildsUrl: URL
public var badgeUrl: URL
public var createdAt: Date
public var scheduledBuildsCount: Int
public var runningBuildsCount: Int
public var scheduledJobsCount: Int
public var runningJobsCount: Int
public var waitingJobsCount: Int
public var visibility: String
public var steps: [Step]
public var env: [String: EnvVar?]?
public struct Provider: Codable, Equatable {
public var id: String
public var webhookUrl: URL
public var settings: Settings
}
}
extension Pipeline.Provider {
public struct Settings: Codable, Equatable {
public var repository: String?
/// Whether to create builds for commits that are part of a Pull Request.
public var buildPullRequests: Bool?
/// Whether to limit the creation of builds to specific branches or patterns.
public var pullRequestBranchFilterEnabled: Bool?
/// The branch filtering pattern. Only pull requests on branches matching this pattern will cause builds to be created.
public var pullRequestBranchFilterConfiguration: String?
/// Whether to skip creating a new build for a pull request if an existing build for the commit and branch already exists.
public var skipPullRequestBuildsForExistingCommits: Bool?
/// Whether to create builds when tags are pushed.
public var buildTags: Bool?
/// Whether to update the status of commits in Bitbucket or GitHub.
public var publishCommitStatus: Bool?
/// Whether to create a separate status for each job in a build, allowing you to see the status of each job directly in Bitbucket or GitHub.
public var publishCommitStatusPerStep: Bool?
/// What type of event to trigger builds on. Code will create builds when code is pushed to GitHub. Deployment will create builds when a deployment is created with the GitHub Deployments API. Fork will create builds when the GitHub repository is forked. None will not create any builds based on GitHub activity.
public var triggerMode: String?
/// Whether filter conditions are being used for this step.
public var filterEnabled: Bool?
/// The conditions under which this step will run. See the Using Conditionals guide for more information.
public var filterCondition: String?
/// Whether to create builds for pull requests from third-party forks.
public var buildPullRequestForks: Bool?
/// Prefix branch names for third-party fork builds to ensure they don't trigger branch conditions. For example, the master branch from some-user will become some-user:master.
public var prefixPullRequestForkBranchNames: Bool?
/// Whether to create a separate status for pull request builds, allowing you to require a passing pull request build in your required status checks in GitHub.
public var separatePullRequestStatuses: Bool?
/// The status to use for blocked builds. Pending can be used with required status checks to prevent merging pull requests with blocked builds.
public var publishBlockedAsPending: Bool?
}
}
extension Pipeline {
public enum Step: Codable, Equatable {
case script(Command)
case waiter(Wait)
case manual(Block)
case trigger(Trigger)
private enum Unassociated: String, Codable {
case script
case waiter
case manual
case trigger
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let type = try container.decode(Unassociated.self, forKey: .type)
switch type {
case .script:
self = .script(try Command(from: decoder))
case .waiter:
self = .waiter(try Wait(from: decoder))
case .manual:
self = .manual(try Block(from: decoder))
case .trigger:
self = .trigger(try Trigger(from: decoder))
}
}
public func encode(to encoder: Encoder) throws {
switch self {
case .script(let step):
try step.encode(to: encoder)
case .waiter(let step):
try step.encode(to: encoder)
case .manual(let step):
try step.encode(to: encoder)
case .trigger(let step):
try step.encode(to: encoder)
}
}
private enum CodingKeys: String, CodingKey {
case type
}
public struct Command: Codable, Equatable {
public let type = "script"
public var name: String
public var command: String?
public var label: String?
public var artifactPaths: String?
public var branchConfiguration: String?
public var env: [String: EnvVar?]
public var timeoutInMinutes: Int?
public var agentQueryRules: [String]
public var async: Bool?
public var concurrency: Int?
public var parallelism: Int?
}
public struct Wait: Codable, Equatable {
public let type = "waiter"
public var label: String?
public var continueAfterFailure: Bool?
}
public struct Block: Codable, Equatable {
public let type = "manual"
public var label: String?
}
public struct Trigger: Codable, Equatable {
public let type = "trigger"
public var triggerProjectSlug: String
public var label: String?
public var triggerCommit: String?
public var triggerBranch: String?
public var triggerAsync: Bool?
}
}
}
public enum EnvVar: Codable, Equatable {
case bool(Bool)
case number(Double)
case string(String)
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
do {
let bool = try container.decode(Bool.self)
self = .bool(bool)
} catch DecodingError.typeMismatch {
do {
let number = try container.decode(Double.self)
self = .number(number)
} catch DecodingError.typeMismatch {
let string = try container.decode(String.self)
self = .string(string)
}
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .bool(let bool):
try container.encode(bool)
case .number(let number):
try container.encode(number)
case .string(let string):
try container.encode(string)
}
}
}

View File

@ -0,0 +1,17 @@
//
// User.swift
// Buildkite
//
// Created by Aaron Sky on 5/3/20.
// Copyright © 2020 Fangamer. All rights reserved.
//
import Foundation
public struct User: Codable, Equatable {
public var id: UUID
public var name: String
public var email: String
public var avatarUrl: URL
public var createdAt: Date
}

View File

@ -0,0 +1,24 @@
//
// APIVersion.swift
//
//
// Created by Aaron Sky on 3/22/20.
//
import Foundation
public struct APIVersion {
public static let v2: APIVersion = "v2"
let id: String
init(_ version: String) {
id = version
}
}
extension APIVersion: ExpressibleByStringLiteral {
public init(stringLiteral value: StringLiteralType) {
self.init(value)
}
}

View File

@ -0,0 +1,36 @@
//
// Configuration.swift
//
//
// Created by Aaron Sky on 3/22/20.
//
import Foundation
public struct Configuration {
public let baseURL = URL(string: "https://api.buildkite.com")!
public var version: APIVersion
public static var `default`: Configuration {
.init(version: .v2)
}
public init(version: APIVersion) {
self.version = version
}
func url(for path: String) -> URL {
baseURL
.appendingPathComponent(version.id)
.appendingPathComponent(path)
}
var token: String?
func authorizeIfNeeded(_ request: inout URLRequest) {
if let token = token {
request.addValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
}
}
}

View File

@ -0,0 +1,86 @@
//
// Constants.swift
//
//
// Created by Aaron Sky on 3/23/20.
//
import Foundation
enum Formatters {
static let iso8601WithFractionalSeconds: ISO8601DateFormatter = {
let formatter: ISO8601DateFormatter
if #available(iOS 11.0, macOS 10.13, *) {
formatter = ISO8601DateFormatter()
formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
} else {
formatter = Formatters.iso8601WithoutFractionalSeconds
}
return formatter
}()
static let iso8601WithoutFractionalSeconds: ISO8601DateFormatter = {
let formatter = ISO8601DateFormatter()
formatter.formatOptions = [.withInternetDateTime]
return formatter
}()
static func dateIfPossible(fromISO8601 string: String) -> Date? {
if let date = iso8601WithFractionalSeconds.date(from: string) {
return date
} else if let date = iso8601WithoutFractionalSeconds.date(from: string) {
return date
}
return nil
}
static func encodeISO8601(date: Date, encoder: Encoder) throws {
var container = encoder.singleValueContainer()
let dateString = iso8601WithoutFractionalSeconds.string(from: date)
try container.encode(dateString)
}
static func decodeISO8601(decoder: Decoder) throws -> Date {
let container = try decoder.singleValueContainer()
let dateString = try container.decode(String.self)
guard let date = dateIfPossible(fromISO8601: dateString) else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: container.codingPath, debugDescription: "Expected date string to be ISO8601-formatted."))
}
return date
}
}
class OptionalFractionalSecondsDateFormatter: DateFormatter {
static let withoutSeconds: DateFormatter = {
let formatter = DateFormatter()
formatter.calendar = Calendar(identifier: .iso8601)
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(identifier: "UTC")
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssXXX"
return formatter
}()
func setup() {
calendar = Calendar(identifier: .iso8601)
locale = Locale(identifier: "en_US_POSIX")
timeZone = TimeZone(identifier: "UTC")
dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSSSSXXX" // handle up to 6 decimal places, although iOS currently only preserves 2 digits of precision
}
override init() {
super.init()
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
override func date(from string: String) -> Date? {
if let result = super.date(from: string) {
return result
}
return OptionalFractionalSecondsDateFormatter.withoutSeconds.date(from: string)
}
}

View File

@ -0,0 +1,43 @@
//
// Resource.swift
//
//
// Created by Aaron Sky on 3/22/20.
//
import Foundation
public protocol HasRequestBody {
associatedtype Body: Encodable
var body: Body { get }
}
public protocol HasResponseBody {
associatedtype Content: Decodable
}
public protocol Resource {
var path: String { get }
func transformRequest(_ request: inout URLRequest)
}
extension Resource {
public func transformRequest(_ request: inout URLRequest) {
}
}
extension URLRequest {
init<R: Resource & HasRequestBody>(_ resource: R, configuration: Configuration, encoder: JSONEncoder) throws {
self.init(resource, configuration: configuration)
httpBody = try encoder.encode(resource.body)
}
init<R: Resource>(_ resource: R, configuration: Configuration) {
let url = configuration.url(for: resource.path)
var request = URLRequest(url: url)
configuration.authorizeIfNeeded(&request)
resource.transformRequest(&request)
self = request
}
}

View File

@ -0,0 +1,23 @@
//
// Response.swift
//
//
// Created by Aaron Sky on 3/22/20.
//
import Foundation
enum ResponseError: Error {
case missingResponse
case unexpectedlyNoContent
}
public struct Response<T> {
public let content: T
public let response: URLResponse
init(content: T, response: URLResponse) {
self.content = content
self.response = response
}
}

View File

@ -0,0 +1,71 @@
//
// StatusCode.swift
//
//
// Created by Aaron Sky on 3/22/20.
//
import Foundation
public enum StatusCode: Int, Error {
/// The request was successfully processed by Buildkite.
case ok = 200
/// The request has been fulfilled and a new resource has been created.
case created = 201
/// The request has been accepted, but not yet processed.
case accepted = 202
/// 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
/// The request was not understood by the server, generally due to bad syntax or because the Content-Type header was not correctly set to application/json.
///
/// This status is also returned when the request provides an invalid code parameter during the OAuth token exchange process.
case badRequest = 400
/// The necessary authentication credentials are not present in the request or are incorrect.
case unauthorized = 401
/// The requested shop is currently frozen. The shop owner needs to log in to the shop's admin and pay the outstanding balance to unfreeze the shop.
case paymentRequired = 402
/// The server is refusing to respond to the request. This is generally because you have not requested the appropriate scope for this action.
case forbidden = 403
/// The requested resource was not found but could be available again in the future.
case notFound = 404
/// The requested resource is only capable of generating content not acceptable according to the Accept headers sent in the request.
case notAcceptable = 406
/// The request body was well-formed but contains semantic errors. The response body will provide more details in the errors or error parameters.
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.
case tooManyRequests = 429
/// An internal error occurred in Buildkite. Please post to the API & Technology forum so that Buildkite staff can investigate.
case internalServerError = 500
/// The requested endpoint is not available on that particular shop, e.g. requesting access to a Plus-specific API on a non-Plus shop. This response may also indicate that this endpoint is reserved for future use.
case notImplemented = 501
/// The server is currently unavailable. Check the status page for reported service outages.
case serviceUnavailable = 503
/// The request could not complete in time. Try breaking it down in multiple smaller requests.
case gatewayTimeout = 504
var isSuccess: Bool {
self == .ok
|| self == .created
|| self == .accepted
}
}

View File

@ -0,0 +1,54 @@
//
// Transport.swift
//
//
// Created by Aaron Sky on 3/22/20.
//
import Foundation
#if canImport(FoundationNetworking)
import FoundationNetworking
#endif
#if canImport(Combine)
import Combine
#endif
public protocol Transport {
typealias Output = (data: Data, response: URLResponse)
func send(request: URLRequest, completion: @escaping (Result<Output, Error>) -> Void)
#if canImport(Combine)
@available(iOS 13.0, macOS 10.15, *)
func sendPublisher(request: URLRequest) -> AnyPublisher<Output, Error>
#endif
}
extension URLSession: Transport {
public func send(request: URLRequest, completion: @escaping (Result<Output, Error>) -> Void) {
let task = dataTask(with: request) { data, response, error in
if let error = error {
completion(.failure(error))
return
}
guard let data = data, let response = response else {
completion(.failure(ResponseError.missingResponse))
return
}
completion(.success((data: data, response: response)))
}
task.resume()
}
#if canImport(Combine)
@available(iOS 13.0, macOS 10.15, *)
public func sendPublisher(request: URLRequest) -> AnyPublisher<Output, Error> {
dataTaskPublisher(for: request)
.mapError { $0 as Error }
.eraseToAnyPublisher()
}
#endif
}

View File

@ -0,0 +1,38 @@
//
// AccessTokens.swift
//
//
// Created by Aaron Sky on 4/21/20.
//
import Foundation
extension AccessToken {
public enum Resources {}
}
extension AccessToken.Resources {
/// Get the current token
///
/// Returns details about the API Access Token that was used to authenticate the request.
public struct Get: Resource, HasResponseBody {
public typealias Content = AccessToken
public let path = "access-token"
public init() {}
}
/// Revoke the current token
///
/// Once revoked, the token can no longer be used for further requests.
public struct Revoke: Resource {
public typealias Content = Void
public let path = "access-token"
public init() {}
public func transformRequest(_ request: inout URLRequest) {
request.httpMethod = "DELETE"
}
}
}

View File

@ -0,0 +1,82 @@
//
// Agents.swift
//
//
// Created by Aaron Sky on 4/21/20.
//
import Foundation
extension Agent {
public enum Resources { }
}
extension Agent.Resources {
/// List agents
///
/// Returns a paginated list of an organizations agents.
public struct List: Resource, HasResponseBody {
public typealias Content = [Agent]
/// organization slug
public var organization: String
public var path: String {
"organizations/\(organization)/agents"
}
public init(organization: String) {
self.organization = organization
}
}
public struct Get: Resource, HasResponseBody {
public typealias Content = Agent
/// organization slug
public var organization: String
/// agent ID
public var agentId: UUID
public var path: String {
"organizations/\(organization)/agents/\(agentId)"
}
public init(organization: String, agentId: UUID) {
self.organization = organization
self.agentId = agentId
}
}
/// Stop an agent
///
/// Instruct an agent to stop accepting new build jobs and shut itself down.
public struct Stop: Resource {
public typealias Content = Void
/// organization slug
public var organization: String
/// agent ID
public var agentId: UUID
public var force: Bool?
public var path: String {
"organizations/\(organization)/agents/\(agentId)/stop"
}
public init(organization: String, agentId: UUID, force: Bool? = nil) {
self.organization = organization
self.agentId = agentId
self.force = force
}
public func transformRequest(_ request: inout URLRequest) {
request.httpMethod = "PUT"
guard let url = request.url,
var components = URLComponents(url: url, resolvingAgainstBaseURL: true) else {
return
}
var queryItems: [URLQueryItem] = []
queryItems.appendIfNeeded(force, forKey: "force")
components.queryItems = queryItems
request.url = components.url
}
}
}

View File

@ -0,0 +1,37 @@
//
// Annotations.swift
//
//
// Created by Aaron Sky on 4/21/20.
//
import Foundation
extension Annotation {
public enum Resources { }
}
extension Annotation.Resources {
/// List annotations for a build
///
/// Returns a paginated list of a builds annotations.
public struct List: Resource, HasResponseBody {
public typealias Content = [Annotation]
/// organization slug
public var organization: String
/// pipeline slug
public var pipeline: String
/// build number
public var build: Int
public var path: String {
"organizations/\(organization)/pipelines/\(pipeline)/builds/\(build)/annotations"
}
public init(organization: String, pipeline: String, build: Int) {
self.organization = organization
self.pipeline = pipeline
self.build = build
}
}
}

View File

@ -0,0 +1,152 @@
//
// Artifacts.swift
//
//
// Created by Aaron Sky on 4/21/20.
//
import Foundation
extension Artifact {
enum Resources { }
}
extension Artifact.Resources {
/// List artifacts for a build
///
/// Returns a paginated list of a builds artifacts across all of its jobs.
struct ListByBuild: Resource, HasResponseBody {
typealias Content = [Artifact]
/// organization slug
var organization: String
/// pipeline slug
var pipeline: String
/// build number
var build: Int
var path: String {
"organizations/\(organization)/pipelines/\(pipeline)/builds/\(build)/artifacts"
}
public init(organization: String, pipeline: String, build: Int) {
self.organization = organization
self.pipeline = pipeline
self.build = build
}
}
/// List artifacts for a job
///
/// Returns a paginated list of a jobs artifacts.
struct ListByJob: Resource, HasResponseBody {
typealias Content = [Artifact]
/// organization slug
var organization: String
/// pipeline slug
var pipeline: String
/// build number
var build: Int
/// job ID
var jobId: UUID
var path: String {
"organizations/\(organization)/pipelines/\(pipeline)/builds/\(build)/jobs/\(jobId)/artifacts"
}
public init(organization: String, pipeline: String, build: Int, jobId: UUID) {
self.organization = organization
self.pipeline = pipeline
self.build = build
self.jobId = jobId
}
}
/// Get an artifact
struct Get: Resource, HasResponseBody {
typealias Content = Artifact
/// organization slug
var organization: String
/// pipeline slug
var pipeline: String
/// build number
var build: Int
/// job ID
var jobId: UUID
/// artifact ID
var artifactId: UUID
var path: String {
"organizations/\(organization)/pipelines/\(pipeline)/builds/\(build)/jobs/\(jobId)/artifacts/\(artifactId)"
}
public init(organization: String, pipeline: String, build: Int, jobId: UUID, artifactId: UUID) {
self.organization = organization
self.pipeline = pipeline
self.build = build
self.jobId = jobId
self.artifactId = artifactId
}
}
/// Download an artifact
///
///
struct Download: Resource {
typealias Content = Artifact.URLs
/// organization slug
var organization: String
/// pipeline slug
var pipeline: String
/// build number
var build: Int
/// job ID
var jobId: UUID
/// artifact ID
var artifactId: UUID
var path: String {
"organizations/\(organization)/pipelines/\(pipeline)/builds/\(build)/jobs/\(jobId)/artifacts/\(artifactId)/download"
}
public init(organization: String, pipeline: String, build: Int, jobId: UUID, artifactId: UUID) {
self.organization = organization
self.pipeline = pipeline
self.build = build
self.jobId = jobId
self.artifactId = artifactId
}
}
/// Delete an artifact
///
///
struct Delete: Resource {
typealias Content = Void
/// organization slug
var organization: String
/// pipeline slug
var pipeline: String
/// build number
var build: Int
/// job ID
var jobId: UUID
/// artifact ID
var artifactId: UUID
var path: String {
"organizations/\(organization)/pipelines/\(pipeline)/builds/\(build)/jobs/\(jobId)/artifacts/\(artifactId)"
}
func transformRequest(_ request: inout URLRequest) {
request.httpMethod = "DELETE"
}
public init(organization: String, pipeline: String, build: Int, jobId: UUID, artifactId: UUID) {
self.organization = organization
self.pipeline = pipeline
self.build = build
self.jobId = jobId
self.artifactId = artifactId
}
}
}

View File

@ -0,0 +1,306 @@
//
// Builds.swift
//
//
// Created by Aaron Sky on 4/21/20.
//
import Foundation
extension Build {
public enum Resources { }
}
extension Build.Resources {
/// List all builds
///
/// Returns a paginated list of all builds across all the users organizations and pipelines. If using token-based authentication
/// the list of builds will be for the authorized organizations only. Builds are listed in the order they were created (newest first).
public struct ListAll: Resource, HasResponseBody {
public typealias Content = [Build]
public let path = "builds"
public var options: QueryOptions?
public init(options: Build.Resources.QueryOptions? = nil) {
self.options = options
}
public func transformRequest(_ request: inout URLRequest) {
guard let url = request.url,
var components = URLComponents(url: url, resolvingAgainstBaseURL: true) else {
return
}
if let options = options {
components.queryItems = [URLQueryItem](options: options)
}
request.url = components.url
}
}
/// List builds for an organization
///
/// Returns a paginated list of an organizations builds across all of an organizations pipelines. Builds are listed in the order
/// they were created (newest first).
public struct ListForOrganization: Resource, HasResponseBody {
public typealias Content = [Build]
/// organization slug
public var organization: String
public var options: QueryOptions?
public var path: String {
"organizations/\(organization)/builds"
}
public init(organization: String, options: Build.Resources.QueryOptions? = nil) {
self.organization = organization
self.options = options
}
public func transformRequest(_ request: inout URLRequest) {
guard let url = request.url,
var components = URLComponents(url: url, resolvingAgainstBaseURL: true) else {
return
}
if let options = options {
components.queryItems = [URLQueryItem](options: options)
}
request.url = components.url
}
}
/// List builds for a pipeline
///
/// Returns a paginated list of a pipelines builds. Builds are listed in the order they were created (newest first).
public struct ListForPipeline: Resource, HasResponseBody {
public typealias Content = [Build]
/// organization slug
public var organization: String
/// pipeline slug
public var pipeline: String
public var options: QueryOptions?
public var path: String {
"organizations/\(organization)/pipelines/\(pipeline)/builds"
}
public init(organization: String, pipeline: String, options: Build.Resources.QueryOptions? = nil) {
self.organization = organization
self.pipeline = pipeline
self.options = options
}
public func transformRequest(_ request: inout URLRequest) {
guard let url = request.url,
var components = URLComponents(url: url, resolvingAgainstBaseURL: true) else {
return
}
if let options = options {
components.queryItems = [URLQueryItem](options: options)
}
request.url = components.url
}
}
/// Get a build
public struct Get: Resource, HasResponseBody {
public typealias Content = Build
/// organization slug
public var organization: String
/// pipeline slug
public var pipeline: String
/// build number
public var build: Int
public var path: String {
"organizations/\(organization)/pipelines/\(pipeline)/builds/\(build)"
}
public init(organization: String, pipeline: String, build: Int) {
self.organization = organization
self.pipeline = pipeline
self.build = build
}
}
/// Create a build
public struct Create: Resource, HasRequestBody, HasResponseBody {
public typealias Content = Build
/// organization slug
public var organization: String
/// pipeline slug
public var pipeline: String
/// body of the request
public var body: Body
public struct Body: Codable {
public struct Author: Codable {
public var name: String
public var email: String
public init(name: String, email: String) {
self.name = name
self.email = email
}
}
/// Ref, SHA or tag to be built.
public var commit: String
/// Branch the commit belongs to. This allows you to take advantage of your pipeline and step-level branch filtering rules.
public var branch: String
/// A hash with a "name" and "email" key to show who created this build.
public var author: Author?
/// Force the agent to remove any existing build directory and perform a fresh checkout.
public var cleanCheckout: Bool?
/// Environment variables to be made available to the build.
public var env: [String: String]?
/// Run the build regardless of the pipelines branch filtering rules. Step branch filtering rules will still apply.
public var ignorePipelineBranchFilters: Bool?
/// Message for the build.
public var message: String?
/// A hash of meta-data to make available to the build.
public var metaData: [String: String]?
/// For a pull request build, the base branch of the pull request.
public var pullRequestBaseBranch: String?
/// For a pull request build, the pull request number.
public var pullRequestId: Int?
/// For a pull request build, the git repository of the pull request.
public var pullRequestRepository: String?
public init(commit: String, branch: String, author: Build.Resources.Create.Body.Author? = nil, cleanCheckout: Bool? = nil, env: [String : String]? = nil, ignorePipelineBranchFilters: Bool? = nil, message: String? = nil, metaData: [String : String]? = nil, pullRequestBaseBranch: String? = nil, pullRequestId: Int? = nil, pullRequestRepository: String? = nil) {
self.commit = commit
self.branch = branch
self.author = author
self.cleanCheckout = cleanCheckout
self.env = env
self.ignorePipelineBranchFilters = ignorePipelineBranchFilters
self.message = message
self.metaData = metaData
self.pullRequestBaseBranch = pullRequestBaseBranch
self.pullRequestId = pullRequestId
self.pullRequestRepository = pullRequestRepository
}
}
public var path: String {
"organizations/\(organization)/pipelines/\(pipeline)/builds"
}
public init(organization: String, pipeline: String, body: Body) {
self.organization = organization
self.pipeline = pipeline
self.body = body
}
public func transformRequest(_ request: inout URLRequest) {
request.httpMethod = "POST"
}
}
/// Cancel a build
///
/// Cancels the build if it's state is either scheduled or running.
public struct Cancel: Resource, HasResponseBody {
public typealias Content = Build
/// organization slug
public var organization: String
/// pipeline slug
public var pipeline: String
/// build number
public var build: Int
public var path: String {
"organizations/\(organization)/pipelines/\(pipeline)/builds/\(build)/cancel"
}
public init(organization: String, pipeline: String, build: Int) {
self.organization = organization
self.pipeline = pipeline
self.build = build
}
public func transformRequest(_ request: inout URLRequest) {
request.httpMethod = "PUT"
}
}
/// Rebuild a build
///
/// Returns the newly created build.
public struct Rebuild: Resource, HasResponseBody {
public typealias Content = Build
/// organization slug
public var organization: String
/// pipeline slug
public var pipeline: String
/// build number
public var build: Int
public var path: String {
"organizations/\(organization)/pipelines/\(pipeline)/builds/\(build)/rebuild"
}
public init(organization: String, pipeline: String, build: Int) {
self.organization = organization
self.pipeline = pipeline
self.build = build
}
public func transformRequest(_ request: inout URLRequest) {
request.httpMethod = "PUT"
}
}
public struct QueryOptions {
internal init(branches: [String] = [], commit: String? = nil, createdFrom: Date? = nil, createdTo: Date? = nil, creator: UUID? = nil, finishedFrom: Date? = nil, includeRetriedJobs: Bool? = nil, metadata: [String : String] = [:], state: String? = nil) {
self.branches = branches
self.commit = commit
self.createdFrom = createdFrom
self.createdTo = createdTo
self.creator = creator
self.finishedFrom = finishedFrom
self.includeRetriedJobs = includeRetriedJobs
self.metadata = metadata
self.state = state
}
/// Filters the results by the given branch or branches.
public var branches: [String] = []
/// Filters the results by the commit (only works for full sha, not for shortened ones).
public var commit: String?
/// Filters the results by builds created on or after the given time (in ISO 8601 format)
public var createdFrom: Date?
/// Filters the results by builds created before the given time (in ISO 8601 format)
public var createdTo: Date?
/// Filters the results by the user who created the build
public var creator: UUID?
/// Filters the results by builds finished on or after the given time (in ISO 8601 format)
public var finishedFrom: Date?
/// Include all retried job executions in each builds jobs list. Without this parameter, you'll see only the most recently run job for each step.
public var includeRetriedJobs: Bool?
/// Filters the results by the given meta_data.
public var metadata: [String: String] = [:]
/// Filters the results by the given build state. The finished state is a shortcut to automatically search for builds with passed, failed, blocked, canceled states.
public var state: String?
}
}
extension Array where Element == URLQueryItem {
init(options: Build.Resources.QueryOptions) {
self.init()
append(options.branches, forKey: "branch")
appendIfNeeded(options.commit, forKey: "commit")
appendIfNeeded(options.createdFrom, forKey: "created_from")
appendIfNeeded(options.createdTo, forKey: "created_to")
appendIfNeeded(options.creator, forKey: "creator")
appendIfNeeded(options.finishedFrom, forKey: "finished_from")
appendIfNeeded(options.includeRetriedJobs, forKey: "include_retried_jobs")
append(options.metadata, forKey: "meta_data")
appendIfNeeded(options.state, forKey: "state")
}
}

View File

@ -0,0 +1,31 @@
//
// Emojis.swift
//
//
// Created by Aaron Sky on 4/21/20.
//
import Foundation
extension Emoji {
public enum Resources { }
}
extension Emoji.Resources {
/// List emojis
///
/// Returns a list of all the emojis for a given organization, including custom emojis and aliases. This list is not paginated.
public struct List: Resource, HasResponseBody {
public typealias Content = [Emoji]
/// organization slug
public var organization: String
public var path: String {
"organizations/\(organization)/emojis"
}
public init(organization: String) {
self.organization = organization
}
}
}

View File

@ -0,0 +1,163 @@
//
// Jobs.swift
//
//
// Created by Aaron Sky on 4/21/20.
//
import Foundation
extension Job {
public enum Resources { }
}
extension Job.Resources {
/// Retry a job
///
/// Retries a `failed` or `timed_out` job.
public struct Retry: Resource, HasResponseBody {
public typealias Content = Job
/// 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 path: String {
"organizations/\(organization)/pipelines/\(pipeline)/builds/\(build)/jobs/\(job)/retry"
}
public func transformRequest(_ request: inout URLRequest) {
request.httpMethod = "PUT"
}
public init(organization: String, pipeline: String, build: Int, job: UUID) {
self.organization = organization
self.pipeline = pipeline
self.build = build
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.
public struct Unblock: Resource, HasRequestBody, HasResponseBody {
public typealias Content = Job
/// organization slug
public var organization: String
/// pipeline slug
public var pipeline: String
/// build number
public var build: Int
/// job ID
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]
public init(unblocker: UUID? = nil, fields: [String : String] = [:]) {
self.unblocker = unblocker
self.fields = fields
}
}
public var path: String {
"organizations/\(organization)/pipelines/\(pipeline)/builds/\(build)/jobs/\(job)/unblock"
}
public init(organization: String, pipeline: String, build: Int, job: UUID, body: Job.Resources.Unblock.Body) {
self.organization = organization
self.pipeline = pipeline
self.build = build
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
/// 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 path: String {
"organizations/\(organization)/pipelines/\(pipeline)/builds/\(build)/jobs/\(job)/log"
}
public init(organization: String, pipeline: String, build: Int, job: UUID) {
self.organization = organization
self.pipeline = pipeline
self.build = build
self.job = job
}
}
/// Delete a jobs log output
public struct DeleteLogOutput: Resource {
public typealias Content = Void
/// 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 path: String {
"organizations/\(organization)/pipelines/\(pipeline)/builds/\(build)/jobs/\(job)/log"
}
public init(organization: String, pipeline: String, build: Int, job: UUID) {
self.organization = organization
self.pipeline = pipeline
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
/// 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 path: String {
"organizations/\(organization)/pipelines/\(pipeline)/builds/\(build)/jobs/\(job)/env"
}
public init(organization: String, pipeline: String, build: Int, job: UUID) {
self.organization = organization
self.pipeline = pipeline
self.build = build
self.job = job
}
}
}

View File

@ -0,0 +1,40 @@
//
// Organizations.swift
//
//
// Created by Aaron Sky on 4/21/20.
//
import Foundation
extension Organization {
public enum Resources { }
}
extension Organization.Resources {
/// List organizations
///
/// Returns a paginated list of the users organizations.
public struct List: Resource, HasResponseBody {
public typealias Content = [Organization]
public let path = "organizations"
public init() {}
}
/// Get an organization
public struct Get: Resource, HasResponseBody {
public typealias Content = Organization
/// organization slug
public var organization: String
public var path: String {
"organizations/\(organization)"
}
public init(organization: String) {
self.organization = organization
}
}
}

View File

@ -0,0 +1,206 @@
//
// Pipelines.swift
//
//
// Created by Aaron Sky on 4/21/20.
//
import Foundation
extension Pipeline {
public enum Resources { }
}
extension Pipeline.Resources {
/// List pipelines
///
/// Returns a paginated list of an organizations pipelines.
public struct List: Resource, HasResponseBody {
public typealias Content = [Pipeline]
/// organization slug
public var organization: String
/// pipeline slug
public var pipeline: String
public var path: String {
"organizations/\(organization)/pipelines"
}
public init(organization: String, pipeline: String) {
self.organization = organization
self.pipeline = pipeline
}
}
/// Get a pipeline
public struct Get: Resource, HasResponseBody {
public typealias Content = Pipeline
/// organization slug
public var organization: String
/// pipeline slug
public var pipeline: String
public var path: String {
"organizations/\(organization)/pipelines/\(pipeline)"
}
public init(organization: String, pipeline: String) {
self.organization = organization
self.pipeline = pipeline
}
}
/// Create a pipeline
public struct Create: Resource, HasRequestBody, HasResponseBody {
public typealias Content = Pipeline
/// organization slug
public var organization: String
public var body: Body
public struct Body: Codable {
/// The name of the pipeline.
public var name: String
/// The repository URL.
public var repository: URL
/// An array of the build pipeline steps.
public var steps: [Pipeline.Step]
/// A branch filter pattern to limit which pushed branches trigger builds on this pipeline.
public var branchConfiguration: String?
/// Cancel intermediate builds. When a new build is created on a branch, any previous builds that are running on the same branch will be automatically canceled.
public var cancelRunningBranchBuilds: Bool?
/// A branch filter pattern to limit which branches intermediate build cancelling applies to.
public var cancelRunningBranchBuildsFilter: String?
/// The name of the branch to prefill when new builds are created or triggered in Buildkite. It is also used to filter the builds and metrics shown on the Pipelines page.
public var defaultBranch: String?
/// The pipeline description.
public var description: String?
/// The pipeline environment variables.
public var env: [String: String]?
/// The source provider settings. See the Provider Settings section for accepted properties.
public var providerSettings: Pipeline.Provider.Settings?
/// Skip intermediate builds. When a new build is created on a branch, any previous builds that haven't yet started on the same branch will be automatically marked as skipped.
public var skipQueuedBranchBuilds: Bool?
/// A branch filter pattern to limit which branches intermediate build skipping applies to.
public var skipQueuedBranchBuildsFilter: String?
/// An array of team UUIDs to add this pipeline to. You can find your teams UUID either via the GraphQL API, or on the settings page for a team. This property is only available if your organization has enabled Teams.
public var teamUUIDs: [UUID]?
public init(name: String, repository: URL, steps: [Pipeline.Step], branchConfiguration: String? = nil, cancelRunningBranchBuilds: Bool? = nil, cancelRunningBranchBuildsFilter: String? = nil, defaultBranch: String? = nil, description: String? = nil, env: [String : String]? = nil, providerSettings: Pipeline.Provider.Settings? = nil, skipQueuedBranchBuilds: Bool? = nil, skipQueuedBranchBuildsFilter: String? = nil, teamUUIDs: [UUID]? = nil) {
self.name = name
self.repository = repository
self.steps = steps
self.branchConfiguration = branchConfiguration
self.cancelRunningBranchBuilds = cancelRunningBranchBuilds
self.cancelRunningBranchBuildsFilter = cancelRunningBranchBuildsFilter
self.defaultBranch = defaultBranch
self.description = description
self.env = env
self.providerSettings = providerSettings
self.skipQueuedBranchBuilds = skipQueuedBranchBuilds
self.skipQueuedBranchBuildsFilter = skipQueuedBranchBuildsFilter
self.teamUUIDs = teamUUIDs
}
}
public var path: String {
"organizations/\(organization)/pipelines"
}
public init(organization: String, body: Pipeline.Resources.Create.Body) {
self.organization = organization
self.body = body
}
public func transformRequest(_ request: inout URLRequest) {
request.httpMethod = "POST"
}
}
/// Update a pipeline
///
/// Updates one or more properties of an existing pipeline
public struct Update: Resource, HasRequestBody, HasResponseBody {
public typealias Content = Pipeline
/// organization slug
public var organization: String
/// pipeline slug
public var pipeline: String
public var body: Body
public struct Body: Codable {
/// A branch filter pattern to limit which pushed branches trigger builds on this pipeline.
public var branchConfiguration: String?
/// Cancel intermediate builds. When a new build is created on a branch, any previous builds that are running on the same branch will be automatically canceled.
public var cancelRunningBranchBuilds: Bool?
/// A branch filter pattern to limit which branches intermediate build cancelling applies to.
public var cancelRunningBranchBuildsFilter: String?
/// The name of the branch to prefill when new builds are created or triggered in Buildkite.
public var defaultBranch: String?
/// The pipeline description.
public var description: String?
/// The pipeline environment variables.
public var env: [String: String]?
/// The name of the pipeline.
public var name: String?
/// The source provider settings. See the Provider Settings section for accepted properties.
public var providerSettings: Pipeline.Provider.Settings?
/// The repository URL.
public var repository: URL?
/// An array of the build pipeline steps.
public var steps: [Pipeline.Step]?
/// Skip intermediate builds. When a new build is created on a branch, any previous builds that haven't yet started on the same branch will be automatically marked as skipped.
public var skipQueuedBranchBuilds: Bool?
/// A branch filter pattern to limit which branches intermediate build skipping applies to.
public var skipQueuedBranchBuildsFilter: String?
/// Whether the pipeline is visible to everyone, including users outside this organization.
public var visibility: String?
public init(branchConfiguration: String? = nil, cancelRunningBranchBuilds: Bool? = nil, cancelRunningBranchBuildsFilter: String? = nil, defaultBranch: String? = nil, description: String? = nil, env: [String : String]? = nil, name: String? = nil, providerSettings: Pipeline.Provider.Settings? = nil, repository: URL? = nil, steps: [Pipeline.Step]? = nil, skipQueuedBranchBuilds: Bool? = nil, skipQueuedBranchBuildsFilter: String? = nil, visibility: String? = nil) {
self.branchConfiguration = branchConfiguration
self.cancelRunningBranchBuilds = cancelRunningBranchBuilds
self.cancelRunningBranchBuildsFilter = cancelRunningBranchBuildsFilter
self.defaultBranch = defaultBranch
self.description = description
self.env = env
self.name = name
self.providerSettings = providerSettings
self.repository = repository
self.steps = steps
self.skipQueuedBranchBuilds = skipQueuedBranchBuilds
self.skipQueuedBranchBuildsFilter = skipQueuedBranchBuildsFilter
self.visibility = visibility
}
}
public var path: String {
"organizations/\(organization)/pipelines/\(pipeline)"
}
public init(organization: String, pipeline: String, body: Pipeline.Resources.Update.Body) {
self.organization = organization
self.pipeline = pipeline
self.body = body
}
}
/// Delete a pipeline
public struct Delete: Resource {
public typealias Content = Void
/// organization slug
public var organization: String
/// pipeline slug
public var pipeline: String
public var path: String {
"organizations/\(organization)/pipelines/\(pipeline)"
}
public init(organization: String, pipeline: String) {
self.organization = organization
self.pipeline = pipeline
}
}
}

View File

@ -0,0 +1,21 @@
//
// Users.swift
//
//
// Created by Aaron Sky on 4/21/20.
//
import Foundation
extension User {
public enum Resources { }
}
extension User.Resources {
public struct Me: Resource, HasResponseBody {
public typealias Content = User
public let path = "user"
public init() {}
}
}

View File

@ -0,0 +1,66 @@
//
// URL+Buildkite.swift
//
//
// Created by Aaron Sky on 5/5/20.
//
import Foundation
extension Array where Element == URLQueryItem {
mutating func appendIfNeeded<S: LosslessStringConvertible>(_ value: S?, forKey key: String) {
guard let value = value else {
return
}
append(URLQueryItem(name: key, value: String(value)))
}
enum ArrayFormat {
case indices
case brackets
func format(for index: Int) -> String {
switch self {
case .indices:
return "[\(index)]"
case .brackets:
return "[]"
}
}
}
mutating func append(_ items: [String], forKey key: String, arrayFormat: ArrayFormat = .brackets) {
append(contentsOf: items
.enumerated()
.map {
URLQueryItem(name: "\(key)\(arrayFormat.format(for: $0.offset))",
value: $0.element)
})
}
mutating func append(_ items: [String: String], forKey key: String) {
append(contentsOf: items
.map {
URLQueryItem(name: "\(key)[\($0.key)]",
value: $0.value)
})
}
}
extension Date: LosslessStringConvertible {
public init?(_ description: String) {
guard let date = Formatters.dateIfPossible(fromISO8601: description) else {
return nil
}
self = date
}
}
extension UUID: LosslessStringConvertible {
public init?(_ description: String) {
guard let id = UUID(uuidString: description) else {
return nil
}
self = id
}
}

View File

@ -0,0 +1,196 @@
//
// BuildkiteTests.swift
//
//
// Created by Aaron Sky on 3/22/20.
//
import Foundation
import XCTest
@testable import Buildkite
#if canImport(FoundationNetworking)
import FoundationNetworking
#endif
#if canImport(Combine)
import Combine
#endif
class BuildkiteTests: XCTestCase {
private struct TestData {
enum Case {
case success
case successNoContent
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 .successNoContent:
responses = [MockData.mockingSuccessNoContent(url: configuration.baseURL)]
case .badResponse:
responses = [MockData.mockingIncompatibleResponse(for: configuration.baseURL)]
case .unsuccessfulResponse:
responses = [MockData.mockingUnsuccessfulResponse(for: configuration.baseURL)]
case .noData:
responses = []
}
client = Buildkite(configuration: configuration,
transport: MockTransport(responses: responses))
}
}
}
// MARK: Closure-based Requests
extension BuildkiteTests {
func testClosureBasedRequest() throws {
let testData = try TestData(testCase: .success)
let expectation = XCTestExpectation()
testData.client.send(testData.resources.contentResource) { result in
do {
let response = try result.get()
XCTAssertEqual(testData.resources.content, response.content)
} 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.noContentResource) { _ in
expectation.fulfill()
}
wait(for: [expectation])
}
func testClosureBasedRequestInvalidResponse() throws {
let testData = try TestData(testCase: .badResponse)
let expectation = XCTestExpectation()
testData.client.send(testData.resources.contentResource) { result in
do {
_ = try result.get()
XCTFail("Expected to have failed with an error, but closure fulfilled normally")
} catch {
}
expectation.fulfill()
}
wait(for: [expectation])
}
func testClosureBasedRequestUnsuccessfulResponse() throws {
let testData = try TestData(testCase: .unsuccessfulResponse)
let expectation = XCTestExpectation()
testData.client.send(testData.resources.contentResource) { result in
defer {
expectation.fulfill()
}
guard let _ = try? result.get() else {
return
}
XCTFail("Expected to have failed with an error, but closure fulfilled normally")
}
wait(for: [expectation])
}
func testFailureFromTransport() throws {
let testData = try TestData(testCase: .noData)
let expectation = XCTestExpectation()
testData.client.send(testData.resources.contentResource) { result in
defer {
expectation.fulfill()
}
guard let _ = try? result.get() else {
return
}
XCTFail("Expected to have failed with an error, but closure fulfilled normally")
}
wait(for: [expectation])
}
}
// MARK: Combine-based Requests
#if canImport(Combine)
@available(iOS 13.0, macOS 10.15, *)
extension BuildkiteTests {
func testPublisherBasedRequest() throws {
let testData = try TestData(testCase: .success)
let expectation = XCTestExpectation()
var cancellables: Set<AnyCancellable> = []
testData.client.sendPublisher(testData.resources.contentResource)
.sink(receiveCompletion: {
if case let .failure(error) = $0 {
XCTFail(error.localizedDescription)
}
expectation.fulfill()
},
receiveValue: { XCTAssertEqual(testData.resources.content, $0.content) })
.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)
.sink(receiveCompletion: {
if case let .failure(error) = $0 {
XCTFail(error.localizedDescription)
}
expectation.fulfill()
}, receiveValue: { _ in })
.store(in: &cancellables)
wait(for: [expectation])
}
func testPublisherBasedRequestInvalidResponse() throws {
let testData = try TestData(testCase: .badResponse)
let expectation = XCTestExpectation()
var cancellables: Set<AnyCancellable> = []
testData.client.sendPublisher(testData.resources.contentResource)
.sink(receiveCompletion: {
if case .finished = $0 {
XCTFail("Expected to have failed with an error, but publisher fulfilled normally")
}
expectation.fulfill()
}, receiveValue: { _ in })
.store(in: &cancellables)
wait(for: [expectation])
}
func testPublisherBasedRequestUnsuccessfulResponse() throws {
let testData = try TestData(testCase: .unsuccessfulResponse)
let expectation = XCTestExpectation()
var cancellables: Set<AnyCancellable> = []
testData.client.sendPublisher(testData.resources.contentResource)
.sink(receiveCompletion: {
if case .finished = $0 {
XCTFail("Expected to have failed with an error, but publisher fulfilled normally")
}
expectation.fulfill()
}, receiveValue: { _ in })
.store(in: &cancellables)
wait(for: [expectation])
}
}
#endif

View File

@ -0,0 +1,21 @@
//
// StatusCodeTests.swift
//
//
// Created by Aaron Sky on 3/23/20.
//
import Foundation
import XCTest
@testable import Buildkite
class StatusCodeTests: XCTestCase {
func testFlag() {
XCTAssertTrue(StatusCode.ok.isSuccess)
XCTAssertTrue(StatusCode.created.isSuccess)
XCTAssertTrue(StatusCode.accepted.isSuccess)
XCTAssertFalse(StatusCode.paymentRequired.isSuccess)
XCTAssertFalse(StatusCode.unauthorized.isSuccess)
XCTAssertFalse(StatusCode.notFound.isSuccess)
}
}

View File

@ -0,0 +1,134 @@
//
// TransportTests.swift
//
//
// Created by Aaron Sky on 3/22/20.
//
import Foundation
import XCTest
@testable import Buildkite
#if canImport(FoundationNetworking)
import FoundationNetworking
#endif
#if canImport(Combine)
import Combine
#endif
class TransportTests: XCTestCase {
private func createSession(testCase: MockURLProtocol.Case = .success) -> URLSession {
switch testCase {
case .success:
MockURLProtocol.requestHandler = MockData.mockingSuccessNoContent
case .error:
MockURLProtocol.requestHandler = MockData.mockingError
}
let config = URLSessionConfiguration.ephemeral
config.protocolClasses = [MockURLProtocol.self]
return URLSession(configuration: config)
}
private class MockURLProtocol: URLProtocol {
enum Case {
case success
case error
}
typealias RequestHandler = (URLRequest) throws -> (Data, URLResponse)
static var requestHandler: RequestHandler?
override class func canInit(with request: URLRequest) -> Bool {
return true
}
override class func canonicalRequest(for request: URLRequest) -> URLRequest {
return request
}
override func startLoading() {
guard let handler = MockURLProtocol.requestHandler else {
return
}
do {
let (data, response) = try handler(request)
client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed)
client?.urlProtocol(self, didLoad: data)
client?.urlProtocolDidFinishLoading(self)
} catch {
client?.urlProtocol(self, didFailWithError: error)
}
}
override func stopLoading() {
}
}
}
// MARK: Closure-based Requests
extension TransportTests {
func testURLSessionSendClosureBasedRequest() {
let request = URLRequest(url: URL())
let expectation = XCTestExpectation()
createSession().send(request: request) {
if case let .failure(error) = $0 {
XCTFail(error.localizedDescription)
}
expectation.fulfill()
}
wait(for: [expectation])
}
func testURLSessionSendClosureBasedRequestFailure() {
let request = URLRequest(url: URL())
let expectation = XCTestExpectation()
createSession(testCase: .error).send(request: request) {
if case .success(_) = $0 {
XCTFail("Expected to have failed with an error, but closure fulfilled normally")
}
expectation.fulfill()
}
wait(for: [expectation])
}
}
// MARK: Combine-based Requests
#if canImport(Combine)
@available(iOS 13.0, macOS 10.15, *)
extension TransportTests {
func testURLSessionSendPublisherBasedRequest() {
let request = URLRequest(url: URL())
let expectation = XCTestExpectation()
var cancellables: Set<AnyCancellable> = []
createSession()
.sendPublisher(request: request)
.sink(receiveCompletion: {
if case let .failure(error) = $0 {
XCTFail(error.localizedDescription)
}
expectation.fulfill()
}, receiveValue: { _ in })
.store(in: &cancellables)
wait(for: [expectation])
}
func testURLSessionSendPublisherBasedRequestFailure() {
let request = URLRequest(url: URL())
let expectation = XCTestExpectation()
var cancellables: Set<AnyCancellable> = []
createSession(testCase: .error)
.sendPublisher(request: request)
.sink(receiveCompletion: {
if case .finished = $0 {
XCTFail("Expected to have failed with an error, but publisher fulfilled normally")
}
expectation.fulfill()
}, receiveValue: { _ in })
.store(in: &cancellables)
wait(for: [expectation])
}
}
#endif

View File

@ -0,0 +1,45 @@
//
// AccessTokensTests.swift
//
//
// Created by Aaron Sky on 5/4/20.
//
import XCTest
@testable import Buildkite
class AccessTokensTests: XCTestCase {
func testAccessTokenGet() throws {
let expected = AccessToken(uuid: UUID(), scopes: [])
let context = try MockContext(content: expected)
let expectation = XCTestExpectation()
context.client.send(AccessToken.Resources.Get()) { result in
do {
let response = try result.get()
XCTAssertEqual(expected, response.content)
} catch {
XCTFail(error.localizedDescription)
}
expectation.fulfill()
}
wait(for: [expectation])
}
func testAccessTokenDelete() throws {
let context = MockContext()
let expectation = XCTestExpectation()
context.client.send(AccessToken.Resources.Revoke()) { result in
do {
_ = try result.get()
} catch {
XCTFail(error.localizedDescription)
}
expectation.fulfill()
}
wait(for: [expectation])
}
}

View File

@ -0,0 +1,83 @@
//
// AgentsTests.swift
//
//
// Created by Aaron Sky on 5/4/20.
//
import XCTest
@testable import Buildkite
extension Agent {
init() {
self.init(id: UUID(),
url: URL(),
webUrl: URL(),
name: "jeffrey",
connectionState: "connected",
hostname: "jeffrey",
ipAddress: "192.168.1.1",
userAgent: "buildkite/host",
version: "3.20.0",
creator: User(),
createdAt: Date(timeIntervalSince1970: 1000),
job: Job(),
lastJobFinishedAt: nil,
priority: nil,
metaData: [])
}
}
class AgentsTests: XCTestCase {
func testAgentsList() throws {
let expected = [Agent(), Agent()]
let context = try MockContext(content: expected)
let expectation = XCTestExpectation()
context.client.send(Agent.Resources.List(organization: "buildkite")) { result in
do {
let response = try result.get()
XCTAssertEqual(expected, response.content)
} catch {
XCTFail(error.localizedDescription)
}
expectation.fulfill()
}
wait(for: [expectation])
}
func testAgentsGet() throws {
let expected = Agent()
let context = try MockContext(content: expected)
let expectation = XCTestExpectation()
context.client.send(Agent.Resources.Get(organization: "buildkite", agentId: UUID())) { result in
do {
let response = try result.get()
XCTAssertEqual(expected, response.content)
} catch {
XCTFail(error.localizedDescription)
}
expectation.fulfill()
}
wait(for: [expectation])
}
func testAgentsStop() throws {
let context = MockContext()
let expectation = XCTestExpectation()
context.client.send(Agent.Resources.Stop(organization: "buildkite", agentId: UUID(), force: true)) { result in
do {
_ = try result.get()
} catch {
XCTFail(error.localizedDescription)
}
expectation.fulfill()
}
wait(for: [expectation])
}
}

View File

@ -0,0 +1,40 @@
//
// AnnotationsTests.swift
//
//
// Created by Aaron Sky on 5/4/20.
//
import XCTest
@testable import Buildkite
extension Annotation {
init() {
self.init(id: UUID(),
context: "message",
style: .info,
bodyHtml: "<div></div>",
createdAt: Date(timeIntervalSince1970: 1000),
updatedAt: Date(timeIntervalSince1970: 1001))
}
}
class AnnotationsTests: XCTestCase {
func testAnnotationsList() throws {
let expected = [Annotation(), Annotation()]
let context = try MockContext(content: expected)
let expectation = XCTestExpectation()
context.client.send(Annotation.Resources.List(organization: "buildkite", pipeline: "my-pipeline", build: 1)) { result in
do {
let response = try result.get()
XCTAssertEqual(expected, response.content)
} catch {
XCTFail(error.localizedDescription)
}
expectation.fulfill()
}
wait(for: [expectation])
}
}

View File

@ -0,0 +1,113 @@
//
// ArtifactsTests.swift
//
//
// Created by Aaron Sky on 5/4/20.
//
import XCTest
@testable import Buildkite
extension Artifact {
init() {
self.init(id: UUID(),
jobId: UUID(),
url: URL(),
downloadUrl: URL(),
state: .new,
path: "",
dirname: "",
filename: "",
mimeType: "",
fileSize: 0,
sha1sum: "")
}
}
class ArtifactsTests: XCTestCase {
func testArtifactsListByBuild() throws {
let expected = [Artifact(), Artifact()]
let context = try MockContext(content: expected)
let expectation = XCTestExpectation()
context.client.send(Artifact.Resources.ListByBuild(organization: "buildkite", pipeline: "my-pipeline", build: 1)) { result in
do {
let response = try result.get()
XCTAssertEqual(expected, response.content)
} catch {
XCTFail(error.localizedDescription)
}
expectation.fulfill()
}
wait(for: [expectation])
}
func testArtifactsListByJob() throws {
let expected = [Artifact(), Artifact()]
let context = try MockContext(content: expected)
let expectation = XCTestExpectation()
context.client.send(Artifact.Resources.ListByJob(organization: "buildkite", pipeline: "my-pipeline", build: 1, jobId: UUID())) { result in
do {
let response = try result.get()
XCTAssertEqual(expected, response.content)
} catch {
XCTFail(error.localizedDescription)
}
expectation.fulfill()
}
wait(for: [expectation])
}
func testArtifactsGet() throws {
let expected = Artifact()
let context = try MockContext(content: expected)
let expectation = XCTestExpectation()
context.client.send(Artifact.Resources.Get(organization: "buildkite", pipeline: "my-pipeline", build: 1, jobId: UUID(), artifactId: UUID())) { result in
do {
let response = try result.get()
XCTAssertEqual(expected, response.content)
} catch {
XCTFail(error.localizedDescription)
}
expectation.fulfill()
}
wait(for: [expectation])
}
func testArtifactsDownload() throws {
let context = MockContext()
let expectation = XCTestExpectation()
context.client.send(Artifact.Resources.Download(organization: "buildkite", pipeline: "my-pipeline", build: 1, jobId: UUID(), artifactId: UUID())) { result in
do {
_ = try result.get()
} catch {
XCTFail(error.localizedDescription)
}
expectation.fulfill()
}
wait(for: [expectation])
}
func testArtifactsDelete() throws {
let context = MockContext()
let expectation = XCTestExpectation()
context.client.send(Artifact.Resources.Delete(organization: "buildkite", pipeline: "my-pipeline", build: 1, jobId: UUID(), artifactId: UUID())) { result in
do {
_ = try result.get()
} catch {
XCTFail(error.localizedDescription)
}
expectation.fulfill()
}
wait(for: [expectation])
}
}

View File

@ -0,0 +1,202 @@
//
// BuildsTests.swift
//
//
// Created by Aaron Sky on 5/4/20.
//
import XCTest
@testable import Buildkite
extension Build {
init() {
self.init(id: UUID(),
url: URL(),
webUrl: URL(),
number: 1,
state: "passed",
blocked: false,
message: "a commit",
commit: "HEAD",
branch: "master",
env: [:],
source: "webhook",
creator: User(),
jobs: [],
createdAt: Date(timeIntervalSince1970: 1000),
scheduledAt: Date(timeIntervalSince1970: 1000),
startedAt: Date(timeIntervalSince1970: 1000),
finishedAt: Date(timeIntervalSince1970: 1001),
metaData: [:],
pullRequest: [:],
pipeline: Pipeline())
}
}
class BuildsTests: XCTestCase {
func testBuildsListAllDefaultQuery() throws {
let expected = [Build(), Build()]
let context = try MockContext(content: expected)
let expectation = XCTestExpectation()
context.client.send(Build.Resources.ListAll()) { result in
do {
let response = try result.get()
XCTAssertEqual(expected, response.content)
} catch {
XCTFail(error.localizedDescription)
}
expectation.fulfill()
}
wait(for: [expectation])
}
func testBuildsListAllSpecializedQuery() throws {
let expected = [Build(), Build()]
let context = try MockContext(content: expected)
let resource = Build.Resources.ListAll(options: Build.Resources.QueryOptions(branches: ["master"],
commit: "HEAD",
createdFrom: Date(timeIntervalSince1970: 1000),
createdTo: Date(timeIntervalSince1970: 1000),
creator: UUID(),
finishedFrom: Date(timeIntervalSince1970: 1000),
includeRetriedJobs: true,
metadata: ["buildkite": "is cool"],
state: "passed"))
let expectation = XCTestExpectation()
context.client.send(resource) { result in
do {
let response = try result.get()
XCTAssertEqual(expected, response.content)
} catch {
XCTFail(error.localizedDescription)
}
expectation.fulfill()
}
wait(for: [expectation])
}
func testBuildsListForOrganization() throws {
let expected = [Build(), Build()]
let context = try MockContext(content: expected)
let expectation = XCTestExpectation()
context.client.send(Build.Resources.ListForOrganization(organization: "buildkite")) { result in
do {
let response = try result.get()
XCTAssertEqual(expected, response.content)
} catch {
XCTFail(error.localizedDescription)
}
expectation.fulfill()
}
wait(for: [expectation])
}
func testBuildsListForPipeline() throws {
let expected = [Build(), Build()]
let context = try MockContext(content: expected)
let expectation = XCTestExpectation()
context.client.send(Build.Resources.ListForPipeline(organization: "buildkite", pipeline: "my-pipeline")) { result in
do {
let response = try result.get()
XCTAssertEqual(expected, response.content)
} catch {
XCTFail(error.localizedDescription)
}
expectation.fulfill()
}
wait(for: [expectation])
}
func testBuildsGet() throws {
let expected = Build()
let context = try MockContext(content: expected)
let expectation = XCTestExpectation()
context.client.send(Build.Resources.Get(organization: "buildkite", pipeline: "my-pipeline", build: 1)) { result in
do {
let response = try result.get()
XCTAssertEqual(expected, response.content)
} catch {
XCTFail(error.localizedDescription)
}
expectation.fulfill()
}
wait(for: [expectation])
}
func testBuildsCreate() throws {
let expected = Build()
let context = try MockContext(content: expected)
let expectation = XCTestExpectation()
let body = Build.Resources.Create.Body(commit: "HEAD",
branch: "master",
author: nil,
cleanCheckout: nil,
env: nil,
ignorePipelineBranchFilters: nil,
message: nil,
metaData: nil,
pullRequestBaseBranch: nil,
pullRequestId: nil,
pullRequestRepository: nil)
context.client.send(Build.Resources.Create(organization: "buildkite", pipeline: "my-pipeline", body: body)) { result in
do {
let response = try result.get()
XCTAssertEqual(expected, response.content)
} catch {
XCTFail(error.localizedDescription)
}
expectation.fulfill()
}
wait(for: [expectation])
}
func testBuildsCancel() throws {
let expected = Build()
let context = try MockContext(content: expected)
let expectation = XCTestExpectation()
context.client.send(Build.Resources.Cancel(organization: "buildkite", pipeline: "my-pipeline", build: 1)) { result in
do {
let response = try result.get()
XCTAssertEqual(expected, response.content)
} catch {
XCTFail(error.localizedDescription)
}
expectation.fulfill()
}
wait(for: [expectation])
}
func testBuildsRebuild() throws {
let expected = Build()
let context = try MockContext(content: expected)
let expectation = XCTestExpectation()
context.client.send(Build.Resources.Rebuild(organization: "buildkite", pipeline: "my-pipeline", build: 1)) { result in
do {
let response = try result.get()
XCTAssertEqual(expected, response.content)
} catch {
XCTFail(error.localizedDescription)
}
expectation.fulfill()
}
wait(for: [expectation])
}
}

View File

@ -0,0 +1,36 @@
//
// EmojisTests.swift
//
//
// Created by Aaron Sky on 5/4/20.
//
import XCTest
@testable import Buildkite
extension Emoji {
init() {
self.init(name: "jeff",
url: URL())
}
}
class EmojisTests: XCTestCase {
func testEmojisList() throws {
let expected = [Emoji(), Emoji()]
let context = try MockContext(content: expected)
let expectation = XCTestExpectation()
context.client.send(Emoji.Resources.List(organization: "buildkite")) { result in
do {
let response = try result.get()
XCTAssertEqual(expected, response.content)
} catch {
XCTFail(error.localizedDescription)
}
expectation.fulfill()
}
wait(for: [expectation])
}
}

View File

@ -0,0 +1,131 @@
//
// JobsTests.swift
//
//
// Created by Aaron Sky on 5/4/20.
//
import XCTest
@testable import Buildkite
extension Job {
init() {
self.init(id: UUID(),
type: "script",
name: "📦",
stepKey: "package",
agentQueryRules: [],
state: "finished",
buildUrl: URL(),
webUrl: URL(),
logUrl: URL(),
rawLogUrl: URL(),
artifactsUrl: URL(),
command: "echo 1",
softFailed: false,
exitStatus: 0,
artifactPaths: "",
agent: nil,
createdAt: Date(timeIntervalSince1970: 1000),
scheduledAt: Date(timeIntervalSince1970: 1000),
runnableAt: nil,
startedAt: nil,
finishedAt: nil,
retried: false,
retriedInJobId: UUID(),
retriesCount: 0,
parallelGroupIndex: nil,
parallelGroupTotal: nil)
}
}
class JobsTests: XCTestCase {
func testJobsRetry() throws {
let expected = Job()
let context = try MockContext(content: expected)
let expectation = XCTestExpectation()
context.client.send(Job.Resources.Retry(organization: "buildkite", pipeline: "my-pipeline", build: 1, job: UUID())) { result in
do {
let response = try result.get()
XCTAssertEqual(expected, response.content)
} catch {
XCTFail(error.localizedDescription)
}
expectation.fulfill()
}
wait(for: [expectation])
}
func testJobsUnblock() throws {
let expected = Job()
let context = try MockContext(content: expected)
let body = Job.Resources.Unblock.Body()
let resource = Job.Resources.Unblock(organization: "buildkite", pipeline: "my-pipeline", build: 1, job: UUID(), body: body)
let expectation = XCTestExpectation()
context.client.send(resource) { result in
do {
let response = try result.get()
XCTAssertEqual(expected, response.content)
} catch {
XCTFail(error.localizedDescription)
}
expectation.fulfill()
}
wait(for: [expectation])
}
func testJobsLogOutput() throws {
let context = MockContext()
let expectation = XCTestExpectation()
context.client.send(Job.Resources.LogOutput(organization: "buildkite", pipeline: "my-pipeline", build: 1, job: UUID())) { result in
do {
_ = try result.get()
} catch {
XCTFail(error.localizedDescription)
}
expectation.fulfill()
}
wait(for: [expectation])
}
func testJobsDeleteLogOutput() throws {
let context = MockContext()
let expectation = XCTestExpectation()
context.client.send(Job.Resources.DeleteLogOutput(organization: "buildkite", pipeline: "my-pipeline", build: 1, job: UUID())) { result in
do {
_ = try result.get()
} catch {
XCTFail(error.localizedDescription)
}
expectation.fulfill()
}
wait(for: [expectation])
}
func testJobsEnvironmentVariables() throws {
let expected = Job.EnvironmentVariables(env: [:])
let context = try MockContext(content: expected)
let expectation = XCTestExpectation()
context.client.send(Job.Resources.EnvironmentVariables(organization: "buildkite", pipeline: "my-pipeline", build: 1, job: UUID())) { result in
do {
let response = try result.get()
XCTAssertEqual(expected, response.content)
} catch {
XCTFail(error.localizedDescription)
}
expectation.fulfill()
}
wait(for: [expectation])
}
}

View File

@ -0,0 +1,61 @@
//
// OrganizationsTests.swift
//
//
// Created by Aaron Sky on 5/4/20.
//
import XCTest
@testable import Buildkite
extension Organization {
init() {
self.init(id: UUID(),
url: URL(),
webUrl: URL(),
name: "Buildkite",
slug: "buildkite",
pipelinesUrl: URL(),
agentsUrl: URL(),
emojisUrl: URL(),
createdAt: URL())
}
}
class OrganizationsTests: XCTestCase {
func testOrganizationsList() throws {
let expected = [Organization()]
let context = try MockContext(content: expected)
let expectation = XCTestExpectation()
context.client.send(Organization.Resources.List()) { result in
do {
let response = try result.get()
XCTAssertEqual(expected, response.content)
} catch {
XCTFail(error.localizedDescription)
}
expectation.fulfill()
}
wait(for: [expectation])
}
func testOrganizationsGet() throws {
let expected = Organization()
let context = try MockContext(content: expected)
let expectation = XCTestExpectation()
context.client.send(Organization.Resources.Get(organization: "buildkite")) { result in
do {
let response = try result.get()
XCTAssertEqual(expected, response.content)
} catch {
XCTFail(error.localizedDescription)
}
expectation.fulfill()
}
wait(for: [expectation])
}
}

View File

@ -0,0 +1,192 @@
//
// PipelinesTests.swift
//
//
// Created by Aaron Sky on 5/4/20.
//
import XCTest
@testable import Buildkite
extension Pipeline {
init(steps: [Step] = []) {
self.init(id: UUID(),
url: URL(),
webUrl: URL(),
name: "My Pipeline",
slug: "my-pipeline",
repository: "git@github.com:buildkite/agent.git",
branchConfiguration: nil,
defaultBranch: "master",
provider: Provider(id: "github",
webhookUrl: URL(),
settings: Provider.Settings(repository: nil,
buildPullRequests: nil,
pullRequestBranchFilterEnabled: nil,
pullRequestBranchFilterConfiguration: nil,
skipPullRequestBuildsForExistingCommits: nil,
buildTags: nil,
publishCommitStatus: nil,
publishCommitStatusPerStep: nil,
triggerMode: nil,
filterEnabled: nil,
filterCondition: nil,
buildPullRequestForks: nil,
prefixPullRequestForkBranchNames: nil,
separatePullRequestStatuses: nil,
publishBlockedAsPending: nil)),
skipQueuedBranchBuilds: false,
skipQueuedBranchBuildsFilter: nil,
cancelRunningBranchBuilds: false,
cancelRunningBranchBuildsFilter: nil,
buildsUrl: URL(),
badgeUrl: URL(),
createdAt: Date(timeIntervalSince1970: 1000),
scheduledBuildsCount: 0,
runningBuildsCount: 0,
scheduledJobsCount: 0,
runningJobsCount: 0,
waitingJobsCount: 0,
visibility: "private",
steps: steps,
env: [:])
}
}
class PipelinesTests: XCTestCase {
func testPipelinesList() throws {
let expected = [Pipeline(), Pipeline()]
let context = try MockContext(content: expected)
let expectation = XCTestExpectation()
context.client.send(Pipeline.Resources.List(organization: "buildkite", pipeline: "my-pipeline")) { result in
do {
let response = try result.get()
XCTAssertEqual(expected, response.content)
} catch {
XCTFail(error.localizedDescription)
}
expectation.fulfill()
}
wait(for: [expectation])
}
func testPipelinesGet() throws {
let expected = Pipeline()
let context = try MockContext(content: expected)
let expectation = XCTestExpectation()
context.client.send(Pipeline.Resources.Get(organization: "buildkite", pipeline: "my-pipeline")) { result in
do {
let response = try result.get()
XCTAssertEqual(expected, response.content)
} catch {
XCTFail(error.localizedDescription)
}
expectation.fulfill()
}
wait(for: [expectation])
}
func testPipelinesCreate() throws {
let steps: [Pipeline.Step] = [
.script(Pipeline.Step.Command(name: "📦",
command: "echo true",
label: "📦",
artifactPaths: "*",
branchConfiguration: nil,
env: [:],
timeoutInMinutes: nil,
agentQueryRules: [],
async: nil,
concurrency: nil,
parallelism: nil)),
.waiter(Pipeline.Step.Wait(label: "wait",
continueAfterFailure: true)),
.manual(Pipeline.Step.Block(label: "manual")),
.trigger(Pipeline.Step.Trigger(triggerProjectSlug: "my-other-pipeline",
label: "trigger",
triggerCommit: nil,
triggerBranch: nil,
triggerAsync: nil))
]
let expected = Pipeline(steps: steps)
let context = try MockContext(content: expected)
let resource = Pipeline.Resources.Create(organization: "buildkite",
body: Pipeline.Resources.Create.Body(name: "My Pipeline",
repository: URL(),
steps: steps,
branchConfiguration: nil,
cancelRunningBranchBuilds: nil,
cancelRunningBranchBuildsFilter: nil,
defaultBranch: nil,
description: nil,
env: nil,
providerSettings: nil,
skipQueuedBranchBuilds: nil,
skipQueuedBranchBuildsFilter: nil,
teamUUIDs: nil))
let expectation = XCTestExpectation()
context.client.send(resource) { result in
do {
_ = try result.get()
} catch {
XCTFail(error.localizedDescription)
}
expectation.fulfill()
}
wait(for: [expectation])
}
func testPipelinesUpdate() throws {
let expected = Pipeline()
let context = try MockContext(content: expected)
let resource = Pipeline.Resources.Update(organization: "buildkite",
pipeline: "my-pipeline",
body: Pipeline.Resources.Update.Body(branchConfiguration: nil,
cancelRunningBranchBuilds: nil,
cancelRunningBranchBuildsFilter: nil,
defaultBranch: nil,
description: nil,
env: nil,
name: nil,
providerSettings: nil,
repository: nil,
steps: nil,
skipQueuedBranchBuilds: nil,
skipQueuedBranchBuildsFilter: nil,
visibility: nil))
let expectation = XCTestExpectation()
context.client.send(resource) { result in
do {
_ = try result.get()
} catch {
XCTFail(error.localizedDescription)
}
expectation.fulfill()
}
wait(for: [expectation])
}
func testPipelinesDelete() throws {
let context = MockContext()
let expectation = XCTestExpectation()
context.client.send(Pipeline.Resources.Delete(organization: "buildkite", pipeline: "my-pipeline")) { result in
do {
_ = try result.get()
} catch {
XCTFail(error.localizedDescription)
}
expectation.fulfill()
}
wait(for: [expectation])
}
}

View File

@ -0,0 +1,39 @@
//
// UsersTests.swift
//
//
// Created by Aaron Sky on 5/4/20.
//
import XCTest
@testable import Buildkite
extension User {
init() {
self.init(id: UUID(),
name: "Jeff",
email: "jeff@buildkite.com",
avatarUrl: URL(),
createdAt: Date(timeIntervalSince1970: 1000))
}
}
class UsersTests: XCTestCase {
func testUserMe() throws {
let expected = User()
let context = try MockContext(content: expected)
let expectation = XCTestExpectation()
context.client.send(User.Resources.Me()) { result in
do {
let response = try result.get()
XCTAssertEqual(expected, response.content)
} catch {
XCTFail(error.localizedDescription)
}
expectation.fulfill()
}
wait(for: [expectation])
}
}

View File

@ -0,0 +1,34 @@
//
// MockContext.swift
//
//
// Created by Aaron Sky on 5/4/20.
//
import Foundation
import Buildkite
struct MockContext {
var client: Buildkite
var resources = MockResources()
init() {
let configuration = Configuration.default
self.init(configuration: configuration, responses: [
MockData.mockingSuccessNoContent(url: configuration.baseURL)
])
}
init<Content: Codable>(content: Content) throws {
let configuration = Configuration.default
try self.init(configuration: configuration, responses: [
MockData.mockingSuccess(with: content, url: configuration.baseURL)
])
}
private init(configuration: Configuration, responses: [(Data, URLResponse)]) {
let transport = MockTransport(responses: responses)
client = Buildkite(configuration: configuration,
transport: transport)
}
}

View File

@ -0,0 +1,86 @@
//
// MockData.swift
//
//
// Created by Aaron Sky on 3/22/20.
//
import Foundation
@testable import Buildkite
#if canImport(FoundationNetworking)
import FoundationNetworking
#endif
struct MockResources {
struct HasContent: Resource, HasResponseBody {
struct Content: Codable, Equatable {
var name: String
var age: Int
}
let path = "mock"
}
var contentResource = HasContent()
var content = HasContent.Content(name: "Jeff", age: 35)
struct NoContent: Resource {
typealias Content = Void
let path = "mock"
}
var noContentResource = NoContent()
}
enum MockData {
static let encoder: JSONEncoder = {
let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .iso8601
encoder.keyEncodingStrategy = .convertToSnakeCase
return encoder
}()
}
extension MockData {
static func mockingSuccess<Content: Codable>(with content: Content, url: URL) throws -> (Data, URLResponse) {
let data = try encoder.encode(content)
return (data, urlResponse(for: url, status: .ok))
}
static func mockingSuccessNoContent(url: URL) -> (Data, URLResponse) {
return (Data(), urlResponse(for: url, status: .ok))
}
static func mockingIncompatibleResponse(for url: URL) -> (Data, URLResponse) {
return (Data(), urlResponse(for: url, rawStatus: -128))
}
static func mockingUnsuccessfulResponse(for url: URL) -> (Data, URLResponse) {
return (Data(), urlResponse(for: url, status: .notFound))
}
static func mockingSuccessNoContent(for request: URLRequest) throws -> (Data, URLResponse) {
mockingSuccessNoContent(url: request.url!)
}
static func mockingError(for request: URLRequest) throws -> (Data, URLResponse) {
throw URLError(.notConnectedToInternet)
}
private static func urlResponse(for url: URL, status: StatusCode) -> URLResponse {
urlResponse(for: url, rawStatus: status.rawValue)
}
private static func urlResponse(for url: URL, rawStatus status: Int) -> URLResponse {
HTTPURLResponse(url: url,
statusCode: status,
httpVersion: "HTTP/1.1",
headerFields: [:])!
}
}
// MARK: Response Mocks
extension MockData {
}

View File

@ -0,0 +1,58 @@
//
// MockTransport.swift
//
//
// Created by Aaron Sky on 3/22/20.
//
import Foundation
import Buildkite
#if canImport(FoundationNetworking)
import FoundationNetworking
#endif
#if canImport(Combine)
import Combine
#endif
final class MockTransport {
enum Error: Swift.Error {
case tooManyRequests
}
var history: [URLRequest] = []
var responses: [Transport.Output]
init(responses: [Transport.Output]) {
self.responses = responses
}
}
extension MockTransport: Transport {
func send(request: URLRequest, completion: @escaping (Result<Output, Swift.Error>) -> Void) {
history.append(request)
guard !responses.isEmpty else {
completion(.failure(MockTransport.Error.tooManyRequests))
return
}
completion(.success(responses.removeFirst()))
}
#if canImport(Combine)
@available(iOS 13.0, macOS 10.15, *)
func sendPublisher(request: URLRequest) -> AnyPublisher<Output, Swift.Error> {
history.append(request)
return Future { [weak self] promise in
guard let self = self else {
return
}
guard !self.responses.isEmpty else {
promise(.failure(MockTransport.Error.tooManyRequests))
return
}
promise(.success(self.responses.removeFirst()))
}.eraseToAnyPublisher()
}
#endif
}

View File

@ -0,0 +1,14 @@
//
// URL+Buildkite.swift
//
//
// Created by Aaron Sky on 5/5/20.
//
import Foundation
extension URL {
init() {
self.init(string: "https://www.buildkite.com")!
}
}

View File

@ -0,0 +1,19 @@
//
// Extensions.swift
//
//
// Created by Aaron Sky on 3/24/20.
//
import Foundation
import XCTest
enum Constants {
fileprivate static let asyncTestTimeout = 1.0
}
extension XCTestCase {
func wait(for expectations: [XCTestExpectation]) {
wait(for: expectations, timeout: Constants.asyncTestTimeout)
}
}

View File

@ -0,0 +1,172 @@
#if !canImport(ObjectiveC)
import XCTest
extension AccessTokensTests {
// DO NOT MODIFY: This is autogenerated, use:
// `swift test --generate-linuxmain`
// to regenerate.
static let __allTests__AccessTokensTests = [
("testAccessTokenDelete", testAccessTokenDelete),
("testAccessTokenGet", testAccessTokenGet),
]
}
extension AgentsTests {
// DO NOT MODIFY: This is autogenerated, use:
// `swift test --generate-linuxmain`
// to regenerate.
static let __allTests__AgentsTests = [
("testAgentsGet", testAgentsGet),
("testAgentsList", testAgentsList),
("testAgentsStop", testAgentsStop),
]
}
extension AnnotationsTests {
// DO NOT MODIFY: This is autogenerated, use:
// `swift test --generate-linuxmain`
// to regenerate.
static let __allTests__AnnotationsTests = [
("testAnnotationsList", testAnnotationsList),
]
}
extension ArtifactsTests {
// DO NOT MODIFY: This is autogenerated, use:
// `swift test --generate-linuxmain`
// to regenerate.
static let __allTests__ArtifactsTests = [
("testArtifactsDelete", testArtifactsDelete),
("testArtifactsDownload", testArtifactsDownload),
("testArtifactsGet", testArtifactsGet),
("testArtifactsListByBuild", testArtifactsListByBuild),
("testArtifactsListByJob", testArtifactsListByJob),
]
}
extension BuildkiteTests {
// DO NOT MODIFY: This is autogenerated, use:
// `swift test --generate-linuxmain`
// to regenerate.
static let __allTests__BuildkiteTests = [
("testClosureBasedRequest", testClosureBasedRequest),
("testClosureBasedRequestInvalidResponse", testClosureBasedRequestInvalidResponse),
("testClosureBasedRequestNoContent", testClosureBasedRequestNoContent),
("testClosureBasedRequestUnsuccessfulResponse", testClosureBasedRequestUnsuccessfulResponse),
("testFailureFromTransport", testFailureFromTransport),
("testPublisherBasedRequest", testPublisherBasedRequest),
("testPublisherBasedRequestInvalidResponse", testPublisherBasedRequestInvalidResponse),
("testPublisherBasedRequestNoContent", testPublisherBasedRequestNoContent),
("testPublisherBasedRequestUnsuccessfulResponse", testPublisherBasedRequestUnsuccessfulResponse),
]
}
extension BuildsTests {
// DO NOT MODIFY: This is autogenerated, use:
// `swift test --generate-linuxmain`
// to regenerate.
static let __allTests__BuildsTests = [
("testBuildsCancel", testBuildsCancel),
("testBuildsCreate", testBuildsCreate),
("testBuildsGet", testBuildsGet),
("testBuildsListAllDefaultQuery", testBuildsListAllDefaultQuery),
("testBuildsListAllSpecializedQuery", testBuildsListAllSpecializedQuery),
("testBuildsListForOrganization", testBuildsListForOrganization),
("testBuildsListForPipeline", testBuildsListForPipeline),
("testBuildsRebuild", testBuildsRebuild),
]
}
extension EmojisTests {
// DO NOT MODIFY: This is autogenerated, use:
// `swift test --generate-linuxmain`
// to regenerate.
static let __allTests__EmojisTests = [
("testEmojisList", testEmojisList),
]
}
extension JobsTests {
// DO NOT MODIFY: This is autogenerated, use:
// `swift test --generate-linuxmain`
// to regenerate.
static let __allTests__JobsTests = [
("testJobsDeleteLogOutput", testJobsDeleteLogOutput),
("testJobsEnvironmentVariables", testJobsEnvironmentVariables),
("testJobsLogOutput", testJobsLogOutput),
("testJobsRetry", testJobsRetry),
("testJobsUnblock", testJobsUnblock),
]
}
extension OrganizationsTests {
// DO NOT MODIFY: This is autogenerated, use:
// `swift test --generate-linuxmain`
// to regenerate.
static let __allTests__OrganizationsTests = [
("testOrganizationsGet", testOrganizationsGet),
("testOrganizationsList", testOrganizationsList),
]
}
extension PipelinesTests {
// DO NOT MODIFY: This is autogenerated, use:
// `swift test --generate-linuxmain`
// to regenerate.
static let __allTests__PipelinesTests = [
("testPipelinesCreate", testPipelinesCreate),
("testPipelinesDelete", testPipelinesDelete),
("testPipelinesGet", testPipelinesGet),
("testPipelinesList", testPipelinesList),
("testPipelinesUpdate", testPipelinesUpdate),
]
}
extension StatusCodeTests {
// DO NOT MODIFY: This is autogenerated, use:
// `swift test --generate-linuxmain`
// to regenerate.
static let __allTests__StatusCodeTests = [
("testFlag", testFlag),
]
}
extension TransportTests {
// DO NOT MODIFY: This is autogenerated, use:
// `swift test --generate-linuxmain`
// to regenerate.
static let __allTests__TransportTests = [
("testURLSessionSendClosureBasedRequest", testURLSessionSendClosureBasedRequest),
("testURLSessionSendClosureBasedRequestFailure", testURLSessionSendClosureBasedRequestFailure),
("testURLSessionSendPublisherBasedRequest", testURLSessionSendPublisherBasedRequest),
("testURLSessionSendPublisherBasedRequestFailure", testURLSessionSendPublisherBasedRequestFailure),
]
}
extension UsersTests {
// DO NOT MODIFY: This is autogenerated, use:
// `swift test --generate-linuxmain`
// to regenerate.
static let __allTests__UsersTests = [
("testUserMe", testUserMe),
]
}
public func __allTests() -> [XCTestCaseEntry] {
return [
testCase(AccessTokensTests.__allTests__AccessTokensTests),
testCase(AgentsTests.__allTests__AgentsTests),
testCase(AnnotationsTests.__allTests__AnnotationsTests),
testCase(ArtifactsTests.__allTests__ArtifactsTests),
testCase(BuildkiteTests.__allTests__BuildkiteTests),
testCase(BuildsTests.__allTests__BuildsTests),
testCase(EmojisTests.__allTests__EmojisTests),
testCase(JobsTests.__allTests__JobsTests),
testCase(OrganizationsTests.__allTests__OrganizationsTests),
testCase(PipelinesTests.__allTests__PipelinesTests),
testCase(StatusCodeTests.__allTests__StatusCodeTests),
testCase(TransportTests.__allTests__TransportTests),
testCase(UsersTests.__allTests__UsersTests),
]
}
#endif

8
Tests/LinuxMain.swift Normal file
View File

@ -0,0 +1,8 @@
import XCTest
import BuildkiteTests
var tests = [XCTestCaseEntry]()
tests += BuildkiteTests.__allTests()
XCTMain(tests)