From 70dc4c7a467e6d1d226e8e09fc419f3c3e1d0488 Mon Sep 17 00:00:00 2001 From: fraxken Date: Wed, 7 May 2025 14:40:26 +0200 Subject: [PATCH 01/12] chore: update eslint & remove MyUnisoft for OpenAlly --- .all-contributorsrc | 2 +- README.md | 36 +++++++++++++++--------------- SECURITY.md | 2 +- docs/agents.md | 2 +- docs/errors.md | 6 ++--- docs/request.md | 2 +- docs/retry.md | 4 ++-- examples/agent.mjs | 2 +- examples/get.mjs | 2 +- examples/mode.ts | 2 +- examples/pipeline.mjs | 2 +- examples/ratelimit.mjs | 2 +- examples/retry.mjs | 2 +- examples/stream.mjs | 4 ++-- examples/throwOnHttpieError.mjs | 2 +- package.json | 20 ++++++++--------- src/agents.ts | 16 ++++++++----- src/class/HttpieCommonError.ts | 2 +- src/class/HttpieHandlerError.ts | 10 ++++++--- src/class/HttpieOnHttpError.ts | 4 ++-- src/class/Operation.class.ts | 2 +- src/class/undiciResponseHandler.ts | 8 +++++-- src/index.ts | 22 +++++++++--------- src/policies/index.ts | 4 ++-- src/request.ts | 21 ++++++++++++----- src/retry.ts | 4 ++-- src/stream.ts | 10 ++++++--- src/utils.ts | 8 +++---- test/HttpieOnHttpError.spec.ts | 2 +- test/request2.spec.ts | 10 +++++---- test/safeRequest.spec.ts | 11 +++++---- test/server/index.ts | 18 +++++++-------- test/undiciResponseHandler.spec.ts | 2 +- tsconfig.json | 2 +- 34 files changed, 138 insertions(+), 110 deletions(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index a1af349..691f526 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -41,7 +41,7 @@ ], "contributorsPerLine": 7, "projectName": "httpie", - "projectOwner": "MyUnisoft", + "projectOwner": "OpenAlly", "repoType": "github", "repoHost": "https://github.com", "skipCi": true, diff --git a/README.md b/README.md index 28e421b..be8b244 100644 --- a/README.md +++ b/README.md @@ -8,20 +8,20 @@

- - npm version + + npm version - - license + + license - - ossf scorecard + + ossf scorecard - - github ci workflow + + github ci workflow - - size + + size

@@ -58,17 +58,17 @@ Light with seriously maintained dependencies: This package is available in the Node Package Repository and can be easily installed with [npm](https://docs.npmjs.com/getting-started/what-is-npm) or [yarn](https://yarnpkg.com). ```bash -$ npm i @myunisoft/httpie +$ npm i @openally/httpie # or -$ yarn add @myunisoft/httpie +$ yarn add @openally/httpie ``` ## 📚 Usage example -The MyUnisoft httpie client is very similar to lukeed httpie http client. +This client is very similar to lukeed httpie http client. ```js -import * as httpie from "@myunisoft/httpie"; +import * as httpie from "@openally/httpie"; try { const { data } = await httpie.get("https://jsonplaceholder.typicode.com/posts"); @@ -97,7 +97,7 @@ catch (error) { Since v2.0.0 you can also use the `safe` prefix API to get a `Promise>` ```ts -import * as httpie from "@myunisoft/httpie"; +import * as httpie from "@openally/httpie"; const response = (await httpie.safePost("https://jsonplaceholder.typicode.com/posts", { body: { @@ -138,9 +138,9 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d - - - + + +
Gentilhomme
Gentilhomme

đŸ’ģ 📖 👀 đŸ›Ąī¸ 🐛
PierreDemailly
PierreDemailly

đŸ’ģ âš ī¸
Yefis
Yefis

đŸ’ģ 🐛
Gentilhomme
Gentilhomme

đŸ’ģ 📖 👀 đŸ›Ąī¸ 🐛
PierreDemailly
PierreDemailly

đŸ’ģ âš ī¸
Yefis
Yefis

đŸ’ģ 🐛
diff --git a/SECURITY.md b/SECURITY.md index 2f34da4..91aac10 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,4 +1,4 @@ # Reporting Security Issues -To report a security issue, please email `node@myunisoft.fr` with a description of the issue, the steps you took to create the issue, affected versions, and, if known, mitigations for the issue. +To report a security issue, please [publish a private security advisory](https://github.com/OpenAlly/httpie/security/advisories) with a description of the issue, the steps you took to create the issue, affected versions, and, if known, mitigations for the issue. Our vulnerability management team will respond within one week to your email. If the issue is confirmed as a vulnerability, we will open a Security Advisory and acknowledge your contributions as part of it. This project follows a 90 day disclosure timeline. diff --git a/docs/agents.md b/docs/agents.md index b9bbf4a..2df62d8 100644 --- a/docs/agents.md +++ b/docs/agents.md @@ -3,7 +3,7 @@ Agents are custom constructs that are used to describe internal and external services. ```js -import { agents } from "@myunisoft/httpie"; +import { agents } from "@openally/httpie"; console.log(agents); // <- push a new agent in this Array ``` diff --git a/docs/errors.md b/docs/errors.md index 1e2ebb6..a13a81e 100644 --- a/docs/errors.md +++ b/docs/errors.md @@ -13,14 +13,14 @@ interface HttpieError { ### isHttpieError -The `isHttpieError` function can be used to find out weither the error is a `@myunisoft/httpie` or a `undici` error. +The `isHttpieError` function can be used to find out weither the error is a `@openally/httpie` or a `undici` error. ```ts function isHttpieError(error: unknown): boolean; ``` Example: ```ts -import * as httpie from "@myunisoft/httpie"; +import * as httpie from "@openally/httpie"; try { await httpie.request("GET", "127.0.0.1"); @@ -48,7 +48,7 @@ function isHTTPError(error: unknown): boolean; Example: ```ts -import * as httpie from "@myunisoft/httpie"; +import * as httpie from "@openally/httpie"; try { await httpie.request("GET", "127.0.0.1"); diff --git a/docs/request.md b/docs/request.md index 9eb30cd..ccf3428 100644 --- a/docs/request.md +++ b/docs/request.md @@ -37,7 +37,7 @@ The first **method** argument take an [HTTP Verb](https://developer.mozilla.org/ The options allow you to quickly authenticate and add additional headers: ```js -import { request } from "@myunisoft/httpie"; +import { request } from "@openally/httpie"; const { data } = await request("GET", "https://test.domain.fr/user/info", { authorization: "Token here", diff --git a/docs/retry.md b/docs/retry.md index ce13f4b..6d2c2f2 100644 --- a/docs/retry.md +++ b/docs/retry.md @@ -7,7 +7,7 @@ Allows to restart http calls according to various criteria that we will call pol The httpcode by default retry on codes: `307`, `408`, `429`, `444`, `500`, `503`, `504`, `520`, `521`, `522`, `523`, `524`. However you can also choose to extend the list yourself: ```js -import * as httpie from "@myunisoft/httpie"; +import * as httpie from "@openally/httpie"; const policy = httpie.policies.httpcode(new Set([501]), true); ``` @@ -15,7 +15,7 @@ const policy = httpie.policies.httpcode(new Set([501]), true); ## Usage example ```js -import * as httpie from "@myunisoft/httpie"; +import * as httpie from "@openally/httpie"; const { data } = httpie.retry(async() => { return await httpie.get("https://jsonplaceholder.typicode.com/posts"); diff --git a/examples/agent.mjs b/examples/agent.mjs index e62a9b8..b88982e 100644 --- a/examples/agent.mjs +++ b/examples/agent.mjs @@ -1,5 +1,5 @@ import * as httpie from "../dist/index.js"; -// import * as httpie from "@myunisoft/httpie"; +// import * as httpie from "@openally/httpie"; const yoda = { customPath: "yoda", diff --git a/examples/get.mjs b/examples/get.mjs index bf92d4a..b7379eb 100644 --- a/examples/get.mjs +++ b/examples/get.mjs @@ -1,5 +1,5 @@ import * as httpie from "../dist/index.js"; -// import * as httpie from "@myunisoft/httpie"; +// import * as httpie from "@openally/httpie"; const { data } = await httpie.get("https://jsonplaceholder.typicode.com/posts"); console.log(data); diff --git a/examples/mode.ts b/examples/mode.ts index b16bda5..76a6363 100644 --- a/examples/mode.ts +++ b/examples/mode.ts @@ -1,5 +1,5 @@ import * as httpie from "../dist/index.js"; -// import * as httpie from "@myunisoft/httpie"; +// import * as httpie from "@openally/httpie"; { const { data } = await httpie.request("GET", "127.0.0.1", { mode: "raw" }); diff --git a/examples/pipeline.mjs b/examples/pipeline.mjs index b3bf758..c77554e 100644 --- a/examples/pipeline.mjs +++ b/examples/pipeline.mjs @@ -6,7 +6,7 @@ import { fileURLToPath } from "url"; // Third-party Dependencies import * as httpie from "../dist/index.js"; -// import * as httpie from "@myunisoft/httpie"; +// import * as httpie from "@openally/httpie"; // CONSTANTS const __dirname = path.dirname(fileURLToPath(import.meta.url)); diff --git a/examples/ratelimit.mjs b/examples/ratelimit.mjs index 69dcc63..768dc86 100644 --- a/examples/ratelimit.mjs +++ b/examples/ratelimit.mjs @@ -1,7 +1,7 @@ // Import Third-party Dependencies import { pRateLimit } from "p-ratelimit"; import * as httpie from "../dist/index.js"; -// import * as httpie from "@myunisoft/httpie"; +// import * as httpie from "@openally/httpie"; // Note: limit can also be provided to an Agent! const limit = pRateLimit({ diff --git a/examples/retry.mjs b/examples/retry.mjs index cb2d6f1..0127eac 100644 --- a/examples/retry.mjs +++ b/examples/retry.mjs @@ -1,6 +1,6 @@ // Import Third-party Dependencies import * as httpie from "../dist/index.js"; -// import * as httpie from "@myunisoft/httpie"; +// import * as httpie from "@openally/httpie"; const { data } = await httpie.retry(async() => { return await httpie.get("https://jsonplaceholder.typicode.com/posts"); diff --git a/examples/stream.mjs b/examples/stream.mjs index 6c727f6..7e226db 100644 --- a/examples/stream.mjs +++ b/examples/stream.mjs @@ -5,7 +5,7 @@ import { fileURLToPath } from "url"; // Third-party Dependencies import * as httpie from "../dist/index.js"; -// import * as httpie from "@myunisoft/httpie"; +// import * as httpie from "@openally/httpie"; // CONSTANTS const __dirname = path.dirname(fileURLToPath(import.meta.url)); @@ -26,4 +26,4 @@ await cursor(({ statusCode, headers }) => { return writable; }); -console.log(code, contentType); \ No newline at end of file +console.log(code, contentType); diff --git a/examples/throwOnHttpieError.mjs b/examples/throwOnHttpieError.mjs index 4c0cd4e..69277ba 100644 --- a/examples/throwOnHttpieError.mjs +++ b/examples/throwOnHttpieError.mjs @@ -1,5 +1,5 @@ import * as httpie from "../dist/index.js"; -// import * as httpie from "@myunisoft/httpie"; +// import * as httpie from "@openally/httpie"; // Should not throw { diff --git a/package.json b/package.json index 39bc071..51dfc75 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,8 @@ { - "name": "@myunisoft/httpie", - "version": "5.0.1", - "description": "MyUnisoft Node.js HTTP client that use Undici client", + "name": "@openally/httpie", + "version": "1.0.0", + "description": "Node.js HTTP client that use Undici client", + "type": "module", "main": "dist/index.js", "types": "dist/index.d.ts", "scripts": { @@ -12,26 +13,23 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/MyUnisoft/httpie.git" + "url": "git+https://github.com/OpenAlly/httpie.git" }, "engines": { - "node": ">=20" + "node": ">=22" }, "keywords": [], "files": [ "dist" ], - "publishConfig": { - "@myunisoft:registry": "https://registry.npmjs.org/" - }, "author": "GENTILHOMME Thomas ", "license": "MIT", "bugs": { - "url": "https://github.com/MyUnisoft/httpie/issues" + "url": "https://github.com/OpenAlly/httpie/issues" }, - "homepage": "https://github.com/MyUnisoft/httpie#readme", + "homepage": "https://github.com/OpenAlly/httpie#readme", "devDependencies": { - "@openally/config.eslint": "^1.1.0", + "@openally/config.eslint": "^2.1.0", "@openally/config.typescript": "^1.0.3", "@types/content-type": "^1.1.8", "@types/jest": "^29.5.11", diff --git a/src/agents.ts b/src/agents.ts index 2b7284f..1e7f40d 100644 --- a/src/agents.ts +++ b/src/agents.ts @@ -3,8 +3,12 @@ import { Agent, ProxyAgent, MockAgent } from "undici"; import { LRUCache } from "lru-cache"; // Import Internal Dependencies -import { InlineCallbackAction, HttpMethod, WebDavMethod } from "./request"; -import { getCurrentEnv } from "./utils"; +import { + type InlineCallbackAction, + type HttpMethod, + type WebDavMethod +} from "./request.js"; +import { getCurrentEnv } from "./utils.js"; // CONSTANTS const kEnvName = getCurrentEnv(); @@ -12,7 +16,7 @@ const kEnvName = getCurrentEnv(); /** * @see https://en.wikipedia.org/wiki/Page_replacement_algorithm */ -export const URICache = new LRUCache({ +export const URI_CACHE = new LRUCache({ max: 100, ttl: 1_000 * 60 * 120 }); @@ -102,8 +106,8 @@ export function computeURI( uri: string | URL ): computedUrlAndAgent { const uriStr = method.toUpperCase() + uri.toString(); - if (URICache.has(uriStr)) { - return URICache.get(uriStr)!; + if (URI_CACHE.has(uriStr)) { + return URI_CACHE.get(uriStr)!; } let response: computedUrlAndAgent; @@ -115,7 +119,7 @@ export function computeURI( response = { url: uri, agent: agent?.agent ?? null, limit: agent?.limit }; } - URICache.set(uriStr, response); + URI_CACHE.set(uriStr, response); return response; } diff --git a/src/class/HttpieCommonError.ts b/src/class/HttpieCommonError.ts index d80fbf4..d2119d6 100644 --- a/src/class/HttpieCommonError.ts +++ b/src/class/HttpieCommonError.ts @@ -1,5 +1,5 @@ // Import Third-party Dependencies -import { IncomingHttpHeaders } from "undici/types/header"; +import { type IncomingHttpHeaders } from "undici/types/header.js"; type CommonResponseData = { statusCode: number; diff --git a/src/class/HttpieHandlerError.ts b/src/class/HttpieHandlerError.ts index ed1253a..30ac336 100644 --- a/src/class/HttpieHandlerError.ts +++ b/src/class/HttpieHandlerError.ts @@ -1,8 +1,12 @@ /* eslint-disable max-classes-per-file */ -// Import Third-party Dependencies -import { HttpieError, HttpieErrorOptions } from "./HttpieCommonError"; -import { getDecompressionError, getFetchError, getParserError } from "../common/errors"; +// Import Internal Dependencies +import { HttpieError, type HttpieErrorOptions } from "./HttpieCommonError.js"; +import { + getDecompressionError, + getFetchError, + getParserError +} from "../common/errors.js"; type MessageOfGetDecompressionError = Parameters[0]["message"]; type MessageOfGetParserError = Parameters[0]["message"]; diff --git a/src/class/HttpieOnHttpError.ts b/src/class/HttpieOnHttpError.ts index 3eb4855..8242fcb 100644 --- a/src/class/HttpieOnHttpError.ts +++ b/src/class/HttpieOnHttpError.ts @@ -1,6 +1,6 @@ // Import Internal Dependencies -import { HttpieError } from "./HttpieCommonError"; -import { RequestResponse } from "../request"; +import { HttpieError } from "./HttpieCommonError.js"; +import { type RequestResponse } from "../request.js"; /** * @description Class to generate an Error with all the required properties from the response. diff --git a/src/class/Operation.class.ts b/src/class/Operation.class.ts index c4ddb41..087fd0d 100644 --- a/src/class/Operation.class.ts +++ b/src/class/Operation.class.ts @@ -2,7 +2,7 @@ import timers from "node:timers/promises"; // Import Internal Dependencies -import { RetryOptions } from "../retry"; +import { type RetryOptions } from "../retry.js"; // CONSTANTS const kDefaultOperationOptions: Partial = { diff --git a/src/class/undiciResponseHandler.ts b/src/class/undiciResponseHandler.ts index f07c5a9..e8ade2e 100644 --- a/src/class/undiciResponseHandler.ts +++ b/src/class/undiciResponseHandler.ts @@ -8,8 +8,12 @@ import { Dispatcher } from "undici"; import * as contentType from "content-type"; // Import Internal Dependencies -import { getEncodingCharset } from "../utils"; -import { HttpieDecompressionError, HttpieFetchBodyError, HttpieParserError } from "./HttpieHandlerError"; +import { getEncodingCharset } from "../utils.js"; +import { + HttpieDecompressionError, + HttpieFetchBodyError, + HttpieParserError +} from "./HttpieHandlerError.js"; const kAsyncGunzip = promisify(gunzip); const kDecompress = { diff --git a/src/index.ts b/src/index.ts index 646dbef..18265ca 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,26 +6,26 @@ import { setGlobalDispatcher, getGlobalDispatcher, Headers, - HeadersInit, + type HeadersInit, FormData, File, - BodyInit, + type BodyInit, BodyMixin, MockAgent, mockErrors, MockPool, - Interceptable, + type Interceptable, Client } from "undici"; -export * from "./request"; -export * from "./stream"; -export * from "./retry"; -export * as policies from "./policies"; -export { agents, computeURI, type CustomHttpAgent } from "./agents"; -export { DEFAULT_HEADER, isHTTPError, isHttpieError } from "./utils"; -export { HttpieOnHttpError } from "./class/HttpieOnHttpError"; -export * from "./class/undiciResponseHandler"; +export * from "./request.js"; +export * from "./stream.js"; +export * from "./retry.js"; +export * as policies from "./policies/index.js"; +export { agents, computeURI, type CustomHttpAgent } from "./agents.js"; +export { DEFAULT_HEADER, isHTTPError, isHttpieError } from "./utils.js"; +export { HttpieOnHttpError } from "./class/HttpieOnHttpError.js"; +export * from "./class/undiciResponseHandler.js"; export { Agent, diff --git a/src/policies/index.ts b/src/policies/index.ts index 1d7bb38..9f4968c 100644 --- a/src/policies/index.ts +++ b/src/policies/index.ts @@ -1,4 +1,4 @@ -export * from "./none"; -export * from "./httpcode"; +export * from "./none.js"; +export * from "./httpcode.js"; export type PolicyCallback = (error?: any) => boolean; diff --git a/src/request.ts b/src/request.ts index b6843f9..ed19da6 100644 --- a/src/request.ts +++ b/src/request.ts @@ -1,5 +1,5 @@ // Import Node.js Dependencies -import { IncomingHttpHeaders } from "node:http"; +import { type IncomingHttpHeaders } from "node:http"; import { URLSearchParams } from "node:url"; // Import Third-party Dependencies @@ -8,11 +8,20 @@ import { Result } from "@openally/result"; import status from "statuses"; // Import Internal Dependencies -import * as Utils from "./utils"; -import { computeURI } from "./agents"; -import { HttpieResponseHandler, ModeOfHttpieResponseHandler } from "./class/undiciResponseHandler"; -import { HttpieOnHttpError } from "./class/HttpieOnHttpError"; -import { HttpieDecompressionError, HttpieFetchBodyError, HttpieParserError } from "./class/HttpieHandlerError"; +import * as Utils from "./utils.js"; +import { computeURI } from "./agents.js"; +import { + HttpieResponseHandler, + type ModeOfHttpieResponseHandler +} from "./class/undiciResponseHandler.js"; +import { + HttpieOnHttpError +} from "./class/HttpieOnHttpError.js"; +import { + HttpieDecompressionError, + HttpieFetchBodyError, + HttpieParserError +} from "./class/HttpieHandlerError.js"; export type WebDavMethod = "MKCOL" | "COPY" | "MOVE" | "LOCK" | "UNLOCK" | "PROPFIND" | "PROPPATCH"; export type HttpMethod = "GET" | "HEAD" | "POST" | "PUT" | "DELETE" | "CONNECT" | "OPTIONS" | "TRACE" | "PATCH"; diff --git a/src/retry.ts b/src/retry.ts index 3569104..e2658a1 100644 --- a/src/retry.ts +++ b/src/retry.ts @@ -1,6 +1,6 @@ // Import Internal Dependencies -import Operation, { OperationResult } from "./class/Operation.class"; -import { PolicyCallback, none } from "./policies"; +import Operation, { type OperationResult } from "./class/Operation.class.js"; +import { type PolicyCallback, none } from "./policies/index.js"; /** * Those options are inspired by the retry package diff --git a/src/stream.ts b/src/stream.ts index 451130d..fb493b8 100644 --- a/src/stream.ts +++ b/src/stream.ts @@ -5,9 +5,13 @@ import { Duplex } from "node:stream"; import * as undici from "undici"; // Import Internal Dependencies -import { RequestOptions, HttpMethod, WebDavMethod } from "./request"; -import { computeURI } from "./agents"; -import * as Utils from "./utils"; +import { + type RequestOptions, + type HttpMethod, + type WebDavMethod +} from "./request.js"; +import { computeURI } from "./agents.js"; +import * as Utils from "./utils.js"; export type StreamOptions = Omit; diff --git a/src/utils.ts b/src/utils.ts index 56b0e45..95a3bd1 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,11 +1,11 @@ // Import Node.js Dependencies -import { IncomingHttpHeaders } from "node:http"; +import { type IncomingHttpHeaders } from "node:http"; // Import Internal Dependencies -import { RequestOptions, RequestResponse } from "./request"; -import { HttpieError } from "./class/HttpieCommonError"; -import { HttpieOnHttpError } from "./class/HttpieOnHttpError"; +import { type RequestOptions, type RequestResponse } from "./request.js"; +import { HttpieError } from "./class/HttpieCommonError.js"; +import { HttpieOnHttpError } from "./class/HttpieOnHttpError.js"; // CONSTANTS const kDefaultUserAgent = "httpie"; diff --git a/test/HttpieOnHttpError.spec.ts b/test/HttpieOnHttpError.spec.ts index 93f5626..59f1cb7 100644 --- a/test/HttpieOnHttpError.spec.ts +++ b/test/HttpieOnHttpError.spec.ts @@ -1,4 +1,4 @@ -// Import Third-Party Dependencies +// Import Third-party Dependencies import { MockAgent, setGlobalDispatcher } from "undici"; // Import Internal Dependencies diff --git a/test/request2.spec.ts b/test/request2.spec.ts index e0b7766..fb0f391 100644 --- a/test/request2.spec.ts +++ b/test/request2.spec.ts @@ -1,13 +1,15 @@ /* eslint-disable max-lines */ -// Import Third-Party Dependencies +// Import Node.js Dependencies +import { brotliCompress, deflate, gzip } from "node:zlib"; +import { promisify } from "node:util"; +import { randomInt } from "node:crypto"; + +// Import Third-party Dependencies import { Interceptable, MockAgent, setGlobalDispatcher } from "undici"; // Import Internal Dependencies import { request } from "../src/request"; -import { brotliCompress, deflate, gzip } from "zlib"; -import { promisify } from "util"; import { isHTTPError, isHttpieError } from "../src"; -import { randomInt } from "crypto"; // CONSTANTS const kUrl = "http://test.com"; diff --git a/test/safeRequest.spec.ts b/test/safeRequest.spec.ts index a8d6a80..4fff367 100644 --- a/test/safeRequest.spec.ts +++ b/test/safeRequest.spec.ts @@ -1,13 +1,16 @@ /* eslint-disable max-lines */ -// Import Third-Party Dependencies + +// Import Node.js Dependencies +import { brotliCompress, deflate, gzip } from "node:zlib"; +import { randomInt } from "node:crypto"; +import { promisify } from "node:util"; + +// Import Third-party Dependencies import { Interceptable, MockAgent, setGlobalDispatcher } from "undici"; // Import Internal Dependencies import { safeDel, safeGet, safePost, safePut } from "../src/request"; -import { brotliCompress, deflate, gzip } from "zlib"; -import { promisify } from "util"; import { isHTTPError, isHttpieError } from "../src"; -import { randomInt } from "crypto"; // CONSTANTS const kUrl = "http://test.com"; diff --git a/test/server/index.ts b/test/server/index.ts index d820aeb..26cd529 100644 --- a/test/server/index.ts +++ b/test/server/index.ts @@ -14,7 +14,7 @@ import { CustomHttpAgent, agents } from "../../src/agents"; const kFixturesPath = path.join(__dirname, "..", "fixtures"); const toUpperCase = new Transform({ - transform(chunk, enc, next) { + transform(chunk, _enc, next) { for (let id = 0; id < chunk.length; id++) { const char = chunk[id]; chunk[id] = char < 97 || char > 122 ? char : char - 32; @@ -49,7 +49,7 @@ export async function createServer(customPath = "local", port = 3000) { server.get("/qs", async(request) => request.query); - server.get("/home", (request, reply) => { + server.get("/home", (_request, reply) => { reply.send( fs.createReadStream(path.join(kFixturesPath, "home.html")) ); @@ -61,36 +61,36 @@ export async function createServer(customPath = "local", port = 3000) { ); }); - server.get("/redirect", (request, reply) => { + server.get("/redirect", (_request, reply) => { reply.redirect("/"); }); - server.get("/jsonError", (request, reply) => { + server.get("/jsonError", (_request, reply) => { reply.type("application/json"); reply.send("{ 'foo': bar }"); }); - server.get("/notimplemented", (request, reply) => { + server.get("/notimplemented", (_request, reply) => { reply.code(501); reply.send(); }); - server.get("/internalerror", (request, reply) => { + server.get("/internalerror", (_request, reply) => { reply.code(500); reply.send(); }); - server.get("/badEncoding", (request, reply) => { + server.get("/badEncoding", (_request, reply) => { reply.header("content-encoding", "oui"); reply.send("{ 'foo': bar }"); }); - server.get("/pdf", (request, reply) => { + server.get("/pdf", (_request, reply) => { reply.header("content-type", "application/pdf"); reply.send("{ 'foo': bar }"); }); - server.get("/text", (request, reply) => { + server.get("/text", (_request, reply) => { reply.header("content-type", "text/anything"); reply.send("text"); }); diff --git a/test/undiciResponseHandler.spec.ts b/test/undiciResponseHandler.spec.ts index 651cdfe..ef2ef54 100644 --- a/test/undiciResponseHandler.spec.ts +++ b/test/undiciResponseHandler.spec.ts @@ -3,7 +3,7 @@ import { randomBytes } from "node:crypto"; // Import Third-party Dependencies -import { brotliCompressSync, deflateSync, gzipSync } from "zlib"; +import { brotliCompressSync, deflateSync, gzipSync } from "node:zlib"; // Import Internal Dependencies import { HttpieResponseHandler } from "../src/class/undiciResponseHandler"; diff --git a/tsconfig.json b/tsconfig.json index 6b26bef..d275979 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "@openally/config.typescript/cjs", + "extends": "@openally/config.typescript/esm", "compilerOptions": { "outDir": "dist", "rootDir": "./src", From e225f9904932f7d07fb73c4a81b13fb340c6d2d7 Mon Sep 17 00:00:00 2001 From: fraxken Date: Sun, 1 Jun 2025 13:41:06 +0200 Subject: [PATCH 02/12] refactor: migrate to node:test --- docs/agents.md | 12 +- docs/images/nodesecure.PNG | Bin 101251 -> 198862 bytes examples/agent.mjs | 9 +- jest.config.js | 16 - nsecure-result.json | 1228 ++++++++++ package.json | 12 +- src/agents.ts | 18 +- src/class/HttpieCommonError.ts | 4 +- src/index.ts | 4 - src/request.ts | 2 +- src/utils.ts | 36 +- ...rror.spec.ts => HttpieOnHttpError.test.ts} | 16 +- test/__snapshots__/request.spec.ts.snap | 23 - test/{agents.spec.ts => agents.test.ts} | 61 +- test/helpers.ts | 9 +- test/jest.setup.js | 1 - test/{request.spec.ts => request.test.ts} | 100 +- test/request2.spec.ts | 1993 --------------- test/request2.test.ts | 489 ++++ test/{retry.spec.ts => retry.test.ts} | 52 +- test/safeRequest.spec.ts | 2155 ----------------- test/server/index.ts | 8 +- test/{stream.spec.ts => stream.test.ts} | 31 +- ....spec.ts => undiciResponseHandler.test.ts} | 92 +- test/utils.spec.ts | 212 -- test/utils.test.ts | 199 ++ tsconfig.json | 2 +- 27 files changed, 2144 insertions(+), 4640 deletions(-) delete mode 100644 jest.config.js create mode 100644 nsecure-result.json rename test/{HttpieOnHttpError.spec.ts => HttpieOnHttpError.test.ts} (73%) delete mode 100644 test/__snapshots__/request.spec.ts.snap rename test/{agents.spec.ts => agents.test.ts} (59%) delete mode 100644 test/jest.setup.js rename test/{request.spec.ts => request.test.ts} (64%) delete mode 100644 test/request2.spec.ts create mode 100644 test/request2.test.ts rename test/{retry.spec.ts => retry.test.ts} (63%) delete mode 100644 test/safeRequest.spec.ts rename test/{stream.spec.ts => stream.test.ts} (77%) rename test/{undiciResponseHandler.spec.ts => undiciResponseHandler.test.ts} (79%) delete mode 100644 test/utils.spec.ts create mode 100644 test/utils.test.ts diff --git a/docs/agents.md b/docs/agents.md index 2df62d8..f428e8c 100644 --- a/docs/agents.md +++ b/docs/agents.md @@ -12,11 +12,8 @@ Those custom `agents` are described by the following TypeScript interface: ```ts export interface CustomHttpAgent { customPath: string; - domains: Set; + origin: string; agent: Agent; - prod: string; - preprod: string; - dev: string; } ``` @@ -24,15 +21,10 @@ Example with a test custom agent: ```ts export const test: CustomHttpAgent = { customPath: "test", - domains: new Set([ - "test.domain.fr", - ]), agent: new Agent({ connections: 30 }), - prod: "", - preprod: "", - dev: "https://test.domain.fr" + origin: "https://test.domain.fr" }; // Note: push it to the package agents list diff --git a/docs/images/nodesecure.PNG b/docs/images/nodesecure.PNG index 5f3dab5edd58b38b568fc0f5d5fda5fb89130f97..9e315769f1fc271972d127f6bb653e732b6f0f62 100644 GIT binary patch literal 198862 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 Lu6{1-oD!MId+ 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

vzALMzJZCCFh#oNi5`;#&v74h#@9yyI=YYvJ)8 zi4-N5^qwD8yJg2(WxA6+N|zhLyu2&v+cj+ue^S_obSv^18EgXgZ()w(1uY!Cx7fs;{LVY^<%s- zO84ZOQo-7jKd9`;Mos}&nRY}#`mML1GTIZP_tpy|P6p8ez|S9}Vk1wmb?nBmlQOE9 zbn!U_4Q`p(JJ!GZ8#3PM(}YjXu;(fBXL$Ho>C^P^P0MO{XXSkj4Y=Dk(Tv^rD&hTc zuKW6xZ%^z|b>aG4QgNae7j}{F>`LUOOIGPTq(C3J??VhlzfVH|z++CFwA|cdlBZ~w z!xv9qh%Tpq8brOU#439s&&Lr~>8rsfuIt215q4?jmz@^AUJw%6>bPo3>eryK{dxfO=Go>lNk>})|nAyO#|x}Ikkb*EO4@Hx`; zthJDa9YiV%JG2WEyw>CvJK4nsCDrtfnd>y6oluq`0kLT>gL?jyCf78tE2Z@q03w9L zjQ}~XKz(knF0@*>jEL!vo0c8)+z4X?GcUE)WKufY zp=JGJEMiOgcV}R)kKHO+Qh09W)EcmrSx);x>mzBp z-suPTh3yzwhu$>3CmahMXRO7|Z4Lk^Q)V5U;wEF8hTn8^^PSaJpDh}U#%B(W76VH5 z_GAwXlmse-J9(%V?zb3e3Z&X|ZmcDG+8g&I zmX(3O6*@%+gA1zwp*bc_zNM(V<7WImd%*LWdO7bZO2j6&K4PV`*Rnd$3r)p|7^8crJd-ZSOCg-cgA0$n?E&Z@I7im=`t_zU^z!#B z9BB}xO=4Nof%fd!-I{KK3T3eGZ-vVa6g6I;x(tL=TUK8nXZbVjmVJ1PC$o3|i@ip*wQ*;K zYc}r87BxZc{j_pVn&B}>A9(ZY3cGsiIpQccStEl-IF8loS1~7wo`(E({G737?Vp7o zn?nWf-;TY`?bM&EOR@yvkzIwtM+92oCE~^dY2iFfdGe=UFD+96PYi}r!{h#T=ZDxm zEysQWZjMUqW7vGAoNHzwS%o+-cw*u01P5`i*Up!1R#Cb2*FjAVzRTc-sB?EZeQkcy zNVw|6vxOJd_~zhNzGm^%gAu_aEz+W>Z zE*bJ{o!P`iCF)(i$#SP_xRhVQi}J`>Gza8Y>F=>~fm-Lns)3Wy6DKFYeEjnPAW%RH z-)^jQy6wR@;nw;`s)wxomg3p7kLQ}zXt~Y7do)ne5p`9<6>J4I@w&*s3F`@n02rY@S45I%aFMe7$&1F#cvDl9%9+}r`{tAIDJ9GjbQbsmD0AGWu!SE zQqV8o!usIRZGlIJ{J{;it7}Y?EV_zVxfkUT5rmjW`#3J?%F#ORfCKxhLi|+)C6Ukh z3WEX2^N4All|bACqo&w3<;w(jGU+}I$aII#);s^tAo^m=i1M&2L^FylkOggyZPRjH z!8I_Ra6Nb|_)FrGEVx^kV^!3czh*{}!ElnA2#sn3 zYV3jB7=#+qv{Z)r7V8vDBS-)I zAA8)#2B;qo6Ua)$C&cn0=}hZ7az#dr27&wc($CK+Jw>*DES87Pw_rzEO#iD`spa{- zH)%`1k_^Kv$D8VFf_#m+PF{J_KTT~c^N$;sf}a4(0*ZV`9~FG;qhFTSUN5uxeoCXl^LOR` zg`l{VIH!KPy+3ZEC~(>-ZAJ5S$V*a7!3zCB%LaefGHoh^YcFiA;144qflnDQ0kO4A z1mvNXfVL31kq0W6DZbCY2yWAmkk5B}5K;~ZELd?FcijFZ1=2%y(bU)qG^4#|NiEAc>qc36**q?Z#i1!>Q`bLLq4 zoV;ntE{(eLPb=K*SDgOA*IMg8u$d6WiZj$`A+x~|A=yg)LGeinuV`Prn>A*|dDN!E z2xzwtGq$fc+e!=j9C*PDMFNU`iji2Vj$QqT?16h2yQxXVnZs!o)XVOh*6uFtqfg%V)zN1x9w(}~_>)RsmDm7BQ z6V6^lyNK66A1kXa=?opxtCE~~yQ#2u@!pDTeSYc6%)?R@^Ura1vX+I`kY&TA`@d_@ z26eMIIdQ;kV(}FH(d~{DV8Y2%?*2t3&mRAVv%s(PULvvq8@k(ojcYi!KL8am5N?fx zW~qE$e;0AfS0~D=QOtCAi?r=H7eST2kT({k(g}lyJ_7ybAY&ab0dvwoj~`px8xY zxEjPp;X>PMslwKk3jUY3qX%SW$V0!YK!vM9JsWI9wmtyMuxEH&z7lS`$m;TWz(OaP zJGP{~-v#yJ%a$(S`99;GyuCg`Ht6()MZ4H>5^FsDb8g2^X$$O<^Nq zSL7?6#EJ2JWdkm*EetNr8laKJ;!tf+;`nkVfpx-VHX=b@4xp>Q0R-_bt*-y(faIalf64ms!wbg z4MNQ1ZUNpl)B!eNw3hn`AI;pTT0u9Y6TL#q#`5Cm5pBS`JHi zpWgFeq>2r8_JW$C#4}vh<$da2(BIm4|xU}y%lH3bM|qf8xf2; zu?_nHwKDS=(Ha3awc3Qxd8{X__%6Sl1zmdOrJ+n90+wc51zanGiRz-Iw#2H=oz z*c*!Rw#KXht63XHPc7w9wHpDU=9OlcvAdr@Fux%ZmxD>a>qZ81k=7E)@N^Dn6Do7{ zz{M{`S3{GzNqh42#PgF=nq#8hO*s^cZ9O}jP3~~hDb$hsp})|It;B7xCph7>h51eu zhY6@`Zy8l1>*^KwV?kYbC z8r?O29Y2P@XVS#L8lKn4RRJ&e{Qcv|`Z4`VPMbKFUi^Sw-QZKjhO74}Fn0xiDfA%4 z4o#r|VfBQGcDzNCMKy*T^50$1#{$lLw|Y5|=F4U*ZiU{z$F&eJ9h1sXqf7=J;pYNF zN)x27m`_%Ju!}c8s+fVE0V;F)!9HJ|^Fmb3AD-D#U+2r@HF?PrH5N#^eMz0M-JVyl z@6j;YaZPNnD!d6W;6J%RrVQdsu7~CzQmgaSiKqSo$#(>~hWq4M!nbi3o`J8!ne&n! z7whenmHFLyvr?)cbV`1-dB5TC?^)1sgXIiYYaw&{7P%vUSq~Y6RI@U>8Dq9?7Aohe zbyt`~jvhkT(6g3YatlB(==+m3;o0nv5iN#p>ng9h_ZZ8aAM7&=CUULYLOS#w?5Se7 zeECYdD_l(QmnaG7cB9;A*iN~%ek;#f8guTzu_xxbiV32W`$N< zgcImJ?&3(u#lgoSIN~LA1TbL-|Gj(Aj*M`iaHB2u@PEwpGSy+x7bUuZY|l9XnF;#& zJ^8aA&Qh^arhjfK^V&wSXBt;P-C(02%;SUa)e9F%`i}3;zbjqDembhEyX;czaB+OY z#Srrr_La}`&s^TsMOfxT`bP@cK;Y>4%t}4qK(`@J^2?EbCc}y0a|RuVxxf%fBJIK& zs2s6?3#)mi$n8xMy0;p7?-w`;1j<)iug~4skmR2GWgKzzxZQNzG`eSTes!hm0RMwd7aG7ry)?rk-O{2yZcb>~e! zcjVFLQ&Vr2x;(MdtcCEHss7^AB@z+JWsm@6ft46I% zeou;e^1-6cfUG!@k$wBK+_&XKg&YpQHbCYEN782eF}7gY3GVo=d5i%s**HQ}?*%f8 z6Lsrt%-87fkyj6vo)PFm8bGfQZDt7G<7{987hs4R2#-DZS2olsQmk(7T^wEaJroID*h5Ii{so| zDI;tin3*1m%UgvpxES&OGd8aOjO{W%l+Vz%OEgXa**-0M{61w5&*mfm>zgE2mRaK(J0T`XCOHO|nt z;!=l2#h0(SYmTASAhxWjvo2LoBHenGWKbKPd)1wh5Kbb`$&umsvQ^OQ_#>#^!qm+j zKSm|fIj(s6{w`>ezp=o*VaW;pI_KT6^NickgD&=IN$9yQb)Ljv!-GgGr9{0LAPyPm z5zcnbQ{@0}d4@%I<1Ih+$>@!*m;$r=JR|=gwz+OlmMn+B$zAC-lAHShwp*z1%kJ-8 z_-8G63N2Ui>wfjBDofe=D*i3_&XNwUpTf`AoPRp*(TbL~;8Z*iEtSHrDBwIUD6*&+ zC0;|@^5rOUB?;tfau*qDGS;4vsWt53`55?PX{DKops1%g1(5k6MIb^mGn>yZS z9jfb4Y_&1)YW)p8@>?Q^K#N z-er_)=Ha@l04k;gWRSQ;47JE+Ds9x`fh}ALZfwdnQ0(8=?fpJ zfWNj%@n$x4UAn(ejSE?C+Bud*-7*JtdtalDo{2WbF-*kU`j5cb0MCECzw9L3RBvdb zHuAe_M|1Rt-VlV!WdZMT+h$T@yDSqE7g%7<*GiZNAeH}Mq)VWi$K4YEmj0#kPO*<) zeN->o%w{i%TE-0m3;KP{Q>>J=*p1?#I{-G&9h?NQpNw>T@XS(wv{h8JTb3(H{GI&U zKaUTEjuOp@h4Wkt*~W#dQqPZ6TF=}`YFu7(X@t%T6E-{*tVzUEYbwLj#O}8>zvWEV zUjRHn|0fyy@p8Q_e78q%&`|vS+tQ!^Ew|^Z2FXkd-|e_x5*x$zzl(h>!=IiWo|%%X zhVE@HfyF;GlJyQ75w9+T36p~(awPG^p?W=;FrBMz^@kjn3RgnP?fQZ*%a*%I9d{_dOhuTmd}14SF7J2^?*hBVKnqlT;@@}x$jIb#(T=-9 zmWrNAX?MDAGCSk+e@Si|e8#$!RzUMGNO==9A8CCoxDS3St0Jd|% zc=Uh(d6cPQ>s6Z!s!tX7qpsGHAH`t{5r?FMGkaQ`k%ObyGk2*DUNIJ}FWC z=T-z_R5YP4jguWG?KTRT%9(wrmT>%4_hvuDk@3d5rGY@1BGbur!(5u zb~Mk_>gf7O?TwWlK)~byrTKd+^*~M#Z&6JrfJA-;!!yVRbIrh*geoQM=eA^?uXP-G zZsqxy-72L6n~Q#bGQyuR^DQ%>nT47NRf zx0`}ix)Q!ORZ3BrcJYJmj}X~79Q4*S26!s|;y53ui_$caO)JTBmUtwq)KC8o#I5*R z-CQZJY)}pra+14yQ|rI%NnJcQ-|KtixKJ`j1OL-sZXibH^7rT6 z9Dh_uZ-`qin&Rls&Ca6~c1K>(VT|C+{qCFe`}=4`?XRE!Y(G0EypGKm53J^a zN5X*}7nOQ$he!&8WE7PUZt2+HCngOcMD;&nC^iYk)r?dB$bLQ!H?ewXW-yVRcCtUn zDDna0@>;!m4~_@aAD2(zw#n09i&Irowq5_T0(G}tN)yqfu^!5YqDfj7`8pO{!AF^e zLws*+2~ks^*DtS(emUy5^KL)abzkzrDi+S}H{uH_d(5*Mt0M+v(KhN0xqSFWQTi9B z*EU9_Z?|#1d3zVb(9`U7@N;TnNRqYTd}6`{E9AKY0JN)3M{TkOTxd`aUq<2ns%Qz6 zMn@hsr_7;K8p;U+ui@JQ#*$U)#`w>C5787wm#Ni9i;UNu8B~K+x9dOqC|?cD>?KM3 z%_1LI>ram3GH!-W6zNseUc4pu)vdln&j^I4AAZesUJM~g(vPOF_D&7vUI{!~5*rXO z>Qs*+E2I}s-Fm#FJiA`95HE?k+Xj+7f2${~zJ|`Les}JL3cA#;%zoGxq4O7r8dw81 z+rnE_h|kQuu_bGwxpsiL5Bi=+XPPAl{xFZowtY%lT5ct#e?-W>{CK3Q`H+;}JthVD z$R~!v-R0q8o`4O2rLQ|-x1K8bRy0eEgP%t>zMtlhip88E~ViH>x5r!6N2^XsL^kdP(`AQ0DtDzJ!v{l^aaV zyN6S_YoRa~S%H@mQ{!)(3~$#8InE~SR18Vd**nzCdH#H7*A|3Nmnm^|5WUWMzeG_H zhZDlQ(aynK%lC&g`gW~*$E$CWs__(AQ6=v|g`;-Cb;o`Ezz2<2e!1s*H+dzk-&9VJLpXS&Bc; z7<{uTUWW2ScW}3CSxpfe`)i_O;-oTe=7c!c5z$|u7S0@v&_CtqJ3Iy~hFVtn$)52t zPC?xCHrf%zixG(&cVt*@q2R9S!0sCR8UhQV#K;1Qye=bCccekf`sV!QQZ({t3BA$) z`Yi=8fa%@4-VrTv@R%$|1+WPV3^H>4BnSeRDn~?VJpi{yuW#cnc633sS+{rR*)zn_ zDP8(?!GxvoypONv+(3=xP{35n+7ZP*=zs>GS+PThy&hBwg7KrEnk!0FP|Z#l*RvDGg!fa1S9TQ{}b6@JuepW;xx zWC3xUR4#FHG84ZScRCUNGKZL+y4ZoNF^{CE&nAExLT~A+vU$q*RijUw(=dMEja_3& zVb&E(+f6vPwK|FEZQBOAb!?dJ(BuTWV$;JKH36t;*1a{55Rk=IKjqQ{=z#~H6 z<|zIeM)(A4X&Xgs@(UO!Y`_kzxB3bL2jBNuNL{f>kp&0G@OjUb!7bz%ef`h_dwvBXBD!bBQILg_sjg#zeBXx%=GvCj{d#VZX z2?jlS8>J_Eu4dP;RO&Hb;1yeG9v%Ij-SL|1n0f-OmZA6d3w7;J6w`$Su~w1$S|na< z6l|ZraU4TwzbD7xHIIF)Ci5luoQR0x=_6(q1SGcgl|>mZ_jcr-Seq#0`stwW-UQq* zJ@9}$uyY-chATfqadKaMIK(w()Qy*4YsRs`;a{l#jQ`18#pTIDMljc@k?-%T-+u?i z_xhu$VwV0X4dFBH33!C%djqV$T+yCceIMV`)#L6^C}24I4jD-#Xzl#HF*2MwcTo3l z*XZjfmM!7SG$rS1EA_KS@Q%0X=!=}I3(WE}?yb(zIxnPOeUyxWrs){kec5l?JTbi9 zwf0A(@<5&9JEVy=vmzGwJN_qwE}^0NLw5F3TH>#hE4IN}#YZ_x6Thi7vnu3-EOuT( zU(N&vlbFZyrf&vkNG78a!MI1H)XCV0$d}VUn+rV?nAF5;4noeJtHwi8ryN&brNrB% zi<~7B;eKv44p;Yrp}vBUJpnNuHqRfYdM?S zLz+aNs1C`geQ+R}34l7_V%{%OV0H_1ZVP#c5^}D_KEZ5QNg1=Bc(ZNvAqM(2#u(2g z;T9Y^fYNuZ(B zWYGo0$acN}x1`E;kM(lsovdTI-Ta}9XsaMFbFRHA>iNSDnE!d>Nkb@qu0Q0Z@!qnz z$`04~ulp{+p}~gBgYAhmkt1?hnO+m%jE*I1rFacKBs=irWNk-l!Z%4 zb{SsvdpJyP09Epe-8o-O%pdO#Op~Y@4v0@|cShQ<&8_N<0{UqWFFRIb@~yTa;Tf%M zjD(Ue&nk@06$bz5^`v<9sL*2&A7bGvxia;$UIgNdXAY}89VGYrL__lw@9nywGoJuX zBba$ZMW|msYqM}(O?G>r%(&}JR_G2&b6qDMaR!Nxo=H+Q@_(k6?&Qn1!M`3(7xC`a zR6%0n>XRu7Il4CKdmn6wXn65-Vv#|s6f0o#N@p~rCb>opU-yR?JQ3^am`RXnwiFpj zD(DWjZBxy_9W}YP2Y>6Iat#@VxER_+lvkou;Uk;J6J{I7K;8a~jD4!>eE9Tc%UZ_Q z3PYx5Gah4A)Kw`yw-hiW(~ksTCK<(FSR;K+MG{{PcM_fsq+=K3$SS-e>@qn>e6EMbj^+|4(hWpLa8p5e?2FxVf zGWr_MHR|&UeV_X}sW@Avt!gNlyWX-cHD}-~l5PbESj^_`FMlON@%Jk2@3v%cRbKVf zcAdo>D+$&dOkxnd?k(M*b>y>*0lx-d1KC;Xanm=@`Fn@h7{`JbbBaynP7Yc)mBJV% z;uR)iTGu3JUO$S{RwVo-QCzR`;Y?efd)5&Wi;zCky|ZB_SpsEo-B%*}JT&ql_WNEW z0B^P|V<8gw4Lfzw^n2HL2yfTSd64sjzq{4e!Bf%_hIle!L10LkRNGf-Byjp==XsC5 z7J84pDv;9s(aZ-v<7!Dv{75asc(B}87+#oMBvElG12n+$2?wtlqIn!uN`z(jV%TYbf&-+QH?KuF@yR0)!%`ZfGV zdfO>C-mN)0CjX!A+h#TW?}#H@8-;N6xC9p4G4=a}a_;TAp+zNwV1~#1)0jIj zq{Vjq_wBU&EGLtea3Ruel;k~!FV`KqX;RLd9MKI78phXI2Cu-CEKRqD)KDa&*eo$z zLCHZ|XE0aUvo4P#R1v4ZT!}AC&yl|{=1Hq7uROk?&qwxWz(e9|?Jg@PbK?nY^e+|o zkuyAxib{bi`S%Iu6y9eJ$z%;&eHCT#JIKJD*lTfkdTuqEU{+;pCn&BBaVal}M(SSDzB#3waJHyGz1q#me zT~-C<&M#_J&&%@Zf%~(6>QDsP>hp;;1H4L^-`!}QAZm*>QK%Hw=^2o++i)i*bm?_j z2z6%UPJWuwK@VSBel22(zugem8`2=!Mb-ZDVJG+ObjNbi9j6<8OxGeBMLH&qZzu-X z^AE7^1l_51uRI-N2yi$}3lC`ZcyVfB%%Dcmz;6iIfE(Q>>_V}=CAnwR`^CyKub-?` zjin9mI(rXxk*)gcs8iMNvw$A^#eB>e!iAq<^Wsjl+U>0 zbv)c_>ELmbj@eS)Eq?}~hmtoLPShncx zsf(w-N7H?GlYbdCeIp!b_y7;rMYGN9=<1aqK)dm9t0^?`Y?`Q_V_?GZ==3JBQBqeZ zY%Y%6Lrss4p?#7_N?I0fYjZ;xAc8LP)SP%Oj39=^A%o;P#|&KFD*Fh^>btm9UT$l2 z{50k%xJ?GcREI`3&>wHH6p13D+1d-8uRKPN^@v`uDGrG}ytF$!w zgO4@l8KV|NrC=_@lJT+F3NMJ99W-wPfJbeSVehc$*~A|u=e z5!}to00{n25ntMtB)%>XW&peT*>&}(N5(3OPvfb~yTiF~o&lBbKKFH_k^3;RlG@f& zPLdyTUqTkQNT#onvX%BK?>e|7>vWyos=ZNm@yt~+=GX#$VlKt z!(OvTVV4}WimD?ZMgV%zEx#47NATOMwDC;$_c+4%>d=0yKVQ3<5Y*(WMt7A>^N+*)_~eek{O;$*v`|n{i$wO<$yyLelbaSEEFU}-Rq zltl9e#(%o(mOOvlc4s_zkrexJ;Jx#sRzBGE3DW>PT;ssgcbm8x@jk8$I!RKma3s&& z%`EFnm#7MJ_`tCd#M?}~hiMnH5GSg%9|ZqpZrl3d?~PX-D~uzuq&Bz^?r&H*WWO|p zWMG&OL`BH`iFj*&f#98?hOXm3#2h6pf!mm0Jlu~#am&+j2YCoccTFOJqKNf7z6sPD zF8vb`^Oen!520)+=nMv3cvfYDd8dzBj5x0mjQP2>RdILlG(}>}(l$CBcDG>LVJ9|^ z_S;Yl{byZg1?#*M?_Y&z_=tYj>Jy=fA{l!dO20-@Zod>yS^SQ;{rk(tz3>UenZMpB zKxfFpKOmxubh`~k(u*Btr+KXh2GQ%c7&AoY)-(8ci0S--(WyARi?gHO`4|quJEbY0 zkO@Je+bMia794ihsGW!~IJx5t_tsnKZ^+Q>z6K?E?N3;3bU;$>!iOTQ;kKHlNhdv@ z*9T`jGTZe0#%8#z&Iu86y$1bgZsUWb~|LBybRo3rk-!P6IWYXrRZno0+3gh&)gV+!8~eJTaHQ;Kh0J$ zpWv}7vY+Qjuli1sRvx`@xVus=b%{y)5Bq!qbv8r)nV_Gl^-C*UDYiPZ8o#e`T;-HL z5WENG=6`x`KKk-$lySEUZIf@d^?!B%XXy!AN4ULStj~)EYN&%mLM^tH& zTnZCZZ0jG5TCwjqwOAHz!T2-igdAwyj|(|4pIC5BxniBtRNbNlk4KHU_?}@@w6=Lh zdD>Z8*9_hG8 zwNHPKYY4RZ2!aKyvo4rwPJc5?{!3R>%uGrnbdDH8N#^pJ+U*TB{H(I~5jY5r*USD> z%{W7e`Ch3DEz_nvA_IHwpgvG3arb`jTyil1>)WZ@HSR^YY2^b>LP6Z|ORteRj3@7@ z$~gi(<K-GGIq;aYgaiyJ?K3axN?@{{ zDqhB;ClB_=;6ZW5L zCN$qWAI6m16*uu?N?K;)ctW4sP8ng;d41KYcNeYHu+SvwM%C-hjj44*shhFK58Az@ zMyTAwZa`mFR%yNj6E(rbhwF*$g)RJ=K%Q2A1(XFwo2IU$+ z*>iG!&B+??e|;tr`Q42tNz9L&1Huq_qnUELr6+0tX?$bPFtuG)C^xQ?O9<{WoKzfO zu=iv=+-`GJJx_GT*)?{}^N2H(tcLV<4JM-R<#ovrO{_-y9?)h8tT@MUgz^>O@xVuse?%vSA z!NX2*UsTIt*wrzf+2-fc>w(~XFy1i+-lWHYQkGLkM^Ysz#|QMu{mKw(=+kyX;;OMx zBd%GLx0+xLH|n~pAHKkKNY2B;3P_H~RjK-yM)xpLI|4PfK;^z6y;8f6EI8cHz!uj^ zS%I59-dL_9XSq0@Q}X*lDI&m@>643zx0N5C`N*mm{90?I_M=|4>Fb6S0%7N!sZlFm zX)2ys8-lsHxSy+Gu~=d~60&jlIC8AQCf9b#ewdsP7L#5=6nB}fb%+OnF!n?Gf7McH z3RKIF3l0yHB0ut*XvB?v1}$LA9`9Vf%f#r(YnCE zP$YVHHSJ371J_S0DswI8F2YM>#&+ZYfswR>yb>*ow08bBDc}ZZpDQmX#Vd}aM||s| zvu1y}Efd%miR4RN=p>@V`};E&Z%bFs+r$|rHSvjCI#rlY!N)^#?qqdMB+Xtjy+123 zB-N}!=XVMZ%C1bM-sfIEehK%9c<5)?V9bk~e%Xjx-4o-uNcq=2fzar=3idj?If|r8 z74I;@!Dc5{P9DsT=IID$oaZz8DxZTuymkE{kJjAh8)C+u3cK#utMu}H2Ypf%<9tK4 z#I_U*n9O!*O<$Ep+!$Ypg;NT&xH1=b&9{v+PdEen51!W%;J7B(;I!vbvmxhTuKDg4 zvWJ$8-L4uAwV*AgniVzYmJb;+{Te#c9l?*P&9*eJn54Gl*V}1m;UxP|-n?W@!K&)- zyVI4CE3I^rzfLVikA|-h_3aG*c((5 zx=%KPdLnyt^ZP?v9TW0rYhQwa=-V0vjX6jZZT+$u_P4@r?~7l^e#PTNLq?ucEn;p# zf&^{i3Jk4*bFrv$^OOx7nvZ<7>hIVi6BSaJ06sRV`{@mX&O3|39LlG*9v^y_Q)CXXUr!+nf}H;Yh(R2;dJPY z*oD{Fg~;q}@v`R&i($GKLMrHOSwt(%KI#OifB2?%Bg-vok8ln0{$s;~7*D;s3yQG| zk8N;%3H1v<8{p|U%jR~l}7$!o}-2QF;>u{^WD?eeMy^1a=S+jJm+&4DTDV4wMK zz%#Pk8MbxkOBBdZoSK`JgkBXi*hPrF2=A)_7|e6#7UWN{-ISShilj`78ee7wckI8% zS~;5_%h?zYt(~Ed18USzxd*H7Wd=^Av*#L$X{SL!?`@xj2K}#?@mjsopyk1m7THTt zb2Z;HzdPKmS@cEitXGA+@V{IPaQLQ2X!<1a@^e(I;HQrvY8PU)r0 zuimKlrv&zcfMfd*nW>JJlJT{a!X2$Mm2cs=kG7|F#mv1RZ82%d2`f!F-X6l?-l}@V zq0*N6eR@`7LadbK?m%6-hGmsAchr!YBtJ8);Ujiukz#OnxGH<#*0Zkr3qYLl?ELW# zj#?c^_#^}X4Vg0pEQz9=*`CbY4mU|Pp=VASN1c>d<&zRC9co6M7**M^=p5Rn|*$JU};{)}tj7V?#+`@L*x z^Ax8W8-3qspw?9n@IPigi^ApemU8U(B%=Z<1)C$@Syublfvx{5lfxYxdb7XaLY}=@ zMHI#umTta4FdR4Tnqx(mC4!p!vOJo9f%$&*P{&2R+eN~<099)|2dMhFU7sJ8efH-1 za-U_K+q;TrH-wX7W~jBvms+~8ix39MqFh>NSKO4> zpmE@cV&AOG7Zk+4j(nAH?Zxj^G9#4Mg#Ez>mO?7cuT*&ULF%kTS}CuLapbQG8UKpF zB#PXleL|H5F7fjEw;_zV0kHl5mb*co&t(U_$uPvtfer5C_f`uZc5_XNsfc3#~ zhIrjKF14zu!Qi<3YoV1#)6K=D?0y{Ijv&{mCF8l~q}!MoT_dADrK(>YubIiX7i`z` zsFGM{kH4Wq`zo5`PM<_{zcw-__e8D)=%+prt|}0Y$`(El4{%r7&0<3ka^)MkT-6+C zK>9(9LcnvisW&u4P+f&;&eWZapDHSVt~Wn7e7azlKg*$zsqTM;IVS%40w3UOL;)K& z!S%DHz;^g%LpHekcivp7N37Rs_pAc;SLL5oDq$!d2D_x1-#W}Fkn1`in7JQq z*Af>@Xee04Ot$N}y9u%nPG{X-9Y=#HM%cp)obXQ{ z11;~Gub>ORV(`hZ;4`ik`fxTaukWL_7&NlpF(zsm$=Y!e90SiGbwXMc{@*=4>6fV24)!1 z!Ae;RT5kcSq?BD%(Rnw=5Zb<_EK@n&fv#5^l-1rd2NT7BSY@BP^mm9PmB+q7zSi0#5%LA$XE*|+|L3Tkyhvyei`f}4ff*E*4N8{?=X~3e9g$NE) z+_Q$B79_Dh6Ni7j37hidrIlfeppe^S_7TJtPY9lhl4TKaS)ePhsJiRCGyM)o=ci&Q zTR8fj6>e{BM%Ika%j687kYEUXQu>|cM~;QRzd1!eol;T+t>)9=x51tJ&hqIcQr+Ev zrNn})7WnAmso%cu%ux*%G9QJ^u(+>!#GKZ6XsJmIB|3=Xr%b=|il0FLlPet6khF9p$*=V(J@h*Pcbq`r`-&{N-=1ktA>xfvbU1AB#tSk&o4%93*-isA;Yr%o4= zODtc|^lV(`52@~QyJTKxI?UKDp-C*&b!^ok51M%NuMlf^2 z0qGp+bu6g@4cYZf0OhGBR8D3sG)hN$R0@KTJs*a{+_g5%oQ;G;_XCdh zXZ?>P)oVXQjHf&v2ZcuZk&U(Cc;9(p5Zr-Lq<^UI$?W{azOQ=Ci$D%tsV@O0h&6qX@S4gR#8q-NfduZjD`!?3(0X*&#*SFEntG+nRU;05J)t zQQ*lw0!`R6(1Za5cQ(Ekx+L9dHTpv62JKS4(On)bgPwf->kDtO8LvSd2%*=D9-!cD z5bQ6E(wU@#!@!sz z^8%*Ar&H{Fb4U0XRl}hVzv{l3EC9RGlR##4(iXuHe}?a@$RcGz&BFA+hUXn%n>}fW zhdvh_dH5W9O)&-detu4;yvv<>okCmo{uAaVyZe4Kvrx@;oYRlF`RM$9*I)zb@Xpp5 zO++!`Y}wK=r8_sZ$pM$+obG3<+JUgRXa;ov=BQpfPglGsSEHno>N1d%C{vd2O_rUD z^4sUj&Kah~qzEL_pla)aJzc06sm;f%Dp6xH)Qp-)IaxAuIAXLcisbN`2@R6=HK^U( zzocysvNucn2p%Dk8OksU^e3!qKi$;I7X1PR<5NwtuCLnunu*-@>-K74#pxwyl?Lfl zby!8phTqI`{%l6Y()HGE<60u+bqZAP880lS))Gc@8vM)(Cg2KW3(RUk*2hU0C3!YBs+k(y8%RoG-hl=eCQ zj$&~6x{Tg03KlV=ahb1yS>c5j-5Pi9{8-h?RNzf@DA|4@i6&09Ussx|pseiMKP)xJ z>zo8TqHu^|CSLTV+sx`!`l>y?T;TQAI8t1TKO66<0U552&2PO#!fSoVvZc9^1+s}$bKq41p3kPx6cO<{-49{qyG6CCWq8>kFCk{ zcOw=5>SISKnH-1P)n8)fj4%BXU9YgQnFP;NYU@jWNfSF54_{bs7D$zv(u$ zr6G;+=;ZnF%8|7kh4EI5TYOSiQqu1@Pw9rDejYpkE6v_W-U5*rNR4D-y%ogg?1zRo z7;D8iN->L{Pu^)@iWOpj$pTMesHoCVyhC8$C;AWh+A%9PuX-dHchCJTyAq!5Oiy+C z%Es#Ghy2ii1Dy?L;XF4M`M*But8L!vCbnb35WA(JT^Z~e-DyMig`Xk-)+~>~!3(*O zUGs&ago88-28FY14g;X$?o@qS70Rq8l%rXr$3c|nX zO}VvEi?D-k!{EK034a#wu9KoWW~7-bL(b?vVNc3yfpKqr|NIl!=4Mp2R`S2YA?*Cz zUsj?9WvNdq;{kCu8QGiZ$Iw{%X7cvUKTEjX0UWH>oXxUqL+Nv`LKo)fFCd(y&%fCi ztC%?6BjsF#-YhCm0x|*?sa6gb=*wr~nd-W)du$|%kkK=zzGBbTxFz3omGR}y)Avnv zcNa*syN{=dIuu5GoWjUVM7+I1iWYa5k75@l7}}!z3+qJ@fnWS^?36zs8^!$fJ8vfZ z`=`&c`jo!6%^3=GFw8I(`7QYf$FLx~@#XV_uA$HLCH?Ziv8v0q@!bTFpot7RYbz?V z&lc_K8UX9F#~|agPZ~9m*YdWx+loQQF2J~zder+%=~3-Vq`TAcr?=1bSA(72SI;Is z%~T(XnfT2XZiNuv{H=_Zy`_C2%FDK@qtUBL_5T2h)V?84dyb zaqesKG1UpOO>`$7Q3_(2 zjDLg^FSjvimnGKU#va^jB<|Z0++Ajon5q{MZ8mkmN=>FTKBKG+*+j^~jJHjGD! zx^|F12wPu`|K{W$%;(OT6#%vpwd@(8h&wqwz2TOHfs#^HeGQF8xfXTO7p(p)U09#K zh2hfU-&I>~r8by})|5($qGTuG&FZ6)*w@$X{ch+4+C)Q=hD8?{>TL`jP(Tz@I4(n% zc4bT(2myqPe!p%{n)MN08GAUbJ_&TYuE}94l+4)vhIfeRM$@WRcsbe2!+G=tGpcz( zv0NrQZj9fyTX@$oo-}GuE>*gOtmc~kxi&ez&X1lP+8C~!sF!M`ICA@%BL^H*Z8dpo zDK9ZPQ0dVfAs%wSn^q64C<(Q_Vd@FCN7q_gx5iEanX(1)=oHBIy$bK`m%YB6&QlYT z?hZy2T2vtQYQz#tUc-SnPvLv|WN8-(qJr9@|-0+T(+3IrQ$lUip4%=2Qljlnpgf%EIlJR|%1vfGm6mrMgUBbzmw>tah z74w~Bm_%4f5&w(qFxP@h3(!oqVlW@3M@s)$>9L7;HEP!YjAd>DQzF$ei#UZ6urQbxJH1{@wI(o!T|@+(TP+W9!Mye8$6Hon871jr1ILFgCE)p ze0dBxzY`uJb^=^lyqZ)1aLEk4h+~lZ63Xzj!a@7g&L_+7A}4OYaxLdxu)J8eTD3WQ zFY6=bnTb3r%^dHW;z2Wl9W2=%!vvfMBy?ykV|!UO@vH^q|G;88{B~>7yhrXrAxSls z!5Txho!WR+@gjRvDK1pjG4!Z;`rs)0t?v?2TZH!S(g_Mry2OTJW;5ad8W>xs*wdmu zwyzw8G8sE(Tnfhsv`=POchyE1vt&EbKwLyC4V*fB_+@0qbEZbVH;Q#JtT(=5)6~Sf zHSgmu;XpliUAQ0Nb}`$i=HX5@rO+RU=IqZkZq83TmCHR7;u>Aetbr@#_w`%0Cy20# z{j2B@BYTdd;Y%&)4#uR!g1CY$Qx1T$6g0inGrZH+R9jFw5FWNFPPu$W30iEc{EM=nk z0y^HCo$gq?y(T!)q@-h1VantKVGSq(!j$eMpf6J!GA*5b+_6+JA<^88QpNSt0XusY zi3z4ApWO)*5JCu-bSwsKm&OT#iE6YL`EfjE*R}4b2n}#aC*~Lkd_GKUg-K+-(cWZ* z>MMVE6BIaB@+qc%gG$v@?^OLvcqZ!Q7pZe?2 zLHNArVQba=$_U0jn>_}ZiNutL%m5fdSgWij>vFc}4Vz^{TA@suPU@Qm(a=c+~kX9@{tV$Z01l2$|I5{(l^)7Q$AS6^jW$>|LnT(8PX_ zv~&;Q%-TGQC?Kp$SB4H!Z_J-+SF9fS*WSVAE=7ZF#LKKGlJn}Vr19V7#;G*ZbH?XH zM%}gge%jW%1t)89D5Jv7Z5Cg@ySp0WZY9l03!A(WxYE3;l^-QfEK|y8s8P1W%%ZN$b1o z;V!Y2wELrD7s2;^gghP^Ld8r>7!&8dvXEwjB>^hK?Qp$y+XKh9#P0by?eH+E^tsXtp{^6+28BqB?eO-W+xCL}?UKC<7o{w3{dP*}I}W$j=viWe)tt*s z2_QK|E)J#mDvV*WLI)>cG5{AON2tm_Cq1A0J{BGmhn+fVCX<8zhYFDHPmbU0VkpSf zn8eu+dwZ=inL-*K!JqJslN103fJ#4aqG-YurGf=*hj*G<$;t@?pdR~p0s%N?X|A0S zf7W|s4-#mityPzMeg4;jWLBnR;^QhZNH;;csYEW+R zzT@hgOL1BqP(ypnzt}bZ(7f)e-)F%$^P0r_5g6EgeYRHAt%k06dz?{Ya4ZNAnut^@ zZMa(#?kh{&+!ck)oQ@~dGI@9OUI;9-(u~S+6$Vb^Ydr}zABRw!=qg;R{8TwetWCiu z<-RUV_X&;A@GCS(M?-#cl`%Ta>@90vBp zG4dN7CHAFjiR!OE%AKy2&FnrK@m)pe1|;7}vB6}R|2mAxa`4SHqeyUl`bU$CCd5W) zkwGr@A!Doul=#0~8R>x50mC9}cG_BlUY7p);)tj?^_FD2*3So&G=Kgo!kqf z#e<{fsVM;_vzITr(nl5^lS@jJTnmMD&Ellmx%6CPGK}74s>7IjGeh4K!h=NrE97_5 zG`5ST2*DS6TdBFh7S?aSNn$AWF~r3(WRFIxsq2zXvwri50Y_lMRlO47EA15>(`%at zA1vL-Ep|!Sjd#3iBL3*so2))jO^~?BnUWKS$`uvqPvvMi((vgI;y3`R?3gJRoPBV!;j`Hhw*asj!Y+I(6ovovb2!hP;iPzqwi?dm za1xs{m$}@6tEJ(7nv4$k^iZ61(1LX)Z8E{s5qC^-J%^0GWir=q|Y=B_jTWT|fNn}aPI z=PmF*3ZMq$JdUHAZ@l$Q3oU+{m=F8*UmjhVhk|_PzxU3y7%?NA^=_!MOYQOaqoHZp zN-c;)5sweKEf4Br8xPQ{rD@m+a~(+$QeZIn~NUa;eg@b8c{$N z7{_q7$_;B&h^;wBM}@ArO+0-0XzMp$vwoN zL~*)CKKgMvuU_CoAE@F0Jw^uZkQVlKu?O<{!oO;6*lU8-ka9jnYgzMMlSLAJb--EU zT>33fc5@D^%EapMSJ?=3h?Vqa$Jxe~Nciqk6c?eM>jylUm-xzomUX_Gg2;2?c= zXelK@ULwS8l+^62ba`^!2PXO$vcNI2kUiQDiu=;cS*8v9=^1iDhB2Mxx01upsc|i{00&QeQfvL(Q-}b;?nN zh}NNjKZPguyfDy~#PWfuaH-j9g$nS(sd4n~>nc55)>iQOzotrQQjIL`y2)#Dc#C2w z8+Y(=MH^_?WgP|xHs3{n-zFt(DWnu{hx(SBvPSIxqxcRX3fIOEjX_5xU^k|8b7NSE zp&I_In_}y};@e_k7ceP&Z>zad6Uc}JQfE!*`JCqL@F))YX{8Jo7X6Bo4jT<`;-uILYqd=5dUY;4Z;c zTN8&wut@qR%A{#dfg1$p5_Iv)l|E$$5-D=Urd*&vM`dN;<4UmRSRX zTq;4~vJGb;Dvf)ST`HCAPqJz8+y$+}-*y_1g4ER-)jWN!C*ZP*Y&q|S znvf?TlfwRGQj^=P&u=W`o{rZ;%KmxSlsU)7v*tV5@Z<^?tccx}xg=-KrW@qde7A?z z;f^xKdGTe~SnLOo>%)vIKHf($NKU8N7c5E5c6(LVMd^6x(4^7cJ0+WPdSKuHEgdZ7 z%}Fs%I553|T42`lR2tnauUB|~(mlx)cAJxgnCw;i)`XMFLW^g2=qwj3GYN5B-94lk ziv?wk?nmwytL2xyuv;p3ygqyC9R7Z-5eEzhYG-fVIFkrP+}bEaJxsH$dQfzfnvMm> z4t9bq1)_0)_sqC;?zveNg+URbRlPXxa&A}ImwNfJFVFVGke@eS>_nFhakjQ?=azw^ zO)qMi`v3jKYSM0N)pmFlUb0_}NB8QNdzQ@=yJYMe7!c;%T3fcuLIsvb3w4VLrE~d9 zQtDkC{1&QU)hCLq2~@ZhGw52fEKY!hY24wc_8 zm`|zbRm$Elt@dTKEDwEMDX3U9d*zbzua+B9?{WZ^ojt*_RU;eU9u)ZSrunrCz$$_V zWs}CIwO$s9lBy?+65rM>iDH{~y_!>B4H?-x$MxTSd$V1}<{r*|S6bF{$gZHTOs|Tm z91}6Y^`i$;I`}&|&hUU?!KNFIM^+yflS+$82kkGjT;wW0jcxh6Bu9^Jsp+trd7Phm zGLS(}4^AV#*DH{YJ87A2Y=z57qj5}}{KK;jC}#LA&GlI@(t1d(dF{-%t}D47$5|Pk zmZ%X@FLsgEo0~Ld?Tr{n1s5DX-AUwfyXO7N+nYF~hWoCNdGuk_g3defJxK&^6j`A3 z5KOP$SnigH+U;&2>!*6r+b!az;8d3Xj~yC=lF6AG(8b%`O}efGp?G!@r*{R+6J>US zhgz=Fsw+UGyyvFx@cW@PT_OutJkEB{N!AE>wj6CBCFuic&RxCzT}i>Ley*gwz{AQ? z19F|5lCY(ch%Uzg`$;`_Z(qoy@zwvrsz#901la<4S`a()XYU4}Hl4)P*Z;*;>i^=3 zo2_#1R1gD1Li7VH=`sixJ>-Ma8E!A~S?djdY)`En{IJ~#oZPTwKKaLOp7?rzpQV$t z_Na9Twc?Y|LJ{BR_3+BBfz4nw5!M8Ss5$B!zg$H4a*RzumYNp6zO;L9ye$eBL`zMZ zl;b#7=UGf;lUALPRi~2Jr$_EXAYd0T<$a}~NpSiV?k6L^>*oFSgD%@0RZWB>9r%S; zwJJ8Aj(ajq5lX-DI<@BRoF~=h{KQY;$yV024`aO#BXTCM)1z3qs)?0Jy`vt;UmKST zQc{l|?#7v#*IZ10D|M3U)@i%faY}E{!)pL6wlg*HmCrHBK^?0_GlfpFoLXg&`3X#P z9co-L^esfot2irz=8Hd?>XE2<*$<;Ww{yyvRONI`bF30<49L6n;*o#O#N^)ptBabr zww!#=uN2!X34@VZ-lAQ`=qS)R@})gwNagC|uguO0c55@(7bTD-H#k~)ABzH1Y%$f#HM8(C$K z^>lR`P95)))E6}G!NFN#k?I;j9}t6FCo$Lucq{-y2AFC9dH%eK3^V^f`-j$pz*qCL zRFIFBEXJ%NBhK2waxDo(`{(OieJ)ucwHlS9q*mQ%;AZ~HtRA1tEmyVgk)DaBGL9>L%$=hasCFP*b~HZzM@ z#;hJIQ`y^c3oHR)<#mKStYsX$jbf;FchZ2(4NHLmIKCP`?$qDQD7&0$p^Q;2Gi)Rg z24qOmOIa+#s5SY+8K(Za9rT8UdK}bJ(}i8ym&stO2G3)VXJR^^*uNx{p;w zj#S=;J!5yy(@x0GTe`D6Ra&vp#H;pa<4jK3WO^Ans#{HaeCRk9J5}GTI!6i8CHA9r zWZ25=CE1ZR4_evEyAfaxg+?oyoXnw$CQpz7fsK+{@}7I6NYd~O0Et&%FZuh_Z3hUm zTJU)IHcoCkb;i~AupA#w@=fvHST!iI_Fk^;v&>YxpNu%eg^^LJY?V<8)|4k@VFP{l zoWO!*y4rcQ*a zdV@5qRcx@nFJezy6{|=|6_J0C`J!e&44*4x-?L868||lE-l<-WE4Rj>6uR~6HR)K6 zUhmX*mF>5LyAK4fgeV^IU{%YerVTS6(usx3Z+GB_bhLUY3q02=+poCopN0yD+G>9^ zQ7hK!m)qNLHxOM@4|5Qfu9J8(yjk>qa<5J5h76;kt}jWNarwkhq43rxF)9P;vJeHl zDRz!?w*oJUa@Qb6gvei)bz2mLze!Gf`&e1`=>2)~>gjQqj*<$Qf7JeO`X6wJw3{%n zc!Ji~aa=KDe@UfpTIBLupL5Tnn8;bbc zOYxPd1`Lwz_^Oo8F0!==K*F6m9rVM=e8DtugSzcd?21xNvRsmoeT|T3`xRY8I~`BN zohz~#OevnFPW#G_l=l8`VBt)MJf16Au%2$DPrE+0K=72`t+&IFv?KfVV^!qua^E)g ztInp1t7$$D_63HBFPjh>@^kH2iSns-zr(G~`*O=12?aISIsF4!2F5S;qm~rx(&PYDPcTR*Benrtli8n9u zIb6p@Ns0CQiTHveQ@JHs_|t^^B`bs8Fa7>;T}V@b^QUj6r+g!_58k-ni7_q(lTJ|7 zt!Hk*+G+nte+><7c&!1UKb@ot1Ve%DV5dL+o)t7GntH*wB*lRzZIVul3~3)u`ezfu zrv|I1eXl3ci*ZObP})%dp(E|M-h z5AJMQSRqsb@&}>yiq$cFYvPJ~9E-2cRaF_P7x&Y}Iw;!J;2Kj?84?;(CJEc1^95*wwnu4#4S70$|uWtV2U5ppQ*Wb z-r=E%`wV8$ae%OYp+h8&h?$Aix8IWIZ+T-^I$gr?8 z;0|@3VWpVoJTI3%82y}>Vdxcdj{$wo-d+(ZpD+pzMM`geReX6GN`7U&nly|F=p9-pJx9N7SFNuVaTa4W`*UFzYc3u&|`><9&)Jj zIv3c!7AbChGE8bvDFVyCI_gBQg$>yYuy5_Uq`uhI`}HR76`o)4`m)Cu#<#`)^ti;- zP<9+Vj(j-mx&QgBUI3*9WK){<3cbZSAFsIyY#o16RP9o3&h~x9MRT?@cW-Sp?54yO z_^u>s_UBiw8Li)$@)*6;XF^SJj;JeZVQTS2(fddOiyr!LO+BFfrOAp0+@G#kD5Z8Y zZeMJqoy*A8nyA5R6R;UiPYO0~E;6a#lI}K=8s=$IG@a@d>tGC;H;ixePtvsr^g#?x z>?1#WH`aBECgRk{1%`;c5_S@Qw;^)PzF6GXS@lTz+|t#QDBCfYJs3)iL`r8%`TRG7rd8~a`xSrslF`Gu{tu= zJtEn}VqTSmC2{d4r-aBMjbZWh!mN?>c;cecUG^m1Ed8mE|P2QpakW^C%4 z9PEzZauaXc{(idJ=T)6&Q*@4Fy=4E$pF5gZvbI3A;l zo|7j0aqoZ4Jj*H>q+PZE%2vaZJv7;feD4mVM%&1NACbVyS`n{)scSkibw`xrRQ_+ z-WpdoN3sRyfd8X*Hjv1;n9t7q`W+{**KoVb!WiC2CF_H4zJ!_TM8(cm6ybTep6EwO z`GE6tsj24eRp(7+MK8GscC}m^qA5dnG8jH+H&ELG3Lkf@}{+{0;stL^E*lQ z7A}*7bC=vI`v&X1womtI%+ySen_&|-d?J*L!zfhI`IyM;_*90a(Me-ElHc@HosULm z9hT&5X**^;^QxT7_oZsSN1ms@!y>FeWc9Pk41)ImG{F|plw<>S9Le)2pmSR9#=8}9Td5`VYw+^>8^Y z(sKxnFy1I+8i81-MqK*5u!&QpqU#8F!0o@YHLG*o9% z6wiM%VUM!#;>Pn5DNw7|{XVzbj6kq}%d{QS?XL2Uo&;N<`@$?gBVBaPQ$Lz#proJT z^n)`Ijqn0K9Ymi>0CY;m&oCuKP&m-Ch_Ph6u5C4l#Gp(ork`)pJ)~6knqj@Lx@NA6 zu_nL%=wOl(+R=lUK&))z-xf?>W2$*-QGK1YYV}8s^mNtCJGMP-<5mB^Eb`YnR0jA4 znKQNDt)P*kg1%HAUiFA)!Et4+%9vH)sfGBv^{yiCh5w65)KgECB;1_=OOA731C&f5 zV_(k2g}pSHbvnW*K=@~^YG+USL8A*ik@Qf_2PZ|W@Lv&Qs`{^pUD_Jc=t_WYp87w4 z-)$oyTK%PM--ffjh{LF6<5+N47=E^(QI{`iuA+J1f&s%&W8#mCRFDD60(v znB1cU#_scng+)<`)tR)eGkbG|1YNTs z&CSjqj$xGdTDr6}VOS5Fc8DPX^{^6454KxziFc}-Sp&OtM^Q;8A3PF48Eax))w~>S z1a9Znf?R){(?aComwpMq$;+#$cFXbHI&`VG-m}eWrkpP5_aTq9b_!7NoPq^eSi;}4{f|2AW5YkxzNOW&z9F^AR}P@zD4L|k=f}-oI)zB zKbh`3@Lho>7}dCmlF~T|0(~8o!*A88;r*RunI@s)@Wc1ocx>UE0IW} zlO1;0$xg<%iA>Hz@Tcdug0=)cdC~XQ*w!PNWI=FB_#UW!IK#WbaB$mi)MC>9sm?$!BNO%l6}O4AidDNHy8nJ3$-VCE z>I3U}+)pnwM_2DOWg|_ocE=-ATL`Cd=bWH%p_%LZI>x768RAHnzK%Zb<;Ia4R5lPD zldVyhQPbG22ydjjRl~fj-IU{<4t$!TQGrP;sZXe@mp~%+KVO&AVqWDL3*Iq>3E75U z5a(Vr*lvUWmStYrgHncZ*y`hbVW4(g|xMLQ_36=P`3o z>ijD$pS#2|$a;L9@#db_SqNwK!-nAYPfYTgr8xD+W+JxAvE3wTPYZy6U99(?T^pyGPazeD|z`_Lq7)M48jdmEe?+#(=LCX$52C*xY zh~nlVEhrI~T>I==*1VL-Ywm>CNTx5i(!IiErZS*EItwog|9bU>rp}NPdeUnpOSl6FWCsm9pU&|5EzYE3G+sabB5HBak_|x;06X1 zA%db=5-017F?oY=B!#Q?m6N+=I$Vvk?eeF}ee)4h?g38<1eaAL&zg7JqBna}Hj7MZ zi`OOp1$@4Dsl>V{{IH9LEH<>JSgljG;91t4OzeBReFyhyV>GsOV#!KW3DKLrhjDk= z%}}R%v}jNIjz1>NG-9>ty`Jz*N2W3?u+9;d7>G+%@IWX=52`!BXZf}SUYGhN4vy@! zt0lZpe6go}pW}}VP^nbTlrl$71b}wO7E%x%fsds!WbziEjy=C&5C!D&A{dhcaI||( zlHHa_`8&lKu`=~nQXY>qKK?O|F}^R8S6w=`^+DW6`=7Zh542-GP`UJvuUHP=!NoF9K0= zA-WHwm#rLG3$#T|Bpx;KYP2yB79W9-b7PKe+*qvIdULPl;*Co)m=gW*Em&d^^s?LL1Y>D$4CuBwH2^L##?;+on5sA)>k26FTA0Zz^9okNNI zUrgv>hgSVrmmDVBI0eGU+hSjz?qOkziZT*DH!0=GQ~qDG;%a=nfsm`)Y~|`T>T=!+GZ_}@>Q=@6MNsLPJ#Cy%vz7Jc z{hKKvNxXv8xU-8Dt-VZliUh6LICJhx0e%c1)SR#Zhe4`TwQU+ia#w z67s~ei3K5ys*Kb#K_q|{zyEZ7TT6l1nv*#_%B4t)=n`V=UDa1FQiH#!jG1Hfkf(jl zVxruEn#s^$ar<$PKR_O}_*(IX&ci z*a$ZuC!s(87XE<{o1-c4{y50q~{~i-!eRGvM ziR+9ed;y_&i;;nZnv0lgt*d=p|9wbNi+M4PF+jY=>huylR*5RB!-_|B1eA;C5NDo> z<|^jf>Ir}vH#=Y9kh=(bd0u|j=?;fG$9pQhMTyPYl&Q9qwFL~htM?hb^%yveZ~2OqN*4!=QiWRLd>j(fVfI~?)``KajWl8;tIQ61r9(n{5{ zWVt2(OF^PW_P#UWGRN(~BSj_l#wDhxrq$a>5jG~IL<^L5#^Uay%V7S34O ziJaL3wj5rD%8($9i?s5$MU?M-TOJ^LyBp0`mot?J^^MsReDYb z_!G!Z&$iHwzFyt^zSc)lUx!6MljG{+!;)mdph?gONC18f|fnJM)RmPS`{J#&n zF;FPJ;ed}akTA!3ei{oS+j_|S=x`mkPi;8Y9`bgY{aQs!x|+pWkH@tsceTn&A6u@C ztEzB(993RELWavN>CI7rGG4c%TYJsPZ$VK_nf^3XpAi@59bOx&@rN3|$3NRq#ltGu z-3!@LA$A*_cb-u&9kJihEk%6l+;2DvKH5+_q+b`URtgWyzMtoPmX<2sr>i?T5?<)o zjqj>0?AWq5yBWT>#HYTQ znSpgBdmdC?%Jb|MHJp0(fH~A$WqOew_>AynyQ$Z5I4kcx=Pkqj4SX+YUKPLQ)9Acn zFI)N7x65)&vU&n@e6yTnUv2Cv3)b&%$079*IB=D_!@=c*9G64|DR=cY`$Tz>ahL8F z?eBlBj&Xy0o}qN#b7a@jTfTP8Mdz7`C-oHsBl|Yfi;=lngYjtz*VuW(OYL&CxwfgZ zvTvWk{Qe%CiaYOg97}mqBc1o&W@+;Ut~LtD&%G4RWLk>> z@sN~J5vGbl5sN4bG~bmSW;gG@SoLDL{t-D(LYl$-Pr=)kmE4R~MhnTbtya~A#(U4d z?hCcD&OpL>lp(9!=HRzC95qKNB!RHs8T(5~-Rb-`!Mf`5UDQf3V5k5xp6S0nqWXBZ zl@xp-Xxp0EJc|l_VO{_`Ntrj z5{W%i*6@!m?6de&l*@a4kh*s5Czs>msVQRn`M=Y@n8k!5Gp-Dw4j8La9gWq~F9U78 zAH^}%)dUXbRk{~QFX!{lBAzu@DTBf;0UBYeSeib+a41G;x@acb9^QqnO)yn%hlzDt z$`7NIikyg9J5t*)E>+63Dq-FC*T?=`jDGK+cq^9PzfZKC{esBhSaRpNs?%-G+Mb^z z*}UJIO(Z9Bjvdp1JG`cV(sy(OS>Gs#kxN)T*^|}?8u6@4W~$Gdut&!|R3ug_YX7nY zx27K+HvTjjR4c+W8%HST8dl)dZc&{amLwK4<|1U`QK1)EoR!_4y=#ZpSIsFh2HaYt zzVcnyhiJBb#~a49b}g*)v#)C4+7}c%ZHDexDlV?RQie2jyn70#bVRDdb68(C&o;c; zUK;%<#P@IkGM5As&k=5hF61b**%-eR; zGBu;uQbqijmwTsHjvX>{@|b@Hhj|7#Xwbd8HB8^A5w^+75;&_0z~3p4&u70u_GyMx zN0530&p8Hz+e3jEewB9nC@M4NqtD%o-F#bB2894XvtMNr*tqOV2T69&&<#_kvT>5R zChQnCcm|2^1EU;6>Hj})9`AS}QO)@MrlHHwnO^4)NTnkK`C;1n%PsM&VfM;2^ns`~ zQBA=hKZZH)Tg0|Q%jr~~+donnuKp>tpm{iTyikC4F^j&otP=aa#Q2>`$xFqpJ3At} zg(@4?blp59mrK}BpYc4d7O=2|6#FfT<;y}7x`UF;G1h}>R#v|<9swB>L=3(>m%+ua z_6bDrLAoZCoQ}wq1FQTJgz>Qv?kAML+>tjKd)D_!g!&1=AZnfxu)c5?bZ1ym%xDLG zz#Uq=Jj)QB6~oXjLw)bv3BQ0^PxIb%21@G2^3~fNd}ped!?*=Rm-4_IvZMg+=z=^> zS?@bqI#!IB*czL%sOE6B%4Q6@4@^dJzwwJCyZ$4b@473Ey&h&af8T{@?^}r~5Ajf{ zCokeFx#Y5*^*^&2AS5vOCec34EIfxFl>6k}cr+({tN!tTgiS;Ta2`1g{foVBmy_*# zcP#IS$-J`u6<7&q7nAPKNCG+9i^qUBbv%r>f}FYP!2Cm|rO|96q18a`MX=4-md;dGl85jWm(po<4XRLa%v^r*YGfzFpZ(W~D`}k4E3rwo|-?-yiCAs};1cpAI z<*U(;^4gsrlKS3OwwsgqO`AnwpbxIB^Cj+XNc^`d9zWmj20S&H(q^yGnrGGi<92;` zZ#4+qlx8nX1w4kd7OQ$SQWqg+iq$vM^`0%5jdru>eK@Tb?nGBb;}RpZJh~KAQar}> z%>Rs-j`cDA3w|MmY*aQYk`)uH_KpE7L}kb8LU^M%jKxxflsq#Gu17t@S<>VySGek zL(9d$j>!M0DgQ_yOud-Y`~+Dk@KJS+`u}6?&%>c$-~MsDl7u2#6h@*fX>5hDO`9Yd zdz7&bsqDkp8HFfok$veVWXW3gkZoj}itPIs6UM$9%l8^}KhN`ie!t`M{rvI$?>;*2 z7Bkm*p0Dk^F5=uoR6VS#iPmFGA0)aNM0p6ek3LrTHBEN80YZwC6ZODIs}d(%Akxyw zT_yk*ZPe^Jl1_`mRiTtW)T(gw=J%@~a*@}2WwFbQOp$w=sT+?}u^Bt~%6nhNyF9kcxR&}>H)rv%#dxFRqR$Al(odZ!Kh+5;zRk!hpF z(0@Jl%xJCe7FR{nysJu#q2qDAWBHdQEVL zPXrX{JX(C7*A;KDt7x>HSzyJtKl0YYb$X!6ejFDp@;sli@51Ro*Toeb@pVfFf1+}+ zoULt4JF{Cge&@LpsJ#qUI%As|P`i6SAYB*istT~r((~HE!??`=^I8GS+ueA9wvqV- zuIW-w9JFkf_7bRvMr(y%PG~SNT>dI8*MNr% z;V5c}>l=T+K=|;E(|cK=e__v<_L{rc<748FBm&7@SKU5f&Oi3*RK=^Yf(x4{`y;77 z6BL7(nF@&b4Z7-mqyFSt-P*cp%7)uA;oCt!`U-AxG_eu6Z|BbHFhfc*nTHoaOOSHy zvlI^n#BuaPH*N>nOm$0XHRWrGgH_6s&Ebp`0Dk;_2e_^y(iq##YR1xxY2nHdZ;tB7 z(3K}efIqcc-}O7D-9UV@{ZwoFyEdl#?M0^dzDA4xes!kHs@1!Z9`MOK>%`H0wCQ?j z!M+*zsV+o0XvA7ldy16t96Vt=xxPpvp6$+t5QjbC`f?K+|3Mlq$_zwj*txdiCQ0Yt z@|4%j}jU&yG9^!;iUi(9v@zfiCM-#xLhG3toL77Ts+ z0QN6)tzvb(b$J0KZ28(pFCOJhcJnd&gOt22Xuu*4F!LBQIW8H9eGRkFehYGt>SKZo z#IptzYNM@eW2!vWg7jE9iF{G?fGgc9d|HZc!DqD!-iqxH-uhr2KK+K7UlYo=&<#+ z&cl7c%LVMXCbps_^~Sh-E&~?hw{0w@FHuQ|Nb2gC9$0svdIH@I7;S_~<+F&6elOgEI)gRV$h{YL zr?Uhl9n=gMi_aI8p`jJ^`{Iq>if4D$@%iKX|2&39)G@}3wyP>_rM}utL~@i1P=ZWgz(IH8{%#hg@?@hZbDnlWt4{)O3LtES_C02G{9Ku~`d8|3wgB+rNpmz5D?c zKs1TbCnbQxnDA?l8Fc1(poZ4ViLCJH?gme)PgyR6Y{_#E`PD*n37+6BMl!cLlHDmmA$IoSPU#KrS zY4DUHt~`@Q^a@UeF#5+LOe)ypF3FmfdEw@8TV5_SrQJ zKs{(`4Q!}ZW?F1w{*0JqS^_z9kOS!xp>oT2+R);De);Q&aKJY&H*%iRyaX~J96P}P zpz1*GU>q;Q;NXlt2AM&Eb}#siRjJwQaM#*N@RXP~_|gIg-6Or1ZU=KN-3tNPDBZ~^ z)SFcOMv`UMbU=Vq>1*lKmWvn95Dx&NVaz2%V?*i}CS@0wY#(~=emF+20(gPbaY2ML z;3uL+10&LKF4xWM_qFqtfUnU#O5ZCt^P&iCP9*M)gws8rpzHo@2=3BYM^h=(+E@2D z>fRG?#9x@lS%-l>wE*t^W8m)Rj@>>$dEiTmwgBrWQ7nzVz`U_jGP17v9;5|- zQJP32p>$H8!d6wQZM&iA82z??ezd;)edb;-&XHQ<-1nA3Q5v!NhKb`((F28YZ@~*v z(?{iCbw!$Mh^LEBo7-F~uMNn5OfS)W$2RYRlIx%AyZv?;Rc?!ulB43+E8rtsx+7(5 zG%LnQXL7uB3fhg>l~J>*{BECa>I{H~aqqo}d%N$u+IGczz(QTs@ce$8(=%T9=0wOF zP`tJtLvhfcLfv5`pJ5;hWczN&gF67u{h+mwFqQa$cy=#LRqcpKA_}}YjS2nt8+&b8 zS$aFuhKbf#5p>?mmX*Sb=rHiQ0{9xS;A{K|zp4-;;(SC6OI^D@4RKA8mx4{BSk}keA2nb9gK)|vk);l*^-GGb4Iw2R z%$~I+*SV-a@Achj7P@L#ubmw0nG>;|pIWX18s*-8$H!zY4m$WFKd7)Raa)1!zj!hq z1ft1B1@p%vTr^_u6pbw1hPqa39a?!QN!_@lM&Hjp2l#|zXp{QI%3D{0OD@bnIKJ_C zb$5}H2k}||6!+-#zbQ{mc`~7Yic)=*lGRj|{VsR8O=WHtWNc z+V>x2T!dz4@2`RFA#I*hST1{<>{V2yMbdrpq=i36xRbn@kOB{74D11)tv)N+MQa|- z$ussTGzyh;gjZT>SR!}Q`*7zc>wU?lZ|rI|5{p&IE9+zy?or9dTAyS_ zIX#7F7w5O4ghxyV9D1v-`+NTCis+CToek?J^eBtt9lxF1*1^If8t1NT-w)R z2j|xv0>6HGV(XW8OR~faaq%B@>Mjr}R?m5^+Dy4aqwZ~OC)TgYiw=>&LVSRan-e$l z8(!?@L{u@jGMT*&C1t{1K_9had&^TM5kbe(+IPFtJMFLPBVbtr9(<%W4mr>`7?2Mo z6^u8phFZ0wn>s*o$@nNFs>vyvG+VMBbDU_NN=yxGzNU8d_+cUJuRG#^P^JlNe%Mc|q8+**qRZ<;B3|1teSCg-iZ3 znrV2G8hP-1dCu7O5@gHc^I#H?MFvJvsPpUBu-oi(fh74gDtB(aOz`9(wjQujhNuQfC2MQO>r;Y^9o&E6R+9(HeahZcz~!F zD7@6{wPdH!@k?cd#ExY@2EJoej-lx^YhV+c>{17|OVCc^R8ZAo`Lc`l2Nmfj+Xp*l zF^+ztB7J11+3x<_7q{}us+g&c6YFLxN@mh1Zn#X}MG@=aOUA~-@4Vi=K6UrF#0f45 zt}vF;!{$-8Qd|;W1*23tuLmbdHy@3gLhDo}VMA`f8@Fn*t#z4Q|)v#BpPv@{_ zih;v^PHt|)eEw&9%$!fokkWq2wEX%8^O2){uE)sTU$un3B9dQB*CinYU2<~KCCo2J z^+<%22^Woj+Scg%%aL$gw!c1XX5G2(8XS$ApZ{%4sq}XgKizGL!QJWA7E0AYVkJ|y zhSWq$f;J4?M`<@u?)y{oD|HPdp^c=s9-H2z?PsA;3CXHy?5;esPm|cBokSa)&f>Nb z@cP?*%D(E@Q00W7-P7rOd;x6sgOAZ81@-##IO|Wu7{*!16nzbDIpn?WV8X0(z;gmaXLvGx^#d(c*mNRhZTn)c_f4SqpbhD=W+uw(^E1FgKON5Ki@iSwJn}(O$ zZeC91+s|d}jY)1TNBMn+ZN3SIPl`Y-rox}RA|yREW?!CY5)fukO;#1*m>>4eho;YK zL993VaSOl0`%7vSmftJggncGf2~S#Ga#sAh#8*Pn^Sw9F%h(Bb^$Er07hvABvW#*y z&2QY;NDCvPdcw{oRR9>@Auo}jf`)Z%RtR$5`k?B`G#ucVLI1Q=(riCK};t!pG;7h%M+ z8?vT&7aI1$8NIBR##w?HNz=DEA8AULI<=kD+%eAQikg(8dp0fqbGJ^w`1gs$2diR- zj|iCez}_A2+SZA=b_HFPDi*;@iSzf$^64oO#)+Ivy_pb82ud!@Rj?nO9%fN+&7GC4 z;`6NkHZ19-$@^A(MCJ1d{Sk+c8zpN!B$2rceAQIOZrG|clMQ0&s^zD*R{fpk9rt!F z7U}Lpxepb{+D*GyaJ&i%lo)M_o9|_zX9v45GJwT*6csFV2%` zmw_bolU$mHA%<70pgM7yL)s!$;0AD&ooAdyP7S|TC&bRK6b?xbFWNtltb&SK+$|g` zj?4~EDp=Gy?VD2S|2q&7Fr*!-3$f3w8l&!> z@NfY;n}O%9D#YmgT7mL7Y4Zl{a^6n6(u16oh_DQ_Rq@FvpH}Utp{$hnd>k@Dk2-3M zqn$OkeO3#R%?Jf1^)Jsle2w~|l=hzXAfqjvPpZS(9qv0LIFsMC(uSk%Du@t#=zdB2 zj3Kt_Xc2=Skc8DkACQwS=>~G^lA&gS$f=oN1IImNXHAE9?&~(Ej%m|}dYq{SMX0I~ zI|Y3N#h_xu3B22=zZBHs-kDfuH7yxS%u_dp}pJWxw%ERL91r9zPQJ@&^D&bw6HMbe9E1?l`vJ|%TK(! zFEBL0E`AeNb0r`Bl+P!3XYcgD1ffaFCtqj2bMEKF^KMP~{M_ctojBcDxzT4oVvL}9 zRTT(S%xsqP&+N-#i6obMPOr_q&g+}-%a4CkR&$||YnQ$Cxhrg;TzK;Q4;sn-s5BcVyVp&>Y9|e78&B>F>xrzv3n`3>U|=<7h{|2wD9RN zPgGmPDE6YJgj-u#e)|^h=auOB$9JWIRz5?Jsf(3cz0%ctUr9G~U=wMPoVkm8cqO6y zs7Vjq)V9#5dJ??HG#dO1GDKNx1%J#KZ|k`!6*PQqS`%a2eiTL*>l3KxI!&TWxxT)W zfMiGOSZ%dEpK^i48;4U1(xT=R?4pR++LbA~Oz|?=Ylm|Ym{)Sd)kEx(3d4OWwmf#B z9uo`bjJN@_aAbt9(p%gfvHkEr*4+^jRfV%?^$q?&x zG*Qx(KVwxrmV6u4yej9iMvE25jm9&cJS8}n&?hnjaxxIB*?OFIu}9x}l$S}qwKDci z=i;KT`cUfP1ZA+Y5R~X;P@=U{w7bkDt2db%vTNHU2hJ9Ct1ph7pSGTpmxrI}zfl`f zGrU?`Jkn!bVD_mi)<9wi;&#<*9*64w)nAUE-g&1u>KIr079D2bi0E~E=g;uJ3)4kF zfm)b6R+V3ei!EG@-%DY%Bp)^P$(Qnf+v4Uk?1|s*&VGn5nmzxB+c2jeqkY-IPwrgL zqLc4d+=bW24QHM`IRmj42wmo>T{w|aOYGrhU%uZ(54w*;(Ag%wUlF*jgo9cn zgvKd5W_gbt$+FYScd8uMzn^Sx`xc{`h{~k%eC|w_UO4T3B$E)0o&F~@LV@I6UEddB z1Vs|0(hL^lj)jFJhx1!jAo#gsnTE|7PTj0%(1ttR-1%;&P(?t%lCkptyjSKSO-d&F z8z~FpbU8k8lHrOqeM?p0?`IdI)^GKSk2X*Q${p^AmP9OQidAhCNO+O=#8-!$A54v} zq|OQ^y=sdrZ}0NB1r;~X zD!qchPoFmzI$Ktyw8+xy9{4*zh;nr4WL;ZxU>8ERvK5R<&XtyGk4Cu@jc(mYjW@8kixH%X9N_4l6+m=COdU)`yaZ0 zuH5}pQso~pXx=vI`JgF3Ypstojy35~6IJy_>22>AZlu7$SnL2}(cxe${sc=o=U*9! zZh%X%k{w=i3ZITRCQIRQ9-#|F{v+Yb7z@hsed}^85kGwwmTbnd64)ZCW}Z#g93~fS zqrvC?pT$PsEMs3WK5pusL6(=V7VeUfd=ybM%YJS`BiSyrKhPMz8=|}uyFb>46U_7} zCz{bm$_w-*ET>QmZi~ds)hX;O3yb$ejC`(D*y}E-Y|tDUjwNsnd&*r*4MU5~onV0F z-;~PBS>8;u-HjX0TAuA$^c3rr-k|$-X~W$cZsGC4z_Zqfvvg#;I^)YH`1k5}`{pse zE#@6>_fFY`8O~fVT&dg}4&$DnT?&i|T4v~}zT)d?!dXD~0=Giru9zy35AZV@+&7=4 zZrKMhj0vLQ)vNQK2)|1$ny5{h)i~v`^D_2v_Le~^C`imz0G>x0^F#D9=@gp9#=#3y zgqM<-$ARR^;CT5uok#Q=2wsJl62+r_*FPi0>Ajn5X&$%eFdyb@69B6j49g(E2hUbM;4EGyUQ%|uA= zRD0X((4nG_Oeo+o#ZT)f%1r#4?0(UD6(L*Qn$s2bv3UNmbNlKcO~-jNb{Ss$2?X!N zSoc?bFS2R*S~%)wdEYeG4cWucDa0820*zh!1+AcJIb1=TgTfqn{(~_kYR!=e(j{3* z^uJ+(*}`!vn9BL4ljD@K!dhiVq&GN3<_Q$eSx=^g{UK*oFwzpiNCQ)5Uwb?IEHedH zI4X#%$%K7F=j}&$iPZ=u&B%C(`QER9;M)NEnOtW5m2J1_i77D;RI^>2)UOh=*g{LV+ zb;0AZ%bQg;{xT|*%-BtzdS$VT36T{J)kWc(zI;{THxLOOt&mGyJ|DRCGNslw0cPB% ziudM6Yv_RfiW7Lq-$j?d7XD^Gk%oPLxZg;FmgXwU5d- z)}c86j#MsMC^=&zU0wujpeFVL zh32vAep$-c`kL5vx`fzu)t}6;yo#TEX&=SfT$yZ4dPl3AZdRYG`M#`W@--p0Srgy2 zB;UE{?t3VL#MZ2zS*er1bhmT*EHWC!n(9tNg^H}$clT_EB!*jV-Zs~up(Dy(SNc|Ak(Tp z+*C-X7}Gw|WvdjWSH9MBmr;!qTP0m>@^!5{f4?psA7R<&S$AGUocKi4$0W}{DFRf2 z{_Zo!Miq?W{K5;XSWpQ;yH48scE);ND>m6eOwGOh)7wcB$}>rNPj-5cdQBNyua;=c z5S(hna@_6egrtEuvP(lOzzm|`HuunY$QXSm@7?w%d{N2rjbF2G=C1eEI?nRqu6 z(m2Fja#SXncY(&HJ*ckwIDGm2mz~*V)#U1e4>hxLba<~oWd4@wrz5U0ZNyYf_dPT3 zDz=xoRlX=CXVceukC+z-o4cJR+tr55skKAS4?!^q0DLEioG{=2u$tNz+e}0h^UUrO zAR*%PS(P1-W;`-U-#CI~1!)rs#Vtnw8n*aRHY)_7^_O@Z{!6^>_fwgd)&cYS{p@~4 zGeL^?M_gjE{4KE!R;cUD@1hgTuo(ct#NnqzA&l?7P+mT3TTFLL9k7G1wa+iIxh}~d zM=ni%M%R}qZ3ziRV;a_YuuO}6BqBEDHQuPNQh2gSbiF!SPAx@stpZFEGT)osJ79WI zMCbUf8nDk67*$_4_hum6SDrr^Zs%>UC6-d={@m^3 zj)_UjJ;wdi5A#OCEBMP~|wfj{(hp%emib>}$oTP^aZmdMR zMMqP2?f9>>{s;4KVYQ2%ajB7VSq?@$;$Vm{f*~U3v|k?rh(>*nQL5^w4TZ+sy|1=$ zTOgZMICU2rTv{wP2+Q*@#_}}>xhpQk{X0W0@?hGyAb94nCGYz3Ry~8(t0wqqG`RCZ zaPL}TU%9z+3FH4wJy`Urw0FPe{I?>62M?Vo?&hY`yL+^cH`7Y4#ww`yrRqZcPCCEY z21CDn+5LElpMm0nt5)%|vsmSqc;-I`D_Rece&Vj;h# z)4Y}if)Ym_a(bm3bboHV)_af_r$=--!Jl|Y<`KQEK+LtX=F_oYLzI}4;Rt#&sRc_%yI*xw|lv;7lo^R+twEM=;trJX(f;Q@YIg>JLB(- zjjzOdA=;`x?SDs3rv?}yr{P87VQtBl{R<1~6DnM$Xs2N6c;JFNJKe`9rNnxOrAk2B z;BOFi4ZY5lR}OTD|8qImAR9G!(~NqYi*!{ZtlV3)IRmTRx1MP&NkoPZ2_Cz1GZE<6~-*vzQS8aHD`QbAHLnR^2eZ&1%j0LbfdsEG#&@OztKL;?uW>9wo7_ zGZ*c>Z~VWig&yb1Z8YUD=l|_gaQr_`1;6OmN5E8AKP)q;Jk~5XxBO9FBSAd702clY zC4Ve6>_&`d0$M;z-P*dsU}`6=*WY_E+gdh47_rj6dt6M zvMyeV`Dssg0k7f>q$jx-WM#0+hL(q!GtN`87Shnam z@Mzr|0|0}7N!!ABSS#dNLyou>MB07%eAgr^|jkt^#Qrz!bgT>^-eI>4z`WB^B6abD9 zwBfYlYb_22SrFcI9(H*JS^FBDfn|j*6=Lj4+EV=q&{0of7so3^`G5+)Y_P*26B%fdYh{tX43yykhteU@UVvOqD}5L3 zbw_3joOLeI4GJcj)Hfk`m10zpgtjQY;~WM@ew#;Iew)2@)GW`Lrr2@eQy4xivNwmr zMBE-eXWRCA)=p{1JAmlCmh`?Dqummszv(@hBG9Y0teAthYgRMgk0h%~K^giDp6q|9 zNHfF^Pn={RboV(sIXK!9oc%49s+&UCr-VHBii?~pZ)CBBO0NHqHVx*9Vvq40TMEA4 zvij_jqUHDc&XYQA|DOy49M7>KF6MC4ShsycOrFU{LY!hIYO!A!SIqnA?cVyt?`=8n zA(5<=Mo+ih>XZuj9r=a^iX<2UY#VT|I^9&b`KUUDrbC?~Fdf9xgPdmmaX-dNA?Rws z?80z4zsV`n#~#83W*zE;EYh?)ueG}(+z+=xPY~5L7U^}rKyo=L)++f@v-+9we^tc) zAMOAu0^O?ebEz>-{?qX+YG*QIQ8hktJ=~a+=w2zf$We0ie$ZBg{sO!N5CQ~Kp&{wm z7)uwLFF5`q+P2K-(#8F4v&q+32~6S^GOagNX|wuHJz0sHGpz3cbo~DZ9zE$?b0F+F zI%+>}*putQ+CX#0kUKA%3M6X2YELW=Cx|!KKGp85DoYFHVnPhq!MRU`oTrJ z1uQwy>5?e@%%w=z0KqhH={6#<=NzjO`8y**8O_F^?r5ak?t)aN339 z!hJL>27-*3{s%Ac=VE?_=JE5H>V27&@{8BCei^&PgZnMX8->3Oi*Y>`$kY26VOWcMB+=6m$-$?plyFnJD6 z{^K|6VFpeSxfgjdkm>|f>on! z*R}mVtaOmK{H`fqMM_+GZL(?=IZ0szy0R5{z?0uB>!f53kgjtfM13IrfghuuQfQ`v zLw+jGkG>$XH>l7Z}I zul?o8ngKRR@{P~3{n;LI<}dZt2Csypz8`R9nZx$&tedyRp2=7?Eq%P@n_S+tkNTB6 zBPSFw%`>!o9hQ>w?u*&}_)=3iHiz#eocYtn>cln%x}nwr2 z8c+%$x0o9(HD5s%pK=Cj@IDO4DhJAi24K+F{D`6e6B(MHm+{3ij=qSJf@{6O9kE>? z915$nKg2noJhLsvuyswB?0K}etr9spw|Zt`qsk&R2;ZY70)8iwn37mW+)`IgWfAhg z*Dh+9WFl*SR|;FIn(rsg;*2>1leEMjCn3!R$RuuWtriZ^u!V$>8;Be+?1R0e{K)?h zcEiIQiH4XQ-A0itx_7~VIYTW7k5TQ?ozo2Q1HqD2j;-1LJaO{nTqTTEDHKk7YH^f; z@f8sX8&i33B&~r*Yem51PVr*f@@Ak1(}d5pa9VPhTay3(6moTW(xaCj#B}p{O|1w` zgA4b{8h7c%Yid6-8aeok{509#u`&6+FQE>flu#ruu-`TUA`U$y$+99J+dH9hxQoh? z7YE!$$R1CsW#*mb;lg;?Kc-4x=kzi#Re+qt0YSc(NG(s-aRuby)h3O&1>r?U2OsK3 zTo5s3`j}hWE1)#7bkQutsPtog#NAW9@*z4M!Fw(yw;#hIjagM8S00oEO97iC^KW5F zmSz2#(qJ8Tad+1dshD9uC5wmiWhfbjep^!EFtjr_zkSKN6Yje^Hnq)!J)uy(2YlC* zu#nqiF&o8#yQy@5A|x)3-NV;X1*0cSloG@JtcsTL!rIGo1y?yQ7>0?f^$_0vXK_SN zKKE`Mml$=La>=+|jz*lsCC%t5{I`oz#8M!U4n+^#CZ5fryGA^p#r*~F2j6%4Q0D_> zNi!fz&Mj}Edz>mQRsRoZ$+6w%+R5Q#-6+>YJ_V(nqg#V7_qM+V)z$R#_L{|4Yx~Mw z=-RYZpPhyH?tS&St`o0RyJosmt7Mo6 zF)cDS_a~{La=ZGfyomQ+)b1@Rsb-**A>jvwQ{k%s{vWniP3d3N34^DEfFDv(o-MVT%oJ z$?X_T7vDOPy_NK3Lm@p3>~8vW;AX(=^m7prIz z+VAwxpfRDWSshoD9~#EUy|MD_f0&URN#I8U@=(;~4R*&?71(`{U0~tnTpw0E@h_`5 zEc}O6jQ+t;l1*{))i+#?0fw?<1L`ZBS~%L_Z2=BK%Mr=)qX>4jF|kM7aW`W;QKc{M z05Mjh4FzEhkTm*LY-eqb^iUk%dXI-Y?T!hW%k6Qkvk1-2&AtTX61!L}`epQEnX)NW zalME4IuKk<{|K%@yLQ_9wg)m3Ylx=#&Jk|<*hbZ4rC-f9=H*mc!9pgsr?>mVq$1vPp z6LLW=Qw@P*ucVKpSt(=NNd3WUgZ)^|+&;ltelvVK@_3%l=UpJ*Ug!2L3#k)7Kn);G6II70vJXI@DBrr=GSi}(XfSsg%=fh#|nE)hDD?Ty#2yM(bKc`oPM5|N%blGI@%Yp5O zYGx;4Gev&GUR;Y-N3HK1w-{2eGiv-2a=NKUJp%7Jy~Q1244)MoM{T5xU===#Gb7oT z(HtYA6{I@M8%50-=UAsV8{UlgNvqa1FA)Rw_1X$wc_QE)-vRHi`^!5j0qRN zX!dS&fSMD8HHW1NA%twDs2T3Bs+=9?%W(@lu#Ef0PBe zvZYXIQLG9Ih=;hwUs1LW(sfd+vH4_I*nHvR_6BtH^kK(8qefSC?B7vy^+Pmp?lYM_ z1*apP9cp_3IdU8SCFG~KFzo|Dqxn%pk|_Nr;9$5?nfcgUFruY6x%rdgBQ?RheUFu8 zOmG||Mj5h3pB{l=3f2&6!<@>-ltcwo&c!gJ-8+;r zwx#qeBm(#N$cK-l!lOMbsz=<1fCcI?mpgH%u`CtRIoIl)FZi|}x4|4UOi_9E@hduT{GMy9(@${U0@wc2H2}*(C5;XYNzTCbv~UogiD+}NL>-ih z8MRdCsimTDgun#pmj`0_Jh?6QGJPYzs?zDPbjZaMsM|(2w`Pi#ey+TfQzHi7+%zst z*G#dtA5|3nQhQbC+e(_U!5M(O3Kli~5b{iqXXstPGx>$sLuCDfmP9yl$N1RMf%Th7 zA%uK@yhPjKKRsSe$}0Y(l@QQgOn{kmYlW({%+$&sIuv59cI&zx4YLA|Q|t5*c1hC@ ze>nSMvV(2FC0vef@daA9SsPU<8~ zTCE9b`pvbWc9}YoVJWxJaa8kia7J>Ng^tKUiD<_pTg6JA_3ZwZ71|?yzk;IO=Q+Fb z6>6K@y}BdM3KA+)h49u5Q~wawy!EW`08kN*P~XN0O|H~tBxTwb(D)8 z6`XSEki^-@`2i2z_|RKM9$d7mPV-u5Kc@2f!(?D{S}YsmWnTO%6W0Q|}^ zOqVWKN&yr13qs*saluJG4L|2Y9_iY|i8J??Nqqk4Rf_kB@ieU6TO9TVBB z+=1$KbM%hlP?Mjz1=n0(T;Ut#Gt80%_V}gMgItFd#LL7;@ki>X@}ITlruO?nfTgq* z?J3L`ZALogC?v)~YmMM3m8L>xXvB>+K?$7bS3{@%!g%zDZdLJ{e!mr3iem$F(BsiNrs zaAhIkQ-7FB!fcT4$UBM7P$kZ?V>lUQSzwPFQ4=T?)s1f79Ad6yU78#}bek-1?5ESL z>^a34i+ZD*zvLbeVjub6lOz*NlCQ!Clw}KcRD{HIsk!?$AoZN)y%mHoNle@UsoxO) zO*J*d#sxH$1F_~ii)?PWvmll7qWy41+yy^{cKTDxyIG`=#R0|`JYGGV(cYH&v z+M!39`le{Y-((F`5XVL-mS+F@R@ez?o zGgv(xAC|TDZsR2t`us;wMl}UdmEAjiDtklf9DTSY^tEq?Q^kZ$#c_&61U~fr-iCl9p_RxID_KF|P>0a{|!yzT^1&D??i^ zdM`NYutY7EoZnp0dOR2%0XOab6{qC7f5p=sZiB!61($+5t+~p188TbTgR9AceJdX2 zi`^gra>7U@C%6gIK$KB~uOf8C1#+s557-&8o3;Qn+PitB3FI#^{Gd%FN8OT*Z1BFR zub?Cs{-+EMgkAmaXL=wMS)_rJodR*nOs0g2A0S4NN^%(2tcXK1Xc)?Vla0`*DV&&PiiU1eFRu6SuXzEywX_l z3>e(Pp|urq$y9?|1g!iX-iT zrjPfOp%;L=p_7pVKIH^jOz_yZFlD(4A47UtB zmroUX&I2K!Emn}prpE*NZNc@)?6J=y@SfcHQ7+Y_?$v78!6Z^ zIOAhT@8G6bi9Nrt0QHH)&Xk9Qf~hn?*-6eXq?LGD{4_&9ep3;|rml%{%7u%}3BDyj z&#aHR(0rEvEZIN(Oy>c{(@_uVO!ike@pdptK`!tfj&uBM7Sls3*BM74uT60bfy+tG zbHjHJvvrf4x~%pqh`^;@-aj38Q-=Op$_4H3uyGXud86-Y_u5mcM#f1N%Hr*F^=0PF z#ddc+R#zNW`*vXkL3o3Cn1)9h{PB|E{-oUcN2d_Vu7d|w$sGz6MhpT6ZxrpMg@M1oEtnI;#tVU z8Hg109Q%|1_<+iG)oww>hdZ_FQbqJ~ql@j>G^^40B$o=JN7l6$L>5f|x@_!J#vP;} z81%QH{Jvf7-$j&PRjJ&`?8>G^q#|nnf3+q{tpL-%6=1O_Q8PL5eT*vPhLl}~GQhHt zBjZ)6wI{a1q_b?e#-(g8h2O5$5it9d&J{JG$TJpfoW-bjKrGCi58@vUp`Pt7H#w*= z;b2_^vmva@U!XnZku4#HJ3kq9jW?Vy^!2UOXBN~tR~3TSH7UE{@bf z#s)Y;HEqxQPdgP1v1U}Eo|UB@r&dAEmmRi5vumPw)a zGU3z&NhpG>B4i;fH}qfv*dVs{Y77D&rhUi$XjsQYsTx)T z2UWwe3VX2XcNO^8=QG2b|4xArDNK1_5*nyief$K>M3EO_Hhee)PIP`_0-s_3P>Aff z={H;9_Cs&PTZ&#C=3pNNhY$I0Dx?&x$s47*z~!kkfZfYC=Z}-e2PpS}i|v-Nn=zHf z(7Co>w8_#OD;cz;7Gf&z^)##?Ata~#N~(lOVy#~L^j#nw4q3YA=<U=K;k7uv9&J=lMe%GQ23DQel4k>jr@d!rYL$4BNHc3-mb3 zgZ0@Ir?}!IV;xJnBuRaOVjev{Twk*Z0l5MQY4q!|l+iImgL-m2eztHlP_+>AAnPUiUn-5py5C$%y7>)7DA2moSO$IJfGRb%67Eq^veoOF% zI%p*i*e@;JF=)vT33e)M(s%_ToDJ*GEj{G^xlXIW*?H;G-!_y7tkyV)#6c)6U4uEr z=zhBh5ZHM&4g~fqkGifm$H`CKKvet|-kSS3H&s^Vu7p`FR{o})s+ zO7WH$z%;6@bC5` zjmMcDKjp#%%jd0N>G7Gl^D_-L|6#D6oYAW=eh{Gd9gSC6Lv7$K0Dp^i-}Dy67dVCl z?S}l^Hyb@$rT~&=*f6g?ENR;V{b&3dyrnF$*;NqhD{&KLOA>pe<)f}o)rofc%!)s(zE^V z#ARk8N1lgG^>Q`Tgy}>C4tS?2+y-YbG%=oPJ;-m?2t z2?F^3pT3i~ZW?~~3^bu`YtJ}>SqE<=F#cxqeMwf&X7)jVfR$SjTqGl+88uXgG)r%p>`m*vYNzo!8+^1SC0QU@?fopVEU#iRmfMINuXq4+e66} zjvrrEe4!YH+h(S=mce98SP!;YiJa=ZD+l|fU(wXC86ASkp)9c+objL~7%$G*J5{qy*N09u8EaE>^K3Dhad)6?bNh?Zw-O^PJTYM*U z5D8ssGrQ!Lss$lnYSe%wfL_h_NbNj$Vgon(>;fG#;U+Zv1Q6Zt7P}sH-*JP>UJiEL(FJX6ysMezxTYE|soD~gLklL3( zoIj8vOyd5WbL^>;e{~4M#^yjDS>}R&wL47PD;SYFW7`jAEb2DhE7L{g@pk7bHoSbS z5FaXFHqtImOp~|90NzQ(o^^)DfkJUP#@c#0G9CJ`q_ZcgH@3TF5TO)>z4F-<51kc2 zBoOyp1oJ9Bgxej79y9N^>QJF)qNM-a({R?N|n!WNLqE)x~ zY3HW9qr-dkq)WniRd$YV6wa9c*0V0|q7|nK-A+PQiZVw|KiUE50S#AAKYb|LQ|l5B zg-aS^C|U+{D`ZFvm#u$BVAe}@Y=#YYI^!1*U*>o#qrOobgf5n%vJOd0&>l5+9T$Oc zUpK5KBt3MW>#AP$?3LTocKYOV&qN1HE}}RwF{j4%t^E33v{&auHX>{#St|9w!O_P$ z?_T+P^rJ#_!O>@?9(}8n@UEiG6ql!7GH%Of6@Feo-!9Jt`2beLLxO7==TF%6adg2c zpL0>h>}dbZ7ep%puuGZk>xEuI7Qa!`G?>Stv6Sn$x!>WV*!yLq=YuX3k?{eKd0NE@ z+HsEbY#U4Yc}Z~U+u8kwic>hJ*na~*`!jw%qc_!9Zr)w#6O@>Spo7C)B}a!b4aC~Pw#S65;_befP_KM9!{6PN0wLmey<(;47BMdLAGF* zCn(yPjtBKLJ-Td8p=8kQ+g#Nb&XH1u*Q_7GGSV>bXPePYqtJhJ51oxWa^q47W0s=q zgjTBuPBPzP1>GJYzZE_Idzj+CTrn@+VU)`30p)=@P1Et*9t6-*^pCi3naoGnee&kt z0tf^77*6Yh0^n{YWV#~;?|N|9sNdok)%Wp#YW9a{ zeLpXgU*b_7NR8dA$Eh&8uu&J#W)>~lUllEEo(b- zIQ=zM(LVI)RCwi4bWlDvAdmdMMnUa)#s*#8calCv>k88^B8sQZIy| z6s4A?4j%*Sd|1Olnbn;n*Cb~Iz8?qT(Dg4!8?F|kyP2_5Mra!U5_>Ht@ylN%iW z;p=Y|&isRGpygU(&tg96+fvFqeEItW9@YIDA#!dP8F0zbOa7`FyeYcMj{dsbqP=qR zW?>=TssU|06h*EAg(E2-V12=IhyCcq_Ehwb3&Q{`r+X0ubtA&fU9(E*GIx)cFyf~b z^qXOPcf-ACuUm5PfCuj<^c?}*YGm|@t2J?FQ5L+bEZ!m_qSf!j6Vg0~C~l`m;O}TX zO=jhO%Z9+{+dI8V7B#3)dm2Dp4?|8}496iSyj#?=O7-oRBqGdZ!7TVmtPn7AdLz{M z|1kCD;ZXK(*m&zBsbt9{yBWL2R@p}>#-I@@#1Il?H@2~+tl4)mD3r9=v&=9fYu3q< zCHp$|Z7>+d@Af?3_kDkVJ35Yzxj)x-UgvpU46PFHPi_kv7%hs4ahmlBbYYSmEO z2|06bnggHVM<|DkVemDZB3sJ6&|QHXxCPI%9r&LqHwK-7zYSlVMA2`85xj{x=0 zuijnol_Fdh;d>)0^Tmo)i244%wL)OG5>RTghx2gEVvPIKLioplfA~jsArIZPhWzl1 zDOYt8cUqq|pI%3L=JH-Z;DlB6yNpa$7D+y>4yf6D9wJ*Wcw@+UIPh@|0X)eH`crb> zn^ol)d(eL^#(A|Y_o_Y6`(U3cwv{3507~g;LtX9?{p-H!-y8t~!6V5HLQ3?&4BP1? zs9Uui5Y{8NONMzKS0@VisF=mEfRq9(xr0*RpGDK-O+R zR{{zNeE7AkRcTsSvCbk58KM6RSduB%oXdyYwrO#7xJDvFmar3|W~)hqeM*i1&}R7U zVRc)4+Jz_pfu5Aj-_3N1SvxP1`S)>kll5K%SvWhQW5I3Y@v@Lg6VFDFC?LTKU16}~ zVbk;)T|AW-%r9dA6ow$tt4IKDu)nfU24wy!1V zCaa2z5)LZOg)|@s;OVNgXc`E?;iO?4U43{t2ZTE${1 zugxB|OME}z{>Vk|TJspd_5aA(53x9KW>2ed^*{J0br>nZSB=*5>IKKFI=NHcAQ(J| zzP~0PR^TlxmTf-{9w_D%NK!>a%wpDZ@|Y~7@8tVlK6UzR{ys~R&Bk*|HHnx^2^gWv z?LL}*-~$M=UXJSQLB5_o&QAAbz?O{ZtX%mu;Xt*>u6t+b;9x~M{ysPx z2eZMdh+df#1?a4X?!P4?t6e@V*-v9Jo&k)?5pWf^PAFay44JKaDBJ{aiGk2y@M9N3 zi2#31u)BCIEs^z%xEqpiE24h-y6tqR>#8e9G{!ha_N-6kw{#x0XS7SmZ0r|XlYKO66!?`UMZ|Z8r|5@ze3o;Ny2koJ z_ET^{ybjO!!3?nh&I4A0E4*f95LP(bansq#6Y@=wo<;*?+e;=lq=thR7n`l!`R=a~um(SjX*$yu~fT%T{yP^^SQRtDa%t zLRVGmXZfhI%`j8t=}9owE%8-HSnzz()Z=@3~F;5NoRJq zLm1>Ex~%Ct3!?*sy_MDPYyLzdf_pyX&a9w3q9Y9`JnZz9jL{rCeY>bg*fhs+xWZN4 z9pHMrP}WxPrj?_urljc)*0+ynNAraqnM+>8Dst9nk=aiFDp75wZ1jE>C3UpZ+yz5j zVy3xxQOKhO_(@f#zQci~Lr`I)TACv(H{$amH<~4o8rOj18d;aej~~=#xV;uziXFmd z!v~A`c0Y(X8}x;OH%cFbVG$ng~!okbb<}i&PP+-U@7U>XROPYubbS+68%|mxRtE* zF_WPkJLOTYm?eRAe`z|)5j)G5wX~-jfCS;SSg*tDZ$i(fH!^%$X9or804+I!x&5n= zz)=5&+JPJdHU9KaAfb=~U4-qo1}+m05Bc1g{SOkGha*k}9LQz;@n3_ht&Y*+V|`|G zB3FH4SCfqh5o~3Ahs)$qwZj7o9N*EF?I?)2|6^6C2y@qKXJzddBDlcW%=qA~5#e2B zvh~FQuKeKOt+*E02ig{h&`7Hz5RP)Q-PaB#!tlzYOL{dvACT4CO`{YG`j^$Eak>*V zG5b(%iEDh1H#O6Z-W1@k&1)576sd*lQlWILBE4{i)cnBx{L$G|i!R(&j}Bf3TAio7 zH$F)8gdh-@h>|A2cP zt#BMgS1JON8+OMG6yo5X2#%TSu3j>y;zph6bIzXfRX;D{>@8@2{S!hcdmkKBesQtdq4Kx?(*f=qjs{1aQSTBcZ%7vU~cV~}V z1NNz!W*eu?vy*=2`qP5N>AfK{`FfwHx-PPpjee+awqEv0lTkWGMNZ!B>RdQ~rEqQO zQP~UTSuOMZyd~1=KoR*_QBl5(5cKSYz(KaGK@_EP&3$~2&Wx8QQdF@WsuS_CE4+s@ z`5w*~s&Dl{nFS6OD_uQ(>4bPW3@Y-&cJV(Qt5gs^x2wCk?))D zBHPiq7gL?xcJ;9iSdV@a>@$>W)0NQ{HTuRX@H<~yI?cCxn>r6yd&gE{Ix@w9ZCtkl zIqOA)O=08C_`Mn5y^pL4*xqE`&NYSCZ1=4SljvVpIiv{X%KQEz7zD_@Z#Z_kktz3~ zKi<8gC*ZAXltNL)=<@Ti@KM(mo)*N_{?V;Z@++6r4%W>Th)4StM-s6I+baqCEK(tu zyh!q9TKp;Zw&#uZrhHoQ{OdlzfClrpXZIBy5tbdcckoG?uHA5JkwNmjt0p9GxDQv7~$;S zlvrcB)kux`vk(04-Ej)ZGKfKs_C@Uf))j3T>dI}h zzS*_$&?h_nfG%+cIJp-wDo>;xu7b07*<)otNUBug4;N-f)dJ{|NtgX&;Ek7;N4F=u zYL4EFQUkWwc<)bJe|*M*i1|hiZrL0UTe;VlAXend82a#W$eAkD~nDbEuE z_gr}Y)X!%q3+{XtQTst#WeyzipOmmzTJ>J2RaA^~G`_^=+5<(xx?r^BRRLzqIT4eR zqc}eSvAb5UyYmS=Ra&!Xnxgdpp7vF7^7W;F*-Ql>LUo`kF+Nl$^Gk`)`3Z?j(~jMt zF9skmO;^o?FcwMq$-l(FKrho@#Sj?&U$3YxmE{`r zo81Uf{OOitn^+etxovif^;U!qlSqj1-+Ex~F`mM3;x__20s=1Ej%pw-ZKi)xM14TN zIltt@{Iu~VZ@Xw1lakd^fgTFmKlMlJHU?o!|?xbaJjLum8;^T^jT$-^|*rcA)>fC%d0nqf9gJxo7`=-p_& z>{+?IirKSRw})d;)KR{J&-g{2wM8ckO?h)aA4WavcR_(eI_4+`Kk^2i#t%_7DBL^b z-O=K0t(RydV0SLc7eNDiYG+l~me}rfs3mR}W+=XFSTgXcH51{MNMM9|ji@qIj__Qp zq50o0we62R+&`KgP>4{M-L75pCw(%SBc>7$W=H=RSva;HP-DDma`DTh?YJc~vXY8= za|A-bhX)ul4$i_Io5lQHN-gAAhh<@!BCB~gU0+L2(!Y-Ha)^Xn{ZTKW(m}3Q zf;_SVtpu?g$ZUqpu(X63B_tdKGd>y-(=R#r8S9TFe^Fi64;)R6RQL24I?da`_<=j) zF~i@0eG{zN$X=J%YGKW$cE;n2N2+8+(gJL+Rqx3)dc)V|Ol7Zk-OzH4R(Rj{;%o~- zXCf_ciJG;iaI-qWC<NJBEK9dqfl;x~ z13Di$&z@Pm;_MBU+liQ&w$9$y&2F>eHatG_SUH8Z%RfZydP{*SM-^$X5|c0w_G230Mfw$TP*zWXiOqML|)+^FSYuq zFbBCJjzFLuQV|2)YkZ_})=3Yrneg9SsJTYYMNE@-4xTSWMr5g3o*uL z6l=ceTCZOj6;0E19gv`=PnWhe{|^FC`2})+ym_&JTr#)C?2LSC{OM^2unly)Yx5sw z%=a`30eEe&XY__V4SNMeq$>A>Y$v!^gHh0o9jBpM@?E|KNCD<|E?-O08MVD3DVH7r zk8(xSp%%z7ESRyCOS3WV!iJ~2gA2q|D)2CQw3cr-u3Kw>ZH7x_(Rs3e?ihNedo9#C*>87w`p&A&%fwo1s{cb6Z{Yn&yO}t?wpcqit*`jybnCWyf^0G3 zTAi#x*ua-T1`)O{pX@N3`4^^~s$dSDvX$nCJc*MgOoltV=Uhir-J^#ODCPq7$c|g|n?jx96Lvm3 z=o4sIwk;O$EaD)(U03lfE?;PB2xm$GDAH7du3W>+LB-GA( z1ya56z+5ba&S*GiG{34fpwWPZa{V4GpvZwycQ9@CaATEEXYQo=GszrRp-1bihvAvN z7=uGQ)loK$WreQVU=SJ7j;-5@I+NEg=Bi&^|H3qx?DE#*7@4&CA2P`v_~=%b>yt_G zf1yb&`h+i3s(UoOSBSOHm8Hb7D%AyG0t#;gNti88E23n3Y!W!Vkf{)}zrP;;a#BTP z)#qu=M7s;be<+15_&|hEZ9d$l^X9aa?tl2$Hp(ov46+)8E=+ENHU9*cs6kMAK0D~@ zG3I~+z0r*E?XiH`)Du0j2`9^-TG($o^&v)YimEohbo)#T7o0N*@H_=}Jn*%t;09p- zy-@(_%(8fc;Dk`J;(`yBZPm)ek`YzB^!2bZ`y>DLH+(agr&_O{-3T9!+U@q;aYa!x zA`6|i8bLrSZY~t+v5q&lJ3U@82u-K?AFJJMjUr1G5&t%SV2VL(2{OiU3Pd-H%Va z<{YrH6F~lAkHOz}xyJ3dtMW#)8i1DmotnJA>hNCPR4EisAUz|yJN{5K2cjf@C^&-k z#Amxa9m_)aFf(Ex7c;|H1d=JqVv~Ck>timM+p$n@|5GoJuA>rvV9x9vr+CXZ%BzZ8 zi)-iG5v8Z(q`G(eNG%9zPK6&%E7WQ67W1K1LiItE_TO_NFbW1nO)8dh2(z}VTzMs9 z5US{eb*Y`aAE2smcJHdiOAC0wlJ9sWV=79o{;O1!lfcHc3OPi>q;dyI<7wl0O8E>? z1hx^;9n5s3x`5D|$~KSd=I4GhT-f}XHr`crB%1!ZK97kwex-l!BZaq>F{xYx{6wtz z9GB*lON>^j^x`%baf7R)u=oMWy9(LH`0*I&$DU0B{BRZ-_Ytt}bMR@U-A=0Rv2v zNzsUHOoRCUR@#rgRnJ`HZJ~r^*(k@}GnO z{bV?K_<}^NM?CA;S0$ruRX?H5sj; zrQIvziCNob&moVEV==Z*&yAbUL{Pm5_S7vXW|%o59l;4tM1n*4eJ;{b-5V%p_ySSMY;TPKehNP9nI~?Lrdx z&Te$!(QfQf@ry?XY6o`Wnvv4#p#zB{F};MAm|Q5m&?-&$PMV|?x@P?)i;ZloPOu~9 z;r+i?LL592!5t(Re|Nn+2nC!OeSy6(MXi#ygt40|TOmOKbL#fKahRt0Sa*R}b&hel zU6H4)q`3Gq(plqfig+(>^H7S-8k(7hJPj&W`pPtCn>DUP`HHv4Xq%wBqdFKd zg`O3FY$3t|Tz}jQl2mR0)D}dPH>$q*z8@&X+oBy8nSgEAd+qm$Z@~aw<^5)%i{X9o z>dq@bO<5Avm)>gwuX+Y)hn%Zih8|KF)}8GH((i1p|CJ~HpluovyBIIG^~FQ~BL8wG zKq=Q7*G0gEel+F1f&PtN=8||QEQY4WzuOxA)jL=_K=u@48W1`JnFZdWxd))A#tV$s zG&|$GCo__?9Hd=&$KN-O0NGy7d|j|Gc7hK_`(j9_4R^K?)Njz@79Pvx)vR!dCT#t% z#~Iabbed27qYCtF6NrsL5p&BaYI8A<<}&PO5je0*P3q_N`0l6Go9?3))P67bB22I8 zZrCH&`*GZMP`0j(wu@lnRr}_3A}Nqg!D1&xK+=ouBd`O%(|=_UTJs8;mIr;OEB2aH zQ+-;K&LKDwpnhl>voMxY`=fd(Pxu-zfpDSI?}qaEB^$kcx1NE@&+_>~_0e~qiHEyQ zH4%07R+`~8T`Qi33Ew&w)NPGb^X*r@u5G1rlmLVsY(Gy-cC1UAqP~GQ<`^K>A!#YN zL%p=k9u~U%eLSm(wMuVGTbsepAAX3Hnoav!VkYfl8?>teGB3z=(^XW*I@kcCY+?m!V` zz)M#UPY{J%AJa;eiytfhc%-msKepAsx;&gdK6oA|)!`e=xk}?H_x$^UiY=l-Cr5ts zvKC61@4chf+yn~!gV4MsISbr75`wTD9WM|u%yqkr<4>JOk0-@mY^k@&()_W<_a zqFUkT-ye`rNh&}!r9`s5656zS1xr|i@DRKA>ezSlBh*DPz4C^NtBZuWvT)p z(Jh19_d>N#WhkORTVTUUEEM6rzb%%(|FMHHO?oXq<#^Gi#l3-le6`a@DbW7+cU#Ru zZ9pAi?D|EHNjWC_)gZ8m-V+h1!X}(7G7sIbUrQTsUosv4OT(P)My|Hf^5;pFQrNOSq`USJFxMJL6gfYjG7ZnLA^1OH-Xyc9ev~ zumP%GSkTQ%((tpL;}-{ruA2QnDJ&+_I$R&yO#g2u0ma--2s_|!E6r?qN8U^Z;1B^k zRIoz%f6IxzGur8psDa4pc&K4aqqVU`*D-%S*!sm&V7jej=#)Q^tHLX}n^;MbM!TP1rOu$;)s&Sg*meW*q zaU=CfKMIflALLqi4(%WB_!9X4M7hjjnIBk=t}sSdHHTgJakP`uv5*iCmG8b?eLooR zQh88ar+Cs(VKwiSj0{iF)eUhr3Ng+P<#WUU+|SVM2&DO{)g>PA9i+iMf$Kuf9!Jz8 zn!plX?ZAx+r{bj;{LAGcdu0k74reIP<(=`JOQT(;nMH+0nF(j2H_9$)xvi6(%Ufqt zQ9JNay}*F`fdnH9irC*S2+v*RdYc#)$*5w15?2pZ?RRl!c=%BBy+-v1W!?yP5cNAfsi?TUDC zy5(TPT5pDY)!cwMhDcGov%tw1+O>j=c-wXbIs8Q>tQVcX34u?^NdNWxckfWph6DH8 z|F?KZRpFoESNmPvG>wUt&6h|jP?)<0Vi`WY1ne8xrr10l%I3FU=L5PmO_e5zVCy~w z7ogvg&^)+S+rikteKYFSq~1x(d%E{=P-4*wYhi)UOAZG~Oj0s|G0niOqYeL=={48> zjAxb|WOw-TV3V5Y+q|<8%w}6*fm;ltB!JN^wNUT*C?QyACY=Y}H)5SF?qrnQv7p}b zM+r#78KmQtdpz`@A(@jJiib6~1Nb}Jv(-ZxU=NwbGNAmwyde8w69D8D#Tsp++eaF` zEY-&J07sat`U7xkzHw}fJL2`jb}=%W=yTut^IWm6ZPsX<7JHqEmf3^V?|Q{c<^B@w zGFq4{#Ca+?$3>xKNdRd8W(>#k)=+Dh@Ua0?wf;GqKo!L~Gc08vEa!WS?{|T4fk!01 z_5Gv6wecmwR#D2X%_0+hK7>$$&Nq25swW1a{)Dq-CPgaJ-cO}oXxYyfNZXfc zX0D$6DF>W!ZP->dEH-`gJi89fM^aDo>%P!}g&w?kUu=eDyR1~vB+VKZw?KDq%Q)83 zaGIcLD);AtbDg-3&6Qo{euj4{aYHEz?|pcWqgsifI60=Dt&=B6H_q1mH|@|R!d@oHmobWSK`9^K%XcIb>+)gQ_UPau@+APX ze;mcb)>B|5`@dVpI#*PdG)N0{5|KF~Ln{GVZo3nC#&*41%c$7gECISa4M0?i&eY!` zDxYX|HrUGpSdZJNw#tg|>ua9+fX4Uwe{l=xtj8z#SOOdNq+um4|vk_zbV z2EYH?prB)@=Y64=JHM`Hk&Ij<*Iz556o>x#YId)KVM^zIIFa2L<>lN+3?TRj(?WST&EhOt0vQ-VcbXV= zifa+YTk+6%2l=m%5=IOdH6u-0`1>4=1{f89F=KyaJNbPzr@&0$e6xnx#4u9% ztFkej*ZR)kH^8>*4zQU68i%^5s#)r;RS^(i=}$8 z@BeV}Im7y4`W?K=4eJK``Md)3St_<*HH);vRs5WssI}8FD%k8iAj5D z!FyFN(~liat|fOca#c2!l*&sq8sB@v1cQumB)`b zkWipuA5!}sTZTY}ZoY`RHM$vXHSGvSp<^e$VUCI8vLaW{`t@}g;=p=iMrPyEtWn&( zhjU4lWz*$O98gzUjqZQ(Pfef7F)nuqwN*s{%Sc))D3i1~(r60efMp$&dE zPk9$)BMiPfyfKMU;Cm(70s-7H+2`1V|8y;|GUB1KQH{AF$No`_E#|Ey@r9A}iNaA( zAzdLga4s@%Ty+?uDjLCBX*(-ya5-h1@}bhp*V+GrQS4Hv7P* zh0Vs#?`8a#3I;fC#Z;*jNAzxg!;7{e11?Vo9J5P#X?wbWOVjU%?IdFey01s^SWODDd z^02(YF9|?2viC>ex;b~3So)fB!Kk-*FiMTSaMMrMm_B6$>mHqR3bb47meJ}7Ii$A} z{WB`}$bmEtxwb&{;i(^xMG><5&`!?CjBp7jMJ2}C!E&6O+52Osb1Lr~y1rWu@QC1> z*8QBiy@I5gu;{q}!T^mbe0pE8 zElU(^!W9Kf_l|0KRoib4N0yG(;Ue8eAc$Z$l`>y6?z15X&M*|~t~iSTU4LhU0zT&Y zTLo4q#Joe$gm0R&iP7XFQ07xu^t{R!D5vg`KD40$wvo7iKzCIKOD@AL8DD+qrg1Ci z9cree)wvms>r*tJR>M7*!sv+%tO~A&_vlU`+_s zM{rWhsj`>H#6F@iLT=#kSxSOvcC)1(^3(7VG zn=1g(t9U(T$?JJ==9~HEfI&EI*Ph?o8ySie#m<4r{K^ zh9tmwx+xWAYIcK-4JvRpepJ;t+)UhvZ5lX9WD0rlF#k58*k$#!I})sL8|y zAK@8nob-XB^%KPSez_8tOXmb*d-vL;dmbs&C!>e%{6Z^P@P~L*g8JNsfQcLo00RJ~;Vpf*u=#gb$Q$!r(#%^Yz@)=_9HuKT$$uZtLqgC`T zZpLfAyDJ(syh)67yJ?J+Ka4Lj!@YIQeR5>%@I&Qxixq2~??*UJJCijm>3E zLL?j~x{&iU=tS#DMr0=6QRI+Y)tE~_ zDq+oR*uUaa8VYI*=ShoNH~wiR08GAH`3h++jXkVLr4=0jB$%$M&(f)3u5LnOG*tLn ztL5|CA=>EnRlrCBDmZuA3t~1uhJ|WJ4Q8G>4Ye^_mBg!L51+9G{(4^Vx?^5@?WHLI z!cU_JA?}N2)zBf7$gNsCd_NGlk41gA3_J8p-d|ATa~(Q;RHKKps$avXl7;3f8x6+I zCB3vCJ_LrO>5y>NFRXI|k?78aC$vR8;3>d^Z$W-G>i^gX1X|0euqZfd)vni8|Cij` zEV=(xOZ6PrbnrjS4py6KWI(Q%1(7-j$kKWLr79W4d`G`2ZxEh4-KlI*XH@}GnE=Dl zxGpOy>%}BAXFndvXcAQ&5OrTL->q%I8|Z3(%T+i3)fyUG(m^wfLkq#6wMD8TZpZGf$HFVLPo zx!gPC&1Pt7VBYi&ZB}UWmJI0KAq_%$;c|a@(T;Q$Y7P)|sjaJ@-=RP(l#{8l2!-|a z;YdmC-~==ddr$k=dF}$Z#LiNY5X1nwBAuD9)FV{?Llh{jbI&yrs%DIRYv&8$vaGh+ z55fq!awTG|I!%7; zohz9JGWCbywc z%|j{vsJb#G*2tg|sY)Qs^F_C4xYQe~F>{-1Up{{1fJ#0M5r^myh2pNcS zQ^}81&-%RX7w?Y90bd&w7G`pl!`*m3s`0}t7hvO^J)_t6v!vdQN3Jj7_IlESU07y$IUX^y5=NE$j7W^;M)a>x!O(Su*iHVV@Fm;Njsi1 zbQ9`WrAYT^1cjecW`HTE%F&M>&z-2JpUXBn8}|3KYX8`@ygsoA^%u2^uWi72=(Ry4($KbU3vcHlS& z>;bR|TT&#GI?D#NFPxRXkH)!uYhSH;2ksIPZj))14UiR7>al6EDO6zj^3(u0#v>bb zFDJxNa0XCRJQ1G!+woHjbbs~~!b(<+YhWl!Jh}9^T{G~6;}lLrezv|#?w4ydmC`-g zd)<^WrBE}rNG^G>vljLyMmawFXQH!*WhFId*s~?u=ToKmqK@*cF4a6GdTuUnU03q; z@#qI(Ux^xANTE4c*{d!=wPl+&o~)*#06m4PBjFCWhc-?V=;=hFF~8bb#txYp7A97mZ}ah*9RtEjTQeD4Go1RpJY||NLG883jdq}N3<K zIRb@)jkgal{n&D;1w%mBfP)?l3Qp4ksQ>}vpH`0x7$o4>Dj*y2R*RKOEOw&?3Vx{A zkD8Iy#9bPlBP~oJwgk^`*p5E=n!iqYF~Oc~u5AtqA;(&^4`Ut<2u!chgrkH!#nchj`CrK41eJ z)s+KppX3`NEnL&Y?LP~B&5>%->N5?{1OET7Is5dYt5Yt^&2&j^^4+Y0q$5<;y0qjzg@KN%EO zzr=ieY5-MfqA;7IQ6`9Z$Uy=)p7Ehx7&}M zyQCJ2G2St3YQg?qA-r1s$mZp$*cQ5djjOZsQN1eR9Kd>$0DrKvoDPq6{Zuv7Xmk8} zSGbRn3+Sdomef7KHeW#*jEVzgJs_#O$aqqc-hxz^-8~^N! zZK*5+8bX*$>$y<(yaOrsB!;E3njK8-&VGyc&w%As!@WwJ_EK4I?c+u!4>XC%dfW*U zHH>9t0Pf-}_f)mLun?E37xVnOtTzIM7-RvtTG6c2B79N<$P>`>7~Y}Ne6G2@ zXBDi-=2;D|o&PQ?tQt9a+tZW4bqhn$-X_ znmWI;G|j`a0|D?QQ#X6sa(E-()S*rwy$)Q&p#xX>YgVocb8F$H7rYs*B&1mYV1e)C zGgh;5NhdFrY4mzc4AIGDb#%2mbHrkR2@#+Q*zr4m=RiG}2|v9H=bPeZmO<{dx(YX?*CZJRs4GvW`3Prs{hPk@a^1ReF#c25cy3Zf~ zKe(4>%C<$p(ZL(%0X>de4FyX9^nEB+RQ9xZCIJFeRVVHv z>#LO_J}P>NlbKY?L>lG8>m$)@Pg?**4`7l0I-Iu_Diy4i)P7*U#XFrOLT|eLRt_VUkA2-|7G=GhUXz75D>?i^ z5}o8HtihSteK2yvN^`iQ)o;|!WR<}Tge(uQ4Q}t3ntSA_oNvznXkfDZCFEK{UR!0m zn3%s1uybN<=KBeo%^BXGl~_xPdnhd;E6(L?Req(oFQf6TwfXd&maN-{C#JI5Y+uuV z+6vLNfNmozGS<48&?33rR^2Ev%QaJ9Ye)bne?w>P&Om@?9Oi(!k=zauc??+h0~y?O zJ0exItBON%>RTkcrZ3=F;o1g>r!abR8_~C_J_n7GPQQz^H!F95#<>T7lmr{+K$Iii ze$I?`zOV!~7cp?N(ZrQ2>ank%65vEP+4zszly!e8n@@^#ZZC5C;bDK>Xt$M{KE&-g zLYt%R?ec;97%YDWG4ML5y$96y-JYSeS@z>x9mRK+8@Sli@(6G*Cx80zTx9Z9beP#1 z@MLeyJg+1#_$UYpo6YSs^KP)WxW07g$_*#G`yOhJOaLPL7-ht)CsE_3q3lEwbB#K3 zlQpTJrhY8eedwM*iu6{*;|IEro9OA6p2xCSE_B9Dc$HI6#LYQyfp89+A`O^doiapip}&Vskq zeHjh+k5uNWbg-8&EC6uyqy|`|DO}@`LA0ql0Z*0k%RCM6uwju2yc`3hX#HOh!cTT{ zESTTkLDRs@2>^;O=DdH>2)ybTBLiW57Q~bRshK<_28(i!K8+LQ?*6hJeQh#fWva#u z5MYit9SISO3LBQbU!(Uj96qAbW9fIPD{DvZX{{uBH7!5;NQYX>cUVQ+%~u;%-6`$Q3U2XBhd(5GU6ToV>obplHRY`sbaFywkCh^$MMwKZUc@aOVxwPT zLu~0V^SHqcsBXRDla-HoOhuq>svz$)Uw%0HsVeV0pqMt4By>)GlZJ)4`n@IPGlEf{ zf~9OhzZ5ak4rAbro*5JtK*k)5Hv`2huuYht|&yXKE_2P zOjnOX#Cy|sAJ?z5JuugRy0lqu3Phw z@2xVDygi9@fmnZa>h-@Z`NZ{)tFM*EI}4my)8dZSN%KC23E_uFz+*>i;BC%}jfN^aQtV9yF>R6V=Yz%N6}n1-+cWbv{j&IEn9^C#<*NWx zq~4pqWMbVjZ+0v!o`zO;OyYnmO+Sl5E=$U2Z~Px=#9TMqSj?h@-+-?>AYY<+^DMrH zR#cN`nJ2bCQ+oyoK?Air_nnu98$P!i5jVcC$^_Ef3#kR9z@VinH+b6RWi?8Zl$YB6 z5D8;iv+2`em^D_kM%{RB!&kwZaM6*^4Lf!9&VCm)aQ@HTsZDY1=W*XGXUNC87}KMa zQJz(d2{ok1FPbeYU+pNwjNu@(l8{S;dY|3huOyZY`w|do&+?W5ihhlc`6_v%xJ4~~ zIAHsc^G;g}aM!R&!lENHf)r!}bI`S^x`kvjt3xV9-mT_7C% zD579SCbOthZpa6pGXZ*Hq~FC=N-FbGyET2zt2VXhr~UX=KP+f_vAe`RDqcLw*m0bY znfSfdm+I+->&?WqOA)ai8N(x=^eSSM7C$=;-@@fPT6t13#<7*Ep5qI5Y3sknSDWtb zzme`~I;@RgmV8UBr+7;ent1#xnj>9qs_|#dA?Z^ya)*^5e|d?~V9{qJg!sSR$S?E3 z|1x*n|H<5Uu9#8EC+HU*ZIhYv-qS8#N?P+i zV!#_2)Ndfp_X9f(65cCd;x4zJOixhtJuMCxJ)Pv5Ne!4;!*Dr7-uj&axu>N9JDM%> zJSKL9`gk@j#2!uE83ku=RbnA`j8o!2hZ*#209w#-Oy_pRv6v7Vd!Rz+B)zlYD~x+6 z_@a6$xoCDpZJm1b8$f(x7onw$!i6UjxC-^|&?fc#v;ox(Jk3eYNojxTqxj$yrTTow;jrc+hBfyhHOuB7@Kd4a(Ssvo;DHEM z)bid14?y*)cneZ>hzoZiT+LjBH*p`c)+h7WTg60Pj`>WM?d?zD_`7`;(b=Q{HxCTq8~0y$F;{yj+(;uJX|VMRDkLw*SshO-kpXK#ets&HNe-u$&s_sZ&zwP}e+y^_TuO4>h1Q_-?Y!g28D&`)8ByIQ1 z%Cz8egYdOOSk|H0^R+9&D|&wu_o2Q;pcS8*mkko~;<}a!$1n)mB*yXvBVx#Yo^m3WHzhu;cIKw@JnvRoi$^wb|La{r zjiq?@)}5zF>0j1hRO#3((L0ZGAWk6N1Crvb`&RULw^GM` z^eu+@)p8xgI+43Fv1XPt+9{JyB`w@qg;Yw+&F{25^EJ`zXK1RpIQ?f6jlPjf_FiVQ@`y^057A?F0OBV0JS0$ zkhi>~onM$7^Z9?ehs#Kn%Lg*4zrLf37*x{UTqnN60Ul`rukqXFcNjAn$E2P%e@@J} z{NV!aZbAzpxa@mP&or)l)Q*o`ce2(0cWJrEHfkpUisvQ2Ih;Ch$=Ve=7jY|ZH zHvJ&AgY@t{N<#6LKQa|@`(EHiI6NcoE8oJker5y+WoXLb6xbwT-sJJDamBedzEEIN zW;mLYkxldKvDC;t73Ik(Vsbe}AnKI>kWQdl<}$pVgO&$A8ys*x+u`J$=*VE|88GVd z)DPWHJyLkHDZyC#X_1(lb2(3-MvjEiR{%kwl0kL~82U~{S9N#)1G}BP_a<|E zFZ5AW$I3;uDaFx)NZXFPr;=thl+GfPZC$Wv##H5ka580|r97@P{K2njmhap}pD0=Y zX?2|pY|=lqQfJE?4gFdCiL@e!Yjx>U`uQ;OFJEsEKa+szM<@WT{qh$^BUiC`+UB?$M5&+ zpZde&k@q?0wV%)DtA4am7*H=1h~c$21_UIaY)rzL88z9L_$(9_u)d2gxI8paKH>%N zzS6Dk#)(+xP<5n2C)_=Rec~PI>vUbeR9Ls0ZWYCjcAb7e?1;5iT}Ret6=u$OqQCG3 z`dn-q={Z-ocBZc;5kP8Ae=8&KCx^XO-^r8(#iyj!PRM2AMKHREp3y-Vu!RCCQAB7e z;oQ$FKsJa6do=^`1et6pD_)Da8| z4`-8knWPS1-7fMQrN;#4FSjKruw)Hqt#y$Ws^glB*e_YS@2fIML%DP0cWieVx$um}Inml996OD;=37-(7l+(6SaK*u@0K!nkOg{PTP!Y2WaIu*XR_ zJ3gt;;9q<7628mCkCuf|W3-n1e3rj`H2pF)4?VjoUF_aeHt&Vw(z?M%7n(INDaN{= zo|$a7In$#l-S^&Z zb3grlQ-vjMM*N33Xs?6T>*_0R11y&pWv1_vm|{&}?R;V_-e~yko9*Y?e{b_v7w+6E zWt%(-a--?za|q_+gXZBpw5ksByUo{ELlDY^-9OpK*hlmLd1q619nkP@U@a0%BhA(Y zVsxSqO6UK~>zGJ2u3R=n*XDe)?X%yr6ju;Bn?lNuEX9mw{+=5TS@`w-a(uIW7hB_k z$B};--9jXdp3jsH+YY`&D8<{E-(hcuH=#zYb~k5x`HuR_hWb6bq>a#Vy^7aZUcr6k zgViVV!ZXuKhdNxV>cQKhjhmv%&$;M5JtmjosRU;p8cG}0k?)ussVn^h=G=?cg|&-E zcV9I6_xx^Y7uy~~wc15YH_yKonrGumq}}x|x>I-6A~4y7l`bOwr?I&PUOHt}xtiR3 zO`ugvCTHr@6Mg^UPT1>`p#@}8HC3}_*_|gGT(GKB)j{>cTEt9=F>7C50a5T3XZUJC zjZm{F1%YWfmvMDoYUm8hIfdB6<;>s7*H-6$xy`&dy`}n8lBdGs2R!YW3X2)1F0WxQc=CEjeK$eEONAzpApIhA>u{t{U%g{> zyft2^12^Juq=x+Q$c6Wf1_&^RKh`7yyc=iH*|viFdL&=Cr>jas|6a0_ZzU;@bPTzk zAbkCeX2b<)Rc`$i%!H%^N$q5BDs!b_;d9L&(hbFLYBv(y)2Ui5xn;CAd)D1mz8Hqj zvqu1+AzKf`4Ru+%*?Z-rt<>K#O!9G>k<#7+Upi^t@K!sifyg`a{ z;e2}!a=%peDSjFMYR_i)cKFqB zd!5$?Ky3V5CGOFGz3bQ#KWv6kUU966?w!o^r7b13Q)gfcIu~TK=aNP~7AgD^o&&yh zj5qtc%bka{6VN+o7gfL3m+&bo?K=J+k$M9KM^C6IRGFfWh4@zFe{NKs$$97T5qJEA zs-YmPy6V+AJ}pOeem>zE8g!EmO8Ck(f+KRCG#TDi-Y|+?9acM}dCFOWiA$q*D{>aQM7QbR znZ+c_pRaUnbkd8snzU8drBqt!=tb@C7ePfoeNQ7e&`H8tWg*$B@sjQBInq?DZx!jt zXDYI$9yP|k@>I55*dH=SQ~T* zTYdQ;Oz?a%!;I3nsoRapAo?XQZetFtAEDrv&a0l zUF}(yY1DTbvo*^7C26s<;Er-RU4D)T5tHxWH4Mz7hQvI?>pK!W#tqh8&#!vVjd>#qC1o_oYry6=W-OC_tX7XdUk;7m z0~p7gxmE&5X&;-#+PEw;cYD&}nFx&e4{ty(T;jW5^KEkVC%@=L|9k3swO7$E&{K5B zq#yq4c1SegFfuq^V*JDyc0b@ou3ZH4Dr~N@^^uip3-cyrY-gwsmLLhZjUpT=VXgK( zLoaVP4{U_5diL;&NZLNf*enA$q~QL{2iMlcv(nw-fBT*1wlR#*FD_LnncBaS`m6~nVu<4nhER#l&fyqvcl;$*a&h7ksZ8Xv-0 z3Kc~p2Zisl5h=iaK>BPfly(-6igMqM2;6qgw?`QSx$r2*^4-K`yiE%TMHxIgSQ>t5 zEmve1?H<~&o2ysHNyucgT(3Knw(t4mJ-S>vZ~At(cKry#(_*jRb2NA0zBaYf%s!j> z`&y=ZqWg2^-Yi`LbxhioNz1>)UDOSFF!^}5pq1?9# zV9ivrhZXoNc`L1Z7;EUFN8A!TAZ2kXLZ2pp_8c3Zk4+TFpg<fE5G5{I}9GOgrR%>7>>^<5l|)9UM?YYCM@{FeeiQocwfkYNe1|rKCOf z*WbOJ@*@H2``l&)8pz!BifVZrjY#J#Z#(%ZVZSsViuACwa3~&Hw%gTj2;?2S)uZ?W zfa;&s0?tJ#R!se5gO~6bz15RP6W3icbgHc3Dg(bths`1kEBEJEy1~~ZU`O(4&)gP# ztt*G-NTn>bUCx&dyX<({LVGlP@oZ+7T+BOVZoZ~(_+nr#4!Y_0`{s*bxQk_}Z3>uc ziB8IiHv;hrAIj7QG*4p%1rGqYr{NW2k4Z>$*rAuC?~cNv?$4Z17Y6`kWAY!O4bvdX zWnL{<)*d|odCb!BA&+xowsvsl!5xQ$xBT6nPze`5D$zCt)>+Vyy1;NP=D@8kc>7Gz z{si&f$Sw_W(#Cam%3>@2I}QvG9oT<&KVYS!=H0%}YJ?QlQ{u7!Sb`g)?0IjEC3rso zHFK!60ZIp~R(qKKvrCxma`kD%tOIi>6@$dX(s!)I-x>U6{awHROr+8rxpPrk1YcT6)hyGe%_MAk zLYi(I_S?75SvfxV6fihE%mity2Sx?>Ezod5XV9f<+uzPr8fa5T^SHpia+F|Qeu4@U z=0>3WbQqFsmFP};1XbV`bY1>tbt;+~AuNYtzTEqf;yirMterSN&8xb)Ufe+i8zWNW&*bS^d=) zm}A*}ACf^4%e&qmq6H%}8{YSF=z*hmhkW`D#u=u`Q zUdFqdf@DmB#6ie6_eVzug-AfsIo<}>4TMA+0=DDUz|;IEmB+;!tTvh&_BzT{tF>^})zC93HSBg05CJUZ1CUvOP z`J8A^|2o2)9GxyD0X%&BIO^qF4TG@^44}1W&=@mfe;;K^{ONU6iy6@+q9pCw=I#J~ z8$8qc?*W{HbXl(mS)wVr+k4NRw~)l+Ycn5kt`du&DOViOTyES*Hgx|>V=--* z#rrY+iUl<3pQJAsmCvO8M&HC?9`l5q_?xqGJWLs>^~VK%eqJX!f&Ry(({nG?_AisV zSE{(6{c7W`y9gKnWKJ;3^9RQI4&c!Fw1m!aV{76JM!90T#@FR4_}{i&iYt2H-2zitw$vlI`rXBmOqK*Zr)4r zBo_!7-Td-H_hS?$o1P-+1!;zBfo(Tgq|34m*LIZA&Fk4^=F)QRG|Ho<3fD?$V;-tXM!k?OF_2EMd_uENw(~XU=MiU?ED|f(eQbn$Uq-ZR22G zm$u>_w<;MX!zRCGf13u~(J_C1`iP5Z%SIGD&{V;nM}IN+dnI1DgFHYJ&SvKJ9E4}G z&6YD3yJEhISPg%LSgzcF$8^!EuwoZHgr?f(+{!VVq5Ji~TN`|smA0r+;&K^)4)q4b zj=CjuMFWTjq(XiT%rhCK)f8f^(K_Ui7;*n}X-T(^zWBIC^K9>OE`CF*koI(6)Y{EM zJI{9wJ&zg{;Ms-Rby2A??mW-(Cj2~}jcGv6cmn#DPTCWy^ECZaTN2JobZde-Y6pd- zE`DC@pM;=nLq}vBeJoAhv&Ts%^M%9ivEB*$>=7uyUwR0PEvc*9IUbYXOg9nh{@%$0 zM`110Hf@9~|JU5=fWM)FbZv4hNWL!hU=|C1qWS7hh}jJ;+vNff7isTx|7`%QPyl?> zRNBRhtj+Vy!rFHqa`4RnTwJoTK?GDZu`AuzNV(1Sm0d3v-y%@6xyBd>7)2m>TLk?A zfUx6d`Q0Ts03{x*a!BW-*=*~7V=nU1zJ?cZvgceKrL*_v|+RM)D) z4GFP`7l&-wSUZkI89J@eHRqn82dtf^6CG5NHSfc_24X;?(#FSTy5k6o>>?*k_NG~jd`rVZ5-$^!Z{mn^r_50&As!$ zIbUxHG}2u6_a%M&)o6~#r;jL3U~PAuRvd%p&zD|yy50Hqr(2=~hiI3ktAS3#23_}v z4x1iRHqC@&VQs4>m)@Lg(5kC{KCP_*@_MTM8bWl!hxfR$BC|u=%m!>>*BKCCQRkAM zDfg&sQ7-+^P0i@~KCxXa-M3?q^CfTiCw&RDzINPTZ7-vg9Q=cE?%mcmVELPBT)hj` zmn0IzGU?paOP%KobKK48hb5y@e|G|O9w55&i^>2}7`zJK?<6U*=D)n5q_&rEr}mkO zz$v(&m8(}iEp&{ecF=dsg0qX{D%3c)=o> zvgZoi>Jo^rVTiX%L}^yT3>DVtRItrzFgbD5<>zWpw8A3)@M zvetpv%_`vI#p1jj2Lu`i&shDTbHkLaSxoQy?t*?q0P>}EJ}v1=1e0L0e6uE(@!WjL zGHRlm;2r_ukuJ`@60>>QH3<*tQOiVA*zR z!3W=v0mvx&>pN#-6qc@bh`;I#HIBJjt)Lb7b94^|(n$UV>w8L|?3El8ig}#833zx9 zgJXfY?0Ij8gYVn0YnU-V(n%JLQk)gyE>jVlwy{X+?iiRnUNJ;G5uNB`SqSaSp#(d^ zeOUGcw?sBjl1TWYcDLp=?;6#`HxU8wxwbuIgbS}-NeEOUs^)r(Uk{G)GYZuj8#Y)@ zv)-=5cB1fk(T;?eX_K3Cu5X`jZZHfFAor%GE|qGd6)@%B(5>+jKBuHpC^^bKk~Lk` z&yTOB{cAk!yuyzzra~PLQ4l<@wf8B4O#)DUId8_4VV+iXjf^q2ohLNeL_GsSR2KtB z!r~=nosUdrcE~MC2T~1N<3zyLkHghAD-GJ?a>M$ZNAmu&OR-!0$SS@FGyP0=l?BGB z&-t2$V^Idj)(+YmVT&0^j!y2TB5A#PX$#0ifKNa7?3K#Wci*#HIL|svayVz3 z4&p=p`5SGHL!}b)?ojb0cZQ}ea$KUV?R`1ZyD8&m!N;cX@;a@P)jHrt^xxcI@;dn^ z&Y~gNS9U;PS@@jKwjQdB@wXm=sLBO6)_kAXaO6-iOXsF2pFW6hqjZgNx|rC!C*7O1 zV@)g_wIL$6eJK~*@lWU-KmjErV~vHT^hVG!aKk#X6YrC8CG332AL^jh5iWer+g2L> zfxO)01u_IR_iT!WEsy@#HMlL?uZk#lp5HDrm1_WTd%r8=yQZEk^f7D4ktgNC+$R^$}TXkCB$DIG%7!=|lT zx_NWBKq_O-C-kX8Kd}sC{&LS_0HS~Yd)dsUM-& zaa7R_kjycI16p+#s4c)loO^I-__GSDQ>Rf1!3=H6;UOCX0DI*J5VDwHUv68^zKjgp zx$gEnhx#P>c06TH7daC+3GHuU)aIo6Q~+iH(Qa2QuZ%ZYHnp?08N;-EO#-3pZ9@0% z;X!3`Q*(&`GHd5r|4%&){mNn89Y1`d>>pqU`sQle68Y}CRnC1|)$m?SW#qmPqCiiS z>C(eN&8BCk3fphb;vsznZx+prd-yCDO+&x%LW0!S3>BU%_vA`v&}vkS|CbxfvO@PO zYVTa^`1?5c;v|3y8yGL1O=-0fNUKWU-vbM0JhR>G>2-Oj?gBEAW#jX-;Kk1_jj$GR z3Ec{)4r^B%IlHckOehJ7tz=#-j$;6onHwf!0CDcAErcoOJMbY&s$u)5M?{`{GVKX@ z9bfV>E?1d?FARNTNpG1=2#VH=gJhk5I%Z4yHmJ~#)jKS>jLZ+ z;nZZ)OGoD57jPex5+8hCdwpzpRe_|OcD;G+jhrN2VNa z_~k--6K&VCKp`XUbb_1D4Isg169b( zd8*qnh!qNftS6&D_^c(GmHcGvz{<8SJXEUDsf)bU?BO1QZHVirXO0pVgcL>FYZP{< z42q;85GOAkhqHXALhC)J4{{w%OKDZIR%)M(!UQr0Inb9a=ZR-*fWMsrB38{BgQgfW@#MBHa0qg;ilDcFhUuTaV{_hF&&qZ7 z3Ly|JG0o@0$x#-Q}A6?bGUgrg4I z!HP0L-PXR%B@u=19NLm`m zZS!m!;(eE&{p2^mR{9qim<<&sE5Em&x#ZHQpG$kiwQ;4~1qj!aZQp~_rLwz=kc<37 zw!wcmTz+1b_NZkC<%Y5ES^!ZpC5$l^HM%Eg<*oXpm3B>4Qgd7~kZ`Q*o^n(oft{`V z!{8>3DqCkcdd`x_jLYVlyFNK>#wr9=!4;!jV%&WEN>fi16Wn4IS`m7RDnd`^?YB8G=zpfp_Z@K`d}I=2gBKb1L`b(` zf;vMB6~9jANcwbS4EGnNILCx3RsH>+G9Xtz zhMX!-(3b%7-E+NfhtdDQUxi_{2e-an6J*;_=~wF=JBbIG0gB``8|{|4%>Thn*lM(d z%|)j1l8eM6>HRxW19C-ix-XT*t}AtCn0}{Du)$Nxc9Uy8y7&JlaWHP?6`sD9tvwL$ zC`$(Go(Y#LMLs8&Uu~M~y{m7(HQM8+n2(gcyrso40(*QuOpxLVE(WSQZD_@Zb2_G< zGANg`@+&-y6w4$1t`6mKBbtVK>5>S=vzzb0ey_w${gb&w*Vxo`g|k%e1Q(jV?r4%G z_!N0c{fMfINIuYjRyr~s31~a02}Ks2Orc}!O`DAh?ccku=f+{);JNE#H&b~U!IFzE z)O4Iqmyc%(14o-`Q#ipfe52h;NONQ&u(>7VQUxKO7p#Q+iiYrW28cTeu!#pkq?~xy zQVH`Q#y^}=7Fk_?x;1cY2@Gvy*G%~y2;>rAB}Rs!sO*3({YSfm?p$St?!CanRwreG z=)C`^4Q}DA*kj;zuShUVRrvc`4sxR>W_ypQ&<09P$Bt(`omW#Et*zB#S%6j%(m$$9zP)1)_n{Te&_gNE!7@z zSz^pVIcH(baA9sr^uWSIj;)NjD zFxQVzJF0lz)|{Alhn#vV0akR?C1b8)?8drU)H` zJqeiGaL8VkZ1MNlQYx{f(atJ~?lJtC6@99rSTVm%UmUOg6cuM3Hv#Kpl>+3M<#~PK1TGh| zt+(%6t)kOVlJc?9#56+4Cn%3&>C_wgW3YQzTi|~}F!&_K+U9H;IN-!wM zbO)F3&6Sc4yxf7H5I_NVpIm6W~I1_^eJjE}elTd|^~Hq*!udck&MQ+9W& z*16<2)+`ZK)_AtEU2OG$qn#o8U%Noc>CUUuZ>HW+DEu;nZx#PH4dNiZnY=q>Bk$1K zOBqj~Y#I=SN6Ry0@YM@ z*Z6z(danKV;r8@U>+%PPV${zAUx{8ll6;$y5Vqi`a_*yXGq_81%Cn@#k;A97V7?`! z*!7t5KNpoHJ=w&w+s(x;&v2j8!U^9yJ1{Gj@$cF1pOx~7*@}6orMw&7`?gRT8MUnJ z(%9n{U|_v@Z_RJ*_oP??z!&GC)-exe zr-c4+7rlu26L4P=gpLevp0J~uc2J{$h2x4{LY^M`U8l*kisR7BBZ^R4@|uP>lVwAC z+-cz%)G}D=4|%5mmh?KOYBsT>;k+hNs{X6hJRrSPnEq5UV| zkT_B?<5Sv1CD!%S^-(Z;n3R1o?%YvH4o!QviVB21L)g66g3fa*|ALAzaN3Id7>LZV zxRB4j*!IO5$rRCot~$93mW)G&xnCcBz=4x*13gf2qqIj=f}U_NdOL?4f^L0T`X0(# zK;9FH>`C_;_|x=iLQ-h77Ws~LkhfgOaQksQOG zCLiqkn+FUeLlcQkttiCjw&{vJl{dS3e-`3P1^^Y%fc(n>skENH&7lVyr+&JAr?gE0 zG5tyL5<0^4i3u$4@SjJ=WO_tu)~?o9}U71+gkzZz^*e#6tX0KI}RGASO^))k1M+Mth;EQ}g__jJ|@S z*tw5`;927l#c9?NQ%&){*13T3?TY4N458+x5^l?tX6Uvl@ufCkZ~!$Qru|OXK+_Og zn?*}O%hU>o%fKdbjq!u+_5BKkCAVpA_BIO)akRI~z4)Qo*kXb617!*!oCVbPCvGvk z1%YmED2p#4=jy$u_3=rOAGXSRR>bVlaxBV$Utt?JGi;YN!2vJcy;!{}3l8|kRk6TP z!qeqkxzf=s)Vh3_IW3``z~0kUIVv$<7BDa(f~lBvKzqo&!9+(`#^}M4tmQyPqQA^o z!pfjl8wiCXn{w+kO8*2^!N;sF)-ArRmsm&bK=`I z%s%2LMw+PO%dWsGS(7d42b%G?S6Gf`9b4WTw-P5@Q{#e_i#D~KO^7AbyOcgmO&5ywl{G!7``}G zQ&u&oH(SUGuVz%;UJs(;`AMa?f+K!&>V0pYFCmCffij5~FFWq@+lHKbG5yVzJh_U@Py4PeR zp;|5I36X%VXbd9FJQGT+UWpbO1z)U-da~_6Khwl9`*x|Ez3o3oA+mav7e4l$rEc-= z7JBDvso!e%=GZY*Vd?8pp)+s>!e(!IW0YG6A>U+Nytvt9S``!YhLotIt z3ts&o+rDSHyh~53Rm<`AoiGD25h$$0qBgVCJe+}FhTd>g{BwEa zpaNXhBU1bR1lCPXe#dmcpp>FyDONjkdHw6&yUHqdycC@C#~R^!hG)KLj}-nD;J||{ z^v>VZ11|dEUC7%mI?i14>Z#UT0eMJquJV|G2D*b4{f+4?#sN zKntpLfL?5_pI&}OUH-JSA~;d|N0v^04UzTL0Zf0}DxXONSO9l>=(9sJAJp=G&xf4B z2VVvlD?)+Wil3NKQR=pTndHi89mVeVB8&?MH5f~B(0=z6hl+JFR}RVRg&=N#T_F#< z?}-uF&0@nMiSFq2QFC}wCd9HG=aBqZbtW#0vD~GZ*>xef{=B8`Nn_&~2T?+@u5qP%h1{cLrE2)KNwe5{|E;Q}hhtGglxeg^hy`g!KcBRPrX zMq4$b>ecrBn>+5Ryl=_@FNo>W8`H21O*a8wh{3l2+#BExiYhA6CSQW-L67do_hkn^ z9^AOh!mDz>-ZL~RKi~sc`*kXSJQ?*={ zSu3h9aiUBpBlU|FPg0C-usJ6MmH3@y;HI)#&gZQLy{$(DoQ|!eQ+GR~9Vylh$&a=I z>(MChv%JOSVHAUTv3lZ--KjwxE3OO=t4@aahRml#Hv0;u4YC+U(ch0|zUdZp8}87| zrmO%S@V!zyJ7jV1=P`PC{r=5DAPe}m;#n&5=pkez)G9FQtq!Bw`aV>SDC>SV4 z4MmO$K69zoEkj+Up_iNqZ=o%5+dCbNSbby1#eeUOCW!C^lxqytnKHmqf8c;VVLQkj zJph$Wg7WSWm({3K=~;sv>Z+Uwnup@g=wq)XW>2*gZn|(uq_o`E864nC9qt; z-fwne70vWWPw`|brPlPq?MnYW^dHLU$CdMGz(IftNo=*rPv z$i(*V&!PL5fUb|9>>SH1k4vQUjd)t*k83>V&1HtKWiWh{Tt}{M`RrY7A5r`iUL5%X zQap5unW*~MU_KqqEj{uCJ?gu*^$5&2-6l_rz*{pjCNa-WRUVl?KdIemLke<*nIwV3mMX!4Y@y!aC|${;)6=Dl$h{Jzlo>@3^p;fJE7pH92>s+N8=N!L2^ z8fny?`>w_BpUbG!ZJ`O!31F^zZTwYHW-*h4Y9LiCQ)b+C?y`w<5c=zLU)c}~-vxh_ zfpc+HG0eo>I)KM)S1hJwm3Q}@E_5TKM_-&ZP?fBj?u1Qp|J;#(ZQI!9 zu***Pim8|ICwr1FJzQ9AGhJ0T@%!hQ-@mu+FyRSP>e?Ji^er(FZCZ;}y6?3t1{}byBxZH4H!8l+Iw! zB_AsLx|KHLvQANI1u%omA=qj34-#V-3!!(-?NKbMb_DR#&D>O+>d9GGJ=>1F{q#+S zIl!T3Fq7?~rWj)BRx1+Pyi6F62$9)}o zXkSeEAn#auMNPYCw~>989PaHei#z1&F;!Ss)0jgn1V;F!AYAv~&di&Qt(wcMW&?w0 zi4knczry-!t4uk|F0CbDj<3+I5xtN+1I-~!?6N7KbA(hFWdZEIayWek8tmNRua3 zIEC9xvRA&SaFfk0-`?4{|8oCk7e?t4Wa$MU9>)xKJ$I)ZV|Rb-Mj3`K=W>twf6oz> ywCtpHWSp;N5v?J`$#9k{W%#Yg{B?}`iCTv{y>?8>@!%=&2dZhHQKJ4h_= 0.6" + }, + "scripts": { + "lint": "eslint .", + "test": "mocha --reporter spec --check-leaks --bail test/", + "test-ci": "nyc --reporter=lcovonly --reporter=text npm test", + "test-cov": "nyc --reporter=html --reporter=text npm test", + "version": "node scripts/version-history.js && git add HISTORY.md" + }, + "licenses": [ + { + "licenses": { + "MIT": "https://spdx.org/licenses/MIT.html#licenseText" + }, + "spdx": { + "osi": true, + "fsf": true, + "fsfAndOsi": true, + "includesDeprecated": false + }, + "fileName": "package.json" + }, + { + "licenses": { + "MIT": "https://spdx.org/licenses/MIT.html#licenseText" + }, + "spdx": { + "osi": true, + "fsf": true, + "fsfAndOsi": true, + "includesDeprecated": false + }, + "fileName": "LICENSE" + } + ], + "uniqueLicenseIds": [ + "MIT" + ], + "composition": { + "extensions": [ + ".md", + ".js", + "", + ".json" + ], + "files": [ + "HISTORY.md", + "LICENSE", + "README.md", + "index.js", + "package.json" + ], + "minified": [], + "unused": [], + "missing": [], + "required_files": [], + "required_nodejs": [], + "required_thirdparty": [], + "required_subpath": {} + }, + "repository": "jshttp/content-type", + "integrity": "57f537305321f48c236b623051c26e43e7e620fd", + "links": { + "npm": "https://www.npmjs.com/package/content-type/v/1.0.5", + "homepage": "https://github.com/jshttp/content-type#readme", + "repository": "https://github.com/jshttp/content-type" + } + } + }, + "vulnerabilities": [], + "metadata": { + "author": { + "name": "Douglas Christopher Wilson", + "email": "doug@somethingdoug.com", + "npmAvatar": "/npm-avatar/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdmF0YXJVUkwiOiJodHRwczovL3MuZ3JhdmF0YXIuY29tL2F2YXRhci9jNGI2NGM2ZTQ3ZDk3MGJiMGQwN2EyYjlkNTU5YmU5Yj9zaXplPTUwJmRlZmF1bHQ9cmV0cm8ifQ.PysVThLy9zc22g8f3XWGCfhIBy_bxYGq4JAOjJCj3xk" + }, + "homepage": "https://github.com/jshttp/content-type#readme", + "publishedCount": 7, + "lastVersion": "1.0.5", + "lastUpdateAt": "2023-01-29T19:25:59.622Z", + "hasReceivedUpdateInOneYear": false, + "hasManyPublishers": true, + "hasChangedAuthor": false, + "maintainers": [ + { + "email": "ulisesgascondev@gmail.com", + "name": "ulisesgascon", + "npmAvatar": "/npm-avatar/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdmF0YXJVUkwiOiJodHRwczovL3MuZ3JhdmF0YXIuY29tL2F2YXRhci8xZTlkNmRlOGU1YzMxN2EyYWYxZDc4YWNlYWFmYTkwZT9zaXplPTUwJmRlZmF1bHQ9cmV0cm8ifQ.H3nB2cyKYs5mgiYnXdJNQ-KnKTbtZRdlM-bSV9_Rp3g" + }, + { + "email": "hello@blakeembrey.com", + "name": "blakeembrey", + "npmAvatar": "/npm-avatar/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdmF0YXJVUkwiOiJodHRwczovL3MuZ3JhdmF0YXIuY29tL2F2YXRhci9hOWQ3NTE4YzRjYjQ3ZWY2NjhjZDhiMDMxMTk5NDVjYT9zaXplPTUwJmRlZmF1bHQ9cmV0cm8ifQ.Z4Ap77mB2kIKoNdGpj0krdkHNVXFMqdGAGweYebCQBw" + }, + { + "email": "doug@somethingdoug.com", + "name": "dougwilson", + "npmAvatar": "/npm-avatar/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdmF0YXJVUkwiOiJodHRwczovL3MuZ3JhdmF0YXIuY29tL2F2YXRhci9jNGI2NGM2ZTQ3ZDk3MGJiMGQwN2EyYjlkNTU5YmU5Yj9zaXplPTUwJmRlZmF1bHQ9cmV0cm8ifQ.PysVThLy9zc22g8f3XWGCfhIBy_bxYGq4JAOjJCj3xk" + } + ], + "publishers": [ + { + "name": "dougwilson", + "email": "doug@somethingdoug.com", + "version": "1.0.5", + "at": "2023-01-29T19:25:59.622Z", + "npmAvatar": "/npm-avatar/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdmF0YXJVUkwiOiJodHRwczovL3MuZ3JhdmF0YXIuY29tL2F2YXRhci9jNGI2NGM2ZTQ3ZDk3MGJiMGQwN2EyYjlkNTU5YmU5Yj9zaXplPTUwJmRlZmF1bHQ9cmV0cm8ifQ.PysVThLy9zc22g8f3XWGCfhIBy_bxYGq4JAOjJCj3xk" + }, + { + "name": "deoxxa", + "email": "deoxxa@fknsrs.biz", + "version": "0.0.1", + "at": "2013-10-08T07:37:54.398Z", + "npmAvatar": "/npm-avatar/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdmF0YXJVUkwiOiJodHRwczovL3MuZ3JhdmF0YXIuY29tL2F2YXRhci9kMjdiYWU1MWJhMTYzNzg1ODY5MTYxMTI2NDM0ZWE1Nj9zaXplPTUwJmRlZmF1bHQ9cmV0cm8ifQ._IXNYcSMxwgp6huLbUBmWPy3v4hWLq5cQp1zztAJWj8" + } + ], + "integrity": { + "1.0.5": "57f537305321f48c236b623051c26e43e7e620fd" + } + } + }, + "statuses": { + "versions": { + "2.0.1": { + "id": 2, + "usedBy": { + "@openally/httpie": "1.0.0" + }, + "isDevDependency": false, + "existOnRemoteRegistry": true, + "flags": [ + "hasManyPublishers" + ], + "warnings": [], + "dependencyCount": 0, + "gitUrl": null, + "alias": {}, + "description": "HTTP status utility", + "size": 12116, + "author": null, + "engines": { + "node": ">= 0.8" + }, + "scripts": { + "build": "node scripts/build.js", + "fetch": "node scripts/fetch-apache.js && node scripts/fetch-iana.js && node scripts/fetch-nginx.js && node scripts/fetch-node.js", + "lint": "eslint --plugin markdown --ext js,md .", + "test": "mocha --reporter spec --check-leaks --bail test/", + "test-ci": "nyc --reporter=lcov --reporter=text npm test", + "test-cov": "nyc --reporter=html --reporter=text npm test", + "update": "npm run fetch && npm run build", + "version": "node scripts/version-history.js && git add HISTORY.md" + }, + "licenses": [ + { + "licenses": { + "MIT": "https://spdx.org/licenses/MIT.html#licenseText" + }, + "spdx": { + "osi": true, + "fsf": true, + "fsfAndOsi": true, + "includesDeprecated": false + }, + "fileName": "package.json" + }, + { + "licenses": { + "MIT": "https://spdx.org/licenses/MIT.html#licenseText" + }, + "spdx": { + "osi": true, + "fsf": true, + "fsfAndOsi": true, + "includesDeprecated": false + }, + "fileName": "LICENSE" + } + ], + "uniqueLicenseIds": [ + "MIT" + ], + "composition": { + "extensions": [ + ".json", + ".md", + ".js", + "" + ], + "files": [ + "HISTORY.md", + "LICENSE", + "README.md", + "codes.json", + "index.js", + "package.json" + ], + "minified": [], + "unused": [], + "missing": [], + "required_files": [ + "codes.json" + ], + "required_nodejs": [], + "required_thirdparty": [], + "required_subpath": {} + }, + "repository": "jshttp/statuses", + "integrity": "05ac0194e1db35877751400aea69ff922df051b6", + "links": { + "npm": "https://www.npmjs.com/package/statuses/v/2.0.1", + "homepage": "https://github.com/jshttp/statuses#readme", + "repository": "https://github.com/jshttp/statuses" + } + } + }, + "vulnerabilities": [], + "metadata": { + "author": { + "name": "dougwilson", + "email": "doug@somethingdoug.com", + "npmAvatar": "/npm-avatar/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdmF0YXJVUkwiOiJodHRwczovL3MuZ3JhdmF0YXIuY29tL2F2YXRhci9jNGI2NGM2ZTQ3ZDk3MGJiMGQwN2EyYjlkNTU5YmU5Yj9zaXplPTUwJmRlZmF1bHQ9cmV0cm8ifQ.PysVThLy9zc22g8f3XWGCfhIBy_bxYGq4JAOjJCj3xk" + }, + "homepage": "https://github.com/jshttp/statuses#readme", + "publishedCount": 14, + "lastVersion": "2.0.1", + "lastUpdateAt": "2021-01-03T06:37:47.488Z", + "hasReceivedUpdateInOneYear": false, + "hasManyPublishers": true, + "hasChangedAuthor": false, + "maintainers": [ + { + "email": "ulisesgascondev@gmail.com", + "name": "ulisesgascon", + "npmAvatar": "/npm-avatar/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdmF0YXJVUkwiOiJodHRwczovL3MuZ3JhdmF0YXIuY29tL2F2YXRhci8xZTlkNmRlOGU1YzMxN2EyYWYxZDc4YWNlYWFmYTkwZT9zaXplPTUwJmRlZmF1bHQ9cmV0cm8ifQ.H3nB2cyKYs5mgiYnXdJNQ-KnKTbtZRdlM-bSV9_Rp3g" + }, + { + "email": "hello@blakeembrey.com", + "name": "blakeembrey", + "npmAvatar": "/npm-avatar/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdmF0YXJVUkwiOiJodHRwczovL3MuZ3JhdmF0YXIuY29tL2F2YXRhci9hOWQ3NTE4YzRjYjQ3ZWY2NjhjZDhiMDMxMTk5NDVjYT9zaXplPTUwJmRlZmF1bHQ9cmV0cm8ifQ.Z4Ap77mB2kIKoNdGpj0krdkHNVXFMqdGAGweYebCQBw" + }, + { + "email": "doug@somethingdoug.com", + "name": "dougwilson", + "npmAvatar": "/npm-avatar/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdmF0YXJVUkwiOiJodHRwczovL3MuZ3JhdmF0YXIuY29tL2F2YXRhci9jNGI2NGM2ZTQ3ZDk3MGJiMGQwN2EyYjlkNTU5YmU5Yj9zaXplPTUwJmRlZmF1bHQ9cmV0cm8ifQ.PysVThLy9zc22g8f3XWGCfhIBy_bxYGq4JAOjJCj3xk" + }, + { + "email": "jonathanrichardong@gmail.com", + "name": "jongleberry", + "npmAvatar": "/npm-avatar/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdmF0YXJVUkwiOiJodHRwczovL3MuZ3JhdmF0YXIuY29tL2F2YXRhci82ZTMzY2MwNDEyYjYxY2MwMWRhYWMyM2M4OTg5MDAzYz9zaXplPTUwJmRlZmF1bHQ9cmV0cm8ifQ.xeKiBokeI0NPlBc-_eDtxcICYyY9PV9dRjRwios7vgQ" + } + ], + "publishers": [ + { + "name": "dougwilson", + "email": "doug@somethingdoug.com", + "version": "2.0.1", + "at": "2021-01-03T06:37:47.488Z", + "npmAvatar": "/npm-avatar/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdmF0YXJVUkwiOiJodHRwczovL3MuZ3JhdmF0YXIuY29tL2F2YXRhci9jNGI2NGM2ZTQ3ZDk3MGJiMGQwN2EyYjlkNTU5YmU5Yj9zaXplPTUwJmRlZmF1bHQ9cmV0cm8ifQ.PysVThLy9zc22g8f3XWGCfhIBy_bxYGq4JAOjJCj3xk" + }, + { + "name": "jongleberry", + "email": "jonathanrichardong@gmail.com", + "version": "1.2.0", + "at": "2014-09-29T04:11:14.857Z", + "npmAvatar": "/npm-avatar/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdmF0YXJVUkwiOiJodHRwczovL3MuZ3JhdmF0YXIuY29tL2F2YXRhci82ZTMzY2MwNDEyYjYxY2MwMWRhYWMyM2M4OTg5MDAzYz9zaXplPTUwJmRlZmF1bHQ9cmV0cm8ifQ.xeKiBokeI0NPlBc-_eDtxcICYyY9PV9dRjRwios7vgQ" + } + ], + "integrity": { + "2.0.1": "05ac0194e1db35877751400aea69ff922df051b6" + } + } + }, + "undici": { + "versions": { + "7.10.0": { + "id": 3, + "usedBy": { + "@openally/httpie": "1.0.0" + }, + "isDevDependency": false, + "existOnRemoteRegistry": true, + "flags": [ + "hasMissingOrUnusedDependency", + "hasWarnings", + "hasManyPublishers" + ], + "warnings": [ + { + "kind": "unsafe-regex", + "location": [ + [ + 772, + 25 + ], + [ + 772, + 54 + ] + ], + "source": "JS-X-Ray", + "value": "^bytes (\\d+)-(\\d+)\\/(\\d+)?$", + "i18n": "sast_warnings.unsafe_regex", + "severity": "Warning", + "file": "lib\\core\\util.js" + }, + { + "kind": "suspicious-literal", + "location": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "source": "JS-X-Ray", + "value": 94, + "i18n": "sast_warnings.suspicious_literal", + "severity": "Warning", + "file": "lib\\llhttp\\llhttp-wasm.js" + }, + { + "kind": "suspicious-literal", + "location": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "source": "JS-X-Ray", + "value": 94, + "i18n": "sast_warnings.suspicious_literal", + "severity": "Warning", + "file": "lib\\llhttp\\llhttp_simd-wasm.js" + }, + { + "kind": "unsafe-regex", + "location": [ + [ + 772, + 29 + ], + [ + 772, + 128 + ] + ], + "source": "JS-X-Ray", + "value": "(?sha256|sha384|sha512)-((?[A-Za-z0-9+/]+|[A-Za-z0-9_-]+)={0,2}(?:\\s|$)( +[!-~]*)?)?", + "i18n": "sast_warnings.unsafe_regex", + "severity": "Warning", + "file": "lib\\web\\fetch\\util.js" + } + ], + "dependencyCount": 0, + "gitUrl": null, + "alias": {}, + "description": "An HTTP/1.1 client, written from scratch for Node.js", + "size": 1355734, + "author": null, + "engines": { + "node": ">=20.18.1" + }, + "scripts": { + "build:node": "esbuild index-fetch.js --bundle --platform=node --outfile=undici-fetch.js --define:esbuildDetection=1 --keep-names && node scripts/strip-comments.js", + "build:wasm": "node build/wasm.js --docker", + "generate-pem": "node scripts/generate-pem.js", + "lint": "eslint --cache", + "lint:fix": "eslint --fix --cache", + "test": "npm run test:javascript && cross-env NODE_V8_COVERAGE= npm run test:typescript", + "test:javascript": "npm run test:javascript:no-jest && npm run test:jest", + "test:javascript:no-jest": "npm run generate-pem && npm run test:unit && npm run test:node-fetch && npm run test:cache && npm run test:cache-interceptor && npm run test:interceptors && npm run test:fetch && npm run test:cookies && npm run test:eventsource && npm run test:wpt && npm run test:websocket && npm run test:node-test && npm run test:cache-tests", + "test:javascript:without-intl": "npm run test:javascript:no-jest", + "test:busboy": "borp -p \"test/busboy/*.js\"", + "test:cache": "borp -p \"test/cache/*.js\"", + "test:sqlite": "NODE_OPTIONS=--experimental-sqlite borp -p \"test/cache-interceptor/*.js\"", + "test:cache-interceptor": "borp -p \"test/cache-interceptor/*.js\"", + "test:cookies": "borp -p \"test/cookie/*.js\"", + "test:eventsource": "npm run build:node && borp --expose-gc -p \"test/eventsource/*.js\"", + "test:fuzzing": "node test/fuzzing/fuzzing.test.js", + "test:fetch": "npm run build:node && borp --timeout 180000 --expose-gc --concurrency 1 -p \"test/fetch/*.js\" && npm run test:webidl && npm run test:busboy", + "test:h2": "npm run test:h2:core && npm run test:h2:fetch", + "test:h2:core": "borp -p \"test/+(http2|h2)*.js\"", + "test:h2:fetch": "npm run build:node && borp -p \"test/fetch/http2*.js\"", + "test:interceptors": "borp -p \"test/interceptors/*.js\"", + "test:jest": "cross-env NODE_V8_COVERAGE= jest", + "test:unit": "borp --expose-gc -p \"test/*.js\"", + "test:node-fetch": "borp -p \"test/node-fetch/**/*.js\"", + "test:node-test": "borp -p \"test/node-test/**/*.js\"", + "test:tdd": "borp --expose-gc -p \"test/*.js\"", + "test:tdd:node-test": "borp -p \"test/node-test/**/*.js\" -w", + "test:typescript": "tsd && tsc test/imports/undici-import.ts --typeRoots ./types --noEmit && tsc ./types/*.d.ts --noEmit --typeRoots ./types", + "test:webidl": "borp -p \"test/webidl/*.js\"", + "test:websocket": "borp -p \"test/websocket/*.js\"", + "test:websocket:autobahn": "node test/autobahn/client.js", + "test:websocket:autobahn:report": "node test/autobahn/report.js", + "test:wpt": "node test/wpt/start-fetch.mjs && node test/wpt/start-mimesniff.mjs && node test/wpt/start-xhr.mjs && node test/wpt/start-websockets.mjs && node test/wpt/start-cacheStorage.mjs && node test/wpt/start-eventsource.mjs", + "test:wpt:withoutintl": "node test/wpt/start-fetch.mjs && node test/wpt/start-mimesniff.mjs && node test/wpt/start-xhr.mjs && node test/wpt/start-cacheStorage.mjs && node test/wpt/start-eventsource.mjs", + "test:cache-tests": "node test/cache-interceptor/cache-tests.mjs --ci", + "coverage": "npm run coverage:clean && cross-env NODE_V8_COVERAGE=./coverage/tmp npm run test:javascript && npm run coverage:report", + "coverage:ci": "npm run coverage:clean && cross-env NODE_V8_COVERAGE=./coverage/tmp npm run test:javascript && npm run coverage:report:ci", + "coverage:clean": "node ./scripts/clean-coverage.js", + "coverage:report": "cross-env NODE_V8_COVERAGE= c8 report", + "coverage:report:ci": "c8 report", + "bench": "echo \"Error: Benchmarks have been moved to '/benchmarks'\" && exit 1", + "serve:website": "echo \"Error: Documentation has been moved to '/docs'\" && exit 1", + "prepare": "husky && node ./scripts/platform-shell.js" + }, + "licenses": [ + { + "licenses": { + "MIT": "https://spdx.org/licenses/MIT.html#licenseText" + }, + "spdx": { + "osi": true, + "fsf": true, + "fsfAndOsi": true, + "includesDeprecated": false + }, + "fileName": "package.json" + }, + { + "licenses": { + "MIT": "https://spdx.org/licenses/MIT.html#licenseText" + }, + "spdx": { + "osi": true, + "fsf": true, + "fsfAndOsi": true, + "includesDeprecated": false + }, + "fileName": "LICENSE" + } + ], + "uniqueLicenseIds": [ + "MIT" + ], + "composition": { + "extensions": [ + ".md", + ".js", + ".ts", + "", + ".map", + ".json" + ], + "files": [ + "LICENSE", + "README.md", + "docs\\docs\\api\\Agent.md", + "docs\\docs\\api\\BalancedPool.md", + "docs\\docs\\api\\CacheStorage.md", + "docs\\docs\\api\\CacheStore.md", + "docs\\docs\\api\\Client.md", + "docs\\docs\\api\\ClientStats.md", + "docs\\docs\\api\\Connector.md", + "docs\\docs\\api\\ContentType.md", + "docs\\docs\\api\\Cookies.md", + "docs\\docs\\api\\Debug.md", + "docs\\docs\\api\\DiagnosticsChannel.md", + "docs\\docs\\api\\Dispatcher.md", + "docs\\docs\\api\\EnvHttpProxyAgent.md", + "docs\\docs\\api\\Errors.md", + "docs\\docs\\api\\EventSource.md", + "docs\\docs\\api\\Fetch.md", + "docs\\docs\\api\\H2CClient.md", + "docs\\docs\\api\\MockAgent.md", + "docs\\docs\\api\\MockCallHistory.md", + "docs\\docs\\api\\MockCallHistoryLog.md", + "docs\\docs\\api\\MockClient.md", + "docs\\docs\\api\\MockErrors.md", + "docs\\docs\\api\\MockPool.md", + "docs\\docs\\api\\Pool.md", + "docs\\docs\\api\\PoolStats.md", + "docs\\docs\\api\\ProxyAgent.md", + "docs\\docs\\api\\RedirectHandler.md", + "docs\\docs\\api\\RetryAgent.md", + "docs\\docs\\api\\RetryHandler.md", + "docs\\docs\\api\\Util.md", + "docs\\docs\\api\\WebSocket.md", + "docs\\docs\\api\\api-lifecycle.md", + "docs\\docs\\best-practices\\client-certificate.md", + "docs\\docs\\best-practices\\mocking-request.md", + "docs\\docs\\best-practices\\proxy.md", + "docs\\docs\\best-practices\\writing-tests.md", + "index-fetch.js", + "index.d.ts", + "index.js", + "lib\\api\\abort-signal.js", + "lib\\api\\api-connect.js", + "lib\\api\\api-pipeline.js", + "lib\\api\\api-request.js", + "lib\\api\\api-stream.js", + "lib\\api\\api-upgrade.js", + "lib\\api\\index.js", + "lib\\api\\readable.js", + "lib\\api\\util.js", + "lib\\cache\\memory-cache-store.js", + "lib\\cache\\sqlite-cache-store.js", + "lib\\core\\connect.js", + "lib\\core\\constants.js", + "lib\\core\\diagnostics.js", + "lib\\core\\errors.js", + "lib\\core\\request.js", + "lib\\core\\symbols.js", + "lib\\core\\tree.js", + "lib\\core\\util.js", + "lib\\dispatcher\\agent.js", + "lib\\dispatcher\\balanced-pool.js", + "lib\\dispatcher\\client-h1.js", + "lib\\dispatcher\\client-h2.js", + "lib\\dispatcher\\client.js", + "lib\\dispatcher\\dispatcher-base.js", + "lib\\dispatcher\\dispatcher.js", + "lib\\dispatcher\\env-http-proxy-agent.js", + "lib\\dispatcher\\fixed-queue.js", + "lib\\dispatcher\\h2c-client.js", + "lib\\dispatcher\\pool-base.js", + "lib\\dispatcher\\pool.js", + "lib\\dispatcher\\proxy-agent.js", + "lib\\dispatcher\\retry-agent.js", + "lib\\global.js", + "lib\\handler\\cache-handler.js", + "lib\\handler\\cache-revalidation-handler.js", + "lib\\handler\\decorator-handler.js", + "lib\\handler\\redirect-handler.js", + "lib\\handler\\retry-handler.js", + "lib\\handler\\unwrap-handler.js", + "lib\\handler\\wrap-handler.js", + "lib\\interceptor\\cache.js", + "lib\\interceptor\\dns.js", + "lib\\interceptor\\dump.js", + "lib\\interceptor\\redirect.js", + "lib\\interceptor\\response-error.js", + "lib\\interceptor\\retry.js", + "lib\\llhttp\\.gitkeep", + "lib\\llhttp\\constants.d.ts", + "lib\\llhttp\\constants.js", + "lib\\llhttp\\constants.js.map", + "lib\\llhttp\\llhttp-wasm.js", + "lib\\llhttp\\llhttp_simd-wasm.js", + "lib\\llhttp\\utils.d.ts", + "lib\\llhttp\\utils.js", + "lib\\llhttp\\utils.js.map", + "lib\\mock\\mock-agent.js", + "lib\\mock\\mock-call-history.js", + "lib\\mock\\mock-client.js", + "lib\\mock\\mock-errors.js", + "lib\\mock\\mock-interceptor.js", + "lib\\mock\\mock-pool.js", + "lib\\mock\\mock-symbols.js", + "lib\\mock\\mock-utils.js", + "lib\\mock\\pending-interceptors-formatter.js", + "lib\\util\\cache.js", + "lib\\util\\date.js", + "lib\\util\\stats.js", + "lib\\util\\timers.js", + "lib\\web\\cache\\cache.js", + "lib\\web\\cache\\cachestorage.js", + "lib\\web\\cache\\util.js", + "lib\\web\\cookies\\constants.js", + "lib\\web\\cookies\\index.js", + "lib\\web\\cookies\\parse.js", + "lib\\web\\cookies\\util.js", + "lib\\web\\eventsource\\eventsource-stream.js", + "lib\\web\\eventsource\\eventsource.js", + "lib\\web\\eventsource\\util.js", + "lib\\web\\fetch\\LICENSE", + "lib\\web\\fetch\\body.js", + "lib\\web\\fetch\\constants.js", + "lib\\web\\fetch\\data-url.js", + "lib\\web\\fetch\\dispatcher-weakref.js", + "lib\\web\\fetch\\formdata-parser.js", + "lib\\web\\fetch\\formdata.js", + "lib\\web\\fetch\\global.js", + "lib\\web\\fetch\\headers.js", + "lib\\web\\fetch\\index.js", + "lib\\web\\fetch\\request.js", + "lib\\web\\fetch\\response.js", + "lib\\web\\fetch\\util.js", + "lib\\web\\fetch\\webidl.js", + "lib\\web\\websocket\\connection.js", + "lib\\web\\websocket\\constants.js", + "lib\\web\\websocket\\events.js", + "lib\\web\\websocket\\frame.js", + "lib\\web\\websocket\\permessage-deflate.js", + "lib\\web\\websocket\\receiver.js", + "lib\\web\\websocket\\sender.js", + "lib\\web\\websocket\\stream\\websocketerror.js", + "lib\\web\\websocket\\stream\\websocketstream.js", + "lib\\web\\websocket\\util.js", + "lib\\web\\websocket\\websocket.js", + "package.json", + "scripts\\strip-comments.js", + "types\\README.md", + "types\\agent.d.ts", + "types\\api.d.ts", + "types\\balanced-pool.d.ts", + "types\\cache-interceptor.d.ts", + "types\\cache.d.ts", + "types\\client-stats.d.ts", + "types\\client.d.ts", + "types\\connector.d.ts", + "types\\content-type.d.ts", + "types\\cookies.d.ts", + "types\\diagnostics-channel.d.ts", + "types\\dispatcher.d.ts", + "types\\env-http-proxy-agent.d.ts", + "types\\errors.d.ts", + "types\\eventsource.d.ts", + "types\\fetch.d.ts", + "types\\formdata.d.ts", + "types\\global-dispatcher.d.ts", + "types\\global-origin.d.ts", + "types\\h2c-client.d.ts", + "types\\handlers.d.ts", + "types\\header.d.ts", + "types\\index.d.ts", + "types\\interceptors.d.ts", + "types\\mock-agent.d.ts", + "types\\mock-call-history.d.ts", + "types\\mock-client.d.ts", + "types\\mock-errors.d.ts", + "types\\mock-interceptor.d.ts", + "types\\mock-pool.d.ts", + "types\\patch.d.ts", + "types\\pool-stats.d.ts", + "types\\pool.d.ts", + "types\\proxy-agent.d.ts", + "types\\readable.d.ts", + "types\\retry-agent.d.ts", + "types\\retry-handler.d.ts", + "types\\util.d.ts", + "types\\utility.d.ts", + "types\\webidl.d.ts", + "types\\websocket.d.ts" + ], + "minified": [], + "unused": [], + "missing": [ + "node:sqlite" + ], + "required_files": [ + "lib\\global.js", + "lib\\dispatcher\\env-http-proxy-agent.js", + "lib\\web\\fetch.js", + "lib\\web\\fetch\\formdata.js", + "lib\\web\\fetch\\headers.js", + "lib\\web\\fetch\\response.js", + "lib\\web\\fetch\\request.js", + "lib\\web\\websocket\\events.js", + "lib\\web\\websocket\\websocket.js", + "lib\\web\\eventsource\\eventsource.js", + "lib\\api.js", + "lib\\dispatcher\\dispatcher.js", + "lib\\dispatcher\\client.js", + "lib\\dispatcher\\pool.js", + "lib\\dispatcher\\balanced-pool.js", + "lib\\dispatcher\\agent.js", + "lib\\dispatcher\\proxy-agent.js", + "lib\\dispatcher\\retry-agent.js", + "lib\\dispatcher\\h2c-client.js", + "lib\\core\\errors.js", + "lib\\core\\util.js", + "lib\\core\\connect.js", + "lib\\mock\\mock-client.js", + "lib\\mock\\mock-call-history.js", + "lib\\mock\\mock-agent.js", + "lib\\mock\\mock-pool.js", + "lib\\mock\\mock-errors.js", + "lib\\handler\\retry-handler.js", + "lib\\handler\\decorator-handler.js", + "lib\\handler\\redirect-handler.js", + "lib\\interceptor\\redirect.js", + "lib\\interceptor\\response-error.js", + "lib\\interceptor\\retry.js", + "lib\\interceptor\\dump.js", + "lib\\interceptor\\dns.js", + "lib\\interceptor\\cache.js", + "lib\\cache\\memory-cache-store.js", + "lib\\cache\\sqlite-cache-store.js", + "lib\\web\\fetch\\global.js", + "lib\\web\\cache\\cachestorage.js", + "lib\\core\\symbols.js", + "lib\\web\\cookies.js", + "lib\\web\\fetch\\data-url.js", + "lib\\web\\websocket\\stream\\websocketstream.js", + "lib\\web\\websocket\\stream\\websocketerror.js", + "lib\\api\\abort-signal.js", + "lib\\api\\readable.js", + "lib\\api\\api-request.js", + "lib\\api\\api-stream.js", + "lib\\api\\api-pipeline.js", + "lib\\api\\api-upgrade.js", + "lib\\api\\api-connect.js", + "lib\\util\\cache.js", + "lib\\core\\diagnostics.js", + "lib\\core\\constants.js", + "lib\\util\\timers.js", + "lib\\core\\tree.js", + "lib\\dispatcher\\dispatcher-base.js", + "lib\\dispatcher\\pool-base.js", + "lib\\llhttp\\constants.js", + "lib\\llhttp\\llhttp-wasm.js", + "lib\\llhttp\\llhttp_simd-wasm.js", + "lib\\web\\fetch\\body.js", + "lib\\util\\stats.js", + "lib\\core\\request.js", + "lib\\dispatcher\\client-h1.js", + "lib\\dispatcher\\client-h2.js", + "lib\\handler\\unwrap-handler.js", + "lib\\handler\\wrap-handler.js", + "lib\\dispatcher\\fixed-queue.js", + "lib\\util\\date.js", + "lib\\handler\\cache-handler.js", + "lib\\handler\\cache-revalidation-handler.js", + "lib\\llhttp\\utils.js", + "lib\\mock\\mock-symbols.js", + "lib\\mock\\mock-utils.js", + "lib\\mock\\pending-interceptors-formatter.js", + "lib\\mock\\mock-interceptor.js", + "lib\\web\\cache\\util.js", + "lib\\web\\fetch\\webidl.js", + "lib\\web\\fetch\\index.js", + "lib\\web\\fetch\\util.js", + "lib\\web\\cache\\cache.js", + "lib\\web\\cookies\\parse.js", + "lib\\web\\cookies\\util.js", + "lib\\web\\cookies\\constants.js", + "lib\\web\\eventsource\\util.js", + "lib\\web\\eventsource\\eventsource-stream.js", + "lib\\web\\fetch\\formdata-parser.js", + "lib\\web\\fetch\\constants.js", + "lib\\web\\fetch\\dispatcher-weakref.js", + "lib\\web\\websocket\\constants.js", + "lib\\web\\websocket\\util.js", + "lib\\web\\websocket\\frame.js", + "lib\\web\\websocket\\connection.js", + "lib\\web\\websocket\\permessage-deflate.js", + "lib\\web\\websocket\\receiver.js", + "lib\\web\\websocket\\sender.js" + ], + "required_nodejs": [ + "node:assert", + "node:async_hooks", + "node:stream", + "node:events", + "stream", + "node:net", + "node:tls", + "node:diagnostics_channel", + "node:util", + "node:http", + "node:buffer", + "node:querystring", + "node:http2", + "node:url", + "node:dns", + "node:console", + "node:crypto", + "node:zlib", + "node:perf_hooks", + "node:worker_threads", + "node:fs" + ], + "required_thirdparty": [ + "node:sqlite" + ], + "required_subpath": {} + }, + "repository": { + "type": "git", + "url": "git+https://github.com/nodejs/undici.git" + }, + "integrity": "6cd313748e3ddd158436a9cb313f5b301a8d7d8a", + "links": { + "npm": "https://www.npmjs.com/package/undici/v/7.10.0", + "homepage": "https://undici.nodejs.org", + "repository": "https://github.com/nodejs/undici" + } + } + }, + "vulnerabilities": [], + "metadata": { + "author": { + "name": "matteo.collina", + "email": "hello@matteocollina.com", + "npmAvatar": "/npm-avatar/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdmF0YXJVUkwiOiJodHRwczovL3MuZ3JhdmF0YXIuY29tL2F2YXRhci9jM2ZjNzM3MGJjMDk1MWZiYTk0NGI3YjhjYWM1YjljYz9zaXplPTUwJmRlZmF1bHQ9cmV0cm8ifQ.J2Uu8Dfsu7sVPjnVTSTjbFO0Lenbufv6l9a3m3OrJNc" + }, + "homepage": "https://undici.nodejs.org", + "publishedCount": 244, + "lastVersion": "7.10.0", + "lastUpdateAt": "2025-05-20T07:19:22.524Z", + "hasReceivedUpdateInOneYear": true, + "hasManyPublishers": true, + "hasChangedAuthor": false, + "maintainers": [ + { + "name": "matteo.collina", + "email": "hello@matteocollina.com", + "npmAvatar": "/npm-avatar/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdmF0YXJVUkwiOiJodHRwczovL3MuZ3JhdmF0YXIuY29tL2F2YXRhci9jM2ZjNzM3MGJjMDk1MWZiYTk0NGI3YjhjYWM1YjljYz9zaXplPTUwJmRlZmF1bHQ9cmV0cm8ifQ.J2Uu8Dfsu7sVPjnVTSTjbFO0Lenbufv6l9a3m3OrJNc" + }, + { + "name": "ronag", + "email": "ronagy@icloud.com", + "npmAvatar": "/npm-avatar/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdmF0YXJVUkwiOiJodHRwczovL3MuZ3JhdmF0YXIuY29tL2F2YXRhci9kZDBjNDc0YzQwMTYyNTFiMzUyYmQ3MTExMjU1MTMxND9zaXplPTUwJmRlZmF1bHQ9cmV0cm8ifQ.oAkv4YQXyWUdFDQGvyKz7jfgXvXV9D-pLZxlDvWoYuE" + }, + { + "name": "ethan_arrowood", + "email": "ethan@arrowood.dev", + "npmAvatar": "/npm-avatar/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdmF0YXJVUkwiOiJodHRwczovL3MuZ3JhdmF0YXIuY29tL2F2YXRhci9jNTgyMDZjZGVkN2I1N2ZjMWNlMGVhZDRmZjQzZTU2MT9zaXplPTUwJmRlZmF1bHQ9cmV0cm8ifQ.gMXL8-kt8M6zfmmygti6RYuWABM1MboI-VcpcOJByjo" + } + ], + "publishers": [ + { + "name": "matteo.collina", + "email": "hello@matteocollina.com", + "version": "7.10.0", + "at": "2025-05-20T07:19:22.524Z", + "npmAvatar": "/npm-avatar/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdmF0YXJVUkwiOiJodHRwczovL3MuZ3JhdmF0YXIuY29tL2F2YXRhci9jM2ZjNzM3MGJjMDk1MWZiYTk0NGI3YjhjYWM1YjljYz9zaXplPTUwJmRlZmF1bHQ9cmV0cm8ifQ.J2Uu8Dfsu7sVPjnVTSTjbFO0Lenbufv6l9a3m3OrJNc" + }, + { + "name": "ronag", + "email": "ronagy@icloud.com", + "version": "6.20.1", + "at": "2024-10-14T12:19:28.069Z", + "npmAvatar": "/npm-avatar/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdmF0YXJVUkwiOiJodHRwczovL3MuZ3JhdmF0YXIuY29tL2F2YXRhci9kZDBjNDc0YzQwMTYyNTFiMzUyYmQ3MTExMjU1MTMxND9zaXplPTUwJmRlZmF1bHQ9cmV0cm8ifQ.oAkv4YQXyWUdFDQGvyKz7jfgXvXV9D-pLZxlDvWoYuE" + }, + { + "name": "ethan_arrowood", + "email": "ethan@arrowood.dev", + "version": "5.26.3", + "at": "2023-10-11T19:12:36.882Z", + "npmAvatar": "/npm-avatar/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdmF0YXJVUkwiOiJodHRwczovL3MuZ3JhdmF0YXIuY29tL2F2YXRhci9jNTgyMDZjZGVkN2I1N2ZjMWNlMGVhZDRmZjQzZTU2MT9zaXplPTUwJmRlZmF1bHQ9cmV0cm8ifQ.gMXL8-kt8M6zfmmygti6RYuWABM1MboI-VcpcOJByjo" + } + ], + "integrity": { + "7.10.0": "6cd313748e3ddd158436a9cb313f5b301a8d7d8a" + } + } + }, + "lru-cache": { + "versions": { + "11.1.0": { + "id": 4, + "usedBy": { + "@openally/httpie": "1.0.0" + }, + "isDevDependency": false, + "existOnRemoteRegistry": true, + "flags": [ + "hasMinifiedCode", + "hasManyPublishers" + ], + "warnings": [], + "dependencyCount": 0, + "gitUrl": null, + "alias": {}, + "description": "A cache object that deletes the least-recently-used items.", + "size": 819995, + "author": { + "name": "Isaac Z. Schlueter", + "email": "i@izs.me" + }, + "engines": { + "node": "20 || >=22" + }, + "scripts": { + "build": "npm run prepare", + "prepare": "tshy && bash fixup.sh", + "pretest": "npm run prepare", + "presnap": "npm run prepare", + "test": "tap", + "snap": "tap", + "preversion": "npm test", + "postversion": "npm publish", + "prepublishOnly": "git push origin --follow-tags", + "format": "prettier --write .", + "typedoc": "typedoc --tsconfig ./.tshy/esm.json ./src/*.ts", + "benchmark-results-typedoc": "bash scripts/benchmark-results-typedoc.sh", + "prebenchmark": "npm run prepare", + "benchmark": "make -C benchmark", + "preprofile": "npm run prepare", + "profile": "make -C benchmark profile" + }, + "licenses": [ + { + "licenses": { + "ISC": "https://spdx.org/licenses/ISC.html#licenseText" + }, + "spdx": { + "osi": true, + "fsf": true, + "fsfAndOsi": true, + "includesDeprecated": false + }, + "fileName": "package.json" + }, + { + "licenses": { + "ISC": "https://spdx.org/licenses/ISC.html#licenseText" + }, + "spdx": { + "osi": true, + "fsf": true, + "fsfAndOsi": true, + "includesDeprecated": false + }, + "fileName": "LICENSE" + } + ], + "uniqueLicenseIds": [ + "ISC" + ], + "composition": { + "extensions": [ + ".ts", + ".map", + ".js", + ".json", + "", + ".md" + ], + "files": [ + "LICENSE", + "README.md", + "dist\\commonjs\\index.d.ts", + "dist\\commonjs\\index.d.ts.map", + "dist\\commonjs\\index.js", + "dist\\commonjs\\index.js.map", + "dist\\commonjs\\index.min.js", + "dist\\commonjs\\index.min.js.map", + "dist\\commonjs\\package.json", + "dist\\esm\\index.d.ts", + "dist\\esm\\index.d.ts.map", + "dist\\esm\\index.js", + "dist\\esm\\index.js.map", + "dist\\esm\\index.min.js", + "dist\\esm\\index.min.js.map", + "dist\\esm\\package.json", + "package.json" + ], + "minified": [ + "dist\\commonjs\\index.min.js", + "dist\\esm\\index.min.js" + ], + "unused": [], + "missing": [], + "required_files": [], + "required_nodejs": [], + "required_thirdparty": [], + "required_subpath": {} + }, + "repository": { + "type": "git", + "url": "git://github.com/isaacs/node-lru-cache.git" + }, + "integrity": "3349f1325b6de04cb88ccbd42728afaa69a2eced", + "links": { + "npm": "https://www.npmjs.com/package/lru-cache/v/11.1.0", + "homepage": "https://github.com/isaacs/node-lru-cache#readme", + "repository": "https://github.com/isaacs/node-lru-cache" + } + } + }, + "vulnerabilities": [], + "metadata": { + "author": { + "name": "Isaac Z. Schlueter", + "email": "i@izs.me", + "npmAvatar": "/npm-avatar/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdmF0YXJVUkwiOiJodHRwczovL3MuZ3JhdmF0YXIuY29tL2F2YXRhci83M2EyYjI0ZGFlY2I5NzZhZjgxZTAxMGI3YTNjZTNjNj9zaXplPTUwJmRlZmF1bHQ9cmV0cm8ifQ.ZFhE1nM8V3YD3hmGM9kkVTinxFMbl1fdyav5i1R7LY0" + }, + "homepage": "https://github.com/isaacs/node-lru-cache#readme", + "publishedCount": 144, + "lastVersion": "11.1.0", + "lastUpdateAt": "2025-03-24T15:14:18.553Z", + "hasReceivedUpdateInOneYear": true, + "hasManyPublishers": true, + "hasChangedAuthor": false, + "maintainers": [ + { + "name": "isaacs", + "email": "i@izs.me", + "npmAvatar": "/npm-avatar/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdmF0YXJVUkwiOiJodHRwczovL3MuZ3JhdmF0YXIuY29tL2F2YXRhci83M2EyYjI0ZGFlY2I5NzZhZjgxZTAxMGI3YTNjZTNjNj9zaXplPTUwJmRlZmF1bHQ9cmV0cm8ifQ.ZFhE1nM8V3YD3hmGM9kkVTinxFMbl1fdyav5i1R7LY0" + } + ], + "publishers": [ + { + "name": "isaacs", + "email": "i@izs.me", + "version": "11.1.0", + "at": "2025-03-24T15:14:18.553Z", + "npmAvatar": "/npm-avatar/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdmF0YXJVUkwiOiJodHRwczovL3MuZ3JhdmF0YXIuY29tL2F2YXRhci83M2EyYjI0ZGFlY2I5NzZhZjgxZTAxMGI3YTNjZTNjNj9zaXplPTUwJmRlZmF1bHQ9cmV0cm8ifQ.ZFhE1nM8V3YD3hmGM9kkVTinxFMbl1fdyav5i1R7LY0" + } + ], + "integrity": { + "11.1.0": "3349f1325b6de04cb88ccbd42728afaa69a2eced" + } + } + }, + "@openally/result": { + "versions": { + "1.3.0": { + "id": 5, + "usedBy": { + "@openally/httpie": "1.0.0" + }, + "isDevDependency": false, + "existOnRemoteRegistry": true, + "flags": [ + "hasManyPublishers" + ], + "warnings": [], + "dependencyCount": 0, + "gitUrl": null, + "alias": {}, + "description": "Another inspired Rust's Result implementation.", + "size": 19407, + "author": { + "name": "GENTILHOMME Thomas", + "email": "gentilhomme.thomas@gmail.com" + }, + "engines": { + "node": ">=16.9.x" + }, + "scripts": { + "build": "tsup src/index.ts --format cjs,esm --dts --clean", + "prepublishOnly": "npm run build", + "test": "glob -c \"tsx --test\" \"./test/**/*.spec.ts\"", + "coverage": "c8 -r html npm test", + "lint": "cross-env eslint src/**/*.ts" + }, + "licenses": [ + { + "licenses": { + "MIT": "https://spdx.org/licenses/MIT.html#licenseText" + }, + "spdx": { + "osi": true, + "fsf": true, + "fsfAndOsi": true, + "includesDeprecated": false + }, + "fileName": "package.json" + } + ], + "uniqueLicenseIds": [ + "MIT" + ], + "composition": { + "extensions": [ + ".mts", + ".ts", + ".js", + ".mjs", + ".json", + ".md" + ], + "files": [ + "README.md", + "dist\\index.d.mts", + "dist\\index.d.ts", + "dist\\index.js", + "dist\\index.mjs", + "package.json" + ], + "minified": [], + "unused": [], + "missing": [], + "required_files": [], + "required_nodejs": [], + "required_thirdparty": [], + "required_subpath": {} + }, + "integrity": "79f2d1df46738fb5bcdd48afb6a6fe3e260666fd", + "links": { + "npm": "https://www.npmjs.com/package/@openally/result/v/1.3.0", + "homepage": null, + "repository": null + } + } + }, + "vulnerabilities": [], + "metadata": { + "author": { + "name": "GENTILHOMME Thomas", + "email": "gentilhomme.thomas@gmail.com", + "npmAvatar": "/npm-avatar/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdmF0YXJVUkwiOiJodHRwczovL3MuZ3JhdmF0YXIuY29tL2F2YXRhci8xY2U2ODM4ZDU0ZGQ1NGNiNWM3NTZhZTY4YmNiYjQ1Yj9zaXplPTUwJmRlZmF1bHQ9cmV0cm8ifQ.zuCk5kxPtbbn7N259ilOmYpLx58-C5V2dkpie8Q-TVQ" + }, + "homepage": null, + "publishedCount": 5, + "lastVersion": "1.3.0", + "lastUpdateAt": "2024-08-07T18:30:41.149Z", + "hasReceivedUpdateInOneYear": true, + "hasManyPublishers": true, + "hasChangedAuthor": false, + "maintainers": [ + { + "name": "pierred", + "email": "pierredemailly.pro@gmail.com", + "npmAvatar": "/npm-avatar/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdmF0YXJVUkwiOiJodHRwczovL3MuZ3JhdmF0YXIuY29tL2F2YXRhci8yODcyNmM4NmY4ZjAwOGM0YTk5NjYwMDQ0YjY1NmE3Yj9zaXplPTUwJmRlZmF1bHQ9cmV0cm8ifQ.h_8Lqq_v8bwfjKenZivmoRV9DKlAY0hc0ojgpnAyg4s" + }, + { + "name": "fraxken", + "email": "gentilhomme.thomas@gmail.com", + "npmAvatar": "/npm-avatar/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdmF0YXJVUkwiOiJodHRwczovL3MuZ3JhdmF0YXIuY29tL2F2YXRhci8xY2U2ODM4ZDU0ZGQ1NGNiNWM3NTZhZTY4YmNiYjQ1Yj9zaXplPTUwJmRlZmF1bHQ9cmV0cm8ifQ.zuCk5kxPtbbn7N259ilOmYpLx58-C5V2dkpie8Q-TVQ" + } + ], + "publishers": [ + { + "name": "fraxken", + "email": "gentilhomme.thomas@gmail.com", + "version": "1.3.0", + "at": "2024-08-07T18:30:41.149Z", + "npmAvatar": "/npm-avatar/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdmF0YXJVUkwiOiJodHRwczovL3MuZ3JhdmF0YXIuY29tL2F2YXRhci8xY2U2ODM4ZDU0ZGQ1NGNiNWM3NTZhZTY4YmNiYjQ1Yj9zaXplPTUwJmRlZmF1bHQ9cmV0cm8ifQ.zuCk5kxPtbbn7N259ilOmYpLx58-C5V2dkpie8Q-TVQ" + } + ], + "integrity": { + "1.3.0": "79f2d1df46738fb5bcdd48afb6a6fe3e260666fd" + } + } + }, + "@openally/httpie": { + "versions": { + "1.0.0": { + "id": 0, + "usedBy": {}, + "isDevDependency": false, + "existOnRemoteRegistry": false, + "flags": [ + "hasDependencies" + ], + "warnings": [], + "dependencyCount": 5, + "gitUrl": null, + "alias": {}, + "description": "", + "size": 0, + "author": { + "name": "GENTILHOMME Thomas", + "email": "gentilhomme.thomas@gmail.com" + }, + "engines": {}, + "scripts": {}, + "licenses": [], + "uniqueLicenseIds": [], + "composition": { + "extensions": [], + "files": [], + "minified": [], + "unused": [], + "missing": [], + "required_files": [], + "required_nodejs": [], + "required_thirdparty": [], + "required_subpath": [] + }, + "links": { + "npm": null, + "homepage": "https://github.com/OpenAlly/httpie#readme", + "repository": "https://github.com/OpenAlly/httpie" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/OpenAlly/httpie.git" + } + } + }, + "vulnerabilities": [], + "metadata": { + "publishedCount": 0, + "lastUpdateAt": "2025-06-01T11:39:08.578Z", + "lastVersion": "N/A", + "hasChangedAuthor": false, + "hasManyPublishers": false, + "hasReceivedUpdateInOneYear": true, + "homepage": "https://github.com/OpenAlly/httpie#readme", + "author": { + "name": "GENTILHOMME Thomas", + "email": "gentilhomme.thomas@gmail.com" + }, + "publishers": [], + "maintainers": [], + "integrity": {} + } + } + } +} \ No newline at end of file diff --git a/package.json b/package.json index 51dfc75..2373401 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "build": "tsc", "lint": "eslint src test", "prepublishOnly": "npm run build", - "test": "jest" + "test": "tsx --test ./**/*.test.ts" }, "repository": { "type": "git", @@ -32,15 +32,13 @@ "@openally/config.eslint": "^2.1.0", "@openally/config.typescript": "^1.0.3", "@types/content-type": "^1.1.8", - "@types/jest": "^29.5.11", - "@types/lru-cache": "^7.10.10", + "@types/lru-cache": "^7.10.9", "@types/node": "^22.0.0", "@types/statuses": "^2.0.4", - "dotenv": "^16.3.1", "fastify": "^5.2.0", - "jest": "^29.7.0", + "is-html": "^3.1.0", "p-ratelimit": "^1.0.1", - "ts-jest": "^29.1.1", + "tsx": "^4.19.4", "typescript": "^5.3.3" }, "dependencies": { @@ -48,6 +46,6 @@ "content-type": "^1.0.5", "lru-cache": "^11.0.0", "statuses": "^2.0.1", - "undici": "^6.9.0" + "undici": "^7.10.0" } } diff --git a/src/agents.ts b/src/agents.ts index 1e7f40d..f8c985d 100644 --- a/src/agents.ts +++ b/src/agents.ts @@ -8,10 +8,6 @@ import { type HttpMethod, type WebDavMethod } from "./request.js"; -import { getCurrentEnv } from "./utils.js"; - -// CONSTANTS -const kEnvName = getCurrentEnv(); /** * @see https://en.wikipedia.org/wiki/Page_replacement_algorithm @@ -32,11 +28,8 @@ export interface computedUrlAndAgent { */ export interface CustomHttpAgent { customPath: string; - domains: Set; + origin: string; agent: Agent | ProxyAgent | MockAgent; - prod: string; - preprod: string; - dev: string; limit?: InlineCallbackAction; } @@ -49,12 +42,15 @@ export const agents: Set = new Set(); * const URI = computeAgentPath("/windev/ws_monitoring", windev); * assert.strictEqual(URI, "https://ws-dev.myunisoft.fr/ws_monitoring"); */ -export function isAgentPathMatchingURI(uri: string, agent: CustomHttpAgent): URL | null { +export function isAgentPathMatchingURI( + uri: string, + agent: CustomHttpAgent +): URL | null { // Note: we want to match both '/path/xxx...' and 'path/xxx...' const localCustomPath = uri.charAt(0) === "/" ? `/${agent.customPath}` : agent.customPath; return uri.startsWith(localCustomPath) ? - new URL(uri.slice(localCustomPath.length), agent[kEnvName]) : + new URL(uri.slice(localCustomPath.length), agent.origin) : null; } @@ -89,7 +85,7 @@ export function detectAgentFromURI(uri: URL): CustomHttpAgent | null { const hostname = uri.hostname; for (const agent of agents) { - if (agent.domains.has(hostname)) { + if (new URL(agent.origin).hostname === hostname) { return agent; } } diff --git a/src/class/HttpieCommonError.ts b/src/class/HttpieCommonError.ts index d2119d6..75b54b5 100644 --- a/src/class/HttpieCommonError.ts +++ b/src/class/HttpieCommonError.ts @@ -1,5 +1,5 @@ -// Import Third-party Dependencies -import { type IncomingHttpHeaders } from "undici/types/header.js"; +// Import Node.js Dependencies +import type { IncomingHttpHeaders } from "node:http"; type CommonResponseData = { statusCode: number; diff --git a/src/index.ts b/src/index.ts index 18265ca..4bafbab 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,9 +8,7 @@ import { Headers, type HeadersInit, FormData, - File, type BodyInit, - BodyMixin, MockAgent, mockErrors, MockPool, @@ -36,9 +34,7 @@ export { Headers, type HeadersInit, FormData, - File, type BodyInit, - BodyMixin, MockAgent, mockErrors, MockPool, diff --git a/src/request.ts b/src/request.ts index ed19da6..d55e2ac 100644 --- a/src/request.ts +++ b/src/request.ts @@ -95,7 +95,7 @@ export async function request( const statusCode = requestResponse.statusCode; const responseHandler = new HttpieResponseHandler(requestResponse); - let data; + let data: any; if (options.mode === "parse" || !options.mode) { data = await responseHandler.getData("parse"); } diff --git a/src/utils.ts b/src/utils.ts index 95a3bd1..b465a76 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -24,7 +24,9 @@ export function isAsyncIterable(value: any): boolean { * @description Get a valid Node.js charset from the "content-type" http header. * @see https://nodejs.org/api/buffer.html#buffer_buffers_and_character_encodings */ -export function getEncodingCharset(charset = kDefaultEncodingCharset): BufferEncoding { +export function getEncodingCharset( + charset = kDefaultEncodingCharset +): BufferEncoding { if (Buffer.isEncoding(charset)) { return charset as BufferEncoding; } @@ -37,7 +39,9 @@ export function getEncodingCharset(charset = kDefaultEncodingCharset): BufferEnc * - User-agent * - Authorization */ -export function createHeaders(options: Partial>): IncomingHttpHeaders { +export function createHeaders( + options: Partial> +): IncomingHttpHeaders { const headers = Object.assign({ ...DEFAULT_HEADER }, options.headers ?? {}); if (options.authorization) { @@ -47,7 +51,9 @@ export function createHeaders(options: Partial { - it("it should create an HttpieOnHttpError with the properties of RequestResponse", async() => { - expect.assertions(2); + it("it should create an HttpieOnHttpError with the properties of RequestResponse", async(t) => { + t.plan(2); const expectedResponseData = { statusCode: 404, @@ -34,8 +37,13 @@ describe("HttpieOnHttpError", () => { await request(targetUrl.method as any, path); } catch (error: any) { - expect(error.name).toStrictEqual("HttpieOnHttpError"); - expect(error).toMatchObject(expectedResponseData); + const { data, headers, statusCode, statusMessage } = error; + + t.assert.equal(error.name, "HttpieOnHttpError"); + t.assert.deepEqual( + { data, headers, statusCode, statusMessage }, + expectedResponseData + ); } }); }); diff --git a/test/__snapshots__/request.spec.ts.snap b/test/__snapshots__/request.spec.ts.snap deleted file mode 100644 index c906e83..0000000 --- a/test/__snapshots__/request.spec.ts.snap +++ /dev/null @@ -1,23 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`http.get should throw a 404 Not Found error because the path is not known 1`] = ` -" - -404 Not Found - -

Not Found

-

The requested URL was not found on this server.

- -" -`; - -exports[`http.safeGet should throw a 404 Not Found error because the path is not known 1`] = ` -" - -404 Not Found - -

Not Found

-

The requested URL was not found on this server.

- -" -`; diff --git a/test/agents.spec.ts b/test/agents.test.ts similarity index 59% rename from test/agents.spec.ts rename to test/agents.test.ts index b7ce8e3..45f769a 100644 --- a/test/agents.spec.ts +++ b/test/agents.test.ts @@ -1,3 +1,7 @@ +// Import Node.js Dependencies +import { beforeEach, describe, it } from "node:test"; +import assert from "node:assert"; + // Import Internal Dependencies import { windev } from "./helpers"; import * as Agents from "../src/agents"; @@ -7,44 +11,43 @@ const kWindevMonitoringURL = "https://ws.dev.myunisoft.tech/ws_monitoring"; describe("agents", () => { it("should be an Array of CustomHttpAgent and must remain extensible", () => { - expect(Agents.agents instanceof Set).toStrictEqual(true); - - expect(Object.isExtensible(Agents.agents)).toStrictEqual(true); + assert.ok(Agents.agents instanceof Set); + assert.ok(Object.isExtensible(Agents.agents)); }); }); describe("isAgentPathMatchingURI", () => { it("should compute the path because it start with '/windev'", () => { const result = Agents.isAgentPathMatchingURI("/windev/ws_monitoring", windev); - expect(result?.href).toStrictEqual(kWindevMonitoringURL); + assert.strictEqual(result?.href, kWindevMonitoringURL); // Same but without '/' at the beginning const result2 = Agents.isAgentPathMatchingURI("windev/ws_monitoring", windev); - expect(result2?.href).toStrictEqual(kWindevMonitoringURL); + assert.strictEqual(result2?.href, kWindevMonitoringURL); }); it("should not compute the path and return null instead", () => { const result = Agents.isAgentPathMatchingURI("/xd/ws_monitoring", windev); - expect(result).toStrictEqual(null); + assert.strictEqual(result, null); }); }); describe("computeURIOnAllAgents", () => { it("should compute with windev agent", () => { const result = Agents.computeURIOnAllAgents("/windev/ws_monitoring"); - expect(result.url.href).toStrictEqual(kWindevMonitoringURL); - expect(result.agent).toStrictEqual(windev.agent); + assert.strictEqual(result.url.href, kWindevMonitoringURL); + assert.strictEqual(result.agent, windev.agent); }); it("should return the given URI with no computation", () => { const result = Agents.computeURIOnAllAgents("https://www.google.fr/"); - expect(result.url.href).toStrictEqual("https://www.google.fr/"); - expect(result.agent).toStrictEqual(null); + assert.strictEqual(result.url.href, "https://www.google.fr/"); + assert.strictEqual(result.agent, null); }); it("should throw an Error if no computation because that's not a valid URI", () => { - expect(() => Agents.computeURIOnAllAgents("/xdd/healthz")).toThrow(); + assert.throws(() => Agents.computeURIOnAllAgents("/xdd/healthz")); }); }); @@ -52,63 +55,63 @@ describe("detectAgentFromURI", () => { it("should detect windev agent with URI hostname", () => { const returnedAgent = Agents.detectAgentFromURI(new URL("https://ws.dev.myunisoft.tech")); - expect(returnedAgent).toStrictEqual(windev); + assert.strictEqual(returnedAgent, windev); }); it("should return null if hostname is not internaly known", () => { const returnedAgent = Agents.detectAgentFromURI(new URL("https://www.google.fr/")); - expect(returnedAgent).toStrictEqual(null); + assert.strictEqual(returnedAgent, null); }); }); describe("computeURI", () => { beforeEach(() => { - Agents.URICache.clear(); + Agents.URI_CACHE.clear(); }); it("should compute a windev URI (as string)", () => { const result = Agents.computeURI("GET", kWindevMonitoringURL); - expect(result.url.href).toStrictEqual(kWindevMonitoringURL); - expect(result.agent).toStrictEqual(windev.agent); + assert.strictEqual(result.url.href, kWindevMonitoringURL); + assert.strictEqual(result.agent, windev.agent); - expect(Agents.URICache.has("GET" + kWindevMonitoringURL)).toStrictEqual(true); + assert.strictEqual(Agents.URI_CACHE.has("GET" + kWindevMonitoringURL), true); }); it("should compute a windev URI (as WHATWG URL)", () => { const localURL = new URL(kWindevMonitoringURL); const result = Agents.computeURI("POST", localURL); - expect(result.url.href).toStrictEqual(kWindevMonitoringURL); - expect(result.agent).toStrictEqual(windev.agent); + assert.strictEqual(result.url.href, kWindevMonitoringURL); + assert.strictEqual(result.agent, windev.agent); - expect(Agents.URICache.has("POST" + localURL.toString())).toStrictEqual(true); + assert.strictEqual(Agents.URI_CACHE.has("POST" + localURL.toString()), true); }); it("should return cached entry", () => { - Agents.URICache.set("GET" + kWindevMonitoringURL, true as any); + Agents.URI_CACHE.set("GET" + kWindevMonitoringURL, true as any); const result = Agents.computeURI("GET", kWindevMonitoringURL) as unknown as boolean; - expect(result).toStrictEqual(true); + assert.strictEqual(result, true); }); it("should not return cached entry because method doesn't match", () => { - Agents.URICache.set("POST" + kWindevMonitoringURL, true as any); + Agents.URI_CACHE.set("POST" + kWindevMonitoringURL, true as any); const result = Agents.computeURI("GET", kWindevMonitoringURL); - expect(result.url.href).toStrictEqual(kWindevMonitoringURL); - expect(result.agent).toStrictEqual(windev.agent); + assert.strictEqual(result.url.href, kWindevMonitoringURL); + assert.strictEqual(result.agent, windev.agent); - expect(Agents.URICache.has("GET" + kWindevMonitoringURL)).toStrictEqual(true); + assert.strictEqual(Agents.URI_CACHE.has("GET" + kWindevMonitoringURL), true); }); it("should compute an URL not related to any local agents", () => { const stringURL = "https://www.linkedin.com/feed/"; const result = Agents.computeURI("GET", new URL("", stringURL)); - expect(result.url.href).toStrictEqual(stringURL); - expect(result.agent).toStrictEqual(null); - expect(result.limit).toStrictEqual(undefined); + assert.strictEqual(result.url.href, stringURL); + assert.strictEqual(result.agent, null); + assert.strictEqual(result.limit, undefined); }); }); diff --git a/test/helpers.ts b/test/helpers.ts index b2b287e..4479f19 100644 --- a/test/helpers.ts +++ b/test/helpers.ts @@ -6,15 +6,10 @@ import { CustomHttpAgent, agents } from "../src/agents"; const windev: CustomHttpAgent = { customPath: "windev", - domains: new Set([ - "ws.dev.myunisoft.tech" - ]), + origin: "https://ws.dev.myunisoft.tech", agent: new undici.Agent({ connections: 500 - }), - prod: "https://ws.dev.myunisoft.tech", - preprod: "https://ws.dev.myunisoft.tech", - dev: "https://ws.dev.myunisoft.tech" + }) }; agents.add(windev); diff --git a/test/jest.setup.js b/test/jest.setup.js deleted file mode 100644 index 33a2fba..0000000 --- a/test/jest.setup.js +++ /dev/null @@ -1 +0,0 @@ -jest.setTimeout(120_000); diff --git a/test/request.spec.ts b/test/request.test.ts similarity index 64% rename from test/request.spec.ts rename to test/request.test.ts index 7c94354..6f07a54 100644 --- a/test/request.spec.ts +++ b/test/request.test.ts @@ -1,20 +1,23 @@ +// Import Node.js Dependencies +import { describe, it, before, after } from "node:test"; +import assert from "node:assert"; + // Import Third-party Dependencies import { FastifyInstance } from "fastify"; +import isHtml from "is-html"; // Import Internal Dependencies -import { get, post, put, patch, del, safeGet } from "../src/index"; -import { isHTTPError } from "../src/utils"; - -// Helpers and mock -import { createServer } from "./server/index"; -import { windev } from "./helpers"; +import { get, post, put, patch, del, safeGet } from "../src/index.js"; +import { isHTTPError } from "../src/utils.js"; +import { createServer } from "./server/index.js"; +import { windev } from "./helpers.js"; let httpServer: FastifyInstance; -beforeAll(async() => { +before(async() => { httpServer = await createServer(); }); -afterAll(async() => { +after(async() => { await httpServer.close(); }); @@ -22,8 +25,8 @@ describe("http.get", () => { it("should GET uptime from local fastify server", async() => { const { data } = await get<{ uptime: number; }>("/local/"); - expect("uptime" in data).toStrictEqual(true); - expect(typeof data.uptime).toStrictEqual("number"); + assert.ok("uptime" in data); + assert.equal(typeof data.uptime, "number"); }); it("should GET query parameters provided to fastify", async() => { @@ -33,15 +36,16 @@ describe("http.get", () => { }) }); - expect("name" in data).toStrictEqual(true); - expect(data.name).toStrictEqual("foobar"); + assert.ok("name" in data); + assert.equal(data.name, "foobar"); }); - it("should GET uptime by following an HTTP redirection from local fastify server", async() => { + // FIX: maxRedirections do not work + it.skip("should GET uptime by following an HTTP redirection from local fastify server", async() => { const { data } = await get<{ uptime: number; }>("/local/redirect", { maxRedirections: 1 }); - expect("uptime" in data).toStrictEqual(true); - expect(typeof data.uptime).toStrictEqual("number"); + assert.ok("uptime" in data); + assert.equal(typeof data.uptime, "number"); }); it("should GET uptime through a limit function handler from local fastify server", async() => { @@ -54,15 +58,15 @@ describe("http.get", () => { }; const { data } = await get<{ uptime: number; }>("/local/", { limit }); - expect("uptime" in data).toStrictEqual(true); - expect(typeof data.uptime).toStrictEqual("number"); - expect(executed).toStrictEqual(true); + assert.ok("uptime" in data); + assert.equal(typeof data.uptime, "number"); + assert.equal(executed, true); }); it("should GET response from windev ws-monitoring endpoint (without Agent)", async() => { const { data } = await get("/windev/ws_monitoring"); - expect(data).toStrictEqual(true); + assert.equal(data, true); }); it("should GET response from windev ws-monitoring endpoint (with Agent)", async() => { @@ -70,43 +74,43 @@ describe("http.get", () => { agent: windev.agent }); - expect(data).toStrictEqual(true); + assert.equal(data, true); }); it("should GET json response from node.js health endpoint", async() => { const { data } = await get("https://app.dev.myunisoft.tech/api/authenticate/healthz"); - expect(Object.keys(data).sort()).toMatchObject([ + assert.deepEqual(Object.keys(data).sort(), [ "status", "version", "description", "checks" ].sort()); }); - it("should throw a 404 Not Found error because the path is not known", async() => { - expect.assertions(4); + it("should throw a 404 Not Found error because the path is not known", async(t) => { + t.plan(4); try { await get("/windev/hlkezcjcke"); } catch (error: any) { - expect(error.name).toStrictEqual("HttpieOnHttpError"); - expect(error.statusCode).toStrictEqual(404); - expect(error.statusMessage).toStrictEqual("Not Found"); - expect(error.data).toMatchSnapshot(); + t.assert.equal(error.name, "HttpieOnHttpError"); + t.assert.equal(error.statusCode, 404); + t.assert.equal(error.statusMessage, "Not Found"); + t.assert.equal(isHtml(error.data), true); } }); - it("should throw a 'HttpieParserError' with jsonError endpoint from local fastify server", async() => { - expect.assertions(4); + it("should throw a 'HttpieParserError' with jsonError endpoint from local fastify server", async(t) => { + t.plan(4); const expectedPayload = "{ 'foo': bar }"; try { await get("/local/jsonError"); } catch (error: any) { - expect(error.name).toStrictEqual("ResponseParsingError"); - expect(error.reason.name).toStrictEqual("SyntaxError"); - expect(error.text).toStrictEqual(expectedPayload); - expect(error.buffer).toStrictEqual(Buffer.from(expectedPayload)); + t.assert.equal(error.name, "ResponseParsingError"); + t.assert.equal(error.reason.name, "SyntaxError"); + t.assert.equal(error.text, expectedPayload); + t.assert.equal(Buffer.from(expectedPayload).compare(error.buffer), 0); } }); }); @@ -123,8 +127,8 @@ describe("http.post", () => { }; const { data } = await post("https://jsonplaceholder.typicode.com/posts", { body }); - expect(typeof data.userId).toStrictEqual("number"); - expect(data).toMatchObject(body); + assert.equal(typeof data.userId, "number"); + assert.partialDeepStrictEqual(data, body); }); }); @@ -138,7 +142,7 @@ describe("http.put", () => { }; const { data } = await put("https://jsonplaceholder.typicode.com/posts/1", { body }); - expect(data).toEqual(body); + assert.deepEqual(data, body); }); }); @@ -153,7 +157,7 @@ describe("http.patch", () => { const { data } = await patch("https://jsonplaceholder.typicode.com/posts/1", { body: { title: "foo" } }); - expect(data).toMatchObject(body); + assert.partialDeepStrictEqual(data, body); }); }); @@ -162,7 +166,7 @@ describe("http.del", () => { const { statusCode } = await del("https://jsonplaceholder.typicode.com/posts/1", { body: { title: "foo" } }); - expect(statusCode).toStrictEqual(200); + assert.equal(statusCode, 200); }); }); @@ -170,26 +174,26 @@ describe("http.safeGet", () => { it("should GET uptime from local fastify server", async() => { const result = await safeGet<{ uptime: number; }, any>("/local/"); - expect(result.ok).toStrictEqual(true); + assert.ok(result.ok); const { data } = result.unwrap(); - expect("uptime" in data).toStrictEqual(true); - expect(typeof data.uptime).toStrictEqual("number"); + assert.ok("uptime" in data); + assert.equal(typeof data.uptime, "number"); }); - it("should throw a 404 Not Found error because the path is not known", async() => { - expect.assertions(5); + it("should throw a 404 Not Found error because the path is not known", async(t) => { + t.plan(4); const result = await safeGet("/windev/hlkezcjcke"); - expect(result.err).toStrictEqual(true); + assert.ok(result.err); if (result.err) { const error = result.val; if (isHTTPError(error)) { - expect(error.name).toStrictEqual("HttpieOnHttpError"); - expect(error.statusCode).toStrictEqual(404); - expect(error.statusMessage).toStrictEqual("Not Found"); - expect(error.data).toMatchSnapshot(); + t.assert.equal(error.name, "HttpieOnHttpError"); + t.assert.equal(error.statusCode, 404); + t.assert.equal(error.statusMessage, "Not Found"); + t.assert.equal(isHtml(error.data), true); } } }); diff --git a/test/request2.spec.ts b/test/request2.spec.ts deleted file mode 100644 index fb0f391..0000000 --- a/test/request2.spec.ts +++ /dev/null @@ -1,1993 +0,0 @@ -/* eslint-disable max-lines */ -// Import Node.js Dependencies -import { brotliCompress, deflate, gzip } from "node:zlib"; -import { promisify } from "node:util"; -import { randomInt } from "node:crypto"; - -// Import Third-party Dependencies -import { Interceptable, MockAgent, setGlobalDispatcher } from "undici"; - -// Import Internal Dependencies -import { request } from "../src/request"; -import { isHTTPError, isHttpieError } from "../src"; - -// CONSTANTS -const kUrl = "http://test.com"; -const kAsyncGzip = promisify(gzip); -const kAsyncBrotli = promisify(brotliCompress); -const kAsyncDeflate = promisify(deflate); - -// VARS -let pool: Interceptable; - -describe("Httpie.safeRequest", () => { - beforeAll(() => { - const mockAgent = new MockAgent(); - setGlobalDispatcher(mockAgent); - mockAgent.disableNetConnect(); - - pool = mockAgent.get(kUrl); - }); - - describe("with ThrowOnHttpError", () => { - describe("GET", () => { - it("should throw if the response status code is higher than 400", async() => { - expect.assertions(4); - - const target = { - method: "GET", - path: "/test" - }; - - const statusCode = randomInt(400, 503); - const headers = { "content-type": "text/html" }; - const payload = Buffer.from("Body"); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - try { - await request(target.method as any, kUrl + target.path); - } - catch (error: any) { - expect(isHTTPError(error)).toBeTruthy(); - expect(error.statusCode).toBe(statusCode); - expect(error.data).toBe(payload.toString()); - expect(error.headers).toMatchObject(headers); - } - }); - - it("should not throw if the response status code is lower than 400", async() => { - const target = { - method: "GET", - path: "/test" - }; - - const statusCode = randomInt(200, 399); - const headers = { "content-type": "text/html" }; - const payload = Buffer.from("Body"); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - const response = await request(target.method as any, kUrl + target.path); - - expect(response.statusCode).toBe(statusCode); - expect(response.data).toBe(payload.toString()); - expect(response.headers).toMatchObject(headers); - }); - }); - - describe("POST", () => { - it("should throw if the response status code is higher than 400", async() => { - expect.assertions(4); - - const target = { - method: "POST", - path: "/test" - }; - - const statusCode = randomInt(400, 503); - const headers = { "content-type": "text/html" }; - const payload = Buffer.from("Body"); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - try { - await request(target.method as any, kUrl + target.path); - } - catch (error: any) { - expect(isHTTPError(error)).toBeTruthy(); - expect(error.statusCode).toBe(statusCode); - expect(error.data).toBe(payload.toString()); - expect(error.headers).toMatchObject(headers); - } - }); - - it("should not throw if the response status code is lower than 400", async() => { - const target = { - method: "POST", - path: "/test" - }; - - const statusCode = randomInt(200, 399); - const headers = { "content-type": "text/html" }; - const payload = Buffer.from("Body"); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - const response = await request(target.method as any, kUrl + target.path); - - expect(response.statusCode).toBe(statusCode); - expect(response.data).toBe(payload.toString()); - expect(response.headers).toMatchObject(headers); - }); - }); - - describe("PUT", () => { - it("should throw if the response status code is higher than 400", async() => { - expect.assertions(4); - - const target = { - method: "PUT", - path: "/test" - }; - - const statusCode = randomInt(400, 503); - const headers = { "content-type": "text/html" }; - const payload = Buffer.from("Body"); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - try { - await request(target.method as any, kUrl + target.path); - } - catch (error: any) { - expect(isHTTPError(error)).toBeTruthy(); - expect(error.statusCode).toBe(statusCode); - expect(error.data).toBe(payload.toString()); - expect(error.headers).toMatchObject(headers); - } - }); - - it("should not throw if the response status code is lower than 400", async() => { - const target = { - method: "PUT", - path: "/test" - }; - - const statusCode = randomInt(200, 399); - const headers = { "content-type": "text/html" }; - const payload = Buffer.from("Body"); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - const response = await request(target.method as any, kUrl + target.path); - - expect(response.statusCode).toBe(statusCode); - expect(response.data).toBe(payload.toString()); - expect(response.headers).toMatchObject(headers); - }); - }); - - describe("DELETE", () => { - it("should throw if the response status code is higher than 400", async() => { - expect.assertions(4); - - const target = { - method: "DELETE", - path: "/test" - }; - - const statusCode = randomInt(400, 503); - const headers = { "content-type": "text/html" }; - const payload = Buffer.from("Body"); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - try { - await request(target.method as any, kUrl + target.path); - } - catch (error: any) { - expect(isHTTPError(error)).toBeTruthy(); - expect(error.statusCode).toBe(statusCode); - expect(error.data).toBe(payload.toString()); - expect(error.headers).toMatchObject(headers); - } - }); - - it("should not throw if the response status code is lower than 400", async() => { - const target = { - method: "DELETE", - path: "/test" - }; - - const statusCode = randomInt(200, 399); - const headers = { "content-type": "text/html" }; - const payload = Buffer.from("Body"); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - const response = await request(target.method as any, kUrl + target.path); - - expect(response.statusCode).toBe(statusCode); - expect(response.data).toBe(payload.toString()); - expect(response.headers).toMatchObject(headers); - }); - }); - }); - - describe("without ThrowOnHttpError", () => { - describe("GET", () => { - it("should not throw if the response status code is higher than 400", async() => { - const target = { - method: "GET", - path: "/test" - }; - - const statusCode = randomInt(400, 503); - const headers = { "content-type": "text/html" }; - const payload = Buffer.from("Body"); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - const response = await request(target.method as any, kUrl + target.path, { throwOnHttpError: false }); - - expect(response.statusCode).toBe(statusCode); - expect(response.data).toBe(payload.toString()); - expect(response.headers).toMatchObject(headers); - }); - - it("should not throw if the response status code is lower than 400", async() => { - const target = { - method: "GET", - path: "/test" - }; - - const statusCode = randomInt(200, 399); - const headers = { "content-type": "text/html" }; - const payload = Buffer.from("Body"); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - const response = await request(target.method as any, kUrl + target.path, { throwOnHttpError: false }); - - expect(response.statusCode).toBe(statusCode); - expect(response.data).toBe(payload.toString()); - expect(response.headers).toMatchObject(headers); - }); - }); - - describe("POST", () => { - it("should not throw if the response status code is higher than 400", async() => { - const target = { - method: "POST", - path: "/test" - }; - - const statusCode = randomInt(400, 503); - const headers = { "content-type": "text/html" }; - const payload = Buffer.from("Body"); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - const response = await request(target.method as any, kUrl + target.path, { throwOnHttpError: false }); - - expect(response.statusCode).toBe(statusCode); - expect(response.data).toBe(payload.toString()); - expect(response.headers).toMatchObject(headers); - }); - - it("should not throw if the response status code is lower than 400", async() => { - const target = { - method: "POST", - path: "/test" - }; - - const statusCode = randomInt(200, 399); - const headers = { "content-type": "text/html" }; - const payload = Buffer.from("Body"); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - const response = await request(target.method as any, kUrl + target.path, { throwOnHttpError: false }); - - expect(response.statusCode).toBe(statusCode); - expect(response.data).toBe(payload.toString()); - expect(response.headers).toMatchObject(headers); - }); - }); - - describe("PUT", () => { - it("should not throw if the response status code is higher than 400", async() => { - const target = { - method: "PUT", - path: "/test" - }; - - const statusCode = randomInt(400, 503); - const headers = { "content-type": "text/html" }; - const payload = Buffer.from("Body"); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - const response = await request(target.method as any, kUrl + target.path, { throwOnHttpError: false }); - - expect(response.statusCode).toBe(statusCode); - expect(response.data).toBe(payload.toString()); - expect(response.headers).toMatchObject(headers); - }); - - it("should not throw if the response status code is lower than 400", async() => { - const target = { - method: "PUT", - path: "/test" - }; - - const statusCode = randomInt(200, 399); - const headers = { "content-type": "text/html" }; - const payload = Buffer.from("Body"); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - const response = await request(target.method as any, kUrl + target.path, { throwOnHttpError: false }); - - expect(response.statusCode).toBe(statusCode); - expect(response.data).toBe(payload.toString()); - expect(response.headers).toMatchObject(headers); - }); - }); - - describe("DELETE", () => { - it("should not throw if the response status code is higher than 400", async() => { - const target = { - method: "DELETE", - path: "/test" - }; - - const statusCode = randomInt(400, 503); - const headers = { "content-type": "text/html" }; - const payload = Buffer.from("Body"); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - const response = await request(target.method as any, kUrl + target.path, { throwOnHttpError: false }); - - expect(response.statusCode).toBe(statusCode); - expect(response.data).toBe(payload.toString()); - expect(response.headers).toMatchObject(headers); - }); - - it("should not throw if the response status code is lower than 400", async() => { - const target = { - method: "DELETE", - path: "/test" - }; - - const statusCode = randomInt(200, 399); - const headers = { "content-type": "text/html" }; - const payload = Buffer.from("Body"); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - const response = await request(target.method as any, kUrl + target.path, { throwOnHttpError: false }); - - expect(response.statusCode).toBe(statusCode); - expect(response.data).toBe(payload.toString()); - expect(response.headers).toMatchObject(headers); - }); - }); - }); - - describe("PARSE mode (default)", () => { - describe("GET", () => { - it("should return a parsed response as text when 'content-type' header starts with 'text/'", async() => { - const target = { - method: "GET", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "text/klsmdkf" }; - const payload = "La data."; - const buf = Buffer.from(payload); - - pool.intercept(target).reply(statusCode, buf, { headers }); - - const response = await request(target.method as any, kUrl + target.path); - - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - expect(response.statusCode).toBe(statusCode); - }); - - it("should return a parsed response as object when 'content-type' header is set with 'application/json'", async() => { - const target = { - method: "GET", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "application/json" }; - const payload = { my: "object" }; - const buf = Buffer.from(JSON.stringify(payload)); - - pool.intercept(target).reply(statusCode, buf, { headers }); - - const response = await request(target.method as any, kUrl + target.path); - - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - expect(response.statusCode).toBe(statusCode); - }); - - it("should return a buffer when 'content-type' header is set with 'application/pdf'", async() => { - const target = { - method: "GET", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "application/pdf" }; - const payload = Buffer.from("mon pdf"); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - const response = await request(target.method as any, kUrl + target.path); - - expect(response.data).toStrictEqual(Buffer.from(payload)); - expect(response.headers).toMatchObject(headers); - expect(response.statusCode).toBe(statusCode); - }); - - it("should return a buffer when 'content-type' header is set with unsupported value", async() => { - const target = { - method: "GET", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "application/msword" }; - const payload = Buffer.from(JSON.stringify({ my: "object" })); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - const response = await request(target.method as any, kUrl + target.path); - - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - }); - - it("should throw when 'content-type' header is set with unsupported value", async() => { - expect.assertions(5); - - const target = { - method: "GET", - path: "/test" - }; - const statusCode = 200; - const headers = { "content-type": "unknown" }; - const payload = Buffer.from(JSON.stringify({ my: "object" })); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - try { - await request(target.method as any, kUrl + target.path); - } - catch (error: any) { - expect(isHttpieError(error)).toBeTruthy(); - expect(error.message).toStrictEqual( - "An unexpected error occurred when trying to parse the response body (reason: 'invalid media type')." - ); - expect(error.buffer).toStrictEqual(payload); - expect(error.headers).toMatchObject(headers); - expect(error.statusCode).toBe(200); - } - }); - - it("should throw when 'content-encoding' header is set with unsupported value", async() => { - expect.assertions(5); - - const target = { - method: "GET", - path: "/test" - }; - - const payload = await kAsyncGzip("Mon document"); - const headers = { "content-encoding": "unknown" }; - const statusCode = 200; - - pool.intercept(target).reply(statusCode, payload, { headers }); - - try { - await request(target.method as any, kUrl + target.path); - } - catch (error: any) { - expect(error.message).toStrictEqual("Unsupported encoding 'unknown'."); - expect(error.buffer).toStrictEqual(payload); - expect(error.headers).toMatchObject(headers); - expect(error.statusCode).toBe(statusCode); - expect(isHttpieError(error)).toBeTruthy(); - } - }); - - it("should decompress data when 'content-encoding' header is set with 'gzip'", async() => { - const target = { - method: "GET", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "text/html", "content-encoding": "gzip" }; - const payload = Buffer.from("Payload"); - const compressedPayload = await kAsyncGzip(payload); - - pool.intercept(target).reply(statusCode, compressedPayload, { headers }); - - const response = await request(target.method as any, kUrl + target.path); - - expect(response.data).toStrictEqual(payload.toString()); - expect(response.headers).toMatchObject(headers); - expect(response.statusCode).toBe(statusCode); - }); - - it("should decompress data when 'content-encoding' header is set with 'x-gzip'", async() => { - const target = { - method: "GET", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "text/html", "content-encoding": "x-gzip" }; - const payload = "Payload"; - const compressedPayload = await kAsyncGzip(payload); - - pool.intercept(target).reply(statusCode, compressedPayload, { headers }); - - const response = await request(target.method as any, kUrl + target.path); - - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - }); - - it("should decompress data when 'content-encoding' header is set with 'br'", async() => { - const target = { - method: "GET", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "text/html", "content-encoding": "br" }; - const payload = "Payload"; - const compressedPayload = await kAsyncBrotli(payload); - - pool.intercept(target).reply(statusCode, compressedPayload, { headers }); - - const response = await request(target.method as any, kUrl + target.path); - - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - }); - - it("should decompress data when 'content-encoding' header is set with 'deflate'", async() => { - const target = { - method: "GET", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "text/html", "content-encoding": "deflate" }; - const payload = "Payload"; - const compressedPayload = await kAsyncDeflate(payload); - - pool.intercept(target).reply(statusCode, compressedPayload, { headers }); - - const response = await request(target.method as any, kUrl + target.path); - - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - expect(response.statusCode).toBe(200); - }); - }); - - describe("POST", () => { - it("should return a parsed response as text when 'content-type' header starts with 'text/'", async() => { - const target = { - method: "POST", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "text/klsmdkf" }; - const payload = "La data."; - const buf = Buffer.from(payload); - - pool.intercept(target).reply(statusCode, buf, { headers }); - - const response = await request(target.method as any, kUrl + target.path); - - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - expect(response.statusCode).toBe(statusCode); - }); - - it("should return a parsed response as object when 'content-type' header is set with 'application/json'", async() => { - const target = { - method: "POST", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "application/json" }; - const payload = { my: "object" }; - const buf = Buffer.from(JSON.stringify(payload)); - - pool.intercept(target).reply(statusCode, buf, { headers }); - - const response = await request(target.method as any, kUrl + target.path); - - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - expect(response.statusCode).toBe(statusCode); - }); - - it("should return a buffer when 'content-type' header is set with 'application/pdf'", async() => { - const target = { - method: "POST", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "application/pdf" }; - const payload = Buffer.from("mon pdf"); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - const response = await request(target.method as any, kUrl + target.path); - - expect(response.data).toStrictEqual(Buffer.from(payload)); - expect(response.headers).toMatchObject(headers); - expect(response.statusCode).toBe(statusCode); - }); - - it("should return a buffer when 'content-type' header is set with unsupported value", async() => { - const target = { - method: "POST", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "application/msword" }; - const payload = Buffer.from(JSON.stringify({ my: "object" })); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - const response = await request(target.method as any, kUrl + target.path); - - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - }); - - it("should throw when 'content-type' header is set with unsupported value", async() => { - expect.assertions(5); - - const target = { - method: "POST", - path: "/test" - }; - const statusCode = 200; - const headers = { "content-type": "unknown" }; - const payload = Buffer.from(JSON.stringify({ my: "object" })); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - try { - await request(target.method as any, kUrl + target.path); - } - catch (error: any) { - expect(isHttpieError(error)).toBeTruthy(); - expect(error.message).toStrictEqual( - "An unexpected error occurred when trying to parse the response body (reason: 'invalid media type')." - ); - expect(error.buffer).toStrictEqual(payload); - expect(error.headers).toMatchObject(headers); - expect(error.statusCode).toBe(200); - } - }); - - it("should throw when 'content-encoding' header is set with unsupported value", async() => { - expect.assertions(5); - - const target = { - method: "POST", - path: "/test" - }; - - const payload = await kAsyncGzip("Mon document"); - const headers = { "content-encoding": "unknown" }; - const statusCode = 200; - - pool.intercept(target).reply(statusCode, payload, { headers }); - - try { - await request(target.method as any, kUrl + target.path); - } - catch (error: any) { - expect(error.message).toStrictEqual("Unsupported encoding 'unknown'."); - expect(error.buffer).toStrictEqual(payload); - expect(error.headers).toMatchObject(headers); - expect(error.statusCode).toBe(statusCode); - expect(isHttpieError(error)).toBeTruthy(); - } - }); - - it("should decompress data when 'content-encoding' header is set with 'gzip'", async() => { - const target = { - method: "POST", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "text/html", "content-encoding": "gzip" }; - const payload = Buffer.from("Payload"); - const compressedPayload = await kAsyncGzip(payload); - - pool.intercept(target).reply(statusCode, compressedPayload, { headers }); - - const response = await request(target.method as any, kUrl + target.path); - - expect(response.data).toStrictEqual(payload.toString()); - expect(response.headers).toMatchObject(headers); - expect(response.statusCode).toBe(statusCode); - }); - - it("should decompress data when 'content-encoding' header is set with 'x-gzip'", async() => { - const target = { - method: "POST", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "text/html", "content-encoding": "x-gzip" }; - const payload = "Payload"; - const compressedPayload = await kAsyncGzip(payload); - - pool.intercept(target).reply(statusCode, compressedPayload, { headers }); - - const response = await request(target.method as any, kUrl + target.path); - - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - }); - - it("should decompress data when 'content-encoding' header is set with 'br'", async() => { - const target = { - method: "POST", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "text/html", "content-encoding": "br" }; - const payload = "Payload"; - const compressedPayload = await kAsyncBrotli(payload); - - pool.intercept(target).reply(statusCode, compressedPayload, { headers }); - - const response = await request(target.method as any, kUrl + target.path); - - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - }); - - it("should decompress data when 'content-encoding' header is set with 'deflate'", async() => { - const target = { - method: "POST", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "text/html", "content-encoding": "deflate" }; - const payload = "Payload"; - const compressedPayload = await kAsyncDeflate(payload); - - pool.intercept(target).reply(statusCode, compressedPayload, { headers }); - - const response = await request(target.method as any, kUrl + target.path); - - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - expect(response.statusCode).toBe(200); - }); - }); - - describe("PUT", () => { - it("should return a parsed response as text when 'content-type' header starts with 'text/'", async() => { - const target = { - method: "PUT", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "text/klsmdkf" }; - const payload = "La data."; - const buf = Buffer.from(payload); - - pool.intercept(target).reply(statusCode, buf, { headers }); - - const response = await request(target.method as any, kUrl + target.path); - - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - expect(response.statusCode).toBe(statusCode); - }); - - it("should return a parsed response as object when 'content-type' header is set with 'application/json'", async() => { - const target = { - method: "PUT", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "application/json" }; - const payload = { my: "object" }; - const buf = Buffer.from(JSON.stringify(payload)); - - pool.intercept(target).reply(statusCode, buf, { headers }); - - const response = await request(target.method as any, kUrl + target.path); - - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - expect(response.statusCode).toBe(statusCode); - }); - - it("should return a buffer when 'content-type' header is set with 'application/pdf'", async() => { - const target = { - method: "PUT", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "application/pdf" }; - const payload = Buffer.from("mon pdf"); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - const response = await request(target.method as any, kUrl + target.path); - - expect(response.data).toStrictEqual(Buffer.from(payload)); - expect(response.headers).toMatchObject(headers); - expect(response.statusCode).toBe(statusCode); - }); - - it("should return a buffer when 'content-type' header is set with unsupported value", async() => { - const target = { - method: "PUT", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "application/msword" }; - const payload = Buffer.from(JSON.stringify({ my: "object" })); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - const response = await request(target.method as any, kUrl + target.path); - - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - }); - - it("should throw when 'content-type' header is set with unsupported value", async() => { - expect.assertions(5); - - const target = { - method: "PUT", - path: "/test" - }; - const statusCode = 200; - const headers = { "content-type": "unknown" }; - const payload = Buffer.from(JSON.stringify({ my: "object" })); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - try { - await request(target.method as any, kUrl + target.path); - } - catch (error: any) { - expect(isHttpieError(error)).toBeTruthy(); - expect(error.message).toStrictEqual( - "An unexpected error occurred when trying to parse the response body (reason: 'invalid media type')." - ); - expect(error.buffer).toStrictEqual(payload); - expect(error.headers).toMatchObject(headers); - expect(error.statusCode).toBe(200); - } - }); - - it("should throw when 'content-encoding' header is set with unsupported value", async() => { - expect.assertions(5); - - const target = { - method: "PUT", - path: "/test" - }; - - const payload = await kAsyncGzip("Mon document"); - const headers = { "content-encoding": "unknown" }; - const statusCode = 200; - - pool.intercept(target).reply(statusCode, payload, { headers }); - - try { - await request(target.method as any, kUrl + target.path); - } - catch (error: any) { - expect(error.message).toStrictEqual("Unsupported encoding 'unknown'."); - expect(error.buffer).toStrictEqual(payload); - expect(error.headers).toMatchObject(headers); - expect(error.statusCode).toBe(statusCode); - expect(isHttpieError(error)).toBeTruthy(); - } - }); - - it("should decompress data when 'content-encoding' header is set with 'gzip'", async() => { - const target = { - method: "PUT", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "text/html", "content-encoding": "gzip" }; - const payload = Buffer.from("Payload"); - const compressedPayload = await kAsyncGzip(payload); - - pool.intercept(target).reply(statusCode, compressedPayload, { headers }); - - const response = await request(target.method as any, kUrl + target.path); - - expect(response.data).toStrictEqual(payload.toString()); - expect(response.headers).toMatchObject(headers); - expect(response.statusCode).toBe(statusCode); - }); - - it("should decompress data when 'content-encoding' header is set with 'x-gzip'", async() => { - const target = { - method: "PUT", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "text/html", "content-encoding": "x-gzip" }; - const payload = "Payload"; - const compressedPayload = await kAsyncGzip(payload); - - pool.intercept(target).reply(statusCode, compressedPayload, { headers }); - - const response = await request(target.method as any, kUrl + target.path); - - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - }); - - it("should decompress data when 'content-encoding' header is set with 'br'", async() => { - const target = { - method: "PUT", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "text/html", "content-encoding": "br" }; - const payload = "Payload"; - const compressedPayload = await kAsyncBrotli(payload); - - pool.intercept(target).reply(statusCode, compressedPayload, { headers }); - - const response = await request(target.method as any, kUrl + target.path); - - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - }); - - it("should decompress data when 'content-encoding' header is set with 'deflate'", async() => { - const target = { - method: "PUT", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "text/html", "content-encoding": "deflate" }; - const payload = "Payload"; - const compressedPayload = await kAsyncDeflate(payload); - - pool.intercept(target).reply(statusCode, compressedPayload, { headers }); - - const response = await request(target.method as any, kUrl + target.path); - - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - expect(response.statusCode).toBe(200); - }); - }); - - describe("DELETE", () => { - it("should return a parsed response as text when 'content-type' header starts with 'text/'", async() => { - const target = { - method: "DELETE", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "text/klsmdkf" }; - const payload = "La data."; - const buf = Buffer.from(payload); - - pool.intercept(target).reply(statusCode, buf, { headers }); - - const response = await request(target.method as any, kUrl + target.path); - - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - expect(response.statusCode).toBe(statusCode); - }); - - it("should return a parsed response as object when 'content-type' header is set with 'application/json'", async() => { - const target = { - method: "DELETE", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "application/json" }; - const payload = { my: "object" }; - const buf = Buffer.from(JSON.stringify(payload)); - - pool.intercept(target).reply(statusCode, buf, { headers }); - - const response = await request(target.method as any, kUrl + target.path); - - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - expect(response.statusCode).toBe(statusCode); - }); - - it("should return a buffer when 'content-type' header is set with 'application/pdf'", async() => { - const target = { - method: "DELETE", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "application/pdf" }; - const payload = Buffer.from("mon pdf"); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - const response = await request(target.method as any, kUrl + target.path); - - expect(response.data).toStrictEqual(Buffer.from(payload)); - expect(response.headers).toMatchObject(headers); - expect(response.statusCode).toBe(statusCode); - }); - - it("should return a buffer when 'content-type' header is set with unsupported value", async() => { - const target = { - method: "DELETE", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "application/msword" }; - const payload = Buffer.from(JSON.stringify({ my: "object" })); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - const response = await request(target.method as any, kUrl + target.path); - - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - }); - - it("should throw when 'content-type' header is set with unsupported value", async() => { - expect.assertions(5); - - const target = { - method: "DELETE", - path: "/test" - }; - const statusCode = 200; - const headers = { "content-type": "unknown" }; - const payload = Buffer.from(JSON.stringify({ my: "object" })); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - try { - await request(target.method as any, kUrl + target.path); - } - catch (error: any) { - expect(isHttpieError(error)).toBeTruthy(); - expect(error.message).toStrictEqual( - "An unexpected error occurred when trying to parse the response body (reason: 'invalid media type')." - ); - expect(error.buffer).toStrictEqual(payload); - expect(error.headers).toMatchObject(headers); - expect(error.statusCode).toBe(200); - } - }); - - it("should throw when 'content-encoding' header is set with unsupported value", async() => { - expect.assertions(5); - - const target = { - method: "DELETE", - path: "/test" - }; - - const payload = await kAsyncGzip("Mon document"); - const headers = { "content-encoding": "unknown" }; - const statusCode = 200; - - pool.intercept(target).reply(statusCode, payload, { headers }); - - try { - await request(target.method as any, kUrl + target.path); - } - catch (error: any) { - expect(error.message).toStrictEqual("Unsupported encoding 'unknown'."); - expect(error.buffer).toStrictEqual(payload); - expect(error.headers).toMatchObject(headers); - expect(error.statusCode).toBe(statusCode); - expect(isHttpieError(error)).toBeTruthy(); - } - }); - - it("should decompress data when 'content-encoding' header is set with 'gzip'", async() => { - const target = { - method: "DELETE", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "text/html", "content-encoding": "gzip" }; - const payload = Buffer.from("Payload"); - const compressedPayload = await kAsyncGzip(payload); - - pool.intercept(target).reply(statusCode, compressedPayload, { headers }); - - const response = await request(target.method as any, kUrl + target.path); - - expect(response.data).toStrictEqual(payload.toString()); - expect(response.headers).toMatchObject(headers); - expect(response.statusCode).toBe(statusCode); - }); - - it("should decompress data when 'content-encoding' header is set with 'x-gzip'", async() => { - const target = { - method: "DELETE", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "text/html", "content-encoding": "x-gzip" }; - const payload = "Payload"; - const compressedPayload = await kAsyncGzip(payload); - - pool.intercept(target).reply(statusCode, compressedPayload, { headers }); - - const response = await request(target.method as any, kUrl + target.path); - - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - }); - - it("should decompress data when 'content-encoding' header is set with 'br'", async() => { - const target = { - method: "DELETE", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "text/html", "content-encoding": "br" }; - const payload = "Payload"; - const compressedPayload = await kAsyncBrotli(payload); - - pool.intercept(target).reply(statusCode, compressedPayload, { headers }); - - const response = await request(target.method as any, kUrl + target.path); - - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - }); - - it("should decompress data when 'content-encoding' header is set with 'deflate'", async() => { - const target = { - method: "DELETE", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "text/html", "content-encoding": "deflate" }; - const payload = "Payload"; - const compressedPayload = await kAsyncDeflate(payload); - - pool.intercept(target).reply(statusCode, compressedPayload, { headers }); - - const response = await request(target.method as any, kUrl + target.path); - - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - expect(response.statusCode).toBe(200); - }); - }); - }); - - describe("DECOMPRESS mode", () => { - describe("GET", () => { - it("should return a buffer without parsing it even if 'content-type' header exists", async() => { - const target = { - method: "GET", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "text/klsmdkf" }; - const payload = Buffer.from("La data."); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - const response = await request(target.method as any, kUrl + target.path, { mode: "decompress" }); - - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - }); - - it("should throw when 'content-encoding' header is set with unsupported value", async() => { - expect.assertions(5); - - const target = { - method: "GET", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-encoding": "unknown" }; - const payload = Buffer.from("Mon document"); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - try { - await request(target.method as any, kUrl + target.path, { mode: "decompress" }); - } - catch (error: any) { - expect(error.message).toStrictEqual("Unsupported encoding 'unknown'."); - expect(error.buffer).toMatchObject(payload); - expect(error.headers).toMatchObject(headers); - expect(isHttpieError(error)).toBeTruthy(); - expect(error.statusCode).toBe(200); - } - }); - - it("should throw when 'content-encoding' header is invalid", async() => { - expect.assertions(8); - - const target = { - method: "GET", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-encoding": "gzip" }; - const payload = await kAsyncBrotli("Mon document"); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - try { - await request(target.method as any, kUrl + target.path, { mode: "decompress" }); - } - catch (error: any) { - expect(error.reason).toBeTruthy(); - expect(error.message).toStrictEqual( - "An unexpected error occurred when trying to decompress the response body (reason: 'incorrect header check')." - ); - expect(error.buffer).toMatchObject(payload); - expect(error.headers).toMatchObject(headers); - expect(error.reason).toBeTruthy(); - expect(error.reason.message).toStrictEqual("incorrect header check"); - expect(isHttpieError(error)).toBeTruthy(); - expect(error.statusCode).toBe(200); - } - }); - - it("should decompress data when 'content-encoding' header is set with 'gzip'", async() => { - const target = { - method: "GET", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "text/html", "content-encoding": "gzip" }; - const payload = Buffer.from("payload"); - const compressedPayload = await kAsyncGzip("payload"); - - pool.intercept(target).reply(statusCode, compressedPayload, { headers }); - - const response = await request(target.method as any, kUrl + target.path, { mode: "decompress" }); - - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - }); - - it("should decompress data when 'content-encoding' header is set with 'x-gzip'", async() => { - const target = { - method: "GET", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "text/html", "content-encoding": "x-gzip" }; - const payload = Buffer.from("Payload"); - const compressedPayload = await kAsyncGzip(payload); - - pool.intercept(target).reply(statusCode, compressedPayload, { headers }); - - const response = await request(target.method as any, kUrl + target.path, { mode: "decompress" }); - - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - }); - - it("should decompress data when 'content-encoding' header is set with 'br'", async() => { - const target = { - method: "GET", - path: "/test" - }; - - const payload = Buffer.from("Payload"); - const compressedPayload = await kAsyncBrotli(payload); - const statusCode = 200; - const headers = { "content-type": "text/html", "content-encoding": "br" }; - - pool.intercept(target).reply(statusCode, compressedPayload, { headers }); - const response = await request(target.method as any, kUrl + target.path, { mode: "decompress" }); - - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - }); - - it("should decompress data when 'content-encoding' header is set with 'deflate'", async() => { - const target = { - method: "GET", - path: "/test" - }; - - const payload = Buffer.from("Payload"); - const compressedPayload = await kAsyncDeflate(payload); - - const statusCode = 200; - const headers = { "content-type": "text/html", "content-encoding": "deflate" }; - - pool.intercept(target).reply(statusCode, compressedPayload, { headers }); - const response = await request(target.method as any, kUrl + target.path, { mode: "decompress" }); - - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - }); - }); - - describe("POST", () => { - it("should return a buffer without parsing it even if 'content-type' header exists", async() => { - const target = { - method: "POST", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "text/klsmdkf" }; - const payload = Buffer.from("La data."); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - const response = await request(target.method as any, kUrl + target.path, { mode: "decompress" }); - - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - }); - - it("should throw when 'content-encoding' header is set with unsupported value", async() => { - expect.assertions(5); - - const target = { - method: "POST", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-encoding": "unknown" }; - const payload = Buffer.from("Mon document"); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - try { - await request(target.method as any, kUrl + target.path, { mode: "decompress" }); - } - catch (error: any) { - expect(error.message).toStrictEqual("Unsupported encoding 'unknown'."); - expect(error.buffer).toMatchObject(payload); - expect(error.headers).toMatchObject(headers); - expect(isHttpieError(error)).toBeTruthy(); - expect(error.statusCode).toBe(200); - } - }); - - it("should throw when 'content-encoding' header is invalid", async() => { - expect.assertions(8); - - const target = { - method: "POST", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-encoding": "gzip" }; - const payload = await kAsyncBrotli("Mon document"); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - try { - await request(target.method as any, kUrl + target.path, { mode: "decompress" }); - } - catch (error: any) { - expect(error.reason).toBeTruthy(); - expect(error.message).toStrictEqual( - "An unexpected error occurred when trying to decompress the response body (reason: 'incorrect header check')." - ); - expect(error.buffer).toMatchObject(payload); - expect(error.headers).toMatchObject(headers); - expect(error.reason).toBeTruthy(); - expect(error.reason.message).toStrictEqual("incorrect header check"); - expect(isHttpieError(error)).toBeTruthy(); - expect(error.statusCode).toBe(200); - } - }); - - it("should decompress data when 'content-encoding' header is set with 'gzip'", async() => { - const target = { - method: "POST", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "text/html", "content-encoding": "gzip" }; - const payload = Buffer.from("payload"); - const compressedPayload = await kAsyncGzip("payload"); - - pool.intercept(target).reply(statusCode, compressedPayload, { headers }); - - const response = await request(target.method as any, kUrl + target.path, { mode: "decompress" }); - - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - }); - - it("should decompress data when 'content-encoding' header is set with 'x-gzip'", async() => { - const target = { - method: "POST", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "text/html", "content-encoding": "x-gzip" }; - const payload = Buffer.from("Payload"); - const compressedPayload = await kAsyncGzip(payload); - - pool.intercept(target).reply(statusCode, compressedPayload, { headers }); - - const response = await request(target.method as any, kUrl + target.path, { mode: "decompress" }); - - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - }); - - it("should decompress data when 'content-encoding' header is set with 'br'", async() => { - const target = { - method: "POST", - path: "/test" - }; - - const payload = Buffer.from("Payload"); - const compressedPayload = await kAsyncBrotli(payload); - const statusCode = 200; - const headers = { "content-type": "text/html", "content-encoding": "br" }; - - pool.intercept(target).reply(statusCode, compressedPayload, { headers }); - const response = await request(target.method as any, kUrl + target.path, { mode: "decompress" }); - - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - }); - - it("should decompress data when 'content-encoding' header is set with 'deflate'", async() => { - const target = { - method: "POST", - path: "/test" - }; - - const payload = Buffer.from("Payload"); - const compressedPayload = await kAsyncDeflate(payload); - - const statusCode = 200; - const headers = { "content-type": "text/html", "content-encoding": "deflate" }; - - pool.intercept(target).reply(statusCode, compressedPayload, { headers }); - const response = await request(target.method as any, kUrl + target.path, { mode: "decompress" }); - - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - }); - }); - - describe("PUT", () => { - it("should return a buffer without parsing it even if 'content-type' header exists", async() => { - const target = { - method: "PUT", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "text/klsmdkf" }; - const payload = Buffer.from("La data."); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - const response = await request(target.method as any, kUrl + target.path, { mode: "decompress" }); - - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - }); - - it("should throw when 'content-encoding' header is set with unsupported value", async() => { - expect.assertions(5); - - const target = { - method: "PUT", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-encoding": "unknown" }; - const payload = Buffer.from("Mon document"); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - try { - await request(target.method as any, kUrl + target.path, { mode: "decompress" }); - } - catch (error: any) { - expect(error.message).toStrictEqual("Unsupported encoding 'unknown'."); - expect(error.buffer).toMatchObject(payload); - expect(error.headers).toMatchObject(headers); - expect(isHttpieError(error)).toBeTruthy(); - expect(error.statusCode).toBe(200); - } - }); - - it("should throw when 'content-encoding' header is invalid", async() => { - expect.assertions(8); - - const target = { - method: "PUT", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-encoding": "gzip" }; - const payload = await kAsyncBrotli("Mon document"); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - try { - await request(target.method as any, kUrl + target.path, { mode: "decompress" }); - } - catch (error: any) { - expect(error.reason).toBeTruthy(); - expect(error.message).toStrictEqual( - "An unexpected error occurred when trying to decompress the response body (reason: 'incorrect header check')." - ); - expect(error.buffer).toMatchObject(payload); - expect(error.headers).toMatchObject(headers); - expect(error.reason).toBeTruthy(); - expect(error.reason.message).toStrictEqual("incorrect header check"); - expect(isHttpieError(error)).toBeTruthy(); - expect(error.statusCode).toBe(200); - } - }); - - it("should decompress data when 'content-encoding' header is set with 'gzip'", async() => { - const target = { - method: "PUT", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "text/html", "content-encoding": "gzip" }; - const payload = Buffer.from("payload"); - const compressedPayload = await kAsyncGzip("payload"); - - pool.intercept(target).reply(statusCode, compressedPayload, { headers }); - - const response = await request(target.method as any, kUrl + target.path, { mode: "decompress" }); - - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - }); - - it("should decompress data when 'content-encoding' header is set with 'x-gzip'", async() => { - const target = { - method: "PUT", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "text/html", "content-encoding": "x-gzip" }; - const payload = Buffer.from("Payload"); - const compressedPayload = await kAsyncGzip(payload); - - pool.intercept(target).reply(statusCode, compressedPayload, { headers }); - - const response = await request(target.method as any, kUrl + target.path, { mode: "decompress" }); - - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - }); - - it("should decompress data when 'content-encoding' header is set with 'br'", async() => { - const target = { - method: "PUT", - path: "/test" - }; - - const payload = Buffer.from("Payload"); - const compressedPayload = await kAsyncBrotli(payload); - const statusCode = 200; - const headers = { "content-type": "text/html", "content-encoding": "br" }; - - pool.intercept(target).reply(statusCode, compressedPayload, { headers }); - const response = await request(target.method as any, kUrl + target.path, { mode: "decompress" }); - - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - }); - - it("should decompress data when 'content-encoding' header is set with 'deflate'", async() => { - const target = { - method: "PUT", - path: "/test" - }; - - const payload = Buffer.from("Payload"); - const compressedPayload = await kAsyncDeflate(payload); - - const statusCode = 200; - const headers = { "content-type": "text/html", "content-encoding": "deflate" }; - - pool.intercept(target).reply(statusCode, compressedPayload, { headers }); - const response = await request(target.method as any, kUrl + target.path, { mode: "decompress" }); - - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - }); - }); - - describe("DELETE", () => { - it("should return a buffer without parsing it even if 'content-type' header exists", async() => { - const target = { - method: "DELETE", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "text/klsmdkf" }; - const payload = Buffer.from("La data."); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - const response = await request(target.method as any, kUrl + target.path, { mode: "decompress" }); - - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - }); - - it("should throw when 'content-encoding' header is set with unsupported value", async() => { - expect.assertions(5); - - const target = { - method: "DELETE", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-encoding": "unknown" }; - const payload = Buffer.from("Mon document"); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - try { - await request(target.method as any, kUrl + target.path, { mode: "decompress" }); - } - catch (error: any) { - expect(error.message).toStrictEqual("Unsupported encoding 'unknown'."); - expect(error.buffer).toMatchObject(payload); - expect(error.headers).toMatchObject(headers); - expect(isHttpieError(error)).toBeTruthy(); - expect(error.statusCode).toBe(200); - } - }); - - it("should throw when 'content-encoding' header is invalid", async() => { - expect.assertions(8); - - const target = { - method: "DELETE", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-encoding": "gzip" }; - const payload = await kAsyncBrotli("Mon document"); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - try { - await request(target.method as any, kUrl + target.path, { mode: "decompress" }); - } - catch (error: any) { - expect(error.reason).toBeTruthy(); - expect(error.message).toStrictEqual( - "An unexpected error occurred when trying to decompress the response body (reason: 'incorrect header check')." - ); - expect(error.buffer).toMatchObject(payload); - expect(error.headers).toMatchObject(headers); - expect(error.reason).toBeTruthy(); - expect(error.reason.message).toStrictEqual("incorrect header check"); - expect(isHttpieError(error)).toBeTruthy(); - expect(error.statusCode).toBe(200); - } - }); - - it("should decompress data when 'content-encoding' header is set with 'gzip'", async() => { - const target = { - method: "DELETE", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "text/html", "content-encoding": "gzip" }; - const payload = Buffer.from("payload"); - const compressedPayload = await kAsyncGzip("payload"); - - pool.intercept(target).reply(statusCode, compressedPayload, { headers }); - - const response = await request(target.method as any, kUrl + target.path, { mode: "decompress" }); - - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - }); - - it("should decompress data when 'content-encoding' header is set with 'x-gzip'", async() => { - const target = { - method: "DELETE", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "text/html", "content-encoding": "x-gzip" }; - const payload = Buffer.from("Payload"); - const compressedPayload = await kAsyncGzip(payload); - - pool.intercept(target).reply(statusCode, compressedPayload, { headers }); - - const response = await request(target.method as any, kUrl + target.path, { mode: "decompress" }); - - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - }); - - it("should decompress data when 'content-encoding' header is set with 'br'", async() => { - const target = { - method: "DELETE", - path: "/test" - }; - - const payload = Buffer.from("Payload"); - const compressedPayload = await kAsyncBrotli(payload); - const statusCode = 200; - const headers = { "content-type": "text/html", "content-encoding": "br" }; - - pool.intercept(target).reply(statusCode, compressedPayload, { headers }); - const response = await request(target.method as any, kUrl + target.path, { mode: "decompress" }); - - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - }); - - it("should decompress data when 'content-encoding' header is set with 'deflate'", async() => { - const target = { - method: "DELETE", - path: "/test" - }; - - const payload = Buffer.from("Payload"); - const compressedPayload = await kAsyncDeflate(payload); - - const statusCode = 200; - const headers = { "content-type": "text/html", "content-encoding": "deflate" }; - - pool.intercept(target).reply(statusCode, compressedPayload, { headers }); - const response = await request(target.method as any, kUrl + target.path, { mode: "decompress" }); - - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - }); - }); - }); - - describe("RAW mode", () => { - describe("GET", () => { - it("should return a buffer without parsing it even if 'content-type' header exists", async() => { - const target = { - method: "GET", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "text/klsmdkf" }; - const payload = Buffer.from("payload"); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - const response = await request(target.method as any, kUrl + target.path, { mode: "raw" }); - - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - expect(response.statusCode).toBe(200); - }); - - it("should return a buffer without decompress it even if 'content-encoding' header exists", async() => { - const target = { - method: "GET", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-encoding": "gzip" }; - const payload = await kAsyncGzip("Doc"); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - const response = await request(target.method as any, kUrl + target.path, { mode: "raw" }); - - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - expect(response.statusCode).toBe(200); - }); - }); - - describe("POST", () => { - it("should return a buffer without parsing it even if 'content-type' header exists", async() => { - const target = { - method: "POST", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "text/klsmdkf" }; - const payload = Buffer.from("payload"); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - const response = await request(target.method as any, kUrl + target.path, { mode: "raw" }); - - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - expect(response.statusCode).toBe(200); - }); - - it("should return a buffer without decompress it even if 'content-encoding' header exists", async() => { - const target = { - method: "POST", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-encoding": "gzip" }; - const payload = await kAsyncGzip("Doc"); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - const response = await request(target.method as any, kUrl + target.path, { mode: "raw" }); - - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - expect(response.statusCode).toBe(200); - }); - }); - - describe("PUT", () => { - it("should return a buffer without parsing it even if 'content-type' header exists", async() => { - const target = { - method: "PUT", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "text/klsmdkf" }; - const payload = Buffer.from("payload"); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - const response = await request(target.method as any, kUrl + target.path, { mode: "raw" }); - - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - expect(response.statusCode).toBe(200); - }); - - it("should return a buffer without decompress it even if 'content-encoding' header exists", async() => { - const target = { - method: "PUT", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-encoding": "gzip" }; - const payload = await kAsyncGzip("Doc"); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - const response = await request(target.method as any, kUrl + target.path, { mode: "raw" }); - - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - expect(response.statusCode).toBe(200); - }); - }); - - describe("DELETE", () => { - it("should return a buffer without parsing it even if 'content-type' header exists", async() => { - const target = { - method: "DELETE", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "text/klsmdkf" }; - const payload = Buffer.from("payload"); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - const response = await request(target.method as any, kUrl + target.path, { mode: "raw" }); - - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - expect(response.statusCode).toBe(200); - }); - - it("should return a buffer without decompress it even if 'content-encoding' header exists", async() => { - const target = { - method: "DELETE", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-encoding": "gzip" }; - const payload = await kAsyncGzip("Doc"); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - const response = await request(target.method as any, kUrl + target.path, { mode: "raw" }); - - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - expect(response.statusCode).toBe(200); - }); - }); - }); -}); diff --git a/test/request2.test.ts b/test/request2.test.ts new file mode 100644 index 0000000..4d0a98a --- /dev/null +++ b/test/request2.test.ts @@ -0,0 +1,489 @@ +// Import Node.js Dependencies +import { describe, it, before } from "node:test"; +import { brotliCompress, deflate, gzip } from "node:zlib"; +import { promisify } from "node:util"; +import { randomInt } from "node:crypto"; +import assert from "node:assert"; + +// Import Third-party Dependencies +import { Interceptable, MockAgent, setGlobalDispatcher } from "undici"; + +// Import Internal Dependencies +import { HttpMethod, isHTTPError, isHttpieError, request } from "../src/index.js"; + +// CONSTANTS +const kUrl = "http://com"; +const kAsyncGzip = promisify(gzip); +const kAsyncBrotli = promisify(brotliCompress); +const kAsyncDeflate = promisify(deflate); + +// VARS +let pool: Interceptable; + +describe("Httpie.safeRequest", () => { + before(() => { + const mockAgent = new MockAgent(); + setGlobalDispatcher(mockAgent); + mockAgent.disableNetConnect(); + + pool = mockAgent.get(kUrl); + }); + + describe("with ThrowOnHttpError", () => { + describe("GET", () => { + it("should throw if the response status code is higher than 400", async(t) => { + t.plan(4); + + const target = { + method: "GET", + path: "/test" + }; + + const statusCode = randomInt(400, 503); + const headers = { "content-type": "text/html" }; + const payload = Buffer.from("Body"); + + pool.intercept(target).reply(statusCode, payload, { headers }); + + try { + await request(target.method as any, kUrl + target.path); + } + catch (error: any) { + t.assert.equal(isHTTPError(error), true); + t.assert.equal(error.statusCode, statusCode); + t.assert.equal(error.data, payload.toString()); + t.assert.deepEqual(error.headers, headers); + } + }); + + it("should not throw if the response status code is lower than 400", async() => { + const target = { + method: "GET", + path: "/test" + }; + + const statusCode = randomInt(200, 399); + const headers = { "content-type": "text/html" }; + const payload = Buffer.from("Body"); + + pool.intercept(target).reply(statusCode, payload, { headers }); + + const response = await request(target.method as any, kUrl + target.path); + + assert.strictEqual(response.statusCode, statusCode); + assert.strictEqual(response.data, payload.toString()); + assert.deepStrictEqual(response.headers, headers); + }); + }); + + for (const method of ["POST", "PUT", "DELETE"] as const) { + describe(method, () => { + it("should throw if the response status code is higher than 400", async() => { + const target = { method, path: "/test" }; + const statusCode = randomInt(400, 503); + const headers = { "content-type": "text/html" }; + const payload = Buffer.from("Body"); + + pool.intercept(target).reply(statusCode, payload, { headers }); + + try { + await request(target.method, kUrl + target.path); + assert.fail("Expected an error to be thrown"); + } + catch (error: any) { + assert.ok(isHTTPError(error)); + assert.strictEqual(error.statusCode, statusCode); + assert.strictEqual(error.data, payload.toString()); + assert.deepStrictEqual(error.headers, headers); + } + }); + + it("should not throw if the response status code is lower than 400", async() => { + const target = { method, path: "/test" }; + const statusCode = randomInt(200, 399); + const headers = { "content-type": "text/html" }; + const payload = Buffer.from("Body"); + + pool.intercept(target).reply(statusCode, payload, { headers }); + + const response = await request(target.method, kUrl + target.path); + + assert.strictEqual(response.statusCode, statusCode); + assert.strictEqual(response.data, payload.toString()); + assert.deepStrictEqual(response.headers, headers); + }); + }); + } + }); + + describe("without ThrowOnHttpError", () => { + describe("GET", () => { + it("should not throw if the response status code is higher than 400", async() => { + const target = { method: "GET", path: "/test" }; + const statusCode = randomInt(400, 503); + const headers = { "content-type": "text/html" }; + const payload = Buffer.from("Body"); + + pool.intercept(target).reply(statusCode, payload, { headers }); + + const response = await request(target.method as any, kUrl + target.path, { throwOnHttpError: false }); + + assert.strictEqual(response.statusCode, statusCode); + assert.strictEqual(response.data, payload.toString()); + assert.deepStrictEqual(response.headers, headers); + }); + + it("should not throw if the response status code is lower than 400", async() => { + const target = { method: "GET", path: "/test" }; + const statusCode = randomInt(200, 399); + const headers = { "content-type": "text/html" }; + const payload = Buffer.from("Body"); + + pool.intercept(target).reply(statusCode, payload, { headers }); + + const response = await request(target.method as any, kUrl + target.path, { throwOnHttpError: false }); + + assert.strictEqual(response.statusCode, statusCode); + assert.strictEqual(response.data, payload.toString()); + assert.deepStrictEqual(response.headers, headers); + }); + }); + + for (const method of ["POST", "PUT", "DELETE"] as const) { + describe(method, () => { + it("should not throw if the response status code is higher than 400", async() => { + const target = { method, path: "/test" }; + const statusCode = randomInt(400, 503); + const headers = { "content-type": "text/html" }; + const payload = Buffer.from("Body"); + + pool.intercept(target).reply(statusCode, payload, { headers }); + + const response = await request(target.method, kUrl + target.path, { throwOnHttpError: false }); + + assert.strictEqual(response.statusCode, statusCode); + assert.strictEqual(response.data, payload.toString()); + assert.deepStrictEqual(response.headers, headers); + }); + + it("should not throw if the response status code is lower than 400", async() => { + const target = { method, path: "/test" }; + const statusCode = randomInt(200, 399); + const headers = { "content-type": "text/html" }; + const payload = Buffer.from("Body"); + + pool.intercept(target).reply(statusCode, payload, { headers }); + + const response = await request(target.method, kUrl + target.path, { throwOnHttpError: false }); + + assert.strictEqual(response.statusCode, statusCode); + assert.strictEqual(response.data, payload.toString()); + assert.deepStrictEqual(response.headers, headers); + }); + }); + } + }); + + describe("RAW mode", () => { + describe("GET", () => { + it("should return a buffer without parsing it even if 'content-type' header exists", async() => { + const target = { method: "GET", path: "/test" }; + const statusCode = 200; + const headers = { "content-type": "text/klsmdkf" }; + const payload = Buffer.from("payload"); + + pool.intercept(target).reply(statusCode, payload, { headers }); + + const response = await request(target.method as any, kUrl + target.path, { mode: "raw" }); + + assert.deepStrictEqual(response.data, payload); + assert.deepStrictEqual(response.headers, headers); + assert.strictEqual(response.statusCode, 200); + }); + + it("should return a buffer without decompress it even if 'content-encoding' header exists", async() => { + const target = { method: "GET", path: "/test" }; + const statusCode = 200; + const headers = { "content-encoding": "gzip" }; + const payload = await kAsyncGzip("Doc"); + + pool.intercept(target).reply(statusCode, payload, { headers }); + + const response = await request(target.method as any, kUrl + target.path, { mode: "raw" }); + + assert.deepStrictEqual(response.data, payload); + assert.deepStrictEqual(response.headers, headers); + assert.strictEqual(response.statusCode, 200); + }); + }); + + for (const method of ["POST", "PUT", "DELETE"] as const) { + describe(method, () => { + it("should return a buffer without parsing it even if 'content-type' header exists", async() => { + const target = { method, path: "/test" }; + const statusCode = 200; + const headers = { "content-type": "text/klsmdkf" }; + const payload = Buffer.from("payload"); + + pool.intercept(target).reply(statusCode, payload, { headers }); + + const response = await request(target.method, kUrl + target.path, { mode: "raw" }); + + assert.deepStrictEqual(response.data, payload); + assert.deepStrictEqual(response.headers, headers); + assert.strictEqual(response.statusCode, 200); + }); + + it("should return a buffer without decompress it even if 'content-encoding' header exists", async() => { + const target = { method, path: "/test" }; + const statusCode = 200; + const headers = { "content-encoding": "gzip" }; + const payload = await kAsyncGzip("Doc"); + + pool.intercept(target).reply(statusCode, payload, { headers }); + + const response = await request(target.method, kUrl + target.path, { mode: "raw" }); + + assert.deepStrictEqual(response.data, payload); + assert.deepStrictEqual(response.headers, headers); + assert.strictEqual(response.statusCode, 200); + }); + }); + } + }); + + describe("PARSE mode (default)", () => { + const methods: HttpMethod[] = ["GET", "POST", "PUT", "DELETE"]; + + for (const method of methods) { + describe(method, () => { + it("should return a parsed response as text when 'content-type' header starts with 'text/'", async() => { + const target = { method, path: "/test" }; + const payload = "La data."; + const buf = Buffer.from(payload); + const headers = { "content-type": "text/klsmdkf" }; + const statusCode = 200; + + pool.intercept(target).reply(statusCode, buf, { headers }); + + const response = await request(target.method, kUrl + target.path); + + assert.strictEqual(response.data, payload); + assert.deepStrictEqual(response.headers, headers); + assert.strictEqual(response.statusCode, statusCode); + }); + + it("should return a parsed response as object when 'content-type' is 'application/json'", async() => { + const target = { method, path: "/test" }; + const payload = { my: "object" }; + const buf = Buffer.from(JSON.stringify(payload)); + const headers = { "content-type": "application/json" }; + const statusCode = 200; + + pool.intercept(target).reply(statusCode, buf, { headers }); + + const response = await request(target.method, kUrl + target.path); + + assert.deepStrictEqual(response.data, payload); + assert.deepStrictEqual(response.headers, headers); + assert.strictEqual(response.statusCode, statusCode); + }); + + it("should return a buffer when 'content-type' is 'application/pdf'", async() => { + const target = { method, path: "/test" }; + const payload = Buffer.from("mon pdf"); + const headers = { "content-type": "application/pdf" }; + const statusCode = 200; + + pool.intercept(target).reply(statusCode, payload, { headers }); + + const response = await request(target.method, kUrl + target.path); + + assert.deepStrictEqual(response.data, payload); + assert.deepStrictEqual(response.headers, headers); + assert.strictEqual(response.statusCode, statusCode); + }); + + it("should return a buffer when 'content-type' is unsupported", async() => { + const target = { method, path: "/test" }; + const payload = Buffer.from(JSON.stringify({ my: "object" })); + const headers = { "content-type": "application/msword" }; + const statusCode = 200; + + pool.intercept(target).reply(statusCode, payload, { headers }); + + const response = await request(target.method, kUrl + target.path); + + assert.deepStrictEqual(response.data, payload); + assert.deepStrictEqual(response.headers, headers); + }); + + it("should throw when 'content-type' is unknown", async() => { + const target = { method, path: "/test" }; + const payload = Buffer.from(JSON.stringify({ my: "object" })); + const headers = { "content-type": "unknown" }; + const statusCode = 200; + + pool.intercept(target).reply(statusCode, payload, { headers }); + + try { + await request(target.method, kUrl + target.path); + assert.fail("Expected error not thrown"); + } + catch (error: any) { + assert.ok(isHttpieError(error)); + assert.strictEqual( + error.message, + "An unexpected error occurred when trying to parse the response body (reason: 'invalid media type')." + ); + assert.deepStrictEqual(error.headers, headers); + assert.strictEqual(error.statusCode, 200); + } + }); + + it("should throw when 'content-encoding' is unsupported", async() => { + const target = { method, path: "/test" }; + const payload = await kAsyncGzip("Mon document"); + const headers = { "content-encoding": "unknown" }; + const statusCode = 200; + + pool.intercept(target).reply(statusCode, payload, { headers }); + + try { + await request(target.method, kUrl + target.path); + assert.fail("Expected error not thrown"); + } + catch (error: any) { + assert.strictEqual(error.message, "Unsupported encoding 'unknown'."); + assert.deepStrictEqual(error.buffer, payload); + assert.deepStrictEqual(error.headers, headers); + assert.strictEqual(error.statusCode, statusCode); + assert.ok(isHttpieError(error)); + } + }); + + const encodings = [ + { encoding: "gzip", compress: kAsyncGzip }, + { encoding: "x-gzip", compress: kAsyncGzip }, + { encoding: "br", compress: kAsyncBrotli }, + { encoding: "deflate", compress: kAsyncDeflate } + ]; + + for (const { encoding, compress } of encodings) { + it(`should decompress data when 'content-encoding' is '${encoding}'`, async() => { + const target = { method, path: "/test" }; + const payload = "Payload"; + const compressedPayload = await compress(Buffer.from(payload)); + const headers = { + "content-type": "text/html", + "content-encoding": encoding + }; + const statusCode = 200; + + pool.intercept(target).reply(statusCode, compressedPayload, { headers }); + + const response = await request(target.method, kUrl + target.path); + + assert.strictEqual(response.data, payload); + assert.deepStrictEqual(response.headers, headers); + assert.strictEqual(response.statusCode, statusCode); + }); + } + }); + } + }); + + describe("DECOMPRESS mode", () => { + const methods: HttpMethod[] = ["GET", "POST", "PUT", "DELETE"]; + + for (const method of methods) { + describe(method, () => { + it("should return a buffer without parsing it even if 'content-type' header exists", async() => { + const target = { method, path: "/test" }; + const statusCode = 200; + const headers = { "content-type": "text/klsmdkf" }; + const payload = Buffer.from("La data."); + + pool.intercept(target).reply(statusCode, payload, { headers }); + + const response = await request(target.method, kUrl + target.path, { mode: "decompress" }); + + assert.deepStrictEqual(response.data, payload); + assert.deepStrictEqual(response.headers, headers); + }); + + it("should throw when 'content-encoding' header is set with unsupported value", async() => { + const target = { method, path: "/test" }; + const statusCode = 200; + const headers = { "content-encoding": "unknown" }; + const payload = Buffer.from("Mon document"); + + pool.intercept(target).reply(statusCode, payload, { headers }); + + try { + await request(target.method, kUrl + target.path, { mode: "decompress" }); + assert.fail("Expected an error to be thrown"); + } + catch (error: any) { + assert.strictEqual(error.message, "Unsupported encoding 'unknown'."); + assert.deepStrictEqual(error.buffer, payload); + assert.deepStrictEqual(error.headers, headers); + assert.ok(isHttpieError(error)); + assert.strictEqual(error.statusCode, 200); + } + }); + + it("should throw when 'content-encoding' header is invalid", async() => { + const target = { method, path: "/test" }; + const statusCode = 200; + const headers = { "content-encoding": "gzip" }; + const payload = await kAsyncBrotli("Mon document"); + + pool.intercept(target).reply(statusCode, payload, { headers }); + + try { + await request(target.method, kUrl + target.path, { mode: "decompress" }); + assert.fail("Expected an error to be thrown"); + } + catch (error: any) { + assert.ok(error.reason); + assert.strictEqual( + error.message, + "An unexpected error occurred when trying to decompress the response body (reason: 'incorrect header check')." + ); + assert.deepStrictEqual(error.buffer, payload); + assert.deepStrictEqual(error.headers, headers); + assert.ok(error.reason); + assert.strictEqual(error.reason.message, "incorrect header check"); + assert.ok(isHttpieError(error)); + assert.strictEqual(error.statusCode, 200); + } + }); + + const encodings = [ + { encoding: "gzip", compress: kAsyncGzip }, + { encoding: "x-gzip", compress: kAsyncGzip }, + { encoding: "br", compress: kAsyncBrotli }, + { encoding: "deflate", compress: kAsyncDeflate } + ]; + + for (const { encoding, compress } of encodings) { + it(`should decompress data when 'content-encoding' header is set with '${encoding}'`, async() => { + const target = { method, path: "/test" }; + const payload = Buffer.from("Payload"); + const compressedPayload = await compress(payload); + const statusCode = 200; + const headers = { "content-type": "text/html", "content-encoding": encoding }; + + pool.intercept(target).reply(statusCode, compressedPayload, { headers }); + + const response = await request(target.method, kUrl + target.path, { mode: "decompress" }); + + assert.deepStrictEqual(response.data, payload); + assert.deepStrictEqual(response.headers, headers); + }); + } + }); + } + }); +}); diff --git a/test/retry.spec.ts b/test/retry.test.ts similarity index 63% rename from test/retry.spec.ts rename to test/retry.test.ts index 90f4a06..3383aa6 100644 --- a/test/retry.spec.ts +++ b/test/retry.test.ts @@ -1,24 +1,26 @@ +// Import Node.js Dependencies +import { describe, it, before, after } from "node:test"; +import assert from "node:assert"; + // Import Third-party Dependencies import { FastifyInstance } from "fastify"; // Import Internal Dependencies -import { retry, get, policies } from "../src/index"; - -// Helpers and mock -import { createServer } from "./server/index"; +import { retry, get, policies } from "../src/index.js"; +import { createServer } from "./server/index.js"; let httpServer: FastifyInstance; -beforeAll(async() => { +before(async() => { httpServer = await createServer("retry", 1337); }); -afterAll(async() => { +after(async() => { await httpServer.close(); }); describe("retry (with default policy)", () => { - it("should throw an Error because the number of retries has been exceeded", async() => { - expect.assertions(1); + it("should throw an Error because the number of retries has been exceeded", async(t) => { + t.plan(1); try { await retry(() => { @@ -26,7 +28,7 @@ describe("retry (with default policy)", () => { }, { factor: 1 }); } catch (error: any) { - expect(error.message).toStrictEqual("Exceeded the maximum number of allowed retries!"); + t.assert.equal(error.message, "Exceeded the maximum number of allowed retries!"); } }); @@ -42,14 +44,14 @@ describe("retry (with default policy)", () => { return "hello world!"; }); - expect(data).toStrictEqual("hello world!"); - expect(metrics.attempt).toStrictEqual(1); - expect(typeof metrics.elapsedTimeoutTime).toStrictEqual("number"); - expect(typeof metrics.executionTimestamp).toStrictEqual("number"); + assert.equal(data, "hello world!"); + assert.equal(metrics.attempt, 1); + assert.equal(typeof metrics.elapsedTimeoutTime, "number"); + assert.equal(typeof metrics.executionTimestamp, "number"); }); - it("should be stopped with Node.js AbortController", async() => { - expect.assertions(1); + it("should be stopped with Node.js AbortController", async(t) => { + t.plan(1); let count = 0; const controller = new AbortController(); @@ -66,36 +68,36 @@ describe("retry (with default policy)", () => { }, { forever: true, signal: controller.signal }); } catch (error: any) { - expect(error.message).toStrictEqual("Aborted"); + t.assert.equal(error.message, "Aborted"); } }); }); describe("retry (with http policy)", () => { - it("should throw an Error because the number of retries has been exceeded", async() => { - expect.assertions(1); + it("should throw an Error because the number of retries has been exceeded", async(t) => { + t.plan(1); try { await retry(async() => get("/retry/internalerror"), { factor: 1, retries: 2 }, policies.httpcode()); } catch (error: any) { - expect(error.message).toStrictEqual("Exceeded the maximum number of allowed retries!"); + t.assert.equal(error.message, "Exceeded the maximum number of allowed retries!"); } }); - it("should return the http error because the code (501) is not supported by the policy", async() => { - expect.assertions(1); + it("should return the http error because the code (501) is not supported by the policy", async(t) => { + t.plan(1); try { await retry(async() => get("/retry/notimplemented"), { factor: 1, retries: 2 }, policies.httpcode()); } catch (error: any) { - expect(error.message).toStrictEqual("Not Implemented"); + t.assert.equal(error.message, "Not Implemented"); } }); - it("should include code 501 and all other default port", async() => { - expect.assertions(1); + it("should include code 501 and all other default port", async(t) => { + t.plan(1); try { const policy = policies.httpcode(new Set([501]), true); @@ -103,7 +105,7 @@ describe("retry (with http policy)", () => { await retry(async() => get("/retry/notimplemented"), { factor: 1, retries: 2 }, policy); } catch (error: any) { - expect(error.message).toStrictEqual("Exceeded the maximum number of allowed retries!"); + t.assert.equal(error.message, "Exceeded the maximum number of allowed retries!"); } }); }); diff --git a/test/safeRequest.spec.ts b/test/safeRequest.spec.ts deleted file mode 100644 index 4fff367..0000000 --- a/test/safeRequest.spec.ts +++ /dev/null @@ -1,2155 +0,0 @@ -/* eslint-disable max-lines */ - -// Import Node.js Dependencies -import { brotliCompress, deflate, gzip } from "node:zlib"; -import { randomInt } from "node:crypto"; -import { promisify } from "node:util"; - -// Import Third-party Dependencies -import { Interceptable, MockAgent, setGlobalDispatcher } from "undici"; - -// Import Internal Dependencies -import { safeDel, safeGet, safePost, safePut } from "../src/request"; -import { isHTTPError, isHttpieError } from "../src"; - -// CONSTANTS -const kUrl = "http://test.com"; -const kAsyncGzip = promisify(gzip); -const kAsyncBrotli = promisify(brotliCompress); -const kAsyncDeflate = promisify(deflate); - -// VARS -let pool: Interceptable; - -describe("Httpie.safeRequest", () => { - beforeAll(() => { - const mockAgent = new MockAgent(); - setGlobalDispatcher(mockAgent); - mockAgent.disableNetConnect(); - - pool = mockAgent.get(kUrl); - }); - - describe("with ThrowOnHttpError", () => { - describe("GET", () => { - it("should throw if the response status code is higher than 400", async() => { - expect.assertions(5); - - const target = { - method: "GET", - path: "/test" - }; - - const statusCode = randomInt(400, 503); - const headers = { "content-type": "text/html" }; - const payload = Buffer.from("Body"); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - try { - const result = await safeGet(kUrl + target.path); - expect(result.ok).toBeFalsy(); - result.unwrap(); - } - catch (error: any) { - expect(isHTTPError(error)).toBeTruthy(); - expect(error.statusCode).toBe(statusCode); - expect(error.data).toBe(payload.toString()); - expect(error.headers).toMatchObject(headers); - } - }); - - it("should not throw if the response status code is lower than 400", async() => { - const target = { - method: "GET", - path: "/test" - }; - - const statusCode = randomInt(200, 399); - const headers = { "content-type": "text/html" }; - const payload = Buffer.from("Body"); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - const result = await safeGet(kUrl + target.path); - expect(result.ok).toBeTruthy(); - - const response = result.unwrap(); - expect(response.statusCode).toBe(statusCode); - expect(response.data).toBe(payload.toString()); - expect(response.headers).toMatchObject(headers); - }); - }); - - describe("POST", () => { - it("should throw if the response status code is higher than 400", async() => { - expect.assertions(5); - - const target = { - method: "POST", - path: "/test" - }; - - const statusCode = randomInt(400, 503); - const headers = { "content-type": "text/html" }; - const payload = Buffer.from("Body"); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - try { - const result = await safePost(kUrl + target.path); - expect(result.ok).toBeFalsy(); - result.unwrap(); - } - catch (error: any) { - expect(isHTTPError(error)).toBeTruthy(); - expect(error.statusCode).toBe(statusCode); - expect(error.data).toBe(payload.toString()); - expect(error.headers).toMatchObject(headers); - } - }); - - it("should not throw if the response status code is lower than 400", async() => { - const target = { - method: "POST", - path: "/test" - }; - - const statusCode = randomInt(200, 399); - const headers = { "content-type": "text/html" }; - const payload = Buffer.from("Body"); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - const result = await safePost(kUrl + target.path); - expect(result.ok).toBeTruthy(); - - const response = result.unwrap(); - expect(response.statusCode).toBe(statusCode); - expect(response.data).toBe(payload.toString()); - expect(response.headers).toMatchObject(headers); - }); - }); - - describe("PUT", () => { - it("should throw if the response status code is higher than 400", async() => { - expect.assertions(5); - - const target = { - method: "PUT", - path: "/test" - }; - - const statusCode = randomInt(400, 503); - const headers = { "content-type": "text/html" }; - const payload = Buffer.from("Body"); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - try { - const result = await safePut(kUrl + target.path); - expect(result.ok).toBeFalsy(); - result.unwrap(); - } - catch (error: any) { - expect(isHTTPError(error)).toBeTruthy(); - expect(error.statusCode).toBe(statusCode); - expect(error.data).toBe(payload.toString()); - expect(error.headers).toMatchObject(headers); - } - }); - - it("should not throw if the response status code is lower than 400", async() => { - const target = { - method: "PUT", - path: "/test" - }; - - const statusCode = randomInt(200, 399); - const headers = { "content-type": "text/html" }; - const payload = Buffer.from("Body"); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - const result = await safePut(kUrl + target.path); - expect(result.ok).toBeTruthy(); - - const response = result.unwrap(); - expect(response.statusCode).toBe(statusCode); - expect(response.data).toBe(payload.toString()); - expect(response.headers).toMatchObject(headers); - }); - }); - - describe("DELETE", () => { - it("should throw if the response status code is higher than 400", async() => { - expect.assertions(5); - - const target = { - method: "DELETE", - path: "/test" - }; - - const statusCode = randomInt(400, 503); - const headers = { "content-type": "text/html" }; - const payload = Buffer.from("Body"); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - try { - const result = await safeDel(kUrl + target.path); - expect(result.ok).toBeFalsy(); - result.unwrap(); - } - catch (error: any) { - expect(isHTTPError(error)).toBeTruthy(); - expect(error.statusCode).toBe(statusCode); - expect(error.data).toBe(payload.toString()); - expect(error.headers).toMatchObject(headers); - } - }); - - it("should not throw if the response status code is lower than 400", async() => { - const target = { - method: "DELETE", - path: "/test" - }; - - const statusCode = randomInt(200, 399); - const headers = { "content-type": "text/html" }; - const payload = Buffer.from("Body"); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - const result = await safeDel(kUrl + target.path); - expect(result.ok).toBeTruthy(); - - const response = result.unwrap(); - expect(response.statusCode).toBe(statusCode); - expect(response.data).toBe(payload.toString()); - expect(response.headers).toMatchObject(headers); - }); - }); - }); - - describe("without ThrowOnHttpError", () => { - describe("GET", () => { - it("should not throw if the response status code is higher than 400", async() => { - const target = { - method: "GET", - path: "/test" - }; - - const statusCode = randomInt(400, 503); - const headers = { "content-type": "text/html" }; - const payload = Buffer.from("Body"); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - const result = await safeGet(kUrl + target.path, { throwOnHttpError: false }); - const response = result.unwrap(); - expect(result.ok).toBeTruthy(); - expect(response.statusCode).toBe(statusCode); - expect(response.data).toBe(payload.toString()); - expect(response.headers).toMatchObject(headers); - }); - - it("should not throw if the response status code is lower than 400", async() => { - const target = { - method: "GET", - path: "/test" - }; - - const statusCode = randomInt(200, 399); - const headers = { "content-type": "text/html" }; - const payload = Buffer.from("Body"); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - const result = await safeGet(kUrl + target.path, { throwOnHttpError: false }); - const response = result.unwrap(); - expect(result.ok).toBeTruthy(); - expect(response.statusCode).toBe(statusCode); - expect(response.data).toBe(payload.toString()); - expect(response.headers).toMatchObject(headers); - }); - }); - - describe("POST", () => { - it("should not throw if the response status code is higher than 400", async() => { - const target = { - method: "POST", - path: "/test" - }; - - const statusCode = randomInt(400, 503); - const headers = { "content-type": "text/html" }; - const payload = Buffer.from("Body"); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - const result = await safePost(kUrl + target.path, { throwOnHttpError: false }); - const response = result.unwrap(); - expect(result.ok).toBeTruthy(); - expect(response.statusCode).toBe(statusCode); - expect(response.data).toBe(payload.toString()); - expect(response.headers).toMatchObject(headers); - }); - - it("should not throw if the response status code is lower than 400", async() => { - const target = { - method: "POST", - path: "/test" - }; - - const statusCode = randomInt(200, 399); - const headers = { "content-type": "text/html" }; - const payload = Buffer.from("Body"); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - const result = await safePost(kUrl + target.path, { throwOnHttpError: false }); - const response = result.unwrap(); - expect(result.ok).toBeTruthy(); - expect(response.statusCode).toBe(statusCode); - expect(response.data).toBe(payload.toString()); - expect(response.headers).toMatchObject(headers); - }); - }); - - describe("PUT", () => { - it("should not throw if the response status code is higher than 400", async() => { - const target = { - method: "PUT", - path: "/test" - }; - - const statusCode = randomInt(400, 503); - const headers = { "content-type": "text/html" }; - const payload = Buffer.from("Body"); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - const result = await safePut(kUrl + target.path, { throwOnHttpError: false }); - const response = result.unwrap(); - expect(result.ok).toBeTruthy(); - expect(response.statusCode).toBe(statusCode); - expect(response.data).toBe(payload.toString()); - expect(response.headers).toMatchObject(headers); - }); - - it("should not throw if the response status code is lower than 400", async() => { - const target = { - method: "PUT", - path: "/test" - }; - - const statusCode = randomInt(200, 399); - const headers = { "content-type": "text/html" }; - const payload = Buffer.from("Body"); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - const result = await safePut(kUrl + target.path, { throwOnHttpError: false }); - const response = result.unwrap(); - expect(result.ok).toBeTruthy(); - expect(response.statusCode).toBe(statusCode); - expect(response.data).toBe(payload.toString()); - expect(response.headers).toMatchObject(headers); - }); - }); - - describe("DELETE", () => { - it("should not throw if the response status code is higher than 400", async() => { - const target = { - method: "DELETE", - path: "/test" - }; - - const statusCode = randomInt(400, 503); - const headers = { "content-type": "text/html" }; - const payload = Buffer.from("Body"); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - const result = await safeDel(kUrl + target.path, { throwOnHttpError: false }); - const response = result.unwrap(); - expect(result.ok).toBeTruthy(); - expect(response.statusCode).toBe(statusCode); - expect(response.data).toBe(payload.toString()); - expect(response.headers).toMatchObject(headers); - }); - - it("should not throw if the response status code is lower than 400", async() => { - const target = { - method: "DELETE", - path: "/test" - }; - - const statusCode = randomInt(200, 399); - const headers = { "content-type": "text/html" }; - const payload = Buffer.from("Body"); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - const result = await safeDel(kUrl + target.path, { throwOnHttpError: false }); - const response = result.unwrap(); - expect(result.ok).toBeTruthy(); - expect(response.statusCode).toBe(statusCode); - expect(response.data).toBe(payload.toString()); - expect(response.headers).toMatchObject(headers); - }); - }); - }); - - describe("PARSE mode (default)", () => { - describe("GET", () => { - it("should return a parsed response as text when 'content-type' header starts with 'text/'", async() => { - const target = { - method: "GET", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "text/klsmdkf" }; - const payload = "La data."; - const buf = Buffer.from(payload); - - pool.intercept(target).reply(statusCode, buf, { headers }); - - const result = await safeGet(kUrl + target.path); - expect(result.ok).toBeTruthy(); - - const response = result.unwrap(); - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - expect(response.statusCode).toBe(statusCode); - }); - - it("should return a parsed response as object when 'content-type' header is set with 'application/json'", async() => { - const target = { - method: "GET", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "application/json" }; - const payload = { my: "object" }; - const buf = Buffer.from(JSON.stringify(payload)); - - pool.intercept(target).reply(statusCode, buf, { headers }); - - const result = await safeGet(kUrl + target.path); - expect(result.ok).toBeTruthy(); - - const response = result.unwrap(); - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - expect(response.statusCode).toBe(statusCode); - }); - - it("should return a buffer when 'content-type' header is set with 'application/pdf'", async() => { - const target = { - method: "GET", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "application/pdf" }; - const payload = Buffer.from("mon pdf"); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - const result = await safeGet(kUrl + target.path); - expect(result.ok).toBeTruthy(); - - const response = result.unwrap(); - expect(response.data).toStrictEqual(Buffer.from(payload)); - expect(response.headers).toMatchObject(headers); - expect(response.statusCode).toBe(statusCode); - }); - - it("should return a buffer when 'content-type' header is set with unsupported value", async() => { - const target = { - method: "GET", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "application/msword" }; - const payload = Buffer.from(JSON.stringify({ my: "object" })); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - const result = await safeGet(kUrl + target.path); - expect(result.ok).toBeTruthy(); - - const response = result.unwrap(); - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - }); - - it("should throw when 'content-type' header is set with unsupported value", async() => { - expect.assertions(6); - - const target = { - method: "GET", - path: "/test" - }; - const statusCode = 200; - const headers = { "content-type": "unknown" }; - const payload = Buffer.from(JSON.stringify({ my: "object" })); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - try { - const result = await safeGet(kUrl + target.path); - expect(result.ok).toBeFalsy(); - result.unwrap(); - } - catch (error: any) { - expect(isHttpieError(error)).toBeTruthy(); - expect(error.message).toStrictEqual( - "An unexpected error occurred when trying to parse the response body (reason: 'invalid media type')." - ); - expect(error.buffer).toStrictEqual(payload); - expect(error.headers).toMatchObject(headers); - expect(error.statusCode).toBe(200); - } - }); - - it("should throw when 'content-encoding' header is set with unsupported value", async() => { - expect.assertions(6); - - const target = { - method: "GET", - path: "/test" - }; - - const payload = await kAsyncGzip("Mon document"); - const headers = { "content-encoding": "unknown" }; - const statusCode = 200; - - pool.intercept(target).reply(statusCode, payload, { headers }); - - try { - const result = await safeGet(kUrl + target.path); - expect(result.ok).toBeFalsy(); - result.unwrap(); - } - catch (error: any) { - expect(error.message).toStrictEqual("Unsupported encoding 'unknown'."); - expect(error.buffer).toStrictEqual(payload); - expect(error.headers).toMatchObject(headers); - expect(error.statusCode).toBe(statusCode); - expect(isHttpieError(error)).toBeTruthy(); - } - }); - - it("should decompress data when 'content-encoding' header is set with 'gzip'", async() => { - const target = { - method: "GET", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "text/html", "content-encoding": "gzip" }; - const payload = Buffer.from("Payload"); - const compressedPayload = await kAsyncGzip(payload); - - pool.intercept(target).reply(statusCode, compressedPayload, { headers }); - - const result = await safeGet(kUrl + target.path); - expect(result.ok).toBeTruthy(); - - const response = result.unwrap(); - expect(response.data).toStrictEqual(payload.toString()); - expect(response.headers).toMatchObject(headers); - expect(response.statusCode).toBe(statusCode); - }); - - it("should decompress data when 'content-encoding' header is set with 'x-gzip'", async() => { - const target = { - method: "GET", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "text/html", "content-encoding": "x-gzip" }; - const payload = "Payload"; - const compressedPayload = await kAsyncGzip(payload); - - pool.intercept(target).reply(statusCode, compressedPayload, { headers }); - - const result = await safeGet(kUrl + target.path); - const response = result.unwrap(); - expect(result.ok).toBeTruthy(); - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - }); - - it("should decompress data when 'content-encoding' header is set with 'br'", async() => { - const target = { - method: "GET", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "text/html", "content-encoding": "br" }; - const payload = "Payload"; - const compressedPayload = await kAsyncBrotli(payload); - - pool.intercept(target).reply(statusCode, compressedPayload, { headers }); - - const result = await safeGet(kUrl + target.path); - expect(result.ok).toBeTruthy(); - - const response = result.unwrap(); - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - }); - - it("should decompress data when 'content-encoding' header is set with 'deflate'", async() => { - const target = { - method: "GET", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "text/html", "content-encoding": "deflate" }; - const payload = "Payload"; - const compressedPayload = await kAsyncDeflate(payload); - - pool.intercept(target).reply(statusCode, compressedPayload, { headers }); - - const result = await safeGet(kUrl + target.path); - const response = result.unwrap(); - expect(result.ok).toBeTruthy(); - - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - expect(response.statusCode).toBe(200); - }); - }); - - describe("POST", () => { - it("should return a parsed response as text when 'content-type' header starts with 'text/'", async() => { - const target = { - method: "POST", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "text/klsmdkf" }; - const payload = "La data."; - const buf = Buffer.from(payload); - - pool.intercept(target).reply(statusCode, buf, { headers }); - - const result = await safePost(kUrl + target.path); - expect(result.ok).toBeTruthy(); - - const response = result.unwrap(); - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - expect(response.statusCode).toBe(statusCode); - }); - - it("should return a parsed response as object when 'content-type' header is set with 'application/json'", async() => { - const target = { - method: "POST", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "application/json" }; - const payload = { my: "object" }; - const buf = Buffer.from(JSON.stringify(payload)); - - pool.intercept(target).reply(statusCode, buf, { headers }); - - const result = await safePost(kUrl + target.path); - expect(result.ok).toBeTruthy(); - - const response = result.unwrap(); - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - expect(response.statusCode).toBe(statusCode); - }); - - it("should return a buffer when 'content-type' header is set with 'application/pdf'", async() => { - const target = { - method: "POST", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "application/pdf" }; - const payload = Buffer.from("mon pdf"); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - const result = await safePost(kUrl + target.path); - expect(result.ok).toBeTruthy(); - - const response = result.unwrap(); - expect(response.data).toStrictEqual(Buffer.from(payload)); - expect(response.headers).toMatchObject(headers); - expect(response.statusCode).toBe(statusCode); - }); - - it("should return a buffer when 'content-type' header is set with unsupported value", async() => { - const target = { - method: "POST", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "application/msword" }; - const payload = Buffer.from(JSON.stringify({ my: "object" })); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - const result = await safePost(kUrl + target.path); - expect(result.ok).toBeTruthy(); - - const response = result.unwrap(); - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - }); - - it("should throw when 'content-type' header is set with unsupported value", async() => { - expect.assertions(6); - - const target = { - method: "POST", - path: "/test" - }; - const statusCode = 200; - const headers = { "content-type": "unknown" }; - const payload = Buffer.from(JSON.stringify({ my: "object" })); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - try { - const result = await safePost(kUrl + target.path); - expect(result.ok).toBeFalsy(); - result.unwrap(); - } - catch (error: any) { - expect(isHttpieError(error)).toBeTruthy(); - expect(error.message).toStrictEqual( - "An unexpected error occurred when trying to parse the response body (reason: 'invalid media type')." - ); - expect(error.buffer).toStrictEqual(payload); - expect(error.headers).toMatchObject(headers); - expect(error.statusCode).toBe(200); - } - }); - - it("should throw when 'content-encoding' header is set with unsupported value", async() => { - expect.assertions(6); - - const target = { - method: "POST", - path: "/test" - }; - - const payload = await kAsyncGzip("Mon document"); - const headers = { "content-encoding": "unknown" }; - const statusCode = 200; - - pool.intercept(target).reply(statusCode, payload, { headers }); - - try { - const result = await safePost(kUrl + target.path); - expect(result.ok).toBeFalsy(); - result.unwrap(); - } - catch (error: any) { - expect(error.message).toStrictEqual("Unsupported encoding 'unknown'."); - expect(error.buffer).toStrictEqual(payload); - expect(error.headers).toMatchObject(headers); - expect(error.statusCode).toBe(statusCode); - expect(isHttpieError(error)).toBeTruthy(); - } - }); - - it("should decompress data when 'content-encoding' header is set with 'gzip'", async() => { - const target = { - method: "POST", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "text/html", "content-encoding": "gzip" }; - const payload = Buffer.from("Payload"); - const compressedPayload = await kAsyncGzip(payload); - - pool.intercept(target).reply(statusCode, compressedPayload, { headers }); - - const result = await safePost(kUrl + target.path); - expect(result.ok).toBeTruthy(); - - const response = result.unwrap(); - expect(response.data).toStrictEqual(payload.toString()); - expect(response.headers).toMatchObject(headers); - expect(response.statusCode).toBe(statusCode); - }); - - it("should decompress data when 'content-encoding' header is set with 'x-gzip'", async() => { - const target = { - method: "POST", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "text/html", "content-encoding": "x-gzip" }; - const payload = "Payload"; - const compressedPayload = await kAsyncGzip(payload); - - pool.intercept(target).reply(statusCode, compressedPayload, { headers }); - - const result = await safePost(kUrl + target.path); - const response = result.unwrap(); - expect(result.ok).toBeTruthy(); - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - }); - - it("should decompress data when 'content-encoding' header is set with 'br'", async() => { - const target = { - method: "POST", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "text/html", "content-encoding": "br" }; - const payload = "Payload"; - const compressedPayload = await kAsyncBrotli(payload); - - pool.intercept(target).reply(statusCode, compressedPayload, { headers }); - - const result = await safePost(kUrl + target.path); - expect(result.ok).toBeTruthy(); - - const response = result.unwrap(); - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - }); - - it("should decompress data when 'content-encoding' header is set with 'deflate'", async() => { - const target = { - method: "POST", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "text/html", "content-encoding": "deflate" }; - const payload = "Payload"; - const compressedPayload = await kAsyncDeflate(payload); - - pool.intercept(target).reply(statusCode, compressedPayload, { headers }); - - const result = await safePost(kUrl + target.path); - const response = result.unwrap(); - expect(result.ok).toBeTruthy(); - - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - expect(response.statusCode).toBe(200); - }); - }); - - describe("PUT", () => { - it("should return a parsed response as text when 'content-type' header starts with 'text/'", async() => { - const target = { - method: "PUT", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "text/klsmdkf" }; - const payload = "La data."; - const buf = Buffer.from(payload); - - pool.intercept(target).reply(statusCode, buf, { headers }); - - const result = await safePut(kUrl + target.path); - expect(result.ok).toBeTruthy(); - - const response = result.unwrap(); - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - expect(response.statusCode).toBe(statusCode); - }); - - it("should return a parsed response as object when 'content-type' header is set with 'application/json'", async() => { - const target = { - method: "PUT", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "application/json" }; - const payload = { my: "object" }; - const buf = Buffer.from(JSON.stringify(payload)); - - pool.intercept(target).reply(statusCode, buf, { headers }); - - const result = await safePut(kUrl + target.path); - expect(result.ok).toBeTruthy(); - - const response = result.unwrap(); - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - expect(response.statusCode).toBe(statusCode); - }); - - it("should return a buffer when 'content-type' header is set with 'application/pdf'", async() => { - const target = { - method: "PUT", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "application/pdf" }; - const payload = Buffer.from("mon pdf"); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - const result = await safePut(kUrl + target.path); - expect(result.ok).toBeTruthy(); - - const response = result.unwrap(); - expect(response.data).toStrictEqual(Buffer.from(payload)); - expect(response.headers).toMatchObject(headers); - expect(response.statusCode).toBe(statusCode); - }); - - it("should return a buffer when 'content-type' header is set with unsupported value", async() => { - const target = { - method: "PUT", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "application/msword" }; - const payload = Buffer.from(JSON.stringify({ my: "object" })); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - const result = await safePut(kUrl + target.path); - expect(result.ok).toBeTruthy(); - - const response = result.unwrap(); - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - }); - - it("should throw when 'content-type' header is set with unsupported value", async() => { - expect.assertions(6); - - const target = { - method: "PUT", - path: "/test" - }; - const statusCode = 200; - const headers = { "content-type": "unknown" }; - const payload = Buffer.from(JSON.stringify({ my: "object" })); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - try { - const result = await safePut(kUrl + target.path); - expect(result.ok).toBeFalsy(); - result.unwrap(); - } - catch (error: any) { - expect(isHttpieError(error)).toBeTruthy(); - expect(error.message).toStrictEqual( - "An unexpected error occurred when trying to parse the response body (reason: 'invalid media type')." - ); - expect(error.buffer).toStrictEqual(payload); - expect(error.headers).toMatchObject(headers); - expect(error.statusCode).toBe(200); - } - }); - - it("should throw when 'content-encoding' header is set with unsupported value", async() => { - expect.assertions(6); - - const target = { - method: "PUT", - path: "/test" - }; - - const payload = await kAsyncGzip("Mon document"); - const headers = { "content-encoding": "unknown" }; - const statusCode = 200; - - pool.intercept(target).reply(statusCode, payload, { headers }); - - try { - const result = await safePut(kUrl + target.path); - expect(result.ok).toBeFalsy(); - result.unwrap(); - } - catch (error: any) { - expect(error.message).toStrictEqual("Unsupported encoding 'unknown'."); - expect(error.buffer).toStrictEqual(payload); - expect(error.headers).toMatchObject(headers); - expect(error.statusCode).toBe(statusCode); - expect(isHttpieError(error)).toBeTruthy(); - } - }); - - it("should decompress data when 'content-encoding' header is set with 'gzip'", async() => { - const target = { - method: "PUT", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "text/html", "content-encoding": "gzip" }; - const payload = Buffer.from("Payload"); - const compressedPayload = await kAsyncGzip(payload); - - pool.intercept(target).reply(statusCode, compressedPayload, { headers }); - - const result = await safePut(kUrl + target.path); - expect(result.ok).toBeTruthy(); - - const response = result.unwrap(); - expect(response.data).toStrictEqual(payload.toString()); - expect(response.headers).toMatchObject(headers); - expect(response.statusCode).toBe(statusCode); - }); - - it("should decompress data when 'content-encoding' header is set with 'x-gzip'", async() => { - const target = { - method: "PUT", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "text/html", "content-encoding": "x-gzip" }; - const payload = "Payload"; - const compressedPayload = await kAsyncGzip(payload); - - pool.intercept(target).reply(statusCode, compressedPayload, { headers }); - - const result = await safePut(kUrl + target.path); - const response = result.unwrap(); - expect(result.ok).toBeTruthy(); - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - }); - - it("should decompress data when 'content-encoding' header is set with 'br'", async() => { - const target = { - method: "PUT", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "text/html", "content-encoding": "br" }; - const payload = "Payload"; - const compressedPayload = await kAsyncBrotli(payload); - - pool.intercept(target).reply(statusCode, compressedPayload, { headers }); - - const result = await safePut(kUrl + target.path); - expect(result.ok).toBeTruthy(); - - const response = result.unwrap(); - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - }); - - it("should decompress data when 'content-encoding' header is set with 'deflate'", async() => { - const target = { - method: "PUT", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "text/html", "content-encoding": "deflate" }; - const payload = "Payload"; - const compressedPayload = await kAsyncDeflate(payload); - - pool.intercept(target).reply(statusCode, compressedPayload, { headers }); - - const result = await safePut(kUrl + target.path); - const response = result.unwrap(); - expect(result.ok).toBeTruthy(); - - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - expect(response.statusCode).toBe(200); - }); - }); - - describe("DELETE", () => { - it("should return a parsed response as text when 'content-type' header starts with 'text/'", async() => { - const target = { - method: "DELETE", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "text/klsmdkf" }; - const payload = "La data."; - const buf = Buffer.from(payload); - - pool.intercept(target).reply(statusCode, buf, { headers }); - - const result = await safeDel(kUrl + target.path); - expect(result.ok).toBeTruthy(); - - const response = result.unwrap(); - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - expect(response.statusCode).toBe(statusCode); - }); - - it("should return a parsed response as object when 'content-type' header is set with 'application/json'", async() => { - const target = { - method: "DELETE", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "application/json" }; - const payload = { my: "object" }; - const buf = Buffer.from(JSON.stringify(payload)); - - pool.intercept(target).reply(statusCode, buf, { headers }); - - const result = await safeDel(kUrl + target.path); - expect(result.ok).toBeTruthy(); - - const response = result.unwrap(); - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - expect(response.statusCode).toBe(statusCode); - }); - - it("should return a buffer when 'content-type' header is set with 'application/pdf'", async() => { - const target = { - method: "DELETE", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "application/pdf" }; - const payload = Buffer.from("mon pdf"); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - const result = await safeDel(kUrl + target.path); - expect(result.ok).toBeTruthy(); - - const response = result.unwrap(); - expect(response.data).toStrictEqual(Buffer.from(payload)); - expect(response.headers).toMatchObject(headers); - expect(response.statusCode).toBe(statusCode); - }); - - it("should return a buffer when 'content-type' header is set with unsupported value", async() => { - const target = { - method: "DELETE", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "application/msword" }; - const payload = Buffer.from(JSON.stringify({ my: "object" })); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - const result = await safeDel(kUrl + target.path); - expect(result.ok).toBeTruthy(); - - const response = result.unwrap(); - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - }); - - it("should throw when 'content-type' header is set with unsupported value", async() => { - expect.assertions(6); - - const target = { - method: "DELETE", - path: "/test" - }; - const statusCode = 200; - const headers = { "content-type": "unknown" }; - const payload = Buffer.from(JSON.stringify({ my: "object" })); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - try { - const result = await safeDel(kUrl + target.path); - expect(result.ok).toBeFalsy(); - result.unwrap(); - } - catch (error: any) { - expect(isHttpieError(error)).toBeTruthy(); - expect(error.message).toStrictEqual( - "An unexpected error occurred when trying to parse the response body (reason: 'invalid media type')." - ); - expect(error.buffer).toStrictEqual(payload); - expect(error.headers).toMatchObject(headers); - expect(error.statusCode).toBe(200); - } - }); - - it("should throw when 'content-encoding' header is set with unsupported value", async() => { - expect.assertions(6); - - const target = { - method: "DELETE", - path: "/test" - }; - - const payload = await kAsyncGzip("Mon document"); - const headers = { "content-encoding": "unknown" }; - const statusCode = 200; - - pool.intercept(target).reply(statusCode, payload, { headers }); - - try { - const result = await safeDel(kUrl + target.path); - expect(result.ok).toBeFalsy(); - result.unwrap(); - } - catch (error: any) { - expect(error.message).toStrictEqual("Unsupported encoding 'unknown'."); - expect(error.buffer).toStrictEqual(payload); - expect(error.headers).toMatchObject(headers); - expect(error.statusCode).toBe(statusCode); - expect(isHttpieError(error)).toBeTruthy(); - } - }); - - it("should decompress data when 'content-encoding' header is set with 'gzip'", async() => { - const target = { - method: "DELETE", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "text/html", "content-encoding": "gzip" }; - const payload = Buffer.from("Payload"); - const compressedPayload = await kAsyncGzip(payload); - - pool.intercept(target).reply(statusCode, compressedPayload, { headers }); - - const result = await safeDel(kUrl + target.path); - expect(result.ok).toBeTruthy(); - - const response = result.unwrap(); - expect(response.data).toStrictEqual(payload.toString()); - expect(response.headers).toMatchObject(headers); - expect(response.statusCode).toBe(statusCode); - }); - - it("should decompress data when 'content-encoding' header is set with 'x-gzip'", async() => { - const target = { - method: "DELETE", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "text/html", "content-encoding": "x-gzip" }; - const payload = "Payload"; - const compressedPayload = await kAsyncGzip(payload); - - pool.intercept(target).reply(statusCode, compressedPayload, { headers }); - - const result = await safeDel(kUrl + target.path); - const response = result.unwrap(); - expect(result.ok).toBeTruthy(); - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - }); - - it("should decompress data when 'content-encoding' header is set with 'br'", async() => { - const target = { - method: "DELETE", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "text/html", "content-encoding": "br" }; - const payload = "Payload"; - const compressedPayload = await kAsyncBrotli(payload); - - pool.intercept(target).reply(statusCode, compressedPayload, { headers }); - - const result = await safeDel(kUrl + target.path); - expect(result.ok).toBeTruthy(); - - const response = result.unwrap(); - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - }); - - it("should decompress data when 'content-encoding' header is set with 'deflate'", async() => { - const target = { - method: "DELETE", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "text/html", "content-encoding": "deflate" }; - const payload = "Payload"; - const compressedPayload = await kAsyncDeflate(payload); - - pool.intercept(target).reply(statusCode, compressedPayload, { headers }); - - const result = await safeDel(kUrl + target.path); - const response = result.unwrap(); - expect(result.ok).toBeTruthy(); - - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - expect(response.statusCode).toBe(200); - }); - }); - }); - - describe("DECOMPRESS mode", () => { - describe("GET", () => { - it("should return a buffer without parsing it even if 'content-type' header exists", async() => { - const target = { - method: "GET", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "text/klsmdkf" }; - const payload = Buffer.from("La data."); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - const result = await safeGet(kUrl + target.path, { mode: "decompress" }); - const response = result.unwrap(); - expect(result.ok).toBeTruthy(); - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - }); - - it("should throw when 'content-encoding' header is set with unsupported value", async() => { - expect.assertions(6); - - const target = { - method: "GET", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-encoding": "unknown" }; - const payload = Buffer.from("Mon document"); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - try { - const result = await safeGet(kUrl + target.path, { mode: "decompress" }); - expect(result.ok).toBeFalsy(); - result.unwrap(); - } - catch (error: any) { - expect(error.message).toStrictEqual("Unsupported encoding 'unknown'."); - expect(error.buffer).toMatchObject(payload); - expect(error.headers).toMatchObject(headers); - expect(isHttpieError(error)).toBeTruthy(); - expect(error.statusCode).toBe(200); - } - }); - - it("should throw when 'content-encoding' header is invalid", async() => { - expect.assertions(10); - - const target = { - method: "GET", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-encoding": "gzip" }; - const payload = await kAsyncBrotli("Mon document"); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - try { - const result = await safeGet(kUrl + target.path, { mode: "decompress" }); - expect(result.ok).toBeFalsy(); - result.unwrap(); - } - catch (error: any) { - expect(error).toBeTruthy(); - expect(error.reason).toBeTruthy(); - expect(error.message).toStrictEqual( - "An unexpected error occurred when trying to decompress the response body (reason: 'incorrect header check')." - ); - expect(error.buffer).toMatchObject(payload); - expect(error.headers).toMatchObject(headers); - expect(error.reason).toBeTruthy(); - expect(error.reason.message).toStrictEqual("incorrect header check"); - expect(isHttpieError(error)).toBeTruthy(); - expect(error.statusCode).toBe(200); - } - }); - - it("should decompress data when 'content-encoding' header is set with 'gzip'", async() => { - const target = { - method: "GET", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "text/html", "content-encoding": "gzip" }; - const payload = Buffer.from("payload"); - const compressedPayload = await kAsyncGzip("payload"); - - pool.intercept(target).reply(statusCode, compressedPayload, { headers }); - - const result = await safeGet(kUrl + target.path, { mode: "decompress" }); - expect(result.ok).toBeTruthy(); - - const response = result.unwrap(); - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - }); - - it("should decompress data when 'content-encoding' header is set with 'x-gzip'", async() => { - const target = { - method: "GET", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "text/html", "content-encoding": "x-gzip" }; - const payload = Buffer.from("Payload"); - const compressedPayload = await kAsyncGzip(payload); - - pool.intercept(target).reply(statusCode, compressedPayload, { headers }); - - const result = await safeGet(kUrl + target.path, { mode: "decompress" }); - expect(result.ok).toBeTruthy(); - - const response = result.unwrap(); - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - }); - - it("should decompress data when 'content-encoding' header is set with 'br'", async() => { - const target = { - method: "GET", - path: "/test" - }; - - const payload = Buffer.from("Payload"); - const compressedPayload = await kAsyncBrotli(payload); - const statusCode = 200; - const headers = { "content-type": "text/html", "content-encoding": "br" }; - - pool.intercept(target).reply(statusCode, compressedPayload, { headers }); - const result = await safeGet(kUrl + target.path, { mode: "decompress" }); - const response = result.unwrap(); - expect(result.ok).toBeTruthy(); - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - }); - - it("should decompress data when 'content-encoding' header is set with 'deflate'", async() => { - const target = { - method: "GET", - path: "/test" - }; - - const payload = Buffer.from("Payload"); - const compressedPayload = await kAsyncDeflate(payload); - - const statusCode = 200; - const headers = { "content-type": "text/html", "content-encoding": "deflate" }; - - pool.intercept(target).reply(statusCode, compressedPayload, { headers }); - const result = await safeGet(kUrl + target.path, { mode: "decompress" }); - const response = result.unwrap(); - expect(result.ok).toBeTruthy(); - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - }); - }); - - describe("POST", () => { - it("should return a buffer without parsing it even if 'content-type' header exists", async() => { - const target = { - method: "POST", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "text/klsmdkf" }; - const payload = Buffer.from("La data."); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - const result = await safePost(kUrl + target.path, { mode: "decompress" }); - const response = result.unwrap(); - expect(result.ok).toBeTruthy(); - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - }); - - it("should throw when 'content-encoding' header is set with unsupported value", async() => { - expect.assertions(6); - - const target = { - method: "POST", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-encoding": "unknown" }; - const payload = Buffer.from("Mon document"); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - try { - const result = await safePost(kUrl + target.path, { mode: "decompress" }); - expect(result.ok).toBeFalsy(); - result.unwrap(); - } - catch (error: any) { - expect(error.message).toStrictEqual("Unsupported encoding 'unknown'."); - expect(error.buffer).toMatchObject(payload); - expect(error.headers).toMatchObject(headers); - expect(isHttpieError(error)).toBeTruthy(); - expect(error.statusCode).toBe(200); - } - }); - - it("should throw when 'content-encoding' header is invalid", async() => { - expect.assertions(10); - - const target = { - method: "POST", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-encoding": "gzip" }; - const payload = await kAsyncBrotli("Mon document"); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - try { - const result = await safePost(kUrl + target.path, { mode: "decompress" }); - expect(result.ok).toBeFalsy(); - result.unwrap(); - } - catch (error: any) { - expect(error).toBeTruthy(); - expect(error.reason).toBeTruthy(); - expect(error.message).toStrictEqual( - "An unexpected error occurred when trying to decompress the response body (reason: 'incorrect header check')." - ); - expect(error.buffer).toMatchObject(payload); - expect(error.headers).toMatchObject(headers); - expect(error.reason).toBeTruthy(); - expect(error.reason.message).toStrictEqual("incorrect header check"); - expect(isHttpieError(error)).toBeTruthy(); - expect(error.statusCode).toBe(200); - } - }); - - it("should decompress data when 'content-encoding' header is set with 'gzip'", async() => { - const target = { - method: "POST", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "text/html", "content-encoding": "gzip" }; - const payload = Buffer.from("payload"); - const compressedPayload = await kAsyncGzip("payload"); - - pool.intercept(target).reply(statusCode, compressedPayload, { headers }); - - const result = await safePost(kUrl + target.path, { mode: "decompress" }); - expect(result.ok).toBeTruthy(); - - const response = result.unwrap(); - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - }); - - it("should decompress data when 'content-encoding' header is set with 'x-gzip'", async() => { - const target = { - method: "POST", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "text/html", "content-encoding": "x-gzip" }; - const payload = Buffer.from("Payload"); - const compressedPayload = await kAsyncGzip(payload); - - pool.intercept(target).reply(statusCode, compressedPayload, { headers }); - - const result = await safePost(kUrl + target.path, { mode: "decompress" }); - expect(result.ok).toBeTruthy(); - - const response = result.unwrap(); - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - }); - - it("should decompress data when 'content-encoding' header is set with 'br'", async() => { - const target = { - method: "POST", - path: "/test" - }; - - const payload = Buffer.from("Payload"); - const compressedPayload = await kAsyncBrotli(payload); - const statusCode = 200; - const headers = { "content-type": "text/html", "content-encoding": "br" }; - - pool.intercept(target).reply(statusCode, compressedPayload, { headers }); - const result = await safePost(kUrl + target.path, { mode: "decompress" }); - const response = result.unwrap(); - expect(result.ok).toBeTruthy(); - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - }); - - it("should decompress data when 'content-encoding' header is set with 'deflate'", async() => { - const target = { - method: "POST", - path: "/test" - }; - - const payload = Buffer.from("Payload"); - const compressedPayload = await kAsyncDeflate(payload); - - const statusCode = 200; - const headers = { "content-type": "text/html", "content-encoding": "deflate" }; - - pool.intercept(target).reply(statusCode, compressedPayload, { headers }); - const result = await safePost(kUrl + target.path, { mode: "decompress" }); - const response = result.unwrap(); - expect(result.ok).toBeTruthy(); - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - }); - }); - - describe("PUT", () => { - it("should return a buffer without parsing it even if 'content-type' header exists", async() => { - const target = { - method: "PUT", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "text/klsmdkf" }; - const payload = Buffer.from("La data."); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - const result = await safePut(kUrl + target.path, { mode: "decompress" }); - const response = result.unwrap(); - expect(result.ok).toBeTruthy(); - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - }); - - it("should throw when 'content-encoding' header is set with unsupported value", async() => { - expect.assertions(6); - - const target = { - method: "PUT", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-encoding": "unknown" }; - const payload = Buffer.from("Mon document"); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - try { - const result = await safePut(kUrl + target.path, { mode: "decompress" }); - expect(result.ok).toBeFalsy(); - result.unwrap(); - } - catch (error: any) { - expect(error.message).toStrictEqual("Unsupported encoding 'unknown'."); - expect(error.buffer).toMatchObject(payload); - expect(error.headers).toMatchObject(headers); - expect(isHttpieError(error)).toBeTruthy(); - expect(error.statusCode).toBe(200); - } - }); - - it("should throw when 'content-encoding' header is invalid", async() => { - expect.assertions(10); - - const target = { - method: "PUT", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-encoding": "gzip" }; - const payload = await kAsyncBrotli("Mon document"); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - try { - const result = await safePut(kUrl + target.path, { mode: "decompress" }); - expect(result.ok).toBeFalsy(); - result.unwrap(); - } - catch (error: any) { - expect(error).toBeTruthy(); - expect(error.reason).toBeTruthy(); - expect(error.message).toStrictEqual( - "An unexpected error occurred when trying to decompress the response body (reason: 'incorrect header check')." - ); - expect(error.buffer).toMatchObject(payload); - expect(error.headers).toMatchObject(headers); - expect(error.reason).toBeTruthy(); - expect(error.reason.message).toStrictEqual("incorrect header check"); - expect(isHttpieError(error)).toBeTruthy(); - expect(error.statusCode).toBe(200); - } - }); - - it("should decompress data when 'content-encoding' header is set with 'gzip'", async() => { - const target = { - method: "PUT", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "text/html", "content-encoding": "gzip" }; - const payload = Buffer.from("payload"); - const compressedPayload = await kAsyncGzip("payload"); - - pool.intercept(target).reply(statusCode, compressedPayload, { headers }); - - const result = await safePut(kUrl + target.path, { mode: "decompress" }); - expect(result.ok).toBeTruthy(); - - const response = result.unwrap(); - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - }); - - it("should decompress data when 'content-encoding' header is set with 'x-gzip'", async() => { - const target = { - method: "PUT", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "text/html", "content-encoding": "x-gzip" }; - const payload = Buffer.from("Payload"); - const compressedPayload = await kAsyncGzip(payload); - - pool.intercept(target).reply(statusCode, compressedPayload, { headers }); - - const result = await safePut(kUrl + target.path, { mode: "decompress" }); - expect(result.ok).toBeTruthy(); - - const response = result.unwrap(); - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - }); - - it("should decompress data when 'content-encoding' header is set with 'br'", async() => { - const target = { - method: "PUT", - path: "/test" - }; - - const payload = Buffer.from("Payload"); - const compressedPayload = await kAsyncBrotli(payload); - const statusCode = 200; - const headers = { "content-type": "text/html", "content-encoding": "br" }; - - pool.intercept(target).reply(statusCode, compressedPayload, { headers }); - const result = await safePut(kUrl + target.path, { mode: "decompress" }); - const response = result.unwrap(); - expect(result.ok).toBeTruthy(); - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - }); - - it("should decompress data when 'content-encoding' header is set with 'deflate'", async() => { - const target = { - method: "PUT", - path: "/test" - }; - - const payload = Buffer.from("Payload"); - const compressedPayload = await kAsyncDeflate(payload); - - const statusCode = 200; - const headers = { "content-type": "text/html", "content-encoding": "deflate" }; - - pool.intercept(target).reply(statusCode, compressedPayload, { headers }); - const result = await safePut(kUrl + target.path, { mode: "decompress" }); - const response = result.unwrap(); - expect(result.ok).toBeTruthy(); - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - }); - }); - - describe("DELETE", () => { - it("should return a buffer without parsing it even if 'content-type' header exists", async() => { - const target = { - method: "DELETE", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "text/klsmdkf" }; - const payload = Buffer.from("La data."); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - const result = await safeDel(kUrl + target.path, { mode: "decompress" }); - const response = result.unwrap(); - expect(result.ok).toBeTruthy(); - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - }); - - it("should throw when 'content-encoding' header is set with unsupported value", async() => { - expect.assertions(6); - - const target = { - method: "DELETE", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-encoding": "unknown" }; - const payload = Buffer.from("Mon document"); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - try { - const result = await safeDel(kUrl + target.path, { mode: "decompress" }); - expect(result.ok).toBeFalsy(); - result.unwrap(); - } - catch (error: any) { - expect(error.message).toStrictEqual("Unsupported encoding 'unknown'."); - expect(error.buffer).toMatchObject(payload); - expect(error.headers).toMatchObject(headers); - expect(isHttpieError(error)).toBeTruthy(); - expect(error.statusCode).toBe(200); - } - }); - - it("should throw when 'content-encoding' header is invalid", async() => { - expect.assertions(10); - - const target = { - method: "DELETE", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-encoding": "gzip" }; - const payload = await kAsyncBrotli("Mon document"); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - try { - const result = await safeDel(kUrl + target.path, { mode: "decompress" }); - expect(result.ok).toBeFalsy(); - result.unwrap(); - } - catch (error: any) { - expect(error).toBeTruthy(); - expect(error.reason).toBeTruthy(); - expect(error.message).toStrictEqual( - "An unexpected error occurred when trying to decompress the response body (reason: 'incorrect header check')." - ); - expect(error.buffer).toMatchObject(payload); - expect(error.headers).toMatchObject(headers); - expect(error.reason).toBeTruthy(); - expect(error.reason.message).toStrictEqual("incorrect header check"); - expect(isHttpieError(error)).toBeTruthy(); - expect(error.statusCode).toBe(200); - } - }); - - it("should decompress data when 'content-encoding' header is set with 'gzip'", async() => { - const target = { - method: "DELETE", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "text/html", "content-encoding": "gzip" }; - const payload = Buffer.from("payload"); - const compressedPayload = await kAsyncGzip("payload"); - - pool.intercept(target).reply(statusCode, compressedPayload, { headers }); - - const result = await safeDel(kUrl + target.path, { mode: "decompress" }); - expect(result.ok).toBeTruthy(); - - const response = result.unwrap(); - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - }); - - it("should decompress data when 'content-encoding' header is set with 'x-gzip'", async() => { - const target = { - method: "DELETE", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "text/html", "content-encoding": "x-gzip" }; - const payload = Buffer.from("Payload"); - const compressedPayload = await kAsyncGzip(payload); - - pool.intercept(target).reply(statusCode, compressedPayload, { headers }); - - const result = await safeDel(kUrl + target.path, { mode: "decompress" }); - expect(result.ok).toBeTruthy(); - - const response = result.unwrap(); - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - }); - - it("should decompress data when 'content-encoding' header is set with 'br'", async() => { - const target = { - method: "DELETE", - path: "/test" - }; - - const payload = Buffer.from("Payload"); - const compressedPayload = await kAsyncBrotli(payload); - const statusCode = 200; - const headers = { "content-type": "text/html", "content-encoding": "br" }; - - pool.intercept(target).reply(statusCode, compressedPayload, { headers }); - const result = await safeDel(kUrl + target.path, { mode: "decompress" }); - const response = result.unwrap(); - expect(result.ok).toBeTruthy(); - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - }); - - it("should decompress data when 'content-encoding' header is set with 'deflate'", async() => { - const target = { - method: "DELETE", - path: "/test" - }; - - const payload = Buffer.from("Payload"); - const compressedPayload = await kAsyncDeflate(payload); - - const statusCode = 200; - const headers = { "content-type": "text/html", "content-encoding": "deflate" }; - - pool.intercept(target).reply(statusCode, compressedPayload, { headers }); - const result = await safeDel(kUrl + target.path, { mode: "decompress" }); - const response = result.unwrap(); - expect(result.ok).toBeTruthy(); - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - }); - }); - }); - - describe("RAW mode", () => { - describe("GET", () => { - it("should return a buffer without parsing it even if 'content-type' header exists", async() => { - const target = { - method: "GET", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "text/klsmdkf" }; - const payload = Buffer.from("payload"); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - const result = await safeGet(kUrl + target.path, { mode: "raw" }); - expect(result.ok).toBeTruthy(); - - const response = result.unwrap(); - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - expect(response.statusCode).toBe(200); - }); - - it("should return a buffer without decompress it even if 'content-encoding' header exists", async() => { - const target = { - method: "GET", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-encoding": "gzip" }; - const payload = await kAsyncGzip("Doc"); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - const result = await safeGet(kUrl + target.path, { mode: "raw" }); - const response = result.unwrap(); - expect(result.ok).toBeTruthy(); - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - expect(response.statusCode).toBe(200); - }); - }); - - describe("POST", () => { - it("should return a buffer without parsing it even if 'content-type' header exists", async() => { - const target = { - method: "POST", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "text/klsmdkf" }; - const payload = Buffer.from("payload"); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - const result = await safePost(kUrl + target.path, { mode: "raw" }); - expect(result.ok).toBeTruthy(); - - const response = result.unwrap(); - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - expect(response.statusCode).toBe(200); - }); - - it("should return a buffer without decompress it even if 'content-encoding' header exists", async() => { - const target = { - method: "POST", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-encoding": "gzip" }; - const payload = await kAsyncGzip("Doc"); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - const result = await safePost(kUrl + target.path, { mode: "raw" }); - expect(result.ok).toBeTruthy(); - - const response = result.unwrap(); - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - expect(response.statusCode).toBe(200); - }); - }); - - describe("PUT", () => { - it("should return a buffer without parsing it even if 'content-type' header exists", async() => { - const target = { - method: "PUT", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "text/klsmdkf" }; - const payload = Buffer.from("payload"); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - const result = await safePut(kUrl + target.path, { mode: "raw" }); - expect(result.ok).toBeTruthy(); - - const response = result.unwrap(); - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - expect(response.statusCode).toBe(200); - }); - - it("should return a buffer without decompress it even if 'content-encoding' header exists", async() => { - const target = { - method: "PUT", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-encoding": "gzip" }; - const payload = await kAsyncGzip("Doc"); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - const result = await safePut(kUrl + target.path, { mode: "raw" }); - const response = result.unwrap(); - expect(result.ok).toBeTruthy(); - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - expect(response.statusCode).toBe(200); - }); - }); - - describe("DELETE", () => { - it("should return a buffer without parsing it even if 'content-type' header exists", async() => { - const target = { - method: "DELETE", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-type": "text/klsmdkf" }; - const payload = Buffer.from("payload"); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - const result = await safeDel(kUrl + target.path, { mode: "raw" }); - expect(result.ok).toBeTruthy(); - - const response = result.unwrap(); - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - expect(response.statusCode).toBe(200); - }); - - it("should return a buffer without decompress it even if 'content-encoding' header exists", async() => { - const target = { - method: "DELETE", - path: "/test" - }; - - const statusCode = 200; - const headers = { "content-encoding": "gzip" }; - const payload = await kAsyncGzip("Doc"); - - pool.intercept(target).reply(statusCode, payload, { headers }); - - const result = await safeDel(kUrl + target.path, { mode: "raw" }); - const response = result.unwrap(); - expect(result.ok).toBeTruthy(); - expect(response.data).toStrictEqual(payload); - expect(response.headers).toMatchObject(headers); - expect(response.statusCode).toBe(200); - }); - }); - }); -}); diff --git a/test/server/index.ts b/test/server/index.ts index 26cd529..ab1279d 100644 --- a/test/server/index.ts +++ b/test/server/index.ts @@ -11,6 +11,7 @@ import * as undici from "undici"; import { CustomHttpAgent, agents } from "../../src/agents"; // CONSTANTS +const __dirname = import.meta.dirname; const kFixturesPath = path.join(__dirname, "..", "fixtures"); const toUpperCase = new Transform({ @@ -29,15 +30,10 @@ export async function createServer(customPath = "local", port = 3000) { const server = fastify({ logger: false }); const serverAgent: CustomHttpAgent = { customPath, - domains: new Set([ - `localhost:${port}` - ]), agent: new undici.Agent({ connections: 10 }), - prod: `http://localhost:${port}/`, - preprod: `http://localhost:${port}/`, - dev: `http://localhost:${port}/` + origin: `http://localhost:${port}/` }; agents.add(serverAgent); diff --git a/test/stream.spec.ts b/test/stream.test.ts similarity index 77% rename from test/stream.spec.ts rename to test/stream.test.ts index d1e093f..2444067 100644 --- a/test/stream.spec.ts +++ b/test/stream.test.ts @@ -1,7 +1,9 @@ // Import Node.js Dependencies +import { describe, it, before, after } from "node:test"; import { createWriteStream, createReadStream, existsSync, promises as fs } from "node:fs"; import path from "node:path"; import { pipeline } from "node:stream/promises"; +import assert from "node:assert"; // Import Third-party Dependencies import { FastifyInstance } from "fastify"; @@ -11,25 +13,28 @@ import * as httpie from "../src/index"; import { createServer } from "./server/index"; // CONSTANTS +const __dirname = import.meta.dirname; + const kGithubURL = new URL("https://github.com/"); const kFixturesPath = path.join(__dirname, "fixtures"); const kDownloadPath = path.join(__dirname, "download"); let httpServer: FastifyInstance; -beforeAll(async() => { +before(async() => { httpServer = await createServer("stream", 1338); await fs.mkdir(kDownloadPath, { recursive: true }); }); -afterAll(async() => { +after(async() => { await httpServer.close(); await fs.rm(kDownloadPath, { force: true, recursive: true }); }); describe("stream", () => { - it("should use callback dispatcher to init headers/statusCode etc.", async() => { - const fileDestination = path.join(kDownloadPath, "i18n-main.tar.gz"); - const repositoryURL = new URL("NodeSecure/i18n/archive/main.tar.gz", kGithubURL); + // FIX: maxRedirections doesn't work ? + it.skip("should use callback dispatcher to init headers/statusCode etc.", async() => { + const fileDestination = path.join(kDownloadPath, "fs-walk-main.tar.gz"); + const repositoryURL = new URL("NodeSecure/fs-walk/archive/main.tar.gz", kGithubURL); const cursor = httpie.stream("GET", repositoryURL, { headers: { @@ -48,9 +53,9 @@ describe("stream", () => { return createWriteStream(fileDestination); }); - expect(existsSync(fileDestination)).toStrictEqual(true); - expect(contentType).toBe("application/x-gzip"); - expect(code).toBe(200); + assert.ok(existsSync(fileDestination)); + assert.equal(contentType, "application/x-gzip"); + assert.equal(code, 200); }); it("should fetch a .tar.gz of a given github repository", async() => { @@ -65,7 +70,7 @@ describe("stream", () => { maxRedirections: 1 })(() => createWriteStream(fileDestination)); - expect(existsSync(fileDestination)).toStrictEqual(true); + assert.ok(existsSync(fileDestination)); }); it("should fetch the HTML home from the local fastify server", async() => { @@ -73,13 +78,13 @@ describe("stream", () => { await httpie.stream("GET", "/stream/home")(() => createWriteStream(fileDestination)); - expect(existsSync(fileDestination)).toStrictEqual(true); + assert.ok(existsSync(fileDestination)); const [contentA, contentB] = await Promise.all([ fs.readFile(path.join(kFixturesPath, "home.html"), "utf-8"), fs.readFile(path.join(kDownloadPath, "home.html"), "utf-8") ]); - expect(contentA).toStrictEqual(contentB); + assert.equal(contentA, contentB); }); }); @@ -94,12 +99,12 @@ describe("pipeline", () => { createWriteStream(fileDestination) ); - expect(existsSync(fileDestination)).toStrictEqual(true); + assert.ok(existsSync(fileDestination)); const [contentA, contentB] = await Promise.all([ fs.readFile(fixtureLocation, "utf-8"), fs.readFile(fileDestination, "utf-8") ]); - expect(contentA.toUpperCase()).toStrictEqual(contentB); + assert.equal(contentA.toUpperCase(), contentB); }); }); diff --git a/test/undiciResponseHandler.spec.ts b/test/undiciResponseHandler.test.ts similarity index 79% rename from test/undiciResponseHandler.spec.ts rename to test/undiciResponseHandler.test.ts index ef2ef54..c2e7179 100644 --- a/test/undiciResponseHandler.spec.ts +++ b/test/undiciResponseHandler.test.ts @@ -1,6 +1,8 @@ // Import Node.js Dependencies import { randomBytes } from "node:crypto"; +import { describe, it } from "node:test"; +import assert from "node:assert"; // Import Third-party Dependencies import { brotliCompressSync, deflateSync, gzipSync } from "node:zlib"; @@ -24,7 +26,7 @@ describe("HttpieResponseHandler.getData", () => { const handler = new HttpieResponseHandler(mockResponse as any); const data = await handler.getData(); - expect(data).toMatchObject(payload); + assert.deepEqual(data, payload); }); }); @@ -38,11 +40,11 @@ describe("HttpieResponseHandler.getData (mode: 'raw')", () => { const handler = new HttpieResponseHandler(mockResponse as any); const data = await handler.getData("raw"); - expect(data).toMatchObject(payload); + assert.deepEqual(data, payload); }); - it("should throw HttpieFetchBodyError", async() => { - expect.assertions(4); + it("should throw HttpieFetchBodyError", async(t) => { + t.plan(4); const errMsg = "unexpected error"; const mockResponse = { @@ -59,11 +61,13 @@ describe("HttpieResponseHandler.getData (mode: 'raw')", () => { await handler.getData(); } catch (error: any) { - expect(error.name).toStrictEqual("ResponseFetchError"); - expect(error.message) - .toStrictEqual(`An unexpected error occurred while trying to retrieve the response body (reason: '${errMsg}').`); - expect(error.statusCode).toStrictEqual(mockResponse.statusCode); - expect(error.headers).toStrictEqual(mockResponse.headers); + t.assert.equal(error.name, "ResponseFetchError"); + t.assert.equal( + error.message, + `An unexpected error occurred while trying to retrieve the response body (reason: '${errMsg}').` + ); + t.assert.equal(error.statusCode, mockResponse.statusCode); + t.assert.deepEqual(error.headers, mockResponse.headers); } }); }); @@ -79,11 +83,11 @@ describe("HttpieResponseHandler.getData (mode: 'decompress')", () => { const handler = new HttpieResponseHandler(mockResponse as any); const data = await handler.getData("decompress"); - expect(data).toStrictEqual(buf); + assert.deepEqual(data, buf); }); - it("must throw when the 'content-encoding' header is set with an unknown value", async() => { - expect.assertions(6); + it("must throw when the 'content-encoding' header is set with an unknown value", async(t) => { + t.plan(6); const buf = Buffer.from("hello world!"); const encoding = randomBytes(4).toString("hex"); @@ -98,17 +102,17 @@ describe("HttpieResponseHandler.getData (mode: 'decompress')", () => { await handler.getData("decompress"); } catch (error: any) { - expect(error.message).toStrictEqual(`Unsupported encoding '${encoding}'.`); - expect(error.buffer).toStrictEqual(buf); - expect(error.encodings).toStrictEqual([encoding]); - expect(error.name).toStrictEqual("DecompressionNotSupported"); - expect(error.statusCode).toStrictEqual(mockResponse.statusCode); - expect(error.headers).toStrictEqual(mockResponse.headers); + t.assert.equal(error.message, `Unsupported encoding '${encoding}'.`); + t.assert.deepEqual(error.buffer, buf); + t.assert.deepEqual(error.encodings, [encoding]); + t.assert.equal(error.name, "DecompressionNotSupported"); + t.assert.equal(error.statusCode, mockResponse.statusCode); + t.assert.deepEqual(error.headers, mockResponse.headers); } }); - it("must throw when the 'content-encoding' header is a list that includes an unknown value", async() => { - expect.assertions(6); + it("must throw when the 'content-encoding' header is a list that includes an unknown value", async(t) => { + t.plan(6); const buf = Buffer.from("hello world!"); const encoding = randomBytes(4).toString("hex"); @@ -123,12 +127,12 @@ describe("HttpieResponseHandler.getData (mode: 'decompress')", () => { await handler.getData("decompress"); } catch (error: any) { - expect(error.message).toStrictEqual(`Unsupported encoding '${encoding}'.`); - expect(error.buffer).toStrictEqual(buf); - expect(error.encodings).toStrictEqual([encoding]); - expect(error.name).toStrictEqual("DecompressionNotSupported"); - expect(error.statusCode).toStrictEqual(mockResponse.statusCode); - expect(error.headers).toStrictEqual(mockResponse.headers); + t.assert.equal(error.message, `Unsupported encoding '${encoding}'.`); + t.assert.deepEqual(error.buffer, buf); + t.assert.deepEqual(error.encodings, [encoding]); + t.assert.equal(error.name, "DecompressionNotSupported"); + t.assert.equal(error.statusCode, mockResponse.statusCode); + t.assert.deepEqual(error.headers, mockResponse.headers); } }); @@ -142,7 +146,7 @@ describe("HttpieResponseHandler.getData (mode: 'decompress')", () => { const handler = new HttpieResponseHandler(mockResponse as any); const data = await handler.getData("decompress"); - expect(data).toStrictEqual(buf); + assert.deepEqual(data, buf); }); it(`must use 'gunzip' before to returning an uncompressed buffer @@ -155,7 +159,7 @@ describe("HttpieResponseHandler.getData (mode: 'decompress')", () => { const handler = new HttpieResponseHandler(mockResponse as any); const data = await handler.getData("decompress"); - expect(data).toStrictEqual(buf); + assert.deepEqual(data, buf); }); it(`must use 'brotliDecompress' before to returning an uncompressed buffer @@ -168,7 +172,7 @@ describe("HttpieResponseHandler.getData (mode: 'decompress')", () => { const handler = new HttpieResponseHandler(mockResponse as any); const data = await handler.getData("decompress"); - expect(data).toStrictEqual(buf); + assert.deepEqual(data, buf); }); it(`must use 'inflate' before to returning an uncompressed buffer @@ -181,7 +185,7 @@ describe("HttpieResponseHandler.getData (mode: 'decompress')", () => { const handler = new HttpieResponseHandler(mockResponse as any); const data = await handler.getData("decompress"); - expect(data).toStrictEqual(buf); + assert.deepEqual(data, buf); }); it("must decompress in reverse order of the given encodings list when there are multiple compression types", async() => { @@ -194,7 +198,7 @@ describe("HttpieResponseHandler.getData (mode: 'decompress')", () => { const handler = new HttpieResponseHandler(mockResponse as any); const data = await handler.getData("decompress"); - expect(data).toStrictEqual(buf); + assert.deepEqual(data, buf); }); it("must decompress in reverse order of the given encodings string when there are multiple compression types", async() => { @@ -207,7 +211,7 @@ describe("HttpieResponseHandler.getData (mode: 'decompress')", () => { const handler = new HttpieResponseHandler(mockResponse as any); const data = await handler.getData("decompress"); - expect(data).toStrictEqual(buf); + assert.deepEqual(data, buf); }); }); @@ -221,11 +225,11 @@ describe("HttpieResponseHandler.getData (mode: 'parse')", () => { const handler = new HttpieResponseHandler(mockResponse as any); const data = await handler.getData("parse"); - expect(data).toMatchObject(payload); + assert.deepEqual(data, payload); }); - it("should parse an invalid JSON response but still keep the request data in the Error", async() => { - expect.assertions(5); + it("should parse an invalid JSON response but still keep the request data in the Error", async(t) => { + t.plan(5); const payload = "{\"foo\": bar}"; const buf = Buffer.from("{\"foo\": bar}"); @@ -240,11 +244,11 @@ describe("HttpieResponseHandler.getData (mode: 'parse')", () => { await handler.getData("parse"); } catch (error: any) { - expect(error.text).toStrictEqual(payload); - expect(error.buffer).toStrictEqual(buf); - expect(error.name).toStrictEqual("ResponseParsingError"); - expect(error.statusCode).toStrictEqual(mockResponse.statusCode); - expect(error.headers).toMatchObject(mockResponse.headers); + t.assert.equal(error.text, payload); + t.assert.deepEqual(error.buffer, buf); + t.assert.equal(error.name, "ResponseParsingError"); + t.assert.equal(error.statusCode, mockResponse.statusCode); + t.assert.deepEqual(error.headers, mockResponse.headers); } }); @@ -259,7 +263,7 @@ describe("HttpieResponseHandler.getData (mode: 'parse')", () => { const handler = new HttpieResponseHandler(mockResponse as any); const data = await handler.getData("parse"); - expect(data).toStrictEqual(payload); + assert.deepEqual(data, payload); }); it("must converting it to a string when the 'content-type' header starts with 'text/'", async() => { @@ -271,7 +275,7 @@ describe("HttpieResponseHandler.getData (mode: 'parse')", () => { const handler = new HttpieResponseHandler(mockResponse as any); const data = await handler.getData("parse"); - expect(data).toStrictEqual(payload); + assert.deepEqual(data, payload); }); it("must converting body to JSON when the 'content-type' header is set with 'application/json'", async() => { @@ -284,7 +288,7 @@ describe("HttpieResponseHandler.getData (mode: 'parse')", () => { const handler = new HttpieResponseHandler(mockResponse as any); const data = await handler.getData("parse"); - expect(data).toStrictEqual(payload); + assert.deepEqual(data, payload); }); it("must return the original buffer when 'content-type' header is set with 'application/pdf'", async() => { @@ -297,6 +301,6 @@ describe("HttpieResponseHandler.getData (mode: 'parse')", () => { const handler = new HttpieResponseHandler(mockResponse as any); const data = await handler.getData("parse"); - expect(data).toStrictEqual(buf); + assert.deepEqual(data, buf); }); }); diff --git a/test/utils.spec.ts b/test/utils.spec.ts deleted file mode 100644 index e7f26a0..0000000 --- a/test/utils.spec.ts +++ /dev/null @@ -1,212 +0,0 @@ -// Import Node.js Dependencies -import { IncomingHttpHeaders } from "node:http2"; -import stream from "node:stream"; - -// Import Internal Dependencies -import * as Utils from "../src/utils"; -import { HttpieOnHttpError } from "../src/class/HttpieOnHttpError"; -import { HttpieDecompressionError, HttpieFetchBodyError, HttpieParserError } from "../src/class/HttpieHandlerError"; - -describe("isAsyncIterable", () => { - it("should return false for synchronous iterable like an Array", () => { - expect(Utils.isAsyncIterable([])).toStrictEqual(false); - }); - - it("should return false for synchronous iterable like a primitive string", () => { - expect(Utils.isAsyncIterable("foobar")).toStrictEqual(false); - }); - - it("should return true for a Async Generator Function", () => { - async function* foo() { - yield "bar"; - } - expect(Utils.isAsyncIterable(foo())).toStrictEqual(true); - }); -}); - -describe("getEncodingCharset", () => { - it("should return 'utf-8' if no value is provided", () => { - expect(Utils.getEncodingCharset()).toStrictEqual("utf-8"); - }); - - it("should return 'utf-8' if the provided charset is not known", () => { - expect(Utils.getEncodingCharset("bolekeole")).toStrictEqual("utf-8"); - }); - - it("should return 'latin1' if the charset is equal to 'ISO-8859-1'", () => { - expect(Utils.getEncodingCharset("ISO-8859-1")).toStrictEqual("latin1"); - }); - - it("should return the charset unchanged (only if the charset is a valid BufferEncoding)", () => { - expect(Utils.getEncodingCharset("ascii")).toStrictEqual("ascii"); - }); -}); - -describe("createHeaders", () => { - it("should return a plain object with 'user-agent' equal to 'httpie'", () => { - const result = Utils.createHeaders({}); - - expect(result).toEqual({ "user-agent": "httpie" }); - }); - - it("should re-use provided headers plain object", () => { - const result = Utils.createHeaders({ - headers: { foo: "bar" } - }); - - expect(result).toEqual({ foo: "bar", "user-agent": "httpie" }); - }); - - it("should overwrite the 'user-agent' header", () => { - const result = Utils.createHeaders({ - headers: { "user-agent": "myUserAgent" } - }); - - expect(result).toEqual({ "user-agent": "myUserAgent" }); - }); - - it("should add authorization header (and override original property)", () => { - const result = Utils.createHeaders({ - headers: { - Authorization: "bar" - }, - authorization: "foo" - }); - - expect(result).toEqual({ Authorization: "Bearer foo", "user-agent": "httpie" }); - }); -}); - -describe("createBody", () => { - it("should return 'undefined' when undefined is provided as body argument", () => { - expect(Utils.createBody(undefined)).toStrictEqual(undefined); - }); - - it("should be able to prepare and stringify a JSON body", () => { - const body = { - foo: "bar" - }; - const bodyStr = JSON.stringify(body); - const headerRef: IncomingHttpHeaders = {}; - - const result = Utils.createBody(body, headerRef); - - expect(result).toStrictEqual(bodyStr); - expect(Object.keys(headerRef).length).toStrictEqual(2); - expect(headerRef["content-type"]).toStrictEqual("application/json"); - expect(headerRef["content-length"]).toStrictEqual(String(Buffer.byteLength(bodyStr))); - }); - - it("should be able to prepare a FORM (URLEncoded) body", () => { - const body = new URLSearchParams({ - foo: "bar" - }); - const bodyStr = body.toString(); - const headerRef: IncomingHttpHeaders = {}; - - const result = Utils.createBody(body, headerRef); - - expect(result).toStrictEqual(bodyStr); - expect(Object.keys(headerRef).length).toStrictEqual(2); - expect(headerRef["content-type"]).toStrictEqual("application/x-www-form-urlencoded"); - expect(headerRef["content-length"]).toStrictEqual(String(Buffer.byteLength(bodyStr))); - }); - - it("should be able to prepare a Buffer body", () => { - const body = Buffer.from("hello world!"); - const headerRef: IncomingHttpHeaders = {}; - - const result = Utils.createBody(body, headerRef); - - expect(result).toStrictEqual(body); - expect(Object.keys(headerRef).length).toStrictEqual(1); - expect(headerRef["content-length"]).toStrictEqual(String(Buffer.byteLength(body))); - }); - - it("should return the ReadableStream without any transformation", () => { - const headerRef: IncomingHttpHeaders = {}; - const readStream = new stream.Readable(); - - const result = Utils.createBody(readStream, headerRef); - - expect(result).toStrictEqual(readStream); - expect(Object.keys(headerRef)).toHaveLength(0); - }); -}); - -describe("createAuthorizationHeader", () => { - it("it should start with 'Bearer ' if the token is Bearer or empty string", () => { - expect(Utils.createAuthorizationHeader("")).toStrictEqual("Bearer "); - expect(Utils.createAuthorizationHeader("lol")).toStrictEqual("Bearer lol"); - }); - - it("it should start with 'Basic ' for a Basic Authentication", () => { - const result = Utils.createAuthorizationHeader("toto:lolo"); - const base64 = result.split(" ")[1]; - - expect(result.startsWith("Basic ")).toBe(true); - expect(Buffer.from(base64, "base64").toString("ascii")).toStrictEqual("toto:lolo"); - }); -}); - -describe("isHttpieError", () => { - it("it should be true", () => { - expect(Utils.isHttpieError(new HttpieOnHttpError({} as any))).toBeTruthy(); - expect(Utils.isHttpieError(new HttpieFetchBodyError({ message: "ResponseFetchError", response: {} } as any))).toBeTruthy(); - expect( - Utils.isHttpieError(new HttpieDecompressionError({ message: "UnexpectedDecompressionError", response: {} } as any)) - ).toBeTruthy(); - expect(Utils.isHttpieError(new HttpieParserError({ message: "ResponseParsingError", response: {} } as any))).toBeTruthy(); - }); - - it("it should be false", () => { - expect(Utils.isHttpieError(new Error())).toBeFalsy(); - }); -}); - -describe("isHTTPError", () => { - it("it should be true", () => { - expect(Utils.isHTTPError(new HttpieOnHttpError({} as any))).toBeTruthy(); - }); - - it("it should be false", () => { - expect(Utils.isHTTPError(new Error())).toBeFalsy(); - expect(Utils.isHTTPError(new HttpieFetchBodyError({ message: "ResponseFetchError", response: {} } as any))).toBeFalsy(); - expect( - Utils.isHTTPError(new HttpieDecompressionError({ message: "UnexpectedDecompressionError", response: {} } as any)) - ).toBeFalsy(); - expect(Utils.isHTTPError(new HttpieParserError({ message: "ResponseParsingError", response: {} } as any))).toBeFalsy(); - }); -}); - -describe("getCurrentEnv", () => { - afterAll(() => { - Utils.env.NODE_ENV = "dev"; - }); - - it("should return 'prod'", () => { - Utils.env.NODE_ENV = "prod"; - expect(Utils.getCurrentEnv()).toStrictEqual("prod"); - }); - - it("should return 'preprod'", () => { - Utils.env.NODE_ENV = "staging"; - expect(Utils.getCurrentEnv()).toStrictEqual("preprod"); - - Utils.env.NODE_ENV = "preprod"; - expect(Utils.getCurrentEnv()).toStrictEqual("preprod"); - }); - - it("should return 'dev'", () => { - Utils.env.NODE_ENV = "dev"; - expect(Utils.getCurrentEnv()).toStrictEqual("dev"); - - Utils.env.NODE_ENV = "test"; - expect(Utils.getCurrentEnv()).toStrictEqual("dev"); - }); - - it("should return 'dev' as default value", () => { - delete Utils.env.NODE_ENV; - expect(Utils.getCurrentEnv()).toStrictEqual("dev"); - }); -}); diff --git a/test/utils.test.ts b/test/utils.test.ts new file mode 100644 index 0000000..56c0c99 --- /dev/null +++ b/test/utils.test.ts @@ -0,0 +1,199 @@ +// Import Node.js Dependencies +import { describe, it } from "node:test"; +import { IncomingHttpHeaders } from "node:http2"; +import assert from "node:assert"; +import stream from "node:stream"; + +// Import Internal Dependencies +import * as Utils from "../src/utils"; +import { HttpieOnHttpError } from "../src/class/HttpieOnHttpError"; +import { HttpieDecompressionError, HttpieFetchBodyError, HttpieParserError } from "../src/class/HttpieHandlerError"; + +describe("isAsyncIterable", () => { + it("should return false for synchronous iterable like an Array", () => { + assert.strictEqual(Utils.isAsyncIterable([]), false); + }); + + it("should return false for synchronous iterable like a primitive string", () => { + assert.strictEqual(Utils.isAsyncIterable("foobar"), false); + }); + + it("should return true for a Async Generator Function", () => { + async function* foo() { + yield "bar"; + } + assert.strictEqual(Utils.isAsyncIterable(foo()), true); + }); +}); + +describe("getEncodingCharset", () => { + it("should return 'utf-8' if no value is provided", () => { + assert.strictEqual(Utils.getEncodingCharset(), "utf-8"); + }); + + it("should return 'utf-8' if the provided charset is not known", () => { + assert.strictEqual(Utils.getEncodingCharset("bolekeole"), "utf-8"); + }); + + it("should return 'latin1' if the charset is equal to 'ISO-8859-1'", () => { + assert.strictEqual(Utils.getEncodingCharset("ISO-8859-1"), "latin1"); + }); + + it("should return the charset unchanged (only if the charset is a valid BufferEncoding)", () => { + assert.strictEqual(Utils.getEncodingCharset("ascii"), "ascii"); + }); +}); + +describe("createHeaders", () => { + it("should return a plain object with 'user-agent' equal to 'httpie'", () => { + const result = Utils.createHeaders({}); + + assert.deepStrictEqual(result, { "user-agent": "httpie" }); + }); + + it("should re-use provided headers plain object", () => { + const result = Utils.createHeaders({ + headers: { foo: "bar" } + }); + + assert.deepStrictEqual(result, { foo: "bar", "user-agent": "httpie" }); + }); + + it("should overwrite the 'user-agent' header", () => { + const result = Utils.createHeaders({ + headers: { "user-agent": "myUserAgent" } + }); + + assert.deepStrictEqual(result, { "user-agent": "myUserAgent" }); + }); + + it("should add authorization header (and override original property)", () => { + const result = Utils.createHeaders({ + headers: { + Authorization: "bar" + }, + authorization: "foo" + }); + + assert.deepStrictEqual(result, { Authorization: "Bearer foo", "user-agent": "httpie" }); + }); +}); + +describe("createBody", () => { + it("should return 'undefined' when undefined is provided as body argument", () => { + assert.strictEqual(Utils.createBody(undefined), undefined); + }); + + it("should be able to prepare and stringify a JSON body", () => { + const body = { + foo: "bar" + }; + const bodyStr = JSON.stringify(body); + const headerRef: IncomingHttpHeaders = {}; + + const result = Utils.createBody(body, headerRef); + + assert.strictEqual(result, bodyStr); + assert.strictEqual(Object.keys(headerRef).length, 2); + assert.strictEqual(headerRef["content-type"], "application/json"); + assert.strictEqual(headerRef["content-length"], String(Buffer.byteLength(bodyStr))); + }); + + it("should be able to prepare a FORM (URLEncoded) body", () => { + const body = new URLSearchParams({ + foo: "bar" + }); + const bodyStr = body.toString(); + const headerRef: IncomingHttpHeaders = {}; + + const result = Utils.createBody(body, headerRef); + + assert.strictEqual(result, bodyStr); + assert.strictEqual(Object.keys(headerRef).length, 2); + assert.strictEqual(headerRef["content-type"], "application/x-www-form-urlencoded"); + assert.strictEqual(headerRef["content-length"], String(Buffer.byteLength(bodyStr))); + }); + + it("should be able to prepare a Buffer body", () => { + const body = Buffer.from("hello world!"); + const headerRef: IncomingHttpHeaders = {}; + + const result = Utils.createBody(body, headerRef); + + assert.strictEqual(result, body); + assert.strictEqual(Object.keys(headerRef).length, 1); + assert.strictEqual(headerRef["content-length"], String(Buffer.byteLength(body))); + }); + + it("should return the ReadableStream without any transformation", () => { + const headerRef: IncomingHttpHeaders = {}; + const readStream = new stream.Readable(); + + const result = Utils.createBody(readStream, headerRef); + + assert.strictEqual(result, readStream); + assert.strictEqual(Object.keys(headerRef).length, 0); + }); +}); + +describe("createAuthorizationHeader", () => { + it("it should start with 'Bearer ' if the token is Bearer or empty string", () => { + assert.strictEqual(Utils.createAuthorizationHeader(""), "Bearer "); + assert.strictEqual(Utils.createAuthorizationHeader("lol"), "Bearer lol"); + }); + + it("it should start with 'Basic ' for a Basic Authentication", () => { + const result = Utils.createAuthorizationHeader("toto:lolo"); + const base64 = result.split(" ")[1]; + + assert.strictEqual(result.startsWith("Basic "), true); + assert.strictEqual(Buffer.from(base64, "base64").toString("ascii"), "toto:lolo"); + }); +}); + +describe("isHttpieError", () => { + it("it should be true", () => { + assert.strictEqual( + Utils.isHttpieError(new HttpieOnHttpError({} as any)), + true + ); + assert.strictEqual( + Utils.isHttpieError(new HttpieFetchBodyError({ message: "ResponseFetchError", response: {} } as any)), + true + ); + assert.strictEqual( + Utils.isHttpieError(new HttpieDecompressionError({ message: "UnexpectedDecompressionError", response: {} } as any)), + true + ); + assert.strictEqual( + Utils.isHttpieError(new HttpieParserError({ message: "ResponseParsingError", response: {} } as any)), + true + ); + }); + + it("it should be false", () => { + assert.strictEqual(Utils.isHttpieError(new Error()), false); + }); +}); + +describe("isHTTPError", () => { + it("it should be true", () => { + assert.strictEqual(Utils.isHTTPError(new HttpieOnHttpError({} as any)), true); + }); + + it("it should be false", () => { + assert.strictEqual(Utils.isHTTPError(new Error()), false); + assert.strictEqual( + Utils.isHTTPError(new HttpieFetchBodyError({ message: "ResponseFetchError", response: {} } as any)), + false + ); + assert.strictEqual( + Utils.isHTTPError(new HttpieDecompressionError({ message: "UnexpectedDecompressionError", response: {} } as any)), + false + ); + assert.strictEqual( + Utils.isHTTPError(new HttpieParserError({ message: "ResponseParsingError", response: {} } as any)), + false + ); + }); +}); diff --git a/tsconfig.json b/tsconfig.json index d275979..74a87f6 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "@openally/config.typescript/esm", + "extends": "@openally/config.typescript", "compilerOptions": { "outDir": "dist", "rootDir": "./src", From 1c2d0023dc2b1b92ba457bc21db0459c46940b90 Mon Sep 17 00:00:00 2001 From: fraxken Date: Sun, 29 Jun 2025 17:58:54 +0200 Subject: [PATCH 03/12] fix: use new undici.interceptors.redirect --- src/index.ts | 6 ++++++ test/request.test.ts | 3 +-- test/server/index.ts | 2 +- test/stream.test.ts | 3 +-- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/index.ts b/src/index.ts index 4bafbab..d5256ab 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,6 +2,7 @@ import { Agent, ProxyAgent, + interceptors, fetch, setGlobalDispatcher, getGlobalDispatcher, @@ -16,6 +17,10 @@ import { Client } from "undici"; +setGlobalDispatcher( + new Agent().compose(interceptors.redirect()) +); + export * from "./request.js"; export * from "./stream.js"; export * from "./retry.js"; @@ -28,6 +33,7 @@ export * from "./class/undiciResponseHandler.js"; export { Agent, ProxyAgent, + interceptors, fetch, setGlobalDispatcher, getGlobalDispatcher, diff --git a/test/request.test.ts b/test/request.test.ts index 6f07a54..7343854 100644 --- a/test/request.test.ts +++ b/test/request.test.ts @@ -40,8 +40,7 @@ describe("http.get", () => { assert.equal(data.name, "foobar"); }); - // FIX: maxRedirections do not work - it.skip("should GET uptime by following an HTTP redirection from local fastify server", async() => { + it("should GET uptime by following an HTTP redirection from local fastify server", async() => { const { data } = await get<{ uptime: number; }>("/local/redirect", { maxRedirections: 1 }); assert.ok("uptime" in data); diff --git a/test/server/index.ts b/test/server/index.ts index ab1279d..832b29b 100644 --- a/test/server/index.ts +++ b/test/server/index.ts @@ -32,7 +32,7 @@ export async function createServer(customPath = "local", port = 3000) { customPath, agent: new undici.Agent({ connections: 10 - }), + }).compose(undici.interceptors.redirect()), origin: `http://localhost:${port}/` }; agents.add(serverAgent); diff --git a/test/stream.test.ts b/test/stream.test.ts index 2444067..3f81d55 100644 --- a/test/stream.test.ts +++ b/test/stream.test.ts @@ -31,8 +31,7 @@ after(async() => { }); describe("stream", () => { - // FIX: maxRedirections doesn't work ? - it.skip("should use callback dispatcher to init headers/statusCode etc.", async() => { + it("should use callback dispatcher to init headers/statusCode etc.", async() => { const fileDestination = path.join(kDownloadPath, "fs-walk-main.tar.gz"); const repositoryURL = new URL("NodeSecure/fs-walk/archive/main.tar.gz", kGithubURL); From d2f5e60679fd844430d2701d78a444e99d4ff90a Mon Sep 17 00:00:00 2001 From: fraxken Date: Mon, 25 Aug 2025 13:09:08 +0200 Subject: [PATCH 04/12] chore: update dependencies --- package.json | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 2373401..4b7da1a 100644 --- a/package.json +++ b/package.json @@ -29,23 +29,23 @@ }, "homepage": "https://github.com/OpenAlly/httpie#readme", "devDependencies": { - "@openally/config.eslint": "^2.1.0", - "@openally/config.typescript": "^1.0.3", - "@types/content-type": "^1.1.8", + "@openally/config.eslint": "^2.2.0", + "@openally/config.typescript": "^1.1.0", + "@types/content-type": "^1.1.9", "@types/lru-cache": "^7.10.9", - "@types/node": "^22.0.0", - "@types/statuses": "^2.0.4", - "fastify": "^5.2.0", + "@types/node": "^24.3.0", + "@types/statuses": "^2.0.6", + "fastify": "^5.5.0", "is-html": "^3.1.0", "p-ratelimit": "^1.0.1", - "tsx": "^4.19.4", - "typescript": "^5.3.3" + "tsx": "^4.20.5", + "typescript": "^5.9.2" }, "dependencies": { "@openally/result": "^1.2.1", "content-type": "^1.0.5", "lru-cache": "^11.0.0", - "statuses": "^2.0.1", - "undici": "^7.10.0" + "statuses": "^2.0.2", + "undici": "^7.15.0" } } From 63f554842114302f9d9e66df4d4b5ad67aac9675 Mon Sep 17 00:00:00 2001 From: fraxken Date: Mon, 25 Aug 2025 13:26:46 +0200 Subject: [PATCH 05/12] refactor!: remove Retry & policies in favor of undici interceptors --- README.md | 10 ++-- docs/request.md | 2 - docs/retry.md | 53 ----------------- src/agents.ts | 10 ++-- src/class/Operation.class.ts | 108 ---------------------------------- src/common/errors.ts | 1 - src/index.ts | 2 - src/policies/httpcode.ts | 12 ---- src/policies/index.ts | 4 -- src/policies/none.ts | 3 - src/request.ts | 6 +- src/retry.ts | 41 ------------- src/stream.ts | 7 +-- test/request.test.ts | 11 +++- test/retry.test.ts | 111 ----------------------------------- test/stream.test.ts | 14 +++-- 16 files changed, 30 insertions(+), 365 deletions(-) delete mode 100644 docs/retry.md delete mode 100644 src/class/Operation.class.ts delete mode 100644 src/policies/httpcode.ts delete mode 100644 src/policies/index.ts delete mode 100644 src/policies/none.ts delete mode 100644 src/retry.ts delete mode 100644 test/retry.test.ts diff --git a/README.md b/README.md index be8b244..af3f664 100644 --- a/README.md +++ b/README.md @@ -36,12 +36,11 @@ The package is inspired by lukeed [httpie](https://github.com/lukeed/httpie) (Th - Includes aliases for common HTTP verbs: `get`, `post`, `put`, `patch`, and `del`. - Able to automatically detect domains and paths to assign the right Agent (use a LRU cache to avoid repetitive computation). - Allows to use an accurate rate-limiter like `p-ratelimit` with the `limit` option. -- Built-in retry mechanism with **custom policies**. - Safe error handling with Rust-like [Result](https://github.com/OpenAlly/npm-packages/tree/main/src/result). Thanks to undici: -- Support [HTTP redirections](https://developer.mozilla.org/en-US/docs/Web/HTTP/Redirections) with the `maxRedirections` argument. +- Support [redirections](https://developer.mozilla.org/en-US/docs/Web/HTTP/Redirections) or retry using interceptors. - Implement high-level API for undici **stream** and **pipeline** method. - High performance (see [benchmarks](https://undici.nodejs.org/#/?id=benchmarks)). - Work well with **newest** Node.js API [AbortController](https://nodejs.org/dist/latest-v16.x/docs/api/globals.html#globals_class_abortcontroller) to cancel http request. @@ -94,7 +93,7 @@ catch (error) { } ``` -Since v2.0.0 you can also use the `safe` prefix API to get a `Promise>` +You can also use the `safe` prefix API to get a `Promise>` ```ts import * as httpie from "@openally/httpie"; @@ -111,19 +110,18 @@ const response = (await httpie.safePost("https://jsonplaceholder.typicode.com/po .unwrap(); ``` -> 👀 For more examples of use please look at the root folder **examples**. +> [!TIP] +> More examples available in the root folder **examples**. ## 📜 API - [Request API](./docs/request.md) -- [Retry API](./docs/retry.md) - [Work and manage Agents](./docs/agents.md) ## Error handling Read the [error documentation](./docs/errors.md). - ## Contributors ✨ diff --git a/docs/request.md b/docs/request.md index ccf3428..31af0be 100644 --- a/docs/request.md +++ b/docs/request.md @@ -7,8 +7,6 @@ The method **options** and **response** are described by the following TypeScrip type ModeOfHttpieResponseHandler = "decompress" | "parse" | "raw"; export interface RequestOptions { - /** @default 0 */ - maxRedirections?: number; /** @default{ "user-agent": "httpie" } */ headers?: IncomingHttpHeaders; querystring?: string | URLSearchParams; diff --git a/docs/retry.md b/docs/retry.md deleted file mode 100644 index 6d2c2f2..0000000 --- a/docs/retry.md +++ /dev/null @@ -1,53 +0,0 @@ -# Retry API - -Allows to restart http calls according to various criteria that we will call policies. By default there is two built-in policies: -- none (do not stop retry to append). **This is the default policy**. -- httpcode (allow to retry or fail on some http codes). - -The httpcode by default retry on codes: `307`, `408`, `429`, `444`, `500`, `503`, `504`, `520`, `521`, `522`, `523`, `524`. However you can also choose to extend the list yourself: - -```js -import * as httpie from "@openally/httpie"; - -const policy = httpie.policies.httpcode(new Set([501]), true); -``` - -## Usage example - -```js -import * as httpie from "@openally/httpie"; - -const { data } = httpie.retry(async() => { - return await httpie.get("https://jsonplaceholder.typicode.com/posts"); -}, { factor: 1, forever: true }, httpie.policies.httpcode()); -``` - -Retry options are described by the following interface: -```ts -export interface RetryOptions { - retries?: number; - minTimeout?: number; - maxTimeout?: number; - unref?: boolean; - factor?: number; - forever?: boolean; - signal?: AbortSignal | null; -} -``` - -By default it will retry three times, with a minTimeout of `1_000` and a factor of `2`. - -## Creating your own policy - -A policy "callback" is described by the following interface -```ts -export type PolicyCallback = (error?: any) => boolean; -``` - -So it's pretty straightforward to create a new one. For example here this policy will throw if the error is an `AbortError`. - -```js -export function abort(error) { - return error.name === "AbortError"; -} -``` diff --git a/src/agents.ts b/src/agents.ts index f8c985d..1949215 100644 --- a/src/agents.ts +++ b/src/agents.ts @@ -12,12 +12,12 @@ import { /** * @see https://en.wikipedia.org/wiki/Page_replacement_algorithm */ -export const URI_CACHE = new LRUCache({ +export const URI_CACHE = new LRUCache({ max: 100, ttl: 1_000 * 60 * 120 }); -export interface computedUrlAndAgent { +export interface ComputedUrlAndAgent { url: URL; agent: Agent | ProxyAgent | MockAgent | null; limit?: InlineCallbackAction; @@ -57,7 +57,7 @@ export function isAgentPathMatchingURI( /** * @description Compute a given string URI to the local list of agents. */ -export function computeURIOnAllAgents(uri: string): computedUrlAndAgent { +export function computeURIOnAllAgents(uri: string): ComputedUrlAndAgent { for (const agent of agents) { const url = isAgentPathMatchingURI(uri, agent); @@ -100,13 +100,13 @@ export function detectAgentFromURI(uri: URL): CustomHttpAgent | null { export function computeURI( method: HttpMethod | WebDavMethod, uri: string | URL -): computedUrlAndAgent { +): ComputedUrlAndAgent { const uriStr = method.toUpperCase() + uri.toString(); if (URI_CACHE.has(uriStr)) { return URI_CACHE.get(uriStr)!; } - let response: computedUrlAndAgent; + let response: ComputedUrlAndAgent; if (typeof uri === "string") { response = computeURIOnAllAgents(uri); } diff --git a/src/class/Operation.class.ts b/src/class/Operation.class.ts deleted file mode 100644 index 087fd0d..0000000 --- a/src/class/Operation.class.ts +++ /dev/null @@ -1,108 +0,0 @@ -// Import Node.js Dependencies -import timers from "node:timers/promises"; - -// Import Internal Dependencies -import { type RetryOptions } from "../retry.js"; - -// CONSTANTS -const kDefaultOperationOptions: Partial = { - retries: 3, - minTimeout: 1_000, - maxTimeout: Infinity, - forever: false, - unref: false, - factor: 2, - signal: null -}; - -export interface OperationResult { - data: T; - metrics: { - attempt: number; - executionTimestamp: number; - elapsedTimeoutTime: number; - }; -} - -export default class Operation { - private retries: number; - private minTimeout: number; - private maxTimeout: number; - private forever: boolean; - private unref: boolean; - private factor: number; - private signal: AbortSignal; - private data: T; - private continueExecution = true; - - private attempt = 0; - private startAt = Date.now(); - private executionTimestamp: number; - private elapsedTimeoutTime = 0; - - constructor(options: RetryOptions) { - Object.assign(this, {}, kDefaultOperationOptions, options); - if (this.forever) { - this.retries = Infinity; - } - } - - /** - * @see http://dthain.blogspot.com/2009/02/exponential-backoff-in-distributed.html - */ - private get backoff() { - return Math.min(this.minTimeout * Math.pow(this.factor, this.attempt - 1), this.maxTimeout); - } - - get continue() { - return this.continueExecution; - } - - ensureAbort() { - if (this.signal === null) { - return; - } - - if (this.signal.aborted) { - throw new Error("Aborted"); - } - } - - async retry() { - this.ensureAbort(); - this.attempt++; - - if (this.attempt > this.retries) { - // TODO: add error causes ? - - throw new Error("Exceeded the maximum number of allowed retries!"); - } - - const timeout = this.backoff; - const signal = this.signal ?? void 0; - this.continueExecution = true; - - await timers.setTimeout(timeout, void 0, { ref: this.unref, signal }); - this.ensureAbort(); - - this.elapsedTimeoutTime += timeout; - } - - success(data: T) { - this.data = data; - - this.continueExecution = false; - this.executionTimestamp = Date.now() - this.startAt; - } - - toJSON(): OperationResult { - return { - data: this.data, - metrics: { - attempt: this.attempt, - executionTimestamp: this.executionTimestamp, - elapsedTimeoutTime: this.elapsedTimeoutTime - } - }; - } -} diff --git a/src/common/errors.ts b/src/common/errors.ts index 5ff133b..d53db83 100644 --- a/src/common/errors.ts +++ b/src/common/errors.ts @@ -3,7 +3,6 @@ interface GetErrorOptions { message: keyof T; } -// from myu-utils function taggedString(chains: TemplateStringsArray, ...expectedValues: string[] | number[]) { return function cur(...args: any[]): string { const directory = args.at(-1) || {}; diff --git a/src/index.ts b/src/index.ts index d5256ab..3a0013e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -23,8 +23,6 @@ setGlobalDispatcher( export * from "./request.js"; export * from "./stream.js"; -export * from "./retry.js"; -export * as policies from "./policies/index.js"; export { agents, computeURI, type CustomHttpAgent } from "./agents.js"; export { DEFAULT_HEADER, isHTTPError, isHttpieError } from "./utils.js"; export { HttpieOnHttpError } from "./class/HttpieOnHttpError.js"; diff --git a/src/policies/httpcode.ts b/src/policies/httpcode.ts deleted file mode 100644 index 8853b1e..0000000 --- a/src/policies/httpcode.ts +++ /dev/null @@ -1,12 +0,0 @@ -/** - * @see https://fr.wikipedia.org/wiki/Liste_des_codes_HTTP - */ -const kDefaultCodes = new Set([307, 408, 429, 444, 500, 503, 504, 520, 521, 522, 523, 524]); - -export function httpcode(codes: Set = kDefaultCodes, useDefault = false) { - if (useDefault) { - [...kDefaultCodes].forEach((code) => codes.add(code)); - } - - return ({ statusCode }) => !codes.has(statusCode); -} diff --git a/src/policies/index.ts b/src/policies/index.ts deleted file mode 100644 index 9f4968c..0000000 --- a/src/policies/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from "./none.js"; -export * from "./httpcode.js"; - -export type PolicyCallback = (error?: any) => boolean; diff --git a/src/policies/none.ts b/src/policies/none.ts deleted file mode 100644 index 7149d70..0000000 --- a/src/policies/none.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function none() { - return false; -} diff --git a/src/request.ts b/src/request.ts index d55e2ac..4ef9d8a 100644 --- a/src/request.ts +++ b/src/request.ts @@ -34,8 +34,6 @@ export type RequestError = HttpieParserError; export interface RequestOptions { - /** @default 0 */ - maxRedirections?: number; /** @default{ "user-agent": "httpie" } */ headers?: IncomingHttpHeaders; querystring?: string | URLSearchParams; @@ -71,8 +69,6 @@ export async function request( uri: string | URL, options: RequestOptions = {} ): Promise> { - const { maxRedirections = 0 } = options; - const computedURI = computeURI(method, uri); if (typeof options.querystring !== "undefined") { const qs = typeof options.querystring === "string" ? new URLSearchParams(options.querystring) : options.querystring; @@ -87,7 +83,7 @@ export async function request( const headers = Utils.createHeaders({ headers: options.headers, authorization: options.authorization }); const body = Utils.createBody(options.body, headers); - const requestOptions = { method: method as HttpMethod, headers, body, dispatcher, maxRedirections }; + const requestOptions = { method: method as HttpMethod, headers, body, dispatcher }; const requestResponse = limit === null ? await undici.request(computedURI.url, requestOptions) : await limit(() => undici.request(computedURI.url, requestOptions)); diff --git a/src/retry.ts b/src/retry.ts deleted file mode 100644 index e2658a1..0000000 --- a/src/retry.ts +++ /dev/null @@ -1,41 +0,0 @@ -// Import Internal Dependencies -import Operation, { type OperationResult } from "./class/Operation.class.js"; -import { type PolicyCallback, none } from "./policies/index.js"; - -/** - * Those options are inspired by the retry package - * @see https://www.npmjs.com/package/retry - */ -export interface RetryOptions { - retries?: number; - minTimeout?: number; - maxTimeout?: number; - unref?: boolean; - factor?: number; - forever?: boolean; - signal?: AbortSignal | null; -} - -export type RetryCallback = (() => Promise) | (() => T); - -export async function retry( - callback: RetryCallback, options: RetryOptions = {}, policy: PolicyCallback = none -): Promise> { - const op = new Operation(options); - - do { - try { - const data = await callback(); - op.success(data); - } - catch (error: any) { - if (policy(error)) { - throw error; - } - - await op.retry(); - } - } while (op.continue); - - return op.toJSON(); -} diff --git a/src/stream.ts b/src/stream.ts index fb493b8..030b39e 100644 --- a/src/stream.ts +++ b/src/stream.ts @@ -20,8 +20,6 @@ export function pipeline( uri: string | URL, options: StreamOptions = {} ): Duplex { - const { maxRedirections = 0 } = options; - const computedURI = computeURI(method, uri); if (typeof options.querystring !== "undefined") { const qs = typeof options.querystring === "string" ? new URLSearchParams(options.querystring) : options.querystring; @@ -35,7 +33,7 @@ export function pipeline( const body = Utils.createBody(options.body, headers); return undici.pipeline(computedURI.url, { - method: method as HttpMethod, headers, body, dispatcher, maxRedirections + method: method as HttpMethod, headers, body, dispatcher }, ({ body }) => body); } @@ -48,7 +46,6 @@ export function stream( uri: string | URL, options: StreamOptions = {} ): WritableStreamCallback { - const { maxRedirections = 0 } = options; const computedURI = computeURI(method, uri); const dispatcher = options.agent ?? computedURI.agent ?? void 0; @@ -58,7 +55,7 @@ export function stream( return (factory) => undici .stream( computedURI.url, - { method: method as HttpMethod, headers, body, dispatcher, maxRedirections }, + { method: method as HttpMethod, headers, body, dispatcher }, factory ); } diff --git a/test/request.test.ts b/test/request.test.ts index 7343854..bff4592 100644 --- a/test/request.test.ts +++ b/test/request.test.ts @@ -7,7 +7,10 @@ import { FastifyInstance } from "fastify"; import isHtml from "is-html"; // Import Internal Dependencies -import { get, post, put, patch, del, safeGet } from "../src/index.js"; +import { + get, post, put, patch, del, safeGet, + Agent, interceptors +} from "../src/index.js"; import { isHTTPError } from "../src/utils.js"; import { createServer } from "./server/index.js"; import { windev } from "./helpers.js"; @@ -41,7 +44,11 @@ describe("http.get", () => { }); it("should GET uptime by following an HTTP redirection from local fastify server", async() => { - const { data } = await get<{ uptime: number; }>("/local/redirect", { maxRedirections: 1 }); + const agent = new Agent() + .compose(interceptors.redirect({ maxRedirections: 2 })); + const { data } = await get<{ uptime: number; }>("/local/redirect", { + agent + }); assert.ok("uptime" in data); assert.equal(typeof data.uptime, "number"); diff --git a/test/retry.test.ts b/test/retry.test.ts deleted file mode 100644 index 3383aa6..0000000 --- a/test/retry.test.ts +++ /dev/null @@ -1,111 +0,0 @@ -// Import Node.js Dependencies -import { describe, it, before, after } from "node:test"; -import assert from "node:assert"; - -// Import Third-party Dependencies -import { FastifyInstance } from "fastify"; - -// Import Internal Dependencies -import { retry, get, policies } from "../src/index.js"; -import { createServer } from "./server/index.js"; - -let httpServer: FastifyInstance; -before(async() => { - httpServer = await createServer("retry", 1337); -}); - -after(async() => { - await httpServer.close(); -}); - -describe("retry (with default policy)", () => { - it("should throw an Error because the number of retries has been exceeded", async(t) => { - t.plan(1); - - try { - await retry(() => { - throw new Error("exceed"); - }, { factor: 1 }); - } - catch (error: any) { - t.assert.equal(error.message, "Exceeded the maximum number of allowed retries!"); - } - }); - - it("should succeed after one try", async() => { - let count = 0; - - const { data, metrics } = await retry(() => { - count++; - if (count === 1) { - throw new Error("oops"); - } - - return "hello world!"; - }); - - assert.equal(data, "hello world!"); - assert.equal(metrics.attempt, 1); - assert.equal(typeof metrics.elapsedTimeoutTime, "number"); - assert.equal(typeof metrics.executionTimestamp, "number"); - }); - - it("should be stopped with Node.js AbortController", async(t) => { - t.plan(1); - - let count = 0; - const controller = new AbortController(); - - try { - await retry(() => { - count++; - if (count <= 2) { - throw new Error("oops"); - } - controller.abort(); - - throw new Error("oops"); - }, { forever: true, signal: controller.signal }); - } - catch (error: any) { - t.assert.equal(error.message, "Aborted"); - } - }); -}); - -describe("retry (with http policy)", () => { - it("should throw an Error because the number of retries has been exceeded", async(t) => { - t.plan(1); - - try { - await retry(async() => get("/retry/internalerror"), { factor: 1, retries: 2 }, policies.httpcode()); - } - catch (error: any) { - t.assert.equal(error.message, "Exceeded the maximum number of allowed retries!"); - } - }); - - it("should return the http error because the code (501) is not supported by the policy", async(t) => { - t.plan(1); - - try { - await retry(async() => get("/retry/notimplemented"), { factor: 1, retries: 2 }, policies.httpcode()); - } - catch (error: any) { - t.assert.equal(error.message, "Not Implemented"); - } - }); - - it("should include code 501 and all other default port", async(t) => { - t.plan(1); - - try { - const policy = policies.httpcode(new Set([501]), true); - - await retry(async() => get("/retry/notimplemented"), { factor: 1, retries: 2 }, policy); - } - catch (error: any) { - t.assert.equal(error.message, "Exceeded the maximum number of allowed retries!"); - } - }); -}); diff --git a/test/stream.test.ts b/test/stream.test.ts index 3f81d55..617705e 100644 --- a/test/stream.test.ts +++ b/test/stream.test.ts @@ -33,14 +33,16 @@ after(async() => { describe("stream", () => { it("should use callback dispatcher to init headers/statusCode etc.", async() => { const fileDestination = path.join(kDownloadPath, "fs-walk-main.tar.gz"); - const repositoryURL = new URL("NodeSecure/fs-walk/archive/main.tar.gz", kGithubURL); + const repositoryURL = new URL("NodeSecure/vulnera/archive/main.tar.gz", kGithubURL); + const agent = new httpie.Agent() + .compose(httpie.interceptors.redirect({ maxRedirections: 1 })); const cursor = httpie.stream("GET", repositoryURL, { headers: { "User-Agent": "httpie", "Accept-Encoding": "gzip, deflate" }, - maxRedirections: 1 + agent }); let contentType = ""; @@ -58,15 +60,17 @@ describe("stream", () => { }); it("should fetch a .tar.gz of a given github repository", async() => { - const fileDestination = path.join(kDownloadPath, "i18n-main.tar.gz"); - const repositoryURL = new URL("NodeSecure/i18n/archive/main.tar.gz", kGithubURL); + const fileDestination = path.join(kDownloadPath, "vulnera-main.tar.gz"); + const repositoryURL = new URL("NodeSecure/vulnera/archive/main.tar.gz", kGithubURL); + const agent = new httpie.Agent() + .compose(httpie.interceptors.redirect()); await httpie.stream("GET", repositoryURL, { headers: { "User-Agent": "httpie", "Accept-Encoding": "gzip, deflate" }, - maxRedirections: 1 + agent })(() => createWriteStream(fileDestination)); assert.ok(existsSync(fileDestination)); From f6df4d978f5c1f58b26c888e768a5eff5588f03b Mon Sep 17 00:00:00 2001 From: fraxken Date: Mon, 25 Aug 2025 13:29:00 +0200 Subject: [PATCH 06/12] ci: update GA & use Node.js v22 --- .github/workflows/codeql.yml | 10 +++++----- .github/workflows/node.js.yml | 8 ++++---- .github/workflows/scorecard.yml | 10 +++++----- README.md | 2 +- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 3a15900..a8eef23 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -41,16 +41,16 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2 + uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0 with: egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs - name: Checkout repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0 + uses: github/codeql-action/init@3c3833e0f8c1c83d449a7478aa59c036a9165498 # v3.29.11 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -60,7 +60,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0 + uses: github/codeql-action/autobuild@3c3833e0f8c1c83d449a7478aa59c036a9165498 # v3.29.11 # â„šī¸ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun @@ -73,6 +73,6 @@ jobs: # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0 + uses: github/codeql-action/analyze@3c3833e0f8c1c83d449a7478aa59c036a9165498 # v3.29.11 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index 9d5cf16..414589e 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -19,18 +19,18 @@ jobs: strategy: matrix: - node-version: [20.x] + node-version: [22.x, 24.x] # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ steps: - name: Harden Runner - uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2 + uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0 with: egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: ${{ matrix.node-version }} - run: npm i diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 960a3b0..63444b0 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -32,17 +32,17 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2 + uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0 with: egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs - name: "Checkout code" - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false - name: "Run analysis" - uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0 + uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2 with: results_file: results.sarif results_format: sarif @@ -64,7 +64,7 @@ jobs: # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # format to the repository Actions tab. - name: "Upload artifact" - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: SARIF file path: results.sarif @@ -72,6 +72,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0 + uses: github/codeql-action/upload-sarif@3c3833e0f8c1c83d449a7478aa59c036a9165498 # v3.29.11 with: sarif_file: results.sarif diff --git a/README.md b/README.md index af3f664..1f012e7 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ Light with seriously maintained dependencies: ![](./docs/images/nodesecure.PNG) ## 🚧 Requirements -- [Node.js](https://nodejs.org/en/) version 20 or higher +- [Node.js](https://nodejs.org/en/) version 22 or higher ## 🚀 Getting Started From 8186fabb8216bda163ed76e490a53820a4196a15 Mon Sep 17 00:00:00 2001 From: fraxken Date: Mon, 25 Aug 2025 13:31:23 +0200 Subject: [PATCH 07/12] docs: update examples --- eslint.config.mjs | 4 +++- examples/retry.mjs | 8 -------- examples/stream.mjs | 11 ++++++++--- 3 files changed, 11 insertions(+), 12 deletions(-) delete mode 100644 examples/retry.mjs diff --git a/eslint.config.mjs b/eslint.config.mjs index 8a1e9d0..f221107 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,3 +1,5 @@ import { typescriptConfig } from "@openally/config.eslint"; -export default typescriptConfig(); +export default typescriptConfig({ + ignores: ["./examples"] +}); diff --git a/examples/retry.mjs b/examples/retry.mjs deleted file mode 100644 index 0127eac..0000000 --- a/examples/retry.mjs +++ /dev/null @@ -1,8 +0,0 @@ -// Import Third-party Dependencies -import * as httpie from "../dist/index.js"; -// import * as httpie from "@openally/httpie"; - -const { data } = await httpie.retry(async() => { - return await httpie.get("https://jsonplaceholder.typicode.com/posts"); -}, { forever: true }, httpie.policies.httpcode()); -console.log(data); diff --git a/examples/stream.mjs b/examples/stream.mjs index 7e226db..ee83e23 100644 --- a/examples/stream.mjs +++ b/examples/stream.mjs @@ -11,9 +11,14 @@ import * as httpie from "../dist/index.js"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const kGithubURL = new URL("https://github.com/"); -const cursor = httpie.stream("GET", new URL("NodeSecure/i18n/archive/main.tar.gz", kGithubURL), { - maxRedirections: 1 -}); +const agent = new httpie.Agent( + httpie.interceptors.redirect({ maxRedirections: 2 }) +); +const cursor = httpie.stream( + "GET", + new URL("NodeSecure/vulnera/archive/main.tar.gz", kGithubURL), + { agent } +); const writable = fs.createWriteStream(path.join(__dirname, "archive.tar.gz")); From 03b36dd5219204517ac7b5fe665ac4c6d3d7d411 Mon Sep 17 00:00:00 2001 From: fraxken Date: Mon, 25 Aug 2025 13:34:19 +0200 Subject: [PATCH 08/12] chore: remove nsecure-result --- .gitignore | 1 + nsecure-result.json | 1228 ------------------------------------------- 2 files changed, 1 insertion(+), 1228 deletions(-) delete mode 100644 nsecure-result.json diff --git a/.gitignore b/.gitignore index 0e2529b..b22eb85 100644 --- a/.gitignore +++ b/.gitignore @@ -120,3 +120,4 @@ dist tmp/ test/download +nsecure-result.json diff --git a/nsecure-result.json b/nsecure-result.json deleted file mode 100644 index da1bbb6..0000000 --- a/nsecure-result.json +++ /dev/null @@ -1,1228 +0,0 @@ -{ - "id": "J3v6Rx", - "rootDependencyName": "@openally/httpie", - "scannerVersion": "6.4.0", - "vulnerabilityStrategy": "github-advisory", - "warnings": [], - "highlighted": { - "contacts": [] - }, - "dependencies": { - "content-type": { - "versions": { - "1.0.5": { - "id": 1, - "usedBy": { - "@openally/httpie": "1.0.0" - }, - "isDevDependency": false, - "existOnRemoteRegistry": true, - "flags": [ - "hasManyPublishers" - ], - "warnings": [], - "dependencyCount": 0, - "gitUrl": null, - "alias": {}, - "description": "Create and parse HTTP Content-Type header", - "size": 10471, - "author": { - "name": "Douglas Christopher Wilson", - "email": "doug@somethingdoug.com" - }, - "engines": { - "node": ">= 0.6" - }, - "scripts": { - "lint": "eslint .", - "test": "mocha --reporter spec --check-leaks --bail test/", - "test-ci": "nyc --reporter=lcovonly --reporter=text npm test", - "test-cov": "nyc --reporter=html --reporter=text npm test", - "version": "node scripts/version-history.js && git add HISTORY.md" - }, - "licenses": [ - { - "licenses": { - "MIT": "https://spdx.org/licenses/MIT.html#licenseText" - }, - "spdx": { - "osi": true, - "fsf": true, - "fsfAndOsi": true, - "includesDeprecated": false - }, - "fileName": "package.json" - }, - { - "licenses": { - "MIT": "https://spdx.org/licenses/MIT.html#licenseText" - }, - "spdx": { - "osi": true, - "fsf": true, - "fsfAndOsi": true, - "includesDeprecated": false - }, - "fileName": "LICENSE" - } - ], - "uniqueLicenseIds": [ - "MIT" - ], - "composition": { - "extensions": [ - ".md", - ".js", - "", - ".json" - ], - "files": [ - "HISTORY.md", - "LICENSE", - "README.md", - "index.js", - "package.json" - ], - "minified": [], - "unused": [], - "missing": [], - "required_files": [], - "required_nodejs": [], - "required_thirdparty": [], - "required_subpath": {} - }, - "repository": "jshttp/content-type", - "integrity": "57f537305321f48c236b623051c26e43e7e620fd", - "links": { - "npm": "https://www.npmjs.com/package/content-type/v/1.0.5", - "homepage": "https://github.com/jshttp/content-type#readme", - "repository": "https://github.com/jshttp/content-type" - } - } - }, - "vulnerabilities": [], - "metadata": { - "author": { - "name": "Douglas Christopher Wilson", - "email": "doug@somethingdoug.com", - "npmAvatar": "/npm-avatar/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdmF0YXJVUkwiOiJodHRwczovL3MuZ3JhdmF0YXIuY29tL2F2YXRhci9jNGI2NGM2ZTQ3ZDk3MGJiMGQwN2EyYjlkNTU5YmU5Yj9zaXplPTUwJmRlZmF1bHQ9cmV0cm8ifQ.PysVThLy9zc22g8f3XWGCfhIBy_bxYGq4JAOjJCj3xk" - }, - "homepage": "https://github.com/jshttp/content-type#readme", - "publishedCount": 7, - "lastVersion": "1.0.5", - "lastUpdateAt": "2023-01-29T19:25:59.622Z", - "hasReceivedUpdateInOneYear": false, - "hasManyPublishers": true, - "hasChangedAuthor": false, - "maintainers": [ - { - "email": "ulisesgascondev@gmail.com", - "name": "ulisesgascon", - "npmAvatar": "/npm-avatar/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdmF0YXJVUkwiOiJodHRwczovL3MuZ3JhdmF0YXIuY29tL2F2YXRhci8xZTlkNmRlOGU1YzMxN2EyYWYxZDc4YWNlYWFmYTkwZT9zaXplPTUwJmRlZmF1bHQ9cmV0cm8ifQ.H3nB2cyKYs5mgiYnXdJNQ-KnKTbtZRdlM-bSV9_Rp3g" - }, - { - "email": "hello@blakeembrey.com", - "name": "blakeembrey", - "npmAvatar": "/npm-avatar/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdmF0YXJVUkwiOiJodHRwczovL3MuZ3JhdmF0YXIuY29tL2F2YXRhci9hOWQ3NTE4YzRjYjQ3ZWY2NjhjZDhiMDMxMTk5NDVjYT9zaXplPTUwJmRlZmF1bHQ9cmV0cm8ifQ.Z4Ap77mB2kIKoNdGpj0krdkHNVXFMqdGAGweYebCQBw" - }, - { - "email": "doug@somethingdoug.com", - "name": "dougwilson", - "npmAvatar": "/npm-avatar/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdmF0YXJVUkwiOiJodHRwczovL3MuZ3JhdmF0YXIuY29tL2F2YXRhci9jNGI2NGM2ZTQ3ZDk3MGJiMGQwN2EyYjlkNTU5YmU5Yj9zaXplPTUwJmRlZmF1bHQ9cmV0cm8ifQ.PysVThLy9zc22g8f3XWGCfhIBy_bxYGq4JAOjJCj3xk" - } - ], - "publishers": [ - { - "name": "dougwilson", - "email": "doug@somethingdoug.com", - "version": "1.0.5", - "at": "2023-01-29T19:25:59.622Z", - "npmAvatar": "/npm-avatar/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdmF0YXJVUkwiOiJodHRwczovL3MuZ3JhdmF0YXIuY29tL2F2YXRhci9jNGI2NGM2ZTQ3ZDk3MGJiMGQwN2EyYjlkNTU5YmU5Yj9zaXplPTUwJmRlZmF1bHQ9cmV0cm8ifQ.PysVThLy9zc22g8f3XWGCfhIBy_bxYGq4JAOjJCj3xk" - }, - { - "name": "deoxxa", - "email": "deoxxa@fknsrs.biz", - "version": "0.0.1", - "at": "2013-10-08T07:37:54.398Z", - "npmAvatar": "/npm-avatar/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdmF0YXJVUkwiOiJodHRwczovL3MuZ3JhdmF0YXIuY29tL2F2YXRhci9kMjdiYWU1MWJhMTYzNzg1ODY5MTYxMTI2NDM0ZWE1Nj9zaXplPTUwJmRlZmF1bHQ9cmV0cm8ifQ._IXNYcSMxwgp6huLbUBmWPy3v4hWLq5cQp1zztAJWj8" - } - ], - "integrity": { - "1.0.5": "57f537305321f48c236b623051c26e43e7e620fd" - } - } - }, - "statuses": { - "versions": { - "2.0.1": { - "id": 2, - "usedBy": { - "@openally/httpie": "1.0.0" - }, - "isDevDependency": false, - "existOnRemoteRegistry": true, - "flags": [ - "hasManyPublishers" - ], - "warnings": [], - "dependencyCount": 0, - "gitUrl": null, - "alias": {}, - "description": "HTTP status utility", - "size": 12116, - "author": null, - "engines": { - "node": ">= 0.8" - }, - "scripts": { - "build": "node scripts/build.js", - "fetch": "node scripts/fetch-apache.js && node scripts/fetch-iana.js && node scripts/fetch-nginx.js && node scripts/fetch-node.js", - "lint": "eslint --plugin markdown --ext js,md .", - "test": "mocha --reporter spec --check-leaks --bail test/", - "test-ci": "nyc --reporter=lcov --reporter=text npm test", - "test-cov": "nyc --reporter=html --reporter=text npm test", - "update": "npm run fetch && npm run build", - "version": "node scripts/version-history.js && git add HISTORY.md" - }, - "licenses": [ - { - "licenses": { - "MIT": "https://spdx.org/licenses/MIT.html#licenseText" - }, - "spdx": { - "osi": true, - "fsf": true, - "fsfAndOsi": true, - "includesDeprecated": false - }, - "fileName": "package.json" - }, - { - "licenses": { - "MIT": "https://spdx.org/licenses/MIT.html#licenseText" - }, - "spdx": { - "osi": true, - "fsf": true, - "fsfAndOsi": true, - "includesDeprecated": false - }, - "fileName": "LICENSE" - } - ], - "uniqueLicenseIds": [ - "MIT" - ], - "composition": { - "extensions": [ - ".json", - ".md", - ".js", - "" - ], - "files": [ - "HISTORY.md", - "LICENSE", - "README.md", - "codes.json", - "index.js", - "package.json" - ], - "minified": [], - "unused": [], - "missing": [], - "required_files": [ - "codes.json" - ], - "required_nodejs": [], - "required_thirdparty": [], - "required_subpath": {} - }, - "repository": "jshttp/statuses", - "integrity": "05ac0194e1db35877751400aea69ff922df051b6", - "links": { - "npm": "https://www.npmjs.com/package/statuses/v/2.0.1", - "homepage": "https://github.com/jshttp/statuses#readme", - "repository": "https://github.com/jshttp/statuses" - } - } - }, - "vulnerabilities": [], - "metadata": { - "author": { - "name": "dougwilson", - "email": "doug@somethingdoug.com", - "npmAvatar": "/npm-avatar/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdmF0YXJVUkwiOiJodHRwczovL3MuZ3JhdmF0YXIuY29tL2F2YXRhci9jNGI2NGM2ZTQ3ZDk3MGJiMGQwN2EyYjlkNTU5YmU5Yj9zaXplPTUwJmRlZmF1bHQ9cmV0cm8ifQ.PysVThLy9zc22g8f3XWGCfhIBy_bxYGq4JAOjJCj3xk" - }, - "homepage": "https://github.com/jshttp/statuses#readme", - "publishedCount": 14, - "lastVersion": "2.0.1", - "lastUpdateAt": "2021-01-03T06:37:47.488Z", - "hasReceivedUpdateInOneYear": false, - "hasManyPublishers": true, - "hasChangedAuthor": false, - "maintainers": [ - { - "email": "ulisesgascondev@gmail.com", - "name": "ulisesgascon", - "npmAvatar": "/npm-avatar/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdmF0YXJVUkwiOiJodHRwczovL3MuZ3JhdmF0YXIuY29tL2F2YXRhci8xZTlkNmRlOGU1YzMxN2EyYWYxZDc4YWNlYWFmYTkwZT9zaXplPTUwJmRlZmF1bHQ9cmV0cm8ifQ.H3nB2cyKYs5mgiYnXdJNQ-KnKTbtZRdlM-bSV9_Rp3g" - }, - { - "email": "hello@blakeembrey.com", - "name": "blakeembrey", - "npmAvatar": "/npm-avatar/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdmF0YXJVUkwiOiJodHRwczovL3MuZ3JhdmF0YXIuY29tL2F2YXRhci9hOWQ3NTE4YzRjYjQ3ZWY2NjhjZDhiMDMxMTk5NDVjYT9zaXplPTUwJmRlZmF1bHQ9cmV0cm8ifQ.Z4Ap77mB2kIKoNdGpj0krdkHNVXFMqdGAGweYebCQBw" - }, - { - "email": "doug@somethingdoug.com", - "name": "dougwilson", - "npmAvatar": "/npm-avatar/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdmF0YXJVUkwiOiJodHRwczovL3MuZ3JhdmF0YXIuY29tL2F2YXRhci9jNGI2NGM2ZTQ3ZDk3MGJiMGQwN2EyYjlkNTU5YmU5Yj9zaXplPTUwJmRlZmF1bHQ9cmV0cm8ifQ.PysVThLy9zc22g8f3XWGCfhIBy_bxYGq4JAOjJCj3xk" - }, - { - "email": "jonathanrichardong@gmail.com", - "name": "jongleberry", - "npmAvatar": "/npm-avatar/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdmF0YXJVUkwiOiJodHRwczovL3MuZ3JhdmF0YXIuY29tL2F2YXRhci82ZTMzY2MwNDEyYjYxY2MwMWRhYWMyM2M4OTg5MDAzYz9zaXplPTUwJmRlZmF1bHQ9cmV0cm8ifQ.xeKiBokeI0NPlBc-_eDtxcICYyY9PV9dRjRwios7vgQ" - } - ], - "publishers": [ - { - "name": "dougwilson", - "email": "doug@somethingdoug.com", - "version": "2.0.1", - "at": "2021-01-03T06:37:47.488Z", - "npmAvatar": "/npm-avatar/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdmF0YXJVUkwiOiJodHRwczovL3MuZ3JhdmF0YXIuY29tL2F2YXRhci9jNGI2NGM2ZTQ3ZDk3MGJiMGQwN2EyYjlkNTU5YmU5Yj9zaXplPTUwJmRlZmF1bHQ9cmV0cm8ifQ.PysVThLy9zc22g8f3XWGCfhIBy_bxYGq4JAOjJCj3xk" - }, - { - "name": "jongleberry", - "email": "jonathanrichardong@gmail.com", - "version": "1.2.0", - "at": "2014-09-29T04:11:14.857Z", - "npmAvatar": "/npm-avatar/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdmF0YXJVUkwiOiJodHRwczovL3MuZ3JhdmF0YXIuY29tL2F2YXRhci82ZTMzY2MwNDEyYjYxY2MwMWRhYWMyM2M4OTg5MDAzYz9zaXplPTUwJmRlZmF1bHQ9cmV0cm8ifQ.xeKiBokeI0NPlBc-_eDtxcICYyY9PV9dRjRwios7vgQ" - } - ], - "integrity": { - "2.0.1": "05ac0194e1db35877751400aea69ff922df051b6" - } - } - }, - "undici": { - "versions": { - "7.10.0": { - "id": 3, - "usedBy": { - "@openally/httpie": "1.0.0" - }, - "isDevDependency": false, - "existOnRemoteRegistry": true, - "flags": [ - "hasMissingOrUnusedDependency", - "hasWarnings", - "hasManyPublishers" - ], - "warnings": [ - { - "kind": "unsafe-regex", - "location": [ - [ - 772, - 25 - ], - [ - 772, - 54 - ] - ], - "source": "JS-X-Ray", - "value": "^bytes (\\d+)-(\\d+)\\/(\\d+)?$", - "i18n": "sast_warnings.unsafe_regex", - "severity": "Warning", - "file": "lib\\core\\util.js" - }, - { - "kind": "suspicious-literal", - "location": [ - [ - 0, - 0 - ], - [ - 0, - 0 - ] - ], - "source": "JS-X-Ray", - "value": 94, - "i18n": "sast_warnings.suspicious_literal", - "severity": "Warning", - "file": "lib\\llhttp\\llhttp-wasm.js" - }, - { - "kind": "suspicious-literal", - "location": [ - [ - 0, - 0 - ], - [ - 0, - 0 - ] - ], - "source": "JS-X-Ray", - "value": 94, - "i18n": "sast_warnings.suspicious_literal", - "severity": "Warning", - "file": "lib\\llhttp\\llhttp_simd-wasm.js" - }, - { - "kind": "unsafe-regex", - "location": [ - [ - 772, - 29 - ], - [ - 772, - 128 - ] - ], - "source": "JS-X-Ray", - "value": "(?sha256|sha384|sha512)-((?[A-Za-z0-9+/]+|[A-Za-z0-9_-]+)={0,2}(?:\\s|$)( +[!-~]*)?)?", - "i18n": "sast_warnings.unsafe_regex", - "severity": "Warning", - "file": "lib\\web\\fetch\\util.js" - } - ], - "dependencyCount": 0, - "gitUrl": null, - "alias": {}, - "description": "An HTTP/1.1 client, written from scratch for Node.js", - "size": 1355734, - "author": null, - "engines": { - "node": ">=20.18.1" - }, - "scripts": { - "build:node": "esbuild index-fetch.js --bundle --platform=node --outfile=undici-fetch.js --define:esbuildDetection=1 --keep-names && node scripts/strip-comments.js", - "build:wasm": "node build/wasm.js --docker", - "generate-pem": "node scripts/generate-pem.js", - "lint": "eslint --cache", - "lint:fix": "eslint --fix --cache", - "test": "npm run test:javascript && cross-env NODE_V8_COVERAGE= npm run test:typescript", - "test:javascript": "npm run test:javascript:no-jest && npm run test:jest", - "test:javascript:no-jest": "npm run generate-pem && npm run test:unit && npm run test:node-fetch && npm run test:cache && npm run test:cache-interceptor && npm run test:interceptors && npm run test:fetch && npm run test:cookies && npm run test:eventsource && npm run test:wpt && npm run test:websocket && npm run test:node-test && npm run test:cache-tests", - "test:javascript:without-intl": "npm run test:javascript:no-jest", - "test:busboy": "borp -p \"test/busboy/*.js\"", - "test:cache": "borp -p \"test/cache/*.js\"", - "test:sqlite": "NODE_OPTIONS=--experimental-sqlite borp -p \"test/cache-interceptor/*.js\"", - "test:cache-interceptor": "borp -p \"test/cache-interceptor/*.js\"", - "test:cookies": "borp -p \"test/cookie/*.js\"", - "test:eventsource": "npm run build:node && borp --expose-gc -p \"test/eventsource/*.js\"", - "test:fuzzing": "node test/fuzzing/fuzzing.test.js", - "test:fetch": "npm run build:node && borp --timeout 180000 --expose-gc --concurrency 1 -p \"test/fetch/*.js\" && npm run test:webidl && npm run test:busboy", - "test:h2": "npm run test:h2:core && npm run test:h2:fetch", - "test:h2:core": "borp -p \"test/+(http2|h2)*.js\"", - "test:h2:fetch": "npm run build:node && borp -p \"test/fetch/http2*.js\"", - "test:interceptors": "borp -p \"test/interceptors/*.js\"", - "test:jest": "cross-env NODE_V8_COVERAGE= jest", - "test:unit": "borp --expose-gc -p \"test/*.js\"", - "test:node-fetch": "borp -p \"test/node-fetch/**/*.js\"", - "test:node-test": "borp -p \"test/node-test/**/*.js\"", - "test:tdd": "borp --expose-gc -p \"test/*.js\"", - "test:tdd:node-test": "borp -p \"test/node-test/**/*.js\" -w", - "test:typescript": "tsd && tsc test/imports/undici-import.ts --typeRoots ./types --noEmit && tsc ./types/*.d.ts --noEmit --typeRoots ./types", - "test:webidl": "borp -p \"test/webidl/*.js\"", - "test:websocket": "borp -p \"test/websocket/*.js\"", - "test:websocket:autobahn": "node test/autobahn/client.js", - "test:websocket:autobahn:report": "node test/autobahn/report.js", - "test:wpt": "node test/wpt/start-fetch.mjs && node test/wpt/start-mimesniff.mjs && node test/wpt/start-xhr.mjs && node test/wpt/start-websockets.mjs && node test/wpt/start-cacheStorage.mjs && node test/wpt/start-eventsource.mjs", - "test:wpt:withoutintl": "node test/wpt/start-fetch.mjs && node test/wpt/start-mimesniff.mjs && node test/wpt/start-xhr.mjs && node test/wpt/start-cacheStorage.mjs && node test/wpt/start-eventsource.mjs", - "test:cache-tests": "node test/cache-interceptor/cache-tests.mjs --ci", - "coverage": "npm run coverage:clean && cross-env NODE_V8_COVERAGE=./coverage/tmp npm run test:javascript && npm run coverage:report", - "coverage:ci": "npm run coverage:clean && cross-env NODE_V8_COVERAGE=./coverage/tmp npm run test:javascript && npm run coverage:report:ci", - "coverage:clean": "node ./scripts/clean-coverage.js", - "coverage:report": "cross-env NODE_V8_COVERAGE= c8 report", - "coverage:report:ci": "c8 report", - "bench": "echo \"Error: Benchmarks have been moved to '/benchmarks'\" && exit 1", - "serve:website": "echo \"Error: Documentation has been moved to '/docs'\" && exit 1", - "prepare": "husky && node ./scripts/platform-shell.js" - }, - "licenses": [ - { - "licenses": { - "MIT": "https://spdx.org/licenses/MIT.html#licenseText" - }, - "spdx": { - "osi": true, - "fsf": true, - "fsfAndOsi": true, - "includesDeprecated": false - }, - "fileName": "package.json" - }, - { - "licenses": { - "MIT": "https://spdx.org/licenses/MIT.html#licenseText" - }, - "spdx": { - "osi": true, - "fsf": true, - "fsfAndOsi": true, - "includesDeprecated": false - }, - "fileName": "LICENSE" - } - ], - "uniqueLicenseIds": [ - "MIT" - ], - "composition": { - "extensions": [ - ".md", - ".js", - ".ts", - "", - ".map", - ".json" - ], - "files": [ - "LICENSE", - "README.md", - "docs\\docs\\api\\Agent.md", - "docs\\docs\\api\\BalancedPool.md", - "docs\\docs\\api\\CacheStorage.md", - "docs\\docs\\api\\CacheStore.md", - "docs\\docs\\api\\Client.md", - "docs\\docs\\api\\ClientStats.md", - "docs\\docs\\api\\Connector.md", - "docs\\docs\\api\\ContentType.md", - "docs\\docs\\api\\Cookies.md", - "docs\\docs\\api\\Debug.md", - "docs\\docs\\api\\DiagnosticsChannel.md", - "docs\\docs\\api\\Dispatcher.md", - "docs\\docs\\api\\EnvHttpProxyAgent.md", - "docs\\docs\\api\\Errors.md", - "docs\\docs\\api\\EventSource.md", - "docs\\docs\\api\\Fetch.md", - "docs\\docs\\api\\H2CClient.md", - "docs\\docs\\api\\MockAgent.md", - "docs\\docs\\api\\MockCallHistory.md", - "docs\\docs\\api\\MockCallHistoryLog.md", - "docs\\docs\\api\\MockClient.md", - "docs\\docs\\api\\MockErrors.md", - "docs\\docs\\api\\MockPool.md", - "docs\\docs\\api\\Pool.md", - "docs\\docs\\api\\PoolStats.md", - "docs\\docs\\api\\ProxyAgent.md", - "docs\\docs\\api\\RedirectHandler.md", - "docs\\docs\\api\\RetryAgent.md", - "docs\\docs\\api\\RetryHandler.md", - "docs\\docs\\api\\Util.md", - "docs\\docs\\api\\WebSocket.md", - "docs\\docs\\api\\api-lifecycle.md", - "docs\\docs\\best-practices\\client-certificate.md", - "docs\\docs\\best-practices\\mocking-request.md", - "docs\\docs\\best-practices\\proxy.md", - "docs\\docs\\best-practices\\writing-tests.md", - "index-fetch.js", - "index.d.ts", - "index.js", - "lib\\api\\abort-signal.js", - "lib\\api\\api-connect.js", - "lib\\api\\api-pipeline.js", - "lib\\api\\api-request.js", - "lib\\api\\api-stream.js", - "lib\\api\\api-upgrade.js", - "lib\\api\\index.js", - "lib\\api\\readable.js", - "lib\\api\\util.js", - "lib\\cache\\memory-cache-store.js", - "lib\\cache\\sqlite-cache-store.js", - "lib\\core\\connect.js", - "lib\\core\\constants.js", - "lib\\core\\diagnostics.js", - "lib\\core\\errors.js", - "lib\\core\\request.js", - "lib\\core\\symbols.js", - "lib\\core\\tree.js", - "lib\\core\\util.js", - "lib\\dispatcher\\agent.js", - "lib\\dispatcher\\balanced-pool.js", - "lib\\dispatcher\\client-h1.js", - "lib\\dispatcher\\client-h2.js", - "lib\\dispatcher\\client.js", - "lib\\dispatcher\\dispatcher-base.js", - "lib\\dispatcher\\dispatcher.js", - "lib\\dispatcher\\env-http-proxy-agent.js", - "lib\\dispatcher\\fixed-queue.js", - "lib\\dispatcher\\h2c-client.js", - "lib\\dispatcher\\pool-base.js", - "lib\\dispatcher\\pool.js", - "lib\\dispatcher\\proxy-agent.js", - "lib\\dispatcher\\retry-agent.js", - "lib\\global.js", - "lib\\handler\\cache-handler.js", - "lib\\handler\\cache-revalidation-handler.js", - "lib\\handler\\decorator-handler.js", - "lib\\handler\\redirect-handler.js", - "lib\\handler\\retry-handler.js", - "lib\\handler\\unwrap-handler.js", - "lib\\handler\\wrap-handler.js", - "lib\\interceptor\\cache.js", - "lib\\interceptor\\dns.js", - "lib\\interceptor\\dump.js", - "lib\\interceptor\\redirect.js", - "lib\\interceptor\\response-error.js", - "lib\\interceptor\\retry.js", - "lib\\llhttp\\.gitkeep", - "lib\\llhttp\\constants.d.ts", - "lib\\llhttp\\constants.js", - "lib\\llhttp\\constants.js.map", - "lib\\llhttp\\llhttp-wasm.js", - "lib\\llhttp\\llhttp_simd-wasm.js", - "lib\\llhttp\\utils.d.ts", - "lib\\llhttp\\utils.js", - "lib\\llhttp\\utils.js.map", - "lib\\mock\\mock-agent.js", - "lib\\mock\\mock-call-history.js", - "lib\\mock\\mock-client.js", - "lib\\mock\\mock-errors.js", - "lib\\mock\\mock-interceptor.js", - "lib\\mock\\mock-pool.js", - "lib\\mock\\mock-symbols.js", - "lib\\mock\\mock-utils.js", - "lib\\mock\\pending-interceptors-formatter.js", - "lib\\util\\cache.js", - "lib\\util\\date.js", - "lib\\util\\stats.js", - "lib\\util\\timers.js", - "lib\\web\\cache\\cache.js", - "lib\\web\\cache\\cachestorage.js", - "lib\\web\\cache\\util.js", - "lib\\web\\cookies\\constants.js", - "lib\\web\\cookies\\index.js", - "lib\\web\\cookies\\parse.js", - "lib\\web\\cookies\\util.js", - "lib\\web\\eventsource\\eventsource-stream.js", - "lib\\web\\eventsource\\eventsource.js", - "lib\\web\\eventsource\\util.js", - "lib\\web\\fetch\\LICENSE", - "lib\\web\\fetch\\body.js", - "lib\\web\\fetch\\constants.js", - "lib\\web\\fetch\\data-url.js", - "lib\\web\\fetch\\dispatcher-weakref.js", - "lib\\web\\fetch\\formdata-parser.js", - "lib\\web\\fetch\\formdata.js", - "lib\\web\\fetch\\global.js", - "lib\\web\\fetch\\headers.js", - "lib\\web\\fetch\\index.js", - "lib\\web\\fetch\\request.js", - "lib\\web\\fetch\\response.js", - "lib\\web\\fetch\\util.js", - "lib\\web\\fetch\\webidl.js", - "lib\\web\\websocket\\connection.js", - "lib\\web\\websocket\\constants.js", - "lib\\web\\websocket\\events.js", - "lib\\web\\websocket\\frame.js", - "lib\\web\\websocket\\permessage-deflate.js", - "lib\\web\\websocket\\receiver.js", - "lib\\web\\websocket\\sender.js", - "lib\\web\\websocket\\stream\\websocketerror.js", - "lib\\web\\websocket\\stream\\websocketstream.js", - "lib\\web\\websocket\\util.js", - "lib\\web\\websocket\\websocket.js", - "package.json", - "scripts\\strip-comments.js", - "types\\README.md", - "types\\agent.d.ts", - "types\\api.d.ts", - "types\\balanced-pool.d.ts", - "types\\cache-interceptor.d.ts", - "types\\cache.d.ts", - "types\\client-stats.d.ts", - "types\\client.d.ts", - "types\\connector.d.ts", - "types\\content-type.d.ts", - "types\\cookies.d.ts", - "types\\diagnostics-channel.d.ts", - "types\\dispatcher.d.ts", - "types\\env-http-proxy-agent.d.ts", - "types\\errors.d.ts", - "types\\eventsource.d.ts", - "types\\fetch.d.ts", - "types\\formdata.d.ts", - "types\\global-dispatcher.d.ts", - "types\\global-origin.d.ts", - "types\\h2c-client.d.ts", - "types\\handlers.d.ts", - "types\\header.d.ts", - "types\\index.d.ts", - "types\\interceptors.d.ts", - "types\\mock-agent.d.ts", - "types\\mock-call-history.d.ts", - "types\\mock-client.d.ts", - "types\\mock-errors.d.ts", - "types\\mock-interceptor.d.ts", - "types\\mock-pool.d.ts", - "types\\patch.d.ts", - "types\\pool-stats.d.ts", - "types\\pool.d.ts", - "types\\proxy-agent.d.ts", - "types\\readable.d.ts", - "types\\retry-agent.d.ts", - "types\\retry-handler.d.ts", - "types\\util.d.ts", - "types\\utility.d.ts", - "types\\webidl.d.ts", - "types\\websocket.d.ts" - ], - "minified": [], - "unused": [], - "missing": [ - "node:sqlite" - ], - "required_files": [ - "lib\\global.js", - "lib\\dispatcher\\env-http-proxy-agent.js", - "lib\\web\\fetch.js", - "lib\\web\\fetch\\formdata.js", - "lib\\web\\fetch\\headers.js", - "lib\\web\\fetch\\response.js", - "lib\\web\\fetch\\request.js", - "lib\\web\\websocket\\events.js", - "lib\\web\\websocket\\websocket.js", - "lib\\web\\eventsource\\eventsource.js", - "lib\\api.js", - "lib\\dispatcher\\dispatcher.js", - "lib\\dispatcher\\client.js", - "lib\\dispatcher\\pool.js", - "lib\\dispatcher\\balanced-pool.js", - "lib\\dispatcher\\agent.js", - "lib\\dispatcher\\proxy-agent.js", - "lib\\dispatcher\\retry-agent.js", - "lib\\dispatcher\\h2c-client.js", - "lib\\core\\errors.js", - "lib\\core\\util.js", - "lib\\core\\connect.js", - "lib\\mock\\mock-client.js", - "lib\\mock\\mock-call-history.js", - "lib\\mock\\mock-agent.js", - "lib\\mock\\mock-pool.js", - "lib\\mock\\mock-errors.js", - "lib\\handler\\retry-handler.js", - "lib\\handler\\decorator-handler.js", - "lib\\handler\\redirect-handler.js", - "lib\\interceptor\\redirect.js", - "lib\\interceptor\\response-error.js", - "lib\\interceptor\\retry.js", - "lib\\interceptor\\dump.js", - "lib\\interceptor\\dns.js", - "lib\\interceptor\\cache.js", - "lib\\cache\\memory-cache-store.js", - "lib\\cache\\sqlite-cache-store.js", - "lib\\web\\fetch\\global.js", - "lib\\web\\cache\\cachestorage.js", - "lib\\core\\symbols.js", - "lib\\web\\cookies.js", - "lib\\web\\fetch\\data-url.js", - "lib\\web\\websocket\\stream\\websocketstream.js", - "lib\\web\\websocket\\stream\\websocketerror.js", - "lib\\api\\abort-signal.js", - "lib\\api\\readable.js", - "lib\\api\\api-request.js", - "lib\\api\\api-stream.js", - "lib\\api\\api-pipeline.js", - "lib\\api\\api-upgrade.js", - "lib\\api\\api-connect.js", - "lib\\util\\cache.js", - "lib\\core\\diagnostics.js", - "lib\\core\\constants.js", - "lib\\util\\timers.js", - "lib\\core\\tree.js", - "lib\\dispatcher\\dispatcher-base.js", - "lib\\dispatcher\\pool-base.js", - "lib\\llhttp\\constants.js", - "lib\\llhttp\\llhttp-wasm.js", - "lib\\llhttp\\llhttp_simd-wasm.js", - "lib\\web\\fetch\\body.js", - "lib\\util\\stats.js", - "lib\\core\\request.js", - "lib\\dispatcher\\client-h1.js", - "lib\\dispatcher\\client-h2.js", - "lib\\handler\\unwrap-handler.js", - "lib\\handler\\wrap-handler.js", - "lib\\dispatcher\\fixed-queue.js", - "lib\\util\\date.js", - "lib\\handler\\cache-handler.js", - "lib\\handler\\cache-revalidation-handler.js", - "lib\\llhttp\\utils.js", - "lib\\mock\\mock-symbols.js", - "lib\\mock\\mock-utils.js", - "lib\\mock\\pending-interceptors-formatter.js", - "lib\\mock\\mock-interceptor.js", - "lib\\web\\cache\\util.js", - "lib\\web\\fetch\\webidl.js", - "lib\\web\\fetch\\index.js", - "lib\\web\\fetch\\util.js", - "lib\\web\\cache\\cache.js", - "lib\\web\\cookies\\parse.js", - "lib\\web\\cookies\\util.js", - "lib\\web\\cookies\\constants.js", - "lib\\web\\eventsource\\util.js", - "lib\\web\\eventsource\\eventsource-stream.js", - "lib\\web\\fetch\\formdata-parser.js", - "lib\\web\\fetch\\constants.js", - "lib\\web\\fetch\\dispatcher-weakref.js", - "lib\\web\\websocket\\constants.js", - "lib\\web\\websocket\\util.js", - "lib\\web\\websocket\\frame.js", - "lib\\web\\websocket\\connection.js", - "lib\\web\\websocket\\permessage-deflate.js", - "lib\\web\\websocket\\receiver.js", - "lib\\web\\websocket\\sender.js" - ], - "required_nodejs": [ - "node:assert", - "node:async_hooks", - "node:stream", - "node:events", - "stream", - "node:net", - "node:tls", - "node:diagnostics_channel", - "node:util", - "node:http", - "node:buffer", - "node:querystring", - "node:http2", - "node:url", - "node:dns", - "node:console", - "node:crypto", - "node:zlib", - "node:perf_hooks", - "node:worker_threads", - "node:fs" - ], - "required_thirdparty": [ - "node:sqlite" - ], - "required_subpath": {} - }, - "repository": { - "type": "git", - "url": "git+https://github.com/nodejs/undici.git" - }, - "integrity": "6cd313748e3ddd158436a9cb313f5b301a8d7d8a", - "links": { - "npm": "https://www.npmjs.com/package/undici/v/7.10.0", - "homepage": "https://undici.nodejs.org", - "repository": "https://github.com/nodejs/undici" - } - } - }, - "vulnerabilities": [], - "metadata": { - "author": { - "name": "matteo.collina", - "email": "hello@matteocollina.com", - "npmAvatar": "/npm-avatar/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdmF0YXJVUkwiOiJodHRwczovL3MuZ3JhdmF0YXIuY29tL2F2YXRhci9jM2ZjNzM3MGJjMDk1MWZiYTk0NGI3YjhjYWM1YjljYz9zaXplPTUwJmRlZmF1bHQ9cmV0cm8ifQ.J2Uu8Dfsu7sVPjnVTSTjbFO0Lenbufv6l9a3m3OrJNc" - }, - "homepage": "https://undici.nodejs.org", - "publishedCount": 244, - "lastVersion": "7.10.0", - "lastUpdateAt": "2025-05-20T07:19:22.524Z", - "hasReceivedUpdateInOneYear": true, - "hasManyPublishers": true, - "hasChangedAuthor": false, - "maintainers": [ - { - "name": "matteo.collina", - "email": "hello@matteocollina.com", - "npmAvatar": "/npm-avatar/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdmF0YXJVUkwiOiJodHRwczovL3MuZ3JhdmF0YXIuY29tL2F2YXRhci9jM2ZjNzM3MGJjMDk1MWZiYTk0NGI3YjhjYWM1YjljYz9zaXplPTUwJmRlZmF1bHQ9cmV0cm8ifQ.J2Uu8Dfsu7sVPjnVTSTjbFO0Lenbufv6l9a3m3OrJNc" - }, - { - "name": "ronag", - "email": "ronagy@icloud.com", - "npmAvatar": "/npm-avatar/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdmF0YXJVUkwiOiJodHRwczovL3MuZ3JhdmF0YXIuY29tL2F2YXRhci9kZDBjNDc0YzQwMTYyNTFiMzUyYmQ3MTExMjU1MTMxND9zaXplPTUwJmRlZmF1bHQ9cmV0cm8ifQ.oAkv4YQXyWUdFDQGvyKz7jfgXvXV9D-pLZxlDvWoYuE" - }, - { - "name": "ethan_arrowood", - "email": "ethan@arrowood.dev", - "npmAvatar": "/npm-avatar/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdmF0YXJVUkwiOiJodHRwczovL3MuZ3JhdmF0YXIuY29tL2F2YXRhci9jNTgyMDZjZGVkN2I1N2ZjMWNlMGVhZDRmZjQzZTU2MT9zaXplPTUwJmRlZmF1bHQ9cmV0cm8ifQ.gMXL8-kt8M6zfmmygti6RYuWABM1MboI-VcpcOJByjo" - } - ], - "publishers": [ - { - "name": "matteo.collina", - "email": "hello@matteocollina.com", - "version": "7.10.0", - "at": "2025-05-20T07:19:22.524Z", - "npmAvatar": "/npm-avatar/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdmF0YXJVUkwiOiJodHRwczovL3MuZ3JhdmF0YXIuY29tL2F2YXRhci9jM2ZjNzM3MGJjMDk1MWZiYTk0NGI3YjhjYWM1YjljYz9zaXplPTUwJmRlZmF1bHQ9cmV0cm8ifQ.J2Uu8Dfsu7sVPjnVTSTjbFO0Lenbufv6l9a3m3OrJNc" - }, - { - "name": "ronag", - "email": "ronagy@icloud.com", - "version": "6.20.1", - "at": "2024-10-14T12:19:28.069Z", - "npmAvatar": "/npm-avatar/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdmF0YXJVUkwiOiJodHRwczovL3MuZ3JhdmF0YXIuY29tL2F2YXRhci9kZDBjNDc0YzQwMTYyNTFiMzUyYmQ3MTExMjU1MTMxND9zaXplPTUwJmRlZmF1bHQ9cmV0cm8ifQ.oAkv4YQXyWUdFDQGvyKz7jfgXvXV9D-pLZxlDvWoYuE" - }, - { - "name": "ethan_arrowood", - "email": "ethan@arrowood.dev", - "version": "5.26.3", - "at": "2023-10-11T19:12:36.882Z", - "npmAvatar": "/npm-avatar/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdmF0YXJVUkwiOiJodHRwczovL3MuZ3JhdmF0YXIuY29tL2F2YXRhci9jNTgyMDZjZGVkN2I1N2ZjMWNlMGVhZDRmZjQzZTU2MT9zaXplPTUwJmRlZmF1bHQ9cmV0cm8ifQ.gMXL8-kt8M6zfmmygti6RYuWABM1MboI-VcpcOJByjo" - } - ], - "integrity": { - "7.10.0": "6cd313748e3ddd158436a9cb313f5b301a8d7d8a" - } - } - }, - "lru-cache": { - "versions": { - "11.1.0": { - "id": 4, - "usedBy": { - "@openally/httpie": "1.0.0" - }, - "isDevDependency": false, - "existOnRemoteRegistry": true, - "flags": [ - "hasMinifiedCode", - "hasManyPublishers" - ], - "warnings": [], - "dependencyCount": 0, - "gitUrl": null, - "alias": {}, - "description": "A cache object that deletes the least-recently-used items.", - "size": 819995, - "author": { - "name": "Isaac Z. Schlueter", - "email": "i@izs.me" - }, - "engines": { - "node": "20 || >=22" - }, - "scripts": { - "build": "npm run prepare", - "prepare": "tshy && bash fixup.sh", - "pretest": "npm run prepare", - "presnap": "npm run prepare", - "test": "tap", - "snap": "tap", - "preversion": "npm test", - "postversion": "npm publish", - "prepublishOnly": "git push origin --follow-tags", - "format": "prettier --write .", - "typedoc": "typedoc --tsconfig ./.tshy/esm.json ./src/*.ts", - "benchmark-results-typedoc": "bash scripts/benchmark-results-typedoc.sh", - "prebenchmark": "npm run prepare", - "benchmark": "make -C benchmark", - "preprofile": "npm run prepare", - "profile": "make -C benchmark profile" - }, - "licenses": [ - { - "licenses": { - "ISC": "https://spdx.org/licenses/ISC.html#licenseText" - }, - "spdx": { - "osi": true, - "fsf": true, - "fsfAndOsi": true, - "includesDeprecated": false - }, - "fileName": "package.json" - }, - { - "licenses": { - "ISC": "https://spdx.org/licenses/ISC.html#licenseText" - }, - "spdx": { - "osi": true, - "fsf": true, - "fsfAndOsi": true, - "includesDeprecated": false - }, - "fileName": "LICENSE" - } - ], - "uniqueLicenseIds": [ - "ISC" - ], - "composition": { - "extensions": [ - ".ts", - ".map", - ".js", - ".json", - "", - ".md" - ], - "files": [ - "LICENSE", - "README.md", - "dist\\commonjs\\index.d.ts", - "dist\\commonjs\\index.d.ts.map", - "dist\\commonjs\\index.js", - "dist\\commonjs\\index.js.map", - "dist\\commonjs\\index.min.js", - "dist\\commonjs\\index.min.js.map", - "dist\\commonjs\\package.json", - "dist\\esm\\index.d.ts", - "dist\\esm\\index.d.ts.map", - "dist\\esm\\index.js", - "dist\\esm\\index.js.map", - "dist\\esm\\index.min.js", - "dist\\esm\\index.min.js.map", - "dist\\esm\\package.json", - "package.json" - ], - "minified": [ - "dist\\commonjs\\index.min.js", - "dist\\esm\\index.min.js" - ], - "unused": [], - "missing": [], - "required_files": [], - "required_nodejs": [], - "required_thirdparty": [], - "required_subpath": {} - }, - "repository": { - "type": "git", - "url": "git://github.com/isaacs/node-lru-cache.git" - }, - "integrity": "3349f1325b6de04cb88ccbd42728afaa69a2eced", - "links": { - "npm": "https://www.npmjs.com/package/lru-cache/v/11.1.0", - "homepage": "https://github.com/isaacs/node-lru-cache#readme", - "repository": "https://github.com/isaacs/node-lru-cache" - } - } - }, - "vulnerabilities": [], - "metadata": { - "author": { - "name": "Isaac Z. Schlueter", - "email": "i@izs.me", - "npmAvatar": "/npm-avatar/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdmF0YXJVUkwiOiJodHRwczovL3MuZ3JhdmF0YXIuY29tL2F2YXRhci83M2EyYjI0ZGFlY2I5NzZhZjgxZTAxMGI3YTNjZTNjNj9zaXplPTUwJmRlZmF1bHQ9cmV0cm8ifQ.ZFhE1nM8V3YD3hmGM9kkVTinxFMbl1fdyav5i1R7LY0" - }, - "homepage": "https://github.com/isaacs/node-lru-cache#readme", - "publishedCount": 144, - "lastVersion": "11.1.0", - "lastUpdateAt": "2025-03-24T15:14:18.553Z", - "hasReceivedUpdateInOneYear": true, - "hasManyPublishers": true, - "hasChangedAuthor": false, - "maintainers": [ - { - "name": "isaacs", - "email": "i@izs.me", - "npmAvatar": "/npm-avatar/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdmF0YXJVUkwiOiJodHRwczovL3MuZ3JhdmF0YXIuY29tL2F2YXRhci83M2EyYjI0ZGFlY2I5NzZhZjgxZTAxMGI3YTNjZTNjNj9zaXplPTUwJmRlZmF1bHQ9cmV0cm8ifQ.ZFhE1nM8V3YD3hmGM9kkVTinxFMbl1fdyav5i1R7LY0" - } - ], - "publishers": [ - { - "name": "isaacs", - "email": "i@izs.me", - "version": "11.1.0", - "at": "2025-03-24T15:14:18.553Z", - "npmAvatar": "/npm-avatar/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdmF0YXJVUkwiOiJodHRwczovL3MuZ3JhdmF0YXIuY29tL2F2YXRhci83M2EyYjI0ZGFlY2I5NzZhZjgxZTAxMGI3YTNjZTNjNj9zaXplPTUwJmRlZmF1bHQ9cmV0cm8ifQ.ZFhE1nM8V3YD3hmGM9kkVTinxFMbl1fdyav5i1R7LY0" - } - ], - "integrity": { - "11.1.0": "3349f1325b6de04cb88ccbd42728afaa69a2eced" - } - } - }, - "@openally/result": { - "versions": { - "1.3.0": { - "id": 5, - "usedBy": { - "@openally/httpie": "1.0.0" - }, - "isDevDependency": false, - "existOnRemoteRegistry": true, - "flags": [ - "hasManyPublishers" - ], - "warnings": [], - "dependencyCount": 0, - "gitUrl": null, - "alias": {}, - "description": "Another inspired Rust's Result implementation.", - "size": 19407, - "author": { - "name": "GENTILHOMME Thomas", - "email": "gentilhomme.thomas@gmail.com" - }, - "engines": { - "node": ">=16.9.x" - }, - "scripts": { - "build": "tsup src/index.ts --format cjs,esm --dts --clean", - "prepublishOnly": "npm run build", - "test": "glob -c \"tsx --test\" \"./test/**/*.spec.ts\"", - "coverage": "c8 -r html npm test", - "lint": "cross-env eslint src/**/*.ts" - }, - "licenses": [ - { - "licenses": { - "MIT": "https://spdx.org/licenses/MIT.html#licenseText" - }, - "spdx": { - "osi": true, - "fsf": true, - "fsfAndOsi": true, - "includesDeprecated": false - }, - "fileName": "package.json" - } - ], - "uniqueLicenseIds": [ - "MIT" - ], - "composition": { - "extensions": [ - ".mts", - ".ts", - ".js", - ".mjs", - ".json", - ".md" - ], - "files": [ - "README.md", - "dist\\index.d.mts", - "dist\\index.d.ts", - "dist\\index.js", - "dist\\index.mjs", - "package.json" - ], - "minified": [], - "unused": [], - "missing": [], - "required_files": [], - "required_nodejs": [], - "required_thirdparty": [], - "required_subpath": {} - }, - "integrity": "79f2d1df46738fb5bcdd48afb6a6fe3e260666fd", - "links": { - "npm": "https://www.npmjs.com/package/@openally/result/v/1.3.0", - "homepage": null, - "repository": null - } - } - }, - "vulnerabilities": [], - "metadata": { - "author": { - "name": "GENTILHOMME Thomas", - "email": "gentilhomme.thomas@gmail.com", - "npmAvatar": "/npm-avatar/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdmF0YXJVUkwiOiJodHRwczovL3MuZ3JhdmF0YXIuY29tL2F2YXRhci8xY2U2ODM4ZDU0ZGQ1NGNiNWM3NTZhZTY4YmNiYjQ1Yj9zaXplPTUwJmRlZmF1bHQ9cmV0cm8ifQ.zuCk5kxPtbbn7N259ilOmYpLx58-C5V2dkpie8Q-TVQ" - }, - "homepage": null, - "publishedCount": 5, - "lastVersion": "1.3.0", - "lastUpdateAt": "2024-08-07T18:30:41.149Z", - "hasReceivedUpdateInOneYear": true, - "hasManyPublishers": true, - "hasChangedAuthor": false, - "maintainers": [ - { - "name": "pierred", - "email": "pierredemailly.pro@gmail.com", - "npmAvatar": "/npm-avatar/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdmF0YXJVUkwiOiJodHRwczovL3MuZ3JhdmF0YXIuY29tL2F2YXRhci8yODcyNmM4NmY4ZjAwOGM0YTk5NjYwMDQ0YjY1NmE3Yj9zaXplPTUwJmRlZmF1bHQ9cmV0cm8ifQ.h_8Lqq_v8bwfjKenZivmoRV9DKlAY0hc0ojgpnAyg4s" - }, - { - "name": "fraxken", - "email": "gentilhomme.thomas@gmail.com", - "npmAvatar": "/npm-avatar/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdmF0YXJVUkwiOiJodHRwczovL3MuZ3JhdmF0YXIuY29tL2F2YXRhci8xY2U2ODM4ZDU0ZGQ1NGNiNWM3NTZhZTY4YmNiYjQ1Yj9zaXplPTUwJmRlZmF1bHQ9cmV0cm8ifQ.zuCk5kxPtbbn7N259ilOmYpLx58-C5V2dkpie8Q-TVQ" - } - ], - "publishers": [ - { - "name": "fraxken", - "email": "gentilhomme.thomas@gmail.com", - "version": "1.3.0", - "at": "2024-08-07T18:30:41.149Z", - "npmAvatar": "/npm-avatar/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdmF0YXJVUkwiOiJodHRwczovL3MuZ3JhdmF0YXIuY29tL2F2YXRhci8xY2U2ODM4ZDU0ZGQ1NGNiNWM3NTZhZTY4YmNiYjQ1Yj9zaXplPTUwJmRlZmF1bHQ9cmV0cm8ifQ.zuCk5kxPtbbn7N259ilOmYpLx58-C5V2dkpie8Q-TVQ" - } - ], - "integrity": { - "1.3.0": "79f2d1df46738fb5bcdd48afb6a6fe3e260666fd" - } - } - }, - "@openally/httpie": { - "versions": { - "1.0.0": { - "id": 0, - "usedBy": {}, - "isDevDependency": false, - "existOnRemoteRegistry": false, - "flags": [ - "hasDependencies" - ], - "warnings": [], - "dependencyCount": 5, - "gitUrl": null, - "alias": {}, - "description": "", - "size": 0, - "author": { - "name": "GENTILHOMME Thomas", - "email": "gentilhomme.thomas@gmail.com" - }, - "engines": {}, - "scripts": {}, - "licenses": [], - "uniqueLicenseIds": [], - "composition": { - "extensions": [], - "files": [], - "minified": [], - "unused": [], - "missing": [], - "required_files": [], - "required_nodejs": [], - "required_thirdparty": [], - "required_subpath": [] - }, - "links": { - "npm": null, - "homepage": "https://github.com/OpenAlly/httpie#readme", - "repository": "https://github.com/OpenAlly/httpie" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/OpenAlly/httpie.git" - } - } - }, - "vulnerabilities": [], - "metadata": { - "publishedCount": 0, - "lastUpdateAt": "2025-06-01T11:39:08.578Z", - "lastVersion": "N/A", - "hasChangedAuthor": false, - "hasManyPublishers": false, - "hasReceivedUpdateInOneYear": true, - "homepage": "https://github.com/OpenAlly/httpie#readme", - "author": { - "name": "GENTILHOMME Thomas", - "email": "gentilhomme.thomas@gmail.com" - }, - "publishers": [], - "maintainers": [], - "integrity": {} - } - } - } -} \ No newline at end of file From 876f01b80becb67f73b2cfcbe714d819270f390d Mon Sep 17 00:00:00 2001 From: fraxken Date: Mon, 25 Aug 2025 13:39:15 +0200 Subject: [PATCH 09/12] chore(package): securize tests & publish --- package.json | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 4b7da1a..29405fa 100644 --- a/package.json +++ b/package.json @@ -8,8 +8,10 @@ "scripts": { "build": "tsc", "lint": "eslint src test", - "prepublishOnly": "npm run build", - "test": "tsx --test ./**/*.test.ts" + "prepublishOnly": "npm run build && npm run test-types", + "test-only": "tsx --test ./**/*.test.ts", + "test-types": "attw --pack . --profile esm-only", + "test": "c8 -r html npm run test-only && npm run test-types" }, "repository": { "type": "git", @@ -29,12 +31,14 @@ }, "homepage": "https://github.com/OpenAlly/httpie#readme", "devDependencies": { + "@arethetypeswrong/cli": "^0.18.2", "@openally/config.eslint": "^2.2.0", "@openally/config.typescript": "^1.1.0", "@types/content-type": "^1.1.9", "@types/lru-cache": "^7.10.9", "@types/node": "^24.3.0", "@types/statuses": "^2.0.6", + "c8": "^10.1.3", "fastify": "^5.5.0", "is-html": "^3.1.0", "p-ratelimit": "^1.0.1", From 79e7bf207cb1959f9fd91f8e5b61a92f2fc5401e Mon Sep 17 00:00:00 2001 From: fraxken Date: Thu, 23 Oct 2025 17:46:05 +0200 Subject: [PATCH 10/12] chore: install dependencies with --ignore-scripts flag --- .github/workflows/node.js.yml | 4 ++-- .npmrc | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index 414589e..9ef83d0 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -19,7 +19,7 @@ jobs: strategy: matrix: - node-version: [22.x, 24.x] + node-version: [24.x] # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ steps: @@ -33,6 +33,6 @@ jobs: uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: ${{ matrix.node-version }} - - run: npm i + - run: npm i --ignore-scripts - run: npm run build - run: npm test diff --git a/.npmrc b/.npmrc index 43c97e7..304fb65 100644 --- a/.npmrc +++ b/.npmrc @@ -1 +1,3 @@ package-lock=false +ignore-scripts=true +save-exact=true From e74185a8fd7ba08466c36b0a7614b0d545601960 Mon Sep 17 00:00:00 2001 From: fraxken Date: Thu, 23 Oct 2025 17:46:58 +0200 Subject: [PATCH 11/12] chore(dependabot): cool-down to 5 days --- .github/dependabot.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index d4abe09..8b7deb1 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,6 +4,8 @@ updates: directory: / schedule: interval: monthly + cooldown: + default-days: 5 groups: github-actions: patterns: @@ -14,6 +16,8 @@ updates: versioning-strategy: widen schedule: interval: weekly + cooldown: + default-days: 5 groups: dependencies: dependency-type: "production" From 86c08ff3edd216c956f978f0970abe881d9d582f Mon Sep 17 00:00:00 2001 From: fraxken Date: Thu, 23 Oct 2025 17:49:34 +0200 Subject: [PATCH 12/12] chore: update dependencies --- package.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 29405fa..b9208fc 100644 --- a/package.json +++ b/package.json @@ -33,23 +33,23 @@ "devDependencies": { "@arethetypeswrong/cli": "^0.18.2", "@openally/config.eslint": "^2.2.0", - "@openally/config.typescript": "^1.1.0", + "@openally/config.typescript": "1.2.1", "@types/content-type": "^1.1.9", "@types/lru-cache": "^7.10.9", - "@types/node": "^24.3.0", + "@types/node": "24.9.1", "@types/statuses": "^2.0.6", "c8": "^10.1.3", - "fastify": "^5.5.0", - "is-html": "^3.1.0", + "fastify": "5.6.1", + "is-html": "3.2.0", "p-ratelimit": "^1.0.1", - "tsx": "^4.20.5", - "typescript": "^5.9.2" + "tsx": "4.20.6", + "typescript": "5.9.3" }, "dependencies": { "@openally/result": "^1.2.1", "content-type": "^1.0.5", - "lru-cache": "^11.0.0", + "lru-cache": "11.2.2", "statuses": "^2.0.2", - "undici": "^7.15.0" + "undici": "7.16.0" } }

Y&fFhJz+VLG4;B=LWu1*hCZ9p?(%#-MT>Z+lD*fF;uOoF8m|_AxRtO(d_U7sX8Z#CqS|$u=G6)Z31W_k;6HjhtSJd5qLBRQ?>(*<^-k9> z?k)y+uZ4{?rpGHMo#}n+6&B*dixZL*zx`$tGW#JW0V*Ag;tlQ}t6I7_^wUNw@j39) zVwynmNwc*`)qMl>Md4Hmyy|sgFXQ%MvNnl-Z7+E!oEmMQVzT)So(^E#qS6ZRp;!SL z-AeLSg5KL}b7jMsJ)pKT(4cCvpPZBIYrd}C1xgwIty-Zbk!j)jyrJCtW)bMH`4QuD3VnrRL3qoV-s-&`pWdxo>0d*~6C% z>Z(CnWm19jx~jJmUJLm}?n|X5-{qqdEC)7;#C861mhf|e zQt3TJIZruYAh|8)J~)wC6We}HZAPE&Hn}TH8h1Iiz9!=Iz8^9B7Iu?B38|@7bj+gA z@YWO6c#WgpMIWf<3QGip9=u4@cO_?;c&dP*f;IfP!bsx74x^4?+2b+Zi^7Wrz}KQf z+Q*&*?FX_ni9;J-s`EWNHJj>XZ1Tn~`<;P4^T}_&8HGw(1aG*{aHu+96-alts#t<3 zVHv}-^q9p0UV5ClWz){L%{G`c{-(j_D%**tiCl$});>ENHrLnFw-p~?R-XGnkoS2L zSE$m-#yD(8ZpD@~EmUzZbsk>hN<1h(YPNlM96 zoW?SjwlY)UpGfjh0M~kcjpphyrTPz;bO8C`9y7KLCqWLS>ERnrq#~=j<~pwk(iVKt z#OJV?lFpvnkTX`g?lH}v*$Vaj@f z+MEK!%by>zYR}u~mAsm$ePrpxvbs#y1|S+m0m=j0BG15y9rTnxaI}fvp%bZ(5_B85 zJTZfTvO?LU>@Q@VfKN8)+2N#u3PKrbz!y79J0ier|qHrpnc8>ygr@opKQd|#79eHVR2 zRKWfvaV-7BlD4#<8*!n%*z?#r0p47zo0lHOMs&o@1aU*GSYlhDoY|zs^LouSaLHwYdgkU_A@95cv>?YkeQX|>@3BN<=oyf+=-n1-i(OeEhQ&zm+Y1mMas@{K#uJEWjUP!K!cqkhrZ7}pIdLwD>3+8 z(X)e8z8SSMHT*&}G*u zk*zI<=)=eg(4#~$h8{vjo%{$nAg@tI+d?RPl>YYi@Gi&!SO$4iOF@LNpMc04^gs~F ze(s5HRKGSv8^$e>?5CC)X>zFO!$?Pq-eEUb3KK7?SCab0D83n3LZi z@!*pldN>(H_GBlwQFiC99ueV7&0a1$n};OYw{&8q+SF@;CJ zaAwt{M2$W8Sc5;#ZBZyK;Ljr&5X`#Yuz_^+H7uVrK8kEUu!i^_;=XADn*rS^$=maN zS5W^MQDUUTq@E~42NNqi5(`6~{fndI&;-i_yT6&Pe7HfmA}d%;O56AM(t3$jdYNo> z#k)o(dQSO3dP|Vic+WwM_BgMtv`2$VJ^lG9Eaa?s^-M~T&G((mlUQ<;!9N}*L-WM< z!SmK8zB{-zOeVw^zm^X`6uzrXnH`=6tRwOUyI&mpYXV{9$fwBZJxPMG2W~F>X0%=L z819OUB)Q$_K?x9bRt#}#`I#@_-DHPFR$uXQs&wfim-0VdiY5%1n!cAz;u>ggY~LHt zeW5(|b5Fk*w6tmh_CL{nls#5qqVl7?%0%O$zBagj0aSW|UKo*>6UuBzx{J979J?a# zJ;T*_!U2IC@s8FLl5vY6uW%d8uybIKRrNT_a!GKw8FcedatnzIMSV5ltS-QO!@l00JCN-yfiFHuGq>YWF=2bwD#PJV;z0C2pv#MXB=ciT7jPaoZdU z`23C4B)$Pz$~So5vb!3`C#a!4AOr$X7mZ$k`&WV@A%2GoDCoaWj!W>{iTs0;jrBL0 zOW%8nto_vob{~&z;5frFeqxlD>7b&6Apro?bhTP0%Pwi$*G|+I0vC3Rf(L-jMm+ts z3YUTvC7p!VJIPhj*^7?J0`7)Z?Hlt@5w8l6WvySHeoKl6Tbqv8lk`agvub*+gx7(f zmsP=u8dU++R!i6>`{XE4F7g?k(*Rr&MIozAlGY$kw-fN`+6cpA#*3a%6-fSLutLlat?RK<>IgTM9#Gt#L;flbeYt zP@bJS{-cf`tMHRM^NWi~;plYXS@RP>t$x#1X54tqB8SYHBrh^86 zxPcpbDZ{B0N+E$_FV;#OgYomtC2^g6mIdgJO@_$ZP=b0HXkO`BBbC7&;Qn4y?~)#C zWq8LEdA3W^RPS!Ad*P_Zc90R|>ZLK^kHz@OMY9K;|9M135_5iD8@22yDC6EW6pD;o zaYL9q%Y_@uZrZZquj&#uZUE20%NzP0mE6gCDxJ=%3IKd1)jzn(4QenbDlx!VY};jo zi~9n6pHhJgqkkf;;55roMU-tLNR2jqyS2|XX+GfId|kq8XBVkx43#xhE#6PYWrvO8 z@Blj*M#Y5({7u;>Nbi!=kvDrn*qiZrN7L=% z$CkSW&z#_$sB%wZw)V^O?cj>{CYVkJuR#T;a|xaA$nNNin%kekP#-`FmQ8dmhW>~j zm%#xUjLeCho0q`er|9MKJzSDPSejK%c&51j*=}0F3;USRpUo0P)NxWWW|GBN4vK)i zZI#!NR0bp~wB-xn?=|MS(2u5Tl}~eQZO0AKYc<|igzcy?oP_&*AYpqbv&U`ul!yMk z;kNLO-3_Oy(~T23{oFVZbZ?7vPqkDXaTm0*ea!5f&zJsp}@t#&q(8V zp;hadWO!%+=Bi(5p+rk~3g$v1`9DYwZlTWd0$CPRrNcv+AxuC`rnYJR(9L?8#`DIy zg{j+>Yb8rCwiEl#PJ6zE^tD8taIy^IQ3&L?t8}QezdWPiR(Zdwq*JcbUL!RTgc zShmDSR}>>>X#v=IY;1-zm6Qq|SJGQ6y-_)E`$3vFn@={9HW`@Z?*>2^}m z)X0HDlzWs8t)vd=JVt{6JpQGciK;5Q>~>;}#o3LEUOV#Ecg1b*Biyp}1Q6GAMe(Deq;bO;x8c6c7J0@61a2Fl zDnQoks{#gqP_r{huNUI%-wU~-|H9?@%vJES$)n6R09xQR_SpyLkau7;S(J`h62&N; zLMftxM#*4<{6~%Z;QXN&$iTsa%rjuS$)y_@@3T;n4EPBq7mIx1ny$7R(b7PBYx~#~ zcmR}D6U`^k4K_)7LB}4RO!yuy+@#rD&a*t&oos+Pa+d+9vELZ!AEntd;Otf2ir&$b z{=4sL|KQEK0NHzztBERxX}ubA}&aNI;h`Rm0!L=-f5P# zY7hFxht+;gi-ve(OTflo1JN4JpmRIH3xMt0ys`a^_u%UTF4d`@_E;GT~ec)Jk`vxfm8c4pgc)>t5pag6;ZV(0>Mdl#?459R5MZ;~(^{o-Zr- zcWY_>#pd6jgOBIY?3CU3cd~{klVbDq;h*j-fG06d>>T=pTLkI1mco{)d}p#GI8@Dl z5GwBXSKdqq$pJpg`|s+i?8gb7PlIxWk^#RMX9j{XSn4~d-m~O>r_a=GZMMZ)5qYS3 zcd}zLG4I9xNM3JWBrCxTu==p?@r{|Zid1E{YO z6g4Bv#bSW4)9wD_jy}VqK@D{^T#Wchor<|2CN_r%eZ`F91RmL+U7+YVlgDaTrf&h# z|7zyzztE8RJf*`%-%1tG2;UzNvJ2Jp&o;gRSjtUVOFg^6wCEIAIg2sVpvYI!XzIa* z-Dv5o&N38EP*}`*v-4l)Le|C%pxfmh&`0gcHUKRG8V{u|+t?;*a0arj(Ygxm9|ZsU zY!bf)f%Z~oyB=qpsNVZY{S`xVQoE0)?5~q3LVsA1^RU^=Co(<+RIL=)YXmU2<}V7R zrm8@h+S_jM4jdGoX2gK_pCn>RO0j*Af3hO>E;!rvPRwwWfYs0ouvN)YJ79t|d9lbH zEpCLop7oyd-8N$%bJ^yA46frvw)-OEfOT_tUqzL4UXnR4>mk0^0{gF1Oa!P z<@F^jc0~HHzxT}Te~Y+Dcz+AVx*?LYx-F8ss}Kij_bqQxq?lpbq^UYkN%-d1X=G;2 z0FdYw9=ikwrBVzF{|$bN%oMKO?#~+MQ-(&WqEHqEiK0KdECAE6%dia9$;;dJKFg+? zk@zqeiWSvuS9HpuKgTq-Fi?oD2F_#Y4TjQ!<%gOWy7mJ1SvH_N;ysb44`1k zq}{@NUr}h$PQ{-&gf08&KVu6Onx zRuV}ur8fQo^2GW*X5dxl65}~e5pD%ch+FZhI+l?=mX7@+)TenLD*Bm)xi@%8t{cZ_ zbA@1vIq%0h=E90$FKs$$ZR0WV)hXPli|CNenR?Ak2mnz0v6Y6?N77Tp5ts(AXY+Nf z{Z0pe$8!nK(xzZ!d%#i*?vAn_U%G){@3L%UOxubx>@P|ny`CwdHR4{e?88dl-4kdlt3XzX}6nE<@u12hWNP z89U$6w}QP6z;oE-mYGZ9^YQ@q5YNHNu>h;=6xzYm)zmS`XF4P5WB3Hip`gp*7`5AS zX6E-_f*&#`sLsC#ev=-IOj`Y7)ZuQ_aY9e#LY5Aid9#k~u+S&7l)#!4XV3JPi*8tT z3L9>?X!Iu-*vqkJ3sz3>ngn3otwob78MC!ObFt~ZTf!RPj8y~>bf=qFD~ z!VlOEKAz08nQzHX{+<=BYyFSBHB{xV=g;POC|GU-q+|!~z4x}YijF#v{NRecMW@^o z3DINeZ(S?Ip*bCLVkzp^K|EgWP$$Dsu(i%VM9h$m+Oth*y9Vo!%I#C z%}8Q?J2$M1kpUx$=*I-@zAvllIozv{#qm?uf>2Cz#o6T&iepdBgsI_tuG;%Jz$6(*Bc_Iwj_ zp)5K*byjiCXUn@n=)&s#CW*BQRch0w?!ikKhv#u_Cv$huuntB>+(#jm; zaNg;DrRLe6=h+v5EZTU4Ib5v-vqRw+&)oWmjk>u&zZrb89zmtt#kP099KFMBL4LiF z7Dc#VfQb;9?~S98EI{e(+_-JNB6|`2o8z?ytg?P#Wpp;Rg@smfs(Zy+c#{$7bD!C# z%QVvlY{s`v_4t`{;SgVrj8_-q5Pc(lg3 z4q{YnP1-M4Z5~W4RK|#gXTT)wc3s^_yXG#m#a_yR*~SjapcZWPd{^!=EWb=3HiJ)C z&10Tc%;Qq= zO8WPnEJ2+=yfFbNG0p-~=y=-X41H6egt2_}x5sV?d#lQylGtZd%f-~F(fmtp7Tcs+ zhI*1&jMq?e_jFzAYQBuTKuvGAyx+g1!E66MKX)bJ(%>$Qj* z50dyZ&OQ9*`L272SkB*oW&B+Vaw*ZN=>T^$HkYvecV$I$gu5%}y$D6MoN2MkRFw!M zbuU#EpX=;^97FGO^Acg?dGO^5#u)aZFVnoRs`@u86AF*}f3wVD2qFn;Pxi0cW25|X zws}ZicI@^hDcXt8g#;q?2uo&A(xD4hVIX@v6{NhyJSTbLU*(B;N5nQwWof)$^qcRR zVAizrtH^Bi(kk)&U8UDjZ$4@?8kJ`yCY>$uM!av)Z#7R&l+xc;_UJ79y8w;qlLXbE z3Fq05AkOg%?EkK2Ct}X;c%_XQ>!){$>KGRq!Us0ol9f5zj$60fV48(yfzx)_7nlGc zO(vC?e@3#sTjY@MXT-xEOR8QMh5ik^iTmB|#mpeH3RVt{brgxE5XUG3H7gJx4)pC+|{>>f#Pn%-%fNxih{Ep9F zdfp;;*{`BoT6pB0_8icPSfnxf;~Jd`xaXCBgEc_XuFSy{b9-OfPS;_(mdl1sOYput z>uf&tNMX3*38YvvEBF)5zw0xnw7SiQG?Uy5N|vM?eV&cg97{s9<<9hT^@PPANwlWa zhTL)utcmCA&p~Ic@B6qE^M5Jo#b=yz%Z}&aa(X3EjP$r;#UpPmAt> ztpCpM26_{J0#Vx-z{1A!7)2TDPU)n+Hr_U*7`hyJq#A*iB3_G9E=CTucuOm6XUlNA zpL0uz-?&87gSmD`848Vgz!Du+7DW~uN`JWdXHrrRf_%Em#%MA&l}G+E#qT}EZ=w8C z!uH^5fKYAJ^@hPFJ|ltbnOzadwUMMMjx}dLzSr()aSTi&b1{L3e9wwFYb`022>NH& z29CWOw}0#w%dMA|ik5{R%m~x`nsILNe0=bb*->1Wg?jdyB|s+*cpulzf0H_?9s!m$ z=CXM>9}8xiC2cAHg}oD@U3Xz1FxTy&yg2nK8^H=%D}omTKF_1NpUSG_&Ny(a8Sib^ znD`>Q*+OVXw~x+4Wje{qp3D*d2ur7PZeruzE45JJY8Q5){e+8F#G$x1h~AzYYbePA zag#lpKQ^-5f*TI0pA}*R)xh&^s_XHziz8!28#4ZMxyB^Ml7TAa>S29T{l6dwK*DuD z_3uEOLZ)}y-S6MSWO2v=$)oxw%Z64pyA*^A7Wbj0x~@?Nkhz5;00^YEeMk#n%3xb0 zdGVMFc?xs((EHhlw%GtNdI_Ok%eG$*vzc88PetB=Tx<<8Lhcw7 zBYbj$c>XA}P`u3lyW4w^D)bMYGe7o;+OFJr-;lf%a6s!3XG^#RwO9Fme^t25gu}TD zhO6uP43n2dAtNlgp$XCj7l>-htbJ%& z(^|Z)x3!n1oFDW#TgqEL7J&Yq!gI<3w*+nlD`WP1oWe9)0Fw0U-S`c^#>bH5i^Z6@ zYn*Q`^eVd_+yCVo)4)kg%9*mrJ#xq2t%(ivgX1jxb)C*&QdW2u(zrbJ2*1=z-$l@W!<5x>*=T3g9MGDBZ*Q6d zsJJf7yPcp?+@sxfD#e-FXOWNWAb~(z`RL;DjOUSkBgzzMGbR1>lngTbC4(OTGWahf za<3n_OaYu z?HO8n-;Q@)%I=p(U!*P48uwhF+w&i%)?+{SlZw6X`5NMs7UCj3A*9WP^f^C+)3Us0 zzS;2Y=#A@-gCMu|3NuT8bsX9Xt4>2OLLH@Du5zM6v+#kcr|&^KdfqxP^ad(uY~sz` z+q%D{wiwZfySeXVa18}{23we$JgQLS9BmT0cyQlf`V#O2fC85O0+sqz$@$-G1IpVF zIKFF?RjYkjB$sr41Adr1-yR2=k-0s;d=`6N({amN>t36Z+qoqr*#@^TZg22Idjs(S zmPYOrn!W_UAQ2Xw6kbDcw%J^+Tu2J!kY@d@=mbE@+{X_$I(&8Wo5laClDBlg;Zgp3 z5`R-{CeY6hXCbGHH5EtY&8JnNZPObL4&9zr16R-?men3A=kn({EDLqnkH~2WT4=Vi z*sZ+msr=&PTVAy%DMwEnVR z9!Y?BEN0*T2e()NN)Cv9tZ`#wSu$vWl?8F-@LX1DvfLSHZ==Y-1zop0-psuz!*X9G z3(*rP#Us9`fH~R`$F>V|LhTEcArsD3fgBsOZ)#c{4l~bvV33pfyRPF_)iuvKisfmF zjnb{vj68*)mAiQ!rb=AZ=eB3<72CgHpTTwx-L!nxGW#IewEwnS^upCqp;s9?rh+UE z%iaB2f|gEZoqbZ8h6tjXv*xXXs}s`oDeQdIdJ;(4Pe1PWe8VDTywUBN+fiXaBdTj6 zW^GQ0u~dXVc`nl3K+1=28kpd~Ehd92%zw(*hK)tVj(fH7d50%}~;01(opEU`Q^Q`$#L?*8ibFcrQIDYYsF+bQ_l4_`nll%CXWw+}Kc6B*i zDGeO6&N|;dEv=JscJyhbje5>_i@m|7@#EoM_5GFO&jsVa0R;|^n7y(B{H|UzIK=&> zSzCwb1Gv}W+J;ji!08p(hs1vPcLT`yD+LsGY*q9qNp%z8^>x&q{?;1^xUcQ!7M5?W zk#)*#jEAd~Kf2{|{O8^?>7NH{k8&KS+I}Hs{VApY>D>*<*^so| znhhm0BQ@)X&|T-tBletKidd>jZt=@0`C}HbS+{{H$S2Kr^+O^SL=sdi#B~{l->C2I zorBt#Vt-OgI=rVZiT$q#8{eUa?{U-aH$NBwg!RsO;=6Io#O`CCbQ{6){10w1)7Sfq z_c`wH=)o3yrL(S}bK#}3xzq!ZP3F2p zfJsV>&;oC!gN693SN)Uy(xkur1C_c@E7!k!7SfXG}dW;|U|bt^xA+PM$R za)rYthJOvIW3P=MU}%N7gE<9}Taj6%F-Orr8QxNK3;J;da})7??-5AA2_SzzM%|3i zNZ~=ETCkAvGM%kCp~fz36Q8(d-r`#?9+*et{g@R_UQi#9V~xW% zbt~_9gXhHrYQ7BLD=*+?Xbk=1%bNLw`N2|IK^s-bpR-nNB6k(WZud8Wl0F6*du3hf zRVEi<|5^eb0KdYr`;#|3Pvx=;>C^GP3S~}SG=E%YJ+;Wp9J(mOWkontd-z(<(X=sL z-%-W1zXG4MTxG}3NAGyf?f$ZEZ}misX9o@PcGprADs?HX`Jxl{d&Z`~MC?g^eV~u7 zfnx1L{_07jUX3LaHYx02IN|Rc4b)oCOuAk=cNb!N@`45l3T(WDtXsc#r@M=TC-T%R z^bJuKj3INnHl9b0`&g?~UMu33-({+wv^U%IdrbI%PDycbx9xAQ{n}_QMaS;jvG+OG zQ{3on8pqoSQzo|lC-Umz>?QvOa7UO8avUx(L{lG;NVr10m(0$a`ALlargY-d$cgEx z_J@&sX|D)K0~ArDHO*`#CB?Jwz+j`i@RgRS<;nHz+R zQq4cy3Mu|-=k%S2p%H|Wzvx4IMG|^NC9iq6zpx1~aw4`mQkzd!7fv+)(i9@yW}6E; z&+vKUQieX({kS)?w+Sy2jD-7N_MFbW)g&{o3#a;VR~p6La*bc0)q^=*MR&JKC9Y3< z&)gO}NNJ_DHlWt#HE_J>s%6om^bMT7&TtES zydS{;SC^nSLtnH~@4Ix0niC)Fertm=gJ>A758svaEC=S^NTwaTKxgapsS?P>5Fi^n zgj+)}r^z_(Nx~(T>SR(jPW5*&yOs%b9n1U)ea(kq(8z+J66W(DilJC05V3S6Dg2V# zsJ1ws30*-|IbHiJN2xAqw)tp`YHu5DAP-v23cBmW9I zfQJ4!ZPYvqhX+syp%e5yru&Peq1lBpU{k({daQVda|*xs9RTd)kTc3}t~my@s*>!i z7q%^g9&%Cv#Ez5oP3B#|!(s?MmNG0ChCI*%>?a3?ihHBpa-Kh6eyR^c^bon|sq6i+03u+LEWD{u1qQ}X&7iA? zJH7u3y3KzD-G^$$Y|Db)ee4JENMD5<>D*5T+2vyWU0E7zKg->Px6gxb6!a+F1HE)@ zTK&aXOg|tILL&+q@%=PQ-J3G^&wy_qw|W`XNN=v7+~t!9D^`Qx9o8WcT(Ge8U4@z3 zhnv=NWw;nNbK$EB?g-6FLp+G9heiPs= z8CQE@eDrBBo|~;{I)oS1(W&<|UzVD$Ab69QLu3lvtoy%#`vF}$&0fgkqr7hcFL2Z& z4*bvCnv_%2hfduHN3RIuPeq+1_(u~VeQ3As`Ud-}i}pW#%E#T_$3D?WalO&?+$!A{ z^A$|ztNcu`keppKdsaMX_O{J~af@DVU59xw*?qaMM}_F=l)8~=r$FSe!3=HbPWq(G za-ruut!2#@nmyCiN?POq;A6^N_`oG6)00&MtPO;ncOz-S$p-80V>@Q7L(M>5cKn-{ z>ja07{P82LMsGCB&=VZa{Xbc1(E9?n@mGx7);AO)*6`|kQmp9j(RZs zwJ0~C%SFCGRwfaK9lw6|k*|g55*3NmhJikV^M9#mBw$DYU3qKOylEjuc75UN&^z%w zs#fB2RA18cNi&|;y>>>PKlvVeSuKgj7;wh2?*nJ18|h9e1B}JtD(X-qk0je(7#f69 z432Zp*A4y$lDN*W6YPQoWIBMZr@{2fDoBbn$nQQlZ3!H9Y+y;-3rnbGL4e69%Vjr7 zZJE3b$4QHSyB*>0e2-To=u44%{(yo&unc_xg4Z4y zcd=NrWqnNN6}s(%jYc`WQ18pPvWb!4FV~YqijA}hDtrFLGPx~A;^Up}K^WN1wJ38T ztbybqz;{p+Ca?;2UE(~{O#k|p?C31;4w|(8H1V3HzHgQEw<`W@m*hXaz^Dx~NngAC zC|SGJd&j|whnj;Y-o4`b+P!y3dW20_kJ8ITHAH89cp1>o$vf+*(LVG!E_9aKOFg>r zlBo3vGIUna4j+Mf68!46mGB>$*A@twzzJOcvnyBR&lx67Ki`wTYS`k<09^$tWSo@b zNwq#*F<_@de+Lm;GD({wf1FB|RwT>7bRI-|44{Vw(@Zs^k96Ml@}*g`j>^a|CeO8x zB0G$nxwT4e6Rx4}38TMLy@F0%Kr8ZTs`TG8MC__PGPK`Fo55hwCp1L)TFraTs4E9I z_dnHMF3h-k;9N{~~8n``HDiwvW!dvOfA4mj2<@}g^^ zUa>?#U$*C^l+Si@0K78)yGoP%=NKzN^%=+g+Q+k@KemIXa2h2qf*073S$#Dw@&T4l zetu}>FHV!Eed#g2_)W546mUAhlRN8%SYXn$eNWXZlOVMJqr8+>F8aGuC8N8b>CA0R z_XT?>t32AeI-?S81Q=&j=+7Jg+$!PK_N25*%=jMW(;^~zQM`Z??RBNM+LRA8=iQ~a zA!}LzWT76hJI)4NNF1&+631M8aXJfJD4lN$i%Ust?cBgEeplA;ON=^s5 z%+A7{&&|`-SVz0+=&_t#C7_oTMiwjC4QGGoS4rnA{?XMXaCkeJj$Xh#ChVn%roQMi zT3>YQR4C+B=oO`DW`0PwwSPP11L$fD5d7YsU4pL^m{RA1x;Km0PpjoXM@Lmu(|-SD zfRQIIv@|`c+aDoSmtiX;uJus?Yz=1FTUkFX_uKl}pgEfj;t7(yS1d@959sz#xs80d z-&~$=3jM`G1sm@FCRNHNj~89uKtIZNYMo*)biv+|nJ_729+j&Ktw&Cxl_~^EYyP(C zj!<_`SE%;^qu?+`aWlNn#y-kKKZS1|sRM|L-cA@~ZKQhBrL!Zfs%73G)*V zO@30-CeT{FL32MoTxBkln%ZA7*?emNQmS}@?7N6=C@TTHeL65z)5-=Z0&`$B|#x{lb zLXDVZp3g69@qo0_G&Dq8w+Zf_u4V;kvhW(TG% zugt$NJ?+ERu=~G#luam;37ROYjUKr6;c87l)(1T*Y0Mc8nxFriDw5MSWk%c1g0)E= zgbgi~@mpw@;oBVUuBG?a4T9cUbJ*lq`Sxdmsl2G?cxnsm3gHIEE~9p`HU}NLF68+~ zMq9v?)qEwFH>s@{$qg$3Owv|`#~STMMR>=Zbu^0f{bswc4x}4aR8Cm_27P_znoK{AugZ(K^ufs5agUX zUW^UbHp#IT7H6!}DNJD)1W@(;nCOJ~KyhOpz|y>~vYC3I8!&ygho)A8#yA!({UPf@q0R9x>)*Uma zKVPcu!mZ4~u&u7Sn_z+l(TG1aVXNZzk)|wmqX?D#AAp;~ zSqBnaBgAV@3Jix|c3fL!2Brt|h66d_V*|ZIsUc?FH#q9Ui*k5th9p=lxWh7{gZ_GH1zDvSvOSCg z11H;K!+dW>ybuThF?NgR@6}Cf7^Jm6;6au2)j3Z=4o!&j7o~Qc^6?o-$+j73U%=-Y z{=}|8!o1F6s;p&4;~5)xg?Kjsv|)MPS-KoP)0|;owSH+(@%)r zIp@y2m6G(y$x@BLg-QDU?)Ho9N?~9olNPDRUxjvGd5;p7d#YPcdzO>$*HM}8-qi!Y z;K4_~u+2`bcFX~f5>byu&;NlDjhjTPI^xEH?6CL@V)^{e*M_pfg~BjIj0O0wd<}Br zOSv`g+J@>bTs8Q(Z|2guQjHZ-w|if_^?p7be6Dctxni3*spWgSVn4~Y(D|`o+Jtn! zEXl7O|EC!f7M%iDFYmH<7(DfDu6hzbLIh#N2M4H2 z#RQ5XyOP$F{~67-xe9GMvG8}h>)&E*9xlje=*?b`8i`dvhJ{IA4Sm>jp?8=|cxFlL z@5}RcWW~5#5qmmMjF>{7zRW4GBbihN{^9zFrb|IlMcto@d35Uo-8x@Gr&B`Yvv8LA zBy%+Xi}mPnf|8_2WEoaB&UxelMiW}V2aJR70ML*Pu4Hn->2ClAUT4(&3d}EIDi^Ru zH0nRHDgAYR2o{4QyHryMkq=3D^`NuEa{hQj>dnuD--%RtYYG{*Pmdl7`JEwTT94Pa zW$QdpMWL-56_=uSc_ClDYmwE_V-+La)-Dg+@htgCJiY$GrbLUTD;KhCDlXPg<;Cz4 zel$Lk2BUXrCM*(5BmM(PmQiSpv!}yaZIzu+Hw&Umzmo@2e*+9$ZfxEIyMX2S9op4v zWrWN+ToR8ixS&)ps$(sZX;9|rqVi~IKayufW)GneAJ;=KlqyP75!rEat-eTFFH0BG zEs-bdtXkZzztI7qH-;npgUgKRz|z_-kWmxK*0x=NglKIi{#fwZHUP)DW0eMDfj)ap z+VMOGtLllEFG>8G)=B`KL6Ommu@wNyE@3Uh5EseWEaegw_n;@)>h{cCer znaeH&MsX5+3efLH?`Qm^3mgJ7A2NC(6pRkiY=@a5ffK4Br-CS|&<$NP#LFL3+oyl%v243;qXycO!iLX&V`?i<7VFl z4;e?tk{Rwsr`uazE+w2^(J!ufdZ1-bq@JQ7iL3%*H*8e%pG1^PXiD5r+k=@MmTMO60xgjJVou=;g$4 zNJb$X{810xRWt0!f7B^Z#@%Z^gfHFP*pum4z$Ym&tU~mx5=q%k0A(DjArfjL-E}@x zSC&Xc=&0iyUfza}mRG=*ar78|sHNoA+kru4qtvlqAMg@@0sZh+{v+b%Cphn^47~En z%JN!;FVAjBLvLz>D_G9`nZczO)%QO)a^nsf&FBC9j!_>G;rwhl9V@>sSn8Daqx31K z&MXEYw{(I%{4V|ag*jImYez4)KYv|RFh6A=-tA27S$@dNs(LNNlu8Rruz-Wi-A0dy z;kW*TrYlU%zOD6yA+4#7TB;-0;(`u$R(O+lK(;*N(i6g8WX?&XnB8L}M5N`H)1vJZ z74_$UyeEIQKBJha)0sl$eY55&{ba=g<(BebR>>9_sheO!P`7@(36CN>bQ(>~d|Stn z9o6XdF^8YBNHu@(bQ7Voxf*#V%wFYyU&ucd6hyz2D`T023jNm$o+Xf$}Hb&}oW5?gJ3b zx_nV)BEEH&LxPs)3XP=Bh@QZo9{yXIN<1{_-(~j?+A@#}+!`}uoH{vS1X%iuhUcW& zG2bw=aS^ub!RdtB2)<1YawpH%N{I&7cYoCn2BQjEcrH1&@ z!Nxw!RRw7L^q9%{yMT+hj*QlDJ5Tg4`*>I;cnI~nHl{UV;4aSYB3e9_tsg%;3eKa8 zpPyOzScL|sP=H#zcg;bk!r%eMQsnh$Tk$=2sdh~>9b5UfeDIFRwt6JtJ_%C!N1t&q z0y5}4S^!C=ex>n-~Kkc$GwKhply zLF6+xveZDv_;(6+VRsohsw`#;o&I*lzM4d~L-9?wv3pcz}POp?kgk5(0aluJV@z4hd)t z@Qn{k414*;yp=ENj>!JYmks9H-t^p0$6v)L)mZI+J9tP_&~G}fP?`vqL$)9AxMq8~=xUpH zgh%Bwmdnh@uvqDoi$82%4_<5uWVwpoDqj6#KJM^8+WS1ExPjM7o4b^X5C1c<$4mRH zSW1ot{nG1ftS0^*NC4?-c&(rE32(6FPmvvy6Kp)DOxN-hGwRK6U4}hMi22T8guO$F zQ!PpqZg2>Zuk7xXH@53q--46w3@J<4kI1_7B(91+b)J$3-qsr5O9O$DfpCjpf=@@fOkacC23Z2zuc{V(nU72#g$>&my8*|||1x{M^Webzt|O`VkpdA@I8 z#CU54K!Uuj48Z{H9A&z0`X5HXytvEDq+L3Q!&C|%J#VAvD0rnE7$ahdU#gte#D3P6C zX^BUW0yE4TfuerJ;}Q@V*jB$Xf%YDW6n=~qsii`u`DM=) zIY>K){c>Lws3l=5z_#`92jf5c@(UkFl}jS6#XlW8;%;en2=B3_@v?@0Wt(aCbRq`` zsNU>Y=|kyf*1W#zsuJLveOJE5$JX{4==f#lQqaru0`(prO(5O!i_vD-w(nxv>2B<= z8DDi4z_V^^72np)11EszN^yY<5-!%4zw#Pcgg@C-dQP|>-64Sw?#@bIPhLb&Shm^} zp5b+p_AI6{ji;eQ=*g`f-=MTfjIsYy1*yB6I5!nPpVPT+EwAdc-SHcWw=gkzRZr>< z81gJnYgjFU02B;rto<4Mt*`_z+Gmvn2h4NIQe}53@p;k-5x$65Hynysk;dVQujL~| z_OpTFdviwsJz?yEQ_qP8+xW(Z*a-8|=#2>3(DC+*RwiA$`9 zTGxhFHdK)hqj~%~`jDd5C{POmlPJCIjYNG<+Z|i+SAu#$9;1|krFZseimFWBV1X7~ zGsu$ZxG12H;LBf{QE={2?95v60TAA*>B<8D;V%IQ-~Eg0Al&Tv4e|tt4SrO{-abMR zUPY6e{t>~z^a&-wg#?~S<0%ho;8d^yLh0dQgcW+zHODgLT9r=w!oc9lu${D056L%g zJ6rpHP@d&tVVQU|hn|%~+67F`^MYay6Q1+<1M98}?f1YfqMoR=l2nJZ7MYxwGz8kk{GW3mpa-O4;ksQbdkOs_ZL>!L~&Z+`>`~ zTzd$SI%&U&q%?XMCja)Z|CZ=d0M#;Mb z0tle?I98px2DF7B9*F=n@8^l|Bo36fr(UGWKQ0eM)326f8Bdg7UG=G}lKB#*YJHR8Nc;VIUj zQQ5vCgmizvcr@Y_KNFs^>$9TU4+2e>X==-8eiyOwRu}24iLc!S++|_HeQqgEPzt