diff --git a/README.md b/README.md index 6afee74..6fa7289 100644 --- a/README.md +++ b/README.md @@ -135,6 +135,7 @@ interface CircuitBreakerResult { state: CircuitState // Current circuit breaker state failureCount: number // Current failure count executionTimeMs: number // Execution time in milliseconds + fallbackResponseProvided?: boolean // Whether a fallback response was provided } ``` @@ -179,7 +180,10 @@ interface ProxyRequestOptions { res: Response, body?: ReadableStream | null, ) => void | Promise - onError?: (req: Request, error: Error) => void | Promise + onError?: ( + req: Request, + error: Error, + ) => void | Promise | Promise beforeCircuitBreakerExecution?: ( req: Request, opts: ProxyRequestOptions, @@ -547,6 +551,23 @@ proxy(req, undefined, { }) ``` +#### Returning Fallback Responses + +You can return a fallback response from the `onError` hook by resolving the hook with a `Response` object. This allows you to customize the error response sent to the client. + +```typescript +proxy(req, undefined, { + onError: async (req, error) => { + // Log error + console.error("Proxy error:", error) + + // Return a fallback response + console.log("Returning fallback response for:", req.url) + return new Response("Fallback response", { status: 200 }) + }, +}) +``` + ## Performance Tips 1. **URL Caching**: Keep `cacheURLs` enabled (default 100) for better performance diff --git a/package.json b/package.json index dc72168..130298a 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,8 @@ "clean": "rm -rf lib/", "prepublishOnly": "bun run clean && bun run build", "example:benchmark": "bun run examples/local-gateway-server.ts", - "deploy": "bun run prepublishOnly && bun publish" + "deploy": "bun run prepublishOnly && bun publish", + "actions": "DOCKER_HOST=$(docker context inspect --format '{{.Endpoints.docker.Host}}') act" }, "repository": { "type": "git", diff --git a/src/proxy.ts b/src/proxy.ts index ed4efaa..7e2e9d8 100644 --- a/src/proxy.ts +++ b/src/proxy.ts @@ -166,8 +166,9 @@ export class FetchProxy { currentLogger.logRequestError(req, err, { requestId, executionTime }) // Execute error hooks + let fallbackResponse: Response | void = undefined if (options.onError) { - await options.onError(req, err) + fallbackResponse = await options.onError(req, err) } // Execute circuit breaker completion hooks for failures @@ -179,12 +180,17 @@ export class FetchProxy { state: this.circuitBreaker.getState(), failureCount: this.circuitBreaker.getFailures(), executionTimeMs: executionTime, + fallbackResponseProvided: fallbackResponse instanceof Response, }, options, ) + if (fallbackResponse instanceof Response) { + // If onError provided a fallback response, return it + return fallbackResponse + } // Return appropriate error response - if (err.message.includes("Circuit breaker is OPEN")) { + else if (err.message.includes("Circuit breaker is OPEN")) { return new Response("Service Unavailable", { status: 503 }) } else if ( err.message.includes("timeout") || diff --git a/src/types.ts b/src/types.ts index bbbccdb..7d24bd4 100644 --- a/src/types.ts +++ b/src/types.ts @@ -83,7 +83,10 @@ export type AfterCircuitBreakerHook = ( result: CircuitBreakerResult, ) => void | Promise -export type ErrorHook = (req: Request, error: Error) => void | Promise +export type ErrorHook = ( + req: Request, + error: Error, +) => void | Promise | Promise // Circuit breaker result information export interface CircuitBreakerResult { @@ -92,6 +95,7 @@ export interface CircuitBreakerResult { state: CircuitState failureCount: number executionTimeMs: number + fallbackResponseProvided?: boolean } export enum CircuitState { diff --git a/tests/dos-prevention.test.ts b/tests/dos-prevention.test.ts index c5b1299..12e6423 100644 --- a/tests/dos-prevention.test.ts +++ b/tests/dos-prevention.test.ts @@ -1,8 +1,4 @@ -import { afterAll, describe, expect, test, mock } from "bun:test" - -afterAll(() => { - mock.restore() -}) +import { describe, expect, test } from "bun:test" describe("DoS and Resource Exhaustion Security Tests", () => { describe("Request Parameter Validation", () => { diff --git a/tests/enhanced-hooks.test.ts b/tests/enhanced-hooks.test.ts index 52d295b..f405cde 100644 --- a/tests/enhanced-hooks.test.ts +++ b/tests/enhanced-hooks.test.ts @@ -9,18 +9,17 @@ import { beforeEach, jest, afterAll, - mock, + spyOn, } from "bun:test" import { FetchProxy } from "../src/proxy" import { CircuitState } from "../src/types" import type { ProxyRequestOptions, CircuitBreakerResult } from "../src/types" -// Mock fetch for testing -const mockFetch = jest.fn() -;(global as any).fetch = mockFetch +// Spy on fetch for testing +let fetchSpy: ReturnType afterAll(() => { - mock.restore() + fetchSpy?.mockRestore() }) describe("Enhanced Hook Naming Conventions", () => { @@ -39,8 +38,9 @@ describe("Enhanced Hook Naming Conventions", () => { headers: new Headers({ "content-type": "application/json" }), }) - mockFetch.mockClear() - mockFetch.mockResolvedValue(mockResponse) + fetchSpy = spyOn(global, "fetch") + fetchSpy.mockClear() + fetchSpy.mockResolvedValue(mockResponse) }) describe("beforeRequest Hook", () => { @@ -56,7 +56,7 @@ describe("Enhanced Hook Naming Conventions", () => { expect(beforeRequestHook).toHaveBeenCalledTimes(1) expect(beforeRequestHook).toHaveBeenCalledWith(request, options) - expect(mockFetch).toHaveBeenCalledTimes(1) + expect(fetchSpy).toHaveBeenCalledTimes(1) }) it("should handle async beforeRequest hooks", async () => { @@ -139,7 +139,7 @@ describe("Enhanced Hook Naming Conventions", () => { const request = new Request("https://example.com/test") const error = new Error("Network error") - mockFetch.mockRejectedValueOnce(error) + fetchSpy.mockRejectedValueOnce(error) const options: ProxyRequestOptions = { afterCircuitBreakerExecution: afterCircuitBreakerHook, @@ -166,7 +166,7 @@ describe("Enhanced Hook Naming Conventions", () => { const request = new Request("https://example.com/test") // Add some delay to the fetch - mockFetch.mockImplementationOnce( + fetchSpy.mockImplementationOnce( () => new Promise((resolve) => setTimeout(() => resolve(mockResponse), 50)), ) @@ -296,8 +296,8 @@ describe("Enhanced Hook Naming Conventions", () => { await proxy.proxy(request, undefined, options) // Verify the mock was called (we can't easily verify exact headers due to internal processing) - expect(mockFetch).toHaveBeenCalledTimes(1) - expect(mockFetch).toHaveBeenCalledWith( + expect(fetchSpy).toHaveBeenCalledTimes(1) + expect(fetchSpy).toHaveBeenCalledWith( expect.any(String), expect.objectContaining({ headers: expect.any(Headers), @@ -315,7 +315,7 @@ describe("Enhanced Hook Naming Conventions", () => { }, }) - mockFetch.mockResolvedValueOnce(originalResponse) + fetchSpy.mockResolvedValueOnce(originalResponse) const request = new Request("https://example.com/test") diff --git a/tests/header-injection.test.ts b/tests/header-injection.test.ts index 0c9e569..3475add 100644 --- a/tests/header-injection.test.ts +++ b/tests/header-injection.test.ts @@ -1,14 +1,10 @@ /** * Security tests for header injection vulnerabilities */ -import { describe, expect, it, afterAll, mock } from "bun:test" +import { describe, expect, it } from "bun:test" import { recordToHeaders } from "../src/utils" -afterAll(() => { - mock.restore() -}) - describe("Header Injection Security Tests", () => { describe("CRLF Header Injection", () => { it("should reject header names with CRLF characters", () => { diff --git a/tests/http-method-validation.test.ts b/tests/http-method-validation.test.ts index c24c981..d75fc4e 100644 --- a/tests/http-method-validation.test.ts +++ b/tests/http-method-validation.test.ts @@ -1,12 +1,13 @@ -import { describe, it, expect, beforeEach, afterAll, mock } from "bun:test" +import { describe, it, expect, beforeEach, spyOn, afterEach } from "bun:test" import { validateHttpMethod } from "../src/utils" import { FetchProxy } from "../src/proxy" -afterAll(() => { - mock.restore() -}) - describe("HTTP Method Validation Security Tests", () => { + let fetchSpy: ReturnType + + afterEach(() => { + fetchSpy?.mockRestore() + }) describe("Direct Method Validation", () => { it("should reject CONNECT method", () => { expect(() => { @@ -75,6 +76,15 @@ describe("HTTP Method Validation Security Tests", () => { base: "http://httpbin.org", // Use a real service for testing circuitBreaker: { enabled: false }, }) + + // Mock fetch to return a successful response + fetchSpy = spyOn(global, "fetch").mockResolvedValue( + new Response("", { + status: 200, + statusText: "OK", + headers: new Headers({ "content-type": "text/plain" }), + }), + ) }) it("should reject CONNECT method in proxy (if runtime allows it)", async () => { @@ -107,6 +117,9 @@ describe("HTTP Method Validation Security Tests", () => { // The normalized request should work fine const response = await proxy.proxy(request) expect(response.status).toBe(200) + + // Verify fetch was called + expect(fetchSpy).toHaveBeenCalledTimes(1) }) it("should allow safe methods in proxy", async () => { @@ -116,6 +129,9 @@ describe("HTTP Method Validation Security Tests", () => { const response = await proxy.proxy(request) expect(response.status).toBe(200) + + // Verify fetch was called + expect(fetchSpy).toHaveBeenCalledTimes(1) }) it("should validate methods when passed through request options", async () => { diff --git a/tests/index.test.ts b/tests/index.test.ts index f4a4564..e832a6a 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -1,4 +1,4 @@ -import { describe, it, expect, beforeAll, afterAll, mock } from "bun:test" +import { describe, it, expect, beforeAll, afterAll, spyOn } from "bun:test" import createFetchGate, { FetchProxy } from "../src/index" import { buildURL, @@ -55,7 +55,7 @@ describe("fetch-gate", () => { afterAll(() => { server?.stop() - mock.restore() + // No need for explicit restore with spyOn as it's automatically cleaned up }) describe("createFetchGate", () => { @@ -308,10 +308,9 @@ describe("fetch-gate", () => { describe("Circuit Breaker Edge Cases", () => { it("should transition to HALF_OPEN state after reset timeout", async () => { - // Custom mock for Date.now() - const originalDateNow = Date.now - let now = originalDateNow() - global.Date.now = () => now + // Spy on Date.now() + let now = Date.now() + const dateNowSpy = spyOn(Date, "now").mockImplementation(() => now) const circuitBreaker = new CircuitBreaker({ failureThreshold: 1, @@ -330,8 +329,8 @@ describe("fetch-gate", () => { expect(circuitBreaker.getState()).toBe(CircuitState.HALF_OPEN) - // Restore original Date.now() - global.Date.now = originalDateNow + // Restore Date.now() spy + dateNowSpy.mockRestore() }) it("should reset failures after successful execution in HALF_OPEN state", async () => { diff --git a/tests/logging.test.ts b/tests/logging.test.ts index 91c0d25..103b7ca 100644 --- a/tests/logging.test.ts +++ b/tests/logging.test.ts @@ -1,12 +1,4 @@ -import { - describe, - expect, - it, - beforeEach, - spyOn, - afterAll, - mock, -} from "bun:test" +import { describe, expect, it, beforeEach, spyOn, afterAll } from "bun:test" import { FetchProxy } from "../src/proxy" import { ProxyLogger, @@ -15,11 +7,11 @@ import { } from "../src/logger" import { CircuitState } from "../src/types" -// Mock fetch for testing -const originalFetch = global.fetch +// Spy on fetch for testing +let fetchSpy: ReturnType afterAll(() => { - mock.restore() + fetchSpy?.mockRestore() }) describe("Logging Integration", () => { diff --git a/tests/path-traversal.test.ts b/tests/path-traversal.test.ts index c8d082b..5feeb17 100644 --- a/tests/path-traversal.test.ts +++ b/tests/path-traversal.test.ts @@ -1,10 +1,6 @@ -import { describe, expect, test, mock, afterAll } from "bun:test" +import { describe, expect, test } from "bun:test" import { normalizeSecurePath } from "../src/utils" -afterAll(() => { - mock.restore() -}) - describe("Path Traversal Security", () => { describe("normalizeSecurePath", () => { test("should normalize simple valid paths", () => { diff --git a/tests/proxy-fallback.test.ts b/tests/proxy-fallback.test.ts new file mode 100644 index 0000000..2e3360f --- /dev/null +++ b/tests/proxy-fallback.test.ts @@ -0,0 +1,446 @@ +/** + * Tests for proxy fallback response using onError hook + */ + +import { + describe, + expect, + it, + beforeEach, + jest, + afterAll, + spyOn, +} from "bun:test" +import { FetchProxy } from "../src/proxy" + +// Spy on fetch for testing +let fetchSpy: ReturnType + +afterAll(() => { + fetchSpy?.mockRestore() +}) + +describe("Proxy Fallback Response", () => { + let proxy: FetchProxy + + beforeEach(() => { + proxy = new FetchProxy({ + base: "https://api.example.com", + timeout: 5000, + }) + fetchSpy = spyOn(global, "fetch") + fetchSpy.mockClear() + }) + + describe("onError Hook Fallback", () => { + it("should return fallback response when onError hook provides one", async () => { + // Mock a network error + fetchSpy.mockRejectedValue(new Error("Network error")) + + const fallbackResponse = new Response( + JSON.stringify({ + message: "Service temporarily unavailable", + fallback: true, + }), + { + status: 200, + statusText: "OK", + headers: new Headers({ "content-type": "application/json" }), + }, + ) + + const onErrorHook = jest.fn().mockResolvedValue(fallbackResponse) + + const request = new Request("https://example.com/test") + const response = await proxy.proxy(request, "/api/data", { + onError: onErrorHook, + }) + + expect(onErrorHook).toHaveBeenCalledWith( + expect.any(Request), + expect.any(Error), + ) + expect(response).toBe(fallbackResponse) + expect(response.status).toBe(200) + + const body = (await response.json()) as { fallback: boolean } + expect(body.fallback).toBe(true) + }) + + it("should handle async fallback response generation", async () => { + fetchSpy.mockRejectedValue(new Error("Timeout error")) + + const onErrorHook = jest.fn().mockImplementation(async (req, error) => { + // Simulate async fallback logic + await new Promise((resolve) => setTimeout(resolve, 10)) + + return new Response( + JSON.stringify({ + error: "Service unavailable", + timestamp: Date.now(), + originalUrl: req.url, + }), + { + status: 503, + statusText: "Service Unavailable", + headers: new Headers({ "content-type": "application/json" }), + }, + ) + }) + + const request = new Request("https://example.com/test") + const response = await proxy.proxy(request, "/api/data", { + onError: onErrorHook, + }) + + expect(onErrorHook).toHaveBeenCalledWith( + expect.any(Request), + expect.any(Error), + ) + expect(response.status).toBe(503) + + const body = (await response.json()) as { + error: string + originalUrl: string + } + expect(body.error).toBe("Service unavailable") + expect(body.originalUrl).toBe("https://example.com/test") + }) + + it("should fallback to default error response when onError hook returns void", async () => { + fetchSpy.mockRejectedValue(new Error("Network error")) + + const onErrorHook = jest.fn().mockResolvedValue(undefined) + + const request = new Request("https://example.com/test") + const response = await proxy.proxy(request, "/api/data", { + onError: onErrorHook, + }) + + expect(onErrorHook).toHaveBeenCalledWith( + expect.any(Request), + expect.any(Error), + ) + expect(response.status).toBe(502) // Default error response + }) + + it("should handle different error types with appropriate fallbacks", async () => { + const testCases = [ + { + error: new Error("timeout"), + expectedStatus: 504, + fallbackStatus: 408, + fallbackMessage: "Request timeout - try again later", + }, + { + error: new Error("Circuit breaker is OPEN"), + expectedStatus: 503, + fallbackStatus: 503, + fallbackMessage: "Service temporarily unavailable", + }, + { + error: new Error("Network error"), + expectedStatus: 502, + fallbackStatus: 500, + fallbackMessage: "Internal server error", + }, + ] + + for (const testCase of testCases) { + fetchSpy.mockRejectedValue(testCase.error) + + const onErrorHook = jest.fn().mockResolvedValue( + new Response(JSON.stringify({ message: testCase.fallbackMessage }), { + status: testCase.fallbackStatus, + headers: new Headers({ "content-type": "application/json" }), + }), + ) + + const request = new Request("https://example.com/test") + const response = await proxy.proxy(request, "/api/data", { + onError: onErrorHook, + }) + + expect(response.status).toBe(testCase.fallbackStatus) + + const body = (await response.json()) as { message: string } + expect(body.message).toBe(testCase.fallbackMessage) + } + }) + + it("should handle circuit breaker with fallback response", async () => { + const proxyWithCircuitBreaker = new FetchProxy({ + base: "https://api.example.com", + circuitBreaker: { + failureThreshold: 1, + resetTimeout: 1000, + enabled: true, + }, + }) + + // First request fails to trigger circuit breaker + fetchSpy.mockRejectedValue(new Error("Service error")) + + const onErrorHook = jest + .fn() + .mockResolvedValueOnce( + new Response( + JSON.stringify({ + message: "Using cached data", + data: { cached: true }, + source: "fallback", + }), + { + status: 200, + headers: new Headers({ "content-type": "application/json" }), + }, + ), + ) + .mockResolvedValueOnce( + new Response( + JSON.stringify({ + message: "Using cached data", + data: { cached: true }, + source: "fallback", + }), + { + status: 200, + headers: new Headers({ "content-type": "application/json" }), + }, + ), + ) + + const request = new Request("https://example.com/test") + + // First request - should fail and trigger circuit breaker + const response1 = await proxyWithCircuitBreaker.proxy( + request, + "/api/data", + { + onError: onErrorHook, + }, + ) + + expect(response1.status).toBe(200) + const body1 = (await response1.json()) as { source: string } + expect(body1.source).toBe("fallback") + + // Second request - circuit breaker should be open + const response2 = await proxyWithCircuitBreaker.proxy( + request, + "/api/data", + { + onError: onErrorHook, + }, + ) + + expect(response2.status).toBe(200) + const body2 = (await response2.json()) as { source: string } + expect(body2.source).toBe("fallback") + }) + + it("should pass correct request and error objects to onError hook", async () => { + const networkError = new Error("ECONNREFUSED") + fetchSpy.mockRejectedValue(networkError) + + const onErrorHook = jest + .fn() + .mockResolvedValue(new Response("Fallback response", { status: 200 })) + + const originalRequest = new Request("https://example.com/test", { + method: "POST", + headers: { "X-Custom": "value" }, + body: JSON.stringify({ test: "data" }), + }) + + await proxy.proxy(originalRequest, "/api/data", { + onError: onErrorHook, + }) + + expect(onErrorHook).toHaveBeenCalledWith( + expect.any(Request), + networkError, + ) + + // Check the actual URL passed to the hook (original request URL, not target URL) + const actualRequest = onErrorHook.mock.calls[0][0] + expect(actualRequest.url).toBe("https://example.com/test") + expect(actualRequest.method).toBe("POST") + }) + + it("should handle multiple concurrent requests with fallback", async () => { + fetchSpy.mockRejectedValue(new Error("Service unavailable")) + + const onErrorHook = jest.fn().mockImplementation(async (req, error) => { + return new Response( + JSON.stringify({ + message: "Fallback response", + requestId: Math.random().toString(36).substr(2, 9), + timestamp: Date.now(), + }), + { + status: 200, + headers: new Headers({ "content-type": "application/json" }), + }, + ) + }) + + const requests = Array.from( + { length: 5 }, + (_, i) => new Request(`https://example.com/test${i}`), + ) + + const responses = await Promise.all( + requests.map((req) => + proxy.proxy(req, `/api/data${req.url.slice(-1)}`, { + onError: onErrorHook, + }), + ), + ) + + expect(onErrorHook).toHaveBeenCalledTimes(5) + + for (const response of responses) { + expect(response.status).toBe(200) + const body = (await response.json()) as { + message: string + requestId: string + } + expect(body.message).toBe("Fallback response") + expect(body.requestId).toBeDefined() + } + }) + + it("should handle onError hook that throws an error", async () => { + fetchSpy.mockRejectedValue(new Error("Network error")) + + const onErrorHook = jest.fn().mockImplementation(async () => { + throw new Error("Hook error") + }) + + const request = new Request("https://example.com/test") + + try { + await proxy.proxy(request, "/api/data", { + onError: onErrorHook, + }) + // If we reach here, the test should fail + expect(true).toBe(false) + } catch (error) { + expect(onErrorHook).toHaveBeenCalled() + expect((error as Error).message).toBe("Hook error") + } + }) + + it("should handle fallback response with custom headers", async () => { + fetchSpy.mockRejectedValue(new Error("Service error")) + + const onErrorHook = jest.fn().mockResolvedValue( + new Response(JSON.stringify({ fallback: true }), { + status: 200, + headers: new Headers({ + "content-type": "application/json", + "x-fallback": "true", + "x-timestamp": Date.now().toString(), + "cache-control": "no-cache", + }), + }), + ) + + const request = new Request("https://example.com/test") + const response = await proxy.proxy(request, "/api/data", { + onError: onErrorHook, + }) + + expect(response.status).toBe(200) + expect(response.headers.get("x-fallback")).toBe("true") + expect(response.headers.get("x-timestamp")).toBeTruthy() + expect(response.headers.get("cache-control")).toBe("no-cache") + }) + + it("should handle streaming fallback response", async () => { + fetchSpy.mockRejectedValue(new Error("Streaming error")) + + const onErrorHook = jest.fn().mockImplementation(async (req, error) => { + const stream = new ReadableStream({ + start(controller) { + const data = JSON.stringify({ + message: "Fallback stream", + chunks: ["chunk1", "chunk2", "chunk3"], + }) + controller.enqueue(new TextEncoder().encode(data)) + controller.close() + }, + }) + + return new Response(stream, { + status: 200, + headers: new Headers({ "content-type": "application/json" }), + }) + }) + + const request = new Request("https://example.com/test") + const response = await proxy.proxy(request, "/api/data", { + onError: onErrorHook, + }) + + expect(response.status).toBe(200) + + const body = (await response.json()) as { + message: string + chunks: string[] + } + expect(body.message).toBe("Fallback stream") + expect(body.chunks).toEqual(["chunk1", "chunk2", "chunk3"]) + }) + }) + + describe("Integration with Other Features", () => { + it("should work with beforeRequest and afterResponse hooks", async () => { + fetchSpy.mockRejectedValue(new Error("Network error")) + + const beforeRequestHook = jest.fn() + const afterResponseHook = jest.fn() + const onErrorHook = jest + .fn() + .mockResolvedValue(new Response("Fallback", { status: 200 })) + + const request = new Request("https://example.com/test") + const response = await proxy.proxy(request, "/api/data", { + beforeRequest: beforeRequestHook, + afterResponse: afterResponseHook, + onError: onErrorHook, + }) + + expect(beforeRequestHook).toHaveBeenCalled() + expect(onErrorHook).toHaveBeenCalled() + // afterResponse hook should NOT be called for error responses + expect(afterResponseHook).not.toHaveBeenCalled() + expect(response.status).toBe(200) + }) + + it("should work with custom headers and query parameters", async () => { + fetchSpy.mockRejectedValue(new Error("Network error")) + + const onErrorHook = jest + .fn() + .mockResolvedValue(new Response("Fallback", { status: 200 })) + + const request = new Request("https://example.com/test") + await proxy.proxy(request, "/api/data", { + headers: { "X-Custom": "test" }, + queryString: { param: "value" }, + onError: onErrorHook, + }) + + expect(onErrorHook).toHaveBeenCalledWith( + expect.any(Request), + expect.any(Error), + ) + + // Check the actual URL passed to the hook (original request URL, not target URL) + const actualRequest = onErrorHook.mock.calls[0][0] + expect(actualRequest.url).toBe("https://example.com/test") + }) + }) +}) diff --git a/tests/query-injection.test.ts b/tests/query-injection.test.ts index 9602e6f..8dc36cb 100644 --- a/tests/query-injection.test.ts +++ b/tests/query-injection.test.ts @@ -1,11 +1,7 @@ -import { describe, it, expect, mock, afterAll } from "bun:test" +import { describe, it, expect } from "bun:test" import { buildQueryString } from "../src/utils" import { FetchProxy } from "../src/proxy" -afterAll(() => { - mock.restore() -}) - describe("Query String Injection Security Tests", () => { describe("Parameter Name Validation", () => { it("should handle parameter names with special characters safely", () => { diff --git a/tests/security.test.ts b/tests/security.test.ts index 53d3036..322ef95 100644 --- a/tests/security.test.ts +++ b/tests/security.test.ts @@ -1,10 +1,6 @@ -import { describe, it, expect, afterAll, mock } from "bun:test" +import { describe, it, expect } from "bun:test" import { buildURL } from "../src/utils" -afterAll(() => { - mock.restore() -}) - describe("Security Tests", () => { describe("SSRF Prevention", () => { it("should prevent file:// protocol access", () => { diff --git a/tests/utils.test.ts b/tests/utils.test.ts index b9d2e65..b943d84 100644 --- a/tests/utils.test.ts +++ b/tests/utils.test.ts @@ -1,4 +1,4 @@ -import { describe, it, expect, mock, beforeEach } from "bun:test" +import { describe, it, expect, beforeEach } from "bun:test" import { buildURL, filterHeaders,