@faremeter/test-harness 0.16.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.
Files changed (64) hide show
  1. package/LICENSE +165 -0
  2. package/README.md +41 -0
  3. package/dist/src/choosers.d.ts +12 -0
  4. package/dist/src/choosers.d.ts.map +1 -0
  5. package/dist/src/choosers.js +79 -0
  6. package/dist/src/harness/config.d.ts +38 -0
  7. package/dist/src/harness/config.d.ts.map +1 -0
  8. package/dist/src/harness/config.js +1 -0
  9. package/dist/src/harness/defaults.d.ts +8 -0
  10. package/dist/src/harness/defaults.d.ts.map +1 -0
  11. package/dist/src/harness/defaults.js +20 -0
  12. package/dist/src/harness/harness.d.ts +70 -0
  13. package/dist/src/harness/harness.d.ts.map +1 -0
  14. package/dist/src/harness/harness.js +205 -0
  15. package/dist/src/harness/harness.test.d.ts +3 -0
  16. package/dist/src/harness/harness.test.d.ts.map +1 -0
  17. package/dist/src/harness/harness.test.js +230 -0
  18. package/dist/src/harness/resource.d.ts +17 -0
  19. package/dist/src/harness/resource.d.ts.map +1 -0
  20. package/dist/src/harness/resource.js +8 -0
  21. package/dist/src/index.d.ts +23 -0
  22. package/dist/src/index.d.ts.map +1 -0
  23. package/dist/src/index.js +17 -0
  24. package/dist/src/interceptors/delay.d.ts +5 -0
  25. package/dist/src/interceptors/delay.d.ts.map +1 -0
  26. package/dist/src/interceptors/delay.js +30 -0
  27. package/dist/src/interceptors/failures.d.ts +12 -0
  28. package/dist/src/interceptors/failures.d.ts.map +1 -0
  29. package/dist/src/interceptors/failures.js +62 -0
  30. package/dist/src/interceptors/hooks.d.ts +20 -0
  31. package/dist/src/interceptors/hooks.d.ts.map +1 -0
  32. package/dist/src/interceptors/hooks.js +56 -0
  33. package/dist/src/interceptors/logging.d.ts +20 -0
  34. package/dist/src/interceptors/logging.d.ts.map +1 -0
  35. package/dist/src/interceptors/logging.js +57 -0
  36. package/dist/src/interceptors/matchers.d.ts +15 -0
  37. package/dist/src/interceptors/matchers.d.ts.map +1 -0
  38. package/dist/src/interceptors/matchers.js +30 -0
  39. package/dist/src/interceptors/responses.d.ts +11 -0
  40. package/dist/src/interceptors/responses.d.ts.map +1 -0
  41. package/dist/src/interceptors/responses.js +47 -0
  42. package/dist/src/interceptors/types.d.ts +20 -0
  43. package/dist/src/interceptors/types.d.ts.map +1 -0
  44. package/dist/src/interceptors/types.js +19 -0
  45. package/dist/src/interceptors/utils.d.ts +2 -0
  46. package/dist/src/interceptors/utils.d.ts.map +1 -0
  47. package/dist/src/interceptors/utils.js +9 -0
  48. package/dist/src/scheme/client.d.ts +16 -0
  49. package/dist/src/scheme/client.d.ts.map +1 -0
  50. package/dist/src/scheme/client.js +38 -0
  51. package/dist/src/scheme/constants.d.ts +4 -0
  52. package/dist/src/scheme/constants.d.ts.map +1 -0
  53. package/dist/src/scheme/constants.js +3 -0
  54. package/dist/src/scheme/facilitator.d.ts +16 -0
  55. package/dist/src/scheme/facilitator.d.ts.map +1 -0
  56. package/dist/src/scheme/facilitator.js +121 -0
  57. package/dist/src/scheme/types.d.ts +8 -0
  58. package/dist/src/scheme/types.d.ts.map +1 -0
  59. package/dist/src/scheme/types.js +3 -0
  60. package/dist/src/testing/console.d.ts +9 -0
  61. package/dist/src/testing/console.d.ts.map +1 -0
  62. package/dist/src/testing/console.js +15 -0
  63. package/dist/tsconfig.tsbuildinfo +1 -0
  64. package/package.json +51 -0
