- web/: React + Vite + TypeScript 前端 - backend/: Django + DRF + SimpleJWT 后端 - prototype/: HTML 设计原型 - docs/: PRD 和设计评审文档 - test: 单元测试 + E2E 极限测试
199 lines
6.6 KiB
TypeScript
199 lines
6.6 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
|
|
// We test the API module's structure and endpoint definitions
|
|
// The actual HTTP calls are mocked; we verify correct URL/method/payload patterns
|
|
|
|
vi.mock('axios', () => {
|
|
const mockAxiosInstance = {
|
|
get: vi.fn().mockResolvedValue({ data: {} }),
|
|
post: vi.fn().mockResolvedValue({ data: {} }),
|
|
put: vi.fn().mockResolvedValue({ data: {} }),
|
|
patch: vi.fn().mockResolvedValue({ data: {} }),
|
|
interceptors: {
|
|
request: { use: vi.fn() },
|
|
response: { use: vi.fn() },
|
|
},
|
|
};
|
|
return {
|
|
default: {
|
|
create: vi.fn(() => mockAxiosInstance),
|
|
post: vi.fn(),
|
|
},
|
|
};
|
|
});
|
|
|
|
import { authApi, videoApi, adminApi, profileApi } from '../../src/lib/api';
|
|
import api from '../../src/lib/api';
|
|
|
|
describe('API Client', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
describe('authApi', () => {
|
|
it('should have register method that posts to /auth/register', async () => {
|
|
await authApi.register('user1', 'user1@test.com', 'pass123');
|
|
expect(api.post).toHaveBeenCalledWith(
|
|
'/auth/register',
|
|
{ username: 'user1', email: 'user1@test.com', password: 'pass123' }
|
|
);
|
|
});
|
|
|
|
it('should have login method that posts to /auth/login', async () => {
|
|
await authApi.login('user1', 'pass123');
|
|
expect(api.post).toHaveBeenCalledWith(
|
|
'/auth/login',
|
|
{ username: 'user1', password: 'pass123' }
|
|
);
|
|
});
|
|
|
|
it('should have refreshToken method that posts to /auth/token/refresh', async () => {
|
|
await authApi.refreshToken('my-refresh-token');
|
|
expect(api.post).toHaveBeenCalledWith(
|
|
'/auth/token/refresh',
|
|
{ refresh: 'my-refresh-token' }
|
|
);
|
|
});
|
|
|
|
it('should have getMe method that gets /auth/me', async () => {
|
|
await authApi.getMe();
|
|
expect(api.get).toHaveBeenCalledWith('/auth/me');
|
|
});
|
|
});
|
|
|
|
describe('videoApi', () => {
|
|
it('should have generate method that posts FormData to /video/generate', async () => {
|
|
const formData = new FormData();
|
|
formData.append('prompt', 'test prompt');
|
|
await videoApi.generate(formData);
|
|
expect(api.post).toHaveBeenCalledWith(
|
|
'/video/generate',
|
|
formData,
|
|
{ headers: { 'Content-Type': 'multipart/form-data' } }
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('adminApi', () => {
|
|
it('should have getStats method that gets /admin/stats', async () => {
|
|
await adminApi.getStats();
|
|
expect(api.get).toHaveBeenCalledWith('/admin/stats');
|
|
});
|
|
|
|
it('should have getUsers with default params', async () => {
|
|
await adminApi.getUsers();
|
|
expect(api.get).toHaveBeenCalledWith('/admin/users', { params: {} });
|
|
});
|
|
|
|
it('should have getUsers with search params', async () => {
|
|
await adminApi.getUsers({ page: 2, search: 'alice' });
|
|
expect(api.get).toHaveBeenCalledWith('/admin/users', {
|
|
params: { page: 2, search: 'alice' },
|
|
});
|
|
});
|
|
|
|
it('should have updateUserQuota that puts /admin/users/:id/quota', async () => {
|
|
await adminApi.updateUserQuota(42, 600, 6000);
|
|
expect(api.put).toHaveBeenCalledWith('/admin/users/42/quota', {
|
|
daily_seconds_limit: 600,
|
|
monthly_seconds_limit: 6000,
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('API Client Configuration', () => {
|
|
it('should export authApi with all 4 methods', () => {
|
|
expect(authApi).toBeDefined();
|
|
expect(typeof authApi.register).toBe('function');
|
|
expect(typeof authApi.login).toBe('function');
|
|
expect(typeof authApi.refreshToken).toBe('function');
|
|
expect(typeof authApi.getMe).toBe('function');
|
|
});
|
|
|
|
it('should export videoApi with generate method', () => {
|
|
expect(videoApi).toBeDefined();
|
|
expect(typeof videoApi.generate).toBe('function');
|
|
});
|
|
|
|
it('should export adminApi with Phase 3 methods', () => {
|
|
expect(adminApi).toBeDefined();
|
|
expect(typeof adminApi.getStats).toBe('function');
|
|
expect(typeof adminApi.getUsers).toBe('function');
|
|
expect(typeof adminApi.getUserDetail).toBe('function');
|
|
expect(typeof adminApi.updateUserQuota).toBe('function');
|
|
expect(typeof adminApi.updateUserStatus).toBe('function');
|
|
expect(typeof adminApi.getRecords).toBe('function');
|
|
expect(typeof adminApi.getSettings).toBe('function');
|
|
expect(typeof adminApi.updateSettings).toBe('function');
|
|
});
|
|
|
|
it('should export profileApi with Phase 3 methods', () => {
|
|
expect(profileApi).toBeDefined();
|
|
expect(typeof profileApi.getOverview).toBe('function');
|
|
expect(typeof profileApi.getRecords).toBe('function');
|
|
});
|
|
});
|
|
|
|
describe('Admin API Phase 3 — Additional Endpoint Tests', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
it('adminApi.getUserDetail fetches /admin/users/:id', async () => {
|
|
await adminApi.getUserDetail(7);
|
|
expect(api.get).toHaveBeenCalledWith('/admin/users/7');
|
|
});
|
|
|
|
it('adminApi.updateUserStatus patches /admin/users/:id/status', async () => {
|
|
await adminApi.updateUserStatus(5, false);
|
|
expect(api.patch).toHaveBeenCalledWith('/admin/users/5/status', { is_active: false });
|
|
});
|
|
|
|
it('adminApi.getRecords fetches /admin/records with params', async () => {
|
|
await adminApi.getRecords({ page: 2, search: 'bob', start_date: '2026-03-01' });
|
|
expect(api.get).toHaveBeenCalledWith('/admin/records', {
|
|
params: { page: 2, search: 'bob', start_date: '2026-03-01' },
|
|
});
|
|
});
|
|
|
|
it('adminApi.getSettings fetches /admin/settings', async () => {
|
|
await adminApi.getSettings();
|
|
expect(api.get).toHaveBeenCalledWith('/admin/settings');
|
|
});
|
|
|
|
it('adminApi.updateSettings puts /admin/settings', async () => {
|
|
const settings = {
|
|
default_daily_seconds_limit: 900,
|
|
default_monthly_seconds_limit: 9000,
|
|
announcement: 'test',
|
|
announcement_enabled: true,
|
|
};
|
|
await adminApi.updateSettings(settings);
|
|
expect(api.put).toHaveBeenCalledWith('/admin/settings', settings);
|
|
});
|
|
});
|
|
|
|
describe('Profile API Phase 3', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
it('profileApi.getOverview fetches /profile/overview with default period', async () => {
|
|
await profileApi.getOverview();
|
|
expect(api.get).toHaveBeenCalledWith('/profile/overview', { params: { period: '7d' } });
|
|
});
|
|
|
|
it('profileApi.getOverview supports 30d period', async () => {
|
|
await profileApi.getOverview('30d');
|
|
expect(api.get).toHaveBeenCalledWith('/profile/overview', { params: { period: '30d' } });
|
|
});
|
|
|
|
it('profileApi.getRecords fetches /profile/records with pagination', async () => {
|
|
await profileApi.getRecords(2, 10);
|
|
expect(api.get).toHaveBeenCalledWith('/profile/records', {
|
|
params: { page: 2, page_size: 10 },
|
|
});
|
|
});
|
|
});
|