UNPKG

cumulocity-cypress

Version:
1,311 lines (1,299 loc) 58.9 kB
import { Request as Request$1, RequestHandler, Express } from 'express'; import winston from 'winston'; import { IncomingMessage, ServerResponse, ClientRequest, Server } from 'http'; import { IAuthentication, ICredentials, Client, IResult, IFetchResponse, IResultList, IFetchOptions } from '@c8y/client'; import debug from 'debug'; import { FormatFn } from 'morgan'; import { Options } from 'http-proxy-middleware'; import { Har } from 'har-format'; export { Content, Entry, Har, Header, PostData, QueryString } from 'har-format'; /** * Interface for a preprocessor to apply transformations to `C8yPact` records * before critical operations such as matching or saving. A preprocessor can be used to * unify records by removing or obfuscating sensitive data, picking only certain keys to keep, * or applying regex substitutions to values. Preprocessors operate in-place on the * given object, which can be a full `C8yPact`, an individual `C8yPactRecord`, or a * plain object such as `Cypress.Response`. * * The default implementation is `C8yDefaultPactPreprocessor`, which supports the * operations and key-path syntax described in `C8yPactPreprocessorOptions`. * * See {@link C8yDefaultPactPreprocessor} for default implementation. * */ interface C8yPactPreprocessor { /** * Configuration options used by the preprocessor. */ readonly options?: C8yPactPreprocessorOptions; /** * Applies the preprocessor options (rules) to the given object in-place. * * When `obj` is a `C8yPact` (i.e. contains a `records` array), all records * are processed individually. For a plain object, including `Cypress.Response` * or `C8yPactRecord`, the object itself is processed. * * Operations are applied in this sequence: * 1. **pick** — remove any keys not listed * 2. **obfuscate** — replace values with the obfuscation pattern * 3. **regexReplace** — apply regex substitutions * 4. **ignore** — delete values entirely * * @param obj Object to preprocess. Modified in place. * @param options Options that override the instance-level options for this * single call. */ apply: (obj: Partial<Cypress.Response<any> | C8yPactRecord | C8yPact>, options?: C8yPactPreprocessorOptions) => void; } /** * Configuration options for `C8yPactPreprocessor`. * * All key-path strings support: * - Dot-separated segments: `response.body.password` * - Bracket / numeric-index notation: `response.body.items[0].token` * - Array fan-out: `response.body.users.password` (applied to every element) * - Recursive descent: `response.body..password` (any depth below `body`) * * Key resolution is case-insensitive when `ignoreCase` is `true` (default). */ interface C8yPactPreprocessorOptions { /** * Key paths whose values should be replaced with `obfuscationPattern`. * * For `Authorization` headers whose value begins with `Bearer` or `Basic`, * the scheme prefix is kept and only the credential token is replaced, e.g. * `Bearer ****`. * * Cookie values can be targeted by appending the cookie name as an extra * segment: `request.headers.cookie.XSRF-TOKEN`. * * @example * obfuscate: [ * "response.body.password", * "request.headers.authorization", * "response.body.users.password", // every user object * "response.body..token", // any depth * ] */ obfuscate?: string[]; /** * Key paths whose values should be deleted entirely from the record. * * Cookie values can be targeted by appending the cookie name as an extra * segment: `request.headers.cookie.XSRF-TOKEN`. * * @example * ignore: [ * "request.headers.accept-encoding", * "response.headers.cache-control", * "response.body..internalDebugField", // any depth * ] */ ignore?: string[]; /** * Restricts which child keys are retained under the specified parent paths. * All other sibling keys are removed. * * Two forms are accepted: * * **Object form** — maps a parent key path to an array of child keys to keep: * ```ts * pick: { * "response.headers": ["content-type", "location"], * "request.headers": ["authorization"] * } * ``` * * **Array form** — keeps only the listed top-level keys of the record: * ```ts * pick: ["request", "response"] * ``` */ pick?: { [key: string]: string[]; } | string[]; /** * Applies one or more regex substitutions to the value at the given key path. * * The key is a key path (same syntax as `obfuscate` / `ignore`). The value * is a single regex string or an ordered array of regex strings applied in * sequence. Each regex string must use the format: * ``` * /pattern/replacement/flags * ``` * Capture groups (`$1`, `$2`, …) and all standard JS regex flags are * supported. * * @example * // Redact all but the first four characters of a token: * // key: "response.body.token" * // value: "/^(.{4}).+$/$1----/" * // * // Normalise a date field, then strip milliseconds in sequence: * // key: "response.body.timestamp" * // value: ["/T\\d{2}:\\d{2}:\\d{2}/THH:MM:SS/", "/\\.\\d+Z$/Z/"] */ regexReplace?: { [key: string]: string | string[]; }; /** * Replacement string used by the `obfuscate` operation. * Defaults to `"****"`. */ obfuscationPattern?: string; /** * When `true` (default), key segments are matched case-insensitively at * every level of the path. The actual casing of the key found in the object * is always used for mutations — the original structure is never changed. */ ignoreCase?: boolean; } /** * Default options for `C8yPactPreprocessor`. Used when constructing an instance * without custom options, and as fallback for missing properties when applying * with partial options. */ declare const C8yPactPreprocessorDefaultOptions: { ignore: string[]; obfuscate: string[]; obfuscationPattern: string; ignoreCase: boolean; }; /** * Preprocessor for `C8yPact` objects. A preprocessor is applied when a pact * record is **saved** (recording mode) and before it is applied (matched) * against any other record. This is used to unify records before they are * **matched**. A preprocessor transforms objects such as `Cypress.Response`, * `C8yPactRecord` or a full `C8yPact` in-place. It supports various operations * to remove or obfuscate sensitive data, or to pick only certain keys to keep. * * The default implementation is `C8yDefaultPactPreprocessor`. * * ### Supported operations (configured via `C8yPactPreprocessorOptions`) * * | Option | Effect | * |---|---| * | `ignore` | Removes the value at each key path | * | `obfuscate` | Replaces the value at each key path with `obfuscationPattern` | * | `pick` | Keeps only the specified child keys; removes all others | * | `regexReplace` | Applies one or more `/pattern/replacement/flags` expressions | * * ### Key-path syntax * * All key paths use dot-separated segments. Bracket notation and numeric array * indices are supported: * ``` * response.body.password * response.body.items[0].token * response.body.items.0.token * ``` * When a path segment resolves to an **array of objects** and the next segment * is *not* a numeric index, the operation fans out to every element: * ``` * response.body.users.password // applied to every object in `users` * ``` * * ### Recursive-descent operator (`..`) * * Prefix a leaf key with `..` to match it at **any depth** below the optional * prefix path: * ``` * ..password // `password` anywhere in the record * response.body..password // `password` anywhere inside `body` * ``` * * ### Case-insensitive matching * * When `ignoreCase` is `true` (the default), each path segment is resolved * without regard to capitalization. Mutations always use the actual key name * found in the object. * * ### Cookie / Set-Cookie shorthand * * Preprocessors automatically parse `Cookie` and `Set-Cookie` header strings and * apply obfuscation or ignoring to individual cookie values when the key path * is appended with the cookie name as an extra segment: * * ``` * request.headers.cookie.XSRF-TOKEN * response.headers.set-cookie.authorization * ``` * * ### Authorization-header obfuscation * * When obfuscating an `Authorization` header whose value starts with `Bearer` * or `Basic`, the scheme prefix is preserved and only the credential is * replaced: * ``` * Bearer ******** * Basic ******** * ``` */ declare class C8yDefaultPactPreprocessor implements C8yPactPreprocessor { static defaultObfuscationPattern: string; options?: C8yPactPreprocessorOptions; protected reservedKeys: string[]; constructor(options?: C8yPactPreprocessorOptions); /** {@inheritDoc C8yPactPreprocessor.apply} */ apply(obj: Partial<Cypress.Response<any> | C8yPactRecord | C8yPact>, options?: C8yPactPreprocessorOptions): void; private filterObjectByKeepPaths; private applyKeepArray; private removeKey; private removeSetCookie; private removeCookie; private filterValidKeys; /** * Unified key-path traversal. Calls `fn(parent, resolvedKey)` on every * matching leaf. Handles recursive descent (`..leafKey` / `prefix..leafKey`) * and regular dot-/bracket-separated paths including array indices. */ private traverseKeyPath; /** * Applies a list of regex-replace patterns to the value at the given key path. */ private applyRegexReplace; /** * Recursively walks `obj` (depth-first) and calls `fn` on every node whose * key matches `leafKey` (case-sensitively, or case-insensitively when * `ignoreCase` is true). Traverses into arrays and plain objects. */ private applyRecursive; private obfuscateKey; private obfuscateSetCookie; private obfuscateCookie; protected resolveOptions(options?: Partial<C8yPactPreprocessorOptions>): C8yPactPreprocessorOptions; private hasKey; private getCookieObject; } declare function parseRegexReplace(input: string): { pattern: RegExp; replacement: string; }; declare function performRegexReplace(input: string | any, regexes: { pattern: RegExp; replacement: string; }[] | { pattern: RegExp; replacement: string; }): string | any; interface C8yAuthOptions extends ICredentials { sendImmediately?: boolean; userAlias?: string; type?: string; xsrfToken?: string; } type C8yPactAuthObject = { user: string; userAlias?: string; type?: string; token?: string; } | { userAlias: string; user?: string; type?: string; token?: string; }; type C8yAuthentication = IAuthentication; /** * Matcher for matching objects against a schema. If the object does not match * the schema an Error will be thrown. */ interface C8ySchemaMatcher { /** * Matches the given object against the given schema. Throws an error when * schema does not match. Strict matching controls whether additional properties * are allowed in the object. * * @param obj Object to match. * @param schema Schema to match obj against. * @param strictMatching If true, additional properties are not allowed. */ match(obj: any, schema: any, strictMatching?: boolean): boolean; } /** * A C8ySchemaGenerator is used to generate json schemas from json objects. */ interface C8ySchemaGenerator { /** * Generates a json schema for the given object. * * @param obj The object to generate the schema for. * @param options The options passed to the schema generator. */ generate: (obj: any, options?: any) => Promise<any>; } /** * Tenant ID of a Cumulocity tenant. * @example t123456 */ type C8yTenant = string; /** * Base URL of a Cumulocity tenant. * @example https://tenant.eu-latest.cumulocity.com */ type C8yBaseUrl = string; /** * Options used to configure c8yclient command. */ type C8yClientOptions = Partial<Cypress.Loggable> & Partial<Cypress.Timeoutable> & Partial<Pick<Cypress.Failable, "failOnStatusCode">> & Partial<{ auth: IAuthentication; baseUrl: C8yBaseUrl; client: Client; preferBasicAuth: boolean; skipClientAuthentication: boolean; failOnPactValidation: boolean; ignorePact: boolean; schema: any; matchSchemaAndObject: boolean; record: C8yPactRecord; schemaMatcher: C8ySchemaMatcher; strictMatching: boolean; /** Custom ID to identify this request in logs. If not provided, a unique ID will be generated. */ requestId: string; }>; /** * Wrapper for Client to pass auth and options without extending Client. * Using underscore to avoid name clashes with Client and misunderstandings reading the code. */ interface C8yClient { _auth?: C8yAuthentication; _options?: C8yClientOptions; _client?: Client; } /** * Converts the given object to a Cypress.Response. * @param obj The object to convert. * @param duration The duration of the request. * @param fetchOptions The fetch options used for the request. * @param url The URL of the request. * @param schema The schema of the response. */ declare function toCypressResponse(obj: IFetchResponse | IResult<any> | IResultList<any> | C8yPactRecord | Partial<Response>, duration?: number, fetchOptions?: IFetchOptions, url?: RequestInfo | URL, schema?: any): Cypress.Response<any> | undefined; /** * Checks if the given object is a window.Response. * @param obj The object to check. */ declare function isWindowFetchResponse(obj: any): obj is Response; /** * Checks if the given object is an IResult. * @param obj The object to check. */ declare function isIResult(obj: any): obj is IResult<any>; /** * Checks if the given object is a CypressError. * @param error The object to check. * @returns True if the object is a CypressError, false otherwise. */ declare function isCypressError(error: any): boolean; declare const C8yPactModeValues: readonly ["record", "recording", "apply", "forward", "disabled", "mock"]; /** * The pact mode is used to determine the behavior of the recording and mocking capabilities. * - `record`: Records the requests and responses and stores them in a pact file. * - `apply`: Mocks or matches the requests and responses from the recorded pact file. * - `disabled`: Disables the pact recording and mocking (same as undefined). * - `forward`: Forward requests without recording or mocking (might not be supported in all contexts). * - `recording` (deprecated): same as `record`, use `record` instead. * - `mock`: Mock responses for requests. Same as `apply`, but `apply` could also mean matching the requests and responses depending on the context. */ type C8yPactMode = (typeof C8yPactModeValues)[number]; declare const C8yPactRecordingModeValues: readonly ["refresh", "append", "new", "replace"]; /** * The pact recording mode is used to determine how or if requests and responses are recorded. * - `refresh` (default): Recreates the pact file with the all requests and responses. * - `append`: Appends the new requests and responses to the existing pact file. * - `new`: Only creates a new pact file if no pact file exists. If pact file exists, only new requests and responses are added. * - `replace`: Overwrites existing records of a pact with new request and response in the order of occurence. Other records are kept as is. */ type C8yPactRecordingMode = (typeof C8yPactRecordingModeValues)[number]; /** * ID representing a pact object. Should be unique. * @example api__get__permission_failure_tests */ type C8yPactID = string; interface C8yPactRequestMatchingOptions { ignoreUrlParameters?: string[]; baseUrl?: C8yBaseUrl; } interface C8yPactConfigOptions { /** * ID representing a pact object. */ id?: C8yPactID; /** * Use to enable additional logging. */ log?: boolean; /** * Information describing the producer of the pact. Includes name and version information */ producer?: { name: string; version?: string; } | string; /** * Information describing the consumer of the pact. Includes name and version information */ consumer?: { name: string; version?: string; } | string; /** * Tags describing the pact. */ tags?: string[]; /** * Description of the pact. */ description?: string; /** * Use ignore to disable the pact for the current test case. */ ignore?: boolean; /** * Use failOnMissingPacts to disable failing the test if no pact or no next record * is found for the current test case. */ failOnMissingPacts?: boolean; /** * Use strictMatching to enable strict matching of the pact records. If strict matching * is enabled, all properties of the pact records must match and tests fail if a property * is missing. Default is false. */ strictMatching?: boolean; /** * If strictMocking is enabled, an error will be thrown if no pact record matches the * current request. If disabled, the request will be passed through to the configured baseUrl. */ strictMocking?: boolean; /** * Options to configure the C8yPactPreprocessor. * * The preprocessor runs on every pact record when it is saved (recording mode) * and before it is matched against any other record. Use it to remove or obfuscate * sensitive values before they reach the fixture file, or to sanitize * response bodies before they are compared. * * **Key-path syntax** * * All option values that accept key paths use dot-separated segments: * ``` * response.body.password * request.headers.Authorization * ``` * Bracket notation and numeric indices are also supported and the style is * preserved in output: * ``` * response.body.items[0].token // bracket style * response.body.items.0.token // dot style * ``` * When a segment resolves to an **array of objects** and the next segment is * not a numeric index, the operation is applied to every element of that * array automatically: * ``` * response.body.users.password // obfuscates `password` in every user object * ``` * * **Recursive-descent operator (`..`)** * * Prefix the leaf key with `..` to apply the operation at any nesting depth * below the current node (or below the optional prefix path): * ``` * ..password // find `password` anywhere in the record * response.body..password // find `password` anywhere inside body * ``` * * **Case-insensitive matching** * * Set `ignoreCase: true` (the default) to resolve keys without regard to * capitalization. The resolved casing of the actual object key is used for * all mutations, so the original structure is never corrupted. * * **Cookie / Set-Cookie shorthand** * * Append the cookie name as an extra segment to operate on a single cookie * rather than the entire header string: * ``` * request.headers.cookie.XSRF-TOKEN * response.headers.set-cookie.authorization * ``` * * @example * // Remove non-essential headers, obfuscate credentials, redact a token * preprocessor: { * ignore: ["response.headers.cache-control"], * obfuscate: ["request.headers.authorization", "response.body..password"], * regexReplace: { * // keep only the first 4 chars, e.g. "abcd----" * "response.body.token": "/^(.{4}).+$/$1----/" * } * } */ preprocessor?: C8yPactPreprocessorOptions; /** * Options to configure the C8yPact request matching. */ requestMatching?: C8yPactRequestMatchingOptions; /** * Recording mode for the pact. Default is `refresh`. */ recordingMode?: C8yPactRecordingMode; /** * When set to true, both schema validation and pact object matching are performed * when a schema is provided to cy.c8yclient. By default only schema validation runs * when a schema is provided. Default is false. */ matchSchemaAndObject?: boolean; } type C8yPactConfigKeys = keyof C8yPactConfigOptions; interface C8yPactEnv { tenant?: C8yTenant; loggedInUser?: string; loggedInUserAlias?: string; testTitlePath?: string[]; systemVersion?: string; pluginVersion?: string; pluginFolder?: string; } interface C8yPactObject { /** * Pact records containing the requests and responses as well as auth information, * configuration options and created objects. */ records: C8yPactRecord[]; /** * Meta information describing the pact. */ info: C8yPactInfo; /** * Unique id of the pact. */ id: C8yPactID; } declare const C8yPactObjectKeys: string[]; /** * Pact object. Contains all information about a recorded pact, including * the pact records with requests and responses as well as info object with * meta data. A C8yPact objtect must have an unique id. */ interface C8yPact extends C8yPactObject { /** * Clears all records of the pact. Also resets all indexes internally used for * iterating over the records. */ clearRecords(): void; /** * Appends a new record to the pact. If skipIfExists is true, the record is * only appended if no record with the same request exists. * @param record The record to add. * @param skipIfExists If true, the record is only appended if no record for the same request exists. * @returns True if the record was appended, false otherwise. */ appendRecord(record: C8yPactRecord, skipIfExists?: boolean): boolean; /** * Replaces an existing record with a new record. If no record with the same * request exists, the record is appended. * @param record The record to be replaced. * @returns True if the record was replaced, false otherwise. */ replaceRecord(record: C8yPactRecord): boolean; /** * Returns the next pact record or null if no more records are available. * If an id is provided, the record is looked up by requestId or record id * and the cursor is advanced to the position after the matched record. * If no id is provided, the next record by sequential index is returned. */ nextRecord(id?: string): C8yPactRecord | null; /** * Returns the next pact record matching the given request. Request matching is * based ob criteria like url and method. Returns null if no record is found. */ nextRecordMatchingRequest(request: Partial<Request> | { url: string; method: string; }, baseUrl?: C8yBaseUrl): C8yPactRecord | null; /** * Returns an iterator for the pact records. */ [Symbol.iterator](): Iterator<C8yPactRecord | null>; } interface C8yPactInfoVersion { system?: string; c8ypact?: string; runner?: string; } /** * Meta information describing a pact and how it was recorded. */ interface C8yPactInfo extends C8yPactConfigOptions { id: C8yPactID; /** * Version information of the system, runner and pact standard used to record the pact. */ version?: { system?: string; shell?: string; shellName?: string; c8ypact?: string; runner?: string; }; /** * Title of the pact. Title is an array of suite and test titles. */ title?: string[]; /** * Base URL when recording the pact. */ baseUrl: C8yBaseUrl; /** * Tenant when recording the pact. */ tenant?: C8yTenant; } /** * The request stored in a C8yPactRecord. */ type C8yPactRequest = Partial<Cypress.RequestOptions> & { $body?: any; }; /** * The response stored in a C8yPactRecord. */ interface C8yPactResponse<T> { allRequestResponses?: any[]; body?: T; duration?: number; headers?: { [key: string]: string | string[]; }; isOkStatusCode?: boolean; status?: number; statusText?: string; method?: string; $body?: any; } /** * The C8yPactRecord contains all information about a recorded request. It contains * the request and response as well as configuration options, auth information and * the created object id. */ interface C8yPactRecord { /** * Unique id of the record. Optional. */ id?: C8yPactID; /** * Request of the record. */ request: C8yPactRequest; /** * Response of the record. */ response: C8yPactResponse<any>; /** * Modified response returned by interception RouteHandler. */ modifiedResponse?: C8yPactResponse<any>; /** * Configuration options used for the request. */ options?: C8yClientOptions; /** * Auth information used for the request. Can be Basic or Cookie auth. Contains username and possibly alias. */ auth?: C8yPactAuthObject; /** * Id of an object created by the request. Used for mapping when running the recording. */ createdObject?: string; /** * Converts the C8yPactRecord to a Cypress.Response object. */ toCypressResponse(): Cypress.Response<any>; /** * Returns the date of the response. */ date(): Date | null; /** * Returns if the record has a request header with the given key. Comparison is case-insensitive. */ hasRequestHeader(key: string): boolean; /** * Returns the auth type of the record. Currently supports `BasicAuth`, `CookieAuth` or undefined. */ authType(): "BasicAuth" | "CookieAuth" | "BearerAuth" | undefined; } declare function isValidPactId(value: string): boolean; /** * Creates an C8yPactID for a given string or array of strings. * @param value The string or array of strings to convert to a pact id. * @returns The pact id. */ declare function pactId(value: string | string[]): C8yPactID | undefined; /** * Validate the given pact mode. Throws an error if the mode is not supported * or undefined. * @param mode The pact mode to validate. */ declare function validatePactMode(mode?: string): void; /** * Validate the given pact recording mode. Throws an error if the mode is not supported * or undefined. * @param mode The pact recording mode to validate. */ declare function validatePactRecordingMode(mode?: string): void; /** * Checks if the given object is a C8yPact. This also includes checking * all records to be valid C8yPactRecord instances. * * @param obj The object to check. * @returns True if the object is a C8yPact, false otherwise. */ declare function isPact(obj: any): obj is C8yPact; /** * Checks if the given object is a C8yPactRecord. * * @param obj The object to check. * @returns True if the object is a C8yPactRecord, false otherwise. */ declare function isPactRecord(obj: any): obj is C8yPactRecord; /** * Checks if the given object is a Cypress.Response. * * @param obj The object to check. * @returns True if the object is a Cypress.Response, false otherwise. */ declare function isCypressResponse(obj: any): obj is Cypress.Response<any>; /** * Checks if the given object is a C8yPactError. A C8yPactError is an error * with the name "C8yPactError". * * @param error The object to check. * @returns True if the object is a C8yPactError, false otherwise. */ declare function isPactError(error: any): boolean; /** * Converts a Cypress.Response to a C8yPactRequest. */ declare function toPactRequest(response: Cypress.Response<any> | Partial<Cypress.Response<any>>): C8yPactRequest | undefined; /** * Converts a Cypress.Response to a C8yPactResponse. */ declare function toPactResponse<T>(response: Cypress.Response<T> | Partial<Cypress.Response<T>>): C8yPactResponse<T> | undefined; type C8yPactSaveKeys = "id" | "info" | "records"; /** * Returns the value of the environment variable with the given name. The function * tries to find the value in the global `process.env` or `Cypress.env()`. If `env` * is provided, the function uses the given object as environment. * * The function tries to find the value in the following order: * - `name` * - `camelCase(name)` * - `CYPRESS_name` * - `name.replace(/^C8Y_/i, "")` * - `CYPRESS_camelCase(name)` * - `CYPRESS_camelCase(name.replace(/^C8Y_/i, ""))` * * @param name The name of the environment variable. * @param env The environment object to use. Default is `process.env` or `Cypress.env()` * * @returns The value of the environment variable or `undefined` if not found. */ declare function getEnvVar(name: string, env?: { [key: string]: string; }): string | undefined; declare function isOneOfStrings(value: string, values: string[]): boolean; declare function getCreatedObjectId(response: Cypress.Response<any> | Partial<Cypress.Response<any>> | C8yPactResponse<any>): string | undefined; /** * Default implementation of C8yPact. Use C8yDefaultPact.from to create a C8yPact from * a Cypress.Response object, a serialized pact as string or an object implementing the * C8yPact interface. Note, objects implementing the C8yPact interface may not provide * all required functions and properties. */ declare class C8yDefaultPact implements C8yPact { records: C8yPactRecord[]; info: C8yPactInfo; id: C8yPactID; protected recordIndex: number; protected iteratorIndex: number; protected requestIndexMap: { [key: string]: number; }; static strictMatching: boolean; constructor(records: C8yPactRecord[], info: C8yPactInfo, id: C8yPactID); /** * Creates a C8yPact from a Cypress.Response object, a serialized pact as string * or an object containing the pact records and info object. Throws an error if * the input can not be converted to a C8yPact. * @param obj The Cypress.Response, string or object to create a pact from. * @param info The C8yPactInfo object containing additional information for the pact. * @param client The optional C8yClient for options and auth information. */ static from(...args: [obj: Cypress.Response<any>, info: C8yPactInfo, client?: C8yClient] | [obj: string | C8yPact | C8yPactObject]): C8yDefaultPact; clearRecords(): void; appendRecord(record: C8yPactRecord, skipIfExists?: boolean): boolean; replaceRecord(record: C8yPactRecord): boolean; /** * Returns the next pact record or null if no more records are available. * If an id is provided, the record is looked up by requestId or record id * and the cursor is advanced to the position after the matched record. * If no id is provided, the next record by sequential index is returned. */ nextRecord(id?: string): C8yPactRecord | null; currentRecordIndex(): number; nextRecordMatchingRequest(request: Partial<Request> | { url: string; method: string; }, baseUrl?: C8yBaseUrl): C8yPactRecord | null; protected getIndexForKey(key: string): number; protected setIndexForKey(key: string, index: number): void; protected indexMapKey(request: Partial<Request> | C8yPactRequest, baseUrl?: C8yBaseUrl): string | undefined; protected normalizeUrl(url: string | URL, parametersToRemove?: string[], baseUrl?: C8yBaseUrl): string; protected matchUrls(url1: string | URL, url2: string | URL, baseUrl?: C8yBaseUrl): boolean; protected getRequesIndex(key: string): number; /** * Returns the pact record for the given request or null if no record is found. * Currently only url and method are used for matching. * @param req The request to use for matching. */ getRecordsMatchingRequest(req: Partial<Request> | C8yPactRequest, baseUrl?: C8yBaseUrl): C8yPactRecord[] | null; /** * Returns an iterator for the pact records to iterate records using `for (const record of pact) {...}`. */ [Symbol.iterator](): Iterator<C8yPactRecord | null>; } type C8yPactSerializeOptions = { preprocessor?: C8yPactPreprocessor; client?: C8yClient; modifiedResponse?: Cypress.Response<any>; loggedInUser?: string; loggedInUserAlias?: string; authType?: string; baseUrl?: C8yBaseUrl; }; declare function toSerializablePactRecord(response: Partial<Cypress.Response<any>>, options?: C8yPactSerializeOptions): C8yPactRecord; declare function toPactSerializableObject(response: Partial<Cypress.Response<any>>, info: C8yPactInfo, options?: C8yPactSerializeOptions): Promise<Pick<C8yPact, C8yPactSaveKeys>>; /** * Constructor parameters for C8yDefaultPactRecord */ interface C8yDefaultPactRecordInit { request: C8yPactRequest; response: C8yPactResponse<any>; options?: C8yClientOptions; auth?: C8yPactAuthObject; createdObject?: string; modifiedResponse?: C8yPactResponse<any>; id?: C8yPactID; } /** * Default implementation of C8yPactRecord. Use C8yDefaultPactRecord.from to create * a C8yPactRecord from a Cypress.Response object or an C8yPactRecord object. */ declare class C8yDefaultPactRecord implements C8yPactRecord { id?: C8yPactID; request: C8yPactRequest; response: C8yPactResponse<any>; options?: C8yClientOptions; auth?: C8yPactAuthObject; createdObject?: string; modifiedResponse?: C8yPactResponse<any>; constructor(request: C8yPactRequest, response: C8yPactResponse<any>, options?: C8yClientOptions, auth?: C8yPactAuthObject, createdObject?: string, modifiedResponse?: C8yPactResponse<any>, id?: C8yPactID); constructor(params: C8yDefaultPactRecordInit); /** * Creates a C8yPactRecord from a Cypress.Response or an C8yPactRecord object. * @param obj The Cypress.Response<any> or C8yPactRecord object. * @param auth The auth information to use. * @param client The C8yClient for options and auth information. * @param id The optional ID for the pact record. */ static from(obj: Cypress.Response<any> | C8yPactRecord | Partial<Cypress.Response<any>>, auth?: C8yAuthOptions, client?: C8yClient, id?: C8yPactID): C8yPactRecord; /** * Returns the date of the response. */ date(): Date | null; /** * Converts the C8yPactRecord to a Cypress.Response object. */ toCypressResponse<T>(): Cypress.Response<T>; hasRequestHeader(key: string): boolean; authType(): "BearerAuth" | "CookieAuth" | "BasicAuth" | undefined; } declare function createPactRecord(response: Partial<Cypress.Response<any>>, client?: C8yClient, options?: { loggedInUser?: string; loggedInUserAlias?: string; authType?: string; id?: C8yPactID; }): C8yPactRecord; /** * Matcher for C8yPactRecord objects. Use C8yPactMatcher to match any two * records. Depending on the matcher implementation an Error will be thrown * or boolean is returned. */ interface C8yPactMatcher { /** * Matches objectToMatch against objectPact. Returns false if objectToMatch * does not match objectPact or throws an error with details on failing match. * * @param obj1 Object to match. * @param obj2 Pact to match obj1 against. * @param {C8yPactMatcherOptions} options The C8yPactMatcherOptions to use for matching. */ match: (objectToMatch: any, objectPact: any, options?: C8yPactMatcherOptions) => boolean; } /** * Error thrown when a C8yPactMatcher fails to match two objects. * Contains the actual and expected values, the key that failed to match and * the key path of the property that failed to match. * The key path is a string representation of the path to the property that failed to match. * For example: "body > id" for a property "id" in the "body" object. * This error is used to provide detailed information about the match failure. */ declare class C8yPactMatchError extends Error { actual: any; expected: any; key?: string; keyPath?: string; schema?: any; constructor(message: string, options: { actual: any; expected: any; key?: string; keyPath?: string; schema?: any; }); } interface C8yPactMatcherOptions { strictMatching?: boolean; matchSchemaAndObject?: boolean; loggerProps?: { [key: string]: any; }; schemaMatcher?: C8ySchemaMatcher; parents?: (string | number)[]; ignoreCase?: boolean; ignorePrimitiveArrayOrder?: boolean; requestId?: string; } /** * Default implementation of C8yPactMatcher to match C8yPactRecord objects. Pacts * are matched by comparing the properties of the objects using property matchers. * If no property matcher is configured for a property, the property will be matched * by equality. Disable Cypress.c8ypact.config.strictMatching to ignore properties that are * missing in matched objects. In case objects do not match an C8yPactError is thrown. */ declare class C8yDefaultPactMatcher implements C8yPactMatcher { propertyMatchers: { [key: string]: C8yPactMatcher; }; static schemaMatcher: C8ySchemaMatcher; static options?: C8yPactMatcherOptions; options?: C8yPactMatcherOptions; /** * Standard JSON Schema keywords that start with $ but are not schema matcher keys. * These should be treated as regular object properties. * @see https://json-schema.org/understanding-json-schema/reference */ private static readonly JSON_SCHEMA_KEYWORDS; constructor(propertyMatchers?: { [key: string]: C8yPactMatcher; }, options?: C8yPactMatcherOptions); match(obj1: any, obj2: any, options?: C8yPactMatcherOptions): boolean; /** * Check if a key is a schema matcher key (starts with $ but is not a standard JSON Schema keyword) */ private isSchemaMatcherKey; private isKeyPathInObject; /** * Returns the property matcher for the given property name. * @param key The property name to get the matcher for. * @param ignoreCase Whether to ignore the case of the property name. */ getPropertyMatcher(key: string, ignoreCase?: boolean): any; /** * Adds a new property matcher for the given property name. */ addPropertyMatcher(propertyName: string, matcher: C8yPactMatcher): void; /** * Removes the property matcher for the given property name. */ removePropertyMatcher(propertyName: string): void; } /** * Extends C8yDefaultPactMatcher with default property matchers for Cumulocity * response bodies. It has rules configured at least for the following properties: * id, statistics, lastUpdated, creationTime, next, self, password, owner, tenantId * and lastPasswordChange. It is registered for the properties body and requestBody. */ declare class C8yPactBodyMatcher extends C8yDefaultPactMatcher { constructor(propertyMatchers?: {}); } declare class C8yIdentifierMatcher implements C8yPactMatcher { match(obj1: any, obj2: any): boolean; } declare class C8yNumberMatcher implements C8yPactMatcher { match(obj1: any, obj2: any): boolean; } declare class C8yStringMatcher implements C8yPactMatcher { match(obj1: any, obj2: any): boolean; } declare class C8yIgnoreMatcher implements C8yPactMatcher { match(): boolean; } declare class C8ySameTypeMatcher implements C8yPactMatcher { match(obj1: any, obj2: any): boolean; } declare class C8yISODateStringMatcher { match(obj1: any, obj2: any): boolean; } declare function oauthLogin(auth: C8yAuthOptions, baseUrl?: C8yBaseUrl): Promise<C8yAuthOptions>; interface C8yPactAdapterOptions { /** Enable loading of JavaScript pact files (.js, .cjs). Defaults to false. */ enableJavaScript?: boolean; /** Optional id to use for example for logging purposes. */ id?: string; } /** * Using C8yPactFileAdapter you can implement your own adapter to load and save pacts using any format you want. * This allows loading pact objects from different sources, such as HAR files, pact.io, etc. * * The default adapter is C8yPactDefaultFileAdapter which loads and saves pact objects from/to * json files using C8yPact objects. Default location is cypress/fixtures/c8ypact folder. * * Alternative adapters: * - C8yPactHARFileAdapter: Reads/writes HAR (HTTP Archive) format for use with external tools */ interface C8yPactFileAdapter { /** * Loads all pact objects. The key must be the pact id used in C8yPact.id. */ loadPacts: () => { [key: string]: C8yPactObject; }; /** * Loads a pact object by id from file. */ loadPact: (id: string) => C8yPactObject | null; /** * Saves a pact object. */ savePact: (pact: C8yPactObject) => void; /** * Deletes a pact object or file. */ deletePact: (id: string) => void; /** * Gets the folder where the pact files are stored. */ getFolder: () => string; /** * Checks if a pact exists for a given id. */ pactExists(id: string): boolean; /** * Provides some custom description of the adapter. * @example C8yPactFileAdapter */ description(): string; } /** * Default implementation of C8yPactFileAdapter which loads and saves C8yPact objects * Provide location of the files using folder option. Default location is * cypress/fixtures/c8ypact folder. * * This adapter supports loading of JSON and YAML pact files (.json, .yaml, .yml). When * saviing pact files, it saves them as JSON files (.json). * * By using C8yPactAdapterOptions you can enable loading of JavaScript pact files (.js, .cjs). * Use with caution, as this can lead to security issues if the files are not trusted. */ declare class C8yPactDefaultFileAdapter implements C8yPactFileAdapter { folder: string; protected enabledExtensions: string[]; protected fileExtension: string; protected readonly id: string; protected readonly log: debug.Debugger; /** * Creates an instance of C8yPactDefaultFileAdapter. * * @param folder - The folder where pact files are stored. Can be an absolute or relative path. * @param options - Optional configuration for the adapter. * @param options.enableJavaScript - If true, enables loading of JavaScript pact files (.js, .cjs). Defaults to false. */ constructor(folder: string, options?: C8yPactAdapterOptions); description(): string; getFolder(): string; loadPacts(): { [key: string]: C8yPactObject; }; loadPact(id: string): C8yPactObject | null; pactExists(id: string): boolean; savePact(pact: C8yPactObject | Pick<C8yPactObject, C8yPactSaveKeys>): void; deletePact(id: string): void; readPactFiles(): string[]; /** * @deprecated Use readPactFiles() instead. */ readJsonFiles(): string[]; protected deleteJsonFiles(): void; protected loadPactObjects(): (C8yPactObject | null)[]; protected loadPactFromFile(filePath: string): C8yPactObject | null; protected createFolderRecursive(f: string): string | undefined; protected toAbsolutePath(f: string): string; protected isNodeError<T extends new (...args: any) => Error>(error: any, type: T): error is InstanceType<T> & NodeJS.ErrnoException; } type LogFormat = "json" | "simple" | "combined" | "short" | "dev" | "tiny" | "common"; type C8yPactHttpResponse<T = any> = Pick<C8yPactResponse<T>, "status" | "statusText" | "body" | "headers">; declare const C8yPactHttpControllerLogLevel: readonly ["info", "debug", "warn", "error"]; declare const C8yPactHttpControllerDefaultMode: C8yPactMode; declare const C8yPactHttpControllerDefaultRecordingMode: C8yPactRecordingMode; interface C8yPactHttpControllerOptions { /** * The resource path to use for the controller. Default is "/c8yctrl". */ resourcePath?: string; /** * Base URL of the target server to proxy requests to. */ baseUrl?: C8yBaseUrl; /** * Authentication options to use for authenticating against the target server. */ auth?: C8yAuthOptions; /** * Hostname or interface to listen on. Default is to listen on all interfaces. * @example "localhost" might listen on IPv6 only depending on the system * @example "127.0.0.1" for IPv4 localhost only * @example "0.0.0.0" for all IPv4 interfaces * @example "::" for all IPv6 interfaces */ hostname?: string; /** * Port to listen on. Default is 3000. */ port?: number; /** * Tenant id of the target server to proxy requests to. */ tenant?: C8yTenant; /** * Root folder for static files to serve. */ staticRoot?: string; /** * Adapter to use for loading and saving pact files. */ adapter: C8yPactFileAdapter; /** * Preprocessor to use for modifying requests and responses. */ preprocessor?: C8yPactPreprocessor; /** * Request matching options to use for matching requests to recorded responses. */ requestMatching?: C8yPactRequestMatchingOptions; /** * Enable strict mocking. If true, only recorded responses are returned. */ strictMocking?: boolean; /** * Mode the controller is running in. */ mode?: C8yPactMode; /** * Recording mode to use for recording requests and responses. */ recordingMode?: C8yPactRecordingMode; /** * Id of the pact to use for recording and mocking. Default is undefined. */ pactId?: C8yPactID; /** * Record to use for error responses when no mock is found. */ mockNotFoundResponse?: C8yPactHttpResponse | ((req: Request$1<any, any, any, any>) => C8yPactHttpResponse); /** * Logger to use for logging. Currently only winston is supported. */ logger?: winston.Logger; /** * RequestHandler to use for logging requests. Default is morgan. */ requestLogger?: RequestHandler[] | ((logger?: winston.Logger) => RequestHandler[]); /** * RequestHandler to use for logging errors. Default is morgan logger * that logs error object with url, status, request and response details. * Use "common", "combined", "dev", "short" or "tiny" to use predefined * mporgan log formats. */ errorLogger?: RequestHandler | "common" | "combined" | "dev" | "short" | "tiny" | string; /** * Log level to use for logging. Default is info. */ logLevel?: (typeof C8yPactHttpControllerLogLevel)[number]; /** * Log format to use for logging. */ logFormat?: LogFormat | string | FormatFn<IncomingMessage, ServerResponse<IncomingMessage>>; /** * The app names and version to use from static folder. E.g. "cockpit: 1020". * The version must be a semver range. * @example { "cockpit": ">1020.0.0", "dtm": "^1018.1.0" } */ appsVersions?: { [key: string]: string; }; /** * Custom replacer function to use for JSON.stringify. Use for customization of JSON output. * Default is to replace URLs in "self", "next", "initRequest" properties with hostname and * port of the controller. */ stringifyReplacer?: (key: string, value: any) => any; /** * Callbacks for hooking into the controller lifecycle. */ on: C8yPactHttpControllerCallbackOptions; /** * Options to pass to the underlying http-proxy. */ proxyOptions?: Omit<Options, "on" | "plugins" | "ejectPlugins">; } interface C8yPactHttpControllerCallbackOptions { /** * Called before the controller is started. * @param ctrl The controller instance. * @param config The configuration used for the controller. */ beforeStart?: (ctrl: C8yPactHttpController, config?: C8yPactHttpControllerOptions) => void; /** * Called before a request is mocked. Use to modify or return custom response as mock. Also * use to forward custom headers from the request to the response. By default, only the * recorded `content-type` and `set-cookie` headers are forwarded. Any other recorded headers * must be forwarded by adding them to the `response.headers` object. * * By returning null or undefined, the request is passed to the proxy handler without mocking. * * **Note**: return `record.response` to use the recorded response as mock. * * @param ctrl The controller instance. * @param req The request to mock. * @param record The record used for mocking. * @returns A response to use as mock or null/undefined to pass the request to the proxy handler. */ mockRequest?: (ctrl: C8yPactHttpController, req: Request$1<any, any, any, any>, record: C8yPactRecord | undefined | null) => C8yPactHttpResponse | undefined | null; /** * Called when a request is not found in the recorded pacts. Use to return a custom response * for the request or `undefined` if `mockNotFoundResponse` or default 404 response should * be returned for the given request. To access the pact, the response was not found in, use * `c8yctl.currentPact`. * @param ctrl The controller instance. * @param req The request to be mocked. * @returns A response to use as mock for the request or `undefined` to use the default 404 response. */ mockNotFound?: (ctrl: C8yPactHttpController, req: Request$1) => C8yPactHttpResponse | undefined; /** * Called before a request is proxied. Use to modify the request before it * is proxied, e.g. to add or remove headers, etc. or to abort the request * by returning a custom or error response to send back to the client. * @param ctrl The controller instance. * @param proxyReq The proxy request. * @param req The request to proxy. * @returns A response to send back to the client to abort the request or