diff --git a/novalon-manage-web/e2e/role-based-tests/shared/__tests__/role-auth-manager.test.ts b/novalon-manage-web/e2e/role-based-tests/shared/__tests__/role-auth-manager.test.ts new file mode 100644 index 0000000..8f4ae3f --- /dev/null +++ b/novalon-manage-web/e2e/role-based-tests/shared/__tests__/role-auth-manager.test.ts @@ -0,0 +1,79 @@ +import { describe, it, expect, beforeEach, vi } from 'vitest'; +import { RoleAuthManager } from '../role-auth-manager'; + +// Mock fetch +global.fetch = vi.fn(); + +describe('RoleAuthManager', () => { + beforeEach(() => { + RoleAuthManager.clearCache(); + vi.clearAllMocks(); + }); + + it('should authenticate and cache token', async () => { + const mockToken = 'mock-jwt-token-12345'; + (global.fetch as any).mockResolvedValueOnce({ + ok: true, + json: async () => ({ data: { token: mockToken } }) + }); + + const token = await RoleAuthManager.getRoleToken('admin'); + + expect(token).toBe(mockToken); + expect(global.fetch).toHaveBeenCalledWith( + expect.stringContaining('/api/auth/login'), + expect.objectContaining({ + method: 'POST', + body: expect.stringContaining('admin') + }) + ); + }); + + it('should return cached token on second call', async () => { + const mockToken = 'cached-token'; + (global.fetch as any).mockResolvedValueOnce({ + ok: true, + json: async () => ({ data: { token: mockToken } }) + }); + + const token1 = await RoleAuthManager.getRoleToken('admin'); + const token2 = await RoleAuthManager.getRoleToken('admin'); + + expect(token1).toBe(token2); + expect(global.fetch).toHaveBeenCalledTimes(1); + }); + + it('should throw error for unknown role', async () => { + await expect(RoleAuthManager.getRoleToken('unknown')).rejects.toThrow("Role 'unknown' not found"); + }); + + it('should throw error on authentication failure', async () => { + (global.fetch as any).mockResolvedValueOnce({ + ok: false, + statusText: 'Unauthorized' + }); + + await expect(RoleAuthManager.getRoleToken('admin')).rejects.toThrow('Authentication failed'); + }); + + it('should clear specific role token', async () => { + const mockToken = 'token-to-clear'; + (global.fetch as any).mockResolvedValueOnce({ + ok: true, + json: async () => ({ data: { token: mockToken } }) + }); + + await RoleAuthManager.getRoleToken('admin'); + RoleAuthManager.clearRoleToken('admin'); + + // 再次获取应该重新认证 + (global.fetch as any).mockResolvedValueOnce({ + ok: true, + json: async () => ({ data: { token: 'new-token' } }) + }); + + const newToken = await RoleAuthManager.getRoleToken('admin'); + expect(newToken).toBe('new-token'); + expect(global.fetch).toHaveBeenCalledTimes(2); + }); +}); diff --git a/novalon-manage-web/e2e/role-based-tests/shared/auth-helper.ts b/novalon-manage-web/e2e/role-based-tests/shared/auth-helper.ts new file mode 100644 index 0000000..4f019d7 --- /dev/null +++ b/novalon-manage-web/e2e/role-based-tests/shared/auth-helper.ts @@ -0,0 +1,76 @@ +import { Page, BrowserContext } from '@playwright/test'; +import { RoleFactory } from '../roles/role-factory'; +import { RoleAuthManager } from './role-auth-manager'; +import type { RoleDefinition } from '../roles/base.role'; + +export class AuthHelper { + constructor( + private page: Page, + private context: BrowserContext + ) {} + + async loginAsRole(roleName: string, useTokenInjection: boolean = true): Promise { + const role = RoleFactory.getRole(roleName); + + if (useTokenInjection) { + await this.injectToken(role); + } else { + await this.performLogin(role); + } + } + + private async injectToken(role: RoleDefinition): Promise { + const token = await RoleAuthManager.getRoleToken(role.name); + + // 注入token到localStorage + await this.page.addInitScript((token) => { + localStorage.setItem('token', token); + localStorage.setItem('username', 'admin'); + }, token); + + // 设置cookie + await this.context.addCookies([ + { + name: 'token', + value: token, + domain: 'localhost', + path: '/', + } + ]); + } + + private async performLogin(role: RoleDefinition): Promise { + await this.page.goto('/login'); + + await this.page.fill('input[placeholder*="用户名"]', role.credentials.username); + await this.page.fill('input[placeholder*="密码"]', role.credentials.password); + await this.page.click('button[type="submit"]'); + + // 等待登录成功跳转 + await this.page.waitForURL(/\/(dashboard|home)?/, { timeout: 10000 }); + } + + async logout(): Promise { + await this.page.click('[data-testid="user-menu"]'); + await this.page.click('[data-testid="logout-button"]'); + await this.page.waitForURL('/login'); + } + + async clearAuth(): Promise { + await this.context.clearCookies(); + await this.page.evaluate(() => { + localStorage.clear(); + sessionStorage.clear(); + }); + } +} + +export async function createAuthenticatedPage( + page: Page, + context: BrowserContext, + roleName: string +): Promise { + const helper = new AuthHelper(page, context); + await helper.loginAsRole(roleName); + return helper; +} diff --git a/novalon-manage-web/e2e/role-based-tests/shared/role-auth-manager.ts b/novalon-manage-web/e2e/role-based-tests/shared/role-auth-manager.ts new file mode 100644 index 0000000..7f43152 --- /dev/null +++ b/novalon-manage-web/e2e/role-based-tests/shared/role-auth-manager.ts @@ -0,0 +1,55 @@ +import { RoleFactory } from '../roles/role-factory'; + +interface TokenCache { + token: string; + expiresAt: number; +} + +export class RoleAuthManager { + private static tokenCache: Map = new Map(); + private static readonly API_BASE_URL = process.env.VITE_API_BASE_URL || 'http://localhost:8084'; + private static readonly TOKEN_EXPIRY_BUFFER = 60000; // 1分钟缓冲 + + static async getRoleToken(roleName: string): Promise { + const cached = this.tokenCache.get(roleName); + + if (cached && cached.expiresAt > Date.now() + this.TOKEN_EXPIRY_BUFFER) { + return cached.token; + } + + const role = RoleFactory.getRole(roleName); + const token = await this.authenticateWithBackend(role.credentials); + + this.tokenCache.set(roleName, { + token, + expiresAt: Date.now() + 3600000 // 假设token有效期1小时 + }); + + return token; + } + + private static async authenticateWithBackend(credentials: { username: string; password: string }): Promise { + const response = await fetch(`${this.API_BASE_URL}/api/auth/login`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(credentials), + }); + + if (!response.ok) { + throw new Error(`Authentication failed for user ${credentials.username}: ${response.statusText}`); + } + + const data = await response.json(); + return data.data?.token || data.token; + } + + static clearCache(): void { + this.tokenCache.clear(); + } + + static clearRoleToken(roleName: string): void { + this.tokenCache.delete(roleName); + } +}