diff --git a/README.md b/README.md index 6c4696b..97b18c8 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,75 @@ # http-utils-processor-ts +[![Build and test with Bun](https://github.com/jenspots/http-utils-processor-ts/actions/workflows/build-test.yml/badge.svg)](https://github.com/jenspots/http-utils-processor-ts/actions/workflows/build-test.yml) + Connector Architecture Typescript processors for handling HTTP operations. + +## Functions + +### [`httpFetch`](./src/index.ts) + +Build and execute an HTTP request. Writes the body of the response into a user specified channel. + +- `url`: endpoint against which a request is made. +- `writer`: channel into which the resulting data is written. +- `options`: an optional parameter which may include: + - `method` the HTTP method to use. (default: `GET`) + - `headers`: an array of strings to be used as headers in the outgoing request. (default: `[]`) + - `acceptStatusCodes`: an array of strings which lists all the status codes deemed "successful". These strings contain either integer literals such as `"200"`, or ranges such as `"200-300"`. Note that range "`a-b`" is inclusive `a`, exclusive `b`. (default: `["200-300"]`) + - `closeOnEnd`: whether to close the writer stream on end. (default: `true`) + - `timeOutMilliseconds`: maximum time spend waiting for a response before throwing a `HttpFetchError.timeOutError` error. (default: `null`) + - `auth`: object describing which authentication flow to use, as well as its parameters. See below for more info. (default: `null`) + - `cron`: specify the interval at which the function should run as a crontab expression. If `null`, the function only executes once before returning. (default: `null`) + +#### Authentication + +This package supports some forms of authentication such as HTTP Basic Authentication and the OAuth 2.0 Password Grant. Additional methods may be implemented by extending the abstract [`Auth`](./src/auth/index.ts) class, after which you must define an additional [`AuthConfig`](./src/auth/index.ts) type and extend the [`Auth.from`](./src/auth/index.ts) static method. + +##### HTTP Basic Authentication + +A simple flow which includes the base64 encoded username and password in each request. + +- `type`: must be set to`basic`. +- `username`: your username as string. +- `password`: your plaintext password. + +##### OAuth 2.0 Password Grant + +Before executing your request, a POST request is sent to the OAuth server in order to obtain a token. The result of which is embedded as a header inside the original request. + +- `type`: must be set to `oauth2` +- `endpoint`: the URL of the OAuth 2.0 server. +- `username`: your username as string. +- `password`: your plaintext password. + +Note that your credentials are not send to the server you specified in the `url` option of `httpFetch`, but only to the `endpoint` you specified above. + +## Errors + +All errors thrown in `httpFetch` are of the `HttpFetchError` type, as defined in [`./src/error.ts`](./src/error.ts). This class contains a `HttpUtilsErrorType` enum value which reflects the nature of the error. + +## Tests + +At the time of writing, tests should be executed using the Bun runtime. + +```sh +$ bun run build +$ bun test +``` + +Some tests interact with real online servers, and may therefore require credentials. These can be supplied inside a `.env` file at the root of the repository. + +```shell +# Requires OAuth 2.0 Password Grant +RINF_USERNAME= +RINF_PASSWORD= + +# Requires HTTP Basic Auth +WoRMS_USERNAME= +WoRMS_PASSWORD= + +# Needs to be `true` in order to execute +BLUE_BIKE=true +``` + +Additional information can be found [here](./tests/README.md). diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000..23908bd --- /dev/null +++ b/tests/README.md @@ -0,0 +1,4 @@ +# Test Suite + +> [!NOTE] +> This page still needs to be written. diff --git a/tests/online/index.test.ts b/tests/online/index.test.ts index 78370f9..795a91d 100644 --- a/tests/online/index.test.ts +++ b/tests/online/index.test.ts @@ -5,17 +5,14 @@ import { httpFetch } from "../../src"; import { writer } from "../writer"; describe("Real world datasets", () => { - test( + // Retrieve RINF credentials. + const rinfUsername = process.env["RINF_USERNAME"]; + const rinfPassword = process.env["RINF_PASSWORD"]; + const rinfTest = rinfUsername && rinfPassword ? test : test.skip; + + rinfTest( "RINF - success", async () => { - // Retrieve credentials. - const username = process.env["RINF_USERNAME"]; - const password = process.env["RINF_PASSWORD"]; - - if (!username || !password) { - throw new Error("RINF_USERNAME and RINF_PASSWORD must be set"); - } - // Output stream which we'll check for correctness. const writeStream = writer((output) => { const json = JSON.parse(output); @@ -31,8 +28,8 @@ describe("Real world datasets", () => { { auth: { type: "oauth2", - username, - password, + username: rinfUsername!, + password: rinfPassword!, endpoint: "https://rinf.era.europa.eu/api/token", }, }, @@ -43,7 +40,7 @@ describe("Real world datasets", () => { 10 * 1000, ); - test( + rinfTest( "RINF - missing credentials", async () => { const func = await httpFetch( @@ -58,7 +55,7 @@ describe("Real world datasets", () => { 10 * 1000, ); - test( + rinfTest( "RINF - invalid credentials", async () => { const func = await httpFetch( @@ -81,26 +78,22 @@ describe("Real world datasets", () => { 10 * 1000, ); - test( + // Retrieve WoRMS credentials. + const wormsUsername = process.env["WoRMS_USERNAME"]; + const wormsPassword = process.env["WoRMS_PASSWORD"]; + const wormsTest = wormsUsername && wormsPassword ? test : test.skip; + + wormsTest( "WoRMS - success", async () => { - const username = process.env["WoRMS_USERNAME"]; - const password = process.env["WoRMS_PASSWORD"]; - - if (!username || !password) { - throw new Error( - "WoRMS_USERNAME and WoRMS_PASSWORD must be set", - ); - } - const func = await httpFetch( "https://www.marinespecies.org/download/", new SimpleStream(), { auth: { type: "basic", - username, - password, + username: wormsUsername!, + password: wormsPassword!, }, }, ); @@ -110,7 +103,7 @@ describe("Real world datasets", () => { 10 * 1000, ); - test( + wormsTest( "WoRMS - missing credentials", async () => { const func = await httpFetch( @@ -125,7 +118,7 @@ describe("Real world datasets", () => { 10 * 1000, ); - test( + wormsTest( "WoRMS - invalid credentials", async () => { const func = await httpFetch( @@ -147,7 +140,11 @@ describe("Real world datasets", () => { 10 * 1000, ); - test("BlueBike - success", async () => { + // Check if BLUE_BIKE is defined. + const blueBike = process.env["BLUE_BIKE"]; + const blueBikeTest = blueBike === "true" ? test : test.skip; + + blueBikeTest("BlueBike - success", async () => { const writeStream = writer((output) => { expect(JSON.parse(output).length).toBeGreaterThan(100); });