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
152 lines
5.8 KiB
TypeScript
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);
|
|
}
|
|
}
|