fix: migrations, auth, providers health check, E2E tests, remove neko-sama
- Add proper Alembic initial migration (0001_initial_schema.py) - Migrate refresh tokens from JSON file to SQLite (RefreshTokenTable) - Remove Neko-Sama provider entirely (redirects to Gupy, not a host) - Fix provider health check always showing UNKNOWN - Run check_all_health() on startup - Fix POST /providers/health/check background task bug - Add HTMX refresh after manual health check trigger - Fix anime search relevance scoring with MIN_RELEVANCE_THRESHOLD=0.5 - Replace bare 'except:' with 'except Exception:' across codebase - Add Playwright E2E test suite (12 tests, auth setup, helpers) - Fix toast container blocking clicks via pointer-events: none - Remove obsolete Jest/Vite test files and config - Clean up obsolete test_watchlist scripts - Update sonarr model comment for active providers
This commit is contained in:
+59
-85
@@ -1,119 +1,93 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { TEST_USER, login } from './helpers';
|
||||
|
||||
test.describe('Auth Flow', () => {
|
||||
test('login success - redirects to home and stores token', async ({ page }) => {
|
||||
await page.goto('/login');
|
||||
|
||||
// Fill login form
|
||||
await page.fill('#loginUsername', 'testuser');
|
||||
await page.fill('#loginPassword', 'password123');
|
||||
|
||||
// Click login button
|
||||
await page.click('#loginSubmit');
|
||||
|
||||
// Wait for redirect or success message
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// Check if redirected or success message shown
|
||||
const currentUrl = page.url();
|
||||
const successMessage = await page.locator('#authSuccess').textContent().catch(() => '');
|
||||
|
||||
// Either redirect happened or success message shown
|
||||
expect(currentUrl.includes('/web') || successMessage.includes('réussie')).toBeTruthy();
|
||||
await login(page, TEST_USER.username, TEST_USER.password);
|
||||
|
||||
// Verify redirect to /web
|
||||
await expect(page).toHaveURL(/\/web/);
|
||||
|
||||
// Verify token stored
|
||||
const token = await page.evaluate(() => localStorage.getItem('auth_token'));
|
||||
expect(token).toBeTruthy();
|
||||
});
|
||||
|
||||
test('login with wrong credentials shows error', async ({ page }) => {
|
||||
await page.goto('/login');
|
||||
|
||||
// Fill login form with wrong credentials
|
||||
await page.fill('#loginUsername', 'nonexistentuser');
|
||||
await page.fill('#loginUsername', 'nonexistentuser_xyz');
|
||||
await page.fill('#loginPassword', 'wrongpassword');
|
||||
|
||||
// Click login button
|
||||
await page.click('#loginSubmit');
|
||||
|
||||
// Wait for error
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// Check error message is displayed
|
||||
const errorVisible = await page.locator('#authError').isVisible().catch(() => false);
|
||||
const errorText = await page.locator('#authError').textContent().catch(() => '');
|
||||
|
||||
// Error should be shown (and NOT be "[object Object]")
|
||||
expect(errorVisible || errorText.length > 0).toBeTruthy();
|
||||
expect(errorText).not.toContain('[object Object]');
|
||||
|
||||
const [response] = await Promise.all([
|
||||
page.waitForResponse((resp) => resp.url().includes('/api/auth/login')),
|
||||
page.click('#loginSubmit'),
|
||||
]);
|
||||
|
||||
expect(response.status()).toBe(401);
|
||||
|
||||
// Error message should be visible
|
||||
const errorLocator = page.locator('#authError');
|
||||
await expect(errorLocator).toBeVisible();
|
||||
await expect(errorLocator).toContainText(/incorrect|mot de passe|connexion/i);
|
||||
});
|
||||
|
||||
test('register new user shows success', async ({ page }) => {
|
||||
await page.goto('/login');
|
||||
|
||||
// Switch to register tab
|
||||
await page.click('text=Inscription');
|
||||
|
||||
// Fill register form with unique username
|
||||
const uniqueUsername = 'testuser_' + Date.now();
|
||||
|
||||
const uniqueUsername = `testuser_${Date.now()}`;
|
||||
await page.fill('#registerUsername', uniqueUsername);
|
||||
await page.fill('#registerPassword', 'password123');
|
||||
await page.fill('#registerPasswordConfirm', 'password123');
|
||||
|
||||
// Click register button
|
||||
await page.click('#registerSubmit');
|
||||
|
||||
// Wait for success
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// Check success message
|
||||
const successVisible = await page.locator('#authSuccess').isVisible().catch(() => false);
|
||||
const successText = await page.locator('#authSuccess').textContent().catch(() => '');
|
||||
|
||||
// Success should be shown
|
||||
expect(successVisible || successText.includes('réussie')).toBeTruthy();
|
||||
|
||||
const [response] = await Promise.all([
|
||||
page.waitForResponse((resp) => resp.url().includes('/api/auth/register')),
|
||||
page.click('#registerSubmit'),
|
||||
]);
|
||||
|
||||
expect(response.status()).toBeLessThan(400);
|
||||
|
||||
await expect(page.locator('#authSuccess')).toBeVisible();
|
||||
await expect(page.locator('#authSuccess')).toContainText(/réussie|succès/i);
|
||||
});
|
||||
|
||||
test('password mismatch shows validation error', async ({ page }) => {
|
||||
await page.goto('/login');
|
||||
|
||||
// Switch to register tab
|
||||
await page.click('text=Inscription');
|
||||
|
||||
// Fill register form with mismatching passwords
|
||||
|
||||
await page.fill('#registerUsername', 'testuser');
|
||||
await page.fill('#registerPassword', 'password123');
|
||||
await page.fill('#registerPasswordConfirm', 'differentpassword');
|
||||
|
||||
// Click register button
|
||||
await page.click('#registerSubmit');
|
||||
|
||||
// Wait for error
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// Check error message
|
||||
const errorText = await page.locator('#authError').textContent().catch(() => '');
|
||||
|
||||
// Should show password mismatch error
|
||||
expect(errorText).toContain('correspondent');
|
||||
|
||||
await expect(page.locator('#authError')).toBeVisible();
|
||||
await expect(page.locator('#authError')).toContainText(/correspondent|match/i);
|
||||
});
|
||||
|
||||
test('login button shows loading state during request', async ({ page }) => {
|
||||
await page.goto('/login');
|
||||
|
||||
// Get button and check initial state
|
||||
const button = page.locator('#loginSubmit');
|
||||
const initialText = await button.textContent();
|
||||
|
||||
// Fill form and click
|
||||
await page.fill('#loginUsername', 'testuser');
|
||||
await page.fill('#loginPassword', 'password123');
|
||||
|
||||
// Click and immediately check loading state
|
||||
await button.click();
|
||||
|
||||
// Check loading state (should change text or be disabled)
|
||||
await page.waitForTimeout(100);
|
||||
const buttonText = await button.textContent();
|
||||
const isDisabled = await button.isDisabled().catch(() => false);
|
||||
|
||||
// Button should either show loading text or be disabled
|
||||
expect(buttonText !== initialText || isDisabled).toBeTruthy();
|
||||
|
||||
await page.fill('#loginUsername', TEST_USER.username);
|
||||
await page.fill('#loginPassword', TEST_USER.password);
|
||||
|
||||
// Start the click but don't await it fully — we want to observe the loading state
|
||||
const clickPromise = button.click();
|
||||
|
||||
// Poll briefly for loading state
|
||||
let sawLoading = false;
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const text = await button.textContent();
|
||||
const disabled = await button.isDisabled();
|
||||
if (text !== initialText || disabled) {
|
||||
sawLoading = true;
|
||||
break;
|
||||
}
|
||||
await page.waitForTimeout(50);
|
||||
}
|
||||
|
||||
await clickPromise;
|
||||
expect(sawLoading).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user