Skip to content

Commit

Permalink
Add some of the tests in paw file to xctest
Browse files Browse the repository at this point in the history
  • Loading branch information
adam-fowler committed Mar 9, 2024
1 parent e4c88fa commit 12fe2ff
Show file tree
Hide file tree
Showing 3 changed files with 276 additions and 31 deletions.
24 changes: 12 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
# Hummingbird Example Code

Examples converted to Hummingbird 2.0
- [auth-cognito](https://github.com/hummingbird-project/hummingbird-examples/tree/2.x.x/auth-cognito) - Authentication via AWS Cognito.
- [auth-jwt](https://github.com/hummingbird-project/hummingbird-examples/tree/2.x.x/auth-jwt) - Authentication using JWT.
- [hello](https://github.com/hummingbird-project/hummingbird-examples/tree/2.x.x/hello) - Basic application setup.
- [html-form](https://github.com/hummingbird-project/hummingbird-examples/tree/2.x.x/html-form) - Link HTML form to Hummingbird application.
- [http2](https://github.com/hummingbird-project/hummingbird-examples/tree/2.x.x/http2) - Basic application with HTTP2 upgrade added.
- [sessions](https://github.com/hummingbird-project/hummingbird-examples/tree/2.x.x/sessions) - Username/password and session authentication.
- [todos-dynamodb](https://github.com/hummingbird-project/hummingbird-examples/tree/2.x.x/todos-dynamodb) - Todos application, based off [TodoBackend](http://todobackend.com) spec, using DynamoDB.
- [auth-cognito](https://github.com/hummingbird-project/hummingbird-examples/tree/main/auth-cognito) - Authentication via AWS Cognito.
- [auth-jwt](https://github.com/hummingbird-project/hummingbird-examples/tree/main/auth-jwt) - Authentication using JWT.
- [graphql-server](https://github.com/hummingbird-project/hummingbird-examples/tree/main/graphql-server) - GraphQL server using [Graphiti](https://github.com/GraphQLSwift/Graphiti)
- [hello](https://github.com/hummingbird-project/hummingbird-examples/tree/main/hello) - Basic application setup.
- [html-form](https://github.com/hummingbird-project/hummingbird-examples/tree/main/html-form) - Link HTML form to Hummingbird application.
- [http2](https://github.com/hummingbird-project/hummingbird-examples/tree/main/http2) - Basic application with HTTP2 upgrade added.
- [sessions](https://github.com/hummingbird-project/hummingbird-examples/tree/main/sessions) - Username/password and session authentication.
- [todos-dynamodb](https://github.com/hummingbird-project/hummingbird-examples/tree/main/todos-dynamodb) - Todos application, based off [TodoBackend](http://todobackend.com) spec, using DynamoDB.
- [todos-lambda](https://github.com/hummingbird-project/hummingbird-examples/tree/main/todos-lambda) - Todos application, based off [TodoBackend](http://todobackend.com) spec, using DynamoDB and running on AWS Lambda.
- [todos-mongokitten-openapi](https://github.com/hummingbird-project/hummingbird-examples/tree/2.x.x/todos-mongokitten-openapi) - Todos application, using MongoDB driver [MongoKitten](https://github.com/orlandos-nl/MongoKitten) and the [OpenAPI runtime](https://github.com/apple/swift-openapi-runtime).
- [todos-postgres-tutorial](https://github.com/hummingbird-project/hummingbird-examples/tree/2.x.x/todos-postgres-tutorial) - Todos application, based off [TodoBackend](http://todobackend.com) spec, using PostgresNIO. Sample code that goes along with the [Todos tutorial](https://hummingbird-project.github.io/hummingbird-docs/2.0/tutorials/todos).
- [upload](https://github.com/hummingbird-project/hummingbird-examples/tree/2.x.x/upload) - File uploading and downloading.
- [webauthn](https://github.com/hummingbird-project/hummingbird-examples/tree/2.x.x/webauthn) - Web app demonstrating WebAuthn(PassKey) authentication.
- [todos-mongokitten-openapi](https://github.com/hummingbird-project/hummingbird-examples/tree/main/todos-mongokitten-openapi) - Todos application, using MongoDB driver [MongoKitten](https://github.com/orlandos-nl/MongoKitten) and the [OpenAPI runtime](https://github.com/apple/swift-openapi-runtime).
- [todos-postgres-tutorial](https://github.com/hummingbird-project/hummingbird-examples/tree/main/todos-postgres-tutorial) - Todos application, based off [TodoBackend](http://todobackend.com) spec, using PostgresNIO. Sample code that goes along with the [Todos tutorial](https://hummingbird-project.github.io/hummingbird-docs/2.0/tutorials/todos).
- [upload](https://github.com/hummingbird-project/hummingbird-examples/tree/main/upload) - File uploading and downloading.
- [webauthn](https://github.com/hummingbird-project/hummingbird-examples/tree/main/webauthn) - Web app demonstrating WebAuthn(PassKey) authentication.

And finally

- [todos-auth-fluent](https://github.com/hummingbird-project/hummingbird-examples/tree/main/todos-auth-fluent) - This is a more complete example which shows authentication, CRUD operations and mustache rendering all in one app.

Examples still working with Hummingbird 1.0
- [auth-srp](https://github.com/hummingbird-project/hummingbird-examples/tree/main/auth-srp) - Secure Remote Password authentication.
- [graphql-server](https://github.com/hummingbird-project/hummingbird-examples/tree/main/graphql-server) - GraphQL server using [Graphiti](https://github.com/GraphQLSwift/Graphiti)
- [ios-image-server](https://github.com/hummingbird-project/hummingbird-examples/tree/main/ios-image-server) - iOS web server that provides access to iPhone photo library.
- [jobs](https://github.com/hummingbird-project/hummingbird-examples/tree/main/jobs) - Demonstrating offloading of jobs to another server.
- [multipart-form](https://github.com/hummingbird-project/hummingbird-examples/tree/main/multipart-form) - HTML form using Multipart form data, using MultipartKit
Expand Down
4 changes: 2 additions & 2 deletions graphql-server/Sources/App/StarWarsAPI/StarWarsResolver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ extension Character {
nil
}

public func getFriends(context: StarWarsContext, arguments: NoArguments) -> [Character] {
[]
public func getFriends(context: StarWarsContext, arguments: NoArguments) async throws -> [Character] {
try await context.getFriends(of: self)
}
}

Expand Down
279 changes: 262 additions & 17 deletions graphql-server/Tests/AppTests/AppTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,122 @@ import HummingbirdTesting
import XCTest

final class AppTests: XCTestCase {
func testGraphQLSuccess() async throws {
func buildQuery(_ query: String, variables: [String: Any] = [:]) throws -> ByteBuffer {
struct AnyCodable: Encodable {
let value: Any

func encode(to encoder: any Encoder) throws {
var container = encoder.singleValueContainer()
switch self.value {
case let string as String:
try container.encode(string)
case let integer as Int:
try container.encode(integer)
default:
throw EncodingError.invalidValue(self.value, .init(codingPath: encoder.codingPath, debugDescription: .init("Cannot encode")))
}
}
}
struct Query: Encodable {
let query: String
let variables: [String: Any]

func encode(to encoder: any Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.query, forKey: .query)
try container.encode(self.variables.mapValues { AnyCodable(value: $0) }, forKey: .variables)
}

private enum CodingKeys: String, CodingKey {
case query, variables
}
}
let query = Query(query: query, variables: variables)
return try JSONEncoder().encodeAsByteBuffer(query, allocator: .init())
}

func testQuery(
_ query: String,
variables: [String: Any] = [:],
expectedResult: String,
client: some HBTestClientProtocol,
file: StaticString = #filePath,
line: UInt = #line
) async throws {
let testQuery = try self.buildQuery(query, variables: variables)
try await client.execute(
uri: "/graphql",
method: .post,
headers: [.contentType: "application/json; charset=utf-8"],
body: testQuery
) { res in
XCTAssertEqual(res.status, .ok)
XCTAssertEqual(String(buffer: res.body).trimmingCharacters(in: .whitespacesAndNewlines), expectedResult, file: file, line: line)
}
}

struct DataWrapper<Value: Codable>: Codable {
let data: Value
}

func testQuery<Result: Codable & Equatable>(
_ query: String,
variables: [String: Any] = [:],
expectedResult: Result,
client: some HBTestClientProtocol,
file: StaticString = #filePath,
line: UInt = #line
) async throws {
let testQuery = try self.buildQuery(query, variables: variables)
try await client.execute(
uri: "/graphql",
method: .post,
headers: [.contentType: "application/json; charset=utf-8"],
body: testQuery
) { res in
XCTAssertEqual(res.status, .ok)
let result = try JSONDecoder().decode(DataWrapper<Result>.self, from: res.body)
XCTAssertEqual(result.data, expectedResult, file: file, line: line)
}
}

// MARK: Tests

func testHeroNewHope() async throws {
let app = buildApplication(configuration: .init(address: .hostname("127.0.0.1", port: 8080)))
try await app.test(.router) { client in
let testQuery = """
{
"query": "{hero(episode:NEWHOPE){name}}",
"variables": {}
}
"""
let testBody = ByteBuffer(string: testQuery)
let expectedResult = #"{"data":{"hero":{"name":"R2-D2"}}}"#
try await client.execute(
uri: "/graphql",
method: .post,
headers: [.contentType: "application/json; charset=utf-8"],
body: testBody
) { res in
XCTAssertEqual(res.status, .ok)
XCTAssertEqual(String(buffer: res.body).trimmingCharacters(in: .whitespacesAndNewlines), expectedResult)
try await self.testQuery(
"{hero(episode:NEWHOPE){name}}",
expectedResult: #"{"data":{"hero":{"name":"R2-D2"}}}"#,
client: client
)
}
}

func testHeroNameAndFriends() async throws {
struct Result: Codable, Equatable {
struct Hero: Codable, Equatable {
struct Friend: Codable, ExpressibleByStringLiteral, Equatable {
let name: String
init(stringLiteral string: String) {
self.name = string
}
}

let id: String
let name: String
let friends: [Friend]
}

let hero: Hero
}
let app = buildApplication(configuration: .init(address: .hostname("127.0.0.1", port: 8080)))
try await app.test(.router) { client in
try await self.testQuery(
"query HeroNameAndFriendsQuery{hero{id name friends{name}}}",
expectedResult: Result(hero: .init(id: "2001", name: "R2-D2", friends: ["Luke Skywalker", "Han Solo", "Leia Organa"])),
client: client
)
}
}

Expand All @@ -42,4 +138,153 @@ final class AppTests: XCTestCase {
}
}
}

func testFetchLuke() async throws {
let app = buildApplication(configuration: .init(address: .hostname("127.0.0.1", port: 8080)))
try await app.test(.router) { client in
try await self.testQuery(
"query FetchLukeQuery{human(id:\"1000\"){name}}",
expectedResult: #"{"data":{"human":{"name":"Luke Skywalker"}}}"#,
client: client
)
}
}

func testFetchSomeID() async throws {
let app = buildApplication(configuration: .init(address: .hostname("127.0.0.1", port: 8080)))
try await app.test(.router) { client in
try await self.testQuery(
"query FetchSomeIDQuery($someId:String!){human(id:$someId){name}}",
variables: ["someId": 1002],
expectedResult: #"{"data":{"human":{"name":"Han Solo"}}}"#,
client: client
)
}
}

func testFetchLukeAliased() async throws {
let app = buildApplication(configuration: .init(address: .hostname("127.0.0.1", port: 8080)))
try await app.test(.router) { client in
try await self.testQuery(
"query FetchLukeAliasedQuery{luke:human(id:\"1000\"){name}}",
expectedResult: #"{"data":{"luke":{"name":"Luke Skywalker"}}}"#,
client: client
)
}
}

func testDuplicateFields() async throws {
struct Result: Codable, Equatable {
struct Character: Codable, Equatable {
struct HomePlanet: Codable, Equatable, ExpressibleByStringLiteral {
let name: String
init(stringLiteral string: String) {
self.name = string
}
}

let name: String
let homePlanet: HomePlanet
}

let luke: Character
let leia: Character
}
let app = buildApplication(configuration: .init(address: .hostname("127.0.0.1", port: 8080)))
try await app.test(.router) { client in
try await self.testQuery(
"query DuplicateFieldsQuery{luke:human(id:\"1000\"){name homePlanet{name}}leia:human(id:\"1003\"){name homePlanet{name}}}",
expectedResult: Result(
luke: .init(name: "Luke Skywalker", homePlanet: "Tatooine"),
leia: .init(name: "Leia Organa", homePlanet: "Alderaan")
),
client: client
)
}
}

func testUseFragment() async throws {
struct Result: Codable, Equatable {
struct Character: Codable, Equatable {
struct HomePlanet: Codable, Equatable, ExpressibleByStringLiteral {
let name: String
init(stringLiteral string: String) {
self.name = string
}
}

let name: String
let homePlanet: HomePlanet
}

let luke: Character
let leia: Character
}
let app = buildApplication(configuration: .init(address: .hostname("127.0.0.1", port: 8080)))
try await app.test(.router) { client in
try await self.testQuery(
"query UseFragmentQuery{luke:human(id:\"1000\"){...HumanFragment}leia:human(id:\"1003\"){...HumanFragment}}fragment HumanFragment on Human{name homePlanet{name}}",
expectedResult: Result(
luke: .init(name: "Luke Skywalker", homePlanet: "Tatooine"),
leia: .init(name: "Leia Organa", homePlanet: "Alderaan")
),
client: client
)
}
}

func testTypeOfR2D2() async throws {
struct Result: Codable, Equatable {
struct Character: Codable, Equatable {
let name: String
let typename: String

enum CodingKeys: String, CodingKey {
case name
case typename = "__typename"
}
}

let hero: Character
}
let app = buildApplication(configuration: .init(address: .hostname("127.0.0.1", port: 8080)))
try await app.test(.router) { client in
try await self.testQuery(
"query CheckTypeOfR2Query{hero{__typename name}}",
expectedResult: Result(hero: .init(name: "R2-D2", typename: "Droid")),
client: client
)
}
}

func testSearch() async throws {
struct Result: Codable, Equatable {
struct SearchResult: Codable, Equatable {
let name: String
let primaryFunction: String?
let diameter: Int?

init(name: String, primaryFunction: String? = nil, diameter: Int? = nil) {
self.name = name
self.primaryFunction = primaryFunction
self.diameter = diameter
}
}

let search: [SearchResult]
}
let app = buildApplication(configuration: .init(address: .hostname("127.0.0.1", port: 8080)))
try await app.test(.router) { client in
try await self.testQuery(
"query{search(query:\"o\"){... on Planet{name diameter}... on Human{name}... on Droid{name primaryFunction}}}",
expectedResult: Result(search: [
.init(name: "Tatooine", diameter: 10465),
.init(name: "Han Solo"),
.init(name: "Leia Organa"),
.init(name: "C-3PO", primaryFunction: "Protocol"),
]),
client: client
)
}
}
}

0 comments on commit 12fe2ff

Please sign in to comment.