@scenarist/msw-adapter 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,11 +1,13 @@
1
1
  import type { HttpHandler } from "msw";
2
- import type { ActiveScenario, ScenaristScenario, ResponseSelector } from "@scenarist/core";
2
+ import type { ActiveScenario, ScenaristScenario, ResponseSelector, ErrorBehaviors, Logger } from "@scenarist/core";
3
3
  export type DynamicHandlerOptions = {
4
4
  readonly getTestId: (request: Request) => string;
5
5
  readonly getActiveScenario: (testId: string) => ActiveScenario | undefined;
6
6
  readonly getScenarioDefinition: (scenarioId: string) => ScenaristScenario | undefined;
7
7
  readonly strictMode: boolean;
8
8
  readonly responseSelector: ResponseSelector;
9
+ readonly errorBehaviors?: ErrorBehaviors;
10
+ readonly logger?: Logger;
9
11
  };
10
12
  export declare const createDynamicHandler: (options: DynamicHandlerOptions) => HttpHandler;
11
13
  //# sourceMappingURL=dynamic-handler.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"dynamic-handler.d.ts","sourceRoot":"","sources":["../../src/handlers/dynamic-handler.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,KAAK,CAAC;AACvC,OAAO,KAAK,EACV,cAAc,EACd,iBAAiB,EAIjB,gBAAgB,EACjB,MAAM,iBAAiB,CAAC;AAIzB,MAAM,MAAM,qBAAqB,GAAG;IAClC,QAAQ,CAAC,SAAS,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,MAAM,CAAC;IACjD,QAAQ,CAAC,iBAAiB,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,cAAc,GAAG,SAAS,CAAC;IAC3E,QAAQ,CAAC,qBAAqB,EAAE,CAC9B,UAAU,EAAE,MAAM,KACf,iBAAiB,GAAG,SAAS,CAAC;IACnC,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,gBAAgB,EAAE,gBAAgB,CAAC;CAC7C,CAAC;AAiGF,eAAO,MAAM,oBAAoB,GAC/B,SAAS,qBAAqB,KAC7B,WAmCF,CAAC"}
