| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322 |
- import * as fc from 'fast-check';
- import jwt from 'jsonwebtoken';
- /**
- * **Feature: backend-frontend-integration, Property 8: Authentication Token Contains Role**
- * **Validates: Requirements 10.1**
- *
- * *For any* successful login, the returned JWT token SHALL contain a role claim
- * that matches the user's role in the database.
- */
- // 定义有效的用户角色
- type UserRole = 'buyer' | 'seller' | 'admin' | 'customer_service';
- /**
- * 模拟用户数据
- */
- interface MockUser {
- id: string;
- username: string;
- email: string;
- role: UserRole;
- permissions?: string[];
- }
- /**
- * JWT载荷接口
- */
- interface JwtPayload {
- userId: string;
- username: string;
- email: string;
- role: UserRole;
- permissions?: string[];
- iat?: number;
- exp?: number;
- }
- /**
- * 模拟Token生成逻辑
- * 这是一个纯函数,用于属性测试验证业务逻辑的正确性
- */
- function generateAccessToken(user: MockUser, secret: string, expiresIn: string = '24h'): string {
- const payload: Omit<JwtPayload, 'iat' | 'exp'> = {
- userId: user.id,
- username: user.username,
- email: user.email,
- role: user.role,
- permissions: user.permissions || [],
- };
- return jwt.sign(payload, secret, { expiresIn } as jwt.SignOptions);
- }
- /**
- * 验证Token中的角色信息
- */
- function verifyTokenRole(token: string, secret: string): JwtPayload | null {
- try {
- const decoded = jwt.verify(token, secret) as JwtPayload;
- return decoded;
- } catch {
- return null;
- }
- }
- /**
- * 模拟登录响应
- */
- interface LoginResponse {
- success: boolean;
- token?: string;
- user?: {
- id: string;
- username: string;
- email: string;
- role: UserRole;
- };
- error?: string;
- }
- /**
- * 模拟登录逻辑
- */
- function simulateLogin(user: MockUser | null, secret: string): LoginResponse {
- if (!user) {
- return { success: false, error: '用户不存在' };
- }
- const token = generateAccessToken(user, secret);
- return {
- success: true,
- token,
- user: {
- id: user.id,
- username: user.username,
- email: user.email,
- role: user.role,
- },
- };
- }
- describe('AuthService Property Tests', () => {
- const TEST_SECRET = 'test-jwt-secret-key-for-property-testing';
- describe('Property 8: Authentication Token Contains Role', () => {
- // 生成有效的用户角色
- const roleArbitrary = fc.constantFrom<UserRole>('buyer', 'seller', 'admin', 'customer_service');
- // 生成有效的用户名(字母数字,3-20字符)
- const usernameArbitrary = fc.stringMatching(/^[a-zA-Z][a-zA-Z0-9_]{2,19}$/);
- // 生成有效的邮箱
- const emailArbitrary = fc.emailAddress();
- // 生成有效的用户ID
- const userIdArbitrary = fc.uuid();
- // 生成完整的用户对象
- const userArbitrary = fc.record({
- id: userIdArbitrary,
- username: usernameArbitrary,
- email: emailArbitrary,
- role: roleArbitrary,
- permissions: fc.array(fc.string({ minLength: 1, maxLength: 50 }), { maxLength: 10 }),
- });
- it('生成的Token应该包含与用户相同的角色', () => {
- fc.assert(
- fc.property(userArbitrary, (user) => {
- // 生成Token
- const token = generateAccessToken(user, TEST_SECRET);
- // 解码Token
- const decoded = verifyTokenRole(token, TEST_SECRET);
- // 验证:Token中的角色应该与用户角色匹配
- return decoded !== null && decoded.role === user.role;
- }),
- { numRuns: 100 }
- );
- });
- it('登录成功后返回的Token应该包含正确的角色信息', () => {
- fc.assert(
- fc.property(userArbitrary, (user) => {
- // 模拟登录
- const loginResult = simulateLogin(user, TEST_SECRET);
- if (!loginResult.success || !loginResult.token) {
- return false;
- }
- // 解码Token
- const decoded = verifyTokenRole(loginResult.token, TEST_SECRET);
- // 验证:Token中的角色应该与登录响应中的用户角色匹配
- return (
- decoded !== null &&
- decoded.role === user.role &&
- loginResult.user?.role === user.role
- );
- }),
- { numRuns: 100 }
- );
- });
- it('Token中的角色应该是有效的角色类型', () => {
- const validRoles: UserRole[] = ['buyer', 'seller', 'admin', 'customer_service'];
- fc.assert(
- fc.property(userArbitrary, (user) => {
- // 生成Token
- const token = generateAccessToken(user, TEST_SECRET);
- // 解码Token
- const decoded = verifyTokenRole(token, TEST_SECRET);
- // 验证:Token中的角色应该是有效的角色类型
- return decoded !== null && validRoles.includes(decoded.role);
- }),
- { numRuns: 100 }
- );
- });
- it('seller角色的用户登录后Token应该包含seller角色', () => {
- fc.assert(
- fc.property(
- fc.record({
- id: userIdArbitrary,
- username: usernameArbitrary,
- email: emailArbitrary,
- role: fc.constant<UserRole>('seller'),
- permissions: fc.array(fc.string({ minLength: 1, maxLength: 50 }), { maxLength: 10 }),
- }),
- (sellerUser) => {
- // 模拟登录
- const loginResult = simulateLogin(sellerUser, TEST_SECRET);
- if (!loginResult.success || !loginResult.token) {
- return false;
- }
- // 解码Token
- const decoded = verifyTokenRole(loginResult.token, TEST_SECRET);
- // 验证:Token中的角色应该是seller
- return decoded !== null && decoded.role === 'seller';
- }
- ),
- { numRuns: 100 }
- );
- });
- it('Token中应该包含完整的用户信息', () => {
- fc.assert(
- fc.property(userArbitrary, (user) => {
- // 生成Token
- const token = generateAccessToken(user, TEST_SECRET);
- // 解码Token
- const decoded = verifyTokenRole(token, TEST_SECRET);
- // 验证:Token应该包含所有必要的用户信息
- return (
- decoded !== null &&
- decoded.userId === user.id &&
- decoded.username === user.username &&
- decoded.email === user.email &&
- decoded.role === user.role
- );
- }),
- { numRuns: 100 }
- );
- });
- it('不同角色的用户生成的Token应该有不同的角色声明', () => {
- fc.assert(
- fc.property(
- fc.tuple(
- fc.record({
- id: userIdArbitrary,
- username: usernameArbitrary,
- email: emailArbitrary,
- role: fc.constant<UserRole>('buyer'),
- }),
- fc.record({
- id: userIdArbitrary,
- username: usernameArbitrary,
- email: emailArbitrary,
- role: fc.constant<UserRole>('seller'),
- })
- ),
- ([buyerUser, sellerUser]) => {
- // 生成两个Token
- const buyerToken = generateAccessToken(buyerUser, TEST_SECRET);
- const sellerToken = generateAccessToken(sellerUser, TEST_SECRET);
- // 解码Token
- const buyerDecoded = verifyTokenRole(buyerToken, TEST_SECRET);
- const sellerDecoded = verifyTokenRole(sellerToken, TEST_SECRET);
- // 验证:两个Token的角色应该不同
- return (
- buyerDecoded !== null &&
- sellerDecoded !== null &&
- buyerDecoded.role === 'buyer' &&
- sellerDecoded.role === 'seller' &&
- (buyerDecoded.role as string) !== (sellerDecoded.role as string)
- );
- }
- ),
- { numRuns: 100 }
- );
- });
- it('Token应该可以被正确验证', () => {
- fc.assert(
- fc.property(userArbitrary, (user) => {
- // 生成Token
- const token = generateAccessToken(user, TEST_SECRET);
- // 使用正确的密钥验证
- const decoded = verifyTokenRole(token, TEST_SECRET);
- // 使用错误的密钥验证
- const invalidDecoded = verifyTokenRole(token, 'wrong-secret');
- // 验证:正确密钥应该成功,错误密钥应该失败
- return decoded !== null && invalidDecoded === null;
- }),
- { numRuns: 100 }
- );
- });
- it('admin角色应该能够访问所有资源', () => {
- fc.assert(
- fc.property(
- fc.record({
- id: userIdArbitrary,
- username: usernameArbitrary,
- email: emailArbitrary,
- role: fc.constant<UserRole>('admin'),
- permissions: fc.array(fc.string({ minLength: 1, maxLength: 50 }), { maxLength: 10 }),
- }),
- (adminUser) => {
- // 生成Token
- const token = generateAccessToken(adminUser, TEST_SECRET);
- // 解码Token
- const decoded = verifyTokenRole(token, TEST_SECRET);
- // 验证:admin角色应该被正确设置
- return decoded !== null && decoded.role === 'admin';
- }
- ),
- { numRuns: 100 }
- );
- });
- });
- });
|