feat: 实现权限验证工具
- 创建 PermissionHelper 类 - 支持验证页面访问权限 - 支持验证CRUD操作权限 - 支持验证角色权限边界 - 添加基础单元测试(5个测试用例全部通过)
This commit is contained in:
@@ -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');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -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<void> {
|
||||||
|
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<void> {
|
||||||
|
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<boolean> {
|
||||||
|
const deniedMessage = this.page.locator('text=/无权限|权限不足|Access Denied|Forbidden/i');
|
||||||
|
return await deniedMessage.count() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
async verifyCanCreate(resource: string, createButtonSelector: string): Promise<void> {
|
||||||
|
const createButton = this.page.locator(createButtonSelector);
|
||||||
|
await expect(createButton).toBeVisible();
|
||||||
|
await expect(createButton).toBeEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
async verifyCannotCreate(resource: string, createButtonSelector: string): Promise<void> {
|
||||||
|
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<void> {
|
||||||
|
const editButton = this.page.locator(editButtonSelector);
|
||||||
|
await expect(editButton).toBeVisible();
|
||||||
|
await expect(editButton).toBeEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
async verifyCannotEdit(resourceId: string, editButtonSelector: string): Promise<void> {
|
||||||
|
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<void> {
|
||||||
|
const deleteButton = this.page.locator(deleteButtonSelector);
|
||||||
|
await expect(deleteButton).toBeVisible();
|
||||||
|
await expect(deleteButton).toBeEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
async verifyCannotDelete(resourceId: string, deleteButtonSelector: string): Promise<void> {
|
||||||
|
const deleteButton = this.page.locator(deleteButtonSelector);
|
||||||
|
const count = await deleteButton.count();
|
||||||
|
|
||||||
|
if (count > 0) {
|
||||||
|
await expect(deleteButton).not.toBeVisible();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async verifyRolePermissions(role: RoleDefinition): Promise<void> {
|
||||||
|
// 验证可访问的路径
|
||||||
|
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<void> {
|
||||||
|
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);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user