/** * Run Scripts tab on PC Details. * * The thing worth pinning here is the SEPARATION. run_script and * scheduledtask_run map to different grants in plugins/endpoints/commands.py * (endpoints.runscript and endpoints.corptools), split out of a single * endpoints.deploy bundle precisely so that being able to trigger a corporate * tool does not also mean being able to run arbitrary PowerShell as SYSTEM. * * That split lives in the backend. The UI can quietly undo it by hanging the * script picker off the Corporate Tools tab, whose only gate is * endpoints.corptools - and the failure would be invisible in review, because * the page would look correct and the server would simply 403 the push. These * tests fail if someone merges the two. * * Asserted against the file text, matching systemScriptsSettings.spec.js. */ import { describe, it, expect } from 'vitest' import { readFileSync } from 'fs' import { join, dirname } from 'path' import { fileURLToPath } from 'url' const here = dirname(fileURLToPath(import.meta.url)) const source = readFileSync(join(here, 'PCDetail.vue'), 'utf8') describe('PCDetail Run Scripts tab', () => { it('gates the tab on endpoints.runscript, not the Corporate Tools grant', () => { expect(source).toMatch( /const canRunScript = computed\(\(\) => authStore\.hasPermission\('endpoints\.runscript'\)\)/) }) it('pushes run_script from its own tab, not from the Corporate Tools block', () => { // The corptools block must not gain a run_script call: that would put the // action behind endpoints.corptools in the UI while the server still // demands endpoints.runscript. const corptools = source.slice( source.indexOf("activegroup === 'corptools'"), source.indexOf("activegroup === 'runscripts'")) expect(corptools).not.toMatch(/run_script/) expect(source).toMatch(/activegroup === 'runscripts'/) }) it('enqueues run_script with a script-id target', () => { expect(source).toMatch(/'run_script', `script:\$\{script\.scriptid\}`/) }) it('requires a typed confirmation before running', () => { // Every script on this tab is arbitrary code, so unlike Corporate Tools // there is no safe branch that gets a plain OK button. const handler = source.slice(source.indexOf('async function confirmRunScript')) const body = handler.slice(0, handler.indexOf('\n}\n')) expect(body).toMatch(/Type RUN to confirm/) expect(body).toMatch(/typed !== 'RUN'/) }) it('warns that a script runs as SYSTEM before it is sent', () => { const tab = source.slice(source.indexOf("activegroup === 'runscripts'")) expect(tab.slice(0, 2000)).toMatch(/runs as SYSTEM/) }) it('shows the run context and working directory read-only', () => { // Both are authoring decisions made in the library. Offering an override // here would mean the script that ran is not the script that was reviewed, // so they must be rendered as facts and never bound with v-model. const tab = source.slice(source.indexOf("activegroup === 'runscripts'")) const block = tab.slice(0, tab.indexOf('runscripts-actions')) expect(block).toMatch(/selectedscript\.workingdirectory/) expect(block).toMatch(/selectedscript\.runcontext/) expect(block).not.toMatch(/v-model="[^"]*workingdirectory/) expect(block).not.toMatch(/v-model="[^"]*runcontext/) }) it('offers only scripts that have a body', () => { // A bodyless script is rejected at enqueue by _resolve_run_script, so // listing it would offer a button whose only outcome is an error. expect(source).toMatch(/filter\(\(script\) => script\.hasbody\)/) }) it('reports the push as queued rather than as finished', () => { // The agent collects the command on its next check-in; claiming it ran // would assert an outcome nothing has observed. const handler = source.slice(source.indexOf('async function confirmRunScript')) expect(handler.slice(0, handler.indexOf('\n}\n'))).toMatch(/queued for/) }) })