test/user-journey #3

Merged
zhangxiang merged 142 commits from test/user-journey into dev 2026-04-12 13:17:03 +08:00
3 changed files with 95 additions and 5 deletions
Showing only changes of commit c0599fd7b1 - Show all commits
+47 -4
View File
@@ -17,15 +17,30 @@ export class AdminUserPage {
}
async createUser(data: UserData) {
await this.goto();
console.log('开始创建用户:', data.email);
await this.goto();
await this.page.waitForLoadState('domcontentloaded');
await this.page.waitForTimeout(1000);
console.log('页面加载完成,准备点击添加用户按钮');
const addButton = this.page.locator('button:has-text("添加用户")');
await addButton.waitFor({ timeout: 10000, state: 'visible' });
await addButton.click();
console.log('已点击添加用户按钮,等待模态框打开');
await this.page.waitForTimeout(500);
await this.page.waitForSelector('.fixed.inset-0.bg-black.bg-opacity-50', {
timeout: 5000,
state: 'visible'
});
console.log('模态框已打开,等待表单加载');
await this.page.waitForTimeout(300);
await this.page.waitForSelector('input[name="email"]', { timeout: 5000, state: 'visible' });
await this.page.fill('input[name="email"]', data.email);
await this.page.fill('input[name="password"]', data.password);
@@ -38,12 +53,40 @@ export class AdminUserPage {
await this.page.selectOption('select[name="role"]', data.role);
}
console.log('表单填写完成,准备提交');
await this.page.click('button:has-text("创建")');
console.log('用户创建请求已提交');
}
async expectUserInList(email: string) {
console.log(`检查用户是否在列表中: ${email}`);
await this.goto();
const row = this.page.locator(`tr:has-text("${email}")`);
await expect(row).toBeVisible();
await this.page.waitForLoadState('domcontentloaded');
await this.page.waitForTimeout(1000);
let row = this.page.locator(`tr:has-text("${email}")`);
let isVisible = await row.count() > 0;
if (!isVisible) {
console.log('用户不在列表中,尝试刷新页面');
await this.page.reload({ waitUntil: 'domcontentloaded' });
await this.page.waitForSelector('table tbody tr', { timeout: 5000 });
await this.page.waitForTimeout(1000);
row = this.page.locator(`tr:has-text("${email}")`);
isVisible = await row.count() > 0;
}
if (!isVisible) {
const allRows = await this.page.locator('table tbody tr').allTextContents();
console.log('当前列表中的用户:');
allRows.forEach((text, i) => console.log(`${i + 1}: ${text}`));
}
await expect(row).toBeVisible({ timeout: 10000 });
console.log(`✅ 用户已在列表中: ${email}`);
}
}
+8
View File
@@ -262,6 +262,7 @@ export default function UsersPage() {
<label className="block text-sm font-medium text-gray-700 mb-1"></label>
<input
type="email"
name="email"
value={formData.email}
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#C41E3A]"
@@ -271,6 +272,7 @@ export default function UsersPage() {
<label className="block text-sm font-medium text-gray-700 mb-1"></label>
<input
type="text"
name="name"
value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#C41E3A]"
@@ -280,6 +282,7 @@ export default function UsersPage() {
<label className="block text-sm font-medium text-gray-700 mb-1"></label>
<input
type="password"
name="password"
value={formData.password}
onChange={(e) => setFormData({ ...formData, password: e.target.value })}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#C41E3A]"
@@ -288,6 +291,7 @@ export default function UsersPage() {
<div>
<label className="block text-sm font-medium text-gray-700 mb-1"></label>
<select
name="role"
value={formData.role}
onChange={(e) => setFormData({ ...formData, role: e.target.value as any })}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#C41E3A]"
@@ -330,6 +334,7 @@ export default function UsersPage() {
<label className="block text-sm font-medium text-gray-700 mb-1"></label>
<input
type="email"
name="email"
value={formData.email}
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#C41E3A]"
@@ -339,6 +344,7 @@ export default function UsersPage() {
<label className="block text-sm font-medium text-gray-700 mb-1"></label>
<input
type="text"
name="name"
value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#C41E3A]"
@@ -348,6 +354,7 @@ export default function UsersPage() {
<label className="block text-sm font-medium text-gray-700 mb-1"></label>
<input
type="password"
name="password"
value={formData.password}
onChange={(e) => setFormData({ ...formData, password: e.target.value })}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#C41E3A]"
@@ -356,6 +363,7 @@ export default function UsersPage() {
<div>
<label className="block text-sm font-medium text-gray-700 mb-1"></label>
<select
name="role"
value={formData.role}
onChange={(e) => setFormData({ ...formData, role: e.target.value as any })}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#C41E3A]"
+40 -1
View File
@@ -2,8 +2,10 @@ import { NextRequest } from 'next/server';
import { db } from '@/db';
import { users } from '@/db/schema';
import { checkIsAdmin } from '@/lib/auth/check-permission';
import { forbidden, success, handleApiError } from '@/lib/api-response';
import { forbidden, success, handleApiError, badRequest } from '@/lib/api-response';
import { desc } from 'drizzle-orm';
import bcrypt from 'bcryptjs';
import { nanoid } from 'nanoid';
export async function GET(_request: NextRequest) {
try {
@@ -31,3 +33,40 @@ export async function GET(_request: NextRequest) {
return handleApiError(error);
}
}
export async function POST(request: NextRequest) {
try {
const { isAdmin } = await checkIsAdmin();
if (!isAdmin) {
return forbidden();
}
const body = await request.json();
const { email, name, password, role } = body;
if (!email || !name || !password || !role) {
return badRequest('缺少必填字段');
}
const hashedPassword = await bcrypt.hash(password, 10);
const [newUser] = await db
.insert(users)
.values({
id: nanoid(),
email,
name,
password: hashedPassword,
isAdmin: role === 'admin',
createdAt: new Date(),
updatedAt: new Date(),
})
.returning();
return success({ user: newUser });
} catch (error) {
console.error('创建用户失败:', error);
return handleApiError(error);
}
}