From c48fdaac7ceb99436f0bdceeed5969536caf7584 Mon Sep 17 00:00:00 2001 From: "Sven A. Schmidt" Date: Sat, 16 Nov 2024 12:48:55 +0100 Subject: [PATCH 1/5] Move Current.aws* to environment.aws* --- .../PackageController+routes.swift | 8 +- Sources/App/Core/AppEnvironment.swift | 8 - .../Core/Dependencies/EnvironmentClient.swift | 8 + Sources/App/Core/Extensions/S3Store+ext.swift | 14 +- Sources/App/Core/Gitlab.swift | 2 +- Tests/AppTests/GithubTests.swift | 82 ++++----- Tests/AppTests/GitlabBuilderTests.swift | 7 +- .../AppTests/Mocks/AppEnvironment+mock.swift | 4 - .../PackageController+routesTests.swift | 160 +++++++++--------- Tests/AppTests/S3StoreExtensionTests.swift | 20 ++- 10 files changed, 167 insertions(+), 146 deletions(-) diff --git a/Sources/App/Controllers/PackageController+routes.swift b/Sources/App/Controllers/PackageController+routes.swift index c9b3444d7..9ef2394a6 100644 --- a/Sources/App/Controllers/PackageController+routes.swift +++ b/Sources/App/Controllers/PackageController+routes.swift @@ -12,10 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. +import Dependencies import Fluent import Plot -import Vapor import SemanticVersion +import Vapor enum PackageController { @@ -222,7 +223,7 @@ enum PackageController { // r.owner, // v.reference, // d.linkable_paths_count - // '(owner: "' || r.owner || '", repository: "' || r.name || '"), // ' || d.linkable_paths_count || ' urls' + // '(owner: "' || r.owner || '", repository: "' || r.name || '"), // ' || d.linkable_paths_count || ' urls' // FROM // packages p // INNER JOIN repositories r ON p.id = r.package_id @@ -437,7 +438,8 @@ extension PackageController { extension PackageController { static func awsDocumentationURL(route: DocRoute) throws -> URI { - guard let bucket = Current.awsDocsBucket() else { + @Dependency(\.environment) var environment + guard let bucket = environment.awsDocsBucket() else { throw AppError.envVariableNotSet("AWS_DOCS_BUCKET") } diff --git a/Sources/App/Core/AppEnvironment.swift b/Sources/App/Core/AppEnvironment.swift index 4aac8d49f..4dd45319b 100644 --- a/Sources/App/Core/AppEnvironment.swift +++ b/Sources/App/Core/AppEnvironment.swift @@ -25,10 +25,6 @@ import FoundationNetworking struct AppEnvironment: Sendable { var apiSigningKey: @Sendable () -> String? var appVersion: @Sendable () -> String? - var awsAccessKeyId: @Sendable () -> String? - var awsDocsBucket: @Sendable () -> String? - var awsReadmeBucket: @Sendable () -> String? - var awsSecretAccessKey: @Sendable () -> String? var buildTriggerAllowList: @Sendable () -> [Package.Id] var buildTriggerDownscaling: @Sendable () -> Double var buildTriggerLatestSwiftVersionDownscaling: @Sendable () -> Double @@ -106,10 +102,6 @@ extension AppEnvironment { static let live = AppEnvironment( apiSigningKey: { Environment.get("API_SIGNING_KEY") }, appVersion: { App.appVersion }, - awsAccessKeyId: { Environment.get("AWS_ACCESS_KEY_ID") }, - awsDocsBucket: { Environment.get("AWS_DOCS_BUCKET") }, - awsReadmeBucket: { Environment.get("AWS_README_BUCKET") }, - awsSecretAccessKey: { Environment.get("AWS_SECRET_ACCESS_KEY") }, buildTriggerAllowList: { Environment.get("BUILD_TRIGGER_ALLOW_LIST") .map { Data($0.utf8) } diff --git a/Sources/App/Core/Dependencies/EnvironmentClient.swift b/Sources/App/Core/Dependencies/EnvironmentClient.swift index 80561a928..65b422a70 100644 --- a/Sources/App/Core/Dependencies/EnvironmentClient.swift +++ b/Sources/App/Core/Dependencies/EnvironmentClient.swift @@ -23,6 +23,10 @@ struct EnvironmentClient { // regarding the use of XCTFail here. var allowBuildTriggers: @Sendable () -> Bool = { XCTFail(#function); return true } var allowSocialPosts: @Sendable () -> Bool = { XCTFail(#function); return true } + var awsAccessKeyId: @Sendable () -> String? + var awsDocsBucket: @Sendable () -> String? + var awsReadmeBucket: @Sendable () -> String? + var awsSecretAccessKey: @Sendable () -> String? var builderToken: @Sendable () -> String? var buildTimeout: @Sendable () -> Int = { XCTFail(#function); return 10 } // We're not defaulting current to XCTFail, because its use is too pervasive and would require the vast @@ -47,6 +51,10 @@ extension EnvironmentClient: DependencyKey { .flatMap(\.asBool) ?? Constants.defaultAllowSocialPosts }, + awsAccessKeyId: { Environment.get("AWS_ACCESS_KEY_ID") }, + awsDocsBucket: { Environment.get("AWS_DOCS_BUCKET") }, + awsReadmeBucket: { Environment.get("AWS_README_BUCKET") }, + awsSecretAccessKey: { Environment.get("AWS_SECRET_ACCESS_KEY") }, builderToken: { Environment.get("BUILDER_TOKEN") }, buildTimeout: { Environment.get("BUILD_TIMEOUT").flatMap(Int.init) ?? 10 }, current: { (try? Environment.detect()) ?? .development }, diff --git a/Sources/App/Core/Extensions/S3Store+ext.swift b/Sources/App/Core/Extensions/S3Store+ext.swift index 0e2f9b81c..83e35b87f 100644 --- a/Sources/App/Core/Extensions/S3Store+ext.swift +++ b/Sources/App/Core/Extensions/S3Store+ext.swift @@ -14,6 +14,7 @@ import S3Store import Vapor +import Dependencies extension S3Store { @@ -27,8 +28,9 @@ extension S3Store { } static func storeReadme(owner: String, repository: String, readme: String) async throws -> String { - guard let accessKeyId = Current.awsAccessKeyId(), - let secretAccessKey = Current.awsSecretAccessKey() + @Dependency(\.environment) var environment + guard let accessKeyId = environment.awsAccessKeyId(), + let secretAccessKey = environment.awsSecretAccessKey() else { throw Error.genericError("missing AWS credentials") } @@ -42,8 +44,9 @@ extension S3Store { } static func storeReadmeImages(client: Client, imagesToCache: [Github.Readme.ImageToCache]) async throws { - guard let accessKeyId = Current.awsAccessKeyId(), - let secretAccessKey = Current.awsSecretAccessKey() + @Dependency(\.environment) var environment + guard let accessKeyId = environment.awsAccessKeyId(), + let secretAccessKey = environment.awsSecretAccessKey() else { throw Error.genericError("missing AWS credentials") } @@ -63,7 +66,8 @@ extension S3Store { extension S3Store.Key { static func readme(owner: String, repository: String, imageUrl: String? = nil) throws -> Self { - guard let bucket = Current.awsReadmeBucket() else { + @Dependency(\.environment) var environment + guard let bucket = environment.awsReadmeBucket() else { throw S3Store.Error.genericError("AWS_README_BUCKET not set") } diff --git a/Sources/App/Core/Gitlab.swift b/Sources/App/Core/Gitlab.swift index 6544a0224..2f83402d6 100644 --- a/Sources/App/Core/Gitlab.swift +++ b/Sources/App/Core/Gitlab.swift @@ -82,7 +82,7 @@ extension Gitlab.Builder { guard let pipelineToken = Current.gitlabPipelineToken(), let builderToken = environment.builderToken() else { throw Gitlab.Error.missingToken } - guard let awsDocsBucket = Current.awsDocsBucket() else { + guard let awsDocsBucket = environment.awsDocsBucket() else { throw Gitlab.Error.missingConfiguration("AWS_DOCS_BUCKET") } let timeout = environment.buildTimeout() + (isDocBuild ? 5 : 0) diff --git a/Tests/AppTests/GithubTests.swift b/Tests/AppTests/GithubTests.swift index f8613d740..99fe3f249 100644 --- a/Tests/AppTests/GithubTests.swift +++ b/Tests/AppTests/GithubTests.swift @@ -12,12 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. +import XCTest + @testable import App -import Vapor -import SwiftSoup +import Dependencies import S3Store -import XCTest +import SwiftSoup +import Vapor class GithubTests: AppTestCase { @@ -396,9 +398,10 @@ class GithubTests: AppTestCase { } func test_extractImagesRequiringCaching() async throws { - Current.awsReadmeBucket = { "awsReadmeBucket" } - - var readme = """ + try withDependencies { + $0.environment.awsReadmeBucket = { "awsReadmeBucket" } + } operation: { + var readme = """ @@ -411,35 +414,37 @@ class GithubTests: AppTestCase { """ - // MUT - let images = Github.replaceImagesRequiringCaching(owner: "owner", repository: "repo", readme: &readme) - - XCTAssertEqual(images, [ - .init(originalUrl: "https://private-user-images.githubusercontent.com/with-jwt.jpg?jwt=some-jwt", - s3Key: S3Store.Key.init(bucket: "awsReadmeBucket", path: "owner/repo/with-jwt.jpg")) - ]) - - let document = try SwiftSoup.parse(readme) - let imageElements = try document.select("img").array() - - XCTAssertEqual(try imageElements.map { try $0.attr("src") }, [ - "https://awsReadmeBucket.s3.us-east-2.amazonaws.com/owner/repo/with-jwt.jpg", - "https://private-user-images.githubusercontent.com/without-jwt.jpg", - "https://raw.githubusercontent.com/raw-image.png", - "https://github.com/example/repo/branch/assets/example.png", - "https://example.com/other-domain.jpg" - ]) - - XCTAssertEqual(try imageElements.map { try $0.attr("data-original-src") }, [ - "https://private-user-images.githubusercontent.com/with-jwt.jpg?jwt=some-jwt", - "", "", "", "" // This attribute only gets added to images that will be cached. - ]) + // MUT + let images = Github.replaceImagesRequiringCaching(owner: "owner", repository: "repo", readme: &readme) + + XCTAssertEqual(images, [ + .init(originalUrl: "https://private-user-images.githubusercontent.com/with-jwt.jpg?jwt=some-jwt", + s3Key: S3Store.Key.init(bucket: "awsReadmeBucket", path: "owner/repo/with-jwt.jpg")) + ]) + + let document = try SwiftSoup.parse(readme) + let imageElements = try document.select("img").array() + + XCTAssertEqual(try imageElements.map { try $0.attr("src") }, [ + "https://awsReadmeBucket.s3.us-east-2.amazonaws.com/owner/repo/with-jwt.jpg", + "https://private-user-images.githubusercontent.com/without-jwt.jpg", + "https://raw.githubusercontent.com/raw-image.png", + "https://github.com/example/repo/branch/assets/example.png", + "https://example.com/other-domain.jpg" + ]) + + XCTAssertEqual(try imageElements.map { try $0.attr("data-original-src") }, [ + "https://private-user-images.githubusercontent.com/with-jwt.jpg?jwt=some-jwt", + "", "", "", "" // This attribute only gets added to images that will be cached. + ]) + } } func test_extractImagesRequiringCaching_noUnnecessaryChanges() async throws { - Current.awsReadmeBucket = { "awsReadmeBucket" } - - var readme = """ + try withDependencies { + $0.environment.awsReadmeBucket = { "awsReadmeBucket" } + } operation: { + var readme = """ @@ -450,14 +455,15 @@ class GithubTests: AppTestCase { """ - let originalReadme = readme + let originalReadme = readme - // MUT - let images = Github.replaceImagesRequiringCaching(owner: "owner", repository: "repo", readme: &readme) + // MUT + let images = Github.replaceImagesRequiringCaching(owner: "owner", repository: "repo", readme: &readme) - // Checks - XCTAssertEqual(originalReadme, readme) - XCTAssertEqual(images, []) + // Checks + XCTAssertEqual(originalReadme, readme) + XCTAssertEqual(images, []) + } } func test_Readme_containsSPIBadge() throws { diff --git a/Tests/AppTests/GitlabBuilderTests.swift b/Tests/AppTests/GitlabBuilderTests.swift index 4611107b2..2fa2edfc5 100644 --- a/Tests/AppTests/GitlabBuilderTests.swift +++ b/Tests/AppTests/GitlabBuilderTests.swift @@ -55,10 +55,10 @@ class GitlabBuilderTests: AppTestCase { func test_triggerBuild() async throws { try await withDependencies { + $0.environment.awsDocsBucket = { "docs-bucket" } $0.environment.builderToken = { "builder token" } $0.environment.buildTimeout = { 10 } } operation: { - Current.awsDocsBucket = { "docs-bucket" } Current.gitlabPipelineToken = { "pipeline token" } Current.siteURL = { "http://example.com" } let buildId = UUID() @@ -104,10 +104,10 @@ class GitlabBuilderTests: AppTestCase { func test_issue_588() async throws { try await withDependencies { + $0.environment.awsDocsBucket = { "docs-bucket" } $0.environment.builderToken = { "builder token" } $0.environment.buildTimeout = { 10 } } operation: { - Current.awsDocsBucket = { "docs-bucket" } Current.gitlabPipelineToken = { "pipeline token" } Current.siteURL = { "http://example.com" } @@ -181,12 +181,13 @@ class LiveGitlabBuilderTests: AppTestCase { // Set this to a valid value if you want to report build results back to the server ProcessInfo.processInfo.environment["LIVE_BUILDER_TOKEN"] } + // make sure environment variables are configured for live access + $0.environment.awsDocsBucket = { "spi-dev-docs" } } operation: { // set build branch to trigger on Gitlab.Builder.branch = "main" // make sure environment variables are configured for live access - Current.awsDocsBucket = { "spi-dev-docs" } Current.gitlabPipelineToken = { // This Gitlab token is required in order to trigger the pipeline ProcessInfo.processInfo.environment["LIVE_GITLAB_PIPELINE_TOKEN"] diff --git a/Tests/AppTests/Mocks/AppEnvironment+mock.swift b/Tests/AppTests/Mocks/AppEnvironment+mock.swift index c6b0f2787..e80b904f9 100644 --- a/Tests/AppTests/Mocks/AppEnvironment+mock.swift +++ b/Tests/AppTests/Mocks/AppEnvironment+mock.swift @@ -24,10 +24,6 @@ extension AppEnvironment { .init( apiSigningKey: { nil }, appVersion: { "test" }, - awsAccessKeyId: { nil }, - awsDocsBucket: { "awsDocsBucket" }, - awsReadmeBucket: { "awsReadmeBucket" }, - awsSecretAccessKey: { nil }, buildTriggerAllowList: { [] }, buildTriggerDownscaling: { 1.0 }, buildTriggerLatestSwiftVersionDownscaling: { 1.0 }, diff --git a/Tests/AppTests/PackageController+routesTests.swift b/Tests/AppTests/PackageController+routesTests.swift index a828cf466..b7a826022 100644 --- a/Tests/AppTests/PackageController+routesTests.swift +++ b/Tests/AppTests/PackageController+routesTests.swift @@ -327,90 +327,98 @@ class PackageController_routesTests: SnapshotTestCase { } func test_awsDocumentationURL() throws { - Current.awsDocsBucket = { "docs-bucket" } - XCTAssertEqual( - try PackageController.awsDocumentationURL(route: .init(owner: "Foo", repository: "Bar", docVersion: .reference("Main"), fragment: .documentation, pathElements: ["path"])).string, - "http://docs-bucket.s3-website.us-east-2.amazonaws.com/foo/bar/main/documentation/path" - ) - XCTAssertEqual( - try PackageController.awsDocumentationURL(route: .init(owner: "Foo", repository: "Bar", docVersion: .reference("1.2.3"), fragment: .css, pathElements: ["path"])).string, - "http://docs-bucket.s3-website.us-east-2.amazonaws.com/foo/bar/1.2.3/css/path" - ) - XCTAssertEqual( - try PackageController.awsDocumentationURL(route: .init(owner: "Foo", repository: "Bar", docVersion: .reference("1.2.3"), fragment: .documentation, pathElements: ["path"])).string, - "http://docs-bucket.s3-website.us-east-2.amazonaws.com/foo/bar/1.2.3/documentation/path" - ) - XCTAssertEqual( - try PackageController.awsDocumentationURL(route: .init(owner: "Foo", repository: "Bar", docVersion: .reference("1.2.3"), fragment: .data, pathElements: ["path"])).string, - "http://docs-bucket.s3-website.us-east-2.amazonaws.com/foo/bar/1.2.3/data/path" - ) - XCTAssertEqual( - try PackageController.awsDocumentationURL(route: .init(owner: "Foo", repository: "Bar", docVersion: .reference("1.2.3"), fragment: .images, pathElements: ["path"])).string, - "http://docs-bucket.s3-website.us-east-2.amazonaws.com/foo/bar/1.2.3/images/path" - ) - XCTAssertEqual( - try PackageController.awsDocumentationURL(route: .init(owner: "Foo", repository: "Bar", docVersion: .reference("1.2.3"), fragment: .img, pathElements: ["path"])).string, - "http://docs-bucket.s3-website.us-east-2.amazonaws.com/foo/bar/1.2.3/img/path" - ) - XCTAssertEqual( - try PackageController.awsDocumentationURL(route: .init(owner: "Foo", repository: "Bar", docVersion: .reference("1.2.3"), fragment: .svgImages, pathElements: ["path"])).string, - "http://docs-bucket.s3-website.us-east-2.amazonaws.com/foo/bar/1.2.3/images/path" - ) - XCTAssertEqual( - try PackageController.awsDocumentationURL(route: .init(owner: "Foo", repository: "Bar", docVersion: .reference("1.2.3"), fragment: .svgImg, pathElements: ["path"])).string, - "http://docs-bucket.s3-website.us-east-2.amazonaws.com/foo/bar/1.2.3/img/path" - ) - XCTAssertEqual( - try PackageController.awsDocumentationURL(route: .init(owner: "Foo", repository: "Bar", docVersion: .reference("1.2.3"), fragment: .js, pathElements: ["path"])).string, - "http://docs-bucket.s3-website.us-east-2.amazonaws.com/foo/bar/1.2.3/js/path" - ) - XCTAssertEqual( - try PackageController.awsDocumentationURL(route: .init(owner: "Foo", repository: "Bar", docVersion: .reference("1.2.3"), fragment: .linkablePaths, pathElements: [""])).string, - "http://docs-bucket.s3-website.us-east-2.amazonaws.com/foo/bar/1.2.3/linkable-paths.json" - ) - XCTAssertEqual( - try PackageController.awsDocumentationURL(route: .init(owner: "Foo", repository: "Bar", docVersion: .reference("1.2.3"), fragment: .linkablePaths, pathElements: ["ignored"])).string, - "http://docs-bucket.s3-website.us-east-2.amazonaws.com/foo/bar/1.2.3/linkable-paths.json" - ) - XCTAssertEqual( - try PackageController.awsDocumentationURL(route: .init(owner: "Foo", repository: "Bar", docVersion: .reference("1.2.3"), fragment: .themeSettings, pathElements: ["path"])).string, - "http://docs-bucket.s3-website.us-east-2.amazonaws.com/foo/bar/1.2.3/path/theme-settings.json" - ) - XCTAssertEqual( - try PackageController.awsDocumentationURL(route: .init(owner: "Foo", repository: "Bar", docVersion: .reference("1.2.3"), fragment: .themeSettings, pathElements: [""])).string, - "http://docs-bucket.s3-website.us-east-2.amazonaws.com/foo/bar/1.2.3/theme-settings.json" - ) - XCTAssertEqual( - try PackageController.awsDocumentationURL(route: .init(owner: "Foo", repository: "Bar", docVersion: .reference("feature/a"), fragment: .documentation, pathElements: ["path"])).string, - "http://docs-bucket.s3-website.us-east-2.amazonaws.com/foo/bar/feature-a/documentation/path" - ) + try withDependencies { + $0.environment.awsDocsBucket = { "docs-bucket" } + } operation: { + XCTAssertEqual( + try PackageController.awsDocumentationURL(route: .init(owner: "Foo", repository: "Bar", docVersion: .reference("Main"), fragment: .documentation, pathElements: ["path"])).string, + "http://docs-bucket.s3-website.us-east-2.amazonaws.com/foo/bar/main/documentation/path" + ) + XCTAssertEqual( + try PackageController.awsDocumentationURL(route: .init(owner: "Foo", repository: "Bar", docVersion: .reference("1.2.3"), fragment: .css, pathElements: ["path"])).string, + "http://docs-bucket.s3-website.us-east-2.amazonaws.com/foo/bar/1.2.3/css/path" + ) + XCTAssertEqual( + try PackageController.awsDocumentationURL(route: .init(owner: "Foo", repository: "Bar", docVersion: .reference("1.2.3"), fragment: .documentation, pathElements: ["path"])).string, + "http://docs-bucket.s3-website.us-east-2.amazonaws.com/foo/bar/1.2.3/documentation/path" + ) + XCTAssertEqual( + try PackageController.awsDocumentationURL(route: .init(owner: "Foo", repository: "Bar", docVersion: .reference("1.2.3"), fragment: .data, pathElements: ["path"])).string, + "http://docs-bucket.s3-website.us-east-2.amazonaws.com/foo/bar/1.2.3/data/path" + ) + XCTAssertEqual( + try PackageController.awsDocumentationURL(route: .init(owner: "Foo", repository: "Bar", docVersion: .reference("1.2.3"), fragment: .images, pathElements: ["path"])).string, + "http://docs-bucket.s3-website.us-east-2.amazonaws.com/foo/bar/1.2.3/images/path" + ) + XCTAssertEqual( + try PackageController.awsDocumentationURL(route: .init(owner: "Foo", repository: "Bar", docVersion: .reference("1.2.3"), fragment: .img, pathElements: ["path"])).string, + "http://docs-bucket.s3-website.us-east-2.amazonaws.com/foo/bar/1.2.3/img/path" + ) + XCTAssertEqual( + try PackageController.awsDocumentationURL(route: .init(owner: "Foo", repository: "Bar", docVersion: .reference("1.2.3"), fragment: .svgImages, pathElements: ["path"])).string, + "http://docs-bucket.s3-website.us-east-2.amazonaws.com/foo/bar/1.2.3/images/path" + ) + XCTAssertEqual( + try PackageController.awsDocumentationURL(route: .init(owner: "Foo", repository: "Bar", docVersion: .reference("1.2.3"), fragment: .svgImg, pathElements: ["path"])).string, + "http://docs-bucket.s3-website.us-east-2.amazonaws.com/foo/bar/1.2.3/img/path" + ) + XCTAssertEqual( + try PackageController.awsDocumentationURL(route: .init(owner: "Foo", repository: "Bar", docVersion: .reference("1.2.3"), fragment: .js, pathElements: ["path"])).string, + "http://docs-bucket.s3-website.us-east-2.amazonaws.com/foo/bar/1.2.3/js/path" + ) + XCTAssertEqual( + try PackageController.awsDocumentationURL(route: .init(owner: "Foo", repository: "Bar", docVersion: .reference("1.2.3"), fragment: .linkablePaths, pathElements: [""])).string, + "http://docs-bucket.s3-website.us-east-2.amazonaws.com/foo/bar/1.2.3/linkable-paths.json" + ) + XCTAssertEqual( + try PackageController.awsDocumentationURL(route: .init(owner: "Foo", repository: "Bar", docVersion: .reference("1.2.3"), fragment: .linkablePaths, pathElements: ["ignored"])).string, + "http://docs-bucket.s3-website.us-east-2.amazonaws.com/foo/bar/1.2.3/linkable-paths.json" + ) + XCTAssertEqual( + try PackageController.awsDocumentationURL(route: .init(owner: "Foo", repository: "Bar", docVersion: .reference("1.2.3"), fragment: .themeSettings, pathElements: ["path"])).string, + "http://docs-bucket.s3-website.us-east-2.amazonaws.com/foo/bar/1.2.3/path/theme-settings.json" + ) + XCTAssertEqual( + try PackageController.awsDocumentationURL(route: .init(owner: "Foo", repository: "Bar", docVersion: .reference("1.2.3"), fragment: .themeSettings, pathElements: [""])).string, + "http://docs-bucket.s3-website.us-east-2.amazonaws.com/foo/bar/1.2.3/theme-settings.json" + ) + XCTAssertEqual( + try PackageController.awsDocumentationURL(route: .init(owner: "Foo", repository: "Bar", docVersion: .reference("feature/a"), fragment: .documentation, pathElements: ["path"])).string, + "http://docs-bucket.s3-website.us-east-2.amazonaws.com/foo/bar/feature-a/documentation/path" + ) + } } func test_awsDocumentationURL_current() throws { - Current.awsDocsBucket = { "docs-bucket" } - XCTAssertEqual( - try PackageController.awsDocumentationURL(route: .init(owner: "Foo", repository: "Bar", docVersion: .current(referencing: "Main"), fragment: .documentation, pathElements: ["path"])).string, - "http://docs-bucket.s3-website.us-east-2.amazonaws.com/foo/bar/main/documentation/path" - ) - XCTAssertEqual( - try PackageController.awsDocumentationURL(route: .init(owner: "Foo", repository: "Bar", docVersion: .current(referencing: "1.2.3"), fragment: .documentation, pathElements: ["path"])).string, - "http://docs-bucket.s3-website.us-east-2.amazonaws.com/foo/bar/1.2.3/documentation/path" - ) - XCTAssertEqual( - try PackageController.awsDocumentationURL(route: .init(owner: "Foo", repository: "Bar", docVersion: .current(referencing: "feature/a"), fragment: .documentation, pathElements: ["path"])).string, - "http://docs-bucket.s3-website.us-east-2.amazonaws.com/foo/bar/feature-a/documentation/path" - ) - + try withDependencies { + $0.environment.awsDocsBucket = { "docs-bucket" } + } operation: { + XCTAssertEqual( + try PackageController.awsDocumentationURL(route: .init(owner: "Foo", repository: "Bar", docVersion: .current(referencing: "Main"), fragment: .documentation, pathElements: ["path"])).string, + "http://docs-bucket.s3-website.us-east-2.amazonaws.com/foo/bar/main/documentation/path" + ) + XCTAssertEqual( + try PackageController.awsDocumentationURL(route: .init(owner: "Foo", repository: "Bar", docVersion: .current(referencing: "1.2.3"), fragment: .documentation, pathElements: ["path"])).string, + "http://docs-bucket.s3-website.us-east-2.amazonaws.com/foo/bar/1.2.3/documentation/path" + ) + XCTAssertEqual( + try PackageController.awsDocumentationURL(route: .init(owner: "Foo", repository: "Bar", docVersion: .current(referencing: "feature/a"), fragment: .documentation, pathElements: ["path"])).string, + "http://docs-bucket.s3-website.us-east-2.amazonaws.com/foo/bar/feature-a/documentation/path" + ) + } } func test_awsDocumentationURL_issue2287() throws { // https://github.com/SwiftPackageIndex/SwiftPackageIndex-Server/issues/2287 // reference with / needs to be escaped - Current.awsDocsBucket = { "docs-bucket" } - XCTAssertEqual( - try PackageController.awsDocumentationURL(route: .init(owner: "linhay", repository: "SectionKit", docVersion: .reference("feature/2.0.0"), fragment: .documentation, pathElements: ["sectionui"])).string, - "http://docs-bucket.s3-website.us-east-2.amazonaws.com/linhay/sectionkit/feature-2.0.0/documentation/sectionui" - ) + try withDependencies { + $0.environment.awsDocsBucket = { "docs-bucket" } + } operation: { + XCTAssertEqual( + try PackageController.awsDocumentationURL(route: .init(owner: "linhay", repository: "SectionKit", docVersion: .reference("feature/2.0.0"), fragment: .documentation, pathElements: ["sectionui"])).string, + "http://docs-bucket.s3-website.us-east-2.amazonaws.com/linhay/sectionkit/feature-2.0.0/documentation/sectionui" + ) + } } func test_canonicalDocumentationUrl() throws { diff --git a/Tests/AppTests/S3StoreExtensionTests.swift b/Tests/AppTests/S3StoreExtensionTests.swift index 62d4693d6..89ce62cf2 100644 --- a/Tests/AppTests/S3StoreExtensionTests.swift +++ b/Tests/AppTests/S3StoreExtensionTests.swift @@ -16,19 +16,23 @@ import XCTest @testable import App +import Dependencies import S3Store + class S3StoreExtensionTests: XCTestCase { func test_Key_readme() throws { - Current.awsReadmeBucket = { "awsReadmeBucket" } - - let imageKey = try S3Store.Key.readme(owner: "owner", repository: "repository", - imageUrl: "https://example.com/image/example-image.png") - XCTAssertEqual(imageKey.s3Uri, "s3://awsReadmeBucket/owner/repository/example-image.png") - - let readmeKey = try S3Store.Key.readme(owner: "owner", repository: "repository") - XCTAssertEqual(readmeKey.s3Uri, "s3://awsReadmeBucket/owner/repository/readme.html") + try withDependencies { + $0.environment.awsReadmeBucket = { "awsReadmeBucket" } + } operation: { + let imageKey = try S3Store.Key.readme(owner: "owner", repository: "repository", + imageUrl: "https://example.com/image/example-image.png") + XCTAssertEqual(imageKey.s3Uri, "s3://awsReadmeBucket/owner/repository/example-image.png") + + let readmeKey = try S3Store.Key.readme(owner: "owner", repository: "repository") + XCTAssertEqual(readmeKey.s3Uri, "s3://awsReadmeBucket/owner/repository/readme.html") + } } } From 1b97ce39fc2c9522bfc09fc2194e02f2338fd953 Mon Sep 17 00:00:00 2001 From: "Sven A. Schmidt" Date: Sat, 16 Nov 2024 12:49:09 +0100 Subject: [PATCH 2/5] Make sure test doesn't crash if it fails --- Tests/AppTests/PackageController+routesTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/AppTests/PackageController+routesTests.swift b/Tests/AppTests/PackageController+routesTests.swift index b7a826022..7ff1e9ee8 100644 --- a/Tests/AppTests/PackageController+routesTests.swift +++ b/Tests/AppTests/PackageController+routesTests.swift @@ -1080,7 +1080,7 @@ class PackageController_routesTests: SnapshotTestCase { let linkElements = try document.select("link[rel='canonical']") XCTAssertEqual(linkElements.count, 1) - let href = try linkElements.first()!.attr("href") + let href = try XCTUnwrap(linkElements.first()?.attr("href")) XCTAssertEqual(href, "/Owner/Package/1.2.3/documentation/a/b") } } From deba25482d15106d01cbfe695f61f5d0e3470c87 Mon Sep 17 00:00:00 2001 From: "Sven A. Schmidt" Date: Sat, 16 Nov 2024 13:16:48 +0100 Subject: [PATCH 3/5] All tests pass when pre-setting dependencies --- Sources/App/Core/Dependencies/EnvironmentClient.swift | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Sources/App/Core/Dependencies/EnvironmentClient.swift b/Sources/App/Core/Dependencies/EnvironmentClient.swift index 65b422a70..9c68aea7b 100644 --- a/Sources/App/Core/Dependencies/EnvironmentClient.swift +++ b/Sources/App/Core/Dependencies/EnvironmentClient.swift @@ -69,7 +69,15 @@ extension EnvironmentClient: DependencyKey { extension EnvironmentClient: TestDependencyKey { - static var testValue: Self { Self() } + static var testValue: Self { + var testValue = Self() + testValue.current = { .development } + testValue.awsAccessKeyId = { nil } + testValue.awsDocsBucket = { "awsDocsBucket" } + testValue.awsReadmeBucket = { "awsReadmeBucket" } + testValue.awsSecretAccessKey = { nil } + return testValue + } } From 4f1edd83903e533bdf583434671529a91876cf59 Mon Sep 17 00:00:00 2001 From: "Sven A. Schmidt" Date: Sat, 16 Nov 2024 13:57:39 +0100 Subject: [PATCH 4/5] Update tests --- Tests/AppTests/BuildTests.swift | 2 + Tests/AppTests/BuildTriggerTests.swift | 10 + Tests/AppTests/IngestorTests.swift | 8 +- .../PackageController+routesTests.swift | 1371 +++++++++-------- Tests/AppTests/RoutesTests.swift | 55 +- Tests/AppTests/SitemapTests.swift | 126 +- 6 files changed, 844 insertions(+), 728 deletions(-) diff --git a/Tests/AppTests/BuildTests.swift b/Tests/AppTests/BuildTests.swift index 5d7dceb2c..089c67788 100644 --- a/Tests/AppTests/BuildTests.swift +++ b/Tests/AppTests/BuildTests.swift @@ -130,6 +130,7 @@ class BuildTests: AppTestCase { func test_trigger() async throws { try await withDependencies { + $0.environment.awsDocsBucket = { "awsDocsBucket" } $0.environment.builderToken = { "builder token" } $0.environment.buildTimeout = { 10 } } operation: { @@ -197,6 +198,7 @@ class BuildTests: AppTestCase { func test_trigger_isDocBuild() async throws { try await withDependencies { + $0.environment.awsDocsBucket = { "awsDocsBucket" } $0.environment.builderToken = { "builder token" } $0.environment.buildTimeout = { 10 } } operation: { diff --git a/Tests/AppTests/BuildTriggerTests.swift b/Tests/AppTests/BuildTriggerTests.swift index 38e0a33cc..3899d0f4d 100644 --- a/Tests/AppTests/BuildTriggerTests.swift +++ b/Tests/AppTests/BuildTriggerTests.swift @@ -329,6 +329,7 @@ class BuildTriggerTests: AppTestCase { func test_triggerBuildsUnchecked() async throws { try await withDependencies { + $0.environment.awsDocsBucket = { "awsDocsBucket" } $0.environment.builderToken = { "builder token" } $0.environment.buildTimeout = { 10 } } operation: { @@ -390,6 +391,7 @@ class BuildTriggerTests: AppTestCase { func test_triggerBuildsUnchecked_supported() async throws { try await withDependencies { + $0.environment.awsDocsBucket = { "awsDocsBucket" } $0.environment.builderToken = { "builder token" } $0.environment.buildTimeout = { 10 } } operation: { @@ -470,6 +472,7 @@ class BuildTriggerTests: AppTestCase { func test_triggerBuildsUnchecked_build_exists() async throws { try await withDependencies { + $0.environment.awsDocsBucket = { "awsDocsBucket" } $0.environment.builderToken = { "builder token" } $0.environment.buildTimeout = { 10 } } operation: { @@ -550,6 +553,7 @@ class BuildTriggerTests: AppTestCase { func test_triggerBuilds_checked() async throws { try await withDependencies { $0.environment.allowBuildTriggers = { true } + $0.environment.awsDocsBucket = { "awsDocsBucket" } $0.environment.builderToken = { "builder token" } $0.environment.buildTimeout = { 10 } } operation: { @@ -662,6 +666,7 @@ class BuildTriggerTests: AppTestCase { func test_triggerBuilds_multiplePackages() async throws { try await withDependencies { $0.environment.allowBuildTriggers = { true } + $0.environment.awsDocsBucket = { "awsDocsBucket" } $0.environment.builderToken = { "builder token" } $0.environment.buildTimeout = { 10 } } operation: { @@ -712,6 +717,7 @@ class BuildTriggerTests: AppTestCase { func test_triggerBuilds_trimming() async throws { try await withDependencies { $0.environment.allowBuildTriggers = { true } + $0.environment.awsDocsBucket = { "awsDocsBucket" } $0.environment.builderToken = { "builder token" } } operation: { // Ensure we trim builds as part of triggering @@ -747,6 +753,7 @@ class BuildTriggerTests: AppTestCase { func test_triggerBuilds_error() async throws { try await withDependencies { $0.environment.allowBuildTriggers = { true } + $0.environment.awsDocsBucket = { "awsDocsBucket" } $0.environment.builderToken = { "builder token" } $0.environment.buildTimeout = { 10 } } operation: { @@ -843,6 +850,7 @@ class BuildTriggerTests: AppTestCase { func test_override_switch() async throws { try await withDependencies { + $0.environment.awsDocsBucket = { "awsDocsBucket" } $0.environment.builderToken = { "builder token" } $0.environment.buildTimeout = { 10 } } operation: { @@ -917,6 +925,7 @@ class BuildTriggerTests: AppTestCase { func test_downscaling() async throws { try await withDependencies { $0.environment.allowBuildTriggers = { true } + $0.environment.awsDocsBucket = { "awsDocsBucket" } $0.environment.builderToken = { "builder token" } $0.environment.buildTimeout = { 10 } } operation: { @@ -990,6 +999,7 @@ class BuildTriggerTests: AppTestCase { func test_downscaling_allow_list_override() async throws { try await withDependencies { $0.environment.allowBuildTriggers = { true } + $0.environment.awsDocsBucket = { "awsDocsBucket" } $0.environment.builderToken = { "builder token" } $0.environment.buildTimeout = { 10 } } operation: { diff --git a/Tests/AppTests/IngestorTests.swift b/Tests/AppTests/IngestorTests.swift index ff8f89e59..e63d8a82b 100644 --- a/Tests/AppTests/IngestorTests.swift +++ b/Tests/AppTests/IngestorTests.swift @@ -419,8 +419,12 @@ class IngestorTests: AppTestCase { } func test_S3Store_Key_readme() throws { - XCTAssertEqual(try S3Store.Key.readme(owner: "foo", repository: "bar").path, "foo/bar/readme.html") - XCTAssertEqual(try S3Store.Key.readme(owner: "FOO", repository: "bar").path, "foo/bar/readme.html") + try withDependencies { + $0.environment.awsReadmeBucket = { "readme-bucket" } + } operation: { + XCTAssertEqual(try S3Store.Key.readme(owner: "foo", repository: "bar").path, "foo/bar/readme.html") + XCTAssertEqual(try S3Store.Key.readme(owner: "FOO", repository: "bar").path, "foo/bar/readme.html") + } } func test_ingest_storeS3Readme() async throws { diff --git a/Tests/AppTests/PackageController+routesTests.swift b/Tests/AppTests/PackageController+routesTests.swift index 7ff1e9ee8..7cda52d38 100644 --- a/Tests/AppTests/PackageController+routesTests.swift +++ b/Tests/AppTests/PackageController+routesTests.swift @@ -453,23 +453,27 @@ class PackageController_routesTests: SnapshotTestCase { } func test_documentation_routes_contentType() async throws { - 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")) - } - try await app.test(.GET, "/owner/package/main/images/foo/bar.svg") { res async in - XCTAssertEqual(res.headers.contentType, .init(type: "image", subType: "svg+xml")) - } - try await app.test(.GET, "/owner/package/main/images/foo/bar.SVG") { res async in - XCTAssertEqual(res.headers.contentType, .init(type: "image", subType: "svg+xml")) - } - try await app.test(.GET, "/owner/package/main/img/foo/bar.jpeg") { res async in - XCTAssertEqual(res.headers.contentType, .init(type: "application", subType: "octet-stream")) - } - try await app.test(.GET, "/owner/package/main/img/foo/bar.svg") { res async in - XCTAssertEqual(res.headers.contentType, .init(type: "image", subType: "svg+xml")) - } - try await app.test(.GET, "/owner/package/main/img/foo/bar.SVG") { res async in - XCTAssertEqual(res.headers.contentType, .init(type: "image", subType: "svg+xml")) + try await withDependencies { + $0.environment.awsDocsBucket = { "docs-bucket" } + } 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")) + } + try await app.test(.GET, "/owner/package/main/images/foo/bar.svg") { res async in + XCTAssertEqual(res.headers.contentType, .init(type: "image", subType: "svg+xml")) + } + try await app.test(.GET, "/owner/package/main/images/foo/bar.SVG") { res async in + XCTAssertEqual(res.headers.contentType, .init(type: "image", subType: "svg+xml")) + } + try await app.test(.GET, "/owner/package/main/img/foo/bar.jpeg") { res async in + XCTAssertEqual(res.headers.contentType, .init(type: "application", subType: "octet-stream")) + } + try await app.test(.GET, "/owner/package/main/img/foo/bar.svg") { res async in + XCTAssertEqual(res.headers.contentType, .init(type: "image", subType: "svg+xml")) + } + try await app.test(.GET, "/owner/package/main/img/foo/bar.SVG") { res async in + XCTAssertEqual(res.headers.contentType, .init(type: "image", subType: "svg+xml")) + } } } @@ -532,263 +536,279 @@ class PackageController_routesTests: SnapshotTestCase { func test_documentation_routes_current() async throws { // Test the current (~) documentation routes: // /owner/package/documentation/~ + various path elements - // setup - let pkg = try await savePackage(on: app.db, "1") - try await Repository(package: pkg, name: "package", owner: "owner") - .save(on: app.db) - try await Version(package: pkg, - commit: "0123456789", - commitDate: .t0, - docArchives: [.init(name: "target", title: "Target")], - latest: .defaultBranch, - packageName: "pkg", - reference: .branch("main")) + try await withDependencies { + $0.environment.awsDocsBucket = { "docs-bucket" } + } operation: { + // setup + let pkg = try await savePackage(on: app.db, "1") + try await Repository(package: pkg, name: "package", owner: "owner") + .save(on: app.db) + try await Version(package: pkg, + commit: "0123456789", + commitDate: .t0, + docArchives: [.init(name: "target", title: "Target")], + latest: .defaultBranch, + packageName: "pkg", + reference: .branch("main")) .save(on: app.db) - try await Version(package: pkg, - commit: "9876543210", - commitDate: .t0, - docArchives: [.init(name: "target", title: "Target")], - latest: .release, - packageName: "pkg", - reference: .tag(1, 0, 0)) + try await Version(package: pkg, + commit: "9876543210", + commitDate: .t0, + docArchives: [.init(name: "target", title: "Target")], + latest: .release, + packageName: "pkg", + reference: .tag(1, 0, 0)) .save(on: app.db) - Current.fetchDocumentation = { _, _ in .init(status: .ok, body: .mockIndexHTML()) } + Current.fetchDocumentation = { _, _ in .init(status: .ok, body: .mockIndexHTML()) } - // MUT + // MUT - // test partially qualified route (no archive) - try await app.test(.GET, "/owner/package/~/documentation") { @Sendable res async in - XCTAssertEqual(res.status, .seeOther) - XCTAssertEqual(res.headers.location, "/owner/package/1.0.0/documentation/target") - } + // test partially qualified route (no archive) + try await app.test(.GET, "/owner/package/~/documentation") { @Sendable res async in + XCTAssertEqual(res.status, .seeOther) + XCTAssertEqual(res.headers.location, "/owner/package/1.0.0/documentation/target") + } - // test fully qualified route - try await app.test(.GET, "/owner/package/~/documentation/target") { @MainActor res in - await Task.yield() // essential to avoid deadlocking - XCTAssertEqual(res.status, .ok) - XCTAssertEqual(res.content.contentType?.description, "text/html; charset=utf-8") - let body = String(buffer: res.body) - assertSnapshot(of: body, as: .html, named: "index") - // Call out a couple of specific snippets in the html - XCTAssert(body.contains(#"var baseUrl = "/owner/package/~/""#)) - XCTAssert(body.contains(#""#)) - XCTAssertFalse(body.contains(#"1.0.0"#)) - } + // test fully qualified route + try await app.test(.GET, "/owner/package/~/documentation/target") { @MainActor res in + await Task.yield() // essential to avoid deadlocking + XCTAssertEqual(res.status, .ok) + XCTAssertEqual(res.content.contentType?.description, "text/html; charset=utf-8") + let body = String(buffer: res.body) + assertSnapshot(of: body, as: .html, named: "index") + // Call out a couple of specific snippets in the html + XCTAssert(body.contains(#"var baseUrl = "/owner/package/~/""#)) + XCTAssert(body.contains(#""#)) + XCTAssertFalse(body.contains(#"1.0.0"#)) + } - // test catchall - try await app.test(.GET, "/owner/package/~/documentation/target/a/b#anchor") { @MainActor res in - await Task.yield() // essential to avoid deadlocking - XCTAssertEqual(res.status, .ok) - XCTAssertEqual(res.content.contentType?.description, "text/html; charset=utf-8") - let body = String(buffer: res.body) - assertSnapshot(of: body, as: .html, named: "index") - // Call out a couple of specific snippets in the html - XCTAssert(body.contains(#"var baseUrl = "/owner/package/~/""#)) - XCTAssert(body.contains(#""#)) - XCTAssertFalse(body.contains(#"1.0.0"#)) - } + // test catchall + try await app.test(.GET, "/owner/package/~/documentation/target/a/b#anchor") { @MainActor res in + await Task.yield() // essential to avoid deadlocking + XCTAssertEqual(res.status, .ok) + XCTAssertEqual(res.content.contentType?.description, "text/html; charset=utf-8") + let body = String(buffer: res.body) + assertSnapshot(of: body, as: .html, named: "index") + // Call out a couple of specific snippets in the html + XCTAssert(body.contains(#"var baseUrl = "/owner/package/~/""#)) + XCTAssert(body.contains(#""#)) + XCTAssertFalse(body.contains(#"1.0.0"#)) + } - // Test case insensitive path. - try await app.test(.GET, "/Owner/Package/~/documentation/target/A/b#anchor") { @MainActor res in - await Task.yield() // essential to avoid deadlocking - XCTAssertEqual(res.status, .ok) - XCTAssertEqual(res.content.contentType?.description, "text/html; charset=utf-8") - let body = String(buffer: res.body) - assertSnapshot(of: body, as: .html, named: "index-mixed-case") - // Call out a couple of specific snippets in the html - XCTAssert(body.contains(#"var baseUrl = "/owner/package/~/""#)) - XCTAssert(body.contains(#""#)) - XCTAssertFalse(body.contains(#"1.0.0"#)) + // Test case insensitive path. + try await app.test(.GET, "/Owner/Package/~/documentation/target/A/b#anchor") { @MainActor res in + await Task.yield() // essential to avoid deadlocking + XCTAssertEqual(res.status, .ok) + XCTAssertEqual(res.content.contentType?.description, "text/html; charset=utf-8") + let body = String(buffer: res.body) + assertSnapshot(of: body, as: .html, named: "index-mixed-case") + // Call out a couple of specific snippets in the html + XCTAssert(body.contains(#"var baseUrl = "/owner/package/~/""#)) + XCTAssert(body.contains(#""#)) + XCTAssertFalse(body.contains(#"1.0.0"#)) + } } } func test_documentation_routes_current_rewrite() async throws { // Test the current (~) documentation routes with baseURL rewriting: // /owner/package/documentation/~ + various path elements - // setup - let pkg = try await savePackage(on: app.db, "1") - try await Repository(package: pkg, name: "package", owner: "owner") - .save(on: app.db) - try await Version(package: pkg, - commit: "0123456789", - commitDate: .t0, - docArchives: [.init(name: "target", title: "Target")], - latest: .defaultBranch, - packageName: "pkg", - reference: .branch("main")) + try await withDependencies { + $0.environment.awsDocsBucket = { "docs-bucket" } + } operation: { + // setup + let pkg = try await savePackage(on: app.db, "1") + try await Repository(package: pkg, name: "package", owner: "owner") + .save(on: app.db) + try await Version(package: pkg, + commit: "0123456789", + commitDate: .t0, + docArchives: [.init(name: "target", title: "Target")], + latest: .defaultBranch, + packageName: "pkg", + reference: .branch("main")) .save(on: app.db) - try await Version(package: pkg, - commit: "9876543210", - commitDate: .t0, - docArchives: [.init(name: "target", title: "Target")], - latest: .release, - packageName: "pkg", - reference: .tag(1, 0, 0)) + try await Version(package: pkg, + commit: "9876543210", + commitDate: .t0, + docArchives: [.init(name: "target", title: "Target")], + latest: .release, + 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")) } + Current.fetchDocumentation = { _, _ in .init(status: .ok, body: .mockIndexHTML(baseURL: "/owner/package/1.0.0")) } - // MUT + // MUT - // test fully qualified route - try await app.test(.GET, "/owner/package/~/documentation/target") { @MainActor res in - await Task.yield() // essential to avoid deadlocking - XCTAssertEqual(res.status, .ok) - XCTAssertEqual(res.content.contentType?.description, "text/html; charset=utf-8") - let body = String(buffer: res.body) - assertSnapshot(of: body, as: .html, named: "index") - // Call out a couple of specific snippets in the html - XCTAssert(body.contains(#"var baseUrl = "/owner/package/~/""#)) - XCTAssert(body.contains(#""#)) - XCTAssertFalse(body.contains(#"1.0.0"#)) - } + // test fully qualified route + try await app.test(.GET, "/owner/package/~/documentation/target") { @MainActor res in + await Task.yield() // essential to avoid deadlocking + XCTAssertEqual(res.status, .ok) + XCTAssertEqual(res.content.contentType?.description, "text/html; charset=utf-8") + let body = String(buffer: res.body) + assertSnapshot(of: body, as: .html, named: "index") + // Call out a couple of specific snippets in the html + XCTAssert(body.contains(#"var baseUrl = "/owner/package/~/""#)) + XCTAssert(body.contains(#""#)) + XCTAssertFalse(body.contains(#"1.0.0"#)) + } - // test catchall - try await app.test(.GET, "/owner/package/~/documentation/target/a/b#anchor") { @MainActor res in - await Task.yield() // essential to avoid deadlocking - XCTAssertEqual(res.status, .ok) - XCTAssertEqual(res.content.contentType?.description, "text/html; charset=utf-8") - let body = String(buffer: res.body) - assertSnapshot(of: body, as: .html, named: "index") - // Call out a couple of specific snippets in the html - XCTAssert(body.contains(#""#)) - XCTAssertFalse(body.contains(#"1.0.0"#)) - } + // test catchall + try await app.test(.GET, "/owner/package/~/documentation/target/a/b#anchor") { @MainActor res in + await Task.yield() // essential to avoid deadlocking + XCTAssertEqual(res.status, .ok) + XCTAssertEqual(res.content.contentType?.description, "text/html; charset=utf-8") + let body = String(buffer: res.body) + assertSnapshot(of: body, as: .html, named: "index") + // Call out a couple of specific snippets in the html + XCTAssert(body.contains(#""#)) + XCTAssertFalse(body.contains(#"1.0.0"#)) + } - // Test case insensitive path. - try await app.test(.GET, "/Owner/Package/~/documentation/target/A/b#anchor") { @MainActor res in - await Task.yield() // essential to avoid deadlocking - XCTAssertEqual(res.status, .ok) - XCTAssertEqual(res.content.contentType?.description, "text/html; charset=utf-8") - let body = String(buffer: res.body) - assertSnapshot(of: body, as: .html, named: "index-mixed-case") - // Call out a couple of specific snippets in the html - XCTAssert(body.contains(#""#)) - XCTAssertFalse(body.contains(#"1.0.0"#)) + // Test case insensitive path. + try await app.test(.GET, "/Owner/Package/~/documentation/target/A/b#anchor") { @MainActor res in + await Task.yield() // essential to avoid deadlocking + XCTAssertEqual(res.status, .ok) + XCTAssertEqual(res.content.contentType?.description, "text/html; charset=utf-8") + let body = String(buffer: res.body) + assertSnapshot(of: body, as: .html, named: "index-mixed-case") + // Call out a couple of specific snippets in the html + XCTAssert(body.contains(#""#)) + XCTAssertFalse(body.contains(#"1.0.0"#)) + } } } func test_documentation_routes_ref() async throws { // Test the documentation routes with a reference: // /owner/package/documentation/{reference} + various path elements - // setup - let pkg = try await savePackage(on: app.db, "1") - try await Repository(package: pkg, name: "package", owner: "owner") - .save(on: app.db) - try await Version(package: pkg, - commit: "0123456789", - commitDate: .t0, - docArchives: [.init(name: "target", title: "Target")], - latest: .defaultBranch, - packageName: "pkg", - reference: .branch("main")) + try await withDependencies { + $0.environment.awsDocsBucket = { "docs-bucket" } + } operation: { + // setup + let pkg = try await savePackage(on: app.db, "1") + try await Repository(package: pkg, name: "package", owner: "owner") + .save(on: app.db) + try await Version(package: pkg, + commit: "0123456789", + commitDate: .t0, + docArchives: [.init(name: "target", title: "Target")], + latest: .defaultBranch, + packageName: "pkg", + reference: .branch("main")) .save(on: app.db) - try await Version(package: pkg, - commit: "9876543210", - commitDate: .t0, - docArchives: [.init(name: "target", title: "Target")], - latest: .release, - packageName: "pkg", - reference: .tag(1, 2, 3)) + try await Version(package: pkg, + commit: "9876543210", + commitDate: .t0, + docArchives: [.init(name: "target", title: "Target")], + latest: .release, + packageName: "pkg", + reference: .tag(1, 2, 3)) .save(on: app.db) - Current.fetchDocumentation = { _, _ in .init(status: .ok, body: .mockIndexHTML()) } + Current.fetchDocumentation = { _, _ in .init(status: .ok, body: .mockIndexHTML()) } - // MUT + // MUT - // test partially qualified route (no archive) - try await app.test(.GET, "/owner/package/1.2.3/documentation") { @Sendable res async in - XCTAssertEqual(res.status, .seeOther) - XCTAssertEqual(res.headers.location, "/owner/package/1.2.3/documentation/target") - } + // test partially qualified route (no archive) + try await app.test(.GET, "/owner/package/1.2.3/documentation") { @Sendable res async in + XCTAssertEqual(res.status, .seeOther) + XCTAssertEqual(res.headers.location, "/owner/package/1.2.3/documentation/target") + } - // test fully qualified route - try await app.test(.GET, "/owner/package/1.2.3/documentation/target") { @MainActor res in - await Task.yield() // essential to avoid deadlocking - XCTAssertEqual(res.status, .ok) - XCTAssertEqual(res.content.contentType?.description, "text/html; charset=utf-8") - let body = String(buffer: res.body) - assertSnapshot(of: body, as: .html, named: "index-target") - // Call out a couple of specific snippets in the html - XCTAssert(body.contains(#"var baseUrl = "/owner/package/1.2.3/""#)) - XCTAssert(body.contains(#""#)) - XCTAssert(body.contains(#""#)) - XCTAssert(body.contains(#"Documentation for 1.2.3"#)) - } + // test fully qualified route + try await app.test(.GET, "/owner/package/1.2.3/documentation/target") { @MainActor res in + await Task.yield() // essential to avoid deadlocking + XCTAssertEqual(res.status, .ok) + XCTAssertEqual(res.content.contentType?.description, "text/html; charset=utf-8") + let body = String(buffer: res.body) + assertSnapshot(of: body, as: .html, named: "index-target") + // Call out a couple of specific snippets in the html + XCTAssert(body.contains(#"var baseUrl = "/owner/package/1.2.3/""#)) + XCTAssert(body.contains(#""#)) + XCTAssert(body.contains(#""#)) + XCTAssert(body.contains(#"Documentation for 1.2.3"#)) + } - // test catchall - try await app.test(.GET, "/owner/package/1.2.3/documentation/target/a/b#anchor") { @MainActor res in - await Task.yield() // essential to avoid deadlocking - XCTAssertEqual(res.status, .ok) - XCTAssertEqual(res.content.contentType?.description, "text/html; charset=utf-8") - let body = String(buffer: res.body) - assertSnapshot(of: body, as: .html, named: "index-target-a-b") - // Call out a couple of specific snippets in the html - XCTAssert(body.contains(#"var baseUrl = "/owner/package/1.2.3/""#)) - XCTAssert(body.contains(#""#)) - XCTAssert(body.contains(#""#)) - XCTAssert(body.contains(#"Documentation for 1.2.3"#)) - } + // test catchall + try await app.test(.GET, "/owner/package/1.2.3/documentation/target/a/b#anchor") { @MainActor res in + await Task.yield() // essential to avoid deadlocking + XCTAssertEqual(res.status, .ok) + XCTAssertEqual(res.content.contentType?.description, "text/html; charset=utf-8") + let body = String(buffer: res.body) + assertSnapshot(of: body, as: .html, named: "index-target-a-b") + // Call out a couple of specific snippets in the html + XCTAssert(body.contains(#"var baseUrl = "/owner/package/1.2.3/""#)) + XCTAssert(body.contains(#""#)) + XCTAssert(body.contains(#""#)) + XCTAssert(body.contains(#"Documentation for 1.2.3"#)) + } - // Test case insensitive path. - try await app.test(.GET, "/Owner/Package/1.2.3/documentation/target/A/b#Anchor") { @MainActor res in - await Task.yield() // essential to avoid deadlocking - XCTAssertEqual(res.status, .ok) - XCTAssertEqual(res.content.contentType?.description, "text/html; charset=utf-8") - let body = String(buffer: res.body) - assertSnapshot(of: body, as: .html, named: "index-target-a-b-mixed-case") - // Call out a couple of specific snippets in the html - XCTAssert(body.contains(#"var baseUrl = "/owner/package/1.2.3/""#)) - XCTAssert(body.contains(#""#)) - XCTAssert(body.contains(#""#)) - XCTAssert(body.contains(#"Documentation for 1.2.3"#)) + // Test case insensitive path. + try await app.test(.GET, "/Owner/Package/1.2.3/documentation/target/A/b#Anchor") { @MainActor res in + await Task.yield() // essential to avoid deadlocking + XCTAssertEqual(res.status, .ok) + XCTAssertEqual(res.content.contentType?.description, "text/html; charset=utf-8") + let body = String(buffer: res.body) + assertSnapshot(of: body, as: .html, named: "index-target-a-b-mixed-case") + // Call out a couple of specific snippets in the html + XCTAssert(body.contains(#"var baseUrl = "/owner/package/1.2.3/""#)) + XCTAssert(body.contains(#""#)) + XCTAssert(body.contains(#""#)) + XCTAssert(body.contains(#"Documentation for 1.2.3"#)) + } } } func test_documentation_routes_no_archive() async throws { // Test documentation routes when no archive is in the path - // setup - let pkg = try await savePackage(on: app.db, "1") - try await Repository(package: pkg, name: "package", owner: "owner") - .save(on: app.db) - try await Version(package: pkg, - commit: "0123456789", - commitDate: .t0, - docArchives: [.init(name: "target", title: "Target")], - latest: .defaultBranch, - packageName: "pkg", - reference: .branch("main")) + try await withDependencies { + $0.environment.awsDocsBucket = { "docs-bucket" } + } operation: { + // setup + let pkg = try await savePackage(on: app.db, "1") + try await Repository(package: pkg, name: "package", owner: "owner") + .save(on: app.db) + try await Version(package: pkg, + commit: "0123456789", + commitDate: .t0, + docArchives: [.init(name: "target", title: "Target")], + latest: .defaultBranch, + packageName: "pkg", + reference: .branch("main")) .save(on: app.db) - try await Version(package: pkg, - commit: "9876543210", - commitDate: .t0, - docArchives: [.init(name: "target", title: "Target")], - latest: .release, - packageName: "pkg", - reference: .tag(1, 0, 0)) + try await Version(package: pkg, + commit: "9876543210", + commitDate: .t0, + docArchives: [.init(name: "target", title: "Target")], + latest: .release, + packageName: "pkg", + reference: .tag(1, 0, 0)) .save(on: app.db) - Current.fetchDocumentation = { _, _ in .init(status: .ok, body: .mockIndexHTML()) } + Current.fetchDocumentation = { _, _ in .init(status: .ok, body: .mockIndexHTML()) } - // MUT - try await app.test(.GET, "/owner/package/main/documentation") { res async in - XCTAssertEqual(res.status, .seeOther) - XCTAssertEqual(res.headers.location, "/owner/package/main/documentation/target") - } - try await app.test(.GET, "/owner/package/1.0.0/documentation") { res async in - XCTAssertEqual(res.status, .seeOther) - XCTAssertEqual(res.headers.location, "/owner/package/1.0.0/documentation/target") - } - try await app.test(.GET, "/owner/package/~/documentation") { res async in - XCTAssertEqual(res.status, .seeOther) - XCTAssertEqual(res.headers.location, "/owner/package/1.0.0/documentation/target") + // MUT + try await app.test(.GET, "/owner/package/main/documentation") { res async in + XCTAssertEqual(res.status, .seeOther) + XCTAssertEqual(res.headers.location, "/owner/package/main/documentation/target") + } + try await app.test(.GET, "/owner/package/1.0.0/documentation") { res async in + XCTAssertEqual(res.status, .seeOther) + XCTAssertEqual(res.headers.location, "/owner/package/1.0.0/documentation/target") + } + try await app.test(.GET, "/owner/package/~/documentation") { res async in + XCTAssertEqual(res.status, .seeOther) + XCTAssertEqual(res.headers.location, "/owner/package/1.0.0/documentation/target") + } } } @@ -826,462 +846,522 @@ class PackageController_routesTests: SnapshotTestCase { func test_documentation_404() async throws { // Test conversion of any doc fetching errors into 404s. - // 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) - try await Version(package: pkg, latest: .defaultBranch, packageName: "pkg") - .save(on: app.db) - - // MUT - // test base url - try await app.test(.GET, "/owner/package/1.2.3/documentation") { res async in - XCTAssertEqual(res.status, .notFound) - } + try await withDependencies { + $0.environment.awsDocsBucket = { "docs-bucket" } + } 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) + try await Version(package: pkg, latest: .defaultBranch, packageName: "pkg") + .save(on: app.db) + + // MUT + // test base url + try await app.test(.GET, "/owner/package/1.2.3/documentation") { res async in + XCTAssertEqual(res.status, .notFound) + } - // test path a/b - try await app.test(.GET, "/owner/package/1.2.3/documentation/a/b") { res async in - XCTAssertEqual(res.status, .notFound) + // test path a/b + try await app.test(.GET, "/owner/package/1.2.3/documentation/a/b") { res async in + XCTAssertEqual(res.status, .notFound) + } } } func test_documentation_error() async throws { // Test behaviour when fetchDocumentation throws - 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") + try await withDependencies { + $0.environment.awsDocsBucket = { "docs-bucket" } + } 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) + try await Version(package: pkg, + commit: "123", + commitDate: .t0, + docArchives: [.init(name: "foo", title: "Foo")], + latest: .defaultBranch, + packageName: "pkg", + reference: .tag(1, 2, 3)) .save(on: app.db) - try await Version(package: pkg, - commit: "123", - commitDate: .t0, - docArchives: [.init(name: "foo", title: "Foo")], - latest: .defaultBranch, - packageName: "pkg", - reference: .tag(1, 2, 3)) - .save(on: app.db) - // MUT - try await app.test(.GET, "/owner/package/1.2.3/documentation") { res async in - XCTAssertEqual(res.status, .seeOther) - XCTAssertEqual(res.headers.location, "/owner/package/1.2.3/documentation/foo") - } - try await app.test(.GET, "/owner/package/1.2.3/documentation/foo") { res async in - // hits Current.fetchDocumentation which throws, converted to notFound - // Regression test for https://github.com/SwiftPackageIndex/SwiftPackageIndex-Server/issues/2015 - XCTAssertEqual(res.status, .notFound) + // MUT + try await app.test(.GET, "/owner/package/1.2.3/documentation") { res async in + XCTAssertEqual(res.status, .seeOther) + XCTAssertEqual(res.headers.location, "/owner/package/1.2.3/documentation/foo") + } + try await app.test(.GET, "/owner/package/1.2.3/documentation/foo") { res async in + // hits Current.fetchDocumentation which throws, converted to notFound + // Regression test for https://github.com/SwiftPackageIndex/SwiftPackageIndex-Server/issues/2015 + XCTAssertEqual(res.status, .notFound) + } } } func test_documentation_current_css() async throws { - // 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) - try await Version(package: pkg, - docArchives: [.init(name: "target", title: "Target")], - latest: .defaultBranch, - reference: .branch("main")) + try await withDependencies { + $0.environment.awsDocsBucket = { "docs-bucket" } + } 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) + try await Version(package: pkg, + docArchives: [.init(name: "target", title: "Target")], + latest: .defaultBranch, + reference: .branch("main")) .save(on: app.db) - // MUT - // test base url - try await app.test(.GET, "/owner/package/~/css/a") { res async in - XCTAssertEqual(res.status, .ok) - XCTAssertEqual(res.content.contentType?.description, "text/css") - XCTAssertEqual(res.body.asString(), "/owner/package/main/css/a") - } + // MUT + // test base url + try await app.test(.GET, "/owner/package/~/css/a") { res async in + XCTAssertEqual(res.status, .ok) + XCTAssertEqual(res.content.contentType?.description, "text/css") + XCTAssertEqual(res.body.asString(), "/owner/package/main/css/a") + } - // test path a/b - try await app.test(.GET, "/owner/package/~/css/a/b") { res async in - XCTAssertEqual(res.status, .ok) - XCTAssertEqual(res.content.contentType?.description, "text/css") - XCTAssertEqual(res.body.asString(), "/owner/package/main/css/a/b") + // test path a/b + try await app.test(.GET, "/owner/package/~/css/a/b") { res async in + XCTAssertEqual(res.status, .ok) + XCTAssertEqual(res.content.contentType?.description, "text/css") + XCTAssertEqual(res.body.asString(), "/owner/package/main/css/a/b") + } } } func test_documentation_ref_css() throws { - // 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)) - } + try withDependencies { + $0.environment.awsDocsBucket = { "docs-bucket" } + } 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") { - XCTAssertEqual($0.status, .ok) - XCTAssertEqual($0.content.contentType?.description, "text/css") - XCTAssertEqual($0.body.asString(), "/owner/package/1.2.3/css/a") - } + // MUT + // test base url + try app.test(.GET, "/owner/package/1.2.3/css/a") { + XCTAssertEqual($0.status, .ok) + XCTAssertEqual($0.content.contentType?.description, "text/css") + XCTAssertEqual($0.body.asString(), "/owner/package/1.2.3/css/a") + } - // test path a/b - try app.test(.GET, "/owner/package/1.2.3/css/a/b") { - XCTAssertEqual($0.status, .ok) - XCTAssertEqual($0.content.contentType?.description, "text/css") - XCTAssertEqual($0.body.asString(), "/owner/package/1.2.3/css/a/b") + // test path a/b + try app.test(.GET, "/owner/package/1.2.3/css/a/b") { + XCTAssertEqual($0.status, .ok) + XCTAssertEqual($0.content.contentType?.description, "text/css") + XCTAssertEqual($0.body.asString(), "/owner/package/1.2.3/css/a/b") + } } } func test_documentation_current_js() async throws { - // 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) - try await Version(package: pkg, - docArchives: [.init(name: "target", title: "Target")], - latest: .defaultBranch, - reference: .branch("main")) + try await withDependencies { + $0.environment.awsDocsBucket = { "docs-bucket" } + } 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) + try await Version(package: pkg, + docArchives: [.init(name: "target", title: "Target")], + latest: .defaultBranch, + reference: .branch("main")) .save(on: app.db) - // MUT - // test base url - try await app.test(.GET, "/owner/package/~/js/a") { res async in - XCTAssertEqual(res.status, .ok) - XCTAssertEqual(res.content.contentType?.description, "application/javascript") - XCTAssertEqual(res.body.asString(), "/owner/package/main/js/a") - } + // MUT + // test base url + try await app.test(.GET, "/owner/package/~/js/a") { res async in + XCTAssertEqual(res.status, .ok) + XCTAssertEqual(res.content.contentType?.description, "application/javascript") + XCTAssertEqual(res.body.asString(), "/owner/package/main/js/a") + } - // test path a/b - try await app.test(.GET, "/owner/package/~/js/a/b") { res async in - XCTAssertEqual(res.status, .ok) - XCTAssertEqual(res.content.contentType?.description, "application/javascript") - XCTAssertEqual(res.body.asString(), "/owner/package/main/js/a/b") + // test path a/b + try await app.test(.GET, "/owner/package/~/js/a/b") { res async in + XCTAssertEqual(res.status, .ok) + XCTAssertEqual(res.content.contentType?.description, "application/javascript") + XCTAssertEqual(res.body.asString(), "/owner/package/main/js/a/b") + } } } func test_documentation_ref_js() throws { - // 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)) - } + try withDependencies { + $0.environment.awsDocsBucket = { "docs-bucket" } + } 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") { - XCTAssertEqual($0.status, .ok) - XCTAssertEqual($0.content.contentType?.description, "application/javascript") - XCTAssertEqual($0.body.asString(), "/owner/package/1.2.3/js/a") - } + // MUT + // test base url + try app.test(.GET, "/owner/package/1.2.3/js/a") { + XCTAssertEqual($0.status, .ok) + XCTAssertEqual($0.content.contentType?.description, "application/javascript") + XCTAssertEqual($0.body.asString(), "/owner/package/1.2.3/js/a") + } - // test path a/b - try app.test(.GET, "/owner/package/1.2.3/js/a/b") { - XCTAssertEqual($0.status, .ok) - XCTAssertEqual($0.content.contentType?.description, "application/javascript") - XCTAssertEqual($0.body.asString(), "/owner/package/1.2.3/js/a/b") + // test path a/b + try app.test(.GET, "/owner/package/1.2.3/js/a/b") { + XCTAssertEqual($0.status, .ok) + XCTAssertEqual($0.content.contentType?.description, "application/javascript") + XCTAssertEqual($0.body.asString(), "/owner/package/1.2.3/js/a/b") + } } } func test_documentation_current_data() async throws { - // 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) - try await Version(package: pkg, - docArchives: [.init(name: "target", title: "Target")], - latest: .defaultBranch, - reference: .branch("main")) + try await withDependencies { + $0.environment.awsDocsBucket = { "docs-bucket" } + } 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) + try await Version(package: pkg, + docArchives: [.init(name: "target", title: "Target")], + latest: .defaultBranch, + reference: .branch("main")) .save(on: app.db) - // MUT - // test base url - try await app.test(.GET, "/owner/package/~/data/a") { res async in - XCTAssertEqual(res.status, .ok) - XCTAssertEqual(res.content.contentType?.description, "application/octet-stream") - XCTAssertEqual(res.body.asString(), "/owner/package/main/data/a") - } + // MUT + // test base url + try await app.test(.GET, "/owner/package/~/data/a") { res async in + XCTAssertEqual(res.status, .ok) + XCTAssertEqual(res.content.contentType?.description, "application/octet-stream") + XCTAssertEqual(res.body.asString(), "/owner/package/main/data/a") + } - // test path a/b - try await app.test(.GET, "/owner/package/~/data/a/b") { res async in - XCTAssertEqual(res.status, .ok) - XCTAssertEqual(res.content.contentType?.description, "application/octet-stream") - XCTAssertEqual(res.body.asString(), "/owner/package/main/data/a/b") - } + // test path a/b + try await app.test(.GET, "/owner/package/~/data/a/b") { res async in + XCTAssertEqual(res.status, .ok) + XCTAssertEqual(res.content.contentType?.description, "application/octet-stream") + XCTAssertEqual(res.body.asString(), "/owner/package/main/data/a/b") + } - // test case-insensitivity - // https://github.com/SwiftPackageIndex/SwiftPackageIndex-Server/issues/2168 - try await app.test(.GET, "/owner/package/~/data/documentation/Foo.json") { res async in - XCTAssertEqual(res.status, .ok) - XCTAssertEqual(res.content.contentType?.description, "application/octet-stream") - XCTAssertEqual(res.body.asString(), - "/owner/package/main/data/documentation/foo.json") + // test case-insensitivity + // https://github.com/SwiftPackageIndex/SwiftPackageIndex-Server/issues/2168 + try await app.test(.GET, "/owner/package/~/data/documentation/Foo.json") { res async in + XCTAssertEqual(res.status, .ok) + XCTAssertEqual(res.content.contentType?.description, "application/octet-stream") + XCTAssertEqual(res.body.asString(), + "/owner/package/main/data/documentation/foo.json") + } } } func test_documentation_ref_data() throws { - // 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)) - } + try withDependencies { + $0.environment.awsDocsBucket = { "docs-bucket" } + } 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") { - XCTAssertEqual($0.status, .ok) - XCTAssertEqual($0.content.contentType?.description, "application/octet-stream") - XCTAssertEqual($0.body.asString(), "/owner/package/1.2.3/data/a") - } + // MUT + // test base url + try app.test(.GET, "/owner/package/1.2.3/data/a") { + XCTAssertEqual($0.status, .ok) + XCTAssertEqual($0.content.contentType?.description, "application/octet-stream") + XCTAssertEqual($0.body.asString(), "/owner/package/1.2.3/data/a") + } - // test path a/b - try app.test(.GET, "/owner/package/1.2.3/data/a/b") { - XCTAssertEqual($0.status, .ok) - XCTAssertEqual($0.content.contentType?.description, "application/octet-stream") - XCTAssertEqual($0.body.asString(), "/owner/package/1.2.3/data/a/b") - } + // test path a/b + try app.test(.GET, "/owner/package/1.2.3/data/a/b") { + XCTAssertEqual($0.status, .ok) + XCTAssertEqual($0.content.contentType?.description, "application/octet-stream") + XCTAssertEqual($0.body.asString(), "/owner/package/1.2.3/data/a/b") + } - // test case-insensitivity - // https://github.com/SwiftPackageIndex/SwiftPackageIndex-Server/issues/2168 - try app.test(.GET, "/apple/swift-nio/main/data/documentation/NIOCore.json") { - XCTAssertEqual($0.status, .ok) - XCTAssertEqual($0.content.contentType?.description, "application/octet-stream") - XCTAssertEqual($0.body.asString(), - "/apple/swift-nio/main/data/documentation/niocore.json") + // test case-insensitivity + // https://github.com/SwiftPackageIndex/SwiftPackageIndex-Server/issues/2168 + try app.test(.GET, "/apple/swift-nio/main/data/documentation/NIOCore.json") { + XCTAssertEqual($0.status, .ok) + XCTAssertEqual($0.content.contentType?.description, "application/octet-stream") + XCTAssertEqual($0.body.asString(), + "/apple/swift-nio/main/data/documentation/niocore.json") + } } } func test_documentation_canonicalCapitalisation() async throws { - // 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") - try await Repository(package: pkg, name: "Package", owner: "Owner") - .save(on: app.db) - try await Version(package: pkg, - commit: "0123456789", - commitDate: .t0, - docArchives: [.init(name: "docs", title: "Docs")], - latest: .defaultBranch, - packageName: "package", - reference: .tag(1, 2, 3)) + try await withDependencies { + $0.environment.awsDocsBucket = { "docs-bucket" } + } 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") + try await Repository(package: pkg, name: "Package", owner: "Owner") + .save(on: app.db) + try await Version(package: pkg, + commit: "0123456789", + commitDate: .t0, + docArchives: [.init(name: "docs", title: "Docs")], + latest: .defaultBranch, + packageName: "package", + reference: .tag(1, 2, 3)) .save(on: app.db) - try await app.test(.GET, "/owner/package/1.2.3/documentation/a/b") { res async throws in - let document = try SwiftSoup.parse(res.body.string) - let linkElements = try document.select("link[rel='canonical']") - XCTAssertEqual(linkElements.count, 1) + try await app.test(.GET, "/owner/package/1.2.3/documentation/a/b") { res async throws in + let document = try SwiftSoup.parse(res.body.string) + let linkElements = try document.select("link[rel='canonical']") + XCTAssertEqual(linkElements.count, 1) - let href = try XCTUnwrap(linkElements.first()?.attr("href")) - XCTAssertEqual(href, "/Owner/Package/1.2.3/documentation/a/b") + let href = try XCTUnwrap(linkElements.first()?.attr("href")) + XCTAssertEqual(href, "/Owner/Package/1.2.3/documentation/a/b") + } } } func test_documentation_issue_2287() async throws { // https://github.com/SwiftPackageIndex/SwiftPackageIndex-Server/issues/2287 // Ensure references are path encoded - // setup - let pkg = try await savePackage(on: app.db, "1") - try await Repository(package: pkg, name: "package", owner: "owner") + try await withDependencies { + $0.environment.awsDocsBucket = { "docs-bucket" } + } operation: { + // setup + let pkg = try await savePackage(on: app.db, "1") + try await Repository(package: pkg, name: "package", owner: "owner") + .save(on: app.db) + try await Version(package: pkg, + commit: "0123456789", + commitDate: .t0, + docArchives: [.init(name: "target", title: "Target")], + latest: .defaultBranch, + packageName: "pkg", + reference: .branch("feature/1.2.3")) .save(on: app.db) - try await Version(package: pkg, - commit: "0123456789", - commitDate: .t0, - docArchives: [.init(name: "target", title: "Target")], - latest: .defaultBranch, - packageName: "pkg", - reference: .branch("feature/1.2.3")) - .save(on: app.db) - Current.fetchDocumentation = { _, _ in .init(status: .ok, body: .mockIndexHTML()) } + Current.fetchDocumentation = { _, _ in .init(status: .ok, body: .mockIndexHTML()) } - // MUT + // MUT - // test default path - try await app.test(.GET, "/owner/package/~/documentation/target") { @MainActor res in - await Task.yield() // essential to avoid deadlocking - XCTAssertEqual(res.status, .ok) - let body = String(buffer: res.body) - assertSnapshot(of: body, as: .html, named: "current-index") - // Call out a couple of specific snippets in the html - XCTAssert(body.contains(#"var baseUrl = "/owner/package/~/""#)) - XCTAssert(body.contains(#""#)) - XCTAssertFalse(body.contains(#"feature/1.2.3"#)) - XCTAssert(body.contains(#"
  • feature/1.2.3
  • "#)) - } + // test default path + try await app.test(.GET, "/owner/package/~/documentation/target") { @MainActor res in + await Task.yield() // essential to avoid deadlocking + XCTAssertEqual(res.status, .ok) + let body = String(buffer: res.body) + assertSnapshot(of: body, as: .html, named: "current-index") + // Call out a couple of specific snippets in the html + XCTAssert(body.contains(#"var baseUrl = "/owner/package/~/""#)) + XCTAssert(body.contains(#""#)) + XCTAssertFalse(body.contains(#"feature/1.2.3"#)) + XCTAssert(body.contains(#"
  • feature/1.2.3
  • "#)) + } - // test reference root path - try await app.test(.GET, "/owner/package/feature-1.2.3/documentation/target") { @MainActor res in - await Task.yield() // essential to avoid deadlocking - XCTAssertEqual(res.status, .ok) - let body = String(buffer: res.body) - assertSnapshot(of: body, as: .html, named: "ref-index") - // Call out a couple of specific snippets in the html - XCTAssert(body.contains(#"var baseUrl = "/owner/package/feature-1.2.3/""#)) - XCTAssert(body.contains(#""#)) - XCTAssert(body.contains(#""#)) - XCTAssert(body.contains(#"Documentation for feature-1.2.3"#)) - } + // test reference root path + try await app.test(.GET, "/owner/package/feature-1.2.3/documentation/target") { @MainActor res in + await Task.yield() // essential to avoid deadlocking + XCTAssertEqual(res.status, .ok) + let body = String(buffer: res.body) + assertSnapshot(of: body, as: .html, named: "ref-index") + // Call out a couple of specific snippets in the html + XCTAssert(body.contains(#"var baseUrl = "/owner/package/feature-1.2.3/""#)) + XCTAssert(body.contains(#""#)) + XCTAssert(body.contains(#""#)) + XCTAssert(body.contains(#"Documentation for feature-1.2.3"#)) + } - // test path a/b - try await app.test(.GET, "/owner/package/feature-1.2.3/documentation/a/b") { @MainActor res in - await Task.yield() // essential to avoid deadlocking - XCTAssertEqual(res.status, .ok) - XCTAssertEqual(res.content.contentType?.description, "text/html; charset=utf-8") - let body = String(buffer: res.body) - assertSnapshot(of: body, as: .html, named: "ref-index-path") - // Call out a couple of specific snippets in the html - XCTAssert(body.contains(#"var baseUrl = "/owner/package/feature-1.2.3/""#)) - XCTAssert(body.contains(#""#)) - XCTAssert(body.contains(#""#)) - XCTAssert(body.contains(#"Documentation for feature-1.2.3"#)) + // test path a/b + try await app.test(.GET, "/owner/package/feature-1.2.3/documentation/a/b") { @MainActor res in + await Task.yield() // essential to avoid deadlocking + XCTAssertEqual(res.status, .ok) + XCTAssertEqual(res.content.contentType?.description, "text/html; charset=utf-8") + let body = String(buffer: res.body) + assertSnapshot(of: body, as: .html, named: "ref-index-path") + // Call out a couple of specific snippets in the html + XCTAssert(body.contains(#"var baseUrl = "/owner/package/feature-1.2.3/""#)) + XCTAssert(body.contains(#""#)) + XCTAssert(body.contains(#""#)) + XCTAssert(body.contains(#"Documentation for feature-1.2.3"#)) + } } } func test_documentation_routes_tutorials() async throws { - // setup - let pkg = try await savePackage(on: app.db, "1") - try await Repository(package: pkg, name: "package", owner: "owner") - .save(on: app.db) - try await Version(package: pkg, - commit: "0123456789", - commitDate: .t0, - docArchives: [.init(name: "docs", title: "Docs")], - latest: .defaultBranch, - packageName: "pkg", - reference: .branch("main")) + try await withDependencies { + $0.environment.awsDocsBucket = { "docs-bucket" } + } operation: { + // setup + let pkg = try await savePackage(on: app.db, "1") + try await Repository(package: pkg, name: "package", owner: "owner") + .save(on: app.db) + try await Version(package: pkg, + commit: "0123456789", + commitDate: .t0, + docArchives: [.init(name: "docs", title: "Docs")], + latest: .defaultBranch, + packageName: "pkg", + reference: .branch("main")) .save(on: app.db) - try await Version(package: pkg, - commit: "9876543210", - commitDate: .t0, - docArchives: [.init(name: "docs", title: "Docs")], - latest: .release, - packageName: "pkg", - reference: .tag(1, 0, 0)) + try await Version(package: pkg, + commit: "9876543210", + commitDate: .t0, + docArchives: [.init(name: "docs", title: "Docs")], + latest: .release, + packageName: "pkg", + reference: .tag(1, 0, 0)) .save(on: app.db) - Current.fetchDocumentation = { _, _ in .init(status: .ok, body: .mockIndexHTML()) } + Current.fetchDocumentation = { _, _ in .init(status: .ok, body: .mockIndexHTML()) } - // MUT - try await app.test(.GET, "/owner/package/~/tutorials") { res async in - XCTAssertEqual(res.status, .notFound) - } - try await app.test(.GET, "/owner/package/~/tutorials/foo") { @MainActor res in - await Task.yield() // essential to avoid deadlocking - XCTAssertEqual(res.status, .ok) - let body = String(buffer: res.body) - assertSnapshot(of: body, as: .html, named: "index") - XCTAssert(body.contains(#"var baseUrl = "/owner/package/~/""#)) - } - try await app.test(.GET, "/owner/package/~/tutorials/foo#anchor") { @MainActor res in - await Task.yield() // essential to avoid deadlocking - XCTAssertEqual(res.status, .ok) - let body = String(buffer: res.body) - assertSnapshot(of: body, as: .html, named: "index") - XCTAssert(body.contains(#"var baseUrl = "/owner/package/~/""#)) + // MUT + try await app.test(.GET, "/owner/package/~/tutorials") { res async in + XCTAssertEqual(res.status, .notFound) + } + try await app.test(.GET, "/owner/package/~/tutorials/foo") { @MainActor res in + await Task.yield() // essential to avoid deadlocking + XCTAssertEqual(res.status, .ok) + let body = String(buffer: res.body) + assertSnapshot(of: body, as: .html, named: "index") + XCTAssert(body.contains(#"var baseUrl = "/owner/package/~/""#)) + } + try await app.test(.GET, "/owner/package/~/tutorials/foo#anchor") { @MainActor res in + await Task.yield() // essential to avoid deadlocking + XCTAssertEqual(res.status, .ok) + let body = String(buffer: res.body) + assertSnapshot(of: body, as: .html, named: "index") + XCTAssert(body.contains(#"var baseUrl = "/owner/package/~/""#)) + } } } func test_favicon() throws { - // 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)) - } + try withDependencies { + $0.environment.awsDocsBucket = { "docs-bucket" } + } 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) - XCTAssertEqual($0.content.contentType?.description, "application/octet-stream") - XCTAssertEqual($0.body.asString(), "/owner/package/1.2.3/favicon.ico") - } + // MUT + try app.test(.GET, "/owner/package/1.2.3/favicon.ico") { + XCTAssertEqual($0.status, .ok) + XCTAssertEqual($0.content.contentType?.description, "application/octet-stream") + XCTAssertEqual($0.body.asString(), "/owner/package/1.2.3/favicon.ico") + } - try app.test(.GET, "/owner/package/1.2.3/favicon.svg") { - XCTAssertEqual($0.status, .ok) - XCTAssertEqual($0.content.contentType?.description, "application/octet-stream") - XCTAssertEqual($0.body.asString(), "/owner/package/1.2.3/favicon.svg") + try app.test(.GET, "/owner/package/1.2.3/favicon.svg") { + XCTAssertEqual($0.status, .ok) + XCTAssertEqual($0.content.contentType?.description, "application/octet-stream") + XCTAssertEqual($0.body.asString(), "/owner/package/1.2.3/favicon.svg") + } } } func test_themeSettings() throws { - // 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)) - } + try withDependencies { + $0.environment.awsDocsBucket = { "docs-bucket" } + } 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) - XCTAssertEqual($0.content.contentType?.description, "application/json") - XCTAssertEqual($0.body.asString(), "/owner/package/1.2.3/theme-settings.json") + // MUT + try app.test(.GET, "/owner/package/1.2.3/theme-settings.json") { + XCTAssertEqual($0.status, .ok) + XCTAssertEqual($0.content.contentType?.description, "application/json") + XCTAssertEqual($0.body.asString(), "/owner/package/1.2.3/theme-settings.json") + } } } func test_linkablePaths() throws { - // 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)) - } + try withDependencies { + $0.environment.awsDocsBucket = { "docs-bucket" } + } 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) - XCTAssertEqual($0.content.contentType?.description, "application/json") - XCTAssertEqual($0.body.asString(), "/owner/package/1.2.3/linkable-paths.json") + // MUT + try app.test(.GET, "/owner/package/1.2.3/linkable-paths.json") { + XCTAssertEqual($0.status, .ok) + XCTAssertEqual($0.content.contentType?.description, "application/json") + XCTAssertEqual($0.body.asString(), "/owner/package/1.2.3/linkable-paths.json") + } } } func test_tutorial() async throws { - // 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) - try await Version(package: pkg, - commit: "0123456789", - commitDate: Date(timeIntervalSince1970: 0), - docArchives: [.init(name: "docs", title: "Docs")], - latest: .defaultBranch, - packageName: "pkg", - reference: .tag(.init(1, 2, 3))) + try await withDependencies { + $0.environment.awsDocsBucket = { "docs-bucket" } + } 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) + try await Version(package: pkg, + commit: "0123456789", + commitDate: Date(timeIntervalSince1970: 0), + docArchives: [.init(name: "docs", title: "Docs")], + latest: .defaultBranch, + packageName: "pkg", + reference: .tag(.init(1, 2, 3))) .save(on: app.db) - // MUT - // test path a/b - try await app.test(.GET, "/owner/package/1.2.3/tutorials/a/b") { res async in - XCTAssertEqual(res.status, .ok) - XCTAssertEqual(res.content.contentType?.description, "text/html; charset=utf-8") - XCTAssertTrue( - res.body.asString().contains("

    /owner/package/1.2.3/tutorials/a/b

    "), - "was: \(res.body.asString())" - ) - // Assert body includes the docc.css stylesheet link (as a test that our proxy header injection works) - XCTAssertTrue(res.body.asString().contains(#""#), - "was: \(res.body.asString())") - } + // MUT + // test path a/b + try await app.test(.GET, "/owner/package/1.2.3/tutorials/a/b") { res async in + XCTAssertEqual(res.status, .ok) + XCTAssertEqual(res.content.contentType?.description, "text/html; charset=utf-8") + XCTAssertTrue( + res.body.asString().contains("

    /owner/package/1.2.3/tutorials/a/b

    "), + "was: \(res.body.asString())" + ) + // Assert body includes the docc.css stylesheet link (as a test that our proxy header injection works) + XCTAssertTrue(res.body.asString().contains(#""#), + "was: \(res.body.asString())") + } - // Test case insensitive path. - try await app.test(.GET, "/Owner/Package/1.2.3/tutorials/a/b") { res async in - XCTAssertEqual(res.status, .ok) + // Test case insensitive path. + try await app.test(.GET, "/Owner/Package/1.2.3/tutorials/a/b") { res async in + XCTAssertEqual(res.status, .ok) + } } } @@ -1396,64 +1476,67 @@ class PackageController_routesTests: SnapshotTestCase { func test_issue_2288() async throws { // Ensures default branch updates don't introduce a "documentation gap" // https://github.com/SwiftPackageIndex/SwiftPackageIndex-Server/issues/2288 - - // setup - let pkg = try await savePackage(on: app.db, "https://github.com/foo/bar".url, processingStage: .ingestion) - try await Repository(package: pkg, defaultBranch: "main", name: "bar", owner: "foo") - .save(on: app.db) - try await Version(package: pkg, - commit: "0123456789", - commitDate: .t0, - docArchives: [.init(name: "target", title: "Target")], - latest: .defaultBranch, - packageName: "bar", - reference: .branch("main")) - .save(on: app.db) - Current.fileManager.fileExists = { @Sendable path in - if path.hasSuffix("Package.resolved") { return false } - return true - } - Current.git = .init( - commitCount: { _ in 2 }, - firstCommitDate: { _ in .t0 }, - lastCommitDate: { _ in .t1 }, - getTags: { _ in [] }, - hasBranch: { _, _ in true }, - revisionInfo: { ref, _ in - if ref == .branch("main") { return .init(commit: "new-commit", date: .t1) } - fatalError("revisionInfo: \(ref)") - }, - shortlog: { _ in "2\tauthor" } - ) - Current.shell.run = { @Sendable cmd, _ in - if cmd.description == "swift package dump-package" { return .mockManifest } - return "" - } - // Make sure the new commit doesn't get throttled try await withDependencies { - $0.date.now = .t1 + Constants.branchVersionRefreshDelay + 1 + $0.environment.awsDocsBucket = { "docs-bucket" } } 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 - XCTAssertEqual(res.status, .ok) - assertSnapshot(of: String(buffer: res.body), as: .html, named: "index") + // setup + let pkg = try await savePackage(on: app.db, "https://github.com/foo/bar".url, processingStage: .ingestion) + try await Repository(package: pkg, defaultBranch: "main", name: "bar", owner: "foo") + .save(on: app.db) + try await Version(package: pkg, + commit: "0123456789", + commitDate: .t0, + docArchives: [.init(name: "target", title: "Target")], + latest: .defaultBranch, + packageName: "bar", + reference: .branch("main")) + .save(on: app.db) + Current.fileManager.fileExists = { @Sendable path in + if path.hasSuffix("Package.resolved") { return false } + return true + } + Current.git = .init( + commitCount: { _ in 2 }, + firstCommitDate: { _ in .t0 }, + lastCommitDate: { _ in .t1 }, + getTags: { _ in [] }, + hasBranch: { _, _ in true }, + revisionInfo: { ref, _ in + if ref == .branch("main") { return .init(commit: "new-commit", date: .t1) } + fatalError("revisionInfo: \(ref)") + }, + shortlog: { _ in "2\tauthor" } + ) + Current.shell.run = { @Sendable cmd, _ in + if cmd.description == "swift package dump-package" { return .mockManifest } + return "" } + // Make sure the new commit doesn't get throttled + 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 + XCTAssertEqual(res.status, .ok) + assertSnapshot(of: String(buffer: res.body), as: .html, named: "index") + } - // Run analyze to detect a new default branch version - try await Analyze.analyze(client: app.client, database: app.db, mode: .limit(1)) + // Run analyze to detect a new default branch version + try await Analyze.analyze(client: app.client, database: app.db, mode: .limit(1)) - // Confirm that analysis has picked up the new version - let commit = try await Version.query(on: app.db).all().map(\.commit) - XCTAssertEqual(commit, ["new-commit"]) + // Confirm that analysis has picked up the new version + let commit = try await Version.query(on: app.db).all().map(\.commit) + XCTAssertEqual(commit, ["new-commit"]) - // Ensure documentation is still being resolved - try await app.test(.GET, "/foo/bar/~/documentation/target") { @MainActor res in - await Task.yield() // essential to avoid deadlocking - XCTAssertEqual(res.status, .ok) - assertSnapshot(of: String(buffer: res.body), as: .html, named: "index") + // Ensure documentation is still being resolved + try await app.test(.GET, "/foo/bar/~/documentation/target") { @MainActor res in + await Task.yield() // essential to avoid deadlocking + XCTAssertEqual(res.status, .ok) + assertSnapshot(of: String(buffer: res.body), as: .html, named: "index") + } } } } diff --git a/Tests/AppTests/RoutesTests.swift b/Tests/AppTests/RoutesTests.swift index ba155ced6..c17228840 100644 --- a/Tests/AppTests/RoutesTests.swift +++ b/Tests/AppTests/RoutesTests.swift @@ -14,41 +14,50 @@ @testable import App +import Dependencies import XCTVapor final class RoutesTests: AppTestCase { func test_documentation_images() async throws { - // 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)) - } + try await withDependencies { + $0.environment.awsDocsBucket = { "docs-bucket" } + } 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 - XCTAssertEqual(res.status, .ok) - XCTAssertEqual(res.content.contentType?.description, "application/octet-stream") - XCTAssertEqual(res.body.asString(), "/foo/bar/1.2.3/images/baz.png") - } - try await app.test(.GET, "foo/bar/1.2.3/images/BAZ.png") { res async in - // validation - XCTAssertEqual(res.status, .ok) - XCTAssertEqual(res.content.contentType?.description, "application/octet-stream") - XCTAssertEqual(res.body.asString(), "/foo/bar/1.2.3/images/BAZ.png") + // MUT + try await app.test(.GET, "foo/bar/1.2.3/images/baz.png") { res async in + // validation + XCTAssertEqual(res.status, .ok) + XCTAssertEqual(res.content.contentType?.description, "application/octet-stream") + XCTAssertEqual(res.body.asString(), "/foo/bar/1.2.3/images/baz.png") + } + try await app.test(.GET, "foo/bar/1.2.3/images/BAZ.png") { res async in + // validation + XCTAssertEqual(res.status, .ok) + XCTAssertEqual(res.content.contentType?.description, "application/octet-stream") + XCTAssertEqual(res.body.asString(), "/foo/bar/1.2.3/images/BAZ.png") + } } } func test_documentation_img() async throws { - // setup - Current.fetchDocumentation = { _, _ in .init(status: .ok) } + try await withDependencies { + $0.environment.awsDocsBucket = { "docs-bucket" } + } 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 - XCTAssertEqual(res.status, .ok) + // MUT + try await app.test(.GET, "foo/bar/1.2.3/img/baz.png") { res async in + // validation + XCTAssertEqual(res.status, .ok) + } } } diff --git a/Tests/AppTests/SitemapTests.swift b/Tests/AppTests/SitemapTests.swift index 1b1903208..3052543f6 100644 --- a/Tests/AppTests/SitemapTests.swift +++ b/Tests/AppTests/SitemapTests.swift @@ -120,83 +120,91 @@ class SitemapTests: SnapshotTestCase { } func test_linkablePathUrls() async throws { - // setup - let package = Package(url: URL(stringLiteral: "https://example.com/owner/repo0")) - try await package.save(on: app.db) - try await Repository(package: package, defaultBranch: "default", - lastCommitDate: Date.now, - name: "Repo0", owner: "Owner").save(on: app.db) - try await Version(package: package, - commit: "123456", - commitDate: .t0, - docArchives: [.init(name: "t1", title: "T1")], - latest: .defaultBranch, - packageName: "SomePackage", - reference: .branch("default"), - spiManifest: .init(builder: .init(configs: [.init(documentationTargets: ["t1", "t2"])]))).save(on: app.db) - 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: """ + try await withDependencies { + $0.environment.awsDocsBucket = { "docs-bucket" } + } operation: { + // setup + let package = Package(url: URL(stringLiteral: "https://example.com/owner/repo0")) + try await package.save(on: app.db) + try await Repository(package: package, defaultBranch: "default", + lastCommitDate: Date.now, + name: "Repo0", owner: "Owner").save(on: app.db) + try await Version(package: package, + commit: "123456", + commitDate: .t0, + docArchives: [.init(name: "t1", title: "T1")], + latest: .defaultBranch, + packageName: "SomePackage", + reference: .branch("default"), + spiManifest: .init(builder: .init(configs: [.init(documentationTargets: ["t1", "t2"])]))).save(on: app.db) + 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) + // MUT + let urls = await PackageController.linkablePathUrls(client: app.client, packageResult: packageResult) - XCTAssertEqual(urls, [ - "https://spi.com/Owner/Repo0/default/documentation/foo/bar/1", - "https://spi.com/Owner/Repo0/default/documentation/foo/bar/2" - ]) + XCTAssertEqual(urls, [ + "https://spi.com/Owner/Repo0/default/documentation/foo/bar/1", + "https://spi.com/Owner/Repo0/default/documentation/foo/bar/2" + ]) + } } func test_linkablePathUrls_reference_pathEncoded() async throws { // Ensure branch names with / are properly "path encoded" // https://github.com/SwiftPackageIndex/SwiftPackageIndex-Server/issues/2462 - // setup - let package = Package(url: URL(stringLiteral: "https://example.com/owner/repo0")) - try await package.save(on: app.db) - try await Repository(package: package, defaultBranch: "a/b", - lastCommitDate: Date.now, - name: "Repo0", owner: "Owner").save(on: app.db) - try await Version(package: package, - commit: "123456", - commitDate: .t0, - docArchives: [.init(name: "t1", title: "T1")], - latest: .defaultBranch, - packageName: "SomePackage", - reference: .branch("a/b"), - spiManifest: .init(builder: .init(configs: [.init(documentationTargets: ["t1", "t2"])]))).save(on: app.db) - 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: """ + try await withDependencies { + $0.environment.awsDocsBucket = { "docs-bucket" } + } operation: { + // setup + let package = Package(url: URL(stringLiteral: "https://example.com/owner/repo0")) + try await package.save(on: app.db) + try await Repository(package: package, defaultBranch: "a/b", + lastCommitDate: Date.now, + name: "Repo0", owner: "Owner").save(on: app.db) + try await Version(package: package, + commit: "123456", + commitDate: .t0, + docArchives: [.init(name: "t1", title: "T1")], + latest: .defaultBranch, + packageName: "SomePackage", + reference: .branch("a/b"), + spiManifest: .init(builder: .init(configs: [.init(documentationTargets: ["t1", "t2"])]))).save(on: app.db) + 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) + + XCTAssertEqual(urls, [ + "https://spi.com/Owner/Repo0/a-b/documentation/foo/bar/1", + "https://spi.com/Owner/Repo0/a-b/documentation/foo/bar/2" + ]) } - - // MUT - let urls = await PackageController.linkablePathUrls(client: app.client, packageResult: packageResult) - - XCTAssertEqual(urls, [ - "https://spi.com/Owner/Repo0/a-b/documentation/foo/bar/1", - "https://spi.com/Owner/Repo0/a-b/documentation/foo/bar/2" - ]) } @MainActor From 86a00c511bb28b91d45416364e5376d4e7e0b9e0 Mon Sep 17 00:00:00 2001 From: "Sven A. Schmidt" Date: Sat, 16 Nov 2024 13:57:47 +0100 Subject: [PATCH 5/5] Change back client --- Sources/App/Core/Dependencies/EnvironmentClient.swift | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/Sources/App/Core/Dependencies/EnvironmentClient.swift b/Sources/App/Core/Dependencies/EnvironmentClient.swift index 9c68aea7b..65b422a70 100644 --- a/Sources/App/Core/Dependencies/EnvironmentClient.swift +++ b/Sources/App/Core/Dependencies/EnvironmentClient.swift @@ -69,15 +69,7 @@ extension EnvironmentClient: DependencyKey { extension EnvironmentClient: TestDependencyKey { - static var testValue: Self { - var testValue = Self() - testValue.current = { .development } - testValue.awsAccessKeyId = { nil } - testValue.awsDocsBucket = { "awsDocsBucket" } - testValue.awsReadmeBucket = { "awsReadmeBucket" } - testValue.awsSecretAccessKey = { nil } - return testValue - } + static var testValue: Self { Self() } }