Initial commit: Pixel AI comic/video creation platform

- FastAPI backend with SQLModel, Alembic migrations, AgentScope agents
- Next.js 15 frontend with React 19, Tailwind, Zustand, React Flow
- Multi-provider AI system (DashScope, Kling, MiniMax, Volcengine, OpenAI, etc.)
- All HTTP clients migrated from sync requests to async httpx
- Admin-managed API keys via environment variables
- SSRF vulnerability fixed in ensure_url()
This commit is contained in:
张鹏
2026-04-29 01:20:12 +08:00
commit f9f4560459
808 changed files with 151724 additions and 0 deletions

View File

@@ -0,0 +1,227 @@
import { test, expect, Page } from '@playwright/test';
/**
* Authentication Tests based on feature_list.json
* Covers test cases: 5, 7-8, 14-16, 31-34, 35-37
*/
const BASE_URL = process.env.PLAYWRIGHT_BASE_URL || 'http://127.0.0.1:3000';
// Helper to login
async function login(page: Page) {
await page.goto(`${BASE_URL}/login`);
await page.fill('#username', 'testuser');
await page.fill('#password', 'Test123456');
await page.click('button[type="submit"]');
await page.waitForURL(`${BASE_URL}/`, { timeout: 10000 });
}
test.describe('Authentication Flow', () => {
// Test 5: 登录成功
test('should login successfully with valid credentials', async ({ page }) => {
await page.goto(`${BASE_URL}/login`);
// Fill in credentials using id selectors
await page.fill('#username', 'testuser');
await page.fill('#password', 'Test123456');
// Click login and check loading state
const loginButton = page.locator('button[type="submit"]');
await loginButton.click();
// Verify loading state
await expect(loginButton).toContainText('登录中');
// Wait for navigation
await page.waitForURL(`${BASE_URL}/`, { timeout: 10000 });
// Verify redirected to home
await expect(page).toHaveURL(`${BASE_URL}/`);
// Verify user avatar is visible in navbar
const avatar = page.locator('[data-testid="user-avatar"]').or(page.locator('.avatar')).first();
await expect(avatar).toBeVisible();
});
// Test 7: 已登录用户访问登录页自动重定向
test('should redirect logged-in user from login page to home', async ({ page }) => {
await login(page);
// Try to access login page again
await page.goto(`${BASE_URL}/login`);
// Should be redirected to home
await expect(page).toHaveURL(`${BASE_URL}/`);
});
// Test 8: 登录后重定向到原页面
test('should redirect to original page after login', async ({ page }) => {
// Access protected page while logged out
await page.goto(`${BASE_URL}/canvas`);
// Should be redirected to login with redirect param
await expect(page).toHaveURL(/.*login.*/);
await expect(page).toHaveURL(/.*redirect=.*/);
// Login
await page.fill('#username', 'testuser');
await page.fill('#password', 'Test123456');
await page.click('button[type="submit"]');
// Should redirect back to original page
await page.waitForURL(`${BASE_URL}/canvas`, { timeout: 10000 });
await expect(page).toHaveURL(`${BASE_URL}/canvas`);
});
// Test 14: 注册失败 - 用户名已存在
test('should show error for existing username during registration', async ({ page }) => {
await page.goto(`${BASE_URL}/register`);
await page.fill('#username', 'testuser');
await page.fill('#email', 'unique@example.com');
await page.fill('#password', 'Test123456');
await page.fill('#confirmPassword', 'Test123456');
await page.click('button[type="submit"]');
// Should show error about existing user
await expect(page.locator('text=/already registered|已注册|用户名已存在|Username already exists/i').first()).toBeVisible({ timeout: 5000 });
});
// Test 15: 注册成功
test('should register successfully with new user', async ({ page }) => {
await page.goto(`${BASE_URL}/register`);
const timestamp = Date.now();
await page.fill('#username', `newuser${timestamp}`);
await page.fill('#email', `newuser${timestamp}@example.com`);
await page.fill('#password', 'Test123456');
await page.fill('#confirmPassword', 'Test123456');
const registerButton = page.locator('button[type="submit"]');
await registerButton.click();
// Verify loading state
await expect(registerButton).toContainText('注册中');
// Wait for navigation to home
await page.waitForURL(`${BASE_URL}/`, { timeout: 10000 });
await expect(page).toHaveURL(`${BASE_URL}/`);
// Verify avatar is visible
const avatar = page.locator('[data-testid="user-avatar"]').or(page.locator('.avatar')).first();
await expect(avatar).toBeVisible();
});
// Test 16: 已登录用户访问注册页自动重定向
test('should redirect logged-in user from register page to home', async ({ page }) => {
await login(page);
// Try to access register page
await page.goto(`${BASE_URL}/register`);
// Should be redirected to home
await expect(page).toHaveURL(`${BASE_URL}/`);
});
});
test.describe('User Menu', () => {
// Test 31: 已登录状态显示用户头像
test('should display user avatar when logged in', async ({ page }) => {
await login(page);
// Check avatar is visible
const avatar = page.locator('[data-testid="user-avatar"]').or(page.locator('.avatar')).first();
await expect(avatar).toBeVisible();
// Check it shows first letter of username
await expect(avatar).toContainText('T'); // 'testuser' starts with T
});
// Test 32: 点击头像显示下拉菜单
test('should show dropdown menu when clicking avatar', async ({ page }) => {
await login(page);
// Click avatar
const avatar = page.locator('[data-testid="user-avatar"]').or(page.locator('.avatar')).first();
await avatar.click();
// Verify dropdown shows username and email
await expect(page.locator('text=testuser').first()).toBeVisible();
await expect(page.locator('text=/API Keys|API 密钥/i').first()).toBeVisible();
await expect(page.locator('text=/Log out|退出|登出/i').first()).toBeVisible();
});
// Test 33: 点击 API Keys 菜单项跳转
test('should navigate to API Keys page from dropdown', async ({ page }) => {
await login(page);
// Click avatar
const avatar = page.locator('[data-testid="user-avatar"]').or(page.locator('.avatar')).first();
await avatar.click();
// Click API Keys
await page.click('text=/API Keys|API 密钥/i');
// Should navigate to api-keys page
await expect(page).toHaveURL(`${BASE_URL}/canvas`);
});
// Test 34: 点击 Log out 登出
test('should logout when clicking logout', async ({ page }) => {
await login(page);
// Click avatar
const avatar = page.locator('[data-testid="user-avatar"]').or(page.locator('.avatar')).first();
await avatar.click();
// Click logout
await page.click('text=/Log out|退出|登出/i');
// Should redirect to login or home
await page.waitForLoadState('networkidle');
const url = page.url();
expect(url.includes('/login') || url === `${BASE_URL}/`).toBeTruthy();
// Verify login icon is shown (user is logged out)
const loginIcon = page.locator('[data-testid="login-icon"]').or(page.locator('a[href="/login"]')).first();
await expect(loginIcon).toBeVisible();
});
});
test.describe('Route Protection', () => {
// Test 35: 访问 /settings/* 需要登录
test('should redirect to login when accessing settings without auth', async ({ page }) => {
await page.goto(`${BASE_URL}/canvas`);
// Should be redirected to login
await expect(page).toHaveURL(/.*login.*/);
});
// Test 36: 已登录用户访问登录/注册页重定向
test('should redirect authenticated users from auth pages', async ({ page }) => {
await login(page);
// Try accessing login
await page.goto(`${BASE_URL}/login`);
await expect(page).toHaveURL(`${BASE_URL}/`);
// Try accessing register
await page.goto(`${BASE_URL}/register`);
await expect(page).toHaveURL(`${BASE_URL}/`);
});
// Test 37: 访问公开页面无需登录
test('should allow access to public pages without auth', async ({ page }) => {
await page.goto(`${BASE_URL}/`);
// Should stay on home page
await expect(page).toHaveURL(`${BASE_URL}/`);
// Should not redirect to login
await expect(page).not.toHaveURL(/.*login.*/);
});
});