@jterrazz/test 6.1.0 → 6.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.
package/dist/index.d.cts CHANGED
@@ -1,3 +1,4 @@
1
+ import { n as InterceptResponse, r as InterceptTrigger } from "./types.cjs";
1
2
  import { a as PostgresOptions, c as IsolationStrategy, i as redis, l as DatabasePort, n as sqlite, o as postgres, r as RedisOptions, s as ServiceHandle, t as SqliteOptions } from "./sqlite.adapter.cjs";
2
3
  import { i as mockOfDate, n as mockOf, r as MockDatePort, t as MockPort } from "./mock-of.cjs";
3
4
 
@@ -56,7 +57,7 @@ interface ServerResponse {
56
57
  */
57
58
  interface ServerPort {
58
59
  /** Send an HTTP request and return the parsed response. */
59
- request(method: string, path: string, body?: unknown): Promise<ServerResponse>;
60
+ request(method: string, path: string, body?: unknown, headers?: Record<string, string>): Promise<ServerResponse>;
60
61
  }
61
62
  //#endregion
62
63
  //#region src/builder/directory-accessor.d.ts
@@ -210,10 +211,12 @@ declare class SpecificationBuilder {
210
211
  private commandEnv;
211
212
  private config;
212
213
  private fixtures;
214
+ private intercepts;
213
215
  private label;
214
216
  private mocks;
215
217
  private projectName;
216
218
  private request;
219
+ private requestHeaders;
217
220
  private seeds;
218
221
  private spawnConfig;
219
222
  private testDir;
@@ -244,6 +247,29 @@ declare class SpecificationBuilder {
244
247
  * spec("...").env({ HOME: "$WORKDIR", TZ: "UTC" }).exec("status").run();
245
248
  */
246
249
  env(env: CommandEnv): this;
250
+ /**
251
+ * Set HTTP headers for the request. Multiple calls merge.
252
+ *
253
+ * @example
254
+ * spec("french").headers({ 'Accept-Language': 'fr' }).get("/articles").run();
255
+ */
256
+ headers(headers: Record<string, string>): this;
257
+ /**
258
+ * Intercept an outgoing HTTP request and return a controlled response.
259
+ * Uses MSW under the hood. Intercepts are queued — multiple calls with the
260
+ * same trigger fire sequentially (first match consumed first).
261
+ *
262
+ * @param trigger - What to match (use openai.chat(), anthropic.messages(), http.get(), etc.)
263
+ * @param response - What to return (use openai.response(), http.json(), etc.)
264
+ *
265
+ * @example
266
+ * spec('pipeline')
267
+ * .intercept(openai.chat(), openai.response({ categories: ['TECH'] }))
268
+ * .intercept(openai.chat(), openai.response({ headline: 'AI News' }))
269
+ * .exec('process')
270
+ * .run();
271
+ */
272
+ intercept(trigger: InterceptTrigger, response: InterceptResponse): this;
247
273
  /**
248
274
  * Send a GET request to the server adapter.
249
275
  *
@@ -440,8 +466,8 @@ declare class Orchestrator {
440
466
  }
441
467
  //#endregion
442
468
  //#region src/runner/targets.d.ts
469
+ /** Any object with a request method compatible with Hono's app.request(). */
443
470
  type HonoApp$1 = {
444
- fetch: (...args: any[]) => any;
445
471
  request: (path: string, init?: RequestInit) => Promise<Response> | Response;
446
472
  };
447
473
  /** Services map passed to the app factory after infrastructure starts. */
@@ -582,7 +608,7 @@ declare class ExecAdapter implements CommandPort {
582
608
  declare class FetchAdapter implements ServerPort {
583
609
  private baseUrl;
584
610
  constructor(url: string);
585
- request(method: string, path: string, body?: unknown): Promise<ServerResponse>;
611
+ request(method: string, path: string, body?: unknown, headers?: Record<string, string>): Promise<ServerResponse>;
586
612
  }
587
613
  //#endregion
588
614
  //#region src/adapters/hono.adapter.d.ts
@@ -595,7 +621,7 @@ declare class HonoAdapter implements ServerPort {
595
621
  constructor(app: {
596
622
  request: (path: string, init?: RequestInit) => Promise<Response> | Response;
597
623
  });
598
- request(method: string, path: string, body?: unknown): Promise<ServerResponse>;
624
+ request(method: string, path: string, body?: unknown, headers?: Record<string, string>): Promise<ServerResponse>;
599
625
  }
600
626
  //#endregion
601
627
  //#region src/runner/integration.d.ts
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { n as InterceptResponse, r as InterceptTrigger } from "./types.js";
1
2
  import { a as PostgresOptions, c as IsolationStrategy, i as redis, l as DatabasePort, n as sqlite, o as postgres, r as RedisOptions, s as ServiceHandle, t as SqliteOptions } from "./sqlite.adapter.js";
2
3
  import { i as mockOfDate, n as mockOf, r as MockDatePort, t as MockPort } from "./mock-of.js";
3
4
 
@@ -56,7 +57,7 @@ interface ServerResponse {
56
57
  */
57
58
  interface ServerPort {
58
59
  /** Send an HTTP request and return the parsed response. */
59
- request(method: string, path: string, body?: unknown): Promise<ServerResponse>;
60
+ request(method: string, path: string, body?: unknown, headers?: Record<string, string>): Promise<ServerResponse>;
60
61
  }
61
62
  //#endregion
62
63
  //#region src/builder/directory-accessor.d.ts
@@ -210,10 +211,12 @@ declare class SpecificationBuilder {
210
211
  private commandEnv;
211
212
  private config;
212
213
  private fixtures;
214
+ private intercepts;
213
215
  private label;
214
216
  private mocks;
215
217
  private projectName;
216
218
  private request;
219
+ private requestHeaders;
217
220
  private seeds;
218
221
  private spawnConfig;
219
222
  private testDir;
@@ -244,6 +247,29 @@ declare class SpecificationBuilder {
244
247
  * spec("...").env({ HOME: "$WORKDIR", TZ: "UTC" }).exec("status").run();
245
248
  */
246
249
  env(env: CommandEnv): this;
250
+ /**
251
+ * Set HTTP headers for the request. Multiple calls merge.
252
+ *
253
+ * @example
254
+ * spec("french").headers({ 'Accept-Language': 'fr' }).get("/articles").run();
255
+ */
256
+ headers(headers: Record<string, string>): this;
257
+ /**
258
+ * Intercept an outgoing HTTP request and return a controlled response.
259
+ * Uses MSW under the hood. Intercepts are queued — multiple calls with the
260
+ * same trigger fire sequentially (first match consumed first).
261
+ *
262
+ * @param trigger - What to match (use openai.chat(), anthropic.messages(), http.get(), etc.)
263
+ * @param response - What to return (use openai.response(), http.json(), etc.)
264
+ *
265
+ * @example
266
+ * spec('pipeline')
267
+ * .intercept(openai.chat(), openai.response({ categories: ['TECH'] }))
268
+ * .intercept(openai.chat(), openai.response({ headline: 'AI News' }))
269
+ * .exec('process')
270
+ * .run();
271
+ */
272
+ intercept(trigger: InterceptTrigger, response: InterceptResponse): this;
247
273
  /**
248
274
  * Send a GET request to the server adapter.
249
275
  *
@@ -440,8 +466,8 @@ declare class Orchestrator {
440
466
  }
441
467
  //#endregion
442
468
  //#region src/runner/targets.d.ts
469
+ /** Any object with a request method compatible with Hono's app.request(). */
443
470
  type HonoApp$1 = {
444
- fetch: (...args: any[]) => any;
445
471
  request: (path: string, init?: RequestInit) => Promise<Response> | Response;
446
472
  };
447
473
  /** Services map passed to the app factory after infrastructure starts. */
@@ -582,7 +608,7 @@ declare class ExecAdapter implements CommandPort {
582
608
  declare class FetchAdapter implements ServerPort {
583
609
  private baseUrl;
584
610
  constructor(url: string);
585
- request(method: string, path: string, body?: unknown): Promise<ServerResponse>;
611
+ request(method: string, path: string, body?: unknown, headers?: Record<string, string>): Promise<ServerResponse>;
586
612
  }
587
613
  //#endregion
588
614
  //#region src/adapters/hono.adapter.d.ts
@@ -595,7 +621,7 @@ declare class HonoAdapter implements ServerPort {
595
621
  constructor(app: {
596
622
  request: (path: string, init?: RequestInit) => Promise<Response> | Response;
597
623
  });
598
- request(method: string, path: string, body?: unknown): Promise<ServerResponse>;
624
+ request(method: string, path: string, body?: unknown, headers?: Record<string, string>): Promise<ServerResponse>;
599
625
  }
600
626
  //#endregion
601
627
  //#region src/runner/integration.d.ts
package/dist/index.js CHANGED
@@ -113,22 +113,25 @@ var FetchAdapter = class {
113
113
  constructor(url) {
114
114
  this.baseUrl = url.replace(/\/$/, "");
115
115
  }
116
- async request(method, path, body) {
116
+ async request(method, path, body, headers) {
117
117
  const init = {
118
118
  method,
119
- headers: { "Content-Type": "application/json" }
119
+ headers: {
120
+ "Content-Type": "application/json",
121
+ ...headers
122
+ }
120
123
  };
121
124
  if (body !== void 0) init.body = JSON.stringify(body);
122
125
  const response = await fetch(`${this.baseUrl}${path}`, init);
123
126
  const responseBody = await response.json().catch(() => null);
124
- const headers = {};
127
+ const responseHeaders = {};
125
128
  response.headers.forEach((value, key) => {
126
- headers[key] = value;
129
+ responseHeaders[key] = value;
127
130
  });
128
131
  return {
129
132
  status: response.status,
130
133
  body: responseBody,
131
- headers
134
+ headers: responseHeaders
132
135
  };
133
136
  }
134
137
  };
@@ -143,22 +146,25 @@ var HonoAdapter = class {
143
146
  constructor(app) {
144
147
  this.app = app;
145
148
  }
146
- async request(method, path, body) {
149
+ async request(method, path, body, headers) {
147
150
  const init = {
148
151
  method,
149
- headers: { "Content-Type": "application/json" }
152
+ headers: {
153
+ "Content-Type": "application/json",
154
+ ...headers
155
+ }
150
156
  };
151
157
  if (body !== void 0) init.body = JSON.stringify(body);
152
158
  const response = await this.app.request(path, init);
153
159
  const responseBody = await response.json().catch(() => null);
154
- const headers = {};
160
+ const responseHeaders = {};
155
161
  response.headers.forEach((value, key) => {
156
- headers[key] = value;
162
+ responseHeaders[key] = value;
157
163
  });
158
164
  return {
159
165
  status: response.status,
160
166
  body: responseBody,
161
- headers
167
+ headers: responseHeaders
162
168
  };
163
169
  }
164
170
  };
@@ -584,10 +590,12 @@ var SpecificationBuilder = class {
584
590
  commandEnv = {};
585
591
  config;
586
592
  fixtures = [];
593
+ intercepts = [];
587
594
  label;
588
595
  mocks = [];
589
596
  projectName = null;
590
597
  request = null;
598
+ requestHeaders = {};
591
599
  seeds = [];
592
600
  spawnConfig = null;
593
601
  testDir;
@@ -642,6 +650,41 @@ var SpecificationBuilder = class {
642
650
  return this;
643
651
  }
644
652
  /**
653
+ * Set HTTP headers for the request. Multiple calls merge.
654
+ *
655
+ * @example
656
+ * spec("french").headers({ 'Accept-Language': 'fr' }).get("/articles").run();
657
+ */
658
+ headers(headers) {
659
+ this.requestHeaders = {
660
+ ...this.requestHeaders,
661
+ ...headers
662
+ };
663
+ return this;
664
+ }
665
+ /**
666
+ * Intercept an outgoing HTTP request and return a controlled response.
667
+ * Uses MSW under the hood. Intercepts are queued — multiple calls with the
668
+ * same trigger fire sequentially (first match consumed first).
669
+ *
670
+ * @param trigger - What to match (use openai.chat(), anthropic.messages(), http.get(), etc.)
671
+ * @param response - What to return (use openai.response(), http.json(), etc.)
672
+ *
673
+ * @example
674
+ * spec('pipeline')
675
+ * .intercept(openai.chat(), openai.response({ categories: ['TECH'] }))
676
+ * .intercept(openai.chat(), openai.response({ headline: 'AI News' }))
677
+ * .exec('process')
678
+ * .run();
679
+ */
680
+ intercept(trigger, response) {
681
+ this.intercepts.push({
682
+ trigger,
683
+ response
684
+ });
685
+ return this;
686
+ }
687
+ /**
645
688
  * Send a GET request to the server adapter.
646
689
  *
647
690
  * @example
@@ -735,9 +778,17 @@ var SpecificationBuilder = class {
735
778
  await db.seed(sql);
736
779
  }
737
780
  if (this.fixtures.length > 0 && workDir) for (const entry of this.fixtures) cpSync(resolve(this.testDir, "fixtures", entry.file), resolve(workDir, entry.file), { recursive: true });
738
- for (const entry of this.mocks) JSON.parse(readFileSync(resolve(this.testDir, "mock", entry.file), "utf8"));
739
- if (hasHttpAction) return this.runHttpAction();
740
- return this.runCliAction(workDir);
781
+ let cleanupIntercepts = null;
782
+ if (this.intercepts.length > 0) {
783
+ const { registerIntercepts } = await import("./server.js");
784
+ cleanupIntercepts = await registerIntercepts(this.intercepts);
785
+ }
786
+ try {
787
+ if (hasHttpAction) return await this.runHttpAction();
788
+ return await this.runCliAction(workDir);
789
+ } finally {
790
+ if (cleanupIntercepts) cleanupIntercepts();
791
+ }
741
792
  }
742
793
  resolveEnv(workDir) {
743
794
  const keys = Object.keys(this.commandEnv);
@@ -762,7 +813,8 @@ var SpecificationBuilder = class {
762
813
  if (!this.config.server) throw new Error("HTTP actions require a server adapter (use integration() or e2e())");
763
814
  let body;
764
815
  if (this.request.bodyFile) body = JSON.parse(readFileSync(resolve(this.testDir, "requests", this.request.bodyFile), "utf8"));
765
- const response = await this.config.server.request(this.request.method, this.request.path, body);
816
+ const headers = Object.keys(this.requestHeaders).length > 0 ? this.requestHeaders : void 0;
817
+ const response = await this.config.server.request(this.request.method, this.request.path, body, headers);
766
818
  return new SpecificationResult({
767
819
  config: this.config,
768
820
  requestInfo: {