Files
privacy.sexy/tests/unit/application/Parser/ApplicationParser.spec.ts
undergroundwires 6ecfa9b954 Add object property validation in parser #369
This commit introduces stricter type validation across the application
to reject objects with unexpected properties, enhancing the robustness
and predictability of data handling.

Changes include:

- Implement a common utility to validate object types.
- Refactor across various parsers and data handlers to utilize the new
  validations.
- Update error messages for better clarity and troubleshooting.
2024-06-13 22:26:57 +02:00

198 lines
7.7 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import type { CollectionData } from '@/application/collections/';
import { parseProjectDetails } from '@/application/Parser/ProjectDetailsParser';
import { parseApplication } from '@/application/Parser/ApplicationParser';
import WindowsData from '@/application/collections/windows.yaml';
import MacOsData from '@/application/collections/macos.yaml';
import LinuxData from '@/application/collections/linux.yaml';
import { OperatingSystem } from '@/domain/OperatingSystem';
import { CategoryCollectionStub } from '@tests/unit/shared/Stubs/CategoryCollectionStub';
import { CollectionDataStub } from '@tests/unit/shared/Stubs/CollectionDataStub';
import { CategoryCollectionParserStub } from '@tests/unit/shared/Stubs/CategoryCollectionParserStub';
import { ProjectDetailsParserStub } from '@tests/unit/shared/Stubs/ProjectDetailsParserStub';
import { ProjectDetailsStub } from '@tests/unit/shared/Stubs/ProjectDetailsStub';
import type { CategoryCollectionParser } from '@/application/Parser/CategoryCollectionParser';
import type { NonEmptyCollectionAssertion, TypeValidator } from '@/application/Parser/Common/TypeValidator';
import { TypeValidatorStub } from '@tests/unit/shared/Stubs/TypeValidatorStub';
import type { ICategoryCollection } from '@/domain/ICategoryCollection';
describe('ApplicationParser', () => {
describe('parseApplication', () => {
describe('categoryParser', () => {
it('returns result from the parser', () => {
// arrange
const os = OperatingSystem.macOS;
const data = new CollectionDataStub();
const expected = new CategoryCollectionStub()
.withOs(os);
const parser = new CategoryCollectionParserStub()
.withReturnValue(data, expected)
.getStub();
const sut = new ApplicationParserBuilder()
.withCategoryCollectionParser(parser)
.withCollectionsData([data]);
// act
const app = sut.parseApplication();
// assert
const actual = app.getCollection(os);
expect(expected).to.equal(actual);
});
});
describe('projectDetails', () => {
it('projectDetailsParser is used to create the instance', () => {
// arrange
const expectedProjectDetails = new ProjectDetailsStub();
const projectDetailsParserStub = new ProjectDetailsParserStub()
.withReturnValue(expectedProjectDetails);
const sut = new ApplicationParserBuilder()
.withProjectDetailsParser(projectDetailsParserStub.getStub());
// act
const app = sut.parseApplication();
// assert
const actualProjectDetails = app.projectDetails;
expect(expectedProjectDetails).to.deep.equal(actualProjectDetails);
});
it('projectDetailsParser is used to parse collection', () => {
// arrange
const expectedProjectDetails = new ProjectDetailsStub();
const projectDetailsParserStub = new ProjectDetailsParserStub()
.withReturnValue(expectedProjectDetails);
const collectionParserStub = new CategoryCollectionParserStub();
const sut = new ApplicationParserBuilder()
.withProjectDetailsParser(projectDetailsParserStub.getStub())
.withCategoryCollectionParser(collectionParserStub.getStub());
// act
sut.parseApplication();
// assert
expect(collectionParserStub.arguments).to.have.length.above(0);
const actuallyUsedInfos = collectionParserStub.arguments.map((arg) => arg.projectDetails);
expect(actuallyUsedInfos.every(
(actualProjectDetails) => actualProjectDetails === expectedProjectDetails,
)).to.equal(true);
});
});
describe('collectionsData', () => {
describe('set as expected', () => {
// arrange
const testScenarios: {
readonly description: string;
readonly input: readonly CollectionData[];
readonly output: readonly ICategoryCollection[];
}[] = [
{
description: 'single collection',
input: [new CollectionDataStub()],
output: [new CategoryCollectionStub().withOs(OperatingSystem.macOS)],
},
{
description: 'multiple collections',
input: [
new CollectionDataStub().withOs('windows'),
new CollectionDataStub().withOs('macos'),
],
output: [
new CategoryCollectionStub().withOs(OperatingSystem.macOS),
new CategoryCollectionStub().withOs(OperatingSystem.Windows),
],
},
];
// act
testScenarios.forEach(({
description, input, output,
}) => {
it(description, () => {
let categoryParserStub = new CategoryCollectionParserStub();
for (let i = 0; i < input.length; i++) {
categoryParserStub = categoryParserStub
.withReturnValue(input[i], output[i]);
}
const sut = new ApplicationParserBuilder()
.withCategoryCollectionParser(categoryParserStub.getStub())
.withCollectionsData(input);
// act
const app = sut.parseApplication();
// assert
expect(app.collections).to.deep.equal(output);
});
});
});
it('defaults to expected data', () => {
// arrange
const expected = [WindowsData, MacOsData, LinuxData];
const categoryParserStub = new CategoryCollectionParserStub();
const sut = new ApplicationParserBuilder()
.withCollectionsData(undefined)
.withCategoryCollectionParser(categoryParserStub.getStub());
// act
sut.parseApplication();
// assert
const actual = categoryParserStub.arguments.map((args) => args.data);
expect(actual).to.deep.equal(expected);
});
it('validates non empty array', () => {
// arrange
const data = [new CollectionDataStub()];
const expectedAssertion: NonEmptyCollectionAssertion = {
value: data,
valueName: 'collections',
};
const validator = new TypeValidatorStub();
const sut = new ApplicationParserBuilder()
.withCollectionsData(data)
.withTypeValidator(validator);
// act
sut.parseApplication();
// assert
validator.expectNonEmptyCollectionAssertion(expectedAssertion);
});
});
});
});
class ApplicationParserBuilder {
private categoryCollectionParser
: CategoryCollectionParser = new CategoryCollectionParserStub().getStub();
private projectDetailsParser
: typeof parseProjectDetails = new ProjectDetailsParserStub().getStub();
private validator: TypeValidator = new TypeValidatorStub();
private collectionsData: readonly CollectionData[] | undefined = [new CollectionDataStub()];
public withCategoryCollectionParser(
categoryCollectionParser: CategoryCollectionParser,
): this {
this.categoryCollectionParser = categoryCollectionParser;
return this;
}
public withProjectDetailsParser(
projectDetailsParser: typeof parseProjectDetails,
): this {
this.projectDetailsParser = projectDetailsParser;
return this;
}
public withCollectionsData(collectionsData: readonly CollectionData[] | undefined): this {
this.collectionsData = collectionsData;
return this;
}
public withTypeValidator(validator: TypeValidator): this {
this.validator = validator;
return this;
}
public parseApplication(): ReturnType<typeof parseApplication> {
return parseApplication(
this.collectionsData,
{
parseCategoryCollection: this.categoryCollectionParser,
parseProjectDetails: this.projectDetailsParser,
validator: this.validator,
},
);
}
}