mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-17 02:51:36 -05:00
Fix resource deployment validations (#15963)
* Fix resource deployment validations * comment out unused for now * Fix tests
This commit is contained in:
@@ -12,10 +12,9 @@ import { InputValueType } from '../../../ui/modelViewUtils';
|
|||||||
import { createValidation, GreaterThanOrEqualsValidation, IntegerValidation, LessThanOrEqualsValidation, RegexValidation, validateInputBoxComponent, Validation, ValidationType } from '../../../ui/validation/validations';
|
import { createValidation, GreaterThanOrEqualsValidation, IntegerValidation, LessThanOrEqualsValidation, RegexValidation, validateInputBoxComponent, Validation, ValidationType } from '../../../ui/validation/validations';
|
||||||
|
|
||||||
const inputBox = <azdata.InputBoxComponent>{
|
const inputBox = <azdata.InputBoxComponent>{
|
||||||
updateProperty(key: string, value: any) { }
|
validationErrorMessage: ''
|
||||||
};
|
};
|
||||||
let inputBoxStub: sinon.SinonStub;
|
|
||||||
const validationMessage = 'The field value is not valid';
|
|
||||||
const testValidations = [
|
const testValidations = [
|
||||||
{
|
{
|
||||||
type: ValidationType.IsInteger,
|
type: ValidationType.IsInteger,
|
||||||
@@ -42,7 +41,6 @@ let onValidityChangedEmitter: vscode.EventEmitter<boolean>;
|
|||||||
describe('Validation', () => {
|
describe('Validation', () => {
|
||||||
beforeEach('validation setup', () => {
|
beforeEach('validation setup', () => {
|
||||||
sinon.restore(); //cleanup all previously defined sinon mocks
|
sinon.restore(); //cleanup all previously defined sinon mocks
|
||||||
inputBoxStub = sinon.stub(inputBox, 'updateProperty').resolves();
|
|
||||||
onValidityChangedEmitter = new vscode.EventEmitter<boolean>(); // recreate for every test so that any previous subscriptions on the event are cleared out.
|
onValidityChangedEmitter = new vscode.EventEmitter<boolean>(); // recreate for every test so that any previous subscriptions on the event are cleared out.
|
||||||
});
|
});
|
||||||
describe('createValidation and validate input Box', () => {
|
describe('createValidation and validate input Box', () => {
|
||||||
@@ -50,7 +48,6 @@ describe('Validation', () => {
|
|||||||
it(`validationType: ${testObj.type}`, async () => {
|
it(`validationType: ${testObj.type}`, async () => {
|
||||||
const validation = createValidation(
|
const validation = createValidation(
|
||||||
testObj,
|
testObj,
|
||||||
async (isValid) => (isValid) ? inputBox.updateProperty('validationErrorMessage', undefined) : inputBox.updateProperty('validationErrorMessage', validationMessage),
|
|
||||||
async () => undefined,
|
async () => undefined,
|
||||||
async (_varName: string) => undefined,
|
async (_varName: string) => undefined,
|
||||||
(_variableName) => onValidityChangedEmitter.event,
|
(_variableName) => onValidityChangedEmitter.event,
|
||||||
@@ -62,9 +59,7 @@ describe('Validation', () => {
|
|||||||
case ValidationType.LessThanOrEqualsTo: should(validation).be.instanceOf(LessThanOrEqualsValidation); break;
|
case ValidationType.LessThanOrEqualsTo: should(validation).be.instanceOf(LessThanOrEqualsValidation); break;
|
||||||
case ValidationType.GreaterThanOrEqualsTo: should(validation).be.instanceOf(GreaterThanOrEqualsValidation); break;
|
case ValidationType.GreaterThanOrEqualsTo: should(validation).be.instanceOf(GreaterThanOrEqualsValidation); break;
|
||||||
}
|
}
|
||||||
should(await validateInputBoxComponent(inputBox, [validation])).be.true(); // undefined and '' values are valid so validation should return true. This allows for fields that are not required
|
should(await validateInputBoxComponent(inputBox, [validation])).be.true(`Call to validate should be true`); // undefined and '' values are valid so validation should return true. This allows for fields that are not required
|
||||||
should(inputBoxStub.calledOnce).be.true();
|
|
||||||
should(inputBoxStub.getCall(0).args[1]).be.undefined();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -87,7 +82,6 @@ describe('Validation', () => {
|
|||||||
const validationDescription = `value: ${displayTestValue} was not an integer`;
|
const validationDescription = `value: ${displayTestValue} was not an integer`;
|
||||||
const validation = new IntegerValidation(
|
const validation = new IntegerValidation(
|
||||||
{ type: ValidationType.IsInteger, description: validationDescription },
|
{ type: ValidationType.IsInteger, description: validationDescription },
|
||||||
async (isValid) => (isValid) ? inputBox.updateProperty('validationErrorMessage', undefined) : inputBox.updateProperty('validationErrorMessage', validationMessage),
|
|
||||||
async () => testObj.value
|
async () => testObj.value
|
||||||
);
|
);
|
||||||
await testValidation(validation, testObj, validationDescription);
|
await testValidation(validation, testObj, validationDescription);
|
||||||
@@ -114,7 +108,6 @@ describe('Validation', () => {
|
|||||||
const validationDescription = `value:${displayTestValue} did not match the regex:/${testRegex}/`;
|
const validationDescription = `value:${displayTestValue} did not match the regex:/${testRegex}/`;
|
||||||
const validation = new RegexValidation(
|
const validation = new RegexValidation(
|
||||||
{ type: ValidationType.IsInteger, description: validationDescription, regex: testRegex },
|
{ type: ValidationType.IsInteger, description: validationDescription, regex: testRegex },
|
||||||
async (isValid) => (isValid) ? inputBox.updateProperty('validationErrorMessage', undefined) : inputBox.updateProperty('validationErrorMessage', validationMessage),
|
|
||||||
async () => testOb.value
|
async () => testOb.value
|
||||||
);
|
);
|
||||||
await testValidation(validation, testOb, validationDescription);
|
await testValidation(validation, testOb, validationDescription);
|
||||||
@@ -173,7 +166,6 @@ describe('Validation', () => {
|
|||||||
const validationDescription = `${displayTestValue} did not test as <= ${displayTargetValue}`;
|
const validationDescription = `${displayTestValue} did not test as <= ${displayTargetValue}`;
|
||||||
const validation = new LessThanOrEqualsValidation(
|
const validation = new LessThanOrEqualsValidation(
|
||||||
{ type: ValidationType.IsInteger, description: validationDescription, target: targetVariableName },
|
{ type: ValidationType.IsInteger, description: validationDescription, target: targetVariableName },
|
||||||
async (isValid) => (isValid) ? inputBox.updateProperty('validationErrorMessage', undefined) : inputBox.updateProperty('validationErrorMessage', validationMessage),
|
|
||||||
async () => testObj.value,
|
async () => testObj.value,
|
||||||
async (_variableName: string) => testObj.targetValue,
|
async (_variableName: string) => testObj.targetValue,
|
||||||
(_variableName) => onValidityChangedEmitter.event,
|
(_variableName) => onValidityChangedEmitter.event,
|
||||||
@@ -228,7 +220,6 @@ describe('Validation', () => {
|
|||||||
const validationDescription = `${displayTestValue} did not test as >= ${displayTargetValue}`;
|
const validationDescription = `${displayTestValue} did not test as >= ${displayTargetValue}`;
|
||||||
const validation = new GreaterThanOrEqualsValidation(
|
const validation = new GreaterThanOrEqualsValidation(
|
||||||
{ type: ValidationType.IsInteger, description: validationDescription, target: targetVariableName },
|
{ type: ValidationType.IsInteger, description: validationDescription, target: targetVariableName },
|
||||||
async (isValid) => (isValid) ? inputBox.updateProperty('validationErrorMessage', undefined) : inputBox.updateProperty('validationErrorMessage', validationMessage),
|
|
||||||
async () => testObj.value,
|
async () => testObj.value,
|
||||||
async (_variableName: string) => testObj.targetValue,
|
async (_variableName: string) => testObj.targetValue,
|
||||||
(_variableName) => onValidityChangedEmitter.event,
|
(_variableName) => onValidityChangedEmitter.event,
|
||||||
|
|||||||
@@ -539,13 +539,6 @@ async function processField(context: FieldContext): Promise<void> {
|
|||||||
//populate the fieldValidations objects for each field based on the information from the fieldInfo
|
//populate the fieldValidations objects for each field based on the information from the fieldInfo
|
||||||
context.fieldValidations = context.fieldInfo.validations?.map((validation => createValidation(
|
context.fieldValidations = context.fieldInfo.validations?.map((validation => createValidation(
|
||||||
validation,
|
validation,
|
||||||
async (isValid: boolean) => {
|
|
||||||
const inputBox = (<azdata.InputBoxComponent>context.inputComponents[context.fieldInfo.variableName || context.fieldInfo.label].component);
|
|
||||||
const validationMessage = (isValid) ? '' : validation.description;
|
|
||||||
if (inputBox.validationErrorMessage !== validationMessage) { // unset validationErrorMessage if it is set
|
|
||||||
await inputBox.updateProperty('validationErrorMessage', validationMessage);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
() => context.inputComponents[context.fieldInfo.variableName || context.fieldInfo.label].getValue(), // callback to fetch the value of this field, and return the default value if the field value is undefined
|
() => context.inputComponents[context.fieldInfo.variableName || context.fieldInfo.label].getValue(), // callback to fetch the value of this field, and return the default value if the field value is undefined
|
||||||
(variable: string) => context.inputComponents[variable].getValue(), // callback to fetch the value of a variable corresponding to any field already defined.
|
(variable: string) => context.inputComponents[variable].getValue(), // callback to fetch the value of a variable corresponding to any field already defined.
|
||||||
(targetVariable: string) => (<azdata.InputBoxComponent>context.inputComponents[targetVariable].component).onValidityChanged,
|
(targetVariable: string) => (<azdata.InputBoxComponent>context.inputComponents[targetVariable].component).onValidityChanged,
|
||||||
|
|||||||
@@ -55,9 +55,6 @@ export abstract class Validation {
|
|||||||
get description(): string {
|
get description(): string {
|
||||||
return this._description;
|
return this._description;
|
||||||
}
|
}
|
||||||
protected get onValidation(): OnValidation {
|
|
||||||
return this._onValidation;
|
|
||||||
}
|
|
||||||
// gets the validation result for this validation object
|
// gets the validation result for this validation object
|
||||||
abstract validate(): Promise<ValidationResult>;
|
abstract validate(): Promise<ValidationResult>;
|
||||||
|
|
||||||
@@ -69,15 +66,15 @@ export abstract class Validation {
|
|||||||
return this._targetValueGetter!(variable);
|
return this._targetValueGetter!(variable);
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(validation: ValidationInfo, protected _onValidation: OnValidation, protected _valueGetter: ValueGetter, protected _targetValueGetter?: TargetValueGetter, protected _onTargetValidityChangedGetter?: OnTargetValidityChangedGetter, protected _onNewDisposableCreated?: (disposable: vscode.Disposable) => void) {
|
constructor(validation: ValidationInfo, protected _valueGetter: ValueGetter, protected _targetValueGetter?: TargetValueGetter, protected _onTargetValidityChangedGetter?: OnTargetValidityChangedGetter, protected _onNewDisposableCreated?: (disposable: vscode.Disposable) => void) {
|
||||||
this._description = validation.description;
|
this._description = validation.description;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class IntegerValidation extends Validation {
|
export class IntegerValidation extends Validation {
|
||||||
constructor(validation: IntegerValidationInfo, onValidation: OnValidation, valueGetter: ValueGetter) {
|
constructor(validation: IntegerValidationInfo, valueGetter: ValueGetter) {
|
||||||
super(validation, onValidation, valueGetter);
|
super(validation, valueGetter);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async isIntegerOrEmptyOrUndefined(): Promise<boolean> {
|
private async isIntegerOrEmptyOrUndefined(): Promise<boolean> {
|
||||||
@@ -91,7 +88,6 @@ export class IntegerValidation extends Validation {
|
|||||||
|
|
||||||
async validate(): Promise<ValidationResult> {
|
async validate(): Promise<ValidationResult> {
|
||||||
const isValid = await this.isIntegerOrEmptyOrUndefined();
|
const isValid = await this.isIntegerOrEmptyOrUndefined();
|
||||||
await this.onValidation(isValid);
|
|
||||||
return {
|
return {
|
||||||
valid: isValid,
|
valid: isValid,
|
||||||
message: isValid ? undefined : this.description
|
message: isValid ? undefined : this.description
|
||||||
@@ -106,8 +102,8 @@ export class RegexValidation extends Validation {
|
|||||||
return this._regex;
|
return this._regex;
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(validation: RegexValidationInfo, validationMessageUpdater: OnValidation, valueGetter: ValueGetter) {
|
constructor(validation: RegexValidationInfo, valueGetter: ValueGetter) {
|
||||||
super(validation, validationMessageUpdater, valueGetter);
|
super(validation, valueGetter);
|
||||||
throwUnless(validation.regex !== undefined);
|
throwUnless(validation.regex !== undefined);
|
||||||
this._regex = (typeof validation.regex === 'string') ? new RegExp(validation.regex) : validation.regex;
|
this._regex = (typeof validation.regex === 'string') ? new RegExp(validation.regex) : validation.regex;
|
||||||
}
|
}
|
||||||
@@ -115,7 +111,6 @@ export class RegexValidation extends Validation {
|
|||||||
async validate(): Promise<ValidationResult> {
|
async validate(): Promise<ValidationResult> {
|
||||||
const value = (await this.getValue())?.toString();
|
const value = (await this.getValue())?.toString();
|
||||||
const isValid = isUndefinedOrEmpty(value) ? true : this.regex.test(value!);
|
const isValid = isUndefinedOrEmpty(value) ? true : this.regex.test(value!);
|
||||||
await this.onValidation(isValid);
|
|
||||||
return {
|
return {
|
||||||
valid: isValid,
|
valid: isValid,
|
||||||
message: isValid ? undefined : this.description
|
message: isValid ? undefined : this.description
|
||||||
@@ -136,8 +131,8 @@ export abstract class Comparison extends Validation {
|
|||||||
this._onNewDisposableCreated!(onValidityChanged(isValid => onTargetValidityChangedAction(isValid)));
|
this._onNewDisposableCreated!(onValidityChanged(isValid => onTargetValidityChangedAction(isValid)));
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(validation: ComparisonValidationInfo, onValidation: OnValidation, valueGetter: ValueGetter, targetValueGetter: TargetValueGetter, onTargetValidityChangedGetter: OnTargetValidityChangedGetter, onNewDisposableCreated: (disposable: vscode.Disposable) => void) {
|
constructor(validation: ComparisonValidationInfo, valueGetter: ValueGetter, targetValueGetter: TargetValueGetter, onTargetValidityChangedGetter: OnTargetValidityChangedGetter, onNewDisposableCreated: (disposable: vscode.Disposable) => void) {
|
||||||
super(validation, onValidation, valueGetter, targetValueGetter, onTargetValidityChangedGetter, onNewDisposableCreated);
|
super(validation, valueGetter, targetValueGetter, onTargetValidityChangedGetter, onNewDisposableCreated);
|
||||||
throwUnless(validation.target !== undefined);
|
throwUnless(validation.target !== undefined);
|
||||||
this._target = validation.target;
|
this._target = validation.target;
|
||||||
}
|
}
|
||||||
@@ -157,7 +152,6 @@ export abstract class Comparison extends Validation {
|
|||||||
async validate(): Promise<ValidationResult> {
|
async validate(): Promise<ValidationResult> {
|
||||||
this.validateOnTargetValidityChange();
|
this.validateOnTargetValidityChange();
|
||||||
const isValid = await this.isComparisonSuccessful();
|
const isValid = await this.isComparisonSuccessful();
|
||||||
await this.onValidation(isValid);
|
|
||||||
return {
|
return {
|
||||||
valid: isValid,
|
valid: isValid,
|
||||||
message: isValid ? undefined : this.description
|
message: isValid ? undefined : this.description
|
||||||
@@ -181,22 +175,29 @@ export class GreaterThanOrEqualsValidation extends Comparison {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createValidation(validation: ValidationInfo, onValidation: OnValidation, valueGetter: ValueGetter, targetValueGetter?: TargetValueGetter, onTargetValidityChangedGetter?: OnTargetValidityChangedGetter, onDisposableCreated?: (disposable: vscode.Disposable) => void): Validation {
|
export function createValidation(validation: ValidationInfo, valueGetter: ValueGetter, targetValueGetter?: TargetValueGetter, onTargetValidityChangedGetter?: OnTargetValidityChangedGetter, onDisposableCreated?: (disposable: vscode.Disposable) => void): Validation {
|
||||||
switch (validation.type) {
|
switch (validation.type) {
|
||||||
case ValidationType.Regex: return new RegexValidation(<RegexValidationInfo>validation, onValidation, valueGetter);
|
case ValidationType.Regex: return new RegexValidation(<RegexValidationInfo>validation, valueGetter);
|
||||||
case ValidationType.IsInteger: return new IntegerValidation(<IntegerValidationInfo>validation, onValidation, valueGetter);
|
case ValidationType.IsInteger: return new IntegerValidation(<IntegerValidationInfo>validation, valueGetter);
|
||||||
case ValidationType.LessThanOrEqualsTo: return new LessThanOrEqualsValidation(<ComparisonValidationInfo>validation, onValidation, valueGetter, targetValueGetter!, onTargetValidityChangedGetter!, onDisposableCreated!);
|
case ValidationType.LessThanOrEqualsTo: return new LessThanOrEqualsValidation(<ComparisonValidationInfo>validation, valueGetter, targetValueGetter!, onTargetValidityChangedGetter!, onDisposableCreated!);
|
||||||
case ValidationType.GreaterThanOrEqualsTo: return new GreaterThanOrEqualsValidation(<ComparisonValidationInfo>validation, onValidation, valueGetter, targetValueGetter!, onTargetValidityChangedGetter!, onDisposableCreated!);
|
case ValidationType.GreaterThanOrEqualsTo: return new GreaterThanOrEqualsValidation(<ComparisonValidationInfo>validation, valueGetter, targetValueGetter!, onTargetValidityChangedGetter!, onDisposableCreated!);
|
||||||
default: throw new Error(`unknown validation type:${validation.type}`); //dev error
|
default: throw new Error(`unknown validation type:${validation.type}`); //dev error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function validateInputBoxComponent(component: azdata.InputBoxComponent, validations: Validation[] = []): Promise<boolean> {
|
export async function validateInputBoxComponent(component: azdata.InputBoxComponent, validations: Validation[] = []): Promise<boolean> {
|
||||||
|
let valid = true;
|
||||||
|
let message = '';
|
||||||
for (const validation of validations) {
|
for (const validation of validations) {
|
||||||
const result = await validation.validate();
|
const result = await validation.validate();
|
||||||
if (!result.valid) {
|
if (!result.valid) {
|
||||||
return false; //bail out on first failure, remaining validations are processed after this one has been fixed by the user.
|
valid = false;
|
||||||
|
message = validation.description;
|
||||||
|
break; //bail out on first failure, remaining validations are processed after this one has been fixed by the user.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
if ((component.validationErrorMessage ?? '') !== message) { // Update the message if needed
|
||||||
|
component.validationErrorMessage = message;
|
||||||
|
}
|
||||||
|
return valid;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user