Tests/KituraTests/TestCodableRouter.swift
/**
* Copyright IBM Corporation 2017-2019
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
import XCTest
import Foundation
import KituraContracts
@testable import Kitura
final class TestCodableRouter: KituraTest, KituraTestSuite {
static var allTests: [(String, (TestCodableRouter) -> () throws -> Void)] {
return [
("testBasicPost", testBasicPost),
("testBasicPostIdentifier", testBasicPostIdentifier),
("testBasicGetSingleton", testBasicGetSingleton),
("testBasicGetArray", testBasicGetArray),
("testBasicGetSingle", testBasicGetSingle),
("testBasicGetIdentifiersArray", testBasicGetIdentifiersArray),
("testBasicDelete", testBasicDelete),
("testBasicDeleteSingle", testBasicDeleteSingle),
("testBasicPut", testBasicPut),
("testBasicPatch", testBasicPatch),
("testErrorOverridesBody", testErrorOverridesBody),
("testCodableRoutesWithBodyParsingFail", testCodableRoutesWithBodyParsingFail),
("testCodableGetSingleQueryParameters", testCodableGetSingleQueryParameters),
("testCodableGetArrayQueryParameters", testCodableGetArrayQueryParameters),
("testCodableDeleteQueryParameters", testCodableDeleteQueryParameters),
("testCodablePostSuccessStatuses", testCodablePostSuccessStatuses),
("testNoDataCustomStatus", testNoDataCustomStatus),
("testNoDataDefaultStatus", testNoDataDefaultStatus),
]
}
// Need to initialise to avoid compiler error
var router = Router()
var userStore: [Int: User] = [:]
var userStoreArray: [User] = []
// Reset for each test
override func setUp() {
super.setUp() // Initialize logging
router = Router()
userStore = [1: User(id: 1, name: "Mike"), 2: User(id: 2, name: "Chris"), 3: User(id: 3, name: "Ricardo")]
// Swift 5: Dictionary ordering is randomized on each creation due to hashing changes. We cannot rely on the ordering of elements from `userStore` when comparing arrays.
userStoreArray = [User(id: 1, name: "Mike"), User(id: 2, name: "Chris"), User(id: 3, name: "Ricardo")]
}
struct Conflict: Codable, Equatable {
let field: String
init(on field: String) {
self.field = field
}
static func ==(lhs: Conflict, rhs: Conflict) -> Bool {
return lhs.field == rhs.field
}
}
struct User: Codable, Equatable {
let id: Int
let name: String
init(id: Int, name: String) {
self.id = id
self.name = name
}
static func ==(lhs: User, rhs: User) -> Bool {
return lhs.id == rhs.id && lhs.name == rhs.name
}
}
struct OptionalUser: Codable, Equatable {
let id: Int?
let name: String?
init(id: Int?, name: String?) {
self.id = id
self.name = name
}
static func ==(lhs: OptionalUser, rhs: OptionalUser) -> Bool {
return lhs.id == rhs.id && lhs.name == rhs.name
}
}
struct Status: Codable, Equatable {
let description: String
init(_ desc: String) {
description = desc
}
static func ==(lhs: Status, rhs: Status) -> Bool {
return lhs.description == rhs.description
}
}
struct MyQuery: QueryParams, Equatable {
public let intField: Int
public let optionalIntField: Int?
public let stringField: String
public let intArray: [Int]
public let dateField: Date
public let optionalDateField: Date?
public let nested: Nested
public static func ==(lhs: MyQuery, rhs: MyQuery) -> Bool {
return lhs.intField == rhs.intField &&
lhs.optionalIntField == rhs.optionalIntField &&
lhs.stringField == rhs.stringField &&
lhs.intArray == rhs.intArray &&
lhs.dateField.timeIntervalSince1970 == rhs.dateField.timeIntervalSince1970 &&
lhs.optionalDateField?.timeIntervalSince1970 == rhs.optionalDateField?.timeIntervalSince1970 &&
lhs.nested == rhs.nested
}
}
struct Nested: Codable, Equatable {
public let nestedIntField: Int
public let nestedStringField: String
public static func ==(lhs: Nested, rhs: Nested) -> Bool {
return lhs.nestedIntField == rhs.nestedIntField && lhs.nestedStringField == rhs.nestedStringField
}
}
func testBasicPost() {
router.post("/users") { (user: User, respondWith: (User?, RequestError?) -> Void) in
print("POST on /users for user \(user)")
respondWith(user, nil)
}
router.post("/error/users") { (user: User, respondWith: (User?, RequestError?) -> Void) in
print("POST on /error/users for user \(user)")
respondWith(nil, .conflict)
}
router.post("/bodyerror/users") { (user: User, respondWith: (User?, RequestError?) -> Void) in
print("POST on /bodyerror/users for user \(user)")
respondWith(nil, RequestError(.conflict, body: Conflict(on: "id")))
}
router.post("/urlencoded") { (user: User, respondWith: (User?, RequestError?) -> Void) in
print("POST on /urlencoded for user \(user)")
respondWith(user, nil)
}
let user = User(id: 4, name: "David")
buildServerTest(router, timeout: 30)
.request("post", path: "/users", data: user)
.hasStatus(.created)
.hasContentType(withPrefix: "application/json")
.hasData(user)
.request("post", path: "/error/users", data: user)
.hasStatus(.conflict)
.hasNoData()
.request("post", path: "/bodyerror/users", data: user)
.hasStatus(.conflict)
.hasContentType(withPrefix: "application/json")
.hasData(Conflict(on: "id"))
.request("post", path: "/urlencoded", urlEncodedString: "id=4&name=David")
.hasStatus(.created)
.hasContentType(withPrefix: "application/json")
.hasData(user)
.request("post", path: "/urlencoded", urlEncodedString: "id=4&name=David&extra=yes")
.hasStatus(.created)
.hasContentType(withPrefix: "application/json")
.hasData(user)
.request("post", path: "/urlencoded", urlEncodedString: "encoding=valid&failed=match")
.hasStatus(.unprocessableEntity)
.hasData()
.request("post", path: "/urlencoded", urlEncodedString: "invalidEncoding")
.hasStatus(.unprocessableEntity)
.hasData()
.run()
}
func testBasicPostIdentifier() {
router.post("/users") { (user: User, respondWith: (Int?, User?, RequestError?) -> Void) in
print("POST on /users for user \(user)")
respondWith(user.id, user, nil)
}
router.post("/error/users") { (user: User, respondWith: (Int?, User?, RequestError?) -> Void) in
print("POST on /error/users for user \(user)")
respondWith(nil, nil, .conflict)
}
router.post("/bodyerror/users") { (user: User, respondWith: (Int?, User?, RequestError?) -> Void) in
print("POST on /bodyerror/users for user \(user)")
respondWith(nil, nil, RequestError(.conflict, body: Conflict(on: "id")))
}
let user = User(id: 4, name: "David")
buildServerTest(router, timeout: 30)
.request("post", path: "/users", data: user)
.hasStatus(.created)
.hasHeader("Location", only: String(user.id))
.hasContentType(withPrefix: "application/json")
.hasData(user)
.request("post", path: "/error/users", data: user)
.hasStatus(.conflict)
.hasNoData()
.request("post", path: "/bodyerror/users", data: user)
.hasStatus(.conflict)
.hasContentType(withPrefix: "application/json")
.hasData(Conflict(on: "id"))
.run()
}
func testBasicGetSingleton() {
router.get("/status") { (respondWith: (Status?, RequestError?) -> Void) in
print("GET on /status")
respondWith(Status("GOOD"), nil)
}
router.get("/error/status") { (respondWith: (Status?, RequestError?) -> Void) in
print("GET on /error/status")
respondWith(nil, .serviceUnavailable)
}
router.get("/bodyerror/status") { (respondWith: (Status?, RequestError?) -> Void) in
print("GET on /bodyerror/status")
respondWith(nil, RequestError(.serviceUnavailable, body: Status("BAD")))
}
buildServerTest(router, timeout: 30)
.request("get", path: "/status")
.hasStatus(.OK)
.hasContentType(withPrefix: "application/json")
.hasData(Status("GOOD"))
.request("get", path: "/error/status")
.hasStatus(.serviceUnavailable)
.hasNoData()
.request("get", path: "/bodyerror/status")
.hasStatus(.serviceUnavailable)
.hasContentType(withPrefix: "application/json")
.hasData(Status("BAD"))
.run()
}
func testBasicGetArray() {
router.get("/users") { (respondWith: ([User]?, RequestError?) -> Void) in
print("GET on /users")
respondWith(self.userStoreArray, nil)
}
router.get("/error/users") { (respondWith: ([User]?, RequestError?) -> Void) in
print("GET on /error/users")
respondWith(nil, .serviceUnavailable)
}
router.get("/bodyerror/users") { (respondWith: ([User]?, RequestError?) -> Void) in
print("GET on /bodyerror/users")
respondWith(nil, RequestError(.serviceUnavailable, body: Status("BAD")))
}
buildServerTest(router, timeout: 30)
.request("get", path: "/users")
.hasStatus(.OK)
.hasContentType(withPrefix: "application/json")
.hasData(self.userStoreArray)
.request("get", path: "/error/users")
.hasStatus(.serviceUnavailable)
.hasNoData()
.request("get", path: "/bodyerror/users")
.hasStatus(.serviceUnavailable)
.hasContentType(withPrefix: "application/json")
.hasData(Status("BAD"))
.run()
}
func testBasicGetIdentifiersArray() {
let intTuple: [(Int, User)] = [(1, User(id: 1, name: "Andy")), (2, User(id: 2, name: "Dave")), (3, User(id: 3, name: "Ian"))]
// expectedIntData = [["1": User(id: 1, name: "Andy")], ["2": User(id: 2, name: "Dave")], ["3": User(id: 3, name: "Ian")]]
let expectedIntData: [[String: User]] = intTuple.map({ [$0.value: $1] })
let stringTuple: [(String, User)] = [("1", User(id: 1, name: "Andy")), ("2", User(id: 2, name: "Dave")), ("3", User(id: 3, name: "Ian"))]
// expectedStringData = [["1": User(id: 1, name: "Andy")], ["2": User(id: 2, name: "Dave")], ["3": User(id: 3, name: "Ian")]]
let expectedStringData: [[String: User]] = stringTuple.map({ [$0.value: $1] })
router.get("/int/users") { (respondWith: ([(Int, User)]?, RequestError?) -> Void) in
print("GET on /int/users")
respondWith(intTuple, nil)
}
router.get("/int/explicitStatus") { (respondWith: ([(Int, User)]?, RequestError?) -> Void) in
print("GET on /int/explicitStatus")
respondWith(intTuple, .ok)
}
router.get("/string/users") { (respondWith: ([(String, User)]?, RequestError?) -> Void) in
print("GET on /string/users")
respondWith(stringTuple, nil)
}
router.get("/error/users") { (respondWith: ([(String, User)]?, RequestError?) -> Void) in
print("GET on /error/users")
respondWith(nil, .serviceUnavailable)
}
buildServerTest(router, timeout: 30)
.request("get", path: "/int/users")
.hasStatus(.OK)
.hasContentType(withPrefix: "application/json")
.hasData(expectedIntData)
.request("get", path: "/int/explicitStatus")
.hasStatus(.OK)
.hasContentType(withPrefix: "application/json")
.hasData(expectedIntData)
.request("get", path: "/string/users")
.hasStatus(.OK)
.hasContentType(withPrefix: "application/json")
.hasData(expectedStringData)
.request("get", path: "/error/users")
.hasStatus(.serviceUnavailable)
.hasNoData()
.run()
}
func testBasicGetSingle() {
router.get("/users") { (id: Int, respondWith: (User?, RequestError?) -> Void) in
print("GET on /users/\(id)")
guard let user = self.userStore[id] else {
XCTFail("ERROR!!! Couldn't find user with id \(id)")
respondWith(nil, .notFound)
return
}
respondWith(user, nil)
}
router.get("/error/users") { (id: Int, respondWith: (User?, RequestError?) -> Void) in
print("GET on /error/users/\(id)")
respondWith(nil, .serviceUnavailable)
}
router.get("/bodyerror/users") { (id: Int, respondWith: (User?, RequestError?) -> Void) in
print("GET on /bodyerror/users/\(id)")
respondWith(nil, RequestError(.serviceUnavailable, body: Status("BAD: \(id)")))
}
guard let user = self.userStore[1] else {
XCTFail("ERROR!!! Couldn't find user with id 1")
return
}
buildServerTest(router, timeout: 30)
.request("get", path: "/users/1")
.hasStatus(.OK)
.hasContentType(withPrefix: "application/json")
.hasData(user)
.request("get", path: "/error/users/1")
.hasStatus(.serviceUnavailable)
.hasNoData()
.request("get", path: "/bodyerror/users/1")
.hasStatus(.serviceUnavailable)
.hasContentType(withPrefix: "application/json")
.hasData(Status("BAD: 1"))
.run()
}
func testBasicDelete() {
router.delete("/users") { (respondWith: (RequestError?) -> Void) in
print("DELETE on /users")
self.userStore.removeAll()
respondWith(nil)
}
router.delete("/error/users") { (respondWith: (RequestError?) -> Void) in
print("DELETE on /error/users")
respondWith(.serviceUnavailable)
}
router.delete("/bodyerror/users") { (respondWith: (RequestError?) -> Void) in
print("DELETE on /bodyerror/users")
respondWith(RequestError(.serviceUnavailable, body: Status("BAD")))
}
buildServerTest(router, timeout: 30)
.request("delete", path: "/users")
.hasStatus(.noContent)
.hasNoData()
.has { _ in XCTAssertEqual(self.userStore.count, 0) }
.request("delete", path: "/error/users")
.hasStatus(.serviceUnavailable)
.hasNoData()
.request("delete", path: "/bodyerror/users")
.hasStatus(.serviceUnavailable)
.hasContentType(withPrefix: "application/json")
.hasData(Status("BAD"))
.run()
}
func testBasicDeleteSingle() {
router.delete("/users") { (id: Int, respondWith: (RequestError?) -> Void) in
print("DELETE on /users/\(id)")
guard let _ = self.userStore.removeValue(forKey: id) else {
respondWith(.notFound)
return
}
respondWith(nil)
}
router.delete("/error/users") { (id: Int, respondWith: (RequestError?) -> Void) in
print("DELETE on /error/users/\(id)")
respondWith(.serviceUnavailable)
}
router.delete("/bodyerror/users") { (id: Int, respondWith: (RequestError?) -> Void) in
print("DELETE on /bodyerror/users/\(id)")
respondWith(RequestError(.serviceUnavailable, body: Status("BAD: \(id)")))
}
buildServerTest(router, timeout: 30)
.request("delete", path: "/users/1")
.hasStatus(.noContent)
.hasNoData()
.has { _ in XCTAssertNil(self.userStore[1]) }
.request("delete", path: "/error/users/1")
.hasStatus(.serviceUnavailable)
.hasNoData()
.request("delete", path: "/bodyerror/users/1")
.hasStatus(.serviceUnavailable)
.hasContentType(withPrefix: "application/json")
.hasData(Status("BAD: 1"))
.run()
}
func testBasicPut() {
router.put("/users") { (id: Int, user: User, respondWith: (User?, RequestError?) -> Void) in
print("PUT on /users/\(id)")
self.userStore[id] = user
respondWith(user, nil)
}
router.put("/error/users") { (id: Int, user: User, respondWith: (User?, RequestError?) -> Void) in
print("PUT on /error/users/\(id)")
respondWith(nil, .serviceUnavailable)
}
router.put("/bodyerror/users") { (id: Int, user: User, respondWith: (User?, RequestError?) -> Void) in
print("PUT on /bodyerror/users/\(id)")
respondWith(nil, RequestError(.serviceUnavailable, body: Status("BAD: \(id)")))
}
XCTAssertEqual(self.userStore[1]?.name, "Mike")
let user = User(id: 1, name: "David")
buildServerTest(router, timeout: 30)
.request("put", path: "/users/1", data: user)
.hasStatus(.OK)
.hasContentType(withPrefix: "application/json")
.hasData(user)
.has { _ in XCTAssertEqual(self.userStore[1]?.name, "David") }
.request("put", path: "/error/users/1", data: user)
.hasStatus(.serviceUnavailable)
.hasNoData()
.request("put", path: "/bodyerror/users/1", data: user)
.hasStatus(.serviceUnavailable)
.hasContentType(withPrefix: "application/json")
.hasData(Status("BAD: 1"))
.run()
}
// Tests that a handler is able to return a nil response with a custom success status.
func testNoDataCustomStatus() {
router.put("/noBody") { (id: Int, data: User, respondWith: (User?, RequestError?) -> Void) in
print("PUT on /noBody/\(id)")
respondWith(nil, .noContent)
}
router.post("/noBody") { (data: User, respondWith: (Int?, User?, RequestError?) -> Void) in
print("POST on /noBody")
respondWith(1, nil, .noContent)
}
let user = User(id: 1, name: "David")
buildServerTest(router, timeout: 30)
.request("post", path: "/noBody", data: user)
.hasStatus(.noContent)
.hasHeader("Location", only: "1")
.hasNoData()
.request("put", path: "/noBody/1", data: user)
.hasStatus(.noContent)
.hasNoData()
.run()
}
// Tests that a handler is able to return a nil response with the default success status
// for that method.
func testNoDataDefaultStatus() {
router.put("/noBody") { (id: Int, data: User, respondWith: (User?, RequestError?) -> Void) in
print("PUT on /noBody/\(id)")
respondWith(nil, nil)
}
router.post("/noBody") { (data: User, respondWith: (Int?, User?, RequestError?) -> Void) in
print("POST on /noBody")
respondWith(1, nil, nil)
}
let user = User(id: 1, name: "David")
buildServerTest(router, timeout: 30)
.request("post", path: "/noBody", data: user)
.hasStatus(.created)
.hasHeader("Location", only: "1")
.hasNoData()
.request("put", path: "/noBody/1", data: user)
.hasStatus(.OK)
.hasNoData()
.run()
}
func testBasicPatch() {
router.patch("/users") { (id: Int, patchUser: OptionalUser, respondWith: (User?, RequestError?) -> Void) -> Void in
print("PATCH on /users/\(id)")
guard let existingUser = self.userStore[id] else {
respondWith(nil, .notFound)
return
}
if let patchUserName = patchUser.name {
let updatedUser = User(id: id, name: patchUserName)
self.userStore[id] = updatedUser
respondWith(updatedUser, nil)
} else {
respondWith(existingUser, nil)
}
}
router.patch("/error/users") { (id: Int, patchUser: OptionalUser, respondWith: (User?, RequestError?) -> Void) -> Void in
print("PATCH on /error/users/\(id)")
respondWith(nil, .serviceUnavailable)
}
router.patch("/bodyerror/users") { (id: Int, patchUser: OptionalUser, respondWith: (User?, RequestError?) -> Void) -> Void in
print("PATCH on /bodyerror/users/\(id)")
respondWith(nil, RequestError(.serviceUnavailable, body: Status("BAD: \(id)")))
}
XCTAssertEqual(self.userStore[2]?.name, "Chris")
let user = User(id: 2, name: "David")
buildServerTest(router, timeout: 30)
.request("patch", path: "/users/2", data: user)
.hasStatus(.OK)
.hasContentType(withPrefix: "application/json")
.hasData(user)
.has { _ in XCTAssertEqual(self.userStore[2]?.name, "David") }
.request("patch", path: "/error/users/2", data: user)
.hasStatus(.serviceUnavailable)
.hasNoData()
.request("patch", path: "/bodyerror/users/2", data: user)
.hasStatus(.serviceUnavailable)
.hasContentType(withPrefix: "application/json")
.hasData(Status("BAD: 2"))
.run()
}
func testErrorOverridesBody() {
let status = Status("This should not be sent")
router.get("/status") { (id: Int, respondWith: (Status?, RequestError?) -> Void) in respondWith(status, .conflict) }
router.post("/status") { (status: Status, respondWith: (Status?, RequestError?) -> Void) in respondWith(status, .conflict) }
router.put("/status") { (id: Int, status: Status, respondWith: (Status?, RequestError?) -> Void) in respondWith(status, .conflict) }
router.patch("/status") { (id: Int, status: Status, respondWith: (Status?, RequestError?) -> Void) in respondWith(status, .conflict) }
let conflict = Conflict(on: "life")
let bodyError = RequestError(.conflict, body: conflict)
router.get("/bodyerror/status") { (id: Int, respondWith: (Status?, RequestError?) -> Void) in respondWith(status, bodyError) }
router.post("/bodyerror/status") { (status: Status, respondWith: (Status?, RequestError?) -> Void) in respondWith(status, bodyError) }
router.put("/bodyerror/status") { (id: Int, status: Status, respondWith: (Status?, RequestError?) -> Void) in respondWith(status, bodyError) }
router.patch("/bodyerror/status") { (id: Int, status: Status, respondWith: (Status?, RequestError?) -> Void) in respondWith(status, bodyError) }
buildServerTest(router, timeout: 30)
.request("get", path: "/status/1")
.hasStatus(.conflict)
.hasNoData()
.request("post", path: "/status", data: status)
.hasStatus(.conflict)
.hasNoData()
.request("put", path: "/status/1", data: status)
.hasStatus(.conflict)
.hasNoData()
.request("patch", path: "/status/1", data: status)
.hasStatus(.conflict)
.hasNoData()
.request("get", path: "/bodyerror/status/1")
.hasStatus(.conflict)
.hasContentType(withPrefix: "application/json")
.hasData(conflict)
.request("post", path: "/bodyerror/status", data: status)
.hasStatus(.conflict)
.hasContentType(withPrefix: "application/json")
.hasData(conflict)
.request("put", path: "/bodyerror/status/1", data: status)
.hasStatus(.conflict)
.hasContentType(withPrefix: "application/json")
.hasData(conflict)
.request("patch", path: "/bodyerror/status/1", data: status)
.hasStatus(.conflict)
.hasContentType(withPrefix: "application/json")
.hasData(conflict)
.run()
}
// Test that we get an internalServerError when using BodyParser with a Codable route
func testCodableRoutesWithBodyParsingFail() {
// Add a BodyParser that covers everything
router.all(middleware: BodyParser())
let status = Status("Should not be seen")
router.put("/status") { (id: Int, status: Status, respondWith: (Status?, RequestError?) -> Void) in respondWith(status, nil) }
router.patch("/status") { (id: Int, status: Status, respondWith: (Status?, RequestError?) -> Void) -> Void in respondWith(status, nil) }
router.post("/status") { (status: Status, respondWith: (Status?, RequestError?) -> Void) -> Void in respondWith(status, nil) }
buildServerTest(router, timeout: 30)
.request("put", path: "/status/2", data: status).hasStatus(.internalServerError).hasNoData()
.request("patch", path: "/status/2", data: status).hasStatus(.internalServerError).hasNoData()
.request("post", path: "/status", data: status).hasStatus(.internalServerError).hasNoData()
.run()
}
func testCodableGetSingleQueryParameters() {
let date: Date = Coder.defaultDateFormatter.date(from: Coder.defaultDateFormatter.string(from: Date()))!
let expectedQuery = MyQuery(intField: 23, optionalIntField: 282, stringField: "a string", intArray: [1, 2, 3], dateField: date, optionalDateField: date, nested: Nested(nestedIntField: 333, nestedStringField: "nested string"))
guard let queryStr: String = try? QueryEncoder().encode(expectedQuery) else {
XCTFail("ERROR!!! Could not encode query object to string")
return
}
router.get("/query") { (query: MyQuery, respondWith: (MyQuery?, RequestError?) -> Void) in
XCTAssertEqual(query, expectedQuery)
respondWith(query, nil)
}
router.get("/optionalquery") { (query: MyQuery?, respondWith: (MyQuery?, RequestError?) -> Void) in
if let query = query {
XCTAssertEqual(query, expectedQuery)
respondWith(query, nil)
} else {
respondWith(nil, nil)
}
}
buildServerTest(router, timeout: 30)
.request("get", path: "/query\(queryStr)")
.hasStatus(.OK)
.hasContentType(withPrefix: "application/json")
.hasData(expectedQuery)
.request("get", path: "/query?param=badRequest")
.hasStatus(.badRequest)
.hasNoData()
.request("get", path: "/optionalquery\(queryStr)")
.hasStatus(.OK)
.hasContentType(withPrefix: "application/json")
.hasData(expectedQuery)
.request("get", path: "/optionalquery")
.hasStatus(.OK)
.hasNoData()
.request("get", path: "/optionalquery?param=badRequest")
.hasStatus(.badRequest)
.hasNoData()
.run()
}
func testCodableGetArrayQueryParameters() {
/// Currently the milliseconds are cut off by our date formatter
/// This synchronizes it for testing with the codable route
let date: Date = Coder.defaultDateFormatter.date(from: Coder.defaultDateFormatter.string(from: Date()))!
let expectedQuery = MyQuery(intField: 23, optionalIntField: 282, stringField: "a string", intArray: [1, 2, 3], dateField: date, optionalDateField: date, nested: Nested(nestedIntField: 333, nestedStringField: "nested string"))
guard let queryStr: String = try? QueryEncoder().encode(expectedQuery) else {
XCTFail("ERROR!!! Could not encode query object to string")
return
}
router.get("/query") { (query: MyQuery, respondWith: ([MyQuery]?, RequestError?) -> Void) in
XCTAssertEqual(query, expectedQuery)
respondWith([query], nil)
}
router.get("/optionalquery") { (query: MyQuery?, respondWith: ([MyQuery]?, RequestError?) -> Void) in
if let query = query {
XCTAssertEqual(query, expectedQuery)
respondWith([query], nil)
} else {
respondWith(nil, nil)
}
}
buildServerTest(router, timeout: 30)
.request("get", path: "/query\(queryStr)")
.hasStatus(.OK)
.hasContentType(withPrefix: "application/json")
.hasData([expectedQuery])
.request("get", path: "/optionalquery\(queryStr)")
.hasStatus(.OK)
.hasContentType(withPrefix: "application/json")
.hasData([expectedQuery])
.request("get", path: "/optionalquery")
.hasStatus(.OK)
.hasNoData()
.request("get", path: "/query?param=badRequest")
.hasStatus(.badRequest)
.hasNoData()
.request("get", path: "/optionalquery?param=badRequest")
.hasStatus(.badRequest)
.hasNoData()
.run()
}
func testCodableDeleteQueryParameters() {
/// Currently the milliseconds are cut off by our date formatter
/// This synchronizes it for testing with the codable route
let date: Date = Coder.defaultDateFormatter.date(from: Coder.defaultDateFormatter.string(from: Date()))!
let expectedQuery = MyQuery(intField: 23, optionalIntField: 282, stringField: "a string", intArray: [1, 2, 3], dateField: date, optionalDateField: date, nested: Nested(nestedIntField: 333, nestedStringField: "nested string"))
guard let queryStr: String = try? QueryEncoder().encode(expectedQuery) else {
XCTFail("ERROR!!! Could not encode query object to string")
return
}
router.delete("/query") { (query: MyQuery, respondWith: (RequestError?) -> Void) in
XCTAssertEqual(query, expectedQuery)
respondWith(nil)
}
router.delete("/optionalquery") { (query: MyQuery?, respondWith: (RequestError?) -> Void) in
if let query = query {
XCTAssertEqual(query, expectedQuery)
}
respondWith(nil)
}
buildServerTest(router, timeout: 30)
.request("delete", path: "/query\(queryStr)")
.hasStatus(.noContent)
.hasNoData()
.request("delete", path: "/query?param=badRequest")
.hasStatus(.badRequest)
.hasNoData()
.request("delete", path: "/optionalquery\(queryStr)")
.hasStatus(.noContent)
.hasNoData()
.request("delete", path: "/optionalquery")
.hasStatus(.noContent)
.hasNoData()
.request("delete", path: "/optionalquery?param=badRequest")
.hasStatus(.badRequest)
.hasNoData()
.run()
}
func testCodablePostSuccessStatuses() {
// Test POST success statuses other than .created
router.post("/ok") { (user: User, respondWith: (User?, RequestError?) -> Void) in
print("POST on /ok for user \(user)")
respondWith(user, .ok)
}
router.post("/partialContent") { (user: User, respondWith: (User?, RequestError?) -> Void) in
print("POST on /partialContent for user \(user)")
respondWith(user, .partialContent)
}
router.post("/okId") { (user: User, respondWith: (Int?, User?, RequestError?) -> Void) in
print("POST on /okId for user \(user)")
respondWith(user.id, user, .ok)
}
let user = User(id: 5, name: "Jane")
buildServerTest(router, timeout: 30)//
.request("post", path: "/ok", data: user)
.hasStatus(.OK)
.hasContentType(withPrefix: "application/json")
.hasData(user)
.request("post", path: "/partialContent", data: user)
.hasStatus(.partialContent)
.hasContentType(withPrefix: "application/json")
.hasData(user)
.request("post", path: "/okId", data: user)
.hasStatus(.OK)
.hasHeader("Location", only: String(user.id))
.hasContentType(withPrefix: "application/json")
.hasData(user)
.run()
}
}