@ibm-adw/skill-toolkit
Version:
Developing your own skills with IBM Automation Digital Worker Skill Toolkit
572 lines (510 loc) • 21.4 kB
JavaScript
// skill-config.test.js
/* eslint-env jest */
'use strict';
const form = require('../skill-config')['form'];
const inputSchema = require('../skill-config')['inputSchema'];
const outputSchema = require('../skill-config')['outputSchema'];
const snippet = require('../skill-config')['snippet'];
const path = require('path');
const fs = require('fs');
const Ajv = require('ajv');
const ajv = new Ajv();
const schemaSkill = () => {
return {
'type': 'object',
'description': 'Specify whether you want to estimate the age for a given country or not.',
'properties': {
'useCountry': {
'type': 'boolean',
'title': 'Estimate the average age of a person for a given country'
},
'link': {
'type': 'string',
'title': 'See the available data',
'description': 'Don\'t know whether to choose a specific country or not?',
'url': 'https://agify.io/our-data'
}
},
'required': [
'useCountry'
],
'dependencies': {
'useCountry': {
'oneOf': [
{
'properties': {
'useCountry': {
'enum': [
true
]
},
'countryRetrievalMethod': {
'title': 'How do you want the country to be provided?',
'type': 'string',
'enum': [
'set',
'provide'
],
'enumNames': [
'Set the country now',
'Set the country at runtime'
],
'default': 'set'
},
},
'required': [
'countryRetrievalMethod'
],
'dependencies': {
'countryRetrievalMethod': {
'oneOf': [
{
'properties': {
'countryRetrievalMethod': {
'enum': [
'set'
]
},
'countryId': {
'title': 'Country',
'type': 'string'
}
},
'required': [
'countryId'
]
},
{
'properties': {
'countryRetrievalMethod': {
'enum': [
'provide'
]
}
}
}
]
}
}
},
{
'properties': {
'useCountry': {
'enum': [
false
]
}
}
}
]
}
}
};
};
const uiSchemaSkill = () => {
return {
'useCountry': {
'ui:options': {
'triggerEvent': 'changeCountryOption'
}
},
'link': {
'ui:field': 'LinkField'
},
'countryRetrievalMethod': {
'ui:widget': 'radio',
'ui:options': {
'triggerEvent': 'changeCountryOption'
}
},
'countryId': {
'ui:placeholder': 'Enter the country ID in ISO 3166-1 alpha-2 format. For example: US.',
'ui:options': {
'triggerEvent': 'changeCountryOption'
}
}
};
};
const formDataSkill = () => {
return {
'useCountry': false
};
};
const config_schema = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../skill-spec.json'), { encoding: 'utf8' }))['config_schema'];
const inputSchemaExpected = {
'$schema': 'http://json-schema.org/draft-07/schema#',
'title': 'Input schema for skill: Age Estimate',
'type': 'object',
'properties': {
'name': {
'description': 'The first name to be used to estimate the age',
'type': 'string'
},
'timeout': {
'description': 'Timeout in milliseconds before canceling requests. This timeout is set by default to `5000` ms.',
'type': 'integer',
'default': 5000,
'minimum': 0
}
},
'required': [
'name'
]
};
const inputSchemaExpectedWithCountryAtRuntime = {
'$schema': 'http://json-schema.org/draft-07/schema#',
'title': 'Input schema for skill: Age Estimate',
'type': 'object',
'properties': {
'name': {
'description': 'The first name to be used to estimate the age',
'type': 'string'
},
'countryId': {
'description': 'The ID of the country (in ISO 3166-1 alpha-2 format) to be used to estimate the age',
'type': 'string'
},
'timeout': {
'description': 'Timeout in milliseconds before canceling requests. This timeout is set by default to `5000` ms.',
'type': 'integer',
'default': 5000,
'minimum': 0
}
},
'required': [
'name',
'countryId'
]
};
const outputSchemaExpected = {
'$schema': 'http://json-schema.org/draft-07/schema#',
'title': 'Output schema for skill: Age Estimate',
'type': 'object',
'properties': {
'name': {
'description': 'The first name used to estimate the age',
'type': 'string'
},
'age': {
'description': 'The estimated age',
'type': 'integer'
},
'count': {
'description': 'The number of persons with the given first name who are used to estimate the age',
'type': 'integer'
}
},
'required': [
'name',
'age',
'count'
]
};
const outputSchemaExpectedWithCountryAtRuntime = {
'$schema': 'http://json-schema.org/draft-07/schema#',
'title': 'Output schema for skill: Age Estimate',
'type': 'object',
'properties': {
'name': {
'description': 'The first name used to estimate the age',
'type': 'string'
},
'age': {
'description': 'The estimated age',
'type': 'integer'
},
'count': {
'description': 'The number of persons with the given first name who are used to estimate the age',
'type': 'integer'
},
'countryId': {
'description': 'The ID of the country (in ISO 3166-1 alpha-2 format) used to estimate the age',
'type': 'string'
}
},
'required': [
'name',
'age',
'count',
'countryId'
]
};
const snippetExpected = '// Snippet code to get and execute a skill\n' +
'const skill = task.getSkill("<SKILL_NAME>");\n' +
'const input = {\n' +
'\tname: "Jane"\n' +
'};\n' +
'const result = await skill.execute(input);\n' +
'task.context.logger.info(result);\n' +
'return result;';
const snippetExpectedWithCountryAtRuntime = '// Snippet code to get and execute a skill\n' +
'const skill = task.getSkill("<SKILL_NAME>");\n' +
'const input = {\n' +
'\tname: "Jane",\n' +
'\tcountryId: "US"\n' +
'};\n' +
'const result = await skill.execute(input);\n' +
'task.context.logger.info(result);\n' +
'return result;';
const assertForm = (actual, expected, isFormSubmittable) => {
// Test actual.schema is a valid JSON Schema
expect(() => ajv.compile(actual.schema)).not.toThrow();
// Test actual.uiSchema is a valid JSON Schema
expect(() => ajv.compile(actual.uiSchema)).not.toThrow();
if (isFormSubmittable) {
// Test actual.formData is a valid JSON according to config_schema
ajv.validate(config_schema, actual.formData);
expect(ajv.errors).toBe(null);
}
expect(actual.schema).toStrictEqual(expected.schema);
expect(actual.uiSchema).toStrictEqual(expected.uiSchema);
expect(actual.formData).toStrictEqual(expected.formData);
expect(actual.isFormSubmittable).toBe(expected.isFormSubmittable);
};
describe('SKILL CONFIG - Basic Tests', () => {
test('Test config_schema is a valid JSON Schema', () => {
expect(() => ajv.compile(config_schema)).not.toThrow();
}, 30000);
test('Test config_schema without using country', () => {
const formDataSkillValue = {
'useCountry': false
};
ajv.validate(config_schema, formDataSkillValue);
expect(ajv.errors).toBe(null);
}, 30000);
test('Test config_schema without using country and with additional property', () => {
const formDataSkillValue = {
'useCountry': false,
'otherProperty': 'value'
};
ajv.validate(config_schema, formDataSkillValue);
const expected = [{'dataPath': '', 'keyword': 'additionalProperties', 'message': 'should NOT have additional properties', 'params': {'additionalProperty': 'otherProperty'}, 'schemaPath': '#/else/additionalProperties'}];
expect(ajv.errors).toEqual(expected);
}, 30000);
test('Test config_schema with using country but without country retrieval method', () => {
const formDataSkillValue = {
'useCountry': true
};
ajv.validate(config_schema, formDataSkillValue);
const expected = [{'dataPath': '', 'keyword': 'required', 'message': 'should have required property \'countryRetrievalMethod\'', 'params': {'missingProperty': 'countryRetrievalMethod'}, 'schemaPath': '#/then/required'}];
expect(ajv.errors).toEqual(expected);
}, 30000);
test('Test config_schema with using country and country retrieval method but without country ID', () => {
const formDataSkillValue = {
'useCountry': true,
'countryRetrievalMethod': 'set'
};
ajv.validate(config_schema, formDataSkillValue);
const expected = [{'dataPath': '', 'keyword': 'required', 'message': 'should have required property \'countryId\'', 'params': {'missingProperty': 'countryId'}, 'schemaPath': '#/then/then/required'}];
expect(ajv.errors).toEqual(expected);
}, 30000);
test('Test config_schema with using country, country retrieval method and country ID provided in the configuration', () => {
const formDataSkillValue = {
'useCountry': true,
'countryRetrievalMethod': 'set',
'countryId': 'US'
};
ajv.validate(config_schema, formDataSkillValue);
expect(ajv.errors).toBe(null);
}, 30000);
test('Test config_schema with using country, country retrieval method, country ID and additional property', () => {
const formDataSkillValue = {
'useCountry': true,
'countryRetrievalMethod': 'set',
'countryId': 'US',
'otherProperty': 'value'
};
ajv.validate(config_schema, formDataSkillValue);
const expected = [{'dataPath': '', 'keyword': 'additionalProperties', 'message': 'should NOT have additional properties', 'params': {'additionalProperty': 'otherProperty'}, 'schemaPath': '#/then/then/additionalProperties'}];
expect(ajv.errors).toEqual(expected);
}, 30000);
test('Test config_schema with using country and country retrieval method set to \'provide\'', () => {
const formDataSkillValue = {
'useCountry': true,
'countryRetrievalMethod': 'provide'
};
ajv.validate(config_schema, formDataSkillValue);
expect(ajv.errors).toBe(null);
}, 30000);
test('Test config_schema with using country, country retrieval method set to \'provide\' but with country ID in the configuration', () => {
const formDataSkillValue = {
'useCountry': true,
'countryRetrievalMethod': 'provide',
'countryId': 'US',
};
ajv.validate(config_schema, formDataSkillValue);
const expected = [{'dataPath': '', 'keyword': 'additionalProperties', 'message': 'should NOT have additional properties', 'params': {'additionalProperty': 'countryId'}, 'schemaPath': '#/then/else/additionalProperties'}];
expect(ajv.errors).toEqual(expected);
}, 30000);
test('Test config_schema with using country, country retrieval method set to \'provide\' and additional property', () => {
const formDataSkillValue = {
'useCountry': true,
'countryRetrievalMethod': 'provide',
'otherProperty': 'value'
};
ajv.validate(config_schema, formDataSkillValue);
const expected = [{'dataPath': '', 'keyword': 'additionalProperties', 'message': 'should NOT have additional properties', 'params': {'additionalProperty': 'otherProperty'}, 'schemaPath': '#/then/else/additionalProperties'}];
expect(ajv.errors).toEqual(expected);
}, 30000);
test('Test config_schema with using country, country retrieval method set to \'provide\' but with country ID in the configuration and additional property', () => {
const formDataSkillValue = {
'useCountry': true,
'countryRetrievalMethod': 'provide',
'countryId': 'US',
'otherProperty': 'value'
};
ajv.validate(config_schema, formDataSkillValue);
const expected = [{'dataPath': '', 'keyword': 'additionalProperties', 'message': 'should NOT have additional properties', 'params': {'additionalProperty': 'countryId'}, 'schemaPath': '#/then/else/additionalProperties'}];
expect(ajv.errors).toEqual(expected);
}, 30000);
test('Invoke form on \'init\' event', async () => {
const schemaSkillValue = schemaSkill();
const uiSchemaSkillValue = uiSchemaSkill();
const formDataSkillValue = formDataSkill();
const expected = {
schema: schemaSkillValue,
uiSchema: uiSchemaSkillValue,
formData: formDataSkillValue,
isFormSubmittable: true
};
const configuration = await form({}, 'init', null);
assertForm(configuration, expected, expected.isFormSubmittable);
}, 30000);
test('Invoke form with an unsupported event', async () => {
const config = {};
await expect(form(config, 'dummy', null))
.rejects
.toThrow('dummy is not a valid event in the configuration of this skill');
}, 30000);
test('Invoke form with an unexpected configuration error', async () => {
await expect(form(undefined, 'init', null))
.rejects
.toThrow('The configuration is not valid');
}, 30000);
test('Invoke form on \'reopen\' event', async () => {
const schemaSkillValue = schemaSkill();
const uiSchemaSkillValue = uiSchemaSkill();
const formDataSkillValue = formDataSkill();
const expected = {
schema: schemaSkillValue,
uiSchema: uiSchemaSkillValue,
formData: formDataSkillValue,
isFormSubmittable: true
};
const config = {};
const configuration = await form(config, 'reopen', null);
assertForm(configuration, expected, expected.isFormSubmittable);
}, 30000);
test('Invoke form on \'changeCountryOption\' event (with country set at config time)', async () => {
const schemaSkillValue = schemaSkill();
const uiSchemaSkillValue = uiSchemaSkill();
const formDataSkillValue = {
'useCountry': true,
'countryRetrievalMethod': 'set',
'countryId': 'US'
};
const expected = {
schema: schemaSkillValue,
uiSchema: uiSchemaSkillValue,
formData: formDataSkillValue,
isFormSubmittable: true
};
const config = {
'useCountry': true,
'countryRetrievalMethod': 'set',
'countryId': 'US'
};
const configuration = await form(config, 'changeCountryOption', null);
assertForm(configuration, expected, expected.isFormSubmittable);
}, 30000);
test('Invoke form on \'changeCountryOption\' event (with invalid country)', async () => {
const schemaSkillValue = schemaSkill();
schemaSkillValue.dependencies.useCountry.oneOf[0].dependencies.countryRetrievalMethod.oneOf[0].properties.countryId.error = 'is not a valid country ID. Only ISO 3166-1 alpha-2 codes are supported.';
const uiSchemaSkillValue = uiSchemaSkill();
const formDataSkillValue = {
'useCountry': true,
'countryRetrievalMethod': 'set',
'countryId': 'UK'
};
const expected = {
schema: schemaSkillValue,
uiSchema: uiSchemaSkillValue,
formData: formDataSkillValue,
isFormSubmittable: false
};
const config = {
'useCountry': true,
'countryRetrievalMethod': 'set',
'countryId': 'UK'
};
const configuration = await form(config, 'changeCountryOption', null);
assertForm(configuration, expected, expected.isFormSubmittable);
}, 30000);
test('Invoke form on \'changeCountryOption\' event (with country set at runtime)', async () => {
const schemaSkillValue = schemaSkill();
const uiSchemaSkillValue = uiSchemaSkill();
const formDataSkillValue = {
'useCountry': true,
'countryRetrievalMethod': 'provide'
};
const expected = {
schema: schemaSkillValue,
uiSchema: uiSchemaSkillValue,
formData: formDataSkillValue,
isFormSubmittable: true
};
const config = {
'useCountry': true,
'countryRetrievalMethod': 'provide'
};
const configuration = await form(config, 'changeCountryOption', null);
assertForm(configuration, expected, expected.isFormSubmittable);
}, 30000);
test('Invoke inputSchema without country set at runtime', () => {
const config = {};
const inputSchemaValue = inputSchema(config);
// Test inputSchemaValue is a valid JSON Schema
expect(() => ajv.compile(inputSchemaValue)).not.toThrow();
expect(inputSchemaValue).toStrictEqual(inputSchemaExpected);
});
test('Invoke inputSchema with country set at runtime', () => {
const config = {
'useCountry': true,
'countryRetrievalMethod': 'provide'
};
const inputSchemaValue = inputSchema(config);
// Test inputSchemaValue is a valid JSON Schema
expect(() => ajv.compile(inputSchemaValue)).not.toThrow();
expect(inputSchemaValue).toStrictEqual(inputSchemaExpectedWithCountryAtRuntime);
});
test('Invoke outputSchema', () => {
const config = {};
const outputSchemaValue = outputSchema(config);
// Test outputSchemaValue is a valid JSON Schema
expect(() => ajv.compile(outputSchemaValue)).not.toThrow();
expect(outputSchemaValue).toStrictEqual(outputSchemaExpected);
});
test('Invoke outputSchema with country set at runtime', () => {
const config = {
'useCountry': true,
'countryRetrievalMethod': 'provide'
};
const outputSchemaValue = outputSchema(config);
// Test outputSchemaValue is a valid JSON Schema
expect(() => ajv.compile(outputSchemaValue)).not.toThrow();
expect(outputSchemaValue).toStrictEqual(outputSchemaExpectedWithCountryAtRuntime);
});
test('Invoke snippet', () => {
const config = {};
const snippetValue = snippet(config);
expect(snippetValue).toBe(snippetExpected);
});
test('Invoke snippet with country set at runtime', () => {
const config = {
'useCountry': true,
'countryRetrievalMethod': 'provide'
};
const snippetValue = snippet(config);
expect(snippetValue).toBe(snippetExpectedWithCountryAtRuntime);
});
});