Skip to main content
ThunderLang
← All articles
ai-engineering

Test AI-Written Cursor Pagination for Skipped Records

6 min read · 2026-10-20 · Allen Codewell

A clock-shaped token and a toothed clock-shaped key beside a gate’s two-part lock.

AI-generated illustration. The review decision is whether continuation preserves the required position. For this stateless example, timestamp and ID together distinguish records at a tied boundary.

Test AI-written cursor pagination against a written contract, not just examples with distinct timestamps. Put a tie across a page boundary. If continuation uses only a strict timestamp comparison, it can skip records.

The review question is simple: does the cursor, or the state behind it, preserve enough information to resume the required order?

Write the contract before reviewing the implementation

For this example, use this requirement:

For an unchanged dataset and fixed filter, traversing every page must return every eligible record exactly once, in descending timestamp order and then descending numeric ID order, and must terminate.

Make the assumptions explicit:

  • IDs are unique, immutable integers.
  • Timestamps are non-null, with unchanged values and precision during traversal.
  • Every request uses the same filter and ordering.
  • The page size is two. Each page is full while enough records remain.
  • Traversal ends after the last nonempty page or one final empty page.
  • The cursor is opaque to the caller; its format is not part of this contract.

This is a behavioral contract, not a required cursor format. Server-side state could satisfy it too. For a stateless keyset implementation, carrying both the timestamp and ID directly represents the boundary.

Split a timestamp tie across pages

Seed an isolated test repository with these four eligible records:

ID Timestamp
103 2025-01-02T12:00:00Z
102 2025-01-02T12:00:00Z
101 2025-01-02T12:00:00Z
100 2025-01-01T12:00:00Z

With a page size of two, the required pages are:

Page 1: [103, 102]
Page 2: [101, 100]
Then termination, possibly after a final empty page.

Suppose page one ends at record 102, but the cursor retains only its timestamp. A continuation condition of timestamp < cursor_timestamp excludes every record with that timestamp, including the unseen record 101. The next page contains only 100.

Changing < to <= is not enough. Without additional position information, the next query can return 103 and 102 again.

For the declared descending timestamp-and-ID order, a schematic SQL continuation is:

WHERE <unchanged eligibility filter>
  AND (
    timestamp < :cursor_timestamp
    OR (
      timestamp = :cursor_timestamp
      AND id < :cursor_id
    )
  )
ORDER BY timestamp DESC, id DESC
LIMIT :page_size

The first page omits the continuation condition. Later pages use the timestamp and ID of the last returned record, not an extra lookahead record used to detect whether another page exists.

Record 101 shares the boundary timestamp but has a smaller ID than 102, so it remains eligible. Record 100 follows because its timestamp is older.

Two paper sleeves hold four cards; three cards share a clock symbol, including one in the second sleeve.

AI-generated illustration. The fixture splits three equal timestamps across two pages. Strict timestamp-only continuation skips the tied record on page two; preserving its position allows it to follow the first pair.

Exercise the actual implementation

The Python test below defines an adapter boundary. It is illustrative code, not a reported test run.

Your adapter must:

  1. Seed only these eligible records in an isolated test resource.
  2. Call the AI-written pagination implementation, including its cursor handling and query path.
  3. Keep the filter fixed across calls.
  4. Expose fetch_page(limit=..., cursor=...), returning {"items": [...], "next_cursor": ...}. Normalize termination to None.
  5. Normalize returned records to numeric id values and the timestamp strings shown here, without sorting or deduplicating them.

Do not put the expected pagination logic inside the adapter. That would test the adapter rather than the change.

RECORDS = [
    {"id": 103, "timestamp": "2025-01-02T12:00:00Z"},
    {"id": 102, "timestamp": "2025-01-02T12:00:00Z"},
    {"id": 101, "timestamp": "2025-01-02T12:00:00Z"},
    {"id": 100, "timestamp": "2025-01-01T12:00:00Z"},
]


def acceptance_test(fetch_page):
    expected_pages = [[103, 102], [101, 100]]
    expected_keys = [
        (record["timestamp"], record["id"])
        for record in RECORDS
    ]
    expected_ids = {record["id"] for record in RECORDS}

    cursor = None
    pages = []
    returned = []

    # Safety bound for this fixture, not a general termination proof.
    for _ in range(len(RECORDS) + 1):
        page = fetch_page(limit=2, cursor=cursor)
        items = page["items"]
        next_cursor = page["next_cursor"]

        assert len(items) <= 2, "Page exceeds requested size"
        if items:
            pages.append([record["id"] for record in items])
            returned.extend(items)
        else:
            assert next_cursor is None, "Empty page must terminate"

        if next_cursor is None:
            break
        cursor = next_cursor
    else:
        raise AssertionError("Traversal did not terminate within bound")

    ids = [record["id"] for record in returned]
    keys = [
        (record["timestamp"], record["id"])
        for record in returned
    ]

    assert len(ids) == len(set(ids)), "Duplicate records returned"
    assert set(ids) == expected_ids, "Missing or unexpected records"
    assert keys == sorted(keys, reverse=True), "Incorrect ordering"
    assert keys == expected_keys, "Unexpected record keys or sequence"
    assert pages == expected_pages, "Incorrect page contents"

Load RECORDS through the adapter setup, then call acceptance_test(fetch_page) with Python assertions enabled.

The ordering assertion compares timestamp strings because this fixture uses one UTC representation and precision. It is not a general timestamp parser.

A strict timestamp-only continuation should fail the completeness check: 101 is missing. Repeating boundary records will fail the termination bound or uniqueness check.

Keep a requirement-to-test worksheet

Use this worksheet to connect the contract to observable behavior:

Requirement Checkable evidence
Descending timestamp, then descending ID Returned keys equal the declared sequence
Ties survive a page boundary 101 appears after the first page ends at 102
Every eligible record appears Returned ID set equals the fixture ID set
No record appears twice Returned IDs contain no duplicates
Requested page shape is respected Nonempty pages are [103, 102] and [101, 100]
Traversal finishes A terminal response arrives within the fixture's safety bound

Ask the coding agent to explain how its ordering, continuation predicate, and cursor representation satisfy each row. Use that explanation to review the diff, not as a substitute for running the test.

If the implementation changes the order, relaxes completeness, or alters expected pages to pass, treat that as a specification change.

Decide what passing establishes

Passing provides evidence that the exercised implementation handles this tied-timestamp boundary under the stated assumptions. It does not establish correctness across every dataset.

Separate coverage is still needed for:

  • Other inputs: empty datasets, different page sizes, longer tie groups, and more pages.
  • Filter behavior: excluded records and enforcement of the same eligibility rule on later requests. This fixture makes every record eligible.
  • Representation details: timestamp precision, serialization, ID types, and null handling if nulls are permitted.
  • Cursor validation: malformed, modified, or filter-mismatched cursors.
  • Concurrent changes: inserts, deletions, and updates during traversal. A unique tie-breaker defines order, but does not itself provide snapshot consistency.

Keep a timestamp-only continuation only if a narrower contract justifies it, such as enforced unique timestamps within the eligible dataset, or another mechanism preserves the full position. If ties are allowed and no such mechanism exists, this counterexample is enough to reject strict timestamp-only continuation.

For a next step in reviewing an AI-assisted change, see ThunderLang's getting-started guide.