Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { usePermissionsStore } from "../stores/permissions"
import { AuditLogService } from "../services/audit-log-service"
import { RotationService } from "../services/rotation-service"
import { PageDataService } from "../services/page-data-service"
import AuditLogPage from "../pages/AuditLogPage.vue"
import {
setupTest,
createTestWrapper,
waitForAsync,
createMockPermissionsStore,
createMockUserPermissions,
} from "./test-utils"
import type { UserPermissions } from "../types"

type MockPermissionsStore = Omit<ReturnType<typeof createMockPermissionsStore>, "userPermissions"> & {
hasManagePermission?: boolean
userPermissions: UserPermissions | null
}

// Mock the permissions store and the services the page calls
vi.mock("../stores/permissions")
vi.mock("../services/audit-log-service")
vi.mock("../services/rotation-service")
vi.mock("../services/page-data-service")

// Stub child components that hit the network or rely on named routes
vi.mock("../components/AccessDeniedCard.vue", () => ({
default: {
name: "AccessDeniedCard",
props: ["message", "subtitle"],
template: '<div class="no-access-card"><div>Access Denied</div><div>{{message}}</div></div>',
},
}))
vi.mock("../components/SchedulerNavigation.vue", () => ({
default: { name: "SchedulerNavigation", template: "<div class='scheduler-nav'></div>" },
}))

