diff --git a/.gitignore b/.gitignore index fb8980b..10a9c83 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ # ignore xcode project for Swift Package *.xcodeproj +.swiftpm ## Build generated build/ @@ -70,4 +71,4 @@ fastlane/screenshots fastlane/test_output -.DS_Store \ No newline at end of file +.DS_Store diff --git a/Dockerfile b/Dockerfile index c9ba3f9..173a8f8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM swift:4.1 +FROM swift:5.1 MAINTAINER Christoph Pageler @@ -8,4 +8,4 @@ COPY . ./ RUN swift package resolve RUN swift build -CMD swift test \ No newline at end of file +CMD swift test diff --git a/Package.resolved b/Package.resolved index 39b30e4..1dacd0d 100644 --- a/Package.resolved +++ b/Package.resolved @@ -2,57 +2,57 @@ "object": { "pins": [ { - "package": "Alamofire", - "repositoryURL": "https://github.com/Alamofire/Alamofire", + "package": "async-http-client", + "repositoryURL": "https://github.com/swift-server/async-http-client", "state": { "branch": null, - "revision": "2fb881a1702cb1976c36192aceb54dcedab6fdc2", - "version": "4.7.2" + "revision": "d84a1da59e0fa879cde27e01cdf9f079208cc0cc", + "version": "1.0.0-alpha.4" } }, { - "package": "Commander", - "repositoryURL": "https://github.com/kylef/Commander", + "package": "console-kit", + "repositoryURL": "https://github.com/vapor/console-kit", "state": { "branch": null, - "revision": "e5b50ad7b2e91eeb828393e89b03577b16be7db9", - "version": "0.8.0" + "revision": "00ce1abaac919593897b6c60cecdbd4d6290b1f9", + "version": "4.0.0-alpha.2.1" } }, { - "package": "Quack", - "repositoryURL": "https://github.com/cpageler93/Quack", + "package": "swift-log", + "repositoryURL": "https://github.com/apple/swift-log.git", "state": { "branch": null, - "revision": "0694f6a3c4f965e4bcc7414f5d168dca5c426e01", - "version": "1.6.0" + "revision": "e8aabbe95db22e064ad42f1a4a9f8982664c70ed", + "version": "1.1.1" } }, { - "package": "Result", - "repositoryURL": "https://github.com/antitypical/Result.git", + "package": "swift-nio", + "repositoryURL": "https://github.com/apple/swift-nio.git", "state": { "branch": null, - "revision": "7477584259bfce2560a19e06ad9f71db441fff11", - "version": "3.2.4" + "revision": "9201908b54578aa33f1d1826a5a680aca8991843", + "version": "2.8.0" } }, { - "package": "Spectre", - "repositoryURL": "https://github.com/kylef/Spectre.git", + "package": "swift-nio-ssl", + "repositoryURL": "https://github.com/apple/swift-nio-ssl.git", "state": { "branch": null, - "revision": "e34d5687e1e9d865e3527dd58bc2f7464ef6d936", - "version": "0.8.0" + "revision": "f5dd7a60ff56f501ff7bf9be753e4b1875bfaf20", + "version": "2.4.0" } }, { - "package": "SwiftyJSON", - "repositoryURL": "https://github.com/IBM-Swift/SwiftyJSON.git", + "package": "SwiftyTextTable", + "repositoryURL": "https://github.com/scottrhoyt/SwiftyTextTable.git", "state": { "branch": null, - "revision": "7eb6c83f91c519c19e6a507d604c9176d5ae5b15", - "version": "17.0.1" + "revision": "c6df6cf533d120716bff38f8ff9885e1ce2a4ac3", + "version": "0.9.0" } } ] diff --git a/Package.swift b/Package.swift index efc3921..fb68500 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:4.0 +// swift-tools-version:5.1 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription @@ -7,15 +7,16 @@ let package = Package( name: "JiraSwift", products: [ .library(name: "JiraSwift", targets: ["JiraSwift"]), - .executable(name: "JiraSwiftCLI", targets: ["JiraSwiftCLI"]) + .executable(name: "jira", targets: ["JiraSwiftCLI"]) ], dependencies: [ - .package(url: "https://github.com/cpageler93/Quack", from: "1.6.0"), - .package(url: "https://github.com/kylef/Commander", from: "0.8.0") + .package(url: "https://github.com/swift-server/async-http-client", from: "1.0.0-alpha.4"), + .package(url: "https://github.com/vapor/console-kit", from: "4.0.0-alpha.2.1"), + .package(url: "https://github.com/scottrhoyt/SwiftyTextTable.git", from: "0.9.0") ], targets: [ - .target(name: "JiraSwift", dependencies: ["Quack"]), - .target(name: "JiraSwiftCLI", dependencies: ["JiraSwift", "Commander"]), + .target(name: "JiraSwift", dependencies: ["AsyncHTTPClient"]), + .target(name: "JiraSwiftCLI", dependencies: ["JiraSwift", "ConsoleKit", "SwiftyTextTable"]), .testTarget(name: "JiraSwiftTests", dependencies: ["JiraSwift"]) ] ) diff --git a/README.md b/README.md index d88f7c8..6848aee 100644 --- a/README.md +++ b/README.md @@ -1,50 +1,80 @@ # JiraSwift -![Platforms](https://img.shields.io/badge/Platforms-iOS|macOS|tvOS|watchOS|Linux-yellow.svg?style=flat) +![Swift](https://img.shields.io/badge/Swift-5.1-orange.svg?style=flat) +![Platforms](https://img.shields.io/badge/Platforms-iOS%20%7C%20macOS%20%7C%20tvOS%20%7C%20watchOS%20%7C%20Linux-lightgrey.svg?style=flat) +![Xcode](https://img.shields.io/badge/Xcode-11-blue.svg?style=flat) [![License](https://img.shields.io/badge/license-MIT-green.svg?style=flat)](https://github.com/cpageler93/JiraSwift/blob/master/LICENSE) -[![Twitter: @cpageler93](https://img.shields.io/badge/contact-@cpageler93-lightgrey.svg?style=flat)](https://twitter.com/cpageler93) +[![Twitter: @cpageler93](https://img.shields.io/badge/contact-@cpageler93-blue.svg?style=flat)](https://twitter.com/cpageler93) -Jira Client for Swift +`JiraSwift` is a HTTP client for Jira implemented in swift based on [swift-server/async-http-client](https://github.com/swift-server/async-http-client). +# Framework Usage -## Usage - - -### Framework - -#### JQL Search +## JQL Search ```swift -let jiraClient = Jira.Client(url: URL(string: "https://your_jira_url")!, - username: "your_username", - password: "your_password") -jiraClient.search(jql: "key in (XXX027-65, XXX038-3, XXX027-58)") { result in - switch result { - case .success(let result): - for issue in result.issues { - print("issue: \(issue.key)") - } - case .failure: - break +let client = Jira.Client(baseURL: "https://jira.tinyspeck.com", + username: "your_username", + password: "your_password") + +do { + let searchResult = try client.search(jql: "key in (XXX027-65, XXX038-3, XXX027-58)").wait() + for issue in searchResult.issues { + print("\(issue.key): \(issue.fields.assignee?.name ?? "NONE" )") } +} catch Jira.ClientError.jiraError(let error) { + print(error.errorMessages) +} catch { + print(error) } + ``` -### CLI +## Implemented Methods + +| Implemented | Method | Route | +|:----------- |:---------------- |:-------------------------- | +| ✅ | getMyself() | [`/rest/api/2/myself`](https://docs.atlassian.com/software/jira/docs/api/REST/8.4.1/#api/2/myself) | +| ✅ | search() | [`/rest/api/2/search`](https://docs.atlassian.com/software/jira/docs/api/REST/8.4.1/#api/2/search) | +| ✅ | getServerInfo() | [`/rest/api/2/serverInfo`](https://docs.atlassian.com/software/jira/docs/api/REST/8.4.1/#api/2/serverInfo) | +| ✅ | projects() | [`/rest/api/2/project`](https://docs.atlassian.com/software/jira/docs/api/REST/8.4.1/#api/2/project) | +| ✅ | projectTypes() | [`/rest/api/2/project/type`](https://docs.atlassian.com/software/jira/docs/api/REST/8.4.1/#api/2/project/type) | + +# Command Line Interface + +## Environment -#### JQL Search +You can either setup your environment ```shell # setup environment JIRA_URL=https://your_jira_url JIRA_USERNAME=your_username -JIRA_PASSWORd=your_password #or leave it blank and get prompted from JiraSwiftCLI +JIRA_PASSWORD=your_password -./JiraSwiftCLI search "key in (XYZ027-65, XYZ038-3, XYZ027-58)" +jira search --jql "key in (XYZ027-65, XYZ038-3, XYZ027-58)" ``` -Or pass all values as options to JiraSwiftCLI +Or pass all values as options to jira the command ```shell -.build/debug/JiraSwiftCLI search "key in (XYZ027-65, XYZ038-3, XYZ027-58)" --url "https://your_jira_url" --username "your_username" --password "your_password" -``` \ No newline at end of file +jira search --url "https://your_jira_url" \ + --username "your_username" \ + --password "your_password" \ + --jql "key in (XYZ027-65, XYZ038-3, XYZ027-58)" +``` + +## Commands + +| Command | Description | +| --------------- | -------------------------- | +| `search` | Search for Issues with JQL | +| `projects` | List all projects | +| `project-types` | List all project types | + + +# Contribute + +Feel free to add a missing REST API method or create an issue if you want me to implement it! + +[Jira REST API Documentation](https://docs.atlassian.com/software/jira/docs/api/REST/8.4.1) \ No newline at end of file diff --git a/Sources/JiraSwift/Codables/AvatarURLs.swift b/Sources/JiraSwift/Codables/AvatarURLs.swift new file mode 100644 index 0000000..d7ad4b0 --- /dev/null +++ b/Sources/JiraSwift/Codables/AvatarURLs.swift @@ -0,0 +1,27 @@ +// +// AvatarURLs.swift +// AsyncHTTPClient +// +// Created by Christoph Pageler on 30.09.19. +// + + +public extension Jira { + + class AvatarURLs: Codable { + + public let sixteen: String + public let twentyFour: String + public let thirtyTwo: String + public let fortyEight: String + + enum CodingKeys: String, CodingKey { + case sixteen = "16x16" + case twentyFour = "24x24" + case thirtyTwo = "32x32" + case fortyEight = "48x48" + } + + } + +} diff --git a/Sources/JiraSwift/Codables/Issue-Codable.swift b/Sources/JiraSwift/Codables/Issue-Codable.swift new file mode 100644 index 0000000..b838581 --- /dev/null +++ b/Sources/JiraSwift/Codables/Issue-Codable.swift @@ -0,0 +1,118 @@ +// +// JiraIssue.swift +// JiraSwift +// +// Created by Christoph Pageler on 17.11.17. +// + + +import Foundation + + +public extension Jira { + + class Issue: Codable { + + public let id: String + public let key: String + public let fields: Fields + + } + + + class Fields: Codable { + + public let summary: String? + public let project: Jira.Project? + public let status: Status? + public let description: String? + public let issuetype: IssueType? + public let components: [Component]? + public let assignee: Jira.User? + public let creator: Jira.User? + public let reporter: Jira.User? + public let lastViewed: Date? + public let created: Date? + public let updated: Date? + public let duedate: Date? + public let resolution: Resolution? + public let resolutiondate: Date? + public let timespent: Int? + public let timeestimate: Int? + public let watches: Watches? + public let versions: [Version]? + public let fixVersions: [Version]? + public let priority: Priority? + + } + + class Status: Codable { + + public let id: String + public let name: String + public let iconUrl: String + public let description: String + public let statusCategory: StatusCategory + + public class StatusCategory: Codable { + + public let id: Int + public let key: String + public let name: String + public let colorName: String + + } + } + + class Component: Codable { + + public let id: String + public let name: String + + } + + class IssueType: Codable { + + public let id: String + public let description: String + public let iconUrl: String + public let name: String + public let subtask: Bool + + } + + class Resolution: Codable { + + public let id: String + public let name: String + public let description: String + + } + + class Watches: Codable { + + public let watchCount: Int + public let isWatching: Bool + + } + + class Version: Codable { + + public let id: String + public let description: String + public let name: String + public let archived: Bool + public let released: Bool + + // TODO: Implement date parser without time + // public let releaseDate: Date? + + } + + class Priority: Codable { + + public let id: String + public let name: String + public let iconUrl: String + } +} diff --git a/Sources/JiraSwift/Codables/Project-Codable.swift b/Sources/JiraSwift/Codables/Project-Codable.swift new file mode 100644 index 0000000..e1f5fd9 --- /dev/null +++ b/Sources/JiraSwift/Codables/Project-Codable.swift @@ -0,0 +1,30 @@ +// +// JiraProject.swift +// JiraSwift +// +// Created by Christoph Pageler on 24.10.18. +// + + +public extension Jira { + + class Project: Codable { + + public let id: String + public let key: String + public let name: String + public let projectTypeKey: String + public let avatarUrls: AvatarURLs + + } + + class ProjectType: Codable { + + public let key: String + public let formattedKey: String + public let descriptionI18nKey: String + public let icon: String + public let color: String + } + +} diff --git a/Sources/JiraSwift/Codables/RemoteLink-Codable.swift b/Sources/JiraSwift/Codables/RemoteLink-Codable.swift new file mode 100644 index 0000000..64ff825 --- /dev/null +++ b/Sources/JiraSwift/Codables/RemoteLink-Codable.swift @@ -0,0 +1,18 @@ +// +// JiraRemoteLink.swift +// JiraSwiftPackageDescription +// +// Created by Christoph Pageler on 19.02.18. +// + + +public extension Jira { + + class RemoteLink: Decodable { + + let id: Int + let selfUrl: String + + } + +} diff --git a/Sources/JiraSwift/Codables/SearchRequest-Codable.swift b/Sources/JiraSwift/Codables/SearchRequest-Codable.swift new file mode 100644 index 0000000..0c514d4 --- /dev/null +++ b/Sources/JiraSwift/Codables/SearchRequest-Codable.swift @@ -0,0 +1,63 @@ +// +// JiraSearchRequest.swift +// AsyncHTTPClient +// +// Created by Christoph Pageler on 28.09.19. +// + + +import Foundation + + +public extension Jira { + + // https://docs.atlassian.com/DAC/javadoc/jira/reference/com/atlassian/jira/rest/v2/search/SearchRequestBean.html + struct SearchRequest: Codable { + + /// A JQL query string. + let jql: String + + /// The index of the first issue to return (0-based). + let startAt: Int + + /// The maximum number of issues to return (defaults to 50). + let maxResults: Int + + /// The list of fields to return for each issue. + let fields: [Field] + + ///The list of issue parameters to expand on each issue. + let expand: [String] + + /// Whether to validate the JQL query. + let validateQuery: Bool = true + + public enum Field: String, Codable { + + case summary + case project + case status + case description + case issuetype + case components + case assignee + case creator + case reporter + case lastViewed + case created + case updated + case duedate + case resolution + case resolutiondate + case timespent + case timeestimate + case watches + case versions + case fixVersions + case priority + + } + + } + +} diff --git a/Sources/JiraSwift/Codables/SearchResult-Codable.swift b/Sources/JiraSwift/Codables/SearchResult-Codable.swift new file mode 100644 index 0000000..c88170d --- /dev/null +++ b/Sources/JiraSwift/Codables/SearchResult-Codable.swift @@ -0,0 +1,21 @@ +// +// JiraSearchResult.swift +// JiraSwift +// +// Created by Christoph Pageler on 17.11.17. +// + + +public extension Jira { + + class SearchResult: Decodable { + + public let expand: String? + public let startAt: Int + public let maxResults: Int + public let total: Int + public let issues: [Issue] + + } + +} diff --git a/Sources/JiraSwift/Codables/ServerInfoResult-Codable.swift b/Sources/JiraSwift/Codables/ServerInfoResult-Codable.swift new file mode 100644 index 0000000..2c8cef8 --- /dev/null +++ b/Sources/JiraSwift/Codables/ServerInfoResult-Codable.swift @@ -0,0 +1,28 @@ +// +// Jira.swift +// JiraSwift +// +// Created by Robert Feldhus on 24.09.18. +// + + +import Foundation + + +public extension Jira { + + class ServerInfoResult: Decodable { + + public let baseUrl: String + public let version: String + public let versionNumbers: [Int] + public let deploymentType: String + public let buildNumber: Int + public let buildDate: Date + public let serverTime: Date + public let scmInfo: String + public let serverTitle: String + + } + +} diff --git a/Sources/JiraSwift/Codables/User-Codable.swift b/Sources/JiraSwift/Codables/User-Codable.swift new file mode 100644 index 0000000..19f1456 --- /dev/null +++ b/Sources/JiraSwift/Codables/User-Codable.swift @@ -0,0 +1,24 @@ +// +// JiraUser.swift +// JiraSwift +// +// Created by Christoph Pageler on 11.10.18. +// + + +public extension Jira { + + class User: Codable { + + public let accountId: String? + public let key: String + public let name: String + public let emailAddress: String? + public let displayName: String + public let active: Bool + public let timeZone: String + public let avatarUrls: AvatarURLs + + } + +} diff --git a/Sources/JiraSwift/JiraClient.swift b/Sources/JiraSwift/JiraClient.swift index cda675e..6dd8872 100644 --- a/Sources/JiraSwift/JiraClient.swift +++ b/Sources/JiraSwift/JiraClient.swift @@ -7,162 +7,109 @@ import Foundation -import Quack +import AsyncHTTPClient +import NIO +import NIOHTTP1 public extension Jira { - - public class Client: Quack.Client { - - private var authorizationHeaderValue: String - - private var defaultHeader: [String: String] { - return [ - "Authorization": authorizationHeaderValue, - "Content-Type": "application/json" - ] - } - - public init(url: URL, username: String, password: String) { - let base64Auth = "\(username):\(password)".data(using: .utf8)?.base64EncodedString() ?? "" - self.authorizationHeaderValue = "Basic \(base64Auth)" - super.init(url: url) - } - public init(url: URL, oAuthAccessToken: String) { - self.authorizationHeaderValue = "Bearer \(oAuthAccessToken)" - super.init(url: url) - } - - public func search(jql: String, - startAt: Int = 0, - maxResults: Int = 50, - fields: [String] = [], - fieldsByKeys: Bool = false) -> Quack.Result { - let body: [String: Any] = [ - "jql": jql, - "startAt": startAt, - "maxResults": maxResults, - "fields": fields, - "fieldsByKeys": fieldsByKeys - ] - - return respond(method: .post, - path: "/rest/api/2/search", - body: Quack.JSONBody(body), - headers: defaultHeader, - model: SearchResult.self, - requestModification: { request in - var request = request - request.encoding = .json - return request - }) - } - - public func search(jql: String, - startAt: Int = 0, - maxResults: Int = 50, - fields: [String] = [], - fieldsByKeys: Bool = false, - completion: @escaping (Quack.Result) -> ()) { - let body: [String: Any] = [ - "jql": jql, - "startAt": startAt, - "maxResults": maxResults, - "fields": fields, - "fieldsByKeys": fieldsByKeys - ] - - return respondAsync(method: .post, - path: "/rest/api/2/search", - body: Quack.JSONBody(body), - headers: defaultHeader, - model: SearchResult.self, - requestModification: { request in - var request = request - request.encoding = .json - return request - }, - completion: completion) - + /// Errors communicating with JIRA + enum ClientError: Swift.Error { + + case noBodyError(UInt) + case couldNotReadBody + case httpError(UInt) + case jiraError(Jira.JiraResponseError) + + } + + /// ➡ Class to communicate with JIRA + class Client { + + static var dateFormatter: DateFormatter { + let df = DateFormatter() + df.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ" + return df } - - public func updateRemoteLinkForIssue(withKey issueKey: String, - link: String, - title: String) -> Quack.Result { - let body: [String : Any] = [ - "application": [ - "type": "com.thepeaklab.jiraSwift", - "name": "Jira Swift Client" - ], - "relationship": "mentioned in", - "object": [ - "url": link, - "title": title - ] - ] - - return respond(method: .post, - path: "/rest/api/2/issue/\(issueKey)/remotelink", - body: Quack.JSONBody(body), - headers: defaultHeader, - model: RemoteLink.self) + + static var jsonDecoder: JSONDecoder { + let decoder = JSONDecoder() + decoder.dateDecodingStrategy = .formatted(Jira.Client.dateFormatter) + return decoder } - - public func updateRemoteLinkForIssue(withKey issueKey: String, - link: String, - title: String, - completion: @escaping (Quack.Result) -> ()) { - let body: [String : Any] = [ - "application": [ - "type": "com.thepeaklab.jiraSwift", - "name": "Jira Swift Client" - ], - "relationship": "mentioned in", - "object": [ - "url": link, - "title": title - ] - ] - - return respondAsync(method: .post, - path: "/rest/api/2/issue/\(issueKey)/remotelink", - body: Quack.JSONBody(body), - headers: defaultHeader, - model: RemoteLink.self, - completion: completion) + + static var jsonEncoder: JSONEncoder { + let decoder = JSONEncoder() + decoder.dateEncodingStrategy = .formatted(Jira.Client.dateFormatter) + return decoder } - public func getServerInfo() -> Quack.Result { - return respond(method: .get, - path: "/rest/api/2/serverInfo", - headers: defaultHeader, - model: ServerInfoResult.self) + /// The underlying URL for the instance of JIRA + internal var baseURL: String + + /// All headers, typically for authentication, to be added to each request + internal var headers = HTTPHeaders([("Content-Type", "application/json")]) + + /// The client powering all requests + internal var httpClient: HTTPClient + + public init(baseURL: String, + headers: [(String, String)], + eventLoopGroupProvider: HTTPClient.EventLoopGroupProvider = .createNew) { + for (name, value) in headers { + self.headers.add(name: name, value: value) + } + self.baseURL = baseURL + self.httpClient = HTTPClient(eventLoopGroupProvider: eventLoopGroupProvider) } - public func getServerInfo(completion: @escaping (Quack.Result) -> ()) { - return respondAsync(method: .get, - path: "/rest/api/2/serverInfo", - headers: defaultHeader, - model: ServerInfoResult.self, - completion: completion) + deinit { + try? httpClient.syncShutdown() } - public func getMyself() -> Quack.Result { - return respond(method: .get, - path: "/rest/api/2/myself", - headers: defaultHeader, - model: Myself.self) + public convenience init(baseURL: String, + username: String, + password: String, + eventLoopGroupProvider: HTTPClient.EventLoopGroupProvider = .createNew) { + let base64Auth = "\(username):\(password)".data(using: .utf8)?.base64EncodedString() ?? "" + self.init(baseURL: baseURL, + headers: [("Authorization", "Basic \(base64Auth)")], + eventLoopGroupProvider: eventLoopGroupProvider) } - public func getMyself(completion: @escaping (Quack.Result) -> ()) { - return respondAsync(method: .get, - path: "/rest/api/2/myself", - headers: defaultHeader, - model: Myself.self, - completion: completion) + func submit(request: HTTPClient.Request) -> EventLoopFuture { + return httpClient.execute(request: request).flatMapThrowing { response in + guard response.status.code < 400 else { + throw ClientError.httpError(response.status.code) + } + guard var body = response.body else { + throw ClientError.noBodyError(response.status.code) + } + guard let response = body.readBytes(length: body.readableBytes) else { + throw ClientError.couldNotReadBody + } + + //print(String(data: Data(response), encoding: .utf8)!.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)) // Debugging + + let decoder = JSONDecoder() + decoder.dateDecodingStrategy = .formatted(Jira.Client.dateFormatter) + + // We try to convert to the right response type, if this doesn't work, we look if we + // got an error message back from JIRA. If that doesn't appear to be the case, this + // was likely a parsing error, so we should surface the original issue. + do { + return try Client.jsonDecoder.decode(T.self, from: Data(response)) + } catch { + guard let jiraResponseError = try? Client.jsonDecoder.decode(Jira.JiraResponseError.self, + from: Data(response)) + else { + throw error + } + throw ClientError.jiraError(jiraResponseError) + } + } } - + } - } diff --git a/Sources/JiraSwift/JiraError.swift b/Sources/JiraSwift/JiraError.swift new file mode 100644 index 0000000..40c4bf7 --- /dev/null +++ b/Sources/JiraSwift/JiraError.swift @@ -0,0 +1,17 @@ +// +// JiraError.swift +// +// +// Created by Joe Smith on 9/25/19. +// + + +public extension Jira { + + class JiraResponseError: Decodable { + + public let errorMessages: [String] + + } + +} diff --git a/Sources/JiraSwift/JiraIssue.swift b/Sources/JiraSwift/JiraIssue.swift deleted file mode 100644 index bee41a5..0000000 --- a/Sources/JiraSwift/JiraIssue.swift +++ /dev/null @@ -1,82 +0,0 @@ -// -// JiraIssue.swift -// JiraSwift -// -// Created by Christoph Pageler on 17.11.17. -// - - -import Foundation -import Quack - - -public extension Jira { - - public class Issue: Quack.Model { - - public let id: String - public let key: String - public let fields: Fields - - public required init?(json: JSON) { - guard let idString = json["id"].string, - let keyString = json["key"].string, - let fields = Fields(json: json["fields"]) - else { - return nil - } - - self.id = idString - self.key = keyString - self.fields = fields - } - - } - -} - - -extension Jira.Issue { - - public class Fields: Quack.Model { - - public let summary: String? - public let project: Jira.Project - public let components: [Jira.Issue.Component] - - public required init?(json: JSON) { - guard let project = Jira.Project(json: json["project"]) else { return nil } - self.summary = json["summary"].string - self.project = project - self.components = json["components"].array?.compactMap { Component(json: $0) } ?? [] - } - - } - -} - - -extension Jira.Issue { - - public class Component: Quack.Model { - - public let id: String - public let name: String - public let selfURL: String - - public required init?(json: JSON) { - guard let idString = json.dictionary?["id"]?.string, - let nameString = json.dictionary?["name"]?.string, - let selfURL = json.dictionary?["self"]?.string - else { - return nil - } - - self.id = idString - self.name = nameString - self.selfURL = selfURL - } - - } - -} diff --git a/Sources/JiraSwift/JiraMyself.swift b/Sources/JiraSwift/JiraMyself.swift deleted file mode 100644 index 44e10b3..0000000 --- a/Sources/JiraSwift/JiraMyself.swift +++ /dev/null @@ -1,55 +0,0 @@ -// -// JiraMyself.swift -// JiraSwift -// -// Created by Christoph Pageler on 11.10.18. -// - - -import Foundation -import Quack - - -public extension Jira { - - public class Myself: Quack.Model { - - public let accountId: String - public let key: String - public let name: String - public let emailAddress: String - public let displayName: String - public let active: Bool - - public let avatarUrl16: String? - public let avatarUrl24: String? - public let avatarUrl32: String? - public let avatarUrl48: String? - - public required init?(json: JSON) { - guard let accountId = json["accountId"].string, - let key = json["key"].string, - let name = json["name"].string, - let emailAddress = json["emailAddress"].string, - let displayName = json["displayName"].string, - let active = json["active"].bool - else { - return nil - } - - self.accountId = accountId - self.key = key - self.name = name - self.emailAddress = emailAddress - self.displayName = displayName - self.active = active - - self.avatarUrl16 = json["avatarUrls"]["16x16"].string - self.avatarUrl24 = json["avatarUrls"]["24x24"].string - self.avatarUrl32 = json["avatarUrls"]["32x32"].string - self.avatarUrl48 = json["avatarUrls"]["48x48"].string - } - - } - -} diff --git a/Sources/JiraSwift/JiraProject.swift b/Sources/JiraSwift/JiraProject.swift deleted file mode 100644 index 37d8b1d..0000000 --- a/Sources/JiraSwift/JiraProject.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// JiraProject.swift -// JiraSwift -// -// Created by Christoph Pageler on 24.10.18. -// - - -import Foundation -import Quack - - -public extension Jira { - - public class Project: Quack.Model { - - public let id: String - public let key: String - public let name: String - - public required init?(json: JSON) { - guard let id = json["id"].string, - let key = json["key"].string, - let name = json["name"].string - else { - return nil - } - - self.id = id - self.key = key - self.name = name - } - - } - -} diff --git a/Sources/JiraSwift/JiraRemoteLink.swift b/Sources/JiraSwift/JiraRemoteLink.swift deleted file mode 100644 index 5be95dd..0000000 --- a/Sources/JiraSwift/JiraRemoteLink.swift +++ /dev/null @@ -1,33 +0,0 @@ -// -// JiraRemoteLink.swift -// JiraSwiftPackageDescription -// -// Created by Christoph Pageler on 19.02.18. -// - -import Foundation -import Quack - - -public extension Jira { - - public class RemoteLink: Quack.Model { - - let id: Int - let selfUrl: String - - required public init?(json: JSON) { - guard let id = json["id"].int, - let selfUrl = json["self"].string - else { - return nil - } - - self.id = id - self.selfUrl = selfUrl - } - - } - - -} diff --git a/Sources/JiraSwift/JiraSearchResult.swift b/Sources/JiraSwift/JiraSearchResult.swift deleted file mode 100644 index 9990efe..0000000 --- a/Sources/JiraSwift/JiraSearchResult.swift +++ /dev/null @@ -1,39 +0,0 @@ -// -// JiraSearchResult.swift -// JiraSwift -// -// Created by Christoph Pageler on 17.11.17. -// - -import Foundation -import Quack - - -public extension Jira { - - public class SearchResult: Quack.Model { - - public let expand: String? - public let startAt: Int - public let maxResults: Int - public let total: Int - public let issues: [Issue] - - public required init?(json: JSON) { - guard let startAt = json["startAt"].int, - let maxResults = json["maxResults"].int, - let total = json["total"].int - else { - return nil - } - self.expand = json["expand"].string - self.startAt = startAt - self.maxResults = maxResults - self.total = total - self.issues = json["issues"].array?.compactMap { Issue(json: $0) } ?? [] - } - - } - - -} diff --git a/Sources/JiraSwift/JiraServerInfoResult.swift b/Sources/JiraSwift/JiraServerInfoResult.swift deleted file mode 100644 index afb3cc1..0000000 --- a/Sources/JiraSwift/JiraServerInfoResult.swift +++ /dev/null @@ -1,61 +0,0 @@ -// -// Jira.swift -// JiraSwift -// -// Created by Robert Feldhus on 24.09.18. -// - -import Foundation -import Quack - - -public extension Jira { - - public class ServerInfoResult: Quack.Model { - - public static var dateFormatter: DateFormatter { - let df = DateFormatter() - df.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ" - return df - } - - public let baseUrl: String - public let version: String - public let versionNumbers: [Int] - public let deploymentType: String - public let buildNumber: Int - public let buildDate: Date - public let serverTime: Date - public let scmInfo: String - public let serverTitle: String - - public required init?(json: JSON) { - guard let baseUrl = json["baseUrl"].string, - let version = json["version"].string, - let versionNumbers = json["versionNumbers"].array, - let deploymentType = json["deploymentType"].string, - let buildNumber = json["buildNumber"].int, - let buildDateString = json["buildDate"].string, - let buildDate = ServerInfoResult.dateFormatter.date(from: buildDateString), - let serverTimeString = json["serverTime"].string, - let serverTime = ServerInfoResult.dateFormatter.date(from: serverTimeString), - let scmInfo = json["scmInfo"].string, - let serverTitle = json["serverTitle"].string - else { - return nil - } - self.baseUrl = baseUrl - self.version = version - self.versionNumbers = versionNumbers.compactMap({ $0.int }) - self.deploymentType = deploymentType - self.buildNumber = buildNumber - self.buildDate = buildDate - self.serverTime = serverTime - self.scmInfo = scmInfo - self.serverTitle = serverTitle - } - - } - - -} diff --git a/Sources/JiraSwift/Routes/Myself-Routes.swift b/Sources/JiraSwift/Routes/Myself-Routes.swift new file mode 100644 index 0000000..eef2243 --- /dev/null +++ b/Sources/JiraSwift/Routes/Myself-Routes.swift @@ -0,0 +1,29 @@ +// +// Myself-Routes.swift +// AsyncHTTPClient +// +// Created by Christoph Pageler on 28.09.19. +// + + +import AsyncHTTPClient +import NIO + + +public extension Jira.Client { + + func getMyself() -> EventLoopFuture { + let request: HTTPClient.Request + do { + request = try HTTPClient.Request(url: "\(self.baseURL)/rest/api/2/myself", + method: .GET, + headers: self.headers, + body: nil) + } catch { + return self.httpClient.eventLoopGroup.next().makeFailedFuture(error) + } + let user: EventLoopFuture = self.submit(request: request) + return user + } + +} diff --git a/Sources/JiraSwift/Routes/Project-Routes.swift b/Sources/JiraSwift/Routes/Project-Routes.swift new file mode 100644 index 0000000..2715e5f --- /dev/null +++ b/Sources/JiraSwift/Routes/Project-Routes.swift @@ -0,0 +1,37 @@ +// +// Project-Routes.swift +// AsyncHTTPClient +// +// Created by Christoph Pageler on 30.09.19. +// + + +import AsyncHTTPClient +import NIO + + +public extension Jira.Client { + + func projects() -> EventLoopFuture<[Jira.Project]> { + do { + let request = try HTTPClient.Request(url: "\(self.baseURL)/rest/api/2/project", + method: .GET, + headers: headers) + return submit(request: request) + } catch { + return httpClient.eventLoopGroup.next().makeFailedFuture(error) + } + } + + func projectTypes() -> EventLoopFuture<[Jira.ProjectType]> { + do { + let request = try HTTPClient.Request(url: "\(self.baseURL)/rest/api/2/project/type", + method: .GET, + headers: headers) + return submit(request: request) + } catch { + return httpClient.eventLoopGroup.next().makeFailedFuture(error) + } + } + +} diff --git a/Sources/JiraSwift/Routes/Search-Routes.swift b/Sources/JiraSwift/Routes/Search-Routes.swift new file mode 100644 index 0000000..dd0a77e --- /dev/null +++ b/Sources/JiraSwift/Routes/Search-Routes.swift @@ -0,0 +1,42 @@ +// +// SearchRoutes.swift +// AsyncHTTPClient +// +// Created by Christoph Pageler on 27.09.19. +// + + +import AsyncHTTPClient +import NIO + + +public extension Jira.Client { + + func search(searchRequest: Jira.SearchRequest) -> EventLoopFuture { + let request: HTTPClient.Request + do { + request = try HTTPClient.Request(url: "\(self.baseURL)/rest/api/2/search", + method: .POST, + headers: self.headers, + body: HTTPClient.Body.data(Jira.Client.jsonEncoder.encode(searchRequest))) + } catch { + return self.httpClient.eventLoopGroup.next().makeFailedFuture(error) + } + let searchResult: EventLoopFuture = self.submit(request: request) + return searchResult + } + + func search(jql: String, + startAt: Int = 0, + maxResults: Int = 50, + fields: [Jira.SearchRequest.Field] = [], + expand: [String] = []) -> EventLoopFuture { + let searchRequest = Jira.SearchRequest(jql: jql, + startAt: startAt, + maxResults: maxResults, + fields: fields, + expand: expand) + return search(searchRequest: searchRequest) + } + +} diff --git a/Sources/JiraSwift/Routes/ServerInfo-Routes.swift b/Sources/JiraSwift/Routes/ServerInfo-Routes.swift new file mode 100644 index 0000000..6668e9b --- /dev/null +++ b/Sources/JiraSwift/Routes/ServerInfo-Routes.swift @@ -0,0 +1,29 @@ +// +// ServerInfo-Routes.swift +// AsyncHTTPClient +// +// Created by Christoph Pageler on 28.09.19. +// + + +import AsyncHTTPClient +import NIO + + +public extension Jira.Client { + + func getServerInfo() -> EventLoopFuture { + let request: HTTPClient.Request + do { + request = try HTTPClient.Request(url: "\(self.baseURL)/rest/api/2/serverInfo", + method: .GET, + headers: self.headers, + body: nil) + } catch { + return self.httpClient.eventLoopGroup.next().makeFailedFuture(error) + } + let serverInfo: EventLoopFuture = self.submit(request: request) + return serverInfo + } + +} diff --git a/Sources/JiraSwiftCLI/ClientCreatableCommandSignature.swift b/Sources/JiraSwiftCLI/ClientCreatableCommandSignature.swift new file mode 100644 index 0000000..f28b699 --- /dev/null +++ b/Sources/JiraSwiftCLI/ClientCreatableCommandSignature.swift @@ -0,0 +1,36 @@ +// +// ClientCreatableCommandSignature.swift +// JiraSwiftCLI +// +// Created by Christoph Pageler on 30.09.19. +// + + +import Foundation +import ConsoleKit +import JiraSwift + + +protocol ClientCreatableCommandSignature { + + var url: String? { get } + var username: String? { get } + var password: String? { get } + + func client() -> Jira.Client? + +} + +extension ClientCreatableCommandSignature { + + func client() -> Jira.Client? { + let env = ProcessInfo.processInfo.environment + guard let url = url ?? env["JIRA_URL"], + let username = username ?? env["JIRA_USERNAME"], + let password = password ?? env["JIRA_PASSWORD"] + else { + return nil + } + return Jira.Client(baseURL: url, username: username, password: password) + } +} diff --git a/Sources/JiraSwiftCLI/JiraSwiftCLI.swift b/Sources/JiraSwiftCLI/JiraSwiftCLI.swift index c9ce658..5bc9291 100644 --- a/Sources/JiraSwiftCLI/JiraSwiftCLI.swift +++ b/Sources/JiraSwiftCLI/JiraSwiftCLI.swift @@ -6,130 +6,42 @@ // -import Commander import Foundation -import Quack +import ConsoleKit +import JiraSwift public class JiraSwiftCLI { - - internal static let noDefaultValue: String = "NO DEFAULT VALUE" - - internal let urlOption = Option("url", - default: JiraSwiftCLI.noDefaultValue, - flag: nil, - description: "Jira URL", - validator: nil) - internal let urlEnvKey: String = "JIRA_URL" - - - internal let usernameOption = Option("username", - default: JiraSwiftCLI.noDefaultValue, - flag: Character("u"), - description: "Jira Username", - validator: nil) - internal let usernameEnvKey: String = "JIRA_USERNAME" - - - internal let passwordOption = Option("password", - default: JiraSwiftCLI.noDefaultValue, - flag: Character("p"), - description: "Jira Password", - validator: nil) - internal let passwordEnvKey: String = "JIRA_PASSWORD" - - - internal let jqlArgument = Argument("jql", - description: "JQL Query") - + public func run() { - - Group { - search(group: $0) - }.run() - } - - internal func secure(_ closure: () throws -> ()) { do { - try closure() - } catch let error { - print("Error: \(error.localizedDescription)") - exit(1) - } - - exit(0) - } - -} - -internal extension JiraSwiftCLI { - - enum Error: Swift.Error { - case noEnvWith(key: String) - case notAValidURL(string: String) - case quackError(error: Quack.Error) - } - -} - -extension JiraSwiftCLI.Error: LocalizedError { - - var errorDescription: String? { - switch self { - case .noEnvWith(let key): return "No Environment Variable with key `\(key)`" - case .notAValidURL(let string): return "Not a valid url `\(string)`" - case .quackError(let error): return "\(error)" - default: return "Unknown Error" - } - } - -} - -internal extension String { - - func isNoDefaultValue() -> Bool { - return self == JiraSwiftCLI.noDefaultValue - } - - mutating func loadFromEnv(_ key: String) throws { - guard let valueFromEnv = ProcessInfo.processInfo.environment[key] else { - throw JiraSwiftCLI.Error.noEnvWith(key: key) - } - self = valueFromEnv - } - - mutating func ensureWithEnv(_ key: String, allowUserInput: Bool = false, secure: Bool = false) throws { - if isNoDefaultValue() { - if allowUserInput { - do { - try loadFromEnv(key) - } catch { - print("Value for \(key): ") - guard let userInput = JiraSwiftCLI.userInput(secure: secure) else { - throw JiraSwiftCLI.Error.noEnvWith(key: key) - } - self = userInput + var config = CommandConfiguration() + + config.use(SearchCommand(), as: "search") + config.use(ProjectsCommand(), as: "projects") + config.use(ProjectTypesCommand(), as: "project-types") + + let group = try config.resolve().group() + let commandInput = CommandInput(arguments: CommandLine.arguments) + + let terminal = Terminal() + try terminal.run(group, input: commandInput) + } catch { + switch error { + case let httpError as Jira.ClientError: + switch httpError { + case .noBodyError(let statusCode): + print("No Body (\(statusCode))") + case .couldNotReadBody: + print("Could not read body") + case .httpError(let statusCode): + print("HTTP Error: \(statusCode)") + case .jiraError(let jiraResponseError): + print("JIRA Error: \(jiraResponseError.errorMessages)") } - } else { - try loadFromEnv(key) + default: print("Unknown error") } } } - -} -internal extension JiraSwiftCLI { - - func userInput(secure: Bool = false) -> String? { - return JiraSwiftCLI.userInput(secure: secure) - } - - static func userInput(secure: Bool = false) -> String? { - if secure { - return String(validatingUTF8: UnsafePointer(getpass(""))) - } else { - return readLine() - } - } - } diff --git a/Sources/JiraSwiftCLI/JiraSwiftCLISearch.swift b/Sources/JiraSwiftCLI/JiraSwiftCLISearch.swift deleted file mode 100644 index 33078de..0000000 --- a/Sources/JiraSwiftCLI/JiraSwiftCLISearch.swift +++ /dev/null @@ -1,50 +0,0 @@ -// -// JiraSwiftCLISearch.swift -// JiraSwiftCLI -// -// Created by Christoph Pageler on 11.04.18. -// - -import JiraSwift -import Commander -import Foundation - - -internal extension JiraSwiftCLI { - - func search(group: Group) { - group.command( - "search", - urlOption, - usernameOption, - passwordOption, - jqlArgument - ) { (jiraURL: String, username: String, password: String, jql: String) in - self.secure { - var jiraURL = jiraURL - try jiraURL.ensureWithEnv(self.urlEnvKey) - - guard let url = URL(string: jiraURL) else { - throw Error.notAValidURL(string: jiraURL) - } - - var username = username - try username.ensureWithEnv(self.usernameEnvKey) - - var password = password - try password.ensureWithEnv(self.passwordEnvKey, allowUserInput: true, secure: true) - - let client = Jira.Client(url: url, username: username, password: password) - let result = client.search(jql: jql) - - switch result { - case .success(let result): - print("result: \(result.total)") - case .failure(let error): - throw error - } - } - } - } - -} diff --git a/Sources/JiraSwiftCLI/ProjectTypes-Command.swift b/Sources/JiraSwiftCLI/ProjectTypes-Command.swift new file mode 100644 index 0000000..a2d216c --- /dev/null +++ b/Sources/JiraSwiftCLI/ProjectTypes-Command.swift @@ -0,0 +1,65 @@ +// +// ProjectTypes-Command.swift +// JiraSwiftCLI +// +// Created by Christoph Pageler on 30.09.19. +// + + +import Foundation +import ConsoleKit +import JiraSwift +import SwiftyTextTable + + +struct ProjectTypesCommand: Command { + + public struct ProjectTypesSignature: CommandSignature, ClientCreatableCommandSignature { + + @Option(name: "url", help: "Set the url of your jira instace") + var url: String + + @Option(name: "username", short: "u", help: "Set your jira username") + var username: String + + @Option(name: "password", short: "p", help: "Set your jira password") + var password: String + + public init() { } + } + + public typealias Signature = ProjectTypesSignature + + public var help: String { + "Lists all project types" + } + + public func run(using context: CommandContext, signature: Signature) throws { + guard let client = signature.client() else { + var context = context + outputHelp(using: &context) + return + } + + let loading = context.console.loadingBar(title: "Searching") + loading.start() + + let projectTypes = try client.projectTypes().wait() + loading.succeed() + + var table = TextTable(columns: [ + TextTableColumn(header: "Key"), + TextTableColumn(header: "Formatted Key"), + TextTableColumn(header: "Color") + ], header: "Project Types") + for projectType in projectTypes { + table.addRow(values: [ + projectType.key, + projectType.formattedKey, + projectType.color + ]) + } + context.console.print(table.render()) + } + +} diff --git a/Sources/JiraSwiftCLI/Projects-Command.swift b/Sources/JiraSwiftCLI/Projects-Command.swift new file mode 100644 index 0000000..c5ef261 --- /dev/null +++ b/Sources/JiraSwiftCLI/Projects-Command.swift @@ -0,0 +1,65 @@ +// +// Projects-Command.swift +// AsyncHTTPClient +// +// Created by Christoph Pageler on 30.09.19. +// + + +import Foundation +import ConsoleKit +import JiraSwift +import SwiftyTextTable + + +struct ProjectsCommand: Command { + + public struct ProjectsSignature: CommandSignature, ClientCreatableCommandSignature { + + @Option(name: "url", help: "Set the url of your jira instace") + var url: String + + @Option(name: "username", short: "u", help: "Set your jira username") + var username: String + + @Option(name: "password", short: "p", help: "Set your jira password") + var password: String + + public init() { } + } + + public typealias Signature = ProjectsSignature + + public var help: String { + "Lists all projects" + } + + public func run(using context: CommandContext, signature: Signature) throws { + guard let client = signature.client() else { + var context = context + outputHelp(using: &context) + return + } + + let loading = context.console.loadingBar(title: "Searching") + loading.start() + + let projects = try client.projects().wait() + loading.succeed() + + var table = TextTable(columns: [ + TextTableColumn(header: "Key"), + TextTableColumn(header: "Name"), + TextTableColumn(header: "Project Type Key") + ], header: "Projects") + for project in projects { + table.addRow(values: [ + project.key, + project.name, + project.projectTypeKey + ]) + } + context.console.print(table.render()) + } + +} diff --git a/Sources/JiraSwiftCLI/Search-Command.swift b/Sources/JiraSwiftCLI/Search-Command.swift new file mode 100644 index 0000000..b246c94 --- /dev/null +++ b/Sources/JiraSwiftCLI/Search-Command.swift @@ -0,0 +1,73 @@ +// +// Search-Command.swift +// JiraSwiftCLI +// +// Created by Christoph Pageler on 11.04.18. +// + + +import Foundation +import ConsoleKit +import JiraSwift +import SwiftyTextTable + + +struct SearchCommand: Command { + + public struct SearchCommandSignature: CommandSignature, ClientCreatableCommandSignature { + + @Option(name: "url", help: "Set the url of your jira instace") + var url: String + + @Option(name: "username", short: "u", help: "Set your jira username") + var username: String + + @Option(name: "password", short: "p", help: "Set your jira password") + var password: String + + @Option(name: "jql", short: "j", help: "Set the JQL Query to execute") + var jql: String + + public init() { } + } + + public typealias Signature = SearchCommandSignature + + public var help: String { + "Search Issues" + } + + public func run(using context: CommandContext, signature: Signature) throws { + guard let client = signature.client(), + let jql = signature.jql + else { + var context = context + outputHelp(using: &context) + return + } + + let loading = context.console.loadingBar(title: "Searching") + loading.start() + + let searchResult = try client.search(jql: jql, fields: [.lastViewed, .created, .updated, .duedate]).wait() + + loading.succeed() + + var table = TextTable(columns: [ + TextTableColumn(header: "Key"), + TextTableColumn(header: "Creator"), + TextTableColumn(header: "Assignee"), + TextTableColumn(header: "Summary") + ], header: "Search Results") + for issue in searchResult.issues { + table.addRow(values: [ + issue.key, + issue.fields.creator?.name ?? "", + issue.fields.assignee?.name ?? "", + issue.fields.summary ?? "" + ]) + } + context.console.print(table.render()) + } + +} diff --git a/Tests/JiraSwiftTests/JiraSwiftSearchTests.swift b/Tests/JiraSwiftTests/JiraSwiftSearchTests.swift index 2d941a2..f034cd4 100644 --- a/Tests/JiraSwiftTests/JiraSwiftSearchTests.swift +++ b/Tests/JiraSwiftTests/JiraSwiftSearchTests.swift @@ -9,73 +9,55 @@ import XCTest @testable import JiraSwift + class JiraSwiftSearchTests: JiraSwiftTest { static var allTests = [ ("testSearchShouldReturnSomeIssues", testSearchShouldReturnSomeIssues), - ("testClientSearchWithOAuthTokenShouldReturnSomeIssues", testClientSearchWithOAuthTokenShouldReturnSomeIssues), +// ("testClientSearchWithOAuthTokenShouldReturnSomeIssues", testClientSearchWithOAuthTokenShouldReturnSomeIssues), ("testGetServerInfoShouldReturnServerInfo", testGetServerInfoShouldReturnServerInfo) ] func testSearchShouldReturnSomeIssues() { - let e = expectation(description: "searchExpecation") let (url, username, password) = jiraCredentialsFromEnvironment() - let jiraClient = Jira.Client(url: url, username: username, password: password) - jiraClient.search(jql: "key in (EWE27-65, EWE038-3, EWE027-58)") { result in - switch result { - case .success(let result): - XCTAssertEqual(result.issues.count, 3) - case .failure: - XCTFail() - } - e.fulfill() - } - - waitForExpectations(timeout: 15, handler: nil) + let jiraClient = Jira.Client(baseURL: url.absoluteString, username: username, password: password) + let seachResult = try! jiraClient.search(jql: "key in (EWE046-250, EWE046-249, EWE046-248)").wait() + XCTAssertEqual(seachResult.issues.count, 3) } func testSearchShouldReturnIssuesWithComponents() { - let e = expectation(description: "searchWithComponentsExpecation") let (url, username, password) = jiraCredentialsFromEnvironment() - let jiraClient = Jira.Client(url: url, username: username, password: password) - jiraClient.search(jql: "key in (SPEED-28, BA-10)") { result in - switch result { - case .success(let result): - XCTAssertEqual(result.issues.count, 2) - XCTAssertFalse(result.issues[0].fields.components.isEmpty) - XCTAssertFalse(result.issues[1].fields.components.isEmpty) - case .failure: - XCTFail() - } - e.fulfill() - } + let jiraClient = Jira.Client(baseURL: url.absoluteString, username: username, password: password) + let searchResult = try! jiraClient.search(jql: "key in (SPEED-28, BA-10)").wait() - waitForExpectations(timeout: 15, handler: nil) + XCTAssertEqual(searchResult.issues.count, 2) + XCTAssertNotNil(searchResult.issues[0].fields.components) + XCTAssertNotNil(searchResult.issues[1].fields.components) } - func testClientSearchWithOAuthTokenShouldReturnSomeIssues() { - let e = expectation(description: "searchExpecationWithOAuth") - let (url, token) = jiraOAuthTokenFromCredentials() - let jiraClient = Jira.Client(url: url, oAuthAccessToken: token) - - jiraClient.search(jql: "key in (EWE027-65, EWE038-3, EWE027-58)") { result in - switch result { - case .success(let result): - XCTAssertEqual(result.issues.count, 3) - case .failure: - XCTFail() - } - e.fulfill() - } - - waitForExpectations(timeout: 15, handler: nil) - } +// func testClientSearchWithOAuthTokenShouldReturnSomeIssues() { +// let e = expectation(description: "searchExpecationWithOAuth") +// let (url, token) = jiraOAuthTokenFromCredentials() +// let jiraClient = Jira.Client(baseURL: url.absoluteString, headers: [("Authorization", token)]) +// +// jiraClient.search(jql: "key in (EWE027-65, EWE038-3, EWE027-58)").whenComplete { result in +// switch result { +// case .success(let result): +// XCTAssertEqual(result.issues.count, 3) +// case .failure: +// XCTFail() +// } +// e.fulfill() +// } +// +// waitForExpectations(timeout: 15, handler: nil) +// } func testGetServerInfoShouldReturnServerInfo() { let e = expectation(description: "serverInfoExpectation") let (url, username, password) = jiraCredentialsFromEnvironment() - let jiraClient = Jira.Client(url: url, username: username, password: password) - jiraClient.getServerInfo { result in + let jiraClient = Jira.Client(baseURL: url.absoluteString, username: username, password: password) + jiraClient.getServerInfo().whenComplete { result in switch result { case .success(let result): XCTAssertNotNil(result) @@ -92,8 +74,8 @@ class JiraSwiftSearchTests: JiraSwiftTest { func testMyself() { let e = expectation(description: "myselfExpectation") let (url, username, password) = jiraCredentialsFromEnvironment() - let jiraClient = Jira.Client(url: url, username: username, password: password) - jiraClient.getMyself() { result in + let jiraClient = Jira.Client(baseURL: url.absoluteString, username: username, password: password) + jiraClient.getMyself().whenComplete { result in switch result { case .success(let result): XCTAssertNotNil(result) diff --git a/Tests/JiraSwiftTests/JiraSwiftTest.swift b/Tests/JiraSwiftTests/JiraSwiftTest.swift index d250ca8..f78ac7f 100644 --- a/Tests/JiraSwiftTests/JiraSwiftTest.swift +++ b/Tests/JiraSwiftTests/JiraSwiftTest.swift @@ -12,9 +12,9 @@ import XCTest class JiraSwiftTest: XCTestCase { func jiraCredentialsFromEnvironment() -> (URL, String, String) { - guard let jiraUsername = ProcessInfo.processInfo.environment["jiraUsername"], - let jiraPassword = ProcessInfo.processInfo.environment["jiraPassword"], - let urlString = ProcessInfo.processInfo.environment["jiraURL"], + guard let jiraUsername = ProcessInfo.processInfo.environment["JIRA_USERNAME"], + let jiraPassword = ProcessInfo.processInfo.environment["JIRA_PASSWORD"], + let urlString = ProcessInfo.processInfo.environment["JIRA_URL"], let jiraURL = URL(string: urlString) else { fatalError() @@ -24,9 +24,9 @@ class JiraSwiftTest: XCTestCase { } func jiraOAuthTokenFromCredentials() -> (URL, String) { - guard let urlString = ProcessInfo.processInfo.environment["jiraURL"], + guard let urlString = ProcessInfo.processInfo.environment["JIRA_URL"], let jiraURL = URL(string: urlString), - let oAuthToken = ProcessInfo.processInfo.environment["jiraOAuthToken"] + let oAuthToken = ProcessInfo.processInfo.environment["JIRA_OAUTH_TOKEN"] else { fatalError() }