diff --git a/src/common/data-scope.spec.ts b/src/common/data-scope.spec.ts new file mode 100644 index 0000000..8666e0d --- /dev/null +++ b/src/common/data-scope.spec.ts @@ -0,0 +1,16 @@ +import { describe, expect, it } from 'vitest'; +import { resolveDataScope } from './data-scope.js'; +import type { DataScope } from './enums.js'; + +describe('resolveDataScope', () => { + it('空角色默认 self(仅本人)', () => { + expect(resolveDataScope([])).toBe('self'); + }); + + it('多角色取最高数据权限档', () => { + expect(resolveDataScope(['self', 'all'] as DataScope[])).toBe('all'); + expect(resolveDataScope(['self', 'dept'] as DataScope[])).toBe('dept'); + expect(resolveDataScope(['dept', 'dept_and_child'] as DataScope[])).toBe('dept_and_child'); + expect(resolveDataScope(['custom', 'dept'] as DataScope[])).toBe('dept'); + }); +}); diff --git a/src/common/mask.spec.ts b/src/common/mask.spec.ts new file mode 100644 index 0000000..9d076a0 --- /dev/null +++ b/src/common/mask.spec.ts @@ -0,0 +1,24 @@ +import { describe, expect, it } from 'vitest'; +import { maskSensitive } from './mask.js'; + +describe('maskSensitive', () => { + it('脱敏密码/令牌类字段', () => { + expect(maskSensitive({ password: 'secret', token: 'abc' })).toEqual({ password: '***', token: '***' }); + }); + + it('脱敏手机号与邮箱', () => { + expect(maskSensitive({ phone: '13800138000', email: 'a@b.com' })).toEqual({ phone: '***', email: '***' }); + }); + + it('递归脱敏嵌套对象与数组', () => { + expect(maskSensitive({ user: { password: 'x' }, list: [{ phone: '1' }] })).toEqual({ + user: { password: '***' }, + list: [{ phone: '***' }], + }); + }); + + it('原样返回基本类型', () => { + expect(maskSensitive('hello')).toBe('hello'); + expect(maskSensitive(null)).toBeNull(); + }); +}); diff --git a/test/app.e2e-spec.ts b/test/app.e2e-spec.ts index af084aa..9dcf563 100644 --- a/test/app.e2e-spec.ts +++ b/test/app.e2e-spec.ts @@ -1,29 +1,45 @@ import { Test, TestingModule } from '@nestjs/testing'; -import { INestApplication } from '@nestjs/common'; +import { INestApplication, ValidationPipe } from '@nestjs/common'; import request from 'supertest'; import { App } from 'supertest/types.js'; import { AppModule } from './../src/app.module.js'; -describe('AppController (e2e)', () => { +/** + * 登录链路 e2e:需要已启动的 MySQL/Redis 且已执行 pnpm seed(默认账号 touyun / Touyun@123)。 + */ +describe('Auth (e2e)', () => { let app: INestApplication; - beforeEach(async () => { + 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('/ (GET)', () => { - return request(app.getHttpServer()) - .get('/') - .expect(200) - .expect('Hello World!'); + 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(); }); - afterEach(async () => { + it('密码错误返回 401', async () => { + const res = await request(app.getHttpServer()) + .post('/api/v1/auth/login') + .send({ username: 'touyun', password: 'wrong-password' }) + .expect(401); + expect(res.body.code).toBe(40100); + }); + + afterAll(async () => { await app.close(); }); });