-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #29 from jonny7/users
adds User API
- Loading branch information
Showing
7 changed files
with
306 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,48 @@ | ||
# TestRailKit | ||
![](https://img.shields.io/badge/Swift-5.3-orange.svg?style=svg) [![codecov](https://codecov.io/gh/jonny7/testrail-kit/branch/master/graph/badge.svg)](https://codecov.io/gh/jonny7/testrail-kit) ![testrail-ci](https://github.com/jonny7/testrail-kit/workflows/testrail-ci/badge.svg) ![license](https://img.shields.io/github/license/jonny7/testrail-kit) [![Maintainability](https://api.codeclimate.com/v1/badges/58d6e1a7f9f8038f92c8/maintainability)](https://codeclimate.com/github/jonny7/testrail-kit/maintainability) | ||
|
||
![wip](https://img.shields.io/badge/WIP-Work%20In%20Progress-orange) | ||
## Overview | ||
|
||
**TestRailKit** is an asynchronous pure Swift wrapper around the [TestRail API](https://www.gurock.com/testrail/docs/api), written on top of Apple's [swift-nio](https://github.com/apple/swift-nio) and [AsyncHTTPClient](https://github.com/swift-server/async-http-client). Whereas other TestRail bindings generally provide some form of minimal client to send requests. This library provides the full type safe implementation of TestRail's API. Meaning that it will automatically encode and decode models to be sent and received and all the endpoints are already built in. These models were generally created by using our own unmodified TestRail instance and seeing what endpoints returned. But you can still make your own models easily. | ||
|
||
## Installing | ||
Add the following entry in your Package.swift to start using TestRailKit: | ||
```swift | ||
.package(url: "https://github.com/jonny7/testrail-kit", from: "1.0.0-alpha.1") | ||
``` | ||
## Getting Started | ||
```swift | ||
import TestRailKit | ||
|
||
let httpClient = HTTPClient(...) | ||
let client = TestRailClient(httpClient: httpClient, eventLoop: eventLoop, username: "your_username", apiKey: "your-key", testRailUrl: "https://my-testrail-domain", port: nil) // `use port` if you're on a non-standard port | ||
``` | ||
This gives you access to the TestRail client now. The library has extensive tests for all the endpoints, so you can always look there for example usage. At it's heart there are two main functions: | ||
```swift | ||
client.action(resource: ConfigurationRepresentable, body: TestRailPostable) -> TestRailModel // for posting models to TestRail | ||
client.action(resource: ConfigurationRepresentable) -> TestRailModel // for retrieving models from TestRail | ||
``` | ||
|
||
`ConfigurationRepresentation`: When calling either `action` method you will need to pass the resource argument, these all follow the same naming convention of TestRail resource, eg `Case`, `Suite`, `Plan` etc + "Resource". So the previously listed models all become `CaseResource`, `SuiteResource`, `PlanResource`. Each resource, then has various other enumerated options in order to provide an abstracted type-safe API, leaving the developer to pass simple typed arguments to manage TestRail. | ||
|
||
For example: | ||
```swift | ||
let tests: EventLoopFuture<[Test]> = client.action(resource: TestResource.all(runId: 89, statusIds: [1]))).wait() // return all tests for run 89 with a status of 1 | ||
``` | ||
> _**Note**: Use of `wait()` was used here for simplicity. Never call this method on an `eventLoop`!_ | ||
## Conventions | ||
TestRail uses Unix timestamps when working with dates. TestRailKit will encode or decode all Swift `Date` objects into UNIX timestamps automatically. | ||
TestRail also uses `snake_case` for property names, TestRailKit automatically encodes or decodes to `camelCase` as per Swift conventions | ||
|
||
## Customizing | ||
If you wish to use a model that doesn't currently exist in the library because your own TestRail is mofidied you can simply make this model conform to `TestRailModel` if decoding it from TestRail or `TestRailPostable` if you wish to post this model to TestRail. | ||
|
||
### Partial Updates | ||
TestRail supports partial updates, if you wish to use these, you will need to make this new object conform to `TestRailPostable`. You can see an example of this in the [tests](https://github.com/jonny7/testrail-kit/blob/master/Tests/TestRailKitTests/Utilities/Classes/SuiteUtilities.swift). | ||
|
||
## Vapor | ||
For those who want to use this library with the most popular server side Swift framework Vapor, please see this ![repo](https://github.com/jonny7/testrail). | ||
|
||
## Contributing | ||
All help is welcomed, please open a PR |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
public struct User: TestRailModel { | ||
public var email: String | ||
public var id: Int | ||
public var isActive: Bool | ||
public var name: String | ||
public var roleId: Int | ||
public var role: String | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
public enum UserResource: ConfigurationRepresentable { | ||
case get(type: GetAction) | ||
|
||
public var request: RequestDetails { | ||
switch self { | ||
case .get(.one(let userId)): | ||
return (uri: "get_user/\(userId)", method: .GET) | ||
case .get(type: .current(let userId)): | ||
return (uri: "get_current_user/\(userId)", method: .GET) | ||
case .get(type: .email(let email)): | ||
return (uri: "get_user_by_email&email=\(email)", method: .GET) | ||
case .get(type: .all(let projectId)): | ||
guard let project = projectId else { | ||
return (uri: "get_users", method: .GET) | ||
} | ||
return (uri: "get_users&project_id=\(project)", method: .GET) | ||
} | ||
} | ||
|
||
public enum GetAction { | ||
/// Returns an existing user. | ||
/// See https://www.gurock.com/testrail/docs/api/reference/users#get_user | ||
case one(userId: Int) | ||
|
||
/// Returns user details for the TestRail user making the API request. | ||
/// See https://www.gurock.com/testrail/docs/api/reference/users#get_current_user | ||
case current(userId: Int) | ||
|
||
/// Returns an existing user by his/her email address. | ||
/// See https://www.gurock.com/testrail/docs/api/reference/users#get_user_by_email | ||
case email(email: String) | ||
|
||
/// Returns a list of users. | ||
/// See https://www.gurock.com/testrail/docs/api/reference/users#get_users | ||
case all(projectId: Int?) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,187 @@ | ||
import NIO | ||
import NIOHTTP1 | ||
import XCTest | ||
|
||
@testable import TestRailKit | ||
|
||
class UserTests: XCTestCase { | ||
|
||
static var utilities = UserUtilities() | ||
|
||
override class func tearDown() { | ||
//XCTAssertNoThrow(try Self.utilities.testServer.stop()) //this is a nio problem and should remain. Omitting for GH Actions only | ||
XCTAssertNoThrow(try Self.utilities.httpClient.syncShutdown()) | ||
XCTAssertNoThrow(try Self.utilities.group.syncShutdownGracefully()) | ||
} | ||
|
||
func testGetUser() { | ||
var requestComplete: EventLoopFuture<User>! | ||
XCTAssertNoThrow(requestComplete = try Self.utilities.client.action(resource: UserResource.get(type: .one(userId: 1)))) | ||
|
||
XCTAssertNoThrow( | ||
XCTAssertEqual( | ||
.head( | ||
.init( | ||
version: .init(major: 1, minor: 1), | ||
method: .GET, | ||
uri: "/index.php?/api/v2/get_user/1", | ||
headers: .init([ | ||
("authorization", "Basic dXNlckB0ZXN0cmFpbC5pbzoxMjM0YWJjZA=="), | ||
("content-type", "application/json; charset=utf-8"), | ||
("Host", "127.0.0.1:\(Self.utilities.testServer.serverPort)"), | ||
("Content-Length", "0"), | ||
]))), | ||
try Self.utilities.testServer.readInbound())) | ||
|
||
XCTAssertEqual(try Self.utilities.testServer.readInbound(), .end(nil)) | ||
|
||
var responseBuffer = Self.utilities.allocator.buffer(capacity: 0) | ||
responseBuffer.writeString(Self.utilities.userResponse) | ||
|
||
XCTAssertNoThrow( | ||
try Self.utilities.testServer.writeOutbound(.head(.init(version: .init(major: 1, minor: 1), status: .ok)))) | ||
XCTAssertNoThrow(try Self.utilities.testServer.writeOutbound(.body(.byteBuffer(responseBuffer)))) | ||
XCTAssertNoThrow(try Self.utilities.testServer.writeOutbound(.end(nil))) | ||
|
||
let response = try! requestComplete.wait() | ||
XCTAssertEqual(response.email, "jonny@github.com") | ||
} | ||
|
||
func testGetCurrentUser() { | ||
var requestComplete: EventLoopFuture<User>! | ||
XCTAssertNoThrow(requestComplete = try Self.utilities.client.action(resource: UserResource.get(type: .current(userId: 1)))) | ||
|
||
XCTAssertNoThrow( | ||
XCTAssertEqual( | ||
.head( | ||
.init( | ||
version: .init(major: 1, minor: 1), | ||
method: .GET, | ||
uri: "/index.php?/api/v2/get_current_user/1", | ||
headers: .init([ | ||
("authorization", "Basic dXNlckB0ZXN0cmFpbC5pbzoxMjM0YWJjZA=="), | ||
("content-type", "application/json; charset=utf-8"), | ||
("Host", "127.0.0.1:\(Self.utilities.testServer.serverPort)"), | ||
("Content-Length", "0"), | ||
]))), | ||
try Self.utilities.testServer.readInbound())) | ||
|
||
XCTAssertEqual(try Self.utilities.testServer.readInbound(), .end(nil)) | ||
|
||
var responseBuffer = Self.utilities.allocator.buffer(capacity: 0) | ||
responseBuffer.writeString(Self.utilities.userResponse) | ||
|
||
XCTAssertNoThrow( | ||
try Self.utilities.testServer.writeOutbound(.head(.init(version: .init(major: 1, minor: 1), status: .ok)))) | ||
XCTAssertNoThrow(try Self.utilities.testServer.writeOutbound(.body(.byteBuffer(responseBuffer)))) | ||
XCTAssertNoThrow(try Self.utilities.testServer.writeOutbound(.end(nil))) | ||
|
||
let response = try! requestComplete.wait() | ||
XCTAssertEqual(response.email, "jonny@github.com") | ||
} | ||
|
||
func testGetUserByEmail() { | ||
var requestComplete: EventLoopFuture<User>! | ||
XCTAssertNoThrow( | ||
requestComplete = try Self.utilities.client.action(resource: UserResource.get(type: .email(email: "jonny@github.com"))) | ||
) | ||
|
||
XCTAssertNoThrow( | ||
XCTAssertEqual( | ||
.head( | ||
.init( | ||
version: .init(major: 1, minor: 1), | ||
method: .GET, | ||
uri: "/index.php?/api/v2/get_user_by_email&email=jonny@github.com", | ||
headers: .init([ | ||
("authorization", "Basic dXNlckB0ZXN0cmFpbC5pbzoxMjM0YWJjZA=="), | ||
("content-type", "application/json; charset=utf-8"), | ||
("Host", "127.0.0.1:\(Self.utilities.testServer.serverPort)"), | ||
("Content-Length", "0"), | ||
]))), | ||
try Self.utilities.testServer.readInbound())) | ||
|
||
XCTAssertEqual(try Self.utilities.testServer.readInbound(), .end(nil)) | ||
|
||
var responseBuffer = Self.utilities.allocator.buffer(capacity: 0) | ||
responseBuffer.writeString(Self.utilities.userResponse) | ||
|
||
XCTAssertNoThrow( | ||
try Self.utilities.testServer.writeOutbound(.head(.init(version: .init(major: 1, minor: 1), status: .ok)))) | ||
XCTAssertNoThrow(try Self.utilities.testServer.writeOutbound(.body(.byteBuffer(responseBuffer)))) | ||
XCTAssertNoThrow(try Self.utilities.testServer.writeOutbound(.end(nil))) | ||
|
||
let response = try! requestComplete.wait() | ||
XCTAssertEqual(response.email, "jonny@github.com") | ||
} | ||
|
||
func testGetUsers() { | ||
var requestComplete: EventLoopFuture<[User]>! | ||
XCTAssertNoThrow( | ||
requestComplete = try Self.utilities.client.action(resource: UserResource.get(type: .all(projectId: nil))) | ||
) | ||
|
||
XCTAssertNoThrow( | ||
XCTAssertEqual( | ||
.head( | ||
.init( | ||
version: .init(major: 1, minor: 1), | ||
method: .GET, | ||
uri: "/index.php?/api/v2/get_users", | ||
headers: .init([ | ||
("authorization", "Basic dXNlckB0ZXN0cmFpbC5pbzoxMjM0YWJjZA=="), | ||
("content-type", "application/json; charset=utf-8"), | ||
("Host", "127.0.0.1:\(Self.utilities.testServer.serverPort)"), | ||
("Content-Length", "0"), | ||
]))), | ||
try Self.utilities.testServer.readInbound())) | ||
|
||
XCTAssertEqual(try Self.utilities.testServer.readInbound(), .end(nil)) | ||
|
||
var responseBuffer = Self.utilities.allocator.buffer(capacity: 0) | ||
responseBuffer.writeString(Self.utilities.usersResponse) | ||
|
||
XCTAssertNoThrow( | ||
try Self.utilities.testServer.writeOutbound(.head(.init(version: .init(major: 1, minor: 1), status: .ok)))) | ||
XCTAssertNoThrow(try Self.utilities.testServer.writeOutbound(.body(.byteBuffer(responseBuffer)))) | ||
XCTAssertNoThrow(try Self.utilities.testServer.writeOutbound(.end(nil))) | ||
|
||
let response = try! requestComplete.wait() | ||
XCTAssertEqual(response.first?.email, "jonny@github.com") | ||
} | ||
|
||
func testGetUsersNonAdmin() { | ||
var requestComplete: EventLoopFuture<[User]>! | ||
XCTAssertNoThrow( | ||
requestComplete = try Self.utilities.client.action(resource: UserResource.get(type: .all(projectId: 1))) | ||
) | ||
|
||
XCTAssertNoThrow( | ||
XCTAssertEqual( | ||
.head( | ||
.init( | ||
version: .init(major: 1, minor: 1), | ||
method: .GET, | ||
uri: "/index.php?/api/v2/get_users&project_id=1", | ||
headers: .init([ | ||
("authorization", "Basic dXNlckB0ZXN0cmFpbC5pbzoxMjM0YWJjZA=="), | ||
("content-type", "application/json; charset=utf-8"), | ||
("Host", "127.0.0.1:\(Self.utilities.testServer.serverPort)"), | ||
("Content-Length", "0"), | ||
]))), | ||
try Self.utilities.testServer.readInbound())) | ||
|
||
XCTAssertEqual(try Self.utilities.testServer.readInbound(), .end(nil)) | ||
|
||
var responseBuffer = Self.utilities.allocator.buffer(capacity: 0) | ||
responseBuffer.writeString(Self.utilities.usersResponse) | ||
|
||
XCTAssertNoThrow( | ||
try Self.utilities.testServer.writeOutbound(.head(.init(version: .init(major: 1, minor: 1), status: .ok)))) | ||
XCTAssertNoThrow(try Self.utilities.testServer.writeOutbound(.body(.byteBuffer(responseBuffer)))) | ||
XCTAssertNoThrow(try Self.utilities.testServer.writeOutbound(.end(nil))) | ||
|
||
let response = try! requestComplete.wait() | ||
XCTAssertEqual(response.first?.email, "jonny@github.com") | ||
} | ||
} |
10 changes: 10 additions & 0 deletions
10
Tests/TestRailKitTests/Utilities/Classes/UserUtilities.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import Foundation | ||
|
||
@testable import TestRailKit | ||
|
||
class UserUtilities: TestingUtilities { | ||
let userResponse = userResponseString | ||
let usersResponse = "[\(userResponseString)]" | ||
} | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters