Skip to content

Commit 592ff8b

Browse files
committed
Add reconcileCustomCollection + test
1 parent a7dbc13 commit 592ff8b

File tree

4 files changed

+108
-30
lines changed

4 files changed

+108
-30
lines changed

Sources/App/Commands/Reconcile.swift

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15+
import Dependencies
1516
import Fluent
1617
import Vapor
1718

@@ -48,16 +49,24 @@ struct ReconcileCommand: AsyncCommand {
4849
func reconcile(client: Client, database: Database) async throws {
4950
let start = DispatchTime.now().uptimeNanoseconds
5051
defer { AppMetrics.reconcileDurationSeconds?.time(since: start) }
51-
async let sourcePackageList = try Current.fetchPackageList(client)
52-
async let sourcePackageDenyList = try Current.fetchPackageDenyList(client)
53-
async let currentList = try fetchCurrentPackageList(database)
5452

55-
let packageList = processPackageDenyList(packageList: try await sourcePackageList,
56-
denyList: try await sourcePackageDenyList)
53+
do { // reconcile main package list
54+
async let sourcePackageList = try Current.fetchPackageList(client)
55+
async let sourcePackageDenyList = try Current.fetchPackageDenyList(client)
56+
async let currentList = try fetchCurrentPackageList(database)
5757

58-
try await reconcileLists(db: database,
59-
source: packageList,
60-
target: currentList)
58+
let packageList = processPackageDenyList(packageList: try await sourcePackageList,
59+
denyList: try await sourcePackageDenyList)
60+
61+
try await reconcileLists(db: database,
62+
source: packageList,
63+
target: currentList)
64+
}
65+
66+
do { // reconcile custom package collections
67+
// - fetch custom-package-collections.json
68+
// - for each entry: reconcileCustomCollection
69+
}
6170
}
6271

6372

@@ -115,6 +124,7 @@ func reconcileLists(db: Database, source: [URL], target: [URL]) async throws {
115124
}
116125
}
117126

127+
118128
func processPackageDenyList(packageList: [URL], denyList: [URL]) -> [URL] {
119129
// Note: If the implementation of this function ever changes, the `RemoveDenyList`
120130
// command in the Validator will also need updating to match.
@@ -140,3 +150,15 @@ func processPackageDenyList(packageList: [URL], denyList: [URL]) -> [URL] {
140150
.subtracting(Set(denyList.map(CaseInsensitiveURL.init)))
141151
).map(\.url)
142152
}
153+
154+
155+
func reconcileCustomCollection(client: Client, database: Database, fullPackageList: [URL], _ dto: CustomCollection.DTO) async throws {
156+
let collection = try await CustomCollection.findOrCreate(on: database, dto)
157+
158+
// Limit incoming URLs to 50 since this is input outside of our control
159+
@Dependency(\.packageListRepository) var packageListRepository
160+
let incomingURLs = try await packageListRepository.fetchCustomCollection(client: client, url: collection.url)
161+
.prefix(50)
162+
163+
try await collection.reconcile(on: database, packageURLs: incomingURLs)
164+
}

