refactor: 项目架构重构 - 目录标准化 + 清理冗余
- 目录重命名: backend/wdkj-server/ → server/, frontend/ai-dimension/ → client/ - 删除 20+ 冗余文件(WDKJ 旧脚本、Windows 脚本、过时文档、设计稿) - 更新 package.json 元数据(移除 wdkj 命名) - 完善三级 .gitignore(根 + server + client) - 重写 README.md 和 CHANGELOG.md - 工具脚本移至 scripts/
This commit is contained in:
Executable
+211
@@ -0,0 +1,211 @@
|
||||
/**
|
||||
* 认证 API 集成测试
|
||||
*/
|
||||
|
||||
const request = require('supertest')
|
||||
const mongoose = require('mongoose')
|
||||
const app = require('../../src/index')
|
||||
const User = require('../../src/models/User')
|
||||
const Admin = require('../../src/models/Admin')
|
||||
|
||||
describe('Auth API', () => {
|
||||
beforeAll(async () => {
|
||||
// 连接测试数据库
|
||||
const mongoURI = process.env.MONGODB_URI || 'mongodb://localhost:27017/wdkj'
|
||||
if (mongoose.connection.readyState === 0) {
|
||||
await mongoose.connect(mongoURI)
|
||||
}
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
// 清理并关闭连接
|
||||
if (mongoose.connection.readyState !== 0) {
|
||||
await mongoose.connection.dropDatabase()
|
||||
await mongoose.connection.close()
|
||||
}
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
// 每个测试前清空集合
|
||||
await User.deleteMany({})
|
||||
await Admin.deleteMany({})
|
||||
})
|
||||
|
||||
describe('POST /api/auth/login', () => {
|
||||
test('新用户应该成功注册', async () => {
|
||||
const response = await request(app)
|
||||
.post('/api/auth/login')
|
||||
.send({
|
||||
openid: 'new_user_openid',
|
||||
userInfo: {
|
||||
nickName: '新用户',
|
||||
avatarUrl: 'https://example.com/avatar.jpg'
|
||||
}
|
||||
})
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
expect(response.body.success).toBe(true)
|
||||
expect(response.body.data.isNewUser).toBe(true)
|
||||
expect(response.body.data.openid).toBe('new_user_openid')
|
||||
expect(response.body.data.token).toBeDefined()
|
||||
})
|
||||
|
||||
test('老用户应该成功登录', async () => {
|
||||
// 先创建一个用户
|
||||
await User.create({
|
||||
openid: 'existing_user_openid',
|
||||
nickName: '老用户',
|
||||
totalScore: 100
|
||||
})
|
||||
|
||||
const response = await request(app)
|
||||
.post('/api/auth/login')
|
||||
.send({
|
||||
openid: 'existing_user_openid',
|
||||
userInfo: {
|
||||
nickName: '老用户新名字'
|
||||
}
|
||||
})
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
expect(response.body.success).toBe(true)
|
||||
expect(response.body.data.isNewUser).toBe(false)
|
||||
expect(response.body.data.openid).toBe('existing_user_openid')
|
||||
})
|
||||
|
||||
test('缺少 openid 应该返回错误', async () => {
|
||||
const response = await request(app)
|
||||
.post('/api/auth/login')
|
||||
.send({
|
||||
userInfo: {
|
||||
nickName: '测试用户'
|
||||
}
|
||||
})
|
||||
|
||||
expect(response.status).toBe(400)
|
||||
expect(response.body.success).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('POST /api/auth/admin/login', () => {
|
||||
beforeEach(async () => {
|
||||
// 创建测试管理员
|
||||
const admin = new Admin({
|
||||
username: 'testadmin',
|
||||
password: 'password123',
|
||||
role: 'super_admin',
|
||||
status: 'active'
|
||||
})
|
||||
await admin.save()
|
||||
})
|
||||
|
||||
test('管理员应该成功登录', async () => {
|
||||
const response = await request(app)
|
||||
.post('/api/auth/admin/login')
|
||||
.send({
|
||||
username: 'testadmin',
|
||||
password: 'password123'
|
||||
})
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
expect(response.body.success).toBe(true)
|
||||
expect(response.body.data.token).toBeDefined()
|
||||
expect(response.body.data.username).toBe('testadmin')
|
||||
expect(response.body.data.role).toBe('super_admin')
|
||||
})
|
||||
|
||||
test('错误的密码应该返回401', async () => {
|
||||
const response = await request(app)
|
||||
.post('/api/auth/admin/login')
|
||||
.send({
|
||||
username: 'testadmin',
|
||||
password: 'wrongpassword'
|
||||
})
|
||||
|
||||
expect(response.status).toBe(401)
|
||||
expect(response.body.success).toBe(false)
|
||||
})
|
||||
|
||||
test('不存在的用户应该返回401', async () => {
|
||||
const response = await request(app)
|
||||
.post('/api/auth/admin/login')
|
||||
.send({
|
||||
username: 'nonexistent',
|
||||
password: 'password123'
|
||||
})
|
||||
|
||||
expect(response.status).toBe(401)
|
||||
expect(response.body.success).toBe(false)
|
||||
})
|
||||
|
||||
test('禁用的账户应该返回403', async () => {
|
||||
// 创建禁用的管理员
|
||||
const disabledAdmin = new Admin({
|
||||
username: 'disabled',
|
||||
password: 'password123',
|
||||
role: 'viewer',
|
||||
status: 'inactive'
|
||||
})
|
||||
await disabledAdmin.save()
|
||||
|
||||
const response = await request(app)
|
||||
.post('/api/auth/admin/login')
|
||||
.send({
|
||||
username: 'disabled',
|
||||
password: 'password123'
|
||||
})
|
||||
|
||||
expect(response.status).toBe(403)
|
||||
expect(response.body.success).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('GET /api/auth/me', () => {
|
||||
let userToken
|
||||
let testUser
|
||||
|
||||
beforeEach(async () => {
|
||||
// 创建测试用户并获取token
|
||||
testUser = await User.create({
|
||||
openid: 'test_user_openid',
|
||||
nickName: '测试用户',
|
||||
totalScore: 500
|
||||
})
|
||||
|
||||
// 登录获取token
|
||||
const loginResponse = await request(app)
|
||||
.post('/api/auth/login')
|
||||
.send({ openid: 'test_user_openid' })
|
||||
|
||||
userToken = loginResponse.body.data.token
|
||||
})
|
||||
|
||||
test('应该返回当前用户信息', async () => {
|
||||
const response = await request(app)
|
||||
.get('/api/auth/me')
|
||||
.set('Authorization', `Bearer ${userToken}`)
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
expect(response.body.success).toBe(true)
|
||||
expect(response.body.data.openid).toBe('test_user_openid')
|
||||
expect(response.body.data.nickName).toBe('测试用户')
|
||||
})
|
||||
|
||||
test('缺少 token 应该返回401', async () => {
|
||||
const response = await request(app)
|
||||
.get('/api/auth/me')
|
||||
|
||||
expect(response.status).toBe(401)
|
||||
expect(response.body.success).toBe(false)
|
||||
})
|
||||
|
||||
test('不存在的用户应该返回401', async () => {
|
||||
const response = await request(app)
|
||||
.get('/api/auth/me')
|
||||
.set('Authorization', 'Bearer invalid_token')
|
||||
|
||||
expect(response.status).toBe(401)
|
||||
expect(response.body.success).toBe(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
Executable
+205
@@ -0,0 +1,205 @@
|
||||
/**
|
||||
* 用户 API 集成测试
|
||||
*/
|
||||
|
||||
const request = require('supertest')
|
||||
const mongoose = require('mongoose')
|
||||
const app = require('../../src/index')
|
||||
const User = require('../../src/models/User')
|
||||
const jwt = require('jsonwebtoken')
|
||||
|
||||
describe('User API', () => {
|
||||
let testUser
|
||||
let authToken
|
||||
|
||||
beforeAll(async () => {
|
||||
const mongoURI = process.env.MONGODB_URI || 'mongodb://localhost:27017/wdkj'
|
||||
if (mongoose.connection.readyState === 0) {
|
||||
await mongoose.connect(mongoURI)
|
||||
}
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
if (mongoose.connection.readyState !== 0) {
|
||||
await mongoose.connection.dropDatabase()
|
||||
await mongoose.connection.close()
|
||||
}
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
await User.deleteMany({})
|
||||
|
||||
// 创建测试用户
|
||||
testUser = await User.create({
|
||||
openid: 'test_user_openid',
|
||||
nickName: '测试用户',
|
||||
avatarUrl: 'https://example.com/avatar.jpg',
|
||||
exploreData: {
|
||||
dim1: { bestScore: 100, completed: true },
|
||||
dim2: { bestArea: 200, completed: false }
|
||||
},
|
||||
totalScore: 300,
|
||||
unlockedDims: [1, 2]
|
||||
})
|
||||
|
||||
// 生成认证 Token
|
||||
authToken = jwt.sign(
|
||||
{ userId: testUser._id.toString(), openid: testUser.openid },
|
||||
process.env.JWT_SECRET,
|
||||
{ expiresIn: '1h' }
|
||||
)
|
||||
})
|
||||
|
||||
describe('GET /api/user/progress', () => {
|
||||
test('应该返回用户探索进度', async () => {
|
||||
const response = await request(app)
|
||||
.get('/api/user/progress')
|
||||
.set('Authorization', `Bearer ${authToken}`)
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
expect(response.body.success).toBe(true)
|
||||
expect(response.body.data.exploreData).toBeDefined()
|
||||
expect(response.body.data.exploreData.dim1.bestScore).toBe(100)
|
||||
expect(response.body.data.exploreData.dim2.bestArea).toBe(200)
|
||||
})
|
||||
|
||||
test('不存在的用户应该返回404', async () => {
|
||||
const response = await request(app)
|
||||
.get('/api/user/progress')
|
||||
.set('Authorization', 'Bearer invalid_token')
|
||||
|
||||
expect(response.status).toBe(401)
|
||||
})
|
||||
})
|
||||
|
||||
describe('POST /api/user/progress', () => {
|
||||
test('应该成功保存探索进度', async () => {
|
||||
const response = await request(app)
|
||||
.post('/api/user/progress')
|
||||
.set('Authorization', `Bearer ${authToken}`)
|
||||
.send({
|
||||
dim: 1,
|
||||
score: 150,
|
||||
collectedEggs: 5
|
||||
})
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
expect(response.body.success).toBe(true)
|
||||
|
||||
// 验证数据库更新
|
||||
const updatedUser = await User.findOne({ openid: 'test_user_openid' })
|
||||
expect(updatedUser.exploreData.dim1.bestScore).toBe(150)
|
||||
expect(updatedUser.exploreData.dim1.collectedEggs).toBe(5)
|
||||
})
|
||||
|
||||
test('新用户应该自动解锁维度', async () => {
|
||||
const response = await request(app)
|
||||
.post('/api/user/progress')
|
||||
.set('Authorization', `Bearer ${authToken}`)
|
||||
.send({
|
||||
dim: 3,
|
||||
score: 100
|
||||
})
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
|
||||
const updatedUser = await User.findOne({ openid: 'test_user_openid' })
|
||||
expect(updatedUser.unlockedDims).toContain(3)
|
||||
})
|
||||
})
|
||||
|
||||
describe('GET /api/user/leaderboard', () => {
|
||||
beforeEach(async () => {
|
||||
// 创建多个测试用户
|
||||
await User.create([
|
||||
{ openid: 'user1', nickName: '用户1', totalScore: 500, dim1Score: 200 },
|
||||
{ openid: 'user2', nickName: '用户2', totalScore: 300, dim1Score: 150 },
|
||||
{ openid: 'user3', nickName: '用户3', totalScore: 400, dim1Score: 100 }
|
||||
])
|
||||
})
|
||||
|
||||
test('应该返回总分排行榜', async () => {
|
||||
const response = await request(app)
|
||||
.get('/api/user/leaderboard')
|
||||
.query({ limit: 10 })
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
expect(response.body.success).toBe(true)
|
||||
expect(response.body.data).toHaveLength(4) // 包含testUser
|
||||
expect(response.body.data[0].totalScore).toBeGreaterThanOrEqual(
|
||||
response.body.data[1].totalScore
|
||||
)
|
||||
})
|
||||
|
||||
test('应该返回维度排行榜', async () => {
|
||||
const response = await request(app)
|
||||
.get('/api/user/leaderboard')
|
||||
.query({ dim: 1, limit: 10 })
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
expect(response.body.data[0].dim1Score).toBe(200)
|
||||
})
|
||||
|
||||
test('应该限制返回数量', async () => {
|
||||
const response = await request(app)
|
||||
.get('/api/user/leaderboard')
|
||||
.query({ limit: 2 })
|
||||
|
||||
expect(response.body.data).toHaveLength(2)
|
||||
})
|
||||
})
|
||||
|
||||
describe('PUT /api/user/info', () => {
|
||||
test('应该成功更新用户信息', async () => {
|
||||
const response = await request(app)
|
||||
.put('/api/user/info')
|
||||
.set('Authorization', `Bearer ${authToken}`)
|
||||
.send({
|
||||
nickName: '新名字',
|
||||
avatarUrl: 'https://example.com/new-avatar.jpg'
|
||||
})
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
expect(response.body.success).toBe(true)
|
||||
|
||||
const updatedUser = await User.findOne({ openid: 'test_user_openid' })
|
||||
expect(updatedUser.nickName).toBe('新名字')
|
||||
expect(updatedUser.avatarUrl).toBe('https://example.com/new-avatar.jpg')
|
||||
})
|
||||
})
|
||||
|
||||
describe('POST /api/user/collect-knowledge', () => {
|
||||
test('应该成功收藏知识', async () => {
|
||||
const knowledgeId = new mongoose.Types.ObjectId()
|
||||
|
||||
const response = await request(app)
|
||||
.post('/api/user/collect-knowledge')
|
||||
.set('Authorization', `Bearer ${authToken}`)
|
||||
.send({ knowledgeId: knowledgeId.toString() })
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
expect(response.body.success).toBe(true)
|
||||
|
||||
const updatedUser = await User.findOne({ openid: 'test_user_openid' })
|
||||
expect(updatedUser.collectedKnowledge).toContainEqual(knowledgeId)
|
||||
})
|
||||
|
||||
test('重复收藏应该返回已存在', async () => {
|
||||
const knowledgeId = new mongoose.Types.ObjectId()
|
||||
|
||||
// 第一次收藏
|
||||
await request(app)
|
||||
.post('/api/user/collect-knowledge')
|
||||
.set('Authorization', `Bearer ${authToken}`)
|
||||
.send({ knowledgeId: knowledgeId.toString() })
|
||||
|
||||
// 第二次收藏
|
||||
const response = await request(app)
|
||||
.post('/api/user/collect-knowledge')
|
||||
.set('Authorization', `Bearer ${authToken}`)
|
||||
.send({ knowledgeId: knowledgeId.toString() })
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
})
|
||||
})
|
||||
})
|
||||
Executable
+27
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Jest 测试设置
|
||||
*/
|
||||
|
||||
// 增加测试超时时间
|
||||
jest.setTimeout(30000)
|
||||
|
||||
// 全局清理
|
||||
afterAll(async () => {
|
||||
// 关闭所有数据库连接
|
||||
const mongoose = require('mongoose')
|
||||
if (mongoose.connection.readyState !== 0) {
|
||||
await mongoose.connection.close()
|
||||
}
|
||||
})
|
||||
|
||||
// 抑制控制台日志(测试时)
|
||||
if (process.env.SUPPRESS_LOGS) {
|
||||
global.console = {
|
||||
...console,
|
||||
log: jest.fn(),
|
||||
debug: jest.fn(),
|
||||
info: jest.fn(),
|
||||
warn: jest.fn(),
|
||||
error: jest.fn()
|
||||
}
|
||||
}
|
||||
Executable
+166
@@ -0,0 +1,166 @@
|
||||
/**
|
||||
* 用户模型单元测试
|
||||
*/
|
||||
|
||||
const mongoose = require('mongoose')
|
||||
const User = require('../../src/models/User')
|
||||
|
||||
describe('User Model', () => {
|
||||
beforeAll(async () => {
|
||||
// 连接测试数据库
|
||||
const mongoURI = process.env.MONGODB_URI || 'mongodb://localhost:27017/wdkj'
|
||||
await mongoose.connect(mongoURI)
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
// 清理并关闭连接
|
||||
await mongoose.connection.dropDatabase()
|
||||
await mongoose.connection.close()
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
// 每个测试前清空users集合
|
||||
await User.deleteMany({})
|
||||
})
|
||||
|
||||
describe('用户创建', () => {
|
||||
test('应该成功创建新用户', async () => {
|
||||
const userData = {
|
||||
openid: 'test_openid_123',
|
||||
nickName: '测试用户',
|
||||
avatarUrl: 'https://example.com/avatar.jpg'
|
||||
}
|
||||
|
||||
const user = new User(userData)
|
||||
const savedUser = await user.save()
|
||||
|
||||
expect(savedUser._id).toBeDefined()
|
||||
expect(savedUser.openid).toBe(userData.openid)
|
||||
expect(savedUser.nickName).toBe(userData.nickName)
|
||||
expect(savedUser.avatarUrl).toBe(userData.avatarUrl)
|
||||
expect(savedUser.unlockedDims).toContain(1)
|
||||
expect(savedUser.totalScore).toBe(0)
|
||||
})
|
||||
|
||||
test('openid 是必需的', async () => {
|
||||
const user = new User({
|
||||
nickName: '测试用户'
|
||||
})
|
||||
|
||||
await expect(user.save()).rejects.toThrow()
|
||||
})
|
||||
|
||||
test('openid 必须唯一', async () => {
|
||||
const userData = {
|
||||
openid: 'unique_openid',
|
||||
nickName: '用户1'
|
||||
}
|
||||
|
||||
await new User(userData).save()
|
||||
|
||||
const duplicateUser = new User({
|
||||
openid: 'unique_openid',
|
||||
nickName: '用户2'
|
||||
})
|
||||
|
||||
await expect(duplicateUser.save()).rejects.toThrow()
|
||||
})
|
||||
|
||||
test('默认昵称应该是"星辰旅行者"', async () => {
|
||||
const user = new User({ openid: 'test_openid' })
|
||||
await user.save()
|
||||
|
||||
expect(user.nickName).toBe('星辰旅行者')
|
||||
})
|
||||
})
|
||||
|
||||
describe('探索数据', () => {
|
||||
test('应该正确计算总分', async () => {
|
||||
const user = new User({
|
||||
openid: 'test_openid',
|
||||
exploreData: {
|
||||
dim1: { bestScore: 100 },
|
||||
dim2: { bestArea: 200 },
|
||||
dim3: { bestScore: 150 }
|
||||
}
|
||||
})
|
||||
|
||||
await user.save()
|
||||
|
||||
expect(user.totalScore).toBe(450)
|
||||
expect(user.dim1Score).toBe(100)
|
||||
expect(user.dim2Score).toBe(200)
|
||||
expect(user.dim3Score).toBe(150)
|
||||
})
|
||||
|
||||
test('未解锁维度分数应为0', async () => {
|
||||
const user = new User({
|
||||
openid: 'test_openid',
|
||||
exploreData: {
|
||||
dim1: { bestScore: 100 }
|
||||
}
|
||||
})
|
||||
|
||||
await user.save()
|
||||
|
||||
expect(user.dim2Score).toBe(0)
|
||||
expect(user.dim3Score).toBe(0)
|
||||
expect(user.dim4Score).toBe(0)
|
||||
expect(user.dim5Score).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('虚拟字段', () => {
|
||||
test('isNew 应该对新用户返回 true', async () => {
|
||||
const user = new User({ openid: 'test_openid' })
|
||||
await user.save()
|
||||
|
||||
expect(user.isNew).toBe(true)
|
||||
})
|
||||
|
||||
test('isNew 应该对老用户返回 false', async () => {
|
||||
const yesterday = new Date()
|
||||
yesterday.setDate(yesterday.getDate() - 2)
|
||||
|
||||
const user = new User({
|
||||
openid: 'test_openid',
|
||||
createdAt: yesterday
|
||||
})
|
||||
await user.save()
|
||||
|
||||
expect(user.isNew).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('静态方法', () => {
|
||||
test('getLeaderboard 应该返回排行榜', async () => {
|
||||
// 创建测试用户
|
||||
await User.create([
|
||||
{ openid: 'user1', totalScore: 300, dim1Score: 100 },
|
||||
{ openid: 'user2', totalScore: 500, dim1Score: 200 },
|
||||
{ openid: 'user3', totalScore: 400, dim1Score: 150 }
|
||||
])
|
||||
|
||||
const leaderboard = await User.getLeaderboard(null, 10)
|
||||
|
||||
expect(leaderboard).toHaveLength(3)
|
||||
expect(leaderboard[0].totalScore).toBe(500)
|
||||
expect(leaderboard[1].totalScore).toBe(400)
|
||||
expect(leaderboard[2].totalScore).toBe(300)
|
||||
})
|
||||
|
||||
test('getLeaderboard 应该支持按维度排序', async () => {
|
||||
await User.create([
|
||||
{ openid: 'user1', dim1Score: 100, totalScore: 100 },
|
||||
{ openid: 'user2', dim1Score: 200, totalScore: 200 },
|
||||
{ openid: 'user3', dim1Score: 150, totalScore: 150 }
|
||||
])
|
||||
|
||||
const leaderboard = await User.getLeaderboard(1, 10)
|
||||
|
||||
expect(leaderboard[0].dim1Score).toBe(200)
|
||||
expect(leaderboard[1].dim1Score).toBe(150)
|
||||
expect(leaderboard[2].dim1Score).toBe(100)
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user