Skip to content

Commit e68c51c

Browse files
committed
archive files for server test runs. add endpoint to retrieve files from server.
1 parent f6407f9 commit e68c51c

File tree

6 files changed

+120
-6
lines changed

6 files changed

+120
-6
lines changed

Sources/App/Controllers/APITestController.swift

Lines changed: 79 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import APITesting
99
/// Controls basic CRUD operations on `Todo`s.
1010
final class APITestController {
1111

12+
static let zipPathPrefix = Environment.archivesPath
1213
let outputPath: String
1314
let openAPISource: OpenAPISource
1415
let database: Database
@@ -55,6 +56,27 @@ final class APITestController {
5556
}
5657
}
5758

59+
func files(_ req: TypedRequest<FilesContext>) throws -> EventLoopFuture<Response> {
60+
guard let id = req.parameters.get("id", as: UUID.self) else {
61+
return req.response.badRequest
62+
}
63+
64+
let query = APITestDescriptor.query(on: database)
65+
.filter(\APITestDescriptor.$id == id)
66+
67+
return query.first()
68+
.unwrap(or: Abort(.notFound))
69+
.flatMap { req.fileio.collectFile(at: self.zipPath(for: $0)) }
70+
.flatMap { req.response.success.encode($0) }
71+
.flatMapError { error in
72+
guard let abortError = error as? Abort,
73+
abortError.status == .notFound else {
74+
return req.response.serverError
75+
}
76+
return req.response.notFound
77+
}
78+
}
79+
5880
/// Create an `APITestDescriptor` and run a new test suite.
5981
func create(_ req: TypedRequest<CreateContext>) throws -> EventLoopFuture<Response> {
6082
let reqUUIDGuess = req
@@ -72,18 +94,30 @@ final class APITestController {
7294

7395
savedDescriptor.whenSuccess { [weak self] in
7496

75-
guard let source = self?.openAPISource,
76-
let database = self?.database,
77-
let outPath = self?.outputPath,
78-
let eventLoop = self?.testEventLoop() else { return } // this just happens if the controller has been released from memory
97+
// this just happens if the controller has been released from memory
98+
// which we consider possible here because this whole process is async
99+
// and independent of the API request completion.
100+
guard let self = self else { return }
101+
102+
let source = self.openAPISource
103+
let database = self.database
104+
let outPath = self.outPath(for: descriptor)
105+
let zipPath = self.zipPath(for: descriptor)
106+
let eventLoop = self.testEventLoop()
107+
108+
req.logger.info("Running tests in \(outPath)")
79109

80110
prepOutputFolder(on: eventLoop, at: outPath, logger: logger)
81111
.flatMap { descriptor.markBuilding().save(on: database) }
82112
.flatMap { openAPIDoc(on: eventLoop, from: source) }
83-
.flatMap { openAPIDoc in produceAPITestPackage(on: eventLoop, given: openAPIDoc, to: outPath, logger: logger) }
113+
.flatMap { openAPIDoc in produceAPITestPackage(on: eventLoop, given: openAPIDoc, to: outPath, zipToPath: zipPath, logger: logger) }
84114
.flatMap { descriptor.markRunning().save(on: database) }
85115
.flatMap { runAPITestPackage(on: eventLoop, at: outPath, logger: logger) }
86116
.flatMap { descriptor.markPassed().save(on: database) }
117+
.always { _ in
118+
try? cleanupOutFolder(outPath, logger: logger)
119+
req.logger.info("Cleaning up tests in \(outPath)")
120+
}
87121
.whenFailure { error in
88122
req.logger.error("Testing Failed",
89123
metadata: ["error": .stringConvertible(String(describing: error))])
@@ -110,6 +144,16 @@ final class APITestController {
110144
return testEventLoopGroup.next()
111145
}
112146

147+
private func zipPath(for test: APITestDescriptor) -> String {
148+
return Self.zipPathPrefix
149+
+ "/\(test.id!.uuidString).zip"
150+
}
151+
152+
private func outPath(for test: APITestDescriptor) -> String {
153+
return self.outputPath
154+
+ "/\(test.id!.uuidString)/"
155+
}
156+
113157
// MARK: - Contexts
114158

115159
struct CreateContext: RouteContext {
@@ -214,6 +258,36 @@ final class APITestController {
214258

215259
static let builder = { return Self() }
216260
}
261+
262+
struct FilesContext: RouteContext {
263+
typealias RequestBodyType = EmptyRequestBody
264+
265+
let success: ResponseContext<ByteBuffer> =
266+
.init { response in
267+
response.status = .ok
268+
response.headers.contentType = .zip
269+
}
270+
271+
let notFound: CannedResponse<EmptyResponseBody> =
272+
.init(response: Response(
273+
status: .notFound
274+
)
275+
)
276+
277+
let badRequest: CannedResponse<EmptyResponseBody> =
278+
.init(response: Response(
279+
status: .badRequest
280+
)
281+
)
282+
283+
let serverError: CannedResponse<EmptyResponseBody> =
284+
.init(response: Response(
285+
status: .internalServerError
286+
)
287+
)
288+
289+
static let builder = { return Self() }
290+
}
217291
}
218292

219293
extension APITestController {

Sources/App/Environment/APITestEnvironment+database.swift renamed to Sources/App/Environment/APITestEnvironment+server.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// APITestEnvironment+database.swift
2+
// APITestEnvironment+server.swift
33
// App
44
//
55
// Created by Mathew Polzin on 9/28/19.
@@ -26,3 +26,11 @@ public extension Environment {
2626
case invalidUrl(String)
2727
}
2828
}
29+
30+
// MARK: - Archive Vars
31+
public extension Environment {
32+
/// Optional Archives path (folder location in which zip archives will be stored).
33+
static var archivesPath: String {
34+
return Environment.get("API_TEST_ARCHIVES_PATH") ?? "/app/archives/"
35+
}
36+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
//
2+
// ByteBuffer+ResponseEncodable.swift
3+
// App
4+
//
5+
// Created by Mathew Polzin on 12/4/19.
6+
//
7+
8+
import Vapor
9+
10+
extension ByteBuffer: ResponseEncodable {
11+
public func encodeResponse(for request: Request) -> EventLoopFuture<Response> {
12+
let response = Response(status: .ok, body: .init(buffer: self))
13+
return request.eventLoop.makeSucceededFuture(response)
14+
}
15+
}

Sources/App/Vapor Extensions/RouteContext.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@
88
import Vapor
99

1010
public struct EmptyRequestBody: Decodable {}
11+
public struct EmptyResponseBody: Encodable {}
12+
13+
extension EmptyResponseBody: ResponseEncodable {
14+
public func encodeResponse(for request: Request) -> EventLoopFuture<Response> {
15+
return "".encodeResponse(for: request)
16+
}
17+
}
1118

1219
public protocol AbstractRouteContext {
1320
var requestBodyType: Any.Type { get }

Sources/App/routes.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ public func routes(_ app: Application) throws {
1919
app.get("api_test", ":id", use: testController.show)
2020
.tags("Status")
2121
.summary("Retrieve a single test result")
22+
23+
app.get("api_test", ":id", "files", use: testController.files)
24+
.tags("Test Files")
25+
.summary("Retrieve the test files for the given test.")
2226
}
2327

2428
extension Route {

Sources/SwiftGen/ApiTestPackageUtil.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import OpenAPIKit
1010

1111
public func prepOutFolder(_ outPath: String, logger: Logger) throws {
1212
try? FileManager.default.removeItem(atPath: outPath + "/Tests/GeneratedAPITests")
13+
try? FileManager.default.removeItem(atPath: outPath + "/api_tests.log")
14+
try? FileManager.default.removeItem(atPath: outPath + "/.build")
1315

1416
if !FileManager.default.fileExists(atPath: outPath + "/Tests/GeneratedAPITests/resourceObjects") {
1517
try FileManager.default.createDirectory(atPath: outPath + "/Tests/GeneratedAPITests/resourceObjects",
@@ -18,6 +20,10 @@ public func prepOutFolder(_ outPath: String, logger: Logger) throws {
1820
}
1921
}
2022

23+
public func cleanupOutFolder(_ outPath: String, logger: Logger) throws {
24+
try FileManager.default.removeItem(atPath: outPath)
25+
}
26+
2127
public func runAPITestPackage(at path: String, logger: Logger) throws {
2228
let (exitCode, stdout) = shell("cd '\(path)' && swift test 2>&1")
2329

0 commit comments

Comments
 (0)