Files
shopdb-flask/plugins/endpoints/frontend/views/agentKeySettings.spec.js
cproudlock 1c2ba677c3 Let key-less PCs ask to enroll, and let an admin retry a trust-blocked PC
Found in the Windows VM re-test.

- A PC installed without the shared key got 401 from POST
  /api/endpoints/enroll while the shared key was allowed, so no request was
  recorded and no admin could approve it. Enroll now never refuses: a valid
  shared key only decides auto-issue on a clean match; no key or an invalid
  key creates or updates the one pending request for that hostname and
  serial. An approved request still issues only to its own nonce.
- A PC blocked with "server certificate not trusted by this PC" could never be
  queued again after trust was fixed, because the blocker comes from its last
  failed upgrade. Naming the PC in a wave now queues it when trust is its only
  blocker, and the response lists it under "retried"; "next n" still skips
  it. The Migration table lets such a PC be selected.
- counts.pending on GET /enrollment counts pending requests, including ones
  for unknown hostnames; counts.pendingcomputers keeps the per-computer figure.
2026-09-15 16:07:06 -04:00

134 lines
5.3 KiB
JavaScript

/**
* Endpoints (shared key) settings page.
*
* The shared key only matters to PCs that have not enrolled, so the page must
* say how far enrollment has got, and the regenerate and revoke confirms must
* name how many PCs the change actually reaches.
*/
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { mount, flushPromises, RouterLinkStub } from '@vue/test-utils'
vi.mock('@/api', () => ({
default: { get: vi.fn(), post: vi.fn(), put: vi.fn(), delete: vi.fn() }
}))
vi.mock('vue-router', () => ({ useRouter: () => ({ push: vi.fn() }) }))
import api from '@/api'
import AgentKeySettings from './AgentKeySettings.vue'
const KEY = { tokenprefix: 'abcd', createddate: null, lastusedat: null }
function enrollmentPayload(settings = { sharedkeyallowed: true, autoenroll: true }) {
return {
data: {
data: {
settings,
counts: { enrolled: 30, notenrolled: 8, pendingcomputers: 2, pending: 3 },
computers: [],
requests: [
{ requestid: 1, status: 'pending', reason: 'serial mismatch' },
{ requestid: 2, status: 'pending', reason: 'awaiting approval' },
{ requestid: 3, status: 'pending', reason: 'reenroll blocked' },
{ requestid: 4, status: 'issued', reason: null }
]
}
}
}
}
async function render(payload = enrollmentPayload()) {
api.get.mockImplementation((path) => {
if (path === '/endpoints/agentkey') {
return Promise.resolve({ data: { data: { key: KEY, envkeyfallback: false } } })
}
if (path === '/endpoints/enrollment') return Promise.resolve(payload)
return Promise.resolve({ data: { data: {} } })
})
api.post.mockResolvedValue({ data: { data: { key: KEY } } })
api.delete.mockResolvedValue({ data: { data: { key: null } } })
const wrapper = mount(AgentKeySettings, { global: { stubs: { RouterLink: RouterLinkStub } } })
await flushPromises()
return wrapper
}
describe('AgentKeySettings shared key section', () => {
beforeEach(() => {
api.get.mockReset()
api.post.mockReset()
api.delete.mockReset()
})
afterEach(() => {
vi.unstubAllGlobals()
})
it('titles the section for the per-PC model', async () => {
const wrapper = await render()
expect(wrapper.text()).toContain('Shared key (used until PCs enroll)')
expect(wrapper.text()).not.toContain('Key management')
expect(wrapper.text()).toMatch(/Enrolled PCs ignore it/)
expect(wrapper.text()).toMatch(/At cutover/)
})
it('shows enrollment progress from one small enrollment read', async () => {
const wrapper = await render()
expect(api.get).toHaveBeenCalledWith('/endpoints/enrollment', { params: { perpage: 1 } })
const summary = wrapper.find('.enrollment-summary').text()
expect(summary).toContain('30 of 40 PCs enrolled')
expect(summary).toContain('10 still on the shared key')
// A blocked PC asking again is not a request anyone has to decide.
expect(summary).toContain('2 pending enrollment requests')
const link = wrapper.findComponent(RouterLinkStub)
expect(link.props('to')).toEqual({ name: 'endpoints-enrollment-settings' })
})
it('regenerate confirm names the PCs that need the new key', async () => {
const confirmSpy = vi.fn(() => true)
vi.stubGlobal('confirm', confirmSpy)
const wrapper = await render()
const button = wrapper.findAll('button').find(b => b.text() === 'Regenerate key')
await button.trigger('click')
await flushPromises()
expect(confirmSpy.mock.calls[0][0]).toMatch(/10 PCs not enrolled will need the new key/)
expect(confirmSpy.mock.calls[0][0]).toMatch(/Enrolled PCs are not affected/)
expect(api.post).toHaveBeenCalledWith('/endpoints/agentkey/regenerate')
})
it('revoke confirm names the PCs that lose access', async () => {
const confirmSpy = vi.fn(() => true)
vi.stubGlobal('confirm', confirmSpy)
const wrapper = await render()
const button = wrapper.findAll('button').find(b => b.text() === 'Revoke key')
await button.trigger('click')
await flushPromises()
expect(confirmSpy.mock.calls[0][0]).toMatch(/10 PCs not enrolled will lose access/)
expect(api.delete).toHaveBeenCalledWith('/endpoints/agentkey')
})
it('sends nothing when revoke is declined', async () => {
vi.stubGlobal('confirm', vi.fn(() => false))
const wrapper = await render()
const button = wrapper.findAll('button').find(b => b.text() === 'Revoke key')
await button.trigger('click')
await flushPromises()
expect(api.delete).not.toHaveBeenCalled()
})
it('still manages the key when the enrollment read fails', async () => {
api.get.mockImplementation((path) => {
if (path === '/endpoints/enrollment') return Promise.reject(new Error('403'))
if (path === '/endpoints/agentkey') {
return Promise.resolve({ data: { data: { key: KEY, envkeyfallback: false } } })
}
return Promise.resolve({ data: { data: {} } })
})
vi.stubGlobal('confirm', vi.fn(() => true))
vi.spyOn(console, 'error').mockImplementation(() => {})
const wrapper = mount(AgentKeySettings, { global: { stubs: { RouterLink: RouterLinkStub } } })
await flushPromises()
expect(wrapper.find('.enrollment-summary').exists()).toBe(false)
const button = wrapper.findAll('button').find(b => b.text() === 'Revoke key')
expect(button.exists()).toBe(true)
})
})