/** * Shared per-test fixture helpers for library / collection / news-subscription * UI tests. * * Seed and clean up a collection or a news subscription via the synchronous API * so tests don't depend on pre-existing DB state. These mirror the helpers proven in * test_crud_operations_ci.js (#4174/#4180/#4187); they live here now that a * second test file needs them. Each cleanup wraps page.evaluate * in a Node-side try/catch so a torn-down page during teardown can never throw * and mask a test result. * * All helpers read the CSRF token from the current page's meta tag, so the page * must already be on a same-origin app page before calling them. */ async function seedCollection(page) { const r = await page.evaluate(async () => { const csrf = document.querySelector('meta[name="csrf-token"]')?.content; // Best-effort uniqueness (not guaranteed): timestamp + random suffix. const name = `ldr-ui-test-collection-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`; const res = await fetch('/library/api/collections', { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': csrf || '' }, body: JSON.stringify({ name, description: 'UI test fixture', type: 'user_uploads' }), }); const body = await res.json().catch(() => ({})); return { ok: res.ok, name, success: body?.success === true, id: body?.collection?.id }; }); return r.ok && r.success && r.id ? { id: r.id, name: r.name } : null; } async function deleteCollection(page, collectionId) { if (!collectionId) return; try { await page.evaluate(async (id) => { const csrf = document.querySelector('meta[name="csrf-token"]')?.content; try { await fetch(`/library/api/collections/${id}`, { method: 'DELETE', credentials: 'same-origin', headers: { 'X-CSRFToken': csrf || '' }, }); } catch { /* swallow fetch errors */ } }, collectionId); } catch { /* swallow page.evaluate errors so cleanup never masks the test result */ } } async function seedSubscription(page, { isActive = true } = {}) { const r = await page.evaluate(async (active) => { const csrf = document.querySelector('meta[name="csrf-token"]')?.content; // Best-effort uniqueness (not guaranteed): timestamp + random suffix. const query = `ldr-ui-test-sub-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`; const res = await fetch('/news/api/subscribe', { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': csrf || '' }, body: JSON.stringify({ query, subscription_type: 'search', is_active: active }), }); const body = await res.json().catch(() => ({})); return { ok: res.ok, status: res.status, query, id: body?.subscription_id }; }, isActive); return r.ok && r.id ? { id: r.id, query: r.query } : null; } async function deleteSubscription(page, subscriptionId) { if (!subscriptionId) return; try { await page.evaluate(async (id) => { const csrf = document.querySelector('meta[name="csrf-token"]')?.content; try { await fetch(`/news/api/subscriptions/${id}`, { method: 'DELETE', credentials: 'same-origin', headers: { 'X-CSRFToken': csrf || '' }, }); } catch { /* swallow fetch errors */ } }, subscriptionId); } catch { /* swallow page.evaluate errors so cleanup never masks the test result */ } } // NOTE: a document-upload helper lives in test_crud_operations_ci.js. It is // intentionally NOT exported here yet: the only consumer (documentCardActions) // is deferred until it can clean up the uploaded doc (collection delete doesn't // cascade to docs — see test_library_documents_ci.js). When that lands, add // uploadFixtureDocument here returning the document id(s) so callers can delete // the doc, not just the collection. module.exports = { seedCollection, deleteCollection, seedSubscription, deleteSubscription };