diff --git a/Sources/App/Core/AppEnvironment.swift b/Sources/App/Core/AppEnvironment.swift index cf1b4ff04..d414cd385 100644 --- a/Sources/App/Core/AppEnvironment.swift +++ b/Sources/App/Core/AppEnvironment.swift @@ -23,10 +23,6 @@ import FoundationNetworking struct AppEnvironment: Sendable { - var apiSigningKey: @Sendable () -> String? - var appVersion: @Sendable () -> String? - var collectionSigningCertificateChain: @Sendable () -> [URL] - var collectionSigningPrivateKey: @Sendable () -> Data? var currentReferenceCache: @Sendable () -> CurrentReferenceCache? var dbId: @Sendable () -> String? var fetchDocumentation: @Sendable (_ client: Client, _ url: URI) async throws -> ClientResponse @@ -91,22 +87,6 @@ extension AppEnvironment { nonisolated(unsafe) static var logger: Logger! static let live = AppEnvironment( - apiSigningKey: { Environment.get("API_SIGNING_KEY") }, - appVersion: { App.appVersion }, - collectionSigningCertificateChain: { - [ - SignedCollection.certsDir - .appendingPathComponent("package_collections.cer"), - SignedCollection.certsDir - .appendingPathComponent("AppleWWDRCAG3.cer"), - SignedCollection.certsDir - .appendingPathComponent("AppleIncRootCertificate.cer") - ] - }, - collectionSigningPrivateKey: { - Environment.get("COLLECTION_SIGNING_PRIVATE_KEY") - .map { Data($0.utf8) } - }, currentReferenceCache: { .live }, dbId: { Environment.get("DATABASE_ID") }, fetchDocumentation: { client, url in try await client.get(url) }, diff --git a/Sources/App/Core/Authentication/User.swift b/Sources/App/Core/Authentication/User.swift index d72c58859..bf25235c7 100644 --- a/Sources/App/Core/Authentication/User.swift +++ b/Sources/App/Core/Authentication/User.swift @@ -35,8 +35,10 @@ extension User { struct APITierAuthenticator: AsyncBearerAuthenticator { var tier: Tier + @Dependency(\.environment) var environment + func authenticate(bearer: BearerAuthorization, for request: Request) async throws { - guard let signingKey = Current.apiSigningKey() else { throw AppError.envVariableNotSet("API_SIGNING_KEY") } + guard let signingKey = environment.apiSigningKey() else { throw AppError.envVariableNotSet("API_SIGNING_KEY") } let signer = Signer(secretSigningKey: signingKey) do { let key = try signer.verifyToken(bearer.token) diff --git a/Sources/App/Core/Dependencies/EnvironmentClient.swift b/Sources/App/Core/Dependencies/EnvironmentClient.swift index 9713db6bc..31bb9f9f2 100644 --- a/Sources/App/Core/Dependencies/EnvironmentClient.swift +++ b/Sources/App/Core/Dependencies/EnvironmentClient.swift @@ -21,8 +21,12 @@ import Vapor struct EnvironmentClient { // See https://swiftpackageindex.com/pointfreeco/swift-dependencies/main/documentation/dependenciesmacros/dependencyclient()#Restrictions // regarding the use of XCTFail here. + // Closures returning optionals or Void don't need this, because they automatically get the default failing + // mechanism when they're not set up in a test. var allowBuildTriggers: @Sendable () -> Bool = { XCTFail("allowBuildTriggers"); return true } var allowSocialPosts: @Sendable () -> Bool = { XCTFail("allowSocialPosts"); return true } + var apiSigningKey: @Sendable () -> String? + var appVersion: @Sendable () -> String? var awsAccessKeyId: @Sendable () -> String? var awsDocsBucket: @Sendable () -> String? var awsReadmeBucket: @Sendable () -> String? @@ -32,12 +36,9 @@ struct EnvironmentClient { var buildTriggerAllowList: @Sendable () -> [Package.Id] = { XCTFail("buildTriggerAllowList"); return [] } var buildTriggerDownscaling: @Sendable () -> Double = { XCTFail("buildTriggerDownscaling"); return 1 } var buildTriggerLatestSwiftVersionDownscaling: @Sendable () -> Double = { XCTFail("buildTriggerLatestSwiftVersionDownscaling"); return 1 } - // We're not defaulting current to XCTFail, because its use is too pervasive and would require the vast - // majority of tests to be wrapped with `withDependencies`. - // We can do so at a later time once more tests are transitioned over for other dependencies. This is - // the exact same default behaviour we have with the Current dependency injection: it defaults to - // .development and does not raise an error when not injected. - var current: @Sendable () -> Environment = { .development } + var collectionSigningCertificateChain: @Sendable () -> [URL] = { XCTFail("collectionSigningCertificateChain"); return [] } + var collectionSigningPrivateKey: @Sendable () -> Data? + var current: @Sendable () -> Environment = { XCTFail("current"); return .development } var mastodonCredentials: @Sendable () -> Mastodon.Credentials? var mastodonPost: @Sendable (_ client: Client, _ post: String) async throws -> Void var random: @Sendable (_ range: ClosedRange) -> Double = { XCTFail("random"); return Double.random(in: $0) } @@ -55,6 +56,8 @@ extension EnvironmentClient: DependencyKey { .flatMap(\.asBool) ?? Constants.defaultAllowSocialPosts }, + 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") }, @@ -77,6 +80,16 @@ extension EnvironmentClient: DependencyKey { .flatMap(Double.init) ?? 1.0 }, + collectionSigningCertificateChain: { + [ + "package_collections.cer", + "AppleWWDRCAG3.cer", + "AppleIncRootCertificate.cer", + ].map { SignedCollection.certsDir.appendingPathComponent($0) } + }, + collectionSigningPrivateKey: { + Environment.get("COLLECTION_SIGNING_PRIVATE_KEY").map { Data($0.utf8) } + }, current: { (try? Environment.detect()) ?? .development }, mastodonCredentials: { Environment.get("MASTODON_ACCESS_TOKEN") @@ -102,7 +115,19 @@ extension EnvironmentClient { extension EnvironmentClient: TestDependencyKey { - static var testValue: Self { Self() } + static var testValue: Self { + // sas 2024-11-22: + // For a few attributes we provide a default value overriding the XCTFail, because theis use is too + // pervasive and would require the vast majority of tests to be wrapped with `withDependencies`. + // We can do so at a later time once more tests are transitioned over for other dependencies. This is + // the exact same default behaviour we had with the Current dependency injection. It did not have + // a "fail if not set" mechanism and relied on default values only. We're simply preserving this + // mechanism for a few heavily used dependencies at the moment. + var mock = Self() + mock.appVersion = { "test" } + mock.current = { .development } + return mock + } } diff --git a/Sources/App/Core/PackageCollection+signing.swift b/Sources/App/Core/PackageCollection+signing.swift index d2d476add..329c5d79e 100644 --- a/Sources/App/Core/PackageCollection+signing.swift +++ b/Sources/App/Core/PackageCollection+signing.swift @@ -13,10 +13,11 @@ // limitations under the License. import Basics +import Dependencies import Fluent +import Vapor @preconcurrency import PackageCollectionsModel @preconcurrency import PackageCollectionsSigning -import Vapor typealias SignedCollection = PackageCollectionSigning.Model.SignedCollection @@ -42,12 +43,14 @@ extension SignedCollection { } static func sign(collection: PackageCollection) async throws -> SignedCollection { - guard let privateKey = Current.collectionSigningPrivateKey() else { + @Dependency(\.environment) var environment + + guard let privateKey = environment.collectionSigningPrivateKey() else { throw AppError.envVariableNotSet("COLLECTION_SIGNING_PRIVATE_KEY") } return try await signer.sign(collection: collection, - certChainPaths: Current.collectionSigningCertificateChain(), + certChainPaths: environment.collectionSigningCertificateChain(), privateKeyPEM: privateKey) } diff --git a/Sources/App/Views/PublicPage.swift b/Sources/App/Views/PublicPage.swift index 4e6b49ccb..b69c142ff 100644 --- a/Sources/App/Views/PublicPage.swift +++ b/Sources/App/Views/PublicPage.swift @@ -30,9 +30,10 @@ class PublicPage { /// The page's full HTML document. /// - Returns: A fully formed page inside a element. final func document() -> HTML { - HTML( + @Dependency(\.environment) var environment + return HTML( .lang(.english), - .comment("Version: \(Current.appVersion())"), + .comment("Version: \(environment.appVersion())"), .comment("DB Id: \(Current.dbId())"), head(), body() diff --git a/Sources/App/Views/ResourceReloadIdentifier.swift b/Sources/App/Views/ResourceReloadIdentifier.swift index 2be4c37ef..a6846fb03 100644 --- a/Sources/App/Views/ResourceReloadIdentifier.swift +++ b/Sources/App/Views/ResourceReloadIdentifier.swift @@ -20,9 +20,10 @@ import Vapor struct ResourceReloadIdentifier { static var value: String { + @Dependency(\.environment) var environment // In staging or production appVersion will be set to a commit hash or a tag name. // It will only ever be nil when running in a local development environment. - if let appVersion = Current.appVersion() { + if let appVersion = environment.appVersion() { return appVersion } else { // Return the date of the most recently modified between the JavaScript and CSS resources. diff --git a/Tests/AppTests/ApiTests.swift b/Tests/AppTests/ApiTests.swift index ead2bd307..19d1c29a7 100644 --- a/Tests/AppTests/ApiTests.swift +++ b/Tests/AppTests/ApiTests.swift @@ -30,92 +30,96 @@ class ApiTests: AppTestCase { } func test_search_noQuery() throws { - // setup - Current.apiSigningKey = { "secret" } - - // MUT - try app.test(.GET, "api/search", - headers: .bearerApplicationJSON(try .apiToken(secretKey: "secret", tier: .tier1)), - afterResponse: { res in - XCTAssertEqual(res.status, .ok) - XCTAssertEqual(try res.content.decode(Search.Response.self), - .init(hasMoreResults: false, - searchTerm: "", - searchFilters: [], - results: [])) - }) + try withDependencies { + $0.environment.apiSigningKey = { "secret" } + } operation: { + // MUT + try app.test(.GET, "api/search", + headers: .bearerApplicationJSON(try .apiToken(secretKey: "secret", tier: .tier1)), + afterResponse: { res in + XCTAssertEqual(res.status, .ok) + XCTAssertEqual(try res.content.decode(Search.Response.self), + .init(hasMoreResults: false, + searchTerm: "", + searchFilters: [], + results: [])) + }) + } } func test_search_basic_param() async throws { - // setup - Current.apiSigningKey = { "secret" } - let p1 = Package(id: .id0, url: "1") - try await p1.save(on: app.db) - let p2 = Package(id: .id1, url: "2") - try await p2.save(on: app.db) - try await Repository(package: p1, - defaultBranch: "main", - summary: "some package").save(on: app.db) - try await Repository(package: p2, - defaultBranch: "main", - lastCommitDate: .t0, - name: "name 2", - owner: "owner 2", - stars: 1234, - summary: "foo bar package").save(on: app.db) - try await Version(package: p1, packageName: "Foo", reference: .branch("main")).save(on: app.db) - try await Version(package: p2, packageName: "Bar", reference: .branch("main")).save(on: app.db) - try await Search.refresh(on: app.db) + try await withDependencies { + $0.environment.apiSigningKey = { "secret" } + } operation: { + let p1 = Package(id: .id0, url: "1") + try await p1.save(on: app.db) + let p2 = Package(id: .id1, url: "2") + try await p2.save(on: app.db) + try await Repository(package: p1, + defaultBranch: "main", + summary: "some package").save(on: app.db) + try await Repository(package: p2, + defaultBranch: "main", + lastCommitDate: .t0, + name: "name 2", + owner: "owner 2", + stars: 1234, + summary: "foo bar package").save(on: app.db) + try await Version(package: p1, packageName: "Foo", reference: .branch("main")).save(on: app.db) + try await Version(package: p2, packageName: "Bar", reference: .branch("main")).save(on: app.db) + try await Search.refresh(on: app.db) - let event = App.ActorIsolated(nil) - Current.postPlausibleEvent = { @Sendable _, kind, path, _ in - await event.setValue(.init(kind: kind, path: path)) - } + let event = App.ActorIsolated(nil) + Current.postPlausibleEvent = { @Sendable _, kind, path, _ in + await event.setValue(.init(kind: kind, path: path)) + } - // MUT - try await app.test(.GET, "api/search?query=foo%20bar", - headers: .bearerApplicationJSON(try .apiToken(secretKey: "secret", tier: .tier1)), - afterResponse: { res async in - // validation - XCTAssertEqual(res.status, .ok) - XCTAssertEqual( - try res.content.decode(Search.Response.self), - .init(hasMoreResults: false, - searchTerm: "foo bar", - searchFilters: [], - results: [ - .package( - .init(packageId: .id1, - packageName: "Bar", - packageURL: "/owner%202/name%202", - repositoryName: "name 2", - repositoryOwner: "owner 2", - stars: 1234, - lastActivityAt: .t0, - summary: "foo bar package", - keywords: [], - hasDocs: false)! - ) - ]) - ) - }) + // MUT + try await app.test(.GET, "api/search?query=foo%20bar", + headers: .bearerApplicationJSON(try .apiToken(secretKey: "secret", tier: .tier1)), + afterResponse: { res async in + // validation + XCTAssertEqual(res.status, .ok) + XCTAssertEqual( + try res.content.decode(Search.Response.self), + .init(hasMoreResults: false, + searchTerm: "foo bar", + searchFilters: [], + results: [ + .package( + .init(packageId: .id1, + packageName: "Bar", + packageURL: "/owner%202/name%202", + repositoryName: "name 2", + repositoryOwner: "owner 2", + stars: 1234, + lastActivityAt: .t0, + summary: "foo bar package", + keywords: [], + hasDocs: false)! + ) + ]) + ) + }) - // ensure API event has been reported - await event.withValue { - XCTAssertEqual($0, .some(.init(kind: .pageview, path: .search))) + // ensure API event has been reported + await event.withValue { + XCTAssertEqual($0, .some(.init(kind: .pageview, path: .search))) + } } } func test_search_unauthenticated() async throws { - // setup - Current.apiSigningKey = { "secret" } - - // MUT - try await app.test(.GET, "api/search?query=test", - afterResponse: { res async in - // validation - XCTAssertEqual(res.status, .unauthorized) - }) + try await withDependencies { + $0.environment.apiSigningKey = { "secret" } + } operation: { + // MUT + try await app.test(.GET, "api/search?query=test", + afterResponse: { res async in + // validation + XCTAssertEqual(res.status, .unauthorized) + }) + } } func test_buildReportDecoder() throws { @@ -823,12 +827,14 @@ class ApiTests: AppTestCase { } func test_package_collections_owner() async throws { - try XCTSkipIf(!isRunningInCI && Current.collectionSigningPrivateKey() == nil, "Skip test for local user due to unset COLLECTION_SIGNING_PRIVATE_KEY env variable") + try XCTSkipIf(!isRunningInCI && EnvironmentClient.liveValue.collectionSigningPrivateKey() == nil, "Skip test for local user due to unset COLLECTION_SIGNING_PRIVATE_KEY env variable") try await withDependencies { $0.date.now = .t0 + $0.environment.apiSigningKey = { "secret" } + $0.environment.collectionSigningCertificateChain = EnvironmentClient.liveValue.collectionSigningCertificateChain + $0.environment.collectionSigningPrivateKey = EnvironmentClient.liveValue.collectionSigningPrivateKey } operation: { // setup - Current.apiSigningKey = { "secret" } let p1 = Package(id: .id1, url: "1") try await p1.save(on: app.db) try await Repository(package: p1, @@ -890,13 +896,15 @@ class ApiTests: AppTestCase { } func test_package_collections_packageURLs() async throws { - try XCTSkipIf(!isRunningInCI && Current.collectionSigningPrivateKey() == nil, "Skip test for local user due to unset COLLECTION_SIGNING_PRIVATE_KEY env variable") + try XCTSkipIf(!isRunningInCI && EnvironmentClient.liveValue.collectionSigningPrivateKey() == nil, "Skip test for local user due to unset COLLECTION_SIGNING_PRIVATE_KEY env variable") let refDate = Date(timeIntervalSince1970: 0) try await withDependencies { $0.date.now = refDate + $0.environment.apiSigningKey = { "secret" } + $0.environment.collectionSigningCertificateChain = EnvironmentClient.liveValue.collectionSigningCertificateChain + $0.environment.collectionSigningPrivateKey = EnvironmentClient.liveValue.collectionSigningPrivateKey } operation: { // setup - Current.apiSigningKey = { "secret" } let p1 = Package(id: UUID(uuidString: "442cf59f-0135-4d08-be00-bc9a7cebabd3")!, url: "1") try await p1.save(on: app.db) @@ -968,28 +976,31 @@ class ApiTests: AppTestCase { } func test_package_collections_packageURLs_limit() throws { - Current.apiSigningKey = { "secret" } - let dto = API.PostPackageCollectionDTO( - // request 21 urls - this should raise a 400 - selection: .packageURLs((0...20).map(String.init)) - ) - let body: ByteBuffer = .init(data: try JSONEncoder().encode(dto)) + try withDependencies { + $0.environment.apiSigningKey = { "secret" } + } operation: { + let dto = API.PostPackageCollectionDTO( + // request 21 urls - this should raise a 400 + selection: .packageURLs((0...20).map(String.init)) + ) + let body: ByteBuffer = .init(data: try JSONEncoder().encode(dto)) - try app.test(.POST, - "api/package-collections", - headers: .bearerApplicationJSON((try .apiToken(secretKey: "secret", tier: .tier3))), - body: body, - afterResponse: { res in - // validation - XCTAssertEqual(res.status, .badRequest) - }) + try app.test(.POST, + "api/package-collections", + headers: .bearerApplicationJSON((try .apiToken(secretKey: "secret", tier: .tier3))), + body: body, + afterResponse: { res in + // validation + XCTAssertEqual(res.status, .badRequest) + }) + } } func test_package_collections_unauthorized() throws { - // setup - Current.apiSigningKey = { "secret" } - - do { // MUT - happy path + try withDependencies { + $0.environment.apiSigningKey = { "secret" } + } operation: { + // MUT - happy path let body: ByteBuffer = .init(string: """ { "revision": 3, @@ -1029,92 +1040,95 @@ class ApiTests: AppTestCase { } func test_packages_get() async throws { - // setup - Current.apiSigningKey = { "secret" } - let owner = "owner" - let repo = "repo" - let p = try await savePackage(on: app.db, "1") - let v = try Version(package: p, latest: .defaultBranch, reference: .branch("main")) - try await v.save(on: app.db) - try await Repository(package: p, - defaultBranch: "main", - license: .mit, - name: repo, - owner: owner).save(on: app.db) + try await withDependencies { + $0.environment.apiSigningKey = { "secret" } + } operation: { + let owner = "owner" + let repo = "repo" + let p = try await savePackage(on: app.db, "1") + let v = try Version(package: p, latest: .defaultBranch, reference: .branch("main")) + try await v.save(on: app.db) + try await Repository(package: p, + defaultBranch: "main", + license: .mit, + name: repo, + owner: owner).save(on: app.db) - do { // MUT - happy path - try await app.test(.GET, "api/packages/owner/repo", - headers: .bearerApplicationJSON(try .apiToken(secretKey: "secret", tier: .tier3)), - afterResponse: { res async throws in - // validation - XCTAssertEqual(res.status, .ok) - let model = try res.content.decode(API.PackageController.GetRoute.Model.self) - XCTAssertEqual(model.repositoryOwner, "owner") - }) - } + do { // MUT - happy path + try await app.test(.GET, "api/packages/owner/repo", + headers: .bearerApplicationJSON(try .apiToken(secretKey: "secret", tier: .tier3)), + afterResponse: { res async throws in + // validation + XCTAssertEqual(res.status, .ok) + let model = try res.content.decode(API.PackageController.GetRoute.Model.self) + XCTAssertEqual(model.repositoryOwner, "owner") + }) + } - do { // MUT - unauthorized (no token provided) - try await app.test(.GET, "api/packages/owner/repo", - headers: .applicationJSON, - afterResponse: { res async in - // validation - XCTAssertEqual(res.status, .unauthorized) - }) - } + do { // MUT - unauthorized (no token provided) + try await app.test(.GET, "api/packages/owner/repo", + headers: .applicationJSON, + afterResponse: { res async in + // validation + XCTAssertEqual(res.status, .unauthorized) + }) + } - do { // MUT - unauthorized (wrong token provided) - try await app.test(.GET, "api/packages/owner/repo", - headers: .bearerApplicationJSON("bad token"), - afterResponse: { res async in - // validation - XCTAssertEqual(res.status, .unauthorized) - }) - } + do { // MUT - unauthorized (wrong token provided) + try await app.test(.GET, "api/packages/owner/repo", + headers: .bearerApplicationJSON("bad token"), + afterResponse: { res async in + // validation + XCTAssertEqual(res.status, .unauthorized) + }) + } - do { // MUT - unauthorized (signed with wrong key) - try await app.test(.GET, "api/packages/unknown/package", - headers: .bearerApplicationJSON((try .apiToken(secretKey: "wrong", tier: .tier3))), - afterResponse: { res async in - // validation - XCTAssertEqual(res.status, .unauthorized) - }) - } - do { // MUT - package not found - try await app.test(.GET, "api/packages/unknown/package", - headers: .bearerApplicationJSON((try .apiToken(secretKey: "secret", tier: .tier3))), - afterResponse: { res async in - // validation - XCTAssertEqual(res.status, .notFound) - }) + do { // MUT - unauthorized (signed with wrong key) + try await app.test(.GET, "api/packages/unknown/package", + headers: .bearerApplicationJSON((try .apiToken(secretKey: "wrong", tier: .tier3))), + afterResponse: { res async in + // validation + XCTAssertEqual(res.status, .unauthorized) + }) + } + do { // MUT - package not found + try await app.test(.GET, "api/packages/unknown/package", + headers: .bearerApplicationJSON((try .apiToken(secretKey: "secret", tier: .tier3))), + afterResponse: { res async in + // validation + XCTAssertEqual(res.status, .notFound) + }) + } } } func test_dependencies_get() async throws { - // setup - Current.apiSigningKey = { "secret" } - let pkg = try await savePackage(on: app.db, id: .id0, "http://github.com/foo/bar") - try await Repository(package: pkg, - defaultBranch: "default", - name: "bar", - owner: "foo").save(on: app.db) - try await Version(package: pkg, - commitDate: .t0, - latest: .defaultBranch, - reference: .branch("default"), - resolvedDependencies: []) + try await withDependencies { + $0.environment.apiSigningKey = { "secret" } + } operation: { + let pkg = try await savePackage(on: app.db, id: .id0, "http://github.com/foo/bar") + try await Repository(package: pkg, + defaultBranch: "default", + name: "bar", + owner: "foo").save(on: app.db) + try await Version(package: pkg, + commitDate: .t0, + latest: .defaultBranch, + reference: .branch("default"), + resolvedDependencies: []) .save(on: app.db) - - // MUT - try await app.test(.GET, "api/dependencies", - headers: .bearerApplicationJSON((try .apiToken(secretKey: "secret", tier: .tier3))), - afterResponse: { res async in - // validation - XCTAssertEqual(res.status, .ok) - XCTAssertGreaterThan(res.body.asString().count, 0) - }) + + // MUT + try await app.test(.GET, "api/dependencies", + headers: .bearerApplicationJSON((try .apiToken(secretKey: "secret", tier: .tier3))), + afterResponse: { res async in + // validation + XCTAssertEqual(res.status, .ok) + XCTAssertGreaterThan(res.body.asString().count, 0) + }) + } } - } diff --git a/Tests/AppTests/Mocks/AppEnvironment+mock.swift b/Tests/AppTests/Mocks/AppEnvironment+mock.swift index d6b6d2818..7a745d061 100644 --- a/Tests/AppTests/Mocks/AppEnvironment+mock.swift +++ b/Tests/AppTests/Mocks/AppEnvironment+mock.swift @@ -22,10 +22,6 @@ import Vapor extension AppEnvironment { static func mock(eventLoop: EventLoop) -> Self { .init( - apiSigningKey: { nil }, - appVersion: { "test" }, - collectionSigningCertificateChain: AppEnvironment.live.collectionSigningCertificateChain, - collectionSigningPrivateKey: AppEnvironment.live.collectionSigningPrivateKey, currentReferenceCache: { nil }, dbId: { "db-id" }, fetchDocumentation: { _, _ in .init(status: .ok) }, diff --git a/Tests/AppTests/PackageCollectionControllerTests.swift b/Tests/AppTests/PackageCollectionControllerTests.swift index 664664938..d1544c726 100644 --- a/Tests/AppTests/PackageCollectionControllerTests.swift +++ b/Tests/AppTests/PackageCollectionControllerTests.swift @@ -22,9 +22,11 @@ import XCTVapor class PackageCollectionControllerTests: AppTestCase { func test_owner_request() async throws { - try XCTSkipIf(!isRunningInCI && Current.collectionSigningPrivateKey() == nil, "Skip test for local user due to unset COLLECTION_SIGNING_PRIVATE_KEY env variable") + try XCTSkipIf(!isRunningInCI && EnvironmentClient.liveValue.collectionSigningPrivateKey() == nil, "Skip test for local user due to unset COLLECTION_SIGNING_PRIVATE_KEY env variable") try await withDependencies { $0.date.now = .t0 + $0.environment.collectionSigningCertificateChain = EnvironmentClient.liveValue.collectionSigningCertificateChain + $0.environment.collectionSigningPrivateKey = EnvironmentClient.liveValue.collectionSigningPrivateKey } operation: { let p = try await savePackage(on: app.db, "https://github.com/foo/1") do { @@ -75,9 +77,11 @@ class PackageCollectionControllerTests: AppTestCase { } func test_custom_request() async throws { - try XCTSkipIf(!isRunningInCI && Current.collectionSigningPrivateKey() == nil, "Skip test for local user due to unset COLLECTION_SIGNING_PRIVATE_KEY env variable") + try XCTSkipIf(!isRunningInCI && EnvironmentClient.liveValue.collectionSigningPrivateKey() == nil, "Skip test for local user due to unset COLLECTION_SIGNING_PRIVATE_KEY env variable") try await withDependencies { $0.date.now = .t0 + $0.environment.collectionSigningCertificateChain = EnvironmentClient.liveValue.collectionSigningCertificateChain + $0.environment.collectionSigningPrivateKey = EnvironmentClient.liveValue.collectionSigningPrivateKey } operation: { let p = try await savePackage(on: app.db, "https://github.com/foo/1") do { diff --git a/Tests/AppTests/PackageCollectionTests.swift b/Tests/AppTests/PackageCollectionTests.swift index 012852849..ab8e1ebf3 100644 --- a/Tests/AppTests/PackageCollectionTests.swift +++ b/Tests/AppTests/PackageCollectionTests.swift @@ -830,27 +830,32 @@ class PackageCollectionTests: AppTestCase { } func test_sign_collection() async throws { - try XCTSkipIf(!isRunningInCI && Current.collectionSigningPrivateKey() == nil, "Skip test for local user due to unset COLLECTION_SIGNING_PRIVATE_KEY env variable") + try XCTSkipIf(!isRunningInCI && EnvironmentClient.liveValue.collectionSigningPrivateKey() == nil, "Skip test for local user due to unset COLLECTION_SIGNING_PRIVATE_KEY env variable") - // setup - let collection: PackageCollection = .mock + try await withDependencies { + $0.environment.collectionSigningCertificateChain = EnvironmentClient.liveValue.collectionSigningCertificateChain + $0.environment.collectionSigningPrivateKey = EnvironmentClient.liveValue.collectionSigningPrivateKey + } operation: { + // setup + let collection: PackageCollection = .mock - // MUT - let signedCollection = try await SignedCollection.sign(collection: collection) + // MUT + let signedCollection = try await SignedCollection.sign(collection: collection) - // validate signed collection content - XCTAssertFalse(signedCollection.signature.signature.isEmpty) + // validate signed collection content + XCTAssertFalse(signedCollection.signature.signature.isEmpty) #if compiler(<6) - await MainActor.run { - assertSnapshot(of: signedCollection, as: .json(encoder)) - } + await MainActor.run { + assertSnapshot(of: signedCollection, as: .json(encoder)) + } #else - assertSnapshot(of: signedCollection, as: .json(encoder)) + assertSnapshot(of: signedCollection, as: .json(encoder)) #endif - // validate signature - let validated = try await SignedCollection.validate(signedCollection: signedCollection) - XCTAssertTrue(validated) + // validate signature + let validated = try await SignedCollection.validate(signedCollection: signedCollection) + XCTAssertTrue(validated) + } } func test_sign_collection_revoked_key() async throws { @@ -866,29 +871,29 @@ class PackageCollectionTests: AppTestCase { XCTAssertTrue(Foundation.FileManager.default.fileExists(atPath: revokedUrl.path)) let revokedKey = try XCTUnwrap(fixtureData(for: "revoked.pem")) - Current.collectionSigningCertificateChain = { - [ - revokedUrl, - SignedCollection.certsDir - .appendingPathComponent("AppleWWDRCAG3.cer"), - SignedCollection.certsDir - .appendingPathComponent("AppleIncRootCertificate.cer") - ] - } - Current.collectionSigningPrivateKey = { revokedKey } - - // MUT - do { - let signedCollection = try await SignedCollection.sign(collection: collection) - // NB: signing _can_ succeed in case of reachability issues to verify the cert - // in this case we need to check the signature - // https://github.com/SwiftPackageIndex/SwiftPackageIndex-Server/issues/1583#issuecomment-1048408400 - let validated = try await SignedCollection.validate(signedCollection: signedCollection) - XCTAssertFalse(validated) - } catch PackageCollectionSigningError.invalidCertChain { - // ok - } catch { - XCTFail("unexpected signing error: \(error)") + await withDependencies { + $0.environment.collectionSigningCertificateChain = { + [ + revokedUrl, + SignedCollection.certsDir.appendingPathComponent("AppleWWDRCAG3.cer"), + SignedCollection.certsDir.appendingPathComponent("AppleIncRootCertificate.cer") + ] + } + $0.environment.collectionSigningPrivateKey = { revokedKey } + } operation: { + do { + // MUT + let signedCollection = try await SignedCollection.sign(collection: collection) + // NB: signing _can_ succeed in case of reachability issues to verify the cert + // in this case we need to check the signature + // https://github.com/SwiftPackageIndex/SwiftPackageIndex-Server/issues/1583#issuecomment-1048408400 + let validated = try await SignedCollection.validate(signedCollection: signedCollection) + XCTAssertFalse(validated) + } catch PackageCollectionSigningError.invalidCertChain { + // ok + } catch { + XCTFail("unexpected signing error: \(error)") + } } } diff --git a/Tests/AppTests/ResourceReloadIdentifierTests.swift b/Tests/AppTests/ResourceReloadIdentifierTests.swift index fd791351b..0e05f6879 100644 --- a/Tests/AppTests/ResourceReloadIdentifierTests.swift +++ b/Tests/AppTests/ResourceReloadIdentifierTests.swift @@ -12,15 +12,20 @@ // See the License for the specific language governing permissions and // limitations under the License. +import XCTest + @testable import App -import XCTest +import Dependencies import Vapor + class ResourceReloadIdentifierTests: AppTestCase { func test_withAppVersion() throws { - Current.appVersion = { "1.2.3" } - - XCTAssertEqual(ResourceReloadIdentifier.value, "1.2.3") + withDependencies { + $0.environment.appVersion = { "1.2.3" } + } operation: { + XCTAssertEqual(ResourceReloadIdentifier.value, "1.2.3") + } } }