fix: migrations, auth, providers health check, E2E tests, remove neko-sama
CI / Test (Python 3.11) (push) Has been cancelled
CI / Test (Python 3.12) (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Type Check (push) Has been cancelled
CI / Summary (push) Has been cancelled

- 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:
Kimi Agent
2026-05-12 11:45:56 +00:00
parent 693615a7dc
commit 520be53901
47 changed files with 654 additions and 3437 deletions
+59 -85
View File
@@ -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);
});
});