Skip to content

Commit

Permalink
Fix Tests
Browse files Browse the repository at this point in the history
  • Loading branch information
joshuawright11 committed Jul 15, 2024
1 parent be46f14 commit 12e73e7
Show file tree
Hide file tree
Showing 22 changed files with 112 additions and 166 deletions.
26 changes: 0 additions & 26 deletions Alchemy/HTTP/ByteStream.swift
Original file line number Diff line number Diff line change
@@ -1,32 +1,6 @@
import HummingbirdCore
import NIOCore

public struct StreamWriter: AsyncSequence {
public typealias Element = ByteBuffer

public struct AsyncIterator: AsyncIteratorProtocol {
mutating public func next() async throws -> ByteBuffer? {
fatalError()
}
}

public func makeAsyncIterator() -> AsyncIterator {
AsyncIterator()
}

public func write(_ chunk: ByteBuffer) async throws {
fatalError()
}

public func finish() {
fatalError()
}

public func write(_ string: String) async throws {
fatalError()
}
}

/// A stream of bytes, that are delivered in sequential `ByteBuffer`s.
//public final class ByteStream: AsyncSequence {
// public struct Writer {
Expand Down
25 changes: 25 additions & 0 deletions Alchemy/HTTP/Bytes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,20 @@ import HummingbirdCore

