/** * 用户 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_test' if (mongoose.connection.readyState === 0) { await mongoose.connect(mongoURI) } // 确保唯一索引已构建(dropDatabase 后 mongoose 不会自动重建) await mongoose.connection.syncIndexes() }) afterAll(async () => { if (mongoose.connection.readyState !== 0) { // 防呆:只允许在测试库中 dropDatabase if (mongoose.connection.name === 'wdkj') { throw new Error('[安全] 不允许对生产数据库 wdkj 执行 dropDatabase') } 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 } }, 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.isNewUser).toBe(false) const userData = response.body.data.data expect(userData.exploreData).toBeDefined() expect(userData.exploreData.dim1.bestScore).toBe(100) expect(userData.exploreData.dim2.bestArea).toBe(200) }) test('无效 token 应该返回401', 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: 'dim1', data: { bestScore: 150 } }) expect(response.status).toBe(200) expect(response.body.success).toBe(true) expect(response.body.data.totalScore).toBe(350) // 验证数据库更新 const updatedUser = await User.findOne({ openid: 'test_user_openid' }) expect(updatedUser.exploreData.dim1.bestScore).toBe(150) }) test('完成维度后应解锁下一维度', async () => { const response = await request(app) .post('/api/user/progress') .set('Authorization', `Bearer ${authToken}`) .send({ dim: 'dim2', data: { completed: true, bestArea: 300 } }) 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 () => { // 创建多个测试用户(分数由 exploreData 派生) await User.create([ { openid: 'user1', nickName: '用户1', exploreData: { dim1: { bestScore: 500 } } }, { openid: 'user2', nickName: '用户2', exploreData: { dim1: { bestScore: 300 } } }, { openid: 'user3', nickName: '用户3', exploreData: { dim1: { bestScore: 400 } } } ]) }) 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(500) }) test('应该限制返回数量', async () => { const response = await request(app) .get('/api/user/leaderboard') .query({ limit: 2 }) expect(response.body.data).toHaveLength(2) }) }) describe('GET /api/user/achievements', () => { test('应该返回成就列表', async () => { const response = await request(app) .get('/api/user/achievements') .set('Authorization', `Bearer ${authToken}`) expect(response.status).toBe(200) expect(response.body.success).toBe(true) expect(Array.isArray(response.body.data)).toBe(true) expect(response.body.data.length).toBeGreaterThan(0) }) }) })