teqo-backup/tests/int/onda0Provision.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

166 lines
6.5 KiB
TypeScript

// @vitest-environment node
import { sql } from '@payloadcms/db-postgres'
import { getPayload, type Payload } from 'payload'
import { beforeAll, describe, expect, it } from 'vitest'
import { ONDA0_CONSENT_ENTRIES, ONDA0_CONSENT_KEY_LIST } from '@/lib/onda0ConsentTexts'
import config from '@/payload.config'
import { requireConsentByKey } from '@/utilities/campaignConsent'
import {
provisionOnda0ConsentAndPrivacy,
provisionOnda0ConsentAndPrivacyDb,
removeOnda0ConsentAndPrivacyDb,
} from '@/utilities/onda0Provision'
import { assertTestDatabase } from '../helpers/assertTestDatabase'
import { installCampaignFixtures } from '../helpers/campaignFixtures'
import {
CAMPAIGN_INVITE_CONSENT_LEASE_KEY,
SUPPORTER_REGISTRATION_CONSENT_LEASE_KEY,
SUPPORTER_VOTE_INTENTION_CONSENT_LEASE_KEY,
restoreConsentRows,
withExclusiveTestDatabaseLease,
withSharedTestDatabaseLease,
type ConsentSnapshot,
} from '../helpers/testDatabaseLease'
let payload: Payload
installCampaignFixtures({
getPayload: () => payload,
setPayload: (nextPayload) => {
payload = nextPayload
},
})
describe('Onda 0 provision (integration)', () => {
beforeAll(async () => {
assertTestDatabase(process.env.DATABASE_URL)
payload = await getPayload({ config })
})
it('upserts consent keys and publishes privacy-policy idempotently', async () => {
// EXCLUSIVE leases on the stable keys other spec files exercise (D10 F2):
// the provision REWRITES the shared rows' texts, and a consumer that reads
// the canonical consent between two of those writes would assert against a
// text that changed under it (observed: campaignInviteUi's preview
// `requiresConsent`/`consentData` mismatch). Exclusive serializes every
// consumer for the ~500ms of the provision — bounded, unlike the pre-D10
// cascade where the ONDA0 exclusive sat behind a queue of EXCLUSIVE
// writers. The down-test below restores the removed rows with their
// original ids atomically, so the shared rows are never observed absent.
await withExclusiveTestDatabaseLease(payload, CAMPAIGN_INVITE_CONSENT_LEASE_KEY, () =>
withExclusiveTestDatabaseLease(payload, SUPPORTER_REGISTRATION_CONSENT_LEASE_KEY, () =>
withExclusiveTestDatabaseLease(
payload,
SUPPORTER_VOTE_INTENTION_CONSENT_LEASE_KEY,
async () => {
await provisionOnda0ConsentAndPrivacy(payload)
await provisionOnda0ConsentAndPrivacy(payload)
for (const { key } of ONDA0_CONSENT_ENTRIES) {
const descriptor = await requireConsentByKey(payload, key)
expect(descriptor.key).toBe(key)
expect(descriptor.contentHash).toMatch(/^[a-f0-9]{64}$/)
}
const privacy = await payload.findGlobal({
slug: 'privacy-policy',
overrideAccess: true,
})
expect(privacy.published).toBe(true)
expect(privacy.body).toBeTruthy()
},
),
),
)
})
it('provisionOnda0ConsentAndPrivacyDb upserts and removes via SQL', async () => {
await withSharedTestDatabaseLease(payload, CAMPAIGN_INVITE_CONSENT_LEASE_KEY, () =>
withSharedTestDatabaseLease(payload, SUPPORTER_REGISTRATION_CONSENT_LEASE_KEY, () =>
withSharedTestDatabaseLease(
payload,
SUPPORTER_VOTE_INTENTION_CONSENT_LEASE_KEY,
async () => {
const db = payload.db.drizzle
await provisionOnda0ConsentAndPrivacyDb(db)
await provisionOnda0ConsentAndPrivacyDb(db)
const consentRows = await db.execute(sql`
SELECT "id", "key", "text", "updated_at", "created_at"
FROM "consent"
WHERE "key" IN (${sql.join(
ONDA0_CONSENT_KEY_LIST.map((key) => sql`${key}`),
sql`, `,
)})
ORDER BY "key"
`)
expect(consentRows.rows.map((row) => row.key)).toEqual(
[...ONDA0_CONSENT_KEY_LIST].sort(),
)
const privacyRows = await db.execute(sql`
SELECT "published", "body"
FROM "privacy_policy"
LIMIT 1
`)
expect(privacyRows.rows[0]?.published).toBe(true)
expect(privacyRows.rows[0]?.body).toBeTruthy()
// The down-migration must stay invisible to parallel spec files:
// delete + count + restore run in ONE transaction, so no consumer
// ever observes the stable keys committed-absent or re-created
// with a new id (D10 F2 — the non-atomic version flaked load runs
// with `consent_id` FK violations and fail-closed reads).
const transactionID = await payload.db.beginTransaction()
if (transactionID === null) {
throw new Error('Expected a PostgreSQL transaction for the Onda0 down.')
}
const transactionDb = payload.db.sessions?.[String(transactionID)]?.db as
| typeof db
| undefined
if (!transactionDb) {
await payload.db.rollbackTransaction(transactionID).catch(() => undefined)
throw new Error('The Onda0 down transaction session is unavailable.')
}
try {
await removeOnda0ConsentAndPrivacyDb(transactionDb)
const consentAfterDown = await transactionDb.execute(sql`
SELECT count(*)::integer AS count
FROM "consent"
WHERE "key" IN (${sql.join(
ONDA0_CONSENT_KEY_LIST.map((key) => sql`${key}`),
sql`, `,
)})
`)
expect(consentAfterDown.rows[0]?.count).toBe(0)
const privacyAfterDown = await transactionDb.execute(sql`
SELECT "published"
FROM "privacy_policy"
LIMIT 1
`)
expect(privacyAfterDown.rows[0]?.published).toBe(false)
// Restore the removed rows with their ORIGINAL ids inside the
// same transaction — the commit publishes delete+restore as one
// atomic state change.
await restoreConsentRows(
(query) => transactionDb.execute(query),
consentRows.rows as ConsentSnapshot[],
)
await payload.db.commitTransaction(transactionID)
} catch (error) {
await payload.db.rollbackTransaction(transactionID).catch(() => undefined)
throw error
}
},
),
),
)
})
})