@@ -213,6 +213,204 @@ final class EnumeratorTests: XCTestCase {
213213 XCTAssertEqual ( dbFolder. etag, remoteFolder. versionIdentifier)
214214 }
215215
216+ func testWorkingSetPaginatedEnumeration( ) async throws {
217+ // 1. Setup a flat file structure with more items than the page size.
218+ rootItem. children = [ ] // Clear existing children
219+ for i in 0 ..< 15 {
220+ let childItem = MockRemoteItem (
221+ identifier: " item \( i) " ,
222+ name: " item \( i) .txt " ,
223+ remotePath: Self . account. davFilesUrl + " /item \( i) .txt " ,
224+ account: Self . account. ncKitAccount,
225+ username: Self . account. username,
226+ userId: Self . account. id,
227+ serverUrl: Self . account. serverUrl
228+ )
229+ childItem. parent = rootItem
230+ rootItem. children. append ( childItem)
231+ }
232+
233+ let db = Self . dbManager. ncDatabase ( ) // Strong ref for in-memory test db
234+ debugPrint ( db)
235+ let remoteInterface = MockRemoteInterface ( rootItem: rootItem, pagination: true )
236+
237+ // 2. Create the enumerator with a specific page size.
238+ let enumerator = Enumerator (
239+ enumeratedItemIdentifier: . workingSet,
240+ account: Self . account,
241+ remoteInterface: remoteInterface,
242+ dbManager: Self . dbManager,
243+ pageSize: 5
244+ )
245+ let observer = MockEnumerationObserver ( enumerator: enumerator)
246+
247+ // 3. Enumerate the items.
248+ try await observer. enumerateItems ( )
249+
250+ // 4. Assert the results.
251+ XCTAssertEqual ( observer. items. count, 15 )
252+
253+ for item in observer. items {
254+ XCTAssertNotNil ( Self . dbManager. itemMetadata ( ocId: item. itemIdentifier. rawValue) )
255+ }
256+
257+ // The enumeration flow:
258+ // - Initial call enumerates the root.
259+ // - The enumerator then gets the first page of children.
260+ // - The observer requests the next pages until all items are fetched.
261+ // With 15 children and a page size of 5, we expect 3 pages of children.
262+ // Total pages observed: 1 (initial) + 3 (for children) = 4
263+ XCTAssertEqual ( observer. observedPages. count, 4 )
264+ XCTAssertEqual (
265+ observer. observedPages. first,
266+ NSFileProviderPage . initialPageSortedByName as NSFileProviderPage
267+ )
268+ }
269+
270+ func testWorkingSetPaginatedEnumerationWithMultipleDirectories( ) async throws {
271+ // 1. Setup a more complex, nested directory structure.
272+ // root
273+ // |- folder1 (7 items)
274+ // |- folder2
275+ // |- subfolder (3 items)
276+ // |- (8 items)
277+
278+ rootItem. children = [ ] // Clear existing children
279+
280+ let folder1 = MockRemoteItem (
281+ identifier: " folder1 " ,
282+ name: " folder1 " ,
283+ remotePath: Self . account. davFilesUrl + " /folder1 " ,
284+ directory: true ,
285+ account: Self . account. ncKitAccount,
286+ username: Self . account. username,
287+ userId: Self . account. id,
288+ serverUrl: Self . account. serverUrl
289+ )
290+ folder1. parent = rootItem
291+ rootItem. children. append ( folder1)
292+
293+ for i in 0 ..< 7 {
294+ let childItem = MockRemoteItem (
295+ identifier: " folder1-item \( i) " ,
296+ name: " item \( i) .txt " ,
297+ remotePath: folder1. remotePath + " /item \( i) .txt " ,
298+ account: Self . account. ncKitAccount,
299+ username: Self . account. username,
300+ userId: Self . account. id,
301+ serverUrl: Self . account. serverUrl
302+ )
303+ childItem. parent = folder1
304+ folder1. children. append ( childItem)
305+ }
306+
307+ let folder2 = MockRemoteItem (
308+ identifier: " folder2 " ,
309+ name: " folder2 " ,
310+ remotePath: Self . account. davFilesUrl + " /folder2 " ,
311+ directory: true ,
312+ account: Self . account. ncKitAccount,
313+ username: Self . account. username,
314+ userId: Self . account. id,
315+ serverUrl: Self . account. serverUrl
316+ )
317+ folder2. parent = rootItem
318+ rootItem. children. append ( folder2)
319+
320+ for i in 0 ..< 8 {
321+ let childItem = MockRemoteItem (
322+ identifier: " folder2-item \( i) " ,
323+ name: " item \( i) .txt " ,
324+ remotePath: folder2. remotePath + " /item \( i) .txt " ,
325+ account: Self . account. ncKitAccount,
326+ username: Self . account. username,
327+ userId: Self . account. id,
328+ serverUrl: Self . account. serverUrl
329+ )
330+ childItem. parent = folder2
331+ folder2. children. append ( childItem)
332+ }
333+
334+ let subfolder = MockRemoteItem (
335+ identifier: " subfolder " ,
336+ name: " subfolder " ,
337+ remotePath: folder2. remotePath + " /subfolder " ,
338+ directory: true ,
339+ account: Self . account. ncKitAccount,
340+ username: Self . account. username,
341+ userId: Self . account. id,
342+ serverUrl: Self . account. serverUrl
343+ )
344+ subfolder. parent = folder2
345+ folder2. children. append ( subfolder)
346+
347+ for i in 0 ..< 3 {
348+ let childItem = MockRemoteItem (
349+ identifier: " subfolder-item \( i) " ,
350+ name: " item \( i) .txt " ,
351+ remotePath: subfolder. remotePath + " /item \( i) .txt " ,
352+ account: Self . account. ncKitAccount,
353+ username: Self . account. username,
354+ userId: Self . account. id,
355+ serverUrl: Self . account. serverUrl
356+ )
357+ childItem. parent = subfolder
358+ subfolder. children. append ( childItem)
359+ }
360+
361+ let db = Self . dbManager. ncDatabase ( )
362+ debugPrint ( db)
363+ let remoteInterface = MockRemoteInterface ( rootItem: rootItem, pagination: true )
364+
365+ // 2. Create the enumerator.
366+ let enumerator = Enumerator (
367+ enumeratedItemIdentifier: . workingSet,
368+ account: Self . account,
369+ remoteInterface: remoteInterface,
370+ dbManager: Self . dbManager,
371+ pageSize: 5
372+ )
373+ let observer = MockEnumerationObserver ( enumerator: enumerator)
374+
375+ // 3. Enumerate the items.
376+ try await observer. enumerateItems ( )
377+
378+ // 4. Assert the results.
379+ // Total items =
380+ // 0 (root) +
381+ // 1 (folder1) +
382+ // 7 (items in folder1) +
383+ // 1 (folder2) +
384+ // 8 (items in folder2) +
385+ // 1 (subfolder) +
386+ // 3 (items in subfolder)
387+ // = 22
388+ let expectedTotalItems = 0 + 1 + 7 + 1 + 8 + 1 + 3
389+ print ( observer. items. map ( \. itemIdentifier. rawValue) )
390+ XCTAssertEqual ( observer. items. count, expectedTotalItems)
391+
392+ for item in observer. items {
393+ XCTAssertNotNil ( Self . dbManager. itemMetadata ( ocId: item. itemIdentifier. rawValue) )
394+ }
395+
396+ // The working set enumeration is recursive. The enumerator will first list the items
397+ // in the root directory, then it will be called again with the URLs of the subdirectories
398+ // as pages. This process continues until all directories have been enumerated.
399+ // We can verify that all items are discovered and stored in the database.
400+ let allItemIds = [
401+ rootItem. identifier,
402+ folder1. identifier,
403+ folder2. identifier,
404+ subfolder. identifier
405+ ] + folder1. children. map ( \. identifier)
406+ + folder2. children. map ( \. identifier)
407+ + subfolder. children. map ( \. identifier)
408+
409+ for itemId in allItemIds {
410+ XCTAssertNotNil ( Self . dbManager. itemMetadata ( ocId: itemId) , " Item with id \( itemId) should be in the database " )
411+ }
412+ }
413+
216414 func testWorkingSetChangeEnumeration( ) async throws {
217415 let db = Self . dbManager. ncDatabase ( ) // Strong ref for in memory test db
218416 debugPrint ( db)
0 commit comments