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 = { 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('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('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('buyer'), }), fc.record({ id: userIdArbitrary, username: usernameArbitrary, email: emailArbitrary, role: fc.constant('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('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 } ); }); }); });