@contract-case/case-plugin-base
Version:
Plugin framework for writing plugins for the ContractCase test framework
1,366 lines (1,284 loc) • 61.2 kB
TypeScript
import { AnyCaseMatcher } from '@contract-case/case-plugin-dsl-types';
import { AnyCaseMatcherOrData } from '@contract-case/case-plugin-dsl-types';
import { AnyData } from '@contract-case/case-plugin-dsl-types';
import { AnyLeafOrStructure } from '@contract-case/case-plugin-dsl-types';
import { AnyMockDescriptor } from '@contract-case/case-plugin-dsl-types';
import { AnyState } from '@contract-case/case-plugin-dsl-types';
import { CaseMockDescriptorFor } from '@contract-case/case-plugin-dsl-types';
import { LookupableMatcher } from '@contract-case/case-plugin-dsl-types';
import { SetupInfoFor } from '@contract-case/case-plugin-dsl-types';
/**
* Converts actual data into a string, for printing
* @public
* @param actual - the actual data that ContractCase encountered
* @param indent - how many spaces to indent this string
* @returns a printable string
*/
export declare const actualToString: <T>(actual: T, indent?: number) => string;
/**
* Adds the current location to the context. Used to determine where we are when
* printing logs and errors.
*
* There are some semantics here:
*
* Locations are joined together with `.`. For example, calling:
*
* ```
* addLocation('b', addLocation('a', context))
* ```
*
* will result in `"a.b"` being the location at print time.
*
* If the location is part of an iteration, put it in square brackets, for
* example `[2]` or `[keyName]`. This will prevent the location logger from
* adding `.` between consecutive locations. For example:
*
* ```
* addLocation('[1]', addLocation('a', context))
* ```
*
* will result in `"a[1]"` being the location at print time.
*
* If the location starts with `:`, it's only logged during maintainer logs. For
* example:
*
* ```
* addLocation(':internalDetail', addLocation('a', context))
* ```
*
* will result in `"a"` during normal and debug logging, but `a:internalDetail`
* when maintainer logging (or deeper) is enabled
*
* @public
*
* @param location - a string representing the current location. Prefix with `:`
* if this location should only be printed during maintainer debugging
* @param context - the current {@link MatchContext}
* @returns a new {@link MatchContext} with updated location.
*/
export declare const addLocation: (location: string, context: MatchContext) => MatchContext;
/**
* TODO: Move this out of the plugin lib
*
* @internal
*/
export declare const applyNodeToContext: (caseNodeOrData: AnyCaseMatcherOrData | AnyMockDescriptor, context: MatchContext, runConfig?: Partial<RunContext>) => MatchContext;
/**
* The base type that all examples extend.
*
* This isn't really part of the plugin interface; it's only here
* because the ResultPrinter needs it, and the context holds the result printer.
*/
declare interface BaseCaseExample {
readonly states: AnyState[];
readonly mock: AnyMockDescriptor;
}
/**
* True if this context can never publish results. This is useful for
* determining whether warning logs should be printed if the verification won't
* have any effect
*
* @param context - MatchContext
* @returns True if this context can never publish results; false if it might publish
*/
export declare const cantPublish: (context: MatchContext) => boolean;
/**
* Indicates that the user has configured ContractCase incorrectly.
*
* Throw this during mock setup if you encounter configuration errors.
*
* @public
*/
export declare class CaseConfigurationError extends Error {
/**
* The error code to help the user debug this
*/
contractCaseErrorCode: ConfigurationErrorCode;
/**
* If there's a user facing stack trace, this will be set to a non-empty string.
*
* The stack trace will have multiple lines separated by '\\n'
*/
userFacingStackTrace: string;
/**
* Constructs a CaseConfigurationError.
*
* @param message - the message for this error
* @param context - the match context, used to add '(at $location)' to the
* message. You can also pass 'DONT_ADD_LOCATION' if you don't want to append
* this to the message.
* @param code - an optional ConfigurationErrorCode to aid the user in debugging.
*/
constructor(message: string, context: LogLevelContext | 'DONT_ADD_LOCATION', code?: ConfigurationErrorCode, userFacingStackTrace?: string);
}
/**
* Something went wrong in ContractCase internals.
*
* @remarks
* Use this when there is almost certainly a bug in the implementation of the contract case core or your plugin.
*
* If used during a plugin execution, please include the plugin name / bug reporting instructions in the message.
* @public
*/
export declare class CaseCoreError extends Error {
userFacingStackTrace: string;
constructor(message: string, context?: LogLevelContext, userFacingStackTrace?: string);
}
/**
* Union of all error data types.
* @public
*/
export declare type CaseError = MatchingError | ConfigurationError | TriggerError | VerificationError | RawMatchError;
/**
* All states of Examples.
*
* This isn't really part of the plugin interface; it's only here
* because the ResultPrinter needs it.
*/
export declare type CaseExample = SuccessfulCaseExample | FailedCaseExample | PendingCaseExample;
/**
* Helper type to extract a case matcher descriptor out of a list of descriptors
* @public
*/
export declare type CaseMatcherFor<KnownMatcherDescriptors, T extends string> = Extract<KnownMatcherDescriptors, IsCaseNodeForType<T>>;
/**
* This is thrown by ContractCase core to indicate that the user-provided
* trigger failed when we weren't expecting it to.
*
* @public
*/
export declare class CaseTriggerError extends Error {
userFacingStackTrace: string;
constructor(message: string, context?: LogLevelContext, userFacingStackTrace?: string);
}
/**
* Checks a matcher against some actual data and returns a Promise containing a MatchResult.
*
* For checks beyond this matcher, use {@link MatchContext#descendAndCheck} to
* descend into any children.
*
* @remarks
*
* This function must have no side effects.
* It may be called repeatedly on the
* same data by ContractCase during a run.
*
* It must not modify the matcher descriptor during a run, but you may generate
* alternate matcher descriptors when you call the descend methods on the context.
*
* Note that calling check and strip together must always return no errors:
*
* ```
* yourMatcher.check(
* descriptor,
* context,
* yourMatcher.strip(descriptor)
* ) // must be a `MatchResult` with no errors
* ```
* @public
* @typeParam T - a matcher descriptor
* @param matcher - the matcher descriptor
* @param matchContext - the {@link MatchContext} for this run
* @param actual - the actual data to check against
* @returns either a Promise containing a {@link MatchResult} or
* a raw {@link MatchResult}. The Promise return type is preferred.
*/
export declare type CheckMatchFn<T> = (matcher: T, matchContext: MatchContext, actual: unknown) => Promise<MatchResult> | MatchResult;
/**
* Combines multiple {@link MatchResult} objects / Promises containing match results.
* @public
*
* @param results - MatchResult or `Promise<MatchResult>` objects
* @returns a Promise containing the combined Match Results.
*/
export declare const combineResultPromises: (...results: (MatchResult | Promise<MatchResult>)[]) => Promise<MatchResult>;
/**
* Combines multiple {@link MatchResult} objects into one match result object
* @public
*
* @deprecated Prefer {@link combineResultPromises}
*
* @param results - MatchResult or `Promise<MatchResult>` objects
* @returns combined Match Results.
*/
export declare const combineResults: (...results: MatchResult[]) => MatchResult;
/**
* The data for a user configuration error.
*
* Don't create this directly, use `configurationError` to create one.
* @public
*/
export declare interface ConfigurationError {
type: typeof ERROR_TYPE_CONFIGURATION;
message: string;
/**
* The error code that is associated with this configuration error
* This should be a unique code specific to this kind of error that users
* could look up in the documentation for more information.
*/
code: string;
location: Array<string>;
toString: () => string;
}
export declare type ConfigurationErrorCode = keyof ErrorCodeDefinitions['configuration'];
/**
* These are the error codes, emitted by every CaseConfigurationError.
* The documentation here provides additional information that should hopefully help if the
* information in the error message is unclear.
*
* Some of the errors print advice. This advice can be overridden by setting
* an entry in the `adviceOverrides` configuration map (keyed by the error code
* that you want to override errors for). This feature exists for users who have
* wrapped ContractCase with some common boilerplate, or who have a dedicated
* team looking after their contract testing infrastructure.
*/
export declare interface ConfigurationErrorCodes {
/**
* Used for when a file access problem happened trying to write a contract file.
* This usually means ContractCase has been given a bad path, is out of disk
* space, or some other I/O error has occurred.
*
* The error message should have additional information here.
*/
DISK_IO_PROBLEM: 'DISK_IO_PROBLEM';
/**
* Used when an interaction definition isn't valid.
*
* Because interaction definitions are very flexible,
* it's sometimes possible to pass the wrong matcher at an inappropriate
* time. For example, it could be possible to specify that an http response
* code must be a complex object - which should never be possible.
*
* If you're getting this code, it means that the matching engine has
* discovered that the expectations in your interaction aren't valid.
*
* Although the type system tries to prevent invalid interaction
* definitions, this isn't always possible in all target languages, so
* ContractCase's default plugins try to detect this and fail with a helpful
* error message.
*
* Usually this means you'll need to update your interaction definition. The
* error message should have more information.
*
*/
BAD_INTERACTION_DEFINITION: 'BAD_INTERACTION_DEFINITION';
/**
* Used for when a configuration value is outside its normal range. The error message alongside this code should
* tell you what specifically went wrong.
*
* In most cases this class of error is prevented with the type system.
*/
INVALID_CONFIG: 'INVALID_CONFIG';
/**
* A lifecycle method was called out of order.
*
* Check that your code has the method calls in the right order.
*
* Although care has been taken to prevent invalid lifecycle calls from
* being possible, there are still some cases where this can happen.
*
* For contract definition, it should be:
*
* 1. Begin definition
*
* 2. Multiple calls to runInteraction or your language's equivalent of
* runRejectingInteraction
*
* 3. End record (writing the contract).
*
* Check that you're not accidentally reusing test instances between runs.
*
*/
INVALID_LIFECYCLE: 'INVALID_LIFECYCLE';
/**
* The current interaction was configured to have a particular state setup
* handler, but it was missing.
*
* State handlers are functions that you define to set up a particular state
* within your code (for example, an interaction on an HTTP provider might
* have a state named `'User with id 123 exists'`).
*
* This error indicates that Contractcase was expecting a named state handler,
* but it couldn't find it.
*
* This usually indicates a misconfiguration - check that you have provided
* a state handler with the exact name of the handler that was missing in
* the configuration of your test.
*
* If you need help investigating this error, you can set the configuration
* property `logLevel` to `'DEBUG'` to see a list of the configured state
* handlers.
*
*/
MISSING_STATE_HANDLER: 'MISSING_STATE_HANDLER';
/**
* Tried to publish verification results for a contract that doesn't have
* information on where to publish the verification results.
*
* This can happen if you're sharing contracts locally, but still have
* publishing verification results enabled.
*/
NON_BROKERED_CONTRACT: 'NON_BROKERED_CONTRACT';
/**
* The existing contract didn't match the new contract being written.
*
* This is an error when ContractCase is running in validate snapshot mode,
* where defined contracts are checked against the contract on disk, and
* a failure happens if the new contract is different.
*
* To address this, you'll need to run contract definitions with
* a changedContracts behaviour set to overwrite the contract instead of check.
*
* Please re-run your tests with one of:
*
* - The configuration property changedContracts is set to 'OVERWRITE'
*
* - The environment variable CASE_changedContracts=OVERWRITE
*
* If you see this on consecutive runs, please check
* that your contract doesn't contain randomness
* during contract definition
*/
OVERWRITE_CONTRACTS_NEEDED: 'OVERWRITE_CONTRACTS_NEEDED';
/**
* Used when there is no additional documentation for this error code.
*
* Long term, this code should be removed, and all configuration errors should have documentation.
*
* This code doesn't have any behaviour when set as an `adviceOverride`.
*/
UNDOCUMENTED: 'UNDOCUMENTED';
/**
* This error code is never emitted, it exists to allow the advice given in the crash reporter to be overridden.
*
* Hopefully, you didn't know the crash reporter exists - when ContractCase
* crashes, it prints the stack trace and asks the user to report the crash as
* a bug. This code can be used to replace the bug report request part of the crash report.
*/
CASE_CRASH_ADVICE: 'CASE_CRASH_ADVICE';
}
/**
* Constructs a data context object
*
* @internal
*
*/
export declare const constructDataContext: (makeLogger: (c: LogLevelContext) => Logger, resultPrinter: ResultFormatter, runConfig: Partial<RunContext>, defaults: Record<string, AnyData>, parentVersions: Array<string>) => DataContext;
/**
* TODO: Move this out of the plugin lib
*
* @internal
*/
export declare const constructMatchContext: (traversals: TraversalFns, makeLogger: (c: LogLevelContext) => Logger, makeLookup: (c: MatchContextWithoutLookup) => ContractLookupFns, resultPrinter: ResultFormatter, runConfig: Partial<RunContext>, defaults: Record<string, AnyData>, parentVersions: Array<string>) => MatchContext;
/**
* Represents a plugin for the ContractCase contract testing framework.
* A plugin can defines custom matchers or mock setups for testing different cases.
*
* @public
* @typeParam MatcherTypes - A union type of string constants for the matcher types supported by the plugin.
* @typeParam MockTypes - A union type of string constants for the mocks types supported by the plugin.
* @typeParam MatcherDescriptors - A union type of all matcher descriptor objects for the matchers supplied by this plugin.
* @typeParam MockDescriptors - T A union type of all mock descriptor objects for the mocks supplied by this plugin
* @typeParam AllSetupInfo - A union type representing the setup information required for all mocks supplied by this plugin.
*/
export declare type ContractCasePlugin<MatcherTypes extends string, MockTypes extends string, MatcherDescriptors extends IsCaseNodeForType<MatcherTypes>, MockDescriptors extends AnyMockDescriptor, AllSetupInfo> = {
description: PluginDescription;
matcherExecutors: {
[T in MatcherTypes]: MatcherExecutor<T, CaseMatcherFor<MatcherDescriptors, T>>;
};
setupMocks: {
[T in MockTypes]: MockExecutorFn<MockDescriptors, AllSetupInfo, T>;
};
};
/**
* Context to do with the contract file
*
* @internal
*/
export declare interface ContractFileConfig {
/**
* The current test run ID
*/
'_case:currentRun:context:testRunId': string;
/**
* The directory for contract files.
*
* Note that they may be in a subdirectory of this dir
*/
'_case:currentRun:context:contractDir': string;
/**
* The filename for the contract file (if known)
*/
'_case:currentRun:context:contractFilename'?: string;
/**
* Which contracts to write:
*
* * `'main'` Will write the main contract file
* * `'hash'` Will write the contract file, hashed by the contents
*
* These should never include a ',', in case we want to allow them
* to be specified by an environment variable
*/
'_case:currentRun:context:contractsToWrite': Array<'main' | 'hash'>;
/**
* Whether we should allow overwriting the contract file.
*
* This setting is ignored (assumed true) if contractDir is set
*
* Currently, this setting is only used internally, it is not exposed to users.
*/
'_case:currentRun:context:overwriteFile'?: boolean;
}
/**
* The part of the context that allows saving or looking up data
* bound to a specific context and contract
*
* @public
*/
export declare interface ContractLookupFns {
/**
* Looks up a previously saved matcher by unique name
*
* @param uniqueName - the name the matcher was saved with
*
* @returns the cached matcher
* @throws CaseConfigurationError if there is no matcher defined
*/
lookupMatcher: (uniqueName: string) => AnyCaseMatcherOrData;
/**
* Saves a matcher by the unique description. The description is generated
* from the matcher, and may be overridden if the matcher has a uniqueName.
*
* @param matcher - the matcher to save.
* @returns the cached matcher
* @throws CaseConfigurationError if there is no matcher defined
*/
saveLookupableMatcher: (matcher: AnyCaseMatcher) => void;
/**
* Adds a default variable to the *contract* (not the context). Primarily used
* by the state handler setup code.
*
* It is unlikely that plugins will need to call this code.
*
* Note that this function modifies the contract.
*
* @param matcher - the matcher to save.
* @returns the cached matcher
* @throws CaseConfigurationError if there is no matcher defined
*/
addDefaultVariable: (name: string, stateName: string, value: AnyCaseMatcherOrData) => [name: string, value: AnyCaseMatcherOrData];
/**
* This function adds a state variable to the *contract*. Primarily used
* by the state handler setup code.
*
* It is unlikely that plugins will need to call this function.
*
* @param matcher - the matcher to save.
* @returns the cached matcher
* @throws CaseConfigurationError if there is no matcher defined
*/
addStateVariable: (name: string, stateName: string, value: AnyCaseMatcherOrData) => [name: string, value: AnyCaseMatcherOrData];
/**
* Get a previously saved state variable, either from state or from the
* default value.
*
* @param name - the name of the variable
* @returns the matcher (or data) for the variable.
* @throws CaseConfigurationError if the variable isn't set.
*/
lookupVariable: (name: string) => AnyCaseMatcherOrData;
/**
* Convenience function so that mock executions can call out to user provided functions.
*
* Primarily used by the Function plugin, but may have other uses.
* @param handle - the handle to the function (must have been previously registered)
* @param callerArguments - the arguments to pass to the function (as an array)
* @returns a promise that is the result of the call, or a CaseConfigurationError if the function invocation failed.
*/
invokeFunctionByHandle: (handle: string, callerArguments: unknown[]) => Promise<unknown>;
}
/**
* DO NOT USE THIS IN YOUR OWN PLUGINS
*
* @remarks
* The prefix for ContractCase core plugin names. Plugin names with
* this prefix are treated as always loaded, log less debug information,
* and any errors in loading are treated as core crashes rather than user
* configuration errors.
*
* Other than the way logs and load failures are treated, there's no special
* treatment given to core plugins,
*
* @public
*/
export declare const CORE_PLUGIN_PREFIX: "_CaseCore:";
export declare interface CoreErrorCodes {
/**
* Used to control the advice printed when ContractCase crashes.
*/
CASE_CRASH_ADVICE: 'CASE_CRASH_ADVICE';
}
/**
* Creates a matcher descriptor for a lookupable matcher.
* @public
* @remarks
*
* Useful if you want to automatically name lookupable matcher descriptors in your plugin.
*
* Note that lookup matchers must have identical contents when rendered.
*
* @param uniqueName - the name for this lookupable matcher
* @param child - the contents of this lookupable matcher
* @returns a {@link LookupableMatcher}
*/
export declare const coreLookupMatcher: (uniqueName: string, child: AnyCaseMatcherOrData) => LookupableMatcher;
/**
* Convenience type for just the parts of the context that have data - everything in this type
* will be serialisable.
*
* @public
*/
export declare type DataContext = DefaultContext & Partial<InjectableContext> & Partial<ContractFileConfig> & RunContext & LogLevelContext & LogContext;
/**
* Helper type that could be data or a case node for this type
*
* @internal
*/
export declare type DataOrCaseNodeFor<KnownMatcherDescriptors, T extends string> = CaseMatcherFor<KnownMatcherDescriptors, T> | AnyLeafOrStructure;
/**
* The settings that are set as default context for a run
*
* @public
*/
export declare type DefaultContext = LogLevelContext & {
'_case:context:matchBy': typeof MATCH_BY_TYPE | typeof MATCH_BY_EXACT;
/**
* What the contract must be serialisable to - used by matchers for excluding
* values of number (etc) that are not valid in json
*/
'_case:context:serialisableTo': typeof SERIALISABLE_TO_JSON;
/**
* Whether we are currently writing (ie, defining) or reading (ie, verifying) a contract.
* This is only different to 'define' and 'verify' to avoid confusing terminology internally
*/
'_case:currentRun:context:contractMode': 'write' | 'read';
/**
* Whether or not we should print results during this run
*/
'_case:currentRun:context:printResults': boolean;
/**
* What's the connector client (ie, host language) for this run?
*/
'_case:currentRun:context:connectorClient': string;
/**
* How to generate the version for the system under test
*/
'_case:currentRun:context:autoVersionFrom': 'TAG' | 'GIT_SHA';
};
/**
* Represents an error because of configuration during test execution
* @public
*/
export declare const ERROR_TYPE_CONFIGURATION: "CONFIGURATION_ERROR";
/**
* Represents an error from a matcher. The values passed to `actual` and `expected`
* in a matching error are automatically serialised for pretty printing in error messages.
*
* @public
*/
export declare const ERROR_TYPE_MATCHING: "MATCHING_ERROR";
/**
* Represents an error that would be from a matcher, but there's no physical matcher.
* Otherwise identical to {@link ERROR_TYPE_MATCHING}.
*
* @public
*/
export declare const ERROR_TYPE_RAW_MATCH: "RAW_MATCH_ERROR";
/**
* Represents an error during the testResponse or testErrorResponse function
* @public
*/
export declare const ERROR_TYPE_TEST_RESPONSE: "TEST_RESPONSE_ERROR";
/**
* Represents an error because of the user supplied trigger
* @public
*/
export declare const ERROR_TYPE_TRIGGER: "TRIGGER_FUNCTION_ERROR";
/**
* Optional human-readable annotations to show alongside the actual value. You
* can use this to provide some context with your error message,
* like "expected an exception".
* @public
*/
export declare interface ErrorAnnotations {
/**
* Additional information to display before the expected value.
* If this is undefined or missing, nothing is shown.
*/
expected?: string | undefined;
/**
* Additional information to display before the actual value.
* If this is undefined or missing, nothing is shown.
*/
actual?: string | undefined;
}
export declare type ErrorCodeDefinitions = {
configuration: ConfigurationErrorCodes;
core: CoreErrorCodes;
};
export declare const ErrorCodes: ErrorCodeDefinitions;
/**
* Helper function that will return a {@link CaseError} if the test condition is met, or a non-erroring {@link MatchResult} otherwise.
*
* @public
* @param test - a boolean condition
* @param err - either an error or an array of errors
* @returns a {@link MatchResult} containing the errors if `test` is true, or a passing {@link MatchResult} otherwise.
*/
export declare const errorWhen: (test: boolean, err: CaseError | Array<CaseError>) => MatchResult;
/**
* An example where verification was attempted but failed. This might be due to
* a configuration, matching, or core error.
*
* TODO: It would be best to make it clear whether an example has been defined
* or verified. This would make it hard to accidentally assume a verification
* that wasn't run was successful.
*
* This isn't really part of the plugin interface; it's only here
* because the ResultPrinter needs it, and the context holds the result printer.
*/
declare interface FailedCaseExample extends BaseCaseExample {
readonly result: 'FAILED';
readonly errors: CaseError[];
}
/**
* This represents a mismatch during a case execution that isn't covered by a
* matcher (usually this is an expectation failure during a MockInteraction which
* means we can't run the matching engine for some reason).
*
* Most of the time you won't need to use this, because most matching errors
* come from matchers. Errors encountered during matcher execution should use
* {@link matchingError} instead.
*
* @public
*
*
* @param message - The message that describes this error
* @param actual - The actual value that was received
* @param code - A code that can be looked up in the documentation. This should
* be a unique code specific to this kind of error that users
* could look up in the documentation for more information.
* @param context - The match context this occurred in
* @param expected - An optional expected value (might be a description of what was expected)
* @param annotations - Optional annotations to provide additional information for the user about the values printed
* @returns CaseError
*/
export declare const failedExpectationError: (message: string, actual: unknown, code: string, context: MatchContext, expected: unknown, annotations?: ErrorAnnotations) => CaseError;
/**
* Folds this case matcher into the context
*
* @internal
*
* @param caseNode - any descriptor
* @param context - the current context
* @returns a context object containing the descriptor combined with the context
*/
export declare const foldIntoContext: (caseNode: AnyCaseMatcher | AnyMockDescriptor, context: MatchContext) => MatchContext;
/**
* Gets the plugin configuration (the mockConfig object keyed by your plugin
* short name) for the given plugin. This function validates the context and
* throws {@link CaseConfigurationError} if the mockConfig object is not set
* or the key is missing. You don't need to catch this exception, you can let
* it bubble up to the framework for it to be rendered to the user.
*
* This function doesn't know the expected shape of your configuration object;
* it's up to you to then validate the object has the appropriate fields set
* to valid values.
* @public
*
* @param context - the current {@link MatchContext}
* @param pluginShortName - the short name for the currently executing plugin
* @returns the value of `mockConfig[pluginShortName]`
* @throws CaseConfigurationError if the expected configuration keys are not
* set, or are set but not defined.
*/
export declare const getPluginConfig: (context: DataContext, description: PluginDescription) => Record<string, unknown>;
/**
* The part of the context that contains the base url under test.
*
* @internal
*/
export declare interface HasBaseUrlUnderTest {
'_case:currentRun:context:baseUrlUnderTest': string;
}
/**
* Convenience type combining base context and contract file context
*
* @internal
*/
export declare type HasContractFileConfig = DataContext & ContractFileConfig;
/**
* Tests whether a given {@link MatchResult} object has any errors.
* @public
*
* @param result - a {@link MatchResult} object
* @returns true if `result` has any errors
*/
export declare const hasErrors: (result: MatchResult | CaseError[]) => boolean;
/**
* A matcher descriptor that has an example
* @public
*/
export declare type HasExample<T extends AnyCaseMatcher> = T & {
'_case:matcher:example': unknown;
};
/**
* A type for the part of the context that contains the makeLookup factory
*
* TODO: Move this out of the plugin lib
*
* @internal
*/
declare interface HasMakeLookupFn {
makeLookup: (c: MatchContextWithoutLookup) => ContractLookupFns;
}
/**
* Tests whether a given {@link MatchResult} object has no errors.
* @public
*
* @param result - a {@link MatchResult} object
* @returns true if `result` has no errors (ie, is a passing {@link MatchResult})
*/
export declare const hasNoErrors: (result: MatchResult) => boolean;
/**
* Setup information for HTTP tests
* TODO: Move this out of the base plugin
*
* @internal
*/
export declare interface HttpTestContext {
baseUrl: string;
}
/**
* These elements of context are injectable by the user
*
* @internal
*/
declare interface InjectableContext {
'_case:currentRun:context:baseUrlUnderTest'?: string;
'_case:currentRun:context:contractMode': 'write' | 'read';
'_case:currentRun:context:autoVersionFrom': 'TAG' | 'GIT_SHA';
'_case:currentRun:context:adviceOverrides': Record<string, string>;
}
/**
* Type guard to determine if an object is a ContractCase matcher descriptor or not
* @public
* @param maybeMatcher - a matcher or data
* @returns true if `maybeMatcher` is a matcher descriptor, false if not
*/
export declare const isCaseNode: (maybeMatcher: unknown) => maybeMatcher is AnyCaseMatcher;
/**
* The base type for a case matcher descriptor that has this string constant
* @public
*/
export declare interface IsCaseNodeForType<T extends string> {
'_case:matcher:type': T;
}
/**
* Gets the string representation of the current context
*
* @internal
*/
export declare const locationString: (matchContext: LogLevelContext) => string;
/**
* Part of the context with the logger attached. Useful if you just want to pass
* the logging functions to something. This is used in a few places where the whole
* context isn't available (eg, before the context has been constructed).
*
* @public
*/
export declare interface LogContext {
/** Current logger */
logger: Logger;
/** Used for printing results (should not be called by plugins) */
resultPrinter: ResultFormatter;
/** Constructor for making the logger when moving to different locations (should not be called by plugins) */
makeLogger: (m: LogLevelContext) => Logger;
}
/**
* Describes the logger object
* @public
*/
export declare interface Logger {
/**
* Something has gone wrong during the execution of the test framework
*/
error: (message: string, ...additional: unknown[]) => Promise<void>;
/**
* It seems likely that there is a misconfiguration
*/
warn: (message: string, ...additional: unknown[]) => Promise<void>;
/**
* Information to help users find out what is happening during their tests
*/
debug: (message: string, ...additional: unknown[]) => Promise<void>;
/**
* Information to help maintainers debug what is happening in the test framework
*/
maintainerDebug: (message: string, ...additional: unknown[]) => Promise<void>;
/**
* Like maintainerDebug, but much deeper - including eg detailled matching docs, etc.
*/
deepMaintainerDebug: (message: string, ...additional: unknown[]) => Promise<void>;
}
/**
* The log levels available
* @public
*/
export declare type LogLevel = 'none' | 'error' | 'warn' | 'debug' | 'maintainerDebug' | 'deepMaintainerDebug';
/**
* LogLevelContext is the subset of the overall context object that's needed for
* logging. It exists so that it's possible to call log and error related
* functions no matter what context you're in.
*/
export declare type LogLevelContext = {
'_case:currentRun:context:parentVersions': Array<string>;
'_case:currentRun:context:logLevel': LogLevel;
'_case:currentRun:context:location': Array<string>;
};
/**
* Makes a passing {@link MatchResult} object.
* @public
*
* @returns a {@link MatchResult} object that has no errors.
*/
export declare const makeNoErrorResult: () => MatchResult;
/**
* Makes a {@link MatchResult} object.
* @public
*
* @param err - any {@link CaseError} objects. If none supplied, then it makes a passing {@link MatchResult} object
* @returns a {@link MatchResult} object.
*/
export declare const makeResults: (...err: CaseError[]) => MatchResult;
/**
* Indicates that we are doing an exact values match in this context
*
* @public
*/
export declare const MATCH_BY_EXACT: "exact";
/**
* Indicates that we are doing a general shape match in this context
*
* @public
*/
export declare const MATCH_BY_TYPE: "type";
/**
* The part of the context used during a matching run - contains traversal functions
* and any lookup functions that arbitrary matchers or mocks might need.
*
* @public
*/
export declare type MatchContext = DataContext & TraversalFns & ContractLookupFns & HasMakeLookupFn;
/**
* If the context is assignable to this type, it indicates
* that the matchers should default to exact matching
*
* @public
*/
export declare interface MatchContextByExact {
'_case:context:matchBy': 'exact';
}
/**
* If the context is assignable to this type, it indicates that
* the matchers should default to shape (ie, type) matching
*
* @public
*/
export declare interface MatchContextByType {
'_case:context:matchBy': 'type';
}
/**
* The parts of the context that don't have the lookup functions
*
* @remarks
* TODO: This is a convenience type for the Case Core, and could be moved out
* of the plugin lib.
*
* @internal
*/
export declare type MatchContextWithoutLookup = Omit<MatchContext, keyof ContractLookupFns | keyof HasMakeLookupFn>;
/**
* A MatcherExecutor contains the three functions
* needed to execute a matcher descriptor during a run.
* @public
* @remarks
* All functions must have no side effects.
*
* See the individual function types for more details.
*
* @typeParam matcherType - the string constant for this matcher descriptor
* @typeParam T - the matcher descriptor object type
*/
export declare interface MatcherExecutor<MatcherType extends string, T extends IsCaseNodeForType<MatcherType>> {
/** Describes the matcher descriptor in english */
describe: NameMatcherFn<T>;
/** Checks the matcher against some actual data */
check: CheckMatchFn<T>;
/** Strips the matchers from this descriptor, returning example data */
strip: StripMatcherFn<T>;
/** Validate the configured arguments of this matcher */
validate: ValidateMatcherFn<T>;
}
/**
* Converts a matcher or data into a human friendly string for printing
* @public
* @remarks
* This is currently the same implementation as {@link actualToString}
*
* TODO: Add a better implementation that walks the tree and prints something
* like the DSL does for easy readability
*
* @param actual - the matcher descriptor
* @param indent - how many spaces to indent this string
* @returns a printable string
*/
export declare const matcherToString: <T>(actual: T, indent?: number) => string;
/**
* Describes the data of an error encountered during a matcher execution. Don't
* create these manually, use `matchingError` to create them.
*
* @privateRemarks
*
* TODO: link `matchingError` in this documentation when tsdoc supports better imports
* @public
*/
export declare interface MatchingError {
type: typeof ERROR_TYPE_MATCHING;
/**
* A human readable message for this error
*/
message: string;
/**
* What data was expected (could be an english description, or the raw expected data. Prefer raw data if possible)
*/
expected: unknown;
/**
* The matcher descriptor that emitted this error
*/
matcher: AnyCaseMatcher;
/**
* The actual data that was encountered (could be an english description, or the raw expected data. Prefer raw data if possible)
*/
actual: unknown;
/**
* The ContractCase run location array
*/
location: Array<string>;
/**
* A helper to make it easy to print these errors during debugging
*
* @returns A combination of the location and error messages.
*/
toString: () => string;
/**
* Optional annotations to show alongside the actual value. You can use this
* to provide some context, like "expected an exception".
*/
annotations?: ErrorAnnotations;
}
/**
* Creates a mismatched matcher expectations error
*
* @public
*
* @param matcher - The matcher that generated this error
* @param message - The message that describes this error
* @param actual - The actual value that was received
* @param context - The match context this occurred in
* @param expected - An optional expected value. If this is not provided or is `undefined`, it is calculated using `descendAndStrip`
* @param annotations - Optional annotations to provide additional information for the user about the values printed
* @returns CaseError
*/
export declare const matchingError: (matcher: AnyCaseMatcher, message: string, actual: unknown, context: MatchContext, expected?: unknown, annotations?: ErrorAnnotations) => CaseError;
/**
* Describes the result of calling a matcher executor. If there are no errors, the array should be empty.
* @public
*/
export declare type MatchResult = Array<CaseError>;
/**
* Custom configuration for plugin mocks. Plugins must validate their config
* data at the time it needs it - throw a CaseConfigurationError if required
* configuration keys are missing, or malformed.
*
* The format is a double Record - the first key is the plugin short name, the
* second key is the configuration key. Values are arbitrary.
*
* @internal
*/
export declare interface MockConfig {
'_case:currentRun:context:mockConfig': Record<string, Record<string, unknown>>;
}
/**
* Represents the data produced by an invocation of a mock, ready for assertion.
* @public
* @typeParam AllSetupInfo - All known SetupInfo objects
* @typeParam T - the type of the mock descriptor that this data is for
*/
export declare type MockData<AllSetupInfo, T extends string> = {
config: SetupInfoFor<AllSetupInfo, T>;
/**
* Returns the results of the mock invocation, but without any assertions run on it.
*
* If your mock generates its own triggers, generate and call them during the execution of the assertableData method.
* If the trigger fails, throw either a CaseConfigurationError (in the case of
* user-supplied configuration mistakes or their code throwing unexpected
* errors), or a CaseCoreError if your plugin has crashed.
*
* @returns the {@link MockOutput} that can be asserted on.
*/
assertableData: () => Promise<MockOutput>;
};
/**
* A function that will set up and run a mock.
*
* During the execution of this function, you should validate the mock
* descriptor is correctly formed, and any configuration properties on the
* context that your plugin requires are present and correctly formed.
*
* Additionally, any listeners (eg http servers) that the mock requires should be
*
* @public
* @typeParam AllMockDescriptors - All known MockDescriptor objects
* @typeParam AllSetupInfo - All known SetupInfo objects
* @typeParam T - the type of the mock descriptor that this function consumes
* @param mock - The mock descriptor object that the user provides
* @param context - The {@link MatchContext} object for this run
* @returns - a Promise of the {@link MockData} resulting from this execution
*/
export declare type MockExecutorFn<AllMockDescriptors extends AnyMockDescriptor, AllSetupInfo, T extends string> = (mock: CaseMockDescriptorFor<AllMockDescriptors, T>, context: MatchContext) => Promise<MockData<AllSetupInfo, T>>;
/**
* Represents the output state of a mock's execution, including what was expected.
* @public
*/
export declare type MockOutput = {
/**
* The actual data received by this mock, formatted by the executor.
*/
actual: unknown;
/**
* The expectations at this point from the matcher
*/
expected: AnyCaseMatcherOrData;
/**
* The current match context at this point in the matching
*/
context: MatchContext;
};
/**
* During a matcher execution, this function can be called to ensure that the
* provided matcher resolves to number.
*
* @remarks
*
* Use it if you expect that it's not possible for the matcher to
* resolve to anything other than a number, as it throws a
* {@link CaseConfigurationError}.
*
* If you use this function during a `check` operation, then the
* `CaseConfigurationError` will ultimately become a `CaseCoreError`, because
* it's not supposed to be possible to throw exceptions from a `check`
*
* If you're using it in `check`, make sure you also call it in `validate`.
* @public
*
* @param matcher - any matcher descriptor or data object
* @param context - the current {@link MatchContext}
* @returns the number value that the matcher resolves to
* @throws a {@link CaseConfigurationError} if the matcher doesn't resolve to a number.
*/
export declare const mustResolveToNumber: (matcher: AnyCaseMatcherOrData, context: MatchContext) => number;
/**
* During a matcher execution, this function can be called to ensure that the
* provided matcher resolves to a string when stripped with `stripMatchers`.
*
* @remarks
*
* Use it if you expect that it's not possible for the matcher to
* resolve to anything other than a string, as it throws a
* {@link CaseConfigurationError}.
*
* If you use this function during a `check` operation, then the
* `CaseConfigurationError` will ultimately become a `CaseCoreError`, because
* it's not supposed to be possible to throw exceptions from a `check`
*
* If you're using it in `check`, make sure you also call it in `validate`.
*
* @public
*
* @param matcher - any matcher descriptor or data object
* @param context - the current {@link MatchContext}
* @returns the string value that the matcher resolves to
* @throws a {@link CaseConfigurationError} if the matcher doesn't resolve to a string.
*/
export declare const mustResolveToString: (matcher: AnyCaseMatcherOrData, context: MatchContext) => string;
/**
* Extracts the name for this matcher in an English, human readable format.
*
* @remarks
* CAUTION: Any two matchers that produce the same string MUST have
* the exact same matching behaviour in all cases. The core relies on this
* property.
*
* This function must have no side effects,
* it may be called repeatedly on the
* same data by ContractCase during a run.
*
* @public
* @typeParam T - a matcher descriptor
* @param matcher - the matcher descriptor
* @param matchContext - the {@link MatchContext} for this run
* @returns the raw example data
*/
export declare type NameMatcherFn<T> = (matcher: T, matchContext: MatchContext) => string;
/**
* Helper function that will name this mock if it isn't already named.
* @public
* @remarks
*
* You probably don't need to use this function, it is used by ContractCase internals.
* TODO: it could be moved to the core.
*
* @param mock - a Mock Descriptor object
* @param context - the {@link MatchContext} for this run
* @returns a mock descriptor object where the request and response are guaranteed to be named.
*/
export declare const nameMock: <M extends AnyMockDescriptor>(mock: M, context: MatchContext) => M;
/**
* An example that hasn't been verified in this run.
*
* TODO: It would be best to make it clear whether an example has been defined
* or verified. This would make it hard to accidentally assume a verification
* that wasn't run was successful.
*
* This isn't really part of the plugin interface; it's only here
* because the ResultPrinter needs it, and the context holds the result printer.
*/
declare interface PendingCaseExample extends BaseCaseExample {
readonly result: 'PENDING';
}
/**
* Describes the plugin name and version
* @public
*/
export declare type PluginDescription = {
/**
* The full name of the plugin, for humans. This is printed in error messages
* about the plugin.
*/
humanReadableName: string;
/**
* The short name of the plugin, for convenience in configuration.
*
* This is the key used by the mockConfig configuration property.
* It should be reasonably unique. If two plugins share a shortName, they
* might not be able to be loaded during the same run, as plugins validate
* their `mockConfig[shortName]` setting.
*
* If you have two plugins that need the same configuration properties,
* it's fine for them to share their shortName.
*/
shortName: string;
/**
* A unique machine name, used to reason about the plugin's load state when
* loading a plugin. This must be unique in the plugin ecosystem,
* as two plugins with the same uniqueMachineName can't be loaded in the same
* contract. For this reason, it is recommended you namespace your plugin
* names with a common prefix.
*
* This is different from the shortname above, since you might have multiple
* plugins wanting to share configuration, and the global uniqueness
* constraint means that the uniqueMachineName might be too clumsy for a user
* to comfortably use them as a configuration key.
*
* Plugins for distribution with ContractCase have their unique names
* prefixed with {@link CORE_PLUGIN_PREFIX}. This is used to control the
* logging and error messages during plugin loads. To keep the logs and error
* messages correct, only use this prefix if you are developing core a plugin.
*/
uniqueMachineName: string;
/**
* The version of this plugin, used to reason about it at load time.
* Must be a semantic version string. See {@link https://semver.org/} for
* details.
*/
version: string;
};
/**
* The functions that allows saving or looking up matchers
* with a provided context
*
* @internal
*/
export declare interface RawLookupFns {
lookupMatcher: (uniqueName: string, context: MatchContextWithoutLookup) => AnyCaseMatcherOrData;
saveLookupableMatcher: (matcher: AnyCaseMatcher, context: MatchContextWithoutLookup) => void;
addVariable: (name: string, type: 'default' | 'state', stateName: string, value: AnyCaseMatcherOrData, context: MatchContextWithoutLookup) => [name: string, value: AnyCaseMatcherOrData];
lookupVariable: (name: string, context: MatchContextWithoutLookup) => AnyCaseMatcherOrData;
}
/**
* Describes the data of an error encountered during an execution, where an
* expectation of a mock couldn't be met (eg, expected a call when we didn't get
* one)
*
* Don't create these manually, use `failedExpectationError` to create them.
*
* @privateRemarks
* TODO: link `failedExpectationError` in this documentation when tsdoc supports better imports
* @public
*/
export declare interface RawMatchError {
type: typeof ERROR_TYPE_RAW_MATCH;
/**
* A human readable message for this error
*/
message: string;
/**
* What we expected to happen (in most cases this will be an English string that describes the situation)
*/
expected: unknown;
/**
* The error code that is associated with this raw match.
* This should be a unique code specific to this kind of error that users
* could look up in the documentation for more information.
*/
code: string;
/**
* What actually happened (in most cases this will be an English string that describes the situation)
*/
actual: unknown;
/**
* The ContractCase run location array
*/
location: Array<string>;
toString: () => string;
/**
* Optional annotations to show alongside the actual value. You can use this
* to provide some context, like "expected an exception".
*/
annotations?: ErrorAnnotations;
}
/**
* The result formatter is used for printing results. The core doesn't control how these are
* formatted and printed - this is up to the host DSL wrappers.
*
* However, it does return a string that is close to what the host wrappers are expected to format.
* This is useful to be able to throw an error that looks the same as the host reporting.
*
* @internal
*/
export declare type ResultFormatter = {
printError: (e: CaseError, context: DataContext) => string;
printSuccessTitle: (example: CaseExample, index: string, context: DataContext) => string;
printFailureTitle: (example: CaseExample, index: string, context: DataContext) => string;
pr