fix(e2e): implement automatic cleanup of test form submissions with authorized secret header

This commit is contained in:
2026-04-21 19:26:06 +02:00
parent f989e0604f
commit f87c714402
3 changed files with 39 additions and 13 deletions

View File

@@ -532,6 +532,7 @@ jobs:
env: env:
NEXT_PUBLIC_BASE_URL: ${{ needs.prepare.outputs.next_public_url }} NEXT_PUBLIC_BASE_URL: ${{ needs.prepare.outputs.next_public_url }}
GATEKEEPER_PASSWORD: ${{ secrets.GATEKEEPER_PASSWORD || 'klz2026' }} GATEKEEPER_PASSWORD: ${{ secrets.GATEKEEPER_PASSWORD || 'klz2026' }}
PAYLOAD_SECRET: ${{ secrets.PAYLOAD_SECRET || vars.PAYLOAD_SECRET }}
PUPPETEER_EXECUTABLE_PATH: /usr/bin/chromium PUPPETEER_EXECUTABLE_PATH: /usr/bin/chromium
run: pnpm run check:forms run: pnpm run check:forms

View File

@@ -348,13 +348,23 @@ async function main() {
// 5. Cleanup: Delete test submissions from Payload CMS // 5. Cleanup: Delete test submissions from Payload CMS
console.log(`\n🧹 Starting cleanup of test submissions...`); console.log(`\n🧹 Starting cleanup of test submissions...`);
const payloadSecret = process.env.PAYLOAD_SECRET;
if (!payloadSecret) {
console.warn(` ⚠️ PAYLOAD_SECRET not found in environment. Cleanup will likely fail with 403.`);
}
try { try {
const apiUrl = `${targetUrl.replace(/\/$/, '')}/api/form-submissions`; const apiUrl = `${targetUrl.replace(/\/$/, '')}/api/form-submissions`;
const searchUrl = `${apiUrl}?where[email][equals]=testing@mintel.me`; // We fetch ALL submissions matching the test email to clean up potential lefovers from previous runs
const searchUrl = `${apiUrl}?where[email][equals]=testing@mintel.me&limit=100`;
// Fetch test submissions // Fetch test submissions with bypass header
const searchResponse = await axios.get(searchUrl, { const searchResponse = await axios.get(searchUrl, {
headers: { Cookie: `klz_gatekeeper_session=${gatekeeperPassword}` }, headers: {
Cookie: `klz_gatekeeper_session=${gatekeeperPassword}`,
'x-e2e-secret': payloadSecret || '',
},
}); });
const testSubmissions = searchResponse.data.docs || []; const testSubmissions = searchResponse.data.docs || [];
@@ -363,25 +373,30 @@ async function main() {
for (const doc of testSubmissions) { for (const doc of testSubmissions) {
try { try {
await axios.delete(`${apiUrl}/${doc.id}`, { await axios.delete(`${apiUrl}/${doc.id}`, {
headers: { Cookie: `klz_gatekeeper_session=${gatekeeperPassword}` }, headers: {
Cookie: `klz_gatekeeper_session=${gatekeeperPassword}`,
'x-e2e-secret': payloadSecret || '',
},
}); });
console.log(` ✅ Deleted submission: ${doc.id}`); console.log(` ✅ Deleted submission: ${doc.id} (${doc.name})`);
} catch (delErr: any) { } catch (delErr: any) {
// Log but don't fail, 403s on Directus / Payload APIs for guest Gatekeeper sessions are normal
console.warn( console.warn(
` ⚠️ Cleanup attempt on ${doc.id} returned an error, typically due to API Auth separation: ${delErr.message}`, ` ⚠️ Cleanup attempt on ${doc.id} failed: ${delErr.message}`,
); );
} }
} }
if (testSubmissions.length > 0) {
console.log(`✅ Cleanup completed successfully.`);
}
} catch (err: any) { } catch (err: any) {
if (err.response?.status === 403) { if (err.response?.status === 403) {
console.warn( console.error(
` ⚠️ Cleanup fetch failed with 403 Forbidden. This is expected if the runner lacks admin API credentials. Test submissions remain in the database.`, ` Cleanup failed with 403 Forbidden. Ensure FormSubmissions access control and PAYLOAD_SECRET are aligned.`,
); );
} else { } else {
console.error(` ❌ Cleanup fetch failed: ${err.message}`); console.error(` ❌ Cleanup fetch failed: ${err.message}`);
} }
// Don't mark the whole test as failed just because cleanup failed
} }
await browser.close(); await browser.close();

View File

@@ -9,9 +9,19 @@ export const FormSubmissions: CollectionConfig = {
}, },
access: { access: {
// Only Admins can view and delete leads via dashboard. // Only Admins can view and delete leads via dashboard.
read: ({ req: { user } }) => Boolean(user) || process.env.NODE_ENV === 'development', // E2E scripts can bypass if they provide the correct secret header.
update: ({ req: { user } }) => Boolean(user) || process.env.NODE_ENV === 'development', read: ({ req }) => {
delete: ({ req: { user } }) => Boolean(user) || process.env.NODE_ENV === 'development', const isE2E = req.headers.get('x-e2e-secret') === process.env.PAYLOAD_SECRET;
return Boolean(req.user) || isE2E || process.env.NODE_ENV === 'development';
},
update: ({ req }) => {
const isE2E = req.headers.get('x-e2e-secret') === process.env.PAYLOAD_SECRET;
return Boolean(req.user) || isE2E || process.env.NODE_ENV === 'development';
},
delete: ({ req }) => {
const isE2E = req.headers.get('x-e2e-secret') === process.env.PAYLOAD_SECRET;
return Boolean(req.user) || isE2E || process.env.NODE_ENV === 'development';
},
// Next.js server actions handle secure inserts natively. No public client create access. // Next.js server actions handle secure inserts natively. No public client create access.
create: () => false, create: () => false,
}, },