Skip to content

test: fix read-replica and mongo seed flakes in integration tests - #17543

Draft
denolfe wants to merge 5 commits into
mainfrom
ci/main-flakes
Draft

test: fix read-replica and mongo seed flakes in integration tests#17543
denolfe wants to merge 5 commits into
mainfrom
ci/main-flakes

Conversation

@denolfe

@denolfe denolfe commented Jul 29, 2026

Copy link
Copy Markdown
Member

Fixes two integration test flakes that fail on main and on feature branches. Both defects are in the test code, not in Payload.

Root Cause

The postgres vector tests read through payload.db.drizzle. That handle is the withReplicas proxy (drizzle's replica router), so a raw .select() goes to a read replica. The tests write rows with payload.create and read them back immediately. Rows that have not replicated yet are absent. The query returns nothing. Payload's own reads stay correct through shouldReadFromPrimary, which pins reads to the primary for readReplicasAfterWriteInterval after a write. The raw handle skips that guard.

The ecommerce seed created each variant type's options with Promise.all. On the first write, the variantOptions namespace (MongoDB's term for a collection) and its versions namespace do not exist yet. MongoDB cannot create the same namespace from two concurrent transactions, so one commit fails with a TransientTransactionError write conflict. The seed caught that error and returned false. onInit ignores the return value, so Payload booted with empty collections. Thirteen cart tests then failed with messages that point nowhere near the seed.

Key Changes

  • Postgres vector tests query the primary

    • The three raw drizzle reads now use primaryDrizzle when it is set. They fall back to drizzle when no replica is configured.
  • Ecommerce seed creates variant options sequentially

    • A helper creates each variant type's options one at a time. Sequential creates cannot race on namespace creation.
    • The seed now logs through payload.logger.error and rethrows. A broken seed fails at the seed rather than during the cart tests.
  • Ecommerce cart fixtures fail fast

    • Both beforeAll hooks throw when the seed yields no product or variant. Optional chaining previously left those ids undefined and the hook still passed.
    • The guest cart paths assert that the cart secret came back. A missing secret otherwise fails access control and reports the cart as not found.

denolfe added 3 commits July 28, 2026 16:19
…ica flake

payload.db.drizzle is the withReplicas proxy, so .select() is routed to a read
replica. The vector tests write rows through payload.create and then read them
back with the raw drizzle handle, which misses rows that have not replicated
yet and fails with an empty result set.

Payload's own reads avoid this via shouldReadFromPrimary, which keeps reads on
the primary for readReplicasAfterWriteInterval after a write. The raw handle
bypasses that guard, so query primaryDrizzle directly instead.
The cart tests read productId/variantId with optional chaining and cast the
result, so an incomplete seed leaves them undefined without failing the hook.
The guest cart secret has the same problem: when it is absent every later
request fails access control and reports "cart not found".

Both cases currently surface as thirteen unrelated-looking failures well after
the real problem. Assert on them where they are read instead.
…o flake

The seed created all variant options for a variant type with Promise.all. On the
first write the variantOptions namespace and its versions namespace do not exist
yet, and MongoDB cannot create the same namespace from two concurrent
transactions, so the parallel creates raced and one commit failed with a
TransientTransactionError WriteConflict.

The seed then swallowed that error and returned false, which onInit ignores, so
Payload booted with empty collections and thirteen cart tests failed much later
with unrelated messages. Create the options sequentially and rethrow instead.
@github-actions

Copy link
Copy Markdown
Contributor

📦 esbuild Bundle Analysis for payload

This analysis was generated by esbuild-bundle-analyzer. 🤖
This PR introduced no changes to the esbuild bundle! 🙌

Mongoose starts building indexes as soon as the connection opens and does not
wait for those builds to finish. They ran at the same time as the test-only
database drop and the first writes from onInit, so MongoDB rejected the writes
with "Cannot create collection - database is in the process of being dropped"
and "Unable to write to collection due to catalog changes". The documentdb int
shard failed this way in collections-graphql.

Disable autoIndex when the database will be dropped, then build the indexes
after the drop and wait for them. The awaited build now also covers version
models, which were previously left to the background builds and so had no
indexes when connect resolved. The shared globals discriminator model stays
excluded because its auto-named indexes collide with each other.
…cart tests

The authenticated user cart and cart transfer beforeAll hooks still used
unguarded optional chaining for productId, and several guest cart creations
never asserted the secret came back.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants