From 383de8a84435f228ab243e6438ab35a22b91f68f Mon Sep 17 00:00:00 2001 From: "Sven A. Schmidt" Date: Fri, 20 Dec 2024 14:15:59 +0100 Subject: [PATCH 1/5] Move postPlausibleEvent to httpClient --- Sources/App/Core/AppEnvironment.swift | 15 -------- .../App/Core/BackendReportingMiddleware.swift | 4 ++- .../Core/Dependencies/EnvironmentClient.swift | 2 +- .../App/Core/Dependencies/HTTPClient.swift | 10 ++++++ Sources/App/Core/Plausible.swift | 20 ++++++----- Tests/AppTests/ApiTests.swift | 36 ++++++++----------- .../AppTests/Mocks/AppEnvironment+mock.swift | 1 - Tests/AppTests/PlausibleTests.swift | 4 +-- 8 files changed, 43 insertions(+), 49 deletions(-) diff --git a/Sources/App/Core/AppEnvironment.swift b/Sources/App/Core/AppEnvironment.swift index 610b8631d..92c03be34 100644 --- a/Sources/App/Core/AppEnvironment.swift +++ b/Sources/App/Core/AppEnvironment.swift @@ -41,7 +41,6 @@ struct AppEnvironment: Sendable { var logger: @Sendable () -> Logger var metricsPushGatewayUrl: @Sendable () -> String? var plausibleBackendReportingSiteID: @Sendable () -> String? - var postPlausibleEvent: @Sendable (Client, Plausible.Event.Kind, Plausible.Path, User?) async throws -> Void var processingBuildBacklog: @Sendable () -> Bool var runnerIds: @Sendable () -> [String] var setHTTPClient: @Sendable (Client) -> Void @@ -65,19 +64,6 @@ struct AppEnvironment: Sendable { } -extension AppEnvironment { - func postPlausibleEvent(_ event: Plausible.Event.Kind, path: Plausible.Path, user: User?) { - Task { - do { - try await Current.postPlausibleEvent(Current.httpClient(), event, path, user) - } catch { - Current.logger().warning("Plausible.postEvent failed: \(error)") - } - } - } -} - - extension AppEnvironment { nonisolated(unsafe) static var httpClient: Client! nonisolated(unsafe) static var logger: Logger! @@ -115,7 +101,6 @@ extension AppEnvironment { logger: { logger }, 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) }, processingBuildBacklog: { Environment.get("PROCESSING_BUILD_BACKLOG").flatMap(\.asBool) ?? false }, diff --git a/Sources/App/Core/BackendReportingMiddleware.swift b/Sources/App/Core/BackendReportingMiddleware.swift index 3b5900f12..f2e9ab7af 100644 --- a/Sources/App/Core/BackendReportingMiddleware.swift +++ b/Sources/App/Core/BackendReportingMiddleware.swift @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +import Dependencies import Vapor @@ -23,7 +24,8 @@ struct BackendReportingMiddleware: AsyncMiddleware { let response = try await next.respond(to: request) guard isActive else { return response } let user = try? request.auth.require(User.self) - Current.postPlausibleEvent(.pageview, path: path, user: user) + @Dependency(\.httpClient) var httpClient + try await httpClient.postPlausibleEvent(kind: .pageview, path: path, user: user) return response } } diff --git a/Sources/App/Core/Dependencies/EnvironmentClient.swift b/Sources/App/Core/Dependencies/EnvironmentClient.swift index 166993620..5c92243df 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. - // Closures returning optionals or Void don't need this, because they automatically get the default failing + // Closures that are throwing or return 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 } diff --git a/Sources/App/Core/Dependencies/HTTPClient.swift b/Sources/App/Core/Dependencies/HTTPClient.swift index 822cf7344..88a97fd23 100644 --- a/Sources/App/Core/Dependencies/HTTPClient.swift +++ b/Sources/App/Core/Dependencies/HTTPClient.swift @@ -22,13 +22,16 @@ import Vapor struct HTTPClient { typealias Response = Vapor.HTTPClient.Response + var client: @Sendable () -> Vapor.HTTPClient = { XCTFail("client"); return .shared } var fetchDocumentation: @Sendable (_ url: URI) async throws -> Response var fetchHTTPStatusCode: @Sendable (_ url: String) async throws -> HTTPStatus + var postPlausibleEvent: @Sendable (_ kind: Plausible.Event.Kind, _ path: Plausible.Path, _ user: User?) async throws -> Void } extension HTTPClient: DependencyKey { static var liveValue: HTTPClient { .init( + client: { .shared }, fetchDocumentation: { url in try await Vapor.HTTPClient.shared.get(url: url.string).get() }, @@ -45,6 +48,9 @@ extension HTTPClient: DependencyKey { } defer: { try await client.shutdown() } + }, + postPlausibleEvent: { kind, path, user in + try await Plausible.postEvent(kind: kind, path: path, user: user) } ) } @@ -74,6 +80,10 @@ extension HTTPClient { .init(status: .ok, headers: headers, body: .init(string: url.path)) } } + + static var noop: @Sendable (_ kind: Plausible.Event.Kind, _ path: Plausible.Path, _ user: User?) async throws -> Void { + { _, _, _ in } + } } extension HTTPClient.Response { diff --git a/Sources/App/Core/Plausible.swift b/Sources/App/Core/Plausible.swift index 3fc020b96..d93c6baf7 100644 --- a/Sources/App/Core/Plausible.swift +++ b/Sources/App/Core/Plausible.swift @@ -43,18 +43,22 @@ enum Plausible { var message: String } - static let postEventURI = URI(string: "https://plausible.io/api/event") + static let postEventURL = "https://plausible.io/api/event" - static func postEvent(client: Client, kind: Event.Kind, path: Path, user: User?) async throws { + static func postEvent(kind: Event.Kind, path: Path, user: User?) async throws { +#warning("FIXME: need to inject a http client here to so we can test this in PlausibleTests") guard let siteID = Current.plausibleBackendReportingSiteID() else { throw Error(message: "PLAUSIBLE_BACKEND_REPORTING_SITE_ID not set") } - let res = try await client.post(postEventURI, headers: .applicationJSON) { req in - try req.content.encode(Event(name: .pageview, - url: "https://\(siteID)\(path.rawValue)", - domain: siteID, - props: user.props)) - } + let data = try JSONEncoder().encode(Event(name: .pageview, + url: "https://\(siteID)\(path.rawValue)", + domain: siteID, + props: user.props)) + let req = try Vapor.HTTPClient.Request(url: postEventURL, + method: .POST, + headers: .applicationJSON, + body: .data(data)) + let res = try await Vapor.HTTPClient.shared.execute(request: req).get() guard res.status.succeeded else { throw Error(message: "Request failed with status code: \(res.status)") } diff --git a/Tests/AppTests/ApiTests.swift b/Tests/AppTests/ApiTests.swift index 576c0f133..1313fa30d 100644 --- a/Tests/AppTests/ApiTests.swift +++ b/Tests/AppTests/ApiTests.swift @@ -32,6 +32,7 @@ class ApiTests: AppTestCase { func test_search_noQuery() throws { try withDependencies { $0.environment.apiSigningKey = { "secret" } + $0.httpClient.postPlausibleEvent = App.HTTPClient.noop } operation: { // MUT try app.test(.GET, "api/search", @@ -48,8 +49,12 @@ class ApiTests: AppTestCase { } func test_search_basic_param() async throws { + let event = App.ActorIsolated(nil) try await withDependencies { $0.environment.apiSigningKey = { "secret" } + $0.httpClient.postPlausibleEvent = { @Sendable kind, path, _ in + await event.setValue(.init(kind: kind, path: path)) + } } operation: { let p1 = Package(id: .id0, url: "1") try await p1.save(on: app.db) @@ -69,11 +74,6 @@ class ApiTests: AppTestCase { 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)) - } - // MUT try await app.test(.GET, "api/search?query=foo%20bar", headers: .bearerApplicationJSON(try .apiToken(secretKey: "secret", tier: .tier1)), @@ -765,6 +765,7 @@ class ApiTests: AppTestCase { } func test_get_badge() async throws { + // sas 2024-12-20: Badges are not reporting plausbile events, because they triggered way too many events. (This is an old changes, just adding this comment today as I'm removing the old, commented out test remnants we still had in place.) // setup let owner = "owner" let repo = "repo" @@ -782,11 +783,6 @@ class ApiTests: AppTestCase { try await Build(version: v, platform: .macosXcodebuild, status: .ok, swiftVersion: .v1) .save(on: app.db) - let event = App.ActorIsolated(nil) - Current.postPlausibleEvent = { @Sendable _, kind, path, _ in - await event.setValue(.init(kind: kind, path: path)) - } - // MUT - swift versions try await app.test( .GET, @@ -822,21 +818,20 @@ class ApiTests: AppTestCase { XCTAssertEqual(badge.cacheSeconds, 6*3600) XCTAssertNotNil(badge.logoSvg) }) - - // ensure API event has been reported - // API reporting for badges is currently disabled, because it is very noisy - // await event.withValue { - // XCTAssertEqual($0, .some(.init(kind: .pageview, path: .badge))) - // } } func test_package_collections_owner() async throws { try XCTSkipIf(!isRunningInCI && EnvironmentClient.liveValue.collectionSigningPrivateKey() == nil, "Skip test for local user due to unset COLLECTION_SIGNING_PRIVATE_KEY env variable") + + let event = App.ActorIsolated(nil) try await withDependencies { $0.date.now = .t0 $0.environment.apiSigningKey = { "secret" } $0.environment.collectionSigningCertificateChain = EnvironmentClient.liveValue.collectionSigningCertificateChain $0.environment.collectionSigningPrivateKey = EnvironmentClient.liveValue.collectionSigningPrivateKey + $0.httpClient.postPlausibleEvent = { @Sendable kind, path, _ in + await event.setValue(.init(kind: kind, path: path)) + } } operation: { // setup let p1 = Package(id: .id1, url: "1") @@ -855,11 +850,6 @@ class ApiTests: AppTestCase { try await Product(version: v, type: .library(.automatic), name: "lib") .save(on: app.db) - let event = App.ActorIsolated(nil) - Current.postPlausibleEvent = { @Sendable _, kind, path, _ in - await event.setValue(.init(kind: kind, path: path)) - } - do { // MUT let body: ByteBuffer = .init(string: """ { @@ -901,12 +891,14 @@ class ApiTests: AppTestCase { func test_package_collections_packageURLs() async throws { 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 + $0.httpClient.postPlausibleEvent = App.HTTPClient.noop } operation: { // setup let p1 = Package(id: UUID(uuidString: "442cf59f-0135-4d08-be00-bc9a7cebabd3")!, @@ -1049,6 +1041,7 @@ class ApiTests: AppTestCase { try await withDependencies { $0.environment.apiSigningKey = { "secret" } $0.environment.dbId = { nil } + $0.httpClient.postPlausibleEvent = App.HTTPClient.noop } operation: { let owner = "owner" let repo = "repo" @@ -1112,6 +1105,7 @@ class ApiTests: AppTestCase { func test_dependencies_get() async throws { try await withDependencies { $0.environment.apiSigningKey = { "secret" } + $0.httpClient.postPlausibleEvent = App.HTTPClient.noop } operation: { let pkg = try await savePackage(on: app.db, id: .id0, "http://github.com/foo/bar") try await Repository(package: pkg, diff --git a/Tests/AppTests/Mocks/AppEnvironment+mock.swift b/Tests/AppTests/Mocks/AppEnvironment+mock.swift index b9fc8efe9..f5a4a119d 100644 --- a/Tests/AppTests/Mocks/AppEnvironment+mock.swift +++ b/Tests/AppTests/Mocks/AppEnvironment+mock.swift @@ -40,7 +40,6 @@ extension AppEnvironment { logger: { logger }, metricsPushGatewayUrl: { "http://pushgateway:9091" }, plausibleBackendReportingSiteID: { nil }, - postPlausibleEvent: { _, _, _, _ in }, processingBuildBacklog: { false }, runnerIds: { [] }, setHTTPClient: { client in Self.httpClient = client }, diff --git a/Tests/AppTests/PlausibleTests.swift b/Tests/AppTests/PlausibleTests.swift index deea618d5..0587153cb 100644 --- a/Tests/AppTests/PlausibleTests.swift +++ b/Tests/AppTests/PlausibleTests.swift @@ -43,7 +43,7 @@ final class PlausibleTests: XCTestCase { } // MUT - _ = try await Plausible.postEvent(client: client, kind: .pageview, path: .search, user: nil) + _ = try await Plausible.postEvent(kind: .pageview, path: .search, user: nil) XCTAssertTrue(called) } @@ -64,7 +64,7 @@ final class PlausibleTests: XCTestCase { } // MUT - _ = try await Plausible.postEvent(client: client, kind: .pageview, path: .package, user: user) + _ = try await Plausible.postEvent(kind: .pageview, path: .package, user: user) XCTAssertTrue(called) } From 698edb109eebfd238fc322c1911f2fba72f7cab1 Mon Sep 17 00:00:00 2001 From: "Sven A. Schmidt" Date: Sun, 22 Dec 2024 15:02:02 +0100 Subject: [PATCH 2/5] Add warning to move EnvironmentClient.mastodonPost to httpClient.mastodonPost --- Sources/App/Core/Dependencies/EnvironmentClient.swift | 3 ++- Sources/App/Core/Social.swift | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Sources/App/Core/Dependencies/EnvironmentClient.swift b/Sources/App/Core/Dependencies/EnvironmentClient.swift index 5c92243df..7d13856bd 100644 --- a/Sources/App/Core/Dependencies/EnvironmentClient.swift +++ b/Sources/App/Core/Dependencies/EnvironmentClient.swift @@ -42,7 +42,8 @@ struct EnvironmentClient { var currentReferenceCache: @Sendable () -> CurrentReferenceCache? var dbId: @Sendable () -> String? var mastodonCredentials: @Sendable () -> Mastodon.Credentials? - var mastodonPost: @Sendable (_ client: Client, _ post: String) async throws -> Void + #warning("drop client parameter and move this to httpClient") + var mastodonPost: @Sendable (_ client: Client, _ message: String) async throws -> Void var random: @Sendable (_ range: ClosedRange) -> Double = { XCTFail("random"); return Double.random(in: $0) } enum FailureMode: String { diff --git a/Sources/App/Core/Social.swift b/Sources/App/Core/Social.swift index cf722e9f3..8d651eb77 100644 --- a/Sources/App/Core/Social.swift +++ b/Sources/App/Core/Social.swift @@ -105,7 +105,7 @@ enum Social { throw Error.invalidMessage } // Ignore errors from here for now to keep concurrency simpler - async let _ = try? await environment.mastodonPost(client, message) + async let _ = try? await environment.mastodonPost(client: client, message: message) } static func postToFirehose(client: Client, From 0a230314a164e14dd2979c483455f3ad52906f63 Mon Sep 17 00:00:00 2001 From: "Sven A. Schmidt" Date: Sun, 22 Dec 2024 15:56:16 +0100 Subject: [PATCH 3/5] Fix PlausibleTests --- .../App/Core/Dependencies/HTTPClient.swift | 8 ++- Sources/App/Core/Plausible.swift | 11 ++- Tests/AppTests/PlausibleTests.swift | 70 +++++++++++-------- 3 files changed, 49 insertions(+), 40 deletions(-) diff --git a/Sources/App/Core/Dependencies/HTTPClient.swift b/Sources/App/Core/Dependencies/HTTPClient.swift index 88a97fd23..248046686 100644 --- a/Sources/App/Core/Dependencies/HTTPClient.swift +++ b/Sources/App/Core/Dependencies/HTTPClient.swift @@ -20,9 +20,10 @@ import Vapor @DependencyClient struct HTTPClient { + typealias Request = Vapor.HTTPClient.Request typealias Response = Vapor.HTTPClient.Response - var client: @Sendable () -> Vapor.HTTPClient = { XCTFail("client"); return .shared } + var post: @Sendable (_ url: String, _ headers: HTTPHeaders, _ body: Data) async throws -> Response var fetchDocumentation: @Sendable (_ url: URI) async throws -> Response var fetchHTTPStatusCode: @Sendable (_ url: String) async throws -> HTTPStatus var postPlausibleEvent: @Sendable (_ kind: Plausible.Event.Kind, _ path: Plausible.Path, _ user: User?) async throws -> Void @@ -31,7 +32,10 @@ struct HTTPClient { extension HTTPClient: DependencyKey { static var liveValue: HTTPClient { .init( - client: { .shared }, + post: { url, headers, body in + let req = try Request(url: url, method: .POST, headers: headers, body: .data(body)) + return try await Vapor.HTTPClient.shared.execute(request: req).get() + }, fetchDocumentation: { url in try await Vapor.HTTPClient.shared.get(url: url.string).get() }, diff --git a/Sources/App/Core/Plausible.swift b/Sources/App/Core/Plausible.swift index d93c6baf7..9408f1488 100644 --- a/Sources/App/Core/Plausible.swift +++ b/Sources/App/Core/Plausible.swift @@ -13,6 +13,7 @@ // limitations under the License. import Vapor +import Dependencies enum Plausible { @@ -46,19 +47,15 @@ enum Plausible { static let postEventURL = "https://plausible.io/api/event" static func postEvent(kind: Event.Kind, path: Path, user: User?) async throws { -#warning("FIXME: need to inject a http client here to so we can test this in PlausibleTests") guard let siteID = Current.plausibleBackendReportingSiteID() else { throw Error(message: "PLAUSIBLE_BACKEND_REPORTING_SITE_ID not set") } - let data = try JSONEncoder().encode(Event(name: .pageview, + let body = try JSONEncoder().encode(Event(name: .pageview, url: "https://\(siteID)\(path.rawValue)", domain: siteID, props: user.props)) - let req = try Vapor.HTTPClient.Request(url: postEventURL, - method: .POST, - headers: .applicationJSON, - body: .data(data)) - let res = try await Vapor.HTTPClient.shared.execute(request: req).get() + @Dependency(\.httpClient) var httpClient + let res = try await httpClient.post(url: postEventURL, headers: .applicationJSON, body: body) guard res.status.succeeded else { throw Error(message: "Request failed with status code: \(res.status)") } diff --git a/Tests/AppTests/PlausibleTests.swift b/Tests/AppTests/PlausibleTests.swift index 0587153cb..dbfb53e6f 100644 --- a/Tests/AppTests/PlausibleTests.swift +++ b/Tests/AppTests/PlausibleTests.swift @@ -16,6 +16,8 @@ import XCTest @testable import App +import Dependencies + final class PlausibleTests: XCTestCase { @@ -29,43 +31,49 @@ final class PlausibleTests: XCTestCase { } func test_postEvent_anonymous() async throws { - Current.plausibleBackendReportingSiteID = { "foo.bar" } - - var called = false - let client = MockClient { req, _ in - called = true - // validate - XCTAssertEqual(try? req.content.decode(Plausible.Event.self), - .init(name: .pageview, - url: "https://foo.bar/api/search", - domain: "foo.bar", - props: ["user": "none"])) - } + let called = ActorIsolated(false) + try await withDependencies { + $0.httpClient.post = { @Sendable _, _, body in + await called.withValue { $0 = true } + // validate + XCTAssertEqual(try? JSONDecoder().decode(Plausible.Event.self, from: body), + .init(name: .pageview, + url: "https://foo.bar/api/search", + domain: "foo.bar", + props: ["user": "none"])) + return .ok + } + } operation: { + Current.plausibleBackendReportingSiteID = { "foo.bar" } - // MUT - _ = try await Plausible.postEvent(kind: .pageview, path: .search, user: nil) + // MUT + _ = try await Plausible.postEvent(kind: .pageview, path: .search, user: nil) - XCTAssertTrue(called) + await called.withValue { XCTAssertTrue($0) } + } } func test_postEvent_package() async throws { - Current.plausibleBackendReportingSiteID = { "foo.bar" } + let called = ActorIsolated(false) + try await withDependencies { + $0.httpClient.post = { @Sendable _, _, body in + await called.withValue { $0 = true } + // validate + XCTAssertEqual(try? JSONDecoder().decode(Plausible.Event.self, from: body), + .init(name: .pageview, + url: "https://foo.bar/api/packages/{owner}/{repository}", + domain: "foo.bar", + props: ["user": "3c469e9d"])) + return .ok + } + } operation: { + Current.plausibleBackendReportingSiteID = { "foo.bar" } + let user = User(name: "api", identifier: "3c469e9d") - let user = User(name: "api", identifier: "3c469e9d") - var called = false - let client = MockClient { req, _ in - called = true - // validate - XCTAssertEqual(try? req.content.decode(Plausible.Event.self), - .init(name: .pageview, - url: "https://foo.bar/api/packages/{owner}/{repository}", - domain: "foo.bar", - props: ["user": user.identifier])) - } - - // MUT - _ = try await Plausible.postEvent(kind: .pageview, path: .package, user: user) + // MUT + _ = try await Plausible.postEvent(kind: .pageview, path: .package, user: user) - XCTAssertTrue(called) + await called.withValue { XCTAssertTrue($0) } + } } } From 618cef917f4550adaff4b1e32b0f312f852f5f22 Mon Sep 17 00:00:00 2001 From: "Sven A. Schmidt" Date: Sun, 22 Dec 2024 16:32:04 +0100 Subject: [PATCH 4/5] Fix RSSTests --- Tests/AppTests/RSSTests.swift | 283 ++++++++++++++++++---------------- 1 file changed, 152 insertions(+), 131 deletions(-) diff --git a/Tests/AppTests/RSSTests.swift b/Tests/AppTests/RSSTests.swift index 796a6f57e..cd04caaf9 100644 --- a/Tests/AppTests/RSSTests.swift +++ b/Tests/AppTests/RSSTests.swift @@ -14,6 +14,7 @@ @testable import App +import Dependencies import SnapshotTesting import XCTVapor @@ -132,157 +133,177 @@ class RSSTests: SnapshotTestCase { } func test_recentPackages_route() throws { - // Test request handler - try app.test(.GET, "packages.rss", afterResponse: { res in - XCTAssertEqual(res.status, .ok) - XCTAssertEqual(res.content.contentType, - .some(.init(type: "application", subType: "rss+xml"))) - }) + try withDependencies { + $0.httpClient.postPlausibleEvent = App.HTTPClient.noop + } operation: { + // Test request handler + try app.test(.GET, "packages.rss", afterResponse: { res in + XCTAssertEqual(res.status, .ok) + XCTAssertEqual(res.content.contentType, + .some(.init(type: "application", subType: "rss+xml"))) + }) + } } func test_recentReleases_route_all() async throws { - // Test request handler - without parameters (all) - // setup - // see RecentViewsTests.test_recentReleases_filter for filter results - for idx in 1...10 { - let major = idx / 3 // 0, 0, 1, 1, 1, 2, 2, 2, 3, 3 - let minor = idx % 3 // 1, 2, 0, 1, 2, 0, 1, 2, 0, 1 - let patch = idx % 2 // 1, 0, 1, 0, 1, 0, 1, 0, 1, 0 - let pkg = Package(id: UUID(), url: "\(idx)".asGithubUrl.url) - try await pkg.save(on: app.db) - try await Repository(package: pkg, - name: "pkg-\(idx)", - owner: "owner-\(idx)", - summary: "Summary") - .create(on: app.db) - try await Version(package: pkg, - commitDate: Date(timeIntervalSince1970: TimeInterval(idx)), - packageName: "pkg-\(idx)", - reference: .tag(.init(major, minor, patch)), - url: "https://example.com/release-url") - .save(on: app.db) - } - // make sure to refresh the materialized view - try await RecentRelease.refresh(on: app.db) + try await withDependencies { + $0.httpClient.postPlausibleEvent = App.HTTPClient.noop + } operation: { + // Test request handler - without parameters (all) + // setup + // see RecentViewsTests.test_recentReleases_filter for filter results + for idx in 1...10 { + let major = idx / 3 // 0, 0, 1, 1, 1, 2, 2, 2, 3, 3 + let minor = idx % 3 // 1, 2, 0, 1, 2, 0, 1, 2, 0, 1 + let patch = idx % 2 // 1, 0, 1, 0, 1, 0, 1, 0, 1, 0 + let pkg = Package(id: UUID(), url: "\(idx)".asGithubUrl.url) + try await pkg.save(on: app.db) + try await Repository(package: pkg, + name: "pkg-\(idx)", + owner: "owner-\(idx)", + summary: "Summary") + .create(on: app.db) + try await Version(package: pkg, + commitDate: Date(timeIntervalSince1970: TimeInterval(idx)), + packageName: "pkg-\(idx)", + reference: .tag(.init(major, minor, patch)), + url: "https://example.com/release-url") + .save(on: app.db) + } + // make sure to refresh the materialized view + try await RecentRelease.refresh(on: app.db) - // MUT - try await app.test(.GET, "releases.rss", afterResponse: { @MainActor res async in - XCTAssertEqual(res.status, .ok) - XCTAssertEqual(res.content.contentType, - .some(.init(type: "application", subType: "rss+xml"))) - // validation - assertSnapshot(of: String(decoding: res.body.readableBytesView, as: UTF8.self), - as: .init(pathExtension: "xml", diffing: .lines)) - }) + // MUT + try await app.test(.GET, "releases.rss", afterResponse: { @MainActor res async in + XCTAssertEqual(res.status, .ok) + XCTAssertEqual(res.content.contentType, + .some(.init(type: "application", subType: "rss+xml"))) + // validation + assertSnapshot(of: String(decoding: res.body.readableBytesView, as: UTF8.self), + as: .init(pathExtension: "xml", diffing: .lines)) + }) + } } func test_recentReleases_route_major() async throws { - // Test request handler - major releases only - // setup - // see RecentViewsTests.test_recentReleases_filter for filter results - for idx in 1...10 { - let major = idx / 3 // 0, 0, 1, 1, 1, 2, 2, 2, 3, 3 - let minor = idx % 3 // 1, 2, 0, 1, 2, 0, 1, 2, 0, 1 - let patch = idx % 2 // 1, 0, 1, 0, 1, 0, 1, 0, 1, 0 - let pkg = Package(id: UUID(), url: "\(idx)".asGithubUrl.url) - try await pkg.save(on: app.db) - try await Repository(package: pkg, - name: "pkg-\(idx)", - owner: "owner-\(idx)", - summary: "Summary") - .create(on: app.db) - try await Version(package: pkg, - commitDate: Date(timeIntervalSince1970: TimeInterval(idx)), - packageName: "pkg-\(idx)", - reference: .tag(.init(major, minor, patch)), - url: "https://example.com/release-url") - .save(on: app.db) - } - // make sure to refresh the materialized view - try await RecentRelease.refresh(on: app.db) + try await withDependencies { + $0.httpClient.postPlausibleEvent = App.HTTPClient.noop + } operation: { + // Test request handler - major releases only + // setup + // see RecentViewsTests.test_recentReleases_filter for filter results + for idx in 1...10 { + let major = idx / 3 // 0, 0, 1, 1, 1, 2, 2, 2, 3, 3 + let minor = idx % 3 // 1, 2, 0, 1, 2, 0, 1, 2, 0, 1 + let patch = idx % 2 // 1, 0, 1, 0, 1, 0, 1, 0, 1, 0 + let pkg = Package(id: UUID(), url: "\(idx)".asGithubUrl.url) + try await pkg.save(on: app.db) + try await Repository(package: pkg, + name: "pkg-\(idx)", + owner: "owner-\(idx)", + summary: "Summary") + .create(on: app.db) + try await Version(package: pkg, + commitDate: Date(timeIntervalSince1970: TimeInterval(idx)), + packageName: "pkg-\(idx)", + reference: .tag(.init(major, minor, patch)), + url: "https://example.com/release-url") + .save(on: app.db) + } + // make sure to refresh the materialized view + try await RecentRelease.refresh(on: app.db) - // MUT - try await app.test(.GET, "releases.rss?major=true", afterResponse: { @MainActor res async in - XCTAssertEqual(res.status, .ok) - XCTAssertEqual(res.content.contentType, - .some(.init(type: "application", subType: "rss+xml"))) - // validation - assertSnapshot(of: String(decoding: res.body.readableBytesView, as: UTF8.self), - as: .init(pathExtension: "xml", diffing: .lines)) - }) + // MUT + try await app.test(.GET, "releases.rss?major=true", afterResponse: { @MainActor res async in + XCTAssertEqual(res.status, .ok) + XCTAssertEqual(res.content.contentType, + .some(.init(type: "application", subType: "rss+xml"))) + // validation + assertSnapshot(of: String(decoding: res.body.readableBytesView, as: UTF8.self), + as: .init(pathExtension: "xml", diffing: .lines)) + }) + } } func test_recentReleases_route_majorMinor() async throws { - // Test request handler - major & minor releases only - // setup - // see RecentViewsTests.test_recentReleases_filter for filter results - for idx in 1...10 { - let major = idx / 3 // 0, 0, 1, 1, 1, 2, 2, 2, 3, 3 - let minor = idx % 3 // 1, 2, 0, 1, 2, 0, 1, 2, 0, 1 - let patch = idx % 2 // 1, 0, 1, 0, 1, 0, 1, 0, 1, 0 - let pkg = Package(id: UUID(), url: "\(idx)".asGithubUrl.url) - try await pkg.save(on: app.db) - try await Repository(package: pkg, - name: "pkg-\(idx)", - owner: "owner-\(idx)", - summary: "Summary") + try await withDependencies { + $0.httpClient.postPlausibleEvent = App.HTTPClient.noop + } operation: { + // Test request handler - major & minor releases only + // setup + // see RecentViewsTests.test_recentReleases_filter for filter results + for idx in 1...10 { + let major = idx / 3 // 0, 0, 1, 1, 1, 2, 2, 2, 3, 3 + let minor = idx % 3 // 1, 2, 0, 1, 2, 0, 1, 2, 0, 1 + let patch = idx % 2 // 1, 0, 1, 0, 1, 0, 1, 0, 1, 0 + let pkg = Package(id: UUID(), url: "\(idx)".asGithubUrl.url) + try await pkg.save(on: app.db) + try await Repository(package: pkg, + name: "pkg-\(idx)", + owner: "owner-\(idx)", + summary: "Summary") .create(on: app.db) - try await Version(package: pkg, - commitDate: Date(timeIntervalSince1970: TimeInterval(idx)), - packageName: "pkg-\(idx)", - reference: .tag(.init(major, minor, patch)), - url: "https://example.com/release-url") + try await Version(package: pkg, + commitDate: Date(timeIntervalSince1970: TimeInterval(idx)), + packageName: "pkg-\(idx)", + reference: .tag(.init(major, minor, patch)), + url: "https://example.com/release-url") .save(on: app.db) - } - // make sure to refresh the materialized view - try await RecentRelease.refresh(on: app.db) + } + // make sure to refresh the materialized view + try await RecentRelease.refresh(on: app.db) - // MUT - try await app.test(.GET, "releases.rss?major=true&minor=true", afterResponse: { @MainActor res async in - XCTAssertEqual(res.status, .ok) - XCTAssertEqual(res.content.contentType, - .some(.init(type: "application", subType: "rss+xml"))) - // validation - assertSnapshot(of: String(decoding: res.body.readableBytesView, as: UTF8.self), - as: .init(pathExtension: "xml", diffing: .lines)) - }) + // MUT + try await app.test(.GET, "releases.rss?major=true&minor=true", afterResponse: { @MainActor res async in + XCTAssertEqual(res.status, .ok) + XCTAssertEqual(res.content.contentType, + .some(.init(type: "application", subType: "rss+xml"))) + // validation + assertSnapshot(of: String(decoding: res.body.readableBytesView, as: UTF8.self), + as: .init(pathExtension: "xml", diffing: .lines)) + }) + } } func test_recentReleases_route_preRelease() async throws { - // Test request handler - pre-releases only - // setup - // see RecentViewsTests.test_recentReleases_filter for filter results - for idx in 1...12 { - let major = idx / 3 // 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4 - let minor = idx % 3 // 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0 - let patch = idx % 2 // 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0 - let pre = idx <= 10 ? "" : "b1" - let pkg = Package(id: UUID(), url: "\(idx)".asGithubUrl.url) - try await pkg.save(on: app.db) - try await Repository(package: pkg, - name: "pkg-\(idx)", - owner: "owner-\(idx)", - summary: "Summary") + try await withDependencies { + $0.httpClient.postPlausibleEvent = App.HTTPClient.noop + } operation: { + // Test request handler - pre-releases only + // setup + // see RecentViewsTests.test_recentReleases_filter for filter results + for idx in 1...12 { + let major = idx / 3 // 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4 + let minor = idx % 3 // 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0 + let patch = idx % 2 // 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0 + let pre = idx <= 10 ? "" : "b1" + let pkg = Package(id: UUID(), url: "\(idx)".asGithubUrl.url) + try await pkg.save(on: app.db) + try await Repository(package: pkg, + name: "pkg-\(idx)", + owner: "owner-\(idx)", + summary: "Summary") .create(on: app.db) - try await Version(package: pkg, - commitDate: Date(timeIntervalSince1970: TimeInterval(idx)), - packageName: "pkg-\(idx)", - reference: .tag(.init(major, minor, patch, pre)), - url: "https://example.com/release-url") + try await Version(package: pkg, + commitDate: Date(timeIntervalSince1970: TimeInterval(idx)), + packageName: "pkg-\(idx)", + reference: .tag(.init(major, minor, patch, pre)), + url: "https://example.com/release-url") .save(on: app.db) + } + // make sure to refresh the materialized view + try await RecentRelease.refresh(on: app.db) + + // MUT + try await app.test(.GET, "releases.rss?pre=true", afterResponse: { @MainActor res async in + XCTAssertEqual(res.status, .ok) + XCTAssertEqual(res.content.contentType, + .some(.init(type: "application", subType: "rss+xml"))) + // validation + assertSnapshot(of: String(decoding: res.body.readableBytesView, as: UTF8.self), + as: .init(pathExtension: "xml", diffing: .lines)) + }) } - // make sure to refresh the materialized view - try await RecentRelease.refresh(on: app.db) - - // MUT - try await app.test(.GET, "releases.rss?pre=true", afterResponse: { @MainActor res async in - XCTAssertEqual(res.status, .ok) - XCTAssertEqual(res.content.contentType, - .some(.init(type: "application", subType: "rss+xml"))) - // validation - assertSnapshot(of: String(decoding: res.body.readableBytesView, as: UTF8.self), - as: .init(pathExtension: "xml", diffing: .lines)) - }) } } From 1b000b806f59d246f1a42be113296a4a48bacde1 Mon Sep 17 00:00:00 2001 From: "Sven A. Schmidt" Date: Sun, 22 Dec 2024 16:33:14 +0100 Subject: [PATCH 5/5] Fix SitemapTests --- Tests/AppTests/SitemapTests.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Tests/AppTests/SitemapTests.swift b/Tests/AppTests/SitemapTests.swift index 7de2d8f95..b3182447b 100644 --- a/Tests/AppTests/SitemapTests.swift +++ b/Tests/AppTests/SitemapTests.swift @@ -49,6 +49,7 @@ class SitemapTests: SnapshotTestCase { // Ensure sitemap routing is configured in prod try await withDependencies { $0.environment.current = { .production } + $0.httpClient.postPlausibleEvent = App.HTTPClient.noop } operation: { // We also need to set up a new app that's configured for production, // because app.test is not affected by @Dependency overrides. @@ -100,6 +101,7 @@ class SitemapTests: SnapshotTestCase { // Ensure sitemap routing is configured in prod try await withDependencies { $0.environment.current = { .production } + $0.httpClient.postPlausibleEvent = App.HTTPClient.noop } operation: { // We also need to set up a new app that's configured for production, // because app.test is not affected by @Dependency overrides.