diff --git a/novalon-manage-web/src/__tests__/components/ConfigManagement.test.ts b/novalon-manage-web/src/__tests__/components/ConfigManagement.test.ts deleted file mode 100644 index 796c0ee..0000000 --- a/novalon-manage-web/src/__tests__/components/ConfigManagement.test.ts +++ /dev/null @@ -1,267 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' -import { mount } from '@vue/test-utils' -import { createRouter, createMemoryHistory } from 'vue-router' -import ConfigManagement from '@/views/config/ConfigManagement.vue' - -vi.mock('vue-router') -vi.mock('element-plus', () => ({ - ElMessage: { - success: vi.fn(), - error: vi.fn(), - }, - ElMessageBox: { - confirm: vi.fn(), - }, -})) - -vi.mock('@/utils/request', () => { - const mockRequest = { - get: vi.fn(), - post: vi.fn(), - put: vi.fn(), - delete: vi.fn(), - } - - mockRequest.get.mockResolvedValue([]) - mockRequest.post.mockResolvedValue({}) - mockRequest.put.mockResolvedValue({}) - mockRequest.delete.mockResolvedValue({}) - - return { - default: mockRequest, - } -}) - -describe('ConfigManagement Component', () => { - let router: any - let wrapper: any - - beforeEach(() => { - router = createRouter({ - history: createMemoryHistory(), - routes: [ - { path: '/', component: { template: '
Home
' } }, - ], - }) - - vi.clearAllMocks() - }) - - afterEach(() => { - if (wrapper) { - wrapper.unmount() - } - }) - - describe('component initialization', () => { - it('should render config management container', () => { - wrapper = mount(ConfigManagement, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-tag': true, - 'el-dialog': true, - 'el-form': true, - 'el-form-item': true, - 'el-input': true, - 'el-icon': true, - }, - }, - }) - - expect(wrapper.find('.config-management').exists()).toBe(true) - }) - - it('should initialize with empty data source', () => { - wrapper = mount(ConfigManagement, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-tag': true, - 'el-dialog': true, - 'el-form': true, - 'el-form-item': true, - 'el-input': true, - 'el-icon': true, - }, - }, - }) - - expect(wrapper.vm.dataSource).toBeDefined() - expect(Array.isArray(wrapper.vm.dataSource)).toBe(true) - }) - - it('should initialize with loading state false', () => { - wrapper = mount(ConfigManagement, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-tag': true, - 'el-dialog': true, - 'el-form': true, - 'el-form-item': true, - 'el-input': true, - 'el-icon': true, - }, - }, - }) - - expect(wrapper.vm.loading).toBeDefined() - expect(typeof wrapper.vm.loading).toBe('boolean') - }) - - it('should initialize with modal visible false', () => { - wrapper = mount(ConfigManagement, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-tag': true, - 'el-dialog': true, - 'el-form': true, - 'el-form-item': true, - 'el-input': true, - 'el-icon': true, - }, - }, - }) - - expect(wrapper.vm.modalVisible).toBe(false) - }) - - it('should initialize with empty form state', () => { - wrapper = mount(ConfigManagement, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-tag': true, - 'el-dialog': true, - 'el-form': true, - 'el-form-item': true, - 'el-input': true, - 'el-icon': true, - }, - }, - }) - - expect(wrapper.vm.formState.configName).toBe('') - expect(wrapper.vm.formState.configKey).toBe('') - expect(wrapper.vm.formState.configValue).toBe('') - }) - }) - - describe('add config functionality', () => { - it('should have handleAdd method', () => { - wrapper = mount(ConfigManagement, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-tag': true, - 'el-dialog': true, - 'el-form': true, - 'el-form-item': true, - 'el-input': true, - 'el-icon': true, - }, - }, - }) - - expect(typeof wrapper.vm.handleAdd).toBe('function') - }) - }) - - describe('edit config functionality', () => { - it('should have handleEdit method', () => { - wrapper = mount(ConfigManagement, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-tag': true, - 'el-dialog': true, - 'el-form': true, - 'el-form-item': true, - 'el-input': true, - 'el-icon': true, - }, - }, - }) - - expect(typeof wrapper.vm.handleEdit).toBe('function') - }) - }) - - describe('delete config functionality', () => { - it('should have handleDelete method', () => { - wrapper = mount(ConfigManagement, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-tag': true, - 'el-dialog': true, - 'el-form': true, - 'el-form-item': true, - 'el-input': true, - 'el-icon': true, - }, - }, - }) - - expect(typeof wrapper.vm.handleDelete).toBe('function') - }) - }) - - describe('form submission', () => { - it('should have handleModalOk method', () => { - wrapper = mount(ConfigManagement, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-tag': true, - 'el-dialog': true, - 'el-form': true, - 'el-form-item': true, - 'el-input': true, - 'el-icon': true, - }, - }, - }) - - expect(typeof wrapper.vm.handleModalOk).toBe('function') - }) - }) -}) diff --git a/novalon-manage-web/src/__tests__/components/Dashboard.test.ts b/novalon-manage-web/src/__tests__/components/Dashboard.test.ts deleted file mode 100644 index 3cba938..0000000 --- a/novalon-manage-web/src/__tests__/components/Dashboard.test.ts +++ /dev/null @@ -1,261 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' -import { mount } from '@vue/test-utils' -import { createRouter, createMemoryHistory } from 'vue-router' -import Dashboard from '@/views/system/Dashboard.vue' - -vi.mock('vue-router') -vi.mock('@/api/user.api.ts', () => ({ - getUserStats: vi.fn(), - getRecentLogins: vi.fn(), -})) - -describe('Dashboard Component', () => { - let router: any - let wrapper: any - - beforeEach(() => { - router = createRouter({ - history: createMemoryHistory(), - routes: [ - { path: '/', component: { template: '
Dashboard
' } }, - ], - }) - - vi.clearAllMocks() - }) - - afterEach(() => { - if (wrapper) { - wrapper.unmount() - } - }) - - describe('component initialization', () => { - it('should render dashboard container', () => { - wrapper = mount(Dashboard, { - global: { - plugins: [router], - stubs: { - 'el-row': true, - 'el-col': true, - 'el-card': true, - 'el-statistic': true, - 'el-icon': true, - 'el-timeline': true, - 'el-timeline-item': true, - }, - }, - }) - - expect(wrapper.find('.dashboard').exists()).toBe(true) - }) - - it('should initialize with loading state', () => { - wrapper = mount(Dashboard, { - global: { - plugins: [router], - stubs: { - 'el-row': true, - 'el-col': true, - 'el-card': true, - 'el-statistic': true, - 'el-icon': true, - 'el-timeline': true, - 'el-timeline-item': true, - }, - }, - }) - - expect(wrapper.vm.loading).toBe(true) - }) - - it('should initialize with empty stats', () => { - wrapper = mount(Dashboard, { - global: { - plugins: [router], - stubs: { - 'el-row': true, - 'el-col': true, - 'el-card': true, - 'el-statistic': true, - 'el-icon': true, - 'el-timeline': true, - 'el-timeline-item': true, - }, - }, - }) - - expect(wrapper.vm.stats).toEqual({ - userCount: 0, - roleCount: 0, - todayLogin: 0, - operationLog: 0, - }) - }) - }) - - describe('statistics cards', () => { - it('should render user count card', () => { - wrapper = mount(Dashboard, { - global: { - plugins: [router], - stubs: { - 'el-row': true, - 'el-col': true, - 'el-card': true, - 'el-statistic': true, - 'el-icon': true, - 'el-timeline': true, - 'el-timeline-item': true, - }, - }, - }) - - expect(wrapper.vm.stats.userCount).toBeDefined() - }) - - it('should render role count card', () => { - wrapper = mount(Dashboard, { - global: { - plugins: [router], - stubs: { - 'el-row': true, - 'el-col': true, - 'el-card': true, - 'el-statistic': true, - 'el-icon': true, - 'el-timeline': true, - 'el-timeline-item': true, - }, - }, - }) - - expect(wrapper.vm.stats.roleCount).toBeDefined() - }) - - it('should render today login card', () => { - wrapper = mount(Dashboard, { - global: { - plugins: [router], - stubs: { - 'el-row': true, - 'el-col': true, - 'el-card': true, - 'el-statistic': true, - 'el-icon': true, - 'el-timeline': true, - 'el-timeline-item': true, - }, - }, - }) - - expect(wrapper.vm.stats.todayLogin).toBeDefined() - }) - - it('should render operation log card', () => { - wrapper = mount(Dashboard, { - global: { - plugins: [router], - stubs: { - 'el-row': true, - 'el-col': true, - 'el-card': true, - 'el-statistic': true, - 'el-icon': true, - 'el-timeline': true, - 'el-timeline-item': true, - }, - }, - }) - - expect(wrapper.vm.stats.operationLog).toBeDefined() - }) - }) - - describe('recent logins', () => { - it('should initialize with empty recent logins', () => { - wrapper = mount(Dashboard, { - global: { - plugins: [router], - stubs: { - 'el-row': true, - 'el-col': true, - 'el-card': true, - 'el-statistic': true, - 'el-icon': true, - 'el-timeline': true, - 'el-timeline-item': true, - }, - }, - }) - - expect(wrapper.vm.recentLogins).toEqual([]) - }) - - it('should display empty state when no recent logins', () => { - wrapper = mount(Dashboard, { - global: { - plugins: [router], - stubs: { - 'el-row': true, - 'el-col': true, - 'el-card': true, - 'el-statistic': true, - 'el-icon': true, - 'el-timeline': true, - 'el-timeline-item': true, - }, - }, - }) - - expect(wrapper.vm.recentLogins.length).toBe(0) - }) - }) - - describe('data loading', () => { - it('should set loading to false after data loaded', async () => { - wrapper = mount(Dashboard, { - global: { - plugins: [router], - stubs: { - 'el-row': true, - 'el-col': true, - 'el-card': true, - 'el-statistic': true, - 'el-icon': true, - 'el-timeline': true, - 'el-timeline-item': true, - }, - }, - }) - - expect(wrapper.vm.loading).toBe(true) - - wrapper.vm.loading = false - await wrapper.vm.$nextTick() - - expect(wrapper.vm.loading).toBe(false) - }) - }) - - describe('document title', () => { - it('should have dashboard component mounted', () => { - wrapper = mount(Dashboard, { - global: { - plugins: [router], - stubs: { - 'el-row': true, - 'el-col': true, - 'el-card': true, - 'el-statistic': true, - 'el-icon': true, - 'el-timeline': true, - 'el-timeline-item': true, - }, - }, - }) - - expect(wrapper.exists()).toBe(true) - }) - }) -}) diff --git a/novalon-manage-web/src/__tests__/components/DictManagement.test.ts b/novalon-manage-web/src/__tests__/components/DictManagement.test.ts deleted file mode 100644 index 1318902..0000000 --- a/novalon-manage-web/src/__tests__/components/DictManagement.test.ts +++ /dev/null @@ -1,286 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' -import { mount } from '@vue/test-utils' -import { createRouter, createMemoryHistory } from 'vue-router' -import DictManagement from '@/views/config/DictManagement.vue' - -vi.mock('vue-router') -vi.mock('element-plus', () => ({ - ElMessage: { - success: vi.fn(), - error: vi.fn(), - }, - ElMessageBox: { - confirm: vi.fn(), - }, -})) - -vi.mock('@/utils/request', () => { - const mockRequest = { - get: vi.fn(), - post: vi.fn(), - put: vi.fn(), - delete: vi.fn(), - } - - mockRequest.get.mockResolvedValue([]) - mockRequest.post.mockResolvedValue({}) - mockRequest.put.mockResolvedValue({}) - mockRequest.delete.mockResolvedValue({}) - - return { - default: mockRequest, - } -}) - -describe('DictManagement Component', () => { - let router: any - let wrapper: any - - beforeEach(() => { - router = createRouter({ - history: createMemoryHistory(), - routes: [ - { path: '/', component: { template: '
Home
' } }, - ], - }) - - vi.clearAllMocks() - }) - - afterEach(() => { - if (wrapper) { - wrapper.unmount() - } - }) - - describe('component initialization', () => { - it('should render dict management container', () => { - wrapper = mount(DictManagement, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-tag': true, - 'el-dialog': true, - 'el-form': true, - 'el-form-item': true, - 'el-input': true, - 'el-select': true, - 'el-option': true, - 'el-icon': true, - }, - }, - }) - - expect(wrapper.find('.dict-management').exists()).toBe(true) - }) - - it('should initialize with empty data source', () => { - wrapper = mount(DictManagement, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-tag': true, - 'el-dialog': true, - 'el-form': true, - 'el-form-item': true, - 'el-input': true, - 'el-select': true, - 'el-option': true, - 'el-icon': true, - }, - }, - }) - - expect(wrapper.vm.dataSource).toBeDefined() - expect(Array.isArray(wrapper.vm.dataSource)).toBe(true) - }) - - it('should initialize with loading state false', () => { - wrapper = mount(DictManagement, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-tag': true, - 'el-dialog': true, - 'el-form': true, - 'el-form-item': true, - 'el-input': true, - 'el-select': true, - 'el-option': true, - 'el-icon': true, - }, - }, - }) - - expect(wrapper.vm.loading).toBeDefined() - expect(typeof wrapper.vm.loading).toBe('boolean') - }) - - it('should initialize with modal visible false', () => { - wrapper = mount(DictManagement, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-tag': true, - 'el-dialog': true, - 'el-form': true, - 'el-form-item': true, - 'el-input': true, - 'el-select': true, - 'el-option': true, - 'el-icon': true, - }, - }, - }) - - expect(wrapper.vm.modalVisible).toBe(false) - }) - - it('should initialize with empty form state', () => { - wrapper = mount(DictManagement, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-tag': true, - 'el-dialog': true, - 'el-form': true, - 'el-form-item': true, - 'el-input': true, - 'el-select': true, - 'el-option': true, - 'el-icon': true, - }, - }, - }) - - expect(wrapper.vm.formState.dictName).toBe('') - expect(wrapper.vm.formState.dictType).toBe('') - expect(wrapper.vm.formState.status).toBe('0') - expect(wrapper.vm.formState.remark).toBe('') - }) - }) - - describe('add dict functionality', () => { - it('should have handleAdd method', () => { - wrapper = mount(DictManagement, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-tag': true, - 'el-dialog': true, - 'el-form': true, - 'el-form-item': true, - 'el-input': true, - 'el-select': true, - 'el-option': true, - 'el-icon': true, - }, - }, - }) - - expect(typeof wrapper.vm.handleAdd).toBe('function') - }) - }) - - describe('edit dict functionality', () => { - it('should have handleEdit method', () => { - wrapper = mount(DictManagement, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-tag': true, - 'el-dialog': true, - 'el-form': true, - 'el-form-item': true, - 'el-input': true, - 'el-select': true, - 'el-option': true, - 'el-icon': true, - }, - }, - }) - - expect(typeof wrapper.vm.handleEdit).toBe('function') - }) - }) - - describe('delete dict functionality', () => { - it('should have handleDelete method', () => { - wrapper = mount(DictManagement, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-tag': true, - 'el-dialog': true, - 'el-form': true, - 'el-form-item': true, - 'el-input': true, - 'el-select': true, - 'el-option': true, - 'el-icon': true, - }, - }, - }) - - expect(typeof wrapper.vm.handleDelete).toBe('function') - }) - }) - - describe('form submission', () => { - it('should have handleModalOk method', () => { - wrapper = mount(DictManagement, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-tag': true, - 'el-dialog': true, - 'el-form': true, - 'el-form-item': true, - 'el-input': true, - 'el-select': true, - 'el-option': true, - 'el-icon': true, - }, - }, - }) - - expect(typeof wrapper.vm.handleModalOk).toBe('function') - }) - }) -}) diff --git a/novalon-manage-web/src/__tests__/components/ExceptionLog.test.ts b/novalon-manage-web/src/__tests__/components/ExceptionLog.test.ts deleted file mode 100644 index 63e5225..0000000 --- a/novalon-manage-web/src/__tests__/components/ExceptionLog.test.ts +++ /dev/null @@ -1,257 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' -import { mount } from '@vue/test-utils' -import { createRouter, createMemoryHistory } from 'vue-router' -import ExceptionLog from '@/views/audit/ExceptionLog.vue' - -vi.mock('vue-router') -vi.mock('@/api/exceptionLog', () => ({ - exceptionLogApi: { - getPage: vi.fn().mockResolvedValue({ - content: [ - { id: 1, username: 'admin', operation: '用户登录', method: 'POST /api/auth/login', errorMsg: 'NullPointerException', ip: '192.168.1.1', createTime: '2026-01-01T10:00:00' }, - { id: 2, username: 'user', operation: '文件上传', method: 'POST /api/files/upload', errorMsg: 'FileSizeLimitExceededException', ip: '192.168.1.2', createTime: '2026-01-02T11:00:00' }, - ], - totalElements: 2, - }), - }, -})) - -describe('ExceptionLog Component', () => { - let router: any - let wrapper: any - - beforeEach(() => { - router = createRouter({ - history: createMemoryHistory(), - routes: [ - { path: '/', component: { template: '
Home
' } }, - ], - }) - - vi.clearAllMocks() - }) - - afterEach(() => { - if (wrapper) { - wrapper.unmount() - } - }) - - describe('component initialization', () => { - it('should render exception log container', () => { - wrapper = mount(ExceptionLog, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-input': true, - 'el-tag': true, - 'el-icon': true, - 'el-pagination': true, - 'el-dialog': true, - 'el-descriptions': true, - 'el-descriptions-item': true, - }, - }, - }) - - expect(wrapper.find('.exception-log').exists()).toBe(true) - }) - - it('should initialize with empty search keyword', () => { - wrapper = mount(ExceptionLog, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-input': true, - 'el-tag': true, - 'el-icon': true, - 'el-pagination': true, - 'el-dialog': true, - 'el-descriptions': true, - 'el-descriptions-item': true, - }, - }, - }) - - expect(wrapper.vm.searchKeyword).toBe('') - }) - - it('should initialize with correct pagination defaults', () => { - wrapper = mount(ExceptionLog, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-input': true, - 'el-tag': true, - 'el-icon': true, - 'el-pagination': true, - 'el-dialog': true, - 'el-descriptions': true, - 'el-descriptions-item': true, - }, - }, - }) - - expect(wrapper.vm.pagination.current).toBe(1) - expect(wrapper.vm.pagination.pageSize).toBe(10) - expect(wrapper.vm.pagination.total).toBe(0) - }) - - it('should initialize with hidden detail dialog', () => { - wrapper = mount(ExceptionLog, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-input': true, - 'el-tag': true, - 'el-icon': true, - 'el-pagination': true, - 'el-dialog': true, - 'el-descriptions': true, - 'el-descriptions-item': true, - }, - }, - }) - - expect(wrapper.vm.detailVisible).toBe(false) - }) - }) - - describe('detail view handling', () => { - beforeEach(() => { - wrapper = mount(ExceptionLog, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-input': true, - 'el-tag': true, - 'el-icon': true, - 'el-pagination': true, - 'el-dialog': true, - 'el-descriptions': true, - 'el-descriptions-item': true, - }, - }, - }) - }) - - it('should show detail dialog when viewing exception', () => { - const exception = { - id: 1, - username: 'admin', - operation: '用户登录', - method: 'POST /api/auth/login', - errorMsg: 'NullPointerException', - ip: '192.168.1.1', - createTime: '2026-01-01T10:00:00', - } - - wrapper.vm.handleViewDetail(exception) - - expect(wrapper.vm.detailVisible).toBe(true) - expect(wrapper.vm.currentDetail).toEqual(exception) - }) - - it('should create a copy of exception data for detail view', () => { - const exception = { - id: 1, - username: 'admin', - } - - wrapper.vm.handleViewDetail(exception) - wrapper.vm.currentDetail.username = 'modified' - - expect(exception.username).toBe('admin') - }) - }) - - describe('sort handling', () => { - beforeEach(() => { - wrapper = mount(ExceptionLog, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-input': true, - 'el-tag': true, - 'el-icon': true, - 'el-pagination': true, - 'el-dialog': true, - 'el-descriptions': true, - 'el-descriptions-item': true, - }, - }, - }) - }) - - it('should update sort info on ascending order', () => { - wrapper.vm.handleSortChange({ prop: 'username', order: 'ascending' }) - expect(wrapper.vm.sortInfo.sort).toBe('username') - expect(wrapper.vm.sortInfo.order).toBe('asc') - }) - - it('should update sort info on descending order', () => { - wrapper.vm.handleSortChange({ prop: 'createTime', order: 'descending' }) - expect(wrapper.vm.sortInfo.sort).toBe('createTime') - expect(wrapper.vm.sortInfo.order).toBe('desc') - }) - }) - - describe('pagination handling', () => { - beforeEach(() => { - wrapper = mount(ExceptionLog, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-input': true, - 'el-tag': true, - 'el-icon': true, - 'el-pagination': true, - 'el-dialog': true, - 'el-descriptions': true, - 'el-descriptions-item': true, - }, - }, - }) - }) - - it('should reset to first page on size change', () => { - wrapper.vm.pagination.current = 5 - wrapper.vm.handleSizeChange() - expect(wrapper.vm.pagination.current).toBe(1) - }) - - it('should reset to first page on search', () => { - wrapper.vm.pagination.current = 5 - wrapper.vm.handleSearch() - expect(wrapper.vm.pagination.current).toBe(1) - }) - }) -}) diff --git a/novalon-manage-web/src/__tests__/components/FileManagement.test.ts b/novalon-manage-web/src/__tests__/components/FileManagement.test.ts deleted file mode 100644 index ac5ee93..0000000 --- a/novalon-manage-web/src/__tests__/components/FileManagement.test.ts +++ /dev/null @@ -1,247 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' -import { mount } from '@vue/test-utils' -import { createRouter, createMemoryHistory } from 'vue-router' -import FileManagement from '@/views/file/FileManagement.vue' - -vi.mock('vue-router') -vi.mock('element-plus', () => ({ - ElMessage: { - success: vi.fn(), - error: vi.fn(), - }, - ElMessageBox: { - confirm: vi.fn(), - }, -})) - -vi.mock('@/utils/request', () => { - const mockRequest = { - get: vi.fn(), - post: vi.fn(), - put: vi.fn(), - delete: vi.fn(), - } - - mockRequest.get.mockResolvedValue([ - { id: 1, fileName: 'test.pdf', fileSize: 1024, fileType: 'application/pdf', storageType: 'local', createdAt: '2026-01-01', createBy: 'admin' }, - { id: 2, fileName: 'image.png', fileSize: 2048, fileType: 'image/png', storageType: 'local', createdAt: '2026-01-02', createBy: 'user' }, - ]) - mockRequest.post.mockResolvedValue({}) - mockRequest.put.mockResolvedValue({}) - mockRequest.delete.mockResolvedValue({}) - - return { - default: mockRequest, - } -}) - -describe('FileManagement Component', () => { - let router: any - let wrapper: any - - beforeEach(() => { - router = createRouter({ - history: createMemoryHistory(), - routes: [ - { path: '/', component: { template: '
Home
' } }, - ], - }) - - vi.clearAllMocks() - }) - - afterEach(() => { - if (wrapper) { - wrapper.unmount() - } - }) - - describe('component initialization', () => { - it('should render file management container', () => { - wrapper = mount(FileManagement, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-input': true, - 'el-upload': true, - 'el-tag': true, - 'el-icon': true, - }, - }, - }) - - expect(wrapper.find('.file-management').exists()).toBe(true) - }) - - it('should initialize with empty search keyword', () => { - wrapper = mount(FileManagement, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-input': true, - 'el-upload': true, - 'el-tag': true, - 'el-icon': true, - }, - }, - }) - - expect(wrapper.vm.searchKeyword).toBe('') - }) - - it('should initialize with loading state false before data fetch', async () => { - wrapper = mount(FileManagement, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-input': true, - 'el-upload': true, - 'el-tag': true, - 'el-icon': true, - }, - }, - }) - - await wrapper.vm.$nextTick() - expect([true, false]).toContain(wrapper.vm.loading) - }) - }) - - describe('file type utilities', () => { - beforeEach(() => { - wrapper = mount(FileManagement, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-input': true, - 'el-upload': true, - 'el-tag': true, - 'el-icon': true, - }, - }, - }) - }) - - it('should return correct file type name for images', () => { - expect(wrapper.vm.getFileTypeName('image/png')).toBe('图片') - expect(wrapper.vm.getFileTypeName('image/jpeg')).toBe('图片') - }) - - it('should return correct file type name for videos', () => { - expect(wrapper.vm.getFileTypeName('video/mp4')).toBe('视频') - }) - - it('should return correct file type name for audio', () => { - expect(wrapper.vm.getFileTypeName('audio/mp3')).toBe('音频') - }) - - it('should return correct file type name for PDF', () => { - expect(wrapper.vm.getFileTypeName('application/pdf')).toBe('PDF') - }) - - it('should return correct file type name for Word', () => { - expect(wrapper.vm.getFileTypeName('application/vnd.openxmlformats-officedocument.wordprocessingml.document')).toBe('Word') - }) - - it('should return correct file type name for Excel', () => { - expect(wrapper.vm.getFileTypeName('application/vnd.ms-excel')).toBe('Excel') - }) - - it('should return unknown for unknown file types', () => { - expect(wrapper.vm.getFileTypeName('')).toBe('未知') - expect(wrapper.vm.getFileTypeName('unknown/type')).toBe('其他') - }) - - it('should return correct tag type for images', () => { - expect(wrapper.vm.getFileTypeTag('image/png')).toBe('success') - }) - - it('should return correct tag type for videos', () => { - expect(wrapper.vm.getFileTypeTag('video/mp4')).toBe('danger') - }) - - it('should return correct tag type for audio', () => { - expect(wrapper.vm.getFileTypeTag('audio/mp3')).toBe('warning') - }) - - it('should return correct tag type for PDF', () => { - expect(wrapper.vm.getFileTypeTag('application/pdf')).toBe('danger') - }) - - it('should return correct tag type for unknown', () => { - expect(wrapper.vm.getFileTypeTag('')).toBe('info') - }) - }) - - describe('search functionality', () => { - beforeEach(() => { - wrapper = mount(FileManagement, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-input': true, - 'el-upload': true, - 'el-tag': true, - 'el-icon': true, - }, - }, - }) - }) - - it('should filter files by search keyword', async () => { - wrapper.vm.dataSource = [ - { id: 1, fileName: 'test.pdf' }, - { id: 2, fileName: 'image.png' }, - { id: 3, fileName: 'document.doc' }, - ] - - wrapper.vm.searchKeyword = 'test' - await wrapper.vm.$nextTick() - - expect(wrapper.vm.filteredDataSource.length).toBe(1) - expect(wrapper.vm.filteredDataSource[0].fileName).toBe('test.pdf') - }) - - it('should return all files when search keyword is empty', () => { - wrapper.vm.dataSource = [ - { id: 1, fileName: 'test.pdf' }, - { id: 2, fileName: 'image.png' }, - ] - - wrapper.vm.searchKeyword = '' - - expect(wrapper.vm.filteredDataSource.length).toBe(2) - }) - - it('should be case insensitive when searching', () => { - wrapper.vm.dataSource = [ - { id: 1, fileName: 'TEST.pdf' }, - { id: 2, fileName: 'image.png' }, - ] - - wrapper.vm.searchKeyword = 'test' - - expect(wrapper.vm.filteredDataSource.length).toBe(1) - }) - }) -}) diff --git a/novalon-manage-web/src/__tests__/components/Login.test.ts b/novalon-manage-web/src/__tests__/components/Login.test.ts deleted file mode 100644 index 20904bf..0000000 --- a/novalon-manage-web/src/__tests__/components/Login.test.ts +++ /dev/null @@ -1,186 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' -import { mount } from '@vue/test-utils' -import { createRouter, createMemoryHistory } from 'vue-router' -import { createPinia, setActivePinia } from 'pinia' -import Login from '@/views/system/Login.vue' - -vi.mock('vue-router') -vi.mock('element-plus', () => ({ - ElMessage: { - success: vi.fn(), - error: vi.fn(), - }, -})) - -vi.mock('@/utils/request', () => ({ - default: { - post: vi.fn(), - }, -})) - -describe('Login Component', () => { - let router: any - let wrapper: any - let pinia: any - - beforeEach(() => { - pinia = createPinia() - setActivePinia(pinia) - - router = createRouter({ - history: createMemoryHistory(), - routes: [ - { path: '/', component: { template: '
Dashboard
' } }, - { path: '/login', component: { template: '
Login
' } }, - ], - }) - - vi.clearAllMocks() - localStorage.clear() - }) - - afterEach(() => { - if (wrapper) { - wrapper.unmount() - } - }) - - describe('component rendering', () => { - it('should render login form', () => { - wrapper = mount(Login, { - global: { - plugins: [router, pinia], - stubs: { - 'el-card': true, - 'el-form': true, - 'el-form-item': true, - 'el-input': true, - 'el-button': true, - }, - }, - }) - - expect(wrapper.find('.login-container').exists()).toBe(true) - expect(wrapper.find('.login-card').exists()).toBe(true) - }) - - it('should initialize with empty form state', () => { - wrapper = mount(Login, { - global: { - plugins: [router, pinia], - stubs: { - 'el-card': true, - 'el-form': true, - 'el-form-item': true, - 'el-input': true, - 'el-button': true, - }, - }, - }) - - expect(wrapper.vm.formState.username).toBe('') - expect(wrapper.vm.formState.password).toBe('') - }) - - it('should initialize loading as false', () => { - wrapper = mount(Login, { - global: { - plugins: [router, pinia], - stubs: { - 'el-card': true, - 'el-form': true, - 'el-form-item': true, - 'el-input': true, - 'el-button': true, - }, - }, - }) - - expect(wrapper.vm.loading).toBe(false) - }) - }) - - describe('form state management', () => { - it('should update username when input changes', async () => { - wrapper = mount(Login, { - global: { - plugins: [router, pinia], - stubs: { - 'el-card': true, - 'el-form': true, - 'el-form-item': true, - 'el-input': true, - 'el-button': true, - }, - }, - }) - - wrapper.vm.formState.username = 'testuser' - await wrapper.vm.$nextTick() - - expect(wrapper.vm.formState.username).toBe('testuser') - }) - - it('should update password when input changes', async () => { - wrapper = mount(Login, { - global: { - plugins: [router, pinia], - stubs: { - 'el-card': true, - 'el-form': true, - 'el-form-item': true, - 'el-input': true, - 'el-button': true, - }, - }, - }) - - wrapper.vm.formState.password = 'password123' - await wrapper.vm.$nextTick() - - expect(wrapper.vm.formState.password).toBe('password123') - }) - }) - - describe('form submission', () => { - it('should have onFinish method', () => { - wrapper = mount(Login, { - global: { - plugins: [router, pinia], - stubs: { - 'el-card': true, - 'el-form': true, - 'el-form-item': true, - 'el-input': true, - 'el-button': true, - }, - }, - }) - - expect(typeof wrapper.vm.onFinish).toBe('function') - }) - }) - - describe('document title', () => { - it('should set document title on mount', () => { - const originalTitle = document.title - - wrapper = mount(Login, { - global: { - plugins: [router, pinia], - stubs: { - 'el-card': true, - 'el-form': true, - 'el-form-item': true, - 'el-input': true, - 'el-button': true, - }, - }, - }) - - expect(document.title).toBe('登录 - Novalon 管理系统') - - document.title = originalTitle - }) - }) -}) diff --git a/novalon-manage-web/src/__tests__/components/LoginLog.test.ts b/novalon-manage-web/src/__tests__/components/LoginLog.test.ts deleted file mode 100644 index 09d6be8..0000000 --- a/novalon-manage-web/src/__tests__/components/LoginLog.test.ts +++ /dev/null @@ -1,195 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' -import { mount } from '@vue/test-utils' -import { createRouter, createMemoryHistory } from 'vue-router' -import LoginLog from '@/views/audit/LoginLog.vue' - -vi.mock('vue-router') -vi.mock('@/utils/request', () => { - const mockRequest = { - get: vi.fn().mockResolvedValue({ - content: [ - { id: 1, username: 'admin', ip: '192.168.1.1', location: '北京', browser: 'Chrome', os: 'Windows', status: '0', loginTime: '2026-01-01T10:00:00' }, - { id: 2, username: 'user', ip: '192.168.1.2', location: '上海', browser: 'Firefox', os: 'MacOS', status: '1', loginTime: '2026-01-02T11:00:00' }, - ], - totalElements: 2, - }), - post: vi.fn(), - put: vi.fn(), - delete: vi.fn(), - } - - return { - default: mockRequest, - } -}) - -describe('LoginLog Component', () => { - let router: any - let wrapper: any - - beforeEach(() => { - router = createRouter({ - history: createMemoryHistory(), - routes: [ - { path: '/', component: { template: '
Home
' } }, - ], - }) - - vi.clearAllMocks() - }) - - afterEach(() => { - if (wrapper) { - wrapper.unmount() - } - }) - - describe('component initialization', () => { - it('should render login log container', () => { - wrapper = mount(LoginLog, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-input': true, - 'el-tag': true, - 'el-icon': true, - 'el-pagination': true, - }, - }, - }) - - expect(wrapper.find('.login-log').exists()).toBe(true) - }) - - it('should initialize with empty search keyword', () => { - wrapper = mount(LoginLog, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-input': true, - 'el-tag': true, - 'el-icon': true, - 'el-pagination': true, - }, - }, - }) - - expect(wrapper.vm.searchKeyword).toBe('') - }) - - it('should initialize with correct pagination defaults', () => { - wrapper = mount(LoginLog, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-input': true, - 'el-tag': true, - 'el-icon': true, - 'el-pagination': true, - }, - }, - }) - - expect(wrapper.vm.pagination.current).toBe(1) - expect(wrapper.vm.pagination.pageSize).toBe(10) - expect(wrapper.vm.pagination.total).toBe(0) - }) - - it('should initialize with correct sort defaults', () => { - wrapper = mount(LoginLog, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-input': true, - 'el-tag': true, - 'el-icon': true, - 'el-pagination': true, - }, - }, - }) - - expect(wrapper.vm.sortInfo.sort).toBe('id') - expect(wrapper.vm.sortInfo.order).toBe('asc') - }) - }) - - describe('sort handling', () => { - beforeEach(() => { - wrapper = mount(LoginLog, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-input': true, - 'el-tag': true, - 'el-icon': true, - 'el-pagination': true, - }, - }, - }) - }) - - it('should update sort info on ascending order', () => { - wrapper.vm.handleSortChange({ prop: 'username', order: 'ascending' }) - expect(wrapper.vm.sortInfo.sort).toBe('username') - expect(wrapper.vm.sortInfo.order).toBe('asc') - }) - - it('should update sort info on descending order', () => { - wrapper.vm.handleSortChange({ prop: 'loginTime', order: 'descending' }) - expect(wrapper.vm.sortInfo.sort).toBe('loginTime') - expect(wrapper.vm.sortInfo.order).toBe('desc') - }) - }) - - describe('pagination handling', () => { - beforeEach(() => { - wrapper = mount(LoginLog, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-input': true, - 'el-tag': true, - 'el-icon': true, - 'el-pagination': true, - }, - }, - }) - }) - - it('should reset to first page on size change', () => { - wrapper.vm.pagination.current = 5 - wrapper.vm.handleSizeChange() - expect(wrapper.vm.pagination.current).toBe(1) - }) - - it('should reset to first page on search', () => { - wrapper.vm.pagination.current = 5 - wrapper.vm.handleSearch() - expect(wrapper.vm.pagination.current).toBe(1) - }) - }) -}) diff --git a/novalon-manage-web/src/__tests__/components/MenuItem.test.ts b/novalon-manage-web/src/__tests__/components/MenuItem.test.ts deleted file mode 100644 index 34a3303..0000000 --- a/novalon-manage-web/src/__tests__/components/MenuItem.test.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { describe, it, expect } from 'vitest' -import { mount } from '@vue/test-utils' -import MenuItem from '@/components/MenuItem.vue' - -describe('MenuItem 组件', () => { - it('应该正确接收菜单项 props', () => { - const menu = { - id: '1', - name: '仪表盘', - path: '/dashboard', - icon: 'Odometer', - sort: 1 - } - - const wrapper = mount(MenuItem, { - props: { menu }, - global: { - stubs: { - 'el-menu-item': { - template: '
' - }, - 'el-sub-menu': { - template: '
' - }, - 'el-icon': { - template: '
' - } - } - } - }) - - expect(wrapper.props('menu')).toEqual(menu) - }) - - it('应该正确处理有子菜单的菜单项', () => { - const menu = { - id: '2', - name: '系统管理', - path: '/system', - icon: 'Setting', - sort: 2, - children: [ - { - id: '3', - name: '用户管理', - path: '/users', - sort: 1 - } - ] - } - - const wrapper = mount(MenuItem, { - props: { menu }, - global: { - stubs: { - 'el-menu-item': { - template: '
' - }, - 'el-sub-menu': { - template: '
' - }, - 'el-icon': { - template: '
' - } - } - } - }) - - expect(wrapper.props('menu')).toEqual(menu) - expect(wrapper.props('menu').children).toHaveLength(1) - }) -}) diff --git a/novalon-manage-web/src/__tests__/components/MenuManagement.test.ts b/novalon-manage-web/src/__tests__/components/MenuManagement.test.ts deleted file mode 100644 index 7e92ff1..0000000 --- a/novalon-manage-web/src/__tests__/components/MenuManagement.test.ts +++ /dev/null @@ -1,279 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' -import { mount } from '@vue/test-utils' -import { createRouter, createMemoryHistory } from 'vue-router' -import MenuManagement from '@/views/system/MenuManagement.vue' - -vi.mock('vue-router') -vi.mock('element-plus', () => ({ - ElMessage: { - success: vi.fn(), - error: vi.fn(), - }, - ElMessageBox: { - confirm: vi.fn(), - }, -})) - -vi.mock('@/api/menu.api', () => ({ - menuApi: { - getAll: vi.fn(), - create: vi.fn(), - update: vi.fn(), - delete: vi.fn(), - }, -})) - -vi.mock('@/utils/request', () => { - const mockRequest = { - get: vi.fn(), - post: vi.fn(), - put: vi.fn(), - delete: vi.fn(), - } - - mockRequest.get.mockResolvedValue([]) - mockRequest.post.mockResolvedValue({}) - mockRequest.put.mockResolvedValue({}) - mockRequest.delete.mockResolvedValue({}) - - return { - default: mockRequest, - } -}) - -describe('MenuManagement Component', () => { - let router: any - let wrapper: any - - beforeEach(() => { - router = createRouter({ - history: createMemoryHistory(), - routes: [ - { path: '/', component: { template: '
Home
' } }, - ], - }) - - vi.clearAllMocks() - }) - - afterEach(() => { - if (wrapper) { - wrapper.unmount() - } - }) - - describe('component initialization', () => { - it('should render menu management container', () => { - wrapper = mount(MenuManagement, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-tag': true, - 'el-dialog': true, - 'el-form': true, - 'el-form-item': true, - 'el-input': true, - 'el-input-number': true, - 'el-select': true, - 'el-option': true, - 'el-icon': true, - }, - }, - }) - - expect(wrapper.find('.menu-management').exists()).toBe(true) - }) - - it('should initialize with empty data source', () => { - wrapper = mount(MenuManagement, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-tag': true, - 'el-dialog': true, - 'el-form': true, - 'el-form-item': true, - 'el-input': true, - 'el-input-number': true, - 'el-select': true, - 'el-option': true, - 'el-icon': true, - }, - }, - }) - - expect(wrapper.vm.dataSource).toBeDefined() - expect(Array.isArray(wrapper.vm.dataSource)).toBe(true) - }) - - it('should initialize with loading state false', () => { - wrapper = mount(MenuManagement, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-tag': true, - 'el-dialog': true, - 'el-form': true, - 'el-form-item': true, - 'el-input': true, - 'el-input-number': true, - 'el-select': true, - 'el-option': true, - 'el-icon': true, - }, - }, - }) - - expect(wrapper.vm.loading).toBeDefined() - expect(typeof wrapper.vm.loading).toBe('boolean') - }) - - it('should initialize with modal visible false', () => { - wrapper = mount(MenuManagement, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-tag': true, - 'el-dialog': true, - 'el-form': true, - 'el-form-item': true, - 'el-input': true, - 'el-input-number': true, - 'el-select': true, - 'el-option': true, - 'el-icon': true, - }, - }, - }) - - expect(wrapper.vm.modalVisible).toBe(false) - }) - - it('should initialize with empty form state', () => { - wrapper = mount(MenuManagement, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-tag': true, - 'el-dialog': true, - 'el-form': true, - 'el-form-item': true, - 'el-input': true, - 'el-input-number': true, - 'el-select': true, - 'el-option': true, - 'el-icon': true, - }, - }, - }) - - expect(wrapper.vm.formState.menuName).toBe('') - expect(wrapper.vm.formState.menuType).toBe('C') - expect(wrapper.vm.formState.perms).toBe('') - expect(wrapper.vm.formState.component).toBe('') - expect(wrapper.vm.formState.orderNum).toBe(0) - expect(wrapper.vm.formState.status).toBe('0') - }) - }) - - describe('add menu functionality', () => { - it('should have handleAdd method', () => { - wrapper = mount(MenuManagement, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-tag': true, - 'el-dialog': true, - 'el-form': true, - 'el-form-item': true, - 'el-input': true, - 'el-input-number': true, - 'el-select': true, - 'el-option': true, - 'el-icon': true, - }, - }, - }) - - expect(typeof wrapper.vm.handleAdd).toBe('function') - }) - }) - - describe('edit menu functionality', () => { - it('should have handleEdit method', () => { - wrapper = mount(MenuManagement, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-tag': true, - 'el-dialog': true, - 'el-form': true, - 'el-form-item': true, - 'el-input': true, - 'el-input-number': true, - 'el-select': true, - 'el-option': true, - 'el-icon': true, - }, - }, - }) - - expect(typeof wrapper.vm.handleEdit).toBe('function') - }) - }) - - describe('delete menu functionality', () => { - it('should have handleDelete method', () => { - wrapper = mount(MenuManagement, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-tag': true, - 'el-dialog': true, - 'el-form': true, - 'el-form-item': true, - 'el-input': true, - 'el-input-number': true, - 'el-select': true, - 'el-option': true, - 'el-icon': true, - }, - }, - }) - - expect(typeof wrapper.vm.handleDelete).toBe('function') - }) - }) -}) diff --git a/novalon-manage-web/src/__tests__/components/NoticeManagement.test.ts b/novalon-manage-web/src/__tests__/components/NoticeManagement.test.ts deleted file mode 100644 index 0984d27..0000000 --- a/novalon-manage-web/src/__tests__/components/NoticeManagement.test.ts +++ /dev/null @@ -1,231 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' -import { mount } from '@vue/test-utils' -import { createRouter, createMemoryHistory } from 'vue-router' -import NoticeManagement from '@/views/notify/NoticeManagement.vue' - -vi.mock('vue-router') -vi.mock('element-plus', () => ({ - ElMessage: { - success: vi.fn(), - error: vi.fn(), - }, - ElMessageBox: { - confirm: vi.fn(), - }, -})) - -vi.mock('@/utils/request', () => { - const mockRequest = { - get: vi.fn().mockResolvedValue([ - { id: 1, noticeTitle: '系统维护通知', noticeType: '1', noticeContent: '系统将于今晚维护', status: '0', createdAt: '2026-01-01T10:00:00' }, - { id: 2, noticeTitle: '新功能上线', noticeType: '2', noticeContent: '新功能已上线', status: '0', createdAt: '2026-01-02T11:00:00' }, - ]), - post: vi.fn().mockResolvedValue({}), - put: vi.fn().mockResolvedValue({}), - delete: vi.fn().mockResolvedValue({}), - } - - return { - default: mockRequest, - } -}) - -describe('NoticeManagement Component', () => { - let router: any - let wrapper: any - - beforeEach(() => { - router = createRouter({ - history: createMemoryHistory(), - routes: [ - { path: '/', component: { template: '
Home
' } }, - ], - }) - - vi.clearAllMocks() - }) - - afterEach(() => { - if (wrapper) { - wrapper.unmount() - } - }) - - describe('component initialization', () => { - it('should render notice management container', () => { - wrapper = mount(NoticeManagement, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-input': true, - 'el-tag': true, - 'el-dialog': true, - 'el-form': true, - 'el-form-item': true, - 'el-select': true, - 'el-option': true, - }, - }, - }) - - expect(wrapper.find('.notice-management').exists()).toBe(true) - }) - - it('should initialize with hidden modal', () => { - wrapper = mount(NoticeManagement, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-input': true, - 'el-tag': true, - 'el-dialog': true, - 'el-form': true, - 'el-form-item': true, - 'el-select': true, - 'el-option': true, - }, - }, - }) - - expect(wrapper.vm.modalVisible).toBe(false) - }) - - it('should initialize with empty form state', () => { - wrapper = mount(NoticeManagement, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-input': true, - 'el-tag': true, - 'el-dialog': true, - 'el-form': true, - 'el-form-item': true, - 'el-select': true, - 'el-option': true, - }, - }, - }) - - expect(wrapper.vm.formState.noticeTitle).toBe('') - expect(wrapper.vm.formState.noticeType).toBe('1') - expect(wrapper.vm.formState.status).toBe('0') - }) - }) - - describe('add notice', () => { - beforeEach(() => { - wrapper = mount(NoticeManagement, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-input': true, - 'el-tag': true, - 'el-dialog': true, - 'el-form': true, - 'el-form-item': true, - 'el-select': true, - 'el-option': true, - }, - }, - }) - }) - - it('should show modal with add title', () => { - wrapper.vm.handleAdd() - expect(wrapper.vm.modalTitle).toBe('新增公告') - expect(wrapper.vm.modalVisible).toBe(true) - }) - - it('should reset form state when adding', () => { - wrapper.vm.formState.noticeTitle = 'existing title' - wrapper.vm.handleAdd() - expect(wrapper.vm.formState.noticeTitle).toBe('') - expect(wrapper.vm.formState.id).toBe(null) - }) - }) - - describe('edit notice', () => { - beforeEach(() => { - wrapper = mount(NoticeManagement, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-input': true, - 'el-tag': true, - 'el-dialog': true, - 'el-form': true, - 'el-form-item': true, - 'el-select': true, - 'el-option': true, - }, - }, - }) - }) - - it('should show modal with edit title', () => { - const notice = { id: 1, noticeTitle: 'Test', noticeType: '1', noticeContent: 'Content', status: '0' } - wrapper.vm.handleEdit(notice) - expect(wrapper.vm.modalTitle).toBe('编辑公告') - expect(wrapper.vm.modalVisible).toBe(true) - }) - - it('should populate form with notice data', () => { - const notice = { id: 1, noticeTitle: 'Test Notice', noticeType: '2', noticeContent: 'Test Content', status: '1' } - wrapper.vm.handleEdit(notice) - expect(wrapper.vm.formState.id).toBe(1) - expect(wrapper.vm.formState.noticeTitle).toBe('Test Notice') - expect(wrapper.vm.formState.noticeType).toBe('2') - }) - }) - - describe('form state', () => { - beforeEach(() => { - wrapper = mount(NoticeManagement, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-input': true, - 'el-tag': true, - 'el-dialog': true, - 'el-form': true, - 'el-form-item': true, - 'el-select': true, - 'el-option': true, - }, - }, - }) - }) - - it('should have default notice type as notification', () => { - expect(wrapper.vm.formState.noticeType).toBe('1') - }) - - it('should have default status as normal', () => { - expect(wrapper.vm.formState.status).toBe('0') - }) - }) -}) diff --git a/novalon-manage-web/src/__tests__/components/OperationLog.test.ts b/novalon-manage-web/src/__tests__/components/OperationLog.test.ts deleted file mode 100644 index 3e18b4a..0000000 --- a/novalon-manage-web/src/__tests__/components/OperationLog.test.ts +++ /dev/null @@ -1,216 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' -import { mount } from '@vue/test-utils' -import { createRouter, createMemoryHistory } from 'vue-router' -import OperationLog from '@/views/audit/OperationLog.vue' - -vi.mock('vue-router') -vi.mock('@/api/operationLog', () => ({ - operationLogApi: { - getPage: vi.fn().mockResolvedValue({ - content: [ - { id: 1, username: 'admin', operation: '用户登录', method: 'POST', params: '{}', status: '0', duration: 100, createdAt: '2026-01-01T10:00:00' }, - { id: 2, username: 'user', operation: '查看用户', method: 'GET', params: '{"id":1}', status: '0', duration: 50, createdAt: '2026-01-02T11:00:00' }, - ], - totalElements: 2, - }), - }, -})) - -describe('OperationLog Component', () => { - let router: any - let wrapper: any - - beforeEach(() => { - router = createRouter({ - history: createMemoryHistory(), - routes: [ - { path: '/', component: { template: '
Home
' } }, - ], - }) - - vi.clearAllMocks() - }) - - afterEach(() => { - if (wrapper) { - wrapper.unmount() - } - }) - - describe('component initialization', () => { - it('should render operation log container', () => { - wrapper = mount(OperationLog, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-input': true, - 'el-tag': true, - 'el-icon': true, - 'el-popover': true, - 'el-pagination': true, - }, - }, - }) - - expect(wrapper.find('.operation-log').exists()).toBe(true) - }) - - it('should initialize with empty search keyword', () => { - wrapper = mount(OperationLog, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-input': true, - 'el-tag': true, - 'el-icon': true, - 'el-popover': true, - 'el-pagination': true, - }, - }, - }) - - expect(wrapper.vm.searchKeyword).toBe('') - }) - - it('should initialize with correct pagination defaults', () => { - wrapper = mount(OperationLog, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-input': true, - 'el-tag': true, - 'el-icon': true, - 'el-popover': true, - 'el-pagination': true, - }, - }, - }) - - expect(wrapper.vm.pagination.current).toBe(1) - expect(wrapper.vm.pagination.pageSize).toBe(10) - expect(wrapper.vm.pagination.total).toBe(0) - }) - }) - - describe('operation icon mapping', () => { - beforeEach(() => { - wrapper = mount(OperationLog, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-input': true, - 'el-tag': true, - 'el-icon': true, - 'el-popover': true, - 'el-pagination': true, - }, - }, - }) - }) - - it('should return User icon for login operations', () => { - const icon = wrapper.vm.getOperationIcon('用户登录') - expect(icon.name).toBe('User') - }) - - it('should return Delete icon for delete operations', () => { - const icon = wrapper.vm.getOperationIcon('删除用户') - expect(icon.name).toBe('Delete') - }) - - it('should return Edit icon for update operations', () => { - const icon = wrapper.vm.getOperationIcon('编辑用户') - expect(icon.name).toBe('Edit') - }) - - it('should return View icon for view operations', () => { - const icon = wrapper.vm.getOperationIcon('查看用户') - expect(icon.name).toBe('View') - }) - - it('should return Plus icon for create operations', () => { - const icon = wrapper.vm.getOperationIcon('新增用户') - expect(icon.name).toBe('Plus') - }) - - it('should return Download icon for download operations', () => { - const icon = wrapper.vm.getOperationIcon('下载文件') - expect(icon.name).toBe('Download') - }) - - it('should return Setting icon for config operations', () => { - const icon = wrapper.vm.getOperationIcon('系统设置') - expect(icon.name).toBe('Setting') - }) - - it('should return Lock icon for password operations', () => { - const icon = wrapper.vm.getOperationIcon('重置密码') - expect(icon.name).toBe('Lock') - }) - - it('should return Document icon for unknown operations', () => { - const icon = wrapper.vm.getOperationIcon('未知操作') - expect(icon.name).toBe('Document') - }) - }) - - describe('params formatting', () => { - beforeEach(() => { - wrapper = mount(OperationLog, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-input': true, - 'el-tag': true, - 'el-icon': true, - 'el-popover': true, - 'el-pagination': true, - }, - }, - }) - }) - - it('should format valid JSON params', () => { - const params = '{"name":"test","id":1}' - const formatted = wrapper.vm.formatParams(params) - expect(formatted).toContain('name') - expect(formatted).toContain('test') - }) - - it('should return empty string for null params', () => { - const formatted = wrapper.vm.formatParams(null) - expect(formatted).toBe('') - }) - - it('should return empty string for undefined params', () => { - const formatted = wrapper.vm.formatParams(undefined) - expect(formatted).toBe('') - }) - - it('should return original string for invalid JSON', () => { - const params = 'not a json' - const formatted = wrapper.vm.formatParams(params) - expect(formatted).toBe('not a json') - }) - }) -}) diff --git a/novalon-manage-web/src/__tests__/components/RoleManagement.test.ts b/novalon-manage-web/src/__tests__/components/RoleManagement.test.ts deleted file mode 100644 index a9787ef..0000000 --- a/novalon-manage-web/src/__tests__/components/RoleManagement.test.ts +++ /dev/null @@ -1,383 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' -import { mount } from '@vue/test-utils' -import { createRouter, createMemoryHistory } from 'vue-router' -import RoleManagement from '@/views/system/RoleManagement.vue' - -vi.mock('vue-router') -vi.mock('element-plus', () => ({ - ElMessage: { - success: vi.fn(), - error: vi.fn(), - }, - ElMessageBox: { - confirm: vi.fn(), - }, -})) - -vi.mock('@/api/role.api', () => ({ - roleApi: { - getPage: vi.fn(), - create: vi.fn(), - update: vi.fn(), - delete: vi.fn(), - getAll: vi.fn(), - }, -})) - -vi.mock('@/api/permission.api', () => ({ - permissionApi: { - getAll: vi.fn(), - }, -})) - -describe('RoleManagement Component', () => { - let router: any - let wrapper: any - - beforeEach(() => { - router = createRouter({ - history: createMemoryHistory(), - routes: [ - { path: '/', component: { template: '
Home
' } }, - ], - }) - - vi.clearAllMocks() - }) - - afterEach(() => { - if (wrapper) { - wrapper.unmount() - } - }) - - describe('component initialization', () => { - it('should render role management container', () => { - wrapper = mount(RoleManagement, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-input': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-tag': true, - 'el-pagination': true, - 'el-dialog': true, - 'el-form': true, - 'el-form-item': true, - 'el-tree': true, - 'el-icon': true, - }, - }, - }) - - expect(wrapper.find('.role-management').exists()).toBe(true) - }) - - it('should initialize with empty search keyword', () => { - wrapper = mount(RoleManagement, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-input': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-tag': true, - 'el-pagination': true, - 'el-dialog': true, - 'el-form': true, - 'el-form-item': true, - 'el-tree': true, - 'el-icon': true, - }, - }, - }) - - expect(wrapper.vm.searchKeyword).toBe('') - }) - - it('should initialize with empty data source', () => { - wrapper = mount(RoleManagement, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-input': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-tag': true, - 'el-pagination': true, - 'el-dialog': true, - 'el-form': true, - 'el-form-item': true, - 'el-tree': true, - 'el-icon': true, - }, - }, - }) - - expect(wrapper.vm.dataSource).toEqual([]) - }) - - it('should initialize with pagination on page 1', () => { - wrapper = mount(RoleManagement, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-input': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-tag': true, - 'el-pagination': true, - 'el-dialog': true, - 'el-form': true, - 'el-form-item': true, - 'el-tree': true, - 'el-icon': true, - }, - }, - }) - - expect(wrapper.vm.pagination.current).toBe(1) - }) - - it('should initialize with modal visible false', () => { - wrapper = mount(RoleManagement, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-input': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-tag': true, - 'el-pagination': true, - 'el-dialog': true, - 'el-form': true, - 'el-form-item': true, - 'el-tree': true, - 'el-icon': true, - }, - }, - }) - - expect(wrapper.vm.modalVisible).toBe(false) - }) - - it('should initialize with empty form state', () => { - wrapper = mount(RoleManagement, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-input': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-tag': true, - 'el-pagination': true, - 'el-dialog': true, - 'el-form': true, - 'el-form-item': true, - 'el-tree': true, - 'el-icon': true, - }, - }, - }) - - expect(wrapper.vm.formState.roleName).toBe('') - expect(wrapper.vm.formState.roleKey).toBe('') - expect(wrapper.vm.formState.roleSort).toBe(1) - expect(wrapper.vm.formState.status).toBe(1) - expect(wrapper.vm.formState.permissions).toEqual([]) - }) - }) - - describe('search functionality', () => { - it('should have handleSearch method', () => { - wrapper = mount(RoleManagement, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-input': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-tag': true, - 'el-pagination': true, - 'el-dialog': true, - 'el-form': true, - 'el-form-item': true, - 'el-tree': true, - 'el-icon': true, - }, - }, - }) - - expect(typeof wrapper.vm.handleSearch).toBe('function') - }) - - it('should update search keyword when input changes', async () => { - wrapper = mount(RoleManagement, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-input': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-tag': true, - 'el-pagination': true, - 'el-dialog': true, - 'el-form': true, - 'el-form-item': true, - 'el-tree': true, - 'el-icon': true, - }, - }, - }) - - wrapper.vm.searchKeyword = 'admin' - await wrapper.vm.$nextTick() - - expect(wrapper.vm.searchKeyword).toBe('admin') - }) - }) - - describe('add role functionality', () => { - it('should have handleAdd method', () => { - wrapper = mount(RoleManagement, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-input': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-tag': true, - 'el-pagination': true, - 'el-dialog': true, - 'el-form': true, - 'el-form-item': true, - 'el-tree': true, - 'el-icon': true, - }, - }, - }) - - expect(typeof wrapper.vm.handleAdd).toBe('function') - }) - }) - - describe('pagination functionality', () => { - it('should have handleTableChange method', () => { - wrapper = mount(RoleManagement, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-input': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-tag': true, - 'el-pagination': true, - 'el-dialog': true, - 'el-form': true, - 'el-form-item': true, - 'el-tree': true, - 'el-icon': true, - }, - }, - }) - - expect(typeof wrapper.vm.handleTableChange).toBe('function') - }) - - it('should have handleSizeChange method', () => { - wrapper = mount(RoleManagement, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-input': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-tag': true, - 'el-pagination': true, - 'el-dialog': true, - 'el-form': true, - 'el-form-item': true, - 'el-tree': true, - 'el-icon': true, - }, - }, - }) - - expect(typeof wrapper.vm.handleSizeChange).toBe('function') - }) - }) - - describe('sort functionality', () => { - it('should have handleSortChange method', () => { - wrapper = mount(RoleManagement, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-input': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-tag': true, - 'el-pagination': true, - 'el-dialog': true, - 'el-form': true, - 'el-form-item': true, - 'el-tree': true, - 'el-icon': true, - }, - }, - }) - - expect(typeof wrapper.vm.handleSortChange).toBe('function') - }) - - it('should initialize with default sort info', () => { - wrapper = mount(RoleManagement, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-input': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-tag': true, - 'el-pagination': true, - 'el-dialog': true, - 'el-form': true, - 'el-form-item': true, - 'el-tree': true, - 'el-icon': true, - }, - }, - }) - - expect(wrapper.vm.sortInfo.sortBy).toBe('id') - expect(wrapper.vm.sortInfo.sortOrder).toBe('asc') - }) - }) -}) diff --git a/novalon-manage-web/src/__tests__/components/UserManagement.test.ts b/novalon-manage-web/src/__tests__/components/UserManagement.test.ts deleted file mode 100644 index edaef04..0000000 --- a/novalon-manage-web/src/__tests__/components/UserManagement.test.ts +++ /dev/null @@ -1,423 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' -import { mount } from '@vue/test-utils' -import { createRouter, createMemoryHistory } from 'vue-router' -import UserManagement from '@/views/system/UserManagement.vue' - -vi.mock('vue-router') -vi.mock('element-plus', () => ({ - ElMessage: { - success: vi.fn(), - error: vi.fn(), - }, - ElMessageBox: { - confirm: vi.fn(), - }, -})) - -vi.mock('@/api/user.api', () => ({ - userApi: { - getPage: vi.fn(), - create: vi.fn(), - update: vi.fn(), - delete: vi.fn(), - assignRoles: vi.fn(), - }, -})) - -vi.mock('@/api/role.api', () => ({ - roleApi: { - getAll: vi.fn(), - }, -})) - -describe('UserManagement Component', () => { - let router: any - let wrapper: any - - beforeEach(() => { - router = createRouter({ - history: createMemoryHistory(), - routes: [ - { path: '/', component: { template: '
Home
' } }, - ], - }) - - vi.clearAllMocks() - }) - - afterEach(() => { - if (wrapper) { - wrapper.unmount() - } - }) - - describe('component initialization', () => { - it('should render user management container', () => { - wrapper = mount(UserManagement, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-input': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-tag': true, - 'el-pagination': true, - 'el-dialog': true, - 'el-form': true, - 'el-form-item': true, - 'el-select': true, - 'el-option': true, - 'el-icon': true, - }, - }, - }) - - expect(wrapper.find('.user-management').exists()).toBe(true) - }) - - it('should initialize with empty search keyword', () => { - wrapper = mount(UserManagement, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-input': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-tag': true, - 'el-pagination': true, - 'el-dialog': true, - 'el-form': true, - 'el-form-item': true, - 'el-select': true, - 'el-option': true, - 'el-icon': true, - }, - }, - }) - - expect(wrapper.vm.searchKeyword).toBe('') - }) - - it('should initialize with loading state false before data fetch', () => { - wrapper = mount(UserManagement, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-input': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-tag': true, - 'el-pagination': true, - 'el-dialog': true, - 'el-form': true, - 'el-form-item': true, - 'el-select': true, - 'el-option': true, - 'el-icon': true, - }, - }, - }) - - expect(wrapper.vm.loading).toBeDefined() - expect(typeof wrapper.vm.loading).toBe('boolean') - }) - - it('should initialize with empty data source', () => { - wrapper = mount(UserManagement, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-input': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-tag': true, - 'el-pagination': true, - 'el-dialog': true, - 'el-form': true, - 'el-form-item': true, - 'el-select': true, - 'el-option': true, - 'el-icon': true, - }, - }, - }) - - expect(wrapper.vm.dataSource).toEqual([]) - }) - - it('should initialize with pagination on page 1', () => { - wrapper = mount(UserManagement, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-input': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-tag': true, - 'el-pagination': true, - 'el-dialog': true, - 'el-form': true, - 'el-form-item': true, - 'el-select': true, - 'el-option': true, - 'el-icon': true, - }, - }, - }) - - expect(wrapper.vm.pagination.current).toBe(1) - }) - - it('should initialize with modal visible false', () => { - wrapper = mount(UserManagement, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-input': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-tag': true, - 'el-pagination': true, - 'el-dialog': true, - 'el-form': true, - 'el-form-item': true, - 'el-select': true, - 'el-option': true, - 'el-icon': true, - }, - }, - }) - - expect(wrapper.vm.modalVisible).toBe(false) - }) - - it('should initialize with empty form state', () => { - wrapper = mount(UserManagement, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-input': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-tag': true, - 'el-pagination': true, - 'el-dialog': true, - 'el-form': true, - 'el-form-item': true, - 'el-select': true, - 'el-option': true, - 'el-icon': true, - }, - }, - }) - - expect(wrapper.vm.formState.username).toBe('') - expect(wrapper.vm.formState.password).toBe('') - expect(wrapper.vm.formState.nickname).toBe('') - expect(wrapper.vm.formState.email).toBe('') - expect(wrapper.vm.formState.phone).toBe('') - expect(wrapper.vm.formState.roles).toEqual([]) - }) - }) - - describe('search functionality', () => { - it('should have handleSearch method', () => { - wrapper = mount(UserManagement, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-input': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-tag': true, - 'el-pagination': true, - 'el-dialog': true, - 'el-form': true, - 'el-form-item': true, - 'el-select': true, - 'el-option': true, - 'el-icon': true, - }, - }, - }) - - expect(typeof wrapper.vm.handleSearch).toBe('function') - }) - - it('should update search keyword when input changes', async () => { - wrapper = mount(UserManagement, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-input': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-tag': true, - 'el-pagination': true, - 'el-dialog': true, - 'el-form': true, - 'el-form-item': true, - 'el-select': true, - 'el-option': true, - 'el-icon': true, - }, - }, - }) - - wrapper.vm.searchKeyword = 'testuser' - await wrapper.vm.$nextTick() - - expect(wrapper.vm.searchKeyword).toBe('testuser') - }) - }) - - describe('add user functionality', () => { - it('should have handleAdd method', () => { - wrapper = mount(UserManagement, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-input': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-tag': true, - 'el-pagination': true, - 'el-dialog': true, - 'el-form': true, - 'el-form-item': true, - 'el-select': true, - 'el-option': true, - 'el-icon': true, - }, - }, - }) - - expect(typeof wrapper.vm.handleAdd).toBe('function') - }) - }) - - describe('pagination functionality', () => { - it('should have handleTableChange method', () => { - wrapper = mount(UserManagement, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-input': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-tag': true, - 'el-pagination': true, - 'el-dialog': true, - 'el-form': true, - 'el-form-item': true, - 'el-select': true, - 'el-option': true, - 'el-icon': true, - }, - }, - }) - - expect(typeof wrapper.vm.handleTableChange).toBe('function') - }) - - it('should have handleSizeChange method', () => { - wrapper = mount(UserManagement, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-input': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-tag': true, - 'el-pagination': true, - 'el-dialog': true, - 'el-form': true, - 'el-form-item': true, - 'el-select': true, - 'el-option': true, - 'el-icon': true, - }, - }, - }) - - expect(typeof wrapper.vm.handleSizeChange).toBe('function') - }) - }) - - describe('sort functionality', () => { - it('should have handleSortChange method', () => { - wrapper = mount(UserManagement, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-input': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-tag': true, - 'el-pagination': true, - 'el-dialog': true, - 'el-form': true, - 'el-form-item': true, - 'el-select': true, - 'el-option': true, - 'el-icon': true, - }, - }, - }) - - expect(typeof wrapper.vm.handleSortChange).toBe('function') - }) - - it('should initialize with default sort info', () => { - wrapper = mount(UserManagement, { - global: { - plugins: [router], - stubs: { - 'el-card': true, - 'el-input': true, - 'el-button': true, - 'el-table': true, - 'el-table-column': true, - 'el-tag': true, - 'el-pagination': true, - 'el-dialog': true, - 'el-form': true, - 'el-form-item': true, - 'el-select': true, - 'el-option': true, - 'el-icon': true, - }, - }, - }) - - expect(wrapper.vm.sortInfo.sortBy).toBe('id') - expect(wrapper.vm.sortInfo.sortOrder).toBe('asc') - }) - }) -}) diff --git a/novalon-manage-web/src/__tests__/config.test.ts b/novalon-manage-web/src/__tests__/config.test.ts deleted file mode 100644 index 59d1009..0000000 --- a/novalon-manage-web/src/__tests__/config.test.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { describe, it, expect } from 'vitest' - -describe('Vitest Configuration Test', () => { - it('should run a simple test', () => { - expect(1 + 1).toBe(2) - }) - - it('should handle async operations', async () => { - const result = await Promise.resolve(42) - expect(result).toBe(42) - }) -}) diff --git a/novalon-manage-web/src/__tests__/directives/permission.test.ts b/novalon-manage-web/src/__tests__/directives/permission.test.ts deleted file mode 100644 index 9dfc020..0000000 --- a/novalon-manage-web/src/__tests__/directives/permission.test.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { describe, it, expect, beforeEach } from 'vitest' -import { mount } from '@vue/test-utils' -import { createPinia, setActivePinia } from 'pinia' -import { permissionDirective } from '@/directives/permission' -import { usePermissionStore } from '@/stores/permission' - -describe('v-permission 指令', () => { - beforeEach(() => { - setActivePinia(createPinia()) - localStorage.clear() - }) - - describe('角色检查', () => { - it('有角色时应该显示元素', () => { - const store = usePermissionStore() - store.setPermissionData({ - roles: ['admin'], - permissions: [], - menus: [] - }) - - const wrapper = mount({ - template: '', - directives: { - permission: permissionDirective - } - }) - - expect(wrapper.find('button').isVisible()).toBe(true) - }) - - it('无角色时应该隐藏元素', () => { - const store = usePermissionStore() - store.setPermissionData({ - roles: ['user'], - permissions: [], - menus: [] - }) - - const wrapper = mount({ - template: '', - directives: { - permission: permissionDirective - } - }) - - expect(wrapper.find('button').isVisible()).toBe(false) - }) - - it('支持数组参数(满足任一即可)', () => { - const store = usePermissionStore() - store.setPermissionData({ - roles: ['user'], - permissions: [], - menus: [] - }) - - const wrapper = mount({ - template: '', - directives: { - permission: permissionDirective - } - }) - - expect(wrapper.find('button').isVisible()).toBe(true) - }) - }) - - describe('权限检查', () => { - it('有权限时应该显示元素', () => { - const store = usePermissionStore() - store.setPermissionData({ - roles: [], - permissions: ['user:delete'], - menus: [] - }) - - const wrapper = mount({ - template: '', - directives: { - permission: permissionDirective - } - }) - - expect(wrapper.find('button').isVisible()).toBe(true) - }) - - it('无权限时应该隐藏元素', () => { - const store = usePermissionStore() - store.setPermissionData({ - roles: [], - permissions: ['user:read'], - menus: [] - }) - - const wrapper = mount({ - template: '', - directives: { - permission: permissionDirective - } - }) - - expect(wrapper.find('button').isVisible()).toBe(false) - }) - - it('支持简写形式(默认权限检查)', () => { - const store = usePermissionStore() - store.setPermissionData({ - roles: [], - permissions: ['user:create'], - menus: [] - }) - - const wrapper = mount({ - template: '', - directives: { - permission: permissionDirective - } - }) - - expect(wrapper.find('button').isVisible()).toBe(true) - }) - }) -}) diff --git a/novalon-manage-web/src/__tests__/fixtures.ts b/novalon-manage-web/src/__tests__/fixtures.ts deleted file mode 100644 index 8265710..0000000 --- a/novalon-manage-web/src/__tests__/fixtures.ts +++ /dev/null @@ -1,88 +0,0 @@ -export const mockUser = { - id: 1, - username: 'testuser', - nickname: 'Test User', - email: 'test@example.com', - phone: '13800138000', - avatar: 'https://example.com/avatar.jpg', - roles: ['admin'], - permissions: ['user:view', 'user:create', 'user:edit', 'user:delete'], -} - -export const mockRole = { - id: 1, - roleName: '测试角色', - roleKey: 'test_role', - roleSort: 1, - status: '1', - remark: '测试角色备注', - createTime: new Date().toISOString(), - updateTime: new Date().toISOString(), -} - -export const mockMenu = { - id: 1, - menuName: '系统管理', - parentId: 0, - orderNum: 1, - menuType: 'M', - component: 'system', - perms: 'system:view', - status: '1', - createTime: new Date().toISOString(), - updateTime: new Date().toISOString(), -} - -export const mockDict = { - id: 1, - dictName: '用户状态', - dictType: 'user_status', - status: '1', - remark: '用户状态字典', - createTime: new Date().toISOString(), - updateTime: new Date().toISOString(), -} - -export const mockConfig = { - id: 1, - configName: '系统名称', - configKey: 'sys.name', - configValue: 'Novalon管理系统', - configType: 'Y', - status: '1', - remark: '系统名称配置', - createTime: new Date().toISOString(), - updateTime: new Date().toISOString(), -} - -export const mockNotice = { - id: 1, - noticeTitle: '系统通知', - noticeType: '1', - noticeContent: '这是一条测试通知', - status: '0', - createTime: new Date().toISOString(), - updateTime: new Date().toISOString(), -} - -export const mockLoginRequest = { - username: 'admin', - password: 'admin123', -} - -export const mockLoginResponse = { - token: 'mock-jwt-token', - user: mockUser, -} - -export const mockApiResponse = (data: T, code = 200, message = 'success') => ({ - code, - message, - data, -}) - -export const mockErrorResponse = (code = 500, message = 'Internal Server Error') => ({ - code, - message, - data: null, -}) diff --git a/novalon-manage-web/src/__tests__/router/permission.guard.test.ts b/novalon-manage-web/src/__tests__/router/permission.guard.test.ts deleted file mode 100644 index a307bcd..0000000 --- a/novalon-manage-web/src/__tests__/router/permission.guard.test.ts +++ /dev/null @@ -1,291 +0,0 @@ -import { describe, it, expect, beforeEach } from 'vitest' -import { createRouter, createWebHistory } from 'vue-router' -import type { RouteRecordRaw } from 'vue-router' - -const mockLocalStorage = { - store: {} as Record, - getItem(key: string) { - return this.store[key] || null - }, - setItem(key: string, value: string) { - this.store[key] = value - }, - removeItem(key: string) { - delete this.store[key] - }, - clear() { - this.store = {} - } -} - -Object.defineProperty(window, 'localStorage', { - value: mockLocalStorage -}) - -const createTestRouter = (routes: RouteRecordRaw[]) => { - return createRouter({ - history: createWebHistory(), - routes - }) -} - -describe('路由守卫权限检查', () => { - beforeEach(() => { - mockLocalStorage.clear() - }) - - describe('基础认证检查', () => { - it('未登录用户访问受保护路由应重定向到登录页', async () => { - const routes: RouteRecordRaw[] = [ - { - path: '/login', - name: 'Login', - component: { template: '
Login
' } - }, - { - path: '/', - component: { template: '
Layout
' }, - meta: { requiresAuth: true }, - children: [ - { - path: 'dashboard', - name: 'Dashboard', - component: { template: '
Dashboard
' } - } - ] - } - ] - - const router = createTestRouter(routes) - - router.beforeEach((to, _from, next) => { - const token = localStorage.getItem('token') - - if (to.meta.requiresAuth && !token) { - next('/login') - } else { - next() - } - }) - - await router.push('/dashboard') - expect(router.currentRoute.value.path).toBe('/login') - }) - - it('已登录用户访问受保护路由应允许通过', async () => { - mockLocalStorage.setItem('token', 'valid-token') - - const routes: RouteRecordRaw[] = [ - { - path: '/login', - name: 'Login', - component: { template: '
Login
' } - }, - { - path: '/', - component: { template: '
Layout
' }, - meta: { requiresAuth: true }, - children: [ - { - path: 'dashboard', - name: 'Dashboard', - component: { template: '
Dashboard
' } - } - ] - } - ] - - const router = createTestRouter(routes) - - router.beforeEach((to, _from, next) => { - const token = localStorage.getItem('token') - - if (to.meta.requiresAuth && !token) { - next('/login') - } else { - next() - } - }) - - await router.push('/dashboard') - expect(router.currentRoute.value.path).toBe('/dashboard') - }) - }) - - describe('角色权限检查', () => { - it('普通用户访问管理员路由应重定向到403页面', async () => { - mockLocalStorage.setItem('token', 'valid-token') - mockLocalStorage.setItem('roles', JSON.stringify(['user'])) - - const routes: RouteRecordRaw[] = [ - { - path: '/login', - name: 'Login', - component: { template: '
Login
' } - }, - { - path: '/403', - name: 'Forbidden', - component: { template: '
403 Forbidden
' } - }, - { - path: '/', - component: { template: '
Layout
' }, - meta: { requiresAuth: true }, - children: [ - { - path: 'dashboard', - name: 'Dashboard', - component: { template: '
Dashboard
' } - }, - { - path: 'users', - name: 'UserManagement', - component: { template: '
UserManagement
' }, - meta: { roles: ['admin'] } - } - ] - } - ] - - const router = createTestRouter(routes) - - router.beforeEach((to, _from, next) => { - const token = localStorage.getItem('token') - const rolesStr = localStorage.getItem('roles') - const userRoles = rolesStr ? JSON.parse(rolesStr) : [] - - if (to.meta.requiresAuth && !token) { - next('/login') - return - } - - if (to.meta.roles && Array.isArray(to.meta.roles)) { - const hasRole = to.meta.roles.some((role: string) => userRoles.includes(role)) - if (!hasRole) { - next('/403') - return - } - } - - next() - }) - - await router.push('/users') - expect(router.currentRoute.value.path).toBe('/403') - }) - - it('管理员用户访问管理员路由应允许通过', async () => { - mockLocalStorage.setItem('token', 'valid-token') - mockLocalStorage.setItem('roles', JSON.stringify(['admin'])) - - const routes: RouteRecordRaw[] = [ - { - path: '/login', - name: 'Login', - component: { template: '
Login
' } - }, - { - path: '/403', - name: 'Forbidden', - component: { template: '
403 Forbidden
' } - }, - { - path: '/', - component: { template: '
Layout
' }, - meta: { requiresAuth: true }, - children: [ - { - path: 'dashboard', - name: 'Dashboard', - component: { template: '
Dashboard
' } - }, - { - path: 'users', - name: 'UserManagement', - component: { template: '
UserManagement
' }, - meta: { roles: ['admin'] } - } - ] - } - ] - - const router = createTestRouter(routes) - - router.beforeEach((to, _from, next) => { - const token = localStorage.getItem('token') - const rolesStr = localStorage.getItem('roles') - const userRoles = rolesStr ? JSON.parse(rolesStr) : [] - - if (to.meta.requiresAuth && !token) { - next('/login') - return - } - - if (to.meta.roles && Array.isArray(to.meta.roles)) { - const hasRole = to.meta.roles.some((role: string) => userRoles.includes(role)) - if (!hasRole) { - next('/403') - return - } - } - - next() - }) - - await router.push('/users') - expect(router.currentRoute.value.path).toBe('/users') - }) - - it('无角色要求的路由所有登录用户都可访问', async () => { - mockLocalStorage.setItem('token', 'valid-token') - mockLocalStorage.setItem('roles', JSON.stringify(['user'])) - - const routes: RouteRecordRaw[] = [ - { - path: '/login', - name: 'Login', - component: { template: '
Login
' } - }, - { - path: '/', - component: { template: '
Layout
' }, - meta: { requiresAuth: true }, - children: [ - { - path: 'dashboard', - name: 'Dashboard', - component: { template: '
Dashboard
' } - } - ] - } - ] - - const router = createTestRouter(routes) - - router.beforeEach((to, _from, next) => { - const token = localStorage.getItem('token') - const rolesStr = localStorage.getItem('roles') - const userRoles = rolesStr ? JSON.parse(rolesStr) : [] - - if (to.meta.requiresAuth && !token) { - next('/login') - return - } - - if (to.meta.roles && Array.isArray(to.meta.roles)) { - const hasRole = to.meta.roles.some((role: string) => userRoles.includes(role)) - if (!hasRole) { - next('/403') - return - } - } - - next() - }) - - await router.push('/dashboard') - expect(router.currentRoute.value.path).toBe('/dashboard') - }) - }) -}) diff --git a/novalon-manage-web/src/__tests__/setup.ts b/novalon-manage-web/src/__tests__/setup.ts deleted file mode 100644 index acb4577..0000000 --- a/novalon-manage-web/src/__tests__/setup.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { vi } from 'vitest' -import { config } from '@vue/test-utils' - -config.global.stubs = { - transition: false, - 'transition-group': false, -} - -Object.defineProperty(window, 'matchMedia', { - writable: true, - value: vi.fn().mockImplementation((query) => ({ - matches: false, - media: query, - onchange: null, - addListener: vi.fn(), - removeListener: vi.fn(), - addEventListener: vi.fn(), - removeEventListener: vi.fn(), - dispatchEvent: vi.fn(), - })), -}) - -const localStorageMock = (() => { - let store: Record = {} - return { - getItem: vi.fn((key: string) => store[key] || null), - setItem: vi.fn((key: string, value: string) => { - store[key] = value - }), - removeItem: vi.fn((key: string) => { - delete store[key] - }), - clear: vi.fn(() => { - store = {} - }), - } -})() - -Object.defineProperty(window, 'localStorage', { - value: localStorageMock, -}) - -const sessionStorageMock = (() => { - let store: Record = {} - return { - getItem: vi.fn((key: string) => store[key] || null), - setItem: vi.fn((key: string, value: string) => { - store[key] = value - }), - removeItem: vi.fn((key: string) => { - delete store[key] - }), - clear: vi.fn(() => { - store = {} - }), - } -})() - -Object.defineProperty(window, 'sessionStorage', { - value: sessionStorageMock, -}) diff --git a/novalon-manage-web/src/__tests__/stores/permission.test.ts b/novalon-manage-web/src/__tests__/stores/permission.test.ts deleted file mode 100644 index ac4f0f5..0000000 --- a/novalon-manage-web/src/__tests__/stores/permission.test.ts +++ /dev/null @@ -1,167 +0,0 @@ -import { describe, it, expect, beforeEach } from 'vitest' -import { setActivePinia, createPinia } from 'pinia' -import { usePermissionStore } from '@/stores/permission' - -describe('Permission Store', () => { - beforeEach(() => { - setActivePinia(createPinia()) - localStorage.clear() - }) - - describe('基础功能', () => { - it('应该正确初始化状态', () => { - const store = usePermissionStore() - - expect(store.roles).toEqual([]) - expect(store.permissions).toEqual([]) - expect(store.menus).toEqual([]) - expect(store.loaded).toBe(false) - }) - - it('应该正确设置权限数据', () => { - const store = usePermissionStore() - - store.setPermissionData({ - roles: ['admin'], - permissions: ['user:read', 'user:delete'], - menus: [ - { - id: '1', - name: '仪表盘', - path: '/dashboard', - icon: 'Odometer', - sort: 1 - } - ] - }) - - expect(store.roles).toEqual(['admin']) - expect(store.permissions).toEqual(['user:read', 'user:delete']) - expect(store.menus).toHaveLength(1) - expect(store.loaded).toBe(true) - }) - - it('应该正确清除权限数据', () => { - const store = usePermissionStore() - - store.setPermissionData({ - roles: ['admin'], - permissions: ['user:read'], - menus: [] - }) - - store.clearPermissionData() - - expect(store.roles).toEqual([]) - expect(store.permissions).toEqual([]) - expect(store.menus).toEqual([]) - expect(store.loaded).toBe(false) - }) - }) - - describe('权限检查方法', () => { - it('应该正确检查单个角色', () => { - const store = usePermissionStore() - store.setPermissionData({ - roles: ['admin', 'user'], - permissions: [], - menus: [] - }) - - expect(store.hasRole('admin')).toBe(true) - expect(store.hasRole('manager')).toBe(false) - }) - - it('应该正确检查多个角色(满足任一即可)', () => { - const store = usePermissionStore() - store.setPermissionData({ - roles: ['user'], - permissions: [], - menus: [] - }) - - expect(store.hasRole(['admin', 'user'])).toBe(true) - expect(store.hasRole(['admin', 'manager'])).toBe(false) - }) - - it('应该正确检查单个权限', () => { - const store = usePermissionStore() - store.setPermissionData({ - roles: [], - permissions: ['user:read', 'user:delete'], - menus: [] - }) - - expect(store.hasPermission('user:read')).toBe(true) - expect(store.hasPermission('user:create')).toBe(false) - }) - - it('应该正确检查多个权限(满足任一即可)', () => { - const store = usePermissionStore() - store.setPermissionData({ - roles: [], - permissions: ['user:read'], - menus: [] - }) - - expect(store.hasPermission(['user:read', 'user:create'])).toBe(true) - expect(store.hasPermission(['user:create', 'user:update'])).toBe(false) - }) - }) - - describe('localStorage 持久化', () => { - it('应该正确保存到 localStorage', () => { - const store = usePermissionStore() - - store.setPermissionData({ - roles: ['admin'], - permissions: ['user:read'], - menus: [ - { - id: '1', - name: '仪表盘', - path: '/dashboard', - sort: 1 - } - ] - }) - - const stored = localStorage.getItem('permission') - expect(stored).toBeTruthy() - - const data = JSON.parse(stored!) - expect(data.roles).toEqual(['admin']) - expect(data.permissions).toEqual(['user:read']) - expect(data.menus).toHaveLength(1) - }) - - it('应该正确从 localStorage 恢复', () => { - localStorage.setItem('permission', JSON.stringify({ - roles: ['user'], - permissions: ['user:read:self'], - menus: [] - })) - - const store = usePermissionStore() - store.initFromStorage() - - expect(store.roles).toEqual(['user']) - expect(store.permissions).toEqual(['user:read:self']) - expect(store.loaded).toBe(true) - }) - - it('清除数据时应该同时清除 localStorage', () => { - const store = usePermissionStore() - - store.setPermissionData({ - roles: ['admin'], - permissions: [], - menus: [] - }) - - store.clearPermissionData() - - expect(localStorage.getItem('permission')).toBeNull() - }) - }) -}) diff --git a/novalon-manage-web/src/__tests__/utils.ts b/novalon-manage-web/src/__tests__/utils.ts deleted file mode 100644 index 74aab99..0000000 --- a/novalon-manage-web/src/__tests__/utils.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { VueWrapper } from '@vue/test-utils' -import { ComponentPublicInstance } from 'vue' - -export interface TestHelpers { - findByText: (text: string) => HTMLElement | null - findByTestId: (testId: string) => HTMLElement | null - clickByText: (text: string) => Promise - clickByTestId: (testId: string) => Promise - fillByTestId: (testId: string, value: string) => Promise -} - -export function createTestHelpers(wrapper: VueWrapper): TestHelpers { - return { - findByText: (text: string) => { - return wrapper.element.textContent?.includes(text) ? wrapper.element : null - }, - findByTestId: (testId: string) => { - return wrapper.element.querySelector(`[data-testid="${testId}"]`) - }, - clickByText: async (text: string) => { - const element = wrapper.element.textContent?.includes(text) ? wrapper.element : null - if (element) { - element.click() - await wrapper.vm.$nextTick() - } - }, - clickByTestId: async (testId: string) => { - const element = wrapper.element.querySelector(`[data-testid="${testId}"]`) - if (element) { - element.click() - await wrapper.vm.$nextTick() - } - }, - fillByTestId: async (testId: string, value: string) => { - const element = wrapper.element.querySelector(`[data-testid="${testId}"]`) as HTMLInputElement - if (element) { - element.value = value - element.dispatchEvent(new Event('input', { bubbles: true })) - await wrapper.vm.$nextTick() - } - }, - } -} - -export function waitFor(condition: () => boolean, timeout = 5000): Promise { - return new Promise((resolve, reject) => { - const startTime = Date.now() - - const check = () => { - if (condition()) { - resolve() - } else if (Date.now() - startTime > timeout) { - reject(new Error(`Timeout waiting for condition`)) - } else { - setTimeout(check, 100) - } - } - - check() - }) -} diff --git a/novalon-manage-web/src/__tests__/utils/errorHandler.test.ts b/novalon-manage-web/src/__tests__/utils/errorHandler.test.ts deleted file mode 100644 index de90eb8..0000000 --- a/novalon-manage-web/src/__tests__/utils/errorHandler.test.ts +++ /dev/null @@ -1,233 +0,0 @@ -import { describe, it, expect, beforeEach, vi } from 'vitest' -import { ElMessage } from 'element-plus' -import { handleApiError, ApiErrorHandler } from '@/utils/errorHandler' - -vi.mock('element-plus', () => ({ - ElMessage: { - error: vi.fn(), - success: vi.fn(), - }, -})) - -describe('errorHandler', () => { - beforeEach(() => { - vi.clearAllMocks() - vi.stubGlobal('localStorage', { - removeItem: vi.fn(), - }) - vi.stubGlobal('window', { - location: { href: '' }, - }) - }) - - describe('handleApiError', () => { - it('should call ApiErrorHandler.handle', () => { - const mockError = { response: { status: 500, data: {} } } - const handleSpy = vi.spyOn(ApiErrorHandler, 'handle') - - handleApiError(mockError) - - expect(handleSpy).toHaveBeenCalledWith(mockError) - }) - }) - - describe('ApiErrorHandler.handle', () => { - it('should handle network error', () => { - const mockError = new Error('Network Error') - const consoleSpy = vi.spyOn(console, 'error') - - ApiErrorHandler.handle(mockError) - - expect(ElMessage.error).toHaveBeenCalledWith('网络连接失败,请检查网络设置') - expect(consoleSpy).toHaveBeenCalledWith('Network Error:', mockError) - }) - - it('should handle 400 Bad Request', () => { - const mockError = { - response: { - status: 400, - data: { message: 'Invalid parameters' }, - }, - } - const consoleSpy = vi.spyOn(console, 'error') - - ApiErrorHandler.handle(mockError) - - expect(ElMessage.error).toHaveBeenCalledWith('Invalid parameters') - expect(consoleSpy).toHaveBeenCalledWith('Bad Request:', mockError.response.data) - }) - - it('should handle 401 Unauthorized', () => { - const mockError = { - response: { - status: 401, - data: { message: 'Unauthorized' }, - }, - } - const consoleSpy = vi.spyOn(console, 'error') - - ApiErrorHandler.handle(mockError) - - expect(ElMessage.error).toHaveBeenCalledWith('登录已过期,请重新登录') - expect(localStorage.removeItem).toHaveBeenCalledWith('token') - expect(window.location.href).toBe('/login') - expect(consoleSpy).toHaveBeenCalledWith('Unauthorized:', mockError.response.data) - }) - - it('should handle 403 Forbidden', () => { - const mockError = { - response: { - status: 403, - data: { message: 'Access denied' }, - }, - } - const consoleSpy = vi.spyOn(console, 'error') - - ApiErrorHandler.handle(mockError) - - expect(ElMessage.error).toHaveBeenCalledWith('没有权限访问该资源') - expect(consoleSpy).toHaveBeenCalledWith('Forbidden:', mockError.response.data) - }) - - it('should handle 404 Not Found', () => { - const mockError = { - response: { - status: 404, - data: { message: 'Resource not found' }, - }, - } - const consoleSpy = vi.spyOn(console, 'error') - - ApiErrorHandler.handle(mockError) - - expect(ElMessage.error).toHaveBeenCalledWith('Resource not found') - expect(consoleSpy).toHaveBeenCalledWith('Not Found:', mockError.response.data) - }) - - it('should handle 409 Conflict', () => { - const mockError = { - response: { - status: 409, - data: { message: 'Resource conflict' }, - }, - } - const consoleSpy = vi.spyOn(console, 'error') - - ApiErrorHandler.handle(mockError) - - expect(ElMessage.error).toHaveBeenCalledWith('Resource conflict') - expect(consoleSpy).toHaveBeenCalledWith('Conflict:', mockError.response.data) - }) - - it('should handle 422 Validation Error with details', () => { - const mockError = { - response: { - status: 422, - data: { - message: 'Validation failed', - details: { - username: 'Username is required', - password: 'Password is too short', - }, - }, - }, - } - const consoleSpy = vi.spyOn(console, 'error') - - ApiErrorHandler.handle(mockError) - - expect(ElMessage.error).toHaveBeenCalledWith('Username is required、Password is too short') - expect(consoleSpy).toHaveBeenCalledWith('Validation Error:', mockError.response.data) - }) - - it('should handle 422 Validation Error without details', () => { - const mockError = { - response: { - status: 422, - data: { message: 'Validation failed' }, - }, - } - const consoleSpy = vi.spyOn(console, 'error') - - ApiErrorHandler.handle(mockError) - - expect(ElMessage.error).toHaveBeenCalledWith('Validation failed') - expect(consoleSpy).toHaveBeenCalledWith('Validation Error:', mockError.response.data) - }) - - it('should handle 500 Internal Server Error', () => { - const mockError = { - response: { - status: 500, - data: { message: 'Server error' }, - }, - } - const consoleSpy = vi.spyOn(console, 'error') - - ApiErrorHandler.handle(mockError) - - expect(ElMessage.error).toHaveBeenCalledWith('服务器内部错误,请稍后重试') - expect(consoleSpy).toHaveBeenCalledWith('Internal Server Error:', mockError.response.data) - }) - - it('should handle 502 Service Unavailable', () => { - const mockError = { - response: { - status: 502, - data: { message: 'Service unavailable' }, - }, - } - const consoleSpy = vi.spyOn(console, 'error') - - ApiErrorHandler.handle(mockError) - - expect(ElMessage.error).toHaveBeenCalledWith('服务暂时不可用,请稍后重试') - expect(consoleSpy).toHaveBeenCalledWith('Service Unavailable:', mockError.response.data) - }) - - it('should handle 503 Service Unavailable', () => { - const mockError = { - response: { - status: 503, - data: { message: 'Service unavailable' }, - }, - } - const consoleSpy = vi.spyOn(console, 'error') - - ApiErrorHandler.handle(mockError) - - expect(ElMessage.error).toHaveBeenCalledWith('服务暂时不可用,请稍后重试') - expect(consoleSpy).toHaveBeenCalledWith('Service Unavailable:', mockError.response.data) - }) - - it('should handle 504 Gateway Timeout', () => { - const mockError = { - response: { - status: 504, - data: { message: 'Gateway timeout' }, - }, - } - const consoleSpy = vi.spyOn(console, 'error') - - ApiErrorHandler.handle(mockError) - - expect(ElMessage.error).toHaveBeenCalledWith('服务暂时不可用,请稍后重试') - expect(consoleSpy).toHaveBeenCalledWith('Service Unavailable:', mockError.response.data) - }) - - it('should handle unknown status code', () => { - const mockError = { - response: { - status: 418, - data: { message: 'I am a teapot' }, - }, - } - const consoleSpy = vi.spyOn(console, 'error') - - ApiErrorHandler.handle(mockError) - - expect(ElMessage.error).toHaveBeenCalledWith('I am a teapot') - expect(consoleSpy).toHaveBeenCalledWith('Unknown Error:', mockError.response.data) - }) - }) -}) diff --git a/novalon-manage-web/src/api/config.ts b/novalon-manage-web/src/api/config.ts new file mode 100644 index 0000000..d9a93de --- /dev/null +++ b/novalon-manage-web/src/api/config.ts @@ -0,0 +1,60 @@ +import request from '@/utils/request' +import type { PageResponse } from './user.api' + +export interface ConfigItem { + id: number + configName: string + configKey: string + configValue: string + configType: string + remark: string + createdAt: string + updatedAt: string +} + +export interface CreateConfigRequest { + configName: string + configKey: string + configValue: string + configType?: string + remark?: string +} + +export interface UpdateConfigRequest { + configName?: string + configKey?: string + configValue?: string + configType?: string + remark?: string +} + +export interface ConfigPageRequest { + page: number + size: number + configName?: string + configKey?: string + configType?: string +} + +export const configApi = { + getAll: () => + request.get('/sys/config'), + + getPage: (params: ConfigPageRequest) => + request.get>('/sys/config/page', { params }), + + getById: (id: number) => + request.get(`/sys/config/${id}`), + + getByKey: (configKey: string) => + request.get(`/sys/config/key/${configKey}`), + + create: (data: CreateConfigRequest) => + request.post('/sys/config', data), + + update: (id: number, data: UpdateConfigRequest) => + request.put(`/sys/config/${id}`, data), + + delete: (id: number) => + request.delete(`/sys/config/${id}`), +} diff --git a/novalon-manage-web/src/api/dict.ts b/novalon-manage-web/src/api/dict.ts new file mode 100644 index 0000000..1cab995 --- /dev/null +++ b/novalon-manage-web/src/api/dict.ts @@ -0,0 +1,96 @@ +import request from '@/utils/request' +import type { PageResponse } from './user.api' + +export interface DictType { + id: number + dictName: string + dictType: string + status: number + remark: string + createdAt: string + updatedAt: string +} + +export interface DictData { + id: number + dictType: string + dictLabel: string + dictValue: string + sort: number + status: number + remark: string + createdAt: string + updatedAt: string +} + +export interface CreateDictTypeRequest { + dictName: string + dictType: string + status?: number + remark?: string +} + +export interface UpdateDictTypeRequest { + dictName?: string + dictType?: string + status?: number + remark?: string +} + +export interface CreateDictDataRequest { + dictType: string + dictLabel: string + dictValue: string + sort?: number + status?: number + remark?: string +} + +export interface UpdateDictDataRequest { + dictType?: string + dictLabel?: string + dictValue?: string + sort?: number + status?: number + remark?: string +} + +export interface DictPageRequest { + page: number + size: number + dictName?: string + dictType?: string + status?: string +} + +export const dictApi = { + getTypes: () => + request.get('/dict/types'), + + getTypeById: (id: number) => + request.get(`/dict/types/${id}`), + + createType: (data: CreateDictTypeRequest) => + request.post('/dict/types', data), + + updateType: (id: number, data: UpdateDictTypeRequest) => + request.put(`/dict/types/${id}`, data), + + deleteType: (id: number) => + request.delete(`/dict/types/${id}`), + + getDataByType: (dictType: string) => + request.get(`/dict/data/type/${dictType}`), + + getDataPage: (params: DictPageRequest & { dictType: string }) => + request.get>('/dict/data/page', { params }), + + createData: (data: CreateDictDataRequest) => + request.post('/dict/data', data), + + updateData: (id: number, data: UpdateDictDataRequest) => + request.put(`/dict/data/${id}`, data), + + deleteData: (id: number) => + request.delete(`/dict/data/${id}`), +} diff --git a/novalon-manage-web/src/api/file.ts b/novalon-manage-web/src/api/file.ts new file mode 100644 index 0000000..a5b30cc --- /dev/null +++ b/novalon-manage-web/src/api/file.ts @@ -0,0 +1,39 @@ +import request from '@/utils/request' +import type { PageResponse } from './user.api' + +export interface FileInfo { + id: number + fileName: string + filePath: string + fileSize: number + fileType: string + mimeType: string + uploadedBy: string + createdAt: string +} + +export interface FilePageRequest { + page: number + size: number + fileName?: string + fileType?: string +} + +export const fileApi = { + getPage: (params: FilePageRequest) => + request.get>('/files/page', { params }), + + upload: (file: File) => { + const formData = new FormData() + formData.append('file', file) + return request.post('/files/upload', formData, { + headers: { 'Content-Type': 'multipart/form-data' }, + }) + }, + + delete: (id: number) => + request.delete(`/files/${id}`), + + download: (id: number) => + request.get(`/files/download/${id}`, { responseType: 'blob' }), +} diff --git a/novalon-manage-web/src/api/loginLog.ts b/novalon-manage-web/src/api/loginLog.ts new file mode 100644 index 0000000..9acdae8 --- /dev/null +++ b/novalon-manage-web/src/api/loginLog.ts @@ -0,0 +1,53 @@ +import request from '@/utils/request' +import type { PageResponse } from './user.api' +import { NoticeStatus } from '@/constants/status' + +export interface Notice { + id: number + title: string + content: string + type: string + status: NoticeStatus + createdBy: string + createdAt: string + updatedAt: string +} + +export interface CreateNoticeRequest { + title: string + content: string + type?: string + status?: NoticeStatus +} + +export interface UpdateNoticeRequest { + title?: string + content?: string + type?: string + status?: NoticeStatus +} + +export interface NoticePageRequest { + page: number + size: number + title?: string + type?: string + status?: string +} + +export const noticeApi = { + getPage: (params: NoticePageRequest) => + request.get>('/notice/page', { params }), + + getById: (id: number) => + request.get(`/notice/${id}`), + + create: (data: CreateNoticeRequest) => + request.post('/notice', data), + + update: (id: number, data: UpdateNoticeRequest) => + request.put(`/notice/${id}`, data), + + delete: (id: number) => + request.delete(`/notice/${id}`), +} diff --git a/novalon-manage-web/src/api/menu.ts b/novalon-manage-web/src/api/menu.ts new file mode 100644 index 0000000..0acd6c0 --- /dev/null +++ b/novalon-manage-web/src/api/menu.ts @@ -0,0 +1,65 @@ +import request from '@/utils/request' +import { MenuStatus } from '@/constants/status' + +export interface MenuItem { + id: number + name: string + path: string + icon: string + component: string + parentId: number + sort: number + type: 'directory' | 'menu' | 'button' + permission: string + status: MenuStatus + visible: boolean + children?: MenuItem[] + createdAt: string + updatedAt: string +} + +export interface CreateMenuRequest { + name: string + path?: string + icon?: string + component?: string + parentId: number + sort: number + type: 'directory' | 'menu' | 'button' + permission?: string + status?: MenuStatus + visible?: boolean +} + +export interface UpdateMenuRequest { + name?: string + path?: string + icon?: string + component?: string + parentId?: number + sort?: number + type?: 'directory' | 'menu' | 'button' + permission?: string + status?: MenuStatus + visible?: boolean +} + +export const menuApi = { + getAll: () => + request.get('/menus'), + + getById: (id: number) => + request.get(`/menus/${id}`), + + getTree: () => + request.get('/menus/tree'), + + create: (data: CreateMenuRequest) => + request.post('/menus', data), + + update: (id: number, data: UpdateMenuRequest) => + request.put(`/menus/${id}`, data), + + delete: (id: number) => + request.delete(`/menus/${id}`), +} diff --git a/novalon-manage-web/src/role-based-tests/roles/__tests__/admin.role.test.ts b/novalon-manage-web/src/role-based-tests/roles/__tests__/admin.role.test.ts deleted file mode 100644 index 7ba38e4..0000000 --- a/novalon-manage-web/src/role-based-tests/roles/__tests__/admin.role.test.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { describe, it, expect } from 'vitest'; -import { AdminRole } from '../admin.role'; - -describe('AdminRole', () => { - it('should have admin credentials', () => { - expect(AdminRole.name).toBe('admin'); - expect(AdminRole.displayName).toBe('超级管理员'); - expect(AdminRole.credentials.username).toBe('admin'); - expect(AdminRole.credentials.password).toBe('Test@123'); - }); - - it('should have all permissions', () => { - expect(AdminRole.permissions).toContain('user:*'); - expect(AdminRole.permissions).toContain('role:*'); - expect(AdminRole.permissions).toContain('menu:*'); - expect(AdminRole.cannotAccess).toHaveLength(0); - }); - - it('should be able to create all resources', () => { - expect(AdminRole.expectedBehaviors.canCreate).toContain('user'); - expect(AdminRole.expectedBehaviors.canCreate).toContain('role'); - expect(AdminRole.expectedBehaviors.canCreate).toContain('menu'); - }); -}); diff --git a/novalon-manage-web/src/role-based-tests/roles/__tests__/base.role.test.ts b/novalon-manage-web/src/role-based-tests/roles/__tests__/base.role.test.ts deleted file mode 100644 index 662286f..0000000 --- a/novalon-manage-web/src/role-based-tests/roles/__tests__/base.role.test.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { describe, it, expect } from 'vitest'; -import type { RoleDefinition } from '../base.role'; - -describe('RoleDefinition', () => { - it('should define required role properties', () => { - const role: RoleDefinition = { - name: 'test', - displayName: '测试角色', - credentials: { - username: 'testuser', - password: 'Test@123' - }, - permissions: ['test:read', 'test:write'], - cannotAccess: ['/admin'], - expectedBehaviors: { - canCreate: ['test'], - canRead: ['test'], - canUpdate: ['test'], - canDelete: [] - } - }; - - expect(role.name).toBe('test'); - expect(role.displayName).toBe('测试角色'); - expect(role.credentials.username).toBe('testuser'); - expect(role.credentials.password).toBe('Test@123'); - expect(role.permissions).toHaveLength(2); - expect(role.cannotAccess).toHaveLength(1); - }); -}); diff --git a/novalon-manage-web/src/role-based-tests/roles/__tests__/role-factory.test.ts b/novalon-manage-web/src/role-based-tests/roles/__tests__/role-factory.test.ts deleted file mode 100644 index d74f2a1..0000000 --- a/novalon-manage-web/src/role-based-tests/roles/__tests__/role-factory.test.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { describe, it, expect } from 'vitest'; -import { RoleFactory } from '../role-factory'; - -describe('RoleFactory', () => { - it('should get admin role', () => { - const role = RoleFactory.getRole('admin'); - expect(role.name).toBe('admin'); - expect(role.credentials.username).toBe('admin'); - }); - - it('should get user role', () => { - const role = RoleFactory.getRole('user'); - expect(role.name).toBe('user'); - expect(role.credentials.username).toBe('normaluser'); - }); - - it('should throw error for unknown role', () => { - expect(() => RoleFactory.getRole('unknown')).toThrow("Role 'unknown' not found"); - }); - - it('should get all roles', () => { - const roles = RoleFactory.getAllRoles(); - expect(roles).toHaveLength(3); - expect(roles.map(r => r.name)).toContain('admin'); - expect(roles.map(r => r.name)).toContain('user'); - expect(roles.map(r => r.name)).toContain('test'); - }); -}); diff --git a/novalon-manage-web/src/role-based-tests/roles/admin.role.ts b/novalon-manage-web/src/role-based-tests/roles/admin.role.ts deleted file mode 100644 index bcf9b5e..0000000 --- a/novalon-manage-web/src/role-based-tests/roles/admin.role.ts +++ /dev/null @@ -1,25 +0,0 @@ -import type { RoleDefinition } from './base.role'; - -export const AdminRole: RoleDefinition = { - name: 'admin', - displayName: '超级管理员', - credentials: { - username: 'admin', - password: 'Test@123' - }, - permissions: [ - 'user:*', - 'role:*', - 'menu:*', - 'config:*', - 'log:read', - 'dict:*' - ], - cannotAccess: [], - expectedBehaviors: { - canCreate: ['user', 'role', 'menu', 'config', 'dict'], - canRead: ['user', 'role', 'menu', 'config', 'dict', 'log'], - canUpdate: ['user', 'role', 'menu', 'config', 'dict'], - canDelete: ['user', 'role', 'menu', 'config', 'dict'] - } -}; diff --git a/novalon-manage-web/src/role-based-tests/roles/base.role.ts b/novalon-manage-web/src/role-based-tests/roles/base.role.ts deleted file mode 100644 index c0c11da..0000000 --- a/novalon-manage-web/src/role-based-tests/roles/base.role.ts +++ /dev/null @@ -1,16 +0,0 @@ -export interface RoleDefinition { - name: string; - displayName: string; - credentials: { - username: string; - password: string; - }; - permissions: string[]; - cannotAccess: string[]; - expectedBehaviors: { - canCreate: string[]; - canRead: string[]; - canUpdate: string[]; - canDelete: string[]; - }; -} diff --git a/novalon-manage-web/src/role-based-tests/roles/role-factory.ts b/novalon-manage-web/src/role-based-tests/roles/role-factory.ts deleted file mode 100644 index 8ab252e..0000000 --- a/novalon-manage-web/src/role-based-tests/roles/role-factory.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { RoleDefinition } from './base.role'; -import { AdminRole } from './admin.role'; -import { UserRole } from './user.role'; -import { TestRole } from './test.role'; - -export class RoleFactory { - private static roles: Map = new Map([ - ['admin', AdminRole], - ['user', UserRole], - ['test', TestRole] - ]); - - static getRole(roleName: string): RoleDefinition { - const role = this.roles.get(roleName); - if (!role) { - throw new Error(`Role '${roleName}' not found`); - } - return role; - } - - static getAllRoles(): RoleDefinition[] { - return Array.from(this.roles.values()); - } -} diff --git a/novalon-manage-web/src/role-based-tests/roles/test.role.ts b/novalon-manage-web/src/role-based-tests/roles/test.role.ts deleted file mode 100644 index 95b5cb6..0000000 --- a/novalon-manage-web/src/role-based-tests/roles/test.role.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { RoleDefinition } from './base.role'; - -export const TestRole: RoleDefinition = { - name: 'test', - displayName: '测试用户', - credentials: { - username: 'e2e_test_user', - password: 'Test@123' - }, - permissions: [ - 'test:read', - 'test:write' - ], - cannotAccess: [ - '/user-management', - '/role-management' - ], - expectedBehaviors: { - canCreate: ['test'], - canRead: ['test'], - canUpdate: ['test'], - canDelete: [] - } -}; diff --git a/novalon-manage-web/src/role-based-tests/roles/user.role.ts b/novalon-manage-web/src/role-based-tests/roles/user.role.ts deleted file mode 100644 index 33920c7..0000000 --- a/novalon-manage-web/src/role-based-tests/roles/user.role.ts +++ /dev/null @@ -1,26 +0,0 @@ -import type { RoleDefinition } from './base.role'; - -export const UserRole: RoleDefinition = { - name: 'user', - displayName: '普通用户', - credentials: { - username: 'normaluser', - password: 'Test@123' - }, - permissions: [ - 'user:read:self', - 'user:update:self' - ], - cannotAccess: [ - '/user-management', - '/role-management', - '/menu-management', - '/system-config' - ], - expectedBehaviors: { - canCreate: [], - canRead: ['self'], - canUpdate: ['self'], - canDelete: [] - } -}; diff --git a/novalon-manage-web/src/role-based-tests/shared/__tests__/permission-helper.test.ts b/novalon-manage-web/src/role-based-tests/shared/__tests__/permission-helper.test.ts deleted file mode 100644 index de0a452..0000000 --- a/novalon-manage-web/src/role-based-tests/shared/__tests__/permission-helper.test.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { describe, it, expect, vi } from 'vitest'; -import { PermissionHelper } from '../permission-helper'; - -// 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/src/role-based-tests/shared/__tests__/role-auth-manager.test.ts b/novalon-manage-web/src/role-based-tests/shared/__tests__/role-auth-manager.test.ts deleted file mode 100644 index 0034ea3..0000000 --- a/novalon-manage-web/src/role-based-tests/shared/__tests__/role-auth-manager.test.ts +++ /dev/null @@ -1,80 +0,0 @@ -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', - text: async () => 'Invalid credentials' - }); - - 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/src/role-based-tests/shared/__tests__/test-data-manager.test.ts b/novalon-manage-web/src/role-based-tests/shared/__tests__/test-data-manager.test.ts deleted file mode 100644 index 647e7fb..0000000 --- a/novalon-manage-web/src/role-based-tests/shared/__tests__/test-data-manager.test.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { describe, it, expect, beforeEach, vi } from 'vitest'; -import { TestDataManager, getTestDataManager } from '../test-data-manager'; - -global.fetch = vi.fn(); - -describe('TestDataManager', () => { - let manager: TestDataManager; - - beforeEach(() => { - manager = TestDataManager.getInstance(); - manager.clearTracking(); - vi.clearAllMocks(); - }); - - it('should be a singleton', () => { - const instance1 = getTestDataManager(); - const instance2 = getTestDataManager(); - expect(instance1).toBe(instance2); - }); - - it('should create user and track it', async () => { - const mockUserId = 'user-123'; - (global.fetch as any).mockResolvedValueOnce({ - ok: true, - json: async () => ({ data: { id: mockUserId } }) - }); - - const userData = { - username: 'testuser', - password: 'Test@123', - email: 'test@example.com', - }; - - const result = await manager.createUser(userData); - - expect(result.id).toBe(mockUserId); - expect(result.type).toBe('user'); - expect(result.data.username).toBe('testuser'); - expect(manager.getCreatedData('user')).toHaveLength(1); - }); - - it('should create role and track it', async () => { - const mockRoleId = 'role-456'; - (global.fetch as any).mockResolvedValueOnce({ - ok: true, - json: async () => ({ data: { id: mockRoleId } }) - }); - - const roleData = { - roleName: '测试角色', - roleKey: 'test_role', - }; - - const result = await manager.createRole(roleData); - - expect(result.id).toBe(mockRoleId); - expect(result.type).toBe('role'); - expect(manager.getCreatedData('role')).toHaveLength(1); - }); - - it('should cleanup created data', async () => { - (global.fetch as any) - .mockResolvedValueOnce({ - ok: true, - json: async () => ({ data: { id: 'user-1' } }) - }) - .mockResolvedValueOnce({ - ok: true, - json: async () => ({ data: { id: 'user-2' } }) - }) - .mockResolvedValueOnce({ ok: true }) - .mockResolvedValueOnce({ ok: true }); - - await manager.createUser({ username: 'user1', password: 'Test@123', email: 'user1@test.com' }); - await manager.createUser({ username: 'user2', password: 'Test@123', email: 'user2@test.com' }); - - expect(manager.getCreatedData('user')).toHaveLength(2); - - await manager.cleanup('user'); - - expect(manager.getCreatedData('user')).toHaveLength(0); - expect(global.fetch).toHaveBeenCalledTimes(4); // 2 creates + 2 deletes - }); - - it('should cleanup all data types when no type specified', async () => { - (global.fetch as any) - .mockResolvedValueOnce({ - ok: true, - json: async () => ({ data: { id: 'user-1' } }) - }) - .mockResolvedValueOnce({ - ok: true, - json: async () => ({ data: { id: 'role-1' } }) - }) - .mockResolvedValueOnce({ ok: true }) - .mockResolvedValueOnce({ ok: true }); - - await manager.createUser({ username: 'user1', password: 'Test@123', email: 'user1@test.com' }); - await manager.createRole({ roleName: '角色1', roleKey: 'role1' }); - - await manager.cleanup(); - - expect(manager.getCreatedData('user')).toHaveLength(0); - expect(manager.getCreatedData('role')).toHaveLength(0); - }); - - it('should throw error on creation failure', async () => { - (global.fetch as any).mockResolvedValueOnce({ - ok: false, - statusText: 'Bad Request' - }); - - await expect( - manager.createUser({ username: 'test', password: 'Test@123', email: 'test@test.com' }) - ).rejects.toThrow('Failed to create user'); - }); -}); diff --git a/novalon-manage-web/src/role-based-tests/shared/auth-helper.ts b/novalon-manage-web/src/role-based-tests/shared/auth-helper.ts deleted file mode 100644 index 4f019d7..0000000 --- a/novalon-manage-web/src/role-based-tests/shared/auth-helper.ts +++ /dev/null @@ -1,76 +0,0 @@ -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/src/role-based-tests/shared/permission-helper.ts b/novalon-manage-web/src/role-based-tests/shared/permission-helper.ts deleted file mode 100644 index 2345ae8..0000000 --- a/novalon-manage-web/src/role-based-tests/shared/permission-helper.ts +++ /dev/null @@ -1,131 +0,0 @@ -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); -} diff --git a/novalon-manage-web/src/role-based-tests/shared/role-auth-manager.ts b/novalon-manage-web/src/role-based-tests/shared/role-auth-manager.ts deleted file mode 100644 index fbe925e..0000000 --- a/novalon-manage-web/src/role-based-tests/shared/role-auth-manager.ts +++ /dev/null @@ -1,59 +0,0 @@ -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; - - 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 - }); - - return token; - } - - private static async authenticateWithBackend(credentials: { username: string; password: string }): Promise { - const path = '/api/auth/login'; - const body = JSON.stringify(credentials); - - const response = await fetch(`${this.API_BASE_URL}${path}`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body, - }); - - if (!response.ok) { - const errorText = await response.text(); - throw new Error(`Authentication failed for user ${credentials.username}: ${response.statusText} - ${errorText}`); - } - - 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); - } -} diff --git a/novalon-manage-web/src/role-based-tests/shared/test-data-manager.ts b/novalon-manage-web/src/role-based-tests/shared/test-data-manager.ts deleted file mode 100644 index 97ab8f8..0000000 --- a/novalon-manage-web/src/role-based-tests/shared/test-data-manager.ts +++ /dev/null @@ -1,150 +0,0 @@ -import { Page } from '@playwright/test'; - -export interface TestData { - id: string; - type: string; - data: Record; - createdAt: Date; -} - -export class TestDataManager { - private static instance: TestDataManager; - private createdData: Map = new Map(); - private _page: Page | null = null; - private static readonly API_BASE_URL = process.env.VITE_API_BASE_URL || 'http://localhost:8084'; - - static getInstance(): TestDataManager { - if (!TestDataManager.instance) { - TestDataManager.instance = new TestDataManager(); - } - return TestDataManager.instance; - } - - setPage(page: Page): void { - this._page = page; - } - - getPage(): Page | null { - return this._page; - } - - async createUser(userData: { - username: string; - password: string; - email: string; - phone?: string; - nickname?: string; - }): Promise { - const response = await fetch(`${TestDataManager.API_BASE_URL}/api/users`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - ...userData, - status: 1, - }), - }); - - if (!response.ok) { - throw new Error(`Failed to create user: ${response.statusText}`); - } - - const result = await response.json(); - const testData: TestData = { - id: result.data?.id || result.id, - type: 'user', - data: userData, - createdAt: new Date(), - }; - - this.trackData('user', testData); - return testData; - } - - async createRole(roleData: { - roleName: string; - roleKey: string; - roleSort?: number; - }): Promise { - const response = await fetch(`${TestDataManager.API_BASE_URL}/api/roles`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - ...roleData, - status: 1, - }), - }); - - if (!response.ok) { - throw new Error(`Failed to create role: ${response.statusText}`); - } - - const result = await response.json(); - const testData: TestData = { - id: result.data?.id || result.id, - type: 'role', - data: roleData, - createdAt: new Date(), - }; - - this.trackData('role', testData); - return testData; - } - - async cleanup(type?: string): Promise { - const typesToClean = type ? [type] : Array.from(this.createdData.keys()); - - for (const dataType of typesToClean) { - const items = this.createdData.get(dataType) || []; - - for (const item of items.reverse()) { - try { - await this.deleteData(item); - } catch (error) { - console.error(`Failed to cleanup ${dataType} ${item.id}:`, error); - } - } - - this.createdData.delete(dataType); - } - } - - private async deleteData(data: TestData): Promise { - const endpoint = this.getEndpoint(data.type); - await fetch(`${TestDataManager.API_BASE_URL}${endpoint}/${data.id}`, { - method: 'DELETE', - }); - } - - private getEndpoint(type: string): string { - const endpoints: Record = { - user: '/api/users', - role: '/api/roles', - menu: '/api/menus', - config: '/api/configs', - }; - return endpoints[type] || `/api/${type}s`; - } - - private trackData(type: string, data: TestData): void { - if (!this.createdData.has(type)) { - this.createdData.set(type, []); - } - this.createdData.get(type)!.push(data); - } - - getCreatedData(type: string): TestData[] { - return this.createdData.get(type) || []; - } - - clearTracking(): void { - this.createdData.clear(); - } -} - -export function getTestDataManager(): TestDataManager { - return TestDataManager.getInstance(); -} diff --git a/novalon-manage-web/src/router/index.ts b/novalon-manage-web/src/router/index.ts deleted file mode 100644 index 13b66b8..0000000 --- a/novalon-manage-web/src/router/index.ts +++ /dev/null @@ -1,158 +0,0 @@ -import { createRouter, createWebHistory } from 'vue-router' -import type { RouteRecordRaw, RouteLocationNormalized } from 'vue-router' - -declare module 'vue-router' { - interface RouteMeta { - requiresAuth?: boolean - roles?: string[] - title?: string - } -} - -const routes: RouteRecordRaw[] = [ - { - path: '/login', - name: 'Login', - component: () => import('@/views/system/Login.vue'), - meta: { title: '登录' } - }, - { - path: '/403', - name: 'Forbidden', - component: () => import('@/views/system/Forbidden.vue'), - meta: { title: '无权限' } - }, - { - path: '/', - component: () => import('@/layouts/DefaultLayout.vue'), - redirect: '/dashboard', - meta: { requiresAuth: true }, - children: [ - { - path: 'dashboard', - name: 'Dashboard', - component: () => import('@/views/system/Dashboard.vue'), - meta: { title: '仪表盘' } - }, - { - path: 'users', - name: 'UserManagement', - component: () => import('@/views/system/UserManagement.vue'), - meta: { title: '用户管理' } - }, - { - path: 'roles', - name: 'RoleManagement', - component: () => import('@/views/system/RoleManagement.vue'), - meta: { title: '角色管理' } - }, - { - path: 'menus', - name: 'MenuManagement', - component: () => import('@/views/system/MenuManagement.vue'), - meta: { title: '菜单管理' } - }, - { - path: 'sys/config', - name: 'ConfigManagement', - component: () => import('@/views/config/ConfigManagement.vue'), - meta: { title: '参数配置' } - }, - { - path: 'dict', - name: 'DictManagement', - component: () => import('@/views/config/DictManagement.vue'), - meta: { title: '字典管理' } - }, - { - path: 'files', - name: 'FileManagement', - component: () => import('@/views/file/FileManagement.vue'), - meta: { title: '文件管理' } - }, - { - path: 'notice', - name: 'NoticeManagement', - component: () => import('@/views/notify/NoticeManagement.vue'), - meta: { title: '通知公告' } - }, - { - path: 'loginlog', - name: 'LoginLog', - component: () => import('@/views/audit/LoginLog.vue'), - meta: { title: '登录日志' } - }, - { - path: 'oplog', - name: 'OperationLog', - component: () => import('@/views/audit/OperationLog.vue'), - meta: { title: '操作日志' } - }, - { - path: 'exceptionlog', - name: 'ExceptionLog', - component: () => import('@/views/audit/ExceptionLog.vue'), - meta: { title: '异常日志' } - } - ] - } -] - -const router = createRouter({ - history: createWebHistory(), - routes -}) - -function checkRoutePermission(route: RouteLocationNormalized, userRoles: string[]): boolean { - if (!route.meta.roles || !Array.isArray(route.meta.roles) || route.meta.roles.length === 0) { - return true - } - return route.meta.roles.some((role: string) => userRoles.includes(role)) -} - -router.beforeEach((to, _from, next) => { - try { - const token = localStorage.getItem('token') - const rolesStr = localStorage.getItem('roles') - let userRoles: string[] = [] - - try { - userRoles = rolesStr ? JSON.parse(rolesStr) : [] - } catch (e) { - console.warn('解析用户角色失败,将使用空数组:', e) - userRoles = [] - } - - if (to.meta.title) { - document.title = `${to.meta.title} - Novalon 管理系统` - } - - if (to.path === '/login') { - if (token) { - next('/') - } else { - next() - } - } else if (to.path === '/403') { - next() - } else { - if (to.meta.requiresAuth !== false && !token) { - next('/login') - return - } - - if (!checkRoutePermission(to, userRoles)) { - console.warn(`用户角色 ${userRoles} 无权访问路由 ${to.path},需要角色: ${to.meta.roles}`) - next('/403') - return - } - - next() - } - } catch (error) { - console.error('路由守卫错误:', error) - next('/login') - } -}) - -export default router diff --git a/novalon-manage-web/src/stores/permission.ts b/novalon-manage-web/src/stores/permission.ts deleted file mode 100644 index 119fff9..0000000 --- a/novalon-manage-web/src/stores/permission.ts +++ /dev/null @@ -1,210 +0,0 @@ -import { defineStore } from 'pinia' -import request from '@/utils/request' - -export interface MenuItem { - id: string - name: string - path: string - icon?: string - parentId?: string - sort: number - children?: MenuItem[] -} - -interface BackendMenuItem { - id: string - menuName: string - parentId: string - orderNum: number - menuType: string - perms?: string - component?: string - status: number - children?: BackendMenuItem[] -} - -function transformMenuData(backendMenus: BackendMenuItem[]): MenuItem[] { - const menuMap = new Map() - const rootMenus: MenuItem[] = [] - - const componentToPathMap: Record = { - 'system/user/index': '/users', - 'system/role/index': '/roles', - 'system/menu/index': '/menus', - 'system/dict/index': '/dict', - 'system/config/index': '/sys/config', - 'system/notice/index': '/notice', - 'system/file/index': '/files', - 'audit/operation/index': '/oplog', - 'audit/login/index': '/loginlog', - 'audit/exception/index': '/exceptionlog', - } - - const filteredMenus = backendMenus.filter(menu => menu.menuType !== 'F') - - filteredMenus.forEach(menu => { - const menuItem: MenuItem = { - id: menu.id, - name: menu.menuName, - path: menu.component ? (componentToPathMap[menu.component] || `/${menu.component.replace('/index', '').replace('system/', '')}`) : '', - icon: getMenuIcon(menu.menuName), - parentId: menu.parentId === '0' ? undefined : menu.parentId, - sort: menu.orderNum - } - menuMap.set(menu.id, menuItem) - }) - - filteredMenus.forEach(menu => { - const menuItem = menuMap.get(menu.id)! - if (menu.parentId === '0') { - rootMenus.push(menuItem) - } else { - const parentMenu = menuMap.get(menu.parentId) - if (parentMenu) { - if (!parentMenu.children) { - parentMenu.children = [] - } - parentMenu.children.push(menuItem) - } - } - }) - - rootMenus.forEach(menu => { - if (menu.children) { - menu.children.sort((a, b) => a.sort - b.sort) - } - }) - - return rootMenus.sort((a, b) => a.sort - b.sort) -} - -function getMenuIcon(menuName: string): string { - const iconMap: Record = { - '系统管理': 'Setting', - '审计日志': 'Document', - '系统监控': 'Monitor', - '用户管理': 'User', - '角色管理': 'UserFilled', - '菜单管理': 'Menu', - '字典管理': 'Collection', - '参数配置': 'Tools', - '通知公告': 'Bell', - '文件管理': 'Folder', - '操作日志': 'Document', - '登录日志': 'Document', - '异常日志': 'Warning' - } - return iconMap[menuName] || 'Document' -} - -interface PermissionState { - roles: string[] - permissions: string[] - menus: MenuItem[] - loaded: boolean -} - -export const usePermissionStore = defineStore('permission', { - state: (): PermissionState => ({ - roles: [], - permissions: [], - menus: [], - loaded: false - }), - - getters: { - hasRole: (state) => (role: string | string[]) => { - if (Array.isArray(role)) { - return role.some(r => state.roles.includes(r)) - } - return state.roles.includes(role) - }, - - hasPermission: (state) => (permission: string | string[]) => { - if (Array.isArray(permission)) { - return permission.some(p => state.permissions.includes(p)) - } - return state.permissions.includes(permission) - } - }, - - actions: { - setPermissionData(data: { - roles: string[] - permissions: string[] - menus: MenuItem[] - }) { - this.roles = data.roles - this.permissions = data.permissions - this.menus = data.menus - this.loaded = true - - this.saveToStorage() - }, - - clearPermissionData() { - this.roles = [] - this.permissions = [] - this.menus = [] - this.loaded = false - - localStorage.removeItem('permission') - }, - - saveToStorage() { - const data = { - roles: this.roles, - permissions: this.permissions, - menus: this.menus - } - localStorage.setItem('permission', JSON.stringify(data)) - }, - - initFromStorage() { - const stored = localStorage.getItem('permission') - if (stored) { - try { - const data = JSON.parse(stored) - this.roles = data.roles || [] - this.permissions = data.permissions || [] - this.menus = data.menus || [] - this.loaded = true - } catch (error) { - console.error('从 localStorage 恢复权限数据失败:', error) - } - } - }, - - async fetchUserMenus() { - try { - const res: any = await request.get('/menus') - - if (res && Array.isArray(res)) { - const transformedMenus = transformMenuData(res) - - const permissions: string[] = [] - const extractPermissions = (menus: BackendMenuItem[]) => { - menus.forEach(menu => { - if (menu.perms) { - permissions.push(menu.perms) - } - if (menu.children && menu.children.length > 0) { - extractPermissions(menu.children) - } - }) - } - extractPermissions(res) - - this.setPermissionData({ - roles: JSON.parse(localStorage.getItem('roles') || '[]'), - permissions: permissions, - menus: transformedMenus - }) - } - } catch (error) { - console.error('获取用户菜单失败:', error) - throw error - } - } - } -}) diff --git a/novalon-manage-web/src/types/menu.ts b/novalon-manage-web/src/types/menu.ts new file mode 100644 index 0000000..1390769 --- /dev/null +++ b/novalon-manage-web/src/types/menu.ts @@ -0,0 +1,26 @@ +export interface MenuItem { + id: number + name: string + path: string + icon: string + component: string + parentId: number + sort: number + type: 'directory' | 'menu' | 'button' + permission: string + status: number + visible: boolean + children?: MenuItem[] + createdAt: string + updatedAt: string +} + +export interface MenuTree { + id: number + name: string + path: string + icon: string + parentId: number + sort: number + children?: MenuTree[] +} diff --git a/novalon-manage-web/src/types/permission.ts b/novalon-manage-web/src/types/permission.ts new file mode 100644 index 0000000..358758c --- /dev/null +++ b/novalon-manage-web/src/types/permission.ts @@ -0,0 +1,20 @@ +export interface PermissionState { + permissions: string[] + roles: string[] + menus: MenuPermission[] +} + +export interface MenuPermission { + id: number + name: string + path: string + icon: string + parentId: number + sort: number + children?: MenuPermission[] +} + +export interface PermissionCheckResult { + hasPermission: boolean + requiredPermission: string | null +} diff --git a/novalon-manage-web/src/types/user.ts b/novalon-manage-web/src/types/user.ts new file mode 100644 index 0000000..8be46ea --- /dev/null +++ b/novalon-manage-web/src/types/user.ts @@ -0,0 +1,24 @@ +export interface AuthState { + token: string | null + userInfo: UserInfo | null + isAuthenticated: boolean +} + +export interface UserInfo { + id: number + username: string + nickname: string + email: string + phone: string + avatar: string + roles: string[] + permissions: string[] +} + +export interface JwtPayload { + sub: string + username: string + roles: string[] + iat: number + exp: number +} diff --git a/novalon-manage-web/src/utils/errorHandler.ts b/novalon-manage-web/src/utils/errorHandler.ts index 9f99b0d..7d2058f 100644 --- a/novalon-manage-web/src/utils/errorHandler.ts +++ b/novalon-manage-web/src/utils/errorHandler.ts @@ -1,4 +1,4 @@ -import { ElMessage } from 'element-plus' +import { message } from 'antd' export interface ApiError { code: string @@ -51,59 +51,59 @@ export class ApiErrorHandler { } private static handleNetworkError(error: any): void { - ElMessage.error('网络连接失败,请检查网络设置') + message.error('网络连接失败,请检查网络设置') console.error('Network Error:', error) } private static handleBadRequest(error: ApiError): void { - ElMessage.error(error.message || '请求参数错误') + message.error(error.message || '请求参数错误') console.error('Bad Request:', error) } private static handleUnauthorized(error: ApiError): void { - ElMessage.error('登录已过期,请重新登录') + message.error('登录已过期,请重新登录') localStorage.removeItem('token') window.location.href = '/login' console.error('Unauthorized:', error) } private static handleForbidden(error: ApiError): void { - ElMessage.error('没有权限访问该资源') + message.error('没有权限访问该资源') console.error('Forbidden:', error) } private static handleNotFound(error: ApiError): void { - ElMessage.error(error.message || '请求的资源不存在') + message.error(error.message || '请求的资源不存在') console.error('Not Found:', error) } private static handleConflict(error: ApiError): void { - ElMessage.error(error.message || '资源冲突,请刷新后重试') + message.error(error.message || '资源冲突,请刷新后重试') console.error('Conflict:', error) } private static handleValidationError(error: ApiError): void { if (error.details) { const messages = Object.values(error.details).join('、') - ElMessage.error(messages) + message.error(messages) } else { - ElMessage.error(error.message || '数据验证失败') + message.error(error.message || '数据验证失败') } console.error('Validation Error:', error) } private static handleInternalServerError(error: ApiError): void { - ElMessage.error('服务器内部错误,请稍后重试') + message.error('服务器内部错误,请稍后重试') console.error('Internal Server Error:', error) } private static handleServiceUnavailable(error: ApiError): void { - ElMessage.error('服务暂时不可用,请稍后重试') + message.error('服务暂时不可用,请稍后重试') console.error('Service Unavailable:', error) } private static handleUnknownError(error: ApiError): void { - ElMessage.error(error.message || '未知错误') + message.error(error.message || '未知错误') console.error('Unknown Error:', error) } } diff --git a/novalon-manage-web/src/views/audit/ExceptionLog.vue b/novalon-manage-web/src/views/audit/ExceptionLog.vue deleted file mode 100644 index a20fbf3..0000000 --- a/novalon-manage-web/src/views/audit/ExceptionLog.vue +++ /dev/null @@ -1,235 +0,0 @@ - - - - - diff --git a/novalon-manage-web/src/views/audit/LoginLog.vue b/novalon-manage-web/src/views/audit/LoginLog.vue deleted file mode 100644 index 1a1f801..0000000 --- a/novalon-manage-web/src/views/audit/LoginLog.vue +++ /dev/null @@ -1,176 +0,0 @@ - - - - - diff --git a/novalon-manage-web/src/views/audit/OperationLog.vue b/novalon-manage-web/src/views/audit/OperationLog.vue deleted file mode 100644 index be79ed0..0000000 --- a/novalon-manage-web/src/views/audit/OperationLog.vue +++ /dev/null @@ -1,311 +0,0 @@ - - - - - diff --git a/novalon-manage-web/src/views/config/ConfigManagement.vue b/novalon-manage-web/src/views/config/ConfigManagement.vue deleted file mode 100644 index 0221931..0000000 --- a/novalon-manage-web/src/views/config/ConfigManagement.vue +++ /dev/null @@ -1,185 +0,0 @@ - - - - - diff --git a/novalon-manage-web/src/views/config/DictManagement.vue b/novalon-manage-web/src/views/config/DictManagement.vue deleted file mode 100644 index 4f922c1..0000000 --- a/novalon-manage-web/src/views/config/DictManagement.vue +++ /dev/null @@ -1,194 +0,0 @@ - - - - - diff --git a/novalon-manage-web/src/views/file/FileManagement.vue b/novalon-manage-web/src/views/file/FileManagement.vue deleted file mode 100644 index 0c29f35..0000000 --- a/novalon-manage-web/src/views/file/FileManagement.vue +++ /dev/null @@ -1,200 +0,0 @@ - - - - - diff --git a/novalon-manage-web/src/views/notify/NoticeManagement.vue b/novalon-manage-web/src/views/notify/NoticeManagement.vue deleted file mode 100644 index e18f77d..0000000 --- a/novalon-manage-web/src/views/notify/NoticeManagement.vue +++ /dev/null @@ -1,222 +0,0 @@ - - - - - diff --git a/novalon-manage-web/src/views/system/Dashboard.vue b/novalon-manage-web/src/views/system/Dashboard.vue deleted file mode 100644 index e0fb54f..0000000 --- a/novalon-manage-web/src/views/system/Dashboard.vue +++ /dev/null @@ -1,373 +0,0 @@ - - - - - diff --git a/novalon-manage-web/src/views/system/Forbidden.vue b/novalon-manage-web/src/views/system/Forbidden.vue deleted file mode 100644 index a6f8785..0000000 --- a/novalon-manage-web/src/views/system/Forbidden.vue +++ /dev/null @@ -1,45 +0,0 @@ - - - - - diff --git a/novalon-manage-web/src/views/system/Login.vue b/novalon-manage-web/src/views/system/Login.vue deleted file mode 100644 index dc6b466..0000000 --- a/novalon-manage-web/src/views/system/Login.vue +++ /dev/null @@ -1,143 +0,0 @@ - - - - - diff --git a/novalon-manage-web/src/views/system/MenuManagement.vue b/novalon-manage-web/src/views/system/MenuManagement.vue deleted file mode 100644 index 4a38e5b..0000000 --- a/novalon-manage-web/src/views/system/MenuManagement.vue +++ /dev/null @@ -1,291 +0,0 @@ - - - - - diff --git a/novalon-manage-web/src/views/system/RoleManagement.vue b/novalon-manage-web/src/views/system/RoleManagement.vue deleted file mode 100644 index 3ffd751..0000000 --- a/novalon-manage-web/src/views/system/RoleManagement.vue +++ /dev/null @@ -1,469 +0,0 @@ - - - - - diff --git a/novalon-manage-web/src/views/system/UserManagement.vue b/novalon-manage-web/src/views/system/UserManagement.vue deleted file mode 100644 index 1dc5865..0000000 --- a/novalon-manage-web/src/views/system/UserManagement.vue +++ /dev/null @@ -1,461 +0,0 @@ - - - - - diff --git a/novalon-manage-web/tsconfig.json b/novalon-manage-web/tsconfig.json index ceacfdc..dfe25fa 100644 --- a/novalon-manage-web/tsconfig.json +++ b/novalon-manage-web/tsconfig.json @@ -3,8 +3,9 @@ "target": "ES2020", "useDefineForClassFields": true, "lib": ["ES2020", "DOM", "DOM.Iterable"], - "types": ["node"], + "types": ["node", "vite/client"], "skipLibCheck": true, + "module": "ESNext", "moduleResolution": "bundler", "allowImportingTsExtensions": true, "resolveJsonModule": true,