UNPKG

@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
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