gpii-universal
Version:
Cross platform, core components of the GPII personalization infrastructure.
257 lines (215 loc) • 12.9 kB
JavaScript
;
var fluid = require("infusion");
var gpii = fluid.registerNamespace("gpii");
var jqUnit = require("node-jqunit");
require("json5/lib/register");
// TODO: reenable once we're ready to fill in missing schema data in other platforms.
//var fs = require("fs");
var path = require("path");
require("gpii-json-schema");
require("./lib/validation");
require("../../transformer/src/js/Transformer");
var solutionSchema = require("../src/schemas/solution-schema.json5");
jqUnit.module("Sanity checks for Solutions Registry files.");
fluid.registerNamespace("gpii.tests.universal.solutionsRegistry.srFiles");
gpii.tests.universal.solutionsRegistry.srFiles.checkSolutionsFiles = function (that, globalValidator) {
var resolvedGenericTermsPath = fluid.module.resolvePath(that.options.genericPreferenceTermsPath);
var genericTerms = require(resolvedGenericTermsPath);
var resolvedSrPath = fluid.module.resolvePath(that.options.solutionsRegistryPath);
// TODO: reenable once we're ready to fill in missing schema data in other platforms.
//var files = fs.readdirSync(resolvedSrPath);
var files = ["win32.json5"];
fluid.each(files, function (filename) {
if (filename.match(/^.+\.json5?$/i)) {
var filePath = path.resolve(resolvedSrPath, filename);
var singleFileSolutions = require(filePath);
fluid.each(singleFileSolutions, function (solutionDef, solutionKey) {
gpii.tests.universal.solutionsRegistry.srFiles.checkSingleSolution(globalValidator, filename, solutionKey, solutionDef, genericTerms);
});
}
});
};
gpii.tests.universal.solutionsRegistry.srFiles.checkSingleSolution = function (globalValidator, filename, solutionKey, solutionDef, genericTerms) {
jqUnit.test("Sanity-checking solution '" + solutionKey + "' in file '" + filename + "'.", function () {
var isSolutionValid = gpii.tests.universal.solutionsRegistry.validateSinglePayload(
globalValidator,
solutionDef,
solutionSchema,
"solution file '" + filename + "' -> solution '" + solutionKey + "'"
);
jqUnit.assertTrue("Solution '" + solutionKey + "' in file '" + filename + "' should have a valid schema.", isSolutionValid);
gpii.tests.universal.solutionsRegistry.srFiles.checkDefaults(globalValidator, filename, solutionKey, solutionDef);
gpii.tests.universal.solutionsRegistry.srFiles.checkCapabilities(filename, solutionKey, solutionDef, genericTerms);
gpii.tests.universal.solutionsRegistry.srFiles.checkTransforms(filename, solutionKey, solutionDef, genericTerms);
gpii.tests.universal.solutionsRegistry.srFiles.checkWindowsSettingsHandlers(filename, solutionKey, solutionDef);
// TODO: Ensure that all forward and inverse transforms are sane, i.e. that they can at least be used with an empty object.
// TODO: Ensure that all references to generic preference settings in transforms actually refer to generic preference settings that exist.
// Discuss with Antranig how best to accomplish these checks, i.e. how much in the way of resolvers, etc. do we
// actually need to meaningfully sanity check transforms?
});
};
gpii.tests.universal.solutionsRegistry.srFiles.checkWindowsSettingsHandlers = function (filename, solutionKey, solutionDef) {
// These settings handlers require a deeper structure, i.e an object with properties.
var settingsHandlersDemandingObjects = [
"gpii.windows.nativeSettingsHandler",
"gpii.windows.spiSettingsHandler",
"gpii.windows.systemSettingsHandler",
"gpii.windows.wmiSettingsHandler"
];
var settingsHandlersDemandingDataTypes = [
"gpii.windows.registrySettingsHandler"
];
var objectivelyAwfulSettingsCount = 0;
var brokenRegistrySettingsCount = 0;
fluid.each(fluid.get(solutionDef, "settingsHandlers"), function (settingsHandlerDef, settingsHandlerKey) {
var settingsHandlerType = fluid.get(settingsHandlerDef, "type");
if (settingsHandlersDemandingObjects.indexOf(settingsHandlerType) !== -1) {
fluid.each(settingsHandlerDef.supportedSettings, function (settingDef, settingKey) {
var hasProperties = fluid.get(settingDef, "schema.properties") !== undefined;
if (!hasProperties) {
objectivelyAwfulSettingsCount++;
fluid.log(filename + " -> " + solutionKey + " -> " + settingsHandlerKey + " -> " + settingKey + " should have properties defined.");
}
var isObject = fluid.get(settingDef, "schema.type") === "object";
if (!isObject) {
fluid.log("WARNING: " + filename + " -> " + solutionKey + " -> " + settingsHandlerKey + " -> " + settingKey + " does not have the correct 'object' type value.");
}
});
}
if (settingsHandlersDemandingDataTypes.indexOf(settingsHandlerType) !== -1) {
var dataTypeKeys = Object.keys(fluid.get(settingsHandlerDef, "options.dataTypes"));
var supportedSettingsKeys = Object.keys(fluid.get(settingsHandlerDef, "supportedSettings"));
var keyCounts = {};
fluid.each([dataTypeKeys, supportedSettingsKeys], function (keys) {
fluid.each(keys, function (key) {
var existingValue = fluid.get(keyCounts, key) || 0;
fluid.set(keyCounts, key, existingValue + 1);
});
});
fluid.each(keyCounts, function (count, key) {
if (count !== 2) {
fluid.log("WARNING: " + filename + " -> " + solutionKey + " -> " + settingsHandlerKey + " -> " + key + " should have both a data type and/or supported settings entry.");
brokenRegistrySettingsCount++;
}
});
}
});
jqUnit.assertEquals("There should be no settings that lack a proper object structure.", 0, objectivelyAwfulSettingsCount);
jqUnit.assertEquals("There should be no mismatch between registry settings and data types.", 0, brokenRegistrySettingsCount);
};
// Ensure that all "capabilities" refer to a generic preference setting.
gpii.tests.universal.solutionsRegistry.srFiles.checkCapabilities = function (filename, solutionKey, solutionDef, genericTerms) {
var missingTermsCount = 0;
if (solutionDef.capabilities && solutionDef.capabilities.length) {
fluid.each(solutionDef.capabilities, function (capabilityKey) {
var unescapedKey = capabilityKey.replace(/\\/g, "");
var termDef = genericTerms[unescapedKey];
if (termDef === undefined) {
missingTermsCount++;
fluid.log("The capability '" + capabilityKey + "' specified in solution '" + solutionKey + "' does not exist.");
}
});
jqUnit.assertEquals("There should be no missing capabilities in solution '" + solutionKey + "'.", 0, missingTermsCount);
}
else {
jqUnit.assert("Entry '" + solutionKey + "' has no capabilities, which is fine.");
}
};
// Ensure that all transforms involving common terms refer to common terms that actually exist.
gpii.tests.universal.solutionsRegistry.srFiles.checkTransforms = function (filename, solutionKey, solutionDef, genericTerms) {
fluid.each(solutionDef.settingsHandlers, function (settingsHandlerDef) {
var allTransformTerms = [];
if (settingsHandlerDef.capabilitiesTransformations) {
var forwardTransformTerms = gpii.tests.universal.solutionsRegistry.srFiles.extractCommonTerms(settingsHandlerDef.capabilitiesTransformations);
if (forwardTransformTerms.length) {
allTransformTerms = allTransformTerms.concat(forwardTransformTerms);
}
}
if (settingsHandlerDef.inverseCapabilitiesTransformations) {
var inverseTransformTerms = gpii.tests.universal.solutionsRegistry.srFiles.extractCommonTerms(settingsHandlerDef.inverseCapabilitiesTransformations);
if (inverseTransformTerms.length) {
allTransformTerms = allTransformTerms.concat(inverseTransformTerms);
}
}
var missingTermsCount = 0;
fluid.each(allTransformTerms, function (transformTerm) {
var unescapedTermKey = transformTerm.replace(/\\\./g, ".");
var termExists = (fluid.get(genericTerms, [unescapedTermKey]) !== undefined);
if (!termExists) {
missingTermsCount++;
}
});
jqUnit.assertEquals("There should be no transforms that use missing common terms.", 0, missingTermsCount);
});
};
gpii.tests.universal.solutionsRegistry.srFiles.extractCommonTerms = function (toInspect) {
var foundTerms = [];
if (typeof toInspect === "object") {
fluid.each(toInspect, function (deepValue) {
var subSegments = gpii.tests.universal.solutionsRegistry.srFiles.extractCommonTerms(deepValue);
if (subSegments.length) {
foundTerms = foundTerms.concat(subSegments);
}
});
}
else if (typeof toInspect === "string" && toInspect.startsWith("http://registry.gpii.net/common/")) {
foundTerms.push(toInspect);
}
return foundTerms;
};
// If there are default values, test them against the schema.
gpii.tests.universal.solutionsRegistry.srFiles.checkDefaults = function (globalValidator, filename, solutionKey, solutionDef) {
var fileErrors = 0;
fluid.each(solutionDef.settingsHandlers, function (settingsHandler) {
fluid.each(settingsHandler.supportedSettings, function (supportedSetting, settingKey) {
// Check the specified default against its schema. This only works for simple settings, and not for
// deeply nested objects.
var defaultValue = fluid.get(supportedSetting, "schema.default");
if (defaultValue !== undefined) {
// Check the raw value
var isValid = gpii.tests.universal.solutionsRegistry.validateSinglePayload(
globalValidator,
defaultValue,
supportedSetting.schema,
"Default value for " + filename + " -> " + solutionKey + " -> " + settingKey
);
if (!isValid) {
fileErrors++;
}
// Transform the default value to any "intra-application" variant we might expect to see and ensure that it's still valid.
if (fluid.get(settingsHandler, ["capabilitiesTransformations", settingKey]) && settingsHandler.inverseCapabilitiesTransformations) {
var settingWrapper = {};
fluid.set(settingWrapper, [settingKey], defaultValue);
var invertedPayload = fluid.model.transformWithRules(settingWrapper, settingsHandler.inverseCapabilitiesTransformations);
if (Object.keys(invertedPayload).length > 0) {
var reinvertedPayload = fluid.model.transformWithRules(invertedPayload, settingsHandler.capabilitiesTransformations);
var reinvertedDefaultValue = fluid.get(reinvertedPayload, [settingKey]);
if (reinvertedDefaultValue !== defaultValue) {
// Warn on "softly equal" things like "truthy" and "falsy" values.
if (reinvertedDefaultValue != defaultValue) { // eslint-disable-line eqeqeq
fluid.log("ERROR: The capabilities transforms for " + solutionKey + " -> " + settingKey + " may result in data corruption.");
fileErrors++;
}
else {
fluid.log("WARNING: The capabilities transforms for " + solutionKey + " -> " + settingKey + " are only safe for truthy and falsy values.");
}
}
}
}
}
});
});
jqUnit.assertEquals("There should be no validation errors in solutions registry file '" + filename + "'.", 0, fileErrors);
};
fluid.defaults("gpii.tests.universal.solutionsRegistry.srFiles", {
gradeNames: ["fluid.component"],
solutionsRegistryPath: "%gpii-universal/testData/solutions",
genericPreferenceTermsPath: "%gpii-universal/testData/ontologies/flat.json5",
listeners: {
"onCreate.checkAll": {
funcName: "gpii.tests.universal.solutionsRegistry.srFiles.checkSolutionsFiles",
args: ["{that}", "{gpii.schema.validator}"]
}
}
});
gpii.tests.universal.solutionsRegistry.srFiles();