Files
ctms/frontend/src/views/admin/AuditLogs.test.ts
T
2026-06-10 10:20:04 +08:00

125 lines
6.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, expect, it } from "vitest";
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
const readAuditLogsView = () => readFileSync(resolve(__dirname, "./AuditLogs.vue"), "utf8");
describe("audit logs access", () => {
it("loads audit logs from the selected project context", () => {
const source = readAuditLogsView();
expect(source).toContain("const selectedStudy = ref<Study | null>(null)");
expect(source).toContain("const selectedStudyId = computed({");
expect(source).toContain("get: () => selectedStudy.value?.id || \"\"");
expect(source).toContain("set: (studyId: string) => {");
expect(source).toContain("await fetchAuditLogs(selectedStudyId.value, params)");
expect(source).toContain("await fetchApiEndpointPermissions(selectedStudyId.value)");
expect(source).toContain("await createAuditEvent(selectedStudyId.value,");
expect(source).not.toContain("await fetchAuditLogs(study.currentStudy.id, params)");
});
it("limits non-admin project choices to PM-owned projects", () => {
const source = readAuditLogsView();
expect(source).toContain('const availableItems = isAdmin.value ? items : items.filter((item) => item.role_in_study === "PM");');
expect(source).toContain("studies.value = availableItems;");
expect(source).toContain("availableItems.find((item) => item.id === queryProjectId)");
expect(source).toContain("availableItems.find((item) => item.id === study.currentStudy?.id)");
expect(source).toContain("availableItems[0]");
});
it("allows only admins or authorized PMs to use audit export", () => {
const source = readAuditLogsView();
expect(source).toContain("const canAccessAuditLogs = computed");
expect(source).toContain("isSystemAdmin");
expect(source).toContain("getProjectRole");
expect(source).toContain("study.currentStudyRole");
expect(source).toContain('projectRole.value === "PM"');
expect(source).toContain("isApiPermissionAllowed");
expect(source).toContain('permissionMatrix.value?.[projectRole.value]?.["audit_logs:read"]');
expect(source).toContain('permissionMatrix.value?.[projectRole.value]?.["audit_logs:export"]');
expect(source).toContain("if (!canProjectExport.value) return;");
expect(source).toContain('action: "AUDIT_EXPORT_PROJECT"');
expect(source).not.toContain('command="system"');
expect(source).not.toMatch(/auth\.user\?\.role/);
expect(source).not.toContain('!== "ADMIN"');
});
it("exports all matching audit log pages instead of a fixed first page", () => {
const source = readAuditLogsView();
expect(source).toContain("const fetchAuditLogPages = async");
expect(source).toContain("while (true)");
expect(source).toContain("if (items.length < batchLimit) break;");
expect(source).not.toContain("limit: 2000");
});
it("keeps the audit table compact by removing the duplicate content column", () => {
const source = readAuditLogsView();
expect(source).not.toContain('prop="actionText"');
expect(source).not.toContain("TEXT.modules.adminAuditLogs.columns.action");
expect(source).toContain("TEXT.modules.adminAuditLogs.columns.target");
expect(source).toContain("TEXT.modules.adminAuditLogs.columns.diff");
});
it("shows actor names without avatar icons in the audit table", () => {
const source = readAuditLogsView();
expect(source).toContain('<span class="actor-name">{{ scope.row.actorName }}</span>');
expect(source).not.toContain("actor-avatar");
expect(source).not.toContain("getInitials(scope.row.actorName)");
});
it("lets target and detail columns absorb remaining table width", () => {
const source = readAuditLogsView();
expect(source).toContain('<el-table-column :label="TEXT.modules.adminAuditLogs.columns.target" min-width="240">');
expect(source).toContain('<el-table-column :label="TEXT.modules.adminAuditLogs.columns.diff" min-width="360">');
expect(source).not.toContain(':label="TEXT.modules.adminAuditLogs.columns.target" width="240"');
expect(source).not.toContain(':label="TEXT.modules.adminAuditLogs.columns.diff" width="360"');
});
it("parses diff lines by the last field separator before the arrow", () => {
const source = readAuditLogsView();
expect(source).toMatch(/line\.match\(\/\^\(\.\+\)\[:\]\\s\*\(\.\*\?\)\\s\*->\\s\*\(\.\+\)\$\/\)/);
expect(source).not.toMatch(/line\.match\(\/\^\(\.\+\?\)\(\.\+\?\)\\s\*->\\s\*\(\.\+\)\$\/\)/);
});
it("renders detail object and action as two cards in one row", () => {
const source = readAuditLogsView();
expect(source).toContain('<span class="meta-label">对象</span>');
expect(source).toContain('<span class="meta-label">动作</span>');
expect(source).not.toContain("detail-meta-full");
expect(source).not.toContain("meta-card-full");
});
it("groups setup diff detail rows by business item path", () => {
const source = readAuditLogsView();
expect(source).toContain("const buildDetailDiffGroups = (lines: any[]): DetailDiffGroup[] => {");
expect(source).toContain('const groupedIndex = new Map<string, DetailDiffGroup>();');
expect(source).toContain('title: parts.slice(0, -1).join(" / "),');
expect(source).toContain("field: parts[parts.length - 1],");
expect(source).toContain('class="diff-group-title"');
expect(source).toContain("{{ selectedDiffGroups.length }} 项");
});
it("does not expose legacy startup ethics business labels", () => {
const auditSource = readFileSync(resolve(__dirname, "../../audit/index.ts"), "utf8");
const overviewSource = readFileSync(resolve(__dirname, "../ia/project-overview/overview.adapter.ts"), "utf8");
const combined = `${auditSource}\n${overviewSource}`;
expect(combined).not.toContain("可行性评估");
expect(combined).not.toContain("伦理审批");
expect(combined).not.toContain("立项可行性");
expect(combined).not.toContain("立项伦理");
expect(auditSource).toContain('startup_feasibility: "立项记录"');
expect(auditSource).toContain('startup_ethics: "伦理记录"');
expect(overviewSource).toContain('label: "伦理记录"');
});
});