@@ -547,6 +547,92 @@ final class EnumeratorTests: XCTestCase {
547547 XCTAssertNil ( Self . dbManager. itemMetadata ( ocId: rootItem. identifier) )
548548 }
549549
550+ func testWorkingSetEnumerationStopsOnExhaustedTotal( ) async throws {
551+ // This test verifies that enumeration correctly terminates when a faulty server
552+ // provides a `nextPage` token but all items (according to `pageTotal`) have been received.
553+
554+ // 1. Setup: A folder with 9 items which, when including itself in the propfind, is an exact
555+ // multiple of the page size.
556+ rootItem. children = [ ]
557+ let folder = MockRemoteItem (
558+ identifier: " folder10 " ,
559+ name: " folder10 " ,
560+ remotePath: Self . account. davFilesUrl + " /folder10 " ,
561+ directory: true ,
562+ account: Self . account. ncKitAccount,
563+ username: Self . account. username,
564+ userId: Self . account. id,
565+ serverUrl: Self . account. serverUrl
566+ )
567+ folder. parent = rootItem
568+ rootItem. children. append ( folder)
569+
570+ for i in 0 ..< 9 {
571+ let childItem = MockRemoteItem (
572+ identifier: " item \( i) " ,
573+ name: " item \( i) .txt " ,
574+ remotePath: folder. remotePath + " /item \( i) .txt " ,
575+ account: Self . account. ncKitAccount,
576+ username: Self . account. username,
577+ userId: Self . account. id,
578+ serverUrl: Self . account. serverUrl
579+ )
580+ childItem. parent = folder
581+ folder. children. append ( childItem)
582+ }
583+
584+ let db = Self . dbManager. ncDatabase ( )
585+ debugPrint ( db)
586+
587+ let remoteInterface = MockRemoteInterface ( rootItem: rootItem, pagination: true )
588+ let pageSize = 5
589+
590+ // This test requires a modification to `MockRemoteInterface` to simulate a faulty server
591+ remoteInterface. forceNextPageOnLastContentPage = true
592+
593+ // 2. Manually drive the pagination loop.
594+ var allEnumeratedItems : [ NSFileProviderItem ] = [ ]
595+ var currentPage : NSFileProviderPage ? =
596+ NSFileProviderPage . initialPageSortedByName as NSFileProviderPage
597+ var loopCount = 0
598+
599+ while let pageToEnumerate = currentPage {
600+ loopCount += 1
601+ currentPage = nil // Reset for the next iteration
602+
603+ // Create a NEW enumerator and observer for this single page request
604+ let enumerator = Enumerator (
605+ enumeratedItemIdentifier: . workingSet,
606+ account: Self . account,
607+ remoteInterface: remoteInterface,
608+ dbManager: Self . dbManager,
609+ pageSize: pageSize
610+ )
611+ let observer = MockEnumerationObserver ( enumerator: enumerator)
612+ try await observer. enumerateItemsPage ( page: pageToEnumerate)
613+ XCTAssertNil ( observer. error)
614+ allEnumeratedItems += observer. items
615+ currentPage = observer. page
616+ }
617+
618+ // 3. Assert the results.
619+ // The loop should have run 3 times:
620+ // 1. For root
621+ // 2. For `folder10` page 1 (items 0-4).
622+ // 3. For `folder10` page 2 (items 5-9). The enumerator logic should see that all items
623+ // have been delivered and should not return the phantom page token, terminating the loop.
624+ XCTAssertNil ( currentPage, " Loop should have terminated with a nil next page. " )
625+ XCTAssertEqual ( loopCount, 3 , " The enumeration loop should have terminated correctly. " )
626+
627+ // Total items expected: 1 folder + 9 children = 10.
628+ let expectedTotalItems = 1 + 9
629+ XCTAssertEqual (
630+ allEnumeratedItems. count,
631+ expectedTotalItems,
632+ " The correct number of total items should be enumerated. "
633+ )
634+ }
635+
550636 func testReadServerUrlFollowUpPagination( ) async throws {
551637 // 1. Arrange: Setup a folder with enough children to require multiple pages.
552638 remoteFolder. children = [ ]
0 commit comments