diff --git a/Sources/App/Controllers/CustomCollectionsController.swift b/Sources/App/Controllers/CustomCollectionsController.swift index 91e1fc0ca..a1d0e8e76 100644 --- a/Sources/App/Controllers/CustomCollectionsController.swift +++ b/Sources/App/Controllers/CustomCollectionsController.swift @@ -19,7 +19,7 @@ import Vapor enum CustomCollectionsController { - static func query(on database: Database, name: String, page: Int, pageSize: Int) async throws -> Page> { + static func query(on database: Database, key: String, page: Int, pageSize: Int) async throws -> Page> { try await Joined3 .query(on: database, version: .defaultBranch) .join(CustomCollectionPackage.self, on: \Package.$id == \CustomCollectionPackage.$package.$id) @@ -30,7 +30,7 @@ enum CustomCollectionsController { .field(Repository.self, \.$stars) .field(Repository.self, \.$summary) .field(Version.self, \.$packageName) - .filter(CustomCollection.self, \.$name == name) + .filter(CustomCollection.self, \.$key == key) .sort(Repository.self, \.$name) .page(page, size: pageSize) } @@ -56,11 +56,13 @@ enum CustomCollectionsController { @Sendable static func show(req: Request) async throws -> HTML { - guard let name = req.parameters.get("name") else { + guard let key = req.parameters.get("key") else { throw Abort(.notFound) } let query = try req.query.decode(Query.self) - let page = try await Self.query(on: req.db, name: name, page: query.page, pageSize: query.pageSize) + let collection = try await CustomCollection.find(on: req.db, key: key) + .unwrap(or: Abort(.notFound)) + let page = try await Self.query(on: req.db, key: key, page: query.page, pageSize: query.pageSize) guard !page.results.isEmpty else { throw Abort(.notFound) @@ -69,7 +71,8 @@ enum CustomCollectionsController { let packageInfo = page.results.compactMap(PackageInfo.init(package:)) let model = CustomCollectionShow.Model( - name: name, + key: collection.key, + name: collection.name, packages: packageInfo, page: query.page, hasMoreResults: page.hasMoreResults diff --git a/Sources/App/Controllers/PackageCollectionController.swift b/Sources/App/Controllers/PackageCollectionController.swift index 171f4e584..867d38226 100644 --- a/Sources/App/Controllers/PackageCollectionController.swift +++ b/Sources/App/Controllers/PackageCollectionController.swift @@ -32,12 +32,14 @@ enum PackageCollectionController { filterBy: .author(owner), authorName: "\(owner) via the Swift Package Index" ) - case let .custom(name): + case let .custom(key): + let collection = try await CustomCollection.find(on: req.db, key: key) + .unwrap(or: Abort(.notFound)) return try await SignedCollection.generate( db: req.db, - filterBy: .customCollection(name), + filterBy: .customCollection(key), authorName: "Swift Package Index", - collectionName: name, + collectionName: collection.name, overview: "A custom package collection generated by the Swift Package Index" ) } @@ -53,7 +55,7 @@ enum PackageCollectionController { static func getCollectionType(req: Request) -> CollectionType? { if let owner = req.parameters.get("owner") { return .author(owner) } - if let name = req.parameters.get("name") { return .custom(name) } + if let key = req.parameters.get("key") { return .custom(key) } return nil } } diff --git a/Sources/App/Core/PackageCollection+VersionResult.swift b/Sources/App/Core/PackageCollection+VersionResult.swift index ac3c69e9d..f8078fbd9 100644 --- a/Sources/App/Core/PackageCollection+VersionResult.swift +++ b/Sources/App/Core/PackageCollection+VersionResult.swift @@ -55,11 +55,11 @@ extension PackageCollection.VersionResult { switch filter { case let .author(owner): query.filter(Repository.self, \Repository.$owner, .custom("ilike"), owner) - case let .customCollection(name): + case let .customCollection(key): query .join(CustomCollectionPackage.self, on: \Package.$id == \CustomCollectionPackage.$package.$id) .join(CustomCollection.self, on: \CustomCollection.$id == \CustomCollectionPackage.$customCollection.$id) - .filter(CustomCollection.self, \.$name == name) + .filter(CustomCollection.self, \.$key == key) case let .urls(packageURLs): query.filter(App.Package.self, \.$url ~~ packageURLs) } diff --git a/Sources/App/Core/SiteURL.swift b/Sources/App/Core/SiteURL.swift index e71a5a46a..86c48fc6a 100644 --- a/Sources/App/Core/SiteURL.swift +++ b/Sources/App/Core/SiteURL.swift @@ -113,7 +113,7 @@ enum SiteURL: Resourceable, Sendable { case blogPost(_ slug: Parameter) case buildMonitor case builds(_ id: Parameter) - case collections(_ name: Parameter) + case collections(_ key: Parameter) case docs(Docs) case faq case home @@ -122,7 +122,7 @@ enum SiteURL: Resourceable, Sendable { case keywords(_ keyword: Parameter) case package(_ owner: Parameter, _ repository: Parameter, PackagePathComponents?) case packageCollectionAuthor(_ owner: Parameter) - case packageCollectionCustom(_ name: Parameter) + case packageCollectionCustom(_ key: Parameter) case packageCollections case privacy case readyForSwift6 @@ -171,11 +171,11 @@ enum SiteURL: Resourceable, Sendable { case .buildMonitor: return "build-monitor" - case let .collections(.value(name)): - return "collections/\(name.urlPathEncoded)" + case let .collections(.value(key)): + return "collections/\(key.urlPathEncoded)" case .collections(.key): - fatalError("path must not be called with a name parameter") + fatalError("invalid path: \(self)") case let .docs(next): return "docs/\(next.path)" @@ -215,8 +215,8 @@ enum SiteURL: Resourceable, Sendable { case .packageCollectionAuthor(.key): fatalError("invalid path: \(self)") - case let .packageCollectionCustom(.value(name)): - return "collections/\(name.urlPathEncoded)/collection.json" + case let .packageCollectionCustom(.value(key)): + return "collections/\(key.urlPathEncoded)/collection.json" case .packageCollectionCustom(.key): fatalError("invalid path: \(self)") @@ -298,7 +298,7 @@ enum SiteURL: Resourceable, Sendable { fatalError("pathComponents must not be called with a value parameter") case .collections(.key): - return ["collections", ":name"] + return ["collections", ":key"] case .collections(.value): fatalError("pathComponents must not be called with a value parameter") @@ -325,7 +325,7 @@ enum SiteURL: Resourceable, Sendable { fatalError("pathComponents must not be called with a value parameter") case .packageCollectionCustom(.key): - return ["collections", ":name", "collection.json"] + return ["collections", ":key", "collection.json"] case .packageCollectionCustom(.value): fatalError("pathComponents must not be called with a value parameter") diff --git a/Sources/App/Migrations/083/UpdateCustomCollectionAddKey.swift b/Sources/App/Migrations/083/UpdateCustomCollectionAddKey.swift new file mode 100644 index 000000000..cff7fc1da --- /dev/null +++ b/Sources/App/Migrations/083/UpdateCustomCollectionAddKey.swift @@ -0,0 +1,36 @@ +// 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 Fluent + +struct UpdateCustomCollectionAddKey: AsyncMigration { + func prepare(on database: Database) async throws { + // We need to clear the custom_collections table, because the existing rows don't have + // `key` values and their existence prevents the update. + // They will be automatically regenerated by the next reconciliation run. + try await CustomCollection.query(on: database).delete() + try await database.schema("custom_collections") + .field("key", .string, .required) + .unique(on: "key") + .update() + } + + func revert(on database: Database) async throws { + try await database.schema("custom_collections") + .deleteField("key") + // The unique key constraint is automatically deleted when the field is deleted, + // no explicit clean up is required. + .update() + } +} diff --git a/Sources/App/Models/CustomCollection.swift b/Sources/App/Models/CustomCollection.swift index 1f9b289c1..54711b4c7 100644 --- a/Sources/App/Models/CustomCollection.swift +++ b/Sources/App/Models/CustomCollection.swift @@ -35,6 +35,9 @@ final class CustomCollection: @unchecked Sendable, Model, Content { // data fields + @Field(key: "key") + var key: String + @Field(key: "name") var name: String @@ -58,6 +61,7 @@ final class CustomCollection: @unchecked Sendable, Model, Content { self.id = id self.createdAt = createdAt self.updatedAt = updatedAt + self.key = details.key self.name = details.name self.description = details.description self.badge = details.badge @@ -68,16 +72,26 @@ final class CustomCollection: @unchecked Sendable, Model, Content { extension CustomCollection { struct Details: Codable, Equatable { + var key: String var name: String var description: String? var badge: String? var url: URL } + static func find(on database: Database, key: String) async throws -> CustomCollection? { + try await CustomCollection.query(on: database) + .filter(\.$key == key) + .first() + } + static func findOrCreate(on database: Database, _ details: Details) async throws -> CustomCollection { - if let collection = try await CustomCollection.query(on: database) - .filter(\.$url == details.url) - .first() { + if let collection = try await CustomCollection.find(on: database, key: details.key) { + if collection.details != details { + // Update the collection if any of the details have changed + collection.details = details + try await collection.update(on: database) + } return collection } else { let collection = CustomCollection(details) @@ -101,7 +115,20 @@ extension CustomCollection { } var details: Details { - .init(name: name, description: description, badge: badge, url: url) + get { + .init(key: key, + name: name, + description: description, + badge: badge, + url: url) + } + set { + key = newValue.key + name = newValue.name + description = newValue.description + badge = newValue.badge + url = newValue.url + } } } diff --git a/Sources/App/Views/CustomCollection/CustomCollectionShow+Model.swift b/Sources/App/Views/CustomCollection/CustomCollectionShow+Model.swift index a4334ea20..751bac799 100644 --- a/Sources/App/Views/CustomCollection/CustomCollectionShow+Model.swift +++ b/Sources/App/Views/CustomCollection/CustomCollectionShow+Model.swift @@ -14,15 +14,18 @@ extension CustomCollectionShow { struct Model { + var key: String var name: String var packages: [PackageInfo] var page: Int var hasMoreResults: Bool - internal init(name: String, + internal init(key: String, + name: String, packages: [PackageInfo], page: Int, hasMoreResults: Bool) { + self.key = key self.name = name self.packages = packages self.page = page diff --git a/Sources/App/Views/CustomCollection/CustomCollectionShow+View.swift b/Sources/App/Views/CustomCollection/CustomCollectionShow+View.swift index bbe6f09ee..00eb2c673 100644 --- a/Sources/App/Views/CustomCollection/CustomCollectionShow+View.swift +++ b/Sources/App/Views/CustomCollection/CustomCollectionShow+View.swift @@ -58,7 +58,7 @@ enum CustomCollectionShow { ), .copyableInputForm(buttonName: "Copy Package Collection URL", eventName: "Copy Package Collection URL Button", - valueToCopy: SiteURL.packageCollectionCustom(.value(model.name)).absoluteURL()), + valueToCopy: SiteURL.packageCollectionCustom(.value(model.key)).absoluteURL()), .hr(.class("minor")), .ul( .id("package-list"), diff --git a/Sources/App/Views/PackageController/GetRoute.Model+ext.swift b/Sources/App/Views/PackageController/GetRoute.Model+ext.swift index ea59e2b17..2c8ee3aea 100644 --- a/Sources/App/Views/PackageController/GetRoute.Model+ext.swift +++ b/Sources/App/Views/PackageController/GetRoute.Model+ext.swift @@ -404,7 +404,7 @@ extension API.PackageController.GetRoute.Model { .class("custom-collections"), .forEach(customCollections, { collection in .a( - .href(SiteURL.collections(.value(collection.name)).relativeURL()), + .href(SiteURL.collections(.value(collection.key)).relativeURL()), .text("\(collection.name)") ) }) diff --git a/Sources/App/configure.swift b/Sources/App/configure.swift index 173f3d935..9bbd93778 100644 --- a/Sources/App/configure.swift +++ b/Sources/App/configure.swift @@ -347,6 +347,9 @@ public func configure(_ app: Application) async throws -> String { do { // Migration 082 - Add `has_spi_badge` to `repositories` app.migrations.add(UpdateRepositoryAddHasSPIBadge()) } + do { // Migration 083 - Add `key` and unique constraint to `custom_collections` + app.migrations.add(UpdateCustomCollectionAddKey()) + } app.asyncCommands.use(Analyze.Command(), as: "analyze") app.asyncCommands.use(CreateRestfileCommand(), as: "create-restfile") diff --git a/Tests/AppTests/CustomCollectionTests.swift b/Tests/AppTests/CustomCollectionTests.swift index efd0d1ed9..b2b58a24f 100644 --- a/Tests/AppTests/CustomCollectionTests.swift +++ b/Tests/AppTests/CustomCollectionTests.swift @@ -23,17 +23,33 @@ class CustomCollectionTests: AppTestCase { func test_CustomCollection_save() async throws { // MUT - try await CustomCollection(id: .id0, .init(name: "List", url: "https://github.com/foo/bar/list.json")) + try await CustomCollection(id: .id0, .init(key: "list", + name: "List", + url: "https://github.com/foo/bar/list.json")) .save(on: app.db) do { // validate let collection = try await CustomCollection.find(.id0, on: app.db).unwrap() + XCTAssertEqual(collection.key, "list") XCTAssertEqual(collection.name, "List") XCTAssertEqual(collection.url, "https://github.com/foo/bar/list.json") } + do { // ensure key is unique + try await CustomCollection(.init(key: "list", + name: "List 2", + url: "https://github.com/foo/bar/other-list.json")) + .save(on: app.db) + XCTFail("Expected failure") + } catch { + let msg = String(reflecting: error) + XCTAssert(msg.contains(#"duplicate key value violates unique constraint "uq:custom_collections.key""#), + "was: \(msg)") + } do { // ensure name is unique - try await CustomCollection(.init(name: "List", url: "https://github.com/foo/bar/other-list.json")) + try await CustomCollection(.init(key: "list-2", + name: "List", + url: "https://github.com/foo/bar/other-list.json")) .save(on: app.db) XCTFail("Expected failure") } catch { @@ -43,7 +59,9 @@ class CustomCollectionTests: AppTestCase { } do { // ensure url is unique - try await CustomCollection(.init(name: "List 2", url: "https://github.com/foo/bar/list.json")) + try await CustomCollection(.init(key: "list-2", + name: "List 2", + url: "https://github.com/foo/bar/list.json")) .save(on: app.db) XCTFail("Expected failure") } catch { @@ -56,38 +74,65 @@ class CustomCollectionTests: AppTestCase { func test_CustomCollection_findOrCreate() async throws { do { // initial call creates collection // MUT - let res = try await CustomCollection.findOrCreate(on: app.db, .init(name: "List", url: "url")) + let res = try await CustomCollection.findOrCreate(on: app.db, .init(key: "list", + name: "List", + url: "url")) // validate + XCTAssertEqual(res.key, "list") XCTAssertEqual(res.name, "List") XCTAssertEqual(res.url, "url") let c = try await CustomCollection.query(on: app.db).all() XCTAssertEqual(c.count, 1) + XCTAssertEqual(c.first?.key, "list") XCTAssertEqual(c.first?.name, "List") XCTAssertEqual(c.first?.url, "url") } do { // re-running is idempotent // MUT - let res = try await CustomCollection.findOrCreate(on: app.db, .init(name: "List", url: "url")) + let res = try await CustomCollection.findOrCreate(on: app.db, .init(key: "list", + name: "List", + url: "url")) // validate + XCTAssertEqual(res.key, "list") XCTAssertEqual(res.name, "List") XCTAssertEqual(res.url, "url") let c = try await CustomCollection.query(on: app.db).all() XCTAssertEqual(c.count, 1) + XCTAssertEqual(c.first?.key, "list") XCTAssertEqual(c.first?.name, "List") XCTAssertEqual(c.first?.url, "url") } + + do { // a record is updated if data has changed + // MUT + let res = try await CustomCollection.findOrCreate(on: app.db, .init(key: "list", + name: "New name", + url: "new-url")) + // validate + XCTAssertEqual(res.key, "list") + XCTAssertEqual(res.name, "New name") + XCTAssertEqual(res.url, "new-url") + + let c = try await CustomCollection.query(on: app.db).all() + XCTAssertEqual(c.count, 1) + XCTAssertEqual(c.first?.key, "list") + XCTAssertEqual(c.first?.name, "New name") + XCTAssertEqual(c.first?.url, "new-url") + } } func test_CustomCollectionPackage_attach() async throws { // setup let pkg = Package(id: .id0, url: "1".asGithubUrl.url) try await pkg.save(on: app.db) - let collection = CustomCollection(id: .id1, .init(name: "List", url: "https://github.com/foo/bar/list.json")) + let collection = CustomCollection(id: .id1, .init(key: "list", + name: "List", + url: "https://github.com/foo/bar/list.json")) try await collection.save(on: app.db) // MUT @@ -102,7 +147,7 @@ class CustomCollectionTests: AppTestCase { XCTAssertEqual(pivot.package.url, "1".asGithubUrl) try await pivot.$customCollection.load(on: app.db) XCTAssertEqual(pivot.customCollection.id, .id1) - XCTAssertEqual(pivot.customCollection.name, "List") + XCTAssertEqual(pivot.customCollection.key, "list") } do { // ensure package is unique per list @@ -118,7 +163,9 @@ class CustomCollectionTests: AppTestCase { // setup let pkg = Package(id: .id0, url: "1".asGithubUrl.url) try await pkg.save(on: app.db) - let collection = CustomCollection(id: .id1, .init(name: "List", url: "https://github.com/foo/bar/list.json")) + let collection = CustomCollection(id: .id1, .init(key: "list", + name: "List", + url: "https://github.com/foo/bar/list.json")) try await collection.save(on: app.db) try await collection.$packages.attach(pkg, on: app.db) @@ -143,7 +190,9 @@ class CustomCollectionTests: AppTestCase { try await p1.save(on: app.db) let p2 = Package(id: .id1, url: "2".asGithubUrl.url) try await p2.save(on: app.db) - let collection = CustomCollection(id: .id2, .init(name: "List", url: "https://github.com/foo/bar/list.json")) + let collection = CustomCollection(id: .id2, .init(key: "list", + name: "List", + url: "https://github.com/foo/bar/list.json")) try await collection.save(on: app.db) try await collection.$packages.attach([p1, p2], on: app.db) @@ -161,12 +210,16 @@ class CustomCollectionTests: AppTestCase { let p1 = Package(id: .id0, url: "1".asGithubUrl.url) try await p1.save(on: app.db) do { - let collection = CustomCollection(id: .id1, .init(name: "List 1", url: "https://github.com/foo/bar/list-1.json")) + let collection = CustomCollection(id: .id1, .init(key: "list-1", + name: "List 1", + url: "https://github.com/foo/bar/list-1.json")) try await collection.save(on: app.db) try await collection.$packages.attach(p1, on: app.db) } do { - let collection = CustomCollection(id: .id2, .init(name: "List 2", url: "https://github.com/foo/bar/list-2.json")) + let collection = CustomCollection(id: .id2, .init(key: "list-2", + name: "List 2", + url: "https://github.com/foo/bar/list-2.json")) try await collection.save(on: app.db) try await collection.$packages.attach(p1, on: app.db) } @@ -182,7 +235,9 @@ class CustomCollectionTests: AppTestCase { // setup let pkg = Package(id: .id0, url: "1".asGithubUrl.url) try await pkg.save(on: app.db) - let collection = CustomCollection(id: .id1, .init(name: "List", url: "https://github.com/foo/bar/list.json")) + let collection = CustomCollection(id: .id1, .init(key: "list", + name: "List", + url: "https://github.com/foo/bar/list.json")) try await collection.save(on: app.db) try await collection.$packages.attach(pkg, on: app.db) do { @@ -220,7 +275,9 @@ class CustomCollectionTests: AppTestCase { // setup let pkg = Package(id: .id0, url: "1".asGithubUrl.url) try await pkg.save(on: app.db) - let collection = CustomCollection(id: .id1, .init(name: "List", url: "https://github.com/foo/bar/list.json")) + let collection = CustomCollection(id: .id1, .init(key: "list", + name: "List", + url: "https://github.com/foo/bar/list.json")) try await collection.save(on: app.db) try await collection.$packages.attach(pkg, on: app.db) do { @@ -256,7 +313,9 @@ class CustomCollectionTests: AppTestCase { func test_CustomCollection_reconcile() async throws { // Test reconciliation of a custom collection against a list of package URLs - let collection = CustomCollection(id: .id0, .init(name: "List", url: "https://github.com/foo/bar/list.json")) + let collection = CustomCollection(id: .id0, .init(key: "list", + name: "List", + url: "https://github.com/foo/bar/list.json")) try await collection.save(on: app.db) try await Package(id: .id1, url: URL("https://github.com/a.git")).save(on: app.db) try await Package(id: .id2, url: URL("https://github.com/b.git")).save(on: app.db) @@ -309,25 +368,24 @@ class CustomCollectionTests: AppTestCase { } } - func test_CustomCollection_reconcile_caseSensitive() async throws { + func test_CustomCollection_reconcile_caseInsensitive() async throws { // Test reconciliation with a case-insensitive matching URL - let collection = CustomCollection(id: .id0, .init(name: "List", url: "https://github.com/foo/bar/list.json")) + let collection = CustomCollection(id: .id0, .init(key: "list", + name: "List", + url: "https://github.com/foo/bar/list.json")) try await collection.save(on: app.db) - try await Package(id: .id1, url: URL("a")).save(on: app.db) + try await Package(id: .id1, url: URL("https://github.com/a.git")).save(on: app.db) // MUT - try await collection.reconcile(on: app.db, packageURLs: [URL("A")]) + try await collection.reconcile(on: app.db, packageURLs: [URL("https://github.com/A.git")]) do { // validate - // The package is not added to the custom collection, because it is not an - // exact match for the package URL. - // This is currently a limiting of the Fluent ~~ operator in the query - // filter(\.$url ~~ urls.map(\.absoluteString)) + // The package is added to the custom collection via case-insensitive match let count = try await CustomCollectionPackage.query(on: app.db).count() - XCTAssertEqual(count, 0) + XCTAssertEqual(count, 1) let collection = try await CustomCollection.find(.id0, on: app.db).unwrap() try await collection.$packages.load(on: app.db) - XCTAssertEqual(collection.packages.map(\.url), []) + XCTAssertEqual(collection.packages.map(\.url), ["https://github.com/a.git"]) } } diff --git a/Tests/AppTests/Mocks/CustomCollectionShow+mock.swift b/Tests/AppTests/Mocks/CustomCollectionShow+mock.swift index c42c7a9ae..7179ef43f 100644 --- a/Tests/AppTests/Mocks/CustomCollectionShow+mock.swift +++ b/Tests/AppTests/Mocks/CustomCollectionShow+mock.swift @@ -28,6 +28,9 @@ extension CustomCollectionShow.Model { stars: 4, lastActivityAt: .t0 ) } - return .init(name: "Some Collection", packages: packages, page: 1, hasMoreResults: false) + return .init(key: "custom-collection", + name: "Custom Collection", + packages: packages, page: 1, + hasMoreResults: false) } } diff --git a/Tests/AppTests/PackageCollectionControllerTests.swift b/Tests/AppTests/PackageCollectionControllerTests.swift index 09f31fb13..664664938 100644 --- a/Tests/AppTests/PackageCollectionControllerTests.swift +++ b/Tests/AppTests/PackageCollectionControllerTests.swift @@ -112,7 +112,9 @@ class PackageCollectionControllerTests: AppTestCase { licenseUrl: "https://foo/mit", owner: "foo", summary: "summary 1").create(on: app.db) - let collection = CustomCollection(id: .id2, .init(name: "Custom Collection", url: "https://github.com/foo/bar/list.json")) + let collection = CustomCollection(id: .id2, .init(key: "custom-collection", + name: "Custom Collection", + url: "https://github.com/foo/bar/list.json")) try await collection.save(on: app.db) try await collection.$packages.attach(p, on: app.db) @@ -120,7 +122,7 @@ class PackageCollectionControllerTests: AppTestCase { let encoder = self.encoder try await app.test( .GET, - "collections/Custom%20Collection/collection.json", + "collections/custom-collection/collection.json", afterResponse: { @MainActor res async throws in // validation XCTAssertEqual(res.status, .ok) diff --git a/Tests/AppTests/PackageCollectionTests.swift b/Tests/AppTests/PackageCollectionTests.swift index 31f789940..012852849 100644 --- a/Tests/AppTests/PackageCollectionTests.swift +++ b/Tests/AppTests/PackageCollectionTests.swift @@ -195,13 +195,15 @@ class PackageCollectionTests: AppTestCase { .save(on: app.db) return pkg } - let collection = CustomCollection(id: .id2, .init(name: "List", url: "https://github.com/foo/bar/list.json")) + let collection = CustomCollection(id: .id2, .init(key: "list", + name: "List", + url: "https://github.com/foo/bar/list.json")) try await collection.save(on: app.db) try await collection.$packages.attach([packages[0], packages[1]], on: app.db) // MUT let res = try await VersionResult.query(on: self.app.db, - filterBy: .customCollection("List")) + filterBy: .customCollection("list")) // validate selection (relationship loading is tested in test_query_filter_urls) XCTAssertEqual(res.map(\.version.packageName), diff --git a/Tests/AppTests/ReconcilerTests.swift b/Tests/AppTests/ReconcilerTests.swift index 2ea0dfc6a..ee8b537b4 100644 --- a/Tests/AppTests/ReconcilerTests.swift +++ b/Tests/AppTests/ReconcilerTests.swift @@ -143,7 +143,7 @@ class ReconcilerTests: AppTestCase { try await reconcileCustomCollection(client: app.client, database: app.db, fullPackageList: fullPackageList, - .init(name: "List", url: "url")) + .init(key: "list", name: "List", url: "url")) // validate let count = try await CustomCollection.query(on: app.db).count() @@ -161,7 +161,7 @@ class ReconcilerTests: AppTestCase { try await reconcileCustomCollection(client: app.client, database: app.db, fullPackageList: fullPackageList, - .init(name: "List", url: "url")) + .init(key: "list", name: "List", url: "url")) // validate let count = try await CustomCollection.query(on: app.db).count() @@ -181,7 +181,7 @@ class ReconcilerTests: AppTestCase { try await reconcileCustomCollection(client: app.client, database: app.db, fullPackageList: fullPackageList, - .init(name: "List", url: "url")) + .init(key: "list", name: "List", url: "url")) // validate let count = try await CustomCollection.query(on: app.db).count() @@ -207,7 +207,7 @@ class ReconcilerTests: AppTestCase { try await reconcileCustomCollection(client: app.client, database: app.db, fullPackageList: fullPackageList, - .init(name: "List", url: "url")) + .init(key: "list", name: "List", url: "url")) // validate let collection = try await CustomCollection.query(on: app.db).first().unwrap() @@ -233,7 +233,7 @@ class ReconcilerTests: AppTestCase { } } $0.packageListRepository.fetchCustomCollections = { @Sendable _ in - [.init(name: "List", url: "collectionURL")] + [.init(key: "list", name: "List", url: "collectionURL")] } } operation: { // MUT diff --git a/Tests/AppTests/SiteURLTests.swift b/Tests/AppTests/SiteURLTests.swift index 3f43ffadb..ac55fa533 100644 --- a/Tests/AppTests/SiteURLTests.swift +++ b/Tests/AppTests/SiteURLTests.swift @@ -170,7 +170,7 @@ class SiteURLTests: XCTestCase { func test_collections() throws { XCTAssertEqual(SiteURL.collections(.value("foo")).path, "collections/foo") - XCTAssertEqual(SiteURL.collections(.key).pathComponents.map(\.description), ["collections", ":name"]) + XCTAssertEqual(SiteURL.collections(.key).pathComponents.map(\.description), ["collections", ":key"]) } } diff --git a/Tests/AppTests/WebpageSnapshotTests.swift b/Tests/AppTests/WebpageSnapshotTests.swift index ff544f0f9..1801325c3 100644 --- a/Tests/AppTests/WebpageSnapshotTests.swift +++ b/Tests/AppTests/WebpageSnapshotTests.swift @@ -296,7 +296,9 @@ class WebpageSnapshotTests: SnapshotTestCase { func test_PackageShowView_customCollection() throws { var model = API.PackageController.GetRoute.Model.mock model.homepageUrl = "https://swiftpackageindex.com/" - model.customCollections = [.init(name: "Custom Collection", url: "https://github.com/foo/bar/list.json")] + model.customCollections = [.init(key: "custom-collection", + name: "Custom Collection", + url: "https://github.com/foo/bar/list.json")] let page = { PackageShow.View(path: "", model: model, packageSchema: .mock).document() } assertSnapshot(of: page, as: .html) diff --git a/Tests/AppTests/__Snapshots__/WebpageSnapshotTests/test_CustomCollectionShow.1.html b/Tests/AppTests/__Snapshots__/WebpageSnapshotTests/test_CustomCollectionShow.1.html index fc407c80d..cc119aa3a 100644 --- a/Tests/AppTests/__Snapshots__/WebpageSnapshotTests/test_CustomCollectionShow.1.html +++ b/Tests/AppTests/__Snapshots__/WebpageSnapshotTests/test_CustomCollectionShow.1.html @@ -7,12 +7,12 @@ - Packages for collection Some Collection – Swift Package Index - - - - - + Packages for collection Custom Collection – Swift Package Index + + + + + @@ -75,19 +75,19 @@

  • - Some Collection + Custom Collection
  • -

    Packages for collection “Some Collection”

    +

    Packages for collection “Custom Collection”

    These packages are available as a package collection, usable in Xcode 13 or the Swift Package Manager 5.5.

    - +

      diff --git a/Tests/AppTests/__Snapshots__/WebpageSnapshotTests/test_PackageShowView_customCollection.1.html b/Tests/AppTests/__Snapshots__/WebpageSnapshotTests/test_PackageShowView_customCollection.1.html index a988d08a4..41d1a5581 100644 --- a/Tests/AppTests/__Snapshots__/WebpageSnapshotTests/test_PackageShowView_customCollection.1.html +++ b/Tests/AppTests/__Snapshots__/WebpageSnapshotTests/test_PackageShowView_customCollection.1.html @@ -169,7 +169,7 @@

      When working with a Swift Package Manager manifest:

    • No plugins
    • 2 macros
    • - Custom Collection + Custom Collection