From 5b210054128ef70c1b67cdcfe719c0053827706f Mon Sep 17 00:00:00 2001 From: Javier Evans Date: Fri, 5 Jan 2024 13:40:20 -0800 Subject: [PATCH] run multiple tests, convert to typescript --- __tests__/basic.test.js | 252 -------------- __tests__/basic.test.ts | 156 +++++++++ __tests__/support/configuration.ts | 28 ++ __tests__/support/container.js | 109 ------ __tests__/support/container.ts | 148 +++++++++ __tests__/support/s3Mock.js | 48 --- __tests__/support/s3Mock.ts | 76 +++++ __tests__/uriEncodings.test.js | 10 - __tests__/uriEncodings.test.ts | 148 +++++++++ jest.config.js | 13 +- package-lock.json | 510 ++++++++++++----------------- package.json | 9 +- tsconfig.json | 109 ++++++ 13 files changed, 893 insertions(+), 723 deletions(-) delete mode 100644 __tests__/basic.test.js create mode 100644 __tests__/basic.test.ts create mode 100644 __tests__/support/configuration.ts delete mode 100644 __tests__/support/container.js create mode 100644 __tests__/support/container.ts delete mode 100644 __tests__/support/s3Mock.js create mode 100644 __tests__/support/s3Mock.ts delete mode 100644 __tests__/uriEncodings.test.js create mode 100644 __tests__/uriEncodings.test.ts create mode 100644 tsconfig.json diff --git a/__tests__/basic.test.js b/__tests__/basic.test.js deleted file mode 100644 index 95816a7e..00000000 --- a/__tests__/basic.test.js +++ /dev/null @@ -1,252 +0,0 @@ -const request = require("supertest"); -const container = require("./support/container.js"); -const s3Mock = require("./support/s3Mock.js"); - -const TEST_NAME = "base"; - -const FILES = { - "a.txt": { - content: "Let go, or be dragged.", - }, - "b/c/d.txt": { - content: `When thoughts arise, then do all things arise. When thoughts vanish, then do all things vanish.`, - }, - "b/e.txt": { - content: "If only you could hear the sound of snow.", - }, - "a/abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.txt": { - content: ` - "Where the is not one thing, what then?" - "Throw it away!" - "With not one thing, what there is to throw away?" - "Then carry it off!" - `, - }, - "b/c/=": { - content: ` - This is an awful filename. - このフィール名を選ばないでください - ` - }, - "b/c/@": { - content: "" - }, - "b/c/'(1).txt": { - content: "In the midst of movement and chaos, keep stillness inside of you." - }, - "a/plus+plus.txt": { - content: ` - 代悲白頭翁   Lament for the White-Haired Old Man - 洛陽城東桃李花 In the east of Luoyang City, Peach blossoms abound - 飛來飛去落誰家 Their petals float around, coming and going, to whose house will they fall? - 洛陽女児惜顔色 Girls in Luoyang cherish their complexion - 行逢落花長歎息 They breathe a deep sigh upon seeing the petals fall - 今年花落顔色改 This year the petals fall and their complexion changes - 明年花開復誰在 Who will be there when the flowers bloom next year? - 已見松柏摧為薪 I've seen the pines and cypresses destroyed and turned into firewood - 更聞桑田変成海 I hear that the mulberry fields have fallen into the sea - 古人無復洛城東 The people of old never came back to the east of Luoyang City - 今人還對落花風 The people of today likewise face the falling flowers in the wind - 年年歳歳花相似 Year after year, flowers look alike - 歳歳年年人不同 Year after year, the people are not the same - 寄言全盛紅顔子 I want you to get this message, my child, you are in your prime, with a rosy complexion - 應憐半死白頭翁 Take pity on the half-dead white-haired old man - 此翁白頭真可憐 You really must take pity on this white-haired old man - 伊昔紅顔美少年 For once upon a time, I used to be a red-faced handsome young man - 公子王孫芳樹下 A child of noble birth under a fragrant tree - 清歌妙舞落花前 Singing and dancing in front of the falling petals - 光禄池臺開錦繍 At the platform before the mirror pond, beautiful autumn leaves opening all around - 将軍楼閣畫神仙 The general’s pavilion is painted with gods and goddesses - 一朝臥病無相識 Once I was sick and no one knew me - 三春行楽在誰邉 Who will be at the shore for the spring outing? - 宛轉蛾眉能幾時 For how long will the moths gracefully turn about? - 須臾鶴髪亂如絲 The crane’s feathers are like tangled threads for just a moment - 但看古来歌舞地 Yet, look at the ancient places of song and dance - 惟有黄昏鳥雀悲 Only in twilight, do the birds lament - ` - }, - "системы/%bad%file%name%": { - content: ` - Filename encoding issues are hard. - - ` - } -}; - -const BUCKET_NAME = "bucket-2"; - -// Config for the running container per test - -const CONFIG = container.Config({ - env: { - S3_BUCKET_NAME: BUCKET_NAME, - AWS_ACCESS_KEY_ID: "AKIAIOSFODNN7EXAMPLE", - AWS_SECRET_ACCESS_KEY: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", - S3_SERVER: "minio", - S3_SERVER_PORT: "9000", - S3_SERVER_PROTO: "http", - S3_REGION: "us-east-1", - DEBUG: "true", - S3_STYLE: "virtual", - ALLOW_DIRECTORY_LIST: "false", - PROVIDE_INDEX_PAGE: "", - APPEND_SLASH_FOR_POSSIBLE_DIRECTORY: "", - STRIP_LEADING_DIRECTORY_PATH: "", - PREFIX_LEADING_DIRECTORY_PATH: "", - AWS_SIGS_VERSION: "4", - STATIC_SITE_HOSTING: "", - PROXY_CACHE_MAX_SIZE: "10g", - PROXY_CACHE_INACTIVE: "60m", - PROXY_CACHE_VALID_OK: "1h", - PROXY_CACHE_VALID_NOTFOUND: "1m", - PROXY_CACHE_VALID_FORBIDDEN: "30s", - }, - dockerfileName: "Dockerfile.oss", - testName: TEST_NAME, - networkName: "s3-gateway-test", -}); - -const minioClient = s3Mock.Client("localhost", 9090, "AKIAIOSFODNN7EXAMPLE", "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"); - -beforeAll(async () => { - try { - await container.stop(CONFIG); - } catch (e) { - console.log("no container to stop"); - } - - await s3Mock.ensureBucketWithObjects(minioClient, BUCKET_NAME, FILES); - await container.build(CONFIG); - await container.start(CONFIG); -}); - -afterAll(async () => { - await container.stop(CONFIG); - await s3Mock.deleteBucket(minioClient, BUCKET_NAME); -}); - - -describe("Ordinary filenames", () => { - test("simple url", async () => { - const objectPath = "a.txt"; - const res = await request(CONFIG.testContainer.baseUrl).get(`/${objectPath}`); - expect(res.statusCode).toBe(200); - expect(res.text).toBe(FILES[objectPath].content); - }); - - test("many params that should be stripped", async () => { - const objectPath = "a.txt"; - const res = await request(CONFIG.testContainer.baseUrl) - .get("/a.txt?some=param&that=should&be=stripped#aaah") - .set("accept", "binary/octet-stream"); - - expect(res.statusCode).toBe(200); - expect(res.text).toBe(FILES[objectPath].content); - }); - - test("with a more complex path", async () => { - const objectPath = "b/c/d.txt"; - const res = await request(CONFIG.testContainer.baseUrl) - .get("/b/c/d.txt") - .set("accept", "binary/octet-stream"); - - expect(res.statusCode).toBe(200); - expect(res.text).toBe(FILES[objectPath].content); - }); - - test.skip("with dot segments in the path", async () => { - const objectPath = "b/e.txt"; - const res = await request(CONFIG.testContainer.baseUrl) - .get("/b/c/../e.txt") - .set("accept", "binary/octet-stream"); - - const reqData = JSON.parse(JSON.stringify(res)).req; - expect(res.statusCode).toBe(200); - expect(res.text).toBe(FILES[objectPath].content); - }); - - test("another simple path", async () => { - const objectPath = "b/e.txt"; - const res = await request(CONFIG.testContainer.baseUrl) - .get("/b/e.txt") - .set("accept", "binary/octet-stream"); - - expect(res.statusCode).toBe(200); - expect(res.text).toBe(FILES[objectPath].content); - }); - - test("too many forward slashes", async () => { - const objectPath = "b/e.txt"; - const res = await request(CONFIG.testContainer.baseUrl) - .get("/b//e.txt") - .set("accept", "binary/octet-stream"); - - expect(res.statusCode).toBe(200); - expect(res.text).toBe(FILES[objectPath].content); - }); - - test("very long file name", async () => { - const objectPath = - "a/abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.txt"; - const res = await request(CONFIG.testContainer.baseUrl) - .get( - "/a/abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.txt", - ) - .set("accept", "binary/octet-stream"); - - expect(res.statusCode).toBe(200); - expect(res.text).toBe(FILES[objectPath].content); - }); -}); - -describe("strange file names and encodings", () => { - test("URI encoded equal sign as file name", async () => { - const objectPath = "b/c/="; - const res = await request(CONFIG.testContainer.baseUrl) - .get("/b/c/%3D") - .set("accept", "binary/octet-stream"); - - expect(res.statusCode).toBe(200); - expect(res.text).toBe(FILES[objectPath].content); - }); - - test("URI encoded @ symbol as file name", async () => { - const objectPath = "b/c/@"; - const res = await request(CONFIG.testContainer.baseUrl) - .get("/b/c/%40") - .set("accept", "binary/octet-stream"); - - expect(res.statusCode).toBe(200); - expect(res.text).toBe(FILES[objectPath].content); - }); - - test("URI with encoded punctuation in file name", async () => { - const objectPath = "b/c/'(1).txt"; - const res = await request(CONFIG.testContainer.baseUrl) - .get("/b/c/%27%281%29.txt") - .set("accept", "binary/octet-stream"); - - expect(res.statusCode).toBe(200); - expect(res.text).toBe(FILES[objectPath].content); - }); - - test("URI with encoded plus in file name", async () => { - const objectPath = "a/plus+plus.txt"; - const res = await request(CONFIG.testContainer.baseUrl) - .get("/a/plus%2Bplus.txt") - .set("accept", "binary/octet-stream"); - - expect(res.statusCode).toBe(200); - expect(res.text).toBe(FILES[objectPath].content); - }); - - test("URI with cyrillic script and punctuation in file name", async () => { - const objectPath = "системы/%bad%file%name%"; - const res = await request(CONFIG.testContainer.baseUrl) - .get("/%D1%81%D0%B8%D1%81%D1%82%D0%B5%D0%BC%D1%8B/%25bad%25file%25name%25") - .set("accept", "binary/octet-stream"); - - expect(res.statusCode).toBe(200); - expect(res.text).toBe(FILES[objectPath].content); - }); -}); diff --git a/__tests__/basic.test.ts b/__tests__/basic.test.ts new file mode 100644 index 00000000..9f58e2c0 --- /dev/null +++ b/__tests__/basic.test.ts @@ -0,0 +1,156 @@ +import request from "supertest"; +import container from "./support/container"; +import s3Mock from "./support/s3Mock"; +import { describe, expect, test, beforeAll, afterAll } from "@jest/globals"; +import { TestConfig, DummyFileList } from "./support/configuration"; + +const BUCKET_NAME = "bucket-2"; + +// Config for the running container per test +const testConfig: TestConfig = { + name: "base_test", + image: { + dockerfile: "Dockerfile.oss", + }, + container: { + env: { + S3_BUCKET_NAME: BUCKET_NAME, + AWS_ACCESS_KEY_ID: "AKIAIOSFODNN7EXAMPLE", + AWS_SECRET_ACCESS_KEY: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + S3_SERVER: "minio", + S3_SERVER_PORT: "9000", + S3_SERVER_PROTO: "http", + S3_REGION: "us-east-1", + DEBUG: "true", + S3_STYLE: "virtual", + ALLOW_DIRECTORY_LIST: "false", + PROVIDE_INDEX_PAGE: "", + APPEND_SLASH_FOR_POSSIBLE_DIRECTORY: "", + STRIP_LEADING_DIRECTORY_PATH: "", + PREFIX_LEADING_DIRECTORY_PATH: "", + AWS_SIGS_VERSION: "4", + STATIC_SITE_HOSTING: "", + PROXY_CACHE_MAX_SIZE: "10g", + PROXY_CACHE_INACTIVE: "60m", + PROXY_CACHE_VALID_OK: "1h", + PROXY_CACHE_VALID_NOTFOUND: "1m", + PROXY_CACHE_VALID_FORBIDDEN: "30s", + }, + }, +}; + +const CONFIG = container.Config(testConfig); + +const minioClient = s3Mock.Client( + "localhost", + 9090, + "AKIAIOSFODNN7EXAMPLE", + "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", +); + +beforeAll(async () => { + try { + await container.stop(CONFIG); + } catch (e) { + console.log("no container to stop"); + } + + await s3Mock.ensureBucketWithObjects(minioClient, BUCKET_NAME, files()); + await container.build(CONFIG); + await container.start(CONFIG); +}); + +afterAll(async () => { + await container.stop(CONFIG); + await s3Mock.deleteBucket(minioClient, BUCKET_NAME); +}); + +describe("Ordinary filenames", () => { + test("simple url", async () => { + const objectPath = "a.txt"; + const res = await request(CONFIG.testContainer.baseUrl).get( + `/${objectPath}`, + ); + expect(res.statusCode).toBe(200); + expect(res.text).toBe(fileContent(objectPath)); + }); + + test("many params that should be stripped", async () => { + const objectPath = "a.txt"; + const res = await request(CONFIG.testContainer.baseUrl) + .get("/a.txt?some=param&that=should&be=stripped#aaah") + .set("accept", "binary/octet-stream"); + + expect(res.statusCode).toBe(200); + expect(res.text).toBe(fileContent(objectPath)); + }); + + test("with a more complex path", async () => { + const objectPath = "b/c/d.txt"; + const res = await request(CONFIG.testContainer.baseUrl) + .get("/b/c/d.txt") + .set("accept", "binary/octet-stream"); + + expect(res.statusCode).toBe(200); + expect(res.text).toBe(fileContent(objectPath)); + }); + + test("another simple path", async () => { + const objectPath = "b/e.txt"; + const res = await request(CONFIG.testContainer.baseUrl) + .get("/b/e.txt") + .set("accept", "binary/octet-stream"); + + expect(res.statusCode).toBe(200); + expect(res.text).toBe(fileContent(objectPath)); + }); + + test("too many forward slashes", async () => { + const objectPath = "b/e.txt"; + const res = await request(CONFIG.testContainer.baseUrl) + .get("/b//e.txt") + .set("accept", "binary/octet-stream"); + + expect(res.statusCode).toBe(200); + expect(res.text).toBe(fileContent(objectPath)); + }); + + test("very long file name", async () => { + const objectPath = + "a/abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.txt"; + const res = await request(CONFIG.testContainer.baseUrl) + .get( + "/a/abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.txt", + ) + .set("accept", "binary/octet-stream"); + + expect(res.statusCode).toBe(200); + expect(res.text).toBe(fileContent(objectPath)); + }); +}); + +function fileContent(key: string): string | undefined { + return files()[key]?.content; +} + +function files(): DummyFileList { + return { + "a.txt": { + content: "Let go, or be dragged.", + }, + "b/c/d.txt": { + content: `When thoughts arise, then do all things arise. When thoughts vanish, then do all things vanish.`, + }, + "b/e.txt": { + content: "If only you could hear the sound of snow.", + }, + "a/abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.txt": { + content: ` + "Where the is not one thing, what then?" + "Throw it away!" + "With not one thing, what there is to throw away?" + "Then carry it off!" + `, + }, + }; +} diff --git a/__tests__/support/configuration.ts b/__tests__/support/configuration.ts new file mode 100644 index 00000000..038c58e4 --- /dev/null +++ b/__tests__/support/configuration.ts @@ -0,0 +1,28 @@ +/** + * Configuration for a single test + */ +export type TestConfig = { + name: string; + image: { + dockerfile: string; + prebuiltName?: string; + }; + container: { + env: ContainerEnvironment; + }; +}; + +/** + * The environment variables to be given to the container + */ +type ContainerEnvironment = { + [key: string]: string; +}; + +export type DummyFileList = { + [key: string]: DummyFile; +}; + +type DummyFile = { + content: string; +}; diff --git a/__tests__/support/container.js b/__tests__/support/container.js deleted file mode 100644 index e38925d4..00000000 --- a/__tests__/support/container.js +++ /dev/null @@ -1,109 +0,0 @@ -const util = require("util"); -const execAsync = util.promisify(require("child_process").exec); - -const GATEWAY_HOST = "localhost"; -const GATEWAY_PORT = "8989"; -const GATEWAY_BASE_URL = `http://${GATEWAY_HOST}:${GATEWAY_PORT}`; -const START_TIMEOUT_SECONDS = 10; -const STOP_TIMEOUT_SECONDS = 5; - -function Config(testConfig) { - const imageName = testConfig.prebuiltImageName || buildImageName(testConfig.testName); - return { - imageName: imageName, - containerName: imageNameToContainerName(imageName), - env: testConfig.env, - dockerfileName: testConfig.dockerfileName, - usePrebuiltImage: !!testConfig.prebuiltImageName, - networkName: testConfig.networkName, - testContainer: { - host: GATEWAY_HOST, - port: GATEWAY_PORT, - baseUrl: GATEWAY_BASE_URL, - }, - }; -} - -async function build(config) { - if (config.usePrebuiltImage) return config.imageName; - await execAsync(`docker build -t ${config.imageName} -f ${config.dockerfileName} .`); -} - -async function start(config) { - console.log("Waiting for test container to be ready..."); - async function waitForContainerStart(timeoutAt) { - if (new Date().getTime() > timeoutAt) - throw new Error( - `Failed to start S3 Gateway test container ${config.imageName} with name ${config.containerName}. Check container logs for details`, - ); - - try { - const statusCode = await getStatusCode(`${config.testContainer.baseUrl}/health`); - console.log(statusCode); - - if (statusCode === 200) { - console.log("Verified test container is running!"); - } else { - await timeout(1500); - await waitForContainerStart(timeoutAt); - } - } catch (e) { - await timeout(1500); - await waitForContainerStart(timeoutAt); - } - - } -// `docker run -d --rm --name ${config.containerName} --network ${config.networkName} -p 8989:80 ${envToDockerRunArgs(config.env)} ${imageName}`; - const dockerRunCmd = [ - "docker run -d --rm", - `--name ${config.containerName}`, - `--network ${config.networkName}`, - `-p ${config.testContainer.port}:80`, - envToDockerRunArgs(config.env), - config.imageName - ].join(" "); - - await execAsync(dockerRunCmd); - - await waitForContainerStart( - new Date().getTime() + (START_TIMEOUT_SECONDS * 1000) - ); -} - -async function stop(config) { - await execAsync(`docker stop -t ${STOP_TIMEOUT_SECONDS} ${config.containerName}`); -} - -function timeout(ms) { - return new Promise(resolve => setTimeout(resolve, ms)); -} - -const http = require("http"); -async function getStatusCode(url) { - return new Promise((resolve, reject) => { - http.get(url, (response) => { - // Check the status code. - const statusCode = response.statusCode; - resolve(statusCode); - }).on('error', (error) => { - reject(error); - }); - }); -} - -function buildImageName(testName) { - return `nginx-s3-gateway:test-${testName}`; -} - -function imageNameToContainerName(name) { - return name.replace(":", "-"); -} - -function envToDockerRunArgs(env) { - return Object.keys(env).reduce( - (acc, key) => `${acc} -e ${key}=${env[key]}`, - "", - ); -} - -module.exports = { build, start, stop, Config }; diff --git a/__tests__/support/container.ts b/__tests__/support/container.ts new file mode 100644 index 00000000..d189bef3 --- /dev/null +++ b/__tests__/support/container.ts @@ -0,0 +1,148 @@ +import { exec } from "child_process"; +import { promisify } from "util"; +const asyncExec = promisify(exec); +import http from "http"; +import { TestConfig } from "./configuration"; + +const GATEWAY_HOST = "localhost"; +const GATEWAY_PORT = 8989; +const GATEWAY_BASE_URL = `http://${GATEWAY_HOST}:${GATEWAY_PORT}`; +const START_TIMEOUT_SECONDS = 10; +const STOP_TIMEOUT_SECONDS = 5; + +// Must match network name in docker-compose.yaml +const DOCKER_NETWORK_NAME = "s3-gateway-test"; + +type ContainerEnvironment = { + [key: string]: string; +}; + +type TestContainer = { + host: string; + port: number; + baseUrl: string; +}; + +type ContainerConfig = { + imageName: string; + containerName: string; + env: ContainerEnvironment; + dockerfileName?: string; + usePrebuiltImage: boolean; + networkName: string; + testContainer: TestContainer; +}; + +export function Config(testConfig: TestConfig): ContainerConfig { + const imageName = + testConfig.image.prebuiltName || + (testConfig.name && buildImageName(testConfig.name)); + + return { + imageName: imageName, + containerName: imageNameToContainerName(imageName), + env: testConfig.container.env, + dockerfileName: testConfig.image.dockerfile, + usePrebuiltImage: !!testConfig.image.prebuiltName, + networkName: DOCKER_NETWORK_NAME, + testContainer: { + host: GATEWAY_HOST, + port: GATEWAY_PORT, + baseUrl: GATEWAY_BASE_URL, + }, + }; +} + +export async function build(config: ContainerConfig) { + if (config.usePrebuiltImage) return config.imageName; + await asyncExec( + `docker build -t ${config.imageName} -f ${config.dockerfileName} .`, + ); +} + +export async function start(config: ContainerConfig) { + console.log("Waiting for test container to be ready..."); + async function waitForContainerStart(timeoutAt: number) { + if (new Date().getTime() > timeoutAt) + throw new Error( + `Failed to start S3 Gateway test container ${config.imageName} with name ${config.containerName}. Check container logs for details`, + ); + + try { + const statusCode = await getStatusCode( + `${config.testContainer.baseUrl}/health`, + ); + console.log(statusCode); + + if (statusCode === 200) { + console.log("Verified test container is running!"); + } else { + await timeout(1500); + await waitForContainerStart(timeoutAt); + } + } catch (e) { + await timeout(1500); + await waitForContainerStart(timeoutAt); + } + } + + const dockerRunCmd = [ + "docker run -d --rm", + `--name ${config.containerName}`, + `--network ${config.networkName}`, + `-p ${config.testContainer.port}:80`, + envToDockerRunArgs(config.env), + config.imageName, + ].join(" "); + + await asyncExec(dockerRunCmd); + + await waitForContainerStart( + new Date().getTime() + START_TIMEOUT_SECONDS * 1000, + ); +} + +export async function stop(config: ContainerConfig) { + await asyncExec( + `docker stop -t ${STOP_TIMEOUT_SECONDS} ${config.containerName}`, + ); +} + +function timeout(ms: number) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +async function getStatusCode(url: string): Promise { + return new Promise((resolve, reject) => { + http + .get(url, (response) => { + // Check the status code. + const statusCode = response.statusCode; + if (statusCode) { + resolve(statusCode); + } else { + reject(new Error(`No status code from request to ${url}`)); + } + }) + .on("error", (error) => { + reject(error); + }); + }); +} + +function buildImageName(testName: string): string { + return `nginx-s3-gateway:test-${testName}`; +} + +function imageNameToContainerName(name: string): string { + return name.replace(":", "-"); +} + +function envToDockerRunArgs(env: ContainerEnvironment) { + return Object.keys(env).reduce( + (acc, key) => `${acc} -e ${key}=${env[key]}`, + "", + ); +} + +export default { build, start, stop, Config }; diff --git a/__tests__/support/s3Mock.js b/__tests__/support/s3Mock.js deleted file mode 100644 index 68bdada2..00000000 --- a/__tests__/support/s3Mock.js +++ /dev/null @@ -1,48 +0,0 @@ -const Minio = require("minio"); - -function Client(hostname, port, accessKey, secretKey) { - return new Minio.Client({ - endPoint: hostname, - port: port, - useSSL: false, - accessKey: accessKey, - secretKey: secretKey, - }); -} - -function listObjectsInBucket(s3Client, bucketName, prefix = "") { - const objectStream = s3Client.listObjectsV2(bucketName, prefix, /*recursive: */ true); - const list = []; - - return new Promise((resolve, reject) => { - objectStream.on('data', obj => list.push(obj.name)); - objectStream.on('end', () => resolve(list)); - objectStream.on('error', e => reject(e)); - }); -} - -async function ensureBucketWithObjects(s3Client, bucketName, objects) { - await deleteBucket(s3Client, bucketName); - await s3Client.makeBucket(bucketName, "us-east-1"); - - for (const path of Object.keys(objects)) { - let buf = Buffer.from(objects[path].content, "utf-8"); - let res = await s3Client.putObject(bucketName, path, buf); - } - - console.log("S3 bucket status ensured 🔒"); -} - -async function deleteBucket(s3Client, bucketName) { - if (!await s3Client.bucketExists(bucketName)) return; - - const items = await listObjectsInBucket(s3Client, bucketName); - await s3Client.removeObjects(bucketName, items); - await s3Client.removeBucket(bucketName); -} - -module.exports = { - Client, - ensureBucketWithObjects, - deleteBucket -}; \ No newline at end of file diff --git a/__tests__/support/s3Mock.ts b/__tests__/support/s3Mock.ts new file mode 100644 index 00000000..517ceb31 --- /dev/null +++ b/__tests__/support/s3Mock.ts @@ -0,0 +1,76 @@ +import * as Minio from "minio"; + +type s3BucketItem = { + content: string; +}; + +type s3BucketContents = { + [key: string]: s3BucketItem; +}; + +export function Client( + hostname: string, + port: number, + accessKey: string, + secretKey: string, +): Minio.Client { + return new Minio.Client({ + endPoint: hostname, + port: port, + useSSL: false, + accessKey: accessKey, + secretKey: secretKey, + }); +} + +export function listObjectsInBucket( + s3Client: Minio.Client, + bucketName: string, + prefix = "", +): Promise { + const objectStream = s3Client.listObjectsV2( + bucketName, + prefix, + /*recursive: */ true, + ); + const list: string[] = []; + + return new Promise((resolve, reject) => { + objectStream.on( + "data", + (obj: Minio.BucketItem) => obj.name && list.push(obj.name), + ); + objectStream.on("end", () => resolve(list)); + objectStream.on("error", (e) => reject(e)); + }); +} + +export async function ensureBucketWithObjects( + s3Client: Minio.Client, + bucketName: string, + objects: s3BucketContents, +) { + await deleteBucket(s3Client, bucketName); + await s3Client.makeBucket(bucketName, "us-east-1"); + + for (const path of Object.keys(objects)) { + let buf = Buffer.from(objects[path].content, "utf-8"); + await s3Client.putObject(bucketName, path, buf); + } + + console.log("S3 bucket status ensured 🔒"); +} + +export async function deleteBucket(s3Client: Minio.Client, bucketName: string) { + if (!(await s3Client.bucketExists(bucketName))) return; + + const items = await listObjectsInBucket(s3Client, bucketName); + await s3Client.removeObjects(bucketName, items); + await s3Client.removeBucket(bucketName); +} + +export default { + Client, + ensureBucketWithObjects, + deleteBucket, +}; diff --git a/__tests__/uriEncodings.test.js b/__tests__/uriEncodings.test.js deleted file mode 100644 index 0b35a3f3..00000000 --- a/__tests__/uriEncodings.test.js +++ /dev/null @@ -1,10 +0,0 @@ -const request = require("supertest"); - -const TEST_NAME = "encodings"; - -describe("Ordinary filenames", () => { - test("simple url", async () => { - const objectPath = "a.txt"; - expect(true).toBe(true); - }); -}); \ No newline at end of file diff --git a/__tests__/uriEncodings.test.ts b/__tests__/uriEncodings.test.ts new file mode 100644 index 00000000..7cb888d9 --- /dev/null +++ b/__tests__/uriEncodings.test.ts @@ -0,0 +1,148 @@ +import request from "supertest"; +import container from "./support/container"; +import s3Mock from "./support/s3Mock"; +import { describe, expect, test, beforeAll, afterAll } from "@jest/globals"; +import { TestConfig, DummyFileList } from "./support/configuration"; + +const BUCKET_NAME = "bucket-3"; + +// Config for the running container per test +const testConfig: TestConfig = { + name: "encodings", + image: { + dockerfile: "Dockerfile.oss", + }, + container: { + env: { + S3_BUCKET_NAME: BUCKET_NAME, + AWS_ACCESS_KEY_ID: "AKIAIOSFODNN7EXAMPLE", + AWS_SECRET_ACCESS_KEY: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + S3_SERVER: "minio", + S3_SERVER_PORT: "9000", + S3_SERVER_PROTO: "http", + S3_REGION: "us-east-1", + DEBUG: "true", + S3_STYLE: "virtual", + ALLOW_DIRECTORY_LIST: "false", + PROVIDE_INDEX_PAGE: "", + APPEND_SLASH_FOR_POSSIBLE_DIRECTORY: "", + STRIP_LEADING_DIRECTORY_PATH: "", + PREFIX_LEADING_DIRECTORY_PATH: "", + AWS_SIGS_VERSION: "4", + STATIC_SITE_HOSTING: "", + PROXY_CACHE_MAX_SIZE: "10g", + PROXY_CACHE_INACTIVE: "60m", + PROXY_CACHE_VALID_OK: "1h", + PROXY_CACHE_VALID_NOTFOUND: "1m", + PROXY_CACHE_VALID_FORBIDDEN: "30s", + }, + }, +}; + +const CONFIG = container.Config(testConfig); + +const minioClient = s3Mock.Client( + "localhost", + 9090, + "AKIAIOSFODNN7EXAMPLE", + "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", +); + +beforeAll(async () => { + try { + await container.stop(CONFIG); + } catch (e) { + console.log("no container to stop"); + } + + await s3Mock.ensureBucketWithObjects(minioClient, BUCKET_NAME, files()); + await container.build(CONFIG); + await container.start(CONFIG); +}); + +afterAll(async () => { + await container.stop(CONFIG); + await s3Mock.deleteBucket(minioClient, BUCKET_NAME); +}); + +describe("strange file names and encodings", () => { + test("URI encoded equal sign as file name", async () => { + const objectPath = "b/c/="; + const res = await request(CONFIG.testContainer.baseUrl) + .get("/b/c/%3D") + .set("accept", "binary/octet-stream"); + + expect(res.statusCode).toBe(200); + expect(res.text).toBe(fileContent(objectPath)); + }); + + test("URI encoded @ symbol as file name", async () => { + const objectPath = "b/c/@"; + const res = await request(CONFIG.testContainer.baseUrl) + .get("/b/c/%40") + .set("accept", "binary/octet-stream"); + + expect(res.statusCode).toBe(200); + expect(res.text).toBe(fileContent(objectPath)); + }); + + test("URI with encoded punctuation in file name", async () => { + const objectPath = "b/c/'(1).txt"; + const res = await request(CONFIG.testContainer.baseUrl) + .get("/b/c/%27%281%29.txt") + .set("accept", "binary/octet-stream"); + + expect(res.statusCode).toBe(200); + expect(res.text).toBe(fileContent(objectPath)); + }); + + test("URI with encoded plus in file name", async () => { + const objectPath = "a/plus+plus.txt"; + const res = await request(CONFIG.testContainer.baseUrl) + .get("/a/plus%2Bplus.txt") + .set("accept", "binary/octet-stream"); + + expect(res.statusCode).toBe(200); + expect(res.text).toBe(fileContent(objectPath)); + }); + + test("URI with cyrillic script and punctuation in file name", async () => { + const objectPath = "системы/%bad%file%name%"; + const res = await request(CONFIG.testContainer.baseUrl) + .get( + "/%D1%81%D0%B8%D1%81%D1%82%D0%B5%D0%BC%D1%8B/%25bad%25file%25name%25", + ) + .set("accept", "binary/octet-stream"); + + expect(res.statusCode).toBe(200); + expect(res.text).toBe(fileContent(objectPath)); + }); +}); + +function fileContent(key: string): string | undefined { + return files()[key]?.content; +} + +function files(): DummyFileList { + return { + "b/c/=": { + content: ` + This is an awful filename. + このフィール名を選ばないでください + `, + }, + "b/c/@": { + content: "", + }, + "b/c/'(1).txt": { + content: + "In the midst of movement and chaos, keep stillness inside of you.", + }, + "a/plus+plus.txt": { + content: `代悲白頭翁   Lament for the White-Haired Old Man`, + }, + "системы/%bad%file%name%": { + content: `Filename encoding issues are hard.`, + }, + }; +} \ No newline at end of file diff --git a/jest.config.js b/jest.config.js index dd947aee..b5c95f2e 100644 --- a/jest.config.js +++ b/jest.config.js @@ -101,7 +101,7 @@ const config = { // notifyMode: "failure-change", // A preset that is used as a base for Jest's configuration - // preset: undefined, + preset: "ts-jest", // Run tests from one or more projects // projects: undefined, @@ -145,7 +145,7 @@ const config = { // snapshotSerializers: [], // The test environment that will be used for testing - // testEnvironment: "jest-environment-node", + testEnvironment: "node", // Options that will be passed to the testEnvironment // testEnvironmentOptions: {}, @@ -155,13 +155,14 @@ const config = { // The glob patterns Jest uses to detect test files testMatch: [ - "**/__tests__/**/*.test.js?(x)", + "**/__tests__/**/*.[jt]s?(x)", ], // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped - // testPathIgnorePatterns: [ - // "/node_modules/" - // ], + testPathIgnorePatterns: [ + "/node_modules/", + "/__tests__/support/" + ], // The regexp pattern or array of patterns that Jest uses to detect test files // testRegex: [], diff --git a/package-lock.json b/package-lock.json index 7f2c2e1d..cecc19e5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,15 +4,20 @@ "requires": true, "packages": { "": { + "name": "nginx-s3-gateway", "devDependencies": { + "@jest/globals": "^29.7.0", + "@types/supertest": "^6.0.2", "better-docs": "^2.7.2", - "dockerode": "^4.0.0", + "convict": "^6.2.4", "jest": "^29.7.0", "jsdoc": "^4.0.2", "minio": "^7.1.3", "njs-types": "^0.8.1", "supertest": "^6.3.3", - "taffydb": "^2.7.3" + "taffydb": "^2.7.3", + "ts-jest": "^29.1.1", + "typescript": "^5.3.3" } }, "node_modules/@ampproject/remapping": { @@ -29,12 +34,12 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.22.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", - "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", + "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", "dev": true, "dependencies": { - "@babel/highlight": "^7.22.13", + "@babel/highlight": "^7.23.4", "chalk": "^2.4.2" }, "engines": { @@ -81,12 +86,12 @@ } }, "node_modules/@babel/generator": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", - "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", + "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", "dev": true, "dependencies": { - "@babel/types": "^7.23.0", + "@babel/types": "^7.23.6", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -210,9 +215,9 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", - "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", + "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", "dev": true, "engines": { "node": ">=6.9.0" @@ -251,9 +256,9 @@ } }, "node_modules/@babel/highlight": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", - "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", + "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", "dev": true, "dependencies": { "@babel/helper-validator-identifier": "^7.22.20", @@ -265,9 +270,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", - "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.6.tgz", + "integrity": "sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -480,20 +485,20 @@ } }, "node_modules/@babel/traverse": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.0.tgz", - "integrity": "sha512-t/QaEvyIoIkwzpiZ7aoSKK8kObQYeF7T2v+dazAYCb8SXtp58zEVkWW7zAnju8FNKNdr4ScAOEDmMItbyOmEYw==", + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.7.tgz", + "integrity": "sha512-tY3mM8rH9jM0YHFGyfC0/xf+SB5eKUu7HPj7/k3fpi9dAlsMc5YbQvDi0Sh2QTPXqMhyaAtzAr807TIyfQrmyg==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.23.0", + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.6", "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-function-name": "^7.23.0", "@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.0", - "@babel/types": "^7.23.0", - "debug": "^4.1.0", + "@babel/parser": "^7.23.6", + "@babel/types": "^7.23.6", + "debug": "^4.3.1", "globals": "^11.1.0" }, "engines": { @@ -501,12 +506,12 @@ } }, "node_modules/@babel/types": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", - "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.6.tgz", + "integrity": "sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg==", "dev": true, "dependencies": { - "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-string-parser": "^7.23.4", "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" }, @@ -514,12 +519,6 @@ "node": ">=6.9.0" } }, - "node_modules/@balena/dockerignore": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@balena/dockerignore/-/dockerignore-1.0.2.tgz", - "integrity": "sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q==", - "dev": true - }, "node_modules/@bcoe/v8-coverage": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", @@ -1380,6 +1379,12 @@ "@types/babel-types": "*" } }, + "node_modules/@types/cookiejar": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.5.tgz", + "integrity": "sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==", + "dev": true + }, "node_modules/@types/graceful-fs": { "version": "4.1.9", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", @@ -1435,6 +1440,12 @@ "integrity": "sha512-T5k6kTXak79gwmIOaDF2UUQXFbnBE0zBUzF20pz7wDYu0RQMzWg+Ml/Pz50214NsFHBITkoi5VtdjFZnJ2ijjA==", "dev": true }, + "node_modules/@types/methods": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz", + "integrity": "sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==", + "dev": true + }, "node_modules/@types/node": { "version": "20.10.4", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.4.tgz", @@ -1450,6 +1461,27 @@ "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", "dev": true }, + "node_modules/@types/superagent": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-8.1.1.tgz", + "integrity": "sha512-YQyEXA4PgCl7EVOoSAS3o0fyPFU6erv5mMixztQYe1bqbWmmn8c+IrqoxjQeZe4MgwXikgcaZPiI/DsbmOVlzA==", + "dev": true, + "dependencies": { + "@types/cookiejar": "^2.1.5", + "@types/methods": "^1.1.4", + "@types/node": "*" + } + }, + "node_modules/@types/supertest": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-6.0.2.tgz", + "integrity": "sha512-137ypx2lk/wTQbW6An6safu9hXmajAifU/s7szAHLN/FeIm5w7yR0Wkl9fdJMRSHwOn4HLAI0DaB2TOORuhPDg==", + "dev": true, + "dependencies": { + "@types/methods": "^1.1.4", + "@types/superagent": "^8.1.0" + } + }, "node_modules/@types/yargs": { "version": "17.0.32", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", @@ -1574,15 +1606,6 @@ "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", "dev": true }, - "node_modules/asn1": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", - "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", - "dev": true, - "dependencies": { - "safer-buffer": "~2.1.0" - } - }, "node_modules/ast-types": { "version": "0.14.2", "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.14.2.tgz", @@ -1848,39 +1871,10 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", - "dev": true, - "dependencies": { - "tweetnacl": "^0.14.3" - } - }, "node_modules/better-docs": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/better-docs/-/better-docs-2.7.2.tgz", - "integrity": "sha512-aIOsGhhcTIDAJfBTABIPDs3q98dfNF85yUwmKShXb3ZG6e7s+ojBePiDqvFwy/MpnjYwuSbuzkbEv4iPWcSuTQ==", + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/better-docs/-/better-docs-2.7.3.tgz", + "integrity": "sha512-OEk9e7RQUQbo1DmVo0mdsALZ+mT0SwIen7/DGnN4xKNktXKgz1j7+KQ2pObNHHVSFGu8YMQW/Ig1v6eiTkdnbw==", "dev": true, "dependencies": { "brace": "^0.11.1", @@ -1900,15 +1894,17 @@ "react-dom": "^17.0.2" } }, - "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "node_modules/better-docs/node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "dev": true, - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" } }, "node_modules/block-stream2": { @@ -1992,6 +1988,18 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/bser": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", @@ -2001,30 +2009,6 @@ "node-int64": "^0.4.0" } }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, "node_modules/buffer-crc32": { "version": "0.2.13", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", @@ -2040,16 +2024,6 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, - "node_modules/buildcheck": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/buildcheck/-/buildcheck-0.0.6.tgz", - "integrity": "sha512-8f9ZJCUXyT1M35Jx7MkBgmBMo3oHTTBIPLiY9xyL0pl3T5RwcPEY8cUHr5LBNfu/fk6c2T4DJZuVM/8ZZT2D2A==", - "dev": true, - "optional": true, - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/c8": { "version": "7.14.0", "resolved": "https://registry.npmjs.org/c8/-/c8-7.14.0.tgz", @@ -2185,12 +2159,6 @@ "is-regex": "^1.0.3" } }, - "node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "dev": true - }, "node_modules/ci-info": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", @@ -2317,6 +2285,19 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true }, + "node_modules/convict": { + "version": "6.2.4", + "resolved": "https://registry.npmjs.org/convict/-/convict-6.2.4.tgz", + "integrity": "sha512-qN60BAwdMVdofckX7AlohVJ2x9UvjTNoKVXCL2LxFk1l7757EJqf1nySdMkPQer0bt8kQ5lQiyZ9/2NvrFBuwQ==", + "dev": true, + "dependencies": { + "lodash.clonedeep": "^4.5.0", + "yargs-parser": "^20.2.7" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/cookiejar": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", @@ -2331,21 +2312,6 @@ "dev": true, "hasInstallScript": true }, - "node_modules/cpu-features": { - "version": "0.0.9", - "resolved": "https://registry.npmjs.org/cpu-features/-/cpu-features-0.0.9.tgz", - "integrity": "sha512-AKjgn2rP2yJyfbepsmLfiYcmtNn/2eUvocUyM/09yB0YDiz39HteK/5/T4Onf0pmdYDMgkBoGvRLvEguzyL7wQ==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "dependencies": { - "buildcheck": "~0.0.6", - "nan": "^2.17.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/create-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", @@ -2572,35 +2538,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/docker-modem": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-5.0.1.tgz", - "integrity": "sha512-vqrE/nrweCyzmCpVpdFRC41qS+tfTF+IoUKlTZr52O82urbUqdfyJBGWMvT01pYUprWepLr8IkyVTEWJKRTQSg==", - "dev": true, - "dependencies": { - "debug": "^4.1.1", - "readable-stream": "^3.5.0", - "split-ca": "^1.0.1", - "ssh2": "^1.11.0" - }, - "engines": { - "node": ">= 8.0" - } - }, - "node_modules/dockerode": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/dockerode/-/dockerode-4.0.0.tgz", - "integrity": "sha512-3LF7/3MPz5+9RsUo91rD0MCcx0yxjC9bnbtgtVjOLKyKxlZSJ7/Kk3OPAgARlwlWHqXwAGYhmkAHYx7IwD0tJQ==", - "dev": true, - "dependencies": { - "@balena/dockerignore": "^1.0.2", - "docker-modem": "^5.0.0", - "tar-fs": "~2.0.1" - }, - "engines": { - "node": ">= 8.0" - } - }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -2643,15 +2580,6 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, - "dependencies": { - "once": "^1.4.0" - } - }, "node_modules/entities": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", @@ -2903,12 +2831,6 @@ "url": "https://ko-fi.com/tunnckoCore/commissions" } }, - "node_modules/fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "dev": true - }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -3162,26 +3084,6 @@ "node": ">=10.17.0" } }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, "node_modules/import-local": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", @@ -5343,6 +5245,12 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", + "dev": true + }, "node_modules/lodash.get": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", @@ -5355,6 +5263,12 @@ "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", "dev": true }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true + }, "node_modules/longest": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", @@ -5433,6 +5347,12 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, "node_modules/makeerror": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", @@ -5614,25 +5534,12 @@ "node": ">=10" } }, - "node_modules/mkdirp-classic": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", - "dev": true - }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, - "node_modules/nan": { - "version": "2.18.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.18.0.tgz", - "integrity": "sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w==", - "dev": true, - "optional": true - }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -6125,16 +6032,6 @@ "integrity": "sha512-GMu3M5nUL3fju4/egXwZO0XLi6fW/K3T3VTgFQ14GxNi8btlxgT5qZL//JwZFm/2Fa64J/PNS8AZeys3wiMkVA==", "dev": true }, - "node_modules/pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, "node_modules/pure-rand": { "version": "6.0.4", "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.4.tgz", @@ -6436,12 +6333,6 @@ } ] }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, "node_modules/sax": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz", @@ -6558,12 +6449,6 @@ "source-map": "^0.6.0" } }, - "node_modules/split-ca": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/split-ca/-/split-ca-1.0.1.tgz", - "integrity": "sha512-Q5thBSxp5t8WPTTJQS59LrGqOZqOsrhDGDVm8azCqIBjSBd7nd9o2PM+mDulQQkh8h//4U6hFZnc/mul8t5pWQ==", - "dev": true - }, "node_modules/split-on-first": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz", @@ -6579,24 +6464,6 @@ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "dev": true }, - "node_modules/ssh2": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/ssh2/-/ssh2-1.14.0.tgz", - "integrity": "sha512-AqzD1UCqit8tbOKoj6ztDDi1ffJZ2rV2SwlgrVVrHPkV5vWqGJOVp5pmtj18PunkPJAuKQsnInyKV+/Nb2bUnA==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "asn1": "^0.2.6", - "bcrypt-pbkdf": "^1.0.2" - }, - "engines": { - "node": ">=10.16.0" - }, - "optionalDependencies": { - "cpu-features": "~0.0.8", - "nan": "^2.17.0" - } - }, "node_modules/stack-utils": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", @@ -6820,34 +6687,6 @@ "integrity": "sha512-GQ3gtYFSOAxSMN/apGtDKKkbJf+8izz5YfbGqIsUc7AMiQOapARZ76dhilRY2h39cynYxBFdafQo5HUL5vgkrg==", "dev": true }, - "node_modules/tar-fs": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.0.1.tgz", - "integrity": "sha512-6tzWDMeroL87uF/+lin46k+Q+46rAJ0SyPGz7OW7wTgblI273hsBqk2C1j0/xNadNLKDTUL9BukSjB7cwgmlPA==", - "dev": true, - "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.0.0" - } - }, - "node_modules/tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "dev": true, - "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -6904,6 +6743,91 @@ "integrity": "sha512-nfjOAu/zAWmX9tgwi5NRp7O7zTDUD1miHiB40klUnAh9qnL1iXdgzcz/i5dMaL5jahcBAaSfmNOBBJBLJW8TEg==", "dev": true }, + "node_modules/ts-jest": { + "version": "29.1.1", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.1.tgz", + "integrity": "sha512-D6xjnnbP17cC85nliwGiL+tpoKN0StpgE0TeOjXQTU6MVCfsB4v7aW05CgQ/1OywGb0x/oy9hHFnN+sczTiRaA==", + "dev": true, + "dependencies": { + "bs-logger": "0.x", + "fast-json-stable-stringify": "2.x", + "jest-util": "^29.0.0", + "json5": "^2.2.3", + "lodash.memoize": "4.x", + "make-error": "1.x", + "semver": "^7.5.3", + "yargs-parser": "^21.0.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/types": "^29.0.0", + "babel-jest": "^29.0.0", + "jest": "^29.0.0", + "typescript": ">=4.3 <6" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + } + } + }, + "node_modules/ts-jest/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ts-jest/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ts-jest/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/ts-jest/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, "node_modules/ts-map": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/ts-map/-/ts-map-1.0.3.tgz", @@ -6916,12 +6840,6 @@ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", "dev": true }, - "node_modules/tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", - "dev": true - }, "node_modules/type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", @@ -6944,16 +6862,16 @@ } }, "node_modules/typescript": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", + "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", "dev": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" }, "engines": { - "node": ">=4.2.0" + "node": ">=14.17" } }, "node_modules/uc.micro": { diff --git a/package.json b/package.json index d38baf6c..724b57cf 100644 --- a/package.json +++ b/package.json @@ -1,14 +1,19 @@ { "devDependencies": { + "@jest/globals": "^29.7.0", + "@types/supertest": "^6.0.2", "better-docs": "^2.7.2", + "convict": "^6.2.4", "jest": "^29.7.0", "jsdoc": "^4.0.2", "minio": "^7.1.3", "njs-types": "^0.8.1", "supertest": "^6.3.3", - "taffydb": "^2.7.3" + "taffydb": "^2.7.3", + "ts-jest": "^29.1.1", + "typescript": "^5.3.3" }, "scripts": { - "test": "jest" + "test": "jest --runInBand" } } diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..1795415f --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,109 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig to read more about this file */ + + /* Projects */ + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "ES2020", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + + /* Modules */ + "module": "nodenext", /* Specify what module code is generated. */ + // "rootDir": "./", /* Specify the root folder within your source files. */ + // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ + // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ + // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ + // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ + // "resolveJsonModule": true, /* Enable importing .json files. */ + // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ + // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ + // "outDir": "./", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + + /* Type Checking */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true, /* Skip type checking all .d.ts files. */ + } +}