mirror of
https://github.com/jambonz/jambonz-node.git
synced 2025-12-19 05:17:49 +00:00
add support for env schema, allowing users to provide environment variables for an application
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
const Jambonz = require('./rest/jambonz');
|
const Jambonz = require('./rest/jambonz');
|
||||||
|
const { validateAppConfig, getAppConfig, schema } = require('./validator');
|
||||||
|
|
||||||
const initializer = (accountSid, apiKey, opts) => {
|
const initializer = (accountSid, apiKey, opts) => {
|
||||||
return new Jambonz(accountSid, apiKey, opts);
|
return new Jambonz(accountSid, apiKey, opts);
|
||||||
@@ -8,10 +9,12 @@ initializer.Jambonz = Jambonz;
|
|||||||
initializer.WebhookResponse = require('./jambonz/webhook-response');
|
initializer.WebhookResponse = require('./jambonz/webhook-response');
|
||||||
initializer.WsRouter = require('./jambonz/ws-router');
|
initializer.WsRouter = require('./jambonz/ws-router');
|
||||||
initializer.WsSession = require('./jambonz/ws-session');
|
initializer.WsSession = require('./jambonz/ws-session');
|
||||||
|
initializer.validateAppConfig = validateAppConfig;
|
||||||
|
initializer.getAppConfig = getAppConfig;
|
||||||
|
initializer.appSchema = schema;
|
||||||
initializer.handleProtocols = (protocols) => {
|
initializer.handleProtocols = (protocols) => {
|
||||||
if (!protocols.has('ws.jambonz.org')) return false;
|
if (!protocols.has('ws.jambonz.org')) return false;
|
||||||
return 'ws.jambonz.org';
|
return 'ws.jambonz.org';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
module.exports = initializer;
|
module.exports = initializer;
|
||||||
|
|||||||
30
lib/schema/app-schema.json
Normal file
30
lib/schema/app-schema.json
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"description": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["string", "number", "boolean"]
|
||||||
|
},
|
||||||
|
"required": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"oneOf": [
|
||||||
|
{ "type": "string" },
|
||||||
|
{ "type": "number" },
|
||||||
|
{ "type": "boolean" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"enum": {
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"obscure": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["type", "description"]
|
||||||
|
}
|
||||||
156
lib/validator.js
Normal file
156
lib/validator.js
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
const Ajv = require('ajv');
|
||||||
|
const addFormats = require('ajv-formats');
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const schemaPath = path.join(__dirname, 'schema/app-schema.json');
|
||||||
|
const schema = JSON.parse(fs.readFileSync(schemaPath, 'utf8'));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the appropriate configuration from app.json based on the request path
|
||||||
|
* @param {Object} params - Parameters object
|
||||||
|
* @param {string} params.urlPath - The request path to match against
|
||||||
|
* @param {string} params.appJsonPath - Path to app.json file
|
||||||
|
* @returns {Object} - Result with { success: boolean, config: Object, error: string }
|
||||||
|
*/
|
||||||
|
function getAppConfig({ urlPath, appJsonPath }) {
|
||||||
|
try {
|
||||||
|
if (!appJsonPath) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
config: {},
|
||||||
|
error: 'appJsonPath is required'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fs.existsSync(appJsonPath)) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
config: {},
|
||||||
|
error: `app.json file not found at ${appJsonPath}`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read and parse app.json
|
||||||
|
const appJson = JSON.parse(fs.readFileSync(appJsonPath, 'utf8'));
|
||||||
|
|
||||||
|
// Get regular (non-slash) properties
|
||||||
|
const regularProperties = Object.entries(appJson)
|
||||||
|
.filter(([key]) => !key.startsWith('/'))
|
||||||
|
.reduce((acc, [key, value]) => {
|
||||||
|
// If the property is marked as obscure, replace its value with asterisks
|
||||||
|
if (value.obscure && value.value) {
|
||||||
|
acc[key] = {
|
||||||
|
...value,
|
||||||
|
value: '*'.repeat(value.value.length)
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
acc[key] = value;
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
// Check for a matching path property
|
||||||
|
const pathProperty = appJson[urlPath];
|
||||||
|
if (pathProperty) {
|
||||||
|
// Combine path-specific properties with regular properties
|
||||||
|
// Path-specific properties take precedence
|
||||||
|
const mergedConfig = {
|
||||||
|
...regularProperties,
|
||||||
|
...pathProperty
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle obscure properties in path-specific config
|
||||||
|
Object.entries(pathProperty).forEach(([key, value]) => {
|
||||||
|
if (value.obscure && value.value) {
|
||||||
|
mergedConfig[key] = {
|
||||||
|
...value,
|
||||||
|
value: '*'.repeat(value.value.length)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
config: mergedConfig,
|
||||||
|
error: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no matching path property, return only the regular properties
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
config: regularProperties,
|
||||||
|
error: null
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
config: {},
|
||||||
|
error: `Error reading app.json: ${err.message}`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates a jambonz application configuration against the schema
|
||||||
|
* @param {Object} config - The configuration object to validate
|
||||||
|
* @returns {Object} - Validation result with { isValid: boolean, errors: Array<string> }
|
||||||
|
*/
|
||||||
|
function validateAppConfig(config) {
|
||||||
|
try {
|
||||||
|
const ajv = new Ajv({ allErrors: true });
|
||||||
|
addFormats(ajv);
|
||||||
|
|
||||||
|
const validate = ajv.compile(schema);
|
||||||
|
const errors = [];
|
||||||
|
|
||||||
|
// Validate each property in the config
|
||||||
|
Object.entries(config).forEach(([key, value]) => {
|
||||||
|
if (key.startsWith('/')) {
|
||||||
|
// For slash properties, validate each property within the path object
|
||||||
|
Object.entries(value).forEach(([propKey, propValue]) => {
|
||||||
|
const isValid = validate(propValue);
|
||||||
|
if (!isValid) {
|
||||||
|
errors.push(...validate.errors.map((err) => {
|
||||||
|
const errPath = err.instancePath ?
|
||||||
|
` at path ${key}.${propKey}${err.instancePath}` : ` at path ${key}.${propKey}`;
|
||||||
|
const message = err.message || 'Unknown error';
|
||||||
|
const params = err.params ?
|
||||||
|
` (${Object.entries(err.params).map(([k, v]) => `${k}: ${v}`).join(', ')})` : '';
|
||||||
|
return `${message}${errPath}${params}`;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// For non-slash properties, validate the property directly
|
||||||
|
const isValid = validate(value);
|
||||||
|
if (!isValid) {
|
||||||
|
errors.push(...validate.errors.map((err) => {
|
||||||
|
const errPath = err.instancePath ? ` at path ${key}${err.instancePath}` : ` at path ${key}`;
|
||||||
|
const message = err.message || 'Unknown error';
|
||||||
|
const params = err.params ?
|
||||||
|
` (${Object.entries(err.params).map(([k, v]) => `${k}: ${v}`).join(', ')})` : '';
|
||||||
|
return `${message}${errPath}${params}`;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
isValid: errors.length === 0,
|
||||||
|
errors
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
return {
|
||||||
|
isValid: false,
|
||||||
|
errors: [`Error during validation: ${err.message}`]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
validateAppConfig,
|
||||||
|
getAppConfig,
|
||||||
|
schema
|
||||||
|
};
|
||||||
116
package-lock.json
generated
116
package-lock.json
generated
@@ -10,6 +10,8 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jambonz/verb-specifications": "^0.0.102",
|
"@jambonz/verb-specifications": "^0.0.102",
|
||||||
|
"ajv": "^8.12.0",
|
||||||
|
"ajv-formats": "^2.1.1",
|
||||||
"bent": "^7.3.12",
|
"bent": "^7.3.12",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"parseurl": "^1.3.3"
|
"parseurl": "^1.3.3"
|
||||||
@@ -385,6 +387,28 @@
|
|||||||
"url": "https://opencollective.com/eslint"
|
"url": "https://opencollective.com/eslint"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@eslint/eslintrc/node_modules/ajv": {
|
||||||
|
"version": "6.12.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||||
|
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"fast-deep-equal": "^3.1.1",
|
||||||
|
"fast-json-stable-stringify": "^2.0.0",
|
||||||
|
"json-schema-traverse": "^0.4.1",
|
||||||
|
"uri-js": "^4.2.2"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/epoberezkin"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": {
|
||||||
|
"version": "0.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
||||||
|
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/@eslint/js": {
|
"node_modules/@eslint/js": {
|
||||||
"version": "8.51.0",
|
"version": "8.51.0",
|
||||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.51.0.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.51.0.tgz",
|
||||||
@@ -700,21 +724,36 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ajv": {
|
"node_modules/ajv": {
|
||||||
"version": "6.12.6",
|
"version": "8.17.1",
|
||||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
|
||||||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fast-deep-equal": "^3.1.1",
|
"fast-deep-equal": "^3.1.3",
|
||||||
"fast-json-stable-stringify": "^2.0.0",
|
"fast-uri": "^3.0.1",
|
||||||
"json-schema-traverse": "^0.4.1",
|
"json-schema-traverse": "^1.0.0",
|
||||||
"uri-js": "^4.2.2"
|
"require-from-string": "^2.0.2"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "github",
|
"type": "github",
|
||||||
"url": "https://github.com/sponsors/epoberezkin"
|
"url": "https://github.com/sponsors/epoberezkin"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/ajv-formats": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==",
|
||||||
|
"dependencies": {
|
||||||
|
"ajv": "^8.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"ajv": "^8.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"ajv": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ansi-regex": {
|
"node_modules/ansi-regex": {
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||||
@@ -1650,6 +1689,28 @@
|
|||||||
"url": "https://opencollective.com/eslint"
|
"url": "https://opencollective.com/eslint"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/eslint/node_modules/ajv": {
|
||||||
|
"version": "6.12.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||||
|
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"fast-deep-equal": "^3.1.1",
|
||||||
|
"fast-json-stable-stringify": "^2.0.0",
|
||||||
|
"json-schema-traverse": "^0.4.1",
|
||||||
|
"uri-js": "^4.2.2"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/epoberezkin"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/eslint/node_modules/json-schema-traverse": {
|
||||||
|
"version": "0.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
||||||
|
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/espree": {
|
"node_modules/espree": {
|
||||||
"version": "9.6.1",
|
"version": "9.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
|
||||||
@@ -1741,8 +1802,7 @@
|
|||||||
"node_modules/fast-deep-equal": {
|
"node_modules/fast-deep-equal": {
|
||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"node_modules/fast-json-stable-stringify": {
|
"node_modules/fast-json-stable-stringify": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
@@ -1764,6 +1824,21 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/fast-uri": {
|
||||||
|
"version": "3.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz",
|
||||||
|
"integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/fastify"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/fastify"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"node_modules/fastq": {
|
"node_modules/fastq": {
|
||||||
"version": "1.15.0",
|
"version": "1.15.0",
|
||||||
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz",
|
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz",
|
||||||
@@ -2923,10 +2998,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/json-schema-traverse": {
|
"node_modules/json-schema-traverse": {
|
||||||
"version": "0.4.1",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
||||||
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
|
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"node_modules/json-stable-stringify-without-jsonify": {
|
"node_modules/json-stable-stringify-without-jsonify": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
@@ -3588,9 +3662,9 @@
|
|||||||
"integrity": "sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ=="
|
"integrity": "sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ=="
|
||||||
},
|
},
|
||||||
"node_modules/punycode": {
|
"node_modules/punycode": {
|
||||||
"version": "2.3.0",
|
"version": "2.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||||
"integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==",
|
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
@@ -3707,6 +3781,14 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/require-from-string": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/require-main-filename": {
|
"node_modules/require-main-filename": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
|
||||||
|
|||||||
@@ -16,6 +16,8 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jambonz/verb-specifications": "^0.0.102",
|
"@jambonz/verb-specifications": "^0.0.102",
|
||||||
|
"ajv": "^8.12.0",
|
||||||
|
"ajv-formats": "^2.1.1",
|
||||||
"bent": "^7.3.12",
|
"bent": "^7.3.12",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"parseurl": "^1.3.3"
|
"parseurl": "^1.3.3"
|
||||||
|
|||||||
5
test/data/invalid/missing-description.json
Normal file
5
test/data/invalid/missing-description.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"text": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
12
test/data/invalid/mixed.json
Normal file
12
test/data/invalid/mixed.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"text": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"/hello": {
|
||||||
|
"text": {
|
||||||
|
"description": "Welcome message",
|
||||||
|
"type": "invalid_type",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
5
test/data/invalid/non-slash.json
Normal file
5
test/data/invalid/non-slash.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"text": {
|
||||||
|
"type": "invalid_type"
|
||||||
|
}
|
||||||
|
}
|
||||||
9
test/data/invalid/slash.json
Normal file
9
test/data/invalid/slash.json
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"/hello": {
|
||||||
|
"text": {
|
||||||
|
"description": "Welcome message",
|
||||||
|
"type": "invalid_type",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
13
test/data/valid/mixed.json
Normal file
13
test/data/valid/mixed.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"text": {
|
||||||
|
"description": "Default text property",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"/hello": {
|
||||||
|
"text": {
|
||||||
|
"description": "Welcome message",
|
||||||
|
"type": "string",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
12
test/data/valid/non-slash.json
Normal file
12
test/data/valid/non-slash.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"text": {
|
||||||
|
"description": "Welcome message",
|
||||||
|
"type": "string",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"timeout": {
|
||||||
|
"description": "Timeout in seconds",
|
||||||
|
"type": "number",
|
||||||
|
"default": 30
|
||||||
|
}
|
||||||
|
}
|
||||||
16
test/data/valid/obscure.json
Normal file
16
test/data/valid/obscure.json
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"apiKey": {
|
||||||
|
"description": "API key for the service",
|
||||||
|
"type": "string",
|
||||||
|
"required": true,
|
||||||
|
"obscure": true,
|
||||||
|
"value": "secret123"
|
||||||
|
},
|
||||||
|
"/hello": {
|
||||||
|
"text": {
|
||||||
|
"description": "Welcome message",
|
||||||
|
"type": "string",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
9
test/data/valid/slash.json
Normal file
9
test/data/valid/slash.json
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"/hello": {
|
||||||
|
"text": {
|
||||||
|
"description": "Welcome message",
|
||||||
|
"type": "string",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
110
test/getAppConfig.js
Normal file
110
test/getAppConfig.js
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
const test = require('tape');
|
||||||
|
const path = require('path');
|
||||||
|
const { getAppConfig } = require('../lib/validator');
|
||||||
|
|
||||||
|
test('getAppConfig tests', (t) => {
|
||||||
|
// Test case 1: Missing appJsonPath
|
||||||
|
t.test('should return error when appJsonPath is missing', (st) => {
|
||||||
|
const result = getAppConfig({ urlPath: '/test' });
|
||||||
|
st.equal(result.success, false, 'should not be successful');
|
||||||
|
st.equal(result.error, 'appJsonPath is required', 'should return correct error message');
|
||||||
|
st.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test case 2: Non-existent app.json file
|
||||||
|
t.test('should return error when app.json does not exist', (st) => {
|
||||||
|
const result = getAppConfig({
|
||||||
|
urlPath: '/test',
|
||||||
|
appJsonPath: path.join(__dirname, 'data/nonexistent.json')
|
||||||
|
});
|
||||||
|
st.equal(result.success, false, 'should not be successful');
|
||||||
|
st.ok(result.error.includes('app.json file not found'), 'should return file not found error');
|
||||||
|
st.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test case 3: Valid app.json with regular properties
|
||||||
|
t.test('should return regular properties when no path match', (st) => {
|
||||||
|
const result = getAppConfig({
|
||||||
|
urlPath: '/nonexistent',
|
||||||
|
appJsonPath: path.join(__dirname, 'data/valid/non-slash.json')
|
||||||
|
});
|
||||||
|
st.equal(result.success, true, 'should be successful');
|
||||||
|
st.deepEqual(result.config, {
|
||||||
|
text: {
|
||||||
|
description: 'Welcome message',
|
||||||
|
type: 'string',
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
}, 'should return correct regular properties');
|
||||||
|
st.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test case 4: Valid app.json with path-specific properties
|
||||||
|
t.test('should return path-specific properties when path matches', (st) => {
|
||||||
|
const result = getAppConfig({
|
||||||
|
urlPath: '/hello',
|
||||||
|
appJsonPath: path.join(__dirname, 'data/valid/slash.json')
|
||||||
|
});
|
||||||
|
st.equal(result.success, true, 'should be successful');
|
||||||
|
st.deepEqual(result.config, {
|
||||||
|
'/hello': {
|
||||||
|
text: {
|
||||||
|
description: 'Welcome message',
|
||||||
|
type: 'string',
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 'should return correct path-specific properties');
|
||||||
|
st.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test case 5: Valid app.json with mixed properties
|
||||||
|
t.test('should merge regular and path-specific properties', (st) => {
|
||||||
|
const result = getAppConfig({
|
||||||
|
urlPath: '/hello',
|
||||||
|
appJsonPath: path.join(__dirname, 'data/valid/mixed.json')
|
||||||
|
});
|
||||||
|
st.equal(result.success, true, 'should be successful');
|
||||||
|
st.deepEqual(result.config, {
|
||||||
|
text: {
|
||||||
|
type: 'string'
|
||||||
|
},
|
||||||
|
'/hello': {
|
||||||
|
text: {
|
||||||
|
description: 'Welcome message',
|
||||||
|
type: 'string',
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 'should return merged properties');
|
||||||
|
st.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test case 6: Obscure property handling
|
||||||
|
t.test('should obscure values when obscure flag is true', (st) => {
|
||||||
|
const result = getAppConfig({
|
||||||
|
urlPath: '/hello',
|
||||||
|
appJsonPath: path.join(__dirname, 'data/valid/obscure.json')
|
||||||
|
});
|
||||||
|
st.equal(result.success, true, 'should be successful');
|
||||||
|
st.deepEqual(result.config, {
|
||||||
|
apiKey: {
|
||||||
|
description: 'API key for the service',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
obscure: true,
|
||||||
|
value: '********'
|
||||||
|
},
|
||||||
|
'/hello': {
|
||||||
|
text: {
|
||||||
|
description: 'Welcome message',
|
||||||
|
type: 'string',
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 'should obscure sensitive values');
|
||||||
|
st.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
@@ -45,6 +45,8 @@ test('unit tests', (t) => {
|
|||||||
t.end();
|
t.end();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Run validator tests
|
||||||
|
require('./validator');
|
||||||
|
|
||||||
const errInvalidInstruction = () => makeTask(logger, require('./data/bad/unknown-instruction'));
|
const errInvalidInstruction = () => makeTask(logger, require('./data/bad/unknown-instruction'));
|
||||||
const errUnknownProperty = () => makeTask(logger, require('./data/bad/unknown-property'));
|
const errUnknownProperty = () => makeTask(logger, require('./data/bad/unknown-property'));
|
||||||
|
|||||||
64
test/validator.js
Normal file
64
test/validator.js
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
const test = require('tape');
|
||||||
|
const { validateAppConfig } = require('../lib/validator');
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
// Load test data
|
||||||
|
const loadTestData = (category, name) => {
|
||||||
|
const filePath = path.join(__dirname, 'data', category, `${name}.json`);
|
||||||
|
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
||||||
|
};
|
||||||
|
|
||||||
|
test('App Config Validator', (t) => {
|
||||||
|
t.test('Valid Cases', (t) => {
|
||||||
|
// Test valid non-slash properties
|
||||||
|
const validNonSlash = loadTestData('valid', 'non-slash');
|
||||||
|
let result = validateAppConfig(validNonSlash);
|
||||||
|
t.ok(result.isValid, 'valid non-slash properties pass validation');
|
||||||
|
t.equal(result.errors.length, 0, 'no errors for valid non-slash properties');
|
||||||
|
|
||||||
|
// Test valid slash properties
|
||||||
|
const validSlash = loadTestData('valid', 'slash');
|
||||||
|
result = validateAppConfig(validSlash);
|
||||||
|
t.ok(result.isValid, 'valid slash properties pass validation');
|
||||||
|
t.equal(result.errors.length, 0, 'no errors for valid slash properties');
|
||||||
|
|
||||||
|
// Test valid mixed properties
|
||||||
|
const validMixed = loadTestData('valid', 'mixed');
|
||||||
|
result = validateAppConfig(validMixed);
|
||||||
|
t.ok(result.isValid, 'valid mixed properties pass validation');
|
||||||
|
t.equal(result.errors.length, 0, 'no errors for valid mixed properties');
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
t.test('Invalid Cases', (t) => {
|
||||||
|
// Test invalid non-slash properties
|
||||||
|
const invalidNonSlash = loadTestData('invalid', 'non-slash');
|
||||||
|
let result = validateAppConfig(invalidNonSlash);
|
||||||
|
t.notOk(result.isValid, 'invalid non-slash properties fail validation');
|
||||||
|
t.ok(result.errors.length > 0, 'errors reported for invalid non-slash properties');
|
||||||
|
|
||||||
|
// Test invalid slash properties
|
||||||
|
const invalidSlash = loadTestData('invalid', 'slash');
|
||||||
|
result = validateAppConfig(invalidSlash);
|
||||||
|
t.notOk(result.isValid, 'invalid slash properties fail validation');
|
||||||
|
t.ok(result.errors.length > 0, 'errors reported for invalid slash properties');
|
||||||
|
|
||||||
|
// Test invalid mixed properties
|
||||||
|
const invalidMixed = loadTestData('invalid', 'mixed');
|
||||||
|
result = validateAppConfig(invalidMixed);
|
||||||
|
t.notOk(result.isValid, 'invalid mixed properties fail validation');
|
||||||
|
t.ok(result.errors.length > 0, 'errors reported for invalid mixed properties');
|
||||||
|
|
||||||
|
// Test missing description
|
||||||
|
const missingDescription = loadTestData('invalid', 'missing-description');
|
||||||
|
result = validateAppConfig(missingDescription);
|
||||||
|
t.notOk(result.isValid, 'properties without description fail validation');
|
||||||
|
t.ok(result.errors.some(err => err.includes('description')), 'error message mentions missing description');
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user