Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@pactflow/pact-msw-adapter

Package Overview
Dependencies
Maintainers
6
Versions
13
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@pactflow/pact-msw-adapter - npm Package Compare versions

Comparing version 2.0.1 to 3.0.0

11

CHANGELOG.md

@@ -5,2 +5,13 @@ # Changelog

## [3.0.0](https://github.com/pactflow/pact-msw-adapter/compare/v2.0.1...v3.0.0) (2023-11-09)
### ⚠ BREAKING CHANGES
* make compatible and consistent with msw v2
* update dependencies including msw to v2
* make compatible and consistent with msw v2 ([63c98f6](https://github.com/pactflow/pact-msw-adapter/commit/63c98f67d911ed391e60860ec203f6cd14fa8b22))
* update dependencies including msw to v2 ([37cf49d](https://github.com/pactflow/pact-msw-adapter/commit/37cf49d79b4e44a2d0a0ba796a60bd117f22b7fe))
### [2.0.1](https://github.com/pactflow/pact-msw-adapter/compare/v2.0.0...v2.0.1) (2023-11-07)

@@ -7,0 +18,0 @@

8

dist/convertMswMatchToPact.d.ts

@@ -1,9 +0,11 @@

import { PactFile, MswMatch } from "./pactMswAdapter";
import { PactFile, MatchedRequest } from "./pactMswAdapter";
import { JSONValue } from "./utils/utils";
export declare const readBody: (input: Request | Response) => Promise<JSONValue | FormData | undefined>;
export declare const convertMswMatchToPact: ({ consumer, provider, matches, headers, }: {
consumer: string;
provider: string;
matches: MswMatch[];
matches: MatchedRequest[];
headers?: {
excludeHeaders: string[] | undefined;
} | undefined;
}) => PactFile;
}) => Promise<PactFile>;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.convertMswMatchToPact = void 0;
exports.convertMswMatchToPact = exports.readBody = void 0;
const lodash_1 = require("lodash");
const pjson = require("../package.json");
const convertMswMatchToPact = ({ consumer, provider, matches, headers, }) => {
const readBody = async (input) => {
// so we don't reread body somewhere
const clone = input.clone();
if (clone.body === null)
return undefined;
const contentType = clone.headers.get("content-type");
if (contentType === null || contentType === void 0 ? void 0 : contentType.startsWith("application/json")) {
return clone.json();
}
else if (contentType === null || contentType === void 0 ? void 0 : contentType.startsWith("multipart/form-data")) {
return clone.formData();
}
// default to text
return clone.text();
};
exports.readBody = readBody;
const convertMswMatchToPact = async ({ consumer, provider, matches, headers, }) => {
const pactFile = {
consumer: { name: consumer },
provider: { name: provider },
interactions: matches.map((match) => {
var _a;
interactions: await Promise.all(matches.map(async (match) => {
var _a, _b, _c;
return ({
description: match.request.id,
description: match.requestId,
providerState: "",
request: {
method: match.request.method,
path: match.request.url.pathname,
headers: (headers === null || headers === void 0 ? void 0 : headers.excludeHeaders)
? (0, lodash_1.omit)(Object.fromEntries(match.request.headers.entries()), headers.excludeHeaders)
: Object.fromEntries(match.request.headers.entries()),
body: match.request.body || undefined,
query: match.request.url.search
? match.request.url.search.split("?")[1]
: undefined,
path: new URL(match.request.url).pathname,
headers: (0, lodash_1.omit)(Object.fromEntries(match.request.headers.entries()), (_a = headers === null || headers === void 0 ? void 0 : headers.excludeHeaders) !== null && _a !== void 0 ? _a : []),
body: await (0, exports.readBody)(match.request),
query: (_b = new URL(match.request.url).search) === null || _b === void 0 ? void 0 : _b.split("?")[1]
},
response: {
status: match.response.status,
headers: (headers === null || headers === void 0 ? void 0 : headers.excludeHeaders)
? (0, lodash_1.omit)(Object.fromEntries(match.response.headers.entries()), headers.excludeHeaders)
: Object.fromEntries(match.response.headers.entries()),
body: match.body
? ((_a = match.response.headers.get("content-type")) === null || _a === void 0 ? void 0 : _a.includes("json"))
? JSON.parse(match.body)
: match.body
: undefined,
headers: (0, lodash_1.omit)(Object.fromEntries(match.response.headers.entries()), (_c = headers === null || headers === void 0 ? void 0 : headers.excludeHeaders) !== null && _c !== void 0 ? _c : []),
body: await (0, exports.readBody)(match.response),
},
});
}),
})),
metadata: {

@@ -40,0 +46,0 @@ pactSpecification: {

/// <reference types="node" />
import { DefaultRequestBody, MockedRequest, SetupWorkerApi } from "msw";
import { Logger } from "./utils/utils";
import { SetupWorker } from "msw/browser";
import { SetupServer } from "msw/node";
import { JSONValue, Logger } from "./utils/utils";
import { convertMswMatchToPact } from "./convertMswMatchToPact";
import { EventEmitter } from "events";
import { SetupServerApi } from "msw/lib/types/node/glossary";
import { IsomorphicResponse } from "@mswjs/interceptors";
export interface PactMswAdapterOptions {

@@ -15,3 +14,3 @@ timeout?: number;

[name: string]: string[];
} | ((match: MockedRequest) => string | null);
} | ((event: PendingRequest) => string | null);
includeUrl?: string[];

@@ -29,3 +28,3 @@ excludeUrl?: string[];

[name: string]: string[];
} | ((match: MockedRequest) => string | null);
} | ((event: PendingRequest) => string | null);
includeUrl?: string[];

@@ -45,4 +44,4 @@ excludeUrl?: string[];

options: PactMswAdapterOptions;
worker?: SetupWorkerApi | undefined;
server?: SetupServerApi | undefined;
worker?: SetupWorker | undefined;
server?: SetupServer | undefined;
}) => PactMswAdapter;

@@ -57,3 +56,3 @@ export { convertMswMatchToPact };

headers: any;
body: DefaultRequestBody;
body: JSONValue | FormData | undefined;
query?: string;

@@ -90,11 +89,12 @@ };

}
export interface MswMatch {
request: MockedRequest;
response: IsomorphicResponse | Response;
body: string | undefined;
export interface PendingRequest {
request: Request;
requestId: string;
}
export interface ExpiredRequest {
reqId: string;
export interface MatchedRequest extends PendingRequest {
response: Response;
}
export interface ExpiredRequest extends PendingRequest {
startTime: number;
duration?: number;
}

@@ -40,44 +40,36 @@ "use strict";

const matches = []; // Completed request-response pairs
mswMocker.events.on("request:match", (req) => {
if (!(0, utils_1.checkUrlFilters)(req, options))
mswMocker.events.on("request:match", ({ request, requestId }) => {
if (!(0, utils_1.checkUrlFilters)({ request, requestId }, options))
return;
if (options.debug) {
(0, utils_1.logGroup)(["Matching request", req], { endGroup: true, mode: "debug", logger: options.logger });
(0, utils_1.logGroup)(["Matching request", request], { endGroup: true, mode: "debug", logger: options.logger });
}
const startTime = Date.now();
pendingRequests.push(req);
activeRequestIds.push(req.id);
pendingRequests.push({ request, requestId });
activeRequestIds.push(requestId);
setTimeout(() => {
const activeIdx = activeRequestIds.indexOf(req.id);
emitter.emit("pact-msw-adapter:expired", req);
const expired = { requestId, startTime, request };
const activeIdx = activeRequestIds.indexOf(requestId);
emitter.emit("pact-msw-adapter:expired", expired);
if (activeIdx >= 0) {
// Could be removed if completed or the test ended
activeRequestIds.splice(activeIdx, 1);
expiredRequests.push({
reqId: req.id,
startTime,
});
expiredRequests.push(expired);
}
}, options.timeout);
});
mswMocker.events.on("response:mocked", async (response, reqId) => {
// https://mswjs.io/docs/extensions/life-cycle-events#responsemocked
// Note that the res instance differs between the browser and Node.js.
// Take this difference into account when operating with it.
const responseBody = isWorker
? await response.text()
: response.body;
mswMocker.events.on("response:mocked", async ({ response, requestId }) => {
(0, utils_1.logGroup)(JSON.stringify(response), { endGroup: true, logger: options.logger });
const reqIdx = pendingRequests.findIndex((req) => req.id === reqId);
const reqIdx = pendingRequests.findIndex((pending) => pending.requestId === requestId);
if (reqIdx < 0)
return; // Filtered and (expired and cleared) requests
const endTime = Date.now();
const request = pendingRequests.splice(reqIdx, 1)[0];
const activeReqIdx = activeRequestIds.indexOf(reqId);
const { request } = pendingRequests.splice(reqIdx, 1)[0];
const activeReqIdx = activeRequestIds.indexOf(requestId);
if (activeReqIdx < 0) {
// Expired requests and responses from previous tests
const oldReqId = oldRequestIds.find((id) => id === reqId);
const expiredReq = expiredRequests.find((expired) => expired.reqId === reqId);
const oldReqId = oldRequestIds.find((id) => id === requestId);
const expiredReq = expiredRequests.find((expired) => expired.requestId === requestId);
if (oldReqId) {
orphanResponses.push(request.url.toString());
orphanResponses.push(request.url);
(0, utils_1.log)(`Orphan response: ${request.url}`, {

@@ -91,3 +83,4 @@ mode: "warn",

if (!oldReqId) {
(0, utils_1.log)(`Expired request to ${request.url.pathname}`, {
const pathname = new URL(request.url).pathname;
(0, utils_1.log)(`Expired request to ${pathname}`, {
mode: "warn",

@@ -112,4 +105,4 @@ group: true,

request,
requestId,
response,
body: responseBody,
};

@@ -119,8 +112,7 @@ emitter.emit("pact-msw-adapter:match", match);

});
mswMocker.events.on("request:unhandled", (req) => {
const url = req.url.toString();
if (!(0, utils_1.checkUrlFilters)(req, options))
mswMocker.events.on("request:unhandled", ({ request, requestId }) => {
if (!(0, utils_1.checkUrlFilters)({ request, requestId }, options))
return;
unhandledRequests.push(url);
(0, utils_1.log)(`Unhandled request: ${url}`, { mode: "warn", logger: options.logger });
unhandledRequests.push(request.url);
(0, utils_1.log)(`Unhandled request: ${request.url}`, { mode: "warn", logger: options.logger });
});

@@ -144,6 +136,9 @@ return {

expired,
req: pendingRequests.find((req) => req.id === expired.reqId),
pending: pendingRequests.find((pending) => pending.requestId === expired.requestId),
}))
.filter(({ expired, req }) => expired && req)
.map(({ expired, req }) => `${req.url.pathname}${expired.duration ? `took ${expired.duration}ms and` : ""} timed out after ${options.timeout}ms`)
.filter(({ expired, pending }) => expired && pending)
.map(({ expired, pending }) => {
const pathname = new URL(pending.request.url).pathname;
return `${pathname}${expired.duration ? `took ${expired.duration}ms and` : ""} timed out after ${options.timeout}ms`;
})
.join("\n")}\n`;

@@ -228,9 +223,8 @@ expiredRequests.length = 0;

const pactFiles = [];
const matchProvider = (request) => {
const matchProvider = (match) => {
var _a;
if (typeof options.providers === "function")
return options.providers(request);
const url = request.url.toString();
return options.providers(match);
return (_a = Object.entries(options.providers)
.find(([_, paths]) => paths.some((path) => url.includes(path)))) === null || _a === void 0 ? void 0 : _a[0];
.find(([_, paths]) => paths.some((path) => match.request.url.includes(path)))) === null || _a === void 0 ? void 0 : _a[0];
};

@@ -240,3 +234,3 @@ const matchesByProvider = {};

var _a;
const provider = (_a = matchProvider(match.request)) !== null && _a !== void 0 ? _a : "unknown";
const provider = (_a = matchProvider(match)) !== null && _a !== void 0 ? _a : "unknown";
if (!matchesByProvider[provider])

@@ -247,3 +241,3 @@ matchesByProvider[provider] = [];

for (const [provider, providerMatches] of Object.entries(matchesByProvider)) {
const pactFile = (0, convertMswMatchToPact_1.convertMswMatchToPact)({
const pactFile = await (0, convertMswMatchToPact_1.convertMswMatchToPact)({
consumer: options.consumer,

@@ -250,0 +244,0 @@ provider,

@@ -1,3 +0,2 @@

import { MockedRequest } from "msw";
import { PactMswAdapterOptionsInternal } from "../pactMswAdapter";
import { PactMswAdapterOptionsInternal, PendingRequest } from "../pactMswAdapter";
type LogLevel = 'debug' | 'info' | 'warn' | 'error';

@@ -16,4 +15,7 @@ export type Logger = Pick<typeof console, LogLevel | 'groupEnd' | 'groupCollapsed'>;

declare const createWriter: (options: PactMswAdapterOptionsInternal) => (filePath: string, data: Object) => void;
declare const checkUrlFilters: (request: MockedRequest, options: PactMswAdapterOptionsInternal) => boolean;
declare const checkUrlFilters: (pending: PendingRequest, options: PactMswAdapterOptionsInternal) => boolean;
declare const addTimeout: <T>(promise: Promise<T>, label: string, timeout: number) => Promise<void | T>;
export type JSONValue = string | number | boolean | null | {
[key: string]: JSONValue;
} | JSONValue[];
export { log, logGroup, createWriter, checkUrlFilters, addTimeout };

@@ -64,12 +64,12 @@ "use strict";

exports.createWriter = createWriter;
const hasProvider = (request, options) => {
const hasProvider = (pending, options) => {
var _a;
if (typeof options.providers === 'function') {
return options.providers(request) !== null;
return options.providers(pending) !== null;
}
return (_a = Object.values(options.providers)) === null || _a === void 0 ? void 0 : _a.some(validPaths => validPaths.some(path => request.url.toString().includes(path)));
return (_a = Object.values(options.providers)) === null || _a === void 0 ? void 0 : _a.some(validPaths => validPaths.some(path => pending.request.url.includes(path)));
};
const checkUrlFilters = (request, options) => {
const urlString = request.url.toString();
const providerFilter = hasProvider(request, options);
const checkUrlFilters = (pending, options) => {
const urlString = pending.request.url.toString();
const providerFilter = hasProvider(pending, options);
const includeFilter = !options.includeUrl || options.includeUrl.some(inc => urlString.includes(inc));

@@ -76,0 +76,0 @@ const excludeFilter = !options.excludeUrl || !options.excludeUrl.some(exc => urlString.includes(exc));

{
"name": "@pactflow/pact-msw-adapter",
"version": "2.0.1",
"version": "3.0.0",
"main": "./dist/pactMswAdapter.js",

@@ -23,6 +23,6 @@ "keywords": [

"peerDependencies": {
"msw": ">=0.35.0"
"msw": ">=2.0.0"
},
"engines": {
"node": ">=16"
"node": ">=18"
},

@@ -47,19 +47,18 @@ "scripts": {

"devDependencies": {
"@babel/preset-env": "7.16.11",
"@pact-foundation/pact": "9.17.2",
"@types/axios": "0.14.0",
"@types/jest": "27.4.1",
"@typescript-eslint/eslint-plugin": "5.56.0",
"@typescript-eslint/parser": "5.59.5",
"axios": "0.26.1",
"babel-jest": "27.5.1",
"eslint": "8.10.0",
"jest": "27.5.1",
"msw": "0.36.8",
"nock": "13.2.4",
"regenerator-runtime": "0.13.11",
"rimraf": "5.0.1",
"standard-version": "9.5.0",
"ts-jest": "27.1.3",
"typescript": "4.9.5"
"@babel/preset-env": "^7.23.2",
"@pact-foundation/pact": "^12.1.0",
"@types/jest": "^29.5.7",
"@typescript-eslint/eslint-plugin": "^6.9.1",
"@typescript-eslint/parser": "^6.9.1",
"axios": "^1.6.0",
"babel-jest": "^29.7.0",
"eslint": "^8.52.0",
"jest": "^29.7.0",
"msw": "^2.0.2",
"nock": "^13.3.7",
"regenerator-runtime": "^0.14.0",
"rimraf": "^5.0.5",
"standard-version": "^9.5.0",
"ts-jest": "^29.1.1",
"typescript": "^5.2.2"
},

@@ -66,0 +65,0 @@ "dependencies": {

@@ -25,2 +25,9 @@ # pact-msw-adapter

## Compatibility table
| pact msw version | msw version | node version | migration guide |
|------------------|-------------|--------------|-------------------------------------------------------|
| `^2` | `<=1` | `>=16 <=20` | |
| `^3` | `^2` | `>=18` | [v2 to v3](#migrating-pact-msw-adapter-from-v2-to-v3) |
## Getting started

@@ -46,3 +53,3 @@

```js
import { setupWorker } from "msw";
import { setupWorker } from "msw/browser";
import { setupPactMswAdapter } from "@pactflow/pact-msw-adapter";

@@ -71,8 +78,8 @@ ```

| - | - | - | - | - |
| server | false | `SetupServerApi` | | server provided by msw - a server or worker must be provided|
| worker | false | `SetupWorkerApi` | | worker provided by msw - a server or worker must be provided|
| timeout | `false` | `number` | 200 | Time in ms for a network request to expire, `verifyTest` will fail after twice this amount of time. |
| server | `false` | `SetupServer` | | server provided by msw - a server or worker must be provided|
| worker | `false` | `SetupWorker` | | worker provided by msw - a server or worker must be provided|
| timeout | `false` | `number` | `200` | Time in ms for a network request to expire, `verifyTest` will fail after twice this amount of time. |
| consumer | `true` | `string` | | name of the consumer running the tests |
| providers | `true` | `{ [string]: string[] } | (request: MockedRequest) => string | null` | | names and filters for each provider or function that returns name of provider for given request |
| pactOutDir | `false` | `string` | ./msw_generated_pacts/ | path to write pact files into |
| providers | `true` | `{ [string]: string[] } \| ({ request: Request; requestId: string }) => string \| null` | | names and filters for each provider or function that returns name of provider for given request |
| pactOutDir | `false` | `string` | `./msw_generated_pacts/` | path to write pact files into |
| includeUrl | `false` | `string[]` | | inclusive filters for network calls |

@@ -225,2 +232,20 @@ | excludeUrl | `false` | `string[]` | | exclusive filters for network calls |

## Migrating pact-msw-adapter from v2 to v3
In [October 2023 msw released new version 2](https://mswjs.io/blog/introducing-msw-2.0) that bring significant changes to the msw interface. To reflect these changes we've released pact-msw-adapter@v3 that's compatible with msw@v2.
To migrate you'll need to update `msw to >=2.0` and migrate your usage of the library ([migration guide here](https://mswjs.io/docs/migrations/1.x-to-2.x)).
Breaking changes on pact-msw-adapter side:
- minimal required version of Node is v18
- some exported types were renamed and extended to match msw behaviour
- `MswMatch` is now `MatchedRequest` and is `{ request: Request; requestId: string; response: Response }`
- `ExpiredRequest` still called the same and is `{ request: Request; requestId: string; startTime: number; duration?: number }`
- added `PendingRequest` as `{ request: Request; requestId: string; }`
- `PactMswAdapterOptions.providers` function variant is now consistent with msw and has signature `(event: PendingRequest) => string | null`
- `PactMswAdapter.emitter` events are slightly updated to match msw behaviour
- `pact-msw-adapter:expired` handler must have signature `(event: ExpiredRequest) => void`
- `pact-msw-adapter:match` handler must have signature `(event: MatchedRequest) => void`
- `convertMswMatchToPact` is now async function
## Contributors

@@ -227,0 +252,0 @@

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc