diff --git a/Package.resolved b/Package.resolved index ac770ff5d..27036f48c 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "e33760986d899c78399b50c74f4ceb8f2b5ec7561d009dccf66bcadc4eeb389b", + "originHash" : "7842eb8158770d2bd9ff4b5fd598106f03684dea930e4cac87a4fb62f4670c6d", "pins" : [ { "identity" : "async-http-client", @@ -550,15 +550,6 @@ "revision" : "90bdc2a157ebacc5d3de0c83e085d05d22ca5fa0" } }, - { - "identity" : "swiftopenapi", - "kind" : "remoteSourceControl", - "location" : "https://github.com/dankinsoid/SwiftOpenAPI.git", - "state" : { - "revision" : "44f9468f5ac0ce9e931062dcc667b4a8e2c498d0", - "version" : "2.20.1" - } - }, { "identity" : "swiftsoup", "kind" : "remoteSourceControl", @@ -577,15 +568,6 @@ "version" : "4.106.4" } }, - { - "identity" : "vaportoopenapi", - "kind" : "remoteSourceControl", - "location" : "https://github.com/dankinsoid/VaporToOpenAPI.git", - "state" : { - "revision" : "edafaebdff03366a4d37b59099e63ff34422bf01", - "version" : "4.7.1" - } - }, { "identity" : "websocket-kit", "kind" : "remoteSourceControl", diff --git a/Package.swift b/Package.swift index fe74ab2d7..0f675cba2 100644 --- a/Package.swift +++ b/Package.swift @@ -36,7 +36,6 @@ let package = Package( .package(url: "https://github.com/SwiftPackageIndex/SemanticVersion.git", from: "0.3.0"), .package(url: "https://github.com/SwiftPackageIndex/ShellOut.git", from: "3.1.4"), .package(url: "https://github.com/swiftlang/swift-package-manager.git", branch: "release/5.10"), - .package(url: "https://github.com/dankinsoid/VaporToOpenAPI.git", from: "4.4.4"), .package(url: "https://github.com/pointfreeco/swift-custom-dump.git", from: "1.0.0"), .package(url: "https://github.com/pointfreeco/swift-dependencies", from: "1.8.0"), .package(url: "https://github.com/pointfreeco/swift-parsing.git", from: "0.12.0"), @@ -75,7 +74,6 @@ let package = Package( .product(name: "SwiftPMDataModel-auto", package: "swift-package-manager"), .product(name: "SwiftPMPackageCollections", package: "swift-package-manager"), .product(name: "Vapor", package: "vapor"), - .product(name: "VaporToOpenAPI", package: "VaporToOpenAPI"), .product(name: "SotoCognitoAuthentication", package: "soto-cognito-authentication") ], swiftSettings: swiftSettings, diff --git a/Sources/App/Controllers/API/Types+WithExample.swift b/Sources/App/Controllers/API/Types+WithExample.swift deleted file mode 100644 index 5106812f7..000000000 --- a/Sources/App/Controllers/API/Types+WithExample.swift +++ /dev/null @@ -1,296 +0,0 @@ -// 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 Foundation - -import VaporToOpenAPI -import DependencyResolution -import PackageCollectionsSigning - - -// MARK: - External types - -extension Date: VaporToOpenAPI.WithExample { - public static var example: Self { .init(rfc1123: "Sat, 25 Apr 2020 10:55:00 UTC")! } -} - - -// MARK: - Internal types - -extension Badge: WithExample { - static var example: Self { .init(significantBuilds: .example, badgeType: .platforms)} -} - - -extension API.PackageController.BadgeQuery: WithExample { - static var example: Self { .init(type: .platforms) } -} - - -extension API.SearchController.Query: WithExample { - static var example: Self { .init(query: "LinkedList") } -} - - -extension Search.Result: WithExample { - static var example: Self { - .package( - .init(packageId: .example, - packageName: "LinkedList", - packageURL: "https://github.com/mona/LinkedList.git", - repositoryName: "LinkedList", - repositoryOwner: "mona", - stars: 123, - lastActivityAt: .example, - summary: "An example package", - keywords: [], - hasDocs: true)! - ) - } -} - - -extension Search.Response: WithExample { - static var example: Self { - .init(hasMoreResults: false, - searchTerm: "LinkedList", - searchFilters: [.example], - results: [.example]) - } -} - - -extension SearchFilter.ViewModel: WithExample { - static var example: Self { - .init(key: "author", operator: "is", value: "mona") - } -} - - -extension SignificantBuilds: WithExample { - static var example: Self { - .init(buildInfo: [ - (.v5_8, Build.Platform.iOS, .ok) - ]) - } -} - - -// MARK: - Package collection types - -import PackageCollectionsModel - - -extension API.PostPackageCollectionDTO: WithExample { - static var example: Self { - .init(selection: .packageURLs(["https://github.com/mona/LinkedList.git"]), - collectionName: "LinkedList collection", - overview: "This is a package collection created for demonstration purposes.", - revision: 3) - } -} - -extension PackageCollectionModel.V1.Collection: VaporToOpenAPI.WithExample { - public static var example: Self { - .init(name: "Packages by mona", - overview: "A collection of packages authored by mona from the Swift Package Index", - keywords: nil, packages: [ - .init(url: URL(string: "https://github.com/mona/LinkedList.git")!, - summary: "An example package", - keywords: nil, - versions: [], - readmeURL: URL(string: "https://github.com/mona/LinkedList/blob/main/README.md")!, - license: .init(name: "MIT", - url: URL(string: "https://github.com/mona/LinkedList/blob/main/LICENSE")!)) - ], - formatVersion: .v1_0, - revision: nil, - generatedBy: .init(name: "mona")) - } -} - -extension PackageCollectionModel.V1.Signature.Certificate: VaporToOpenAPI.WithExample { - public static var example: Self { - .init(subject: .init(userID: "V676TFACYJ", - commonName: "Swift Package Collection: SPI Operations Limited", - organizationalUnit: "V676TFACYJ", - organization: "SPI Operations Limited"), - issuer: .init(userID: nil, - commonName: "Apple Worldwide Developer Relations Certification Authority", - organizationalUnit: "G3", - organization: "Apple Inc.")) - } -} - -extension PackageCollectionModel.V1.Signature: VaporToOpenAPI.WithExample { - public static var example: Self { - .init(signature: "ewogICJhbGciIDogIlJ......WD1pXXPrkvVJlv4w", certificate: .example) - } -} - -extension PackageCollectionSigning.Model.SignedCollection: VaporToOpenAPI.WithExample { - public static var example: Self { - .init(collection: .example, signature: .example) - } -} - - -// MARK: - Package API - -extension API.PackageController.GetRoute.Model.Activity: WithExample { - static var example: Self { - .init( - openIssuesCount: 2, - openIssuesURL: "https://github.com/mona/LinkedList/issues", - openPullRequestsCount: 1, - openPullRequestsURL: "https://github.com/mona/LinkedList/pulls", - lastIssueClosedAt: .example, - lastPullRequestClosedAt: .example - ) - } -} - - -extension API.PackageController.GetRoute.Model.History: WithExample { - static var example: Self { - .init(createdAt: .example, - commitCount: 433, - commitCountURL: "https://github.com/mona/LinkedList/commits/main", - releaseCount: 5, - releaseCountURL: "https://github.com/mona/LinkedList/releases") - } -} - - -extension API.PackageController.GetRoute.Model: WithExample { - static var example: Self { - .init(packageId: .example, - repositoryOwner: "mona", - repositoryOwnerName: "Mona", - repositoryName: "LinkedList", - activity: .example, - authors: .fromSPIManifest("Mona"), - swiftVersionBuildInfo: .init( - stable: .init( - referenceName: "1.2.3", - results: .init(results: [.v5_8: .incompatible, - .v5_9: .incompatible, - .v5_10: .unknown, - .v6_0: .compatible])), - beta: .init( - referenceName: "2.0.0-b1", - results: .init(results: [.v5_8: .incompatible, - .v5_9: .incompatible, - .v5_10: .unknown, - .v6_0: .compatible])), - latest: .init( - referenceName: "main", - results: .init(results: [.v5_8: .incompatible, - .v5_9: .incompatible, - .v5_10: .unknown, - .v6_0: .compatible])) - ), - platformBuildInfo: .init( - stable: .init( - referenceName: "1.2.3", - results: .init(results: [.iOS: .compatible, - .linux: .unknown, - .macOS: .unknown, - .tvOS: .unknown, - .visionOS: .unknown, - .watchOS: .unknown])), - beta: .init( - referenceName: "2.0.0-b1", - results: .init(results: [.iOS: .compatible, - .linux: .unknown, - .macOS: .unknown, - .tvOS: .unknown, - .visionOS: .unknown, - .watchOS: .unknown])), - latest: .init( - referenceName: "main", - results: .init(results: [.iOS: .compatible, - .linux: .compatible, - .macOS: .compatible, - .tvOS: .compatible, - .visionOS: .compatible, - .watchOS: .compatible])) - ), - history: .example, - license: .mit, - products: [.init(name: "lib", type: .library)], - releases: .init( - stable: .init(date: .example, - link: .init(label: "1.2.3", - url: "https://github.com/mona/LinkedList/releases/tag/1.2.3")), - latest: .init(date: .example, - link: .init(label: "main", - url: "https://github.com/mona/LinkedList/tree/main"))), - dependencies: nil, - stars: 123, - summary: "An example package", - targets: [.init(name: "target", type: .macro)], - title: "LinkedList", - url: "https://github.com/mona/LinkedList.git", - isArchived: false, - defaultBranchReference: .branch("main"), - releaseReference: .tag(1, 2, 3, "1.2.3"), - preReleaseReference: nil, - swift6Readiness: nil, - forkedFromInfo: nil, - customCollections: []) - } -} - - -// MARK: - Build/doc reporting types - -extension API.PostBuildReportDTO: WithExample { - static var example: Self { - .init(builderVersion: "1.2.3", - buildId: .example, - platform: .iOS, - productDependencies: [ - ProductDependency(identity: "1", - name: "name", - url: "http://vapor.com", - dependencies: []) - ], - status: .ok, - swiftVersion: .v5_8) - } -} - -extension API.PostDocReportDTO: WithExample { - static var example: Self { - .init(docArchives: [.init(name: "linkedlist", title: "LinkedList")], - error: nil, - fileCount: 2639, - linkablePathsCount: 137, - logUrl: "https://us-east-2.console.aws.amazon.com/logs/123456678", - mbSize: 23, - status: .ok) - } -} - - -// MARK: - Dependency types - -extension API.DependencyController.PackageRecord: WithExample { - static var example: Self { - .init(id: .example, - url: .init("https://github.com/foo/bar")!, - resolvedDependencies: [.init("https://github.com/foo/dependency")!]) - } -} diff --git a/Sources/App/Core/AppMetrics.swift b/Sources/App/Core/AppMetrics.swift index c2c4ddb8a..2ec692b17 100644 --- a/Sources/App/Core/AppMetrics.swift +++ b/Sources/App/Core/AppMetrics.swift @@ -15,17 +15,18 @@ import Dependencies import Metrics import Prometheus +import Synchronization import Vapor enum AppMetrics { - nonisolated(unsafe) static var initialized = false + static let initialized = Mutex(false) static func bootstrap() { // prevent tests from boostrapping multiple times - guard !initialized else { return } - defer { initialized = true } + guard !initialized.withLock({ $0 }) else { return } + defer { initialized.withLock{ $0 = true } } let client = PrometheusClient() MetricsSystem.bootstrap(PrometheusMetricsFactory(client: client)) } diff --git a/Sources/App/Core/Authentication/User.swift b/Sources/App/Core/Authentication/User.swift index 75a05b88a..4ea7ed02a 100644 --- a/Sources/App/Core/Authentication/User.swift +++ b/Sources/App/Core/Authentication/User.swift @@ -16,7 +16,6 @@ import Authentication import Dependencies import JWTKit import Vapor -import VaporToOpenAPI struct User: Authenticatable, Equatable { @@ -67,15 +66,3 @@ extension User { } } } - - -extension AuthSchemeObject { - static var apiBearerToken: Self { - .bearer(id: "api_token", - description: "Token used for API access.") - } - static var builderBearerToken: Self { - .bearer(id: "builder_token", - description: "Token used for build result reporting.") - } -} diff --git a/Sources/App/Core/Extensions/PSQLError+ext.swift b/Sources/App/Core/Extensions/PSQLError+ext.swift index e89996a58..e95cdbaf3 100644 --- a/Sources/App/Core/Extensions/PSQLError+ext.swift +++ b/Sources/App/Core/Extensions/PSQLError+ext.swift @@ -14,7 +14,6 @@ import PostgresKit -extension PostgresNIO.PostgresError.Code: @unchecked Swift.Sendable {} extension PSQLError { // TODO: upstream to FluentKit's DatabaseError diff --git a/Sources/App/configure.swift b/Sources/App/configure.swift index 25798673b..666ebc758 100644 --- a/Sources/App/configure.swift +++ b/Sources/App/configure.swift @@ -19,7 +19,7 @@ import Vapor @discardableResult -public func configure(_ app: Application) async throws -> String { +public func configure(_ app: Application, databasePort: Int? = nil) async throws -> String { #if DEBUG && os(macOS) // The bundle is only loaded if /Applications/InjectionIII.app exists on the local development machine. // Requires InjectionIII 4.7.3 or higher to be loaded for compatibility with Package.swift files. @@ -55,7 +55,7 @@ public func configure(_ app: Application) async throws -> String { // Setup database connection guard let host = Environment.get("DATABASE_HOST"), - let port = Environment.get("DATABASE_PORT").flatMap(Int.init), + let port = databasePort ?? Environment.get("DATABASE_PORT").flatMap(Int.init), let username = Environment.get("DATABASE_USERNAME"), let password = Environment.get("DATABASE_PASSWORD"), let database = Environment.get("DATABASE_NAME") diff --git a/Sources/App/routes+documentation.swift b/Sources/App/routes+documentation.swift index 595d4171f..5a8f3920a 100644 --- a/Sources/App/routes+documentation.swift +++ b/Sources/App/routes+documentation.swift @@ -22,76 +22,76 @@ func docRoutes(_ app: Application) throws { // redirected to the fully formed documentation URL. app.get(":owner", ":repository", "documentation") { req -> Response in req.redirect(to: SiteURL.relativeURL(for: try await req.getDocRedirect(), fragment: .documentation)) - }.excludeFromOpenAPI() + } app.get(":owner", ":repository", "documentation", "**") { req -> Response in req.redirect(to: SiteURL.relativeURL(for: try await req.getDocRedirect(), fragment: .documentation)) - }.excludeFromOpenAPI() + } app.get(":owner", ":repository", "tutorials", "**") { req -> Response in req.redirect(to: SiteURL.relativeURL(for: try await req.getDocRedirect(), fragment: .tutorials)) - }.excludeFromOpenAPI() + } app.get(":owner", ":repository", ":reference", "documentation") { req -> Response in req.redirect(to: SiteURL.relativeURL(for: try await req.getDocRedirect(), fragment: .documentation)) - }.excludeFromOpenAPI() + } // Stable URLs with reference (real reference or ~) app.get(":owner", ":repository", ":reference", "documentation", ":archive") { let route = try await $0.getDocRoute(fragment: .documentation) return try await PackageController.documentation(req: $0, route: route) - }.excludeFromOpenAPI() + } app.get(":owner", ":repository", ":reference", "documentation", ":archive", "**") { let route = try await $0.getDocRoute(fragment: .documentation) return try await PackageController.documentation(req: $0, route: route) - }.excludeFromOpenAPI() + } app.get(":owner", ":repository", ":reference", .fragment(.faviconIco)) { let route = try await $0.getDocRoute(fragment: .faviconIco) return try await PackageController.documentation(req: $0, route: route) - }.excludeFromOpenAPI() + } app.get(":owner", ":repository", ":reference", .fragment(.faviconSvg)) { let route = try await $0.getDocRoute(fragment: .faviconSvg) return try await PackageController.documentation(req: $0, route: route) - }.excludeFromOpenAPI() + } app.get(":owner", ":repository", ":reference", "css", "**") { let route = try await $0.getDocRoute(fragment: .css) return try await PackageController.documentation(req: $0, route: route) - }.excludeFromOpenAPI() + } app.get(":owner", ":repository", ":reference", "data", "**") { let route = try await $0.getDocRoute(fragment: .data) return try await PackageController.documentation(req: $0, route: route) - }.excludeFromOpenAPI() + } app.get(":owner", ":repository", ":reference", "images", "**") { let fragment: DocRoute.Fragment = $0.parameters.hasSuffix(".svg", caseInsensitive: true) ? .svgImages : .images let route = try await $0.getDocRoute(fragment: fragment) return try await PackageController.documentation(req: $0, route: route) - }.excludeFromOpenAPI() + } app.get(":owner", ":repository", ":reference", "img", "**") { let fragment: DocRoute.Fragment = $0.parameters.hasSuffix(".svg", caseInsensitive: true) ? .svgImg : .img let route = try await $0.getDocRoute(fragment: fragment) return try await PackageController.documentation(req: $0, route: route) - }.excludeFromOpenAPI() + } app.get(":owner", ":repository", ":reference", "index", "**") { let route = try await $0.getDocRoute(fragment: .index) return try await PackageController.documentation(req: $0, route: route) - }.excludeFromOpenAPI() + } app.get(":owner", ":repository", ":reference", "js", "**") { let route = try await $0.getDocRoute(fragment: .js) return try await PackageController.documentation(req: $0, route: route) - }.excludeFromOpenAPI() + } app.get(":owner", ":repository", ":reference", .fragment(.linkablePaths)) { let route = try await $0.getDocRoute(fragment: .linkablePaths) return try await PackageController.documentation(req: $0, route: route) - }.excludeFromOpenAPI() + } app.get(":owner", ":repository", ":reference", .fragment(.themeSettings)) { let route = try await $0.getDocRoute(fragment: .themeSettings) return try await PackageController.documentation(req: $0, route: route) - }.excludeFromOpenAPI() + } app.get(":owner", ":repository", ":reference", "tutorials", "**") { let route = try await $0.getDocRoute(fragment: .tutorials) return try await PackageController.documentation(req: $0, route: route) - }.excludeFromOpenAPI() + } app.get(":owner", ":repository", ":reference", "videos", "**") { let route = try await $0.getDocRoute(fragment: .videos) return try await PackageController.documentation(req: $0, route: route) - }.excludeFromOpenAPI() + } } diff --git a/Sources/App/routes.swift b/Sources/App/routes.swift index cb654a4dd..dbfb05536 100644 --- a/Sources/App/routes.swift +++ b/Sources/App/routes.swift @@ -18,7 +18,6 @@ import Metrics import Plot import Prometheus import Vapor -import VaporToOpenAPI func routes(_ app: Application) throws { @@ -33,48 +32,48 @@ func routes(_ app: Application) throws { let model = try await HomeIndex.Model.query(database: req.db) return HomeIndex.View(path: req.url.path, model: model).document() } - }.excludeFromOpenAPI() + } } do { // static pages app.get(SiteURL.addAPackage.pathComponents) { req in MarkdownPage(path: req.url.path, "add-a-package.md").document() - }.excludeFromOpenAPI() + } app.get(SiteURL.docs(.builds).pathComponents) { req in MarkdownPage(path: req.url.path, "docs/builds.md").document() - }.excludeFromOpenAPI() + } app.get(SiteURL.faq.pathComponents) { req in MarkdownPage(path: req.url.path, "faq.md").document() - }.excludeFromOpenAPI() + } app.get(SiteURL.packageCollections.pathComponents) { req in MarkdownPage(path: req.url.path, "package-collections.md").document() - }.excludeFromOpenAPI() + } app.get(SiteURL.privacy.pathComponents) { req in MarkdownPage(path: req.url.path, "privacy.md").document() - }.excludeFromOpenAPI() + } app.get(SiteURL.tryInPlayground.pathComponents) { req in MarkdownPage(path: req.url.path, "try-package.md").document() - }.excludeFromOpenAPI() + } } try docRoutes(app) do { // package pages app.get(SiteURL.package(.key, .key, .none).pathComponents, - use: PackageController.show).excludeFromOpenAPI() + use: PackageController.show) app.get(SiteURL.package(.key, .key, .readme).pathComponents, - use: PackageController.readme).excludeFromOpenAPI() + use: PackageController.readme) app.get(SiteURL.package(.key, .key, .releases).pathComponents, - use: PackageController.releases).excludeFromOpenAPI() + use: PackageController.releases) app.get(SiteURL.package(.key, .key, .builds).pathComponents, - use: PackageController.builds).excludeFromOpenAPI() + use: PackageController.builds) app.get(SiteURL.package(.key, .key, .maintainerInfo).pathComponents, - use: PackageController.maintainerInfo).excludeFromOpenAPI() + use: PackageController.maintainerInfo) // Only serve sitemaps in production. if environment.current() == .production { @@ -82,80 +81,65 @@ func routes(_ app: Application) throws { // Backend reporting currently disabled to avoid reporting costs for metrics we don't need. app.group(BackendReportingMiddleware(path: .sitemapPackage, isActive: false)) { $0.get(SiteURL.package(.key, .key, .siteMap).pathComponents, - use: PackageController.siteMap).excludeFromOpenAPI() + use: PackageController.siteMap) } } } do { // package collection pages app.get(SiteURL.packageCollectionAuthor(.key).pathComponents, - use: PackageCollectionController.generate).excludeFromOpenAPI() + use: PackageCollectionController.generate) app.get(SiteURL.packageCollectionCustom(.key).pathComponents, - use: PackageCollectionController.generate).excludeFromOpenAPI() + use: PackageCollectionController.generate) app.get(SiteURL.packageCollectionKeyword(.key).pathComponents, - use: PackageCollectionController.generate).excludeFromOpenAPI() + use: PackageCollectionController.generate) } do { // author page - app.get(SiteURL.author(.key).pathComponents, use: AuthorController.show).excludeFromOpenAPI() + app.get(SiteURL.author(.key).pathComponents, use: AuthorController.show) } do { // keyword page - app.get(SiteURL.keywords(.key).pathComponents, use: KeywordController.show).excludeFromOpenAPI() + app.get(SiteURL.keywords(.key).pathComponents, use: KeywordController.show) } do { // Blog index, post pages, and feed - app.get(SiteURL.blog.pathComponents, use: BlogController.index).excludeFromOpenAPI() - app.get(SiteURL.blogFeed.pathComponents, use: BlogController.indexFeed).excludeFromOpenAPI() - app.get(SiteURL.blogPost(.key).pathComponents, use: BlogController.show).excludeFromOpenAPI() + app.get(SiteURL.blog.pathComponents, use: BlogController.index) + app.get(SiteURL.blogFeed.pathComponents, use: BlogController.indexFeed) + app.get(SiteURL.blogPost(.key).pathComponents, use: BlogController.show) } do { // Build monitor page - app.get(SiteURL.buildMonitor.pathComponents, use: BuildMonitorController.index).excludeFromOpenAPI() + app.get(SiteURL.buildMonitor.pathComponents, use: BuildMonitorController.index) } do { // Build details page - app.get(SiteURL.builds(.key).pathComponents, use: BuildController.show).excludeFromOpenAPI() + app.get(SiteURL.builds(.key).pathComponents, use: BuildController.show) } do { // Custom collections page - app.get(SiteURL.collections(.key).pathComponents, use: CustomCollectionsController.show).excludeFromOpenAPI() + app.get(SiteURL.collections(.key).pathComponents, use: CustomCollectionsController.show) } do { // Search page - app.get(SiteURL.search.pathComponents, use: SearchController.show).excludeFromOpenAPI() + app.get(SiteURL.search.pathComponents, use: SearchController.show) } do { // Supporters - app.get(SiteURL.supporters.pathComponents, use: SupportersController.show).excludeFromOpenAPI() + app.get(SiteURL.supporters.pathComponents, use: SupportersController.show) } do { // Uptime check - app.get(SiteURL.healthCheck.pathComponents, use: HealthCheckController.show).excludeFromOpenAPI() + app.get(SiteURL.healthCheck.pathComponents, use: HealthCheckController.show) } do { // spi.yml validation page app.get(SiteURL.validateSPIManifest.pathComponents, use: ValidateSPIManifestController.show) - .excludeFromOpenAPI() app.post(SiteURL.validateSPIManifest.pathComponents, use: ValidateSPIManifestController.validate) - .excludeFromOpenAPI() } // Ready for Swift 6 app.get(SiteURL.readyForSwift6.pathComponents, use: ReadyForSwift6Controller.show) - .excludeFromOpenAPI() - - do { // OpenAPI spec - app.get("openapi", "openapi.json") { req in - req.application.routes.openAPI( - info: InfoObject( - title: "Swift Package Index API", - description: "Swift Package Index API", - version: "0.1.1" - ) - ) - }.excludeFromOpenAPI() - } do { // api @@ -163,25 +147,10 @@ func routes(_ app: Application) throws { app.get(SiteURL.api(.version).pathComponents) { req in API.Version(version: appVersion ?? "Unknown") } - .openAPI( - summary: "/api/version", - description: "Get the site's version.", - response: .type(of: API.Version(version: "1.2.3")), - responseContentType: .application(.json) - ) - - app.group(User.APITierAuthenticator(tier: .tier1), User.guardMiddleware()) { - $0.groupedOpenAPI(auth: .apiBearerToken).group(tags: []) { protected in - protected.group(BackendReportingMiddleware(path: .search)) { - $0.get(SiteURL.api(.search).pathComponents, use: API.SearchController.get) - .openAPI( - summary: "/api/search", - description: "Execute a search.", - query: .type(of: API.SearchController.Query.example), - response: .type(of: Search.Response.example), - responseContentType: .application(.json) - ) - } + + app.group(User.APITierAuthenticator(tier: .tier1), User.guardMiddleware()) { protected in + protected.group(BackendReportingMiddleware(path: .search)) { + $0.get(SiteURL.api(.search).pathComponents, use: API.SearchController.get) } } @@ -189,77 +158,33 @@ func routes(_ app: Application) throws { app.group(BackendReportingMiddleware(path: .badge, isActive: false)) { $0.get(SiteURL.api(.packages(.key, .key, .badge)).pathComponents, use: API.PackageController.badge) - .openAPI( - summary: "/api/packages/{owner}/{repository}/badge", - description: "Get shields.io badge for the given repository.", - query: .type(of: API.PackageController.BadgeQuery.example), - response: .type(of: Badge.example), - responseContentType: .application(.json) - ) } // api token protected routes - app.group(User.APITierAuthenticator(tier: .tier3), User.guardMiddleware()) { - $0.groupedOpenAPI(auth: .apiBearerToken).group(tags: []) { protected in - protected.group(BackendReportingMiddleware(path: .package)) { - $0.get("api", "packages", ":owner", ":repository", use: API.PackageController.get) - .openAPI( - summary: "/api/packages/{owner}/{repository}", - description: "Get package details.", - response: .type(of: API.PackageController.GetRoute.Model.example), - responseContentType: .application(.json) - ) - } - - protected.group(BackendReportingMiddleware(path: .packageCollections)) { - $0.post(SiteURL.api(.packageCollections).pathComponents, - use: API.PackageCollectionController.generate) - .openAPI( - summary: "/api/package-collections", - description: "Generate a signed package collection.", - body: .type(of: API.PostPackageCollectionDTO.example), - response: .type(of: SignedCollection.example), - responseContentType: .application(.json) - ) - } - - protected.group(BackendReportingMiddleware(path: .dependencies)) { - $0.get(SiteURL.api(.dependencies).pathComponents, use: API.DependencyController.get) - .openAPI( - summary: "/api/dependencies", - description: "Return the full resolved dependencies graph across all packages.", - response: .type(of: [API.DependencyController.PackageRecord.example]), - responseContentType: .application(.json) - ) - } + app.group(User.APITierAuthenticator(tier: .tier3), User.guardMiddleware()) { protected in + protected.group(BackendReportingMiddleware(path: .package)) { + $0.get("api", "packages", ":owner", ":repository", use: API.PackageController.get) + } + + protected.group(BackendReportingMiddleware(path: .packageCollections)) { + $0.post(SiteURL.api(.packageCollections).pathComponents, + use: API.PackageCollectionController.generate) + } + + protected.group(BackendReportingMiddleware(path: .dependencies)) { + $0.get(SiteURL.api(.dependencies).pathComponents, use: API.DependencyController.get) } } // builder token protected routes - app.group(User.BuilderAuthenticator(), User.guardMiddleware()) { - $0.groupedOpenAPI(auth: .builderBearerToken).group(tags: []) { protected in - protected.on(.POST, SiteURL.api(.versions(.key, .buildReport)).pathComponents, - body: .collect(maxSize: 100_000), - use: API.BuildController.buildReport) - .openAPI( - summary: "/api/versions/{id}/build-report", - description: "Send a build report.", - body: .type(of: API.PostBuildReportDTO.example), - response: .type(HTTPStatus.self), - responseContentType: .application(.json) - ) - - protected.on(.POST, SiteURL.api(.builds(.key, .docReport)).pathComponents, - body: .collect(maxSize: 100_000), - use: API.BuildController.docReport) - .openAPI( - summary: "/api/builds/{id}/doc-report", - description: "Send a documentation generation report.", - body: .type(of: API.PostDocReportDTO.example), - response: .type(HTTPStatus.self), - responseContentType: .application(.json) - ) - } + app.group(User.BuilderAuthenticator(), User.guardMiddleware()) { protected in + protected.on(.POST, SiteURL.api(.versions(.key, .buildReport)).pathComponents, + body: .collect(maxSize: 100_000), + use: API.BuildController.buildReport) + + protected.on(.POST, SiteURL.api(.builds(.key, .docReport)).pathComponents, + body: .collect(maxSize: 100_000), + use: API.BuildController.docReport) } } @@ -267,10 +192,7 @@ func routes(_ app: Application) throws { do { // RSS app.group(BackendReportingMiddleware(path: .rss)) { $0.get(SiteURL.rssPackages.pathComponents, use: RSSFeed.showPackages) - .excludeFromOpenAPI() - $0.get(SiteURL.rssReleases.pathComponents, use: RSSFeed.showReleases) - .excludeFromOpenAPI() } } @@ -279,12 +201,10 @@ func routes(_ app: Application) throws { do { // Site map index and static page site map app.group(BackendReportingMiddleware(path: .sitemapIndex)) { $0.get(SiteURL.siteMapIndex.pathComponents, use: SiteMapController.index) - .excludeFromOpenAPI() } app.group(BackendReportingMiddleware(path: .sitemapStaticPages)) { $0.get(SiteURL.siteMapStaticPages.pathComponents, use: SiteMapController.staticPages) - .excludeFromOpenAPI() } } } @@ -292,6 +212,6 @@ func routes(_ app: Application) throws { do { // Metrics app.get("metrics") { req -> String in try await MetricsSystem.prometheus().collect() - }.excludeFromOpenAPI() + } } } diff --git a/Tests/AppTests/ApiTests.swift b/Tests/AppTests/ApiTests.swift index fda64f502..f4c6f0fcc 100644 --- a/Tests/AppTests/ApiTests.swift +++ b/Tests/AppTests/ApiTests.swift @@ -18,7 +18,7 @@ import Dependencies import PackageCollectionsSigning import SnapshotTesting import Testing -import Vapor +import XCTVapor @Suite struct ApiTests { diff --git a/Tests/AppTests/AppTestCase.swift b/Tests/AppTests/AppTestCase.swift deleted file mode 100644 index 2f05db0cf..000000000 --- a/Tests/AppTests/AppTestCase.swift +++ /dev/null @@ -1,245 +0,0 @@ -// 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. - -@testable import App - -import Dependencies -import Fluent -import NIOConcurrencyHelpers -import PostgresNIO -import SQLKit -import XCTVapor - - -class AppTestCase: XCTestCase { - var app: Application! - let logger = CapturingLogger() - - override func setUp() async throws { - try await super.setUp() - app = try await setup(.testing) - - @Dependency(\.logger) var logger - logger.set(to: self.logger) - } - - func setup(_ environment: Environment) async throws -> Application { - try await withDependencies { - // Setting builderToken here when it's also set in all tests may seem redundant but it's - // what allows test_post_buildReport_large to work. - // See https://github.com/pointfreeco/swift-dependencies/discussions/300#discussioncomment-11252906 - // for details. - $0.environment.builderToken = { "secr3t" } - } operation: { - try await Self.setupDb(environment) - return try await Self.setupApp(environment) - } - } - - override func tearDown() async throws { - try await app.asyncShutdown() - try await super.tearDown() - } -} - - -extension AppTestCase { - - static func setupApp(_ environment: Environment) async throws -> Application { - let app = try await Application.make(environment) - try await configure(app) - - // Silence app logging - app.logger = .init(label: "noop") { _ in SwiftLogNoOpLogHandler() } - - return app - } - - - static func setupDb(_ environment: Environment) async throws { - await DotEnvFile.load(for: environment, fileio: .init(threadPool: .singleton)) - - // Ensure DATABASE_HOST is from a restricted set db hostnames and nothing else. - // This is safeguard against accidental inheritance of setup in QueryPerformanceTests - // and to ensure the database resetting cannot impact any other network hosts. - let host = Environment.get("DATABASE_HOST") - precondition(["localhost", "postgres", "host.docker.internal"].contains(host), - "DATABASE_HOST must be a local db, was: \(host)") - - let testDbName = Environment.get("DATABASE_NAME")! - let snapshotName = testDbName + "_snapshot" - - // Create initial db snapshot on first run - try await snapshotCreated.withValue { snapshotCreated in - if !snapshotCreated { - try await createSchema(environment, databaseName: testDbName) - try await createSnapshot(original: testDbName, snapshot: snapshotName, environment: environment) - snapshotCreated = true - } - } - - try await restoreSnapshot(original: testDbName, snapshot: snapshotName, environment: environment) - } - - - static func createSchema(_ environment: Environment, databaseName: String) async throws { - do { - try await withDatabase("postgres", environment) { // Connect to `postgres` db in order to reset the test db - try await $0.query(PostgresQuery(unsafeSQL: "DROP DATABASE IF EXISTS \(databaseName) WITH (FORCE)")) - try await $0.query(PostgresQuery(unsafeSQL: "CREATE DATABASE \(databaseName)")) - } - - do { // Use autoMigrate to spin up the schema - let app = try await Application.make(environment) - app.logger = .init(label: "noop") { _ in SwiftLogNoOpLogHandler() } - try await configure(app) - try await app.autoMigrate() - try await app.asyncShutdown() - } - } catch { - print("Create schema failed with error: ", String(reflecting: error)) - throw error - } - } - - - static func createSnapshot(original: String, snapshot: String, environment: Environment) async throws { - do { - try await withDatabase("postgres", environment) { client in - try await client.query(PostgresQuery(unsafeSQL: "DROP DATABASE IF EXISTS \(snapshot) WITH (FORCE)")) - try await client.query(PostgresQuery(unsafeSQL: "CREATE DATABASE \(snapshot) TEMPLATE \(original)")) - } - } catch { - print("Create snapshot failed with error: ", String(reflecting: error)) - throw error - } - } - - - static func restoreSnapshot(original: String, snapshot: String, environment: Environment) async throws { - // delete db and re-create from snapshot - do { - try await withDatabase("postgres", environment) { client in - try await client.query(PostgresQuery(unsafeSQL: "DROP DATABASE IF EXISTS \(original) WITH (FORCE)")) - try await client.query(PostgresQuery(unsafeSQL: "CREATE DATABASE \(original) TEMPLATE \(snapshot)")) - } - } catch { - print("Restore snapshot failed with error: ", String(reflecting: error)) - throw error - } - } - - - static let snapshotCreated = ActorIsolated(false) - -} - - -extension AppTestCase { - func renderSQL(_ builder: SQLSelectBuilder) -> String { - renderSQL(builder.query) - } - - func renderSQL(_ query: SQLExpression) -> String { - var serializer = SQLSerializer(database: app.db as! SQLDatabase) - query.serialize(to: &serializer) - return serializer.sql - } - - func binds(_ builder: SQLSelectBuilder?) -> [String] { - binds(builder?.query) - } - - func binds(_ query: SQLExpression?) -> [String] { - var serializer = SQLSerializer(database: app.db as! SQLDatabase) - query?.serialize(to: &serializer) - return serializer.binds.reduce(into: []) { result, bind in - switch bind { - case let bind as Date: - result.append(DateFormatter.filterParseFormatter.string(from: bind)) - case let bind as Set: - let s = bind.map(\.rawValue).sorted().joined(separator: ",") - result.append("{\(s)}") - case let bind as Set: - let s = bind.map(\.rawValue).sorted().joined(separator: ",") - result.append("{\(s)}") - default: - result.append("\(bind)") - } - } - } -} - - -// FIXME: Move this once AppTestCase can be removed. These are helpers created during the transition to Swift Testing. Also check if we can just create a PostgresDB object from scratch rather than using withApp and app.db for this at the call site. That does a whole migration + reset just to render the SQL needlessly. -extension Database { - func renderSQL(_ builder: SQLSelectBuilder) -> String { - renderSQL(builder.query) - } - - func renderSQL(_ query: SQLExpression) -> String { - var serializer = SQLSerializer(database: self as! SQLDatabase) - query.serialize(to: &serializer) - return serializer.sql - } - - func binds(_ builder: SQLSelectBuilder?) -> [String] { - binds(builder?.query) - } - - func binds(_ query: SQLExpression?) -> [String] { - var serializer = SQLSerializer(database: self as! SQLDatabase) - query?.serialize(to: &serializer) - return serializer.binds.reduce(into: []) { result, bind in - switch bind { - case let bind as Date: - result.append(DateFormatter.filterParseFormatter.string(from: bind)) - case let bind as Set: - let s = bind.map(\.rawValue).sorted().joined(separator: ",") - result.append("{\(s)}") - case let bind as Set: - let s = bind.map(\.rawValue).sorted().joined(separator: ",") - result.append("{\(s)}") - default: - result.append("\(bind)") - } - } - } -} - - -private func connect(to databaseName: String, _ environment: Environment) async throws -> PostgresClient { - await DotEnvFile.load(for: environment, fileio: .init(threadPool: .singleton)) - let host = Environment.get("DATABASE_HOST")! - let port = Environment.get("DATABASE_PORT").flatMap(Int.init)! - let username = Environment.get("DATABASE_USERNAME")! - let password = Environment.get("DATABASE_PASSWORD")! - - let config = PostgresClient.Configuration(host: host, port: port, username: username, password: password, database: databaseName, tls: .disable) - - return .init(configuration: config) -} - - -private func withDatabase(_ databaseName: String, _ environment: Environment, _ query: @escaping (PostgresClient) async throws -> Void) async throws { - let client = try await connect(to: databaseName, environment) - try await withThrowingTaskGroup(of: Void.self) { taskGroup in - taskGroup.addTask { await client.run() } - - try await query(client) - - taskGroup.cancelAll() - } -} - diff --git a/Tests/AppTests/Helpers/Database+ext.swift b/Tests/AppTests/Helpers/Database+ext.swift new file mode 100644 index 000000000..422bc20ab --- /dev/null +++ b/Tests/AppTests/Helpers/Database+ext.swift @@ -0,0 +1,57 @@ +// 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 Foundation + +@testable import App + +import Fluent +import SQLKit + + +// FIXME: Check if we can just create a PostgresDB object from scratch rather than using withApp and app.db for this at the call site (i.e. check if these need to be extensions on Database). That does a whole migration + reset just to render the SQL needlessly. +extension Database { + func renderSQL(_ builder: SQLSelectBuilder) -> String { + renderSQL(builder.query) + } + + func renderSQL(_ query: SQLExpression) -> String { + var serializer = SQLSerializer(database: self as! SQLDatabase) + query.serialize(to: &serializer) + return serializer.sql + } + + func binds(_ builder: SQLSelectBuilder?) -> [String] { + binds(builder?.query) + } + + func binds(_ query: SQLExpression?) -> [String] { + var serializer = SQLSerializer(database: self as! SQLDatabase) + query?.serialize(to: &serializer) + return serializer.binds.reduce(into: []) { result, bind in + switch bind { + case let bind as Date: + result.append(DateFormatter.filterParseFormatter.string(from: bind)) + case let bind as Set: + let s = bind.map(\.rawValue).sorted().joined(separator: ",") + result.append("{\(s)}") + case let bind as Set: + let s = bind.map(\.rawValue).sorted().joined(separator: ",") + result.append("{\(s)}") + default: + result.append("\(bind)") + } + } + } +} diff --git a/Tests/AppTests/Helpers/TestSupport.swift b/Tests/AppTests/Helpers/TestSupport.swift index 4b5960d80..4e6ad5af9 100644 --- a/Tests/AppTests/Helpers/TestSupport.swift +++ b/Tests/AppTests/Helpers/TestSupport.swift @@ -16,6 +16,7 @@ import Dependencies import Vapor +import PostgresNIO func withApp(_ setup: (Application) async throws -> Void = { _ in }, @@ -23,8 +24,8 @@ func withApp(_ setup: (Application) async throws -> Void = { _ in }, logHandler: LogHandler? = nil, environment: Environment = .testing, _ test: (Application) async throws -> Void) async throws { - try await AppTestCase.setupDb(environment) - let app = try await AppTestCase.setupApp(environment) + try await TestSupport.setupDb(environment) + let app = try await TestSupport.setupApp(environment) return try await run { try await setup(app) @@ -44,3 +45,134 @@ func withApp(_ setup: (Application) async throws -> Void = { _ in }, func isRunningInCI() -> Bool { ProcessInfo.processInfo.environment.keys.contains("GITHUB_WORKFLOW") } + + +enum TestSupport { + + static func setupApp(_ environment: Environment, databasePort: Int? = nil) async throws -> Application { + let app = try await Application.make(environment) + try await configure(app, databasePort: databasePort) + + // Silence app logging + app.logger = .init(label: "noop") { _ in SwiftLogNoOpLogHandler() } + + return app + } + + + static func setupDb(_ environment: Environment, databasePort: Int? = nil) async throws { + await DotEnvFile.load(for: environment, fileio: .init(threadPool: .singleton)) + + // Ensure DATABASE_HOST is from a restricted set db hostnames and nothing else. + // This is safeguard against accidental inheritance of setup in QueryPerformanceTests + // and to ensure the database resetting cannot impact any other network hosts. + let host = Environment.get("DATABASE_HOST") + let databasePort = databasePort ?? Environment.get("DATABASE_PORT").flatMap(Int.init)! + precondition(["localhost", "postgres", "host.docker.internal"].contains(host), + "DATABASE_HOST must be a local db, was: \(host)") + + let testDbName = Environment.get("DATABASE_NAME")! + let snapshotName = testDbName + "_snapshot" + + // Create initial db snapshot on first run + try await snapshotCreated.withValue { snapshotCreated in + if !snapshotCreated { + try await createSchema(environment, databaseName: testDbName, databasePort: databasePort) + try await createSnapshot(original: testDbName, snapshot: snapshotName, databasePort: databasePort, environment: environment) + snapshotCreated = true + } + } + + try await restoreSnapshot(original: testDbName, snapshot: snapshotName, databasePort: databasePort, environment: environment) + } + + + static func createSchema(_ environment: Environment, + databaseName: String, + databasePort: Int) async throws { + do { + try await withDatabase("postgres", port: databasePort, environment) { // Connect to `postgres` db in order to reset the test db + try await $0.query(PostgresQuery(unsafeSQL: "DROP DATABASE IF EXISTS \(databaseName) WITH (FORCE)")) + try await $0.query(PostgresQuery(unsafeSQL: "CREATE DATABASE \(databaseName)")) + } + + do { // Use autoMigrate to spin up the schema + let app = try await Application.make(environment) + app.logger = .init(label: "noop") { _ in SwiftLogNoOpLogHandler() } + try await configure(app, databasePort: databasePort) + try await app.autoMigrate() + try await app.asyncShutdown() + } + } catch { + print("Create schema failed with error: ", String(reflecting: error)) + throw error + } + } + + + static func createSnapshot(original: String, + snapshot: String, + databasePort: Int, + environment: Environment) async throws { + do { + try await withDatabase("postgres", port: databasePort, environment) { client in + try await client.query(PostgresQuery(unsafeSQL: "DROP DATABASE IF EXISTS \(snapshot) WITH (FORCE)")) + try await client.query(PostgresQuery(unsafeSQL: "CREATE DATABASE \(snapshot) TEMPLATE \(original)")) + } + } catch { + print("Create snapshot failed with error: ", String(reflecting: error)) + throw error + } + } + + + static func restoreSnapshot(original: String, + snapshot: String, + databasePort: Int, + environment: Environment) async throws { + // delete db and re-create from snapshot + do { + try await withDatabase("postgres", port: databasePort, environment) { client in + try await client.query(PostgresQuery(unsafeSQL: "DROP DATABASE IF EXISTS \(original) WITH (FORCE)")) + try await client.query(PostgresQuery(unsafeSQL: "CREATE DATABASE \(original) TEMPLATE \(snapshot)")) + } + } catch { + print("Restore snapshot failed with error: ", String(reflecting: error)) + throw error + } + } + + + static let snapshotCreated = ActorIsolated(false) + +} + + +private func connect(to databaseName: String, + port: Int, + _ environment: Environment) async throws -> PostgresClient { + await DotEnvFile.load(for: environment, fileio: .init(threadPool: .singleton)) + let host = Environment.get("DATABASE_HOST")! + let username = Environment.get("DATABASE_USERNAME")! + let password = Environment.get("DATABASE_PASSWORD")! + + let config = PostgresClient.Configuration(host: host, port: port, username: username, password: password, database: databaseName, tls: .disable) + + return .init(configuration: config) +} + + +private func withDatabase(_ databaseName: String, + port: Int, + _ environment: Environment, + _ query: @escaping (PostgresClient) async throws -> Void) async throws { + let client = try await connect(to: databaseName, port: port, environment) + try await withThrowingTaskGroup(of: Void.self) { taskGroup in + taskGroup.addTask { await client.run() } + + try await query(client) + + taskGroup.cancelAll() + } +} + diff --git a/Tests/AppTests/KeywordControllerTests.swift b/Tests/AppTests/KeywordControllerTests.swift index d3e8e9522..90f194961 100644 --- a/Tests/AppTests/KeywordControllerTests.swift +++ b/Tests/AppTests/KeywordControllerTests.swift @@ -16,7 +16,7 @@ import Dependencies import Testing -import Vapor +import XCTVapor @Suite struct KeywordControllerTests { diff --git a/Tests/AppTests/RoutesTests.swift b/Tests/AppTests/RoutesTests.swift index cbcd6b944..10e39a66c 100644 --- a/Tests/AppTests/RoutesTests.swift +++ b/Tests/AppTests/RoutesTests.swift @@ -75,22 +75,6 @@ import Testing } } - @Test func openapi() async throws { - try await withApp { app in - try await app.test(.GET, "openapi/openapi.json") { res async throws in - #expect(res.status == .ok) - struct Response: Codable, Equatable { - var info: Info - struct Info: Codable, Equatable { - var title: String - } - } - let decoded = try JSONDecoder().decode(Response.self, from: res.body) - #expect(decoded == Response(info: .init(title: "Swift Package Index API"))) - } - } - } - @Test func maintenanceMessage() async throws { try await withDependencies { $0.environment.dbId = { nil } diff --git a/Tests/AppTests/SnapshotTestCase.swift b/Tests/AppTests/SnapshotTestCase.swift deleted file mode 100644 index 2ec4732c1..000000000 --- a/Tests/AppTests/SnapshotTestCase.swift +++ /dev/null @@ -1,40 +0,0 @@ -// 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. - -@testable import App - -import Foundation -import SnapshotTesting -import Dependencies - - -@available(*, deprecated) -class SnapshotTestCase: AppTestCase { - - override func setUpWithError() throws { - try super.setUpWithError() - } - - override func invokeTest() { - // To force a re-record of all snapshots, use `record: .all` rather than `record: .missing`. - withSnapshotTesting(record: .missing, diffTool: .ksdiff) { - withDependencies { - $0.date.now = .t0 - } operation: { - super.invokeTest() - } - } - } - -}