diff --git a/tests/idaa_recovery_meeting_edit.test.ts b/tests/idaa_recovery_meeting_edit.test.ts index 09a37860..e507d551 100644 --- a/tests/idaa_recovery_meeting_edit.test.ts +++ b/tests/idaa_recovery_meeting_edit.test.ts @@ -14,14 +14,14 @@ import { test, expect } from '@playwright/test'; import { ae_app_local_data_defaults } from './_helpers/ae_defaults'; -import { testing_account_id } from './_helpers/env'; +import { testing_account_id, mock_site_domain } from './_helpers/env'; // --- Test fixtures ----------------------------------------------------------- -const TEST_EVENT_ID = 'IDAA_RM_TEST01'; +const TEST_EVENT_ID = '1Pkd025vvxU';// Per README test data const TEST_NOVI_UUID = 'c9ea07b5-06b0-4a43-a2d0-8d06558c8a82'; // in default novi_trusted_li -const TEST_NOVI_NAME = 'IDAA Test Member'; -const TEST_NOVI_EMAIL = 'testmember@idaa.org'; +const TEST_NOVI_NAME = 'IDAA Test Novi Member'; +const TEST_NOVI_EMAIL = 'test+novi-member@oneskyit.com'; /** Minimal mock event object returned by the API. */ const mock_event = { @@ -96,11 +96,58 @@ function build_idaa_loc_defaults(opts: { edit_event_obj?: boolean } = {}) { }; } +/** + * Inject ae_loc + ae_idaa_loc into localStorage so the IDAA layout + * authenticates as a trusted member with the edit form open. + * Must be called via page.addInitScript BEFORE any navigation. + */ +async function setup_idaa_auth(page: any) { + await page.addInitScript( + ({ ae_defaults, account_id, idaa_loc_defaults }: any) => { + const ae_loc_data = { + ...ae_defaults, + account_id, + authenticated_access: true, + trusted_access: true, + administrator_access: false, + access_type: 'trusted', + iframe: false, + site_cfg_json: { + slct__event_id: null, + novi_admin_li: ['2b078deb-b4e7-4203-99da-9f7cd62159a5'], + novi_trusted_li: [ + 'c9ea07b5-06b0-4a43-a2d0-8d06558c8a82', + '58db22ee-4b0a-49a7-9f34-53d2ba85a84b' + ], + admin_email: 'admin@oneskyit.com', + noreply_email: 'noreply@oneskyit.com' + } + }; + window.localStorage.setItem('ae_loc', JSON.stringify(ae_loc_data)); + window.localStorage.setItem('ae_idaa_loc', JSON.stringify(idaa_loc_defaults)); + }, + { + ae_defaults: ae_app_local_data_defaults, + account_id: testing_account_id, + idaa_loc_defaults: build_idaa_loc_defaults({ edit_event_obj: true }) + } + ); +} + /** * Register route mocks for the V3 CRUD API and common lookup tables. * Called inside each test after page creation. + * + * @param pass_through_event_patch - When true, the event PATCH (save) is NOT + * intercepted and will reach the real backend. The GET is still mocked so + * the form loads immediately from seeded IDB. Use this for integration + * tests that need to verify actual persistence. */ -async function setup_api_mocks(page: any, event_id: string) { +async function setup_api_mocks( + page: any, + event_id: string, + opts: { pass_through_event_patch?: boolean } = {} +) { await page.route('**/v3/**', async (route: any) => { const url = route.request().url(); const method = route.request().method(); @@ -116,6 +163,7 @@ async function setup_api_mocks(page: any, event_id: string) { // Event PATCH (update) if (url.includes(`/v3/crud/event/${event_id}`) && method === 'PATCH') { + if (opts.pass_through_event_patch) return route.continue(); return route.fulfill({ status: 200, contentType: 'application/json', @@ -198,12 +246,14 @@ async function setup_api_mocks(page: any, event_id: string) { }); } - // Layout/site domain lookup + // Layout/site domain lookup — must return a proper account_id so the + // layout builds ae_api.headers['x-account-id'] correctly. + // search_ae_obj_v3 expects { data: [array] }, not a single object. if (url.includes('/v3/crud/site_domain') || url.includes('/v3/lookup/')) { return route.fulfill({ status: 200, contentType: 'application/json', - body: JSON.stringify({ data: {} }) + body: JSON.stringify({ data: [mock_site_domain] }) }); } @@ -255,42 +305,7 @@ test.describe('IDAA Recovery Meetings — Edit Form', () => { page.on('pageerror', (err: Error) => console.error(`BROWSER ERROR: ${err.message}`) ); - - // Inject localStorage for both ae_loc and ae_idaa_loc - await page.addInitScript( - ({ ae_defaults, account_id, idaa_loc_defaults }: any) => { - // ae_loc: full authenticated+trusted access - const ae_loc_data = { - ...ae_defaults, - account_id, - authenticated_access: true, - trusted_access: true, - administrator_access: false, - access_type: 'trusted', - iframe: false, - site_cfg_json: { - slct__event_id: null, - novi_admin_li: ['2b078deb-b4e7-4203-99da-9f7cd62159a5'], - novi_trusted_li: [ - 'c9ea07b5-06b0-4a43-a2d0-8d06558c8a82', - '58db22ee-4b0a-49a7-9f34-53d2ba85a84b' - ], - admin_email: 'admin@idaa.org', - noreply_email: 'noreply@idaa.org' - } - }; - window.localStorage.setItem('ae_loc', JSON.stringify(ae_loc_data)); - - // ae_idaa_loc: IDAA-specific state - window.localStorage.setItem('ae_idaa_loc', JSON.stringify(idaa_loc_defaults)); - }, - { - ae_defaults: ae_app_local_data_defaults, - account_id: testing_account_id, - idaa_loc_defaults: build_idaa_loc_defaults({ edit_event_obj: true }) - } - ); - + await setup_idaa_auth(page); await setup_api_mocks(page, TEST_EVENT_ID); }); @@ -457,7 +472,7 @@ test.describe('IDAA Recovery Meetings — Edit Form', () => { await expect(save_btn).toBeEnabled(); }); - test('form submission sends PUT request to event API', async ({ page }) => { + test('form submission sends PATCH request to event API', async ({ page }) => { await page.goto(`/`); await seed_event_idb(page, mock_event); await page.goto(`/idaa/recovery_meetings/${TEST_EVENT_ID}`); @@ -491,6 +506,7 @@ test.describe('IDAA Recovery Meetings — Edit Form', () => { // Section 4: Virtual / Zoom platform section // ------------------------------------------------------------------------- + test('virtual checkbox reveals virtual/platform section', async ({ page }) => { await page.goto(`/`); // Use a virtual-only event @@ -512,3 +528,61 @@ test.describe('IDAA Recovery Meetings — Edit Form', () => { }); }); + +// ============================================================================= +// Integration tests — real backend +// These tests use the real event ID and let CRUD calls reach the actual API. +// They will modify real data in the database. +// ============================================================================= + +test.describe('IDAA Recovery Meetings — Real Backend Save (Integration)', () => { + + test.beforeEach(async ({ page }) => { + page.on('pageerror', (err: Error) => + console.error(`BROWSER ERROR: ${err.message}`) + ); + await setup_idaa_auth(page); + // pass_through_event_patch=true: only PATCH for this event reaches the real API. + // Lookups and GET are still mocked so the form loads instantly from seeded IDB. + await setup_api_mocks(page, TEST_EVENT_ID, { pass_through_event_patch: true }); + }); + + test('save updated meeting name persists to backend', async ({ page }) => { + // Seed IDB so the form populates immediately without waiting for the real API GET. + // The mock_event name will appear in the field; we overwrite it before saving. + await page.goto(`/`); + await seed_event_idb(page, mock_event); + await page.goto(`/idaa/recovery_meetings/${TEST_EVENT_ID}`); + + // Wait for the form field to be populated from IDB via liveQuery + const name_input = page.locator('input[name="name"]'); + await expect(name_input).toBeVisible({ timeout: 15000 }); + await expect(name_input).not.toHaveValue('', { timeout: 8000 }); + + // Read the current name and append a test marker (idempotent: strip any previous marker first) + const current_name = await name_input.inputValue(); + const base_name = current_name.replace(/ \[PW\]$/, '').trim(); + const new_name = `${base_name} [PW]`; + + await name_input.fill(new_name); + + const save_btn = page.locator('button[type="submit"]').filter({ hasText: /save/i }).first(); + await expect(save_btn).toBeEnabled(); + + // Click Save and wait for the real PATCH response + const [response] = await Promise.all([ + page.waitForResponse( + (resp) => + resp.url().includes(`/v3/crud/event/${TEST_EVENT_ID}`) && + resp.request().method() === 'PATCH', + { timeout: 15000 } + ), + save_btn.click() + ]); + + const body = await response.json(); + expect(response.status(), 'PATCH should return HTTP 200').toBe(200); + expect(body?.data?.name ?? body?.data?.event_id, 'Response should include event data').toBeTruthy(); + }); + +});