Skip to content

Commit b71b98b

Browse files
tejhanillume
andcommitted
app: electron: Report rejected command exits
Notify renderer callers when validation, permission, or consent rejects a command so command processes do not remain pending. Co-authored-by: René Dudfield <renedudfield@microsoft.com>
1 parent bdcd4e7 commit b71b98b

3 files changed

Lines changed: 82 additions & 0 deletions

File tree

app/e2e-tests/tests/runCommand.spec.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,19 @@ test.describe('run command', () => {
4747
});
4848

4949
test('rejects invalid commands from the renderer', async () => {
50+
const exitCode = electronPage.evaluate(() => {
51+
return new Promise<number>(resolve => {
52+
const removeListener = window.desktopApi.receive(
53+
'command-exit',
54+
(commandId: string, code: number) => {
55+
if (commandId === 'invalid-command-e2e') {
56+
removeListener();
57+
resolve(code);
58+
}
59+
}
60+
);
61+
});
62+
});
5063
const rejection = electronApp.waitForEvent('console', {
5164
predicate: message => message.text().includes('Invalid command: invalid-command'),
5265
});
@@ -62,5 +75,6 @@ test.describe('run command', () => {
6275
});
6376

6477
await expect(rejection).resolves.toBeDefined();
78+
await expect(exitCode).resolves.toBe(-1);
6579
});
6680
});

app/electron/runCmd.test.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,69 @@ describe('handleRunCommand', () => {
319319
expect(spawnMock).not.toHaveBeenCalled();
320320
});
321321

322+
it('reports an exit when command data is invalid', async () => {
323+
await handleRunCommand(
324+
fakeEvent,
325+
{
326+
id: 'invalid-command',
327+
command: 'not-allowed',
328+
args: [],
329+
options: {},
330+
permissionSecrets: {},
331+
},
332+
{ id: 1 } as any,
333+
{}
334+
);
335+
336+
expect(sentMessages).toContainEqual(['command-exit', 'invalid-command', -1]);
337+
expect(spawnMock).not.toHaveBeenCalled();
338+
});
339+
340+
it('does not report an invalid command exit without a string ID', async () => {
341+
await handleRunCommand(fakeEvent, { command: 'not-allowed' }, { id: 1 } as any, {});
342+
343+
expect(sentMessages).toEqual([]);
344+
expect(spawnMock).not.toHaveBeenCalled();
345+
});
346+
347+
it('reports an exit when the permission secret is rejected', async () => {
348+
await handleRunCommand(
349+
fakeEvent,
350+
{
351+
id: 'permission-denied',
352+
command: 'gh',
353+
args: ['auth', 'status'],
354+
options: {},
355+
permissionSecrets: { 'runCmd-gh': 1 },
356+
},
357+
{ id: 1 } as any,
358+
{ 'runCmd-gh': 2 }
359+
);
360+
361+
expect(sentMessages).toContainEqual(['command-exit', 'permission-denied', -2]);
362+
expect(spawnMock).not.toHaveBeenCalled();
363+
});
364+
365+
it('reports an exit when the user denies command consent', async () => {
366+
loadSettingsMock.mockReturnValue({ confirmedCommands: { 'minikube status': false } });
367+
368+
await handleRunCommand(
369+
fakeEvent,
370+
{
371+
id: 'consent-denied',
372+
command: 'minikube',
373+
args: ['status'],
374+
options: {},
375+
permissionSecrets: { 'runCmd-minikube': 1 },
376+
},
377+
{ id: 1 } as any,
378+
{ 'runCmd-minikube': 1 }
379+
);
380+
381+
expect(sentMessages).toContainEqual(['command-exit', 'consent-denied', -3]);
382+
expect(spawnMock).not.toHaveBeenCalled();
383+
});
384+
322385
it('runs gh with the login-shell environment and reports child errors', async () => {
323386
const fakeMainWindow = { id: 1 } as any;
324387
const permissionSecrets = { 'runCmd-gh': 99 };

app/electron/runCmd.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,17 +279,22 @@ export async function handleRunCommand(
279279
const [isValid, errorMessage] = validateCommandData(eventData);
280280
if (!isValid) {
281281
console.error(errorMessage);
282+
if (typeof eventData?.id === 'string') {
283+
event.sender.send('command-exit', eventData.id, -1);
284+
}
282285
return;
283286
}
284287
const commandData = eventData as CommandData;
285288

286289
const [permissionsValid, permissionError] = checkPermissionSecret(commandData, permissionSecrets);
287290
if (!permissionsValid) {
288291
console.error(permissionError);
292+
event.sender.send('command-exit', commandData.id, -2);
289293
return;
290294
}
291295

292296
if (!checkCommandConsent(commandData.command, commandData.args, mainWindow)) {
297+
event.sender.send('command-exit', commandData.id, -3);
293298
return;
294299
}
295300

0 commit comments

Comments
 (0)