Files
OSIT-AE-App-Svelte/tests/leads_config.test.ts

237 lines
9.3 KiB
TypeScript

import { test, expect } from '@playwright/test';
import { testing_event_id, mock_site_domain } from './_helpers/env';
import {
minimal_event_for_leads,
seed_ae_loc,
} from './_helpers/leads_helpers';
const event_id = testing_event_id;
const config_url = `/events/${event_id}/leads/config`;
/**
* Minimal route mock for the Leads Config page.
*
* The config page only needs the event record (not exhibit or badge data) plus
* the site_domain init call. A PATCH handler is included for save tests.
*
* @param patch_handler Optional callback invoked when a PATCH to the event is intercepted.
* Defaults to returning 200 OK.
*/
async function setup_config_routes(
page: import('@playwright/test').Page,
opts: {
event_data_overrides?: Record<string, any>;
on_patch?: (body: any) => void;
} = {}
) {
const { event_data_overrides = {}, on_patch } = opts;
await page.route('**/v3/**', async (route) => {
const req = route.request();
const url = req.url();
const method = req.method();
// Site domain init
if (url.includes('site_domain/search')) {
return route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ data: [mock_site_domain] }),
});
}
// Event GET — provides mod_exhibits_json for the draft
if (url.includes(`/v3/crud/event/${event_id}`) && method === 'GET') {
return route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify(minimal_event_for_leads(event_id, event_data_overrides)),
});
}
// Event PATCH — save config
if (url.includes(`/v3/crud/event/${event_id}`) && method === 'PATCH') {
const raw = await req.postData();
const body = raw ? JSON.parse(raw) : {};
on_patch?.(body);
return route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ data: { id: event_id } }),
});
}
return route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ data: [] }),
});
});
}
test.describe('Leads — Config Page', () => {
// -----------------------------------------------------------------------
// 1. Non-admin user sees access-denied screen
//
// The config page is administrator_access only. Any other access level must
// see the Lock icon and "Administrator access required." — no form fields.
// -----------------------------------------------------------------------
test('non-admin user sees access-denied message, no form', async ({ page }) => {
page.on('pageerror', (err) => console.error(`BROWSER ERROR: ${err.message}`));
await setup_config_routes(page);
await seed_ae_loc(page, { allow_access: true, trusted_access: true });
await page.goto(config_url);
// Access denied block must be visible
await expect(
page.locator('text=Administrator access required.')
).toBeVisible({ timeout: 10_000 });
// Form / save button must NOT be visible
await expect(page.locator('text=Leads Config')).not.toBeVisible();
await expect(page.locator('button:has-text("Save")')).not.toBeVisible();
});
// -----------------------------------------------------------------------
// 2. Admin user sees config form
//
// administrator_access = true reveals the full form with both sections
// (Payment and Stripe Keys) and the Save button.
// -----------------------------------------------------------------------
test('admin user sees config form with Payment and Stripe sections', async ({ page }) => {
page.on('pageerror', (err) => console.error(`BROWSER ERROR: ${err.message}`));
await setup_config_routes(page);
await seed_ae_loc(page, {
allow_access: true,
authenticated_access: true,
trusted_access: true,
administrator_access: true,
});
await page.goto(config_url);
// Heading confirms page rendered
await expect(page.locator('h1:has-text("Leads Config")')).toBeVisible({ timeout: 10_000 });
// Both collapsible sections are visible (default open)
// Use the checkbox label text — it's unique inside the Payment section
await expect(page.locator('text=Require Payment (Stripe)')).toBeVisible({ timeout: 5_000 });
await expect(page.locator('text=Stripe Keys')).toBeVisible({ timeout: 5_000 });
// Save button is present — two exist (header + bottom), check the header one
await expect(page.locator('button:has-text("Save")').first()).toBeVisible({ timeout: 5_000 });
});
// -----------------------------------------------------------------------
// 3. Save button disabled until form is dirty
//
// On load, is_dirty = false because draft JSON === initial JSON.
// After toggling the "Require Payment" checkbox, is_dirty = true and the
// Save button becomes enabled.
// -----------------------------------------------------------------------
test('save button disabled by default, enabled after checkbox change', async ({ page }) => {
page.on('pageerror', (err) => console.error(`BROWSER ERROR: ${err.message}`));
// Start with leads_require_payment: false so toggling it makes a real change
await setup_config_routes(page, {
event_data_overrides: {
mod_exhibits_json: { leads_require_payment: false },
},
});
await seed_ae_loc(page, {
allow_access: true,
authenticated_access: true,
trusted_access: true,
administrator_access: true,
});
await page.goto(config_url);
// Wait for event to load and draft to initialize
await page.waitForResponse(
(r) => r.url().includes(`crud/event/${event_id}`) && r.status() === 200,
{ timeout: 8_000 }
);
// Wait for the form to render (draft_initialized)
await expect(
page.locator('input[type="checkbox"]').first()
).toBeVisible({ timeout: 8_000 });
// Save must be disabled (not dirty yet)
await expect(page.locator('button:has-text("Save Config")')).toBeDisabled({ timeout: 3_000 });
// Toggle the "Require Payment" checkbox
await page.locator('input[type="checkbox"]').first().click();
// Save must now be enabled
await expect(page.locator('button:has-text("Save Config")')).toBeEnabled({ timeout: 3_000 });
});
// -----------------------------------------------------------------------
// 4. Save sends PATCH with mod_exhibits_json and shows "Saved" badge
//
// Clicking the Save button must PATCH event.mod_exhibits_json via the V3
// API. The "Saved" badge must appear confirming success.
// -----------------------------------------------------------------------
test('save sends PATCH to event and shows Saved badge', async ({ page }) => {
page.on('pageerror', (err) => console.error(`BROWSER ERROR: ${err.message}`));
let patched_body: any = null;
await setup_config_routes(page, {
event_data_overrides: {
mod_exhibits_json: { leads_require_payment: false },
},
on_patch: (body) => {
patched_body = body;
},
});
await seed_ae_loc(page, {
allow_access: true,
authenticated_access: true,
trusted_access: true,
administrator_access: true,
});
await page.goto(config_url);
// Wait for form to initialize
await page.waitForResponse(
(r) => r.url().includes(`crud/event/${event_id}`) && r.status() === 200,
{ timeout: 8_000 }
);
await expect(
page.locator('input[type="checkbox"]').first()
).toBeVisible({ timeout: 8_000 });
// Make a change to unlock save
await page.locator('input[type="checkbox"]').first().click();
// Intercept the PATCH before clicking save
const patch_promise = page.waitForRequest(
(r) =>
r.url().includes(`crud/event/${event_id}`) &&
r.method() === 'PATCH',
{ timeout: 5_000 }
);
await page.locator('button:has-text("Save Config")').click();
// PATCH must have been made
await patch_promise;
// "Saved" badge must appear
await expect(page.locator('text=Saved')).toBeVisible({ timeout: 5_000 });
// The patched body must include mod_exhibits_json.
// update_ae_obj auto-serializes *_json fields to strings before the PATCH,
// so parse it back to an object before asserting field values.
expect(patched_body?.mod_exhibits_json).toBeDefined();
const patched_cfg =
typeof patched_body.mod_exhibits_json === 'string'
? JSON.parse(patched_body.mod_exhibits_json)
: patched_body.mod_exhibits_json;
expect(patched_cfg?.leads_require_payment).toBe(true);
});
});