From db2509c8f044f2174e981099178e7828c213d8f7 Mon Sep 17 00:00:00 2001 From: "Sven A. Schmidt" Date: Wed, 6 Nov 2024 19:26:36 +0100 Subject: [PATCH 1/2] Add EnvironmentClient --- .../Core/Dependencies/EnvironmentClient.swift | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 Sources/App/Core/Dependencies/EnvironmentClient.swift diff --git a/Sources/App/Core/Dependencies/EnvironmentClient.swift b/Sources/App/Core/Dependencies/EnvironmentClient.swift new file mode 100644 index 000000000..410f68c74 --- /dev/null +++ b/Sources/App/Core/Dependencies/EnvironmentClient.swift @@ -0,0 +1,49 @@ +// Copyright Dave Verwer, Sven A. Schmidt, and other contributors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import Dependencies +import DependenciesMacros +import Vapor + + +@DependencyClient +struct EnvironmentClient { + // See https://swiftpackageindex.com/pointfreeco/swift-dependencies/main/documentation/dependenciesmacros/dependencyclient()#Restrictions + // regarding the use of XCTFail here. + var allowBuildTriggers: @Sendable () -> Bool = { XCTFail(#function); return false } +} + + +extension EnvironmentClient: DependencyKey { + static var liveValue: EnvironmentClient { + .init( + allowBuildTriggers: { + Environment.get("ALLOW_BUILD_TRIGGERS").flatMap(\.asBool) ?? Constants.defaultAllowBuildTriggering + } + ) + } +} + + +extension EnvironmentClient: TestDependencyKey { + static var testValue: Self { Self() } +} + + +extension DependencyValues { + var environment: EnvironmentClient { + get { self[EnvironmentClient.self] } + set { self[EnvironmentClient.self] = newValue } + } +} From 408228beb2719c1497298786155f305bfc6462ff Mon Sep 17 00:00:00 2001 From: "Sven A. Schmidt" Date: Wed, 6 Nov 2024 19:47:16 +0100 Subject: [PATCH 2/2] Replace Current.allowBuildTriggers() with @Dependency --- Sources/App/Commands/TriggerBuilds.swift | 4 +- Sources/App/Core/AppEnvironment.swift | 6 - .../Core/Dependencies/EnvironmentClient.swift | 2 +- Tests/AppTests/BuildTriggerTests.swift | 622 +++++++++--------- Tests/AppTests/MetricsTests.swift | 22 +- .../AppTests/Mocks/AppEnvironment+mock.swift | 1 - 6 files changed, 340 insertions(+), 317 deletions(-) diff --git a/Sources/App/Commands/TriggerBuilds.swift b/Sources/App/Commands/TriggerBuilds.swift index 75fb3a640..0b220f40d 100644 --- a/Sources/App/Commands/TriggerBuilds.swift +++ b/Sources/App/Commands/TriggerBuilds.swift @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +import Dependencies import Fluent import PostgresKit import SQLKit @@ -175,7 +176,8 @@ func triggerBuilds(on database: Database, client: Client, packages: [Package.Id], force: Bool = false) async throws { - guard Current.allowBuildTriggers() else { + @Dependency(\.environment) var environment + guard environment.allowBuildTriggers() else { Current.logger().info("Build trigger override switch OFF - no builds are being triggered") return } diff --git a/Sources/App/Core/AppEnvironment.swift b/Sources/App/Core/AppEnvironment.swift index c32530980..5bf80cc83 100644 --- a/Sources/App/Core/AppEnvironment.swift +++ b/Sources/App/Core/AppEnvironment.swift @@ -23,7 +23,6 @@ import FoundationNetworking struct AppEnvironment: Sendable { - var allowBuildTriggers: @Sendable () -> Bool var allowTwitterPosts: @Sendable () -> Bool var apiSigningKey: @Sendable () -> String? var appVersion: @Sendable () -> String? @@ -111,11 +110,6 @@ extension AppEnvironment { nonisolated(unsafe) static var logger: Logger! static let live = AppEnvironment( - allowBuildTriggers: { - Environment.get("ALLOW_BUILD_TRIGGERS") - .flatMap(\.asBool) - ?? Constants.defaultAllowBuildTriggering - }, allowTwitterPosts: { Environment.get("ALLOW_TWITTER_POSTS") .flatMap(\.asBool) diff --git a/Sources/App/Core/Dependencies/EnvironmentClient.swift b/Sources/App/Core/Dependencies/EnvironmentClient.swift index 410f68c74..314a555ba 100644 --- a/Sources/App/Core/Dependencies/EnvironmentClient.swift +++ b/Sources/App/Core/Dependencies/EnvironmentClient.swift @@ -21,7 +21,7 @@ import Vapor struct EnvironmentClient { // See https://swiftpackageindex.com/pointfreeco/swift-dependencies/main/documentation/dependenciesmacros/dependencyclient()#Restrictions // regarding the use of XCTFail here. - var allowBuildTriggers: @Sendable () -> Bool = { XCTFail(#function); return false } + var allowBuildTriggers: @Sendable () -> Bool = { XCTFail(#function); return true } } diff --git a/Tests/AppTests/BuildTriggerTests.swift b/Tests/AppTests/BuildTriggerTests.swift index dc76987c4..9d64609df 100644 --- a/Tests/AppTests/BuildTriggerTests.swift +++ b/Tests/AppTests/BuildTriggerTests.swift @@ -16,8 +16,9 @@ import XCTest @testable import App -import NIOConcurrencyHelpers +import Dependencies import Fluent +import NIOConcurrencyHelpers import SPIManifest import SQLKit import Vapor @@ -535,238 +536,253 @@ class BuildTriggerTests: AppTestCase { } func test_triggerBuilds_checked() async throws { - // Ensure we respect the pipeline limit when triggering builds - // setup - Current.builderToken = { "builder token" } - Current.gitlabPipelineToken = { "pipeline token" } - Current.siteURL = { "http://example.com" } - Current.gitlabPipelineLimit = { 300 } - // Use live dependency but replace actual client with a mock so we can - // assert on the details being sent without actually making a request - Current.triggerBuild = { client, buildId, cloneURL, isDocBuild, platform, ref, swiftVersion, versionID in - try await Gitlab.Builder.triggerBuild(client: client, - buildId: buildId, - cloneURL: cloneURL, - isDocBuild: isDocBuild, - platform: platform, - reference: ref, - swiftVersion: swiftVersion, - versionID: versionID) - } - var triggerCount = 0 - let client = MockClient { _, res in - triggerCount += 1 - try? res.content.encode( - Gitlab.Builder.Response.init(webUrl: "http://web_url") - ) - } + try await withDependencies { + $0.environment.allowBuildTriggers = { true } + } operation: { + // Ensure we respect the pipeline limit when triggering builds + // setup + Current.builderToken = { "builder token" } + Current.gitlabPipelineToken = { "pipeline token" } + Current.siteURL = { "http://example.com" } + Current.gitlabPipelineLimit = { 300 } + // Use live dependency but replace actual client with a mock so we can + // assert on the details being sent without actually making a request + Current.triggerBuild = { client, buildId, cloneURL, isDocBuild, platform, ref, swiftVersion, versionID in + try await Gitlab.Builder.triggerBuild(client: client, + buildId: buildId, + cloneURL: cloneURL, + isDocBuild: isDocBuild, + platform: platform, + reference: ref, + swiftVersion: swiftVersion, + versionID: versionID) + } + var triggerCount = 0 + let client = MockClient { _, res in + triggerCount += 1 + try? res.content.encode( + Gitlab.Builder.Response.init(webUrl: "http://web_url") + ) + } - do { // fist run: we are at capacity and should not be triggering more builds - Current.getStatusCount = { _, _ in 300 } + do { // fist run: we are at capacity and should not be triggering more builds + Current.getStatusCount = { _, _ in 300 } - let pkgId = UUID() - let versionId = UUID() - let p = Package(id: pkgId, url: "1") - try await p.save(on: app.db) - try await Version(id: versionId, package: p, latest: .defaultBranch, reference: .branch("main")) - .save(on: app.db) + let pkgId = UUID() + let versionId = UUID() + let p = Package(id: pkgId, url: "1") + try await p.save(on: app.db) + try await Version(id: versionId, package: p, latest: .defaultBranch, reference: .branch("main")) + .save(on: app.db) - // MUT - try await triggerBuilds(on: app.db, - client: client, - mode: .packageId(pkgId, force: false)) + // MUT + try await triggerBuilds(on: app.db, + client: client, + mode: .packageId(pkgId, force: false)) + + // validate + XCTAssertEqual(triggerCount, 0) + // ensure no build stubs have been created either + let v = try await Version.find(versionId, on: app.db) + try await v?.$builds.load(on: app.db) + XCTAssertEqual(v?.builds.count, 0) + } - // validate - XCTAssertEqual(triggerCount, 0) - // ensure no build stubs have been created either - let v = try await Version.find(versionId, on: app.db) - try await v?.$builds.load(on: app.db) - XCTAssertEqual(v?.builds.count, 0) - } + triggerCount = 0 - triggerCount = 0 + do { // second run: we are just below capacity and allow more builds to be triggered + Current.getStatusCount = { c, _ in 299 } - do { // second run: we are just below capacity and allow more builds to be triggered - Current.getStatusCount = { c, _ in 299 } + let pkgId = UUID() + let versionId = UUID() + let p = Package(id: pkgId, url: "2") + try await p.save(on: app.db) + try await Version(id: versionId, package: p, latest: .defaultBranch, reference: .branch("main")) + .save(on: app.db) - let pkgId = UUID() - let versionId = UUID() - let p = Package(id: pkgId, url: "2") - try await p.save(on: app.db) - try await Version(id: versionId, package: p, latest: .defaultBranch, reference: .branch("main")) - .save(on: app.db) + // MUT + try await triggerBuilds(on: app.db, + client: client, + mode: .packageId(pkgId, force: false)) + + // validate + XCTAssertEqual(triggerCount, 27) + // ensure builds are now in progress + let v = try await Version.find(versionId, on: app.db) + try await v?.$builds.load(on: app.db) + XCTAssertEqual(v?.builds.count, 27) + } - // MUT - try await triggerBuilds(on: app.db, - client: client, - mode: .packageId(pkgId, force: false)) + do { // third run: we are at capacity and using the `force` flag + Current.getStatusCount = { c, _ in 300 } - // validate - XCTAssertEqual(triggerCount, 27) - // ensure builds are now in progress - let v = try await Version.find(versionId, on: app.db) - try await v?.$builds.load(on: app.db) - XCTAssertEqual(v?.builds.count, 27) - } + var triggerCount = 0 + let client = MockClient { _, res in + triggerCount += 1 + try? res.content.encode( + Gitlab.Builder.Response.init(webUrl: "http://web_url") + ) + } - do { // third run: we are at capacity and using the `force` flag - Current.getStatusCount = { c, _ in 300 } + let pkgId = UUID() + let versionId = UUID() + let p = Package(id: pkgId, url: "3") + try await p.save(on: app.db) + try await Version(id: versionId, package: p, latest: .defaultBranch, reference: .branch("main")) + .save(on: app.db) - var triggerCount = 0 + // MUT + try await triggerBuilds(on: app.db, + client: client, + mode: .packageId(pkgId, force: true)) + + // validate + XCTAssertEqual(triggerCount, 27) + // ensure builds are now in progress + let v = try await Version.find(versionId, on: app.db) + try await v?.$builds.load(on: app.db) + XCTAssertEqual(v?.builds.count, 27) + } + } + } + + func test_triggerBuilds_multiplePackages() async throws { + try await withDependencies { + $0.environment.allowBuildTriggers = { true } + } operation: { + // Ensure we respect the pipeline limit when triggering builds for multiple package ids + // setup + Current.builderToken = { "builder token" } + Current.gitlabPipelineToken = { "pipeline token" } + Current.siteURL = { "http://example.com" } + Current.gitlabPipelineLimit = { 300 } + // Use live dependency but replace actual client with a mock so we can + // assert on the details being sent without actually making a request + Current.triggerBuild = { client, buildId, cloneURL, isDocBuild, platform, ref, swiftVersion, versionID in + try await Gitlab.Builder.triggerBuild(client: client, + buildId: buildId, + cloneURL: cloneURL, + isDocBuild: isDocBuild, + platform: platform, + reference: ref, + swiftVersion: swiftVersion, + versionID: versionID) + } + let triggerCount = NIOLockedValueBox(0) let client = MockClient { _, res in - triggerCount += 1 + triggerCount.withLockedValue { $0 += 1 } try? res.content.encode( Gitlab.Builder.Response.init(webUrl: "http://web_url") ) } + Current.getStatusCount = { c, _ in 299 + triggerCount.withLockedValue { $0 } } - let pkgId = UUID() - let versionId = UUID() - let p = Package(id: pkgId, url: "3") - try await p.save(on: app.db) - try await Version(id: versionId, package: p, latest: .defaultBranch, reference: .branch("main")) - .save(on: app.db) + let pkgIds = [UUID(), UUID()] + for id in pkgIds { + let p = Package(id: id, url: id.uuidString.url) + try await p.save(on: app.db) + try await Version(id: UUID(), package: p, latest: .defaultBranch, reference: .branch("main")) + .save(on: app.db) + } // MUT try await triggerBuilds(on: app.db, client: client, - mode: .packageId(pkgId, force: true)) + mode: .limit(4)) - // validate - XCTAssertEqual(triggerCount, 27) - // ensure builds are now in progress - let v = try await Version.find(versionId, on: app.db) - try await v?.$builds.load(on: app.db) - XCTAssertEqual(v?.builds.count, 27) + // validate - only the first batch must be allowed to trigger + XCTAssertEqual(triggerCount.withLockedValue { $0 }, 27) } - } - func test_triggerBuilds_multiplePackages() async throws { - // Ensure we respect the pipeline limit when triggering builds for multiple package ids - // setup - Current.builderToken = { "builder token" } - Current.gitlabPipelineToken = { "pipeline token" } - Current.siteURL = { "http://example.com" } - Current.gitlabPipelineLimit = { 300 } - // Use live dependency but replace actual client with a mock so we can - // assert on the details being sent without actually making a request - Current.triggerBuild = { client, buildId, cloneURL, isDocBuild, platform, ref, swiftVersion, versionID in - try await Gitlab.Builder.triggerBuild(client: client, - buildId: buildId, - cloneURL: cloneURL, - isDocBuild: isDocBuild, - platform: platform, - reference: ref, - swiftVersion: swiftVersion, - versionID: versionID) - } - let triggerCount = NIOLockedValueBox(0) - let client = MockClient { _, res in - triggerCount.withLockedValue { $0 += 1 } - try? res.content.encode( - Gitlab.Builder.Response.init(webUrl: "http://web_url") - ) - } - Current.getStatusCount = { c, _ in 299 + triggerCount.withLockedValue { $0 } } - - let pkgIds = [UUID(), UUID()] - for id in pkgIds { - let p = Package(id: id, url: id.uuidString.url) + func test_triggerBuilds_trimming() async throws { + try await withDependencies { + $0.environment.allowBuildTriggers = { true } + } operation: { + // Ensure we trim builds as part of triggering + // setup + Current.builderToken = { "builder token" } + Current.gitlabPipelineToken = { "pipeline token" } + Current.siteURL = { "http://example.com" } + Current.gitlabPipelineLimit = { 300 } + + let client = MockClient { _, _ in } + + let p = Package(id: .id0, url: "2") try await p.save(on: app.db) - try await Version(id: UUID(), package: p, latest: .defaultBranch, reference: .branch("main")) + let v = try Version(id: .id1, package: p, latest: nil, reference: .branch("main")) + try await v.save(on: app.db) + try await Build(id: .id2, version: v, platform: .iOS, status: .triggered, swiftVersion: .v2) .save(on: app.db) - } - - // MUT - try await triggerBuilds(on: app.db, - client: client, - mode: .limit(4)) - - // validate - only the first batch must be allowed to trigger - XCTAssertEqual(triggerCount.withLockedValue { $0 }, 27) - } - - func test_triggerBuilds_trimming() async throws { - // Ensure we trim builds as part of triggering - // setup - Current.builderToken = { "builder token" } - Current.gitlabPipelineToken = { "pipeline token" } - Current.siteURL = { "http://example.com" } - Current.gitlabPipelineLimit = { 300 } - - let client = MockClient { _, _ in } - - let p = Package(id: .id0, url: "2") - try await p.save(on: app.db) - let v = try Version(id: .id1, package: p, latest: nil, reference: .branch("main")) - try await v.save(on: app.db) - try await Build(id: .id2, version: v, platform: .iOS, status: .triggered, swiftVersion: .v2) - .save(on: app.db) - // shift createdAt back to make build eligible from trimming - try await updateBuildCreatedAt(id: .id2, addTimeInterval: -.hours(5), on: app.db) - let db = app.db - try await XCTAssertEqualAsync(try await Build.query(on: db).count(), 1) + // shift createdAt back to make build eligible from trimming + try await updateBuildCreatedAt(id: .id2, addTimeInterval: -.hours(5), on: app.db) + let db = app.db + try await XCTAssertEqualAsync(try await Build.query(on: db).count(), 1) - // MUT - try await triggerBuilds(on: app.db, - client: client, - mode: .packageId(p.id!, force: false)) + // MUT + try await triggerBuilds(on: app.db, + client: client, + mode: .packageId(p.id!, force: false)) - // validate - let count = try await Build.query(on: app.db).count() - XCTAssertEqual(count, 0) + // validate + let count = try await Build.query(on: app.db).count() + XCTAssertEqual(count, 0) + } } func test_triggerBuilds_error() async throws { - // Ensure we trim builds as part of triggering - // setup - Current.builderToken = { "builder token" } - Current.gitlabPipelineToken = { "pipeline token" } - Current.siteURL = { "http://example.com" } - Current.gitlabPipelineLimit = { 300 } - // Use live dependency but replace actual client with a mock so we can - // assert on the details being sent without actually making a request - Current.triggerBuild = { client, buildId, cloneURL, isDocBuild, platform, ref, swiftVersion, versionID in - try await Gitlab.Builder.triggerBuild(client: client, - buildId: buildId, - cloneURL: cloneURL, - isDocBuild: isDocBuild, - platform: platform, - reference: ref, - swiftVersion: swiftVersion, - versionID: versionID) - } - var triggerCount = 0 - let client = MockClient { _, res in - // let the 5th trigger succeed to ensure we don't early out on errors - if triggerCount == 5 { - try? res.content.encode( - Gitlab.Builder.Response.init(webUrl: "http://web_url") - ) - } else { - struct Response: Content { - var message: String + try await withDependencies { + $0.environment.allowBuildTriggers = { true } + } operation: { + // Ensure we trim builds as part of triggering + // setup + Current.builderToken = { "builder token" } + Current.gitlabPipelineToken = { "pipeline token" } + Current.siteURL = { "http://example.com" } + Current.gitlabPipelineLimit = { 300 } + // Use live dependency but replace actual client with a mock so we can + // assert on the details being sent without actually making a request + Current.triggerBuild = { client, buildId, cloneURL, isDocBuild, platform, ref, swiftVersion, versionID in + try await Gitlab.Builder.triggerBuild(client: client, + buildId: buildId, + cloneURL: cloneURL, + isDocBuild: isDocBuild, + platform: platform, + reference: ref, + swiftVersion: swiftVersion, + versionID: versionID) + } + var triggerCount = 0 + let client = MockClient { _, res in + // let the 5th trigger succeed to ensure we don't early out on errors + if triggerCount == 5 { + try? res.content.encode( + Gitlab.Builder.Response.init(webUrl: "http://web_url") + ) + } else { + struct Response: Content { + var message: String + } + try? res.content.encode(Response(message: "Too many pipelines created in the last minute. Try again later.")) + res.status = .tooManyRequests } - try? res.content.encode(Response(message: "Too many pipelines created in the last minute. Try again later.")) - res.status = .tooManyRequests + triggerCount += 1 } - triggerCount += 1 - } - let p = Package(id: .id0, url: "1") - try await p.save(on: app.db) - let v = try Version(id: .id1, package: p, latest: .defaultBranch, reference: .branch("main")) - try await v.save(on: app.db) + let p = Package(id: .id0, url: "1") + try await p.save(on: app.db) + let v = try Version(id: .id1, package: p, latest: .defaultBranch, reference: .branch("main")) + try await v.save(on: app.db) - // MUT - try await triggerBuilds(on: app.db, - client: client, - mode: .packageId(.id0, force: false)) + // MUT + try await triggerBuilds(on: app.db, + client: client, + mode: .packageId(.id0, force: false)) - // validate that one build record is saved, for the successful trigger - let count = try await Build.query(on: app.db).count() - XCTAssertEqual(count, 1) + // validate that one build record is saved, for the successful trigger + let count = try await Build.query(on: app.db).count() + XCTAssertEqual(count, 1) + } } func test_buildTriggerCandidatesSkipLatestSwiftVersion() throws { @@ -836,10 +852,10 @@ class BuildTriggerTests: AppTestCase { ) } - do { // confirm that the off switch prevents triggers - Current.allowBuildTriggers = { false } - - + try await withDependencies { + // confirm that the off switch prevents triggers + $0.environment.allowBuildTriggers = { false } + } operation: { let pkgId = UUID() let versionId = UUID() let p = Package(id: pkgId, url: "1") @@ -858,9 +874,10 @@ class BuildTriggerTests: AppTestCase { triggerCount = 0 - do { // flipping the switch to on should allow triggers to proceed - Current.allowBuildTriggers = { true } - + try await withDependencies { + // flipping the switch to on should allow triggers to proceed + $0.environment.allowBuildTriggers = { true } + } operation: { let pkgId = UUID() let versionId = UUID() let p = Package(id: pkgId, url: "2") @@ -879,119 +896,126 @@ class BuildTriggerTests: AppTestCase { } func test_downscaling() async throws { - // Test build trigger downscaling behaviour - // setup - Current.builderToken = { "builder token" } - Current.gitlabPipelineToken = { "pipeline token" } - Current.siteURL = { "http://example.com" } - Current.buildTriggerDownscaling = { 0.05 } // 5% downscaling rate - // Use live dependency but replace actual client with a mock so we can - // assert on the details being sent without actually making a request - Current.triggerBuild = { client, buildId, cloneURL, isDocBuild, platform, ref, swiftVersion, versionID in - try await Gitlab.Builder.triggerBuild(client: client, - buildId: buildId, - cloneURL: cloneURL, - isDocBuild: isDocBuild, - platform: platform, - reference: ref, - swiftVersion: swiftVersion, - versionID: versionID) - } - var triggerCount = 0 - let client = MockClient { _, res in - triggerCount += 1 - try? res.content.encode( - Gitlab.Builder.Response.init(webUrl: "http://web_url") - ) - } + try await withDependencies { + $0.environment.allowBuildTriggers = { true } + } operation: { + // Test build trigger downscaling behaviour + // setup + Current.builderToken = { "builder token" } + Current.gitlabPipelineToken = { "pipeline token" } + Current.siteURL = { "http://example.com" } + Current.buildTriggerDownscaling = { 0.05 } // 5% downscaling rate + // Use live dependency but replace actual client with a mock so we can + // assert on the details being sent without actually making a request + Current.triggerBuild = { client, buildId, cloneURL, isDocBuild, platform, ref, swiftVersion, versionID in + try await Gitlab.Builder.triggerBuild(client: client, + buildId: buildId, + cloneURL: cloneURL, + isDocBuild: isDocBuild, + platform: platform, + reference: ref, + swiftVersion: swiftVersion, + versionID: versionID) + } + var triggerCount = 0 + let client = MockClient { _, res in + triggerCount += 1 + try? res.content.encode( + Gitlab.Builder.Response.init(webUrl: "http://web_url") + ) + } - do { // confirm that bad luck prevents triggers - Current.random = { _ in 0.05 } // rolling a 0.05 ... so close! + do { // confirm that bad luck prevents triggers + Current.random = { _ in 0.05 } // rolling a 0.05 ... so close! - let pkgId = UUID() - let versionId = UUID() - let p = Package(id: pkgId, url: "1") - try await p.save(on: app.db) - try await Version(id: versionId, package: p, latest: .defaultBranch, reference: .branch("main")) - .save(on: app.db) + let pkgId = UUID() + let versionId = UUID() + let p = Package(id: pkgId, url: "1") + try await p.save(on: app.db) + try await Version(id: versionId, package: p, latest: .defaultBranch, reference: .branch("main")) + .save(on: app.db) - // MUT - try await triggerBuilds(on: app.db, - client: client, - mode: .packageId(pkgId, force: false)) + // MUT + try await triggerBuilds(on: app.db, + client: client, + mode: .packageId(pkgId, force: false)) - // validate - XCTAssertEqual(triggerCount, 0) - } + // validate + XCTAssertEqual(triggerCount, 0) + } - triggerCount = 0 + triggerCount = 0 - do { // if we get lucky however... - Current.random = { _ in 0.049 } // rolling a 0.049 gets you in + do { // if we get lucky however... + Current.random = { _ in 0.049 } // rolling a 0.049 gets you in - let pkgId = UUID() - let versionId = UUID() - let p = Package(id: pkgId, url: "2") - try await p.save(on: app.db) - try await Version(id: versionId, package: p, latest: .defaultBranch, reference: .branch("main")) - .save(on: app.db) + let pkgId = UUID() + let versionId = UUID() + let p = Package(id: pkgId, url: "2") + try await p.save(on: app.db) + try await Version(id: versionId, package: p, latest: .defaultBranch, reference: .branch("main")) + .save(on: app.db) - // MUT - try await triggerBuilds(on: app.db, - client: client, - mode: .packageId(pkgId, force: false)) + // MUT + try await triggerBuilds(on: app.db, + client: client, + mode: .packageId(pkgId, force: false)) - // validate - XCTAssertEqual(triggerCount, 27) + // validate + XCTAssertEqual(triggerCount, 27) + } } - } func test_downscaling_allow_list_override() async throws { - // Test build trigger downscaling behaviour for allow-listed packages - // setup - Current.builderToken = { "builder token" } - Current.gitlabPipelineToken = { "pipeline token" } - Current.siteURL = { "http://example.com" } - Current.buildTriggerDownscaling = { 0.05 } // 5% downscaling rate - let pkgId = UUID() - Current.buildTriggerAllowList = { [pkgId] } - // Use live dependency but replace actual client with a mock so we can - // assert on the details being sent without actually making a request - Current.triggerBuild = { client, buildId, cloneURL, isDocBuild, platform, ref, swiftVersion, versionID in - try await Gitlab.Builder.triggerBuild(client: client, - buildId: buildId, - cloneURL: cloneURL, - isDocBuild: isDocBuild, - platform: platform, - reference: ref, - swiftVersion: swiftVersion, - versionID: versionID) - } - var triggerCount = 0 - let client = MockClient { _, res in - triggerCount += 1 - try? res.content.encode( - Gitlab.Builder.Response.init(webUrl: "http://web_url") - ) - } + try await withDependencies { + $0.environment.allowBuildTriggers = { true } + } operation: { + // Test build trigger downscaling behaviour for allow-listed packages + // setup + Current.builderToken = { "builder token" } + Current.gitlabPipelineToken = { "pipeline token" } + Current.siteURL = { "http://example.com" } + Current.buildTriggerDownscaling = { 0.05 } // 5% downscaling rate + let pkgId = UUID() + Current.buildTriggerAllowList = { [pkgId] } + // Use live dependency but replace actual client with a mock so we can + // assert on the details being sent without actually making a request + Current.triggerBuild = { client, buildId, cloneURL, isDocBuild, platform, ref, swiftVersion, versionID in + try await Gitlab.Builder.triggerBuild(client: client, + buildId: buildId, + cloneURL: cloneURL, + isDocBuild: isDocBuild, + platform: platform, + reference: ref, + swiftVersion: swiftVersion, + versionID: versionID) + } + var triggerCount = 0 + let client = MockClient { _, res in + triggerCount += 1 + try? res.content.encode( + Gitlab.Builder.Response.init(webUrl: "http://web_url") + ) + } - do { // confirm that we trigger even when rolling above the threshold - Current.random = { _ in 0.051 } + do { // confirm that we trigger even when rolling above the threshold + Current.random = { _ in 0.051 } - let versionId = UUID() - let p = Package(id: pkgId, url: "https://github.com/foo/bar.git") - try await p.save(on: app.db) - try await Version(id: versionId, package: p, latest: .defaultBranch, reference: .branch("main")) - .save(on: app.db) + let versionId = UUID() + let p = Package(id: pkgId, url: "https://github.com/foo/bar.git") + try await p.save(on: app.db) + try await Version(id: versionId, package: p, latest: .defaultBranch, reference: .branch("main")) + .save(on: app.db) - // MUT - try await triggerBuilds(on: app.db, - client: client, - mode: .packageId(pkgId, force: false)) + // MUT + try await triggerBuilds(on: app.db, + client: client, + mode: .packageId(pkgId, force: false)) - // validate - XCTAssertEqual(triggerCount, 27) + // validate + XCTAssertEqual(triggerCount, 27) + } } } diff --git a/Tests/AppTests/MetricsTests.swift b/Tests/AppTests/MetricsTests.swift index 3addc215b..d72c5df71 100644 --- a/Tests/AppTests/MetricsTests.swift +++ b/Tests/AppTests/MetricsTests.swift @@ -135,15 +135,19 @@ class MetricsTests: AppTestCase { } func test_triggerBuildsDurationSeconds() async throws { - // setup - let pkg = try await savePackage(on: app.db, "1") - - // MUT - try await triggerBuilds(on: app.db, client: app.client, mode: .packageId(pkg.id!, force: true)) - - // validation - XCTAssert((AppMetrics.buildTriggerDurationSeconds?.get()) ?? 0 > 0) - print(AppMetrics.buildTriggerDurationSeconds!.get()) + try await withDependencies { + $0.environment.allowBuildTriggers = { true } + } operation: { + // setup + let pkg = try await savePackage(on: app.db, "1") + + // MUT + try await triggerBuilds(on: app.db, client: app.client, mode: .packageId(pkg.id!, force: true)) + + // validation + XCTAssert((AppMetrics.buildTriggerDurationSeconds?.get()) ?? 0 > 0) + print(AppMetrics.buildTriggerDurationSeconds!.get()) + } } } diff --git a/Tests/AppTests/Mocks/AppEnvironment+mock.swift b/Tests/AppTests/Mocks/AppEnvironment+mock.swift index 5d08bc4c4..fc6c41135 100644 --- a/Tests/AppTests/Mocks/AppEnvironment+mock.swift +++ b/Tests/AppTests/Mocks/AppEnvironment+mock.swift @@ -22,7 +22,6 @@ import Vapor extension AppEnvironment { static func mock(eventLoop: EventLoop) -> Self { .init( - allowBuildTriggers: { true }, allowTwitterPosts: { true }, apiSigningKey: { nil }, appVersion: { "test" },