/** * One-line install block on the endpoints agent key page. * * The token is shown exactly once, so the two things worth pinning are that the * command rendered is the one the server returned, and that reloading the page * does NOT put a token back on screen - the list read carries no secret, and a * field that repopulated from it would be lying about what is recoverable. */ 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 TOKENS_URL = '/endpoints/siteagent/bootstrap/tokens' const SECRET = 'sdbb_0123456789abcdefghijklmnopqrstuvwxyzABCDEFG' const SCRIPT_URL = `https://shopdb.test/shopdb/api/endpoints/siteagent/bootstrap.ps1?t=${SECRET}` // Download then run: Defender kills the `irm ... | iex` shape before it runs. const TEMP = 'C:\\Windows\\Temp\\shopdb-bootstrap.ps1' const COMMAND = 'powershell -ExecutionPolicy Bypass -c "try { Invoke-WebRequest ' + `-UseBasicParsing '${SCRIPT_URL}' -OutFile '${TEMP}'; & '${TEMP}' }` + ` finally { Remove-Item '${TEMP}' -Force -ErrorAction SilentlyContinue }"` const MINTED_TOKEN = { tokenid: 7, tokenprefix: 'sdbb_012345', createdat: '2026-09-15T10:00:00Z', expiresat: '2026-09-16T10:00:00Z', maxuses: 25, usecount: 0, remaining: 25, revokedat: null, state: 'active' } function listResponse(tokens) { return { data: { data: { tokens } } } } async function render(tokens = []) { api.get.mockImplementation((path) => { if (path === TOKENS_URL) return Promise.resolve(listResponse(tokens)) if (path === '/endpoints/agentkey') { return Promise.resolve({ data: { data: { key: null, envkeyfallback: false } } }) } return Promise.resolve({ data: { data: {} } }) }) const wrapper = mount(AgentKeySettings, { global: { stubs: { RouterLink: RouterLinkStub } } }) await flushPromises() return wrapper } function generateButton(wrapper) { return wrapper.findAll('button') .find((button) => button.text() === 'Generate install command') } describe('AgentKeySettings one-line install', () => { beforeEach(() => { api.get.mockReset() api.post.mockReset() api.delete.mockReset() }) afterEach(() => { vi.unstubAllGlobals() }) it('says the PC waits for approval and that managed bays use GE-Enforce', async () => { const wrapper = await render() const block = wrapper.find('.bootstrap-block').text() expect(block).toContain('One-line install') expect(block).toMatch(/waits for approval in\s+Settings > Endpoints > Enrollment/) expect(block).toContain('GE-Enforce') expect(block).toContain('elevated') }) it('generate posts the hours and maximum uses', async () => { api.post.mockResolvedValue({ data: { data: { secret: SECRET, command: COMMAND, scripturl: SCRIPT_URL, token: MINTED_TOKEN } } }) const wrapper = await render() await wrapper.find('#bootstrap-hours').setValue(48) await wrapper.find('#bootstrap-maxuses').setValue(5) await generateButton(wrapper).trigger('click') await flushPromises() expect(api.post).toHaveBeenCalledWith(TOKENS_URL, { hours: 48, maxuses: 5 }) }) it('defaults to 24 hours and 25 uses', async () => { api.post.mockResolvedValue({ data: { data: { secret: SECRET, command: COMMAND, scripturl: SCRIPT_URL, token: MINTED_TOKEN } } }) const wrapper = await render() expect(wrapper.find('#bootstrap-hours').element.value).toBe('24') expect(wrapper.find('#bootstrap-maxuses').element.value).toBe('25') await generateButton(wrapper).trigger('click') await flushPromises() expect(api.post).toHaveBeenCalledWith(TOKENS_URL, { hours: 24, maxuses: 25 }) }) it('renders the returned command, the once-only warning and the expiry', async () => { api.post.mockResolvedValue({ data: { data: { secret: SECRET, command: COMMAND, scripturl: SCRIPT_URL, token: MINTED_TOKEN } } }) const wrapper = await render() await generateButton(wrapper).trigger('click') await flushPromises() const field = wrapper.find('#bootstrap-command') expect(field.exists()).toBe(true) expect(field.element.value).toBe(COMMAND) expect(field.element.value).toContain(SECRET) expect(field.attributes('readonly')).toBeDefined() const block = wrapper.find('.bootstrap-block').text() expect(block).toContain('The token is shown once and cannot be recovered') expect(block).toContain('25 of') expect(block).toContain('uses remaining') }) it('offers the same token as a Download .ps1 link', async () => { api.post.mockResolvedValue({ data: { data: { secret: SECRET, command: COMMAND, scripturl: SCRIPT_URL, token: MINTED_TOKEN } } }) const wrapper = await render() expect(wrapper.find('.bootstrap-download').exists()).toBe(false) await generateButton(wrapper).trigger('click') await flushPromises() const link = wrapper.find('.bootstrap-download') expect(link.attributes('href')).toBe(SCRIPT_URL) expect(link.attributes('download')).toBe('shopdb-bootstrap.ps1') expect(wrapper.find('#bootstrap-command').element.value).not.toContain(' | iex') }) it('copies the command to the clipboard', async () => { const writeText = vi.fn(() => Promise.resolve()) vi.stubGlobal('navigator', { clipboard: { writeText } }) api.post.mockResolvedValue({ data: { data: { secret: SECRET, command: COMMAND, scripturl: SCRIPT_URL, token: MINTED_TOKEN } } }) const wrapper = await render() await generateButton(wrapper).trigger('click') await flushPromises() const copy = wrapper.findAll('button').find((button) => button.text() === 'Copy') await copy.trigger('click') await flushPromises() expect(writeText).toHaveBeenCalledWith(COMMAND) expect(wrapper.find('.bootstrap-block').text()).toContain('Copied') }) it('lists live tokens and revoke posts to that token', async () => { api.post.mockResolvedValue({ data: { data: { token: MINTED_TOKEN } } }) const wrapper = await render([MINTED_TOKEN]) expect(api.get).toHaveBeenCalledWith(TOKENS_URL) const rows = wrapper.findAll('.bootstrap-block tbody tr') expect(rows).toHaveLength(1) expect(rows[0].text()).toContain('sdbb_012345') expect(rows[0].text()).toContain('0 / 25') const revoke = wrapper.findAll('button').find((button) => button.text() === 'Revoke') await revoke.trigger('click') await flushPromises() expect(api.post).toHaveBeenCalledWith(`${TOKENS_URL}/7/revoke`) }) it('offers no Revoke on a token that is already spent', async () => { const wrapper = await render([ { ...MINTED_TOKEN, state: 'revoked', usecount: 2 } ]) expect(wrapper.findAll('button').find((button) => button.text() === 'Revoke')) .toBeUndefined() }) it('does not show the secret again after a reload', async () => { api.post.mockResolvedValue({ data: { data: { secret: SECRET, command: COMMAND, scripturl: SCRIPT_URL, token: MINTED_TOKEN } } }) const first = await render() await generateButton(first).trigger('click') await flushPromises() expect(first.find('#bootstrap-command').exists()).toBe(true) // The reload sees the token in the list read, which carries no secret. const reloaded = await render([MINTED_TOKEN]) expect(reloaded.find('#bootstrap-command').exists()).toBe(false) expect(reloaded.text()).not.toContain(SECRET) expect(reloaded.text()).not.toContain('The token is shown once') // The row is still there, by prefix only. expect(reloaded.find('.bootstrap-block tbody tr').text()).toContain('sdbb_012345') }) })