From cb12ff7b9f334b4ffcc1cb99cca16c590c4ac02b Mon Sep 17 00:00:00 2001 From: "Sven A. Schmidt" Date: Wed, 9 Oct 2024 15:07:02 +0200 Subject: [PATCH] Add CustomCollection.reconcile --- Sources/App/Models/CustomCollection.swift | 31 ++++++++++++ Tests/AppTests/CustomCollectionTests.swift | 55 ++++++++++++++++++++++ 2 files changed, 86 insertions(+) diff --git a/Sources/App/Models/CustomCollection.swift b/Sources/App/Models/CustomCollection.swift index a57cd3d22..2c1d6fe64 100644 --- a/Sources/App/Models/CustomCollection.swift +++ b/Sources/App/Models/CustomCollection.swift @@ -64,3 +64,34 @@ final class CustomCollection: @unchecked Sendable, Model, Content { } } + + +extension CustomCollection { + func reconcile(on database: Database, packageURLs: [URL]) async throws { + let incoming: [Package.Id: Package] = .init( + packages: try await Package.query(on: database) + .filter(by: packageURLs) + .all() + ) + try await $packages.load(on: database) + let existing: [Package.Id: Package] = .init(packages: packages) + let newIDs = Set(incoming.keys).subtracting(Set(existing.keys)) + try await $packages.attach(incoming[newIDs], on: database) + let removedIDs = Set(existing.keys).subtracting(Set(incoming.keys)) + try await $packages.detach(existing[removedIDs], on: database) + } +} + + +private extension [Package.Id: Package] { + init(packages: [Package]) { + self.init( + packages.compactMap({ pkg in pkg.id.map({ ($0, pkg) }) }), + uniquingKeysWith: { (first, second) in first } + ) + } + + subscript(ids: some Collection) -> [Package] { + Array(ids.compactMap { self[$0] }) + } +} diff --git a/Tests/AppTests/CustomCollectionTests.swift b/Tests/AppTests/CustomCollectionTests.swift index 65b913e91..26dc3502e 100644 --- a/Tests/AppTests/CustomCollectionTests.swift +++ b/Tests/AppTests/CustomCollectionTests.swift @@ -200,4 +200,59 @@ 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, 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://a")).save(on: app.db) + try await Package(id: .id2, url: URL("https://b")).save(on: app.db) + + do { // Initial set of URLs + // MUT + try await collection.reconcile(on: app.db, packageURLs: [URL("https://a")]) + + do { // validate + let count = try await CustomCollectionPackage.query(on: app.db).count() + 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), ["https://a"]) + } + } + + do { // Add more URLs + // MUT + try await collection.reconcile(on: app.db, packageURLs: [ + URL("https://a"), + URL("https://b") + ]) + + do { // validate + let count = try await CustomCollectionPackage.query(on: app.db).count() + XCTAssertEqual(count, 2) + let collection = try await CustomCollection.find(.id0, on: app.db).unwrap() + try await collection.$packages.load(on: app.db) + XCTAssertEqual(collection.packages.map(\.url).sorted(), [ + "https://a", + "https://b" + ]) + } + } + + do { // Remove URLs + // MUT + try await collection.reconcile(on: app.db, packageURLs: [ + URL("https://b") + ]) + + do { // validate + let count = try await CustomCollectionPackage.query(on: app.db).count() + 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), ["https://b"]) + } + } + } + }