diff --git a/novalon-manage-web/e2e/role-based-tests/shared/__tests__/permission-helper.test.ts b/novalon-manage-web/e2e/role-based-tests/shared/__tests__/permission-helper.test.ts new file mode 100644 index 0000000..34c4761 --- /dev/null +++ b/novalon-manage-web/e2e/role-based-tests/shared/__tests__/permission-helper.test.ts @@ -0,0 +1,69 @@ +import { describe, it, expect, vi } from 'vitest'; +import { PermissionHelper } from '../permission-helper'; +import type { RoleDefinition } from '../../roles/base.role'; + +// Mock Playwright +vi.mock('@playwright/test', () => ({ + expect: Object.assign(vi.fn(), { + extend: vi.fn().mockReturnValue(expect), + }), +})); + +describe('PermissionHelper', () => { + it('should create PermissionHelper instance', () => { + const mockPage = { + goto: vi.fn(), + url: vi.fn().mockReturnValue('http://localhost:3000/dashboard'), + locator: vi.fn().mockReturnValue({ + count: vi.fn().mockResolvedValue(0), + }), + } as any; + + const helper = new PermissionHelper(mockPage); + expect(helper).toBeDefined(); + }); + + it('should have verifyCanAccess method', () => { + const mockPage = { + goto: vi.fn(), + url: vi.fn().mockReturnValue('http://localhost:3000/dashboard'), + locator: vi.fn(), + } as any; + + const helper = new PermissionHelper(mockPage); + expect(typeof helper.verifyCanAccess).toBe('function'); + }); + + it('should have verifyCannotAccess method', () => { + const mockPage = { + goto: vi.fn(), + url: vi.fn(), + locator: vi.fn(), + } as any; + + const helper = new PermissionHelper(mockPage); + expect(typeof helper.verifyCannotAccess).toBe('function'); + }); + + it('should have verifyRolePermissions method', () => { + const mockPage = { + goto: vi.fn(), + url: vi.fn(), + locator: vi.fn(), + } as any; + + const helper = new PermissionHelper(mockPage); + expect(typeof helper.verifyRolePermissions).toBe('function'); + }); + + it('should have verifyPermissionBoundary method', () => { + const mockPage = { + goto: vi.fn(), + url: vi.fn(), + locator: vi.fn(), + } as any; + + const helper = new PermissionHelper(mockPage); + expect(typeof helper.verifyPermissionBoundary).toBe('function'); + }); +}); diff --git a/novalon-manage-web/e2e/role-based-tests/shared/permission-helper.ts b/novalon-manage-web/e2e/role-based-tests/shared/permission-helper.ts new file mode 100644 index 0000000..cf61aac --- /dev/null +++ b/novalon-manage-web/e2e/role-based-tests/shared/permission-helper.ts @@ -0,0 +1,131 @@ +import { Page, expect } from '@playwright/test'; +import type { RoleDefinition } from '../roles/base.role'; + +export class PermissionHelper { + constructor(private page: Page) {} + + async verifyCanAccess(path: string): Promise { + await this.page.goto(path); + await expect(this.page).not.toHaveURL(/\/login/); + await expect(this.page).not.toHaveURL(/\/403/); + await expect(this.page).not.toHaveURL(/\/404/); + } + + async verifyCannotAccess(path: string): Promise { + await this.page.goto(path); + + // 应该被重定向到登录页或显示403错误 + const url = this.page.url(); + const isForbidden = url.includes('/403') || url.includes('/login'); + + expect(isForbidden || await this.isAccessDenied()).toBeTruthy(); + } + + private async isAccessDenied(): Promise { + const deniedMessage = this.page.locator('text=/无权限|权限不足|Access Denied|Forbidden/i'); + return await deniedMessage.count() > 0; + } + + async verifyCanCreate(resource: string, createButtonSelector: string): Promise { + const createButton = this.page.locator(createButtonSelector); + await expect(createButton).toBeVisible(); + await expect(createButton).toBeEnabled(); + } + + async verifyCannotCreate(resource: string, createButtonSelector: string): Promise { + const createButton = this.page.locator(createButtonSelector); + const count = await createButton.count(); + + if (count > 0) { + await expect(createButton).not.toBeVisible(); + } + } + + async verifyCanEdit(resourceId: string, editButtonSelector: string): Promise { + const editButton = this.page.locator(editButtonSelector); + await expect(editButton).toBeVisible(); + await expect(editButton).toBeEnabled(); + } + + async verifyCannotEdit(resourceId: string, editButtonSelector: string): Promise { + const editButton = this.page.locator(editButtonSelector); + const count = await editButton.count(); + + if (count > 0) { + await expect(editButton).not.toBeVisible(); + } + } + + async verifyCanDelete(resourceId: string, deleteButtonSelector: string): Promise { + const deleteButton = this.page.locator(deleteButtonSelector); + await expect(deleteButton).toBeVisible(); + await expect(deleteButton).toBeEnabled(); + } + + async verifyCannotDelete(resourceId: string, deleteButtonSelector: string): Promise { + const deleteButton = this.page.locator(deleteButtonSelector); + const count = await deleteButton.count(); + + if (count > 0) { + await expect(deleteButton).not.toBeVisible(); + } + } + + async verifyRolePermissions(role: RoleDefinition): Promise { + // 验证可访问的路径 + for (const path of role.expectedBehaviors.canRead) { + if (path !== 'self') { + await this.verifyCanAccess(`/${path}`); + } + } + + // 验证不可访问的路径 + for (const path of role.cannotAccess) { + await this.verifyCannotAccess(path); + } + } + + async verifyPermissionBoundary( + role: RoleDefinition, + testScenarios: { + resource: string; + path: string; + createButton?: string; + editButton?: string; + deleteButton?: string; + } + ): Promise { + await this.page.goto(testScenarios.path); + + // 验证创建权限 + if (testScenarios.createButton) { + if (role.expectedBehaviors.canCreate.includes(testScenarios.resource)) { + await this.verifyCanCreate(testScenarios.resource, testScenarios.createButton); + } else { + await this.verifyCannotCreate(testScenarios.resource, testScenarios.createButton); + } + } + + // 验证编辑权限 + if (testScenarios.editButton) { + if (role.expectedBehaviors.canUpdate.includes(testScenarios.resource)) { + await this.verifyCanEdit(testScenarios.resource, testScenarios.editButton); + } else { + await this.verifyCannotEdit(testScenarios.resource, testScenarios.editButton); + } + } + + // 验证删除权限 + if (testScenarios.deleteButton) { + if (role.expectedBehaviors.canDelete.includes(testScenarios.resource)) { + await this.verifyCanDelete(testScenarios.resource, testScenarios.deleteButton); + } else { + await this.verifyCannotDelete(testScenarios.resource, testScenarios.deleteButton); + } + } + } +} + +export function createPermissionHelper(page: Page): PermissionHelper { + return new PermissionHelper(page); +}