mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-13 15:13:07 +00:00
- knip (files/deps at error level; unused exports ledgered as warn) with config for Payload/Next entries; deleted the dead modules it confirmed: RecentlyVisited + UpcomingActionPlansCard + actionPlanUpcomingPreview, lazy-dialog helpers, tseZone, federalCandidateTotalsAggregate (test-only), unused ui kit files, one-shot rename script; pruned unused radix deps and typescript-eslint; added pg/@types/pg (pinned to drizzle's 8.10.2) - GitHub Actions CI on the mirror: lint, typecheck, knip, unit, migrate, int against a Postgres 17 service - eslint: unused-vars with ^_ ignore patterns (migrations args exempt), repo-wide ban on `as never` (no-restricted-syntax), lint gate at --max-warnings=0; fixed all 54 pre-existing warnings (dead imports, hooks deps, tracking-pixel img disable with impeccable config exception) - tsconfig: include consolidated from 219 lines to 5 (wildcard covers all .next dist variants), noUnusedLocals on - typed replacements for every as-never: hookFilledCreateData (actions create data completed by collection hooks), drizzle scopeColumn narrowing, tests' stub<T> helper (property-level checking kept, completeness waived) - test infra: leased-consent generalization used by onda0 spec (shared leases over the stable consent keys; window race ledgered), purge fix - docs: IMPROVE-CODE-QUALITY-PLAN.md tracker, TECH-DEBT.md ledger, TESTING.md safety-net map (package.json also gains the Phase 2 build:election-aggregates script entry; the script lands next commit)
265 lines
9.3 KiB
TypeScript
265 lines
9.3 KiB
TypeScript
// @vitest-environment node
|
|
|
|
import { createRequire } from 'node:module'
|
|
|
|
import { sql, type MigrateUpArgs } from '@payloadcms/db-postgres'
|
|
import { drizzle } from '@payloadcms/db-postgres/drizzle/node-postgres'
|
|
import { getPayload, type Payload } from 'payload'
|
|
import type { Pool as PgPool } from 'pg'
|
|
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
|
|
|
|
import { migrations } from '@/migrations'
|
|
import { withCampaignFixtures } from '../helpers/campaignFixtures'
|
|
import { stub } from '../helpers/stub'
|
|
|
|
const require = createRequire(import.meta.resolve('@payloadcms/db-postgres'))
|
|
const { Pool } = require('pg') as {
|
|
Pool: new (options: { connectionString: string }) => PgPool
|
|
}
|
|
|
|
const testDatabaseUrl =
|
|
process.env.DATABASE_URL ?? 'postgresql://teqo:teqo@localhost:5432/teqo_test'
|
|
const adminUrl = testDatabaseUrl.replace(/\/[^/]+$/, '/postgres')
|
|
|
|
// The campaign fixture surface after the Município remodel. Municipalities are seeded
|
|
// reference rows: `municipality` itself never changes count, while `municipality_rels`
|
|
// (advisors) and the strategy array tables must return to their baseline
|
|
// after cleanup resets every touched municipality.
|
|
const campaignFixtureTables = [
|
|
'campaign_user',
|
|
'campaign_user_sessions',
|
|
'consent',
|
|
'contact',
|
|
'municipality',
|
|
'municipality_rels',
|
|
'municipality_strengths',
|
|
'municipality_risks',
|
|
'organization',
|
|
'organization_rels',
|
|
'leadership',
|
|
'leadership_rels',
|
|
'vote_pledge',
|
|
'campaign_demand',
|
|
'campaign_demand_status_history',
|
|
'municipality_update',
|
|
'supporter',
|
|
'campaign_invite',
|
|
'payload_locked_documents',
|
|
'payload_locked_documents_rels',
|
|
] as const
|
|
|
|
/** Seeded reference tables: their counts must never grow, only stay equal. */
|
|
const seededTables = new Set<CampaignFixtureTable>(['municipality'])
|
|
|
|
type CampaignFixtureTable = (typeof campaignFixtureTables)[number]
|
|
type CampaignFixtureTableCounts = Record<CampaignFixtureTable, number>
|
|
|
|
let databaseName = ''
|
|
let databaseUrl = ''
|
|
let originalDatabaseUrl: string | undefined
|
|
let payload: Payload
|
|
|
|
const quoteIdentifier = (identifier: string): string => {
|
|
if (!/^[a-z0-9_]+$/.test(identifier)) throw new Error(`Unsafe database identifier: ${identifier}`)
|
|
return `"${identifier}"`
|
|
}
|
|
|
|
const readCampaignFixtureTableCounts = async (): Promise<CampaignFixtureTableCounts> => {
|
|
const availableResult = await payload.db.drizzle.execute(sql`
|
|
SELECT "tablename"
|
|
FROM "pg_tables"
|
|
WHERE "schemaname" = 'public'
|
|
`)
|
|
const available = new Set(availableResult.rows.map(({ tablename }) => String(tablename)))
|
|
const missing = campaignFixtureTables.filter((table) => !available.has(table))
|
|
if (missing.length > 0) {
|
|
throw new Error(`Missing expected campaign fixture tables: ${missing.join(', ')}`)
|
|
}
|
|
|
|
const entries = await Promise.all(
|
|
campaignFixtureTables.map(async (table) => {
|
|
const result = await payload.db.drizzle.execute(
|
|
sql.raw(`SELECT count(*)::integer AS "count" FROM "${table}"`),
|
|
)
|
|
return [table, Number(result.rows[0]?.count)] as const
|
|
}),
|
|
)
|
|
return Object.fromEntries(entries) as CampaignFixtureTableCounts
|
|
}
|
|
|
|
const expectEveryTableToGrow = (
|
|
before: CampaignFixtureTableCounts,
|
|
during: CampaignFixtureTableCounts,
|
|
): void => {
|
|
for (const table of campaignFixtureTables) {
|
|
if (seededTables.has(table)) {
|
|
expect(during[table], `${table} is seeded and must not change count`).toBe(before[table])
|
|
continue
|
|
}
|
|
expect(during[table], `${table} should contain a representative fixture row`).toBeGreaterThan(
|
|
before[table],
|
|
)
|
|
}
|
|
}
|
|
|
|
beforeAll(async () => {
|
|
originalDatabaseUrl = process.env.DATABASE_URL
|
|
databaseName = `teqo_campaign_fixture_counts_${process.pid}_test`
|
|
databaseUrl = testDatabaseUrl.replace(/\/[^/]+$/, `/${databaseName}`)
|
|
const adminPool = new Pool({ connectionString: adminUrl })
|
|
await adminPool.query(`DROP DATABASE IF EXISTS ${quoteIdentifier(databaseName)} WITH (FORCE)`)
|
|
await adminPool.query(`CREATE DATABASE ${quoteIdentifier(databaseName)}`)
|
|
await adminPool.end()
|
|
|
|
const migrationPool = new Pool({ connectionString: databaseUrl })
|
|
const database = drizzle<Record<string, unknown>>(migrationPool)
|
|
for (const migration of migrations) {
|
|
await migration.up(stub<MigrateUpArgs>({ db: database }))
|
|
}
|
|
await migrationPool.end()
|
|
|
|
process.env.DATABASE_URL = databaseUrl
|
|
const { default: config } = await import('@/payload.config')
|
|
payload = await getPayload({ config: await config })
|
|
}, 120_000)
|
|
|
|
afterAll(async () => {
|
|
await payload.db.destroy?.()
|
|
const pool = payload.db.pool as typeof payload.db.pool & {
|
|
_clients?: Array<{ release?: () => void }>
|
|
_idle?: Array<{ client: object }>
|
|
}
|
|
const idleClients = new Set(pool._idle?.map(({ client }) => client))
|
|
const poolEnd = pool.end()
|
|
for (const client of pool._clients ?? []) {
|
|
if (!idleClients.has(client)) client.release?.()
|
|
}
|
|
await poolEnd
|
|
if (databaseName) {
|
|
const adminPool = new Pool({ connectionString: adminUrl })
|
|
await adminPool.query(`DROP DATABASE IF EXISTS ${quoteIdentifier(databaseName)}`)
|
|
await adminPool.end()
|
|
}
|
|
if (originalDatabaseUrl === undefined) delete process.env.DATABASE_URL
|
|
else process.env.DATABASE_URL = originalDatabaseUrl
|
|
}, 30_000)
|
|
|
|
describe('campaign fixture PostgreSQL row-count invariant', () => {
|
|
it('restores exact counts after a representative full graph', async () => {
|
|
const before = await readCampaignFixtureTableCounts()
|
|
let during: CampaignFixtureTableCounts | undefined
|
|
|
|
await withCampaignFixtures(payload, async (fixtures) => {
|
|
const coordinator = await fixtures.createCampaignUser('coordinator')
|
|
const advisor = await fixtures.createCampaignUser('advisor')
|
|
const consent = await fixtures.createConsent()
|
|
const contact = await fixtures.createContact()
|
|
const supporterContact = await fixtures.createContact()
|
|
const municipality = await fixtures.getMunicipality()
|
|
|
|
await fixtures.assignMunicipalityAdvisors(municipality, [advisor])
|
|
await payload.update({
|
|
collection: 'municipality',
|
|
id: municipality.id,
|
|
data: {
|
|
priority: 'alta',
|
|
strengths: [{ text: fixtures.value('Força') }],
|
|
risks: [{ text: fixtures.value('Risco') }],
|
|
},
|
|
depth: 0,
|
|
})
|
|
|
|
const organization = await fixtures.createOrganization({ municipalities: [municipality.id] })
|
|
const leadership = await fixtures.createLeadership({
|
|
contact,
|
|
municipalities: [municipality.id],
|
|
organizations: [organization.id],
|
|
consent,
|
|
createdBy: coordinator,
|
|
})
|
|
await fixtures.createVotePledge({ leadership, municipality })
|
|
await fixtures.createCampaignDemand({ municipality, leadership, createdBy: coordinator })
|
|
await fixtures.createMunicipalityUpdate({ municipality, author: coordinator })
|
|
await fixtures.createSupporter({
|
|
contact: supporterContact,
|
|
municipality,
|
|
createdBy: coordinator,
|
|
})
|
|
await fixtures.createInvite({ leadership, createdBy: coordinator })
|
|
await payload.db.drizzle.execute(sql`
|
|
INSERT INTO "campaign_user_sessions" (
|
|
"_order",
|
|
"_parent_id",
|
|
"id",
|
|
"created_at",
|
|
"expires_at"
|
|
)
|
|
VALUES (
|
|
1,
|
|
${coordinator.id},
|
|
${fixtures.value('campaign-session')},
|
|
now(),
|
|
now() + interval '1 hour'
|
|
)
|
|
`)
|
|
const lockedDocument = await payload.db.drizzle.execute(sql`
|
|
INSERT INTO "payload_locked_documents" ("updated_at", "created_at")
|
|
VALUES (now(), now())
|
|
RETURNING "id"
|
|
`)
|
|
const lockedDocumentID = Number(lockedDocument.rows[0]?.id)
|
|
await payload.db.drizzle.execute(sql`
|
|
INSERT INTO "payload_locked_documents_rels" (
|
|
"parent_id",
|
|
"path",
|
|
"leadership_id"
|
|
)
|
|
VALUES (${lockedDocumentID}, 'relationTo', ${leadership.id})
|
|
`)
|
|
during = await readCampaignFixtureTableCounts()
|
|
})
|
|
|
|
if (!during) throw new Error('Representative fixture graph was not measured.')
|
|
expectEveryTableToGrow(before, during)
|
|
expect(await readCampaignFixtureTableCounts()).toEqual(before)
|
|
})
|
|
|
|
it('restores exact counts after callback and partial-setup failures', async () => {
|
|
const before = await readCampaignFixtureTableCounts()
|
|
|
|
await expect(
|
|
withCampaignFixtures(payload, async (fixtures) => {
|
|
const user = await fixtures.createCampaignUser('coordinator')
|
|
await payload.db.drizzle.execute(sql`
|
|
INSERT INTO "campaign_user_sessions" (
|
|
"_order",
|
|
"_parent_id",
|
|
"id",
|
|
"created_at",
|
|
"expires_at"
|
|
)
|
|
VALUES (
|
|
1,
|
|
${user.id},
|
|
${fixtures.value('failed-session')},
|
|
now(),
|
|
now() + interval '1 hour'
|
|
)
|
|
`)
|
|
throw new Error('row-count callback failure')
|
|
}),
|
|
).rejects.toThrow('row-count callback failure')
|
|
expect(await readCampaignFixtureTableCounts()).toEqual(before)
|
|
|
|
await expect(
|
|
withCampaignFixtures(payload, async (fixtures) => {
|
|
await fixtures.createContact()
|
|
await fixtures.createLeadership({
|
|
contact: 999_999_999,
|
|
municipalities: [999_999_999],
|
|
})
|
|
}),
|
|
).rejects.toThrow()
|
|
expect(await readCampaignFixtureTableCounts()).toEqual(before)
|
|
})
|
|
})
|