EternalAI/e2e/admin-sync-flow.spec.js

302 lines
10 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const { test, expect, request } = require('@playwright/test');
const { cleanDatabase, seedExistingUser, seedAdmin, disconnect, prisma } = require('./fixtures/database');
test.describe('管理员审核 + Hermes 同步流程', () => {
let adminToken;
let userToken;
let roleId;
test.beforeAll(async () => {
await cleanDatabase();
await seedExistingUser();
await seedAdmin();
// 管理员登录
const adminContext = await request.newContext();
const adminRes = await adminContext.post('/api/admin-auth/login', {
data: { account: 'admin', password: 'admin123' },
});
const adminData = await adminRes.json();
adminToken = adminData.token;
// 用户登录
const userContext = await request.newContext();
const userRes = await userContext.post('/api/auth/login', {
data: { account: 'e2e_existing', password: 'Test123456' },
});
const userData = await userRes.json();
userToken = userData.token;
// 创建角色(待审核)
const roleRes = await userContext.post('/api/roles', {
headers: { Authorization: `Bearer ${userToken}` },
data: {
displayName: '测试角色',
personality: '温柔体贴',
background: '来自未来',
speechStyle: '轻声细语',
greeting: '你好呀',
soulMd: '# SOUL\n这是测试角色的灵魂文件',
},
});
const roleData = await roleRes.json();
roleId = roleData.role.id;
});
test.afterAll(async () => {
await cleanDatabase();
await disconnect();
});
test('管理员登录成功', async () => {
expect(adminToken).toBeTruthy();
});
test('管理员登录密码错误返回 401', async () => {
const context = await request.newContext();
const res = await context.post('/api/admin-auth/login', {
data: { account: 'admin', password: 'wrong' },
});
expect(res.status()).toBe(401);
});
test('用户 JWT 不能访问管理员接口', async () => {
const context = await request.newContext();
const res = await context.get('/api/admin/reviews', {
headers: { Authorization: `Bearer ${userToken}` },
});
expect(res.status()).toBe(401);
});
test('无 token 不能访问管理员接口', async () => {
const context = await request.newContext();
const res = await context.get('/api/admin/reviews');
expect(res.status()).toBe(401);
});
test('创建角色后状态为 pending_review', async () => {
const role = await prisma.role.findUnique({ where: { id: roleId } });
expect(role.reviewStatus).toBe('pending_review');
});
test('管理员获取待审核列表', async () => {
const context = await request.newContext();
const res = await context.get('/api/admin/reviews', {
headers: { Authorization: `Bearer ${adminToken}` },
});
expect(res.status()).toBe(200);
const data = await res.json();
expect(data.roles.length).toBeGreaterThan(0);
expect(data.roles[0].reviewStatus).toBe('pending_review');
});
test('管理员获取角色详情', async () => {
const context = await request.newContext();
const res = await context.get(`/api/admin/reviews/${roleId}`, {
headers: { Authorization: `Bearer ${adminToken}` },
});
expect(res.status()).toBe(200);
const data = await res.json();
expect(data.role.displayName).toBe('测试角色');
});
test('管理员通过审核', async () => {
const context = await request.newContext();
const res = await context.post(`/api/admin/reviews/${roleId}/approve`, {
headers: { Authorization: `Bearer ${adminToken}` },
});
expect(res.status()).toBe(200);
const data = await res.json();
expect(data.role.reviewStatus).toBe('approved');
});
test('角色库不显示未同步的角色', async () => {
const context = await request.newContext();
const res = await context.get('/api/roles');
const data = await res.json();
const found = data.roles.find((r) => r.id === roleId);
expect(found).toBeUndefined();
});
test('对非 approved 角色发起同步返回 400', async () => {
// 先创建一个新角色pending_review
const userContext = await request.newContext();
await userContext.post('/api/roles', {
headers: { Authorization: `Bearer ${userToken}` },
data: {
displayName: '未审核角色',
personality: '测试',
background: '测试',
speechStyle: '测试',
greeting: '测试',
soulMd: '# test',
},
});
const roles = await prisma.role.findMany({ where: { displayName: '未审核角色' } });
const pendingRoleId = roles[0].id;
const adminContext = await request.newContext();
const res = await adminContext.post(`/api/admin/sync/${pendingRoleId}`, {
headers: { Authorization: `Bearer ${adminToken}` },
data: { profileName: 'test-profile' },
});
expect(res.status()).toBe(400);
});
test('未配置 Hermes webhook URL 时同步返回 400', async () => {
const context = await request.newContext();
const res = await context.post(`/api/admin/sync/${roleId}`, {
headers: { Authorization: `Bearer ${adminToken}` },
data: { profileName: 'test-profile' },
});
expect(res.status()).toBe(400);
});
test('配置 Hermes webhook URL 指向 mock', async () => {
const context = await request.newContext();
const res = await context.put('/api/admin/config/HERMES_WEBHOOK_URL', {
headers: { Authorization: `Bearer ${adminToken}` },
data: { value: 'http://localhost:3001/api/mock-hermes/sync' },
});
expect(res.status()).toBe(200);
});
test('管理员发起同步 → mock Hermes 回调拉取文件 → 返回二维码', async () => {
const context = await request.newContext();
const res = await context.post(`/api/admin/sync/${roleId}`, {
headers: { Authorization: `Bearer ${adminToken}` },
data: {
profileName: 'test-profile',
modelKey: 'sk-test-key',
provider: 'openrouter',
multimediaModelKey: 'sk-multi-key',
multimediaProvider: 'openrouter',
enableSchedule: false,
},
});
expect(res.status()).toBe(200);
const data = await res.json();
expect(data.role.reviewStatus).toBe('synced');
expect(data.role.qrCodeUrl).toContain('mock.hermes.local');
expect(data.profileId).toContain('mock-profile');
});
test('同步成功后角色出现在角色库', async () => {
const context = await request.newContext();
const res = await context.get('/api/roles');
const data = await res.json();
const found = data.roles.find((r) => r.id === roleId);
expect(found).toBeDefined();
});
test('用户查看自己的角色列表包含审核状态和二维码', async () => {
const context = await request.newContext();
const res = await context.get('/api/roles/my/roles', {
headers: { Authorization: `Bearer ${userToken}` },
});
const data = await res.json();
const role = data.roles.find((r) => r.id === roleId);
expect(role).toBeDefined();
expect(role.reviewStatus).toBe('synced');
expect(role.qrCodeUrl).toBeTruthy();
});
test('sync_token 拉取文件 — SOUL.md', async () => {
// 生成新的 sync_token通过管理员发起同步流程会自动生成
// 这里直接用 API 测试:先通过审核另一个角色,再同步
const userContext = await request.newContext();
// 创建并审核新角色
const roleRes = await userContext.post('/api/roles', {
headers: { Authorization: `Bearer ${userToken}` },
data: {
displayName: '同步测试角色',
personality: '测试',
background: '测试',
speechStyle: '测试',
greeting: '测试',
soulMd: '# Test SOUL\n测试内容',
},
});
const newRoleId = (await roleRes.json()).role.id;
// 管理员审核通过
const adminContext = await request.newContext();
await adminContext.post(`/api/admin/reviews/${newRoleId}/approve`, {
headers: { Authorization: `Bearer ${adminToken}` },
});
// 发起同步mock Hermes 会回调拉取文件)
const syncRes = await adminContext.post(`/api/admin/sync/${newRoleId}`, {
headers: { Authorization: `Bearer ${adminToken}` },
data: {
profileName: 'test-profile-2',
modelKey: 'sk-test',
provider: 'openrouter',
},
});
expect(syncRes.status()).toBe(200);
const syncData = await syncRes.json();
expect(syncData.role.reviewStatus).toBe('synced');
});
test('驳回审核流程', async () => {
const userContext = await request.newContext();
const roleRes = await userContext.post('/api/roles', {
headers: { Authorization: `Bearer ${userToken}` },
data: {
displayName: '待驳回角色',
personality: '测试',
background: '测试',
speechStyle: '测试',
greeting: '测试',
soulMd: '# test',
},
});
const rejectRoleId = (await roleRes.json()).role.id;
const adminContext = await request.newContext();
const res = await adminContext.post(`/api/admin/reviews/${rejectRoleId}/reject`, {
headers: { Authorization: `Bearer ${adminToken}` },
data: { reviewNote: '内容不符合要求' },
});
expect(res.status()).toBe(200);
const data = await res.json();
expect(data.role.reviewStatus).toBe('rejected');
expect(data.role.reviewNote).toBe('内容不符合要求');
});
test('向后兼容API Key 拉取仍正常工作', async () => {
// 生成 API Key
const userContext = await request.newContext();
const keyRes = await userContext.post('/api/apikeys', {
headers: { Authorization: `Bearer ${userToken}` },
data: { name: 'test-key' },
});
const apiKey = (await keyRes.json()).apiKey.key;
// 用 API Key 拉取 SOUL.md
const soulRes = await userContext.get(`/api/hermes/roles/${roleId}/SOUL.md`, {
headers: { Authorization: `Bearer ${apiKey}` },
});
expect(soulRes.status()).toBe(200);
const soulText = await soulRes.text();
expect(soulText).toContain('测试角色的灵魂文件');
});
test('管理员获取同步状态列表', async () => {
const context = await request.newContext();
const res = await context.get('/api/admin/sync-status', {
headers: { Authorization: `Bearer ${adminToken}` },
});
expect(res.status()).toBe(200);
const data = await res.json();
expect(data.roles.length).toBeGreaterThan(0);
expect(data.roles.some((r) => r.reviewStatus === 'synced')).toBe(true);
});
});