// @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 } }, ), ), ) }) })