Sources/App/Models/CustomCollection.swift

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -45,24 +45,14 @@ final class CustomCollection: @unchecked Sendable, Model, Content {
4545
var badge: String?
4646

4747
@Field(key: "url")
48-
var url: String
48+
var url: URL
4949

5050
// reference fields
5151
@Siblings(through: CustomCollectionPackage.self, from: \.$customCollection, to: \.$package)
5252
var packages: [Package]
5353

5454
init() { }
5555

56-
init(id: Id? = nil, createdAt: Date? = nil, updatedAt: Date? = nil, name: String, description: String? = nil, badge: String? = nil, url: String) {
57-
self.id = id
58-
self.createdAt = createdAt
59-
self.updatedAt = updatedAt
60-
self.name = name
61-
self.description = description
62-
self.badge = badge
63-
self.url = url
64-
}
65-
6656
init(id: Id? = nil, createdAt: Date? = nil, updatedAt: Date? = nil, _ dto: DTO) {
6757
self.id = id
6858
self.createdAt = createdAt
@@ -80,7 +70,7 @@ extension CustomCollection {
8070
var name: String
8171
var description: String?
8272
var badge: String?
83-
var url: String
73+
var url: URL
8474
}
8575

8676
static func findOrCreate(on database: Database, _ dto: DTO) async throws -> CustomCollection {

Tests/AppTests/CustomCollectionTests.swift

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ class CustomCollectionTests: AppTestCase {
2323

2424
func test_CustomCollection_save() async throws {
2525
// MUT
26-
try await CustomCollection(id: .id0, name: "List", url: "https://github.com/foo/bar/list.json")
26+
try await CustomCollection(id: .id0, .init(name: "List", url: "https://github.com/foo/bar/list.json"))
2727
.save(on: app.db)
2828

2929
do { // validate
@@ -33,7 +33,7 @@ class CustomCollectionTests: AppTestCase {
3333
}
3434

3535
do { // ensure name is unique
36-
try await CustomCollection(name: "List", url: "https://github.com/foo/bar/other-list.json")
36+
try await CustomCollection(.init(name: "List", url: "https://github.com/foo/bar/other-list.json"))
3737
.save(on: app.db)
3838
XCTFail("Expected failure")
3939
} catch {
@@ -43,7 +43,7 @@ class CustomCollectionTests: AppTestCase {
4343
}
4444

4545
do { // ensure url is unique
46-
try await CustomCollection(name: "List 2", url: "https://github.com/foo/bar/list.json")
46+
try await CustomCollection(.init(name: "List 2", url: "https://github.com/foo/bar/list.json"))
4747
.save(on: app.db)
4848
XCTFail("Expected failure")
4949
} catch {
@@ -87,7 +87,7 @@ class CustomCollectionTests: AppTestCase {
8787
// setup
8888
let pkg = Package(id: .id0, url: "1".asGithubUrl.url)
8989
try await pkg.save(on: app.db)
90-
let collection = CustomCollection(id: .id1, name: "List", url: "https://github.com/foo/bar/list.json")
90+
let collection = CustomCollection(id: .id1, .init(name: "List", url: "https://github.com/foo/bar/list.json"))
9191
try await collection.save(on: app.db)
9292

9393
// MUT
@@ -118,7 +118,7 @@ class CustomCollectionTests: AppTestCase {
118118
// setup
119119
let pkg = Package(id: .id0, url: "1".asGithubUrl.url)
120120
try await pkg.save(on: app.db)
121-
let collection = CustomCollection(id: .id1, name: "List", url: "https://github.com/foo/bar/list.json")
121+
let collection = CustomCollection(id: .id1, .init(name: "List", url: "https://github.com/foo/bar/list.json"))
122122
try await collection.save(on: app.db)
123123
try await collection.$packages.attach(pkg, on: app.db)
124124

@@ -142,7 +142,7 @@ class CustomCollectionTests: AppTestCase {
142142
try await p1.save(on: app.db)
143143
let p2 = Package(id: .id1, url: "2".asGithubUrl.url)
144144
try await p2.save(on: app.db)
145-
let collection = CustomCollection(id: .id2, name: "List", url: "https://github.com/foo/bar/list.json")
145+
let collection = CustomCollection(id: .id2, .init(name: "List", url: "https://github.com/foo/bar/list.json"))
146146
try await collection.save(on: app.db)
147147
try await collection.$packages.attach([p1, p2], on: app.db)
148148

@@ -158,7 +158,7 @@ class CustomCollectionTests: AppTestCase {
158158
// setup
159159
let pkg = Package(id: .id0, url: "1".asGithubUrl.url)
160160
try await pkg.save(on: app.db)
161-
let collection = CustomCollection(id: .id1, name: "List", url: "https://github.com/foo/bar/list.json")
161+
let collection = CustomCollection(id: .id1, .init(name: "List", url: "https://github.com/foo/bar/list.json"))
162162
try await collection.save(on: app.db)
163163
try await collection.$packages.attach(pkg, on: app.db)
164164
do {
@@ -196,7 +196,7 @@ class CustomCollectionTests: AppTestCase {
196196
// setup
197197
let pkg = Package(id: .id0, url: "1".asGithubUrl.url)
198198
try await pkg.save(on: app.db)
199-
let collection = CustomCollection(id: .id1, name: "List", url: "https://github.com/foo/bar/list.json")
199+
let collection = CustomCollection(id: .id1, .init(name: "List", url: "https://github.com/foo/bar/list.json"))
200200
try await collection.save(on: app.db)
201201
try await collection.$packages.attach(pkg, on: app.db)
202202
do {
@@ -232,7 +232,7 @@ class CustomCollectionTests: AppTestCase {
232232

233233
func test_CustomCollection_reconcile() async throws {
234234
// Test reconciliation of a custom collection against a list of package URLs
235-
let collection = CustomCollection(id: .id0, name: "List", url: "https://github.com/foo/bar/list.json")
235+
let collection = CustomCollection(id: .id0, .init(name: "List", url: "https://github.com/foo/bar/list.json"))
236236
try await collection.save(on: app.db)
237237
try await Package(id: .id1, url: URL("https://a")).save(on: app.db)
238238
try await Package(id: .id2, url: URL("https://b")).save(on: app.db)

Tests/AppTests/ReconcilerTests.swift

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,12 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15+
import XCTest
16+
1517
@testable import App
1618

19+
import Dependencies
1720
import Vapor
18-
import XCTest
1921

2022

2123
class ReconcilerTests: AppTestCase {
@@ -114,4 +116,68 @@ class ReconcilerTests: AppTestCase {
114116
let packages = try await Package.query(on: app.db).all()
115117
XCTAssertEqual(packages.map(\.url).sorted(), ["https://example.com/two/two"])
116118
}
119+
120+
func test_reconcileCustomCollections() async throws {
121+
// Test single custom collection reconciliation
122+
// setup
123+
var fullPackageList = [URL("a"), URL("b"), URL("c")]
124+
for url in fullPackageList { try await Package(url: url).save(on: app.db) }
125+
126+
// Initial run
127+
try await withDependencies {
128+
$0.packageListRepository.fetchCustomCollection = { @Sendable _, _ in [URL("b")] }
129+
} operation: {
130+
// MUT
131+
try await reconcileCustomCollection(client: app.client,
132+
database: app.db,
133+
fullPackageList: fullPackageList,
134+
.init(name: "List", url: "url"))
135+
136+
// validate
137+
let count = try await CustomCollection.query(on: app.db).count()
138+
XCTAssertEqual(count, 1)
139+
let collection = try await CustomCollection.query(on: app.db).first().unwrap()
140+
try await collection.$packages.load(on: app.db)
141+
XCTAssertEqual(collection.packages.map(\.url), ["b"])
142+
}
143+
144+
// Reconcile again with an updated list of packages in the collection
145+
try await withDependencies {
146+
$0.packageListRepository.fetchCustomCollection = { @Sendable _, _ in [URL("c")] }
147+
} operation: {
148+
// MUT
149+
try await reconcileCustomCollection(client: app.client,
150+
database: app.db,
151+
fullPackageList: fullPackageList,
152+
.init(name: "List", url: "url"))
153+
154+
// validate
155+
let count = try await CustomCollection.query(on: app.db).count()
156+
XCTAssertEqual(count, 1)
157+
let collection = try await CustomCollection.query(on: app.db).first().unwrap()
158+
try await collection.$packages.load(on: app.db)
159+
XCTAssertEqual(collection.packages.map(\.url), ["c"])
160+
}
161+
162+
// Re-run after the single package in the list has been deleted in the full package list
163+
fullPackageList = [URL("a"), URL("b")]
164+
try await Package.query(on: app.db).filter(by: URL("c")).first()?.delete(on: app.db)
165+
try await withDependencies {
166+
$0.packageListRepository.fetchCustomCollection = { @Sendable _, _ in [URL("c")] }
167+
} operation: {
168+
// MUT
169+
try await reconcileCustomCollection(client: app.client,
170+
database: app.db,
171+
fullPackageList: fullPackageList,
172+
.init(name: "List", url: "url"))
173+
174+
// validate
175+
let count = try await CustomCollection.query(on: app.db).count()
176+
XCTAssertEqual(count, 1)
177+
let collection = try await CustomCollection.query(on: app.db).first().unwrap()
178+
try await collection.$packages.load(on: app.db)
179+
XCTAssertEqual(collection.packages.map(\.url), [])
180+
}
181+
}
182+
117183
}

0 commit comments

Comments
 (0)