Files
peardock/test/acl.test.js
T
snxraven 2b6cfc644f Complete Track F optional Docker/Portainer ops items
Implement volume browse, stack env file, service scale, create
secret/config, disconnect network, registry Hub search, server
schedules, resource editor, and fleet environment tags. Mark roadmap
Tracks A–F complete.
2026-07-10 23:52:26 -04:00

86 lines
2.9 KiB
JavaScript

import test from 'brittle'
import { Roles, roleAllows, MethodRoles, Methods, PROTOCOL_VERSION } from '../shared/protocol.js'
import { resolveRole, assertAllowed } from '../server/core/acl.js'
test('PROTOCOL_VERSION is frozen semver integer', (t) => {
t.is(typeof PROTOCOL_VERSION, 'number')
t.ok(PROTOCOL_VERSION >= 2)
})
test('viewer can list but not remove', (t) => {
t.ok(roleAllows(Roles.viewer, Methods.listContainers))
t.ok(roleAllows(Roles.viewer, Methods.ping))
t.ok(roleAllows(Roles.viewer, Methods.containerTop))
t.not(roleAllows(Roles.viewer, Methods.removeContainer))
t.not(roleAllows(Roles.viewer, Methods.pruneContainers))
t.not(roleAllows(Roles.viewer, Methods.killContainer))
})
test('operator can kill but not prune', (t) => {
t.ok(roleAllows(Roles.operator, Methods.killContainer))
t.ok(roleAllows(Roles.operator, Methods.startContainer))
t.ok(roleAllows(Roles.operator, Methods.pullImage))
t.not(roleAllows(Roles.operator, Methods.pruneContainers))
t.not(roleAllows(Roles.operator, Methods.removeImage))
})
test('admin can everything mapped', (t) => {
for (const method of Object.values(Methods)) {
t.ok(roleAllows(Roles.admin, method), method)
}
})
test('unknown methods require admin', (t) => {
t.not(roleAllows(Roles.viewer, 'secretBackdoor'))
t.ok(roleAllows(Roles.admin, 'secretBackdoor'))
})
test('MethodRoles covers core Methods', (t) => {
const required = [
Methods.listContainers,
Methods.killContainer,
Methods.containerTop,
Methods.updateContainer,
Methods.pruneImages,
Methods.getSystemDf,
Methods.handshake,
]
for (const m of required) {
t.ok(MethodRoles[m], `MethodRoles missing ${m}`)
}
})
test('assertAllowed throws PERMISSION_DENIED', (t) => {
try {
assertAllowed(Roles.viewer, Methods.removeContainer)
t.fail('should throw')
} catch (err) {
t.is(err.code, 'PERMISSION_DENIED')
}
})
test('resolveRole defaults to admin without env policy', (t) => {
// Without PEARDOCK_ADMIN_KEYS / DEFAULT_ROLE override in this process
const role = resolveRole('abc123')
t.ok([Roles.admin, Roles.operator, Roles.viewer].includes(role))
})
test('every Methods entry has MethodRoles mapping', (t) => {
for (const method of Object.values(Methods)) {
t.ok(MethodRoles[method], `missing MethodRoles for ${method}`)
}
})
test('viewer can search images (read-only)', (t) => {
t.ok(roleAllows(Roles.viewer, Methods.searchImages))
t.ok(roleAllows(Roles.admin, Methods.recreateContainer))
t.absent(roleAllows(Roles.operator, Methods.recreateContainer))
t.ok(roleAllows(Roles.admin, Methods.systemPrune))
t.absent(roleAllows(Roles.operator, Methods.systemPrune))
t.ok(roleAllows(Roles.viewer, Methods.browseVolume))
t.ok(roleAllows(Roles.viewer, Methods.listSchedules))
t.ok(roleAllows(Roles.admin, Methods.upsertSchedule))
t.ok(roleAllows(Roles.operator, Methods.scaleService))
t.ok(roleAllows(Roles.operator, Methods.updateContainer))
})