- 资源变更清权限缓存(H4 残留) - 菜单聚合过滤已删角色(与 resolveAuth 一致) - removeRole 事务化;removeTenant 清 Redis(userState/perm) - createTenant 事务化 + 未提供密码时生成随机密码 - 登录失败锁定加 IP 维度;register 禁止平台租户 - refresh token 落 ip/userAgent;e2e 用独立账号避免锁死 - .env.example 补 CORS_ORIGIN/NODE_ENV、脱敏默认密码 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
46 lines
1.5 KiB
TypeScript
46 lines
1.5 KiB
TypeScript
import { Test, TestingModule } from '@nestjs/testing';
|
||
import { INestApplication, ValidationPipe } from '@nestjs/common';
|
||
import request from 'supertest';
|
||
import { App } from 'supertest/types.js';
|
||
import { AppModule } from './../src/app.module.js';
|
||
|
||
/**
|
||
* 登录链路 e2e:需要已启动的 MySQL/Redis 且已执行 pnpm seed(默认账号 touyun / Touyun@123)。
|
||
*/
|
||
describe('Auth (e2e)', () => {
|
||
let app: INestApplication<App>;
|
||
|
||
beforeAll(async () => {
|
||
const moduleFixture: TestingModule = await Test.createTestingModule({
|
||
imports: [AppModule],
|
||
}).compile();
|
||
|
||
app = moduleFixture.createNestApplication();
|
||
app.setGlobalPrefix('api/v1');
|
||
app.useGlobalPipes(new ValidationPipe({ whitelist: true, transform: true }));
|
||
await app.init();
|
||
});
|
||
|
||
it('登录成功返回 token(按用户名自动探测租户)', async () => {
|
||
const res = await request(app.getHttpServer())
|
||
.post('/api/v1/auth/login')
|
||
.send({ username: 'touyun', password: 'Touyun@123' })
|
||
.expect(201);
|
||
expect(res.body.code).toBe(0);
|
||
expect(res.body.data.accessToken).toBeDefined();
|
||
expect(res.body.data.refreshToken).toBeDefined();
|
||
});
|
||
|
||
it('密码错误返回 401', async () => {
|
||
const res = await request(app.getHttpServer())
|
||
.post('/api/v1/auth/login')
|
||
.send({ username: 'e2e_wrong_user', password: 'wrong-password' })
|
||
.expect(401);
|
||
expect(res.body.code).toBe(40100);
|
||
});
|
||
|
||
afterAll(async () => {
|
||
await app.close();
|
||
});
|
||
});
|