describe("auditLogPage - Access Control", () => {
let mockPermissionsStore: MockPermissionsStore = {} as never
let router: any = {}

beforeEach(() => {
const { router: testRouter, mockStore } = setupTest()
router = testRouter
mockPermissionsStore = mockStore
vi.mocked(usePermissionsStore).mockReturnValue(mockPermissionsStore as any)
vi.mocked(AuditLogService.getAuditLog).mockResolvedValue({ result: [], success: true, errors: [] })
vi.mocked(AuditLogService.getModifiers).mockResolvedValue({ result: [], success: true, errors: [] })
vi.mocked(AuditLogService.getPersons).mockResolvedValue({ result: [], success: true, errors: [] })
vi.mocked(AuditLogService.getTerms).mockResolvedValue({ result: [], success: true, errors: [] })
vi.mocked(RotationService.getRotations).mockResolvedValue({ result: [], success: true, errors: [] })
vi.mocked(PageDataService.getPageData).mockResolvedValue({ currentGradYear: 2026, availableGradYears: [2026] })
})

it("shows access denied when user lacks the manage permission", () => {
mockPermissionsStore.hasManagePermission = false
mockPermissionsStore.userPermissions = createMockUserPermissions()

const wrapper = createTestWrapper({ component: AuditLogPage, router })

expect(wrapper.find(".no-access-card").exists()).toBeTruthy()
// The results table (and its "Modified By" column) only renders for managers.
expect(wrapper.text()).not.toContain("Modified By")
})

it("shows the audit trail and loads filter options for managers", async () => {
mockPermissionsStore.hasManagePermission = true
mockPermissionsStore.userPermissions = createMockUserPermissions({
permissions: {
hasAdminPermission: false,
hasManagePermission: true,
hasEditClnSchedulesPermission: false,
hasEditOwnSchedulePermission: false,
servicePermissions: {},
editableServiceCount: 0,
},
})

const wrapper = createTestWrapper({ component: AuditLogPage, router })
await waitForAsync()

expect(wrapper.find(".no-access-card").exists()).toBeFalsy()
expect(wrapper.text()).toContain("Modified By")
expect(RotationService.getRotations).toHaveBeenCalledWith()
expect(AuditLogService.getModifiers).toHaveBeenCalledWith()
})

it("initializes the permissions store on mount when not yet loaded", async () => {
mockPermissionsStore.hasManagePermission = true
mockPermissionsStore.userPermissions = null

createTestWrapper({ component: AuditLogPage, router })
await waitForAsync()

expect(mockPermissionsStore.initialize).toHaveBeenCalledWith()
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import AuditLogResultsTable from "../components/AuditLogResultsTable.vue"
import { createTestWrapper } from "./test-utils"
import type { AuditLogEntry } from "../types/audit-types"

function makeEntry(overrides: Partial<AuditLogEntry> = {}): AuditLogEntry {
return {
scheduleAuditId: 1,
area: "Clinicians",
mothraId: "abc123",
personName: "Dr. Example",
action: "Added to rotation",
rotationId: 5,
rotationName: "Cardiology",
weekId: 10,
weekNum: 3,
weekStart: "2026-01-05",
term: "Spring 2026",
modifiedBy: "mgr456",
modifiedByName: "Manager Person",
timeStamp: "2026-01-06T08:30:00",
...overrides,
}
}

function mountTable(entries: AuditLogEntry[], isLoading = false) {
return createTestWrapper({
component: AuditLogResultsTable,
props: { entries, isLoading },
})
}

describe("auditLogResultsTable", () => {
it("renders audit entries", () => {
expect.assertions(4)

const wrapper = mountTable([makeEntry()])

expect(wrapper.text()).toContain("Dr. Example")
expect(wrapper.text()).toContain("Added to rotation")
expect(wrapper.text()).toContain("Cardiology")
expect(wrapper.text()).toContain("Manager Person")
})

it("renders one row per entry", () => {
expect.assertions(2)

const wrapper = mountTable([
makeEntry(),
makeEntry({ scheduleAuditId: 2, personName: "Dr. Second", action: "Removed from rotation" }),
])

expect(wrapper.text()).toContain("Dr. Second")
expect(wrapper.text()).toContain("Removed from rotation")
})

it("marks the table as loading", () => {
expect.assertions(1)

const wrapper = mountTable([], true)

expect(wrapper.findComponent({ name: "QTable" }).props("loading")).toBeTruthy()
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import WeekHistoryContent from "../components/WeekHistoryContent.vue"
import { createTestWrapper } from "./test-utils"
import type { AuditLogEntry } from "../types/audit-types"

function makeEntry(overrides: Partial<AuditLogEntry> = {}): AuditLogEntry {
return {
scheduleAuditId: 1,
area: "Clinicians",
mothraId: "abc123",
personName: "Dr. Example",
action: "Added to rotation",
rotationId: 5,
rotationName: "Cardiology",
weekId: 10,
weekNum: 3,
weekStart: "2026-01-05",
term: "Spring 2026",
modifiedBy: "mgr456",
modifiedByName: "Manager Person",
timeStamp: "2026-01-06T08:30:00",
...overrides,
}
}

function mountContent(props: Record<string, unknown> = {}) {
return createTestWrapper({
component: WeekHistoryContent,
props: {
titleId: "week-history-title-test",
viewMode: "rotation",
weekNumber: 3,
weekDateStart: "2026-01-05",
contextLabel: "Cardiology",
entries: [],
isLoading: false,
error: null,
...props,
},
})
}

describe("weekHistoryContent", () => {
it("shows skeleton rows while loading", () => {
expect.assertions(2)

const wrapper = mountContent({ isLoading: true })

expect(wrapper.findAll(".week-history__skeleton-row")).toHaveLength(3)
expect(wrapper.attributes("aria-busy")).toBe("true")
})

it("shows the error state and emits retry", async () => {
expect.assertions(2)

const wrapper = mountContent({ error: "Failed to load the audit trail" })

expect(wrapper.find("[role='alert']").text()).toContain("Failed to load the audit trail")

await wrapper.find("[role='alert'] button").trigger("click")

expect(wrapper.emitted("retry")).toHaveLength(1)
})

it("shows an empty state when there are no entries", () => {
expect.assertions(1)

const wrapper = mountContent({ entries: [] })

expect(wrapper.text()).toContain("No audit entries for this week.")
})

it("lists entries with the clinician as subject in rotation view", () => {
expect.assertions(3)

const wrapper = mountContent({ viewMode: "rotation", entries: [makeEntry()] })

expect(wrapper.find(".week-history__subject").text()).toBe("Dr. Example")
expect(wrapper.text()).toContain("Added to rotation")
expect(wrapper.text()).toContain("Manager Person")
})

it("lists entries with the rotation as subject in clinician view", () => {
expect.assertions(1)

const wrapper = mountContent({ viewMode: "clinician", entries: [makeEntry()] })

expect(wrapper.find(".week-history__subject").text()).toBe("Cardiology")
})
})
Loading
Loading