421 lines
13 KiB
TypeScript
421 lines
13 KiB
TypeScript
/**
|
||
* 测试 createdBy 和 visibility 功能
|
||
* 测试场景:
|
||
* 1. 用户创建课程(AI生成)- 应该设置 createdBy 和 visibility='private',生成完成后自动发布
|
||
* 2. 查询课程列表 - 应该只看到公开课程和自己创建的课程
|
||
* 3. 修改自己创建的课程 - 应该成功
|
||
* 4. 修改他人创建的课程 - 应该失败(403)
|
||
* 5. 删除自己创建的课程 - 应该成功
|
||
* 6. 删除他人创建的课程 - 应该失败(403)
|
||
*/
|
||
|
||
import axios from 'axios';
|
||
import * as dotenv from 'dotenv';
|
||
import { join } from 'path';
|
||
|
||
// 加载环境变量
|
||
dotenv.config({ path: join(__dirname, '../.env.test') });
|
||
|
||
const BASE_URL = process.env.API_BASE_URL || 'http://localhost:3000';
|
||
const TEST_EMAIL = process.env.TEST_USER_EMAIL || `test_${Date.now()}@example.com`;
|
||
const TEST_PASSWORD = process.env.TEST_USER_PASSWORD || 'Test123456!';
|
||
|
||
let authToken: string = '';
|
||
let userId: string = '';
|
||
let createdCourseId: string = '';
|
||
let otherUserToken: string = '';
|
||
let otherUserId: string = '';
|
||
|
||
interface LoginResponse {
|
||
success: boolean;
|
||
data: {
|
||
token: string;
|
||
user: {
|
||
id: string;
|
||
email: string;
|
||
};
|
||
};
|
||
}
|
||
|
||
interface ApiResponse<T = any> {
|
||
success: boolean;
|
||
data: T;
|
||
message?: string;
|
||
}
|
||
|
||
interface CreateCourseResponse {
|
||
success: boolean;
|
||
data: {
|
||
courseId: string;
|
||
taskId: string;
|
||
status: string;
|
||
};
|
||
}
|
||
|
||
interface CourseResponse {
|
||
success: boolean;
|
||
data: {
|
||
course: {
|
||
id: string;
|
||
title: string;
|
||
created_by?: string;
|
||
visibility?: string;
|
||
status?: string;
|
||
};
|
||
};
|
||
}
|
||
|
||
interface CourseListResponse {
|
||
success: boolean;
|
||
data: {
|
||
courses: Array<{
|
||
id: string;
|
||
title: string;
|
||
visibility?: string;
|
||
}>;
|
||
};
|
||
}
|
||
|
||
async function registerUser(email: string, password: string): Promise<void> {
|
||
try {
|
||
// 尝试注册,如果接口不存在或用户已存在,则跳过
|
||
await axios.post(`${BASE_URL}/api/auth/register`, {
|
||
email,
|
||
password,
|
||
username: `测试用户_${Date.now()}`,
|
||
});
|
||
console.log(`✅ 用户注册成功: ${email}`);
|
||
} catch (error: any) {
|
||
if (error.response?.status === 404) {
|
||
// 注册接口不存在,跳过注册,直接尝试登录
|
||
console.log(`ℹ️ 注册接口不存在,跳过注册: ${email}`);
|
||
} else if (error.response?.status === 400 && (error.response?.data?.message?.includes('已存在') || error.response?.data?.message?.includes('exists'))) {
|
||
console.log(`ℹ️ 用户已存在: ${email}`);
|
||
} else {
|
||
// 其他错误也忽略,直接尝试登录
|
||
console.log(`ℹ️ 注册失败,将尝试登录: ${error.response?.status || error.message}`);
|
||
}
|
||
}
|
||
}
|
||
|
||
async function login(email: string, password: string): Promise<{ token: string; userId: string }> {
|
||
// 尝试使用测试 token
|
||
try {
|
||
const testTokenResponse = await axios.get<{ success: boolean; data: { token: string; user: { id: string } } }>(`${BASE_URL}/api/auth/test-token`);
|
||
if (testTokenResponse.data.success && testTokenResponse.data.data.token) {
|
||
console.log(`✅ 使用测试 token 登录成功`);
|
||
return {
|
||
token: testTokenResponse.data.data.token,
|
||
userId: testTokenResponse.data.data.user.id,
|
||
};
|
||
}
|
||
} catch (error) {
|
||
console.log(`ℹ️ 测试 token 不可用,尝试其他方式`);
|
||
}
|
||
|
||
// 如果测试 token 不可用,尝试普通登录(需要手机号和验证码)
|
||
const response = await axios.post<LoginResponse>(`${BASE_URL}/api/auth/login`, {
|
||
email,
|
||
password,
|
||
});
|
||
|
||
if (!response.data.success || !response.data.data.token) {
|
||
throw new Error('登录失败');
|
||
}
|
||
|
||
return {
|
||
token: response.data.data.token,
|
||
userId: response.data.data.user.id,
|
||
};
|
||
}
|
||
|
||
async function testCreateCourse() {
|
||
console.log('\n📝 测试 1: 用户创建课程(AI生成)');
|
||
|
||
const content = `# 测试课程
|
||
|
||
这是一个测试课程的内容,用于验证 createdBy 和 visibility 功能。
|
||
|
||
## 第一章:基础概念
|
||
|
||
这是第一章的内容。
|
||
|
||
## 第二章:进阶应用
|
||
|
||
这是第二章的内容。
|
||
`;
|
||
|
||
const response = await axios.post<CreateCourseResponse>(
|
||
`${BASE_URL}/api/ai/content/upload`,
|
||
{
|
||
content,
|
||
style: 'essence',
|
||
},
|
||
{
|
||
headers: {
|
||
Authorization: `Bearer ${authToken}`,
|
||
},
|
||
}
|
||
);
|
||
|
||
if (response.data.success && response.data.data.courseId) {
|
||
createdCourseId = response.data.data.courseId;
|
||
console.log(`✅ 课程创建成功,courseId: ${createdCourseId}`);
|
||
console.log(` taskId: ${response.data.data.taskId}`);
|
||
console.log(` status: ${response.data.data.status}`);
|
||
|
||
// 等待一下,让课程创建完成
|
||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||
|
||
// 查询课程详情,验证 createdBy 和 visibility
|
||
const courseResponse = await axios.get<CourseResponse>(
|
||
`${BASE_URL}/api/courses/${createdCourseId}`,
|
||
{
|
||
headers: {
|
||
Authorization: `Bearer ${authToken}`,
|
||
},
|
||
}
|
||
);
|
||
|
||
const course = courseResponse.data.data.course;
|
||
console.log(`\n📋 课程信息验证:`);
|
||
console.log(` createdBy: ${course.created_by || 'null'} (应该是: ${userId})`);
|
||
console.log(` visibility: ${course.visibility || 'N/A'} (应该是: private)`);
|
||
console.log(` status: ${course.status || 'N/A'}`);
|
||
|
||
if (course.created_by === userId && course.visibility === 'private') {
|
||
console.log(`✅ createdBy 和 visibility 设置正确`);
|
||
} else {
|
||
console.log(`❌ createdBy 或 visibility 设置错误`);
|
||
}
|
||
} else {
|
||
throw new Error('创建课程失败');
|
||
}
|
||
}
|
||
|
||
async function testQueryCourses() {
|
||
console.log('\n📋 测试 2: 查询课程列表');
|
||
|
||
// 测试公开接口(未登录)
|
||
const publicResponse = await axios.get<CourseListResponse>(`${BASE_URL}/api/courses`);
|
||
const publicCourses = publicResponse.data.data.courses || [];
|
||
console.log(`✅ 公开接口返回 ${publicCourses.length} 个课程`);
|
||
console.log(` 所有课程 visibility 应该是 'public': ${publicCourses.every((c: any) => c.visibility === 'public' || !c.visibility)}`);
|
||
|
||
// 测试登录用户接口
|
||
const userResponse = await axios.get<CourseListResponse>(`${BASE_URL}/api/courses`, {
|
||
headers: {
|
||
Authorization: `Bearer ${authToken}`,
|
||
},
|
||
});
|
||
const userCourses = userResponse.data.data.courses || [];
|
||
console.log(`✅ 登录用户接口返回 ${userCourses.length} 个课程`);
|
||
|
||
// 验证应该包含自己创建的课程
|
||
const hasMyCourse = userCourses.some((c: any) => c.id === createdCourseId);
|
||
console.log(` 包含自己创建的课程: ${hasMyCourse ? '✅' : '❌'}`);
|
||
|
||
// 测试 my-courses 接口
|
||
const myCoursesResponse = await axios.get<CourseListResponse>(`${BASE_URL}/api/my-courses`, {
|
||
headers: {
|
||
Authorization: `Bearer ${authToken}`,
|
||
},
|
||
});
|
||
const myCourses = myCoursesResponse.data.data.courses || [];
|
||
console.log(`✅ /api/my-courses 返回 ${myCourses.length} 个课程`);
|
||
const hasMyCourseInList = myCourses.some((c: any) => c.id === createdCourseId);
|
||
console.log(` 包含自己创建的课程: ${hasMyCourseInList ? '✅' : '❌'}`);
|
||
}
|
||
|
||
async function testUpdateOwnCourse() {
|
||
console.log('\n✏️ 测试 3: 修改自己创建的课程');
|
||
|
||
try {
|
||
const response = await axios.put<CourseResponse>(
|
||
`${BASE_URL}/api/courses/${createdCourseId}`,
|
||
{
|
||
title: '修改后的测试课程标题',
|
||
description: '这是修改后的描述',
|
||
},
|
||
{
|
||
headers: {
|
||
Authorization: `Bearer ${authToken}`,
|
||
},
|
||
}
|
||
);
|
||
|
||
if (response.data.success) {
|
||
console.log(`✅ 修改自己创建的课程成功`);
|
||
console.log(` 新标题: ${response.data.data.course.title}`);
|
||
} else {
|
||
const errorMsg = (response.data as any).message || '未知错误';
|
||
console.log(`❌ 修改失败: ${errorMsg}`);
|
||
}
|
||
} catch (error: any) {
|
||
if (error.response?.status === 403) {
|
||
console.log(`❌ 修改失败: 权限不足(403)`);
|
||
} else {
|
||
console.log(`❌ 修改失败: ${error.message}`);
|
||
}
|
||
}
|
||
}
|
||
|
||
async function testUpdateOtherUserCourse() {
|
||
console.log('\n✏️ 测试 4: 尝试修改他人创建的课程(应该失败)');
|
||
|
||
// 先创建一个其他用户的课程(如果还没有)
|
||
if (!otherUserToken) {
|
||
const otherEmail = `other_${Date.now()}@example.com`;
|
||
await registerUser(otherEmail, TEST_PASSWORD);
|
||
const loginResult = await login(otherEmail, TEST_PASSWORD);
|
||
otherUserToken = loginResult.token;
|
||
otherUserId = loginResult.userId;
|
||
}
|
||
|
||
// 尝试修改第一个用户创建的课程
|
||
try {
|
||
await axios.put(
|
||
`${BASE_URL}/api/courses/${createdCourseId}`,
|
||
{
|
||
title: '尝试修改他人课程',
|
||
},
|
||
{
|
||
headers: {
|
||
Authorization: `Bearer ${otherUserToken}`,
|
||
},
|
||
}
|
||
);
|
||
console.log(`❌ 不应该成功修改他人课程`);
|
||
} catch (error: any) {
|
||
if (error.response?.status === 403) {
|
||
console.log(`✅ 正确拒绝修改他人课程(403 Forbidden)`);
|
||
} else {
|
||
console.log(`⚠️ 返回了其他错误: ${error.response?.status} - ${error.message}`);
|
||
}
|
||
}
|
||
}
|
||
|
||
async function testDeleteOwnCourse() {
|
||
console.log('\n🗑️ 测试 5: 删除自己创建的课程');
|
||
|
||
// 先创建一个用于删除的课程
|
||
const content = `# 待删除的测试课程`;
|
||
const createResponse = await axios.post<CreateCourseResponse>(
|
||
`${BASE_URL}/api/ai/content/upload`,
|
||
{
|
||
content,
|
||
style: 'essence',
|
||
},
|
||
{
|
||
headers: {
|
||
Authorization: `Bearer ${authToken}`,
|
||
},
|
||
}
|
||
);
|
||
|
||
if (createResponse.data.success) {
|
||
const courseIdToDelete = createResponse.data.data.courseId;
|
||
console.log(` 创建了用于删除的课程: ${courseIdToDelete}`);
|
||
|
||
// 等待课程创建完成
|
||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||
|
||
try {
|
||
const response = await axios.delete<ApiResponse>(
|
||
`${BASE_URL}/api/courses/${courseIdToDelete}`,
|
||
{
|
||
headers: {
|
||
Authorization: `Bearer ${authToken}`,
|
||
},
|
||
}
|
||
);
|
||
|
||
if (response.data.success) {
|
||
console.log(`✅ 删除自己创建的课程成功`);
|
||
} else {
|
||
console.log(`❌ 删除失败: ${response.data.message || '未知错误'}`);
|
||
}
|
||
} catch (error: any) {
|
||
if (error.response?.status === 403) {
|
||
console.log(`❌ 删除失败: 权限不足(403)`);
|
||
} else {
|
||
console.log(`❌ 删除失败: ${error.message}`);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
async function testDeleteOtherUserCourse() {
|
||
console.log('\n🗑️ 测试 6: 尝试删除他人创建的课程(应该失败)');
|
||
|
||
if (!otherUserToken) {
|
||
const otherEmail = `other_${Date.now()}@example.com`;
|
||
await registerUser(otherEmail, TEST_PASSWORD);
|
||
const loginResult = await login(otherEmail, TEST_PASSWORD);
|
||
otherUserToken = loginResult.token;
|
||
otherUserId = loginResult.userId;
|
||
}
|
||
|
||
// 尝试删除第一个用户创建的课程
|
||
try {
|
||
await axios.delete(
|
||
`${BASE_URL}/api/courses/${createdCourseId}`,
|
||
{
|
||
headers: {
|
||
Authorization: `Bearer ${otherUserToken}`,
|
||
},
|
||
}
|
||
);
|
||
console.log(`❌ 不应该成功删除他人课程`);
|
||
} catch (error: any) {
|
||
if (error.response?.status === 403) {
|
||
console.log(`✅ 正确拒绝删除他人课程(403 Forbidden)`);
|
||
} else {
|
||
console.log(`⚠️ 返回了其他错误: ${error.response?.status} - ${error.message}`);
|
||
}
|
||
}
|
||
}
|
||
|
||
async function main() {
|
||
console.log('🚀 开始测试 createdBy 和 visibility 功能\n');
|
||
console.log(`测试环境: ${BASE_URL}`);
|
||
|
||
try {
|
||
// 1. 注册和登录测试用户
|
||
console.log('\n👤 注册和登录测试用户...');
|
||
await registerUser(TEST_EMAIL, TEST_PASSWORD);
|
||
const loginResult = await login(TEST_EMAIL, TEST_PASSWORD);
|
||
authToken = loginResult.token;
|
||
userId = loginResult.userId;
|
||
console.log(`✅ 登录成功,userId: ${userId}`);
|
||
|
||
// 2. 测试创建课程
|
||
await testCreateCourse();
|
||
|
||
// 3. 测试查询课程列表
|
||
await testQueryCourses();
|
||
|
||
// 4. 测试修改自己创建的课程
|
||
await testUpdateOwnCourse();
|
||
|
||
// 5. 测试修改他人创建的课程(应该失败)
|
||
await testUpdateOtherUserCourse();
|
||
|
||
// 6. 测试删除自己创建的课程
|
||
await testDeleteOwnCourse();
|
||
|
||
// 7. 测试删除他人创建的课程(应该失败)
|
||
await testDeleteOtherUserCourse();
|
||
|
||
console.log('\n✅ 所有测试完成!');
|
||
} catch (error: any) {
|
||
console.error('\n❌ 测试失败:', error.message);
|
||
if (error.response) {
|
||
console.error('响应数据:', JSON.stringify(error.response.data, null, 2));
|
||
}
|
||
process.exit(1);
|
||
}
|
||
}
|
||
|
||
main();
|