@@ -0,0 +1,11 @@
1
+ import type { x402PaymentRequirements } from "@faremeter/types/x402";
2
+ export declare function jsonResponse(status: number, body: object): Response;
3
+ export declare function verifyFailedResponse(reason: string): Response;
4
+ export declare function verifySuccessResponse(): Response;
5
+ export declare function settleFailedResponse(error: string): Response;
6
+ export declare function settleSuccessResponse(txHash: string, networkId: string): Response;
7
+ export declare function paymentRequiredResponse(accepts: x402PaymentRequirements[]): Response;
8
+ export declare function networkError(message?: string): Error;
9
+ export declare function timeoutError(): Error;
10
+ export declare function httpError(status: number, message: string): Response;
11
+ //# sourceMappingURL=responses.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"responses.d.ts","sourceRoot":"","sources":["../../../src/interceptors/responses.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAErE,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,QAAQ,CAKnE;AAED,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ,CAK7D;AAED,wBAAgB,qBAAqB,IAAI,QAAQ,CAIhD;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,QAAQ,CAO5D;AAED,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,GAChB,QAAQ,CAMV;AAED,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,uBAAuB,EAAE,GACjC,QAAQ,CAKV;AAED,wBAAgB,YAAY,CAAC,OAAO,SAAkB,GAAG,KAAK,CAE7D;AAED,wBAAgB,YAAY,IAAI,KAAK,CAEpC;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,QAAQ,CAEnE"}
@@ -0,0 +1,47 @@
1
+ export function jsonResponse(status, body) {
2
+ return new Response(JSON.stringify(body), {
3
+ status,
4
+ headers: { "Content-Type": "application/json" },
5
+ });
6
+ }
7
+ export function verifyFailedResponse(reason) {
8
+ return jsonResponse(200, {
9
+ isValid: false,
10
+ invalidReason: reason,
11
+ });
12
+ }
13
+ export function verifySuccessResponse() {
14
+ return jsonResponse(200, {
15
+ isValid: true,
16
+ });
17
+ }
18
+ export function settleFailedResponse(error) {
19
+ return jsonResponse(200, {
20
+ success: false,
21
+ error,
22
+ txHash: null,
23
+ networkId: null,
24
+ });
25
+ }
26
+ export function settleSuccessResponse(txHash, networkId) {
27
+ return jsonResponse(200, {
28
+ success: true,
29
+ txHash,
30
+ networkId,
31
+ });
32
+ }
33
+ export function paymentRequiredResponse(accepts) {
34
+ return jsonResponse(402, {
35
+ x402Version: 1,
36
+ accepts,
37
+ });
38
+ }
39
+ export function networkError(message = "Network error") {
40
+ return new Error(message);
41
+ }
42
+ export function timeoutError() {
43
+ return new Error("Request timed out");
44
+ }
45
+ export function httpError(status, message) {
46
+ return jsonResponse(status, { error: message });
47
+ }
@@ -0,0 +1,20 @@
1
+ export type Interceptor = (fetch: typeof globalThis.fetch) => typeof globalThis.fetch;
2
+ /**
3
+ * Compose multiple interceptors into a single interceptor.
4
+ *
5
+ * Interceptors are applied right-to-left (last interceptor wraps innermost).
6
+ * This means the first interceptor in the array sees the request first and
7
+ * the response last.
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * const composed = composeInterceptors(
12
+ * loggingInterceptor, // Sees request first, response last
13
+ * failureInterceptor, // Sees request second
14
+ * delayInterceptor, // Innermost - closest to actual fetch
15
+ * );
16
+ * ```
17
+ */
18
+ export declare function composeInterceptors(...interceptors: Interceptor[]): Interceptor;
19
+ export type RequestMatcher = (url: string, init?: RequestInit) => boolean;
20
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/interceptors/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,WAAW,GAAG,CACxB,KAAK,EAAE,OAAO,UAAU,CAAC,KAAK,KAC3B,OAAO,UAAU,CAAC,KAAK,CAAC;AAE7B;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,mBAAmB,CACjC,GAAG,YAAY,EAAE,WAAW,EAAE,GAC7B,WAAW,CAMb;AAED,MAAM,MAAM,cAAc,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Compose multiple interceptors into a single interceptor.
3
+ *
4
+ * Interceptors are applied right-to-left (last interceptor wraps innermost).
5
+ * This means the first interceptor in the array sees the request first and
6
+ * the response last.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * const composed = composeInterceptors(
11
+ * loggingInterceptor, // Sees request first, response last
12
+ * failureInterceptor, // Sees request second
13
+ * delayInterceptor, // Innermost - closest to actual fetch
14
+ * );
15
+ * ```
16
+ */
17
+ export function composeInterceptors(...interceptors) {
18
+ return (baseFetch) => interceptors.reduceRight((fetch, interceptor) => interceptor(fetch), baseFetch);
19
+ }
@@ -0,0 +1,2 @@
1
+ export declare function getURLFromRequestInfo(input: RequestInfo | URL | string): string;
2
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/interceptors/utils.ts"],"names":[],"mappings":"AAAA,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,WAAW,GAAG,GAAG,GAAG,MAAM,GAChC,MAAM,CAUR"}
@@ -0,0 +1,9 @@
1
+ export function getURLFromRequestInfo(input) {
2
+ if (typeof input === "string") {
3
+ return input;
4
+ }
5
+ if (input instanceof URL) {
6
+ return input.href;
7
+ }
8
+ return input.url;
9
+ }
@@ -0,0 +1,16 @@
1
+ import type { PaymentHandler } from "@faremeter/types/client";
2
+ import type { x402PaymentRequirements } from "@faremeter/types/x402";
3
+ import { type TestPaymentPayload } from "./types.js";
4
+ export type CreateTestPaymentHandlerOpts = {
5
+ onMatch?: (requirements: x402PaymentRequirements) => void;
6
+ onExec?: (requirements: x402PaymentRequirements, payload: TestPaymentPayload) => void;
7
+ metadata?: Record<string, unknown>;
8
+ };
9
+ /**
10
+ * Create a test payment handler.
11
+ *
12
+ * This handler creates simple test payment payloads without any cryptographic
13
+ * operations, making it suitable for testing the x402 protocol flow.
14
+ */
15
+ export declare function createTestPaymentHandler(opts?: CreateTestPaymentHandlerOpts): PaymentHandler;
16
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../src/scheme/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EAGf,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAGrE,OAAO,EAAkB,KAAK,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAElE,MAAM,MAAM,4BAA4B,GAAG;IACzC,OAAO,CAAC,EAAE,CAAC,YAAY,EAAE,uBAAuB,KAAK,IAAI,CAAC;IAC1D,MAAM,CAAC,EAAE,CACP,YAAY,EAAE,uBAAuB,EACrC,OAAO,EAAE,kBAAkB,KACxB,IAAI,CAAC;IACV,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC,CAAC;AASF;;;;;GAKG;AACH,wBAAgB,wBAAwB,CACtC,IAAI,GAAE,4BAAiC,GACtC,cAAc,CAiChB"}
@@ -0,0 +1,38 @@
1
+ import { TEST_SCHEME, TEST_NETWORK } from "./constants.js";
2
+ import { generateTestId } from "./types.js";
3
+ function isMatchingRequirement(req) {
4
+ return (req.scheme.toLowerCase() === TEST_SCHEME.toLowerCase() &&
5
+ req.network.toLowerCase() === TEST_NETWORK.toLowerCase());
6
+ }
7
+ /**
8
+ * Create a test payment handler.
9
+ *
10
+ * This handler creates simple test payment payloads without any cryptographic
11
+ * operations, making it suitable for testing the x402 protocol flow.
12
+ */
13
+ export function createTestPaymentHandler(opts = {}) {
14
+ const { onMatch, onExec, metadata } = opts;
15
+ return async function handlePayment(_context, accepts) {
16
+ const compatibleRequirements = accepts.filter(isMatchingRequirement);
17
+ return compatibleRequirements.map((requirements) => {
18
+ if (onMatch) {
19
+ onMatch(requirements);
20
+ }
21
+ return {
22
+ requirements,
23
+ exec: async () => {
24
+ const payload = {
25
+ testId: generateTestId(),
26
+ amount: requirements.maxAmountRequired,
27
+ timestamp: Date.now(),
28
+ metadata,
29
+ };
30
+ if (onExec) {
31
+ onExec(requirements, payload);
32
+ }
33
+ return { payload };
34
+ },
35
+ };
36
+ });
37
+ };
38
+ }
@@ -0,0 +1,4 @@
1
+ export declare const TEST_SCHEME = "test";
2
+ export declare const TEST_NETWORK = "test-local";
3
+ export declare const TEST_ASSET = "TEST";
4
+ //# sourceMappingURL=constants.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../src/scheme/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,WAAW,SAAS,CAAC;AAElC,eAAO,MAAM,YAAY,eAAe,CAAC;AAEzC,eAAO,MAAM,UAAU,SAAS,CAAC"}
@@ -0,0 +1,3 @@
1
+ export const TEST_SCHEME = "test";
2
+ export const TEST_NETWORK = "test-local";
3
+ export const TEST_ASSET = "TEST";
@@ -0,0 +1,16 @@
1
+ import type { x402PaymentRequirements, x402PaymentPayload } from "@faremeter/types/x402";
2
+ import type { FacilitatorHandler } from "@faremeter/types/facilitator";
3
+ import type { TestPaymentPayload } from "./types.js";
4
+ export type CreateTestFacilitatorHandlerOpts = {
5
+ payTo: string;
6
+ onVerify?: (requirements: x402PaymentRequirements, payload: x402PaymentPayload, testPayload: TestPaymentPayload) => void;
7
+ onSettle?: (requirements: x402PaymentRequirements, payload: x402PaymentPayload, testPayload: TestPaymentPayload) => void;
8
+ };
9
+ /**
10
+ * Create a test facilitator handler.
11
+ *
12
+ * This handler validates protocol structure without any cryptographic
13
+ * operations, making it suitable for testing the x402 protocol flow.
14
+ */
15
+ export declare function createTestFacilitatorHandler(opts: CreateTestFacilitatorHandlerOpts): FacilitatorHandler;
16
+ //# sourceMappingURL=facilitator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"facilitator.d.ts","sourceRoot":"","sources":["../../../src/scheme/facilitator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,uBAAuB,EACvB,kBAAkB,EAInB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAGvE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAElD,MAAM,MAAM,gCAAgC,GAAG;IAC7C,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,CACT,YAAY,EAAE,uBAAuB,EACrC,OAAO,EAAE,kBAAkB,EAC3B,WAAW,EAAE,kBAAkB,KAC5B,IAAI,CAAC;IACV,QAAQ,CAAC,EAAE,CACT,YAAY,EAAE,uBAAuB,EACrC,OAAO,EAAE,kBAAkB,EAC3B,WAAW,EAAE,kBAAkB,KAC5B,IAAI,CAAC;CACX,CAAC;AAuCF;;;;;GAKG;AACH,wBAAgB,4BAA4B,CAC1C,IAAI,EAAE,gCAAgC,GACrC,kBAAkB,CAiHpB"}
@@ -0,0 +1,121 @@
1
+ import { TEST_SCHEME, TEST_NETWORK, TEST_ASSET } from "./constants.js";
2
+ function isMatchingRequirement(req) {
3
+ return (req.scheme.toLowerCase() === TEST_SCHEME.toLowerCase() &&
4
+ req.network.toLowerCase() === TEST_NETWORK.toLowerCase());
5
+ }
6
+ function validateTestPayload(payload) {
7
+ const p = payload;
8
+ if (typeof p.testId !== "string" || p.testId.length === 0) {
9
+ return { valid: false, error: "Missing or invalid testId" };
10
+ }
11
+ if (typeof p.amount !== "string" || p.amount.length === 0) {
12
+ return { valid: false, error: "Missing or invalid amount" };
13
+ }
14
+ if (typeof p.timestamp !== "number" || p.timestamp <= 0) {
15
+ return { valid: false, error: "Missing or invalid timestamp" };
16
+ }
17
+ return {
18
+ valid: true,
19
+ payload: {
20
+ testId: p.testId,
21
+ amount: p.amount,
22
+ timestamp: p.timestamp,
23
+ metadata: p.metadata,
24
+ },
25
+ };
26
+ }
27
+ /**
28
+ * Create a test facilitator handler.
29
+ *
30
+ * This handler validates protocol structure without any cryptographic
31
+ * operations, making it suitable for testing the x402 protocol flow.
32
+ */
33
+ export function createTestFacilitatorHandler(opts) {
34
+ const { payTo, onVerify, onSettle } = opts;
35
+ const getSupported = () => {
36
+ return [
37
+ Promise.resolve({
38
+ x402Version: 1,
39
+ scheme: TEST_SCHEME,
40
+ network: TEST_NETWORK,
41
+ }),
42
+ ];
43
+ };
44
+ const getRequirements = async (req) => {
45
+ return req.filter(isMatchingRequirement).map((r) => ({
46
+ ...r,
47
+ asset: r.asset || TEST_ASSET,
48
+ payTo: r.payTo || payTo,
49
+ maxTimeoutSeconds: r.maxTimeoutSeconds || 300,
50
+ }));
51
+ };
52
+ const handleVerify = async (requirements, payment) => {
53
+ if (!isMatchingRequirement(requirements)) {
54
+ return null; // Not for us, let another handler try
55
+ }
56
+ const result = validateTestPayload(payment.payload);
57
+ if (!result.valid) {
58
+ return { isValid: false, invalidReason: result.error };
59
+ }
60
+ const testPayload = result.payload;
61
+ // Verify amount matches
62
+ if (testPayload.amount !== requirements.maxAmountRequired) {
63
+ return { isValid: false, invalidReason: "Amount mismatch" };
64
+ }
65
+ // Verify payment is to the correct address
66
+ if (requirements.payTo.toLowerCase() !== payTo.toLowerCase()) {
67
+ return { isValid: false, invalidReason: "Payment to wrong address" };
68
+ }
69
+ if (onVerify) {
70
+ onVerify(requirements, payment, testPayload);
71
+ }
72
+ return { isValid: true };
73
+ };
74
+ const handleSettle = async (requirements, payment) => {
75
+ if (!isMatchingRequirement(requirements)) {
76
+ return null; // Not for us, let another handler try
77
+ }
78
+ const result = validateTestPayload(payment.payload);
79
+ if (!result.valid) {
80
+ return {
81
+ success: false,
82
+ error: result.error,
83
+ txHash: null,
84
+ networkId: null,
85
+ };
86
+ }
87
+ const testPayload = result.payload;
88
+ // Verify amount matches
89
+ if (testPayload.amount !== requirements.maxAmountRequired) {
90
+ return {
91
+ success: false,
92
+ error: "Amount mismatch",
93
+ txHash: null,
94
+ networkId: null,
95
+ };
96
+ }
97
+ // Verify payment is to the correct address
98
+ if (requirements.payTo.toLowerCase() !== payTo.toLowerCase()) {
99
+ return {
100
+ success: false,
101
+ error: "Payment to wrong address",
102
+ txHash: null,
103
+ networkId: null,
104
+ };
105
+ }
106
+ if (onSettle) {
107
+ onSettle(requirements, payment, testPayload);
108
+ }
109
+ return {
110
+ success: true,
111
+ txHash: `test-tx-${testPayload.testId}`,
112
+ networkId: TEST_NETWORK,
113
+ };
114
+ };
115
+ return {
116
+ getSupported,
117
+ getRequirements,
118
+ handleVerify,
119
+ handleSettle,
120
+ };
121
+ }
@@ -0,0 +1,8 @@
1
+ export type TestPaymentPayload = {
2
+ testId: string;
3
+ amount: string;
4
+ timestamp: number;
5
+ metadata?: Record<string, unknown> | undefined;
6
+ };
7
+ export declare function generateTestId(): string;
8
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/scheme/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;CAChD,CAAC;AAEF,wBAAgB,cAAc,IAAI,MAAM,CAEvC"}
@@ -0,0 +1,3 @@
1
+ export function generateTestId() {
2
+ return `test-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
3
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Suppresses console.error output during tests.
3
+ * Returns a restore function to be called in teardown.
4
+ *
5
+ * Usage with tap:
6
+ * t.teardown(suppressConsoleErrors());
7
+ */
8
+ export declare function suppressConsoleErrors(): () => void;
9
+ //# sourceMappingURL=console.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"console.d.ts","sourceRoot":"","sources":["../../../src/testing/console.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,wBAAgB,qBAAqB,IAAI,MAAM,IAAI,CAMlD"}
@@ -0,0 +1,15 @@
1
+ /* eslint-disable no-console, @typescript-eslint/no-empty-function */
2
+ /**
3
+ * Suppresses console.error output during tests.
4
+ * Returns a restore function to be called in teardown.
5
+ *
6
+ * Usage with tap:
7
+ * t.teardown(suppressConsoleErrors());
8
+ */
9
+ export function suppressConsoleErrors() {
10
+ const original = console.error;
11
+ console.error = () => { };
12
+ return () => {
13
+ console.error = original;
14
+ };
15
+ }