@contract-case/case-core
Version:
Core functionality for the ContractCase contract testing suite
105 lines • 6.21 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.ContractVerifierConnector = void 0;
const async_mutex_1 = require("async-mutex");
const case_plugin_base_1 = require("@contract-case/case-plugin-base");
const ReadingCaseContract_1 = require("../../core/ReadingCaseContract");
const dependencies_1 = require("../dependencies");
const config_1 = require("../../core/config");
const readContractFromStore = (config, reader) => {
if (config.contractFilename !== undefined &&
typeof config.contractFilename === 'string') {
return [reader.readContract(config.contractFilename)];
}
if (config.contractDir !== undefined &&
typeof config.contractDir === 'string') {
return reader.readContractsFromDir(config.contractDir);
}
throw new case_plugin_base_1.CaseConfigurationError('No contractFilename or contractDir specified. Must provide one of these so that Case can find the contract(s) to verify', 'DONT_ADD_LOCATION', 'INVALID_CONFIG');
};
class ContractVerifierConnector {
contracts;
config;
callback;
dependencies;
context;
parentVersions;
// This is needed so that each contract runs one at a time
// (this connector can be passed multiple contracts)
mutex;
constructor(userConfig, callback, printer, parentVersions, dependencies = (0, dependencies_1.readerDependencies)(printer)) {
this.dependencies = dependencies;
this.parentVersions = parentVersions;
this.config = {
...dependencies.defaultConfig,
...(0, config_1.configFromEnv)(),
...userConfig,
};
this.context = // TODO: Extract constructDataContext to somewhere more DRY
(0, case_plugin_base_1.constructDataContext)(this.dependencies.makeLogger, this.dependencies.resultFormatter, {
...(0, config_1.configToRunContext)(this.config),
}, dependencies.defaultConfig, parentVersions);
const store = this.dependencies.makeContractStore(this.context);
this.contracts = readContractFromStore(this.config, store);
this.callback = callback;
this.mutex = new async_mutex_1.Mutex();
}
getAvailableContractDescriptions() {
return this.contracts.map((link) => link.contents.description);
}
verifyContract(invoker, configOverride = {}, invokeableFns = {}) {
const mergedConfig = { ...this.config, ...configOverride };
if (typeof mergedConfig.providerName !== 'string') {
throw new case_plugin_base_1.CaseConfigurationError(`Must provide a providerName to verify (received '${mergedConfig.providerName}').`, 'DONT_ADD_LOCATION', 'INVALID_CONFIG');
}
this.context.logger.debug(`There are ${this.contracts.length} contracts loaded (this may include contracts that don't belong to this run)`);
this.contracts
.filter((item) => item.contents.description?.providerName !== mergedConfig.providerName)
.forEach((item) => {
this.context.logger.debug(`Skipping ${item.filePath} because it is not for the provider '${mergedConfig.providerName}' (It was for '${item.contents.description?.providerName}' instead)`);
});
const caseContractsForProvider = this.contracts.filter((item) => item.contents.description?.providerName === mergedConfig.providerName);
caseContractsForProvider
.filter((item) => typeof mergedConfig.consumerName !== 'undefined' &&
item.contents.description?.consumerName !== mergedConfig.consumerName)
.forEach((item) => {
this.context.logger.debug(`Skipping ${item.filePath} because it is not for the consumer '${mergedConfig.consumerName}' (It was for '${item.contents.description?.consumerName}' instead)`);
});
const contractsToVerify = caseContractsForProvider.filter((item) => typeof mergedConfig.consumerName === 'undefined' ||
item.contents.description?.consumerName === mergedConfig.consumerName);
if (contractsToVerify.length === 0) {
throw new case_plugin_base_1.CaseConfigurationError("No contracts were matched for verification. Try this run again with logLevel: 'debug' to find out more", 'DONT_ADD_LOCATION');
}
if (mergedConfig.internals == null) {
throw new case_plugin_base_1.CaseCoreError('Verify contract was called with no internals set - this is an error in the caller, probably the language specific wrapper');
}
if (contractsToVerify.length > 1) {
this.context.logger.debug('*** Multiple contracts are being verified ***');
this.context.logger.debug('Note that the following debug log may contain interactions from any contract in any order');
}
const results = contractsToVerify.map((contractLink, index) => {
this.context.logger.debug(`Verifying contract from file '${contractLink.filePath}'`);
const contractVerifier = new ReadingCaseContract_1.ReadingCaseContract(contractLink.contents, this.dependencies, {
...mergedConfig,
coreLogContextPrefix: contractsToVerify.length > 1 ? `Contract[${index}]` : '',
}, this.parentVersions, this.mutex);
Object.entries(invokeableFns).forEach(([key, value]) => {
contractVerifier.registerFunction(key, value);
});
return contractVerifier.verifyContract(invoker, this.callback);
});
if (mergedConfig.internals.asyncVerification) {
this.context.logger.maintainerDebug(`Awaiting async verification`);
return Promise.all(results).then(() => {
this.context.logger.maintainerDebug(`Async verification complete (Success)`);
}, (e) => {
this.context.logger.maintainerDebug(`Async verification complete (Error: ${e.message})`);
throw e;
});
}
this.context.logger.maintainerDebug(`Synchronous verification complete`);
return undefined;
}
}
exports.ContractVerifierConnector = ContractVerifierConnector;
//# sourceMappingURL=ContractVerifierConnector.js.map