teqo-backup/tests/int/testDatabaseLease.int.spec.ts
Francisco Solla 8d007285d5 chore(D10): auto-cura de leftovers de consent/supporter + janela missing-consent exclusiva (12/12 sob 6 workers)
F1: purgeOrphanedConsentRenames sob lease exclusivo (autocommit) nos 3
pontos que tocam a key canônica; renames de teste migrados para chaves
privadas; F1b: purge de contatos órfãos no purgeMunicipalityResidue.
F2 (medição): shared-writer ABBA refutado; janela do missing-consent
exclusiva + Onda0 atômico (down transacional) + provision exclusivo.
Contraprova: 7 arquivos da key x 6 workers = 12/12 verdes (baseline 1/4).
2026-08-11 07:41:04 -03:00

681 lines
24 KiB
TypeScript

// @vitest-environment node
import { getPayload, type Payload } from 'payload'
import { beforeAll, describe, expect, it } from 'vitest'
import type { Consent } from '@/payload-types'
import config from '@/payload.config'
import {
acquireSharedTestDatabaseLease,
acquireTestDatabaseLease,
CAMPAIGN_INVITE_CONSENT_LEASE_KEY,
ensureLeasedConsent,
fixtureConsentText,
purgeOrphanedConsentRenames,
startTestDatabaseLeaseAcquisition,
waitForAdvisoryLockWaiter,
withInviteConsent,
withMissingInviteConsentFixture,
withMutableConsentFixture,
withSharedTestDatabaseLease,
type TestDatabaseLease,
} from '../helpers/testDatabaseLease'
let payload: Payload
const expectExactWaiter = (
waiting: Awaited<ReturnType<typeof waitForAdvisoryLockWaiter>>,
pid: number,
mode: 'ExclusiveLock' | 'ShareLock',
) => {
expect(waiting).toEqual({
activityDatabaseOID: waiting.databaseOID,
classID: waiting.expectedClassID,
databaseOID: expect.any(Number),
expectedClassID: expect.stringMatching(/^\d+$/),
expectedObjectID: expect.stringMatching(/^\d+$/),
granted: false,
mode,
objectID: waiting.expectedObjectID,
objectSubID: 1,
pid,
virtualTransaction: expect.stringMatching(/^\d+\/\d+$/),
})
expect(waiting.databaseOID).toBeGreaterThan(0)
}
describe('test database lease', () => {
beforeAll(async () => {
payload = await getPayload({ config: await config })
})
it('blocks an independent transaction session until the first lease releases', async () => {
const first = await acquireTestDatabaseLease(payload, CAMPAIGN_INVITE_CONSENT_LEASE_KEY)
let secondEntered = false
const pendingSecond = startTestDatabaseLeaseAcquisition(
payload,
CAMPAIGN_INVITE_CONSENT_LEASE_KEY,
)
const secondPID = await pendingSecond.backendPID
const secondAcquisition = pendingSecond.acquisition.then((lease) => {
secondEntered = true
return lease
})
const waiting = await waitForAdvisoryLockWaiter(payload, {
key: `test:${CAMPAIGN_INVITE_CONSENT_LEASE_KEY}`,
mode: 'ExclusiveLock',
waiterPID: secondPID,
})
expectExactWaiter(waiting, secondPID, 'ExclusiveLock')
expect(secondEntered).toBe(false)
await first.release()
const second = await secondAcquisition
expect(secondEntered).toBe(true)
await second.release()
})
it('allows shared readers to overlap and blocks an exclusive writer until both release', async () => {
const firstReader = await acquireSharedTestDatabaseLease(
payload,
CAMPAIGN_INVITE_CONSENT_LEASE_KEY,
)
const secondReader = await acquireSharedTestDatabaseLease(
payload,
CAMPAIGN_INVITE_CONSENT_LEASE_KEY,
)
let writerEntered = false
const pendingWriter = startTestDatabaseLeaseAcquisition(
payload,
CAMPAIGN_INVITE_CONSENT_LEASE_KEY,
)
const writerPID = await pendingWriter.backendPID
const writerAcquisition = pendingWriter.acquisition.then((lease) => {
writerEntered = true
return lease
})
const waiting = await waitForAdvisoryLockWaiter(payload, {
key: `test:${CAMPAIGN_INVITE_CONSENT_LEASE_KEY}`,
mode: 'ExclusiveLock',
waiterPID: writerPID,
})
expectExactWaiter(waiting, writerPID, 'ExclusiveLock')
expect(writerEntered).toBe(false)
await firstReader.release()
const stillWaiting = await waitForAdvisoryLockWaiter(payload, {
key: `test:${CAMPAIGN_INVITE_CONSENT_LEASE_KEY}`,
mode: 'ExclusiveLock',
waiterPID: writerPID,
})
expectExactWaiter(stillWaiting, writerPID, 'ExclusiveLock')
expect(stillWaiting.virtualTransaction).toBe(waiting.virtualTransaction)
expect(writerEntered).toBe(false)
await secondReader.release()
const writer = await writerAcquisition
expect(writerEntered).toBe(true)
await writer.release()
})
it('blocks shared readers while an exclusive writer is active', async () => {
const writer = await acquireTestDatabaseLease(payload, CAMPAIGN_INVITE_CONSENT_LEASE_KEY)
let readerEntered = false
const pendingReader = startTestDatabaseLeaseAcquisition(
payload,
CAMPAIGN_INVITE_CONSENT_LEASE_KEY,
'shared',
)
const readerPID = await pendingReader.backendPID
const readerAcquisition = pendingReader.acquisition.then((lease) => {
readerEntered = true
return lease
})
const waiting = await waitForAdvisoryLockWaiter(payload, {
key: `test:${CAMPAIGN_INVITE_CONSENT_LEASE_KEY}`,
mode: 'ShareLock',
waiterPID: readerPID,
})
expectExactWaiter(waiting, readerPID, 'ShareLock')
expect(readerEntered).toBe(false)
await writer.release()
const reader = await readerAcquisition
expect(readerEntered).toBe(true)
await reader.release()
})
it('creates a missing configured consent only once under concurrent ensures', async () => {
// Private key/lease: this test deliberately exercises ensure-then-create
// racing the missing window, so its window stays unleased
// (serializeWindow: false — the ensures lease themselves). Running the
// race on a private key keeps the stable invite row untouched: an
// unleased window on a shared key would let other files' fixtures observe
// the committed-absent state and delete the row out from under them.
const raceKey = `ensure-race-${Date.now()}-${Math.random()}`
const raceLeaseKey = `ensure-race-lease-${Date.now()}-${Math.random()}`
await withMissingInviteConsentFixture(
payload,
async () => {
const ensured = await Promise.all([
ensureLeasedConsent(payload, { consentKey: raceKey, leaseKey: raceLeaseKey }),
ensureLeasedConsent(payload, { consentKey: raceKey, leaseKey: raceLeaseKey }),
ensureLeasedConsent(payload, { consentKey: raceKey, leaseKey: raceLeaseKey }),
])
const configured = await withSharedTestDatabaseLease(payload, raceLeaseKey, () =>
payload.find({
collection: 'consent',
where: { key: { equals: raceKey } },
depth: 0,
limit: 10,
}),
)
expect(new Set(ensured.map((document) => document.id))).toHaveLength(1)
expect(configured.docs).toHaveLength(1)
},
{ consentKey: raceKey, leaseKey: raceLeaseKey, serializeWindow: false },
)
})
it('blocks other consent users while the missing-consent operation window is open', async () => {
const before = await withInviteConsent(payload, async (consent) => consent)
let writerAcquisition: Promise<TestDatabaseLease> | undefined
let readerAcquisition: Promise<TestDatabaseLease> | undefined
try {
await withMissingInviteConsentFixture(payload, async () => {
let writerEntered = false
const pendingWriter = startTestDatabaseLeaseAcquisition(
payload,
CAMPAIGN_INVITE_CONSENT_LEASE_KEY,
)
writerAcquisition = pendingWriter.acquisition.then((lease) => {
writerEntered = true
return lease
})
const writerPID = await pendingWriter.backendPID
const waiting = await waitForAdvisoryLockWaiter(payload, {
key: `test:${CAMPAIGN_INVITE_CONSENT_LEASE_KEY}`,
mode: 'ExclusiveLock',
waiterPID: writerPID,
})
expectExactWaiter(waiting, writerPID, 'ExclusiveLock')
expect(writerEntered).toBe(false)
// D10 F2: the window is EXCLUSIVE, so even a SHARED consumer queues
// while the canonical key is committed-absent — no consumer can
// observe the transient absence (baseline load runs flaked 3/4 with
// consumers reading the absent key or referencing a row the restore
// deleted out from under them). The reader is NOT awaited here: it
// only unblocks when this window releases, which is after the
// operation returns.
let readerEntered = false
const pendingReader = startTestDatabaseLeaseAcquisition(
payload,
CAMPAIGN_INVITE_CONSENT_LEASE_KEY,
'shared',
)
readerAcquisition = pendingReader.acquisition.then((lease) => {
readerEntered = true
return lease
})
const readerPID = await pendingReader.backendPID
const readerWaiting = await waitForAdvisoryLockWaiter(payload, {
key: `test:${CAMPAIGN_INVITE_CONSENT_LEASE_KEY}`,
mode: 'ShareLock',
waiterPID: readerPID,
})
expectExactWaiter(readerWaiting, readerPID, 'ShareLock')
expect(readerEntered).toBe(false)
})
} finally {
// Release the waiter leases even when an assertion above fails, so the
// pending acquisitions never block parallel spec files until teardown.
if (writerAcquisition) {
const writer = await writerAcquisition
await writer.release()
}
if (readerAcquisition) {
const reader = await readerAcquisition
await reader.release()
}
}
const after = await withInviteConsent(payload, async (consent) => consent)
// id+key only: a concurrent legitimate writer (e.g. the Onda0 provision)
// may update the row's text between these reads — the window's contract is
// that no exclusive writer interleaves, not that the text is frozen.
expect(after).toMatchObject({
id: before.id,
key: before.key,
})
})
it('releases a shared consent scope after its callback fails', async () => {
const callbackFailure = new Error('intentional shared callback failure')
await expect(
withInviteConsent(payload, async () => {
throw callbackFailure
}),
).rejects.toBe(callbackFailure)
const writer = await acquireTestDatabaseLease(payload, CAMPAIGN_INVITE_CONSENT_LEASE_KEY)
await writer.release()
})
it('restores the exact configured consent after deletion and recreation', async () => {
// The reference is the fixture's own snapshot (the configured consent the
// callback receives), not a separate read: a concurrent legitimate writer
// (e.g. the Onda0 provision) may update the row's text between reads, and
// the restore faithfully reproduces whatever the snapshot captured.
let before: Consent | undefined
await withMutableConsentFixture(payload, async (consent) => {
before = consent
await payload.delete({ collection: 'consent', id: consent.id })
await payload.create({
collection: 'consent',
data: {
key: 'lideranca-autopreenchimento',
text: {
root: {
type: 'root',
children: [
{
type: 'paragraph',
children: [{ type: 'text', text: 'Temporary recreation', version: 1 }],
direction: null,
format: '',
indent: 0,
version: 1,
},
],
direction: null,
format: '',
indent: 0,
version: 1,
},
},
},
depth: 0,
})
})
const after = await withInviteConsent(payload, async (consent) => consent)
if (!before) throw new Error('The mutable consent fixture must have provided a snapshot.')
expect(after).toMatchObject({
id: before.id,
key: before.key,
text: before.text,
})
})
it('restores the configured consent and releases after a callback assertion failure', async () => {
const before = await withSharedTestDatabaseLease(
payload,
CAMPAIGN_INVITE_CONSENT_LEASE_KEY,
() =>
payload.find({
collection: 'consent',
where: { key: { equals: 'lideranca-autopreenchimento' } },
depth: 0,
limit: 1,
}),
)
const callbackFailure = new Error('intentional callback assertion failure')
// The reference is the fixture's own snapshot (what the callback received),
// not the `before` read: a concurrent legitimate writer (e.g. the Onda0
// provision) may update the row's text between reads, and the restore
// faithfully reproduces whatever the snapshot captured. `before` still
// pins the existence state (restored vs created-then-deleted).
let snapshot: Consent | undefined
await expect(
withMutableConsentFixture(payload, async (consent) => {
snapshot = consent
await payload.update({
collection: 'consent',
id: consent.id,
data: {
key: 'temporarily-renamed-consent',
text: {
root: {
type: 'root',
children: [
{
type: 'paragraph',
children: [{ type: 'text', text: 'Temporary text', version: 1 }],
direction: null,
format: '',
indent: 0,
version: 1,
},
],
direction: null,
format: '',
indent: 0,
version: 1,
},
},
},
})
throw callbackFailure
}),
).rejects.toBe(callbackFailure)
const after = await withSharedTestDatabaseLease(
payload,
CAMPAIGN_INVITE_CONSENT_LEASE_KEY,
() =>
payload.find({
collection: 'consent',
where: { key: { equals: 'lideranca-autopreenchimento' } },
depth: 0,
limit: 1,
}),
)
expect(after.docs).toHaveLength(before.docs.length)
if (after.docs[0]) {
if (!snapshot) throw new Error('The mutable consent fixture must have provided a snapshot.')
expect(after.docs[0]).toMatchObject({
id: snapshot.id,
key: snapshot.key,
text: snapshot.text,
})
}
const subsequent = await acquireTestDatabaseLease(payload, CAMPAIGN_INVITE_CONSENT_LEASE_KEY)
await subsequent.release()
})
it('aggregates callback and restore lease acquisition failures in original order', async () => {
const callbackFailure = new Error('intentional callback failure')
const restoreAcquisitionFailure = new Error('intentional restore acquisition failure')
// Private key: this fault skips the restore, so the committed-absent state
// would leak for the rest of the run on a shared key (id churn in every
// other file). A private key keeps the leak invisible.
const faultKey = `restore-fault-${Date.now()}-${Math.random()}`
const faultLeaseKey = `restore-fault-lease-${Date.now()}-${Math.random()}`
const failure = await withMissingInviteConsentFixture(
payload,
async () => {
throw callbackFailure
},
{
consentKey: faultKey,
leaseKey: faultLeaseKey,
beforeRestoreLeaseAcquire: async () => {
throw restoreAcquisitionFailure
},
},
).catch((error: unknown) => error)
expect(failure).toBeInstanceOf(AggregateError)
expect((failure as AggregateError).errors).toEqual([callbackFailure, restoreAcquisitionFailure])
})
it('aggregates snapshot setup and setup lease release failures in original order', async () => {
const snapshotFailure = new Error('intentional snapshot setup failure')
const releaseFailure = new Error('intentional setup release failure')
const failure = await withMissingInviteConsentFixture(payload, async () => undefined, {
beforeSnapshotSetup: async () => {
throw snapshotFailure
},
beforeSetupLeaseRelease: async () => {
throw releaseFailure
},
}).catch((error: unknown) => error)
expect(failure).toBeInstanceOf(AggregateError)
expect((failure as AggregateError).errors).toEqual([snapshotFailure, releaseFailure])
const subsequent = await acquireTestDatabaseLease(payload, CAMPAIGN_INVITE_CONSENT_LEASE_KEY)
await subsequent.release()
})
it('rolls back a failed missing-consent setup and preserves rollback errors', async () => {
const before = await withInviteConsent(payload, async (consent) => consent)
const releaseFailure = new Error('intentional setup release failure after deletion')
const rollbackFailure = new Error('intentional setup rollback cleanup failure')
let callbackCalled = false
const failure = await withMissingInviteConsentFixture(
payload,
async () => {
callbackCalled = true
},
{
beforeSetupLeaseRelease: async () => {
throw releaseFailure
},
beforeSetupLeaseRollback: async () => {
throw rollbackFailure
},
},
).catch((error: unknown) => error)
expect(callbackCalled).toBe(false)
expect(failure).toBeInstanceOf(AggregateError)
expect((failure as AggregateError).errors).toEqual([releaseFailure, rollbackFailure])
const after = await withInviteConsent(payload, async (consent) => consent)
// The rollback preserves the exact row; the text is not part of the
// contract here (no restore ran, and a concurrent legitimate writer such
// as the Onda0 provision may update it between reads).
expect(after).toMatchObject({
id: before.id,
key: before.key,
})
const subsequent = await acquireTestDatabaseLease(payload, CAMPAIGN_INVITE_CONSENT_LEASE_KEY)
await subsequent.release()
})
it('keeps an absent consent absent when missing-consent setup release fails', async () => {
const consentKey = `absent-consent-${Date.now()}-${Math.random()}`
const leaseKey = `absent-consent-lease-${Date.now()}-${Math.random()}`
const releaseFailure = new Error('intentional absent setup release failure')
const failure = await withMissingInviteConsentFixture(
payload,
async () => {
throw new Error('callback must not run')
},
{
consentKey,
leaseKey,
beforeSetupLeaseRelease: async () => {
throw releaseFailure
},
},
).catch((error: unknown) => error)
expect(failure).toBe(releaseFailure)
const configured = await payload.find({
collection: 'consent',
where: { key: { equals: consentKey } },
depth: 0,
limit: 1,
})
expect(configured.docs).toHaveLength(0)
const subsequent = await acquireTestDatabaseLease(payload, leaseKey)
await subsequent.release()
})
it('purges orphaned consent renames left by an aborted run on ensure', async () => {
// D10 F1: every consent fixture must clear the rename shapes an aborted
// run can leave behind before snapshotting, or the next run's rename hits
// the UNIQUE constraint. Legitimate rows (the private key below) survive.
const privateKey = `purge-ensure-${Date.now()}-${Math.random()}`
const privateLeaseKey = `purge-ensure-lease-${Date.now()}-${Math.random()}`
const legitKey = `purge-legit-${Date.now()}-${Math.random()}`
const orphanIDs: number[] = []
let legitID = 0
// The orphans are planted under the key's EXCLUSIVE lease: the rename
// keys are LIVE inside other spec files' fixtures, and an unleased
// create could hit their UNIQUE constraint mid-rename (D10 F2 load
// measurement). The lease start also purges any leftover from a previous
// failed run of this very test, so the plant never collides with it.
const plantLease = await acquireTestDatabaseLease(payload, privateLeaseKey)
try {
await purgeOrphanedConsentRenames((query) => payload.db.drizzle.execute(query))
for (const key of [
'temporarily-renamed-consent',
'chave-renomeada',
'chave-temporariamente-ausente',
]) {
const orphan = await payload.create({
collection: 'consent',
data: { key, text: fixtureConsentText },
depth: 0,
})
orphanIDs.push(orphan.id)
}
const legit = await payload.create({
collection: 'consent',
data: { key: legitKey, text: fixtureConsentText },
depth: 0,
})
legitID = legit.id
} finally {
await plantLease.release()
}
try {
const configured = await ensureLeasedConsent(payload, {
consentKey: privateKey,
leaseKey: privateLeaseKey,
})
expect(configured.key).toBe(privateKey)
for (const orphanID of orphanIDs) {
const orphan = await payload.find({
collection: 'consent',
where: { id: { equals: orphanID } },
depth: 0,
limit: 1,
})
expect(orphan.docs).toHaveLength(0)
}
expect(
await payload.findByID({ collection: 'consent', id: legitID, depth: 0 }),
).toMatchObject({ key: legitKey })
} finally {
// Cleanup runs even on failure — a failed run of this very test must
// not leave the orphans it planted behind (self-healing tests).
for (const orphanID of orphanIDs) {
await payload
.delete({ collection: 'consent', id: orphanID, depth: 0 })
.catch(() => undefined)
}
if (legitID > 0) {
await payload
.delete({ collection: 'consent', id: legitID, depth: 0 })
.catch(() => undefined)
}
}
})
it('purges orphaned renames during the missing-consent fixture setup', async () => {
const privateKey = `purge-missing-${Date.now()}-${Math.random()}`
const privateLeaseKey = `purge-missing-lease-${Date.now()}-${Math.random()}`
// Planted under the exclusive lease (the rename keys are live inside
// other spec files' fixtures — an unleased create could hit their UNIQUE
// constraint mid-rename). The lease start also purges any leftover from
// a previous failed run of this very test.
const plantLease = await acquireTestDatabaseLease(payload, privateLeaseKey)
let orphanID = 0
try {
await purgeOrphanedConsentRenames((query) => payload.db.drizzle.execute(query))
orphanID = (
await payload.create({
collection: 'consent',
data: { key: 'chave-temporariamente-ausente', text: fixtureConsentText },
depth: 0,
})
).id
} finally {
await plantLease.release()
}
await withMissingInviteConsentFixture(payload, async () => undefined, {
consentKey: privateKey,
leaseKey: privateLeaseKey,
})
try {
const orphan = await payload.find({
collection: 'consent',
where: { id: { equals: orphanID } },
depth: 0,
limit: 1,
})
expect(orphan.docs).toHaveLength(0)
} finally {
// A failed run of this very test must not leave its plant behind.
await payload.delete({ collection: 'consent', id: orphanID, depth: 0 }).catch(() => undefined)
}
})
it('self-heals the canonical invite consent after an aborted rename on the next fixture run', async () => {
// The exact residue an aborted run leaves: the canonical row committed
// with a temporary key and the snapshot lost. The plant holds the
// exclusive lease so parallel spec files never observe the state; the
// fixture's own purge then heals it — the in-fixture rename must succeed
// instead of hitting the UNIQUE `consent_key_idx`. The lease start also
// purges any leftover from a previous failed run of this very test.
const plantLease = await acquireTestDatabaseLease(payload, CAMPAIGN_INVITE_CONSENT_LEASE_KEY)
try {
await purgeOrphanedConsentRenames((query) => payload.db.drizzle.execute(query))
const canonical = await payload.find({
collection: 'consent',
where: { key: { equals: 'lideranca-autopreenchimento' } },
limit: 1,
sort: 'id',
depth: 0,
})
if (!canonical.docs[0]) {
throw new Error(
'The canonical invite consent row must exist before the aborted-rename repro.',
)
}
await payload.update({
collection: 'consent',
id: canonical.docs[0].id,
data: { key: 'chave-renomeada' },
depth: 0,
})
} finally {
await plantLease.release()
}
let renamedDuringFixture = false
await withMutableConsentFixture(payload, async (consent) => {
// Without the F1 purge this rename throws "Valor deve ser único".
await payload.update({
collection: 'consent',
id: consent.id,
data: { key: 'chave-renomeada' },
depth: 0,
})
renamedDuringFixture = true
})
expect(renamedDuringFixture).toBe(true)
// Second run proves the run following the healed one stays clean.
await withMutableConsentFixture(payload, async () => undefined)
const orphans = await payload.find({
collection: 'consent',
where: { key: { equals: 'chave-renomeada' } },
depth: 0,
pagination: false,
})
expect(orphans.totalDocs).toBe(0)
})
})