Add type validation for parameters and fix types
This commit introduces type validation for parameter values within the parser/compiler, aligning with the YAML schema. It aims to eliminate dependencies on side effects in the collection files. This update changes the treatment of data types in the Windows collection, moving away from unintended type casting by the compiler. Previously, numeric and boolean values were used even though only string types were supported. This behavior was unstable and untested, and has now been adjusted to use strings exclusively. Changes ensure that parameter values are correctly validated as strings, enhancing stability and maintainability.
This commit is contained in:
@@ -1,52 +1,110 @@
|
||||
import { describe, expect } from 'vitest';
|
||||
import { FunctionCallArgument } from '@/application/Parser/Executable/Script/Compiler/Function/Call/Argument/FunctionCallArgument';
|
||||
import { itEachAbsentStringValue } from '@tests/unit/shared/TestCases/AbsentTests';
|
||||
import { testParameterName } from '../../../ParameterNameTestRunner';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { createFunctionCallArgument } from '@/application/Parser/Executable/Script/Compiler/Function/Call/Argument/FunctionCallArgument';
|
||||
import { TypeValidatorStub } from '@tests/unit/shared/Stubs/TypeValidatorStub';
|
||||
import type { NonEmptyStringAssertion, TypeValidator } from '@/application/Parser/Common/TypeValidator';
|
||||
import { createParameterNameValidatorStub } from '@tests/unit/shared/Stubs/ParameterNameValidatorStub';
|
||||
import type { ParameterNameValidator } from '@/application/Parser/Executable/Script/Compiler/Function/Shared/ParameterNameValidator';
|
||||
|
||||
describe('FunctionCallArgument', () => {
|
||||
describe('ctor', () => {
|
||||
describe('createFunctionCallArgument', () => {
|
||||
describe('parameter name', () => {
|
||||
testParameterName(
|
||||
(parameterName) => new FunctionCallArgumentBuilder()
|
||||
.withParameterName(parameterName)
|
||||
.build()
|
||||
.parameterName,
|
||||
);
|
||||
});
|
||||
describe('throws if argument value is absent', () => {
|
||||
itEachAbsentStringValue((absentValue) => {
|
||||
it('assigns correctly', () => {
|
||||
// arrange
|
||||
const parameterName = 'paramName';
|
||||
const expectedError = `Missing argument value for the parameter "${parameterName}".`;
|
||||
const argumentValue = absentValue;
|
||||
const expectedName = 'expected parameter name';
|
||||
const context = new TestContext()
|
||||
.withParameterName(expectedName);
|
||||
// act
|
||||
const act = () => new FunctionCallArgumentBuilder()
|
||||
.withParameterName(parameterName)
|
||||
.withArgumentValue(argumentValue)
|
||||
.build();
|
||||
const actualArgument = context.create();
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
}, { excludeNull: true, excludeUndefined: true });
|
||||
const actualName = actualArgument.parameterName;
|
||||
expect(actualName).toEqual(expectedName);
|
||||
});
|
||||
it('validates parameter name', () => {
|
||||
// arrange
|
||||
const validator = createParameterNameValidatorStub();
|
||||
const expectedParameterName = 'parameter name expected to be validated';
|
||||
const context = new TestContext()
|
||||
.withParameterName(expectedParameterName)
|
||||
.withParameterNameValidator(validator.validator);
|
||||
// act
|
||||
context.create();
|
||||
// assert
|
||||
expect(validator.validatedNames).to.have.lengthOf(1);
|
||||
expect(validator.validatedNames).to.include(expectedParameterName);
|
||||
});
|
||||
});
|
||||
describe('argument value', () => {
|
||||
it('assigns correctly', () => {
|
||||
// arrange
|
||||
const expectedValue = 'expected argument value';
|
||||
const context = new TestContext()
|
||||
.withArgumentValue(expectedValue);
|
||||
// act
|
||||
const actualArgument = context.create();
|
||||
// assert
|
||||
const actualValue = actualArgument.argumentValue;
|
||||
expect(actualValue).toEqual(expectedValue);
|
||||
});
|
||||
it('validates argument value', () => {
|
||||
// arrange
|
||||
const parameterNameInError = 'expected parameter with argument error';
|
||||
const expectedArgumentValue = 'argument value to be validated';
|
||||
const expectedAssertion: NonEmptyStringAssertion = {
|
||||
value: expectedArgumentValue,
|
||||
valueName: `Missing argument value for the parameter "${parameterNameInError}".`,
|
||||
};
|
||||
const typeValidator = new TypeValidatorStub();
|
||||
const context = new TestContext()
|
||||
.withArgumentValue(expectedArgumentValue)
|
||||
.withParameterName(parameterNameInError)
|
||||
.withTypeValidator(typeValidator);
|
||||
// act
|
||||
context.create();
|
||||
// assert
|
||||
typeValidator.assertNonEmptyString(expectedAssertion);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
class FunctionCallArgumentBuilder {
|
||||
private parameterName = 'default-parameter-name';
|
||||
class TestContext {
|
||||
private parameterName = `[${TestContext.name}] default-parameter-name`;
|
||||
|
||||
private argumentValue = 'default-argument-value';
|
||||
private argumentValue = `[${TestContext.name}] default-argument-value`;
|
||||
|
||||
public withParameterName(parameterName: string) {
|
||||
private typeValidator: TypeValidator = new TypeValidatorStub();
|
||||
|
||||
private parameterNameValidator
|
||||
: ParameterNameValidator = createParameterNameValidatorStub().validator;
|
||||
|
||||
public withParameterName(parameterName: string): this {
|
||||
this.parameterName = parameterName;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withArgumentValue(argumentValue: string) {
|
||||
public withArgumentValue(argumentValue: string): this {
|
||||
this.argumentValue = argumentValue;
|
||||
return this;
|
||||
}
|
||||
|
||||
public build() {
|
||||
return new FunctionCallArgument(this.parameterName, this.argumentValue);
|
||||
public withTypeValidator(typeValidator: TypeValidator): this {
|
||||
this.typeValidator = typeValidator;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withParameterNameValidator(parameterNameValidator: ParameterNameValidator): this {
|
||||
this.parameterNameValidator = parameterNameValidator;
|
||||
return this;
|
||||
}
|
||||
|
||||
public create(): ReturnType<typeof createFunctionCallArgument> {
|
||||
return createFunctionCallArgument(
|
||||
this.parameterName,
|
||||
this.argumentValue,
|
||||
{
|
||||
typeValidator: this.typeValidator,
|
||||
validateParameterName: this.parameterNameValidator,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { describe, it, expect } from 'vitest';
|
||||
import { FunctionCallArgumentCollection } from '@/application/Parser/Executable/Script/Compiler/Function/Call/Argument/FunctionCallArgumentCollection';
|
||||
import { FunctionCallArgumentStub } from '@tests/unit/shared/Stubs/FunctionCallArgumentStub';
|
||||
import { itEachAbsentStringValue } from '@tests/unit/shared/TestCases/AbsentTests';
|
||||
import type { IFunctionCallArgument } from '@/application/Parser/Executable/Script/Compiler/Function/Call/Argument/IFunctionCallArgument';
|
||||
import type { FunctionCallArgument } from '@/application/Parser/Executable/Script/Compiler/Function/Call/Argument/FunctionCallArgument';
|
||||
|
||||
describe('FunctionCallArgumentCollection', () => {
|
||||
describe('addArgument', () => {
|
||||
@@ -25,7 +25,7 @@ describe('FunctionCallArgumentCollection', () => {
|
||||
// arrange
|
||||
const testCases: ReadonlyArray<{
|
||||
readonly description: string;
|
||||
readonly args: readonly IFunctionCallArgument[];
|
||||
readonly args: readonly FunctionCallArgument[];
|
||||
readonly expectedParameterNames: string[];
|
||||
}> = [{
|
||||
description: 'no args',
|
||||
|
||||
@@ -14,6 +14,8 @@ import { SharedFunctionCollectionStub } from '@tests/unit/shared/Stubs/SharedFun
|
||||
import { itThrowsContextualError } from '@tests/unit/application/Parser/Common/ContextualErrorTester';
|
||||
import type { ErrorWithContextWrapper } from '@/application/Parser/Common/ContextualError';
|
||||
import { errorWithContextWrapperStub } from '@tests/unit/shared/Stubs/ErrorWithContextWrapperStub';
|
||||
import type { FunctionCallArgumentFactory } from '@/application/Parser/Executable/Script/Compiler/Function/Call/Argument/FunctionCallArgument';
|
||||
import { FunctionCallArgumentFactoryStub } from '../../../../../../../../../../../shared/Stubs/FunctionCallArgumentFactoryStub';
|
||||
|
||||
describe('NestedFunctionArgumentCompiler', () => {
|
||||
describe('createCompiledNestedCall', () => {
|
||||
@@ -266,6 +268,9 @@ class NestedFunctionArgumentCompilerBuilder implements ArgumentCompiler {
|
||||
|
||||
private wrapError: ErrorWithContextWrapper = errorWithContextWrapperStub;
|
||||
|
||||
private callArgumentFactory
|
||||
: FunctionCallArgumentFactory = new FunctionCallArgumentFactoryStub().factory;
|
||||
|
||||
public withExpressionsCompiler(expressionsCompiler: IExpressionsCompiler): this {
|
||||
this.expressionsCompiler = expressionsCompiler;
|
||||
return this;
|
||||
@@ -292,10 +297,11 @@ class NestedFunctionArgumentCompilerBuilder implements ArgumentCompiler {
|
||||
}
|
||||
|
||||
public createCompiledNestedCall(): FunctionCall {
|
||||
const compiler = new NestedFunctionArgumentCompiler(
|
||||
this.expressionsCompiler,
|
||||
this.wrapError,
|
||||
);
|
||||
const compiler = new NestedFunctionArgumentCompiler({
|
||||
expressionsCompiler: this.expressionsCompiler,
|
||||
wrapError: this.wrapError,
|
||||
createCallArgument: this.callArgumentFactory,
|
||||
});
|
||||
return compiler.createCompiledNestedCall(
|
||||
this.nestedFunctionCall,
|
||||
this.parentFunctionCall,
|
||||
|
||||
@@ -3,8 +3,12 @@ import type { FunctionCallsData, FunctionCallData } from '@/application/collecti
|
||||
import { parseFunctionCalls } from '@/application/Parser/Executable/Script/Compiler/Function/Call/FunctionCallsParser';
|
||||
import { FunctionCallDataStub } from '@tests/unit/shared/Stubs/FunctionCallDataStub';
|
||||
import { itEachAbsentStringValue } from '@tests/unit/shared/TestCases/AbsentTests';
|
||||
import type { NonEmptyCollectionAssertion, ObjectAssertion, TypeValidator } from '@/application/Parser/Common/TypeValidator';
|
||||
import type {
|
||||
NonEmptyCollectionAssertion, ObjectAssertion, TypeValidator,
|
||||
} from '@/application/Parser/Common/TypeValidator';
|
||||
import { TypeValidatorStub } from '@tests/unit/shared/Stubs/TypeValidatorStub';
|
||||
import type { FunctionCallArgumentFactory } from '@/application/Parser/Executable/Script/Compiler/Function/Call/Argument/FunctionCallArgument';
|
||||
import { FunctionCallArgumentFactoryStub } from '@tests/unit/shared/Stubs/FunctionCallArgumentFactoryStub';
|
||||
|
||||
describe('FunctionCallsParser', () => {
|
||||
describe('parseFunctionCalls', () => {
|
||||
@@ -174,12 +178,15 @@ describe('FunctionCallsParser', () => {
|
||||
});
|
||||
|
||||
class TestContext {
|
||||
private validator: TypeValidator = new TypeValidatorStub();
|
||||
private typeValidator: TypeValidator = new TypeValidatorStub();
|
||||
|
||||
private createCallArgument
|
||||
: FunctionCallArgumentFactory = new FunctionCallArgumentFactoryStub().factory;
|
||||
|
||||
private calls: FunctionCallsData = [new FunctionCallDataStub()];
|
||||
|
||||
public withTypeValidator(typeValidator: TypeValidator): this {
|
||||
this.validator = typeValidator;
|
||||
this.typeValidator = typeValidator;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -191,7 +198,10 @@ class TestContext {
|
||||
public parse(): ReturnType<typeof parseFunctionCalls> {
|
||||
return parseFunctionCalls(
|
||||
this.calls,
|
||||
this.validator,
|
||||
{
|
||||
typeValidator: this.typeValidator,
|
||||
createCallArgument: this.createCallArgument,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user