You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

160 lines
5.4 KiB
TypeScript

/**
* E2E test: Sources CRUD via API.
*
* Validates:
* 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 and delete sources via API', async ({ page }) => {
// Step 1: Login as regular user via cookie injection
await loginAsUser(page);
await page.goto('/', { waitUntil: 'domcontentloaded' });
await page.waitForLoadState('domcontentloaded');
// 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 { status: resp.status, data: await resp.json() };
});
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,
}),
});
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);
// 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: Mark a source as preferred via API
const sourceId2 = addResp2.data.id;
const prefResp = await page.evaluate(async ({ ids, tid }: { ids: string[]; tid: string }) => {
const resp = await fetch('/api/v1/sources/preferred', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest',
},
credentials: 'same-origin',
body: JSON.stringify({ source_ids: ids, theme_id: tid }),
});
return { status: resp.status };
}, { ids: [sourceId2], tid: themeId });
expect(prefResp.status).toBe(204);
// Step 9: Verify source is now preferred
const listResp3 = 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(listResp3.status).toBe(200);
const preferred = listResp3.data.filter((s: any) => s.is_preferred);
expect(preferred.length).toBe(1);
expect(preferred[0].id).toBe(sourceId2);
// Step 10: 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);
});
});