export function validateYamlConfig()

in src/Utils/YamlValidationUtil.ts [9:167]


export function validateYamlConfig(givenYaml : any) : ValidationModel {
    if(!isDictionary(givenYaml)) {
        return {valid : false,error :`Invalid YAML syntax.`};
    }
    
    let unSupportedKeys : string[] = [];
    let supportedKeys : string[] = Object.keys(new YamlConfig());

    unSupportedKeys = Object.keys(givenYaml).filter(element => supportedKeys.indexOf(element) == -1);

    if(unSupportedKeys.length) {
        const result = unSupportedKeys.map(element => `${element}`).join(", ");
        return {valid : false, error : `The YAML file provided has unsupported field(s) "${result}".`};
    }
    if(isNullOrUndefined(givenYaml.testName) && isNullOrUndefined(givenYaml.testId)){
        return {valid : false, error : "The required field testId is missing in the load test YAML file."};
    }
    let testId = '';
    if(!isNullOrUndefined(givenYaml.testName)){
        testId = givenYaml.testName;
    }
    if(!isNullOrUndefined(givenYaml.testId)){
        testId = givenYaml.testId;
    }
    testId = testId.toLowerCase();
    if(typeof(testId) != "string" || invalidName(testId)){
        return {valid : false, error : `The value "${testId}" for testId is not a valid string. Allowed characters are [a-zA-Z0-9-_] and the length must be between 2 to 50 characters.`};
    }
    if(givenYaml.displayName && (typeof givenYaml.displayName != 'string' || invalidDisplayName(givenYaml.displayName))){
        return {valid : false, error : `The value "${givenYaml.displayName}" for displayName is invalid. Display name must be a string of length between 2 to 50.`};
    }
    if(givenYaml.description && (typeof givenYaml.description != 'string' || invalidDescription(givenYaml.description))){
        return {valid : false, error : `The value "${givenYaml.description}" for description is invalid. Description must be a string of length less than 100.`};
    }
    if(isNullOrUndefined(givenYaml.testPlan)){
        return {valid : false, error : "The required field testPlan is missing in the load test YAML file."};
    }
    if(givenYaml.engineInstances && (isNaN(givenYaml.engineInstances) || inValidEngineInstances(givenYaml.engineInstances))){
        return {valid : false, error : `The value "${givenYaml.engineInstances}" for engineInstances is invalid. The value should be an integer between 1 and 400.`};
    }
    
    let kind : TestKind = givenYaml.testType ?? TestKind.JMX;
    
    if(!isValidTestKind(kind)){
        return {valid : false, error : `The value "${kind}" for testType is invalid. Acceptable values are ${EngineUtil.Resources.Strings.allFrameworksFriendly}.`};
    }
    
    let framework : BaseLoadTestFrameworkModel = EngineUtil.getLoadTestFrameworkModelFromKind(kind);
    if(givenYaml.testType as TestKind == TestKind.URL){
        if(!checkFileType(givenYaml.testPlan,'json')) {
            return {valid : false, error : "The testPlan for a URL test should of type \"json\"."};
        }
    }
    else if(!checkFileType(givenYaml.testPlan, framework.testScriptFileExtension)) {
        return {valid : false, error : `The testPlan for a ${kind} test should of type "${framework.testScriptFileExtension}".`};
    }
    if(givenYaml.subnetId && (typeof givenYaml.subnetId!= 'string' || isInValidSubnet(givenYaml.subnetId))){
        return {valid : false, error : `The value "${givenYaml.subnetId}" for subnetId is invalid. The value should be a string of the format: "/subscriptions/{subscriptionId}/resourceGroups/{rgName}/providers/Microsoft.Network/virtualNetworks/{vnetName}/subnets/{subnetName}".`};
    }
    if(givenYaml.keyVaultReferenceIdentity && (typeof givenYaml.keyVaultReferenceIdentity!= 'string' || isInvalidManagedIdentityId(givenYaml.keyVaultReferenceIdentity))){
        return {valid : false, error : `The value "${givenYaml.keyVaultReferenceIdentity}" for keyVaultReferenceIdentity is invalid. The value should be a string of the format: "/subscriptions/{subsId}/resourceGroups/{rgName}/providers/Microsoft.ManagedIdentity/userAssignedIdentities/{identityName}".`};
    }
    if(givenYaml.keyVaultReferenceIdentityType != undefined && givenYaml.keyVaultReferenceIdentityType != null && !isValidManagedIdentityType(givenYaml.keyVaultReferenceIdentityType)){
        return {valid : false, error : `The value "${givenYaml.keyVaultReferenceIdentityType}" for keyVaultReferenceIdentityType is invalid. Allowed values are "SystemAssigned" and "UserAssigned".`};
    }

    if(!isNullOrUndefined(givenYaml.referenceIdentities)) {
        if(!Array.isArray(givenYaml.referenceIdentities)){
            return {valid : false, error : `The value "${givenYaml.referenceIdentities.toString()}" for referenceIdentities is invalid. Provide a valid list of reference identities.`};
        }
        let result = validateReferenceIdentities(givenYaml.referenceIdentities);
        if(result?.valid == false){
            return result;
        }
        try {
            if(givenYaml.keyVaultReferenceIdentityType || givenYaml.keyVaultReferenceIdentity){
                validateAndGetSegregatedManagedIdentities(givenYaml.referenceIdentities as Array<{[key: string] : string}>, true);
            } else {
                validateAndGetSegregatedManagedIdentities(givenYaml.referenceIdentities as Array<{[key: string] : string}>);
            }
        } catch (error : any) {
            return {valid : false, error : error.message};
        }
    }

    if(!isNullOrUndefined(givenYaml.keyVaultReferenceIdentity) && givenYaml.keyVaultReferenceIdentityType == ManagedIdentityType.SystemAssigned){
        return {valid : false, error : `The "keyVaultReferenceIdentity" should omitted or set to null when using the "SystemAssigned" identity type.`};
    }
    if(isNullOrUndefined(givenYaml.keyVaultReferenceIdentity) && givenYaml.keyVaultReferenceIdentityType == ManagedIdentityType.UserAssigned){
        return {valid : false, error : `"The value for 'keyVaultReferenceIdentity' cannot be null when using the 'UserAssigned' identity type. Provide a valid identity reference for 'keyVaultReferenceIdentity'."`};
    }
    if(givenYaml.publicIPDisabled && typeof givenYaml.publicIPDisabled!= 'boolean'){
        return {valid : false, error : `The value "${givenYaml.publicIPDisabled}" for publicIPDisabled is invalid. The value should be either true or false.`};
    }
    if(givenYaml.publicIPDisabled && isNullOrUndefined(givenYaml.subnetId)){
        return {valid : false, error : `Public IP deployment can only be disabled for tests against private endpoints. For public endpoints, set publicIPDisabled to False.`}
    }
    if(givenYaml.configurationFiles && !isArrayOfStrings(givenYaml.configurationFiles)){
        return {valid : false, error : `The value "${givenYaml.configurationFiles}" for configurationFiles is invalid. Provide a valid list of strings.`};
    }
    if(givenYaml.zipArtifacts && !isArrayOfStrings(givenYaml.zipArtifacts)){
        return {valid : false, error : `The value "${givenYaml.zipArtifacts}" for zipArtifacts is invalid. Provide a valid list of strings.`};
    }
    if(givenYaml.splitAllCSVs && typeof givenYaml.splitAllCSVs!= 'boolean'){
        return {valid : false, error : `The value "${givenYaml.splitAllCSVs}" for splitAllCSVs is invalid. The value should be either true or false`};
    }
    if(givenYaml.properties != undefined && givenYaml.properties.userPropertyFile != undefined){
        if(isNull(givenYaml.properties.userPropertyFile) || typeof givenYaml.properties.userPropertyFile != 'string' || !checkFileTypes(givenYaml.properties.userPropertyFile, framework.userPropertyFileExtensions)){
            return {valid : false, error : `The value "${givenYaml.properties.userPropertyFile}" for userPropertyFile is invalid. Provide a valid file path of type ${framework.ClientResources.userPropertyFileExtensionsFriendly}. Refer to the YAML syntax at https://learn.microsoft.com/azure/load-testing/reference-test-config-yaml#properties-configuration.`}
        }
    }
    if(givenYaml.appComponents) {
        if(!Array.isArray(givenYaml.appComponents)){
            return {valid : false, error : `The value "${givenYaml.appComponents}" for appComponents is invalid. Provide a valid list of application components.`};
        }
        let validationAppComponents = validateAppComponentAndServerMetricsConfig(givenYaml.appComponents);
        if(validationAppComponents.valid == false){
            return validationAppComponents;
        }
    }
    if(givenYaml.autoStop){
        let validation = validateAutoStop(givenYaml.autoStop);
        if(validation.valid == false){
            return validation;
        }
    }
    if(givenYaml.failureCriteria != undefined) {
        let result = validateFailureCriteria(givenYaml.failureCriteria);
        if(result.valid == false){
            return result;
        }
    }
    if(givenYaml.regionalLoadTestConfig){
        if(!Array.isArray(givenYaml.regionalLoadTestConfig)){
            return {valid : false, error : `The value "${givenYaml.regionalLoadTestConfig?.toString()}" for regionalLoadTestConfig is invalid. Provide a valid list of region configuration for Multi-region load test.`};
        }
        
        if(givenYaml.regionalLoadTestConfig.length < 2){
            return {valid : false, error : `Multi-region load tests should contain a minimum of 2 geographic regions in the configuration.`};
        }
        
        var totalEngineCount = 0;
        for(let i = 0; i < givenYaml.regionalLoadTestConfig.length; i++){
            if(isNullOrUndefined(givenYaml.regionalLoadTestConfig[i].region) || typeof givenYaml.regionalLoadTestConfig[i].region != 'string' || givenYaml.regionalLoadTestConfig[i].region == ""){
                return {valid : false, error : `The value "${givenYaml.regionalLoadTestConfig[i].region}" for region in regionalLoadTestConfig is invalid. Provide a valid string.`};
            }
            if(isNullOrUndefined(givenYaml.regionalLoadTestConfig[i].engineInstances) || isNaN(givenYaml.regionalLoadTestConfig[i].engineInstances) || inValidEngineInstances(givenYaml.regionalLoadTestConfig[i].engineInstances)){
                return {valid : false, error : `The value "${givenYaml.regionalLoadTestConfig[i].engineInstances}" for engineInstances in regionalLoadTestConfig is invalid. The value should be an integer between 1 and 400.`};
            }
            totalEngineCount += givenYaml.regionalLoadTestConfig[i].engineInstances;
        }
        let engineInstances = givenYaml.engineInstances ?? 1;
        if(totalEngineCount != givenYaml.engineInstances){
            return {valid : false, error : `The sum of engineInstances in regionalLoadTestConfig should be equal to the value of totalEngineInstances "${engineInstances}" in the test configuration.`};
        }
    }

    return {valid : true, error : ""};
}