Files
shopdb-flask/plugins/endpoints/frontend/views/endpointsList.spec.js
cproudlock 22d06abb70 Show per-PC credentials across the admin interface
- Endpoints (shared key) page: the key is presented as what PCs use until
  they enroll, with enrollment progress, a link to Enrollment, rewritten
  guidance, and rotate/revoke confirmations that say how many PCs are not
  enrolled yet.
- Settings cards describe the server certificate as what PCs actually
  receive, on IIS or Docker; Enrollment has its own icon (the icon set lacked
  the one the Server Certificate card already asked for).
- Server Certificate page checks the certificate the site presents at its
  public address and compares its pin with the certificate file, warning when
  they differ. The host comes from site settings only; the check is a TLS
  handshake, never an HTTP request.
- Fleet list: Credential column and filter (enrolled, pending, re-enroll
  blocked, shared key, not reporting), computed in the list query.
- PC detail: credential state, prefix and dates for fleet readers, state only
  for others, with admin Revoke (optionally blocking re-enrollment) and Allow
  re-enrollment.
- Dashboard: pending enrollment requests, and PCs blocked from re-enrolling
  for more than seven days.
- Runbook: where to find each of these in the app.
2026-09-15 16:07:06 -04:00

100 lines
3.3 KiB
JavaScript

/**
* Fleet list Credential column.
*
* The state is computed by the list route so that filtering by it survives
* pagination; the page must send the filter to the server rather than narrowing
* the rows it already has, and must render the server's value, not derive one.
*/
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { mount, flushPromises } from '@vue/test-utils'
import { ref } from 'vue'
vi.mock('@/api', () => ({
endpointsApi: {
list: vi.fn(),
dashboardSummary: vi.fn(),
dashboardStability: vi.fn(),
enqueueCommand: vi.fn()
},
computersApi: { types: { listAll: vi.fn(() => Promise.resolve([])) } },
locationsApi: { listAll: vi.fn(() => Promise.resolve([])) }
}))
vi.mock('vue-router', () => ({ useRouter: () => ({ push: vi.fn() }) }))
vi.mock('@/stores/auth', () => ({
useAuthStore: () => ({ isAdmin: true, hasPermission: () => true })
}))
vi.mock('@/composables/toast', () => ({
useToast: () => ({ success: vi.fn(), error: vi.fn(), info: vi.fn() })
}))
vi.mock('@/composables/listQuery', () => ({
useListQuery: () => ({
page: ref(1), search: ref(''), setPage: vi.fn(), setSearch: vi.fn()
})
}))
vi.mock('@/components/PaginationBar.vue', () => ({
default: { name: 'PaginationBar', template: '<div />' }
}))
import { endpointsApi } from '@/api'
import EndpointsList from './EndpointsList.vue'
function row(assetid, hostname, credentialstate) {
return {
assetid, hostname, computerid: assetid, status: 'reporting', machines: [],
health: null, credentialstate
}
}
beforeEach(() => {
vi.clearAllMocks()
endpointsApi.dashboardSummary.mockResolvedValue({ data: { data: null } })
endpointsApi.dashboardStability.mockResolvedValue({ data: { data: [] } })
endpointsApi.list.mockResolvedValue({
data: {
data: [
row(1, 'PC-ENROLLED', 'enrolled'),
row(2, 'PC-PENDING', 'pending'),
row(3, 'PC-BLOCKED', 'reenrollblocked'),
row(4, 'PC-SHARED', 'sharedkey'),
row(5, 'PC-SILENT', 'notreporting')
],
meta: { pagination: { totalpages: 1 } }
}
})
})
describe('EndpointsList credential column', () => {
it('renders a chip per server state', async () => {
const wrapper = mount(EndpointsList)
await flushPromises()
const chips = wrapper.findAll('.credential-chip').map(chip => chip.text())
expect(chips).toEqual([
'Enrolled', 'Pending', 'Re-enroll blocked', 'Shared key (not enrolled)',
'Not reporting'
])
expect(wrapper.find('.credential-chip.reenrollblocked').exists()).toBe(true)
expect(wrapper.findAll('th').map(th => th.text())).toContain('Credential')
})
it('sends the credential filter to the server', async () => {
const wrapper = mount(EndpointsList)
await flushPromises()
const select = wrapper.findAll('select.fleet-filter')
.find(element => element.text().includes('All credentials'))
await select.setValue('sharedkey')
await flushPromises()
const params = endpointsApi.list.mock.calls.at(-1)[0]
expect(params.credentialstate).toBe('sharedkey')
expect(params.perpage).toBe(20)
})
it('offers no credential actions on the list', async () => {
const wrapper = mount(EndpointsList)
await flushPromises()
expect(wrapper.text()).not.toMatch(/Revoke|Allow re-enrollment/)
})
})