Improve desktop script runs with timestamps & logs

Improve script execution in the desktop app by introducing timestamped
filenames and detailed logging. These changes aim to facilitate easier
debugging, auditing and overall better user experience.

Key changes:

- Add timestamps in filenames for temporary files to aid in
  troubleshooting and auditing.
- Add application logging throughout the script execution process to
  enhance troubleshooting capabilities.

Other supporting changes:

- Refactor `TemporaryFileCodeRunner` with subfunctions for improved
  readability, maintenance, reusability and extensibility.
- Refactor unit tests for `TemporaryFileCodeRunner` for improved
  granularity and simplicity.
- Create centralized definition of supported operating systems by
  privacy.sexy to ensure robust and consistent test case creation.
- Simplify the `runCode` method by removing the file extension
  parameter; now handled internally by `FileNameGenerator`.
This commit is contained in:
undergroundwires
2023-12-31 14:28:58 +01:00
parent 8f4b34f8f1
commit cdc32d1f12
11 changed files with 465 additions and 202 deletions

View File

@@ -2,6 +2,7 @@ import { ViewType } from '@/presentation/components/Scripts/Menu/View/ViewType';
import { getEnumValues } from '@/application/Common/Enum';
import { OperatingSystem } from '@/domain/OperatingSystem';
import { getOperatingSystemDisplayName } from '@/presentation/components/Shared/OperatingSystemNames';
import { AllSupportedOperatingSystems } from '@tests/shared/TestCases/SupportedOperatingSystems';
describe('operating system selector', () => {
// Regression test for a bug where switching between operating systems caused uncaught exceptions.
@@ -29,11 +30,7 @@ function getSupportedOperatingSystemsList() {
the application within Cypress, as its compilation is tightly coupled with Vite.
Ideally, this should dynamically fetch the list from the compiled application.
*/
return [
OperatingSystem.Windows,
OperatingSystem.Linux,
OperatingSystem.macOS,
];
return AllSupportedOperatingSystems;
}
function assertExpectedActions() {

View File

@@ -0,0 +1,11 @@
import { OperatingSystem } from '@/domain/OperatingSystem';
export type SupportedOperatingSystem = OperatingSystem.Windows
| OperatingSystem.Linux
| OperatingSystem.macOS;
export const AllSupportedOperatingSystems: readonly OperatingSystem[] = [
OperatingSystem.Windows,
OperatingSystem.Linux,
OperatingSystem.macOS,
] as const;

View File

@@ -5,6 +5,7 @@ import { EnumRangeTestRunner } from '@tests/unit/application/Common/EnumRangeTes
import { VersionStub } from '@tests/unit/shared/Stubs/VersionStub';
import { Version } from '@/domain/Version';
import { PropertyKeys } from '@/TypeHelpers';
import { SupportedOperatingSystem, AllSupportedOperatingSystems } from '@tests/shared/TestCases/SupportedOperatingSystems';
describe('ProjectInformation', () => {
describe('retrieval of property values', () => {
@@ -117,48 +118,42 @@ describe('ProjectInformation', () => {
});
});
});
describe('correct retrieval of download URL per operating system', () => {
const testCases: ReadonlyArray<{
readonly os: OperatingSystem,
describe('correct retrieval of download URL for every supported operating system', () => {
const testCases: Record<SupportedOperatingSystem, {
readonly expected: string,
readonly repositoryUrl: string,
readonly version: string,
}> = [
{
os: OperatingSystem.macOS,
}> = {
[OperatingSystem.macOS]: {
expected: 'https://github.com/undergroundwires/privacy.sexy/releases/download/0.7.2/privacy.sexy-0.7.2.dmg',
repositoryUrl: 'https://github.com/undergroundwires/privacy.sexy.git',
version: '0.7.2',
},
{
os: OperatingSystem.Linux,
[OperatingSystem.Linux]: {
expected: 'https://github.com/undergroundwires/privacy.sexy/releases/download/0.7.2/privacy.sexy-0.7.2.AppImage',
repositoryUrl: 'https://github.com/undergroundwires/privacy.sexy.git',
version: '0.7.2',
},
{
os: OperatingSystem.Windows,
[OperatingSystem.Windows]: {
expected: 'https://github.com/undergroundwires/privacy.sexy/releases/download/0.7.2/privacy.sexy-Setup-0.7.2.exe',
repositoryUrl: 'https://github.com/undergroundwires/privacy.sexy.git',
version: '0.7.2',
},
];
for (const testCase of testCases) {
it(`should return the expected download URL for ${OperatingSystem[testCase.os]}`, () => {
};
AllSupportedOperatingSystems.forEach((operatingSystem) => {
it(`should return the expected download URL for ${OperatingSystem[operatingSystem]}`, () => {
// arrange
const {
expected, version, repositoryUrl, os,
} = testCase;
const { expected, version, repositoryUrl } = testCases[operatingSystem];
const sut = new ProjectInformationBuilder()
.withVersion(new VersionStub(version))
.withRepositoryUrl(repositoryUrl)
.build();
// act
const actual = sut.getDownloadUrl(os);
const actual = sut.getDownloadUrl(operatingSystem);
// assert
expect(actual).to.equal(expected);
});
}
});
describe('should throw an error when provided with an invalid operating system', () => {
// arrange
const sut = new ProjectInformationBuilder()

View File

@@ -0,0 +1,84 @@
import { describe, it, expect } from 'vitest';
import { AllSupportedOperatingSystems, SupportedOperatingSystem } from '@tests/shared/TestCases/SupportedOperatingSystems';
import { OperatingSystem } from '@/domain/OperatingSystem';
import { generateOsTimestampedFileName } from '@/infrastructure/CodeRunner/FileNameGenerator';
import { formatAssertionMessage } from '@tests/shared/FormatAssertionMessage';
describe('FileNameGenerator', () => {
describe('generateOsTimestampedFileName', () => {
it('generates correct prefix', () => {
// arrange
const expectedPrefix = 'run';
// act
const fileName = generateFileNamePartsForTesting();
// assert
expect(fileName.prefix).to.equal(expectedPrefix);
});
it('generates correct timestamp', () => {
// arrange
const currentDate = '2023-01-01T12:00:00.000Z';
const expectedTimestamp = '2023-01-01_12-00-00';
const date = new Date(currentDate);
// act
const fileName = generateFileNamePartsForTesting({ date });
// assert
expect(fileName.timestamp).to.equal(expectedTimestamp, formatAssertionMessage[
`Generated file name: ${fileName.generatedFileName}`
]);
});
describe('generates correct extension', () => {
const testScenarios: Record<SupportedOperatingSystem, string> = {
[OperatingSystem.Windows]: 'bat',
[OperatingSystem.Linux]: 'sh',
[OperatingSystem.macOS]: 'sh',
};
AllSupportedOperatingSystems.forEach((operatingSystem) => {
it(`on ${OperatingSystem[operatingSystem]}`, () => {
// arrange
const expectedExtension = testScenarios[operatingSystem];
// act
const fileName = generateFileNamePartsForTesting({ operatingSystem });
// assert
expect(fileName.extension).to.equal(expectedExtension, formatAssertionMessage[
`Generated file name: ${fileName.generatedFileName}`
]);
});
});
});
it('generates filename without extension for unknown OS', () => {
// arrange
const unknownOs = 'Unknown' as unknown as OperatingSystem;
// act
const fileName = generateFileNamePartsForTesting({ operatingSystem: unknownOs });
// assert
expect(fileName.extension).toBeUndefined();
});
});
});
interface TestFileNameComponents {
readonly prefix: string;
readonly timestamp: string;
readonly extension?: string;
readonly generatedFileName: string;
}
function generateFileNamePartsForTesting(testScenario?: {
operatingSystem?: OperatingSystem,
date?: Date,
}): TestFileNameComponents {
const operatingSystem = testScenario?.operatingSystem ?? OperatingSystem.Windows;
const date = testScenario?.date ?? new Date();
const fileName = generateOsTimestampedFileName(operatingSystem, date);
const pattern = /^(?<prefix>[^-]+)-(?<timestamp>[^.]+)(?:\.(?<extension>[^.]+))?$/; // prefix-timestamp.extension
const match = fileName.match(pattern);
if (!match?.groups?.prefix || !match?.groups?.timestamp) {
throw new Error(`Failed to parse prefix or timestamp: ${fileName}`);
}
return {
generatedFileName: fileName,
prefix: match.groups.prefix,
timestamp: match.groups.timestamp,
extension: match.groups.extension,
};
}

View File

@@ -1,7 +1,7 @@
import { describe, it, expect } from 'vitest';
import { OperatingSystem } from '@/domain/OperatingSystem';
import { FileSystemOps, SystemOperations } from '@/infrastructure/CodeRunner/SystemOperations/SystemOperations';
import { TemporaryFileCodeRunner } from '@/infrastructure/CodeRunner/TemporaryFileCodeRunner';
import { FileNameGenerator, TemporaryFileCodeRunner } from '@/infrastructure/CodeRunner/TemporaryFileCodeRunner';
import { expectThrowsAsync } from '@tests/shared/Assertions/ExpectThrowsAsync';
import { SystemOperationsStub } from '@tests/unit/shared/Stubs/SystemOperationsStub';
import { OperatingSystemOpsStub } from '@tests/unit/shared/Stubs/OperatingSystemOpsStub';
@@ -9,178 +9,254 @@ import { LocationOpsStub } from '@tests/unit/shared/Stubs/LocationOpsStub';
import { FileSystemOpsStub } from '@tests/unit/shared/Stubs/FileSystemOpsStub';
import { CommandOpsStub } from '@tests/unit/shared/Stubs/CommandOpsStub';
import { FunctionKeys } from '@/TypeHelpers';
import { AllSupportedOperatingSystems, SupportedOperatingSystem } from '@tests/shared/TestCases/SupportedOperatingSystems';
import { formatAssertionMessage } from '@tests/shared/FormatAssertionMessage';
import { LoggerStub } from '@tests/unit/shared/Stubs/LoggerStub';
import { Logger } from '@/application/Common/Log/Logger';
describe('TemporaryFileCodeRunner', () => {
describe('runCode', () => {
it('creates temporary directory recursively', async () => {
// arrange
const expectedDir = 'expected-dir';
const expectedIsRecursive = true;
describe('directory creation', () => {
it('creates temporary directory recursively', async () => {
// arrange
const expectedDir = 'expected-dir';
const expectedIsRecursive = true;
const folderName = 'privacy.sexy';
const temporaryDirName = 'tmp';
const filesystem = new FileSystemOpsStub();
const context = new TestContext()
.withSystemOperationsStub((ops) => ops
.withOperatingSystem(
new OperatingSystemOpsStub()
.withTemporaryDirectoryResult(temporaryDirName),
)
.withLocation(
new LocationOpsStub()
.withJoinResult(expectedDir, temporaryDirName, folderName),
)
.withFileSystem(filesystem));
const folderName = 'privacy.sexy';
const temporaryDirName = 'tmp';
const filesystem = new FileSystemOpsStub();
const context = new CodeRunnerTestSetup()
.withSystemOperationsStub((ops) => ops
.withOperatingSystem(
new OperatingSystemOpsStub()
.withTemporaryDirectoryResult(temporaryDirName),
)
.withLocation(
new LocationOpsStub()
.withJoinResult(expectedDir, temporaryDirName, folderName),
)
.withFileSystem(filesystem));
// act
await context
.withFolderName(folderName)
.runCode();
// act
await context
.withFolderName(folderName)
.runCode();
// assert
const calls = filesystem.callHistory.filter((call) => call.methodName === 'createDirectory');
expect(calls.length).to.equal(1);
const [actualPath, actualIsRecursive] = calls[0].args;
expect(actualPath).to.equal(expectedDir);
expect(actualIsRecursive).to.equal(expectedIsRecursive);
// assert
const calls = filesystem.callHistory.filter((call) => call.methodName === 'createDirectory');
expect(calls.length).to.equal(1);
const [actualPath, actualIsRecursive] = calls[0].args;
expect(actualPath).to.equal(expectedDir);
expect(actualIsRecursive).to.equal(expectedIsRecursive);
});
});
it('creates a file with expected code and path', async () => {
// arrange
const expectedCode = 'expected-code';
const expectedFilePath = 'expected-file-path';
describe('file creation', () => {
it('creates a file with expected code', async () => {
// arrange
const expectedCode = 'expected-code';
const filesystem = new FileSystemOpsStub();
const context = new CodeRunnerTestSetup()
.withSystemOperationsStub((ops) => ops
.withFileSystem(filesystem));
// act
await context
.withCode(expectedCode)
.runCode();
const filesystem = new FileSystemOpsStub();
const extension = '.sh';
const expectedName = `run.${extension}`;
const folderName = 'privacy.sexy';
const temporaryDirName = 'tmp';
const context = new TestContext()
.withSystemOperationsStub((ops) => ops
.withOperatingSystem(
new OperatingSystemOpsStub()
.withTemporaryDirectoryResult(temporaryDirName),
)
.withLocation(
new LocationOpsStub()
.withJoinResult('folder', temporaryDirName, folderName)
.withJoinResult(expectedFilePath, 'folder', expectedName),
)
.withFileSystem(filesystem));
// assert
const calls = filesystem.callHistory.filter((call) => call.methodName === 'writeToFile');
expect(calls.length).to.equal(1);
const [, actualData] = calls[0].args;
expect(actualData).to.equal(expectedCode);
});
it('generates file name for correct operating system', async () => {
// arrange
const expectedOperatingSystem = OperatingSystem.macOS;
const calls = new Array<OperatingSystem>();
const fileNameGenerator: FileNameGenerator = (operatingSystem) => {
calls.push(operatingSystem);
return 'unimportant file name';
};
const context = new CodeRunnerTestSetup()
.withOs(expectedOperatingSystem)
.withFileNameGenerator(fileNameGenerator);
// act
await context
.withCode(expectedCode)
.withFolderName(folderName)
.withExtension(extension)
.runCode();
// act
await context.runCode();
// assert
const calls = filesystem.callHistory.filter((call) => call.methodName === 'writeToFile');
expect(calls.length).to.equal(1);
const [actualFilePath, actualData] = calls[0].args;
expect(actualFilePath).to.equal(expectedFilePath);
expect(actualData).to.equal(expectedCode);
// assert
expect(calls).to.have.lengthOf(1);
const [actualOperatingSystem] = calls;
expect(actualOperatingSystem).to.equal(expectedOperatingSystem);
});
it('creates file in expected directory', async () => {
// arrange
const pathSegmentSeparator = '/PATH-SEGMENT-SEPARATOR/';
const temporaryDirName = '/tmp';
const folderName = 'privacy.sexy';
const expectedDirectory = [temporaryDirName, folderName].join(pathSegmentSeparator);
const filesystem = new FileSystemOpsStub();
const context = new CodeRunnerTestSetup()
.withSystemOperationsStub((ops) => ops
.withOperatingSystem(
new OperatingSystemOpsStub()
.withTemporaryDirectoryResult(temporaryDirName),
)
.withLocation(
new LocationOpsStub().withDefaultSeparator(pathSegmentSeparator),
)
.withFileSystem(filesystem));
// act
await context
.withFolderName(folderName)
.runCode();
// assert
const calls = filesystem.callHistory.filter((call) => call.methodName === 'writeToFile');
expect(calls.length).to.equal(1);
const [actualFilePath] = calls[0].args;
const actualDirectory = actualFilePath
.split(pathSegmentSeparator)
.slice(0, -1)
.join(pathSegmentSeparator);
expect(actualDirectory).to.equal(expectedDirectory, formatAssertionMessage([
`Actual file path: ${actualFilePath}`,
]));
});
it('creates file with expected file name', async () => {
// arrange
const pathSegmentSeparator = '/PATH-SEGMENT-SEPARATOR/';
const filesystem = new FileSystemOpsStub();
const expectedFileName = 'expected-script-file-name';
const context = new CodeRunnerTestSetup()
.withFileNameGenerator(() => expectedFileName)
.withSystemOperationsStub((ops) => ops
.withFileSystem(filesystem)
.withLocation(new LocationOpsStub().withDefaultSeparator(pathSegmentSeparator)));
// act
await context.runCode();
// assert
const calls = filesystem.callHistory.filter((call) => call.methodName === 'writeToFile');
expect(calls.length).to.equal(1);
const [actualFilePath] = calls[0].args;
const actualFileName = actualFilePath
.split(pathSegmentSeparator)
.pop();
expect(actualFileName).to.equal(actualFileName, formatAssertionMessage([
`Actual file path: ${actualFilePath}`,
]));
});
});
it('set file permissions as expected', async () => {
// arrange
const expectedMode = '755';
const expectedFilePath = 'expected-file-path';
describe('file permissions', () => {
it('sets correct permissions', async () => {
// arrange
const expectedMode = '755';
const filesystem = new FileSystemOpsStub();
const context = new CodeRunnerTestSetup()
.withSystemOperationsStub((ops) => ops.withFileSystem(filesystem));
const filesystem = new FileSystemOpsStub();
const extension = '.sh';
const expectedName = `run.${extension}`;
const folderName = 'privacy.sexy';
const temporaryDirName = 'tmp';
const context = new TestContext()
.withSystemOperationsStub((ops) => ops
.withOperatingSystem(
new OperatingSystemOpsStub()
.withTemporaryDirectoryResult(temporaryDirName),
)
.withLocation(
new LocationOpsStub()
.withJoinResult('folder', temporaryDirName, folderName)
.withJoinResult(expectedFilePath, 'folder', expectedName),
)
.withFileSystem(filesystem));
// act
await context.runCode();
// act
await context
.withFolderName(folderName)
.withExtension(extension)
.runCode();
// 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('sets correct permissions on correct file', async () => {
// arrange
const expectedFilePath = 'expected-file-path';
const filesystem = new FileSystemOpsStub();
const extension = '.sh';
const expectedName = `run.${extension}`;
const folderName = 'privacy.sexy';
const temporaryDirName = 'tmp';
const context = new CodeRunnerTestSetup()
.withSystemOperationsStub((ops) => ops
.withOperatingSystem(
new OperatingSystemOpsStub()
.withTemporaryDirectoryResult(temporaryDirName),
)
.withLocation(
new LocationOpsStub()
.withJoinResult('folder', temporaryDirName, folderName)
.withJoinResult(expectedFilePath, 'folder', expectedName),
)
.withFileSystem(filesystem));
// assert
const calls = filesystem.callHistory.filter((call) => call.methodName === 'setFilePermissions');
expect(calls.length).to.equal(1);
const [actualFilePath, actualMode] = calls[0].args;
expect(actualFilePath).to.equal(expectedFilePath);
expect(actualMode).to.equal(expectedMode);
// act
await context
.withFolderName(folderName)
.withFileNameGenerator(() => expectedName)
.runCode();
// 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);
});
});
describe('executes as expected', () => {
// arrange
const filePath = 'expected-file-path';
interface ExecutionTestCase {
readonly givenOs: OperatingSystem;
readonly expectedCommand: string;
}
const testData: readonly ExecutionTestCase[] = [
{
givenOs: OperatingSystem.Windows,
expectedCommand: filePath,
},
{
givenOs: OperatingSystem.macOS,
expectedCommand: `open -a Terminal.app ${filePath}`,
},
{
givenOs: OperatingSystem.Linux,
expectedCommand: `x-terminal-emulator -e '${filePath}'`,
},
];
for (const { givenOs, expectedCommand } of testData) {
it(`returns ${expectedCommand} on ${OperatingSystem[givenOs]}`, async () => {
const command = new CommandOpsStub();
const context = new TestContext()
.withSystemOperationsStub((ops) => ops
.withLocation(
new LocationOpsStub()
.withJoinResultSequence('non-important-folder-name', filePath),
)
.withCommand(command));
describe('file execution', () => {
describe('executes correct command', () => {
// arrange
const filePath = 'executed-file-path';
const testScenarios: Record<SupportedOperatingSystem, string> = {
[OperatingSystem.Windows]: filePath,
[OperatingSystem.macOS]: `open -a Terminal.app ${filePath}`,
[OperatingSystem.Linux]: `x-terminal-emulator -e '${filePath}'`,
};
AllSupportedOperatingSystems.forEach((operatingSystem) => {
it(`returns correctly for ${OperatingSystem[operatingSystem]}`, async () => {
// arrange
const expectedCommand = testScenarios[operatingSystem];
const command = new CommandOpsStub();
const context = new CodeRunnerTestSetup()
.withFileNameGenerator(() => filePath)
.withSystemOperationsStub((ops) => ops
.withLocation(
new LocationOpsStub()
.withJoinResultSequence('non-important-folder-name', filePath),
)
.withCommand(command));
// act
await context
.withOs(givenOs)
.runCode();
// act
await context
.withOs(operatingSystem)
.runCode();
// assert
const calls = command.callHistory.filter((c) => c.methodName === 'execute');
expect(calls.length).to.equal(1);
const [actualCommand] = calls[0].args;
expect(actualCommand).to.equal(expectedCommand);
// assert
const calls = command.callHistory.filter((c) => c.methodName === 'execute');
expect(calls.length).to.equal(1);
const [actualCommand] = calls[0].args;
expect(actualCommand).to.equal(expectedCommand);
});
});
}
});
it('runs in expected order', async () => { // verifies correct `async`, `await` usage.
const expectedOrder: readonly FunctionKeys<FileSystemOps>[] = [
'createDirectory',
'writeToFile',
'setFilePermissions',
];
const fileSystem = new FileSystemOpsStub();
const context = new TestContext()
.withSystemOperationsStub((ops) => ops
.withFileSystem(fileSystem));
});
it('runs in expected order', async () => { // verifies correct `async`, `await` usage.
const expectedOrder: readonly FunctionKeys<FileSystemOps>[] = [
'createDirectory',
'writeToFile',
'setFilePermissions',
];
const fileSystem = new FileSystemOpsStub();
const context = new CodeRunnerTestSetup()
.withSystemOperationsStub((ops) => ops
.withFileSystem(fileSystem));
// act
await context.runCode();
// act
await context.runCode();
// assert
const actualOrder = fileSystem.callHistory
.map((c) => c.methodName)
.filter((command) => expectedOrder.includes(command));
expect(expectedOrder).to.deep.equal(actualOrder);
// assert
const actualOrder = fileSystem.callHistory
.map((c) => c.methodName)
.filter((command) => expectedOrder.includes(command));
expect(expectedOrder).to.deep.equal(actualOrder);
});
});
describe('throws with invalid OS', () => {
const testScenarios: ReadonlyArray<{
@@ -200,7 +276,7 @@ describe('TemporaryFileCodeRunner', () => {
testScenarios.forEach(({ description, invalidOs, expectedError }) => {
it(description, async () => {
// arrange
const context = new TestContext()
const context = new CodeRunnerTestSetup()
.withOs(invalidOs);
// act
const act = async () => { await context.runCode(); };
@@ -212,20 +288,26 @@ describe('TemporaryFileCodeRunner', () => {
});
});
class TestContext {
private code = 'code';
class CodeRunnerTestSetup {
private code = `[${CodeRunnerTestSetup.name}]code`;
private folderName = 'folderName';
private folderName = `[${CodeRunnerTestSetup.name}]folderName`;
private fileExtension = 'fileExtension';
private fileNameGenerator: FileNameGenerator = () => `[${CodeRunnerTestSetup.name}]file-name-stub`;
private os: OperatingSystem = OperatingSystem.Windows;
private systemOperations: SystemOperations = new SystemOperationsStub();
private logger: Logger = new LoggerStub();
public async runCode(): Promise<void> {
const runner = new TemporaryFileCodeRunner(this.systemOperations);
await runner.runCode(this.code, this.folderName, this.fileExtension, this.os);
const runner = new TemporaryFileCodeRunner(
this.systemOperations,
this.fileNameGenerator,
this.logger,
);
await runner.runCode(this.code, this.folderName, this.os);
}
public withSystemOperations(
@@ -257,8 +339,8 @@ class TestContext {
return this;
}
public withExtension(fileExtension: string): this {
this.fileExtension = fileExtension;
public withFileNameGenerator(fileNameGenerator: FileNameGenerator): this {
this.fileNameGenerator = fileNameGenerator;
return this;
}
}

View File

@@ -3,15 +3,15 @@ import { OperatingSystem } from '@/domain/OperatingSystem';
import { getInstructions } from '@/presentation/components/Code/CodeButtons/Save/Instructions/InstructionListDataFactory';
import { getEnumValues } from '@/application/Common/Enum';
import { InstructionsBuilder } from '@/presentation/components/Code/CodeButtons/Save/Instructions/Data/InstructionsBuilder';
import { AllSupportedOperatingSystems } from '@tests/shared/TestCases/SupportedOperatingSystems';
describe('InstructionListDataFactory', () => {
const supportedOsList = [OperatingSystem.macOS, OperatingSystem.Linux];
describe('getInstructions', () => {
it('returns expected if os is supported', () => {
// arrange
const fileName = 'test.file';
// act
const actualResults = supportedOsList.map((os) => getInstructions(os, fileName));
const actualResults = AllSupportedOperatingSystems.map((os) => getInstructions(os, fileName));
// assert
expect(actualResults.every((result) => result instanceof InstructionsBuilder));
});
@@ -20,7 +20,7 @@ describe('InstructionListDataFactory', () => {
const expected = undefined;
const fileName = 'test.file';
const unsupportedOses = getEnumValues(OperatingSystem)
.filter((value) => !supportedOsList.includes(value));
.filter((value) => !AllSupportedOperatingSystems.includes(value));
// act
const actualResults = unsupportedOses.map((os) => getInstructions(os, fileName));
// assert

View File

@@ -8,6 +8,8 @@ export class LocationOpsStub
private scenarios = new Map<string, string>();
private defaultSeparator = `/[${LocationOpsStub.name}]PATH-SEGMENT-SEPARATOR/`;
public withJoinResult(returnValue: string, ...paths: string[]): this {
this.scenarios.set(LocationOpsStub.getScenarioKey(paths), returnValue);
return this;
@@ -19,6 +21,11 @@ export class LocationOpsStub
return this;
}
public withDefaultSeparator(defaultSeparator: string): this {
this.defaultSeparator = defaultSeparator;
return this;
}
public combinePaths(...pathSegments: string[]): string {
this.registerMethodCall({
methodName: 'combinePaths',
@@ -33,7 +40,7 @@ export class LocationOpsStub
if (foundScenario) {
return foundScenario;
}
return pathSegments.join('/PATH-SEGMENT-SEPARATOR/');
return pathSegments.join(this.defaultSeparator);
}
private static getScenarioKey(paths: string[]): string {