From 92029bfbcb30a0373ba5e836e9db28dd1532a4d5 Mon Sep 17 00:00:00 2001 From: "Sven A. Schmidt" Date: Sat, 30 Nov 2024 10:41:51 +0100 Subject: [PATCH 1/5] Add HTTPClient --- Sources/App/Core/AppEnvironment.swift | 4 +- .../App/Core/Dependencies/HTTPClient.swift | 37 +++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 Sources/App/Core/Dependencies/HTTPClient.swift diff --git a/Sources/App/Core/AppEnvironment.swift b/Sources/App/Core/AppEnvironment.swift index ddae1d52d..a49b986f1 100644 --- a/Sources/App/Core/AppEnvironment.swift +++ b/Sources/App/Core/AppEnvironment.swift @@ -152,11 +152,11 @@ extension AppEnvironment { private enum Networking { static func fetchHTTPStatusCode(_ url: String) async throws -> HTTPStatus { - var config = HTTPClient.Configuration() + var config = Vapor.HTTPClient.Configuration() // We're forcing HTTP/1 due to a bug in Github's HEAD request handling // https://github.com/SwiftPackageIndex/SwiftPackageIndex-Server/issues/1676 config.httpVersion = .http1Only - let client = HTTPClient(eventLoopGroupProvider: .singleton, configuration: config) + let client = Vapor.HTTPClient(eventLoopGroupProvider: .singleton, configuration: config) return try await run { var req = HTTPClientRequest(url: url) req.method = .HEAD diff --git a/Sources/App/Core/Dependencies/HTTPClient.swift b/Sources/App/Core/Dependencies/HTTPClient.swift new file mode 100644 index 000000000..0bd53565f --- /dev/null +++ b/Sources/App/Core/Dependencies/HTTPClient.swift @@ -0,0 +1,37 @@ +// Copyright Dave Verwer, Sven A. Schmidt, and other contributors. +// +// 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 AsyncHTTPClient +import Dependencies +import DependenciesMacros + + +@DependencyClient +struct HTTPClient { + typealias Response = AsyncHTTPClient.HTTPClient.Response + + var fetchDocumentation: @Sendable (_ url: String) async throws -> Response +} + +extension HTTPClient: DependencyKey { + static var liveValue: HTTPClient { + .init( + fetchDocumentation: { url in + try await AsyncHTTPClient.HTTPClient.shared.get(url: url).get() + } + ) + } +} + + From 93f61f15cdfa2bb3a49351011363bc1f1b99ddcf Mon Sep 17 00:00:00 2001 From: "Sven A. Schmidt" Date: Sat, 30 Nov 2024 11:12:53 +0100 Subject: [PATCH 2/5] User shared client --- .../PackageController+routes.swift | 22 +++++++++---------- Sources/App/Core/AppEnvironment.swift | 1 + .../App/Core/Dependencies/HTTPClient.swift | 19 ++++++++++++---- .../PackageController+routesTests.swift | 4 +++- 4 files changed, 30 insertions(+), 16 deletions(-) diff --git a/Sources/App/Controllers/PackageController+routes.swift b/Sources/App/Controllers/PackageController+routes.swift index 0e0a0f308..e841c5667 100644 --- a/Sources/App/Controllers/PackageController+routes.swift +++ b/Sources/App/Controllers/PackageController+routes.swift @@ -74,19 +74,18 @@ enum PackageController { ) case .css, .data, .faviconIco, .faviconSvg, .images, .img, .index, .js, .linkablePaths, .themeSettings, .svgImages, .svgImg, .videos: - return try await res.encodeResponse( + return try await ClientResponse( status: .ok, headers: req.headers .replacingOrAdding(name: .contentType, value: route.fragment.contentType) - .replacingOrAdding(name: .cacheControl, value: "no-transform"), - for: req - ) + .replacingOrAdding(name: .cacheControl, value: "no-transform") + ).encodeResponse(for: req) } } static func documentationResponse(req: Request, route: DocRoute, - awsResponse: ClientResponse, + awsResponse: HTTPClient.Response, documentationMetadata: DocumentationMetadata) async throws -> Response { guard let documentation = documentationMetadata.versions[reference: route.docVersion.reference] else { @@ -146,12 +145,11 @@ enum PackageController { updatedAt: documentation.updatedAt, rawHtml: body.asString()) else { - return try await awsResponse.encodeResponse( + return try await ClientResponse( status: .ok, headers: req.headers.replacingOrAdding(name: .contentType, - value: route.contentType), - for: req - ) + value: route.contentType) + ).encodeResponse(for: req) } return try await processor.processedPage.encodeResponse( @@ -162,9 +160,11 @@ enum PackageController { ) } - static func awsResponse(client: Client, route: DocRoute) async throws -> ClientResponse { + static func awsResponse(client: Client, route: DocRoute) async throws -> HTTPClient.Response { + @Dependency(\.httpClient) var httpClient + let url = try Self.awsDocumentationURL(route: route) - guard let response = try? await Current.fetchDocumentation(client, url) else { + guard let response = try? await httpClient.fetchDocumentation(url) else { throw Abort(.notFound) } guard (200..<399).contains(response.status.code) else { diff --git a/Sources/App/Core/AppEnvironment.swift b/Sources/App/Core/AppEnvironment.swift index a49b986f1..396a55a77 100644 --- a/Sources/App/Core/AppEnvironment.swift +++ b/Sources/App/Core/AppEnvironment.swift @@ -23,6 +23,7 @@ import FoundationNetworking struct AppEnvironment: Sendable { + @available(*, deprecated) var fetchDocumentation: @Sendable (_ client: Client, _ url: URI) async throws -> ClientResponse var fetchHTTPStatusCode: @Sendable (_ url: String) async throws -> HTTPStatus var fetchLicense: @Sendable (_ client: Client, _ owner: String, _ repository: String) async -> Github.License? diff --git a/Sources/App/Core/Dependencies/HTTPClient.swift b/Sources/App/Core/Dependencies/HTTPClient.swift index 0bd53565f..fe5fba3a3 100644 --- a/Sources/App/Core/Dependencies/HTTPClient.swift +++ b/Sources/App/Core/Dependencies/HTTPClient.swift @@ -12,26 +12,37 @@ // See the License for the specific language governing permissions and // limitations under the License. -import AsyncHTTPClient import Dependencies import DependenciesMacros +import Vapor @DependencyClient struct HTTPClient { - typealias Response = AsyncHTTPClient.HTTPClient.Response + typealias Response = Vapor.HTTPClient.Response - var fetchDocumentation: @Sendable (_ url: String) async throws -> Response + var fetchDocumentation: @Sendable (_ url: URI) async throws -> Response } extension HTTPClient: DependencyKey { static var liveValue: HTTPClient { .init( fetchDocumentation: { url in - try await AsyncHTTPClient.HTTPClient.shared.get(url: url).get() + try await Vapor.HTTPClient.shared.get(url: url.string).get() } ) } } +extension HTTPClient: TestDependencyKey { + static var testValue: Self { Self() } +} + + +extension DependencyValues { + var httpClient: HTTPClient { + get { self[HTTPClient.self] } + set { self[HTTPClient.self] = newValue } + } +} diff --git a/Tests/AppTests/PackageController+routesTests.swift b/Tests/AppTests/PackageController+routesTests.swift index cf339fd4b..2864c34c8 100644 --- a/Tests/AppTests/PackageController+routesTests.swift +++ b/Tests/AppTests/PackageController+routesTests.swift @@ -567,6 +567,9 @@ class PackageController_routesTests: SnapshotTestCase { try await withDependencies { $0.environment.awsDocsBucket = { "docs-bucket" } $0.environment.currentReferenceCache = { nil } + $0.httpClient.fetchDocumentation = { @Sendable _ in + .init(host: "", status: .ok, version: .http2, headers: .init(), body: .mockIndexHTML()) + } } operation: { // setup let pkg = try await savePackage(on: app.db, "1") @@ -588,7 +591,6 @@ class PackageController_routesTests: SnapshotTestCase { packageName: "pkg", reference: .tag(1, 0, 0)) .save(on: app.db) - Current.fetchDocumentation = { _, _ in .init(status: .ok, body: .mockIndexHTML()) } // MUT From 1bed95b86974e3ba4390262a0ca9b41c70b4dcb6 Mon Sep 17 00:00:00 2001 From: "Sven A. Schmidt" Date: Sat, 30 Nov 2024 11:28:43 +0100 Subject: [PATCH 3/5] Add convenience initialiser --- Sources/App/Core/Dependencies/HTTPClient.swift | 13 +++++++++++++ Tests/AppTests/PackageController+routesTests.swift | 4 +--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/Sources/App/Core/Dependencies/HTTPClient.swift b/Sources/App/Core/Dependencies/HTTPClient.swift index fe5fba3a3..888c03d2c 100644 --- a/Sources/App/Core/Dependencies/HTTPClient.swift +++ b/Sources/App/Core/Dependencies/HTTPClient.swift @@ -46,3 +46,16 @@ extension DependencyValues { set { self[HTTPClient.self] = newValue } } } + + +#if DEBUG +// Convenience initialiser to make testing easier +extension HTTPClient.Response { + init(status: HTTPResponseStatus, + version: HTTPVersion = .http1_1, + headers: HTTPHeaders = .init(), + body: ByteBuffer? = nil) { + self.init(host: "host", status: status, version: version, headers: headers, body: body) + } +} +#endif diff --git a/Tests/AppTests/PackageController+routesTests.swift b/Tests/AppTests/PackageController+routesTests.swift index 2864c34c8..2b77e5a7c 100644 --- a/Tests/AppTests/PackageController+routesTests.swift +++ b/Tests/AppTests/PackageController+routesTests.swift @@ -567,9 +567,7 @@ class PackageController_routesTests: SnapshotTestCase { try await withDependencies { $0.environment.awsDocsBucket = { "docs-bucket" } $0.environment.currentReferenceCache = { nil } - $0.httpClient.fetchDocumentation = { @Sendable _ in - .init(host: "", status: .ok, version: .http2, headers: .init(), body: .mockIndexHTML()) - } + $0.httpClient.fetchDocumentation = { @Sendable _ in .init(status: .ok, body: .mockIndexHTML()) } } operation: { // setup let pkg = try await savePackage(on: app.db, "1") From 3cb0508b486368dc20d3b8e72ae07a3e2e55149e Mon Sep 17 00:00:00 2001 From: "Sven A. Schmidt" Date: Sat, 30 Nov 2024 12:05:54 +0100 Subject: [PATCH 4/5] Move Current.fetchDocumentation to HTTPClient --- Sources/App/Core/AppEnvironment.swift | 3 - .../App/Core/Dependencies/HTTPClient.swift | 16 ++- .../AppTests/Mocks/AppEnvironment+mock.swift | 1 - .../PackageController+routesTests.swift | 101 +++++------------- Tests/AppTests/RoutesTests.swift | 15 +-- Tests/AppTests/SitemapTests.swift | 44 ++++---- 6 files changed, 67 insertions(+), 113 deletions(-) diff --git a/Sources/App/Core/AppEnvironment.swift b/Sources/App/Core/AppEnvironment.swift index 396a55a77..f71d5e11b 100644 --- a/Sources/App/Core/AppEnvironment.swift +++ b/Sources/App/Core/AppEnvironment.swift @@ -23,8 +23,6 @@ import FoundationNetworking struct AppEnvironment: Sendable { - @available(*, deprecated) - var fetchDocumentation: @Sendable (_ client: Client, _ url: URI) async throws -> ClientResponse var fetchHTTPStatusCode: @Sendable (_ url: String) async throws -> HTTPStatus var fetchLicense: @Sendable (_ client: Client, _ owner: String, _ repository: String) async -> Github.License? var fetchMetadata: @Sendable (_ client: Client, _ owner: String, _ repository: String) async throws -> Github.Metadata @@ -86,7 +84,6 @@ extension AppEnvironment { nonisolated(unsafe) static var logger: Logger! static let live = AppEnvironment( - fetchDocumentation: { client, url in try await client.get(url) }, fetchHTTPStatusCode: { url in try await Networking.fetchHTTPStatusCode(url) }, fetchLicense: { client, owner, repo in await Github.fetchLicense(client:client, owner: owner, repository: repo) }, fetchMetadata: { client, owner, repo in try await Github.fetchMetadata(client:client, owner: owner, repository: repo) }, diff --git a/Sources/App/Core/Dependencies/HTTPClient.swift b/Sources/App/Core/Dependencies/HTTPClient.swift index 888c03d2c..87eae4bc4 100644 --- a/Sources/App/Core/Dependencies/HTTPClient.swift +++ b/Sources/App/Core/Dependencies/HTTPClient.swift @@ -49,7 +49,17 @@ extension DependencyValues { #if DEBUG -// Convenience initialiser to make testing easier +// Convenience initialisers to make testing easier + +extension HTTPClient { + static func echoURL(headers: HTTPHeaders = .init()) -> @Sendable (_ url: URI) async throws -> Response { + { url in + // echo url.path in the body as a simple way to test the requested url + .init(status: .ok, headers: headers, body: .init(string: url.path)) + } + } +} + extension HTTPClient.Response { init(status: HTTPResponseStatus, version: HTTPVersion = .http1_1, @@ -57,5 +67,9 @@ extension HTTPClient.Response { body: ByteBuffer? = nil) { self.init(host: "host", status: status, version: version, headers: headers, body: body) } + + static var ok: Self { .init(status: .ok) } + static var notFound: Self { .init(status: .notFound) } + static var badRequest: Self { .init(status: .badRequest) } } #endif diff --git a/Tests/AppTests/Mocks/AppEnvironment+mock.swift b/Tests/AppTests/Mocks/AppEnvironment+mock.swift index d8964bde3..b42abfd85 100644 --- a/Tests/AppTests/Mocks/AppEnvironment+mock.swift +++ b/Tests/AppTests/Mocks/AppEnvironment+mock.swift @@ -22,7 +22,6 @@ import Vapor extension AppEnvironment { static func mock(eventLoop: EventLoop) -> Self { .init( - fetchDocumentation: { _, _ in .init(status: .ok) }, fetchHTTPStatusCode: { _ in .ok }, fetchLicense: { _, _, _ in .init(htmlUrl: "https://github.com/foo/bar/blob/main/LICENSE") }, fetchMetadata: { _, _, _ in .mock }, diff --git a/Tests/AppTests/PackageController+routesTests.swift b/Tests/AppTests/PackageController+routesTests.swift index 2b77e5a7c..dd577c415 100644 --- a/Tests/AppTests/PackageController+routesTests.swift +++ b/Tests/AppTests/PackageController+routesTests.swift @@ -483,6 +483,7 @@ class PackageController_routesTests: SnapshotTestCase { func test_documentation_routes_contentType() async throws { try await withDependencies { $0.environment.awsDocsBucket = { "docs-bucket" } + $0.httpClient.fetchDocumentation = { @Sendable _ in .ok } } operation: { try await app.test(.GET, "/owner/package/main/images/foo/bar.jpeg") { res async in XCTAssertEqual(res.headers.contentType, .init(type: "application", subType: "octet-stream")) @@ -650,6 +651,7 @@ class PackageController_routesTests: SnapshotTestCase { try await withDependencies { $0.environment.awsDocsBucket = { "docs-bucket" } $0.environment.currentReferenceCache = { nil } + $0.httpClient.fetchDocumentation = { @Sendable _ in .init(status: .ok, body: .mockIndexHTML(baseURL: "/owner/package/1.0.0")) } } operation: { // setup let pkg = try await savePackage(on: app.db, "1") @@ -671,7 +673,6 @@ class PackageController_routesTests: SnapshotTestCase { packageName: "pkg", reference: .tag(1, 0, 0)) .save(on: app.db) - Current.fetchDocumentation = { _, _ in .init(status: .ok, body: .mockIndexHTML(baseURL: "/owner/package/1.0.0")) } // MUT @@ -724,6 +725,7 @@ class PackageController_routesTests: SnapshotTestCase { // /owner/package/documentation/{reference} + various path elements try await withDependencies { $0.environment.awsDocsBucket = { "docs-bucket" } + $0.httpClient.fetchDocumentation = { @Sendable _ in .init(status: .ok, body: .mockIndexHTML()) } } operation: { // setup let pkg = try await savePackage(on: app.db, "1") @@ -745,7 +747,6 @@ class PackageController_routesTests: SnapshotTestCase { packageName: "pkg", reference: .tag(1, 2, 3)) .save(on: app.db) - Current.fetchDocumentation = { _, _ in .init(status: .ok, body: .mockIndexHTML()) } // MUT @@ -803,6 +804,7 @@ class PackageController_routesTests: SnapshotTestCase { // Test documentation routes when no archive is in the path try await withDependencies { $0.environment.awsDocsBucket = { "docs-bucket" } + $0.httpClient.fetchDocumentation = { @Sendable _ in .init(status: .ok, body: .mockIndexHTML()) } } operation: { // setup let pkg = try await savePackage(on: app.db, "1") @@ -824,7 +826,6 @@ class PackageController_routesTests: SnapshotTestCase { packageName: "pkg", reference: .tag(1, 0, 0)) .save(on: app.db) - Current.fetchDocumentation = { _, _ in .init(status: .ok, body: .mockIndexHTML()) } // MUT try await app.test(.GET, "/owner/package/main/documentation") { res async in @@ -845,9 +846,9 @@ class PackageController_routesTests: SnapshotTestCase { func test_documentationRoot_notFound() async throws { try await withDependencies { $0.environment.dbId = { nil } + $0.httpClient.fetchDocumentation = { @Sendable _ in .notFound } } operation: { // setup - Current.fetchDocumentation = { _, _ in .init(status: .notFound) } let pkg = try await savePackage(on: app.db, "1") try await Repository(package: pkg, name: "package", owner: "owner") .save(on: app.db) @@ -883,9 +884,9 @@ class PackageController_routesTests: SnapshotTestCase { try await withDependencies { $0.environment.awsDocsBucket = { "docs-bucket" } $0.environment.dbId = { nil } + $0.httpClient.fetchDocumentation = { @Sendable uri in .badRequest } } operation: { // setup - Current.fetchDocumentation = { _, uri in .init(status: .badRequest) } let pkg = try await savePackage(on: app.db, "1") try await Repository(package: pkg, name: "package", owner: "owner") .save(on: app.db) @@ -907,12 +908,12 @@ class PackageController_routesTests: SnapshotTestCase { func test_documentation_error() async throws { // Test behaviour when fetchDocumentation throws + struct SomeError: Error { } try await withDependencies { $0.environment.awsDocsBucket = { "docs-bucket" } $0.environment.dbId = { nil } + $0.httpClient.fetchDocumentation = { @Sendable _ in throw SomeError() } } operation: { - struct SomeError: Error { } - Current.fetchDocumentation = { _, _ in throw SomeError() } let pkg = try await savePackage(on: app.db, "1") try await Repository(package: pkg, name: "package", owner: "owner") .save(on: app.db) @@ -942,12 +943,9 @@ class PackageController_routesTests: SnapshotTestCase { try await withDependencies { $0.environment.awsDocsBucket = { "docs-bucket" } $0.environment.currentReferenceCache = { nil } + $0.httpClient.fetchDocumentation = App.HTTPClient.echoURL() } operation: { // setup - Current.fetchDocumentation = { _, uri in - // embed uri.path in the body as a simple way to test the requested url - .init(status: .ok, body: .init(string: uri.path)) - } let pkg = try await savePackage(on: app.db, "1") try await Repository(package: pkg, name: "package", owner: "owner") .save(on: app.db) @@ -977,13 +975,8 @@ class PackageController_routesTests: SnapshotTestCase { func test_documentation_ref_css() throws { try withDependencies { $0.environment.awsDocsBucket = { "docs-bucket" } + $0.httpClient.fetchDocumentation = App.HTTPClient.echoURL() } operation: { - // setup - Current.fetchDocumentation = { _, uri in - // embed uri.path in the body as a simple way to test the requested url - .init(status: .ok, body: .init(string: uri.path)) - } - // MUT // test base url try app.test(.GET, "/owner/package/1.2.3/css/a") { @@ -1005,12 +998,9 @@ class PackageController_routesTests: SnapshotTestCase { try await withDependencies { $0.environment.awsDocsBucket = { "docs-bucket" } $0.environment.currentReferenceCache = { nil } + $0.httpClient.fetchDocumentation = App.HTTPClient.echoURL() } operation: { // setup - Current.fetchDocumentation = { _, uri in - // embed uri.path in the body as a simple way to test the requested url - .init(status: .ok, body: .init(string: uri.path)) - } let pkg = try await savePackage(on: app.db, "1") try await Repository(package: pkg, name: "package", owner: "owner") .save(on: app.db) @@ -1040,13 +1030,8 @@ class PackageController_routesTests: SnapshotTestCase { func test_documentation_ref_js() throws { try withDependencies { $0.environment.awsDocsBucket = { "docs-bucket" } + $0.httpClient.fetchDocumentation = App.HTTPClient.echoURL() } operation: { - // setup - Current.fetchDocumentation = { _, uri in - // embed uri.path in the body as a simple way to test the requested url - .init(status: .ok, body: .init(string: uri.path)) - } - // MUT // test base url try app.test(.GET, "/owner/package/1.2.3/js/a") { @@ -1068,12 +1053,9 @@ class PackageController_routesTests: SnapshotTestCase { try await withDependencies { $0.environment.awsDocsBucket = { "docs-bucket" } $0.environment.currentReferenceCache = { nil } + $0.httpClient.fetchDocumentation = App.HTTPClient.echoURL() } operation: { // setup - Current.fetchDocumentation = { _, uri in - // embed uri.path in the body as a simple way to test the requested url - .init(status: .ok, body: .init(string: uri.path)) - } let pkg = try await savePackage(on: app.db, "1") try await Repository(package: pkg, name: "package", owner: "owner") .save(on: app.db) @@ -1112,13 +1094,8 @@ class PackageController_routesTests: SnapshotTestCase { func test_documentation_ref_data() throws { try withDependencies { $0.environment.awsDocsBucket = { "docs-bucket" } + $0.httpClient.fetchDocumentation = App.HTTPClient.echoURL() } operation: { - // setup - Current.fetchDocumentation = { _, uri in - // embed uri.path in the body as a simple way to test the requested url - .init(status: .ok, body: .init(string: uri.path)) - } - // MUT // test base url try app.test(.GET, "/owner/package/1.2.3/data/a") { @@ -1148,13 +1125,8 @@ class PackageController_routesTests: SnapshotTestCase { func test_documentation_canonicalCapitalisation() async throws { try await withDependencies { $0.environment.awsDocsBucket = { "docs-bucket" } + $0.httpClient.fetchDocumentation = App.HTTPClient.echoURL() } operation: { - // setup - Current.fetchDocumentation = { _, uri in - // embed uri.path in the body as a simple way to test the requested url - .init(status: .ok, body: .init(string: uri.path)) - } - // The `packageName` property on the `Version` has been set to the lower-cased version so // we can be sure the canonical URL is built from the properties on the `Repository` model. let pkg = try await savePackage(on: app.db, "1") @@ -1186,6 +1158,7 @@ class PackageController_routesTests: SnapshotTestCase { try await withDependencies { $0.environment.awsDocsBucket = { "docs-bucket" } $0.environment.currentReferenceCache = { nil } + $0.httpClient.fetchDocumentation = { @Sendable _ in .init(status: .ok, body: .mockIndexHTML()) } } operation: { // setup let pkg = try await savePackage(on: app.db, "1") @@ -1199,7 +1172,6 @@ class PackageController_routesTests: SnapshotTestCase { packageName: "pkg", reference: .branch("feature/1.2.3")) .save(on: app.db) - Current.fetchDocumentation = { _, _ in .init(status: .ok, body: .mockIndexHTML()) } // MUT @@ -1251,6 +1223,7 @@ class PackageController_routesTests: SnapshotTestCase { $0.environment.awsDocsBucket = { "docs-bucket" } $0.environment.currentReferenceCache = { nil } $0.environment.dbId = { nil } + $0.httpClient.fetchDocumentation = { @Sendable _ in .init(status: .ok, body: .mockIndexHTML()) } } operation: { // setup let pkg = try await savePackage(on: app.db, "1") @@ -1272,7 +1245,6 @@ class PackageController_routesTests: SnapshotTestCase { packageName: "pkg", reference: .tag(1, 0, 0)) .save(on: app.db) - Current.fetchDocumentation = { _, _ in .init(status: .ok, body: .mockIndexHTML()) } // MUT try await app.test(.GET, "/owner/package/~/tutorials") { res async in @@ -1299,15 +1271,9 @@ class PackageController_routesTests: SnapshotTestCase { func test_favicon() throws { try withDependencies { $0.environment.awsDocsBucket = { "docs-bucket" } + $0.httpClient.fetchDocumentation = App.HTTPClient + .echoURL(headers: ["content-type": "application/octet-stream"]) } operation: { - // setup - Current.fetchDocumentation = { _, uri in - // embed uri.path in the body as a simple way to test the requested url - .init(status: .ok, - headers: ["content-type": "application/octet-stream"], - body: .init(string: uri.path)) - } - // MUT try app.test(.GET, "/owner/package/1.2.3/favicon.ico") { XCTAssertEqual($0.status, .ok) @@ -1326,15 +1292,9 @@ class PackageController_routesTests: SnapshotTestCase { func test_themeSettings() throws { try withDependencies { $0.environment.awsDocsBucket = { "docs-bucket" } + $0.httpClient.fetchDocumentation = App.HTTPClient + .echoURL(headers: ["content-type": "application/json"]) } operation: { - // setup - Current.fetchDocumentation = { _, uri in - // embed uri.path in the body as a simple way to test the requested url - .init(status: .ok, - headers: ["content-type": "application/json"], - body: .init(string: uri.path)) - } - // MUT try app.test(.GET, "/owner/package/1.2.3/theme-settings.json") { XCTAssertEqual($0.status, .ok) @@ -1347,15 +1307,9 @@ class PackageController_routesTests: SnapshotTestCase { func test_linkablePaths() throws { try withDependencies { $0.environment.awsDocsBucket = { "docs-bucket" } + $0.httpClient.fetchDocumentation = App.HTTPClient + .echoURL(headers: ["content-type": "application/json"]) } operation: { - // setup - Current.fetchDocumentation = { _, uri in - // embed uri.path in the body as a simple way to test the requested url - .init(status: .ok, - headers: ["content-type": "application/json"], - body: .init(string: uri.path)) - } - // MUT try app.test(.GET, "/owner/package/1.2.3/linkable-paths.json") { XCTAssertEqual($0.status, .ok) @@ -1368,12 +1322,12 @@ class PackageController_routesTests: SnapshotTestCase { func test_tutorial() async throws { try await withDependencies { $0.environment.awsDocsBucket = { "docs-bucket" } - } operation: { - // setup - Current.fetchDocumentation = { _, uri in + $0.httpClient.fetchDocumentation = { @Sendable uri in // embed uri.path in the body as a simple way to test the requested url .init(status: .ok, body: .init(string: "

\(uri.path)

")) } + } operation: { + // setup let pkg = try await savePackage(on: app.db, "1") try await Repository(package: pkg, name: "package", owner: "owner") .save(on: app.db) @@ -1527,6 +1481,7 @@ class PackageController_routesTests: SnapshotTestCase { try await withDependencies { $0.environment.awsDocsBucket = { "docs-bucket" } $0.environment.currentReferenceCache = { .live } + $0.httpClient.fetchDocumentation = { @Sendable _ in .init(status: .ok, body: .mockIndexHTML()) } } operation: { // setup let pkg = try await savePackage(on: app.db, "https://github.com/foo/bar".url, processingStage: .ingestion) @@ -1564,8 +1519,6 @@ class PackageController_routesTests: SnapshotTestCase { try await withDependencies { $0.date.now = .t1 + Constants.branchVersionRefreshDelay + 1 } operation: { - Current.fetchDocumentation = { _, _ in .init(status: .ok, body: .mockIndexHTML()) } - // Ensure documentation is resolved try await app.test(.GET, "/foo/bar/~/documentation/target") { @MainActor res in await Task.yield() // essential to avoid deadlocking diff --git a/Tests/AppTests/RoutesTests.swift b/Tests/AppTests/RoutesTests.swift index dd94c4c1b..44623d6ed 100644 --- a/Tests/AppTests/RoutesTests.swift +++ b/Tests/AppTests/RoutesTests.swift @@ -23,13 +23,8 @@ final class RoutesTests: AppTestCase { func test_documentation_images() async throws { try await withDependencies { $0.environment.awsDocsBucket = { "docs-bucket" } + $0.httpClient.fetchDocumentation = App.HTTPClient.echoURL() } operation: { - // setup - Current.fetchDocumentation = { _, uri in - // embed uri.path in the body as a simple way to test the requested url - .init(status: .ok, body: .init(string: uri.path)) - } - // MUT try await app.test(.GET, "foo/bar/1.2.3/images/baz.png") { res async in // validation @@ -49,10 +44,8 @@ final class RoutesTests: AppTestCase { func test_documentation_img() async throws { try await withDependencies { $0.environment.awsDocsBucket = { "docs-bucket" } + $0.httpClient.fetchDocumentation = { @Sendable _ in .ok } } operation: { - // setup - Current.fetchDocumentation = { _, _ in .init(status: .ok) } - // MUT try await app.test(.GET, "foo/bar/1.2.3/img/baz.png") { res async in // validation @@ -64,10 +57,8 @@ final class RoutesTests: AppTestCase { func test_documentation_videos() async throws { try await withDependencies { $0.environment.awsDocsBucket = { "docs-bucket" } + $0.httpClient.fetchDocumentation = { @Sendable _ in .ok } } operation: { - // setup - Current.fetchDocumentation = { _, _ in .init(status: .ok) } - // MUT try await app.test(.GET, "foo/bar/1.2.3/videos/baz.mov") { res async in // validation diff --git a/Tests/AppTests/SitemapTests.swift b/Tests/AppTests/SitemapTests.swift index 07d0b7475..7de2d8f95 100644 --- a/Tests/AppTests/SitemapTests.swift +++ b/Tests/AppTests/SitemapTests.swift @@ -138,6 +138,17 @@ class SitemapTests: SnapshotTestCase { func test_linkablePathUrls() async throws { try await withDependencies { $0.environment.awsDocsBucket = { "docs-bucket" } + $0.httpClient.fetchDocumentation = { @Sendable url in + guard url.path.hasSuffix("/owner/repo0/default/linkable-paths.json") else { throw Abort(.notFound) } + return .init(status: .ok, + body: .init(string: """ + [ + "/documentation/foo/bar/1", + "/documentation/foo/bar/2", + ] + """) + ) + } } operation: { // setup let package = Package(url: URL(stringLiteral: "https://example.com/owner/repo0")) @@ -156,17 +167,6 @@ class SitemapTests: SnapshotTestCase { let packageResult = try await PackageController.PackageResult .query(on: app.db, owner: "owner", repository: "repo0") Current.siteURL = { "https://spi.com" } - Current.fetchDocumentation = { client, url in - guard url.path.hasSuffix("/owner/repo0/default/linkable-paths.json") else { throw Abort(.notFound) } - return .init(status: .ok, - body: .init(string: """ - [ - "/documentation/foo/bar/1", - "/documentation/foo/bar/2", - ] - """) - ) - } // MUT let urls = await PackageController.linkablePathUrls(client: app.client, packageResult: packageResult) @@ -183,6 +183,17 @@ class SitemapTests: SnapshotTestCase { // https://github.com/SwiftPackageIndex/SwiftPackageIndex-Server/issues/2462 try await withDependencies { $0.environment.awsDocsBucket = { "docs-bucket" } + $0.httpClient.fetchDocumentation = { @Sendable url in + guard url.path.hasSuffix("/owner/repo0/a-b/linkable-paths.json") else { throw Abort(.notFound) } + return .init(status: .ok, + body: .init(string: """ + [ + "/documentation/foo/bar/1", + "/documentation/foo/bar/2", + ] + """) + ) + } } operation: { // setup let package = Package(url: URL(stringLiteral: "https://example.com/owner/repo0")) @@ -201,17 +212,6 @@ class SitemapTests: SnapshotTestCase { let packageResult = try await PackageController.PackageResult .query(on: app.db, owner: "owner", repository: "repo0") Current.siteURL = { "https://spi.com" } - Current.fetchDocumentation = { client, url in - guard url.path.hasSuffix("/owner/repo0/a-b/linkable-paths.json") else { throw Abort(.notFound) } - return .init(status: .ok, - body: .init(string: """ - [ - "/documentation/foo/bar/1", - "/documentation/foo/bar/2", - ] - """) - ) - } // MUT let urls = await PackageController.linkablePathUrls(client: app.client, packageResult: packageResult) From 45a4797df9365107ffe18ba9cc7dae735dbbacc0 Mon Sep 17 00:00:00 2001 From: "Sven A. Schmidt" Date: Fri, 6 Dec 2024 10:51:51 +0100 Subject: [PATCH 5/5] Fix missing body --- Sources/App/Controllers/PackageController+routes.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Sources/App/Controllers/PackageController+routes.swift b/Sources/App/Controllers/PackageController+routes.swift index e841c5667..a12df5449 100644 --- a/Sources/App/Controllers/PackageController+routes.swift +++ b/Sources/App/Controllers/PackageController+routes.swift @@ -78,7 +78,8 @@ enum PackageController { status: .ok, headers: req.headers .replacingOrAdding(name: .contentType, value: route.fragment.contentType) - .replacingOrAdding(name: .cacheControl, value: "no-transform") + .replacingOrAdding(name: .cacheControl, value: "no-transform"), + body: res.body ).encodeResponse(for: req) } } @@ -148,7 +149,8 @@ enum PackageController { return try await ClientResponse( status: .ok, headers: req.headers.replacingOrAdding(name: .contentType, - value: route.contentType) + value: route.contentType), + body: awsResponse.body ).encodeResponse(for: req) }