index.spec.ts 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. import { describe, it, expect, vi, beforeEach } from 'vitest'
  2. import { mount, flushPromises } from '@vue/test-utils'
  3. import { createPinia, setActivePinia } from 'pinia'
  4. import AuditView from '@/views/audit/index.vue'
  5. import { wrapPageResponse, wrapResponse } from '../../../fixtures'
  6. // Mock element-plus
  7. vi.mock('element-plus', () => ({
  8. ElMessage: {
  9. success: vi.fn(),
  10. error: vi.fn(),
  11. info: vi.fn()
  12. }
  13. }))
  14. // Mock audit logs
  15. const mockAuditLogs = [
  16. {
  17. id: '1',
  18. created_at: Math.floor(Date.now() / 1000),
  19. user_id: 'user-1',
  20. username: 'admin',
  21. action: 'create',
  22. resource: 'camera',
  23. resource_id: 'cam-001',
  24. ip_address: '192.168.1.1',
  25. user_agent: 'Mozilla/5.0',
  26. parsedDetails: { message: '创建了摄像头' }
  27. },
  28. {
  29. id: '2',
  30. created_at: Math.floor(Date.now() / 1000) - 3600,
  31. user_id: 'user-1',
  32. username: 'admin',
  33. action: 'update',
  34. resource: 'user',
  35. resource_id: 'user-2',
  36. ip_address: '192.168.1.1',
  37. user_agent: 'Mozilla/5.0',
  38. parsedDetails: { message: '更新了用户信息' }
  39. },
  40. {
  41. id: '3',
  42. created_at: Math.floor(Date.now() / 1000) - 7200,
  43. user_id: 'user-1',
  44. username: 'admin',
  45. action: 'login',
  46. resource: 'auth',
  47. resource_id: null,
  48. ip_address: '192.168.1.1',
  49. user_agent: 'Mozilla/5.0',
  50. parsedDetails: { message: '用户登录' }
  51. }
  52. ]
  53. // Mock audit API
  54. const mockGetAuditLogs = vi.fn()
  55. vi.mock('@/api/audit', () => ({
  56. getAuditLogs: (...args: any[]) => mockGetAuditLogs(...args)
  57. }))
  58. describe('Audit View', () => {
  59. beforeEach(() => {
  60. setActivePinia(createPinia())
  61. vi.clearAllMocks()
  62. mockGetAuditLogs.mockResolvedValue(wrapPageResponse(mockAuditLogs, 3))
  63. })
  64. const mountAudit = () => {
  65. return mount(AuditView, {
  66. global: {
  67. plugins: [createPinia()],
  68. stubs: {
  69. 'el-card': { template: '<div class="el-card"><slot /><slot name="header" /></div>', props: ['shadow'] },
  70. 'el-form': { template: '<form class="el-form"><slot /></form>' },
  71. 'el-form-item': { template: '<div class="el-form-item"><slot /></div>', props: ['label'] },
  72. 'el-select': {
  73. template:
  74. '<select :value="modelValue" @change="$emit(\'update:modelValue\', $event.target.value)"><slot /></select>',
  75. props: ['modelValue', 'placeholder', 'clearable']
  76. },
  77. 'el-option': { template: '<option :value="value">{{ label }}</option>', props: ['label', 'value'] },
  78. 'el-date-picker': {
  79. template:
  80. '<input type="date" :value="modelValue" @input="$emit(\'update:modelValue\', $event.target.value)" />',
  81. props: ['modelValue', 'type', 'rangeSeparator', 'startPlaceholder', 'endPlaceholder', 'valueFormat']
  82. },
  83. 'el-button': {
  84. template: '<button @click="$emit(\'click\')" :disabled="loading"><slot /></button>',
  85. props: ['type', 'icon', 'loading', 'link', 'size']
  86. },
  87. 'el-row': { template: '<div class="el-row"><slot /></div>', props: ['gutter'] },
  88. 'el-col': { template: '<div class="el-col"><slot /></div>', props: ['xs', 'sm'] },
  89. 'el-statistic': {
  90. template: '<div class="el-statistic"><span>{{ title }}</span><span>{{ value }}</span></div>',
  91. props: ['title', 'value']
  92. },
  93. 'el-table': {
  94. template: '<table class="el-table"><slot /></table>',
  95. props: ['data', 'loading', 'border', 'stripe']
  96. },
  97. 'el-table-column': {
  98. template: '<td class="el-table-column"></td>',
  99. props: ['prop', 'label', 'width', 'align', 'showOverflowTooltip', 'fixed', 'minWidth']
  100. },
  101. 'el-tag': { template: '<span class="el-tag" :class="type"><slot /></span>', props: ['type', 'size'] },
  102. 'el-pagination': {
  103. template: '<div class="el-pagination"></div>',
  104. props: ['currentPage', 'pageSize', 'pageSizes', 'total', 'layout']
  105. },
  106. 'el-dialog': {
  107. template: '<div v-if="modelValue" class="el-dialog"><slot /><slot name="footer" /></div>',
  108. props: ['modelValue', 'title', 'width']
  109. },
  110. 'el-descriptions': { template: '<div class="el-descriptions"><slot /></div>', props: ['column', 'border'] },
  111. 'el-descriptions-item': { template: '<div class="el-descriptions-item"><slot /></div>', props: ['label'] },
  112. Search: { template: '<span>Search</span>' },
  113. Refresh: { template: '<span>Refresh</span>' }
  114. }
  115. }
  116. })
  117. }
  118. describe('页面渲染', () => {
  119. it('应该正确渲染审计日志页面', async () => {
  120. const wrapper = mountAudit()
  121. await flushPromises()
  122. expect(wrapper.find('.audit-container').exists()).toBe(true)
  123. })
  124. it('应该显示搜索区域', async () => {
  125. const wrapper = mountAudit()
  126. await flushPromises()
  127. expect(wrapper.find('.search-card').exists()).toBe(true)
  128. })
  129. it('应该显示统计卡片', async () => {
  130. const wrapper = mountAudit()
  131. await flushPromises()
  132. expect(wrapper.find('.stat-row').exists()).toBe(true)
  133. })
  134. it('应该显示数据表格', async () => {
  135. const wrapper = mountAudit()
  136. await flushPromises()
  137. expect(wrapper.find('.el-table').exists()).toBe(true)
  138. })
  139. it('应该显示分页组件', async () => {
  140. const wrapper = mountAudit()
  141. await flushPromises()
  142. expect(wrapper.find('.el-pagination').exists()).toBe(true)
  143. })
  144. })
  145. describe('数据加载', () => {
  146. it('页面加载时应该获取审计日志', async () => {
  147. mountAudit()
  148. await flushPromises()
  149. expect(mockGetAuditLogs).toHaveBeenCalled()
  150. })
  151. it('应该正确传递分页参数', async () => {
  152. mountAudit()
  153. await flushPromises()
  154. expect(mockGetAuditLogs).toHaveBeenCalledWith(
  155. expect.objectContaining({
  156. page: 1,
  157. pageSize: 20
  158. })
  159. )
  160. })
  161. })
  162. describe('搜索和过滤', () => {
  163. it('点击搜索按钮应该触发查询', async () => {
  164. const wrapper = mountAudit()
  165. await flushPromises()
  166. mockGetAuditLogs.mockClear()
  167. const searchBtn = wrapper.findAll('button').find((btn) => btn.text().includes('搜索'))
  168. if (searchBtn) {
  169. await searchBtn.trigger('click')
  170. await flushPromises()
  171. expect(mockGetAuditLogs).toHaveBeenCalled()
  172. }
  173. })
  174. it('点击重置按钮应该清空筛选条件', async () => {
  175. const wrapper = mountAudit()
  176. await flushPromises()
  177. mockGetAuditLogs.mockClear()
  178. const resetBtn = wrapper.findAll('button').find((btn) => btn.text().includes('重置'))
  179. if (resetBtn) {
  180. await resetBtn.trigger('click')
  181. await flushPromises()
  182. expect(mockGetAuditLogs).toHaveBeenCalled()
  183. }
  184. })
  185. })
  186. describe('刷新功能', () => {
  187. it('点击刷新按钮应该重新加载数据', async () => {
  188. const wrapper = mountAudit()
  189. await flushPromises()
  190. mockGetAuditLogs.mockClear()
  191. const refreshBtn = wrapper.findAll('button').find((btn) => btn.text().includes('刷新'))
  192. if (refreshBtn) {
  193. await refreshBtn.trigger('click')
  194. await flushPromises()
  195. expect(mockGetAuditLogs).toHaveBeenCalled()
  196. }
  197. })
  198. })
  199. describe('操作类型标签', () => {
  200. it('创建操作应该显示为 success 类型', async () => {
  201. const wrapper = mountAudit()
  202. await flushPromises()
  203. expect(wrapper.html()).toBeDefined()
  204. })
  205. it('更新操作应该显示为 warning 类型', async () => {
  206. const wrapper = mountAudit()
  207. await flushPromises()
  208. expect(wrapper.html()).toBeDefined()
  209. })
  210. it('删除操作应该显示为 danger 类型', async () => {
  211. const wrapper = mountAudit()
  212. await flushPromises()
  213. expect(wrapper.html()).toBeDefined()
  214. })
  215. })
  216. describe('详情弹窗', () => {
  217. it('点击详情按钮应该打开弹窗', async () => {
  218. const wrapper = mountAudit()
  219. await flushPromises()
  220. const detailBtn = wrapper.findAll('button').find((btn) => btn.text().includes('详情'))
  221. if (detailBtn) {
  222. await detailBtn.trigger('click')
  223. await flushPromises()
  224. expect(wrapper.find('.el-dialog').exists()).toBe(true)
  225. }
  226. })
  227. })
  228. describe('统计计数', () => {
  229. it('应该正确统计创建操作数量', async () => {
  230. const wrapper = mountAudit()
  231. await flushPromises()
  232. const stats = wrapper.findAll('.el-statistic')
  233. expect(stats.length).toBeGreaterThan(0)
  234. })
  235. it('应该正确统计更新操作数量', async () => {
  236. const wrapper = mountAudit()
  237. await flushPromises()
  238. expect(wrapper.html()).toBeDefined()
  239. })
  240. })
  241. describe('错误处理', () => {
  242. it('API 返回错误码应该正确处理', async () => {
  243. mockGetAuditLogs.mockResolvedValue(wrapResponse({ rows: [], total: 0 }, 500, '获取审计日志失败'))
  244. mountAudit()
  245. await flushPromises()
  246. expect(mockGetAuditLogs).toHaveBeenCalled()
  247. })
  248. it('API 返回空数据应该正确处理', async () => {
  249. mockGetAuditLogs.mockResolvedValue(wrapPageResponse([], 0))
  250. mountAudit()
  251. await flushPromises()
  252. expect(mockGetAuditLogs).toHaveBeenCalled()
  253. })
  254. })
  255. describe('时间格式化', () => {
  256. it('应该正确格式化时间戳', async () => {
  257. const wrapper = mountAudit()
  258. await flushPromises()
  259. expect(wrapper.html()).toBeDefined()
  260. })
  261. })
  262. describe('资源类型标签', () => {
  263. it('应该正确显示资源类型', async () => {
  264. const wrapper = mountAudit()
  265. await flushPromises()
  266. expect(wrapper.html()).toBeDefined()
  267. })
  268. })
  269. })