Files
api_user/test/api.test.js
2026-06-12 15:24:20 +08:00

266 lines
7.6 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.
import { describe, it, before, after } from 'node:test';
import assert from 'node:assert/strict';
import supertest from 'supertest';
import { APP } from '../app.js';
import { DBModel } from '../models/index.js';
describe('用户接口集成测试', () => {
let app, agent;
const testMobile = '13900000001';
const testPasswd = 'test123456';
let token = '';
let userId = '';
before(async () => {
app = new APP();
agent = supertest(app.app.callback());
// 等待数据库连接
await new Promise(resolve => setTimeout(resolve, 2000));
});
after(async () => {
// 清理测试数据
if (userId) {
await DBModel.User.delUser(userId);
}
app.stop();
});
describe('POST /user/register', () => {
it('应成功注册新用户', async () => {
const res = await agent
.post('/user/register')
.send({
userInfo: {
profile: { mobile: testMobile, name: '测试用户' },
security: { passwd: testPasswd },
},
});
assert.equal(res.body.code, 0, `注册失败: ${res.body.msg}`);
assert.ok(res.body.data.user);
assert.ok(res.body.data.user.security.token);
assert.equal(res.body.data.user.profile.mobile, testMobile);
// 密码字段不应返回
assert.equal(res.body.data.user.security.passwd, undefined);
assert.equal(res.body.data.user.security.passwdSalt, undefined);
token = res.body.data.user.security.token;
userId = res.body.data.user._id;
});
it('重复注册应返回 409', async () => {
const res = await agent
.post('/user/register')
.send({
userInfo: {
profile: { mobile: testMobile, name: '测试用户' },
security: { passwd: testPasswd },
},
});
assert.equal(res.body.code, 409);
assert.equal(res.body.msg, '手机号已注册');
});
it('缺少手机号应返回 400', async () => {
const res = await agent
.post('/user/register')
.send({
userInfo: {
profile: { name: '测试用户' },
security: { passwd: testPasswd },
},
});
assert.equal(res.body.code, 400);
});
it('缺少密码应返回 400', async () => {
const res = await agent
.post('/user/register')
.send({
userInfo: {
profile: { mobile: '13900000099' },
security: {},
},
});
assert.equal(res.body.code, 400);
});
it('密码少于6位应返回 400', async () => {
const res = await agent
.post('/user/register')
.send({
userInfo: {
profile: { mobile: '13900000098' },
security: { passwd: '123' },
},
});
assert.equal(res.body.code, 400);
});
});
describe('POST /user/signin', () => {
it('用正确密码登录应成功', async () => {
const res = await agent
.post('/user/signin')
.send({ mobile: testMobile, passwd: testPasswd });
assert.equal(res.body.code, 0, `登录失败: ${res.body.msg}`);
assert.ok(res.body.data.user.security.token);
// 更新 token
token = res.body.data.user.security.token;
// 应记录最后登录信息
assert.ok(res.body.data.user.security.lastLoginAt);
});
it('错误密码应返回 401', async () => {
const res = await agent
.post('/user/signin')
.send({ mobile: testMobile, passwd: 'wrongpassword' });
assert.equal(res.body.code, 401);
});
it('不存在的用户应返回 401', async () => {
const res = await agent
.post('/user/signin')
.send({ mobile: '13999999999', passwd: 'test123456' });
assert.equal(res.body.code, 401);
});
it('缺少手机号应返回 400', async () => {
const res = await agent
.post('/user/signin')
.send({ passwd: testPasswd });
assert.equal(res.body.code, 400);
});
});
describe('POST /user/userInfo', () => {
it('用 Authorization Bearer 获取用户信息', async () => {
const res = await agent
.post('/user/userInfo')
.set('Authorization', `Bearer ${token}`)
.send({});
assert.equal(res.body.code, 0, `获取用户信息失败: ${res.body.msg}`);
assert.equal(res.body.data.user.profile.mobile, testMobile);
assert.equal(res.body.data.user.security.passwd, undefined);
});
it('用 body token 获取用户信息(向后兼容)', async () => {
const res = await agent
.post('/user/userInfo')
.send({ token });
assert.equal(res.body.code, 0);
assert.equal(res.body.data.user.profile.mobile, testMobile);
});
it('无效 token 应返回 401', async () => {
const res = await agent
.post('/user/userInfo')
.set('Authorization', 'Bearer invalid_token')
.send({});
assert.equal(res.body.code, 401);
});
it('缺少 token 应返回 400', async () => {
const res = await agent
.post('/user/userInfo')
.send({});
assert.equal(res.body.code, 400);
});
});
describe('POST /user/update', () => {
it('更新用户信息应成功', async () => {
const res = await agent
.post('/user/update')
.set('Authorization', `Bearer ${token}`)
.send({
profile: { name: '更新后的名字' },
});
assert.equal(res.body.code, 0, `更新失败: ${res.body.msg}`);
assert.equal(res.body.data.user.profile.name, '更新后的名字');
});
it('无 token 更新应返回 400', async () => {
const res = await agent
.post('/user/update')
.send({ profile: { name: 'test' } });
assert.equal(res.body.code, 400);
});
});
describe('POST /user/signout', () => {
it('退出登录应成功', async () => {
const res = await agent
.post('/user/signout')
.set('Authorization', `Bearer ${token}`)
.send({});
assert.equal(res.body.code, 0);
assert.equal(res.body.msg, '退出登录成功');
});
it('退出后用旧 token 获取信息应返回 401', async () => {
const res = await agent
.post('/user/userInfo')
.set('Authorization', `Bearer ${token}`)
.send({});
assert.equal(res.body.code, 401);
});
});
describe('密码迁移MD5 → bcrypt', () => {
it('旧 MD5 用户登录应自动升级密码', async () => {
// 先注册一个新用户
const mobile = '13900000002';
const passwd = 'md5test123';
// 直接写入一个 MD5 密码的用户
const crypto = await import('crypto');
const salt = crypto.randomBytes(8).toString('hex');
const md5Hash = crypto.createHash('md5').update(passwd + salt).digest('hex');
const newUser = {
profile: { mobile, name: 'MD5测试用户' },
security: { passwd: md5Hash, passwdSalt: salt },
status: { account: 'normal' },
app: {},
social: { wechat: {} },
};
const user = await DBModel.User.setUser(newUser);
assert.ok(user);
// 用旧密码登录
const res = await agent
.post('/user/signin')
.send({ mobile, passwd });
assert.equal(res.body.code, 0, `MD5迁移登录失败: ${res.body.msg}`);
// 验证密码已升级为 bcrypt
const updatedUser = await DBModel.User.findOne({ 'profile.mobile': mobile });
assert.ok(updatedUser.security.passwd.startsWith('$2'), '密码应已升级为 bcrypt');
assert.equal(updatedUser.security.passwdSalt, undefined, 'salt 应已清除');
// 清理
await DBModel.User.delUser(updatedUser._id);
});
});
});