UNPKG

@contract-case/case-core

Version:

Core functionality for the ContractCase contract testing suite

166 lines (164 loc) 10.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.BaseCaseContract = void 0; const case_plugin_base_1 = require("@contract-case/case-plugin-base"); const case_plugin_dsl_types_1 = require("@contract-case/case-plugin-dsl-types"); const structure_1 = require("./structure"); const config_1 = require("./config"); const defaultTestId_1 = require("./defaultTestId"); const diffmatch_1 = require("../diffmatch"); const entities_1 = require("../entities"); const plugins_1 = require("./plugins"); class BaseCaseContract { currentContract; initialContext; /** * The currently executing context. Used to make it easy to do things * like get state variables during a stripMatchers call inside a test trigger */ runningContext; userProvidedFunctions; constructor(description, config, defaultConfig, resultPrinter, makeLogger, parentVersions, userProvidedFunctions = {}) { this.currentContract = (0, structure_1.makeContract)(description); this.userProvidedFunctions = userProvidedFunctions; const contractFns = { lookupMatcher: this.lookupMatcher.bind(this), saveLookupableMatcher: this.saveLookupableMatcher.bind(this), addVariable: this.addVariable.bind(this), lookupVariable: this.lookupVariable.bind(this), }; const invokeHandle = this.invokeFunctionByHandle.bind(this); const makeLookup = (context) => ({ lookupMatcher: (uniqueName) => contractFns.lookupMatcher(uniqueName, context), saveLookupableMatcher: (matcher) => contractFns.saveLookupableMatcher(matcher, context), addDefaultVariable: (name, stateName, value) => contractFns.addVariable(name, 'default', stateName, value, context), addStateVariable: (name, stateName, value) => contractFns.addVariable(name, 'state', stateName, value, context), lookupVariable: (name) => contractFns.lookupVariable(name, context), invokeFunctionByHandle: (handle, callerArguments) => invokeHandle(handle, callerArguments, context), }); this.initialContext = (0, case_plugin_base_1.constructMatchContext)(diffmatch_1.traversals, makeLogger, makeLookup, resultPrinter, { ...(0, config_1.configToRunContext)({ ...defaultConfig, ...config }) }, defaultConfig, parentVersions); this.runningContext = this.initialContext; if (this.initialContext['_case:currentRun:context:testRunId'] === defaultTestId_1.DEFAULT_TEST_ID) { this.initialContext.logger.warn("The 'testRunId' config property has not been set for this run. It is recommended to set it for each CaseContract() or CaseVerifier() invocation."); } this.initialContext.logger.maintainerDebug(`Running on node ${process.version}, arch ${process.arch}`); (0, plugins_1.loadCorePlugins)(this.initialContext); } registerFunction(handle, invokeableFn) { if (handle in this.userProvidedFunctions) { const message = `Registering a function with handle '${handle}', but a function with this handle is already registered. Make sure you are only calling registerHandle once.`; this.initialContext.logger.error(message); this.initialContext.logger.deepMaintainerDebug('When trying to register the duplicate, the userProvidedFunctions were:', this.userProvidedFunctions); throw new case_plugin_base_1.CaseConfigurationError(message, 'DONT_ADD_LOCATION'); } this.initialContext.logger.debug(`Registered a user-provided invokable function with name '${handle}'`); this.userProvidedFunctions[handle] = invokeableFn; } invokeFunctionByHandle(handle, callerArguments, context) { return Promise.resolve() .then(() => { context.logger.maintainerDebug(`Invoking function by handle: '${handle}', with callerArguments: `, callerArguments); const invokeableFn = this.userProvidedFunctions[handle]; if (invokeableFn == null) { const message = `Tried to invoke a user-provided function with the handle '${handle}', but it didn't exist\nMake sure you have used registerFunction to define a function with this handle when setting up your test`; context.logger.error(message); context.logger.deepMaintainerDebug('When trying to add the duplicate, the userProvidedFunctions were:', this.userProvidedFunctions); throw new case_plugin_base_1.CaseConfigurationError(message, context); } return invokeableFn; }) .then((invokeableFn) => invokeableFn(...callerArguments)); } lookupVariable(name, context) { if (this.currentContract === undefined) { context.logger.error(`lookupVariable was called without initialising the contract file. This should not be possible.`); throw new case_plugin_base_1.CaseCoreError('Contract was not initialised at the time that lookupVariable was called'); } const defaultVariable = (0, structure_1.findVariable)(this.currentContract, name, context); if (defaultVariable === undefined) { throw new case_plugin_base_1.CaseConfigurationError(`Variable '${name}' was requested but appears not to be set. Check that the variable is spelt correctly, and the states for this mock are correctly setup`, context, 'BAD_INTERACTION_DEFINITION'); } return (0, entities_1.coreShapedLike)(defaultVariable); } addVariable(name, type, stateName, value, context) { context.logger.maintainerDebug(`Setting state variable '${name}', in test '${context['_case:currentRun:context:testName']}' to value:`, value); if (this.currentContract === undefined) { context.logger.error(`addVariable was called by state '${stateName}' without initialising the contract file. This should not be possible.`); throw new case_plugin_base_1.CaseCoreError('Contract was not initialised at the time that addVariable was called'); } this.currentContract = (0, structure_1.addVariable)(this.currentContract, name, type, (0, entities_1.coreShapedLike)(value), context); return [name, value]; } saveLookupableMatcher(namedMatcher, context) { if (this.currentContract === undefined) { context.logger.error('saveNamedMatcher was called without initialising the contract file. This should not be possible.'); throw new case_plugin_base_1.CaseCoreError('Contract was not initialised at the time that saveNamedMatcher was called'); } if (!(0, case_plugin_dsl_types_1.isLookupableMatcher)(namedMatcher)) { context.logger.error('Non-lookup matcher incorrectly received in saveLookupMatcher. Matcher follows', namedMatcher); throw new case_plugin_base_1.CaseCoreError(`A non-lookup matcher was passed to saveLookupableMatcher. This is an error in a matcher or mock implementation. Please open an issue.`, context); } this.currentContract = (0, structure_1.addLookupableMatcher)(this.currentContract, namedMatcher, context); return this.currentContract; } lookupMatcher(uniqueName, context) { if (!this.currentContract) { context.logger.error('lookupMatcher was called without initialising the contract file. This should not be possible.'); throw new case_plugin_base_1.CaseCoreError('Contract was not initialised at the time that lookupMatcher was called'); } // Have to pull this out, because typescript can't see that it's the same result even if we call twice const possibleMatch = (0, structure_1.findMatcher)(this.currentContract, uniqueName, context); if (possibleMatch !== undefined) { return possibleMatch; } throw new case_plugin_base_1.CaseConfigurationError(`Contract did not contain a matcher with the name '${uniqueName}'. This can happen if you have an interaction that asks for a named matcher before it has been defined. It can also happen if the named matcher has been misspelt `, context, 'BAD_INTERACTION_DEFINITION'); } /** * Strips the matchers from a (potentially) case matcher and returns * the concrete example it would match. * * Note this isn't the only example it could match - just the default version. * * WARNING: If called outside a test, this function runs using the context of * the last test run. This means that state variables might not be correct. * Perhaps we should consider making it an error to call this if a test * isn't running? * * @param matcherOrData - the matcher or data to strip the matchers from * @returns the concrete example */ stripMatchers(matcherOrData) { this.runningContext.logger.maintainerDebug('Stripping matchers from: ', matcherOrData); this.runningContext.logger.deepMaintainerDebug('At the time of this strip, the current context is', this.runningContext); this.runningContext.logger.deepMaintainerDebug('And the lookupable variables are:', Object.keys(this.currentContract.matcherLookup).filter((key) => key.startsWith('variable'))); const strippedResult = diffmatch_1.traversals.descendAndStrip(matcherOrData, (0, case_plugin_base_1.applyNodeToContext)(matcherOrData, this.runningContext)); this.runningContext.logger.maintainerDebug('Stripped result was: ', strippedResult); return strippedResult; } /** * Checks if a matcher matchers some actual data. * * This is only exposed for tests at the moment - if it were exposed further, * we'd need to use the running test context like stripMatchers does. * * * WARNING: If called outside a test, this function runs using the context of * the last test run. This means that state variables might not be correct. * * @param matcherOrData - the matcher or data that is expected * @param actual - the actual data to match against * @returns a {@link MatchResult} object with the results of the match */ checkMatch(matcherOrData, actual) { return Promise.resolve() .then(() => diffmatch_1.traversals.selfVerify(matcherOrData, (0, case_plugin_base_1.applyNodeToContext)(matcherOrData, this.initialContext))) .then(() => diffmatch_1.traversals.descendAndCheck(matcherOrData, (0, case_plugin_base_1.applyNodeToContext)(matcherOrData, this.initialContext), actual)); } } exports.BaseCaseContract = BaseCaseContract; //# sourceMappingURL=BaseCaseContract.js.map