Fix script cancellation with new dialog on Linux
This commit improves the management of script execution process by enhancing the way terminal commands are handled, paving the way for easier future modifications and providing clearer feedback to users when scripts are cancelled. Previously, the UI displayed a generic error message which could lead to confusion if the user intentionally cancelled the script execution. Now, a specific error dialog will appear, improving the user experience by accurately reflecting the action taken by the user. This change affects code execution on Linux where closing GNOME terminal returns exit code `137` which is then treated by script cancellation by privacy.sexy to show the accurate error dialog. It does not affect macOS and Windows as curret commands result in success (`0`) exit code on cancellation. Additionally, this update encapsulates OS-specific logic into dedicated classes, promoting better separation of concerns and increasing the modularity of the codebase. This makes it simpler to maintain and extend the application. Key changes: - Display a specific error message for script cancellations. - Refactor command execution into dedicated classes. - Improve file permission setting flexibility and avoid setting file permissions on Windows as it's not required to execute files. - Introduce more granular error types for script execution. - Increase logging for shell commands to aid in debugging. - Expand test coverage to ensure reliability. - Fix error dialogs not showing the error messages due to incorrect propagation of errors. Other supported changes: - Update `SECURITY.md` with details on script readback and verification. - Fix a typo in `IpcRegistration.spec.ts`. - Document antivirus scans in `desktop-vs-web-features.md`.
This commit is contained in:
@@ -8,32 +8,70 @@ import { AllSupportedOperatingSystems } from '@tests/shared/TestCases/SupportedO
|
||||
import { ScriptDiagnosticsCollectorStub } from '@tests/unit/shared/Stubs/ScriptDiagnosticsCollectorStub';
|
||||
|
||||
describe('ScriptErrorDialog', () => {
|
||||
describe('handles readback error type', () => {
|
||||
it('handles file readback error', async () => {
|
||||
// arrange
|
||||
const errorDetails = createErrorDetails({ isFileReadbackError: true });
|
||||
const context = new CreateScriptErrorDialogTestSetup()
|
||||
.withDetails(errorDetails);
|
||||
// act
|
||||
const dialog = await context.createScriptErrorDialog();
|
||||
// assert
|
||||
assertValidDialog(dialog);
|
||||
});
|
||||
it('handles generic error', async () => {
|
||||
// arrange
|
||||
const errorDetails = createErrorDetails({ isFileReadbackError: false });
|
||||
const context = new CreateScriptErrorDialogTestSetup()
|
||||
.withDetails(errorDetails);
|
||||
// act
|
||||
const dialog = await context.createScriptErrorDialog();
|
||||
// assert
|
||||
assertValidDialog(dialog);
|
||||
describe('handling different error types', () => {
|
||||
const testScenarios: readonly {
|
||||
readonly description: string,
|
||||
readonly givenErrorDetails: ScriptErrorDetails;
|
||||
readonly expectedDialogTitle: string;
|
||||
}[] = [
|
||||
{
|
||||
description: 'generic error when running',
|
||||
givenErrorDetails: createErrorDetails({
|
||||
isFileReadbackError: false,
|
||||
errorContext: 'run',
|
||||
}),
|
||||
expectedDialogTitle: 'Error Running Script',
|
||||
},
|
||||
{
|
||||
description: 'generic error when saving',
|
||||
givenErrorDetails: createErrorDetails({
|
||||
isFileReadbackError: false,
|
||||
errorContext: 'save',
|
||||
}),
|
||||
expectedDialogTitle: 'Error Saving Script',
|
||||
},
|
||||
{
|
||||
description: 'file readback failure',
|
||||
givenErrorDetails: createErrorDetails({ isFileReadbackError: true }),
|
||||
expectedDialogTitle: 'Possible Antivirus Script Block',
|
||||
},
|
||||
{
|
||||
description: 'script interruption',
|
||||
givenErrorDetails: createErrorDetails({
|
||||
errorContext: 'run',
|
||||
errorType: 'ExternalProcessTermination',
|
||||
}),
|
||||
expectedDialogTitle: 'Script Stopped',
|
||||
},
|
||||
];
|
||||
testScenarios.forEach((
|
||||
{ description, givenErrorDetails, expectedDialogTitle },
|
||||
) => {
|
||||
it(`creates dialog for "${description}"`, async () => {
|
||||
// arrange
|
||||
const context = new CreateScriptErrorDialogTestSetup()
|
||||
.withDetails(givenErrorDetails);
|
||||
// act
|
||||
const dialog = await context.createScriptErrorDialog();
|
||||
// assert
|
||||
assertValidDialog(dialog);
|
||||
});
|
||||
it(`creates dialog for "${description}" with title "${expectedDialogTitle}"`, async () => {
|
||||
// arrange
|
||||
const context = new CreateScriptErrorDialogTestSetup()
|
||||
.withDetails(givenErrorDetails);
|
||||
// act
|
||||
const dialog = await context.createScriptErrorDialog();
|
||||
// assert
|
||||
const [actualDialogTitle] = dialog;
|
||||
expect(actualDialogTitle).to.equal(expectedDialogTitle);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('handles supported operatingSystems', () => {
|
||||
describe('handling supported operating systems', () => {
|
||||
AllSupportedOperatingSystems.forEach((operatingSystem) => {
|
||||
it(`${OperatingSystem[operatingSystem]}`, async () => {
|
||||
it(`creates dialog for ${OperatingSystem[operatingSystem]}`, async () => {
|
||||
// arrange
|
||||
const diagnostics = new ScriptDiagnosticsCollectorStub()
|
||||
.withOperatingSystem(operatingSystem);
|
||||
@@ -47,46 +85,48 @@ describe('ScriptErrorDialog', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('handles undefined diagnostics collector', async () => {
|
||||
const diagnostics = undefined;
|
||||
const context = new CreateScriptErrorDialogTestSetup()
|
||||
.withDiagnostics(diagnostics);
|
||||
// act
|
||||
const dialog = await context.createScriptErrorDialog();
|
||||
// assert
|
||||
assertValidDialog(dialog);
|
||||
describe('handling missing inputs', () => {
|
||||
it('creates dialog when diagnostics collector is undefined', async () => {
|
||||
const diagnostics = undefined;
|
||||
const context = new CreateScriptErrorDialogTestSetup()
|
||||
.withDiagnostics(diagnostics);
|
||||
// act
|
||||
const dialog = await context.createScriptErrorDialog();
|
||||
// assert
|
||||
assertValidDialog(dialog);
|
||||
});
|
||||
|
||||
it('creates dialog when operating system is undefined', async () => {
|
||||
// arrange
|
||||
const undefinedOperatingSystem = undefined;
|
||||
const diagnostics = new ScriptDiagnosticsCollectorStub()
|
||||
.withOperatingSystem(undefinedOperatingSystem);
|
||||
const context = new CreateScriptErrorDialogTestSetup()
|
||||
.withDiagnostics(diagnostics);
|
||||
// act
|
||||
const dialog = await context.createScriptErrorDialog();
|
||||
// assert
|
||||
assertValidDialog(dialog);
|
||||
});
|
||||
|
||||
it('creates dialog when script directory path is undefined', async () => {
|
||||
// arrange
|
||||
const undefinedScriptsDirectory = undefined;
|
||||
const diagnostics = new ScriptDiagnosticsCollectorStub()
|
||||
.withScriptDirectoryPath(undefinedScriptsDirectory);
|
||||
const context = new CreateScriptErrorDialogTestSetup()
|
||||
.withDiagnostics(diagnostics);
|
||||
// act
|
||||
const dialog = await context.createScriptErrorDialog();
|
||||
// assert
|
||||
assertValidDialog(dialog);
|
||||
});
|
||||
});
|
||||
|
||||
it('handles undefined operating system', async () => {
|
||||
// arrange
|
||||
const undefinedOperatingSystem = undefined;
|
||||
const diagnostics = new ScriptDiagnosticsCollectorStub()
|
||||
.withOperatingSystem(undefinedOperatingSystem);
|
||||
const context = new CreateScriptErrorDialogTestSetup()
|
||||
.withDiagnostics(diagnostics);
|
||||
// act
|
||||
const dialog = await context.createScriptErrorDialog();
|
||||
// assert
|
||||
assertValidDialog(dialog);
|
||||
});
|
||||
|
||||
it('handles directory path', async () => {
|
||||
// arrange
|
||||
const undefinedScriptsDirectory = undefined;
|
||||
const diagnostics = new ScriptDiagnosticsCollectorStub()
|
||||
.withScriptDirectoryPath(undefinedScriptsDirectory);
|
||||
const context = new CreateScriptErrorDialogTestSetup()
|
||||
.withDiagnostics(diagnostics);
|
||||
// act
|
||||
const dialog = await context.createScriptErrorDialog();
|
||||
// assert
|
||||
assertValidDialog(dialog);
|
||||
});
|
||||
|
||||
describe('handles all contexts', () => {
|
||||
describe('handling all error contexts', () => {
|
||||
const possibleContexts: ScriptErrorDetails['errorContext'][] = ['run', 'save'];
|
||||
possibleContexts.forEach((dialogContext) => {
|
||||
it(`${dialogContext} context`, async () => {
|
||||
it(`creates dialog for '${dialogContext}' context`, async () => {
|
||||
// arrange
|
||||
const undefinedScriptsDirectory = undefined;
|
||||
const diagnostics = new ScriptDiagnosticsCollectorStub()
|
||||
@@ -114,7 +154,7 @@ function assertValidDialog(dialog: Parameters<Dialog['showError']>): void {
|
||||
function createErrorDetails(partialDetails?: Partial<ScriptErrorDetails>): ScriptErrorDetails {
|
||||
const defaultDetails: ScriptErrorDetails = {
|
||||
errorContext: 'run',
|
||||
errorType: 'test-error-type',
|
||||
errorType: 'UnsupportedPlatform',
|
||||
errorMessage: 'test error message',
|
||||
isFileReadbackError: false,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user