Skip to content
Merged
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
65 changes: 65 additions & 0 deletions e2e/tests/web.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,7 @@ test.describe('Web SPA', () => {
await expect(tabs.getByTestId('admin-tab-user-groups')).toBeVisible()
await expect(tabs.getByTestId('admin-tab-jobs')).toBeVisible()
await expect(tabs.getByTestId('admin-tab-app-info')).toBeVisible()
await expect(tabs.getByTestId('admin-tab-audit-log')).toBeVisible()
})

test('admin users page lists demo users (M18f)', async ({ page }) => {
Expand Down Expand Up @@ -424,4 +425,68 @@ test.describe('Web SPA', () => {
// Submit is disabled until a file is chosen.
await expect(dialog.getByTestId('import-stig-submit')).toBeDisabled()
})

test('admin audit log page renders with table + filters (M19)', async ({
page,
}) => {
await page.goto(`${urls.web}/admin/audit-log`)
await expect(page.getByTestId('admin-audit-log-page')).toBeVisible()
await expect(page.getByTestId('audit-filter-form')).toBeVisible()
await expect(page.getByTestId('audit-table')).toBeVisible({
timeout: 10_000,
})
await expect(page.getByTestId('audit-method-select')).toBeVisible()
await expect(page.getByTestId('audit-path-input')).toBeVisible()
await expect(page.getByTestId('audit-since-input')).toBeVisible()
await expect(page.getByTestId('audit-until-input')).toBeVisible()
await expect(page.getByTestId('audit-limit-input')).toBeVisible()
await expect(page.getByTestId('audit-apply')).toBeVisible()
})

test('admin audit log records a mutation and the row expands (M19)', async ({
page,
}) => {
// Generate a fresh mutation so we know at least one row exists
// (the audit middleware records POSTs synchronously after the
// handler runs, then writes asynchronously with a 5s timeout).
await page.goto(`${urls.web}/collections`)
const collectionName = `e2e-audit-${Date.now()}`
await page.getByTestId('new-collection-button').click()
const cdialog = page.getByTestId('new-collection-dialog')
await cdialog.getByTestId('new-collection-name-input').fill(collectionName)
await cdialog.getByTestId('new-collection-submit').click()
await expect(page).toHaveURL(/\/collections\/\d+$/, { timeout: 10_000 })

await page.goto(`${urls.web}/admin/audit-log`)
await expect(page.getByTestId('admin-audit-log-page')).toBeVisible()

// Filter to POST /api/collections to narrow the result set, then
// expand the first row and assert the payload viewer is rendered.
await page.getByTestId('audit-method-select').selectOption('POST')
await page.getByTestId('audit-path-input').fill('/api/collections')
await page.getByTestId('audit-apply').click()

const table = page.getByTestId('audit-table')
// Give the audit row a moment to land (async writer).
await expect(async () => {
await page.getByTestId('audit-refresh').click()
const rows = await table.locator('tbody tr').count()
// Each audit row renders one base <tr>; expanded rows add a 2nd.
// We expect at least one populated data row (not the empty-state
// placeholder, which spans 7 cols).
expect(rows).toBeGreaterThanOrEqual(1)
const empty = await table
.getByText('No rows match the current filters.')
.count()
expect(empty).toBe(0)
}).toPass({ timeout: 15_000 })

const firstToggle = page
.locator('[data-testid^="audit-row-toggle-"]')
.first()
await firstToggle.click()
await expect(
page.locator('[data-testid^="audit-row-payload-"]').first(),
).toBeVisible({ timeout: 5_000 })
})
})
7 changes: 7 additions & 0 deletions web/src/app/layout/nav-items.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
Briefcase,
ClipboardList,
Layers,
ScrollText,
ShieldCheck,
Users,
UsersRound,
Expand Down Expand Up @@ -64,4 +65,10 @@ export const ADMIN_NAV: readonly NavItem[] = [
icon: ClipboardList,
scope: 'stig-manager:op:read',
},
{
to: '/admin/audit-log',
label: 'Audit log',
icon: ScrollText,
scope: 'stig-manager:op:read',
},
] as const
8 changes: 7 additions & 1 deletion web/src/app/pages/admin/admin-layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// per-page content. Visibility of each link respects the same scope
// rules as the sidebar so we don't show links the user can't follow.

import { Briefcase, ClipboardList, Users, UsersRound } from 'lucide-react'
import { Briefcase, ClipboardList, ScrollText, Users, UsersRound } from 'lucide-react'
import { NavLink, Outlet } from 'react-router-dom'

import { useAuth } from '@/lib/auth/auth-context'
Expand Down Expand Up @@ -41,6 +41,12 @@ const ADMIN_TABS: readonly AdminNavItem[] = [
icon: ClipboardList,
scope: 'stig-manager:op:read',
},
{
to: '/admin/audit-log',
label: 'Audit log',
icon: ScrollText,
scope: 'stig-manager:op:read',
},
]

export function AdminLayout() {
Expand Down
Loading
Loading