diff --git a/Sources/App/Core/AppEnvironment.swift b/Sources/App/Core/AppEnvironment.swift index 1ce20cf0c..58941cd11 100644 --- a/Sources/App/Core/AppEnvironment.swift +++ b/Sources/App/Core/AppEnvironment.swift @@ -23,7 +23,6 @@ import FoundationNetworking struct AppEnvironment: Sendable { - var allowTwitterPosts: @Sendable () -> Bool var apiSigningKey: @Sendable () -> String? var appVersion: @Sendable () -> String? var awsAccessKeyId: @Sendable () -> String? @@ -57,8 +56,6 @@ struct AppEnvironment: Sendable { var httpClient: @Sendable () -> Client var loadSPIManifest: @Sendable (String) -> SPIManifest.Manifest? var logger: @Sendable () -> Logger - var mastodonCredentials: @Sendable () -> Mastodon.Credentials? - var mastodonPost: @Sendable (_ client: Client, _ post: String) async throws -> Void var metricsPushGatewayUrl: @Sendable () -> String? var plausibleBackendReportingSiteID: @Sendable () -> String? var postPlausibleEvent: @Sendable (Client, Plausible.Event.Kind, Plausible.Path, User?) async throws -> Void @@ -109,11 +106,6 @@ extension AppEnvironment { nonisolated(unsafe) static var logger: Logger! static let live = AppEnvironment( - allowTwitterPosts: { - Environment.get("ALLOW_TWITTER_POSTS") - .flatMap(\.asBool) - ?? Constants.defaultAllowTwitterPosts - }, apiSigningKey: { Environment.get("API_SIGNING_KEY") }, appVersion: { App.appVersion }, awsAccessKeyId: { Environment.get("AWS_ACCESS_KEY_ID") }, @@ -186,11 +178,6 @@ extension AppEnvironment { httpClient: { httpClient }, loadSPIManifest: { path in SPIManifest.Manifest.load(in: path) }, logger: { logger }, - mastodonCredentials: { - Environment.get("MASTODON_ACCESS_TOKEN") - .map(Mastodon.Credentials.init(accessToken:)) - }, - mastodonPost: { client, message in try await Mastodon.post(client: client, message: message) }, metricsPushGatewayUrl: { Environment.get("METRICS_PUSHGATEWAY_URL") }, plausibleBackendReportingSiteID: { Environment.get("PLAUSIBLE_BACKEND_REPORTING_SITE_ID") }, postPlausibleEvent: { client, kind, path, user in try await Plausible.postEvent(client: client, kind: kind, path: path, user: user) }, diff --git a/Sources/App/Core/Constants.swift b/Sources/App/Core/Constants.swift index 27bf1af3b..d4cbdd2c8 100644 --- a/Sources/App/Core/Constants.swift +++ b/Sources/App/Core/Constants.swift @@ -17,7 +17,7 @@ import Vapor enum Constants { static let defaultAllowBuildTriggering = true - static let defaultAllowTwitterPosts = true + static let defaultAllowSocialPosts = true static let defaultGitlabPipelineLimit = 200 static let defaultHideStagingBanner = false diff --git a/Sources/App/Core/Dependencies/EnvironmentClient.swift b/Sources/App/Core/Dependencies/EnvironmentClient.swift index e16e26609..24ac005b0 100644 --- a/Sources/App/Core/Dependencies/EnvironmentClient.swift +++ b/Sources/App/Core/Dependencies/EnvironmentClient.swift @@ -22,12 +22,15 @@ 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 true } + var allowSocialPosts: @Sendable () -> Bool = { XCTFail(#function); return true } // 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 mastodonCredentials: @Sendable () -> Mastodon.Credentials? + var mastodonPost: @Sendable (_ client: Client, _ post: String) async throws -> Void } @@ -37,7 +40,17 @@ extension EnvironmentClient: DependencyKey { allowBuildTriggers: { Environment.get("ALLOW_BUILD_TRIGGERS").flatMap(\.asBool) ?? Constants.defaultAllowBuildTriggering }, - current: { (try? Environment.detect()) ?? .development } + allowSocialPosts: { + Environment.get("ALLOW_SOCIAL_POSTS") + .flatMap(\.asBool) + ?? Constants.defaultAllowSocialPosts + }, + current: { (try? Environment.detect()) ?? .development }, + mastodonCredentials: { + Environment.get("MASTODON_ACCESS_TOKEN") + .map(Mastodon.Credentials.init(accessToken:)) + }, + mastodonPost: { client, message in try await Mastodon.post(client: client, message: message) } ) } } diff --git a/Sources/App/Core/Mastodon.swift b/Sources/App/Core/Mastodon.swift index a51d70ba3..8a42bdb3d 100644 --- a/Sources/App/Core/Mastodon.swift +++ b/Sources/App/Core/Mastodon.swift @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +import Dependencies import Vapor @@ -27,7 +28,8 @@ enum Mastodon { // NB: _testEncodedURL is a callback that exists purely to be able to regression test the encoded value static func post(client: Client, message: String, _testEncodedURL: (String) -> Void = { _ in }) async throws { - guard let credentials = Current.mastodonCredentials() else { + @Dependency(\.environment) var environment + guard let credentials = environment.mastodonCredentials() else { throw Social.Error.missingCredentials } diff --git a/Sources/App/Core/Social.swift b/Sources/App/Core/Social.swift index d4be8d024..cf722e9f3 100644 --- a/Sources/App/Core/Social.swift +++ b/Sources/App/Core/Social.swift @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +import Dependencies import SemanticVersion import Vapor @@ -96,16 +97,15 @@ enum Social { static func postToFirehose(client: Client, package: Joined, version: Version) async throws { - guard Current.allowTwitterPosts() else { - throw Error.postingDisabled - } + @Dependency(\.environment) var environment + guard environment.allowSocialPosts() else { throw Error.postingDisabled } guard let message = firehoseMessage(package: package, version: version, maxLength: postMaxLength) else { throw Error.invalidMessage } // Ignore errors from here for now to keep concurrency simpler - async let _ = try? await Current.mastodonPost(client, message) + async let _ = try? await environment.mastodonPost(client, message) } static func postToFirehose(client: Client, diff --git a/Tests/AppTests/AnalyzeErrorTests.swift b/Tests/AppTests/AnalyzeErrorTests.swift index 8ee2a775f..8048d2c7a 100644 --- a/Tests/AppTests/AnalyzeErrorTests.swift +++ b/Tests/AppTests/AnalyzeErrorTests.swift @@ -98,125 +98,117 @@ final class AnalyzeErrorTests: AppTestCase { } Current.shell.run = Self.defaultShellRun + } - Current.mastodonPost = { [socialPosts = self.socialPosts] _, message in - socialPosts.withValue { $0.append(message) } + override func invokeTest() { + withDependencies { + $0.date.now = .t0 + $0.environment.allowSocialPosts = { true } + $0.environment.mastodonPost = { @Sendable [socialPosts = self.socialPosts] _, message in + socialPosts.withValue { $0.append(message) } + } + } operation: { + super.invokeTest() } } func test_analyze_refreshCheckout_failed() async throws { - try await withDependencies { - $0.date.now = .t0 - } operation: { - Current.shell.run = { @Sendable cmd, path in - switch cmd { - case _ where cmd.description.contains("git clone https://github.com/foo/1"): - throw SimulatedError() - - case .gitFetchAndPruneTags where path.hasSuffix("foo-1"): - throw SimulatedError() - - default: - return try Self.defaultShellRun(cmd, path) - } - } - - // MUT - try await Analyze.analyze(client: app.client, - database: app.db, - mode: .limit(10)) - - // validate - try await defaultValidation() - try logger.logs.withValue { logs in - XCTAssertEqual(logs.count, 2) - let error = try logs.last.unwrap() - XCTAssertTrue(error.message.contains("refreshCheckout failed"), "was: \(error.message)") + Current.shell.run = { @Sendable cmd, path in + switch cmd { + case _ where cmd.description.contains("git clone https://github.com/foo/1"): + throw SimulatedError() + + case .gitFetchAndPruneTags where path.hasSuffix("foo-1"): + throw SimulatedError() + + default: + return try Self.defaultShellRun(cmd, path) } } + + // MUT + try await Analyze.analyze(client: app.client, + database: app.db, + mode: .limit(10)) + + // validate + try await defaultValidation() + try logger.logs.withValue { logs in + XCTAssertEqual(logs.count, 2) + let error = try logs.last.unwrap() + XCTAssertTrue(error.message.contains("refreshCheckout failed"), "was: \(error.message)") + } } func test_analyze_updateRepository_invalidPackageCachePath() async throws { - try await withDependencies { - $0.date.now = .t0 - } operation: { - // setup - let pkg = try await Package.find(badPackageID, on: app.db).unwrap() - // This may look weird but its currently the only way to actually create an - // invalid package cache path - we need to mess up the package url. - pkg.url = "foo/1" - XCTAssertNil(pkg.cacheDirectoryName) - try await pkg.save(on: app.db) - - // MUT - try await Analyze.analyze(client: app.client, - database: app.db, - mode: .limit(10)) - - // validate - try await defaultValidation() - try logger.logs.withValue { logs in - XCTAssertEqual(logs.count, 2) - let error = try logs.last.unwrap() - XCTAssertTrue(error.message.contains( "AppError.invalidPackageCachePath"), "was: \(error.message)") - } + // setup + let pkg = try await Package.find(badPackageID, on: app.db).unwrap() + // This may look weird but its currently the only way to actually create an + // invalid package cache path - we need to mess up the package url. + pkg.url = "foo/1" + XCTAssertNil(pkg.cacheDirectoryName) + try await pkg.save(on: app.db) + + // MUT + try await Analyze.analyze(client: app.client, + database: app.db, + mode: .limit(10)) + + // validate + try await defaultValidation() + try logger.logs.withValue { logs in + XCTAssertEqual(logs.count, 2) + let error = try logs.last.unwrap() + XCTAssertTrue(error.message.contains( "AppError.invalidPackageCachePath"), "was: \(error.message)") } } func test_analyze_getPackageInfo_gitCheckout_error() async throws { - try await withDependencies { - $0.date.now = .t0 - } operation: { - // setup - Current.shell.run = { @Sendable cmd, path in - switch cmd { - case .gitCheckout(branch: "main", quiet: true) where path.hasSuffix("foo-1"): - throw SimulatedError() - - default: - return try Self.defaultShellRun(cmd, path) - } + // setup + Current.shell.run = { @Sendable cmd, path in + switch cmd { + case .gitCheckout(branch: "main", quiet: true) where path.hasSuffix("foo-1"): + throw SimulatedError() + + default: + return try Self.defaultShellRun(cmd, path) } + } - // MUT - try await Analyze.analyze(client: app.client, - database: app.db, - mode: .limit(10)) - - // validate - try await defaultValidation() - try logger.logs.withValue { logs in - XCTAssertEqual(logs.count, 2) - let error = try logs.last.unwrap() - XCTAssertTrue(error.message.contains("AppError.noValidVersions"), "was: \(error.message)") - } + // MUT + try await Analyze.analyze(client: app.client, + database: app.db, + mode: .limit(10)) + + // validate + try await defaultValidation() + try logger.logs.withValue { logs in + XCTAssertEqual(logs.count, 2) + let error = try logs.last.unwrap() + XCTAssertTrue(error.message.contains("AppError.noValidVersions"), "was: \(error.message)") } } func test_analyze_dumpPackage_missing_manifest() async throws { - try await withDependencies { - $0.date.now = .t0 - } operation: { - // setup - Current.fileManager.fileExists = { @Sendable path in - if path.hasSuffix("github.com-foo-1/Package.swift") { - return false - } - return true - } - - // MUT - try await Analyze.analyze(client: app.client, - database: app.db, - mode: .limit(10)) - - // validate - try await defaultValidation() - try logger.logs.withValue { logs in - XCTAssertEqual(logs.count, 2) - let error = try logs.last.unwrap() - XCTAssertTrue(error.message.contains("AppError.noValidVersions"), "was: \(error.message)") + // setup + Current.fileManager.fileExists = { @Sendable path in + if path.hasSuffix("github.com-foo-1/Package.swift") { + return false } + return true + } + + // MUT + try await Analyze.analyze(client: app.client, + database: app.db, + mode: .limit(10)) + + // validate + try await defaultValidation() + try logger.logs.withValue { logs in + XCTAssertEqual(logs.count, 2) + let error = try logs.last.unwrap() + XCTAssertTrue(error.message.contains("AppError.noValidVersions"), "was: \(error.message)") } } diff --git a/Tests/AppTests/AnalyzerTests.swift b/Tests/AppTests/AnalyzerTests.swift index 60f75f0cb..6d2ced970 100644 --- a/Tests/AppTests/AnalyzerTests.swift +++ b/Tests/AppTests/AnalyzerTests.swift @@ -35,6 +35,8 @@ class AnalyzerTests: AppTestCase { // expected shell commands for the happy path.) try await withDependencies { $0.date.now = .now + $0.environment.allowSocialPosts = { true } + $0.environment.mastodonPost = { @Sendable _, _ in } } operation: { // setup let urls = ["https://github.com/foo/1", "https://github.com/foo/2"] @@ -216,6 +218,8 @@ class AnalyzerTests: AppTestCase { // changing as well as a tag being moved to a different commit. try await withDependencies { $0.date.now = .now + $0.environment.allowSocialPosts = { true } + $0.environment.mastodonPost = { @Sendable _, _ in } } operation: { // setup let pkgId = UUID() @@ -354,6 +358,7 @@ class AnalyzerTests: AppTestCase { // Ensure packages record success/error status try await withDependencies { $0.date.now = .now + $0.environment.allowSocialPosts = { true } } operation: { // setup let urls = ["https://github.com/foo/1", "https://github.com/foo/2"] @@ -404,6 +409,7 @@ class AnalyzerTests: AppTestCase { // Test to ensure exceptions don't interrupt processing try await withDependencies { $0.date.now = .now + $0.environment.allowSocialPosts = { true } } operation: { // setup let urls = ["https://github.com/foo/1", "https://github.com/foo/2"] @@ -881,6 +887,7 @@ class AnalyzerTests: AppTestCase { // https://github.com/SwiftPackageIndex/SwiftPackageIndex-Server/issues/29 try await withDependencies { $0.date.now = .now + $0.environment.allowSocialPosts = { true } } operation: { // setup Current.git.commitCount = { @Sendable _ in 12 } diff --git a/Tests/AppTests/MastodonTests.swift b/Tests/AppTests/MastodonTests.swift index a161885e0..efe931557 100644 --- a/Tests/AppTests/MastodonTests.swift +++ b/Tests/AppTests/MastodonTests.swift @@ -22,96 +22,99 @@ import XCTVapor final class MastodonTests: AppTestCase { func test_endToEnd() async throws { - // setup let message = QueueIsolated(nil) - Current.mastodonPost = { _, msg in - if message.value == nil { - message.setValue(msg) - } else { - XCTFail("message must only be set once") + try await withDependencies { + $0.environment.allowSocialPosts = { true } + $0.environment.mastodonPost = { @Sendable _, msg in + if message.value == nil { + message.setValue(msg) + } else { + XCTFail("message must only be set once") + } } - } - - let url = "https://github.com/foo/bar" - Current.fetchMetadata = { _, owner, repository in .mock(owner: owner, repository: repository) } - - Current.git.commitCount = { @Sendable _ in 12 } - Current.git.firstCommitDate = { @Sendable _ in .t0 } - Current.git.lastCommitDate = { @Sendable _ in .t2 } - Current.git.getTags = { @Sendable _ in [Reference.tag(1, 2, 3)] } - Current.git.hasBranch = { @Sendable _, _ in true } - Current.git.revisionInfo = { @Sendable _, _ in .init(commit: "sha", date: .t0) } - Current.git.shortlog = { @Sendable _ in + } operation: { + // setup + let url = "https://github.com/foo/bar" + Current.fetchMetadata = { _, owner, repository in .mock(owner: owner, repository: repository) } + + Current.git.commitCount = { @Sendable _ in 12 } + Current.git.firstCommitDate = { @Sendable _ in .t0 } + Current.git.lastCommitDate = { @Sendable _ in .t2 } + Current.git.getTags = { @Sendable _ in [Reference.tag(1, 2, 3)] } + Current.git.hasBranch = { @Sendable _, _ in true } + Current.git.revisionInfo = { @Sendable _, _ in .init(commit: "sha", date: .t0) } + Current.git.shortlog = { @Sendable _ in """ 10\tPerson 1 2\tPerson 2 """ - } - - Current.shell.run = { @Sendable cmd, path in - if cmd.description.hasSuffix("swift package dump-package") { - return #"{ "name": "Mock", "products": [], "targets": [] }"# } - return "" - } - try await withDependencies { - $0.date.now = .now - $0.packageListRepository.fetchPackageList = { @Sendable _ in [url.url] } - $0.packageListRepository.fetchPackageDenyList = { @Sendable _ in [] } - $0.packageListRepository.fetchCustomCollections = { @Sendable _ in [] } - $0.packageListRepository.fetchCustomCollection = { @Sendable _, _ in [] } - } operation: { - // run first two processing steps - try await reconcile(client: app.client, database: app.db) - try await ingest(client: app.client, database: app.db, mode: .limit(10)) - - // MUT - analyze, triggering the post - try await Analyze.analyze(client: app.client, - database: app.db, - mode: .limit(10)) - - do { - let msg = try XCTUnwrap(message.value) - XCTAssertTrue(msg.hasPrefix("📦 foo just added a new package, Mock"), "was \(msg)") + Current.shell.run = { @Sendable cmd, path in + if cmd.description.hasSuffix("swift package dump-package") { + return #"{ "name": "Mock", "products": [], "targets": [] }"# + } + return "" } - // run stages again to simulate the cycle... - message.setValue(nil) - try await reconcile(client: app.client, database: app.db) - } - - try await withDependencies { - $0.date.now = .now.addingTimeInterval(Constants.reIngestionDeadtime) - } operation: { - try await ingest(client: app.client, database: app.db, mode: .limit(10)) + try await withDependencies { + $0.date.now = .now + $0.packageListRepository.fetchPackageList = { @Sendable _ in [url.url] } + $0.packageListRepository.fetchPackageDenyList = { @Sendable _ in [] } + $0.packageListRepository.fetchCustomCollections = { @Sendable _ in [] } + $0.packageListRepository.fetchCustomCollection = { @Sendable _, _ in [] } + } operation: { + // run first two processing steps + try await reconcile(client: app.client, database: app.db) + try await ingest(client: app.client, database: app.db, mode: .limit(10)) + + // MUT - analyze, triggering the post + try await Analyze.analyze(client: app.client, + database: app.db, + mode: .limit(10)) + + do { + let msg = try XCTUnwrap(message.value) + XCTAssertTrue(msg.hasPrefix("📦 foo just added a new package, Mock"), "was \(msg)") + } + + // run stages again to simulate the cycle... + message.setValue(nil) + try await reconcile(client: app.client, database: app.db) + } - // MUT - analyze, triggering posts if any - try await Analyze.analyze(client: app.client, - database: app.db, - mode: .limit(10)) - } + try await withDependencies { + $0.date.now = .now.addingTimeInterval(Constants.reIngestionDeadtime) + } operation: { + try await ingest(client: app.client, database: app.db, mode: .limit(10)) - // validate - there are no new posts to send - XCTAssertNil(message.value) + // MUT - analyze, triggering posts if any + try await Analyze.analyze(client: app.client, + database: app.db, + mode: .limit(10)) + } - // Now simulate receiving a package update: version 2.0.0 - Current.git.getTags = { @Sendable _ in [.tag(2, 0, 0)] } + // validate - there are no new posts to send + XCTAssertNil(message.value) + + // Now simulate receiving a package update: version 2.0.0 + Current.git.getTags = { @Sendable _ in [.tag(2, 0, 0)] } + + try await withDependencies { + // fast forward our clock by the deadtime interval again (*2) and re-ingest + $0.date.now = .now.addingTimeInterval(Constants.reIngestionDeadtime * 2) + } operation: { + try await ingest(client: app.client, database: app.db, mode: .limit(10)) + // MUT - analyze again + try await Analyze.analyze(client: app.client, + database: app.db, + mode: .limit(10)) + } - try await withDependencies { - // fast forward our clock by the deadtime interval again (*2) and re-ingest - $0.date.now = .now.addingTimeInterval(Constants.reIngestionDeadtime * 2) - } operation: { - try await ingest(client: app.client, database: app.db, mode: .limit(10)) - // MUT - analyze again - try await Analyze.analyze(client: app.client, - database: app.db, - mode: .limit(10)) + // validate + let msg = try XCTUnwrap(message.value) + XCTAssertTrue(msg.hasPrefix("⬆️ foo just released Mock v2.0.0"), "was: \(msg)") } - - // validate - let msg = try XCTUnwrap(message.value) - XCTAssertTrue(msg.hasPrefix("⬆️ foo just released Mock v2.0.0"), "was: \(msg)") } } diff --git a/Tests/AppTests/Mocks/AppEnvironment+mock.swift b/Tests/AppTests/Mocks/AppEnvironment+mock.swift index 08d0e8958..fdc88cfc6 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( - allowTwitterPosts: { true }, apiSigningKey: { nil }, appVersion: { "test" }, awsAccessKeyId: { nil }, @@ -56,8 +55,6 @@ extension AppEnvironment { httpClient: { httpClient }, loadSPIManifest: { _ in nil }, logger: { logger }, - mastodonCredentials: { nil }, - mastodonPost: { _, _ in }, metricsPushGatewayUrl: { "http://pushgateway:9091" }, plausibleBackendReportingSiteID: { nil }, postPlausibleEvent: { _, _, _, _ in }, diff --git a/Tests/AppTests/ReAnalyzeVersionsTests.swift b/Tests/AppTests/ReAnalyzeVersionsTests.swift index 118c4c484..b275927c0 100644 --- a/Tests/AppTests/ReAnalyzeVersionsTests.swift +++ b/Tests/AppTests/ReAnalyzeVersionsTests.swift @@ -28,6 +28,8 @@ class ReAnalyzeVersionsTests: AppTestCase { // Basic end-to-end test try await withDependencies { $0.date.now = .t0 + $0.environment.allowSocialPosts = { true } + $0.environment.mastodonPost = { @Sendable _, _ in } } operation: { // setup // - package dump does not include toolsVersion, targets to simulate an "old version" diff --git a/Tests/AppTests/SocialTests.swift b/Tests/AppTests/SocialTests.swift index bbfef8b70..3c41153ef 100644 --- a/Tests/AppTests/SocialTests.swift +++ b/Tests/AppTests/SocialTests.swift @@ -14,6 +14,7 @@ @testable import App +import Dependencies import InlineSnapshotTesting import NIOConcurrencyHelpers import XCTVapor @@ -192,12 +193,16 @@ class SocialTests: AppTestCase { let versions = try await Analyze.updateLatestVersions(on: app.db, package: jpr) let posted: NIOLockedValueBox = .init(0) - Current.mastodonPost = { _, _ in posted.withLockedValue { $0 += 1 } } - // MUT - try await Social.postToFirehose(client: app.client, - package: jpr, - versions: versions) + try await withDependencies { + $0.environment.allowSocialPosts = { true } + $0.environment.mastodonPost = { @Sendable _, _ in posted.withLockedValue { $0 += 1 } } + } operation: { + // MUT + try await Social.postToFirehose(client: app.client, + package: jpr, + versions: versions) + } // validate try await XCTAssertEqualAsync(posted.withLockedValue { $0 }, 2) @@ -220,38 +225,45 @@ class SocialTests: AppTestCase { let versions = try await Analyze.updateLatestVersions(on: app.db, package: jpr) let posted: NIOLockedValueBox = .init(0) - Current.mastodonPost = { _, msg in - XCTAssertTrue(msg.contains("v2.0.0")) - posted.withLockedValue { $0 += 1 } - } - // MUT - try await Social.postToFirehose(client: app.client, - package: jpr, - versions: versions) + try await withDependencies { + $0.environment.allowSocialPosts = { true } + $0.environment.mastodonPost = { @Sendable _, msg in + XCTAssertTrue(msg.contains("v2.0.0")) + posted.withLockedValue { $0 += 1 } + } + } operation: { + // MUT + try await Social.postToFirehose(client: app.client, + package: jpr, + versions: versions) + } // validate try await XCTAssertEqualAsync(posted.withLockedValue { $0 }, 1) } func test_urlEncoding() async throws { - // setup - Current.mastodonCredentials = { .init(accessToken: "fakeToken") } - let message = Social.versionUpdateMessage( - packageName: "packageName", - repositoryOwnerName: "owner", - url: "http://localhost:8080/owner/SuperAwesomePackage", - version: .init(2, 6, 4), - summary: nil, - maxLength: Social.postMaxLength - ) + await withDependencies { + $0.environment.mastodonCredentials = { .init(accessToken: "fakeToken") } + } operation: { + // setup + let message = Social.versionUpdateMessage( + packageName: "packageName", + repositoryOwnerName: "owner", + url: "http://localhost:8080/owner/SuperAwesomePackage", + version: .init(2, 6, 4), + summary: nil, + maxLength: Social.postMaxLength + ) - // MUT - try? await Mastodon.post(client: app.client, message: message) { encoded in - assertInlineSnapshot(of: encoded, as: .lines) { + // MUT + try? await Mastodon.post(client: app.client, message: message) { encoded in + assertInlineSnapshot(of: encoded, as: .lines) { """ https://mas.to/api/v1/statuses?status=%E2%AC%86%EF%B8%8F%20owner%20just%20released%20packageName%20v2.6.4%0A%0Ahttp%3A%2F%2Flocalhost%3A8080%2Fowner%2FSuperAwesomePackage%23releases """ + } } } } diff --git a/app.yml b/app.yml index 0bebf8e6f..0245834ff 100644 --- a/app.yml +++ b/app.yml @@ -20,7 +20,7 @@ x-shared: &shared # set these variables via the environment or a `.env` file, which # docker-compose reads and uses to populate variables ALLOW_BUILD_TRIGGERS: ${ALLOW_BUILD_TRIGGERS} - ALLOW_TWITTER_POSTS: ${ALLOW_TWITTER_POSTS} + ALLOW_SOCIAL_POSTS: ${ALLOW_SOCIAL_POSTS} ANALYZE_LIMIT: ${ANALYZE_LIMIT} ANALYZE_SLEEP: ${ANALYZE_SLEEP} API_SIGNING_KEY: ${API_SIGNING_KEY}