UNPKG

@contract-case/case-core

Version:

Core functionality for the ContractCase contract testing suite

105 lines 6.21 kB
"use strict"; 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