Files
technitium-console/src/screens/dhcp/Dhcp.hub.test.tsx
T
DenozordecandClaude Opus 5.5 8bb1e01936 Hub stage 5: manage each server from one console
Zones, Cache, Blocking, DNS Client, Settings, DHCP and Logs can now be
changed on one chosen server through the hub, with that server's admin
token. Operators manage cache and blocking; admins also zones, settings
and DHCP. Under "All servers", or on a server without an admin token,
the controls are disabled with a hint.

The hub refuses settings/set changes to a server's web service fields
and anything about users, SSO or LDAP; backup, restore, log and stats
deletion and admin/* stay refused. Non-admins get settings with secrets
blanked. Write params travel in the POST body, downloads pass through
with a single-use token, and the audit log names no values.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-31 22:06:37 +07:00

69 lines
2.9 KiB
TypeScript

import { describe, expect, it, vi, afterEach } from 'vitest'
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { Dhcp } from './Dhcp'
import * as api from '../../api/dhcp'
import { HubMode } from '../../ui/ClusterNodeSelect'
import { HubWritable } from '../../lib/hub-write'
afterEach(() => vi.restoreAllMocks())
/* Behind a hub the screen starts on the first server, and changes only where the hub can write. */
const NODES = [
{ name: 'mdns', type: 'Primary' },
{ name: 'ndns', type: 'Secondary' },
]
function hubDhcp(writable: string[], canModify = true) {
vi.spyOn(api, 'listScopes').mockResolvedValue({ kind: 'ok', data: [] })
return render(
<HubMode.Provider value>
<HubWritable.Provider value={writable}>
<Dhcp token="t" sub="Scopes" nodes={NODES} clusterInitialised canModify={canModify} canDelete={canModify} />
</HubWritable.Provider>
</HubMode.Provider>,
)
}
describe('DHCP behind a hub', () => {
it('starts on the first server, named in the request and shown in the selector', async () => {
hubDhcp(['mdns', 'ndns'])
await screen.findByText('No Scope Found')
expect(api.listScopes).toHaveBeenCalledWith('t', 'mdns')
expect(screen.getByRole('combobox', { name: 'Server' })).toHaveTextContent('mdns')
expect(screen.queryByRole('option', { name: 'All servers' })).not.toBeInTheDocument()
expect(screen.getByRole('button', { name: 'Add Scope' })).toBeInTheDocument()
expect(screen.queryByRole('status')).not.toBeInTheDocument()
})
it('switches changes off on a server the hub holds no admin token for, and says why', async () => {
const user = userEvent.setup()
hubDhcp(['mdns'])
await screen.findByText('No Scope Found')
await user.click(screen.getByRole('combobox', { name: 'Server' }))
await user.click(await screen.findByRole('option', { name: 'ndns' }))
expect(await screen.findByText(/No admin token configured on the hub/)).toBeInTheDocument()
expect(api.listScopes).toHaveBeenLastCalledWith('t', 'ndns')
expect(screen.queryByRole('button', { name: 'Add Scope' })).not.toBeInTheDocument()
})
it('says nothing about the server when the role is what forbids the change', async () => {
hubDhcp(['mdns'], false)
await screen.findByText('No Scope Found')
expect(screen.queryByRole('button', { name: 'Add Scope' })).not.toBeInTheDocument()
expect(screen.queryByText(/admin token|Choose a server/)).not.toBeInTheDocument()
})
})
describe('DHCP outside a hub', () => {
it('keeps the empty node and no hint', async () => {
vi.spyOn(api, 'listScopes').mockResolvedValue({ kind: 'ok', data: [] })
render(<Dhcp token="t" sub="Scopes" />)
await screen.findByText('No Scope Found')
expect(api.listScopes).toHaveBeenCalledWith('t', '')
expect(screen.getByRole('button', { name: 'Add Scope' })).toBeInTheDocument()
})
})