New Validation Module for Resource-Deployment extension. (#12953)

This commit is contained in:
Arvind Ranasaria
2020-10-26 09:50:25 -07:00
committed by GitHub
parent ff45bdd072
commit c810c5a0bb
6 changed files with 502 additions and 3 deletions

View File

@@ -492,7 +492,7 @@
"tags": ["SQL Server", "Cloud"],
"providers": [
{
"azureSQLVMWizard":{
"azureSQLVMWizard": {
"notebook": "./notebooks/azurevm/create-sqlvm.ipynb"
},
"requiredTools": [
@@ -534,11 +534,13 @@
"devDependencies": {
"@types/mocha": "^5.2.5",
"@types/semver": "^7.3.1",
"@types/sinon": "^9.0.8",
"@types/yamljs": "0.2.30",
"mocha": "^5.2.0",
"mocha-junit-reporter": "^1.17.0",
"mocha-multi-reporters": "^1.1.7",
"should": "^13.2.3",
"sinon": "^9.2.0",
"typemoq": "^2.1.0",
"vscodetestcover": "^1.1.0"
}

View File

@@ -38,7 +38,8 @@ export const acceptEulaAndSelect = localize('deploymentDialog.RecheckEulaButton'
export const resourceTypePickerDialogTitle = localize('resourceTypePickerDialog.title', "Select the deployment options");
export const resourceTypeSearchBoxDescription = localize('resourceTypePickerDialog.resourceSearchPlaceholder', "Filter resources...");
export const resoucrceTypeCategoryListViewTitle = localize('resourceTypePickerDialog.tagsListViewTitle', 'Categories');
export const resourceTypeCategoryListViewTitle = localize('resourceTypePickerDialog.tagsListViewTitle', "Categories");
export const multipleValidationErrors = localize("validation.multipleValidationErrors", "There are some errors on this page, click 'Show Details' to view the errors.");
export const scriptToNotebook = localize('ui.ScriptToNotebookButton', "Script");
export const deployNotebook = localize('ui.DeployButton', "Run");

View File

@@ -0,0 +1,223 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import 'mocha';
import * as should from 'should';
import * as sinon from 'sinon';
import { createValidation, GreaterThanOrEqualsValidation, IntegerValidation, LessThanOrEqualsValidation, RegexValidation, validateInputBoxComponent, Validation, ValidationType, ValidationValueType } from '../../../ui/validation/validations';
const inputBox = <azdata.InputBoxComponent>{
updateProperty(key: string, value: any) {}
};
let inputBoxStub: sinon.SinonStub;
const testValidations = [
{
type: ValidationType.IsInteger,
description: 'field was not an integer'
},
{
type: ValidationType.Regex,
description: 'field must contain only alphabetic characters',
regex: '^[a-z]+$'
},
{
type: ValidationType.LessThanOrEqualsTo,
description: 'field value must be <= field2\'s value',
target: 'field2'
},
{
type: ValidationType.GreaterThanOrEqualsTo,
description: 'field value must be >= field1\'s value',
target: 'field1'
}
];
suite('Validation', () => {
suite('createValidation and validate input Box', () => {
setup(() => {
sinon.restore(); //cleanup all previously defined sinon mocks
inputBoxStub = sinon.stub(inputBox, 'updateProperty' ).resolves();
});
testValidations.forEach(testObj => {
test(`validationType: ${testObj.type}`, async () => {
const validation = createValidation(testObj, async () => undefined, async (_varName: string) => undefined);
switch(testObj.type) {
case ValidationType.IsInteger: should(validation).be.instanceOf(IntegerValidation); break;
case ValidationType.Regex: should(validation).be.instanceOf(RegexValidation); break;
case ValidationType.LessThanOrEqualsTo: should(validation).be.instanceOf(LessThanOrEqualsValidation); break;
case ValidationType.GreaterThanOrEqualsTo: should(validation).be.instanceOf(GreaterThanOrEqualsValidation); break;
default: console.log(`unexpected validation type: ${testObj.type}`); break;
}
should(await validateInputBoxComponent(inputBox, [validation])).be.false();
should(inputBoxStub.calledOnce).be.true();
should(inputBoxStub.getCall(0).args[0]).equal('validationErrorMessage');
should(inputBoxStub.getCall(0).args[1]).equal(testObj.description);
});
});
});
suite('IntegerValidation', () => {
// all the below test values are arbitrary representative values or sentinel values for integer validation
[
{ value: '342520596781', expected: true },
{ value: 342520596781, expected: true },
{ value: '3.14', expected: false },
{ value: 3.14, expected: false },
{ value: '3.14e2', expected: true },
{ value: 3.14e2, expected: true },
{ value: undefined, expected: false },
{ value: NaN, expected: false },
].forEach((testObj) => {
const displayTestValue = getDisplayString(testObj.value);
test(`testValue:${displayTestValue}`, async () => {
const validationDescription = `value: ${displayTestValue} was not an integer`;
const validation = new IntegerValidation(
{ type: ValidationType.IsInteger, description: validationDescription },
async () => testObj.value
);
await testValidation(validation, testObj, validationDescription);
});
});
});
suite('RegexValidation', () => {
const testRegex = '^[0-9]+$';
// tests
[
{ value: '3425205616179816', expected: true },
{ value: 3425205616179816, expected: true },
{ value: '3.14', expected: false },
{ value: 3.14, expected: false },
{ value: '3.14e2', expected: false },
{ value: 3.14e2, expected: true }, // value of 3.14e2 literal is 342 which in string matches the testRegex
{ value: 'arbitraryString', expected: false },
{ value: undefined, expected: false },
].forEach(testOb => {
const displayTestValue = getDisplayString(testOb.value);
test(`regex: /${testRegex}/, testValue:${displayTestValue}, expect result: ${testOb.expected}`, async () => {
const validationDescription = `value:${displayTestValue} did not match the regex:/${testRegex}/`;
const validation = new RegexValidation(
{ type: ValidationType.IsInteger, description: validationDescription, regex: testRegex },
async () => testOb.value
);
await testValidation(validation, testOb, validationDescription);
});
});
});
suite('LessThanOrEqualsValidation', () => {
const targetVariableName = 'comparisonTarget';
// tests - when operands are mix of string and number then number comparison is performed
[
// integer values
{ value: '342', targetValue: '42', expected: true },
{ value: 342, targetValue: '42', expected: false },
{ value: '342', targetValue: 42, expected: false },
{ value: 42, targetValue: '342', expected: true },
{ value: '42', targetValue: 342, expected: true },
{ value: 42, targetValue: '42', expected: true },
{ value: 342, targetValue: 42, expected: false },
// floating pt values
{ value: '342.15e-1', targetValue: '42.15e-1', expected: true },
{ value: 342.15e-1, targetValue: '42.15e-1', expected: false },
{ value: '342.15e-1', targetValue: 42.15e-1, expected: false },
{ value: 342.15e-1, targetValue: 42.15e-1, expected: false },
// equal values
{ value: '342.15', targetValue: '342.15', expected: true },
{ value: 342.15, targetValue: '342.15', expected: true },
{ value: '342.15', targetValue: 342.15, expected: true },
{ value: 342.15, targetValue: 342.15, expected: true },
// undefined values - if one operand is undefined result is always false
{ value: undefined, targetValue: '42', expected: false },
{ value: undefined, targetValue: 42, expected: false },
{ value: '42', targetValue: undefined, expected: false },
{ value: 42, targetValue: undefined, expected: false },
{ value: undefined, targetValue: undefined, expected: false },
].forEach(testObj => {
const displayTestValue = getDisplayString(testObj.value);
const displayTargetValue = getDisplayString(testObj.targetValue);
test(`testValue:${displayTestValue}, targetValue:${displayTargetValue}`, async () => {
const validationDescription = `${displayTestValue} did not test as <= ${displayTargetValue}`;
const validation = new LessThanOrEqualsValidation(
{ type: ValidationType.IsInteger, description: validationDescription, target: targetVariableName },
async () => testObj.value,
async (_variableName: string) => testObj.targetValue
);
await testValidation(validation, testObj, validationDescription);
});
});
});
suite('GreaterThanOrEqualsValidation', () => {
const targetVariableName = 'comparisonTarget';
// tests - when operands are mix of string and number then number comparison is performed
[
// integer values
{ value: '342', targetValue: '42', expected: false },
{ value: 342, targetValue: '42', expected: true },
{ value: '342', targetValue: 42, expected: true },
{ value: 342, targetValue: 42, expected: true },
// floating pt values
{ value: '342.15e-1', targetValue: '42.15e-1', expected: false },
{ value: 342.15e-1, targetValue: '42.15e-1', expected: true },
{ value: '342.15e-1', targetValue: 42.15e-1, expected: true },
{ value: 342.15e-1, targetValue: 42.15e-1, expected: true },
// equal values
{ value: '342.15', targetValue: '342.15', expected: true },
{ value: 342.15, targetValue: '342.15', expected: true },
{ value: '342.15', targetValue: 342.15, expected: true },
{ value: 342.15, targetValue: 342.15, expected: true },
// undefined values - if one operand is undefined result is always false
{ value: undefined, targetValue: '42', expected: false },
{ value: undefined, targetValue: 42, expected: false },
{ value: '42', targetValue: undefined, expected: false },
{ value: 42, targetValue: undefined, expected: false },
{ value: undefined, targetValue: undefined, expected: false },
].forEach(testObj => {
const displayTestValue = getDisplayString(testObj.value);
const displayTargetValue = getDisplayString(testObj.targetValue);
test(`testValue:${displayTestValue}, targetValue:${displayTargetValue}`, async () => {
const validationDescription = `${displayTestValue} did not test as >= ${displayTargetValue}`;
const validation = new GreaterThanOrEqualsValidation(
{ type: ValidationType.IsInteger, description: validationDescription, target: targetVariableName },
async () => testObj.value,
async (_variableName: string) => testObj.targetValue
);
await testValidation(validation, testObj, validationDescription);
});
});
});
});
interface TestObject {
value: ValidationValueType;
targetValue?: ValidationValueType;
expected: boolean;
}
async function testValidation(validation: Validation, test: TestObject, validationDescription: string) {
const validationResult = await validation.validate();
should(validationResult.valid).be.equal(test.expected, validationDescription);
validationResult.valid
? should(validationResult.message).be.undefined()
: should(validationResult.message).be.equal(validationDescription);
}
function getDisplayString(value: ValidationValueType) {
return typeof value === 'string' ? `"${value}"` : value;
}

View File

@@ -218,7 +218,7 @@ export class ResourceTypePickerDialog extends DialogBase {
});
const listView = this._view.modelBuilder.listView().withProps({
title: {
text: loc.resoucrceTypeCategoryListViewTitle
text: loc.resourceTypeCategoryListViewTitle
},
CSSStyles: {
'width': '140px',

View File

@@ -0,0 +1,169 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import { throwUnless } from '../../common/utils';
export interface ValidationResult {
valid: boolean;
message?: string;
}
export type Validator = () => Promise<ValidationResult>;
export type ValidationValueType = string | number | undefined;
export type VariableValueGetter = (variable: string) => Promise<ValidationValueType>;
export type ValueGetter = () => Promise<ValidationValueType>;
export const enum ValidationType {
IsInteger = 'is_integer',
Regex = 'regex_match',
LessThanOrEqualsTo = '<=',
GreaterThanOrEqualsTo = '>='
}
export type ValidationInfo = RegexValidationInfo | IntegerValidationInfo | ComparisonValidationInfo;
export interface ValidationInfoBase {
readonly type: ValidationType,
readonly description: string,
}
export type IntegerValidationInfo = ValidationInfoBase;
export interface RegexValidationInfo extends ValidationInfoBase {
readonly regex: string | RegExp
}
export interface ComparisonValidationInfo extends ValidationInfoBase {
readonly target: string
}
export abstract class Validation {
private _description: string;
protected readonly _target?: string;
get target(): string | undefined {
return this._target;
}
get description(): string {
return this._description;
}
// gets the validation result for this validation object
abstract validate(): Promise<ValidationResult>;
protected getValue(): Promise<ValidationValueType> {
return this._valueGetter();
}
protected getVariableValue(variable: string): Promise<ValidationValueType> {
return this._variableValueGetter!(variable);
}
constructor(validation: ValidationInfo, protected _valueGetter: ValueGetter, protected _variableValueGetter?: VariableValueGetter) {
this._description = validation.description;
}
}
export class IntegerValidation extends Validation {
constructor(validation: IntegerValidationInfo, valueGetter: ValueGetter) {
super(validation, valueGetter);
}
private async isInteger(): Promise<boolean> {
const value = await this.getValue();
return (typeof value === 'string') ? Number.isInteger(parseFloat(value)) : Number.isInteger(value);
}
async validate(): Promise<ValidationResult> {
const isValid = await this.isInteger();
return {
valid: isValid,
message: isValid ? undefined: this.description
};
}
}
export class RegexValidation extends Validation {
private _regex: RegExp;
get regex(): RegExp {
return this._regex;
}
constructor(validation: RegexValidationInfo, valueGetter: ValueGetter) {
super(validation, valueGetter);
throwUnless(validation.regex !== undefined);
this._regex = (typeof validation.regex === 'string') ? new RegExp(validation.regex) : validation.regex;
}
async validate(): Promise<ValidationResult> {
const value = await this.getValue();
const isValid = value === undefined
? false
: this.regex.test(value.toString());
return {
valid: isValid,
message: isValid ? undefined: this.description
};
}
}
export abstract class Comparison extends Validation {
protected _target: string; // comparison object require a target so override the base optional setting.
get target(): string {
return this._target;
}
constructor(validation: ComparisonValidationInfo, valueGetter: ValueGetter, variableValueGetter: VariableValueGetter) {
super(validation, valueGetter, variableValueGetter);
throwUnless(validation.target !== undefined);
this._target = validation.target;
}
abstract isComparisonSuccessful(): Promise<boolean>;
async validate(): Promise<ValidationResult> {
const isValid = await this.isComparisonSuccessful();
return {
valid: isValid,
message: isValid ? undefined: this.description
};
}
}
export class LessThanOrEqualsValidation extends Comparison {
async isComparisonSuccessful() {
return (await this.getValue())! <= ((await this.getVariableValue(this.target))!);
}
}
export class GreaterThanOrEqualsValidation extends Comparison {
async isComparisonSuccessful() {
return (await this.getValue())! >= ((await this.getVariableValue(this.target))!);
}
}
export function createValidation(validation: ValidationInfo, valueGetter: ValueGetter, variableValueGetter?: VariableValueGetter): Validation {
switch (validation.type) {
case ValidationType.Regex: return new RegexValidation(<RegexValidationInfo>validation, valueGetter);
case ValidationType.IsInteger: return new IntegerValidation(<IntegerValidationInfo>validation, valueGetter);
case ValidationType.LessThanOrEqualsTo: return new LessThanOrEqualsValidation(<ComparisonValidationInfo>validation, valueGetter, variableValueGetter!);
case ValidationType.GreaterThanOrEqualsTo: return new GreaterThanOrEqualsValidation(<ComparisonValidationInfo>validation, valueGetter, variableValueGetter!);
default: throw new Error(`unknown validation type:${validation.type}`); //dev error
}
}
export async function validateInputBoxComponent(component: azdata.InputBoxComponent, validations: Validation[] = []): Promise<boolean> {
for (const validation of validations) {
const result = await validation.validate();
if (!result.valid) {
component.updateProperty('validationErrorMessage', result.message);
return false;
}
}
return true;
}

View File

@@ -189,6 +189,42 @@
resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.2.tgz#26520bf09abe4a5644cd5414e37125a8954241dd"
integrity sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw==
"@sinonjs/commons@^1", "@sinonjs/commons@^1.6.0", "@sinonjs/commons@^1.7.0", "@sinonjs/commons@^1.8.1":
version "1.8.1"
resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.1.tgz#e7df00f98a203324f6dc7cc606cad9d4a8ab2217"
integrity sha512-892K+kWUUi3cl+LlqEWIDrhvLgdL79tECi8JZUyq6IviKy/DNhuzCRlbHUjxK89f4ypPMMaFnFuR9Ie6DoIMsw==
dependencies:
type-detect "4.0.8"
"@sinonjs/fake-timers@^6.0.0", "@sinonjs/fake-timers@^6.0.1":
version "6.0.1"
resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz#293674fccb3262ac782c7aadfdeca86b10c75c40"
integrity sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==
dependencies:
"@sinonjs/commons" "^1.7.0"
"@sinonjs/formatio@^5.0.1":
version "5.0.1"
resolved "https://registry.yarnpkg.com/@sinonjs/formatio/-/formatio-5.0.1.tgz#f13e713cb3313b1ab965901b01b0828ea6b77089"
integrity sha512-KaiQ5pBf1MpS09MuA0kp6KBQt2JUOQycqVG1NZXvzeaXe5LGFqAKueIS0bw4w0P9r7KuBSVdUk5QjXsUdu2CxQ==
dependencies:
"@sinonjs/commons" "^1"
"@sinonjs/samsam" "^5.0.2"
"@sinonjs/samsam@^5.0.2", "@sinonjs/samsam@^5.2.0":
version "5.2.0"
resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-5.2.0.tgz#fcff83ab86f83b5498f4a967869c079408d9b5eb"
integrity sha512-CaIcyX5cDsjcW/ab7HposFWzV1kC++4HNsfnEdFJa7cP1QIuILAKV+BgfeqRXhcnSAc76r/Rh/O5C+300BwUIw==
dependencies:
"@sinonjs/commons" "^1.6.0"
lodash.get "^4.4.2"
type-detect "^4.0.8"
"@sinonjs/text-encoding@^0.7.1":
version "0.7.1"
resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz#8da5c6530915653f3a1f38fd5f101d8c3f8079c5"
integrity sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==
"@types/mocha@^5.2.5":
version "5.2.7"
resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-5.2.7.tgz#315d570ccb56c53452ff8638738df60726d5b6ea"
@@ -199,6 +235,18 @@
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.4.tgz#43d7168fec6fa0988bb1a513a697b29296721afb"
integrity sha512-+nVsLKlcUCeMzD2ufHEYuJ9a2ovstb6Dp52A5VsoKxDXgvE051XgHI/33I1EymwkRGQkwnA0LkhnUzituGs4EQ==
"@types/sinon@^9.0.8":
version "9.0.8"
resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-9.0.8.tgz#1ed0038d356784f75b086104ef83bfd4130bb81b"
integrity sha512-IVnI820FZFMGI+u1R+2VdRaD/82YIQTdqLYC9DLPszZuynAJDtCvCtCs3bmyL66s7FqRM3+LPX7DhHnVTaagDw==
dependencies:
"@types/sinonjs__fake-timers" "*"
"@types/sinonjs__fake-timers@*":
version "6.0.2"
resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.2.tgz#3a84cf5ec3249439015e14049bd3161419bf9eae"
integrity sha512-dIPoZ3g5gcx9zZEszaxLSVTvMReD3xxyyDnQUjA6IYDG9Ba2AV0otMPs+77sG9ojB4Qr2N2Vk5RnKeuA0X/0bg==
"@types/yamljs@0.2.30":
version "0.2.30"
resolved "https://registry.yarnpkg.com/@types/yamljs/-/yamljs-0.2.30.tgz#d034e1d329e46e8d0f737c9a8db97f68f81b5382"
@@ -353,6 +401,11 @@ diff@3.5.0:
resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"
integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==
diff@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
escape-string-regexp@1.0.5, escape-string-regexp@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
@@ -452,6 +505,11 @@ is-buffer@~1.1.1:
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==
isarray@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=
istanbul-lib-coverage@^2.0.5:
version "2.0.5"
resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz#675f0ab69503fad4b1d849f736baaca803344f49"
@@ -527,11 +585,21 @@ json5@^2.1.2:
dependencies:
minimist "^1.2.5"
just-extend@^4.0.2:
version "4.1.1"
resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-4.1.1.tgz#158f1fdb01f128c411dc8b286a7b4837b3545282"
integrity sha512-aWgeGFW67BP3e5181Ep1Fv2v8z//iBJfrvyTnq8wG86vEESwmonn1zPBJ0VfmT9CJq2FIT0VsETtrNFm2a+SHA==
linux-release-info@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/linux-release-info/-/linux-release-info-2.0.0.tgz#bdd4743a3ac49151ce612dbfe063f5eec116aeab"
integrity sha512-w0RoUAZOQvnwuypQT+kwiDRNrMrEyrNWC8OOQH4e0Chii/BqB2tq7yeUHKo9brPDymY0Iz7EKe0TtwsflAlOnA==
lodash.get@^4.4.2:
version "4.4.2"
resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=
lodash@^4.16.4, lodash@^4.17.13, lodash@^4.17.4:
version "4.17.19"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b"
@@ -631,6 +699,17 @@ ms@^2.1.1:
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a"
integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==
nise@^4.0.4:
version "4.0.4"
resolved "https://registry.yarnpkg.com/nise/-/nise-4.0.4.tgz#d73dea3e5731e6561992b8f570be9e363c4512dd"
integrity sha512-bTTRUNlemx6deJa+ZyoCUTRvH3liK5+N6VQZ4NIw90AgDXY6iPnsqplNFf6STcj+ePk0H/xqxnP75Lr0J0Fq3A==
dependencies:
"@sinonjs/commons" "^1.7.0"
"@sinonjs/fake-timers" "^6.0.0"
"@sinonjs/text-encoding" "^0.7.1"
just-extend "^4.0.2"
path-to-regexp "^1.7.0"
once@^1.3.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
@@ -648,6 +727,13 @@ path-parse@^1.0.6:
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c"
integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==
path-to-regexp@^1.7.0:
version "1.8.0"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a"
integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==
dependencies:
isarray "0.0.1"
pify@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231"
@@ -748,6 +834,19 @@ should@^13.2.3:
should-type-adaptors "^1.0.1"
should-util "^1.0.0"
sinon@^9.2.0:
version "9.2.0"
resolved "https://registry.yarnpkg.com/sinon/-/sinon-9.2.0.tgz#1d333967e30023609f7347351ebc0dc964c0f3c9"
integrity sha512-eSNXz1XMcGEMHw08NJXSyTHIu6qTCOiN8x9ODACmZpNQpr0aXTBXBnI4xTzQzR+TEpOmLiKowGf9flCuKIzsbw==
dependencies:
"@sinonjs/commons" "^1.8.1"
"@sinonjs/fake-timers" "^6.0.1"
"@sinonjs/formatio" "^5.0.1"
"@sinonjs/samsam" "^5.2.0"
diff "^4.0.2"
nise "^4.0.4"
supports-color "^7.1.0"
source-map@^0.5.0:
version "0.5.7"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
@@ -806,6 +905,11 @@ to-fast-properties@^2.0.0:
resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e"
integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=
type-detect@4.0.8, type-detect@^4.0.8:
version "4.0.8"
resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"
integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==
typemoq@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/typemoq/-/typemoq-2.1.0.tgz#4452ce360d92cf2a1a180f0c29de2803f87af1e8"