/// A collection of bytes that is either a single buffer or a stream of buffers.
public enum Bytes: ExpressibleByStringLiteral {
public typealias Streamer = (Writer) async throws -> Void

public struct Writer {
let continuation: AsyncStream<ByteBuffer>.Continuation

public func write(_ chunk: ByteBuffer) {
continuation.yield(chunk)
}

public func finish() {
continuation.finish()
}
}

/// The default decoder for reading content from an incoming request.
public static var defaultDecoder: HTTPDecoder = .json
/// The default encoder for writing content to an outgoing response.
Expand Down Expand Up @@ -80,6 +94,17 @@ public enum Bytes: ExpressibleByStringLiteral {
return .buffer(buffer)
}

public static func stream(streamer: @escaping Streamer) -> Bytes {
.stream(
AsyncStream { continuation in
Task {
let writer = Writer(continuation: continuation)
try await streamer(writer)
}
}
)
}

public static func stream<AS: AsyncSequence>(sequence: AS) -> Bytes where AS.Element == ByteBuffer {
.stream(
AsyncStream<ByteBuffer> { continuation in
Expand Down
26 changes: 14 additions & 12 deletions Alchemy/HTTP/Client.swift
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,7 @@ private class ResponseDelegate: HTTPClientResponseDelegate {
case idle
case head(HTTPResponseHead)
case body(HTTPResponseHead, ByteBuffer)
case stream(HTTPResponseHead, StreamWriter)
case stream(HTTPResponseHead, Bytes.Writer)
case error(Error)
}

Expand Down Expand Up @@ -355,24 +355,26 @@ private class ResponseDelegate: HTTPClientResponseDelegate {
return task.eventLoop.makeSucceededFuture(())
case .body(let head, var body):
if allowStreaming {
let stream = StreamWriter()
// TODO: use this for defering
let (stream, continuation) = AsyncStream.makeStream(of: ByteBuffer.self)
let writer = Bytes.Writer(continuation: continuation)
let status = HTTPResponse.Status.init(integerLiteral: Int(head.status.code))
let headers = HTTPFields(head.headers, splitCookie: false)
let response = Client.Response(
request: request,
host: request.host,
status: status,
headers: headers,
body: .stream(sequence: stream)
body: .stream(stream)
)

self.responsePromise.succeed(response)
self.state = .stream(head, stream)
self.state = .stream(head, writer)

// Write the previous part, followed by this part, to the stream.
return Loop.asyncSubmit {
try await stream.write(body)
try await stream.write(part)
return Loop.submit {
writer.write(body)
writer.write(part)
}
} else {
// The compiler can't prove that `self.state` is dead here (and it kinda isn't, there's
Expand All @@ -386,8 +388,8 @@ private class ResponseDelegate: HTTPClientResponseDelegate {
return task.eventLoop.makeSucceededVoidFuture()
}
case .stream(_, let stream):
return Loop.asyncSubmit {
try await stream.write(part)
return Loop.submit {
stream.write(part)
}
case .error:
return task.eventLoop.makeSucceededFuture(())
Expand All @@ -413,8 +415,8 @@ private class ResponseDelegate: HTTPClientResponseDelegate {
let headers = HTTPFields(head.headers, splitCookie: false)
let response = Client.Response(request: request, host: request.host, status: status, headers: headers, body: .buffer(body))
responsePromise.succeed(response)
case .stream(_, let stream):
stream.finish()
case .stream(_, let writer):
writer.finish()
case .error:
break
}
Expand Down
27 changes: 26 additions & 1 deletion Alchemy/HTTP/Response.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,36 @@ public final class Response {
}

/// Creates a new response using a status code, headers and `ByteBuffer` for
/// the body..
/// the body.
public convenience init(status: HTTPResponse.Status = .ok, headers: HTTPFields = [:], buffer: ByteBuffer, contentType: ContentType? = .octetStream) {
self.init(status: status, headers: headers, body: .buffer(buffer), contentType: contentType)
}

/// Initialize this response with a closure that will be called, allowing
/// you to directly write headers, body, and end to the response. The
/// request connection will be left open until the closure finishes.
///
/// Usage:
/// ```swift
/// app.get("/stream") {
/// Response(status: .ok, headers: ["Content-Length": "248"]) { writer in
/// writer.write(...)
/// writer.write(...)
/// }
/// }
/// ```
///
/// - Parameter writer: A closure take a `ResponseWriter` and
/// using it to write response data to a remote peer.
public convenience init(
status: HTTPResponse.Status = .ok,
headers: HTTPFields = [:],
streamer: @escaping Bytes.Streamer,
contentType: ContentType? = .octetStream
) {
self.init(status: status, headers: headers, body: .stream(streamer: streamer), contentType: contentType)
}

public convenience init(status: HTTPResponse.Status = .ok, headers: HTTPFields = [:], stream: AsyncStream<ByteBuffer>, contentType: ContentType? = .octetStream) {
self.init(status: status, headers: headers, body: .stream(stream), contentType: contentType)
}
Expand Down
4 changes: 2 additions & 2 deletions Tests/AlchemyTest/ClientAssertionTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ final class ClientAssertionTests: TestCase<TestApp> {
func testAssertSent() async throws {
_ = try await Http.get("https://localhost:3000/foo?bar=baz")
Http.assertSent(1) {
$0.hasMethod(.GET) &&
$0.hasMethod(.get) &&
$0.hasPath("/foo") &&
$0.hasQuery("bar", value: "baz")
}
Expand All @@ -22,7 +22,7 @@ final class ClientAssertionTests: TestCase<TestApp> {
.withJSON(User(name: "Cyanea", age: 35))
.post("https://localhost:3000/bar")
Http.assertSent(2) {
$0.hasMethod(.POST) &&
$0.hasMethod(.post) &&
$0.hasPath("/bar") &&
$0["name"] == "Cyanea" &&
$0["age"] == 35
Expand Down
1 change: 0 additions & 1 deletion Tests/Cache/CacheTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ final class CacheTests: TestCase<TestApp> {
let client = RedisClient.testing
Container.register(client).singleton()
Container.register(Cache.redis).singleton()
app.lifecycle.registerShutdown(label: "Redis", .async(client.shutdown))

guard await Redis.checkAvailable() else {
throw XCTSkip()
Expand Down
2 changes: 1 addition & 1 deletion Tests/Database/Query/QueryTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ private struct TestModel: Seedable, Equatable {
var bar: Bool

static func generate() async throws -> TestModel {
TestModel(foo: faker.lorem.word(), bar: faker.number.randomBool())
TestModel(foo: .random, bar: .random())
}

struct Migration: Alchemy.Migration {
Expand Down
2 changes: 1 addition & 1 deletion Tests/Database/Rune/Model/ModelCrudTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ private struct TestModel: Seedable, Equatable {
var bar: Bool

static func generate() async throws -> TestModel {
TestModel(foo: faker.lorem.word(), bar: faker.number.randomBool())
TestModel(foo: .random, bar: .random())
}
}

Expand Down
4 changes: 2 additions & 2 deletions Tests/Database/Rune/Relations/EagerLoadableTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ private struct TestParent: Seedable, Equatable {
var baz: String

static func generate() async throws -> TestParent {
TestParent(baz: faker.lorem.word())
TestParent(baz: .random)
}
}

Expand All @@ -49,7 +49,7 @@ private struct TestModel: Seedable, Equatable {
parent = try await .seed()
}

return TestModel(foo: faker.lorem.word(), bar: faker.number.randomBool(), testParentId: parent.id)
return TestModel(foo: .random, bar: .random(), testParentId: parent.id)
}

static func == (lhs: TestModel, rhs: TestModel) -> Bool {
Expand Down
4 changes: 2 additions & 2 deletions Tests/Database/_Fixtures/Models.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ struct SeedModel: Seedable {
let email: String

static func generate() -> SeedModel {
SeedModel(name: faker.name.name(), email: faker.internet.email())
SeedModel(name: .random, email: .random)
}
}

Expand All @@ -46,6 +46,6 @@ struct OtherSeedModel: Seedable {
let bar: Bool

static func generate() -> OtherSeedModel {
OtherSeedModel(foo: faker.number.randomInt(), bar: .random())
OtherSeedModel(foo: .random(in: 0...100), bar: .random())
}
}
9 changes: 0 additions & 9 deletions Tests/HTTP/ByteStreamTests.swift

This file was deleted.

8 changes: 4 additions & 4 deletions Tests/HTTP/ClientTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@ final class ClientTests: TestCase<TestApp> {
}

func testHeaders() {
let headers: HTTPFields = ["foo":"bar"]
let headers: HTTPFields = [.accept:"bar"]
XCTAssertEqual(Client.Response(headers: headers).headers, headers)
XCTAssertEqual(Client.Response(headers: headers).header("foo"), "bar")
XCTAssertEqual(Client.Response(headers: headers).header("baz"), nil)
XCTAssertEqual(Client.Response(headers: headers).header(.accept), "bar")
XCTAssertEqual(Client.Response(headers: headers).header(.age), nil)
}

func testBody() {
Expand Down Expand Up @@ -77,6 +77,6 @@ final class ClientTests: TestCase<TestApp> {

extension Client.Response {
fileprivate init(_ status: HTTPResponse.Status = .ok, headers: HTTPFields = [:], body: Bytes? = nil) {
self.init(request: Client.Request(url: ""), host: "https://example.com", status: status, version: .http1_1, headers: headers, body: body)
self.init(request: Client.Request(url: ""), host: "https://example.com", status: status, headers: headers, body: body)
}
}
2 changes: 1 addition & 1 deletion Tests/HTTP/Commands/ServeCommandTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ final class ServeCommandTests: TestCase<TestApp> {

func testServeWithSideEffects() async throws {
app.get("/foo", use: { _ in "hello" })
try await app.lifecycle.start()
try await app.serviceGroup.run()
try await ServeCommand(workers: 2, schedule: true, migrate: true).run()
try await Http.get("http://127.0.0.1:3000/foo")
.assertBody("hello")
Expand Down
11 changes: 5 additions & 6 deletions Tests/HTTP/Errors/ClientErrorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ final class ClientErrorTests: TestCase<TestApp> {
func testClientError() async throws {
let request = Client.Request(
url: "http://localhost/foo",
method: .POST,
headers: ["foo": "bar"],
method: .post,
headers: [.accept: "bar"],
body: "foo"
)

Expand All @@ -19,9 +19,8 @@ final class ClientErrorTests: TestCase<TestApp> {
request: request,
host: "alchemy",
status: .conflict,
version: .http1_1,
headers: [
"foo": "bar"
.accept: "bar"
],
body: "bar"
)
Expand All @@ -34,14 +33,14 @@ final class ClientErrorTests: TestCase<TestApp> {
*** Request ***
URL: POST http://localhost/foo
Headers: [
foo: bar
Accept: bar
]
Body: foo
*** Response ***
Status: 409 Conflict
Headers: [
foo: bar
Accept: bar
]
Body: bar
"""
Expand Down
Loading

0 comments on commit 12e73e7

Please sign in to comment.