1
+ {"version":3,"file":"dynamic-handler.d.ts","sourceRoot":"","sources":["../../src/handlers/dynamic-handler.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,KAAK,CAAC;AACvC,OAAO,KAAK,EACV,cAAc,EACd,iBAAiB,EAIjB,gBAAgB,EAChB,cAAc,EACd,MAAM,EACP,MAAM,iBAAiB,CAAC;AAKzB,MAAM,MAAM,qBAAqB,GAAG;IAClC,QAAQ,CAAC,SAAS,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,MAAM,CAAC;IACjD,QAAQ,CAAC,iBAAiB,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,cAAc,GAAG,SAAS,CAAC;IAC3E,QAAQ,CAAC,qBAAqB,EAAE,CAC9B,UAAU,EAAE,MAAM,KACf,iBAAiB,GAAG,SAAS,CAAC;IACnC,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,gBAAgB,EAAE,gBAAgB,CAAC;IAC5C,QAAQ,CAAC,cAAc,CAAC,EAAE,cAAc,CAAC;IACzC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAiGF,eAAO,MAAM,oBAAoB,GAC/B,SAAS,qBAAqB,KAC7B,WAiIF,CAAC"}
@@ -1,4 +1,5 @@
1
1
  import { http, passthrough } from "msw";
2
+ import { ScenaristError, ErrorCodes, LogCategories } from "@scenarist/core";
2
3
  import { buildResponse } from "../conversion/response-builder.js";
3
4
  import { matchesUrl } from "../matching/url-matcher.js";
4
5
  /**
@@ -84,20 +85,87 @@ const getMocksFromScenarios = (activeScenario, getScenarioDefinition, method, ur
84
85
  export const createDynamicHandler = (options) => {
85
86
  return http.all("*", async ({ request }) => {
86
87
  const testId = options.getTestId(request);
87
- const activeScenario = options.getActiveScenario(testId);
88
- const scenarioId = activeScenario?.scenarioId ?? "default";
89
- // Extract request context for matching
90
- const context = await extractHttpRequestContext(request);
91
- // Get candidate mocks from active or default scenario
92
- const mocks = getMocksFromScenarios(activeScenario, options.getScenarioDefinition, request.method, request.url);
93
- // Use injected ResponseSelector to find matching mock
94
- const result = options.responseSelector.selectResponse(testId, scenarioId, context, mocks);
95
- if (result.success) {
96
- return buildResponse(result.data);
88
+ try {
89
+ // Check for missing test ID
90
+ if (!testId) {
91
+ if (options.errorBehaviors?.onMissingTestId === "throw") {
92
+ throw new ScenaristError("Missing test ID header. Ensure your test setup sends the x-scenarist-test-id header with each request.", {
93
+ code: ErrorCodes.MISSING_TEST_ID,
94
+ context: {
95
+ requestInfo: {
96
+ method: request.method,
97
+ url: request.url,
98
+ },
99
+ hint: "This typically means: 1) Test didn't call switchScenario() before making requests, 2) Request originated outside the test context, or 3) Header forwarding is misconfigured.",
100
+ },
101
+ });
102
+ }
103
+ if (options.errorBehaviors?.onMissingTestId === "warn" &&
104
+ options.logger) {
105
+ options.logger.warn(LogCategories.REQUEST, "Missing test ID header. Using default scenario.", {
106
+ requestUrl: request.url,
107
+ requestMethod: request.method,
108
+ });
109
+ }
110
+ // For 'ignore' or when no errorBehaviors set, silently continue with empty testId
111
+ }
112
+ const activeScenario = options.getActiveScenario(testId);
113
+ const scenarioId = activeScenario?.scenarioId ?? "default";
114
+ // Extract request context for matching
115
+ const context = await extractHttpRequestContext(request);
116
+ // Get candidate mocks from active or default scenario
117
+ const mocks = getMocksFromScenarios(activeScenario, options.getScenarioDefinition, request.method, request.url);
118
+ // Use injected ResponseSelector to find matching mock
119
+ const result = options.responseSelector.selectResponse(testId, scenarioId, context, mocks);
120
+ if (result.success) {
121
+ return buildResponse(result.data);
122
+ }
123
+ // Determine which error behavior to use based on error code
124
+ const errorCode = result.error.code;
125
+ const errorBehavior = errorCode === ErrorCodes.SEQUENCE_EXHAUSTED
126
+ ? options.errorBehaviors?.onSequenceExhausted
127
+ : options.errorBehaviors?.onNoMockFound;
128
+ // Handle error based on configured behavior
129
+ if (errorBehavior === "throw") {
130
+ throw result.error;
131
+ }
132
+ if (errorBehavior === "warn" && options.logger) {
133
+ options.logger.warn("matching", result.error.message, {
134
+ testId,
135
+ scenarioId,
136
+ requestUrl: context.url,
137
+ requestMethod: context.method,
138
+ });
139
+ }
140
+ if (options.strictMode) {
141
+ return new Response(null, { status: 501 });
142
+ }
143
+ return passthrough();
97
144
  }
98
- if (options.strictMode) {
99
- return new Response(null, { status: 501 });
145
+ catch (error) {
146
+ // Log the error via Logger if available
147
+ if (options.logger) {
148
+ const errorMessage = error instanceof Error ? error.message : String(error);
149
+ options.logger.error(LogCategories.REQUEST, `Handler error: ${errorMessage}`, {
150
+ testId,
151
+ requestUrl: request.url,
152
+ requestMethod: request.method,
153
+ }, {
154
+ errorName: error instanceof Error ? error.name : "Unknown",
155
+ stack: error instanceof Error ? error.stack : undefined,
156
+ });
157
+ }
158
+ // Use specific error code from ScenaristError, or fallback to HANDLER_ERROR
159
+ const errorCode = error instanceof ScenaristError ? error.code : "HANDLER_ERROR";
160
+ // Return a 500 error response with error details
161
+ return new Response(JSON.stringify({
162
+ error: "Internal mock server error",
163
+ message: error instanceof Error ? error.message : String(error),
164
+ code: errorCode,
165
+ }), {
166
+ status: 500,
167
+ headers: { "Content-Type": "application/json" },
168
+ });
100
169
  }
101
- return passthrough();
102
170
  });
103
171
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scenarist/msw-adapter",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "Internal: MSW integration layer for Scenarist framework adapters",
5
5
  "author": "Paul Hammond (citypaul) <paul@packsoftware.co.uk>",
6
6
  "license": "MIT",
@@ -42,7 +42,7 @@
42
42
  ],
43
43
  "dependencies": {
44
44
  "path-to-regexp": "^6.3.0",
45
- "@scenarist/core": "0.2.1"
45
+ "@scenarist/core": "0.3.0"
46
46
  },
47
47
  "peerDependencies": {
48
48
  "msw": "^2.0.0"
@@ -53,7 +53,7 @@
53
53
  "eslint": "^9.39.1",
54
54
  "msw": "^2.12.3",
55
55
  "typescript": "^5.9.3",
56
- "vitest": "^4.0.14",
56
+ "vitest": "^4.0.15",
57
57
  "@scenarist/eslint-config": "^0.0.0",
58
58
  "@scenarist/typescript-config": "0.0.0"
59
59
  },