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
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);
|
|
});
|
|
});
|