Files
privacy.sexy/tests/unit/infrastructure/CodeRunner/Execution/CommandDefinition/Runner/PermissionSetter/FileSystemExecutablePermissionSetter.spec.ts
undergroundwires 2f31bc7b06 Fix file retention after updates on macOS #417
This fixes issue #417 where autoupdate installer files were not deleted
on macOS, leading to accumulation of old installers.

Key changes:

- Store update files in application-specific directory
- Clear update files directory on every app launch

Other supporting changes:

- Refactor file system operations to be more testable and reusable
- Improve separation of concerns in directory management
- Enhance dependency injection for auto-update logic
- Fix async completion to support `await` operations
- Add additional logging and revise some log messages during updates
2024-10-07 17:33:47 +02:00

152 lines
5.8 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import type { CodeRunErrorType } from '@/application/CodeRunner/CodeRunner';
import type { Logger } from '@/application/Common/Log/Logger';
import { FileSystemExecutablePermissionSetter } from '@/infrastructure/CodeRunner/Execution/CommandDefinition/Runner/PermissionSetter/FileSystemExecutablePermissionSetter';
import type { ScriptFileExecutionOutcome } from '@/infrastructure/CodeRunner/Execution/ScriptFileExecutor';
import type { SystemOperations } from '@/infrastructure/CodeRunner/System/SystemOperations';
import { expectTrue } from '@tests/shared/Assertions/ExpectTrue';
import { FileSystemOperationsStub } from '@tests/unit/shared/Stubs/FileSystemOperationsStub';
import { LoggerStub } from '@tests/unit/shared/Stubs/LoggerStub';
import { SystemOperationsStub } from '@tests/unit/shared/Stubs/SystemOperationsStub';
import { expectExists } from '@tests/shared/Assertions/ExpectExists';
describe('FileSystemExecutablePermissionSetter', () => {
describe('makeFileExecutable', () => {
it('sets permissions on the specified file', async () => {
// arrange
const expectedFilePath = 'expected-file-path';
const fileSystem = new FileSystemOperationsStub();
const context = new TestContext()
.withFilePath(expectedFilePath)
.withSystemOperations(new SystemOperationsStub().withFileSystem(fileSystem));
// act
await context.makeFileExecutable();
// assert
const calls = fileSystem.callHistory.filter((call) => call.methodName === 'setFilePermissions');
expect(calls.length).to.equal(1);
const [actualFilePath] = calls[0].args;
expect(actualFilePath).to.equal(expectedFilePath);
});
it('applies the correct permissions mode', async () => {
// arrange
const expectedMode = '755';
const fileSystem = new FileSystemOperationsStub();
const context = new TestContext()
.withSystemOperations(new SystemOperationsStub().withFileSystem(fileSystem));
// act
await context.makeFileExecutable();
// assert
const calls = fileSystem.callHistory.filter((call) => call.methodName === 'setFilePermissions');
expect(calls.length).to.equal(1);
const [, actualMode] = calls[0].args;
expect(actualMode).to.equal(expectedMode);
});
it('reports success when permissions are set without errors', async () => {
// arrange
const fileSystem = new FileSystemOperationsStub();
fileSystem.setFilePermissions = () => Promise.resolve();
const context = new TestContext()
.withSystemOperations(new SystemOperationsStub().withFileSystem(fileSystem));
// act
const result = await context.makeFileExecutable();
// assert
expectTrue(result.success);
expect(result.error).to.equal(undefined);
});
describe('error handling', () => {
it('returns error expected error message when filesystem throws', async () => {
// arrange
const thrownErrorMessage = 'File system error';
const expectedErrorMessage = `Error setting script file permission: ${thrownErrorMessage}`;
const fileSystem = new FileSystemOperationsStub();
fileSystem.setFilePermissions = () => Promise.reject(new Error(thrownErrorMessage));
const context = new TestContext()
.withSystemOperations(new SystemOperationsStub().withFileSystem(fileSystem));
// act
const result = await context.makeFileExecutable();
// assert
expect(result.success).to.equal(false);
expectExists(result.error);
expect(result.error.message).to.equal(expectedErrorMessage);
});
it('returns expected error type when filesystem throws', async () => {
// arrange
const expectedErrorType: CodeRunErrorType = 'FilePermissionChangeError';
const fileSystem = new FileSystemOperationsStub();
fileSystem.setFilePermissions = () => Promise.reject(new Error('File system error'));
const context = new TestContext()
.withSystemOperations(new SystemOperationsStub().withFileSystem(fileSystem));
// act
const result = await context.makeFileExecutable();
// assert
expect(result.success).to.equal(false);
expectExists(result.error);
const actualErrorType = result.error.type;
expect(actualErrorType).to.equal(expectedErrorType);
});
it('logs error when filesystem throws', async () => {
// arrange
const thrownErrorMessage = 'File system error';
const logger = new LoggerStub();
const fileSystem = new FileSystemOperationsStub();
fileSystem.setFilePermissions = () => Promise.reject(new Error(thrownErrorMessage));
const context = new TestContext()
.withLogger(logger)
.withSystemOperations(new SystemOperationsStub().withFileSystem(fileSystem));
// act
await context.makeFileExecutable();
// assert
logger.assertLogsContainMessagePart('error', thrownErrorMessage);
});
});
});
});
class TestContext {
private filePath = `[${TestContext.name}] /file/path`;
private systemOperations: SystemOperations = new SystemOperationsStub();
private logger: Logger = new LoggerStub();
public withFilePath(filePath: string): this {
this.filePath = filePath;
return this;
}
public withLogger(logger: Logger): this {
this.logger = logger;
return this;
}
public withSystemOperations(systemOperations: SystemOperations): this {
this.systemOperations = systemOperations;
return this;
}
public makeFileExecutable(): Promise<ScriptFileExecutionOutcome> {
const sut = new FileSystemExecutablePermissionSetter(
this.systemOperations,
this.logger,
);
return sut.makeFileExecutable(this.filePath);
}
}