feat: 实现登录日志和操作日志的分页查询功能
refactor: 重构日志服务层代码,将分页逻辑移至Repository层 test: 添加日志分页查询的单元测试和组件测试 docs: 更新README文档,记录API响应格式修复过程 chore: 清理无用文件,更新.gitignore配置 build: 添加Jacoco代码覆盖率插件配置 ci: 添加测试环境配置文件application-h2-test.yml style: 统一日志服务代码格式,添加必要的日志输出
This commit is contained in:
@@ -0,0 +1,257 @@
|
||||
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: '<div>Home</div>' } },
|
||||
],
|
||||
})
|
||||
|
||||
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)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,247 @@
|
||||
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: '<div>Home</div>' } },
|
||||
],
|
||||
})
|
||||
|
||||
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)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,195 @@
|
||||
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: '<div>Home</div>' } },
|
||||
],
|
||||
})
|
||||
|
||||
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)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,231 @@
|
||||
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: '<div>Home</div>' } },
|
||||
],
|
||||
})
|
||||
|
||||
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')
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,216 @@
|
||||
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: '<div>Home</div>' } },
|
||||
],
|
||||
})
|
||||
|
||||
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')
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -144,7 +144,11 @@ const handleUpload = async (file: File) => {
|
||||
}
|
||||
|
||||
const handleDownload = (row: any) => {
|
||||
window.open(row.filePath)
|
||||
const downloadUrl = `/api/files/${row.id}/download`
|
||||
const link = document.createElement('a')
|
||||
link.href = downloadUrl
|
||||
link.download = row.fileName
|
||||
link.click()
|
||||
}
|
||||
|
||||
const handleDelete = async (row: any) => {
|
||||
|
||||
Reference in New Issue
Block a user