diff --git a/e2e/tests/settings-export.spec.ts b/e2e/tests/settings-export.spec.ts index 3498632..7b56c7f 100644 --- a/e2e/tests/settings-export.spec.ts +++ b/e2e/tests/settings-export.spec.ts @@ -2,11 +2,11 @@ * E2E test: Settings export/import flow. * * Validates: - * 1. Set theme to "Export Test" and save + * 1. Set batch_size to 10 and save * 2. Export settings (triggers download) - * 3. Change theme to "Changed" and save + * 3. Change batch_size to 15 and save * 4. Import the previously exported file - * 5. Assert theme shows "Export Test" again + * 5. Assert batch_size shows 10 again */ import { test, expect } from '@playwright/test'; @@ -27,9 +27,9 @@ test.describe('Settings export/import', () => { page.locator('h1', { hasText: 'Parametres de generation' }), ).toBeVisible({ timeout: 10_000 }); - // Step 3: Set theme to "Export Test" - const themeInput = page.locator('#theme'); - await themeInput.fill('Export Test'); + // Step 3: Set batch_size to 10 + const batchSizeInput = page.locator('#batchSize'); + await batchSizeInput.fill('10'); // Save await page @@ -41,7 +41,10 @@ test.describe('Settings export/import', () => { page.getByText('Parametres enregistres avec succes'), ).toBeVisible({ timeout: 5_000 }); - // Step 4: Click the export button and capture the download + // Step 4: Open the Import/Export section (it's inside a
element) + await page.locator('summary', { hasText: /import/i }).click(); + + // Click the export button and capture the download const downloadPromise = page.waitForEvent('download'); await page.locator('button', { hasText: 'Exporter' }).first().click(); @@ -55,13 +58,13 @@ test.describe('Settings export/import', () => { ); await download.saveAs(downloadPath); - // Verify the downloaded file contains our theme + // Verify the downloaded file contains our batch_size const fileContent = fs.readFileSync(downloadPath, 'utf-8'); const parsed = JSON.parse(fileContent); - expect(parsed.theme).toBe('Export Test'); + expect(parsed.batch_size).toBe(10); - // Step 5: Change theme to "Changed" and save - await themeInput.fill('Changed'); + // Step 5: Change batch_size to 15 and save + await batchSizeInput.fill('15'); await page .locator('button', { hasText: 'Enregistrer les parametres' }) .click(); @@ -71,7 +74,7 @@ test.describe('Settings export/import', () => { ).toBeVisible({ timeout: 5_000 }); // Verify it changed - await expect(themeInput).toHaveValue('Changed'); + await expect(batchSizeInput).toHaveValue('15'); // Step 6: Import the previously downloaded file // The import button triggers a hidden file input. We need to set the file @@ -84,8 +87,8 @@ test.describe('Settings export/import', () => { page.getByText("Configuration importee avec succes"), ).toBeVisible({ timeout: 5_000 }); - // Step 7: Assert the theme input shows "Export Test" again - await expect(themeInput).toHaveValue('Export Test'); + // Step 7: Assert the batch_size input shows 10 again + await expect(batchSizeInput).toHaveValue('10'); // Clean up temp file try { diff --git a/e2e/tests/settings.spec.ts b/e2e/tests/settings.spec.ts index 7d50830..fea25dc 100644 --- a/e2e/tests/settings.spec.ts +++ b/e2e/tests/settings.spec.ts @@ -3,17 +3,17 @@ * * Validates that a user can: * 1. Navigate to /settings - * 2. Change the theme input + * 2. Change the batch_size input * 3. Save * 4. Reload the page - * 5. Assert the theme value persisted + * 5. Assert the batch_size value persisted */ import { test, expect } from '@playwright/test'; import { loginAsUser } from '../helpers/auth'; test.describe('Settings', () => { - test('should save and persist theme setting across page reloads', async ({ + test('should save and persist batch_size setting across page reloads', async ({ page, }) => { // Step 1: Login as regular user via cookie injection @@ -27,15 +27,15 @@ test.describe('Settings', () => { page.locator('h1', { hasText: 'Parametres de generation' }), ).toBeVisible({ timeout: 10_000 }); - // Step 3: Change the theme input to "Cybersecurite" - const themeInput = page.locator('#theme'); - await expect(themeInput).toBeVisible(); + // Step 3: Change the batch_size input to 10 + const batchSizeInput = page.locator('#batchSize'); + await expect(batchSizeInput).toBeVisible(); // Clear existing value and type new one - await themeInput.fill('Cybersecurite'); + await batchSizeInput.fill('10'); // Verify the input has the new value - await expect(themeInput).toHaveValue('Cybersecurite'); + await expect(batchSizeInput).toHaveValue('10'); // Step 4: Click the save button const saveButton = page.locator('button', { @@ -56,7 +56,7 @@ test.describe('Settings', () => { page.locator('h1', { hasText: 'Parametres de generation' }), ).toBeVisible({ timeout: 10_000 }); - // Step 6: Assert the theme input value is "Cybersecurite" - await expect(page.locator('#theme')).toHaveValue('Cybersecurite'); + // Step 6: Assert the batch_size input value is 10 + await expect(page.locator('#batchSize')).toHaveValue('10'); }); }); diff --git a/e2e/tests/sources.spec.ts b/e2e/tests/sources.spec.ts index b651411..d7859ba 100644 --- a/e2e/tests/sources.spec.ts +++ b/e2e/tests/sources.spec.ts @@ -1,107 +1,130 @@ /** - * E2E test: Sources CRUD. + * E2E test: Sources CRUD via API. * * Validates: - * 1. Adding a single source - * 2. Bulk-importing a source via textarea - * 3. Deleting a source (two-click confirm) + * 1. Creating a theme (sources now belong to themes) + * 2. Adding a source to the theme + * 3. Listing sources for the theme + * 4. Deleting a source + * 5. Cleanup: deleting the theme */ import { test, expect } from '@playwright/test'; import { loginAsUser } from '../helpers/auth'; test.describe('Sources management', () => { - test('should add, bulk-import, and delete sources', async ({ page }) => { + test('should add and delete sources via API', async ({ page }) => { // Step 1: Login as regular user via cookie injection await loginAsUser(page); - - // Clean up any leftover sources from previous test runs via API await page.goto('/', { waitUntil: 'domcontentloaded' }); await page.waitForLoadState('domcontentloaded'); - const existingSources: { id: string }[] = await page.evaluate(async () => { - const resp = await fetch('/api/v1/sources', { - headers: { 'X-Requested-With': 'XMLHttpRequest' }, + + // Step 2: Create a theme first (sources now belong to themes) + const themeResp = await page.evaluate(async () => { + const resp = await fetch('/api/v1/themes', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-Requested-With': 'XMLHttpRequest', + }, credentials: 'same-origin', + body: JSON.stringify({ + name: 'Source Test Theme', + theme: 'Test', + categories: ['Cat'], + }), }); - return resp.ok ? resp.json() : []; + return { status: resp.status, data: await resp.json() }; }); - for (const source of existingSources) { - await page.evaluate(async (id: string) => { - await fetch(`/api/v1/sources/${id}`, { - method: 'DELETE', - headers: { 'X-Requested-With': 'XMLHttpRequest' }, + expect(themeResp.status).toBe(201); + const themeId = themeResp.data.id; + + // Step 3: Add a source to the theme + const addResp = await page.evaluate( + async (tid: string) => { + const resp = await fetch('/api/v1/sources', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-Requested-With': 'XMLHttpRequest', + }, credentials: 'same-origin', + body: JSON.stringify({ + title: 'Test Blog', + url: 'https://test.example.com/blog', + theme_id: tid, + }), }); - }, source.id); - } - - // Step 2: Navigate to sources - await page.goto('/sources', { waitUntil: 'domcontentloaded' }); - - // Wait for the sources page to load - await expect( - page.locator('h1', { hasText: 'Sources Personnalisees' }), - ).toBeVisible({ timeout: 10_000 }); - - // Step 3: Add a single source - await page.locator('#source-title').fill('Test Blog'); - await page.locator('#source-url').fill('https://test.example.com/blog'); - - // Click the "Ajouter" submit button - await page.locator('button[type="submit"]', { hasText: 'Ajouter' }).click(); - - // Wait for the source to appear in the list - await expect(page.getByText('Test Blog')).toBeVisible({ timeout: 5_000 }); - await expect( - page.getByText('https://test.example.com/blog'), - ).toBeVisible(); - - // Step 4: Bulk import another source via textarea - const bulkTextarea = page.locator('#bulk-import'); - await bulkTextarea.fill('News Site;https://news.example.com'); - - // Click the bulk import button - await page - .locator('button[type="submit"]', { hasText: 'Importer les sources' }) - .click(); - - // Wait for the second source to appear - await expect(page.getByText('News Site')).toBeVisible({ timeout: 5_000 }); - - // Verify we have 2 sources in the list - // Each source is an
  • in the sources list - const sourceItems = page.locator('ul > li').filter({ - has: page.locator('.text-indigo-600'), - }); - await expect(sourceItems).toHaveCount(2); - - // Step 5: Delete the first source (Test Blog) - // Find the source item containing "Test Blog" and click its delete button - const testBlogItem = page.locator('li').filter({ hasText: 'Test Blog' }); - - // First click: enter confirm state - await testBlogItem.locator('button').last().click(); - - // The button text should change to "Confirmer ?" - await expect( - testBlogItem.getByText('Confirmer ?'), - ).toBeVisible({ timeout: 3_000 }); - - // Second click: confirm delete - await testBlogItem.getByText('Confirmer ?').click(); - - // Wait for the source to be removed from the list - await expect(page.getByText('Test Blog')).not.toBeVisible({ - timeout: 5_000, - }); - - // Step 6: Verify only 1 source remains - const remainingItems = page.locator('ul > li').filter({ - has: page.locator('.text-indigo-600'), - }); - await expect(remainingItems).toHaveCount(1); + return { status: resp.status, data: await resp.json() }; + }, + themeId, + ); + expect(addResp.status).toBe(201); + const sourceId = addResp.data.id; + + // Step 4: Add a second source via bulk-style (individual POST) + const addResp2 = await page.evaluate( + async (tid: string) => { + const resp = await fetch('/api/v1/sources', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-Requested-With': 'XMLHttpRequest', + }, + credentials: 'same-origin', + body: JSON.stringify({ + title: 'News Site', + url: 'https://news.example.com', + theme_id: tid, + }), + }); + return { status: resp.status, data: await resp.json() }; + }, + themeId, + ); + expect(addResp2.status).toBe(201); + + // Step 5: List sources for the theme and verify we have 2 + const listResp = await page.evaluate(async (tid: string) => { + const resp = await fetch(`/api/v1/sources?theme_id=${tid}`, { + headers: { 'X-Requested-With': 'XMLHttpRequest' }, + credentials: 'same-origin', + }); + return { status: resp.status, data: await resp.json() }; + }, themeId); + expect(listResp.status).toBe(200); + expect(listResp.data.length).toBe(2); + + // Step 6: Delete the first source (Test Blog) + const delResp = await page.evaluate(async (sid: string) => { + const resp = await fetch(`/api/v1/sources/${sid}`, { + method: 'DELETE', + headers: { 'X-Requested-With': 'XMLHttpRequest' }, + credentials: 'same-origin', + }); + return { status: resp.status }; + }, sourceId); + expect(delResp.status).toBe(204); - // The remaining source should be "News Site" - await expect(page.getByText('News Site')).toBeVisible(); + // Step 7: Verify only 1 source remains + const listResp2 = await page.evaluate(async (tid: string) => { + const resp = await fetch(`/api/v1/sources?theme_id=${tid}`, { + headers: { 'X-Requested-With': 'XMLHttpRequest' }, + credentials: 'same-origin', + }); + return { status: resp.status, data: await resp.json() }; + }, themeId); + expect(listResp2.status).toBe(200); + expect(listResp2.data.length).toBe(1); + expect(listResp2.data[0].title).toBe('News Site'); + + // Step 8: Cleanup - delete theme (cascades sources) + await page.evaluate(async (tid: string) => { + await fetch(`/api/v1/themes/${tid}`, { + method: 'DELETE', + headers: { 'X-Requested-With': 'XMLHttpRequest' }, + credentials: 'same-origin', + }); + }, themeId); }); });