diff --git a/.github/workflows/lab.yml b/.github/workflows/lab.yml new file mode 100644 index 0000000..21a5a0a --- /dev/null +++ b/.github/workflows/lab.yml @@ -0,0 +1,39 @@ +name: Sigyn Lab CI + +on: + push: + branches: [main] + + pull_request: + paths: + - "package-lock.json" + - "src/lab/**" + - ".github/workflows/lab.yml" + - "!src/lab/**/*.md" + +permissions: + contents: read + +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [22.x] + fail-fast: false + steps: + - name: Harden Runner + uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2 + with: + egress-policy: audit + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 + with: + node-version: ${{ matrix.node-version }} + - name: Install dependencies + run: npm ci + - name: Build packages + run: npm run build + - name: Unit tests + run: npm run test --workspace=src/lab diff --git a/package-lock.json b/package-lock.json index b52a1b6..29d5b39 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,7 @@ "src/slack", "src/teams", "src/pattern", - "src/demo-api" + "src/lab" ], "devDependencies": { "@openally/config.eslint": "^1.3.0", @@ -1004,14 +1004,14 @@ "resolved": "src/config", "link": true }, - "node_modules/@sigyn/demo-api": { - "resolved": "src/demo-api", - "link": true - }, "node_modules/@sigyn/discord": { "resolved": "src/discord", "link": true }, + "node_modules/@sigyn/lab": { + "resolved": "src/lab", + "link": true + }, "node_modules/@sigyn/logql": { "resolved": "src/logql", "link": true @@ -6275,9 +6275,9 @@ "license": "Apache-2.0" }, "node_modules/ts-pattern": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/ts-pattern/-/ts-pattern-5.6.0.tgz", - "integrity": "sha512-SL8u60X5+LoEy9tmQHWCdPc2hhb2pKI6I1tU5Jue3v8+iRqZdcT3mWPwKKJy1fMfky6uha82c8ByHAE8PMhKHw==", + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/ts-pattern/-/ts-pattern-5.6.1.tgz", + "integrity": "sha512-k126YkAs7sZFGTIr6rEdEbxOkqj4cEKAfhwpJMQcwt2M1SuwSD4PM2ClfeUuIlLwEMtVotiZhOsqXNNWqAMWKA==", "license": "MIT" }, "node_modules/tsd": { @@ -7198,7 +7198,49 @@ } }, "src/demo-api": { - "name": "@sigyn/demo-api", + "extraneous": true + }, + "src/discord": { + "name": "@sigyn/discord", + "version": "2.0.0", + "license": "MIT", + "dependencies": { + "@myunisoft/httpie": "^5.0.1" + }, + "engines": { + "node": ">=20" + } + }, + "src/discord/node_modules/@myunisoft/httpie": { + "version": "5.0.1", + "license": "MIT", + "dependencies": { + "@openally/result": "^1.2.1", + "content-type": "^1.0.5", + "lru-cache": "^11.0.0", + "statuses": "^2.0.1", + "undici": "^6.9.0" + }, + "engines": { + "node": ">=20" + } + }, + "src/discord/node_modules/lru-cache": { + "version": "11.0.2", + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, + "src/discord/node_modules/undici": { + "version": "6.21.1", + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, + "src/lab": { + "name": "@sigyn/lab", "version": "1.0.0", "license": "MIT", "dependencies": { @@ -7212,7 +7254,7 @@ "node": ">=20" } }, - "src/demo-api/node_modules/@fastify/ajv-compiler": { + "src/lab/node_modules/@fastify/ajv-compiler": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-3.6.0.tgz", "integrity": "sha512-LwdXQJjmMD+GwLOkP7TVC68qa+pSSogeWWmznRJ/coyTcfe9qA05AHFSe1eZFwK6q+xVRpChnvFUkf1iYaSZsQ==", @@ -7223,7 +7265,7 @@ "fast-uri": "^2.0.0" } }, - "src/demo-api/node_modules/@fastify/ajv-compiler/node_modules/ajv-formats": { + "src/lab/node_modules/@fastify/ajv-compiler/node_modules/ajv-formats": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", @@ -7240,13 +7282,13 @@ } } }, - "src/demo-api/node_modules/@fastify/error": { + "src/lab/node_modules/@fastify/error": { "version": "3.4.1", "resolved": "https://registry.npmjs.org/@fastify/error/-/error-3.4.1.tgz", "integrity": "sha512-wWSvph+29GR783IhmvdwWnN4bUxTD01Vm5Xad4i7i1VuAOItLvbPAb69sb0IQ2N57yprvhNIwAP5B6xfKTmjmQ==", "license": "MIT" }, - "src/demo-api/node_modules/@fastify/fast-json-stringify-compiler": { + "src/lab/node_modules/@fastify/fast-json-stringify-compiler": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/@fastify/fast-json-stringify-compiler/-/fast-json-stringify-compiler-4.3.0.tgz", "integrity": "sha512-aZAXGYo6m22Fk1zZzEUKBvut/CIIQe/BapEORnxiD5Qr0kPHqqI69NtEMCme74h+at72sPhbkb4ZrLd1W3KRLA==", @@ -7255,7 +7297,7 @@ "fast-json-stringify": "^5.7.0" } }, - "src/demo-api/node_modules/@fastify/merge-json-schemas": { + "src/lab/node_modules/@fastify/merge-json-schemas": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/@fastify/merge-json-schemas/-/merge-json-schemas-0.1.1.tgz", "integrity": "sha512-fERDVz7topgNjtXsJTTW1JKLy0rhuLRcquYqNR9rF7OcVpCa2OVW49ZPDIhaRRCaUuvVxI+N416xUoF76HNSXA==", @@ -7264,7 +7306,7 @@ "fast-deep-equal": "^3.1.3" } }, - "src/demo-api/node_modules/avvio": { + "src/lab/node_modules/avvio": { "version": "8.4.0", "resolved": "https://registry.npmjs.org/avvio/-/avvio-8.4.0.tgz", "integrity": "sha512-CDSwaxINFy59iNwhYnkvALBwZiTydGkOecZyPkqBpABYR1KqGEsET0VOOYDwtleZSUIdeY36DC2bSZ24CO1igA==", @@ -7274,7 +7316,7 @@ "fastq": "^1.17.1" } }, - "src/demo-api/node_modules/cookie": { + "src/lab/node_modules/cookie": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", @@ -7283,7 +7325,7 @@ "node": ">= 0.6" } }, - "src/demo-api/node_modules/fast-json-stringify": { + "src/lab/node_modules/fast-json-stringify": { "version": "5.16.1", "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-5.16.1.tgz", "integrity": "sha512-KAdnLvy1yu/XrRtP+LJnxbBGrhN+xXu+gt3EUvZhYGKCr3lFHq/7UFJHHFgmJKoqlh6B40bZLEv7w46B0mqn1g==", @@ -7298,13 +7340,13 @@ "rfdc": "^1.2.0" } }, - "src/demo-api/node_modules/fast-uri": { + "src/lab/node_modules/fast-uri": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-2.4.0.tgz", "integrity": "sha512-ypuAmmMKInk5q7XcepxlnUWDLWv4GFtaJqAzWKqn62IpQ3pejtr5dTVbt3vwqVaMKmkNR55sTT+CqUKIaT21BA==", "license": "MIT" }, - "src/demo-api/node_modules/fastify": { + "src/lab/node_modules/fastify": { "version": "4.29.0", "resolved": "https://registry.npmjs.org/fastify/-/fastify-4.29.0.tgz", "integrity": "sha512-MaaUHUGcCgC8fXQDsDtioaCcag1fmPJ9j64vAKunqZF4aSub040ZGi/ag8NGE2714yREPOKZuHCfpPzuUD3UQQ==", @@ -7338,7 +7380,7 @@ "toad-cache": "^3.3.0" } }, - "src/demo-api/node_modules/find-my-way": { + "src/lab/node_modules/find-my-way": { "version": "8.2.2", "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-8.2.2.tgz", "integrity": "sha512-Dobi7gcTEq8yszimcfp/R7+owiT4WncAJ7VTTgFH1jYJ5GaG1FbhjwDG820hptN0QDFvzVY3RfCzdInvGPGzjA==", @@ -7352,7 +7394,7 @@ "node": ">=14" } }, - "src/demo-api/node_modules/json-schema-ref-resolver": { + "src/lab/node_modules/json-schema-ref-resolver": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-schema-ref-resolver/-/json-schema-ref-resolver-1.0.1.tgz", "integrity": "sha512-EJAj1pgHc1hxF6vo2Z3s69fMjO1INq6eGHXZ8Z6wCQeldCuwxGK9Sxf4/cScGn3FZubCVUehfWtcDM/PLteCQw==", @@ -7361,7 +7403,7 @@ "fast-deep-equal": "^3.1.3" } }, - "src/demo-api/node_modules/light-my-request": { + "src/lab/node_modules/light-my-request": { "version": "5.14.0", "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-5.14.0.tgz", "integrity": "sha512-aORPWntbpH5esaYpGOOmri0OHDOe3wC5M2MQxZ9dvMLZm6DnaAn0kJlcbU9hwsQgLzmZyReKwFwwPkR+nHu5kA==", @@ -7372,13 +7414,13 @@ "set-cookie-parser": "^2.4.1" } }, - "src/demo-api/node_modules/process-warning": { + "src/lab/node_modules/process-warning": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-3.0.0.tgz", "integrity": "sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==", "license": "MIT" }, - "src/demo-api/node_modules/ret": { + "src/lab/node_modules/ret": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/ret/-/ret-0.4.3.tgz", "integrity": "sha512-0f4Memo5QP7WQyUEAYUO3esD/XjOc3Zjjg5CPsAq1p8sIu0XPeMbHJemKA0BO7tV0X7+A0FoEpbmHXWxPyD3wQ==", @@ -7387,7 +7429,7 @@ "node": ">=10" } }, - "src/demo-api/node_modules/safe-regex2": { + "src/lab/node_modules/safe-regex2": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-3.1.0.tgz", "integrity": "sha512-RAAZAGbap2kBfbVhvmnTFv73NWLMvDGOITFYTZBAaY8eR+Ir4ef7Up/e7amo+y1+AH+3PtLkrt9mvcTsG9LXug==", @@ -7396,54 +7438,12 @@ "ret": "~0.4.0" } }, - "src/demo-api/node_modules/secure-json-parse": { + "src/lab/node_modules/secure-json-parse": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==", "license": "BSD-3-Clause" }, - "src/discord": { - "name": "@sigyn/discord", - "version": "2.0.0", - "license": "MIT", - "dependencies": { - "@myunisoft/httpie": "^5.0.1" - }, - "engines": { - "node": ">=20" - } - }, - "src/discord/node_modules/@myunisoft/httpie": { - "version": "5.0.1", - "license": "MIT", - "dependencies": { - "@openally/result": "^1.2.1", - "content-type": "^1.0.5", - "lru-cache": "^11.0.0", - "statuses": "^2.0.1", - "undici": "^6.9.0" - }, - "engines": { - "node": ">=20" - } - }, - "src/discord/node_modules/lru-cache": { - "version": "11.0.2", - "license": "ISC", - "engines": { - "node": "20 || >=22" - } - }, - "src/discord/node_modules/undici": { - "version": "6.21.1", - "license": "MIT", - "engines": { - "node": ">=18.17" - } - }, - "src/lab": { - "extraneous": true - }, "src/logql": { "name": "@sigyn/logql", "version": "2.2.0", diff --git a/package.json b/package.json index a65162e..6560c2a 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "src/slack", "src/teams", "src/pattern", - "src/demo-api" + "src/lab" ], "devDependencies": { "@openally/config.eslint": "^1.3.0", diff --git a/src/demo-api/README.md b/src/demo-api/README.md deleted file mode 100644 index 1be7523..0000000 --- a/src/demo-api/README.md +++ /dev/null @@ -1,5 +0,0 @@ -TBC. - -1. build: `npm run build` -2. start: `npm run start` -3. check the result on `http://localhost:3000/` diff --git a/src/demo-api/src/index.ts b/src/demo-api/src/index.ts deleted file mode 100644 index f1b9e8a..0000000 --- a/src/demo-api/src/index.ts +++ /dev/null @@ -1,39 +0,0 @@ -// Import Node.js Dependencies -import path from "node:path"; - -// Import Third-party Dependencies -import { DockerComposeEnvironment } from "testcontainers"; -import { GrafanaApi } from "@myunisoft/loki"; - -// Import Internal Dependencies -import { LogGenerator } from "./seeder/logs.js"; - -const environment = await new DockerComposeEnvironment( - // "../" because we are in ./dist/ folder after TSC compilation - path.join(__dirname, "../docker"), - "docker-compose.yaml" -).up(); -const loki = environment.getContainer("loki"); -const lokiUrl = `http://${loki.getHost()}:${loki.getMappedPort(3100)}`; - -const api = new GrafanaApi({ - remoteApiURL: lokiUrl, - apiToken: "" -}); - -const logGenerator = new LogGenerator({ - count: 500, - labels: { app: "demo" } -}); -const logs = [...logGenerator.generate()]; - -await api.Loki.push(logs); - -console.log("Logs ready on http://localhost:3000/explore"); - -// keep process alive -process.stdin.resume(); - -process.once("exit", () => { - environment.stop(); -}); diff --git a/src/lab/README.md b/src/lab/README.md new file mode 100644 index 0000000..58b0280 --- /dev/null +++ b/src/lab/README.md @@ -0,0 +1,120 @@ +

+ Lab +

+ +

+ A test and demonstration tool for generating and exploring logs in both formatted and JSON styles, integrated with Grafana Loki via Docker. +

+ +

+ + npm version + + + size + + + ossf scorecard + + + + + + license + +

+ +## 🚧 Requirements + +- [Node.js](https://nodejs.org/en/) version 20 or higher + +## 🚀 Getting Started + +This package is available in the Node Package Repository and can be easily installed with [npm](https://doc.npmjs.com/getting-started/what-is-npm) or [yarn](https://yarnpkg.com) + +```bash +$ npm i @sigyn/lab +# or +$ yarn add @sigyn/lab +``` + +## 📚 Usage + +```ts +import { run } from "@sigyn/lab"; + +await run({ + count: 200, + labels: { + app: "demo-app" + } +}); +// Grafana Loki ready on http://localhost:3000 with 200 random logs. +``` + +## 🌐 API + +### `run(options?: LogGeneratorOptions): Promise` + +Run Grafana + Loki via Docker (using `testcontainers`) with multiple logs. + +See [`LogGenerator`](#loggenerator) for options. + +Default options: +```js +{ + count: 500, + labels: { app: "demo" } +} +``` + +### `LogGenerator` + +The `LogGenerator` class is responsible for generating logs in two different styles: formatted strings and JSON objects. These logs are designed for testing, demonstrations, and integration with Grafana Loki. + +```ts +new LogGenerator(options: LogGeneratorOptions) +``` + +**Constructor parameters** + +`options` (optional): An object to customize log generation: +- `options.mode` (optional): Specifies the style of the logs. Can be: + - `"formated"`: Generates logs as formatted strings. + - `"json"`: G**enerates logs as JSON objects. + - `"random"` **(default)**: Randomly chooses between `"formated"` and `"json"` for each log. +- `options.count` (optional): The number of logs to generate. Default is 100. +- `options.labels` (optional): An object containing custom labels to associate with each log stream. No labels by default. +- `options.startUnixEpoch` (optional): The start timestamp for logs in nanoseconds. Default is one hour before the current time. +- `options.endUnixEpoch` (optional): The end timestamp for logs in nanoseconds. Default is the current time. + +### `LogGenerator.generate()` + +Generates logs based on the provided options. This method is a generator function that yields individual log entries. + +Example usage: + +```ts +const generator = new LogGenerator({ + count: 10, + mode: "json" +}); +const logs = [...generator.generate()]; + +console.log(logs); +``` + +## 🖋️ Interfaces + +```ts +interface LogGeneratorOptions { + mode?: Mode; + count?: number; + labels?: Record; + startUnixEpoch?: number; + endUnixEpoch?: number; +} +``` + +## License +MIT diff --git a/src/demo-api/docker/config/grafana-datasources.yml b/src/lab/docker/config/grafana-datasources.yml similarity index 100% rename from src/demo-api/docker/config/grafana-datasources.yml rename to src/lab/docker/config/grafana-datasources.yml diff --git a/src/demo-api/docker/docker-compose.yaml b/src/lab/docker/docker-compose.yaml similarity index 100% rename from src/demo-api/docker/docker-compose.yaml rename to src/lab/docker/docker-compose.yaml diff --git a/src/demo-api/package.json b/src/lab/package.json similarity index 69% rename from src/demo-api/package.json rename to src/lab/package.json index 6626446..c06f0b2 100644 --- a/src/demo-api/package.json +++ b/src/lab/package.json @@ -1,27 +1,28 @@ { - "name": "@sigyn/demo-api", + "name": "@sigyn/lab", "version": "1.0.0", - "description": "TBC", + "description": "A test and demonstration tool for generating and exploring logs in both formatted and JSON styles, integrated with Grafana Loki via Docker.", "engines": { "node": ">=20" }, "scripts": { "build": "tsup src/index.ts --format esm --clean", "start": "node ./dist/index.js", - "lint": "eslint src" + "lint": "eslint src test", + "test": "glob -c \"tsx --test\" \"./test/**/*.spec.ts\"" }, "repository": { "type": "git", "url": "git+https://github.com/MyUnisoft/sigyn.git", - "directory": "src/demo-api" + "directory": "src/lab" }, "bugs": { "url": "https://github.com/MyUnisoft/sigyn/issues" }, - "homepage": "https://github.com/MyUnisoft/sigyn/blob/main/src/demo-api/README.md", + "homepage": "https://github.com/MyUnisoft/sigyn/blob/main/src/lab/README.md", "files": [ "dist", - "data" + "docker" ], "keywords": [], "author": "GENTILHOMME Thomas ", diff --git a/src/lab/src/index.ts b/src/lab/src/index.ts new file mode 100644 index 0000000..f5db833 --- /dev/null +++ b/src/lab/src/index.ts @@ -0,0 +1,48 @@ +// Import Node.js Dependencies +import path from "node:path"; +import url from "node:url"; + +// Import Third-party Dependencies +import { DockerComposeEnvironment } from "testcontainers"; +import { GrafanaApi } from "@myunisoft/loki"; + +// Import Internal Dependencies +import { LogGenerator, type LogGeneratorOptions } from "./seeder/logs.js"; +export { LogGenerator }; + +// CONSTANTS +const kDefaultOptions = { + count: 500, + labels: { app: "demo" } +}; + +const __dirname = path.dirname(url.fileURLToPath(import.meta.url)); + +export async function run(options: LogGeneratorOptions = kDefaultOptions) { + const environment = await new DockerComposeEnvironment( + // "../" because we are in ./dist/ folder after TSC compilation + path.join(__dirname, "../docker"), + "docker-compose.yaml" + ).up(); + const loki = environment.getContainer("loki"); + const lokiUrl = `http://${loki.getHost()}:${loki.getMappedPort(3100)}`; + + const api = new GrafanaApi({ + remoteApiURL: lokiUrl, + apiToken: "" + }); + + const logGenerator = new LogGenerator(options); + const logs = [...logGenerator.generate()]; + + await api.Loki.push(logs); + + console.log("Logs ready on http://localhost:3000/explore"); + + // keep process alive + process.stdin.resume(); + + process.once("exit", () => { + environment.stop(); + }); +} diff --git a/src/demo-api/src/seeder/http.ts b/src/lab/src/seeder/http.ts similarity index 100% rename from src/demo-api/src/seeder/http.ts rename to src/lab/src/seeder/http.ts diff --git a/src/demo-api/src/seeder/logs.ts b/src/lab/src/seeder/logs.ts similarity index 75% rename from src/demo-api/src/seeder/logs.ts rename to src/lab/src/seeder/logs.ts index 75d4770..0fb8aaf 100644 --- a/src/demo-api/src/seeder/logs.ts +++ b/src/lab/src/seeder/logs.ts @@ -30,6 +30,10 @@ export interface Superhero { company: string; } +interface SendOptions { + debug?: boolean; +} + export class LogGenerator { mode: Mode; count: number; @@ -70,10 +74,13 @@ export class LogGenerator { return modes[Math.floor(Math.random() * modes.length)]; } - refresh() { + #refresh() { if (this.mode === "random") { this.currentMode = LogGenerator.randomMode(); } + else { + this.currentMode = this.mode; + } this.timestamp = (this.startUnixEpoch + Math.floor(Math.random() * (this.endUnixEpoch - this.startUnixEpoch))).toString(); this.ip = randIp(); @@ -85,24 +92,24 @@ export class LogGenerator { * generate() { while (this.count--) { - this.refresh(); + this.#refresh(); const hero = randSuperhero() as Superhero; - yield this.debug(hero); - yield* this.log(hero); + yield this.#debug(hero); + yield* this.#log(hero); } } - get baseHeader() { + get #baseHeader() { return `${this.ip} <${this.reqId}>`; } - baseFormatted() { - return `${this.baseHeader} "${this.method} /api/v1/superhero"`; + #baseFormatted() { + return `${this.#baseHeader} "${this.method} /api/v1/superhero"`; } - baseJson() { + #baseJson() { return { ip: this.ip, reqId: this.reqId, @@ -113,10 +120,12 @@ export class LogGenerator { }; } - send(message: string): LokiIngestLogs { + #send(message: string, options: SendOptions = {}): LokiIngestLogs { + const { debug = false } = options; + return { stream: { - level: this.level, + level: debug ? "debug" : this.level, format: this.currentMode, ...this.labels }, @@ -126,28 +135,28 @@ export class LogGenerator { }; } - debug( + #debug( hero: Superhero ) { if (this.currentMode === "formated") { - return this.send( - `${this.baseHeader} (realName:${hero.realName}|alterEgo:${hero.alterEgo}|company:${hero.company})` - ); + const log = `${this.#baseHeader} (realName:${hero.realName}|alterEgo:${hero.alterEgo}|company:${hero.company})`; + + return this.#send(log, { debug: true }); } - const base = this.baseJson(); + const base = this.#baseJson(); Object.assign(base.req, { body: { ...hero } }); - return this.send(JSON.stringify({ ...base })); + return this.#send(JSON.stringify({ ...base }), { debug: true }); } - * log( + * #log( hero: Superhero ) { if (this.status === 500) { const error = new Error("Internal Server Error"); - yield this.send(error.stack!); + yield this.#send(error.stack!); return; } @@ -156,25 +165,25 @@ export class LogGenerator { const error = new Error(`Unknown superhero: ${hero.realName}`); if (this.currentMode === "formated") { - yield this.send(`${this.baseFormatted()} 400 (error: ${error.message})`); + yield this.#send(`${this.#baseFormatted()} 400 (error: ${error.message})`); } else { - const { req, ...base } = this.baseJson(); + const { req, ...base } = this.#baseJson(); Object.assign(base, { res: { status: 400, error: error.message } }); - yield this.send(JSON.stringify({ ...base })); + yield this.#send(JSON.stringify({ ...base })); } return; } if (this.currentMode === "formated") { - yield this.send(`${this.baseFormatted()} 200 (time: ${random(1, 1200)}ms)`); + yield this.#send(`${this.#baseFormatted()} 200 (time: ${random(1, 1200)}ms)`); return; } - const base = this.baseJson(); + const base = this.#baseJson(); Object.assign(base, { res: { status: 200, count: random(1, 50), time: random(1, 1200) } }); } } diff --git a/src/demo-api/src/utils.ts b/src/lab/src/utils.ts similarity index 100% rename from src/demo-api/src/utils.ts rename to src/lab/src/utils.ts diff --git a/src/lab/test/seeder-http.spec.ts b/src/lab/test/seeder-http.spec.ts new file mode 100644 index 0000000..93eacd7 --- /dev/null +++ b/src/lab/test/seeder-http.spec.ts @@ -0,0 +1,35 @@ +// Import Node.js Dependencies +import { describe, it } from "node:test"; +import assert from "node:assert"; + +// Import Internal Dependencies +import { randomStatus } from "../src/seeder/http.js"; + +describe("randomStatus()", { concurrency: 1 }, () => { + it("level should be critical when status is 500", (tc) => { + tc.mock.method(global.Math, "random", () => 0.9); + + const { status, level } = randomStatus(); + + assert.strictEqual(status, 500); + assert.strictEqual(level, "critical"); + }); + + it("level should be error when status is 400", (tc) => { + tc.mock.method(global.Math, "random", () => 0.7); + + const { status, level } = randomStatus(); + + assert.strictEqual(status, 400); + assert.strictEqual(level, "error"); + }); + + it("level should be info when status is 200", (tc) => { + tc.mock.method(global.Math, "random", () => 0.1); + + const { status, level } = randomStatus(); + + assert.strictEqual(status, 200); + assert.strictEqual(level, "info"); + }); +}); diff --git a/src/lab/test/seeder-logs.spec.ts b/src/lab/test/seeder-logs.spec.ts new file mode 100644 index 0000000..6efd26b --- /dev/null +++ b/src/lab/test/seeder-logs.spec.ts @@ -0,0 +1,49 @@ +import { describe, it } from "node:test"; +import assert from "node:assert"; + +// Import Internal Dependencies +import { LogGenerator } from "../src/seeder/logs.js"; + +describe("LogGenerator", () => { + it("should generate 2 logs (one debug log + one random log)", (tc) => { + // Mock so we have a deterministic test with 200 - info log + tc.mock.method(global.Math, "random", () => 0.1); + + const logGenerator = new LogGenerator({ count: 1, mode: "formated" }); + const logs = [...logGenerator.generate()]; + assert.strictEqual(logs.length, 2); + + const [debugLog, infoLog] = logs; + assert.deepStrictEqual(debugLog.stream, { + level: "debug", + format: "formated" + }); + assert.deepStrictEqual(infoLog.stream, { + level: "info", + format: "formated" + }); + }); + + it("should generate mixed logs (formated and json)", () => { + const logGenerator = new LogGenerator({ count: 200, mode: "random" }); + const logs = [...logGenerator.generate()]; + + assert.ok(logs.find((log) => log.stream.format === "formated")); + assert.ok(logs.find((log) => log.stream.format === "json")); + }); + + it("should generate formated logs", () => { + const logGenerator = new LogGenerator({ count: 200, mode: "formated" }); + const logs = [...logGenerator.generate()]; + + assert.ok(logs.every((log) => log.stream.format === "formated")); + }); + + it("should generate json logs", () => { + const logGenerator = new LogGenerator({ count: 200, mode: "json" }); + const logs = [...logGenerator.generate()]; + + assert.ok(logs.every((log) => log.stream.format === "json")); + }); +}); + diff --git a/src/demo-api/tsconfig.json b/src/lab/tsconfig.json similarity index 100% rename from src/demo-api/tsconfig.json rename to src/lab/tsconfig.json diff --git a/tsconfig.json b/tsconfig.json index c290748..727ddf9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -29,7 +29,7 @@ "path": "./src/pattern" }, { - "path": "./src/demo-api" + "path": "./src/lab" } ] }