BuildArtifactLocatorAdapter.unit.test.ts•7.56 kB
import { describe, it, expect, jest, beforeEach } from '@jest/globals';
import { BuildArtifactLocatorAdapter } from '../../infrastructure/BuildArtifactLocatorAdapter.js';
import { ICommandExecutor, ExecutionResult, ExecutionOptions } from '../../../../application/ports/CommandPorts.js';
import { existsSync } from 'fs';
// Mock fs module
jest.mock('fs', () => ({
existsSync: jest.fn<(path: string) => boolean>()
}));
const mockExistsSync = existsSync as jest.MockedFunction<typeof existsSync>;
describe('BuildArtifactLocator', () => {
// Factory method for creating the SUT with its dependencies
function createSUT() {
const mockExecute = jest.fn<(command: string, options?: ExecutionOptions) => Promise<ExecutionResult>>();
const mockExecutor: ICommandExecutor = {
execute: mockExecute
};
const sut = new BuildArtifactLocatorAdapter(mockExecutor);
return { sut, mockExecutor, mockExecute };
}
beforeEach(() => {
jest.clearAllMocks();
});
describe('findApp', () => {
describe('when app is found', () => {
it('should return app path when app exists', async () => {
// Arrange
const { sut, mockExecutor, mockExecute } = createSUT();
const derivedDataPath = '/path/to/DerivedData';
const appPath = '/path/to/DerivedData/Build/Products/Debug-iphonesimulator/MyApp.app';
mockExecute.mockResolvedValue({
stdout: appPath + '\n',
stderr: '',
exitCode: 0
});
mockExistsSync.mockReturnValue(true);
// Act
const result = await sut.findApp(derivedDataPath);
// Assert
expect(result).toBe(appPath);
expect(mockExecutor.execute).toHaveBeenCalledWith(
'find "/path/to/DerivedData" -name "*.app" -type d | head -1',
{ timeout: 5000 }
);
expect(mockExistsSync).toHaveBeenCalledWith(appPath);
});
it('should handle paths with spaces correctly', async () => {
// Arrange
const { sut, mockExecutor, mockExecute } = createSUT();
const derivedDataPath = '/path/with spaces/DerivedData';
const appPath = '/path/with spaces/DerivedData/MyApp.app';
mockExecute.mockResolvedValue({
stdout: appPath,
stderr: '',
exitCode: 0
});
mockExistsSync.mockReturnValue(true);
// Act
const result = await sut.findApp(derivedDataPath);
// Assert
expect(result).toBe(appPath);
expect(mockExecutor.execute).toHaveBeenCalledWith(
'find "/path/with spaces/DerivedData" -name "*.app" -type d | head -1',
{ timeout: 5000 }
);
});
it('should trim whitespace from command output', async () => {
// Arrange
const { sut, mockExecutor, mockExecute } = createSUT();
const appPath = '/path/to/MyApp.app';
mockExecute.mockResolvedValue({
stdout: ' ' + appPath + ' \n\n',
stderr: '',
exitCode: 0
});
mockExistsSync.mockReturnValue(true);
// Act
const result = await sut.findApp('/path');
// Assert
expect(result).toBe(appPath);
expect(mockExistsSync).toHaveBeenCalledWith(appPath);
});
});
describe('when app is not found', () => {
it('should return undefined when find returns empty', async () => {
// Arrange
const { sut, mockExecutor, mockExecute } = createSUT();
mockExecute.mockResolvedValue({
stdout: '',
stderr: '',
exitCode: 0
});
// Act
const result = await sut.findApp('/path/to/DerivedData');
// Assert
expect(result).toBeUndefined();
expect(existsSync).not.toHaveBeenCalled();
});
it('should return undefined when app path does not exist', async () => {
// Arrange
const { sut, mockExecutor, mockExecute } = createSUT();
const appPath = '/path/to/NonExistent.app';
mockExecute.mockResolvedValue({
stdout: appPath,
stderr: '',
exitCode: 0
});
mockExistsSync.mockReturnValue(false);
// Act
const result = await sut.findApp('/path/to/DerivedData');
// Assert
expect(result).toBeUndefined();
expect(mockExistsSync).toHaveBeenCalledWith(appPath);
});
it('should return undefined when find command fails', async () => {
// Arrange
const { sut, mockExecutor, mockExecute } = createSUT();
mockExecute.mockResolvedValue({
stdout: '',
stderr: 'find: /path/to/DerivedData: No such file or directory',
exitCode: 1
});
// Act
const result = await sut.findApp('/path/to/DerivedData');
// Assert
expect(result).toBeUndefined();
expect(existsSync).not.toHaveBeenCalled();
});
});
describe('error handling', () => {
it('should return undefined when executor throws', async () => {
// Arrange
const { sut, mockExecutor, mockExecute } = createSUT();
mockExecute.mockRejectedValue(
new Error('Command execution failed')
);
// Act
const result = await sut.findApp('/path/to/DerivedData');
// Assert
expect(result).toBeUndefined();
expect(existsSync).not.toHaveBeenCalled();
});
it('should handle timeout gracefully', async () => {
// Arrange
const { sut, mockExecutor, mockExecute } = createSUT();
mockExecute.mockRejectedValue(
new Error('Command timed out')
);
// Act
const result = await sut.findApp('/path/to/DerivedData');
// Assert
expect(result).toBeUndefined();
});
});
describe('command construction', () => {
it('should use correct find command', async () => {
// Arrange
const { sut, mockExecutor, mockExecute } = createSUT();
const derivedDataPath = '/Users/dev/DerivedData';
mockExecute.mockResolvedValue({
stdout: '/some/app.app',
stderr: '',
exitCode: 0
});
mockExistsSync.mockReturnValue(true);
// Act
await sut.findApp(derivedDataPath);
// Assert
expect(mockExecutor.execute).toHaveBeenCalledWith(
'find "/Users/dev/DerivedData" -name "*.app" -type d | head -1',
{ timeout: 5000 }
);
});
it('should escape quotes in path', async () => {
// Arrange
const { sut, mockExecutor, mockExecute } = createSUT();
const derivedDataPath = '/path/with"quote';
mockExecute.mockResolvedValue({
stdout: '',
stderr: '',
exitCode: 0
});
// Act
await sut.findApp(derivedDataPath);
// Assert
// Note: The current implementation doesn't escape quotes properly
// This test documents the current behavior
expect(mockExecutor.execute).toHaveBeenCalledWith(
'find "/path/with"quote" -name "*.app" -type d | head -1',
{ timeout: 5000 }
);
});
});
});
});