test(cms): Payload config + Local API tests, fix stale Docker tests (MITHOME-96)
Adds the two test categories the ticket asked for, replacing the old content-JSON-schema-only testing story now that Payload is the real source of truth: 1. src/payload-config.test.ts — fast, DB-free unit tests over the Collection/Global config objects themselves. WHY not importing payload.config.ts directly: it pulls in the real `payload` and `@payloadcms/db-mongodb` packages as values (not just types), which are ESM-only and Jest's default transformIgnorePatterns skips node_modules entirely — confirmed by trying it (`SyntaxError: Cannot use import statement outside a module` from payload's own dist). Individual collection/global files only ever `import type` from 'payload' (erased at compile time), so they're safely importable in isolation. Two of the assertions are deliberate regression guards for real bugs found earlier this session (MITHOME-121): Media.access.read must stay public, and Partners.logo must stay optional. 2. scripts/test-payload-local-api.ts (npm run test:payload) — exercises every src/lib/payload-content.ts getter plus a full create/findByID/delete cycle against a real, already-migrated MongoDB. WHY a plain node script instead of a Jest integration config: same ESM problem as above, and Payload's dependency graph is too broad to safely add to transformIgnorePatterns — this follows the same working `node --import tsx` pattern already used by migrate-content-to-payload.ts. Not wired into pre-deploy-tests.sh (needs a live, pre-migrated MongoDB, same reasoning as the removed CMS integration tests); run manually or before a release. 3. Fixed the pre-existing (Docker-stack-gated, so silently never caught) staleness in integration.test.ts and e2e-docker.test.ts: unprefixed routes (/rolunk) → locale-prefixed (/hu/rolunk), and Mongo assertions against the old site_config/contact_submissions collections → the real globals/legal-pages/partners/ contact-submissions collections Payload actually uses. Also fixed an unrelated stale error-message assertion for spam detection that never matched the route's real string. Verified: full gate green (tsc, lint, 63 unit tests — 13 new — build), and separately ran test:payload live against the dev MongoDB (8/8 passed, including the write/read/delete cycle actually hitting Mongo). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
a7e0530068
commit
e2b0c62628
@@ -1,6 +1,10 @@
|
||||
/**
|
||||
* End-to-End tests for the Docker environment
|
||||
* These tests verify the full application flow in the Docker stack
|
||||
*
|
||||
* MITHOME-96: routes are locale-prefixed (/hu/..., /en/...) since the
|
||||
* Payload CMS migration (MITHOME-91/114) — updated from the old unprefixed
|
||||
* paths.
|
||||
*/
|
||||
|
||||
describe('Docker E2E Tests', () => {
|
||||
@@ -27,24 +31,24 @@ describe('Docker E2E Tests', () => {
|
||||
expect(html).toContain('mozdIT Bt.')
|
||||
|
||||
// Test navigation links exist in homepage
|
||||
expect(html).toContain('href="/rolunk"')
|
||||
expect(html).toContain('href="/szolgaltatasok"')
|
||||
expect(html).toContain('href="/kapcsolat"')
|
||||
expect(html).toContain('href="/hu/rolunk"')
|
||||
expect(html).toContain('href="/hu/szolgaltatasok"')
|
||||
expect(html).toContain('href="/hu/kapcsolat"')
|
||||
|
||||
// Test about page
|
||||
response = await fetch(`${APP_URL}/rolunk`)
|
||||
response = await fetch(`${APP_URL}/hu/rolunk`)
|
||||
expect(response.status).toBe(200)
|
||||
html = await response.text()
|
||||
expect(html).toContain('Rólunk')
|
||||
|
||||
// Test services page
|
||||
response = await fetch(`${APP_URL}/szolgaltatasok`)
|
||||
response = await fetch(`${APP_URL}/hu/szolgaltatasok`)
|
||||
expect(response.status).toBe(200)
|
||||
html = await response.text()
|
||||
expect(html).toContain('Szolgáltatásaink')
|
||||
|
||||
// Test contact page
|
||||
response = await fetch(`${APP_URL}/kapcsolat`)
|
||||
response = await fetch(`${APP_URL}/hu/kapcsolat`)
|
||||
expect(response.status).toBe(200)
|
||||
html = await response.text()
|
||||
expect(html).toContain('Kapcsolat')
|
||||
@@ -210,9 +214,9 @@ describe('Docker E2E Tests', () => {
|
||||
|
||||
const pages = [
|
||||
{ url: '', title: 'mozdIT Bt.' },
|
||||
{ url: '/rolunk', title: 'Rólunk' },
|
||||
{ url: '/szolgaltatasok', title: 'Szolgáltatásaink' },
|
||||
{ url: '/kapcsolat', title: 'Kapcsolatfelvétel' }
|
||||
{ url: '/hu/rolunk', title: 'Rólunk' },
|
||||
{ url: '/hu/szolgaltatasok', title: 'Szolgáltatásaink' },
|
||||
{ url: '/hu/kapcsolat', title: 'Kapcsolat' }
|
||||
]
|
||||
|
||||
for (const page of pages) {
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
/**
|
||||
* Integration tests for the Docker development environment
|
||||
* These tests run against the real services in the Docker stack
|
||||
*
|
||||
* MITHOME-96: updated for the Payload CMS migration (MITHOME-85 epic) —
|
||||
* routes are now locale-prefixed (/hu/..., /en/...) and content lives in
|
||||
* Payload collections/globals (`globals`, `legal-pages`, `partners`,
|
||||
* `contact-submissions`), not the old raw `site_config`/`contact_submissions`
|
||||
* Mongoose collections. Assumes the Docker dev DB already has content
|
||||
* migrated (`npm run migrate:content`) — see docs/backups for a snapshot if
|
||||
* you need to reseed a throwaway environment.
|
||||
*/
|
||||
|
||||
import { MongoClient, ObjectId } from 'mongodb'
|
||||
@@ -72,14 +80,18 @@ describe('Docker Environment Integration Tests', () => {
|
||||
|
||||
const mozditDb = mongoClient.db('mozdit')
|
||||
const collections = await mozditDb.listCollections().toArray()
|
||||
|
||||
|
||||
const collectionNames = collections.map(c => c.name)
|
||||
expect(collectionNames).toContain('site_config')
|
||||
expect(collectionNames).toContain('contact_submissions')
|
||||
// Payload Globals (Home/About/Services/Contact/Common) live in a
|
||||
// single `globals` collection, one document per global.
|
||||
expect(collectionNames).toContain('globals')
|
||||
expect(collectionNames).toContain('legal-pages')
|
||||
expect(collectionNames).toContain('partners')
|
||||
expect(collectionNames).toContain('contact-submissions')
|
||||
expect(collectionNames).toContain('users')
|
||||
})
|
||||
|
||||
it('should verify site config data exists', async () => {
|
||||
it('should verify the Home global exists and is published', async () => {
|
||||
if (process.env.NODE_ENV === 'test' && !process.env.INTEGRATION_TESTS) {
|
||||
return
|
||||
}
|
||||
@@ -88,17 +100,14 @@ describe('Docker Environment Integration Tests', () => {
|
||||
mongoClient = new MongoClient(DOCKER_SERVICES.mongodb)
|
||||
await mongoClient.connect()
|
||||
}
|
||||
|
||||
const mozditDb = mongoClient.db('mozdit')
|
||||
const siteConfig = await mozditDb.collection('site_config').findOne()
|
||||
|
||||
if (!siteConfig) throw new Error('site_config document not found')
|
||||
expect(siteConfig).toBeTruthy()
|
||||
expect(siteConfig).toHaveProperty('type', 'site_config')
|
||||
expect(siteConfig).toHaveProperty('environment', 'development')
|
||||
expect(siteConfig).toHaveProperty('data')
|
||||
expect(siteConfig.data).toHaveProperty('general')
|
||||
expect(siteConfig.data.general).toHaveProperty('name', 'mozdIT Bt.')
|
||||
const mozditDb = mongoClient.db('mozdit')
|
||||
const home = await mozditDb.collection('globals').findOne({ globalType: 'home' })
|
||||
|
||||
if (!home) throw new Error('Home global document not found')
|
||||
expect(home).toHaveProperty('_status', 'published')
|
||||
expect(home).toHaveProperty('hero')
|
||||
expect(home.hero).toHaveProperty('title')
|
||||
})
|
||||
|
||||
it('should access Mongo Express UI', async () => {
|
||||
@@ -179,7 +188,7 @@ describe('Docker Environment Integration Tests', () => {
|
||||
}
|
||||
const savedSubmission = await mongoClient
|
||||
.db('mozdit')
|
||||
.collection('contact_submissions')
|
||||
.collection('contact-submissions')
|
||||
.findOne({ _id: new ObjectId(result.submissionId) })
|
||||
expect(savedSubmission).toEqual(expect.objectContaining({
|
||||
name: contactData.name,
|
||||
@@ -256,7 +265,7 @@ describe('Docker Environment Integration Tests', () => {
|
||||
|
||||
const result = await response.json()
|
||||
if (response.status === 400) {
|
||||
expect(result).toHaveProperty('error', 'Spam gyanús tartalom észlelve')
|
||||
expect(result).toHaveProperty('error', 'Az üzenet spam gyanús tartalmat tartalmaz.')
|
||||
} else if (response.status === 429) {
|
||||
expect(result).toHaveProperty('error')
|
||||
expect(result.error).toContain('Túl sok')
|
||||
@@ -270,13 +279,14 @@ describe('Docker Environment Integration Tests', () => {
|
||||
return
|
||||
}
|
||||
|
||||
// MITHOME-91/114: bare "/" 307-redirects to "/hu" — fetch follows
|
||||
// redirects by default, so this still lands on the real homepage.
|
||||
const response = await fetch(DOCKER_SERVICES.app)
|
||||
expect(response.status).toBe(200)
|
||||
|
||||
|
||||
const html = await response.text()
|
||||
expect(html).toContain('mozdIT Bt.')
|
||||
expect(html).toContain('Megbízható web‑ és email‑szolgáltatás')
|
||||
expect(html).toContain('Webmail Ugrás')
|
||||
expect(html).toContain('Megbízható web- és email szolgáltatás')
|
||||
})
|
||||
|
||||
it('should load about page', async () => {
|
||||
@@ -284,9 +294,9 @@ describe('Docker Environment Integration Tests', () => {
|
||||
return
|
||||
}
|
||||
|
||||
const response = await fetch(`${DOCKER_SERVICES.app}/rolunk`)
|
||||
const response = await fetch(`${DOCKER_SERVICES.app}/hu/rolunk`)
|
||||
expect(response.status).toBe(200)
|
||||
|
||||
|
||||
const html = await response.text()
|
||||
expect(html).toContain('Rólunk')
|
||||
expect(html).toContain('mozdIT Bt.')
|
||||
@@ -297,14 +307,14 @@ describe('Docker Environment Integration Tests', () => {
|
||||
return
|
||||
}
|
||||
|
||||
const response = await fetch(`${DOCKER_SERVICES.app}/szolgaltatasok`)
|
||||
const response = await fetch(`${DOCKER_SERVICES.app}/hu/szolgaltatasok`)
|
||||
expect(response.status).toBe(200)
|
||||
|
||||
|
||||
const html = await response.text()
|
||||
expect(html).toContain('Szolgáltatásaink')
|
||||
expect(html).toContain('Web Hosting')
|
||||
expect(html).toContain('Email Szolgáltatás')
|
||||
expect(html).toContain('DNS Adminisztráció')
|
||||
expect(html).toContain('Webtárhely')
|
||||
expect(html).toContain('E-mail szolgáltatás')
|
||||
expect(html).toContain('DNS adminisztráció')
|
||||
})
|
||||
|
||||
it('should load contact page', async () => {
|
||||
@@ -312,9 +322,9 @@ describe('Docker Environment Integration Tests', () => {
|
||||
return
|
||||
}
|
||||
|
||||
const response = await fetch(`${DOCKER_SERVICES.app}/kapcsolat`)
|
||||
const response = await fetch(`${DOCKER_SERVICES.app}/hu/kapcsolat`)
|
||||
expect(response.status).toBe(200)
|
||||
|
||||
|
||||
const html = await response.text()
|
||||
// The page title is "Kapcsolat" not "Kapcsolatfelvétel"
|
||||
expect(html).toContain('Kapcsolat')
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
/**
|
||||
* MITHOME-96 — Payload collection/global config tesztek.
|
||||
*
|
||||
* WHY nem a teljes payload.config.ts-t importáljuk: az a valódi `payload` és
|
||||
* `@payloadcms/db-mongodb` csomagokat importálja értékként (nem csak
|
||||
* típusként), amik ESM-only, natív node_modules-forrást futtatnak — Jest
|
||||
* (a next/jest transzformmal is) nem tudja lefordítani őket anélkül, hogy a
|
||||
* teljes payload-függőségi fát is a transformIgnorePatterns kivételévé
|
||||
* tennénk (próbáltuk: `SyntaxError: Cannot use import statement outside a
|
||||
* module` a payload csomag saját forrásából). Az egyes Collection/Global
|
||||
* fájlok viszont csak `import type` formában hivatkoznak a `payload`
|
||||
* csomagra — ez típus-only import, a build kitörli, így ezek a fájlok
|
||||
* önmagukban, gyors unit tesztként importálhatók, élő MongoDB/PAYLOAD_SECRET
|
||||
* nélkül. Ez fedi le a ticket "Payload config tesztek" részét; a Local API
|
||||
* ellen futó, élő adatbázist igénylő tesztek külön (integrációs) fájlban
|
||||
* vannak — lásd src/__tests__/payload-local-api.test.ts.
|
||||
*
|
||||
* Ezek a tesztek szándékosan regresszió-őrök is: mindkettő egy-egy valódi,
|
||||
* élesben megtalált hibát rögzít (MITHOME-121: Media publikus olvasása,
|
||||
* Partners.logo opcionalitása), hogy soha többé ne térjenek vissza észrevétlenül.
|
||||
*/
|
||||
import { Users } from './collections/Users'
|
||||
import { LegalPages } from './collections/LegalPages'
|
||||
import { Media } from './collections/Media'
|
||||
import { Partners } from './collections/Partners'
|
||||
import { ContactSubmissions } from './collections/ContactSubmissions'
|
||||
import { Home } from './globals/Home'
|
||||
import { About } from './globals/About'
|
||||
import { Services } from './globals/Services'
|
||||
import { Contact } from './globals/Contact'
|
||||
import { Common } from './globals/Common'
|
||||
|
||||
type FieldLike = { name?: string; required?: boolean; type?: string }
|
||||
|
||||
function findField(fields: unknown[], name: string): FieldLike | undefined {
|
||||
return (fields as FieldLike[]).find((f) => f.name === name)
|
||||
}
|
||||
|
||||
describe('Payload collections', () => {
|
||||
it('all collection slugs are present and unique', () => {
|
||||
const slugs = [Users, LegalPages, Media, Partners, ContactSubmissions].map((c) => c.slug)
|
||||
expect(slugs).toEqual(['users', 'legal-pages', 'media', 'partners', 'contact-submissions'])
|
||||
expect(new Set(slugs).size).toBe(slugs.length)
|
||||
})
|
||||
|
||||
describe('Media', () => {
|
||||
// Regresszió-őr — MITHOME-121: a Media collection read access-e sokáig
|
||||
// (MITHOME-89 óta) alapértelmezetten csak bejelentkezett usernek volt
|
||||
// engedélyezett, emiatt a partner logók sosem töltődtek be a publikus
|
||||
// oldalon (403 -> a Next.js image-optimizer "nem érvényes kép" hibája).
|
||||
it('allows public (unauthenticated) read access', () => {
|
||||
expect(Media.access?.read).toBeDefined()
|
||||
const result = Media.access!.read!({ req: { user: null } } as never)
|
||||
expect(result).toBe(true)
|
||||
})
|
||||
|
||||
it('only accepts image uploads', () => {
|
||||
expect(Media.upload).toEqual(expect.objectContaining({ mimeTypes: ['image/*'] }))
|
||||
})
|
||||
})
|
||||
|
||||
describe('Partners', () => {
|
||||
it('requires name and url', () => {
|
||||
expect(findField(Partners.fields, 'name')?.required).toBe(true)
|
||||
expect(findField(Partners.fields, 'url')?.required).toBe(true)
|
||||
})
|
||||
|
||||
// Regresszió-őr — MITHOME-121: a felhasználó kérésére a logo mező
|
||||
// opcionálissá vált, hogy egy partner logó nélkül is menthető legyen.
|
||||
it('does not require a logo', () => {
|
||||
const logo = findField(Partners.fields, 'logo')
|
||||
expect(logo).toBeDefined()
|
||||
expect(logo?.required).not.toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('ContactSubmissions', () => {
|
||||
// WHY nincs egyedi access blokk itt tesztelve mint "hiányzik": a Payload
|
||||
// defaultAccess (csak bejelentkezett user) itt a kívánt viselkedés —
|
||||
// lásd a collection saját WHY-kommentjét. Nincs mit tesztelni rajta
|
||||
// (nincs felülírás), de a mezőszerkezetet igen.
|
||||
it('requires the core submission fields', () => {
|
||||
for (const name of ['name', 'email', 'subject', 'message', 'gdprConsent']) {
|
||||
expect(findField(ContactSubmissions.fields, name)?.required).toBe(true)
|
||||
}
|
||||
})
|
||||
|
||||
it('defaults status to "new"', () => {
|
||||
const status = findField(ContactSubmissions.fields, 'status') as FieldLike & {
|
||||
defaultValue?: string
|
||||
options?: { value: string }[]
|
||||
}
|
||||
expect(status?.defaultValue).toBe('new')
|
||||
expect(status?.options?.map((o) => o.value)).toEqual(['new', 'read', 'replied', 'archived'])
|
||||
})
|
||||
})
|
||||
|
||||
describe('LegalPages', () => {
|
||||
it('has a required, unique slug field', () => {
|
||||
const slug = findField(LegalPages.fields, 'slug') as FieldLike & { unique?: boolean }
|
||||
expect(slug?.required).toBe(true)
|
||||
expect(slug?.unique).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Users', () => {
|
||||
it('locks accounts after 5 failed attempts for 10 minutes', () => {
|
||||
expect(Users.auth).toEqual(
|
||||
expect.objectContaining({ maxLoginAttempts: 5, lockTime: 10 * 60 * 1000 })
|
||||
)
|
||||
})
|
||||
|
||||
it('requires an authenticated user for every access-controlled operation', () => {
|
||||
const access = Users.access!
|
||||
for (const op of ['create', 'read', 'update', 'delete', 'unlock'] as const) {
|
||||
expect(access[op]!({ req: { user: null } } as never)).toBe(false)
|
||||
expect(access[op]!({ req: { user: {} } } as never)).toBe(true)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('Payload globals', () => {
|
||||
it('all global slugs are present and unique', () => {
|
||||
const slugs = [Home, About, Services, Contact, Common].map((g) => g.slug)
|
||||
expect(slugs).toEqual(['home', 'about', 'services', 'contact', 'common'])
|
||||
expect(new Set(slugs).size).toBe(slugs.length)
|
||||
})
|
||||
|
||||
it('Home does not duplicate the Partners collection', () => {
|
||||
// MITHOME-89 döntés: a partnerek önálló collection-ök, a Home global
|
||||
// szándékosan nem tartalmaz "partners" mezőt.
|
||||
expect(findField(Home.fields, 'partners')).toBeUndefined()
|
||||
})
|
||||
|
||||
it('Common.buttons.* fields are localized (MITHOME-110 bizonyíték)', () => {
|
||||
const buttonsGroup = findField(Common.fields, 'buttons') as FieldLike & {
|
||||
fields?: (FieldLike & { localized?: boolean })[]
|
||||
}
|
||||
expect(buttonsGroup?.fields?.length).toBeGreaterThan(0)
|
||||
for (const field of buttonsGroup!.fields!) {
|
||||
expect(field.localized).toBe(true)
|
||||
}
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user