diff --git a/.gitattributes b/.gitattributes index a6344aac8c09253b3b630fb776ae94478aa0275b..318a043684eef6a6bd62c130755a1d554cda5196 100644 --- a/.gitattributes +++ b/.gitattributes @@ -33,3 +33,6 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text *.zip filter=lfs diff=lfs merge=lfs -text *.zst filter=lfs diff=lfs merge=lfs -text *tfevents* filter=lfs diff=lfs merge=lfs -text +services/slides/node_modules/.bin/esbuild filter=lfs diff=lfs merge=lfs -text +services/slides/node_modules/@esbuild/darwin-arm64/bin/esbuild filter=lfs diff=lfs merge=lfs -text +services/slides/node_modules/esbuild/bin/esbuild filter=lfs diff=lfs merge=lfs -text diff --git a/services/slides/Dockerfile b/services/slides/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..1bea6a8bc9f14d8a97dd499f53bc9567c524599f --- /dev/null +++ b/services/slides/Dockerfile @@ -0,0 +1,10 @@ +FROM node:22-slim + +WORKDIR /app +COPY package.json tsconfig.json ./ +RUN npm install +COPY src ./src +RUN npm run build + +EXPOSE 8787 +CMD ["npm", "start"] diff --git a/services/slides/dist/charts.js b/services/slides/dist/charts.js new file mode 100644 index 0000000000000000000000000000000000000000..e0c0f827e43f5c6ff0c9c12c7af90efeae7dcd2e --- /dev/null +++ b/services/slides/dist/charts.js @@ -0,0 +1,7 @@ +export function chartTable(chart) { + const series = chart.series[0]; + return { + labels: series?.points.map((point) => point[0]) ?? [], + values: series?.points.map((point) => point[1]) ?? [] + }; +} diff --git a/services/slides/dist/layouts.js b/services/slides/dist/layouts.js new file mode 100644 index 0000000000000000000000000000000000000000..2b558cce9cf1b78cf6955bab27c843109a231606 --- /dev/null +++ b/services/slides/dist/layouts.js @@ -0,0 +1 @@ +export const layouts = ["title", "section", "bullets", "chart_and_insight", "image_reference"]; diff --git a/services/slides/dist/render.js b/services/slides/dist/render.js new file mode 100644 index 0000000000000000000000000000000000000000..5aff5b0a9d389befb6a776f8597d009019bc2ca4 --- /dev/null +++ b/services/slides/dist/render.js @@ -0,0 +1,55 @@ +import * as PptxModule from "pptxgenjs"; +import { cleanHex, normalizeTheme } from "./themes.js"; +export async function renderPresentation(spec) { + const PptxGenJS = (PptxModule.default ?? PptxModule); + const pptx = new PptxGenJS(); + pptx.layout = "LAYOUT_WIDE"; + pptx.author = "GLM Visual Variable Runtime"; + pptx.subject = "Generated from visual evidence"; + pptx.title = spec.title; + const theme = normalizeTheme(spec.theme); + pptx.defineSlideMaster({ + title: "VVR_MASTER", + background: { color: cleanHex(theme.background) }, + objects: [ + { rect: { x: 0, y: 0, w: 13.333, h: 0.12, fill: { color: cleanHex(theme.accent) }, line: { color: cleanHex(theme.accent) } } } + ], + slideNumber: { x: 12.3, y: 7.15, color: cleanHex(theme.secondary) } + }); + const slides = spec.slides.length ? spec.slides : [{ layout: "title", title: spec.title }]; + for (const item of slides) { + const slide = pptx.addSlide("VVR_MASTER"); + slide.addText(item.title, { + x: 0.7, + y: 0.65, + w: 11.9, + h: 0.6, + fontFace: "Aptos Display", + fontSize: item.layout === "title" ? 34 : 26, + bold: true, + color: cleanHex(theme.primary), + margin: 0 + }); + if (item.subtitle) { + slide.addText(item.subtitle, { x: 0.75, y: 1.35, w: 11.7, h: 0.4, fontSize: 16, color: cleanHex(theme.secondary) }); + } + const bullets = [...(item.bullets ?? []), ...(item.insights ?? [])]; + if (bullets.length) { + slide.addText(bullets.map((text) => ({ text, options: { bullet: { indent: 14 }, hanging: 4 } })), { + x: 0.9, + y: 2.0, + w: 11.4, + h: 4.6, + fontSize: 18, + color: cleanHex(theme.text_dark), + breakLine: false, + fit: "shrink" + }); + } + if (item.notes || spec.provenance?.length) { + slide.addNotes(`Provenance: ${JSON.stringify(spec.provenance ?? [])}\n${item.notes ?? ""}`); + } + } + const data = await pptx.write({ outputType: "nodebuffer" }); + return Buffer.from(data); +} diff --git a/services/slides/dist/server.js b/services/slides/dist/server.js new file mode 100644 index 0000000000000000000000000000000000000000..16a610c9820cbc5d24f3cc8057ce50a4c28655c5 --- /dev/null +++ b/services/slides/dist/server.js @@ -0,0 +1,28 @@ +import Fastify from "fastify"; +import { z } from "zod"; +import { renderPresentation } from "./render.js"; +const app = Fastify({ logger: true }); +const Slide = z.object({ + layout: z.enum(["title", "section", "bullets", "chart_and_insight", "image_reference"]).default("bullets"), + title: z.string(), + subtitle: z.string().optional(), + bullets: z.array(z.string()).default([]), + insights: z.array(z.string()).default([]), + notes: z.string().optional() +}); +const Presentation = z.object({ + title: z.string(), + theme: z.record(z.unknown()).default({}), + slides: z.array(Slide).default([]), + provenance: z.array(z.record(z.unknown())).default([]) +}); +app.get("/health", async () => ({ status: "ok" })); +app.post("/render", async (request, reply) => { + const spec = Presentation.parse(request.body); + const pptx = await renderPresentation(spec); + reply.header("content-type", "application/vnd.openxmlformats-officedocument.presentationml.presentation"); + reply.header("content-disposition", "attachment; filename=\"visual-runtime-deck.pptx\""); + return reply.send(pptx); +}); +const port = Number(process.env.PORT ?? 8787); +await app.listen({ host: "0.0.0.0", port }); diff --git a/services/slides/dist/themes.js b/services/slides/dist/themes.js new file mode 100644 index 0000000000000000000000000000000000000000..415d522257ab8f4120422097d152f41e07045519 --- /dev/null +++ b/services/slides/dist/themes.js @@ -0,0 +1,15 @@ +export function normalizeTheme(theme) { + return { + background: theme.background ?? "F5F1E8", + surface: theme.surface ?? "FFFFFF", + primary: theme.primary ?? "16324F", + secondary: theme.secondary ?? "587B7F", + accent: theme.accent ?? "E07A5F", + text_dark: theme.text_dark ?? "182026", + text_light: theme.text_light ?? "FFFFFF", + data_series: theme.data_series ?? ["16324F", "E07A5F", "81B29A", "F2CC8F"] + }; +} +export function cleanHex(value) { + return value.replace("#", "").toUpperCase(); +} diff --git a/services/slides/node_modules/.bin/esbuild b/services/slides/node_modules/.bin/esbuild new file mode 100644 index 0000000000000000000000000000000000000000..fa0433571f85e995b4b214441fa8ee004250d8a9 --- /dev/null +++ b/services/slides/node_modules/.bin/esbuild @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e2dc9a52440a2a34f09434a2f4843cb1e30f84e40dcf238976ec61ef8cd7f36a +size 10573778 diff --git a/services/slides/node_modules/.bin/image-size b/services/slides/node_modules/.bin/image-size new file mode 100644 index 0000000000000000000000000000000000000000..f8ba67694e30bf0cddd1028f2cfde28ae0cae6f6 --- /dev/null +++ b/services/slides/node_modules/.bin/image-size @@ -0,0 +1,53 @@ +#!/usr/bin/env node +/* eslint-disable @typescript-eslint/no-var-requires */ +'use strict' + +const fs = require('fs') +const path = require('path') +const { imageSize } = require('..') + +const files = process.argv.slice(2) + +if (!files.length) { + console.error('Usage: image-size image1 [image2] [image3] ...') + process.exit(-1) +} + +const red = ['\x1B[31m', '\x1B[39m'] +// const bold = ['\x1B[1m', '\x1B[22m'] +const grey = ['\x1B[90m', '\x1B[39m'] +const green = ['\x1B[32m', '\x1B[39m'] + +function colorize(text, color) { + return color[0] + text + color[1] +} + +files.forEach(function (image) { + try { + if (fs.existsSync(path.resolve(image))) { + const greyX = colorize('x', grey) + const greyImage = colorize(image, grey) + const size = imageSize(image) + const sizes = size.images || [size] + sizes.forEach((size) => { + let greyType = '' + if (size.type) { + greyType = colorize(' (' + size.type + ')', grey) + } + console.info( + colorize(size.width, green) + + greyX + + colorize(size.height, green) + + ' - ' + + greyImage + + greyType, + ) + }) + } else { + console.error("file doesn't exist - ", image) + } + } catch (e) { + // console.error(e.stack) + console.error(colorize(e.message, red), '-', image) + } +}) diff --git a/services/slides/node_modules/.bin/pino b/services/slides/node_modules/.bin/pino new file mode 100644 index 0000000000000000000000000000000000000000..939b117bcef076a272d18bdae2cbf63a73b51fb8 --- /dev/null +++ b/services/slides/node_modules/.bin/pino @@ -0,0 +1,6 @@ +#!/usr/bin/env node +console.error( + '`pino` cli has been removed. Use `pino-pretty` cli instead.\n' + + '\nSee: https://github.com/pinojs/pino-pretty' +) +process.exit(1) diff --git a/services/slides/node_modules/.bin/safe-regex2 b/services/slides/node_modules/.bin/safe-regex2 new file mode 100644 index 0000000000000000000000000000000000000000..393098a5e1db3169c088c9d39458912f81e8f4d7 --- /dev/null +++ b/services/slides/node_modules/.bin/safe-regex2 @@ -0,0 +1,58 @@ +#!/usr/bin/env node +'use strict' + +const { parseArgs } = require('node:util') +const { safeRegex } = require('../index.js') + +const { version } = require('../package.json') + +const { values: options, positionals } = parseArgs({ + allowPositionals: true, + options: { + version: { + type: 'boolean', + short: 'v', + default: false, + }, + help: { + type: 'boolean', + short: 'h', + default: false, + } + }, +}) + +function help () { + console.log(`Usage: safe-regex2 [options] + +Check if a regular expression is safe to use in a production environment. + +Options: + -v, --version Display the version number + -h, --help Display this help message + The regular expression to check` + ) +} + +if (options.help) { + help() +} else if (options.version) { + console.log(version) +} else { + if (positionals.length === 0) { + help() + } else if (positionals.length > 1) { + console.error('Error: Too many positional arguments.') + help() + } else { + const regex = positionals[0] + const isSafe = safeRegex(regex) + if (isSafe === false) { + console.error('Provided regex is invalid or unsafe.') + process.exit(1) + } else { + console.log('Provided regex is safe.') + process.exit(0) + } + } +} diff --git a/services/slides/node_modules/.bin/semver b/services/slides/node_modules/.bin/semver new file mode 100644 index 0000000000000000000000000000000000000000..9ae8aadb95fbd9a67a0e8da3a1c8eacc86e86201 --- /dev/null +++ b/services/slides/node_modules/.bin/semver @@ -0,0 +1,195 @@ +#!/usr/bin/env node +// Standalone semver comparison program. +// Exits successfully and prints matching version(s) if +// any supplied version is valid and passes all tests. + +'use strict' + +const argv = process.argv.slice(2) + +let versions = [] + +const range = [] + +let inc = null + +const version = require('../package.json').version + +let loose = false + +let includePrerelease = false + +let coerce = false + +let rtl = false + +let identifier + +let identifierBase + +const semver = require('../') +const parseOptions = require('../internal/parse-options') + +let reverse = false + +let options = {} + +const main = () => { + if (!argv.length) { + return help() + } + while (argv.length) { + let a = argv.shift() + const indexOfEqualSign = a.indexOf('=') + if (indexOfEqualSign !== -1) { + const value = a.slice(indexOfEqualSign + 1) + a = a.slice(0, indexOfEqualSign) + argv.unshift(value) + } + + switch (a) { + case '-rv': case '-rev': case '--rev': case '--reverse': + reverse = true + break + case '-l': case '--loose': + loose = true + break + case '-p': case '--include-prerelease': + includePrerelease = true + break + case '-v': case '--version': + versions.push(argv.shift()) + break + case '-i': case '--inc': case '--increment': + if (semver.RELEASE_TYPES.includes(argv[0]) || (argv[0] === 'release')) { + inc = { value: argv.shift(), maybeErrantValue: null, option: a } + } else { + inc = { value: 'patch', maybeErrantValue: argv[0], option: a } + } + break + case '--preid': + identifier = argv.shift() + break + case '-r': case '--range': + range.push(argv.shift()) + break + case '-n': + identifierBase = argv.shift() + if (identifierBase === 'false') { + identifierBase = false + } + break + case '-c': case '--coerce': + coerce = true + break + case '--rtl': + rtl = true + break + case '--ltr': + rtl = false + break + case '-h': case '--help': case '-?': + return help() + default: + versions.push(a) + break + } + } + + options = parseOptions({ loose, includePrerelease, rtl }) + + if ( + inc && + versions.includes(inc.maybeErrantValue) && + !semver.valid(inc.maybeErrantValue, options) + ) { + console.warn(`Invalid value for ${inc.option}; defaulting to 'patch'. This may become a failure in future major versions.`) + } + + versions = versions.map((v) => { + return coerce ? (semver.coerce(v, options) || { version: v }).version : v + }).filter((v) => { + return semver.valid(v, options) + }) + if (!versions.length) { + return fail() + } + if (inc && (versions.length !== 1 || range.length)) { + return failInc() + } + + for (let i = 0, l = range.length; i < l; i++) { + versions = versions.filter((v) => { + return semver.satisfies(v, range[i], options) + }) + if (!versions.length) { + return fail() + } + } + versions + .sort((a, b) => semver[reverse ? 'rcompare' : 'compare'](a, b, options)) + .map(v => semver.clean(v, options)) + .map(v => inc ? semver.inc(v, inc.value, options, identifier, identifierBase) : v) + .forEach(v => console.log(v)) +} + +const failInc = () => { + console.error('--inc can only be used on a single version with no range') + fail() +} + +const fail = () => process.exit(1) + +const help = () => console.log( +`SemVer ${version} + +A JavaScript implementation of the https://semver.org/ specification +Copyright Isaac Z. Schlueter + +Usage: semver [options] [ [...]] +Prints valid versions sorted by SemVer precedence + +Options: +-r --range + Print versions that match the specified range. + +-i --increment [] + Increment a version by the specified level. Level can + be one of: major, minor, patch, premajor, preminor, + prepatch, prerelease, or release. Default level is 'patch'. + Only one version may be specified. + +--preid + Identifier to be used to prefix premajor, preminor, + prepatch or prerelease version increments. + +-l --loose + Interpret versions and ranges loosely + +-p --include-prerelease + Always include prerelease versions in range matching + +-c --coerce + Coerce a string into SemVer if possible + (does not imply --loose) + +--rtl + Coerce version strings right to left + +--ltr + Coerce version strings left to right (default) + +-n + Base number to be used for the prerelease identifier. + Can be either 0 or 1, or false to omit the number altogether. + Defaults to 0. + +Program exits successfully if any valid version satisfies +all supplied ranges, and prints all satisfying versions. + +If no satisfying versions are found, then exits failure. + +Versions are printed in ascending order, so supplying +multiple versions to the utility will just sort them.`) + +main() diff --git a/services/slides/node_modules/.bin/tsc b/services/slides/node_modules/.bin/tsc new file mode 100644 index 0000000000000000000000000000000000000000..19c62bf7a0004aab7bd188aae51ff2564fdfc18d --- /dev/null +++ b/services/slides/node_modules/.bin/tsc @@ -0,0 +1,2 @@ +#!/usr/bin/env node +require('../lib/tsc.js') diff --git a/services/slides/node_modules/.bin/tsserver b/services/slides/node_modules/.bin/tsserver new file mode 100644 index 0000000000000000000000000000000000000000..7143b6a73ab8a901ccf93752cc36f8e9f8191d93 --- /dev/null +++ b/services/slides/node_modules/.bin/tsserver @@ -0,0 +1,2 @@ +#!/usr/bin/env node +require('../lib/tsserver.js') diff --git a/services/slides/node_modules/.bin/tsx b/services/slides/node_modules/.bin/tsx new file mode 100644 index 0000000000000000000000000000000000000000..c3e3c1931d79cb2268ae9b8f4509fcf18110aea5 --- /dev/null +++ b/services/slides/node_modules/.bin/tsx @@ -0,0 +1,55 @@ +#!/usr/bin/env node +var Rn=Object.defineProperty;var a=(t,e)=>Rn(t,"name",{value:e,configurable:!0});import{constants as lt}from"node:os";import bn from"tty";import{transformSync as vn}from"esbuild";import{v as Sn}from"./package-Bj47PlGH.mjs";import{r as Ie,g as Bn,i as $n}from"./get-pipe-path-_tAJyU_v.mjs";import{pathToFileURL as Tn,fileURLToPath as xn}from"node:url";import On from"child_process";import z from"path";import De from"fs";import{i as mu,a as Nn,t as Hn}from"./node-features-B9BBLzwu.mjs";import Pn from"node:path";import Ln from"events";import ge from"util";import In from"stream";import _u from"os";import{g as kn,l as Mn,e as Gn,f as Wn,y as me}from"./index-gbaejti9.mjs";import jn from"node:net";import ct from"node:fs";import{t as Un}from"./temporary-directory-BDDVQOvU.mjs";import"module";const Kn="known-flag",Vn="unknown-flag",zn="argument",{stringify:_e}=JSON,Yn=/\B([A-Z])/g,qn=a(t=>t.replace(Yn,"-$1").toLowerCase(),"v$1"),{hasOwnProperty:Xn}=Object.prototype,Ae=a((t,e)=>Xn.call(t,e),"w$2"),Qn=a(t=>Array.isArray(t),"L$2"),Au=a(t=>typeof t=="function"?[t,!1]:Qn(t)?[t[0],!0]:Au(t.type),"b$2"),Zn=a((t,e)=>t===Boolean?e!=="false":e,"d$2"),Jn=a((t,e)=>typeof e=="boolean"?e:t===Number&&e===""?Number.NaN:t(e),"m$1"),er=/[\s.:=]/,tr=a(t=>{const e=`Flag name ${_e(t)}`;if(t.length===0)throw new Error(`${e} cannot be empty`);if(t.length===1)throw new Error(`${e} must be longer than a character`);const u=t.match(er);if(u)throw new Error(`${e} cannot contain ${_e(u?.[0])}`)},"B"),ur=a(t=>{const e={},u=a((s,n)=>{if(Ae(e,s))throw new Error(`Duplicate flags named ${_e(s)}`);e[s]=n},"r");for(const s in t){if(!Ae(t,s))continue;tr(s);const n=t[s],r=[[],...Au(n),n];u(s,r);const i=qn(s);if(s!==i&&u(i,r),"alias"in n&&typeof n.alias=="string"){const{alias:D}=n,o=`Flag alias ${_e(D)} for flag ${_e(s)}`;if(D.length===0)throw new Error(`${o} cannot be empty`);if(D.length>1)throw new Error(`${o} must be a single character`);u(D,r)}}return e},"K$1"),sr=a((t,e)=>{const u={};for(const s in t){if(!Ae(t,s))continue;const[n,,r,i]=e[s];if(n.length===0&&"default"in i){let{default:D}=i;typeof D=="function"&&(D=D()),u[s]=D}else u[s]=r?n:n.pop()}return u},"_$2"),ke="--",nr=/[.:=]/,rr=/^-{1,2}\w/,ir=a(t=>{if(!rr.test(t))return;const e=!t.startsWith(ke);let u=t.slice(e?1:2),s;const n=u.match(nr);if(n){const{index:r}=n;s=u.slice(r+1),u=u.slice(0,r)}return[u,s,e]},"N"),Dr=a((t,{onFlag:e,onArgument:u})=>{let s;const n=a((r,i)=>{if(typeof s!="function")return!0;s(r,i),s=void 0},"o");for(let r=0;r{for(const[u,s,n]of e.reverse()){if(s){const r=t[u];let i=r.slice(0,s);if(n||(i+=r.slice(s+1)),i!=="-"){t[u]=i;continue}}t.splice(u,1)}},"E"),yu=a((t,e=process.argv.slice(2),{ignore:u}={})=>{const s=[],n=ur(t),r={},i=[];return i[ke]=[],Dr(e,{onFlag(D,o,c){const f=Ae(n,D);if(!u?.(f?Kn:Vn,D,o)){if(f){const[h,l]=n[D],p=Zn(l,o),C=a((g,y)=>{s.push(c),y&&s.push(y),h.push(Jn(l,g||""))},"p");return p===void 0?C:C(p)}Ae(r,D)||(r[D]=[]),r[D].push(o===void 0?!0:o),s.push(c)}},onArgument(D,o,c){u?.(zn,e[o[0]])||(i.push(...D),c?(i[ke]=D,e.splice(o[0])):s.push(o))}}),or(e,s),{flags:sr(t,n),unknownFlags:r,_:i}},"U$2");var ar=Object.create,Me=Object.defineProperty,lr=Object.defineProperties,cr=Object.getOwnPropertyDescriptor,fr=Object.getOwnPropertyDescriptors,hr=Object.getOwnPropertyNames,wu=Object.getOwnPropertySymbols,dr=Object.getPrototypeOf,Ru=Object.prototype.hasOwnProperty,Er=Object.prototype.propertyIsEnumerable,bu=a((t,e,u)=>e in t?Me(t,e,{enumerable:!0,configurable:!0,writable:!0,value:u}):t[e]=u,"W$1"),Ge=a((t,e)=>{for(var u in e||(e={}))Ru.call(e,u)&&bu(t,u,e[u]);if(wu)for(var u of wu(e))Er.call(e,u)&&bu(t,u,e[u]);return t},"p"),ft=a((t,e)=>lr(t,fr(e)),"c"),pr=a(t=>Me(t,"__esModule",{value:!0}),"nD"),Cr=a((t,e)=>()=>(t&&(e=t(t=0)),e),"rD"),Fr=a((t,e)=>()=>(e||t((e={exports:{}}).exports,e),e.exports),"iD"),gr=a((t,e,u,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of hr(e))!Ru.call(t,n)&&n!=="default"&&Me(t,n,{get:a(()=>e[n],"get"),enumerable:!(s=cr(e,n))||s.enumerable});return t},"oD"),mr=a((t,e)=>gr(pr(Me(t!=null?ar(dr(t)):{},"default",{value:t,enumerable:!0})),t),"BD"),K=Cr(()=>{}),_r=Fr((t,e)=>{K(),e.exports=function(){return/\uD83C\uDFF4\uDB40\uDC67\uDB40\uDC62(?:\uDB40\uDC77\uDB40\uDC6C\uDB40\uDC73|\uDB40\uDC73\uDB40\uDC63\uDB40\uDC74|\uDB40\uDC65\uDB40\uDC6E\uDB40\uDC67)\uDB40\uDC7F|(?:\uD83E\uDDD1\uD83C\uDFFF\u200D\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1|\uD83D\uDC69\uD83C\uDFFF\u200D\uD83E\uDD1D\u200D(?:\uD83D[\uDC68\uDC69]))(?:\uD83C[\uDFFB-\uDFFE])|(?:\uD83E\uDDD1\uD83C\uDFFE\u200D\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1|\uD83D\uDC69\uD83C\uDFFE\u200D\uD83E\uDD1D\u200D(?:\uD83D[\uDC68\uDC69]))(?:\uD83C[\uDFFB-\uDFFD\uDFFF])|(?:\uD83E\uDDD1\uD83C\uDFFD\u200D\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1|\uD83D\uDC69\uD83C\uDFFD\u200D\uD83E\uDD1D\u200D(?:\uD83D[\uDC68\uDC69]))(?:\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])|(?:\uD83E\uDDD1\uD83C\uDFFC\u200D\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1|\uD83D\uDC69\uD83C\uDFFC\u200D\uD83E\uDD1D\u200D(?:\uD83D[\uDC68\uDC69]))(?:\uD83C[\uDFFB\uDFFD-\uDFFF])|(?:\uD83E\uDDD1\uD83C\uDFFB\u200D\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1|\uD83D\uDC69\uD83C\uDFFB\u200D\uD83E\uDD1D\u200D(?:\uD83D[\uDC68\uDC69]))(?:\uD83C[\uDFFC-\uDFFF])|\uD83D\uDC68(?:\uD83C\uDFFB(?:\u200D(?:\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D\uD83D\uDC68(?:\uD83C[\uDFFB-\uDFFF])|\uD83D\uDC68(?:\uD83C[\uDFFB-\uDFFF]))|\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFC-\uDFFF])|[\u2695\u2696\u2708]\uFE0F|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD]))?|(?:\uD83C[\uDFFC-\uDFFF])\u200D\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D\uD83D\uDC68(?:\uD83C[\uDFFB-\uDFFF])|\uD83D\uDC68(?:\uD83C[\uDFFB-\uDFFF]))|\u200D(?:\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D)?\uD83D\uDC68|(?:\uD83D[\uDC68\uDC69])\u200D(?:\uD83D\uDC66\u200D\uD83D\uDC66|\uD83D\uDC67\u200D(?:\uD83D[\uDC66\uDC67]))|\uD83D\uDC66\u200D\uD83D\uDC66|\uD83D\uDC67\u200D(?:\uD83D[\uDC66\uDC67])|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFF\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB-\uDFFE])|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFE\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB-\uDFFD\uDFFF])|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFD\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFC\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB\uDFFD-\uDFFF])|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|(?:\uD83C\uDFFF\u200D[\u2695\u2696\u2708]|\uD83C\uDFFE\u200D[\u2695\u2696\u2708]|\uD83C\uDFFD\u200D[\u2695\u2696\u2708]|\uD83C\uDFFC\u200D[\u2695\u2696\u2708]|\u200D[\u2695\u2696\u2708])\uFE0F|\u200D(?:(?:\uD83D[\uDC68\uDC69])\u200D(?:\uD83D[\uDC66\uDC67])|\uD83D[\uDC66\uDC67])|\uD83C\uDFFF|\uD83C\uDFFE|\uD83C\uDFFD|\uD83C\uDFFC)?|(?:\uD83D\uDC69(?:\uD83C\uDFFB\u200D\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D(?:\uD83D[\uDC68\uDC69])|\uD83D[\uDC68\uDC69])|(?:\uD83C[\uDFFC-\uDFFF])\u200D\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D(?:\uD83D[\uDC68\uDC69])|\uD83D[\uDC68\uDC69]))|\uD83E\uDDD1(?:\uD83C[\uDFFB-\uDFFF])\u200D\uD83E\uDD1D\u200D\uD83E\uDDD1)(?:\uD83C[\uDFFB-\uDFFF])|\uD83D\uDC69\u200D\uD83D\uDC69\u200D(?:\uD83D\uDC66\u200D\uD83D\uDC66|\uD83D\uDC67\u200D(?:\uD83D[\uDC66\uDC67]))|\uD83D\uDC69(?:\u200D(?:\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D(?:\uD83D[\uDC68\uDC69])|\uD83D[\uDC68\uDC69])|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFF\u200D(?:\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFE\u200D(?:\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFD\u200D(?:\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFC\u200D(?:\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFB\u200D(?:\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD]))|\uD83E\uDDD1(?:\u200D(?:\uD83E\uDD1D\u200D\uD83E\uDDD1|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFF\u200D(?:\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFE\u200D(?:\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFD\u200D(?:\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFC\u200D(?:\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFB\u200D(?:\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD]))|\uD83D\uDC69\u200D\uD83D\uDC66\u200D\uD83D\uDC66|\uD83D\uDC69\u200D\uD83D\uDC69\u200D(?:\uD83D[\uDC66\uDC67])|\uD83D\uDC69\u200D\uD83D\uDC67\u200D(?:\uD83D[\uDC66\uDC67])|(?:\uD83D\uDC41\uFE0F\u200D\uD83D\uDDE8|\uD83E\uDDD1(?:\uD83C\uDFFF\u200D[\u2695\u2696\u2708]|\uD83C\uDFFE\u200D[\u2695\u2696\u2708]|\uD83C\uDFFD\u200D[\u2695\u2696\u2708]|\uD83C\uDFFC\u200D[\u2695\u2696\u2708]|\uD83C\uDFFB\u200D[\u2695\u2696\u2708]|\u200D[\u2695\u2696\u2708])|\uD83D\uDC69(?:\uD83C\uDFFF\u200D[\u2695\u2696\u2708]|\uD83C\uDFFE\u200D[\u2695\u2696\u2708]|\uD83C\uDFFD\u200D[\u2695\u2696\u2708]|\uD83C\uDFFC\u200D[\u2695\u2696\u2708]|\uD83C\uDFFB\u200D[\u2695\u2696\u2708]|\u200D[\u2695\u2696\u2708])|\uD83D\uDE36\u200D\uD83C\uDF2B|\uD83C\uDFF3\uFE0F\u200D\u26A7|\uD83D\uDC3B\u200D\u2744|(?:(?:\uD83C[\uDFC3\uDFC4\uDFCA]|\uD83D[\uDC6E\uDC70\uDC71\uDC73\uDC77\uDC81\uDC82\uDC86\uDC87\uDE45-\uDE47\uDE4B\uDE4D\uDE4E\uDEA3\uDEB4-\uDEB6]|\uD83E[\uDD26\uDD35\uDD37-\uDD39\uDD3D\uDD3E\uDDB8\uDDB9\uDDCD-\uDDCF\uDDD4\uDDD6-\uDDDD])(?:\uD83C[\uDFFB-\uDFFF])|\uD83D\uDC6F|\uD83E[\uDD3C\uDDDE\uDDDF])\u200D[\u2640\u2642]|(?:\u26F9|\uD83C[\uDFCB\uDFCC]|\uD83D\uDD75)(?:\uFE0F|\uD83C[\uDFFB-\uDFFF])\u200D[\u2640\u2642]|\uD83C\uDFF4\u200D\u2620|(?:\uD83C[\uDFC3\uDFC4\uDFCA]|\uD83D[\uDC6E\uDC70\uDC71\uDC73\uDC77\uDC81\uDC82\uDC86\uDC87\uDE45-\uDE47\uDE4B\uDE4D\uDE4E\uDEA3\uDEB4-\uDEB6]|\uD83E[\uDD26\uDD35\uDD37-\uDD39\uDD3D\uDD3E\uDDB8\uDDB9\uDDCD-\uDDCF\uDDD4\uDDD6-\uDDDD])\u200D[\u2640\u2642]|[\xA9\xAE\u203C\u2049\u2122\u2139\u2194-\u2199\u21A9\u21AA\u2328\u23CF\u23ED-\u23EF\u23F1\u23F2\u23F8-\u23FA\u24C2\u25AA\u25AB\u25B6\u25C0\u25FB\u25FC\u2600-\u2604\u260E\u2611\u2618\u2620\u2622\u2623\u2626\u262A\u262E\u262F\u2638-\u263A\u2640\u2642\u265F\u2660\u2663\u2665\u2666\u2668\u267B\u267E\u2692\u2694-\u2697\u2699\u269B\u269C\u26A0\u26A7\u26B0\u26B1\u26C8\u26CF\u26D1\u26D3\u26E9\u26F0\u26F1\u26F4\u26F7\u26F8\u2702\u2708\u2709\u270F\u2712\u2714\u2716\u271D\u2721\u2733\u2734\u2744\u2747\u2763\u27A1\u2934\u2935\u2B05-\u2B07\u3030\u303D\u3297\u3299]|\uD83C[\uDD70\uDD71\uDD7E\uDD7F\uDE02\uDE37\uDF21\uDF24-\uDF2C\uDF36\uDF7D\uDF96\uDF97\uDF99-\uDF9B\uDF9E\uDF9F\uDFCD\uDFCE\uDFD4-\uDFDF\uDFF5\uDFF7]|\uD83D[\uDC3F\uDCFD\uDD49\uDD4A\uDD6F\uDD70\uDD73\uDD76-\uDD79\uDD87\uDD8A-\uDD8D\uDDA5\uDDA8\uDDB1\uDDB2\uDDBC\uDDC2-\uDDC4\uDDD1-\uDDD3\uDDDC-\uDDDE\uDDE1\uDDE3\uDDE8\uDDEF\uDDF3\uDDFA\uDECB\uDECD-\uDECF\uDEE0-\uDEE5\uDEE9\uDEF0\uDEF3])\uFE0F|\uD83C\uDFF3\uFE0F\u200D\uD83C\uDF08|\uD83D\uDC69\u200D\uD83D\uDC67|\uD83D\uDC69\u200D\uD83D\uDC66|\uD83D\uDE35\u200D\uD83D\uDCAB|\uD83D\uDE2E\u200D\uD83D\uDCA8|\uD83D\uDC15\u200D\uD83E\uDDBA|\uD83E\uDDD1(?:\uD83C\uDFFF|\uD83C\uDFFE|\uD83C\uDFFD|\uD83C\uDFFC|\uD83C\uDFFB)?|\uD83D\uDC69(?:\uD83C\uDFFF|\uD83C\uDFFE|\uD83C\uDFFD|\uD83C\uDFFC|\uD83C\uDFFB)?|\uD83C\uDDFD\uD83C\uDDF0|\uD83C\uDDF6\uD83C\uDDE6|\uD83C\uDDF4\uD83C\uDDF2|\uD83D\uDC08\u200D\u2B1B|\u2764\uFE0F\u200D(?:\uD83D\uDD25|\uD83E\uDE79)|\uD83D\uDC41\uFE0F|\uD83C\uDFF3\uFE0F|\uD83C\uDDFF(?:\uD83C[\uDDE6\uDDF2\uDDFC])|\uD83C\uDDFE(?:\uD83C[\uDDEA\uDDF9])|\uD83C\uDDFC(?:\uD83C[\uDDEB\uDDF8])|\uD83C\uDDFB(?:\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDEE\uDDF3\uDDFA])|\uD83C\uDDFA(?:\uD83C[\uDDE6\uDDEC\uDDF2\uDDF3\uDDF8\uDDFE\uDDFF])|\uD83C\uDDF9(?:\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDED\uDDEF-\uDDF4\uDDF7\uDDF9\uDDFB\uDDFC\uDDFF])|\uD83C\uDDF8(?:\uD83C[\uDDE6-\uDDEA\uDDEC-\uDDF4\uDDF7-\uDDF9\uDDFB\uDDFD-\uDDFF])|\uD83C\uDDF7(?:\uD83C[\uDDEA\uDDF4\uDDF8\uDDFA\uDDFC])|\uD83C\uDDF5(?:\uD83C[\uDDE6\uDDEA-\uDDED\uDDF0-\uDDF3\uDDF7-\uDDF9\uDDFC\uDDFE])|\uD83C\uDDF3(?:\uD83C[\uDDE6\uDDE8\uDDEA-\uDDEC\uDDEE\uDDF1\uDDF4\uDDF5\uDDF7\uDDFA\uDDFF])|\uD83C\uDDF2(?:\uD83C[\uDDE6\uDDE8-\uDDED\uDDF0-\uDDFF])|\uD83C\uDDF1(?:\uD83C[\uDDE6-\uDDE8\uDDEE\uDDF0\uDDF7-\uDDFB\uDDFE])|\uD83C\uDDF0(?:\uD83C[\uDDEA\uDDEC-\uDDEE\uDDF2\uDDF3\uDDF5\uDDF7\uDDFC\uDDFE\uDDFF])|\uD83C\uDDEF(?:\uD83C[\uDDEA\uDDF2\uDDF4\uDDF5])|\uD83C\uDDEE(?:\uD83C[\uDDE8-\uDDEA\uDDF1-\uDDF4\uDDF6-\uDDF9])|\uD83C\uDDED(?:\uD83C[\uDDF0\uDDF2\uDDF3\uDDF7\uDDF9\uDDFA])|\uD83C\uDDEC(?:\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEE\uDDF1-\uDDF3\uDDF5-\uDDFA\uDDFC\uDDFE])|\uD83C\uDDEB(?:\uD83C[\uDDEE-\uDDF0\uDDF2\uDDF4\uDDF7])|\uD83C\uDDEA(?:\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDED\uDDF7-\uDDFA])|\uD83C\uDDE9(?:\uD83C[\uDDEA\uDDEC\uDDEF\uDDF0\uDDF2\uDDF4\uDDFF])|\uD83C\uDDE8(?:\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDEE\uDDF0-\uDDF5\uDDF7\uDDFA-\uDDFF])|\uD83C\uDDE7(?:\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEF\uDDF1-\uDDF4\uDDF6-\uDDF9\uDDFB\uDDFC\uDDFE\uDDFF])|\uD83C\uDDE6(?:\uD83C[\uDDE8-\uDDEC\uDDEE\uDDF1\uDDF2\uDDF4\uDDF6-\uDDFA\uDDFC\uDDFD\uDDFF])|[#\*0-9]\uFE0F\u20E3|\u2764\uFE0F|(?:\uD83C[\uDFC3\uDFC4\uDFCA]|\uD83D[\uDC6E\uDC70\uDC71\uDC73\uDC77\uDC81\uDC82\uDC86\uDC87\uDE45-\uDE47\uDE4B\uDE4D\uDE4E\uDEA3\uDEB4-\uDEB6]|\uD83E[\uDD26\uDD35\uDD37-\uDD39\uDD3D\uDD3E\uDDB8\uDDB9\uDDCD-\uDDCF\uDDD4\uDDD6-\uDDDD])(?:\uD83C[\uDFFB-\uDFFF])|(?:\u26F9|\uD83C[\uDFCB\uDFCC]|\uD83D\uDD75)(?:\uFE0F|\uD83C[\uDFFB-\uDFFF])|\uD83C\uDFF4|(?:[\u270A\u270B]|\uD83C[\uDF85\uDFC2\uDFC7]|\uD83D[\uDC42\uDC43\uDC46-\uDC50\uDC66\uDC67\uDC6B-\uDC6D\uDC72\uDC74-\uDC76\uDC78\uDC7C\uDC83\uDC85\uDC8F\uDC91\uDCAA\uDD7A\uDD95\uDD96\uDE4C\uDE4F\uDEC0\uDECC]|\uD83E[\uDD0C\uDD0F\uDD18-\uDD1C\uDD1E\uDD1F\uDD30-\uDD34\uDD36\uDD77\uDDB5\uDDB6\uDDBB\uDDD2\uDDD3\uDDD5])(?:\uD83C[\uDFFB-\uDFFF])|(?:[\u261D\u270C\u270D]|\uD83D[\uDD74\uDD90])(?:\uFE0F|\uD83C[\uDFFB-\uDFFF])|[\u270A\u270B]|\uD83C[\uDF85\uDFC2\uDFC7]|\uD83D[\uDC08\uDC15\uDC3B\uDC42\uDC43\uDC46-\uDC50\uDC66\uDC67\uDC6B-\uDC6D\uDC72\uDC74-\uDC76\uDC78\uDC7C\uDC83\uDC85\uDC8F\uDC91\uDCAA\uDD7A\uDD95\uDD96\uDE2E\uDE35\uDE36\uDE4C\uDE4F\uDEC0\uDECC]|\uD83E[\uDD0C\uDD0F\uDD18-\uDD1C\uDD1E\uDD1F\uDD30-\uDD34\uDD36\uDD77\uDDB5\uDDB6\uDDBB\uDDD2\uDDD3\uDDD5]|\uD83C[\uDFC3\uDFC4\uDFCA]|\uD83D[\uDC6E\uDC70\uDC71\uDC73\uDC77\uDC81\uDC82\uDC86\uDC87\uDE45-\uDE47\uDE4B\uDE4D\uDE4E\uDEA3\uDEB4-\uDEB6]|\uD83E[\uDD26\uDD35\uDD37-\uDD39\uDD3D\uDD3E\uDDB8\uDDB9\uDDCD-\uDDCF\uDDD4\uDDD6-\uDDDD]|\uD83D\uDC6F|\uD83E[\uDD3C\uDDDE\uDDDF]|[\u231A\u231B\u23E9-\u23EC\u23F0\u23F3\u25FD\u25FE\u2614\u2615\u2648-\u2653\u267F\u2693\u26A1\u26AA\u26AB\u26BD\u26BE\u26C4\u26C5\u26CE\u26D4\u26EA\u26F2\u26F3\u26F5\u26FA\u26FD\u2705\u2728\u274C\u274E\u2753-\u2755\u2757\u2795-\u2797\u27B0\u27BF\u2B1B\u2B1C\u2B50\u2B55]|\uD83C[\uDC04\uDCCF\uDD8E\uDD91-\uDD9A\uDE01\uDE1A\uDE2F\uDE32-\uDE36\uDE38-\uDE3A\uDE50\uDE51\uDF00-\uDF20\uDF2D-\uDF35\uDF37-\uDF7C\uDF7E-\uDF84\uDF86-\uDF93\uDFA0-\uDFC1\uDFC5\uDFC6\uDFC8\uDFC9\uDFCF-\uDFD3\uDFE0-\uDFF0\uDFF8-\uDFFF]|\uD83D[\uDC00-\uDC07\uDC09-\uDC14\uDC16-\uDC3A\uDC3C-\uDC3E\uDC40\uDC44\uDC45\uDC51-\uDC65\uDC6A\uDC79-\uDC7B\uDC7D-\uDC80\uDC84\uDC88-\uDC8E\uDC90\uDC92-\uDCA9\uDCAB-\uDCFC\uDCFF-\uDD3D\uDD4B-\uDD4E\uDD50-\uDD67\uDDA4\uDDFB-\uDE2D\uDE2F-\uDE34\uDE37-\uDE44\uDE48-\uDE4A\uDE80-\uDEA2\uDEA4-\uDEB3\uDEB7-\uDEBF\uDEC1-\uDEC5\uDED0-\uDED2\uDED5-\uDED7\uDEEB\uDEEC\uDEF4-\uDEFC\uDFE0-\uDFEB]|\uD83E[\uDD0D\uDD0E\uDD10-\uDD17\uDD1D\uDD20-\uDD25\uDD27-\uDD2F\uDD3A\uDD3F-\uDD45\uDD47-\uDD76\uDD78\uDD7A-\uDDB4\uDDB7\uDDBA\uDDBC-\uDDCB\uDDD0\uDDE0-\uDDFF\uDE70-\uDE74\uDE78-\uDE7A\uDE80-\uDE86\uDE90-\uDEA8\uDEB0-\uDEB6\uDEC0-\uDEC2\uDED0-\uDED6]|(?:[\u231A\u231B\u23E9-\u23EC\u23F0\u23F3\u25FD\u25FE\u2614\u2615\u2648-\u2653\u267F\u2693\u26A1\u26AA\u26AB\u26BD\u26BE\u26C4\u26C5\u26CE\u26D4\u26EA\u26F2\u26F3\u26F5\u26FA\u26FD\u2705\u270A\u270B\u2728\u274C\u274E\u2753-\u2755\u2757\u2795-\u2797\u27B0\u27BF\u2B1B\u2B1C\u2B50\u2B55]|\uD83C[\uDC04\uDCCF\uDD8E\uDD91-\uDD9A\uDDE6-\uDDFF\uDE01\uDE1A\uDE2F\uDE32-\uDE36\uDE38-\uDE3A\uDE50\uDE51\uDF00-\uDF20\uDF2D-\uDF35\uDF37-\uDF7C\uDF7E-\uDF93\uDFA0-\uDFCA\uDFCF-\uDFD3\uDFE0-\uDFF0\uDFF4\uDFF8-\uDFFF]|\uD83D[\uDC00-\uDC3E\uDC40\uDC42-\uDCFC\uDCFF-\uDD3D\uDD4B-\uDD4E\uDD50-\uDD67\uDD7A\uDD95\uDD96\uDDA4\uDDFB-\uDE4F\uDE80-\uDEC5\uDECC\uDED0-\uDED2\uDED5-\uDED7\uDEEB\uDEEC\uDEF4-\uDEFC\uDFE0-\uDFEB]|\uD83E[\uDD0C-\uDD3A\uDD3C-\uDD45\uDD47-\uDD78\uDD7A-\uDDCB\uDDCD-\uDDFF\uDE70-\uDE74\uDE78-\uDE7A\uDE80-\uDE86\uDE90-\uDEA8\uDEB0-\uDEB6\uDEC0-\uDEC2\uDED0-\uDED6])|(?:[#\*0-9\xA9\xAE\u203C\u2049\u2122\u2139\u2194-\u2199\u21A9\u21AA\u231A\u231B\u2328\u23CF\u23E9-\u23F3\u23F8-\u23FA\u24C2\u25AA\u25AB\u25B6\u25C0\u25FB-\u25FE\u2600-\u2604\u260E\u2611\u2614\u2615\u2618\u261D\u2620\u2622\u2623\u2626\u262A\u262E\u262F\u2638-\u263A\u2640\u2642\u2648-\u2653\u265F\u2660\u2663\u2665\u2666\u2668\u267B\u267E\u267F\u2692-\u2697\u2699\u269B\u269C\u26A0\u26A1\u26A7\u26AA\u26AB\u26B0\u26B1\u26BD\u26BE\u26C4\u26C5\u26C8\u26CE\u26CF\u26D1\u26D3\u26D4\u26E9\u26EA\u26F0-\u26F5\u26F7-\u26FA\u26FD\u2702\u2705\u2708-\u270D\u270F\u2712\u2714\u2716\u271D\u2721\u2728\u2733\u2734\u2744\u2747\u274C\u274E\u2753-\u2755\u2757\u2763\u2764\u2795-\u2797\u27A1\u27B0\u27BF\u2934\u2935\u2B05-\u2B07\u2B1B\u2B1C\u2B50\u2B55\u3030\u303D\u3297\u3299]|\uD83C[\uDC04\uDCCF\uDD70\uDD71\uDD7E\uDD7F\uDD8E\uDD91-\uDD9A\uDDE6-\uDDFF\uDE01\uDE02\uDE1A\uDE2F\uDE32-\uDE3A\uDE50\uDE51\uDF00-\uDF21\uDF24-\uDF93\uDF96\uDF97\uDF99-\uDF9B\uDF9E-\uDFF0\uDFF3-\uDFF5\uDFF7-\uDFFF]|\uD83D[\uDC00-\uDCFD\uDCFF-\uDD3D\uDD49-\uDD4E\uDD50-\uDD67\uDD6F\uDD70\uDD73-\uDD7A\uDD87\uDD8A-\uDD8D\uDD90\uDD95\uDD96\uDDA4\uDDA5\uDDA8\uDDB1\uDDB2\uDDBC\uDDC2-\uDDC4\uDDD1-\uDDD3\uDDDC-\uDDDE\uDDE1\uDDE3\uDDE8\uDDEF\uDDF3\uDDFA-\uDE4F\uDE80-\uDEC5\uDECB-\uDED2\uDED5-\uDED7\uDEE0-\uDEE5\uDEE9\uDEEB\uDEEC\uDEF0\uDEF3-\uDEFC\uDFE0-\uDFEB]|\uD83E[\uDD0C-\uDD3A\uDD3C-\uDD45\uDD47-\uDD78\uDD7A-\uDDCB\uDDCD-\uDDFF\uDE70-\uDE74\uDE78-\uDE7A\uDE80-\uDE86\uDE90-\uDEA8\uDEB0-\uDEB6\uDEC0-\uDEC2\uDED0-\uDED6])\uFE0F|(?:[\u261D\u26F9\u270A-\u270D]|\uD83C[\uDF85\uDFC2-\uDFC4\uDFC7\uDFCA-\uDFCC]|\uD83D[\uDC42\uDC43\uDC46-\uDC50\uDC66-\uDC78\uDC7C\uDC81-\uDC83\uDC85-\uDC87\uDC8F\uDC91\uDCAA\uDD74\uDD75\uDD7A\uDD90\uDD95\uDD96\uDE45-\uDE47\uDE4B-\uDE4F\uDEA3\uDEB4-\uDEB6\uDEC0\uDECC]|\uD83E[\uDD0C\uDD0F\uDD18-\uDD1F\uDD26\uDD30-\uDD39\uDD3C-\uDD3E\uDD77\uDDB5\uDDB6\uDDB8\uDDB9\uDDBB\uDDCD-\uDDCF\uDDD1-\uDDDD])/g}});K(),K(),K();var Ar=a(t=>{var e,u,s;let n=(e=process.stdout.columns)!=null?e:Number.POSITIVE_INFINITY;return typeof t=="function"&&(t=t(n)),t||(t={}),Array.isArray(t)?{columns:t,stdoutColumns:n}:{columns:(u=t.columns)!=null?u:[],stdoutColumns:(s=t.stdoutColumns)!=null?s:n}},"v");K(),K(),K(),K(),K();function yr({onlyFirst:t=!1}={}){let e=["[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)","(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))"].join("|");return new RegExp(e,t?void 0:"g")}a(yr,"w$1");function vu(t){if(typeof t!="string")throw new TypeError(`Expected a \`string\`, got \`${typeof t}\``);return t.replace(yr(),"")}a(vu,"d$1"),K();function wr(t){return Number.isInteger(t)?t>=4352&&(t<=4447||t===9001||t===9002||11904<=t&&t<=12871&&t!==12351||12880<=t&&t<=19903||19968<=t&&t<=42182||43360<=t&&t<=43388||44032<=t&&t<=55203||63744<=t&&t<=64255||65040<=t&&t<=65049||65072<=t&&t<=65131||65281<=t&&t<=65376||65504<=t&&t<=65510||110592<=t&&t<=110593||127488<=t&&t<=127569||131072<=t&&t<=262141):!1}a(wr,"y$1");var Rr=mr(_r());function oe(t){if(typeof t!="string"||t.length===0||(t=vu(t),t.length===0))return 0;t=t.replace((0,Rr.default)()," ");let e=0;for(let u=0;u=127&&s<=159||s>=768&&s<=879||(s>65535&&u++,e+=wr(s)?2:1)}return e}a(oe,"g");var Su=a(t=>Math.max(...t.split(` +`).map(oe)),"b$1"),br=a(t=>{let e=[];for(let u of t){let{length:s}=u,n=s-e.length;for(let r=0;re[r]&&(e[r]=i)}}return e},"k$1");K();var Bu=/^\d+%$/,$u={width:"auto",align:"left",contentWidth:0,paddingLeft:0,paddingRight:0,paddingTop:0,paddingBottom:0,horizontalPadding:0,paddingLeftString:"",paddingRightString:""},vr=a((t,e)=>{var u;let s=[];for(let n=0;n=e){let o=i-e,c=Math.ceil(u.paddingLeft/n*o),f=o-c;u.paddingLeft-=c,u.paddingRight-=f,u.horizontalPadding=u.paddingLeft+u.paddingRight}u.paddingLeftString=u.paddingLeft?" ".repeat(u.paddingLeft):"",u.paddingRightString=u.paddingRight?" ".repeat(u.paddingRight):"";let D=e-u.horizontalPadding;u.width=Math.max(Math.min(u.width,D),r)}}a(Sr,"aD");var Tu=a(()=>Object.assign([],{columns:0}),"G$1");function Br(t,e){let u=[Tu()],[s]=u;for(let n of t){let r=n.width+n.horizontalPadding;s.columns+r>e&&(s=Tu(),u.push(s)),s.push(n),s.columns+=r}for(let n of u){let r=n.reduce((l,p)=>l+p.width+p.horizontalPadding,0),i=e-r;if(i===0)continue;let D=n.filter(l=>"autoOverflow"in l),o=D.filter(l=>l.autoOverflow>0),c=o.reduce((l,p)=>l+p.autoOverflow,0),f=Math.min(c,i);for(let l of o){let p=Math.floor(l.autoOverflow/c*f);l.width+=p,i-=p}let h=Math.floor(i/D.length);for(let l=0;le=>`\x1B[${e+t}m`,"U$1"),Ou=a((t=0)=>e=>`\x1B[${38+t};5;${e}m`,"V$1"),Nu=a((t=0)=>(e,u,s)=>`\x1B[${38+t};2;${e};${u};${s}m`,"Y");function Tr(){let t=new Map,e={modifier:{reset:[0,0],bold:[1,22],dim:[2,22],italic:[3,23],underline:[4,24],overline:[53,55],inverse:[7,27],hidden:[8,28],strikethrough:[9,29]},color:{black:[30,39],red:[31,39],green:[32,39],yellow:[33,39],blue:[34,39],magenta:[35,39],cyan:[36,39],white:[37,39],blackBright:[90,39],redBright:[91,39],greenBright:[92,39],yellowBright:[93,39],blueBright:[94,39],magentaBright:[95,39],cyanBright:[96,39],whiteBright:[97,39]},bgColor:{bgBlack:[40,49],bgRed:[41,49],bgGreen:[42,49],bgYellow:[43,49],bgBlue:[44,49],bgMagenta:[45,49],bgCyan:[46,49],bgWhite:[47,49],bgBlackBright:[100,49],bgRedBright:[101,49],bgGreenBright:[102,49],bgYellowBright:[103,49],bgBlueBright:[104,49],bgMagentaBright:[105,49],bgCyanBright:[106,49],bgWhiteBright:[107,49]}};e.color.gray=e.color.blackBright,e.bgColor.bgGray=e.bgColor.bgBlackBright,e.color.grey=e.color.blackBright,e.bgColor.bgGrey=e.bgColor.bgBlackBright;for(let[u,s]of Object.entries(e)){for(let[n,r]of Object.entries(s))e[n]={open:`\x1B[${r[0]}m`,close:`\x1B[${r[1]}m`},s[n]=e[n],t.set(r[0],r[1]);Object.defineProperty(e,u,{value:s,enumerable:!1})}return Object.defineProperty(e,"codes",{value:t,enumerable:!1}),e.color.close="\x1B[39m",e.bgColor.close="\x1B[49m",e.color.ansi=xu(),e.color.ansi256=Ou(),e.color.ansi16m=Nu(),e.bgColor.ansi=xu(ht),e.bgColor.ansi256=Ou(ht),e.bgColor.ansi16m=Nu(ht),Object.defineProperties(e,{rgbToAnsi256:{value:a((u,s,n)=>u===s&&s===n?u<8?16:u>248?231:Math.round((u-8)/247*24)+232:16+36*Math.round(u/255*5)+6*Math.round(s/255*5)+Math.round(n/255*5),"value"),enumerable:!1},hexToRgb:{value:a(u=>{let s=/(?[a-f\d]{6}|[a-f\d]{3})/i.exec(u.toString(16));if(!s)return[0,0,0];let{colorString:n}=s.groups;n.length===3&&(n=n.split("").map(i=>i+i).join(""));let r=Number.parseInt(n,16);return[r>>16&255,r>>8&255,r&255]},"value"),enumerable:!1},hexToAnsi256:{value:a(u=>e.rgbToAnsi256(...e.hexToRgb(u)),"value"),enumerable:!1},ansi256ToAnsi:{value:a(u=>{if(u<8)return 30+u;if(u<16)return 90+(u-8);let s,n,r;if(u>=232)s=((u-232)*10+8)/255,n=s,r=s;else{u-=16;let o=u%36;s=Math.floor(u/36)/5,n=Math.floor(o/6)/5,r=o%6/5}let i=Math.max(s,n,r)*2;if(i===0)return 30;let D=30+(Math.round(r)<<2|Math.round(n)<<1|Math.round(s));return i===2&&(D+=60),D},"value"),enumerable:!1},rgbToAnsi:{value:a((u,s,n)=>e.ansi256ToAnsi(e.rgbToAnsi256(u,s,n)),"value"),enumerable:!1},hexToAnsi:{value:a(u=>e.ansi256ToAnsi(e.hexToAnsi256(u)),"value"),enumerable:!1}}),e}a(Tr,"AD");var xr=Tr(),Or=xr,We=new Set(["\x1B","\x9B"]),Nr=39,dt="\x07",Hu="[",Hr="]",Pu="m",Et=`${Hr}8;;`,Lu=a(t=>`${We.values().next().value}${Hu}${t}${Pu}`,"J$1"),Iu=a(t=>`${We.values().next().value}${Et}${t}${dt}`,"Q"),Pr=a(t=>t.split(" ").map(e=>oe(e)),"hD"),pt=a((t,e,u)=>{let s=[...e],n=!1,r=!1,i=oe(vu(t[t.length-1]));for(let[D,o]of s.entries()){let c=oe(o);if(i+c<=u?t[t.length-1]+=o:(t.push(o),i=0),We.has(o)&&(n=!0,r=s.slice(D+1).join("").startsWith(Et)),n){r?o===dt&&(n=!1,r=!1):o===Pu&&(n=!1);continue}i+=c,i===u&&D0&&t.length>1&&(t[t.length-2]+=t.pop())},"S$1"),Lr=a(t=>{let e=t.split(" "),u=e.length;for(;u>0&&!(oe(e[u-1])>0);)u--;return u===e.length?t:e.slice(0,u).join(" ")+e.slice(u).join("")},"cD"),Ir=a((t,e,u={})=>{if(u.trim!==!1&&t.trim()==="")return"";let s="",n,r,i=Pr(t),D=[""];for(let[c,f]of t.split(" ").entries()){u.trim!==!1&&(D[D.length-1]=D[D.length-1].trimStart());let h=oe(D[D.length-1]);if(c!==0&&(h>=e&&(u.wordWrap===!1||u.trim===!1)&&(D.push(""),h=0),(h>0||u.trim===!1)&&(D[D.length-1]+=" ",h++)),u.hard&&i[c]>e){let l=e-h,p=1+Math.floor((i[c]-l-1)/e);Math.floor((i[c]-1)/e)e&&h>0&&i[c]>0){if(u.wordWrap===!1&&he&&u.wordWrap===!1){pt(D,f,e);continue}D[D.length-1]+=f}u.trim!==!1&&(D=D.map(c=>Lr(c)));let o=[...D.join(` +`)];for(let[c,f]of o.entries()){if(s+=f,We.has(f)){let{groups:l}=new RegExp(`(?:\\${Hu}(?\\d+)m|\\${Et}(?.*)${dt})`).exec(o.slice(c).join(""))||{groups:{}};if(l.code!==void 0){let p=Number.parseFloat(l.code);n=p===Nr?void 0:p}else l.uri!==void 0&&(r=l.uri.length===0?void 0:l.uri)}let h=Or.codes.get(Number(n));o[c+1]===` +`?(r&&(s+=Iu("")),n&&h&&(s+=Lu(h))):f===` +`&&(n&&h&&(s+=Lu(n)),r&&(s+=Iu(r)))}return s},"dD");function kr(t,e,u){return String(t).normalize().replace(/\r\n/g,` +`).split(` +`).map(s=>Ir(s,e,u)).join(` +`)}a(kr,"T$1");var ku=a(t=>Array.from({length:t}).fill(""),"X");function Mr(t,e){let u=[],s=0;for(let n of t){let r=0,i=n.map(o=>{var c;let f=(c=e[s])!=null?c:"";s+=1,o.preprocess&&(f=o.preprocess(f)),Su(f)>o.width&&(f=kr(f,o.width,{hard:!0}));let h=f.split(` +`);if(o.postprocess){let{postprocess:l}=o;h=h.map((p,C)=>l.call(o,p,C))}return o.paddingTop&&h.unshift(...ku(o.paddingTop)),o.paddingBottom&&h.push(...ku(o.paddingBottom)),h.length>r&&(r=h.length),ft(Ge({},o),{lines:h})}),D=[];for(let o=0;o{var h;let l=(h=f.lines[o])!=null?h:"",p=Number.isFinite(f.width)?" ".repeat(f.width-oe(l)):"",C=f.paddingLeftString;return f.align==="right"&&(C+=p),C+=l,f.align==="left"&&(C+=p),C+f.paddingRightString}).join("");D.push(c)}u.push(D.join(` +`))}return u.join(` +`)}a(Mr,"P");function Gr(t,e){if(!t||t.length===0)return"";let u=br(t),s=u.length;if(s===0)return"";let{stdoutColumns:n,columns:r}=Ar(e);if(r.length>s)throw new Error(`${r.length} columns defined, but only ${s} columns found`);let i=$r(n,r,u);return t.map(D=>Mr(i,D)).join(` +`)}a(Gr,"mD"),K();var Wr=["<",">","=",">=","<="];function jr(t){if(!Wr.includes(t))throw new TypeError(`Invalid breakpoint operator: ${t}`)}a(jr,"xD");function Ur(t){let e=Object.keys(t).map(u=>{let[s,n]=u.split(" ");jr(s);let r=Number.parseInt(n,10);if(Number.isNaN(r))throw new TypeError(`Invalid breakpoint value: ${n}`);let i=t[u];return{operator:s,breakpoint:r,value:i}}).sort((u,s)=>s.breakpoint-u.breakpoint);return u=>{var s;return(s=e.find(({operator:n,breakpoint:r})=>n==="="&&u===r||n===">"&&u>r||n==="<"&&u="&&u>=r||n==="<="&&u<=r))==null?void 0:s.value}}a(Ur,"wD");const Kr=a(t=>t.replace(/[\W_]([a-z\d])?/gi,(e,u)=>u?u.toUpperCase():""),"S"),Vr=a(t=>t.replace(/\B([A-Z])/g,"-$1").toLowerCase(),"q"),zr={"> 80":[{width:"content-width",paddingLeft:2,paddingRight:8},{width:"auto"}],"> 40":[{width:"auto",paddingLeft:2,paddingRight:8,preprocess:a(t=>t.trim(),"preprocess")},{width:"100%",paddingLeft:2,paddingBottom:1}],"> 0":{stdoutColumns:1e3,columns:[{width:"content-width",paddingLeft:2,paddingRight:8},{width:"content-width"}]}};function Yr(t){let e=!1;return{type:"table",data:{tableData:Object.keys(t).sort((u,s)=>u.localeCompare(s)).map(u=>{const s=t[u],n="alias"in s;return n&&(e=!0),{name:u,flag:s,flagFormatted:`--${Vr(u)}`,aliasesEnabled:e,aliasFormatted:n?`-${s.alias}`:void 0}}).map(u=>(u.aliasesEnabled=e,[{type:"flagName",data:u},{type:"flagDescription",data:u}])),tableBreakpoints:zr}}}a(Yr,"D");const Mu=a(t=>!t||(t.version??(t.help?t.help.version:void 0)),"A"),Gu=a(t=>{const e="parent"in t&&t.parent?.name;return(e?`${e} `:"")+t.name},"C");function qr(t){const e=[];t.name&&e.push(Gu(t));const u=Mu(t)??("parent"in t&&Mu(t.parent));if(u&&e.push(`v${u}`),e.length!==0)return{id:"name",type:"text",data:`${e.join(" ")} +`}}a(qr,"R");function Xr(t){const{help:e}=t;if(!(!e||!e.description))return{id:"description",type:"text",data:`${e.description} +`}}a(Xr,"L");function Qr(t){const e=t.help||{};if("usage"in e)return e.usage?{id:"usage",type:"section",data:{title:"Usage:",body:Array.isArray(e.usage)?e.usage.join(` +`):e.usage}}:void 0;if(t.name){const u=[],s=[Gu(t)];if(t.flags&&Object.keys(t.flags).length>0&&s.push("[flags...]"),t.parameters&&t.parameters.length>0){const{parameters:n}=t,r=n.indexOf("--"),i=r>-1&&n.slice(r+1).some(D=>D.startsWith("<"));s.push(n.map(D=>D!=="--"?D:i?"--":"[--]").join(" "))}if(s.length>1&&u.push(s.join(" ")),"commands"in t&&t.commands?.length&&u.push(`${t.name} `),u.length>0)return{id:"usage",type:"section",data:{title:"Usage:",body:u.join(` +`)}}}}a(Qr,"T");function Zr(t){return!("commands"in t)||!t.commands?.length?void 0:{id:"commands",type:"section",data:{title:"Commands:",body:{type:"table",data:{tableData:t.commands.map(e=>[e.options.name,e.options.help?e.options.help.description:""]),tableOptions:[{width:"content-width",paddingLeft:2,paddingRight:8}]}},indentBody:0}}}a(Zr,"_");function Jr(t){if(!(!t.flags||Object.keys(t.flags).length===0))return{id:"flags",type:"section",data:{title:"Flags:",body:Yr(t.flags),indentBody:0}}}a(Jr,"k");function ei(t){const{help:e}=t;if(!e||!e.examples||e.examples.length===0)return;let{examples:u}=e;if(Array.isArray(u)&&(u=u.join(` +`)),u)return{id:"examples",type:"section",data:{title:"Examples:",body:u}}}a(ei,"F");function ti(t){if(!("alias"in t)||!t.alias)return;const{alias:e}=t;return{id:"aliases",type:"section",data:{title:"Aliases:",body:Array.isArray(e)?e.join(", "):e}}}a(ti,"H");const ui=a(t=>[qr,Xr,Qr,Zr,Jr,ei,ti].map(e=>e(t)).filter(Boolean),"U"),si=bn.WriteStream.prototype.hasColors();class ni{static{a(this,"M")}text(e){return e}bold(e){return si?`\x1B[1m${e}\x1B[22m`:e.toLocaleUpperCase()}indentText({text:e,spaces:u}){return e.replace(/^/gm," ".repeat(u))}heading(e){return this.bold(e)}section({title:e,body:u,indentBody:s=2}){return`${(e?`${this.heading(e)} +`:"")+(u?this.indentText({text:this.render(u),spaces:s}):"")} +`}table({tableData:e,tableOptions:u,tableBreakpoints:s}){return Gr(e.map(n=>n.map(r=>this.render(r))),s?Ur(s):u)}flagParameter(e){return e===Boolean?"":e===String?"":e===Number?"":Array.isArray(e)?this.flagParameter(e[0]):""}flagOperator(e){return" "}flagName(e){const{flag:u,flagFormatted:s,aliasesEnabled:n,aliasFormatted:r}=e;let i="";if(r?i+=`${r}, `:n&&(i+=" "),i+=s,"placeholder"in u&&typeof u.placeholder=="string")i+=`${this.flagOperator(e)}${u.placeholder}`;else{const D=this.flagParameter("type"in u?u.type:u);D&&(i+=`${this.flagOperator(e)}${D}`)}return i}flagDefault(e){return JSON.stringify(e)}flagDescription({flag:e}){let u="description"in e?e.description??"":"";if("default"in e){let{default:s}=e;typeof s=="function"&&(s=s()),s&&(u+=` (default: ${this.flagDefault(s)})`)}return u}render(e){if(typeof e=="string")return e;if(Array.isArray(e))return e.map(u=>this.render(u)).join(` +`);if("type"in e&&this[e.type]){const u=this[e.type];if(typeof u=="function")return u.call(this,e.data)}throw new Error(`Invalid node type: ${JSON.stringify(e)}`)}}const Ct=/^[\w.-]+$/,{stringify:ee}=JSON,ri=/[|\\{}()[\]^$+*?.]/;function Ft(t){const e=[];let u,s;for(const n of t){if(s)throw new Error(`Invalid parameter: Spread parameter ${ee(s)} must be last`);const r=n[0],i=n[n.length-1];let D;if(r==="<"&&i===">"&&(D=!0,u))throw new Error(`Invalid parameter: Required parameter ${ee(n)} cannot come after optional parameter ${ee(u)}`);if(r==="["&&i==="]"&&(D=!1,u=n),D===void 0)throw new Error(`Invalid parameter: ${ee(n)}. Must be wrapped in <> (required parameter) or [] (optional parameter)`);let o=n.slice(1,-1);const c=o.slice(-3)==="...";c&&(s=n,o=o.slice(0,-3));const f=o.match(ri);if(f)throw new Error(`Invalid parameter: ${ee(n)}. Invalid character found ${ee(f[0])}`);e.push({name:o,required:D,spread:c})}return e}a(Ft,"w");function gt(t,e,u,s){for(let n=0;n{console.log(e.version)},"f");if(r&&o.flags.version===!0)return c(),process.exit(0);const f=new ni,h=D&&i?.render?i.render:C=>f.render(C),l=a(C=>{const g=ui({...e,...C?{help:C}:{},flags:n});console.log(h(g,f))},"u");if(D&&o.flags.help===!0)return l(),process.exit(0);if(e.parameters){let{parameters:C}=e,g=o._;const y=C.indexOf("--"),B=C.slice(y+1),H=Object.create(null);if(y>-1&&B.length>0){C=C.slice(0,y);const $=o._["--"];g=g.slice(0,-$.length||void 0),gt(H,Ft(C),g,l),gt(H,Ft(B),$,l)}else gt(H,Ft(C),g,l);Object.assign(o._,H)}const p={...o,showVersion:c,showHelp:l};return typeof u=="function"&&u(p),{command:t,...p}}a(Wu,"x");function Di(t,e){const u=new Map;for(const s of e){const n=[s.options.name],{alias:r}=s.options;r&&(Array.isArray(r)?n.push(...r):n.push(r));for(const i of n){if(u.has(i))throw new Error(`Duplicate command name found: ${ee(i)}`);u.set(i,s)}}return u.get(t)}a(Di,"z");function ju(t,e,u=process.argv.slice(2)){if(!t)throw new Error("Options is required");if("name"in t&&(!t.name||!Ct.test(t.name)))throw new Error(`Invalid script name: ${ee(t.name)}`);const s=u[0];if(t.commands&&Ct.test(s)){const n=Di(s,t.commands);if(n)return Wu(n.options.name,{...n.options,parent:t},n.callback,u.slice(1))}return Wu(void 0,t,e,u)}a(ju,"Z");function oi(t,e){if(!t)throw new Error("Command options are required");const{name:u}=t;if(t.name===void 0)throw new Error("Command name is required");if(!Ct.test(u))throw new Error(`Invalid command name ${JSON.stringify(u)}. Command names must be one word.`);return{options:t,callback:e}}a(oi,"G");var ai=typeof globalThis<"u"?globalThis:typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:{};function li(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}a(li,"getDefaultExportFromCjs");var fe={exports:{}},mt,Uu;function ci(){if(Uu)return mt;Uu=1,mt=s,s.sync=n;var t=De;function e(r,i){var D=i.pathExt!==void 0?i.pathExt:process.env.PATHEXT;if(!D||(D=D.split(";"),D.indexOf("")!==-1))return!0;for(var o=0;oObject.assign(new Error(`not found: ${t}`),{code:"ENOENT"}),"getNotFoundError"),qu=a((t,e)=>{const u=e.colon||Ei,s=t.match(/\//)||he&&t.match(/\\/)?[""]:[...he?[process.cwd()]:[],...(e.path||process.env.PATH||"").split(u)],n=he?e.pathExt||process.env.PATHEXT||".EXE;.CMD;.BAT;.COM":"",r=he?n.split(u):[""];return he&&t.indexOf(".")!==-1&&r[0]!==""&&r.unshift(""),{pathEnv:s,pathExt:r,pathExtExe:n}},"getPathInfo"),Xu=a((t,e,u)=>{typeof e=="function"&&(u=e,e={}),e||(e={});const{pathEnv:s,pathExt:n,pathExtExe:r}=qu(t,e),i=[],D=a(c=>new Promise((f,h)=>{if(c===s.length)return e.all&&i.length?f(i):h(Yu(t));const l=s[c],p=/^".*"$/.test(l)?l.slice(1,-1):l,C=Vu.join(p,t),g=!p&&/^\.[\\\/]/.test(t)?t.slice(0,2)+C:C;f(o(g,c,0))}),"step"),o=a((c,f,h)=>new Promise((l,p)=>{if(h===n.length)return l(D(f+1));const C=n[h];zu(c+C,{pathExt:r},(g,y)=>{if(!g&&y)if(e.all)i.push(c+C);else return l(c+C);return l(o(c,f,h+1))})}),"subStep");return u?D(0).then(c=>u(null,c),u):D(0)},"which$1"),pi=a((t,e)=>{e=e||{};const{pathEnv:u,pathExt:s,pathExtExe:n}=qu(t,e),r=[];for(let i=0;i{const e=t.env||process.env;return(t.platform||process.platform)!=="win32"?"PATH":Object.keys(e).reverse().find(s=>s.toUpperCase()==="PATH")||"Path"},"pathKey");yt.exports=Qu,yt.exports.default=Qu;var Fi=yt.exports;const Zu=z,gi=Ci,mi=Fi;function Ju(t,e){const u=t.options.env||process.env,s=process.cwd(),n=t.options.cwd!=null,r=n&&process.chdir!==void 0&&!process.chdir.disabled;if(r)try{process.chdir(t.options.cwd)}catch{}let i;try{i=gi.sync(t.command,{path:u[mi({env:u})],pathExt:e?Zu.delimiter:void 0})}catch{}finally{r&&process.chdir(s)}return i&&(i=Zu.resolve(n?t.options.cwd:"",i)),i}a(Ju,"resolveCommandAttempt");function _i(t){return Ju(t)||Ju(t,!0)}a(_i,"resolveCommand$1");var Ai=_i,wt={};const Rt=/([()\][%!^"`<>&|;, *?])/g;function yi(t){return t=t.replace(Rt,"^$1"),t}a(yi,"escapeCommand");function wi(t,e){return t=`${t}`,t=t.replace(/(\\*)"/g,'$1$1\\"'),t=t.replace(/(\\*)$/,"$1$1"),t=`"${t}"`,t=t.replace(Rt,"^$1"),e&&(t=t.replace(Rt,"^$1")),t}a(wi,"escapeArgument"),wt.command=yi,wt.argument=wi;var Ri=/^#!(.*)/;const bi=Ri;var vi=a((t="")=>{const e=t.match(bi);if(!e)return null;const[u,s]=e[0].replace(/#! ?/,"").split(" "),n=u.split("/").pop();return n==="env"?s:s?`${n} ${s}`:n},"shebangCommand$1");const bt=De,Si=vi;function Bi(t){const u=Buffer.alloc(150);let s;try{s=bt.openSync(t,"r"),bt.readSync(s,u,0,150,0),bt.closeSync(s)}catch{}return Si(u.toString())}a(Bi,"readShebang$1");var $i=Bi;const Ti=z,es=Ai,ts=wt,xi=$i,Oi=process.platform==="win32",Ni=/\.(?:com|exe)$/i,Hi=/node_modules[\\/].bin[\\/][^\\/]+\.cmd$/i;function Pi(t){t.file=es(t);const e=t.file&&xi(t.file);return e?(t.args.unshift(t.file),t.command=e,es(t)):t.file}a(Pi,"detectShebang");function Li(t){if(!Oi)return t;const e=Pi(t),u=!Ni.test(e);if(t.options.forceShell||u){const s=Hi.test(e);t.command=Ti.normalize(t.command),t.command=ts.command(t.command),t.args=t.args.map(r=>ts.argument(r,s));const n=[t.command].concat(t.args).join(" ");t.args=["/d","/s","/c",`"${n}"`],t.command=process.env.comspec||"cmd.exe",t.options.windowsVerbatimArguments=!0}return t}a(Li,"parseNonShell");function Ii(t,e,u){e&&!Array.isArray(e)&&(u=e,e=null),e=e?e.slice(0):[],u=Object.assign({},u);const s={command:t,args:e,options:u,file:void 0,original:{command:t,args:e}};return u.shell?s:Li(s)}a(Ii,"parse$5");var ki=Ii;const vt=process.platform==="win32";function St(t,e){return Object.assign(new Error(`${e} ${t.command} ENOENT`),{code:"ENOENT",errno:"ENOENT",syscall:`${e} ${t.command}`,path:t.command,spawnargs:t.args})}a(St,"notFoundError");function Mi(t,e){if(!vt)return;const u=t.emit;t.emit=function(s,n){if(s==="exit"){const r=us(n,e);if(r)return u.call(t,"error",r)}return u.apply(t,arguments)}}a(Mi,"hookChildProcess");function us(t,e){return vt&&t===1&&!e.file?St(e.original,"spawn"):null}a(us,"verifyENOENT");function Gi(t,e){return vt&&t===1&&!e.file?St(e.original,"spawnSync"):null}a(Gi,"verifyENOENTSync");var Wi={hookChildProcess:Mi,verifyENOENT:us,verifyENOENTSync:Gi,notFoundError:St};const ss=On,Bt=ki,$t=Wi;function ns(t,e,u){const s=Bt(t,e,u),n=ss.spawn(s.command,s.args,s.options);return $t.hookChildProcess(n,s),n}a(ns,"spawn");function ji(t,e,u){const s=Bt(t,e,u),n=ss.spawnSync(s.command,s.args,s.options);return n.error=n.error||$t.verifyENOENTSync(n.status,s),n}a(ji,"spawnSync"),fe.exports=ns,fe.exports.spawn=ns,fe.exports.sync=ji,fe.exports._parse=Bt,fe.exports._enoent=$t;var Ui=fe.exports,Ki=li(Ui);const rs=a((t,e)=>{const u={...process.env},s=["inherit","inherit","inherit"];process.send&&s.push("ipc"),e&&(e.noCache&&(u.TSX_DISABLE_CACHE="1"),e.tsconfigPath&&(u.TSX_TSCONFIG_PATH=e.tsconfigPath));const n=t.filter(r=>r!=="-i"&&r!=="--interactive").length===0;return Ki(process.execPath,["--require",Ie.resolve("./preflight.cjs"),...n?["--require",Ie.resolve("./patch-repl.cjs")]:[],mu(Nn)?"--import":"--loader",Tn(Ie.resolve("./loader.mjs")).toString(),...t],{stdio:s,env:u})},"run");var Ue={};const Vi=z,te="\\\\/",is=`[^${te}]`,ue="\\.",zi="\\+",Yi="\\?",Ke="\\/",qi="(?=.)",Ds="[^/]",Tt=`(?:${Ke}|$)`,os=`(?:^|${Ke})`,xt=`${ue}{1,2}${Tt}`,Xi=`(?!${ue})`,Qi=`(?!${os}${xt})`,Zi=`(?!${ue}{0,1}${Tt})`,Ji=`(?!${xt})`,eD=`[^.${Ke}]`,tD=`${Ds}*?`,as={DOT_LITERAL:ue,PLUS_LITERAL:zi,QMARK_LITERAL:Yi,SLASH_LITERAL:Ke,ONE_CHAR:qi,QMARK:Ds,END_ANCHOR:Tt,DOTS_SLASH:xt,NO_DOT:Xi,NO_DOTS:Qi,NO_DOT_SLASH:Zi,NO_DOTS_SLASH:Ji,QMARK_NO_DOT:eD,STAR:tD,START_ANCHOR:os},uD={...as,SLASH_LITERAL:`[${te}]`,QMARK:is,STAR:`${is}*?`,DOTS_SLASH:`${ue}{1,2}(?:[${te}]|$)`,NO_DOT:`(?!${ue})`,NO_DOTS:`(?!(?:^|[${te}])${ue}{1,2}(?:[${te}]|$))`,NO_DOT_SLASH:`(?!${ue}{0,1}(?:[${te}]|$))`,NO_DOTS_SLASH:`(?!${ue}{1,2}(?:[${te}]|$))`,QMARK_NO_DOT:`[^.${te}]`,START_ANCHOR:`(?:^|[${te}])`,END_ANCHOR:`(?:[${te}]|$)`},sD={alnum:"a-zA-Z0-9",alpha:"a-zA-Z",ascii:"\\x00-\\x7F",blank:" \\t",cntrl:"\\x00-\\x1F\\x7F",digit:"0-9",graph:"\\x21-\\x7E",lower:"a-z",print:"\\x20-\\x7E ",punct:"\\-!\"#$%&'()\\*+,./:;<=>?@[\\]^_`{|}~",space:" \\t\\r\\n\\v\\f",upper:"A-Z",word:"A-Za-z0-9_",xdigit:"A-Fa-f0-9"};var Ve={MAX_LENGTH:1024*64,POSIX_REGEX_SOURCE:sD,REGEX_BACKSLASH:/\\(?![*+?^${}(|)[\]])/g,REGEX_NON_SPECIAL_CHARS:/^[^@![\].,$*+?^{}()|\\/]+/,REGEX_SPECIAL_CHARS:/[-*+?.^${}(|)[\]]/,REGEX_SPECIAL_CHARS_BACKREF:/(\\?)((\W)(\3*))/g,REGEX_SPECIAL_CHARS_GLOBAL:/([-*+?.^${}(|)[\]])/g,REGEX_REMOVE_BACKSLASH:/(?:\[.*?[^\\]\]|\\(?=.))/g,REPLACEMENTS:{"***":"*","**/**":"**","**/**/**":"**"},CHAR_0:48,CHAR_9:57,CHAR_UPPERCASE_A:65,CHAR_LOWERCASE_A:97,CHAR_UPPERCASE_Z:90,CHAR_LOWERCASE_Z:122,CHAR_LEFT_PARENTHESES:40,CHAR_RIGHT_PARENTHESES:41,CHAR_ASTERISK:42,CHAR_AMPERSAND:38,CHAR_AT:64,CHAR_BACKWARD_SLASH:92,CHAR_CARRIAGE_RETURN:13,CHAR_CIRCUMFLEX_ACCENT:94,CHAR_COLON:58,CHAR_COMMA:44,CHAR_DOT:46,CHAR_DOUBLE_QUOTE:34,CHAR_EQUAL:61,CHAR_EXCLAMATION_MARK:33,CHAR_FORM_FEED:12,CHAR_FORWARD_SLASH:47,CHAR_GRAVE_ACCENT:96,CHAR_HASH:35,CHAR_HYPHEN_MINUS:45,CHAR_LEFT_ANGLE_BRACKET:60,CHAR_LEFT_CURLY_BRACE:123,CHAR_LEFT_SQUARE_BRACKET:91,CHAR_LINE_FEED:10,CHAR_NO_BREAK_SPACE:160,CHAR_PERCENT:37,CHAR_PLUS:43,CHAR_QUESTION_MARK:63,CHAR_RIGHT_ANGLE_BRACKET:62,CHAR_RIGHT_CURLY_BRACE:125,CHAR_RIGHT_SQUARE_BRACKET:93,CHAR_SEMICOLON:59,CHAR_SINGLE_QUOTE:39,CHAR_SPACE:32,CHAR_TAB:9,CHAR_UNDERSCORE:95,CHAR_VERTICAL_LINE:124,CHAR_ZERO_WIDTH_NOBREAK_SPACE:65279,SEP:Vi.sep,extglobChars(t){return{"!":{type:"negate",open:"(?:(?!(?:",close:`))${t.STAR})`},"?":{type:"qmark",open:"(?:",close:")?"},"+":{type:"plus",open:"(?:",close:")+"},"*":{type:"star",open:"(?:",close:")*"},"@":{type:"at",open:"(?:",close:")"}}},globChars(t){return t===!0?uD:as}};(function(t){const e=z,u=process.platform==="win32",{REGEX_BACKSLASH:s,REGEX_REMOVE_BACKSLASH:n,REGEX_SPECIAL_CHARS:r,REGEX_SPECIAL_CHARS_GLOBAL:i}=Ve;t.isObject=D=>D!==null&&typeof D=="object"&&!Array.isArray(D),t.hasRegexChars=D=>r.test(D),t.isRegexChar=D=>D.length===1&&t.hasRegexChars(D),t.escapeRegex=D=>D.replace(i,"\\$1"),t.toPosixSlashes=D=>D.replace(s,"/"),t.removeBackslashes=D=>D.replace(n,o=>o==="\\"?"":o),t.supportsLookbehinds=()=>{const D=process.version.slice(1).split(".").map(Number);return D.length===3&&D[0]>=9||D[0]===8&&D[1]>=10},t.isWindows=D=>D&&typeof D.windows=="boolean"?D.windows:u===!0||e.sep==="\\",t.escapeLast=(D,o,c)=>{const f=D.lastIndexOf(o,c);return f===-1?D:D[f-1]==="\\"?t.escapeLast(D,o,f-1):`${D.slice(0,f)}\\${D.slice(f)}`},t.removePrefix=(D,o={})=>{let c=D;return c.startsWith("./")&&(c=c.slice(2),o.prefix="./"),c},t.wrapOutput=(D,o={},c={})=>{const f=c.contains?"":"^",h=c.contains?"":"$";let l=`${f}(?:${D})${h}`;return o.negated===!0&&(l=`(?:^(?!${l}).*$)`),l}})(Ue);const ls=Ue,{CHAR_ASTERISK:Ot,CHAR_AT:nD,CHAR_BACKWARD_SLASH:ye,CHAR_COMMA:rD,CHAR_DOT:Nt,CHAR_EXCLAMATION_MARK:Ht,CHAR_FORWARD_SLASH:cs,CHAR_LEFT_CURLY_BRACE:Pt,CHAR_LEFT_PARENTHESES:Lt,CHAR_LEFT_SQUARE_BRACKET:iD,CHAR_PLUS:DD,CHAR_QUESTION_MARK:fs,CHAR_RIGHT_CURLY_BRACE:oD,CHAR_RIGHT_PARENTHESES:hs,CHAR_RIGHT_SQUARE_BRACKET:aD}=Ve,ds=a(t=>t===cs||t===ye,"isPathSeparator"),Es=a(t=>{t.isPrefix!==!0&&(t.depth=t.isGlobstar?1/0:1)},"depth"),lD=a((t,e)=>{const u=e||{},s=t.length-1,n=u.parts===!0||u.scanToEnd===!0,r=[],i=[],D=[];let o=t,c=-1,f=0,h=0,l=!1,p=!1,C=!1,g=!1,y=!1,B=!1,H=!1,$=!1,Q=!1,G=!1,ne=0,W,A,v={value:"",depth:0,isGlob:!1};const M=a(()=>c>=s,"eos"),F=a(()=>o.charCodeAt(c+1),"peek"),O=a(()=>(W=A,o.charCodeAt(++c)),"advance");for(;c0&&(re=o.slice(0,f),o=o.slice(f),h-=f),T&&C===!0&&h>0?(T=o.slice(0,h),d=o.slice(h)):C===!0?(T="",d=o):T=o,T&&T!==""&&T!=="/"&&T!==o&&ds(T.charCodeAt(T.length-1))&&(T=T.slice(0,-1)),u.unescape===!0&&(d&&(d=ls.removeBackslashes(d)),T&&H===!0&&(T=ls.removeBackslashes(T)));const E={prefix:re,input:t,start:f,base:T,glob:d,isBrace:l,isBracket:p,isGlob:C,isExtglob:g,isGlobstar:y,negated:$,negatedExtglob:Q};if(u.tokens===!0&&(E.maxDepth=0,ds(A)||i.push(v),E.tokens=i),u.parts===!0||u.tokens===!0){let j;for(let b=0;b{if(typeof e.expandRange=="function")return e.expandRange(...t,e);t.sort();const u=`[${t.join("-")}]`;try{new RegExp(u)}catch{return t.map(n=>Y.escapeRegex(n)).join("..")}return u},"expandRange"),de=a((t,e)=>`Missing ${t}: "${e}" - use "\\\\${e}" to match literal characters`,"syntaxError"),It=a((t,e)=>{if(typeof t!="string")throw new TypeError("Expected a string");t=ps[t]||t;const u={...e},s=typeof u.maxLength=="number"?Math.min(Ye,u.maxLength):Ye;let n=t.length;if(n>s)throw new SyntaxError(`Input length: ${n}, exceeds maximum allowed length: ${s}`);const r={type:"bos",value:"",output:u.prepend||""},i=[r],D=u.capture?"":"?:",o=Y.isWindows(e),c=ze.globChars(o),f=ze.extglobChars(c),{DOT_LITERAL:h,PLUS_LITERAL:l,SLASH_LITERAL:p,ONE_CHAR:C,DOTS_SLASH:g,NO_DOT:y,NO_DOT_SLASH:B,NO_DOTS_SLASH:H,QMARK:$,QMARK_NO_DOT:Q,STAR:G,START_ANCHOR:ne}=c,W=a(_=>`(${D}(?:(?!${ne}${_.dot?g:h}).)*?)`,"globstar"),A=u.dot?"":y,v=u.dot?$:Q;let M=u.bash===!0?W(u):G;u.capture&&(M=`(${M})`),typeof u.noext=="boolean"&&(u.noextglob=u.noext);const F={input:t,index:-1,start:0,dot:u.dot===!0,consumed:"",output:"",prefix:"",backtrack:!1,negated:!1,brackets:0,braces:0,parens:0,quotes:0,globstar:!1,tokens:i};t=Y.removePrefix(t,F),n=t.length;const O=[],T=[],re=[];let d=r,E;const j=a(()=>F.index===n-1,"eos"),b=F.peek=(_=1)=>t[F.index+_],Z=F.advance=()=>t[++F.index]||"",J=a(()=>t.slice(F.index+1),"remaining"),V=a((_="",x=0)=>{F.consumed+=_,F.index+=x},"consume"),Ne=a(_=>{F.output+=_.output!=null?_.output:_.value,V(_.value)},"append"),yn=a(()=>{let _=1;for(;b()==="!"&&(b(2)!=="("||b(3)==="?");)Z(),F.start++,_++;return _%2===0?!1:(F.negated=!0,F.start++,!0)},"negate"),He=a(_=>{F[_]++,re.push(_)},"increment"),ie=a(_=>{F[_]--,re.pop()},"decrement"),R=a(_=>{if(d.type==="globstar"){const x=F.braces>0&&(_.type==="comma"||_.type==="brace"),m=_.extglob===!0||O.length&&(_.type==="pipe"||_.type==="paren");_.type!=="slash"&&_.type!=="paren"&&!x&&!m&&(F.output=F.output.slice(0,-d.output.length),d.type="star",d.value="*",d.output=M,F.output+=d.output)}if(O.length&&_.type!=="paren"&&(O[O.length-1].inner+=_.value),(_.value||_.output)&&Ne(_),d&&d.type==="text"&&_.type==="text"){d.value+=_.value,d.output=(d.output||"")+_.value;return}_.prev=d,i.push(_),d=_},"push"),Pe=a((_,x)=>{const m={...f[x],conditions:1,inner:""};m.prev=d,m.parens=F.parens,m.output=F.output;const w=(u.capture?"(":"")+m.open;He("parens"),R({type:_,value:x,output:F.output?"":C}),R({type:"paren",extglob:!0,value:Z(),output:w}),O.push(m)},"extglobOpen"),wn=a(_=>{let x=_.close+(u.capture?")":""),m;if(_.type==="negate"){let w=M;if(_.inner&&_.inner.length>1&&_.inner.includes("/")&&(w=W(u)),(w!==M||j()||/^\)+$/.test(J()))&&(x=_.close=`)$))${w}`),_.inner.includes("*")&&(m=J())&&/^\.[^\\/.]+$/.test(m)){const N=It(m,{...e,fastpaths:!1}).output;x=_.close=`)${N})${w})`}_.prev.type==="bos"&&(F.negatedExtglob=!0)}R({type:"paren",extglob:!0,value:E,output:x}),ie("parens")},"extglobClose");if(u.fastpaths!==!1&&!/(^[*!]|[/()[\]{}"])/.test(t)){let _=!1,x=t.replace(dD,(m,w,N,U,L,at)=>U==="\\"?(_=!0,m):U==="?"?w?w+U+(L?$.repeat(L.length):""):at===0?v+(L?$.repeat(L.length):""):$.repeat(N.length):U==="."?h.repeat(N.length):U==="*"?w?w+U+(L?M:""):M:w?m:`\\${m}`);return _===!0&&(u.unescape===!0?x=x.replace(/\\/g,""):x=x.replace(/\\+/g,m=>m.length%2===0?"\\\\":m?"\\":"")),x===t&&u.contains===!0?(F.output=t,F):(F.output=Y.wrapOutput(x,F,e),F)}for(;!j();){if(E=Z(),E==="\0")continue;if(E==="\\"){const m=b();if(m==="/"&&u.bash!==!0||m==="."||m===";")continue;if(!m){E+="\\",R({type:"text",value:E});continue}const w=/^\\+/.exec(J());let N=0;if(w&&w[0].length>2&&(N=w[0].length,F.index+=N,N%2!==0&&(E+="\\")),u.unescape===!0?E=Z():E+=Z(),F.brackets===0){R({type:"text",value:E});continue}}if(F.brackets>0&&(E!=="]"||d.value==="["||d.value==="[^")){if(u.posix!==!1&&E===":"){const m=d.value.slice(1);if(m.includes("[")&&(d.posix=!0,m.includes(":"))){const w=d.value.lastIndexOf("["),N=d.value.slice(0,w),U=d.value.slice(w+2),L=fD[U];if(L){d.value=N+L,F.backtrack=!0,Z(),!r.output&&i.indexOf(d)===1&&(r.output=C);continue}}}(E==="["&&b()!==":"||E==="-"&&b()==="]")&&(E=`\\${E}`),E==="]"&&(d.value==="["||d.value==="[^")&&(E=`\\${E}`),u.posix===!0&&E==="!"&&d.value==="["&&(E="^"),d.value+=E,Ne({value:E});continue}if(F.quotes===1&&E!=='"'){E=Y.escapeRegex(E),d.value+=E,Ne({value:E});continue}if(E==='"'){F.quotes=F.quotes===1?0:1,u.keepQuotes===!0&&R({type:"text",value:E});continue}if(E==="("){He("parens"),R({type:"paren",value:E});continue}if(E===")"){if(F.parens===0&&u.strictBrackets===!0)throw new SyntaxError(de("opening","("));const m=O[O.length-1];if(m&&F.parens===m.parens+1){wn(O.pop());continue}R({type:"paren",value:E,output:F.parens?")":"\\)"}),ie("parens");continue}if(E==="["){if(u.nobracket===!0||!J().includes("]")){if(u.nobracket!==!0&&u.strictBrackets===!0)throw new SyntaxError(de("closing","]"));E=`\\${E}`}else He("brackets");R({type:"bracket",value:E});continue}if(E==="]"){if(u.nobracket===!0||d&&d.type==="bracket"&&d.value.length===1){R({type:"text",value:E,output:`\\${E}`});continue}if(F.brackets===0){if(u.strictBrackets===!0)throw new SyntaxError(de("opening","["));R({type:"text",value:E,output:`\\${E}`});continue}ie("brackets");const m=d.value.slice(1);if(d.posix!==!0&&m[0]==="^"&&!m.includes("/")&&(E=`/${E}`),d.value+=E,Ne({value:E}),u.literalBrackets===!1||Y.hasRegexChars(m))continue;const w=Y.escapeRegex(d.value);if(F.output=F.output.slice(0,-d.value.length),u.literalBrackets===!0){F.output+=w,d.value=w;continue}d.value=`(${D}${w}|${d.value})`,F.output+=d.value;continue}if(E==="{"&&u.nobrace!==!0){He("braces");const m={type:"brace",value:E,output:"(",outputIndex:F.output.length,tokensIndex:F.tokens.length};T.push(m),R(m);continue}if(E==="}"){const m=T[T.length-1];if(u.nobrace===!0||!m){R({type:"text",value:E,output:E});continue}let w=")";if(m.dots===!0){const N=i.slice(),U=[];for(let L=N.length-1;L>=0&&(i.pop(),N[L].type!=="brace");L--)N[L].type!=="dots"&&U.unshift(N[L].value);w=ED(U,u),F.backtrack=!0}if(m.comma!==!0&&m.dots!==!0){const N=F.output.slice(0,m.outputIndex),U=F.tokens.slice(m.tokensIndex);m.value=m.output="\\{",E=w="\\}",F.output=N;for(const L of U)F.output+=L.output||L.value}R({type:"brace",value:E,output:w}),ie("braces"),T.pop();continue}if(E==="|"){O.length>0&&O[O.length-1].conditions++,R({type:"text",value:E});continue}if(E===","){let m=E;const w=T[T.length-1];w&&re[re.length-1]==="braces"&&(w.comma=!0,m="|"),R({type:"comma",value:E,output:m});continue}if(E==="/"){if(d.type==="dot"&&F.index===F.start+1){F.start=F.index+1,F.consumed="",F.output="",i.pop(),d=r;continue}R({type:"slash",value:E,output:p});continue}if(E==="."){if(F.braces>0&&d.type==="dot"){d.value==="."&&(d.output=h);const m=T[T.length-1];d.type="dots",d.output+=E,d.value+=E,m.dots=!0;continue}if(F.braces+F.parens===0&&d.type!=="bos"&&d.type!=="slash"){R({type:"text",value:E,output:h});continue}R({type:"dot",value:E,output:h});continue}if(E==="?"){if(!(d&&d.value==="(")&&u.noextglob!==!0&&b()==="("&&b(2)!=="?"){Pe("qmark",E);continue}if(d&&d.type==="paren"){const w=b();let N=E;if(w==="<"&&!Y.supportsLookbehinds())throw new Error("Node.js v10 or higher is required for regex lookbehinds");(d.value==="("&&!/[!=<:]/.test(w)||w==="<"&&!/<([!=]|\w+>)/.test(J()))&&(N=`\\${E}`),R({type:"text",value:E,output:N});continue}if(u.dot!==!0&&(d.type==="slash"||d.type==="bos")){R({type:"qmark",value:E,output:Q});continue}R({type:"qmark",value:E,output:$});continue}if(E==="!"){if(u.noextglob!==!0&&b()==="("&&(b(2)!=="?"||!/[!=<:]/.test(b(3)))){Pe("negate",E);continue}if(u.nonegate!==!0&&F.index===0){yn();continue}}if(E==="+"){if(u.noextglob!==!0&&b()==="("&&b(2)!=="?"){Pe("plus",E);continue}if(d&&d.value==="("||u.regex===!1){R({type:"plus",value:E,output:l});continue}if(d&&(d.type==="bracket"||d.type==="paren"||d.type==="brace")||F.parens>0){R({type:"plus",value:E});continue}R({type:"plus",value:l});continue}if(E==="@"){if(u.noextglob!==!0&&b()==="("&&b(2)!=="?"){R({type:"at",extglob:!0,value:E,output:""});continue}R({type:"text",value:E});continue}if(E!=="*"){(E==="$"||E==="^")&&(E=`\\${E}`);const m=hD.exec(J());m&&(E+=m[0],F.index+=m[0].length),R({type:"text",value:E});continue}if(d&&(d.type==="globstar"||d.star===!0)){d.type="star",d.star=!0,d.value+=E,d.output=M,F.backtrack=!0,F.globstar=!0,V(E);continue}let _=J();if(u.noextglob!==!0&&/^\([^?]/.test(_)){Pe("star",E);continue}if(d.type==="star"){if(u.noglobstar===!0){V(E);continue}const m=d.prev,w=m.prev,N=m.type==="slash"||m.type==="bos",U=w&&(w.type==="star"||w.type==="globstar");if(u.bash===!0&&(!N||_[0]&&_[0]!=="/")){R({type:"star",value:E,output:""});continue}const L=F.braces>0&&(m.type==="comma"||m.type==="brace"),at=O.length&&(m.type==="pipe"||m.type==="paren");if(!N&&m.type!=="paren"&&!L&&!at){R({type:"star",value:E,output:""});continue}for(;_.slice(0,3)==="/**";){const Le=t[F.index+4];if(Le&&Le!=="/")break;_=_.slice(3),V("/**",3)}if(m.type==="bos"&&j()){d.type="globstar",d.value+=E,d.output=W(u),F.output=d.output,F.globstar=!0,V(E);continue}if(m.type==="slash"&&m.prev.type!=="bos"&&!U&&j()){F.output=F.output.slice(0,-(m.output+d.output).length),m.output=`(?:${m.output}`,d.type="globstar",d.output=W(u)+(u.strictSlashes?")":"|$)"),d.value+=E,F.globstar=!0,F.output+=m.output+d.output,V(E);continue}if(m.type==="slash"&&m.prev.type!=="bos"&&_[0]==="/"){const Le=_[1]!==void 0?"|$":"";F.output=F.output.slice(0,-(m.output+d.output).length),m.output=`(?:${m.output}`,d.type="globstar",d.output=`${W(u)}${p}|${p}${Le})`,d.value+=E,F.output+=m.output+d.output,F.globstar=!0,V(E+Z()),R({type:"slash",value:"/",output:""});continue}if(m.type==="bos"&&_[0]==="/"){d.type="globstar",d.value+=E,d.output=`(?:^|${p}|${W(u)}${p})`,F.output=d.output,F.globstar=!0,V(E+Z()),R({type:"slash",value:"/",output:""});continue}F.output=F.output.slice(0,-d.output.length),d.type="globstar",d.output=W(u),d.value+=E,F.output+=d.output,F.globstar=!0,V(E);continue}const x={type:"star",value:E,output:M};if(u.bash===!0){x.output=".*?",(d.type==="bos"||d.type==="slash")&&(x.output=A+x.output),R(x);continue}if(d&&(d.type==="bracket"||d.type==="paren")&&u.regex===!0){x.output=E,R(x);continue}(F.index===F.start||d.type==="slash"||d.type==="dot")&&(d.type==="dot"?(F.output+=B,d.output+=B):u.dot===!0?(F.output+=H,d.output+=H):(F.output+=A,d.output+=A),b()!=="*"&&(F.output+=C,d.output+=C)),R(x)}for(;F.brackets>0;){if(u.strictBrackets===!0)throw new SyntaxError(de("closing","]"));F.output=Y.escapeLast(F.output,"["),ie("brackets")}for(;F.parens>0;){if(u.strictBrackets===!0)throw new SyntaxError(de("closing",")"));F.output=Y.escapeLast(F.output,"("),ie("parens")}for(;F.braces>0;){if(u.strictBrackets===!0)throw new SyntaxError(de("closing","}"));F.output=Y.escapeLast(F.output,"{"),ie("braces")}if(u.strictSlashes!==!0&&(d.type==="star"||d.type==="bracket")&&R({type:"maybe_slash",value:"",output:`${p}?`}),F.backtrack===!0){F.output="";for(const _ of F.tokens)F.output+=_.output!=null?_.output:_.value,_.suffix&&(F.output+=_.suffix)}return F},"parse$3");It.fastpaths=(t,e)=>{const u={...e},s=typeof u.maxLength=="number"?Math.min(Ye,u.maxLength):Ye,n=t.length;if(n>s)throw new SyntaxError(`Input length: ${n}, exceeds maximum allowed length: ${s}`);t=ps[t]||t;const r=Y.isWindows(e),{DOT_LITERAL:i,SLASH_LITERAL:D,ONE_CHAR:o,DOTS_SLASH:c,NO_DOT:f,NO_DOTS:h,NO_DOTS_SLASH:l,STAR:p,START_ANCHOR:C}=ze.globChars(r),g=u.dot?h:f,y=u.dot?l:f,B=u.capture?"":"?:",H={negated:!1,prefix:""};let $=u.bash===!0?".*?":p;u.capture&&($=`(${$})`);const Q=a(A=>A.noglobstar===!0?$:`(${B}(?:(?!${C}${A.dot?c:i}).)*?)`,"globstar"),G=a(A=>{switch(A){case"*":return`${g}${o}${$}`;case".*":return`${i}${o}${$}`;case"*.*":return`${g}${$}${i}${o}${$}`;case"*/*":return`${g}${$}${D}${o}${y}${$}`;case"**":return g+Q(u);case"**/*":return`(?:${g}${Q(u)}${D})?${y}${o}${$}`;case"**/*.*":return`(?:${g}${Q(u)}${D})?${y}${$}${i}${o}${$}`;case"**/.*":return`(?:${g}${Q(u)}${D})?${i}${o}${$}`;default:{const v=/^(.*?)\.(\w+)$/.exec(A);if(!v)return;const M=G(v[1]);return M?M+i+v[2]:void 0}}},"create"),ne=Y.removePrefix(t,H);let W=G(ne);return W&&u.strictSlashes!==!0&&(W+=`${D}?`),W};var pD=It;const CD=z,FD=cD,kt=pD,Mt=Ue,gD=Ve,mD=a(t=>t&&typeof t=="object"&&!Array.isArray(t),"isObject$1"),P=a((t,e,u=!1)=>{if(Array.isArray(t)){const f=t.map(l=>P(l,e,u));return a(l=>{for(const p of f){const C=p(l);if(C)return C}return!1},"arrayMatcher")}const s=mD(t)&&t.tokens&&t.input;if(t===""||typeof t!="string"&&!s)throw new TypeError("Expected pattern to be a non-empty string");const n=e||{},r=Mt.isWindows(e),i=s?P.compileRe(t,e):P.makeRe(t,e,!1,!0),D=i.state;delete i.state;let o=a(()=>!1,"isIgnored");if(n.ignore){const f={...e,ignore:null,onMatch:null,onResult:null};o=P(n.ignore,f,u)}const c=a((f,h=!1)=>{const{isMatch:l,match:p,output:C}=P.test(f,i,e,{glob:t,posix:r}),g={glob:t,state:D,regex:i,posix:r,input:f,output:C,match:p,isMatch:l};return typeof n.onResult=="function"&&n.onResult(g),l===!1?(g.isMatch=!1,h?g:!1):o(f)?(typeof n.onIgnore=="function"&&n.onIgnore(g),g.isMatch=!1,h?g:!1):(typeof n.onMatch=="function"&&n.onMatch(g),h?g:!0)},"matcher");return u&&(c.state=D),c},"picomatch$3");P.test=(t,e,u,{glob:s,posix:n}={})=>{if(typeof t!="string")throw new TypeError("Expected input to be a string");if(t==="")return{isMatch:!1,output:""};const r=u||{},i=r.format||(n?Mt.toPosixSlashes:null);let D=t===s,o=D&&i?i(t):t;return D===!1&&(o=i?i(t):t,D=o===s),(D===!1||r.capture===!0)&&(r.matchBase===!0||r.basename===!0?D=P.matchBase(t,e,u,n):D=e.exec(o)),{isMatch:!!D,match:D,output:o}},P.matchBase=(t,e,u,s=Mt.isWindows(u))=>(e instanceof RegExp?e:P.makeRe(e,u)).test(CD.basename(t)),P.isMatch=(t,e,u)=>P(e,u)(t),P.parse=(t,e)=>Array.isArray(t)?t.map(u=>P.parse(u,e)):kt(t,{...e,fastpaths:!1}),P.scan=(t,e)=>FD(t,e),P.compileRe=(t,e,u=!1,s=!1)=>{if(u===!0)return t.output;const n=e||{},r=n.contains?"":"^",i=n.contains?"":"$";let D=`${r}(?:${t.output})${i}`;t&&t.negated===!0&&(D=`^(?!${D}).*$`);const o=P.toRegex(D,e);return s===!0&&(o.state=t),o},P.makeRe=(t,e={},u=!1,s=!1)=>{if(!t||typeof t!="string")throw new TypeError("Expected a non-empty string");let n={negated:!1,fastpaths:!0};return e.fastpaths!==!1&&(t[0]==="."||t[0]==="*")&&(n.output=kt.fastpaths(t,e)),n.output||(n=kt(t,e)),P.compileRe(n,e,u,s)},P.toRegex=(t,e)=>{try{const u=e||{};return new RegExp(t,u.flags||(u.nocase?"i":""))}catch(u){if(e&&e.debug===!0)throw u;return/$^/}},P.constants=gD;var _D=P,Cs=_D;const we=De,{Readable:AD}=In,Re=z,{promisify:qe}=ge,Gt=Cs,yD=qe(we.readdir),wD=qe(we.stat),Fs=qe(we.lstat),RD=qe(we.realpath),bD="!",gs="READDIRP_RECURSIVE_ERROR",vD=new Set(["ENOENT","EPERM","EACCES","ELOOP",gs]),Wt="files",ms="directories",Xe="files_directories",Qe="all",_s=[Wt,ms,Xe,Qe],SD=a(t=>vD.has(t.code),"isNormalFlowError"),[As,BD]=process.versions.node.split(".").slice(0,2).map(t=>Number.parseInt(t,10)),$D=process.platform==="win32"&&(As>10||As===10&&BD>=5),ys=a(t=>{if(t!==void 0){if(typeof t=="function")return t;if(typeof t=="string"){const e=Gt(t.trim());return u=>e(u.basename)}if(Array.isArray(t)){const e=[],u=[];for(const s of t){const n=s.trim();n.charAt(0)===bD?u.push(Gt(n.slice(1))):e.push(Gt(n))}return u.length>0?e.length>0?s=>e.some(n=>n(s.basename))&&!u.some(n=>n(s.basename)):s=>!u.some(n=>n(s.basename)):s=>e.some(n=>n(s.basename))}}},"normalizeFilter");class ot extends AD{static{a(this,"ReaddirpStream")}static get defaultOptions(){return{root:".",fileFilter:a(e=>!0,"fileFilter"),directoryFilter:a(e=>!0,"directoryFilter"),type:Wt,lstat:!1,depth:2147483648,alwaysStat:!1}}constructor(e={}){super({objectMode:!0,autoDestroy:!0,highWaterMark:e.highWaterMark||4096});const u={...ot.defaultOptions,...e},{root:s,type:n}=u;this._fileFilter=ys(u.fileFilter),this._directoryFilter=ys(u.directoryFilter);const r=u.lstat?Fs:wD;$D?this._stat=i=>r(i,{bigint:!0}):this._stat=r,this._maxDepth=u.depth,this._wantsDir=[ms,Xe,Qe].includes(n),this._wantsFile=[Wt,Xe,Qe].includes(n),this._wantsEverything=n===Qe,this._root=Re.resolve(s),this._isDirent="Dirent"in we&&!u.alwaysStat,this._statsProp=this._isDirent?"dirent":"stats",this._rdOptions={encoding:"utf8",withFileTypes:this._isDirent},this.parents=[this._exploreDir(s,1)],this.reading=!1,this.parent=void 0}async _read(e){if(!this.reading){this.reading=!0;try{for(;!this.destroyed&&e>0;){const{path:u,depth:s,files:n=[]}=this.parent||{};if(n.length>0){const r=n.splice(0,e).map(i=>this._formatEntry(i,u));for(const i of await Promise.all(r)){if(this.destroyed)return;const D=await this._getEntryType(i);D==="directory"&&this._directoryFilter(i)?(s<=this._maxDepth&&this.parents.push(this._exploreDir(i.fullPath,s+1)),this._wantsDir&&(this.push(i),e--)):(D==="file"||this._includeAsFile(i))&&this._fileFilter(i)&&this._wantsFile&&(this.push(i),e--)}}else{const r=this.parents.pop();if(!r){this.push(null);break}if(this.parent=await r,this.destroyed)return}}}catch(u){this.destroy(u)}finally{this.reading=!1}}}async _exploreDir(e,u){let s;try{s=await yD(e,this._rdOptions)}catch(n){this._onError(n)}return{files:s,depth:u,path:e}}async _formatEntry(e,u){let s;try{const n=this._isDirent?e.name:e,r=Re.resolve(Re.join(u,n));s={path:Re.relative(this._root,r),fullPath:r,basename:n},s[this._statsProp]=this._isDirent?e:await this._stat(r)}catch(n){this._onError(n)}return s}_onError(e){SD(e)&&!this.destroyed?this.emit("warn",e):this.destroy(e)}async _getEntryType(e){const u=e&&e[this._statsProp];if(u){if(u.isFile())return"file";if(u.isDirectory())return"directory";if(u&&u.isSymbolicLink()){const s=e.fullPath;try{const n=await RD(s),r=await Fs(n);if(r.isFile())return"file";if(r.isDirectory()){const i=n.length;if(s.startsWith(n)&&s.substr(i,1)===Re.sep){const D=new Error(`Circular symlink detected: "${s}" points to "${n}"`);return D.code=gs,this._onError(D)}return"directory"}}catch(n){this._onError(n)}}}}_includeAsFile(e){const u=e&&e[this._statsProp];return u&&this._wantsEverything&&!u.isDirectory()}}const Ee=a((t,e={})=>{let u=e.entryType||e.type;if(u==="both"&&(u=Xe),u&&(e.type=u),t){if(typeof t!="string")throw new TypeError("readdirp: root argument must be a string. Usage: readdirp(root, options)");if(u&&!_s.includes(u))throw new Error(`readdirp: Invalid type passed. Use one of ${_s.join(", ")}`)}else throw new Error("readdirp: root argument is required. Usage: readdirp(root, options)");return e.root=t,new ot(e)},"readdirp$1"),TD=a((t,e={})=>new Promise((u,s)=>{const n=[];Ee(t,e).on("data",r=>n.push(r)).on("end",()=>u(n)).on("error",r=>s(r))}),"readdirpPromise");Ee.promise=TD,Ee.ReaddirpStream=ot,Ee.default=Ee;var xD=Ee,jt={exports:{}};/*! + * normalize-path + * + * Copyright (c) 2014-2018, Jon Schlinkert. + * Released under the MIT License. + */var ws=a(function(t,e){if(typeof t!="string")throw new TypeError("expected path to be a string");if(t==="\\"||t==="/")return"/";var u=t.length;if(u<=1)return t;var s="";if(u>4&&t[3]==="\\"){var n=t[2];(n==="?"||n===".")&&t.slice(0,2)==="\\\\"&&(t=t.slice(2),s="//")}var r=t.split(/[/\\]+/);return e!==!1&&r[r.length-1]===""&&r.pop(),s+r.join("/")},"normalizePath$2"),OD=jt.exports;Object.defineProperty(OD,"__esModule",{value:!0});const Rs=Cs,ND=ws,bs="!",HD={returnIndex:!1},PD=a(t=>Array.isArray(t)?t:[t],"arrify$1"),LD=a((t,e)=>{if(typeof t=="function")return t;if(typeof t=="string"){const u=Rs(t,e);return s=>t===s||u(s)}return t instanceof RegExp?u=>t.test(u):u=>!1},"createPattern"),vs=a((t,e,u,s)=>{const n=Array.isArray(u),r=n?u[0]:u;if(!n&&typeof r!="string")throw new TypeError("anymatch: second argument must be a string: got "+Object.prototype.toString.call(r));const i=ND(r,!1);for(let o=0;o{if(t==null)throw new TypeError("anymatch: specify first argument");const s=typeof u=="boolean"?{returnIndex:u}:u,n=s.returnIndex||!1,r=PD(t),i=r.filter(o=>typeof o=="string"&&o.charAt(0)===bs).map(o=>o.slice(1)).map(o=>Rs(o,s)),D=r.filter(o=>typeof o!="string"||typeof o=="string"&&o.charAt(0)!==bs).map(o=>LD(o,s));return e==null?(o,c=!1)=>vs(D,i,o,typeof c=="boolean"?c:!1):vs(D,i,e,n)},"anymatch$1");Ut.default=Ut,jt.exports=Ut;var ID=jt.exports;/*! + * is-extglob + * + * Copyright (c) 2014-2016, Jon Schlinkert. + * Licensed under the MIT License. + */var kD=a(function(e){if(typeof e!="string"||e==="")return!1;for(var u;u=/(\\).|([@?!+*]\(.*\))/g.exec(e);){if(u[2])return!0;e=e.slice(u.index+u[0].length)}return!1},"isExtglob");/*! + * is-glob + * + * Copyright (c) 2014-2017, Jon Schlinkert. + * Released under the MIT License. + */var MD=kD,Ss={"{":"}","(":")","[":"]"},GD=a(function(t){if(t[0]==="!")return!0;for(var e=0,u=-2,s=-2,n=-2,r=-2,i=-2;ee&&(i===-1||i>s||(i=t.indexOf("\\",e),i===-1||i>s)))||n!==-1&&t[e]==="{"&&t[e+1]!=="}"&&(n=t.indexOf("}",e),n>e&&(i=t.indexOf("\\",e),i===-1||i>n))||r!==-1&&t[e]==="("&&t[e+1]==="?"&&/[:!=]/.test(t[e+2])&&t[e+3]!==")"&&(r=t.indexOf(")",e),r>e&&(i=t.indexOf("\\",e),i===-1||i>r))||u!==-1&&t[e]==="("&&t[e+1]!=="|"&&(uu&&(i=t.indexOf("\\",u),i===-1||i>r))))return!0;if(t[e]==="\\"){var D=t[e+1];e+=2;var o=Ss[D];if(o){var c=t.indexOf(o,e);c!==-1&&(e=c+1)}if(t[e]==="!")return!0}else e++}return!1},"strictCheck"),WD=a(function(t){if(t[0]==="!")return!0;for(var e=0;etypeof e=="number"?Number.isInteger(e):typeof e=="string"&&e.trim()!==""?Number.isInteger(Number(e)):!1,t.find=(e,u)=>e.nodes.find(s=>s.type===u),t.exceedsLimit=(e,u,s=1,n)=>n===!1||!t.isInteger(e)||!t.isInteger(u)?!1:(Number(u)-Number(e))/Number(s)>=n,t.escapeNode=(e,u=0,s)=>{let n=e.nodes[u];n&&(s&&n.type===s||n.type==="open"||n.type==="close")&&n.escaped!==!0&&(n.value="\\"+n.value,n.escaped=!0)},t.encloseBrace=e=>e.type!=="brace"||e.commas>>0+e.ranges>>0?!1:(e.invalid=!0,!0),t.isInvalidBrace=e=>e.type!=="brace"?!1:e.invalid===!0||e.dollar?!0:!(e.commas>>0+e.ranges>>0)||e.open!==!0||e.close!==!0?(e.invalid=!0,!0):!1,t.isOpenOrClose=e=>e.type==="open"||e.type==="close"?!0:e.open===!0||e.close===!0,t.reduce=e=>e.reduce((u,s)=>(s.type==="text"&&u.push(s.value),s.type==="range"&&(s.type="text"),u),[]),t.flatten=(...e)=>{const u=[],s=a(n=>{for(let r=0;r{let u=a((s,n={})=>{let r=e.escapeInvalid&&$s.isInvalidBrace(n),i=s.invalid===!0&&e.escapeInvalid===!0,D="";if(s.value)return(r||i)&&$s.isOpenOrClose(s)?"\\"+s.value:s.value;if(s.value)return s.value;if(s.nodes)for(let o of s.nodes)D+=u(o);return D},"stringify");return u(t)},"stringify$4");/*! + * is-number + * + * Copyright (c) 2014-present, Jon Schlinkert. + * Released under the MIT License. + */var QD=a(function(t){return typeof t=="number"?t-t===0:typeof t=="string"&&t.trim()!==""?Number.isFinite?Number.isFinite(+t):isFinite(+t):!1},"isNumber$2");/*! + * to-regex-range + * + * Copyright (c) 2015-present, Jon Schlinkert. + * Released under the MIT License. + */const Ts=QD,ae=a((t,e,u)=>{if(Ts(t)===!1)throw new TypeError("toRegexRange: expected the first argument to be a number");if(e===void 0||t===e)return String(t);if(Ts(e)===!1)throw new TypeError("toRegexRange: expected the second argument to be a number.");let s={relaxZeros:!0,...u};typeof s.strictZeros=="boolean"&&(s.relaxZeros=s.strictZeros===!1);let n=String(s.relaxZeros),r=String(s.shorthand),i=String(s.capture),D=String(s.wrap),o=t+":"+e+"="+n+r+i+D;if(ae.cache.hasOwnProperty(o))return ae.cache[o].result;let c=Math.min(t,e),f=Math.max(t,e);if(Math.abs(c-f)===1){let g=t+"|"+e;return s.capture?`(${g})`:s.wrap===!1?g:`(?:${g})`}let h=Ls(t)||Ls(e),l={min:t,max:e,a:c,b:f},p=[],C=[];if(h&&(l.isPadded=h,l.maxLen=String(l.max).length),c<0){let g=f<0?Math.abs(f):1;C=xs(g,Math.abs(c),l,s),c=l.a=0}return f>=0&&(p=xs(c,f,l,s)),l.negatives=C,l.positives=p,l.result=ZD(C,p),s.capture===!0?l.result=`(${l.result})`:s.wrap!==!1&&p.length+C.length>1&&(l.result=`(?:${l.result})`),ae.cache[o]=l,l.result},"toRegexRange$1");function ZD(t,e,u){let s=zt(t,e,"-",!1)||[],n=zt(e,t,"",!1)||[],r=zt(t,e,"-?",!0)||[];return s.concat(r).concat(n).join("|")}a(ZD,"collatePatterns");function JD(t,e){let u=1,s=1,n=Ns(t,u),r=new Set([e]);for(;t<=n&&n<=e;)r.add(n),u+=1,n=Ns(t,u);for(n=Hs(e+1,s)-1;t1&&D.count.pop(),D.count.push(f.count[0]),D.string=D.pattern+Ps(D.count),i=c+1;continue}u.isPadded&&(h=no(c,u,s)),f.string=h+f.pattern+Ps(f.count),r.push(f),i=c+1,D=f}return r}a(xs,"splitToPatterns");function zt(t,e,u,s,n){let r=[];for(let i of t){let{string:D}=i;!s&&!Os(e,"string",D)&&r.push(u+D),s&&Os(e,"string",D)&&r.push(u+D)}return r}a(zt,"filterPatterns");function to(t,e){let u=[];for(let s=0;se?1:e>t?-1:0}a(uo,"compare");function Os(t,e,u){return t.some(s=>s[e]===u)}a(Os,"contains");function Ns(t,e){return Number(String(t).slice(0,-e)+"9".repeat(e))}a(Ns,"countNines");function Hs(t,e){return t-t%Math.pow(10,e)}a(Hs,"countZeros");function Ps(t){let[e=0,u=""]=t;return u||e>1?`{${e+(u?","+u:"")}}`:""}a(Ps,"toQuantifier");function so(t,e,u){return`[${t}${e-t===1?"":"-"}${e}]`}a(so,"toCharacterClass");function Ls(t){return/^-?(0+)\d/.test(t)}a(Ls,"hasPadding");function no(t,e,u){if(!e.isPadded)return t;let s=Math.abs(e.maxLen-String(t).length),n=u.relaxZeros!==!1;switch(s){case 0:return"";case 1:return n?"0?":"0";case 2:return n?"0{0,2}":"00";default:return n?`0{0,${s}}`:`0{${s}}`}}a(no,"padZeros"),ae.cache={},ae.clearCache=()=>ae.cache={};var ro=ae;/*! + * fill-range + * + * Copyright (c) 2014-present, Jon Schlinkert. + * Licensed under the MIT License. + */const io=ge,Is=ro,ks=a(t=>t!==null&&typeof t=="object"&&!Array.isArray(t),"isObject"),Do=a(t=>e=>t===!0?Number(e):String(e),"transform"),Yt=a(t=>typeof t=="number"||typeof t=="string"&&t!=="","isValidValue"),be=a(t=>Number.isInteger(+t),"isNumber"),qt=a(t=>{let e=`${t}`,u=-1;if(e[0]==="-"&&(e=e.slice(1)),e==="0")return!1;for(;e[++u]==="0";);return u>0},"zeros"),oo=a((t,e,u)=>typeof t=="string"||typeof e=="string"?!0:u.stringify===!0,"stringify$3"),ao=a((t,e,u)=>{if(e>0){let s=t[0]==="-"?"-":"";s&&(t=t.slice(1)),t=s+t.padStart(s?e-1:e,"0")}return u===!1?String(t):t},"pad"),Ms=a((t,e)=>{let u=t[0]==="-"?"-":"";for(u&&(t=t.slice(1),e--);t.length{t.negatives.sort((i,D)=>iD?1:0),t.positives.sort((i,D)=>iD?1:0);let u=e.capture?"":"?:",s="",n="",r;return t.positives.length&&(s=t.positives.join("|")),t.negatives.length&&(n=`-(${u}${t.negatives.join("|")})`),s&&n?r=`${s}|${n}`:r=s||n,e.wrap?`(${u}${r})`:r},"toSequence"),Gs=a((t,e,u,s)=>{if(u)return Is(t,e,{wrap:!1,...s});let n=String.fromCharCode(t);if(t===e)return n;let r=String.fromCharCode(e);return`[${n}-${r}]`},"toRange"),Ws=a((t,e,u)=>{if(Array.isArray(t)){let s=u.wrap===!0,n=u.capture?"":"?:";return s?`(${n}${t.join("|")})`:t.join("|")}return Is(t,e,u)},"toRegex"),js=a((...t)=>new RangeError("Invalid range arguments: "+io.inspect(...t)),"rangeError"),Us=a((t,e,u)=>{if(u.strictRanges===!0)throw js([t,e]);return[]},"invalidRange"),co=a((t,e)=>{if(e.strictRanges===!0)throw new TypeError(`Expected step "${t}" to be a number`);return[]},"invalidStep"),fo=a((t,e,u=1,s={})=>{let n=Number(t),r=Number(e);if(!Number.isInteger(n)||!Number.isInteger(r)){if(s.strictRanges===!0)throw js([t,e]);return[]}n===0&&(n=0),r===0&&(r=0);let i=n>r,D=String(t),o=String(e),c=String(u);u=Math.max(Math.abs(u),1);let f=qt(D)||qt(o)||qt(c),h=f?Math.max(D.length,o.length,c.length):0,l=f===!1&&oo(t,e,s)===!1,p=s.transform||Do(l);if(s.toRegex&&u===1)return Gs(Ms(t,h),Ms(e,h),!0,s);let C={negatives:[],positives:[]},g=a(H=>C[H<0?"negatives":"positives"].push(Math.abs(H)),"push"),y=[],B=0;for(;i?n>=r:n<=r;)s.toRegex===!0&&u>1?g(n):y.push(ao(p(n,B),h,l)),n=i?n-u:n+u,B++;return s.toRegex===!0?u>1?lo(C,s):Ws(y,null,{wrap:!1,...s}):y},"fillNumbers"),ho=a((t,e,u=1,s={})=>{if(!be(t)&&t.length>1||!be(e)&&e.length>1)return Us(t,e,s);let n=s.transform||(l=>String.fromCharCode(l)),r=`${t}`.charCodeAt(0),i=`${e}`.charCodeAt(0),D=r>i,o=Math.min(r,i),c=Math.max(r,i);if(s.toRegex&&u===1)return Gs(o,c,!1,s);let f=[],h=0;for(;D?r>=i:r<=i;)f.push(n(r,h)),r=D?r-u:r+u,h++;return s.toRegex===!0?Ws(f,null,{wrap:!1,options:s}):f},"fillLetters"),Je=a((t,e,u,s={})=>{if(e==null&&Yt(t))return[t];if(!Yt(t)||!Yt(e))return Us(t,e,s);if(typeof u=="function")return Je(t,e,1,{transform:u});if(ks(u))return Je(t,e,0,u);let n={...s};return n.capture===!0&&(n.wrap=!0),u=u||n.step||1,be(u)?be(t)&&be(e)?fo(t,e,u,n):ho(t,e,Math.max(Math.abs(u),1),n):u!=null&&!ks(u)?co(u,n):Je(t,e,1,u)},"fill$2");var Ks=Je;const Eo=Ks,Vs=Ze,po=a((t,e={})=>{let u=a((s,n={})=>{let r=Vs.isInvalidBrace(n),i=s.invalid===!0&&e.escapeInvalid===!0,D=r===!0||i===!0,o=e.escapeInvalid===!0?"\\":"",c="";if(s.isOpen===!0||s.isClose===!0)return o+s.value;if(s.type==="open")return D?o+s.value:"(";if(s.type==="close")return D?o+s.value:")";if(s.type==="comma")return s.prev.type==="comma"?"":D?s.value:"|";if(s.value)return s.value;if(s.nodes&&s.ranges>0){let f=Vs.reduce(s.nodes),h=Eo(...f,{...e,wrap:!1,toRegex:!0});if(h.length!==0)return f.length>1&&h.length>1?`(${h})`:h}if(s.nodes)for(let f of s.nodes)c+=u(f,s);return c},"walk");return u(t)},"compile$1");var Co=po;const Fo=Ks,zs=Vt,pe=Ze,le=a((t="",e="",u=!1)=>{let s=[];if(t=[].concat(t),e=[].concat(e),!e.length)return t;if(!t.length)return u?pe.flatten(e).map(n=>`{${n}}`):e;for(let n of t)if(Array.isArray(n))for(let r of n)s.push(le(r,e,u));else for(let r of e)u===!0&&typeof r=="string"&&(r=`{${r}}`),s.push(Array.isArray(r)?le(n,r,u):n+r);return pe.flatten(s)},"append"),go=a((t,e={})=>{let u=e.rangeLimit===void 0?1e3:e.rangeLimit,s=a((n,r={})=>{n.queue=[];let i=r,D=r.queue;for(;i.type!=="brace"&&i.type!=="root"&&i.parent;)i=i.parent,D=i.queue;if(n.invalid||n.dollar){D.push(le(D.pop(),zs(n,e)));return}if(n.type==="brace"&&n.invalid!==!0&&n.nodes.length===2){D.push(le(D.pop(),["{}"]));return}if(n.nodes&&n.ranges>0){let h=pe.reduce(n.nodes);if(pe.exceedsLimit(...h,e.step,u))throw new RangeError("expanded array length exceeds range limit. Use options.rangeLimit to increase or disable the limit.");let l=Fo(...h,e);l.length===0&&(l=zs(n,e)),D.push(le(D.pop(),l)),n.nodes=[];return}let o=pe.encloseBrace(n),c=n.queue,f=n;for(;f.type!=="brace"&&f.type!=="root"&&f.parent;)f=f.parent,c=f.queue;for(let h=0;h",CHAR_RIGHT_CURLY_BRACE:"}",CHAR_RIGHT_SQUARE_BRACKET:"]",CHAR_SEMICOLON:";",CHAR_SINGLE_QUOTE:"'",CHAR_SPACE:" ",CHAR_TAB:" ",CHAR_UNDERSCORE:"_",CHAR_VERTICAL_LINE:"|",CHAR_ZERO_WIDTH_NOBREAK_SPACE:"\uFEFF"};const Ao=Vt,{MAX_LENGTH:Ys,CHAR_BACKSLASH:Xt,CHAR_BACKTICK:yo,CHAR_COMMA:wo,CHAR_DOT:Ro,CHAR_LEFT_PARENTHESES:bo,CHAR_RIGHT_PARENTHESES:vo,CHAR_LEFT_CURLY_BRACE:So,CHAR_RIGHT_CURLY_BRACE:Bo,CHAR_LEFT_SQUARE_BRACKET:qs,CHAR_RIGHT_SQUARE_BRACKET:Xs,CHAR_DOUBLE_QUOTE:$o,CHAR_SINGLE_QUOTE:To,CHAR_NO_BREAK_SPACE:xo,CHAR_ZERO_WIDTH_NOBREAK_SPACE:Oo}=_o,No=a((t,e={})=>{if(typeof t!="string")throw new TypeError("Expected a string");let u=e||{},s=typeof u.maxLength=="number"?Math.min(Ys,u.maxLength):Ys;if(t.length>s)throw new SyntaxError(`Input length (${t.length}), exceeds max characters (${s})`);let n={type:"root",input:t,nodes:[]},r=[n],i=n,D=n,o=0,c=t.length,f=0,h=0,l;const p=a(()=>t[f++],"advance"),C=a(g=>{if(g.type==="text"&&D.type==="dot"&&(D.type="text"),D&&D.type==="text"&&g.type==="text"){D.value+=g.value;return}return i.nodes.push(g),g.parent=i,g.prev=D,D=g,g},"push");for(C({type:"bos"});f0){if(i.ranges>0){i.ranges=0;let g=i.nodes.shift();i.nodes=[g,{type:"text",value:Ao(i)}]}C({type:"comma",value:l}),i.commas++;continue}if(l===Ro&&h>0&&i.commas===0){let g=i.nodes;if(h===0||g.length===0){C({type:"text",value:l});continue}if(D.type==="dot"){if(i.range=[],D.value+=l,D.type="range",i.nodes.length!==3&&i.nodes.length!==5){i.invalid=!0,i.ranges=0,D.type="text";continue}i.ranges++,i.args=[];continue}if(D.type==="range"){g.pop();let y=g[g.length-1];y.value+=D.value+l,D=y,i.ranges--;continue}C({type:"dot",value:l});continue}C({type:"text",value:l})}do if(i=r.pop(),i.type!=="root"){i.nodes.forEach(B=>{B.nodes||(B.type==="open"&&(B.isOpen=!0),B.type==="close"&&(B.isClose=!0),B.nodes||(B.type="text"),B.invalid=!0)});let g=r[r.length-1],y=g.nodes.indexOf(i);g.nodes.splice(y,1,...i.nodes)}while(r.length>0);return C({type:"eos"}),n},"parse$1");var Ho=No;const Qs=Vt,Po=Co,Lo=mo,Io=Ho,q=a((t,e={})=>{let u=[];if(Array.isArray(t))for(let s of t){let n=q.create(s,e);Array.isArray(n)?u.push(...n):u.push(n)}else u=[].concat(q.create(t,e));return e&&e.expand===!0&&e.nodupes===!0&&(u=[...new Set(u)]),u},"braces$1");q.parse=(t,e={})=>Io(t,e),q.stringify=(t,e={})=>Qs(typeof t=="string"?q.parse(t,e):t,e),q.compile=(t,e={})=>(typeof t=="string"&&(t=q.parse(t,e)),Po(t,e)),q.expand=(t,e={})=>{typeof t=="string"&&(t=q.parse(t,e));let u=Lo(t,e);return e.noempty===!0&&(u=u.filter(Boolean)),e.nodupes===!0&&(u=[...new Set(u)]),u},q.create=(t,e={})=>t===""||t.length<3?[t]:e.expand!==!0?q.compile(t,e):q.expand(t,e);var ko=q,Mo=["3dm","3ds","3g2","3gp","7z","a","aac","adp","afdesign","afphoto","afpub","ai","aif","aiff","alz","ape","apk","appimage","ar","arj","asf","au","avi","bak","baml","bh","bin","bk","bmp","btif","bz2","bzip2","cab","caf","cgm","class","cmx","cpio","cr2","cur","dat","dcm","deb","dex","djvu","dll","dmg","dng","doc","docm","docx","dot","dotm","dra","DS_Store","dsk","dts","dtshd","dvb","dwg","dxf","ecelp4800","ecelp7470","ecelp9600","egg","eol","eot","epub","exe","f4v","fbs","fh","fla","flac","flatpak","fli","flv","fpx","fst","fvt","g3","gh","gif","graffle","gz","gzip","h261","h263","h264","icns","ico","ief","img","ipa","iso","jar","jpeg","jpg","jpgv","jpm","jxr","key","ktx","lha","lib","lvp","lz","lzh","lzma","lzo","m3u","m4a","m4v","mar","mdi","mht","mid","midi","mj2","mka","mkv","mmr","mng","mobi","mov","movie","mp3","mp4","mp4a","mpeg","mpg","mpga","mxu","nef","npx","numbers","nupkg","o","odp","ods","odt","oga","ogg","ogv","otf","ott","pages","pbm","pcx","pdb","pdf","pea","pgm","pic","png","pnm","pot","potm","potx","ppa","ppam","ppm","pps","ppsm","ppsx","ppt","pptm","pptx","psd","pya","pyc","pyo","pyv","qt","rar","ras","raw","resources","rgb","rip","rlc","rmf","rmvb","rpm","rtf","rz","s3m","s7z","scpt","sgi","shar","snap","sil","sketch","slk","smv","snk","so","stl","suo","sub","swf","tar","tbz","tbz2","tga","tgz","thmx","tif","tiff","tlz","ttc","ttf","txz","udf","uvh","uvi","uvm","uvp","uvs","uvu","viv","vob","war","wav","wax","wbmp","wdp","weba","webm","webp","whl","wim","wm","wma","wmv","wmx","woff","woff2","wrm","wvx","xbm","xif","xla","xlam","xls","xlsb","xlsm","xlsx","xlt","xltm","xltx","xm","xmind","xpi","xpm","xwd","xz","z","zip","zipx"],Go=Mo;const Wo=z,jo=Go,Uo=new Set(jo);var Ko=a(t=>Uo.has(Wo.extname(t).slice(1).toLowerCase()),"isBinaryPath$1"),et={};(function(t){const{sep:e}=z,{platform:u}=process,s=_u;t.EV_ALL="all",t.EV_READY="ready",t.EV_ADD="add",t.EV_CHANGE="change",t.EV_ADD_DIR="addDir",t.EV_UNLINK="unlink",t.EV_UNLINK_DIR="unlinkDir",t.EV_RAW="raw",t.EV_ERROR="error",t.STR_DATA="data",t.STR_END="end",t.STR_CLOSE="close",t.FSEVENT_CREATED="created",t.FSEVENT_MODIFIED="modified",t.FSEVENT_DELETED="deleted",t.FSEVENT_MOVED="moved",t.FSEVENT_CLONED="cloned",t.FSEVENT_UNKNOWN="unknown",t.FSEVENT_FLAG_MUST_SCAN_SUBDIRS=1,t.FSEVENT_TYPE_FILE="file",t.FSEVENT_TYPE_DIRECTORY="directory",t.FSEVENT_TYPE_SYMLINK="symlink",t.KEY_LISTENERS="listeners",t.KEY_ERR="errHandlers",t.KEY_RAW="rawEmitters",t.HANDLER_KEYS=[t.KEY_LISTENERS,t.KEY_ERR,t.KEY_RAW],t.DOT_SLASH=`.${e}`,t.BACK_SLASH_RE=/\\/g,t.DOUBLE_SLASH_RE=/\/\//,t.SLASH_OR_BACK_SLASH_RE=/[/\\]/,t.DOT_RE=/\..*\.(sw[px])$|~$|\.subl.*\.tmp/,t.REPLACER_RE=/^\.[/\\]/,t.SLASH="/",t.SLASH_SLASH="//",t.BRACE_START="{",t.BANG="!",t.ONE_DOT=".",t.TWO_DOTS="..",t.STAR="*",t.GLOBSTAR="**",t.ROOT_GLOBSTAR="/**/*",t.SLASH_GLOBSTAR="/**",t.DIR_SUFFIX="Dir",t.ANYMATCH_OPTS={dot:!0},t.STRING_TYPE="string",t.FUNCTION_TYPE="function",t.EMPTY_STR="",t.EMPTY_FN=()=>{},t.IDENTITY_FN=n=>n,t.isWindows=u==="win32",t.isMacos=u==="darwin",t.isLinux=u==="linux",t.isIBMi=s.type()==="OS400"})(et);const se=De,I=z,{promisify:ve}=ge,Vo=Ko,{isWindows:zo,isLinux:Yo,EMPTY_FN:qo,EMPTY_STR:Xo,KEY_LISTENERS:Ce,KEY_ERR:Qt,KEY_RAW:Se,HANDLER_KEYS:Qo,EV_CHANGE:tt,EV_ADD:ut,EV_ADD_DIR:Zo,EV_ERROR:Zs,STR_DATA:Jo,STR_END:ea,BRACE_START:ta,STAR:ua}=et,sa="watch",na=ve(se.open),Js=ve(se.stat),ra=ve(se.lstat),ia=ve(se.close),Zt=ve(se.realpath),Da={lstat:ra,stat:Js},Jt=a((t,e)=>{t instanceof Set?t.forEach(e):e(t)},"foreach"),Be=a((t,e,u)=>{let s=t[e];s instanceof Set||(t[e]=s=new Set([s])),s.add(u)},"addAndConvert"),oa=a(t=>e=>{const u=t[e];u instanceof Set?u.clear():delete t[e]},"clearItem"),$e=a((t,e,u)=>{const s=t[e];s instanceof Set?s.delete(u):s===u&&delete t[e]},"delFromSet"),en=a(t=>t instanceof Set?t.size===0:!t,"isEmptySet"),st=new Map;function tn(t,e,u,s,n){const r=a((i,D)=>{u(t),n(i,D,{watchedPath:t}),D&&t!==D&&nt(I.resolve(t,D),Ce,I.join(t,D))},"handleEvent");try{return se.watch(t,e,r)}catch(i){s(i)}}a(tn,"createFsWatchInstance");const nt=a((t,e,u,s,n)=>{const r=st.get(t);r&&Jt(r[e],i=>{i(u,s,n)})},"fsWatchBroadcast"),aa=a((t,e,u,s)=>{const{listener:n,errHandler:r,rawEmitter:i}=s;let D=st.get(e),o;if(!u.persistent)return o=tn(t,u,n,r,i),o.close.bind(o);if(D)Be(D,Ce,n),Be(D,Qt,r),Be(D,Se,i);else{if(o=tn(t,u,nt.bind(null,e,Ce),r,nt.bind(null,e,Se)),!o)return;o.on(Zs,async c=>{const f=nt.bind(null,e,Qt);if(D.watcherUnusable=!0,zo&&c.code==="EPERM")try{const h=await na(t,"r");await ia(h),f(c)}catch{}else f(c)}),D={listeners:n,errHandlers:r,rawEmitters:i,watcher:o},st.set(e,D)}return()=>{$e(D,Ce,n),$e(D,Qt,r),$e(D,Se,i),en(D.listeners)&&(D.watcher.close(),st.delete(e),Qo.forEach(oa(D)),D.watcher=void 0,Object.freeze(D))}},"setFsWatchListener"),eu=new Map,la=a((t,e,u,s)=>{const{listener:n,rawEmitter:r}=s;let i=eu.get(e);const D=i&&i.options;return D&&(D.persistentu.interval)&&(i.listeners,i.rawEmitters,se.unwatchFile(e),i=void 0),i?(Be(i,Ce,n),Be(i,Se,r)):(i={listeners:n,rawEmitters:r,options:u,watcher:se.watchFile(e,u,(o,c)=>{Jt(i.rawEmitters,h=>{h(tt,e,{curr:o,prev:c})});const f=o.mtimeMs;(o.size!==c.size||f>c.mtimeMs||f===0)&&Jt(i.listeners,h=>h(t,o))})},eu.set(e,i)),()=>{$e(i,Ce,n),$e(i,Se,r),en(i.listeners)&&(eu.delete(e),se.unwatchFile(e),i.options=i.watcher=void 0,Object.freeze(i))}},"setFsWatchFileListener");let ca=class{static{a(this,"NodeFsHandler")}constructor(e){this.fsw=e,this._boundHandleError=u=>e._handleError(u)}_watchWithNodeFs(e,u){const s=this.fsw.options,n=I.dirname(e),r=I.basename(e);this.fsw._getWatchedDir(n).add(r);const D=I.resolve(e),o={persistent:s.persistent};u||(u=qo);let c;return s.usePolling?(o.interval=s.enableBinaryInterval&&Vo(r)?s.binaryInterval:s.interval,c=la(e,D,o,{listener:u,rawEmitter:this.fsw._emitRaw})):c=aa(e,D,o,{listener:u,errHandler:this._boundHandleError,rawEmitter:this.fsw._emitRaw}),c}_handleFile(e,u,s){if(this.fsw.closed)return;const n=I.dirname(e),r=I.basename(e),i=this.fsw._getWatchedDir(n);let D=u;if(i.has(r))return;const o=a(async(f,h)=>{if(this.fsw._throttle(sa,e,5)){if(!h||h.mtimeMs===0)try{const l=await Js(e);if(this.fsw.closed)return;const p=l.atimeMs,C=l.mtimeMs;(!p||p<=C||C!==D.mtimeMs)&&this.fsw._emit(tt,e,l),Yo&&D.ino!==l.ino?(this.fsw._closeFile(f),D=l,this.fsw._addPathCloser(f,this._watchWithNodeFs(e,o))):D=l}catch{this.fsw._remove(n,r)}else if(i.has(r)){const l=h.atimeMs,p=h.mtimeMs;(!l||l<=p||p!==D.mtimeMs)&&this.fsw._emit(tt,e,h),D=h}}},"listener"),c=this._watchWithNodeFs(e,o);if(!(s&&this.fsw.options.ignoreInitial)&&this.fsw._isntIgnored(e)){if(!this.fsw._throttle(ut,e,0))return;this.fsw._emit(ut,e,u)}return c}async _handleSymlink(e,u,s,n){if(this.fsw.closed)return;const r=e.fullPath,i=this.fsw._getWatchedDir(u);if(!this.fsw.options.followSymlinks){this.fsw._incrReadyCount();let D;try{D=await Zt(s)}catch{return this.fsw._emitReady(),!0}return this.fsw.closed?void 0:(i.has(n)?this.fsw._symlinkPaths.get(r)!==D&&(this.fsw._symlinkPaths.set(r,D),this.fsw._emit(tt,s,e.stats)):(i.add(n),this.fsw._symlinkPaths.set(r,D),this.fsw._emit(ut,s,e.stats)),this.fsw._emitReady(),!0)}if(this.fsw._symlinkPaths.has(r))return!0;this.fsw._symlinkPaths.set(r,!0)}_handleRead(e,u,s,n,r,i,D){if(e=I.join(e,Xo),!s.hasGlob&&(D=this.fsw._throttle("readdir",e,1e3),!D))return;const o=this.fsw._getWatchedDir(s.path),c=new Set;let f=this.fsw._readdirp(e,{fileFilter:a(h=>s.filterPath(h),"fileFilter"),directoryFilter:a(h=>s.filterDir(h),"directoryFilter"),depth:0}).on(Jo,async h=>{if(this.fsw.closed){f=void 0;return}const l=h.path;let p=I.join(e,l);if(c.add(l),!(h.stats.isSymbolicLink()&&await this._handleSymlink(h,e,p,l))){if(this.fsw.closed){f=void 0;return}(l===n||!n&&!o.has(l))&&(this.fsw._incrReadyCount(),p=I.join(r,I.relative(r,p)),this._addToNodeFs(p,u,s,i+1))}}).on(Zs,this._boundHandleError);return new Promise(h=>f.once(ea,()=>{if(this.fsw.closed){f=void 0;return}const l=D?D.clear():!1;h(),o.getChildren().filter(p=>p!==e&&!c.has(p)&&(!s.hasGlob||s.filterPath({fullPath:I.resolve(e,p)}))).forEach(p=>{this.fsw._remove(e,p)}),f=void 0,l&&this._handleRead(e,!1,s,n,r,i,D)}))}async _handleDir(e,u,s,n,r,i,D){const o=this.fsw._getWatchedDir(I.dirname(e)),c=o.has(I.basename(e));!(s&&this.fsw.options.ignoreInitial)&&!r&&!c&&(!i.hasGlob||i.globFilter(e))&&this.fsw._emit(Zo,e,u),o.add(I.basename(e)),this.fsw._getWatchedDir(e);let f,h;const l=this.fsw.options.depth;if((l==null||n<=l)&&!this.fsw._symlinkPaths.has(D)){if(!r&&(await this._handleRead(e,s,i,r,e,n,f),this.fsw.closed))return;h=this._watchWithNodeFs(e,(p,C)=>{C&&C.mtimeMs===0||this._handleRead(p,!1,i,r,e,n,f)})}return h}async _addToNodeFs(e,u,s,n,r){const i=this.fsw._emitReady;if(this.fsw._isIgnored(e)||this.fsw.closed)return i(),!1;const D=this.fsw._getWatchHelpers(e,n);!D.hasGlob&&s&&(D.hasGlob=s.hasGlob,D.globFilter=s.globFilter,D.filterPath=o=>s.filterPath(o),D.filterDir=o=>s.filterDir(o));try{const o=await Da[D.statMethod](D.watchPath);if(this.fsw.closed)return;if(this.fsw._isIgnored(D.watchPath,o))return i(),!1;const c=this.fsw.options.followSymlinks&&!e.includes(ua)&&!e.includes(ta);let f;if(o.isDirectory()){const h=I.resolve(e),l=c?await Zt(e):e;if(this.fsw.closed||(f=await this._handleDir(D.watchPath,o,u,n,r,D,l),this.fsw.closed))return;h!==l&&l!==void 0&&this.fsw._symlinkPaths.set(h,l)}else if(o.isSymbolicLink()){const h=c?await Zt(e):e;if(this.fsw.closed)return;const l=I.dirname(D.watchPath);if(this.fsw._getWatchedDir(l).add(D.watchPath),this.fsw._emit(ut,D.watchPath,o),f=await this._handleDir(l,o,u,n,e,D,h),this.fsw.closed)return;h!==void 0&&this.fsw._symlinkPaths.set(I.resolve(e),h)}else f=this._handleFile(D.watchPath,o,u);return i(),this.fsw._addPathCloser(e,f),!1}catch(o){if(this.fsw._handleError(o))return i(),e}}};var fa=ca,tu={exports:{}};const uu=De,k=z,{promisify:su}=ge;let Fe;try{Fe=Ie("fsevents")}catch(t){process.env.CHOKIDAR_PRINT_FSEVENTS_REQUIRE_ERROR&&console.error(t)}if(Fe){const t=process.version.match(/v(\d+)\.(\d+)/);if(t&&t[1]&&t[2]){const e=Number.parseInt(t[1],10),u=Number.parseInt(t[2],10);e===8&&u<16&&(Fe=void 0)}}const{EV_ADD:nu,EV_CHANGE:ha,EV_ADD_DIR:un,EV_UNLINK:rt,EV_ERROR:da,STR_DATA:Ea,STR_END:pa,FSEVENT_CREATED:Ca,FSEVENT_MODIFIED:Fa,FSEVENT_DELETED:ga,FSEVENT_MOVED:ma,FSEVENT_UNKNOWN:_a,FSEVENT_FLAG_MUST_SCAN_SUBDIRS:Aa,FSEVENT_TYPE_FILE:ya,FSEVENT_TYPE_DIRECTORY:Te,FSEVENT_TYPE_SYMLINK:sn,ROOT_GLOBSTAR:nn,DIR_SUFFIX:wa,DOT_SLASH:rn,FUNCTION_TYPE:ru,EMPTY_FN:Ra,IDENTITY_FN:ba}=et,va=a(t=>isNaN(t)?{}:{depth:t},"Depth"),iu=su(uu.stat),Sa=su(uu.lstat),Dn=su(uu.realpath),Ba={stat:iu,lstat:Sa},ce=new Map,$a=10,Ta=new Set([69888,70400,71424,72704,73472,131328,131840,262912]),xa=a((t,e)=>({stop:Fe.watch(t,e)}),"createFSEventsInstance");function Oa(t,e,u,s){let n=k.extname(e)?k.dirname(e):e;const r=k.dirname(n);let i=ce.get(n);Na(r)&&(n=r);const D=k.resolve(t),o=D!==e,c=a((h,l,p)=>{o&&(h=h.replace(e,D)),(h===D||!h.indexOf(D+k.sep))&&u(h,l,p)},"filteredListener");let f=!1;for(const h of ce.keys())if(e.indexOf(k.resolve(h)+k.sep)===0){n=h,i=ce.get(n),f=!0;break}return i||f?i.listeners.add(c):(i={listeners:new Set([c]),rawEmitter:s,watcher:xa(n,(h,l)=>{if(!i.listeners.size||l&Aa)return;const p=Fe.getInfo(h,l);i.listeners.forEach(C=>{C(h,l,p)}),i.rawEmitter(p.event,h,p)})},ce.set(n,i)),()=>{const h=i.listeners;if(h.delete(c),!h.size&&(ce.delete(n),i.watcher))return i.watcher.stop().then(()=>{i.rawEmitter=i.watcher=void 0,Object.freeze(i)})}}a(Oa,"setFSEventsListener");const Na=a(t=>{let e=0;for(const u of ce.keys())if(u.indexOf(t)===0&&(e++,e>=$a))return!0;return!1},"couldConsolidate"),Ha=a(()=>Fe&&ce.size<128,"canUse"),Du=a((t,e)=>{let u=0;for(;!t.indexOf(e)&&(t=k.dirname(t))!==e;)u++;return u},"calcDepth"),on=a((t,e)=>t.type===Te&&e.isDirectory()||t.type===sn&&e.isSymbolicLink()||t.type===ya&&e.isFile(),"sameTypes");let Pa=class{static{a(this,"FsEventsHandler")}constructor(e){this.fsw=e}checkIgnored(e,u){const s=this.fsw._ignoredPaths;if(this.fsw._isIgnored(e,u))return s.add(e),u&&u.isDirectory()&&s.add(e+nn),!0;s.delete(e),s.delete(e+nn)}addOrChange(e,u,s,n,r,i,D,o){const c=r.has(i)?ha:nu;this.handleEvent(c,e,u,s,n,r,i,D,o)}async checkExists(e,u,s,n,r,i,D,o){try{const c=await iu(e);if(this.fsw.closed)return;on(D,c)?this.addOrChange(e,u,s,n,r,i,D,o):this.handleEvent(rt,e,u,s,n,r,i,D,o)}catch(c){c.code==="EACCES"?this.addOrChange(e,u,s,n,r,i,D,o):this.handleEvent(rt,e,u,s,n,r,i,D,o)}}handleEvent(e,u,s,n,r,i,D,o,c){if(!(this.fsw.closed||this.checkIgnored(u)))if(e===rt){const f=o.type===Te;(f||i.has(D))&&this.fsw._remove(r,D,f)}else{if(e===nu){if(o.type===Te&&this.fsw._getWatchedDir(u),o.type===sn&&c.followSymlinks){const h=c.depth===void 0?void 0:Du(s,n)+1;return this._addToFsEvents(u,!1,!0,h)}this.fsw._getWatchedDir(r).add(D)}const f=o.type===Te?e+wa:e;this.fsw._emit(f,u),f===un&&this._addToFsEvents(u,!1,!0)}}_watchWithFsEvents(e,u,s,n){if(this.fsw.closed||this.fsw._isIgnored(e))return;const r=this.fsw.options,D=Oa(e,u,a(async(o,c,f)=>{if(this.fsw.closed||r.depth!==void 0&&Du(o,u)>r.depth)return;const h=s(k.join(e,k.relative(e,o)));if(n&&!n(h))return;const l=k.dirname(h),p=k.basename(h),C=this.fsw._getWatchedDir(f.type===Te?h:l);if(Ta.has(c)||f.event===_a)if(typeof r.ignored===ru){let g;try{g=await iu(h)}catch{}if(this.fsw.closed||this.checkIgnored(h,g))return;on(f,g)?this.addOrChange(h,o,u,l,C,p,f,r):this.handleEvent(rt,h,o,u,l,C,p,f,r)}else this.checkExists(h,o,u,l,C,p,f,r);else switch(f.event){case Ca:case Fa:return this.addOrChange(h,o,u,l,C,p,f,r);case ga:case ma:return this.checkExists(h,o,u,l,C,p,f,r)}},"watchCallback"),this.fsw._emitRaw);return this.fsw._emitReady(),D}async _handleFsEventsSymlink(e,u,s,n){if(!(this.fsw.closed||this.fsw._symlinkPaths.has(u))){this.fsw._symlinkPaths.set(u,!0),this.fsw._incrReadyCount();try{const r=await Dn(e);if(this.fsw.closed)return;if(this.fsw._isIgnored(r))return this.fsw._emitReady();this.fsw._incrReadyCount(),this._addToFsEvents(r||e,i=>{let D=e;return r&&r!==rn?D=i.replace(r,e):i!==rn&&(D=k.join(e,i)),s(D)},!1,n)}catch(r){if(this.fsw._handleError(r))return this.fsw._emitReady()}}}emitAdd(e,u,s,n,r){const i=s(e),D=u.isDirectory(),o=this.fsw._getWatchedDir(k.dirname(i)),c=k.basename(i);D&&this.fsw._getWatchedDir(i),!o.has(c)&&(o.add(c),(!n.ignoreInitial||r===!0)&&this.fsw._emit(D?un:nu,i,u))}initWatch(e,u,s,n){if(this.fsw.closed)return;const r=this._watchWithFsEvents(s.watchPath,k.resolve(e||s.watchPath),n,s.globFilter);this.fsw._addPathCloser(u,r)}async _addToFsEvents(e,u,s,n){if(this.fsw.closed)return;const r=this.fsw.options,i=typeof u===ru?u:ba,D=this.fsw._getWatchHelpers(e);try{const o=await Ba[D.statMethod](D.watchPath);if(this.fsw.closed)return;if(this.fsw._isIgnored(D.watchPath,o))throw null;if(o.isDirectory()){if(D.globFilter||this.emitAdd(i(e),o,i,r,s),n&&n>r.depth)return;this.fsw._readdirp(D.watchPath,{fileFilter:a(c=>D.filterPath(c),"fileFilter"),directoryFilter:a(c=>D.filterDir(c),"directoryFilter"),...va(r.depth-(n||0))}).on(Ea,c=>{if(this.fsw.closed||c.stats.isDirectory()&&!D.filterPath(c))return;const f=k.join(D.watchPath,c.path),{fullPath:h}=c;if(D.followSymlinks&&c.stats.isSymbolicLink()){const l=r.depth===void 0?void 0:Du(f,k.resolve(D.watchPath))+1;this._handleFsEventsSymlink(f,h,i,l)}else this.emitAdd(f,c.stats,i,r,s)}).on(da,Ra).on(pa,()=>{this.fsw._emitReady()})}else this.emitAdd(D.watchPath,o,i,r,s),this.fsw._emitReady()}catch(o){(!o||this.fsw._handleError(o))&&(this.fsw._emitReady(),this.fsw._emitReady())}if(r.persistent&&s!==!0)if(typeof u===ru)this.initWatch(void 0,e,D,i);else{let o;try{o=await Dn(D.watchPath)}catch{}this.initWatch(o,e,D,i)}}};tu.exports=Pa,tu.exports.canUse=Ha;var La=tu.exports;const{EventEmitter:Ia}=Ln,ou=De,S=z,{promisify:an}=ge,ka=xD,au=ID.default,Ma=XD,lu=Bs,Ga=ko,Wa=ws,ja=fa,ln=La,{EV_ALL:cu,EV_READY:Ua,EV_ADD:it,EV_CHANGE:xe,EV_UNLINK:cn,EV_ADD_DIR:Ka,EV_UNLINK_DIR:Va,EV_RAW:za,EV_ERROR:fu,STR_CLOSE:Ya,STR_END:qa,BACK_SLASH_RE:Xa,DOUBLE_SLASH_RE:fn,SLASH_OR_BACK_SLASH_RE:Qa,DOT_RE:Za,REPLACER_RE:Ja,SLASH:hu,SLASH_SLASH:el,BRACE_START:tl,BANG:du,ONE_DOT:hn,TWO_DOTS:ul,GLOBSTAR:sl,SLASH_GLOBSTAR:Eu,ANYMATCH_OPTS:pu,STRING_TYPE:Cu,FUNCTION_TYPE:nl,EMPTY_STR:Fu,EMPTY_FN:rl,isWindows:il,isMacos:Dl,isIBMi:ol}=et,al=an(ou.stat),ll=an(ou.readdir),gu=a((t=[])=>Array.isArray(t)?t:[t],"arrify"),dn=a((t,e=[])=>(t.forEach(u=>{Array.isArray(u)?dn(u,e):e.push(u)}),e),"flatten"),En=a(t=>{const e=dn(gu(t));if(!e.every(u=>typeof u===Cu))throw new TypeError(`Non-string provided as watch path: ${e}`);return e.map(Cn)},"unifyPaths"),pn=a(t=>{let e=t.replace(Xa,hu),u=!1;for(e.startsWith(el)&&(u=!0);e.match(fn);)e=e.replace(fn,hu);return u&&(e=hu+e),e},"toUnix"),Cn=a(t=>pn(S.normalize(pn(t))),"normalizePathToUnix"),Fn=a((t=Fu)=>e=>typeof e!==Cu?e:Cn(S.isAbsolute(e)?e:S.join(t,e)),"normalizeIgnored"),cl=a((t,e)=>S.isAbsolute(t)?t:t.startsWith(du)?du+S.join(e,t.slice(1)):S.join(e,t),"getAbsolutePath"),X=a((t,e)=>t[e]===void 0,"undef");class fl{static{a(this,"DirEntry")}constructor(e,u){this.path=e,this._removeWatcher=u,this.items=new Set}add(e){const{items:u}=this;u&&e!==hn&&e!==ul&&u.add(e)}async remove(e){const{items:u}=this;if(!u||(u.delete(e),u.size>0))return;const s=this.path;try{await ll(s)}catch{this._removeWatcher&&this._removeWatcher(S.dirname(s),S.basename(s))}}has(e){const{items:u}=this;if(u)return u.has(e)}getChildren(){const{items:e}=this;if(e)return[...e.values()]}dispose(){this.items.clear(),delete this.path,delete this._removeWatcher,delete this.items,Object.freeze(this)}}const hl="stat",dl="lstat";class El{static{a(this,"WatchHelper")}constructor(e,u,s,n){this.fsw=n,this.path=e=e.replace(Ja,Fu),this.watchPath=u,this.fullWatchPath=S.resolve(u),this.hasGlob=u!==e,e===Fu&&(this.hasGlob=!1),this.globSymlink=this.hasGlob&&s?void 0:!1,this.globFilter=this.hasGlob?au(e,void 0,pu):!1,this.dirParts=this.getDirParts(e),this.dirParts.forEach(r=>{r.length>1&&r.pop()}),this.followSymlinks=s,this.statMethod=s?hl:dl}checkGlobSymlink(e){return this.globSymlink===void 0&&(this.globSymlink=e.fullParentDir===this.fullWatchPath?!1:{realPath:e.fullParentDir,linkPath:this.fullWatchPath}),this.globSymlink?e.fullPath.replace(this.globSymlink.realPath,this.globSymlink.linkPath):e.fullPath}entryPath(e){return S.join(this.watchPath,S.relative(this.watchPath,this.checkGlobSymlink(e)))}filterPath(e){const{stats:u}=e;if(u&&u.isSymbolicLink())return this.filterDir(e);const s=this.entryPath(e);return(this.hasGlob&&typeof this.globFilter===nl?this.globFilter(s):!0)&&this.fsw._isntIgnored(s,u)&&this.fsw._hasReadPermissions(u)}getDirParts(e){if(!this.hasGlob)return[];const u=[];return(e.includes(tl)?Ga.expand(e):[e]).forEach(n=>{u.push(S.relative(this.watchPath,n).split(Qa))}),u}filterDir(e){if(this.hasGlob){const u=this.getDirParts(this.checkGlobSymlink(e));let s=!1;this.unmatchedGlob=!this.dirParts.some(n=>n.every((r,i)=>(r===sl&&(s=!0),s||!u[0][i]||au(r,u[0][i],pu))))}return!this.unmatchedGlob&&this.fsw._isntIgnored(this.entryPath(e),e.stats)}}class pl extends Ia{static{a(this,"FSWatcher")}constructor(e){super();const u={};e&&Object.assign(u,e),this._watched=new Map,this._closers=new Map,this._ignoredPaths=new Set,this._throttled=new Map,this._symlinkPaths=new Map,this._streams=new Set,this.closed=!1,X(u,"persistent")&&(u.persistent=!0),X(u,"ignoreInitial")&&(u.ignoreInitial=!1),X(u,"ignorePermissionErrors")&&(u.ignorePermissionErrors=!1),X(u,"interval")&&(u.interval=100),X(u,"binaryInterval")&&(u.binaryInterval=300),X(u,"disableGlobbing")&&(u.disableGlobbing=!1),u.enableBinaryInterval=u.binaryInterval!==u.interval,X(u,"useFsEvents")&&(u.useFsEvents=!u.usePolling),ln.canUse()||(u.useFsEvents=!1),X(u,"usePolling")&&!u.useFsEvents&&(u.usePolling=Dl),ol&&(u.usePolling=!0);const n=process.env.CHOKIDAR_USEPOLLING;if(n!==void 0){const o=n.toLowerCase();o==="false"||o==="0"?u.usePolling=!1:o==="true"||o==="1"?u.usePolling=!0:u.usePolling=!!o}const r=process.env.CHOKIDAR_INTERVAL;r&&(u.interval=Number.parseInt(r,10)),X(u,"atomic")&&(u.atomic=!u.usePolling&&!u.useFsEvents),u.atomic&&(this._pendingUnlinks=new Map),X(u,"followSymlinks")&&(u.followSymlinks=!0),X(u,"awaitWriteFinish")&&(u.awaitWriteFinish=!1),u.awaitWriteFinish===!0&&(u.awaitWriteFinish={});const i=u.awaitWriteFinish;i&&(i.stabilityThreshold||(i.stabilityThreshold=2e3),i.pollInterval||(i.pollInterval=100),this._pendingWrites=new Map),u.ignored&&(u.ignored=gu(u.ignored));let D=0;this._emitReady=()=>{D++,D>=this._readyCount&&(this._emitReady=rl,this._readyEmitted=!0,process.nextTick(()=>this.emit(Ua)))},this._emitRaw=(...o)=>this.emit(za,...o),this._readyEmitted=!1,this.options=u,u.useFsEvents?this._fsEventsHandler=new ln(this):this._nodeFsHandler=new ja(this),Object.freeze(u)}add(e,u,s){const{cwd:n,disableGlobbing:r}=this.options;this.closed=!1;let i=En(e);return n&&(i=i.map(D=>{const o=cl(D,n);return r||!lu(D)?o:Wa(o)})),i=i.filter(D=>D.startsWith(du)?(this._ignoredPaths.add(D.slice(1)),!1):(this._ignoredPaths.delete(D),this._ignoredPaths.delete(D+Eu),this._userIgnored=void 0,!0)),this.options.useFsEvents&&this._fsEventsHandler?(this._readyCount||(this._readyCount=i.length),this.options.persistent&&(this._readyCount+=i.length),i.forEach(D=>this._fsEventsHandler._addToFsEvents(D))):(this._readyCount||(this._readyCount=0),this._readyCount+=i.length,Promise.all(i.map(async D=>{const o=await this._nodeFsHandler._addToNodeFs(D,!s,0,0,u);return o&&this._emitReady(),o})).then(D=>{this.closed||D.filter(o=>o).forEach(o=>{this.add(S.dirname(o),S.basename(u||o))})})),this}unwatch(e){if(this.closed)return this;const u=En(e),{cwd:s}=this.options;return u.forEach(n=>{!S.isAbsolute(n)&&!this._closers.has(n)&&(s&&(n=S.join(s,n)),n=S.resolve(n)),this._closePath(n),this._ignoredPaths.add(n),this._watched.has(n)&&this._ignoredPaths.add(n+Eu),this._userIgnored=void 0}),this}close(){if(this.closed)return this._closePromise;this.closed=!0,this.removeAllListeners();const e=[];return this._closers.forEach(u=>u.forEach(s=>{const n=s();n instanceof Promise&&e.push(n)})),this._streams.forEach(u=>u.destroy()),this._userIgnored=void 0,this._readyCount=0,this._readyEmitted=!1,this._watched.forEach(u=>u.dispose()),["closers","watched","streams","symlinkPaths","throttled"].forEach(u=>{this[`_${u}`].clear()}),this._closePromise=e.length?Promise.all(e).then(()=>{}):Promise.resolve(),this._closePromise}getWatched(){const e={};return this._watched.forEach((u,s)=>{const n=this.options.cwd?S.relative(this.options.cwd,s):s;e[n||hn]=u.getChildren().sort()}),e}emitWithAll(e,u){this.emit(...u),e!==fu&&this.emit(cu,...u)}async _emit(e,u,s,n,r){if(this.closed)return;const i=this.options;il&&(u=S.normalize(u)),i.cwd&&(u=S.relative(i.cwd,u));const D=[e,u];r!==void 0?D.push(s,n,r):n!==void 0?D.push(s,n):s!==void 0&&D.push(s);const o=i.awaitWriteFinish;let c;if(o&&(c=this._pendingWrites.get(u)))return c.lastChange=new Date,this;if(i.atomic){if(e===cn)return this._pendingUnlinks.set(u,D),setTimeout(()=>{this._pendingUnlinks.forEach((f,h)=>{this.emit(...f),this.emit(cu,...f),this._pendingUnlinks.delete(h)})},typeof i.atomic=="number"?i.atomic:100),this;e===it&&this._pendingUnlinks.has(u)&&(e=D[0]=xe,this._pendingUnlinks.delete(u))}if(o&&(e===it||e===xe)&&this._readyEmitted){const f=a((h,l)=>{h?(e=D[0]=fu,D[1]=h,this.emitWithAll(e,D)):l&&(D.length>2?D[2]=l:D.push(l),this.emitWithAll(e,D))},"awfEmit");return this._awaitWriteFinish(u,o.stabilityThreshold,e,f),this}if(e===xe&&!this._throttle(xe,u,50))return this;if(i.alwaysStat&&s===void 0&&(e===it||e===Ka||e===xe)){const f=i.cwd?S.join(i.cwd,u):u;let h;try{h=await al(f)}catch{}if(!h||this.closed)return;D.push(h)}return this.emitWithAll(e,D),this}_handleError(e){const u=e&&e.code;return e&&u!=="ENOENT"&&u!=="ENOTDIR"&&(!this.options.ignorePermissionErrors||u!=="EPERM"&&u!=="EACCES")&&this.emit(fu,e),e||this.closed}_throttle(e,u,s){this._throttled.has(e)||this._throttled.set(e,new Map);const n=this._throttled.get(e),r=n.get(u);if(r)return r.count++,!1;let i;const D=a(()=>{const c=n.get(u),f=c?c.count:0;return n.delete(u),clearTimeout(i),c&&clearTimeout(c.timeoutObject),f},"clear");i=setTimeout(D,s);const o={timeoutObject:i,clear:D,count:0};return n.set(u,o),o}_incrReadyCount(){return this._readyCount++}_awaitWriteFinish(e,u,s,n){let r,i=e;this.options.cwd&&!S.isAbsolute(e)&&(i=S.join(this.options.cwd,e));const D=new Date,o=a(c=>{ou.stat(i,(f,h)=>{if(f||!this._pendingWrites.has(e)){f&&f.code!=="ENOENT"&&n(f);return}const l=Number(new Date);c&&h.size!==c.size&&(this._pendingWrites.get(e).lastChange=l);const p=this._pendingWrites.get(e);l-p.lastChange>=u?(this._pendingWrites.delete(e),n(void 0,h)):r=setTimeout(o,this.options.awaitWriteFinish.pollInterval,h)})},"awaitWriteFinish");this._pendingWrites.has(e)||(this._pendingWrites.set(e,{lastChange:D,cancelWait:a(()=>(this._pendingWrites.delete(e),clearTimeout(r),s),"cancelWait")}),r=setTimeout(o,this.options.awaitWriteFinish.pollInterval))}_getGlobIgnored(){return[...this._ignoredPaths.values()]}_isIgnored(e,u){if(this.options.atomic&&Za.test(e))return!0;if(!this._userIgnored){const{cwd:s}=this.options,n=this.options.ignored,r=n&&n.map(Fn(s)),i=gu(r).filter(o=>typeof o===Cu&&!lu(o)).map(o=>o+Eu),D=this._getGlobIgnored().map(Fn(s)).concat(r,i);this._userIgnored=au(D,void 0,pu)}return this._userIgnored([e,u])}_isntIgnored(e,u){return!this._isIgnored(e,u)}_getWatchHelpers(e,u){const s=u||this.options.disableGlobbing||!lu(e)?e:Ma(e),n=this.options.followSymlinks;return new El(e,s,n,this)}_getWatchedDir(e){this._boundRemove||(this._boundRemove=this._remove.bind(this));const u=S.resolve(e);return this._watched.has(u)||this._watched.set(u,new fl(u,this._boundRemove)),this._watched.get(u)}_hasReadPermissions(e){if(this.options.ignorePermissionErrors)return!0;const s=(e&&Number.parseInt(e.mode,10))&511;return!!(4&Number.parseInt(s.toString(8)[0],10))}_remove(e,u,s){const n=S.join(e,u),r=S.resolve(n);if(s=s??(this._watched.has(n)||this._watched.has(r)),!this._throttle("remove",n,100))return;!s&&!this.options.useFsEvents&&this._watched.size===1&&this.add(e,u,!0),this._getWatchedDir(n).getChildren().forEach(l=>this._remove(n,l));const o=this._getWatchedDir(e),c=o.has(u);o.remove(u),this._symlinkPaths.has(r)&&this._symlinkPaths.delete(r);let f=n;if(this.options.cwd&&(f=S.relative(this.options.cwd,n)),this.options.awaitWriteFinish&&this._pendingWrites.has(f)&&this._pendingWrites.get(f).cancelWait()===it)return;this._watched.delete(n),this._watched.delete(r);const h=s?Va:cn;c&&!this._isIgnored(n)&&this._emit(h,n),this.options.useFsEvents||this._closePath(n)}_closePath(e){this._closeFile(e);const u=S.dirname(e);this._getWatchedDir(u).remove(S.basename(e))}_closeFile(e){const u=this._closers.get(e);u&&(u.forEach(s=>s()),this._closers.delete(e))}_addPathCloser(e,u){if(!u)return;let s=this._closers.get(e);s||(s=[],this._closers.set(e,s)),s.push(u)}_readdirp(e,u){if(this.closed)return;const s={type:cu,alwaysStat:!0,lstat:!0,...u};let n=ka(e,s);return this._streams.add(n),n.once(Ya,()=>{n=void 0}),n.once(qa,()=>{n&&(this._streams.delete(n),n=void 0)}),n}}const Cl=a((t,e)=>{const u=new pl(e);return u.add(t),u},"watch");var Fl=Cl;const Dt=a((t=!0)=>{let e=!1;return u=>{if(e||u==="unknown-flag")return!0;if(u==="argument")return e=!0,t}},"ignoreAfterArgument"),gn=a((t,e=process.argv.slice(2))=>(yu(t,e,{ignore:Dt()}),e),"removeArgvFlags"),gl=a(t=>{let e=Buffer.alloc(0);return u=>{for(e=Buffer.concat([e,u]);e.length>4;){const s=e.readInt32BE(0);if(e.length>=4+s){const n=e.slice(4,4+s);t(n),e=e.slice(4+s)}else break}}},"bufferData"),mn=a(async()=>{const t=jn.createServer(u=>{u.on("data",gl(s=>{const n=JSON.parse(s.toString());t.emit("data",n)}))}),e=Bn(process.pid);return await ct.promises.mkdir(Un,{recursive:!0}),await ct.promises.rm(e,{force:!0}),await new Promise((u,s)=>{t.listen(e,u),t.on("error",s)}),t.unref(),process.on("exit",()=>{if(t.close(),!$n)try{ct.rmSync(e)}catch{}}),t},"createIpcServer"),ml=a(()=>new Date().toLocaleTimeString(),"currentTime"),Oe=a((...t)=>console.log(kn(ml()),Mn("[tsx]"),...t),"log"),_l="\x1Bc",Al=a((t,e)=>{let u;return function(){u&&clearTimeout(u),u=setTimeout(()=>Reflect.apply(t,this,arguments),e)}},"debounce"),_n={noCache:{type:Boolean,description:"Disable caching",default:!1},tsconfig:{type:String,description:"Custom tsconfig.json path"},clearScreen:{type:Boolean,description:"Clearing the screen on rerun",default:!0},ignore:{type:[String],description:"Paths & globs to exclude from being watched (Deprecated: use --exclude)"},include:{type:[String],description:"Additional paths & globs to watch"},exclude:{type:[String],description:"Paths & globs to exclude from being watched"}},yl=oi({name:"watch",parameters:["') + t.assert.equal(found.isEmpty, true) + t.assert.equal(found.isValid, false) + + found = new ContentType('application/json/extra/slashes') + t.assert.equal(found.isEmpty, true) + t.assert.equal(found.isValid, false) + + found = new ContentType('application/json(garbage)') + t.assert.equal(found.isEmpty, true) + t.assert.equal(found.isValid, false) + + found = new ContentType('application/json@evil') + t.assert.equal(found.isEmpty, true) + t.assert.equal(found.isValid, false) + + found = new ContentType('application/json\x00garbage') + t.assert.equal(found.isEmpty, true) + t.assert.equal(found.isValid, false) + }) + + test('subtype with multiple fields validates as incorrect', (t) => { + let found = new ContentType('application/json whatever') + t.assert.equal(found.isValid, false) + t.assert.equal(found.isEmpty, true) + + found = new ContentType('application/ json whatever') + t.assert.equal(found.isValid, false) + t.assert.equal(found.isEmpty, true) + + found = new ContentType('application/json whatever; foo=bar') + t.assert.equal(found.isValid, false) + t.assert.equal(found.isEmpty, true) + + found = new ContentType('application/ json whatever; foo=bar') + t.assert.equal(found.isValid, false) + t.assert.equal(found.isEmpty, true) + }) + + test('returns a plain media type instance', (t) => { + const found = new ContentType('Application/JSON') + t.assert.equal(found.mediaType, 'application/json') + t.assert.equal(found.type, 'application') + t.assert.equal(found.subtype, 'json') + t.assert.equal(found.parameters.size, 0) + }) + + test('handles empty parameters list', (t) => { + const found = new ContentType('Application/JSON ;') + t.assert.equal(found.isEmpty, false) + t.assert.equal(found.mediaType, 'application/json') + t.assert.equal(found.type, 'application') + t.assert.equal(found.subtype, 'json') + t.assert.equal(found.parameters.size, 0) + }) + + test('returns a media type instance with parameters', (t) => { + const found = new ContentType('Application/JSON ; charset=utf-8; foo=BaR;baz=" 42"') + t.assert.equal(found.isEmpty, false) + t.assert.equal(found.mediaType, 'application/json') + t.assert.equal(found.type, 'application') + t.assert.equal(found.subtype, 'json') + t.assert.equal(found.parameters.size, 3) + + const expected = [ + ['charset', 'utf-8'], + ['foo', 'BaR'], + ['baz', ' 42'] + ] + t.assert.deepStrictEqual( + Array.from(found.parameters.entries()), + expected + ) + + t.assert.equal( + found.toString(), + 'application/json; charset="utf-8"; foo="BaR"; baz=" 42"' + ) + }) + + test('skips invalid quoted string parameters', (t) => { + const found = new ContentType('Application/JSON ; charset=utf-8; foo=BaR;baz=" 42') + t.assert.equal(found.isEmpty, false) + t.assert.equal(found.mediaType, 'application/json') + t.assert.equal(found.type, 'application') + t.assert.equal(found.subtype, 'json') + t.assert.equal(found.parameters.size, 3) + + const expected = [ + ['charset', 'utf-8'], + ['foo', 'BaR'], + ['baz', 'invalid quoted string'] + ] + t.assert.deepStrictEqual( + Array.from(found.parameters.entries()), + expected + ) + + t.assert.equal( + found.toString(), + 'application/json; charset="utf-8"; foo="BaR"; baz="invalid quoted string"' + ) + }) +}) diff --git a/services/slides/node_modules/fastify/test/context-config.test.js b/services/slides/node_modules/fastify/test/context-config.test.js new file mode 100644 index 0000000000000000000000000000000000000000..00f4bc62068037814fa54791ecb7f353ad508f00 --- /dev/null +++ b/services/slides/node_modules/fastify/test/context-config.test.js @@ -0,0 +1,164 @@ +'use strict' + +const { test } = require('node:test') + +const { kRouteContext } = require('../lib/symbols') +const Fastify = require('..') + +const schema = { + schema: { }, + config: { + value1: 'foo', + value2: true + } +} + +function handler (req, reply) { + reply.send(reply[kRouteContext].config) +} + +test('config', async t => { + t.plan(6) + const fastify = Fastify() + + fastify.get('/get', { + schema: schema.schema, + config: Object.assign({}, schema.config) + }, handler) + + fastify.route({ + method: 'GET', + url: '/route', + schema: schema.schema, + handler, + config: Object.assign({}, schema.config) + }) + + fastify.route({ + method: 'GET', + url: '/no-config', + schema: schema.schema, + handler + }) + + let response = await fastify.inject({ + method: 'GET', + url: '/route' + }) + + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(response.json(), Object.assign({ url: '/route', method: 'GET' }, schema.config)) + + response = await fastify.inject({ + method: 'GET', + url: '/route' + }) + + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(response.json(), Object.assign({ url: '/route', method: 'GET' }, schema.config)) + + response = await fastify.inject({ + method: 'GET', + url: '/no-config' + }) + + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(response.json(), { url: '/no-config', method: 'GET' }) +}) + +test('config with exposeHeadRoutes', async t => { + t.plan(6) + const fastify = Fastify({ exposeHeadRoutes: true }) + + fastify.get('/get', { + schema: schema.schema, + config: Object.assign({}, schema.config) + }, handler) + + fastify.route({ + method: 'GET', + url: '/route', + schema: schema.schema, + handler, + config: Object.assign({}, schema.config) + }) + + fastify.route({ + method: 'GET', + url: '/no-config', + schema: schema.schema, + handler + }) + + let response = await fastify.inject({ + method: 'GET', + url: '/get' + }) + + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(response.json(), Object.assign({ url: '/get', method: 'GET' }, schema.config)) + + response = await fastify.inject({ + method: 'GET', + url: '/route' + }) + + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(response.json(), Object.assign({ url: '/route', method: 'GET' }, schema.config)) + + response = await fastify.inject({ + method: 'GET', + url: '/no-config' + }) + + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(response.json(), { url: '/no-config', method: 'GET' }) +}) + +test('config without exposeHeadRoutes', async t => { + t.plan(6) + const fastify = Fastify({ exposeHeadRoutes: false }) + + fastify.get('/get', { + schema: schema.schema, + config: Object.assign({}, schema.config) + }, handler) + + fastify.route({ + method: 'GET', + url: '/route', + schema: schema.schema, + handler, + config: Object.assign({}, schema.config) + }) + + fastify.route({ + method: 'GET', + url: '/no-config', + schema: schema.schema, + handler + }) + + let response = await fastify.inject({ + method: 'GET', + url: '/get' + }) + + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(response.json(), Object.assign({ url: '/get', method: 'GET' }, schema.config)) + + response = await fastify.inject({ + method: 'GET', + url: '/route' + }) + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(response.json(), Object.assign({ url: '/route', method: 'GET' }, schema.config)) + + response = await fastify.inject({ + method: 'GET', + url: '/no-config' + }) + + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(response.json(), { url: '/no-config', method: 'GET' }) +}) diff --git a/services/slides/node_modules/fastify/test/custom-http-server.test.js b/services/slides/node_modules/fastify/test/custom-http-server.test.js new file mode 100644 index 0000000000000000000000000000000000000000..b0cf7c64789e17ad296ca874569d5006132e845b --- /dev/null +++ b/services/slides/node_modules/fastify/test/custom-http-server.test.js @@ -0,0 +1,118 @@ +'use strict' + +const { test } = require('node:test') +const http = require('node:http') +const dns = require('node:dns').promises +const Fastify = require('..') +const { FST_ERR_FORCE_CLOSE_CONNECTIONS_IDLE_NOT_AVAILABLE } = require('../lib/errors') + +async function setup () { + const localAddresses = await dns.lookup('localhost', { all: true }) + + test('Should support a custom http server', { skip: localAddresses.length < 1 }, async t => { + t.plan(5) + + const fastify = Fastify({ + serverFactory: (handler, opts) => { + t.assert.ok(opts.serverFactory, 'it is called once for localhost') + + const server = http.createServer((req, res) => { + req.custom = true + handler(req, res) + }) + + return server + } + }) + + t.after(() => fastify.close()) + fastify.get('/', (req, reply) => { + t.assert.ok(req.raw.custom) + reply.send({ hello: 'world' }) + }) + + await fastify.listen({ port: 0 }) + + const response = await fetch('http://localhost:' + fastify.server.address().port, { + method: 'GET' + }) + t.assert.ok(response.ok) + t.assert.strictEqual(response.status, 200) + const body = await response.text() + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + }) + + test('Should not allow forceCloseConnection=idle if the server does not support closeIdleConnections', t => { + t.plan(1) + + t.assert.throws( + () => { + Fastify({ + forceCloseConnections: 'idle', + serverFactory (handler, opts) { + return { + on () { + + } + } + } + }) + }, + FST_ERR_FORCE_CLOSE_CONNECTIONS_IDLE_NOT_AVAILABLE, + "Cannot set forceCloseConnections to 'idle' as your HTTP server does not support closeIdleConnections method" + ) + }) + + test('Should accept user defined serverFactory and ignore secondary server creation', async t => { + const server = http.createServer(() => { }) + t.after(() => new Promise(resolve => server.close(resolve))) + const app = Fastify({ + serverFactory: () => server + }) + await t.assert.doesNotReject(async () => { await app.listen({ port: 0 }) }) + }) + + test('Should not call close on the server if it has not created it', async t => { + const server = http.createServer() + + const serverFactory = (handler, opts) => { + server.on('request', handler) + return server + } + + const fastify = Fastify({ serverFactory }) + + fastify.get('/', (req, reply) => { + reply.send({ hello: 'world' }) + }) + + await fastify.ready() + + await new Promise((resolve, reject) => { + server.listen(0) + server.on('listening', resolve) + server.on('error', reject) + }) + + const address = server.address() + t.assert.strictEqual(server.listening, true) + await fastify.close() + + t.assert.strictEqual(server.listening, true) + t.assert.deepStrictEqual(server.address(), address) + t.assert.deepStrictEqual(fastify.addresses(), [address]) + + await new Promise((resolve, reject) => { + server.close((err) => { + if (err) { + return reject(err) + } + resolve() + }) + }) + t.assert.strictEqual(server.listening, false) + t.assert.deepStrictEqual(server.address(), null) + }) +} + +setup() diff --git a/services/slides/node_modules/fastify/test/custom-parser-async.test.js b/services/slides/node_modules/fastify/test/custom-parser-async.test.js new file mode 100644 index 0000000000000000000000000000000000000000..679d99e0f76ea867cab3ef269759635a4f23756e --- /dev/null +++ b/services/slides/node_modules/fastify/test/custom-parser-async.test.js @@ -0,0 +1,59 @@ +'use strict' + +const { test } = require('node:test') +const Fastify = require('../fastify') + +process.removeAllListeners('warning') + +test('contentTypeParser should add a custom async parser', async t => { + t.plan(2) + const fastify = Fastify() + + fastify.post('/', (req, reply) => { + reply.send(req.body) + }) + + fastify.options('/', (req, reply) => { + reply.send(req.body) + }) + + fastify.addContentTypeParser('application/jsoff', async function (req, payload) { + const res = await new Promise((resolve, reject) => resolve(payload)) + return res + }) + + t.after(() => fastify.close()) + const fastifyServer = await fastify.listen({ port: 0 }) + + await t.test('in POST', async t => { + t.plan(3) + + const result = await fetch(fastifyServer, { + method: 'POST', + headers: { + 'Content-Type': 'application/jsoff' + }, + body: '{"hello":"world"}' + }) + + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.deepStrictEqual(await result.json(), { hello: 'world' }) + }) + + await t.test('in OPTIONS', async t => { + t.plan(3) + + const result = await fetch(fastifyServer, { + method: 'OPTIONS', + headers: { + 'Content-Type': 'application/jsoff' + }, + body: '{"hello":"world"}' + }) + + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.deepStrictEqual(await result.json(), { hello: 'world' }) + }) +}) diff --git a/services/slides/node_modules/fastify/test/custom-parser.0.test.js b/services/slides/node_modules/fastify/test/custom-parser.0.test.js new file mode 100644 index 0000000000000000000000000000000000000000..a7649ff4f2b34e1b22fb4ec3486a5c16f431aecc --- /dev/null +++ b/services/slides/node_modules/fastify/test/custom-parser.0.test.js @@ -0,0 +1,701 @@ +'use strict' + +const fs = require('node:fs') +const { test } = require('node:test') +const Fastify = require('../fastify') +const jsonParser = require('fast-json-body') +const { plainTextParser } = require('./helper') + +process.removeAllListeners('warning') + +test('contentTypeParser method should exist', t => { + t.plan(1) + const fastify = Fastify() + t.assert.ok(fastify.addContentTypeParser) +}) + +test('contentTypeParser should add a custom parser', async (t) => { + t.plan(2) + const fastify = Fastify() + + fastify.post('/', (req, reply) => { + reply.send(req.body) + }) + + fastify.options('/', (req, reply) => { + reply.send(req.body) + }) + + fastify.addContentTypeParser('application/jsoff', function (req, payload, done) { + jsonParser(payload, function (err, body) { + done(err, body) + }) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + await t.test('in POST', async (t) => { + t.plan(3) + + const result = await fetch(fastifyServer, { + method: 'POST', + body: '{"hello":"world"}', + headers: { + 'Content-Type': 'application/jsoff' + } + }) + + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.deepStrictEqual(await result.json(), { hello: 'world' }) + }) + + await t.test('in OPTIONS', async (t) => { + t.plan(2) + + const result = await fetch(fastifyServer, { + method: 'OPTIONS', + body: '{"hello":"world"}', + headers: { + 'Content-Type': 'application/jsoff' + } + }) + + t.assert.strictEqual(result.status, 200) + const body = await result.text() + t.assert.strictEqual(body, JSON.stringify({ hello: 'world' })) + }) +}) + +test('contentTypeParser should handle multiple custom parsers', async (t) => { + t.plan(6) + const fastify = Fastify() + + fastify.post('/', (req, reply) => { + reply.send(req.body) + }) + + fastify.post('/hello', (req, reply) => { + reply.send(req.body) + }) + + function customParser (req, payload, done) { + jsonParser(payload, function (err, body) { + done(err, body) + }) + } + + fastify.addContentTypeParser('application/jsoff', customParser) + fastify.addContentTypeParser('application/ffosj', customParser) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const result1 = await fetch(fastifyServer, { + method: 'POST', + body: '{"hello":"world"}', + headers: { + 'Content-Type': 'application/jsoff' + } + }) + + t.assert.ok(result1.ok) + t.assert.strictEqual(result1.status, 200) + t.assert.deepStrictEqual(await result1.json(), { hello: 'world' }) + + const result2 = await fetch(fastifyServer + '/hello', { + method: 'POST', + body: '{"hello":"world"}', + headers: { + 'Content-Type': 'application/ffosj' + } + }) + + t.assert.ok(result2.ok) + t.assert.strictEqual(result2.status, 200) + + t.assert.deepStrictEqual(await result2.json(), { hello: 'world' }) +}) + +test('contentTypeParser should handle an array of custom contentTypes', async (t) => { + t.plan(6) + const fastify = Fastify() + + fastify.post('/', (req, reply) => { + reply.send(req.body) + }) + + fastify.post('/hello', (req, reply) => { + reply.send(req.body) + }) + + function customParser (req, payload, done) { + jsonParser(payload, function (err, body) { + done(err, body) + }) + } + + fastify.addContentTypeParser(['application/jsoff', 'application/ffosj'], customParser) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const result1 = await fetch(fastifyServer, { + method: 'POST', + body: '{"hello":"world"}', + headers: { + 'Content-Type': 'application/jsoff' + } + }) + + t.assert.ok(result1.ok) + t.assert.strictEqual(result1.status, 200) + t.assert.deepStrictEqual(await result1.json(), { hello: 'world' }) + + const result2 = await fetch(fastifyServer + '/hello', { + method: 'POST', + body: '{"hello":"world"}', + headers: { + 'Content-Type': 'application/ffosj' + } + }) + + t.assert.ok(result2.ok) + t.assert.strictEqual(result2.status, 200) + t.assert.deepStrictEqual(await result2.json(), { hello: 'world' }) +}) + +test('contentTypeParser should handle errors', async (t) => { + t.plan(1) + const fastify = Fastify() + + fastify.post('/', (req, reply) => { + reply.send(req.body) + }) + + fastify.addContentTypeParser('application/jsoff', function (req, payload, done) { + done(new Error('kaboom!'), {}) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const result = await fetch(fastifyServer, { + method: 'POST', + body: '{"hello":"world"}', + headers: { + 'Content-Type': 'application/jsoff' + } + }) + + t.assert.strictEqual(result.status, 500) +}) + +test('contentTypeParser should support encapsulation', (t, testDone) => { + t.plan(6) + const fastify = Fastify() + + fastify.register((instance, opts, done) => { + instance.addContentTypeParser('application/jsoff', () => {}) + t.assert.ok(instance.hasContentTypeParser('application/jsoff')) + + instance.register((instance, opts, done) => { + instance.addContentTypeParser('application/ffosj', () => {}) + t.assert.ok(instance.hasContentTypeParser('application/jsoff')) + t.assert.ok(instance.hasContentTypeParser('application/ffosj')) + done() + }) + + done() + }) + + fastify.ready(err => { + t.assert.ifError(err) + t.assert.ok(!fastify.hasContentTypeParser('application/jsoff')) + t.assert.ok(!fastify.hasContentTypeParser('application/ffosj')) + testDone() + }) +}) + +test('contentTypeParser should support encapsulation, second try', async (t) => { + t.plan(2) + const fastify = Fastify() + + fastify.register((instance, opts, done) => { + instance.post('/', (req, reply) => { + reply.send(req.body) + }) + + instance.addContentTypeParser('application/jsoff', function (req, payload, done) { + jsonParser(payload, function (err, body) { + done(err, body) + }) + }) + + done() + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const result = await fetch(fastifyServer, { + method: 'POST', + body: '{"hello":"world"}', + headers: { + 'Content-Type': 'application/jsoff' + } + }) + + t.assert.strictEqual(result.status, 200) + const body = await result.text() + t.assert.strictEqual(body, JSON.stringify({ hello: 'world' })) +}) + +test('contentTypeParser shouldn\'t support request with undefined "Content-Type"', async (t) => { + t.plan(1) + const fastify = Fastify() + + fastify.post('/', (req, reply) => { + reply.send(req.body) + }) + + fastify.addContentTypeParser('application/jsoff', function (req, payload, done) { + jsonParser(payload, function (err, body) { + done(err, body) + }) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const result = await fetch(fastifyServer, { + method: 'POST', + body: 'unknown content type!', + headers: { + 'Content-Type': undefined + } + }) + + t.assert.strictEqual(result.status, 415) +}) + +test('the content type should be a string or RegExp', t => { + t.plan(2) + const fastify = Fastify() + + try { + fastify.addContentTypeParser(null, () => {}) + t.assert.fail() + } catch (err) { + t.assert.strictEqual(err.code, 'FST_ERR_CTP_INVALID_TYPE') + t.assert.strictEqual(err.message, 'The content type should be a string or a RegExp') + } +}) + +test('the content type cannot be an empty string', t => { + t.plan(2) + const fastify = Fastify() + + try { + fastify.addContentTypeParser('', () => {}) + t.assert.fail() + } catch (err) { + t.assert.strictEqual(err.code, 'FST_ERR_CTP_EMPTY_TYPE') + t.assert.strictEqual(err.message, 'The content type cannot be an empty string') + } +}) + +test('the content type handler should be a function', t => { + t.plan(2) + const fastify = Fastify() + + try { + fastify.addContentTypeParser('aaa', null) + t.assert.fail() + } catch (err) { + t.assert.strictEqual(err.code, 'FST_ERR_CTP_INVALID_HANDLER') + t.assert.strictEqual(err.message, 'The content type handler should be a function') + } +}) + +test('catch all content type parser', async (t) => { + t.plan(6) + const fastify = Fastify() + + fastify.post('/', (req, reply) => { + reply.send(req.body) + }) + + fastify.addContentTypeParser('*', function (req, payload, done) { + let data = '' + payload.on('data', chunk => { data += chunk }) + payload.on('end', () => { + done(null, data) + }) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const result1 = await fetch(fastifyServer, { + method: 'POST', + body: 'hello', + headers: { + 'Content-Type': 'application/jsoff' + } + }) + + t.assert.ok(result1.ok) + t.assert.strictEqual(result1.status, 200) + t.assert.strictEqual(await result1.text(), 'hello') + + const result2 = await fetch(fastifyServer, { + method: 'POST', + body: 'hello', + headers: { + 'Content-Type': 'very-weird-content-type/foo' + } + }) + + t.assert.ok(result2.ok) + t.assert.strictEqual(result2.status, 200) + t.assert.strictEqual(await result2.text(), 'hello') +}) + +test('catch all content type parser should not interfere with other content type parsers', async (t) => { + t.plan(6) + const fastify = Fastify() + + fastify.post('/', (req, reply) => { + reply.send(req.body) + }) + + fastify.addContentTypeParser('*', function (req, payload, done) { + let data = '' + payload.on('data', chunk => { data += chunk }) + payload.on('end', () => { + done(null, data) + }) + }) + + fastify.addContentTypeParser('application/jsoff', function (req, payload, done) { + jsonParser(payload, function (err, body) { + done(err, body) + }) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const result1 = await fetch(fastifyServer, { + method: 'POST', + body: '{"hello":"world"}', + headers: { + 'Content-Type': 'application/jsoff' + } + }) + + t.assert.ok(result1.ok) + t.assert.strictEqual(result1.status, 200) + t.assert.deepStrictEqual(await result1.json(), { hello: 'world' }) + + const result2 = await fetch(fastifyServer, { + method: 'POST', + body: 'hello', + headers: { + 'Content-Type': 'very-weird-content-type/foo' + } + }) + + t.assert.ok(result2.ok) + t.assert.strictEqual(result2.status, 200) + t.assert.strictEqual(await result2.text(), 'hello') +}) + +// Issue 492 https://github.com/fastify/fastify/issues/492 +test('\'*\' catch undefined Content-Type requests', async (t) => { + t.plan(3) + + const fastify = Fastify() + + t.after(() => fastify.close()) + + fastify.addContentTypeParser('*', function (req, payload, done) { + let data = '' + payload.on('data', chunk => { data += chunk }) + payload.on('end', () => { + done(null, data) + }) + }) + + fastify.post('/', (req, res) => { + // Needed to avoid json stringify + res.type('text/plain').send(req.body) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + + const fileStream = fs.createReadStream(__filename) + + const result = await fetch(fastifyServer + '/', { + method: 'POST', + body: fileStream, + duplex: 'half' + }) + + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.strictEqual(await result.text(), fs.readFileSync(__filename).toString()) +}) + +test('cannot add custom parser after binding', (t, testDone) => { + t.plan(2) + + const fastify = Fastify() + + t.after(() => fastify.close()) + + fastify.post('/', (req, res) => { + res.type('text/plain').send(req.body) + }) + + fastify.listen({ port: 0 }, function (err) { + t.assert.ifError(err) + + try { + fastify.addContentTypeParser('*', () => {}) + t.assert.fail() + } catch (e) { + t.assert.ok(true) + testDone() + } + }) +}) + +test('Can override the default json parser', async (t) => { + t.plan(3) + const fastify = Fastify() + + fastify.post('/', (req, reply) => { + reply.send(req.body) + }) + + fastify.addContentTypeParser('application/json', function (req, payload, done) { + t.assert.ok('called') + jsonParser(payload, function (err, body) { + done(err, body) + }) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const result = await fetch(fastifyServer, { + method: 'POST', + body: '{"hello":"world"}', + headers: { + 'Content-Type': 'application/json' + } + }) + + t.assert.strictEqual(result.status, 200) + const body = await result.text() + t.assert.strictEqual(body, '{"hello":"world"}') +}) + +test('Can override the default plain text parser', async (t) => { + t.plan(3) + const fastify = Fastify() + + fastify.post('/', (req, reply) => { + reply.send(req.body) + }) + + fastify.addContentTypeParser('text/plain', function (req, payload, done) { + t.assert.ok('called') + plainTextParser(payload, function (err, body) { + done(err, body) + }) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const result = await fetch(fastifyServer, { + method: 'POST', + body: 'hello world', + headers: { + 'Content-Type': 'text/plain' + } + }) + + t.assert.strictEqual(result.status, 200) + const body = await result.text() + t.assert.strictEqual(body, 'hello world') +}) + +test('Can override the default json parser in a plugin', async (t) => { + t.plan(3) + const fastify = Fastify() + + fastify.register((instance, opts, done) => { + instance.addContentTypeParser('application/json', function (req, payload, done) { + t.assert.ok('called') + jsonParser(payload, function (err, body) { + done(err, body) + }) + }) + + instance.post('/', (req, reply) => { + reply.send(req.body) + }) + + done() + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const result = await fetch(fastifyServer, { + method: 'POST', + body: '{"hello":"world"}', + headers: { + 'Content-Type': 'application/json' + } + }) + + t.assert.strictEqual(result.status, 200) + const body = await result.text() + t.assert.strictEqual(body, '{"hello":"world"}') +}) + +test('Can\'t override the json parser multiple times', t => { + t.plan(2) + const fastify = Fastify() + + fastify.addContentTypeParser('application/json', function (req, payload, done) { + jsonParser(payload, function (err, body) { + done(err, body) + }) + }) + + try { + fastify.addContentTypeParser('application/json', function (req, payload, done) { + t.assert.ok('called') + jsonParser(payload, function (err, body) { + done(err, body) + }) + }) + } catch (err) { + t.assert.strictEqual(err.code, 'FST_ERR_CTP_ALREADY_PRESENT') + t.assert.strictEqual(err.message, 'Content type parser \'application/json\' already present.') + } +}) + +test('Can\'t override the plain text parser multiple times', t => { + t.plan(2) + const fastify = Fastify() + + fastify.addContentTypeParser('text/plain', function (req, payload, done) { + plainTextParser(payload, function (err, body) { + done(err, body) + }) + }) + + try { + fastify.addContentTypeParser('text/plain', function (req, payload, done) { + t.assert.ok('called') + plainTextParser(payload, function (err, body) { + done(err, body) + }) + }) + } catch (err) { + t.assert.strictEqual(err.code, 'FST_ERR_CTP_ALREADY_PRESENT') + t.assert.strictEqual(err.message, 'Content type parser \'text/plain\' already present.') + } +}) + +test('Should get the body as string', async (t) => { + t.plan(4) + const fastify = Fastify() + + fastify.post('/', (req, reply) => { + reply.send(req.body) + }) + + fastify.addContentTypeParser('application/json', { parseAs: 'string' }, function (req, body, done) { + t.assert.ok('called') + t.assert.ok(typeof body === 'string') + try { + const json = JSON.parse(body) + done(null, json) + } catch (err) { + err.statusCode = 400 + done(err, undefined) + } + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const result = await fetch(fastifyServer, { + method: 'POST', + body: '{"hello":"world"}', + headers: { + 'Content-Type': 'application/json' + } + }) + + t.assert.strictEqual(result.status, 200) + const body = await result.text() + t.assert.strictEqual(body, '{"hello":"world"}') +}) + +test('Should return defined body with no custom parser defined and content type = \'text/plain\'', async (t) => { + t.plan(2) + const fastify = Fastify() + + fastify.post('/', (req, reply) => { + reply.send(req.body) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const result = await fetch(fastifyServer, { + method: 'POST', + body: 'hello world', + headers: { + 'Content-Type': 'text/plain' + } + }) + + t.assert.strictEqual(result.status, 200) + const body = await result.text() + t.assert.strictEqual(body, 'hello world') +}) + +test('Should have typeof body object with no custom parser defined, no body defined and content type = \'text/plain\'', async (t) => { + t.plan(3) + const fastify = Fastify() + + fastify.post('/', (req, reply) => { + reply.send(req.body) + }) + + t.after(() => fastify.close()) + + const fastifyServer = await fastify.listen({ port: 0 }) + + const result = await fetch(fastifyServer, { + method: 'POST', + headers: { + 'Content-Type': 'text/plain' + } + }) + + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.strictEqual(await result.text(), '') +}) diff --git a/services/slides/node_modules/fastify/test/custom-parser.1.test.js b/services/slides/node_modules/fastify/test/custom-parser.1.test.js new file mode 100644 index 0000000000000000000000000000000000000000..ea0fe31ceb07d31ecc8c658e6423ebf42076e0de --- /dev/null +++ b/services/slides/node_modules/fastify/test/custom-parser.1.test.js @@ -0,0 +1,266 @@ +'use strict' + +const { test } = require('node:test') +const Fastify = require('../fastify') +const jsonParser = require('fast-json-body') + +process.removeAllListeners('warning') + +test('Should have typeof body object with no custom parser defined, null body and content type = \'text/plain\'', async (t) => { + t.plan(3) + const fastify = Fastify() + + fastify.post('/', (req, reply) => { + reply.send(req.body) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const result = await fetch(fastifyServer, { + method: 'POST', + body: null, + headers: { + 'Content-Type': 'text/plain' + } + }) + + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.strictEqual(await result.text(), '') +}) + +test('Should have typeof body object with no custom parser defined, undefined body and content type = \'text/plain\'', async (t) => { + t.plan(3) + const fastify = Fastify() + + fastify.post('/', (req, reply) => { + reply.send(req.body) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const result = await fetch(fastifyServer, { + method: 'POST', + body: undefined, + headers: { + 'Content-Type': 'text/plain' + } + }) + + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.strictEqual(await result.text(), '') +}) + +test('Should get the body as string /1', async (t) => { + t.plan(4) + const fastify = Fastify() + + fastify.post('/', (req, reply) => { + reply.send(req.body) + }) + + fastify.addContentTypeParser('text/plain', { parseAs: 'string' }, function (req, body, done) { + t.assert.ok('called') + t.assert.ok(typeof body === 'string') + try { + const plainText = body + done(null, plainText) + } catch (err) { + err.statusCode = 400 + done(err, undefined) + } + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const result = await fetch(fastifyServer, { + method: 'POST', + body: 'hello world', + headers: { + 'Content-Type': 'text/plain' + } + }) + + t.assert.strictEqual(result.status, 200) + t.assert.strictEqual(await result.text(), 'hello world') +}) + +test('Should get the body as buffer', async (t) => { + t.plan(4) + const fastify = Fastify() + + fastify.post('/', (req, reply) => { + reply.send(req.body) + }) + + fastify.addContentTypeParser('application/json', { parseAs: 'buffer' }, function (req, body, done) { + t.assert.ok('called') + t.assert.ok(body instanceof Buffer) + try { + const json = JSON.parse(body) + done(null, json) + } catch (err) { + err.statusCode = 400 + done(err, undefined) + } + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const result = await fetch(fastifyServer, { + method: 'POST', + body: '{"hello":"world"}', + headers: { + 'Content-Type': 'application/json' + } + }) + + t.assert.strictEqual(result.status, 200) + t.assert.strictEqual(await result.text(), '{"hello":"world"}') +}) + +test('Should get the body as buffer', async (t) => { + t.plan(4) + const fastify = Fastify() + + fastify.post('/', (req, reply) => { + reply.send(req.body) + }) + + fastify.addContentTypeParser('text/plain', { parseAs: 'buffer' }, function (req, body, done) { + t.assert.ok('called') + t.assert.ok(body instanceof Buffer) + try { + const plainText = body + done(null, plainText) + } catch (err) { + err.statusCode = 400 + done(err, undefined) + } + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const result = await fetch(fastifyServer, { + method: 'POST', + body: 'hello world', + headers: { + 'Content-Type': 'text/plain' + } + }) + + t.assert.strictEqual(result.status, 200) + t.assert.strictEqual(await result.text(), 'hello world') +}) + +test('Should parse empty bodies as a string', async (t) => { + t.plan(8) + const fastify = Fastify() + + fastify.addContentTypeParser('text/plain', { parseAs: 'string' }, (req, body, done) => { + t.assert.strictEqual(body, '') + done(null, body) + }) + + fastify.route({ + method: ['POST', 'DELETE'], + url: '/', + handler (request, reply) { + reply.send(request.body) + } + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const postResult = await fetch(fastifyServer, { + method: 'POST', + body: '', + headers: { + 'Content-Type': 'text/plain' + } + }) + + t.assert.ok(postResult.ok) + t.assert.strictEqual(postResult.status, 200) + t.assert.strictEqual(await postResult.text(), '') + + const deleteResult = await fetch(fastifyServer, { + method: 'DELETE', + body: '', + headers: { + 'Content-Type': 'text/plain', + 'Content-Length': '0' + } + }) + + t.assert.ok(deleteResult.ok) + t.assert.strictEqual(deleteResult.status, 200) + t.assert.strictEqual(await deleteResult.text(), '') +}) + +test('Should parse empty bodies as a buffer', async (t) => { + t.plan(4) + const fastify = Fastify() + + fastify.post('/', (req, reply) => { + reply.send(req.body) + }) + + fastify.addContentTypeParser('text/plain', { parseAs: 'buffer' }, function (req, body, done) { + t.assert.ok(body instanceof Buffer) + t.assert.strictEqual(body.length, 0) + done(null, body) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const result = await fetch(fastifyServer, { + method: 'POST', + body: '', + headers: { + 'Content-Type': 'text/plain' + } + }) + + t.assert.strictEqual(result.status, 200) + t.assert.strictEqual((await result.arrayBuffer()).byteLength, 0) +}) + +test('The charset should not interfere with the content type handling', async (t) => { + t.plan(4) + const fastify = Fastify() + + fastify.post('/', (req, reply) => { + reply.send(req.body) + }) + + fastify.addContentTypeParser('application/json', function (req, payload, done) { + t.assert.ok('called') + jsonParser(payload, function (err, body) { + done(err, body) + }) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const result = await fetch(fastifyServer, { + method: 'POST', + body: '{"hello":"world"}', + headers: { + 'Content-Type': 'application/json; charset=utf-8' + } + }) + + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.strictEqual(await result.text(), '{"hello":"world"}') +}) diff --git a/services/slides/node_modules/fastify/test/custom-parser.2.test.js b/services/slides/node_modules/fastify/test/custom-parser.2.test.js new file mode 100644 index 0000000000000000000000000000000000000000..88d596ca6c4fd234ac745827040b10c8671fbaf4 --- /dev/null +++ b/services/slides/node_modules/fastify/test/custom-parser.2.test.js @@ -0,0 +1,91 @@ +'use strict' + +const { test } = require('node:test') +const Fastify = require('..') + +process.removeAllListeners('warning') + +test('Wrong parseAs parameter', t => { + t.plan(2) + const fastify = Fastify() + + try { + fastify.addContentTypeParser('application/json', { parseAs: 'fireworks' }, () => {}) + t.assert.fail('should throw') + } catch (err) { + t.assert.strictEqual(err.code, 'FST_ERR_CTP_INVALID_PARSE_TYPE') + t.assert.strictEqual(err.message, "The body parser can only parse your data as 'string' or 'buffer', you asked 'fireworks' which is not supported.") + } +}) + +test('Should allow defining the bodyLimit per parser', async (t) => { + t.plan(2) + const fastify = Fastify() + t.after(() => fastify.close()) + + fastify.post('/', (req, reply) => { + reply.send(req.body) + }) + + fastify.addContentTypeParser( + 'x/foo', + { parseAs: 'string', bodyLimit: 5 }, + function (req, body, done) { + t.assert.fail('should not be invoked') + done() + } + ) + + const fastifyServer = await fastify.listen({ port: 0 }) + + const result = await fetch(fastifyServer, { + method: 'POST', + body: '1234567890', + headers: { + 'Content-Type': 'x/foo' + } + }) + + t.assert.ok(!result.ok) + t.assert.deepStrictEqual(await result.json(), { + statusCode: 413, + code: 'FST_ERR_CTP_BODY_TOO_LARGE', + error: 'Payload Too Large', + message: 'Request body is too large' + }) +}) + +test('route bodyLimit should take precedence over a custom parser bodyLimit', async (t) => { + t.plan(2) + const fastify = Fastify() + t.after(() => fastify.close()) + + fastify.post('/', { bodyLimit: 5 }, (request, reply) => { + reply.send(request.body) + }) + + fastify.addContentTypeParser( + 'x/foo', + { parseAs: 'string', bodyLimit: 100 }, + function (req, body, done) { + t.assert.fail('should not be invoked') + done() + } + ) + + const fastifyServer = await fastify.listen({ port: 0 }) + + const result = await fetch(fastifyServer, { + method: 'POST', + body: '1234567890', + headers: { 'Content-Type': 'x/foo' } + }) + + t.assert.ok(!result.ok) + t.assert.deepStrictEqual(await result.json(), { + statusCode: 413, + code: 'FST_ERR_CTP_BODY_TOO_LARGE', + error: 'Payload Too Large', + message: 'Request body is too large' + }) +}) diff --git a/services/slides/node_modules/fastify/test/custom-parser.3.test.js b/services/slides/node_modules/fastify/test/custom-parser.3.test.js new file mode 100644 index 0000000000000000000000000000000000000000..b8758fcbad6e7e2d57f05d5493905d1e4353deb0 --- /dev/null +++ b/services/slides/node_modules/fastify/test/custom-parser.3.test.js @@ -0,0 +1,208 @@ +'use strict' + +const { test } = require('node:test') +const Fastify = require('..') +const jsonParser = require('fast-json-body') + +process.removeAllListeners('warning') + +test('should be able to use default parser for extra content type', async t => { + t.plan(3) + const fastify = Fastify() + t.after(() => fastify.close()) + + fastify.post('/', (request, reply) => { + reply.send(request.body) + }) + + fastify.addContentTypeParser('text/json', { parseAs: 'string' }, fastify.getDefaultJsonParser('ignore', 'ignore')) + + const fastifyServer = await fastify.listen({ port: 0 }) + + const response = await fetch(fastifyServer, { + method: 'POST', + body: '{"hello":"world"}', + headers: { + 'Content-Type': 'text/json' + } + }) + t.assert.ok(response.ok) + t.assert.strictEqual(response.status, 200) + t.assert.deepStrictEqual(await response.json(), { hello: 'world' }) +}) + +test('contentTypeParser should add a custom parser with RegExp value', async (t) => { + const fastify = Fastify() + t.after(() => fastify.close()) + + fastify.post('/', (req, reply) => { + reply.send(req.body) + }) + + fastify.options('/', (req, reply) => { + reply.send(req.body) + }) + + fastify.addContentTypeParser(/.*\+json$/, function (req, payload, done) { + jsonParser(payload, function (err, body) { + done(err, body) + }) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + + await t.test('in POST', async t => { + t.plan(3) + + const response = await fetch(fastifyServer, { + method: 'POST', + body: '{"hello":"world"}', + headers: { + 'Content-Type': 'application/vnd.test+json' + } + }) + t.assert.ok(response.ok) + t.assert.strictEqual(response.status, 200) + const body = await response.text() + t.assert.deepStrictEqual(body.toString(), JSON.stringify({ hello: 'world' })) + }) + + await t.test('in OPTIONS', async t => { + t.plan(3) + + const response = await fetch(fastifyServer, { + method: 'OPTIONS', + body: '{"hello":"world"}', + headers: { + 'Content-Type': 'weird/content-type+json' + } + }) + t.assert.ok(response.ok) + t.assert.strictEqual(response.status, 200) + const body = await response.text() + t.assert.deepStrictEqual(body.toString(), JSON.stringify({ hello: 'world' })) + }) +}) + +test('contentTypeParser should add multiple custom parsers with RegExp values', async t => { + t.plan(6) + const fastify = Fastify() + t.after(() => fastify.close()) + + fastify.post('/', (req, reply) => { + reply.send(req.body) + }) + + fastify.addContentTypeParser(/.*\+json$/, function (req, payload, done) { + jsonParser(payload, function (err, body) { + done(err, body) + }) + }) + + fastify.addContentTypeParser(/.*\+xml$/, function (req, payload, done) { + done(null, 'xml') + }) + + fastify.addContentTypeParser(/.*\+myExtension$/i, function (req, payload, done) { + let data = '' + payload.on('data', chunk => { data += chunk }) + payload.on('end', () => { + done(null, data + 'myExtension') + }) + }) + + await fastify.ready() + + { + const response = await fastify.inject({ + method: 'POST', + url: '/', + body: '{"hello":"world"}', + headers: { + 'Content-Type': 'application/vnd.hello+json' + } + }) + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(response.payload.toString(), '{"hello":"world"}') + } + + { + const response = await fastify.inject({ + method: 'POST', + url: '/', + body: '{"hello":"world"}', + headers: { + 'Content-Type': 'application/test+xml' + } + }) + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(response.payload.toString(), 'xml') + } + + await fastify.inject({ + method: 'POST', + path: '/', + payload: 'abcdefg', + headers: { + 'Content-Type': 'application/+myExtension' + } + }).then((response) => { + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(response.payload.toString(), 'abcdefgmyExtension') + }).catch((err) => { + t.assert.ifError(err) + }) +}) + +test('catch all content type parser should not interfere with content type parser', async t => { + t.plan(9) + const fastify = Fastify() + t.after(() => fastify.close()) + + fastify.post('/', (req, reply) => { + reply.send(req.body) + }) + + fastify.addContentTypeParser('*', function (req, payload, done) { + let data = '' + payload.on('data', chunk => { data += chunk }) + payload.on('end', () => { + done(null, data) + }) + }) + + fastify.addContentTypeParser(/^application\/.*/, function (req, payload, done) { + jsonParser(payload, function (err, body) { + done(err, body) + }) + }) + + fastify.addContentTypeParser('text/html', function (req, payload, done) { + let data = '' + payload.on('data', chunk => { data += chunk }) + payload.on('end', () => { + done(null, data + 'html') + }) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + + const assertions = [ + { body: '{"myKey":"myValue"}', contentType: 'application/json', expected: JSON.stringify({ myKey: 'myValue' }) }, + { body: 'body', contentType: 'very-weird-content-type/foo', expected: 'body' }, + { body: 'my text', contentType: 'text/html', expected: 'my texthtml' } + ] + + for (const { body, contentType, expected } of assertions) { + const response = await fetch(fastifyServer, { + method: 'POST', + body, + headers: { + 'Content-Type': contentType + } + }) + t.assert.ok(response.ok) + t.assert.strictEqual(response.status, 200) + t.assert.deepStrictEqual(await response.text(), expected) + } +}) diff --git a/services/slides/node_modules/fastify/test/custom-parser.4.test.js b/services/slides/node_modules/fastify/test/custom-parser.4.test.js new file mode 100644 index 0000000000000000000000000000000000000000..6481a48f8bf1c573cc83cdf839ccf736876b4b83 --- /dev/null +++ b/services/slides/node_modules/fastify/test/custom-parser.4.test.js @@ -0,0 +1,218 @@ +'use strict' + +const { test } = require('node:test') +const Fastify = require('../fastify') +const jsonParser = require('fast-json-body') + +process.removeAllListeners('warning') + +test('should prefer string content types over RegExp ones', async (t) => { + t.plan(6) + const fastify = Fastify() + t.after(() => { fastify.close() }) + fastify.post('/', (req, reply) => { + reply.send(req.body) + }) + + fastify.addContentTypeParser(/^application\/.*/, function (req, payload, done) { + let data = '' + payload.on('data', chunk => { data += chunk }) + payload.on('end', () => { + done(null, data) + }) + }) + + fastify.addContentTypeParser('application/json', function (req, payload, done) { + jsonParser(payload, function (err, body) { + done(err, body) + }) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + + const result1 = await fetch(fastifyServer, { + method: 'POST', + body: '{"k1":"myValue", "k2": "myValue"}', + headers: { + 'Content-Type': 'application/json' + } + }) + + t.assert.ok(result1.ok) + t.assert.strictEqual(result1.status, 200) + t.assert.equal(await result1.text(), JSON.stringify({ k1: 'myValue', k2: 'myValue' })) + + const result2 = await fetch(fastifyServer, { + method: 'POST', + body: 'javascript', + headers: { + 'Content-Type': 'application/javascript' + } + }) + + t.assert.ok(result2.ok) + t.assert.strictEqual(result2.status, 200) + t.assert.equal(await result2.text(), 'javascript') +}) + +test('removeContentTypeParser should support arrays of content types to remove', async (t) => { + t.plan(7) + + const fastify = Fastify() + t.after(() => fastify.close()) + + fastify.addContentTypeParser('application/xml', function (req, payload, done) { + payload.on('data', () => {}) + payload.on('end', () => { + done(null, 'xml') + }) + }) + + fastify.addContentTypeParser(/^image\/.*/, function (req, payload, done) { + payload.on('data', () => {}) + payload.on('end', () => { + done(null, 'image') + }) + }) + + fastify.removeContentTypeParser([/^image\/.*/, 'application/json']) + + fastify.post('/', (req, reply) => { + reply.send(req.body) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + + const result1 = await fetch(fastifyServer, { + method: 'POST', + body: '', + headers: { + 'Content-Type': 'application/xml' + } + }) + + t.assert.ok(result1.ok) + t.assert.strictEqual(result1.status, 200) + t.assert.equal(await result1.text(), 'xml') + + const result2 = await fetch(fastifyServer, { + method: 'POST', + body: '', + headers: { + 'Content-Type': 'image/png' + } + }) + + t.assert.ok(!result2.ok) + t.assert.strictEqual(result2.status, 415) + + const result3 = await fetch(fastifyServer, { + method: 'POST', + body: '{test: "test"}', + headers: { + 'Content-Type': 'application/json' + } + }) + + t.assert.ok(!result3.ok) + t.assert.strictEqual(result3.status, 415) +}) + +test('removeContentTypeParser should support encapsulation', async (t) => { + t.plan(5) + + const fastify = Fastify() + t.after(() => fastify.close()) + + fastify.addContentTypeParser('application/xml', function (req, payload, done) { + payload.on('data', () => {}) + payload.on('end', () => { + done(null, 'xml') + }) + }) + + fastify.post('/', (req, reply) => { + reply.send(req.body) + }) + + fastify.register(function (instance, options, done) { + instance.removeContentTypeParser('application/xml') + + instance.post('/encapsulated', (req, reply) => { + reply.send(req.body) + }) + + done() + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + + const result1 = await fetch(fastifyServer + '/encapsulated', { + method: 'POST', + body: '', + headers: { + 'Content-Type': 'application/xml' + } + }) + + t.assert.ok(!result1.ok) + t.assert.strictEqual(result1.status, 415) + + const result2 = await fetch(fastifyServer, { + method: 'POST', + body: '', + headers: { + 'Content-Type': 'application/xml' + } + }) + + t.assert.ok(result2.ok) + t.assert.strictEqual(result2.status, 200) + t.assert.equal(await result2.text(), 'xml') +}) + +test('removeAllContentTypeParsers should support encapsulation', async (t) => { + t.plan(5) + + const fastify = Fastify() + t.after(() => fastify.close()) + + fastify.post('/', (req, reply) => { + reply.send(req.body) + }) + + fastify.register(function (instance, options, done) { + instance.removeAllContentTypeParsers() + + instance.post('/encapsulated', (req, reply) => { + reply.send(req.body) + }) + + done() + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + + const result1 = await fetch(fastifyServer + '/encapsulated', { + method: 'POST', + body: '{}', + headers: { + 'Content-Type': 'application/json' + } + }) + + t.assert.ok(!result1.ok) + t.assert.strictEqual(result1.status, 415) + + const result2 = await fetch(fastifyServer, { + method: 'POST', + body: '{"test":1}', + headers: { + 'Content-Type': 'application/json' + } + }) + + t.assert.ok(result2.ok) + t.assert.strictEqual(result2.status, 200) + t.assert.equal(JSON.parse(await result2.text()).test, 1) +}) diff --git a/services/slides/node_modules/fastify/test/custom-parser.5.test.js b/services/slides/node_modules/fastify/test/custom-parser.5.test.js new file mode 100644 index 0000000000000000000000000000000000000000..f630e851e43cdfb6aee8b9c667e27740df75b3c7 --- /dev/null +++ b/services/slides/node_modules/fastify/test/custom-parser.5.test.js @@ -0,0 +1,130 @@ +'use strict' + +const { test } = require('node:test') +const Fastify = require('../fastify') +const jsonParser = require('fast-json-body') +const { plainTextParser } = require('./helper') + +process.removeAllListeners('warning') + +test('cannot remove all content type parsers after binding', async (t) => { + t.plan(1) + + const fastify = Fastify() + + t.after(() => fastify.close()) + + await fastify.listen({ port: 0 }) + t.assert.throws(() => fastify.removeAllContentTypeParsers()) +}) + +test('cannot remove content type parsers after binding', async (t) => { + t.plan(1) + + const fastify = Fastify() + t.after(() => fastify.close()) + + await fastify.listen({ port: 0 }) + t.assert.throws(() => fastify.removeContentTypeParser('application/json')) +}) + +test('should be able to override the default json parser after removeAllContentTypeParsers', async (t) => { + t.plan(4) + + const fastify = Fastify() + + fastify.post('/', (req, reply) => { + reply.send(req.body) + }) + + fastify.removeAllContentTypeParsers() + + fastify.addContentTypeParser('application/json', function (req, payload, done) { + t.assert.ok('called') + jsonParser(payload, function (err, body) { + done(err, body) + }) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + + const result = await fetch(fastifyServer, { + method: 'POST', + body: JSON.stringify({ hello: 'world' }), + headers: { + 'Content-Type': 'application/json' + } + }) + + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.deepStrictEqual(await result.text(), JSON.stringify({ hello: 'world' })) + await fastify.close() +}) + +test('should be able to override the default plain text parser after removeAllContentTypeParsers', async (t) => { + t.plan(4) + + const fastify = Fastify() + + fastify.post('/', (req, reply) => { + reply.send(req.body) + }) + + fastify.removeAllContentTypeParsers() + + fastify.addContentTypeParser('text/plain', function (req, payload, done) { + t.assert.ok('called') + plainTextParser(payload, function (err, body) { + done(err, body) + }) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + + const result = await fetch(fastifyServer, { + method: 'POST', + body: 'hello world', + headers: { + 'Content-Type': 'text/plain' + } + }) + + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.strictEqual(await result.text(), 'hello world') + await fastify.close() +}) + +test('should be able to add a custom content type parser after removeAllContentTypeParsers', async (t) => { + t.plan(4) + + const fastify = Fastify() + + fastify.post('/', (req, reply) => { + reply.send(req.body) + }) + + fastify.removeAllContentTypeParsers() + fastify.addContentTypeParser('application/jsoff', function (req, payload, done) { + t.assert.ok('called') + jsonParser(payload, function (err, body) { + done(err, body) + }) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + + const result = await fetch(fastifyServer, { + method: 'POST', + body: JSON.stringify({ hello: 'world' }), + headers: { + 'Content-Type': 'application/jsoff' + } + }) + + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.deepStrictEqual(await result.text(), JSON.stringify({ hello: 'world' })) + await fastify.close() +}) diff --git a/services/slides/node_modules/fastify/test/custom-querystring-parser.test.js b/services/slides/node_modules/fastify/test/custom-querystring-parser.test.js new file mode 100644 index 0000000000000000000000000000000000000000..a6f8a24d8d16a2d8cf550767eadef3850c3b78a6 --- /dev/null +++ b/services/slides/node_modules/fastify/test/custom-querystring-parser.test.js @@ -0,0 +1,129 @@ +'use strict' + +const { test } = require('node:test') +const querystring = require('node:querystring') +const Fastify = require('..') + +test('Custom querystring parser', async t => { + t.plan(7) + + const fastify = Fastify({ + querystringParser: function (str) { + t.assert.strictEqual(str, 'foo=bar&baz=faz') + return querystring.parse(str) + } + }) + + fastify.get('/', (req, reply) => { + t.assert.deepEqual(req.query, { + foo: 'bar', + baz: 'faz' + }) + reply.send({ hello: 'world' }) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const result = await fetch(`${fastifyServer}?foo=bar&baz=faz`) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + + const injectResponse = await fastify.inject({ + method: 'GET', + url: `${fastifyServer}?foo=bar&baz=faz` + }) + t.assert.strictEqual(injectResponse.statusCode, 200) +}) + +test('Custom querystring parser should be called also if there is nothing to parse', async t => { + t.plan(7) + + const fastify = Fastify({ + querystringParser: function (str) { + t.assert.strictEqual(str, '') + return querystring.parse(str) + } + }) + + fastify.get('/', (req, reply) => { + t.assert.deepEqual(req.query, {}) + reply.send({ hello: 'world' }) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const result = await fetch(fastifyServer) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + + const injectResponse = await fastify.inject({ + method: 'GET', + url: fastifyServer + }) + t.assert.strictEqual(injectResponse.statusCode, 200) +}) + +test('Querystring without value', async t => { + t.plan(7) + + const fastify = Fastify({ + querystringParser: function (str) { + t.assert.strictEqual(str, 'foo') + return querystring.parse(str) + } + }) + + fastify.get('/', (req, reply) => { + t.assert.deepEqual(req.query, { foo: '' }) + reply.send({ hello: 'world' }) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const result = await fetch(`${fastifyServer}?foo`) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + + const injectResponse = await fastify.inject({ + method: 'GET', + url: `${fastifyServer}?foo` + }) + t.assert.strictEqual(injectResponse.statusCode, 200) +}) + +test('Custom querystring parser should be a function', t => { + t.plan(1) + + try { + Fastify({ + querystringParser: 10 + }) + t.assert.fail('Should throw') + } catch (err) { + t.assert.strictEqual( + err.message, + "querystringParser option should be a function, instead got 'number'" + ) + } +}) + +test('Custom querystring parser should be a function', t => { + t.plan(1) + + try { + Fastify({ + routerOptions: { + querystringParser: 10 + } + }) + t.fail('Should throw') + } catch (err) { + t.assert.equal( + err.message, + "querystringParser option should be a function, instead got 'number'" + ) + } +}) diff --git a/services/slides/node_modules/fastify/test/decorator.test.js b/services/slides/node_modules/fastify/test/decorator.test.js new file mode 100644 index 0000000000000000000000000000000000000000..972658f9c5817ac517cd149a445cb28d7b12c405 --- /dev/null +++ b/services/slides/node_modules/fastify/test/decorator.test.js @@ -0,0 +1,1330 @@ +'use strict' + +const { test, describe } = require('node:test') +const Fastify = require('..') +const fp = require('fastify-plugin') +const symbols = require('../lib/symbols.js') + +test('server methods should exist', t => { + t.plan(2) + const fastify = Fastify() + t.assert.ok(fastify.decorate) + t.assert.ok(fastify.hasDecorator) +}) + +test('should check if the given decoration already exist when null', (t, done) => { + t.plan(1) + const fastify = Fastify() + fastify.decorate('null', null) + fastify.ready(() => { + t.assert.ok(fastify.hasDecorator('null')) + done() + }) +}) + +test('server methods should be encapsulated via .register', (t, done) => { + t.plan(2) + const fastify = Fastify() + + fastify.register((instance, opts, done) => { + instance.decorate('test', () => {}) + t.assert.ok(instance.test) + done() + }) + + fastify.ready(() => { + t.assert.strictEqual(fastify.test, undefined) + done() + }) +}) + +test('hasServerMethod should check if the given method already exist', (t, done) => { + t.plan(2) + const fastify = Fastify() + + fastify.register((instance, opts, done) => { + instance.decorate('test', () => {}) + t.assert.ok(instance.hasDecorator('test')) + done() + }) + + fastify.ready(() => { + t.assert.strictEqual(fastify.hasDecorator('test'), false) + done() + }) +}) + +test('decorate should throw if a declared dependency is not present', (t, done) => { + t.plan(3) + const fastify = Fastify() + + fastify.register((instance, opts, done) => { + try { + instance.decorate('test', () => {}, ['dependency']) + t.assert.fail() + } catch (e) { + t.assert.strictEqual(e.code, 'FST_ERR_DEC_MISSING_DEPENDENCY') + t.assert.strictEqual(e.message, 'The decorator is missing dependency \'dependency\'.') + } + done() + }) + + fastify.ready(() => { + t.assert.ok('ready') + done() + }) +}) + +test('decorate should throw if declared dependency is not array', (t, done) => { + t.plan(3) + const fastify = Fastify() + + fastify.register((instance, opts, done) => { + try { + instance.decorate('test', () => {}, {}) + t.assert.fail() + } catch (e) { + t.assert.strictEqual(e.code, 'FST_ERR_DEC_DEPENDENCY_INVALID_TYPE') + t.assert.strictEqual(e.message, 'The dependencies of decorator \'test\' must be of type Array.') + } + done() + }) + + fastify.ready(() => { + t.assert.ok('ready') + done() + }) +}) + +// issue #777 +test('should pass error for missing request decorator', (t, done) => { + t.plan(2) + const fastify = Fastify() + + const plugin = fp(function (instance, opts, done) { + done() + }, { + decorators: { + request: ['foo'] + } + }) + fastify + .register(plugin) + .ready((err) => { + t.assert.ok(err instanceof Error) + t.assert.ok(err.message.includes("'foo'")) + done() + }) +}) + +const runTests = async (t, fastifyServer) => { + const endpoints = [ + { path: '/yes', expectedBody: { hello: 'world' } }, + { path: '/no', expectedBody: { hello: 'world' } } + ] + + for (const { path, expectedBody } of endpoints) { + const result = await fetch(`${fastifyServer}${path}`) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), expectedBody) + } +} + +test('decorateReply inside register', async (t) => { + t.plan(10) + const fastify = Fastify() + + fastify.register((instance, opts, done) => { + instance.decorateReply('test', 'test') + + instance.get('/yes', (req, reply) => { + t.assert.ok(reply.test, 'test exists') + reply.send({ hello: 'world' }) + }) + + done() + }) + + fastify.get('/no', (req, reply) => { + t.assert.ok(!reply.test) + reply.send({ hello: 'world' }) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + await runTests(t, fastifyServer) +}) + +test('decorateReply as plugin (inside .after)', async t => { + t.plan(10) + const fastify = Fastify() + + fastify.register((instance, opts, done) => { + instance.register(fp((i, o, n) => { + instance.decorateReply('test', 'test') + n() + })).after(() => { + instance.get('/yes', (req, reply) => { + t.assert.ok(reply.test) + reply.send({ hello: 'world' }) + }) + }) + done() + }) + + fastify.get('/no', (req, reply) => { + t.assert.ok(!reply.test) + reply.send({ hello: 'world' }) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + await runTests(t, fastifyServer) +}) + +test('decorateReply as plugin (outside .after)', async t => { + t.plan(10) + const fastify = Fastify() + + fastify.register((instance, opts, done) => { + instance.register(fp((i, o, n) => { + instance.decorateReply('test', 'test') + n() + })) + + instance.get('/yes', (req, reply) => { + t.assert.ok(reply.test) + reply.send({ hello: 'world' }) + }) + done() + }) + + fastify.get('/no', (req, reply) => { + t.assert.ok(!reply.test) + reply.send({ hello: 'world' }) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + await runTests(t, fastifyServer) +}) + +test('decorateRequest inside register', async t => { + t.plan(10) + const fastify = Fastify() + + fastify.register((instance, opts, done) => { + instance.decorateRequest('test', 'test') + + instance.get('/yes', (req, reply) => { + t.assert.ok(req.test, 'test exists') + reply.send({ hello: 'world' }) + }) + + done() + }) + + fastify.get('/no', (req, reply) => { + t.assert.ok(!req.test) + reply.send({ hello: 'world' }) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + await runTests(t, fastifyServer) +}) + +test('decorateRequest as plugin (inside .after)', async t => { + t.plan(10) + const fastify = Fastify() + + fastify.register((instance, opts, done) => { + instance.register(fp((i, o, n) => { + instance.decorateRequest('test', 'test') + n() + })).after(() => { + instance.get('/yes', (req, reply) => { + t.assert.ok(req.test) + reply.send({ hello: 'world' }) + }) + }) + done() + }) + + fastify.get('/no', (req, reply) => { + t.assert.ok(!req.test) + reply.send({ hello: 'world' }) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + await runTests(t, fastifyServer) +}) + +test('decorateRequest as plugin (outside .after)', async (t) => { + t.plan(10) + const fastify = Fastify() + + fastify.register((instance, opts, done) => { + instance.register(fp((i, o, n) => { + instance.decorateRequest('test', 'test') + n() + })) + + instance.get('/yes', (req, reply) => { + t.assert.ok(req.test) + reply.send({ hello: 'world' }) + }) + done() + }) + + fastify.get('/no', (req, reply) => { + t.assert.ok(!req.test) + reply.send({ hello: 'world' }) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + await runTests(t, fastifyServer) +}) + +test('decorators should be instance separated', (t, done) => { + t.plan(1) + + const fastify1 = Fastify() + const fastify2 = Fastify() + + fastify1.decorate('test', 'foo') + fastify2.decorate('test', 'foo') + + fastify1.decorateRequest('test', 'foo') + fastify2.decorateRequest('test', 'foo') + + fastify1.decorateReply('test', 'foo') + fastify2.decorateReply('test', 'foo') + + t.assert.ok('Done') + done() +}) + +describe('hasRequestDecorator', () => { + const requestDecoratorName = 'my-decorator-name' + + test('is a function', async t => { + const fastify = Fastify() + t.assert.ok(fastify.hasRequestDecorator) + }) + + test('should check if the given request decoration already exist', async t => { + const fastify = Fastify() + + t.assert.ok(!fastify.hasRequestDecorator(requestDecoratorName)) + fastify.decorateRequest(requestDecoratorName, 42) + t.assert.ok(fastify.hasRequestDecorator(requestDecoratorName)) + }) + + test('should check if the given request decoration already exist when null', async t => { + const fastify = Fastify() + + t.assert.ok(!fastify.hasRequestDecorator(requestDecoratorName)) + fastify.decorateRequest(requestDecoratorName, null) + t.assert.ok(fastify.hasRequestDecorator(requestDecoratorName)) + }) + + test('should be plugin encapsulable', async t => { + const fastify = Fastify() + + t.assert.ok(!fastify.hasRequestDecorator(requestDecoratorName)) + + await fastify.register(async function (fastify2, opts) { + fastify2.decorateRequest(requestDecoratorName, 42) + t.assert.ok(fastify2.hasRequestDecorator(requestDecoratorName)) + }) + + t.assert.ok(!fastify.hasRequestDecorator(requestDecoratorName)) + + await fastify.ready() + t.assert.ok(!fastify.hasRequestDecorator(requestDecoratorName)) + }) + + test('should be inherited', async t => { + const fastify = Fastify() + + fastify.decorateRequest(requestDecoratorName, 42) + + await fastify.register(async function (fastify2, opts) { + t.assert.ok(fastify2.hasRequestDecorator(requestDecoratorName)) + }) + + await fastify.ready() + t.assert.ok(fastify.hasRequestDecorator(requestDecoratorName)) + }) +}) + +describe('hasReplyDecorator', () => { + const replyDecoratorName = 'my-decorator-name' + + test('is a function', async t => { + const fastify = Fastify() + t.assert.ok(fastify.hasReplyDecorator) + }) + + test('should check if the given reply decoration already exist', async t => { + const fastify = Fastify() + + t.assert.ok(!fastify.hasReplyDecorator(replyDecoratorName)) + fastify.decorateReply(replyDecoratorName, 42) + t.assert.ok(fastify.hasReplyDecorator(replyDecoratorName)) + }) + + test('should check if the given reply decoration already exist when null', async t => { + const fastify = Fastify() + + t.assert.ok(!fastify.hasReplyDecorator(replyDecoratorName)) + fastify.decorateReply(replyDecoratorName, null) + t.assert.ok(fastify.hasReplyDecorator(replyDecoratorName)) + }) + + test('should be plugin encapsulable', async t => { + const fastify = Fastify() + + t.assert.ok(!fastify.hasReplyDecorator(replyDecoratorName)) + + await fastify.register(async function (fastify2, opts) { + fastify2.decorateReply(replyDecoratorName, 42) + t.assert.ok(fastify2.hasReplyDecorator(replyDecoratorName)) + }) + + t.assert.ok(!fastify.hasReplyDecorator(replyDecoratorName)) + + await fastify.ready() + t.assert.ok(!fastify.hasReplyDecorator(replyDecoratorName)) + }) + + test('should be inherited', async t => { + const fastify = Fastify() + + fastify.decorateReply(replyDecoratorName, 42) + + await fastify.register(async function (fastify2, opts) { + t.assert.ok(fastify2.hasReplyDecorator(replyDecoratorName)) + }) + + await fastify.ready() + t.assert.ok(fastify.hasReplyDecorator(replyDecoratorName)) + }) +}) + +test('should register properties via getter/setter objects', (t, done) => { + t.plan(3) + const fastify = Fastify() + + fastify.register((instance, opts, done) => { + instance.decorate('test', { + getter () { + return 'a getter' + } + }) + t.assert.ok(instance.test) + t.assert.ok(instance.test, 'a getter') + done() + }) + + fastify.ready(() => { + t.assert.ok(!fastify.test) + done() + }) +}) + +test('decorateRequest should work with getter/setter', (t, done) => { + t.plan(5) + const fastify = Fastify() + + fastify.register((instance, opts, done) => { + instance.decorateRequest('test', { + getter () { + return 'a getter' + } + }) + + instance.get('/req-decorated-get-set', (req, res) => { + res.send({ test: req.test }) + }) + + done() + }) + + fastify.get('/not-decorated', (req, res) => { + t.assert.ok(!req.test) + res.send() + }) + + let pending = 2 + + function completed () { + if (--pending === 0) { + done() + } + } + + fastify.ready(() => { + fastify.inject({ url: '/req-decorated-get-set' }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { test: 'a getter' }) + completed() + }) + + fastify.inject({ url: '/not-decorated' }, (err, res) => { + t.assert.ifError(err) + t.assert.ok('ok', 'not decorated') + completed() + }) + }) +}) + +test('decorateReply should work with getter/setter', (t, done) => { + t.plan(5) + const fastify = Fastify() + + fastify.register((instance, opts, done) => { + instance.decorateReply('test', { + getter () { + return 'a getter' + } + }) + + instance.get('/res-decorated-get-set', (req, res) => { + res.send({ test: res.test }) + }) + + done() + }) + + fastify.get('/not-decorated', (req, res) => { + t.assert.ok(!res.test) + res.send() + }) + + let pending = 2 + + function completed () { + if (--pending === 0) { + done() + } + } + fastify.ready(() => { + fastify.inject({ url: '/res-decorated-get-set' }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { test: 'a getter' }) + completed() + }) + + fastify.inject({ url: '/not-decorated' }, (err, res) => { + t.assert.ifError(err) + t.assert.ok('ok') + completed() + }) + }) +}) + +test('should register empty values', (t, done) => { + t.plan(2) + const fastify = Fastify() + + fastify.register((instance, opts, done) => { + instance.decorate('test', null) + t.assert.ok(Object.hasOwn(instance, 'test')) + done() + }) + + fastify.ready(() => { + t.assert.ok(!fastify.test) + done() + }) +}) + +test('nested plugins can override things', (t, done) => { + t.plan(6) + const fastify = Fastify() + + const rootFunc = () => {} + fastify.decorate('test', rootFunc) + fastify.decorateRequest('test', rootFunc) + fastify.decorateReply('test', rootFunc) + + fastify.register((instance, opts, done) => { + const func = () => {} + instance.decorate('test', func) + instance.decorateRequest('test', func) + instance.decorateReply('test', func) + + t.assert.strictEqual(instance.test, func) + t.assert.strictEqual(instance[symbols.kRequest].prototype.test, func) + t.assert.strictEqual(instance[symbols.kReply].prototype.test, func) + done() + }) + + fastify.ready(() => { + t.assert.strictEqual(fastify.test, rootFunc) + t.assert.strictEqual(fastify[symbols.kRequest].prototype.test, rootFunc) + t.assert.strictEqual(fastify[symbols.kReply].prototype.test, rootFunc) + done() + }) +}) + +test('a decorator should addSchema to all the encapsulated tree', (t, done) => { + t.plan(1) + const fastify = Fastify() + + const decorator = function (instance, opts, done) { + instance.decorate('decoratorAddSchema', function (whereAddTheSchema) { + instance.addSchema({ + $id: 'schema', + type: 'string' + }) + }) + done() + } + + fastify.register(fp(decorator)) + + fastify.register(function (instance, opts, done) { + instance.register((subInstance, opts, done) => { + subInstance.decoratorAddSchema() + done() + }) + done() + }) + + fastify.ready(() => { + t.assert.ifError() + done() + }) +}) + +test('after can access to a decorated instance and previous plugin decoration', (t, done) => { + t.plan(11) + const TEST_VALUE = {} + const OTHER_TEST_VALUE = {} + const NEW_TEST_VALUE = {} + + const fastify = Fastify() + + fastify.register(fp(function (instance, options, done) { + instance.decorate('test', TEST_VALUE) + + done() + })).after(function (err, instance, done) { + t.assert.ifError(err) + t.assert.strictEqual(instance.test, TEST_VALUE) + + instance.decorate('test2', OTHER_TEST_VALUE) + done() + }) + + fastify.register(fp(function (instance, options, done) { + t.assert.strictEqual(instance.test, TEST_VALUE) + t.assert.strictEqual(instance.test2, OTHER_TEST_VALUE) + + instance.decorate('test3', NEW_TEST_VALUE) + + done() + })).after(function (err, instance, done) { + t.assert.ifError(err) + t.assert.strictEqual(instance.test, TEST_VALUE) + t.assert.strictEqual(instance.test2, OTHER_TEST_VALUE) + t.assert.strictEqual(instance.test3, NEW_TEST_VALUE) + + done() + }) + + fastify.get('/', function (req, res) { + t.assert.strictEqual(this.test, TEST_VALUE) + t.assert.strictEqual(this.test2, OTHER_TEST_VALUE) + res.send({}) + }) + + fastify.inject('/') + .then(response => { + t.assert.strictEqual(response.statusCode, 200) + done() + }) +}) + +test('decorate* should throw if called after ready', async t => { + t.plan(6) + const fastify = Fastify() + + fastify.get('/', (request, reply) => { + reply.send({ + hello: 'world' + }) + }) + + await fastify.listen({ port: 0 }) + try { + fastify.decorate('test', true) + t.assert.fail('should not decorate') + } catch (err) { + t.assert.strictEqual(err.code, 'FST_ERR_DEC_AFTER_START') + t.assert.strictEqual(err.message, "The decorator 'test' has been added after start!") + } + try { + fastify.decorateRequest('test', true) + t.assert.fail('should not decorate') + } catch (e) { + t.assert.strictEqual(e.code, 'FST_ERR_DEC_AFTER_START') + t.assert.strictEqual(e.message, "The decorator 'test' has been added after start!") + } + try { + fastify.decorateReply('test', true) + t.assert.fail('should not decorate') + } catch (e) { + t.assert.strictEqual(e.code, 'FST_ERR_DEC_AFTER_START') + t.assert.strictEqual(e.message, "The decorator 'test' has been added after start!") + } + await fastify.close() +}) + +test('decorate* should emit error if an array is passed', t => { + t.plan(2) + + const fastify = Fastify() + try { + fastify.decorateRequest('test_array', []) + t.assert.fail('should not decorate') + } catch (err) { + t.assert.strictEqual(err.code, 'FST_ERR_DEC_REFERENCE_TYPE') + t.assert.strictEqual(err.message, "The decorator 'test_array' of type 'object' is a reference type. Use the { getter, setter } interface instead.") + } +}) + +test('server.decorate should not emit error if reference type is passed', async t => { + t.plan(1) + + const fastify = Fastify() + fastify.decorate('test_array', []) + fastify.decorate('test_object', {}) + await fastify.ready() + t.assert.ok('Done') +}) + +test('decorate* should emit warning if object type is passed', t => { + t.plan(2) + + const fastify = Fastify() + try { + fastify.decorateRequest('test_object', { foo: 'bar' }) + t.assert.fail('should not decorate') + } catch (err) { + t.assert.strictEqual(err.code, 'FST_ERR_DEC_REFERENCE_TYPE') + t.assert.strictEqual(err.message, "The decorator 'test_object' of type 'object' is a reference type. Use the { getter, setter } interface instead.") + } +}) + +test('decorate* should not emit warning if object with getter/setter is passed', t => { + const fastify = Fastify() + + fastify.decorateRequest('test_getter_setter', { + setter (val) { + this._ = val + }, + getter () { + return 'a getter' + } + }) + t.assert.ok('Done') +}) + +test('decorateRequest with getter/setter can handle encapsulation', async t => { + t.plan(24) + + const fastify = Fastify({ logger: true }) + + fastify.decorateRequest('test_getter_setter_holder') + fastify.decorateRequest('test_getter_setter', { + getter () { + this.test_getter_setter_holder ??= {} + return this.test_getter_setter_holder + } + }) + + fastify.get('/', async function (req, reply) { + t.assert.deepStrictEqual(req.test_getter_setter, {}, 'a getter') + req.test_getter_setter.a = req.id + t.assert.deepStrictEqual(req.test_getter_setter, { a: req.id }) + }) + + fastify.addHook('onResponse', async function hook (req, reply) { + t.assert.deepStrictEqual(req.test_getter_setter, { a: req.id }) + }) + + await Promise.all([ + fastify.inject('/').then(res => t.assert.strictEqual(res.statusCode, 200)), + fastify.inject('/').then(res => t.assert.strictEqual(res.statusCode, 200)), + fastify.inject('/').then(res => t.assert.strictEqual(res.statusCode, 200)), + fastify.inject('/').then(res => t.assert.strictEqual(res.statusCode, 200)), + fastify.inject('/').then(res => t.assert.strictEqual(res.statusCode, 200)), + fastify.inject('/').then(res => t.assert.strictEqual(res.statusCode, 200)) + ]) +}) + +test('decorateRequest with getter/setter can handle encapsulation with arrays', async t => { + t.plan(24) + + const fastify = Fastify({ logger: true }) + + fastify.decorateRequest('array_holder') + fastify.decorateRequest('my_array', { + getter () { + this.array_holder ??= [] + return this.array_holder + } + }) + + fastify.get('/', async function (req, reply) { + t.assert.deepStrictEqual(req.my_array, []) + req.my_array.push(req.id) + t.assert.deepStrictEqual(req.my_array, [req.id]) + }) + + fastify.addHook('onResponse', async function hook (req, reply) { + t.assert.deepStrictEqual(req.my_array, [req.id]) + }) + + await Promise.all([ + fastify.inject('/').then(res => t.assert.strictEqual(res.statusCode, 200)), + fastify.inject('/').then(res => t.assert.strictEqual(res.statusCode, 200)), + fastify.inject('/').then(res => t.assert.strictEqual(res.statusCode, 200)), + fastify.inject('/').then(res => t.assert.strictEqual(res.statusCode, 200)), + fastify.inject('/').then(res => t.assert.strictEqual(res.statusCode, 200)), + fastify.inject('/').then(res => t.assert.strictEqual(res.statusCode, 200)) + ]) +}) + +test('decorate* should not emit error if string,bool,numbers are passed', t => { + const fastify = Fastify() + + fastify.decorateRequest('test_str', 'foo') + fastify.decorateRequest('test_bool', true) + fastify.decorateRequest('test_number', 42) + fastify.decorateRequest('test_null', null) + fastify.decorateRequest('test_undefined', undefined) + fastify.decorateReply('test_str', 'foo') + fastify.decorateReply('test_bool', true) + fastify.decorateReply('test_number', 42) + fastify.decorateReply('test_null', null) + fastify.decorateReply('test_undefined', undefined) + t.assert.ok('Done') +}) + +test('Request/reply decorators should be able to access the server instance', async t => { + t.plan(6) + + const server = require('..')({ logger: false }) + server.decorateRequest('assert', rootAssert) + server.decorateReply('assert', rootAssert) + + server.get('/root-assert', async (req, res) => { + req.assert() + res.assert() + return 'done' + }) + + server.register(async instance => { + instance.decorateRequest('assert', nestedAssert) + instance.decorateReply('assert', nestedAssert) + instance.decorate('foo', 'bar') + + instance.get('/nested-assert', async (req, res) => { + req.assert() + res.assert() + return 'done' + }) + }) + + await server.inject({ method: 'GET', url: '/root-assert' }) + await server.inject({ method: 'GET', url: '/nested-assert' }) + + // ---- + function rootAssert () { + t.assert.strictEqual(this.server, server) + } + + function nestedAssert () { + t.assert.notStrictEqual(this.server, server) + t.assert.strictEqual(this.server.foo, 'bar') + } +}) + +test('plugin required decorators', async t => { + const plugin1 = fp( + async (instance) => { + instance.decorateRequest('someThing', null) + + instance.addHook('onRequest', async (request, reply) => { + request.someThing = 'hello' + }) + }, + { + name: 'custom-plugin-one' + } + ) + + const plugin2 = fp( + async () => { + // nothing + }, + { + name: 'custom-plugin-two', + dependencies: ['custom-plugin-one'], + decorators: { + request: ['someThing'] + } + } + ) + + const app = Fastify() + app.register(plugin1) + app.register(plugin2) + await app.ready() +}) + +test('decorateRequest/decorateReply empty string', async t => { + t.plan(6) + const fastify = Fastify() + + fastify.decorateRequest('test', '') + fastify.decorateReply('test2', '') + fastify.get('/yes', (req, reply) => { + t.assert.strictEqual(req.test, '') + t.assert.strictEqual(reply.test2, '') + reply.send({ hello: 'world' }) + }) + t.after(() => fastify.close()) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const result = await fetch(`${fastifyServer}/yes`) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) +}) + +test('decorateRequest/decorateReply is undefined', async t => { + t.plan(6) + const fastify = Fastify() + + fastify.decorateRequest('test', undefined) + fastify.decorateReply('test2', undefined) + fastify.get('/yes', (req, reply) => { + t.assert.strictEqual(req.test, undefined) + t.assert.strictEqual(reply.test2, undefined) + reply.send({ hello: 'world' }) + }) + t.after(() => fastify.close()) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const result = await fetch(`${fastifyServer}/yes`) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) +}) + +test('decorateRequest/decorateReply is not set to a value', async t => { + t.plan(6) + const fastify = Fastify() + + fastify.decorateRequest('test') + fastify.decorateReply('test2') + fastify.get('/yes', (req, reply) => { + t.assert.strictEqual(req.test, undefined) + t.assert.strictEqual(reply.test2, undefined) + reply.send({ hello: 'world' }) + }) + t.after(() => fastify.close()) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const result = await fetch(`${fastifyServer}/yes`) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) +}) + +test('decorateRequest with dependencies', (t, done) => { + t.plan(2) + const app = Fastify() + + const decorator1 = 'bar' + const decorator2 = 'foo' + + app.decorate('decorator1', decorator1) + app.decorateRequest('decorator1', decorator1) + + if ( + app.hasDecorator('decorator1') && + app.hasRequestDecorator('decorator1') + ) { + t.assert.doesNotThrow(() => app.decorateRequest('decorator2', decorator2, ['decorator1'])) + t.assert.ok(app.hasRequestDecorator('decorator2')) + done() + } +}) + +test('decorateRequest with dependencies (functions)', (t, done) => { + t.plan(2) + const app = Fastify() + + const decorator1 = () => 'bar' + const decorator2 = () => 'foo' + + app.decorate('decorator1', decorator1) + app.decorateRequest('decorator1', decorator1) + + if ( + app.hasDecorator('decorator1') && + app.hasRequestDecorator('decorator1') + ) { + t.assert.doesNotThrow(() => app.decorateRequest('decorator2', decorator2, ['decorator1'])) + t.assert.ok(app.hasRequestDecorator('decorator2')) + done() + } +}) + +test('chain of decorators on Request', async t => { + const fastify = Fastify() + fastify.register(fp(async function (fastify) { + fastify.decorateRequest('foo', 'toto') + fastify.decorateRequest('bar', () => 'tata') + }, { + name: 'first' + })) + + fastify.get('/foo', async function (request, reply) { + return request.foo + }) + fastify.get('/bar', function (request, reply) { + return request.bar() + }) + fastify.register(async function second (fastify) { + fastify.get('/foo', async function (request, reply) { + return request.foo + }) + fastify.get('/bar', async function (request, reply) { + return request.bar() + }) + fastify.register(async function fourth (fastify) { + fastify.get('/plugin3/foo', async function (request, reply) { + return request.foo + }) + fastify.get('/plugin3/bar', function (request, reply) { + return request.bar() + }) + }) + fastify.register(fp(async function (fastify) { + fastify.decorateRequest('fooB', 'toto') + fastify.decorateRequest('barB', () => 'tata') + }, { + name: 'third' + })) + }, + { prefix: '/plugin2', name: 'plugin2' } + ) + + await fastify.ready() + + { + const response = await fastify.inject('/foo') + t.assert.strictEqual(response.body, 'toto') + } + + { + const response = await fastify.inject('/bar') + t.assert.strictEqual(response.body, 'tata') + } + + { + const response = await fastify.inject('/plugin2/foo') + t.assert.strictEqual(response.body, 'toto') + } + + { + const response = await fastify.inject('/plugin2/bar') + t.assert.strictEqual(response.body, 'tata') + } + + { + const response = await fastify.inject('/plugin2/plugin3/foo') + t.assert.strictEqual(response.body, 'toto') + } + + { + const response = await fastify.inject('/plugin2/plugin3/bar') + t.assert.strictEqual(response.body, 'tata') + } +}) + +test('chain of decorators on Reply', async (t) => { + const fastify = Fastify() + fastify.register(fp(async function (fastify) { + fastify.decorateReply('foo', 'toto') + fastify.decorateReply('bar', () => 'tata') + }, { + name: 'first' + })) + + fastify.get('/foo', async function (request, reply) { + return reply.foo + }) + fastify.get('/bar', function (request, reply) { + return reply.bar() + }) + fastify.register(async function second (fastify) { + fastify.get('/foo', async function (request, reply) { + return reply.foo + }) + fastify.get('/bar', async function (request, reply) { + return reply.bar() + }) + fastify.register(async function fourth (fastify) { + fastify.get('/plugin3/foo', async function (request, reply) { + return reply.foo + }) + fastify.get('/plugin3/bar', function (request, reply) { + return reply.bar() + }) + }) + fastify.register(fp(async function (fastify) { + fastify.decorateReply('fooB', 'toto') + fastify.decorateReply('barB', () => 'tata') + }, { + name: 'third' + })) + }, + { prefix: '/plugin2', name: 'plugin2' } + ) + + await fastify.ready() + + { + const response = await fastify.inject('/foo') + t.assert.strictEqual(response.body, 'toto') + } + + { + const response = await fastify.inject('/bar') + t.assert.strictEqual(response.body, 'tata') + } + + { + const response = await fastify.inject('/plugin2/foo') + t.assert.strictEqual(response.body, 'toto') + } + + { + const response = await fastify.inject('/plugin2/bar') + t.assert.strictEqual(response.body, 'tata') + } + + { + const response = await fastify.inject('/plugin2/plugin3/foo') + t.assert.strictEqual(response.body, 'toto') + } + + { + const response = await fastify.inject('/plugin2/plugin3/bar') + t.assert.strictEqual(response.body, 'tata') + } +}) + +test('getDecorator should return the decorator', (t, done) => { + t.plan(12) + const fastify = Fastify() + + fastify.decorate('root', 'from_root') + fastify.decorateRequest('root', 'from_root_request') + fastify.decorateReply('root', 'from_root_reply') + + t.assert.strictEqual(fastify.getDecorator('root'), 'from_root') + fastify.get('/', async (req, res) => { + t.assert.strictEqual(req.getDecorator('root'), 'from_root_request') + t.assert.strictEqual(res.getDecorator('root'), 'from_root_reply') + + res.send() + }) + + fastify.register((child) => { + child.decorate('child', 'from_child') + + t.assert.strictEqual(child.getDecorator('child'), 'from_child') + t.assert.strictEqual(child.getDecorator('root'), 'from_root') + + child.get('/child', async (req, res) => { + t.assert.strictEqual(req.getDecorator('root'), 'from_root_request') + t.assert.strictEqual(res.getDecorator('root'), 'from_root_reply') + + res.send() + }) + }) + + fastify.ready((err) => { + t.assert.ifError(err) + fastify.inject({ url: '/' }, (err, res) => { + t.assert.ifError(err) + t.assert.ok(true) + }) + + fastify.inject({ url: '/child' }, (err, res) => { + t.assert.ifError(err) + t.assert.ok(true) + done() + }) + }) +}) + +test('getDecorator should return function decorators with expected binded context', (t, done) => { + t.plan(12) + const fastify = Fastify() + + fastify.decorate('a', function () { + return this + }) + fastify.decorateRequest('b', function () { + return this + }) + fastify.decorateReply('c', function () { + return this + }) + + fastify.register((child) => { + child.decorate('a', function () { + return this + }) + + t.assert.deepEqual(child.getDecorator('a')(), child) + child.get('/child', async (req, res) => { + t.assert.deepEqual(req.getDecorator('b')(), req) + t.assert.deepEqual(res.getDecorator('c')(), res) + + res.send() + }) + }) + + t.assert.deepEqual(fastify.getDecorator('a')(), fastify) + fastify.get('/', async (req, res) => { + t.assert.deepEqual(req.getDecorator('b')(), req) + t.assert.deepEqual(res.getDecorator('c')(), res) + res.send() + }) + + fastify.ready((err) => { + t.assert.ifError(err) + fastify.inject({ url: '/' }, (err, res) => { + t.assert.ifError(err) + t.assert.ok(true, 'passed') + }) + + fastify.inject({ url: '/child' }, (err, res) => { + t.assert.ifError(err) + t.assert.ok(true, 'passed') + done() + }) + t.assert.ok(true, 'passed') + }) +}) + +test('getDecorator should only return decorators existing in the scope', (t, done) => { + t.plan(9) + + function assertsThrowOnUndeclaredDecorator (notDecorated, instanceType) { + try { + notDecorated.getDecorator('foo') + t.assert.fail() + } catch (e) { + t.assert.deepEqual(e.code, 'FST_ERR_DEC_UNDECLARED') + t.assert.deepEqual(e.message, `No decorator 'foo' has been declared on ${instanceType}.`) + } + } + + const fastify = Fastify() + fastify.register(child => { + child.decorate('foo', true) + child.decorateRequest('foo', true) + child.decorateReply('foo', true) + }) + + fastify.get('/', async (req, res) => { + assertsThrowOnUndeclaredDecorator(req, 'request') + assertsThrowOnUndeclaredDecorator(res, 'reply') + + return { hello: 'world' } + }) + + fastify.ready((err) => { + t.assert.ifError(err) + + assertsThrowOnUndeclaredDecorator(fastify, 'instance') + fastify.inject({ url: '/' }, (err, res) => { + t.assert.ifError(err) + t.assert.ok(true, 'passed') + done() + }) + }) +}) + +test('Request.setDecorator should update an existing decorator', (t, done) => { + t.plan(7) + const fastify = Fastify() + + fastify.decorateRequest('session', null) + fastify.decorateRequest('utility', null) + fastify.addHook('onRequest', async (req, reply) => { + req.setDecorator('session', { user: 'Jean' }) + req.setDecorator('utility', function () { + return this + }) + try { + req.setDecorator('foo', { user: 'Jean' }) + t.assert.fail() + } catch (e) { + t.assert.deepEqual(e.code, 'FST_ERR_DEC_UNDECLARED') + t.assert.deepEqual(e.message, "No decorator 'foo' has been declared on request.") + } + }) + + fastify.get('/', async (req, res) => { + t.assert.deepEqual(req.getDecorator('session'), { user: 'Jean' }) + t.assert.deepEqual(req.getDecorator('utility')(), req) + + res.send() + }) + + fastify.ready((err) => { + t.assert.ifError(err) + fastify.inject({ url: '/' }, (err, res) => { + t.assert.ifError(err) + t.assert.ok(true, 'passed') + done() + }) + }) +}) diff --git a/services/slides/node_modules/fastify/test/delete.test.js b/services/slides/node_modules/fastify/test/delete.test.js new file mode 100644 index 0000000000000000000000000000000000000000..3621703846d880ff156de5c9bbcebd449da75d17 --- /dev/null +++ b/services/slides/node_modules/fastify/test/delete.test.js @@ -0,0 +1,344 @@ +'use strict' + +const { test } = require('node:test') +const fastify = require('..')() + +const schema = { + schema: { + response: { + '2xx': { + type: 'object', + properties: { + hello: { + type: 'string' + } + } + } + } + } +} + +const querySchema = { + schema: { + querystring: { + type: 'object', + properties: { + hello: { + type: 'integer' + } + } + } + } +} + +const paramsSchema = { + schema: { + params: { + type: 'object', + properties: { + foo: { + type: 'string' + }, + test: { + type: 'integer' + } + } + } + } +} + +const headersSchema = { + schema: { + headers: { + type: 'object', + properties: { + 'x-test': { + type: 'number' + } + } + } + } +} + +const bodySchema = { + schema: { + body: { + type: 'object', + properties: { + hello: { + type: 'string' + } + } + }, + response: { + '2xx': { + type: 'object', + properties: { + hello: { + type: 'string' + } + } + } + } + } +} + +test('shorthand - delete', (t, done) => { + t.plan(1) + try { + fastify.delete('/', schema, function (req, reply) { + reply.code(200).send({ hello: 'world' }) + }) + t.assert.ok(true) + } catch (e) { + t.assert.fail() + } finally { + done() + } +}) + +test('shorthand - delete params', t => { + t.plan(1) + try { + fastify.delete('/params/:foo/:test', paramsSchema, function (req, reply) { + reply.code(200).send(req.params) + }) + t.assert.ok(true) + } catch (e) { + t.assert.fail() + } +}) + +test('shorthand - delete, querystring schema', t => { + t.plan(1) + try { + fastify.delete('/query', querySchema, function (req, reply) { + reply.send(req.query) + }) + t.assert.ok(true) + } catch (e) { + t.assert.fail() + } +}) + +test('shorthand - get, headers schema', t => { + t.plan(1) + try { + fastify.delete('/headers', headersSchema, function (req, reply) { + reply.code(200).send(req.headers) + }) + t.assert.ok(true) + } catch (e) { + t.assert.fail() + } +}) + +test('missing schema - delete', t => { + t.plan(1) + try { + fastify.delete('/missing', function (req, reply) { + reply.code(200).send({ hello: 'world' }) + }) + t.assert.ok(true) + } catch (e) { + t.assert.fail() + } +}) + +test('body - delete', t => { + t.plan(1) + try { + fastify.delete('/body', bodySchema, function (req, reply) { + reply.send(req.body) + }) + t.assert.ok(true) + } catch (e) { + t.assert.fail() + } +}) + +test('delete tests', async t => { + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) + + await t.test('shorthand - request delete', async t => { + t.plan(4) + + const response = await fetch(fastifyServer, { + method: 'DELETE' + }) + t.assert.ok(response.ok) + t.assert.strictEqual(response.status, 200) + const body = await response.text() + t.assert.strictEqual(response.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + }) + + await t.test('shorthand - request delete params schema', async t => { + t.plan(4) + + const response = await fetch(fastifyServer + '/params/world/123', { + method: 'DELETE' + }) + t.assert.ok(response.ok) + t.assert.strictEqual(response.status, 200) + const body = await response.text() + t.assert.strictEqual(response.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { foo: 'world', test: 123 }) + }) + + await t.test('shorthand - request delete params schema error', async t => { + t.plan(3) + + const response = await fetch(fastifyServer + '/params/world/string', { + method: 'DELETE' + }) + + t.assert.ok(!response.ok) + t.assert.strictEqual(response.status, 400) + t.assert.deepStrictEqual(await response.json(), { + error: 'Bad Request', + code: 'FST_ERR_VALIDATION', + message: 'params/test must be integer', + statusCode: 400 + }) + }) + + await t.test('shorthand - request delete headers schema', async t => { + t.plan(4) + + const response = await fetch(fastifyServer + '/headers', { + method: 'DELETE', + headers: { + 'x-test': '1' + } + }) + t.assert.ok(response.ok) + t.assert.strictEqual(response.status, 200) + const body = await response.text() + t.assert.strictEqual(response.headers.get('content-length'), '' + body.length) + t.assert.strictEqual(JSON.parse(body)['x-test'], 1) + }) + + await t.test('shorthand - request delete headers schema error', async t => { + t.plan(3) + + const response = await fetch(fastifyServer + '/headers', { + method: 'DELETE', + headers: { + 'x-test': 'abc' + } + }) + + t.assert.ok(!response.ok) + t.assert.strictEqual(response.status, 400) + const body = await response.text() + t.assert.deepStrictEqual(JSON.parse(body), { + error: 'Bad Request', + code: 'FST_ERR_VALIDATION', + message: 'headers/x-test must be number', + statusCode: 400 + }) + }) + + await t.test('shorthand - request delete querystring schema', async t => { + t.plan(4) + + const response = await fetch(fastifyServer + '/query?hello=123', { + method: 'DELETE' + }) + t.assert.ok(response.ok) + t.assert.strictEqual(response.status, 200) + const body = await response.text() + t.assert.strictEqual(response.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 123 }) + }) + + await t.test('shorthand - request delete querystring schema error', async t => { + t.plan(3) + + const response = await fetch(fastifyServer + '/query?hello=world', { + method: 'DELETE' + }) + + t.assert.ok(!response.ok) + t.assert.strictEqual(response.status, 400) + const body = await response.text() + t.assert.deepStrictEqual(JSON.parse(body), { + error: 'Bad Request', + code: 'FST_ERR_VALIDATION', + message: 'querystring/hello must be integer', + statusCode: 400 + }) + }) + + await t.test('shorthand - request delete missing schema', async t => { + t.plan(4) + + const response = await fetch(fastifyServer + '/missing', { + method: 'DELETE' + }) + t.assert.ok(response.ok) + t.assert.strictEqual(response.status, 200) + const body = await response.text() + t.assert.strictEqual(response.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + }) + + await t.test('shorthand - delete with body', async t => { + t.plan(3) + + const response = await fetch(fastifyServer + '/body', { + method: 'DELETE', + body: JSON.stringify({ hello: 'world' }), + headers: { + 'Content-Type': 'application/json' + } + }) + t.assert.ok(response.ok) + t.assert.strictEqual(response.status, 200) + const body = await response.json() + t.assert.deepStrictEqual(body, { hello: 'world' }) + }) +}) + +test('shorthand - delete with application/json Content-Type header and null body', (t, done) => { + t.plan(4) + const fastify = require('..')() + fastify.delete('/', {}, (req, reply) => { + t.assert.strictEqual(req.body, null) + reply.send(req.body) + }) + fastify.inject({ + method: 'DELETE', + url: '/', + headers: { 'Content-Type': 'application/json' }, + body: 'null' + }, (err, response) => { + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.payload.toString(), 'null') + done() + }) +}) + +// https://github.com/fastify/fastify/issues/936 +// Skip this test because this is an invalid request +test('shorthand - delete with application/json Content-Type header and without body', { skip: 'https://github.com/fastify/fastify/pull/5419' }, t => { + t.plan(4) + const fastify = require('..')() + fastify.delete('/', {}, (req, reply) => { + t.assert.strictEqual(req.body, undefined) + reply.send(req.body) + }) + fastify.inject({ + method: 'DELETE', + url: '/', + headers: { 'Content-Type': 'application/json' }, + body: null + }, (err, response) => { + t.assert.ifError(err) + t.assert.strictEqual(response.statusCode, 200) + t.assert.strictEqual(response.payload.toString(), '') + }) +}) diff --git a/services/slides/node_modules/fastify/test/diagnostics-channel/404.test.js b/services/slides/node_modules/fastify/test/diagnostics-channel/404.test.js new file mode 100644 index 0000000000000000000000000000000000000000..f02b0b644d1105430ea31f4316c3e0b1288fe0a8 --- /dev/null +++ b/services/slides/node_modules/fastify/test/diagnostics-channel/404.test.js @@ -0,0 +1,49 @@ +'use strict' + +const { test } = require('node:test') +const diagnostics = require('node:diagnostics_channel') +const Fastify = require('../..') +const Request = require('../../lib/request') +const Reply = require('../../lib/reply') + +test('diagnostics channel sync events fire in expected order', async t => { + t.plan(9) + let callOrder = 0 + let firstEncounteredMessage + + diagnostics.subscribe('tracing:fastify.request.handler:start', (msg) => { + t.assert.strictEqual(callOrder++, 0) + firstEncounteredMessage = msg + t.assert.ok(msg.request instanceof Request) + t.assert.ok(msg.reply instanceof Reply) + }) + + diagnostics.subscribe('tracing:fastify.request.handler:end', (msg) => { + t.assert.ok(msg.request instanceof Request) + t.assert.ok(msg.reply instanceof Reply) + t.assert.strictEqual(callOrder++, 1) + t.assert.strictEqual(msg, firstEncounteredMessage) + }) + + diagnostics.subscribe('tracing:fastify.request.handler:error', (msg) => { + t.assert.fail('should not trigger error channel') + }) + + const fastify = Fastify() + fastify.route({ + method: 'GET', + url: '/', + handler: function (req, reply) { + reply.callNotFound() + } + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) + + const response = await fetch(fastifyServer, { + method: 'GET' + }) + t.assert.ok(!response.ok) + t.assert.strictEqual(response.status, 404) +}) diff --git a/services/slides/node_modules/fastify/test/diagnostics-channel/async-delay-request.test.js b/services/slides/node_modules/fastify/test/diagnostics-channel/async-delay-request.test.js new file mode 100644 index 0000000000000000000000000000000000000000..d86ae124fe5ee8db47b16519d67d94a37dc29291 --- /dev/null +++ b/services/slides/node_modules/fastify/test/diagnostics-channel/async-delay-request.test.js @@ -0,0 +1,65 @@ +'use strict' + +const diagnostics = require('node:diagnostics_channel') +const { test } = require('node:test') +const Fastify = require('../..') +const Request = require('../../lib/request') +const Reply = require('../../lib/reply') + +test('diagnostics channel async events fire in expected order', async t => { + t.plan(19) + let callOrder = 0 + let firstEncounteredMessage + + diagnostics.subscribe('tracing:fastify.request.handler:start', (msg) => { + t.assert.strictEqual(callOrder++, 0) + firstEncounteredMessage = msg + t.assert.ok(msg.request instanceof Request) + t.assert.ok(msg.reply instanceof Reply) + }) + + diagnostics.subscribe('tracing:fastify.request.handler:end', (msg) => { + t.assert.strictEqual(callOrder++, 1) + t.assert.ok(msg.request instanceof Request) + t.assert.ok(msg.reply instanceof Reply) + t.assert.strictEqual(msg, firstEncounteredMessage) + t.assert.strictEqual(msg.async, true) + }) + + diagnostics.subscribe('tracing:fastify.request.handler:asyncStart', (msg) => { + t.assert.strictEqual(callOrder++, 2) + t.assert.ok(msg.request instanceof Request) + t.assert.ok(msg.reply instanceof Reply) + t.assert.strictEqual(msg, firstEncounteredMessage) + }) + + diagnostics.subscribe('tracing:fastify.request.handler:asyncEnd', (msg) => { + t.assert.strictEqual(callOrder++, 3) + t.assert.ok(msg.request instanceof Request) + t.assert.ok(msg.reply instanceof Reply) + t.assert.strictEqual(msg, firstEncounteredMessage) + }) + + diagnostics.subscribe('tracing:fastify.request.handler:error', (msg) => { + t.assert.fail('should not trigger error channel') + }) + + const fastify = Fastify() + fastify.route({ + method: 'GET', + url: '/', + handler: async function (req, reply) { + setImmediate(() => reply.send({ hello: 'world' })) + return reply + } + }) + + t.after(() => { fastify.close() }) + + const fastifyServer = await fastify.listen({ port: 0 }) + + const result = await fetch(fastifyServer + '/') + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.deepStrictEqual(await result.json(), { hello: 'world' }) +}) diff --git a/services/slides/node_modules/fastify/test/diagnostics-channel/async-request.test.js b/services/slides/node_modules/fastify/test/diagnostics-channel/async-request.test.js new file mode 100644 index 0000000000000000000000000000000000000000..813eccdd8d737985d85408323d2c6b5e452408c9 --- /dev/null +++ b/services/slides/node_modules/fastify/test/diagnostics-channel/async-request.test.js @@ -0,0 +1,64 @@ +'use strict' + +const { test } = require('node:test') +const diagnostics = require('node:diagnostics_channel') +const Fastify = require('../..') +const Request = require('../../lib/request') +const Reply = require('../../lib/reply') + +test('diagnostics channel async events fire in expected order', async t => { + t.plan(18) + let callOrder = 0 + let firstEncounteredMessage + + diagnostics.subscribe('tracing:fastify.request.handler:start', (msg) => { + t.assert.strictEqual(callOrder++, 0) + firstEncounteredMessage = msg + t.assert.ok(msg.request instanceof Request) + t.assert.ok(msg.reply instanceof Reply) + }) + + diagnostics.subscribe('tracing:fastify.request.handler:end', (msg) => { + t.assert.strictEqual(callOrder++, 1) + t.assert.ok(msg.request instanceof Request) + t.assert.ok(msg.reply instanceof Reply) + t.assert.strictEqual(msg, firstEncounteredMessage) + }) + + diagnostics.subscribe('tracing:fastify.request.handler:asyncStart', (msg) => { + t.assert.strictEqual(callOrder++, 2) + t.assert.ok(msg.request instanceof Request) + t.assert.ok(msg.reply instanceof Reply) + t.assert.strictEqual(msg, firstEncounteredMessage) + }) + + diagnostics.subscribe('tracing:fastify.request.handler:asyncEnd', (msg) => { + t.assert.strictEqual(callOrder++, 3) + t.assert.ok(msg.request instanceof Request) + t.assert.ok(msg.reply instanceof Reply) + t.assert.strictEqual(msg, firstEncounteredMessage) + }) + + diagnostics.subscribe('tracing:fastify.request.handler:error', (msg) => { + t.assert.fail('should not trigger error channel') + }) + + const fastify = Fastify() + fastify.route({ + method: 'GET', + url: '/', + handler: async function (req, reply) { + return { hello: 'world' } + } + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + + t.after(() => { fastify.close() }) + + const response = await fetch(fastifyServer) + t.assert.ok(response.ok) + t.assert.strictEqual(response.status, 200) + const body = await response.text() + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) +}) diff --git a/services/slides/node_modules/fastify/test/diagnostics-channel/error-before-handler.test.js b/services/slides/node_modules/fastify/test/diagnostics-channel/error-before-handler.test.js new file mode 100644 index 0000000000000000000000000000000000000000..edac466b706bd41808a6dc58d0c0e220e768b4ce --- /dev/null +++ b/services/slides/node_modules/fastify/test/diagnostics-channel/error-before-handler.test.js @@ -0,0 +1,35 @@ +'use strict' + +const diagnostics = require('node:diagnostics_channel') +const { test } = require('node:test') +require('../../lib/hooks').onSendHookRunner = function Stub () {} +const Request = require('../../lib/request') +const Reply = require('../../lib/reply') +const symbols = require('../../lib/symbols.js') +const { preHandlerCallback } = require('../../lib/handle-request')[Symbol.for('internals')] + +test('diagnostics channel handles an error before calling context handler', t => { + t.plan(3) + let callOrder = 0 + + diagnostics.subscribe('tracing:fastify.request.handler:start', (msg) => { + t.assert.strictEqual(callOrder++, 0) + }) + + diagnostics.subscribe('tracing:fastify.request.handler:error', (msg) => { + t.assert.strictEqual(callOrder++, 1) + t.assert.strictEqual(msg.error.message, 'oh no') + }) + + const error = new Error('oh no') + const request = new Request() + const reply = new Reply({}, request) + request[symbols.kRouteContext] = { + config: { + url: '/foo', + method: 'GET' + } + } + + preHandlerCallback(error, request, reply) +}) diff --git a/services/slides/node_modules/fastify/test/diagnostics-channel/error-request.test.js b/services/slides/node_modules/fastify/test/diagnostics-channel/error-request.test.js new file mode 100644 index 0000000000000000000000000000000000000000..17ee85ef9c6e24c9a7dfd3fc7b10d5940b45c896 --- /dev/null +++ b/services/slides/node_modules/fastify/test/diagnostics-channel/error-request.test.js @@ -0,0 +1,53 @@ +'use strict' + +const { test } = require('node:test') +const diagnostics = require('node:diagnostics_channel') +const Fastify = require('../..') +const Request = require('../../lib/request') +const Reply = require('../../lib/reply') + +test('diagnostics channel events report on errors', async t => { + t.plan(14) + let callOrder = 0 + let firstEncounteredMessage + + diagnostics.subscribe('tracing:fastify.request.handler:start', (msg) => { + t.assert.strictEqual(callOrder++, 0) + firstEncounteredMessage = msg + t.assert.ok(msg.request instanceof Request) + t.assert.ok(msg.reply instanceof Reply) + }) + + diagnostics.subscribe('tracing:fastify.request.handler:end', (msg) => { + t.assert.ok(msg.request instanceof Request) + t.assert.ok(msg.reply instanceof Reply) + t.assert.strictEqual(callOrder++, 2) + t.assert.strictEqual(msg, firstEncounteredMessage) + }) + + diagnostics.subscribe('tracing:fastify.request.handler:error', (msg) => { + t.assert.ok(msg.request instanceof Request) + t.assert.ok(msg.reply instanceof Reply) + t.assert.ok(msg.error instanceof Error) + t.assert.strictEqual(callOrder++, 1) + t.assert.strictEqual(msg.error.message, 'borked') + }) + + const fastify = Fastify() + fastify.route({ + method: 'GET', + url: '/', + handler: function (req, reply) { + throw new Error('borked') + } + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) + + const response = await fetch(fastifyServer, { + method: 'GET' + }) + t.assert.ok(!response.ok) + t.assert.strictEqual(response.status, 500) +}) diff --git a/services/slides/node_modules/fastify/test/diagnostics-channel/error-status.test.js b/services/slides/node_modules/fastify/test/diagnostics-channel/error-status.test.js new file mode 100644 index 0000000000000000000000000000000000000000..474a91c2c51e97206b8edea8f37b75fa712c56a4 --- /dev/null +++ b/services/slides/node_modules/fastify/test/diagnostics-channel/error-status.test.js @@ -0,0 +1,123 @@ +'use strict' + +const { test } = require('node:test') +const Fastify = require('../..') +const statusCodes = require('node:http').STATUS_CODES +const diagnostics = require('node:diagnostics_channel') + +test('diagnostics channel error event should report correct status code', async (t) => { + t.plan(3) + const fastify = Fastify() + t.after(() => fastify.close()) + + let diagnosticsStatusCode + + const channel = diagnostics.channel('tracing:fastify.request.handler:error') + const handler = (msg) => { + diagnosticsStatusCode = msg.reply.statusCode + } + channel.subscribe(handler) + t.after(() => channel.unsubscribe(handler)) + + fastify.get('/', async () => { + const err = new Error('test error') + err.statusCode = 503 + throw err + }) + + const res = await fastify.inject('/') + + t.assert.strictEqual(res.statusCode, 503) + t.assert.strictEqual(diagnosticsStatusCode, 503, 'diagnostics channel should report correct status code') + t.assert.strictEqual(diagnosticsStatusCode, res.statusCode, 'diagnostics status should match response status') +}) + +test('diagnostics channel error event should report 500 for errors without status', async (t) => { + t.plan(3) + const fastify = Fastify() + t.after(() => fastify.close()) + + let diagnosticsStatusCode + + const channel = diagnostics.channel('tracing:fastify.request.handler:error') + const handler = (msg) => { + diagnosticsStatusCode = msg.reply.statusCode + } + channel.subscribe(handler) + t.after(() => channel.unsubscribe(handler)) + + fastify.get('/', async () => { + throw new Error('plain error without status') + }) + + const res = await fastify.inject('/') + + t.assert.strictEqual(res.statusCode, 500) + t.assert.strictEqual(diagnosticsStatusCode, 500, 'diagnostics channel should report 500 for plain errors') + t.assert.strictEqual(diagnosticsStatusCode, res.statusCode, 'diagnostics status should match response status') +}) + +test('diagnostics channel error event should report correct status with custom error handler', async (t) => { + t.plan(3) + const fastify = Fastify() + t.after(() => fastify.close()) + + let diagnosticsStatusCode + + const channel = diagnostics.channel('tracing:fastify.request.handler:error') + const handler = (msg) => { + diagnosticsStatusCode = msg.reply.statusCode + } + channel.subscribe(handler) + t.after(() => channel.unsubscribe(handler)) + + fastify.setErrorHandler((error, request, reply) => { + reply.status(503).send({ error: error.message }) + }) + + fastify.get('/', async () => { + throw new Error('handler error') + }) + + const res = await fastify.inject('/') + + // Note: The diagnostics channel fires before the custom error handler runs, + // so it reports 500 (default) rather than 503 (set by custom handler). + // This is expected behavior - the error channel reports the initial error state. + t.assert.strictEqual(res.statusCode, 503) + t.assert.strictEqual(diagnosticsStatusCode, 500, 'diagnostics channel reports status before custom handler') + t.assert.notStrictEqual(diagnosticsStatusCode, res.statusCode, 'custom handler can change status after diagnostics') +}) + +test('Error.status property support', (t, done) => { + t.plan(4) + const fastify = Fastify() + t.after(() => fastify.close()) + const err = new Error('winter is coming') + err.status = 418 + + diagnostics.subscribe('tracing:fastify.request.handler:error', (msg) => { + t.assert.strictEqual(msg.error.message, 'winter is coming') + }) + + fastify.get('/', () => { + return Promise.reject(err) + }) + + fastify.inject({ + method: 'GET', + url: '/' + }, (error, res) => { + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 418) + t.assert.deepStrictEqual( + { + error: statusCodes['418'], + message: err.message, + statusCode: 418 + }, + JSON.parse(res.payload) + ) + done() + }) +}) diff --git a/services/slides/node_modules/fastify/test/diagnostics-channel/init.test.js b/services/slides/node_modules/fastify/test/diagnostics-channel/init.test.js new file mode 100644 index 0000000000000000000000000000000000000000..295cacc82973a7cd17f311c97ee77cbe72670a4b --- /dev/null +++ b/services/slides/node_modules/fastify/test/diagnostics-channel/init.test.js @@ -0,0 +1,50 @@ +'use strict' + +const { test } = require('node:test') +const proxyquire = require('proxyquire') + +test('diagnostics_channel when present and subscribers', t => { + t.plan(3) + + let fastifyInHook + + const diagnostics = { + channel (name) { + t.assert.strictEqual(name, 'fastify.initialization') + return { + hasSubscribers: true, + publish (event) { + t.assert.ok(event.fastify) + fastifyInHook = event.fastify + } + } + }, + '@noCallThru': true + } + + const fastify = proxyquire('../../fastify', { + 'node:diagnostics_channel': diagnostics + })() + t.assert.strictEqual(fastifyInHook, fastify) +}) + +test('diagnostics_channel when present and no subscribers', t => { + t.plan(1) + + const diagnostics = { + channel (name) { + t.assert.strictEqual(name, 'fastify.initialization') + return { + hasSubscribers: false, + publish () { + t.assert.fail('publish should not be called') + } + } + }, + '@noCallThru': true + } + + proxyquire('../../fastify', { + 'node:diagnostics_channel': diagnostics + })() +}) diff --git a/services/slides/node_modules/fastify/test/diagnostics-channel/sync-delay-request.test.js b/services/slides/node_modules/fastify/test/diagnostics-channel/sync-delay-request.test.js new file mode 100644 index 0000000000000000000000000000000000000000..f83bffec1ee0374b42f7ffe5203e3cdeb56fac1b --- /dev/null +++ b/services/slides/node_modules/fastify/test/diagnostics-channel/sync-delay-request.test.js @@ -0,0 +1,49 @@ +'use strict' + +const { test } = require('node:test') +const diagnostics = require('node:diagnostics_channel') +const Fastify = require('../..') +const Request = require('../../lib/request') +const Reply = require('../../lib/reply') + +test('diagnostics channel sync events fire in expected order', async t => { + t.plan(10) + let callOrder = 0 + let firstEncounteredMessage + + diagnostics.subscribe('tracing:fastify.request.handler:start', (msg) => { + t.assert.strictEqual(callOrder++, 0) + firstEncounteredMessage = msg + t.assert.ok(msg.request instanceof Request) + t.assert.ok(msg.reply instanceof Reply) + }) + + diagnostics.subscribe('tracing:fastify.request.handler:end', (msg) => { + t.assert.ok(msg.request instanceof Request) + t.assert.ok(msg.reply instanceof Reply) + t.assert.strictEqual(callOrder++, 1) + t.assert.strictEqual(msg, firstEncounteredMessage) + }) + + diagnostics.subscribe('tracing:fastify.request.handler:error', (msg) => { + t.assert.fail('should not trigger error channel') + }) + + const fastify = Fastify() + fastify.route({ + method: 'GET', + url: '/', + handler: function (req, reply) { + setImmediate(() => reply.send({ hello: 'world' })) + } + }) + + t.after(() => { fastify.close() }) + + const fastifyServer = await fastify.listen({ port: 0 }) + + const result = await fetch(fastifyServer + '/') + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.deepStrictEqual(await result.json(), { hello: 'world' }) +}) diff --git a/services/slides/node_modules/fastify/test/diagnostics-channel/sync-request-reply.test.js b/services/slides/node_modules/fastify/test/diagnostics-channel/sync-request-reply.test.js new file mode 100644 index 0000000000000000000000000000000000000000..d1e38b6f445fbd9371ca27cfbe5d50adf05d5f56 --- /dev/null +++ b/services/slides/node_modules/fastify/test/diagnostics-channel/sync-request-reply.test.js @@ -0,0 +1,51 @@ +'use strict' + +const { test } = require('node:test') +const diagnostics = require('node:diagnostics_channel') +const Fastify = require('../..') +const Request = require('../../lib/request') +const Reply = require('../../lib/reply') + +test('diagnostics channel sync events fire in expected order', async t => { + t.plan(10) + let callOrder = 0 + let firstEncounteredMessage + + diagnostics.subscribe('tracing:fastify.request.handler:start', (msg) => { + t.assert.strictEqual(callOrder++, 0) + firstEncounteredMessage = msg + t.assert.ok(msg.request instanceof Request) + t.assert.ok(msg.reply instanceof Reply) + }) + + diagnostics.subscribe('tracing:fastify.request.handler:end', (msg) => { + t.assert.ok(msg.request instanceof Request) + t.assert.ok(msg.reply instanceof Reply) + t.assert.strictEqual(callOrder++, 1) + t.assert.strictEqual(msg, firstEncounteredMessage) + }) + + diagnostics.subscribe('tracing:fastify.request.handler:error', (msg) => { + t.assert.fail('should not trigger error channel') + }) + + const fastify = Fastify() + fastify.route({ + method: 'GET', + url: '/', + handler: function (req, reply) { + reply.send({ hello: 'world' }) + } + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) + + const response = await fetch(fastifyServer, { + method: 'GET' + }) + t.assert.ok(response.ok) + t.assert.strictEqual(response.status, 200) + const body = await response.text() + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) +}) diff --git a/services/slides/node_modules/fastify/test/diagnostics-channel/sync-request.test.js b/services/slides/node_modules/fastify/test/diagnostics-channel/sync-request.test.js new file mode 100644 index 0000000000000000000000000000000000000000..c80935397fadb2c0e98fb01eadde8b64c2b7a354 --- /dev/null +++ b/services/slides/node_modules/fastify/test/diagnostics-channel/sync-request.test.js @@ -0,0 +1,54 @@ +'use strict' + +const { test } = require('node:test') +const diagnostics = require('node:diagnostics_channel') +const Fastify = require('../..') +const Request = require('../../lib/request') +const Reply = require('../../lib/reply') + +test('diagnostics channel sync events fire in expected order', async t => { + t.plan(13) + let callOrder = 0 + let firstEncounteredMessage + + diagnostics.subscribe('tracing:fastify.request.handler:start', (msg) => { + t.assert.strictEqual(callOrder++, 0) + firstEncounteredMessage = msg + t.assert.ok(msg.request instanceof Request) + t.assert.ok(msg.reply instanceof Reply) + t.assert.ok(msg.route) + t.assert.strictEqual(msg.route.url, '/:id') + t.assert.strictEqual(msg.route.method, 'GET') + }) + + diagnostics.subscribe('tracing:fastify.request.handler:end', (msg) => { + t.assert.ok(msg.request instanceof Request) + t.assert.ok(msg.reply instanceof Reply) + t.assert.strictEqual(callOrder++, 1) + t.assert.strictEqual(msg, firstEncounteredMessage) + }) + + diagnostics.subscribe('tracing:fastify.request.handler:error', (msg) => { + t.assert.fail('should not trigger error channel') + }) + + const fastify = Fastify() + fastify.route({ + method: 'GET', + url: '/:id', + handler: function (req, reply) { + return { hello: 'world' } + } + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) + + const response = await fetch(fastifyServer + '/7', { + method: 'GET' + }) + t.assert.ok(response.ok) + t.assert.strictEqual(response.status, 200) + const body = await response.text() + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) +}) diff --git a/services/slides/node_modules/fastify/test/encapsulated-child-logger-factory.test.js b/services/slides/node_modules/fastify/test/encapsulated-child-logger-factory.test.js new file mode 100644 index 0000000000000000000000000000000000000000..9ef7c9fd35bffa8a372d7fbea98c33f93eae16fe --- /dev/null +++ b/services/slides/node_modules/fastify/test/encapsulated-child-logger-factory.test.js @@ -0,0 +1,69 @@ +'use strict' + +const { test } = require('node:test') +const Fastify = require('..') +const fp = require('fastify-plugin') + +test('encapsulates an child logger factory', async t => { + t.plan(4) + + const fastify = Fastify() + fastify.register(async function (fastify) { + fastify.setChildLoggerFactory(function pluginFactory (logger, bindings, opts) { + const child = logger.child(bindings, opts) + child.customLog = function (message) { + t.assert.strictEqual(message, 'custom') + } + return child + }) + fastify.get('/encapsulated', async (req) => { + req.log.customLog('custom') + }) + }) + + fastify.setChildLoggerFactory(function globalFactory (logger, bindings, opts) { + const child = logger.child(bindings, opts) + child.globalLog = function (message) { + t.assert.strictEqual(message, 'global') + } + return child + }) + fastify.get('/not-encapsulated', async (req) => { + req.log.globalLog('global') + }) + + const res1 = await fastify.inject('/encapsulated') + t.assert.strictEqual(res1.statusCode, 200) + + const res2 = await fastify.inject('/not-encapsulated') + t.assert.strictEqual(res2.statusCode, 200) +}) + +test('child logger factory set on root scope when using fastify-plugin', async t => { + t.plan(4) + + const fastify = Fastify() + fastify.register(fp(async function (fastify) { + // Using fastify-plugin, the factory should be set on the root scope + fastify.setChildLoggerFactory(function pluginFactory (logger, bindings, opts) { + const child = logger.child(bindings, opts) + child.customLog = function (message) { + t.assert.strictEqual(message, 'custom') + } + return child + }) + fastify.get('/not-encapsulated-1', async (req) => { + req.log.customLog('custom') + }) + })) + + fastify.get('/not-encapsulated-2', async (req) => { + req.log.customLog('custom') + }) + + const res1 = await fastify.inject('/not-encapsulated-1') + t.assert.strictEqual(res1.statusCode, 200) + + const res2 = await fastify.inject('/not-encapsulated-2') + t.assert.strictEqual(res2.statusCode, 200) +}) diff --git a/services/slides/node_modules/fastify/test/encapsulated-error-handler.test.js b/services/slides/node_modules/fastify/test/encapsulated-error-handler.test.js new file mode 100644 index 0000000000000000000000000000000000000000..48c29d3e8ee77b6d7dfd0da10ed5a3080addf74c --- /dev/null +++ b/services/slides/node_modules/fastify/test/encapsulated-error-handler.test.js @@ -0,0 +1,237 @@ +'use strict' + +const { test } = require('node:test') +const Fastify = require('..') + +// Because of how error handlers wrap things, following the control flow can be tricky +// In this test file numbered comments indicate the order statements are expected to execute + +test('encapsulates an asynchronous error handler', async t => { + t.plan(3) + + const fastify = Fastify() + fastify.register(async function (fastify) { + fastify.setErrorHandler(async function a (err) { + // 3. the inner error handler catches the error, and throws a new error + t.assert.strictEqual(err.message, 'from_endpoint') + throw new Error('from_inner') + }) + fastify.get('/encapsulated', async () => { + // 2. the endpoint throws an error + throw new Error('from_endpoint') + }) + }) + + fastify.setErrorHandler(async function b (err) { + // 4. the outer error handler catches the error thrown by the inner error handler + t.assert.strictEqual(err.message, 'from_inner') + // 5. the outer error handler throws a new error + throw new Error('from_outer') + }) + + // 1. the endpoint is called + const res = await fastify.inject('/encapsulated') + // 6. the default error handler returns the error from the outer error handler + t.assert.strictEqual(res.json().message, 'from_outer') +}) + +// See discussion in https://github.com/fastify/fastify/pull/5222#discussion_r1432573655 +test('encapsulates a synchronous error handler', async t => { + t.plan(3) + + const fastify = Fastify() + fastify.register(async function (fastify) { + fastify.setErrorHandler(function a (err) { + // 3. the inner error handler catches the error, and throws a new error + t.assert.strictEqual(err.message, 'from_endpoint') + throw new Error('from_inner') + }) + fastify.get('/encapsulated', async () => { + // 2. the endpoint throws an error + throw new Error('from_endpoint') + }) + }) + + fastify.setErrorHandler(async function b (err) { + // 4. the outer error handler catches the error thrown by the inner error handler + t.assert.strictEqual(err.message, 'from_inner') + // 5. the outer error handler throws a new error + throw new Error('from_outer') + }) + + // 1. the endpoint is called + const res = await fastify.inject('/encapsulated') + // 6. the default error handler returns the error from the outer error handler + t.assert.strictEqual(res.json().message, 'from_outer') +}) + +test('onError hook nested', async t => { + t.plan(4) + + const fastify = Fastify() + fastify.register(async function (fastify) { + fastify.setErrorHandler(async function a (err) { + // 4. the inner error handler catches the error, and throws a new error + t.assert.strictEqual(err.message, 'from_endpoint') + throw new Error('from_inner') + }) + fastify.get('/encapsulated', async () => { + // 2. the endpoint throws an error + throw new Error('from_endpoint') + }) + }) + + fastify.setErrorHandler(async function b (err) { + // 5. the outer error handler catches the error thrown by the inner error handler + t.assert.strictEqual(err.message, 'from_inner') + // 6. the outer error handler throws a new error + throw new Error('from_outer') + }) + + fastify.addHook('onError', async function (request, reply, err) { + // 3. the hook receives the error + t.assert.strictEqual(err.message, 'from_endpoint') + }) + + // 1. the endpoint is called + const res = await fastify.inject('/encapsulated') + // 7. the default error handler returns the error from the outer error handler + t.assert.strictEqual(res.json().message, 'from_outer') +}) + +// See https://github.com/fastify/fastify/issues/5220 +test('encapuslates an error handler, for errors thrown in hooks', async t => { + t.plan(3) + + const fastify = Fastify() + fastify.register(async function (fastify) { + fastify.setErrorHandler(function a (err) { + // 3. the inner error handler catches the error, and throws a new error + t.assert.strictEqual(err.message, 'from_hook') + throw new Error('from_inner') + }) + fastify.addHook('onRequest', async () => { + // 2. the hook throws an error + throw new Error('from_hook') + }) + fastify.get('/encapsulated', async () => {}) + }) + + fastify.setErrorHandler(function b (err) { + // 4. the outer error handler catches the error thrown by the inner error handler + t.assert.strictEqual(err.message, 'from_inner') + // 5. the outer error handler throws a new error + throw new Error('from_outer') + }) + + // 1. the endpoint is called + const res = await fastify.inject('/encapsulated') + // 6. the default error handler returns the error from the outer error handler + t.assert.strictEqual(res.json().message, 'from_outer') +}) + +// See https://github.com/fastify/fastify/issues/5220 +test('encapuslates many synchronous error handlers that rethrow errors', async t => { + const DEPTH = 100 + t.plan(DEPTH + 2) + + /** + * This creates a very nested set of error handlers, that looks like: + * plugin + * - error handler + * - plugin + * - error handler + * - plugin + * ... {to DEPTH levels} + * - plugin + * - error handler + * - GET /encapsulated + */ + const createNestedRoutes = (fastify, depth) => { + if (depth < 0) { + throw new Error('Expected depth >= 0') + } else if (depth === 0) { + fastify.setErrorHandler(function a (err) { + // 3. innermost error handler catches the error, and throws a new error + t.assert.strictEqual(err.message, 'from_route') + throw new Error(`from_handler_${depth}`) + }) + fastify.get('/encapsulated', async () => { + // 2. the endpoint throws an error + throw new Error('from_route') + }) + } else { + fastify.setErrorHandler(function d (err) { + // 4 to {DEPTH+4}. error handlers each catch errors, and then throws a new error + t.assert.strictEqual(err.message, `from_handler_${depth - 1}`) + throw new Error(`from_handler_${depth}`) + }) + + fastify.register(async function (fastify) { + createNestedRoutes(fastify, depth - 1) + }) + } + } + + const fastify = Fastify() + createNestedRoutes(fastify, DEPTH) + + // 1. the endpoint is called + const res = await fastify.inject('/encapsulated') + // {DEPTH+5}. the default error handler returns the error from the outermost error handler + t.assert.strictEqual(res.json().message, `from_handler_${DEPTH}`) +}) + +// See https://github.com/fastify/fastify/issues/5220 +// This was not failing previously, but we want to make sure the behavior continues to work in the same way across async and sync handlers +// Plus, the current setup is somewhat fragile to tweaks to wrapThenable as that's what retries (by calling res.send(err) again) +test('encapuslates many asynchronous error handlers that rethrow errors', async t => { + const DEPTH = 100 + t.plan(DEPTH + 2) + + /** + * This creates a very nested set of error handlers, that looks like: + * plugin + * - error handler + * - plugin + * - error handler + * - plugin + * ... {to DEPTH levels} + * - plugin + * - error handler + * - GET /encapsulated + */ + const createNestedRoutes = (fastify, depth) => { + if (depth < 0) { + throw new Error('Expected depth >= 0') + } else if (depth === 0) { + fastify.setErrorHandler(async function a (err) { + // 3. innermost error handler catches the error, and throws a new error + t.assert.strictEqual(err.message, 'from_route') + throw new Error(`from_handler_${depth}`) + }) + fastify.get('/encapsulated', async () => { + // 2. the endpoint throws an error + throw new Error('from_route') + }) + } else { + fastify.setErrorHandler(async function m (err) { + // 4 to {DEPTH+4}. error handlers each catch errors, and then throws a new error + t.assert.strictEqual(err.message, `from_handler_${depth - 1}`) + throw new Error(`from_handler_${depth}`) + }) + + fastify.register(async function (fastify) { + createNestedRoutes(fastify, depth - 1) + }) + } + } + + const fastify = Fastify() + createNestedRoutes(fastify, DEPTH) + + // 1. the endpoint is called + const res = await fastify.inject('/encapsulated') + // {DEPTH+5}. the default error handler returns the error from the outermost error handler + t.assert.strictEqual(res.json().message, `from_handler_${DEPTH}`) +}) diff --git a/services/slides/node_modules/fastify/test/esm/errorCodes.test.mjs b/services/slides/node_modules/fastify/test/esm/errorCodes.test.mjs new file mode 100644 index 0000000000000000000000000000000000000000..cfeaf261355a5281d27ddba63ef9df7fc0251e08 --- /dev/null +++ b/services/slides/node_modules/fastify/test/esm/errorCodes.test.mjs @@ -0,0 +1,10 @@ +import { errorCodes } from '../../fastify.js' +import { test } from 'node:test' + +test('errorCodes in ESM', async t => { + // test a custom fastify error using errorCodes with ESM + const customError = errorCodes.FST_ERR_VALIDATION('custom error message') + t.assert.ok(typeof customError !== 'undefined') + t.assert.ok(customError instanceof errorCodes.FST_ERR_VALIDATION) + t.assert.strictEqual(customError.message, 'custom error message') +}) diff --git a/services/slides/node_modules/fastify/test/esm/esm.test.mjs b/services/slides/node_modules/fastify/test/esm/esm.test.mjs new file mode 100644 index 0000000000000000000000000000000000000000..49c6bfebf20cba7e875ed949a0f1951e68eec395 --- /dev/null +++ b/services/slides/node_modules/fastify/test/esm/esm.test.mjs @@ -0,0 +1,13 @@ +import { test } from 'node:test' +import Fastify from '../../fastify.js' + +test('esm support', async t => { + const fastify = Fastify() + + fastify.register(import('./plugin.mjs'), { foo: 'bar' }) + fastify.register(import('./other.mjs')) + + await fastify.ready() + + t.assert.strictEqual(fastify.foo, 'bar') +}) diff --git a/services/slides/node_modules/fastify/test/esm/index.test.js b/services/slides/node_modules/fastify/test/esm/index.test.js new file mode 100644 index 0000000000000000000000000000000000000000..f1cf2a72c53b4711fa9da93ade24c530dbb812da --- /dev/null +++ b/services/slides/node_modules/fastify/test/esm/index.test.js @@ -0,0 +1,8 @@ +'use strict' + +import('./named-exports.mjs') + .catch(err => { + process.nextTick(() => { + throw err + }) + }) diff --git a/services/slides/node_modules/fastify/test/esm/named-exports.mjs b/services/slides/node_modules/fastify/test/esm/named-exports.mjs new file mode 100644 index 0000000000000000000000000000000000000000..84323e924020cfbcd343b087c5255b9d9055dbc4 --- /dev/null +++ b/services/slides/node_modules/fastify/test/esm/named-exports.mjs @@ -0,0 +1,14 @@ +import { test } from 'node:test' +import { fastify } from '../../fastify.js' + +// This test is executed in index.test.js +test('named exports support', async t => { + const app = fastify() + + app.register(import('./plugin.mjs'), { foo: 'bar' }) + app.register(import('./other.mjs')) + + await app.ready() + + t.assert.strictEqual(app.foo, 'bar') +}) diff --git a/services/slides/node_modules/fastify/test/esm/other.mjs b/services/slides/node_modules/fastify/test/esm/other.mjs new file mode 100644 index 0000000000000000000000000000000000000000..448064cbaf4f286ff401c8728c9e5773073e43ed --- /dev/null +++ b/services/slides/node_modules/fastify/test/esm/other.mjs @@ -0,0 +1,8 @@ +// Imported in both index.test.js & esm.test.mjs +import { strictEqual } from 'node:assert' + +async function other (fastify, opts) { + strictEqual(fastify.foo, 'bar') +} + +export default other diff --git a/services/slides/node_modules/fastify/test/esm/plugin.mjs b/services/slides/node_modules/fastify/test/esm/plugin.mjs new file mode 100644 index 0000000000000000000000000000000000000000..bbead40f6d863eb7978583dadede6d2ea6bf7a14 --- /dev/null +++ b/services/slides/node_modules/fastify/test/esm/plugin.mjs @@ -0,0 +1,8 @@ +// Imported in both index.test.js & esm.test.mjs +async function plugin (fastify, opts) { + fastify.decorate('foo', opts.foo) +} + +plugin[Symbol.for('skip-override')] = true + +export default plugin diff --git a/services/slides/node_modules/fastify/test/fastify-instance.test.js b/services/slides/node_modules/fastify/test/fastify-instance.test.js new file mode 100644 index 0000000000000000000000000000000000000000..69996b6d40d07c1d4222d77b6688d381fb3d40a1 --- /dev/null +++ b/services/slides/node_modules/fastify/test/fastify-instance.test.js @@ -0,0 +1,300 @@ +'use strict' + +const { test } = require('node:test') +const Fastify = require('..') +const os = require('node:os') + +const { + kOptions, + kErrorHandler, + kChildLoggerFactory, + kState +} = require('../lib/symbols') + +const isIPv6Missing = !Object.values(os.networkInterfaces()).flat().some(({ family }) => family === 'IPv6') + +test('root fastify instance is an object', t => { + t.plan(1) + t.assert.strictEqual(typeof Fastify(), 'object') +}) + +test('fastify instance should contains ajv options', t => { + t.plan(1) + const fastify = Fastify({ + ajv: { + customOptions: { + nullable: false + } + } + }) + t.assert.deepStrictEqual(fastify[kOptions].ajv, { + customOptions: { + nullable: false + }, + plugins: [] + }) +}) + +test('fastify instance should contains ajv options.plugins nested arrays', t => { + t.plan(1) + const fastify = Fastify({ + ajv: { + customOptions: { + nullable: false + }, + plugins: [[]] + } + }) + t.assert.deepStrictEqual(fastify[kOptions].ajv, { + customOptions: { + nullable: false + }, + plugins: [[]] + }) +}) + +test('fastify instance get invalid ajv options', t => { + t.plan(1) + t.assert.throws(() => Fastify({ + ajv: { + customOptions: 8 + } + })) +}) + +test('fastify instance get invalid ajv options.plugins', t => { + t.plan(1) + t.assert.throws(() => Fastify({ + ajv: { + customOptions: {}, + plugins: 8 + } + })) +}) + +test('fastify instance should contain default errorHandler', t => { + t.plan(3) + const fastify = Fastify() + t.assert.ok(fastify[kErrorHandler].func instanceof Function) + t.assert.deepStrictEqual(fastify.errorHandler, fastify[kErrorHandler].func) + t.assert.deepStrictEqual(Object.getOwnPropertyDescriptor(fastify, 'errorHandler').set, undefined) +}) + +test('errorHandler in plugin should be separate from the external one', async t => { + t.plan(4) + const fastify = Fastify() + + fastify.register((instance, opts, done) => { + const inPluginErrHandler = (_, __, reply) => { + reply.send({ plugin: 'error-object' }) + } + + instance.setErrorHandler(inPluginErrHandler) + + t.assert.notDeepStrictEqual(instance.errorHandler, fastify.errorHandler) + t.assert.strictEqual(instance.errorHandler.name, 'bound inPluginErrHandler') + + done() + }) + + await fastify.ready() + + t.assert.ok(fastify[kErrorHandler].func instanceof Function) + t.assert.deepStrictEqual(fastify.errorHandler, fastify[kErrorHandler].func) +}) + +test('fastify instance should contain default childLoggerFactory', t => { + t.plan(3) + const fastify = Fastify() + t.assert.ok(fastify[kChildLoggerFactory] instanceof Function) + t.assert.deepStrictEqual(fastify.childLoggerFactory, fastify[kChildLoggerFactory]) + t.assert.deepStrictEqual(Object.getOwnPropertyDescriptor(fastify, 'childLoggerFactory').set, undefined) +}) + +test('childLoggerFactory in plugin should be separate from the external one', async t => { + t.plan(4) + const fastify = Fastify() + + fastify.register((instance, opts, done) => { + const inPluginLoggerFactory = function (logger, bindings, opts) { + return logger.child(bindings, opts) + } + + instance.setChildLoggerFactory(inPluginLoggerFactory) + + t.assert.notDeepStrictEqual(instance.childLoggerFactory, fastify.childLoggerFactory) + t.assert.strictEqual(instance.childLoggerFactory.name, 'inPluginLoggerFactory') + + done() + }) + + await fastify.ready() + + t.assert.ok(fastify[kChildLoggerFactory] instanceof Function) + t.assert.deepStrictEqual(fastify.childLoggerFactory, fastify[kChildLoggerFactory]) +}) + +test('ready should resolve in order when called multiply times (promises only)', async (t) => { + const app = Fastify() + const expectedOrder = [1, 2, 3, 4, 5] + const result = [] + + const promises = [1, 2, 3, 4, 5] + .map((id) => app.ready().then(() => result.push(id))) + + await Promise.all(promises) + + t.assert.deepStrictEqual(result, expectedOrder, 'Should resolve in order') +}) + +test('ready should reject in order when called multiply times (promises only)', async (t) => { + const app = Fastify() + const expectedOrder = [1, 2, 3, 4, 5] + const result = [] + + app.register((instance, opts, done) => { + setTimeout(() => done(new Error('test')), 500) + }) + + const promises = [1, 2, 3, 4, 5] + .map((id) => app.ready().catch(() => result.push(id))) + + await Promise.all(promises) + + t.assert.deepStrictEqual(result, expectedOrder, 'Should resolve in order') +}) + +test('ready should reject in order when called multiply times (callbacks only)', async (t) => { + const app = Fastify() + const expectedOrder = [1, 2, 3, 4, 5] + const result = [] + + app.register((instance, opts, done) => { + setTimeout(() => done(new Error('test')), 500) + }) + + expectedOrder.map((id) => app.ready(() => result.push(id))) + + await app.ready().catch(err => { + t.assert.strictEqual(err.message, 'test') + }) + + t.assert.deepStrictEqual(result, expectedOrder, 'Should resolve in order') +}) + +test('ready should resolve in order when called multiply times (callbacks only)', async (t) => { + const app = Fastify() + const expectedOrder = [1, 2, 3, 4, 5] + const result = [] + + expectedOrder.map((id) => app.ready(() => result.push(id))) + + await app.ready() + + t.assert.deepStrictEqual(result, expectedOrder, 'Should resolve in order') +}) + +test('ready should resolve in order when called multiply times (mixed)', async (t) => { + const app = Fastify() + const expectedOrder = [1, 2, 3, 4, 5, 6] + const result = [] + + for (const order of expectedOrder) { + if (order % 2) { + app.ready(() => result.push(order)) + } else { + app.ready().then(() => result.push(order)) + } + } + + await app.ready() + + t.assert.deepStrictEqual(result, expectedOrder, 'Should resolve in order') +}) + +test('ready should reject in order when called multiply times (mixed)', async (t) => { + const app = Fastify() + const expectedOrder = [1, 2, 3, 4, 5, 6] + const result = [] + + app.register((instance, opts, done) => { + setTimeout(() => done(new Error('test')), 500) + }) + + for (const order of expectedOrder) { + if (order % 2) { + app.ready(() => result.push(order)) + } else { + app.ready().then(null, () => result.push(order)) + } + } + + await app.ready().catch(err => { + t.assert.strictEqual(err.message, 'test') + }) + + t.assert.deepStrictEqual(result, expectedOrder, 'Should resolve in order') +}) + +test('ready should resolve in order when called multiply times (mixed)', async (t) => { + const app = Fastify() + const expectedOrder = [1, 2, 3, 4, 5, 6] + const result = [] + + for (const order of expectedOrder) { + if (order % 2) { + app.ready().then(() => result.push(order)) + } else { + app.ready(() => result.push(order)) + } + } + + await app.ready() + + t.assert.deepStrictEqual(result, expectedOrder, 'Should resolve in order') +}) + +test('fastify instance should contains listeningOrigin property (with port and host)', async t => { + t.plan(1) + const port = 3000 + const host = '127.0.0.1' + const fastify = Fastify() + await fastify.listen({ port, host }) + t.assert.deepStrictEqual(fastify.listeningOrigin, `http://${host}:${port}`) + await fastify.close() +}) + +test('fastify instance should contains listeningOrigin property (with port and https)', async t => { + t.plan(1) + const port = 3000 + const host = '127.0.0.1' + const fastify = Fastify({ https: {} }) + await fastify.listen({ port, host }) + t.assert.deepStrictEqual(fastify.listeningOrigin, `https://${host}:${port}`) + await fastify.close() +}) + +test('fastify instance should contains listeningOrigin property (unix socket)', { skip: os.platform() === 'win32' }, async t => { + const fastify = Fastify() + const path = `fastify.${Date.now()}.sock` + await fastify.listen({ path }) + t.assert.deepStrictEqual(fastify.listeningOrigin, path) + await fastify.close() +}) + +test('fastify instance should contains listeningOrigin property (IPv6)', { skip: isIPv6Missing }, async t => { + t.plan(1) + const port = 3000 + const host = '::1' + const fastify = Fastify() + await fastify.listen({ port, host }) + t.assert.deepStrictEqual(fastify.listeningOrigin, `http://[::1]:${port}`) + await fastify.close() +}) + +test('fastify instance should ensure ready promise cleanup on ready', async t => { + t.plan(1) + const fastify = Fastify() + await fastify.ready() + t.assert.strictEqual(fastify[kState].readyResolver, null) +}) diff --git a/services/slides/node_modules/fastify/test/find-route.test.js b/services/slides/node_modules/fastify/test/find-route.test.js new file mode 100644 index 0000000000000000000000000000000000000000..9e39ef49055cda9a1ab6b7f59cdd5ea24542dcd4 --- /dev/null +++ b/services/slides/node_modules/fastify/test/find-route.test.js @@ -0,0 +1,152 @@ +'use strict' + +const { test } = require('node:test') +const Fastify = require('..') +const fastifyPlugin = require('fastify-plugin') + +test('findRoute should return null when route cannot be found due to a different method', t => { + t.plan(1) + const fastify = Fastify() + + fastify.get('/artists/:artistId', { + schema: { + params: { artistId: { type: 'integer' } } + }, + handler: (req, reply) => reply.send(typeof req.params.artistId) + }) + + t.assert.strictEqual(fastify.findRoute({ + method: 'POST', + url: '/artists/:artistId' + }), null) +}) + +test('findRoute should return an immutable route to avoid leaking and runtime route modifications', t => { + t.plan(1) + const fastify = Fastify() + + fastify.get('/artists/:artistId', { + schema: { + params: { artistId: { type: 'integer' } } + }, + handler: (req, reply) => reply.send(typeof req.params.artistId) + }) + + let route = fastify.findRoute({ + method: 'GET', + url: '/artists/:artistId' + }) + + route.params = { + ...route.params, + id: ':id' + } + + route = fastify.findRoute({ + method: 'GET', + url: '/artists/:artistId' + }) + + t.assert.strictEqual(route.params.artistId, ':artistId') +}) + +test('findRoute should return null when when url is not passed', t => { + t.plan(1) + const fastify = Fastify() + + fastify.get('/artists/:artistId', { + schema: { + params: { artistId: { type: 'integer' } } + }, + handler: (req, reply) => reply.send(typeof req.params.artistId) + }) + + t.assert.strictEqual(fastify.findRoute({ + method: 'POST' + }), null) +}) + +test('findRoute should return null when route cannot be found due to a different path', t => { + t.plan(1) + const fastify = Fastify() + + fastify.get('/artists/:artistId', { + schema: { + params: { artistId: { type: 'integer' } } + }, + handler: (req, reply) => reply.send(typeof req.params.artistId) + }) + + t.assert.strictEqual(fastify.findRoute({ + method: 'GET', + url: '/books/:bookId' + }), null) +}) + +test('findRoute should return the route when found', t => { + t.plan(1) + const fastify = Fastify() + + const handler = (req, reply) => reply.send(typeof req.params.artistId) + + fastify.get('/artists/:artistId', { + schema: { + params: { artistId: { type: 'integer' } } + }, + handler + }) + + const route = fastify.findRoute({ + method: 'GET', + url: '/artists/:artistId' + }) + t.assert.strictEqual(route.params.artistId, ':artistId') +}) + +test('findRoute should work correctly when used within plugins', (t, done) => { + t.plan(1) + const fastify = Fastify() + const handler = (req, reply) => reply.send(typeof req.params.artistId) + fastify.get('/artists/:artistId', { + schema: { + params: { artistId: { type: 'integer' } } + }, + handler + }) + + function validateRoutePlugin (instance, opts, done) { + const validateParams = function () { + return instance.findRoute({ + method: 'GET', + url: '/artists/:artistId' + }) !== null + } + instance.decorate('validateRoutes', { validateParams }) + done() + } + + fastify.register(fastifyPlugin(validateRoutePlugin)) + + fastify.ready(() => { + t.assert.strictEqual(fastify.validateRoutes.validateParams(), true) + done() + }) +}) + +test('findRoute should not expose store', t => { + t.plan(1) + const fastify = Fastify() + + fastify.get('/artists/:artistId', { + schema: { + params: { artistId: { type: 'integer' } } + }, + handler: (req, reply) => reply.send(typeof req.params.artistId) + }) + + const route = fastify.findRoute({ + method: 'GET', + url: '/artists/:artistId' + }) + t.assert.strictEqual(route.store, undefined) +}) diff --git a/services/slides/node_modules/fastify/test/fluent-schema.test.js b/services/slides/node_modules/fastify/test/fluent-schema.test.js new file mode 100644 index 0000000000000000000000000000000000000000..4e6d7ba46e1403e3da65ea8ec5a96bf12088ecea --- /dev/null +++ b/services/slides/node_modules/fastify/test/fluent-schema.test.js @@ -0,0 +1,209 @@ +'use strict' + +const { test } = require('node:test') +const Fastify = require('..') +const S = require('fluent-json-schema') + +test('use fluent-json-schema object', async (t) => { + t.plan(10) + const fastify = Fastify() + + fastify.post('/:id', { + handler: (req, reply) => { reply.send({ name: 'a', surname: 'b', dateOfBirth: '01-01-2020' }) }, + schema: { + params: S.object().prop('id', S.integer().minimum(42)), + headers: S.object().prop('x-custom', S.string().format('email')), + query: S.object().prop('surname', S.string().required()), + body: S.object().prop('name', S.string().required()), + response: { + 200: S.object() + .prop('name', S.string()) + .prop('surname', S.string()) + } + } + }) + + const res1 = await fastify.inject({ + method: 'POST', + url: '/1', + headers: { 'x-custom': 'me@me.me' }, + query: { surname: 'bar' }, + payload: { name: 'foo' } + }) + t.assert.strictEqual(res1.statusCode, 400) + t.assert.deepStrictEqual(res1.json(), { statusCode: 400, code: 'FST_ERR_VALIDATION', error: 'Bad Request', message: 'params/id must be >= 42' }) + + // check header + const res2 = await fastify.inject({ + method: 'POST', + url: '/42', + headers: { 'x-custom': 'invalid' }, + query: { surname: 'bar' }, + payload: { name: 'foo' } + }) + t.assert.strictEqual(res2.statusCode, 400) + t.assert.deepStrictEqual(res2.json(), { statusCode: 400, code: 'FST_ERR_VALIDATION', error: 'Bad Request', message: 'headers/x-custom must match format "email"' }) + + // check query + const res3 = await fastify.inject({ + method: 'POST', + url: '/42', + headers: { 'x-custom': 'me@me.me' }, + query: { }, + payload: { name: 'foo' } + }) + t.assert.strictEqual(res3.statusCode, 400) + t.assert.deepStrictEqual(res3.json(), { statusCode: 400, code: 'FST_ERR_VALIDATION', error: 'Bad Request', message: 'querystring must have required property \'surname\'' }) + + // check body + const res4 = await fastify.inject({ + method: 'POST', + url: '/42', + headers: { 'x-custom': 'me@me.me' }, + query: { surname: 'bar' }, + payload: { name: [1, 2, 3] } + }) + t.assert.strictEqual(res4.statusCode, 400) + t.assert.deepStrictEqual(res4.json(), { statusCode: 400, code: 'FST_ERR_VALIDATION', error: 'Bad Request', message: 'body/name must be string' }) + + // check response + const res5 = await fastify.inject({ + method: 'POST', + url: '/42', + headers: { 'x-custom': 'me@me.me' }, + query: { surname: 'bar' }, + payload: { name: 'foo' } + }) + t.assert.strictEqual(res5.statusCode, 200) + t.assert.deepStrictEqual(res5.json(), { name: 'a', surname: 'b' }) +}) + +test('use complex fluent-json-schema object', (t, done) => { + t.plan(1) + const fastify = Fastify() + + const addressSchema = S.object() + .id('#address') + .prop('line1').required() + .prop('line2') + .prop('country').required() + .prop('city').required() + .prop('zipcode').required() + + const commonSchemas = S.object() + .id('https://fastify/demo') + .definition('addressSchema', addressSchema) + + fastify.addSchema(commonSchemas) + + const bodyJsonSchema = S.object() + .prop('residence', S.ref('https://fastify/demo#address')).required() + .prop('office', S.ref('https://fastify/demo#/definitions/addressSchema')).required() + + fastify.post('/the/url', { schema: { body: bodyJsonSchema } }, () => { }) + fastify.ready(err => { + t.assert.ifError(err) + done() + }) +}) + +test('use fluent schema and plain JSON schema', (t, done) => { + t.plan(1) + + const fastify = Fastify() + + const addressSchema = S.object() + .id('#address') + .prop('line1').required() + .prop('line2') + .prop('country').required() + .prop('city').required() + .prop('zipcode').required() + + const commonSchemas = S.object() + .id('https://fastify/demo') + .definition('addressSchema', addressSchema) + + const sharedAddressSchema = { + $id: 'sharedAddress', + type: 'object', + required: ['line1', 'country', 'city', 'zipcode'], + properties: { + line1: { type: 'string' }, + line2: { type: 'string' }, + country: { type: 'string' }, + city: { type: 'string' }, + zipcode: { type: 'string' } + } + } + + fastify.addSchema(commonSchemas) + fastify.addSchema(sharedAddressSchema) + + const bodyJsonSchema = S.object() + .prop('residence', S.ref('https://fastify/demo#address')).required() + .prop('office', S.ref('https://fastify/demo#/definitions/addressSchema')).required() + + fastify.post('/the/url', { schema: { body: bodyJsonSchema } }, () => { }) + fastify.ready(err => { + t.assert.ifError(err) + done() + }) +}) + +test('Should call valueOf internally', (t, done) => { + t.plan(1) + + const fastify = new Fastify() + + const addressSchema = S.object() + .id('#address') + .prop('line1').required() + .prop('line2') + .prop('country').required() + .prop('city').required() + .prop('zipcode').required() + + const commonSchemas = S.object() + .id('https://fastify/demo') + .definition('addressSchema', addressSchema) + + fastify.addSchema(commonSchemas) + + fastify.route({ + method: 'POST', + url: '/query', + handler: () => {}, + schema: { + query: S.object().prop('hello', S.string()).required(), + body: S.object().prop('hello', S.string()).required(), + params: S.object().prop('hello', S.string()).required(), + headers: S.object().prop('hello', S.string()).required(), + response: { + 200: S.object().prop('hello', S.string()).required(), + 201: S.object().prop('hello', S.string()).required() + } + } + }) + + fastify.route({ + method: 'POST', + url: '/querystring', + handler: () => {}, + schema: { + querystring: S.object().prop('hello', S.string()).required(), + body: S.object().prop('hello', S.string()).required(), + params: S.object().prop('hello', S.string()).required(), + headers: S.object().prop('hello', S.string()).required(), + response: { + 200: S.object().prop('hello', S.string()).required(), + 201: S.object().prop('hello', S.string()).required() + } + } + }) + + fastify.ready(err => { + t.assert.ifError(err) + done() + }) +}) diff --git a/services/slides/node_modules/fastify/test/genReqId.test.js b/services/slides/node_modules/fastify/test/genReqId.test.js new file mode 100644 index 0000000000000000000000000000000000000000..682935ba4ac9052b1f5db64ce9b662146d52cc78 --- /dev/null +++ b/services/slides/node_modules/fastify/test/genReqId.test.js @@ -0,0 +1,426 @@ +'use strict' + +const { Readable } = require('node:stream') +const { test } = require('node:test') +const fp = require('fastify-plugin') +const Fastify = require('..') + +test('Should accept a custom genReqId function', (t, done) => { + t.plan(4) + + const fastify = Fastify({ + genReqId: function (req) { + return 'a' + } + }) + + t.after(() => fastify.close()) + fastify.get('/', (req, reply) => { + t.assert.ok(req.id) + reply.send({ id: req.id }) + }) + + fastify.listen({ port: 0 }, err => { + t.assert.ifError(err) + fastify.inject({ + method: 'GET', + url: `http://localhost:${fastify.server.address().port}` + }, (err, res) => { + t.assert.ifError(err) + const payload = JSON.parse(res.payload) + t.assert.strictEqual(payload.id, 'a') + done() + }) + }) +}) + +test('Custom genReqId function gets raw request as argument', (t, done) => { + t.plan(9) + + const REQUEST_ID = 'REQ-1234' + + const fastify = Fastify({ + genReqId: function (req) { + t.assert.strictEqual('id' in req, false) + t.assert.strictEqual('raw' in req, false) + t.assert.ok(req instanceof Readable) + // http.IncomingMessage does have `rawHeaders` property, but FastifyRequest does not + const index = req.rawHeaders.indexOf('x-request-id') + const xReqId = req.rawHeaders[index + 1] + t.assert.strictEqual(xReqId, REQUEST_ID) + t.assert.strictEqual(req.headers['x-request-id'], REQUEST_ID) + return xReqId + } + }) + t.after(() => fastify.close()) + + fastify.get('/', (req, reply) => { + t.assert.strictEqual(req.id, REQUEST_ID) + reply.send({ id: req.id }) + }) + + fastify.listen({ port: 0 }, err => { + t.assert.ifError(err) + fastify.inject({ + method: 'GET', + headers: { + 'x-request-id': REQUEST_ID + }, + url: `http://localhost:${fastify.server.address().port}` + }, (err, res) => { + t.assert.ifError(err) + const payload = JSON.parse(res.payload) + t.assert.strictEqual(payload.id, REQUEST_ID) + done() + }) + }) +}) + +test('Should handle properly requestIdHeader option', t => { + t.plan(4) + + t.assert.strictEqual(Fastify({ requestIdHeader: '' }).initialConfig.requestIdHeader, false) + t.assert.strictEqual(Fastify({ requestIdHeader: false }).initialConfig.requestIdHeader, false) + t.assert.strictEqual(Fastify({ requestIdHeader: true }).initialConfig.requestIdHeader, 'request-id') + t.assert.strictEqual(Fastify({ requestIdHeader: 'x-request-id' }).initialConfig.requestIdHeader, 'x-request-id') +}) + +test('Should accept option to set genReqId with setGenReqId option', (t, done) => { + t.plan(9) + + const fastify = Fastify({ + genReqId: function (req) { + return 'base' + } + }) + + t.after(() => fastify.close()) + + fastify.register(function (instance, opts, next) { + instance.setGenReqId(function (req) { + return 'foo' + }) + instance.get('/', (req, reply) => { + t.assert.ok(req.id) + reply.send({ id: req.id }) + }) + next() + }, { prefix: 'foo' }) + + fastify.register(function (instance, opts, next) { + instance.setGenReqId(function (req) { + return 'bar' + }) + instance.get('/', (req, reply) => { + t.assert.ok(req.id) + reply.send({ id: req.id }) + }) + next() + }, { prefix: 'bar' }) + + fastify.get('/', (req, reply) => { + t.assert.ok(req.id) + reply.send({ id: req.id }) + }) + + let pending = 3 + + function completed () { + if (--pending === 0) { + done() + } + } + + fastify.inject({ + method: 'GET', + url: '/' + }, (err, res) => { + t.assert.ifError(err) + const payload = JSON.parse(res.payload) + t.assert.strictEqual(payload.id, 'base') + completed() + }) + + fastify.inject({ + method: 'GET', + url: '/foo' + }, (err, res) => { + t.assert.ifError(err) + const payload = JSON.parse(res.payload) + t.assert.strictEqual(payload.id, 'foo') + completed() + }) + + fastify.inject({ + method: 'GET', + url: '/bar' + }, (err, res) => { + t.assert.ifError(err) + const payload = JSON.parse(res.payload) + t.assert.strictEqual(payload.id, 'bar') + completed() + }) +}) + +test('Should encapsulate setGenReqId', (t, done) => { + t.plan(12) + + const fastify = Fastify({ + genReqId: function (req) { + return 'base' + } + }) + + t.after(() => fastify.close()) + const bazInstance = function (instance, opts, next) { + instance.register(barInstance, { prefix: 'baz' }) + + instance.setGenReqId(function (req) { + return 'baz' + }) + instance.get('/', (req, reply) => { + t.assert.ok(req.id) + reply.send({ id: req.id }) + }) + next() + } + + const barInstance = function (instance, opts, next) { + instance.setGenReqId(function (req) { + return 'bar' + }) + instance.get('/', (req, reply) => { + t.assert.ok(req.id) + reply.send({ id: req.id }) + }) + next() + } + + const fooInstance = function (instance, opts, next) { + instance.register(bazInstance, { prefix: 'baz' }) + instance.register(barInstance, { prefix: 'bar' }) + + instance.setGenReqId(function (req) { + return 'foo' + }) + + instance.get('/', (req, reply) => { + t.assert.ok(req.id) + reply.send({ id: req.id }) + }) + next() + } + + fastify.register(fooInstance, { prefix: 'foo' }) + + fastify.get('/', (req, reply) => { + t.assert.ok(req.id) + reply.send({ id: req.id }) + }) + + let pending = 4 + + function completed () { + if (--pending === 0) { + done() + } + } + + fastify.inject({ + method: 'GET', + url: '/' + }, (err, res) => { + t.assert.ifError(err) + const payload = JSON.parse(res.payload) + t.assert.strictEqual(payload.id, 'base') + completed() + }) + + fastify.inject({ + method: 'GET', + url: '/foo' + }, (err, res) => { + t.assert.ifError(err) + const payload = JSON.parse(res.payload) + t.assert.strictEqual(payload.id, 'foo') + completed() + }) + + fastify.inject({ + method: 'GET', + url: '/foo/bar' + }, (err, res) => { + t.assert.ifError(err) + const payload = JSON.parse(res.payload) + t.assert.strictEqual(payload.id, 'bar') + completed() + }) + + fastify.inject({ + method: 'GET', + url: '/foo/baz' + }, (err, res) => { + t.assert.ifError(err) + const payload = JSON.parse(res.payload) + t.assert.strictEqual(payload.id, 'baz') + completed() + }) +}) + +test('Should not alter parent of genReqId', (t, done) => { + t.plan(6) + + const fastify = Fastify() + t.after(() => fastify.close()) + const fooInstance = function (instance, opts, next) { + instance.setGenReqId(function (req) { + return 'foo' + }) + + instance.get('/', (req, reply) => { + t.assert.ok(req.id) + reply.send({ id: req.id }) + }) + next() + } + + fastify.register(fooInstance, { prefix: 'foo' }) + + fastify.get('/', (req, reply) => { + t.assert.ok(req.id) + reply.send({ id: req.id }) + }) + + let pending = 2 + + function completed () { + if (--pending === 0) { + done() + } + } + + fastify.inject({ + method: 'GET', + url: '/' + }, (err, res) => { + t.assert.ifError(err) + const payload = JSON.parse(res.payload) + t.assert.strictEqual(payload.id, 'req-1') + completed() + }) + + fastify.inject({ + method: 'GET', + url: '/foo' + }, (err, res) => { + t.assert.ifError(err) + const payload = JSON.parse(res.payload) + t.assert.strictEqual(payload.id, 'foo') + completed() + }) +}) + +test('Should have child instance user parent genReqId', (t, done) => { + t.plan(6) + + const fastify = Fastify({ + genReqId: function (req) { + return 'foo' + } + }) + t.after(() => fastify.close()) + + const fooInstance = function (instance, opts, next) { + instance.get('/', (req, reply) => { + t.assert.ok(req.id) + reply.send({ id: req.id }) + }) + next() + } + + fastify.register(fooInstance, { prefix: 'foo' }) + + fastify.get('/', (req, reply) => { + t.assert.ok(req.id) + reply.send({ id: req.id }) + }) + + let pending = 2 + + function completed () { + if (--pending === 0) { + done() + } + } + + fastify.inject({ + method: 'GET', + url: '/' + }, (err, res) => { + t.assert.ifError(err) + const payload = JSON.parse(res.payload) + t.assert.strictEqual(payload.id, 'foo') + completed() + }) + + fastify.inject({ + method: 'GET', + url: '/foo' + }, (err, res) => { + t.assert.ifError(err) + const payload = JSON.parse(res.payload) + t.assert.strictEqual(payload.id, 'foo') + completed() + }) +}) + +test('genReqId set on root scope when using fastify-plugin', (t, done) => { + t.plan(6) + + const fastify = Fastify() + t.after(() => fastify.close()) + + fastify.register(fp(function (fastify, options, done) { + fastify.setGenReqId(function (req) { + return 'not-encapsulated' + }) + fastify.get('/not-encapsulated-1', (req, reply) => { + t.assert.ok(req.id) + reply.send({ id: req.id }) + }) + done() + })) + + fastify.get('/not-encapsulated-2', (req, reply) => { + t.assert.ok(req.id) + reply.send({ id: req.id }) + }) + + let pending = 2 + + function completed () { + if (--pending === 0) { + done() + } + } + + fastify.inject({ + method: 'GET', + url: '/not-encapsulated-1' + }, (err, res) => { + t.assert.ifError(err) + const payload = JSON.parse(res.payload) + t.assert.strictEqual(payload.id, 'not-encapsulated') + completed() + }) + + fastify.inject({ + method: 'GET', + url: '/not-encapsulated-2' + }, (err, res) => { + t.assert.ifError(err) + const payload = JSON.parse(res.payload) + t.assert.strictEqual(payload.id, 'not-encapsulated') + completed() + }) +}) diff --git a/services/slides/node_modules/fastify/test/handler-context.test.js b/services/slides/node_modules/fastify/test/handler-context.test.js new file mode 100644 index 0000000000000000000000000000000000000000..868377341cfc75992a484cc5045ccb5181f82950 --- /dev/null +++ b/services/slides/node_modules/fastify/test/handler-context.test.js @@ -0,0 +1,45 @@ +'use strict' +const { test } = require('node:test') +const { kRouteContext } = require('../lib/symbols') +const fastify = require('..') + +test('handlers receive correct `this` context', async (t) => { + t.plan(4) + + // simulate plugin that uses fastify-plugin + const plugin = function (instance, opts, done) { + instance.decorate('foo', 'foo') + done() + } + plugin[Symbol.for('skip-override')] = true + + const instance = fastify() + instance.register(plugin) + + instance.get('/', function (req, reply) { + t.assert.ok(this.foo) + t.assert.strictEqual(this.foo, 'foo') + reply.send() + }) + + await instance.inject('/') + + t.assert.ok(instance.foo) + t.assert.strictEqual(instance.foo, 'foo') +}) + +test('handlers have access to the internal context', async (t) => { + t.plan(5) + + const instance = fastify() + instance.get('/', { config: { foo: 'bar' } }, function (req, reply) { + t.assert.ok(reply[kRouteContext]) + t.assert.ok(reply[kRouteContext].config) + t.assert.ok(typeof reply[kRouteContext].config, Object) + t.assert.ok(reply[kRouteContext].config.foo) + t.assert.strictEqual(reply[kRouteContext].config.foo, 'bar') + reply.send() + }) + + await instance.inject('/') +}) diff --git a/services/slides/node_modules/fastify/test/handler-timeout.test.js b/services/slides/node_modules/fastify/test/handler-timeout.test.js new file mode 100644 index 0000000000000000000000000000000000000000..60ac3847714171ca4a5ce0137dfb9c37bfbf8a2c --- /dev/null +++ b/services/slides/node_modules/fastify/test/handler-timeout.test.js @@ -0,0 +1,367 @@ +'use strict' + +const { test } = require('node:test') +const net = require('node:net') +const Fastify = require('..') +const { Readable } = require('node:stream') +const { kTimeoutTimer, kOnAbort } = require('../lib/symbols') + +// --- Option validation --- + +test('server-level handlerTimeout defaults to 0 in initialConfig', t => { + t.plan(1) + const fastify = Fastify() + t.assert.strictEqual(fastify.initialConfig.handlerTimeout, 0) +}) + +test('server-level handlerTimeout: 5000 is accepted and exposed in initialConfig', t => { + t.plan(1) + const fastify = Fastify({ handlerTimeout: 5000 }) + t.assert.strictEqual(fastify.initialConfig.handlerTimeout, 5000) +}) + +test('route-level handlerTimeout rejects invalid values', async t => { + const fastify = Fastify() + + t.assert.throws(() => { + fastify.get('/a', { handlerTimeout: 'fast' }, async () => 'ok') + }, { code: 'FST_ERR_ROUTE_HANDLER_TIMEOUT_OPTION_NOT_INT' }) + + t.assert.throws(() => { + fastify.get('/b', { handlerTimeout: -1 }, async () => 'ok') + }, { code: 'FST_ERR_ROUTE_HANDLER_TIMEOUT_OPTION_NOT_INT' }) + + t.assert.throws(() => { + fastify.get('/c', { handlerTimeout: 1.5 }, async () => 'ok') + }, { code: 'FST_ERR_ROUTE_HANDLER_TIMEOUT_OPTION_NOT_INT' }) + + t.assert.throws(() => { + fastify.get('/d', { handlerTimeout: 0 }, async () => 'ok') + }, { code: 'FST_ERR_ROUTE_HANDLER_TIMEOUT_OPTION_NOT_INT' }) +}) + +// --- Lazy signal without handlerTimeout --- + +test('when handlerTimeout is 0 (default), request.signal is lazily created', async t => { + t.plan(3) + const fastify = Fastify() + + fastify.get('/', async (request) => { + const signal = request.signal + t.assert.ok(signal instanceof AbortSignal) + t.assert.strictEqual(signal.aborted, false) + return { ok: true } + }) + + const res = await fastify.inject({ method: 'GET', url: '/' }) + t.assert.strictEqual(res.statusCode, 200) +}) + +test('client disconnect aborts lazily created signal (no handlerTimeout)', async t => { + t.plan(1) + + const fastify = Fastify() + let signalAborted = false + + fastify.get('/', async (request) => { + await new Promise((resolve) => { + request.signal.addEventListener('abort', () => { + signalAborted = true + resolve() + }) + }) + return 'should not reach' + }) + + await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const address = fastify.server.address() + await new Promise((resolve) => { + const client = net.connect(address.port, () => { + client.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') + setTimeout(() => { + client.destroy() + setTimeout(resolve, 100) + }, 50) + }) + }) + + t.assert.strictEqual(signalAborted, true) +}) + +// --- Basic timeout behavior --- + +test('slow handler returns 503 with FST_ERR_HANDLER_TIMEOUT', async t => { + t.plan(2) + const fastify = Fastify() + + fastify.get('/', { handlerTimeout: 50 }, async () => { + await new Promise(resolve => setTimeout(resolve, 500)) + return 'too late' + }) + + const res = await fastify.inject({ method: 'GET', url: '/' }) + t.assert.strictEqual(res.statusCode, 503) + t.assert.strictEqual(JSON.parse(res.payload).code, 'FST_ERR_HANDLER_TIMEOUT') +}) + +test('fast handler completes normally with 200', async t => { + t.plan(2) + const fastify = Fastify() + + fastify.get('/', { handlerTimeout: 5000 }, async () => { + return { hello: 'world' } + }) + + const res = await fastify.inject({ method: 'GET', url: '/' }) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) +}) + +// --- Per-route override --- + +test('route-level handlerTimeout overrides server default', async t => { + t.plan(4) + const fastify = Fastify({ handlerTimeout: 5000 }) + + fastify.get('/slow', { handlerTimeout: 50 }, async () => { + await new Promise(resolve => setTimeout(resolve, 500)) + return 'too late' + }) + + fastify.get('/fast', async () => { + return { ok: true } + }) + + const resSlow = await fastify.inject({ method: 'GET', url: '/slow' }) + t.assert.strictEqual(resSlow.statusCode, 503) + t.assert.strictEqual(JSON.parse(resSlow.payload).code, 'FST_ERR_HANDLER_TIMEOUT') + + const resFast = await fastify.inject({ method: 'GET', url: '/fast' }) + t.assert.strictEqual(resFast.statusCode, 200) + t.assert.deepStrictEqual(JSON.parse(resFast.payload), { ok: true }) +}) + +// --- request.signal behavior --- + +test('request.signal is an AbortSignal when handlerTimeout > 0', async t => { + t.plan(2) + const fastify = Fastify() + + fastify.get('/', { handlerTimeout: 5000 }, async (request) => { + t.assert.ok(request.signal instanceof AbortSignal) + t.assert.strictEqual(request.signal.aborted, false) + return 'ok' + }) + + await fastify.inject({ method: 'GET', url: '/' }) +}) + +test('request.signal aborts when timeout fires with reason', async t => { + t.plan(2) + const fastify = Fastify() + + let signalReason = null + fastify.get('/', { handlerTimeout: 50 }, async (request) => { + request.signal.addEventListener('abort', () => { + signalReason = request.signal.reason + }) + await new Promise(resolve => setTimeout(resolve, 500)) + return 'too late' + }) + + await fastify.inject({ method: 'GET', url: '/' }) + t.assert.ok(signalReason !== null) + t.assert.strictEqual(signalReason.code, 'FST_ERR_HANDLER_TIMEOUT') +}) + +// --- Streaming response --- + +test('streaming response: timer clears when response finishes', async t => { + t.plan(1) + + const fastify = Fastify() + fastify.get('/', { handlerTimeout: 5000 }, async (request, reply) => { + const stream = new Readable({ + read () { + this.push('hello') + this.push(null) + } + }) + reply.type('text/plain').send(stream) + return reply + }) + + await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const address = fastify.server.address() + const res = await fetch(`http://localhost:${address.port}/`) + t.assert.strictEqual(res.status, 200) +}) + +// --- SSE with reply.hijack() --- + +test('reply.hijack() clears timeout timer', async t => { + t.plan(1) + + const fastify = Fastify() + fastify.get('/', { handlerTimeout: 100 }, async (request, reply) => { + reply.hijack() + // Write after the original timeout would have fired + await new Promise(resolve => setTimeout(resolve, 200)) + reply.raw.writeHead(200, { 'Content-Type': 'text/plain' }) + reply.raw.end('hijacked response') + }) + + await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const address = fastify.server.address() + const res = await fetch(`http://localhost:${address.port}/`) + t.assert.strictEqual(res.status, 200) +}) + +// --- Error handler integration --- + +test('route-level errorHandler receives FST_ERR_HANDLER_TIMEOUT', async t => { + t.plan(3) + const fastify = Fastify() + + fastify.get('/', { + handlerTimeout: 50, + errorHandler: (error, request, reply) => { + t.assert.strictEqual(error.code, 'FST_ERR_HANDLER_TIMEOUT') + reply.code(504).send({ custom: 'timeout' }) + } + }, async () => { + await new Promise(resolve => setTimeout(resolve, 500)) + return 'too late' + }) + + const res = await fastify.inject({ method: 'GET', url: '/' }) + t.assert.strictEqual(res.statusCode, 504) + t.assert.deepStrictEqual(JSON.parse(res.payload), { custom: 'timeout' }) +}) + +// --- Timer cleanup / no leaks --- + +test('timer is cleaned up after fast response (no leak)', async t => { + t.plan(3) + const fastify = Fastify() + + let capturedRequest + fastify.get('/', { handlerTimeout: 60000 }, async (request) => { + capturedRequest = request + return 'fast' + }) + + const res = await fastify.inject({ method: 'GET', url: '/' }) + t.assert.strictEqual(res.statusCode, 200) + // Timer and listener should be cleaned up + t.assert.strictEqual(capturedRequest[kTimeoutTimer], null) + t.assert.strictEqual(capturedRequest[kOnAbort], null) +}) + +// --- routeOptions exposure --- + +test('request.routeOptions.handlerTimeout reflects configured value', async t => { + t.plan(2) + const fastify = Fastify() + + fastify.get('/', { handlerTimeout: 3000 }, async (request) => { + t.assert.strictEqual(request.routeOptions.handlerTimeout, 3000) + return 'ok' + }) + + const res = await fastify.inject({ method: 'GET', url: '/' }) + t.assert.strictEqual(res.statusCode, 200) +}) + +test('request.routeOptions.handlerTimeout reflects server default', async t => { + t.plan(2) + const fastify = Fastify({ handlerTimeout: 7000 }) + + fastify.get('/', async (request) => { + t.assert.strictEqual(request.routeOptions.handlerTimeout, 7000) + return 'ok' + }) + + const res = await fastify.inject({ method: 'GET', url: '/' }) + t.assert.strictEqual(res.statusCode, 200) +}) + +// --- Client disconnect aborts signal --- + +test('client disconnect aborts request.signal', async t => { + t.plan(1) + + const fastify = Fastify() + let signalAborted = false + + fastify.get('/', { handlerTimeout: 5000 }, async (request) => { + await new Promise((resolve) => { + request.signal.addEventListener('abort', () => { + signalAborted = true + resolve() + }) + }) + return 'should not reach' + }) + + await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const address = fastify.server.address() + await new Promise((resolve) => { + const client = net.connect(address.port, () => { + client.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') + setTimeout(() => { + client.destroy() + // Give the server time to process the close event + setTimeout(resolve, 100) + }, 50) + }) + }) + + t.assert.strictEqual(signalAborted, true) +}) + +// --- Race: handler completes just as timeout fires --- + +test('no double-send when handler completes near timeout boundary', async t => { + t.plan(2) + const fastify = Fastify() + + fastify.get('/', { handlerTimeout: 50 }, async (request, reply) => { + // Respond just before timeout + await new Promise(resolve => setTimeout(resolve, 40)) + reply.send({ ok: true }) + return reply + }) + + const res = await fastify.inject({ method: 'GET', url: '/' }) + // Should get either 200 or 503 depending on race, but never crash + t.assert.ok(res.statusCode === 200 || res.statusCode === 503) + // Verify response is valid JSON regardless of which won the race + t.assert.ok(JSON.parse(res.payload)) +}) + +// --- Server default inherited by routes --- + +test('routes inherit server-level handlerTimeout', async t => { + t.plan(3) + const fastify = Fastify({ handlerTimeout: 50 }) + + fastify.get('/', async (request) => { + // Verify the signal is present (inherited from server default) + t.assert.ok(request.signal instanceof AbortSignal) + await new Promise(resolve => setTimeout(resolve, 500)) + return 'too late' + }) + + const res = await fastify.inject({ method: 'GET', url: '/' }) + t.assert.strictEqual(res.statusCode, 503) + t.assert.strictEqual(JSON.parse(res.payload).code, 'FST_ERR_HANDLER_TIMEOUT') +}) diff --git a/services/slides/node_modules/fastify/test/has-route.test.js b/services/slides/node_modules/fastify/test/has-route.test.js new file mode 100644 index 0000000000000000000000000000000000000000..97761b1eb13138bd7199924f305b44e7e7d326ba --- /dev/null +++ b/services/slides/node_modules/fastify/test/has-route.test.js @@ -0,0 +1,88 @@ +'use strict' + +const { test, describe } = require('node:test') +const Fastify = require('..') + +const fastify = Fastify() + +describe('hasRoute', async t => { + test('hasRoute - invalid options', t => { + t.plan(3) + + t.assert.strictEqual(fastify.hasRoute({ }), false) + t.assert.strictEqual(fastify.hasRoute({ method: 'GET' }), false) + t.assert.strictEqual(fastify.hasRoute({ constraints: [] }), false) + }) + + test('hasRoute - primitive method', t => { + t.plan(2) + fastify.route({ + method: 'GET', + url: '/', + handler: function (req, reply) { + reply.send({ hello: 'world' }) + } + }) + + t.assert.strictEqual(fastify.hasRoute({ + method: 'GET', + url: '/' + }), true) + + t.assert.strictEqual(fastify.hasRoute({ + method: 'POST', + url: '/' + }), false) + }) + + test('hasRoute - with constraints', t => { + t.plan(2) + fastify.route({ + method: 'GET', + url: '/', + constraints: { version: '1.2.0' }, + handler: (req, reply) => { + reply.send({ hello: 'world' }) + } + }) + + t.assert.strictEqual(fastify.hasRoute({ + method: 'GET', + url: '/', + constraints: { version: '1.2.0' } + }), true) + + t.assert.strictEqual(fastify.hasRoute({ + method: 'GET', + url: '/', + constraints: { version: '1.3.0' } + }), false) + }) + + test('hasRoute - parametric route regexp with constraints', t => { + t.plan(1) + // parametric with regexp + fastify.get('/example/:file(^\\d+).png', function (request, reply) { }) + + t.assert.strictEqual(fastify.hasRoute({ + method: 'GET', + url: '/example/:file(^\\d+).png' + }), true) + }) + + test('hasRoute - finds a route even if method is not uppercased', t => { + t.plan(1) + fastify.route({ + method: 'GET', + url: '/equal', + handler: function (req, reply) { + reply.send({ hello: 'world' }) + } + }) + + t.assert.strictEqual(fastify.hasRoute({ + method: 'get', + url: '/equal' + }), true) + }) +}) diff --git a/services/slides/node_modules/fastify/test/header-overflow.test.js b/services/slides/node_modules/fastify/test/header-overflow.test.js new file mode 100644 index 0000000000000000000000000000000000000000..cfc32b88f2dacf1bb22ec5661d3ff65707d8952c --- /dev/null +++ b/services/slides/node_modules/fastify/test/header-overflow.test.js @@ -0,0 +1,55 @@ +'use strict' + +const { test } = require('node:test') +const Fastify = require('..') + +const maxHeaderSize = 1024 + +test('Should return 431 if request header fields are too large', async (t) => { + t.plan(2) + + const fastify = Fastify({ http: { maxHeaderSize } }) + fastify.route({ + method: 'GET', + url: '/', + handler: (_req, reply) => { + reply.send({ hello: 'world' }) + } + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + + const result = await fetch(fastifyServer, { + method: 'GET', + headers: { + 'Large-Header': 'a'.repeat(maxHeaderSize) + } + }) + + t.assert.ok(!result.ok) + t.assert.strictEqual(result.status, 431) + + t.after(() => fastify.close()) +}) + +test('Should return 431 if URI is too long', async (t) => { + t.plan(2) + + const fastify = Fastify({ http: { maxHeaderSize } }) + fastify.route({ + method: 'GET', + url: '/', + handler: (_req, reply) => { + reply.send({ hello: 'world' }) + } + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + + const result = await fetch(`${fastifyServer}/${'a'.repeat(maxHeaderSize)}`) + + t.assert.ok(!result.ok) + t.assert.strictEqual(result.status, 431) + + t.after(() => fastify.close()) +}) diff --git a/services/slides/node_modules/fastify/test/helper.js b/services/slides/node_modules/fastify/test/helper.js new file mode 100644 index 0000000000000000000000000000000000000000..43c1db27e8cbf9b5513ca011fdbfa1cde07d25c1 --- /dev/null +++ b/services/slides/node_modules/fastify/test/helper.js @@ -0,0 +1,496 @@ +'use strict' + +const dns = require('node:dns').promises +const stream = require('node:stream') +const { promisify } = require('node:util') +const symbols = require('../lib/symbols') +const { waitForCb } = require('./toolkit') +const assert = require('node:assert') + +module.exports.sleep = promisify(setTimeout) + +/** + * @param method HTTP request method + * @param t node:test instance + * @param isSetErrorHandler true: using setErrorHandler + */ +module.exports.payloadMethod = function (method, t, isSetErrorHandler = false) { + const test = t.test + const fastify = require('..')() + + if (isSetErrorHandler) { + fastify.setErrorHandler(function (err, request, reply) { + assert.ok(request instanceof fastify[symbols.kRequest].parent) + assert.strictEqual(typeof request, 'object') + reply + .code(err.statusCode) + .type('application/json; charset=utf-8') + .send(err) + }) + } + + const upMethod = method.toUpperCase() + const loMethod = method.toLowerCase() + + const schema = { + schema: { + response: { + '2xx': { + type: 'object', + properties: { + hello: { + type: 'string' + } + } + } + } + } + } + + test(`${upMethod} can be created`, t => { + t.plan(1) + try { + fastify[loMethod]('/', schema, function (req, reply) { + reply.code(200).send(req.body) + }) + t.assert.ok(true) + } catch (e) { + t.assert.fail() + } + }) + + test(`${upMethod} without schema can be created`, t => { + t.plan(1) + try { + fastify[loMethod]('/missing', function (req, reply) { + reply.code(200).send(req.body) + }) + t.assert.ok(true) + } catch (e) { + t.assert.fail() + } + }) + + test(`${upMethod} with body and querystring`, t => { + t.plan(1) + try { + fastify[loMethod]('/with-query', function (req, reply) { + req.body.hello = req.body.hello + req.query.foo + reply.code(200).send(req.body) + }) + t.assert.ok(true) + } catch (e) { + t.assert.fail() + } + }) + + test(`${upMethod} with bodyLimit option`, t => { + t.plan(1) + try { + fastify[loMethod]('/with-limit', { bodyLimit: 1 }, function (req, reply) { + reply.send(req.body) + }) + t.assert.ok(true) + } catch (e) { + t.assert.fail() + } + }) + + fastify.listen({ port: 0 }, function (err) { + if (err) { + t.assert.ifError(err) + return + } + + t.after(() => { fastify.close() }) + + test(`${upMethod} - correctly replies`, async (t) => { + t.plan(3) + + const result = await fetch('http://localhost:' + fastify.server.address().port, { + method: upMethod, + body: JSON.stringify({ hello: 'world' }), + headers: { + 'Content-Type': 'application/json' + } + }) + + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.deepStrictEqual(await result.json(), { hello: 'world' }) + }) + + test(`${upMethod} - correctly replies with very large body`, async (t) => { + t.plan(3) + + const largeString = 'world'.repeat(13200) + const result = await fetch('http://localhost:' + fastify.server.address().port, { + method: upMethod, + body: JSON.stringify({ hello: largeString }), + headers: { + 'Content-Type': 'application/json' + } + }) + + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.deepStrictEqual(await result.json(), { hello: largeString }) + }) + + test(`${upMethod} - correctly replies if the content type has the charset`, async (t) => { + t.plan(3) + + const result = await fetch('http://localhost:' + fastify.server.address().port, { + method: upMethod, + body: JSON.stringify({ hello: 'world' }), + headers: { + 'content-type': 'application/json; charset=utf-8' + } + }) + + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.deepStrictEqual(await result.text(), JSON.stringify({ hello: 'world' })) + }) + + test(`${upMethod} without schema - correctly replies`, async (t) => { + t.plan(3) + + const result = await fetch('http://localhost:' + fastify.server.address().port + '/missing', { + method: upMethod, + body: JSON.stringify({ hello: 'world' }), + headers: { + 'Content-Type': 'application/json' + } + }) + + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.deepStrictEqual(await result.json(), { hello: 'world' }) + }) + + test(`${upMethod} with body and querystring - correctly replies`, async (t) => { + t.plan(3) + + const result = await fetch('http://localhost:' + fastify.server.address().port + '/with-query?foo=hello', { + method: upMethod, + body: JSON.stringify({ hello: 'world' }), + headers: { + 'Content-Type': 'application/json' + } + }) + + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.deepStrictEqual(await result.json(), { hello: 'worldhello' }) + }) + + test(`${upMethod} with no body - correctly replies`, t => { + t.plan(6) + + const { stepIn, patience } = waitForCb({ steps: 2 }) + + fetch('http://localhost:' + fastify.server.address().port + '/missing', { + method: upMethod, + headers: { 'Content-Length': '0' } + }).then(async (response) => { + t.assert.ok(response.ok) + t.assert.strictEqual(response.status, 200) + t.assert.strictEqual(await response.text(), '') + stepIn() + }) + + // Must use inject to make a request without a Content-Length header + fastify.inject({ + method: upMethod, + url: '/missing' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload.toString(), '') + stepIn() + }) + + return patience + }) + + test(`${upMethod} returns 415 - incorrect media type if body is not json`, async (t) => { + t.plan(2) + + const result = await fetch('http://localhost:' + fastify.server.address().port + '/missing', { + method: upMethod, + body: 'hello world', + headers: { + 'Content-Type': undefined + } + }) + + t.assert.ok(!result.ok) + t.assert.strictEqual(result.status, 415) + }) + + if (loMethod === 'options') { + test('OPTIONS returns 415 - should return 415 if Content-Type is not json or plain text', async (t) => { + t.plan(2) + + const result = await fetch('http://localhost:' + fastify.server.address().port + '/missing', { + method: upMethod, + body: 'hello world', + headers: { + 'Content-Type': 'text/xml' + } + }) + + t.assert.ok(!result.ok) + t.assert.strictEqual(result.status, 415) + }) + } + + test(`${upMethod} returns 400 - Bad Request`, t => { + const isOptions = upMethod === 'OPTIONS' + t.plan(isOptions ? 2 : 4) + + const { stepIn, patience } = waitForCb({ steps: isOptions ? 1 : 2 }) + + fetch('http://localhost:' + fastify.server.address().port, { + method: upMethod, + body: 'hello world', + headers: { + 'Content-Type': 'application/json' + } + }).then((response) => { + t.assert.ok(!response.ok) + t.assert.strictEqual(response.status, 400) + stepIn() + }) + + if (!isOptions) { + fetch(`http://localhost:${fastify.server.address().port}`, { + method: upMethod, + headers: { + 'Content-Type': 'application/json', + 'Content-Length': '0' + } + }).then((response) => { + t.assert.ok(!response.ok) + t.assert.strictEqual(response.status, 400) + stepIn() + }) + } + + return patience + }) + + test(`${upMethod} returns 413 - Payload Too Large`, t => { + const isOptions = upMethod === 'OPTIONS' + t.plan(isOptions ? 3 : 5) + + const { stepIn, patience } = waitForCb({ steps: isOptions ? 2 : 3 }) + + fetch(`http://localhost:${fastify.server.address().port}`, { + method: upMethod, + body: JSON.stringify({ w: 'w'.repeat(1024 * 1024 + 1) }), + headers: { + 'Content-Type': 'application/json' + } + }).then((response) => { + t.assert.strictEqual(response.status, 413) + stepIn() + }).catch((err) => { + // Handle EPIPE error - server closed connection after sending 413 + if (err.cause?.code === 'EPIPE' || err.message.includes('fetch failed')) { + t.assert.ok(true, 'Expected EPIPE error due to server closing connection on 413') + } else { + throw err + } + stepIn() + }) + + // Node errors for OPTIONS requests with a stream body and no Content-Length header + if (!isOptions) { + let chunk = Buffer.alloc(1024 * 1024 + 1, 0) + const largeStream = new stream.Readable({ + read () { + this.push(chunk) + chunk = null + } + }) + fetch('http://localhost:' + fastify.server.address().port, { + method: upMethod, + headers: { 'Content-Type': 'application/json' }, + body: largeStream, + duplex: 'half' + }).then((response) => { + t.assert.ok(!response.ok) + t.assert.strictEqual(response.status, 413) + stepIn() + }).catch((err) => { + // Handle EPIPE error - server closed connection after sending 413 + if (err.cause?.code === 'EPIPE' || err.message.includes('fetch failed')) { + t.assert.ok(true, 'Expected EPIPE error due to server closing connection on 413') + } else { + throw err + } + stepIn() + }) + } + + fetch(`http://localhost:${fastify.server.address().port}/with-limit`, { + method: upMethod, + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({}) + }).then((response) => { + t.assert.ok(!response.ok) + t.assert.strictEqual(response.status, 413) + stepIn() + }) + + return patience + }) + + test(`${upMethod} should fail with empty body and application/json content-type`, t => { + if (upMethod === 'OPTIONS') return + + t.plan(12) + + const { stepIn, patience } = waitForCb({ steps: 5 }) + + fastify.inject({ + method: `${upMethod}`, + url: '/', + headers: { + 'Content-Type': 'application/json' + } + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { + error: 'Bad Request', + code: 'FST_ERR_CTP_EMPTY_JSON_BODY', + message: 'Body cannot be empty when content-type is set to \'application/json\'', + statusCode: 400 + }) + }) + + fetch(`http://localhost:${fastify.server.address().port}`, { + method: upMethod, + headers: { + 'Content-Type': 'application/json' + } + }).then(async (res) => { + t.assert.ok(!res.ok) + t.assert.deepStrictEqual(await res.json(), { + error: 'Bad Request', + code: 'FST_ERR_CTP_EMPTY_JSON_BODY', + message: 'Body cannot be empty when content-type is set to \'application/json\'', + statusCode: 400 + }) + stepIn() + }) + + fastify.inject({ + method: `${upMethod}`, + url: '/', + headers: { + 'Content-Type': 'application/json' + }, + payload: null + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { + error: 'Bad Request', + code: 'FST_ERR_CTP_EMPTY_JSON_BODY', + message: 'Body cannot be empty when content-type is set to \'application/json\'', + statusCode: 400 + }) + stepIn() + }) + + fetch(`http://localhost:${fastify.server.address().port}`, { + method: upMethod, + headers: { + 'Content-Type': 'application/json' + }, + body: null + }).then(async (res) => { + t.assert.ok(!res.ok) + t.assert.deepStrictEqual(await res.json(), { + error: 'Bad Request', + code: 'FST_ERR_CTP_EMPTY_JSON_BODY', + message: 'Body cannot be empty when content-type is set to \'application/json\'', + statusCode: 400 + }) + stepIn() + }) + + fastify.inject({ + method: `${upMethod}`, + url: '/', + headers: { + 'Content-Type': 'application/json' + }, + payload: undefined + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { + error: 'Bad Request', + code: 'FST_ERR_CTP_EMPTY_JSON_BODY', + message: 'Body cannot be empty when content-type is set to \'application/json\'', + statusCode: 400 + }) + stepIn() + }) + + fetch(`http://localhost:${fastify.server.address().port}`, { + method: upMethod, + headers: { + 'Content-Type': 'application/json' + }, + body: undefined + }).then(async (res) => { + t.assert.ok(!res.ok) + t.assert.deepStrictEqual(await res.json(), { + error: 'Bad Request', + code: 'FST_ERR_CTP_EMPTY_JSON_BODY', + message: 'Body cannot be empty when content-type is set to \'application/json\'', + statusCode: 400 + }) + stepIn() + }) + + return patience + }) + }) +} + +function lookupToIp (lookup) { + return lookup.family === 6 ? `[${lookup.address}]` : lookup.address +} + +module.exports.getLoopbackHost = async () => { + const lookup = await dns.lookup('localhost') + return [lookup.address, lookupToIp(lookup)] +} + +module.exports.plainTextParser = function (request, callback) { + let body = '' + request.setEncoding('utf8') + request.on('error', onError) + request.on('data', onData) + request.on('end', onEnd) + function onError (err) { + callback(err, null) + } + function onData (chunk) { + body += chunk + } + function onEnd () { + callback(null, body) + } +} + +module.exports.getServerUrl = function (app) { + const { address, port } = app.server.address() + return address === '::1' + ? `http://[${address}]:${port}` + : `http://${address}:${port}` +} diff --git a/services/slides/node_modules/fastify/test/hooks-async.test.js b/services/slides/node_modules/fastify/test/hooks-async.test.js new file mode 100644 index 0000000000000000000000000000000000000000..698eb99e228b198b7a19aff13b8dfa85889b0461 --- /dev/null +++ b/services/slides/node_modules/fastify/test/hooks-async.test.js @@ -0,0 +1,1099 @@ +'use strict' + +const { Readable } = require('node:stream') +const { test, describe } = require('node:test') +const Fastify = require('../fastify') +const fs = require('node:fs') +const { sleep } = require('./helper') +const { waitForCb } = require('./toolkit') + +process.removeAllListeners('warning') + +test('async hooks', async t => { + t.plan(20) + const fastify = Fastify({ exposeHeadRoutes: false }) + fastify.addHook('onRequest', async function (request, reply) { + await sleep(1) + request.test = 'the request is coming' + reply.test = 'the reply has come' + if (request.raw.method === 'DELETE') { + throw new Error('some error') + } + }) + + fastify.addHook('preHandler', async function (request, reply) { + await sleep(1) + t.assert.strictEqual(request.test, 'the request is coming') + t.assert.strictEqual(reply.test, 'the reply has come') + if (request.raw.method === 'HEAD') { + throw new Error('some error') + } + }) + + fastify.addHook('onSend', async function (request, reply, payload) { + await sleep(1) + t.assert.ok('onSend called') + }) + + const completion = waitForCb({ + steps: 6 + }) + fastify.addHook('onResponse', async function (request, reply) { + await sleep(1) + t.assert.ok('onResponse called') + completion.stepIn() + }) + + fastify.get('/', function (request, reply) { + t.assert.strictEqual(request.test, 'the request is coming') + t.assert.strictEqual(reply.test, 'the reply has come') + reply.code(200).send({ hello: 'world' }) + }) + + fastify.head('/', function (req, reply) { + reply.code(200).send({ hello: 'world' }) + }) + + fastify.delete('/', function (req, reply) { + reply.code(200).send({ hello: 'world' }) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) + + const response1 = await fetch(fastifyServer, { + method: 'GET' + }) + t.assert.ok(response1.ok) + t.assert.strictEqual(response1.status, 200) + const body1 = await response1.text() + t.assert.strictEqual(response1.headers.get('content-length'), '' + body1.length) + t.assert.deepStrictEqual(JSON.parse(body1), { hello: 'world' }) + completion.stepIn() + + const response2 = await fetch(fastifyServer, { + method: 'HEAD' + }) + t.assert.ok(!response2.ok) + t.assert.strictEqual(response2.status, 500) + completion.stepIn() + + const response3 = await fetch(fastifyServer, { + method: 'DELETE' + }) + t.assert.ok(!response3.ok) + t.assert.strictEqual(response3.status, 500) + completion.stepIn() + + return completion.patience +}) + +test('modify payload', (t, testDone) => { + t.plan(10) + const fastify = Fastify() + const payload = { hello: 'world' } + const modifiedPayload = { hello: 'modified' } + const anotherPayload = '"winter is coming"' + + fastify.addHook('onSend', async function (request, reply, thePayload) { + t.assert.ok('onSend called') + t.assert.deepStrictEqual(JSON.parse(thePayload), payload) + return thePayload.replace('world', 'modified') + }) + + fastify.addHook('onSend', async function (request, reply, thePayload) { + t.assert.ok('onSend called') + t.assert.deepStrictEqual(JSON.parse(thePayload), modifiedPayload) + return anotherPayload + }) + + fastify.addHook('onSend', async function (request, reply, thePayload) { + t.assert.ok('onSend called') + t.assert.deepStrictEqual(thePayload, anotherPayload) + }) + + fastify.get('/', (req, reply) => { + reply.send(payload) + }) + + fastify.inject({ + method: 'GET', + url: '/' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.payload, anotherPayload) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['content-length'], '18') + testDone() + }) +}) + +test('onRequest hooks should be able to block a request', (t, testDone) => { + t.plan(5) + const fastify = Fastify() + + fastify.addHook('onRequest', async (req, reply) => { + await reply.send('hello') + }) + + fastify.addHook('onRequest', async (req, reply) => { + t.assert.fail('this should not be called') + }) + + fastify.addHook('preHandler', async (req, reply) => { + t.assert.fail('this should not be called') + }) + + fastify.addHook('onSend', async (req, reply, payload) => { + t.assert.ok('called') + }) + + fastify.addHook('onResponse', async (request, reply) => { + t.assert.ok('called') + }) + + fastify.get('/', function (request, reply) { + t.assert.fail('we should not be here') + }) + + fastify.inject({ + url: '/', + method: 'GET' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload, 'hello') + testDone() + }) +}) + +test('preParsing hooks should be able to modify the payload', (t, testDone) => { + t.plan(3) + const fastify = Fastify() + + fastify.addHook('preParsing', async (req, reply, payload) => { + const stream = new Readable() + + stream.receivedEncodedLength = parseInt(req.headers['content-length'], 10) + stream.push(JSON.stringify({ hello: 'another world' })) + stream.push(null) + + return stream + }) + + fastify.post('/', function (request, reply) { + reply.send(request.body) + }) + + fastify.inject({ + method: 'POST', + url: '/', + payload: { hello: 'world' } + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'another world' }) + testDone() + }) +}) + +test('preParsing hooks should be able to supply statusCode', (t, testDone) => { + t.plan(4) + const fastify = Fastify() + + fastify.addHook('preParsing', async (req, reply, payload) => { + const stream = new Readable({ + read () { + const error = new Error('kaboom') + error.statusCode = 408 + this.destroy(error) + } + }) + stream.receivedEncodedLength = 20 + return stream + }) + + fastify.addHook('onError', async (req, res, err) => { + t.assert.strictEqual(err.statusCode, 408) + }) + + fastify.post('/', function (request, reply) { + t.assert.fail('should not be called') + }) + + fastify.inject({ + method: 'POST', + url: '/', + payload: { hello: 'world' } + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 408) + t.assert.deepStrictEqual(JSON.parse(res.payload), { + statusCode: 408, + error: 'Request Timeout', + message: 'kaboom' + }) + + testDone() + }) +}) + +test('preParsing hooks should ignore statusCode 200 in stream error', (t, testDone) => { + t.plan(4) + const fastify = Fastify() + + fastify.addHook('preParsing', async (req, reply, payload) => { + const stream = new Readable({ + read () { + const error = new Error('kaboom') + error.statusCode = 200 + this.destroy(error) + } + }) + stream.receivedEncodedLength = 20 + return stream + }) + + fastify.addHook('onError', async (req, res, err) => { + t.assert.strictEqual(err.statusCode, 400) + }) + + fastify.post('/', function (request, reply) { + t.assert.fail('should not be called') + }) + + fastify.inject({ + method: 'POST', + url: '/', + payload: { hello: 'world' } + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 400) + t.assert.deepStrictEqual(JSON.parse(res.payload), { + statusCode: 400, + error: 'Bad Request', + message: 'kaboom' + }) + testDone() + }) +}) + +test('preParsing hooks should ignore non-number statusCode in stream error', (t, testDone) => { + t.plan(4) + const fastify = Fastify() + + fastify.addHook('preParsing', async (req, reply, payload) => { + const stream = new Readable({ + read () { + const error = new Error('kaboom') + error.statusCode = '418' + this.destroy(error) + } + }) + stream.receivedEncodedLength = 20 + return stream + }) + + fastify.addHook('onError', async (req, res, err) => { + t.assert.strictEqual(err.statusCode, 400) + }) + + fastify.post('/', function (request, reply) { + t.assert.fail('should not be called') + }) + + fastify.inject({ + method: 'POST', + url: '/', + payload: { hello: 'world' } + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 400) + t.assert.deepStrictEqual(JSON.parse(res.payload), { + statusCode: 400, + error: 'Bad Request', + message: 'kaboom' + }) + testDone() + }) +}) + +test('preParsing hooks should default to statusCode 400 if stream error', (t, testDone) => { + t.plan(4) + const fastify = Fastify() + + fastify.addHook('preParsing', async (req, reply, payload) => { + const stream = new Readable({ + read () { + this.destroy(new Error('kaboom')) + } + }) + stream.receivedEncodedLength = 20 + return stream + }) + + fastify.addHook('onError', async (req, res, err) => { + t.assert.strictEqual(err.statusCode, 400) + }) + + fastify.post('/', function (request, reply) { + t.assert.fail('should not be called') + }) + + fastify.inject({ + method: 'POST', + url: '/', + payload: { hello: 'world' } + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 400) + t.assert.deepStrictEqual(JSON.parse(res.payload), { + statusCode: 400, + error: 'Bad Request', + message: 'kaboom' + }) + testDone() + }) +}) + +test('preParsing hooks should handle errors', (t, testDone) => { + t.plan(3) + + const fastify = Fastify() + fastify.addHook('preParsing', async (req, reply, payload) => { + const e = new Error('kaboom') + e.statusCode = 501 + throw e + }) + + fastify.post('/', function (request, reply) { + reply.send(request.body) + }) + + fastify.inject({ + method: 'POST', + url: '/', + payload: { hello: 'world' } + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 501) + t.assert.deepStrictEqual(JSON.parse(res.payload), { error: 'Not Implemented', message: 'kaboom', statusCode: 501 }) + testDone() + }) +}) + +test('preHandler hooks should be able to block a request', (t, testDone) => { + t.plan(5) + const fastify = Fastify() + + fastify.addHook('preHandler', async (req, reply) => { + await reply.send('hello') + }) + + fastify.addHook('preHandler', async (req, reply) => { + t.assert.fail('this should not be called') + }) + + fastify.addHook('onSend', async (req, reply, payload) => { + t.assert.strictEqual(payload, 'hello') + }) + + fastify.addHook('onResponse', async (request, reply) => { + t.assert.ok('called') + }) + + fastify.get('/', function (request, reply) { + t.assert.fail('we should not be here') + }) + + fastify.inject({ + url: '/', + method: 'GET' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload, 'hello') + testDone() + }) +}) + +test('preValidation hooks should be able to block a request', (t, testDone) => { + t.plan(5) + const fastify = Fastify() + + fastify.addHook('preValidation', async (req, reply) => { + await reply.send('hello') + }) + + fastify.addHook('preValidation', async (req, reply) => { + t.assert.fail('this should not be called') + }) + + fastify.addHook('onSend', async (req, reply, payload) => { + t.assert.strictEqual(payload, 'hello') + }) + + fastify.addHook('onResponse', async (request, reply) => { + t.assert.ok('called') + }) + + fastify.get('/', function (request, reply) { + t.assert.fail('we should not be here') + }) + + fastify.inject({ + url: '/', + method: 'GET' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload, 'hello') + testDone() + }) +}) + +test('preValidation hooks should be able to change request body before validation', (t, testDone) => { + t.plan(4) + const fastify = Fastify() + + fastify.addHook('preValidation', async (req, _reply) => { + const buff = Buffer.from(req.body.message, 'base64') + req.body = JSON.parse(buff.toString('utf-8')) + t.assert.ok('has been called') + }) + + fastify.post( + '/', + { + schema: { + body: { + type: 'object', + properties: { + foo: { + type: 'string' + }, + bar: { + type: 'number' + } + }, + required: ['foo', 'bar'] + } + } + }, + (req, reply) => { + reply.status(200).send('hello') + } + ) + + fastify.inject({ + url: '/', + method: 'POST', + payload: { + message: Buffer.from(JSON.stringify({ foo: 'example', bar: 1 })).toString('base64') + } + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload, 'hello') + testDone() + }) +}) + +test('preSerialization hooks should be able to modify the payload', (t, testDone) => { + t.plan(3) + const fastify = Fastify() + + fastify.addHook('preSerialization', async (req, reply, payload) => { + return { hello: 'another world' } + }) + + fastify.get('/', function (request, reply) { + reply.send({ hello: 'world' }) + }) + + fastify.inject({ + url: '/', + method: 'GET' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'another world' }) + testDone() + }) +}) + +test('preSerialization hooks should handle errors', (t, testDone) => { + t.plan(3) + const fastify = Fastify() + + fastify.addHook('preSerialization', async (req, reply, payload) => { + throw new Error('kaboom') + }) + + fastify.get('/', function (request, reply) { + reply.send({ hello: 'world' }) + }) + + fastify.inject({ + url: '/', + method: 'GET' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 500) + t.assert.deepStrictEqual(JSON.parse(res.payload), { error: 'Internal Server Error', message: 'kaboom', statusCode: 500 }) + testDone() + }) +}) + +test('preValidation hooks should handle throwing null', (t, testDone) => { + t.plan(4) + const fastify = Fastify() + + fastify.setErrorHandler(async (error, request, reply) => { + t.assert.ok(error instanceof Error) + await reply.send(error) + }) + + fastify.addHook('preValidation', async () => { + // eslint-disable-next-line no-throw-literal + throw null + }) + + fastify.get('/', function (request, reply) { t.assert.fail('the handler must not be called') }) + + fastify.inject({ + url: '/', + method: 'GET' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 500) + t.assert.deepStrictEqual(res.json(), { + error: 'Internal Server Error', + code: 'FST_ERR_SEND_UNDEFINED_ERR', + message: 'Undefined error has occurred', + statusCode: 500 + }) + testDone() + }) +}) + +test('preValidation hooks should handle throwing a string', (t, testDone) => { + t.plan(3) + const fastify = Fastify() + + fastify.addHook('preValidation', async () => { + // eslint-disable-next-line no-throw-literal + throw 'this is an error' + }) + + fastify.get('/', function (request, reply) { t.assert.fail('the handler must not be called') }) + + fastify.inject({ + url: '/', + method: 'GET' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 500) + t.assert.strictEqual(res.payload, 'this is an error') + testDone() + }) +}) + +test('onRequest hooks should be able to block a request (last hook)', (t, testDone) => { + t.plan(5) + const fastify = Fastify() + + fastify.addHook('onRequest', async (req, reply) => { + await reply.send('hello') + }) + + fastify.addHook('preHandler', async (req, reply) => { + t.assert.fail('this should not be called') + }) + + fastify.addHook('onSend', async (req, reply, payload) => { + t.assert.ok('called') + }) + + fastify.addHook('onResponse', async (request, reply) => { + t.assert.ok('called') + }) + + fastify.get('/', function (request, reply) { + t.assert.fail('we should not be here') + }) + + fastify.inject({ + url: '/', + method: 'GET' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload, 'hello') + testDone() + }) +}) + +test('preHandler hooks should be able to block a request (last hook)', (t, testDone) => { + t.plan(5) + const fastify = Fastify() + + fastify.addHook('preHandler', async (req, reply) => { + await reply.send('hello') + }) + + fastify.addHook('onSend', async (req, reply, payload) => { + t.assert.strictEqual(payload, 'hello') + }) + + fastify.addHook('onResponse', async (request, reply) => { + t.assert.ok('called') + }) + + fastify.get('/', function (request, reply) { + t.assert.fail('we should not be here') + }) + + fastify.inject({ + url: '/', + method: 'GET' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload, 'hello') + testDone() + }) +}) + +test('onRequest respond with a stream', (t, testDone) => { + t.plan(4) + const fastify = Fastify() + + fastify.addHook('onRequest', async (req, reply) => { + return new Promise((resolve, reject) => { + const stream = fs.createReadStream(__filename, 'utf8') + // stream.pipe(res) + // res.once('finish', resolve) + reply.send(stream).then(() => { + reply.raw.once('finish', () => resolve()) + }) + }) + }) + + fastify.addHook('onRequest', async (req, res) => { + t.assert.fail('this should not be called') + }) + + fastify.addHook('preHandler', async (req, reply) => { + t.assert.fail('this should not be called') + }) + + fastify.addHook('onSend', async (req, reply, payload) => { + t.assert.ok('called') + }) + + fastify.addHook('onResponse', async (request, reply) => { + t.assert.ok('called') + }) + + fastify.get('/', function (request, reply) { + t.assert.fail('we should not be here') + }) + + fastify.inject({ + url: '/', + method: 'GET' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + testDone() + }) +}) + +test('preHandler respond with a stream', (t, testDone) => { + t.plan(7) + const fastify = Fastify() + + fastify.addHook('onRequest', async (req, res) => { + t.assert.ok('called') + }) + + // we are calling `reply.send` inside the `preHandler` hook with a stream, + // this triggers the `onSend` hook event if `preHandler` has not yet finished + const order = [1, 2] + + fastify.addHook('preHandler', async (req, reply) => { + const stream = fs.createReadStream(__filename, 'utf8') + reply.raw.once('finish', () => { + t.assert.strictEqual(order.shift(), 2) + }) + return reply.send(stream) + }) + + fastify.addHook('preHandler', async (req, reply) => { + t.assert.fail('this should not be called') + }) + + fastify.addHook('onSend', async (req, reply, payload) => { + t.assert.strictEqual(order.shift(), 1) + t.assert.strictEqual(typeof payload.pipe, 'function') + }) + + fastify.addHook('onResponse', async (request, reply) => { + t.assert.ok('called') + }) + + fastify.get('/', function (request, reply) { + t.assert.fail('we should not be here') + }) + + fastify.inject({ + url: '/', + method: 'GET' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + testDone() + }) +}) + +describe('Should log a warning if is an async function with `done`', () => { + test('2 arguments', t => { + const fastify = Fastify() + + try { + fastify.addHook('onRequestAbort', async (req, done) => { + t.assert.fail('should have not be called') + }) + } catch (e) { + t.assert.strictEqual(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER') + t.assert.strictEqual(e.message, 'Async function has too many arguments. Async hooks should not use the \'done\' argument.') + } + }) + + test('3 arguments', t => { + const fastify = Fastify() + + try { + fastify.addHook('onRequest', async (req, reply, done) => { + t.assert.fail('should have not be called') + }) + } catch (e) { + t.assert.strictEqual(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER') + t.assert.strictEqual(e.message, 'Async function has too many arguments. Async hooks should not use the \'done\' argument.') + } + }) + + test('4 arguments', t => { + const fastify = Fastify() + + try { + fastify.addHook('onSend', async (req, reply, payload, done) => { + t.assert.fail('should have not be called') + }) + } catch (e) { + t.assert.strictEqual(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER') + t.assert.strictEqual(e.message, 'Async function has too many arguments. Async hooks should not use the \'done\' argument.') + } + try { + fastify.addHook('preSerialization', async (req, reply, payload, done) => { + t.assert.fail('should have not be called') + }) + } catch (e) { + t.assert.strictEqual(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER') + t.assert.strictEqual(e.message, 'Async function has too many arguments. Async hooks should not use the \'done\' argument.') + } + try { + fastify.addHook('onError', async (req, reply, payload, done) => { + t.assert.fail('should have not be called') + }) + } catch (e) { + t.assert.strictEqual(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER') + t.assert.strictEqual(e.message, 'Async function has too many arguments. Async hooks should not use the \'done\' argument.') + } + }) +}) + +test('early termination, onRequest async', async t => { + const app = Fastify() + + app.addHook('onRequest', async (req, reply) => { + setImmediate(() => reply.send('hello world')) + return reply + }) + + app.get('/', (req, reply) => { + t.assert.fail('should not happen') + }) + + const res = await app.inject('/') + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.body.toString(), 'hello world') +}) + +test('The this should be the same of the encapsulation level', async t => { + const fastify = Fastify() + + fastify.addHook('onRequest', async function (req, reply) { + if (req.raw.url === '/nested') { + t.assert.strictEqual(this.foo, 'bar') + } else { + t.assert.strictEqual(this.foo, undefined) + } + }) + + fastify.register(plugin) + fastify.get('/', (req, reply) => reply.send('ok')) + + async function plugin (fastify, opts) { + fastify.decorate('foo', 'bar') + fastify.get('/nested', (req, reply) => reply.send('ok')) + } + + await fastify.inject({ method: 'GET', path: '/' }) + await fastify.inject({ method: 'GET', path: '/nested' }) + await fastify.inject({ method: 'GET', path: '/' }) + await fastify.inject({ method: 'GET', path: '/nested' }) +}) + +describe('preSerializationEnd should handle errors if the serialize method throws', () => { + test('works with sync preSerialization', (t, testDone) => { + t.plan(3) + const fastify = Fastify() + + fastify.addHook('preSerialization', (request, reply, payload, done) => { + t.assert.ok('called') + done(null, payload) + }) + + fastify.post('/', { + handler (req, reply) { reply.send({ notOk: true }) }, + schema: { response: { 200: { required: ['ok'], properties: { ok: { type: 'boolean' } } } } } + }) + + fastify.inject({ + method: 'POST', + url: '/' + }, (err, res) => { + t.assert.ifError(err) + t.assert.notEqual(res.statusCode, 200) + testDone() + }) + }) + + test('works with async preSerialization', (t, testDone) => { + t.plan(3) + const fastify = Fastify() + + fastify.addHook('preSerialization', async (request, reply, payload) => { + t.assert.ok('called') + return payload + }) + + fastify.post('/', { + handler (req, reply) { reply.send({ notOk: true }) }, + schema: { response: { 200: { required: ['ok'], properties: { ok: { type: 'boolean' } } } } } + }) + + fastify.inject({ + method: 'POST', + url: '/' + }, (err, res) => { + t.assert.ifError(err) + t.assert.notEqual(res.statusCode, 200) + testDone() + }) + }) +}) + +test('nested hooks to do not crash on 404', (t, testDone) => { + t.plan(3) + const fastify = Fastify() + + fastify.get('/hello', (req, reply) => { + reply.send({ hello: 'world' }) + }) + + fastify.register(async function (fastify) { + fastify.get('/something', (req, reply) => { + reply.callNotFound() + }) + + fastify.setNotFoundHandler(async (request, reply) => { + t.assert.ok('called') + reply.statusCode = 404 + return { status: 'nested-not-found' } + }) + + fastify.setErrorHandler(async (error, request, reply) => { + t.assert.fail('should have not be called') + reply.statusCode = 500 + return { status: 'nested-error', error } + }) + }, { prefix: '/nested' }) + + fastify.setNotFoundHandler(async (request, reply) => { + t.assert.fail('should have not be called') + reply.statusCode = 404 + return { status: 'not-found' } + }) + + fastify.setErrorHandler(async (error, request, reply) => { + t.assert.fail('should have not be called') + reply.statusCode = 500 + return { status: 'error', error } + }) + + fastify.inject({ + method: 'GET', + url: '/nested/something' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 404) + testDone() + }) +}) + +test('Register an hook (preHandler) as route option should fail if mixing async and callback style', t => { + const fastify = Fastify() + + try { + fastify.get( + '/', + { + preHandler: [ + async (request, reply, done) => { + done() + } + ] + }, + async (request, reply) => { + return { hello: 'world' } + } + ) + t.assert.fail('preHandler mixing async and callback style') + } catch (e) { + t.assert.strictEqual(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER') + t.assert.strictEqual(e.message, 'Async function has too many arguments. Async hooks should not use the \'done\' argument.') + } +}) + +test('Register an hook (onSend) as route option should fail if mixing async and callback style', t => { + const fastify = Fastify() + + try { + fastify.get( + '/', + { + onSend: [ + async (request, reply, payload, done) => { + done() + } + ] + }, + async (request, reply) => { + return { hello: 'world' } + } + ) + t.assert.fail('onSend mixing async and callback style') + } catch (e) { + t.assert.strictEqual(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER') + t.assert.strictEqual(e.message, 'Async function has too many arguments. Async hooks should not use the \'done\' argument.') + } +}) + +test('Register an hook (preSerialization) as route option should fail if mixing async and callback style', t => { + const fastify = Fastify() + + try { + fastify.get( + '/', + { + preSerialization: [ + async (request, reply, payload, done) => { + done() + } + ] + }, + async (request, reply) => { + return { hello: 'world' } + } + ) + t.assert.fail('preSerialization mixing async and callback style') + } catch (e) { + t.assert.strictEqual(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER') + t.assert.strictEqual(e.message, 'Async function has too many arguments. Async hooks should not use the \'done\' argument.') + } +}) + +test('Register an hook (onError) as route option should fail if mixing async and callback style', t => { + const fastify = Fastify() + + try { + fastify.get( + '/', + { + onError: [ + async (request, reply, error, done) => { + done() + } + ] + }, + async (request, reply) => { + return { hello: 'world' } + } + ) + t.assert.fail('onError mixing async and callback style') + } catch (e) { + t.assert.strictEqual(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER') + t.assert.strictEqual(e.message, 'Async function has too many arguments. Async hooks should not use the \'done\' argument.') + } +}) + +test('Register an hook (preParsing) as route option should fail if mixing async and callback style', t => { + const fastify = Fastify() + + try { + fastify.get( + '/', + { + preParsing: [ + async (request, reply, payload, done) => { + done() + } + ] + }, + async (request, reply) => { + return { hello: 'world' } + } + ) + t.assert.fail('preParsing mixing async and callback style') + } catch (e) { + t.assert.strictEqual(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER') + t.assert.strictEqual(e.message, 'Async function has too many arguments. Async hooks should not use the \'done\' argument.') + } +}) + +test('Register an hook (onRequestAbort) as route option should fail if mixing async and callback style', (t) => { + const fastify = Fastify() + + try { + fastify.get( + '/', + { + onRequestAbort: [ + async (request, done) => { + done() + } + ] + }, + async (request, reply) => { + return { hello: 'world' } + } + ) + t.assert.fail('onRequestAbort mixing async and callback style') + } catch (e) { + t.assert.strictEqual(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER') + t.assert.strictEqual(e.message, 'Async function has too many arguments. Async hooks should not use the \'done\' argument.') + } +}) diff --git a/services/slides/node_modules/fastify/test/hooks.on-listen.test.js b/services/slides/node_modules/fastify/test/hooks.on-listen.test.js new file mode 100644 index 0000000000000000000000000000000000000000..d1791926fa75b538f6887675619755833cf3172a --- /dev/null +++ b/services/slides/node_modules/fastify/test/hooks.on-listen.test.js @@ -0,0 +1,1162 @@ +'use strict' + +const { test, before } = require('node:test') +const Fastify = require('../fastify') +const fp = require('fastify-plugin') +const split = require('split2') +const helper = require('./helper') +const { kState } = require('../lib/symbols') +const { networkInterfaces } = require('node:os') + +const isIPv6Missing = !Object.values(networkInterfaces()).flat().some(({ family }) => family === 'IPv6') + +let localhost +before(async function () { + [localhost] = await helper.getLoopbackHost() +}) + +test('onListen should not be processed when .ready() is called', (t, testDone) => { + t.plan(1) + const fastify = Fastify() + t.after(() => fastify.close()) + + fastify.addHook('onListen', function (done) { + t.assert.fail() + done() + }) + + fastify.ready(err => { + t.assert.ifError(err) + testDone() + }) +}) + +test('localhost onListen should be called in order', (t, testDone) => { + t.plan(2) + const fastify = Fastify() + t.after(() => fastify.close()) + let order = 0 + + fastify.addHook('onListen', function (done) { + t.assert.strictEqual(++order, 1, '1st called in root') + done() + }) + + fastify.addHook('onListen', function (done) { + t.assert.strictEqual(++order, 2, '2nd called in root') + done() + }) + + fastify.listen({ + host: 'localhost', + port: 0 + }, testDone) +}) + +test('localhost async onListen should be called in order', async t => { + t.plan(3) + const fastify = Fastify() + t.after(() => fastify.close()) + let order = 0 + + fastify.addHook('onListen', async function () { + t.assert.strictEqual(++order, 1, '1st async called in root') + }) + + fastify.addHook('onListen', async function () { + t.assert.strictEqual(++order, 2, '2nd async called in root') + }) + + await fastify.listen({ + host: 'localhost', + port: 0 + }) + t.assert.strictEqual(order, 2, 'the onListen hooks are awaited') +}) + +test('localhost onListen sync should log errors as warnings and continue /1', async t => { + t.plan(8) + let order = 0 + const stream = split(JSON.parse) + const fastify = Fastify({ + forceCloseConnections: false, + logger: { + stream, + level: 'info' + } + }) + t.after(() => fastify.close()) + + stream.on('data', message => { + if (message.msg.includes('FAIL ON LISTEN')) { + t.assert.strictEqual(order, 2) + t.assert.ok('Logged Error Message') + } + }) + + fastify.addHook('onListen', function (done) { + t.assert.strictEqual(++order, 1, '1st call') + t.assert.ok('called in root') + done() + }) + + fastify.addHook('onListen', function (done) { + t.assert.strictEqual(++order, 2, '2nd call') + t.assert.ok('called onListen error') + throw new Error('FAIL ON LISTEN') + }) + + fastify.addHook('onListen', function (done) { + t.assert.strictEqual(++order, 3, '3rd call') + t.assert.ok('onListen hooks continue after error') + done() + }) + + await fastify.listen({ + host: 'localhost', + port: 0 + }) +}) + +test('localhost onListen sync should log errors as warnings and continue /2', (t, testDone) => { + t.plan(7) + const stream = split(JSON.parse) + const fastify = Fastify({ + forceCloseConnections: false, + logger: { + stream, + level: 'info' + } + }) + t.after(() => fastify.close()) + + let order = 0 + + stream.on('data', message => { + if (message.msg.includes('FAIL ON LISTEN')) { + t.assert.ok('Logged Error Message') + } + }) + + fastify.addHook('onListen', function (done) { + t.assert.strictEqual(++order, 1, '1st call') + t.assert.ok('called in root') + done() + }) + + fastify.addHook('onListen', function (done) { + t.assert.strictEqual(++order, 2, '2nd call') + t.assert.ok('called onListen error') + done(new Error('FAIL ON LISTEN')) + }) + + fastify.addHook('onListen', function (done) { + t.assert.strictEqual(++order, 3, '3rd call') + t.assert.ok('onListen hooks continue after error') + done() + }) + + fastify.listen({ + host: 'localhost', + port: 0 + }, testDone) +}) + +test('localhost onListen async should log errors as warnings and continue', async t => { + t.plan(4) + const stream = split(JSON.parse) + const fastify = Fastify({ + forceCloseConnections: false, + logger: { + stream, + level: 'info' + } + }) + t.after(() => fastify.close()) + + stream.on('data', message => { + if (message.msg.includes('FAIL ON LISTEN')) { + t.assert.ok('Logged Error Message') + } + }) + + fastify.addHook('onListen', async function () { + t.assert.ok('called in root') + }) + + fastify.addHook('onListen', async function () { + t.assert.ok('called onListen error') + throw new Error('FAIL ON LISTEN') + }) + + fastify.addHook('onListen', async function () { + t.assert.ok('onListen hooks continue after error') + }) + + await fastify.listen({ + host: 'localhost', + port: 0 + }) +}) + +test('localhost Register onListen hook after a plugin inside a plugin', (t, testDone) => { + t.plan(3) + const fastify = Fastify() + t.after(() => fastify.close()) + + fastify.register(fp(function (instance, opts, done) { + instance.addHook('onListen', function (done) { + t.assert.ok('called') + done() + }) + done() + })) + + fastify.register(fp(function (instance, opts, done) { + instance.addHook('onListen', function (done) { + t.assert.ok('called') + done() + }) + + instance.addHook('onListen', function (done) { + t.assert.ok('called') + done() + }) + + done() + })) + + fastify.listen({ + host: 'localhost', + port: 0 + }, testDone) +}) + +test('localhost Register onListen hook after a plugin inside a plugin should log errors as warnings and continue', (t, testDone) => { + t.plan(6) + const stream = split(JSON.parse) + const fastify = Fastify({ + forceCloseConnections: false, + logger: { + stream, + level: 'info' + } + }) + t.after(() => fastify.close()) + + stream.on('data', message => { + if (message.msg.includes('Plugin Error')) { + t.assert.ok('Logged Error Message') + } + }) + + fastify.register(fp(function (instance, opts, done) { + instance.addHook('onListen', function () { + t.assert.ok('called') + throw new Error('Plugin Error') + }) + done() + })) + + fastify.register(fp(function (instance, opts, done) { + instance.addHook('onListen', function () { + t.assert.ok('called') + throw new Error('Plugin Error') + }) + + instance.addHook('onListen', function () { + t.assert.ok('called') + throw new Error('Plugin Error') + }) + + done() + })) + + fastify.listen({ + host: 'localhost', + port: 0 + }, testDone) +}) + +test('localhost onListen encapsulation should be called in order', async t => { + t.plan(8) + const fastify = Fastify() + t.after(() => fastify.close()) + + let order = 0 + + fastify.addHook('onListen', function (done) { + t.assert.strictEqual(++order, 1, 'called in root') + t.assert.strictEqual(this.pluginName, fastify.pluginName, 'the this binding is the right instance') + done() + }) + + await fastify.register(async (childOne, o) => { + childOne.addHook('onListen', function (done) { + t.assert.strictEqual(++order, 2, 'called in childOne') + t.assert.strictEqual(this.pluginName, childOne.pluginName, 'the this binding is the right instance') + done() + }) + + await childOne.register(async (childTwo, o) => { + childTwo.addHook('onListen', async function () { + t.assert.strictEqual(++order, 3, 'called in childTwo') + t.assert.strictEqual(this.pluginName, childTwo.pluginName, 'the this binding is the right instance') + }) + }) + + await childOne.register(async (childTwoPeer, o) => { + childTwoPeer.addHook('onListen', async function () { + t.assert.strictEqual(++order, 4, 'called second in childTwo') + t.assert.strictEqual(this.pluginName, childTwoPeer.pluginName, 'the this binding is the right instance') + }) + }) + }) + await fastify.listen({ + host: 'localhost', + port: 0 + }) +}) + +test('localhost onListen encapsulation with only nested hook', async t => { + t.plan(1) + const fastify = Fastify() + t.after(() => fastify.close()) + + await fastify.register(async (child) => { + await child.register(async (child2) => { + child2.addHook('onListen', function (done) { + t.assert.ok() + done() + }) + }) + }) + + await fastify.listen({ + host: 'localhost', + port: 0 + }) +}) + +test('localhost onListen peer encapsulations with only nested hooks', async t => { + t.plan(2) + const fastify = Fastify() + t.after(() => fastify.close()) + + await fastify.register(async (child) => { + await child.register(async (child2) => { + child2.addHook('onListen', function (done) { + t.assert.ok() + done() + }) + }) + + await child.register(async (child2) => { + child2.addHook('onListen', function (done) { + t.assert.ok() + done() + }) + }) + }) + + await fastify.listen({ + host: 'localhost', + port: 0 + }) +}) + +test('localhost onListen encapsulation should be called in order and should log errors as warnings and continue', (t, testDone) => { + t.plan(7) + const stream = split(JSON.parse) + const fastify = Fastify({ + forceCloseConnections: false, + logger: { + stream, + level: 'info' + } + }) + t.after(() => fastify.close()) + + stream.on('data', message => { + if (message.msg.includes('Error in onListen hook of childTwo')) { + t.assert.ok('Logged Error Message') + } + }) + + let order = 0 + + fastify.addHook('onListen', function (done) { + t.assert.strictEqual(++order, 1, 'called in root') + t.assert.strictEqual(this.pluginName, fastify.pluginName, 'the this binding is the right instance') + done() + }) + + fastify.register(async (childOne, o) => { + childOne.addHook('onListen', function (done) { + t.assert.strictEqual(++order, 2, 'called in childOne') + t.assert.strictEqual(this.pluginName, childOne.pluginName, 'the this binding is the right instance') + done() + }) + childOne.register(async (childTwo, o) => { + childTwo.addHook('onListen', async function () { + t.assert.strictEqual(++order, 3, 'called in childTwo') + t.assert.strictEqual(this.pluginName, childTwo.pluginName, 'the this binding is the right instance') + throw new Error('Error in onListen hook of childTwo') + }) + }) + }) + fastify.listen({ + host: 'localhost', + port: 0 + }, testDone) +}) + +test('non-localhost onListen should be called in order', { skip: isIPv6Missing }, (t, testDone) => { + t.plan(2) + + const fastify = Fastify() + t.after(() => fastify.close()) + + let order = 0 + + fastify.addHook('onListen', function (done) { + t.assert.strictEqual(++order, 1, '1st called in root') + done() + }) + + fastify.addHook('onListen', function (done) { + t.assert.strictEqual(++order, 2, '2nd called in root') + done() + }) + fastify.listen({ + host: '::1', + port: 0 + }, testDone) +}) + +test('non-localhost async onListen should be called in order', { skip: isIPv6Missing }, async t => { + t.plan(2) + const fastify = Fastify() + t.after(() => fastify.close()) + let order = 0 + + fastify.addHook('onListen', async function () { + t.assert.strictEqual(++order, 1, '1st async called in root') + }) + + fastify.addHook('onListen', async function () { + t.assert.strictEqual(++order, 2, '2nd async called in root') + }) + + await fastify.listen({ + host: '::1', + port: 0 + }) +}) + +test('non-localhost sync onListen should log errors as warnings and continue', { skip: isIPv6Missing }, (t, testDone) => { + t.plan(4) + const stream = split(JSON.parse) + const fastify = Fastify({ + forceCloseConnections: false, + logger: { + stream, + level: 'info' + } + }) + t.after(() => fastify.close()) + + stream.on('data', message => { + if (message.msg.includes('FAIL ON LISTEN')) { + t.assert.ok('Logged Error Message') + } + }) + let order = 0 + + fastify.addHook('onListen', function (done) { + t.assert.strictEqual(++order, 1) + done() + }) + + fastify.addHook('onListen', function () { + t.assert.strictEqual(++order, 2) + throw new Error('FAIL ON LISTEN') + }) + + fastify.addHook('onListen', function (done) { + t.assert.strictEqual(++order, 3, 'should still run') + done() + }) + + fastify.listen({ + host: '::1', + port: 0 + }, testDone) +}) + +test('non-localhost async onListen should log errors as warnings and continue', { skip: isIPv6Missing }, async t => { + t.plan(6) + const stream = split(JSON.parse) + const fastify = Fastify({ + forceCloseConnections: false, + logger: { + stream, + level: 'info' + } + }) + t.after(() => fastify.close()) + + stream.on('data', message => { + if (message.msg.includes('FAIL ON LISTEN')) { + t.assert.ok('Logged Error Message') + } + }) + + let order = 0 + + fastify.addHook('onListen', async function () { + t.assert.strictEqual(++order, 1) + t.assert.ok('called in root') + }) + + fastify.addHook('onListen', async function () { + t.assert.strictEqual(++order, 2, '2nd async failed in root') + throw new Error('FAIL ON LISTEN') + }) + + fastify.addHook('onListen', async function () { + t.assert.strictEqual(++order, 3) + t.assert.ok('should still run') + }) + + await fastify.listen({ + host: '::1', + port: 0 + }) +}) + +test('non-localhost Register onListen hook after a plugin inside a plugin', { skip: isIPv6Missing }, (t, testDone) => { + t.plan(3) + const fastify = Fastify() + t.after(() => fastify.close()) + + fastify.register(fp(function (instance, opts, done) { + instance.addHook('onListen', function (done) { + t.assert.ok('called') + done() + }) + done() + })) + + fastify.register(fp(function (instance, opts, done) { + instance.addHook('onListen', function (done) { + t.assert.ok('called') + done() + }) + + instance.addHook('onListen', function (done) { + t.assert.ok('called') + done() + }) + + done() + })) + + fastify.listen({ + host: '::1', + port: 0 + }, testDone) +}) + +test('non-localhost Register onListen hook after a plugin inside a plugin should log errors as warnings and continue', { skip: isIPv6Missing }, (t, testDone) => { + t.plan(6) + const stream = split(JSON.parse) + const fastify = Fastify({ + forceCloseConnections: false, + logger: { + stream, + level: 'info' + } + }) + t.after(() => fastify.close()) + + stream.on('data', message => { + if (message.msg.includes('Plugin Error')) { + t.assert.ok('Logged Error Message') + } + }) + + fastify.register(fp(function (instance, opts, done) { + instance.addHook('onListen', function () { + t.assert.ok('called') + throw new Error('Plugin Error') + }) + done() + })) + + fastify.register(fp(function (instance, opts, done) { + instance.addHook('onListen', function () { + t.assert.ok('called') + throw new Error('Plugin Error') + }) + + instance.addHook('onListen', function () { + t.assert.ok('called') + throw new Error('Plugin Error') + }) + + done() + })) + + fastify.listen({ + host: '::1', + port: 0 + }, testDone) +}) + +test('non-localhost onListen encapsulation should be called in order', { skip: isIPv6Missing }, (t, testDone) => { + t.plan(6) + const fastify = Fastify() + t.after(() => fastify.close()) + + let order = 0 + + fastify.addHook('onListen', function (done) { + t.assert.strictEqual(++order, 1, 'called in root') + t.assert.strictEqual(this.pluginName, fastify.pluginName, 'the this binding is the right instance') + done() + }) + + fastify.register(async (childOne, o) => { + childOne.addHook('onListen', function (done) { + t.assert.strictEqual(++order, 2, 'called in childOne') + t.assert.strictEqual(this.pluginName, childOne.pluginName, 'the this binding is the right instance') + done() + }) + childOne.register(async (childTwo, o) => { + childTwo.addHook('onListen', async function () { + t.assert.strictEqual(++order, 3, 'called in childTwo') + t.assert.strictEqual(this.pluginName, childTwo.pluginName, 'the this binding is the right instance') + }) + }) + }) + fastify.listen({ + host: '::1', + port: 0 + }, testDone) +}) + +test('non-localhost onListen encapsulation should be called in order and should log errors as warnings and continue', { skip: isIPv6Missing }, (t, testDone) => { + t.plan(7) + const stream = split(JSON.parse) + const fastify = Fastify({ + forceCloseConnections: false, + logger: { + stream, + level: 'info' + } + }) + t.after(() => fastify.close()) + + stream.on('data', message => { + if (message.msg.includes('Error in onListen hook of childTwo')) { + t.assert.ok('Logged Error Message') + } + }) + + let order = 0 + + fastify.addHook('onListen', function (done) { + t.assert.strictEqual(++order, 1, 'called in root') + + t.assert.strictEqual(this.pluginName, fastify.pluginName, 'the this binding is the right instance') + done() + }) + + fastify.register(async (childOne, o) => { + childOne.addHook('onListen', function (done) { + t.assert.strictEqual(++order, 2, 'called in childOne') + t.assert.strictEqual(this.pluginName, childOne.pluginName, 'the this binding is the right instance') + done() + }) + childOne.register(async (childTwo, o) => { + childTwo.addHook('onListen', async function () { + t.assert.strictEqual(++order, 3, 'called in childTwo') + t.assert.strictEqual(this.pluginName, childTwo.pluginName, 'the this binding is the right instance') + throw new Error('Error in onListen hook of childTwo') + }) + }) + }) + fastify.listen({ + host: '::1', + port: 0 + }, testDone) +}) + +test('onListen localhost should work in order with callback', (t, testDone) => { + t.plan(4) + const fastify = Fastify() + t.after(() => fastify.close()) + let order = 0 + + fastify.addHook('onListen', function (done) { + t.assert.strictEqual(++order, 1, '1st called in root') + done() + }) + + fastify.addHook('onListen', function (done) { + t.assert.strictEqual(++order, 2, '2nd called in root') + done() + }) + + fastify.listen({ port: 0 }, (err) => { + t.assert.strictEqual(fastify.server.address().address, localhost) + t.assert.ifError(err) + testDone() + }) +}) + +test('onListen localhost should work in order with callback in async', (t, testDone) => { + t.plan(4) + const fastify = Fastify() + t.after(() => fastify.close()) + let order = 0 + + fastify.addHook('onListen', async function () { + t.assert.strictEqual(++order, 1, '1st called in root') + }) + + fastify.addHook('onListen', async function () { + t.assert.strictEqual(++order, 2, '2nd called in root') + }) + + fastify.listen({ host: 'localhost', port: 0 }, (err) => { + t.assert.strictEqual(fastify.server.address().address, localhost) + t.assert.ifError(err) + testDone() + }) +}) + +test('onListen localhost sync with callback should log errors as warnings and continue', (t, testDone) => { + t.plan(6) + const stream = split(JSON.parse) + const fastify = Fastify({ + forceCloseConnections: false, + logger: { + stream, + level: 'info' + } + }) + t.after(() => fastify.close()) + + stream.on('data', message => { + if (message.msg.includes('FAIL ON LISTEN')) { + t.assert.ok('Logged Error Message') + } + }) + + let order = 0 + + fastify.addHook('onListen', function (done) { + t.assert.strictEqual(++order, 1, '1st called in root') + done() + }) + + fastify.addHook('onListen', function () { + t.assert.strictEqual(++order, 2, 'error sync called in root') + throw new Error('FAIL ON LISTEN') + }) + + fastify.addHook('onListen', function (done) { + t.assert.strictEqual(++order, 3, '1st called in root') + done() + }) + + fastify.listen({ port: 0 }, (err) => { + t.assert.ifError(err) + t.assert.strictEqual(fastify.server.address().address, localhost) + testDone() + }) +}) + +test('onListen localhost async with callback should log errors as warnings and continue', (t, testDone) => { + t.plan(6) + const stream = split(JSON.parse) + const fastify = Fastify({ + forceCloseConnections: false, + logger: { + stream, + level: 'info' + } + }) + t.after(() => fastify.close()) + + stream.on('data', message => { + if (message.msg.includes('FAIL ON LISTEN')) { + t.assert.ok('Logged Error Message') + } + }) + + let order = 0 + + fastify.addHook('onListen', async function () { + t.assert.ok('1st called in root') + }) + + fastify.addHook('onListen', async function () { + t.assert.strictEqual(++order, 1, 'error sync called in root') + throw new Error('FAIL ON LISTEN') + }) + + fastify.addHook('onListen', async function () { + t.assert.ok('3rd called in root') + }) + + fastify.listen({ port: 0 }, (err) => { + t.assert.ifError(err) + t.assert.strictEqual(fastify.server.address().address, localhost) + testDone() + }) +}) + +test('Register onListen hook localhost with callback after a plugin inside a plugin', (t, testDone) => { + t.plan(5) + const fastify = Fastify() + t.after(() => fastify.close()) + + fastify.register(fp(function (instance, opts, done) { + instance.addHook('onListen', function (done) { + t.assert.ok('called') + done() + }) + done() + })) + + fastify.register(fp(function (instance, opts, done) { + instance.addHook('onListen', function (done) { + t.assert.ok('called') + done() + }) + + instance.addHook('onListen', function (done) { + t.assert.ok('called') + done() + }) + + done() + })) + + fastify.listen({ port: 0 }, (err) => { + t.assert.strictEqual(fastify.server.address().address, localhost) + t.assert.ifError(err) + testDone() + }) +}) + +test('onListen localhost with callback encapsulation should be called in order', (t, testDone) => { + t.plan(8) + const fastify = Fastify() + t.after(() => fastify.close()) + + let order = 0 + + fastify.addHook('onListen', function (done) { + t.assert.strictEqual(++order, 1, 'called in root') + t.assert.strictEqual(this.pluginName, fastify.pluginName, 'the this binding is the right instance') + done() + }) + + fastify.register(async (childOne, o) => { + childOne.addHook('onListen', function (done) { + t.assert.strictEqual(++order, 2, 'called in childOne') + t.assert.strictEqual(this.pluginName, childOne.pluginName, 'the this binding is the right instance') + done() + }) + childOne.register(async (childTwo, o) => { + childTwo.addHook('onListen', async function () { + t.assert.strictEqual(++order, 3, 'called in childTwo') + t.assert.strictEqual(this.pluginName, childTwo.pluginName, 'the this binding is the right instance') + }) + }) + }) + fastify.listen({ port: 0 }, (err) => { + t.assert.strictEqual(fastify.server.address().address, localhost) + t.assert.ifError(err) + testDone() + }) +}) + +test('onListen non-localhost should work in order with callback in sync', { skip: isIPv6Missing }, (t, testDone) => { + t.plan(4) + const fastify = Fastify() + t.after(() => fastify.close()) + let order = 0 + + fastify.addHook('onListen', function (done) { + t.assert.strictEqual(++order, 1, '1st called in root') + done() + }) + + fastify.addHook('onListen', function (done) { + t.assert.strictEqual(++order, 2, '2nd called in root') + done() + }) + + fastify.listen({ host: '::1', port: 0 }, (err) => { + t.assert.strictEqual(fastify.server.address().address, '::1') + t.assert.ifError(err) + testDone() + }) +}) + +test('onListen non-localhost should work in order with callback in async', { skip: isIPv6Missing }, (t, testDone) => { + t.plan(4) + const fastify = Fastify() + t.after(() => fastify.close()) + let order = 0 + + fastify.addHook('onListen', async function () { + t.assert.strictEqual(++order, 1, '1st called in root') + }) + + fastify.addHook('onListen', async function () { + t.assert.strictEqual(++order, 2, '2nd called in root') + }) + + fastify.listen({ host: '::1', port: 0 }, (err) => { + t.assert.strictEqual(fastify.server.address().address, '::1') + t.assert.ifError(err) + testDone() + }) +}) + +test('onListen non-localhost sync with callback should log errors as warnings and continue', { skip: isIPv6Missing }, (t, testDone) => { + t.plan(8) + + const stream = split(JSON.parse) + const fastify = Fastify({ + forceCloseConnections: false, + logger: { + stream, + level: 'info' + } + }) + t.after(() => fastify.close()) + + stream.on('data', message => { + if (message.msg.includes('FAIL ON LISTEN')) { + t.assert.ok('Logged Error Message') + } + }) + + let order = 0 + + fastify.addHook('onListen', function (done) { + t.assert.strictEqual(++order, 1) + t.assert.ok('1st called in root') + done() + }) + + fastify.addHook('onListen', function () { + t.assert.strictEqual(++order, 2) + throw new Error('FAIL ON LISTEN') + }) + + fastify.addHook('onListen', function (done) { + t.assert.strictEqual(++order, 3) + t.assert.ok('3rd called in root') + done() + }) + + fastify.listen({ host: '::1', port: 0 }, (err) => { + t.assert.ifError(err) + t.assert.strictEqual(fastify.server.address().address, '::1') + testDone() + }) +}) + +test('onListen non-localhost async with callback should log errors as warnings and continue', { skip: isIPv6Missing }, (t, testDone) => { + t.plan(8) + + const stream = split(JSON.parse) + const fastify = Fastify({ + forceCloseConnections: false, + logger: { + stream, + level: 'info' + } + }) + t.after(() => fastify.close()) + + stream.on('data', message => { + if (message.msg.includes('FAIL ON LISTEN')) { + t.assert.ok('Logged Error Message') + } + }) + + let order = 0 + + fastify.addHook('onListen', async function () { + t.assert.strictEqual(++order, 1) + t.assert.ok('1st called in root') + }) + + fastify.addHook('onListen', async function () { + t.assert.strictEqual(++order, 2, 'error sync called in root') + throw new Error('FAIL ON LISTEN') + }) + + fastify.addHook('onListen', async function () { + t.assert.strictEqual(++order, 3) + t.assert.ok('3rd called in root') + }) + + fastify.listen({ host: '::1', port: 0 }, (err) => { + t.assert.ifError(err) + t.assert.strictEqual(fastify.server.address().address, '::1') + testDone() + }) +}) + +test('Register onListen hook non-localhost with callback after a plugin inside a plugin', { skip: isIPv6Missing }, (t, testDone) => { + t.plan(5) + const fastify = Fastify() + t.after(() => fastify.close()) + + fastify.register(fp(function (instance, opts, done) { + instance.addHook('onListen', function (done) { + t.assert.ok('called') + done() + }) + done() + })) + + fastify.register(fp(function (instance, opts, done) { + instance.addHook('onListen', function (done) { + t.assert.ok('called') + done() + }) + + instance.addHook('onListen', function (done) { + t.assert.ok('called') + done() + }) + + done() + })) + + fastify.listen({ host: '::1', port: 0 }, (err) => { + t.assert.strictEqual(fastify.server.address().address, '::1') + t.assert.ifError(err) + testDone() + }) +}) + +test('onListen non-localhost with callback encapsulation should be called in order', { skip: isIPv6Missing }, (t, testDone) => { + t.plan(8) + const fastify = Fastify() + t.after(() => fastify.close()) + + let order = 0 + + fastify.addHook('onListen', function (done) { + t.assert.strictEqual(++order, 1, 'called in root') + t.assert.strictEqual(this.pluginName, fastify.pluginName, 'the this binding is the right instance') + done() + }) + + fastify.register(async (childOne, o) => { + childOne.addHook('onListen', function (done) { + t.assert.strictEqual(++order, 2, 'called in childOne') + t.assert.strictEqual(this.pluginName, childOne.pluginName, 'the this binding is the right instance') + done() + }) + childOne.register(async (childTwo, o) => { + childTwo.addHook('onListen', async function () { + t.assert.strictEqual(++order, 3, 'called in childTwo') + t.assert.strictEqual(this.pluginName, childTwo.pluginName, 'the this binding is the right instance') + }) + }) + }) + fastify.listen({ host: '::1', port: 0 }, (err) => { + t.assert.strictEqual(fastify.server.address().address, '::1') + t.assert.ifError(err) + testDone() + }) +}) + +test('onListen sync should work if user does not pass done', (t, testDone) => { + t.plan(2) + const fastify = Fastify() + t.after(() => fastify.close()) + let order = 0 + + fastify.addHook('onListen', function () { + t.assert.strictEqual(++order, 1, '1st called in root') + }) + + fastify.addHook('onListen', function () { + t.assert.strictEqual(++order, 2, '2nd called in root') + }) + + fastify.listen({ + host: 'localhost', + port: 0 + }, testDone) +}) + +test('async onListen does not need to be awaited', (t, testDone) => { + const fastify = Fastify() + t.after(() => fastify.close()) + let order = 0 + + fastify.addHook('onListen', async function () { + t.assert.strictEqual(++order, 1, '1st async called in root') + }) + + fastify.addHook('onListen', async function () { + t.assert.strictEqual(++order, 2, '2nd async called in root') + t.end() + }) + + fastify.listen({ + host: 'localhost', + port: 0 + }, testDone) +}) + +test('onListen hooks do not block /1', (t, testDone) => { + t.plan(2) + + const fastify = Fastify() + t.after(() => fastify.close()) + + fastify.addHook('onListen', function (done) { + t.assert.strictEqual(fastify[kState].listening, true) + done() + }) + + fastify.listen({ + host: 'localhost', + port: 0 + }, err => { + t.assert.ifError(err) + testDone() + }) +}) + +test('onListen hooks do not block /2', async t => { + t.plan(1) + + const fastify = Fastify() + t.after(() => fastify.close()) + + fastify.addHook('onListen', async function () { + t.assert.strictEqual(fastify[kState].listening, true) + }) + + await fastify.listen({ + host: 'localhost', + port: 0 + }) +}) diff --git a/services/slides/node_modules/fastify/test/hooks.on-ready.test.js b/services/slides/node_modules/fastify/test/hooks.on-ready.test.js new file mode 100644 index 0000000000000000000000000000000000000000..4e0379eb34f642e27196f473698c653849720f3e --- /dev/null +++ b/services/slides/node_modules/fastify/test/hooks.on-ready.test.js @@ -0,0 +1,421 @@ +'use strict' + +const { test } = require('node:test') +const Fastify = require('../fastify') +const immediate = require('node:util').promisify(setImmediate) + +test('onReady should be called in order', (t, done) => { + t.plan(7) + const fastify = Fastify() + + let order = 0 + + fastify.addHook('onReady', function (done) { + t.assert.strictEqual(order++, 0, 'called in root') + t.assert.strictEqual(this.pluginName, fastify.pluginName, 'the this binding is the right instance') + done() + }) + + fastify.register(async (childOne, o) => { + childOne.addHook('onReady', function (done) { + t.assert.strictEqual(order++, 1, 'called in childOne') + t.assert.strictEqual(this.pluginName, childOne.pluginName, 'the this binding is the right instance') + done() + }) + + childOne.register(async (childTwo, o) => { + childTwo.addHook('onReady', async function () { + await immediate() + t.assert.strictEqual(order++, 2, 'called in childTwo') + t.assert.strictEqual(this.pluginName, childTwo.pluginName, 'the this binding is the right instance') + }) + }) + }) + + fastify.ready(err => { + t.assert.ifError(err) + done() + }) +}) + +test('onReady should be called once', async (t) => { + const app = Fastify() + let counter = 0 + + app.addHook('onReady', async function () { + counter++ + }) + + const promises = [1, 2, 3, 4, 5].map((id) => app.ready().then(() => id)) + + const result = await Promise.race(promises) + + t.assert.strictEqual(result, 1, 'Should resolve in order') + t.assert.strictEqual(counter, 1, 'Should call onReady only once') +}) + +test('async onReady should be called in order', async t => { + t.plan(7) + const fastify = Fastify() + + let order = 0 + + fastify.addHook('onReady', async function () { + await immediate() + t.assert.strictEqual(order++, 0, 'called in root') + t.assert.strictEqual(this.pluginName, fastify.pluginName, 'the this binding is the right instance') + }) + + fastify.register(async (childOne, o) => { + childOne.addHook('onReady', async function () { + await immediate() + t.assert.strictEqual(order++, 1, 'called in childOne') + t.assert.strictEqual(this.pluginName, childOne.pluginName, 'the this binding is the right instance') + }) + + childOne.register(async (childTwo, o) => { + childTwo.addHook('onReady', async function () { + await immediate() + t.assert.strictEqual(order++, 2, 'called in childTwo') + t.assert.strictEqual(this.pluginName, childTwo.pluginName, 'the this binding is the right instance') + }) + }) + }) + + await fastify.ready() + t.assert.ok('ready') +}) + +test('mix ready and onReady', async t => { + t.plan(2) + const fastify = Fastify() + let order = 0 + + fastify.addHook('onReady', async function () { + await immediate() + order++ + }) + + await fastify.ready() + t.assert.strictEqual(order, 1) + + await fastify.ready() + t.assert.strictEqual(order, 1, 'ready hooks execute once') +}) + +test('listen and onReady order', async t => { + t.plan(9) + + const fastify = Fastify() + let order = 0 + + fastify.register((instance, opts, done) => { + instance.ready(checkOrder.bind(null, 0)) + instance.addHook('onReady', checkOrder.bind(null, 4)) + + instance.register((subinstance, opts, done) => { + subinstance.ready(checkOrder.bind(null, 1)) + subinstance.addHook('onReady', checkOrder.bind(null, 5)) + + subinstance.register((realSubInstance, opts, done) => { + realSubInstance.ready(checkOrder.bind(null, 2)) + realSubInstance.addHook('onReady', checkOrder.bind(null, 6)) + done() + }) + done() + }) + done() + }) + + fastify.addHook('onReady', checkOrder.bind(null, 3)) + + await fastify.ready() + t.assert.ok('trigger the onReady') + await fastify.listen({ port: 0 }) + t.assert.ok('do not trigger the onReady') + + await fastify.close() + + function checkOrder (shouldbe) { + t.assert.strictEqual(order, shouldbe) + order++ + } +}) + +test('multiple ready calls', async t => { + t.plan(11) + + const fastify = Fastify() + let order = 0 + + fastify.register(async (instance, opts) => { + instance.ready(checkOrder.bind(null, 1)) + instance.addHook('onReady', checkOrder.bind(null, 6)) + + await instance.register(async (subinstance, opts) => { + subinstance.ready(checkOrder.bind(null, 2)) + subinstance.addHook('onReady', checkOrder.bind(null, 7)) + }) + + t.assert.strictEqual(order, 0, 'ready and hooks not triggered yet') + order++ + }) + + fastify.addHook('onReady', checkOrder.bind(null, 3)) + fastify.addHook('onReady', checkOrder.bind(null, 4)) + fastify.addHook('onReady', checkOrder.bind(null, 5)) + + await fastify.ready() + t.assert.ok('trigger the onReady') + + await fastify.ready() + t.assert.ok('do not trigger the onReady') + + await fastify.ready() + t.assert.ok('do not trigger the onReady') + + function checkOrder (shouldbe) { + t.assert.strictEqual(order, shouldbe) + order++ + } +}) + +test('onReady should manage error in sync', (t, done) => { + t.plan(4) + const fastify = Fastify() + + fastify.addHook('onReady', function (done) { + t.assert.ok('called in root') + done() + }) + + fastify.register(async (childOne, o) => { + childOne.addHook('onReady', function (done) { + t.assert.ok('called in childOne') + done(new Error('FAIL ON READY')) + }) + + childOne.register(async (childTwo, o) => { + childTwo.addHook('onReady', async function () { + t.assert.fail('should not be called') + }) + }) + }) + + fastify.ready(err => { + t.assert.ok(err) + t.assert.strictEqual(err.message, 'FAIL ON READY') + done() + }) +}) + +test('onReady should manage error in async', (t, done) => { + t.plan(4) + const fastify = Fastify() + + fastify.addHook('onReady', function (done) { + t.assert.ok('called in root') + done() + }) + + fastify.register(async (childOne, o) => { + childOne.addHook('onReady', async function () { + t.assert.ok('called in childOne') + throw new Error('FAIL ON READY') + }) + + childOne.register(async (childTwo, o) => { + childTwo.addHook('onReady', async function () { + t.assert.fail('should not be called') + }) + }) + }) + + fastify.ready(err => { + t.assert.ok(err) + t.assert.strictEqual(err.message, 'FAIL ON READY') + done() + }) +}) + +test('onReady should manage sync error', (t, done) => { + t.plan(4) + const fastify = Fastify() + + fastify.addHook('onReady', function (done) { + t.assert.ok('called in root') + done() + }) + + fastify.register(async (childOne, o) => { + childOne.addHook('onReady', function (done) { + t.assert.ok('called in childOne') + throw new Error('FAIL UNWANTED SYNC EXCEPTION') + }) + + childOne.register(async (childTwo, o) => { + childTwo.addHook('onReady', async function () { + t.assert.fail('should not be called') + }) + }) + }) + + fastify.ready(err => { + t.assert.ok(err) + t.assert.strictEqual(err.message, 'FAIL UNWANTED SYNC EXCEPTION') + done() + }) +}) + +test('onReady can not add decorators or application hooks', (t, done) => { + t.plan(3) + const fastify = Fastify() + + fastify.addHook('onReady', function (done) { + t.assert.ok('called in root') + fastify.decorate('test', () => {}) + + fastify.addHook('onReady', async function () { + t.assert.fail('it will be not called') + }) + done() + }) + + fastify.addHook('onReady', function (done) { + t.assert.ok(this.hasDecorator('test')) + done() + }) + + fastify.ready(err => { + t.assert.ifError(err) + done() + }) +}) + +test('onReady cannot add lifecycle hooks', (t, done) => { + t.plan(5) + const fastify = Fastify() + + fastify.addHook('onReady', function (done) { + t.assert.ok('called in root') + try { + fastify.addHook('onRequest', (request, reply, done) => {}) + } catch (error) { + t.assert.ok(error) + t.assert.strictEqual(error.message, 'Root plugin has already booted') + // TODO: look where the error pops up + t.assert.strictEqual(error.code, 'AVV_ERR_ROOT_PLG_BOOTED') + done(error) + } + }) + + fastify.addHook('onRequest', (request, reply, done) => {}) + fastify.get('/', async () => 'hello') + + fastify.ready((err) => { + t.assert.ok(err) + done() + }) +}) + +test('onReady throw loading error', t => { + t.plan(2) + const fastify = Fastify() + + try { + fastify.addHook('onReady', async function (done) {}) + } catch (e) { + t.assert.strictEqual(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER') + t.assert.ok(e.message === 'Async function has too many arguments. Async hooks should not use the \'done\' argument.') + } +}) + +test('onReady does not call done', (t, done) => { + t.plan(6) + const fastify = Fastify({ pluginTimeout: 500 }) + + fastify.addHook('onReady', function someHookName (done) { + t.assert.ok('called in root') + // done() // don't call done to test timeout + }) + + fastify.ready(err => { + t.assert.ok(err) + t.assert.strictEqual(err.message, 'A callback for \'onReady\' hook "someHookName" timed out. You may have forgotten to call \'done\' function or to resolve a Promise') + t.assert.strictEqual(err.code, 'FST_ERR_HOOK_TIMEOUT') + t.assert.ok(err.cause) + t.assert.strictEqual(err.cause.code, 'AVV_ERR_READY_TIMEOUT') + done() + }) +}) + +test('onReady execution order', (t, done) => { + t.plan(3) + const fastify = Fastify({ }) + + let i = 0 + fastify.ready(() => { i++; t.assert.strictEqual(i, 1) }) + fastify.ready(() => { i++; t.assert.strictEqual(i, 2) }) + fastify.ready(() => { + i++ + t.assert.strictEqual(i, 3) + done() + }) +}) + +test('ready return the server with callback', (t, done) => { + t.plan(2) + const fastify = Fastify() + + fastify.ready((err, instance) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(instance, fastify) + done() + }) +}) + +test('ready return the server with Promise', async t => { + t.plan(1) + const fastify = Fastify() + + await fastify.ready() + .then(instance => { t.assert.deepStrictEqual(instance, fastify) }) + .catch(err => { t.assert.fail(err) }) +}) + +test('ready return registered', async t => { + t.plan(4) + const fastify = Fastify() + + fastify.register((one, opts, done) => { + one.ready().then(itself => { t.assert.deepStrictEqual(itself, one) }) + done() + }) + + fastify.register((two, opts, done) => { + two.ready().then(itself => { t.assert.deepStrictEqual(itself, two) }) + + two.register((twoDotOne, opts, done) => { + twoDotOne.ready().then(itself => { t.assert.deepStrictEqual(itself, twoDotOne) }) + done() + }) + done() + }) + + await fastify.ready() + .then(instance => { t.assert.deepStrictEqual(instance, fastify) }) + .catch(err => { t.assert.fail(err) }) +}) + +test('do not crash with error in follow up onReady hook', async t => { + const fastify = Fastify() + + fastify.addHook('onReady', async function () { + }) + + fastify.addHook('onReady', function () { + throw new Error('kaboom') + }) + + await t.assert.rejects(fastify.ready()) +}) diff --git a/services/slides/node_modules/fastify/test/hooks.test.js b/services/slides/node_modules/fastify/test/hooks.test.js new file mode 100644 index 0000000000000000000000000000000000000000..f5aab8b2360a48b0731c21cde5ecbcc0ee462c71 --- /dev/null +++ b/services/slides/node_modules/fastify/test/hooks.test.js @@ -0,0 +1,3578 @@ +'use strict' + +const { test } = require('node:test') +const stream = require('node:stream') +const Fastify = require('..') +const fp = require('fastify-plugin') +const fs = require('node:fs') +const split = require('split2') +const symbols = require('../lib/symbols.js') +const payload = { hello: 'world' } +const proxyquire = require('proxyquire') +const { connect } = require('node:net') +const { sleep } = require('./helper') +const { waitForCb } = require('./toolkit.js') + +process.removeAllListeners('warning') + +test('hooks', async t => { + t.plan(48) + const fastify = Fastify({ exposeHeadRoutes: false }) + + try { + fastify.addHook('preHandler', function (request, reply, done) { + t.assert.strictEqual(request.test, 'the request is coming') + t.assert.strictEqual(reply.test, 'the reply has come') + if (request.raw.method === 'HEAD') { + done(new Error('some error')) + } else { + done() + } + }) + t.assert.ok('should pass') + } catch (e) { + t.assert.fail() + } + + try { + fastify.addHook('preHandler', null) + } catch (e) { + t.assert.strictEqual(e.code, 'FST_ERR_HOOK_INVALID_HANDLER') + t.assert.strictEqual(e.message, 'preHandler hook should be a function, instead got null') + t.assert.ok('should pass') + } + + try { + fastify.addHook('preParsing') + } catch (e) { + t.assert.strictEqual(e.code, 'FST_ERR_HOOK_INVALID_HANDLER') + t.assert.strictEqual(e.message, 'preParsing hook should be a function, instead got undefined') + t.assert.ok('should pass') + } + + try { + fastify.addHook('preParsing', function (request, reply, payload, done) { + request.preParsing = true + t.assert.strictEqual(request.test, 'the request is coming') + t.assert.strictEqual(reply.test, 'the reply has come') + done() + }) + t.assert.ok('should pass') + } catch (e) { + t.assert.fail() + } + + try { + fastify.addHook('preParsing', function (request, reply, payload, done) { + request.preParsing = true + t.assert.strictEqual(request.test, 'the request is coming') + t.assert.strictEqual(reply.test, 'the reply has come') + done() + }) + t.assert.ok('should pass') + } catch (e) { + t.assert.fail() + } + + try { + fastify.addHook('preValidation', function (request, reply, done) { + t.assert.strictEqual(request.preParsing, true) + t.assert.strictEqual(request.test, 'the request is coming') + t.assert.strictEqual(reply.test, 'the reply has come') + done() + }) + t.assert.ok('should pass') + } catch (e) { + t.assert.fail() + } + + try { + fastify.addHook('preSerialization', function (request, reply, payload, done) { + t.assert.ok('preSerialization called') + done() + }) + t.assert.ok('should pass') + } catch (e) { + t.assert.fail() + } + + try { + fastify.addHook('onRequest', function (request, reply, done) { + request.test = 'the request is coming' + reply.test = 'the reply has come' + if (request.raw.method === 'DELETE') { + done(new Error('some error')) + } else { + done() + } + }) + t.assert.ok('should pass') + } catch (e) { + t.assert.fail() + } + + fastify.addHook('onResponse', function (request, reply, done) { + t.assert.ok('onResponse called') + done() + }) + + fastify.addHook('onSend', function (req, reply, thePayload, done) { + t.assert.ok('onSend called') + done() + }) + + fastify.route({ + method: 'GET', + url: '/', + handler: function (req, reply) { + t.assert.strictEqual(req.test, 'the request is coming') + t.assert.strictEqual(reply.test, 'the reply has come') + reply.code(200).send(payload) + }, + onResponse: function (req, reply, done) { + t.assert.ok('onResponse inside hook') + }, + response: { + 200: { + type: 'object' + } + } + }) + + fastify.head('/', function (req, reply) { + reply.code(200).send(payload) + }) + + fastify.delete('/', function (req, reply) { + reply.code(200).send(payload) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) + + const getResult = await fetch(fastifyServer) + t.assert.ok(getResult.ok) + t.assert.strictEqual(getResult.status, 200) + const getBody = await getResult.text() + t.assert.strictEqual(getResult.headers.get('content-length'), '' + getBody.length) + t.assert.deepStrictEqual(JSON.parse(getBody), { hello: 'world' }) + + const headResult = await fetch(fastifyServer, { method: 'HEAD' }) + t.assert.ok(!headResult.ok) + t.assert.strictEqual(headResult.status, 500) + + const deleteResult = await fetch(fastifyServer, { method: 'DELETE' }) + t.assert.ok(!deleteResult.ok) + t.assert.strictEqual(deleteResult.status, 500) +}) + +test('onRequest hook should support encapsulation / 1', (t, testDone) => { + t.plan(5) + const fastify = Fastify() + + fastify.register((instance, opts, done) => { + instance.addHook('onRequest', (req, reply, done) => { + t.assert.strictEqual(req.raw.url, '/plugin') + done() + }) + + instance.get('/plugin', (request, reply) => { + reply.send() + }) + + done() + }) + + fastify.get('/root', (request, reply) => { + reply.send() + }) + + fastify.inject('/root', (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + + fastify.inject('/plugin', (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + testDone() + }) + }) +}) + +test('onRequest hook should support encapsulation / 2', (t, testDone) => { + t.plan(3) + const fastify = Fastify() + let pluginInstance + + fastify.addHook('onRequest', () => { }) + + fastify.register((instance, opts, done) => { + instance.addHook('onRequest', () => { }) + pluginInstance = instance + done() + }) + + fastify.ready(err => { + t.assert.ifError(err) + t.assert.strictEqual(fastify[symbols.kHooks].onRequest.length, 1) + t.assert.strictEqual(pluginInstance[symbols.kHooks].onRequest.length, 2) + testDone() + }) +}) + +test('onRequest hook should support encapsulation / 3', async t => { + t.plan(19) + const fastify = Fastify() + fastify.decorate('hello', 'world') + + fastify.addHook('onRequest', function (req, reply, done) { + t.assert.ok(this.hello) + t.assert.ok(this.hello2) + req.first = true + done() + }) + + fastify.decorate('hello2', 'world') + + fastify.get('/first', (req, reply) => { + t.assert.ok(req.first) + t.assert.ok(!req.second) + reply.send({ hello: 'world' }) + }) + + fastify.register((instance, opts, done) => { + instance.decorate('hello3', 'world') + instance.addHook('onRequest', function (req, reply, done) { + t.assert.ok(this.hello) + t.assert.ok(this.hello2) + t.assert.ok(this.hello3) + req.second = true + done() + }) + + instance.get('/second', (req, reply) => { + t.assert.ok(req.first) + t.assert.ok(req.second) + reply.send({ hello: 'world' }) + }) + + done() + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) + + const firstResult = await fetch(fastifyServer + '/first', { method: 'GET' }) + t.assert.ok(firstResult.ok) + t.assert.strictEqual(firstResult.status, 200) + const firstBody = await firstResult.text() + t.assert.strictEqual(firstResult.headers.get('content-length'), '' + firstBody.length) + t.assert.deepStrictEqual(JSON.parse(firstBody), { hello: 'world' }) + + const secondResult = await fetch(fastifyServer + '/second', { method: 'GET' }) + t.assert.ok(secondResult.ok) + t.assert.strictEqual(secondResult.status, 200) + const secondBody = await secondResult.text() + t.assert.strictEqual(secondResult.headers.get('content-length'), '' + secondBody.length) + t.assert.deepStrictEqual(JSON.parse(secondBody), { hello: 'world' }) +}) + +test('preHandler hook should support encapsulation / 5', async t => { + t.plan(16) + const fastify = Fastify() + t.after(() => { fastify.close() }) + fastify.decorate('hello', 'world') + + fastify.addHook('preHandler', function (req, res, done) { + t.assert.ok(this.hello) + req.first = true + done() + }) + + fastify.get('/first', (req, reply) => { + t.assert.ok(req.first) + t.assert.ok(!req.second) + reply.send({ hello: 'world' }) + }) + + fastify.register((instance, opts, done) => { + instance.decorate('hello2', 'world') + instance.addHook('preHandler', function (req, res, done) { + t.assert.ok(this.hello) + t.assert.ok(this.hello2) + req.second = true + done() + }) + + instance.get('/second', (req, reply) => { + t.assert.ok(req.first) + t.assert.ok(req.second) + reply.send({ hello: 'world' }) + }) + + done() + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + + const firstResult = await fetch(fastifyServer + '/first') + t.assert.ok(firstResult.ok) + t.assert.strictEqual(firstResult.status, 200) + const firstBody = await firstResult.text() + t.assert.strictEqual(firstResult.headers.get('content-length'), '' + firstBody.length) + t.assert.deepStrictEqual(JSON.parse(firstBody), { hello: 'world' }) + + const secondResult = await fetch(fastifyServer + '/second') + t.assert.ok(secondResult.ok) + t.assert.strictEqual(secondResult.status, 200) + const secondBody = await secondResult.text() + t.assert.strictEqual(secondResult.headers.get('content-length'), '' + secondBody.length) + t.assert.deepStrictEqual(JSON.parse(secondBody), { hello: 'world' }) +}) + +test('onRoute hook should be called / 1', (t, testDone) => { + t.plan(2) + const fastify = Fastify({ exposeHeadRoutes: false }) + + fastify.register((instance, opts, done) => { + instance.addHook('onRoute', () => { + t.assert.ok('should pass') + }) + instance.get('/', opts, function (req, reply) { + reply.send() + }) + done() + }) + + fastify.ready(err => { + t.assert.ifError(err) + testDone() + }) +}) + +test('onRoute hook should be called / 2', (t, testDone) => { + t.plan(5) + let firstHandler = 0 + let secondHandler = 0 + const fastify = Fastify({ exposeHeadRoutes: false }) + fastify.addHook('onRoute', (route) => { + t.assert.ok('should pass') + firstHandler++ + }) + + fastify.register((instance, opts, done) => { + instance.addHook('onRoute', (route) => { + t.assert.ok('should pass') + secondHandler++ + }) + instance.get('/', opts, function (req, reply) { + reply.send() + }) + done() + }) + .after(() => { + t.assert.strictEqual(firstHandler, 1) + t.assert.strictEqual(secondHandler, 1) + }) + + fastify.ready(err => { + t.assert.ifError(err) + testDone() + }) +}) + +test('onRoute hook should be called / 3', (t, testDone) => { + t.plan(5) + const fastify = Fastify({ exposeHeadRoutes: false }) + + function handler (req, reply) { + reply.send() + } + + fastify.addHook('onRoute', (route) => { + t.assert.ok('should pass') + }) + + fastify.register((instance, opts, done) => { + instance.addHook('onRoute', (route) => { + t.assert.ok('should pass') + }) + instance.get('/a', handler) + done() + }) + .after((err, done) => { + t.assert.ifError(err) + setTimeout(() => { + fastify.get('/b', handler) + done() + }, 10) + }) + + fastify.ready(err => { + t.assert.ifError(err) + testDone() + }) +}) + +test('onRoute hook should be called (encapsulation support) / 4', (t, testDone) => { + t.plan(4) + const fastify = Fastify({ exposeHeadRoutes: false }) + + fastify.addHook('onRoute', () => { + t.assert.ok('should pass') + }) + + fastify.register((instance, opts, done) => { + instance.addHook('onRoute', () => { + t.assert.ok('should pass') + }) + instance.get('/nested', opts, function (req, reply) { + reply.send() + }) + done() + }) + + fastify.get('/', function (req, reply) { + reply.send() + }) + + fastify.ready(err => { + t.assert.ifError(err) + testDone() + }) +}) + +test('onRoute hook should be called (encapsulation support) / 5', (t, testDone) => { + t.plan(2) + const fastify = Fastify({ exposeHeadRoutes: false }) + + fastify.get('/first', function (req, reply) { + reply.send() + }) + + fastify.register((instance, opts, done) => { + instance.addHook('onRoute', () => { + t.assert.ok('should pass') + }) + instance.get('/nested', opts, function (req, reply) { + reply.send() + }) + done() + }) + + fastify.get('/second', function (req, reply) { + reply.send() + }) + + fastify.ready(err => { + t.assert.ifError(err) + testDone() + }) +}) + +test('onRoute hook should be called (encapsulation support) / 6', (t, testDone) => { + t.plan(1) + const fastify = Fastify({ exposeHeadRoutes: false }) + + fastify.get('/first', function (req, reply) { + reply.send() + }) + + fastify.addHook('onRoute', () => { + t.assert.fail('This should not be called') + }) + + fastify.ready(err => { + t.assert.ifError(err) + testDone() + }) +}) + +test('onRoute should keep the context', (t, testDone) => { + t.plan(4) + const fastify = Fastify({ exposeHeadRoutes: false }) + fastify.register((instance, opts, done) => { + instance.decorate('test', true) + instance.addHook('onRoute', onRoute) + t.assert.ok(instance.prototype === fastify.prototype) + + function onRoute (route) { + t.assert.ok(this.test) + t.assert.strictEqual(this, instance) + } + + instance.get('/', opts, function (req, reply) { + reply.send() + }) + + done() + }) + + fastify.close((err) => { + t.assert.ifError(err) + testDone() + }) +}) + +test('onRoute hook should pass correct route', (t, testDone) => { + t.plan(9) + const fastify = Fastify({ exposeHeadRoutes: false }) + fastify.addHook('onRoute', (route) => { + t.assert.strictEqual(route.method, 'GET') + t.assert.strictEqual(route.url, '/') + t.assert.strictEqual(route.path, '/') + t.assert.strictEqual(route.routePath, '/') + }) + + fastify.register((instance, opts, done) => { + instance.addHook('onRoute', (route) => { + t.assert.strictEqual(route.method, 'GET') + t.assert.strictEqual(route.url, '/') + t.assert.strictEqual(route.path, '/') + t.assert.strictEqual(route.routePath, '/') + }) + instance.get('/', opts, function (req, reply) { + reply.send() + }) + done() + }) + + fastify.ready(err => { + t.assert.ifError(err) + testDone() + }) +}) + +test('onRoute hook should pass correct route with custom prefix', (t, testDone) => { + t.plan(11) + const fastify = Fastify({ exposeHeadRoutes: false }) + fastify.addHook('onRoute', function (route) { + t.assert.strictEqual(route.method, 'GET') + t.assert.strictEqual(route.url, '/v1/foo') + t.assert.strictEqual(route.path, '/v1/foo') + t.assert.strictEqual(route.routePath, '/foo') + t.assert.strictEqual(route.prefix, '/v1') + }) + + fastify.register((instance, opts, done) => { + instance.addHook('onRoute', function (route) { + t.assert.strictEqual(route.method, 'GET') + t.assert.strictEqual(route.url, '/v1/foo') + t.assert.strictEqual(route.path, '/v1/foo') + t.assert.strictEqual(route.routePath, '/foo') + t.assert.strictEqual(route.prefix, '/v1') + }) + instance.get('/foo', opts, function (req, reply) { + reply.send() + }) + done() + }, { prefix: '/v1' }) + + fastify.ready(err => { + t.assert.ifError(err) + testDone() + }) +}) + +test('onRoute hook should pass correct route with custom options', (t, testDone) => { + t.plan(6) + const fastify = Fastify({ exposeHeadRoutes: false }) + fastify.register((instance, opts, done) => { + instance.addHook('onRoute', function (route) { + t.assert.strictEqual(route.method, 'GET') + t.assert.strictEqual(route.url, '/foo') + t.assert.strictEqual(route.logLevel, 'info') + t.assert.strictEqual(route.bodyLimit, 100) + t.assert.ok(typeof route.logSerializers.test === 'function') + }) + instance.get('/foo', { + logLevel: 'info', + bodyLimit: 100, + logSerializers: { + test: value => value + } + }, function (req, reply) { + reply.send() + }) + done() + }) + + fastify.ready(err => { + t.assert.ifError(err) + testDone() + }) +}) + +test('onRoute hook should receive any route option', (t, testDone) => { + t.plan(5) + const fastify = Fastify({ exposeHeadRoutes: false }) + fastify.register((instance, opts, done) => { + instance.addHook('onRoute', function (route) { + t.assert.strictEqual(route.method, 'GET') + t.assert.strictEqual(route.url, '/foo') + t.assert.strictEqual(route.routePath, '/foo') + t.assert.strictEqual(route.auth, 'basic') + }) + instance.get('/foo', { auth: 'basic' }, function (req, reply) { + reply.send() + }) + done() + }) + + fastify.ready(err => { + t.assert.ifError(err) + testDone() + }) +}) + +test('onRoute hook should preserve system route configuration', (t, testDone) => { + t.plan(5) + const fastify = Fastify({ exposeHeadRoutes: false }) + fastify.register((instance, opts, done) => { + instance.addHook('onRoute', function (route) { + t.assert.strictEqual(route.method, 'GET') + t.assert.strictEqual(route.url, '/foo') + t.assert.strictEqual(route.routePath, '/foo') + t.assert.strictEqual(route.handler.length, 2) + }) + instance.get('/foo', { url: '/bar', method: 'POST' }, function (req, reply) { + reply.send() + }) + done() + }) + + fastify.ready(err => { + t.assert.ifError(err) + testDone() + }) +}) + +test('onRoute hook should preserve handler function in options of shorthand route system configuration', (t, testDone) => { + t.plan(2) + + const handler = (req, reply) => { } + + const fastify = Fastify({ exposeHeadRoutes: false }) + fastify.register((instance, opts, done) => { + instance.addHook('onRoute', function (route) { + t.assert.strictEqual(route.handler, handler) + }) + instance.get('/foo', { handler }) + done() + }) + + fastify.ready(err => { + t.assert.ifError(err) + testDone() + }) +}) + +// issue ref https://github.com/fastify/fastify-compress/issues/140 +test('onRoute hook should be called once when prefixTrailingSlash', (t, testDone) => { + t.plan(3) + + let onRouteCalled = 0 + let routePatched = 0 + + const fastify = Fastify({ ignoreTrailingSlash: false, exposeHeadRoutes: false }) + + // a plugin that patches route options, similar to fastify-compress + fastify.register(fp(function myPlugin (instance, opts, next) { + function patchTheRoute () { + routePatched++ + } + + instance.addHook('onRoute', function (routeOptions) { + onRouteCalled++ + patchTheRoute(routeOptions) + }) + + next() + })) + + fastify.register(function routes (instance, opts, next) { + instance.route({ + method: 'GET', + url: '/', + prefixTrailingSlash: 'both', + handler: (req, reply) => { + reply.send({ hello: 'world' }) + } + }) + + next() + }, { prefix: '/prefix' }) + + fastify.ready(err => { + t.assert.ifError(err) + t.assert.strictEqual(onRouteCalled, 1) // onRoute hook was called once + t.assert.strictEqual(routePatched, 1) // and plugin acted once and avoided redundant route patching + testDone() + }) +}) + +test('onRoute hook should able to change the route url', async t => { + t.plan(4) + + const fastify = Fastify({ exposeHeadRoutes: false }) + t.after(() => { fastify.close() }) + + fastify.register((instance, opts, done) => { + instance.addHook('onRoute', (route) => { + t.assert.strictEqual(route.url, '/foo') + route.url = encodeURI(route.url) + }) + + instance.get('/foo', (request, reply) => { + reply.send('here /foo') + }) + + done() + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + + const result = await fetch(fastifyServer + encodeURI('/foo')) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.strictEqual(await result.text(), 'here /foo') +}) + +test('onRoute hook that throws should be caught', (t, testDone) => { + t.plan(1) + const fastify = Fastify({ exposeHeadRoutes: false }) + + fastify.register((instance, opts, done) => { + instance.addHook('onRoute', () => { + throw new Error('snap') + }) + + try { + instance.get('/', opts, function (req, reply) { + reply.send() + }) + + t.assert.fail('onRoute should throw sync if error') + } catch (error) { + t.assert.ok(error) + } + + done() + }) + + fastify.ready(testDone) +}) + +test('onRoute hook with many prefix', (t, testDone) => { + t.plan(3) + const fastify = Fastify({ exposeHeadRoutes: false }) + const handler = (req, reply) => { reply.send({}) } + + const onRouteChecks = [ + { routePath: '/anotherPath', prefix: '/one/two', url: '/one/two/anotherPath' }, + { routePath: '/aPath', prefix: '/one', url: '/one/aPath' } + ] + + fastify.register((instance, opts, done) => { + instance.addHook('onRoute', ({ routePath, prefix, url }) => { + t.assert.deepStrictEqual({ routePath, prefix, url }, onRouteChecks.pop()) + }) + instance.route({ method: 'GET', url: '/aPath', handler }) + + instance.register((instance, opts, done) => { + instance.route({ method: 'GET', path: '/anotherPath', handler }) + done() + }, { prefix: '/two' }) + done() + }, { prefix: '/one' }) + + fastify.ready(err => { + t.assert.ifError(err) + testDone() + }) +}) + +test('onRoute hook should not be called when it registered after route', (t, testDone) => { + t.plan(3) + const fastify = Fastify() + + fastify.addHook('onRoute', () => { + t.assert.ok('should pass') + }) + + fastify.get('/', function (req, reply) { + reply.send() + }) + + fastify.addHook('onRoute', () => { + t.assert.fail('should not be called') + }) + + fastify.ready(err => { + t.assert.ifError(err) + testDone() + }) +}) + +test('onResponse hook should log request error', (t, testDone) => { + t.plan(4) + + let fastify = null + const logStream = split(JSON.parse) + try { + fastify = Fastify({ + logger: { + stream: logStream, + level: 'error' + } + }) + } catch (e) { + t.assert.fail() + } + + logStream.once('data', line => { + t.assert.strictEqual(line.msg, 'request errored') + t.assert.strictEqual(line.level, 50) + }) + + fastify.addHook('onResponse', (request, reply, done) => { + done(new Error('kaboom')) + }) + + fastify.get('/root', (request, reply) => { + reply.send() + }) + + fastify.inject('/root', (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + testDone() + }) +}) + +test('onResponse hook should support encapsulation / 1', (t, testDone) => { + t.plan(5) + const fastify = Fastify() + + fastify.register((instance, opts, done) => { + instance.addHook('onResponse', (request, reply, done) => { + t.assert.strictEqual(reply.plugin, true) + done() + }) + + instance.get('/plugin', (request, reply) => { + reply.plugin = true + reply.send() + }) + + done() + }) + + fastify.get('/root', (request, reply) => { + reply.send() + }) + + fastify.inject('/root', (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + }) + + fastify.inject('/plugin', (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + testDone() + }) +}) + +test('onResponse hook should support encapsulation / 2', (t, testDone) => { + t.plan(3) + const fastify = Fastify() + let pluginInstance + + fastify.addHook('onResponse', () => { }) + + fastify.register((instance, opts, done) => { + instance.addHook('onResponse', () => { }) + pluginInstance = instance + done() + }) + + fastify.ready(err => { + t.assert.ifError(err) + t.assert.strictEqual(fastify[symbols.kHooks].onResponse.length, 1) + t.assert.strictEqual(pluginInstance[symbols.kHooks].onResponse.length, 2) + testDone() + }) +}) + +test('onResponse hook should support encapsulation / 3', async t => { + t.plan(15) + const fastify = Fastify() + t.after(() => { fastify.close() }) + fastify.decorate('hello', 'world') + + fastify.addHook('onResponse', function (request, reply, done) { + t.assert.ok(this.hello) + t.assert.ok('onResponse called') + done() + }) + + fastify.get('/first', (req, reply) => { + reply.send({ hello: 'world' }) + }) + + fastify.register((instance, opts, done) => { + instance.decorate('hello2', 'world') + instance.addHook('onResponse', function (request, reply, done) { + t.assert.ok(this.hello) + t.assert.ok(this.hello2) + t.assert.ok('onResponse called') + done() + }) + + instance.get('/second', (req, reply) => { + reply.send({ hello: 'world' }) + }) + + done() + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + + const firstResult = await fetch(fastifyServer + '/first', { method: 'GET' }) + t.assert.ok(firstResult.ok) + t.assert.strictEqual(firstResult.status, 200) + const firstBody = await firstResult.text() + t.assert.strictEqual(firstResult.headers.get('content-length'), '' + firstBody.length) + t.assert.deepStrictEqual(JSON.parse(firstBody), { hello: 'world' }) + + const secondResult = await fetch(fastifyServer + '/second') + t.assert.ok(secondResult.ok) + t.assert.strictEqual(secondResult.status, 200) + const secondBody = await secondResult.text() + t.assert.strictEqual(secondResult.headers.get('content-length'), '' + secondBody.length) + t.assert.deepStrictEqual(JSON.parse(secondBody), { hello: 'world' }) +}) + +test('onSend hook should support encapsulation / 1', (t, testDone) => { + t.plan(3) + const fastify = Fastify() + let pluginInstance + + fastify.addHook('onSend', () => { }) + + fastify.register((instance, opts, done) => { + instance.addHook('onSend', () => { }) + pluginInstance = instance + done() + }) + + fastify.ready(err => { + t.assert.ifError(err) + t.assert.strictEqual(fastify[symbols.kHooks].onSend.length, 1) + t.assert.strictEqual(pluginInstance[symbols.kHooks].onSend.length, 2) + testDone() + }) +}) + +test('onSend hook should support encapsulation / 2', async t => { + t.plan(15) + const fastify = Fastify() + t.after(() => { fastify.close() }) + fastify.decorate('hello', 'world') + + fastify.addHook('onSend', function (request, reply, thePayload, done) { + t.assert.ok(this.hello) + t.assert.ok('onSend called') + done() + }) + + fastify.get('/first', (req, reply) => { + reply.send({ hello: 'world' }) + }) + + fastify.register((instance, opts, done) => { + instance.decorate('hello2', 'world') + instance.addHook('onSend', function (request, reply, thePayload, done) { + t.assert.ok(this.hello) + t.assert.ok(this.hello2) + t.assert.ok('onSend called') + done() + }) + + instance.get('/second', (req, reply) => { + reply.send({ hello: 'world' }) + }) + + done() + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + + const firstResult = await fetch(fastifyServer + '/first') + t.assert.ok(firstResult.ok) + t.assert.strictEqual(firstResult.status, 200) + const firstBody = await firstResult.text() + t.assert.strictEqual(firstResult.headers.get('content-length'), '' + firstBody.length) + t.assert.deepStrictEqual(JSON.parse(firstBody), { hello: 'world' }) + + const secondResult = await fetch(fastifyServer + '/second') + t.assert.ok(secondResult.ok) + t.assert.strictEqual(secondResult.status, 200) + const secondBody = await secondResult.text() + t.assert.strictEqual(secondResult.headers.get('content-length'), '' + secondBody.length) + t.assert.deepStrictEqual(JSON.parse(secondBody), { hello: 'world' }) +}) + +test('onSend hook is called after payload is serialized and headers are set', (t, testDone) => { + t.plan(30) + const fastify = Fastify() + + fastify.register((instance, opts, done) => { + const thePayload = { hello: 'world' } + + instance.addHook('onSend', function (request, reply, payload, done) { + t.assert.deepStrictEqual(JSON.parse(payload), thePayload) + t.assert.strictEqual(reply[symbols.kReplyHeaders]['content-type'], 'application/json; charset=utf-8') + done() + }) + + instance.get('/json', (request, reply) => { + reply.send(thePayload) + }) + + done() + }) + + fastify.register((instance, opts, done) => { + instance.addHook('onSend', function (request, reply, payload, done) { + t.assert.strictEqual(payload, 'some text') + t.assert.strictEqual(reply[symbols.kReplyHeaders]['content-type'], 'text/plain; charset=utf-8') + done() + }) + + instance.get('/text', (request, reply) => { + reply.send('some text') + }) + + done() + }) + + fastify.register((instance, opts, done) => { + const thePayload = Buffer.from('buffer payload') + + instance.addHook('onSend', function (request, reply, payload, done) { + t.assert.strictEqual(payload, thePayload) + t.assert.strictEqual(reply[symbols.kReplyHeaders]['content-type'], 'application/octet-stream') + done() + }) + + instance.get('/buffer', (request, reply) => { + reply.send(thePayload) + }) + + done() + }) + + fastify.register((instance, opts, done) => { + let chunk = 'stream payload' + const thePayload = new stream.Readable({ + read () { + this.push(chunk) + chunk = null + } + }) + + instance.addHook('onSend', function (request, reply, payload, done) { + t.assert.strictEqual(payload, thePayload) + t.assert.strictEqual(reply[symbols.kReplyHeaders]['content-type'], 'application/octet-stream') + done() + }) + + instance.get('/stream', (request, reply) => { + reply.header('content-type', 'application/octet-stream') + reply.send(thePayload) + }) + + done() + }) + + fastify.register((instance, opts, done) => { + const serializedPayload = 'serialized' + + instance.addHook('onSend', function (request, reply, payload, done) { + t.assert.strictEqual(payload, serializedPayload) + t.assert.strictEqual(reply[symbols.kReplyHeaders]['content-type'], 'text/custom') + done() + }) + + instance.get('/custom-serializer', (request, reply) => { + reply + .serializer(() => serializedPayload) + .type('text/custom') + .send('needs to be serialized') + }) + + done() + }) + + const completion = waitForCb({ steps: 5 }) + fastify.inject({ + method: 'GET', + url: '/json' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + t.assert.strictEqual(res.headers['content-length'], '17') + completion.stepIn() + }) + + fastify.inject({ + method: 'GET', + url: '/text' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(res.payload, 'some text') + t.assert.strictEqual(res.headers['content-length'], '9') + completion.stepIn() + }) + + fastify.inject({ + method: 'GET', + url: '/buffer' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(res.payload, 'buffer payload') + t.assert.strictEqual(res.headers['content-length'], '14') + completion.stepIn() + }) + + fastify.inject({ + method: 'GET', + url: '/stream' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(res.payload, 'stream payload') + t.assert.strictEqual(res.headers['transfer-encoding'], 'chunked') + completion.stepIn() + }) + + fastify.inject({ + method: 'GET', + url: '/custom-serializer' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(res.payload, 'serialized') + t.assert.strictEqual(res.headers['content-type'], 'text/custom') + completion.stepIn() + }) + completion.patience.then(testDone) +}) + +test('modify payload', (t, testDone) => { + t.plan(10) + const fastify = Fastify() + const payload = { hello: 'world' } + const modifiedPayload = { hello: 'modified' } + const anotherPayload = '"winter is coming"' + + fastify.addHook('onSend', function (request, reply, thePayload, done) { + t.assert.ok('onSend called') + t.assert.deepStrictEqual(JSON.parse(thePayload), payload) + thePayload = thePayload.replace('world', 'modified') + done(null, thePayload) + }) + + fastify.addHook('onSend', function (request, reply, thePayload, done) { + t.assert.ok('onSend called') + t.assert.deepStrictEqual(JSON.parse(thePayload), modifiedPayload) + done(null, anotherPayload) + }) + + fastify.addHook('onSend', function (request, reply, thePayload, done) { + t.assert.ok('onSend called') + t.assert.strictEqual(thePayload, anotherPayload) + done() + }) + + fastify.get('/', (req, reply) => { + reply.send(payload) + }) + + fastify.inject({ + method: 'GET', + url: '/' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.payload, anotherPayload) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['content-length'], '18') + testDone() + }) +}) + +test('clear payload', (t, testDone) => { + t.plan(6) + const fastify = Fastify() + + fastify.addHook('onSend', function (request, reply, payload, done) { + t.assert.ok('onSend called') + reply.code(304) + done(null, null) + }) + + fastify.get('/', (req, reply) => { + reply.send({ hello: 'world' }) + }) + + fastify.inject({ + method: 'GET', + url: '/' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 304) + t.assert.strictEqual(res.payload, '') + t.assert.strictEqual(res.headers['content-length'], undefined) + t.assert.strictEqual(res.headers['content-type'], 'application/json; charset=utf-8') + testDone() + }) +}) + +test('onSend hook throws', async t => { + t.plan(10) + const Fastify = proxyquire('..', { + './lib/schemas.js': { + getSchemaSerializer: (param1, param2, param3) => { + t.assert.strictEqual(param3, 'application/json; charset=utf-8', 'param3 should be "application/json; charset=utf-8"') + } + } + }) + const fastify = Fastify() + t.after(() => { fastify.close() }) + fastify.addHook('onSend', function (request, reply, payload, done) { + if (request.raw.method === 'DELETE') { + done(new Error('some error')) + return + } + + if (request.raw.method === 'PUT') { + throw new Error('some error') + } + + if (request.raw.method === 'POST') { + throw new Error('some error') + } + + done() + }) + + fastify.get('/', (req, reply) => { + reply.send({ hello: 'world' }) + }) + + fastify.post('/', { + schema: { + response: { + 200: { + content: { + 'application/json': { + schema: { + name: { type: 'string' }, + image: { type: 'string' }, + address: { type: 'string' } + } + } + } + } + } + } + }, (req, reply) => { + reply.send({ hello: 'world' }) + }) + + fastify.delete('/', (req, reply) => { + reply.send({ hello: 'world' }) + }) + + fastify.put('/', (req, reply) => { + reply.send({ hello: 'world' }) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + + const getResult = await fetch(fastifyServer) + t.assert.ok(getResult.ok) + t.assert.strictEqual(getResult.status, 200) + const getBody = await getResult.text() + t.assert.strictEqual(getResult.headers.get('content-length'), '' + getBody.length) + t.assert.deepStrictEqual(JSON.parse(getBody), { hello: 'world' }) + + const postResult = await fetch(fastifyServer, { method: 'POST' }) + t.assert.ok(!postResult.ok) + t.assert.strictEqual(postResult.status, 500) + + const deleteResult = await fetch(fastifyServer, { method: 'DELETE' }) + t.assert.ok(!deleteResult.ok) + t.assert.strictEqual(deleteResult.status, 500) + + const putResult = await fetch(fastifyServer, { method: 'PUT' }) + t.assert.ok(!putResult.ok) + t.assert.strictEqual(putResult.status, 500) +}) + +test('onSend hook should receive valid request and reply objects if onRequest hook fails', (t, testDone) => { + t.plan(4) + const fastify = Fastify() + + fastify.decorateRequest('testDecorator', 'testDecoratorVal') + fastify.decorateReply('testDecorator', 'testDecoratorVal') + + fastify.addHook('onRequest', function (req, reply, done) { + done(new Error('onRequest hook failed')) + }) + + fastify.addHook('onSend', function (request, reply, payload, done) { + t.assert.strictEqual(request.testDecorator, 'testDecoratorVal') + t.assert.strictEqual(reply.testDecorator, 'testDecoratorVal') + done() + }) + + fastify.get('/', (req, reply) => { + reply.send('hello') + }) + + fastify.inject({ + method: 'GET', + url: '/' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 500) + testDone() + }) +}) + +test('onSend hook should receive valid request and reply objects if a custom content type parser fails', (t, testDone) => { + t.plan(4) + const fastify = Fastify() + + fastify.decorateRequest('testDecorator', 'testDecoratorVal') + fastify.decorateReply('testDecorator', 'testDecoratorVal') + + fastify.addContentTypeParser('*', function (req, payload, done) { + done(new Error('content type parser failed')) + }) + + fastify.addHook('onSend', function (request, reply, payload, done) { + t.assert.strictEqual(request.testDecorator, 'testDecoratorVal') + t.assert.strictEqual(reply.testDecorator, 'testDecoratorVal') + done() + }) + + fastify.get('/', (req, reply) => { + reply.send('hello') + }) + + fastify.inject({ + method: 'POST', + url: '/', + payload: 'body' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 500) + testDone() + }) +}) + +test('Content-Length header should be updated if onSend hook modifies the payload', (t, testDone) => { + t.plan(2) + + const instance = Fastify() + + instance.get('/', async (_, rep) => { + rep.header('content-length', 3) + return 'foo' + }) + + instance.addHook('onSend', async () => 'bar12233000') + + instance.inject({ + method: 'GET', + url: '/' + }, (err, res) => { + t.assert.ifError(err) + const payloadLength = Buffer.byteLength(res.body) + const contentLength = Number(res.headers['content-length']) + + t.assert.strictEqual(payloadLength, contentLength) + testDone() + }) +}) + +test('cannot add hook after binding', (t, testDone) => { + t.plan(1) + const instance = Fastify() + t.after(() => instance.close()) + + instance.get('/', function (request, reply) { + reply.send({ hello: 'world' }) + }) + + instance.listen({ port: 0 }, err => { + t.assert.ifError(err) + + try { + instance.addHook('onRequest', () => { }) + t.assert.fail() + } catch (e) { + testDone() + } + }) +}) + +test('onRequest hooks should be able to block a request', (t, testDone) => { + t.plan(5) + const fastify = Fastify() + + fastify.addHook('onRequest', (req, reply, done) => { + reply.send('hello') + done() + }) + + fastify.addHook('onRequest', (req, reply, done) => { + t.assert.fail('this should not be called') + }) + + fastify.addHook('preHandler', (req, reply, done) => { + t.assert.fail('this should not be called') + }) + + fastify.addHook('onSend', (req, reply, payload, done) => { + t.assert.ok('called') + done() + }) + + fastify.addHook('onResponse', (request, reply, done) => { + t.assert.ok('called') + done() + }) + + fastify.get('/', function (request, reply) { + t.assert.fail('we should not be here') + }) + + fastify.inject({ + url: '/', + method: 'GET' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload, 'hello') + testDone() + }) +}) + +test('preValidation hooks should be able to block a request', (t, testDone) => { + t.plan(5) + const fastify = Fastify() + + fastify.addHook('preValidation', (req, reply, done) => { + reply.send('hello') + done() + }) + + fastify.addHook('preValidation', (req, reply, done) => { + t.assert.fail('this should not be called') + }) + + fastify.addHook('preHandler', (req, reply, done) => { + t.assert.fail('this should not be called') + }) + + fastify.addHook('onSend', (req, reply, payload, done) => { + t.assert.ok('called') + done() + }) + + fastify.addHook('onResponse', (request, reply, done) => { + t.assert.ok('called') + done() + }) + + fastify.get('/', function (request, reply) { + t.assert.fail('we should not be here') + }) + + fastify.inject({ + url: '/', + method: 'GET' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload, 'hello') + testDone() + }) +}) + +test('preValidation hooks should be able to change request body before validation', (t, testDone) => { + t.plan(4) + const fastify = Fastify() + + fastify.addHook('preValidation', (req, _reply, done) => { + const buff = Buffer.from(req.body.message, 'base64') + req.body = JSON.parse(buff.toString('utf-8')) + done() + }) + + fastify.post( + '/', + { + schema: { + body: { + type: 'object', + properties: { + foo: { + type: 'string' + }, + bar: { + type: 'number' + } + }, + required: ['foo', 'bar'] + } + } + }, + (req, reply) => { + t.assert.ok('should pass') + reply.status(200).send('hello') + } + ) + + fastify.inject({ + url: '/', + method: 'POST', + payload: { + message: Buffer.from(JSON.stringify({ foo: 'example', bar: 1 })).toString('base64') + } + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload, 'hello') + testDone() + }) +}) + +test('preParsing hooks should be able to block a request', (t, testDone) => { + t.plan(5) + const fastify = Fastify() + + fastify.addHook('preParsing', (req, reply, payload, done) => { + reply.send('hello') + done() + }) + + fastify.addHook('preParsing', (req, reply, payload, done) => { + t.assert.fail('this should not be called') + }) + + fastify.addHook('preHandler', (req, reply, done) => { + t.assert.fail('this should not be called') + }) + + fastify.addHook('onSend', (req, reply, payload, done) => { + t.assert.ok('called') + done() + }) + + fastify.addHook('onResponse', (request, reply, done) => { + t.assert.ok('called') + done() + }) + + fastify.get('/', function (request, reply) { + t.assert.fail('we should not be here') + }) + + fastify.inject({ + url: '/', + method: 'GET' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload, 'hello') + testDone() + }) +}) + +test('preHandler hooks should be able to block a request', (t, testDone) => { + t.plan(5) + const fastify = Fastify() + + fastify.addHook('preHandler', (req, reply, done) => { + reply.send('hello') + done() + }) + + fastify.addHook('preHandler', (req, reply, done) => { + t.assert.fail('this should not be called') + }) + + fastify.addHook('onSend', (req, reply, payload, done) => { + t.assert.strictEqual(payload, 'hello') + done() + }) + + fastify.addHook('onResponse', (request, reply, done) => { + t.assert.ok('called') + done() + }) + + fastify.get('/', function (request, reply) { + t.assert.fail('we should not be here') + }) + + fastify.inject({ + url: '/', + method: 'GET' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload, 'hello') + testDone() + }) +}) + +test('onRequest hooks should be able to block a request (last hook)', (t, testDone) => { + t.plan(5) + const fastify = Fastify() + + fastify.addHook('onRequest', (req, reply, done) => { + reply.send('hello') + done() + }) + + fastify.addHook('preHandler', (req, reply, done) => { + t.assert.fail('this should not be called') + }) + + fastify.addHook('onSend', (req, reply, payload, done) => { + t.assert.ok('called') + done() + }) + + fastify.addHook('onResponse', (request, reply, done) => { + t.assert.ok('called') + done() + }) + + fastify.get('/', function (request, reply) { + t.assert.fail('we should not be here') + }) + + fastify.inject({ + url: '/', + method: 'GET' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload, 'hello') + testDone() + }) +}) + +test('preHandler hooks should be able to block a request (last hook)', (t, testDone) => { + t.plan(5) + const fastify = Fastify() + + fastify.addHook('preHandler', (req, reply, done) => { + reply.send('hello') + done() + }) + + fastify.addHook('onSend', (req, reply, payload, done) => { + t.assert.strictEqual(payload, 'hello') + done() + }) + + fastify.addHook('onResponse', (request, reply, done) => { + t.assert.ok('called') + done() + }) + + fastify.get('/', function (request, reply) { + t.assert.fail('we should not be here') + }) + + fastify.inject({ + url: '/', + method: 'GET' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload, 'hello') + testDone() + }) +}) + +test('preParsing hooks should handle errors', (t, testDone) => { + t.plan(3) + const fastify = Fastify() + + fastify.addHook('preParsing', (req, reply, payload, done) => { + const e = new Error('kaboom') + e.statusCode = 501 + throw e + }) + + fastify.post('/', function (request, reply) { + reply.send(request.body) + }) + + fastify.inject({ + method: 'POST', + url: '/', + payload: { hello: 'world' } + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 501) + t.assert.deepStrictEqual(JSON.parse(res.payload), { error: 'Not Implemented', message: 'kaboom', statusCode: 501 }) + testDone() + }) +}) + +test('onRequest respond with a stream', (t, testDone) => { + t.plan(4) + const fastify = Fastify() + + fastify.addHook('onRequest', (req, reply, done) => { + const stream = fs.createReadStream(__filename, 'utf8') + // stream.pipe(res) + // res.once('finish', done) + reply.send(stream) + }) + + fastify.addHook('onRequest', (req, res, done) => { + t.assert.fail('this should not be called') + }) + + fastify.addHook('preHandler', (req, reply, done) => { + t.assert.fail('this should not be called') + }) + + fastify.addHook('onSend', (req, reply, payload, done) => { + t.assert.ok('called') + done() + }) + + fastify.addHook('onResponse', (request, reply, done) => { + t.assert.ok('called') + done() + }) + + fastify.get('/', function (request, reply) { + t.assert.fail('we should not be here') + }) + + fastify.inject({ + url: '/', + method: 'GET' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + testDone() + }) +}) + +test('preHandler respond with a stream', (t, testDone) => { + t.plan(7) + const fastify = Fastify() + + fastify.addHook('onRequest', (req, reply, done) => { + t.assert.ok('called') + done() + }) + + // we are calling `reply.send` inside the `preHandler` hook with a stream, + // this triggers the `onSend` hook event if `preHandler` has not yet finished + const order = [1, 2] + + fastify.addHook('preHandler', (req, reply, done) => { + const stream = fs.createReadStream(__filename, 'utf8') + reply.send(stream) + reply.raw.once('finish', () => { + t.assert.strictEqual(order.shift(), 2) + done() + }) + }) + + fastify.addHook('preHandler', (req, reply, done) => { + t.assert.fail('this should not be called') + }) + + fastify.addHook('onSend', (req, reply, payload, done) => { + t.assert.strictEqual(order.shift(), 1) + t.assert.strictEqual(typeof payload.pipe, 'function') + done() + }) + + fastify.addHook('onResponse', (request, reply, done) => { + t.assert.ok('called') + done() + }) + + fastify.get('/', function (request, reply) { + t.assert.fail('we should not be here') + }) + + fastify.inject({ + url: '/', + method: 'GET' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + testDone() + }) +}) + +test('Register an hook after a plugin inside a plugin', (t, testDone) => { + t.plan(6) + const fastify = Fastify() + + fastify.register(fp(function (instance, opts, done) { + instance.addHook('preHandler', function (req, reply, done) { + t.assert.ok('called') + done() + }) + + instance.get('/', function (request, reply) { + reply.send({ hello: 'world' }) + }) + + done() + })) + + fastify.register(fp(function (instance, opts, done) { + instance.addHook('preHandler', function (req, reply, done) { + t.assert.ok('called') + done() + }) + + instance.addHook('preHandler', function (req, reply, done) { + t.assert.ok('called') + done() + }) + + done() + })) + + fastify.inject({ + url: '/', + method: 'GET' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + testDone() + }) +}) + +test('Register an hook after a plugin inside a plugin (with preHandler option)', (t, testDone) => { + t.plan(7) + const fastify = Fastify() + + fastify.register(fp(function (instance, opts, done) { + instance.addHook('preHandler', function (req, reply, done) { + t.assert.ok('called') + done() + }) + + instance.get('/', { + preHandler: (req, reply, done) => { + t.assert.ok('called') + done() + } + }, function (request, reply) { + reply.send({ hello: 'world' }) + }) + + done() + })) + + fastify.register(fp(function (instance, opts, done) { + instance.addHook('preHandler', function (req, reply, done) { + t.assert.ok('called') + done() + }) + + instance.addHook('preHandler', function (req, reply, done) { + t.assert.ok('called') + done() + }) + + done() + })) + + fastify.inject({ + url: '/', + method: 'GET' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + testDone() + }) +}) + +test('Register hooks inside a plugin after an encapsulated plugin', (t, testDone) => { + t.plan(7) + const fastify = Fastify() + + fastify.register(function (instance, opts, done) { + instance.get('/', function (request, reply) { + reply.send({ hello: 'world' }) + }) + + done() + }) + + fastify.register(fp(function (instance, opts, done) { + instance.addHook('onRequest', function (req, reply, done) { + t.assert.ok('called') + done() + }) + + instance.addHook('preHandler', function (request, reply, done) { + t.assert.ok('called') + done() + }) + + instance.addHook('onSend', function (request, reply, payload, done) { + t.assert.ok('called') + done() + }) + + instance.addHook('onResponse', function (request, reply, done) { + t.assert.ok('called') + done() + }) + + done() + })) + + fastify.inject('/', (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + testDone() + }) +}) + +test('onRequest hooks should run in the order in which they are defined', (t, testDone) => { + t.plan(9) + const fastify = Fastify() + + fastify.register(function (instance, opts, done) { + instance.addHook('onRequest', function (req, reply, done) { + t.assert.strictEqual(req.previous, undefined) + req.previous = 1 + done() + }) + + instance.get('/', function (request, reply) { + t.assert.strictEqual(request.previous, 5) + reply.send({ hello: 'world' }) + }) + + instance.register(fp(function (i, opts, done) { + i.addHook('onRequest', function (req, reply, done) { + t.assert.strictEqual(req.previous, 1) + req.previous = 2 + done() + }) + done() + })) + + done() + }) + + fastify.register(fp(function (instance, opts, done) { + instance.addHook('onRequest', function (req, reply, done) { + t.assert.strictEqual(req.previous, 2) + req.previous = 3 + done() + }) + + instance.register(fp(function (i, opts, done) { + i.addHook('onRequest', function (req, reply, done) { + t.assert.strictEqual(req.previous, 3) + req.previous = 4 + done() + }) + done() + })) + + instance.addHook('onRequest', function (req, reply, done) { + t.assert.strictEqual(req.previous, 4) + req.previous = 5 + done() + }) + + done() + })) + + fastify.inject('/', (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + testDone() + }) +}) + +test('preHandler hooks should run in the order in which they are defined', (t, testDone) => { + t.plan(9) + const fastify = Fastify() + + fastify.register(function (instance, opts, done) { + instance.addHook('preHandler', function (request, reply, done) { + t.assert.strictEqual(request.previous, undefined) + request.previous = 1 + done() + }) + + instance.get('/', function (request, reply) { + t.assert.strictEqual(request.previous, 5) + reply.send({ hello: 'world' }) + }) + + instance.register(fp(function (i, opts, done) { + i.addHook('preHandler', function (request, reply, done) { + t.assert.strictEqual(request.previous, 1) + request.previous = 2 + done() + }) + done() + })) + + done() + }) + + fastify.register(fp(function (instance, opts, done) { + instance.addHook('preHandler', function (request, reply, done) { + t.assert.strictEqual(request.previous, 2) + request.previous = 3 + done() + }) + + instance.register(fp(function (i, opts, done) { + i.addHook('preHandler', function (request, reply, done) { + t.assert.strictEqual(request.previous, 3) + request.previous = 4 + done() + }) + done() + })) + + instance.addHook('preHandler', function (request, reply, done) { + t.assert.strictEqual(request.previous, 4) + request.previous = 5 + done() + }) + + done() + })) + + fastify.inject('/', (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + testDone() + }) +}) + +test('onSend hooks should run in the order in which they are defined', (t, testDone) => { + t.plan(8) + const fastify = Fastify() + + fastify.register(function (instance, opts, done) { + instance.addHook('onSend', function (request, reply, payload, done) { + t.assert.strictEqual(request.previous, undefined) + request.previous = 1 + done() + }) + + instance.get('/', function (request, reply) { + reply.send({}) + }) + + instance.register(fp(function (i, opts, done) { + i.addHook('onSend', function (request, reply, payload, done) { + t.assert.strictEqual(request.previous, 1) + request.previous = 2 + done() + }) + done() + })) + + done() + }) + + fastify.register(fp(function (instance, opts, done) { + instance.addHook('onSend', function (request, reply, payload, done) { + t.assert.strictEqual(request.previous, 2) + request.previous = 3 + done() + }) + + instance.register(fp(function (i, opts, done) { + i.addHook('onSend', function (request, reply, payload, done) { + t.assert.strictEqual(request.previous, 3) + request.previous = 4 + done() + }) + done() + })) + + instance.addHook('onSend', function (request, reply, payload, done) { + t.assert.strictEqual(request.previous, 4) + done(null, '5') + }) + + done() + })) + + fastify.inject('/', (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(JSON.parse(res.payload), 5) + testDone() + }) +}) + +test('onResponse hooks should run in the order in which they are defined', (t, testDone) => { + t.plan(8) + const fastify = Fastify() + + fastify.register(function (instance, opts, done) { + instance.addHook('onResponse', function (request, reply, done) { + t.assert.strictEqual(reply.previous, undefined) + reply.previous = 1 + done() + }) + + instance.get('/', function (request, reply) { + reply.send({ hello: 'world' }) + }) + + instance.register(fp(function (i, opts, done) { + i.addHook('onResponse', function (request, reply, done) { + t.assert.strictEqual(reply.previous, 1) + reply.previous = 2 + done() + }) + done() + })) + + done() + }) + + fastify.register(fp(function (instance, opts, done) { + instance.addHook('onResponse', function (request, reply, done) { + t.assert.strictEqual(reply.previous, 2) + reply.previous = 3 + done() + }) + + instance.register(fp(function (i, opts, done) { + i.addHook('onResponse', function (request, reply, done) { + t.assert.strictEqual(reply.previous, 3) + reply.previous = 4 + done() + }) + done() + })) + + instance.addHook('onResponse', function (request, reply, done) { + t.assert.strictEqual(reply.previous, 4) + done() + }) + + done() + })) + + fastify.inject('/', (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + testDone() + }) +}) + +test('onRequest, preHandler, and onResponse hooks that resolve to a value do not cause an error', (t, testDone) => { + t.plan(3) + const fastify = Fastify() + + fastify + .addHook('onRequest', () => Promise.resolve(1)) + .addHook('onRequest', () => Promise.resolve(true)) + .addHook('preValidation', () => Promise.resolve(null)) + .addHook('preValidation', () => Promise.resolve('a')) + .addHook('preHandler', () => Promise.resolve(null)) + .addHook('preHandler', () => Promise.resolve('a')) + .addHook('onResponse', () => Promise.resolve({})) + .addHook('onResponse', () => Promise.resolve([])) + + fastify.get('/', (request, reply) => { + reply.send('hello') + }) + + fastify.inject('/', (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload, 'hello') + testDone() + }) +}) + +test('If a response header has been set inside an hook it should not be overwritten by the final response handler', (t, testDone) => { + t.plan(5) + const fastify = Fastify() + + fastify.addHook('onRequest', (req, reply, done) => { + reply.header('X-Custom-Header', 'hello') + done() + }) + + fastify.get('/', (request, reply) => { + reply.send('hello') + }) + + fastify.inject('/', (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.headers['x-custom-header'], 'hello') + t.assert.strictEqual(res.headers['content-type'], 'text/plain; charset=utf-8') + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload, 'hello') + testDone() + }) +}) + +test('If the content type has been set inside an hook it should not be changed', (t, testDone) => { + t.plan(5) + const fastify = Fastify() + + fastify.addHook('onRequest', (req, reply, done) => { + reply.header('content-type', 'text/html') + done() + }) + + fastify.get('/', (request, reply) => { + t.assert.ok(reply[symbols.kReplyHeaders]['content-type']) + reply.send('hello') + }) + + fastify.inject('/', (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.headers['content-type'], 'text/html') + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload, 'hello') + testDone() + }) +}) + +test('request in onRequest, preParsing, preValidation and onResponse', (t, testDone) => { + t.plan(18) + const fastify = Fastify() + + fastify.addHook('onRequest', function (request, reply, done) { + t.assert.deepStrictEqual(request.body, undefined) + t.assert.deepStrictEqual(request.query.key, 'value') + t.assert.deepStrictEqual(request.params.greeting, 'hello') + t.assert.deepStrictEqual(request.headers, { + 'content-length': '17', + 'content-type': 'application/json', + host: 'localhost:80', + 'user-agent': 'lightMyRequest', + 'x-custom': 'hello' + }) + done() + }) + + fastify.addHook('preParsing', function (request, reply, payload, done) { + t.assert.deepStrictEqual(request.body, undefined) + t.assert.deepStrictEqual(request.query.key, 'value') + t.assert.deepStrictEqual(request.params.greeting, 'hello') + t.assert.deepStrictEqual(request.headers, { + 'content-length': '17', + 'content-type': 'application/json', + host: 'localhost:80', + 'user-agent': 'lightMyRequest', + 'x-custom': 'hello' + }) + done() + }) + + fastify.addHook('preValidation', function (request, reply, done) { + t.assert.deepStrictEqual(request.body, { hello: 'world' }) + t.assert.deepStrictEqual(request.query.key, 'value') + t.assert.deepStrictEqual(request.params.greeting, 'hello') + t.assert.deepStrictEqual(request.headers, { + 'content-length': '17', + 'content-type': 'application/json', + host: 'localhost:80', + 'user-agent': 'lightMyRequest', + 'x-custom': 'hello' + }) + done() + }) + + fastify.addHook('onResponse', function (request, reply, done) { + t.assert.deepStrictEqual(request.body, { hello: 'world' }) + t.assert.deepStrictEqual(request.query.key, 'value') + t.assert.deepStrictEqual(request.params.greeting, 'hello') + t.assert.deepStrictEqual(request.headers, { + 'content-length': '17', + 'content-type': 'application/json', + host: 'localhost:80', + 'user-agent': 'lightMyRequest', + 'x-custom': 'hello' + }) + done() + }) + + fastify.post('/:greeting', function (req, reply) { + reply.send('ok') + }) + + fastify.inject({ + method: 'POST', + url: '/hello?key=value', + headers: { 'x-custom': 'hello' }, + payload: { hello: 'world' } + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + testDone() + }) +}) + +test('preValidation hook should support encapsulation / 1', (t, testDone) => { + t.plan(5) + const fastify = Fastify() + + fastify.register((instance, opts, done) => { + instance.addHook('preValidation', (req, reply, done) => { + t.assert.strictEqual(req.raw.url, '/plugin') + done() + }) + + instance.get('/plugin', (request, reply) => { + reply.send() + }) + + done() + }) + + fastify.get('/root', (request, reply) => { + reply.send() + }) + + fastify.inject('/root', (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + fastify.inject('/plugin', (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + testDone() + }) + }) +}) + +test('preValidation hook should support encapsulation / 2', (t, testDone) => { + t.plan(3) + const fastify = Fastify() + let pluginInstance + + fastify.addHook('preValidation', () => { }) + + fastify.register((instance, opts, done) => { + instance.addHook('preValidation', () => { }) + pluginInstance = instance + done() + }) + + fastify.ready(err => { + t.assert.ifError(err) + t.assert.strictEqual(fastify[symbols.kHooks].preValidation.length, 1) + t.assert.strictEqual(pluginInstance[symbols.kHooks].preValidation.length, 2) + testDone() + }) +}) + +test('preValidation hook should support encapsulation / 3', async t => { + t.plan(19) + const fastify = Fastify() + t.after(() => { fastify.close() }) + fastify.decorate('hello', 'world') + + fastify.addHook('preValidation', function (req, reply, done) { + t.assert.ok(this.hello) + t.assert.ok(this.hello2) + req.first = true + done() + }) + + fastify.decorate('hello2', 'world') + + fastify.get('/first', (req, reply) => { + t.assert.ok(req.first) + t.assert.ok(!req.second) + reply.send({ hello: 'world' }) + }) + + fastify.register((instance, opts, done) => { + instance.decorate('hello3', 'world') + instance.addHook('preValidation', function (req, reply, done) { + t.assert.ok(this.hello) + t.assert.ok(this.hello2) + t.assert.ok(this.hello3) + req.second = true + done() + }) + + instance.get('/second', (req, reply) => { + t.assert.ok(req.first) + t.assert.ok(req.second) + reply.send({ hello: 'world' }) + }) + + done() + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + + const result1 = await fetch(fastifyServer + '/first') + t.assert.ok(result1.ok) + t.assert.strictEqual(result1.status, 200) + const body1 = await result1.text() + t.assert.strictEqual(result1.headers.get('content-length'), '' + body1.length) + t.assert.deepStrictEqual(JSON.parse(body1), { hello: 'world' }) + + const result2 = await fetch(fastifyServer + '/second') + t.assert.ok(result2.ok) + t.assert.strictEqual(result2.status, 200) + const body2 = await result2.text() + t.assert.strictEqual(result2.headers.get('content-length'), '' + body2.length) + t.assert.deepStrictEqual(JSON.parse(body2), { hello: 'world' }) +}) + +test('onError hook', (t, testDone) => { + t.plan(3) + + const fastify = Fastify() + + const err = new Error('kaboom') + + fastify.addHook('onError', (request, reply, error, done) => { + t.assert.deepStrictEqual(error, err) + done() + }) + + fastify.get('/', (req, reply) => { + reply.send(err) + }) + + fastify.inject({ + method: 'GET', + url: '/' + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { + error: 'Internal Server Error', + message: 'kaboom', + statusCode: 500 + }) + testDone() + }) +}) + +test('reply.send should throw if called inside the onError hook', (t, testDone) => { + t.plan(3) + + const fastify = Fastify() + + const err = new Error('kaboom') + + fastify.addHook('onError', (request, reply, error, done) => { + try { + reply.send() + t.assert.fail('Should throw') + } catch (err) { + t.assert.strictEqual(err.code, 'FST_ERR_SEND_INSIDE_ONERR') + } + done() + }) + + fastify.get('/', (req, reply) => { + reply.send(err) + }) + + fastify.inject({ + method: 'GET', + url: '/' + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { + error: 'Internal Server Error', + message: 'kaboom', + statusCode: 500 + }) + testDone() + }) +}) + +test('onError hook with setErrorHandler', (t, testDone) => { + t.plan(3) + + const fastify = Fastify() + + const external = new Error('ouch') + const internal = new Error('kaboom') + + fastify.setErrorHandler((_, req, reply) => { + reply.send(external) + }) + + fastify.addHook('onError', (request, reply, error, done) => { + t.assert.deepStrictEqual(error, internal) + done() + }) + + fastify.get('/', (req, reply) => { + reply.send(internal) + }) + + fastify.inject({ + method: 'GET', + url: '/' + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { + error: 'Internal Server Error', + message: 'ouch', + statusCode: 500 + }) + testDone() + }) +}) + +test('preParsing hook should run before parsing and be able to modify the payload', async t => { + t.plan(4) + const fastify = Fastify() + t.after(() => { fastify.close() }) + + fastify.addHook('preParsing', function (req, reply, payload, done) { + const modified = new stream.Readable() + modified.receivedEncodedLength = parseInt(req.headers['content-length'], 10) + modified.push(JSON.stringify({ hello: 'another world' })) + modified.push(null) + done(null, modified) + }) + + fastify.route({ + method: 'POST', + url: '/first', + handler: function (req, reply) { + reply.send(req.body) + } + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + + const result = await fetch(fastifyServer + '/first', { + method: 'POST', + body: JSON.stringify({ hello: 'world' }), + headers: { 'Content-Type': 'application/json' } + }) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'another world' }) +}) + +test('preParsing hooks should run in the order in which they are defined', async t => { + t.plan(4) + const fastify = Fastify() + t.after(() => { fastify.close() }) + + fastify.addHook('preParsing', function (req, reply, payload, done) { + const modified = new stream.Readable() + modified.receivedEncodedLength = parseInt(req.headers['content-length'], 10) + modified.push('{"hello":') + done(null, modified) + }) + + fastify.addHook('preParsing', function (req, reply, payload, done) { + payload.push('"another world"}') + payload.push(null) + done(null, payload) + }) + + fastify.route({ + method: 'POST', + url: '/first', + handler: function (req, reply) { + reply.send(req.body) + } + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + + const result = await fetch(fastifyServer + '/first', { + method: 'POST', + body: JSON.stringify({ hello: 'world' }), + headers: { 'Content-Type': 'application/json' } + }) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'another world' }) +}) + +test('preParsing hooks should support encapsulation', async t => { + t.plan(8) + const fastify = Fastify() + t.after(() => { fastify.close() }) + + fastify.addHook('preParsing', function (req, reply, payload, done) { + const modified = new stream.Readable() + modified.receivedEncodedLength = parseInt(req.headers['content-length'], 10) + modified.push('{"hello":"another world"}') + modified.push(null) + done(null, modified) + }) + + fastify.post('/first', (req, reply) => { + reply.send(req.body) + }) + + fastify.register((instance, opts, done) => { + instance.addHook('preParsing', function (req, reply, payload, done) { + const modified = new stream.Readable() + modified.receivedEncodedLength = payload.receivedEncodedLength || parseInt(req.headers['content-length'], 10) + modified.push('{"hello":"encapsulated world"}') + modified.push(null) + done(null, modified) + }) + + instance.post('/second', (req, reply) => { + reply.send(req.body) + }) + + done() + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + + const result1 = await fetch(fastifyServer + '/first', { + method: 'POST', + body: JSON.stringify({ hello: 'world' }), + headers: { 'Content-Type': 'application/json' } + }) + t.assert.ok(result1.ok) + t.assert.strictEqual(result1.status, 200) + const body1 = await result1.text() + t.assert.strictEqual(result1.headers.get('content-length'), '' + body1.length) + t.assert.deepStrictEqual(JSON.parse(body1), { hello: 'another world' }) + + const result2 = await fetch(fastifyServer + '/second', { + method: 'POST', + body: JSON.stringify({ hello: 'world' }), + headers: { 'Content-Type': 'application/json' } + }) + t.assert.ok(result2.ok) + t.assert.strictEqual(result2.status, 200) + const body2 = await result2.text() + t.assert.strictEqual(result2.headers.get('content-length'), '' + body2.length) + t.assert.deepStrictEqual(JSON.parse(body2), { hello: 'encapsulated world' }) +}) + +test('preParsing hook should support encapsulation / 1', (t, testDone) => { + t.plan(5) + const fastify = Fastify() + + fastify.register((instance, opts, done) => { + instance.addHook('preParsing', (req, reply, payload, done) => { + t.assert.strictEqual(req.raw.url, '/plugin') + done() + }) + + instance.get('/plugin', (request, reply) => { + reply.send() + }) + + done() + }) + + fastify.get('/root', (request, reply) => { + reply.send() + }) + + fastify.inject('/root', (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + fastify.inject('/plugin', (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + testDone() + }) + }) +}) + +test('preParsing hook should support encapsulation / 2', (t, testDone) => { + t.plan(3) + const fastify = Fastify() + let pluginInstance + + fastify.addHook('preParsing', function a () { }) + + fastify.register((instance, opts, done) => { + instance.addHook('preParsing', function b () { }) + pluginInstance = instance + done() + }) + + fastify.ready(err => { + t.assert.ifError(err) + t.assert.strictEqual(fastify[symbols.kHooks].preParsing.length, 1) + t.assert.strictEqual(pluginInstance[symbols.kHooks].preParsing.length, 2) + testDone() + }) +}) + +test('preParsing hook should support encapsulation / 3', async t => { + t.plan(19) + const fastify = Fastify() + t.after(() => { fastify.close() }) + fastify.decorate('hello', 'world') + + fastify.addHook('preParsing', function (req, reply, payload, done) { + t.assert.ok(this.hello) + t.assert.ok(this.hello2) + req.first = true + done() + }) + + fastify.decorate('hello2', 'world') + + fastify.get('/first', (req, reply) => { + t.assert.ok(req.first) + t.assert.ok(!req.second) + reply.send({ hello: 'world' }) + }) + + fastify.register((instance, opts, done) => { + instance.decorate('hello3', 'world') + instance.addHook('preParsing', function (req, reply, payload, done) { + t.assert.ok(this.hello) + t.assert.ok(this.hello2) + t.assert.ok(this.hello3) + req.second = true + done() + }) + + instance.get('/second', (req, reply) => { + t.assert.ok(req.first) + t.assert.ok(req.second) + reply.send({ hello: 'world' }) + }) + + done() + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + + const result1 = await fetch(fastifyServer + '/first') + t.assert.ok(result1.ok) + t.assert.strictEqual(result1.status, 200) + const body1 = await result1.text() + t.assert.strictEqual(result1.headers.get('content-length'), '' + body1.length) + t.assert.deepStrictEqual(JSON.parse(body1), { hello: 'world' }) + + const result2 = await fetch(fastifyServer + '/second') + t.assert.ok(result2.ok) + t.assert.strictEqual(result2.status, 200) + const body2 = await result2.text() + t.assert.strictEqual(result2.headers.get('content-length'), '' + body2.length) + t.assert.deepStrictEqual(JSON.parse(body2), { hello: 'world' }) +}) + +test('preSerialization hook should run before serialization and be able to modify the payload', async t => { + t.plan(4) + const fastify = Fastify() + t.after(() => { fastify.close() }) + + fastify.addHook('preSerialization', function (req, reply, payload, done) { + payload.hello += '1' + payload.world = 'ok' + + done(null, payload) + }) + + fastify.route({ + method: 'GET', + url: '/first', + handler: function (req, reply) { + reply.send({ hello: 'world' }) + }, + schema: { + response: { + 200: { + type: 'object', + properties: { + hello: { + type: 'string' + }, + world: { + type: 'string' + } + }, + required: ['world'], + additionalProperties: false + } + } + } + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + + const result = await fetch(fastifyServer + '/first') + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world1', world: 'ok' }) +}) + +test('preSerialization hook should be able to throw errors which are validated against schema response', async t => { + t.plan(5) + const fastify = Fastify() + t.after(() => { fastify.close() }) + + fastify.addHook('preSerialization', function (req, reply, payload, done) { + done(new Error('preSerialization aborted')) + }) + + fastify.setErrorHandler((err, request, reply) => { + t.assert.strictEqual(err.message, 'preSerialization aborted') + err.world = 'error' + reply.send(err) + }) + + fastify.route({ + method: 'GET', + url: '/first', + handler: function (req, reply) { + reply.send({ world: 'hello' }) + }, + schema: { + response: { + 500: { + type: 'object', + properties: { + world: { + type: 'string' + } + }, + required: ['world'], + additionalProperties: false + } + } + } + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + + const result = await fetch(fastifyServer + '/first') + t.assert.ok(!result.ok) + t.assert.strictEqual(result.status, 500) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { world: 'error' }) +}) + +test('preSerialization hook which returned error should still run onError hooks', async t => { + t.plan(3) + const fastify = Fastify() + t.after(() => { fastify.close() }) + + fastify.addHook('preSerialization', function (req, reply, payload, done) { + done(new Error('preSerialization aborted')) + }) + + fastify.addHook('onError', function (req, reply, payload, done) { + t.assert.ok('should pass') + done() + }) + + fastify.get('/first', (req, reply) => { + reply.send({ hello: 'world' }) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + + const result = await fetch(fastifyServer + '/first') + t.assert.ok(!result.ok) + t.assert.strictEqual(result.status, 500) +}) + +test('preSerialization hooks should run in the order in which they are defined', async t => { + t.plan(4) + const fastify = Fastify() + t.after(() => { fastify.close() }) + + fastify.addHook('preSerialization', function (req, reply, payload, done) { + payload.hello += '2' + + done(null, payload) + }) + + fastify.addHook('preSerialization', function (req, reply, payload, done) { + payload.hello += '1' + + done(null, payload) + }) + + fastify.get('/first', (req, reply) => { + reply.send({ hello: 'world' }) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + + const result = await fetch(fastifyServer + '/first') + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world21' }) +}) + +test('preSerialization hooks should support encapsulation', async t => { + t.plan(8) + const fastify = Fastify() + t.after(() => { fastify.close() }) + + fastify.addHook('preSerialization', function (req, reply, payload, done) { + payload.hello += '1' + + done(null, payload) + }) + + fastify.get('/first', (req, reply) => { + reply.send({ hello: 'world' }) + }) + + fastify.register((instance, opts, done) => { + instance.addHook('preSerialization', function (req, reply, payload, done) { + payload.hello += '2' + + done(null, payload) + }) + + instance.get('/second', (req, reply) => { + reply.send({ hello: 'world' }) + }) + + done() + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + + const result1 = await fetch(fastifyServer + '/first') + t.assert.ok(result1.ok) + t.assert.strictEqual(result1.status, 200) + const body1 = await result1.text() + t.assert.strictEqual(result1.headers.get('content-length'), '' + body1.length) + t.assert.deepStrictEqual(JSON.parse(body1), { hello: 'world1' }) + + const result2 = await fetch(fastifyServer + '/second') + t.assert.ok(result2.ok) + t.assert.strictEqual(result2.status, 200) + const body2 = await result2.text() + t.assert.strictEqual(result2.headers.get('content-length'), '' + body2.length) + t.assert.deepStrictEqual(JSON.parse(body2), { hello: 'world12' }) +}) + +test('onRegister hook should be called / 1', (t, testDone) => { + t.plan(5) + const fastify = Fastify() + + fastify.addHook('onRegister', function (instance, opts, done) { + t.assert.ok(this.addHook) + t.assert.ok(instance.addHook) + t.assert.deepStrictEqual(opts, pluginOpts) + t.assert.ok(!done) + }) + + const pluginOpts = { prefix: 'hello', custom: 'world' } + fastify.register((instance, opts, done) => { + done() + }, pluginOpts) + + fastify.ready(err => { + t.assert.ifError(err) + testDone() + }) +}) + +test('onRegister hook should be called / 2', (t, testDone) => { + t.plan(7) + const fastify = Fastify() + + fastify.addHook('onRegister', function (instance) { + t.assert.ok(this.addHook) + t.assert.ok(instance.addHook) + }) + + fastify.register((instance, opts, done) => { + instance.register((instance, opts, done) => { + done() + }) + done() + }) + + fastify.register((instance, opts, done) => { + done() + }) + + fastify.ready(err => { + t.assert.ifError(err) + testDone() + }) +}) + +test('onRegister hook should be called / 3', (t, testDone) => { + t.plan(4) + const fastify = Fastify() + + fastify.decorate('data', []) + + fastify.addHook('onRegister', instance => { + instance.data = instance.data.slice() + }) + + fastify.register((instance, opts, done) => { + instance.data.push(1) + instance.register((instance, opts, done) => { + instance.data.push(2) + t.assert.deepStrictEqual(instance.data, [1, 2]) + done() + }) + t.assert.deepStrictEqual(instance.data, [1]) + done() + }) + + fastify.register((instance, opts, done) => { + t.assert.deepStrictEqual(instance.data, []) + done() + }) + + fastify.ready(err => { + t.assert.ifError(err) + testDone() + }) +}) + +test('onRegister hook should be called (encapsulation)', (t, testDone) => { + t.plan(1) + const fastify = Fastify() + + function plugin (instance, opts, done) { + done() + } + plugin[Symbol.for('skip-override')] = true + + fastify.addHook('onRegister', (instance, opts) => { + t.assert.fail('This should not be called') + }) + + fastify.register(plugin) + + fastify.ready(err => { + t.assert.ifError(err) + testDone() + }) +}) + +test('early termination, onRequest', (t, testDone) => { + t.plan(3) + + const app = Fastify() + + app.addHook('onRequest', (req, reply) => { + setImmediate(() => reply.send('hello world')) + return reply + }) + + app.get('/', (req, reply) => { + t.assert.fail('should not happen') + }) + + app.inject('/', function (err, res) { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.body.toString(), 'hello world') + testDone() + }) +}) + +test('reply.send should throw if undefined error is thrown', (t, testDone) => { + /* eslint prefer-promise-reject-errors: ["error", {"allowEmptyReject": true}] */ + + t.plan(3) + const fastify = Fastify() + + fastify.addHook('onRequest', function (req, reply, done) { + return Promise.reject() + }) + + fastify.get('/', (req, reply) => { + reply.send('hello') + }) + + fastify.inject({ + method: 'GET', + url: '/' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 500) + t.assert.deepStrictEqual(JSON.parse(res.payload), { + error: 'Internal Server Error', + code: 'FST_ERR_SEND_UNDEFINED_ERR', + message: 'Undefined error has occurred', + statusCode: 500 + }) + testDone() + }) +}) + +test('reply.send should throw if undefined error is thrown at preParsing hook', (t, testDone) => { + t.plan(3) + const fastify = Fastify() + + fastify.addHook('preParsing', function (req, reply, done) { + return Promise.reject() + }) + + fastify.get('/', (req, reply) => { + reply.send('hello') + }) + + fastify.inject({ + method: 'GET', + url: '/' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 500) + t.assert.deepStrictEqual(JSON.parse(res.payload), { + error: 'Internal Server Error', + code: 'FST_ERR_SEND_UNDEFINED_ERR', + message: 'Undefined error has occurred', + statusCode: 500 + }) + testDone() + }) +}) + +test('reply.send should throw if undefined error is thrown at onSend hook', (t, testDone) => { + t.plan(3) + const fastify = Fastify() + + fastify.addHook('onSend', function (req, reply, done) { + return Promise.reject() + }) + + fastify.get('/', (req, reply) => { + reply.send('hello') + }) + + fastify.inject({ + method: 'GET', + url: '/' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 500) + t.assert.deepStrictEqual(JSON.parse(res.payload), { + error: 'Internal Server Error', + code: 'FST_ERR_SEND_UNDEFINED_ERR', + message: 'Undefined error has occurred', + statusCode: 500 + }) + testDone() + }) +}) + +test('onTimeout should be triggered', async t => { + t.plan(4) + const fastify = Fastify({ connectionTimeout: 500 }) + t.after(() => { fastify.close() }) + + fastify.addHook('onTimeout', function (req, res, done) { + t.assert.ok('called', 'onTimeout') + done() + }) + + fastify.get('/', async (req, reply) => { + await reply.send({ hello: 'world' }) + }) + + fastify.get('/timeout', async (req, reply) => { + return reply + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + + const result1 = await fetch(fastifyServer) + t.assert.ok(result1.ok) + t.assert.strictEqual(result1.status, 200) + + await t.assert.rejects(() => fetch(fastifyServer + '/timeout')) +}) + +test('onTimeout should be triggered and socket _meta is set', async t => { + t.plan(4) + const fastify = Fastify({ connectionTimeout: 500 }) + t.after(() => { fastify.close() }) + + fastify.addHook('onTimeout', function (req, res, done) { + t.assert.ok('called', 'onTimeout') + done() + }) + + fastify.get('/', async (req, reply) => { + req.raw.socket._meta = {} + return reply.send({ hello: 'world' }) + }) + + fastify.get('/timeout', async (req, reply) => { + return reply + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + + const result1 = await fetch(fastifyServer) + t.assert.ok(result1.ok) + t.assert.strictEqual(result1.status, 200) + + try { + await fetch(fastifyServer + '/timeout') + t.fail('Should have thrown an error') + } catch (err) { + t.assert.ok(err instanceof Error) + } +}) + +test('registering invalid hooks should throw an error', async t => { + t.plan(3) + + const fastify = Fastify() + + t.assert.throws(() => { + fastify.route({ + method: 'GET', + path: '/invalidHook', + onRequest: [undefined], + async handler () { + return 'hello world' + } + }) + }, { + message: 'onRequest hook should be a function, instead got [object Undefined]' + }) + + t.assert.throws(() => { + fastify.route({ + method: 'GET', + path: '/invalidHook', + onRequest: null, + async handler () { + return 'hello world' + } + }) + }, { message: 'onRequest hook should be a function, instead got [object Null]' }) + + // undefined is ok + fastify.route({ + method: 'GET', + path: '/validhook', + onRequest: undefined, + async handler () { + return 'hello world' + } + }) + + t.assert.throws(() => { + fastify.addHook('onRoute', (routeOptions) => { + routeOptions.onSend = [undefined] + }) + + fastify.get('/', function (request, reply) { + reply.send('hello world') + }) + }, { message: 'onSend hook should be a function, instead got [object Undefined]' }) +}) + +test('onRequestAbort should be triggered', (t, testDone) => { + const fastify = Fastify() + let order = 0 + + t.plan(7) + t.after(() => fastify.close()) + + const completion = waitForCb({ steps: 2 }) + completion.patience.then(testDone) + + fastify.addHook('onRequestAbort', function (req, done) { + t.assert.strictEqual(++order, 1, 'called in hook') + t.assert.ok(req.pendingResolve, 'request has pendingResolve') + req.pendingResolve() + completion.stepIn() + done() + }) + + fastify.addHook('onError', function hook (request, reply, error, done) { + t.assert.fail('onError should not be called') + done() + }) + + fastify.addHook('onSend', function hook (request, reply, payload, done) { + t.assert.strictEqual(payload, '{"hello":"world"}', 'onSend should be called') + done(null, payload) + }) + + fastify.addHook('onResponse', function hook (request, reply, done) { + t.assert.fail('onResponse should not be called') + done() + }) + + fastify.route({ + method: 'GET', + path: '/', + async handler (request, reply) { + t.assert.ok('handler called') + let resolvePromise + const promise = new Promise(resolve => { resolvePromise = resolve }) + request.pendingResolve = resolvePromise + await promise + t.assert.ok('handler promise resolved') + return { hello: 'world' } + }, + async onRequestAbort (req) { + t.assert.strictEqual(++order, 2, 'called in route') + completion.stepIn() + } + }) + + fastify.listen({ port: 0 }, err => { + t.assert.ifError(err) + + const socket = connect(fastify.server.address().port) + + socket.write('GET / HTTP/1.1\r\nHost: fastify.test\r\n\r\n') + + sleep(500).then(() => socket.destroy()) + }) +}) + +test('onRequestAbort should support encapsulation', (t, testDone) => { + const fastify = Fastify() + let order = 0 + let child + + t.plan(6) + t.after(() => fastify.close()) + + const completion = waitForCb({ steps: 2 }) + completion.patience.then(testDone) + + fastify.addHook('onRequestAbort', function (req, done) { + t.assert.strictEqual(++order, 1, 'called in root') + t.assert.deepStrictEqual(this.pluginName, child.pluginName) + completion.stepIn() + done() + }) + + fastify.register(async function (_child, _) { + child = _child + + fastify.addHook('onRequestAbort', async function (req) { + t.assert.strictEqual(++order, 2, 'called in child') + t.assert.deepStrictEqual(this.pluginName, child.pluginName) + completion.stepIn() + }) + + child.route({ + method: 'GET', + path: '/', + async handler (request, reply) { + await sleep(1000) + return { hello: 'world' } + }, + async onRequestAbort (_req) { + t.assert.strictEqual(++order, 3, 'called in route') + } + }) + }) + + fastify.listen({ port: 0 }, err => { + t.assert.ifError(err) + + const socket = connect(fastify.server.address().port) + + socket.write('GET / HTTP/1.1\r\nHost: fastify.test\r\n\r\n') + + sleep(500).then(() => socket.destroy()) + }) +}) + +test('onRequestAbort should handle errors / 1', (t, testDone) => { + const fastify = Fastify() + + t.plan(2) + t.after(() => fastify.close()) + + fastify.addHook('onRequestAbort', function (req, done) { + process.nextTick(() => { + t.assert.ok('should pass') + testDone() + }) + done(new Error('KABOOM!')) + }) + + fastify.route({ + method: 'GET', + path: '/', + async handler (request, reply) { + await sleep(1000) + return { hello: 'world' } + } + }) + + fastify.listen({ port: 0 }, err => { + t.assert.ifError(err) + + const socket = connect(fastify.server.address().port) + + socket.write('GET / HTTP/1.1\r\nHost: fastify.test\r\n\r\n') + + sleep(500).then(() => socket.destroy()) + }) +}) + +test('onRequestAbort should handle errors / 2', (t, testDone) => { + const fastify = Fastify() + + t.plan(2) + t.after(() => fastify.close()) + + fastify.addHook('onRequestAbort', function (req, done) { + process.nextTick(() => { + t.assert.ok('should pass') + testDone() + }) + throw new Error('KABOOM!') + }) + + fastify.route({ + method: 'GET', + path: '/', + async handler (request, reply) { + await sleep(1000) + return { hello: 'world' } + } + }) + + fastify.listen({ port: 0 }, err => { + t.assert.ifError(err) + + const socket = connect(fastify.server.address().port) + + socket.write('GET / HTTP/1.1\r\nHost: fastify.test\r\n\r\n') + + sleep(500).then(() => socket.destroy()) + }) +}) + +test('onRequestAbort should handle async errors / 1', (t, testDone) => { + const fastify = Fastify() + + t.plan(2) + t.after(() => fastify.close()) + + fastify.addHook('onRequestAbort', async function (req) { + process.nextTick(() => { + t.assert.ok('should pass') + testDone() + }) + throw new Error('KABOOM!') + }) + + fastify.route({ + method: 'GET', + path: '/', + async handler (request, reply) { + await sleep(1000) + return { hello: 'world' } + } + }) + + fastify.listen({ port: 0 }, err => { + t.assert.ifError(err) + + const socket = connect(fastify.server.address().port) + + socket.write('GET / HTTP/1.1\r\nHost: fastify.test\r\n\r\n') + + sleep(500).then(() => socket.destroy()) + }) +}) + +test('onRequestAbort should handle async errors / 2', (t, testDone) => { + const fastify = Fastify() + + t.plan(2) + t.after(() => fastify.close()) + + fastify.addHook('onRequestAbort', async function (req) { + process.nextTick(() => { + t.assert.ok('should pass') + testDone() + }) + + return Promise.reject() + }) + + fastify.route({ + method: 'GET', + path: '/', + async handler (request, reply) { + await sleep(1000) + return { hello: 'world' } + } + }) + + fastify.listen({ port: 0 }, err => { + t.assert.ifError(err) + + const socket = connect(fastify.server.address().port) + + socket.write('GET / HTTP/1.1\r\nHost: fastify.test\r\n\r\n') + + sleep(500).then(() => socket.destroy()) + }) +}) diff --git a/services/slides/node_modules/fastify/test/http-methods/copy.test.js b/services/slides/node_modules/fastify/test/http-methods/copy.test.js new file mode 100644 index 0000000000000000000000000000000000000000..15a4fc807ef6e21967b629bbc641ceaf31d05122 --- /dev/null +++ b/services/slides/node_modules/fastify/test/http-methods/copy.test.js @@ -0,0 +1,35 @@ +'use strict' + +const { test } = require('node:test') +const fastify = require('../../fastify')() +fastify.addHttpMethod('COPY') + +test('can be created - copy', async t => { + t.plan(3) + + t.after(() => fastify.close()) + + try { + fastify.route({ + method: 'COPY', + url: '*', + handler: function (req, reply) { + reply.code(204).send() + } + }) + t.assert.ok(true) + } catch (e) { + t.assert.fail() + } + + const fastifyServer = await fastify.listen({ port: 0 }) + + const result = await fetch(`${fastifyServer}/test.txt`, { + method: 'COPY', + headers: { + Destination: '/test2.txt' + } + }) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 204) +}) diff --git a/services/slides/node_modules/fastify/test/http-methods/custom-http-methods.test.js b/services/slides/node_modules/fastify/test/http-methods/custom-http-methods.test.js new file mode 100644 index 0000000000000000000000000000000000000000..61feed4e31858881615324dafa8e8e8614edfe39 --- /dev/null +++ b/services/slides/node_modules/fastify/test/http-methods/custom-http-methods.test.js @@ -0,0 +1,114 @@ +'use strict' + +const http = require('node:http') +const { test } = require('node:test') +const Fastify = require('../../fastify') + +function addEcho (fastify, method) { + fastify.route({ + method, + url: '/', + handler: function (req, reply) { + reply.send(req.body) + } + }) +} + +test('missing method from http client', (t, done) => { + t.plan(2) + const fastify = Fastify() + + fastify.listen({ port: 3000 }, (err) => { + t.assert.ifError(err) + + const port = fastify.server.address().port + const req = http.request({ + port, + method: 'REBIND', + path: '/' + }, (res) => { + t.assert.strictEqual(res.statusCode, 404) + fastify.close() + done() + }) + + req.end() + }) +}) + +test('addHttpMethod increase the supported HTTP methods supported', (t, done) => { + t.plan(8) + const app = Fastify() + + t.assert.throws(() => { addEcho(app, 'REBIND') }, /REBIND method is not supported./) + t.assert.ok(!app.supportedMethods.includes('REBIND')) + t.assert.ok(!app.rebind) + + app.addHttpMethod('REBIND') + t.assert.doesNotThrow(() => { addEcho(app, 'REBIND') }, 'REBIND method is supported.') + t.assert.ok(app.supportedMethods.includes('REBIND')) + t.assert.ok(app.rebind) + + app.rebind('/foo', () => 'hello') + + app.inject({ + method: 'REBIND', + url: '/foo' + }, (err, response) => { + t.assert.ifError(err) + t.assert.strictEqual(response.payload, 'hello') + done() + }) +}) + +test('addHttpMethod adds a new custom method without body', t => { + t.plan(3) + const app = Fastify() + + t.assert.throws(() => { addEcho(app, 'REBIND') }, /REBIND method is not supported./) + + app.addHttpMethod('REBIND') + t.assert.doesNotThrow(() => { addEcho(app, 'REBIND') }, 'REBIND method is supported.') + + t.assert.throws(() => { + app.route({ + url: '/', + method: 'REBIND', + schema: { + body: { + type: 'object', + properties: { + hello: { type: 'string' } + } + } + }, + handler: function (req, reply) { + reply.send(req.body) + } + }) + }, /Body validation schema for REBIND:\/ route is not supported!/) +}) + +test('addHttpMethod adds a new custom method with body', (t, done) => { + t.plan(3) + const app = Fastify() + + app.addHttpMethod('REBIND', { hasBody: true }) + t.assert.doesNotThrow(() => { addEcho(app, 'REBIND') }, 'REBIND method is supported.') + + app.inject({ + method: 'REBIND', + url: '/', + payload: { hello: 'world' } + }, (err, response) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(response.json(), { hello: 'world' }) + done() + }) +}) + +test('addHttpMethod rejects fake http method', t => { + t.plan(1) + const fastify = Fastify() + t.assert.throws(() => { fastify.addHttpMethod('FOOO') }, /Provided method is invalid!/) +}) diff --git a/services/slides/node_modules/fastify/test/http-methods/get.test.js b/services/slides/node_modules/fastify/test/http-methods/get.test.js new file mode 100644 index 0000000000000000000000000000000000000000..8522a083c644e9ebee6fe9b0b4c73db14f555b5e --- /dev/null +++ b/services/slides/node_modules/fastify/test/http-methods/get.test.js @@ -0,0 +1,412 @@ +'use strict' + +const { test } = require('node:test') +const { Client } = require('undici') +const fastify = require('../../fastify')() + +const schema = { + schema: { + response: { + '2xx': { + type: 'object', + properties: { + hello: { + type: 'string' + } + } + } + } + } +} + +const nullSchema = { + schema: { + response: { + '2xx': { + type: 'null' + } + } + } +} + +const numberSchema = { + schema: { + response: { + '2xx': { + type: 'object', + properties: { + hello: { + type: 'number' + } + } + } + } + } +} + +const querySchema = { + schema: { + querystring: { + type: 'object', + properties: { + hello: { + type: 'integer' + } + } + } + } +} + +const paramsSchema = { + schema: { + params: { + type: 'object', + properties: { + foo: { + type: 'string' + }, + test: { + type: 'integer' + } + } + } + } +} + +const headersSchema = { + schema: { + headers: { + type: 'object', + properties: { + 'x-test': { + type: 'number' + }, + 'Y-Test': { + type: 'number' + } + } + } + } +} + +test('shorthand - get', t => { + t.plan(1) + try { + fastify.get('/', schema, function (req, reply) { + reply.code(200).send({ hello: 'world' }) + }) + t.assert.ok(true) + } catch (e) { + t.assert.fail() + } +}) + +test('shorthand - get (return null)', t => { + t.plan(1) + try { + fastify.get('/null', nullSchema, function (req, reply) { + reply.code(200).send(null) + }) + t.assert.ok(true) + } catch (e) { + t.assert.fail() + } +}) + +test('shorthand - get params', t => { + t.plan(1) + try { + fastify.get('/params/:foo/:test', paramsSchema, function (req, reply) { + reply.code(200).send(req.params) + }) + t.assert.ok(true) + } catch (e) { + t.assert.fail() + } +}) + +test('shorthand - get, querystring schema', t => { + t.plan(1) + try { + fastify.get('/query', querySchema, function (req, reply) { + reply.code(200).send(req.query) + }) + t.assert.ok(true) + } catch (e) { + t.assert.fail() + } +}) + +test('shorthand - get, headers schema', t => { + t.plan(1) + try { + fastify.get('/headers', headersSchema, function (req, reply) { + reply.code(200).send(req.headers) + }) + t.assert.ok(true) + } catch (e) { + t.assert.fail() + } +}) + +test('missing schema - get', t => { + t.plan(1) + try { + fastify.get('/missing', function (req, reply) { + reply.code(200).send({ hello: 'world' }) + }) + t.assert.ok(true) + } catch (e) { + t.assert.fail() + } +}) + +test('custom serializer - get', t => { + t.plan(1) + + function customSerializer (data) { + return JSON.stringify(data) + } + + try { + fastify.get('/custom-serializer', numberSchema, function (req, reply) { + reply.code(200).serializer(customSerializer).send({ hello: 'world' }) + }) + t.assert.ok(true) + } catch (e) { + t.assert.fail() + } +}) + +test('empty response', t => { + t.plan(1) + try { + fastify.get('/empty', function (req, reply) { + reply.code(200).send() + }) + t.assert.ok(true) + } catch (e) { + t.assert.fail() + } +}) + +test('send a falsy boolean', t => { + t.plan(1) + try { + fastify.get('/boolean', function (req, reply) { + reply.code(200).send(false) + }) + t.assert.ok(true) + } catch (e) { + t.assert.fail() + } +}) + +test('shorthand - get, set port', t => { + t.plan(1) + try { + fastify.get('/port', headersSchema, function (req, reply) { + reply.code(200).send({ port: req.port }) + }) + t.assert.ok(true) + } catch (e) { + t.assert.fail() + } +}) + +test('get test', async t => { + t.after(() => { fastify.close() }) + + await fastify.listen({ port: 0 }) + + await t.test('shorthand - request get', async t => { + t.plan(4) + + const response = await fetch('http://localhost:' + fastify.server.address().port, { + method: 'GET' + }) + t.assert.ok(response.ok) + t.assert.strictEqual(response.status, 200) + const body = await response.text() + t.assert.strictEqual(response.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + }) + + await t.test('shorthand - request get params schema', async t => { + t.plan(4) + + const response = await fetch('http://localhost:' + fastify.server.address().port + '/params/world/123', { + method: 'GET' + }) + t.assert.ok(response.ok) + t.assert.strictEqual(response.status, 200) + const body = await response.text() + t.assert.strictEqual(response.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { foo: 'world', test: 123 }) + }) + + await t.test('shorthand - request get params schema error', async t => { + t.plan(3) + + const response = await fetch('http://localhost:' + fastify.server.address().port + '/params/world/string', { + method: 'GET' + }) + t.assert.ok(!response.ok) + t.assert.strictEqual(response.status, 400) + const body = await response.text() + t.assert.deepStrictEqual(JSON.parse(body), { + error: 'Bad Request', + code: 'FST_ERR_VALIDATION', + message: 'params/test must be integer', + statusCode: 400 + }) + }) + + await t.test('shorthand - request get headers schema', async t => { + t.plan(4) + + const response = await fetch('http://localhost:' + fastify.server.address().port + '/headers', { + method: 'GET', + headers: { + 'x-test': '1', + 'Y-Test': '3' + } + }) + t.assert.ok(response.ok) + t.assert.strictEqual(response.status, 200) + const body = await response.json() + t.assert.strictEqual(body['x-test'], 1) + t.assert.strictEqual(body['y-test'], 3) + }) + + await t.test('shorthand - request get headers schema error', async t => { + t.plan(3) + + const response = await fetch('http://localhost:' + fastify.server.address().port + '/headers', { + method: 'GET', + headers: { + 'x-test': 'abc' + } + }) + t.assert.ok(!response.ok) + t.assert.strictEqual(response.status, 400) + const body = await response.text() + t.assert.deepStrictEqual(JSON.parse(body), { + error: 'Bad Request', + code: 'FST_ERR_VALIDATION', + message: 'headers/x-test must be number', + statusCode: 400 + }) + }) + + await t.test('shorthand - request get querystring schema', async t => { + t.plan(4) + + const response = await fetch('http://localhost:' + fastify.server.address().port + '/query?hello=123', { + method: 'GET' + }) + t.assert.ok(response.ok) + t.assert.strictEqual(response.status, 200) + const body = await response.text() + t.assert.strictEqual(response.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 123 }) + }) + + await t.test('shorthand - request get querystring schema error', async t => { + t.plan(3) + + const response = await fetch('http://localhost:' + fastify.server.address().port + '/query?hello=world', { + method: 'GET' + }) + t.assert.ok(!response.ok) + t.assert.strictEqual(response.status, 400) + const body = await response.text() + t.assert.deepStrictEqual(JSON.parse(body), { + error: 'Bad Request', + code: 'FST_ERR_VALIDATION', + message: 'querystring/hello must be integer', + statusCode: 400 + }) + }) + + await t.test('shorthand - request get missing schema', async t => { + t.plan(4) + + const response = await fetch('http://localhost:' + fastify.server.address().port + '/missing', { + method: 'GET' + }) + t.assert.ok(response.ok) + t.assert.strictEqual(response.status, 200) + const body = await response.text() + t.assert.strictEqual(response.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + }) + + await t.test('shorthand - custom serializer', async t => { + t.plan(4) + + const response = await fetch('http://localhost:' + fastify.server.address().port + '/custom-serializer', { + method: 'GET' + }) + t.assert.ok(response.ok) + t.assert.strictEqual(response.status, 200) + const body = await response.text() + t.assert.strictEqual(response.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + }) + + await t.test('shorthand - empty response', async t => { + t.plan(4) + + const response = await fetch('http://localhost:' + fastify.server.address().port + '/empty', { + method: 'GET' + }) + t.assert.ok(response.ok) + t.assert.strictEqual(response.status, 200) + const body = await response.text() + t.assert.strictEqual(response.headers.get('content-length'), '0') + t.assert.deepStrictEqual(body.toString(), '') + }) + + await t.test('shorthand - send a falsy boolean', async t => { + t.plan(3) + + const response = await fetch('http://localhost:' + fastify.server.address().port + '/boolean', { + method: 'GET' + }) + t.assert.ok(response.ok) + t.assert.strictEqual(response.status, 200) + const body = await response.text() + t.assert.deepStrictEqual(body.toString(), 'false') + }) + + await t.test('shorthand - send null value', async t => { + t.plan(3) + + const response = await fetch('http://localhost:' + fastify.server.address().port + '/null', { + method: 'GET' + }) + t.assert.ok(response.ok) + t.assert.strictEqual(response.status, 200) + const body = await response.text() + t.assert.deepStrictEqual(body.toString(), 'null') + }) + + await t.test('shorthand - request get headers - test fall back port', async t => { + t.plan(2) + + const instance = new Client('http://localhost:' + fastify.server.address().port) + + const response = await instance.request({ + path: '/port', + method: 'GET', + headers: { + host: 'fastify.test' + } + }) + + t.assert.strictEqual(response.statusCode, 200) + const body = JSON.parse(await response.body.text()) + t.assert.strictEqual(body.port, null) + }) +}) diff --git a/services/slides/node_modules/fastify/test/http-methods/head.test.js b/services/slides/node_modules/fastify/test/http-methods/head.test.js new file mode 100644 index 0000000000000000000000000000000000000000..fe68694ef37ceb105f497495a145102fc3ef3b7b --- /dev/null +++ b/services/slides/node_modules/fastify/test/http-methods/head.test.js @@ -0,0 +1,263 @@ +'use strict' + +const { test } = require('node:test') +const fastify = require('../../fastify')() + +const schema = { + schema: { + response: { + '2xx': { + type: 'null' + } + } + } +} + +const querySchema = { + schema: { + querystring: { + type: 'object', + properties: { + hello: { + type: 'integer' + } + } + } + } +} + +const paramsSchema = { + schema: { + params: { + type: 'object', + properties: { + foo: { + type: 'string' + }, + test: { + type: 'integer' + } + } + } + } +} + +test('shorthand - head', t => { + t.plan(1) + try { + fastify.head('/', schema, function (req, reply) { + reply.code(200).send(null) + }) + t.assert.ok(true) + } catch (e) { + t.assert.fail() + } +}) + +test('shorthand - custom head', t => { + t.plan(1) + try { + fastify.head('/proxy/*', function (req, reply) { + reply.headers({ 'x-foo': 'bar' }) + reply.code(200).send(null) + }) + + fastify.get('/proxy/*', function (req, reply) { + reply.code(200).send(null) + }) + + t.assert.ok(true) + } catch (e) { + t.assert.fail() + } +}) + +test('shorthand - custom head with constraints', t => { + t.plan(1) + try { + fastify.head('/proxy/*', { constraints: { version: '1.0.0' } }, function (req, reply) { + reply.headers({ 'x-foo': 'bar' }) + reply.code(200).send(null) + }) + + fastify.get('/proxy/*', { constraints: { version: '1.0.0' } }, function (req, reply) { + reply.code(200).send(null) + }) + + t.assert.ok(true) + } catch (e) { + t.assert.fail() + } +}) + +test('shorthand - should not reset a head route', t => { + t.plan(1) + try { + fastify.get('/query1', function (req, reply) { + reply.code(200).send(null) + }) + + fastify.put('/query1', function (req, reply) { + reply.code(200).send(null) + }) + + t.assert.ok(true) + } catch (e) { + t.assert.fail() + } +}) + +test('shorthand - should set get and head route in the same api call', t => { + t.plan(1) + try { + fastify.route({ + method: ['HEAD', 'GET'], + url: '/query4', + handler: function (req, reply) { + reply.headers({ 'x-foo': 'bar' }) + reply.code(200).send(null) + } + }) + + t.assert.ok(true) + } catch (e) { + t.assert.fail() + } +}) + +test('shorthand - head params', t => { + t.plan(1) + try { + fastify.head('/params/:foo/:test', paramsSchema, function (req, reply) { + reply.send(null) + }) + t.assert.ok(true) + } catch (e) { + t.assert.fail() + } +}) + +test('shorthand - head, querystring schema', t => { + t.plan(1) + try { + fastify.head('/query', querySchema, function (req, reply) { + reply.code(200).send(null) + }) + t.assert.ok(true) + } catch (e) { + t.assert.fail() + } +}) + +test('missing schema - head', t => { + t.plan(1) + try { + fastify.head('/missing', function (req, reply) { + reply.code(200).send(null) + }) + t.assert.ok(true) + } catch (e) { + t.assert.fail() + } +}) + +test('head test', async t => { + t.after(() => { fastify.close() }) + const fastifyServer = await fastify.listen({ port: 0 }) + + await t.test('shorthand - request head', async t => { + t.plan(2) + const result = await fetch(fastifyServer, { + method: 'HEAD' + }) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + }) + + await t.test('shorthand - request head params schema', async t => { + t.plan(2) + const result = await fetch(`${fastifyServer}/params/world/123`, { + method: 'HEAD' + }) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + }) + + await t.test('shorthand - request head params schema error', async t => { + t.plan(2) + const result = await fetch(`${fastifyServer}/params/world/string`, { + method: 'HEAD' + }) + t.assert.ok(!result.ok) + t.assert.strictEqual(result.status, 400) + }) + + await t.test('shorthand - request head querystring schema', async t => { + t.plan(2) + const result = await fetch(`${fastifyServer}/query?hello=123`, { + method: 'HEAD' + }) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + }) + + await t.test('shorthand - request head querystring schema error', async t => { + t.plan(2) + const result = await fetch(`${fastifyServer}/query?hello=world`, { + method: 'HEAD' + }) + t.assert.ok(!result.ok) + t.assert.strictEqual(result.status, 400) + }) + + await t.test('shorthand - request head missing schema', async t => { + t.plan(2) + const result = await fetch(`${fastifyServer}/missing`, { + method: 'HEAD' + }) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + }) + + await t.test('shorthand - request head custom head', async t => { + t.plan(3) + const result = await fetch(`${fastifyServer}/proxy/test`, { + method: 'HEAD' + }) + t.assert.ok(result.ok) + t.assert.strictEqual(result.headers.get('x-foo'), 'bar') + t.assert.strictEqual(result.status, 200) + }) + + await t.test('shorthand - request head custom head with constraints', async t => { + t.plan(3) + const result = await fetch(`${fastifyServer}/proxy/test`, { + method: 'HEAD', + headers: { + version: '1.0.0' + } + }) + t.assert.ok(result.ok) + t.assert.strictEqual(result.headers.get('x-foo'), 'bar') + t.assert.strictEqual(result.status, 200) + }) + + await t.test('shorthand - should not reset a head route', async t => { + t.plan(2) + const result = await fetch(`${fastifyServer}/query1`, { + method: 'HEAD' + }) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + }) + + await t.test('shorthand - should set get and head route in the same api call', async t => { + t.plan(3) + const result = await fetch(`${fastifyServer}/query4`, { + method: 'HEAD' + }) + t.assert.ok(result.ok) + t.assert.strictEqual(result.headers.get('x-foo'), 'bar') + t.assert.strictEqual(result.status, 200) + }) +}) diff --git a/services/slides/node_modules/fastify/test/http-methods/lock.test.js b/services/slides/node_modules/fastify/test/http-methods/lock.test.js new file mode 100644 index 0000000000000000000000000000000000000000..6df050207fa24f7272931ba6f486256c40b8787a --- /dev/null +++ b/services/slides/node_modules/fastify/test/http-methods/lock.test.js @@ -0,0 +1,108 @@ +'use strict' + +const { test } = require('node:test') +const fastify = require('../../fastify')() +fastify.addHttpMethod('LOCK', { hasBody: true }) + +const bodySample = ` + + + + + http://fastify.test/~ejw/contact.html + + ` + +test('can be created - lock', t => { + t.plan(1) + try { + fastify.route({ + method: 'LOCK', + url: '*', + handler: function (req, reply) { + reply + .code(200) + .send(` + + + + + + + + + + infinity + + http://fastify.test/~ejw/contact.html + + Second-604800 + + urn:uuid:e71d4fae-5dec-22d6-fea5-00a0c91e6be4 + + + http://fastify.test/workspace/webdav/proposal.oc + + + + ` + ) + } + }) + t.assert.ok(true) + } catch (e) { + t.assert.fail() + } +}) + +test('lock test', async t => { + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => { + fastify.close() + }) + // the body test uses a text/plain content type instead of application/xml because it requires + // a specific content type parser + await t.test('request with body - lock', async (t) => { + t.plan(3) + + const result = await fetch(`${fastifyServer}/test/a.txt`, { + method: 'LOCK', + headers: { 'content-type': 'text/plain' }, + body: bodySample + }) + + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) + }) + + await t.test('request with body and no content type (415 error) - lock', async (t) => { + t.plan(3) + + const result = await fetch(`${fastifyServer}/test/a.txt`, { + method: 'LOCK', + body: bodySample, + headers: { 'content-type': undefined } + }) + + t.assert.ok(!result.ok) + t.assert.strictEqual(result.status, 415) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) + }) + + await t.test('request without body - lock', async (t) => { + t.plan(3) + + const result = await fetch(`${fastifyServer}/test/a.txt`, { + method: 'LOCK', + headers: { 'content-type': 'text/plain' } + }) + + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) + }) +}) diff --git a/services/slides/node_modules/fastify/test/http-methods/mkcalendar.test.js b/services/slides/node_modules/fastify/test/http-methods/mkcalendar.test.js new file mode 100644 index 0000000000000000000000000000000000000000..d22218844320ef1f4f3005eb45d948ed0903b77a --- /dev/null +++ b/services/slides/node_modules/fastify/test/http-methods/mkcalendar.test.js @@ -0,0 +1,143 @@ +'use strict' + +const { test } = require('node:test') +const fastify = require('../../fastify')() +fastify.addHttpMethod('MKCALENDAR', { hasBody: true }) + +const bodySample = ` + + + + + + + 0 + + + + CALENDAR_NAME + BEGIN:VCALENDAR + VERSION:2.0 + + + + + ` + +test('can be created - mkcalendar', (t) => { + t.plan(1) + try { + fastify.route({ + method: 'MKCALENDAR', + url: '*', + handler: function (req, reply) { + return reply.code(207).send(` + + + / + + + + + + 2022-04-13T12:35:30Z + Wed, 13 Apr 2022 12:35:30 GMT + "e0-5dc8869b53ef1" + + + + + + + + + + + + + + + + + + + + httpd/unix-directory + + HTTP/1.1 200 OK + + + `) + } + }) + t.assert.ok(true) + } catch (e) { + t.assert.fail() + } +}) + +test('mkcalendar test', async t => { + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => { + fastify.close() + }) + + await t.test('request - mkcalendar', async t => { + t.plan(2) + const result = await fetch(`${fastifyServer}/`, { + method: 'MKCALENDAR' + }) + t.assert.strictEqual(result.status, 207) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) + }) + + await t.test('request with other path - mkcalendar', async t => { + t.plan(2) + const result = await fetch(`${fastifyServer}/test`, { + method: 'MKCALENDAR' + }) + t.assert.strictEqual(result.status, 207) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) + }) + + // the body test uses a text/plain content type instead of application/xml because it requires + // a specific content type parser + await t.test('request with body - mkcalendar', async t => { + t.plan(3) + const result = await fetch(`${fastifyServer}/test`, { + method: 'MKCALENDAR', + headers: { 'content-type': 'text/plain' }, + body: bodySample + }) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 207) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) + }) + + await t.test('request with body and no content type (415 error) - mkcalendar', async t => { + t.plan(3) + const result = await fetch(`${fastifyServer}/test`, { + method: 'MKCALENDAR', + body: bodySample, + headers: { 'content-type': undefined } + }) + t.assert.ok(!result.ok) + t.assert.strictEqual(result.status, 415) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) + }) + + await t.test('request without body - mkcalendar', async t => { + t.plan(3) + const result = await fetch(`${fastifyServer}/test`, { + method: 'MKCALENDAR' + }) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 207) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) + }) +}) diff --git a/services/slides/node_modules/fastify/test/http-methods/mkcol.test.js b/services/slides/node_modules/fastify/test/http-methods/mkcol.test.js new file mode 100644 index 0000000000000000000000000000000000000000..3ef662a938fcd4b2747e967ba0ea365ee6399601 --- /dev/null +++ b/services/slides/node_modules/fastify/test/http-methods/mkcol.test.js @@ -0,0 +1,35 @@ +'use strict' + +const { test } = require('node:test') +const fastify = require('../../')() +fastify.addHttpMethod('MKCOL') + +test('can be created - mkcol', t => { + t.plan(1) + try { + fastify.route({ + method: 'MKCOL', + url: '*', + handler: function (req, reply) { + reply.code(201).send() + } + }) + t.assert.ok(true) + } catch (e) { + t.assert.fail() + } +}) + +test('mkcol test', async t => { + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) + + await t.test('request - mkcol', async t => { + t.plan(2) + const result = await fetch(`${fastifyServer}/test/`, { + method: 'MKCOL' + }) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 201) + }) +}) diff --git a/services/slides/node_modules/fastify/test/http-methods/move.test.js b/services/slides/node_modules/fastify/test/http-methods/move.test.js new file mode 100644 index 0000000000000000000000000000000000000000..b1a566b49d769dd8780e2335870e8cb64b23e385 --- /dev/null +++ b/services/slides/node_modules/fastify/test/http-methods/move.test.js @@ -0,0 +1,42 @@ +'use strict' + +const { test } = require('node:test') +const fastify = require('../../')() +fastify.addHttpMethod('MOVE') + +test('shorthand - move', t => { + t.plan(1) + try { + fastify.route({ + method: 'MOVE', + url: '*', + handler: function (req, reply) { + const destination = req.headers.destination + reply.code(201) + .header('location', destination) + .send() + } + }) + t.assert.ok(true) + } catch (e) { + t.assert.fail() + } +}) +test('move test', async t => { + const fastifyServer = await fastify.listen({ port: 0 }) + + t.after(() => { fastify.close() }) + + await t.test('request - move', async t => { + t.plan(3) + const result = await fetch(`${fastifyServer}/test.txt`, { + method: 'MOVE', + headers: { + Destination: '/test2.txt' + } + }) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 201) + t.assert.strictEqual(result.headers.get('location'), '/test2.txt') + }) +}) diff --git a/services/slides/node_modules/fastify/test/http-methods/propfind.test.js b/services/slides/node_modules/fastify/test/http-methods/propfind.test.js new file mode 100644 index 0000000000000000000000000000000000000000..1d6dd22ec8c00d6a76e4a44c3e600361bb737e0a --- /dev/null +++ b/services/slides/node_modules/fastify/test/http-methods/propfind.test.js @@ -0,0 +1,136 @@ +'use strict' + +const { test } = require('node:test') +const fastify = require('../../')() +fastify.addHttpMethod('PROPFIND', { hasBody: true }) + +const bodySample = ` + + + + + + ` + +test('can be created - propfind', t => { + t.plan(1) + try { + fastify.route({ + method: 'PROPFIND', + url: '*', + handler: function (req, reply) { + return reply.code(207) + .send(` + + + / + + + + + + 2022-04-13T12:35:30Z + Wed, 13 Apr 2022 12:35:30 GMT + "e0-5dc8869b53ef1" + + + + + + + + + + + + + + + + + + + + httpd/unix-directory + + HTTP/1.1 200 OK + + + ` + ) + } + }) + t.assert.ok(true) + } catch (e) { + t.assert.fail() + } +}) + +test('propfind test', async t => { + await fastify.listen({ port: 0 }) + + t.after(() => { + fastify.close() + }) + + await t.test('request - propfind', async t => { + t.plan(3) + const result = await fetch(`http://localhost:${fastify.server.address().port}/`, { + method: 'PROPFIND' + }) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 207) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) + }) + + await t.test('request with other path - propfind', async t => { + t.plan(3) + const result = await fetch(`http://localhost:${fastify.server.address().port}/test`, { + method: 'PROPFIND' + }) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 207) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) + }) + + // the body test uses a text/plain content type instead of application/xml because it requires + // a specific content type parser + await t.test('request with body - propfind', async t => { + t.plan(3) + const result = await fetch(`http://localhost:${fastify.server.address().port}/test`, { + method: 'PROPFIND', + headers: { 'content-type': 'text/plain' }, + body: bodySample + }) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 207) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) + }) + + await t.test('request with body and no content type (415 error) - propfind', async t => { + t.plan(3) + const result = await fetch(`http://localhost:${fastify.server.address().port}/test`, { + method: 'PROPFIND', + body: bodySample, + headers: { 'content-type': '' } + }) + t.assert.ok(!result.ok) + t.assert.strictEqual(result.status, 415) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) + }) + + await t.test('request without body - propfind', async t => { + t.plan(3) + const result = await fetch(`http://localhost:${fastify.server.address().port}/test`, { + method: 'PROPFIND' + }) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 207) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) + }) +}) diff --git a/services/slides/node_modules/fastify/test/http-methods/proppatch.test.js b/services/slides/node_modules/fastify/test/http-methods/proppatch.test.js new file mode 100644 index 0000000000000000000000000000000000000000..c2ec2180b7d35c24908bfda26ba4022a9e534410 --- /dev/null +++ b/services/slides/node_modules/fastify/test/http-methods/proppatch.test.js @@ -0,0 +1,105 @@ +'use strict' + +const { test } = require('node:test') +const fastify = require('../../')() +fastify.addHttpMethod('PROPPATCH', { hasBody: true }) + +const bodySample = ` + + + + + Jim Whitehead + Roy Fielding + + + + + + + + + ` + +test('shorthand - proppatch', t => { + t.plan(1) + try { + fastify.route({ + method: 'PROPPATCH', + url: '*', + handler: function (req, reply) { + reply + .code(207) + .send(` + + + http://www.fastify.test/bar.html + + + + + HTTP/1.1 424 Failed Dependency + + + + + + HTTP/1.1 409 Conflict + + Copyright Owner cannot be deleted or altered. + + ` + ) + } + }) + t.assert.ok(true) + } catch (e) { + t.assert.fail() + } +}) + +test('proppatch test', async t => { + const fastifyServer = await fastify.listen({ port: 0 }) + + t.after(() => { fastify.close() }) + // the body test uses a text/plain content type instead of application/xml because it requires + // a specific content type parser + await t.test('request with body - proppatch', async t => { + t.plan(3) + const result = await fetch(`${fastifyServer}/test/a.txt`, { + method: 'PROPPATCH', + headers: { 'content-type': 'text/plain' }, + body: bodySample + }) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 207) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) + }) + + await t.test('request with body and no content type (415 error) - proppatch', async t => { + t.plan(3) + const result = await fetch(`${fastifyServer}/test/a.txt`, { + method: 'PROPPATCH', + body: bodySample, + headers: { 'content-type': undefined } + }) + t.assert.ok(!result.ok) + t.assert.strictEqual(result.status, 415) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) + }) + + await t.test('request without body - proppatch', async t => { + t.plan(3) + const result = await fetch(`${fastifyServer}/test/a.txt`, { + method: 'PROPPATCH' + }) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 207) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) + }) +}) diff --git a/services/slides/node_modules/fastify/test/http-methods/report.test.js b/services/slides/node_modules/fastify/test/http-methods/report.test.js new file mode 100644 index 0000000000000000000000000000000000000000..c8533fb2948c8380661b89d94234e9b6a93063ab --- /dev/null +++ b/services/slides/node_modules/fastify/test/http-methods/report.test.js @@ -0,0 +1,142 @@ +'use strict' + +const { test } = require('node:test') +const fastify = require('../../fastify')() +fastify.addHttpMethod('REPORT', { hasBody: true }) + +const bodySample = ` + + + + + + + + + + + + + + ` + +test('can be created - report', (t) => { + t.plan(1) + try { + fastify.route({ + method: 'REPORT', + url: '*', + handler: function (req, reply) { + return reply.code(207).send(` + + + / + + + + + + 2022-04-13T12:35:30Z + Wed, 13 Apr 2022 12:35:30 GMT + "e0-5dc8869b53ef1" + + + + + + + + + + + + + + + + + + + + httpd/unix-directory + + HTTP/1.1 200 OK + + + `) + } + }) + t.assert.ok(true) + } catch (e) { + t.assert.fail() + } +}) + +test('report test', async t => { + await fastify.listen({ port: 0 }) + + t.after(() => { + fastify.close() + }) + + await t.test('request - report', async (t) => { + t.plan(3) + const result = await fetch(`http://localhost:${fastify.server.address().port}/`, { + method: 'REPORT' + }) + + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 207) + t.assert.strictEqual(result.headers.get('content-length'), '' + (await result.text()).length) + }) + + await t.test('request with other path - report', async (t) => { + t.plan(3) + const result = await fetch(`http://localhost:${fastify.server.address().port}/test`, { + method: 'REPORT' + }) + + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 207) + t.assert.strictEqual(result.headers.get('content-length'), '' + (await result.text()).length) + }) + + // the body test uses a text/plain content type instead of application/xml because it requires + // a specific content type parser + await t.test('request with body - report', async (t) => { + t.plan(3) + const result = await fetch(`http://localhost:${fastify.server.address().port}/test`, { + method: 'REPORT', + headers: { 'content-type': 'text/plain' }, + body: bodySample + }) + + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 207) + t.assert.strictEqual(result.headers.get('content-length'), '' + (await result.text()).length) + }) + + await t.test('request with body and no content type (415 error) - report', async (t) => { + t.plan(3) + const result = await fetch(`http://localhost:${fastify.server.address().port}/test`, { + method: 'REPORT', + body: bodySample, + headers: { 'content-type': '' } + }) + + t.assert.ok(!result.ok) + t.assert.strictEqual(result.status, 415) + t.assert.strictEqual(result.headers.get('content-length'), '' + (await result.text()).length) + }) + + await t.test('request without body - report', async (t) => { + t.plan(3) + const result = await fetch(`http://localhost:${fastify.server.address().port}/test`, { + method: 'REPORT' + }) + + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 207) + t.assert.strictEqual(result.headers.get('content-length'), '' + (await result.text()).length) + }) +}) diff --git a/services/slides/node_modules/fastify/test/http-methods/search.test.js b/services/slides/node_modules/fastify/test/http-methods/search.test.js new file mode 100644 index 0000000000000000000000000000000000000000..7e0cdcf58b995d0e03eda08be4e807e97ba51894 --- /dev/null +++ b/services/slides/node_modules/fastify/test/http-methods/search.test.js @@ -0,0 +1,233 @@ +'use strict' + +const { test } = require('node:test') +const fastify = require('../../fastify')() +fastify.addHttpMethod('SEARCH', { hasBody: true }) + +const schema = { + response: { + '2xx': { + type: 'object', + properties: { + hello: { + type: 'string' + } + } + } + } +} + +const querySchema = { + querystring: { + type: 'object', + properties: { + hello: { + type: 'integer' + } + } + } +} + +const paramsSchema = { + params: { + type: 'object', + properties: { + foo: { + type: 'string' + }, + test: { + type: 'integer' + } + } + } +} + +const bodySchema = { + body: { + type: 'object', + properties: { + foo: { + type: 'string' + }, + test: { + type: 'integer' + } + } + } +} + +test('search', t => { + t.plan(1) + try { + fastify.route({ + method: 'SEARCH', + url: '/', + schema, + handler: function (request, reply) { + reply.code(200).send({ hello: 'world' }) + } + }) + t.assert.ok(true) + } catch (e) { + t.assert.fail() + } +}) + +test('search, params schema', t => { + t.plan(1) + try { + fastify.route({ + method: 'SEARCH', + url: '/params/:foo/:test', + schema: paramsSchema, + handler: function (request, reply) { + reply.code(200).send(request.params) + } + }) + t.assert.ok(true) + } catch (e) { + t.assert.fail() + } +}) + +test('search, querystring schema', t => { + t.plan(1) + try { + fastify.route({ + method: 'SEARCH', + url: '/query', + schema: querySchema, + handler: function (request, reply) { + reply.code(200).send(request.query) + } + }) + t.assert.ok(true) + } catch (e) { + t.assert.fail() + } +}) + +test('search, body schema', t => { + t.plan(1) + try { + fastify.route({ + method: 'SEARCH', + url: '/body', + schema: bodySchema, + handler: function (request, reply) { + reply.code(200).send(request.body) + } + }) + t.assert.ok(true) + } catch (e) { + t.assert.fail() + } +}) + +test('search test', async t => { + await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) + const url = `http://localhost:${fastify.server.address().port}` + + await t.test('request - search', async t => { + t.plan(4) + const result = await fetch(url, { + method: 'SEARCH' + }) + const body = await result.text() + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + }) + + await t.test('request search params schema', async t => { + t.plan(4) + const result = await fetch(`${url}/params/world/123`, { + method: 'SEARCH' + }) + const body = await result.text() + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { foo: 'world', test: 123 }) + }) + + await t.test('request search params schema error', async t => { + t.plan(3) + const result = await fetch(`${url}/params/world/string`, { + method: 'SEARCH' + }) + const body = await result.text() + t.assert.strictEqual(result.status, 400) + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { + error: 'Bad Request', + code: 'FST_ERR_VALIDATION', + message: 'params/test must be integer', + statusCode: 400 + }) + }) + + await t.test('request search querystring schema', async t => { + t.plan(4) + const result = await fetch(`${url}/query?hello=123`, { + method: 'SEARCH' + }) + const body = await result.text() + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 123 }) + }) + + await t.test('request search querystring schema error', async t => { + t.plan(3) + const result = await fetch(`${url}/query?hello=world`, { + method: 'SEARCH' + }) + const body = await result.text() + t.assert.strictEqual(result.status, 400) + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { + error: 'Bad Request', + code: 'FST_ERR_VALIDATION', + message: 'querystring/hello must be integer', + statusCode: 400 + }) + }) + + await t.test('request search body schema', async t => { + t.plan(4) + const replyBody = { foo: 'bar', test: 5 } + const result = await fetch(`${url}/body`, { + method: 'SEARCH', + body: JSON.stringify(replyBody), + headers: { 'content-type': 'application/json' } + }) + const body = await result.text() + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), replyBody) + }) + + await t.test('request search body schema error', async t => { + t.plan(4) + const result = await fetch(`${url}/body`, { + method: 'SEARCH', + body: JSON.stringify({ foo: 'bar', test: 'test' }), + headers: { 'content-type': 'application/json' } + }) + const body = await result.text() + t.assert.ok(!result.ok) + t.assert.strictEqual(result.status, 400) + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { + error: 'Bad Request', + code: 'FST_ERR_VALIDATION', + message: 'body/test must be integer', + statusCode: 400 + }) + }) +}) diff --git a/services/slides/node_modules/fastify/test/http-methods/trace.test.js b/services/slides/node_modules/fastify/test/http-methods/trace.test.js new file mode 100644 index 0000000000000000000000000000000000000000..07f869c2675961380d2c5aa99c8946ab6422f18e --- /dev/null +++ b/services/slides/node_modules/fastify/test/http-methods/trace.test.js @@ -0,0 +1,21 @@ +'use strict' + +const { test } = require('node:test') +const fastify = require('../../fastify')() +fastify.addHttpMethod('TRACE') + +test('shorthand - trace', t => { + t.plan(1) + try { + fastify.route({ + method: 'TRACE', + url: '/', + handler: function (request, reply) { + reply.code(200).send('TRACE OK') + } + }) + t.assert.ok(true) + } catch (e) { + t.assert.fail() + } +}) diff --git a/services/slides/node_modules/fastify/test/http-methods/unlock.test.js b/services/slides/node_modules/fastify/test/http-methods/unlock.test.js new file mode 100644 index 0000000000000000000000000000000000000000..7260ba4f07acd827a78384e4858d0dcadc10108f --- /dev/null +++ b/services/slides/node_modules/fastify/test/http-methods/unlock.test.js @@ -0,0 +1,38 @@ +'use strict' + +const { test } = require('node:test') +const fastify = require('../../fastify')() +fastify.addHttpMethod('UNLOCK') + +test('can be created - unlock', t => { + t.plan(1) + try { + fastify.route({ + method: 'UNLOCK', + url: '*', + handler: function (req, reply) { + reply.code(204).send() + } + }) + t.assert.ok(true) + } catch (e) { + t.assert.fail() + } +}) + +test('unlock test', async t => { + const fastifyServer = await fastify.listen({ port: 0 }) + + t.after(() => { fastify.close() }) + await t.test('request - unlock', async t => { + t.plan(2) + const result = await fetch(`${fastifyServer}/test/a.txt`, { + method: 'UNLOCK', + headers: { + 'Lock-Token': 'urn:uuid:a515cfa4-5da4-22e1-f5b5-00a0451e6bf7' + } + }) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 204) + }) +}) diff --git a/services/slides/node_modules/fastify/test/http2/closing.test.js b/services/slides/node_modules/fastify/test/http2/closing.test.js new file mode 100644 index 0000000000000000000000000000000000000000..8253b59fedca38e938ebed64e50f55e083b5789d --- /dev/null +++ b/services/slides/node_modules/fastify/test/http2/closing.test.js @@ -0,0 +1,270 @@ +'use strict' + +const { test } = require('node:test') +const Fastify = require('../..') +const http2 = require('node:http2') +const { promisify } = require('node:util') +const connect = promisify(http2.connect) +const { once } = require('node:events') +const { buildCertificate } = require('../build-certificate') +const { getServerUrl } = require('../helper') +const { kHttp2ServerSessions } = require('../../lib/symbols') + +test.before(buildCertificate) + +const isNode24OrGreater = Number(process.versions.node.split('.')[0]) >= 24 + +test('http/2 request while fastify closing Node <24', { skip: isNode24OrGreater }, (t, done) => { + const fastify = Fastify({ + http2: true + }) + t.assert.ok('http2 successfully loaded') + + fastify.get('/', () => Promise.resolve({})) + + t.after(() => { fastify.close() }) + fastify.listen({ port: 0 }, err => { + t.assert.ifError(err) + + const url = getServerUrl(fastify) + const session = http2.connect(url, function () { + this.request({ + ':method': 'GET', + ':path': '/' + }).on('response', headers => { + t.assert.strictEqual(headers[':status'], 503) + done() + this.destroy() + }).on('error', () => { + // Nothing to do here, + // we are not interested in this error that might + // happen or not + }) + session.on('error', () => { + // Nothing to do here, + // we are not interested in this error that might + // happen or not + done() + }) + fastify.close() + }) + }) +}) + +test('http/2 request while fastify closing Node >=24', { skip: !isNode24OrGreater }, (t, done) => { + const fastify = Fastify({ + http2: true + }) + t.assert.ok('http2 successfully loaded') + + fastify.get('/', () => Promise.resolve({})) + + t.after(() => { fastify.close() }) + fastify.listen({ port: 0 }, err => { + t.assert.ifError(err) + + const url = getServerUrl(fastify) + const session = http2.connect(url, function () { + session.on('error', () => { + // Nothing to do here, + // we are not interested in this error that might + // happen or not + }) + session.on('close', () => { + done() + }) + fastify.close() + }) + }) +}) + +test('http/2 request while fastify closing - return503OnClosing: false', { skip: isNode24OrGreater }, (t, done) => { + const fastify = Fastify({ + http2: true, + return503OnClosing: false + }) + + t.after(() => { fastify.close() }) + + fastify.get('/', () => Promise.resolve({})) + + fastify.listen({ port: 0 }, err => { + t.assert.ifError(err) + const url = getServerUrl(fastify) + const session = http2.connect(url, function () { + this.request({ + ':method': 'GET', + ':path': '/' + }).on('response', headers => { + t.assert.strictEqual(headers[':status'], 200) + done() + this.destroy() + }).on('error', () => { + // Nothing to do here, + // we are not interested in this error that might + // happen or not + }) + fastify.close() + }) + session.on('error', () => { + // Nothing to do here, + // we are not interested in this error that might + // happen or not + done() + }) + }) +}) + +test('http/2 closes successfully with async await', async t => { + const fastify = Fastify({ + http2SessionTimeout: 100, + http2: true + }) + + await fastify.listen({ port: 0 }) + + const url = getServerUrl(fastify) + const session = await connect(url) + // An error might or might not happen, as it's OS dependent. + session.on('error', () => {}) + await fastify.close() +}) + +test('https/2 closes successfully with async await', async t => { + const fastify = Fastify({ + http2SessionTimeout: 100, + http2: true, + https: { + key: global.context.key, + cert: global.context.cert + } + }) + + await fastify.listen({ port: 0 }) + + const url = getServerUrl(fastify) + const session = await connect(url) + // An error might or might not happen, as it's OS dependent. + session.on('error', () => {}) + await fastify.close() +}) + +test('http/2 server side session emits a timeout event', async t => { + let _resolve + const p = new Promise((resolve) => { _resolve = resolve }) + + const fastify = Fastify({ + http2SessionTimeout: 100, + http2: true + }) + + fastify.get('/', async (req) => { + req.raw.stream.session.on('timeout', () => _resolve()) + return {} + }) + + await fastify.listen({ port: 0 }) + + const url = getServerUrl(fastify) + const session = await connect(url) + const req = session.request({ + ':method': 'GET', + ':path': '/' + }).end() + + const [headers] = await once(req, 'response') + t.assert.strictEqual(headers[':status'], 200) + req.resume() + + // An error might or might not happen, as it's OS dependent. + session.on('error', () => {}) + await p + await fastify.close() +}) + +test('http/2 sessions closed after closing server', async t => { + t.plan(1) + const fastify = Fastify({ + http2: true, + http2SessionTimeout: 100 + }) + await fastify.listen() + const url = getServerUrl(fastify) + const waitSessionConnect = once(fastify.server, 'session') + const session = http2.connect(url) + await once(session, 'connect') + await waitSessionConnect + const waitSessionClosed = once(session, 'close') + await fastify.close() + await waitSessionClosed + t.assert.strictEqual(session.closed, true) +}) + +test('http/2 sessions should be closed when setting forceClosedConnections to true', async t => { + t.plan(2) + const fastify = Fastify({ http2: true, http2SessionTimeout: 100, forceCloseConnections: true }) + fastify.get('/', () => 'hello world') + await fastify.listen() + const client = await connect(getServerUrl(fastify)) + const req = client.request({ + [http2.HTTP2_HEADER_PATH]: '/', + [http2.HTTP2_HEADER_METHOD]: 'GET' + }) + await once(req, 'response') + fastify.close() + const r2 = client.request({ + [http2.HTTP2_HEADER_PATH]: '/', + [http2.TTP2_HEADER_METHOD]: 'GET' + }) + r2.on('error', (err) => { + t.assert.strictEqual(err.toString(), 'Error [ERR_HTTP2_STREAM_ERROR]: Stream closed with error code NGHTTP2_REFUSED_STREAM') + }) + await once(r2, 'error') + r2.end() + t.assert.strictEqual(client.closed, true) + client.destroy() +}) + +test('http/2 sessions should be removed from server[kHttp2ServerSessions] Set on goaway', async t => { + t.plan(2) + const fastify = Fastify({ http2: true, http2SessionTimeout: 100, forceCloseConnections: true }) + await fastify.listen() + const waitSession = once(fastify.server, 'session') + const client = http2.connect(getServerUrl(fastify)) + const [session] = await waitSession + const waitGoaway = once(session, 'goaway') + t.assert.strictEqual(fastify.server[kHttp2ServerSessions].size, 1) + client.goaway() + await waitGoaway + t.assert.strictEqual(fastify.server[kHttp2ServerSessions].size, 0) + client.destroy() + await fastify.close() +}) + +test('http/2 sessions should be removed from server[kHttp2ServerSessions] Set on frameError', async t => { + t.plan(2) + const fastify = Fastify({ http2: true, http2SessionTimeout: 100, forceCloseConnections: true }) + await fastify.listen() + const waitSession = once(fastify.server, 'session') + const client = http2.connect(getServerUrl(fastify)) + const [session] = await waitSession + t.assert.strictEqual(fastify.server[kHttp2ServerSessions].size, 1) + session.emit('frameError', 0, 0, 0) + t.assert.strictEqual(fastify.server[kHttp2ServerSessions].size, 0) + client.destroy() + await fastify.close() +}) + +test('http/2 sessions should not be removed from server[kHttp2ServerSessions] from Set if stream id passed on frameError', async t => { + t.plan(2) + const fastify = Fastify({ http2: true, http2SessionTimeout: 100, forceCloseConnections: true }) + await fastify.listen() + const waitSession = once(fastify.server, 'session') + const client = http2.connect(getServerUrl(fastify)) + const [session] = await waitSession + t.assert.strictEqual(fastify.server[kHttp2ServerSessions].size, 1) + session.emit('frameError', 0, 0, 1) + t.assert.strictEqual(fastify.server[kHttp2ServerSessions].size, 1) + client.destroy() + await fastify.close() +}) diff --git a/services/slides/node_modules/fastify/test/http2/constraint.test.js b/services/slides/node_modules/fastify/test/http2/constraint.test.js new file mode 100644 index 0000000000000000000000000000000000000000..06f53b7903d18736ead6b671470353eb244b840b --- /dev/null +++ b/services/slides/node_modules/fastify/test/http2/constraint.test.js @@ -0,0 +1,109 @@ +'use strict' + +const { test } = require('node:test') +const Fastify = require('../..') +const h2url = require('h2url') + +const alpha = { res: 'alpha' } +const beta = { res: 'beta' } + +const { buildCertificate } = require('../build-certificate') +test.before(buildCertificate) + +test('A route supports host constraints under http2 protocol and secure connection', async (t) => { + t.plan(5) + + let fastify + try { + fastify = Fastify({ + http2: true, + https: { + key: global.context.key, + cert: global.context.cert + } + }) + t.assert.ok(true, 'Key/cert successfully loaded') + } catch (e) { + t.assert.fail('Key/cert loading failed') + } + + const constrain = 'fastify.dev' + + fastify.route({ + method: 'GET', + url: '/', + handler: function (_, reply) { + reply.code(200).send(alpha) + } + }) + fastify.route({ + method: 'GET', + url: '/beta', + constraints: { host: constrain }, + handler: function (_, reply) { + reply.code(200).send(beta) + } + }) + fastify.route({ + method: 'GET', + url: '/hostname_port', + constraints: { host: constrain }, + handler: function (req, reply) { + reply.code(200).send({ ...beta, hostname: req.hostname }) + } + }) + t.after(() => { fastify.close() }) + + await fastify.listen({ port: 0 }) + + await t.test('https get request - no constrain', async (t) => { + t.plan(3) + + const url = `https://localhost:${fastify.server.address().port}` + const res = await h2url.concat({ url }) + + t.assert.strictEqual(res.headers[':status'], 200) + t.assert.strictEqual(res.headers['content-length'], '' + JSON.stringify(alpha).length) + t.assert.deepStrictEqual(JSON.parse(res.body), alpha) + }) + + await t.test('https get request - constrain', async (t) => { + t.plan(3) + + const url = `https://localhost:${fastify.server.address().port}/beta` + const res = await h2url.concat({ + url, + headers: { + ':authority': constrain + } + }) + + t.assert.strictEqual(res.headers[':status'], 200) + t.assert.strictEqual(res.headers['content-length'], '' + JSON.stringify(beta).length) + t.assert.deepStrictEqual(JSON.parse(res.body), beta) + }) + + await t.test('https get request - constrain - not found', async (t) => { + t.plan(1) + + const url = `https://localhost:${fastify.server.address().port}/beta` + const res = await h2url.concat({ + url + }) + + t.assert.strictEqual(res.headers[':status'], 404) + }) + await t.test('https get request - constrain - verify hostname and port from request', async (t) => { + t.plan(1) + + const url = `https://localhost:${fastify.server.address().port}/hostname_port` + const res = await h2url.concat({ + url, + headers: { + ':authority': constrain + } + }) + const body = JSON.parse(res.body) + t.assert.strictEqual(body.hostname, constrain) + }) +}) diff --git a/services/slides/node_modules/fastify/test/http2/head.test.js b/services/slides/node_modules/fastify/test/http2/head.test.js new file mode 100644 index 0000000000000000000000000000000000000000..bf5f254cc58c73f5995ebe08b99f56751d95e40b --- /dev/null +++ b/services/slides/node_modules/fastify/test/http2/head.test.js @@ -0,0 +1,34 @@ +'use strict' + +const { test } = require('node:test') +const Fastify = require('../..') +const h2url = require('h2url') +const msg = { hello: 'world' } + +test('http2 HEAD test', async (t) => { + let fastify + try { + fastify = Fastify({ + http2: true + }) + t.assert.ok(true, 'http2 successfully loaded') + } catch (e) { + t.assert.fail('http2 loading failed') + } + + fastify.all('/', function (req, reply) { + reply.code(200).send(msg) + }) + t.after(() => { fastify.close() }) + + await fastify.listen({ port: 0 }) + + await t.test('http HEAD request', async (t) => { + t.plan(1) + + const url = `http://localhost:${fastify.server.address().port}` + const res = await h2url.concat({ url, method: 'HEAD' }) + + t.assert.strictEqual(res.headers[':status'], 200) + }) +}) diff --git a/services/slides/node_modules/fastify/test/http2/plain.test.js b/services/slides/node_modules/fastify/test/http2/plain.test.js new file mode 100644 index 0000000000000000000000000000000000000000..3e1d5b4c96efa024f51e6e5c81f4b153844d0ffb --- /dev/null +++ b/services/slides/node_modules/fastify/test/http2/plain.test.js @@ -0,0 +1,68 @@ +'use strict' + +const { test } = require('node:test') +const Fastify = require('../..') +const h2url = require('h2url') +const msg = { hello: 'world' } + +test('http2 plain test', async t => { + let fastify + try { + fastify = Fastify({ + http2: true + }) + t.assert.ok(true, 'http2 successfully loaded') + } catch (e) { + t.assert.fail('http2 loading failed') + } + + fastify.get('/', function (req, reply) { + reply.code(200).send(msg) + }) + + fastify.get('/host', function (req, reply) { + reply.code(200).send(req.host) + }) + + fastify.get('/hostname_port', function (req, reply) { + reply.code(200).send({ hostname: req.hostname, port: req.port }) + }) + + t.after(() => { fastify.close() }) + + await fastify.listen({ port: 0 }) + + await t.test('http get request', async (t) => { + t.plan(3) + + const url = `http://localhost:${fastify.server.address().port}` + const res = await h2url.concat({ url }) + + t.assert.strictEqual(res.headers[':status'], 200) + t.assert.strictEqual(res.headers['content-length'], '' + JSON.stringify(msg).length) + + t.assert.deepStrictEqual(JSON.parse(res.body), msg) + }) + + await t.test('http host', async (t) => { + t.plan(1) + + const host = `localhost:${fastify.server.address().port}` + + const url = `http://${host}/host` + const res = await h2url.concat({ url }) + + t.assert.strictEqual(res.body, host) + }) + await t.test('http hostname and port', async (t) => { + t.plan(2) + + const host = `localhost:${fastify.server.address().port}` + + const url = `http://${host}/hostname_port` + const res = await h2url.concat({ url }) + + t.assert.strictEqual(JSON.parse(res.body).hostname, host.split(':')[0]) + t.assert.strictEqual(JSON.parse(res.body).port, parseInt(host.split(':')[1])) + }) +}) diff --git a/services/slides/node_modules/fastify/test/http2/secure-with-fallback.test.js b/services/slides/node_modules/fastify/test/http2/secure-with-fallback.test.js new file mode 100644 index 0000000000000000000000000000000000000000..25566389226999e59449f278f429c6e09a4b9882 --- /dev/null +++ b/services/slides/node_modules/fastify/test/http2/secure-with-fallback.test.js @@ -0,0 +1,113 @@ +'use strict' + +const { test } = require('node:test') +const Fastify = require('../..') +const h2url = require('h2url') +const msg = { hello: 'world' } + +const { buildCertificate } = require('../build-certificate') +const { Agent } = require('undici') +test.before(buildCertificate) + +test('secure with fallback', async (t) => { + t.plan(6) + + let fastify + try { + fastify = Fastify({ + http2: true, + https: { + allowHTTP1: true, + key: global.context.key, + cert: global.context.cert + } + }) + t.assert.ok(true, 'Key/cert successfully loaded') + } catch (e) { + t.assert.fail('Key/cert loading failed') + } + + fastify.get('/', function (req, reply) { + reply.code(200).send(msg) + }) + + fastify.post('/', function (req, reply) { + reply.code(200).send(req.body) + }) + + fastify.get('/error', async function (req, reply) { + throw new Error('kaboom') + }) + + t.after(() => fastify.close()) + + const fastifyServer = await fastify.listen({ port: 0 }) + + await t.test('https get error', async (t) => { + t.plan(1) + + const url = `${fastifyServer}/error` + const res = await h2url.concat({ url }) + + t.assert.strictEqual(res.headers[':status'], 500) + }) + + await t.test('https post', async (t) => { + t.plan(2) + + const res = await h2url.concat({ + url: fastifyServer, + method: 'POST', + body: JSON.stringify({ hello: 'http2' }), + headers: { + 'content-type': 'application/json' + } + }) + + t.assert.strictEqual(res.headers[':status'], 200) + t.assert.deepStrictEqual(JSON.parse(res.body), { hello: 'http2' }) + }) + + await t.test('https get request', async (t) => { + t.plan(3) + + const res = await h2url.concat({ url: fastifyServer }) + + t.assert.strictEqual(res.headers[':status'], 200) + t.assert.strictEqual(res.headers['content-length'], '' + JSON.stringify(msg).length) + t.assert.deepStrictEqual(JSON.parse(res.body), msg) + }) + + await t.test('http1 get request', async t => { + t.plan(4) + + const result = await fetch(fastifyServer, { + dispatcher: new Agent({ + connect: { + rejectUnauthorized: false + } + }) + }) + + const body = await result.text() + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), msg) + }) + + await t.test('http1 get error', async t => { + t.plan(2) + + const result = await fetch(`${fastifyServer}/error`, { + dispatcher: new Agent({ + connect: { + rejectUnauthorized: false + } + }) + }) + + t.assert.ok(!result.ok) + t.assert.strictEqual(result.status, 500) + }) +}) diff --git a/services/slides/node_modules/fastify/test/http2/secure.test.js b/services/slides/node_modules/fastify/test/http2/secure.test.js new file mode 100644 index 0000000000000000000000000000000000000000..2a12ce8d0ea7c15f719877ca699f14bafbc2ae08 --- /dev/null +++ b/services/slides/node_modules/fastify/test/http2/secure.test.js @@ -0,0 +1,67 @@ +'use strict' + +const { test } = require('node:test') +const Fastify = require('../..') +const h2url = require('h2url') +const msg = { hello: 'world' } + +const { buildCertificate } = require('../build-certificate') +test.before(buildCertificate) + +test('secure', async (t) => { + t.plan(4) + + let fastify + try { + fastify = Fastify({ + http2: true, + https: { + key: global.context.key, + cert: global.context.cert + } + }) + t.assert.ok(true, 'Key/cert successfully loaded') + } catch (e) { + t.assert.fail('Key/cert loading failed') + } + + fastify.get('/', function (req, reply) { + reply.code(200).send(msg) + }) + fastify.get('/proto', function (req, reply) { + reply.code(200).send({ proto: req.protocol }) + }) + fastify.get('/hostname_port', function (req, reply) { + reply.code(200).send({ hostname: req.hostname, port: req.port }) + }) + + t.after(() => { fastify.close() }) + await fastify.listen({ port: 0 }) + + await t.test('https get request', async (t) => { + t.plan(3) + + const url = `https://localhost:${fastify.server.address().port}` + const res = await h2url.concat({ url }) + + t.assert.strictEqual(res.headers[':status'], 200) + t.assert.strictEqual(res.headers['content-length'], '' + JSON.stringify(msg).length) + t.assert.deepStrictEqual(JSON.parse(res.body), msg) + }) + + await t.test('https get request without trust proxy - protocol', async (t) => { + t.plan(2) + + const url = `https://localhost:${fastify.server.address().port}/proto` + t.assert.deepStrictEqual(JSON.parse((await h2url.concat({ url })).body), { proto: 'https' }) + t.assert.deepStrictEqual(JSON.parse((await h2url.concat({ url, headers: { 'X-Forwarded-Proto': 'lorem' } })).body), { proto: 'https' }) + }) + await t.test('https get request - test hostname and port', async (t) => { + t.plan(2) + + const url = `https://localhost:${fastify.server.address().port}/hostname_port` + const parsedbody = JSON.parse((await h2url.concat({ url })).body) + t.assert.strictEqual(parsedbody.hostname, 'localhost') + t.assert.strictEqual(parsedbody.port, fastify.server.address().port) + }) +}) diff --git a/services/slides/node_modules/fastify/test/http2/unknown-http-method.test.js b/services/slides/node_modules/fastify/test/http2/unknown-http-method.test.js new file mode 100644 index 0000000000000000000000000000000000000000..56941a2d4977259bc1a48c0502e7850a025a8eb9 --- /dev/null +++ b/services/slides/node_modules/fastify/test/http2/unknown-http-method.test.js @@ -0,0 +1,34 @@ +'use strict' + +const { test } = require('node:test') +const Fastify = require('../..') +const h2url = require('h2url') +const msg = { hello: 'world' } + +test('http2 unknown http method', async t => { + const fastify = Fastify({ + http2: true + }) + + fastify.get('/', function (req, reply) { + reply.code(200).send(msg) + }) + + t.after(() => { fastify.close() }) + await fastify.listen({ port: 0 }) + + await t.test('http UNKNOWN_METHOD request', async (t) => { + t.plan(2) + + const url = `http://localhost:${fastify.server.address().port}` + const res = await h2url.concat({ url, method: 'UNKNOWN_METHOD' }) + + t.assert.strictEqual(res.headers[':status'], 404) + t.assert.deepStrictEqual(JSON.parse(res.body), { + statusCode: 404, + code: 'FST_ERR_NOT_FOUND', + error: 'Not Found', + message: 'Not Found' + }) + }) +}) diff --git a/services/slides/node_modules/fastify/test/https/custom-https-server.test.js b/services/slides/node_modules/fastify/test/https/custom-https-server.test.js new file mode 100644 index 0000000000000000000000000000000000000000..ba7a7b65fc336115d20050dabdff42986113431d --- /dev/null +++ b/services/slides/node_modules/fastify/test/https/custom-https-server.test.js @@ -0,0 +1,58 @@ +'use strict' + +const { test } = require('node:test') +const Fastify = require('../..') +const https = require('node:https') +const dns = require('node:dns').promises +const { buildCertificate } = require('../build-certificate') +const { Agent } = require('undici') + +async function setup () { + await buildCertificate() + + const localAddresses = await dns.lookup('localhost', { all: true }) + + test('Should support a custom https server', { skip: localAddresses.length < 1 }, async t => { + t.plan(5) + + const fastify = Fastify({ + serverFactory: (handler, opts) => { + t.assert.ok(opts.serverFactory, 'it is called once for localhost') + + const options = { + key: global.context.key, + cert: global.context.cert + } + + const server = https.createServer(options, (req, res) => { + req.custom = true + handler(req, res) + }) + + return server + } + }) + + t.after(() => { fastify.close() }) + + fastify.get('/', (req, reply) => { + t.assert.ok(req.raw.custom) + reply.send({ hello: 'world' }) + }) + + await fastify.listen({ port: 0 }) + + const result = await fetch('https://localhost:' + fastify.server.address().port, { + dispatcher: new Agent({ + connect: { + rejectUnauthorized: false + } + }) + }) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.deepStrictEqual(await result.json(), { hello: 'world' }) + }) +} + +setup() diff --git a/services/slides/node_modules/fastify/test/https/https.test.js b/services/slides/node_modules/fastify/test/https/https.test.js new file mode 100644 index 0000000000000000000000000000000000000000..7e0a9f3d5d191d9fac74a8116fbe108806d94285 --- /dev/null +++ b/services/slides/node_modules/fastify/test/https/https.test.js @@ -0,0 +1,136 @@ +'use strict' + +const { test } = require('node:test') +const { request } = require('undici') +const Fastify = require('../..') + +const { buildCertificate } = require('../build-certificate') +const { Agent } = require('undici') +test.before(buildCertificate) + +test('https', async (t) => { + t.plan(3) + + let fastify + try { + fastify = Fastify({ + https: { + key: global.context.key, + cert: global.context.cert + } + }) + t.assert.ok('Key/cert successfully loaded') + } catch (e) { + t.assert.fail('Key/cert loading failed') + } + + fastify.get('/', function (req, reply) { + reply.code(200).send({ hello: 'world' }) + }) + + fastify.get('/proto', function (req, reply) { + reply.code(200).send({ proto: req.protocol }) + }) + + await fastify.listen({ port: 0 }) + + t.after(() => { fastify.close() }) + + await t.test('https get request', async t => { + t.plan(4) + const result = await fetch('https://localhost:' + fastify.server.address().port, { + dispatcher: new Agent({ + connect: { + rejectUnauthorized: false + } + }) + }) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + }) + + await t.test('https get request without trust proxy - protocol', async t => { + t.plan(3) + const result1 = await fetch(`${'https://localhost:' + fastify.server.address().port}/proto`, { + dispatcher: new Agent({ + connect: { + rejectUnauthorized: false + } + }) + }) + t.assert.ok(result1.ok) + t.assert.deepStrictEqual(await result1.json(), { proto: 'https' }) + + const result2 = await fetch(`${'https://localhost:' + fastify.server.address().port}/proto`, { + dispatcher: new Agent({ + connect: { + rejectUnauthorized: false + } + }), + headers: { + 'x-forwarded-proto': 'lorem' + } + }) + t.assert.deepStrictEqual(await result2.json(), { proto: 'https' }) + }) +}) + +test('https - headers', async (t) => { + t.plan(3) + let fastify + try { + fastify = Fastify({ + https: { + key: global.context.key, + cert: global.context.cert + } + }) + t.assert.ok('Key/cert successfully loaded') + } catch (e) { + t.assert.fail('Key/cert loading failed') + } + + fastify.get('/', function (req, reply) { + reply.code(200).send({ hello: 'world', hostname: req.hostname, port: req.port }) + }) + + t.after(async () => { await fastify.close() }) + + await fastify.listen({ port: 0 }) + + await t.test('https get request', async t => { + t.plan(3) + const result = await fetch('https://localhost:' + fastify.server.address().port, { + dispatcher: new Agent({ + connect: { + rejectUnauthorized: false + } + }) + }) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.deepStrictEqual(await result.json(), { hostname: 'localhost', port: fastify.server.address().port, hello: 'world' }) + }) + + await t.test('https get request - test port fall back', async t => { + t.plan(2) + + const result = await request('https://localhost:' + fastify.server.address().port, { + method: 'GET', + headers: { + host: 'fastify.test' + }, + dispatcher: new Agent({ + connect: { + rejectUnauthorized: false + } + }) + }) + + t.assert.strictEqual(result.statusCode, 200) + t.assert.deepStrictEqual(await result.body.json(), { hello: 'world', hostname: 'fastify.test', port: null }) + }) +}) diff --git a/services/slides/node_modules/fastify/test/imports.test.js b/services/slides/node_modules/fastify/test/imports.test.js new file mode 100644 index 0000000000000000000000000000000000000000..62f6d9a752c5ab75341e6a9e9cd75e58fc089d9e --- /dev/null +++ b/services/slides/node_modules/fastify/test/imports.test.js @@ -0,0 +1,17 @@ +'use strict' + +const { test } = require('node:test') + +test('should import as default', t => { + t.plan(2) + const fastify = require('..') + t.assert.ok(fastify) + t.assert.strictEqual(typeof fastify, 'function') +}) + +test('should import as esm', t => { + t.plan(2) + const { fastify } = require('..') + t.assert.ok(fastify) + t.assert.strictEqual(typeof fastify, 'function') +}) diff --git a/services/slides/node_modules/fastify/test/inject.test.js b/services/slides/node_modules/fastify/test/inject.test.js new file mode 100644 index 0000000000000000000000000000000000000000..9cf3802d2a515c5e99e4671aadfbed513ba9a435 --- /dev/null +++ b/services/slides/node_modules/fastify/test/inject.test.js @@ -0,0 +1,502 @@ +'use strict' + +const { test } = require('node:test') +const Stream = require('node:stream') +const util = require('node:util') +const Fastify = require('..') +const { Readable } = require('node:stream') + +test('inject should exist', t => { + t.plan(2) + const fastify = Fastify() + t.assert.ok(fastify.inject) + t.assert.strictEqual(typeof fastify.inject, 'function') +}) + +test('should wait for the ready event', (t, done) => { + t.plan(4) + const fastify = Fastify() + const payload = { hello: 'world' } + + fastify.register((instance, opts, done) => { + instance.get('/', (req, reply) => { + reply.send(payload) + }) + setTimeout(done, 500) + }) + + fastify.inject({ + method: 'GET', + url: '/' + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(payload, JSON.parse(res.payload)) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['content-length'], '17') + done() + }) +}) + +test('inject get request', (t, done) => { + t.plan(4) + const fastify = Fastify() + const payload = { hello: 'world' } + + fastify.get('/', (req, reply) => { + reply.send(payload) + }) + + fastify.inject({ + method: 'GET', + url: '/' + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(payload, JSON.parse(res.payload)) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['content-length'], '17') + done() + }) +}) + +test('inject get request - code check', (t, done) => { + t.plan(4) + const fastify = Fastify() + const payload = { hello: 'world' } + + fastify.get('/', (req, reply) => { + reply.code(201).send(payload) + }) + + fastify.inject({ + method: 'GET', + url: '/' + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(payload, JSON.parse(res.payload)) + t.assert.strictEqual(res.statusCode, 201) + t.assert.strictEqual(res.headers['content-length'], '17') + done() + }) +}) + +test('inject get request - headers check', (t, done) => { + t.plan(4) + const fastify = Fastify() + + fastify.get('/', (req, reply) => { + reply.header('content-type', 'text/plain').send('') + }) + + fastify.inject({ + method: 'GET', + url: '/' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual('', res.payload) + t.assert.strictEqual(res.headers['content-type'], 'text/plain') + t.assert.strictEqual(res.headers['content-length'], '0') + done() + }) +}) + +test('inject get request - querystring', (t, done) => { + t.plan(4) + const fastify = Fastify() + + fastify.get('/', (req, reply) => { + reply.send(req.query) + }) + + fastify.inject({ + method: 'GET', + url: '/?hello=world' + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual({ hello: 'world' }, JSON.parse(res.payload)) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['content-length'], '17') + done() + }) +}) + +test('inject get request - params', (t, done) => { + t.plan(4) + const fastify = Fastify() + + fastify.get('/:hello', (req, reply) => { + reply.send(req.params) + }) + + fastify.inject({ + method: 'GET', + url: '/world' + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual({ hello: 'world' }, JSON.parse(res.payload)) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['content-length'], '17') + done() + }) +}) + +test('inject get request - wildcard', (t, done) => { + t.plan(4) + const fastify = Fastify() + + fastify.get('/test/*', (req, reply) => { + reply.send(req.params) + }) + + fastify.inject({ + method: 'GET', + url: '/test/wildcard' + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual({ '*': 'wildcard' }, JSON.parse(res.payload)) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['content-length'], '16') + done() + }) +}) + +test('inject get request - headers', (t, done) => { + t.plan(4) + const fastify = Fastify() + + fastify.get('/', (req, reply) => { + reply.send(req.headers) + }) + + fastify.inject({ + method: 'GET', + url: '/', + headers: { hello: 'world' } + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual('world', JSON.parse(res.payload).hello) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['content-length'], '69') + done() + }) +}) + +test('inject post request', (t, done) => { + t.plan(4) + const fastify = Fastify() + const payload = { hello: 'world' } + + fastify.post('/', (req, reply) => { + reply.send(req.body) + }) + + fastify.inject({ + method: 'POST', + url: '/', + payload + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(payload, JSON.parse(res.payload)) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['content-length'], '17') + done() + }) +}) + +test('inject post request - send stream', (t, done) => { + t.plan(4) + const fastify = Fastify() + + fastify.post('/', (req, reply) => { + reply.send(req.body) + }) + + fastify.inject({ + method: 'POST', + url: '/', + headers: { 'content-type': 'application/json' }, + payload: getStream() + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual('{"hello":"world"}', res.payload) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['content-length'], '17') + done() + }) +}) + +test('inject get request - reply stream', (t, done) => { + t.plan(3) + const fastify = Fastify() + + fastify.get('/', (req, reply) => { + reply.send(getStream()) + }) + + fastify.inject({ + method: 'GET', + url: '/' + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual('{"hello":"world"}', res.payload) + t.assert.strictEqual(res.statusCode, 200) + done() + }) +}) + +test('inject promisify - waiting for ready event', (t, done) => { + t.plan(1) + const fastify = Fastify() + const payload = { hello: 'world' } + + fastify.get('/', (req, reply) => { + reply.send(payload) + }) + + const injectParams = { + method: 'GET', + url: '/' + } + fastify.inject(injectParams) + .then(res => { + t.assert.strictEqual(res.statusCode, 200) + done() + }) + .catch(t.assert.fail) +}) + +test('inject promisify - after the ready event', (t, done) => { + t.plan(2) + const fastify = Fastify() + const payload = { hello: 'world' } + + fastify.get('/', (req, reply) => { + reply.send(payload) + }) + + fastify.ready(err => { + t.assert.ifError(err) + + const injectParams = { + method: 'GET', + url: '/' + } + fastify.inject(injectParams) + .then(res => { + t.assert.strictEqual(res.statusCode, 200) + done() + }) + .catch(t.assert.fail) + }) +}) + +test('inject promisify - when the server is up', (t, done) => { + t.plan(2) + const fastify = Fastify() + const payload = { hello: 'world' } + + fastify.get('/', (req, reply) => { + reply.send(payload) + }) + + fastify.ready(err => { + t.assert.ifError(err) + + // setTimeout because the ready event don't set "started" flag + // in this iteration of the 'event loop' + setTimeout(() => { + const injectParams = { + method: 'GET', + url: '/' + } + fastify.inject(injectParams) + .then(res => { + t.assert.strictEqual(res.statusCode, 200) + done() + }) + .catch(t.assert.fail) + }, 10) + }) +}) + +test('should reject in error case', (t, done) => { + t.plan(1) + const fastify = Fastify() + + const error = new Error('DOOM!') + fastify.register((instance, opts, done) => { + setTimeout(done, 500, error) + }) + + fastify.inject({ + method: 'GET', + url: '/' + }) + .catch(e => { + t.assert.strictEqual(e, error) + done() + }) +}) + +test('inject a multipart request using form-body', (t, done) => { + t.plan(2) + const fastify = Fastify() + + fastify.addContentTypeParser('*', function (req, payload, done) { + let body = '' + payload.on('data', d => { + body += d + }) + payload.on('end', () => { + done(null, body) + }) + }) + fastify.post('/', (req, reply) => { + reply.send(req.body) + }) + + const form = new FormData() + form.set('my_field', 'my value') + + fastify.inject({ + method: 'POST', + url: '/', + payload: form + }) + .then(response => { + t.assert.strictEqual(response.statusCode, 200) + t.assert.ok(/Content-Disposition: form-data; name="my_field"/.test(response.payload)) + done() + }) +}) + +// https://github.com/hapijs/shot/blob/master/test/index.js#L836 +function getStream () { + const Read = function () { + Stream.Readable.call(this) + } + util.inherits(Read, Stream.Readable) + const word = '{"hello":"world"}' + let i = 0 + + Read.prototype._read = function (size) { + this.push(word[i] ? word[i++] : null) + } + + return new Read() +} + +test('should error the promise if ready errors', (t, done) => { + t.plan(3) + const fastify = Fastify() + + fastify.register((instance, opts) => { + return Promise.reject(new Error('kaboom')) + }).after(function () { + t.assert.ok('after is called') + }) + + fastify.inject({ + method: 'GET', + url: '/' + }).then(() => { + t.assert.fail('this should not be called') + }).catch(err => { + t.assert.ok(err) + t.assert.strictEqual(err.message, 'kaboom') + done() + }) +}) + +test('should throw error if callback specified and if ready errors', (t, done) => { + t.plan(2) + const fastify = Fastify() + const error = new Error('kaboom') + + fastify.register((instance, opts) => { + return Promise.reject(error) + }) + + fastify.inject({ + method: 'GET', + url: '/' + }, err => { + t.assert.ok(err) + t.assert.strictEqual(err, error) + done() + }) +}) + +test('should support builder-style injection with ready app', async (t) => { + t.plan(3) + const fastify = Fastify() + const payload = { hello: 'world' } + + fastify.get('/', (req, reply) => { + reply.send(payload) + }) + + await fastify.ready() + const res = await fastify.inject().get('/').end() + t.assert.deepStrictEqual(payload, JSON.parse(res.payload)) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['content-length'], '17') +}) + +test('should support builder-style injection with non-ready app', async (t) => { + t.plan(3) + const fastify = Fastify() + const payload = { hello: 'world' } + + fastify.get('/', (req, reply) => { + reply.send(payload) + }) + + const res = await fastify.inject().get('/').end() + t.assert.deepStrictEqual(payload, JSON.parse(res.payload)) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['content-length'], '17') +}) + +test('should handle errors in builder-style injection correctly', async (t) => { + t.plan(2) + const fastify = Fastify() + fastify.register((instance, opts, done) => { + done(new Error('Kaboom')) + }) + + try { + await fastify.inject().get('/') + } catch (err) { + t.assert.ok(err) + t.assert.strictEqual(err.message, 'Kaboom') + } +}) + +test('Should not throw on access to routeConfig frameworkErrors handler - FST_ERR_BAD_URL', (t, done) => { + t.plan(5) + + const fastify = Fastify({ + frameworkErrors: function (err, req, res) { + t.assert.ok(typeof req.id === 'string') + t.assert.ok(req.raw instanceof Readable) + t.assert.deepStrictEqual(req.routeOptions.url, undefined) + res.send(`${err.message} - ${err.code}`) + } + }) + + fastify.get('/test/:id', (req, res) => { + res.send('{ hello: \'world\' }') + }) + + fastify.inject( + { + method: 'GET', + url: '/test/%world' + }, + (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.body, '\'/test/%world\' is not a valid url component - FST_ERR_BAD_URL') + done() + } + ) +}) diff --git a/services/slides/node_modules/fastify/test/input-validation.js b/services/slides/node_modules/fastify/test/input-validation.js new file mode 100644 index 0000000000000000000000000000000000000000..52ae26784bb64e7b9172a3ff750bd02abee1232f --- /dev/null +++ b/services/slides/node_modules/fastify/test/input-validation.js @@ -0,0 +1,335 @@ +'use strict' + +const Ajv = require('ajv') +const Joi = require('joi') +const yup = require('yup') +const assert = require('node:assert') + +module.exports.payloadMethod = function (method, t) { + const test = t.test + const fastify = require('..')() + const upMethod = method.toUpperCase() + const loMethod = method.toLowerCase() + + const opts = { + schema: { + body: { + type: 'object', + properties: { + hello: { + type: 'integer' + } + } + } + } + } + + const ajv = new Ajv({ coerceTypes: true, removeAdditional: true }) + const optsWithCustomValidator = { + schema: { + body: { + type: 'object', + properties: { + hello: { + type: 'integer' + } + }, + additionalProperties: false + } + }, + validatorCompiler: function ({ schema, method, url, httpPart }) { + return ajv.compile(schema) + } + } + + const optsWithJoiValidator = { + schema: { + body: Joi.object().keys({ + hello: Joi.string().required() + }).required() + }, + validatorCompiler: function ({ schema, method, url, httpPart }) { + return schema.validate.bind(schema) + } + } + + const yupOptions = { + strict: true, // don't coerce + abortEarly: false, // return all errors + stripUnknown: true, // remove additional properties + recursive: true + } + + const optsWithYupValidator = { + schema: { + body: yup.object().shape({ + hello: yup.string().required() + }).required() + }, + validatorCompiler: function ({ schema, method, url, httpPart }) { + return data => { + try { + const result = schema.validateSync(data, yupOptions) + return { value: result } + } catch (e) { + return { error: [e] } + } + } + } + } + + test(`${upMethod} can be created`, t => { + t.plan(1) + try { + fastify[loMethod]('/', opts, function (req, reply) { + reply.send(req.body) + }) + fastify[loMethod]('/custom', optsWithCustomValidator, function (req, reply) { + reply.send(req.body) + }) + fastify[loMethod]('/joi', optsWithJoiValidator, function (req, reply) { + reply.send(req.body) + }) + fastify[loMethod]('/yup', optsWithYupValidator, function (req, reply) { + reply.send(req.body) + }) + + fastify.register(function (fastify2, opts, done) { + fastify2.setValidatorCompiler(function schema ({ schema, method, url, httpPart }) { + return body => ({ error: new Error('From custom schema compiler!') }) + }) + const withInstanceCustomCompiler = { + schema: { + body: { + type: 'object', + properties: { }, + additionalProperties: false + } + } + } + fastify2[loMethod]('/plugin', withInstanceCustomCompiler, (req, reply) => reply.send({ hello: 'never here!' })) + + const optsWithCustomValidator2 = { + schema: { + body: { + type: 'object', + properties: { }, + additionalProperties: false + } + }, + validatorCompiler: function ({ schema, method, url, httpPart }) { + return function (body) { + return { error: new Error('Always fail!') } + } + } + } + fastify2[loMethod]('/plugin/custom', optsWithCustomValidator2, (req, reply) => reply.send({ hello: 'never here!' })) + + done() + }) + t.assert.ok(true) + } catch (e) { + t.assert.fail() + } + }) + + fastify.listen({ port: 0 }, function (err) { + assert.ifError(err) + + t.after(() => { fastify.close() }) + + test(`${upMethod} - correctly replies`, async (t) => { + if (upMethod === 'HEAD') { + t.plan(2) + const result = await fetch('http://localhost:' + fastify.server.address().port, { + method: upMethod + }) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + } else { + t.plan(3) + + const result = await fetch('http://localhost:' + fastify.server.address().port, { + method: upMethod, + body: JSON.stringify({ hello: 42 }), + headers: { + 'Content-Type': 'application/json' + } + }) + + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.deepStrictEqual(await result.json(), { hello: 42 }) + } + }) + + test(`${upMethod} - 400 on bad parameters`, async (t) => { + t.plan(3) + + const result = await fetch('http://localhost:' + fastify.server.address().port, { + method: upMethod, + body: JSON.stringify({ hello: 'world' }), + headers: { + 'Content-Type': 'application/json' + } + }) + + t.assert.ok(!result.ok) + t.assert.strictEqual(result.status, 400) + t.assert.deepStrictEqual(await result.json(), { + error: 'Bad Request', + message: 'body/hello must be integer', + statusCode: 400, + code: 'FST_ERR_VALIDATION' + }) + }) + + test(`${upMethod} - input-validation coerce`, async (t) => { + t.plan(3) + + const restult = await fetch('http://localhost:' + fastify.server.address().port, { + method: upMethod, + body: JSON.stringify({ hello: '42' }), + headers: { + 'Content-Type': 'application/json' + } + }) + + t.assert.ok(restult.ok) + t.assert.strictEqual(restult.status, 200) + t.assert.deepStrictEqual(await restult.json(), { hello: 42 }) + }) + + test(`${upMethod} - input-validation custom schema compiler`, async (t) => { + t.plan(3) + + const result = await fetch('http://localhost:' + fastify.server.address().port + '/custom', { + method: upMethod, + body: JSON.stringify({ hello: '42', world: 55 }), + headers: { + 'Content-Type': 'application/json' + } + }) + + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.deepStrictEqual(await result.json(), { hello: 42 }) + }) + + test(`${upMethod} - input-validation joi schema compiler ok`, async (t) => { + t.plan(3) + + const result = await fetch('http://localhost:' + fastify.server.address().port + '/joi', { + method: upMethod, + body: JSON.stringify({ hello: '42' }), + headers: { + 'Content-Type': 'application/json' + } + }) + + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.deepStrictEqual(await result.json(), { hello: '42' }) + }) + + test(`${upMethod} - input-validation joi schema compiler ko`, async (t) => { + t.plan(3) + + const result = await fetch('http://localhost:' + fastify.server.address().port + '/joi', { + method: upMethod, + body: JSON.stringify({ hello: 44 }), + headers: { + 'Content-Type': 'application/json' + } + }) + + t.assert.ok(!result.ok) + t.assert.strictEqual(result.status, 400) + t.assert.deepStrictEqual(await result.json(), { + error: 'Bad Request', + message: '"hello" must be a string', + statusCode: 400, + code: 'FST_ERR_VALIDATION' + }) + }) + + test(`${upMethod} - input-validation yup schema compiler ok`, async (t) => { + t.plan(3) + + const result = await fetch('http://localhost:' + fastify.server.address().port + '/yup', { + method: upMethod, + body: JSON.stringify({ hello: '42' }), + headers: { + 'Content-Type': 'application/json' + } + }) + + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.deepStrictEqual(await result.json(), { hello: '42' }) + }) + + test(`${upMethod} - input-validation yup schema compiler ko`, async (t) => { + t.plan(3) + + const result = await fetch('http://localhost:' + fastify.server.address().port + '/yup', { + method: upMethod, + body: JSON.stringify({ hello: 44 }), + headers: { + 'Content-Type': 'application/json' + } + }) + + t.assert.ok(!result.ok) + t.assert.strictEqual(result.status, 400) + t.assert.deepStrictEqual(await result.json(), { + error: 'Bad Request', + message: 'body hello must be a `string` type, but the final value was: `44`.', + statusCode: 400, + code: 'FST_ERR_VALIDATION' + }) + }) + + test(`${upMethod} - input-validation instance custom schema compiler encapsulated`, async (t) => { + t.plan(3) + + const result = await fetch('http://localhost:' + fastify.server.address().port + '/plugin', { + method: upMethod, + body: JSON.stringify({}), + headers: { + 'Content-Type': 'application/json' + } + }) + + t.assert.ok(!result.ok) + t.assert.strictEqual(result.status, 400) + t.assert.deepStrictEqual(await result.json(), { + error: 'Bad Request', + message: 'From custom schema compiler!', + statusCode: 400, + code: 'FST_ERR_VALIDATION' + }) + }) + + test(`${upMethod} - input-validation custom schema compiler encapsulated`, async (t) => { + t.plan(3) + + const result = await fetch('http://localhost:' + fastify.server.address().port + '/plugin/custom', { + method: upMethod, + body: JSON.stringify({}), + headers: { + 'Content-Type': 'application/json' + } + }) + + t.assert.ok(!result.ok) + t.assert.strictEqual(result.status, 400) + t.assert.deepStrictEqual(await result.json(), { + error: 'Bad Request', + message: 'Always fail!', + statusCode: 400, + code: 'FST_ERR_VALIDATION' + }) + }) + }) +} diff --git a/services/slides/node_modules/fastify/test/internals/all.test.js b/services/slides/node_modules/fastify/test/internals/all.test.js new file mode 100644 index 0000000000000000000000000000000000000000..14d3370145a5755adc1ad53d40dee92ad3954382 --- /dev/null +++ b/services/slides/node_modules/fastify/test/internals/all.test.js @@ -0,0 +1,38 @@ +'use strict' + +const { test } = require('node:test') +const Fastify = require('../..') + +test('fastify.all should add all the methods to the same url', async t => { + const fastify = Fastify() + + const requirePayload = [ + 'POST', + 'PUT', + 'PATCH' + ] + + const supportedMethods = fastify.supportedMethods + t.plan(supportedMethods.length) + + fastify.all('/', (req, reply) => { + reply.send({ method: req.raw.method }) + }) + + await Promise.all(supportedMethods.map(async method => injectRequest(method))) + + async function injectRequest (method) { + const options = { + url: '/', + method + } + + if (requirePayload.includes(method)) { + options.payload = { hello: 'world' } + } + + const res = await fastify.inject(options) + const payload = JSON.parse(res.payload) + t.assert.deepStrictEqual(payload, { method }) + } +}) diff --git a/services/slides/node_modules/fastify/test/internals/content-type-parser.test.js b/services/slides/node_modules/fastify/test/internals/content-type-parser.test.js new file mode 100644 index 0000000000000000000000000000000000000000..aadff2dedc1e372c1266785b9031896e20101767 --- /dev/null +++ b/services/slides/node_modules/fastify/test/internals/content-type-parser.test.js @@ -0,0 +1,111 @@ +'use strict' + +const { test } = require('node:test') +const proxyquire = require('proxyquire') +const { Readable } = require('node:stream') +const { kTestInternals, kRouteContext } = require('../../lib/symbols') +const Request = require('../../lib/request') +const Reply = require('../../lib/reply') + +test('rawBody function', t => { + t.plan(2) + + const internals = require('../../lib/content-type-parser')[kTestInternals] + const body = Buffer.from('你好 世界') + const parser = { + asString: true, + asBuffer: false, + fn (req, bodyInString, done) { + t.assert.strictEqual(bodyInString, body.toString()) + t.assert.strictEqual(typeof done, 'function') + return { + then (cb) { + cb() + } + } + } + } + const res = {} + res.end = () => { } + res.writeHead = () => { } + + res.log = { error: () => { }, info: () => { } } + const context = { + Reply, + Request, + preHandler: [], + onSend: [], + _parserOptions: { + limit: 1024 + } + } + const rs = new Readable() + rs._read = function () { } + rs.headers = { 'content-length': body.length } + const request = new Request('id', 'params', rs, 'query', 'log', context) + const reply = new Reply(res, request) + const done = () => { } + + internals.rawBody( + request, + reply, + reply[kRouteContext]._parserOptions, + parser, + done + ) + rs.emit('data', body.toString()) + rs.emit('end') +}) + +test('Should support Webpack and faux modules', t => { + t.plan(2) + + const internals = proxyquire('../../lib/content-type-parser', { + 'toad-cache': { default: () => { } } + })[kTestInternals] + + const body = Buffer.from('你好 世界') + const parser = { + asString: true, + asBuffer: false, + fn (req, bodyInString, done) { + t.assert.strictEqual(bodyInString, body.toString()) + t.assert.strictEqual(typeof done, 'function') + return { + then (cb) { + cb() + } + } + } + } + const res = {} + res.end = () => { } + res.writeHead = () => { } + + res.log = { error: () => { }, info: () => { } } + const context = { + Reply, + Request, + preHandler: [], + onSend: [], + _parserOptions: { + limit: 1024 + } + } + const rs = new Readable() + rs._read = function () { } + rs.headers = { 'content-length': body.length } + const request = new Request('id', 'params', rs, 'query', 'log', context) + const reply = new Reply(res, request) + const done = () => { } + + internals.rawBody( + request, + reply, + reply[kRouteContext]._parserOptions, + parser, + done + ) + rs.emit('data', body.toString()) + rs.emit('end') +}) diff --git a/services/slides/node_modules/fastify/test/internals/context.test.js b/services/slides/node_modules/fastify/test/internals/context.test.js new file mode 100644 index 0000000000000000000000000000000000000000..1bab2bf376223bbe0df8c207f85f6b2efd67aa18 --- /dev/null +++ b/services/slides/node_modules/fastify/test/internals/context.test.js @@ -0,0 +1,31 @@ +'use strict' + +const { test } = require('node:test') +const { kRouteContext } = require('../../lib/symbols') +const Context = require('../../lib/context') + +const Fastify = require('../..') + +test('context', async context => { + context.plan(1) + + await context.test('Should not contain undefined as key prop', async t => { + t.plan(4) + const app = Fastify() + + app.get('/', (req, reply) => { + t.assert.ok(req[kRouteContext] instanceof Context) + t.assert.ok(reply[kRouteContext] instanceof Context) + t.assert.ok(!('undefined' in reply[kRouteContext])) + t.assert.ok(!('undefined' in req[kRouteContext])) + + reply.send('hello world!') + }) + + try { + await app.inject('/') + } catch (e) { + t.assert.fail(e) + } + }) +}) diff --git a/services/slides/node_modules/fastify/test/internals/decorator.test.js b/services/slides/node_modules/fastify/test/internals/decorator.test.js new file mode 100644 index 0000000000000000000000000000000000000000..c83f2c926aec905c525c06a0fd78e04193a4555f --- /dev/null +++ b/services/slides/node_modules/fastify/test/internals/decorator.test.js @@ -0,0 +1,156 @@ +'use strict' + +const { test } = require('node:test') +const decorator = require('../../lib/decorate') +const { + kState +} = require('../../lib/symbols') + +test('decorate should add the given method to its instance', t => { + t.plan(1) + function build () { + server.add = decorator.add + server[kState] = { + listening: false, + closing: false, + started: false + } + return server + function server () {} + } + + const server = build() + server.add('test', () => {}) + t.assert.ok(server.test) +}) + +test('decorate is chainable', t => { + t.plan(3) + function build () { + server.add = decorator.add + server[kState] = { + listening: false, + closing: false, + started: false + } + return server + function server () {} + } + + const server = build() + server + .add('test1', () => {}) + .add('test2', () => {}) + .add('test3', () => {}) + + t.assert.ok(server.test1) + t.assert.ok(server.test2) + t.assert.ok(server.test3) +}) + +test('checkExistence should check if a property is part of the given instance', t => { + t.plan(1) + const instance = { test: () => {} } + t.assert.ok(decorator.exist(instance, 'test')) +}) + +test('checkExistence should find the instance if not given', t => { + t.plan(1) + function build () { + server.add = decorator.add + server.check = decorator.exist + server[kState] = { + listening: false, + closing: false, + started: false + } + return server + function server () {} + } + + const server = build() + server.add('test', () => {}) + t.assert.ok(server.check('test')) +}) + +test('checkExistence should check the prototype as well', t => { + t.plan(1) + function Instance () {} + Instance.prototype.test = () => {} + + const instance = new Instance() + t.assert.ok(decorator.exist(instance, 'test')) +}) + +test('checkDependencies should throw if a dependency is not present', t => { + t.plan(2) + const instance = {} + try { + decorator.dependencies(instance, 'foo', ['test']) + t.assert.fail() + } catch (e) { + t.assert.strictEqual(e.code, 'FST_ERR_DEC_MISSING_DEPENDENCY') + t.assert.strictEqual(e.message, 'The decorator is missing dependency \'test\'.') + } +}) + +test('decorate should internally call checkDependencies', t => { + t.plan(2) + function build () { + server.add = decorator.add + server[kState] = { + listening: false, + closing: false, + started: false + } + return server + function server () {} + } + + const server = build() + + try { + server.add('method', () => {}, ['test']) + t.assert.fail() + } catch (e) { + t.assert.strictEqual(e.code, 'FST_ERR_DEC_MISSING_DEPENDENCY') + t.assert.strictEqual(e.message, 'The decorator is missing dependency \'test\'.') + } +}) + +test('decorate should recognize getter/setter objects', t => { + t.plan(6) + + const one = { + [kState]: { + listening: false, + closing: false, + started: false + } + } + decorator.add.call(one, 'foo', { + getter: () => this._a, + setter: (val) => { + t.assert.ok(true) + this._a = val + } + }) + t.assert.strictEqual(Object.hasOwn(one, 'foo'), true) + t.assert.strictEqual(one.foo, undefined) + one.foo = 'a' + t.assert.strictEqual(one.foo, 'a') + + // getter only + const two = { + [kState]: { + listening: false, + closing: false, + started: false + } + } + decorator.add.call(two, 'foo', { + getter: () => 'a getter' + }) + t.assert.strictEqual(Object.hasOwn(two, 'foo'), true) + t.assert.strictEqual(two.foo, 'a getter') +}) diff --git a/services/slides/node_modules/fastify/test/internals/errors.test.js b/services/slides/node_modules/fastify/test/internals/errors.test.js new file mode 100644 index 0000000000000000000000000000000000000000..e132f385cdf8ef9482a430e982d4a054be6dd4d5 --- /dev/null +++ b/services/slides/node_modules/fastify/test/internals/errors.test.js @@ -0,0 +1,982 @@ +'use strict' + +const { test } = require('node:test') +const errors = require('../../lib/errors') +const { readFileSync } = require('node:fs') +const { resolve } = require('node:path') + +const expectedErrors = 88 + +test(`should expose ${expectedErrors} errors`, t => { + t.plan(1) + const exportedKeys = Object.keys(errors) + let counter = 0 + for (const key of exportedKeys) { + if (errors[key].name === 'FastifyError') { + counter++ + } + } + t.assert.strictEqual(counter, expectedErrors) +}) + +test('ensure name and codes of Errors are identical', t => { + t.plan(expectedErrors) + + const exportedKeys = Object.keys(errors) + for (const key of exportedKeys) { + if (errors[key].name === 'FastifyError') { + t.assert.strictEqual(key, new errors[key]().code, key) + } + } +}) + +test('FST_ERR_NOT_FOUND', t => { + t.plan(5) + const error = new errors.FST_ERR_NOT_FOUND() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_NOT_FOUND') + t.assert.strictEqual(error.message, 'Not Found') + t.assert.strictEqual(error.statusCode, 404) + t.assert.ok(error instanceof Error) +}) + +test('FST_ERR_OPTIONS_NOT_OBJ', t => { + t.plan(5) + const error = new errors.FST_ERR_OPTIONS_NOT_OBJ() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_OPTIONS_NOT_OBJ') + t.assert.strictEqual(error.message, 'Options must be an object') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof TypeError) +}) + +test('FST_ERR_QSP_NOT_FN', t => { + t.plan(5) + const error = new errors.FST_ERR_QSP_NOT_FN() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_QSP_NOT_FN') + t.assert.strictEqual(error.message, "querystringParser option should be a function, instead got '%s'") + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof TypeError) +}) + +test('FST_ERR_SCHEMA_CONTROLLER_BUCKET_OPT_NOT_FN', t => { + t.plan(5) + const error = new errors.FST_ERR_SCHEMA_CONTROLLER_BUCKET_OPT_NOT_FN() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_SCHEMA_CONTROLLER_BUCKET_OPT_NOT_FN') + t.assert.strictEqual(error.message, "schemaController.bucket option should be a function, instead got '%s'") + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof TypeError) +}) + +test('FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN', t => { + t.plan(5) + const error = new errors.FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN') + t.assert.strictEqual(error.message, "schemaErrorFormatter option should be a non async function. Instead got '%s'.") + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof TypeError) +}) + +test('FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_OBJ', t => { + t.plan(5) + const error = new errors.FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_OBJ() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_OBJ') + t.assert.strictEqual(error.message, "ajv.customOptions option should be an object, instead got '%s'") + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof TypeError) +}) + +test('FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_ARR', t => { + t.plan(5) + const error = new errors.FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_ARR() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_ARR') + t.assert.strictEqual(error.message, "ajv.plugins option should be an array, instead got '%s'") + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof TypeError) +}) + +test('FST_ERR_CTP_ALREADY_PRESENT', t => { + t.plan(5) + const error = new errors.FST_ERR_CTP_ALREADY_PRESENT() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_CTP_ALREADY_PRESENT') + t.assert.strictEqual(error.message, "Content type parser '%s' already present.") + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) +}) + +test('FST_ERR_CTP_INVALID_TYPE', t => { + t.plan(5) + const error = new errors.FST_ERR_CTP_INVALID_TYPE() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_CTP_INVALID_TYPE') + t.assert.strictEqual(error.message, 'The content type should be a string or a RegExp') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof TypeError) +}) + +test('FST_ERR_CTP_EMPTY_TYPE', t => { + t.plan(5) + const error = new errors.FST_ERR_CTP_EMPTY_TYPE() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_CTP_EMPTY_TYPE') + t.assert.strictEqual(error.message, 'The content type cannot be an empty string') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof TypeError) +}) + +test('FST_ERR_CTP_INVALID_HANDLER', t => { + t.plan(5) + const error = new errors.FST_ERR_CTP_INVALID_HANDLER() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_CTP_INVALID_HANDLER') + t.assert.strictEqual(error.message, 'The content type handler should be a function') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof TypeError) +}) + +test('FST_ERR_CTP_INVALID_PARSE_TYPE', t => { + t.plan(5) + const error = new errors.FST_ERR_CTP_INVALID_PARSE_TYPE() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_CTP_INVALID_PARSE_TYPE') + t.assert.strictEqual(error.message, "The body parser can only parse your data as 'string' or 'buffer', you asked '%s' which is not supported.") + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof TypeError) +}) + +test('FST_ERR_CTP_BODY_TOO_LARGE', t => { + t.plan(5) + const error = new errors.FST_ERR_CTP_BODY_TOO_LARGE() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_CTP_BODY_TOO_LARGE') + t.assert.strictEqual(error.message, 'Request body is too large') + t.assert.strictEqual(error.statusCode, 413) + t.assert.ok(error instanceof RangeError) +}) + +test('FST_ERR_CTP_INVALID_MEDIA_TYPE', t => { + t.plan(5) + const error = new errors.FST_ERR_CTP_INVALID_MEDIA_TYPE() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_CTP_INVALID_MEDIA_TYPE') + t.assert.strictEqual(error.message, 'Unsupported Media Type') + t.assert.strictEqual(error.statusCode, 415) + t.assert.ok(error instanceof Error) +}) + +test('FST_ERR_CTP_INVALID_CONTENT_LENGTH', t => { + t.plan(5) + const error = new errors.FST_ERR_CTP_INVALID_CONTENT_LENGTH() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_CTP_INVALID_CONTENT_LENGTH') + t.assert.strictEqual(error.message, 'Request body size did not match Content-Length') + t.assert.strictEqual(error.statusCode, 400) + t.assert.ok(error instanceof RangeError) +}) + +test('FST_ERR_CTP_EMPTY_JSON_BODY', t => { + t.plan(5) + const error = new errors.FST_ERR_CTP_EMPTY_JSON_BODY() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_CTP_EMPTY_JSON_BODY') + t.assert.strictEqual(error.message, "Body cannot be empty when content-type is set to 'application/json'") + t.assert.strictEqual(error.statusCode, 400) + t.assert.ok(error instanceof Error) +}) + +test('FST_ERR_CTP_INVALID_JSON_BODY', t => { + t.plan(5) + const error = new errors.FST_ERR_CTP_INVALID_JSON_BODY() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_CTP_INVALID_JSON_BODY') + t.assert.strictEqual(error.message, "Body is not valid JSON but content-type is set to 'application/json'") + t.assert.strictEqual(error.statusCode, 400) + t.assert.ok(error instanceof Error) +}) + +test('FST_ERR_CTP_INSTANCE_ALREADY_STARTED', t => { + t.plan(5) + const error = new errors.FST_ERR_CTP_INSTANCE_ALREADY_STARTED() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_CTP_INSTANCE_ALREADY_STARTED') + t.assert.strictEqual(error.message, 'Cannot call "%s" when fastify instance is already started!') + t.assert.strictEqual(error.statusCode, 400) + t.assert.ok(error instanceof Error) +}) + +test('FST_ERR_DEC_ALREADY_PRESENT', t => { + t.plan(5) + const error = new errors.FST_ERR_DEC_ALREADY_PRESENT() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_DEC_ALREADY_PRESENT') + t.assert.strictEqual(error.message, "The decorator '%s' has already been added!") + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) +}) + +test('FST_ERR_DEC_DEPENDENCY_INVALID_TYPE', t => { + t.plan(5) + const error = new errors.FST_ERR_DEC_DEPENDENCY_INVALID_TYPE() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_DEC_DEPENDENCY_INVALID_TYPE') + t.assert.strictEqual(error.message, "The dependencies of decorator '%s' must be of type Array.") + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof TypeError) +}) + +test('FST_ERR_DEC_MISSING_DEPENDENCY', t => { + t.plan(5) + const error = new errors.FST_ERR_DEC_MISSING_DEPENDENCY() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_DEC_MISSING_DEPENDENCY') + t.assert.strictEqual(error.message, "The decorator is missing dependency '%s'.") + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) +}) + +test('FST_ERR_DEC_AFTER_START', t => { + t.plan(5) + const error = new errors.FST_ERR_DEC_AFTER_START() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_DEC_AFTER_START') + t.assert.strictEqual(error.message, "The decorator '%s' has been added after start!") + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) +}) + +test('FST_ERR_DEC_REFERENCE_TYPE', t => { + t.plan(5) + const error = new errors.FST_ERR_DEC_REFERENCE_TYPE() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_DEC_REFERENCE_TYPE') + t.assert.strictEqual(error.message, "The decorator '%s' of type '%s' is a reference type. Use the { getter, setter } interface instead.") + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) +}) + +test('FST_ERR_DEC_UNDECLARED', t => { + t.plan(5) + const error = new errors.FST_ERR_DEC_UNDECLARED('myDecorator', 'request') + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_DEC_UNDECLARED') + t.assert.strictEqual(error.message, "No decorator 'myDecorator' has been declared on request.") + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) +}) + +test('FST_ERR_HOOK_INVALID_TYPE', t => { + t.plan(5) + const error = new errors.FST_ERR_HOOK_INVALID_TYPE() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_HOOK_INVALID_TYPE') + t.assert.strictEqual(error.message, 'The hook name must be a string') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof TypeError) +}) + +test('FST_ERR_HOOK_INVALID_HANDLER', t => { + t.plan(5) + const error = new errors.FST_ERR_HOOK_INVALID_HANDLER() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_HOOK_INVALID_HANDLER') + t.assert.strictEqual(error.message, '%s hook should be a function, instead got %s') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof TypeError) +}) + +test('FST_ERR_HOOK_INVALID_ASYNC_HANDLER', t => { + t.plan(5) + const error = new errors.FST_ERR_HOOK_INVALID_ASYNC_HANDLER() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER') + t.assert.strictEqual(error.message, "Async function has too many arguments. Async hooks should not use the 'done' argument.") + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof TypeError) +}) + +test('FST_ERR_HOOK_NOT_SUPPORTED', t => { + t.plan(5) + const error = new errors.FST_ERR_HOOK_NOT_SUPPORTED() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_HOOK_NOT_SUPPORTED') + t.assert.strictEqual(error.message, '%s hook not supported!') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof TypeError) +}) + +test('FST_ERR_MISSING_MIDDLEWARE', t => { + t.plan(5) + const error = new errors.FST_ERR_MISSING_MIDDLEWARE() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_MISSING_MIDDLEWARE') + t.assert.strictEqual(error.message, 'You must register a plugin for handling middlewares, visit fastify.dev/docs/latest/Reference/Middleware/ for more info.') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) +}) + +test('FST_ERR_HOOK_TIMEOUT', t => { + t.plan(5) + const error = new errors.FST_ERR_HOOK_TIMEOUT() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_HOOK_TIMEOUT') + t.assert.strictEqual(error.message, "A callback for '%s' hook%s timed out. You may have forgotten to call 'done' function or to resolve a Promise") + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) +}) + +test('FST_ERR_LOG_INVALID_DESTINATION', t => { + t.plan(5) + const error = new errors.FST_ERR_LOG_INVALID_DESTINATION() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_LOG_INVALID_DESTINATION') + t.assert.strictEqual(error.message, 'Cannot specify both logger.stream and logger.file options') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) +}) + +test('FST_ERR_LOG_INVALID_LOGGER', t => { + t.plan(5) + const error = new errors.FST_ERR_LOG_INVALID_LOGGER() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_LOG_INVALID_LOGGER') + t.assert.strictEqual(error.message, "Invalid logger object provided. The logger instance should have these functions(s): '%s'.") + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof TypeError) +}) + +test('FST_ERR_LOG_INVALID_LOGGER_INSTANCE', t => { + t.plan(5) + const error = new errors.FST_ERR_LOG_INVALID_LOGGER_INSTANCE() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_LOG_INVALID_LOGGER_INSTANCE') + t.assert.strictEqual(error.message, 'loggerInstance only accepts a logger instance.') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof TypeError) +}) + +test('FST_ERR_LOG_INVALID_LOGGER_CONFIG', t => { + t.plan(5) + const error = new errors.FST_ERR_LOG_INVALID_LOGGER_CONFIG() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_LOG_INVALID_LOGGER_CONFIG') + t.assert.strictEqual(error.message, 'logger options only accepts a configuration object.') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof TypeError) +}) + +test('FST_ERR_LOG_LOGGER_AND_LOGGER_INSTANCE_PROVIDED', t => { + t.plan(5) + const error = new errors.FST_ERR_LOG_LOGGER_AND_LOGGER_INSTANCE_PROVIDED() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_LOG_LOGGER_AND_LOGGER_INSTANCE_PROVIDED') + t.assert.strictEqual(error.message, 'You cannot provide both logger and loggerInstance. Please provide only one.') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof TypeError) +}) + +test('FST_ERR_REP_INVALID_PAYLOAD_TYPE', t => { + t.plan(5) + const error = new errors.FST_ERR_REP_INVALID_PAYLOAD_TYPE() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_REP_INVALID_PAYLOAD_TYPE') + t.assert.strictEqual(error.message, "Attempted to send payload of invalid type '%s'. Expected a string or Buffer.") + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof TypeError) +}) + +test('FST_ERR_REP_RESPONSE_BODY_CONSUMED', t => { + t.plan(5) + const error = new errors.FST_ERR_REP_RESPONSE_BODY_CONSUMED() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_REP_RESPONSE_BODY_CONSUMED') + t.assert.strictEqual(error.message, 'Response.body is already consumed.') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) +}) + +test('FST_ERR_REP_READABLE_STREAM_LOCKED', t => { + t.plan(5) + const error = new errors.FST_ERR_REP_READABLE_STREAM_LOCKED() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_REP_READABLE_STREAM_LOCKED') + t.assert.strictEqual(error.message, 'ReadableStream was locked. You should call releaseLock() method on reader before sending.') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) +}) + +test('FST_ERR_REP_ALREADY_SENT', t => { + t.plan(5) + const error = new errors.FST_ERR_REP_ALREADY_SENT('/hello', 'GET') + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_REP_ALREADY_SENT') + t.assert.strictEqual(error.message, 'Reply was already sent, did you forget to "return reply" in "/hello" (GET)?') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) +}) + +test('FST_ERR_REP_SENT_VALUE', t => { + t.plan(5) + const error = new errors.FST_ERR_REP_SENT_VALUE() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_REP_SENT_VALUE') + t.assert.strictEqual(error.message, 'The only possible value for reply.sent is true.') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof TypeError) +}) + +test('FST_ERR_SEND_INSIDE_ONERR', t => { + t.plan(5) + const error = new errors.FST_ERR_SEND_INSIDE_ONERR() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_SEND_INSIDE_ONERR') + t.assert.strictEqual(error.message, 'You cannot use `send` inside the `onError` hook') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) +}) + +test('FST_ERR_SEND_UNDEFINED_ERR', t => { + t.plan(5) + const error = new errors.FST_ERR_SEND_UNDEFINED_ERR() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_SEND_UNDEFINED_ERR') + t.assert.strictEqual(error.message, 'Undefined error has occurred') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) +}) + +test('FST_ERR_BAD_STATUS_CODE', t => { + t.plan(5) + const error = new errors.FST_ERR_BAD_STATUS_CODE() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_BAD_STATUS_CODE') + t.assert.strictEqual(error.message, 'Called reply with an invalid status code: %s') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) +}) + +test('FST_ERR_BAD_TRAILER_NAME', t => { + t.plan(5) + const error = new errors.FST_ERR_BAD_TRAILER_NAME() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_BAD_TRAILER_NAME') + t.assert.strictEqual(error.message, 'Called reply.trailer with an invalid header name: %s') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) +}) + +test('FST_ERR_BAD_TRAILER_VALUE', t => { + t.plan(5) + const error = new errors.FST_ERR_BAD_TRAILER_VALUE() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_BAD_TRAILER_VALUE') + t.assert.strictEqual(error.message, "Called reply.trailer('%s', fn) with an invalid type: %s. Expected a function.") + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) +}) + +test('FST_ERR_FAILED_ERROR_SERIALIZATION', t => { + t.plan(5) + const error = new errors.FST_ERR_FAILED_ERROR_SERIALIZATION() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_FAILED_ERROR_SERIALIZATION') + t.assert.strictEqual(error.message, 'Failed to serialize an error. Error: %s. Original error: %s') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) +}) + +test('FST_ERR_MISSING_SERIALIZATION_FN', t => { + t.plan(5) + const error = new errors.FST_ERR_MISSING_SERIALIZATION_FN() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_MISSING_SERIALIZATION_FN') + t.assert.strictEqual(error.message, 'Missing serialization function. Key "%s"') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) +}) + +test('FST_ERR_MISSING_CONTENTTYPE_SERIALIZATION_FN', t => { + t.plan(5) + const error = new errors.FST_ERR_MISSING_CONTENTTYPE_SERIALIZATION_FN() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_MISSING_CONTENTTYPE_SERIALIZATION_FN') + t.assert.strictEqual(error.message, 'Missing serialization function. Key "%s:%s"') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) +}) + +test('FST_ERR_REQ_INVALID_VALIDATION_INVOCATION', t => { + t.plan(5) + const error = new errors.FST_ERR_REQ_INVALID_VALIDATION_INVOCATION() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_REQ_INVALID_VALIDATION_INVOCATION') + t.assert.strictEqual(error.message, 'Invalid validation invocation. Missing validation function for HTTP part "%s" nor schema provided.') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) +}) + +test('FST_ERR_SCH_MISSING_ID', t => { + t.plan(5) + const error = new errors.FST_ERR_SCH_MISSING_ID() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_SCH_MISSING_ID') + t.assert.strictEqual(error.message, 'Missing schema $id property') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) +}) + +test('FST_ERR_SCH_ALREADY_PRESENT', t => { + t.plan(5) + const error = new errors.FST_ERR_SCH_ALREADY_PRESENT() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_SCH_ALREADY_PRESENT') + t.assert.strictEqual(error.message, "Schema with id '%s' already declared!") + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) +}) + +test('FST_ERR_SCH_CONTENT_MISSING_SCHEMA', t => { + t.plan(5) + const error = new errors.FST_ERR_SCH_CONTENT_MISSING_SCHEMA() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_SCH_CONTENT_MISSING_SCHEMA') + t.assert.strictEqual(error.message, "Schema is missing for the content type '%s'") + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) +}) + +test('FST_ERR_SCH_DUPLICATE', t => { + t.plan(5) + const error = new errors.FST_ERR_SCH_DUPLICATE() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_SCH_DUPLICATE') + t.assert.strictEqual(error.message, "Schema with '%s' already present!") + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) +}) + +test('FST_ERR_SCH_VALIDATION_BUILD', t => { + t.plan(5) + const error = new errors.FST_ERR_SCH_VALIDATION_BUILD() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_SCH_VALIDATION_BUILD') + t.assert.strictEqual(error.message, 'Failed building the validation schema for %s: %s, due to error %s') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) +}) + +test('FST_ERR_SCH_SERIALIZATION_BUILD', t => { + t.plan(5) + const error = new errors.FST_ERR_SCH_SERIALIZATION_BUILD() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_SCH_SERIALIZATION_BUILD') + t.assert.strictEqual(error.message, 'Failed building the serialization schema for %s: %s, due to error %s') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) +}) + +test('FST_ERR_SCH_RESPONSE_SCHEMA_NOT_NESTED_2XX', t => { + t.plan(5) + const error = new errors.FST_ERR_SCH_RESPONSE_SCHEMA_NOT_NESTED_2XX() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_SCH_RESPONSE_SCHEMA_NOT_NESTED_2XX') + t.assert.strictEqual(error.message, 'response schemas should be nested under a valid status code, e.g { 2xx: { type: "object" } }') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) +}) + +test('FST_ERR_INIT_OPTS_INVALID', t => { + t.plan(5) + const error = new errors.FST_ERR_INIT_OPTS_INVALID() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_INIT_OPTS_INVALID') + t.assert.strictEqual(error.message, "Invalid initialization options: '%s'") + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) +}) + +test('FST_ERR_FORCE_CLOSE_CONNECTIONS_IDLE_NOT_AVAILABLE', t => { + t.plan(5) + const error = new errors.FST_ERR_FORCE_CLOSE_CONNECTIONS_IDLE_NOT_AVAILABLE() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_FORCE_CLOSE_CONNECTIONS_IDLE_NOT_AVAILABLE') + t.assert.strictEqual(error.message, "Cannot set forceCloseConnections to 'idle' as your HTTP server does not support closeIdleConnections method") + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) +}) + +test('FST_ERR_DUPLICATED_ROUTE', t => { + t.plan(5) + const error = new errors.FST_ERR_DUPLICATED_ROUTE() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_DUPLICATED_ROUTE') + t.assert.strictEqual(error.message, "Method '%s' already declared for route '%s'") + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) +}) + +test('FST_ERR_BAD_URL', t => { + t.plan(5) + const error = new errors.FST_ERR_BAD_URL() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_BAD_URL') + t.assert.strictEqual(error.message, "'%s' is not a valid url component") + t.assert.strictEqual(error.statusCode, 400) + t.assert.ok(error instanceof Error) +}) + +test('FST_ERR_ASYNC_CONSTRAINT', t => { + t.plan(5) + const error = new errors.FST_ERR_ASYNC_CONSTRAINT() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_ASYNC_CONSTRAINT') + t.assert.strictEqual(error.message, 'Unexpected error from async constraint') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) +}) + +test('FST_ERR_INVALID_URL', t => { + t.plan(5) + const error = new errors.FST_ERR_INVALID_URL() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_INVALID_URL') + t.assert.strictEqual(error.message, "URL must be a string. Received '%s'") + t.assert.strictEqual(error.statusCode, 400) + t.assert.ok(error instanceof TypeError) +}) + +test('FST_ERR_ROUTE_OPTIONS_NOT_OBJ', t => { + t.plan(5) + const error = new errors.FST_ERR_ROUTE_OPTIONS_NOT_OBJ() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_ROUTE_OPTIONS_NOT_OBJ') + t.assert.strictEqual(error.message, 'Options for "%s:%s" route must be an object') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof TypeError) +}) + +test('FST_ERR_ROUTE_DUPLICATED_HANDLER', t => { + t.plan(5) + const error = new errors.FST_ERR_ROUTE_DUPLICATED_HANDLER() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_ROUTE_DUPLICATED_HANDLER') + t.assert.strictEqual(error.message, 'Duplicate handler for "%s:%s" route is not allowed!') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) +}) + +test('FST_ERR_ROUTE_HANDLER_NOT_FN', t => { + t.plan(5) + const error = new errors.FST_ERR_ROUTE_HANDLER_NOT_FN() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_ROUTE_HANDLER_NOT_FN') + t.assert.strictEqual(error.message, 'Error Handler for %s:%s route, if defined, must be a function') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof TypeError) +}) + +test('FST_ERR_ROUTE_MISSING_HANDLER', t => { + t.plan(5) + const error = new errors.FST_ERR_ROUTE_MISSING_HANDLER() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_ROUTE_MISSING_HANDLER') + t.assert.strictEqual(error.message, 'Missing handler function for "%s:%s" route.') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) +}) + +test('FST_ERR_ROUTE_METHOD_INVALID', t => { + t.plan(5) + const error = new errors.FST_ERR_ROUTE_METHOD_INVALID() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_ROUTE_METHOD_INVALID') + t.assert.strictEqual(error.message, 'Provided method is invalid!') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof TypeError) +}) + +test('FST_ERR_ROUTE_METHOD_NOT_SUPPORTED', t => { + t.plan(5) + const error = new errors.FST_ERR_ROUTE_METHOD_NOT_SUPPORTED() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_ROUTE_METHOD_NOT_SUPPORTED') + t.assert.strictEqual(error.message, '%s method is not supported.') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) +}) + +test('FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED', t => { + t.plan(5) + const error = new errors.FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED') + t.assert.strictEqual(error.message, 'Body validation schema for %s:%s route is not supported!') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) +}) + +test('FST_ERR_ROUTE_BODY_LIMIT_OPTION_NOT_INT', t => { + t.plan(5) + const error = new errors.FST_ERR_ROUTE_BODY_LIMIT_OPTION_NOT_INT() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_ROUTE_BODY_LIMIT_OPTION_NOT_INT') + t.assert.strictEqual(error.message, "'bodyLimit' option must be an integer > 0. Got '%s'") + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof TypeError) +}) + +test('FST_ERR_ROUTE_BODY_LIMIT_OPTION_NOT_INT', t => { + t.plan(5) + const error = new errors.FST_ERR_ROUTE_BODY_LIMIT_OPTION_NOT_INT() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_ROUTE_BODY_LIMIT_OPTION_NOT_INT') + t.assert.strictEqual(error.message, "'bodyLimit' option must be an integer > 0. Got '%s'") + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof TypeError) +}) + +test('FST_ERR_ROUTE_REWRITE_NOT_STR', t => { + t.plan(5) + const error = new errors.FST_ERR_ROUTE_REWRITE_NOT_STR() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_ROUTE_REWRITE_NOT_STR') + t.assert.strictEqual(error.message, 'Rewrite url for "%s" needs to be of type "string" but received "%s"') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof TypeError) +}) + +test('FST_ERR_REOPENED_CLOSE_SERVER', t => { + t.plan(5) + const error = new errors.FST_ERR_REOPENED_CLOSE_SERVER() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_REOPENED_CLOSE_SERVER') + t.assert.strictEqual(error.message, 'Fastify has already been closed and cannot be reopened') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) +}) + +test('FST_ERR_REOPENED_SERVER', t => { + t.plan(5) + const error = new errors.FST_ERR_REOPENED_SERVER() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_REOPENED_SERVER') + t.assert.strictEqual(error.message, 'Fastify is already listening') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) +}) + +test('FST_ERR_INSTANCE_ALREADY_LISTENING', t => { + t.plan(5) + const error = new errors.FST_ERR_INSTANCE_ALREADY_LISTENING() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_INSTANCE_ALREADY_LISTENING') + t.assert.strictEqual(error.message, 'Fastify instance is already listening. %s') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) +}) + +test('FST_ERR_PLUGIN_VERSION_MISMATCH', t => { + t.plan(5) + const error = new errors.FST_ERR_PLUGIN_VERSION_MISMATCH() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_PLUGIN_VERSION_MISMATCH') + t.assert.strictEqual(error.message, "fastify-plugin: %s - expected '%s' fastify version, '%s' is installed") + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) +}) + +test('FST_ERR_PLUGIN_NOT_PRESENT_IN_INSTANCE', t => { + t.plan(5) + const error = new errors.FST_ERR_PLUGIN_NOT_PRESENT_IN_INSTANCE() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_PLUGIN_NOT_PRESENT_IN_INSTANCE') + t.assert.strictEqual(error.message, "The decorator '%s'%s is not present in %s") + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) +}) + +test('FST_ERR_PLUGIN_INVALID_ASYNC_HANDLER', t => { + t.plan(5) + const error = new errors.FST_ERR_PLUGIN_INVALID_ASYNC_HANDLER('easter-egg') + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_PLUGIN_INVALID_ASYNC_HANDLER') + t.assert.strictEqual(error.message, 'The easter-egg plugin being registered mixes async and callback styles. Async plugin should not mix async and callback style.') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof TypeError) +}) + +test('FST_ERR_PLUGIN_CALLBACK_NOT_FN', t => { + t.plan(5) + const error = new errors.FST_ERR_PLUGIN_CALLBACK_NOT_FN() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_PLUGIN_CALLBACK_NOT_FN') + t.assert.strictEqual(error.message, 'fastify-plugin: %s') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof TypeError) +}) + +test('FST_ERR_PLUGIN_NOT_VALID', t => { + t.plan(5) + const error = new errors.FST_ERR_PLUGIN_NOT_VALID() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_PLUGIN_NOT_VALID') + t.assert.strictEqual(error.message, 'fastify-plugin: %s') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) +}) + +test('FST_ERR_ROOT_PLG_BOOTED', t => { + t.plan(5) + const error = new errors.FST_ERR_ROOT_PLG_BOOTED() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_ROOT_PLG_BOOTED') + t.assert.strictEqual(error.message, 'fastify-plugin: %s') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) +}) + +test('FST_ERR_PARENT_PLUGIN_BOOTED', t => { + t.plan(5) + const error = new errors.FST_ERR_PARENT_PLUGIN_BOOTED() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_PARENT_PLUGIN_BOOTED') + t.assert.strictEqual(error.message, 'fastify-plugin: %s') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) +}) + +test('FST_ERR_PLUGIN_TIMEOUT', t => { + t.plan(5) + const error = new errors.FST_ERR_PLUGIN_TIMEOUT() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_PLUGIN_TIMEOUT') + t.assert.strictEqual(error.message, 'fastify-plugin: %s') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof Error) +}) + +test('FST_ERR_VALIDATION', t => { + t.plan(5) + const error = new errors.FST_ERR_VALIDATION() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_VALIDATION') + t.assert.strictEqual(error.message, '%s') + t.assert.strictEqual(error.statusCode, 400) + t.assert.ok(error instanceof Error) +}) + +test('FST_ERR_LISTEN_OPTIONS_INVALID', t => { + t.plan(5) + const error = new errors.FST_ERR_LISTEN_OPTIONS_INVALID() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_LISTEN_OPTIONS_INVALID') + t.assert.strictEqual(error.message, "Invalid listen options: '%s'") + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof TypeError) +}) + +test('FST_ERR_ERROR_HANDLER_NOT_FN', t => { + t.plan(5) + const error = new errors.FST_ERR_ERROR_HANDLER_NOT_FN() + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.code, 'FST_ERR_ERROR_HANDLER_NOT_FN') + t.assert.strictEqual(error.message, 'Error Handler must be a function') + t.assert.strictEqual(error.statusCode, 500) + t.assert.ok(error instanceof TypeError) +}) + +test('Ensure that all errors are in Errors.md TOC', t => { + t.plan(expectedErrors) + + const errorsMd = readFileSync(resolve(__dirname, '../../docs/Reference/Errors.md'), 'utf8') + + const exportedKeys = Object.keys(errors) + for (const key of exportedKeys) { + if (errors[key].name === 'FastifyError') { + t.assert.ok(errorsMd.includes(` - [${key.toUpperCase()}](#${key.toLowerCase()})`), key) + } + } +}) + +test('Ensure that non-existing errors are not in Errors.md TOC', t => { + t.plan(expectedErrors) + const errorsMd = readFileSync(resolve(__dirname, '../../docs/Reference/Errors.md'), 'utf8') + + const matchRE = / {4}- \[([A-Z0-9_]+)\]\(#[a-z0-9_]+\)/g + const matches = errorsMd.matchAll(matchRE) + const exportedKeys = Object.keys(errors) + + for (const match of matches) { + t.assert.ok(exportedKeys.indexOf(match[1]) !== -1, match[1]) + } +}) + +test('Ensure that all errors are in Errors.md documented', t => { + t.plan(expectedErrors) + const errorsMd = readFileSync(resolve(__dirname, '../../docs/Reference/Errors.md'), 'utf8') + + const exportedKeys = Object.keys(errors) + for (const key of exportedKeys) { + if (errors[key].name === 'FastifyError') { + t.assert.ok(errorsMd.includes(`${key.toUpperCase()}`), key) + } + } +}) + +test('Ensure that non-existing errors are not in Errors.md documented', t => { + t.plan(expectedErrors) + + const errorsMd = readFileSync(resolve(__dirname, '../../docs/Reference/Errors.md'), 'utf8') + + const matchRE = /([0-9a-zA-Z_]+)<\/a>/g + const matches = errorsMd.matchAll(matchRE) + const exportedKeys = Object.keys(errors) + + for (const match of matches) { + t.assert.ok(exportedKeys.indexOf(match[1]) !== -1, match[1]) + } +}) + +test('Ensure that all errors are in errors.d.ts', t => { + t.plan(expectedErrors) + + const errorsDts = readFileSync(resolve(__dirname, '../../types/errors.d.ts'), 'utf8') + + const FastifyErrorCodesRE = /export type FastifyErrorCodes = Record<([^>]+),\s*FastifyErrorConstructor>/m + + const [, errorCodeType] = errorsDts.match(FastifyErrorCodesRE) + + const errorCodeRE = /'([A-Z0-9_]+)'/g + const matches = errorCodeType.matchAll(errorCodeRE) + const errorTypes = [...matches].map(match => match[1]) + const exportedKeys = Object.keys(errors) + + for (const key of exportedKeys) { + if (errors[key].name === 'FastifyError') { + t.assert.ok(errorTypes.includes(key), key) + } + } +}) + +test('Ensure that non-existing errors are not in errors.d.ts', t => { + t.plan(expectedErrors) + + const errorsDts = readFileSync(resolve(__dirname, '../../types/errors.d.ts'), 'utf8') + + const FastifyErrorCodesRE = /export type FastifyErrorCodes = Record<([^>]+),\s*FastifyErrorConstructor>/m + + const [, errorCodeType] = errorsDts.match(FastifyErrorCodesRE) + + const errorCodeRE = /'([A-Z0-9_]+)'/g + const matches = errorCodeType.matchAll(errorCodeRE) + const exportedKeys = Object.keys(errors) + + for (const match of matches) { + t.assert.ok(exportedKeys.indexOf(match[1]) !== -1, match[1]) + } +}) diff --git a/services/slides/node_modules/fastify/test/internals/handle-request.test.js b/services/slides/node_modules/fastify/test/internals/handle-request.test.js new file mode 100644 index 0000000000000000000000000000000000000000..2a6962d7b246ca68dd48db5f1efde639403fda99 --- /dev/null +++ b/services/slides/node_modules/fastify/test/internals/handle-request.test.js @@ -0,0 +1,270 @@ +'use strict' + +const { test } = require('node:test') +const handleRequest = require('../../lib/handle-request') +const internals = require('../../lib/handle-request')[Symbol.for('internals')] +const Request = require('../../lib/request') +const Reply = require('../../lib/reply') +const { kRouteContext } = require('../../lib/symbols') +const buildSchema = require('../../lib/validation').compileSchemasForValidation + +const Ajv = require('ajv') +const ajv = new Ajv({ coerceTypes: true }) + +function schemaValidator ({ schema, method, url, httpPart }) { + const validateFunction = ajv.compile(schema) + const fn = function (body) { + const isOk = validateFunction(body) + if (isOk) return + return false + } + fn.errors = [] + return fn +} + +test('handleRequest function - sent reply', t => { + t.plan(1) + const request = {} + const reply = { sent: true } + const res = handleRequest(null, request, reply) + t.assert.strictEqual(res, undefined) +}) + +test('handleRequest function - invoke with error', t => { + t.plan(1) + const request = {} + const reply = {} + reply.send = (err) => t.assert.strictEqual(err.message, 'Kaboom') + handleRequest(new Error('Kaboom'), request, reply) +}) + +test('handler function - invalid schema', t => { + t.plan(1) + const res = {} + res.log = { error: () => {}, info: () => {} } + const context = { + config: { + method: 'GET', + url: '/an-url' + }, + schema: { + body: { + type: 'object', + properties: { + hello: { type: 'number' } + } + } + }, + errorHandler: { func: () => { t.assert.ok('errorHandler called') } }, + handler: () => {}, + Reply, + Request, + preValidation: [], + preHandler: [], + onSend: [], + onError: [], + attachValidation: false, + schemaErrorFormatter: () => new Error() + } + buildSchema(context, schemaValidator) + const request = { + body: { hello: 'world' }, + [kRouteContext]: context + } + internals.handler(request, new Reply(res, request)) +}) + +test('handler function - reply', t => { + t.plan(3) + const res = {} + res.end = () => { + t.assert.strictEqual(res.statusCode, 204) + t.assert.ok(true) + } + res.writeHead = () => {} + const context = { + handler: (req, reply) => { + t.assert.strictEqual(typeof reply, 'object') + reply.code(204) + reply.send(undefined) + }, + Reply, + Request, + preValidation: [], + preHandler: [], + onSend: [], + onError: [], + config: { + url: '', + method: '' + } + } + buildSchema(context, schemaValidator) + internals.handler({ [kRouteContext]: context }, new Reply(res, { [kRouteContext]: context })) +}) + +test('handler function - preValidationCallback with finished response', t => { + t.plan(0) + const res = {} + // Be sure to check only `writableEnded` where is available + res.writableEnded = true + res.end = () => { + t.assert.fail() + } + res.writeHead = () => {} + const context = { + handler: (req, reply) => { + t.assert.fail() + reply.send(undefined) + }, + Reply, + Request, + preValidation: null, + preHandler: [], + onSend: [], + onError: [] + } + buildSchema(context, schemaValidator) + internals.handler({ [kRouteContext]: context }, new Reply(res, { [kRouteContext]: context })) +}) + +test('request should be defined in onSend Hook on post request with content type application/json', async t => { + t.plan(6) + const fastify = require('../..')() + + t.after(() => { + fastify.close() + }) + + fastify.addHook('onSend', (request, reply, payload, done) => { + t.assert.ok(request) + t.assert.ok(request.raw) + t.assert.ok(request.id) + t.assert.ok(request.params) + t.assert.ok(request.query) + done() + }) + fastify.post('/', (request, reply) => { + reply.send(200) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + const result = await fetch(fastifyServer, { + method: 'POST', + headers: { + 'content-type': 'application/json' + } + }) + + t.assert.strictEqual(result.status, 400) +}) + +test('request should be defined in onSend Hook on post request with content type application/x-www-form-urlencoded', async t => { + t.plan(5) + const fastify = require('../..')() + + t.after(() => { + fastify.close() + }) + + fastify.addHook('onSend', (request, reply, payload, done) => { + t.assert.ok(request) + t.assert.ok(request.raw) + t.assert.ok(request.params) + t.assert.ok(request.query) + done() + }) + fastify.post('/', (request, reply) => { + reply.send(200) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + const result = await fetch(fastifyServer, { + method: 'POST', + headers: { + 'content-type': 'application/x-www-form-urlencoded' + } + }) + + // a 415 error is expected because of missing content type parser + t.assert.strictEqual(result.status, 415) +}) + +test('request should be defined in onSend Hook on options request with content type application/x-www-form-urlencoded', async t => { + t.plan(15) + const fastify = require('../..')() + + t.after(() => { + fastify.close() + }) + + fastify.addHook('onSend', (request, reply, payload, done) => { + t.assert.ok(request) + t.assert.ok(request.raw) + t.assert.ok(request.params) + t.assert.ok(request.query) + done() + }) + fastify.options('/', (request, reply) => { + reply.send(200) + }) + + // Test 1: OPTIONS with body and content-type header + const result1 = await fastify.inject({ + method: 'OPTIONS', + url: '/', + body: 'first-name=OPTIONS&last-name=METHOD', + headers: { + 'content-type': 'application/x-www-form-urlencoded' + } + }) + + // Content-Type is not supported + t.assert.strictEqual(result1.statusCode, 415) + + // Test 2: OPTIONS with content-type header only (no body) + const result2 = await fastify.inject({ + method: 'OPTIONS', + url: '/', + headers: { + 'content-type': 'application/x-www-form-urlencoded' + } + }) + + // Content-Type is not supported + t.assert.strictEqual(result2.statusCode, 415) + + // Test 3: OPTIONS with body but no content-type header + const result3 = await fastify.inject({ + method: 'OPTIONS', + url: '/', + body: 'first-name=OPTIONS&last-name=METHOD' + }) + + // No content-type with payload + t.assert.strictEqual(result3.statusCode, 415) +}) + +test('request should respond with an error if an unserialized payload is sent inside an async handler', async t => { + t.plan(2) + + const fastify = require('../..')() + + fastify.get('/', (request, reply) => { + reply.type('text/html') + return Promise.resolve(request.headers) + }) + + const res = await fastify.inject({ + method: 'GET', + url: '/' + }) + + t.assert.strictEqual(res.statusCode, 500) + t.assert.deepStrictEqual(JSON.parse(res.payload), { + error: 'Internal Server Error', + code: 'FST_ERR_REP_INVALID_PAYLOAD_TYPE', + message: 'Attempted to send payload of invalid type \'object\'. Expected a string or Buffer.', + statusCode: 500 + }) +}) diff --git a/services/slides/node_modules/fastify/test/internals/hook-runner.test.js b/services/slides/node_modules/fastify/test/internals/hook-runner.test.js new file mode 100644 index 0000000000000000000000000000000000000000..f8319fe29a4532ca09953d1ecd79a777e81edd66 --- /dev/null +++ b/services/slides/node_modules/fastify/test/internals/hook-runner.test.js @@ -0,0 +1,449 @@ +'use strict' + +const { test } = require('node:test') +const { hookRunnerGenerator, onSendHookRunner } = require('../../lib/hooks') + +test('hookRunner - Basic', t => { + t.plan(9) + + const hookRunner = hookRunnerGenerator(iterator) + + hookRunner([fn1, fn2, fn3], 'a', 'b', done) + + function iterator (fn, a, b, done) { + return fn(a, b, done) + } + + function fn1 (a, b, done) { + t.assert.strictEqual(a, 'a') + t.assert.strictEqual(b, 'b') + done() + } + + function fn2 (a, b, done) { + t.assert.strictEqual(a, 'a') + t.assert.strictEqual(b, 'b') + done() + } + + function fn3 (a, b, done) { + t.assert.strictEqual(a, 'a') + t.assert.strictEqual(b, 'b') + done() + } + + function done (err, a, b) { + t.assert.ifError(err) + t.assert.strictEqual(a, 'a') + t.assert.strictEqual(b, 'b') + } +}) + +test('hookRunner - In case of error should skip to done', t => { + t.plan(7) + + const hookRunner = hookRunnerGenerator(iterator) + + hookRunner([fn1, fn2, fn3], 'a', 'b', done) + + function iterator (fn, a, b, done) { + return fn(a, b, done) + } + + function fn1 (a, b, done) { + t.assert.strictEqual(a, 'a') + t.assert.strictEqual(b, 'b') + done() + } + + function fn2 (a, b, done) { + t.assert.strictEqual(a, 'a') + t.assert.strictEqual(b, 'b') + done(new Error('kaboom')) + } + + function fn3 () { + t.assert.fail('We should not be here') + } + + function done (err, a, b) { + t.assert.strictEqual(err.message, 'kaboom') + t.assert.strictEqual(a, 'a') + t.assert.strictEqual(b, 'b') + } +}) + +test('hookRunner - Should handle throw', t => { + t.plan(7) + + const hookRunner = hookRunnerGenerator(iterator) + + hookRunner([fn1, fn2, fn3], 'a', 'b', done) + + function iterator (fn, a, b, done) { + return fn(a, b, done) + } + + function fn1 (a, b, done) { + t.assert.strictEqual(a, 'a') + t.assert.strictEqual(b, 'b') + done() + } + + function fn2 (a, b, done) { + t.assert.strictEqual(a, 'a') + t.assert.strictEqual(b, 'b') + throw new Error('kaboom') + } + + function fn3 () { + t.assert.fail('We should not be here') + } + + function done (err, a, b) { + t.assert.strictEqual(err.message, 'kaboom') + t.assert.strictEqual(a, 'a') + t.assert.strictEqual(b, 'b') + } +}) + +test('hookRunner - Should handle promises', t => { + t.plan(9) + + const hookRunner = hookRunnerGenerator(iterator) + + hookRunner([fn1, fn2, fn3], 'a', 'b', done) + + function iterator (fn, a, b, done) { + return fn(a, b, done) + } + + function fn1 (a, b) { + t.assert.strictEqual(a, 'a') + t.assert.strictEqual(b, 'b') + return Promise.resolve() + } + + function fn2 (a, b) { + t.assert.strictEqual(a, 'a') + t.assert.strictEqual(b, 'b') + return Promise.resolve() + } + + function fn3 (a, b) { + t.assert.strictEqual(a, 'a') + t.assert.strictEqual(b, 'b') + return Promise.resolve() + } + + function done (err, a, b) { + t.assert.ifError(err) + t.assert.strictEqual(a, 'a') + t.assert.strictEqual(b, 'b') + } +}) + +test('hookRunner - In case of error should skip to done (with promises)', t => { + t.plan(7) + + const hookRunner = hookRunnerGenerator(iterator) + + hookRunner([fn1, fn2, fn3], 'a', 'b', done) + + function iterator (fn, a, b, done) { + return fn(a, b, done) + } + + function fn1 (a, b) { + t.assert.strictEqual(a, 'a') + t.assert.strictEqual(b, 'b') + return Promise.resolve() + } + + function fn2 (a, b) { + t.assert.strictEqual(a, 'a') + t.assert.strictEqual(b, 'b') + return Promise.reject(new Error('kaboom')) + } + + function fn3 () { + t.assert.fail('We should not be here') + } + + function done (err, a, b) { + t.assert.strictEqual(err.message, 'kaboom') + t.assert.strictEqual(a, 'a') + t.assert.strictEqual(b, 'b') + } +}) + +test('hookRunner - Be able to exit before its natural end', t => { + t.plan(4) + + const hookRunner = hookRunnerGenerator(iterator) + + let shouldStop = false + hookRunner([fn1, fn2, fn3], 'a', 'b', done) + + function iterator (fn, a, b, done) { + if (shouldStop) { + return undefined + } + return fn(a, b, done) + } + + function fn1 (a, b, done) { + t.assert.strictEqual(a, 'a') + t.assert.strictEqual(b, 'b') + done() + } + + function fn2 (a, b) { + t.assert.strictEqual(a, 'a') + t.assert.strictEqual(b, 'b') + shouldStop = true + return Promise.resolve() + } + + function fn3 () { + t.assert.fail('this should not be called') + } + + function done () { + t.assert.fail('this should not be called') + } +}) + +test('hookRunner - Promises that resolve to a value do not change the state', t => { + t.plan(5) + + const originalState = { a: 'a', b: 'b' } + + const hookRunner = hookRunnerGenerator(iterator) + + hookRunner([fn1, fn2, fn3], originalState, 'b', done) + + function iterator (fn, state, b, done) { + return fn(state, b, done) + } + + function fn1 (state, b, done) { + t.assert.strictEqual(state, originalState) + return Promise.resolve(null) + } + + function fn2 (state, b, done) { + t.assert.strictEqual(state, originalState) + return Promise.resolve('string') + } + + function fn3 (state, b, done) { + t.assert.strictEqual(state, originalState) + return Promise.resolve({ object: true }) + } + + function done (err, state, b) { + t.assert.ifError(err) + t.assert.strictEqual(state, originalState) + } +}) + +test('onSendHookRunner - Basic', t => { + t.plan(13) + + const originalRequest = { body: null } + const originalReply = { request: originalRequest } + const originalPayload = 'payload' + + onSendHookRunner([fn1, fn2, fn3], originalRequest, originalReply, originalPayload, done) + + function fn1 (request, reply, payload, done) { + t.assert.deepStrictEqual(request, originalRequest) + t.assert.deepStrictEqual(reply, originalReply) + t.assert.strictEqual(payload, originalPayload) + done() + } + + function fn2 (request, reply, payload, done) { + t.assert.deepStrictEqual(request, originalRequest) + t.assert.deepStrictEqual(reply, originalReply) + t.assert.strictEqual(payload, originalPayload) + done() + } + + function fn3 (request, reply, payload, done) { + t.assert.deepStrictEqual(request, originalRequest) + t.assert.deepStrictEqual(reply, originalReply) + t.assert.strictEqual(payload, originalPayload) + done() + } + + function done (err, request, reply, payload) { + t.assert.ifError(err) + t.assert.deepStrictEqual(request, originalRequest) + t.assert.deepStrictEqual(reply, originalReply) + t.assert.strictEqual(payload, originalPayload) + } +}) + +test('onSendHookRunner - Can change the payload', t => { + t.plan(7) + + const originalRequest = { body: null } + const originalReply = { request: originalRequest } + const v1 = { hello: 'world' } + const v2 = { ciao: 'mondo' } + const v3 = { winter: 'is coming' } + const v4 = { winter: 'has come' } + + onSendHookRunner([fn1, fn2, fn3], originalRequest, originalReply, v1, done) + + function fn1 (request, reply, payload, done) { + t.assert.deepStrictEqual(payload, v1) + done(null, v2) + } + + function fn2 (request, reply, payload, done) { + t.assert.deepStrictEqual(payload, v2) + done(null, v3) + } + + function fn3 (request, reply, payload, done) { + t.assert.deepStrictEqual(payload, v3) + done(null, v4) + } + + function done (err, request, reply, payload) { + t.assert.ifError(err) + t.assert.deepStrictEqual(request, originalRequest) + t.assert.deepStrictEqual(reply, originalReply) + t.assert.deepStrictEqual(payload, v4) + } +}) + +test('onSendHookRunner - In case of error should skip to done', t => { + t.plan(6) + + const originalRequest = { body: null } + const originalReply = { request: originalRequest } + const v1 = { hello: 'world' } + const v2 = { ciao: 'mondo' } + + onSendHookRunner([fn1, fn2, fn3], originalRequest, originalReply, v1, done) + + function fn1 (request, reply, payload, done) { + t.assert.deepStrictEqual(payload, v1) + done(null, v2) + } + + function fn2 (request, reply, payload, done) { + t.assert.deepStrictEqual(payload, v2) + done(new Error('kaboom')) + } + + function fn3 () { + t.assert.fail('We should not be here') + } + + function done (err, request, reply, payload) { + t.assert.strictEqual(err.message, 'kaboom') + t.assert.deepStrictEqual(request, originalRequest) + t.assert.deepStrictEqual(reply, originalReply) + t.assert.deepStrictEqual(payload, v2) + } +}) + +test('onSendHookRunner - Should handle promises', t => { + t.plan(7) + + const originalRequest = { body: null } + const originalReply = { request: originalRequest } + const v1 = { hello: 'world' } + const v2 = { ciao: 'mondo' } + const v3 = { winter: 'is coming' } + const v4 = { winter: 'has come' } + + onSendHookRunner([fn1, fn2, fn3], originalRequest, originalReply, v1, done) + + function fn1 (request, reply, payload) { + t.assert.deepStrictEqual(payload, v1) + return Promise.resolve(v2) + } + + function fn2 (request, reply, payload) { + t.assert.deepStrictEqual(payload, v2) + return Promise.resolve(v3) + } + + function fn3 (request, reply, payload) { + t.assert.deepStrictEqual(payload, v3) + return Promise.resolve(v4) + } + + function done (err, request, reply, payload) { + t.assert.ifError(err) + t.assert.deepStrictEqual(request, originalRequest) + t.assert.deepStrictEqual(reply, originalReply) + t.assert.deepStrictEqual(payload, v4) + } +}) + +test('onSendHookRunner - In case of error should skip to done (with promises)', t => { + t.plan(6) + + const originalRequest = { body: null } + const originalReply = { request: originalRequest } + const v1 = { hello: 'world' } + const v2 = { ciao: 'mondo' } + + onSendHookRunner([fn1, fn2, fn3], originalRequest, originalReply, v1, done) + + function fn1 (request, reply, payload) { + t.assert.deepStrictEqual(payload, v1) + return Promise.resolve(v2) + } + + function fn2 (request, reply, payload) { + t.assert.deepStrictEqual(payload, v2) + return Promise.reject(new Error('kaboom')) + } + + function fn3 () { + t.assert.fail('We should not be here') + } + + function done (err, request, reply, payload) { + t.assert.strictEqual(err.message, 'kaboom') + t.assert.deepStrictEqual(request, originalRequest) + t.assert.deepStrictEqual(reply, originalReply) + t.assert.deepStrictEqual(payload, v2) + } +}) + +test('onSendHookRunner - Be able to exit before its natural end', t => { + t.plan(2) + + const originalRequest = { body: null } + const originalReply = { request: originalRequest } + const v1 = { hello: 'world' } + const v2 = { ciao: 'mondo' } + + onSendHookRunner([fn1, fn2, fn3], originalRequest, originalReply, v1, done) + + function fn1 (request, reply, payload, done) { + t.assert.deepStrictEqual(payload, v1) + done(null, v2) + } + + function fn2 (request, reply, payload) { + t.assert.deepStrictEqual(payload, v2) + } + + function fn3 () { + t.assert.fail('this should not be called') + } + + function done () { + t.assert.fail('this should not be called') + } +}) diff --git a/services/slides/node_modules/fastify/test/internals/hooks.test.js b/services/slides/node_modules/fastify/test/internals/hooks.test.js new file mode 100644 index 0000000000000000000000000000000000000000..a0811a97b02211a86c142d5292b82d2d0cf3be92 --- /dev/null +++ b/services/slides/node_modules/fastify/test/internals/hooks.test.js @@ -0,0 +1,96 @@ +'use strict' + +const { test } = require('node:test') +const { Hooks } = require('../../lib/hooks') +const { default: fastify } = require('../../fastify') +const noop = () => {} + +test('hooks should have 4 array with the registered hooks', t => { + const hooks = new Hooks() + t.assert.strictEqual(typeof hooks, 'object') + t.assert.ok(Array.isArray(hooks.onRequest)) + t.assert.ok(Array.isArray(hooks.onSend)) + t.assert.ok(Array.isArray(hooks.preParsing)) + t.assert.ok(Array.isArray(hooks.preValidation)) + t.assert.ok(Array.isArray(hooks.preHandler)) + t.assert.ok(Array.isArray(hooks.onResponse)) + t.assert.ok(Array.isArray(hooks.onError)) +}) + +test('hooks.add should add a hook to the given hook', t => { + const hooks = new Hooks() + hooks.add('onRequest', noop) + t.assert.strictEqual(hooks.onRequest.length, 1) + t.assert.strictEqual(typeof hooks.onRequest[0], 'function') + + hooks.add('preParsing', noop) + t.assert.strictEqual(hooks.preParsing.length, 1) + t.assert.strictEqual(typeof hooks.preParsing[0], 'function') + + hooks.add('preValidation', noop) + t.assert.strictEqual(hooks.preValidation.length, 1) + t.assert.strictEqual(typeof hooks.preValidation[0], 'function') + + hooks.add('preHandler', noop) + t.assert.strictEqual(hooks.preHandler.length, 1) + t.assert.strictEqual(typeof hooks.preHandler[0], 'function') + + hooks.add('onResponse', noop) + t.assert.strictEqual(hooks.onResponse.length, 1) + t.assert.strictEqual(typeof hooks.onResponse[0], 'function') + + hooks.add('onSend', noop) + t.assert.strictEqual(hooks.onSend.length, 1) + t.assert.strictEqual(typeof hooks.onSend[0], 'function') + + hooks.add('onError', noop) + t.assert.strictEqual(hooks.onError.length, 1) + t.assert.strictEqual(typeof hooks.onError[0], 'function') +}) + +test('hooks should throw on unexisting handler', t => { + t.plan(1) + const hooks = new Hooks() + try { + hooks.add('onUnexistingHook', noop) + t.assert.fail() + } catch (e) { + t.assert.ok(true) + } +}) + +test('should throw on wrong parameters', t => { + const hooks = new Hooks() + t.plan(4) + try { + hooks.add(null, () => {}) + t.assert.fail() + } catch (e) { + t.assert.strictEqual(e.code, 'FST_ERR_HOOK_INVALID_TYPE') + t.assert.strictEqual(e.message, 'The hook name must be a string') + } + + try { + hooks.add('onSend', null) + t.assert.fail() + } catch (e) { + t.assert.strictEqual(e.code, 'FST_ERR_HOOK_INVALID_HANDLER') + t.assert.strictEqual(e.message, 'onSend hook should be a function, instead got [object Null]') + } +}) + +test('Integration test: internal function _addHook should be turned into app.ready() rejection', async (t) => { + const app = fastify() + + app.register(async function () { + app.addHook('notRealHook', async () => {}) + }) + + try { + await app.ready() + t.assert.fail('Expected ready() to throw') + } catch (err) { + t.assert.strictEqual(err.code, 'FST_ERR_HOOK_NOT_SUPPORTED') + t.assert.match(err.message, /hook not supported/i) + } +}) diff --git a/services/slides/node_modules/fastify/test/internals/initial-config.test.js b/services/slides/node_modules/fastify/test/internals/initial-config.test.js new file mode 100644 index 0000000000000000000000000000000000000000..5cd5229aea4afbf3e4960a7f656211dcc3491f35 --- /dev/null +++ b/services/slides/node_modules/fastify/test/internals/initial-config.test.js @@ -0,0 +1,383 @@ +'use strict' + +const { test, before } = require('node:test') +const Fastify = require('../..') +const helper = require('../helper') +const http = require('node:http') +const pino = require('pino') +const split = require('split2') +const deepClone = require('rfdc')({ circles: true, proto: false }) +const { deepFreezeObject } = require('../../lib/initial-config-validation').utils + +const { buildCertificate } = require('../build-certificate') + +process.removeAllListeners('warning') + +let localhost +let localhostForURL + +before(async function () { + await buildCertificate(); + [localhost, localhostForURL] = await helper.getLoopbackHost() +}) + +test('Fastify.initialConfig is an object', t => { + t.plan(1) + t.assert.ok(typeof Fastify().initialConfig === 'object') +}) + +test('without options passed to Fastify, initialConfig should expose default values', t => { + t.plan(1) + + const fastifyDefaultOptions = { + connectionTimeout: 0, + keepAliveTimeout: 72000, + maxRequestsPerSocket: 0, + requestTimeout: 0, + handlerTimeout: 0, + bodyLimit: 1024 * 1024, + caseSensitive: true, + allowUnsafeRegex: false, + disableRequestLogging: false, + ignoreTrailingSlash: false, + ignoreDuplicateSlashes: false, + maxParamLength: 100, + onProtoPoisoning: 'error', + onConstructorPoisoning: 'error', + pluginTimeout: 10000, + requestIdHeader: false, + requestIdLogLabel: 'reqId', + http2SessionTimeout: 72000, + exposeHeadRoutes: true, + useSemicolonDelimiter: false + } + + t.assert.deepStrictEqual(Fastify().initialConfig, fastifyDefaultOptions) +}) + +test('Fastify.initialConfig should expose all options', t => { + t.plan(22) + + const serverFactory = (handler, opts) => { + const server = http.createServer((req, res) => { + handler(req, res) + }) + + return server + } + + const versionStrategy = { + name: 'version', + storage: function () { + const versions = {} + return { + get: (version) => { return versions[version] || null }, + set: (version, store) => { versions[version] = store } + } + }, + deriveConstraint: (req, ctx) => { + return req.headers.accept + }, + validate () { return true } + } + + let reqId = 0 + const options = { + http2: true, + https: { + key: global.context.key, + cert: global.context.cert + }, + ignoreTrailingSlash: true, + ignoreDuplicateSlashes: true, + maxParamLength: 200, + connectionTimeout: 0, + keepAliveTimeout: 72000, + bodyLimit: 1049600, + onProtoPoisoning: 'remove', + serverFactory, + caseSensitive: true, + allowUnsafeRegex: false, + requestIdHeader: 'request-id-alt', + pluginTimeout: 20000, + useSemicolonDelimiter: false, + querystringParser: str => str, + genReqId: function (req) { + return reqId++ + }, + loggerInstance: pino({ level: 'info' }), + constraints: { + version: versionStrategy + }, + trustProxy: function myTrustFn (address, hop) { + return address === '1.2.3.4' || hop === 1 + } + } + + const fastify = Fastify(options) + t.assert.strictEqual(fastify.initialConfig.http2, true) + t.assert.strictEqual(fastify.initialConfig.https, true, 'for security reason the key cert is hidden') + t.assert.strictEqual(fastify.initialConfig.ignoreTrailingSlash, true) + t.assert.strictEqual(fastify.initialConfig.ignoreDuplicateSlashes, true) + t.assert.strictEqual(fastify.initialConfig.maxParamLength, 200) + t.assert.strictEqual(fastify.initialConfig.connectionTimeout, 0) + t.assert.strictEqual(fastify.initialConfig.keepAliveTimeout, 72000) + t.assert.strictEqual(fastify.initialConfig.bodyLimit, 1049600) + t.assert.strictEqual(fastify.initialConfig.onProtoPoisoning, 'remove') + t.assert.strictEqual(fastify.initialConfig.caseSensitive, true) + t.assert.strictEqual(fastify.initialConfig.useSemicolonDelimiter, false) + t.assert.strictEqual(fastify.initialConfig.allowUnsafeRegex, false) + t.assert.strictEqual(fastify.initialConfig.requestIdHeader, 'request-id-alt') + t.assert.strictEqual(fastify.initialConfig.pluginTimeout, 20000) + t.assert.ok(fastify.initialConfig.constraints.version) + + // obfuscated options: + t.assert.strictEqual(fastify.initialConfig.serverFactory, undefined) + t.assert.strictEqual(fastify.initialConfig.trustProxy, undefined) + t.assert.strictEqual(fastify.initialConfig.genReqId, undefined) + t.assert.strictEqual(fastify.initialConfig.childLoggerFactory, undefined) + t.assert.strictEqual(fastify.initialConfig.querystringParser, undefined) + t.assert.strictEqual(fastify.initialConfig.logger, undefined) + t.assert.strictEqual(fastify.initialConfig.trustProxy, undefined) +}) + +test('Should throw if you try to modify Fastify.initialConfig', t => { + t.plan(4) + + const fastify = Fastify({ ignoreTrailingSlash: true }) + try { + fastify.initialConfig.ignoreTrailingSlash = false + t.assert.fail() + } catch (error) { + t.assert.ok(error instanceof TypeError) + t.assert.strictEqual(error.message, "Cannot assign to read only property 'ignoreTrailingSlash' of object '#'") + t.assert.ok(error.stack) + t.assert.ok(true) + } +}) + +test('We must avoid shallow freezing and ensure that the whole object is freezed', t => { + t.plan(4) + + const fastify = Fastify({ + https: { + allowHTTP1: true, + key: global.context.key, + cert: global.context.cert + } + }) + + try { + fastify.initialConfig.https.allowHTTP1 = false + t.assert.fail() + } catch (error) { + t.assert.ok(error instanceof TypeError) + t.assert.strictEqual(error.message, "Cannot assign to read only property 'allowHTTP1' of object '#'") + t.assert.ok(error.stack) + t.assert.deepStrictEqual(fastify.initialConfig.https, { + allowHTTP1: true + }, 'key cert removed') + } +}) + +test('https value check', t => { + t.plan(1) + + const fastify = Fastify({}) + t.assert.ok(!fastify.initialConfig.https) +}) + +test('Return an error if options do not match the validation schema', t => { + t.plan(6) + + try { + Fastify({ ignoreTrailingSlash: 'string instead of boolean' }) + + t.assert.fail() + } catch (error) { + t.assert.ok(error instanceof Error) + t.assert.strictEqual(error.name, 'FastifyError') + t.assert.strictEqual(error.message, 'Invalid initialization options: \'["must be boolean"]\'') + t.assert.strictEqual(error.code, 'FST_ERR_INIT_OPTS_INVALID') + t.assert.ok(error.stack) + t.assert.ok(true) + } +}) + +test('Original options must not be frozen', t => { + t.plan(4) + + const originalOptions = { + https: { + allowHTTP1: true, + key: global.context.key, + cert: global.context.cert + } + } + + const fastify = Fastify(originalOptions) + + t.assert.strictEqual(Object.isFrozen(originalOptions), false) + t.assert.strictEqual(Object.isFrozen(originalOptions.https), false) + t.assert.strictEqual(Object.isFrozen(fastify.initialConfig), true) + t.assert.strictEqual(Object.isFrozen(fastify.initialConfig.https), true) +}) + +test('Original options must not be altered (test deep cloning)', t => { + t.plan(3) + + const originalOptions = { + https: { + allowHTTP1: true, + key: global.context.key, + cert: global.context.cert + } + } + + const originalOptionsClone = deepClone(originalOptions) + + const fastify = Fastify(originalOptions) + + // initialConfig has been triggered + t.assert.strictEqual(Object.isFrozen(fastify.initialConfig), true) + + // originalOptions must not have been altered + t.assert.deepStrictEqual(originalOptions.https.key, originalOptionsClone.https.key) + t.assert.deepStrictEqual(originalOptions.https.cert, originalOptionsClone.https.cert) +}) + +test('Should not have issues when passing stream options to Pino.js', (t, done) => { + t.plan(17) + + const stream = split(JSON.parse) + + const originalOptions = { + ignoreTrailingSlash: true, + logger: { + level: 'trace', + stream + } + } + + let fastify + + try { + fastify = Fastify(originalOptions) + fastify.setChildLoggerFactory(function (logger, bindings, opts) { + bindings.someBinding = 'value' + return logger.child(bindings, opts) + }) + + t.assert.ok(typeof fastify === 'object') + t.assert.deepStrictEqual(fastify.initialConfig, { + connectionTimeout: 0, + keepAliveTimeout: 72000, + maxRequestsPerSocket: 0, + requestTimeout: 0, + handlerTimeout: 0, + bodyLimit: 1024 * 1024, + caseSensitive: true, + allowUnsafeRegex: false, + disableRequestLogging: false, + ignoreTrailingSlash: true, + ignoreDuplicateSlashes: false, + maxParamLength: 100, + onProtoPoisoning: 'error', + onConstructorPoisoning: 'error', + pluginTimeout: 10000, + requestIdHeader: false, + requestIdLogLabel: 'reqId', + http2SessionTimeout: 72000, + exposeHeadRoutes: true, + useSemicolonDelimiter: false + }) + } catch (error) { + t.assert.fail() + } + + fastify.get('/', function (req, reply) { + t.assert.ok(req.log) + reply.send({ hello: 'world' }) + }) + + stream.once('data', listenAtLogLine => { + t.assert.ok(listenAtLogLine, 'listen at log message is ok') + + stream.once('data', line => { + const id = line.reqId + t.assert.ok(line.reqId, 'reqId is defined') + t.assert.strictEqual(line.someBinding, 'value', 'child logger binding is set') + t.assert.ok(line.req, 'req is defined') + t.assert.strictEqual(line.msg, 'incoming request', 'message is set') + t.assert.strictEqual(line.req.method, 'GET', 'method is get') + + stream.once('data', line => { + t.assert.strictEqual(line.reqId, id) + t.assert.ok(line.reqId, 'reqId is defined') + t.assert.strictEqual(line.someBinding, 'value', 'child logger binding is set') + t.assert.ok(line.res, 'res is defined') + t.assert.strictEqual(line.msg, 'request completed', 'message is set') + t.assert.strictEqual(line.res.statusCode, 200, 'statusCode is 200') + t.assert.ok(line.responseTime, 'responseTime is defined') + }) + }) + }) + + fastify.listen({ port: 0, host: localhost }, err => { + t.assert.ifError(err) + t.after(() => { fastify.close() }) + + http.get(`http://${localhostForURL}:${fastify.server.address().port}`, () => { + done() + }) + }) +}) + +test('deepFreezeObject() should not throw on TypedArray', t => { + t.plan(5) + + const object = { + buffer: Buffer.from(global.context.key), + dataView: new DataView(new ArrayBuffer(16)), + float: 1.1, + integer: 1, + object: { + nested: { string: 'string' } + }, + stream: split(JSON.parse), + string: 'string' + } + + try { + const frozenObject = deepFreezeObject(object) + + // Buffers should not be frozen, as they are Uint8Array inherited instances + t.assert.strictEqual(Object.isFrozen(frozenObject.buffer), false) + + t.assert.strictEqual(Object.isFrozen(frozenObject), true) + t.assert.strictEqual(Object.isFrozen(frozenObject.object), true) + t.assert.strictEqual(Object.isFrozen(frozenObject.object.nested), true) + + t.assert.ok(true) + } catch (error) { + t.assert.fail() + } +}) + +test('pluginTimeout should be parsed correctly', t => { + const withDisabledTimeout = Fastify({ pluginTimeout: '0' }) + t.assert.strictEqual(withDisabledTimeout.initialConfig.pluginTimeout, 0) + const withInvalidTimeout = Fastify({ pluginTimeout: undefined }) + t.assert.strictEqual(withInvalidTimeout.initialConfig.pluginTimeout, 10000) +}) + +test('Should not mutate the options object outside Fastify', async t => { + const options = Object.freeze({}) + + try { + Fastify(options) + t.assert.ok(true) + } catch (error) { + t.assert.fail(error.message) + } +}) diff --git a/services/slides/node_modules/fastify/test/internals/logger.test.js b/services/slides/node_modules/fastify/test/internals/logger.test.js new file mode 100644 index 0000000000000000000000000000000000000000..3660b828c8f93087db8625c80e8241611ef5a088 --- /dev/null +++ b/services/slides/node_modules/fastify/test/internals/logger.test.js @@ -0,0 +1,163 @@ +'use strict' + +const { test } = require('node:test') +const Fastify = require('../..') +const loggerUtils = require('../../lib/logger-factory') +const { serializers } = require('../../lib/logger-pino') + +test('time resolution', t => { + t.plan(2) + t.assert.strictEqual(typeof loggerUtils.now, 'function') + t.assert.strictEqual(typeof loggerUtils.now(), 'number') +}) + +test('The logger should add a unique id for every request', (t, done) => { + const ids = [] + + const fastify = Fastify() + fastify.get('/', (req, reply) => { + t.assert.ok(req.id) + reply.send({ id: req.id }) + }) + + fastify.listen({ port: 0 }, err => { + t.assert.ifError(err) + const queue = new Queue() + for (let i = 0; i < 10; i++) { + queue.add(checkId) + } + queue.add(() => { + fastify.close() + done() + }) + }) + + function checkId (done) { + fastify.inject({ + method: 'GET', + url: 'http://localhost:' + fastify.server.address().port + }, (err, res) => { + t.assert.ifError(err) + const payload = JSON.parse(res.payload) + t.assert.ok(ids.indexOf(payload.id) === -1, 'the id should not be duplicated') + ids.push(payload.id) + done() + }) + } +}) + +test('The logger should not reuse request id header for req.id', (t, done) => { + const fastify = Fastify() + fastify.get('/', (req, reply) => { + t.assert.ok(req.id) + reply.send({ id: req.id }) + }) + + fastify.listen({ port: 0 }, err => { + t.assert.ifError(err) + + fastify.inject({ + method: 'GET', + url: 'http://localhost:' + fastify.server.address().port, + headers: { + 'Request-Id': 'request-id-1' + } + }, (err, res) => { + t.assert.ifError(err) + const payload = JSON.parse(res.payload) + t.assert.ok(payload.id !== 'request-id-1', 'the request id from the header should not be returned with default configuration') + t.assert.ok(payload.id === 'req-1') // first request id when using the default configuration + fastify.close() + done() + }) + }) +}) + +test('The logger should reuse request id header for req.id if requestIdHeader is set', (t, done) => { + const fastify = Fastify({ + requestIdHeader: 'request-id' + }) + fastify.get('/', (req, reply) => { + t.assert.ok(req.id) + reply.send({ id: req.id }) + }) + + fastify.listen({ port: 0 }, err => { + t.assert.ifError(err) + + fastify.inject({ + method: 'GET', + url: 'http://localhost:' + fastify.server.address().port, + headers: { + 'Request-Id': 'request-id-1' + } + }, (err, res) => { + t.assert.ifError(err) + const payload = JSON.parse(res.payload) + t.assert.ok(payload.id === 'request-id-1', 'the request id from the header should be returned') + fastify.close() + done() + }) + }) +}) + +function Queue () { + this.q = [] + this.running = false +} + +Queue.prototype.add = function add (job) { + this.q.push(job) + if (!this.running) this.run() +} + +Queue.prototype.run = function run () { + this.running = true + const job = this.q.shift() + job(() => { + if (this.q.length) { + this.run() + } else { + this.running = false + } + }) +} + +test('The logger should error if both stream and file destination are given', t => { + t.plan(2) + + const stream = require('node:stream').Writable + + try { + Fastify({ + logger: { + level: 'info', + stream, + file: '/test' + } + }) + } catch (err) { + t.assert.strictEqual(err.code, 'FST_ERR_LOG_INVALID_DESTINATION') + t.assert.strictEqual(err.message, 'Cannot specify both logger.stream and logger.file options') + } +}) + +test('The serializer prevent fails if the request socket is undefined', t => { + t.plan(1) + + const serialized = serializers.req({ + method: 'GET', + url: '/', + socket: undefined, + headers: {} + }) + + t.assert.deepStrictEqual(serialized, { + method: 'GET', + url: '/', + version: undefined, + host: undefined, + remoteAddress: undefined, + remotePort: undefined + }) +}) diff --git a/services/slides/node_modules/fastify/test/internals/plugin.test.js b/services/slides/node_modules/fastify/test/internals/plugin.test.js new file mode 100644 index 0000000000000000000000000000000000000000..d3bd3415b24a6f1461e2406375f7994939d92b10 --- /dev/null +++ b/services/slides/node_modules/fastify/test/internals/plugin.test.js @@ -0,0 +1,170 @@ +'use strict' + +const { test } = require('node:test') + +const pluginUtilsPublic = require('../../lib/plugin-utils.js') +const symbols = require('../../lib/symbols.js') +const pluginUtils = require('../../lib/plugin-utils')[symbols.kTestInternals] + +test("shouldSkipOverride should check the 'skip-override' symbol", t => { + t.plan(2) + + yes[Symbol.for('skip-override')] = true + + t.assert.ok(pluginUtils.shouldSkipOverride(yes)) + t.assert.ok(!pluginUtils.shouldSkipOverride(no)) + + function yes () {} + function no () {} +}) + +test('getPluginName should return plugin name if the file is cached', t => { + t.plan(1) + const expectedPluginName = 'example' + const fn = () => console.log('is just an example') + require.cache[expectedPluginName] = { exports: fn } + const pluginName = pluginUtilsPublic.getPluginName(fn) + + t.assert.strictEqual(pluginName, expectedPluginName) +}) + +test('getPluginName should not throw when require.cache is undefined', t => { + t.plan(1) + function example () { + console.log('is just an example') + } + const cache = require.cache + require.cache = undefined + t.after(() => { + require.cache = cache + }) + const pluginName = pluginUtilsPublic.getPluginName(example) + + t.assert.strictEqual(pluginName, 'example') +}) + +test("getMeta should return the object stored with the 'plugin-meta' symbol", t => { + t.plan(1) + + const meta = { hello: 'world' } + fn[Symbol.for('plugin-meta')] = meta + + t.assert.deepStrictEqual(meta, pluginUtils.getMeta(fn)) + + function fn () {} +}) + +test('checkDecorators should check if the given decorator is present in the instance', t => { + t.plan(1) + + fn[Symbol.for('plugin-meta')] = { + decorators: { + fastify: ['plugin'], + reply: ['plugin'], + request: ['plugin'] + } + } + + function context () {} + context.plugin = true + context[symbols.kReply] = { prototype: { plugin: true }, props: [] } + context[symbols.kRequest] = { prototype: { plugin: true }, props: [] } + + try { + pluginUtils.checkDecorators.call(context, fn) + t.assert.ok('Everything ok') + } catch (err) { + t.assert.fail(err) + } + + function fn () {} +}) + +test('checkDecorators should check if the given decorator is present in the instance (errored)', t => { + t.plan(1) + + fn[Symbol.for('plugin-meta')] = { + decorators: { + fastify: ['plugin'], + reply: ['plugin'], + request: ['plugin'] + } + } + + function context () {} + context.plugin = true + context[symbols.kReply] = { prototype: { plugin: true }, props: [] } + context[symbols.kRequest] = { prototype: {}, props: [] } + + try { + pluginUtils.checkDecorators.call(context, fn) + t.assert.fail('should throw') + } catch (err) { + t.assert.strictEqual(err.message, "The decorator 'plugin' is not present in Request") + } + + function fn () {} +}) + +test('checkDecorators should accept optional decorators', t => { + t.plan(1) + + fn[Symbol.for('plugin-meta')] = { + decorators: { } + } + + function context () {} + context.plugin = true + context[symbols.kReply] = { prototype: { plugin: true } } + context[symbols.kRequest] = { prototype: { plugin: true } } + + try { + pluginUtils.checkDecorators.call(context, fn) + t.assert.ok('Everything ok') + } catch (err) { + t.assert.fail(err) + } + + function fn () {} +}) + +test('checkDependencies should check if the given dependency is present in the instance', t => { + t.plan(1) + + fn[Symbol.for('plugin-meta')] = { + dependencies: ['plugin'] + } + + function context () {} + context[pluginUtilsPublic.kRegisteredPlugins] = ['plugin'] + + try { + pluginUtils.checkDependencies.call(context, fn) + t.assert.ok('Everything ok') + } catch (err) { + t.assert.fail(err) + } + + function fn () {} +}) + +test('checkDependencies should check if the given dependency is present in the instance (errored)', t => { + t.plan(1) + + fn[Symbol.for('plugin-meta')] = { + name: 'test-plugin', + dependencies: ['plugin'] + } + + function context () {} + context[pluginUtilsPublic.kRegisteredPlugins] = [] + + try { + pluginUtils.checkDependencies.call(context, fn) + t.assert.fail('should throw') + } catch (err) { + t.assert.strictEqual(err.message, "The dependency 'plugin' of plugin 'test-plugin' is not registered") + } + + function fn () {} +}) diff --git a/services/slides/node_modules/fastify/test/internals/promise.test.js b/services/slides/node_modules/fastify/test/internals/promise.test.js new file mode 100644 index 0000000000000000000000000000000000000000..f1b06c5753e432a5da9525f37cc9cdeeceb18149 --- /dev/null +++ b/services/slides/node_modules/fastify/test/internals/promise.test.js @@ -0,0 +1,63 @@ +'use strict' + +const { test } = require('node:test') + +const { kTestInternals } = require('../../lib/symbols') +const PonyPromise = require('../../lib/promise') + +test('withResolvers', async (t) => { + t.plan(3) + await t.test('resolve', async (t) => { + t.plan(1) + const { promise, resolve } = PonyPromise.withResolvers() + resolve(true) + t.assert.ok(await promise) + }) + await t.test('reject', async (t) => { + t.plan(1) + const { promise, reject } = PonyPromise.withResolvers() + await t.assert.rejects(async () => { + reject(Error('reject')) + return promise + }, { + name: 'Error', + message: 'reject' + }) + }) + await t.test('thenable', async (t) => { + t.plan(1) + const { promise, resolve } = PonyPromise.withResolvers() + resolve(true) + promise.then((value) => { + t.assert.ok(value) + }) + }) +}) + +test('withResolvers - ponyfill', async (t) => { + await t.test('resolve', async (t) => { + t.plan(1) + const { promise, resolve } = PonyPromise[kTestInternals].withResolvers() + resolve(true) + t.assert.ok(await promise) + }) + await t.test('reject', async (t) => { + t.plan(1) + const { promise, reject } = PonyPromise[kTestInternals].withResolvers() + await t.assert.rejects(async () => { + reject(Error('reject')) + return promise + }, { + name: 'Error', + message: 'reject' + }) + }) + await t.test('thenable', async (t) => { + t.plan(1) + const { promise, resolve } = PonyPromise.withResolvers() + resolve(true) + promise.then((value) => { + t.assert.ok(value) + }) + }) +}) diff --git a/services/slides/node_modules/fastify/test/internals/reply-serialize.test.js b/services/slides/node_modules/fastify/test/internals/reply-serialize.test.js new file mode 100644 index 0000000000000000000000000000000000000000..24606d39808b769706d0fad676f2e384dd9fe33d --- /dev/null +++ b/services/slides/node_modules/fastify/test/internals/reply-serialize.test.js @@ -0,0 +1,714 @@ +'use strict' + +const { test } = require('node:test') +const { kReplyCacheSerializeFns, kRouteContext } = require('../../lib/symbols') +const Fastify = require('../../fastify') + +function getDefaultSchema () { + return { + type: 'object', + required: ['hello'], + properties: { + hello: { type: 'string' }, + world: { type: 'string' } + } + } +} + +function getResponseSchema () { + return { + 201: { + type: 'object', + required: ['status'], + properties: { + status: { + type: 'string', + enum: ['ok'] + }, + message: { + type: 'string' + } + } + }, + '4xx': { + type: 'object', + properties: { + status: { + type: 'string', + enum: ['error'] + }, + code: { + type: 'integer', + minimum: 1 + }, + message: { + type: 'string' + } + } + }, + '3xx': { + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + fullName: { type: 'string' }, + phone: { type: 'number' } + } + } + } + } + } + } +} + +test('Reply#compileSerializationSchema', async t => { + t.plan(4) + + await t.test('Should return a serialization function', async t => { + const fastify = Fastify() + + t.plan(4) + + fastify.get('/', (req, reply) => { + const serialize = reply.compileSerializationSchema(getDefaultSchema()) + const input = { hello: 'world' } + t.assert.ok(serialize instanceof Function) + t.assert.ok(typeof serialize(input) === 'string') + t.assert.strictEqual(serialize(input), JSON.stringify(input)) + + try { + serialize({ world: 'foo' }) + } catch (err) { + t.assert.strictEqual(err.message, '"hello" is required!') + } + + reply.send({ hello: 'world' }) + }) + + await fastify.inject({ + path: '/', + method: 'GET' + }) + }) + + await t.test('Should reuse the serialize fn across multiple invocations - Route without schema', + async t => { + const fastify = Fastify() + let serialize = null + let counter = 0 + + t.plan(17) + + const schemaObj = getDefaultSchema() + + fastify.get('/', (req, reply) => { + const input = { hello: 'world' } + counter++ + if (counter > 1) { + const newSerialize = reply.compileSerializationSchema(schemaObj) + t.assert.strictEqual(serialize, newSerialize, 'Are the same validate function') + serialize = newSerialize + } else { + t.assert.ok(true, 'build the schema compilation function') + serialize = reply.compileSerializationSchema(schemaObj) + } + + t.assert.ok(serialize instanceof Function) + t.assert.strictEqual(serialize(input), JSON.stringify(input)) + + try { + serialize({ world: 'foo' }) + } catch (err) { + t.assert.strictEqual(err.message, '"hello" is required!') + } + + reply.send({ hello: 'world' }) + }) + + await Promise.all([ + fastify.inject('/'), + fastify.inject('/'), + fastify.inject('/'), + fastify.inject('/') + ]) + + t.assert.strictEqual(counter, 4) + } + ) + + await t.test('Should use the custom serializer compiler for the route', + async t => { + const fastify = Fastify() + let called = 0 + const custom = ({ schema, httpStatus, url, method }) => { + t.assert.strictEqual(schema, schemaObj) + t.assert.strictEqual(url, '/') + t.assert.strictEqual(method, 'GET') + t.assert.strictEqual(httpStatus, '201') + + return input => { + called++ + t.assert.deepStrictEqual(input, { hello: 'world' }) + return JSON.stringify(input) + } + } + + const custom2 = ({ schema, httpStatus, url, method, contentType }) => { + t.assert.strictEqual(schema, schemaObj) + t.assert.strictEqual(url, '/user') + t.assert.strictEqual(method, 'GET') + t.assert.strictEqual(httpStatus, '3xx') + t.assert.strictEqual(contentType, 'application/json') + + return input => { + t.assert.deepStrictEqual(input, { fullName: 'Jone', phone: 1090243795 }) + return JSON.stringify(input) + } + } + + t.plan(17) + const schemaObj = getDefaultSchema() + + fastify.get('/', { serializerCompiler: custom }, (req, reply) => { + const input = { hello: 'world' } + const first = reply.compileSerializationSchema(schemaObj, '201') + const second = reply.compileSerializationSchema(schemaObj, '201') + + t.assert.strictEqual(first, second) + t.assert.ok(first(input), JSON.stringify(input)) + t.assert.ok(second(input), JSON.stringify(input)) + t.assert.strictEqual(called, 2) + + reply.send({ hello: 'world' }) + }) + + fastify.get('/user', { serializerCompiler: custom2 }, (req, reply) => { + const input = { fullName: 'Jone', phone: 1090243795 } + const first = reply.compileSerializationSchema(schemaObj, '3xx', 'application/json') + t.assert.ok(first(input), JSON.stringify(input)) + reply.send(input) + }) + + await fastify.inject({ + path: '/', + method: 'GET' + }) + + await fastify.inject({ + path: '/user', + method: 'GET' + }) + } + ) + + await t.test('Should build a WeakMap for cache when called', async t => { + const fastify = Fastify() + + t.plan(4) + + fastify.get('/', (req, reply) => { + const input = { hello: 'world' } + + t.assert.strictEqual(reply[kRouteContext][kReplyCacheSerializeFns], null) + t.assert.strictEqual(reply.compileSerializationSchema(getDefaultSchema())(input), JSON.stringify(input)) + t.assert.ok(reply[kRouteContext][kReplyCacheSerializeFns] instanceof WeakMap) + t.assert.strictEqual(reply.compileSerializationSchema(getDefaultSchema())(input), JSON.stringify(input)) + + reply.send({ hello: 'world' }) + }) + + await fastify.inject({ + path: '/', + method: 'GET' + }) + }) +}) + +test('Reply#getSerializationFunction', async t => { + t.plan(3) + + await t.test('Should retrieve the serialization function from the Schema definition', + async t => { + const fastify = Fastify() + const okInput201 = { + status: 'ok', + message: 'done!' + } + const notOkInput201 = { + message: 'created' + } + const okInput4xx = { + status: 'error', + code: 2, + message: 'oops!' + } + const notOkInput4xx = { + status: 'error', + code: 'something' + } + const okInput3xx = { + fullName: 'Jone', + phone: 0 + } + const noOkInput3xx = { + fullName: 'Jone', + phone: 'phone' + } + let cached4xx + let cached201 + let cachedJson3xx + + t.plan(13) + + const responseSchema = getResponseSchema() + + fastify.get( + '/:id', + { + params: { + type: 'object', + properties: { + id: { + type: 'integer' + } + } + }, + schema: { + response: responseSchema + } + }, + (req, reply) => { + const { id } = req.params + + if (Number(id) === 1) { + const serialize4xx = reply.getSerializationFunction('4xx') + const serialize201 = reply.getSerializationFunction(201) + const serializeJson3xx = reply.getSerializationFunction('3xx', 'application/json') + const serializeUndefined = reply.getSerializationFunction(undefined) + + cached4xx = serialize4xx + cached201 = serialize201 + cachedJson3xx = serializeJson3xx + + t.assert.ok(serialize4xx instanceof Function) + t.assert.ok(serialize201 instanceof Function) + t.assert.ok(serializeJson3xx instanceof Function) + t.assert.strictEqual(serialize4xx(okInput4xx), JSON.stringify(okInput4xx)) + t.assert.strictEqual(serialize201(okInput201), JSON.stringify(okInput201)) + t.assert.strictEqual(serializeJson3xx(okInput3xx), JSON.stringify(okInput3xx)) + t.assert.ok(!serializeUndefined) + + try { + serialize4xx(notOkInput4xx) + } catch (err) { + t.assert.strictEqual( + err.message, + 'The value "something" cannot be converted to an integer.' + ) + } + + try { + serialize201(notOkInput201) + } catch (err) { + t.assert.strictEqual(err.message, '"status" is required!') + } + + try { + serializeJson3xx(noOkInput3xx) + } catch (err) { + t.assert.strictEqual(err.message, 'The value "phone" cannot be converted to a number.') + } + + reply.status(201).send(okInput201) + } else { + const serialize201 = reply.getSerializationFunction(201) + const serialize4xx = reply.getSerializationFunction('4xx') + const serializeJson3xx = reply.getSerializationFunction('3xx', 'application/json') + + t.assert.strictEqual(serialize4xx, cached4xx) + t.assert.strictEqual(serialize201, cached201) + t.assert.strictEqual(serializeJson3xx, cachedJson3xx) + reply.status(401).send(okInput4xx) + } + } + ) + + await Promise.all([ + fastify.inject('/1'), + fastify.inject('/2') + ]) + } + ) + + await t.test('Should retrieve the serialization function from the cached one', + async t => { + const fastify = Fastify() + + const schemaObj = getDefaultSchema() + + const okInput = { + hello: 'world', + world: 'done!' + } + const notOkInput = { + world: 'done!' + } + let cached + + t.plan(6) + + fastify.get( + '/:id', + { + params: { + type: 'object', + properties: { + id: { + type: 'integer' + } + } + } + }, + (req, reply) => { + const { id } = req.params + + if (Number(id) === 1) { + const serialize = reply.compileSerializationSchema(schemaObj) + + t.assert.ok(serialize instanceof Function) + t.assert.strictEqual(serialize(okInput), JSON.stringify(okInput)) + + try { + serialize(notOkInput) + } catch (err) { + t.assert.strictEqual(err.message, '"hello" is required!') + } + + cached = serialize + } else { + const serialize = reply.getSerializationFunction(schemaObj) + + t.assert.strictEqual(serialize, cached) + t.assert.strictEqual(serialize(okInput), JSON.stringify(okInput)) + + try { + serialize(notOkInput) + } catch (err) { + t.assert.strictEqual(err.message, '"hello" is required!') + } + } + + reply.status(201).send(okInput) + } + ) + + await Promise.all([ + fastify.inject('/1'), + fastify.inject('/2') + ]) + } + ) + + await t.test('Should not instantiate a WeakMap if it is not needed', async t => { + const fastify = Fastify() + + t.plan(4) + + fastify.get('/', (req, reply) => { + t.assert.ok(!reply.getSerializationFunction(getDefaultSchema())) + t.assert.strictEqual(reply[kRouteContext][kReplyCacheSerializeFns], null) + t.assert.ok(!reply.getSerializationFunction('200')) + t.assert.strictEqual(reply[kRouteContext][kReplyCacheSerializeFns], null) + + reply.send({ hello: 'world' }) + }) + + await fastify.inject({ + path: '/', + method: 'GET' + }) + }) +}) + +test('Reply#serializeInput', async t => { + t.plan(6) + + await t.test( + 'Should throw if missed serialization function from HTTP status', + async t => { + const fastify = Fastify() + + t.plan(2) + + fastify.get('/', (req, reply) => { + reply.serializeInput({}, 201) + }) + + const result = await fastify.inject({ + path: '/', + method: 'GET' + }) + + t.assert.strictEqual(result.statusCode, 500) + t.assert.deepStrictEqual(result.json(), { + statusCode: 500, + code: 'FST_ERR_MISSING_SERIALIZATION_FN', + error: 'Internal Server Error', + message: 'Missing serialization function. Key "201"' + }) + } + ) + + await t.test( + 'Should throw if missed serialization function from HTTP status with specific content type', + async t => { + const fastify = Fastify() + + t.plan(2) + + fastify.get('/', { + schema: { + response: { + '3xx': { + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + fullName: { type: 'string' }, + phone: { type: 'number' } + } + } + } + } + } + } + } + }, (req, reply) => { + reply.serializeInput({}, '3xx', 'application/vnd.v1+json') + }) + + const result = await fastify.inject({ + path: '/', + method: 'GET' + }) + + t.assert.strictEqual(result.statusCode, 500) + t.assert.deepStrictEqual(result.json(), { + statusCode: 500, + code: 'FST_ERR_MISSING_CONTENTTYPE_SERIALIZATION_FN', + error: 'Internal Server Error', + message: 'Missing serialization function. Key "3xx:application/vnd.v1+json"' + }) + } + ) + + await t.test('Should use a serializer fn from HTTP status', async t => { + const fastify = Fastify() + const okInput201 = { + status: 'ok', + message: 'done!' + } + const notOkInput201 = { + message: 'created' + } + const okInput4xx = { + status: 'error', + code: 2, + message: 'oops!' + } + const notOkInput4xx = { + status: 'error', + code: 'something' + } + const okInput3xx = { + fullName: 'Jone', + phone: 0 + } + const noOkInput3xx = { + fullName: 'Jone', + phone: 'phone' + } + + t.plan(6) + + fastify.get( + '/', + { + params: { + type: 'object', + properties: { + id: { + type: 'integer' + } + } + }, + schema: { + response: getResponseSchema() + } + }, + (req, reply) => { + t.assert.strictEqual( + reply.serializeInput(okInput4xx, '4xx'), + JSON.stringify(okInput4xx) + ) + t.assert.strictEqual( + reply.serializeInput(okInput201, 201), + JSON.stringify(okInput201) + ) + + t.assert.strictEqual( + reply.serializeInput(okInput3xx, {}, '3xx', 'application/json'), + JSON.stringify(okInput3xx) + ) + + try { + reply.serializeInput(noOkInput3xx, '3xx', 'application/json') + } catch (err) { + t.assert.strictEqual(err.message, 'The value "phone" cannot be converted to a number.') + } + + try { + reply.serializeInput(notOkInput4xx, '4xx') + } catch (err) { + t.assert.strictEqual( + err.message, + 'The value "something" cannot be converted to an integer.' + ) + } + + try { + reply.serializeInput(notOkInput201, 201) + } catch (err) { + t.assert.strictEqual(err.message, '"status" is required!') + } + + reply.status(204).send('') + } + ) + + await fastify.inject({ + path: '/', + method: 'GET' + }) + }) + + await t.test( + 'Should compile a serializer out of a schema if serializer fn missed', + async t => { + let compilerCalled = 0 + let serializerCalled = 0 + const testInput = { hello: 'world' } + const schemaObj = getDefaultSchema() + const fastify = Fastify() + const serializerCompiler = ({ schema, httpStatus, method, url }) => { + t.assert.strictEqual(schema, schemaObj) + t.assert.ok(!httpStatus) + t.assert.strictEqual(method, 'GET') + t.assert.strictEqual(url, '/') + + compilerCalled++ + return input => { + t.assert.strictEqual(input, testInput) + serializerCalled++ + return JSON.stringify(input) + } + } + + t.plan(10) + + fastify.get('/', { serializerCompiler }, (req, reply) => { + t.assert.strictEqual( + reply.serializeInput(testInput, schemaObj), + JSON.stringify(testInput) + ) + + t.assert.strictEqual( + reply.serializeInput(testInput, schemaObj), + JSON.stringify(testInput) + ) + + reply.status(201).send(testInput) + }) + + await fastify.inject({ + path: '/', + method: 'GET' + }) + + t.assert.strictEqual(compilerCalled, 1) + t.assert.strictEqual(serializerCalled, 2) + } + ) + + await t.test('Should use a cached serializer fn', async t => { + let compilerCalled = 0 + let serializerCalled = 0 + let cached + const testInput = { hello: 'world' } + const schemaObj = getDefaultSchema() + const fastify = Fastify() + const serializer = input => { + t.assert.strictEqual(input, testInput) + serializerCalled++ + return JSON.stringify(input) + } + const serializerCompiler = ({ schema, httpStatus, method, url }) => { + t.assert.strictEqual(schema, schemaObj) + t.assert.ok(!httpStatus) + t.assert.strictEqual(method, 'GET') + t.assert.strictEqual(url, '/') + + compilerCalled++ + return serializer + } + + t.plan(12) + + fastify.get('/', { serializerCompiler }, (req, reply) => { + t.assert.strictEqual( + reply.serializeInput(testInput, schemaObj), + JSON.stringify(testInput) + ) + + cached = reply.getSerializationFunction(schemaObj) + + t.assert.strictEqual( + reply.serializeInput(testInput, schemaObj), + cached(testInput) + ) + + reply.status(201).send(testInput) + }) + + await fastify.inject({ + path: '/', + method: 'GET' + }) + + t.assert.strictEqual(cached, serializer) + t.assert.strictEqual(compilerCalled, 1) + t.assert.strictEqual(serializerCalled, 3) + }) + + await t.test('Should instantiate a WeakMap after first call', async t => { + const fastify = Fastify() + + t.plan(3) + + fastify.get('/', (req, reply) => { + const input = { hello: 'world' } + t.assert.strictEqual(reply[kRouteContext][kReplyCacheSerializeFns], null) + t.assert.strictEqual(reply.serializeInput(input, getDefaultSchema()), JSON.stringify(input)) + t.assert.ok(reply[kRouteContext][kReplyCacheSerializeFns] instanceof WeakMap) + + reply.send({ hello: 'world' }) + }) + + await fastify.inject({ + path: '/', + method: 'GET' + }) + }) +}) diff --git a/services/slides/node_modules/fastify/test/internals/reply.test.js b/services/slides/node_modules/fastify/test/internals/reply.test.js new file mode 100644 index 0000000000000000000000000000000000000000..adfc352dc31009452a160320ca972b2b79046ad2 --- /dev/null +++ b/services/slides/node_modules/fastify/test/internals/reply.test.js @@ -0,0 +1,1920 @@ +'use strict' + +const { test } = require('node:test') +const http = require('node:http') +const NotFound = require('http-errors').NotFound +const Request = require('../../lib/request') +const Reply = require('../../lib/reply') +const Fastify = require('../..') +const { Readable, Writable } = require('node:stream') +const { + kReplyErrorHandlerCalled, + kReplyHeaders, + kReplySerializer, + kReplyIsError, + kReplySerializerDefault, + kRouteContext +} = require('../../lib/symbols') +const fs = require('node:fs') +const path = require('node:path') + +const doGet = async function (url) { + const result = await fetch(url, { + method: 'GET', + redirect: 'manual', + keepAlive: false + }) + + return { + response: result, + body: await result.json().catch(() => undefined) + } +} + +test('Once called, Reply should return an object with methods', t => { + t.plan(15) + const response = { res: 'res' } + const context = { + config: { onSend: [] }, + schema: {}, + _parserOptions: {}, + server: { hasConstraintStrategy: () => false, initialConfig: {} } + } + const request = new Request(null, null, null, null, null, context) + const reply = new Reply(response, request) + t.assert.strictEqual(typeof reply, 'object') + t.assert.strictEqual(typeof reply[kReplyIsError], 'boolean') + t.assert.strictEqual(typeof reply[kReplyErrorHandlerCalled], 'boolean') + t.assert.strictEqual(typeof reply.send, 'function') + t.assert.strictEqual(typeof reply.code, 'function') + t.assert.strictEqual(typeof reply.status, 'function') + t.assert.strictEqual(typeof reply.header, 'function') + t.assert.strictEqual(typeof reply.serialize, 'function') + t.assert.strictEqual(typeof reply[kReplyHeaders], 'object') + t.assert.deepStrictEqual(reply.raw, response) + t.assert.strictEqual(reply[kRouteContext], context) + t.assert.strictEqual(reply.routeOptions.config, context.config) + t.assert.strictEqual(reply.routeOptions.schema, context.schema) + t.assert.strictEqual(reply.request, request) + // Aim to not bad property keys (including Symbols) + t.assert.ok(!('undefined' in reply)) +}) + +test('reply.send will logStream error and destroy the stream', t => { + t.plan(1) + let destroyCalled + const payload = new Readable({ + read () { }, + destroy (err, cb) { + destroyCalled = true + cb(err) + } + }) + + const response = new Writable() + Object.assign(response, { + setHeader: () => { }, + hasHeader: () => false, + getHeader: () => undefined, + writeHead: () => { }, + write: () => { }, + headersSent: true + }) + + const log = { + warn: () => { } + } + + const reply = new Reply(response, { [kRouteContext]: { onSend: null } }, log) + reply.send(payload) + payload.destroy(new Error('stream error')) + + t.assert.strictEqual(destroyCalled, true, 'Error not logged and not streamed') +}) + +test('reply.send throw with circular JSON', t => { + t.plan(1) + const response = { + setHeader: () => { }, + hasHeader: () => false, + getHeader: () => undefined, + writeHead: () => { }, + write: () => { }, + end: () => { } + } + const reply = new Reply(response, { [kRouteContext]: { onSend: [] } }) + t.assert.throws(() => { + const obj = {} + obj.obj = obj + reply.send(JSON.stringify(obj)) + }, 'Converting circular structure to JSON') +}) + +test('reply.send returns itself', t => { + t.plan(1) + const response = { + setHeader: () => { }, + hasHeader: () => false, + getHeader: () => undefined, + writeHead: () => { }, + write: () => { }, + end: () => { } + } + const reply = new Reply(response, { [kRouteContext]: { onSend: [] } }) + t.assert.strictEqual(reply.send('hello'), reply) +}) + +test('reply.serializer should set a custom serializer', t => { + t.plan(2) + const reply = new Reply(null, null, null) + t.assert.strictEqual(reply[kReplySerializer], null) + reply.serializer('serializer') + t.assert.strictEqual(reply[kReplySerializer], 'serializer') +}) + +test('reply.serializer should support running preSerialization hooks', (t, done) => { + t.plan(3) + const fastify = Fastify() + t.after(() => fastify.close()) + + fastify.addHook('preSerialization', async (request, reply, payload) => { t.assert.ok('called', 'preSerialization') }) + fastify.route({ + method: 'GET', + url: '/', + handler: (req, reply) => { + reply + .type('application/json') + .serializer(JSON.stringify) + .send({ foo: 'bar' }) + } + }) + + fastify.inject({ + method: 'GET', + url: '/' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.payload, '{"foo":"bar"}') + done() + }) +}) + +test('reply.serialize should serialize payload', t => { + t.plan(1) + const response = { statusCode: 200 } + const context = {} + const reply = new Reply(response, { [kRouteContext]: context }) + t.assert.strictEqual(reply.serialize({ foo: 'bar' }), '{"foo":"bar"}') +}) + +test('reply.serialize should serialize payload with a custom serializer', t => { + t.plan(2) + let customSerializerCalled = false + const response = { statusCode: 200 } + const context = {} + const reply = new Reply(response, { [kRouteContext]: context }) + reply.serializer((x) => (customSerializerCalled = true) && JSON.stringify(x)) + t.assert.strictEqual(reply.serialize({ foo: 'bar' }), '{"foo":"bar"}') + t.assert.strictEqual(customSerializerCalled, true, 'custom serializer not called') +}) + +test('reply.serialize should serialize payload with a context default serializer', t => { + t.plan(2) + let customSerializerCalled = false + const response = { statusCode: 200 } + const context = { [kReplySerializerDefault]: (x) => (customSerializerCalled = true) && JSON.stringify(x) } + const reply = new Reply(response, { [kRouteContext]: context }) + t.assert.strictEqual(reply.serialize({ foo: 'bar' }), '{"foo":"bar"}') + t.assert.strictEqual(customSerializerCalled, true, 'custom serializer not called') +}) + +test('reply.serialize should serialize payload with Fastify instance', (t, done) => { + t.plan(2) + const fastify = Fastify() + t.after(() => fastify.close()) + fastify.route({ + method: 'GET', + url: '/', + schema: { + response: { + 200: { + type: 'object', + properties: { + foo: { type: 'string' } + } + } + } + }, + handler: (req, reply) => { + reply.send( + reply.serialize({ foo: 'bar' }) + ) + } + }) + + fastify.inject({ + method: 'GET', + url: '/' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.payload, '{"foo":"bar"}') + done() + }) +}) + +test('within an instance', async t => { + const fastify = Fastify() + t.after(() => fastify.close()) + + fastify.get('/', function (req, reply) { + reply.code(200) + reply.header('Content-Type', 'text/plain') + reply.send('hello world!') + }) + + fastify.get('/auto-type', function (req, reply) { + reply.code(200) + reply.type('text/plain') + reply.send('hello world!') + }) + + fastify.get('/auto-status-code', function (req, reply) { + reply.send('hello world!') + }) + + fastify.get('/redirect', function (req, reply) { + reply.redirect('/') + }) + + fastify.get('/redirect-async', async function (req, reply) { + return reply.redirect('/') + }) + + fastify.get('/redirect-code', function (req, reply) { + reply.redirect('/', 301) + }) + + fastify.get('/redirect-code-before-call', function (req, reply) { + reply.code(307).redirect('/') + }) + + fastify.get('/redirect-code-before-call-overwrite', function (req, reply) { + reply.code(307).redirect('/', 302) + }) + + fastify.get('/custom-serializer', function (req, reply) { + reply.code(200) + reply.type('text/plain') + reply.serializer(function (body) { + return require('node:querystring').stringify(body) + }) + reply.send({ hello: 'world!' }) + }) + + fastify.register(function (instance, options, done) { + fastify.addHook('onSend', function (req, reply, payload, done) { + reply.header('x-onsend', 'yes') + done() + }) + fastify.get('/redirect-onsend', function (req, reply) { + reply.redirect('/') + }) + done() + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + + await t.test('custom serializer should be used', async t => { + t.plan(3) + const result = await fetch(fastifyServer + '/custom-serializer') + t.assert.ok(result.ok) + t.assert.strictEqual(result.headers.get('content-type'), 'text/plain') + t.assert.deepStrictEqual(await result.text(), 'hello=world!') + }) + + await t.test('status code and content-type should be correct', async t => { + t.plan(3) + const result = await fetch(fastifyServer) + t.assert.ok(result.ok) + t.assert.strictEqual(result.headers.get('content-type'), 'text/plain') + t.assert.deepStrictEqual(await result.text(), 'hello world!') + }) + + await t.test('auto status code should be 200', async t => { + t.plan(3) + const result = await fetch(fastifyServer + '/auto-status-code') + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.deepStrictEqual(await result.text(), 'hello world!') + }) + + await t.test('auto type should be text/plain', async t => { + t.plan(3) + const result = await fetch(fastifyServer + '/auto-type') + t.assert.ok(result.ok) + t.assert.strictEqual(result.headers.get('content-type'), 'text/plain') + t.assert.deepStrictEqual(await result.text(), 'hello world!') + }) + + await t.test('redirect to `/` - 1', (t, done) => { + t.plan(1) + + http.get(fastifyServer + '/redirect', function (response) { + t.assert.strictEqual(response.statusCode, 302) + done() + }) + }) + + await t.test('redirect to `/` - 2', (t, done) => { + t.plan(1) + + http.get(fastifyServer + '/redirect-code', function (response) { + t.assert.strictEqual(response.statusCode, 301) + done() + }) + }) + + await t.test('redirect to `/` - 3', async t => { + t.plan(4) + const result = await fetch(fastifyServer + '/redirect') + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.strictEqual(result.headers.get('content-type'), 'text/plain') + t.assert.deepStrictEqual(await result.text(), 'hello world!') + }) + + await t.test('redirect to `/` - 4', async t => { + t.plan(4) + const result = await fetch(fastifyServer + '/redirect-code') + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.strictEqual(result.headers.get('content-type'), 'text/plain') + t.assert.deepStrictEqual(await result.text(), 'hello world!') + }) + + await t.test('redirect to `/` - 5', (t, done) => { + t.plan(3) + const url = fastifyServer + '/redirect-onsend' + http.get(url, (response) => { + t.assert.strictEqual(response.headers['x-onsend'], 'yes') + t.assert.strictEqual(response.headers['content-length'], '0') + t.assert.strictEqual(response.headers.location, '/') + done() + }) + }) + + await t.test('redirect to `/` - 6', async t => { + t.plan(4) + const result = await fetch(fastifyServer + '/redirect-code-before-call') + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.strictEqual(result.headers.get('content-type'), 'text/plain') + t.assert.deepStrictEqual(await result.text(), 'hello world!') + }) + + await t.test('redirect to `/` - 7', async t => { + t.plan(4) + const result = await fetch(fastifyServer + '/redirect-code-before-call-overwrite') + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.strictEqual(result.headers.get('content-type'), 'text/plain') + t.assert.deepStrictEqual(await result.text(), 'hello world!') + }) + + await t.test('redirect to `/` - 8', (t, done) => { + t.plan(1) + + http.get(fastifyServer + '/redirect-code-before-call', function (response) { + t.assert.strictEqual(response.statusCode, 307) + done() + }) + }) + + await t.test('redirect to `/` - 9', (t, done) => { + t.plan(1) + + http.get(fastifyServer + '/redirect-code-before-call-overwrite', function (response) { + t.assert.strictEqual(response.statusCode, 302) + done() + }) + }) + + await t.test('redirect with async function to `/` - 10', (t, done) => { + t.plan(1) + + http.get(fastifyServer + '/redirect-async', function (response) { + t.assert.strictEqual(response.statusCode, 302) + done() + }) + }) +}) + +test('buffer without content type should send a application/octet-stream and raw buffer', async t => { + t.plan(3) + + const fastify = Fastify() + + fastify.get('/', function (req, reply) { + reply.send(Buffer.alloc(1024)) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const result = await fetch(fastifyServer) + t.assert.ok(result.ok) + t.assert.strictEqual(result.headers.get('content-type'), 'application/octet-stream') + t.assert.deepStrictEqual(Buffer.from(await result.arrayBuffer()), Buffer.alloc(1024)) +}) + +test('Uint8Array without content type should send a application/octet-stream and raw buffer', (t, done) => { + t.plan(4) + + const fastify = Fastify() + + fastify.get('/', function (req, reply) { + reply.send(new Uint8Array(1024).fill(0xff)) + }) + + fastify.listen({ port: 0 }, err => { + t.assert.ifError(err) + t.after(() => fastify.close()) + + fastify.inject({ + method: 'GET', + url: '/' + }, (err, response) => { + t.assert.ifError(err) + t.assert.strictEqual(response.headers['content-type'], 'application/octet-stream') + t.assert.deepStrictEqual(new Uint8Array(response.rawPayload), new Uint8Array(1024).fill(0xff)) + done() + }) + }) +}) +test('Uint16Array without content type should send a application/octet-stream and raw buffer', (t, done) => { + t.plan(4) + + const fastify = Fastify() + + fastify.get('/', function (req, reply) { + reply.send(new Uint16Array(50).fill(0xffffffff)) + }) + + fastify.listen({ port: 0 }, err => { + t.assert.ifError(err) + t.after(() => fastify.close()) + + fastify.inject({ + method: 'GET', + url: '/' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.headers['content-type'], 'application/octet-stream') + t.assert.deepStrictEqual( + new Uint16Array( + res.rawPayload.buffer, + res.rawPayload.byteOffset, + res.rawPayload.byteLength / Uint16Array.BYTES_PER_ELEMENT + ), + new Uint16Array(50).fill(0xffffffff) + ) + done() + }) + }) +}) +test('TypedArray with content type should not send application/octet-stream', (t, done) => { + t.plan(4) + + const fastify = Fastify() + + fastify.get('/', function (req, reply) { + reply.header('Content-Type', 'text/plain') + reply.send(new Uint16Array(1024).fill(0xffffffff)) + }) + + fastify.listen({ port: 0 }, err => { + t.assert.ifError(err) + t.after(() => fastify.close()) + + fastify.inject({ + method: 'GET', + url: '/' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.headers['content-type'], 'text/plain') + t.assert.deepStrictEqual( + new Uint16Array( + res.rawPayload.buffer, + res.rawPayload.byteOffset, + res.rawPayload.byteLength / Uint16Array.BYTES_PER_ELEMENT + ), + new Uint16Array(1024).fill(0xffffffff) + ) + done() + }) + }) +}) +test('buffer with content type should not send application/octet-stream', async t => { + t.plan(3) + + const fastify = Fastify() + + fastify.get('/', function (req, reply) { + reply.header('Content-Type', 'text/plain') + reply.send(Buffer.alloc(1024)) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const result = await fetch(fastifyServer) + t.assert.ok(result.ok) + t.assert.strictEqual(result.headers.get('content-type'), 'text/plain') + t.assert.deepStrictEqual(Buffer.from(await result.arrayBuffer()), Buffer.alloc(1024)) +}) + +test('stream with content type should not send application/octet-stream', async t => { + t.plan(3) + + const fastify = Fastify() + + const streamPath = path.join(__dirname, '..', '..', 'package.json') + const stream = fs.createReadStream(streamPath) + const buf = fs.readFileSync(streamPath) + + fastify.get('/', function (req, reply) { + reply.header('Content-Type', 'text/plain').send(stream) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const result = await fetch(fastifyServer) + t.assert.ok(result.ok) + t.assert.strictEqual(result.headers.get('content-type'), 'text/plain') + t.assert.deepStrictEqual(Buffer.from(await result.arrayBuffer()), buf) +}) + +test('stream without content type should not send application/octet-stream', async t => { + t.plan(3) + + const fastify = Fastify() + + const stream = fs.createReadStream(__filename) + const buf = fs.readFileSync(__filename) + + fastify.get('/', function (req, reply) { + reply.send(stream) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const result = await fetch(fastifyServer) + t.assert.ok(result.ok) + t.assert.strictEqual(result.headers.get('content-type'), null) + t.assert.deepStrictEqual(Buffer.from(await result.arrayBuffer()), buf) +}) + +test('stream using reply.raw.writeHead should return customize headers', async t => { + t.plan(5) + + const fastify = Fastify() + const fs = require('node:fs') + const path = require('node:path') + + const streamPath = path.join(__dirname, '..', '..', 'package.json') + const stream = fs.createReadStream(streamPath) + const buf = fs.readFileSync(streamPath) + + fastify.get('/', function (req, reply) { + reply.log.warn = function mockWarn (message) { + t.assert.strictEqual(message, 'response will send, but you shouldn\'t use res.writeHead in stream mode') + } + reply.raw.writeHead(200, { + location: '/' + }) + reply.send(stream) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const result = await fetch(fastifyServer) + t.assert.ok(result.ok) + t.assert.strictEqual(result.headers.get('location'), '/') + t.assert.strictEqual(result.headers.get('content-type'), null) + t.assert.deepStrictEqual(Buffer.from(await result.arrayBuffer()), buf) +}) + +test('plain string without content type should send a text/plain', async t => { + t.plan(3) + + const fastify = Fastify() + + fastify.get('/', function (req, reply) { + reply.send('hello world!') + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const result = await fetch(fastifyServer) + t.assert.ok(result.ok) + t.assert.strictEqual(result.headers.get('content-type'), 'text/plain; charset=utf-8') + t.assert.deepStrictEqual(await result.text(), 'hello world!') +}) + +test('plain string with content type should be sent unmodified', async t => { + t.plan(3) + + const fastify = Fastify() + + fastify.get('/', function (req, reply) { + reply.type('text/css').send('hello world!') + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const result = await fetch(fastifyServer) + t.assert.ok(result.ok) + t.assert.strictEqual(result.headers.get('content-type'), 'text/css') + t.assert.deepStrictEqual(await result.text(), 'hello world!') +}) + +test('plain string with content type and custom serializer should be serialized', async t => { + t.plan(3) + + const fastify = Fastify() + + fastify.get('/', function (req, reply) { + reply + .serializer(() => 'serialized') + .type('text/css') + .send('hello world!') + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const result = await fetch(fastifyServer) + t.assert.ok(result.ok) + t.assert.strictEqual(result.headers.get('content-type'), 'text/css') + t.assert.deepStrictEqual(await result.text(), 'serialized') +}) + +test('plain string with content type application/json should NOT be serialized as json', async t => { + t.plan(3) + + const fastify = Fastify() + + fastify.get('/', function (req, reply) { + reply.type('application/json').send('{"key": "hello world!"}') + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const result = await fetch(fastifyServer) + t.assert.ok(result.ok) + t.assert.strictEqual(result.headers.get('content-type'), 'application/json; charset=utf-8') + t.assert.deepStrictEqual(await result.text(), '{"key": "hello world!"}') +}) + +test('plain string with custom json content type should NOT be serialized as json', async t => { + t.plan(18) + + const fastify = Fastify() + + t.after(() => fastify.close()) + + const customSamples = { + collectionjson: { + mimeType: 'application/vnd.collection+json', + sample: '{"collection":{"version":"1.0","href":"http://api.fastify.test/people/"}}' + }, + hal: { + mimeType: 'application/hal+json', + sample: '{"_links":{"self":{"href":"https://api.fastify.test/people/1"}},"name":"John Doe"}' + }, + jsonapi: { + mimeType: 'application/vnd.api+json', + sample: '{"data":{"type":"people","id":"1"}}' + }, + jsonld: { + mimeType: 'application/ld+json', + sample: '{"@context":"https://json-ld.org/contexts/person.jsonld","name":"John Doe"}' + }, + ndjson: { + mimeType: 'application/x-ndjson', + sample: '{"a":"apple","b":{"bb":"bubble"}}\n{"c":"croissant","bd":{"dd":"dribble"}}' + }, + siren: { + mimeType: 'application/vnd.siren+json', + sample: '{"class":"person","properties":{"name":"John Doe"}}' + } + } + + Object.keys(customSamples).forEach((path) => { + fastify.get(`/${path}`, function (req, reply) { + reply.type(customSamples[path].mimeType).send(customSamples[path].sample) + }) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + + await Promise.all(Object.keys(customSamples).map(async path => { + const result = await fetch(fastifyServer + '/' + path) + t.assert.ok(result.ok) + t.assert.strictEqual(result.headers.get('content-type'), customSamples[path].mimeType + '; charset=utf-8') + t.assert.deepStrictEqual(await result.text(), customSamples[path].sample) + })) +}) + +test('non-string with content type application/json SHOULD be serialized as json', async t => { + t.plan(3) + + const fastify = Fastify() + + fastify.get('/', function (req, reply) { + reply.type('application/json').send({ key: 'hello world!' }) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const result = await fetch(fastifyServer) + t.assert.ok(result.ok) + t.assert.strictEqual(result.headers.get('content-type'), 'application/json; charset=utf-8') + t.assert.deepStrictEqual(await result.text(), JSON.stringify({ key: 'hello world!' })) +}) + +test('non-string with custom json\'s content-type SHOULD be serialized as json', async t => { + t.plan(3) + + const fastify = Fastify() + + fastify.get('/', function (req, reply) { + reply.type('application/json; version=2; ').send({ key: 'hello world!' }) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const result = await fetch(fastifyServer) + t.assert.ok(result.ok) + t.assert.strictEqual(result.headers.get('content-type'), 'application/json; version=2; charset=utf-8') + t.assert.deepStrictEqual(await result.text(), JSON.stringify({ key: 'hello world!' })) +}) + +test('non-string with custom json content type SHOULD be serialized as json', async t => { + t.plan(15) + + const fastify = Fastify() + t.after(() => fastify.close()) + + const customSamples = { + collectionjson: { + mimeType: 'application/vnd.collection+json', + sample: JSON.parse('{"collection":{"version":"1.0","href":"http://api.fastify.test/people/"}}') + }, + hal: { + mimeType: 'application/hal+json', + sample: JSON.parse('{"_links":{"self":{"href":"https://api.fastify.test/people/1"}},"name":"John Doe"}') + }, + jsonapi: { + mimeType: 'application/vnd.api+json', + sample: JSON.parse('{"data":{"type":"people","id":"1"}}') + }, + jsonld: { + mimeType: 'application/ld+json', + sample: JSON.parse('{"@context":"https://json-ld.org/contexts/person.jsonld","name":"John Doe"}') + }, + siren: { + mimeType: 'application/vnd.siren+json', + sample: JSON.parse('{"class":"person","properties":{"name":"John Doe"}}') + } + } + + Object.keys(customSamples).forEach((path) => { + fastify.get(`/${path}`, function (req, reply) { + reply.type(customSamples[path].mimeType).send(customSamples[path].sample) + }) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + + await Promise.all(Object.keys(customSamples).map(async path => { + const result = await fetch(fastifyServer + '/' + path) + t.assert.ok(result.ok) + t.assert.strictEqual(result.headers.get('content-type'), customSamples[path].mimeType + '; charset=utf-8') + t.assert.deepStrictEqual(await result.text(), JSON.stringify(customSamples[path].sample)) + })) +}) + +test('error object with a content type that is not application/json should work', async t => { + t.plan(4) + + const fastify = Fastify() + + fastify.get('/text', function (req, reply) { + reply.type('text/plain') + reply.send(new Error('some application error')) + }) + + fastify.get('/html', function (req, reply) { + reply.type('text/html') + reply.send(new Error('some application error')) + }) + + { + const res = await fastify.inject({ + method: 'GET', + url: '/text' + }) + t.assert.strictEqual(res.statusCode, 500) + t.assert.strictEqual(JSON.parse(res.payload).message, 'some application error') + } + + { + const res = await fastify.inject({ + method: 'GET', + url: '/html' + }) + t.assert.strictEqual(res.statusCode, 500) + t.assert.strictEqual(JSON.parse(res.payload).message, 'some application error') + } +}) + +test('undefined payload should be sent as-is', async t => { + t.plan(5) + + const fastify = Fastify() + + fastify.addHook('onSend', function (request, reply, payload, done) { + t.assert.strictEqual(payload, undefined) + done() + }) + + fastify.get('/', function (req, reply) { + reply.code(204).send() + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const result = await fetch(fastifyServer) + t.assert.ok(result.ok) + t.assert.strictEqual(result.headers.get('content-type'), null) + t.assert.strictEqual(result.headers.get('content-length'), null) + const body = await result.text() + t.assert.strictEqual(body.length, 0) +}) + +test('for HEAD method, no body should be sent but content-length should be', async t => { + t.plan(10) + + const fastify = Fastify() + t.after(() => fastify.close()) + const contentType = 'application/json; charset=utf-8' + const bodySize = JSON.stringify({ foo: 'bar' }).length + + fastify.head('/', { + onSend: function (request, reply, payload, done) { + t.assert.strictEqual(payload, undefined) + done() + } + }, function (req, reply) { + reply.header('content-length', bodySize) + reply.header('content-type', contentType) + reply.code(200).send() + }) + + fastify.head('/with/null', { + onSend: function (request, reply, payload, done) { + t.assert.strictEqual(payload, 'null') + done() + } + }, function (req, reply) { + reply.header('content-length', bodySize) + reply.header('content-type', contentType) + reply.code(200).send(null) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + + const promise1 = (async () => { + const result = await fetch(fastifyServer, { method: 'HEAD' }) + t.assert.ok(result.ok) + t.assert.strictEqual(result.headers.get('content-type'), contentType) + t.assert.strictEqual(result.headers.get('content-length'), bodySize.toString()) + t.assert.strictEqual((await result.text()).length, 0) + })() + + const promise2 = (async () => { + const result = await fetch(fastifyServer + '/with/null', { method: 'HEAD' }) + t.assert.ok(result.ok) + t.assert.strictEqual(result.headers.get('content-type'), contentType) + t.assert.strictEqual(result.headers.get('content-length'), bodySize.toString()) + t.assert.strictEqual((await result.text()).length, 0) + })() + + await Promise.all([promise1, promise2]) +}) + +test('reply.send(new NotFound()) should not invoke the 404 handler', async t => { + t.plan(6) + + const fastify = Fastify() + t.after(() => fastify.close()) + + fastify.setNotFoundHandler((req, reply) => { + t.fail('Should not be called') + }) + + fastify.get('/not-found', function (req, reply) { + reply.send(new NotFound()) + }) + + fastify.register(function (instance, options, done) { + instance.get('/not-found', function (req, reply) { + reply.send(new NotFound()) + }) + + done() + }, { prefix: '/prefixed' }) + + const fastifyServer = await fastify.listen({ port: 0 }) + + const promise1 = (async () => { + const result = await fetch(`${fastifyServer}/not-found`) + t.assert.strictEqual(result.status, 404) + t.assert.strictEqual(result.headers.get('content-type'), 'application/json; charset=utf-8') + t.assert.deepStrictEqual(JSON.parse(await result.text()), { + statusCode: 404, + error: 'Not Found', + message: 'Not Found' + }) + })() + + const promise2 = (async () => { + const result = await fetch(`${fastifyServer}/prefixed/not-found`) + t.assert.strictEqual(result.status, 404) + t.assert.strictEqual(result.headers.get('content-type'), 'application/json; charset=utf-8') + t.assert.deepStrictEqual(JSON.parse(await result.text()), { + statusCode: 404, + error: 'Not Found', + message: 'Not Found' + }) + })() + + await Promise.all([promise1, promise2]) +}) + +test('reply can set multiple instances of same header', async t => { + t.plan(3) + + const fastify = require('../../')() + + fastify.get('/headers', function (req, reply) { + reply + .header('set-cookie', 'one') + .header('set-cookie', 'two') + .send({}) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const result = await fetch(`${fastifyServer}/headers`) + t.assert.ok(result.ok) + t.assert.ok(result.headers.get('set-cookie')) + t.assert.deepStrictEqual(result.headers.getSetCookie(), ['one', 'two']) +}) + +test('reply.hasHeader returns correct values', async t => { + t.plan(2) + + const fastify = require('../../')() + + fastify.get('/headers', function (req, reply) { + reply.header('x-foo', 'foo') + t.assert.strictEqual(reply.hasHeader('x-foo'), true) + t.assert.strictEqual(reply.hasHeader('x-bar'), false) + reply.send() + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + await fetch(`${fastifyServer}/headers`) +}) + +test('reply.getHeader returns correct values', async t => { + t.plan(4) + + const fastify = require('../../')() + + fastify.get('/headers', function (req, reply) { + reply.header('x-foo', 'foo') + t.assert.strictEqual(reply.getHeader('x-foo'), 'foo') + + reply.header('x-foo', 'bar') + t.assert.deepStrictEqual(reply.getHeader('x-foo'), 'bar') + + reply.header('x-foo', 42) + t.assert.deepStrictEqual(reply.getHeader('x-foo'), 42) + + reply.header('set-cookie', 'one') + reply.header('set-cookie', 'two') + t.assert.deepStrictEqual(reply.getHeader('set-cookie'), ['one', 'two']) + + reply.send() + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + await fetch(`${fastifyServer}/headers`) +}) + +test('reply.getHeader returns raw header if there is not in the reply headers', (t) => { + t.plan(1) + const response = { + setHeader: () => { }, + hasHeader: () => true, + getHeader: () => 'bar', + writeHead: () => { }, + end: () => { } + } + const reply = new Reply(response, { onSend: [] }, null) + t.assert.strictEqual(reply.getHeader('foo'), 'bar') +}) + +test('reply.getHeaders returns correct values', (t, done) => { + t.plan(3) + + const fastify = require('../../')() + + fastify.get('/headers', function (req, reply) { + reply.header('x-foo', 'foo') + + t.assert.deepStrictEqual(reply.getHeaders(), { + 'x-foo': 'foo' + }) + + reply.header('x-bar', 'bar') + reply.raw.setHeader('x-foo', 'foo2') + reply.raw.setHeader('x-baz', 'baz') + + t.assert.deepStrictEqual(reply.getHeaders(), { + 'x-foo': 'foo', + 'x-bar': 'bar', + 'x-baz': 'baz' + }) + + reply.send() + }) + + fastify.inject('/headers', (err) => { + t.assert.ifError(err) + done() + }) +}) + +test('reply.removeHeader can remove the value', async t => { + t.plan(3) + + const fastify = require('../../')() + + t.after(() => fastify.close()) + + fastify.get('/headers', function (req, reply) { + reply.header('x-foo', 'foo') + t.assert.strictEqual(reply.getHeader('x-foo'), 'foo') + + t.assert.strictEqual(reply.removeHeader('x-foo'), reply) + t.assert.deepStrictEqual(reply.getHeader('x-foo'), undefined) + + reply.send() + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + + await fetch(`${fastifyServer}/headers`) +}) + +test('reply.header can reset the value', async t => { + t.plan(1) + + const fastify = require('../../')() + + t.after(() => fastify.close()) + + fastify.get('/headers', function (req, reply) { + reply.header('x-foo', 'foo') + reply.header('x-foo', undefined) + t.assert.deepStrictEqual(reply.getHeader('x-foo'), '') + + reply.send() + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + + await fetch(`${fastifyServer}/headers`) +}) + +// https://github.com/fastify/fastify/issues/3030 +test('reply.hasHeader computes raw and fastify headers', async t => { + t.plan(2) + + const fastify = require('../../')() + + t.after(() => fastify.close()) + + fastify.get('/headers', function (req, reply) { + reply.header('x-foo', 'foo') + reply.raw.setHeader('x-bar', 'bar') + t.assert.ok(reply.hasHeader('x-foo')) + t.assert.ok(reply.hasHeader('x-bar')) + + reply.send() + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + await fetch(`${fastifyServer}/headers`) +}) + +test('Reply should handle JSON content type with a charset', async t => { + t.plan(8) + + const fastify = require('../../')() + + fastify.get('/default', function (req, reply) { + reply.send({ hello: 'world' }) + }) + + fastify.get('/utf8', function (req, reply) { + reply + .header('content-type', 'application/json; charset=utf-8') + .send({ hello: 'world' }) + }) + + fastify.get('/utf16', function (req, reply) { + reply + .header('content-type', 'application/json; charset=utf-16') + .send({ hello: 'world' }) + }) + + fastify.get('/utf32', function (req, reply) { + reply + .header('content-type', 'application/json; charset=utf-32') + .send({ hello: 'world' }) + }) + + fastify.get('/type-utf8', function (req, reply) { + reply + .type('application/json; charset=utf-8') + .send({ hello: 'world' }) + }) + + fastify.get('/type-utf16', function (req, reply) { + reply + .type('application/json; charset=utf-16') + .send({ hello: 'world' }) + }) + + fastify.get('/type-utf32', function (req, reply) { + reply + .type('application/json; charset=utf-32') + .send({ hello: 'world' }) + }) + + fastify.get('/no-space-type-utf32', function (req, reply) { + reply + .type('application/json;charset=utf-32') + .send({ hello: 'world' }) + }) + + { + const res = await fastify.inject('/default') + t.assert.strictEqual(res.headers['content-type'], 'application/json; charset=utf-8') + } + + { + const res = await fastify.inject('/utf8') + t.assert.strictEqual(res.headers['content-type'], 'application/json; charset=utf-8') + } + + { + const res = await fastify.inject('/utf16') + t.assert.strictEqual(res.headers['content-type'], 'application/json; charset=utf-16') + } + + { + const res = await fastify.inject('/utf32') + t.assert.strictEqual(res.headers['content-type'], 'application/json; charset=utf-32') + } + + { + const res = await fastify.inject('/type-utf8') + t.assert.strictEqual(res.headers['content-type'], 'application/json; charset=utf-8') + } + + { + const res = await fastify.inject('/type-utf16') + t.assert.strictEqual(res.headers['content-type'], 'application/json; charset=utf-16') + } + { + const res = await fastify.inject('/type-utf32') + t.assert.strictEqual(res.headers['content-type'], 'application/json; charset=utf-32') + } + + { + const res = await fastify.inject('/no-space-type-utf32') + t.assert.strictEqual(res.headers['content-type'], 'application/json;charset=utf-32') + } +}) + +test('Content type and charset set previously', (t, done) => { + t.plan(2) + + const fastify = require('../../')() + + fastify.addHook('onRequest', function (req, reply, done) { + reply.header('content-type', 'application/json; charset=utf-16') + done() + }) + + fastify.get('/', function (req, reply) { + reply.send({ hello: 'world' }) + }) + + fastify.inject('/', (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.headers['content-type'], 'application/json; charset=utf-16') + done() + }) +}) + +test('.status() is an alias for .code()', (t, done) => { + t.plan(2) + const fastify = Fastify() + + fastify.get('/', function (req, reply) { + reply.status(418).send() + }) + + fastify.inject('/', (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 418) + done() + }) +}) + +test('.statusCode is getter and setter', (t, done) => { + t.plan(4) + const fastify = Fastify() + + fastify.get('/', function (req, reply) { + t.assert.strictEqual(reply.statusCode, 200, 'default status value') + reply.statusCode = 418 + t.assert.strictEqual(reply.statusCode, 418) + reply.send() + }) + + fastify.inject('/', (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 418) + done() + }) +}) + +test('reply.header setting multiple cookies as multiple Set-Cookie headers', async t => { + t.plan(5) + + const fastify = require('../../')() + t.after(() => fastify.close()) + + fastify.get('/headers', function (req, reply) { + reply + .header('set-cookie', 'one') + .header('set-cookie', 'two') + .header('set-cookie', 'three') + .header('set-cookie', ['four', 'five', 'six']) + .send({}) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + + const result = await fetch(`${fastifyServer}/headers`) + t.assert.ok(result.ok) + t.assert.ok(result.headers.get('set-cookie')) + t.assert.deepStrictEqual(result.headers.getSetCookie(), ['one', 'two', 'three', 'four', 'five', 'six']) + + const response = await fastify.inject('/headers') + t.assert.ok(response.headers['set-cookie']) + t.assert.deepStrictEqual(response.headers['set-cookie'], ['one', 'two', 'three', 'four', 'five', 'six']) +}) + +test('should throw when trying to modify the reply.sent property', (t, done) => { + t.plan(3) + const fastify = Fastify() + + fastify.get('/', function (req, reply) { + try { + reply.sent = true + } catch (err) { + t.assert.ok(err) + reply.send() + } + }) + + fastify.inject('/', (err, res) => { + t.assert.ifError(err) + t.assert.ok(true) + done() + }) +}) + +test('reply.elapsedTime should return 0 before the timer is initialised on the reply by setting up response listeners', t => { + t.plan(1) + const response = { statusCode: 200 } + const reply = new Reply(response, null) + t.assert.strictEqual(reply.elapsedTime, 0) +}) + +test('reply.elapsedTime should return a number greater than 0 after the timer is initialised on the reply by setting up response listeners', async t => { + t.plan(1) + const fastify = Fastify() + fastify.route({ + method: 'GET', + url: '/', + handler: (req, reply) => { + reply.send('hello world') + } + }) + + fastify.addHook('onResponse', (req, reply) => { + t.assert.ok(reply.elapsedTime > 0) + }) + + await fastify.inject({ method: 'GET', url: '/' }) +}) + +test('reply.elapsedTime should return the time since a request started while inflight', async t => { + t.plan(1) + const fastify = Fastify() + fastify.route({ + method: 'GET', + url: '/', + handler: (req, reply) => { + reply.send('hello world') + } + }) + + let preValidationElapsedTime + + fastify.addHook('preValidation', (req, reply, done) => { + preValidationElapsedTime = reply.elapsedTime + + done() + }) + + fastify.addHook('onResponse', (req, reply) => { + t.assert.ok(reply.elapsedTime > preValidationElapsedTime) + }) + + await fastify.inject({ method: 'GET', url: '/' }) +}) + +test('reply.elapsedTime should return the same value after a request is finished', async t => { + t.plan(1) + const fastify = Fastify() + fastify.route({ + method: 'GET', + url: '/', + handler: (req, reply) => { + reply.send('hello world') + } + }) + + fastify.addHook('onResponse', (req, reply) => { + t.assert.strictEqual(reply.elapsedTime, reply.elapsedTime) + }) + + await fastify.inject({ method: 'GET', url: '/' }) +}) + +test('reply should use the custom serializer', (t, done) => { + t.plan(4) + const fastify = Fastify() + fastify.setReplySerializer((payload, statusCode) => { + t.assert.deepStrictEqual(payload, { foo: 'bar' }) + t.assert.strictEqual(statusCode, 200) + payload.foo = 'bar bar' + return JSON.stringify(payload) + }) + + fastify.route({ + method: 'GET', + url: '/', + handler: (req, reply) => { + reply.send({ foo: 'bar' }) + } + }) + + fastify.inject({ + method: 'GET', + url: '/' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.payload, '{"foo":"bar bar"}') + done() + }) +}) + +test('reply should use the right serializer in encapsulated context', async t => { + t.plan(6) + + const fastify = Fastify() + fastify.setReplySerializer((payload) => { + t.assert.deepStrictEqual(payload, { foo: 'bar' }) + payload.foo = 'bar bar' + return JSON.stringify(payload) + }) + + fastify.route({ + method: 'GET', + url: '/', + handler: (req, reply) => { reply.send({ foo: 'bar' }) } + }) + + fastify.register(function (instance, opts, done) { + instance.route({ + method: 'GET', + url: '/sub', + handler: (req, reply) => { reply.send({ john: 'doo' }) } + }) + instance.setReplySerializer((payload) => { + t.assert.deepStrictEqual(payload, { john: 'doo' }) + payload.john = 'too too' + return JSON.stringify(payload) + }) + done() + }) + + fastify.register(function (instance, opts, done) { + instance.route({ + method: 'GET', + url: '/sub', + handler: (req, reply) => { reply.send({ sweet: 'potato' }) } + }) + instance.setReplySerializer((payload) => { + t.assert.deepStrictEqual(payload, { sweet: 'potato' }) + payload.sweet = 'potato potato' + return JSON.stringify(payload) + }) + done() + }, { prefix: 'sub' }) + + { + const res = await fastify.inject('/') + t.assert.strictEqual(res.payload, '{"foo":"bar bar"}') + } + + { + const res = await fastify.inject('/sub') + t.assert.strictEqual(res.payload, '{"john":"too too"}') + } + + { + const res = await fastify.inject('/sub/sub') + t.assert.strictEqual(res.payload, '{"sweet":"potato potato"}') + } +}) + +test('reply should use the right serializer in deep encapsulated context', async t => { + t.plan(5) + + const fastify = Fastify() + + fastify.route({ + method: 'GET', + url: '/', + handler: (req, reply) => { reply.send({ foo: 'bar' }) } + }) + + fastify.register(function (instance, opts, done) { + instance.route({ + method: 'GET', + url: '/sub', + handler: (req, reply) => { reply.send({ john: 'doo' }) } + }) + instance.setReplySerializer((payload) => { + t.assert.deepStrictEqual(payload, { john: 'doo' }) + payload.john = 'too too' + return JSON.stringify(payload) + }) + + instance.register(function (subInstance, opts, done) { + subInstance.route({ + method: 'GET', + url: '/deep', + handler: (req, reply) => { reply.send({ john: 'deep' }) } + }) + subInstance.setReplySerializer((payload) => { + t.assert.deepStrictEqual(payload, { john: 'deep' }) + payload.john = 'deep deep' + return JSON.stringify(payload) + }) + done() + }) + done() + }) + + { + const res = await fastify.inject('/') + t.assert.strictEqual(res.payload, '{"foo":"bar"}') + } + { + const res = await fastify.inject('/sub') + t.assert.strictEqual(res.payload, '{"john":"too too"}') + } + { + const res = await fastify.inject('/deep') + t.assert.strictEqual(res.payload, '{"john":"deep deep"}') + } +}) + +test('reply should use the route serializer', (t, done) => { + t.plan(3) + + const fastify = Fastify() + fastify.setReplySerializer(() => { + t.fail('this serializer should not be executed') + }) + + fastify.route({ + method: 'GET', + url: '/', + handler: (req, reply) => { + reply + .serializer((payload) => { + t.assert.deepStrictEqual(payload, { john: 'doo' }) + payload.john = 'too too' + return JSON.stringify(payload) + }) + .send({ john: 'doo' }) + } + }) + + fastify.inject({ + method: 'GET', + url: '/' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.payload, '{"john":"too too"}') + done() + }) +}) + +test('cannot set the replySerializer when the server is running', (t, done) => { + t.plan(2) + + const fastify = Fastify() + t.after(() => fastify.close()) + + fastify.listen({ port: 0 }, err => { + t.assert.ifError(err) + try { + fastify.setReplySerializer(() => { }) + t.assert.fail('this serializer should not be setup') + } catch (e) { + t.assert.strictEqual(e.code, 'FST_ERR_INSTANCE_ALREADY_LISTENING') + } finally { + done() + } + }) +}) + +test('reply should not call the custom serializer for errors and not found', async t => { + t.plan(6) + + const fastify = Fastify() + fastify.setReplySerializer((payload, statusCode) => { + t.assert.deepStrictEqual(payload, { foo: 'bar' }) + t.assert.strictEqual(statusCode, 200) + return JSON.stringify(payload) + }) + + fastify.get('/', (req, reply) => { reply.send({ foo: 'bar' }) }) + fastify.get('/err', (req, reply) => { reply.send(new Error('an error')) }) + + { + const res = await fastify.inject('/') + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload, '{"foo":"bar"}') + } + { + const res = await fastify.inject('/err') + t.assert.strictEqual(res.statusCode, 500) + } + { + const res = await fastify.inject('/not-existing') + t.assert.strictEqual(res.statusCode, 404) + } +}) + +test('reply.then', async t => { + t.plan(4) + + function request () { } + + await t.test('without an error', (t, done) => { + t.plan(1) + + const response = new Writable() + const reply = new Reply(response, request) + + reply.then(function () { + t.assert.ok(true) + done() + }) + + response.destroy() + }) + + await t.test('with an error', (t, done) => { + t.plan(1) + + const response = new Writable() + const reply = new Reply(response, request) + const _err = new Error('kaboom') + + reply.then(function () { + t.assert.fail('fulfilled called') + }, function (err) { + t.assert.strictEqual(err, _err) + done() + }) + + response.destroy(_err) + }) + + await t.test('with error but without reject callback', t => { + t.plan(1) + + const response = new Writable() + const reply = new Reply(response, request) + const _err = new Error('kaboom') + + reply.then(function () { + t.assert.fail('fulfilled called') + }) + + t.assert.ok(true) + + response.destroy(_err) + }) + + await t.test('with error, without reject callback, with logger', (t, done) => { + t.plan(1) + + const response = new Writable() + const reply = new Reply(response, request) + // spy logger + reply.log = { + warn: (message) => { + t.assert.strictEqual(message, 'unhandled rejection on reply.then') + done() + } + } + const _err = new Error('kaboom') + + reply.then(function () { + t.assert.fail('fulfilled called') + }) + + response.destroy(_err) + }) +}) + +test('reply.sent should read from response.writableEnded if it is defined', t => { + t.plan(1) + + const reply = new Reply({ writableEnded: true }, {}, {}) + + t.assert.strictEqual(reply.sent, true) +}) + +test('redirect to an invalid URL should not crash the server', async t => { + const fastify = Fastify() + fastify.route({ + method: 'GET', + url: '/redirect', + handler: (req, reply) => { + reply.log.warn = function mockWarn (obj, message) { + t.assert.strictEqual(message, 'Invalid character in header content ["location"]') + } + + switch (req.query.useCase) { + case '1': + reply.redirect('/?key=a’b') + break + + case '2': + reply.redirect(encodeURI('/?key=a’b')) + break + + default: + reply.redirect('/?key=ab') + break + } + } + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + + { + const { response, body } = await doGet(`${fastifyServer}/redirect?useCase=1`) + t.assert.strictEqual(response.status, 500) + t.assert.deepStrictEqual(body, { + statusCode: 500, + code: 'ERR_INVALID_CHAR', + error: 'Internal Server Error', + message: 'Invalid character in header content ["location"]' + }) + } + { + const { response } = await doGet(`${fastifyServer}/redirect?useCase=2`) + t.assert.strictEqual(response.status, 302) + t.assert.strictEqual(response.headers.get('location'), '/?key=a%E2%80%99b') + } + + { + const { response } = await doGet(`${fastifyServer}/redirect?useCase=3`) + t.assert.strictEqual(response.status, 302) + t.assert.strictEqual(response.headers.get('location'), '/?key=ab') + } + + await fastify.close() +}) + +test('invalid response headers should not crash the server', async t => { + const fastify = Fastify() + fastify.route({ + method: 'GET', + url: '/bad-headers', + handler: (req, reply) => { + reply.log.warn = function mockWarn (obj, message) { + t.assert.strictEqual(message, 'Invalid character in header content ["smile-encoded"]', 'only the first invalid header is logged') + } + + reply.header('foo', '$') + reply.header('smile-encoded', '\uD83D\uDE00') + reply.header('smile', '😄') + reply.header('bar', 'ƒ∂å') + + reply.send({}) + } + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + + const { response, body } = await doGet(`${fastifyServer}/bad-headers`) + t.assert.strictEqual(response.status, 500) + t.assert.deepStrictEqual(body, { + statusCode: 500, + code: 'ERR_INVALID_CHAR', + error: 'Internal Server Error', + message: 'Invalid character in header content ["smile-encoded"]' + }) + + await fastify.close() +}) + +test('invalid response headers when sending back an error', async t => { + const fastify = Fastify() + fastify.route({ + method: 'GET', + url: '/bad-headers', + handler: (req, reply) => { + reply.log.warn = function mockWarn (obj, message) { + t.assert.strictEqual(message, 'Invalid character in header content ["smile"]', 'only the first invalid header is logged') + } + + reply.header('smile', '😄') + reply.send(new Error('user land error')) + } + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + + const { response, body } = await doGet(`${fastifyServer}/bad-headers`) + t.assert.strictEqual(response.status, 500) + t.assert.deepStrictEqual(body, { + statusCode: 500, + code: 'ERR_INVALID_CHAR', + error: 'Internal Server Error', + message: 'Invalid character in header content ["smile"]' + }) + + await fastify.close() +}) + +test('invalid response headers and custom error handler', async t => { + const fastify = Fastify() + fastify.route({ + method: 'GET', + url: '/bad-headers', + handler: (req, reply) => { + reply.log.warn = function mockWarn (obj, message) { + t.assert.strictEqual(message, 'Invalid character in header content ["smile"]', 'only the first invalid header is logged') + } + + reply.header('smile', '😄') + reply.send(new Error('user land error')) + } + }) + + fastify.setErrorHandler(function (error, request, reply) { + t.assert.strictEqual(error.message, 'user land error', 'custom error handler receives the error') + reply.status(500).send({ ops: true }) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + + const { response, body } = await doGet(`${fastifyServer}/bad-headers`) + t.assert.strictEqual(response.status, 500) + t.assert.deepStrictEqual(body, { + statusCode: 500, + code: 'ERR_INVALID_CHAR', + error: 'Internal Server Error', + message: 'Invalid character in header content ["smile"]' + }) + + await fastify.close() +}) + +test('reply.send will intercept ERR_HTTP_HEADERS_SENT and log an error message', t => { + t.plan(2) + + const response = new Writable() + Object.assign(response, { + setHeader: () => { }, + hasHeader: () => false, + getHeader: () => undefined, + writeHead: () => { + const err = new Error('kaboom') + err.code = 'ERR_HTTP_HEADERS_SENT' + throw err + }, + write: () => { }, + headersSent: true + }) + + const log = { + warn: (msg) => { + t.assert.strictEqual(msg, 'Reply was already sent, did you forget to "return reply" in the "/hello" (GET) route?') + } + } + + const reply = new Reply(response, { [kRouteContext]: { onSend: null }, raw: { url: '/hello', method: 'GET' } }, log) + + try { + reply.send('') + } catch (err) { + t.assert.strictEqual(err.code, 'ERR_HTTP_HEADERS_SENT') + } +}) + +test('Uint8Array view of ArrayBuffer returns correct byteLength', (t, done) => { + t.plan(5) + const fastify = Fastify() + + const arrBuf = new ArrayBuffer(100) + const arrView = new Uint8Array(arrBuf, 0, 10) + fastify.get('/', function (req, reply) { + return reply.send(arrView) + }) + + fastify.listen({ port: 0 }, err => { + t.assert.ifError(err) + t.after(() => fastify.close()) + + fastify.inject({ + method: 'GET', + url: '/' + }, (err, response) => { + t.assert.ifError(err) + t.assert.strictEqual(response.headers['content-type'], 'application/octet-stream') + t.assert.strictEqual(response.headers['content-length'], '10') + t.assert.deepStrictEqual(response.rawPayload.byteLength, arrView.byteLength) + done() + }) + }) +}) diff --git a/services/slides/node_modules/fastify/test/internals/req-id-gen-factory.test.js b/services/slides/node_modules/fastify/test/internals/req-id-gen-factory.test.js new file mode 100644 index 0000000000000000000000000000000000000000..558841d565db2124336a6a532ca7e046f6ef62b5 --- /dev/null +++ b/services/slides/node_modules/fastify/test/internals/req-id-gen-factory.test.js @@ -0,0 +1,133 @@ +'use strict' + +const { test } = require('node:test') +const { reqIdGenFactory } = require('../../lib/req-id-gen-factory') + +test('should create incremental ids deterministically', t => { + t.plan(1) + const reqIdGen = reqIdGenFactory() + + for (let i = 1; i < 1e4; ++i) { + if (reqIdGen() !== 'req-' + i.toString(36)) { + t.assert.fail() + break + } + } + t.assert.ok(true) +}) + +test('should have prefix "req-"', t => { + t.plan(1) + const reqIdGen = reqIdGenFactory() + + t.assert.ok(reqIdGen().startsWith('req-')) +}) + +test('different id generator functions should have separate internal counters', t => { + t.plan(5) + const reqIdGenA = reqIdGenFactory() + const reqIdGenB = reqIdGenFactory() + + t.assert.strictEqual(reqIdGenA(), 'req-1') + t.assert.strictEqual(reqIdGenA(), 'req-2') + t.assert.strictEqual(reqIdGenB(), 'req-1') + t.assert.strictEqual(reqIdGenA(), 'req-3') + t.assert.strictEqual(reqIdGenB(), 'req-2') +}) + +test('should start counting with 1', t => { + t.plan(1) + const reqIdGen = reqIdGenFactory() + + t.assert.strictEqual(reqIdGen(), 'req-1') +}) + +test('should handle requestIdHeader and return provided id in header', t => { + t.plan(1) + + const reqIdGen = reqIdGenFactory('id') + + t.assert.strictEqual(reqIdGen({ headers: { id: '1337' } }), '1337') +}) + +test('should handle requestIdHeader and fallback if id is not provided in header', t => { + t.plan(1) + + const reqIdGen = reqIdGenFactory('id') + + t.assert.strictEqual(reqIdGen({ headers: { notId: '1337' } }), 'req-1') +}) + +test('should handle requestIdHeader and increment internal counter if no header was provided', t => { + t.plan(4) + + const reqIdGen = reqIdGenFactory('id') + + t.assert.strictEqual(reqIdGen({ headers: {} }), 'req-1') + t.assert.strictEqual(reqIdGen({ headers: {} }), 'req-2') + t.assert.strictEqual(reqIdGen({ headers: { id: '1337' } }), '1337') + t.assert.strictEqual(reqIdGen({ headers: {} }), 'req-3') +}) + +test('should use optGenReqId to generate ids', t => { + t.plan(4) + + let i = 1 + let gotCalled = false + function optGenReqId () { + gotCalled = true + return (i++).toString(16) + } + const reqIdGen = reqIdGenFactory(undefined, optGenReqId) + + t.assert.strictEqual(gotCalled, false) + t.assert.strictEqual(reqIdGen(), '1') + t.assert.strictEqual(gotCalled, true) + t.assert.strictEqual(reqIdGen(), '2') +}) + +test('should use optGenReqId to generate ids if requestIdHeader is used but not provided', t => { + t.plan(4) + + let i = 1 + let gotCalled = false + function optGenReqId () { + gotCalled = true + return (i++).toString(16) + } + const reqIdGen = reqIdGenFactory('reqId', optGenReqId) + + t.assert.strictEqual(gotCalled, false) + t.assert.strictEqual(reqIdGen({ headers: {} }), '1') + t.assert.strictEqual(gotCalled, true) + t.assert.strictEqual(reqIdGen({ headers: {} }), '2') +}) + +test('should not use optGenReqId to generate ids if requestIdHeader is used and provided', t => { + t.plan(2) + + function optGenReqId () { + t.assert.fail() + } + const reqIdGen = reqIdGenFactory('reqId', optGenReqId) + + t.assert.strictEqual(reqIdGen({ headers: { reqId: 'r1' } }), 'r1') + t.assert.strictEqual(reqIdGen({ headers: { reqId: 'r2' } }), 'r2') +}) + +test('should fallback to use optGenReqId to generate ids if requestIdHeader is sometimes provided', t => { + t.plan(4) + + let i = 1 + let gotCalled = false + function optGenReqId () { + gotCalled = true + return (i++).toString(16) + } + const reqIdGen = reqIdGenFactory('reqId', optGenReqId) + + t.assert.strictEqual(reqIdGen({ headers: { reqId: 'r1' } }), 'r1') + t.assert.strictEqual(gotCalled, false) + t.assert.strictEqual(reqIdGen({ headers: {} }), '1') + t.assert.strictEqual(gotCalled, true) +}) diff --git a/services/slides/node_modules/fastify/test/internals/request-validate.test.js b/services/slides/node_modules/fastify/test/internals/request-validate.test.js new file mode 100644 index 0000000000000000000000000000000000000000..914588136bf2cd9a3ff7b040b05776588060ec71 --- /dev/null +++ b/services/slides/node_modules/fastify/test/internals/request-validate.test.js @@ -0,0 +1,1402 @@ +'use strict' + +const { test } = require('node:test') +const Ajv = require('ajv') +const { kRequestCacheValidateFns, kRouteContext } = require('../../lib/symbols') +const Fastify = require('../../fastify') + +const defaultSchema = { + type: 'object', + required: ['hello'], + properties: { + hello: { type: 'string' }, + world: { type: 'string' } + } +} + +const requestSchema = { + params: { + type: 'object', + properties: { + id: { + type: 'integer', + minimum: 1 + } + } + }, + querystring: { + type: 'object', + properties: { + foo: { + type: 'string', + enum: ['bar'] + } + } + }, + body: defaultSchema, + headers: { + type: 'object', + properties: { + 'x-foo': { + type: 'string' + } + } + } +} + +test('#compileValidationSchema', async subtest => { + subtest.plan(7) + + await subtest.test('Should return a function - Route without schema', async t => { + const fastify = Fastify() + + t.plan(3) + + fastify.get('/', (req, reply) => { + const validate = req.compileValidationSchema(defaultSchema) + + t.assert.ok(validate instanceof Function) + t.assert.ok(validate({ hello: 'world' })) + t.assert.ok(!validate({ world: 'foo' })) + + reply.send({ hello: 'world' }) + }) + + await fastify.inject({ + path: '/', + method: 'GET' + }) + }) + + await subtest.test('Validate function errors property should be null after validation when input is valid', async t => { + const fastify = Fastify() + + t.plan(3) + + fastify.get('/', (req, reply) => { + const validate = req.compileValidationSchema(defaultSchema) + + t.assert.ok(validate({ hello: 'world' })) + t.assert.ok(Object.hasOwn(validate, 'errors')) + t.assert.strictEqual(validate.errors, null) + + reply.send({ hello: 'world' }) + }) + + await fastify.inject({ + path: '/', + method: 'GET' + }) + }) + + await subtest.test('Validate function errors property should be an array of errors after validation when input is valid', async t => { + const fastify = Fastify() + + t.plan(4) + + fastify.get('/', (req, reply) => { + const validate = req.compileValidationSchema(defaultSchema) + + t.assert.ok(!validate({ world: 'foo' })) + t.assert.ok(Object.hasOwn(validate, 'errors')) + t.assert.ok(Array.isArray(validate.errors)) + t.assert.ok(validate.errors.length > 0) + + reply.send({ hello: 'world' }) + }) + + await fastify.inject({ + path: '/', + method: 'GET' + }) + }) + + await subtest.test( + 'Should reuse the validate fn across multiple invocations - Route without schema', + async t => { + const fastify = Fastify() + let validate = null + let counter = 0 + + t.plan(16) + + fastify.get('/', (req, reply) => { + counter++ + if (counter > 1) { + const newValidate = req.compileValidationSchema(defaultSchema) + t.assert.strictEqual(validate, newValidate, 'Are the same validate function') + validate = newValidate + } else { + validate = req.compileValidationSchema(defaultSchema) + } + + t.assert.ok(validate instanceof Function) + t.assert.ok(validate({ hello: 'world' })) + t.assert.ok(!validate({ world: 'foo' })) + + reply.send({ hello: 'world' }) + }) + + await Promise.all([ + fastify.inject({ + path: '/', + method: 'GET' + }), + fastify.inject({ + path: '/', + method: 'GET' + }), + fastify.inject({ + path: '/', + method: 'GET' + }), + fastify.inject({ + path: '/', + method: 'GET' + }) + ]) + + t.assert.strictEqual(counter, 4) + } + ) + + await subtest.test('Should return a function - Route with schema', async t => { + const fastify = Fastify() + + t.plan(3) + + fastify.post( + '/', + { + schema: { + body: defaultSchema + } + }, + (req, reply) => { + const validate = req.compileValidationSchema(defaultSchema) + + t.assert.ok(validate instanceof Function) + t.assert.ok(validate({ hello: 'world' })) + t.assert.ok(!validate({ world: 'foo' })) + + reply.send({ hello: 'world' }) + } + ) + + await fastify.inject({ + path: '/', + method: 'POST', + payload: { + hello: 'world', + world: 'foo' + } + }) + }) + + await subtest.test( + 'Should use the custom validator compiler for the route', + async t => { + const fastify = Fastify() + let called = 0 + const custom = ({ schema, httpPart, url, method }) => { + t.assert.strictEqual(schema, defaultSchema) + t.assert.strictEqual(url, '/') + t.assert.strictEqual(method, 'GET') + t.assert.strictEqual(httpPart, 'querystring') + + return input => { + called++ + t.assert.deepStrictEqual(input, { hello: 'world' }) + return true + } + } + + t.plan(10) + + fastify.get('/', { validatorCompiler: custom }, (req, reply) => { + const first = req.compileValidationSchema(defaultSchema, 'querystring') + const second = req.compileValidationSchema(defaultSchema, 'querystring') + + t.assert.strictEqual(first, second) + t.assert.ok(first({ hello: 'world' })) + t.assert.ok(second({ hello: 'world' })) + t.assert.strictEqual(called, 2) + + reply.send({ hello: 'world' }) + }) + + await fastify.inject({ + path: '/', + method: 'GET' + }) + } + ) + + await subtest.test( + 'Should instantiate a WeakMap when executed for first time', + async t => { + const fastify = Fastify() + + t.plan(5) + + fastify.get('/', (req, reply) => { + t.assert.strictEqual(req[kRouteContext][kRequestCacheValidateFns], null) + t.assert.ok(req.compileValidationSchema(defaultSchema) instanceof Function) + t.assert.ok(req[kRouteContext][kRequestCacheValidateFns] instanceof WeakMap) + t.assert.ok(req.compileValidationSchema(Object.assign({}, defaultSchema)) instanceof Function) + t.assert.ok(req[kRouteContext][kRequestCacheValidateFns] instanceof WeakMap) + + reply.send({ hello: 'world' }) + }) + + await fastify.inject({ + path: '/', + method: 'GET' + }) + } + ) +}) + +test('#getValidationFunction', async subtest => { + subtest.plan(6) + + await subtest.test('Should return a validation function', async t => { + const fastify = Fastify() + + t.plan(1) + + fastify.get('/', (req, reply) => { + const original = req.compileValidationSchema(defaultSchema) + const referenced = req.getValidationFunction(defaultSchema) + + t.assert.strictEqual(original, referenced) + + reply.send({ hello: 'world' }) + }) + + await fastify.inject({ + path: '/', + method: 'GET' + }) + }) + + await subtest.test('Validate function errors property should be null after validation when input is valid', async t => { + const fastify = Fastify() + + t.plan(3) + + fastify.get('/', (req, reply) => { + req.compileValidationSchema(defaultSchema) + const validate = req.getValidationFunction(defaultSchema) + + t.assert.ok(validate({ hello: 'world' })) + t.assert.ok(Object.hasOwn(validate, 'errors')) + t.assert.strictEqual(validate.errors, null) + + reply.send({ hello: 'world' }) + }) + + await fastify.inject({ + path: '/', + method: 'GET' + }) + }) + + await subtest.test('Validate function errors property should be an array of errors after validation when input is valid', async t => { + const fastify = Fastify() + + t.plan(4) + + fastify.get('/', (req, reply) => { + req.compileValidationSchema(defaultSchema) + const validate = req.getValidationFunction(defaultSchema) + + t.assert.ok(!validate({ world: 'foo' })) + t.assert.ok(Object.hasOwn(validate, 'errors')) + t.assert.ok(Array.isArray(validate.errors)) + t.assert.ok(validate.errors.length > 0) + + reply.send({ hello: 'world' }) + }) + + await fastify.inject({ + path: '/', + method: 'GET' + }) + }) + + await subtest.test('Should return undefined if no schema compiled', async t => { + const fastify = Fastify() + + t.plan(2) + + fastify.get('/', (req, reply) => { + const validate = req.getValidationFunction(defaultSchema) + t.assert.ok(!validate) + + const validateFn = req.getValidationFunction(42) + t.assert.ok(!validateFn) + + reply.send({ hello: 'world' }) + }) + + await fastify.inject('/') + }) + + await subtest.test( + 'Should return the validation function from each HTTP part', + async t => { + const fastify = Fastify() + let headerValidation = null + let customValidation = null + + t.plan(15) + + fastify.post( + '/:id', + { + schema: requestSchema + }, + (req, reply) => { + const { params } = req + + switch (params.id) { + case 1: + customValidation = req.compileValidationSchema(defaultSchema) + t.assert.ok(req.getValidationFunction('body')) + t.assert.ok(req.getValidationFunction('body')({ hello: 'world' })) + t.assert.ok(!req.getValidationFunction('body')({ world: 'hello' })) + break + case 2: + headerValidation = req.getValidationFunction('headers') + t.assert.ok(headerValidation) + t.assert.ok(headerValidation({ 'x-foo': 'world' })) + t.assert.ok(!headerValidation({ 'x-foo': [] })) + break + case 3: + t.assert.ok(req.getValidationFunction('params')) + t.assert.ok(req.getValidationFunction('params')({ id: 123 })) + t.assert.ok(!req.getValidationFunction('params'({ id: 1.2 }))) + break + case 4: + t.assert.ok(req.getValidationFunction('querystring')) + t.assert.ok(req.getValidationFunction('querystring')({ foo: 'bar' })) + t.assert.ok(!req.getValidationFunction('querystring')({ foo: 'not-bar' }) + ) + break + case 5: + t.assert.strictEqual( + customValidation, + req.getValidationFunction(defaultSchema) + ) + t.assert.ok(customValidation({ hello: 'world' })) + t.assert.ok(!customValidation({})) + t.assert.strictEqual(headerValidation, req.getValidationFunction('headers')) + break + default: + t.assert.fail('Invalid id') + } + + reply.send({ hello: 'world' }) + } + ) + + const promises = [] + + for (let i = 1; i < 6; i++) { + promises.push( + fastify.inject({ + path: `/${i}`, + method: 'post', + query: { foo: 'bar' }, + payload: { + hello: 'world' + }, + headers: { + 'x-foo': 'x-bar' + } + }) + ) + } + + await Promise.all(promises) + } + ) + + await subtest.test('Should not set a WeakMap if there is no schema', async t => { + const fastify = Fastify() + + t.plan(1) + + fastify.get('/', (req, reply) => { + req.getValidationFunction(defaultSchema) + req.getValidationFunction('body') + + t.assert.strictEqual(req[kRouteContext][kRequestCacheValidateFns], null) + reply.send({ hello: 'world' }) + }) + + await fastify.inject({ + path: '/', + method: 'GET' + }) + }) +}) + +test('#validate', async subtest => { + subtest.plan(7) + + await subtest.test( + 'Should return true/false if input valid - Route without schema', + async t => { + const fastify = Fastify() + + t.plan(2) + + fastify.get('/', (req, reply) => { + const isNotValid = req.validateInput({ world: 'string' }, defaultSchema) + const isValid = req.validateInput({ hello: 'string' }, defaultSchema) + + t.assert.ok(!isNotValid) + t.assert.ok(isValid) + + reply.send({ hello: 'world' }) + }) + + await fastify.inject({ + path: '/', + method: 'GET' + }) + } + ) + + await subtest.test( + 'Should use the custom validator compiler for the route', + async t => { + const fastify = Fastify() + let called = 0 + const custom = ({ schema, httpPart, url, method }) => { + t.assert.strictEqual(schema, defaultSchema) + t.assert.strictEqual(url, '/') + t.assert.strictEqual(method, 'GET') + t.assert.strictEqual(httpPart, 'querystring') + + return input => { + called++ + t.assert.deepStrictEqual(input, { hello: 'world' }) + return true + } + } + + t.plan(9) + + fastify.get('/', { validatorCompiler: custom }, (req, reply) => { + const ok = req.validateInput( + { hello: 'world' }, + defaultSchema, + 'querystring' + ) + const ok2 = req.validateInput({ hello: 'world' }, defaultSchema) + + t.assert.ok(ok) + t.assert.ok(ok2) + t.assert.strictEqual(called, 2) + + reply.send({ hello: 'world' }) + }) + + await fastify.inject({ + path: '/', + method: 'GET' + }) + } + ) + + await subtest.test( + 'Should return true/false if input valid - With Schema for Route defined', + async t => { + const fastify = Fastify() + + t.plan(8) + + fastify.post( + '/:id', + { + schema: requestSchema + }, + (req, reply) => { + const { params } = req + + switch (params.id) { + case 1: + t.assert.ok(req.validateInput({ hello: 'world' }, 'body')) + t.assert.ok(!req.validateInput({ hello: [], world: 'foo' }, 'body')) + break + case 2: + t.assert.ok(!req.validateInput({ foo: 'something' }, 'querystring')) + t.assert.ok(req.validateInput({ foo: 'bar' }, 'querystring')) + break + case 3: + t.assert.ok(!req.validateInput({ 'x-foo': [] }, 'headers')) + t.assert.ok(req.validateInput({ 'x-foo': 'something' }, 'headers')) + break + case 4: + t.assert.ok(req.validateInput({ id: params.id }, 'params')) + t.assert.ok(!req.validateInput({ id: 0 }, 'params')) + break + default: + t.assert.fail('Invalid id') + } + + reply.send({ hello: 'world' }) + } + ) + + const promises = [] + + for (let i = 1; i < 5; i++) { + promises.push( + fastify.inject({ + path: `/${i}`, + method: 'post', + query: { foo: 'bar' }, + payload: { + hello: 'world' + }, + headers: { + 'x-foo': 'x-bar' + } + }) + ) + } + + await Promise.all(promises) + } + ) + + await subtest.test( + 'Should throw if missing validation fn for HTTP part and not schema provided', + async t => { + const fastify = Fastify() + + t.plan(10) + + fastify.get('/:id', (req, reply) => { + const { params } = req + + switch (parseInt(params.id)) { + case 1: + req.validateInput({}, 'body') + break + case 2: + req.validateInput({}, 'querystring') + break + case 3: + req.validateInput({}, 'query') + break + case 4: + req.validateInput({ 'x-foo': [] }, 'headers') + break + case 5: + req.validateInput({ id: 0 }, 'params') + break + default: + t.assert.fail('Invalid id') + } + }) + + const promises = [] + + for (let i = 1; i < 6; i++) { + promises.push( + (async j => { + const response = await fastify.inject(`/${j}`) + + const result = response.json() + t.assert.strictEqual(result.statusCode, 500) + t.assert.strictEqual(result.code, 'FST_ERR_REQ_INVALID_VALIDATION_INVOCATION') + })(i) + ) + } + + await Promise.all(promises) + } + ) + + await subtest.test( + 'Should throw if missing validation fn for HTTP part and not valid schema provided', + async t => { + const fastify = Fastify() + + t.plan(10) + + fastify.get('/:id', (req, reply) => { + const { params } = req + + switch (parseInt(params.id)) { + case 1: + req.validateInput({}, 1, 'body') + break + case 2: + req.validateInput({}, [], 'querystring') + break + case 3: + req.validateInput({}, '', 'query') + break + case 4: + req.validateInput({ 'x-foo': [] }, null, 'headers') + break + case 5: + req.validateInput({ id: 0 }, () => {}, 'params') + break + default: + t.assert.fail('Invalid id') + } + }) + + const promises = [] + + for (let i = 1; i < 6; i++) { + promises.push( + (async j => { + const response = await fastify.inject({ + path: `/${j}`, + method: 'GET' + }) + + const result = response.json() + t.assert.strictEqual(result.statusCode, 500) + t.assert.strictEqual(result.code, 'FST_ERR_REQ_INVALID_VALIDATION_INVOCATION') + })(i) + ) + } + + await Promise.all(promises) + } + ) + + await subtest.test('Should throw if invalid schema passed', async t => { + const fastify = Fastify() + + t.plan(10) + + fastify.get('/:id', (req, reply) => { + const { params } = req + + switch (parseInt(params.id)) { + case 1: + req.validateInput({}, 1) + break + case 2: + req.validateInput({}, '') + break + case 3: + req.validateInput({}, []) + break + case 4: + req.validateInput({ 'x-foo': [] }, null) + break + case 5: + req.validateInput({ id: 0 }, () => {}) + break + default: + t.assert.fail('Invalid id') + } + }) + + const promises = [] + + for (let i = 1; i < 6; i++) { + promises.push( + (async j => { + const response = await fastify.inject({ + path: `/${j}`, + method: 'GET' + }) + + const result = response.json() + t.assert.strictEqual(result.statusCode, 500) + t.assert.strictEqual(result.code, 'FST_ERR_REQ_INVALID_VALIDATION_INVOCATION') + })(i) + ) + } + + await Promise.all(promises) + }) + + await subtest.test( + 'Should set a WeakMap if compiling the very first schema', + async t => { + const fastify = Fastify() + + t.plan(3) + + fastify.get('/', (req, reply) => { + t.assert.strictEqual(req[kRouteContext][kRequestCacheValidateFns], null) + t.assert.strictEqual(req.validateInput({ hello: 'world' }, defaultSchema), true) + t.assert.ok(req[kRouteContext][kRequestCacheValidateFns] instanceof WeakMap) + + reply.send({ hello: 'world' }) + }) + + await fastify.inject({ + path: '/', + method: 'GET' + }) + } + ) +}) + +test('Nested Context', async subtest => { + subtest.plan(1) + + await subtest.test('Level_1', async tst => { + tst.plan(3) + await tst.test('#compileValidationSchema', async ntst => { + ntst.plan(5) + + await ntst.test('Should return a function - Route without schema', async t => { + const fastify = Fastify() + + fastify.register((instance, opts, next) => { + instance.get('/', (req, reply) => { + const validate = req.compileValidationSchema(defaultSchema) + + t.assert.ok(validate, Function) + t.assert.ok(validate({ hello: 'world' })) + t.assert.ok(!validate({ world: 'foo' })) + + reply.send({ hello: 'world' }) + }) + + next() + }) + + t.plan(3) + + await fastify.inject({ + path: '/', + method: 'GET' + }) + }) + + await ntst.test( + 'Should reuse the validate fn across multiple invocations - Route without schema', + async t => { + const fastify = Fastify() + let validate = null + let counter = 0 + + t.plan(16) + + fastify.register((instance, opts, next) => { + instance.get('/', (req, reply) => { + counter++ + if (counter > 1) { + const newValidate = req.compileValidationSchema(defaultSchema) + t.assert.strictEqual(validate, newValidate, 'Are the same validate function') + validate = newValidate + } else { + validate = req.compileValidationSchema(defaultSchema) + } + + t.assert.ok(validate, Function) + t.assert.ok(validate({ hello: 'world' })) + t.assert.ok(!validate({ world: 'foo' })) + + reply.send({ hello: 'world' }) + }) + + next() + }) + + await Promise.all([ + fastify.inject('/'), + fastify.inject('/'), + fastify.inject('/'), + fastify.inject('/') + ]) + + t.assert.strictEqual(counter, 4) + } + ) + + await ntst.test('Should return a function - Route with schema', async t => { + const fastify = Fastify() + + t.plan(3) + + fastify.register((instance, opts, next) => { + instance.post( + '/', + { + schema: { + body: defaultSchema + } + }, + (req, reply) => { + const validate = req.compileValidationSchema(defaultSchema) + + t.assert.ok(validate, Function) + t.assert.ok(validate({ hello: 'world' })) + t.assert.ok(!validate({ world: 'foo' })) + + reply.send({ hello: 'world' }) + } + ) + + next() + }) + + await fastify.inject({ + path: '/', + method: 'POST', + payload: { + hello: 'world', + world: 'foo' + } + }) + }) + + await ntst.test( + 'Should use the custom validator compiler for the route', + async t => { + const fastify = Fastify() + let called = 0 + + t.plan(10) + + fastify.register((instance, opts, next) => { + const custom = ({ schema, httpPart, url, method }) => { + t.assert.strictEqual(schema, defaultSchema) + t.assert.strictEqual(url, '/') + t.assert.strictEqual(method, 'GET') + t.assert.strictEqual(httpPart, 'querystring') + + return input => { + called++ + t.assert.deepStrictEqual(input, { hello: 'world' }) + return true + } + } + + fastify.get('/', { validatorCompiler: custom }, (req, reply) => { + const first = req.compileValidationSchema( + defaultSchema, + 'querystring' + ) + const second = req.compileValidationSchema( + defaultSchema, + 'querystring' + ) + + t.assert.strictEqual(first, second) + t.assert.ok(first({ hello: 'world' })) + t.assert.ok(second({ hello: 'world' })) + t.assert.strictEqual(called, 2) + + reply.send({ hello: 'world' }) + }) + + next() + }) + + await fastify.inject('/') + } + ) + + await ntst.test('Should compile the custom validation - nested with schema.headers', async t => { + const fastify = Fastify() + let called = false + + const schemaWithHeaders = { + headers: { + 'x-foo': { + type: 'string' + } + } + } + + const custom = ({ schema, httpPart, url, method }) => { + if (called) return () => true + // only custom validators keep the same headers object + t.assert.strictEqual(schema, schemaWithHeaders.headers) + t.assert.strictEqual(url, '/') + t.assert.strictEqual(httpPart, 'headers') + called = true + return () => true + } + + t.plan(4) + + fastify.setValidatorCompiler(custom) + + fastify.register((instance, opts, next) => { + instance.get('/', { schema: schemaWithHeaders }, (req, reply) => { + t.assert.strictEqual(called, true) + + reply.send({ hello: 'world' }) + }) + + next() + }) + + await fastify.inject('/') + }) + }) + + await tst.test('#getValidationFunction', async ntst => { + ntst.plan(6) + + await ntst.test('Should return a validation function', async t => { + const fastify = Fastify() + + t.plan(1) + + fastify.register((instance, opts, next) => { + instance.get('/', (req, reply) => { + const original = req.compileValidationSchema(defaultSchema) + const referenced = req.getValidationFunction(defaultSchema) + + t.assert.strictEqual(original, referenced) + + reply.send({ hello: 'world' }) + }) + + next() + }) + + await fastify.inject('/') + }) + + await ntst.test('Should return undefined if no schema compiled', async t => { + const fastify = Fastify() + + t.plan(1) + + fastify.register((instance, opts, next) => { + instance.get('/', (req, reply) => { + const validate = req.getValidationFunction(defaultSchema) + + t.assert.ok(!validate) + + reply.send({ hello: 'world' }) + }) + + next() + }) + + await fastify.inject('/') + }) + + await ntst.test( + 'Should return the validation function from each HTTP part', + async t => { + const fastify = Fastify() + let headerValidation = null + let customValidation = null + + t.plan(15) + + fastify.register((instance, opts, next) => { + instance.post( + '/:id', + { + schema: requestSchema + }, + (req, reply) => { + const { params } = req + + switch (params.id) { + case 1: + customValidation = req.compileValidationSchema( + defaultSchema + ) + t.assert.ok(req.getValidationFunction('body')) + t.assert.ok(req.getValidationFunction('body')({ hello: 'world' })) + t.assert.ok(!req.getValidationFunction('body')({ world: 'hello' }) + ) + break + case 2: + headerValidation = req.getValidationFunction('headers') + t.assert.ok(headerValidation) + t.assert.ok(headerValidation({ 'x-foo': 'world' })) + t.assert.ok(!headerValidation({ 'x-foo': [] })) + break + case 3: + t.assert.ok(req.getValidationFunction('params')) + t.assert.ok(req.getValidationFunction('params')({ id: 123 })) + t.assert.ok(!req.getValidationFunction('params'({ id: 1.2 }))) + break + case 4: + t.assert.ok(req.getValidationFunction('querystring')) + t.assert.ok( + req.getValidationFunction('querystring')({ foo: 'bar' }) + ) + t.assert.ok(!req.getValidationFunction('querystring')({ + foo: 'not-bar' + }) + ) + break + case 5: + t.assert.strictEqual( + customValidation, + req.getValidationFunction(defaultSchema) + ) + t.assert.ok(customValidation({ hello: 'world' })) + t.assert.ok(!customValidation({})) + t.assert.strictEqual( + headerValidation, + req.getValidationFunction('headers') + ) + break + default: + t.assert.fail('Invalid id') + } + + reply.send({ hello: 'world' }) + } + ) + + next() + }) + const promises = [] + + for (let i = 1; i < 6; i++) { + promises.push( + fastify.inject({ + path: `/${i}`, + method: 'post', + query: { foo: 'bar' }, + payload: { + hello: 'world' + }, + headers: { + 'x-foo': 'x-bar' + } + }) + ) + } + + await Promise.all(promises) + } + ) + + await ntst.test('Should return a validation function - nested', async t => { + const fastify = Fastify() + let called = false + const custom = ({ schema, httpPart, url, method }) => { + t.assert.strictEqual(schema, defaultSchema) + t.assert.strictEqual(url, '/') + t.assert.strictEqual(method, 'GET') + t.assert.ok(!httpPart) + + called = true + return () => true + } + + t.plan(6) + + fastify.setValidatorCompiler(custom) + + fastify.register((instance, opts, next) => { + instance.get('/', (req, reply) => { + const original = req.compileValidationSchema(defaultSchema) + const referenced = req.getValidationFunction(defaultSchema) + + t.assert.strictEqual(original, referenced) + t.assert.strictEqual(called, true) + + reply.send({ hello: 'world' }) + }) + + next() + }) + + await fastify.inject('/') + }) + + await ntst.test( + 'Should return undefined if no schema compiled - nested', + async t => { + const fastify = Fastify() + let called = 0 + const custom = ({ schema, httpPart, url, method }) => { + called++ + return () => true + } + + t.plan(3) + + fastify.setValidatorCompiler(custom) + + fastify.get('/', (req, reply) => { + const validate = req.compileValidationSchema(defaultSchema) + + t.assert.strictEqual(typeof validate, 'function') + + reply.send({ hello: 'world' }) + }) + + fastify.register( + (instance, opts, next) => { + instance.get('/', (req, reply) => { + const validate = req.getValidationFunction(defaultSchema) + + t.assert.ok(!validate) + t.assert.strictEqual(called, 1) + + reply.send({ hello: 'world' }) + }) + + next() + }, + { prefix: '/nested' } + ) + + await fastify.inject('/') + await fastify.inject('/nested') + } + ) + + await ntst.test('Should per-route defined validation compiler', async t => { + const fastify = Fastify() + let validateParent + let validateChild + let calledParent = 0 + let calledChild = 0 + const customParent = ({ schema, httpPart, url, method }) => { + calledParent++ + return () => true + } + + const customChild = ({ schema, httpPart, url, method }) => { + calledChild++ + return () => true + } + + t.plan(5) + + fastify.setValidatorCompiler(customParent) + + fastify.get('/', (req, reply) => { + validateParent = req.compileValidationSchema(defaultSchema) + + t.assert.strictEqual(typeof validateParent, 'function') + + reply.send({ hello: 'world' }) + }) + + fastify.register( + (instance, opts, next) => { + instance.get( + '/', + { + validatorCompiler: customChild + }, + (req, reply) => { + const validate1 = req.compileValidationSchema(defaultSchema) + validateChild = req.getValidationFunction(defaultSchema) + + t.assert.strictEqual(validate1, validateChild) + t.assert.notStrictEqual(validateParent, validateChild) + t.assert.strictEqual(calledParent, 1) + t.assert.strictEqual(calledChild, 1) + + reply.send({ hello: 'world' }) + } + ) + + next() + }, + { prefix: '/nested' } + ) + + await fastify.inject('/') + await fastify.inject('/nested') + }) + }) + + await tst.test('#validate', async ntst => { + ntst.plan(3) + + await ntst.test( + 'Should return true/false if input valid - Route without schema', + async t => { + const fastify = Fastify() + + t.plan(2) + + fastify.register((instance, opts, next) => { + instance.get('/', (req, reply) => { + const isNotValid = req.validateInput( + { world: 'string' }, + defaultSchema + ) + const isValid = req.validateInput({ hello: 'string' }, defaultSchema) + + t.assert.ok(!isNotValid) + t.assert.ok(isValid) + + reply.send({ hello: 'world' }) + }) + + next() + }) + + await fastify.inject('/') + } + ) + + await ntst.test( + 'Should use the custom validator compiler for the route', + async t => { + const fastify = Fastify() + let parentCalled = 0 + let childCalled = 0 + const customParent = () => { + parentCalled++ + + return () => true + } + + const customChild = ({ schema, httpPart, url, method }) => { + t.assert.strictEqual(schema, defaultSchema) + t.assert.strictEqual(url, '/') + t.assert.strictEqual(method, 'GET') + t.assert.strictEqual(httpPart, 'querystring') + + return input => { + childCalled++ + t.assert.deepStrictEqual(input, { hello: 'world' }) + return true + } + } + + t.plan(10) + + fastify.setValidatorCompiler(customParent) + + fastify.register((instance, opts, next) => { + instance.get( + '/', + { validatorCompiler: customChild }, + (req, reply) => { + const ok = req.validateInput( + { hello: 'world' }, + defaultSchema, + 'querystring' + ) + const ok2 = req.validateInput({ hello: 'world' }, defaultSchema) + + t.assert.ok(ok) + t.assert.ok(ok2) + t.assert.strictEqual(childCalled, 2) + t.assert.strictEqual(parentCalled, 0) + + reply.send({ hello: 'world' }) + } + ) + + next() + }) + + await fastify.inject('/') + } + ) + + await ntst.test( + 'Should return true/false if input valid - With Schema for Route defined and scoped validator compiler', + async t => { + const validator = new Ajv() + const fastify = Fastify() + const childCounter = { + query: 0, + body: 0, + params: 0, + headers: 0 + } + let parentCalled = 0 + + const parent = () => { + parentCalled++ + return () => true + } + const child = ({ schema, httpPart, url, method }) => { + httpPart = httpPart === 'querystring' ? 'query' : httpPart + const validate = validator.compile(schema) + + return input => { + childCounter[httpPart]++ + return validate(input) + } + } + + t.plan(13) + + fastify.setValidatorCompiler(parent) + fastify.register((instance, opts, next) => { + instance.setValidatorCompiler(child) + instance.post( + '/:id', + { + schema: requestSchema + }, + (req, reply) => { + const { params } = req + + switch (parseInt(params.id)) { + case 1: + t.assert.ok(req.validateInput({ hello: 'world' }, 'body')) + t.assert.ok(!req.validateInput({ hello: [], world: 'foo' }, 'body')) + break + case 2: + t.assert.ok(!req.validateInput({ foo: 'something' }, 'querystring')) + t.assert.ok(req.validateInput({ foo: 'bar' }, 'querystring')) + break + case 3: + t.assert.ok(!req.validateInput({ 'x-foo': [] }, 'headers')) + t.assert.ok(req.validateInput({ 'x-foo': 'something' }, 'headers')) + break + case 4: + t.assert.ok(req.validateInput({ id: 1 }, 'params')) + t.assert.ok(!req.validateInput({ id: params.id }, 'params')) + break + default: + t.assert.fail('Invalid id') + } + + reply.send({ hello: 'world' }) + } + ) + + next() + }) + + const promises = [] + + for (let i = 1; i < 5; i++) { + promises.push( + fastify.inject({ + path: `/${i}`, + method: 'post', + query: {}, + payload: { + hello: 'world' + } + }) + ) + } + + await Promise.all(promises) + + t.assert.strictEqual(childCounter.query, 6) // 4 calls made + 2 custom validations + t.assert.strictEqual(childCounter.headers, 6) // 4 calls made + 2 custom validations + t.assert.strictEqual(childCounter.body, 6) // 4 calls made + 2 custom validations + t.assert.strictEqual(childCounter.params, 6) // 4 calls made + 2 custom validations + t.assert.strictEqual(parentCalled, 0) + } + ) + }) + }) +}) diff --git a/services/slides/node_modules/fastify/test/internals/request.test.js b/services/slides/node_modules/fastify/test/internals/request.test.js new file mode 100644 index 0000000000000000000000000000000000000000..7ca08eb6d564c3bc89796129cdb4bd3587baf2e9 --- /dev/null +++ b/services/slides/node_modules/fastify/test/internals/request.test.js @@ -0,0 +1,506 @@ +'use strict' + +const { test } = require('node:test') + +const Request = require('../../lib/request') +const Context = require('../../lib/context') +const { + kReply, + kRequest, + kOptions +} = require('../../lib/symbols') + +process.removeAllListeners('warning') + +test('Regular request', t => { + const headers = { + host: 'hostname' + } + const req = { + method: 'GET', + url: '/', + socket: { remoteAddress: 'ip' }, + headers + } + const context = new Context({ + schema: { + body: { + type: 'object', + required: ['hello'], + properties: { + hello: { type: 'string' } + } + } + }, + config: { + some: 'config', + url: req.url, + method: req.method + }, + server: { + [kReply]: {}, + [kRequest]: Request, + [kOptions]: { + requestIdLogLabel: 'reqId' + }, + server: {} + } + }) + req.connection = req.socket + const request = new Request('id', 'params', req, 'query', 'log', context) + t.assert.ok(request instanceof Request) + t.assert.ok(request.validateInput instanceof Function) + t.assert.ok(request.getValidationFunction instanceof Function) + t.assert.ok(request.compileValidationSchema instanceof Function) + t.assert.strictEqual(request.id, 'id') + t.assert.strictEqual(request.params, 'params') + t.assert.strictEqual(request.raw, req) + t.assert.strictEqual(request.query, 'query') + t.assert.strictEqual(request.headers, headers) + t.assert.strictEqual(request.log, 'log') + t.assert.strictEqual(request.ip, 'ip') + t.assert.strictEqual(request.ips, undefined) + t.assert.strictEqual(request.host, 'hostname') + t.assert.strictEqual(request.body, undefined) + t.assert.strictEqual(request.method, 'GET') + t.assert.strictEqual(request.url, '/') + t.assert.strictEqual(request.originalUrl, '/') + t.assert.strictEqual(request.socket, req.socket) + t.assert.strictEqual(request.protocol, 'http') + // Aim to not bad property keys (including Symbols) + t.assert.ok(!('undefined' in request)) +}) + +test('Request with undefined config', t => { + const headers = { + host: 'hostname' + } + const req = { + method: 'GET', + url: '/', + socket: { remoteAddress: 'ip' }, + headers + } + const context = new Context({ + schema: { + body: { + type: 'object', + required: ['hello'], + properties: { + hello: { type: 'string' } + } + } + }, + server: { + [kReply]: {}, + [kRequest]: Request, + [kOptions]: { + requestIdLogLabel: 'reqId' + }, + server: {} + } + }) + req.connection = req.socket + const request = new Request('id', 'params', req, 'query', 'log', context) + t.assert.ok(request, Request) + t.assert.ok(request.validateInput, Function) + t.assert.ok(request.getValidationFunction, Function) + t.assert.ok(request.compileValidationSchema, Function) + t.assert.strictEqual(request.id, 'id') + t.assert.strictEqual(request.params, 'params') + t.assert.strictEqual(request.raw, req) + t.assert.strictEqual(request.query, 'query') + t.assert.strictEqual(request.headers, headers) + t.assert.strictEqual(request.log, 'log') + t.assert.strictEqual(request.ip, 'ip') + t.assert.strictEqual(request.ips, undefined) + t.assert.strictEqual(request.hostname, 'hostname') + t.assert.strictEqual(request.body, undefined) + t.assert.strictEqual(request.method, 'GET') + t.assert.strictEqual(request.url, '/') + t.assert.strictEqual(request.originalUrl, '/') + t.assert.strictEqual(request.socket, req.socket) + t.assert.strictEqual(request.protocol, 'http') + + // Aim to not bad property keys (including Symbols) + t.assert.ok(!('undefined' in request)) +}) + +test('Regular request - hostname from authority', t => { + t.plan(3) + const headers = { + ':authority': 'authority' + } + const req = { + method: 'GET', + url: '/', + socket: { remoteAddress: 'ip' }, + headers + } + const context = new Context({ + schema: { + body: { + type: 'object', + required: ['hello'], + properties: { + hello: { type: 'string' } + } + } + }, + config: { + some: 'config', + url: req.url, + method: req.method + }, + server: { + [kReply]: {}, + [kRequest]: Request, + [kOptions]: { + requestIdLogLabel: 'reqId' + }, + server: {} + } + }) + + const request = new Request('id', 'params', req, 'query', 'log', context) + t.assert.ok(request instanceof Request) + t.assert.strictEqual(request.host, 'authority') + t.assert.strictEqual(request.port, null) +}) + +test('Regular request - host header has precedence over authority', t => { + t.plan(3) + const headers = { + host: 'hostname', + ':authority': 'authority' + } + const req = { + method: 'GET', + url: '/', + socket: { remoteAddress: 'ip' }, + headers + } + const context = new Context({ + schema: { + body: { + type: 'object', + required: ['hello'], + properties: { + hello: { type: 'string' } + } + } + }, + config: { + some: 'config', + url: req.url, + method: req.method + }, + server: { + [kReply]: {}, + [kRequest]: Request, + [kOptions]: { + requestIdLogLabel: 'reqId' + }, + server: {} + } + }) + const request = new Request('id', 'params', req, 'query', 'log', context) + t.assert.ok(request instanceof Request) + t.assert.strictEqual(request.host, 'hostname') + t.assert.strictEqual(request.port, null) +}) + +test('Request with trust proxy', t => { + t.plan(18) + const headers = { + 'x-forwarded-for': '2.2.2.2, 1.1.1.1', + 'x-forwarded-host': 'fastify.test' + } + const req = { + method: 'GET', + url: '/', + socket: { remoteAddress: 'ip' }, + headers + } + const context = new Context({ + schema: { + body: { + type: 'object', + required: ['hello'], + properties: { + hello: { type: 'string' } + } + } + }, + config: { + some: 'config', + url: req.url, + method: req.method + }, + server: { + [kReply]: {}, + [kRequest]: Request, + [kOptions]: { + requestIdLogLabel: 'reqId' + } + } + }) + + const TpRequest = Request.buildRequest(Request, true) + const request = new TpRequest('id', 'params', req, 'query', 'log', context) + t.assert.ok(request instanceof TpRequest) + t.assert.strictEqual(request.id, 'id') + t.assert.strictEqual(request.params, 'params') + t.assert.deepStrictEqual(request.raw, req) + t.assert.strictEqual(request.query, 'query') + t.assert.strictEqual(request.headers, headers) + t.assert.strictEqual(request.log, 'log') + t.assert.strictEqual(request.ip, '2.2.2.2') + t.assert.deepStrictEqual(request.ips, ['ip', '1.1.1.1', '2.2.2.2']) + t.assert.strictEqual(request.host, 'fastify.test') + t.assert.strictEqual(request.body, undefined) + t.assert.strictEqual(request.method, 'GET') + t.assert.strictEqual(request.url, '/') + t.assert.strictEqual(request.socket, req.socket) + t.assert.strictEqual(request.protocol, 'http') + t.assert.ok(request.validateInput instanceof Function) + t.assert.ok(request.getValidationFunction instanceof Function) + t.assert.ok(request.compileValidationSchema instanceof Function) +}) + +test('Request with trust proxy, encrypted', t => { + t.plan(2) + const headers = { + 'x-forwarded-for': '2.2.2.2, 1.1.1.1', + 'x-forwarded-host': 'fastify.test' + } + const req = { + method: 'GET', + url: '/', + socket: { remoteAddress: 'ip', encrypted: true }, + headers + } + + const TpRequest = Request.buildRequest(Request, true) + const request = new TpRequest('id', 'params', req, 'query', 'log') + t.assert.ok(request instanceof TpRequest) + t.assert.strictEqual(request.protocol, 'https') +}) + +test('Request with trust proxy - no x-forwarded-host header', t => { + t.plan(2) + const headers = { + 'x-forwarded-for': '2.2.2.2, 1.1.1.1', + host: 'hostname' + } + const req = { + method: 'GET', + url: '/', + socket: { remoteAddress: 'ip' }, + headers + } + const context = new Context({ + schema: { + body: { + type: 'object', + required: ['hello'], + properties: { + hello: { type: 'string' } + } + } + }, + config: { + some: 'config', + url: req.url, + method: req.method + }, + server: { + [kReply]: {}, + [kRequest]: Request, + [kOptions]: { + requestIdLogLabel: 'reqId' + }, + server: {} + } + }) + + const TpRequest = Request.buildRequest(Request, true) + const request = new TpRequest('id', 'params', req, 'query', 'log', context) + t.assert.ok(request instanceof TpRequest) + t.assert.strictEqual(request.host, 'hostname') +}) + +test('Request with trust proxy - no x-forwarded-host header and fallback to authority', t => { + t.plan(2) + const headers = { + 'x-forwarded-for': '2.2.2.2, 1.1.1.1', + ':authority': 'authority' + } + const req = { + method: 'GET', + url: '/', + socket: { remoteAddress: 'ip' }, + headers + } + const context = new Context({ + schema: { + body: { + type: 'object', + required: ['hello'], + properties: { + hello: { type: 'string' } + } + } + }, + config: { + some: 'config', + url: req.url, + method: req.method + }, + server: { + [kReply]: {}, + [kRequest]: Request, + [kOptions]: { + requestIdLogLabel: 'reqId' + }, + server: {} + } + }) + + const TpRequest = Request.buildRequest(Request, true) + const request = new TpRequest('id', 'params', req, 'query', 'log', context) + t.assert.ok(request instanceof TpRequest) + t.assert.strictEqual(request.host, 'authority') +}) + +test('Request with trust proxy - x-forwarded-host header has precedence over host', t => { + t.plan(2) + const headers = { + 'x-forwarded-for': ' 2.2.2.2, 1.1.1.1', + 'x-forwarded-host': 'fastify.test', + host: 'hostname' + } + const req = { + method: 'GET', + url: '/', + socket: { remoteAddress: 'ip' }, + headers + } + + const TpRequest = Request.buildRequest(Request, true) + const request = new TpRequest('id', 'params', req, 'query', 'log') + t.assert.ok(request instanceof TpRequest) + t.assert.strictEqual(request.host, 'fastify.test') +}) + +test('Request with trust proxy - handles multiple entries in x-forwarded-host/proto', t => { + t.plan(3) + const headers = { + 'x-forwarded-host': 'example2.com, fastify.test', + 'x-forwarded-proto': 'http, https' + } + const req = { + method: 'GET', + url: '/', + socket: { remoteAddress: 'ip' }, + headers + } + + const TpRequest = Request.buildRequest(Request, true) + const request = new TpRequest('id', 'params', req, 'query', 'log') + t.assert.ok(request instanceof TpRequest) + t.assert.strictEqual(request.host, 'fastify.test') + t.assert.strictEqual(request.protocol, 'https') +}) + +test('Request with trust proxy - plain', t => { + t.plan(1) + const headers = { + 'x-forwarded-for': '2.2.2.2, 1.1.1.1', + 'x-forwarded-host': 'fastify.test' + } + const req = { + method: 'GET', + url: '/', + socket: { remoteAddress: 'ip' }, + headers + } + + const TpRequest = Request.buildRequest(Request, true) + const request = new TpRequest('id', 'params', req, 'query', 'log') + t.assert.deepStrictEqual(request.protocol, 'http') +}) + +test('Request with undefined socket', t => { + t.plan(18) + const headers = { + host: 'hostname' + } + const req = { + method: 'GET', + url: '/', + socket: undefined, + headers + } + const context = new Context({ + schema: { + body: { + type: 'object', + required: ['hello'], + properties: { + hello: { type: 'string' } + } + } + }, + config: { + some: 'config', + url: req.url, + method: req.method + }, + server: { + [kReply]: {}, + [kRequest]: Request, + [kOptions]: { + requestIdLogLabel: 'reqId' + }, + server: {} + } + }) + const request = new Request('id', 'params', req, 'query', 'log', context) + t.assert.ok(request instanceof Request) + t.assert.strictEqual(request.id, 'id') + t.assert.strictEqual(request.params, 'params') + t.assert.deepStrictEqual(request.raw, req) + t.assert.strictEqual(request.query, 'query') + t.assert.strictEqual(request.headers, headers) + t.assert.strictEqual(request.log, 'log') + t.assert.strictEqual(request.ip, undefined) + t.assert.strictEqual(request.ips, undefined) + t.assert.strictEqual(request.host, 'hostname') + t.assert.deepStrictEqual(request.body, undefined) + t.assert.strictEqual(request.method, 'GET') + t.assert.strictEqual(request.url, '/') + t.assert.strictEqual(request.protocol, undefined) + t.assert.deepStrictEqual(request.socket, req.socket) + t.assert.ok(request.validateInput instanceof Function) + t.assert.ok(request.getValidationFunction instanceof Function) + t.assert.ok(request.compileValidationSchema instanceof Function) +}) + +test('Request with trust proxy and undefined socket', t => { + t.plan(1) + const headers = { + 'x-forwarded-for': '2.2.2.2, 1.1.1.1', + 'x-forwarded-host': 'fastify.test' + } + const req = { + method: 'GET', + url: '/', + socket: undefined, + headers + } + + const TpRequest = Request.buildRequest(Request, true) + const request = new TpRequest('id', 'params', req, 'query', 'log') + t.assert.deepStrictEqual(request.protocol, undefined) +}) diff --git a/services/slides/node_modules/fastify/test/internals/schema-controller-perf.test.js b/services/slides/node_modules/fastify/test/internals/schema-controller-perf.test.js new file mode 100644 index 0000000000000000000000000000000000000000..0741b41f60590132aa3614ebb86b552a42769943 --- /dev/null +++ b/services/slides/node_modules/fastify/test/internals/schema-controller-perf.test.js @@ -0,0 +1,40 @@ +const { sep } = require('node:path') +const { test } = require('node:test') +const Fastify = require('../../fastify') + +test('SchemaController are NOT loaded when the controllers are custom', async t => { + const app = Fastify({ + schemaController: { + compilersFactory: { + buildValidator: () => () => { }, + buildSerializer: () => () => { } + } + } + }) + + await app.ready() + + const loaded = Object.keys(require.cache) + const ajvModule = loaded.find((path) => path.includes(`@fastify${sep}ajv-compiler`)) + const stringifyModule = loaded.find((path) => path.includes(`@fastify${sep}fast-json-stringify-compiler`)) + + t.assert.equal(ajvModule, undefined, 'Ajv compiler is loaded') + t.assert.equal(stringifyModule, undefined, 'Stringify compiler is loaded') +}) + +test('SchemaController are loaded when the controllers are not custom', async t => { + const app = Fastify() + await app.ready() + + const loaded = Object.keys(require.cache) + const ajvModule = loaded.find((path) => path.includes(`@fastify${sep}ajv-compiler`)) + const stringifyModule = loaded.find((path) => path.includes(`@fastify${sep}fast-json-stringify-compiler`)) + + t.after(() => { + delete require.cache[ajvModule] + delete require.cache[stringifyModule] + }) + + t.assert.ok(ajvModule, 'Ajv compiler is loaded') + t.assert.ok(stringifyModule, 'Stringify compiler is loaded') +}) diff --git a/services/slides/node_modules/fastify/test/internals/server.test.js b/services/slides/node_modules/fastify/test/internals/server.test.js new file mode 100644 index 0000000000000000000000000000000000000000..b26a172dfcc920747589fb1af65ad62019dacd04 --- /dev/null +++ b/services/slides/node_modules/fastify/test/internals/server.test.js @@ -0,0 +1,91 @@ +'use strict' + +const { test } = require('node:test') +const proxyquire = require('proxyquire') + +const Fastify = require('../../fastify') +const { createServer } = require('../../lib/server') + +const handler = (req, res) => { + res.writeHead(200, { 'Content-Type': 'application/json' }) + res.end(JSON.stringify({ data: 'Hello World!' })) +} + +test('start listening', async t => { + const { server, listen } = createServer({}, handler) + await listen.call(Fastify(), { port: 0, host: 'localhost' }) + server.close() + t.assert.ok(true, 'server started') +}) + +test('DNS errors does not stop the main server on localhost - promise interface', async t => { + const { createServer } = proxyquire('../../lib/server', { + 'node:dns': { + lookup: (hostname, options, cb) => { + cb(new Error('DNS error')) + } + } + }) + const { server, listen } = createServer({}, handler) + await listen.call(Fastify(), { port: 0, host: 'localhost' }) + server.close() + t.assert.ok(true, 'server started') +}) + +test('DNS errors does not stop the main server on localhost - callback interface', (t, done) => { + t.plan(2) + const { createServer } = proxyquire('../../lib/server', { + 'node:dns': { + lookup: (hostname, options, cb) => { + cb(new Error('DNS error')) + } + } + }) + const { server, listen } = createServer({}, handler) + listen.call(Fastify(), { port: 0, host: 'localhost' }, (err) => { + t.assert.ifError(err) + server.close() + t.assert.ok(true, 'server started') + done() + }) +}) + +test('DNS returns empty binding', (t, done) => { + t.plan(2) + const { createServer } = proxyquire('../../lib/server', { + 'node:dns': { + lookup: (hostname, options, cb) => { + cb(null, []) + } + } + }) + const { server, listen } = createServer({}, handler) + listen.call(Fastify(), { port: 0, host: 'localhost' }, (err) => { + t.assert.ifError(err) + server.close() + t.assert.ok(true, 'server started') + done() + }) +}) + +test('DNS returns more than two binding', (t, done) => { + t.plan(2) + const { createServer } = proxyquire('../../lib/server', { + 'node:dns': { + lookup: (hostname, options, cb) => { + cb(null, [ + { address: '::1', family: 6 }, + { address: '127.0.0.1', family: 4 }, + { address: '0.0.0.0', family: 4 } + ]) + } + } + }) + const { server, listen } = createServer({}, handler) + listen.call(Fastify(), { port: 0, host: 'localhost' }, (err) => { + t.assert.ifError(err) + server.close() + t.assert.ok(true, 'server started') + done() + }) +}) diff --git a/services/slides/node_modules/fastify/test/internals/validation.test.js b/services/slides/node_modules/fastify/test/internals/validation.test.js new file mode 100644 index 0000000000000000000000000000000000000000..7fe4738c56ad51418520e0b5a7ec2f21f1d15f0a --- /dev/null +++ b/services/slides/node_modules/fastify/test/internals/validation.test.js @@ -0,0 +1,352 @@ +'use strict' + +const { test } = require('node:test') + +const Ajv = require('ajv') +const ajv = new Ajv({ coerceTypes: true }) + +const validation = require('../../lib/validation') +const { normalizeSchema } = require('../../lib/schemas') +const symbols = require('../../lib/validation').symbols +const { kSchemaVisited } = require('../../lib/symbols') + +test('Symbols', t => { + t.plan(5) + t.assert.strictEqual(typeof symbols.responseSchema, 'symbol') + t.assert.strictEqual(typeof symbols.bodySchema, 'symbol') + t.assert.strictEqual(typeof symbols.querystringSchema, 'symbol') + t.assert.strictEqual(typeof symbols.paramsSchema, 'symbol') + t.assert.strictEqual(typeof symbols.headersSchema, 'symbol') +}) + +;['compileSchemasForValidation', + 'compileSchemasForSerialization'].forEach(func => { + test(`${func} schema - missing schema`, t => { + t.plan(2) + const context = {} + validation[func](context) + t.assert.strictEqual(typeof context[symbols.bodySchema], 'undefined') + t.assert.strictEqual(typeof context[symbols.responseSchema], 'undefined') + }) + + test(`${func} schema - missing output schema`, t => { + t.plan(1) + const context = { schema: {} } + validation[func](context, null) + t.assert.strictEqual(typeof context[symbols.responseSchema], 'undefined') + }) +}) + +test('build schema - output schema', t => { + t.plan(2) + const opts = { + schema: { + response: { + '2xx': { + type: 'object', + properties: { + hello: { type: 'string' } + } + }, + 201: { + type: 'object', + properties: { + hello: { type: 'number' } + } + } + } + } + } + validation.compileSchemasForSerialization(opts, ({ schema, method, url, httpPart }) => ajv.compile(schema)) + t.assert.strictEqual(typeof opts[symbols.responseSchema]['2xx'], 'function') + t.assert.strictEqual(typeof opts[symbols.responseSchema]['201'], 'function') +}) + +test('build schema - body schema', t => { + t.plan(1) + const opts = { + schema: { + body: { + type: 'object', + properties: { + hello: { type: 'string' } + } + } + } + } + validation.compileSchemasForValidation(opts, ({ schema, method, url, httpPart }) => ajv.compile(schema)) + t.assert.strictEqual(typeof opts[symbols.bodySchema], 'function') +}) + +test('build schema - body with multiple content type schemas', t => { + t.plan(2) + const opts = { + schema: { + body: { + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + hello: { type: 'string' } + } + } + }, + 'text/plain': { + schema: { type: 'string' } + } + } + } + } + } + validation.compileSchemasForValidation(opts, ({ schema, method, url, httpPart }) => ajv.compile(schema)) + t.assert.ok(opts[symbols.bodySchema]['application/json'], 'function') + t.assert.ok(opts[symbols.bodySchema]['text/plain'], 'function') +}) + +test('build schema - avoid repeated normalize schema', t => { + t.plan(3) + const serverConfig = {} + const opts = { + schema: { + query: { + type: 'object', + properties: { + hello: { type: 'string' } + } + } + } + } + opts.schema = normalizeSchema(opts.schema, serverConfig) + t.assert.notStrictEqual(kSchemaVisited, undefined) + t.assert.strictEqual(opts.schema[kSchemaVisited], true) + t.assert.strictEqual(opts.schema, normalizeSchema(opts.schema, serverConfig)) +}) + +test('build schema - query schema', t => { + t.plan(2) + const serverConfig = {} + const opts = { + schema: { + query: { + type: 'object', + properties: { + hello: { type: 'string' } + } + } + } + } + opts.schema = normalizeSchema(opts.schema, serverConfig) + validation.compileSchemasForValidation(opts, ({ schema, method, url, httpPart }) => ajv.compile(schema)) + t.assert.ok(typeof opts[symbols.querystringSchema].schema.type === 'string') + t.assert.strictEqual(typeof opts[symbols.querystringSchema], 'function') +}) + +test('build schema - query schema abbreviated', t => { + t.plan(2) + const serverConfig = {} + const opts = { + schema: { + query: { + type: 'object', + properties: { + hello: { type: 'string' } + } + } + } + } + opts.schema = normalizeSchema(opts.schema, serverConfig) + validation.compileSchemasForValidation(opts, ({ schema, method, url, httpPart }) => ajv.compile(schema)) + t.assert.ok(typeof opts[symbols.querystringSchema].schema.type === 'string') + t.assert.strictEqual(typeof opts[symbols.querystringSchema], 'function') +}) + +test('build schema - querystring schema', t => { + t.plan(2) + const opts = { + schema: { + querystring: { + type: 'object', + properties: { + hello: { type: 'string' } + } + } + } + } + validation.compileSchemasForValidation(opts, ({ schema, method, url, httpPart }) => ajv.compile(schema)) + t.assert.ok(typeof opts[symbols.querystringSchema].schema.type === 'string') + t.assert.strictEqual(typeof opts[symbols.querystringSchema], 'function') +}) + +test('build schema - querystring schema abbreviated', t => { + t.plan(2) + const serverConfig = {} + const opts = { + schema: { + querystring: { + type: 'object', + properties: { + hello: { type: 'string' } + } + } + } + } + opts.schema = normalizeSchema(opts.schema, serverConfig) + validation.compileSchemasForValidation(opts, ({ schema, method, url, httpPart }) => ajv.compile(schema)) + t.assert.ok(typeof opts[symbols.querystringSchema].schema.type === 'string') + t.assert.strictEqual(typeof opts[symbols.querystringSchema], 'function') +}) + +test('build schema - must throw if querystring and query schema exist', t => { + t.plan(2) + try { + const serverConfig = {} + const opts = { + schema: { + query: { + type: 'object', + properties: { + hello: { type: 'string' } + } + }, + querystring: { + type: 'object', + properties: { + hello: { type: 'string' } + } + } + } + } + opts.schema = normalizeSchema(opts.schema, serverConfig) + } catch (err) { + t.assert.strictEqual(err.code, 'FST_ERR_SCH_DUPLICATE') + t.assert.strictEqual(err.message, 'Schema with \'querystring\' already present!') + } +}) + +test('build schema - params schema', t => { + t.plan(1) + const opts = { + schema: { + params: { + type: 'object', + properties: { + hello: { type: 'string' } + } + } + } + } + validation.compileSchemasForValidation(opts, ({ schema, method, url, httpPart }) => ajv.compile(schema)) + t.assert.strictEqual(typeof opts[symbols.paramsSchema], 'function') +}) + +test('build schema - headers schema', t => { + t.plan(1) + const opts = { + schema: { + headers: { + type: 'object', + properties: { + 'content-type': { type: 'string' } + } + } + } + } + validation.compileSchemasForValidation(opts, ({ schema, method, url, httpPart }) => ajv.compile(schema)) + t.assert.strictEqual(typeof opts[symbols.headersSchema], 'function') +}) + +test('build schema - headers are lowercase', t => { + t.plan(1) + const opts = { + schema: { + headers: { + type: 'object', + properties: { + 'Content-Type': { type: 'string' } + } + } + } + } + validation.compileSchemasForValidation(opts, ({ schema, method, url, httpPart }) => { + t.assert.ok(schema.properties['content-type'], 'lowercase content-type exists') + return () => { } + }) +}) + +test('build schema - headers are not lowercased in case of custom object', t => { + t.plan(1) + + class Headers { } + const opts = { + schema: { + headers: new Headers() + } + } + validation.compileSchemasForValidation(opts, ({ schema, method, url, httpPart }) => { + t.assert.ok(schema, Headers) + return () => { } + }) +}) + +test('build schema - headers are not lowercased in case of custom validator provided', t => { + t.plan(1) + + class Headers { } + const opts = { + schema: { + headers: new Headers() + } + } + validation.compileSchemasForValidation(opts, ({ schema, method, url, httpPart }) => { + t.assert.ok(schema, Headers) + return () => { } + }, true) +}) + +test('build schema - uppercased headers are not included', t => { + t.plan(1) + const opts = { + schema: { + headers: { + type: 'object', + properties: { + 'Content-Type': { type: 'string' } + } + } + } + } + validation.compileSchemasForValidation(opts, ({ schema, method, url, httpPart }) => { + t.assert.ok(!('Content-Type' in schema.properties), 'uppercase does not exist') + return () => { } + }) +}) + +test('build schema - mixed schema types are individually skipped or normalized', t => { + t.plan(2) + + class CustomSchemaClass { } + + const testCases = [{ + schema: { + body: new CustomSchemaClass() + }, + assertions: (schema) => { + t.assert.ok(schema.body, CustomSchemaClass) + } + }, { + schema: { + response: { + 200: new CustomSchemaClass() + } + }, + assertions: (schema) => { + t.assert.ok(schema.response[200], CustomSchemaClass) + } + }] + + testCases.forEach((testCase) => { + const result = normalizeSchema(testCase.schema, {}) + testCase.assertions(result) + }) +}) diff --git a/services/slides/node_modules/fastify/test/issue-4959.test.js b/services/slides/node_modules/fastify/test/issue-4959.test.js new file mode 100644 index 0000000000000000000000000000000000000000..87c98a3a52004da3eb349a2dd90d8f9a191838b7 --- /dev/null +++ b/services/slides/node_modules/fastify/test/issue-4959.test.js @@ -0,0 +1,118 @@ +'use strict' + +const { test } = require('node:test') +const http = require('node:http') +const Fastify = require('../fastify') +const { setTimeout } = require('node:timers') + +/* +* Ensure that a socket error during the request does not cause the +* onSend hook to be called multiple times. +* +* @see https://github.com/fastify/fastify/issues/4959 +*/ +function runBadClientCall (reqOptions, payload, waitBeforeDestroy) { + let innerResolve, innerReject + const promise = new Promise((resolve, reject) => { + innerResolve = resolve + innerReject = reject + }) + + const postData = JSON.stringify(payload) + + const req = http.request({ + ...reqOptions, + headers: { + 'Content-Type': 'application/json', + 'Content-Length': Buffer.byteLength(postData) + } + }, () => { + innerReject(new Error('Request should have failed')) + }) + + // Kill the socket after the request has been fully written. + // Destroying it on `connect` can race before any bytes are sent, making the + // server-side assertions (hooks/handler) non-deterministic. + // + // To keep the test deterministic, we optionally wait for a server-side signal + // (e.g. onSend entered) before aborting the client. + let socket + req.on('socket', (s) => { socket = s }) + req.on('finish', () => { + if (waitBeforeDestroy && typeof waitBeforeDestroy.then === 'function') { + Promise.race([ + waitBeforeDestroy, + new Promise(resolve => setTimeout(resolve, 200)) + ]).then(() => { + if (socket) socket.destroy() + }, innerResolve) + return + } + setTimeout(() => { socket.destroy() }, 0) + }) + req.on('error', innerResolve) + req.write(postData) + req.end() + + return promise +} + +test('should handle a socket error', async (t) => { + t.plan(4) + const fastify = Fastify() + + let resolveOnSendEntered + const onSendEntered = new Promise((resolve) => { + resolveOnSendEntered = resolve + }) + + function shouldNotHappen () { + t.assert.fail('This should not happen') + } + process.on('unhandledRejection', shouldNotHappen) + + t.after(() => { + fastify.close() + process.removeListener('unhandledRejection', shouldNotHappen) + }) + + fastify.addHook('onRequest', async (request, reply) => { + t.assert.ok('onRequest hook called') + }) + + fastify.addHook('onSend', async (request, reply, payload) => { + if (request.onSendCalled) { + t.assert.fail('onSend hook called more than once') + return + } + + t.assert.ok('onSend hook called') + request.onSendCalled = true + + if (resolveOnSendEntered) { + resolveOnSendEntered() + resolveOnSendEntered = null + } + + // Introduce a delay (gives time for client-side abort to happen while the + // request has already been processed, exercising the original issue). + await new Promise(resolve => setTimeout(resolve, 50)) + return payload + }) + + // The handler must be async to trigger the error + fastify.put('/', async (request, reply) => { + t.assert.ok('PUT handler called') + return reply.send({ hello: 'world' }) + }) + + await fastify.listen({ port: 0 }) + + const err = await runBadClientCall({ + hostname: 'localhost', + port: fastify.server.address().port, + path: '/', + method: 'PUT' + }, { test: 'me' }, onSendEntered) + t.assert.equal(err.code, 'ECONNRESET') +}) diff --git a/services/slides/node_modules/fastify/test/keep-alive-timeout.test.js b/services/slides/node_modules/fastify/test/keep-alive-timeout.test.js new file mode 100644 index 0000000000000000000000000000000000000000..84b14bc4cb5702aeeedd672503baca1a175e8265 --- /dev/null +++ b/services/slides/node_modules/fastify/test/keep-alive-timeout.test.js @@ -0,0 +1,42 @@ +'use strict' + +const Fastify = require('..') +const http = require('node:http') +const { test } = require('node:test') + +test('keepAliveTimeout', t => { + t.plan(6) + + try { + Fastify({ keepAliveTimeout: 1.3 }) + t.assert.fail('option must be an integer') + } catch (err) { + t.assert.ok(err) + } + + try { + Fastify({ keepAliveTimeout: [] }) + t.assert.fail('option must be an integer') + } catch (err) { + t.assert.ok(err) + } + + const httpServer = Fastify({ keepAliveTimeout: 1 }).server + t.assert.strictEqual(httpServer.keepAliveTimeout, 1) + + const httpsServer = Fastify({ keepAliveTimeout: 2, https: {} }).server + t.assert.strictEqual(httpsServer.keepAliveTimeout, 2) + + const http2Server = Fastify({ keepAliveTimeout: 3, http2: true }).server + t.assert.notStrictEqual(http2Server.keepAliveTimeout, 3) + + const serverFactory = (handler, _) => { + const server = http.createServer((req, res) => { + handler(req, res) + }) + server.keepAliveTimeout = 5 + return server + } + const customServer = Fastify({ keepAliveTimeout: 4, serverFactory }).server + t.assert.strictEqual(customServer.keepAliveTimeout, 5) +}) diff --git a/services/slides/node_modules/fastify/test/listen.1.test.js b/services/slides/node_modules/fastify/test/listen.1.test.js new file mode 100644 index 0000000000000000000000000000000000000000..92d9cc0de754033edc850cdbe8e0a5a72df431c2 --- /dev/null +++ b/services/slides/node_modules/fastify/test/listen.1.test.js @@ -0,0 +1,154 @@ +'use strict' + +const { networkInterfaces } = require('node:os') +const { test, before } = require('node:test') +const Fastify = require('..') +const helper = require('./helper') + +let localhost +let localhostForURL + +before(async function () { + [localhost, localhostForURL] = await helper.getLoopbackHost() +}) + +test('listen works without arguments', async t => { + const doNotWarn = () => { + t.assert.fail('should not be deprecated') + } + process.on('warning', doNotWarn) + + const fastify = Fastify() + t.after(() => { + fastify.close() + process.removeListener('warning', doNotWarn) + }) + await fastify.listen() + const address = fastify.server.address() + t.assert.strictEqual(address.address, localhost) + t.assert.ok(address.port > 0) +}) + +test('Async/await listen with arguments', async t => { + const doNotWarn = () => { + t.assert.fail('should not be deprecated') + } + process.on('warning', doNotWarn) + + const fastify = Fastify() + t.after(() => { + fastify.close() + process.removeListener('warning', doNotWarn) + }) + const addr = await fastify.listen({ port: 0, host: '0.0.0.0' }) + const address = fastify.server.address() + const { protocol, hostname, port, pathname } = new URL(addr) + t.assert.strictEqual(protocol, 'http:') + t.assert.ok(Object.values(networkInterfaces()) + .flat() + .filter(({ internal }) => internal) + .some(({ address }) => address === hostname)) + t.assert.strictEqual(pathname, '/') + t.assert.strictEqual(Number(port), address.port) + t.assert.deepEqual(address, { + address: '0.0.0.0', + family: 'IPv4', + port: address.port + }) +}) + +test('listen accepts a callback', (t, done) => { + t.plan(2) + const doNotWarn = () => { + t.assert.fail('should not be deprecated') + } + process.on('warning', doNotWarn) + + const fastify = Fastify() + t.after(() => { + fastify.close() + process.removeListener('warning', doNotWarn) + }) + fastify.listen({ port: 0 }, (err) => { + t.assert.ifError(err) + t.assert.strictEqual(fastify.server.address().address, localhost) + done() + }) +}) + +test('listen accepts options and a callback', (t, done) => { + t.plan(1) + const doNotWarn = () => { + t.assert.fail('should not be deprecated') + } + process.on('warning', doNotWarn) + + const fastify = Fastify() + t.after(() => { + fastify.close() + process.removeListener('warning', doNotWarn) + }) + fastify.listen({ + port: 0, + host: 'localhost', + backlog: 511, + exclusive: false, + readableAll: false, + writableAll: false, + ipv6Only: false + }, (err) => { + t.assert.ifError(err) + done() + }) +}) + +test('listen after Promise.resolve()', (t, done) => { + t.plan(2) + const fastify = Fastify() + t.after(() => fastify.close()) + Promise.resolve() + .then(() => { + fastify.listen({ port: 0 }, (err, address) => { + fastify.server.unref() + t.assert.strictEqual(address, `http://${localhostForURL}:${fastify.server.address().port}`) + t.assert.ifError(err) + done() + }) + }) +}) + +test('listen works with undefined host', async t => { + const doNotWarn = () => { + t.assert.fail('should not be deprecated') + } + process.on('warning', doNotWarn) + + const fastify = Fastify() + t.after(() => fastify.close()) + t.after(() => { + fastify.close() + process.removeListener('warning', doNotWarn) + }) + await fastify.listen({ host: undefined, port: 0 }) + const address = fastify.server.address() + t.assert.strictEqual(address.address, localhost) + t.assert.ok(address.port > 0) +}) + +test('listen works with null host', async t => { + const doNotWarn = () => { + t.fail('should not be deprecated') + } + process.on('warning', doNotWarn) + + const fastify = Fastify() + t.after(() => fastify.close()) + t.after(() => { + fastify.close() + process.removeListener('warning', doNotWarn) + }) + await fastify.listen({ host: null, port: 0 }) + const address = fastify.server.address() + t.assert.strictEqual(address.address, localhost) + t.assert.ok(address.port > 0) +}) diff --git a/services/slides/node_modules/fastify/test/listen.2.test.js b/services/slides/node_modules/fastify/test/listen.2.test.js new file mode 100644 index 0000000000000000000000000000000000000000..3c27a5a73ef73a48bdf43655fb63fe2a87839164 --- /dev/null +++ b/services/slides/node_modules/fastify/test/listen.2.test.js @@ -0,0 +1,113 @@ +'use strict' + +const { test, before } = require('node:test') +const Fastify = require('..') +const helper = require('./helper') +const { networkInterfaces } = require('node:os') + +const isIPv6Missing = !Object.values(networkInterfaces()).flat().some(({ family }) => family === 'IPv6') + +let localhostForURL + +before(async function () { + [, localhostForURL] = await helper.getLoopbackHost() +}) + +test('register after listen using Promise.resolve()', async t => { + t.plan(1) + const fastify = Fastify() + + const handler = (req, res) => res.send({}) + await Promise.resolve() + .then(() => { + fastify.get('/', handler) + fastify.register((f2, options, done) => { + f2.get('/plugin', handler) + done() + }) + return fastify.ready() + }) + .catch((err) => { + t.assert.fail(err.message) + }) + .then(() => t.assert.ok('resolved')) +}) + +test('double listen errors', (t, done) => { + t.plan(3) + const fastify = Fastify() + t.after(() => fastify.close()) + fastify.listen({ port: 0 }, (err) => { + t.assert.ifError(err) + fastify.listen({ port: fastify.server.address().port }, (err, address) => { + t.assert.strictEqual(address, null) + t.assert.ok(err) + done() + }) + }) +}) + +test('double listen errors callback with (err, address)', (t, done) => { + t.plan(4) + const fastify = Fastify() + t.after(() => fastify.close()) + fastify.listen({ port: 0 }, (err1, address1) => { + t.assert.strictEqual(address1, `http://${localhostForURL}:${fastify.server.address().port}`) + t.assert.ifError(err1) + fastify.listen({ port: fastify.server.address().port }, (err2, address2) => { + t.assert.strictEqual(address2, null) + t.assert.ok(err2) + done() + }) + }) +}) + +test('nonlocalhost double listen errors callback with (err, address)', { skip: isIPv6Missing }, (t, done) => { + t.plan(4) + const fastify = Fastify() + t.after(() => fastify.close()) + fastify.listen({ host: '::1', port: 0 }, (err, address) => { + t.assert.strictEqual(address, `http://${'[::1]'}:${fastify.server.address().port}`) + t.assert.ifError(err) + fastify.listen({ host: '::1', port: fastify.server.address().port }, (err2, address2) => { + t.assert.strictEqual(address2, null) + t.assert.ok(err2) + done() + }) + }) +}) + +test('listen twice on the same port', (t, done) => { + t.plan(4) + const fastify = Fastify() + t.after(() => fastify.close()) + fastify.listen({ port: 0 }, (err1, address1) => { + t.assert.strictEqual(address1, `http://${localhostForURL}:${fastify.server.address().port}`) + t.assert.ifError(err1) + const s2 = Fastify() + t.after(() => fastify.close()) + s2.listen({ port: fastify.server.address().port }, (err2, address2) => { + t.assert.strictEqual(address2, null) + t.assert.ok(err2) + done() + }) + }) +}) + +test('listen twice on the same port callback with (err, address)', (t, done) => { + t.plan(4) + const fastify = Fastify() + t.after(() => fastify.close()) + fastify.listen({ port: 0 }, (err1, address1) => { + const _port = fastify.server.address().port + t.assert.strictEqual(address1, `http://${localhostForURL}:${_port}`) + t.assert.ifError(err1) + const s2 = Fastify() + t.after(() => fastify.close()) + s2.listen({ port: _port }, (err2, address2) => { + t.assert.strictEqual(address2, null) + t.assert.ok(err2) + done() + }) + }) +}) diff --git a/services/slides/node_modules/fastify/test/listen.3.test.js b/services/slides/node_modules/fastify/test/listen.3.test.js new file mode 100644 index 0000000000000000000000000000000000000000..3f536232d41af9351ea50df7c6c8e1210c83f8ee --- /dev/null +++ b/services/slides/node_modules/fastify/test/listen.3.test.js @@ -0,0 +1,83 @@ +'use strict' + +const os = require('node:os') +const path = require('node:path') +const fs = require('node:fs') +const { test, before } = require('node:test') +const Fastify = require('..') +const helper = require('./helper') + +let localhostForURL + +before(async function () { + [, localhostForURL] = await helper.getLoopbackHost() +}) + +// https://nodejs.org/api/net.html#net_ipc_support +if (os.platform() !== 'win32') { + test('listen on socket', async t => { + t.plan(2) + const fastify = Fastify() + t.after(() => fastify.close()) + + const sockFile = path.join(os.tmpdir(), `${(Math.random().toString(16) + '0000000').slice(2, 10)}-server.sock`) + try { + fs.unlinkSync(sockFile) + } catch (e) { } + + await fastify.listen({ path: sockFile }) + t.assert.deepStrictEqual(fastify.addresses(), [sockFile]) + t.assert.strictEqual(fastify.server.address(), sockFile) + }) +} else { + test('listen on socket', async t => { + t.plan(2) + const fastify = Fastify() + t.after(() => fastify.close()) + + const sockFile = `\\\\.\\pipe\\${(Math.random().toString(16) + '0000000').slice(2, 10)}-server-sock` + + await fastify.listen({ path: sockFile }) + t.assert.deepStrictEqual(fastify.addresses(), [sockFile]) + t.assert.strictEqual(fastify.server.address(), sockFile) + }) +} + +test('listen without callback with (address)', async t => { + t.plan(1) + const fastify = Fastify() + t.after(() => fastify.close()) + const address = await fastify.listen({ port: 0 }) + t.assert.strictEqual(address, `http://${localhostForURL}:${fastify.server.address().port}`) +}) + +test('double listen without callback rejects', (t, done) => { + t.plan(1) + const fastify = Fastify() + t.after(() => fastify.close()) + fastify.listen({ port: 0 }) + .then(() => { + fastify.listen({ port: 0 }) + .catch(err => { + t.assert.ok(err) + done() + }) + }) + .catch(err => t.assert.ifError(err)) +}) + +test('double listen without callback with (address)', (t, done) => { + t.plan(2) + const fastify = Fastify() + t.after(() => fastify.close()) + fastify.listen({ port: 0 }) + .then(address => { + t.assert.strictEqual(address, `http://${localhostForURL}:${fastify.server.address().port}`) + fastify.listen({ port: 0 }) + .catch(err => { + t.assert.ok(err) + done() + }) + }) + .catch(err => t.assert.ifError(err)) +}) diff --git a/services/slides/node_modules/fastify/test/listen.4.test.js b/services/slides/node_modules/fastify/test/listen.4.test.js new file mode 100644 index 0000000000000000000000000000000000000000..c73c1b2e19f432333118e2d5afc8f1c88b5b6fa8 --- /dev/null +++ b/services/slides/node_modules/fastify/test/listen.4.test.js @@ -0,0 +1,168 @@ +'use strict' + +const { test, before } = require('node:test') +const dns = require('node:dns').promises +const dnsCb = require('node:dns') +const Fastify = require('../fastify') +const helper = require('./helper') + +let localhostForURL + +function getUrl (fastify, lookup) { + const { port } = fastify.server.address() + if (lookup.family === 6) { + return `http://[${lookup.address}]:${port}/` + } else { + return `http://${lookup.address}:${port}/` + } +} + +before(async function () { + [, localhostForURL] = await helper.getLoopbackHost() +}) + +test('listen twice on the same port without callback rejects', (t, done) => { + t.plan(1) + const fastify = Fastify() + t.after(() => fastify.close()) + + fastify.listen({ port: 0 }) + .then(() => { + const server2 = Fastify() + t.after(() => server2.close()) + server2.listen({ port: fastify.server.address().port }) + .catch(err => { + t.assert.ok(err) + done() + }) + }) + .catch(err => { + t.assert.ifError(err) + }) +}) + +test('listen twice on the same port without callback rejects with (address)', (t, done) => { + t.plan(2) + const fastify = Fastify() + t.after(() => fastify.close()) + fastify.listen({ port: 0 }) + .then(address => { + const server2 = Fastify() + t.after(() => server2.close()) + t.assert.strictEqual(address, `http://${localhostForURL}:${fastify.server.address().port}`) + + server2.listen({ port: fastify.server.address().port }) + .catch(err => { + t.assert.ok(err) + done() + }) + }) + .catch(err => { + t.assert.ifError(err) + }) +}) + +test('listen on invalid port without callback rejects', t => { + const fastify = Fastify() + t.after(() => fastify.close()) + return fastify.listen({ port: -1 }) + .catch(err => { + t.assert.ok(err) + return true + }) +}) + +test('listen logs the port as info', async t => { + t.plan(1) + const fastify = Fastify() + t.after(() => fastify.close()) + + const msgs = [] + fastify.log.info = function (msg) { + msgs.push(msg) + } + + await fastify.listen({ port: 0 }) + t.assert.ok(/http:\/\//.test(msgs[0])) +}) + +test('listen on localhost binds IPv4 and IPv6 - promise interface', async t => { + const localAddresses = await dns.lookup('localhost', { all: true }) + t.plan(3 * localAddresses.length) + + const app = Fastify() + app.get('/', async () => 'hello localhost') + t.after(() => app.close()) + await app.listen({ port: 0, host: 'localhost' }) + + for (const lookup of localAddresses) { + const result = await fetch(getUrl(app, lookup), { + method: 'GET' + }) + + t.assert.ok(result.ok) + t.assert.deepEqual(result.status, 200) + t.assert.deepStrictEqual(await result.text(), 'hello localhost') + } +}) + +test('listen on localhost binds to all interfaces (both IPv4 and IPv6 if present) - callback interface', async (t) => { + const lookups = await new Promise((resolve, reject) => { + dnsCb.lookup('localhost', { all: true }, (err, lookups) => { + if (err) return reject(err) + resolve(lookups) + }) + }) + + t.plan(3 * lookups.length) + + const app = Fastify() + app.get('/', async () => 'hello localhost') + t.after(() => app.close()) + + await app.listen({ port: 0, host: 'localhost' }) + + // Loop over each lookup and perform the assertions + for (const lookup of lookups) { + const result = await fetch(getUrl(app, lookup), { + method: 'GET' + }) + + t.assert.ok(result.ok) + t.assert.deepEqual(result.status, 200) + t.assert.deepStrictEqual(await result.text(), 'hello localhost') + } +}) + +test('addresses getter', async t => { + let localAddresses = await dns.lookup('localhost', { all: true }) + + t.plan(4) + const app = Fastify() + app.get('/', async () => 'hello localhost') + t.after(() => app.close()) + + t.assert.deepStrictEqual(app.addresses(), [], 'before ready') + await app.ready() + + t.assert.deepStrictEqual(app.addresses(), [], 'after ready') + await app.listen({ port: 0, host: 'localhost' }) + + // fix citgm + // dns lookup may have duplicated addresses (rhel8-s390x rhel8-ppc64le debian10-x64) + + localAddresses = [...new Set([...localAddresses.map(a => JSON.stringify({ + address: a.address, + family: typeof a.family === 'number' ? 'IPv' + a.family : a.family + }))])].sort() + + const appAddresses = app.addresses().map(a => JSON.stringify({ + address: a.address, + family: typeof a.family === 'number' ? 'IPv' + a.family : a.family + })).sort() + + t.assert.deepStrictEqual(appAddresses, localAddresses, 'after listen') + + await app.close() + t.assert.deepStrictEqual(app.addresses(), [], 'after close') +}) diff --git a/services/slides/node_modules/fastify/test/listen.5.test.js b/services/slides/node_modules/fastify/test/listen.5.test.js new file mode 100644 index 0000000000000000000000000000000000000000..68aa23a10433e428eb1e1484e691cff424956e16 --- /dev/null +++ b/services/slides/node_modules/fastify/test/listen.5.test.js @@ -0,0 +1,122 @@ +'use strict' + +const { test } = require('node:test') +const net = require('node:net') +const Fastify = require('../fastify') +const { once } = require('node:events') +const { FSTWRN003 } = require('../lib/warnings.js') + +function createDeferredPromise () { + const promise = {} + promise.promise = new Promise((resolve) => { + promise.resolve = resolve + }) + return promise +} + +test('same port conflict and success should not fire callback multiple times - callback', async (t) => { + t.plan(7) + const server = net.createServer() + server.listen({ port: 0, host: '127.0.0.1' }) + await once(server, 'listening') + const option = { port: server.address().port, host: server.address().address } + let count = 0 + const fastify = Fastify() + const promise = createDeferredPromise() + function callback (err) { + switch (count) { + case 6: { + // success in here + t.assert.ifError(err) + fastify.close((err) => { + t.assert.ifError(err) + promise.resolve() + }) + break + } + case 5: { + server.close() + setTimeout(() => { + fastify.listen(option, callback) + }, 100) + break + } + default: { + // expect error + t.assert.strictEqual(err.code, 'EADDRINUSE') + setTimeout(() => { + fastify.listen(option, callback) + }, 100) + } + } + count++ + } + fastify.listen(option, callback) + await promise.promise +}) + +test('same port conflict and success should not fire callback multiple times - promise', async (t) => { + t.plan(5) + const server = net.createServer() + server.listen({ port: 0, host: '127.0.0.1' }) + await once(server, 'listening') + const option = { port: server.address().port, host: server.address().address } + const fastify = Fastify() + + try { + await fastify.listen(option) + } catch (err) { + t.assert.strictEqual(err.code, 'EADDRINUSE') + } + try { + await fastify.listen(option) + } catch (err) { + t.assert.strictEqual(err.code, 'EADDRINUSE') + } + try { + await fastify.listen(option) + } catch (err) { + t.assert.strictEqual(err.code, 'EADDRINUSE') + } + try { + await fastify.listen(option) + } catch (err) { + t.assert.strictEqual(err.code, 'EADDRINUSE') + } + try { + await fastify.listen(option) + } catch (err) { + t.assert.strictEqual(err.code, 'EADDRINUSE') + } + + server.close() + + await once(server, 'close') + + // when ever we can listen, and close properly + // which means there is no problem on the callback + await fastify.listen() + await fastify.close() +}) + +test('should emit a warning when using async callback', (t, done) => { + t.plan(2) + + process.on('warning', onWarning) + function onWarning (warning) { + t.assert.strictEqual(warning.name, 'FastifyWarning') + t.assert.strictEqual(warning.code, FSTWRN003.code) + } + + const fastify = Fastify() + + t.after(async () => { + await fastify.close() + process.removeListener('warning', onWarning) + FSTWRN003.emitted = false + }) + + fastify.listen({ port: 0 }, async function doNotUseAsyncCallback () { + done() + }) +}) diff --git a/services/slides/node_modules/fastify/test/logger/instantiation.test.js b/services/slides/node_modules/fastify/test/logger/instantiation.test.js new file mode 100644 index 0000000000000000000000000000000000000000..3fd844219646223c90c9db5fab7d725c428b232f --- /dev/null +++ b/services/slides/node_modules/fastify/test/logger/instantiation.test.js @@ -0,0 +1,341 @@ +'use strict' + +const stream = require('node:stream') +const os = require('node:os') +const fs = require('node:fs') + +const t = require('node:test') +const split = require('split2') + +const { streamSym } = require('pino/lib/symbols') + +const Fastify = require('../../fastify') +const helper = require('../helper') +const { FST_ERR_LOG_INVALID_LOGGER } = require('../../lib/errors') +const { once, on } = stream +const { createTempFile, request } = require('./logger-test-utils') +const { partialDeepStrictEqual } = require('../toolkit') + +t.test('logger instantiation', { timeout: 60000 }, async (t) => { + let localhost + let localhostForURL + + t.plan(11) + t.before(async function () { + [localhost, localhostForURL] = await helper.getLoopbackHost() + }) + + await t.test('can use external logger instance', async (t) => { + const lines = [/^Server listening at /, /^incoming request$/, /^log success$/, /^request completed$/] + t.plan(lines.length + 1) + + const stream = split(JSON.parse) + + const loggerInstance = require('pino')(stream) + + const fastify = Fastify({ loggerInstance }) + t.after(() => fastify.close()) + + fastify.get('/foo', function (req, reply) { + t.assert.ok(req.log) + req.log.info('log success') + reply.send({ hello: 'world' }) + }) + + await fastify.listen({ port: 0, host: localhost }) + + await request(`http://${localhostForURL}:` + fastify.server.address().port + '/foo') + + for await (const [line] of on(stream, 'data')) { + const regex = lines.shift() + t.assert.ok(regex.test(line.msg), '"' + line.msg + '" does not match "' + regex + '"') + if (lines.length === 0) break + } + }) + + await t.test('should create a default logger if provided one is invalid', (t) => { + t.plan(8) + + const logger = new Date() + + const fastify = Fastify({ logger }) + t.after(() => fastify.close()) + + t.assert.strictEqual(typeof fastify.log, 'object') + t.assert.strictEqual(typeof fastify.log.fatal, 'function') + t.assert.strictEqual(typeof fastify.log.error, 'function') + t.assert.strictEqual(typeof fastify.log.warn, 'function') + t.assert.strictEqual(typeof fastify.log.info, 'function') + t.assert.strictEqual(typeof fastify.log.debug, 'function') + t.assert.strictEqual(typeof fastify.log.trace, 'function') + t.assert.strictEqual(typeof fastify.log.child, 'function') + }) + + await t.test('expose the logger', async (t) => { + t.plan(2) + const stream = split(JSON.parse) + const fastify = Fastify({ + logger: { + stream, + level: 'info' + } + }) + t.after(() => fastify.close()) + + await fastify.ready() + + t.assert.ok(fastify.log) + t.assert.strictEqual(typeof fastify.log, 'object') + }) + + const interfaces = os.networkInterfaces() + const ipv6 = Object.keys(interfaces) + .filter(name => name.substr(0, 2) === 'lo') + .map(name => interfaces[name]) + .reduce((list, set) => list.concat(set), []) + .filter(info => info.family === 'IPv6') + .map(info => info.address) + .shift() + + await t.test('Wrap IPv6 address in listening log message', { skip: !ipv6 }, async (t) => { + t.plan(1) + + const stream = split(JSON.parse) + const fastify = Fastify({ + logger: { + stream, + level: 'info' + } + }) + t.after(() => fastify.close()) + + await fastify.ready() + await fastify.listen({ port: 0, host: ipv6 }) + + { + const [line] = await once(stream, 'data') + t.assert.strictEqual(line.msg, `Server listening at http://[${ipv6}]:${fastify.server.address().port}`) + } + }) + + await t.test('Do not wrap IPv4 address', async (t) => { + t.plan(1) + const stream = split(JSON.parse) + const fastify = Fastify({ + logger: { + stream, + level: 'info' + } + }) + t.after(() => fastify.close()) + + await fastify.ready() + await fastify.listen({ port: 0, host: '127.0.0.1' }) + + { + const [line] = await once(stream, 'data') + t.assert.strictEqual(line.msg, `Server listening at http://127.0.0.1:${fastify.server.address().port}`) + } + }) + + await t.test('file option', async (t) => { + const { file, cleanup } = createTempFile(t) + // 0600 permissions (read/write for owner only) + if (process.env.CITGM) { fs.writeFileSync(file, '', { mode: 0o600 }) } + + const fastify = Fastify({ + logger: { file } + }) + + t.after(async () => { + await helper.sleep(250) + // may fail on win + try { + // cleanup the file after sonic-boom closed + // otherwise we may face racing condition + fastify.log[streamSym].once('close', cleanup) + // we must flush the stream ourself + // otherwise buffer may whole sonic-boom + fastify.log[streamSym].flushSync() + // end after flushing to actually close file + fastify.log[streamSym].end() + } catch (err) { + console.warn(err) + } + }) + t.after(() => fastify.close()) + + fastify.get('/', function (req, reply) { + t.assert.ok(req.log) + reply.send({ hello: 'world' }) + }) + + await fastify.ready() + const server = await fastify.listen({ port: 0, host: localhost }) + const lines = [ + { msg: `Server listening at ${server}` }, + { req: { method: 'GET', url: '/' }, msg: 'incoming request' }, + { res: { statusCode: 200 }, msg: 'request completed' } + ] + await request(`http://${localhostForURL}:` + fastify.server.address().port) + + await helper.sleep(250) + + const log = fs.readFileSync(file, 'utf8').split('\n') + // strip last line + log.pop() + + let id + for (let line of log) { + line = JSON.parse(line) + if (id === undefined && line.reqId) id = line.reqId + if (id !== undefined && line.reqId) t.assert.strictEqual(line.reqId, id) + t.assert.ok(partialDeepStrictEqual(line, lines.shift())) + } + }) + + await t.test('should be able to use a custom logger', (t) => { + t.plan(7) + + const loggerInstance = { + fatal: (msg) => { t.assert.strictEqual(msg, 'fatal') }, + error: (msg) => { t.assert.strictEqual(msg, 'error') }, + warn: (msg) => { t.assert.strictEqual(msg, 'warn') }, + info: (msg) => { t.assert.strictEqual(msg, 'info') }, + debug: (msg) => { t.assert.strictEqual(msg, 'debug') }, + trace: (msg) => { t.assert.strictEqual(msg, 'trace') }, + child: () => loggerInstance + } + + const fastify = Fastify({ loggerInstance }) + t.after(() => fastify.close()) + + fastify.log.fatal('fatal') + fastify.log.error('error') + fastify.log.warn('warn') + fastify.log.info('info') + fastify.log.debug('debug') + fastify.log.trace('trace') + const child = fastify.log.child() + t.assert.strictEqual(child, loggerInstance) + }) + + await t.test('should throw in case a partially matching logger is provided', async (t) => { + t.plan(1) + + try { + const fastify = Fastify({ logger: console }) + await fastify.ready() + } catch (err) { + t.assert.strictEqual( + err instanceof FST_ERR_LOG_INVALID_LOGGER, + true, + "Invalid logger object provided. The logger instance should have these functions(s): 'fatal,child'." + ) + } + }) + + await t.test('can use external logger instance with custom serializer', async (t) => { + const lines = [['level', 30], ['req', { url: '/foo' }], ['level', 30], ['res', { statusCode: 200 }]] + t.plan(lines.length + 1) + + const stream = split(JSON.parse) + const loggerInstance = require('pino')({ + level: 'info', + serializers: { + req: function (req) { + return { + url: req.url + } + } + } + }, stream) + + const fastify = Fastify({ + loggerInstance + }) + t.after(() => fastify.close()) + + fastify.get('/foo', function (req, reply) { + t.assert.ok(req.log) + req.log.info('log success') + reply.send({ hello: 'world' }) + }) + + await fastify.ready() + await fastify.listen({ port: 0, host: localhost }) + + await request(`http://${localhostForURL}:` + fastify.server.address().port + '/foo') + + for await (const [line] of on(stream, 'data')) { + const check = lines.shift() + const key = check[0] + const value = check[1] + t.assert.deepStrictEqual(line[key], value) + if (lines.length === 0) break + } + }) + + await t.test('The logger should accept custom serializer', async (t) => { + const stream = split(JSON.parse) + const fastify = Fastify({ + logger: { + stream, + level: 'info', + serializers: { + req: function (req) { + return { + url: req.url + } + } + } + } + }) + t.after(() => fastify.close()) + + fastify.get('/custom', function (req, reply) { + t.assert.ok(req.log) + reply.send(new Error('kaboom')) + }) + + await fastify.ready() + const server = await fastify.listen({ port: 0, host: localhost }) + const lines = [ + { msg: `Server listening at ${server}` }, + { req: { url: '/custom' }, msg: 'incoming request' }, + { res: { statusCode: 500 }, msg: 'kaboom' }, + { res: { statusCode: 500 }, msg: 'request completed' } + ] + t.plan(lines.length + 1) + + await request(`http://${localhostForURL}:` + fastify.server.address().port + '/custom') + + for await (const [line] of on(stream, 'data')) { + t.assert.ok(partialDeepStrictEqual(line, lines.shift())) + if (lines.length === 0) break + } + }) + + await t.test('should throw in case the external logger provided does not have a child method', async (t) => { + t.plan(1) + const loggerInstance = { + info: console.info, + error: console.error, + debug: console.debug, + fatal: console.error, + warn: console.warn, + trace: console.trace + } + + try { + const fastify = Fastify({ logger: loggerInstance }) + await fastify.ready() + } catch (err) { + t.assert.strictEqual( + err instanceof FST_ERR_LOG_INVALID_LOGGER, + true, + "Invalid logger object provided. The logger instance should have these functions(s): 'child'." + ) + } + }) +}) diff --git a/services/slides/node_modules/fastify/test/logger/logger-test-utils.js b/services/slides/node_modules/fastify/test/logger/logger-test-utils.js new file mode 100644 index 0000000000000000000000000000000000000000..cce6669163b60a00cc884d6251fa06da027b7bde --- /dev/null +++ b/services/slides/node_modules/fastify/test/logger/logger-test-utils.js @@ -0,0 +1,47 @@ +'use strict' + +const http = require('node:http') +const os = require('node:os') +const fs = require('node:fs') + +const path = require('node:path') + +function createDeferredPromise () { + const promise = {} + promise.promise = new Promise(function (resolve) { + promise.resolve = resolve + }) + return promise +} + +let count = 0 +function createTempFile () { + const file = path.join(os.tmpdir(), `sonic-boom-${process.pid}-${count++}`) + function cleanup () { + try { + fs.unlinkSync(file) + } catch { } + } + return { file, cleanup } +} + +function request (url, cleanup = () => { }) { + const promise = createDeferredPromise() + http.get(url, (res) => { + const chunks = [] + // we consume the response + res.on('data', function (chunk) { + chunks.push(chunk) + }) + res.once('end', function () { + cleanup(res, Buffer.concat(chunks).toString()) + promise.resolve() + }) + }) + return promise.promise +} + +module.exports = { + request, + createTempFile +} diff --git a/services/slides/node_modules/fastify/test/logger/logging.test.js b/services/slides/node_modules/fastify/test/logger/logging.test.js new file mode 100644 index 0000000000000000000000000000000000000000..0ed5187252dab84b5b3dfb52bfd246abfc32081c --- /dev/null +++ b/services/slides/node_modules/fastify/test/logger/logging.test.js @@ -0,0 +1,460 @@ +'use strict' + +const stream = require('node:stream') + +const t = require('node:test') +const split = require('split2') +const pino = require('pino') + +const Fastify = require('../../fastify') +const helper = require('../helper') +const { once, on } = stream +const { request } = require('./logger-test-utils') +const { partialDeepStrictEqual } = require('../toolkit') + +t.test('logging', { timeout: 60000 }, async (t) => { + let localhost + let localhostForURL + + t.plan(14) + + t.before(async function () { + [localhost, localhostForURL] = await helper.getLoopbackHost() + }) + + await t.test('The default 404 handler logs the incoming request', async (t) => { + const lines = ['incoming request', 'Route GET:/not-found not found', 'request completed'] + t.plan(lines.length + 1) + + const stream = split(JSON.parse) + + const loggerInstance = pino({ level: 'trace' }, stream) + + const fastify = Fastify({ + loggerInstance + }) + t.after(() => fastify.close()) + + await fastify.ready() + + { + const response = await fastify.inject({ method: 'GET', url: '/not-found' }) + t.assert.strictEqual(response.statusCode, 404) + } + + for await (const [line] of on(stream, 'data')) { + t.assert.strictEqual(line.msg, lines.shift()) + if (lines.length === 0) break + } + }) + + await t.test('should not rely on raw request to log errors', async (t) => { + const stream = split(JSON.parse) + const fastify = Fastify({ + logger: { + stream, + level: 'info' + } + }) + t.after(() => fastify.close()) + fastify.get('/error', function (req, reply) { + t.assert.ok(req.log) + reply.status(415).send(new Error('something happened')) + }) + + await fastify.ready() + const server = await fastify.listen({ port: 0, host: localhost }) + const lines = [ + { msg: `Server listening at ${server}` }, + { level: 30, msg: 'incoming request' }, + { res: { statusCode: 415 }, msg: 'something happened' }, + { res: { statusCode: 415 }, msg: 'request completed' } + ] + t.plan(lines.length + 1) + + await request(`http://${localhostForURL}:` + fastify.server.address().port + '/error') + + for await (const [line] of on(stream, 'data')) { + t.assert.ok(partialDeepStrictEqual(line, lines.shift())) + if (lines.length === 0) break + } + }) + + await t.test('should log the error if no error handler is defined', async (t) => { + const stream = split(JSON.parse) + const fastify = Fastify({ + logger: { + stream, + level: 'info' + } + }) + t.after(() => fastify.close()) + + fastify.get('/error', function (req, reply) { + t.assert.ok(req.log) + reply.send(new Error('a generic error')) + }) + + await fastify.ready() + const server = await fastify.listen({ port: 0, host: localhost }) + const lines = [ + { msg: `Server listening at ${server}` }, + { msg: 'incoming request' }, + { level: 50, msg: 'a generic error' }, + { res: { statusCode: 500 }, msg: 'request completed' } + ] + t.plan(lines.length + 1) + + await request(`http://${localhostForURL}:` + fastify.server.address().port + '/error') + + for await (const [line] of on(stream, 'data')) { + t.assert.ok(partialDeepStrictEqual(line, lines.shift())) + if (lines.length === 0) break + } + }) + + await t.test('should log as info if error status code >= 400 and < 500 if no error handler is defined', async (t) => { + const stream = split(JSON.parse) + const fastify = Fastify({ + logger: { + stream, + level: 'info' + } + }) + t.after(() => fastify.close()) + + fastify.get('/400', function (req, reply) { + t.assert.ok(req.log) + reply.send(Object.assign(new Error('a 400 error'), { statusCode: 400 })) + }) + fastify.get('/503', function (req, reply) { + t.assert.ok(req.log) + reply.send(Object.assign(new Error('a 503 error'), { statusCode: 503 })) + }) + + await fastify.ready() + const server = await fastify.listen({ port: 0, host: localhost }) + const lines = [ + { msg: `Server listening at ${server}` }, + { msg: 'incoming request' }, + { level: 30, msg: 'a 400 error' }, + { res: { statusCode: 400 }, msg: 'request completed' } + ] + t.plan(lines.length + 1) + + await request(`http://${localhostForURL}:` + fastify.server.address().port + '/400') + + for await (const [line] of on(stream, 'data')) { + t.assert.ok(partialDeepStrictEqual(line, lines.shift())) + if (lines.length === 0) break + } + }) + + await t.test('should log as error if error status code >= 500 if no error handler is defined', async (t) => { + const stream = split(JSON.parse) + const fastify = Fastify({ + logger: { + stream, + level: 'info' + } + }) + t.after(() => fastify.close()) + fastify.get('/503', function (req, reply) { + t.assert.ok(req.log) + reply.send(Object.assign(new Error('a 503 error'), { statusCode: 503 })) + }) + + await fastify.ready() + const server = await fastify.listen({ port: 0, host: localhost }) + const lines = [ + { msg: `Server listening at ${server}` }, + { msg: 'incoming request' }, + { level: 50, msg: 'a 503 error' }, + { res: { statusCode: 503 }, msg: 'request completed' } + ] + t.plan(lines.length + 1) + + await request(`http://${localhostForURL}:` + fastify.server.address().port + '/503') + + for await (const [line] of on(stream, 'data')) { + t.assert.ok(partialDeepStrictEqual(line, lines.shift())) + if (lines.length === 0) break + } + }) + + await t.test('should not log the error if error handler is defined and it does not error', async (t) => { + const stream = split(JSON.parse) + const fastify = Fastify({ + logger: { + stream, + level: 'info' + } + }) + t.after(() => fastify.close()) + fastify.get('/error', function (req, reply) { + t.assert.ok(req.log) + reply.send(new Error('something happened')) + }) + fastify.setErrorHandler((err, req, reply) => { + t.assert.ok(err) + reply.send('something bad happened') + }) + + await fastify.ready() + const server = await fastify.listen({ port: 0, host: localhost }) + const lines = [ + { msg: `Server listening at ${server}` }, + { level: 30, msg: 'incoming request' }, + { res: { statusCode: 200 }, msg: 'request completed' } + ] + t.plan(lines.length + 2) + + await request(`http://${localhostForURL}:` + fastify.server.address().port + '/error') + + for await (const [line] of on(stream, 'data')) { + t.assert.ok(partialDeepStrictEqual(line, lines.shift())) + if (lines.length === 0) break + } + }) + + await t.test('reply.send logs an error if called twice in a row', async (t) => { + const lines = [ + 'incoming request', + 'request completed', + 'Reply was already sent, did you forget to "return reply" in "/" (GET)?', + 'Reply was already sent, did you forget to "return reply" in "/" (GET)?' + ] + t.plan(lines.length + 1) + + const stream = split(JSON.parse) + const loggerInstance = pino(stream) + + const fastify = Fastify({ + loggerInstance + }) + t.after(() => fastify.close()) + + fastify.get('/', (req, reply) => { + reply.send({ hello: 'world' }) + reply.send({ hello: 'world2' }) + reply.send({ hello: 'world3' }) + }) + + const response = await fastify.inject({ method: 'GET', url: '/' }) + const body = await response.json() + t.assert.ok(partialDeepStrictEqual(body, { hello: 'world' })) + + for await (const [line] of on(stream, 'data')) { + t.assert.strictEqual(line.msg, lines.shift()) + if (lines.length === 0) break + } + }) + + await t.test('should not log incoming request and outgoing response when disabled', async (t) => { + t.plan(1) + const stream = split(JSON.parse) + const fastify = Fastify({ disableRequestLogging: true, logger: { level: 'info', stream } }) + t.after(() => fastify.close()) + + fastify.get('/500', (req, reply) => { + reply.code(500).send(Error('500 error')) + }) + + await fastify.ready() + + await fastify.inject({ method: 'GET', url: '/500' }) + + // no more readable data + t.assert.strictEqual(stream.readableLength, 0) + }) + + await t.test('should not log incoming request, outgoing response and route not found for 404 onBadUrl when disabled', async (t) => { + t.plan(1) + const stream = split(JSON.parse) + const fastify = Fastify({ disableRequestLogging: true, logger: { level: 'info', stream } }) + t.after(() => fastify.close()) + + await fastify.ready() + + await fastify.inject({ method: 'GET', url: '/%c0' }) + + // no more readable data + t.assert.strictEqual(stream.readableLength, 0) + }) + + await t.test('should log incoming request and outgoing response based on disableRequestLogging function', async (t) => { + const lines = [ + 'incoming request', + 'request completed' + ] + t.plan(lines.length) + + const stream = split(JSON.parse) + const loggerInstance = pino(stream) + + const fastify = Fastify({ + disableRequestLogging: (request) => { + return request.url !== '/not-logged' + }, + loggerInstance + }) + t.after(() => fastify.close()) + + fastify.get('/logged', (req, reply) => { + return reply.code(200).send({}) + }) + + fastify.get('/not-logged', (req, reply) => { + return reply.code(200).send({}) + }) + + await fastify.ready() + + await fastify.inject({ method: 'GET', url: '/not-logged' }) + await fastify.inject({ method: 'GET', url: '/logged' }) + + for await (const [line] of on(stream, 'data')) { + t.assert.strictEqual(line.msg, lines.shift()) + if (lines.length === 0) break + } + }) + + await t.test('defaults to info level', async (t) => { + const lines = [ + { req: { method: 'GET' }, msg: 'incoming request' }, + { res: { statusCode: 200 }, msg: 'request completed' } + ] + t.plan(lines.length * 2 + 1) + const stream = split(JSON.parse) + const fastify = Fastify({ + logger: { + stream + } + }) + t.after(() => fastify.close()) + + fastify.get('/', function (req, reply) { + t.assert.ok(req.log) + reply.send({ hello: 'world' }) + }) + + await fastify.ready() + await fastify.listen({ port: 0 }) + + await request(`http://${localhostForURL}:` + fastify.server.address().port) + + let id + for await (const [line] of on(stream, 'data')) { + // we skip the non-request log + if (typeof line.reqId !== 'string') continue + if (id === undefined && line.reqId) id = line.reqId + if (id !== undefined && line.reqId) t.assert.strictEqual(line.reqId, id) + t.assert.ok(partialDeepStrictEqual(line, lines.shift())) + if (lines.length === 0) break + } + }) + + await t.test('test log stream', async (t) => { + const stream = split(JSON.parse) + const fastify = Fastify({ + logger: { + stream, + level: 'info' + } + }) + t.after(() => fastify.close()) + + fastify.get('/', function (req, reply) { + t.assert.ok(req.log) + reply.send({ hello: 'world' }) + }) + + await fastify.ready() + const server = await fastify.listen({ port: 0, host: localhost }) + const lines = [ + { msg: `Server listening at ${server}` }, + { req: { method: 'GET' }, msg: 'incoming request' }, + { res: { statusCode: 200 }, msg: 'request completed' } + ] + t.plan(lines.length + 3) + + await request(`http://${localhostForURL}:` + fastify.server.address().port) + + let id + for await (const [line] of on(stream, 'data')) { + if (id === undefined && line.reqId) id = line.reqId + if (id !== undefined && line.reqId) t.assert.strictEqual(line.reqId, id) + t.assert.ok(partialDeepStrictEqual(line, lines.shift())) + if (lines.length === 0) break + } + }) + + await t.test('test error log stream', async (t) => { + const stream = split(JSON.parse) + const fastify = Fastify({ + logger: { + stream, + level: 'info' + } + }) + t.after(() => fastify.close()) + + fastify.get('/error', function (req, reply) { + t.assert.ok(req.log) + reply.send(new Error('kaboom')) + }) + + await fastify.ready() + const server = await fastify.listen({ port: 0, host: localhost }) + const lines = [ + { msg: `Server listening at ${server}` }, + { req: { method: 'GET' }, msg: 'incoming request' }, + { res: { statusCode: 500 }, msg: 'kaboom' }, + { res: { statusCode: 500 }, msg: 'request completed' } + ] + t.plan(lines.length + 4) + + await request(`http://${localhostForURL}:` + fastify.server.address().port + '/error') + + let id + for await (const [line] of on(stream, 'data')) { + if (id === undefined && line.reqId) id = line.reqId + if (id !== undefined && line.reqId) t.assert.strictEqual(line.reqId, id) + t.assert.ok(partialDeepStrictEqual(line, lines.shift())) + if (lines.length === 0) break + } + }) + + await t.test('should not log the error if request logging is disabled', async (t) => { + t.plan(4) + + const stream = split(JSON.parse) + const fastify = Fastify({ + logger: { + stream, + level: 'info' + }, + disableRequestLogging: true + }) + t.after(() => fastify.close()) + + fastify.get('/error', function (req, reply) { + t.assert.ok(req.log) + reply.send(new Error('a generic error')) + }) + + await fastify.ready() + await fastify.listen({ port: 0, host: localhost }) + + await request(`http://${localhostForURL}:` + fastify.server.address().port + '/error') + + { + const [line] = await once(stream, 'data') + t.assert.ok(typeof line.msg === 'string') + t.assert.ok(line.msg.startsWith('Server listening at'), 'message is set') + } + + // no more readable data + t.assert.strictEqual(stream.readableLength, 0) + }) +}) diff --git a/services/slides/node_modules/fastify/test/logger/options.test.js b/services/slides/node_modules/fastify/test/logger/options.test.js new file mode 100644 index 0000000000000000000000000000000000000000..46b1338ee52c08dcff79f4dec1ac43f1d67f4a40 --- /dev/null +++ b/services/slides/node_modules/fastify/test/logger/options.test.js @@ -0,0 +1,579 @@ +'use strict' + +const stream = require('node:stream') + +const t = require('node:test') +const split = require('split2') +const pino = require('pino') + +const Fastify = require('../../fastify') +const { on } = stream + +t.test('logger options', { timeout: 60000 }, async (t) => { + t.plan(16) + + await t.test('logger can be silenced', (t) => { + t.plan(17) + const fastify = Fastify({ + logger: false + }) + t.after(() => fastify.close()) + t.assert.ok(fastify.log) + t.assert.deepEqual(typeof fastify.log, 'object') + t.assert.deepEqual(typeof fastify.log.fatal, 'function') + t.assert.deepEqual(typeof fastify.log.error, 'function') + t.assert.deepEqual(typeof fastify.log.warn, 'function') + t.assert.deepEqual(typeof fastify.log.info, 'function') + t.assert.deepEqual(typeof fastify.log.debug, 'function') + t.assert.deepEqual(typeof fastify.log.trace, 'function') + t.assert.deepEqual(typeof fastify.log.child, 'function') + + const childLog = fastify.log.child() + + t.assert.deepEqual(typeof childLog, 'object') + t.assert.deepEqual(typeof childLog.fatal, 'function') + t.assert.deepEqual(typeof childLog.error, 'function') + t.assert.deepEqual(typeof childLog.warn, 'function') + t.assert.deepEqual(typeof childLog.info, 'function') + t.assert.deepEqual(typeof childLog.debug, 'function') + t.assert.deepEqual(typeof childLog.trace, 'function') + t.assert.deepEqual(typeof childLog.child, 'function') + }) + + await t.test('Should set a custom logLevel for a plugin', async (t) => { + const lines = ['incoming request', 'Hello', 'request completed'] + t.plan(lines.length + 2) + + const stream = split(JSON.parse) + + const loggerInstance = pino({ level: 'error' }, stream) + + const fastify = Fastify({ + loggerInstance + }) + t.after(() => fastify.close()) + + fastify.get('/', (req, reply) => { + req.log.info('Not Exist') // we should not see this log + reply.send({ hello: 'world' }) + }) + + fastify.register(function (instance, opts, done) { + instance.get('/plugin', (req, reply) => { + req.log.info('Hello') // we should see this log + reply.send({ hello: 'world' }) + }) + done() + }, { logLevel: 'info' }) + + await fastify.ready() + + { + const response = await fastify.inject({ method: 'GET', url: '/' }) + const body = await response.json() + t.assert.deepEqual(body.hello, 'world') + } + + { + const response = await fastify.inject({ method: 'GET', url: '/plugin' }) + const body = await response.json() + t.assert.deepEqual(body.hello, 'world') + } + + for await (const [line] of on(stream, 'data')) { + t.assert.deepEqual(line.msg, lines.shift()) + if (lines.length === 0) break + } + }) + + await t.test('Should set a custom logSerializers for a plugin', async (t) => { + const lines = ['incoming request', 'XHello', 'request completed'] + t.plan(lines.length + 1) + + const stream = split(JSON.parse) + + const loggerInstance = pino({ level: 'error' }, stream) + + const fastify = Fastify({ + loggerInstance + }) + t.after(() => fastify.close()) + + fastify.register(function (instance, opts, done) { + instance.get('/plugin', (req, reply) => { + req.log.info({ test: 'Hello' }) // we should see this log + reply.send({ hello: 'world' }) + }) + done() + }, { logLevel: 'info', logSerializers: { test: value => 'X' + value } }) + + await fastify.ready() + + { + const response = await fastify.inject({ method: 'GET', url: '/plugin' }) + const body = await response.json() + t.assert.deepEqual(body.hello, 'world') + } + + for await (const [line] of on(stream, 'data')) { + // either test or msg + t.assert.deepEqual(line.test || line.msg, lines.shift()) + if (lines.length === 0) break + } + }) + + await t.test('Should set a custom logLevel for every plugin', async (t) => { + const lines = ['incoming request', 'info', 'request completed', 'incoming request', 'debug', 'request completed'] + t.plan(lines.length * 2 + 3) + + const stream = split(JSON.parse) + + const loggerInstance = pino({ level: 'error' }, stream) + + const fastify = Fastify({ + loggerInstance + }) + t.after(() => fastify.close()) + + fastify.get('/', (req, reply) => { + req.log.warn('Hello') // we should not see this log + reply.send({ hello: 'world' }) + }) + + fastify.register(function (instance, opts, done) { + instance.get('/info', (req, reply) => { + req.log.info('info') // we should see this log + req.log.debug('hidden log') + reply.send({ hello: 'world' }) + }) + done() + }, { logLevel: 'info' }) + + fastify.register(function (instance, opts, done) { + instance.get('/debug', (req, reply) => { + req.log.debug('debug') // we should see this log + req.log.trace('hidden log') + reply.send({ hello: 'world' }) + }) + done() + }, { logLevel: 'debug' }) + + await fastify.ready() + + { + const response = await fastify.inject({ method: 'GET', url: '/' }) + const body = await response.json() + t.assert.deepEqual(body, { hello: 'world' }) + } + + { + const response = await fastify.inject({ method: 'GET', url: '/info' }) + const body = await response.json() + t.assert.deepEqual(body, { hello: 'world' }) + } + + { + const response = await fastify.inject({ method: 'GET', url: '/debug' }) + const body = await response.json() + t.assert.deepEqual(body, { hello: 'world' }) + } + + for await (const [line] of on(stream, 'data')) { + t.assert.ok(line.level === 30 || line.level === 20) + t.assert.deepEqual(line.msg, lines.shift()) + if (lines.length === 0) break + } + }) + + await t.test('Should set a custom logSerializers for every plugin', async (t) => { + const lines = ['incoming request', 'Hello', 'request completed', 'incoming request', 'XHello', 'request completed', 'incoming request', 'ZHello', 'request completed'] + t.plan(lines.length + 3) + + const stream = split(JSON.parse) + + const loggerInstance = pino({ level: 'info' }, stream) + const fastify = Fastify({ + loggerInstance + }) + t.after(() => fastify.close()) + + fastify.get('/', (req, reply) => { + req.log.warn({ test: 'Hello' }) + reply.send({ hello: 'world' }) + }) + + fastify.register(function (instance, opts, done) { + instance.get('/test1', (req, reply) => { + req.log.info({ test: 'Hello' }) + reply.send({ hello: 'world' }) + }) + done() + }, { logSerializers: { test: value => 'X' + value } }) + + fastify.register(function (instance, opts, done) { + instance.get('/test2', (req, reply) => { + req.log.info({ test: 'Hello' }) + reply.send({ hello: 'world' }) + }) + done() + }, { logSerializers: { test: value => 'Z' + value } }) + + await fastify.ready() + + { + const response = await fastify.inject({ method: 'GET', url: '/' }) + const body = await response.json() + t.assert.deepEqual(body, { hello: 'world' }) + } + + { + const response = await fastify.inject({ method: 'GET', url: '/test1' }) + const body = await response.json() + t.assert.deepEqual(body, { hello: 'world' }) + } + + { + const response = await fastify.inject({ method: 'GET', url: '/test2' }) + const body = await response.json() + t.assert.deepEqual(body, { hello: 'world' }) + } + + for await (const [line] of on(stream, 'data')) { + t.assert.deepEqual(line.test || line.msg, lines.shift()) + if (lines.length === 0) break + } + }) + + await t.test('Should override serializers from route', async (t) => { + const lines = ['incoming request', 'ZHello', 'request completed'] + t.plan(lines.length + 1) + + const stream = split(JSON.parse) + + const loggerInstance = pino({ level: 'info' }, stream) + const fastify = Fastify({ + loggerInstance + }) + t.after(() => fastify.close()) + + fastify.register(function (instance, opts, done) { + instance.get('/', { + logSerializers: { + test: value => 'Z' + value // should override + } + }, (req, reply) => { + req.log.info({ test: 'Hello' }) + reply.send({ hello: 'world' }) + }) + done() + }, { logSerializers: { test: value => 'X' + value } }) + + await fastify.ready() + + { + const response = await fastify.inject({ method: 'GET', url: '/' }) + const body = await response.json() + t.assert.deepEqual(body, { hello: 'world' }) + } + + for await (const [line] of on(stream, 'data')) { + t.assert.deepEqual(line.test || line.msg, lines.shift()) + if (lines.length === 0) break + } + }) + + await t.test('Should override serializers from plugin', async (t) => { + const lines = ['incoming request', 'ZHello', 'request completed'] + t.plan(lines.length + 1) + + const stream = split(JSON.parse) + + const loggerInstance = pino({ level: 'info' }, stream) + const fastify = Fastify({ + loggerInstance + }) + t.after(() => fastify.close()) + + fastify.register(function (instance, opts, done) { + instance.register(context1, { + logSerializers: { + test: value => 'Z' + value // should override + } + }) + done() + }, { logSerializers: { test: value => 'X' + value } }) + + function context1 (instance, opts, done) { + instance.get('/', (req, reply) => { + req.log.info({ test: 'Hello' }) + reply.send({ hello: 'world' }) + }) + done() + } + + await fastify.ready() + + { + const response = await fastify.inject({ method: 'GET', url: '/' }) + const body = await response.json() + t.assert.deepEqual(body, { hello: 'world' }) + } + + for await (const [line] of on(stream, 'data')) { + t.assert.deepEqual(line.test || line.msg, lines.shift()) + if (lines.length === 0) break + } + }) + + await t.test('Should increase the log level for a specific plugin', async (t) => { + const lines = ['Hello'] + t.plan(lines.length * 2 + 1) + + const stream = split(JSON.parse) + + const loggerInstance = pino({ level: 'info' }, stream) + + const fastify = Fastify({ + loggerInstance + }) + t.after(() => fastify.close()) + + fastify.register(function (instance, opts, done) { + instance.get('/', (req, reply) => { + req.log.error('Hello') // we should see this log + reply.send({ hello: 'world' }) + }) + done() + }, { logLevel: 'error' }) + + await fastify.ready() + + { + const response = await fastify.inject({ method: 'GET', url: '/' }) + const body = await response.json() + t.assert.deepEqual(body, { hello: 'world' }) + } + + for await (const [line] of on(stream, 'data')) { + t.assert.deepEqual(line.level, 50) + t.assert.deepEqual(line.msg, lines.shift()) + if (lines.length === 0) break + } + }) + + await t.test('Should set the log level for the customized 404 handler', async (t) => { + const lines = ['Hello'] + t.plan(lines.length * 2 + 1) + + const stream = split(JSON.parse) + + const loggerInstance = pino({ level: 'warn' }, stream) + + const fastify = Fastify({ + loggerInstance + }) + t.after(() => fastify.close()) + + fastify.register(function (instance, opts, done) { + instance.setNotFoundHandler(function (req, reply) { + req.log.error('Hello') + reply.code(404).send() + }) + done() + }, { logLevel: 'error' }) + + await fastify.ready() + + { + const response = await fastify.inject({ method: 'GET', url: '/' }) + t.assert.deepEqual(response.statusCode, 404) + } + + for await (const [line] of on(stream, 'data')) { + t.assert.deepEqual(line.level, 50) + t.assert.deepEqual(line.msg, lines.shift()) + if (lines.length === 0) break + } + }) + + await t.test('Should set the log level for the customized 500 handler', async (t) => { + const lines = ['Hello'] + t.plan(lines.length * 2 + 1) + + const stream = split(JSON.parse) + + const loggerInstance = pino({ level: 'warn' }, stream) + + const fastify = Fastify({ + loggerInstance + }) + t.after(() => fastify.close()) + + fastify.register(function (instance, opts, done) { + instance.get('/', (req, reply) => { + req.log.error('kaboom') + reply.send(new Error('kaboom')) + }) + + instance.setErrorHandler(function (e, request, reply) { + reply.log.fatal('Hello') + reply.code(500).send() + }) + done() + }, { logLevel: 'fatal' }) + + await fastify.ready() + + { + const response = await fastify.inject({ method: 'GET', url: '/' }) + t.assert.deepEqual(response.statusCode, 500) + } + + for await (const [line] of on(stream, 'data')) { + t.assert.deepEqual(line.level, 60) + t.assert.deepEqual(line.msg, lines.shift()) + if (lines.length === 0) break + } + }) + + await t.test('Should set a custom log level for a specific route', async (t) => { + const lines = ['incoming request', 'Hello', 'request completed'] + t.plan(lines.length + 2) + + const stream = split(JSON.parse) + + const loggerInstance = pino({ level: 'error' }, stream) + + const fastify = Fastify({ + loggerInstance + }) + t.after(() => fastify.close()) + + fastify.get('/log', { logLevel: 'info' }, (req, reply) => { + req.log.info('Hello') + reply.send({ hello: 'world' }) + }) + + fastify.get('/no-log', (req, reply) => { + req.log.info('Hello') + reply.send({ hello: 'world' }) + }) + + await fastify.ready() + + { + const response = await fastify.inject({ method: 'GET', url: '/log' }) + const body = await response.json() + t.assert.deepEqual(body, { hello: 'world' }) + } + + { + const response = await fastify.inject({ method: 'GET', url: '/no-log' }) + const body = await response.json() + t.assert.deepEqual(body, { hello: 'world' }) + } + + for await (const [line] of on(stream, 'data')) { + t.assert.deepEqual(line.msg, lines.shift()) + if (lines.length === 0) break + } + }) + + await t.test('should pass when using unWritable props in the logger option', (t) => { + t.plan(8) + const fastify = Fastify({ + logger: Object.defineProperty({}, 'level', { value: 'info' }) + }) + t.after(() => fastify.close()) + + t.assert.deepEqual(typeof fastify.log, 'object') + t.assert.deepEqual(typeof fastify.log.fatal, 'function') + t.assert.deepEqual(typeof fastify.log.error, 'function') + t.assert.deepEqual(typeof fastify.log.warn, 'function') + t.assert.deepEqual(typeof fastify.log.info, 'function') + t.assert.deepEqual(typeof fastify.log.debug, 'function') + t.assert.deepEqual(typeof fastify.log.trace, 'function') + t.assert.deepEqual(typeof fastify.log.child, 'function') + }) + + await t.test('Should throw an error if logger instance is passed to `logger`', async (t) => { + t.plan(2) + const stream = split(JSON.parse) + + const logger = require('pino')(stream) + + try { + Fastify({ logger }) + } catch (err) { + t.assert.ok(err) + t.assert.deepEqual(err.code, 'FST_ERR_LOG_INVALID_LOGGER_CONFIG') + } + }) + + await t.test('Should throw an error if options are passed to `loggerInstance`', async (t) => { + t.plan(2) + try { + Fastify({ loggerInstance: { level: 'log' } }) + } catch (err) { + t.assert.ok(err) + t.assert.strictEqual(err.code, 'FST_ERR_LOG_INVALID_LOGGER_INSTANCE') + } + }) + + await t.test('If both `loggerInstance` and `logger` are provided, an error should be thrown', async (t) => { + t.plan(2) + const loggerInstanceStream = split(JSON.parse) + const loggerInstance = pino({ level: 'error' }, loggerInstanceStream) + const loggerStream = split(JSON.parse) + try { + Fastify({ + logger: { + stream: loggerStream, + level: 'info' + }, + loggerInstance + }) + } catch (err) { + t.assert.ok(err) + t.assert.deepEqual(err.code, 'FST_ERR_LOG_LOGGER_AND_LOGGER_INSTANCE_PROVIDED') + } + }) + + await t.test('`logger` should take pino configuration and create a pino logger', async (t) => { + const lines = ['hello', 'world'] + t.plan(2 * lines.length + 2) + const loggerStream = split(JSON.parse) + const fastify = Fastify({ + logger: { + stream: loggerStream, + level: 'error' + } + }) + t.after(() => fastify.close()) + fastify.get('/hello', (req, reply) => { + req.log.error('hello') + reply.code(404).send() + }) + + fastify.get('/world', (req, reply) => { + req.log.error('world') + reply.code(201).send() + }) + + await fastify.ready() + { + const response = await fastify.inject({ method: 'GET', url: '/hello' }) + t.assert.deepEqual(response.statusCode, 404) + } + { + const response = await fastify.inject({ method: 'GET', url: '/world' }) + t.assert.deepEqual(response.statusCode, 201) + } + + for await (const [line] of on(loggerStream, 'data')) { + t.assert.deepEqual(line.level, 50) + t.assert.deepEqual(line.msg, lines.shift()) + if (lines.length === 0) break + } + }) +}) diff --git a/services/slides/node_modules/fastify/test/logger/request.test.js b/services/slides/node_modules/fastify/test/logger/request.test.js new file mode 100644 index 0000000000000000000000000000000000000000..212b8d5296f5e761d136911c116b4811bd99c6e2 --- /dev/null +++ b/services/slides/node_modules/fastify/test/logger/request.test.js @@ -0,0 +1,292 @@ +'use strict' + +const stream = require('node:stream') + +const t = require('node:test') +const split = require('split2') + +const Fastify = require('../../fastify') +const helper = require('../helper') +const { on } = stream +const { request } = require('./logger-test-utils') +const { partialDeepStrictEqual } = require('../toolkit') + +t.test('request', { timeout: 60000 }, async (t) => { + let localhost + + t.plan(7) + t.before(async function () { + [localhost] = await helper.getLoopbackHost() + }) + + await t.test('The request id header key can be customized', async (t) => { + const lines = ['incoming request', 'some log message', 'request completed'] + t.plan(lines.length * 2 + 2) + const REQUEST_ID = '42' + + const stream = split(JSON.parse) + const fastify = Fastify({ + logger: { stream, level: 'info' }, + requestIdHeader: 'my-custom-request-id' + }) + t.after(() => fastify.close()) + + fastify.get('/', (req, reply) => { + t.assert.strictEqual(req.id, REQUEST_ID) + req.log.info('some log message') + reply.send({ id: req.id }) + }) + + const response = await fastify.inject({ method: 'GET', url: '/', headers: { 'my-custom-request-id': REQUEST_ID } }) + const body = await response.json() + t.assert.strictEqual(body.id, REQUEST_ID) + + for await (const [line] of on(stream, 'data')) { + t.assert.strictEqual(line.reqId, REQUEST_ID) + t.assert.strictEqual(line.msg, lines.shift(), 'message is set') + if (lines.length === 0) break + } + }) + + await t.test('The request id header key can be ignored', async (t) => { + const lines = ['incoming request', 'some log message', 'request completed'] + t.plan(lines.length * 2 + 2) + const REQUEST_ID = 'ignore-me' + + const stream = split(JSON.parse) + const fastify = Fastify({ + logger: { stream, level: 'info' }, + requestIdHeader: false + }) + t.after(() => fastify.close()) + + fastify.get('/', (req, reply) => { + t.assert.strictEqual(req.id, 'req-1') + req.log.info('some log message') + reply.send({ id: req.id }) + }) + const response = await fastify.inject({ method: 'GET', url: '/', headers: { 'request-id': REQUEST_ID } }) + const body = await response.json() + t.assert.strictEqual(body.id, 'req-1') + + for await (const [line] of on(stream, 'data')) { + t.assert.strictEqual(line.reqId, 'req-1') + t.assert.strictEqual(line.msg, lines.shift(), 'message is set') + if (lines.length === 0) break + } + }) + + await t.test('The request id header key can be customized along with a custom id generator', async (t) => { + const REQUEST_ID = '42' + const matches = [ + { reqId: REQUEST_ID, msg: 'incoming request' }, + { reqId: REQUEST_ID, msg: 'some log message' }, + { reqId: REQUEST_ID, msg: 'request completed' }, + { reqId: 'foo', msg: 'incoming request' }, + { reqId: 'foo', msg: 'some log message 2' }, + { reqId: 'foo', msg: 'request completed' } + ] + t.plan(matches.length + 4) + + const stream = split(JSON.parse) + const fastify = Fastify({ + logger: { stream, level: 'info' }, + requestIdHeader: 'my-custom-request-id', + genReqId (req) { + return 'foo' + } + }) + t.after(() => fastify.close()) + + fastify.get('/one', (req, reply) => { + t.assert.strictEqual(req.id, REQUEST_ID) + req.log.info('some log message') + reply.send({ id: req.id }) + }) + + fastify.get('/two', (req, reply) => { + t.assert.strictEqual(req.id, 'foo') + req.log.info('some log message 2') + reply.send({ id: req.id }) + }) + + { + const response = await fastify.inject({ method: 'GET', url: '/one', headers: { 'my-custom-request-id': REQUEST_ID } }) + const body = await response.json() + t.assert.strictEqual(body.id, REQUEST_ID) + } + + { + const response = await fastify.inject({ method: 'GET', url: '/two' }) + const body = await response.json() + t.assert.strictEqual(body.id, 'foo') + } + + for await (const [line] of on(stream, 'data')) { + t.assert.ok(partialDeepStrictEqual(line, matches.shift())) + if (matches.length === 0) break + } + }) + + await t.test('The request id header key can be ignored along with a custom id generator', async (t) => { + const REQUEST_ID = 'ignore-me' + const matches = [ + { reqId: 'foo', msg: 'incoming request' }, + { reqId: 'foo', msg: 'some log message' }, + { reqId: 'foo', msg: 'request completed' }, + { reqId: 'foo', msg: 'incoming request' }, + { reqId: 'foo', msg: 'some log message 2' }, + { reqId: 'foo', msg: 'request completed' } + ] + t.plan(matches.length + 4) + + const stream = split(JSON.parse) + const fastify = Fastify({ + logger: { stream, level: 'info' }, + requestIdHeader: false, + genReqId (req) { + return 'foo' + } + }) + t.after(() => fastify.close()) + + fastify.get('/one', (req, reply) => { + t.assert.strictEqual(req.id, 'foo') + req.log.info('some log message') + reply.send({ id: req.id }) + }) + + fastify.get('/two', (req, reply) => { + t.assert.strictEqual(req.id, 'foo') + req.log.info('some log message 2') + reply.send({ id: req.id }) + }) + + { + const response = await fastify.inject({ method: 'GET', url: '/one', headers: { 'request-id': REQUEST_ID } }) + const body = await response.json() + t.assert.strictEqual(body.id, 'foo') + } + + { + const response = await fastify.inject({ method: 'GET', url: '/two' }) + const body = await response.json() + t.assert.strictEqual(body.id, 'foo') + } + + for await (const [line] of on(stream, 'data')) { + t.assert.ok(partialDeepStrictEqual(line, matches.shift())) + if (matches.length === 0) break + } + }) + + await t.test('The request id log label can be changed', async (t) => { + const REQUEST_ID = '42' + const matches = [ + { traceId: REQUEST_ID, msg: 'incoming request' }, + { traceId: REQUEST_ID, msg: 'some log message' }, + { traceId: REQUEST_ID, msg: 'request completed' } + ] + t.plan(matches.length + 2) + + const stream = split(JSON.parse) + const fastify = Fastify({ + logger: { stream, level: 'info' }, + requestIdHeader: 'my-custom-request-id', + requestIdLogLabel: 'traceId' + }) + t.after(() => fastify.close()) + + fastify.get('/one', (req, reply) => { + t.assert.strictEqual(req.id, REQUEST_ID) + req.log.info('some log message') + reply.send({ id: req.id }) + }) + + { + const response = await fastify.inject({ method: 'GET', url: '/one', headers: { 'my-custom-request-id': REQUEST_ID } }) + const body = await response.json() + t.assert.strictEqual(body.id, REQUEST_ID) + } + + for await (const [line] of on(stream, 'data')) { + t.assert.ok(partialDeepStrictEqual(line, matches.shift())) + if (matches.length === 0) break + } + }) + + await t.test('should redact the authorization header if so specified', async (t) => { + const stream = split(JSON.parse) + const fastify = Fastify({ + logger: { + stream, + redact: ['req.headers.authorization'], + level: 'info', + serializers: { + req (req) { + return { + method: req.method, + url: req.url, + headers: req.headers, + hostname: req.hostname, + remoteAddress: req.ip, + remotePort: req.socket.remotePort + } + } + } + } + }) + t.after(() => fastify.close()) + + fastify.get('/', function (req, reply) { + t.assert.deepStrictEqual(req.headers.authorization, 'Bearer abcde') + reply.send({ hello: 'world' }) + }) + + await fastify.ready() + const server = await fastify.listen({ port: 0, host: localhost }) + + const lines = [ + { msg: `Server listening at ${server}` }, + { req: { headers: { authorization: '[Redacted]' } }, msg: 'incoming request' }, + { res: { statusCode: 200 }, msg: 'request completed' } + ] + t.plan(lines.length + 3) + + await request({ + method: 'GET', + path: '/', + host: localhost, + port: fastify.server.address().port, + headers: { + authorization: 'Bearer abcde' + } + }, function (response, body) { + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(body, JSON.stringify({ hello: 'world' })) + }) + + for await (const [line] of on(stream, 'data')) { + t.assert.ok(partialDeepStrictEqual(line, lines.shift())) + if (lines.length === 0) break + } + }) + + await t.test('should not throw error when serializing custom req', (t) => { + t.plan(1) + + const lines = [] + const dest = new stream.Writable({ + write: function (chunk, enc, cb) { + lines.push(JSON.parse(chunk)) + cb() + } + }) + const fastify = Fastify({ logger: { level: 'info', stream: dest } }) + t.after(() => fastify.close()) + + fastify.log.info({ req: {} }) + + t.assert.deepStrictEqual(lines[0].req, {}) + }) +}) diff --git a/services/slides/node_modules/fastify/test/logger/response.test.js b/services/slides/node_modules/fastify/test/logger/response.test.js new file mode 100644 index 0000000000000000000000000000000000000000..227f5883fa3623e551de873f61ec873bee6314ed --- /dev/null +++ b/services/slides/node_modules/fastify/test/logger/response.test.js @@ -0,0 +1,183 @@ +'use strict' + +const stream = require('node:stream') + +const t = require('node:test') +const split = require('split2') +const pino = require('pino') + +const Fastify = require('../../fastify') +const { partialDeepStrictEqual } = require('../toolkit') +const { on } = stream + +t.test('response serialization', { timeout: 60000 }, async (t) => { + t.plan(4) + + await t.test('Should use serializers from plugin and route', async (t) => { + const lines = [ + { msg: 'incoming request' }, + { test: 'XHello', test2: 'ZHello' }, + { msg: 'request completed' } + ] + t.plan(lines.length + 1) + + const stream = split(JSON.parse) + + const loggerInstance = pino({ level: 'info' }, stream) + const fastify = Fastify({ + loggerInstance + }) + t.after(() => fastify.close()) + + fastify.register(context1, { + logSerializers: { test: value => 'X' + value } + }) + + function context1 (instance, opts, done) { + instance.get('/', { + logSerializers: { + test2: value => 'Z' + value + } + }, (req, reply) => { + req.log.info({ test: 'Hello', test2: 'Hello' }) // { test: 'XHello', test2: 'ZHello' } + reply.send({ hello: 'world' }) + }) + done() + } + + await fastify.ready() + + { + const response = await fastify.inject({ method: 'GET', url: '/' }) + const body = await response.json() + t.assert.deepStrictEqual(body, { hello: 'world' }) + } + + for await (const [line] of on(stream, 'data')) { + t.assert.ok(partialDeepStrictEqual(line, lines.shift())) + if (lines.length === 0) break + } + }) + + await t.test('Should use serializers from instance fastify and route', async (t) => { + const lines = [ + { msg: 'incoming request' }, + { test: 'XHello', test2: 'ZHello' }, + { msg: 'request completed' } + ] + t.plan(lines.length + 1) + + const stream = split(JSON.parse) + + const loggerInstance = pino({ + level: 'info', + serializers: { + test: value => 'X' + value, + test2: value => 'This should be override - ' + value + } + }, stream) + const fastify = Fastify({ + loggerInstance + }) + t.after(() => fastify.close()) + + fastify.get('/', { + logSerializers: { + test2: value => 'Z' + value + } + }, (req, reply) => { + req.log.info({ test: 'Hello', test2: 'Hello' }) // { test: 'XHello', test2: 'ZHello' } + reply.send({ hello: 'world' }) + }) + + await fastify.ready() + + { + const response = await fastify.inject({ method: 'GET', url: '/' }) + const body = await response.json() + t.assert.deepStrictEqual(body, { hello: 'world' }) + } + + for await (const [line] of on(stream, 'data')) { + t.assert.ok(partialDeepStrictEqual(line, lines.shift())) + if (lines.length === 0) break + } + }) + + await t.test('Should use serializers inherit from contexts', async (t) => { + const lines = [ + { msg: 'incoming request' }, + { test: 'XHello', test2: 'YHello', test3: 'ZHello' }, + { msg: 'request completed' } + ] + t.plan(lines.length + 1) + + const stream = split(JSON.parse) + + const loggerInstance = pino({ + level: 'info', + serializers: { + test: value => 'X' + value + } + }, stream) + + const fastify = Fastify({ loggerInstance }) + t.after(() => fastify.close()) + + fastify.register(context1, { logSerializers: { test2: value => 'Y' + value } }) + + function context1 (instance, opts, done) { + instance.get('/', { + logSerializers: { + test3: value => 'Z' + value + } + }, (req, reply) => { + req.log.info({ test: 'Hello', test2: 'Hello', test3: 'Hello' }) // { test: 'XHello', test2: 'YHello', test3: 'ZHello' } + reply.send({ hello: 'world' }) + }) + done() + } + + await fastify.ready() + + { + const response = await fastify.inject({ method: 'GET', url: '/' }) + const body = await response.json() + t.assert.deepStrictEqual(body, { hello: 'world' }) + } + + for await (const [line] of on(stream, 'data')) { + t.assert.ok(partialDeepStrictEqual(line, lines.shift())) + if (lines.length === 0) break + } + }) + + await t.test('should serialize request and response', async (t) => { + const lines = [ + { req: { method: 'GET', url: '/500' }, msg: 'incoming request' }, + { req: { method: 'GET', url: '/500' }, msg: '500 error' }, + { msg: 'request completed' } + ] + t.plan(lines.length + 1) + + const stream = split(JSON.parse) + const fastify = Fastify({ logger: { level: 'info', stream } }) + t.after(() => fastify.close()) + + fastify.get('/500', (req, reply) => { + reply.code(500).send(Error('500 error')) + }) + + await fastify.ready() + + { + const response = await fastify.inject({ method: 'GET', url: '/500' }) + t.assert.strictEqual(response.statusCode, 500) + } + + for await (const [line] of on(stream, 'data')) { + t.assert.ok(partialDeepStrictEqual(line, lines.shift())) + if (lines.length === 0) break + } + }) +}) diff --git a/services/slides/node_modules/fastify/test/logger/tap-parallel-not-ok b/services/slides/node_modules/fastify/test/logger/tap-parallel-not-ok new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/services/slides/node_modules/fastify/test/max-requests-per-socket.test.js b/services/slides/node_modules/fastify/test/max-requests-per-socket.test.js new file mode 100644 index 0000000000000000000000000000000000000000..d27adbbf18b44ac1fa4c918c43a9940b1708857d --- /dev/null +++ b/services/slides/node_modules/fastify/test/max-requests-per-socket.test.js @@ -0,0 +1,113 @@ +'use strict' + +const net = require('node:net') +const { test } = require('node:test') +const Fastify = require('..') + +test('maxRequestsPerSocket', (t, done) => { + t.plan(8) + + const fastify = Fastify({ maxRequestsPerSocket: 2 }) + fastify.get('/', (req, reply) => { + reply.send({ hello: 'world' }) + }) + + fastify.listen({ port: 0 }, function (err) { + t.assert.ifError(err) + + const port = fastify.server.address().port + const client = net.createConnection({ port }, () => { + client.write('GET / HTTP/1.1\r\nHost: fastify.test\r\n\r\n') + + client.once('data', data => { + t.assert.match(data.toString(), /Connection:\s*keep-alive/i) + t.assert.match(data.toString(), /Keep-Alive:\s*timeout=\d+/i) + t.assert.match(data.toString(), /200 OK/i) + + client.write('GET / HTTP/1.1\r\nHost: fastify.test\r\n\r\n') + + client.once('data', data => { + t.assert.match(data.toString(), /Connection:\s*close/i) + t.assert.match(data.toString(), /200 OK/i) + + client.write('GET / HTTP/1.1\r\nHost: fastify.test\r\n\r\n') + + client.once('data', data => { + t.assert.match(data.toString(), /Connection:\s*close/i) + t.assert.match(data.toString(), /503 Service Unavailable/i) + client.end() + fastify.close() + done() + }) + }) + }) + }) + }) +}) + +test('maxRequestsPerSocket zero should behave same as null', (t, done) => { + t.plan(10) + + const fastify = Fastify({ maxRequestsPerSocket: 0 }) + fastify.get('/', (req, reply) => { + reply.send({ hello: 'world' }) + }) + + fastify.listen({ port: 0 }, function (err) { + t.assert.ifError(err) + + const port = fastify.server.address().port + const client = net.createConnection({ port }, () => { + client.write('GET / HTTP/1.1\r\nHost: fastify.test\r\n\r\n') + + client.once('data', data => { + t.assert.match(data.toString(), /Connection:\s*keep-alive/i) + t.assert.match(data.toString(), /Keep-Alive:\s*timeout=\d+/i) + t.assert.match(data.toString(), /200 OK/i) + + client.write('GET / HTTP/1.1\r\nHost: fastify.test\r\n\r\n') + + client.once('data', data => { + t.assert.match(data.toString(), /Connection:\s*keep-alive/i) + t.assert.match(data.toString(), /Keep-Alive:\s*timeout=\d+/i) + t.assert.match(data.toString(), /200 OK/i) + + client.write('GET / HTTP/1.1\r\nHost: fastify.test\r\n\r\n') + + client.once('data', data => { + t.assert.match(data.toString(), /Connection:\s*keep-alive/i) + t.assert.match(data.toString(), /Keep-Alive:\s*timeout=\d+/i) + t.assert.match(data.toString(), /200 OK/i) + client.end() + fastify.close() + done() + }) + }) + }) + }) + }) +}) + +test('maxRequestsPerSocket should be set', async (t) => { + t.plan(1) + + const initialConfig = Fastify({ maxRequestsPerSocket: 5 }).initialConfig + t.assert.deepStrictEqual(initialConfig.maxRequestsPerSocket, 5) +}) + +test('maxRequestsPerSocket should 0', async (t) => { + t.plan(1) + + const initialConfig = Fastify().initialConfig + t.assert.deepStrictEqual(initialConfig.maxRequestsPerSocket, 0) +}) + +test('requestTimeout passed to server', t => { + t.plan(2) + + const httpServer = Fastify({ maxRequestsPerSocket: 5 }).server + t.assert.strictEqual(httpServer.maxRequestsPerSocket, 5) + + const httpsServer = Fastify({ maxRequestsPerSocket: 5, https: true }).server + t.assert.strictEqual(httpsServer.maxRequestsPerSocket, 5) +}) diff --git a/services/slides/node_modules/fastify/test/middleware.test.js b/services/slides/node_modules/fastify/test/middleware.test.js new file mode 100644 index 0000000000000000000000000000000000000000..05da956673531be31790a8737c7c581bd32b185f --- /dev/null +++ b/services/slides/node_modules/fastify/test/middleware.test.js @@ -0,0 +1,37 @@ +'use strict' + +const { test } = require('node:test') +const Fastify = require('..') +const { + FST_ERR_DEC_ALREADY_PRESENT +} = require('../lib/errors') + +test('Should be able to override the default use API', t => { + t.plan(1) + const fastify = Fastify() + fastify.decorate('use', () => true) + t.assert.strictEqual(fastify.use(), true) +}) + +test('Cannot decorate use twice', t => { + t.plan(1) + const fastify = Fastify() + fastify.decorate('use', () => true) + try { + fastify.decorate('use', () => true) + } catch (err) { + t.assert.ok(err instanceof FST_ERR_DEC_ALREADY_PRESENT) + } +}) + +test('Encapsulation works', t => { + const fastify = Fastify() + + fastify.register((instance, opts, done) => { + instance.decorate('use', () => true) + t.assert.strictEqual(instance.use(), true) + done() + }) + + fastify.ready() +}) diff --git a/services/slides/node_modules/fastify/test/noop-set.test.js b/services/slides/node_modules/fastify/test/noop-set.test.js new file mode 100644 index 0000000000000000000000000000000000000000..8b56a14eb27cf68e15b1eaf1307e9e03e6a0f28c --- /dev/null +++ b/services/slides/node_modules/fastify/test/noop-set.test.js @@ -0,0 +1,19 @@ +'use strict' + +const { test } = require('node:test') +const noopSet = require('../lib/noop-set') + +test('does a lot of nothing', async t => { + const aSet = noopSet() + t.assert.ok(aSet, 'object') + + const item = {} + aSet.add(item) + aSet.add({ another: 'item' }) + aSet.delete(item) + t.assert.strictEqual(aSet.has(item), true) + + for (const i of aSet) { + t.assert.fail('should not have any items: ' + i) + } +}) diff --git a/services/slides/node_modules/fastify/test/nullable-validation.test.js b/services/slides/node_modules/fastify/test/nullable-validation.test.js new file mode 100644 index 0000000000000000000000000000000000000000..456e7a72ac93d7504d83fddd3ac00dc6c44a11a4 --- /dev/null +++ b/services/slides/node_modules/fastify/test/nullable-validation.test.js @@ -0,0 +1,187 @@ +'use strict' + +const { test } = require('node:test') +const Fastify = require('..') + +test('nullable string', (t, done) => { + t.plan(3) + const fastify = Fastify() + fastify.route({ + method: 'POST', + url: '/', + handler: (req, reply) => { + t.assert.strictEqual(req.body.hello, null) + reply.code(200).send(req.body) + }, + schema: { + body: { + type: 'object', + properties: { + hello: { + type: 'string', + format: 'email', + nullable: true + } + } + }, + response: { + 200: { + type: 'object', + properties: { + hello: { + type: 'string', + format: 'email', + nullable: true + } + } + } + } + } + }) + fastify.inject({ + method: 'POST', + url: '/', + body: { + hello: null + } + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.json().hello, null) + done() + }) +}) + +test('object or null body', async (t) => { + t.plan(4) + + const fastify = Fastify() + + fastify.route({ + method: 'POST', + url: '/', + handler: (req, reply) => { + t.assert.strictEqual(req.body, undefined) + reply.code(200).send({ isUndefinedBody: req.body === undefined }) + }, + schema: { + body: { + type: ['object', 'null'], + properties: { + hello: { + type: 'string', + format: 'email' + } + } + }, + response: { + 200: { + type: 'object', + nullable: true, + properties: { + isUndefinedBody: { + type: 'boolean' + } + } + } + } + } + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) + + const result = await fetch(fastifyServer, { + method: 'POST' + }) + + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.deepStrictEqual(await result.json(), { isUndefinedBody: true }) +}) + +test('nullable body', async (t) => { + t.plan(4) + + const fastify = Fastify() + + fastify.route({ + method: 'POST', + url: '/', + handler: (req, reply) => { + t.assert.strictEqual(req.body, undefined) + reply.code(200).send({ isUndefinedBody: req.body === undefined }) + }, + schema: { + body: { + type: 'object', + nullable: true, + properties: { + hello: { + type: 'string', + format: 'email' + } + } + }, + response: { + 200: { + type: 'object', + nullable: true, + properties: { + isUndefinedBody: { + type: 'boolean' + } + } + } + } + } + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const result = await fetch(fastifyServer, { + method: 'POST' + }) + + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.deepStrictEqual(await result.json(), { isUndefinedBody: true }) +}) + +test('Nullable body with 204', async (t) => { + t.plan(4) + + const fastify = Fastify() + + fastify.route({ + method: 'POST', + url: '/', + handler: (req, reply) => { + t.assert.strictEqual(req.body, undefined) + reply.code(204).send() + }, + schema: { + body: { + type: 'object', + nullable: true, + properties: { + hello: { + type: 'string', + format: 'email' + } + } + } + } + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const result = await fetch(fastifyServer, { + method: 'POST' + }) + + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 204) + t.assert.strictEqual((await result.text()).length, 0) +}) diff --git a/services/slides/node_modules/fastify/test/options.error-handler.test.js b/services/slides/node_modules/fastify/test/options.error-handler.test.js new file mode 100644 index 0000000000000000000000000000000000000000..5dfb3987316f67e05c03d0def9c07a25f77583ed --- /dev/null +++ b/services/slides/node_modules/fastify/test/options.error-handler.test.js @@ -0,0 +1,5 @@ +'use strict' + +const t = require('node:test') +require('./helper').payloadMethod('options', t, true) +require('./input-validation').payloadMethod('options', t) diff --git a/services/slides/node_modules/fastify/test/options.test.js b/services/slides/node_modules/fastify/test/options.test.js new file mode 100644 index 0000000000000000000000000000000000000000..0f891513acddd3d55dbd7782a74f4456db26c9a8 --- /dev/null +++ b/services/slides/node_modules/fastify/test/options.test.js @@ -0,0 +1,5 @@ +'use strict' + +const t = require('node:test') +require('./helper').payloadMethod('options', t) +require('./input-validation').payloadMethod('options', t) diff --git a/services/slides/node_modules/fastify/test/output-validation.test.js b/services/slides/node_modules/fastify/test/output-validation.test.js new file mode 100644 index 0000000000000000000000000000000000000000..91ad938de73414c19d4b2a97a4627ef074f9f6f4 --- /dev/null +++ b/services/slides/node_modules/fastify/test/output-validation.test.js @@ -0,0 +1,140 @@ +'use strict' + +const { test } = require('node:test') +const fastify = require('..')() + +const opts = { + schema: { + response: { + 200: { + type: 'object', + properties: { + hello: { + type: 'string' + } + } + }, + '2xx': { + type: 'object', + properties: { + hello: { + type: 'number' + } + } + } + } + } +} + +test('shorthand - output string', t => { + t.plan(1) + try { + fastify.get('/string', opts, function (req, reply) { + reply.code(200).send({ hello: 'world' }) + }) + t.assert.ok(true) + } catch (e) { + t.assert.fail() + } +}) + +test('shorthand - output number', t => { + t.plan(1) + try { + fastify.get('/number', opts, function (req, reply) { + reply.code(201).send({ hello: 55 }) + }) + t.assert.ok(true) + } catch (e) { + t.assert.fail() + } +}) + +test('wrong object for schema - output', t => { + t.plan(1) + try { + fastify.get('/wrong-object-for-schema', opts, function (req, reply) { + // will send { } + reply.code(201).send({ hello: 'world' }) + }) + t.assert.ok(true) + } catch (e) { + t.assert.fail() + } +}) + +test('empty response', t => { + t.plan(1) + try { + // no checks + fastify.get('/empty', opts, function (req, reply) { + reply.code(204).send() + }) + t.assert.ok(true) + } catch (e) { + t.assert.fail() + } +}) + +test('unlisted response code', t => { + t.plan(1) + try { + fastify.get('/400', opts, function (req, reply) { + reply.code(400).send({ hello: 'DOOM' }) + }) + t.assert.ok(true) + } catch (e) { + t.assert.fail() + } +}) + +test('start server and run tests', async (t) => { + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + await test('shorthand - string get ok', async (t) => { + const result = await fetch(fastifyServer + '/string') + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + }) + + await test('shorthand - number get ok', async (t) => { + const result = await fetch(fastifyServer + '/number') + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 201) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 55 }) + }) + + await test('shorthand - wrong-object-for-schema', async (t) => { + const result = await fetch(fastifyServer + '/wrong-object-for-schema') + t.assert.ok(!result.ok) + t.assert.strictEqual(result.status, 500) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { + statusCode: 500, + error: 'Internal Server Error', + message: 'The value "world" cannot be converted to a number.' + }) + }) + + await test('shorthand - empty', async (t) => { + const result = await fetch(fastifyServer + '/empty') + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 204) + }) + + await test('shorthand - 400', async (t) => { + const result = await fetch(fastifyServer + '/400') + t.assert.ok(!result.ok) + t.assert.strictEqual(result.status, 400) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'DOOM' }) + }) +}) diff --git a/services/slides/node_modules/fastify/test/patch.error-handler.test.js b/services/slides/node_modules/fastify/test/patch.error-handler.test.js new file mode 100644 index 0000000000000000000000000000000000000000..fed052cf206081d548d0b691ebe4702e9ec8bb95 --- /dev/null +++ b/services/slides/node_modules/fastify/test/patch.error-handler.test.js @@ -0,0 +1,5 @@ +'use strict' + +const t = require('node:test') +require('./helper').payloadMethod('patch', t, true) +require('./input-validation').payloadMethod('patch', t) diff --git a/services/slides/node_modules/fastify/test/patch.test.js b/services/slides/node_modules/fastify/test/patch.test.js new file mode 100644 index 0000000000000000000000000000000000000000..aa61beec39b83ee0de6158ac12c0a3fd897dd450 --- /dev/null +++ b/services/slides/node_modules/fastify/test/patch.test.js @@ -0,0 +1,5 @@ +'use strict' + +const t = require('node:test') +require('./helper').payloadMethod('patch', t) +require('./input-validation').payloadMethod('patch', t) diff --git a/services/slides/node_modules/fastify/test/plugin.1.test.js b/services/slides/node_modules/fastify/test/plugin.1.test.js new file mode 100644 index 0000000000000000000000000000000000000000..34027aa3d0304ce4197ddfedd8e5c2db236840f3 --- /dev/null +++ b/services/slides/node_modules/fastify/test/plugin.1.test.js @@ -0,0 +1,230 @@ +'use strict' + +const t = require('node:test') +const test = t.test +const Fastify = require('../fastify') +const fp = require('fastify-plugin') + +test('require a plugin', (t, testDone) => { + t.plan(1) + const fastify = Fastify() + fastify.register(require('./plugin.helper')) + fastify.ready(() => { + t.assert.ok(fastify.test) + testDone() + }) +}) + +test('plugin metadata - ignore prefix', (t, testDone) => { + t.plan(2) + const fastify = Fastify() + + plugin[Symbol.for('skip-override')] = true + fastify.register(plugin, { prefix: 'foo' }) + + fastify.inject({ + method: 'GET', + url: '/' + }, function (err, res) { + t.assert.ifError(err) + t.assert.strictEqual(res.payload, 'hello') + testDone() + }) + + function plugin (instance, opts, done) { + instance.get('/', function (request, reply) { + reply.send('hello') + }) + done() + } +}) + +test('plugin metadata - naming plugins', async t => { + t.plan(2) + const fastify = Fastify() + + fastify.register(require('./plugin.name.display')) + fastify.register(function (fastify, opts, done) { + // one line + t.assert.strictEqual(fastify.pluginName, 'function (fastify, opts, done) { -- // one line') + done() + }) + fastify.register(function fooBar (fastify, opts, done) { + t.assert.strictEqual(fastify.pluginName, 'fooBar') + done() + }) + + await fastify.ready() +}) + +test('fastify.register with fastify-plugin should not encapsulate his code', async t => { + t.plan(9) + const fastify = Fastify() + + fastify.register((instance, opts, done) => { + instance.register(fp((i, o, n) => { + i.decorate('test', () => {}) + t.assert.ok(i.test) + n() + })) + + t.assert.ok(!instance.test) + + // the decoration is added at the end + instance.after(() => { + t.assert.ok(instance.test) + }) + + instance.get('/', (req, reply) => { + t.assert.ok(instance.test) + reply.send({ hello: 'world' }) + }) + + done() + }) + + fastify.ready(() => { + t.assert.ok(!fastify.test) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) + + const result = await fetch(fastifyServer) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) +}) + +test('fastify.register with fastify-plugin should provide access to external fastify instance if opts argument is a function', async t => { + t.plan(21) + const fastify = Fastify() + + fastify.register((instance, opts, done) => { + instance.register(fp((i, o, n) => { + i.decorate('global', () => {}) + t.assert.ok(i.global) + n() + })) + + instance.register((i, o, n) => n(), p => { + t.assert.ok(!(p === instance || p === fastify)) + t.assert.ok(Object.prototype.isPrototypeOf.call(instance, p)) + t.assert.ok(Object.prototype.isPrototypeOf.call(fastify, p)) + t.assert.ok(p.global) + }) + + instance.register((i, o, n) => { + i.decorate('local', () => {}) + n() + }) + + instance.register((i, o, n) => n(), p => t.assert.ok(!p.local)) + + instance.register((i, o, n) => { + t.assert.ok(i.local) + n() + }, p => p.decorate('local', () => {})) + + instance.register((i, o, n) => n(), p => t.assert.ok(!p.local)) + + instance.register(fp((i, o, n) => { + t.assert.ok(i.global_2) + n() + }), p => p.decorate('global_2', () => 'hello')) + + instance.register((i, o, n) => { + i.decorate('global_2', () => 'world') + n() + }, p => p.get('/', (req, reply) => { + t.assert.ok(p.global_2) + reply.send({ hello: p.global_2() }) + })) + + t.assert.ok(!instance.global) + t.assert.ok(!instance.global_2) + t.assert.ok(!instance.local) + + // the decoration is added at the end + instance.after(() => { + t.assert.ok(instance.global) + t.assert.strictEqual(instance.global_2(), 'hello') + t.assert.ok(!instance.local) + }) + + done() + }) + + fastify.ready(() => { + t.assert.ok(!fastify.global) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) + + const result = await fetch(fastifyServer) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) +}) + +test('fastify.register with fastify-plugin registers fastify level plugins', async t => { + t.plan(14) + const fastify = Fastify() + + function fastifyPlugin (instance, opts, done) { + instance.decorate('test', 'first') + t.assert.ok(instance.test) + done() + } + + function innerPlugin (instance, opts, done) { + instance.decorate('test2', 'second') + done() + } + + fastify.register(fp(fastifyPlugin)) + + fastify.register((instance, opts, done) => { + t.assert.ok(instance.test) + instance.register(fp(innerPlugin)) + + instance.get('/test2', (req, reply) => { + t.assert.ok(instance.test2) + reply.send({ test2: instance.test2 }) + }) + + done() + }) + + fastify.ready(() => { + t.assert.ok(fastify.test) + t.assert.ok(!fastify.test2) + }) + + fastify.get('/', (req, reply) => { + t.assert.ok(fastify.test) + reply.send({ test: fastify.test }) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) + + const result1 = await fetch(fastifyServer) + t.assert.ok(result1.ok) + t.assert.strictEqual(result1.status, 200) + const body1 = await result1.text() + t.assert.strictEqual(result1.headers.get('content-length'), '' + body1.length) + t.assert.deepStrictEqual(JSON.parse(body1), { test: 'first' }) + + const result2 = await fetch(fastifyServer + '/test2') + t.assert.ok(result2.ok) + t.assert.strictEqual(result2.status, 200) + const body2 = await result2.text() + t.assert.strictEqual(result2.headers.get('content-length'), '' + body2.length) + t.assert.deepStrictEqual(JSON.parse(body2), { test2: 'second' }) +}) diff --git a/services/slides/node_modules/fastify/test/plugin.2.test.js b/services/slides/node_modules/fastify/test/plugin.2.test.js new file mode 100644 index 0000000000000000000000000000000000000000..a073995d843851b6f4395e50e30c87605a037fb3 --- /dev/null +++ b/services/slides/node_modules/fastify/test/plugin.2.test.js @@ -0,0 +1,314 @@ +'use strict' + +const { test } = require('node:test') +const Fastify = require('../fastify') +const fp = require('fastify-plugin') + +test('check dependencies - should not throw', async t => { + t.plan(11) + const fastify = Fastify() + + fastify.register((instance, opts, done) => { + instance.register(fp((i, o, n) => { + i.decorate('test', () => {}) + t.assert.ok(i.test) + n() + })) + + instance.register(fp((i, o, n) => { + try { + i.decorate('otherTest', () => {}, ['test']) + t.assert.ok(i.test) + t.assert.ok(i.otherTest) + n() + } catch (e) { + t.assert.fail() + } + })) + + instance.get('/', (req, reply) => { + t.assert.ok(instance.test) + t.assert.ok(instance.otherTest) + reply.send({ hello: 'world' }) + }) + + done() + }) + + fastify.ready(() => { + t.assert.ok(!fastify.test) + t.assert.ok(!fastify.otherTest) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) + + const result = await fetch(fastifyServer) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) +}) + +test('check dependencies - should throw', async t => { + t.plan(11) + const fastify = Fastify() + + fastify.register((instance, opts, done) => { + instance.register(fp((i, o, n) => { + try { + i.decorate('otherTest', () => {}, ['test']) + t.assert.fail() + } catch (e) { + t.assert.strictEqual(e.code, 'FST_ERR_DEC_MISSING_DEPENDENCY') + t.assert.strictEqual(e.message, 'The decorator is missing dependency \'test\'.') + } + n() + })) + + instance.register(fp((i, o, n) => { + i.decorate('test', () => {}) + t.assert.ok(i.test) + t.assert.ok(!i.otherTest) + n() + })) + + instance.get('/', (req, reply) => { + t.assert.ok(instance.test) + t.assert.ok(!instance.otherTest) + reply.send({ hello: 'world' }) + }) + + done() + }) + + fastify.ready(() => { + t.assert.ok(!fastify.test) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) + + const result = await fetch(fastifyServer) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) +}) + +test('set the plugin name based on the plugin displayName symbol', (t, testDone) => { + t.plan(6) + const fastify = Fastify() + t.after(() => fastify.close()) + + fastify.register(fp((fastify, opts, done) => { + t.assert.strictEqual(fastify.pluginName, 'fastify -> plugin-A') + fastify.register(fp((fastify, opts, done) => { + t.assert.strictEqual(fastify.pluginName, 'fastify -> plugin-A -> plugin-AB') + done() + }, { name: 'plugin-AB' })) + fastify.register(fp((fastify, opts, done) => { + t.assert.strictEqual(fastify.pluginName, 'fastify -> plugin-A -> plugin-AB -> plugin-AC') + done() + }, { name: 'plugin-AC' })) + done() + }, { name: 'plugin-A' })) + + fastify.register(fp((fastify, opts, done) => { + t.assert.strictEqual(fastify.pluginName, 'fastify -> plugin-A -> plugin-AB -> plugin-AC -> plugin-B') + done() + }, { name: 'plugin-B' })) + + t.assert.strictEqual(fastify.pluginName, 'fastify') + + fastify.listen({ port: 0 }, err => { + t.assert.ifError(err) + testDone() + }) +}) + +test('plugin name will change when using no encapsulation', (t, testDone) => { + t.plan(6) + const fastify = Fastify() + t.after(() => fastify.close()) + + fastify.register(fp((fastify, opts, done) => { + // store it in a different variable will hold the correct name + const pluginName = fastify.pluginName + fastify.register(fp((fastify, opts, done) => { + t.assert.strictEqual(fastify.pluginName, 'fastify -> plugin-A -> plugin-AB') + done() + }, { name: 'plugin-AB' })) + fastify.register(fp((fastify, opts, done) => { + t.assert.strictEqual(fastify.pluginName, 'fastify -> plugin-A -> plugin-AB -> plugin-AC') + done() + }, { name: 'plugin-AC' })) + setImmediate(() => { + // normally we would expect the name plugin-A + // but we operate on the same instance in each plugin + t.assert.strictEqual(fastify.pluginName, 'fastify -> plugin-A -> plugin-AB -> plugin-AC') + t.assert.strictEqual(pluginName, 'fastify -> plugin-A') + }) + done() + }, { name: 'plugin-A' })) + + t.assert.strictEqual(fastify.pluginName, 'fastify') + + fastify.listen({ port: 0 }, err => { + t.assert.ifError(err) + testDone() + }) +}) + +test('plugin name is undefined when accessing in no plugin context', (t, testDone) => { + t.plan(2) + const fastify = Fastify() + t.after(() => fastify.close()) + + t.assert.strictEqual(fastify.pluginName, 'fastify') + + fastify.listen({ port: 0 }, err => { + t.assert.ifError(err) + testDone() + }) +}) + +test('set the plugin name based on the plugin function name', (t, testDone) => { + t.plan(5) + const fastify = Fastify() + t.after(() => fastify.close()) + + fastify.register(function myPluginA (fastify, opts, done) { + t.assert.strictEqual(fastify.pluginName, 'myPluginA') + fastify.register(function myPluginAB (fastify, opts, done) { + t.assert.strictEqual(fastify.pluginName, 'myPluginAB') + done() + }) + setImmediate(() => { + // exact name due to encapsulation + t.assert.strictEqual(fastify.pluginName, 'myPluginA') + }) + done() + }) + + fastify.register(function myPluginB (fastify, opts, done) { + t.assert.strictEqual(fastify.pluginName, 'myPluginB') + done() + }) + + fastify.listen({ port: 0 }, err => { + t.assert.ifError(err) + testDone() + }) +}) + +test('approximate a plugin name when no meta data is available', (t, testDone) => { + t.plan(7) + const fastify = Fastify() + t.after(() => fastify.close()) + + fastify.register((fastify, opts, done) => { + // A + t.assert.strictEqual(fastify.pluginName.startsWith('(fastify, opts, done)'), true) + t.assert.strictEqual(fastify.pluginName.includes('// A'), true) + fastify.register((fastify, opts, done) => { + // B + t.assert.strictEqual(fastify.pluginName.startsWith('(fastify, opts, done)'), true) + t.assert.strictEqual(fastify.pluginName.includes('// B'), true) + done() + }) + setImmediate(() => { + t.assert.strictEqual(fastify.pluginName.startsWith('(fastify, opts, done)'), true) + t.assert.strictEqual(fastify.pluginName.includes('// A'), true) + }) + done() + }) + + fastify.listen({ port: 0 }, err => { + t.assert.ifError(err) + testDone() + }) +}) + +test('approximate a plugin name also when fastify-plugin has no meta data', (t, testDone) => { + t.plan(4) + const fastify = Fastify() + t.after(() => fastify.close()) + + // plugin name is got from current file name + const pluginName = /plugin\.2\.test/ + const pluginNameWithFunction = /plugin\.2\.test-auto-\d+ -> B/ + + fastify.register(fp((fastify, opts, done) => { + t.assert.match(fastify.pluginName, pluginName) + fastify.register(fp(function B (fastify, opts, done) { + // function has name + t.assert.match(fastify.pluginName, pluginNameWithFunction) + done() + })) + setImmediate(() => { + t.assert.match(fastify.pluginName, pluginNameWithFunction) + }) + done() + })) + + fastify.listen({ port: 0 }, err => { + t.assert.ifError(err) + testDone() + }) +}) + +test('plugin encapsulation', async t => { + t.plan(9) + const fastify = Fastify() + t.after(() => fastify.close()) + + fastify.register((instance, opts, done) => { + instance.register(fp((i, o, n) => { + i.decorate('test', 'first') + n() + })) + + instance.get('/first', (req, reply) => { + reply.send({ plugin: instance.test }) + }) + + done() + }) + + fastify.register((instance, opts, done) => { + instance.register(fp((i, o, n) => { + i.decorate('test', 'second') + n() + })) + + instance.get('/second', (req, reply) => { + reply.send({ plugin: instance.test }) + }) + + done() + }) + + fastify.ready(() => { + t.assert.ok(!fastify.test) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) + + const result1 = await fetch(fastifyServer + '/first') + t.assert.ok(result1.ok) + t.assert.strictEqual(result1.status, 200) + const body1 = await result1.text() + t.assert.strictEqual(result1.headers.get('content-length'), '' + body1.length) + t.assert.deepStrictEqual(JSON.parse(body1), { plugin: 'first' }) + + const result2 = await fetch(fastifyServer + '/second') + t.assert.ok(result2.ok) + t.assert.strictEqual(result2.status, 200) + const body2 = await result2.text() + t.assert.strictEqual(result2.headers.get('content-length'), '' + body2.length) + t.assert.deepStrictEqual(JSON.parse(body2), { plugin: 'second' }) +}) diff --git a/services/slides/node_modules/fastify/test/plugin.3.test.js b/services/slides/node_modules/fastify/test/plugin.3.test.js new file mode 100644 index 0000000000000000000000000000000000000000..423bb74d6a44fff57669e522d372cf54abf51db3 --- /dev/null +++ b/services/slides/node_modules/fastify/test/plugin.3.test.js @@ -0,0 +1,287 @@ +'use strict' + +const { test } = require('node:test') +const Fastify = require('../fastify') +const fp = require('fastify-plugin') + +test('if a plugin raises an error and there is not a callback to handle it, the server must not start', (t, testDone) => { + t.plan(2) + const fastify = Fastify() + + fastify.register((instance, opts, done) => { + done(new Error('err')) + }) + + fastify.listen({ port: 0 }, err => { + t.assert.ok(err instanceof Error) + t.assert.strictEqual(err.message, 'err') + testDone() + }) +}) + +test('add hooks after route declaration', async t => { + t.plan(2) + const fastify = Fastify() + t.after(() => fastify.close()) + + function plugin (instance, opts, done) { + instance.decorateRequest('check', null) + instance.addHook('onRequest', (req, reply, done) => { + req.check = {} + done() + }) + setImmediate(done) + } + fastify.register(fp(plugin)) + + fastify.register((instance, options, done) => { + instance.addHook('preHandler', function b (req, res, done) { + req.check.hook2 = true + done() + }) + + instance.get('/', (req, reply) => { + reply.send(req.check) + }) + + instance.addHook('preHandler', function c (req, res, done) { + req.check.hook3 = true + done() + }) + + done() + }) + + fastify.addHook('preHandler', function a (req, res, done) { + req.check.hook1 = true + done() + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + + const result = await fetch(fastifyServer) + t.assert.ok(result.ok) + t.assert.deepStrictEqual(await result.json(), { hook1: true, hook2: true, hook3: true }) +}) + +test('nested plugins', async t => { + t.plan(4) + + const fastify = Fastify() + + t.after(() => fastify.close()) + + fastify.register(function (fastify, opts, done) { + fastify.register((fastify, opts, done) => { + fastify.get('/', function (req, reply) { + reply.send('I am child 1') + }) + done() + }, { prefix: '/child1' }) + + fastify.register((fastify, opts, done) => { + fastify.get('/', function (req, reply) { + reply.send('I am child 2') + }) + done() + }, { prefix: '/child2' }) + + done() + }, { prefix: '/parent' }) + + const fastifyServer = await fastify.listen({ port: 0 }) + + const result1 = await fetch(fastifyServer + '/parent/child1') + t.assert.ok(result1.ok) + t.assert.deepStrictEqual(await result1.text(), 'I am child 1') + + const result2 = await fetch(fastifyServer + '/parent/child2') + t.assert.ok(result2.ok) + t.assert.deepStrictEqual(await result2.text(), 'I am child 2') +}) + +test('nested plugins awaited', async t => { + t.plan(4) + + const fastify = Fastify() + + t.after(() => fastify.close()) + + fastify.register(async function wrap (fastify, opts) { + await fastify.register(async function child1 (fastify, opts) { + fastify.get('/', function (req, reply) { + reply.send('I am child 1') + }) + }, { prefix: '/child1' }) + + await fastify.register(async function child2 (fastify, opts) { + fastify.get('/', function (req, reply) { + reply.send('I am child 2') + }) + }, { prefix: '/child2' }) + }, { prefix: '/parent' }) + + const fastifyServer = await fastify.listen({ port: 0 }) + + const result1 = await fetch(fastifyServer + '/parent/child1') + t.assert.ok(result1.ok) + t.assert.deepStrictEqual(await result1.text(), 'I am child 1') + + const result2 = await fetch(fastifyServer + '/parent/child2') + t.assert.ok(result2.ok) + t.assert.deepStrictEqual(await result2.text(), 'I am child 2') +}) + +test('plugin metadata - decorators', (t, testDone) => { + t.plan(1) + const fastify = Fastify() + + fastify.decorate('plugin1', true) + fastify.decorateReply('plugin1', true) + fastify.decorateRequest('plugin1', true) + + plugin[Symbol.for('skip-override')] = true + plugin[Symbol.for('plugin-meta')] = { + decorators: { + fastify: ['plugin1'], + reply: ['plugin1'], + request: ['plugin1'] + } + } + + fastify.register(plugin) + + fastify.ready(() => { + t.assert.ok(fastify.plugin) + testDone() + }) + + function plugin (instance, opts, done) { + instance.decorate('plugin', true) + done() + } +}) + +test('plugin metadata - decorators - should throw', (t, testDone) => { + t.plan(1) + const fastify = Fastify() + + fastify.decorate('plugin1', true) + fastify.decorateReply('plugin1', true) + + plugin[Symbol.for('skip-override')] = true + plugin[Symbol.for('plugin-meta')] = { + decorators: { + fastify: ['plugin1'], + reply: ['plugin1'], + request: ['plugin1'] + } + } + + fastify.register(plugin) + fastify.ready((err) => { + t.assert.strictEqual(err.message, "The decorator 'plugin1' is not present in Request") + testDone() + }) + + function plugin (instance, opts, done) { + instance.decorate('plugin', true) + done() + } +}) + +test('plugin metadata - decorators - should throw with plugin name', (t, testDone) => { + t.plan(1) + const fastify = Fastify() + + fastify.decorate('plugin1', true) + fastify.decorateReply('plugin1', true) + + plugin[Symbol.for('skip-override')] = true + plugin[Symbol.for('plugin-meta')] = { + name: 'the-plugin', + decorators: { + fastify: ['plugin1'], + reply: ['plugin1'], + request: ['plugin1'] + } + } + + fastify.register(plugin) + fastify.ready((err) => { + t.assert.strictEqual(err.message, "The decorator 'plugin1' required by 'the-plugin' is not present in Request") + testDone() + }) + + function plugin (instance, opts, done) { + instance.decorate('plugin', true) + done() + } +}) + +test('plugin metadata - dependencies', (t, testDone) => { + t.plan(1) + const fastify = Fastify() + + dependency[Symbol.for('skip-override')] = true + dependency[Symbol.for('plugin-meta')] = { + name: 'plugin' + } + + plugin[Symbol.for('skip-override')] = true + plugin[Symbol.for('plugin-meta')] = { + dependencies: ['plugin'] + } + + fastify.register(dependency) + fastify.register(plugin) + + fastify.ready(() => { + t.assert.ok('everything right') + testDone() + }) + + function dependency (instance, opts, done) { + done() + } + + function plugin (instance, opts, done) { + done() + } +}) + +test('plugin metadata - dependencies (nested)', (t, testDone) => { + t.plan(1) + const fastify = Fastify() + + dependency[Symbol.for('skip-override')] = true + dependency[Symbol.for('plugin-meta')] = { + name: 'plugin' + } + + nested[Symbol.for('skip-override')] = true + nested[Symbol.for('plugin-meta')] = { + dependencies: ['plugin'] + } + + fastify.register(dependency) + fastify.register(plugin) + + fastify.ready(() => { + t.assert.ok('everything right') + testDone() + }) + + function dependency (instance, opts, done) { + done() + } + + function plugin (instance, opts, done) { + instance.register(nested) + done() + } + + function nested (instance, opts, done) { + done() + } +}) diff --git a/services/slides/node_modules/fastify/test/plugin.4.test.js b/services/slides/node_modules/fastify/test/plugin.4.test.js new file mode 100644 index 0000000000000000000000000000000000000000..497d06cf1db1dd37ebd5597c758482912c434a8b --- /dev/null +++ b/services/slides/node_modules/fastify/test/plugin.4.test.js @@ -0,0 +1,504 @@ +'use strict' + +const { test, describe } = require('node:test') +const Fastify = require('../fastify') +const fp = require('fastify-plugin') +const fakeTimer = require('@sinonjs/fake-timers') +const { FST_ERR_PLUGIN_INVALID_ASYNC_HANDLER } = require('../lib/errors') + +test('pluginTimeout', (t, testDone) => { + t.plan(5) + const fastify = Fastify({ + pluginTimeout: 10 + }) + fastify.register(function (app, opts, done) { + // to no call done on purpose + }) + fastify.ready((err) => { + t.assert.ok(err) + t.assert.strictEqual(err.message, + "fastify-plugin: Plugin did not start in time: 'function (app, opts, done) { -- // to no call done on purpose'. You may have forgotten to call 'done' function or to resolve a Promise") + t.assert.strictEqual(err.code, 'FST_ERR_PLUGIN_TIMEOUT') + t.assert.ok(err.cause) + t.assert.strictEqual(err.cause.code, 'AVV_ERR_PLUGIN_EXEC_TIMEOUT') + testDone() + }) +}) + +test('pluginTimeout - named function', (t, testDone) => { + t.plan(5) + const fastify = Fastify({ + pluginTimeout: 10 + }) + fastify.register(function nameFunction (app, opts, done) { + // to no call done on purpose + }) + fastify.ready((err) => { + t.assert.ok(err) + t.assert.strictEqual(err.message, + "fastify-plugin: Plugin did not start in time: 'nameFunction'. You may have forgotten to call 'done' function or to resolve a Promise") + t.assert.strictEqual(err.code, 'FST_ERR_PLUGIN_TIMEOUT') + t.assert.ok(err.cause) + t.assert.strictEqual(err.cause.code, 'AVV_ERR_PLUGIN_EXEC_TIMEOUT') + testDone() + }) +}) + +test('pluginTimeout default', (t, testDone) => { + t.plan(5) + const clock = fakeTimer.install({ shouldClearNativeTimers: true }) + + const fastify = Fastify() + fastify.register(function (app, opts, done) { + // default time elapsed without calling done + clock.tick(10000) + }) + + fastify.ready((err) => { + t.assert.ok(err) + t.assert.strictEqual(err.message, + "fastify-plugin: Plugin did not start in time: 'function (app, opts, done) { -- // default time elapsed without calling done'. You may have forgotten to call 'done' function or to resolve a Promise") + t.assert.strictEqual(err.code, 'FST_ERR_PLUGIN_TIMEOUT') + t.assert.ok(err.cause) + t.assert.strictEqual(err.cause.code, 'AVV_ERR_PLUGIN_EXEC_TIMEOUT') + testDone() + }) + + t.after(clock.uninstall) +}) + +test('plugin metadata - version', (t, testDone) => { + t.plan(1) + const fastify = Fastify() + + plugin[Symbol.for('skip-override')] = true + plugin[Symbol.for('plugin-meta')] = { + name: 'plugin', + fastify: '2.0.0' + } + + fastify.register(plugin) + + fastify.ready(() => { + t.assert.ok('everything right') + testDone() + }) + + function plugin (instance, opts, done) { + done() + } +}) + +test('plugin metadata - version range', (t, testDone) => { + t.plan(1) + const fastify = Fastify() + + plugin[Symbol.for('skip-override')] = true + plugin[Symbol.for('plugin-meta')] = { + name: 'plugin', + fastify: '>=2.0.0' + } + + fastify.register(plugin) + + fastify.ready(() => { + t.assert.ok('everything right') + testDone() + }) + + function plugin (instance, opts, done) { + done() + } +}) + +test('plugin metadata - version not matching requirement', (t, testDone) => { + t.plan(2) + const fastify = Fastify() + + plugin[Symbol.for('skip-override')] = true + plugin[Symbol.for('plugin-meta')] = { + name: 'plugin', + fastify: '99.0.0' + } + + fastify.register(plugin) + + fastify.ready((err) => { + t.assert.ok(err) + t.assert.strictEqual(err.code, 'FST_ERR_PLUGIN_VERSION_MISMATCH') + testDone() + }) + + function plugin (instance, opts, done) { + done() + } +}) + +test('plugin metadata - version not matching requirement 2', (t, testDone) => { + t.plan(2) + const fastify = Fastify() + Object.defineProperty(fastify, 'version', { + value: '99.0.0' + }) + + plugin[Symbol.for('skip-override')] = true + plugin[Symbol.for('plugin-meta')] = { + name: 'plugin', + fastify: '<=3.0.0' + } + + fastify.register(plugin) + + fastify.ready((err) => { + t.assert.ok(err) + t.assert.strictEqual(err.code, 'FST_ERR_PLUGIN_VERSION_MISMATCH') + testDone() + }) + + function plugin (instance, opts, done) { + done() + } +}) + +test('plugin metadata - version not matching requirement 3', (t, testDone) => { + t.plan(2) + const fastify = Fastify() + + plugin[Symbol.for('skip-override')] = true + plugin[Symbol.for('plugin-meta')] = { + name: 'plugin', + fastify: '>=99.0.0' + } + + fastify.register(plugin) + + fastify.ready((err) => { + t.assert.ok(err) + t.assert.strictEqual(err.code, 'FST_ERR_PLUGIN_VERSION_MISMATCH') + testDone() + }) + + function plugin (instance, opts, done) { + done() + } +}) + +test('plugin metadata - release candidate', (t, testDone) => { + t.plan(2) + const fastify = Fastify() + Object.defineProperty(fastify, 'version', { + value: '99.0.0-rc.1' + }) + + plugin[Symbol.for('plugin-meta')] = { + name: 'plugin', + fastify: '99.x' + } + + fastify.register(plugin) + + fastify.ready((err) => { + t.assert.ifError(err) + t.assert.ok('everything right') + testDone() + }) + + function plugin (instance, opts, done) { + done() + } +}) + +describe('fastify-rc loads prior version plugins', async () => { + test('baseline (rc)', (t, testDone) => { + t.plan(1) + + const fastify = Fastify() + Object.defineProperty(fastify, 'version', { + value: '99.0.0-rc.1' + }) + + plugin[Symbol.for('plugin-meta')] = { + name: 'plugin', + fastify: '^98.1.0' + } + plugin2[Symbol.for('plugin-meta')] = { + name: 'plugin2', + fastify: '98.x' + } + + fastify.register(plugin) + + fastify.ready((err) => { + t.assert.ifError(err) + testDone() + }) + + function plugin (instance, opts, done) { + done() + } + + function plugin2 (instance, opts, done) { + done() + } + }) + + test('pre', (t, testDone) => { + t.plan(1) + + const fastify = Fastify() + Object.defineProperty(fastify, 'version', { value: '99.0.0-pre.1' }) + + plugin[Symbol.for('plugin-meta')] = { name: 'plugin', fastify: '^98.x' } + + fastify.register(plugin) + + fastify.ready((err) => { + t.assert.ifError(err) + testDone() + }) + + function plugin (instance, opts, done) { done() } + }) + + test('alpha', (t, testDone) => { + t.plan(1) + + const fastify = Fastify() + Object.defineProperty(fastify, 'version', { value: '99.0.0-pre.1' }) + + plugin[Symbol.for('plugin-meta')] = { name: 'plugin', fastify: '^98.x' } + + fastify.register(plugin) + + fastify.ready((err) => { + t.assert.ifError(err) + testDone() + }) + + function plugin (instance, opts, done) { done() } + }) +}) + +test('hasPlugin method exists as a function', (t) => { + const fastify = Fastify() + t.assert.strictEqual(typeof fastify.hasPlugin, 'function') +}) + +test('hasPlugin returns true if the specified plugin has been registered', async t => { + t.plan(4) + + const fastify = Fastify() + + function pluginA (fastify, opts, done) { + t.assert.ok(fastify.hasPlugin('plugin-A')) + done() + } + pluginA[Symbol.for('fastify.display-name')] = 'plugin-A' + fastify.register(pluginA) + + fastify.register(function pluginB (fastify, opts, done) { + t.assert.ok(fastify.hasPlugin('pluginB')) + done() + }) + + fastify.register(function (fastify, opts, done) { + // one line + t.assert.ok(fastify.hasPlugin('function (fastify, opts, done) { -- // one line')) + done() + }) + + await fastify.ready() + + t.assert.ok(fastify.hasPlugin('fastify')) +}) + +test('hasPlugin returns false if the specified plugin has not been registered', (t) => { + const fastify = Fastify() + t.assert.ok(!fastify.hasPlugin('pluginFoo')) +}) + +test('hasPlugin returns false when using encapsulation', async t => { + t.plan(25) + + const fastify = Fastify() + t.after(() => fastify.close()) + + fastify.register(function pluginA (fastify, opts, done) { + t.assert.ok(fastify.hasPlugin('pluginA')) + t.assert.ok(!fastify.hasPlugin('pluginAA')) + t.assert.ok(!fastify.hasPlugin('pluginAAA')) + t.assert.ok(!fastify.hasPlugin('pluginAB')) + t.assert.ok(!fastify.hasPlugin('pluginB')) + + fastify.register(function pluginAA (fastify, opts, done) { + t.assert.ok(!fastify.hasPlugin('pluginA')) + t.assert.ok(fastify.hasPlugin('pluginAA')) + t.assert.ok(!fastify.hasPlugin('pluginAAA')) + t.assert.ok(!fastify.hasPlugin('pluginAB')) + t.assert.ok(!fastify.hasPlugin('pluginB')) + + fastify.register(function pluginAAA (fastify, opts, done) { + t.assert.ok(!fastify.hasPlugin('pluginA')) + t.assert.ok(!fastify.hasPlugin('pluginAA')) + t.assert.ok(fastify.hasPlugin('pluginAAA')) + t.assert.ok(!fastify.hasPlugin('pluginAB')) + t.assert.ok(!fastify.hasPlugin('pluginB')) + + done() + }) + + done() + }) + + fastify.register(function pluginAB (fastify, opts, done) { + t.assert.ok(!fastify.hasPlugin('pluginA')) + t.assert.ok(!fastify.hasPlugin('pluginAA')) + t.assert.ok(!fastify.hasPlugin('pluginAAA')) + t.assert.ok(fastify.hasPlugin('pluginAB')) + t.assert.ok(!fastify.hasPlugin('pluginB')) + + done() + }) + + done() + }) + + fastify.register(function pluginB (fastify, opts, done) { + t.assert.ok(!fastify.hasPlugin('pluginA')) + t.assert.ok(!fastify.hasPlugin('pluginAA')) + t.assert.ok(!fastify.hasPlugin('pluginAAA')) + t.assert.ok(!fastify.hasPlugin('pluginAB')) + t.assert.ok(fastify.hasPlugin('pluginB')) + + done() + }) + + await fastify.ready() +}) + +test('hasPlugin returns true when using no encapsulation', async t => { + t.plan(26) + + const fastify = Fastify() + + fastify.register(fp((fastify, opts, done) => { + t.assert.strictEqual(fastify.pluginName, 'fastify -> plugin-AA') + t.assert.ok(fastify.hasPlugin('plugin-AA')) + t.assert.ok(!fastify.hasPlugin('plugin-A')) + t.assert.ok(!fastify.hasPlugin('plugin-AAA')) + t.assert.ok(!fastify.hasPlugin('plugin-AB')) + t.assert.ok(!fastify.hasPlugin('plugin-B')) + + fastify.register(fp((fastify, opts, done) => { + t.assert.ok(fastify.hasPlugin('plugin-AA')) + t.assert.ok(fastify.hasPlugin('plugin-A')) + t.assert.ok(!fastify.hasPlugin('plugin-AAA')) + t.assert.ok(!fastify.hasPlugin('plugin-AB')) + t.assert.ok(!fastify.hasPlugin('plugin-B')) + + fastify.register(fp((fastify, opts, done) => { + t.assert.ok(fastify.hasPlugin('plugin-AA')) + t.assert.ok(fastify.hasPlugin('plugin-A')) + t.assert.ok(fastify.hasPlugin('plugin-AAA')) + t.assert.ok(!fastify.hasPlugin('plugin-AB')) + t.assert.ok(!fastify.hasPlugin('plugin-B')) + + done() + }, { name: 'plugin-AAA' })) + + done() + }, { name: 'plugin-A' })) + + fastify.register(fp((fastify, opts, done) => { + t.assert.ok(fastify.hasPlugin('plugin-AA')) + t.assert.ok(fastify.hasPlugin('plugin-A')) + t.assert.ok(fastify.hasPlugin('plugin-AAA')) + t.assert.ok(fastify.hasPlugin('plugin-AB')) + t.assert.ok(!fastify.hasPlugin('plugin-B')) + + done() + }, { name: 'plugin-AB' })) + + done() + }, { name: 'plugin-AA' })) + + fastify.register(fp((fastify, opts, done) => { + t.assert.ok(fastify.hasPlugin('plugin-AA')) + t.assert.ok(fastify.hasPlugin('plugin-A')) + t.assert.ok(fastify.hasPlugin('plugin-AAA')) + t.assert.ok(fastify.hasPlugin('plugin-AB')) + t.assert.ok(fastify.hasPlugin('plugin-B')) + + done() + }, { name: 'plugin-B' })) + + await fastify.ready() +}) + +test('hasPlugin returns true when using encapsulation', async t => { + t.plan(2) + + const fastify = Fastify() + + const pluginCallback = function (server, options, done) { + done() + } + const pluginName = 'awesome-plugin' + const plugin = fp(pluginCallback, { name: pluginName }) + + fastify.register(plugin) + + fastify.register(async (server) => { + t.assert.ok(server.hasPlugin(pluginName)) + }) + + fastify.register(async function foo (server) { + server.register(async function bar (server) { + t.assert.ok(server.hasPlugin(pluginName)) + }) + }) + + await fastify.ready() +}) + +test('registering anonymous plugin with mixed style should throw', async t => { + t.plan(2) + + const fastify = Fastify() + + const anonymousPlugin = async (app, opts, done) => { + done() + } + + fastify.register(anonymousPlugin) + + try { + await fastify.ready() + t.fail('should throw') + } catch (error) { + t.assert.ok(error instanceof FST_ERR_PLUGIN_INVALID_ASYNC_HANDLER) + t.assert.strictEqual(error.message, 'The anonymousPlugin plugin being registered mixes async and callback styles. Async plugin should not mix async and callback style.') + } +}) + +test('registering named plugin with mixed style should throw', async t => { + t.plan(2) + + const fastify = Fastify() + + const pluginName = 'error-plugin' + const errorPlugin = async (app, opts, done) => { + done() + } + const namedPlugin = fp(errorPlugin, { name: pluginName }) + + fastify.register(namedPlugin) + + try { + await fastify.ready() + t.fail('should throw') + } catch (error) { + t.assert.ok(error instanceof FST_ERR_PLUGIN_INVALID_ASYNC_HANDLER) + t.assert.strictEqual(error.message, 'The error-plugin plugin being registered mixes async and callback styles. Async plugin should not mix async and callback style.') + } +}) diff --git a/services/slides/node_modules/fastify/test/plugin.helper.js b/services/slides/node_modules/fastify/test/plugin.helper.js new file mode 100644 index 0000000000000000000000000000000000000000..69b598855b8f75d5e1e76d0fec78c9e9fbdba814 --- /dev/null +++ b/services/slides/node_modules/fastify/test/plugin.helper.js @@ -0,0 +1,8 @@ +'use strict' + +const fp = require('fastify-plugin') + +module.exports = fp(function (fastify, opts, done) { + fastify.decorate('test', () => {}) + done() +}) diff --git a/services/slides/node_modules/fastify/test/plugin.name.display.js b/services/slides/node_modules/fastify/test/plugin.name.display.js new file mode 100644 index 0000000000000000000000000000000000000000..0d42e0525010df172cf69a8514217a45e42977e7 --- /dev/null +++ b/services/slides/node_modules/fastify/test/plugin.name.display.js @@ -0,0 +1,10 @@ +'use strict' + +const assert = require('node:assert') + +module.exports = function (fastify, opts, done) { + assert.strictEqual(fastify.pluginName, 'test-plugin') + done() +} + +module.exports[Symbol.for('fastify.display-name')] = 'test-plugin' diff --git a/services/slides/node_modules/fastify/test/post-empty-body.test.js b/services/slides/node_modules/fastify/test/post-empty-body.test.js new file mode 100644 index 0000000000000000000000000000000000000000..8389e39da844afc495a95613cefde7f1e77c048a --- /dev/null +++ b/services/slides/node_modules/fastify/test/post-empty-body.test.js @@ -0,0 +1,38 @@ +'use strict' + +const { test } = require('node:test') +const Fastify = require('..') +const { request, setGlobalDispatcher, Agent } = require('undici') + +setGlobalDispatcher(new Agent({ + keepAliveTimeout: 10, + keepAliveMaxTimeout: 10 +})) + +test('post empty body', { timeout: 3_000 }, async t => { + const fastify = Fastify({ forceCloseConnections: true }) + const abortController = new AbortController() + const { signal } = abortController + t.after(() => { + fastify.close() + abortController.abort() + }) + + fastify.post('/bug', async () => { + // This function must be async and return nothing + }) + + await fastify.listen({ port: 0 }) + + const res = await request(`http://localhost:${fastify.server.address().port}/bug`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ foo: 'bar' }), + signal + }) + + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(await res.body.text(), '') +}) diff --git a/services/slides/node_modules/fastify/test/pretty-print.test.js b/services/slides/node_modules/fastify/test/pretty-print.test.js new file mode 100644 index 0000000000000000000000000000000000000000..e02a493d3abc6529a25de6c3264e43792695a5d2 --- /dev/null +++ b/services/slides/node_modules/fastify/test/pretty-print.test.js @@ -0,0 +1,366 @@ +'use strict' + +const { test } = require('node:test') +const Fastify = require('..') + +test('pretty print - static routes', (t, done) => { + t.plan(2) + + const fastify = Fastify({ exposeHeadRoutes: false }) + fastify.get('/test', () => {}) + fastify.get('/test/hello', () => {}) + fastify.get('/hello/world', () => {}) + + fastify.ready(() => { + const tree = fastify.printRoutes() + + const expected = `\ +└── / + ├── test (GET) + │ └── /hello (GET) + └── hello/world (GET) +` + + t.assert.strictEqual(typeof tree, 'string') + t.assert.strictEqual(tree, expected) + done() + }) +}) + +test('pretty print - internal tree - static routes', (t, done) => { + t.plan(4) + + const fastify = Fastify({ exposeHeadRoutes: false }) + fastify.get('/test', () => {}) + fastify.get('/test/hello', () => {}) + fastify.get('/hello/world', () => {}) + + fastify.put('/test', () => {}) + fastify.put('/test/foo', () => {}) + + fastify.ready(() => { + const getTree = fastify.printRoutes({ method: 'GET' }) + const expectedGetTree = `\ +└── / + ├── test (GET) + │ └── /hello (GET) + └── hello/world (GET) +` + + t.assert.strictEqual(typeof getTree, 'string') + t.assert.strictEqual(getTree, expectedGetTree) + + const putTree = fastify.printRoutes({ method: 'PUT' }) + const expectedPutTree = `\ +└── / + └── test (PUT) + └── /foo (PUT) +` + + t.assert.strictEqual(typeof putTree, 'string') + t.assert.strictEqual(putTree, expectedPutTree) + done() + }) +}) + +test('pretty print - parametric routes', (t, done) => { + t.plan(2) + + const fastify = Fastify({ exposeHeadRoutes: false }) + fastify.get('/test', () => {}) + fastify.get('/test/:hello', () => {}) + fastify.get('/hello/:world', () => {}) + + fastify.ready(() => { + const tree = fastify.printRoutes() + + const expected = `\ +└── / + ├── test (GET) + │ └── / + │ └── :hello (GET) + └── hello/ + └── :world (GET) +` + + t.assert.strictEqual(typeof tree, 'string') + t.assert.strictEqual(tree, expected) + done() + }) +}) + +test('pretty print - internal tree - parametric routes', (t, done) => { + t.plan(4) + + const fastify = Fastify({ exposeHeadRoutes: false }) + fastify.get('/test', () => {}) + fastify.get('/test/:hello', () => {}) + fastify.get('/hello/:world', () => {}) + + fastify.put('/test', () => {}) + fastify.put('/test/:hello', () => {}) + + fastify.ready(() => { + const getTree = fastify.printRoutes({ method: 'GET' }) + const expectedGetTree = `\ +└── / + ├── test (GET) + │ └── / + │ └── :hello (GET) + └── hello/ + └── :world (GET) +` + + t.assert.strictEqual(typeof getTree, 'string') + t.assert.strictEqual(getTree, expectedGetTree) + + const putTree = fastify.printRoutes({ method: 'PUT' }) + const expectedPutTree = `\ +└── / + └── test (PUT) + └── / + └── :hello (PUT) +` + + t.assert.strictEqual(typeof putTree, 'string') + t.assert.strictEqual(putTree, expectedPutTree) + done() + }) +}) + +test('pretty print - mixed parametric routes', (t, done) => { + t.plan(2) + + const fastify = Fastify({ exposeHeadRoutes: false }) + fastify.get('/test', () => {}) + fastify.get('/test/:hello', () => {}) + fastify.post('/test/:hello', () => {}) + fastify.get('/test/:hello/world', () => {}) + + fastify.ready(() => { + const tree = fastify.printRoutes() + + const expected = `\ +└── / + └── test (GET) + └── / + └── :hello (GET, POST) + └── /world (GET) +` + + t.assert.strictEqual(typeof tree, 'string') + t.assert.strictEqual(tree, expected) + done() + }) +}) + +test('pretty print - wildcard routes', (t, done) => { + t.plan(2) + + const fastify = Fastify({ exposeHeadRoutes: false }) + fastify.get('/test', () => {}) + fastify.get('/test/*', () => {}) + fastify.get('/hello/*', () => {}) + + fastify.ready(() => { + const tree = fastify.printRoutes() + + const expected = `\ +└── / + ├── test (GET) + │ └── / + │ └── * (GET) + └── hello/ + └── * (GET) +` + + t.assert.strictEqual(typeof tree, 'string') + t.assert.strictEqual(tree, expected) + done() + }) +}) + +test('pretty print - internal tree - wildcard routes', (t, done) => { + t.plan(4) + + const fastify = Fastify({ exposeHeadRoutes: false }) + fastify.get('/test', () => {}) + fastify.get('/test/*', () => {}) + fastify.get('/hello/*', () => {}) + + fastify.put('/*', () => {}) + fastify.put('/test/*', () => {}) + + fastify.ready(() => { + const getTree = fastify.printRoutes({ method: 'GET' }) + const expectedGetTree = `\ +└── / + ├── test (GET) + │ └── / + │ └── * (GET) + └── hello/ + └── * (GET) +` + + t.assert.strictEqual(typeof getTree, 'string') + t.assert.strictEqual(getTree, expectedGetTree) + + const putTree = fastify.printRoutes({ method: 'PUT' }) + const expectedPutTree = `\ +└── / + ├── test/ + │ └── * (PUT) + └── * (PUT) +` + + t.assert.strictEqual(typeof putTree, 'string') + t.assert.strictEqual(putTree, expectedPutTree) + done() + }) +}) + +test('pretty print - empty plugins', (t, done) => { + t.plan(2) + + const fastify = Fastify() + fastify.ready(() => { + const tree = fastify.printPlugins() + t.assert.strictEqual(typeof tree, 'string') + t.assert.match(tree, /root \d+ ms\n└── bound _after \d+ ms/m) + done() + }) +}) + +test('pretty print - nested plugins', (t, done) => { + t.plan(4) + + const fastify = Fastify() + fastify.register(async function foo (instance) { + instance.register(async function bar () {}) + instance.register(async function baz () {}) + }) + fastify.ready(() => { + const tree = fastify.printPlugins() + t.assert.strictEqual(typeof tree, 'string') + t.assert.match(tree, /foo/) + t.assert.match(tree, /bar/) + t.assert.match(tree, /baz/) + done() + }) +}) + +test('pretty print - commonPrefix', (t, done) => { + t.plan(4) + + const fastify = Fastify() + fastify.get('/hello', () => {}) + fastify.put('/hello', () => {}) + fastify.get('/helicopter', () => {}) + + fastify.ready(() => { + const radixTree = fastify.printRoutes() + const flatTree = fastify.printRoutes({ commonPrefix: false }) + + const radixExpected = `\ +└── / + └── hel + ├── lo (GET, HEAD, PUT) + └── icopter (GET, HEAD) +` + const flatExpected = `\ +├── /hello (GET, HEAD, PUT) +└── /helicopter (GET, HEAD) +` + t.assert.strictEqual(typeof radixTree, 'string') + t.assert.strictEqual(typeof flatTree, 'string') + t.assert.strictEqual(radixTree, radixExpected) + t.assert.strictEqual(flatTree, flatExpected) + done() + }) +}) + +test('pretty print - includeMeta, includeHooks', (t, done) => { + t.plan(6) + + const fastify = Fastify() + const onTimeout = () => {} + fastify.get('/hello', () => {}) + fastify.put('/hello', () => {}) + fastify.get('/helicopter', () => {}) + + fastify.addHook('onRequest', () => {}) + fastify.addHook('onTimeout', onTimeout) + + fastify.ready(() => { + const radixTree = fastify.printRoutes({ includeHooks: true, includeMeta: ['errorHandler'] }) + const flatTree = fastify.printRoutes({ commonPrefix: false, includeHooks: true, includeMeta: ['errorHandler'] }) + const hooksOnly = fastify.printRoutes({ commonPrefix: false, includeHooks: true }) + + const radixExpected = `\ +└── / + └── hel + ├── lo (GET, PUT) + │ • (onTimeout) ["onTimeout()"] + │ • (onRequest) ["anonymous()"] + │ • (errorHandler) "defaultErrorHandler()" + │ lo (HEAD) + │ • (onTimeout) ["onTimeout()"] + │ • (onRequest) ["anonymous()"] + │ • (onSend) ["headRouteOnSendHandler()"] + │ • (errorHandler) "defaultErrorHandler()" + └── icopter (GET) + • (onTimeout) ["onTimeout()"] + • (onRequest) ["anonymous()"] + • (errorHandler) "defaultErrorHandler()" + icopter (HEAD) + • (onTimeout) ["onTimeout()"] + • (onRequest) ["anonymous()"] + • (onSend) ["headRouteOnSendHandler()"] + • (errorHandler) "defaultErrorHandler()" +` + const flatExpected = `\ +├── /hello (GET, PUT) +│ • (onTimeout) ["onTimeout()"] +│ • (onRequest) ["anonymous()"] +│ • (errorHandler) "defaultErrorHandler()" +│ /hello (HEAD) +│ • (onTimeout) ["onTimeout()"] +│ • (onRequest) ["anonymous()"] +│ • (onSend) ["headRouteOnSendHandler()"] +│ • (errorHandler) "defaultErrorHandler()" +└── /helicopter (GET) + • (onTimeout) ["onTimeout()"] + • (onRequest) ["anonymous()"] + • (errorHandler) "defaultErrorHandler()" + /helicopter (HEAD) + • (onTimeout) ["onTimeout()"] + • (onRequest) ["anonymous()"] + • (onSend) ["headRouteOnSendHandler()"] + • (errorHandler) "defaultErrorHandler()" +` + + const hooksOnlyExpected = `\ +├── /hello (GET, PUT) +│ • (onTimeout) ["onTimeout()"] +│ • (onRequest) ["anonymous()"] +│ /hello (HEAD) +│ • (onTimeout) ["onTimeout()"] +│ • (onRequest) ["anonymous()"] +│ • (onSend) ["headRouteOnSendHandler()"] +└── /helicopter (GET) + • (onTimeout) ["onTimeout()"] + • (onRequest) ["anonymous()"] + /helicopter (HEAD) + • (onTimeout) ["onTimeout()"] + • (onRequest) ["anonymous()"] + • (onSend) ["headRouteOnSendHandler()"] +` + t.assert.strictEqual(typeof radixTree, 'string') + t.assert.strictEqual(typeof flatTree, 'string') + t.assert.strictEqual(typeof hooksOnlyExpected, 'string') + t.assert.strictEqual(radixTree, radixExpected) + t.assert.strictEqual(flatTree, flatExpected) + t.assert.strictEqual(hooksOnly, hooksOnlyExpected) + done() + }) +}) diff --git a/services/slides/node_modules/fastify/test/promises.test.js b/services/slides/node_modules/fastify/test/promises.test.js new file mode 100644 index 0000000000000000000000000000000000000000..68b828844cc670cbee7ee72a50d56d3891a741ca --- /dev/null +++ b/services/slides/node_modules/fastify/test/promises.test.js @@ -0,0 +1,125 @@ +'use strict' + +const { test } = require('node:test') +const assert = require('node:assert') +const fastify = require('..')() + +test.after(() => fastify.close()) + +const opts = { + schema: { + response: { + '2xx': { + type: 'object', + properties: { + hello: { + type: 'string' + } + } + } + } + } +} + +fastify.get('/return', opts, function (req, reply) { + const promise = new Promise((resolve, reject) => { + resolve({ hello: 'world' }) + }) + return promise +}) + +fastify.get('/return-error', opts, function (req, reply) { + const promise = new Promise((resolve, reject) => { + reject(new Error('some error')) + }) + return promise +}) + +fastify.get('/double', function (req, reply) { + setTimeout(function () { + // this should not throw + reply.send({ hello: 'world' }) + }, 20) + return Promise.resolve({ hello: '42' }) +}) + +fastify.get('/thenable', opts, function (req, reply) { + setImmediate(function () { + reply.send({ hello: 'world' }) + }) + return reply +}) + +fastify.get('/thenable-error', opts, function (req, reply) { + setImmediate(function () { + reply.send(new Error('kaboom')) + }) + return reply +}) + +fastify.get('/return-reply', opts, function (req, reply) { + return reply.send({ hello: 'world' }) +}) + +fastify.listen({ port: 0 }, (err, fastifyServer) => { + assert.ifError(err) + + test('shorthand - fetch return promise es6 get', async t => { + t.plan(4) + + const result = await fetch(`${fastifyServer}/return`) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + }) + + test('shorthand - fetch promise es6 get return error', async t => { + t.plan(2) + + const result = await fetch(`${fastifyServer}/return-error`) + t.assert.ok(!result.ok) + t.assert.strictEqual(result.status, 500) + }) + + test('fetch promise double send', async t => { + t.plan(3) + + const result = await fetch(`${fastifyServer}/double`) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + const body = await result.text() + t.assert.deepStrictEqual(JSON.parse(body), { hello: '42' }) + }) + + test('thenable', async t => { + t.plan(4) + + const result = await fetch(`${fastifyServer}/thenable`) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + }) + + test('thenable (error)', async t => { + t.plan(2) + + const result = await fetch(`${fastifyServer}/thenable-error`) + t.assert.ok(!result.ok) + t.assert.strictEqual(result.status, 500) + }) + + test('return-reply', async t => { + t.plan(4) + + const result = await fetch(`${fastifyServer}/return-reply`) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + const body = await result.text() + t.assert.strictEqual(result.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + }) +}) diff --git a/services/slides/node_modules/fastify/test/proto-poisoning.test.js b/services/slides/node_modules/fastify/test/proto-poisoning.test.js new file mode 100644 index 0000000000000000000000000000000000000000..9b557c00aeb83b3db9c7374dfd48c706a077bf92 --- /dev/null +++ b/services/slides/node_modules/fastify/test/proto-poisoning.test.js @@ -0,0 +1,145 @@ +'use strict' + +const Fastify = require('..') +const { test } = require('node:test') + +test('proto-poisoning error', async (t) => { + t.plan(2) + + const fastify = Fastify() + + fastify.post('/', (request, reply) => { + t.assert.fail('handler should not be called') + }) + + t.after(() => fastify.close()) + + const fastifyServer = await fastify.listen({ port: 0 }) + + const result = await fetch(fastifyServer, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: '{ "__proto__": { "a": 42 } }' + }) + + t.assert.ok(!result.ok) + t.assert.strictEqual(result.status, 400) +}) + +test('proto-poisoning remove', async (t) => { + t.plan(3) + + const fastify = Fastify({ onProtoPoisoning: 'remove' }) + + t.after(() => fastify.close()) + + fastify.post('/', (request, reply) => { + t.assert.strictEqual(undefined, Object.assign({}, request.body).a) + reply.send({ ok: true }) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + + const result = await fetch(fastifyServer, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: '{ "__proto__": { "a": 42 }, "b": 42 }' + }) + + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) +}) + +test('proto-poisoning ignore', async (t) => { + t.plan(3) + + const fastify = Fastify({ onProtoPoisoning: 'ignore' }) + + fastify.post('/', (request, reply) => { + t.assert.strictEqual(42, Object.assign({}, request.body).a) + reply.send({ ok: true }) + }) + + t.after(() => fastify.close()) + + const fastifyServer = await fastify.listen({ port: 0 }) + + const result = await fetch(fastifyServer, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: '{ "__proto__": { "a": 42 }, "b": 42 }' + }) + + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) +}) + +test('constructor-poisoning error (default in v3)', async (t) => { + t.plan(2) + + const fastify = Fastify() + + fastify.post('/', (request, reply) => { + reply.send('ok') + }) + + t.after(() => fastify.close()) + + const fastifyServer = await fastify.listen({ port: 0 }) + + const result = await fetch(fastifyServer, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: '{ "constructor": { "prototype": { "foo": "bar" } } }' + }) + + t.assert.ok(!result.ok) + t.assert.strictEqual(result.status, 400) +}) + +test('constructor-poisoning error', async (t) => { + t.plan(2) + + const fastify = Fastify({ onConstructorPoisoning: 'error' }) + + t.after(() => fastify.close()) + + fastify.post('/', (request, reply) => { + t.assert.fail('handler should not be called') + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + + const result = await fetch(fastifyServer, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: '{ "constructor": { "prototype": { "foo": "bar" } } }' + }) + + t.assert.ok(!result.ok) + t.assert.strictEqual(result.status, 400) +}) + +test('constructor-poisoning remove', async (t) => { + t.plan(3) + + const fastify = Fastify({ onConstructorPoisoning: 'remove' }) + + t.after(() => fastify.close()) + + fastify.post('/', (request, reply) => { + t.assert.strictEqual(undefined, Object.assign({}, request.body).foo) + reply.send({ ok: true }) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + + const result = await fetch(fastifyServer, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: '{ "constructor": { "prototype": { "foo": "bar" } } }' + }) + + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) +}) diff --git a/services/slides/node_modules/fastify/test/put.error-handler.test.js b/services/slides/node_modules/fastify/test/put.error-handler.test.js new file mode 100644 index 0000000000000000000000000000000000000000..8217a88bf74476dce4bef8d293abac92832648cd --- /dev/null +++ b/services/slides/node_modules/fastify/test/put.error-handler.test.js @@ -0,0 +1,5 @@ +'use strict' + +const t = require('node:test') +require('./helper').payloadMethod('put', t, true) +require('./input-validation').payloadMethod('put', t) diff --git a/services/slides/node_modules/fastify/test/put.test.js b/services/slides/node_modules/fastify/test/put.test.js new file mode 100644 index 0000000000000000000000000000000000000000..808d275987ffdc3f728eff3a27c6bbad060453c4 --- /dev/null +++ b/services/slides/node_modules/fastify/test/put.test.js @@ -0,0 +1,5 @@ +'use strict' + +const t = require('node:test') +require('./helper').payloadMethod('put', t) +require('./input-validation').payloadMethod('put', t) diff --git a/services/slides/node_modules/fastify/test/register.test.js b/services/slides/node_modules/fastify/test/register.test.js new file mode 100644 index 0000000000000000000000000000000000000000..e00d01b02773d4a949372998df2ac22e6f318b37 --- /dev/null +++ b/services/slides/node_modules/fastify/test/register.test.js @@ -0,0 +1,184 @@ +'use strict' + +const { test } = require('node:test') +const Fastify = require('..') + +test('register', async (t) => { + t.plan(16) + + const fastify = Fastify() + + fastify.register(function (instance, opts, done) { + t.assert.notStrictEqual(instance, fastify) + t.assert.ok(Object.prototype.isPrototypeOf.call(fastify, instance)) + + t.assert.strictEqual(typeof opts, 'object') + t.assert.strictEqual(typeof done, 'function') + + instance.get('/first', function (req, reply) { + reply.send({ hello: 'world' }) + }) + done() + }) + + fastify.register(function (instance, opts, done) { + t.assert.notStrictEqual(instance, fastify) + t.assert.ok(Object.prototype.isPrototypeOf.call(fastify, instance)) + + t.assert.strictEqual(typeof opts, 'object') + t.assert.strictEqual(typeof done, 'function') + + instance.get('/second', function (req, reply) { + reply.send({ hello: 'world' }) + }) + done() + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + await makeRequest('first') + await makeRequest('second') + + async function makeRequest (path) { + const response = await fetch(fastifyServer + '/' + path) + t.assert.ok(response.ok) + t.assert.strictEqual(response.status, 200) + const body = await response.text() + t.assert.strictEqual(response.headers.get('content-length'), '' + body.length) + t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' }) + } +}) + +test('internal route declaration should pass the error generated by the register to the done handler / 1', (t, done) => { + t.plan(1) + const fastify = Fastify() + + fastify.register((instance, opts, done) => { + done(new Error('kaboom')) + }) + + fastify.get('/', (req, reply) => { + reply.send({ hello: 'world' }) + }) + + fastify.listen({ port: 0 }, err => { + t.after(() => fastify.close()) + t.assert.strictEqual(err.message, 'kaboom') + done() + }) +}) + +test('internal route declaration should pass the error generated by the register to the done handler / 2', (t, done) => { + t.plan(2) + const fastify = Fastify() + + fastify.register((instance, opts, done) => { + done(new Error('kaboom')) + }) + + fastify.get('/', (req, reply) => { + reply.send({ hello: 'world' }) + }) + + fastify.after(err => { + t.assert.strictEqual(err.message, 'kaboom') + }) + + fastify.listen({ port: 0 }, err => { + t.after(() => fastify.close()) + t.assert.ifError(err) + done() + }) +}) + +test('awaitable register and after', async t => { + const fastify = Fastify() + let first = false + let second = false + let third = false + + await fastify.register(async (instance, opts) => { + first = true + }) + + t.assert.strictEqual(first, true) + + fastify.register(async (instance, opts) => { + second = true + }) + + await fastify.after() + t.assert.strictEqual(second, true) + + fastify.register(async (instance, opts) => { + third = true + }) + + await fastify.ready() + t.assert.strictEqual(third, true) +}) + +function thenableRejects (t, promise, error) { + return t.assert.rejects(async () => { await promise }, error) +} + +test('awaitable register error handling', async t => { + const fastify = Fastify() + + const e = new Error('kaboom') + + await thenableRejects(t, fastify.register(async (instance, opts) => { + throw e + }), e) + + fastify.register(async (instance, opts) => { + t.assert.fail('should not be executed') + }) + + await t.assert.rejects(fastify.after(), e) + + fastify.register(async (instance, opts) => { + t.assert.fail('should not be executed') + }) + + await thenableRejects(t, fastify.ready(), e) +}) + +test('awaitable after error handling', async t => { + const fastify = Fastify() + + const e = new Error('kaboom') + + fastify.register(async (instance, opts) => { + throw e + }) + + fastify.register(async (instance, opts) => { + t.assert.fail('should not be executed') + }) + + await t.assert.rejects(fastify.after(), e) + + fastify.register(async (instance, opts) => { + t.assert.fail('should not be executed') + }) + + await t.assert.rejects(fastify.ready()) +}) + +test('chainable register', async t => { + t.plan(3) + + const fastify = Fastify() + + fastify.register(async () => { + t.assert.ok('first loaded') + }).register(async () => { + t.assert.ok('second loaded') + }).register(async () => { + t.assert.ok('third loaded') + }) + + await fastify.ready() +}) diff --git a/services/slides/node_modules/fastify/test/reply-code.test.js b/services/slides/node_modules/fastify/test/reply-code.test.js new file mode 100644 index 0000000000000000000000000000000000000000..700f6f41f0c027cf8efd8ccafff7576f0ed20444 --- /dev/null +++ b/services/slides/node_modules/fastify/test/reply-code.test.js @@ -0,0 +1,148 @@ +'use strict' + +const { test } = require('node:test') +const { Readable } = require('node:stream') +const Fastify = require('..') + +test('code should handle null/undefined/float', (t, done) => { + t.plan(8) + + const fastify = Fastify() + + fastify.get('/null', function (request, reply) { + reply.status(null).send() + }) + + fastify.get('/undefined', function (request, reply) { + reply.status(undefined).send() + }) + + fastify.get('/404.5', function (request, reply) { + reply.status(404.5).send() + }) + + fastify.inject({ + method: 'GET', + url: '/null' + }, (error, res) => { + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 500) + t.assert.deepStrictEqual(res.json(), { + statusCode: 500, + code: 'FST_ERR_BAD_STATUS_CODE', + error: 'Internal Server Error', + message: 'Called reply with an invalid status code: null' + }) + }) + + fastify.inject({ + method: 'GET', + url: '/undefined' + }, (error, res) => { + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 500) + t.assert.deepStrictEqual(res.json(), { + statusCode: 500, + code: 'FST_ERR_BAD_STATUS_CODE', + error: 'Internal Server Error', + message: 'Called reply with an invalid status code: undefined' + }) + }) + + fastify.inject({ + method: 'GET', + url: '/404.5' + }, (error, res) => { + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 404) + done() + }) +}) + +test('code should handle 204', (t, done) => { + t.plan(13) + + const fastify = Fastify() + + fastify.get('/204', function (request, reply) { + reply.status(204) + return null + }) + + fastify.get('/undefined/204', function (request, reply) { + reply.status(204).send({ message: 'hello' }) + }) + + fastify.get('/stream/204', function (request, reply) { + const stream = new Readable({ + read () { + this.push(null) + } + }) + stream.on('end', () => { + t.assert.ok('stream ended') + }) + reply.status(204).send(stream) + }) + + fastify.inject({ + method: 'GET', + url: '/204' + }, (error, res) => { + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 204) + t.assert.strictEqual(res.payload, '') + t.assert.strictEqual(res.headers['content-length'], undefined) + }) + + fastify.inject({ + method: 'GET', + url: '/undefined/204' + }, (error, res) => { + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 204) + t.assert.strictEqual(res.payload, '') + t.assert.strictEqual(res.headers['content-length'], undefined) + }) + + fastify.inject({ + method: 'GET', + url: '/stream/204' + }, (error, res) => { + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 204) + t.assert.strictEqual(res.payload, '') + t.assert.strictEqual(res.headers['content-length'], undefined) + done() + }) +}) + +test('code should handle onSend hook on 204', (t, done) => { + t.plan(5) + + const fastify = Fastify() + fastify.addHook('onSend', async function (request, reply, payload) { + return { + ...payload, + world: 'hello' + } + }) + + fastify.get('/204', function (request, reply) { + reply.status(204).send({ + hello: 'world' + }) + }) + + fastify.inject({ + method: 'GET', + url: '/204' + }, (error, res) => { + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 204) + t.assert.strictEqual(res.payload, '') + t.assert.strictEqual(res.headers['content-length'], undefined) + t.assert.strictEqual(res.headers['content-type'], undefined) + done() + }) +}) diff --git a/services/slides/node_modules/fastify/test/reply-early-hints.test.js b/services/slides/node_modules/fastify/test/reply-early-hints.test.js new file mode 100644 index 0000000000000000000000000000000000000000..a6042e5a36ba4b3cf795d67933cff044b9b7e6da --- /dev/null +++ b/services/slides/node_modules/fastify/test/reply-early-hints.test.js @@ -0,0 +1,100 @@ +'use strict' + +const Fastify = require('..') +const { test } = require('node:test') +const http = require('node:http') +const http2 = require('node:http2') + +const testResBody = 'Hello, world!' + +test('sends early hints', (t, done) => { + t.plan(6) + + const fastify = Fastify({ + logger: false + }) + + fastify.get('/', async (request, reply) => { + reply.writeEarlyHints({ + link: '; rel=preload; as=style' + }, () => { + t.assert.ok('callback called') + }) + + return testResBody + }) + + fastify.listen({ port: 0 }, (err, address) => { + t.assert.ifError(err) + + const req = http.get(address) + + req.on('information', (res) => { + t.assert.strictEqual(res.statusCode, 103) + t.assert.strictEqual(res.headers.link, '; rel=preload; as=style') + }) + + req.on('response', (res) => { + t.assert.strictEqual(res.statusCode, 200) + + let data = '' + res.on('data', (chunk) => { + data += chunk + }) + + res.on('end', () => { + t.assert.strictEqual(data, testResBody) + fastify.close() + done() + }) + }) + }) +}) + +test('sends early hints (http2)', (t, done) => { + t.plan(6) + + const fastify = Fastify({ + http2: true, + logger: false + }) + + fastify.get('/', async (request, reply) => { + reply.writeEarlyHints({ + link: '; rel=preload; as=style' + }) + + return testResBody + }) + + fastify.listen({ port: 0 }, (err, address) => { + t.assert.ifError(err) + + const client = http2.connect(address) + const req = client.request() + + req.on('headers', (headers) => { + t.assert.notStrictEqual(headers, undefined) + t.assert.strictEqual(headers[':status'], 103) + t.assert.strictEqual(headers.link, '; rel=preload; as=style') + }) + + req.on('response', (headers) => { + t.assert.strictEqual(headers[':status'], 200) + }) + + let data = '' + req.on('data', (chunk) => { + data += chunk + }) + + req.on('end', () => { + t.assert.strictEqual(data, testResBody) + client.close() + fastify.close() + done() + }) + + req.end() + }) +}) diff --git a/services/slides/node_modules/fastify/test/reply-error.test.js b/services/slides/node_modules/fastify/test/reply-error.test.js new file mode 100644 index 0000000000000000000000000000000000000000..8f2a35f36de5f4ef7efc710f5c88fa09aad0dffb --- /dev/null +++ b/services/slides/node_modules/fastify/test/reply-error.test.js @@ -0,0 +1,815 @@ +'use strict' + +const { test, describe } = require('node:test') +const net = require('node:net') +const Fastify = require('..') +const statusCodes = require('node:http').STATUS_CODES +const split = require('split2') +const fs = require('node:fs') +const path = require('node:path') + +const codes = Object.keys(statusCodes) +codes.forEach(code => { + if (Number(code) >= 400) helper(code) +}) + +function helper (code) { + test('Reply error handling - code: ' + code, (t, testDone) => { + t.plan(4) + const fastify = Fastify() + t.after(() => fastify.close()) + const err = new Error('winter is coming') + + fastify.get('/', (req, reply) => { + reply + .code(Number(code)) + .send(err) + }) + + fastify.inject({ + method: 'GET', + url: '/' + }, (error, res) => { + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, Number(code)) + t.assert.strictEqual(res.headers['content-type'], 'application/json; charset=utf-8') + t.assert.deepStrictEqual( + { + error: statusCodes[code], + message: err.message, + statusCode: Number(code) + }, + JSON.parse(res.payload) + ) + testDone() + }) + }) +} + +test('preHandler hook error handling with external code', (t, testDone) => { + t.plan(3) + const fastify = Fastify() + t.after(() => fastify.close()) + const err = new Error('winter is coming') + + fastify.addHook('preHandler', (req, reply, done) => { + reply.code(400) + done(err) + }) + + fastify.get('/', () => {}) + + fastify.inject({ + method: 'GET', + url: '/' + }, (error, res) => { + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 400) + t.assert.deepStrictEqual( + { + error: statusCodes['400'], + message: err.message, + statusCode: 400 + }, + JSON.parse(res.payload) + ) + testDone() + }) +}) + +test('onRequest hook error handling with external done', (t, testDone) => { + t.plan(3) + const fastify = Fastify() + t.after(() => fastify.close()) + const err = new Error('winter is coming') + + fastify.addHook('onRequest', (req, reply, done) => { + reply.code(400) + done(err) + }) + + fastify.get('/', () => {}) + + fastify.inject({ + method: 'GET', + url: '/' + }, (error, res) => { + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 400) + t.assert.deepStrictEqual( + { + error: statusCodes['400'], + message: err.message, + statusCode: 400 + }, + JSON.parse(res.payload) + ) + testDone() + }) +}) + +test('Should reply 400 on client error', (t, testDone) => { + t.plan(2) + + const fastify = Fastify() + t.after(() => fastify.close()) + fastify.listen({ port: 0, host: '127.0.0.1' }, err => { + t.assert.ifError(err) + + const client = net.connect(fastify.server.address().port, '127.0.0.1') + client.end('oooops!') + + let chunks = '' + client.on('data', chunk => { + chunks += chunk + }) + + client.once('end', () => { + const body = JSON.stringify({ + error: 'Bad Request', + message: 'Client Error', + statusCode: 400 + }) + t.assert.strictEqual(`HTTP/1.1 400 Bad Request\r\nContent-Length: ${body.length}\r\nContent-Type: application/json\r\n\r\n${body}`, chunks) + testDone() + }) + }) +}) + +test('Should set the response from client error handler', (t, testDone) => { + t.plan(5) + + const responseBody = JSON.stringify({ + error: 'Ended Request', + message: 'Serious Client Error', + statusCode: 400 + }) + const response = `HTTP/1.1 400 Bad Request\r\nContent-Length: ${responseBody.length}\r\nContent-Type: application/json; charset=utf-8\r\n\r\n${responseBody}` + + function clientErrorHandler (err, socket) { + t.assert.ok(err instanceof Error) + + this.log.warn({ err }, 'Handled client error') + socket.end(response) + } + + const logStream = split(JSON.parse) + const fastify = Fastify({ + clientErrorHandler, + logger: { + stream: logStream, + level: 'warn' + } + }) + + fastify.listen({ port: 0, host: '127.0.0.1' }, err => { + t.assert.ifError(err) + t.after(() => fastify.close()) + + const client = net.connect(fastify.server.address().port, '127.0.0.1') + client.end('oooops!') + + let chunks = '' + client.on('data', chunk => { + chunks += chunk + }) + + client.once('end', () => { + t.assert.strictEqual(response, chunks) + + testDone() + }) + }) + + logStream.once('data', line => { + t.assert.strictEqual('Handled client error', line.msg) + t.assert.strictEqual(40, line.level, 'Log level is not warn') + }) +}) + +test('Error instance sets HTTP status code', (t, testDone) => { + t.plan(3) + const fastify = Fastify() + t.after(() => fastify.close()) + const err = new Error('winter is coming') + err.statusCode = 418 + + fastify.get('/', () => { + return Promise.reject(err) + }) + + fastify.inject({ + method: 'GET', + url: '/' + }, (error, res) => { + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 418) + t.assert.deepStrictEqual( + { + error: statusCodes['418'], + message: err.message, + statusCode: 418 + }, + JSON.parse(res.payload) + ) + testDone() + }) +}) + +test('Error status code below 400 defaults to 500', (t, testDone) => { + t.plan(3) + const fastify = Fastify() + t.after(() => fastify.close()) + const err = new Error('winter is coming') + err.statusCode = 399 + + fastify.get('/', () => { + return Promise.reject(err) + }) + + fastify.inject({ + method: 'GET', + url: '/' + }, (error, res) => { + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 500) + t.assert.deepStrictEqual( + { + error: statusCodes['500'], + message: err.message, + statusCode: 500 + }, + JSON.parse(res.payload) + ) + testDone() + }) +}) + +test('Error.status property support', (t, testDone) => { + t.plan(3) + const fastify = Fastify() + t.after(() => fastify.close()) + const err = new Error('winter is coming') + err.status = 418 + + fastify.get('/', () => { + return Promise.reject(err) + }) + + fastify.inject({ + method: 'GET', + url: '/' + }, (error, res) => { + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 418) + t.assert.deepStrictEqual( + { + error: statusCodes['418'], + message: err.message, + statusCode: 418 + }, + JSON.parse(res.payload) + ) + testDone() + }) +}) + +describe('Support rejection with values that are not Error instances', () => { + const objs = [ + 0, + '', + [], + {}, + null, + undefined, + 123, + 'abc', + new RegExp(), + new Date(), + new Uint8Array() + ] + for (const nonErr of objs) { + test('Type: ' + typeof nonErr, (t, testDone) => { + t.plan(4) + const fastify = Fastify() + t.after(() => fastify.close()) + + fastify.get('/', () => { + return Promise.reject(nonErr) + }) + + fastify.setErrorHandler((err, request, reply) => { + if (typeof err === 'object') { + t.assert.deepStrictEqual(err, nonErr) + } else { + t.assert.strictEqual(err, nonErr) + } + reply.code(500).send('error') + }) + + fastify.inject({ + method: 'GET', + url: '/' + }, (error, res) => { + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 500) + t.assert.strictEqual(res.payload, 'error') + testDone() + }) + }) + } +}) + +test('invalid schema - ajv', (t, testDone) => { + t.plan(4) + + const fastify = Fastify() + t.after(() => fastify.close()) + fastify.get('/', { + schema: { + querystring: { + type: 'object', + properties: { + id: { type: 'number' } + } + } + } + }, (req, reply) => { + t.assert.fail('we should not be here') + }) + + fastify.setErrorHandler((err, request, reply) => { + t.assert.ok(Array.isArray(err.validation)) + reply.code(400).send('error') + }) + + fastify.inject({ + url: '/?id=abc', + method: 'GET' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 400) + t.assert.strictEqual(res.payload, 'error') + testDone() + }) +}) + +test('should set the status code and the headers from the error object (from route handler) (no custom error handler)', (t, testDone) => { + t.plan(4) + const fastify = Fastify() + t.after(() => fastify.close()) + + fastify.get('/', (req, reply) => { + const error = new Error('kaboom') + error.headers = { hello: 'world' } + error.statusCode = 400 + reply.send(error) + }) + + fastify.inject({ + url: '/', + method: 'GET' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 400) + t.assert.strictEqual(res.headers.hello, 'world') + t.assert.deepStrictEqual(JSON.parse(res.payload), { + error: 'Bad Request', + message: 'kaboom', + statusCode: 400 + }) + + testDone() + }) +}) + +test('should set the status code and the headers from the error object (from custom error handler)', (t, testDone) => { + t.plan(6) + const fastify = Fastify() + t.after(() => fastify.close()) + + fastify.get('/', (req, reply) => { + const error = new Error('ouch') + error.statusCode = 401 + reply.send(error) + }) + + fastify.setErrorHandler((err, request, reply) => { + t.assert.strictEqual(err.message, 'ouch') + t.assert.strictEqual(reply.raw.statusCode, 200) + const error = new Error('kaboom') + error.headers = { hello: 'world' } + error.statusCode = 400 + reply.send(error) + }) + + fastify.inject({ + url: '/', + method: 'GET' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 400) + t.assert.strictEqual(res.headers.hello, 'world') + t.assert.deepStrictEqual(JSON.parse(res.payload), { + error: 'Bad Request', + message: 'kaboom', + statusCode: 400 + }) + testDone() + }) +}) + +// Issue 595 https://github.com/fastify/fastify/issues/595 +test('\'*\' should throw an error due to serializer can not handle the payload type', (t, testDone) => { + t.plan(3) + const fastify = Fastify() + t.after(() => fastify.close()) + + fastify.get('/', (req, reply) => { + reply.type('text/html') + try { + reply.send({}) + } catch (err) { + t.assert.ok(err instanceof TypeError) + t.assert.strictEqual(err.code, 'FST_ERR_REP_INVALID_PAYLOAD_TYPE') + t.assert.strictEqual(err.message, "Attempted to send payload of invalid type 'object'. Expected a string or Buffer.") + testDone() + } + }) + + fastify.inject({ + url: '/', + method: 'GET' + }, (e, res) => { + t.assert.fail('should not be called') + }) +}) + +test('should throw an error if the custom serializer does not serialize the payload to a valid type', (t, testDone) => { + t.plan(3) + const fastify = Fastify() + t.after(() => fastify.close()) + + fastify.get('/', (req, reply) => { + try { + reply + .type('text/html') + .serializer(payload => payload) + .send({}) + } catch (err) { + t.assert.ok(err instanceof TypeError) + t.assert.strictEqual(err.code, 'FST_ERR_REP_INVALID_PAYLOAD_TYPE') + t.assert.strictEqual(err.message, "Attempted to send payload of invalid type 'object'. Expected a string or Buffer.") + testDone() + } + }) + + fastify.inject({ + url: '/', + method: 'GET' + }, (e, res) => { + t.assert.fail('should not be called') + }) +}) + +test('should not set headers or status code for custom error handler', (t, testDone) => { + t.plan(7) + + const fastify = Fastify() + t.after(() => fastify.close()) + fastify.get('/', function (req, reply) { + const err = new Error('kaboom') + err.headers = { + 'fake-random-header': 'abc' + } + reply.send(err) + }) + + fastify.setErrorHandler(async (err, req, res) => { + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual('fake-random-header' in res.headers, false) + return res.code(500).send(err.message) + }) + + fastify.inject({ + method: 'GET', + url: '/' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 500) + t.assert.strictEqual('fake-random-header' in res.headers, false) + t.assert.strictEqual(res.headers['content-length'], ('kaboom'.length).toString()) + t.assert.deepStrictEqual(res.payload, 'kaboom') + testDone() + }) +}) + +test('error thrown by custom error handler routes to default error handler', (t, testDone) => { + t.plan(6) + + const fastify = Fastify() + t.after(() => fastify.close()) + + const error = new Error('kaboom') + error.headers = { + 'fake-random-header': 'abc' + } + + fastify.get('/', function (req, reply) { + reply.send(error) + }) + + const newError = new Error('kabong') + + fastify.setErrorHandler(async (err, req, res) => { + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual('fake-random-header' in res.headers, false) + t.assert.deepStrictEqual(err.headers, error.headers) + + return res.send(newError) + }) + + fastify.inject({ + method: 'GET', + url: '/' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 500) + t.assert.deepStrictEqual(JSON.parse(res.payload), { + error: statusCodes['500'], + message: newError.message, + statusCode: 500 + }) + testDone() + }) +}) + +// Refs: https://github.com/fastify/fastify/pull/4484#issuecomment-1367301750 +test('allow re-thrown error to default error handler when route handler is async and error handler is sync', (t, testDone) => { + t.plan(4) + const fastify = Fastify() + t.after(() => fastify.close()) + + fastify.setErrorHandler(function (error) { + t.assert.strictEqual(error.message, 'kaboom') + throw Error('kabong') + }) + + fastify.get('/', async function () { + throw Error('kaboom') + }) + + fastify.inject({ + url: '/', + method: 'GET' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 500) + t.assert.deepStrictEqual(JSON.parse(res.payload), { + error: statusCodes['500'], + message: 'kabong', + statusCode: 500 + }) + testDone() + }) +}) + +// Issue 2078 https://github.com/fastify/fastify/issues/2078 +// Supported error code list: http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml +const invalidErrorCodes = [ + undefined, + null, + 'error_code', + + // out of the 100-599 range: + 0, + 1, + 99, + 600, + 700 +] +invalidErrorCodes.forEach((invalidCode) => { + test(`should throw error if error code is ${invalidCode}`, (t, testDone) => { + t.plan(2) + const fastify = Fastify() + t.after(() => fastify.close()) + fastify.get('/', (request, reply) => { + try { + return reply.code(invalidCode).send('You should not read this') + } catch (err) { + t.assert.strictEqual(err.code, 'FST_ERR_BAD_STATUS_CODE') + t.assert.strictEqual(err.message, 'Called reply with an invalid status code: ' + invalidCode) + testDone() + } + }) + + fastify.inject({ + url: '/', + method: 'GET' + }, (e, res) => { + t.assert.fail('should not be called') + }) + }) +}) + +test('error handler is triggered when a string is thrown from sync handler', (t, testDone) => { + t.plan(3) + + const fastify = Fastify() + t.after(() => fastify.close()) + + const throwable = 'test' + const payload = 'error' + + fastify.get('/', function (req, reply) { + throw throwable + }) + + fastify.setErrorHandler((err, req, res) => { + t.assert.strictEqual(err, throwable) + + res.send(payload) + }) + + fastify.inject({ + method: 'GET', + url: '/' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.payload, payload) + testDone() + }) +}) + +test('status code should be set to 500 and return an error json payload if route handler throws any non Error object expression', async t => { + t.plan(2) + const fastify = Fastify() + t.after(() => fastify.close()) + + fastify.get('/', () => { + /* eslint-disable-next-line */ + throw { foo: 'bar' } + }) + + // ---- + const reply = await fastify.inject({ method: 'GET', url: '/' }) + t.assert.strictEqual(reply.statusCode, 500) + t.assert.strictEqual(JSON.parse(reply.body).foo, 'bar') +}) + +test('should preserve the status code set by the user if an expression is thrown in a sync route', async t => { + t.plan(2) + const fastify = Fastify() + t.after(() => fastify.close()) + + fastify.get('/', (_, rep) => { + rep.status(501) + + /* eslint-disable-next-line */ + throw { foo: 'bar' } + }) + + // ---- + const reply = await fastify.inject({ method: 'GET', url: '/' }) + t.assert.strictEqual(reply.statusCode, 501) + t.assert.strictEqual(JSON.parse(reply.body).foo, 'bar') +}) + +test('should trigger error handlers if a sync route throws any non-error object', async t => { + t.plan(2) + + const fastify = Fastify() + t.after(() => fastify.close()) + + const throwable = 'test' + const payload = 'error' + + fastify.get('/', function async (req, reply) { + throw throwable + }) + + fastify.setErrorHandler((err, req, res) => { + t.assert.strictEqual(err, throwable) + res.code(500).send(payload) + }) + + const reply = await fastify.inject({ method: 'GET', url: '/' }) + t.assert.strictEqual(reply.statusCode, 500) +}) + +test('should trigger error handlers if a sync route throws undefined', async t => { + t.plan(1) + + const fastify = Fastify() + t.after(() => fastify.close()) + + fastify.get('/', function async (req, reply) { + // eslint-disable-next-line no-throw-literal + throw undefined + }) + + const reply = await fastify.inject({ method: 'GET', url: '/' }) + t.assert.strictEqual(reply.statusCode, 500) +}) + +test('setting content-type on reply object should not hang the server case 1', (t, testDone) => { + t.plan(2) + const fastify = Fastify() + t.after(() => fastify.close()) + + fastify.get('/', (req, reply) => { + reply + .code(200) + .headers({ 'content-type': 'text/plain; charset=utf-32' }) + .send(JSON.stringify({ bar: 'foo', baz: 'foobar' })) + }) + + fastify.inject({ + url: '/', + method: 'GET' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + testDone() + }) +}) + +test('setting content-type on reply object should not hang the server case 2', async t => { + t.plan(1) + const fastify = Fastify() + t.after(() => fastify.close()) + + fastify.get('/', (req, reply) => { + reply + .code(200) + .headers({ 'content-type': 'text/plain; charset=utf-8' }) + .send({ bar: 'foo', baz: 'foobar' }) + }) + + try { + await fastify.ready() + const res = await fastify.inject({ + url: '/', + method: 'GET' + }) + t.assert.deepStrictEqual({ + error: 'Internal Server Error', + message: 'Attempted to send payload of invalid type \'object\'. Expected a string or Buffer.', + statusCode: 500, + code: 'FST_ERR_REP_INVALID_PAYLOAD_TYPE' + }, + res.json()) + } catch (error) { + t.assert.ifError(error) + } +}) + +test('setting content-type on reply object should not hang the server case 3', (t, testDone) => { + t.plan(2) + const fastify = Fastify() + t.after(() => fastify.close()) + + fastify.get('/', (req, reply) => { + reply + .code(200) + .headers({ 'content-type': 'application/json' }) + .send({ bar: 'foo', baz: 'foobar' }) + }) + + fastify.inject({ + url: '/', + method: 'GET' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + testDone() + }) +}) + +test('pipe stream inside error handler should not cause error', (t, testDone) => { + t.plan(3) + const location = path.join(__dirname, '..', 'package.json') + const json = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json')).toString('utf8')) + + const fastify = Fastify() + t.after(() => fastify.close()) + + fastify.setErrorHandler((_error, _request, reply) => { + const stream = fs.createReadStream(location) + reply.code(400).type('application/json; charset=utf-8').send(stream) + }) + + fastify.get('/', (request, reply) => { + throw new Error('This is an error.') + }) + + fastify.inject({ + url: '/', + method: 'GET' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 400) + t.assert.deepStrictEqual(JSON.parse(res.payload), json) + testDone() + }) +}) diff --git a/services/slides/node_modules/fastify/test/reply-trailers.test.js b/services/slides/node_modules/fastify/test/reply-trailers.test.js new file mode 100644 index 0000000000000000000000000000000000000000..0d72eef42ebfefb84f66a4165808520b526d46f5 --- /dev/null +++ b/services/slides/node_modules/fastify/test/reply-trailers.test.js @@ -0,0 +1,445 @@ +'use strict' + +const { test, describe } = require('node:test') +const Fastify = require('..') +const { Readable } = require('node:stream') +const { createHash } = require('node:crypto') +const { sleep } = require('./helper') + +test('send trailers when payload is empty string', (t, testDone) => { + t.plan(5) + + const fastify = Fastify() + + fastify.get('/', function (request, reply) { + reply.trailer('ETag', function (reply, payload, done) { + done(null, 'custom-etag') + }) + reply.send('') + }) + + fastify.inject({ + method: 'GET', + url: '/' + }, (error, res) => { + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers.trailer, 'etag') + t.assert.strictEqual(res.trailers.etag, 'custom-etag') + t.assert.ok(!res.headers['content-length']) + testDone() + }) +}) + +test('send trailers when payload is empty buffer', (t, testDone) => { + t.plan(5) + + const fastify = Fastify() + + fastify.get('/', function (request, reply) { + reply.trailer('ETag', function (reply, payload, done) { + done(null, 'custom-etag') + }) + reply.send(Buffer.alloc(0)) + }) + + fastify.inject({ + method: 'GET', + url: '/' + }, (error, res) => { + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers.trailer, 'etag') + t.assert.strictEqual(res.trailers.etag, 'custom-etag') + t.assert.ok(!res.headers['content-length']) + testDone() + }) +}) + +test('send trailers when payload is undefined', (t, testDone) => { + t.plan(5) + + const fastify = Fastify() + + fastify.get('/', function (request, reply) { + reply.trailer('ETag', function (reply, payload, done) { + done(null, 'custom-etag') + }) + reply.send(undefined) + }) + + fastify.inject({ + method: 'GET', + url: '/' + }, (error, res) => { + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers.trailer, 'etag') + t.assert.strictEqual(res.trailers.etag, 'custom-etag') + t.assert.ok(!res.headers['content-length']) + testDone() + }) +}) + +test('send trailers when payload is json', (t, testDone) => { + t.plan(7) + + const fastify = Fastify() + const data = JSON.stringify({ hello: 'world' }) + const hash = createHash('md5') + hash.update(data) + const md5 = hash.digest('hex') + + fastify.get('/', function (request, reply) { + reply.trailer('Content-MD5', function (reply, payload, done) { + t.assert.strictEqual(data, payload) + const hash = createHash('md5') + hash.update(payload) + done(null, hash.digest('hex')) + }) + reply.send(data) + }) + + fastify.inject({ + method: 'GET', + url: '/' + }, (error, res) => { + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['transfer-encoding'], 'chunked') + t.assert.strictEqual(res.headers.trailer, 'content-md5') + t.assert.strictEqual(res.trailers['content-md5'], md5) + t.assert.ok(!res.headers['content-length']) + testDone() + }) +}) + +test('send trailers when payload is stream', (t, testDone) => { + t.plan(7) + + const fastify = Fastify() + + fastify.get('/', function (request, reply) { + reply.trailer('ETag', function (reply, payload, done) { + t.assert.deepStrictEqual(payload, null) + done(null, 'custom-etag') + }) + const stream = Readable.from([JSON.stringify({ hello: 'world' })]) + reply.send(stream) + }) + + fastify.inject({ + method: 'GET', + url: '/' + }, (error, res) => { + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['transfer-encoding'], 'chunked') + t.assert.strictEqual(res.headers.trailer, 'etag') + t.assert.strictEqual(res.trailers.etag, 'custom-etag') + t.assert.ok(!res.headers['content-length']) + testDone() + }) +}) + +test('send trailers when using async-await', (t, testDone) => { + t.plan(5) + + const fastify = Fastify() + + fastify.get('/', function (request, reply) { + reply.trailer('ETag', async function (reply, payload) { + return 'custom-etag' + }) + reply.send('') + }) + + fastify.inject({ + method: 'GET', + url: '/' + }, (error, res) => { + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers.trailer, 'etag') + t.assert.strictEqual(res.trailers.etag, 'custom-etag') + t.assert.ok(!res.headers['content-length']) + testDone() + }) +}) + +test('error in trailers should be ignored', (t, testDone) => { + t.plan(5) + + const fastify = Fastify() + + fastify.get('/', function (request, reply) { + reply.trailer('ETag', function (reply, payload, done) { + done('error') + }) + reply.send('') + }) + + fastify.inject({ + method: 'GET', + url: '/' + }, (error, res) => { + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers.trailer, 'etag') + t.assert.ok(!res.trailers['etag']) + t.assert.ok(!res.headers['content-length']) + testDone() + }) +}) + +describe('trailer handler counter', () => { + const data = JSON.stringify({ hello: 'world' }) + const hash = createHash('md5') + hash.update(data) + const md5 = hash.digest('hex') + + test('callback with timeout', (t, testDone) => { + t.plan(9) + const fastify = Fastify() + + fastify.get('/', function (request, reply) { + reply.trailer('Return-Early', function (reply, payload, done) { + t.assert.strictEqual(data, payload) + done(null, 'return') + }) + reply.trailer('Content-MD5', function (reply, payload, done) { + t.assert.strictEqual(data, payload) + const hash = createHash('md5') + hash.update(payload) + setTimeout(() => { + done(null, hash.digest('hex')) + }, 500) + }) + reply.send(data) + }) + + fastify.inject({ + method: 'GET', + url: '/' + }, (error, res) => { + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['transfer-encoding'], 'chunked') + t.assert.strictEqual(res.headers.trailer, 'return-early content-md5') + t.assert.strictEqual(res.trailers['return-early'], 'return') + t.assert.strictEqual(res.trailers['content-md5'], md5) + t.assert.ok(!res.headers['content-length']) + testDone() + }) + }) + + test('async-await', (t, testDone) => { + t.plan(9) + const fastify = Fastify() + + fastify.get('/', function (request, reply) { + reply.trailer('Return-Early', async function (reply, payload) { + t.assert.strictEqual(data, payload) + return 'return' + }) + reply.trailer('Content-MD5', async function (reply, payload) { + t.assert.strictEqual(data, payload) + const hash = createHash('md5') + hash.update(payload) + await sleep(500) + return hash.digest('hex') + }) + reply.send(data) + }) + + fastify.inject({ + method: 'GET', + url: '/' + }, (error, res) => { + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['transfer-encoding'], 'chunked') + t.assert.strictEqual(res.headers.trailer, 'return-early content-md5') + t.assert.strictEqual(res.trailers['return-early'], 'return') + t.assert.strictEqual(res.trailers['content-md5'], md5) + t.assert.ok(!res.headers['content-length']) + testDone() + }) + }) +}) + +test('removeTrailer', (t, testDone) => { + t.plan(6) + + const fastify = Fastify() + + fastify.get('/', function (request, reply) { + reply.removeTrailer('ETag') // remove nothing + reply.trailer('ETag', function (reply, payload, done) { + done(null, 'custom-etag') + }) + reply.trailer('Should-Not-Call', function (reply, payload, done) { + t.assert.fail('it should not called as this trailer is removed') + done(null, 'should-not-call') + }) + reply.removeTrailer('Should-Not-Call') + reply.send(undefined) + }) + + fastify.inject({ + method: 'GET', + url: '/' + }, (error, res) => { + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers.trailer, 'etag') + t.assert.strictEqual(res.trailers.etag, 'custom-etag') + t.assert.ok(!res.trailers['should-not-call']) + t.assert.ok(!res.headers['content-length']) + testDone() + }) +}) + +test('remove all trailers', (t, testDone) => { + t.plan(6) + + const fastify = Fastify() + + fastify.get('/', function (request, reply) { + reply.trailer('ETag', function (reply, payload, done) { + t.assert.fail('it should not called as this trailer is removed') + done(null, 'custom-etag') + }) + reply.removeTrailer('ETag') + reply.trailer('Should-Not-Call', function (reply, payload, done) { + t.assert.fail('it should not called as this trailer is removed') + done(null, 'should-not-call') + }) + reply.removeTrailer('Should-Not-Call') + reply.send('') + }) + + fastify.inject({ + method: 'GET', + url: '/' + }, (error, res) => { + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 200) + t.assert.ok(!res.headers.trailer) + t.assert.ok(!res.trailers.etag) + t.assert.ok(!res.trailers['should-not-call']) + t.assert.ok(!res.headers['content-length']) + testDone() + }) +}) + +test('hasTrailer', (t, testDone) => { + t.plan(10) + + const fastify = Fastify() + + fastify.get('/', function (request, reply) { + t.assert.strictEqual(reply.hasTrailer('ETag'), false) + reply.trailer('ETag', function (reply, payload, done) { + done(null, 'custom-etag') + }) + t.assert.strictEqual(reply.hasTrailer('ETag'), true) + reply.trailer('Should-Not-Call', function (reply, payload, done) { + t.assert.fail('it should not called as this trailer is removed') + done(null, 'should-not-call') + }) + t.assert.strictEqual(reply.hasTrailer('Should-Not-Call'), true) + reply.removeTrailer('Should-Not-Call') + t.assert.strictEqual(reply.hasTrailer('Should-Not-Call'), false) + reply.send(undefined) + }) + + fastify.inject({ + method: 'GET', + url: '/' + }, (error, res) => { + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers.trailer, 'etag') + t.assert.strictEqual(res.trailers.etag, 'custom-etag') + t.assert.ok(!res.trailers['should-not-call']) + t.assert.ok(!res.headers['content-length']) + testDone() + }) +}) + +test('throw error when trailer header name is not allowed', (t, testDone) => { + const INVALID_TRAILERS = [ + 'transfer-encoding', + 'content-length', + 'host', + 'cache-control', + 'max-forwards', + 'te', + 'authorization', + 'set-cookie', + 'content-encoding', + 'content-type', + 'content-range', + 'trailer' + ] + t.plan(INVALID_TRAILERS.length + 2) + + const fastify = Fastify() + + fastify.get('/', function (request, reply) { + for (const key of INVALID_TRAILERS) { + try { + reply.trailer(key, () => { }) + } catch (err) { + t.assert.strictEqual(err.message, `Called reply.trailer with an invalid header name: ${key}`) + } + } + reply.send('') + }) + + fastify.inject({ + method: 'GET', + url: '/' + }, (error, res) => { + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 200) + testDone() + }) +}) + +test('throw error when trailer header value is not function', (t, testDone) => { + const INVALID_TRAILERS_VALUE = [ + undefined, + null, + true, + false, + 'invalid', + [], + new Date(), + {} + ] + t.plan(INVALID_TRAILERS_VALUE.length + 2) + + const fastify = Fastify() + + fastify.get('/', function (request, reply) { + for (const value of INVALID_TRAILERS_VALUE) { + try { + reply.trailer('invalid', value) + } catch (err) { + t.assert.strictEqual(err.message, `Called reply.trailer('invalid', fn) with an invalid type: ${typeof value}. Expected a function.`) + } + } + reply.send('') + }) + + fastify.inject({ + method: 'GET', + url: '/' + }, (error, res) => { + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 200) + testDone() + }) +}) diff --git a/services/slides/node_modules/fastify/test/reply-web-stream-locked.test.js b/services/slides/node_modules/fastify/test/reply-web-stream-locked.test.js new file mode 100644 index 0000000000000000000000000000000000000000..21262a05906ab743eacfcc4e6c600dccc6ab059d --- /dev/null +++ b/services/slides/node_modules/fastify/test/reply-web-stream-locked.test.js @@ -0,0 +1,37 @@ +'use strict' + +const { ReadableStream } = require('node:stream/web') +const { test, after } = require('node:test') +const Fastify = require('..') + +test('reply.send(web ReadableStream) throws if locked', async t => { + t.plan(3) + + const app = Fastify() + after(() => app.close()) + + app.get('/', (req, reply) => { + const rs = new ReadableStream({ + start (controller) { controller.enqueue(new TextEncoder().encode('hi')); controller.close() } + }) + // lock the stream + const reader = rs.getReader() + t.assert.strictEqual(rs.locked, true, 'stream is locked') + + // sending a locked stream should trigger the Fastify error + reply.send(rs) + reader.releaseLock() + }) + + const res = await app.inject({ method: 'GET', url: '/' }) + t.assert.strictEqual(res.statusCode, 500) + t.assert.deepStrictEqual( + JSON.parse(res.body), + { + statusCode: 500, + code: 'FST_ERR_REP_READABLE_STREAM_LOCKED', + error: 'Internal Server Error', + message: 'ReadableStream was locked. You should call releaseLock() method on reader before sending.' + } + ) +}) diff --git a/services/slides/node_modules/fastify/test/request-error.test.js b/services/slides/node_modules/fastify/test/request-error.test.js new file mode 100644 index 0000000000000000000000000000000000000000..d56c2e02deeff3c704fedcd4d1c312cae96f9e68 --- /dev/null +++ b/services/slides/node_modules/fastify/test/request-error.test.js @@ -0,0 +1,624 @@ +'use strict' + +const { connect } = require('node:net') +const { test } = require('node:test') +const Fastify = require('..') +const { kRequest } = require('../lib/symbols.js') +const split = require('split2') +const { Readable } = require('node:stream') +const { getServerUrl } = require('./helper') + +test('default 400 on request error', (t, done) => { + t.plan(4) + + const fastify = Fastify() + + fastify.post('/', function (req, reply) { + reply.send({ hello: 'world' }) + }) + + fastify.inject({ + method: 'POST', + url: '/', + simulate: { + error: true + }, + body: { + text: '12345678901234567890123456789012345678901234567890' + } + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 400) + t.assert.strictEqual(res.headers['content-type'], 'application/json; charset=utf-8') + t.assert.deepStrictEqual(JSON.parse(res.payload), { + error: 'Bad Request', + message: 'Simulated', + statusCode: 400 + }) + done() + }) +}) + +test('default 400 on request error with custom error handler', (t, done) => { + t.plan(6) + + const fastify = Fastify() + + fastify.setErrorHandler(function (err, request, reply) { + t.assert.strictEqual(typeof request, 'object') + t.assert.strictEqual(request instanceof fastify[kRequest].parent, true) + reply + .code(err.statusCode) + .type('application/json; charset=utf-8') + .send(err) + }) + + fastify.post('/', function (req, reply) { + reply.send({ hello: 'world' }) + }) + + fastify.inject({ + method: 'POST', + url: '/', + simulate: { + error: true + }, + body: { + text: '12345678901234567890123456789012345678901234567890' + } + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 400) + t.assert.strictEqual(res.headers['content-type'], 'application/json; charset=utf-8') + t.assert.deepStrictEqual(JSON.parse(res.payload), { + error: 'Bad Request', + message: 'Simulated', + statusCode: 400 + }) + done() + }) +}) + +test('default clientError handler ignores ECONNRESET', (t, done) => { + t.plan(3) + + let logs = '' + let response = '' + + const fastify = Fastify({ + bodyLimit: 1, + keepAliveTimeout: 100, + logger: { + level: 'trace', + stream: { + write () { + logs += JSON.stringify(arguments) + } + } + } + }) + + fastify.get('/', (request, reply) => { + reply.send('OK') + + process.nextTick(() => { + const error = new Error() + error.code = 'ECONNRESET' + + fastify.server.emit('clientError', error, request.raw.socket) + }) + }) + + fastify.listen({ port: 0 }, function (err) { + t.assert.ifError(err) + t.after(() => fastify.close()) + + const client = connect(fastify.server.address().port) + + client.on('data', chunk => { + response += chunk.toString('utf-8') + }) + + client.on('end', () => { + t.assert.match(response, /^HTTP\/1.1 200 OK/) + t.assert.notEqual(logs, /ECONNRESET/) + done() + }) + + client.resume() + client.write('GET / HTTP/1.1\r\n') + client.write('Host: fastify.test\r\n') + client.write('Connection: close\r\n') + client.write('\r\n\r\n') + }) +}) + +test('default clientError handler ignores sockets in destroyed state', t => { + t.plan(1) + + const fastify = Fastify({ + bodyLimit: 1, + keepAliveTimeout: 100 + }) + fastify.server.on('clientError', () => { + // this handler is called after default handler, so we can make sure end was not called + t.assert.ok('end should not be called') + }) + fastify.server.emit('clientError', new Error(), { + destroyed: true, + end () { + t.assert.fail('end should not be called') + }, + destroy () { + t.assert.fail('destroy should not be called') + } + }) +}) + +test('default clientError handler destroys sockets in writable state', t => { + t.plan(2) + + const fastify = Fastify({ + bodyLimit: 1, + keepAliveTimeout: 100 + }) + + fastify.server.emit('clientError', new Error(), { + destroyed: false, + writable: true, + encrypted: true, + end () { + t.assert.fail('end should not be called') + }, + destroy () { + t.assert.ok('destroy should be called') + }, + write (response) { + t.assert.match(response, /^HTTP\/1.1 400 Bad Request/) + } + }) +}) + +test('default clientError handler destroys http sockets in non-writable state', t => { + t.plan(1) + + const fastify = Fastify({ + bodyLimit: 1, + keepAliveTimeout: 100 + }) + + fastify.server.emit('clientError', new Error(), { + destroyed: false, + writable: false, + end () { + t.assert.fail('end should not be called') + }, + destroy () { + t.assert.ok('destroy should be called') + }, + write (response) { + t.assert.fail('write should not be called') + } + }) +}) + +test('error handler binding', (t, done) => { + t.plan(5) + + const fastify = Fastify() + + fastify.setErrorHandler(function (err, request, reply) { + t.assert.strictEqual(this, fastify) + reply + .code(err.statusCode) + .type('application/json; charset=utf-8') + .send(err) + }) + + fastify.post('/', function (req, reply) { + reply.send({ hello: 'world' }) + }) + + fastify.inject({ + method: 'POST', + url: '/', + simulate: { + error: true + }, + body: { + text: '12345678901234567890123456789012345678901234567890' + } + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 400) + t.assert.strictEqual(res.headers['content-type'], 'application/json; charset=utf-8') + t.assert.deepStrictEqual(JSON.parse(res.payload), { + error: 'Bad Request', + message: 'Simulated', + statusCode: 400 + }) + done() + }) +}) + +test('encapsulated error handler binding', (t, done) => { + t.plan(7) + + const fastify = Fastify() + + fastify.register(function (app, opts, done) { + app.decorate('hello', 'world') + t.assert.strictEqual(app.hello, 'world') + app.post('/', function (req, reply) { + reply.send({ hello: 'world' }) + }) + app.setErrorHandler(function (err, request, reply) { + t.assert.strictEqual(this.hello, 'world') + reply + .code(err.statusCode) + .type('application/json; charset=utf-8') + .send(err) + }) + done() + }) + + fastify.inject({ + method: 'POST', + url: '/', + simulate: { + error: true + }, + body: { + text: '12345678901234567890123456789012345678901234567890' + } + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 400) + t.assert.strictEqual(res.headers['content-type'], 'application/json; charset=utf-8') + t.assert.deepStrictEqual(res.json(), { + error: 'Bad Request', + message: 'Simulated', + statusCode: 400 + }) + t.assert.strictEqual(fastify.hello, undefined) + done() + }) +}) + +test('default clientError replies with bad request on reused keep-alive connection', (t, done) => { + t.plan(2) + + let response = '' + + const fastify = Fastify({ + bodyLimit: 1, + keepAliveTimeout: 100 + }) + + fastify.get('/', (request, reply) => { + reply.send('OK\n') + }) + + fastify.listen({ port: 0 }, function (err) { + t.assert.ifError(err) + fastify.server.unref() + + const client = connect(fastify.server.address().port) + + client.on('data', chunk => { + response += chunk.toString('utf-8') + }) + + client.on('end', () => { + t.assert.match(response, /^HTTP\/1.1 200 OK.*HTTP\/1.1 400 Bad Request/s) + done() + }) + + client.resume() + client.write('GET / HTTP/1.1\r\n') + client.write('Host: fastify.test\r\n') + client.write('\r\n\r\n') + client.write('GET /?a b HTTP/1.1\r\n') + client.write('Host: fastify.test\r\n') + client.write('Connection: close\r\n') + client.write('\r\n\r\n') + }) +}) + +test('non-numeric content-length is rejected before Fastify body parsing', (t, done) => { + t.plan(3) + + let response = '' + + const fastify = Fastify({ + bodyLimit: 1, + keepAliveTimeout: 100 + }) + + fastify.post('/', () => { + t.assert.fail('handler should not be called') + }) + + fastify.listen({ port: 0 }, function (err) { + t.assert.ifError(err) + t.after(() => fastify.close()) + + const client = connect(fastify.server.address().port) + + client.on('data', chunk => { + response += chunk.toString('utf-8') + }) + + client.on('end', () => { + t.assert.match(response, /^HTTP\/1.1 400 Bad Request/) + t.assert.match(response, /"message":"Client Error"/) + done() + }) + + client.resume() + client.write('POST / HTTP/1.1\r\n') + client.write('Host: example.com\r\n') + client.write('Content-Type: text/plain\r\n') + client.write('Content-Length: abc\r\n') + client.write('Connection: close\r\n') + client.write('\r\n') + client.write('x'.repeat(32)) + }) +}) + +test('request.routeOptions.method is an uppercase string /1', async t => { + t.plan(3) + const fastify = Fastify() + const handler = function (req, res) { + t.assert.strictEqual('POST', req.routeOptions.method) + res.send({}) + } + + fastify.post('/', { + bodyLimit: 1000, + handler + }) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const result = await fetch(fastifyServer, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify([]) + }) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) +}) + +test('request.routeOptions.method is an uppercase string /2', async t => { + t.plan(3) + const fastify = Fastify() + const handler = function (req, res) { + t.assert.strictEqual('POST', req.routeOptions.method) + res.send({}) + } + + fastify.route({ + url: '/', + method: 'POST', + bodyLimit: 1000, + handler + }) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const result = await fetch(fastifyServer, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify([]) + }) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) +}) + +test('request.routeOptions.method is an uppercase string /3', async t => { + t.plan(3) + const fastify = Fastify() + const handler = function (req, res) { + t.assert.strictEqual('POST', req.routeOptions.method) + res.send({}) + } + + fastify.route({ + url: '/', + method: 'pOSt', + bodyLimit: 1000, + handler + }) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const result = await fetch(fastifyServer, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify([]) + }) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) +}) + +test('request.routeOptions.method is an array with uppercase string', async t => { + t.plan(3) + const fastify = Fastify() + const handler = function (req, res) { + t.assert.deepStrictEqual(['POST'], req.routeOptions.method) + res.send({}) + } + + fastify.route({ + url: '/', + method: ['pOSt'], + bodyLimit: 1000, + handler + }) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const result = await fetch(fastifyServer, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify([]) + }) + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) +}) + +test('test request.routeOptions.version', async t => { + t.plan(6) + const fastify = Fastify() + + fastify.route({ + method: 'POST', + url: '/version', + constraints: { version: '1.2.0' }, + handler: function (request, reply) { + t.assert.strictEqual('1.2.0', request.routeOptions.version) + reply.send({}) + } + }) + + fastify.route({ + method: 'POST', + url: '/version-undefined', + handler: function (request, reply) { + t.assert.strictEqual(undefined, request.routeOptions.version) + reply.send({}) + } + }) + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const result1 = await fetch(fastifyServer + '/version', { + method: 'POST', + headers: { 'Content-Type': 'application/json', 'Accept-Version': '1.2.0' }, + body: JSON.stringify([]) + }) + t.assert.ok(result1.ok) + t.assert.strictEqual(result1.status, 200) + + const result2 = await fetch(fastifyServer + '/version-undefined', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify([]) + }) + t.assert.ok(result2.ok) + t.assert.strictEqual(result2.status, 200) +}) + +test('customErrorHandler should throw for json err and stream response', async (t) => { + t.plan(5) + + const logStream = split(JSON.parse) + const fastify = Fastify({ + logger: { + stream: logStream, + level: 'error' + } + }) + t.after(() => fastify.close()) + + fastify.get('/', async (req, reply) => { + const stream = new Readable({ + read () { + this.push('hello') + } + }) + process.nextTick(() => stream.destroy(new Error('stream error'))) + + reply.type('application/text') + await reply.send(stream) + }) + + fastify.setErrorHandler((err, req, reply) => { + t.assert.strictEqual(err.message, 'stream error') + reply.code(400) + reply.send({ error: err.message }) + }) + + logStream.once('data', line => { + t.assert.strictEqual(line.msg, 'Attempted to send payload of invalid type \'object\'. Expected a string or Buffer.') + t.assert.strictEqual(line.level, 50) + }) + + await fastify.listen({ port: 0 }) + + const response = await fetch(getServerUrl(fastify) + '/') + + t.assert.strictEqual(response.status, 500) + t.assert.deepStrictEqual(await response.json(), { statusCode: 500, code: 'FST_ERR_REP_INVALID_PAYLOAD_TYPE', error: 'Internal Server Error', message: "Attempted to send payload of invalid type 'object'. Expected a string or Buffer." }) +}) + +test('customErrorHandler should not throw for json err and stream response with content-type defined', async (t) => { + t.plan(4) + + const logStream = split(JSON.parse) + const fastify = Fastify({ + logger: { + stream: logStream, + level: 'error' + } + }) + + t.after(() => fastify.close()) + + fastify.get('/', async (req, reply) => { + const stream = new Readable({ + read () { + this.push('hello') + } + }) + process.nextTick(() => stream.destroy(new Error('stream error'))) + + reply.type('application/text') + await reply.send(stream) + }) + + fastify.setErrorHandler((err, req, reply) => { + t.assert.strictEqual(err.message, 'stream error') + reply + .code(400) + .type('application/json') + .send({ error: err.message }) + }) + + await fastify.listen({ port: 0 }) + + const response = await fetch(getServerUrl(fastify) + '/') + + t.assert.strictEqual(response.status, 400) + t.assert.strictEqual(response.headers.get('content-type'), 'application/json; charset=utf-8') + t.assert.deepStrictEqual(await response.json(), { error: 'stream error' }) +}) + +test('customErrorHandler should not call handler for in-stream error', async (t) => { + t.plan(1) + + const fastify = Fastify() + t.after(() => fastify.close()) + + fastify.get('/', async (req, reply) => { + const stream = new Readable({ + read () { + this.push('hello') + stream.destroy(new Error('stream error')) + } + }) + + reply.type('application/text') + await reply.send(stream) + }) + + fastify.setErrorHandler(() => { + t.assert.fail('must not be called') + }) + await fastify.listen({ port: 0 }) + + await t.assert.rejects(fetch(getServerUrl(fastify) + '/'), { + message: 'fetch failed' + }) +}) diff --git a/services/slides/node_modules/fastify/test/request-header-host.test.js b/services/slides/node_modules/fastify/test/request-header-host.test.js new file mode 100644 index 0000000000000000000000000000000000000000..26c77584ac8b12bdc4ac54949902831e5f634069 --- /dev/null +++ b/services/slides/node_modules/fastify/test/request-header-host.test.js @@ -0,0 +1,339 @@ +'use strict' + +const { test } = require('node:test') +const { connect } = require('node:net') +const Fastify = require('..') + +// RFC9112 +// https://www.rfc-editor.org/rfc/rfc9112 +test('Return 400 when Host header is missing', (t, done) => { + t.plan(2) + let data = Buffer.alloc(0) + const fastify = Fastify() + + t.after(() => fastify.close()) + + fastify.get('/', async function () { + t.assert.fail('should not reach handler') + return { ok: true } + }) + fastify.listen({ port: 0 }, err => { + t.assert.ifError(err) + + const socket = connect(fastify.server.address().port) + socket.write('GET / HTTP/1.1\r\n\r\n') + socket.on('data', c => (data = Buffer.concat([data, c]))) + socket.on('end', () => { + t.assert.match( + data.toString('utf-8'), + /^HTTP\/1.1 400 Bad Request/ + ) + done() + }) + }) +}) + +test('Return 400 when Host header is missing with trust proxy', (t, done) => { + t.plan(2) + let data = Buffer.alloc(0) + const fastify = Fastify({ + trustProxy: true + }) + + t.after(() => fastify.close()) + + fastify.get('/', async function () { + t.assert.fail('should not reach handler') + return { ok: true } + }) + fastify.listen({ port: 0 }, err => { + t.assert.ifError(err) + + const socket = connect(fastify.server.address().port) + socket.write('GET / HTTP/1.1\r\n\r\n') + socket.on('data', c => (data = Buffer.concat([data, c]))) + socket.on('end', () => { + t.assert.match( + data.toString('utf-8'), + /^HTTP\/1.1 400 Bad Request/ + ) + done() + }) + }) +}) + +test('Return 200 when Host header is empty', (t, done) => { + t.plan(5) + let data = Buffer.alloc(0) + const fastify = Fastify({ + keepAliveTimeout: 10 + }) + + t.after(() => fastify.close()) + + fastify.get('/', async function (request) { + t.assert.strictEqual(request.host, '') + t.assert.strictEqual(request.hostname, '') + t.assert.strictEqual(request.port, null) + return { ok: true } + }) + fastify.listen({ port: 0 }, err => { + t.assert.ifError(err) + + const socket = connect(fastify.server.address().port) + socket.write('GET / HTTP/1.1\r\nHost:\r\n\r\n') + socket.on('data', c => (data = Buffer.concat([data, c]))) + socket.on('end', () => { + t.assert.match( + data.toString('utf-8'), + /^HTTP\/1.1 200 OK/ + ) + done() + }) + }) +}) + +test('Return 200 when Host header is empty with trust proxy', (t, done) => { + t.plan(5) + let data = Buffer.alloc(0) + const fastify = Fastify({ + trustProxy: true, + keepAliveTimeout: 10 + }) + + t.after(() => fastify.close()) + + fastify.get('/', async function (request) { + t.assert.strictEqual(request.host, '') + t.assert.strictEqual(request.hostname, '') + t.assert.strictEqual(request.port, null) + return { ok: true } + }) + fastify.listen({ port: 0 }, err => { + t.assert.ifError(err) + + const socket = connect(fastify.server.address().port) + socket.write('GET / HTTP/1.1\r\nHost:\r\n\r\n') + socket.on('data', c => (data = Buffer.concat([data, c]))) + socket.on('end', () => { + t.assert.match( + data.toString('utf-8'), + /^HTTP\/1.1 200 OK/ + ) + done() + }) + }) +}) + +// Node.js allows exploiting RFC9112 +// https://nodejs.org/docs/latest-v22.x/api/http.html#httpcreateserveroptions-requestlistener +test('Return 200 when Host header is missing and http.requireHostHeader = false', (t, done) => { + t.plan(5) + let data = Buffer.alloc(0) + const fastify = Fastify({ + http: { + requireHostHeader: false + }, + keepAliveTimeout: 10 + }) + + t.after(() => fastify.close()) + + fastify.get('/', async function (request) { + t.assert.strictEqual(request.host, '') + t.assert.strictEqual(request.hostname, '') + t.assert.strictEqual(request.port, null) + return { ok: true } + }) + fastify.listen({ port: 0 }, err => { + t.assert.ifError(err) + + const socket = connect(fastify.server.address().port) + socket.write('GET / HTTP/1.1\r\n\r\n') + socket.on('data', c => (data = Buffer.concat([data, c]))) + socket.on('end', () => { + t.assert.match( + data.toString('utf-8'), + /^HTTP\/1.1 200 OK/ + ) + done() + }) + }) +}) + +test('Return 200 when Host header is missing and http.requireHostHeader = false with trust proxy', (t, done) => { + t.plan(5) + let data = Buffer.alloc(0) + const fastify = Fastify({ + http: { + requireHostHeader: false + }, + trustProxy: true, + keepAliveTimeout: 10 + }) + + t.after(() => fastify.close()) + + fastify.get('/', async function (request) { + t.assert.strictEqual(request.host, '') + t.assert.strictEqual(request.hostname, '') + t.assert.strictEqual(request.port, null) + return { ok: true } + }) + fastify.listen({ port: 0 }, err => { + t.assert.ifError(err) + + const socket = connect(fastify.server.address().port) + socket.write('GET / HTTP/1.1\r\n\r\n') + socket.on('data', c => (data = Buffer.concat([data, c]))) + socket.on('end', () => { + t.assert.match( + data.toString('utf-8'), + /^HTTP\/1.1 200 OK/ + ) + done() + }) + }) +}) + +test('Return 200 when Host header is missing using HTTP/1.0', (t, done) => { + t.plan(5) + let data = Buffer.alloc(0) + const fastify = Fastify({ + keepAliveTimeout: 10 + }) + + t.after(() => fastify.close()) + + fastify.get('/', async function (request) { + t.assert.strictEqual(request.host, '') + t.assert.strictEqual(request.hostname, '') + t.assert.strictEqual(request.port, null) + return { ok: true } + }) + fastify.listen({ port: 0 }, err => { + t.assert.ifError(err) + + const socket = connect(fastify.server.address().port) + socket.write('GET / HTTP/1.0\r\n\r\n') + socket.on('data', c => (data = Buffer.concat([data, c]))) + socket.on('end', () => { + t.assert.match( + data.toString('utf-8'), + /^HTTP\/1.1 200 OK/ + ) + done() + }) + }) +}) + +test('Return 200 when Host header is missing with trust proxy using HTTP/1.0', (t, done) => { + t.plan(5) + let data = Buffer.alloc(0) + const fastify = Fastify({ + trustProxy: true, + keepAliveTimeout: 10 + }) + + t.after(() => fastify.close()) + + fastify.get('/', async function (request) { + t.assert.strictEqual(request.host, '') + t.assert.strictEqual(request.hostname, '') + t.assert.strictEqual(request.port, null) + return { ok: true } + }) + fastify.listen({ port: 0 }, err => { + t.assert.ifError(err) + + const socket = connect(fastify.server.address().port) + socket.write('GET / HTTP/1.0\r\n\r\n') + socket.on('data', c => (data = Buffer.concat([data, c]))) + socket.on('end', () => { + t.assert.match( + data.toString('utf-8'), + /^HTTP\/1.1 200 OK/ + ) + done() + }) + }) +}) + +test('Return 200 when Host header is removed by schema', (t, done) => { + t.plan(5) + let data = Buffer.alloc(0) + const fastify = Fastify({ + keepAliveTimeout: 10 + }) + + t.after(() => fastify.close()) + + fastify.get('/', { + schema: { + headers: { + type: 'object', + properties: {}, + additionalProperties: false + } + } + }, async function (request) { + t.assert.strictEqual(request.host, '') + t.assert.strictEqual(request.hostname, '') + t.assert.strictEqual(request.port, null) + return { ok: true } + }) + fastify.listen({ port: 0 }, err => { + t.assert.ifError(err) + + const socket = connect(fastify.server.address().port) + socket.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') + socket.on('data', c => (data = Buffer.concat([data, c]))) + socket.on('end', () => { + t.assert.match( + data.toString('utf-8'), + /^HTTP\/1.1 200 OK/ + ) + done() + }) + }) +}) + +test('Return 200 when Host header is removed by schema with trust proxy', (t, done) => { + t.plan(5) + let data = Buffer.alloc(0) + const fastify = Fastify({ + trustProxy: true, + keepAliveTimeout: 10 + }) + + t.after(() => fastify.close()) + + fastify.get('/', { + schema: { + headers: { + type: 'object', + properties: {}, + additionalProperties: false + } + } + }, async function (request) { + t.assert.strictEqual(request.host, '') + t.assert.strictEqual(request.hostname, '') + t.assert.strictEqual(request.port, null) + return { ok: true } + }) + fastify.listen({ port: 0 }, err => { + t.assert.ifError(err) + + const socket = connect(fastify.server.address().port) + socket.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') + socket.on('data', c => (data = Buffer.concat([data, c]))) + socket.on('end', () => { + t.assert.match( + data.toString('utf-8'), + /^HTTP\/1.1 200 OK/ + ) + done() + }) + }) +}) diff --git a/services/slides/node_modules/fastify/test/request-id.test.js b/services/slides/node_modules/fastify/test/request-id.test.js new file mode 100644 index 0000000000000000000000000000000000000000..e0b935d7659f3e86b59435d9e76a6936b041a917 --- /dev/null +++ b/services/slides/node_modules/fastify/test/request-id.test.js @@ -0,0 +1,118 @@ +'use strict' + +const { test } = require('node:test') +const Fastify = require('..') + +test('The request id header key can be customized', async (t) => { + t.plan(2) + const REQUEST_ID = '42' + + const fastify = Fastify({ + requestIdHeader: 'my-custom-request-id' + }) + + fastify.get('/', (req, reply) => { + t.assert.strictEqual(req.id, REQUEST_ID) + reply.send({ id: req.id }) + }) + + const response = await fastify.inject({ method: 'GET', url: '/', headers: { 'my-custom-request-id': REQUEST_ID } }) + const body = await response.json() + t.assert.strictEqual(body.id, REQUEST_ID) +}) + +test('The request id header key can be customized', async (t) => { + t.plan(2) + const REQUEST_ID = '42' + + const fastify = Fastify({ + requestIdHeader: 'my-custom-request-id' + }) + + fastify.get('/', (req, reply) => { + t.assert.strictEqual(req.id, REQUEST_ID) + reply.send({ id: req.id }) + }) + + const response = await fastify.inject({ method: 'GET', url: '/', headers: { 'MY-CUSTOM-REQUEST-ID': REQUEST_ID } }) + const body = await response.json() + t.assert.strictEqual(body.id, REQUEST_ID) +}) + +test('The request id header key can be customized', async (t) => { + t.plan(3) + const REQUEST_ID = '42' + + const fastify = Fastify({ + requestIdHeader: 'my-custom-request-id' + }) + + fastify.get('/', (req, reply) => { + t.assert.strictEqual(req.id, REQUEST_ID) + reply.send({ id: req.id }) + }) + + t.after(() => fastify.close()) + + const fastifyServer = await fastify.listen({ port: 0 }) + + const result = await fetch(fastifyServer, { + headers: { + 'my-custom-request-id': REQUEST_ID + } + }) + t.assert.ok(result.ok) + t.assert.deepStrictEqual(await result.json(), { id: REQUEST_ID }) +}) + +test('The request id header key can be customized', async (t) => { + t.plan(3) + const REQUEST_ID = '42' + + const fastify = Fastify({ + requestIdHeader: 'my-custom-request-id' + }) + + fastify.get('/', (req, reply) => { + t.assert.strictEqual(req.id, REQUEST_ID) + reply.send({ id: req.id }) + }) + + t.after(() => fastify.close()) + + const fastifyServer = await fastify.listen({ port: 0 }) + + const result = await fetch(fastifyServer, { + headers: { + 'MY-CUSTOM-REQUEST-ID': REQUEST_ID + } + }) + t.assert.ok(result.ok) + t.assert.deepStrictEqual(await result.json(), { id: REQUEST_ID }) +}) + +test('The request id header key can be customized', async (t) => { + t.plan(3) + const REQUEST_ID = '42' + + const fastify = Fastify({ + requestIdHeader: 'MY-CUSTOM-REQUEST-ID' + }) + + fastify.get('/', (req, reply) => { + t.assert.strictEqual(req.id, REQUEST_ID) + reply.send({ id: req.id }) + }) + + t.after(() => fastify.close()) + + const fastifyServer = await fastify.listen({ port: 0 }) + + const result = await fetch(fastifyServer, { + headers: { + 'MY-CUSTOM-REQUEST-ID': REQUEST_ID + } + }) + t.assert.ok(result.ok) + t.assert.deepStrictEqual(await result.json(), { id: REQUEST_ID }) +}) diff --git a/services/slides/node_modules/fastify/test/request-port.test.js b/services/slides/node_modules/fastify/test/request-port.test.js new file mode 100644 index 0000000000000000000000000000000000000000..dc7347d2e4bd4a9831c5ac6349ed6c6fca076044 --- /dev/null +++ b/services/slides/node_modules/fastify/test/request-port.test.js @@ -0,0 +1,72 @@ +'use strict' + +const test = require('node:test') +const Request = require('../lib/request') + +test('.port parses port correctly', (t) => { + const fixtures = [ + { + expected: 80, + req: { + headers: { + host: 'example.com:80' + } + } + }, + { + expected: 443, + req: { + headers: { + host: 'example.com:443' + } + } + }, + { + expected: 80, + req: { + headers: { + host: '[::1]:80' + } + } + }, + { + expected: 443, + req: { + headers: { + host: '[::1]:443' + } + } + }, + { + expected: null, + req: { + headers: { + host: '[::1]' + } + } + }, + { + expected: 80, + req: { + headers: { + ':authority': '1.2.3.4:80' + } + } + }, + { + expected: null, + req: { + headers: {} + } + } + ] + + for (const fixture of fixtures) { + const req = new Request(1, {}, fixture.req, '', {}, {}) + t.assert.equal( + req.port, + fixture.expected, + `${fixture.req.headers.host} should parse to ${fixture.expected}` + ) + } +}) diff --git a/services/slides/node_modules/fastify/test/request-timeout.test.js b/services/slides/node_modules/fastify/test/request-timeout.test.js new file mode 100644 index 0000000000000000000000000000000000000000..995dc3840430609dd3029518c3745c1fc6b3be83 --- /dev/null +++ b/services/slides/node_modules/fastify/test/request-timeout.test.js @@ -0,0 +1,53 @@ +'use strict' + +const http = require('node:http') +const { test } = require('node:test') +const Fastify = require('..') + +test('requestTimeout passed to server', t => { + t.plan(5) + + try { + Fastify({ requestTimeout: 500.1 }) + t.assert.fail('option must be an integer') + } catch (err) { + t.assert.ok(err) + } + + try { + Fastify({ requestTimeout: [] }) + t.assert.fail('option must be an integer') + } catch (err) { + t.assert.ok(err) + } + + const httpServer = Fastify({ requestTimeout: 1000 }).server + t.assert.strictEqual(httpServer.requestTimeout, 1000) + + const httpsServer = Fastify({ requestTimeout: 1000, https: true }).server + t.assert.strictEqual(httpsServer.requestTimeout, 1000) + + const serverFactory = (handler, _) => { + const server = http.createServer((req, res) => { + handler(req, res) + }) + server.requestTimeout = 5000 + return server + } + const customServer = Fastify({ requestTimeout: 4000, serverFactory }).server + t.assert.strictEqual(customServer.requestTimeout, 5000) +}) + +test('requestTimeout should be set', async (t) => { + t.plan(1) + + const initialConfig = Fastify({ requestTimeout: 5000 }).initialConfig + t.assert.strictEqual(initialConfig.requestTimeout, 5000) +}) + +test('requestTimeout should 0', async (t) => { + t.plan(1) + + const initialConfig = Fastify().initialConfig + t.assert.strictEqual(initialConfig.requestTimeout, 0) +}) diff --git a/services/slides/node_modules/fastify/test/route-hooks.test.js b/services/slides/node_modules/fastify/test/route-hooks.test.js new file mode 100644 index 0000000000000000000000000000000000000000..107b561c453c601d52f58781b18a17650b6bdf8d --- /dev/null +++ b/services/slides/node_modules/fastify/test/route-hooks.test.js @@ -0,0 +1,635 @@ +'use strict' + +const { Readable } = require('node:stream') +const { test } = require('node:test') +const Fastify = require('../') + +process.removeAllListeners('warning') + +function endRouteHook (doneOrPayload, done, doneValue) { + if (typeof doneOrPayload === 'function') { + doneOrPayload(doneValue) + } else { + done(doneValue) + } +} + +function testExecutionHook (hook) { + test(`${hook}`, (t, testDone) => { + t.plan(3) + const fastify = Fastify() + + fastify.post('/', { + [hook]: (req, reply, doneOrPayload, done) => { + t.assert.ok('hook called') + endRouteHook(doneOrPayload, done) + } + }, (req, reply) => { + reply.send(req.body) + }) + + fastify.inject({ + method: 'POST', + url: '/', + payload: { hello: 'world' } + }, (err, res) => { + t.assert.ifError(err) + const payload = JSON.parse(res.payload) + t.assert.deepStrictEqual(payload, { hello: 'world' }) + testDone() + }) + }) + + test(`${hook} option should be called after ${hook} hook`, (t, testDone) => { + t.plan(3) + const fastify = Fastify() + const checker = Object.defineProperty({ calledTimes: 0 }, 'check', { + get: function () { return ++this.calledTimes } + }) + + fastify.addHook(hook, (req, reply, doneOrPayload, done) => { + t.assert.strictEqual(checker.check, 1) + endRouteHook(doneOrPayload, done) + }) + + fastify.post('/', { + [hook]: (req, reply, doneOrPayload, done) => { + t.assert.strictEqual(checker.check, 2) + endRouteHook(doneOrPayload, done) + } + }, (req, reply) => { + reply.send({}) + }) + + fastify.inject({ + method: 'POST', + url: '/', + payload: { hello: 'world' } + }, (err, res) => { + t.assert.ifError(err) + testDone() + }) + }) + + test(`${hook} option could accept an array of functions`, (t, testDone) => { + t.plan(3) + const fastify = Fastify() + const checker = Object.defineProperty({ calledTimes: 0 }, 'check', { + get: function () { return ++this.calledTimes } + }) + + fastify.post('/', { + [hook]: [ + (req, reply, doneOrPayload, done) => { + t.assert.strictEqual(checker.check, 1) + endRouteHook(doneOrPayload, done) + }, + (req, reply, doneOrPayload, done) => { + t.assert.strictEqual(checker.check, 2) + endRouteHook(doneOrPayload, done) + } + ] + }, (req, reply) => { + reply.send({}) + }) + + fastify.inject({ + method: 'POST', + url: '/', + payload: { hello: 'world' } + }, (err, res) => { + t.assert.ifError(err) + testDone() + }) + }) + + test(`${hook} option could accept an array of async functions`, (t, testDone) => { + t.plan(3) + const fastify = Fastify() + const checker = Object.defineProperty({ calledTimes: 0 }, 'check', { + get: function () { return ++this.calledTimes } + }) + + fastify.post('/', { + [hook]: [ + async (req, reply) => { + t.assert.strictEqual(checker.check, 1) + }, + async (req, reply) => { + t.assert.strictEqual(checker.check, 2) + } + ] + }, (req, reply) => { + reply.send({}) + }) + + fastify.inject({ + method: 'POST', + url: '/', + payload: { hello: 'world' } + }, (err, res) => { + t.assert.ifError(err) + testDone() + }) + }) + + test(`${hook} option does not interfere with ${hook} hook`, (t, testDone) => { + t.plan(7) + const fastify = Fastify() + const checker = Object.defineProperty({ calledTimes: 0 }, 'check', { + get: function () { return ++this.calledTimes } + }) + + fastify.addHook(hook, (req, reply, doneOrPayload, done) => { + t.assert.strictEqual(checker.check, 1) + endRouteHook(doneOrPayload, done) + }) + + fastify.post('/', { + [hook]: (req, reply, doneOrPayload, done) => { + t.assert.strictEqual(checker.check, 2) + endRouteHook(doneOrPayload, done) + } + }, handler) + + fastify.post('/no', handler) + + function handler (req, reply) { + reply.send({}) + } + + fastify.inject({ + method: 'post', + url: '/' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(checker.calledTimes, 2) + + checker.calledTimes = 0 + + fastify.inject({ + method: 'post', + url: '/no' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(checker.calledTimes, 1) + testDone() + }) + }) + }) +} + +function testBeforeHandlerHook (hook) { + test(`${hook} option should be unique per route`, (t, testDone) => { + t.plan(4) + const fastify = Fastify() + + fastify.post('/', { + [hook]: (req, reply, doneOrPayload, done) => { + req.hello = 'earth' + endRouteHook(doneOrPayload, done) + } + }, (req, reply) => { + reply.send({ hello: req.hello }) + }) + + fastify.post('/no', (req, reply) => { + reply.send(req.body) + }) + + fastify.inject({ + method: 'POST', + url: '/', + payload: { hello: 'world' } + }, (err, res) => { + t.assert.ifError(err) + const payload = JSON.parse(res.payload) + t.assert.deepStrictEqual(payload, { hello: 'earth' }) + }) + + fastify.inject({ + method: 'POST', + url: '/no', + payload: { hello: 'world' } + }, (err, res) => { + t.assert.ifError(err) + const payload = JSON.parse(res.payload) + t.assert.deepStrictEqual(payload, { hello: 'world' }) + testDone() + }) + }) + + test(`${hook} option should handle errors`, (t, testDone) => { + t.plan(3) + const fastify = Fastify() + + fastify.post('/', { + [hook]: (req, reply, doneOrPayload, done) => { + endRouteHook(doneOrPayload, done, new Error('kaboom')) + } + }, (req, reply) => { + reply.send(req.body) + }) + + fastify.inject({ + method: 'POST', + url: '/', + payload: { hello: 'world' } + }, (err, res) => { + t.assert.ifError(err) + const payload = JSON.parse(res.payload) + t.assert.strictEqual(res.statusCode, 500) + t.assert.deepStrictEqual(payload, { + message: 'kaboom', + error: 'Internal Server Error', + statusCode: 500 + }) + testDone() + }) + }) + + test(`${hook} option should handle throwing objects`, (t, testDone) => { + t.plan(4) + const fastify = Fastify() + + const myError = { myError: 'kaboom' } + + fastify.setErrorHandler(async (error, request, reply) => { + t.assert.deepStrictEqual(error, myError, 'the error object throws by the user') + return reply.code(500).send({ this: 'is', my: 'error' }) + }) + + fastify.get('/', { + [hook]: async () => { + throw myError + } + }, (req, reply) => { + t.assert.fail('the handler must not be called') + }) + + fastify.inject({ + url: '/', + method: 'GET' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 500) + t.assert.deepStrictEqual(res.json(), { this: 'is', my: 'error' }) + testDone() + }) + }) + + test(`${hook} option should handle throwing objects by default`, (t, testDone) => { + t.plan(3) + const fastify = Fastify() + + fastify.get('/', { + [hook]: async () => { + // eslint-disable-next-line no-throw-literal + throw { myError: 'kaboom', message: 'i am an error' } + } + }, (req, reply) => { + t.assert.fail('the handler must not be called') + }) + + fastify.inject({ + url: '/', + method: 'GET' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 500) + t.assert.deepStrictEqual(res.json(), { myError: 'kaboom', message: 'i am an error' }) + testDone() + }) + }) + + test(`${hook} option should handle errors with custom status code`, (t, testDone) => { + t.plan(3) + const fastify = Fastify() + + fastify.post('/', { + [hook]: (req, reply, doneOrPayload, done) => { + reply.code(401) + endRouteHook(doneOrPayload, done, new Error('go away')) + } + }, (req, reply) => { + reply.send(req.body) + }) + + fastify.inject({ + method: 'POST', + url: '/', + payload: { hello: 'world' } + }, (err, res) => { + t.assert.ifError(err) + const payload = JSON.parse(res.payload) + t.assert.strictEqual(res.statusCode, 401) + t.assert.deepStrictEqual(payload, { + message: 'go away', + error: 'Unauthorized', + statusCode: 401 + }) + testDone() + }) + }) + + test(`${hook} option should keep the context`, (t, testDone) => { + t.plan(3) + const fastify = Fastify() + + fastify.decorate('foo', 42) + + fastify.post('/', { + [hook]: function (req, reply, doneOrPayload, done) { + t.assert.strictEqual(this.foo, 42) + this.foo += 1 + endRouteHook(doneOrPayload, done) + } + }, function (req, reply) { + reply.send({ foo: this.foo }) + }) + + fastify.inject({ + method: 'POST', + url: '/', + payload: { hello: 'world' } + }, (err, res) => { + t.assert.ifError(err) + const payload = JSON.parse(res.payload) + t.assert.deepStrictEqual(payload, { foo: 43 }) + testDone() + }) + }) + + test(`${hook} option should keep the context (array)`, (t, testDone) => { + t.plan(3) + const fastify = Fastify() + + fastify.decorate('foo', 42) + + fastify.post('/', { + [hook]: [function (req, reply, doneOrPayload, done) { + t.assert.strictEqual(this.foo, 42) + this.foo += 1 + endRouteHook(doneOrPayload, done) + }] + }, function (req, reply) { + reply.send({ foo: this.foo }) + }) + + fastify.inject({ + method: 'POST', + url: '/', + payload: { hello: 'world' } + }, (err, res) => { + t.assert.ifError(err) + const payload = JSON.parse(res.payload) + t.assert.deepStrictEqual(payload, { foo: 43 }) + testDone() + }) + }) +} + +testExecutionHook('preHandler') +testExecutionHook('onSend') +testExecutionHook('onRequest') +testExecutionHook('onResponse') +testExecutionHook('preValidation') +testExecutionHook('preParsing') +// hooks that comes before the handler +testBeforeHandlerHook('preHandler') +testBeforeHandlerHook('onRequest') +testBeforeHandlerHook('preValidation') +testBeforeHandlerHook('preParsing') + +test('preValidation option should be called before preHandler hook', (t, testDone) => { + t.plan(3) + const fastify = Fastify() + + fastify.addHook('preHandler', (req, reply, done) => { + t.assert.ok(req.called) + done() + }) + + fastify.post('/', { + preValidation: (req, reply, done) => { + req.called = true + done() + } + }, (req, reply) => { + reply.send(req.body) + }) + + fastify.inject({ + method: 'POST', + url: '/', + payload: { hello: 'world' } + }, (err, res) => { + t.assert.ifError(err) + const payload = JSON.parse(res.payload) + t.assert.deepStrictEqual(payload, { hello: 'world' }) + testDone() + }) +}) + +test('preSerialization option should be able to modify the payload', (t, testDone) => { + t.plan(3) + const fastify = Fastify() + + fastify.get('/only', { + preSerialization: (req, reply, payload, done) => { + done(null, { hello: 'another world' }) + } + }, (req, reply) => { + reply.send({ hello: 'world' }) + }) + + fastify.inject({ + method: 'GET', + url: '/only' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'another world' }) + testDone() + }) +}) + +test('preParsing option should be called before preValidation hook', (t, testDone) => { + t.plan(3) + const fastify = Fastify() + + fastify.addHook('preValidation', (req, reply, done) => { + t.assert.ok(req.called) + done() + }) + + fastify.post('/', { + preParsing: (req, reply, payload, done) => { + req.called = true + done() + } + }, (req, reply) => { + reply.send(req.body) + }) + + fastify.inject({ + method: 'POST', + url: '/', + payload: { hello: 'world' } + }, (err, res) => { + t.assert.ifError(err) + const payload = JSON.parse(res.payload) + t.assert.deepStrictEqual(payload, { hello: 'world' }) + testDone() + }) +}) + +test('preParsing option should be able to modify the payload', (t, testDone) => { + t.plan(3) + const fastify = Fastify() + + fastify.post('/only', { + preParsing: (req, reply, payload, done) => { + const stream = new Readable() + stream.receivedEncodedLength = parseInt(req.headers['content-length'], 10) + stream.push(JSON.stringify({ hello: 'another world' })) + stream.push(null) + done(null, stream) + } + }, (req, reply) => { + reply.send(req.body) + }) + + fastify.inject({ + method: 'POST', + url: '/only', + payload: { hello: 'world' } + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'another world' }) + testDone() + }) +}) + +test('preParsing option should be able to supply statusCode', (t, testDone) => { + t.plan(4) + const fastify = Fastify() + + fastify.post('/only', { + preParsing: async (req, reply, payload) => { + const stream = new Readable({ + read () { + const error = new Error('kaboom') + error.statusCode = 408 + this.destroy(error) + } + }) + stream.receivedEncodedLength = 20 + return stream + }, + onError: async (req, res, err) => { + t.assert.strictEqual(err.statusCode, 408) + } + }, (req, reply) => { + t.assert.fail('should not be called') + }) + + fastify.inject({ + method: 'POST', + url: '/only', + payload: { hello: 'world' } + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 408) + t.assert.deepStrictEqual(JSON.parse(res.payload), { + statusCode: 408, + error: 'Request Timeout', + message: 'kaboom' + }) + testDone() + }) +}) + +test('onRequest option should be called before preParsing', (t, testDone) => { + t.plan(3) + const fastify = Fastify() + + fastify.addHook('preParsing', (req, reply, payload, done) => { + t.assert.ok(req.called) + done() + }) + + fastify.post('/', { + onRequest: (req, reply, done) => { + req.called = true + done() + } + }, (req, reply) => { + reply.send(req.body) + }) + + fastify.inject({ + method: 'POST', + url: '/', + payload: { hello: 'world' } + }, (err, res) => { + t.assert.ifError(err) + const payload = JSON.parse(res.payload) + t.assert.deepStrictEqual(payload, { hello: 'world' }) + testDone() + }) +}) + +test('onTimeout on route', async (t) => { + t.plan(3) + const fastify = Fastify({ connectionTimeout: 500 }) + + fastify.get('/timeout', { + handler (request, reply) { }, + onTimeout (request, reply, done) { + t.assert.ok('onTimeout called') + done() + } + }) + + const address = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + try { + await fetch(`${address}/timeout`) + t.assert.fail('Should have thrown an error') + } catch (err) { + t.assert.ok(err instanceof Error) + t.assert.strictEqual(err.message, 'fetch failed') + } +}) + +test('onError on route', (t, testDone) => { + t.plan(3) + + const fastify = Fastify() + + const err = new Error('kaboom') + + fastify.get('/', + { + onError (request, reply, error, done) { + t.assert.deepStrictEqual(error, err) + done() + } + }, + (req, reply) => { + reply.send(err) + }) + + fastify.inject('/', (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { + error: 'Internal Server Error', + message: 'kaboom', + statusCode: 500 + }) + testDone() + }) +}) diff --git a/services/slides/node_modules/fastify/test/route-prefix.test.js b/services/slides/node_modules/fastify/test/route-prefix.test.js new file mode 100644 index 0000000000000000000000000000000000000000..d910a9a6d7aa291bc1a182e4e52ebb91d179f396 --- /dev/null +++ b/services/slides/node_modules/fastify/test/route-prefix.test.js @@ -0,0 +1,904 @@ +'use strict' + +const { test } = require('node:test') +const Fastify = require('..') +const { waitForCb } = require('./toolkit') + +test('Prefix options should add a prefix for all the routes inside a register / 1', (t, testDone) => { + t.plan(6) + const fastify = Fastify() + + fastify.get('/first', (req, reply) => { + reply.send({ route: '/first' }) + }) + + fastify.register(function (fastify, opts, done) { + fastify.get('/first', (req, reply) => { + reply.send({ route: '/v1/first' }) + }) + + fastify.register(function (fastify, opts, done) { + fastify.get('/first', (req, reply) => { + reply.send({ route: '/v1/v2/first' }) + }) + done() + }, { prefix: '/v2' }) + + done() + }, { prefix: '/v1' }) + + const completion = waitForCb({ steps: 3 }) + fastify.inject({ + method: 'GET', + url: '/first' + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { route: '/first' }) + completion.stepIn() + }) + + fastify.inject({ + method: 'GET', + url: '/v1/first' + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { route: '/v1/first' }) + completion.stepIn() + }) + + fastify.inject({ + method: 'GET', + url: '/v1/v2/first' + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { route: '/v1/v2/first' }) + completion.stepIn() + }) + completion.patience.then(testDone) +}) + +test('Prefix options should add a prefix for all the routes inside a register / 2', (t, testDone) => { + t.plan(4) + const fastify = Fastify() + + fastify.register(function (fastify, opts, done) { + fastify.get('/first', (req, reply) => { + reply.send({ route: '/v1/first' }) + }) + + fastify.get('/second', (req, reply) => { + reply.send({ route: '/v1/second' }) + }) + done() + }, { prefix: '/v1' }) + + const completion = waitForCb({ steps: 2 }) + fastify.inject({ + method: 'GET', + url: '/v1/first' + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { route: '/v1/first' }) + completion.stepIn() + }) + fastify.inject({ + method: 'GET', + url: '/v1/second' + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { route: '/v1/second' }) + completion.stepIn() + }) + completion.patience.then(testDone) +}) + +test('Prefix options should add a prefix for all the chained routes inside a register / 3', (t, testDone) => { + t.plan(4) + + const fastify = Fastify() + + fastify.register(function (fastify, opts, done) { + fastify + .get('/first', (req, reply) => { + reply.send({ route: '/v1/first' }) + }) + .get('/second', (req, reply) => { + reply.send({ route: '/v1/second' }) + }) + done() + }, { prefix: '/v1' }) + + const completion = waitForCb({ steps: 2 }) + fastify.inject({ + method: 'GET', + url: '/v1/first' + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { route: '/v1/first' }) + completion.stepIn() + }) + fastify.inject({ + method: 'GET', + url: '/v1/second' + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { route: '/v1/second' }) + completion.stepIn() + }) + completion.patience.then(testDone) +}) + +test('Prefix should support parameters as well', (t, testDone) => { + t.plan(2) + const fastify = Fastify() + + fastify.register(function (fastify, opts, done) { + fastify.get('/hello', (req, reply) => { + reply.send({ id: req.params.id }) + }) + done() + }, { prefix: '/v1/:id' }) + + fastify.inject({ + method: 'GET', + url: '/v1/param/hello' + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { id: 'param' }) + testDone() + }) +}) + +test('Prefix should support /', (t, testDone) => { + t.plan(2) + const fastify = Fastify() + + fastify.register(function (fastify, opts, done) { + fastify.get('/', (req, reply) => { + reply.send({ hello: 'world' }) + }) + done() + }, { prefix: '/v1' }) + + fastify.inject({ + method: 'GET', + url: '/v1' + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + testDone() + }) +}) + +test('Prefix without /', (t, testDone) => { + t.plan(2) + const fastify = Fastify() + + fastify.register(function (fastify, opts, done) { + fastify.get('/', (req, reply) => { + reply.send({ hello: 'world' }) + }) + done() + }, { prefix: 'v1' }) + + fastify.inject({ + method: 'GET', + url: '/v1' + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + testDone() + }) +}) + +test('Prefix with trailing /', (t, testDone) => { + t.plan(6) + const fastify = Fastify() + + fastify.register(function (fastify, opts, done) { + fastify.get('/route1', (req, reply) => { + reply.send({ hello: 'world1' }) + }) + fastify.get('route2', (req, reply) => { + reply.send({ hello: 'world2' }) + }) + + fastify.register(function (fastify, opts, done) { + fastify.get('/route3', (req, reply) => { + reply.send({ hello: 'world3' }) + }) + done() + }, { prefix: '/inner/' }) + + done() + }, { prefix: '/v1/' }) + + const completion = waitForCb({ steps: 3 }) + fastify.inject({ + method: 'GET', + url: '/v1/route1' + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world1' }) + completion.stepIn() + }) + fastify.inject({ + method: 'GET', + url: '/v1/route2' + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world2' }) + completion.stepIn() + }) + fastify.inject({ + method: 'GET', + url: '/v1/inner/route3' + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world3' }) + completion.stepIn() + }) + completion.patience.then(testDone) +}) + +test('Prefix works multiple levels deep', (t, testDone) => { + t.plan(2) + const fastify = Fastify() + + fastify.register(function (fastify, opts, done) { + fastify.register(function (fastify, opts, done) { + fastify.register(function (fastify, opts, done) { + fastify.register(function (fastify, opts, done) { + fastify.get('/', (req, reply) => { + reply.send({ hello: 'world' }) + }) + done() + }, { prefix: '/v3' }) + done() + }) // No prefix on this level + done() + }, { prefix: 'v2' }) + done() + }, { prefix: '/v1' }) + + fastify.inject({ + method: 'GET', + url: '/v1/v2/v3' + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + testDone() + }) +}) + +test('Different register - encapsulation check', (t, testDone) => { + t.plan(4) + const fastify = Fastify() + + fastify.get('/first', (req, reply) => { + reply.send({ route: '/first' }) + }) + + fastify.register(function (instance, opts, done) { + instance.register(function (f, opts, done) { + f.get('/', (req, reply) => { + reply.send({ route: '/v1/v2' }) + }) + done() + }, { prefix: '/v2' }) + done() + }, { prefix: '/v1' }) + + fastify.register(function (instance, opts, done) { + instance.register(function (f, opts, done) { + f.get('/', (req, reply) => { + reply.send({ route: '/v3/v4' }) + }) + done() + }, { prefix: '/v4' }) + done() + }, { prefix: '/v3' }) + + const completion = waitForCb({ steps: 2 }) + fastify.inject({ + method: 'GET', + url: '/v1/v2' + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { route: '/v1/v2' }) + completion.stepIn() + }) + fastify.inject({ + method: 'GET', + url: '/v3/v4' + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { route: '/v3/v4' }) + completion.stepIn() + }) + completion.patience.then(testDone) +}) + +test('Can retrieve prefix within encapsulated instances', (t, testDone) => { + t.plan(4) + const fastify = Fastify() + + fastify.register(function (instance, opts, done) { + instance.get('/one', function (req, reply) { + reply.send(instance.prefix) + }) + + instance.register(function (instance, opts, done) { + instance.get('/two', function (req, reply) { + reply.send(instance.prefix) + }) + done() + }, { prefix: '/v2' }) + + done() + }, { prefix: '/v1' }) + + const completion = waitForCb({ steps: 2 }) + fastify.inject({ + method: 'GET', + url: '/v1/one' + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(res.payload, '/v1') + completion.stepIn() + }) + fastify.inject({ + method: 'GET', + url: '/v1/v2/two' + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(res.payload, '/v1/v2') + completion.stepIn() + }) + completion.patience.then(testDone) +}) + +test('matches both /prefix and /prefix/ with a / route', (t, testDone) => { + t.plan(4) + const fastify = Fastify() + + fastify.register(function (fastify, opts, done) { + fastify.get('/', (req, reply) => { + reply.send({ hello: 'world' }) + }) + + done() + }, { prefix: '/prefix' }) + + const completion = waitForCb({ steps: 2 }) + fastify.inject({ + method: 'GET', + url: '/prefix' + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + completion.stepIn() + }) + fastify.inject({ + method: 'GET', + url: '/prefix/' + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + completion.stepIn() + }) + completion.patience.then(testDone) +}) + +test('prefix "/prefix/" does not match "/prefix" with a / route', (t, testDone) => { + t.plan(4) + const fastify = Fastify() + + fastify.register(function (fastify, opts, done) { + fastify.get('/', (req, reply) => { + reply.send({ hello: 'world' }) + }) + + done() + }, { prefix: '/prefix/' }) + + const completion = waitForCb({ steps: 2 }) + fastify.inject({ + method: 'GET', + url: '/prefix' + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(res.statusCode, 404) + completion.stepIn() + }) + fastify.inject({ + method: 'GET', + url: '/prefix/' + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + completion.stepIn() + }) + completion.patience.then(testDone) +}) + +test('matches both /prefix and /prefix/ with a / route - ignoreTrailingSlash: true', (t, testDone) => { + t.plan(4) + const fastify = Fastify({ + ignoreTrailingSlash: true + }) + + fastify.register(function (fastify, opts, done) { + fastify.get('/', (req, reply) => { + reply.send({ hello: 'world' }) + }) + + done() + }, { prefix: '/prefix' }) + + const completion = waitForCb({ steps: 2 }) + fastify.inject({ + method: 'GET', + url: '/prefix' + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + completion.stepIn() + }) + fastify.inject({ + method: 'GET', + url: '/prefix/' + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + completion.stepIn() + }) + completion.patience.then(testDone) +}) + +test('matches both /prefix and /prefix/ with a / route - ignoreDuplicateSlashes: true', (t, testDone) => { + t.plan(4) + const fastify = Fastify({ + ignoreDuplicateSlashes: true + }) + + fastify.register(function (fastify, opts, done) { + fastify.get('/', (req, reply) => { + reply.send({ hello: 'world' }) + }) + + done() + }, { prefix: '/prefix' }) + + const completion = waitForCb({ steps: 2 }) + fastify.inject({ + method: 'GET', + url: '/prefix' + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + completion.stepIn() + }) + fastify.inject({ + method: 'GET', + url: '/prefix/' + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + completion.stepIn() + }) + completion.patience.then(testDone) +}) + +test('matches both /prefix and /prefix/ with a / route - prefixTrailingSlash: "both", ignoreTrailingSlash: false', (t, testDone) => { + t.plan(4) + const fastify = Fastify({ + ignoreTrailingSlash: false + }) + + fastify.register(function (fastify, opts, done) { + fastify.route({ + method: 'GET', + url: '/', + prefixTrailingSlash: 'both', + handler: (req, reply) => { + reply.send({ hello: 'world' }) + } + }) + + done() + }, { prefix: '/prefix' }) + + const completion = waitForCb({ steps: 2 }) + fastify.inject({ + method: 'GET', + url: '/prefix' + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + completion.stepIn() + }) + fastify.inject({ + method: 'GET', + url: '/prefix/' + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + completion.stepIn() + }) + completion.patience.then(testDone) +}) + +test('matches both /prefix and /prefix/ with a / route - prefixTrailingSlash: "both", ignoreDuplicateSlashes: false', (t, testDone) => { + t.plan(4) + const fastify = Fastify({ + ignoreDuplicateSlashes: false + }) + + fastify.register(function (fastify, opts, done) { + fastify.route({ + method: 'GET', + url: '/', + prefixTrailingSlash: 'both', + handler: (req, reply) => { + reply.send({ hello: 'world' }) + } + }) + + done() + }, { prefix: '/prefix' }) + + const completion = waitForCb({ steps: 2 }) + fastify.inject({ + method: 'GET', + url: '/prefix' + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + completion.stepIn() + }) + fastify.inject({ + method: 'GET', + url: '/prefix/' + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + completion.stepIn() + }) + completion.patience.then(testDone) +}) + +test('matches both /prefix and /prefix/ with a / route - ignoreTrailingSlash: true, ignoreDuplicateSlashes: true', (t, testDone) => { + t.plan(4) + const fastify = Fastify({ + ignoreTrailingSlash: true, + ignoreDuplicateSlashes: true + }) + + fastify.register(function (fastify, opts, done) { + fastify.get('/', (req, reply) => { + reply.send({ hello: 'world' }) + }) + + done() + }, { prefix: '/prefix' }) + + const completion = waitForCb({ steps: 2 }) + fastify.inject({ + method: 'GET', + url: '/prefix' + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + completion.stepIn() + }) + fastify.inject({ + method: 'GET', + url: '/prefix/' + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + completion.stepIn() + }) + completion.patience.then(testDone) +}) + +test('matches both /prefix and /prefix/ with a / route - ignoreTrailingSlash: true, ignoreDuplicateSlashes: false', (t, testDone) => { + t.plan(4) + const fastify = Fastify({ + ignoreTrailingSlash: true, + ignoreDuplicateSlashes: false + }) + + fastify.register(function (fastify, opts, done) { + fastify.get('/', (req, reply) => { + reply.send({ hello: 'world' }) + }) + + done() + }, { prefix: '/prefix' }) + + const completion = waitForCb({ steps: 2 }) + fastify.inject({ + method: 'GET', + url: '/prefix' + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + completion.stepIn() + }) + fastify.inject({ + method: 'GET', + url: '/prefix/' + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + completion.stepIn() + }) + completion.patience.then(testDone) +}) + +test('returns 404 status code with /prefix/ and / route - prefixTrailingSlash: "both" (default), ignoreTrailingSlash: true', (t, testDone) => { + t.plan(2) + const fastify = Fastify({ + ignoreTrailingSlash: true + }) + + fastify.register(function (fastify, opts, done) { + fastify.route({ + method: 'GET', + url: '/', + handler: (req, reply) => { + reply.send({ hello: 'world' }) + } + }) + + done() + }, { prefix: '/prefix/' }) + + fastify.inject({ + method: 'GET', + url: '/prefix//' + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { + error: 'Not Found', + message: 'Route GET:/prefix// not found', + statusCode: 404 + }) + testDone() + }) +}) + +test('matches both /prefix and /prefix/ with a / route - prefixTrailingSlash: "both", ignoreDuplicateSlashes: true', (t, testDone) => { + t.plan(2) + const fastify = Fastify({ + ignoreDuplicateSlashes: true + }) + + fastify.register(function (fastify, opts, done) { + fastify.route({ + method: 'GET', + url: '/', + handler: (req, reply) => { + reply.send({ hello: 'world' }) + } + }) + + done() + }, { prefix: '/prefix/' }) + + fastify.inject({ + method: 'GET', + url: '/prefix//' + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + testDone() + }) +}) + +test('matches both /prefix and /prefix/ with a / route - prefixTrailingSlash: "both", ignoreTrailingSlash: true, ignoreDuplicateSlashes: true', (t, testDone) => { + t.plan(2) + const fastify = Fastify({ + ignoreTrailingSlash: true, + ignoreDuplicateSlashes: true + }) + + fastify.register(function (fastify, opts, done) { + fastify.route({ + method: 'GET', + url: '/', + handler: (req, reply) => { + reply.send({ hello: 'world' }) + } + }) + + done() + }, { prefix: '/prefix/' }) + + fastify.inject({ + method: 'GET', + url: '/prefix//' + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + testDone() + }) +}) + +test('matches both /prefix and /prefix/ with a / route - prefixTrailingSlash: "both", ignoreDuplicateSlashes: true', (t, testDone) => { + t.plan(2) + const fastify = Fastify({ + ignoreTrailingSlash: true, + ignoreDuplicateSlashes: true + }) + + fastify.register(function (fastify, opts, done) { + fastify.route({ + method: 'GET', + url: '/', + handler: (req, reply) => { + reply.send({ hello: 'world' }) + } + }) + + done() + }, { prefix: '/prefix/' }) + + fastify.inject({ + method: 'GET', + url: '/prefix//' + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + testDone() + }) +}) + +test('matches only /prefix with a / route - prefixTrailingSlash: "no-slash", ignoreTrailingSlash: false', (t, testDone) => { + t.plan(4) + const fastify = Fastify({ + ignoreTrailingSlash: false + }) + + fastify.register(function (fastify, opts, done) { + fastify.route({ + method: 'GET', + url: '/', + prefixTrailingSlash: 'no-slash', + handler: (req, reply) => { + reply.send({ hello: 'world' }) + } + }) + + done() + }, { prefix: '/prefix' }) + + const completion = waitForCb({ steps: 2 }) + fastify.inject({ + method: 'GET', + url: '/prefix' + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + completion.stepIn() + }) + fastify.inject({ + method: 'GET', + url: '/prefix/' + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload).statusCode, 404) + completion.stepIn() + }) + completion.patience.then(testDone) +}) + +test('matches only /prefix with a / route - prefixTrailingSlash: "no-slash", ignoreDuplicateSlashes: false', (t, testDone) => { + t.plan(4) + const fastify = Fastify({ + ignoreDuplicateSlashes: false + }) + + fastify.register(function (fastify, opts, done) { + fastify.route({ + method: 'GET', + url: '/', + prefixTrailingSlash: 'no-slash', + handler: (req, reply) => { + reply.send({ hello: 'world' }) + } + }) + + done() + }, { prefix: '/prefix' }) + + const completion = waitForCb({ steps: 2 }) + fastify.inject({ + method: 'GET', + url: '/prefix' + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + completion.stepIn() + }) + fastify.inject({ + method: 'GET', + url: '/prefix/' + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload).statusCode, 404) + completion.stepIn() + }) + completion.patience.then(testDone) +}) + +test('matches only /prefix/ with a / route - prefixTrailingSlash: "slash", ignoreTrailingSlash: false', (t, testDone) => { + t.plan(4) + const fastify = Fastify({ + ignoreTrailingSlash: false + }) + + fastify.register(function (fastify, opts, done) { + fastify.route({ + method: 'GET', + url: '/', + prefixTrailingSlash: 'slash', + handler: (req, reply) => { + reply.send({ hello: 'world' }) + } + }) + + done() + }, { prefix: '/prefix' }) + + const completion = waitForCb({ steps: 2 }) + fastify.inject({ + method: 'GET', + url: '/prefix/' + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + completion.stepIn() + }) + fastify.inject({ + method: 'GET', + url: '/prefix' + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload).statusCode, 404) + completion.stepIn() + }) + completion.patience.then(testDone) +}) + +test('calls onRoute only once when prefixing', async t => { + t.plan(1) + const fastify = Fastify({ + ignoreTrailingSlash: false, + exposeHeadRoutes: false + }) + + let onRouteCalled = 0 + fastify.register(function (fastify, opts, next) { + fastify.addHook('onRoute', () => { + onRouteCalled++ + }) + + fastify.route({ + method: 'GET', + url: '/', + prefixTrailingSlash: 'both', + handler: (req, reply) => { + reply.send({ hello: 'world' }) + } + }) + + next() + }, { prefix: '/prefix' }) + + await fastify.ready() + + t.assert.deepStrictEqual(onRouteCalled, 1) +}) diff --git a/services/slides/node_modules/fastify/test/route-shorthand.test.js b/services/slides/node_modules/fastify/test/route-shorthand.test.js new file mode 100644 index 0000000000000000000000000000000000000000..46e1b3662be6ee3f61a866038cf1816718ed649e --- /dev/null +++ b/services/slides/node_modules/fastify/test/route-shorthand.test.js @@ -0,0 +1,48 @@ +'use strict' + +const { describe, test } = require('node:test') +const { Client } = require('undici') +const Fastify = require('..') + +describe('route-shorthand', () => { + const methodsReader = new Fastify() + const supportedMethods = methodsReader.supportedMethods + + for (const method of supportedMethods) { + test(`route-shorthand - ${method.toLowerCase()}`, async (t) => { + t.plan(2) + const fastify = new Fastify() + fastify[method.toLowerCase()]('/', (req, reply) => { + t.assert.strictEqual(req.method, method) + reply.send() + }) + await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const instance = new Client(`http://localhost:${fastify.server.address().port}`) + + const response = await instance.request({ path: '/', method }) + t.assert.strictEqual(response.statusCode, 200) + }) + } + + test('route-shorthand - all', async (t) => { + t.plan(2 * supportedMethods.length) + const fastify = new Fastify() + let currentMethod = '' + fastify.all('/', function (req, reply) { + t.assert.strictEqual(req.method, currentMethod) + reply.send() + }) + await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + for (const method of supportedMethods) { + currentMethod = method + const instance = new Client(`http://localhost:${fastify.server.address().port}`) + + const response = await instance.request({ path: '/', method }) + t.assert.strictEqual(response.statusCode, 200) + } + }) +}) diff --git a/services/slides/node_modules/fastify/test/route.1.test.js b/services/slides/node_modules/fastify/test/route.1.test.js new file mode 100644 index 0000000000000000000000000000000000000000..36907519861c5b504bc29e75a6ac701a745b6e5a --- /dev/null +++ b/services/slides/node_modules/fastify/test/route.1.test.js @@ -0,0 +1,259 @@ +'use strict' + +const { test } = require('node:test') +const Fastify = require('..') +const { + FST_ERR_INSTANCE_ALREADY_LISTENING, + FST_ERR_ROUTE_METHOD_INVALID +} = require('../lib/errors') +const { getServerUrl } = require('./helper') + +test('route', async t => { + t.plan(10) + + await t.test('route - get', async (t) => { + t.plan(4) + + const fastify = Fastify() + t.assert.doesNotThrow(() => + fastify.route({ + method: 'GET', + url: '/', + schema: { + response: { + '2xx': { + type: 'object', + properties: { + hello: { + type: 'string' + } + } + } + } + }, + handler: function (req, reply) { + reply.send({ hello: 'world' }) + } + }) + ) + + await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) + + const response = await fetch(getServerUrl(fastify) + '/') + t.assert.ok(response.ok) + t.assert.strictEqual(response.status, 200) + t.assert.deepStrictEqual(await response.json(), { hello: 'world' }) + }) + + await t.test('missing schema - route', async (t) => { + t.plan(4) + + const fastify = Fastify() + t.assert.doesNotThrow(() => + fastify.route({ + method: 'GET', + url: '/missing', + handler: function (req, reply) { + reply.send({ hello: 'world' }) + } + }) + ) + + await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) + + const response = await fetch(getServerUrl(fastify) + '/missing') + t.assert.ok(response.ok) + t.assert.strictEqual(response.status, 200) + t.assert.deepStrictEqual(await response.json(), { hello: 'world' }) + }) + + await t.test('invalid handler attribute - route', t => { + t.plan(1) + + const fastify = Fastify() + t.assert.throws(() => fastify.get('/', { handler: 'not a function' }, () => { })) + }) + + await t.test('Add Multiple methods per route all uppercase', async (t) => { + t.plan(7) + + const fastify = Fastify() + t.assert.doesNotThrow(() => + fastify.route({ + method: ['GET', 'DELETE'], + url: '/multiple', + handler: function (req, reply) { + reply.send({ hello: 'world' }) + } + })) + + await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) + + const getResponse = await fetch(getServerUrl(fastify) + '/multiple') + t.assert.ok(getResponse.ok) + t.assert.strictEqual(getResponse.status, 200) + t.assert.deepStrictEqual(await getResponse.json(), { hello: 'world' }) + + const deleteResponse = await fetch(getServerUrl(fastify) + '/multiple', { method: 'DELETE' }) + t.assert.ok(deleteResponse.ok) + t.assert.strictEqual(deleteResponse.status, 200) + t.assert.deepStrictEqual(await deleteResponse.json(), { hello: 'world' }) + }) + + await t.test('Add Multiple methods per route all lowercase', async (t) => { + t.plan(7) + + const fastify = Fastify() + t.assert.doesNotThrow(() => + fastify.route({ + method: ['get', 'delete'], + url: '/multiple', + handler: function (req, reply) { + reply.send({ hello: 'world' }) + } + })) + + await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) + + const getResponse = await fetch(getServerUrl(fastify) + '/multiple') + t.assert.ok(getResponse.ok) + t.assert.strictEqual(getResponse.status, 200) + t.assert.deepStrictEqual(await getResponse.json(), { hello: 'world' }) + + const deleteResponse = await fetch(getServerUrl(fastify) + '/multiple', { method: 'DELETE' }) + t.assert.ok(deleteResponse.ok) + t.assert.strictEqual(deleteResponse.status, 200) + t.assert.deepStrictEqual(await deleteResponse.json(), { hello: 'world' }) + }) + + await t.test('Add Multiple methods per route mixed uppercase and lowercase', async (t) => { + t.plan(7) + + const fastify = Fastify() + t.assert.doesNotThrow(() => + fastify.route({ + method: ['GET', 'delete'], + url: '/multiple', + handler: function (req, reply) { + reply.send({ hello: 'world' }) + } + })) + + await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) + + const getResponse = await fetch(getServerUrl(fastify) + '/multiple') + t.assert.ok(getResponse.ok) + t.assert.strictEqual(getResponse.status, 200) + t.assert.deepStrictEqual(await getResponse.json(), { hello: 'world' }) + + const deleteResponse = await fetch(getServerUrl(fastify) + '/multiple', { method: 'DELETE' }) + t.assert.ok(deleteResponse.ok) + t.assert.strictEqual(deleteResponse.status, 200) + t.assert.deepStrictEqual(await deleteResponse.json(), { hello: 'world' }) + }) + + t.test('Add invalid Multiple methods per route', t => { + t.plan(1) + + const fastify = Fastify() + t.assert.throws(() => + fastify.route({ + method: ['GET', 1], + url: '/invalid-method', + handler: function (req, reply) { + reply.send({ hello: 'world' }) + } + }), new FST_ERR_ROUTE_METHOD_INVALID()) + }) + + await t.test('Add method', t => { + t.plan(1) + + const fastify = Fastify() + t.assert.throws(() => + fastify.route({ + method: 1, + url: '/invalid-method', + handler: function (req, reply) { + reply.send({ hello: 'world' }) + } + }), new FST_ERR_ROUTE_METHOD_INVALID()) + }) + + await t.test('Add additional multiple methods to existing route', async (t) => { + t.plan(7) + + const fastify = Fastify() + t.assert.doesNotThrow(() => { + fastify.get('/add-multiple', function (req, reply) { + reply.send({ hello: 'Bob!' }) + }) + fastify.route({ + method: ['PUT', 'DELETE'], + url: '/add-multiple', + handler: function (req, reply) { + reply.send({ hello: 'world' }) + } + }) + }) + + await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) + + const putResponse = await fetch(getServerUrl(fastify) + '/add-multiple', { method: 'PUT' }) + t.assert.ok(putResponse.ok) + t.assert.strictEqual(putResponse.status, 200) + t.assert.deepStrictEqual(await putResponse.json(), { hello: 'world' }) + + const deleteResponse = await fetch(getServerUrl(fastify) + '/add-multiple', { method: 'DELETE' }) + t.assert.ok(deleteResponse.ok) + t.assert.strictEqual(deleteResponse.status, 200) + t.assert.deepStrictEqual(await deleteResponse.json(), { hello: 'world' }) + }) + + await t.test('cannot add another route after binding', async (t) => { + t.plan(1) + + const fastify = Fastify() + + await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) + + t.assert.throws(() => fastify.route({ + method: 'GET', + url: '/another-get-route', + handler: function (req, reply) { + reply.send({ hello: 'world' }) + } + }), new FST_ERR_INSTANCE_ALREADY_LISTENING('Cannot add route!')) + }) +}) + +test('invalid schema - route', (t, done) => { + t.plan(3) + + const fastify = Fastify() + fastify.route({ + handler: () => { }, + method: 'GET', + url: '/invalid', + schema: { + querystring: { + id: 'string' + } + } + }) + fastify.after(err => { + t.assert.ok(!err, 'the error is throw on preReady') + }) + fastify.ready(err => { + t.assert.strictEqual(err.code, 'FST_ERR_SCH_VALIDATION_BUILD') + t.assert.match(err.message, /Failed building the validation schema for GET: \/invalid/) + done() + }) +}) diff --git a/services/slides/node_modules/fastify/test/route.2.test.js b/services/slides/node_modules/fastify/test/route.2.test.js new file mode 100644 index 0000000000000000000000000000000000000000..f5d305e071249e5b5db9de26f27a410ff0994b7c --- /dev/null +++ b/services/slides/node_modules/fastify/test/route.2.test.js @@ -0,0 +1,100 @@ +'use strict' + +const { test } = require('node:test') +const Fastify = require('../fastify') + +test('same route definition object on multiple prefixes', async t => { + t.plan(2) + + const routeObject = { + handler: () => { }, + method: 'GET', + url: '/simple' + } + + const fastify = Fastify({ exposeHeadRoutes: false }) + + fastify.register(async function (f) { + f.addHook('onRoute', (routeOptions) => { + t.assert.strictEqual(routeOptions.url, '/v1/simple') + }) + f.route(routeObject) + }, { prefix: '/v1' }) + fastify.register(async function (f) { + f.addHook('onRoute', (routeOptions) => { + t.assert.strictEqual(routeOptions.url, '/v2/simple') + }) + f.route(routeObject) + }, { prefix: '/v2' }) + + await fastify.ready() +}) + +test('path can be specified in place of uri', (t, done) => { + t.plan(3) + const fastify = Fastify() + + fastify.route({ + method: 'GET', + path: '/path', + handler: function (req, reply) { + reply.send({ hello: 'world' }) + } + }) + + const reqOpts = { + method: 'GET', + url: '/path' + } + + fastify.inject(reqOpts, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + done() + }) +}) + +test('invalid bodyLimit option - route', t => { + t.plan(2) + const fastify = Fastify() + + try { + fastify.route({ + bodyLimit: false, + method: 'PUT', + handler: () => null + }) + t.assert.fail('bodyLimit must be an integer') + } catch (err) { + t.assert.strictEqual(err.message, "'bodyLimit' option must be an integer > 0. Got 'false'") + } + + try { + fastify.post('/url', { bodyLimit: 10000.1 }, () => null) + t.assert.fail('bodyLimit must be an integer') + } catch (err) { + t.assert.strictEqual(err.message, "'bodyLimit' option must be an integer > 0. Got '10000.1'") + } +}) + +test('handler function in options of shorthand route should works correctly', (t, done) => { + t.plan(3) + + const fastify = Fastify() + fastify.get('/foo', { + handler: (req, reply) => { + reply.send({ hello: 'world' }) + } + }) + + fastify.inject({ + method: 'GET', + url: '/foo' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + done() + }) +}) diff --git a/services/slides/node_modules/fastify/test/route.3.test.js b/services/slides/node_modules/fastify/test/route.3.test.js new file mode 100644 index 0000000000000000000000000000000000000000..d324b972beffcf28faf6a633416ef4433dac724d --- /dev/null +++ b/services/slides/node_modules/fastify/test/route.3.test.js @@ -0,0 +1,213 @@ +'use strict' + +const { test } = require('node:test') +const joi = require('joi') +const Fastify = require('..') + +test('does not mutate joi schemas', (t, done) => { + t.plan(5) + + const fastify = Fastify() + function validatorCompiler ({ schema, method, url, httpPart }) { + // Needed to extract the params part, + // without the JSON-schema encapsulation + // that is automatically added by the short + // form of params. + schema = joi.object(schema.properties) + + return validateHttpData + + function validateHttpData (data) { + return schema.validate(data) + } + } + + fastify.setValidatorCompiler(validatorCompiler) + + fastify.route({ + path: '/foo/:an_id', + method: 'GET', + schema: { + params: { an_id: joi.number() } + }, + handler (req, res) { + t.assert.strictEqual(Object.keys(req.params).length, 1) + t.assert.strictEqual(req.params.an_id, '42') + res.send({ hello: 'world' }) + } + }) + + fastify.inject({ + method: 'GET', + url: '/foo/42' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(res.json(), { hello: 'world' }) + done() + }) +}) + +test('multiple routes with one schema', (t, done) => { + t.plan(2) + + const fastify = Fastify() + + const schema = { + query: { + type: 'object', + properties: { + id: { type: 'number' } + } + } + } + + fastify.route({ + schema, + method: 'GET', + path: '/first/:id', + handler (req, res) { + res.send({ hello: 'world' }) + } + }) + + fastify.route({ + schema, + method: 'GET', + path: '/second/:id', + handler (req, res) { + res.send({ hello: 'world' }) + } + }) + + fastify.ready(error => { + t.assert.ifError(error) + t.assert.deepStrictEqual(schema, schema) + done() + }) +}) + +test('route error handler overrides default error handler', (t, done) => { + t.plan(4) + + const fastify = Fastify() + + const customRouteErrorHandler = (error, request, reply) => { + t.assert.strictEqual(error.message, 'Wrong Pot Error') + + reply.code(418).send({ + message: 'Make a brew', + statusCode: 418, + error: 'Wrong Pot Error' + }) + } + + fastify.route({ + method: 'GET', + path: '/coffee', + handler: (req, res) => { + res.send(new Error('Wrong Pot Error')) + }, + errorHandler: customRouteErrorHandler + }) + + fastify.inject({ + method: 'GET', + url: '/coffee' + }, (error, res) => { + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 418) + t.assert.deepStrictEqual(res.json(), { + message: 'Make a brew', + statusCode: 418, + error: 'Wrong Pot Error' + }) + done() + }) +}) + +test('route error handler does not affect other routes', (t, done) => { + t.plan(3) + + const fastify = Fastify() + + const customRouteErrorHandler = (error, request, reply) => { + t.assert.strictEqual(error.message, 'Wrong Pot Error') + + reply.code(418).send({ + message: 'Make a brew', + statusCode: 418, + error: 'Wrong Pot Error' + }) + } + + fastify.route({ + method: 'GET', + path: '/coffee', + handler: (req, res) => { + res.send(new Error('Wrong Pot Error')) + }, + errorHandler: customRouteErrorHandler + }) + + fastify.route({ + method: 'GET', + path: '/tea', + handler: (req, res) => { + res.send(new Error('No tea today')) + } + }) + + fastify.inject({ + method: 'GET', + url: '/tea' + }, (error, res) => { + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 500) + t.assert.deepStrictEqual(res.json(), { + message: 'No tea today', + statusCode: 500, + error: 'Internal Server Error' + }) + done() + }) +}) + +test('async error handler for a route', (t, done) => { + t.plan(4) + + const fastify = Fastify() + + const customRouteErrorHandler = async (error, request, reply) => { + t.assert.strictEqual(error.message, 'Delayed Pot Error') + reply.code(418) + return { + message: 'Make a brew sometime later', + statusCode: 418, + error: 'Delayed Pot Error' + } + } + + fastify.route({ + method: 'GET', + path: '/late-coffee', + handler: (req, res) => { + res.send(new Error('Delayed Pot Error')) + }, + errorHandler: customRouteErrorHandler + }) + + fastify.inject({ + method: 'GET', + url: '/late-coffee' + }, (error, res) => { + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 418) + t.assert.deepStrictEqual(res.json(), { + message: 'Make a brew sometime later', + statusCode: 418, + error: 'Delayed Pot Error' + }) + done() + }) +}) diff --git a/services/slides/node_modules/fastify/test/route.4.test.js b/services/slides/node_modules/fastify/test/route.4.test.js new file mode 100644 index 0000000000000000000000000000000000000000..38d48de8488f01e4c36bb8614703c4dd9b3769e1 --- /dev/null +++ b/services/slides/node_modules/fastify/test/route.4.test.js @@ -0,0 +1,127 @@ +'use strict' + +const { test } = require('node:test') +const Fastify = require('..') + +test('route error handler overrides global custom error handler', async t => { + t.plan(3) + + const fastify = Fastify() + + const customGlobalErrorHandler = (error, request, reply) => { + t.assert.ifError(error) + reply.code(429).send({ message: 'Too much coffee' }) + } + + const customRouteErrorHandler = (error, request, reply) => { + t.assert.strictEqual(error.message, 'Wrong Pot Error') + reply.code(418).send({ + message: 'Make a brew', + statusCode: 418, + error: 'Wrong Pot Error' + }) + } + + fastify.setErrorHandler(customGlobalErrorHandler) + + fastify.route({ + method: 'GET', + path: '/more-coffee', + handler: (req, res) => { + res.send(new Error('Wrong Pot Error')) + }, + errorHandler: customRouteErrorHandler + }) + + const res = await fastify.inject({ + method: 'GET', + url: '/more-coffee' + }) + t.assert.strictEqual(res.statusCode, 418) + t.assert.deepStrictEqual(JSON.parse(res.payload), { + message: 'Make a brew', + statusCode: 418, + error: 'Wrong Pot Error' + }) +}) + +test('throws when route with empty url', async t => { + t.plan(1) + + const fastify = Fastify() + try { + fastify.route({ + method: 'GET', + url: '', + handler: (req, res) => { + res.send('hi!') + } + }) + } catch (err) { + t.assert.strictEqual(err.message, 'The path could not be empty') + } +}) + +test('throws when route with empty url in shorthand declaration', async t => { + t.plan(1) + + const fastify = Fastify() + try { + fastify.get( + '', + async function handler () { return {} } + ) + } catch (err) { + t.assert.strictEqual(err.message, 'The path could not be empty') + } +}) + +test('throws when route-level error handler is not a function', t => { + t.plan(1) + + const fastify = Fastify() + + try { + fastify.route({ + method: 'GET', + url: '/tea', + handler: (req, res) => { + res.send('hi!') + }, + errorHandler: 'teapot' + }) + } catch (err) { + t.assert.strictEqual(err.message, 'Error Handler for GET:/tea route, if defined, must be a function') + } +}) + +test('route child logger factory overrides default child logger factory', async t => { + t.plan(2) + + const fastify = Fastify() + + const customRouteChildLogger = (logger, bindings, opts, req) => { + const child = logger.child(bindings, opts) + child.customLog = function (message) { + t.assert.strictEqual(message, 'custom') + } + return child + } + + fastify.route({ + method: 'GET', + path: '/coffee', + handler: (req, res) => { + req.log.customLog('custom') + res.send() + }, + childLoggerFactory: customRouteChildLogger + }) + + const res = await fastify.inject({ + method: 'GET', + url: '/coffee' + }) + + t.assert.strictEqual(res.statusCode, 200) +}) diff --git a/services/slides/node_modules/fastify/test/route.5.test.js b/services/slides/node_modules/fastify/test/route.5.test.js new file mode 100644 index 0000000000000000000000000000000000000000..edcf7712e6e08efa372fea33002bfe69349cb9f7 --- /dev/null +++ b/services/slides/node_modules/fastify/test/route.5.test.js @@ -0,0 +1,211 @@ +'use strict' + +const { test } = require('node:test') +const Fastify = require('..') + +test('route child logger factory does not affect other routes', async t => { + t.plan(4) + + const fastify = Fastify() + + const customRouteChildLogger = (logger, bindings, opts, req) => { + const child = logger.child(bindings, opts) + child.customLog = function (message) { + t.assert.strictEqual(message, 'custom') + } + return child + } + + fastify.route({ + method: 'GET', + path: '/coffee', + handler: (req, res) => { + req.log.customLog('custom') + res.send() + }, + childLoggerFactory: customRouteChildLogger + }) + + fastify.route({ + method: 'GET', + path: '/tea', + handler: (req, res) => { + t.assert.ok(req.log.customLog instanceof Function) + res.send() + } + }) + + let res = await fastify.inject({ + method: 'GET', + url: '/coffee' + }) + t.assert.strictEqual(res.statusCode, 200) + + res = await fastify.inject({ + method: 'GET', + url: '/tea' + }) + t.assert.strictEqual(res.statusCode, 200) +}) +test('route child logger factory overrides global custom error handler', async t => { + t.plan(4) + + const fastify = Fastify() + + const customGlobalChildLogger = (logger, bindings, opts, req) => { + const child = logger.child(bindings, opts) + child.globalLog = function (message) { + t.assert.strictEqual(message, 'global') + } + return child + } + const customRouteChildLogger = (logger, bindings, opts, req) => { + const child = logger.child(bindings, opts) + child.customLog = function (message) { + t.assert.strictEqual(message, 'custom') + } + return child + } + + fastify.setChildLoggerFactory(customGlobalChildLogger) + + fastify.route({ + method: 'GET', + path: '/coffee', + handler: (req, res) => { + req.log.customLog('custom') + res.send() + }, + childLoggerFactory: customRouteChildLogger + }) + fastify.route({ + method: 'GET', + path: '/more-coffee', + handler: (req, res) => { + req.log.globalLog('global') + res.send() + } + }) + + let res = await fastify.inject({ + method: 'GET', + url: '/coffee' + }) + t.assert.strictEqual(res.statusCode, 200) + + res = await fastify.inject({ + method: 'GET', + url: '/more-coffee' + }) + t.assert.strictEqual(res.statusCode, 200) +}) + +test('Creates a HEAD route for each GET one (default)', async t => { + t.plan(6) + + const fastify = Fastify() + + fastify.route({ + method: 'GET', + path: '/more-coffee', + handler: (req, reply) => { + reply.send({ here: 'is coffee' }) + } + }) + + fastify.route({ + method: 'GET', + path: '/some-light', + handler: (req, reply) => { + reply.send('Get some light!') + } + }) + + let res = await fastify.inject({ + method: 'HEAD', + url: '/more-coffee' + }) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['content-type'], 'application/json; charset=utf-8') + t.assert.deepStrictEqual(res.body, '') + + res = await fastify.inject({ + method: 'HEAD', + url: '/some-light' + }) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['content-type'], 'text/plain; charset=utf-8') + t.assert.strictEqual(res.body, '') +}) + +test('Do not create a HEAD route for each GET one (exposeHeadRoutes: false)', async t => { + t.plan(2) + + const fastify = Fastify({ exposeHeadRoutes: false }) + + fastify.route({ + method: 'GET', + path: '/more-coffee', + handler: (req, reply) => { + reply.send({ here: 'is coffee' }) + } + }) + + fastify.route({ + method: 'GET', + path: '/some-light', + handler: (req, reply) => { + reply.send('Get some light!') + } + }) + + let res = await fastify.inject({ + method: 'HEAD', + url: '/more-coffee' + }) + t.assert.strictEqual(res.statusCode, 404) + + res = await fastify.inject({ + method: 'HEAD', + url: '/some-light' + }) + t.assert.strictEqual(res.statusCode, 404) +}) + +test('Creates a HEAD route for each GET one', async t => { + t.plan(6) + + const fastify = Fastify({ exposeHeadRoutes: true }) + + fastify.route({ + method: 'GET', + path: '/more-coffee', + handler: (req, reply) => { + reply.send({ here: 'is coffee' }) + } + }) + + fastify.route({ + method: 'GET', + path: '/some-light', + handler: (req, reply) => { + reply.send('Get some light!') + } + }) + + let res = await fastify.inject({ + method: 'HEAD', + url: '/more-coffee' + }) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['content-type'], 'application/json; charset=utf-8') + t.assert.deepStrictEqual(res.body, '') + + res = await fastify.inject({ + method: 'HEAD', + url: '/some-light' + }) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['content-type'], 'text/plain; charset=utf-8') + t.assert.strictEqual(res.body, '') +}) diff --git a/services/slides/node_modules/fastify/test/route.6.test.js b/services/slides/node_modules/fastify/test/route.6.test.js new file mode 100644 index 0000000000000000000000000000000000000000..07ea4a03654b763fdedfaffd409df7caa3ca01ce --- /dev/null +++ b/services/slides/node_modules/fastify/test/route.6.test.js @@ -0,0 +1,306 @@ +'use strict' + +const stream = require('node:stream') +const { ReadableStream } = require('node:stream/web') +const { test } = require('node:test') +const Fastify = require('..') + +test('Creates a HEAD route for a GET one with prefixTrailingSlash', async (t) => { + t.plan(1) + + const fastify = Fastify() + + const arr = [] + fastify.register((instance, opts, next) => { + instance.addHook('onRoute', (routeOptions) => { + arr.push(`${routeOptions.method} ${routeOptions.url}`) + }) + + instance.route({ + method: 'GET', + path: '/', + exposeHeadRoute: true, + prefixTrailingSlash: 'both', + handler: (req, reply) => { + reply.send({ here: 'is coffee' }) + } + }) + + next() + }, { prefix: '/v1' }) + + await fastify.ready() + + t.assert.ok(true) +}) + +test('Will not create a HEAD route that is not GET', async t => { + t.plan(8) + + const fastify = Fastify({ exposeHeadRoutes: true }) + + fastify.route({ + method: 'GET', + path: '/more-coffee', + handler: (req, reply) => { + reply.send({ here: 'is coffee' }) + } + }) + + fastify.route({ + method: 'GET', + path: '/some-light', + handler: (req, reply) => { + reply.send() + } + }) + + fastify.route({ + method: 'POST', + path: '/something', + handler: (req, reply) => { + reply.send({ look: 'It is something!' }) + } + }) + + let res = await fastify.inject({ + method: 'HEAD', + url: '/more-coffee' + }) + + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['content-type'], 'application/json; charset=utf-8') + t.assert.deepStrictEqual(res.body, '') + + res = await fastify.inject({ + method: 'HEAD', + url: '/some-light' + }) + + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['content-type'], undefined) + t.assert.strictEqual(res.headers['content-length'], '0') + t.assert.strictEqual(res.body, '') + + res = await fastify.inject({ + method: 'HEAD', + url: '/something' + }) + + t.assert.strictEqual(res.statusCode, 404) +}) + +test('HEAD route should handle properly each response type', async t => { + t.plan(24) + + const fastify = Fastify({ exposeHeadRoutes: true }) + const resString = 'Found me!' + const resJSON = { here: 'is Johnny' } + const resBuffer = Buffer.from('I am a buffer!') + const resStream = stream.Readable.from('I am a stream!') + const resWebStream = ReadableStream.from('I am a web stream!') + + fastify.route({ + method: 'GET', + path: '/json', + handler: (req, reply) => { + reply.send(resJSON) + } + }) + + fastify.route({ + method: 'GET', + path: '/string', + handler: (req, reply) => { + reply.send(resString) + } + }) + + fastify.route({ + method: 'GET', + path: '/buffer', + handler: (req, reply) => { + reply.send(resBuffer) + } + }) + + fastify.route({ + method: 'GET', + path: '/buffer-with-content-type', + handler: (req, reply) => { + reply.headers({ 'content-type': 'image/jpeg' }) + reply.send(resBuffer) + } + }) + + fastify.route({ + method: 'GET', + path: '/stream', + handler: (req, reply) => { + return resStream + } + }) + + fastify.route({ + method: 'GET', + path: '/web-stream', + handler: (req, reply) => { + return resWebStream + } + }) + + let res = await fastify.inject({ + method: 'HEAD', + url: '/json' + }) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['content-type'], 'application/json; charset=utf-8') + t.assert.strictEqual(res.headers['content-length'], `${Buffer.byteLength(JSON.stringify(resJSON))}`) + t.assert.deepStrictEqual(res.body, '') + + res = await fastify.inject({ + method: 'HEAD', + url: '/string' + }) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['content-type'], 'text/plain; charset=utf-8') + t.assert.strictEqual(res.headers['content-length'], `${Buffer.byteLength(resString)}`) + t.assert.strictEqual(res.body, '') + + res = await fastify.inject({ + method: 'HEAD', + url: '/buffer' + }) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['content-type'], 'application/octet-stream') + t.assert.strictEqual(res.headers['content-length'], `${resBuffer.byteLength}`) + t.assert.strictEqual(res.body, '') + + res = await fastify.inject({ + method: 'HEAD', + url: '/buffer-with-content-type' + }) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['content-type'], 'image/jpeg') + t.assert.strictEqual(res.headers['content-length'], `${resBuffer.byteLength}`) + t.assert.strictEqual(res.body, '') + + res = await fastify.inject({ + method: 'HEAD', + url: '/stream' + }) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['content-type'], undefined) + t.assert.strictEqual(res.headers['content-length'], undefined) + t.assert.strictEqual(res.body, '') + + res = await fastify.inject({ + method: 'HEAD', + url: '/web-stream' + }) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['content-type'], undefined) + t.assert.strictEqual(res.headers['content-length'], undefined) + t.assert.strictEqual(res.body, '') +}) + +test('HEAD route should respect custom onSend handlers', async t => { + t.plan(5) + + let counter = 0 + const resBuffer = Buffer.from('I am a coffee!') + const fastify = Fastify({ exposeHeadRoutes: true }) + const customOnSend = (res, reply, payload, done) => { + counter = counter + 1 + done(null, payload) + } + + fastify.route({ + method: 'GET', + path: '/more-coffee', + handler: (req, reply) => { + reply.send(resBuffer) + }, + onSend: [customOnSend, customOnSend] + }) + + const res = await fastify.inject({ + method: 'HEAD', + url: '/more-coffee' + }) + + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['content-type'], 'application/octet-stream') + t.assert.strictEqual(res.headers['content-length'], `${resBuffer.byteLength}`) + t.assert.strictEqual(res.body, '') + t.assert.strictEqual(counter, 2) +}) + +test('route onSend can be function or array of functions', async t => { + t.plan(10) + const counters = { single: 0, multiple: 0 } + + const resBuffer = Buffer.from('I am a coffee!') + const fastify = Fastify({ exposeHeadRoutes: true }) + + fastify.route({ + method: 'GET', + path: '/coffee', + handler: () => resBuffer, + onSend: (res, reply, payload, done) => { + counters.single += 1 + done(null, payload) + } + }) + + const customOnSend = (res, reply, payload, done) => { + counters.multiple += 1 + done(null, payload) + } + + fastify.route({ + method: 'GET', + path: '/more-coffee', + handler: () => resBuffer, + onSend: [customOnSend, customOnSend] + }) + + let res = await fastify.inject({ method: 'HEAD', url: '/coffee' }) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['content-type'], 'application/octet-stream') + t.assert.strictEqual(res.headers['content-length'], `${resBuffer.byteLength}`) + t.assert.strictEqual(res.body, '') + t.assert.strictEqual(counters.single, 1) + + res = await fastify.inject({ method: 'HEAD', url: '/more-coffee' }) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['content-type'], 'application/octet-stream') + t.assert.strictEqual(res.headers['content-length'], `${resBuffer.byteLength}`) + t.assert.strictEqual(res.body, '') + t.assert.strictEqual(counters.multiple, 2) +}) + +test('no warning for exposeHeadRoute', async t => { + const fastify = Fastify() + + fastify.route({ + method: 'GET', + path: '/more-coffee', + exposeHeadRoute: true, + async handler () { + return 'hello world' + } + }) + + const listener = (w) => { + t.assert.fail('no warning') + } + + process.on('warning', listener) + + await fastify.listen({ port: 0 }) + + process.removeListener('warning', listener) + + await fastify.close() +}) diff --git a/services/slides/node_modules/fastify/test/route.7.test.js b/services/slides/node_modules/fastify/test/route.7.test.js new file mode 100644 index 0000000000000000000000000000000000000000..44e7a091556dc8924c4e44812fd089bad42307c0 --- /dev/null +++ b/services/slides/node_modules/fastify/test/route.7.test.js @@ -0,0 +1,406 @@ +'use strict' + +const stream = require('node:stream') +const { ReadableStream } = require('node:stream/web') +const split = require('split2') +const { test } = require('node:test') +const Fastify = require('..') +const createError = require('@fastify/error') + +test("HEAD route should handle stream.on('error')", (t, done) => { + t.plan(6) + + const resStream = stream.Readable.from('Hello with error!') + const logStream = split(JSON.parse) + const expectedError = new Error('Hello!') + const fastify = Fastify({ + logger: { + stream: logStream, + level: 'error' + } + }) + + fastify.route({ + method: 'GET', + path: '/more-coffee', + exposeHeadRoute: true, + handler: (req, reply) => { + process.nextTick(() => resStream.emit('error', expectedError)) + return resStream + } + }) + + logStream.once('data', line => { + const { message, stack } = expectedError + t.assert.deepStrictEqual(line.err, { type: 'Error', message, stack }) + t.assert.strictEqual(line.msg, 'Error on Stream found for HEAD route') + t.assert.strictEqual(line.level, 50) + }) + + fastify.inject({ + method: 'HEAD', + url: '/more-coffee' + }, (error, res) => { + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['content-type'], undefined) + done() + }) +}) + +test('HEAD route should handle ReadableStream.cancel() error', (t, done) => { + t.plan(7) + + const logStream = split(JSON.parse) + const expectedError = new Error('Cancel error!') + const fastify = Fastify({ + logger: { + stream: logStream, + level: 'error' + } + }) + + fastify.route({ + method: 'GET', + path: '/web-stream', + exposeHeadRoute: true, + handler: (req, reply) => { + const webStream = new ReadableStream({ + start (controller) { + controller.enqueue('Hello from web stream!') + }, + cancel (reason) { + t.assert.strictEqual(reason, 'Stream cancelled by HEAD route') + throw expectedError + } + }) + return webStream + } + }) + + logStream.once('data', line => { + const { message, stack } = expectedError + t.assert.deepStrictEqual(line.err, { type: 'Error', message, stack }) + t.assert.strictEqual(line.msg, 'Error on Stream found for HEAD route') + t.assert.strictEqual(line.level, 50) + }) + + fastify.inject({ + method: 'HEAD', + url: '/web-stream' + }, (error, res) => { + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['content-type'], undefined) + done() + }) +}) + +test('HEAD route should be exposed by default', async t => { + t.plan(5) + + const resStream = stream.Readable.from('Hello with error!') + const resJson = { hello: 'world' } + const fastify = Fastify() + + fastify.route({ + method: 'GET', + path: '/without-flag', + handler: (req, reply) => { + return resStream + } + }) + + fastify.route({ + exposeHeadRoute: true, + method: 'GET', + path: '/with-flag', + handler: (req, reply) => { + return resJson + } + }) + + let res = await fastify.inject({ + method: 'HEAD', + url: '/without-flag' + }) + t.assert.strictEqual(res.statusCode, 200) + + res = await fastify.inject({ + method: 'HEAD', + url: '/with-flag' + }) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['content-type'], 'application/json; charset=utf-8') + t.assert.strictEqual(res.headers['content-length'], `${Buffer.byteLength(JSON.stringify(resJson))}`) + t.assert.strictEqual(res.body, '') +}) + +test('HEAD route should be exposed if route exposeHeadRoute is set', async t => { + t.plan(5) + + const resBuffer = Buffer.from('I am a coffee!') + const resJson = { hello: 'world' } + const fastify = Fastify({ exposeHeadRoutes: false }) + + fastify.route({ + exposeHeadRoute: true, + method: 'GET', + path: '/one', + handler: (req, reply) => { + return resBuffer + } + }) + + fastify.route({ + method: 'GET', + path: '/two', + handler: (req, reply) => { + return resJson + } + }) + + let res = await fastify.inject({ + method: 'HEAD', + url: '/one' + }) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['content-type'], 'application/octet-stream') + t.assert.strictEqual(res.headers['content-length'], `${resBuffer.byteLength}`) + t.assert.strictEqual(res.body, '') + + res = await fastify.inject({ + method: 'HEAD', + url: '/two' + }) + t.assert.strictEqual(res.statusCode, 404) +}) + +test('Set a custom HEAD route before GET one without disabling exposeHeadRoutes (global)', (t, done) => { + t.plan(6) + + const resBuffer = Buffer.from('I am a coffee!') + const fastify = Fastify({ + exposeHeadRoutes: true + }) + + fastify.route({ + method: 'HEAD', + path: '/one', + handler: (req, reply) => { + reply.header('content-type', 'application/pdf') + reply.header('content-length', `${resBuffer.byteLength}`) + reply.header('x-custom-header', 'some-custom-header') + reply.send() + } + }) + + fastify.route({ + method: 'GET', + path: '/one', + handler: (req, reply) => { + return resBuffer + } + }) + + fastify.inject({ + method: 'HEAD', + url: '/one' + }, (error, res) => { + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['content-type'], 'application/pdf') + t.assert.strictEqual(res.headers['content-length'], `${resBuffer.byteLength}`) + t.assert.strictEqual(res.headers['x-custom-header'], 'some-custom-header') + t.assert.strictEqual(res.body, '') + done() + }) +}) + +test('Set a custom HEAD route before GET one without disabling exposeHeadRoutes (route)', (t, done) => { + t.plan(6) + + const fastify = Fastify() + + const resBuffer = Buffer.from('I am a coffee!') + + fastify.route({ + method: 'HEAD', + path: '/one', + handler: (req, reply) => { + reply.header('content-type', 'application/pdf') + reply.header('content-length', `${resBuffer.byteLength}`) + reply.header('x-custom-header', 'some-custom-header') + reply.send() + } + }) + + fastify.route({ + method: 'GET', + exposeHeadRoute: true, + path: '/one', + handler: (req, reply) => { + return resBuffer + } + }) + + fastify.inject({ + method: 'HEAD', + url: '/one' + }, (error, res) => { + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers['content-type'], 'application/pdf') + t.assert.strictEqual(res.headers['content-length'], `${resBuffer.byteLength}`) + t.assert.strictEqual(res.headers['x-custom-header'], 'some-custom-header') + t.assert.strictEqual(res.body, '') + done() + }) +}) + +test('HEAD routes properly auto created for GET routes when prefixTrailingSlash: \'no-slash\'', (t, done) => { + t.plan(2) + + const fastify = Fastify() + + fastify.register(function routes (f, opts, next) { + f.route({ + method: 'GET', + url: '/', + exposeHeadRoute: true, + prefixTrailingSlash: 'no-slash', + handler: (req, reply) => { + reply.send({ hello: 'world' }) + } + }) + + next() + }, { prefix: '/prefix' }) + + fastify.inject({ url: '/prefix/prefix', method: 'HEAD' }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 404) + done() + }) +}) + +test('HEAD routes properly auto created for GET routes when prefixTrailingSlash: \'both\'', async t => { + t.plan(3) + + const fastify = Fastify() + + fastify.register(function routes (f, opts, next) { + f.route({ + method: 'GET', + url: '/', + exposeHeadRoute: true, + prefixTrailingSlash: 'both', + handler: (req, reply) => { + reply.send({ hello: 'world' }) + } + }) + + next() + }, { prefix: '/prefix' }) + + const doublePrefixReply = await fastify.inject({ url: '/prefix/prefix', method: 'HEAD' }) + const trailingSlashReply = await fastify.inject({ url: '/prefix/', method: 'HEAD' }) + const noneTrailingReply = await fastify.inject({ url: '/prefix', method: 'HEAD' }) + + t.assert.strictEqual(doublePrefixReply.statusCode, 404) + t.assert.strictEqual(trailingSlashReply.statusCode, 200) + t.assert.strictEqual(noneTrailingReply.statusCode, 200) +}) + +test('GET route with body schema should throw', t => { + t.plan(1) + + const fastify = Fastify() + + t.assert.throws(() => { + fastify.route({ + method: 'GET', + path: '/get', + schema: { + body: {} + }, + handler: function (req, reply) { + reply.send({ hello: 'world' }) + } + }) + }, createError('FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED', 'Body validation schema for GET:/get route is not supported!')()) +}) + +test('HEAD route with body schema should throw', t => { + t.plan(1) + + const fastify = Fastify() + + t.assert.throws(() => { + fastify.route({ + method: 'HEAD', + path: '/shouldThrow', + schema: { + body: {} + }, + handler: function (req, reply) { + reply.send({ hello: 'world' }) + } + }) + }, createError('FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED', 'Body validation schema for HEAD:/shouldThrow route is not supported!')()) +}) + +test('[HEAD, GET] route with body schema should throw', t => { + t.plan(1) + + const fastify = Fastify() + + t.assert.throws(() => { + fastify.route({ + method: ['HEAD', 'GET'], + path: '/shouldThrowHead', + schema: { + body: {} + }, + handler: function (req, reply) { + reply.send({ hello: 'world' }) + } + }) + }, createError('FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED', 'Body validation schema for HEAD:/shouldThrowHead route is not supported!')()) +}) + +test('GET route with body schema should throw - shorthand', t => { + t.plan(1) + + const fastify = Fastify() + + t.assert.throws(() => { + fastify.get('/shouldThrow', { + schema: { + body: {} + } + }, + function (req, reply) { + reply.send({ hello: 'world' }) + } + ) + }, createError('FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED', 'Body validation schema for GET:/shouldThrow route is not supported!')()) +}) + +test('HEAD route with body schema should throw - shorthand', t => { + t.plan(1) + + const fastify = Fastify() + + t.assert.throws(() => { + fastify.head('/shouldThrow2', { + schema: { + body: {} + } + }, + function (req, reply) { + reply.send({ hello: 'world' }) + } + ) + }, createError('FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED', 'Body validation schema for HEAD:/shouldThrow2 route is not supported!')()) +}) diff --git a/services/slides/node_modules/fastify/test/route.8.test.js b/services/slides/node_modules/fastify/test/route.8.test.js new file mode 100644 index 0000000000000000000000000000000000000000..2f479eaf4dc14ffe15235ffa90459aa1722fbe0c --- /dev/null +++ b/services/slides/node_modules/fastify/test/route.8.test.js @@ -0,0 +1,225 @@ +'use strict' + +const { test } = require('node:test') +const Fastify = require('..') +const { + FST_ERR_INVALID_URL +} = require('../lib/errors') + +test('Request and Reply share the route options', async t => { + t.plan(3) + + const fastify = Fastify() + + const config = { + this: 'is a string', + thisIs: function aFunction () {} + } + + fastify.route({ + method: 'GET', + url: '/', + config, + handler: (req, reply) => { + t.assert.deepStrictEqual(req.routeOptions, reply.routeOptions) + t.assert.deepStrictEqual(req.routeOptions.config, reply.routeOptions.config) + t.assert.match(req.routeOptions.config, config, 'there are url and method additional properties') + + reply.send({ hello: 'world' }) + } + }) + + await fastify.inject('/') +}) + +test('Will not try to re-createprefixed HEAD route if it already exists and exposeHeadRoutes is true', async (t) => { + t.plan(1) + + const fastify = Fastify({ exposeHeadRoutes: true }) + + fastify.register((scope, opts, next) => { + scope.route({ + method: 'HEAD', + path: '/route', + handler: (req, reply) => { + reply.header('content-type', 'text/plain') + reply.send('custom HEAD response') + } + }) + scope.route({ + method: 'GET', + path: '/route', + handler: (req, reply) => { + reply.send({ ok: true }) + } + }) + + next() + }, { prefix: '/prefix' }) + + await fastify.ready() + + t.assert.ok(true) +}) + +test('route with non-english characters', async (t) => { + t.plan(3) + + const fastify = Fastify() + + fastify.get('/föö', (request, reply) => { + reply.send('here /föö') + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const response = await fetch(fastifyServer + encodeURI('/föö')) + t.assert.ok(response.ok) + t.assert.strictEqual(response.status, 200) + const body = await response.text() + t.assert.strictEqual(body, 'here /föö') +}) + +test('invalid url attribute - non string URL', t => { + t.plan(1) + const fastify = Fastify() + + try { + fastify.get(/^\/(donations|skills|blogs)/, () => { }) + } catch (error) { + t.assert.strictEqual(error.code, FST_ERR_INVALID_URL().code) + } +}) + +test('exposeHeadRoute should not reuse the same route option', async t => { + t.plan(2) + + const fastify = Fastify() + + // we update the onRequest hook in onRoute hook + // if we reuse the same route option + // that means we will append another function inside the array + fastify.addHook('onRoute', function (routeOption) { + if (Array.isArray(routeOption.onRequest)) { + routeOption.onRequest.push(() => {}) + } else { + routeOption.onRequest = [() => {}] + } + }) + + fastify.addHook('onRoute', function (routeOption) { + t.assert.strictEqual(routeOption.onRequest.length, 1) + }) + + fastify.route({ + method: 'GET', + path: '/more-coffee', + async handler () { + return 'hello world' + } + }) +}) + +test('using fastify.all when a catchall is defined does not degrade performance', { timeout: 30000 }, async t => { + t.plan(1) + + const fastify = Fastify() + + fastify.get('/*', async (_, reply) => reply.json({ ok: true })) + + for (let i = 0; i < 100; i++) { + fastify.all(`/${i}`, async (_, reply) => reply.json({ ok: true })) + } + + t.assert.ok("fastify.all doesn't degrade performance") +}) + +test('Adding manually HEAD route after GET with the same path throws Fastify duplicated route instance error', t => { + t.plan(1) + + const fastify = Fastify() + + fastify.route({ + method: 'GET', + path: '/:param1', + handler: (req, reply) => { + reply.send({ hello: 'world' }) + } + }) + + try { + fastify.route({ + method: 'HEAD', + path: '/:param2', + handler: (req, reply) => { + reply.send({ hello: 'world' }) + } + }) + t.assert.fail('Should throw fastify duplicated route declaration') + } catch (error) { + t.assert.strictEqual(error.code, 'FST_ERR_DUPLICATED_ROUTE') + } +}) + +test('Will pass onSend hook to HEAD method if exposeHeadRoutes is true /1', async (t) => { + t.plan(1) + + const fastify = Fastify({ exposeHeadRoutes: true }) + + await fastify.register((scope, opts, next) => { + scope.route({ + method: 'GET', + path: '/route', + handler: (req, reply) => { + reply.send({ ok: true }) + }, + onSend: (req, reply, payload, done) => { + reply.header('x-content-type', 'application/fastify') + done(null, payload) + } + }) + + next() + }, { prefix: '/prefix' }) + + await fastify.ready() + + const result = await fastify.inject({ + url: '/prefix/route', + method: 'HEAD' + }) + + t.assert.strictEqual(result.headers['x-content-type'], 'application/fastify') +}) + +test('Will pass onSend hook to HEAD method if exposeHeadRoutes is true /2', async (t) => { + t.plan(1) + + const fastify = Fastify({ exposeHeadRoutes: true }) + + await fastify.register((scope, opts, next) => { + scope.route({ + method: 'get', + path: '/route', + handler: (req, reply) => { + reply.send({ ok: true }) + }, + onSend: (req, reply, payload, done) => { + reply.header('x-content-type', 'application/fastify') + done(null, payload) + } + }) + + next() + }, { prefix: '/prefix' }) + + await fastify.ready() + + const result = await fastify.inject({ + url: '/prefix/route', + method: 'HEAD' + }) + + t.assert.strictEqual(result.headers['x-content-type'], 'application/fastify') +}) diff --git a/services/slides/node_modules/fastify/test/router-options.test.js b/services/slides/node_modules/fastify/test/router-options.test.js new file mode 100644 index 0000000000000000000000000000000000000000..0da3ec4193e1c4838d97ac93c22e57fd669c4c76 --- /dev/null +++ b/services/slides/node_modules/fastify/test/router-options.test.js @@ -0,0 +1,1108 @@ +'use strict' + +const split = require('split2') +const { test } = require('node:test') +const querystring = require('node:querystring') +const Fastify = require('../') +const { + FST_ERR_BAD_URL, + FST_ERR_ASYNC_CONSTRAINT +} = require('../lib/errors') + +test('Should honor ignoreTrailingSlash option', async t => { + t.plan(4) + const fastify = Fastify({ + ignoreTrailingSlash: true + }) + + fastify.get('/test', (req, res) => { + res.send('test') + }) + + let res = await fastify.inject('/test') + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload.toString(), 'test') + + res = await fastify.inject('/test/') + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload.toString(), 'test') +}) + +test('Should honor ignoreDuplicateSlashes option', async t => { + t.plan(4) + const fastify = Fastify({ + ignoreDuplicateSlashes: true + }) + + fastify.get('/test//test///test', (req, res) => { + res.send('test') + }) + + let res = await fastify.inject('/test/test/test') + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload.toString(), 'test') + + res = await fastify.inject('/test//test///test') + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload.toString(), 'test') +}) + +test('Should honor ignoreTrailingSlash and ignoreDuplicateSlashes options', async t => { + t.plan(4) + const fastify = Fastify({ + ignoreTrailingSlash: true, + ignoreDuplicateSlashes: true + }) + + fastify.get('/test//test///test', (req, res) => { + res.send('test') + }) + + let res = await fastify.inject('/test/test/test/') + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload.toString(), 'test') + + res = await fastify.inject('/test//test///test//') + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload.toString(), 'test') +}) + +test('Should honor maxParamLength option', async (t) => { + const fastify = Fastify({ maxParamLength: 10 }) + + fastify.get('/test/:id', (req, reply) => { + reply.send({ hello: 'world' }) + }) + + const res = await fastify.inject({ + method: 'GET', + url: '/test/123456789' + }) + t.assert.strictEqual(res.statusCode, 200) + + const resError = await fastify.inject({ + method: 'GET', + url: '/test/123456789abcd' + }) + t.assert.strictEqual(resError.statusCode, 404) +}) + +test('Should expose router options via getters on request and reply', (t, done) => { + t.plan(9) + const fastify = Fastify() + const expectedSchema = { + params: { + type: 'object', + properties: { + id: { type: 'integer' } + } + } + } + + fastify.get('/test/:id', { + schema: expectedSchema + }, (req, reply) => { + t.assert.strictEqual(reply.routeOptions.config.url, '/test/:id') + t.assert.strictEqual(reply.routeOptions.config.method, 'GET') + t.assert.deepStrictEqual(req.routeOptions.schema, expectedSchema) + t.assert.strictEqual(typeof req.routeOptions.handler, 'function') + t.assert.strictEqual(req.routeOptions.config.url, '/test/:id') + t.assert.strictEqual(req.routeOptions.config.method, 'GET') + t.assert.strictEqual(req.is404, false) + reply.send({ hello: 'world' }) + }) + + fastify.inject({ + method: 'GET', + url: '/test/123456789' + }, (error, res) => { + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 200) + done() + }) +}) + +test('Should set is404 flag for unmatched paths', (t, done) => { + t.plan(3) + const fastify = Fastify() + + fastify.setNotFoundHandler((req, reply) => { + t.assert.strictEqual(req.is404, true) + reply.code(404).send({ error: 'Not Found', message: 'Four oh for', statusCode: 404 }) + }) + + fastify.inject({ + method: 'GET', + url: '/nonexist/123456789' + }, (error, res) => { + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 404) + done() + }) +}) + +test('Should honor frameworkErrors option - FST_ERR_BAD_URL', (t, done) => { + t.plan(3) + const fastify = Fastify({ + frameworkErrors: function (err, req, res) { + if (err instanceof FST_ERR_BAD_URL) { + t.assert.ok(true) + } else { + t.assert.fail() + } + res.send(`${err.message} - ${err.code}`) + } + }) + + fastify.get('/test/:id', (req, res) => { + res.send('{ hello: \'world\' }') + }) + + fastify.inject( + { + method: 'GET', + url: '/test/%world' + }, + (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.body, '\'/test/%world\' is not a valid url component - FST_ERR_BAD_URL') + done() + } + ) +}) + +test('Should supply Fastify request to the logger in frameworkErrors wrapper - FST_ERR_BAD_URL', (t, done) => { + t.plan(8) + + const REQ_ID = 'REQ-1234' + const logStream = split(JSON.parse) + + const fastify = Fastify({ + frameworkErrors: function (err, req, res) { + t.assert.deepStrictEqual(req.id, REQ_ID) + t.assert.deepStrictEqual(req.raw.httpVersion, '1.1') + res.send(`${err.message} - ${err.code}`) + }, + logger: { + stream: logStream, + serializers: { + req (request) { + t.assert.deepStrictEqual(request.id, REQ_ID) + return { httpVersion: request.raw.httpVersion } + } + } + }, + genReqId: () => REQ_ID + }) + + fastify.get('/test/:id', (req, res) => { + res.send('{ hello: \'world\' }') + }) + + logStream.on('data', (json) => { + t.assert.deepStrictEqual(json.msg, 'incoming request') + t.assert.deepStrictEqual(json.reqId, REQ_ID) + t.assert.deepStrictEqual(json.req.httpVersion, '1.1') + }) + + fastify.inject( + { + method: 'GET', + url: '/test/%world' + }, + (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.body, '\'/test/%world\' is not a valid url component - FST_ERR_BAD_URL') + done() + } + ) +}) + +test('Should honor disableRequestLogging option in frameworkErrors wrapper - FST_ERR_BAD_URL', (t, done) => { + t.plan(2) + + const logStream = split(JSON.parse) + + const fastify = Fastify({ + disableRequestLogging: true, + frameworkErrors: function (err, req, res) { + res.send(`${err.message} - ${err.code}`) + }, + logger: { + stream: logStream, + serializers: { + req () { + t.assert.fail('should not be called') + }, + res () { + t.assert.fail('should not be called') + } + } + } + }) + + fastify.get('/test/:id', (req, res) => { + res.send('{ hello: \'world\' }') + }) + + logStream.on('data', (json) => { + t.assert.fail('should not be called') + }) + + fastify.inject( + { + method: 'GET', + url: '/test/%world' + }, + (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.body, '\'/test/%world\' is not a valid url component - FST_ERR_BAD_URL') + done() + } + ) +}) + +test('Should honor frameworkErrors option - FST_ERR_ASYNC_CONSTRAINT', (t, done) => { + t.plan(3) + + const constraint = { + name: 'secret', + storage: function () { + const secrets = {} + return { + get: (secret) => { return secrets[secret] || null }, + set: (secret, store) => { secrets[secret] = store } + } + }, + deriveConstraint: (req, ctx, done) => { + done(Error('kaboom')) + }, + validate () { return true } + } + + const fastify = Fastify({ + frameworkErrors: function (err, req, res) { + if (err instanceof FST_ERR_ASYNC_CONSTRAINT) { + t.assert.ok(true) + } else { + t.assert.fail() + } + res.send(`${err.message} - ${err.code}`) + }, + constraints: { secret: constraint } + }) + + fastify.route({ + method: 'GET', + url: '/', + constraints: { secret: 'alpha' }, + handler: (req, reply) => { + reply.send({ hello: 'from alpha' }) + } + }) + + fastify.inject( + { + method: 'GET', + url: '/' + }, + (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.body, 'Unexpected error from async constraint - FST_ERR_ASYNC_CONSTRAINT') + done() + } + ) +}) + +test('Should supply Fastify request to the logger in frameworkErrors wrapper - FST_ERR_ASYNC_CONSTRAINT', (t, done) => { + t.plan(8) + + const constraint = { + name: 'secret', + storage: function () { + const secrets = {} + return { + get: (secret) => { return secrets[secret] || null }, + set: (secret, store) => { secrets[secret] = store } + } + }, + deriveConstraint: (req, ctx, done) => { + done(Error('kaboom')) + }, + validate () { return true } + } + + const REQ_ID = 'REQ-1234' + const logStream = split(JSON.parse) + + const fastify = Fastify({ + constraints: { secret: constraint }, + frameworkErrors: function (err, req, res) { + t.assert.deepStrictEqual(req.id, REQ_ID) + t.assert.deepStrictEqual(req.raw.httpVersion, '1.1') + res.send(`${err.message} - ${err.code}`) + }, + logger: { + stream: logStream, + serializers: { + req (request) { + t.assert.deepStrictEqual(request.id, REQ_ID) + return { httpVersion: request.raw.httpVersion } + } + } + }, + genReqId: () => REQ_ID + }) + + fastify.route({ + method: 'GET', + url: '/', + constraints: { secret: 'alpha' }, + handler: (req, reply) => { + reply.send({ hello: 'from alpha' }) + } + }) + + logStream.on('data', (json) => { + t.assert.deepStrictEqual(json.msg, 'incoming request') + t.assert.deepStrictEqual(json.reqId, REQ_ID) + t.assert.deepStrictEqual(json.req.httpVersion, '1.1') + }) + + fastify.inject( + { + method: 'GET', + url: '/' + }, + (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.body, 'Unexpected error from async constraint - FST_ERR_ASYNC_CONSTRAINT') + done() + } + ) +}) + +test('Should honor disableRequestLogging option in frameworkErrors wrapper - FST_ERR_ASYNC_CONSTRAINT', (t, done) => { + t.plan(2) + + const constraint = { + name: 'secret', + storage: function () { + const secrets = {} + return { + get: (secret) => { return secrets[secret] || null }, + set: (secret, store) => { secrets[secret] = store } + } + }, + deriveConstraint: (req, ctx, done) => { + done(Error('kaboom')) + }, + validate () { return true } + } + + const logStream = split(JSON.parse) + + const fastify = Fastify({ + constraints: { secret: constraint }, + disableRequestLogging: true, + frameworkErrors: function (err, req, res) { + res.send(`${err.message} - ${err.code}`) + }, + logger: { + stream: logStream, + serializers: { + req () { + t.assert.fail('should not be called') + }, + res () { + t.assert.fail('should not be called') + } + } + } + }) + + fastify.route({ + method: 'GET', + url: '/', + constraints: { secret: 'alpha' }, + handler: (req, reply) => { + reply.send({ hello: 'from alpha' }) + } + }) + + logStream.on('data', (json) => { + t.assert.fail('should not be called') + }) + + fastify.inject( + { + method: 'GET', + url: '/' + }, + (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.body, 'Unexpected error from async constraint - FST_ERR_ASYNC_CONSTRAINT') + done() + } + ) +}) + +test('Should honor disableRequestLogging function in frameworkErrors wrapper - FST_ERR_BAD_URL', (t, done) => { + t.plan(4) + + let logCallCount = 0 + const logStream = split(JSON.parse) + + const fastify = Fastify({ + disableRequestLogging: (req) => { + // Disable logging for URLs containing 'silent' + return req.url.includes('silent') + }, + frameworkErrors: function (err, req, res) { + res.send(`${err.message} - ${err.code}`) + }, + logger: { + stream: logStream, + level: 'info' + } + }) + + fastify.get('/test/:id', (req, res) => { + res.send('{ hello: \'world\' }') + }) + + logStream.on('data', (json) => { + if (json.msg === 'incoming request') { + logCallCount++ + } + }) + + // First request: URL does not contain 'silent', so logging should happen + fastify.inject( + { + method: 'GET', + url: '/test/%world' + }, + (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.body, '\'/test/%world\' is not a valid url component - FST_ERR_BAD_URL') + + // Second request: URL contains 'silent', so logging should be disabled + fastify.inject( + { + method: 'GET', + url: '/silent/%world' + }, + (err2, res2) => { + t.assert.ifError(err2) + // Give time for any potential log events + setImmediate(() => { + // Only the first request should have logged + t.assert.strictEqual(logCallCount, 1) + done() + }) + } + ) + } + ) +}) + +test('Should honor disableRequestLogging function in frameworkErrors wrapper - FST_ERR_ASYNC_CONSTRAINT', (t, done) => { + t.plan(4) + + let logCallCount = 0 + + const constraint = { + name: 'secret', + storage: function () { + const secrets = {} + return { + get: (secret) => { return secrets[secret] || null }, + set: (secret, store) => { secrets[secret] = store } + } + }, + deriveConstraint: (req, ctx, done) => { + done(Error('kaboom')) + }, + validate () { return true } + } + + const logStream = split(JSON.parse) + + const fastify = Fastify({ + constraints: { secret: constraint }, + disableRequestLogging: (req) => { + // Disable logging for URLs containing 'silent' + return req.url.includes('silent') + }, + frameworkErrors: function (err, req, res) { + res.send(`${err.message} - ${err.code}`) + }, + logger: { + stream: logStream, + level: 'info' + } + }) + + fastify.route({ + method: 'GET', + url: '/', + constraints: { secret: 'alpha' }, + handler: (req, reply) => { + reply.send({ hello: 'from alpha' }) + } + }) + + fastify.route({ + method: 'GET', + url: '/silent', + constraints: { secret: 'alpha' }, + handler: (req, reply) => { + reply.send({ hello: 'from alpha' }) + } + }) + + logStream.on('data', (json) => { + if (json.msg === 'incoming request') { + logCallCount++ + } + }) + + // First request: URL does not contain 'silent', so logging should happen + fastify.inject( + { + method: 'GET', + url: '/' + }, + (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.body, 'Unexpected error from async constraint - FST_ERR_ASYNC_CONSTRAINT') + + // Second request: URL contains 'silent', so logging should be disabled + fastify.inject( + { + method: 'GET', + url: '/silent' + }, + (err2, res2) => { + t.assert.ifError(err2) + // Give time for any potential log events + setImmediate(() => { + // Only the first request should have logged + t.assert.strictEqual(logCallCount, 1) + done() + }) + } + ) + } + ) +}) + +test('Should honor routerOptions.defaultRoute', async t => { + t.plan(3) + const fastify = Fastify({ + routerOptions: { + defaultRoute: function (_, res) { + t.assert.ok('default route called') + res.statusCode = 404 + res.end('default route') + } + } + }) + + const res = await fastify.inject('/') + t.assert.strictEqual(res.statusCode, 404) + t.assert.strictEqual(res.payload, 'default route') +}) + +test('Should honor routerOptions.badUrl', async t => { + t.plan(3) + const fastify = Fastify({ + routerOptions: { + defaultRoute: function (_, res) { + t.asset.fail('default route should not be called') + }, + onBadUrl: function (path, _, res) { + t.assert.ok('bad url called') + res.statusCode = 400 + res.end(`Bath URL: ${path}`) + } + } + }) + + fastify.get('/hello/:id', (req, res) => { + res.send({ hello: 'world' }) + }) + + const res = await fastify.inject('/hello/%world') + t.assert.strictEqual(res.statusCode, 400) + t.assert.strictEqual(res.payload, 'Bath URL: /hello/%world') +}) + +test('Should honor routerOptions.ignoreTrailingSlash', async t => { + t.plan(4) + const fastify = Fastify({ + routerOptions: { + ignoreTrailingSlash: true + } + }) + + fastify.get('/test', (req, res) => { + res.send('test') + }) + + let res = await fastify.inject('/test') + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload.toString(), 'test') + + res = await fastify.inject('/test/') + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload.toString(), 'test') +}) + +test('Should honor routerOptions.ignoreDuplicateSlashes', async t => { + t.plan(4) + const fastify = Fastify({ + routerOptions: { + ignoreDuplicateSlashes: true + } + }) + + fastify.get('/test//test///test', (req, res) => { + res.send('test') + }) + + let res = await fastify.inject('/test/test/test') + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload.toString(), 'test') + + res = await fastify.inject('/test//test///test') + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload.toString(), 'test') +}) + +test('Should honor routerOptions.ignoreTrailingSlash and routerOptions.ignoreDuplicateSlashes', async t => { + t.plan(4) + const fastify = Fastify({ + routerOptions: { + ignoreTrailingSlash: true, + ignoreDuplicateSlashes: true + } + }) + + t.after(() => fastify.close()) + + fastify.get('/test//test///test', (req, res) => { + res.send('test') + }) + + let res = await fastify.inject('/test/test/test/') + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload.toString(), 'test') + + res = await fastify.inject('/test//test///test//') + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload.toString(), 'test') +}) + +test('Should honor routerOptions.maxParamLength', async (t) => { + const fastify = Fastify({ + routerOptions: + { + maxParamLength: 10 + } + }) + + fastify.get('/test/:id', (req, reply) => { + reply.send({ hello: 'world' }) + }) + + const res = await fastify.inject({ + method: 'GET', + url: '/test/123456789' + }) + t.assert.strictEqual(res.statusCode, 200) + + const resError = await fastify.inject({ + method: 'GET', + url: '/test/123456789abcd' + }) + t.assert.strictEqual(resError.statusCode, 404) +}) + +test('Should honor routerOptions.allowUnsafeRegex', async (t) => { + const fastify = Fastify({ + routerOptions: + { + allowUnsafeRegex: true + } + }) + + fastify.get('/test/:id(([a-f0-9]{3},?)+)', (req, reply) => { + reply.send({ hello: 'world' }) + }) + + let res = await fastify.inject({ + method: 'GET', + url: '/test/bac,1ea' + }) + t.assert.strictEqual(res.statusCode, 200) + + res = await fastify.inject({ + method: 'GET', + url: '/test/qwerty' + }) + + t.assert.strictEqual(res.statusCode, 404) +}) + +test('Should honor routerOptions.caseSensitive', async (t) => { + const fastify = Fastify({ + routerOptions: + { + caseSensitive: false + } + }) + + fastify.get('/TeSt', (req, reply) => { + reply.send('test') + }) + + let res = await fastify.inject({ + method: 'GET', + url: '/test' + }) + t.assert.strictEqual(res.statusCode, 200) + + res = await fastify.inject({ + method: 'GET', + url: '/tEsT' + }) + t.assert.strictEqual(res.statusCode, 200) + + res = await fastify.inject({ + method: 'GET', + url: '/TEST' + }) + t.assert.strictEqual(res.statusCode, 200) +}) + +test('Should honor routerOptions.queryStringParser', async (t) => { + t.plan(4) + const fastify = Fastify({ + routerOptions: + { + querystringParser: function (str) { + t.assert.ok('custom query string parser called') + return querystring.parse(str) + } + } + }) + + fastify.get('/test', (req, reply) => { + t.assert.deepStrictEqual(req.query.foo, 'bar') + t.assert.deepStrictEqual(req.query.baz, 'faz') + reply.send('test') + }) + + const res = await fastify.inject({ + method: 'GET', + url: '/test?foo=bar&baz=faz' + }) + t.assert.strictEqual(res.statusCode, 200) +}) + +test('Should honor routerOptions.useSemicolonDelimiter', async (t) => { + t.plan(6) + const fastify = Fastify({ + routerOptions: + { + useSemicolonDelimiter: true + } + }) + + fastify.get('/test', (req, reply) => { + t.assert.deepStrictEqual(req.query.foo, 'bar') + t.assert.deepStrictEqual(req.query.baz, 'faz') + reply.send('test') + }) + + // Support semicolon delimiter + let res = await fastify.inject({ + method: 'GET', + url: '/test;foo=bar&baz=faz' + }) + t.assert.strictEqual(res.statusCode, 200) + + // Support query string `?` delimiter + res = await fastify.inject({ + method: 'GET', + url: '/test?foo=bar&baz=faz' + }) + t.assert.strictEqual(res.statusCode, 200) +}) + +test('Should honor routerOptions.buildPrettyMeta', async (t) => { + t.plan(10) + const fastify = Fastify({ + routerOptions: + { + buildPrettyMeta: function (route) { + t.assert.ok('custom buildPrettyMeta called') + return { metaKey: route.path } + } + } + }) + + fastify.get('/test', () => {}) + fastify.get('/test/hello', () => {}) + fastify.get('/testing', () => {}) + fastify.get('/testing/:param', () => {}) + fastify.put('/update', () => {}) + + await fastify.ready() + + const result = fastify.printRoutes({ includeMeta: true }) + const expected = `\ +└── / + ├── test (GET, HEAD) + │ • (metaKey) "/test" + │ ├── /hello (GET, HEAD) + │ │ • (metaKey) "/test/hello" + │ └── ing (GET, HEAD) + │ • (metaKey) "/testing" + │ └── / + │ └── :param (GET, HEAD) + │ • (metaKey) "/testing/:param" + └── update (PUT) + • (metaKey) "/update" +` + + t.assert.strictEqual(result, expected) +}) + +test('Should honor routerOptions.ignoreTrailingSlash and routerOptions.ignoreDuplicateSlashes over top level options', async t => { + t.plan(4) + const fastify = Fastify({ + ignoreTrailingSlash: false, + ignoreDuplicateSlashes: false, + routerOptions: { + ignoreTrailingSlash: true, + ignoreDuplicateSlashes: true + } + }) + + fastify.get('/test//test///test', (req, res) => { + res.send('test') + }) + + let res = await fastify.inject('/test/test/test/') + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload.toString(), 'test') + + res = await fastify.inject('/test//test///test//') + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload.toString(), 'test') +}) + +test('Should honor routerOptions.maxParamLength over maxParamLength option', async (t) => { + const fastify = Fastify({ + maxParamLength: 0, + routerOptions: + { + maxParamLength: 10 + } + }) + + fastify.get('/test/:id', (req, reply) => { + reply.send({ hello: 'world' }) + }) + + const res = await fastify.inject({ + method: 'GET', + url: '/test/123456789' + }) + t.assert.strictEqual(res.statusCode, 200) + + const resError = await fastify.inject({ + method: 'GET', + url: '/test/123456789abcd' + }) + t.assert.strictEqual(resError.statusCode, 404) +}) + +test('Should honor routerOptions.allowUnsafeRegex over allowUnsafeRegex option', async (t) => { + const fastify = Fastify({ + allowUnsafeRegex: false, + routerOptions: + { + allowUnsafeRegex: true + } + }) + + fastify.get('/test/:id(([a-f0-9]{3},?)+)', (req, reply) => { + reply.send({ hello: 'world' }) + }) + + let res = await fastify.inject({ + method: 'GET', + url: '/test/bac,1ea' + }) + t.assert.strictEqual(res.statusCode, 200) + + res = await fastify.inject({ + method: 'GET', + url: '/test/qwerty' + }) + + t.assert.strictEqual(res.statusCode, 404) +}) + +test('Should honor routerOptions.caseSensitive over caseSensitive option', async (t) => { + const fastify = Fastify({ + caseSensitive: true, + routerOptions: + { + caseSensitive: false + } + }) + + fastify.get('/TeSt', (req, reply) => { + reply.send('test') + }) + + let res = await fastify.inject({ + method: 'GET', + url: '/test' + }) + t.assert.strictEqual(res.statusCode, 200) + + res = await fastify.inject({ + method: 'GET', + url: '/tEsT' + }) + t.assert.strictEqual(res.statusCode, 200) + + res = await fastify.inject({ + method: 'GET', + url: '/TEST' + }) + t.assert.strictEqual(res.statusCode, 200) +}) + +test('Should honor routerOptions.queryStringParser over queryStringParser option', async (t) => { + t.plan(4) + const fastify = Fastify({ + queryStringParser: undefined, + routerOptions: + { + querystringParser: function (str) { + t.assert.ok('custom query string parser called') + return querystring.parse(str) + } + } + }) + + fastify.get('/test', (req, reply) => { + t.assert.deepStrictEqual(req.query.foo, 'bar') + t.assert.deepStrictEqual(req.query.baz, 'faz') + reply.send('test') + }) + + const res = await fastify.inject({ + method: 'GET', + url: '/test?foo=bar&baz=faz' + }) + t.assert.strictEqual(res.statusCode, 200) +}) + +test('Should honor routerOptions.useSemicolonDelimiter over useSemicolonDelimiter option', async (t) => { + t.plan(6) + const fastify = Fastify({ + useSemicolonDelimiter: false, + routerOptions: + { + useSemicolonDelimiter: true + } + }) + + fastify.get('/test', (req, reply) => { + t.assert.deepStrictEqual(req.query.foo, 'bar') + t.assert.deepStrictEqual(req.query.baz, 'faz') + reply.send('test') + }) + + // Support semicolon delimiter + let res = await fastify.inject({ + method: 'GET', + url: '/test;foo=bar&baz=faz' + }) + t.assert.strictEqual(res.statusCode, 200) + + // Support query string `?` delimiter + res = await fastify.inject({ + method: 'GET', + url: '/test?foo=bar&baz=faz' + }) + t.assert.strictEqual(res.statusCode, 200) +}) + +test('Should support extra find-my-way options', async t => { + t.plan(1) + // Use a real upstream option from find-my-way + const fastify = Fastify({ + routerOptions: { + buildPrettyMeta: (route) => { + const cleanMeta = Object.assign({}, route.store) + return cleanMeta + } + } + }) + + t.after(() => fastify.close()) + + await fastify.ready() + + // Ensure the option is preserved after validation + t.assert.strictEqual(typeof fastify.initialConfig.routerOptions.buildPrettyMeta, 'function') +}) + +test('Should allow reusing a routerOptions object across instances', async t => { + t.plan(1) + + const options = { + routerOptions: { + maxParamLength: 2048 + } + } + + const app1 = Fastify(options) + const app2 = Fastify(options) + + t.after(() => Promise.all([ + app1.close(), + app2.close() + ])) + + const response = await app2.inject('/not-found') + t.assert.strictEqual(response.statusCode, 404) +}) + +test('Should not mutate user-provided routerOptions object', async t => { + t.plan(4) + + const routerOptions = { + maxParamLength: 2048 + } + const options = { routerOptions } + + const app = Fastify(options) + t.after(() => app.close()) + + await app.ready() + + t.assert.deepStrictEqual(Object.keys(routerOptions), ['maxParamLength']) + t.assert.strictEqual(routerOptions.maxParamLength, 2048) + t.assert.strictEqual(routerOptions.defaultRoute, undefined) + t.assert.strictEqual(routerOptions.onBadUrl, undefined) +}) diff --git a/services/slides/node_modules/fastify/test/same-shape.test.js b/services/slides/node_modules/fastify/test/same-shape.test.js new file mode 100644 index 0000000000000000000000000000000000000000..f41627133394ab420161353963817fdc758b18a3 --- /dev/null +++ b/services/slides/node_modules/fastify/test/same-shape.test.js @@ -0,0 +1,124 @@ +'use strict' + +const { test } = require('node:test') +const fastify = require('..') + +test('same shape on Request', async (t) => { + t.plan(1) + + const app = fastify() + + let request + + app.decorateRequest('user') + + app.addHook('preHandler', (req, reply, done) => { + if (request) { + req.user = 'User' + } + done() + }) + + app.get('/', (req, reply) => { + if (request) { + t.assert.deepStrictEqual(request, req) + } + + request = req + + return 'hello world' + }) + + await app.inject('/') + await app.inject('/') +}) + +test('same shape on Request when object', async (t) => { + t.plan(1) + + const app = fastify() + + let request + + app.decorateRequest('object', null) + + app.addHook('preHandler', (req, reply, done) => { + if (request) { + req.object = {} + } + done() + }) + + app.get('/', (req, reply) => { + if (request) { + t.assert.deepStrictEqual(request, req) + } + + request = req + + return 'hello world' + }) + + await app.inject('/') + await app.inject('/') +}) + +test('same shape on Reply', async (t) => { + t.plan(1) + + const app = fastify() + + let _reply + + app.decorateReply('user') + + app.addHook('preHandler', (req, reply, done) => { + if (_reply) { + reply.user = 'User' + } + done() + }) + + app.get('/', (req, reply) => { + if (_reply) { + t.assert.deepStrictEqual(_reply, reply) + } + + _reply = reply + + return 'hello world' + }) + + await app.inject('/') + await app.inject('/') +}) + +test('same shape on Reply when object', async (t) => { + t.plan(1) + + const app = fastify() + + let _reply + + app.decorateReply('object', null) + + app.addHook('preHandler', (req, reply, done) => { + if (_reply) { + reply.object = {} + } + done() + }) + + app.get('/', (req, reply) => { + if (_reply) { + t.assert.deepStrictEqual(_reply, reply) + } + + _reply = reply + + return 'hello world' + }) + + await app.inject('/') + await app.inject('/') +}) diff --git a/services/slides/node_modules/fastify/test/schema-examples.test.js b/services/slides/node_modules/fastify/test/schema-examples.test.js new file mode 100644 index 0000000000000000000000000000000000000000..7292a8136ae9da7b52b19c29dba0531eed5417c4 --- /dev/null +++ b/services/slides/node_modules/fastify/test/schema-examples.test.js @@ -0,0 +1,661 @@ +'use strict' + +const { test } = require('node:test') +const localize = require('ajv-i18n') +const Fastify = require('..') + +test('Example - URI $id', (t, done) => { + t.plan(1) + const fastify = Fastify() + fastify.addSchema({ + $id: 'http://fastify.test/', + type: 'object', + properties: { + hello: { type: 'string' } + } + }) + + fastify.post('/', { + handler () { }, + schema: { + body: { + type: 'array', + items: { $ref: 'http://fastify.test#/properties/hello' } + } + } + }) + + fastify.ready(err => { + t.assert.ifError(err) + done() + }) +}) + +test('Example - string $id', (t, done) => { + t.plan(1) + const fastify = Fastify() + fastify.addSchema({ + $id: 'commonSchema', + type: 'object', + properties: { + hello: { type: 'string' } + } + }) + + fastify.post('/', { + handler () { }, + schema: { + body: { $ref: 'commonSchema#' }, + headers: { $ref: 'commonSchema#' } + } + }) + + fastify.ready(err => { + t.assert.ifError(err) + done() + }) +}) + +test('Example - get schema', (t, done) => { + t.plan(1) + const fastify = Fastify() + fastify.addSchema({ + $id: 'schemaId', + type: 'object', + properties: { + hello: { type: 'string' } + } + }) + + const mySchemas = fastify.getSchemas() + const mySchema = fastify.getSchema('schemaId') + t.assert.deepStrictEqual(mySchemas.schemaId, mySchema) + done() +}) + +test('Example - get schema encapsulated', async t => { + const fastify = Fastify() + + fastify.addSchema({ $id: 'one', my: 'hello' }) + // will return only `one` schema + fastify.get('/', (request, reply) => { reply.send(fastify.getSchemas()) }) + + fastify.register((instance, opts, done) => { + instance.addSchema({ $id: 'two', my: 'ciao' }) + // will return `one` and `two` schemas + instance.get('/sub', (request, reply) => { reply.send(instance.getSchemas()) }) + + instance.register((subinstance, opts, done) => { + subinstance.addSchema({ $id: 'three', my: 'hola' }) + // will return `one`, `two` and `three` + subinstance.get('/deep', (request, reply) => { reply.send(subinstance.getSchemas()) }) + done() + }) + done() + }) + + const r1 = await fastify.inject('/') + const r2 = await fastify.inject('/sub') + const r3 = await fastify.inject('/deep') + + t.assert.deepStrictEqual(Object.keys(r1.json()), ['one']) + t.assert.deepStrictEqual(Object.keys(r2.json()), ['one', 'two']) + t.assert.deepStrictEqual(Object.keys(r3.json()), ['one', 'two', 'three']) +}) + +test('Example - validation', (t, done) => { + t.plan(1) + const fastify = Fastify({ + ajv: { + customOptions: { + allowUnionTypes: true + } + } + }) + const handler = () => { } + + const bodyJsonSchema = { + type: 'object', + required: ['requiredKey'], + properties: { + someKey: { type: 'string' }, + someOtherKey: { type: 'number' }, + requiredKey: { + type: 'array', + maxItems: 3, + items: { type: 'integer' } + }, + nullableKey: { type: ['number', 'null'] }, // or { type: 'number', nullable: true } + multipleTypesKey: { type: ['boolean', 'number'] }, + multipleRestrictedTypesKey: { + oneOf: [ + { type: 'string', maxLength: 5 }, + { type: 'number', minimum: 10 } + ] + }, + enumKey: { + type: 'string', + enum: ['John', 'Foo'] + }, + notTypeKey: { + not: { type: 'array' } + } + } + } + + const queryStringJsonSchema = { + type: 'object', + properties: { + name: { type: 'string' }, + excitement: { type: 'integer' } + } + } + + const paramsJsonSchema = { + type: 'object', + properties: { + par1: { type: 'string' }, + par2: { type: 'number' } + } + } + + const headersJsonSchema = { + type: 'object', + properties: { + 'x-foo': { type: 'string' } + }, + required: ['x-foo'] + } + + const schema = { + body: bodyJsonSchema, + querystring: queryStringJsonSchema, + params: paramsJsonSchema, + headers: headersJsonSchema + } + + fastify.post('/the/url', { schema }, handler) + fastify.ready(err => { + t.assert.ifError(err) + done() + }) +}) + +test('Example - ajv config', (t, done) => { + t.plan(1) + + const fastify = Fastify({ + ajv: { + plugins: [ + require('ajv-merge-patch') + ] + } + }) + + fastify.post('/', { + handler (req, reply) { reply.send({ ok: 1 }) }, + schema: { + body: { + $patch: { + source: { + type: 'object', + properties: { + q: { + type: 'string' + } + } + }, + with: [ + { + op: 'add', + path: '/properties/q', + value: { type: 'number' } + } + ] + } + } + } + }) + + fastify.post('/foo', { + handler (req, reply) { reply.send({ ok: 1 }) }, + schema: { + body: { + $merge: { + source: { + type: 'object', + properties: { + q: { + type: 'string' + } + } + }, + with: { + required: ['q'] + } + } + } + } + }) + + fastify.ready(err => { + t.assert.ifError(err) + done() + }) +}) + +test('Example Joi', (t, done) => { + t.plan(1) + const fastify = Fastify() + const handler = () => { } + + const Joi = require('joi') + fastify.post('/the/url', { + schema: { + body: Joi.object().keys({ + hello: Joi.string().required() + }).required() + }, + validatorCompiler: ({ schema, method, url, httpPart }) => { + return data => schema.validate(data) + } + }, handler) + + fastify.ready(err => { + t.assert.ifError(err) + done() + }) +}) + +test('Example yup', (t, done) => { + t.plan(1) + const fastify = Fastify() + const handler = () => { } + + const yup = require('yup') + // Validation options to match ajv's baseline options used in Fastify + const yupOptions = { + strict: false, + abortEarly: false, // return all errors + stripUnknown: true, // remove additional properties + recursive: true + } + + fastify.post('/the/url', { + schema: { + body: yup.object({ + age: yup.number().integer().required(), + sub: yup.object().shape({ + name: yup.string().required() + }).required() + }) + }, + validatorCompiler: ({ schema, method, url, httpPart }) => { + return function (data) { + // with option strict = false, yup `validateSync` function returns the coerced value if validation was successful, or throws if validation failed + try { + const result = schema.validateSync(data, yupOptions) + return { value: result } + } catch (e) { + return { error: e } + } + } + } + }, handler) + + fastify.ready(err => { + t.assert.ifError(err) + done() + }) +}) + +test('Example - serialization', (t, done) => { + t.plan(1) + const fastify = Fastify() + const handler = () => { } + + const schema = { + response: { + 200: { + type: 'object', + properties: { + value: { type: 'string' }, + otherValue: { type: 'boolean' } + } + } + } + } + + fastify.post('/the/url', { schema }, handler) + fastify.ready(err => { + t.assert.ifError(err) + done() + }) +}) + +test('Example - serialization 2', (t, done) => { + t.plan(1) + const fastify = Fastify() + const handler = () => { } + + const schema = { + response: { + '2xx': { + type: 'object', + properties: { + value: { type: 'string' }, + otherValue: { type: 'boolean' } + } + }, + 201: { + // the contract syntax + value: { type: 'string' } + } + } + } + + fastify.post('/the/url', { schema }, handler) + fastify.ready(err => { + t.assert.ifError(err) + done() + }) +}) + +test('Example - serializator', (t, done) => { + t.plan(1) + const fastify = Fastify() + + fastify.setSerializerCompiler(({ schema, method, url, httpStatus }) => { + return data => JSON.stringify(data) + }) + + fastify.get('/user', { + handler (req, reply) { + reply.send({ id: 1, name: 'Foo', image: 'BIG IMAGE' }) + }, + schema: { + response: { + '2xx': { + type: 'object', + properties: { + id: { type: 'number' }, + name: { type: 'string' } + } + } + } + } + }) + + fastify.ready(err => { + t.assert.ifError(err) + done() + }) +}) + +test('Example - schemas examples', (t, done) => { + t.plan(1) + const fastify = Fastify() + const handler = () => { } + + fastify.addSchema({ + $id: 'http://foo/common.json', + type: 'object', + definitions: { + foo: { + $id: '#address', + type: 'object', + properties: { + city: { type: 'string' } + } + } + } + }) + + fastify.addSchema({ + $id: 'http://foo/shared.json', + type: 'object', + definitions: { + foo: { + type: 'object', + properties: { + city: { type: 'string' } + } + } + } + }) + + const refToId = { + type: 'object', + definitions: { + foo: { + $id: '#address', + type: 'object', + properties: { + city: { type: 'string' } + } + } + }, + properties: { + home: { $ref: '#address' }, + work: { $ref: '#address' } + } + } + + const refToDefinitions = { + type: 'object', + definitions: { + foo: { + $id: '#address', + type: 'object', + properties: { + city: { type: 'string' } + } + } + }, + properties: { + home: { $ref: '#/definitions/foo' }, + work: { $ref: '#/definitions/foo' } + } + } + + const refToSharedSchemaId = { + type: 'object', + properties: { + home: { $ref: 'http://foo/common.json#address' }, + work: { $ref: 'http://foo/common.json#address' } + } + } + + const refToSharedSchemaDefinitions = { + type: 'object', + properties: { + home: { $ref: 'http://foo/shared.json#/definitions/foo' }, + work: { $ref: 'http://foo/shared.json#/definitions/foo' } + } + } + + fastify.post('/', { + handler, + schema: { + body: refToId, + headers: refToDefinitions, + params: refToSharedSchemaId, + query: refToSharedSchemaDefinitions + } + + }) + + fastify.ready(err => { + t.assert.ifError(err) + done() + }) +}) + +test('should return custom error messages with ajv-errors', (t, done) => { + t.plan(3) + + const fastify = Fastify({ + ajv: { + customOptions: { allErrors: true }, + plugins: [ + require('ajv-errors') + ] + } + }) + + const schema = { + body: { + type: 'object', + properties: { + name: { type: 'string' }, + work: { type: 'string' }, + age: { + type: 'number', + errorMessage: { + type: 'bad age - should be num' + } + } + }, + required: ['name', 'work'], + errorMessage: { + required: { + name: 'name please', + work: 'work please', + age: 'age please' + } + } + } + } + + fastify.post('/', { schema }, function (req, reply) { + reply.code(200).send(req.body.name) + }) + + fastify.inject({ + method: 'POST', + payload: { + hello: 'salman', + age: 'bad' + }, + url: '/' + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { + statusCode: 400, + code: 'FST_ERR_VALIDATION', + error: 'Bad Request', + message: 'body/age bad age - should be num, body name please, body work please' + }) + t.assert.strictEqual(res.statusCode, 400) + done() + }) +}) + +test('should be able to handle formats of ajv-formats when added by plugins option', (t, done) => { + t.plan(3) + + const fastify = Fastify({ + ajv: { + plugins: [ + require('ajv-formats') + ] + } + }) + + const schema = { + body: { + type: 'object', + properties: { + id: { type: 'string', format: 'uuid' }, + email: { type: 'string', format: 'email' } + }, + required: ['id', 'email'] + } + } + + fastify.post('/', { schema }, function (req, reply) { + reply.code(200).send(req.body.id) + }) + + fastify.inject({ + method: 'POST', + payload: { + id: '254381a5-888c-4b41-8116-e3b1a54980bd', + email: 'info@fastify.dev' + }, + url: '/' + }, (_err, res) => { + t.assert.strictEqual(res.body, '254381a5-888c-4b41-8116-e3b1a54980bd') + t.assert.strictEqual(res.statusCode, 200) + }) + + fastify.inject({ + method: 'POST', + payload: { + id: 'invalid', + email: 'info@fastify.dev' + }, + url: '/' + }, (_err, res) => { + t.assert.deepStrictEqual(JSON.parse(res.payload), { + statusCode: 400, + code: 'FST_ERR_VALIDATION', + error: 'Bad Request', + message: 'body/id must match format "uuid"' + }) + done() + }) +}) + +test('should return localized error messages with ajv-i18n', (t, done) => { + t.plan(3) + + const schema = { + body: { + type: 'object', + properties: { + name: { type: 'string' }, + work: { type: 'string' } + }, + required: ['name', 'work'] + } + } + + const fastify = Fastify({ + ajv: { + customOptions: { allErrors: true } + } + }) + + fastify.setErrorHandler(function (error, request, reply) { + if (error.validation) { + localize.ru(error.validation) + reply.status(400).send(error.validation) + return + } + reply.send(error) + }) + + fastify.post('/', { schema }, function (req, reply) { + reply.code(200).send(req.body.name) + }) + + fastify.inject({ + method: 'POST', + payload: { + name: 'salman' + }, + url: '/' + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), [{ + instancePath: '', + keyword: 'required', + message: 'должно иметь обязательное поле work', + params: { missingProperty: 'work' }, + schemaPath: '#/required' + }]) + t.assert.strictEqual(res.statusCode, 400) + done() + }) +}) diff --git a/services/slides/node_modules/fastify/test/schema-feature.test.js b/services/slides/node_modules/fastify/test/schema-feature.test.js new file mode 100644 index 0000000000000000000000000000000000000000..6e54d1817f7d735258c8b2ddf85eca1a5685087c --- /dev/null +++ b/services/slides/node_modules/fastify/test/schema-feature.test.js @@ -0,0 +1,2198 @@ +'use strict' + +const { test } = require('node:test') +const Fastify = require('..') +const fp = require('fastify-plugin') +const deepClone = require('rfdc')({ circles: true, proto: false }) +const Ajv = require('ajv') +const { kSchemaController } = require('../lib/symbols.js') +const { FSTWRN001 } = require('../lib/warnings') +const { waitForCb } = require('./toolkit') + +const echoParams = (req, reply) => { reply.send(req.params) } +const echoBody = (req, reply) => { reply.send(req.body) } + + ;['addSchema', 'getSchema', 'getSchemas', 'setValidatorCompiler', 'setSerializerCompiler'].forEach(f => { + test(`Should expose ${f} function`, t => { + t.plan(1) + const fastify = Fastify() + t.assert.strictEqual(typeof fastify[f], 'function') + }) +}) + +;['setValidatorCompiler', 'setSerializerCompiler'].forEach(f => { + test(`cannot call ${f} after binding`, (t, testDone) => { + t.plan(2) + const fastify = Fastify() + t.after(() => fastify.close()) + fastify.listen({ port: 0 }, err => { + t.assert.ifError(err) + try { + fastify[f](() => { }) + t.assert.fail() + } catch (e) { + t.assert.ok(true) + testDone() + } + }) + }) +}) + +test('The schemas should be added to an internal storage', t => { + t.plan(1) + const fastify = Fastify() + const schema = { $id: 'id', my: 'schema' } + fastify.addSchema(schema) + t.assert.deepStrictEqual(fastify[kSchemaController].schemaBucket.store, { id: schema }) +}) + +test('The schemas should be accessible via getSchemas', t => { + t.plan(1) + const fastify = Fastify() + + const schemas = { + id: { $id: 'id', my: 'schema' }, + abc: { $id: 'abc', my: 'schema' }, + bcd: { $id: 'bcd', my: 'schema', properties: { a: 'a', b: 1 } } + } + + Object.values(schemas).forEach(schema => { fastify.addSchema(schema) }) + t.assert.deepStrictEqual(fastify.getSchemas(), schemas) +}) + +test('The schema should be accessible by id via getSchema', (t, testDone) => { + t.plan(5) + const fastify = Fastify() + + const schemas = [ + { $id: 'id', my: 'schema' }, + { $id: 'abc', my: 'schema' }, + { $id: 'bcd', my: 'schema', properties: { a: 'a', b: 1 } } + ] + schemas.forEach(schema => { fastify.addSchema(schema) }) + t.assert.deepStrictEqual(fastify.getSchema('abc'), schemas[1]) + t.assert.deepStrictEqual(fastify.getSchema('id'), schemas[0]) + t.assert.deepStrictEqual(fastify.getSchema('foo'), undefined) + + fastify.register((instance, opts, done) => { + const pluginSchema = { $id: 'cde', my: 'schema' } + instance.addSchema(pluginSchema) + t.assert.deepStrictEqual(instance.getSchema('cde'), pluginSchema) + done() + }) + + fastify.ready(err => { + t.assert.ifError(err) + testDone() + }) +}) + +test('Get validatorCompiler after setValidatorCompiler', (t, testDone) => { + t.plan(2) + const myCompiler = () => { } + const fastify = Fastify() + fastify.setValidatorCompiler(myCompiler) + const sc = fastify.validatorCompiler + t.assert.ok(Object.is(myCompiler, sc)) + fastify.ready(err => { + t.assert.ifError(err) + testDone() + }) +}) + +test('Get serializerCompiler after setSerializerCompiler', (t, testDone) => { + t.plan(2) + const myCompiler = () => { } + const fastify = Fastify() + fastify.setSerializerCompiler(myCompiler) + const sc = fastify.serializerCompiler + t.assert.ok(Object.is(myCompiler, sc)) + fastify.ready(err => { + t.assert.ifError(err) + testDone() + }) +}) + +test('Get compilers is empty when settle on routes', (t, testDone) => { + t.plan(3) + + const fastify = Fastify() + + fastify.post('/', { + schema: { + body: { type: 'object', properties: { hello: { type: 'string' } } }, + response: { + '2xx': { + type: 'object', + properties: { + foo: { type: 'array', items: { type: 'string' } } + } + } + } + }, + validatorCompiler: ({ schema, method, url, httpPart }) => { }, + serializerCompiler: ({ schema, method, url, httpPart }) => { } + }, function (req, reply) { + reply.send('ok') + }) + + fastify.inject({ + method: 'POST', + payload: {}, + url: '/' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(fastify.validatorCompiler, undefined) + t.assert.strictEqual(fastify.serializerCompiler, undefined) + testDone() + }) +}) + +test('Should throw if the $id property is missing', t => { + t.plan(1) + const fastify = Fastify() + try { + fastify.addSchema({ type: 'string' }) + t.assert.fail() + } catch (err) { + t.assert.strictEqual(err.code, 'FST_ERR_SCH_MISSING_ID') + } +}) + +test('Cannot add multiple times the same id', t => { + t.plan(2) + const fastify = Fastify() + + fastify.addSchema({ $id: 'id' }) + try { + fastify.addSchema({ $id: 'id' }) + } catch (err) { + t.assert.strictEqual(err.code, 'FST_ERR_SCH_ALREADY_PRESENT') + t.assert.strictEqual(err.message, 'Schema with id \'id\' already declared!') + } +}) + +test('Cannot add schema for query and querystring', (t, testDone) => { + t.plan(2) + const fastify = Fastify() + + fastify.get('/', { + handler: () => { }, + schema: { + query: { + type: 'object', + properties: { + foo: { type: 'string' } + } + }, + querystring: { + type: 'object', + properties: { + foo: { type: 'string' } + } + } + } + }) + + fastify.ready(err => { + t.assert.strictEqual(err.code, 'FST_ERR_SCH_DUPLICATE') + t.assert.strictEqual(err.message, 'Schema with \'querystring\' already present!') + testDone() + }) +}) + +test('Should throw of the schema does not exists in input', (t, testDone) => { + t.plan(2) + const fastify = Fastify() + + fastify.get('/:id', { + handler: echoParams, + schema: { + params: { + type: 'object', + properties: { + name: { $ref: '#notExist' } + } + } + } + }) + + fastify.ready(err => { + t.assert.strictEqual(err.code, 'FST_ERR_SCH_VALIDATION_BUILD') + t.assert.strictEqual(err.message, "Failed building the validation schema for GET: /:id, due to error can't resolve reference #notExist from id #") + testDone() + }) +}) + +test('Should throw if schema is missing for content type', (t, testDone) => { + t.plan(2) + + const fastify = Fastify() + fastify.post('/', { + handler: echoBody, + schema: { + body: { + content: { + 'application/json': {} + } + } + } + }) + + fastify.ready(err => { + t.assert.strictEqual(err.code, 'FST_ERR_SCH_CONTENT_MISSING_SCHEMA') + t.assert.strictEqual(err.message, "Schema is missing for the content type 'application/json'") + testDone() + }) +}) + +test('Should throw of the schema does not exists in output', (t, testDone) => { + t.plan(2) + const fastify = Fastify() + + fastify.get('/:id', { + handler: echoParams, + schema: { + response: { + '2xx': { + type: 'object', + properties: { + name: { $ref: '#notExist' } + } + } + } + } + }) + + fastify.ready(err => { + t.assert.strictEqual(err.code, 'FST_ERR_SCH_SERIALIZATION_BUILD') + t.assert.match(err.message, /^Failed building the serialization schema for GET: \/:id, due to error Cannot find reference.*/) // error from fast-json-stringify + testDone() + }) +}) + +test('Should not change the input schemas', (t, testDone) => { + t.plan(4) + + const theSchema = { + $id: 'helloSchema', + type: 'object', + definitions: { + hello: { type: 'string' } + } + } + + const fastify = Fastify() + fastify.post('/', { + handler: echoBody, + schema: { + body: { + type: 'object', + additionalProperties: false, + properties: { + name: { $ref: 'helloSchema#/definitions/hello' } + } + }, + response: { + '2xx': { + type: 'object', + properties: { + name: { $ref: 'helloSchema#/definitions/hello' } + } + } + } + } + }) + fastify.addSchema(theSchema) + + fastify.inject({ + url: '/', + method: 'POST', + payload: { name: 'Foo', surname: 'Bar' } + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(res.json(), { name: 'Foo' }) + t.assert.ok(theSchema.$id, 'the $id is not removed') + t.assert.deepStrictEqual(fastify.getSchema('helloSchema'), theSchema) + testDone() + }) +}) + +test('Should emit warning if the schema headers is undefined', (t, testDone) => { + t.plan(4) + const fastify = Fastify() + + process.on('warning', onWarning) + function onWarning (warning) { + t.assert.strictEqual(warning.name, 'FastifyWarning') + t.assert.strictEqual(warning.code, FSTWRN001.code) + } + + t.after(() => { + process.removeListener('warning', onWarning) + FSTWRN001.emitted = false + }) + + fastify.post('/:id', { + handler: echoParams, + schema: { + headers: undefined + } + }) + + fastify.inject({ + method: 'POST', + url: '/123' + }, (error, res) => { + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 200) + testDone() + }) +}) + +test('Should emit warning if the schema body is undefined', (t, testDone) => { + t.plan(4) + const fastify = Fastify() + + process.on('warning', onWarning) + function onWarning (warning) { + t.assert.strictEqual(warning.name, 'FastifyWarning') + t.assert.strictEqual(warning.code, FSTWRN001.code) + } + + t.after(() => { + process.removeListener('warning', onWarning) + FSTWRN001.emitted = false + }) + + fastify.post('/:id', { + handler: echoParams, + schema: { + body: undefined + } + }) + + fastify.inject({ + method: 'POST', + url: '/123' + }, (error, res) => { + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 200) + testDone() + }) +}) + +test('Should emit warning if the schema query is undefined', (t, testDone) => { + t.plan(4) + const fastify = Fastify() + + process.on('warning', onWarning) + function onWarning (warning) { + t.assert.strictEqual(warning.name, 'FastifyWarning') + t.assert.strictEqual(warning.code, FSTWRN001.code) + } + + t.after(() => { + process.removeListener('warning', onWarning) + FSTWRN001.emitted = false + }) + + fastify.post('/:id', { + handler: echoParams, + schema: { + querystring: undefined + } + }) + + fastify.inject({ + method: 'POST', + url: '/123' + }, (error, res) => { + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 200) + testDone() + }) +}) + +test('Should emit warning if the schema params is undefined', (t, testDone) => { + t.plan(4) + const fastify = Fastify() + + process.on('warning', onWarning) + function onWarning (warning) { + t.assert.strictEqual(warning.name, 'FastifyWarning') + t.assert.strictEqual(warning.code, FSTWRN001.code) + } + + t.after(() => { + process.removeListener('warning', onWarning) + FSTWRN001.emitted = false + }) + + fastify.post('/:id', { + handler: echoParams, + schema: { + params: undefined + } + }) + + fastify.inject({ + method: 'POST', + url: '/123' + }, (error, res) => { + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 200) + testDone() + }) +}) + +test('Should emit a warning for every route with undefined schema', (t, testDone) => { + t.plan(16) + const fastify = Fastify() + + let runs = 0 + const expectedWarningEmitted = [0, 1, 2, 3] + // It emits 4 warnings: + // - 2 - GET and HEAD for /undefinedParams/:id + // - 2 - GET and HEAD for /undefinedBody/:id + // => 3 x 4 assertions = 12 assertions + function onWarning (warning) { + t.assert.strictEqual(warning.name, 'FastifyWarning') + t.assert.strictEqual(warning.code, FSTWRN001.code) + t.assert.strictEqual(runs++, expectedWarningEmitted.shift()) + } + + process.on('warning', onWarning) + t.after(() => { + process.removeListener('warning', onWarning) + FSTWRN001.emitted = false + }) + + fastify.get('/undefinedParams/:id', { + handler: echoParams, + schema: { + params: undefined + } + }) + + fastify.get('/undefinedBody/:id', { + handler: echoParams, + schema: { + body: undefined + } + }) + + fastify.inject({ + method: 'GET', + url: '/undefinedParams/123' + }, (error, res) => { + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 200) + }) + + fastify.inject({ + method: 'GET', + url: '/undefinedBody/123' + }, (error, res) => { + t.assert.ifError(error) + t.assert.strictEqual(res.statusCode, 200) + testDone() + }) +}) + +test('First level $ref', (t, testDone) => { + t.plan(2) + const fastify = Fastify() + + fastify.addSchema({ + $id: 'test', + type: 'object', + properties: { + id: { type: 'number' } + } + }) + + fastify.get('/:id', { + handler: (req, reply) => { + reply.send({ id: req.params.id * 2, ignore: 'it' }) + }, + schema: { + params: { $ref: 'test#' }, + response: { + 200: { $ref: 'test#' } + } + } + }) + + fastify.inject({ + method: 'GET', + url: '/123' + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(res.json(), { id: 246 }) + testDone() + }) +}) + +test('Customize validator compiler in instance and route', t => { + t.plan(28) + const fastify = Fastify({ exposeHeadRoutes: false }) + + fastify.setValidatorCompiler(({ schema, method, url, httpPart }) => { + t.assert.strictEqual(method, 'POST') // run 4 times + t.assert.strictEqual(url, '/:id') // run 4 times + switch (httpPart) { + case 'body': + t.assert.ok('body evaluated') + return body => { + t.assert.deepStrictEqual(body, { foo: ['bar', 'BAR'] }) + return true + } + case 'params': + t.assert.ok('params evaluated') + return params => { + t.assert.strictEqual(params.id, '1234') + return true + } + case 'querystring': + t.assert.ok('querystring evaluated') + return query => { + t.assert.strictEqual(query.lang, 'en') + return true + } + case 'headers': + t.assert.ok('headers evaluated') + return headers => { + t.assert.strictEqual(headers.x, 'hello') + return true + } + case '2xx': + t.assert.fail('the validator doesn\'t process the response') + break + default: + t.assert.fail(`unknown httpPart ${httpPart}`) + } + }) + + fastify.post('/:id', { + handler: echoBody, + schema: { + query: { + type: 'object', + properties: { + lang: { type: 'string', enum: ['it', 'en'] } + } + }, + headers: { + type: 'object', + properties: { + x: { type: 'string' } + } + }, + params: { + type: 'object', + properties: { + id: { type: 'number' } + } + }, + body: { + type: 'object', + properties: { + foo: { type: 'array' } + } + }, + response: { + '2xx': { + type: 'object', + properties: { + foo: { type: 'array', items: { type: 'string' } } + } + } + } + } + }) + + fastify.get('/wow/:id', { + handler: echoParams, + validatorCompiler: ({ schema, method, url, httpPart }) => { + t.assert.strictEqual(method, 'GET') // run 3 times (params, headers, query) + t.assert.strictEqual(url, '/wow/:id') // run 4 times + return () => { return true } // ignore the validation + }, + schema: { + query: { + type: 'object', + properties: { + lang: { type: 'string', enum: ['it', 'en'] } + } + }, + headers: { + type: 'object', + properties: { + x: { type: 'string' } + } + }, + params: { + type: 'object', + properties: { + id: { type: 'number' } + } + }, + response: { + '2xx': { + type: 'object', + properties: { + foo: { type: 'array', items: { type: 'string' } } + } + } + } + } + }) + + const { stepIn, patience } = waitForCb({ steps: 2 }) + + fastify.inject({ + url: '/1234', + method: 'POST', + headers: { x: 'hello' }, + query: { lang: 'en' }, + payload: { foo: ['bar', 'BAR'] } + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(res.json(), { foo: ['bar', 'BAR'] }) + stepIn() + }) + + fastify.inject({ + url: '/wow/should-be-a-num', + method: 'GET', + headers: { x: 'hello' }, + query: { lang: 'jp' } // not in the enum + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) // the validation is always true + t.assert.deepStrictEqual(res.json(), {}) + stepIn() + }) + + return patience +}) + +test('Use the same schema across multiple routes', (t, testDone) => { + t.plan(4) + const fastify = Fastify() + + fastify.addSchema({ + $id: 'test', + type: 'object', + properties: { + id: { type: 'number' } + } + }) + + fastify.get('/first/:id', { + schema: { + params: { + type: 'object', + properties: { + id: { $ref: 'test#/properties/id' } + } + } + }, + handler: (req, reply) => { + reply.send(typeof req.params.id) + } + }) + + fastify.get('/second/:id', { + schema: { + params: { + type: 'object', + properties: { + id: { $ref: 'test#/properties/id' } + } + } + }, + handler: (req, reply) => { + reply.send(typeof req.params.id) + } + }) + + fastify.inject({ + method: 'GET', + url: '/first/123' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.payload, 'number') + }) + + fastify.inject({ + method: 'GET', + url: '/second/123' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.payload, 'number') + testDone() + }) +}) + +test('Encapsulation should intervene', (t, testDone) => { + t.plan(2) + const fastify = Fastify() + + fastify.register((instance, opts, done) => { + instance.addSchema({ + $id: 'encapsulation', + type: 'object', + properties: { + id: { type: 'number' } + } + }) + done() + }) + + fastify.register((instance, opts, done) => { + instance.get('/:id', { + handler: echoParams, + schema: { + params: { + type: 'object', + properties: { + id: { $ref: 'encapsulation#/properties/id' } + } + } + } + }) + done() + }) + + fastify.ready(err => { + t.assert.strictEqual(err.code, 'FST_ERR_SCH_VALIDATION_BUILD') + t.assert.strictEqual(err.message, "Failed building the validation schema for GET: /:id, due to error can't resolve reference encapsulation#/properties/id from id #") + testDone() + }) +}) + +test('Encapsulation isolation', (t, testDone) => { + t.plan(1) + const fastify = Fastify() + + fastify.register((instance, opts, done) => { + instance.addSchema({ $id: 'id' }) + done() + }) + + fastify.register((instance, opts, done) => { + instance.addSchema({ $id: 'id' }) + done() + }) + + fastify.ready(err => { + t.assert.ifError(err) + testDone() + }) +}) + +test('Add schema after register', (t, testDone) => { + t.plan(5) + + const fastify = Fastify() + fastify.register((instance, opts, done) => { + instance.get('/:id', { + handler: echoParams, + schema: { + params: { $ref: 'test#' } + } + }) + + // add it to the parent instance + fastify.addSchema({ + $id: 'test', + type: 'object', + properties: { + id: { type: 'number' } + } + }) + + try { + instance.addSchema({ $id: 'test' }) + } catch (err) { + t.assert.strictEqual(err.code, 'FST_ERR_SCH_ALREADY_PRESENT') + t.assert.strictEqual(err.message, 'Schema with id \'test\' already declared!') + } + done() + }) + + fastify.inject({ + method: 'GET', + url: '/4242' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(res.json(), { id: 4242 }) + testDone() + }) +}) + +test('Encapsulation isolation for getSchemas', (t, testDone) => { + t.plan(5) + const fastify = Fastify() + + let pluginDeepOneSide + let pluginDeepOne + let pluginDeepTwo + + const schemas = { + z: { $id: 'z', my: 'schema' }, + a: { $id: 'a', my: 'schema' }, + b: { $id: 'b', my: 'schema' }, + c: { $id: 'c', my: 'schema', properties: { a: 'a', b: 1 } } + } + + fastify.addSchema(schemas.z) + + fastify.register((instance, opts, done) => { + instance.addSchema(schemas.a) + pluginDeepOneSide = instance + done() + }) + + fastify.register((instance, opts, done) => { + instance.addSchema(schemas.b) + instance.register((subinstance, opts, done) => { + subinstance.addSchema(schemas.c) + pluginDeepTwo = subinstance + done() + }) + pluginDeepOne = instance + done() + }) + + fastify.ready(err => { + t.assert.ifError(err) + t.assert.deepStrictEqual(fastify.getSchemas(), { z: schemas.z }) + t.assert.deepStrictEqual(pluginDeepOneSide.getSchemas(), { z: schemas.z, a: schemas.a }) + t.assert.deepStrictEqual(pluginDeepOne.getSchemas(), { z: schemas.z, b: schemas.b }) + t.assert.deepStrictEqual(pluginDeepTwo.getSchemas(), { z: schemas.z, b: schemas.b, c: schemas.c }) + testDone() + }) +}) + +test('Use the same schema id in different places', (t, testDone) => { + t.plan(1) + const fastify = Fastify() + + fastify.addSchema({ + $id: 'test', + type: 'object', + properties: { + id: { type: 'number' } + } + }) + + fastify.get('/:id', { + handler: echoParams, + schema: { + response: { + 200: { + type: 'array', + items: { $ref: 'test#/properties/id' } + } + } + } + }) + + fastify.post('/:id', { + handler: echoBody, + schema: { + body: { + type: 'object', + properties: { + id: { $ref: 'test#/properties/id' } + } + }, + response: { + 200: { + type: 'object', + properties: { + id: { $ref: 'test#/properties/id' } + } + } + } + } + }) + + fastify.ready(err => { + t.assert.ifError(err) + testDone() + }) +}) + +test('Get schema anyway should not add `properties` if allOf is present', (t, testDone) => { + t.plan(1) + const fastify = Fastify() + + fastify.addSchema({ + $id: 'first', + type: 'object', + properties: { + first: { type: 'number' } + } + }) + + fastify.addSchema({ + $id: 'second', + type: 'object', + allOf: [ + { + type: 'object', + properties: { + second: { type: 'number' } + } + }, + fastify.getSchema('first') + ] + }) + + fastify.get('/', { + handler: () => { }, + schema: { + querystring: fastify.getSchema('second'), + response: { 200: fastify.getSchema('second') } + } + }) + + fastify.ready(err => { + t.assert.ifError(err) + testDone() + }) +}) + +test('Get schema anyway should not add `properties` if oneOf is present', (t, testDone) => { + t.plan(1) + const fastify = Fastify() + + fastify.addSchema({ + $id: 'first', + type: 'object', + properties: { + first: { type: 'number' } + } + }) + + fastify.addSchema({ + $id: 'second', + type: 'object', + oneOf: [ + { + type: 'object', + properties: { + second: { type: 'number' } + } + }, + fastify.getSchema('first') + ] + }) + + fastify.get('/', { + handler: () => { }, + schema: { + querystring: fastify.getSchema('second'), + response: { 200: fastify.getSchema('second') } + } + }) + + fastify.ready(err => { + t.assert.ifError(err) + testDone() + }) +}) + +test('Get schema anyway should not add `properties` if anyOf is present', (t, testDone) => { + t.plan(1) + const fastify = Fastify() + + fastify.addSchema({ + $id: 'first', + type: 'object', + properties: { + first: { type: 'number' } + } + }) + + fastify.addSchema({ + $id: 'second', + type: 'object', + anyOf: [ + { + type: 'object', + properties: { + second: { type: 'number' } + } + }, + fastify.getSchema('first') + ] + }) + + fastify.get('/', { + handler: () => { }, + schema: { + querystring: fastify.getSchema('second'), + response: { 200: fastify.getSchema('second') } + } + }) + + fastify.ready(err => { + t.assert.ifError(err) + testDone() + }) +}) + +test('Shared schema should be ignored in string enum', (t, testDone) => { + t.plan(2) + const fastify = Fastify() + + fastify.get('/:lang', { + handler: echoParams, + schema: { + params: { + type: 'object', + properties: { + lang: { + type: 'string', + enum: ['Javascript', 'C++', 'C#'] + } + } + } + } + }) + + fastify.inject('/C%23', (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(res.json(), { lang: 'C#' }) + testDone() + }) +}) + +test('Shared schema should NOT be ignored in != string enum', (t, testDone) => { + t.plan(2) + const fastify = Fastify() + + fastify.addSchema({ + $id: 'C', + type: 'object', + properties: { + lang: { + type: 'string', + enum: ['Javascript', 'C++', 'C#'] + } + } + }) + + fastify.post('/:lang', { + handler: echoBody, + schema: { + body: fastify.getSchema('C') + } + }) + + fastify.inject({ + url: '/', + method: 'POST', + payload: { lang: 'C#' } + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(res.json(), { lang: 'C#' }) + testDone() + }) +}) + +test('Case insensitive header validation', (t, testDone) => { + t.plan(2) + const fastify = Fastify() + fastify.get('/', { + handler: (req, reply) => { + reply.code(200).send(req.headers.foobar) + }, + schema: { + headers: { + type: 'object', + required: ['FooBar'], + properties: { + FooBar: { type: 'string' } + } + } + } + }) + fastify.inject({ + url: '/', + method: 'GET', + headers: { + FooBar: 'Baz' + } + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.payload, 'Baz') + testDone() + }) +}) + +test('Not evaluate json-schema $schema keyword', (t, testDone) => { + t.plan(2) + const fastify = Fastify() + fastify.post('/', { + handler: echoBody, + schema: { + body: { + $schema: 'http://json-schema.org/draft-07/schema#', + type: 'object', + additionalProperties: false, + properties: { + hello: { + type: 'string' + } + } + } + } + }) + fastify.inject({ + url: '/', + method: 'POST', + body: { hello: 'world', foo: 'bar' } + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(res.json(), { hello: 'world' }) + testDone() + }) +}) + +test('Validation context in validation result', (t, testDone) => { + t.plan(5) + const fastify = Fastify() + // custom error handler to expose validation context in response, so we can test it later + fastify.setErrorHandler((err, request, reply) => { + t.assert.strictEqual(err instanceof Error, true) + t.assert.ok(err.validation, 'detailed errors') + t.assert.strictEqual(err.validationContext, 'body') + reply.code(400).send() + }) + fastify.post('/', { + handler: echoParams, + schema: { + body: { + type: 'object', + required: ['hello'], + properties: { + hello: { type: 'string' } + } + } + } + }) + fastify.inject({ + method: 'POST', + url: '/', + payload: {} // body lacks required field, will fail validation + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 400) + testDone() + }) +}) + +test('The schema build should not modify the input', (t, testDone) => { + t.plan(3) + const fastify = Fastify() + + const first = { + $id: 'first', + type: 'object', + properties: { + first: { + type: 'number' + } + } + } + + fastify.addSchema(first) + + fastify.addSchema({ + $id: 'second', + type: 'object', + allOf: [ + { + type: 'object', + properties: { + second: { + type: 'number' + } + } + }, + { $ref: 'first#' } + ] + }) + + fastify.post('/', { + schema: { + description: 'get', + body: { $ref: 'second#' }, + response: { + 200: { $ref: 'second#' } + } + }, + handler: (request, reply) => { + reply.send({ hello: 'world' }) + } + }) + + fastify.patch('/', { + schema: { + description: 'patch', + body: { $ref: 'first#' }, + response: { + 200: { $ref: 'first#' } + } + }, + handler: (request, reply) => { + reply.send({ hello: 'world' }) + } + }) + + t.assert.ok(first.$id) + fastify.ready(err => { + t.assert.ifError(err) + t.assert.ok(first.$id) + testDone() + }) +}) + +test('Cross schema reference with encapsulation references', (t, testDone) => { + t.plan(1) + + const fastify = Fastify() + fastify.addSchema({ + $id: 'http://foo/item', + type: 'object', + properties: { foo: { type: 'string' } } + }) + + const refItem = { $ref: 'http://foo/item#' } + + fastify.addSchema({ + $id: 'itemList', + type: 'array', + items: refItem + }) + + fastify.register((instance, opts, done) => { + instance.addSchema({ + $id: 'encapsulation', + type: 'object', + properties: { + id: { type: 'number' }, + item: refItem, + secondItem: refItem + } + }) + + const multipleRef = { + type: 'object', + properties: { + a: { $ref: 'itemList#' }, + b: refItem, + c: refItem, + d: refItem + } + } + + instance.get('/get', { schema: { response: { 200: deepClone(multipleRef) } } }, () => { }) + instance.get('/double-get', { schema: { querystring: multipleRef, response: { 200: multipleRef } } }, () => { }) + instance.post('/post', { schema: { body: multipleRef, response: { 200: multipleRef } } }, () => { }) + instance.post('/double', { schema: { response: { 200: { $ref: 'encapsulation' } } } }, () => { }) + done() + }, { prefix: '/foo' }) + + fastify.post('/post', { schema: { body: refItem, response: { 200: refItem } } }, () => { }) + fastify.get('/get', { schema: { params: refItem, response: { 200: refItem } } }, () => { }) + + fastify.ready(err => { + t.assert.ifError(err) + testDone() + }) +}) + +test('Check how many AJV instances are built #1', (t, testDone) => { + t.plan(12) + const fastify = Fastify() + addRandomRoute(fastify) // this trigger the schema validation creation + t.assert.ok(!fastify.validatorCompiler, 'validator not initialized') + + const instances = [] + fastify.register((instance, opts, done) => { + t.assert.ok(!fastify.validatorCompiler, 'validator not initialized') + instances.push(instance) + done() + }) + fastify.register((instance, opts, done) => { + t.assert.ok(!fastify.validatorCompiler, 'validator not initialized') + addRandomRoute(instance) + instances.push(instance) + done() + instance.register((instance, opts, done) => { + t.assert.ok(!fastify.validatorCompiler, 'validator not initialized') + addRandomRoute(instance) + instances.push(instance) + done() + }) + }) + + fastify.ready(err => { + t.assert.ifError(err) + + t.assert.ok(fastify.validatorCompiler, 'validator initialized on preReady') + fastify.validatorCompiler.checkPointer = true + instances.forEach(i => { + t.assert.ok(i.validatorCompiler, 'validator initialized on preReady') + t.assert.strictEqual(i.validatorCompiler.checkPointer, true, 'validator is only one for all the instances') + }) + testDone() + }) +}) + +test('onReady hook has the compilers ready', (t, testDone) => { + t.plan(6) + + const fastify = Fastify() + + fastify.get(`/${Math.random()}`, { + handler: (req, reply) => reply.send(), + schema: { + headers: { type: 'object' }, + response: { 200: { type: 'object' } } + } + }) + + fastify.addHook('onReady', function (done) { + t.assert.ok(this.validatorCompiler) + t.assert.ok(this.serializerCompiler) + done() + }) + + let hookCallCounter = 0 + fastify.register(async (i, o) => { + i.addHook('onReady', function (done) { + t.assert.ok(this.validatorCompiler) + t.assert.ok(this.serializerCompiler) + done() + }) + + i.register(async (i, o) => { }) + + i.addHook('onReady', function (done) { + hookCallCounter++ + done() + }) + }) + + fastify.ready(err => { + t.assert.ifError(err) + t.assert.strictEqual(hookCallCounter, 1, 'it is called once') + testDone() + }) +}) + +test('Check how many AJV instances are built #2 - verify validatorPool', (t, testDone) => { + t.plan(13) + const fastify = Fastify() + t.assert.ok(!fastify.validatorCompiler, 'validator not initialized') + + fastify.register(function sibling1 (instance, opts, done) { + addRandomRoute(instance) + t.assert.ok(!instance.validatorCompiler, 'validator not initialized') + instance.ready(() => { + t.assert.ok(instance.validatorCompiler, 'validator is initialized') + instance.validatorCompiler.sharedPool = 1 + }) + instance.after(() => { + t.assert.ok(!instance.validatorCompiler, 'validator not initialized') + }) + done() + }) + + fastify.register(function sibling2 (instance, opts, done) { + addRandomRoute(instance) + t.assert.ok(!instance.validatorCompiler, 'validator not initialized') + instance.ready(() => { + t.assert.strictEqual(instance.validatorCompiler.sharedPool, 1, 'this context must share the validator with the same schemas') + instance.validatorCompiler.sharedPool = 2 + }) + instance.after(() => { + t.assert.ok(!instance.validatorCompiler, 'validator not initialized') + }) + + instance.register((instance, opts, done) => { + t.assert.ok(!instance.validatorCompiler, 'validator not initialized') + instance.ready(() => { + t.assert.strictEqual(instance.validatorCompiler.sharedPool, 2, 'this context must share the validator of the parent') + }) + done() + }) + done() + }) + + fastify.register(function sibling3 (instance, opts, done) { + addRandomRoute(instance) + + // this trigger to don't reuse the same compiler pool + instance.addSchema({ $id: 'diff', type: 'object' }) + + t.assert.ok(!instance.validatorCompiler, 'validator not initialized') + instance.ready(() => { + t.assert.ok(instance.validatorCompiler, 'validator is initialized') + t.assert.ok(!instance.validatorCompiler.sharedPool, 'this context has its own compiler') + }) + done() + }) + + fastify.ready(err => { + t.assert.ifError(err) + testDone() + }) +}) + +function addRandomRoute (server) { + server.post(`/${Math.random()}`, + { schema: { body: { type: 'object' } } }, + (req, reply) => reply.send() + ) +} + +test('Add schema order should not break the startup', (t, testDone) => { + t.plan(1) + const fastify = Fastify() + + fastify.get('/', { schema: { random: 'options' } }, () => { }) + + fastify.register(fp((f, opts) => { + f.addSchema({ + $id: 'https://fastify.test/bson/objectId', + type: 'string', + pattern: '\\b[0-9A-Fa-f]{24}\\b' + }) + return Promise.resolve() // avoid async for node 6 + })) + + fastify.get('/:id', { + schema: { + params: { + type: 'object', + properties: { + id: { $ref: 'https://fastify.test/bson/objectId#' } + } + } + } + }, () => { }) + + fastify.ready(err => { + t.assert.ifError(err) + testDone() + }) +}) + +test('The schema compiler recreate itself if needed', (t, testDone) => { + t.plan(1) + const fastify = Fastify() + + fastify.options('/', { schema: { hide: true } }, echoBody) + + fastify.register(function (fastify, options, done) { + fastify.addSchema({ + $id: 'identifier', + type: 'string', + format: 'uuid' + }) + + fastify.get('/:foobarId', { + schema: { + params: { + type: 'object', + properties: { + foobarId: { $ref: 'identifier#' } + } + } + } + }, echoBody) + + done() + }) + + fastify.ready(err => { + t.assert.ifError(err) + testDone() + }) +}) + +test('Schema controller setter', t => { + t.plan(2) + Fastify({ schemaController: {} }) + t.assert.ok('allow empty object') + + try { + Fastify({ schemaController: { bucket: {} } }) + t.fail('the bucket option must be a function') + } catch (err) { + t.assert.strictEqual(err.message, "schemaController.bucket option should be a function, instead got 'object'") + } +}) + +test('Schema controller bucket', (t, testDone) => { + t.plan(10) + + let added = 0 + let builtBucket = 0 + + const initStoreQueue = [] + + function factoryBucket (storeInit) { + builtBucket++ + t.assert.deepStrictEqual(initStoreQueue.pop(), storeInit) + const store = new Map(storeInit) + return { + add (schema) { + added++ + store.set(schema.$id, schema) + }, + getSchema (id) { + return store.get(id) + }, + getSchemas () { + // what is returned by this function, will be the `storeInit` parameter + initStoreQueue.push(store) + return store + } + } + } + + const fastify = Fastify({ + schemaController: { + bucket: factoryBucket + } + }) + + fastify.register(async (instance) => { + instance.addSchema({ $id: 'b', type: 'string' }) + instance.addHook('onReady', function (done) { + t.assert.strictEqual(instance.getSchemas().size, 2) + done() + }) + instance.register(async (subinstance) => { + subinstance.addSchema({ $id: 'c', type: 'string' }) + subinstance.addHook('onReady', function (done) { + t.assert.strictEqual(subinstance.getSchemas().size, 3) + done() + }) + }) + }) + + fastify.register(async (instance) => { + instance.addHook('onReady', function (done) { + t.assert.strictEqual(instance.getSchemas().size, 1) + done() + }) + }) + + fastify.addSchema({ $id: 'a', type: 'string' }) + + fastify.ready(err => { + t.assert.ifError(err) + t.assert.strictEqual(added, 3, 'three schema added') + t.assert.strictEqual(builtBucket, 4, 'one bucket built for every register call + 1 for the root instance') + testDone() + }) +}) + +test('setSchemaController per instance', (t, testDone) => { + t.plan(7) + const fastify = Fastify({}) + + fastify.register(async (instance1) => { + instance1.setSchemaController({ + bucket: function factoryBucket (storeInit) { + t.assert.ok('instance1 has created the bucket') + return { + add (schema) { t.fail('add is not called') }, + getSchema (id) { t.fail('getSchema is not called') }, + getSchemas () { t.fail('getSchemas is not called') } + } + } + }) + }) + + fastify.register(async (instance2) => { + const bSchema = { $id: 'b', type: 'string' } + + instance2.setSchemaController({ + bucket: function factoryBucket (storeInit) { + t.assert.ok('instance2 has created the bucket') + const map = {} + return { + add (schema) { + t.assert.strictEqual(schema.$id, bSchema.$id, 'add is called') + map[schema.$id] = schema + }, + getSchema (id) { + t.assert.ok('getSchema is called') + return map[id] + }, + getSchemas () { + t.assert.ok('getSchemas is called') + } + } + } + }) + + instance2.addSchema(bSchema) + + instance2.addHook('onReady', function (done) { + instance2.getSchemas() + t.assert.deepStrictEqual(instance2.getSchema('b'), bSchema, 'the schema are loaded') + done() + }) + }) + + fastify.ready(err => { + t.assert.ifError(err) + testDone() + }) +}) + +test('setSchemaController: Inherits correctly parent schemas with a customized validator instance', async t => { + t.plan(5) + const customAjv = new Ajv({ coerceTypes: false }) + const server = Fastify() + const someSchema = { + $id: 'some', + type: 'array', + items: { + type: 'string' + } + } + const errorResponseSchema = { + $id: 'error_response', + type: 'object', + properties: { + statusCode: { + type: 'integer' + }, + message: { + type: 'string' + } + } + } + + server.addSchema(someSchema) + server.addSchema(errorResponseSchema) + + server.register((instance, _, done) => { + instance.setSchemaController({ + compilersFactory: { + buildValidator: function (externalSchemas) { + const schemaKeys = Object.keys(externalSchemas) + t.assert.strictEqual(schemaKeys.length, 2, 'Contains same number of schemas') + t.assert.deepStrictEqual([someSchema, errorResponseSchema], Object.values(externalSchemas), 'Contains expected schemas') + for (const key of schemaKeys) { + if (customAjv.getSchema(key) == null) { + customAjv.addSchema(externalSchemas[key], key) + } + } + return function validatorCompiler ({ schema }) { + return customAjv.compile(schema) + } + } + } + }) + + instance.get( + '/', + { + schema: { + querystring: { + type: 'object', + properties: { + msg: { + $ref: 'some#' + } + } + }, + response: { + '4xx': { + $ref: 'error_response#' + } + } + } + }, + (req, reply) => { + reply.send({ noop: 'noop' }) + } + ) + + done() + }) + + const res = await server.inject({ + method: 'GET', + url: '/', + query: { + msg: 'string' + } + }) + const json = res.json() + + t.assert.strictEqual(json.message, 'querystring/msg must be array') + t.assert.strictEqual(json.statusCode, 400) + t.assert.strictEqual(res.statusCode, 400, 'Should not coerce the string into array') +}) + +test('setSchemaController: Inherits buildSerializer from parent if not present within the instance', async t => { + t.plan(6) + const customAjv = new Ajv({ coerceTypes: false }) + const someSchema = { + $id: 'some', + type: 'array', + items: { + type: 'string' + } + } + const errorResponseSchema = { + $id: 'error_response', + type: 'object', + properties: { + statusCode: { + type: 'integer' + }, + message: { + type: 'string' + } + } + } + let rootSerializerCalled = 0 + let rootValidatorCalled = 0 + let childValidatorCalled = 0 + const rootBuildSerializer = function (externalSchemas) { + rootSerializerCalled++ + return function serializer () { + return data => { + return JSON.stringify({ + statusCode: data.statusCode, + message: data.message + }) + } + } + } + const rootBuildValidator = function (externalSchemas) { + rootValidatorCalled++ + return function validatorCompiler ({ schema }) { + return customAjv.compile(schema) + } + } + const server = Fastify({ + schemaController: { + compilersFactory: { + buildValidator: rootBuildValidator, + buildSerializer: rootBuildSerializer + } + } + }) + + server.addSchema(someSchema) + server.addSchema(errorResponseSchema) + + server.register((instance, _, done) => { + instance.setSchemaController({ + compilersFactory: { + buildValidator: function (externalSchemas) { + childValidatorCalled++ + const schemaKeys = Object.keys(externalSchemas) + for (const key of schemaKeys) { + if (customAjv.getSchema(key) == null) { + customAjv.addSchema(externalSchemas[key], key) + } + } + return function validatorCompiler ({ schema }) { + return customAjv.compile(schema) + } + } + } + }) + + instance.get( + '/', + { + schema: { + querystring: { + type: 'object', + properties: { + msg: { + $ref: 'some#' + } + } + }, + response: { + '4xx': { + $ref: 'error_response#' + } + } + } + }, + (req, reply) => { + reply.send({ noop: 'noop' }) + } + ) + + done() + }) + + const res = await server.inject({ + method: 'GET', + url: '/', + query: { + msg: ['string'] + } + }) + const json = res.json() + + t.assert.strictEqual(json.statusCode, 400) + t.assert.strictEqual(json.message, 'querystring/msg must be array') + t.assert.strictEqual(rootSerializerCalled, 1, 'Should be called from the child') + t.assert.strictEqual(rootValidatorCalled, 0, 'Should not be called from the child') + t.assert.strictEqual(childValidatorCalled, 1, 'Should be called from the child') + t.assert.strictEqual(res.statusCode, 400, 'Should not coerce the string into array') +}) + +test('setSchemaController: Inherits buildValidator from parent if not present within the instance', async t => { + t.plan(6) + const customAjv = new Ajv({ coerceTypes: false }) + const someSchema = { + $id: 'some', + type: 'array', + items: { + type: 'string' + } + } + const errorResponseSchema = { + $id: 'error_response', + type: 'object', + properties: { + statusCode: { + type: 'integer' + }, + message: { + type: 'string' + } + } + } + let rootSerializerCalled = 0 + let rootValidatorCalled = 0 + let childSerializerCalled = 0 + const rootBuildSerializer = function (externalSchemas) { + rootSerializerCalled++ + return function serializer () { + return data => JSON.stringify(data) + } + } + const rootBuildValidator = function (externalSchemas) { + rootValidatorCalled++ + const schemaKeys = Object.keys(externalSchemas) + for (const key of schemaKeys) { + if (customAjv.getSchema(key) == null) { + customAjv.addSchema(externalSchemas[key], key) + } + } + return function validatorCompiler ({ schema }) { + return customAjv.compile(schema) + } + } + const server = Fastify({ + schemaController: { + compilersFactory: { + buildValidator: rootBuildValidator, + buildSerializer: rootBuildSerializer + } + } + }) + + server.register((instance, _, done) => { + instance.register((subInstance, _, subDone) => { + subInstance.setSchemaController({ + compilersFactory: { + buildSerializer: function (externalSchemas) { + childSerializerCalled++ + return function serializerCompiler () { + return data => { + return JSON.stringify({ + statusCode: data.statusCode, + message: data.message + }) + } + } + } + } + }) + + subInstance.get( + '/', + { + schema: { + querystring: { + type: 'object', + properties: { + msg: { + $ref: 'some#' + } + } + }, + response: { + '4xx': { + $ref: 'error_response#' + } + } + } + }, + (req, reply) => { + reply.send({ noop: 'noop' }) + } + ) + + subDone() + }) + + done() + }) + + server.addSchema(someSchema) + server.addSchema(errorResponseSchema) + + const res = await server.inject({ + method: 'GET', + url: '/', + query: { + msg: ['string'] + } + }) + const json = res.json() + + t.assert.strictEqual(json.statusCode, 400) + t.assert.strictEqual(json.message, 'querystring/msg must be array') + t.assert.strictEqual(rootSerializerCalled, 0, 'Should be called from the child') + t.assert.strictEqual(rootValidatorCalled, 1, 'Should not be called from the child') + t.assert.strictEqual(childSerializerCalled, 1, 'Should be called from the child') + t.assert.strictEqual(res.statusCode, 400, 'Should not coerce the string into array') +}) + +test('Should throw if not default validator passed', async t => { + t.plan(4) + const customAjv = new Ajv({ coerceTypes: false }) + const someSchema = { + $id: 'some', + type: 'array', + items: { + type: 'string' + } + } + const anotherSchema = { + $id: 'another', + type: 'integer' + } + const plugin = fp(function (pluginInstance, _, pluginDone) { + pluginInstance.setSchemaController({ + compilersFactory: { + buildValidator: function (externalSchemas) { + const schemaKeys = Object.keys(externalSchemas) + t.assert.strictEqual(schemaKeys.length, 2) + t.assert.deepStrictEqual(schemaKeys, ['some', 'another']) + + for (const key of schemaKeys) { + if (customAjv.getSchema(key) == null) { + customAjv.addSchema(externalSchemas[key], key) + } + } + return function validatorCompiler ({ schema }) { + return customAjv.compile(schema) + } + } + } + }) + + pluginDone() + }) + const server = Fastify() + + server.addSchema(someSchema) + + server.register((instance, opts, done) => { + instance.addSchema(anotherSchema) + + instance.register(plugin, {}) + + instance.post( + '/', + { + schema: { + query: { + type: 'object', + properties: { + msg: { + $ref: 'some#' + } + } + }, + headers: { + type: 'object', + properties: { + 'x-another': { + $ref: 'another#' + } + } + } + } + }, + (req, reply) => { + reply.send({ noop: 'noop' }) + } + ) + + done() + }) + + try { + const res = await server.inject({ + method: 'POST', + url: '/', + query: { + msg: ['string'] + } + }) + + t.assert.strictEqual(res.json().message, 'querystring/msg must be array') + t.assert.strictEqual(res.statusCode, 400, 'Should not coerce the string into array') + } catch (err) { + t.assert.ifError(err) + } +}) + +test('Should coerce the array if the default validator is used', async t => { + t.plan(2) + const someSchema = { + $id: 'some', + type: 'array', + items: { + type: 'string' + } + } + const anotherSchema = { + $id: 'another', + type: 'integer' + } + + const server = Fastify() + + server.addSchema(someSchema) + + server.register((instance, opts, done) => { + instance.addSchema(anotherSchema) + + instance.post( + '/', + { + schema: { + query: { + type: 'object', + properties: { + msg: { + $ref: 'some#' + } + } + }, + headers: { + type: 'object', + properties: { + 'x-another': { + $ref: 'another#' + } + } + } + } + }, + (req, reply) => { + reply.send(req.query) + } + ) + + done() + }) + + try { + const res = await server.inject({ + method: 'POST', + url: '/', + query: { + msg: 'string' + } + }) + + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(res.json(), { msg: ['string'] }, 'Should coerce the string into array') + } catch (err) { + t.assert.ifError(err) + } +}) + +test('Should return a human-friendly error if response status codes are not specified', (t, testDone) => { + t.plan(2) + const fastify = Fastify() + + fastify.route({ + url: '/', + method: 'GET', + schema: { + response: { + // This should be nested under a status code key, e.g { 200: { type: 'array' } } + type: 'array' + } + }, + handler: (req, reply) => { + reply.send([]) + } + }) + + fastify.ready(err => { + t.assert.strictEqual(err.code, 'FST_ERR_SCH_SERIALIZATION_BUILD') + t.assert.strictEqual(err.message, 'Failed building the serialization schema for GET: /, due to error response schemas should be nested under a valid status code, e.g { 2xx: { type: "object" } }') + testDone() + }) +}) + +test('setSchemaController: custom validator instance should not mutate headers schema', async t => { + t.plan(2) + class Headers { } + const fastify = Fastify() + + fastify.setSchemaController({ + compilersFactory: { + buildValidator: function () { + return ({ schema, method, url, httpPart }) => { + t.assert.ok(schema instanceof Headers) + return () => { } + } + } + } + }) + + fastify.get('/', { + schema: { + headers: new Headers() + } + }, () => { }) + + await fastify.ready() +}) diff --git a/services/slides/node_modules/fastify/test/schema-serialization.test.js b/services/slides/node_modules/fastify/test/schema-serialization.test.js new file mode 100644 index 0000000000000000000000000000000000000000..d2ed451e38dd7df71b01fcdc81feea3ccebdb12c --- /dev/null +++ b/services/slides/node_modules/fastify/test/schema-serialization.test.js @@ -0,0 +1,1171 @@ +'use strict' + +const { test } = require('node:test') +const Fastify = require('..') +const { waitForCb } = require('./toolkit') + +const echoBody = (req, reply) => { reply.send(req.body) } + +test('basic test', (t, testDone) => { + t.plan(3) + + const fastify = Fastify() + fastify.get('/', { + schema: { + response: { + '2xx': { + type: 'object', + properties: { + name: { type: 'string' }, + work: { type: 'string' } + } + } + } + } + }, function (req, reply) { + reply.code(200).send({ name: 'Foo', work: 'Bar', nick: 'Boo' }) + }) + + fastify.inject('/', (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(res.json(), { name: 'Foo', work: 'Bar' }) + t.assert.strictEqual(res.statusCode, 200) + testDone() + }) +}) + +test('custom serializer options', (t, testDone) => { + t.plan(3) + + const fastify = Fastify({ + serializerOpts: { + rounding: 'ceil' + } + }) + fastify.get('/', { + schema: { + response: { + '2xx': { + type: 'integer' + } + } + } + }, function (req, reply) { + reply.send(4.2) + }) + + fastify.inject('/', (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.payload, '5', 'it must use the ceil rounding') + t.assert.strictEqual(res.statusCode, 200) + testDone() + }) +}) + +test('Different content types', (t, testDone) => { + t.plan(46) + + const fastify = Fastify() + fastify.addSchema({ + $id: 'test', + type: 'object', + properties: { + name: { type: 'string' }, + age: { type: 'number' }, + verified: { type: 'boolean' } + } + }) + + fastify.get('/', { + schema: { + response: { + 200: { + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + name: { type: 'string' }, + image: { type: 'string' }, + address: { type: 'string' } + } + } + }, + 'application/vnd.v1+json': { + schema: { + type: 'array', + items: { $ref: 'test' } + } + } + } + }, + 201: { + content: { + '*/*': { + schema: { type: 'string' } + } + } + }, + 202: { + content: { + '*/*': { + schema: { const: 'Processing exclusive content' } + } + } + }, + '3xx': { + content: { + 'application/vnd.v2+json': { + schema: { + type: 'object', + properties: { + fullName: { type: 'string' }, + phone: { type: 'string' } + } + } + } + } + }, + '4xx': { + content: { + '*/*': { + schema: { + type: 'object', + properties: { + details: { type: 'string' } + } + } + } + } + }, + default: { + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + details: { type: 'string' } + } + } + }, + '*/*': { + schema: { + type: 'object', + properties: { + desc: { type: 'string' }, + details: { type: 'string' } + } + } + } + } + } + } + } + }, function (req, reply) { + switch (req.headers.accept) { + case 'application/json': + reply.header('Content-Type', 'application/json') + reply.send({ id: 1, name: 'Foo', image: 'profile picture', address: 'New Node' }) + break + case 'application/vnd.v1+json': + reply.header('Content-Type', 'application/vnd.v1+json') + reply.send([{ id: 2, name: 'Boo', age: 18, verified: false }, { id: 3, name: 'Woo', age: 30, verified: true }]) + break + case 'application/vnd.v2+json': + reply.header('Content-Type', 'application/vnd.v2+json') + reply.code(300) + reply.send({ fullName: 'Jhon Smith', phone: '01090000000', authMethod: 'google' }) + break + case 'application/vnd.v3+json': + reply.header('Content-Type', 'application/vnd.v3+json') + reply.code(300) + reply.send({ firstName: 'New', lastName: 'Hoo', country: 'eg', city: 'node' }) + break + case 'application/vnd.v4+json': + reply.header('Content-Type', 'application/vnd.v4+json') + reply.code(201) + reply.send({ boxId: 1, content: 'Games' }) + break + case 'application/vnd.v5+json': + reply.header('Content-Type', 'application/vnd.v5+json') + reply.code(202) + reply.send({ content: 'interesting content' }) + break + case 'application/vnd.v6+json': + reply.header('Content-Type', 'application/vnd.v6+json') + reply.code(400) + reply.send({ desc: 'age is missing', details: 'validation error' }) + break + case 'application/vnd.v7+json': + reply.code(400) + reply.send({ details: 'validation error' }) + break + case 'application/vnd.v8+json': + reply.header('Content-Type', 'application/vnd.v8+json') + reply.code(500) + reply.send({ desc: 'age is missing', details: 'validation error' }) + break + case 'application/vnd.v9+json': + reply.code(500) + reply.send({ details: 'validation error' }) + break + default: + // to test if schema not found + reply.header('Content-Type', 'application/vnd.v3+json') + reply.code(200) + reply.send([{ type: 'student', grade: 6 }, { type: 'student', grade: 9 }]) + } + }) + + fastify.get('/test', { + serializerCompiler: ({ contentType }) => { + t.assert.strictEqual(contentType, 'application/json') + return data => JSON.stringify(data) + }, + schema: { + response: { + 200: { + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + name: { type: 'string' }, + image: { type: 'string' }, + address: { type: 'string' } + } + } + } + } + }, + default: { + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + details: { type: 'string' } + } + } + } + } + } + } + } + }, function (req, reply) { + switch (req.headers['code']) { + case '200': { + reply.header('Content-Type', 'application/json') + reply.code(200).send({ age: 18, city: 'AU' }) + break + } + case '201': { + reply.header('Content-Type', 'application/json') + reply.code(201).send({ details: 'validation error' }) + break + } + default: { + reply.header('Content-Type', 'application/vnd.v1+json') + reply.code(201).send({ created: true }) + break + } + } + }) + + const completion = waitForCb({ steps: 14 }) + fastify.inject({ method: 'GET', url: '/', headers: { Accept: 'application/json' } }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.payload, JSON.stringify({ name: 'Foo', image: 'profile picture', address: 'New Node' })) + t.assert.strictEqual(res.statusCode, 200) + completion.stepIn() + }) + fastify.inject({ method: 'GET', url: '/', headers: { Accept: 'application/vnd.v1+json' } }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.payload, JSON.stringify([{ name: 'Boo', age: 18, verified: false }, { name: 'Woo', age: 30, verified: true }])) + t.assert.strictEqual(res.statusCode, 200) + completion.stepIn() + }) + fastify.inject({ method: 'GET', url: '/' }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.payload, JSON.stringify([{ type: 'student', grade: 6 }, { type: 'student', grade: 9 }])) + t.assert.strictEqual(res.statusCode, 200) + completion.stepIn() + }) + fastify.inject({ method: 'GET', url: '/', headers: { Accept: 'application/vnd.v2+json' } }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.payload, JSON.stringify({ fullName: 'Jhon Smith', phone: '01090000000' })) + t.assert.strictEqual(res.statusCode, 300) + completion.stepIn() + }) + fastify.inject({ method: 'GET', url: '/', headers: { Accept: 'application/vnd.v3+json' } }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.payload, JSON.stringify({ firstName: 'New', lastName: 'Hoo', country: 'eg', city: 'node' })) + t.assert.strictEqual(res.statusCode, 300) + completion.stepIn() + }) + fastify.inject({ method: 'GET', url: '/', headers: { Accept: 'application/vnd.v4+json' } }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.payload, '"[object Object]"') + t.assert.strictEqual(res.statusCode, 201) + completion.stepIn() + }) + fastify.inject({ method: 'GET', url: '/', headers: { Accept: 'application/vnd.v5+json' } }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.payload, '"Processing exclusive content"') + t.assert.strictEqual(res.statusCode, 202) + completion.stepIn() + }) + fastify.inject({ method: 'GET', url: '/', headers: { Accept: 'application/vnd.v6+json' } }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.payload, JSON.stringify({ details: 'validation error' })) + t.assert.strictEqual(res.statusCode, 400) + completion.stepIn() + }) + fastify.inject({ method: 'GET', url: '/', headers: { Accept: 'application/vnd.v7+json' } }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.payload, JSON.stringify({ details: 'validation error' })) + t.assert.strictEqual(res.statusCode, 400) + completion.stepIn() + }) + fastify.inject({ method: 'GET', url: '/', headers: { Accept: 'application/vnd.v8+json' } }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.payload, JSON.stringify({ desc: 'age is missing', details: 'validation error' })) + t.assert.strictEqual(res.statusCode, 500) + completion.stepIn() + }) + fastify.inject({ method: 'GET', url: '/', headers: { Accept: 'application/vnd.v9+json' } }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.payload, JSON.stringify({ details: 'validation error' })) + t.assert.strictEqual(res.statusCode, 500) + completion.stepIn() + }) + fastify.inject({ method: 'GET', url: '/test', headers: { Code: '200' } }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.payload, JSON.stringify({ age: 18, city: 'AU' })) + t.assert.strictEqual(res.statusCode, 200) + completion.stepIn() + }) + fastify.inject({ method: 'GET', url: '/test', headers: { Code: '201' } }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.payload, JSON.stringify({ details: 'validation error' })) + t.assert.strictEqual(res.statusCode, 201) + completion.stepIn() + }) + fastify.inject({ method: 'GET', url: '/test', headers: { Accept: 'application/vnd.v1+json' } }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.payload, JSON.stringify({ created: true })) + t.assert.strictEqual(res.statusCode, 201) + completion.stepIn() + }) + + completion.patience.then(testDone) +}) + +test('Invalid multiple content schema, throw FST_ERR_SCH_CONTENT_MISSING_SCHEMA error', (t, testDone) => { + t.plan(3) + const fastify = Fastify() + + fastify.get('/testInvalid', { + schema: { + response: { + 200: { + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + fullName: { type: 'string' }, + phone: { type: 'string' } + } + }, + example: { + fullName: 'John Doe', + phone: '201090243795' + } + }, + type: 'string' + } + } + } + } + }, function (req, reply) { + reply.header('Content-Type', 'application/json') + reply.send({ fullName: 'Any name', phone: '0109001010' }) + }) + + fastify.ready((err) => { + t.assert.strictEqual(err.message, "Schema is missing for the content type 'type'") + t.assert.strictEqual(err.statusCode, 500) + t.assert.strictEqual(err.code, 'FST_ERR_SCH_CONTENT_MISSING_SCHEMA') + testDone() + }) +}) + +test('Use the same schema id in different places', (t, testDone) => { + t.plan(2) + const fastify = Fastify() + + fastify.addSchema({ + $id: 'test', + type: 'object', + properties: { + id: { type: 'number' } + } + }) + + fastify.get('/:id', { + handler (req, reply) { + reply.send([{ id: 1 }, { id: 2 }, { what: 'is this' }]) + }, + schema: { + response: { + 200: { + type: 'array', + items: { $ref: 'test' } + } + } + } + }) + + fastify.inject({ + method: 'GET', + url: '/123' + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(res.json(), [{ id: 1 }, { id: 2 }, {}]) + testDone() + }) +}) + +test('Use shared schema and $ref with $id in response ($ref to $id)', (t, testDone) => { + t.plan(5) + const fastify = Fastify() + + fastify.addSchema({ + $id: 'http://foo/test', + type: 'object', + properties: { + id: { type: 'number' } + } + }) + + const complexSchema = { + $schema: 'http://json-schema.org/draft-07/schema#', + $id: 'http://foo/user', + type: 'object', + definitions: { + address: { + $id: '#address', + type: 'object', + properties: { + city: { type: 'string' } + } + } + }, + properties: { + test: { $ref: 'http://foo/test#' }, + address: { $ref: '#address' } + }, + required: ['address', 'test'] + } + + fastify.post('/', { + schema: { + body: complexSchema, + response: { + 200: complexSchema + } + }, + handler: (req, reply) => { + req.body.removeThis = 'it should not be serialized' + reply.send(req.body) + } + }) + + const payload = { + address: { city: 'New Node' }, + test: { id: Date.now() } + } + + const completion = waitForCb({ steps: 2 }) + fastify.inject({ + method: 'POST', + url: '/', + payload + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(res.json(), payload) + completion.stepIn() + }) + fastify.inject({ + method: 'POST', + url: '/', + payload: { test: { id: Date.now() } } + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 400) + t.assert.deepStrictEqual(res.json(), { + error: 'Bad Request', + message: "body must have required property 'address'", + statusCode: 400, + code: 'FST_ERR_VALIDATION' + }) + completion.stepIn() + }) + + completion.patience.then(testDone) +}) + +test('Shared schema should be pass to serializer and validator ($ref to shared schema /definitions)', (t, testDone) => { + t.plan(5) + const fastify = Fastify() + + fastify.addSchema({ + $id: 'http://fastify.test/asset.json', + $schema: 'http://json-schema.org/draft-07/schema#', + title: 'Physical Asset', + description: 'A generic representation of a physical asset', + type: 'object', + required: [ + 'id', + 'model', + 'location' + ], + properties: { + id: { + type: 'string', + format: 'uuid' + }, + model: { + type: 'string' + }, + location: { $ref: 'http://fastify.test/point.json#' } + }, + definitions: { + inner: { + $id: '#innerId', + type: 'string', + format: 'email' + } + } + }) + + fastify.addSchema({ + $id: 'http://fastify.test/point.json', + $schema: 'http://json-schema.org/draft-07/schema#', + title: 'Longitude and Latitude Values', + description: 'A geographical coordinate.', + type: 'object', + required: [ + 'latitude', + 'longitude' + ], + properties: { + email: { $ref: 'http://fastify.test/asset.json#/definitions/inner' }, + latitude: { + type: 'number', + minimum: -90, + maximum: 90 + }, + longitude: { + type: 'number', + minimum: -180, + maximum: 180 + }, + altitude: { + type: 'number' + } + } + }) + + const schemaLocations = { + $id: 'http://fastify.test/locations.json', + $schema: 'http://json-schema.org/draft-07/schema#', + title: 'List of Asset locations', + type: 'array', + items: { $ref: 'http://fastify.test/asset.json#' } + } + + fastify.post('/', { + schema: { + body: schemaLocations, + response: { 200: schemaLocations } + } + }, (req, reply) => { + reply.send(locations.map(_ => Object.assign({ serializer: 'remove me' }, _))) + }) + + const locations = [ + { id: '550e8400-e29b-41d4-a716-446655440000', model: 'mod', location: { latitude: 10, longitude: 10, email: 'foo@bar.it' } }, + { id: '550e8400-e29b-41d4-a716-446655440000', model: 'mod', location: { latitude: 10, longitude: 10, email: 'foo@bar.it' } } + ] + fastify.inject({ + method: 'POST', + url: '/', + payload: locations + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(res.json(), locations) + + fastify.inject({ + method: 'POST', + url: '/', + payload: locations.map(_ => { + _.location.email = 'not an email' + return _ + }) + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 400) + t.assert.deepStrictEqual(res.json(), { + error: 'Bad Request', + message: 'body/0/location/email must match format "email"', + statusCode: 400, + code: 'FST_ERR_VALIDATION' + }) + testDone() + }) + }) +}) + +test('Custom setSerializerCompiler', (t, testDone) => { + t.plan(7) + const fastify = Fastify({ exposeHeadRoutes: false }) + + const outSchema = { + $id: 'test', + type: 'object', + whatever: 'need to be parsed by the custom serializer' + } + + fastify.setSerializerCompiler(({ schema, method, url, httpStatus }) => { + t.assert.strictEqual(method, 'GET') + t.assert.strictEqual(url, '/foo/:id') + t.assert.strictEqual(httpStatus, '200') + t.assert.deepStrictEqual(schema, outSchema) + return data => JSON.stringify(data) + }) + + fastify.register((instance, opts, done) => { + instance.get('/:id', { + handler (req, reply) { + reply.send({ id: 1 }) + }, + schema: { + response: { + 200: outSchema + } + } + }) + t.assert.ok(instance.serializerCompiler, 'the serializer is set by the parent') + done() + }, { prefix: '/foo' }) + + fastify.inject({ + method: 'GET', + url: '/foo/123' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.payload, JSON.stringify({ id: 1 })) + testDone() + }) +}) + +test('Custom setSerializerCompiler returns bad serialized output', (t, testDone) => { + t.plan(4) + const fastify = Fastify() + + const outSchema = { + $id: 'test', + type: 'object', + whatever: 'need to be parsed by the custom serializer' + } + + fastify.setSerializerCompiler(({ schema, method, url, httpStatus }) => { + return data => { + t.assert.ok('returning an invalid serialization') + return { not: 'a string' } + } + }) + + fastify.get('/:id', { + handler (req, reply) { throw new Error('ops') }, + schema: { + response: { + 500: outSchema + } + } + }) + + fastify.inject({ + method: 'GET', + url: '/123' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 500) + t.assert.deepStrictEqual(res.json(), { + code: 'FST_ERR_REP_INVALID_PAYLOAD_TYPE', + message: 'Attempted to send payload of invalid type \'object\'. Expected a string or Buffer.', + statusCode: 500 + }) + testDone() + }) +}) + +test('Custom setSerializerCompiler with addSchema', (t, testDone) => { + t.plan(6) + const fastify = Fastify({ exposeHeadRoutes: false }) + + const outSchema = { + $id: 'test', + type: 'object', + whatever: 'need to be parsed by the custom serializer' + } + + fastify.setSerializerCompiler(({ schema, method, url, httpStatus }) => { + t.assert.strictEqual(method, 'GET') + t.assert.strictEqual(url, '/foo/:id') + t.assert.strictEqual(httpStatus, '200') + t.assert.deepStrictEqual(schema, outSchema) + return _data => JSON.stringify({ id: 2 }) + }) + + // provoke re-creation of serialization compiler in setupSerializer + fastify.addSchema({ $id: 'dummy', type: 'object' }) + + fastify.get('/foo/:id', { + handler (_req, reply) { + reply.send({ id: 1 }) + }, + schema: { + response: { + 200: outSchema + } + } + }) + + fastify.inject({ + method: 'GET', + url: '/foo/123' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.payload, JSON.stringify({ id: 2 })) + testDone() + }) +}) + +test('Custom serializer per route', async t => { + const fastify = Fastify() + + const outSchema = { + $id: 'test', + type: 'object', + properties: { + mean: { type: 'string' } + } + } + + fastify.get('/default', { + handler (req, reply) { reply.send({ mean: 'default' }) }, + schema: { response: { 200: outSchema } } + }) + + let hit = 0 + fastify.register((instance, opts, done) => { + instance.setSerializerCompiler(({ schema, method, url, httpStatus }) => { + hit++ + return data => JSON.stringify({ mean: 'custom' }) + }) + instance.get('/custom', { + handler (req, reply) { reply.send({}) }, + schema: { response: { 200: outSchema } } + }) + instance.get('/route', { + handler (req, reply) { reply.send({}) }, + serializerCompiler: ({ schema, method, url, httpPart }) => { + hit++ + return data => JSON.stringify({ mean: 'route' }) + }, + schema: { response: { 200: outSchema } } + }) + + done() + }) + + let res = await fastify.inject('/default') + t.assert.strictEqual(res.json().mean, 'default') + + res = await fastify.inject('/custom') + t.assert.strictEqual(res.json().mean, 'custom') + + res = await fastify.inject('/route') + t.assert.strictEqual(res.json().mean, 'route') + + t.assert.strictEqual(hit, 4, 'the custom and route serializer has been called') +}) + +test('Reply serializer win over serializer ', (t, testDone) => { + t.plan(6) + + const fastify = Fastify() + fastify.setReplySerializer(function (payload, statusCode) { + t.assert.deepStrictEqual(payload, { name: 'Foo', work: 'Bar', nick: 'Boo' }) + return 'instance serializator' + }) + + fastify.get('/', { + schema: { + response: { + '2xx': { + type: 'object', + properties: { + name: { type: 'string' }, + work: { type: 'string' } + } + } + } + }, + serializerCompiler: ({ schema, method, url, httpPart }) => { + t.assert.ok(method, 'the custom compiler has been created') + return () => { + t.assert.fail('the serializer must not be called when there is a reply serializer') + return 'fail' + } + } + }, function (req, reply) { + reply.code(200).send({ name: 'Foo', work: 'Bar', nick: 'Boo' }) + }) + + fastify.inject('/', (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(res.payload, 'instance serializator') + t.assert.strictEqual(res.statusCode, 200) + testDone() + }) +}) + +test('Reply serializer win over serializer ', (t, testDone) => { + t.plan(6) + + const fastify = Fastify() + fastify.setReplySerializer(function (payload, statusCode) { + t.assert.deepStrictEqual(payload, { name: 'Foo', work: 'Bar', nick: 'Boo' }) + return 'instance serializator' + }) + + fastify.get('/', { + schema: { + response: { + '2xx': { + type: 'object', + properties: { + name: { type: 'string' }, + work: { type: 'string' } + } + } + } + }, + serializerCompiler: ({ schema, method, url, httpPart }) => { + t.assert.ok(method, 'the custom compiler has been created') + return () => { + t.assert.fail('the serializer must not be called when there is a reply serializer') + return 'fail' + } + } + }, function (req, reply) { + reply.code(200).send({ name: 'Foo', work: 'Bar', nick: 'Boo' }) + }) + + fastify.inject('/', (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(res.payload, 'instance serializator') + t.assert.strictEqual(res.statusCode, 200) + testDone() + }) +}) + +test('The schema compiler recreate itself if needed', (t, testDone) => { + t.plan(1) + const fastify = Fastify() + + fastify.options('/', { + schema: { + response: { '2xx': { hello: { type: 'string' } } } + } + }, echoBody) + + fastify.register(function (fastify, options, done) { + fastify.addSchema({ + $id: 'identifier', + type: 'string', + format: 'uuid' + }) + + fastify.get('/', { + schema: { + response: { + '2xx': { + foobarId: { $ref: 'identifier#' } + } + } + } + }, echoBody) + + done() + }) + + fastify.ready(err => { + t.assert.ifError(err) + testDone() + }) +}) + +test('The schema changes the default error handler output', async t => { + t.plan(4) + const fastify = Fastify() + + fastify.get('/:code', { + schema: { + response: { + '2xx': { hello: { type: 'string' } }, + 501: { + type: 'object', + properties: { + message: { type: 'string' } + } + }, + '5xx': { + type: 'object', + properties: { + customId: { type: 'number' }, + error: { type: 'string' }, + message: { type: 'string' } + } + } + } + } + }, (request, reply) => { + if (request.params.code === '501') { + return reply.code(501).send(new Error('501 message')) + } + const error = new Error('500 message') + error.customId = 42 + reply.send(error) + }) + + let res = await fastify.inject('/501') + t.assert.strictEqual(res.statusCode, 501) + t.assert.deepStrictEqual(res.json(), { message: '501 message' }) + + res = await fastify.inject('/500') + t.assert.strictEqual(res.statusCode, 500) + t.assert.deepStrictEqual(res.json(), { error: 'Internal Server Error', message: '500 message', customId: 42 }) +}) + +test('do not crash if status code serializer errors', async t => { + const fastify = Fastify() + + const requiresFoo = { + type: 'object', + properties: { foo: { type: 'string' } }, + required: ['foo'] + } + + const someUserErrorType2 = { + type: 'object', + properties: { + customCode: { type: 'number' } + }, + required: ['customCode'] + } + + fastify.get( + '/', + { + schema: { + query: requiresFoo, + response: { 400: someUserErrorType2 } + } + }, + (request, reply) => { + t.assert.fail('handler, should not be called') + } + ) + + const res = await fastify.inject({ + path: '/', + query: { + notfoo: true + } + }) + t.assert.strictEqual(res.statusCode, 500) + t.assert.deepStrictEqual(res.json(), { + statusCode: 500, + code: 'FST_ERR_FAILED_ERROR_SERIALIZATION', + message: 'Failed to serialize an error. Error: "customCode" is required!. ' + + 'Original error: querystring must have required property \'foo\'' + }) +}) + +test('custom schema serializer error, empty message', async t => { + t.plan(2) + const fastify = Fastify() + + fastify.get('/:code', { + schema: { + response: { + '2xx': { hello: { type: 'string' } }, + 501: { + type: 'object', + properties: { + message: { type: 'string' } + } + } + } + } + }, (request, reply) => { + if (request.params.code === '501') { + return reply.code(501).send(new Error('')) + } + }) + + const res = await fastify.inject('/501') + t.assert.strictEqual(res.statusCode, 501) + t.assert.deepStrictEqual(res.json(), { message: '' }) +}) + +test('error in custom schema serialize compiler, throw FST_ERR_SCH_SERIALIZATION_BUILD error', (t, testDone) => { + t.plan(3) + + const fastify = Fastify() + + fastify.get('/', { + schema: { + response: { + '2xx': { + type: 'object', + properties: { + some: { type: 'string' } + } + }, + 500: { + type: 'object', + properties: { + message: { type: 'string' } + } + } + } + }, + serializerCompiler: () => { + throw new Error('CUSTOM_ERROR') + } + }, function (req, reply) { + reply.code(200).send({ some: 'thing' }) + }) + + fastify.ready((err) => { + t.assert.strictEqual(err.message, 'Failed building the serialization schema for GET: /, due to error CUSTOM_ERROR') + t.assert.strictEqual(err.statusCode, 500) + t.assert.strictEqual(err.code, 'FST_ERR_SCH_SERIALIZATION_BUILD') + testDone() + }) +}) + +test('Errors in serializer send to errorHandler', async t => { + let savedError + + const fastify = Fastify() + fastify.get('/', { + schema: { + response: { + 200: { + type: 'object', + properties: { + name: { type: 'string' }, + power: { type: 'string' } + }, + required: ['name'] + } + } + } + + }, function (req, reply) { + reply.code(200).send({ no: 'thing' }) + }) + fastify.setErrorHandler((error, request, reply) => { + savedError = error + reply.code(500).send(error) + }) + + const res = await fastify.inject('/') + + t.assert.strictEqual(res.statusCode, 500) + + // t.assert.deepStrictEqual(savedError, new Error('"name" is required!')); + t.assert.deepStrictEqual(res.json(), { + statusCode: 500, + error: 'Internal Server Error', + message: '"name" is required!' + }) + t.assert.ok(savedError, 'error presents') + t.assert.ok(savedError.serialization, 'Serialization sign presents') +}) + +test('capital X', (t, testDone) => { + t.plan(3) + + const fastify = Fastify() + fastify.get('/', { + schema: { + response: { + '2XX': { + type: 'object', + properties: { + name: { type: 'string' }, + work: { type: 'string' } + } + } + } + } + }, function (req, reply) { + reply.code(200).send({ name: 'Foo', work: 'Bar', nick: 'Boo' }) + }) + + fastify.inject('/', (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(res.json(), { name: 'Foo', work: 'Bar' }) + t.assert.strictEqual(res.statusCode, 200) + testDone() + }) +}) + +test('allow default as status code and used as last fallback', (t, testDone) => { + t.plan(3) + const fastify = Fastify() + + fastify.route({ + url: '/', + method: 'GET', + schema: { + response: { + default: { + type: 'object', + properties: { + name: { type: 'string' }, + work: { type: 'string' } + } + } + } + }, + handler: (req, reply) => { + reply.code(200).send({ name: 'Foo', work: 'Bar', nick: 'Boo' }) + } + }) + + fastify.inject('/', (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(res.json(), { name: 'Foo', work: 'Bar' }) + t.assert.strictEqual(res.statusCode, 200) + testDone() + }) +}) diff --git a/services/slides/node_modules/fastify/test/schema-special-usage.test.js b/services/slides/node_modules/fastify/test/schema-special-usage.test.js new file mode 100644 index 0000000000000000000000000000000000000000..85b466a1ba87cdaaf0f828af82d8b3ce2b8da9fd --- /dev/null +++ b/services/slides/node_modules/fastify/test/schema-special-usage.test.js @@ -0,0 +1,1348 @@ +'use strict' + +const { test } = require('node:test') +const Joi = require('joi') +const yup = require('yup') +const AJV = require('ajv') +const S = require('fluent-json-schema') +const Fastify = require('..') +const ajvMergePatch = require('ajv-merge-patch') +const ajvErrors = require('ajv-errors') +const proxyquire = require('proxyquire') +const { waitForCb } = require('./toolkit') + +test('Ajv plugins array parameter', (t, testDone) => { + t.plan(3) + const fastify = Fastify({ + ajv: { + customOptions: { + allErrors: true + }, + plugins: [ + [ajvErrors, { singleError: '@@@@' }] + ] + } + }) + + fastify.post('/', { + schema: { + body: { + type: 'object', + properties: { + foo: { + type: 'number', + minimum: 2, + maximum: 10, + multipleOf: 2, + errorMessage: { + type: 'should be number', + minimum: 'should be >= 2', + maximum: 'should be <= 10', + multipleOf: 'should be multipleOf 2' + } + } + } + } + }, + handler (req, reply) { reply.send({ ok: 1 }) } + }) + + fastify.inject({ + method: 'POST', + url: '/', + payload: { foo: 99 } + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 400) + t.assert.strictEqual(res.json().message, 'body/foo should be <= 10@@@@should be multipleOf 2') + testDone() + }) +}) + +test('Should handle root $merge keywords in header', (t, testDone) => { + t.plan(5) + const fastify = Fastify({ + ajv: { + plugins: [ + ajvMergePatch + ] + } + }) + + fastify.route({ + method: 'GET', + url: '/', + schema: { + headers: { + $merge: { + source: { + type: 'object', + properties: { + q: { type: 'string' } + } + }, + with: { required: ['q'] } + } + } + }, + handler (req, reply) { reply.send({ ok: 1 }) } + }) + + fastify.ready(err => { + t.assert.ifError(err) + + fastify.inject({ + method: 'GET', + url: '/' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 400) + }) + + fastify.inject({ + method: 'GET', + url: '/', + headers: { q: 'foo' } + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + testDone() + }) + }) +}) + +test('Should handle root $patch keywords in header', (t, testDone) => { + t.plan(5) + const fastify = Fastify({ + ajv: { + plugins: [ + ajvMergePatch + ] + } + }) + + fastify.route({ + method: 'GET', + url: '/', + schema: { + headers: { + $patch: { + source: { + type: 'object', + properties: { + q: { type: 'string' } + } + }, + with: [ + { + op: 'add', + path: '/properties/q', + value: { type: 'number' } + } + ] + } + } + }, + handler (req, reply) { reply.send({ ok: 1 }) } + }) + + fastify.ready(err => { + t.assert.ifError(err) + + fastify.inject({ + method: 'GET', + url: '/', + headers: { + q: 'foo' + } + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 400) + }) + + fastify.inject({ + method: 'GET', + url: '/', + headers: { q: 10 } + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + testDone() + }) + }) +}) + +test('Should handle $merge keywords in body', (t, testDone) => { + t.plan(5) + const fastify = Fastify({ + ajv: { + plugins: [ajvMergePatch] + } + }) + + fastify.post('/', { + schema: { + body: { + $merge: { + source: { + type: 'object', + properties: { + q: { + type: 'string' + } + } + }, + with: { + required: ['q'] + } + } + } + }, + handler (req, reply) { reply.send({ ok: 1 }) } + }) + + fastify.ready(err => { + t.assert.ifError(err) + + fastify.inject({ + method: 'POST', + url: '/' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 400) + }) + + fastify.inject({ + method: 'POST', + url: '/', + payload: { q: 'foo' } + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + testDone() + }) + }) +}) + +test('Should handle $patch keywords in body', (t, testDone) => { + t.plan(5) + const fastify = Fastify({ + ajv: { + plugins: [ajvMergePatch] + } + }) + + fastify.post('/', { + schema: { + body: { + $patch: { + source: { + type: 'object', + properties: { + q: { + type: 'string' + } + } + }, + with: [ + { + op: 'add', + path: '/properties/q', + value: { type: 'number' } + } + ] + } + } + }, + handler (req, reply) { reply.send({ ok: 1 }) } + }) + + fastify.ready(err => { + t.assert.ifError(err) + + const completion = waitForCb({ steps: 2 }) + fastify.inject({ + method: 'POST', + url: '/', + payload: { q: 'foo' } + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 400) + completion.stepIn() + }) + fastify.inject({ + method: 'POST', + url: '/', + payload: { q: 10 } + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + completion.stepIn() + }) + completion.patience.then(testDone) + }) +}) + +test("serializer read validator's schemas", (t, testDone) => { + t.plan(4) + const ajvInstance = new AJV() + + const baseSchema = { + $id: 'http://fastify.test/schemas/base', + definitions: { + hello: { type: 'string' } + }, + type: 'object', + properties: { + hello: { $ref: '#/definitions/hello' } + } + } + + const refSchema = { + $id: 'http://fastify.test/schemas/ref', + type: 'object', + properties: { + hello: { $ref: 'http://fastify.test/schemas/base#/definitions/hello' } + } + } + + ajvInstance.addSchema(baseSchema) + ajvInstance.addSchema(refSchema) + + const fastify = Fastify({ + schemaController: { + bucket: function factory (storeInit) { + t.assert.ok(!storeInit, 'is always empty because fastify.addSchema is not called') + return { + getSchemas () { + return { + [baseSchema.$id]: ajvInstance.getSchema(baseSchema.$id).schema, + [refSchema.$id]: ajvInstance.getSchema(refSchema.$id).schema + } + } + } + } + } + }) + + fastify.setValidatorCompiler(function ({ schema }) { + return ajvInstance.compile(schema) + }) + + fastify.get('/', { + schema: { + response: { + '2xx': ajvInstance.getSchema('http://fastify.test/schemas/ref').schema + } + }, + handler (req, res) { res.send({ hello: 'world', evict: 'this' }) } + }) + + fastify.inject('/', (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(res.json(), { hello: 'world' }) + testDone() + }) +}) + +test('setSchemaController in a plugin', (t, testDone) => { + t.plan(5) + const baseSchema = { + $id: 'urn:schema:base', + definitions: { + hello: { type: 'string' } + }, + type: 'object', + properties: { + hello: { $ref: '#/definitions/hello' } + } + } + + const refSchema = { + $id: 'urn:schema:ref', + type: 'object', + properties: { + hello: { $ref: 'urn:schema:base#/definitions/hello' } + } + } + + const ajvInstance = new AJV() + ajvInstance.addSchema(baseSchema) + ajvInstance.addSchema(refSchema) + + const fastify = Fastify({ exposeHeadRoutes: false }) + fastify.register(schemaPlugin) + fastify.get('/', { + schema: { + query: ajvInstance.getSchema('urn:schema:ref').schema, + response: { + '2xx': ajvInstance.getSchema('urn:schema:ref').schema + } + }, + handler (req, res) { + res.send({ hello: 'world', evict: 'this' }) + } + }) + + fastify.inject('/', (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(res.json(), { hello: 'world' }) + testDone() + }) + + async function schemaPlugin (server) { + server.setSchemaController({ + bucket () { + t.assert.ok('the bucket is created') + return { + addSchema (source) { + ajvInstance.addSchema(source) + }, + getSchema (id) { + return ajvInstance.getSchema(id).schema + }, + getSchemas () { + return { + 'urn:schema:base': baseSchema, + 'urn:schema:ref': refSchema + } + } + } + } + }) + server.setValidatorCompiler(function ({ schema }) { + t.assert.ok('the querystring schema is compiled') + return ajvInstance.compile(schema) + }) + } + schemaPlugin[Symbol.for('skip-override')] = true +}) + +test('side effect on schema let the server crash', async t => { + const firstSchema = { + $id: 'example1', + type: 'object', + properties: { + name: { + type: 'string' + } + } + } + + const reusedSchema = { + $id: 'example2', + type: 'object', + properties: { + name: { + oneOf: [ + { + $ref: 'example1' + } + ] + } + } + } + + const fastify = Fastify() + fastify.addSchema(firstSchema) + + fastify.post('/a', { + handler: async () => 'OK', + schema: { + body: reusedSchema, + response: { 200: reusedSchema } + } + }) + fastify.post('/b', { + handler: async () => 'OK', + schema: { + body: reusedSchema, + response: { 200: reusedSchema } + } + }) + + await fastify.ready() +}) + +test('only response schema trigger AJV pollution', async t => { + const ShowSchema = S.object().id('ShowSchema').prop('name', S.string()) + const ListSchema = S.array().id('ListSchema').items(S.ref('ShowSchema#')) + + const fastify = Fastify() + fastify.addSchema(ListSchema) + fastify.addSchema(ShowSchema) + + const routeResponseSchemas = { + schema: { response: { 200: S.ref('ListSchema#') } } + } + + fastify.register( + async (app) => { app.get('/resource/', routeResponseSchemas, () => ({})) }, + { prefix: '/prefix1' } + ) + fastify.register( + async (app) => { app.get('/resource/', routeResponseSchemas, () => ({})) }, + { prefix: '/prefix2' } + ) + + await fastify.ready() +}) + +test('only response schema trigger AJV pollution #2', async t => { + const ShowSchema = S.object().id('ShowSchema').prop('name', S.string()) + const ListSchema = S.array().id('ListSchema').items(S.ref('ShowSchema#')) + + const fastify = Fastify() + fastify.addSchema(ListSchema) + fastify.addSchema(ShowSchema) + + const routeResponseSchemas = { + schema: { + params: S.ref('ListSchema#'), + response: { 200: S.ref('ListSchema#') } + } + } + + fastify.register( + async (app) => { app.get('/resource/', routeResponseSchemas, () => ({})) }, + { prefix: '/prefix1' } + ) + fastify.register( + async (app) => { app.get('/resource/', routeResponseSchemas, () => ({})) }, + { prefix: '/prefix2' } + ) + + await fastify.ready() +}) + +test('setSchemaController in a plugin with head routes', (t, testDone) => { + t.plan(6) + const baseSchema = { + $id: 'urn:schema:base', + definitions: { + hello: { type: 'string' } + }, + type: 'object', + properties: { + hello: { $ref: '#/definitions/hello' } + } + } + + const refSchema = { + $id: 'urn:schema:ref', + type: 'object', + properties: { + hello: { $ref: 'urn:schema:base#/definitions/hello' } + } + } + + const ajvInstance = new AJV() + ajvInstance.addSchema(baseSchema) + ajvInstance.addSchema(refSchema) + + const fastify = Fastify({ exposeHeadRoutes: true }) + fastify.register(schemaPlugin) + fastify.get('/', { + schema: { + query: ajvInstance.getSchema('urn:schema:ref').schema, + response: { + '2xx': ajvInstance.getSchema('urn:schema:ref').schema + } + }, + handler (req, res) { + res.send({ hello: 'world', evict: 'this' }) + } + }) + + fastify.inject('/', (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(res.json(), { hello: 'world' }) + testDone() + }) + + async function schemaPlugin (server) { + server.setSchemaController({ + bucket () { + t.assert.ok('the bucket is created') + return { + addSchema (source) { + ajvInstance.addSchema(source) + }, + getSchema (id) { + return ajvInstance.getSchema(id).schema + }, + getSchemas () { + return { + 'urn:schema:base': baseSchema, + 'urn:schema:ref': refSchema + } + } + } + } + }) + server.setValidatorCompiler(function ({ schema }) { + if (schema.$id) { + const stored = ajvInstance.getSchema(schema.$id) + if (stored) { + t.assert.ok('the schema is reused') + return stored + } + } + t.assert.ok('the schema is compiled') + + return ajvInstance.compile(schema) + }) + } + schemaPlugin[Symbol.for('skip-override')] = true +}) + +test('multiple refs with the same ids', (t, testDone) => { + t.plan(3) + const baseSchema = { + $id: 'urn:schema:base', + definitions: { + hello: { type: 'string' } + }, + type: 'object', + properties: { + hello: { $ref: '#/definitions/hello' } + } + } + + const refSchema = { + $id: 'urn:schema:ref', + type: 'object', + properties: { + hello: { $ref: 'urn:schema:base#/definitions/hello' } + } + } + + const fastify = Fastify() + + fastify.addSchema(baseSchema) + fastify.addSchema(refSchema) + + fastify.head('/', { + schema: { + query: refSchema, + response: { + '2xx': refSchema + } + }, + handler (req, res) { + res.send({ hello: 'world', evict: 'this' }) + } + }) + + fastify.get('/', { + schema: { + query: refSchema, + response: { + '2xx': refSchema + } + }, + handler (req, res) { + res.send({ hello: 'world', evict: 'this' }) + } + }) + + fastify.inject('/', (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(res.json(), { hello: 'world' }) + testDone() + }) +}) + +test('JOI validation overwrite request headers', (t, testDone) => { + t.plan(3) + const schemaValidator = ({ schema }) => data => { + const validationResult = schema.validate(data) + return validationResult + } + + const fastify = Fastify() + fastify.setValidatorCompiler(schemaValidator) + + fastify.get('/', { + schema: { + headers: Joi.object({ + 'user-agent': Joi.string().required(), + host: Joi.string().required() + }) + } + }, (request, reply) => { + reply.send(request.headers) + }) + + fastify.inject('/', (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(res.json(), { + 'user-agent': 'lightMyRequest', + host: 'localhost:80' + }) + testDone() + }) +}) + +test('Custom schema object should not trigger FST_ERR_SCH_DUPLICATE', async t => { + const fastify = Fastify() + const handler = () => { } + + fastify.get('/the/url', { + schema: { + query: yup.object({ + foo: yup.string() + }) + }, + validatorCompiler: ({ schema, method, url, httpPart }) => { + return function (data) { + // with option strict = false, yup `validateSync` function returns the coerced value if validation was successful, or throws if validation failed + try { + const result = schema.validateSync(data, {}) + return { value: result } + } catch (e) { + return { error: e } + } + } + }, + handler + }) + + await fastify.ready() + t.assert.ok('fastify is ready') +}) + +test('The default schema compilers should not be called when overwritten by the user', async t => { + const Fastify = proxyquire('../', { + '@fastify/ajv-compiler': () => { + t.assert.fail('The default validator compiler should not be called') + }, + '@fastify/fast-json-stringify-compiler': () => { + t.assert.fail('The default serializer compiler should not be called') + } + }) + + const fastify = Fastify({ + schemaController: { + compilersFactory: { + buildValidator: function factory () { + t.assert.ok('The custom validator compiler should be called') + return function validatorCompiler () { + return () => { return true } + } + }, + buildSerializer: function factory () { + t.assert.ok('The custom serializer compiler should be called') + return function serializerCompiler () { + return () => { return true } + } + } + } + } + }) + + fastify.get('/', + { + schema: { + query: { foo: { type: 'string' } }, + response: { + 200: { type: 'object' } + } + } + }, () => { }) + + await fastify.ready() +}) + +test('Supports async JOI validation', (t, testDone) => { + t.plan(7) + + const schemaValidator = ({ schema }) => async data => { + const validationResult = await schema.validateAsync(data) + return validationResult + } + + const fastify = Fastify({ + exposeHeadRoutes: false + }) + fastify.setValidatorCompiler(schemaValidator) + + fastify.get('/', { + schema: { + headers: Joi.object({ + 'user-agent': Joi.string().external(async (val) => { + if (val !== 'lightMyRequest') { + throw new Error('Invalid user-agent') + } + + t.assert.strictEqual(val, 'lightMyRequest') + return val + }), + host: Joi.string().required() + }) + } + }, (request, reply) => { + reply.send(request.headers) + }) + + const completion = waitForCb({ steps: 2 }) + fastify.inject('/', (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(res.json(), { + 'user-agent': 'lightMyRequest', + host: 'localhost:80' + }) + completion.stepIn() + }) + fastify.inject({ + url: '/', + headers: { + 'user-agent': 'invalid' + } + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 400) + t.assert.deepStrictEqual(res.json(), { + statusCode: 400, + code: 'FST_ERR_VALIDATION', + error: 'Bad Request', + message: 'Invalid user-agent (user-agent)' + }) + completion.stepIn() + }) + + completion.patience.then(testDone) +}) + +test('Supports async AJV validation', (t, testDone) => { + t.plan(12) + + const fastify = Fastify({ + exposeHeadRoutes: false, + ajv: { + customOptions: { + allErrors: true, + keywords: [ + { + keyword: 'idExists', + async: true, + type: 'number', + validate: checkIdExists + } + ] + }, + plugins: [ + [ajvErrors, { singleError: '@@@@' }] + ] + } + }) + + async function checkIdExists (schema, data) { + const res = await Promise.resolve(data) + switch (res) { + case 42: + return true + + case 500: + throw new Error('custom error') + + default: + return false + } + } + + const schema = { + $async: true, + type: 'object', + properties: { + userId: { + type: 'integer', + idExists: { table: 'users' } + }, + postId: { + type: 'integer', + idExists: { table: 'posts' } + } + } + } + + fastify.post('/', { + schema: { + body: schema + }, + handler (req, reply) { reply.send(req.body) } + }) + + const completion = waitForCb({ steps: 4 }) + + fastify.inject({ + method: 'POST', + url: '/', + payload: { userId: 99 } + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 400) + t.assert.deepStrictEqual(res.json(), { + statusCode: 400, + code: 'FST_ERR_VALIDATION', + error: 'Bad Request', + message: 'validation failed' + }) + completion.stepIn() + }) + fastify.inject({ + method: 'POST', + url: '/', + payload: { userId: 500 } + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 400) + t.assert.deepStrictEqual(res.json(), { + statusCode: 400, + code: 'FST_ERR_VALIDATION', + error: 'Bad Request', + message: 'custom error' + }) + completion.stepIn() + }) + fastify.inject({ + method: 'POST', + url: '/', + payload: { userId: 42 } + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(res.json(), { userId: 42 }) + completion.stepIn() + }) + fastify.inject({ + method: 'POST', + url: '/', + payload: { userId: 42, postId: 19 } + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 400) + t.assert.deepStrictEqual(res.json(), { + statusCode: 400, + code: 'FST_ERR_VALIDATION', + error: 'Bad Request', + message: 'validation failed' + }) + completion.stepIn() + }) + completion.patience.then(testDone) +}) + +test('Check all the async AJV validation paths', async (t) => { + const fastify = Fastify({ + exposeHeadRoutes: false, + ajv: { + customOptions: { + allErrors: true, + keywords: [ + { + keyword: 'idExists', + async: true, + type: 'number', + validate: checkIdExists + } + ] + } + } + }) + + async function checkIdExists (schema, data) { + const res = await Promise.resolve(data) + switch (res) { + case 200: + return true + + default: + return false + } + } + + const schema = { + $async: true, + type: 'object', + properties: { + id: { + type: 'integer', + idExists: { table: 'posts' } + } + } + } + + fastify.post('/:id', { + schema: { + params: schema, + body: schema, + query: schema, + headers: schema + }, + handler (req, reply) { reply.send(req.body) } + }) + + const testCases = [ + { + params: 400, + body: 200, + querystring: 200, + headers: 200, + response: 400 + }, + { + params: 200, + body: 400, + querystring: 200, + headers: 200, + response: 400 + }, + { + params: 200, + body: 200, + querystring: 400, + headers: 200, + response: 400 + }, + { + params: 200, + body: 200, + querystring: 200, + headers: 400, + response: 400 + }, + { + params: 200, + body: 200, + querystring: 200, + headers: 200, + response: 200 + } + ] + t.plan(testCases.length) + for (const testCase of testCases) { + await validate(testCase) + } + + async function validate ({ + params, + body, + querystring, + headers, + response + }) { + try { + const res = await fastify.inject({ + method: 'POST', + url: `/${params}`, + headers: { id: headers }, + query: { id: querystring }, + payload: { id: body } + }) + t.assert.strictEqual(res.statusCode, response) + } catch (error) { + t.assert.fail('should not throw') + } + } +}) + +test('Check mixed sync and async AJV validations', async (t) => { + const fastify = Fastify({ + exposeHeadRoutes: false, + ajv: { + customOptions: { + allErrors: true, + keywords: [ + { + keyword: 'idExists', + async: true, + type: 'number', + validate: checkIdExists + } + ] + } + } + }) + + async function checkIdExists (schema, data) { + const res = await Promise.resolve(data) + switch (res) { + case 200: + return true + + default: + return false + } + } + + const schemaSync = { + type: 'object', + properties: { + id: { type: 'integer' } + } + } + + const schemaAsync = { + $async: true, + type: 'object', + properties: { + id: { + type: 'integer', + idExists: { table: 'posts' } + } + } + } + + fastify.post('/queryAsync/:id', { + schema: { + params: schemaSync, + body: schemaSync, + query: schemaAsync, + headers: schemaSync + }, + handler (req, reply) { reply.send(req.body) } + }) + + fastify.post('/paramsAsync/:id', { + schema: { + params: schemaAsync, + body: schemaSync + }, + handler (req, reply) { reply.send(req.body) } + }) + + fastify.post('/bodyAsync/:id', { + schema: { + params: schemaAsync, + body: schemaAsync, + query: schemaSync + }, + handler (req, reply) { reply.send(req.body) } + }) + + fastify.post('/headersSync/:id', { + schema: { + params: schemaSync, + body: schemaSync, + query: schemaAsync, + headers: schemaSync + }, + handler (req, reply) { reply.send(req.body) } + }) + + fastify.post('/noHeader/:id', { + schema: { + params: schemaSync, + body: schemaSync, + query: schemaAsync + }, + handler (req, reply) { reply.send(req.body) } + }) + + fastify.post('/noBody/:id', { + schema: { + params: schemaSync, + query: schemaAsync, + headers: schemaSync + }, + handler (req, reply) { reply.send(req.body) } + }) + + const testCases = [ + { + url: '/queryAsync', + params: 200, + body: 200, + querystring: 200, + headers: 'not a number sync', + response: 400 + }, + { + url: '/paramsAsync', + params: 200, + body: 'not a number sync', + querystring: 200, + headers: 200, + response: 400 + }, + { + url: '/bodyAsync', + params: 200, + body: 200, + querystring: 'not a number sync', + headers: 200, + response: 400 + }, + { + url: '/headersSync', + params: 200, + body: 200, + querystring: 200, + headers: 'not a number sync', + response: 400 + }, + { + url: '/noHeader', + params: 200, + body: 200, + querystring: 200, + headers: 'not a number sync, but not validated', + response: 200 + }, + { + url: '/noBody', + params: 200, + body: 'not a number sync, but not validated', + querystring: 200, + headers: 'not a number sync', + response: 400 + } + ] + t.plan(testCases.length) + for (const testCase of testCases) { + await validate(testCase) + } + + async function validate ({ + url, + params, + body, + querystring, + headers, + response + }) { + try { + const res = await fastify.inject({ + method: 'POST', + url: `${url}/${params || ''}`, + headers: { id: headers }, + query: { id: querystring }, + payload: { id: body } + }) + t.assert.strictEqual(res.statusCode, response) + } catch (error) { + t.assert.fail('should not fail') + } + } +}) + +test('Check if hooks and attachValidation work with AJV validations', async (t) => { + const fastify = Fastify({ + exposeHeadRoutes: false, + ajv: { + customOptions: { + allErrors: true, + keywords: [ + { + keyword: 'idExists', + async: true, + type: 'number', + validate: checkIdExists + } + ] + } + } + }) + + async function checkIdExists (schema, data) { + const res = await Promise.resolve(data) + switch (res) { + case 200: + return true + + default: + return false + } + } + + const schemaAsync = { + $async: true, + type: 'object', + properties: { + id: { + type: 'integer', + idExists: { table: 'posts' } + } + } + } + + fastify.post('/:id', { + preHandler: function hook (request, reply, done) { + t.assert.strictEqual(request.validationError.message, 'validation failed') + t.assert.ok('preHandler called') + + reply.code(400).send(request.body) + }, + attachValidation: true, + schema: { + params: schemaAsync, + body: schemaAsync, + query: schemaAsync, + headers: schemaAsync + }, + handler (req, reply) { reply.send(req.body) } + }) + + const testCases = [ + { + params: 200, + body: 200, + querystring: 200, + headers: 400, + response: 400 + }, + { + params: 200, + body: 400, + querystring: 200, + headers: 200, + response: 400 + }, + { + params: 200, + body: 200, + querystring: 400, + headers: 200, + response: 400 + }, + { + params: 200, + body: 200, + querystring: 200, + headers: 400, + response: 400 + } + ] + t.plan(testCases.length * 3) + for (const testCase of testCases) { + await validate(testCase) + } + + async function validate ({ + params, + body, + querystring, + headers, + response + }) { + try { + const res = await fastify.inject({ + method: 'POST', + url: `/${params}`, + headers: { id: headers }, + query: { id: querystring }, + payload: { id: body } + }) + t.assert.strictEqual(res.statusCode, response) + } catch (error) { + t.assert.fail('should not fail') + } + } +}) diff --git a/services/slides/node_modules/fastify/test/schema-validation.test.js b/services/slides/node_modules/fastify/test/schema-validation.test.js new file mode 100644 index 0000000000000000000000000000000000000000..344d9531cf3b7c0fa3fcefb9850dc160b778a721 --- /dev/null +++ b/services/slides/node_modules/fastify/test/schema-validation.test.js @@ -0,0 +1,1593 @@ +'use strict' + +const { test } = require('node:test') +const Fastify = require('..') + +const AJV = require('ajv') +const Schema = require('fluent-json-schema') +const { waitForCb } = require('./toolkit') + +const customSchemaCompilers = { + body: new AJV({ + coerceTypes: false + }), + params: new AJV({ + coerceTypes: true + }), + querystring: new AJV({ + coerceTypes: true + }) +} + +const customValidatorCompiler = req => { + if (!req.httpPart) { + throw new Error('Missing httpPart') + } + + const compiler = customSchemaCompilers[req.httpPart] + + if (!compiler) { + throw new Error(`Missing compiler for ${req.httpPart}`) + } + + return compiler.compile(req.schema) +} + +const schemaA = { + $id: 'urn:schema:foo', + type: 'object', + definitions: { + foo: { type: 'integer' } + }, + properties: { + foo: { $ref: '#/definitions/foo' } + } +} +const schemaBRefToA = { + $id: 'urn:schema:response', + type: 'object', + required: ['foo'], + properties: { + foo: { $ref: 'urn:schema:foo#/definitions/foo' } + } +} + +const schemaCRefToB = { + $id: 'urn:schema:request', + type: 'object', + required: ['foo'], + properties: { + foo: { $ref: 'urn:schema:response#/properties/foo' } + } +} + +const schemaArtist = { + type: 'object', + properties: { + name: { type: 'string' }, + work: { type: 'string' } + }, + required: ['name', 'work'] +} + +test('Basic validation test', (t, testDone) => { + t.plan(6) + + const fastify = Fastify() + fastify.post('/', { + schema: { + body: schemaArtist + } + }, function (req, reply) { + reply.code(200).send(req.body.name) + }) + + const completion = waitForCb({ steps: 2 }) + fastify.inject({ + method: 'POST', + payload: { + name: 'michelangelo', + work: 'sculptor, painter, architect and poet' + }, + url: '/' + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(res.payload, 'michelangelo') + t.assert.strictEqual(res.statusCode, 200) + completion.stepIn() + }) + fastify.inject({ + method: 'POST', + payload: { name: 'michelangelo' }, + url: '/' + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(res.json(), { statusCode: 400, code: 'FST_ERR_VALIDATION', error: 'Bad Request', message: "body must have required property 'work'" }) + t.assert.strictEqual(res.statusCode, 400) + completion.stepIn() + }) + completion.patience.then(testDone) +}) + +test('Different schema per content type', (t, testDone) => { + t.plan(12) + + const fastify = Fastify() + fastify.addContentTypeParser('application/octet-stream', { + parseAs: 'buffer' + }, async function (_, payload) { + return payload + }) + fastify.post('/', { + schema: { + body: { + content: { + 'application/json': { + schema: schemaArtist + }, + 'application/octet-stream': { + schema: {} // Skip validation + }, + 'text/plain': { + schema: { type: 'string' } + } + } + } + } + }, async function (req, reply) { + return reply.send(req.body) + }) + + const completion = waitForCb({ steps: 4 }) + fastify.inject({ + url: '/', + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: { + name: 'michelangelo', + work: 'sculptor, painter, architect and poet' + } + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload).name, 'michelangelo') + t.assert.strictEqual(res.statusCode, 200) + completion.stepIn() + }) + fastify.inject({ + url: '/', + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: { name: 'michelangelo' } + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(res.json(), { statusCode: 400, code: 'FST_ERR_VALIDATION', error: 'Bad Request', message: "body must have required property 'work'" }) + t.assert.strictEqual(res.statusCode, 400) + completion.stepIn() + }) + fastify.inject({ + url: '/', + method: 'POST', + headers: { 'Content-Type': 'application/octet-stream' }, + body: Buffer.from('AAAAAAAA') + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(res.payload, 'AAAAAAAA') + t.assert.strictEqual(res.statusCode, 200) + completion.stepIn() + }) + fastify.inject({ + url: '/', + method: 'POST', + headers: { 'Content-Type': 'text/plain' }, + body: 'AAAAAAAA' + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(res.payload, 'AAAAAAAA') + t.assert.strictEqual(res.statusCode, 200) + completion.stepIn() + }) + completion.patience.then(testDone) +}) + +test('Skip validation if no schema for content type', (t, testDone) => { + t.plan(3) + + const fastify = Fastify() + fastify.post('/', { + schema: { + body: { + content: { + 'application/json': { + schema: schemaArtist + } + // No schema for 'text/plain' + } + } + } + }, async function (req, reply) { + return reply.send(req.body) + }) + fastify.inject({ + url: '/', + method: 'POST', + headers: { 'Content-Type': 'text/plain' }, + body: 'AAAAAAAA' + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(res.payload, 'AAAAAAAA') + t.assert.strictEqual(res.statusCode, 200) + testDone() + }) +}) + +test('Skip validation if no content type schemas', (t, testDone) => { + t.plan(3) + + const fastify = Fastify() + fastify.post('/', { + schema: { + body: { + content: { + // No schemas + } + } + } + }, async function (req, reply) { + return reply.send(req.body) + }) + fastify.inject({ + url: '/', + method: 'POST', + headers: { 'Content-Type': 'text/plain' }, + body: 'AAAAAAAA' + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(res.payload, 'AAAAAAAA') + t.assert.strictEqual(res.statusCode, 200) + testDone() + }) +}) + +test('External AJV instance', (t, testDone) => { + t.plan(5) + + const fastify = Fastify() + const ajv = new AJV() + ajv.addSchema(schemaA) + ajv.addSchema(schemaBRefToA) + + // the user must provide the schemas to fastify also + fastify.addSchema(schemaA) + fastify.addSchema(schemaBRefToA) + + fastify.setValidatorCompiler(({ schema, method, url, httpPart }) => { + t.assert.ok('custom validator compiler called') + return ajv.compile(schema) + }) + + fastify.post('/', { + handler (req, reply) { reply.send({ foo: 1 }) }, + schema: { + body: schemaCRefToB, + response: { + '2xx': ajv.getSchema('urn:schema:response').schema + } + } + }) + + const completion = waitForCb({ steps: 2 }) + fastify.inject({ + method: 'POST', + url: '/', + payload: { foo: 42 } + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + completion.stepIn() + }) + fastify.inject({ + method: 'POST', + url: '/', + payload: { foo: 'not a number' } + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 400) + completion.stepIn() + }) + completion.patience.then(testDone) +}) + +test('Encapsulation', (t, testDone) => { + t.plan(21) + + const fastify = Fastify() + const ajv = new AJV() + ajv.addSchema(schemaA) + ajv.addSchema(schemaBRefToA) + + // the user must provide the schemas to fastify also + fastify.addSchema(schemaA) + fastify.addSchema(schemaBRefToA) + + fastify.register((instance, opts, done) => { + const validator = ({ schema, method, url, httpPart }) => { + t.assert.ok('custom validator compiler called') + return ajv.compile(schema) + } + instance.setValidatorCompiler(validator) + instance.post('/one', { + handler (req, reply) { reply.send({ foo: 'one' }) }, + schema: { + body: ajv.getSchema('urn:schema:response').schema + } + }) + + instance.register((instance, opts, done) => { + instance.post('/two', { + handler (req, reply) { + t.assert.deepStrictEqual(instance.validatorCompiler, validator) + reply.send({ foo: 'two' }) + }, + schema: { + body: ajv.getSchema('urn:schema:response').schema + } + }) + + const anotherValidator = ({ schema, method, url, httpPart }) => { + return () => { return true } // always valid + } + instance.post('/three', { + validatorCompiler: anotherValidator, + handler (req, reply) { + t.assert.deepStrictEqual(instance.validatorCompiler, validator, 'the route validator does not change the instance one') + reply.send({ foo: 'three' }) + }, + schema: { + body: ajv.getSchema('urn:schema:response').schema + } + }) + done() + }) + done() + }) + + fastify.register((instance, opts, done) => { + instance.post('/clean', function (req, reply) { + t.assert.strictEqual(instance.validatorCompiler, undefined) + reply.send({ foo: 'bar' }) + }) + done() + }) + + const completion = waitForCb({ steps: 6 }) + fastify.inject({ + method: 'POST', + url: '/one', + payload: { foo: 1 } + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(res.json(), { foo: 'one' }) + completion.stepIn() + }) + fastify.inject({ + method: 'POST', + url: '/one', + payload: { wrongFoo: 'bar' } + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 400) + completion.stepIn() + }) + fastify.inject({ + method: 'POST', + url: '/two', + payload: { foo: 2 } + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(res.json(), { foo: 'two' }) + completion.stepIn() + }) + fastify.inject({ + method: 'POST', + url: '/two', + payload: { wrongFoo: 'bar' } + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 400) + completion.stepIn() + }) + fastify.inject({ + method: 'POST', + url: '/three', + payload: { wrongFoo: 'but works' } + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(res.json(), { foo: 'three' }) + completion.stepIn() + }) + fastify.inject({ + method: 'POST', + url: '/clean', + payload: { wrongFoo: 'bar' } + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(res.json(), { foo: 'bar' }) + completion.stepIn() + }) + completion.patience.then(testDone) +}) + +test('Triple $ref with a simple $id', (t, testDone) => { + t.plan(7) + + const fastify = Fastify() + const ajv = new AJV() + ajv.addSchema(schemaA) + ajv.addSchema(schemaBRefToA) + ajv.addSchema(schemaCRefToB) + + // the user must provide the schemas to fastify also + fastify.addSchema(schemaA) + fastify.addSchema(schemaBRefToA) + fastify.addSchema(schemaCRefToB) + + fastify.setValidatorCompiler(({ schema, method, url, httpPart }) => { + t.assert.ok('custom validator compiler called') + return ajv.compile(schema) + }) + + fastify.post('/', { + handler (req, reply) { reply.send({ foo: 105, bar: 'foo' }) }, + schema: { + body: ajv.getSchema('urn:schema:request').schema, + response: { + '2xx': ajv.getSchema('urn:schema:response').schema + } + } + }) + + const completion = waitForCb({ steps: 2 }) + fastify.inject({ + method: 'POST', + url: '/', + payload: { foo: 43 } + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(res.json(), { foo: 105 }) + completion.stepIn() + }) + fastify.inject({ + method: 'POST', + url: '/', + payload: { fool: 'bar' } + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 400) + t.assert.deepStrictEqual(res.json().message, "body must have required property 'foo'") + completion.stepIn() + }) + completion.patience.then(testDone) +}) + +test('Extending schema', (t, testDone) => { + t.plan(4) + const fastify = Fastify() + + fastify.addSchema({ + $id: 'address.id', + type: 'object', + definitions: { + address: { + type: 'object', + properties: { + city: { type: 'string' }, + state: { type: 'string' } + }, + required: ['city', 'state'] + } + } + }) + + fastify.post('/', { + handler (req, reply) { reply.send('works') }, + schema: { + body: { + type: 'object', + properties: { + billingAddress: { $ref: 'address.id#/definitions/address' }, + shippingAddress: { + allOf: [ + { $ref: 'address.id#/definitions/address' }, + { + type: 'object', + properties: { type: { enum: ['residential', 'business'] } }, + required: ['type'] + } + ] + } + } + } + } + }) + fastify.inject({ + method: 'POST', + url: '/', + payload: { + shippingAddress: { + city: 'Forlì', + state: 'FC' + } + } + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 400) + }) + fastify.inject({ + method: 'POST', + url: '/', + payload: { + shippingAddress: { + city: 'Forlì', + state: 'FC', + type: 'business' + } + } + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + testDone() + }) +}) + +test('Should work with nested ids', (t, testDone) => { + t.plan(6) + const fastify = Fastify() + + fastify.addSchema({ + $id: 'test', + type: 'object', + properties: { + id: { type: 'number' } + } + }) + + fastify.addSchema({ + $id: 'greetings', + type: 'string' + }) + + fastify.post('/:id', { + handler (req, reply) { reply.send(typeof req.params.id) }, + schema: { + params: { $ref: 'test#' }, + body: { + type: 'object', + properties: { + hello: { $ref: 'greetings#' } + } + } + } + }) + + const completion = waitForCb({ steps: 2 }) + fastify.inject({ + method: 'POST', + url: '/123', + payload: { + hello: 'world' + } + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload, 'number') + completion.stepIn() + }) + fastify.inject({ + method: 'POST', + url: '/abc', + payload: { + hello: 'world' + } + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 400) + t.assert.strictEqual(res.json().message, 'params/id must be number') + completion.stepIn() + }) + completion.patience.then(testDone) +}) + +test('Use the same schema across multiple routes', async (t) => { + t.plan(4) + const fastify = Fastify() + + fastify.addSchema({ + $id: 'test', + type: 'object', + properties: { + id: { type: 'number' } + } + }) + + fastify.get('/first/:id', { + handler (req, reply) { reply.send(typeof req.params.id) }, + schema: { + params: { $ref: 'test#' } + } + }) + + fastify.get('/second/:id', { + handler (req, reply) { reply.send(typeof req.params.id) }, + schema: { + params: { $ref: 'test#' } + } + }) + + const validTestCases = [ + '/first/123', + '/second/123' + ] + + for (const url of validTestCases) { + const res = await fastify.inject({ + url, + method: 'GET' + }) + + t.assert.strictEqual(res.payload, 'number') + } + + const invalidTestCases = [ + '/first/abc', + '/second/abc' + ] + + for (const url of invalidTestCases) { + const res = await fastify.inject({ + url, + method: 'GET' + }) + t.assert.strictEqual(res.statusCode, 400) + } +}) + +test('JSON Schema validation keywords', (t, testDone) => { + t.plan(6) + const fastify = Fastify() + + fastify.addSchema({ + $id: 'test', + type: 'object', + properties: { + ip: { + type: 'string', + format: 'ipv4' + } + } + }) + + fastify.get('/:ip', { + handler (req, reply) { reply.send(typeof req.params.ip) }, + schema: { + params: { $ref: 'test#' } + } + }) + + const completion = waitForCb({ steps: 2 }) + fastify.inject({ + method: 'GET', + url: '/127.0.0.1' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload, 'string') + completion.stepIn() + }) + fastify.inject({ + method: 'GET', + url: '/localhost' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 400) + t.assert.deepStrictEqual(res.json(), { + statusCode: 400, + code: 'FST_ERR_VALIDATION', + error: 'Bad Request', + message: 'params/ip must match format "ipv4"' + }) + completion.stepIn() + }) + completion.patience.then(testDone) +}) + +test('Nested id calls', (t, testDone) => { + t.plan(6) + const fastify = Fastify() + + fastify.addSchema({ + $id: 'test', + type: 'object', + properties: { + ip: { + type: 'string', + format: 'ipv4' + } + } + }) + + fastify.addSchema({ + $id: 'hello', + type: 'object', + properties: { + host: { $ref: 'test#' } + } + }) + + fastify.post('/', { + handler (req, reply) { reply.send(typeof req.body.host.ip) }, + schema: { + body: { $ref: 'hello#' } + } + }) + + const completion = waitForCb({ steps: 2 }) + fastify.inject({ + method: 'POST', + url: '/', + payload: { host: { ip: '127.0.0.1' } } + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload, 'string') + completion.stepIn() + }) + fastify.inject({ + method: 'POST', + url: '/', + payload: { host: { ip: 'localhost' } } + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 400) + t.assert.deepStrictEqual(res.json(), { + error: 'Bad Request', + message: 'body/host/ip must match format "ipv4"', + statusCode: 400, + code: 'FST_ERR_VALIDATION' + }) + completion.stepIn() + }) + completion.patience.then(testDone) +}) + +test('Use the same schema id in different places', (t, testDone) => { + t.plan(2) + const fastify = Fastify() + + fastify.addSchema({ + $id: 'test', + type: 'object', + properties: { + id: { type: 'number' } + } + }) + + fastify.post('/', { + handler (req, reply) { reply.send({ id: req.body.id / 2 }) }, + schema: { + body: { $ref: 'test#' }, + response: { + 200: { $ref: 'test#' } + } + } + }) + fastify.inject({ + method: 'POST', + url: '/', + payload: { id: 42 } + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(res.json(), { id: 21 }) + testDone() + }) +}) + +test('Use shared schema and $ref with $id ($ref to $id)', (t, testDone) => { + t.plan(5) + const fastify = Fastify() + + fastify.addSchema({ + $id: 'http://foo/test', + type: 'object', + properties: { + id: { type: 'number' } + } + }) + + const body = { + $id: 'http://foo/user', + $schema: 'http://json-schema.org/draft-07/schema#', + type: 'object', + definitions: { + address: { + $id: '#address', + type: 'object', + properties: { + city: { type: 'string' } + } + } + }, + required: ['address'], + properties: { + test: { $ref: 'http://foo/test#' }, // to external + address: { $ref: '#address' } // to local + } + } + + fastify.post('/', { + handler (req, reply) { reply.send(req.body.test) }, + schema: { + body, + response: { + 200: { $ref: 'http://foo/test#' } + } + } + }) + + const id = Date.now() + const completion = waitForCb({ steps: 2 }) + fastify.inject({ + method: 'POST', + url: '/', + payload: { + address: { city: 'New Node' }, + test: { id } + } + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(res.json(), { id }) + completion.stepIn() + }) + fastify.inject({ + method: 'POST', + url: '/', + payload: { test: { id } } + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 400) + t.assert.deepStrictEqual(res.json(), { + error: 'Bad Request', + message: "body must have required property 'address'", + statusCode: 400, + code: 'FST_ERR_VALIDATION' + }) + completion.stepIn() + }) + completion.patience.then(testDone) +}) + +test('Use items with $ref', (t, testDone) => { + t.plan(4) + const fastify = Fastify() + + fastify.addSchema({ + $id: 'http://fastify.test/ref-to-external-validator.json', + type: 'object', + properties: { + hello: { type: 'string' } + } + }) + + const body = { + type: 'array', + items: { $ref: 'http://fastify.test/ref-to-external-validator.json#' } + } + + fastify.post('/', { + schema: { body }, + handler: (_, r) => { r.send('ok') } + }) + + const completion = waitForCb({ steps: 2 }) + fastify.inject({ + method: 'POST', + url: '/', + payload: [{ hello: 'world' }] + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.payload, 'ok') + completion.stepIn() + }) + fastify.inject({ + method: 'POST', + url: '/', + payload: { hello: 'world' } + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 400) + completion.stepIn() + }) + completion.patience.then(testDone) +}) + +test('Use $ref to /definitions', (t, testDone) => { + t.plan(6) + const fastify = Fastify() + + fastify.addSchema({ + $id: 'test', + type: 'object', + properties: { + id: { type: 'number' } + } + }) + + const body = { + type: 'object', + definitions: { + address: { + $id: '#otherId', + type: 'object', + properties: { + city: { type: 'string' } + } + } + }, + properties: { + test: { $ref: 'test#' }, + address: { $ref: '#/definitions/address' } + }, + required: ['address', 'test'] + } + + fastify.post('/', { + schema: { + body, + response: { + 200: body + } + }, + handler: (req, reply) => { + req.body.removeThis = 'it should not be serialized' + reply.send(req.body) + } + }) + + const payload = { + address: { city: 'New Node' }, + test: { id: Date.now() } + } + const completion = waitForCb({ steps: 2 }) + fastify.inject({ + method: 'POST', + url: '/', + payload + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(res.json(), payload) + completion.stepIn() + }) + fastify.inject({ + method: 'POST', + url: '/', + payload: { + address: { city: 'New Node' }, + test: { id: 'wrong' } + } + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 400) + t.assert.deepStrictEqual(res.json(), { + error: 'Bad Request', + message: 'body/test/id must be number', + statusCode: 400, + code: 'FST_ERR_VALIDATION' + }) + completion.stepIn() + }) + completion.patience.then(testDone) +}) + +test('Custom AJV settings - pt1', (t, testDone) => { + t.plan(4) + const fastify = Fastify() + + fastify.post('/', { + schema: { + body: { + type: 'object', + properties: { + num: { type: 'integer' } + } + } + }, + handler: (req, reply) => { + t.assert.strictEqual(req.body.num, 12) + reply.send(req.body) + } + }) + fastify.inject({ + method: 'POST', + url: '/', + payload: { + num: '12' + } + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.deepStrictEqual(res.json(), { num: 12 }) + testDone() + }) +}) + +test('Custom AJV settings - pt2', (t, testDone) => { + t.plan(2) + const fastify = Fastify({ + ajv: { + customOptions: { + coerceTypes: false + } + } + }) + + fastify.post('/', { + schema: { + body: { + type: 'object', + properties: { + num: { type: 'integer' } + } + } + }, + handler: (req, reply) => { + t.fail('the handler is not called because the "12" is not coerced to number') + } + }) + fastify.inject({ + method: 'POST', + url: '/', + payload: { + num: '12' + } + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 400) + testDone() + }) +}) + +test('Custom AJV settings on different parameters - pt1', (t, testDone) => { + t.plan(2) + const fastify = Fastify() + + fastify.setValidatorCompiler(customValidatorCompiler) + + fastify.post('/api/:id', { + schema: { + querystring: { + type: 'object', + properties: { + id: { type: 'integer' } + } + }, + body: { + type: 'object', + properties: { + num: { type: 'number' } + }, + required: ['num'] + } + }, + handler: (req, reply) => { + t.fail('the handler is not called because the "12" is not coerced to number') + } + }) + fastify.inject({ + method: 'POST', + url: '/api/42', + payload: { + num: '12' + } + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 400) + testDone() + }) +}) + +test('Custom AJV settings on different parameters - pt2', (t, testDone) => { + t.plan(4) + const fastify = Fastify() + + fastify.setValidatorCompiler(customValidatorCompiler) + + fastify.post('/api/:id', { + schema: { + params: { + type: 'object', + properties: { + id: { type: 'number' } + }, + required: ['id'] + }, + body: { + type: 'object', + properties: { + num: { type: 'number' } + }, + required: ['num'] + } + }, + handler: (req, reply) => { + t.assert.deepStrictEqual(typeof req.params.id, 'number') + t.assert.deepStrictEqual(typeof req.body.num, 'number') + t.assert.deepStrictEqual(req.params.id, 42) + t.assert.deepStrictEqual(req.body.num, 12) + testDone() + } + }) + fastify.inject({ + method: 'POST', + url: '/api/42', + payload: { + num: 12 + } + }) +}) + +test("The same $id in route's schema must not overwrite others", (t, testDone) => { + t.plan(4) + const fastify = Fastify() + + const UserSchema = Schema.object() + .id('http://mydomain.com/user') + .title('User schema') + .description('Contains all user fields') + .prop('id', Schema.integer()) + .prop('username', Schema.string().minLength(4)) + .prop('firstName', Schema.string().minLength(1)) + .prop('lastName', Schema.string().minLength(1)) + .prop('fullName', Schema.string().minLength(1)) + .prop('email', Schema.string()) + .prop('password', Schema.string().minLength(6)) + .prop('bio', Schema.string()) + + const userCreateSchema = UserSchema.only([ + 'username', + 'firstName', + 'lastName', + 'email', + 'bio', + 'password', + 'password_confirm' + ]) + .required([ + 'username', + 'firstName', + 'lastName', + 'email', + 'bio', + 'password' + ]) + + const userPatchSchema = UserSchema.only([ + 'firstName', + 'lastName', + 'bio' + ]) + + fastify + .patch('/user/:id', { + schema: { body: userPatchSchema }, + handler: () => { return 'ok' } + }) + .post('/user', { + schema: { body: userCreateSchema }, + handler: () => { return 'ok' } + }) + + const completion = waitForCb({ steps: 2 }) + fastify.inject({ + method: 'POST', + url: '/user', + body: {} + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(res.json().message, "body must have required property 'username'") + completion.stepIn() + }) + fastify.inject({ + url: '/user/1', + method: 'PATCH', + body: {} + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(res.payload, 'ok') + completion.stepIn() + }) + completion.patience.then(testDone) +}) + +test('Custom validator compiler should not mutate schema', async t => { + t.plan(2) + class Headers { } + const fastify = Fastify() + + fastify.setValidatorCompiler(({ schema, method, url, httpPart }) => { + t.assert.ok(schema instanceof Headers) + return () => { } + }) + + fastify.get('/', { + schema: { + headers: new Headers() + } + }, () => { }) + + await fastify.ready() +}) + +test('Custom validator builder override by custom validator compiler', async t => { + t.plan(3) + const ajvDefaults = { + removeAdditional: true, + coerceTypes: true, + allErrors: true + } + const ajv1 = new AJV(ajvDefaults).addKeyword({ keyword: 'extended_one', type: 'object', validator: () => true }) + const ajv2 = new AJV(ajvDefaults).addKeyword({ keyword: 'extended_two', type: 'object', validator: () => true }) + const fastify = Fastify({ + schemaController: { + compilersFactory: { + buildValidator: () => (routeSchemaDef) => ajv1.compile(routeSchemaDef.schema) + } + } + }) + + fastify.setValidatorCompiler((routeSchemaDef) => ajv2.compile(routeSchemaDef.schema)) + + fastify.post('/two/:id', { + schema: { + params: { + type: 'object', + extended_two: true, + properties: { + id: { type: 'number' } + }, + required: ['id'] + } + }, + handler: (req, _reply) => { + t.assert.deepStrictEqual(typeof req.params.id, 'number') + t.assert.deepStrictEqual(req.params.id, 43) + return 'ok' + } + }) + + await fastify.ready() + + const two = await fastify.inject({ + method: 'POST', + url: '/two/43' + }) + t.assert.strictEqual(two.statusCode, 200) +}) + +test('Custom validator builder override by custom validator compiler in child instance', async t => { + t.plan(6) + const ajvDefaults = { + removeAdditional: true, + coerceTypes: true, + allErrors: true + } + const ajv1 = new AJV(ajvDefaults).addKeyword({ keyword: 'extended_one', type: 'object', validator: () => true }) + const ajv2 = new AJV(ajvDefaults).addKeyword({ keyword: 'extended_two', type: 'object', validator: () => true }) + const fastify = Fastify({ + schemaController: { + compilersFactory: { + buildValidator: () => (routeSchemaDef) => ajv1.compile(routeSchemaDef.schema) + } + } + }) + + fastify.register((embedded, _opts, done) => { + embedded.setValidatorCompiler((routeSchemaDef) => ajv2.compile(routeSchemaDef.schema)) + embedded.post('/two/:id', { + schema: { + params: { + type: 'object', + extended_two: true, + properties: { + id: { type: 'number' } + }, + required: ['id'] + } + }, + handler: (req, _reply) => { + t.assert.deepStrictEqual(typeof req.params.id, 'number') + t.assert.deepStrictEqual(req.params.id, 43) + return 'ok' + } + }) + done() + }) + + fastify.post('/one/:id', { + schema: { + params: { + type: 'object', + extended_one: true, + properties: { + id: { type: 'number' } + }, + required: ['id'] + } + }, + handler: (req, _reply) => { + t.assert.deepStrictEqual(typeof req.params.id, 'number') + t.assert.deepStrictEqual(req.params.id, 42) + return 'ok' + } + }) + + await fastify.ready() + + const one = await fastify.inject({ + method: 'POST', + url: '/one/42' + }) + t.assert.strictEqual(one.statusCode, 200) + + const two = await fastify.inject({ + method: 'POST', + url: '/two/43' + }) + t.assert.strictEqual(two.statusCode, 200) +}) + +test('Schema validation when no content type is provided', async t => { + // this case should not be happened in normal use-case, + // it is added for the completeness of code branch + const fastify = Fastify() + + fastify.post('/', { + schema: { + body: { + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + foo: { type: 'string' } + }, + required: ['foo'], + additionalProperties: false + } + } + } + } + }, + preValidation: async (request) => { + request.headers['content-type'] = undefined + } + }, async () => 'ok') + + await fastify.ready() + + const invalid = await fastify.inject({ + method: 'POST', + url: '/', + headers: { + 'content-type': 'application/json' + }, + body: { invalid: 'string' } + }) + t.assert.strictEqual(invalid.statusCode, 200) +}) + +test('Schema validation will not be bypass by different content type', async t => { + const fastify = Fastify() + + fastify.post('/', { + schema: { + body: { + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + foo: { type: 'string' } + }, + required: ['foo'], + additionalProperties: false + } + } + } + } + } + }, async () => 'ok') + + await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + const address = fastify.listeningOrigin + + let found = await fetch(address, { + method: 'POST', + url: '/', + headers: { + 'content-type': 'application/json' + }, + body: JSON.stringify({ foo: 'string' }) + }) + t.assert.strictEqual(found.status, 200) + await found.bytes() + + found = await fetch(address, { + method: 'POST', + url: '/', + headers: { + 'content-type': 'application/json; charset=utf-8' + }, + body: JSON.stringify({ foo: 'string' }) + }) + t.assert.strictEqual(found.status, 200) + await found.bytes() + + found = await fetch(address, { + method: 'POST', + url: '/', + headers: { + 'content-type': 'application/json\t; charset=utf-8' + }, + body: JSON.stringify({ foo: 'string' }) + }) + t.assert.strictEqual(found.status, 200) + await found.bytes() + + found = await fetch(address, { + method: 'POST', + url: '/', + headers: { + 'content-type': 'application/json ;' + }, + body: JSON.stringify({ invalid: 'string' }) + }) + t.assert.strictEqual(found.status, 400) + t.assert.strictEqual((await found.json()).code, 'FST_ERR_VALIDATION') + + let injected = await fastify.inject({ + method: 'POST', + url: '/', + headers: { + 'content-type': ' application/json' + }, + payload: JSON.stringify({ invalid: 'string' }) + }) + t.assert.strictEqual(injected.statusCode, 400) + t.assert.strictEqual(injected.json().code, 'FST_ERR_VALIDATION') + + injected = await fastify.inject({ + method: 'POST', + url: '/', + headers: { + 'content-type': ' application/json' + }, + payload: JSON.stringify({ foo: 'string' }) + }) + t.assert.strictEqual(injected.statusCode, 200) + + found = await fetch(address, { + method: 'POST', + url: '/', + headers: { + 'content-type': 'ApPlIcAtIoN/JsOn;' + }, + body: JSON.stringify({ invalid: 'string' }) + }) + t.assert.strictEqual(found.status, 400) + t.assert.strictEqual((await found.json()).code, 'FST_ERR_VALIDATION') + + found = await fetch(address, { + method: 'POST', + url: '/', + headers: { + 'content-type': 'ApPlIcAtIoN/JsOn ;' + }, + body: JSON.stringify({ invalid: 'string' }) + }) + t.assert.strictEqual(found.status, 400) + t.assert.strictEqual((await found.json()).code, 'FST_ERR_VALIDATION') + + found = await fetch(address, { + method: 'POST', + url: '/', + headers: { + 'content-type': 'ApPlIcAtIoN/JsOn foo;' + }, + body: JSON.stringify({ invalid: 'string' }) + }) + t.assert.strictEqual(found.status, 415) + t.assert.strictEqual((await found.json()).code, 'FST_ERR_CTP_INVALID_MEDIA_TYPE') + + found = await fetch(address, { + method: 'POST', + url: '/', + headers: { + 'content-type': 'ApPlIcAtIoN/JsOn \tfoo;' + }, + body: JSON.stringify({ invalid: 'string' }) + }) + t.assert.strictEqual(found.status, 415) + t.assert.strictEqual((await found.json()).code, 'FST_ERR_CTP_INVALID_MEDIA_TYPE') + + found = await fetch(address, { + method: 'POST', + url: '/', + headers: { + 'content-type': 'ApPlIcAtIoN/JsOn\t foo;' + }, + body: JSON.stringify({ invalid: 'string' }) + }) + t.assert.strictEqual(found.status, 415) + t.assert.strictEqual((await found.json()).code, 'FST_ERR_CTP_INVALID_MEDIA_TYPE') + + found = await fetch(address, { + method: 'POST', + url: '/', + headers: { + 'content-type': 'ApPlIcAtIoN/JsOn \t' + }, + body: JSON.stringify({ invalid: 'string' }) + }) + t.assert.strictEqual(found.status, 400) + t.assert.strictEqual((await found.json()).code, 'FST_ERR_VALIDATION') + + found = await fetch(address, { + method: 'POST', + url: '/', + headers: { + 'content-type': 'ApPlIcAtIoN/JsOn\t' + }, + body: JSON.stringify({ invalid: 'string' }) + }) + t.assert.strictEqual(found.status, 400) + t.assert.strictEqual((await found.json()).code, 'FST_ERR_VALIDATION') + + found = await fetch(address, { + method: 'POST', + url: '/', + headers: { + 'content-type': 'ApPlIcAtIoN/JsOn\ta' + }, + body: JSON.stringify({ invalid: 'string' }) + }) + t.assert.strictEqual(found.status, 415) + t.assert.strictEqual((await found.json()).code, 'FST_ERR_CTP_INVALID_MEDIA_TYPE') + + found = await fetch(address, { + method: 'POST', + url: '/', + headers: { + 'content-type': 'ApPlIcAtIoN/JsOn\ta; charset=utf-8' + }, + body: JSON.stringify({ invalid: 'string' }) + }) + t.assert.strictEqual(found.status, 415) + t.assert.strictEqual((await found.json()).code, 'FST_ERR_CTP_INVALID_MEDIA_TYPE') + + found = await fetch(address, { + method: 'POST', + url: '/', + headers: { + 'content-type': 'application/ json' + }, + body: JSON.stringify({ invalid: 'string' }) + }) + t.assert.strictEqual(found.status, 415) + t.assert.strictEqual((await found.json()).code, 'FST_ERR_CTP_INVALID_MEDIA_TYPE') +}) diff --git a/services/slides/node_modules/fastify/test/scripts/validate-ecosystem-links.test.js b/services/slides/node_modules/fastify/test/scripts/validate-ecosystem-links.test.js new file mode 100644 index 0000000000000000000000000000000000000000..9c98b291c5ed4b47bc5c16e4ec29bfb108802a80 --- /dev/null +++ b/services/slides/node_modules/fastify/test/scripts/validate-ecosystem-links.test.js @@ -0,0 +1,339 @@ +'use strict' + +const { describe, it, beforeEach, afterEach } = require('node:test') +const assert = require('node:assert') +const fs = require('node:fs') +const { MockAgent, setGlobalDispatcher, getGlobalDispatcher } = require('undici') + +function loadValidateEcosystemLinksModule () { + const modulePath = require.resolve('../../scripts/validate-ecosystem-links') + delete require.cache[modulePath] + return require(modulePath) +} + +describe('extractGitHubLinks', () => { + const { extractGitHubLinks } = loadValidateEcosystemLinksModule() + + it('extracts simple GitHub repository links', () => { + const content = ` +# Ecosystem + +- [fastify-helmet](https://github.com/fastify/fastify-helmet) - Important security headers for Fastify +- [fastify-cors](https://github.com/fastify/fastify-cors) - CORS support +` + const links = extractGitHubLinks(content) + + assert.strictEqual(links.length, 2) + assert.deepStrictEqual(links[0], { + name: 'fastify-helmet', + url: 'https://github.com/fastify/fastify-helmet', + owner: 'fastify', + repo: 'fastify-helmet' + }) + assert.deepStrictEqual(links[1], { + name: 'fastify-cors', + url: 'https://github.com/fastify/fastify-cors', + owner: 'fastify', + repo: 'fastify-cors' + }) + }) + + it('extracts links with different owner/repo combinations', () => { + const content = ` +- [some-plugin](https://github.com/user123/awesome-plugin) +- [another-lib](https://github.com/org-name/lib-name) +` + const links = extractGitHubLinks(content) + + assert.strictEqual(links.length, 2) + assert.strictEqual(links[0].owner, 'user123') + assert.strictEqual(links[0].repo, 'awesome-plugin') + assert.strictEqual(links[1].owner, 'org-name') + assert.strictEqual(links[1].repo, 'lib-name') + }) + + it('handles links with hash fragments', () => { + const content = ` +- [project](https://github.com/owner/repo#readme) +` + const links = extractGitHubLinks(content) + + assert.strictEqual(links.length, 1) + assert.strictEqual(links[0].repo, 'repo') + assert.strictEqual(links[0].url, 'https://github.com/owner/repo#readme') + }) + + it('handles links with query parameters', () => { + const content = ` +- [project](https://github.com/owner/repo?tab=readme) +` + const links = extractGitHubLinks(content) + + assert.strictEqual(links.length, 1) + assert.strictEqual(links[0].repo, 'repo') + }) + + it('handles links with subpaths', () => { + const content = ` +- [docs](https://github.com/owner/repo/tree/main/docs) +` + const links = extractGitHubLinks(content) + + assert.strictEqual(links.length, 1) + assert.strictEqual(links[0].owner, 'owner') + assert.strictEqual(links[0].repo, 'repo') + }) + + it('returns empty array for content with no GitHub links', () => { + const content = ` +# No GitHub links here + +Just some regular text and [a link](https://example.com). +` + const links = extractGitHubLinks(content) + + assert.strictEqual(links.length, 0) + }) + + it('ignores non-GitHub links', () => { + const content = ` +- [gitlab](https://gitlab.com/owner/repo) +- [github](https://github.com/owner/repo) +- [bitbucket](https://bitbucket.org/owner/repo) +` + const links = extractGitHubLinks(content) + + assert.strictEqual(links.length, 1) + assert.strictEqual(links[0].owner, 'owner') + }) + + it('extracts multiple links from complex markdown', () => { + const content = ` +## Category 1 + +Some description [inline link](https://github.com/a/b). + +| Plugin | Description | +|--------|-------------| +| [plugin1](https://github.com/x/y) | Desc 1 | +| [plugin2](https://github.com/z/w) | Desc 2 | +` + const links = extractGitHubLinks(content) + + assert.strictEqual(links.length, 3) + }) +}) + +describe('checkGitHubRepo', () => { + let originalDispatcher + let mockAgent + let originalFetch + let originalSetTimeout + + beforeEach(() => { + delete process.env.GITHUB_TOKEN + originalDispatcher = getGlobalDispatcher() + mockAgent = new MockAgent() + mockAgent.disableNetConnect() + setGlobalDispatcher(mockAgent) + originalFetch = global.fetch + originalSetTimeout = global.setTimeout + }) + + afterEach(async () => { + global.fetch = originalFetch + global.setTimeout = originalSetTimeout + setGlobalDispatcher(originalDispatcher) + await mockAgent.close() + delete process.env.GITHUB_TOKEN + }) + + it('returns exists: true for status 200', async () => { + const { checkGitHubRepo } = loadValidateEcosystemLinksModule() + const mockPool = mockAgent.get('https://api.github.com') + mockPool.intercept({ + path: '/repos/fastify/fastify', + method: 'HEAD' + }).reply(200) + + const result = await checkGitHubRepo('fastify', 'fastify') + + assert.strictEqual(result.exists, true) + assert.strictEqual(result.status, 200) + assert.strictEqual(result.owner, 'fastify') + assert.strictEqual(result.repo, 'fastify') + }) + + it('returns exists: false for status 404', async () => { + const { checkGitHubRepo } = loadValidateEcosystemLinksModule() + const mockPool = mockAgent.get('https://api.github.com') + mockPool.intercept({ + path: '/repos/nonexistent/repo', + method: 'HEAD' + }).reply(404) + + const result = await checkGitHubRepo('nonexistent', 'repo') + + assert.strictEqual(result.exists, false) + assert.strictEqual(result.status, 404) + }) + + it('returns invalid status for malformed owner or repository names', async () => { + const { checkGitHubRepo } = loadValidateEcosystemLinksModule() + let called = false + + global.fetch = async () => { + called = true + return { status: 200 } + } + + const result = await checkGitHubRepo('owner/evil', 'repo', 1) + + assert.strictEqual(called, false) + assert.strictEqual(result.exists, false) + assert.strictEqual(result.status, 'invalid') + assert.strictEqual(result.error, 'Invalid GitHub repository identifier') + }) + + it('retries on rate limit responses', async () => { + const { checkGitHubRepo } = loadValidateEcosystemLinksModule() + let attempts = 0 + + global.setTimeout = (fn) => { + fn() + return 0 + } + + global.fetch = async () => { + attempts++ + return { + status: attempts === 1 ? 403 : 200 + } + } + + const result = await checkGitHubRepo('owner', 'repo', 1) + + assert.strictEqual(attempts, 2) + assert.strictEqual(result.exists, true) + assert.strictEqual(result.status, 200) + }) + + it('adds authorization header when GITHUB_TOKEN is set', async () => { + process.env.GITHUB_TOKEN = 'my-token' + const { checkGitHubRepo } = loadValidateEcosystemLinksModule() + let authorization + + global.fetch = async (url, options) => { + authorization = options.headers.Authorization + return { + status: 200 + } + } + + const result = await checkGitHubRepo('owner', 'repo') + + assert.strictEqual(authorization, 'token my-token') + assert.strictEqual(result.exists, true) + }) + + it('handles network errors', async () => { + const { checkGitHubRepo } = loadValidateEcosystemLinksModule() + const mockPool = mockAgent.get('https://api.github.com') + mockPool.intercept({ + path: '/repos/owner/repo', + method: 'HEAD' + }).replyWithError(new Error('Network error')) + + const result = await checkGitHubRepo('owner', 'repo') + + assert.strictEqual(result.exists, false) + assert.strictEqual(result.status, 'error') + assert.ok(result.error.length > 0) + }) +}) + +describe('validateAllLinks', () => { + let originalReadFileSync + let originalFetch + let originalSetTimeout + let originalConsoleLog + let originalStdoutWrite + + beforeEach(() => { + originalReadFileSync = fs.readFileSync + originalFetch = global.fetch + originalSetTimeout = global.setTimeout + originalConsoleLog = console.log + originalStdoutWrite = process.stdout.write + + console.log = () => {} + process.stdout.write = () => true + + global.setTimeout = (fn) => { + fn() + return 0 + } + }) + + afterEach(() => { + fs.readFileSync = originalReadFileSync + global.fetch = originalFetch + global.setTimeout = originalSetTimeout + console.log = originalConsoleLog + process.stdout.write = originalStdoutWrite + }) + + it('validates links, deduplicates repositories and groups inaccessible links', async () => { + const { validateAllLinks } = loadValidateEcosystemLinksModule() + + fs.readFileSync = () => ` +- [repo one](https://github.com/owner/repo) +- [repo one duplicate](https://github.com/owner/repo) +- [repo two](https://github.com/another/project) +` + + let requests = 0 + global.fetch = async (url) => { + requests++ + const pathname = new URL(url).pathname + + if (pathname === '/repos/owner/repo') { + return { status: 404 } + } + + if (pathname === '/repos/another/project') { + return { status: 200 } + } + + throw new Error(`Unexpected url: ${url}`) + } + + const result = await validateAllLinks() + + assert.strictEqual(requests, 2) + assert.strictEqual(result.notFound.length, 1) + assert.strictEqual(result.found.length, 1) + assert.strictEqual(result.notFound[0].owner, 'owner') + assert.strictEqual(result.notFound[0].repo, 'repo') + assert.strictEqual(result.found[0].owner, 'another') + assert.strictEqual(result.found[0].repo, 'project') + }) + + it('returns empty result when no GitHub links are present', async () => { + const { validateAllLinks } = loadValidateEcosystemLinksModule() + + fs.readFileSync = () => '# Ecosystem\nNo links here.' + + let requests = 0 + global.fetch = async () => { + requests++ + return { status: 200 } + } + + const result = await validateAllLinks() + + assert.strictEqual(requests, 0) + assert.strictEqual(result.notFound.length, 0) + assert.strictEqual(result.found.length, 0) + }) +}) diff --git a/services/slides/node_modules/fastify/test/serialize-response.test.js b/services/slides/node_modules/fastify/test/serialize-response.test.js new file mode 100644 index 0000000000000000000000000000000000000000..4adf5ece388a0c87c74292ad0e943478a7fd908b --- /dev/null +++ b/services/slides/node_modules/fastify/test/serialize-response.test.js @@ -0,0 +1,186 @@ +'use strict' + +const { test } = require('node:test') +const { S } = require('fluent-json-schema') +const Fastify = require('../fastify') +const sjson = require('secure-json-parse') + +const BadRequestSchema = S.object() + .prop('statusCode', S.number()) + .prop('error', S.string()) + .prop('message', S.string()) + +const InternalServerErrorSchema = S.object() + .prop('statusCode', S.number()) + .prop('error', S.string()) + .prop('message', S.string()) + +const NotFoundSchema = S.object() + .prop('statusCode', S.number()) + .prop('error', S.string()) + .prop('message', S.string()) + +const options = { + schema: { + body: { + type: 'object', + properties: { + id: { type: 'string' } + } + }, + response: { + 200: { + type: 'object', + properties: { + id: { type: 'string' } + } + }, + 400: { + description: 'Bad Request', + content: { + 'application/json': { + schema: BadRequestSchema.valueOf() + } + } + }, + 404: { + description: 'Resource not found', + content: { + 'application/json': { + schema: NotFoundSchema.valueOf(), + example: { + statusCode: 404, + error: 'Not Found', + message: 'Not Found' + } + } + } + }, + 500: { + description: 'Internal Server Error', + content: { + 'application/json': { + schema: InternalServerErrorSchema.valueOf(), + example: { + message: 'Internal Server Error' + } + } + } + } + } + } +} + +const handler = (request, reply) => { + if (request.body.id === '400') { + return reply.status(400).send({ + statusCode: 400, + error: 'Bad Request', + message: 'Custom message', + extra: 'This should not be in the response' + }) + } + + if (request.body.id === '404') { + return reply.status(404).send({ + statusCode: 404, + error: 'Not Found', + message: 'Custom Not Found', + extra: 'This should not be in the response' + }) + } + + if (request.body.id === '500') { + reply.status(500).send({ + statusCode: 500, + error: 'Internal Server Error', + message: 'Custom Internal Server Error', + extra: 'This should not be in the response' + }) + } + + reply.send({ + id: request.body.id, + extra: 'This should not be in the response' + }) +} + +test('serialize the response for a Bad Request error, as defined on the schema', async t => { + t.plan(2) + + const fastify = Fastify({}) + + fastify.post('/', options, handler) + const response = await fastify.inject({ + method: 'POST', + url: '/' + }) + + t.assert.strictEqual(response.statusCode, 400) + t.assert.deepStrictEqual(sjson(response.body), { + statusCode: 400, + error: 'Bad Request', + message: 'body must be object' + }) +}) + +test('serialize the response for a Not Found error, as defined on the schema', async t => { + t.plan(2) + + const fastify = Fastify({}) + + fastify.post('/', options, handler) + + const response = await fastify.inject({ + method: 'POST', + url: '/', + body: { id: '404' } + }) + + t.assert.strictEqual(response.statusCode, 404) + t.assert.deepStrictEqual(sjson(response.body), { + statusCode: 404, + error: 'Not Found', + message: 'Custom Not Found' + }) +}) + +test('serialize the response for a Internal Server Error error, as defined on the schema', async t => { + t.plan(2) + + const fastify = Fastify({}) + + fastify.post('/', options, handler) + + const response = await fastify.inject({ + method: 'POST', + url: '/', + body: { id: '500' } + }) + + t.assert.strictEqual(response.statusCode, 500) + t.assert.deepStrictEqual(sjson(response.body), { + statusCode: 500, + error: 'Internal Server Error', + message: 'Custom Internal Server Error' + }) +}) + +test('serialize the success response, as defined on the schema', async t => { + t.plan(2) + + const fastify = Fastify({}) + + fastify.post('/', options, handler) + + const response = await fastify.inject({ + method: 'POST', + url: '/', + body: { id: 'test' } + }) + + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(sjson(response.body), { + id: 'test' + }) +}) diff --git a/services/slides/node_modules/fastify/test/server.test.js b/services/slides/node_modules/fastify/test/server.test.js new file mode 100644 index 0000000000000000000000000000000000000000..56c83c1439ac7917ea149f374e2d6d7384e1349d --- /dev/null +++ b/services/slides/node_modules/fastify/test/server.test.js @@ -0,0 +1,347 @@ +'use strict' + +const dns = require('node:dns') +const { networkInterfaces } = require('node:os') +const { test } = require('node:test') +const Fastify = require('..') +const undici = require('undici') +const proxyquire = require('proxyquire') + +const isIPv6Missing = !Object.values(networkInterfaces()).flat().some(({ family }) => family === 'IPv6') + +test('listen should accept null port', async t => { + const fastify = Fastify() + t.after(() => fastify.close()) + + await t.assert.doesNotReject( + fastify.listen({ port: null }) + ) +}) + +test('listen should accept undefined port', async t => { + const fastify = Fastify() + t.after(() => fastify.close()) + + await t.assert.doesNotReject( + fastify.listen({ port: undefined }) + ) +}) + +test('listen should accept stringified number port', async t => { + const fastify = Fastify() + t.after(() => fastify.close()) + + await t.assert.doesNotReject( + fastify.listen({ port: '1234' }) + ) +}) + +test('listen should accept log text resolution function', async t => { + const fastify = Fastify() + t.after(() => fastify.close()) + + await t.assert.doesNotReject( + fastify.listen({ + host: '127.0.0.1', + port: '1234', + listenTextResolver: (address) => { + t.assert.strictEqual(address, 'http://127.0.0.1:1234') + return 'hardcoded text' + } + }) + ) +}) + +test('listen should reject string port', async (t) => { + const fastify = Fastify() + t.after(() => fastify.close()) + + try { + await fastify.listen({ port: 'hello-world' }) + } catch (error) { + t.assert.strictEqual(error.code, 'ERR_SOCKET_BAD_PORT') + } + + try { + await fastify.listen({ port: '1234hello' }) + } catch (error) { + t.assert.strictEqual(error.code, 'ERR_SOCKET_BAD_PORT') + } +}) + +test('Test for hostname and port', async (t) => { + t.plan(3) + const app = Fastify() + t.after(() => app.close()) + app.get('/host', (req, res) => { + t.assert.strictEqual(req.host, 'localhost:8000') + t.assert.strictEqual(req.hostname, 'localhost') + t.assert.strictEqual(req.port, 8000) + res.send('ok') + }) + + await app.listen({ port: 8000 }) + await fetch('http://localhost:8000/host') +}) + +test('Test for IPV6 port', { skip: isIPv6Missing }, async (t) => { + t.plan(3) + const app = Fastify() + t.after(() => app.close()) + app.get('/host', (req, res) => { + t.assert.strictEqual(req.host, '[::1]:3040') + t.assert.strictEqual(req.hostname, '[::1]') + t.assert.strictEqual(req.port, 3040) + res.send('ok') + }) + + await app.listen({ + port: 3040, + host: '::1' + }) + await fetch('http://[::1]:3040/host') +}) + +test('abort signal', async t => { + await t.test('should close server when aborted after', (t, end) => { + t.plan(2) + function onClose (instance, done) { + t.assert.strictEqual(instance, fastify) + done() + end() + } + + const controller = new AbortController() + + const fastify = Fastify() + fastify.addHook('onClose', onClose) + fastify.listen({ port: 1234, signal: controller.signal }, (err) => { + t.assert.ifError(err) + controller.abort() + }) + }) + + await t.test('should close server when aborted after - promise', async (t) => { + t.plan(2) + const resolver = {} + resolver.promise = new Promise(function (resolve) { + resolver.resolve = resolve + }) + function onClose (instance, done) { + t.assert.strictEqual(instance, fastify) + done() + resolver.resolve() + } + + const controller = new AbortController() + + const fastify = Fastify() + fastify.addHook('onClose', onClose) + const address = await fastify.listen({ port: 1234, signal: controller.signal }) + t.assert.ok(address) + controller.abort() + await resolver.promise + }) + + await t.test('should close server when aborted during fastify.ready - promise', async (t) => { + t.plan(2) + const resolver = {} + resolver.promise = new Promise(function (resolve) { + resolver.resolve = resolve + }) + function onClose (instance, done) { + t.assert.strictEqual(instance, fastify) + done() + resolver.resolve() + } + + const controller = new AbortController() + + const fastify = Fastify() + fastify.addHook('onClose', onClose) + const promise = fastify.listen({ port: 1234, signal: controller.signal }) + controller.abort() + const address = await promise + // since the main server is not listening yet, or will not listen + // it should return undefined + t.assert.strictEqual(address, undefined) + await resolver.promise + }) + + await t.test('should close server when aborted during dns.lookup - promise', async (t) => { + t.plan(2) + const Fastify = proxyquire('..', { + './lib/server.js': proxyquire('../lib/server.js', { + 'node:dns': { + lookup: function (host, option, callback) { + controller.abort() + dns.lookup(host, option, callback) + } + } + }) + }) + const resolver = {} + resolver.promise = new Promise(function (resolve) { + resolver.resolve = resolve + }) + function onClose (instance, done) { + t.assert.strictEqual(instance, fastify) + done() + resolver.resolve() + } + + const controller = new AbortController() + + const fastify = Fastify() + fastify.addHook('onClose', onClose) + const address = await fastify.listen({ port: 1234, signal: controller.signal }) + // since the main server is already listening then close + // it should return address + t.assert.ok(address) + await resolver.promise + }) + + await t.test('should close server when aborted before', (t, end) => { + t.plan(1) + function onClose (instance, done) { + t.assert.strictEqual(instance, fastify) + done() + end() + } + + const controller = new AbortController() + controller.abort() + + const fastify = Fastify() + fastify.addHook('onClose', onClose) + fastify.listen({ port: 1234, signal: controller.signal }, () => { + t.assert.fail('should not reach callback') + }) + }) + + await t.test('should close server when aborted before - promise', async (t) => { + t.plan(2) + const resolver = {} + resolver.promise = new Promise(function (resolve) { + resolver.resolve = resolve + }) + function onClose (instance, done) { + t.assert.strictEqual(instance, fastify) + done() + resolver.resolve() + } + + const controller = new AbortController() + controller.abort() + + const fastify = Fastify() + fastify.addHook('onClose', onClose) + const address = await fastify.listen({ port: 1234, signal: controller.signal }) + t.assert.strictEqual(address, undefined) // ensure the API signature + await resolver.promise + }) + + await t.test('listen should not start server', (t, end) => { + t.plan(2) + function onClose (instance, done) { + t.assert.strictEqual(instance, fastify) + done() + end() + } + const controller = new AbortController() + + const fastify = Fastify() + fastify.addHook('onClose', onClose) + fastify.listen({ port: 1234, signal: controller.signal }, (err) => { + t.assert.ifError(err) + }) + controller.abort() + t.assert.strictEqual(fastify.server.listening, false) + }) + + await t.test('listen should not start server if already aborted', (t, end) => { + t.plan(2) + function onClose (instance, done) { + t.assert.strictEqual(instance, fastify) + done() + end() + } + + const controller = new AbortController() + controller.abort() + const fastify = Fastify() + fastify.addHook('onClose', onClose) + fastify.listen({ port: 1234, signal: controller.signal }, (err) => { + t.assert.ifError(err) + }) + t.assert.strictEqual(fastify.server.listening, false) + }) + + await t.test('listen should throw if received invalid signal', t => { + t.plan(2) + const fastify = Fastify() + + try { + fastify.listen({ port: 1234, signal: {} }, (err) => { + t.assert.ifError(err) + }) + t.assert.fail('should throw') + } catch (e) { + t.assert.strictEqual(e.code, 'FST_ERR_LISTEN_OPTIONS_INVALID') + t.assert.strictEqual(e.message, 'Invalid listen options: \'Invalid options.signal\'') + } + }) +}) + +test('#5180 - preClose should be called before closing secondary server', async (t) => { + t.plan(2) + const fastify = Fastify({ forceCloseConnections: true }) + let flag = false + t.after(() => fastify.close()) + + fastify.addHook('preClose', () => { + flag = true + }) + + fastify.get('/', async (req, reply) => { + // request will be pending for 1 second to simulate a slow request + await new Promise((resolve) => { setTimeout(resolve, 1000) }) + return { hello: 'world' } + }) + + fastify.listen({ port: 0 }, (err) => { + t.assert.ifError(err) + const addresses = fastify.addresses() + const mainServerAddress = fastify.server.address() + let secondaryAddress + for (const addr of addresses) { + if (addr.family !== mainServerAddress.family) { + secondaryAddress = addr + secondaryAddress.address = secondaryAddress.family === 'IPv6' + ? `[${secondaryAddress.address}]` + : secondaryAddress.address + break + } + } + + if (!secondaryAddress) { + t.assert.ok(true, 'Secondary address not found') + return + } + + undici.request(`http://${secondaryAddress.address}:${secondaryAddress.port}/`) + .then( + () => { t.assert.fail('Request should not succeed') }, + () => { + t.assert.ok(flag) + } + ) + + // Close the server while the slow request is pending + setTimeout(fastify.close, 250) + }) + + // Wait 1000ms to ensure that the test is finished and async operations are + // completed + await new Promise((resolve) => { setTimeout(resolve, 1000) }) +}) diff --git a/services/slides/node_modules/fastify/test/set-error-handler.test.js b/services/slides/node_modules/fastify/test/set-error-handler.test.js new file mode 100644 index 0000000000000000000000000000000000000000..9fbe9b27626dff7b1ec449cc5314b7f5cf15bc61 --- /dev/null +++ b/services/slides/node_modules/fastify/test/set-error-handler.test.js @@ -0,0 +1,69 @@ +'use strict' + +const { test } = require('node:test') +const Fastify = require('..') +const { FST_ERR_ERROR_HANDLER_NOT_FN, FST_ERR_ERROR_HANDLER_ALREADY_SET } = require('../lib/errors') + +test('setErrorHandler should throw an error if the handler is not a function', t => { + t.plan(1) + const fastify = Fastify() + + t.assert.throws(() => fastify.setErrorHandler('not a function'), new FST_ERR_ERROR_HANDLER_NOT_FN()) +}) + +test('setErrorHandler can be set independently in parent and child scopes', async t => { + t.plan(1) + + const fastify = Fastify() + + t.assert.doesNotThrow(() => { + fastify.setErrorHandler(() => {}) + fastify.register(async (child) => { + child.setErrorHandler(() => {}) + }) + }) +}) + +test('setErrorHandler can be overridden if allowErrorHandlerOverride is set to true', async t => { + t.plan(2) + + const fastify = Fastify() + t.after(() => fastify.close()) + + fastify.register(async (child) => { + child.setErrorHandler(() => {}) + t.assert.doesNotThrow(() => child.setErrorHandler(() => {})) + }) + + fastify.setErrorHandler(() => {}) + t.assert.doesNotThrow(() => fastify.setErrorHandler(() => {})) + + await fastify.ready() +}) + +test('if `allowErrorHandlerOverride` is disabled, setErrorHandler should throw if called more than once in the same scope', t => { + t.plan(1) + + const fastify = Fastify({ + allowErrorHandlerOverride: false + }) + + fastify.setErrorHandler(() => {}) + t.assert.throws(() => fastify.setErrorHandler(() => {}), new FST_ERR_ERROR_HANDLER_ALREADY_SET()) +}) + +test('if `allowErrorHandlerOverride` is disabled, setErrorHandler should throw if called more than once in the same scope 2', async t => { + t.plan(1) + + const fastify = Fastify({ + allowErrorHandlerOverride: false + }) + t.after(() => fastify.close()) + + fastify.register(async (child) => { + child.setErrorHandler(() => {}) + t.assert.throws(() => child.setErrorHandler(() => {}), new FST_ERR_ERROR_HANDLER_ALREADY_SET()) + }) + + await fastify.ready() +}) diff --git a/services/slides/node_modules/fastify/test/skip-reply-send.test.js b/services/slides/node_modules/fastify/test/skip-reply-send.test.js new file mode 100644 index 0000000000000000000000000000000000000000..506bf62e38823c28588da29cffcc67fd18e48d5a --- /dev/null +++ b/services/slides/node_modules/fastify/test/skip-reply-send.test.js @@ -0,0 +1,317 @@ +'use strict' + +const { test, describe } = require('node:test') +const split = require('split2') +const net = require('node:net') +const Fastify = require('../fastify') + +process.removeAllListeners('warning') + +const lifecycleHooks = [ + 'onRequest', + 'preParsing', + 'preValidation', + 'preHandler', + 'preSerialization', + 'onSend', + 'onTimeout', + 'onResponse', + 'onError' +] + +test('skip automatic reply.send() with reply.hijack and a body', async (t) => { + const stream = split(JSON.parse) + const app = Fastify({ + logger: { + stream + } + }) + + stream.on('data', (line) => { + t.assert.notStrictEqual(line.level, 40) // there are no errors + t.assert.notStrictEqual(line.level, 50) // there are no errors + }) + + app.get('/', (req, reply) => { + reply.hijack() + reply.raw.end('hello world') + + return Promise.resolve('this will be skipped') + }) + + await app.inject({ + method: 'GET', + url: '/' + }).then((res) => { + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.body, 'hello world') + }) +}) + +test('skip automatic reply.send() with reply.hijack and no body', async (t) => { + const stream = split(JSON.parse) + const app = Fastify({ + logger: { + stream + } + }) + + stream.on('data', (line) => { + t.assert.notStrictEqual(line.level, 40) // there are no error + t.assert.notStrictEqual(line.level, 50) // there are no error + }) + + app.get('/', (req, reply) => { + reply.hijack() + reply.raw.end('hello world') + + return Promise.resolve() + }) + + await app.inject({ + method: 'GET', + url: '/' + }).then((res) => { + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.body, 'hello world') + }) +}) + +test('skip automatic reply.send() with reply.hijack and an error', async (t) => { + const stream = split(JSON.parse) + const app = Fastify({ + logger: { + stream + } + }) + + let errorSeen = false + + stream.on('data', (line) => { + if (line.level === 50) { + errorSeen = true + t.assert.strictEqual(line.err.message, 'kaboom') + t.assert.strictEqual(line.msg, 'Promise errored, but reply.sent = true was set') + } + }) + + app.get('/', (req, reply) => { + reply.hijack() + reply.raw.end('hello world') + + return Promise.reject(new Error('kaboom')) + }) + + await app.inject({ + method: 'GET', + url: '/' + }).then((res) => { + t.assert.strictEqual(errorSeen, true) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.body, 'hello world') + }) +}) + +function testHandlerOrBeforeHandlerHook (test, hookOrHandler) { + const idx = hookOrHandler === 'handler' ? lifecycleHooks.indexOf('preHandler') : lifecycleHooks.indexOf(hookOrHandler) + const previousHooks = lifecycleHooks.slice(0, idx) + const nextHooks = lifecycleHooks.slice(idx + 1) + + describe(`Hijacking inside ${hookOrHandler} skips all the following hooks and handler execution`, () => { + test('Sending a response using reply.raw => onResponse hook is called', async (t) => { + const stream = split(JSON.parse) + const app = Fastify({ + logger: { + stream + } + }) + + stream.on('data', (line) => { + t.assert.notStrictEqual(line.level, 40) // there are no errors + t.assert.notStrictEqual(line.level, 50) // there are no errors + }) + + previousHooks.forEach(h => app.addHook(h, async (req, reply) => t.assert.ok(`${h} should be called`))) + + if (hookOrHandler === 'handler') { + app.get('/', (req, reply) => { + reply.hijack() + reply.raw.end(`hello from ${hookOrHandler}`) + }) + } else { + app.addHook(hookOrHandler, async (req, reply) => { + reply.hijack() + reply.raw.end(`hello from ${hookOrHandler}`) + }) + app.get('/', (req, reply) => t.assert.fail('Handler should not be called')) + } + + nextHooks.forEach(h => { + if (h === 'onResponse') { + app.addHook(h, async (req, reply) => t.assert.ok(`${h} should be called`)) + } else { + app.addHook(h, async (req, reply) => t.assert.fail(`${h} should not be called`)) + } + }) + + await app.inject({ + method: 'GET', + url: '/' + }).then((res) => { + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.body, `hello from ${hookOrHandler}`) + }) + }) + + test('Sending a response using req.socket => onResponse not called', (t, testDone) => { + const stream = split(JSON.parse) + const app = Fastify({ + logger: { + stream + } + }) + t.after(() => app.close()) + + stream.on('data', (line) => { + t.assert.notStrictEqual(line.level, 40) // there are no errors + t.assert.notStrictEqual(line.level, 50) // there are no errors + }) + + previousHooks.forEach(h => app.addHook(h, async (req, reply) => t.assert.ok(`${h} should be called`))) + + if (hookOrHandler === 'handler') { + app.get('/', (req, reply) => { + reply.hijack() + req.socket.write('HTTP/1.1 200 OK\r\n\r\n') + req.socket.write(`hello from ${hookOrHandler}`) + req.socket.end() + }) + } else { + app.addHook(hookOrHandler, async (req, reply) => { + reply.hijack() + req.socket.write('HTTP/1.1 200 OK\r\n\r\n') + req.socket.write(`hello from ${hookOrHandler}`) + req.socket.end() + }) + app.get('/', (req, reply) => t.assert.fail('Handler should not be called')) + } + + nextHooks.forEach(h => app.addHook(h, async (req, reply) => t.assert.fail(`${h} should not be called`))) + + app.listen({ port: 0 }, err => { + t.assert.ifError(err) + const client = net.createConnection({ port: (app.server.address()).port }, () => { + client.write('GET / HTTP/1.1\r\nHost: fastify.test\r\n\r\n') + + let chunks = '' + client.setEncoding('utf8') + client.on('data', data => { + chunks += data + }) + + client.on('end', function () { + t.assert.match(chunks, new RegExp(`hello from ${hookOrHandler}`, 'i')) + testDone() + }) + }) + }) + }) + + test('Throwing an error does not trigger any hooks', async (t) => { + const stream = split(JSON.parse) + const app = Fastify({ + logger: { + stream + } + }) + t.after(() => app.close()) + + let errorSeen = false + stream.on('data', (line) => { + if (hookOrHandler === 'handler') { + if (line.level === 40) { + errorSeen = true + t.assert.strictEqual(line.err.code, 'FST_ERR_REP_ALREADY_SENT') + } + } else { + t.assert.notStrictEqual(line.level, 40) // there are no errors + t.assert.notStrictEqual(line.level, 50) // there are no errors + } + }) + + previousHooks.forEach(h => app.addHook(h, async (req, reply) => t.assert.ok(`${h} should be called`))) + + if (hookOrHandler === 'handler') { + app.get('/', (req, reply) => { + reply.hijack() + throw new Error('This will be skipped') + }) + } else { + app.addHook(hookOrHandler, async (req, reply) => { + reply.hijack() + throw new Error('This will be skipped') + }) + app.get('/', (req, reply) => t.assert.fail('Handler should not be called')) + } + + nextHooks.forEach(h => app.addHook(h, async (req, reply) => t.assert.fail(`${h} should not be called`))) + + await Promise.race([ + app.inject({ method: 'GET', url: '/' }), + new Promise((resolve, reject) => setTimeout(resolve, 1000)) + ]) + + if (hookOrHandler === 'handler') { + t.assert.strictEqual(errorSeen, true) + } + }) + + test('Calling reply.send() after hijacking logs a warning', async (t) => { + const stream = split(JSON.parse) + const app = Fastify({ + logger: { + stream + } + }) + + let errorSeen = false + + stream.on('data', (line) => { + if (line.level === 40) { + errorSeen = true + t.assert.strictEqual(line.err.code, 'FST_ERR_REP_ALREADY_SENT') + } + }) + + previousHooks.forEach(h => app.addHook(h, async (req, reply) => t.assert.ok(`${h} should be called`))) + + if (hookOrHandler === 'handler') { + app.get('/', (req, reply) => { + reply.hijack() + reply.send('hello from reply.send()') + }) + } else { + app.addHook(hookOrHandler, async (req, reply) => { + reply.hijack() + return reply.send('hello from reply.send()') + }) + app.get('/', (req, reply) => t.assert.fail('Handler should not be called')) + } + + nextHooks.forEach(h => app.addHook(h, async (req, reply) => t.assert.fail(`${h} should not be called`))) + + await Promise.race([ + app.inject({ method: 'GET', url: '/' }), + new Promise((resolve, reject) => setTimeout(resolve, 1000)) + ]) + + t.assert.strictEqual(errorSeen, true) + }) + }) +} + +testHandlerOrBeforeHandlerHook(test, 'onRequest') +testHandlerOrBeforeHandlerHook(test, 'preParsing') +testHandlerOrBeforeHandlerHook(test, 'preValidation') +testHandlerOrBeforeHandlerHook(test, 'preHandler') +testHandlerOrBeforeHandlerHook(test, 'handler') diff --git a/services/slides/node_modules/fastify/test/stream-serializers.test.js b/services/slides/node_modules/fastify/test/stream-serializers.test.js new file mode 100644 index 0000000000000000000000000000000000000000..2df3ad5feb0c49fe2cd978af6b7a82634bb5d465 --- /dev/null +++ b/services/slides/node_modules/fastify/test/stream-serializers.test.js @@ -0,0 +1,40 @@ +'use strict' + +const { test } = require('node:test') +const Fastify = require('..') +const Reply = require('../lib/reply') + +test('should serialize reply when response stream is ended', (t, done) => { + t.plan(5) + + const stream = require('node:stream') + const fastify = Fastify({ + logger: { + serializers: { + res (reply) { + t.assert.strictEqual(reply instanceof Reply, true) + t.assert.ok('passed') + return reply + } + } + } + }) + + fastify.get('/error', function (req, reply) { + const reallyLongStream = new stream.Readable({ + read: () => { } + }) + reply.code(200).send(reallyLongStream) + reply.raw.end(Buffer.from('hello\n')) + }) + + t.after(() => fastify.close()) + + fastify.inject({ + url: '/error', + method: 'GET' + }, (err) => { + t.assert.ifError(err) + done() + }) +}) diff --git a/services/slides/node_modules/fastify/test/stream.1.test.js b/services/slides/node_modules/fastify/test/stream.1.test.js new file mode 100644 index 0000000000000000000000000000000000000000..8f3902e6a66e57748fa1fdc0fd3be9dc8357863f --- /dev/null +++ b/services/slides/node_modules/fastify/test/stream.1.test.js @@ -0,0 +1,94 @@ +'use strict' + +const { test } = require('node:test') +const fs = require('node:fs') +const Fastify = require('../fastify') + +test('should respond with a stream', async t => { + t.plan(4) + const fastify = Fastify() + + fastify.get('/', function (req, reply) { + const stream = fs.createReadStream(__filename, 'utf8') + reply.code(200).send(stream) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) + + const response = await fetch(fastifyServer) + t.assert.ok(response.ok) + t.assert.strictEqual(response.headers.get('content-type'), null) + t.assert.strictEqual(response.status, 200) + + const data = await response.text() + const expected = await fs.promises.readFile(__filename, 'utf8') + t.assert.strictEqual(expected.toString(), data.toString()) +}) + +test('should respond with a stream (error)', async t => { + t.plan(2) + const fastify = Fastify() + + fastify.get('/error', function (req, reply) { + const stream = fs.createReadStream('not-existing-file', 'utf8') + reply.code(200).send(stream) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const response = await fetch(`${fastifyServer}/error`) + t.assert.ok(!response.ok) + t.assert.strictEqual(response.status, 500) +}) + +test('should trigger the onSend hook', async (t) => { + t.plan(3) + const fastify = Fastify() + + fastify.get('/', (req, reply) => { + reply.send(fs.createReadStream(__filename, 'utf8')) + }) + + fastify.addHook('onSend', (req, reply, payload, done) => { + t.assert.ok(payload._readableState) + reply.header('Content-Type', 'application/javascript') + done() + }) + + const res = await fastify.inject({ + url: '/' + }) + t.assert.strictEqual(res.headers['content-type'], 'application/javascript') + t.assert.strictEqual(res.payload, fs.readFileSync(__filename, 'utf8')) + return fastify.close() +}) + +test('should trigger the onSend hook only twice if pumping the stream fails, first with the stream, second with the serialized error', async t => { + t.plan(4) + const fastify = Fastify() + + fastify.get('/', (req, reply) => { + reply.send(fs.createReadStream('not-existing-file', 'utf8')) + }) + + let counter = 0 + fastify.addHook('onSend', (req, reply, payload, done) => { + if (counter === 0) { + t.assert.ok(payload._readableState) + } else if (counter === 1) { + const error = JSON.parse(payload) + t.assert.strictEqual(error.statusCode, 500) + } + counter++ + done() + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) + + const response = await fetch(fastifyServer) + t.assert.ok(!response.ok) + t.assert.strictEqual(response.status, 500) +}) diff --git a/services/slides/node_modules/fastify/test/stream.2.test.js b/services/slides/node_modules/fastify/test/stream.2.test.js new file mode 100644 index 0000000000000000000000000000000000000000..7132e78b28fee9491ee4e9b3f648ff09131c4740 --- /dev/null +++ b/services/slides/node_modules/fastify/test/stream.2.test.js @@ -0,0 +1,129 @@ +'use strict' + +const { test } = require('node:test') +const proxyquire = require('proxyquire') +const fs = require('node:fs') +const resolve = require('node:path').resolve +const zlib = require('node:zlib') +const pipeline = require('node:stream').pipeline +const Fastify = require('..') +const { waitForCb } = require('./toolkit') + +test('onSend hook stream', t => { + t.plan(4) + const fastify = Fastify() + + fastify.get('/', function (req, reply) { + reply.send({ hello: 'world' }) + }) + + const { stepIn, patience } = waitForCb({ steps: 2 }) + + fastify.addHook('onSend', (req, reply, payload, done) => { + const gzStream = zlib.createGzip() + + reply.header('Content-Encoding', 'gzip') + pipeline( + fs.createReadStream(resolve(__filename), 'utf8'), + gzStream, + (err) => { + t.assert.ifError(err) + stepIn() + } + ) + done(null, gzStream) + }) + + fastify.inject({ + url: '/', + method: 'GET' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.headers['content-encoding'], 'gzip') + const file = fs.readFileSync(resolve(__filename), 'utf8') + const payload = zlib.gunzipSync(res.rawPayload) + t.assert.strictEqual(payload.toString('utf-8'), file) + fastify.close() + stepIn() + }) + + return patience +}) + +test('onSend hook stream should work even if payload is not a proper stream', (t, testDone) => { + t.plan(1) + + const reply = proxyquire('../lib/reply', { + 'node:stream': { + finished: (...args) => { + if (args.length === 2) { args[1](new Error('test-error')) } + } + } + }) + const Fastify = proxyquire('..', { + './lib/reply.js': reply + }) + const spyLogger = { + fatal: () => { }, + error: () => { }, + warn: (message) => { + t.assert.strictEqual(message, 'stream payload does not end properly') + fastify.close() + testDone() + }, + info: () => { }, + debug: () => { }, + trace: () => { }, + child: () => { return spyLogger } + } + + const fastify = Fastify({ loggerInstance: spyLogger }) + fastify.get('/', function (req, reply) { + reply.send({ hello: 'world' }) + }) + fastify.addHook('onSend', (req, reply, payload, done) => { + const fakeStream = { pipe: () => { } } + done(null, fakeStream) + }) + + fastify.inject({ + url: '/', + method: 'GET' + }) +}) + +test('onSend hook stream should work on payload with "close" ending function', (t, testDone) => { + t.plan(1) + + const reply = proxyquire('../lib/reply', { + 'node:stream': { + finished: (...args) => { + if (args.length === 2) { args[1](new Error('test-error')) } + } + } + }) + const Fastify = proxyquire('..', { + './lib/reply.js': reply + }) + + const fastify = Fastify({ logger: false }) + fastify.get('/', function (req, reply) { + reply.send({ hello: 'world' }) + }) + fastify.addHook('onSend', (req, reply, payload, done) => { + const fakeStream = { + pipe: () => { }, + close: (cb) => { + cb() + t.assert.ok('close callback called') + testDone() + } + } + done(null, fakeStream) + }) + + fastify.inject({ + url: '/', + method: 'GET' + }) +}) diff --git a/services/slides/node_modules/fastify/test/stream.3.test.js b/services/slides/node_modules/fastify/test/stream.3.test.js new file mode 100644 index 0000000000000000000000000000000000000000..5eb0148148bc1797a8d7b38c6bd82778e592c60d --- /dev/null +++ b/services/slides/node_modules/fastify/test/stream.3.test.js @@ -0,0 +1,198 @@ +'use strict' + +const { test } = require('node:test') +const split = require('split2') +const Fastify = require('..') + +test('Destroying streams prematurely', (t, testDone) => { + t.plan(6) + + let fastify = null + const logStream = split(JSON.parse) + try { + fastify = Fastify({ + logger: { + stream: logStream, + level: 'info' + } + }) + } catch (e) { + t.assert.fail() + } + const stream = require('node:stream') + const http = require('node:http') + + // Test that "premature close" errors are logged with level warn + logStream.on('data', line => { + if (line.res) { + t.assert.strictEqual(line.msg, 'stream closed prematurely') + t.assert.strictEqual(line.level, 30) + testDone() + } + }) + + fastify.get('/', function (request, reply) { + t.assert.ok('Received request') + + let sent = false + const reallyLongStream = new stream.Readable({ + read: function () { + if (!sent) { + this.push(Buffer.from('hello\n')) + } + sent = true + } + }) + + reply.send(reallyLongStream) + }) + + fastify.listen({ port: 0 }, err => { + t.assert.ifError(err) + t.after(() => fastify.close()) + + const port = fastify.server.address().port + + http.get(`http://localhost:${port}`, function (response) { + t.assert.strictEqual(response.statusCode, 200) + response.on('readable', function () { + response.destroy() + }) + + // Node bug? Node never emits 'close' here. + response.on('aborted', function () { + t.assert.ok('Response closed') + }) + }) + }) +}) + +test('Destroying streams prematurely should call close method', (t, testDone) => { + t.plan(7) + + let fastify = null + const logStream = split(JSON.parse) + try { + fastify = Fastify({ + logger: { + stream: logStream, + level: 'info' + } + }) + } catch (e) { + t.assert.fail() + } + const stream = require('node:stream') + const http = require('node:http') + + // Test that "premature close" errors are logged with level warn + logStream.on('data', line => { + if (line.res) { + t.assert.strictEqual(line.msg, 'stream closed prematurely') + t.assert.strictEqual(line.level, 30) + } + }) + + fastify.get('/', function (request, reply) { + t.assert.ok('Received request') + + let sent = false + const reallyLongStream = new stream.Readable({ + read: function () { + if (!sent) { + this.push(Buffer.from('hello\n')) + } + sent = true + } + }) + reallyLongStream.destroy = undefined + reallyLongStream.close = () => { + t.assert.ok('called') + testDone() + } + reply.send(reallyLongStream) + }) + + fastify.listen({ port: 0 }, err => { + t.assert.ifError(err) + t.after(() => { fastify.close() }) + + const port = fastify.server.address().port + + http.get(`http://localhost:${port}`, function (response) { + t.assert.strictEqual(response.statusCode, 200) + response.on('readable', function () { + response.destroy() + }) + // Node bug? Node never emits 'close' here. + response.on('aborted', function () { + t.assert.ok('Response closed') + }) + }) + }) +}) + +test('Destroying streams prematurely should call close method when destroy is not a function', (t, testDone) => { + t.plan(7) + + let fastify = null + const logStream = split(JSON.parse) + try { + fastify = Fastify({ + logger: { + stream: logStream, + level: 'info' + } + }) + } catch (e) { + t.assert.fail() + } + const stream = require('node:stream') + const http = require('node:http') + + // Test that "premature close" errors are logged with level warn + logStream.on('data', line => { + if (line.res) { + t.assert.strictEqual(line.msg, 'stream closed prematurely') + t.assert.strictEqual(line.level, 30) + } + }) + + fastify.get('/', function (request, reply) { + t.assert.ok('Received request') + + let sent = false + const reallyLongStream = new stream.Readable({ + read: function () { + if (!sent) { + this.push(Buffer.from('hello\n')) + } + sent = true + } + }) + reallyLongStream.destroy = true + reallyLongStream.close = () => { + t.assert.ok('called') + testDone() + } + reply.send(reallyLongStream) + }) + + fastify.listen({ port: 0 }, err => { + t.assert.ifError(err) + t.after(() => { fastify.close() }) + + const port = fastify.server.address().port + + http.get(`http://localhost:${port}`, function (response) { + t.assert.strictEqual(response.statusCode, 200) + response.on('readable', function () { + response.destroy() + }) + // Node bug? Node never emits 'close' here. + response.on('aborted', function () { + t.assert.ok('Response closed') + }) + }) + }) +}) diff --git a/services/slides/node_modules/fastify/test/stream.4.test.js b/services/slides/node_modules/fastify/test/stream.4.test.js new file mode 100644 index 0000000000000000000000000000000000000000..9ae386db9490c2bcdb8c508126a7b9cf9e9a9697 --- /dev/null +++ b/services/slides/node_modules/fastify/test/stream.4.test.js @@ -0,0 +1,176 @@ +'use strict' + +const { test } = require('node:test') +const errors = require('http-errors') +const JSONStream = require('JSONStream') +const Readable = require('node:stream').Readable +const split = require('split2') +const Fastify = require('..') +const { kDisableRequestLogging } = require('../lib/symbols.js') + +test('Destroying streams prematurely should call abort method', (t, testDone) => { + t.plan(7) + + let fastify = null + const logStream = split(JSON.parse) + try { + fastify = Fastify({ + logger: { + stream: logStream, + level: 'info' + } + }) + } catch (e) { + t.assert.fail() + } + const stream = require('node:stream') + const http = require('node:http') + + // Test that "premature close" errors are logged with level warn + logStream.on('data', line => { + if (line.res) { + t.assert.strictEqual(line.msg, 'stream closed prematurely') + t.assert.strictEqual(line.level, 30) + testDone() + } + }) + + fastify.get('/', function (request, reply) { + t.assert.ok('Received request') + + let sent = false + const reallyLongStream = new stream.Readable({ + read: function () { + if (!sent) { + this.push(Buffer.from('hello\n')) + } + sent = true + } + }) + reallyLongStream.destroy = undefined + reallyLongStream.close = undefined + reallyLongStream.abort = () => t.assert.ok('called') + reply.send(reallyLongStream) + }) + + fastify.listen({ port: 0 }, err => { + t.assert.ifError(err) + t.after(() => { fastify.close() }) + + const port = fastify.server.address().port + + http.get(`http://localhost:${port}`, function (response) { + t.assert.strictEqual(response.statusCode, 200) + response.on('readable', function () { + response.destroy() + }) + // Node bug? Node never emits 'close' here. + response.on('aborted', function () { + t.assert.ok('Response closed') + }) + }) + }) +}) + +test('Destroying streams prematurely, log is disabled', (t, testDone) => { + t.plan(4) + + let fastify = null + try { + fastify = Fastify({ + logger: false + }) + } catch (e) { + t.assert.fail() + } + const stream = require('node:stream') + const http = require('node:http') + + fastify.get('/', function (request, reply) { + reply.log[kDisableRequestLogging] = true + + let sent = false + const reallyLongStream = new stream.Readable({ + read: function () { + if (!sent) { + this.push(Buffer.from('hello\n')) + } + sent = true + } + }) + reallyLongStream.destroy = true + reallyLongStream.close = () => { + t.assert.ok('called') + testDone() + } + reply.send(reallyLongStream) + }) + + fastify.listen({ port: 0 }, err => { + t.assert.ifError(err) + t.after(() => { fastify.close() }) + + const port = fastify.server.address().port + + http.get(`http://localhost:${port}`, function (response) { + t.assert.strictEqual(response.statusCode, 200) + response.on('readable', function () { + response.destroy() + }) + // Node bug? Node never emits 'close' here. + response.on('aborted', function () { + t.assert.ok('Response closed') + }) + }) + }) +}) + +test('should respond with a stream1', async (t) => { + t.plan(4) + const fastify = Fastify() + + fastify.get('/', function (req, reply) { + const stream = JSONStream.stringify() + reply.code(200).type('application/json').send(stream) + stream.write({ hello: 'world' }) + stream.end({ a: 42 }) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const response = await fetch(fastifyServer) + t.assert.ok(response.ok) + t.assert.strictEqual(response.headers.get('content-type'), 'application/json') + t.assert.strictEqual(response.status, 200) + const body = await response.text() + t.assert.deepStrictEqual(JSON.parse(body), [{ hello: 'world' }, { a: 42 }]) +}) + +test('return a 404 if the stream emits a 404 error', async (t) => { + t.plan(4) + + const fastify = Fastify() + + fastify.get('/', function (request, reply) { + t.assert.ok('Received request') + + const reallyLongStream = new Readable({ + read: function () { + setImmediate(() => { + this.emit('error', new errors.NotFound()) + }) + } + }) + + reply.send(reallyLongStream) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const response = await fetch(fastifyServer) + t.assert.ok(!response.ok) + t.assert.strictEqual(response.headers.get('content-type'), 'application/json; charset=utf-8') + t.assert.strictEqual(response.status, 404) +}) diff --git a/services/slides/node_modules/fastify/test/stream.5.test.js b/services/slides/node_modules/fastify/test/stream.5.test.js new file mode 100644 index 0000000000000000000000000000000000000000..4580462d7ed9505e2512e47e24f3ee71e99a4440 --- /dev/null +++ b/services/slides/node_modules/fastify/test/stream.5.test.js @@ -0,0 +1,188 @@ +'use strict' + +const { test } = require('node:test') +const proxyquire = require('proxyquire') +const fs = require('node:fs') +const Readable = require('node:stream').Readable +const Fastify = require('..') + +test('should destroy stream when response is ended', async (t) => { + t.plan(3) + const stream = require('node:stream') + const fastify = Fastify() + + fastify.get('/error', function (req, reply) { + const reallyLongStream = new stream.Readable({ + read: function () { }, + destroy: function (err, callback) { + t.assert.ok('called') + callback(err) + } + }) + reply.code(200).send(reallyLongStream) + reply.raw.end(Buffer.from('hello\n')) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => fastify.close()) + + const response = await fetch(`${fastifyServer}/error`) + t.assert.ok(response.ok) + t.assert.strictEqual(response.status, 200) +}) + +test('should mark reply as sent before pumping the payload stream into response for async route handler', async (t) => { + t.plan(2) + t.after(() => fastify.close()) + + const handleRequest = proxyquire('../lib/handle-request', { + './wrap-thenable': (thenable, reply) => { + thenable.then(function (payload) { + t.assert.strictEqual(reply.sent, true) + }) + } + }) + + const route = proxyquire('../lib/route', { + './handle-request': handleRequest + }) + + const Fastify = proxyquire('..', { + './lib/route': route + }) + + const fastify = Fastify() + + fastify.get('/', async function (req, reply) { + const stream = fs.createReadStream(__filename, 'utf8') + return reply.code(200).send(stream) + }) + + const res = await fastify.inject({ + url: '/', + method: 'GET' + }) + t.assert.strictEqual(res.payload, fs.readFileSync(__filename, 'utf8')) +}) + +test('reply.send handles aborted requests', (t, done) => { + t.plan(2) + + const spyLogger = { + level: 'error', + fatal: () => { }, + error: () => { + t.assert.fail('should not log an error') + }, + warn: () => { }, + info: () => { }, + debug: () => { }, + trace: () => { }, + child: () => { return spyLogger } + } + const fastify = Fastify({ + loggerInstance: spyLogger + }) + + fastify.get('/', (req, reply) => { + setTimeout(() => { + const stream = new Readable({ + read: function () { + this.push(null) + } + }) + reply.send(stream) + }, 6) + }) + + fastify.listen({ port: 0 }, err => { + t.assert.ifError(err) + t.after(() => fastify.close()) + + const port = fastify.server.address().port + const http = require('node:http') + const req = http.get(`http://localhost:${port}`) + .on('error', (err) => { + t.assert.strictEqual(err.code, 'ECONNRESET') + done() + }) + + setTimeout(() => { + req.destroy() + }, 1) + }) +}) + +test('request terminated should not crash fastify', (t, done) => { + t.plan(10) + + const spyLogger = { + level: 'error', + fatal: () => { }, + error: () => { + t.assert.fail('should not log an error') + }, + warn: () => { }, + info: () => { }, + debug: () => { }, + trace: () => { }, + child: () => { return spyLogger } + } + const fastify = Fastify({ + loggerInstance: spyLogger + }) + + fastify.get('/', async (req, reply) => { + const stream = new Readable() + stream._read = () => { } + reply.header('content-type', 'text/html; charset=utf-8') + reply.header('transfer-encoding', 'chunked') + stream.push('

HTML

') + + reply.send(stream) + + await new Promise((resolve) => { setTimeout(resolve, 100).unref() }) + + stream.push('

should display on second stream

') + stream.push(null) + return reply + }) + + fastify.listen({ port: 0 }, err => { + t.assert.ifError(err) + t.after(() => fastify.close()) + + const port = fastify.server.address().port + const http = require('node:http') + const req = http.get(`http://localhost:${port}`, function (res) { + const { statusCode, headers } = res + t.assert.strictEqual(statusCode, 200) + t.assert.strictEqual(headers['content-type'], 'text/html; charset=utf-8') + t.assert.strictEqual(headers['transfer-encoding'], 'chunked') + res.on('data', function (chunk) { + t.assert.strictEqual(chunk.toString(), '

HTML

') + }) + + setTimeout(() => { + req.destroy() + + // the server is not crash, we can connect it + http.get(`http://localhost:${port}`, function (res) { + const { statusCode, headers } = res + t.assert.strictEqual(statusCode, 200) + t.assert.strictEqual(headers['content-type'], 'text/html; charset=utf-8') + t.assert.strictEqual(headers['transfer-encoding'], 'chunked') + let payload = '' + res.on('data', function (chunk) { + payload += chunk.toString() + }) + res.on('end', function () { + t.assert.strictEqual(payload, '

HTML

should display on second stream

') + t.assert.ok('should end properly') + done() + }) + }) + }, 1) + }) + }) +}) diff --git a/services/slides/node_modules/fastify/test/sync-routes.test.js b/services/slides/node_modules/fastify/test/sync-routes.test.js new file mode 100644 index 0000000000000000000000000000000000000000..9f47d81c53d90cd0b63acdfd9842c7c29a8e2e58 --- /dev/null +++ b/services/slides/node_modules/fastify/test/sync-routes.test.js @@ -0,0 +1,32 @@ +'use strict' + +const { test } = require('node:test') +const Fastify = require('..') + +test('sync route', async t => { + const fastify = Fastify() + t.after(() => fastify.close()) + fastify.get('/', () => 'hello world') + const res = await fastify.inject('/') + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.body, 'hello world') +}) + +test('sync route return null', async t => { + const fastify = Fastify() + t.after(() => fastify.close()) + fastify.get('/', () => null) + const res = await fastify.inject('/') + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.body, 'null') +}) + +test('sync route, error', async t => { + const fastify = Fastify() + t.after(() => fastify.close()) + fastify.get('/', () => { + throw new Error('kaboom') + }) + const res = await fastify.inject('/') + t.assert.strictEqual(res.statusCode, 500) +}) diff --git a/services/slides/node_modules/fastify/test/throw.test.js b/services/slides/node_modules/fastify/test/throw.test.js new file mode 100644 index 0000000000000000000000000000000000000000..747512e30b3d664e5ae94bf5ccaf6592c5aea5ce --- /dev/null +++ b/services/slides/node_modules/fastify/test/throw.test.js @@ -0,0 +1,359 @@ +'use strict' + +const { test } = require('node:test') +const Fastify = require('..') + +test('Fastify should throw on wrong options', (t) => { + t.plan(2) + try { + Fastify('lol') + t.assert.fail() + } catch (e) { + t.assert.strictEqual(e.message, 'Options must be an object') + t.assert.ok(true) + } +}) + +test('Fastify should throw on multiple assignment to the same route', (t) => { + t.plan(1) + const fastify = Fastify() + + fastify.get('/', () => {}) + + try { + fastify.get('/', () => {}) + t.assert.fail('Should throw fastify duplicated route declaration') + } catch (error) { + t.assert.strictEqual(error.code, 'FST_ERR_DUPLICATED_ROUTE') + } +}) + +test('Fastify should throw for an invalid schema, printing the error route - headers', async (t) => { + t.plan(1) + + const badSchema = { + type: 'object', + properties: { + bad: { + type: 'bad-type' + } + } + } + const fastify = Fastify() + fastify.get('/', { schema: { headers: badSchema } }, () => {}) + fastify.get('/not-loaded', { schema: { headers: badSchema } }, () => {}) + + await t.assert.rejects(fastify.ready(), { + code: 'FST_ERR_SCH_VALIDATION_BUILD', + message: /Failed building the validation schema for GET: \// + }) +}) + +test('Fastify should throw for an invalid schema, printing the error route - body', async (t) => { + t.plan(1) + const badSchema = { + type: 'object', + properties: { + bad: { + type: 'bad-type' + } + } + } + + const fastify = Fastify() + fastify.register((instance, opts, done) => { + instance.post('/form', { schema: { body: badSchema } }, () => {}) + done() + }, { prefix: 'hello' }) + + await t.assert.rejects(fastify.ready(), { + code: 'FST_ERR_SCH_VALIDATION_BUILD', + message: /Failed building the validation schema for POST: \/hello\/form/ + }) +}) + +test('Should throw on unsupported method', async (t) => { + t.plan(1) + const fastify = Fastify() + try { + fastify.route({ + method: 'TROLL', + url: '/', + schema: {}, + handler: function (req, reply) {} + }) + t.assert.fail() + } catch (e) { + t.assert.ok(true) + } +}) + +test('Should throw on missing handler', (t) => { + t.plan(1) + const fastify = Fastify() + try { + fastify.route({ + method: 'GET', + url: '/' + }) + t.assert.fail() + } catch (e) { + t.assert.ok(true) + } +}) + +test('Should throw if one method is unsupported', async (t) => { + t.plan(1) + const fastify = Fastify() + try { + fastify.route({ + method: ['GET', 'TROLL'], + url: '/', + schema: {}, + handler: function (req, reply) {} + }) + t.assert.fail() + } catch (e) { + t.assert.ok(true) + } +}) + +test('Should throw on duplicate content type parser', async (t) => { + t.plan(1) + const fastify = Fastify() + function customParser (req, payload, done) { done(null, '') } + + fastify.addContentTypeParser('application/qq', customParser) + try { + fastify.addContentTypeParser('application/qq', customParser) + t.assert.fail() + } catch (e) { + t.assert.ok(true) + } +}) + +test('Should throw on duplicate decorator', async (t) => { + t.plan(1) + + const fastify = Fastify() + const fooObj = {} + + fastify.decorate('foo', fooObj) + try { + fastify.decorate('foo', fooObj) + t.assert.fail() + } catch (e) { + t.assert.ok(true) + } +}) + +test('Should not throw on duplicate decorator encapsulation', async (t) => { + t.plan(1) + const fastify = Fastify() + const foo2Obj = {} + + fastify.decorate('foo2', foo2Obj) + + fastify.register(function (fastify, opts, done) { + t.assert.doesNotThrow(() => { + fastify.decorate('foo2', foo2Obj) + }) + done() + }) + + await fastify.ready() +}) + +test('Should throw on duplicate request decorator', async (t) => { + t.plan(2) + + const fastify = Fastify() + + fastify.decorateRequest('foo', null) + try { + fastify.decorateRequest('foo', null) + t.assert.fail() + } catch (e) { + t.assert.strictEqual(e.code, 'FST_ERR_DEC_ALREADY_PRESENT') + t.assert.strictEqual(e.message, 'The decorator \'foo\' has already been added!') + } +}) + +test('Should throw if request decorator dependencies are not met', async (t) => { + t.plan(2) + + const fastify = Fastify() + + try { + fastify.decorateRequest('bar', null, ['world']) + t.assert.fail() + } catch (e) { + t.assert.strictEqual(e.code, 'FST_ERR_DEC_MISSING_DEPENDENCY') + t.assert.strictEqual(e.message, 'The decorator is missing dependency \'world\'.') + } +}) + +test('Should throw on duplicate reply decorator', async (t) => { + t.plan(1) + + const fastify = Fastify() + + fastify.decorateReply('foo', null) + try { + fastify.decorateReply('foo', null) + t.assert.fail() + } catch (e) { + t.assert.ok(/has already been added/.test(e.message)) + } +}) + +test('Should throw if reply decorator dependencies are not met', async (t) => { + t.plan(1) + + const fastify = Fastify() + + try { + fastify.decorateReply('bar', null, ['world']) + t.assert.fail() + } catch (e) { + t.assert.ok(/missing dependency/.test(e.message)) + } +}) + +test('Should throw if handler as the third parameter to the shortcut method is missing and the second parameter is not a function and also not an object', async (t) => { + t.plan(5) + + const fastify = Fastify() + + try { + fastify.get('/foo/1', '') + t.assert.fail() + } catch (e) { + t.assert.ok(true) + } + + try { + fastify.get('/foo/2', 1) + t.assert.fail() + } catch (e) { + t.assert.ok(true) + } + + try { + fastify.get('/foo/3', []) + t.assert.fail() + } catch (e) { + t.assert.ok(true) + } + + try { + fastify.get('/foo/4', undefined) + t.assert.fail() + } catch (e) { + t.assert.ok(true) + } + + try { + fastify.get('/foo/5', null) + t.assert.fail() + } catch (e) { + t.assert.ok(true) + } +}) + +test('Should throw if handler as the third parameter to the shortcut method is missing and the second parameter is not a function and also not an object', async (t) => { + t.plan(5) + + const fastify = Fastify() + + try { + fastify.get('/foo/1', '') + t.assert.fail() + } catch (e) { + t.assert.ok(true) + } + + try { + fastify.get('/foo/2', 1) + t.assert.fail() + } catch (e) { + t.assert.ok(true) + } + + try { + fastify.get('/foo/3', []) + t.assert.fail() + } catch (e) { + t.assert.ok(true) + } + + try { + fastify.get('/foo/4', undefined) + t.assert.fail() + } catch (e) { + t.assert.ok(true) + } + + try { + fastify.get('/foo/5', null) + t.assert.fail() + } catch (e) { + t.assert.ok(true) + } +}) + +test('Should throw if there is handler function as the third parameter to the shortcut method and options as the second parameter is not an object', async (t) => { + t.plan(5) + + const fastify = Fastify() + + try { + fastify.get('/foo/1', '', (req, res) => {}) + t.assert.fail() + } catch (e) { + t.assert.ok(true) + } + + try { + fastify.get('/foo/2', 1, (req, res) => {}) + t.assert.fail() + } catch (e) { + t.assert.ok(true) + } + + try { + fastify.get('/foo/3', [], (req, res) => {}) + t.assert.fail() + } catch (e) { + t.assert.ok(true) + } + + try { + fastify.get('/foo/4', undefined, (req, res) => {}) + t.assert.fail() + } catch (e) { + t.assert.ok(true) + } + + try { + fastify.get('/foo/5', null, (req, res) => {}) + t.assert.fail() + } catch (e) { + t.assert.ok(true) + } +}) + +test('Should throw if found duplicate handler as the third parameter to the shortcut method and in options', async (t) => { + t.plan(1) + + const fastify = Fastify() + + try { + fastify.get('/foo/abc', { + handler: (req, res) => {} + }, (req, res) => {}) + t.assert.fail() + } catch (e) { + t.assert.ok(true) + } +}) diff --git a/services/slides/node_modules/fastify/test/toolkit.js b/services/slides/node_modules/fastify/test/toolkit.js new file mode 100644 index 0000000000000000000000000000000000000000..96ff21e0b0da4e0291408877f83d02d0adfc1d5c --- /dev/null +++ b/services/slides/node_modules/fastify/test/toolkit.js @@ -0,0 +1,63 @@ +'use strict' + +exports.waitForCb = function (options) { + let count = null + let done = false + let iResolve + let iReject + + function stepIn () { + if (done) { + iReject(new Error('Unexpected done call')) + return + } + + if (--count) { + return + } + + done = true + iResolve() + } + + const patience = new Promise((resolve, reject) => { + iResolve = resolve + iReject = reject + }) + + count = options.steps || 1 + done = false + + return { stepIn, patience } +} + +exports.partialDeepStrictEqual = function partialDeepStrictEqual (actual, expected) { + if (typeof expected !== 'object' || expected === null) { + return actual === expected + } + + if (typeof actual !== 'object' || actual === null) { + return false + } + + if (Array.isArray(expected)) { + if (!Array.isArray(actual)) return false + if (expected.length > actual.length) return false + + for (let i = 0; i < expected.length; i++) { + if (!partialDeepStrictEqual(actual[i], expected[i])) { + return false + } + } + return true + } + + for (const key of Object.keys(expected)) { + if (!(key in actual)) return false + if (!partialDeepStrictEqual(actual[key], expected[key])) { + return false + } + } + + return true +} diff --git a/services/slides/node_modules/fastify/test/trust-proxy.test.js b/services/slides/node_modules/fastify/test/trust-proxy.test.js new file mode 100644 index 0000000000000000000000000000000000000000..84f5def6722fd45f7a3342e9bb7342d917f6daa1 --- /dev/null +++ b/services/slides/node_modules/fastify/test/trust-proxy.test.js @@ -0,0 +1,274 @@ +'use strict' + +const { test, before } = require('node:test') +const fastify = require('..') +const helper = require('./helper') +const Request = require('../lib/request') +const buildRequest = Request.buildRequest + +const fetchForwardedRequest = async (fastifyServer, forHeader, path, protoHeader) => { + const headers = { + 'X-Forwarded-For': forHeader, + 'X-Forwarded-Host': 'fastify.test' + } + if (protoHeader) { + headers['X-Forwarded-Proto'] = protoHeader + } + + return fetch(fastifyServer + path, { + headers + }) +} + +const testRequestValues = (t, req, options) => { + if (options.ip) { + t.assert.ok(req.ip, 'ip is defined') + t.assert.strictEqual(req.ip, options.ip, 'gets ip from x-forwarded-for') + } + if (options.host) { + t.assert.ok(req.host, 'host is defined') + t.assert.strictEqual(req.host, options.host, 'gets host from x-forwarded-host') + t.assert.ok(req.hostname) + t.assert.strictEqual(req.hostname, options.host, 'gets hostname from x-forwarded-host') + } + if (options.ips) { + t.assert.deepStrictEqual(req.ips, options.ips, 'gets ips from x-forwarded-for') + } + if (options.protocol) { + t.assert.ok(req.protocol, 'protocol is defined') + t.assert.strictEqual(req.protocol, options.protocol, 'gets protocol from x-forwarded-proto') + } + if (options.port) { + t.assert.ok(req.port, 'port is defined') + t.assert.strictEqual(req.port, options.port, 'port is taken from x-forwarded-for or host') + } +} + +let localhost +before(async function () { + [localhost] = await helper.getLoopbackHost() +}) + +test('trust proxy, not add properties to node req', async t => { + t.plan(13) + const app = fastify({ + trustProxy: true + }) + t.after(() => app.close()) + + app.get('/trustproxy', function (req, reply) { + testRequestValues(t, req, { ip: '1.1.1.1', host: 'fastify.test', port: app.server.address().port }) + reply.code(200).send({ ip: req.ip, host: req.host }) + }) + + app.get('/trustproxychain', function (req, reply) { + testRequestValues(t, req, { ip: '2.2.2.2', ips: [localhost, '1.1.1.1', '2.2.2.2'], port: app.server.address().port }) + reply.code(200).send({ ip: req.ip, host: req.host }) + }) + + const fastifyServer = await app.listen({ port: 0 }) + + await fetchForwardedRequest(fastifyServer, '1.1.1.1', '/trustproxy', undefined) + await fetchForwardedRequest(fastifyServer, '2.2.2.2, 1.1.1.1', '/trustproxychain', undefined) +}) + +test('trust proxy chain', async t => { + t.plan(8) + const app = fastify({ + trustProxy: [localhost, '192.168.1.1'] + }) + t.after(() => app.close()) + + app.get('/trustproxychain', function (req, reply) { + testRequestValues(t, req, { ip: '1.1.1.1', host: 'fastify.test', port: app.server.address().port }) + reply.code(200).send({ ip: req.ip, host: req.host }) + }) + + const fastifyServer = await app.listen({ port: 0 }) + await fetchForwardedRequest(fastifyServer, '192.168.1.1, 1.1.1.1', '/trustproxychain', undefined) +}) + +test('trust proxy function', async t => { + t.plan(8) + const app = fastify({ + trustProxy: (address) => address === localhost + }) + t.after(() => app.close()) + + app.get('/trustproxyfunc', function (req, reply) { + testRequestValues(t, req, { ip: '1.1.1.1', host: 'fastify.test', port: app.server.address().port }) + reply.code(200).send({ ip: req.ip, host: req.host }) + }) + + const fastifyServer = await app.listen({ port: 0 }) + await fetchForwardedRequest(fastifyServer, '1.1.1.1', '/trustproxyfunc', undefined) +}) + +test('trust proxy number', async t => { + t.plan(9) + const app = fastify({ + trustProxy: 1 + }) + t.after(() => app.close()) + + app.get('/trustproxynumber', function (req, reply) { + testRequestValues(t, req, { ip: '1.1.1.1', ips: [localhost, '1.1.1.1'], host: 'fastify.test', port: app.server.address().port }) + reply.code(200).send({ ip: req.ip, host: req.host }) + }) + + const fastifyServer = await app.listen({ port: 0 }) + await fetchForwardedRequest(fastifyServer, '2.2.2.2, 1.1.1.1', '/trustproxynumber', undefined) +}) + +test('trust proxy IP addresses', async t => { + t.plan(9) + const app = fastify({ + trustProxy: `${localhost}, 2.2.2.2` + }) + t.after(() => app.close()) + + app.get('/trustproxyipaddrs', function (req, reply) { + testRequestValues(t, req, { ip: '1.1.1.1', ips: [localhost, '1.1.1.1'], host: 'fastify.test', port: app.server.address().port }) + reply.code(200).send({ ip: req.ip, host: req.host }) + }) + + const fastifyServer = await app.listen({ port: 0 }) + await fetchForwardedRequest(fastifyServer, '3.3.3.3, 2.2.2.2, 1.1.1.1', '/trustproxyipaddrs', undefined) +}) + +test('trust proxy protocol', async t => { + t.plan(30) + const app = fastify({ + trustProxy: true + }) + t.after(() => app.close()) + + app.get('/trustproxyprotocol', function (req, reply) { + testRequestValues(t, req, { ip: '1.1.1.1', protocol: 'lorem', host: 'fastify.test', port: app.server.address().port }) + reply.code(200).send({ ip: req.ip, host: req.host }) + }) + app.get('/trustproxynoprotocol', function (req, reply) { + testRequestValues(t, req, { ip: '1.1.1.1', protocol: 'http', host: 'fastify.test', port: app.server.address().port }) + reply.code(200).send({ ip: req.ip, host: req.host }) + }) + app.get('/trustproxyprotocols', function (req, reply) { + testRequestValues(t, req, { ip: '1.1.1.1', protocol: 'dolor', host: 'fastify.test', port: app.server.address().port }) + reply.code(200).send({ ip: req.ip, host: req.host }) + }) + + const fastifyServer = await app.listen({ port: 0 }) + + await fetchForwardedRequest(fastifyServer, '1.1.1.1', '/trustproxyprotocol', 'lorem') + await fetchForwardedRequest(fastifyServer, '1.1.1.1', '/trustproxynoprotocol', undefined) + await fetchForwardedRequest(fastifyServer, '1.1.1.1', '/trustproxyprotocols', 'ipsum, dolor') +}) + +test('trust proxy ignores forwarded headers from untrusted connections', async t => { + t.plan(3) + + // Use a restrictive trust function that does NOT trust localhost + // (simulates a direct connection bypassing the proxy) + const app = fastify({ + trustProxy: '10.0.0.1' + }) + t.after(() => app.close()) + + app.get('/untrusted', function (req, reply) { + // protocol should fall back to socket state, not read x-forwarded-proto + t.assert.strictEqual(req.protocol, 'http', 'protocol ignores x-forwarded-proto from untrusted connection') + // host should fall back to raw Host header, not read x-forwarded-host + t.assert.notStrictEqual(req.host, 'evil.com', 'host ignores x-forwarded-host from untrusted connection') + // hostname should also not be spoofed + t.assert.notStrictEqual(req.hostname, 'evil.com', 'hostname ignores x-forwarded-host from untrusted connection') + reply.code(200).send({ protocol: req.protocol, host: req.host }) + }) + + const fastifyServer = await app.listen({ port: 0 }) + + // Attacker connects directly (from localhost, which is NOT in the trust list) + // and sends spoofed forwarded headers + await fetch(fastifyServer + '/untrusted', { + headers: { + 'X-Forwarded-For': '1.1.1.1', + 'X-Forwarded-Host': 'evil.com', + 'X-Forwarded-Proto': 'https' + } + }) +}) + +test('trust proxy reads forwarded headers from trusted connections', async t => { + t.plan(2) + + // Trust localhost (the actual connecting IP in tests) + const app = fastify({ + trustProxy: (address) => address === localhost + }) + t.after(() => app.close()) + + app.get('/trusted', function (req, reply) { + t.assert.strictEqual(req.protocol, 'https', 'protocol reads x-forwarded-proto from trusted connection') + t.assert.strictEqual(req.host, 'example.com', 'host reads x-forwarded-host from trusted connection') + reply.code(200).send({ protocol: req.protocol, host: req.host }) + }) + + const fastifyServer = await app.listen({ port: 0 }) + + await fetch(fastifyServer + '/trusted', { + headers: { + 'X-Forwarded-For': '1.1.1.1', + 'X-Forwarded-Host': 'example.com', + 'X-Forwarded-Proto': 'https' + } + }) +}) + +test('trust proxy with number and undefined socket remoteAddress', t => { + t.plan(3) + + // Test case for issue #6606: trustProxy: 1 with undefined/null socket.remoteAddress + // This simulates IISNode on Windows where socket.remoteAddress may be undefined + const headers = { + 'x-forwarded-for': '2.2.2.2, 1.1.1.1', + 'x-forwarded-host': 'fastify.test', + 'x-forwarded-proto': 'https' + } + // socket must exist but remoteAddress can be undefined + // This is what happens in IISNode with enableXFF="true" + const req = { + method: 'GET', + url: '/', + socket: { remoteAddress: undefined }, + headers + } + + const TpRequest = buildRequest(Request, 1) + const request = new TpRequest('id', 'params', req, 'query', 'log') + // Even with undefined socket.remoteAddress, req.ip should be populated from X-Forwarded-For + t.assert.ok(request.ip, 'ip is defined') + // With trustProxy: 1, we trust 1 hop from socket. Since socket.remoteAddress is undefined, + // the hop count check should skip it and we get 1.1.1.1 (the first trusted address from X-Forwarded-For) + t.assert.strictEqual(request.ip, '1.1.1.1', 'gets ip from x-forwarded-for') + // The host should also work correctly + t.assert.strictEqual(request.host, 'fastify.test', 'gets host from x-forwarded-host') +}) + +test('trust proxy with number and null socket remoteAddress', t => { + t.plan(2) + + // Test case for trustProxy: 1 with null socket.remoteAddress + const headers = { + 'x-forwarded-for': '2.2.2.2, 1.1.1.1', + 'x-forwarded-host': 'fastify.test' + } + const req = { + method: 'GET', + url: '/', + socket: { remoteAddress: null }, + headers + } + + const TpRequest = buildRequest(Request, 1) + const request = new TpRequest('id', 'params', req, 'query', 'log') + t.assert.ok(request.ip, 'ip is defined') + t.assert.strictEqual(request.ip, '1.1.1.1', 'gets ip from x-forwarded-for') +}) diff --git a/services/slides/node_modules/fastify/test/type-provider.test.js b/services/slides/node_modules/fastify/test/type-provider.test.js new file mode 100644 index 0000000000000000000000000000000000000000..80e238e7dfb4b01bd6e079f926ef9a629333b355 --- /dev/null +++ b/services/slides/node_modules/fastify/test/type-provider.test.js @@ -0,0 +1,22 @@ +'use strict' + +const { test } = require('node:test') +const Fastify = require('..') + +test('Should export withTypeProvider function', (t, done) => { + t.plan(1) + try { + Fastify().withTypeProvider() + t.assert.ok('pass') + done() + } catch (e) { + t.assert.fail(e) + } +}) + +test('Should return same instance', (t, done) => { + t.plan(1) + const fastify = Fastify() + t.assert.strictEqual(fastify, fastify.withTypeProvider()) + done() +}) diff --git a/services/slides/node_modules/fastify/test/types/content-type-parser.test-d.ts b/services/slides/node_modules/fastify/test/types/content-type-parser.test-d.ts new file mode 100644 index 0000000000000000000000000000000000000000..f75fc01ae79cf24adfd4751c01638075d81ba1d7 --- /dev/null +++ b/services/slides/node_modules/fastify/test/types/content-type-parser.test-d.ts @@ -0,0 +1,72 @@ +import fastify, { FastifyBodyParser } from '../../fastify' +import { expectError, expectType } from 'tsd' +import { IncomingMessage } from 'node:http' +import { FastifyRequest } from '../../types/request' + +expectType(fastify().addContentTypeParser('contentType', function (request, payload, done) { + expectType(request) + expectType(payload) + done(null) +})) + +// Body limit options + +expectType(fastify().addContentTypeParser('contentType', { bodyLimit: 99 }, function (request, payload, done) { + expectType(request) + expectType(payload) + done(null) +})) + +// Array for contentType + +expectType(fastify().addContentTypeParser(['contentType'], function (request, payload, done) { + expectType(request) + expectType(payload) + done(null) +})) + +// Body Parser - the generic after addContentTypeParser enforces the type of the `body` parameter as well as the value of the `parseAs` property + +expectType(fastify().addContentTypeParser('bodyContentType', { parseAs: 'string' }, function (request, body, done) { + expectType(request) + expectType(body) + done(null) +})) + +expectType(fastify().addContentTypeParser('bodyContentType', { parseAs: 'buffer' }, function (request, body, done) { + expectType(request) + expectType(body) + done(null) +})) + +expectType(fastify().addContentTypeParser('contentType', async function (request: FastifyRequest, payload: IncomingMessage) { + expectType(request) + expectType(payload) + return null +})) + +expectType(fastify().addContentTypeParser('bodyContentType', { parseAs: 'string' }, async function (request: FastifyRequest, body: string) { + expectType(request) + expectType(body) + return null +})) + +expectType(fastify().addContentTypeParser('bodyContentType', { parseAs: 'buffer' }, async function (request: FastifyRequest, body: Buffer) { + expectType(request) + expectType(body) + return null +})) + +expectType>(fastify().getDefaultJsonParser('error', 'ignore')) + +expectError(fastify().getDefaultJsonParser('error', 'skip')) + +expectError(fastify().getDefaultJsonParser('nothing', 'ignore')) + +expectType(fastify().removeAllContentTypeParsers()) +expectError(fastify().removeAllContentTypeParsers('contentType')) + +expectType(fastify().removeContentTypeParser('contentType')) +expectType(fastify().removeContentTypeParser(/contentType+.*/)) +expectType(fastify().removeContentTypeParser(['contentType', /contentType+.*/])) +expectError(fastify().removeContentTypeParser({})) diff --git a/services/slides/node_modules/fastify/test/types/decorate-request-reply.test-d.ts b/services/slides/node_modules/fastify/test/types/decorate-request-reply.test-d.ts new file mode 100644 index 0000000000000000000000000000000000000000..11abd44e39affb3657fc9407cb6029fd3af535b0 --- /dev/null +++ b/services/slides/node_modules/fastify/test/types/decorate-request-reply.test-d.ts @@ -0,0 +1,18 @@ +import fastify from '../../fastify' +import { expectType } from 'tsd' + +type TestType = void + +declare module '../../fastify' { + interface FastifyRequest { + testProp: TestType; + } + interface FastifyReply { + testProp: TestType; + } +} + +fastify().get('/', (req, res) => { + expectType(req.testProp) + expectType(res.testProp) +}) diff --git a/services/slides/node_modules/fastify/test/types/dummy-plugin.ts b/services/slides/node_modules/fastify/test/types/dummy-plugin.ts new file mode 100644 index 0000000000000000000000000000000000000000..e3c8210a7b301e039fb35d8fed39a5314ba3b92e --- /dev/null +++ b/services/slides/node_modules/fastify/test/types/dummy-plugin.ts @@ -0,0 +1,9 @@ +import { FastifyPluginAsync } from '../../fastify' + +export interface DummyPluginOptions { + foo?: number +} + +declare const DummyPlugin: FastifyPluginAsync + +export default DummyPlugin diff --git a/services/slides/node_modules/fastify/test/types/errors.test-d.ts b/services/slides/node_modules/fastify/test/types/errors.test-d.ts new file mode 100644 index 0000000000000000000000000000000000000000..c14dbdb6f458e163740b7d5bb82141451ad23e58 --- /dev/null +++ b/services/slides/node_modules/fastify/test/types/errors.test-d.ts @@ -0,0 +1,90 @@ +import { FastifyErrorConstructor } from '@fastify/error' +import { expectAssignable } from 'tsd' +import { errorCodes } from '../../fastify' + +expectAssignable(errorCodes.FST_ERR_NOT_FOUND) +expectAssignable(errorCodes.FST_ERR_OPTIONS_NOT_OBJ) +expectAssignable(errorCodes.FST_ERR_QSP_NOT_FN) +expectAssignable(errorCodes.FST_ERR_SCHEMA_CONTROLLER_BUCKET_OPT_NOT_FN) +expectAssignable(errorCodes.FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN) +expectAssignable(errorCodes.FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_OBJ) +expectAssignable(errorCodes.FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_ARR) +expectAssignable(errorCodes.FST_ERR_VALIDATION) +expectAssignable(errorCodes.FST_ERR_LISTEN_OPTIONS_INVALID) +expectAssignable(errorCodes.FST_ERR_ERROR_HANDLER_NOT_FN) +expectAssignable(errorCodes.FST_ERR_ERROR_HANDLER_ALREADY_SET) +expectAssignable(errorCodes.FST_ERR_CTP_ALREADY_PRESENT) +expectAssignable(errorCodes.FST_ERR_CTP_INVALID_TYPE) +expectAssignable(errorCodes.FST_ERR_CTP_EMPTY_TYPE) +expectAssignable(errorCodes.FST_ERR_CTP_INVALID_HANDLER) +expectAssignable(errorCodes.FST_ERR_CTP_INVALID_PARSE_TYPE) +expectAssignable(errorCodes.FST_ERR_CTP_BODY_TOO_LARGE) +expectAssignable(errorCodes.FST_ERR_CTP_INVALID_MEDIA_TYPE) +expectAssignable(errorCodes.FST_ERR_CTP_INVALID_CONTENT_LENGTH) +expectAssignable(errorCodes.FST_ERR_CTP_EMPTY_JSON_BODY) +expectAssignable(errorCodes.FST_ERR_CTP_INVALID_JSON_BODY) +expectAssignable(errorCodes.FST_ERR_CTP_INSTANCE_ALREADY_STARTED) +expectAssignable(errorCodes.FST_ERR_DEC_ALREADY_PRESENT) +expectAssignable(errorCodes.FST_ERR_DEC_DEPENDENCY_INVALID_TYPE) +expectAssignable(errorCodes.FST_ERR_DEC_MISSING_DEPENDENCY) +expectAssignable(errorCodes.FST_ERR_DEC_AFTER_START) +expectAssignable(errorCodes.FST_ERR_DEC_REFERENCE_TYPE) +expectAssignable(errorCodes.FST_ERR_DEC_UNDECLARED) +expectAssignable(errorCodes.FST_ERR_HOOK_INVALID_TYPE) +expectAssignable(errorCodes.FST_ERR_HOOK_INVALID_HANDLER) +expectAssignable(errorCodes.FST_ERR_HOOK_INVALID_ASYNC_HANDLER) +expectAssignable(errorCodes.FST_ERR_HOOK_NOT_SUPPORTED) +expectAssignable(errorCodes.FST_ERR_MISSING_MIDDLEWARE) +expectAssignable(errorCodes.FST_ERR_HOOK_TIMEOUT) +expectAssignable(errorCodes.FST_ERR_LOG_INVALID_DESTINATION) +expectAssignable(errorCodes.FST_ERR_LOG_INVALID_LOGGER) +expectAssignable(errorCodes.FST_ERR_LOG_INVALID_LOGGER_INSTANCE) +expectAssignable(errorCodes.FST_ERR_LOG_INVALID_LOGGER_CONFIG) +expectAssignable(errorCodes.FST_ERR_LOG_LOGGER_AND_LOGGER_INSTANCE_PROVIDED) +expectAssignable(errorCodes.FST_ERR_REP_INVALID_PAYLOAD_TYPE) +expectAssignable(errorCodes.FST_ERR_REP_RESPONSE_BODY_CONSUMED) +expectAssignable(errorCodes.FST_ERR_REP_READABLE_STREAM_LOCKED) +expectAssignable(errorCodes.FST_ERR_REP_ALREADY_SENT) +expectAssignable(errorCodes.FST_ERR_REP_SENT_VALUE) +expectAssignable(errorCodes.FST_ERR_SEND_INSIDE_ONERR) +expectAssignable(errorCodes.FST_ERR_SEND_UNDEFINED_ERR) +expectAssignable(errorCodes.FST_ERR_BAD_STATUS_CODE) +expectAssignable(errorCodes.FST_ERR_BAD_TRAILER_NAME) +expectAssignable(errorCodes.FST_ERR_BAD_TRAILER_VALUE) +expectAssignable(errorCodes.FST_ERR_FAILED_ERROR_SERIALIZATION) +expectAssignable(errorCodes.FST_ERR_MISSING_SERIALIZATION_FN) +expectAssignable(errorCodes.FST_ERR_MISSING_CONTENTTYPE_SERIALIZATION_FN) +expectAssignable(errorCodes.FST_ERR_REQ_INVALID_VALIDATION_INVOCATION) +expectAssignable(errorCodes.FST_ERR_SCH_MISSING_ID) +expectAssignable(errorCodes.FST_ERR_SCH_ALREADY_PRESENT) +expectAssignable(errorCodes.FST_ERR_SCH_CONTENT_MISSING_SCHEMA) +expectAssignable(errorCodes.FST_ERR_SCH_DUPLICATE) +expectAssignable(errorCodes.FST_ERR_SCH_VALIDATION_BUILD) +expectAssignable(errorCodes.FST_ERR_SCH_SERIALIZATION_BUILD) +expectAssignable(errorCodes.FST_ERR_SCH_RESPONSE_SCHEMA_NOT_NESTED_2XX) +expectAssignable(errorCodes.FST_ERR_INIT_OPTS_INVALID) +expectAssignable(errorCodes.FST_ERR_FORCE_CLOSE_CONNECTIONS_IDLE_NOT_AVAILABLE) +expectAssignable(errorCodes.FST_ERR_DUPLICATED_ROUTE) +expectAssignable(errorCodes.FST_ERR_BAD_URL) +expectAssignable(errorCodes.FST_ERR_ASYNC_CONSTRAINT) +expectAssignable(errorCodes.FST_ERR_INVALID_URL) +expectAssignable(errorCodes.FST_ERR_ROUTE_OPTIONS_NOT_OBJ) +expectAssignable(errorCodes.FST_ERR_ROUTE_DUPLICATED_HANDLER) +expectAssignable(errorCodes.FST_ERR_ROUTE_HANDLER_NOT_FN) +expectAssignable(errorCodes.FST_ERR_ROUTE_MISSING_HANDLER) +expectAssignable(errorCodes.FST_ERR_ROUTE_METHOD_INVALID) +expectAssignable(errorCodes.FST_ERR_ROUTE_METHOD_NOT_SUPPORTED) +expectAssignable(errorCodes.FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED) +expectAssignable(errorCodes.FST_ERR_ROUTE_BODY_LIMIT_OPTION_NOT_INT) +expectAssignable(errorCodes.FST_ERR_ROUTE_REWRITE_NOT_STR) +expectAssignable(errorCodes.FST_ERR_REOPENED_CLOSE_SERVER) +expectAssignable(errorCodes.FST_ERR_REOPENED_SERVER) +expectAssignable(errorCodes.FST_ERR_INSTANCE_ALREADY_LISTENING) +expectAssignable(errorCodes.FST_ERR_PLUGIN_VERSION_MISMATCH) +expectAssignable(errorCodes.FST_ERR_PLUGIN_NOT_PRESENT_IN_INSTANCE) +expectAssignable(errorCodes.FST_ERR_PLUGIN_INVALID_ASYNC_HANDLER) +expectAssignable(errorCodes.FST_ERR_PLUGIN_CALLBACK_NOT_FN) +expectAssignable(errorCodes.FST_ERR_PLUGIN_NOT_VALID) +expectAssignable(errorCodes.FST_ERR_ROOT_PLG_BOOTED) +expectAssignable(errorCodes.FST_ERR_PARENT_PLUGIN_BOOTED) +expectAssignable(errorCodes.FST_ERR_PLUGIN_TIMEOUT) diff --git a/services/slides/node_modules/fastify/test/types/fastify.test-d.ts b/services/slides/node_modules/fastify/test/types/fastify.test-d.ts new file mode 100644 index 0000000000000000000000000000000000000000..91fd4a69304576d3572dbbe9a1367ffe02e184a3 --- /dev/null +++ b/services/slides/node_modules/fastify/test/types/fastify.test-d.ts @@ -0,0 +1,352 @@ +import Ajv, { ErrorObject as AjvErrorObject } from 'ajv' +import * as http from 'node:http' +import * as http2 from 'node:http2' +import * as https from 'node:https' +import { Socket } from 'node:net' +import { expectAssignable, expectError, expectNotAssignable, expectType } from 'tsd' +import fastify, { + ConnectionError, + FastifyBaseLogger, + FastifyError, + FastifyErrorCodes, + FastifyInstance, + FastifyPlugin, + FastifyPluginAsync, + FastifyPluginCallback, + InjectOptions, + LightMyRequestCallback, + LightMyRequestChain, + LightMyRequestResponse, + RawRequestDefaultExpression, + RouteGenericInterface, + SafePromiseLike +} from '../../fastify' +import { Bindings, ChildLoggerOptions } from '../../types/logger' + +// FastifyInstance +// http server +expectError< + FastifyInstance & + Promise> +>(fastify()) +expectAssignable< + FastifyInstance & + PromiseLike> +>(fastify()) +expectType< + FastifyInstance & + SafePromiseLike> +>(fastify()) +expectType< + FastifyInstance & + SafePromiseLike> +>(fastify({})) +expectType< + FastifyInstance & + SafePromiseLike> +>(fastify({ http: {} })) +// https server +expectType< + FastifyInstance & + SafePromiseLike> +>(fastify({ https: {} })) +expectType< + FastifyInstance & + SafePromiseLike> +>(fastify({ https: null })) +// http2 server +expectType< + FastifyInstance & + SafePromiseLike> +>(fastify({ http2: true, http2SessionTimeout: 1000 })) +expectType< + FastifyInstance & + SafePromiseLike> +>(fastify({ http2: true, https: {}, http2SessionTimeout: 1000 })) +expectType(fastify({ http2: true, https: {} }).inject()) +expectType< + FastifyInstance & + SafePromiseLike> +>(fastify({ schemaController: {} })) +expectType< + FastifyInstance & + SafePromiseLike> +>( + fastify({ + schemaController: { + compilersFactory: {} + } + }) +) + +expectError(fastify({ http2: false })) // http2 option must be true +expectError(fastify({ http2: false })) // http2 option must be true +expectError( + fastify({ + schemaController: { + bucket: () => ({}) // cannot be empty + } + }) +) + +// light-my-request +expectAssignable({ query: '' }) +fastify({ http2: true, https: {} }).inject().then((resp) => { + expectAssignable(resp) +}) +const lightMyRequestCallback: LightMyRequestCallback = ( + err: Error | undefined, + response: LightMyRequestResponse | undefined +) => { + if (err) throw err +} +fastify({ http2: true, https: {} }).inject({}, lightMyRequestCallback) + +// server options +expectAssignable< + FastifyInstance +>(fastify({ http2: true })) +expectAssignable(fastify({ ignoreTrailingSlash: true })) +expectAssignable(fastify({ ignoreDuplicateSlashes: true })) +expectAssignable(fastify({ connectionTimeout: 1000 })) +expectAssignable(fastify({ forceCloseConnections: true })) +expectAssignable(fastify({ keepAliveTimeout: 1000 })) +expectAssignable(fastify({ pluginTimeout: 1000 })) +expectAssignable(fastify({ bodyLimit: 100 })) +expectAssignable(fastify({ handlerTimeout: 5000 })) +expectAssignable(fastify({ maxParamLength: 100 })) +expectAssignable(fastify({ disableRequestLogging: true })) +expectAssignable(fastify({ disableRequestLogging: (req) => req.url?.includes('/health') ?? false })) +expectAssignable(fastify({ requestIdLogLabel: 'request-id' })) +expectAssignable(fastify({ onProtoPoisoning: 'error' })) +expectAssignable(fastify({ onConstructorPoisoning: 'error' })) +expectAssignable(fastify({ serializerOpts: { rounding: 'ceil' } })) +expectAssignable( + fastify({ serializerOpts: { ajv: { missingRefs: 'ignore' } } }) +) +expectAssignable(fastify({ serializerOpts: { schema: {} } })) +expectAssignable(fastify({ serializerOpts: { otherProp: {} } })) +expectAssignable< + FastifyInstance +>(fastify({ logger: true })) +expectAssignable< + FastifyInstance +>(fastify({ logger: true })) +expectAssignable>(fastify({ + logger: { + level: 'info', + genReqId: () => 'request-id', + serializers: { + req: () => { + return { + method: 'GET', + url: '/', + version: '1.0.0', + host: 'localhost', + remoteAddress: '127.0.0.1', + remotePort: 3000 + } + }, + res: () => { + return { + statusCode: 200 + } + }, + err: () => { + return { + type: 'Error', + message: 'foo', + stack: '' + } + } + } + } +})) +const customLogger = { + level: 'info', + info: () => { }, + warn: () => { }, + error: () => { }, + fatal: () => { }, + trace: () => { }, + debug: () => { }, + child: () => customLogger +} +expectAssignable< + FastifyInstance +>(fastify({ logger: customLogger })) +expectAssignable(fastify({ serverFactory: () => http.createServer() })) +expectAssignable(fastify({ caseSensitive: true })) +expectAssignable(fastify({ requestIdHeader: 'request-id' })) +expectAssignable(fastify({ requestIdHeader: false })) +expectAssignable(fastify({ + genReqId: (req) => { + expectType(req) + return 'foo' + } +})) +expectAssignable(fastify({ trustProxy: true })) +expectAssignable(fastify({ querystringParser: () => ({ foo: 'bar' }) })) +expectAssignable(fastify({ querystringParser: () => ({ foo: { bar: 'fuzz' } }) })) +expectAssignable(fastify({ querystringParser: () => ({ foo: ['bar', 'fuzz'] }) })) +expectAssignable(fastify({ constraints: {} })) +expectAssignable(fastify({ + constraints: { + version: { + name: 'version', + storage: () => ({ + get: () => () => { }, + set: () => { }, + del: () => { }, + empty: () => { } + }), + validate () { }, + deriveConstraint: () => 'foo' + }, + host: { + name: 'host', + storage: () => ({ + get: () => () => { }, + set: () => { }, + del: () => { }, + empty: () => { } + }), + validate () { }, + deriveConstraint: () => 'foo' + }, + withObjectValue: { + name: 'withObjectValue', + storage: () => ({ + get: () => () => { }, + set: () => { }, + del: () => { }, + empty: () => { } + }), + validate () { }, + deriveConstraint: () => { } + + } + } +})) +expectAssignable(fastify({ return503OnClosing: true })) +expectAssignable(fastify({ + ajv: { + customOptions: { + removeAdditional: 'all' + }, + plugins: [(ajv: Ajv): Ajv => ajv] + } +})) +expectAssignable(fastify({ + ajv: { + plugins: [[(ajv: Ajv): Ajv => ajv, ['keyword1', 'keyword2']]] + } +})) +expectError(fastify({ + ajv: { + customOptions: { + removeAdditional: 'all' + }, + plugins: [ + () => { + // error, plugins always return the Ajv instance fluently + } + ] + } +})) +expectAssignable(fastify({ + ajv: { + onCreate: (ajvInstance) => { + expectType(ajvInstance) + return ajvInstance + } + } +})) +expectAssignable(fastify({ frameworkErrors: () => { } })) +expectAssignable(fastify({ + rewriteUrl: function (req) { + this.log.debug('rewrite url') + return req.url === '/hi' ? '/hello' : req.url! + } +})) +expectAssignable(fastify({ + schemaErrorFormatter: (errors, dataVar) => { + console.log( + errors[0].keyword.toLowerCase(), + errors[0].message?.toLowerCase(), + errors[0].params, + errors[0].instancePath.toLowerCase(), + errors[0].schemaPath.toLowerCase() + ) + return new Error() + } +})) +expectAssignable(fastify({ + clientErrorHandler: (err, socket) => { + expectType(err) + expectType(socket) + } +})) + +expectAssignable(fastify({ + childLoggerFactory: function ( + this: FastifyInstance, + logger: FastifyBaseLogger, + bindings: Bindings, + opts: ChildLoggerOptions, + req: RawRequestDefaultExpression + ) { + expectType(logger) + expectType(bindings) + expectType(opts) + expectType(req) + expectAssignable(this) + return logger.child(bindings, opts) + } +})) + +// Thenable +expectAssignable>(fastify({ return503OnClosing: true })) +fastify().then(fastifyInstance => expectAssignable(fastifyInstance)) + +expectAssignable(async () => { }) +expectAssignable(() => { }) +expectAssignable(() => { }) + +const ajvErrorObject: AjvErrorObject = { + keyword: '', + instancePath: '', + schemaPath: '', + params: {}, + message: '' +} +expectNotAssignable({ + keyword: '', + instancePath: '', + schemaPath: '', + params: '', + message: '' +}) + +expectAssignable([ajvErrorObject]) +expectAssignable('body') +expectAssignable('headers') +expectAssignable('params') +expectAssignable('querystring') + +const routeGeneric: RouteGenericInterface = {} +expectType(routeGeneric.Body) +expectType(routeGeneric.Headers) +expectType(routeGeneric.Params) +expectType(routeGeneric.Querystring) +expectType(routeGeneric.Reply) + +// ErrorCodes +expectType(fastify.errorCodes) + +fastify({ allowUnsafeRegex: true }) +fastify({ allowUnsafeRegex: false }) +expectError(fastify({ allowUnsafeRegex: 'invalid' })) + +expectAssignable(fastify({ allowErrorHandlerOverride: true })) +expectAssignable(fastify({ allowErrorHandlerOverride: false })) diff --git a/services/slides/node_modules/fastify/test/types/hooks.test-d.ts b/services/slides/node_modules/fastify/test/types/hooks.test-d.ts new file mode 100644 index 0000000000000000000000000000000000000000..312150eb9cf516e20f315408596a6bc2d245ec20 --- /dev/null +++ b/services/slides/node_modules/fastify/test/types/hooks.test-d.ts @@ -0,0 +1,550 @@ +import { FastifyError } from '@fastify/error' +import { expectAssignable, expectError, expectType } from 'tsd' +import fastify, { + ContextConfigDefault, FastifyContextConfig, + FastifyInstance, + FastifyPluginOptions, + FastifyReply, + FastifyRequest, + FastifySchema, + FastifyTypeProviderDefault, + RawReplyDefaultExpression, + RawRequestDefaultExpression, + RawServerDefault, + RegisterOptions, + RouteOptions, + // preClose hook types should be exported correctly https://github.com/fastify/fastify/pull/5335 + /* eslint-disable @typescript-eslint/no-unused-vars */ + preCloseAsyncHookHandler, + preCloseHookHandler +} from '../../fastify' +import { DoneFuncWithErrOrRes, HookHandlerDoneFunction, RequestPayload, preHandlerAsyncHookHandler } from '../../types/hooks' +import { FastifyRouteConfig, RouteGenericInterface } from '../../types/route' + +const server = fastify() + +// Test payload generic pass through for preSerialization and onSend + +type TestPayloadType = { + foo: string; + bar: number; +} + +// Synchronous Tests + +server.addHook('onRequest', function (request, reply, done) { + expectType(this) + expectType(request) + expectType(reply) + expectAssignable<(err?: FastifyError) => void>(done) + expectAssignable<(err?: NodeJS.ErrnoException) => void>(done) + expectType(done(new Error())) +}) + +server.addHook('preParsing', function (request, reply, payload, done) { + expectType(this) + expectType(request) + expectType(reply) + expectType(payload) + expectAssignable<(err?: FastifyError | null, res?: RequestPayload) => void>(done) + expectAssignable<(err?: NodeJS.ErrnoException) => void>(done) + expectType(done(new Error())) +}) + +server.addHook('preValidation', function (request, reply, done) { + expectType(this) + expectType(request) + expectType(reply) + expectAssignable<(err?: FastifyError) => void>(done) + expectAssignable<(err?: NodeJS.ErrnoException) => void>(done) + expectType(done(new Error())) +}) + +server.addHook('preHandler', function (request, reply, done) { + expectType(this) + expectType(request) + expectType(reply) + expectAssignable<(err?: FastifyError) => void>(done) + expectAssignable<(err?: NodeJS.ErrnoException) => void>(done) + expectType(done(new Error())) +}) + +server.addHook('preSerialization', function (request, reply, payload, done) { + expectType(this) + expectType(request) + expectType(reply) + expectType(payload) // we expect this to be unknown when not specified like in the previous test + expectType(done(new Error())) + expectType(done(null, 'foobar')) + expectType(done()) + expectError(done(new Error(), 'foobar')) +}) + +server.addHook('onSend', function (request, reply, payload, done) { + expectType(this) + expectType(request) + expectType(reply) + expectType(payload) + expectType(done(new Error())) + expectType(done(null, 'foobar')) + expectType(done()) + expectError(done(new Error(), 'foobar')) +}) + +server.addHook('onResponse', function (request, reply, done) { + expectType(this) + expectType(request) + expectType(reply) + expectAssignable<(err?: FastifyError) => void>(done) + expectAssignable<(err?: NodeJS.ErrnoException) => void>(done) + expectType(done(new Error())) +}) + +server.addHook('onTimeout', function (request, reply, done) { + expectType(this) + expectType(request) + expectType(reply) + expectAssignable<(err?: FastifyError) => void>(done) + expectAssignable<(err?: NodeJS.ErrnoException) => void>(done) + expectType(done(new Error())) +}) + +server.addHook('onError', function (request, reply, error, done) { + expectType(this) + expectType(request) + expectType(reply) + expectType(error) + expectType<() => void>(done) + expectType(done()) +}) + +server.addHook('onRequestAbort', function (request, done) { + expectType(this) + expectType(request) + expectAssignable<(err?: FastifyError) => void>(done) + expectAssignable<(err?: NodeJS.ErrnoException) => void>(done) + expectType(done(new Error())) +}) + +server.addHook('onRoute', function (opts) { + expectType(this) + expectType(opts) +}) + +server.addHook('onRegister', function (instance, opts) { + expectType(this) + expectType(instance) + expectType(opts) +}) + +server.addHook('onReady', function (done) { + expectType(this) + expectAssignable<(err?: FastifyError) => void>(done) + expectAssignable<(err?: NodeJS.ErrnoException) => void>(done) + expectType(done(new Error())) +}) + +server.addHook('onListen', function (done) { + expectType(this) + expectAssignable<(err?: FastifyError) => void>(done) + expectAssignable<(err?: NodeJS.ErrnoException) => void>(done) +}) + +server.addHook('onClose', function (instance, done) { + expectType(this) + expectType(instance) + expectAssignable<(err?: FastifyError) => void>(done) + expectAssignable<(err?: NodeJS.ErrnoException) => void>(done) + expectType(done(new Error())) +}) + +// Asynchronous + +server.addHook('onRequest', async function (request, reply) { + expectType(this) + expectType(request) + expectType(reply) +}) + +server.addHook('preParsing', async function (request, reply, payload) { + expectType(this) + expectType(request) + expectType(reply) + expectType(payload) +}) + +server.addHook('preValidation', async function (request, reply) { + expectType(this) + expectType(request) + expectType(reply) +}) + +server.addHook('preHandler', async function (request, reply) { + expectType(this) + expectType(request) + expectType(reply) +}) + +server.addHook('preSerialization', async function (request, reply, payload) { + expectType(this) + expectType(request) + expectType(reply) + expectType(payload) // we expect this to be unknown when not specified like in the previous test +}) + +server.addHook('onSend', async function (request, reply, payload) { + expectType(this) + expectType(request) + expectType(reply) + expectType(payload) +}) + +server.addHook('onResponse', async function (request, reply) { + expectType(this) + expectType(request) + expectType(reply) +}) + +server.addHook('onTimeout', async function (request, reply) { + expectType(this) + expectType(request) + expectType(reply) +}) + +server.addHook('onError', async function (request, reply, error) { + expectType(this) + expectType(request) + expectType(reply) + expectType(error) +}) + +server.addHook('onRequestAbort', async function (request) { + expectType(this) + expectType(request) +}) + +server.addHook('onRegister', async (instance, opts) => { + expectType(instance) + expectType(opts) +}) + +server.addHook('onReady', async function () { + expectType(this) +}) + +server.addHook('onListen', async function () { + expectType(this) +}) + +server.addHook('onClose', async function (instance) { + expectType(this) + expectType(instance) +}) + +// Use case to monitor any regression on issue #3620 +// ref.: https://github.com/fastify/fastify/issues/3620 +const customTypedHook: preHandlerAsyncHookHandler< +RawServerDefault, +RawRequestDefaultExpression, +RawReplyDefaultExpression, +RouteGenericInterface, +ContextConfigDefault, +FastifySchema, +FastifyTypeProviderDefault +> = async function (request, reply): Promise { + expectType(this) + expectAssignable(request) + expectAssignable(reply) +} + +server.register(async (instance) => { + instance.addHook('preHandler', customTypedHook) +}) + +// Test custom Context Config types for hooks +type CustomContextConfig = FastifyContextConfig & { + foo: string; + bar: number; +} +type CustomContextConfigWithDefault = CustomContextConfig & FastifyRouteConfig + +server.route({ + method: 'GET', + url: '/', + handler: () => { }, + onRequest: (request, reply, done) => { + expectType(request.routeOptions.config) + expectType(reply.routeOptions.config) + }, + preParsing: (request, reply, payload, done) => { + expectType(request.routeOptions.config) + expectType(reply.routeOptions.config) + }, + preValidation: (request, reply, done) => { + expectType(request.routeOptions.config) + expectType(reply.routeOptions.config) + }, + preHandler: (request, reply, done) => { + expectType(request.routeOptions.config) + expectType(reply.routeOptions.config) + }, + preSerialization: (request, reply, payload, done) => { + expectType(request.routeOptions.config) + expectType(reply.routeOptions.config) + }, + onSend: (request, reply, payload, done) => { + expectType(request.routeOptions.config) + expectType(reply.routeOptions.config) + }, + onResponse: (request, reply, done) => { + expectType(request.routeOptions.config) + expectType(reply.routeOptions.config) + }, + onTimeout: (request, reply, done) => { + expectType(request.routeOptions.config) + expectType(reply.routeOptions.config) + }, + onError: (request, reply, error, done) => { + expectType(request.routeOptions.config) + expectType(reply.routeOptions.config) + } +}) + +server.get('/', { + onRequest: async (request, reply) => { + expectType(request.routeOptions.config) + expectType(reply.routeOptions.config) + }, + preParsing: async (request, reply) => { + expectType(request.routeOptions.config) + expectType(reply.routeOptions.config) + }, + preValidation: async (request, reply) => { + expectType(request.routeOptions.config) + expectType(reply.routeOptions.config) + }, + preHandler: async (request, reply) => { + expectType(request.routeOptions.config) + expectType(reply.routeOptions.config) + }, + preSerialization: async (request, reply) => { + expectType(request.routeOptions.config) + expectType(reply.routeOptions.config) + }, + onSend: async (request, reply) => { + expectType(request.routeOptions.config) + expectType(reply.routeOptions.config) + }, + onResponse: async (request, reply) => { + expectType(request.routeOptions.config) + expectType(reply.routeOptions.config) + }, + onTimeout: async (request, reply) => { + expectType(request.routeOptions.config) + expectType(reply.routeOptions.config) + }, + onError: async (request, reply) => { + expectType(request.routeOptions.config) + expectType(reply.routeOptions.config) + } +}, async (request, reply) => { + expectType(request.routeOptions.config) + expectType(reply.routeOptions.config) +}) + +type CustomContextRequest = FastifyRequest +type CustomContextReply = FastifyReply +server.route({ + method: 'GET', + url: '/', + handler: () => { }, + onRequest: async (request: CustomContextRequest, reply: CustomContextReply) => { + expectType(request.routeOptions.config) + expectType(reply.routeOptions.config) + }, + preParsing: async (request: CustomContextRequest, reply: CustomContextReply, payload: RequestPayload) => { + expectType(request.routeOptions.config) + expectType(reply.routeOptions.config) + }, + preValidation: async (request: CustomContextRequest, reply: CustomContextReply) => { + expectType(request.routeOptions.config) + expectType(reply.routeOptions.config) + }, + preHandler: async (request: CustomContextRequest, reply: CustomContextReply) => { + expectType(request.routeOptions.config) + expectType(reply.routeOptions.config) + }, + preSerialization: async (request: CustomContextRequest, reply: CustomContextReply, payload: any) => { + expectType(request.routeOptions.config) + expectType(reply.routeOptions.config) + }, + onSend: async (request: CustomContextRequest, reply: CustomContextReply, payload: any) => { + expectType(request.routeOptions.config) + expectType(reply.routeOptions.config) + }, + onResponse: async (request: CustomContextRequest, reply: CustomContextReply) => { + expectType(request.routeOptions.config) + expectType(reply.routeOptions.config) + }, + onTimeout: async (request: CustomContextRequest, reply: CustomContextReply) => { + expectType(request.routeOptions.config) + expectType(reply.routeOptions.config) + }, + onError: async (request: CustomContextRequest, reply: CustomContextReply, error: FastifyError) => { + expectType(request.routeOptions.config) + expectType(reply.routeOptions.config) + } +}) + +server.route({ + method: 'GET', + url: '/', + handler: (request, reply) => { + expectType(request) + expectType(reply) + }, + onRequest: (request, reply, done) => { + expectType(request) + expectType(reply) + expectType(done) + }, + onRequestAbort: (request, done) => { + expectType(request) + expectType(done) + }, + preParsing: (request, reply, payload, done) => { + expectType(request) + expectType(reply) + expectType(payload) + expectType< + ( + err?: TError | null | undefined, + res?: RequestPayload | undefined + ) => void + >(done) + }, + preValidation: (request, reply, done) => { + expectType(request) + expectType(reply) + expectType(done) + }, + preHandler: (request, reply, done) => { + expectType(request) + expectType(reply) + expectType(done) + }, + preSerialization: (request, reply, payload, done) => { + expectType(request) + expectType(reply) + expectType(payload) + expectType(done) + }, + onSend: (request, reply, payload, done) => { + expectType(request) + expectType(reply) + expectType(payload) + expectType(done) + }, + onResponse: (request, reply, done) => { + expectType(request) + expectType(reply) + expectType(done) + }, + onTimeout: (request, reply, done) => { + expectType(request) + expectType(reply) + expectType(done) + }, + onError: (request, reply, error, done) => { + expectType(request) + expectType(reply) + expectType(error) + expectType<() => void>(done) + } +}) + +server.get('/', { + onRequest: async (request, reply) => { + expectType(request) + expectType(reply) + }, + onRequestAbort: async (request, reply) => { + expectType(request) + }, + preParsing: async (request, reply, payload) => { + expectType(request) + expectType(reply) + expectType(payload) + }, + preValidation: async (request, reply) => { + expectType(request) + expectType(reply) + }, + preHandler: async (request, reply) => { + expectType(request) + expectType(reply) + }, + preSerialization: async (request, reply, payload) => { + expectType(request) + expectType(reply) + expectType(payload) + }, + onSend: async (request, reply, payload) => { + expectType(request) + expectType(reply) + expectType(payload) + }, + onResponse: async (request, reply) => { + expectType(request) + expectType(reply) + }, + onTimeout: async (request, reply) => { + expectType(request) + expectType(reply) + }, + onError: async (request, reply, error) => { + expectType(request) + expectType(reply) + expectType(error) + } +}, async (request, reply) => { + expectType(request) + expectType(reply) +}) + +// TODO: Should throw errors +// expectError(server.get('/', { onRequest: async (request, reply, done) => {} }, async (request, reply) => {})) +// expectError(server.get('/', { onRequestAbort: async (request, done) => {} }, async (request, reply) => {})) +// expectError(server.get('/', { preParsing: async (request, reply, payload, done) => {} }, async (request, reply) => {})) +// expectError(server.get('/', { preValidation: async (request, reply, done) => {} }, async (request, reply) => {})) +// expectError(server.get('/', { preHandler: async (request, reply, done) => {} }, async (request, reply) => {})) +// expectError(server.get('/', { preSerialization: async (request, reply, payload, done) => {} }, async (request, reply) => {})) +// expectError(server.get('/', { onSend: async (request, reply, payload, done) => {} }, async (request, reply) => {})) +// expectError(server.get('/', { onResponse: async (request, reply, done) => {} }, async (request, reply) => {})) +// expectError(server.get('/', { onTimeout: async (request, reply, done) => {} }, async (request, reply) => {})) +// expectError(server.get('/', { onError: async (request, reply, error, done) => {} }, async (request, reply) => {})) + +server.addHook('preClose', function (done) { + expectType(this) + expectAssignable<(err?: FastifyError) => void>(done) + expectAssignable<(err?: NodeJS.ErrnoException) => void>(done) + expectType(done(new Error())) +}) + +server.addHook('preClose', async function () { + expectType(this) +}) + +expectError(server.addHook('onClose', async function (instance, done) {})) +expectError(server.addHook('onError', async function (request, reply, error, done) {})) +expectError(server.addHook('onReady', async function (done) {})) +expectError(server.addHook('onListen', async function (done) {})) +expectError(server.addHook('onRequest', async function (request, reply, done) {})) +expectError(server.addHook('onRequestAbort', async function (request, done) {})) +expectError(server.addHook('onResponse', async function (request, reply, done) {})) +expectError(server.addHook('onSend', async function (request, reply, payload, done) {})) +expectError(server.addHook('onTimeout', async function (request, reply, done) {})) +expectError(server.addHook('preClose', async function (done) {})) +expectError(server.addHook('preHandler', async function (request, reply, done) {})) +expectError(server.addHook('preSerialization', async function (request, reply, payload, done) {})) +expectError(server.addHook('preValidation', async function (request, reply, done) {})) diff --git a/services/slides/node_modules/fastify/test/types/import.ts b/services/slides/node_modules/fastify/test/types/import.ts new file mode 100644 index 0000000000000000000000000000000000000000..303ec4d6ccf5a5dd35a46eac5af6ba89e82bea45 --- /dev/null +++ b/services/slides/node_modules/fastify/test/types/import.ts @@ -0,0 +1,2 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import { FastifyListenOptions, FastifyLogFn } from '../../fastify' diff --git a/services/slides/node_modules/fastify/test/types/instance.test-d.ts b/services/slides/node_modules/fastify/test/types/instance.test-d.ts new file mode 100644 index 0000000000000000000000000000000000000000..ce77e8b2bba7809b5744868c651ce8cb78466f6d --- /dev/null +++ b/services/slides/node_modules/fastify/test/types/instance.test-d.ts @@ -0,0 +1,588 @@ +import { expectAssignable, expectError, expectNotAssignable, expectNotDeprecated, expectType } from 'tsd' +import fastify, { + FastifyBaseLogger, + FastifyBodyParser, + FastifyError, + FastifyInstance, + FastifyRouterOptions, + RawReplyDefaultExpression, + RawRequestDefaultExpression, + RawServerDefault, + RouteGenericInterface +} from '../../fastify' +import { HookHandlerDoneFunction } from '../../types/hooks' +import { FastifyReply } from '../../types/reply' +import { FastifyRequest } from '../../types/request' +import { FastifySchemaControllerOptions, FastifySchemaCompiler, FastifySerializerCompiler } from '../../types/schema' +import { AddressInfo } from 'node:net' +import { Bindings, ChildLoggerOptions } from '../../types/logger' +import { Config as FindMyWayConfig, ConstraintStrategy } from 'find-my-way' +import { FindMyWayVersion } from '../../types/instance' + +const server = fastify() + +expectAssignable(server.addSchema({ + type: 'null' +})) +expectAssignable(server.addSchema({ + schemaId: 'id' +})) +expectAssignable(server.addSchema({ + schemas: [] +})) + +expectType(server.pluginName) + +expectType>(server.getSchemas()) +expectType(server.addresses()) +expectType(server.getSchema('SchemaId')) +expectType(server.printRoutes()) +expectType(server.printPlugins()) +expectType(server.listeningOrigin) +expectType(server.supportedMethods) + +expectAssignable( + server.setErrorHandler(function (error, request, reply) { + expectType(error) + expectAssignable(this) + }) +) + +expectAssignable( + server.setErrorHandler(function (error, request, reply) { + expectType(error) + }) +) + +expectAssignable( + server.setGenReqId(function (req) { + expectType(req) + return 'foo' + }) +) + +function fastifySetGenReqId (req: RawRequestDefaultExpression) { + return 'foo' +} +server.setGenReqId(fastifySetGenReqId) + +function fastifyErrorHandler (this: FastifyInstance, error: FastifyError) {} +server.setErrorHandler(fastifyErrorHandler) + +async function asyncFastifyErrorHandler (this: FastifyInstance, error: FastifyError) {} +server.setErrorHandler(asyncFastifyErrorHandler) + +function nodeJSErrorHandler (error: NodeJS.ErrnoException) { + if (error) { throw error } +} +server.setErrorHandler(nodeJSErrorHandler) + +function asyncNodeJSErrorHandler (error: NodeJS.ErrnoException) { + if (error) { throw error } +} +server.setErrorHandler(asyncNodeJSErrorHandler) + +class CustomError extends Error { + private __brand: any +} +interface ReplyPayload { + Reply: { + test: boolean; + }; +} +// typed sync error handler +server.setErrorHandler((error, request, reply) => { + expectType(error) + expectType<((...args: [payload: ReplyPayload['Reply']]) => FastifyReply, RawReplyDefaultExpression>)>(reply.send) +}) +// typed async error handler send +server.setErrorHandler(async (error, request, reply) => { + expectType(error) + expectType<((...args: [payload: ReplyPayload['Reply']]) => FastifyReply, RawReplyDefaultExpression>)>(reply.send) +}) +// typed async error handler return +server.setErrorHandler(async (error, request, reply) => { + expectType(error) + return { test: true } +}) +// typed sync error handler send error +expectError(server.setErrorHandler((error, request, reply) => { + expectType(error) + reply.send({ test: 'foo' }) +})) +// typed sync error handler return error +server.setErrorHandler((error, request, reply) => { + expectType(error) + return { test: 'foo' } +}) +// typed async error handler send error +expectError(server.setErrorHandler(async (error, request, reply) => { + expectType(error) + reply.send({ test: 'foo' }) +})) +// typed async error handler return error +server.setErrorHandler(async (error, request, reply) => { + expectType(error) + return { test: 'foo' } +}) + +function notFoundHandler (request: FastifyRequest, reply: FastifyReply) {} +async function notFoundAsyncHandler (request: FastifyRequest, reply: FastifyReply) {} +function notFoundpreHandlerHandler ( + request: FastifyRequest, + reply: FastifyReply, + done: HookHandlerDoneFunction +) { done() } +async function notFoundpreHandlerAsyncHandler ( + request: FastifyRequest, + reply: FastifyReply +) {} +function notFoundpreValidationHandler ( + request: FastifyRequest, + reply: FastifyReply, + done: HookHandlerDoneFunction +) { done() } +async function notFoundpreValidationAsyncHandler ( + request: FastifyRequest, + reply: FastifyReply +) {} + +server.setNotFoundHandler(notFoundHandler) +server.setNotFoundHandler({ preHandler: notFoundpreHandlerHandler }, notFoundHandler) +server.setNotFoundHandler({ preHandler: notFoundpreHandlerAsyncHandler }, notFoundHandler) +server.setNotFoundHandler({ preValidation: notFoundpreValidationHandler }, notFoundHandler) +server.setNotFoundHandler({ preValidation: notFoundpreValidationAsyncHandler }, notFoundHandler) +server.setNotFoundHandler( + { preHandler: notFoundpreHandlerHandler, preValidation: notFoundpreValidationHandler }, + notFoundHandler +) + +server.setNotFoundHandler(notFoundAsyncHandler) +server.setNotFoundHandler({ preHandler: notFoundpreHandlerHandler }, notFoundAsyncHandler) +server.setNotFoundHandler({ preHandler: notFoundpreHandlerAsyncHandler }, notFoundAsyncHandler) +server.setNotFoundHandler({ preValidation: notFoundpreValidationHandler }, notFoundAsyncHandler) +server.setNotFoundHandler({ preValidation: notFoundpreValidationAsyncHandler }, notFoundAsyncHandler) +server.setNotFoundHandler( + { preHandler: notFoundpreHandlerHandler, preValidation: notFoundpreValidationHandler }, + notFoundAsyncHandler +) + +server.setNotFoundHandler(function (_, reply) { + return reply.send('') +}) + +server.setSchemaController({ + bucket: (parentSchemas: unknown) => { + return { + add (schema: unknown) { + expectType(schema) + expectType(server.addSchema({ type: 'null' })) + return server.addSchema({ type: 'null' }) + }, + getSchema (schemaId: string) { + expectType(schemaId) + return server.getSchema('SchemaId') + }, + getSchemas () { + expectType>(server.getSchemas()) + return server.getSchemas() + } + } + } +}) + +function invalidSchemaController (schemaControllerOptions: FastifySchemaControllerOptions) {} +expectError(server.setSchemaController(invalidSchemaController)) + +server.setReplySerializer(function (payload, statusCode) { + expectType(payload) + expectType(statusCode) + return 'serialized' +}) + +function invalidReplySerializer (payload: number, statusCode: string) {} +expectError(server.setReplySerializer(invalidReplySerializer)) + +function serializerWithInvalidReturn (payload: unknown, statusCode: number) {} +expectError(server.setReplySerializer(serializerWithInvalidReturn)) + +function invalidSchemaErrorFormatter (err: Error) { + if (err) { throw err } +} +expectError(server.setSchemaErrorFormatter(invalidSchemaErrorFormatter)) + +expectType(server.addHttpMethod('SEARCH', { hasBody: true })) + +// test listen opts objects +expectAssignable>(server.listen()) +expectAssignable>(server.listen({ port: 3000 })) +expectAssignable>(server.listen({ port: 3000, listenTextResolver: (address) => { return `address: ${address}` } })) +expectAssignable>(server.listen({ port: 3000, host: '0.0.0.0' })) +expectAssignable>(server.listen({ port: 3000, host: '0.0.0.0', backlog: 42 })) +expectAssignable>(server.listen({ port: 3000, host: '0.0.0.0', backlog: 42, exclusive: true })) +expectAssignable>(server.listen({ port: 3000, host: '::/0', ipv6Only: true })) + +expectAssignable(server.listen(() => {})) +expectAssignable(server.listen({ port: 3000 }, () => {})) +expectAssignable(server.listen({ port: 3000, listenTextResolver: (address) => { return `address: ${address}` } }, () => {})) +expectAssignable(server.listen({ port: 3000, host: '0.0.0.0' }, () => {})) +expectAssignable(server.listen({ port: 3000, host: '0.0.0.0', backlog: 42 }, () => {})) +expectAssignable(server.listen({ port: 3000, host: '0.0.0.0', backlog: 42, exclusive: true }, () => {})) +expectAssignable(server.listen({ port: 3000, host: '::/0', ipv6Only: true }, () => {})) + +// test listen opts objects Typescript deprecation exclusion +expectNotDeprecated(server.listen()) +expectNotDeprecated(server.listen({ port: 3000 })) +expectNotDeprecated(server.listen({ port: 3000, host: '0.0.0.0' })) +expectNotDeprecated(server.listen({ port: 3000, host: '0.0.0.0', backlog: 42 })) +expectNotDeprecated(server.listen({ port: 3000, host: '0.0.0.0', backlog: 42, exclusive: true })) +expectNotDeprecated(server.listen({ port: 3000, host: '::/0', ipv6Only: true })) + +expectNotDeprecated(server.listen(() => {})) +expectNotDeprecated(server.listen({ port: 3000 }, () => {})) +expectNotDeprecated(server.listen({ port: 3000, host: '0.0.0.0' }, () => {})) +expectNotDeprecated(server.listen({ port: 3000, host: '0.0.0.0', backlog: 42 }, () => {})) +expectNotDeprecated(server.listen({ port: 3000, host: '0.0.0.0', backlog: 42, exclusive: true }, () => {})) +expectNotDeprecated(server.listen({ port: 3000, host: '::/0', ipv6Only: true }, () => {})) + +// test after method +expectAssignable(server.after()) +expectAssignable(server.after((err) => { + expectType(err) +})) + +// test ready method +expectAssignable(server.ready()) +expectAssignable(server.ready((err) => { + expectType(err) +})) +expectAssignable(server.ready(async (err) => { + expectType(err) +})) +expectAssignable[0]>(async (err) => { + expectType(err) +}) + +expectAssignable(server.routing({} as RawRequestDefaultExpression, {} as RawReplyDefaultExpression)) + +expectType(fastify().get('/', { + handler: () => {}, + errorHandler: (error, request, reply) => { + expectAssignable(error) + expectAssignable(request) + expectAssignable<{ contextKey: string }>(request.routeOptions.config) + expectAssignable(reply) + expectAssignable(server.errorHandler(error, request, reply)) + } +})) + +expectType(fastify().get('/', { + handler: () => {}, + childLoggerFactory: (logger, bindings, opts, req) => { + expectAssignable(server.childLoggerFactory(logger, bindings, opts, req)) + return server.childLoggerFactory(logger, bindings, opts, req) + } +})) + +expectAssignable( + server.setChildLoggerFactory(function (logger, bindings, opts, req) { + expectType(logger) + expectType(bindings) + expectType(opts) + expectType(req) + expectAssignable(this) + return logger.child(bindings, opts) + }) +) + +expectAssignable( + server.setErrorHandler(function (error, request, reply) { + expectType(error) + }) +) + +function childLoggerFactory ( + this: FastifyInstance, + logger: FastifyBaseLogger, + bindings: Bindings, + opts: ChildLoggerOptions, + req: RawRequestDefaultExpression +) { + return logger.child(bindings, opts) +} +server.setChildLoggerFactory(childLoggerFactory) +server.setChildLoggerFactory(server.childLoggerFactory) + +type InitialConfig = Readonly<{ + connectionTimeout?: number, + keepAliveTimeout?: number, + bodyLimit?: number, + caseSensitive?: boolean, + allowUnsafeRegex?: boolean, + forceCloseConnections?: boolean, + http2?: boolean, + https?: boolean | Readonly<{ allowHTTP1: boolean }>, + ignoreTrailingSlash?: boolean, + ignoreDuplicateSlashes?: boolean, + disableRequestLogging?: boolean | ((req: FastifyRequest) => boolean), + maxParamLength?: number, + onProtoPoisoning?: 'error' | 'remove' | 'ignore', + onConstructorPoisoning?: 'error' | 'remove' | 'ignore', + pluginTimeout?: number, + requestIdHeader?: string | false, + requestIdLogLabel?: string, + http2SessionTimeout?: number, + useSemicolonDelimiter?: boolean, + routerOptions?: FastifyRouterOptions +}> + +expectType(fastify().initialConfig) + +const routerOptionsForFindMyWay = {} as FastifyRouterOptions +expectAssignable>>(routerOptionsForFindMyWay) + +fastify({ + routerOptions: { + defaultRoute: (req, res) => { + expectType>(req) + expectType>(res) + expectNotAssignable(res) + res.end('foo') + }, + onBadUrl: (path, req, res) => { + expectType(path) + expectType>(req) + expectType>(res) + expectNotAssignable(res) + res.end('foo') + } + } +}) + +expectType>(server.defaultTextParser) + +expectType>(server.getDefaultJsonParser('ignore', 'error')) + +expectType(server.printRoutes({ includeHooks: true, commonPrefix: false, includeMeta: true })) + +expectType(server.printRoutes({ includeMeta: ['key1', Symbol('key2')] })) + +expectType(server.printRoutes({ method: 'GET' })) + +expectType(server.printRoutes()) + +server.decorate<(x: string) => void>('test', function (x: string): void { + expectType(this) +}) +server.decorate('test', function (x: string): void { + expectType(this) +}) +server.decorate('test', { + getter () { + expectType(this) + return 'foo' + } +}) +server.decorate('test', { + getter () { + expectType(this) + return 'foo' + }, + setter (x) { + expectType(x) + expectType(this) + } +}) +server.decorate('test') +server.decorate('test', null, ['foo']) + +server.decorateRequest<(x: string, y: number) => void>('test', function (x: string, y: number): void { + expectType(this) +}) +server.decorateRequest('test', function (x: string, y: number): void { + expectType(this) +}) +server.decorateRequest('test') +server.decorateRequest('test', null, ['foo']) + +server.decorateReply<(x: string) => void>('test', function (x: string): void { + expectType(this) +}) +server.decorateReply('test', function (x: string): void { + expectType(this) +}) +server.decorateReply('test') +server.decorateReply('test', null, ['foo']) + +expectError(server.decorate('test', true)) +expectError(server.decorate<(myNumber: number) => number>('test', function (myNumber: number): string { + return '' +})) +expectError(server.decorate('test', { + getter () { + return true + } +})) +expectError(server.decorate('test', { + setter (x) {} +})) + +declare module '../../fastify' { + interface FastifyInstance { + typedTestProperty: boolean + typedTestPropertyGetterSetter: string + typedTestMethod (x: string): string + } + + interface FastifyRequest { + typedTestRequestProperty: boolean + typedTestRequestPropertyGetterSetter: string + typedTestRequestMethod (x: string): string + } + + interface FastifyReply { + typedTestReplyProperty: boolean + typedTestReplyPropertyGetterSetter: string + typedTestReplyMethod (x: string): string + } +} + +server.decorate('typedTestProperty', false) +server.decorate('typedTestProperty', { + getter () { + return false + } +}) +server.decorate('typedTestProperty', { + getter (): boolean { + return true + }, + setter (x) { + expectType(x) + expectType(this) + } +}) +server.decorate('typedTestProperty') +server.decorate('typedTestProperty', null, ['foo']) +expectError(server.decorate('typedTestProperty', null)) +expectError(server.decorate('typedTestProperty', 'foo')) +expectError(server.decorate('typedTestProperty', { + getter () { + return 'foo' + } +})) +server.decorate('typedTestMethod', function (x) { + expectType(x) + expectType(this) + return 'foo' +}) +server.decorate('typedTestMethod', x => x) +expectError(server.decorate('typedTestMethod', function (x: boolean) { + return 'foo' +})) +expectError(server.decorate('typedTestMethod', function (x) { + return true +})) +expectError(server.decorate('typedTestMethod', async function (x) { + return 'foo' +})) + +server.decorateRequest('typedTestRequestProperty', false) +server.decorateRequest('typedTestRequestProperty', { + getter () { + return false + } +}) +server.decorateRequest('typedTestRequestProperty', { + getter (): boolean { + return true + }, + setter (x) { + expectType(x) + expectType(this) + } +}) +server.decorateRequest('typedTestRequestProperty') +server.decorateRequest('typedTestRequestProperty', null, ['foo']) +expectError(server.decorateRequest('typedTestRequestProperty', null)) +expectError(server.decorateRequest('typedTestRequestProperty', 'foo')) +expectError(server.decorateRequest('typedTestRequestProperty', { + getter () { + return 'foo' + } +})) +server.decorateRequest('typedTestRequestMethod', function (x) { + expectType(x) + expectType(this) + return 'foo' +}) +server.decorateRequest('typedTestRequestMethod', x => x) +expectError(server.decorateRequest('typedTestRequestMethod', function (x: boolean) { + return 'foo' +})) +expectError(server.decorateRequest('typedTestRequestMethod', function (x) { + return true +})) +expectError(server.decorateRequest('typedTestRequestMethod', async function (x) { + return 'foo' +})) + +server.decorateReply('typedTestReplyProperty', false) +server.decorateReply('typedTestReplyProperty', { + getter () { + return false + } +}) +server.decorateReply('typedTestReplyProperty', { + getter (): boolean { + return true + }, + setter (x) { + expectType(x) + expectType(this) + } +}) +server.decorateReply('typedTestReplyProperty') +server.decorateReply('typedTestReplyProperty', null, ['foo']) +expectError(server.decorateReply('typedTestReplyProperty', null)) +expectError(server.decorateReply('typedTestReplyProperty', 'foo')) +expectError(server.decorateReply('typedTestReplyProperty', { + getter () { + return 'foo' + } +})) +server.decorateReply('typedTestReplyMethod', function (x) { + expectType(x) + expectType(this) + return 'foo' +}) +server.decorateReply('typedTestReplyMethod', x => x) +expectError(server.decorateReply('typedTestReplyMethod', function (x: boolean) { + return 'foo' +})) +expectError(server.decorateReply('typedTestReplyMethod', function (x) { + return true +})) +expectError(server.decorateReply('typedTestReplyMethod', async function (x) { + return 'foo' +})) + +const foo = server.getDecorator('foo') +expectType(foo) + +const versionConstraintStrategy: ConstraintStrategy> = { + name: 'version', + storage: () => ({ + get: () => () => {}, + set: () => { }, + del: () => { }, + empty: () => { } + }), + validate () {}, + deriveConstraint: () => 'foo' +} +expectType(server.addConstraintStrategy(versionConstraintStrategy)) +expectType(server.hasConstraintStrategy(versionConstraintStrategy.name)) + +expectType | undefined>(server.validatorCompiler) +expectType | undefined>(server.serializerCompiler) diff --git a/services/slides/node_modules/fastify/test/types/logger.test-d.ts b/services/slides/node_modules/fastify/test/types/logger.test-d.ts new file mode 100644 index 0000000000000000000000000000000000000000..847cf768ff82fcffce5ad119da7196165abfd7d2 --- /dev/null +++ b/services/slides/node_modules/fastify/test/types/logger.test-d.ts @@ -0,0 +1,277 @@ +import * as fs from 'node:fs' +import { IncomingMessage, Server, ServerResponse } from 'node:http' +import P from 'pino' +import { expectAssignable, expectDeprecated, expectError, expectNotAssignable, expectType } from 'tsd' +import fastify, { + FastifyBaseLogger, + FastifyLogFn, + FastifyReply, + FastifyRequest, + LogLevel +} from '../../fastify' +import { FastifyLoggerInstance, ResSerializerReply } from '../../types/logger' + +expectType(fastify().log) + +class Foo { } + +['trace', 'debug', 'info', 'warn', 'error', 'fatal'].forEach(logLevel => { + expectType( + fastify().log[logLevel as LogLevel] + ) + expectType( + fastify().log[logLevel as LogLevel]('') + ) + expectType( + fastify().log[logLevel as LogLevel]({}) + ) + expectType( + fastify().log[logLevel as LogLevel]({ foo: 'bar' }) + ) + expectType( + fastify().log[logLevel as LogLevel](new Error()) + ) + expectType( + fastify().log[logLevel as LogLevel](new Foo()) + ) +}) + +interface CustomLogger extends FastifyBaseLogger { + customMethod(msg: string, ...args: unknown[]): void; +} + +class CustomLoggerImpl implements CustomLogger { + level = 'info' + customMethod (msg: string, ...args: unknown[]) { console.log(msg, args) } + + // Implementation signature must be compatible with all overloads of FastifyLogFn + info (arg1: unknown, arg2?: unknown, ...args: unknown[]): void { + console.log(arg1, arg2, ...args) + } + + warn (...args: unknown[]) { console.log(args) } + error (...args: unknown[]) { console.log(args) } + fatal (...args: unknown[]) { console.log(args) } + trace (...args: unknown[]) { console.log(args) } + debug (...args: unknown[]) { console.log(args) } + silent (...args: unknown[]) { } + + child (bindings: P.Bindings, options?: P.ChildLoggerOptions): CustomLoggerImpl { return new CustomLoggerImpl() } +} + +const customLogger = new CustomLoggerImpl() + +const serverWithCustomLogger = fastify< + Server, + IncomingMessage, + ServerResponse, + CustomLoggerImpl +>({ logger: customLogger }) + +expectType(serverWithCustomLogger.log) + +const serverWithPino = fastify< + Server, + IncomingMessage, + ServerResponse, + P.Logger +>({ + logger: P({ + level: 'info', + redact: ['x-userinfo'] + }) +}) + +expectType(serverWithPino.log) + +serverWithPino.route({ + method: 'GET', + url: '/', + handler (request) { + expectType(this.log) + expectType(request.log) + } +}) + +serverWithPino.get('/', function (request) { + expectType(this.log) + expectType(request.log) +}) + +const serverWithLogOptions = fastify< + Server, + IncomingMessage, + ServerResponse +>({ + logger: { + level: 'info' + } +}) + +expectType(serverWithLogOptions.log) + +const serverWithFileOption = fastify< + Server, + IncomingMessage, + ServerResponse +>({ + logger: { + level: 'info', + file: '/path/to/file' + } +}) + +expectType(serverWithFileOption.log) + +const serverAutoInferringTypes = fastify({ + logger: { + level: 'info' + } +}) + +expectType(serverAutoInferringTypes.log) + +const serverWithLoggerInstance = fastify({ + loggerInstance: P({ + level: 'info', + redact: ['x-userinfo'] + }) +}) + +expectType(serverWithLoggerInstance.log) + +const serverWithPinoConfig = fastify({ + logger: { + level: 'info', + serializers: { + req (IncomingMessage) { + expectType(IncomingMessage) + return { + method: 'method', + url: 'url', + version: 'version', + host: 'fastify.test', + remoteAddress: 'remoteAddress', + remotePort: 80, + other: '' + } + }, + res (ServerResponse) { + expectType>(ServerResponse) + expectAssignable & Pick>(ServerResponse) + expectNotAssignable(ServerResponse) + return { + statusCode: 'statusCode' + } + }, + err (FastifyError) { + return { + other: '', + type: 'type', + message: 'msg', + stack: 'stack' + } + } + } + } +}) + +expectType(serverWithPinoConfig.log) + +const serverAutoInferredFileOption = fastify({ + logger: { + level: 'info', + file: '/path/to/file' + } +}) + +expectType(serverAutoInferredFileOption.log) + +const serverAutoInferredSerializerResponseObjectOption = fastify({ + logger: { + serializers: { + res (ServerResponse) { + expectType>(ServerResponse) + expectAssignable & Pick>(ServerResponse) + expectNotAssignable(ServerResponse) + return { + status: '200' + } + } + } + } +}) + +expectType(serverAutoInferredSerializerResponseObjectOption.log) + +const serverAutoInferredSerializerObjectOption = fastify({ + logger: { + serializers: { + req (IncomingMessage) { + expectType(IncomingMessage) + return { + method: 'method', + url: 'url', + version: 'version', + host: 'fastify.test', + remoteAddress: 'remoteAddress', + remotePort: 80, + other: '' + } + }, + res (ServerResponse) { + expectType>(ServerResponse) + expectAssignable & Pick>(ServerResponse) + expectNotAssignable(ServerResponse) + return { + statusCode: 'statusCode' + } + }, + err (FastifyError) { + return { + other: '', + type: 'type', + message: 'msg', + stack: 'stack' + } + } + } + } +}) + +expectType(serverAutoInferredSerializerObjectOption.log) + +const passStreamAsOption = fastify({ + logger: { + stream: fs.createWriteStream('/tmp/stream.out') + } +}) + +expectType(passStreamAsOption.log) + +const passPinoOption = fastify({ + logger: { + redact: ['custom'], + messageKey: 'msg', + nestedKey: 'nested', + enabled: true + } +}) + +expectType(passPinoOption.log) + +// FastifyLoggerInstance is deprecated +expectDeprecated({} as FastifyLoggerInstance) + +const childParent = fastify().log +// we test different option variant here +expectType(childParent.child({}, { level: 'info' })) +expectType(childParent.child({}, { level: 'silent' })) +expectType(childParent.child({}, { redact: ['pass', 'pin'] })) +expectType(childParent.child({}, { serializers: { key: () => { } } })) +expectType(childParent.child({}, { level: 'info', redact: ['pass', 'pin'], serializers: { key: () => { } } })) + +// no option pass +expectError(childParent.child()) +// wrong option +expectError(childParent.child({}, { nonExist: true })) diff --git a/services/slides/node_modules/fastify/test/types/plugin.test-d.ts b/services/slides/node_modules/fastify/test/types/plugin.test-d.ts new file mode 100644 index 0000000000000000000000000000000000000000..f5e48217b3a7e8853b758b2ccb8da9bedf5dd8a1 --- /dev/null +++ b/services/slides/node_modules/fastify/test/types/plugin.test-d.ts @@ -0,0 +1,97 @@ +import fastify, { FastifyInstance, FastifyPluginOptions, SafePromiseLike } from '../../fastify' +import * as http from 'node:http' +import * as https from 'node:https' +import { expectType, expectError, expectAssignable } from 'tsd' +import { FastifyPluginCallback, FastifyPluginAsync } from '../../types/plugin' +import { FastifyError } from '@fastify/error' + +// FastifyPlugin & FastifyRegister +interface TestOptions extends FastifyPluginOptions { + option1: string; + option2: boolean; +} +const testOptions: TestOptions = { + option1: 'a', + option2: false +} +const testPluginOpts: FastifyPluginCallback = function (instance, opts, done) { + expectType(opts) +} +const testPluginOptsAsync: FastifyPluginAsync = async function (instance, opts) { + expectType(opts) +} + +const testPluginOptsWithType = ( + instance: FastifyInstance, + opts: FastifyPluginOptions, + done: (error?: FastifyError) => void +) => { } +const testPluginOptsWithTypeAsync = async ( + instance: FastifyInstance, + opts: FastifyPluginOptions +) => { } + +expectError(fastify().register(testPluginOpts, {})) // error because missing required options from generic declaration +expectError(fastify().register(testPluginOptsAsync, {})) // error because missing required options from generic declaration + +expectAssignable(fastify().register(testPluginOpts, { option1: '', option2: true })) +expectAssignable(fastify().register(testPluginOptsAsync, { option1: '', option2: true })) + +expectAssignable(fastify().register(function (instance, opts, done) { })) +expectAssignable(fastify().register(function (instance, opts, done) { }, () => { })) +expectAssignable(fastify().register(function (instance, opts, done) { }, { logLevel: 'info', prefix: 'foobar' })) + +expectAssignable(fastify().register(import('./dummy-plugin'))) +expectAssignable(fastify().register(import('./dummy-plugin'), { foo: 1 })) + +const testPluginCallback: FastifyPluginCallback = function (instance, opts, done) { } +expectAssignable(fastify().register(testPluginCallback, {})) + +const testPluginAsync: FastifyPluginAsync = async function (instance, opts) { } +expectAssignable(fastify().register(testPluginAsync, {})) + +expectAssignable( + fastify().register(function (instance, opts): Promise { return Promise.resolve() }) +) +expectAssignable(fastify().register(async function (instance, opts) { }, () => { })) +expectAssignable(fastify().register(async function (instance, opts) { }, { logLevel: 'info', prefix: 'foobar' })) + +expectError(fastify().register(function (instance, opts, done) { }, { ...testOptions, logLevel: '' })) // must use a valid logLevel + +const httpsServer = fastify({ https: {} }) +expectError< + FastifyInstance & + Promise> +>(httpsServer) +expectAssignable< + FastifyInstance & + PromiseLike> +>(httpsServer) +expectType< + FastifyInstance & + SafePromiseLike> +>(httpsServer) + +// Chainable +httpsServer + .register(testPluginOpts, testOptions) + .after((_error) => { }) + .ready((_error) => { }) + .close(() => { }) + +// Thenable +expectAssignable>(httpsServer.after()) +expectAssignable>(httpsServer.close()) +expectAssignable>(httpsServer.ready()) +expectAssignable>(httpsServer.register(testPluginOpts, testOptions)) +expectAssignable>(httpsServer.register(testPluginOptsWithType)) +expectAssignable>(httpsServer.register(testPluginOptsWithTypeAsync)) +expectAssignable>(httpsServer.register(testPluginOptsWithType, { prefix: '/test' })) +expectAssignable>(httpsServer.register(testPluginOptsWithTypeAsync, { prefix: '/test' })) + +/* eslint-disable @typescript-eslint/no-unused-vars */ +async function testAsync (): Promise { + await httpsServer + .register(testPluginOpts, testOptions) + .register(testPluginOpts, testOptions) +} diff --git a/services/slides/node_modules/fastify/test/types/register.test-d.ts b/services/slides/node_modules/fastify/test/types/register.test-d.ts new file mode 100644 index 0000000000000000000000000000000000000000..a763a679407eaea334cc73c16b6729c5c71c4143 --- /dev/null +++ b/services/slides/node_modules/fastify/test/types/register.test-d.ts @@ -0,0 +1,237 @@ +import { expectAssignable, expectError, expectType } from 'tsd' +import { IncomingMessage, Server, ServerResponse } from 'node:http' +import { Http2Server, Http2ServerRequest, Http2ServerResponse } from 'node:http2' +import fastify, { FastifyInstance, FastifyError, FastifyBaseLogger, FastifyPluginAsync, FastifyPluginCallback, FastifyPluginOptions, RawServerDefault } from '../../fastify' + +const testPluginCallback: FastifyPluginCallback = function (instance, opts, done) { } +const testPluginAsync: FastifyPluginAsync = async function (instance, opts) { } + +const testPluginOpts: FastifyPluginCallback = function (instance, opts, done) { } +const testPluginOptsAsync: FastifyPluginAsync = async function (instance, opts) { } + +const testPluginOptsWithType = ( + instance: FastifyInstance, + opts: FastifyPluginOptions, + done: (error?: FastifyError) => void +) => { } +const testPluginOptsWithTypeAsync = async (instance: FastifyInstance, opts: FastifyPluginOptions) => { } + +interface TestOptions extends FastifyPluginOptions { + option1: string; + option2: boolean; +} + +// Type validation +expectError(fastify().register(testPluginOptsAsync, { prefix: 1 })) +expectError(fastify().register(testPluginOptsAsync, { logLevel: () => ({}) })) +expectError(fastify().register(testPluginOptsAsync, { logSerializers: () => ({}) })) +expectError(fastify().register({})) + +expectAssignable( + fastify().register( + testPluginOptsAsync, { prefix: '/example', logLevel: 'info', logSerializers: { key: (value: any) => `${value}` } } + ) +) + +expectAssignable( + fastify().register(testPluginOptsAsync, () => { + return {} + }) +) + +expectAssignable( + fastify().register(testPluginOptsAsync, (instance) => { + expectType(instance) + }) +) + +// With Http2 +const serverWithHttp2 = fastify({ http2: true }) +type ServerWithHttp2 = FastifyInstance +const testPluginWithHttp2: FastifyPluginCallback = function (instance, opts, done) { } +const testPluginWithHttp2Async: FastifyPluginAsync = async function (instance, opts) { } +const testPluginWithHttp2WithType = ( + instance: ServerWithHttp2, + opts: FastifyPluginOptions, + done: (error?: FastifyError) => void +) => { } +const testPluginWithHttp2WithTypeAsync = async ( + instance: ServerWithHttp2, + opts: FastifyPluginOptions +) => { } +const testOptions: TestOptions = { + option1: 'a', + option2: false +} +expectAssignable(serverWithHttp2.register(testPluginCallback)) +expectAssignable(serverWithHttp2.register(testPluginAsync)) +expectAssignable(serverWithHttp2.register(testPluginOpts)) +expectAssignable(serverWithHttp2.register(testPluginOptsAsync)) +expectAssignable(serverWithHttp2.register(testPluginOptsWithType)) +expectAssignable(serverWithHttp2.register(testPluginOptsWithTypeAsync)) +// @ts-expect-error +serverWithHttp2.register(testPluginWithHttp2) +expectAssignable(serverWithHttp2.register(testPluginWithHttp2, testOptions)) +// @ts-expect-error +serverWithHttp2.register(testPluginWithHttp2Async) +expectAssignable(serverWithHttp2.register(testPluginWithHttp2Async, testOptions)) +expectAssignable(serverWithHttp2.register(testPluginWithHttp2WithType)) +expectAssignable(serverWithHttp2.register(testPluginWithHttp2WithTypeAsync)) +expectAssignable(serverWithHttp2.register((instance) => { + expectAssignable(instance) +})) +expectAssignable(serverWithHttp2.register((instance: ServerWithHttp2) => { + expectAssignable(instance) +})) +expectAssignable(serverWithHttp2.register(async (instance) => { + expectAssignable(instance) +})) +expectAssignable(serverWithHttp2.register(async (instance: ServerWithHttp2) => { + expectAssignable(instance) +})) + +// With Type Provider +type TestTypeProvider = { schema: 'test', validator: 'test', serializer: 'test' } +const serverWithTypeProvider = fastify().withTypeProvider() +type ServerWithTypeProvider = FastifyInstance< + Server, + IncomingMessage, + ServerResponse, + FastifyBaseLogger, + TestTypeProvider +> +const testPluginWithTypeProvider: FastifyPluginCallback< + TestOptions, + RawServerDefault, + TestTypeProvider +> = function (instance, opts, done) { } +const testPluginWithTypeProviderAsync: FastifyPluginAsync< + TestOptions, + RawServerDefault, + TestTypeProvider +> = async function (instance, opts) { } +const testPluginWithTypeProviderWithType = ( + instance: ServerWithTypeProvider, + opts: FastifyPluginOptions, + done: (error?: FastifyError) => void +) => { } +const testPluginWithTypeProviderWithTypeAsync = async ( + instance: ServerWithTypeProvider, + opts: FastifyPluginOptions +) => { } +expectAssignable(serverWithTypeProvider.register(testPluginCallback)) +expectAssignable(serverWithTypeProvider.register(testPluginAsync)) +expectAssignable(serverWithTypeProvider.register(testPluginOpts)) +expectAssignable(serverWithTypeProvider.register(testPluginOptsAsync)) +expectAssignable(serverWithTypeProvider.register(testPluginOptsWithType)) +expectAssignable(serverWithTypeProvider.register(testPluginOptsWithTypeAsync)) +// @ts-expect-error +serverWithTypeProvider.register(testPluginWithTypeProvider) +expectAssignable(serverWithTypeProvider.register(testPluginWithTypeProvider, testOptions)) +// @ts-expect-error +serverWithTypeProvider.register(testPluginWithTypeProviderAsync) +expectAssignable(serverWithTypeProvider.register(testPluginWithTypeProviderAsync, testOptions)) +expectAssignable(serverWithTypeProvider.register(testPluginWithTypeProviderWithType)) +expectAssignable(serverWithTypeProvider.register(testPluginWithTypeProviderWithTypeAsync)) +expectAssignable(serverWithTypeProvider.register((instance) => { + expectAssignable(instance) +})) +expectAssignable(serverWithTypeProvider.register((instance: ServerWithTypeProvider) => { + expectAssignable(instance) +})) +expectAssignable(serverWithTypeProvider.register(async (instance) => { + expectAssignable(instance) +})) +expectAssignable(serverWithTypeProvider.register(async (instance: ServerWithTypeProvider) => { + expectAssignable(instance) +})) + +// With Type Provider and logger +const customLogger = { + level: 'info', + info: () => { }, + warn: () => { }, + error: () => { }, + fatal: () => { }, + trace: () => { }, + debug: () => { }, + child: () => customLogger, + silent: () => { } +} +const serverWithTypeProviderAndLogger = fastify({ + loggerInstance: customLogger +}).withTypeProvider() +type ServerWithTypeProviderAndLogger = FastifyInstance< + Server, + IncomingMessage, + ServerResponse, + typeof customLogger, + TestTypeProvider +> +const testPluginWithTypeProviderAndLogger: FastifyPluginCallback< + TestOptions, + RawServerDefault, + TestTypeProvider, + typeof customLogger +> = function (instance, opts, done) { } +const testPluginWithTypeProviderAndLoggerAsync: FastifyPluginAsync< + TestOptions, + RawServerDefault, + TestTypeProvider, + typeof customLogger +> = async function (instance, opts) { } +const testPluginWithTypeProviderAndLoggerWithType = ( + instance: ServerWithTypeProviderAndLogger, + opts: FastifyPluginOptions, + done: (error?: FastifyError) => void +) => { } +const testPluginWithTypeProviderAndLoggerWithTypeAsync = async ( + instance: ServerWithTypeProviderAndLogger, + opts: FastifyPluginOptions +) => { } +expectAssignable(serverWithTypeProviderAndLogger.register(testPluginCallback)) +expectAssignable(serverWithTypeProviderAndLogger.register(testPluginAsync)) +expectAssignable(serverWithTypeProviderAndLogger.register(testPluginOpts)) +expectAssignable(serverWithTypeProviderAndLogger.register(testPluginOptsAsync)) +expectAssignable(serverWithTypeProviderAndLogger.register(testPluginOptsWithType)) +expectAssignable(serverWithTypeProviderAndLogger.register(testPluginOptsWithTypeAsync)) +expectAssignable( + // @ts-expect-error + serverWithTypeProviderAndLogger.register(testPluginWithTypeProviderAndLogger) +) +expectAssignable( + serverWithTypeProviderAndLogger.register(testPluginWithTypeProviderAndLogger, testOptions) +) +expectAssignable( + // @ts-expect-error + serverWithTypeProviderAndLogger.register(testPluginWithTypeProviderAndLoggerAsync) +) +expectAssignable( + serverWithTypeProviderAndLogger.register(testPluginWithTypeProviderAndLoggerAsync, testOptions) +) +expectAssignable( + serverWithTypeProviderAndLogger.register(testPluginWithTypeProviderAndLoggerWithType) +) +expectAssignable( + serverWithTypeProviderAndLogger.register(testPluginWithTypeProviderAndLoggerWithTypeAsync) +) +expectAssignable( + serverWithTypeProviderAndLogger.register((instance) => { + expectAssignable(instance) + }) +) +expectAssignable( + serverWithTypeProviderAndLogger.register((instance: ServerWithTypeProviderAndLogger) => { + expectAssignable(instance) + }) +) +expectAssignable( + serverWithTypeProviderAndLogger.register(async (instance) => { + expectAssignable(instance) + }) +) +expectAssignable( + serverWithTypeProviderAndLogger.register(async (instance: ServerWithTypeProviderAndLogger) => { + expectAssignable(instance) + }) +) diff --git a/services/slides/node_modules/fastify/test/types/reply.test-d.ts b/services/slides/node_modules/fastify/test/types/reply.test-d.ts new file mode 100644 index 0000000000000000000000000000000000000000..ebad0649581d2fec438f46c9ea8dceb3d528959b --- /dev/null +++ b/services/slides/node_modules/fastify/test/types/reply.test-d.ts @@ -0,0 +1,254 @@ +import { Buffer } from 'node:buffer' +import { expectAssignable, expectError, expectType } from 'tsd' +import fastify, { FastifyContextConfig, FastifyReply, FastifyRequest, FastifySchema, FastifyTypeProviderDefault, RawRequestDefaultExpression, RouteHandler, RouteHandlerMethod } from '../../fastify' +import { FastifyInstance } from '../../types/instance' +import { FastifyBaseLogger } from '../../types/logger' +import { ResolveReplyTypeWithRouteGeneric } from '../../types/reply' +import { FastifyRouteConfig, RouteGenericInterface } from '../../types/route' +import { ContextConfigDefault, RawReplyDefaultExpression, RawServerDefault } from '../../types/utils' + +type DefaultSerializationFunction = (payload: { [key: string]: unknown }) => string +type DefaultFastifyReplyWithCode = FastifyReply> + +const getHandler: RouteHandlerMethod = function (_request, reply) { + expectType(reply.raw) + expectType(reply.log) + expectType>(reply.request) + expectType<(statusCode: Code) => DefaultFastifyReplyWithCode>(reply.code) + expectType<(statusCode: Code) => DefaultFastifyReplyWithCode>(reply.status) + expectType<(...args: [payload?: unknown]) => FastifyReply>(reply.code(100 as number).send) + expectType(reply.elapsedTime) + expectType(reply.statusCode) + expectType(reply.sent) + expectType< + (hints: Record, callback?: (() => void) | undefined) => void + >(reply.writeEarlyHints) + expectType<((...args: [payload?: unknown]) => FastifyReply)>(reply.send) + expectAssignable<(key: string, value: any) => FastifyReply>(reply.header) + expectAssignable<(values: { [key: string]: any }) => FastifyReply>(reply.headers) + expectAssignable<(key: string) => number | string | string[] | undefined>(reply.getHeader) + expectAssignable<() => { [key: string]: number | string | string[] | undefined }>(reply.getHeaders) + expectAssignable<(key: string) => FastifyReply>(reply.removeHeader) + expectAssignable<(key: string) => boolean>(reply.hasHeader) + expectType<(url: string, statusCode?: number) => FastifyReply>(reply.redirect) + expectType<() => FastifyReply>(reply.hijack) + expectType<() => void>(reply.callNotFound) + expectType<(contentType: string) => FastifyReply>(reply.type) + expectType<(fn: (payload: any) => string) => FastifyReply>(reply.serializer) + expectType<(payload: any) => string | ArrayBuffer | Buffer>(reply.serialize) + expectType<(fulfilled: () => void, rejected: (err: Error) => void) => void>(reply.then) + expectType< + ( + key: string, + fn: ((reply: FastifyReply, payload: string | Buffer | null) => Promise) | + ((reply: FastifyReply, payload: string | Buffer | null, + done: (err: Error | null, value?: string) => void) => void) + ) => FastifyReply + >(reply.trailer) + expectType<(key: string) => boolean>(reply.hasTrailer) + expectType<(key: string) => FastifyReply>(reply.removeTrailer) + expectType(reply.server) + expectAssignable< + ((httpStatus: string) => DefaultSerializationFunction | undefined) + >(reply.getSerializationFunction) + expectAssignable< + ((schema: { [key: string]: unknown }) => DefaultSerializationFunction | undefined) + >(reply.getSerializationFunction) + expectAssignable< + ((schema: { [key: string]: unknown }, httpStatus?: string) => DefaultSerializationFunction) + >(reply.compileSerializationSchema) + expectAssignable< + ((input: { [key: string]: unknown }, schema: { [key: string]: unknown }, httpStatus?: string) => unknown) + >(reply.serializeInput) + expectAssignable<((input: { [key: string]: unknown }, httpStatus: string) => unknown)>(reply.serializeInput) + expectType(reply.routeOptions.config) + expectType(reply.getDecorator('foo')) +} + +interface ReplyPayload { + Reply: { + test: boolean; + }; +} + +interface ReplyArrayPayload { + Reply: string[] +} + +interface ReplyUnion { + Reply: { + success: boolean; + } | { + error: string; + } +} + +interface ReplyHttpCodes { + Reply: { + '1xx': number, + 200: 'abc', + 201: boolean, + 300: { foo: string }, + } +} + +interface InvalidReplyHttpCodes { + Reply: { + '1xx': number, + 200: string, + 999: boolean, + } +} + +interface ReplyVoid { + Reply: void; +} + +interface ReplyUndefined { + Reply: undefined; +} + +// Issue #5534 scenario: 204 No Content should allow empty send(), 201 Created should require payload +// Note: `204: undefined` gets converted to `unknown` via UndefinedToUnknown in type-provider.d.ts, +// meaning send() is optional but send({}) is also allowed. Use `void` instead of `undefined` +// if you want stricter "no payload allowed" semantics. +interface ReplyHttpCodesWithNoContent { + Reply: { + 201: { id: string }; + 204: undefined; + } +} + +const typedHandler: RouteHandler = async (request, reply) => { + // When Reply type is specified, send() requires a payload argument + expectType<((...args: [payload: ReplyPayload['Reply']]) => FastifyReply, RawReplyDefaultExpression>)>(reply.send) + expectType<((...args: [payload: ReplyPayload['Reply']]) => FastifyReply, RawReplyDefaultExpression>)>(reply.code(100).send) +} + +const server = fastify() +server.get('/get', getHandler) +server.get('/typed', typedHandler) +server.get('/get-generic-send', async function handler (request, reply) { + reply.send({ test: true }) +}) +// When Reply type is specified, send() requires a payload - calling without arguments should error +expectError(server.get('/get-generic-send-missing-payload', async function handler (request, reply) { + reply.send() +})) +server.get('/get-generic-return', async function handler (request, reply) { + return { test: false } +}) +expectError(server.get('/get-generic-send-error', async function handler (request, reply) { + reply.send({ foo: 'bar' }) +})) +expectError(server.get('/get-generic-return-error', async function handler (request, reply) { + return { foo: 'bar' } +})) +server.get('/get-generic-union-send', async function handler (request, reply) { + if (0 as number === 0) { + reply.send({ success: true }) + } else { + reply.send({ error: 'error' }) + } +}) +server.get('/get-generic-union-return', async function handler (request, reply) { + if (0 as number === 0) { + return { success: true } + } else { + return { error: 'error' } + } +}) +expectError(server.get('/get-generic-union-send-error-1', async function handler (request, reply) { + reply.send({ successes: true }) +})) +expectError(server.get('/get-generic-union-send-error-2', async function handler (request, reply) { + reply.send({ error: 500 }) +})) +expectError(server.get('/get-generic-union-return-error-1', async function handler (request, reply) { + return { successes: true } +})) +expectError(server.get('/get-generic-union-return-error-2', async function handler (request, reply) { + return { error: 500 } +})) +server.get('/get-generic-http-codes-send', async function handler (request, reply) { + reply.code(200).send('abc') + reply.code(201).send(true) + reply.code(300).send({ foo: 'bar' }) + reply.code(101).send(123) +}) +expectError(server.get('/get-generic-http-codes-send-error-1', async function handler (request, reply) { + reply.code(200).send('def') +})) +expectError(server.get('/get-generic-http-codes-send-error-2', async function handler (request, reply) { + reply.code(201).send(0) +})) +expectError(server.get('/get-generic-http-codes-send-error-3', async function handler (request, reply) { + reply.code(300).send({ foo: 123 }) +})) +expectError(server.get('/get-generic-http-codes-send-error-4', async function handler (request, reply) { + reply.code(100).send('asdasd') +})) +expectError(server.get('/get-generic-http-codes-send-error-5', async function handler (request, reply) { + reply.code(401).send({ foo: 123 }) +})) +server.get('/get-generic-array-send', async function handler (request, reply) { + reply.code(200).send(['']) +}) +expectError(server.get('get-invalid-http-codes-reply-error', async function handler (request, reply) { + reply.code(200).send('') +})) +server.get('get-invalid-http-codes-reply-error', async function handler (request, reply) { + reply.code(200).send({ + '1xx': 0, + 200: '', + 999: false + }) +}) + +/* eslint-disable @typescript-eslint/no-unused-vars */ +const httpHeaderHandler: RouteHandlerMethod = function (_request, reply) { + // accept is a header provided by @types/node + reply.getHeader('accept') + /* eslint-disable @typescript-eslint/no-unused-expressions */ + reply.getHeaders().accept + reply.hasHeader('accept') + reply.header('accept', 'test') + reply.headers({ accept: 'test' }) + reply.removeHeader('accept') + + // x-fastify-test is not a header provided by @types/node + // and should not result in a typing error + reply.getHeader('x-fastify-test') + reply.getHeaders()['x-fastify-test'] + reply.hasHeader('x-fastify-test') + reply.header('x-fastify-test', 'test') + reply.headers({ 'x-fastify-test': 'test' }) + reply.removeHeader('x-fastify-test') +} + +// Test: send() without arguments is valid when no Reply type is specified (default unknown) +server.get('/get-no-type-send-empty', async function handler (request, reply) { + reply.send() +}) + +// Test: send() without arguments is valid when Reply type is void +server.get('/get-void-send-empty', async function handler (request, reply) { + reply.send() +}) + +// Test: send() without arguments is valid when Reply type is undefined +server.get('/get-undefined-send-empty', async function handler (request, reply) { + reply.send() +}) + +// Issue #5534 scenario: HTTP status codes with 204 No Content +server.get('/get-http-codes-no-content', async function handler (request, reply) { + // 204 No Content - send() without payload is valid because Reply is undefined + reply.code(204).send() + // 201 Created - send() requires payload + reply.code(201).send({ id: '123' }) +}) +// 201 Created without payload should error +expectError(server.get('/get-http-codes-201-missing-payload', async function handler (request, reply) { + reply.code(201).send() +})) diff --git a/services/slides/node_modules/fastify/test/types/request.test-d.ts b/services/slides/node_modules/fastify/test/types/request.test-d.ts new file mode 100644 index 0000000000000000000000000000000000000000..3a286f48d1c589e3e8aa1bed1531e01e830bf8ed --- /dev/null +++ b/services/slides/node_modules/fastify/test/types/request.test-d.ts @@ -0,0 +1,188 @@ +import { expectAssignable, expectError, expectType } from 'tsd' +import fastify, { + ContextConfigDefault, + FastifyContextConfig, + FastifyLogFn, + FastifySchema, + FastifyTypeProviderDefault, + RawReplyDefaultExpression, + RawRequestDefaultExpression, + RawServerDefault, + RequestBodyDefault, + RequestGenericInterface, + RouteHandler, + RouteHandlerMethod, + SafePromiseLike +} from '../../fastify' +import { FastifyInstance } from '../../types/instance' +import { FastifyBaseLogger } from '../../types/logger' +import { FastifyReply } from '../../types/reply' +import { FastifyRequest, RequestRouteOptions } from '../../types/request' +import { FastifyRouteConfig, RouteGenericInterface } from '../../types/route' +import { RequestHeadersDefault, RequestParamsDefault, RequestQuerystringDefault } from '../../types/utils' + +interface RequestBody { + content: string; +} + +interface RequestQuerystring { + from: string; +} + +interface RequestParams { + id: number; +} + +interface RequestHeaders { + 'x-foobar': string; +} + +interface RequestData extends RequestGenericInterface { + Body: RequestBody; + Querystring: RequestQuerystring; + Params: RequestParams; + Headers: RequestHeaders; +} + +type Handler = RouteHandler + +type CustomRequest = FastifyRequest<{ + Body: RequestBody | undefined; + Querystring: RequestQuerystring; + Params: RequestParams; + Headers: RequestHeaders; +}> + +type HTTPRequestPart = 'body' | 'query' | 'querystring' | 'params' | 'headers' +type ExpectedGetValidationFunction = (input: { [key: string]: unknown }) => boolean + +interface CustomLoggerInterface extends FastifyBaseLogger { + foo: FastifyLogFn; // custom severity logger method +} + +const getHandler: RouteHandler = function (request, _reply) { + expectType(request.url) + expectType(request.originalUrl) + expectType(request.method) + expectType>(request.routeOptions) + expectType(request.is404) + expectType(request.hostname) + expectType(request.host) + expectType(request.port) + expectType(request.ip) + expectType(request.ips) + expectType(request.raw) + expectType(request.body) + expectType(request.params) + expectType(request.routeOptions.config) + expectType(request.routeOptions.schema) + expectType(request.routeOptions.handler) + expectType(request.routeOptions.url) + expectType(request.routeOptions.version) + + expectType(request.headers) + request.headers = {} + + expectType(request.query) + expectType(request.id) + expectType(request.log) + expectType(request.socket) + expectType(request.signal) + expectType(request.validationError) + expectType(request.server) + expectAssignable<(httpPart: HTTPRequestPart) => ExpectedGetValidationFunction>(request.getValidationFunction) + expectAssignable<(schema: { [key: string]: unknown }) => ExpectedGetValidationFunction>(request.getValidationFunction) + expectAssignable< + (input: { [key: string]: unknown }, schema: { [key: string]: unknown }, httpPart?: HTTPRequestPart) => boolean + >(request.validateInput) + expectAssignable<(input: { [key: string]: unknown }, httpPart?: HTTPRequestPart) => boolean>(request.validateInput) + expectType(request.getDecorator('foo')) + expectType(request.setDecorator('foo', 'hello')) + expectType(request.setDecorator('foo', 'hello')) + expectError(request.setDecorator('foo', true)) +} + +const getHandlerWithCustomLogger: RouteHandlerMethod< + RawServerDefault, + RawRequestDefaultExpression, + RawReplyDefaultExpression, + RouteGenericInterface, + ContextConfigDefault, + FastifySchema, + FastifyTypeProviderDefault, + CustomLoggerInterface +> = function (request, _reply) { + expectType(request.log) +} + +const postHandler: Handler = function (request) { + expectType(request.body) + expectType(request.params) + expectType( + request.headers + ) + expectType(request.query) + expectType(request.body.content) + expectType(request.query.from) + expectType(request.params.id) + expectType(request.headers['x-foobar']) + expectType(request.server) + expectType(request.routeOptions.config) +} + +function putHandler (request: CustomRequest, reply: FastifyReply) { + expectType(request.body) + expectType(request.params) + expectType(request.headers) + expectType(request.query) + if (request.body === undefined) { + expectType(request.body) + } else { + expectType(request.body.content) + } + expectType(request.query.from) + expectType(request.params.id) + expectType(request.headers['x-foobar']) + expectType(request.server) + expectType(request.routeOptions.config) +} + +const server = fastify() +server.get('/get', getHandler) +server.post('/post', postHandler) +server.put('/put', putHandler) + +const customLogger: CustomLoggerInterface = { + level: 'info', + silent: () => { }, + info: () => { }, + warn: () => { }, + error: () => { }, + fatal: () => { }, + trace: () => { }, + debug: () => { }, + foo: () => { }, // custom severity logger method + child: () => customLogger +} + +const serverWithCustomLogger = fastify({ loggerInstance: customLogger }) +expectError< +FastifyInstance +& Promise< + FastifyInstance +> +>(serverWithCustomLogger) +expectAssignable< +FastifyInstance +& PromiseLike< + FastifyInstance +> +>(serverWithCustomLogger) +expectType< +FastifyInstance +& SafePromiseLike< + FastifyInstance +> +>(serverWithCustomLogger) + +serverWithCustomLogger.get('/get', getHandlerWithCustomLogger) diff --git a/services/slides/node_modules/fastify/test/types/route.test-d.ts b/services/slides/node_modules/fastify/test/types/route.test-d.ts new file mode 100644 index 0000000000000000000000000000000000000000..26e4ae4647fbb0ade05b0d6b4c7b7d51a4a2c75d --- /dev/null +++ b/services/slides/node_modules/fastify/test/types/route.test-d.ts @@ -0,0 +1,553 @@ +import { FastifyError } from '@fastify/error' +import * as http from 'node:http' +import { expectAssignable, expectError, expectType } from 'tsd' +import fastify, { FastifyInstance, FastifyReply, FastifyRequest, RouteHandlerMethod } from '../../fastify' +import { RequestPayload } from '../../types/hooks' +import { FindMyWayFindResult } from '../../types/instance' +import { HTTPMethods, RawServerDefault } from '../../types/utils' + +/* + * Testing Fastify HTTP Routes and Route Shorthands. + * Verifies Request and Reply types as well. + * For the route shorthand tests the argument orders are: + * - `(path, handler)` + * - `(path, options, handler)` + * - `(path, options)` + */ + +declare module '../../fastify' { + interface FastifyContextConfig { + foo: string; + bar: number; + includeMessage?: boolean; + } + + /* eslint-disable @typescript-eslint/no-unused-vars */ + interface FastifyRequest< + RouteGeneric, + RawServer, + RawRequest, + SchemaCompiler, + TypeProvider, + ContextConfig, + Logger, + RequestType + > { + message: ContextConfig extends { includeMessage: true } + ? string + : null; + } +} + +const routeHandler: RouteHandlerMethod = function (request, reply) { + expectType(this) + expectType(request) + expectType(reply) +} + +const routeHandlerWithReturnValue: RouteHandlerMethod = function (request, reply) { + expectType(this) + expectType(request) + expectType(reply) + + return reply.send() +} + +const asyncPreHandler = async (request: FastifyRequest) => { + expectType(request) +} + +fastify().get('/', { preHandler: asyncPreHandler }, async () => 'this is an example') + +fastify().get( + '/', + { config: { foo: 'bar', bar: 100, includeMessage: true } }, + (req) => { + expectType(req.message) + } +) + +fastify().get( + '/', + { config: { foo: 'bar', bar: 100, includeMessage: false } }, + (req) => { + expectType(req.message) + } +) + +type LowerCaseHTTPMethods = 'delete' | 'get' | 'head' | 'patch' | 'post' | 'put' | + 'options' | 'propfind' | 'proppatch' | 'mkcol' | 'copy' | 'move' | 'lock' | + 'unlock' | 'trace' | 'search' | 'mkcalendar' | 'report' + + ;['DELETE', 'GET', 'HEAD', 'PATCH', 'POST', 'PUT', 'OPTIONS', 'PROPFIND', + 'PROPPATCH', 'MKCOL', 'COPY', 'MOVE', 'LOCK', 'UNLOCK', 'TRACE', 'SEARCH', 'MKCALENDAR', 'REPORT' +].forEach(method => { + // route method + expectType(fastify().route({ + method: method as HTTPMethods, + url: '/', + handler: routeHandler + })) + + const lowerCaseMethod: LowerCaseHTTPMethods = method.toLowerCase() as LowerCaseHTTPMethods + + // method as method + expectType(fastify()[lowerCaseMethod]('/', routeHandler)) + expectType(fastify()[lowerCaseMethod]('/', {}, routeHandler)) + expectType(fastify()[lowerCaseMethod]('/', { handler: routeHandler })) + + expectType(fastify()[lowerCaseMethod]('/', { + handler: routeHandler, + errorHandler: (error, request, reply) => { + expectType(error) + reply.send('error') + }, + childLoggerFactory: function (logger, bindings, opts) { + return logger.child(bindings, opts) + } + })) + + interface BodyInterface { prop: string } + interface QuerystringInterface { prop: number } + interface ParamsInterface { prop: boolean } + interface HeadersInterface { prop: string } + interface RouteSpecificContextConfigType { + extra: boolean + } + interface RouteGeneric { + Body: BodyInterface; + Querystring: QuerystringInterface; + Params: ParamsInterface; + Headers: HeadersInterface; + } + + fastify()[lowerCaseMethod]('/', { config: { foo: 'bar', bar: 100, extra: true } }, (req, res) => { + expectType(req.body) + expectType(req.query) + expectType(req.params) + expectType(req.headers) + expectType(req.routeOptions.config.foo) + expectType(req.routeOptions.config.bar) + expectType(req.routeOptions.config.extra) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) + expectType(res.routeOptions.config.foo) + expectType(res.routeOptions.config.bar) + expectType(res.routeOptions.config.extra) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) + }) + + fastify().route({ + url: '/', + method: method as HTTPMethods, + config: { foo: 'bar', bar: 100 }, + prefixTrailingSlash: 'slash', + onRequest: (req, res, done) => { // these handlers are tested in `hooks.test-d.ts` + expectType(req.body) + expectType(req.query) + expectType(req.params) + expectType(req.headers) + expectType(req.routeOptions.config.foo) + expectType(req.routeOptions.config.bar) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) + expectType(res.routeOptions.config.foo) + expectType(res.routeOptions.config.bar) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) + }, + preParsing: (req, res, payload, done) => { + expectType(req.body) + expectType(req.query) + expectType(req.params) + expectType(req.headers) + expectType(req.routeOptions.config.foo) + expectType(req.routeOptions.config.bar) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) + expectType(res.routeOptions.config.foo) + expectType(res.routeOptions.config.bar) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) + expectType(payload) + expectAssignable<(err?: FastifyError | null, res?: RequestPayload) => void>(done) + expectAssignable<(err?: NodeJS.ErrnoException) => void>(done) + }, + preValidation: (req, res, done) => { + expectType(req.body) + expectType(req.query) + expectType(req.params) + expectType(req.headers) + expectType(req.routeOptions.config.foo) + expectType(req.routeOptions.config.bar) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) + expectType(res.routeOptions.config.foo) + expectType(res.routeOptions.config.bar) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) + }, + preHandler: (req, res, done) => { + expectType(req.body) + expectType(req.query) + expectType(req.params) + expectType(req.headers) + expectType(req.routeOptions.config.foo) + expectType(req.routeOptions.config.bar) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) + expectType(res.routeOptions.config.foo) + expectType(res.routeOptions.config.bar) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) + }, + onResponse: (req, res, done) => { + expectType(req.body) + expectType(req.query) + expectType(req.params) + expectType(req.headers) + expectType(req.routeOptions.config.foo) + expectType(req.routeOptions.config.bar) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) + expectType(res.routeOptions.config.foo) + expectType(res.routeOptions.config.bar) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) + expectType(res.statusCode) + }, + onError: (req, res, error, done) => { + expectType(req.body) + expectType(req.query) + expectType(req.params) + expectType(req.headers) + expectType(req.routeOptions.config.foo) + expectType(req.routeOptions.config.bar) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) + expectType(res.routeOptions.config.foo) + expectType(res.routeOptions.config.bar) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) + }, + preSerialization: (req, res, payload, done) => { + expectType(req.body) + expectType(req.query) + expectType(req.params) + expectType(req.headers) + expectType(req.routeOptions.config.foo) + expectType(req.routeOptions.config.bar) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) + expectType(res.routeOptions.config.foo) + expectType(res.routeOptions.config.bar) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) + }, + onSend: (req, res, payload, done) => { + expectType(req.body) + expectType(req.query) + expectType(req.params) + expectType(req.headers) + expectType(req.routeOptions.config.foo) + expectType(req.routeOptions.config.bar) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) + expectType(res.routeOptions.config.foo) + expectType(res.routeOptions.config.bar) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) + }, + handler: (req, res) => { + expectType(req.body) + expectType(req.query) + expectType(req.params) + expectType(req.headers) + expectType(req.routeOptions.config.foo) + expectType(req.routeOptions.config.bar) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) + expectType(res.routeOptions.config.foo) + expectType(res.routeOptions.config.bar) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) + } + }) + + fastify().route({ + url: '/', + method: method as HTTPMethods, + config: { foo: 'bar', bar: 100 }, + prefixTrailingSlash: 'slash', + onRequest: async (req, res, done) => { // these handlers are tested in `hooks.test-d.ts` + expectType(req.body) + expectType(req.query) + expectType(req.params) + expectType(req.headers) + expectType(req.routeOptions.config.foo) + expectType(req.routeOptions.config.bar) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) + expectType(res.routeOptions.config.foo) + expectType(res.routeOptions.config.bar) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) + }, + preParsing: async (req, res, payload, done) => { + expectType(req.body) + expectType(req.query) + expectType(req.params) + expectType(req.headers) + expectType(req.routeOptions.config.foo) + expectType(req.routeOptions.config.bar) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) + expectType(res.routeOptions.config.foo) + expectType(res.routeOptions.config.bar) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) + expectType(payload) + expectAssignable<(err?: FastifyError | null, res?: RequestPayload) => void>(done) + expectAssignable<(err?: NodeJS.ErrnoException) => void>(done) + }, + preValidation: async (req, res, done) => { + expectType(req.body) + expectType(req.query) + expectType(req.params) + expectType(req.headers) + expectType(req.routeOptions.config.foo) + expectType(req.routeOptions.config.bar) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) + expectType(res.routeOptions.config.foo) + expectType(res.routeOptions.config.bar) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) + }, + preHandler: async (req, res, done) => { + expectType(req.body) + expectType(req.query) + expectType(req.params) + expectType(req.headers) + expectType(req.routeOptions.config.foo) + expectType(req.routeOptions.config.bar) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) + expectType(res.routeOptions.config.foo) + expectType(res.routeOptions.config.bar) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) + }, + onResponse: async (req, res, done) => { + expectType(req.body) + expectType(req.query) + expectType(req.params) + expectType(req.headers) + expectType(req.routeOptions.config.foo) + expectType(req.routeOptions.config.bar) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) + expectType(res.routeOptions.config.foo) + expectType(res.routeOptions.config.bar) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) + expectType(res.statusCode) + }, + onError: async (req, res, error, done) => { + expectType(req.body) + expectType(req.query) + expectType(req.params) + expectType(req.headers) + expectType(req.routeOptions.config.foo) + expectType(req.routeOptions.config.bar) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) + expectType(res.routeOptions.config.foo) + expectType(res.routeOptions.config.bar) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) + }, + preSerialization: async (req, res, payload, done) => { + expectType(req.body) + expectType(req.query) + expectType(req.params) + expectType(req.headers) + expectType(req.routeOptions.config.foo) + expectType(req.routeOptions.config.bar) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) + expectType(res.routeOptions.config.foo) + expectType(res.routeOptions.config.bar) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) + }, + onSend: async (req, res, payload, done) => { + expectType(req.body) + expectType(req.query) + expectType(req.params) + expectType(req.headers) + expectType(req.routeOptions.config.foo) + expectType(req.routeOptions.config.bar) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) + expectType(res.routeOptions.config.foo) + expectType(res.routeOptions.config.bar) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) + }, + handler: (req, res) => { + expectType(req.body) + expectType(req.query) + expectType(req.params) + expectType(req.headers) + expectType(req.routeOptions.config.foo) + expectType(req.routeOptions.config.bar) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) + expectType(res.routeOptions.config.foo) + expectType(res.routeOptions.config.bar) + expectType(req.routeOptions.config.url) + expectType(req.routeOptions.config.method) + } + }) +}) + +expectType(fastify().route({ + url: '/', + method: 'CONNECT', // not a valid method but could be implemented by the user + handler: routeHandler +})) + +expectType(fastify().route({ + url: '/', + method: 'OPTIONS', + handler: routeHandler +})) + +expectType(fastify().route({ + url: '/', + method: 'OPTION', // OPTION is a typo for OPTIONS + handler: routeHandler +})) + +expectType(fastify().route({ + url: '/', + method: ['GET', 'POST'], + handler: routeHandler +})) + +expectType(fastify().route({ + url: '/', + method: ['GET', 'POST', 'OPTION'], // OPTION is a typo for OPTIONS + handler: routeHandler +})) + +expectError(fastify().route({ + url: '/', + method: 'GET', + handler: routeHandler, + schemaErrorFormatter: 500 // Not a valid formatter +})) + +expectType(fastify().route({ + url: '/', + method: 'GET', + handler: routeHandler, + schemaErrorFormatter: (errors, dataVar) => new Error('') +})) + +expectError(fastify().route({ + prefixTrailingSlash: true // Not a valid value +})) + +expectType(fastify().route({ + url: '/', + method: 'GET', + handler: routeHandlerWithReturnValue +})) + +expectType(fastify().hasRoute({ + url: '/', + method: 'GET' +})) + +expectType(fastify().hasRoute({ + url: '/', + method: 'GET', + constraints: { version: '1.2.0' } +})) + +expectType(fastify().hasRoute({ + url: '/', + method: 'GET', + constraints: { host: 'auth.fastify.test' } +})) + +expectType(fastify().hasRoute({ + url: '/', + method: 'GET', + constraints: { host: /.*\.fastify\.test$/ } +})) + +expectType(fastify().hasRoute({ + url: '/', + method: 'GET', + constraints: { host: /.*\.fastify\.test$/, version: '1.2.3' } +})) + +expectType(fastify().hasRoute({ + url: '/', + method: 'GET', + constraints: { + // constraints value should accept any value + number: 12, + date: new Date(), + boolean: true, + function: () => { }, + object: { foo: 'bar' } + } +})) + +expectType, 'store'>>( + fastify().findRoute({ + url: '/', + method: 'get' + }) +) + +// we should not expose store +expectError(fastify().findRoute({ + url: '/', + method: 'get' +}).store) + +expectType(fastify().route({ + url: '/', + method: 'get', + handler: routeHandlerWithReturnValue +})) + +expectType(fastify().route({ + url: '/', + method: ['put', 'patch'], + handler: routeHandlerWithReturnValue +})) + +expectType(fastify().route({ + url: '/', + method: 'GET', + handler: (req) => { + expectType(req.routeOptions.method) + expectAssignable>(req.routeOptions.method) + } +})) + +expectType(fastify().route({ + url: '/', + method: ['HEAD', 'GET'], + handler: (req) => { + expectType(req.routeOptions.method) + expectAssignable>(req.routeOptions.method) + } +})) diff --git a/services/slides/node_modules/fastify/test/types/schema.test-d.ts b/services/slides/node_modules/fastify/test/types/schema.test-d.ts new file mode 100644 index 0000000000000000000000000000000000000000..5d3934d6fe25b009fbdd469227e8bc57b1318240 --- /dev/null +++ b/services/slides/node_modules/fastify/test/types/schema.test-d.ts @@ -0,0 +1,135 @@ +import { StandaloneValidator } from '@fastify/ajv-compiler' +import { StandaloneSerializer } from '@fastify/fast-json-stringify-compiler' +import Ajv from 'ajv' +import { expectAssignable } from 'tsd' +import fastify, { FastifyInstance, FastifySchema } from '../../fastify' + +const server = fastify() + +expectAssignable(server.get( + '/full-schema', + { + schema: { + body: { type: 'null' }, + querystring: { type: 'null' }, + params: { type: 'null' }, + headers: { type: 'null' }, + response: { type: 'null' } + } + }, + () => { } +)) + +expectAssignable(server.post( + '/multiple-content-schema', + { + schema: { + body: { + content: { + 'application/json': { + schema: { type: 'object' } + }, + 'text/plain': { + schema: { type: 'string' } + } + } + } + } + }, + () => { } +)) + +expectAssignable(server.get( + '/empty-schema', + { + schema: {} + }, + () => { } +)) + +expectAssignable(server.get( + '/no-schema', + {}, + () => { } +)) + +expectAssignable(server.setValidatorCompiler(({ schema }) => { + return new Ajv().compile(schema) +})) + +expectAssignable(server.setSerializerCompiler(() => { + return data => JSON.stringify(data) +})) + +expectAssignable(server.post('/test', { + validatorCompiler: ({ schema }) => { + return data => { + if (!data || data.constructor !== Object) { + return { error: new Error('value is not an object') } + } + return { value: data } + } + } +}, async req => req.body)) + +expectAssignable(server.post('/test', { + validatorCompiler: ({ schema }) => { + return data => { + if (!data || data.constructor !== Object) { + return { + error: [ + { + keyword: 'type', + instancePath: '', + schemaPath: '#/type', + params: { type: 'object' }, + message: 'value is not an object' + } + ] + } + } + return { value: data } + } + } +}, async req => req.body)) + +expectAssignable(server.setValidatorCompiler }>( + function ({ schema }) { + return new Ajv().compile(schema) + } +)) + +expectAssignable(server.setSerializerCompiler( + () => data => JSON.stringify(data) +)) + +// https://github.com/fastify/ajv-compiler/issues/95 +{ + const factory = StandaloneValidator({ + readMode: false, + storeFunction (routeOpts, schemaValidationCode) { } + }) + + fastify({ + schemaController: { + compilersFactory: { + buildValidator: factory + } + } + }) +} + +{ + const factory = StandaloneSerializer({ + readMode: false, + storeFunction (routeOpts, schemaValidationCode) { } + }) + + fastify({ + schemaController: { + compilersFactory: { + buildSerializer: factory + } + } + }) +} diff --git a/services/slides/node_modules/fastify/test/types/serverFactory.test-d.ts b/services/slides/node_modules/fastify/test/types/serverFactory.test-d.ts new file mode 100644 index 0000000000000000000000000000000000000000..ecb04b5f61cff2d32d45d4f6b3b67436c99a3a13 --- /dev/null +++ b/services/slides/node_modules/fastify/test/types/serverFactory.test-d.ts @@ -0,0 +1,37 @@ +import fastify, { FastifyServerFactory } from '../../fastify' +import * as http from 'node:http' +import { expectType } from 'tsd' + +// Custom Server +type CustomType = void +interface CustomIncomingMessage extends http.IncomingMessage { + fakeMethod?: () => CustomType; +} + +interface CustomServerResponse extends http.ServerResponse { + fakeMethod?: () => CustomType; +} + +const serverFactory: FastifyServerFactory = (handler, opts) => { + const server = http.createServer((req: CustomIncomingMessage, res: CustomServerResponse) => { + req.fakeMethod = () => {} + res.fakeMethod = () => {} + + handler(req, res) + }) + + return server +} + +// The request and reply objects should have the fakeMethods available (even though they may be undefined) +const customServer = fastify({ serverFactory }) + +customServer.get('/', function (request, reply) { + if (request.raw.fakeMethod) { + expectType(request.raw.fakeMethod()) + } + + if (reply.raw.fakeMethod) { + expectType(reply.raw.fakeMethod()) + } +}) diff --git a/services/slides/node_modules/fastify/test/types/type-provider.test-d.ts b/services/slides/node_modules/fastify/test/types/type-provider.test-d.ts new file mode 100644 index 0000000000000000000000000000000000000000..6cf043592258b26624c8b7d4357ae56f1ba77732 --- /dev/null +++ b/services/slides/node_modules/fastify/test/types/type-provider.test-d.ts @@ -0,0 +1,1213 @@ +import fastify, { + FastifyTypeProvider, + HookHandlerDoneFunction, + FastifyRequest, + FastifyReply, + FastifyInstance, + FastifyError, + SafePromiseLike +} from '../../fastify' +import { expectAssignable, expectError, expectType } from 'tsd' +import { IncomingHttpHeaders } from 'node:http' +import { Type, TSchema, Static } from 'typebox' +import { FromSchema, JSONSchema } from 'json-schema-to-ts' + +const server = fastify() + +// ------------------------------------------------------------------- +// Default (unknown) +// ------------------------------------------------------------------- + +expectAssignable(server.get('/', (req) => expectType(req.body))) + +// ------------------------------------------------------------------- +// Remapping +// ------------------------------------------------------------------- + +interface NumberProvider extends FastifyTypeProvider { + validator: number + serializer: number +} // remap all schemas to numbers + +expectAssignable(server.withTypeProvider().get( + '/', + { + schema: { + body: { type: 'string' }, + querystring: { type: 'string' }, + headers: { type: 'string' }, + params: { type: 'string' } + } + }, + (req) => { + expectType(req.headers) + expectType(req.body) + expectType(req.query) + expectType(req.params) + } +)) + +// ------------------------------------------------------------------- +// Override +// ------------------------------------------------------------------- + +interface OverriddenProvider extends FastifyTypeProvider { validator: 'inferenced' } + +expectAssignable(server.withTypeProvider().get<{ Body: 'override' }>( + '/', + { + schema: { + body: Type.Object({ + x: Type.Number(), + y: Type.Number(), + z: Type.Number() + }) + } + }, + (req) => { + expectType<'override'>(req.body) + } +)) + +// ------------------------------------------------------------------- +// TypeBox +// ------------------------------------------------------------------- + +interface TypeBoxProvider extends FastifyTypeProvider { + validator: this['schema'] extends TSchema ? Static : unknown + serializer: this['schema'] extends TSchema ? Static : unknown +} + +expectAssignable(server.withTypeProvider().get( + '/', + { + schema: { + body: Type.Object({ + x: Type.Number(), + y: Type.Number(), + z: Type.Number() + }) + }, + errorHandler: (error, request, reply) => { + expectType(error) + expectAssignable(request) + expectType(request.body.x) + expectType(request.body.y) + expectType(request.body.z) + expectAssignable(reply) + } + }, + (req) => { + expectType(req.body.x) + expectType(req.body.y) + expectType(req.body.z) + } +)) + +expectAssignable(server.withTypeProvider()) + +// ------------------------------------------------------------------- +// JsonSchemaToTs +// ------------------------------------------------------------------- + +interface JsonSchemaToTsProvider extends FastifyTypeProvider { + validator: this['schema'] extends JSONSchema ? FromSchema : unknown + serializer: this['schema'] extends JSONSchema ? FromSchema : unknown +} + +// explicitly setting schema `as const` + +expectAssignable(server.withTypeProvider().get( + '/', + { + schema: { + body: { + type: 'object', + properties: { + x: { type: 'number' }, + y: { type: 'string' }, + z: { type: 'boolean' } + } + } as const + }, + errorHandler: (error, request, reply) => { + expectType(error) + expectAssignable(request) + expectType(request.body.x) + expectType(request.body.y) + expectType(request.body.z) + expectAssignable(reply) + } + }, + (req) => { + expectType(req.body.x) + expectType(req.body.y) + expectType(req.body.z) + } +)) + +expectAssignable(server.withTypeProvider().route({ + url: '/', + method: 'POST', + schema: { + body: { + type: 'object', + properties: { + x: { type: 'number' }, + y: { type: 'string' }, + z: { type: 'boolean' } + } + } + } as const, + errorHandler: (error, request, reply) => { + expectType(error) + expectAssignable(request) + expectType(request.body.x) + expectType(request.body.y) + expectType(request.body.z) + expectAssignable(reply) + }, + handler: (req) => { + expectType(req.body.x) + expectType(req.body.y) + expectType(req.body.z) + } +})) + +// inferring schema `as const` + +expectAssignable(server.withTypeProvider().get( + '/', + { + schema: { + body: { + type: 'object', + properties: { + x: { type: 'number' }, + y: { type: 'string' }, + z: { type: 'boolean' } + } + } + }, + errorHandler: (error, request, reply) => { + expectType(error) + expectAssignable(request) + expectType(request.body.x) + expectType(request.body.y) + expectType(request.body.z) + expectAssignable(reply) + } + }, + (req) => { + expectType(req.body.x) + expectType(req.body.y) + expectType(req.body.z) + } +)) + +expectAssignable(server.withTypeProvider().route({ + url: '/', + method: 'POST', + schema: { + body: { + type: 'object', + properties: { + x: { type: 'number' }, + y: { type: 'string' }, + z: { type: 'boolean' } + } + } + }, + errorHandler: (error, request, reply) => { + expectType(error) + expectAssignable(request) + expectType(request.body.x) + expectType(request.body.y) + expectType(request.body.z) + expectAssignable(reply) + }, + handler: (req) => { + expectType(req.body.x) + expectType(req.body.y) + expectType(req.body.z) + } +})) + +expectAssignable(server.withTypeProvider()) + +// ------------------------------------------------------------------- +// Instance Type Remappable +// ------------------------------------------------------------------- + +expectAssignable(server.withTypeProvider().withTypeProvider().get( + '/', + { + schema: { + body: { + type: 'object', + properties: { + x: { type: 'number' }, + y: { type: 'string' }, + z: { type: 'boolean' } + } + } as const + }, + errorHandler: (error, request, reply) => { + expectType(error) + expectAssignable(request) + expectType(request.body.x) + expectType(request.body.y) + expectType(request.body.z) + expectAssignable(reply) + } + }, + (req) => { + expectType(req.body.x) + expectType(req.body.y) + expectType(req.body.z) + } +)) + +// ------------------------------------------------------------------- +// Request Hooks +// ------------------------------------------------------------------- + +// Sync handlers + +expectAssignable(server.withTypeProvider().get( + '/', + { + schema: { + body: Type.Object({ + x: Type.Number(), + y: Type.String(), + z: Type.Boolean() + }) + }, + preHandler: (req, reply, done) => { + expectType(req.body.x) + expectType(req.body.y) + expectType(req.body.z) + }, + preParsing: (req, reply, payload, done) => { + expectType(req.body.x) + expectType(req.body.y) + expectType(req.body.z) + }, + preSerialization: (req, reply, payload, done) => { + expectType(req.body.x) + expectType(req.body.y) + expectType(req.body.z) + }, + preValidation: (req, reply, done) => { + expectType(req.body.x) + expectType(req.body.y) + expectType(req.body.z) + }, + onError: (req, reply, error, done) => { + expectType(req.body.x) + expectType(req.body.y) + expectType(req.body.z) + }, + onRequest: (req, reply, done) => { + expectType(req.body.x) + expectType(req.body.y) + expectType(req.body.z) + }, + onResponse: (req, reply, done) => { + expectType(req.body.x) + expectType(req.body.y) + expectType(req.body.z) + }, + onTimeout: (req, reply, done) => { + expectType(req.body.x) + expectType(req.body.y) + expectType(req.body.z) + }, + onSend: (req, reply, payload, done) => { + expectType(req.body.x) + expectType(req.body.y) + expectType(req.body.z) + } + }, + req => { + expectType(req.body.x) + expectType(req.body.y) + expectType(req.body.z) + } +)) + +// Async handlers + +expectAssignable(server.withTypeProvider().get( + '/', + { + schema: { + body: Type.Object({ + x: Type.Number(), + y: Type.String(), + z: Type.Boolean() + }) + }, + preHandler: async (req, reply, done) => { + expectType(req.body.x) + expectType(req.body.y) + expectType(req.body.z) + }, + preParsing: async (req, reply, payload, done) => { + expectType(req.body.x) + expectType(req.body.y) + expectType(req.body.z) + }, + preSerialization: async (req, reply, payload, done) => { + expectType(req.body.x) + expectType(req.body.y) + expectType(req.body.z) + }, + preValidation: async (req, reply, done) => { + expectType(req.body.x) + expectType(req.body.y) + expectType(req.body.z) + }, + onError: async (req, reply, error, done) => { + expectType(req.body.x) + expectType(req.body.y) + expectType(req.body.z) + }, + onRequest: async (req, reply, done) => { + expectType(req.body.x) + expectType(req.body.y) + expectType(req.body.z) + }, + onResponse: async (req, reply, done) => { + expectType(req.body.x) + expectType(req.body.y) + expectType(req.body.z) + }, + onTimeout: async (req, reply, done) => { + expectType(req.body.x) + expectType(req.body.y) + expectType(req.body.z) + }, + onSend: async (req, reply, payload, done) => { + expectType(req.body.x) + expectType(req.body.y) + expectType(req.body.z) + } + }, + req => { + expectType(req.body.x) + expectType(req.body.y) + expectType(req.body.z) + } +)) + +// ------------------------------------------------------------------- +// Request headers +// ------------------------------------------------------------------- + +// JsonSchemaToTsProvider +expectAssignable(server.withTypeProvider().get( + '/', + { + schema: { + headers: { + type: 'object', + properties: { + lowercase: { type: 'string' }, + UPPERCASE: { type: 'number' }, + camelCase: { type: 'boolean' }, + 'KEBAB-case': { type: 'boolean' }, + PRESERVE_OPTIONAL: { type: 'number' } + }, + required: ['lowercase', 'UPPERCASE', 'camelCase', 'KEBAB-case'] + } as const + } + }, + (req) => { + expectType(req.headers.lowercase) + expectType(req.headers.UPPERCASE) + expectType(req.headers.uppercase) + expectType(req.headers.camelcase) + expectType(req.headers['kebab-case']) + expectType(req.headers.preserve_optional) + } +)) + +// TypeBoxProvider +expectAssignable(server.withTypeProvider().get( + '/', + { + schema: { + headers: Type.Object({ + lowercase: Type.String(), + UPPERCASE: Type.Number(), + camelCase: Type.Boolean(), + 'KEBAB-case': Type.Boolean(), + PRESERVE_OPTIONAL: Type.Optional(Type.Number()) + }) + } + }, + (req) => { + expectType(req.headers.lowercase) + expectType(req.headers.UPPERCASE) + expectType(req.headers.uppercase) + expectType(req.headers.camelcase) + expectType(req.headers['kebab-case']) + expectType(req.headers.preserve_optional) + } +)) + +// ------------------------------------------------------------------- +// TypeBox Reply Type +// ------------------------------------------------------------------- + +expectAssignable(server.withTypeProvider().get( + '/', + { + schema: { + response: { + 200: Type.String(), + 400: Type.Number(), + 500: Type.Object({ + error: Type.String() + }) + } + } + }, + async (_, res) => { + res.send('hello') + res.send(42) + res.send({ error: 'error' }) + expectType<((...args: [payload: string]) => typeof res)>(res.code(200).send) + expectType<((...args: [payload: number]) => typeof res)>(res.code(400).send) + expectType<((...args: [payload: { error: string }]) => typeof res)>(res.code(500).send) + expectError<(payload?: unknown) => typeof res>(res.code(200).send) + } +)) + +// ------------------------------------------------------------------- +// TypeBox Reply Type (Different Content-types) +// ------------------------------------------------------------------- + +expectAssignable(server.withTypeProvider().get( + '/', + { + schema: { + response: { + 200: { + content: { + 'text/string': { + schema: Type.String() + }, + 'application/json': { + schema: Type.Object({ + msg: Type.String() + }) + } + } + }, + 500: Type.Object({ + error: Type.String() + }) + } + } + }, + async (_, res) => { + res.send('hello') + res.send({ msg: 'hello' }) + res.send({ error: 'error' }) + } +)) + +// ------------------------------------------------------------------- +// TypeBox Reply Type: Non Assignable +// ------------------------------------------------------------------- + +expectError(server.withTypeProvider().get( + '/', + { + schema: { + response: { + 200: Type.String(), + 400: Type.Number(), + 500: Type.Object({ + error: Type.String() + }) + } + } + }, + async (_, res) => { + res.send(false) + } +)) + +// ------------------------------------------------------------------- +// TypeBox Reply Type: Non Assignable (Different Content-types) +// ------------------------------------------------------------------- + +expectError(server.withTypeProvider().get( + '/', + { + schema: { + response: { + 200: { + content: { + 'text/string': { + schema: Type.String() + }, + 'application/json': { + schema: Type.Object({ + msg: Type.String() + }) + } + } + }, + 500: Type.Object({ + error: Type.String() + }) + } + } + }, + async (_, res) => { + res.send(false) + } +)) + +// ------------------------------------------------------------------- +// TypeBox Reply Return Type +// ------------------------------------------------------------------- + +expectAssignable(server.withTypeProvider().get( + '/', + { + schema: { + response: { + 200: Type.String(), + 400: Type.Number(), + 500: Type.Object({ + error: Type.String() + }) + } + } + }, + async (_, res) => { + const option = 1 as 1 | 2 | 3 + switch (option) { + case 1: return 'hello' + case 2: return 42 + case 3: return { error: 'error' } + } + } +)) + +// ------------------------------------------------------------------- +// TypeBox Reply Return Type (Different Content-types) +// ------------------------------------------------------------------- + +expectAssignable(server.withTypeProvider().get( + '/', + { + schema: { + response: { + 200: { + content: { + 'text/string': { + schema: Type.String() + }, + 'application/json': { + schema: Type.Object({ + msg: Type.String() + }) + } + } + }, + 500: Type.Object({ + error: Type.String() + }) + } + } + }, + async (_, res) => { + const option = 1 as 1 | 2 | 3 + switch (option) { + case 1: return 'hello' + case 2: return { msg: 'hello' } + case 3: return { error: 'error' } + } + } +)) + +// ------------------------------------------------------------------- +// TypeBox Reply Return Type: Non Assignable +// ------------------------------------------------------------------- + +expectError(server.withTypeProvider().get( + '/', + { + schema: { + response: { + 200: Type.String(), + 400: Type.Number(), + 500: Type.Object({ + error: Type.String() + }) + } + } + }, + async (_, res) => { + return false + } +)) + +// ------------------------------------------------------------------- +// TypeBox Reply Return Type: Non Assignable (Different Content-types) +// ------------------------------------------------------------------- + +expectError(server.withTypeProvider().get( + '/', + { + schema: { + response: { + 200: { + content: { + 'text/string': { + schema: Type.String() + }, + 'application/json': { + schema: Type.Object({ + msg: Type.String() + }) + } + } + }, + 500: Type.Object({ + error: Type.String() + }) + } + } + }, + async (_, res) => { + return false + } +)) + +// ------------------------------------------------------------------- +// JsonSchemaToTs Reply Type +// ------------------------------------------------------------------- + +expectAssignable(server.withTypeProvider().get( + '/', + { + schema: { + response: { + 200: { type: 'string' }, + 400: { type: 'number' }, + 500: { type: 'object', properties: { error: { type: 'string' } } } + } as const + } + }, + (_, res) => { + res.send('hello') + res.send(42) + res.send({ error: 'error' }) + expectType<((...args: [payload: string]) => typeof res)>(res.code(200).send) + expectType<((...args: [payload: number]) => typeof res)>(res.code(400).send) + expectType<((...args: [payload: { [x: string]: unknown; error?: string }]) => typeof res)>(res.code(500).send) + expectError<(payload?: unknown) => typeof res>(res.code(200).send) + } +)) + +// ------------------------------------------------------------------- +// JsonSchemaToTs Reply Type (Different Content-types) +// ------------------------------------------------------------------- + +expectAssignable(server.withTypeProvider().get( + '/', + { + schema: { + response: { + 200: { + content: { + 'text/string': { + schema: { type: 'string' } + }, + 'application/json': { + schema: { type: 'object', properties: { msg: { type: 'string' } } } + } + } + }, + 500: { type: 'object', properties: { error: { type: 'string' } } } + } as const + } + }, + (_, res) => { + res.send('hello') + res.send({ msg: 'hello' }) + res.send({ error: 'error' }) + } +)) + +// ------------------------------------------------------------------- +// JsonSchemaToTs Reply Type: Non Assignable +// ------------------------------------------------------------------- + +expectError(server.withTypeProvider().get( + '/', + { + schema: { + response: { + 200: { type: 'string' }, + 400: { type: 'number' }, + 500: { type: 'object', properties: { error: { type: 'string' } } } + } as const + } + }, + async (_, res) => { + res.send(false) + } +)) + +// ------------------------------------------------------------------- +// JsonSchemaToTs Reply Type: Non Assignable (Different Content-types) +// ------------------------------------------------------------------- + +expectError(server.withTypeProvider().get( + '/', + { + schema: { + response: { + 200: { + content: { + 'text/string': { + schema: { type: 'string' } + }, + 'application/json': { + schema: { type: 'object', properties: { msg: { type: 'string' } } } + } + } + }, + 500: { type: 'object', properties: { error: { type: 'string' } } } + } as const + } + }, + async (_, res) => { + res.send(false) + } +)) + +// ------------------------------------------------------------------- +// JsonSchemaToTs Reply Type Return +// ------------------------------------------------------------------- + +expectAssignable(server.withTypeProvider().get( + '/', + { + schema: { + response: { + 200: { type: 'string' }, + 400: { type: 'number' }, + 500: { type: 'object', properties: { error: { type: 'string' } } } + } as const + } + }, + async (_, res) => { + const option = 1 as 1 | 2 | 3 + switch (option) { + case 1: return 'hello' + case 2: return 42 + case 3: return { error: 'error' } + } + } +)) + +// ------------------------------------------------------------------- +// JsonSchemaToTs Reply Type Return (Different Content-types) +// ------------------------------------------------------------------- + +expectAssignable(server.withTypeProvider().get( + '/', + { + schema: { + response: { + 200: { + content: { + 'text/string': { + schema: { type: 'string' } + }, + 'application/json': { + schema: { type: 'object', properties: { msg: { type: 'string' } } } + } + } + }, + 500: { type: 'object', properties: { error: { type: 'string' } } } + } as const + } + }, + async (_, res) => { + const option = 1 as 1 | 2 | 3 + switch (option) { + case 1: return 'hello' + case 2: return { msg: 'hello' } + case 3: return { error: 'error' } + } + } +)) + +// ------------------------------------------------------------------- +// JsonSchemaToTs Reply Type Return: Non Assignable +// ------------------------------------------------------------------- + +expectError(server.withTypeProvider().get( + '/', + { + schema: { + response: { + 200: { type: 'string' }, + 400: { type: 'number' }, + 500: { type: 'object', properties: { error: { type: 'string' } } } + } as const + } + }, + async (_, res) => { + return false + } +)) + +// https://github.com/fastify/fastify/issues/4088 +expectError(server.withTypeProvider().get('/', { + schema: { + response: { + 200: { type: 'string' } + } + } as const +}, (_, res) => { + return { foo: 555 } +})) + +// ------------------------------------------------------------------- +// JsonSchemaToTs Reply Type Return: Non Assignable (Different Content-types) +// ------------------------------------------------------------------- + +expectError(server.withTypeProvider().get( + '/', + { + schema: { + response: { + 200: { + content: { + 'text/string': { + schema: { type: 'string' } + }, + 'application/json': { + schema: { type: 'object', properties: { msg: { type: 'string' } } } + } + } + }, + 500: { type: 'object', properties: { error: { type: 'string' } } } + } as const + } + }, + async (_, res) => { + return false + } +)) + +// ------------------------------------------------------------------- +// Reply Type Override +// ------------------------------------------------------------------- + +expectAssignable(server.withTypeProvider().get<{ Reply: boolean }>( + '/', + { + schema: { + response: { + 200: { type: 'string' }, + 400: { type: 'number' }, + 500: { type: 'object', properties: { error: { type: 'string' } } } + } as const + } + }, + async (_, res) => { + res.send(true) + } +)) + +// ------------------------------------------------------------------- +// Reply Type Override (Different Content-types) +// ------------------------------------------------------------------- + +expectAssignable(server.withTypeProvider().get<{ Reply: boolean }>( + '/', + { + schema: { + response: { + 200: { + content: { + 'text/string': { + schema: { type: 'string' } + }, + 'application/json': { + schema: { type: 'object', properties: { msg: { type: 'string' } } } + } + } + }, + 500: { type: 'object', properties: { error: { type: 'string' } } } + } as const + } + }, + async (_, res) => { + res.send(true) + } +)) + +// ------------------------------------------------------------------- +// Reply Type Return Override +// ------------------------------------------------------------------- + +expectAssignable(server.withTypeProvider().get<{ Reply: boolean }>( + '/', + { + schema: { + response: { + 200: { type: 'string' }, + 400: { type: 'number' }, + 500: { type: 'object', properties: { error: { type: 'string' } } } + } as const + } + }, + async (_, res) => { + return true + } +)) + +// ------------------------------------------------------------------- +// Reply Type Return Override (Different Content-types) +// ------------------------------------------------------------------- + +expectAssignable(server.withTypeProvider().get<{ Reply: boolean }>( + '/', + { + schema: { + response: { + 200: { + content: { + 'text/string': { + schema: { type: 'string' } + }, + 'application/json': { + schema: { type: 'object', properties: { msg: { type: 'string' } } } + } + } + }, + 500: { type: 'object', properties: { error: { type: 'string' } } } + } as const + } + }, + async (_, res) => { + return true + } +)) + +// ------------------------------------------------------------------- +// Reply Status Code (Different Status Codes) +// ------------------------------------------------------------------- + +expectAssignable(server.withTypeProvider().get( + '/', + { + schema: { + response: { + 200: { + content: { + 'text/string': { + schema: { type: 'string' } + }, + 'application/json': { + schema: { type: 'object', properties: { msg: { type: 'string' } } } + } + } + }, + 500: { type: 'object', properties: { error: { type: 'string' } } } + } as const + } + }, + async (_, res) => { + res.code(200) + res.code(500) + expectError(() => res.code(201)) + expectError(() => res.code(400)) + } +)) + +// ------------------------------------------------------------------- +// RouteGeneric Reply Type Return (Different Status Codes) +// ------------------------------------------------------------------- + +expectAssignable(server.get<{ + Reply: { + 200: string | { msg: string } + 400: number + '5xx': { error: string } + } +}>( + '/', + async (_, res) => { + const option = 1 as 1 | 2 | 3 | 4 + switch (option) { + case 1: return 'hello' + case 2: return { msg: 'hello' } + case 3: return 400 + case 4: return { error: 'error' } + } + } +)) + +// ------------------------------------------------------------------- +// RouteGeneric Status Code (Different Status Codes) +// ------------------------------------------------------------------- + +expectAssignable(server.get<{ + Reply: { + 200: string | { msg: string } + 400: number + '5xx': { error: string } + } +}>( + '/', + async (_, res) => { + res.code(200) + res.code(400) + res.code(500) + res.code(502) + expectError(() => res.code(201)) + expectError(() => res.code(300)) + expectError(() => res.code(404)) + return 'hello' + } +)) + +// ------------------------------------------------------------------- +// RouteGeneric Reply Type Return: Non Assignable (Different Status Codes) +// ------------------------------------------------------------------- + +expectError(server.get<{ + Reply: { + 200: string | { msg: string } + 400: number + '5xx': { error: string } + } +}>( + '/', + async (_, res) => { + return true + } +)) + +// ------------------------------------------------------------------- +// FastifyPlugin: Auxiliary +// ------------------------------------------------------------------- + +interface AuxiliaryPluginProvider extends FastifyTypeProvider { validator: 'plugin-auxiliary' } + +// Auxiliary plugins may have varying server types per application. Recommendation would be to explicitly remap instance provider context within plugin if required. +function plugin (instance: T) { + expectAssignable(instance.withTypeProvider().get( + '/', + { + schema: { body: null } + }, + (req) => { + expectType<'plugin-auxiliary'>(req.body) + } + )) +} + +expectAssignable(server.withTypeProvider().register(plugin).get( + '/', + { + schema: { body: null } + }, + (req) => { + expectType<'plugin-auxiliary'>(req.body) + } +)) + +// ------------------------------------------------------------------- +// Handlers: Inline +// ------------------------------------------------------------------- + +interface InlineHandlerProvider extends FastifyTypeProvider { validator: 'handler-inline' } + +// Inline handlers should infer for the request parameters (non-shared) +expectAssignable(server.withTypeProvider().get( + '/', + { + onRequest: (req, res, done) => { + expectType<'handler-inline'>(req.body) + }, + schema: { body: null } + }, + (req) => { + expectType<'handler-inline'>(req.body) + } +)) + +// ------------------------------------------------------------------- +// Handlers: Auxiliary +// ------------------------------------------------------------------- + +interface AuxiliaryHandlerProvider extends FastifyTypeProvider { validator: 'handler-auxiliary' } + +// Auxiliary handlers are likely shared for multiple routes and thus should infer as unknown due to potential varying parameters +function auxiliaryHandler (request: FastifyRequest, reply: FastifyReply, done: HookHandlerDoneFunction): void { + expectType(request.body) +} + +expectAssignable(server.withTypeProvider().get( + '/', + { + onRequest: auxiliaryHandler, + schema: { body: 'handler-auxiliary' } + }, + (req) => { + expectType<'handler-auxiliary'>(req.body) + } +)) + +// ------------------------------------------------------------------- +// SafePromiseLike +// ------------------------------------------------------------------- +const safePromiseLike = { + then: new Promise(resolve => resolve('')).then, + __linterBrands: 'SafePromiseLike' as const +} +expectAssignable>(safePromiseLike) +expectAssignable>(safePromiseLike) +expectError>(safePromiseLike) + +// ------------------------------------------------------------------- +// Separate Providers +// ------------------------------------------------------------------- + +interface SeparateProvider extends FastifyTypeProvider { + validator: string + serializer: Date +} + +expectAssignable(server.withTypeProvider().get( + '/', + { + schema: { + body: null, + response: { + 200: { type: 'string' } + } + } + }, + (req, res) => { + expectType(req.body) + + res.send(new Date()) + } +)) diff --git a/services/slides/node_modules/fastify/test/types/using.test-d.ts b/services/slides/node_modules/fastify/test/types/using.test-d.ts new file mode 100644 index 0000000000000000000000000000000000000000..72252d47fe7d61aaf320e43f8d1da52f2f7939ce --- /dev/null +++ b/services/slides/node_modules/fastify/test/types/using.test-d.ts @@ -0,0 +1,17 @@ +import { expectAssignable } from 'tsd' +import fastify, { FastifyInstance } from '../../fastify' + +async function hasSymbolDisposeWithUsing () { + await using app = fastify() + expectAssignable(app) + expectAssignable(app.close) +} + +async function hasSymbolDispose () { + const app = fastify() + expectAssignable(app) + expectAssignable(app.close) +} + +hasSymbolDisposeWithUsing() +hasSymbolDispose() diff --git a/services/slides/node_modules/fastify/test/upgrade.test.js b/services/slides/node_modules/fastify/test/upgrade.test.js new file mode 100644 index 0000000000000000000000000000000000000000..8f1bf8c290d65efb87c150fb36874e03e7543402 --- /dev/null +++ b/services/slides/node_modules/fastify/test/upgrade.test.js @@ -0,0 +1,52 @@ +'use strict' + +const { describe, test } = require('node:test') +const Fastify = require('..') +const { connect } = require('node:net') +const { once } = require('node:events') +const dns = require('node:dns').promises + +describe('upgrade to both servers', async () => { + const localAddresses = await dns.lookup('localhost', { all: true }) + const skip = localAddresses.length === 1 && 'requires both IPv4 and IPv6' + + await test('upgrade IPv4 and IPv6', { skip }, async t => { + t.plan(2) + + const fastify = Fastify() + fastify.server.on('upgrade', (req, socket, head) => { + t.assert.ok(`upgrade event ${JSON.stringify(socket.address())}`) + socket.end() + }) + + fastify.get('/', (req, res) => { + res.send() + }) + + await fastify.listen() + t.after(() => fastify.close()) + + { + const clientIPv4 = connect(fastify.server.address().port, '127.0.0.1') + clientIPv4.write('GET / HTTP/1.1\r\n') + clientIPv4.write('Upgrade: websocket\r\n') + clientIPv4.write('Connection: Upgrade\r\n') + clientIPv4.write('Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n') + clientIPv4.write('Sec-WebSocket-Protocol: com.xxx.service.v1\r\n') + clientIPv4.write('Sec-WebSocket-Version: 13\r\n\r\n') + clientIPv4.write('\r\n\r\n') + await once(clientIPv4, 'close') + } + + { + const clientIPv6 = connect(fastify.server.address().port, '::1') + clientIPv6.write('GET / HTTP/1.1\r\n') + clientIPv6.write('Upgrade: websocket\r\n') + clientIPv6.write('Connection: Upgrade\r\n') + clientIPv6.write('Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n') + clientIPv6.write('Sec-WebSocket-Protocol: com.xxx.service.v1\r\n') + clientIPv6.write('Sec-WebSocket-Version: 13\r\n\r\n') + await once(clientIPv6, 'close') + } + }) +}) diff --git a/services/slides/node_modules/fastify/test/url-rewriting.test.js b/services/slides/node_modules/fastify/test/url-rewriting.test.js new file mode 100644 index 0000000000000000000000000000000000000000..f76aefdd762cf8a6cf15da79057297e0c78842fb --- /dev/null +++ b/services/slides/node_modules/fastify/test/url-rewriting.test.js @@ -0,0 +1,122 @@ +'use strict' + +const { test } = require('node:test') +const Fastify = require('..') + +test('Should rewrite url', async t => { + t.plan(4) + const fastify = Fastify({ + rewriteUrl (req) { + t.assert.strictEqual(req.url, '/this-would-404-without-url-rewrite') + this.log.info('rewriting url') + return '/' + } + }) + + fastify.route({ + method: 'GET', + url: '/', + handler: (req, reply) => { + reply.send({ hello: 'world' }) + } + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + + t.after(() => fastify.close()) + + const result = await fetch(`${fastifyServer}/this-would-404-without-url-rewrite`) + + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.deepStrictEqual(await result.json(), { hello: 'world' }) +}) + +test('Should not rewrite if the url is the same', async t => { + t.plan(3) + const fastify = Fastify({ + rewriteUrl (req) { + t.assert.strictEqual(req.url, '/this-would-404-without-url-rewrite') + this.log.info('rewriting url') + return req.url + } + }) + + fastify.route({ + method: 'GET', + url: '/', + handler: (req, reply) => { + reply.send({ hello: 'world' }) + } + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + + t.after(() => fastify.close()) + + const result = await fetch(`${fastifyServer}/this-would-404-without-url-rewrite`) + + t.assert.ok(!result.ok) + t.assert.strictEqual(result.status, 404) +}) + +test('Should throw an error', async t => { + t.plan(2) + const fastify = Fastify({ + rewriteUrl (req) { + t.assert.strictEqual(req.url, '/this-would-404-without-url-rewrite') + this.log.info('rewriting url') + return undefined + } + }) + + fastify.route({ + method: 'GET', + url: '/', + handler: (req, reply) => { + reply.send({ hello: 'world' }) + } + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + + t.after(() => fastify.close()) + + try { + await fetch(`${fastifyServer}/this-would-404-without-url-rewrite`) + t.assert.fail('Expected fetch to throw an error') + } catch (err) { + t.assert.ok(err instanceof Error) + } +}) + +test('Should rewrite url but keep originalUrl unchanged', async t => { + t.plan(6) + const fastify = Fastify({ + rewriteUrl (req) { + t.assert.strictEqual(req.url, '/this-would-404-without-url-rewrite') + t.assert.strictEqual(req.originalUrl, '/this-would-404-without-url-rewrite') + return '/' + } + }) + + fastify.route({ + method: 'GET', + url: '/', + handler: (req, reply) => { + reply.send({ hello: 'world', hostname: req.hostname, port: req.port }) + t.assert.strictEqual(req.originalUrl, '/this-would-404-without-url-rewrite') + } + }) + + await fastify.listen({ port: 0 }) + const port = fastify.server.address().port + + t.after(() => fastify.close()) + + const result = await fetch(`http://localhost:${port}/this-would-404-without-url-rewrite`) + + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.deepStrictEqual(await result.json(), { hello: 'world', hostname: 'localhost', port }) +}) diff --git a/services/slides/node_modules/fastify/test/use-semicolon-delimiter.test.js b/services/slides/node_modules/fastify/test/use-semicolon-delimiter.test.js new file mode 100644 index 0000000000000000000000000000000000000000..5d0506d61b160b7b7a5238f67e5456df96c0305a --- /dev/null +++ b/services/slides/node_modules/fastify/test/use-semicolon-delimiter.test.js @@ -0,0 +1,168 @@ +'use strict' + +const { test } = require('node:test') +const Fastify = require('..') + +test('use semicolon delimiter default false', async (t) => { + t.plan(2) + + const fastify = Fastify() + + fastify.get('/1234;foo=bar', (req, reply) => { + reply.send(req.query) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) + + const result = await fetch(fastifyServer + '/1234;foo=bar', { + method: 'GET' + }) + t.assert.strictEqual(result.status, 200) + const body = await result.json() + t.assert.deepStrictEqual(body, {}) +}) + +test('use semicolon delimiter set to true', async (t) => { + t.plan(3) + const fastify = Fastify({ + useSemicolonDelimiter: true + }) + + fastify.get('/1234', (req, reply) => { + reply.send(req.query) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) + + const result = await fetch(fastifyServer + '/1234;foo=bar') + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.deepStrictEqual(await result.json(), { + foo: 'bar' + }) +}) + +test('use semicolon delimiter set to false', async (t) => { + t.plan(3) + + const fastify = Fastify({ + useSemicolonDelimiter: false + }) + + fastify.get('/1234;foo=bar', (req, reply) => { + reply.send(req.query) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) + + const result = await fetch(fastifyServer + '/1234;foo=bar') + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.deepStrictEqual(await result.json(), {}) +}) + +test('use semicolon delimiter set to false return 404', async (t) => { + t.plan(2) + + const fastify = Fastify({ + useSemicolonDelimiter: false + }) + + fastify.get('/1234', (req, reply) => { + reply.send(req.query) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) + + const result = await fetch(fastifyServer + '/1234;foo=bar') + t.assert.ok(!result.ok) + t.assert.strictEqual(result.status, 404) +}) + +test('use routerOptions semicolon delimiter default false', async t => { + t.plan(3) + + const fastify = Fastify() + + fastify.get('/1234;foo=bar', (req, reply) => { + reply.send(req.query) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) + + const result = await fetch(fastifyServer + '/1234;foo=bar') + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.deepStrictEqual(await result.json(), {}) +}) + +test('use routerOptions semicolon delimiter set to true', async t => { + t.plan(3) + const fastify = Fastify({ + routerOptions: { + useSemicolonDelimiter: true + } + }) + + fastify.get('/1234', async (req, reply) => { + reply.send(req.query) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) + + const result = await fetch(fastifyServer + '/1234;foo=bar') + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.deepStrictEqual(await result.json(), { + foo: 'bar' + }) +}) + +test('use routerOptions semicolon delimiter set to false', async t => { + t.plan(3) + + const fastify = Fastify({ + routerOptions: { + useSemicolonDelimiter: false + } + }) + + fastify.get('/1234;foo=bar', (req, reply) => { + reply.send(req.query) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) + + const result = await fetch(fastifyServer + '/1234;foo=bar') + t.assert.ok(result.ok) + t.assert.strictEqual(result.status, 200) + t.assert.deepStrictEqual(await result.json(), {}) +}) + +test('use routerOptions semicolon delimiter set to false return 404', async t => { + t.plan(2) + + const fastify = Fastify({ + routerOptions: { + useSemicolonDelimiter: false + } + }) + + fastify.get('/1234', (req, reply) => { + reply.send(req.query) + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) + + const result = await fetch(fastifyServer + '/1234;foo=bar') + t.assert.ok(!result.ok) + t.assert.strictEqual(result.status, 404) +}) diff --git a/services/slides/node_modules/fastify/test/validation-error-handling.test.js b/services/slides/node_modules/fastify/test/validation-error-handling.test.js new file mode 100644 index 0000000000000000000000000000000000000000..f8933f3e4d42549d0699bb5fcbc524ae40ed3c8b --- /dev/null +++ b/services/slides/node_modules/fastify/test/validation-error-handling.test.js @@ -0,0 +1,900 @@ +'use strict' + +const { describe, test } = require('node:test') +const Joi = require('joi') +const Fastify = require('..') + +const schema = { + body: { + type: 'object', + properties: { + name: { type: 'string' }, + work: { type: 'string' } + }, + required: ['name', 'work'] + } +} + +function echoBody (req, reply) { + reply.code(200).send(req.body.name) +} + +test('should work with valid payload', async (t) => { + t.plan(2) + + const fastify = Fastify() + + fastify.post('/', { schema }, echoBody) + + const response = await fastify.inject({ + method: 'POST', + payload: { + name: 'michelangelo', + work: 'sculptor, painter, architect and poet' + }, + url: '/' + }) + t.assert.deepStrictEqual(response.payload, 'michelangelo') + t.assert.strictEqual(response.statusCode, 200) +}) + +test('should fail immediately with invalid payload', async (t) => { + t.plan(2) + + const fastify = Fastify() + + fastify.post('/', { schema }, echoBody) + + const response = await fastify.inject({ + method: 'POST', + payload: { + hello: 'michelangelo' + }, + url: '/' + }) + + t.assert.deepStrictEqual(response.json(), { + statusCode: 400, + code: 'FST_ERR_VALIDATION', + error: 'Bad Request', + message: "body must have required property 'name'" + }) + t.assert.strictEqual(response.statusCode, 400) +}) + +test('should be able to use setErrorHandler specify custom validation error', async (t) => { + t.plan(2) + + const fastify = Fastify() + + fastify.post('/', { schema }, function (req, reply) { + t.assert.fail('should not be here') + reply.code(200).send(req.body.name) + }) + + fastify.setErrorHandler(function (error, request, reply) { + if (error.validation) { + reply.status(422).send(new Error('validation failed')) + } + }) + + const response = await fastify.inject({ + method: 'POST', + payload: { + hello: 'michelangelo' + }, + url: '/' + }) + + t.assert.deepStrictEqual(JSON.parse(response.payload), { + statusCode: 422, + error: 'Unprocessable Entity', + message: 'validation failed' + }) + t.assert.strictEqual(response.statusCode, 422) +}) + +test('validation error has 400 statusCode set', async (t) => { + t.plan(2) + + const fastify = Fastify() + + fastify.setErrorHandler((error, request, reply) => { + const errorResponse = { + message: error.message, + statusCode: error.statusCode || 500 + } + + reply.code(errorResponse.statusCode).send(errorResponse) + }) + + fastify.post('/', { schema }, echoBody) + + const response = await fastify.inject({ + method: 'POST', + payload: { + hello: 'michelangelo' + }, + url: '/' + }) + + t.assert.deepStrictEqual(response.json(), { + statusCode: 400, + message: "body must have required property 'name'" + }) + t.assert.strictEqual(response.statusCode, 400) +}) + +test('error inside custom error handler should have validationContext', async (t) => { + t.plan(1) + + const fastify = Fastify() + + fastify.post('/', { + schema, + validatorCompiler: ({ schema, method, url, httpPart }) => { + return function (data) { + return { error: new Error('this failed') } + } + } + }, function (req, reply) { + t.assert.fail('should not be here') + reply.code(200).send(req.body.name) + }) + + fastify.setErrorHandler(function (error, request, reply) { + t.assert.strictEqual(error.validationContext, 'body') + reply.status(500).send(error) + }) + + await fastify.inject({ + method: 'POST', + payload: { + name: 'michelangelo', + work: 'artist' + }, + url: '/' + }) +}) + +test('error inside custom error handler should have validationContext if specified by custom error handler', async (t) => { + t.plan(1) + + const fastify = Fastify() + + fastify.post('/', { + schema, + validatorCompiler: ({ schema, method, url, httpPart }) => { + return function (data) { + const error = new Error('this failed') + error.validationContext = 'customContext' + return { error } + } + } + }, function (req, reply) { + t.assert.fail('should not be here') + reply.code(200).send(req.body.name) + }) + + fastify.setErrorHandler(function (error, request, reply) { + t.assert.strictEqual(error.validationContext, 'customContext') + reply.status(500).send(error) + }) + + await fastify.inject({ + method: 'POST', + payload: { + name: 'michelangelo', + work: 'artist' + }, + url: '/' + }) +}) + +test('should be able to attach validation to request', async (t) => { + t.plan(2) + + const fastify = Fastify() + + fastify.post('/', { schema, attachValidation: true }, function (req, reply) { + reply.code(400).send(req.validationError.validation) + }) + + const response = await fastify.inject({ + method: 'POST', + payload: { + hello: 'michelangelo' + }, + url: '/' + }) + + t.assert.deepStrictEqual(response.json(), [{ + keyword: 'required', + instancePath: '', + schemaPath: '#/required', + params: { missingProperty: 'name' }, + message: 'must have required property \'name\'' + }]) + t.assert.strictEqual(response.statusCode, 400) +}) + +test('should respect when attachValidation is explicitly set to false', async (t) => { + t.plan(2) + + const fastify = Fastify() + + fastify.post('/', { schema, attachValidation: false }, function (req, reply) { + t.assert.fail('should not be here') + reply.code(200).send(req.validationError.validation) + }) + + const response = await fastify.inject({ + method: 'POST', + payload: { + hello: 'michelangelo' + }, + url: '/' + }) + + t.assert.deepStrictEqual(JSON.parse(response.payload), { + statusCode: 400, + code: 'FST_ERR_VALIDATION', + error: 'Bad Request', + message: "body must have required property 'name'" + }) + t.assert.strictEqual(response.statusCode, 400) +}) + +test('Attached validation error should take precedence over setErrorHandler', async (t) => { + t.plan(2) + + const fastify = Fastify() + + fastify.post('/', { schema, attachValidation: true }, function (req, reply) { + reply.code(400).send('Attached: ' + req.validationError) + }) + + fastify.setErrorHandler(function (error, request, reply) { + t.assert.fail('should not be here') + if (error.validation) { + reply.status(422).send(new Error('validation failed')) + } + }) + + const response = await fastify.inject({ + method: 'POST', + payload: { + hello: 'michelangelo' + }, + url: '/' + }) + + t.assert.deepStrictEqual(response.payload, "Attached: Error: body must have required property 'name'") + t.assert.strictEqual(response.statusCode, 400) +}) + +test('should handle response validation error', async (t) => { + t.plan(2) + + const response = { + 200: { + type: 'object', + required: ['name', 'work'], + properties: { + name: { type: 'string' }, + work: { type: 'string' } + } + } + } + + const fastify = Fastify() + + fastify.get('/', { schema: { response } }, function (req, reply) { + try { + reply.code(200).send({ work: 'actor' }) + } catch (error) { + reply.code(500).send(error) + } + }) + + const injectResponse = await fastify.inject({ + method: 'GET', + payload: { }, + url: '/' + }) + + t.assert.strictEqual(injectResponse.statusCode, 500) + t.assert.strictEqual(injectResponse.payload, '{"statusCode":500,"error":"Internal Server Error","message":"\\"name\\" is required!"}') +}) + +test('should handle response validation error with promises', async (t) => { + t.plan(2) + + const response = { + 200: { + type: 'object', + required: ['name', 'work'], + properties: { + name: { type: 'string' }, + work: { type: 'string' } + } + } + } + + const fastify = Fastify() + + fastify.get('/', { schema: { response } }, function (req, reply) { + return Promise.resolve({ work: 'actor' }) + }) + + const injectResponse = await fastify.inject({ + method: 'GET', + payload: { }, + url: '/' + }) + + t.assert.strictEqual(injectResponse.statusCode, 500) + t.assert.strictEqual(injectResponse.payload, '{"statusCode":500,"error":"Internal Server Error","message":"\\"name\\" is required!"}') +}) + +test('should return a defined output message parsing AJV errors', async (t) => { + t.plan(2) + + const body = { + type: 'object', + required: ['name', 'work'], + properties: { + name: { type: 'string' }, + work: { type: 'string' } + } + } + + const fastify = Fastify() + + fastify.post('/', { schema: { body } }, function (req, reply) { + t.assert.fail() + }) + + const response = await fastify.inject({ + method: 'POST', + payload: { }, + url: '/' + }) + + t.assert.strictEqual(response.statusCode, 400) + t.assert.strictEqual(response.payload, '{"statusCode":400,"code":"FST_ERR_VALIDATION","error":"Bad Request","message":"body must have required property \'name\'"}') +}) + +test('should return a defined output message parsing JOI errors', async (t) => { + t.plan(2) + + const body = Joi.object().keys({ + name: Joi.string().required(), + work: Joi.string().required() + }).required() + + const fastify = Fastify() + + fastify.post('/', { + schema: { body }, + validatorCompiler: ({ schema, method, url, httpPart }) => { + return data => schema.validate(data) + } + }, + function (req, reply) { + t.assert.fail() + }) + + const response = await fastify.inject({ + method: 'POST', + payload: {}, + url: '/' + }) + + t.assert.strictEqual(response.statusCode, 400) + t.assert.strictEqual(response.payload, '{"statusCode":400,"code":"FST_ERR_VALIDATION","error":"Bad Request","message":"\\"name\\" is required"}') +}) + +test('should return a defined output message parsing JOI error details', async (t) => { + t.plan(2) + + const body = Joi.object().keys({ + name: Joi.string().required(), + work: Joi.string().required() + }).required() + + const fastify = Fastify() + + fastify.post('/', { + schema: { body }, + validatorCompiler: ({ schema, method, url, httpPart }) => { + return data => { + const validation = schema.validate(data) + return { error: validation.error.details } + } + } + }, + function (req, reply) { + t.assert.fail() + }) + + const response = await fastify.inject({ + method: 'POST', + payload: {}, + url: '/' + }) + + t.assert.strictEqual(response.statusCode, 400) + t.assert.strictEqual(response.payload, '{"statusCode":400,"code":"FST_ERR_VALIDATION","error":"Bad Request","message":"body \\"name\\" is required"}') +}) + +test('the custom error formatter context must be the server instance', async (t) => { + t.plan(3) + + const fastify = Fastify() + + fastify.setSchemaErrorFormatter(function (errors, dataVar) { + t.assert.deepStrictEqual(this, fastify) + return new Error('my error') + }) + + fastify.post('/', { schema }, echoBody) + + const response = await fastify.inject({ + method: 'POST', + payload: { + hello: 'michelangelo' + }, + url: '/' + }) + + t.assert.deepStrictEqual(response.json(), { + statusCode: 400, + code: 'FST_ERR_VALIDATION', + error: 'Bad Request', + message: 'my error' + }) + t.assert.strictEqual(response.statusCode, 400) +}) + +test('the custom error formatter context must be the server instance in options', async (t) => { + t.plan(3) + + const fastify = Fastify({ + schemaErrorFormatter: function (errors, dataVar) { + t.assert.deepStrictEqual(this, fastify) + return new Error('my error') + } + }) + + fastify.post('/', { schema }, echoBody) + + const response = await fastify.inject({ + method: 'POST', + payload: { + hello: 'michelangelo' + }, + url: '/' + }) + + t.assert.deepStrictEqual(response.json(), { + statusCode: 400, + code: 'FST_ERR_VALIDATION', + error: 'Bad Request', + message: 'my error' + }) + t.assert.strictEqual(response.statusCode, 400) +}) + +test('should call custom error formatter', async (t) => { + t.plan(8) + + const fastify = Fastify({ + schemaErrorFormatter: (errors, dataVar) => { + t.assert.strictEqual(errors.length, 1) + t.assert.strictEqual(errors[0].message, "must have required property 'name'") + t.assert.strictEqual(errors[0].keyword, 'required') + t.assert.strictEqual(errors[0].schemaPath, '#/required') + t.assert.deepStrictEqual(errors[0].params, { + missingProperty: 'name' + }) + t.assert.strictEqual(dataVar, 'body') + return new Error('my error') + } + }) + + fastify.post('/', { schema }, echoBody) + + const response = await fastify.inject({ + method: 'POST', + payload: { + hello: 'michelangelo' + }, + url: '/' + }) + + t.assert.deepStrictEqual(response.json(), { + statusCode: 400, + code: 'FST_ERR_VALIDATION', + error: 'Bad Request', + message: 'my error' + }) + t.assert.strictEqual(response.statusCode, 400) +}) + +test('should catch error inside formatter and return message', async (t) => { + t.plan(2) + + const fastify = Fastify({ + schemaErrorFormatter: (errors, dataVar) => { + throw new Error('abc') + } + }) + + fastify.post('/', { schema }, echoBody) + + const response = await fastify.inject({ + method: 'POST', + payload: { + hello: 'michelangelo' + }, + url: '/' + }) + + t.assert.deepStrictEqual(response.json(), { + statusCode: 500, + error: 'Internal Server Error', + message: 'abc' + }) + t.assert.strictEqual(response.statusCode, 500) +}) + +test('cannot create a fastify instance with wrong type of errorFormatter', async (t) => { + t.plan(3) + + try { + Fastify({ + schemaErrorFormatter: async (errors, dataVar) => { + return new Error('should not execute') + } + }) + } catch (err) { + t.assert.strictEqual(err.code, 'FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN') + } + + try { + Fastify({ + schemaErrorFormatter: 500 + }) + } catch (err) { + t.assert.strictEqual(err.code, 'FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN') + } + + try { + const fastify = Fastify() + fastify.setSchemaErrorFormatter(500) + } catch (err) { + t.assert.strictEqual(err.code, 'FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN') + } +}) + +test('should register a route based schema error formatter', async (t) => { + t.plan(2) + + const fastify = Fastify() + + fastify.post('/', { + schema, + schemaErrorFormatter: (errors, dataVar) => { + return new Error('abc') + } + }, echoBody) + + const response = await fastify.inject({ + method: 'POST', + payload: { + hello: 'michelangelo' + }, + url: '/' + }) + + t.assert.deepStrictEqual(response.json(), { + statusCode: 400, + code: 'FST_ERR_VALIDATION', + error: 'Bad Request', + message: 'abc' + }) + t.assert.strictEqual(response.statusCode, 400) +}) + +test('prefer route based error formatter over global one', async (t) => { + t.plan(6) + + const fastify = Fastify({ + schemaErrorFormatter: (errors, dataVar) => { + return new Error('abc123') + } + }) + + fastify.post('/', { + schema, + schemaErrorFormatter: (errors, dataVar) => { + return new Error('123') + } + }, echoBody) + + fastify.post('/abc', { + schema, + schemaErrorFormatter: (errors, dataVar) => { + return new Error('abc') + } + }, echoBody) + + fastify.post('/test', { schema }, echoBody) + + const response1 = await fastify.inject({ + method: 'POST', + payload: { + hello: 'michelangelo' + }, + url: '/' + }) + + t.assert.deepStrictEqual(response1.json(), { + statusCode: 400, + code: 'FST_ERR_VALIDATION', + error: 'Bad Request', + message: '123' + }) + t.assert.strictEqual(response1.statusCode, 400) + + const response2 = await fastify.inject({ + method: 'POST', + payload: { + hello: 'michelangelo' + }, + url: '/abc' + }) + + t.assert.deepStrictEqual(response2.json(), { + statusCode: 400, + code: 'FST_ERR_VALIDATION', + error: 'Bad Request', + message: 'abc' + }) + t.assert.strictEqual(response2.statusCode, 400) + + const response3 = await fastify.inject({ + method: 'POST', + payload: { + hello: 'michelangelo' + }, + url: '/test' + }) + + t.assert.deepStrictEqual(response3.json(), { + statusCode: 400, + code: 'FST_ERR_VALIDATION', + error: 'Bad Request', + message: 'abc123' + }) + t.assert.strictEqual(response3.statusCode, 400) +}) + +test('adding schemaErrorFormatter', async (t) => { + t.plan(2) + + const fastify = Fastify() + + fastify.setSchemaErrorFormatter((errors, dataVar) => { + return new Error('abc') + }) + + fastify.post('/', { schema }, echoBody) + + const response = await fastify.inject({ + method: 'POST', + payload: { + hello: 'michelangelo' + }, + url: '/' + }) + + t.assert.deepStrictEqual(response.json(), { + statusCode: 400, + code: 'FST_ERR_VALIDATION', + error: 'Bad Request', + message: 'abc' + }) + t.assert.strictEqual(response.statusCode, 400) +}) + +test('plugin override', async (t) => { + t.plan(10) + + const fastify = Fastify({ + schemaErrorFormatter: (errors, dataVar) => { + return new Error('B') + } + }) + + fastify.register((instance, opts, done) => { + instance.setSchemaErrorFormatter((errors, dataVar) => { + return new Error('C') + }) + + instance.post('/d', { + schema, + schemaErrorFormatter: (errors, dataVar) => { + return new Error('D') + } + }, function (req, reply) { + reply.code(200).send(req.body.name) + }) + + instance.post('/c', { schema }, echoBody) + + instance.register((subinstance, opts, done) => { + subinstance.post('/stillC', { schema }, echoBody) + done() + }) + + done() + }) + + fastify.post('/b', { schema }, echoBody) + + fastify.post('/', { + schema, + schemaErrorFormatter: (errors, dataVar) => { + return new Error('A') + } + }, echoBody) + + const response1 = await fastify.inject({ + method: 'POST', + payload: { + hello: 'michelangelo' + }, + url: '/' + }) + + t.assert.deepStrictEqual(response1.json(), { + statusCode: 400, + code: 'FST_ERR_VALIDATION', + error: 'Bad Request', + message: 'A' + }) + t.assert.strictEqual(response1.statusCode, 400) + + const response2 = await fastify.inject({ + method: 'POST', + payload: { + hello: 'michelangelo' + }, + url: '/b' + }) + + t.assert.deepStrictEqual(response2.json(), { + statusCode: 400, + code: 'FST_ERR_VALIDATION', + error: 'Bad Request', + message: 'B' + }) + t.assert.strictEqual(response2.statusCode, 400) + + const response3 = await fastify.inject({ + method: 'POST', + payload: { + hello: 'michelangelo' + }, + url: '/c' + }) + + t.assert.deepStrictEqual(response3.json(), { + statusCode: 400, + code: 'FST_ERR_VALIDATION', + error: 'Bad Request', + message: 'C' + }) + t.assert.strictEqual(response3.statusCode, 400) + + const response4 = await fastify.inject({ + method: 'POST', + payload: { + hello: 'michelangelo' + }, + url: '/d' + }) + + t.assert.deepStrictEqual(response4.json(), { + statusCode: 400, + code: 'FST_ERR_VALIDATION', + error: 'Bad Request', + message: 'D' + }) + t.assert.strictEqual(response4.statusCode, 400) + + const response5 = await fastify.inject({ + method: 'POST', + payload: { + hello: 'michelangelo' + }, + url: '/stillC' + }) + + t.assert.deepStrictEqual(response5.json(), { + statusCode: 400, + code: 'FST_ERR_VALIDATION', + error: 'Bad Request', + message: 'C' + }) + t.assert.strictEqual(response5.statusCode, 400) +}) + +describe('sync and async must work in the same way', () => { + // Route with custom validator that throws + const throwingRouteValidator = { + schema: { + body: { + type: 'object', + properties: { name: { type: 'string' } } + } + }, + validatorCompiler: () => { + return function (inputData) { + // This custom validator throws a sync error instead of returning `{ error }` + throw new Error('Custom validation failed') + } + }, + handler (request, reply) { reply.send({ success: true }) } + } + + test('async preValidation with custom validator should trigger error handler when validator throws', async (t) => { + t.plan(4) + + const fastify = Fastify() + fastify.setErrorHandler((error, request, reply) => { + t.assert.ok(error instanceof Error, 'error should be an Error instance') + t.assert.strictEqual(error.message, 'Custom validation failed') + reply.status(500).send({ error: error.message }) + }) + + // Add async preValidation hook + fastify.addHook('preValidation', async (request, reply) => { + await Promise.resolve('ok') + }) + fastify.post('/async', throwingRouteValidator) + + const response = await fastify.inject({ + method: 'POST', + url: '/async', + payload: { name: 'test' } + }) + t.assert.strictEqual(response.statusCode, 500) + t.assert.deepStrictEqual(response.json(), { error: 'Custom validation failed' }) + }) + + test('sync preValidation with custom validator should trigger error handler when validator throws', async (t) => { + t.plan(4) + + const fastify = Fastify() + fastify.setErrorHandler((error, request, reply) => { + t.assert.ok(error instanceof Error, 'error should be an Error instance') + t.assert.strictEqual(error.message, 'Custom validation failed') + reply.status(500).send({ error: error.message }) + }) + + // Add sync preValidation hook + fastify.addHook('preValidation', (request, reply, next) => { next() }) + fastify.post('/sync', throwingRouteValidator) + + const response = await fastify.inject({ + method: 'POST', + url: '/sync', + payload: { name: 'test' } + }) + t.assert.strictEqual(response.statusCode, 500) + t.assert.deepStrictEqual(response.json(), { error: 'Custom validation failed' }) + }) +}) diff --git a/services/slides/node_modules/fastify/test/versioned-routes.test.js b/services/slides/node_modules/fastify/test/versioned-routes.test.js new file mode 100644 index 0000000000000000000000000000000000000000..0ec03b1d9f3778697c7bf0eb353ce624ad55d73f --- /dev/null +++ b/services/slides/node_modules/fastify/test/versioned-routes.test.js @@ -0,0 +1,603 @@ +'use strict' + +const { test, before } = require('node:test') +const helper = require('./helper') +const Fastify = require('..') +const http = require('node:http') +const split = require('split2') +const append = require('vary').append + +process.removeAllListeners('warning') + +let localhost +before(async function () { + [localhost] = await helper.getLoopbackHost() +}) + +test('Should register a versioned route (inject)', (t, done) => { + t.plan(11) + const fastify = Fastify() + + fastify.route({ + method: 'GET', + url: '/', + constraints: { version: '1.2.0' }, + handler: (req, reply) => { + reply.send({ hello: 'world' }) + } + }) + + fastify.inject({ + method: 'GET', + url: '/', + headers: { + 'Accept-Version': '1.x' + } + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + t.assert.strictEqual(res.statusCode, 200) + }) + + fastify.inject({ + method: 'GET', + url: '/', + headers: { + 'Accept-Version': '1.2.x' + } + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + t.assert.strictEqual(res.statusCode, 200) + }) + + fastify.inject({ + method: 'GET', + url: '/', + headers: { + 'Accept-Version': '1.2.0' + } + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + t.assert.strictEqual(res.statusCode, 200) + }) + + fastify.inject({ + method: 'GET', + url: '/', + headers: { + 'Accept-Version': '1.2.1' + } + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 404) + done() + }) +}) + +test('Should register a versioned route via route constraints', (t, done) => { + t.plan(6) + const fastify = Fastify() + + fastify.route({ + method: 'GET', + url: '/', + constraints: { version: '1.2.0' }, + handler: (req, reply) => { + reply.send({ hello: 'world' }) + } + }) + + fastify.inject({ + method: 'GET', + url: '/', + headers: { + 'Accept-Version': '1.x' + } + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + t.assert.strictEqual(res.statusCode, 200) + }) + + fastify.inject({ + method: 'GET', + url: '/', + headers: { + 'Accept-Version': '1.2.x' + } + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + t.assert.strictEqual(res.statusCode, 200) + done() + }) +}) + +test('Should register the same route with different versions', (t, done) => { + t.plan(8) + const fastify = Fastify() + + fastify.route({ + method: 'GET', + url: '/', + constraints: { version: '1.2.0' }, + handler: (req, reply) => { + reply.send('1.2.0') + } + }) + + fastify.route({ + method: 'GET', + url: '/', + constraints: { version: '1.3.0' }, + handler: (req, reply) => { + reply.send('1.3.0') + } + }) + + fastify.inject({ + method: 'GET', + url: '/', + headers: { + 'Accept-Version': '1.x' + } + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload, '1.3.0') + }) + + fastify.inject({ + method: 'GET', + url: '/', + headers: { + 'Accept-Version': '1.2.x' + } + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.payload, '1.2.0') + }) + + fastify.inject({ + method: 'GET', + url: '/', + headers: { + 'Accept-Version': '2.x' + } + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 404) + done() + }) +}) + +test('The versioned route should take precedence', (t, done) => { + t.plan(3) + const fastify = Fastify() + + fastify.route({ + method: 'GET', + url: '/', + handler: (req, reply) => { + reply.send({ winter: 'is coming' }) + } + }) + + fastify.route({ + method: 'GET', + url: '/', + constraints: { version: '1.2.0' }, + handler: (req, reply) => { + reply.send({ hello: 'world' }) + } + }) + + fastify.inject({ + method: 'GET', + url: '/', + headers: { + 'Accept-Version': '1.x' + } + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + t.assert.strictEqual(res.statusCode, 200) + done() + }) +}) + +test('Versioned route but not version header should return a 404', (t, done) => { + t.plan(2) + const fastify = Fastify() + + fastify.route({ + method: 'GET', + url: '/', + constraints: { version: '1.2.0' }, + handler: (req, reply) => { + reply.send({ hello: 'world' }) + } + }) + + fastify.inject({ + method: 'GET', + url: '/' + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 404) + done() + }) +}) + +test('Should register a versioned route (server)', async t => { + t.plan(5) + const fastify = Fastify() + + fastify.route({ + method: 'GET', + url: '/', + constraints: { version: '1.2.0' }, + handler: (req, reply) => { + reply.send({ hello: 'world' }) + } + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) + + const result1 = await fetch(fastifyServer, { + headers: { + 'Accept-Version': '1.x' + } + }) + t.assert.ok(result1.ok) + t.assert.strictEqual(result1.status, 200) + const body1 = await result1.json() + t.assert.deepStrictEqual(body1, { hello: 'world' }) + + const result2 = await fetch(fastifyServer, { + headers: { + 'Accept-Version': '2.x' + } + }) + + t.assert.ok(!result2.ok) + t.assert.strictEqual(result2.status, 404) +}) + +test('Shorthand route declaration', (t, done) => { + t.plan(5) + const fastify = Fastify() + + fastify.get('/', { constraints: { version: '1.2.0' } }, (req, reply) => { + reply.send({ hello: 'world' }) + }) + + fastify.inject({ + method: 'GET', + url: '/', + headers: { + 'Accept-Version': '1.x' + } + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + t.assert.strictEqual(res.statusCode, 200) + }) + + fastify.inject({ + method: 'GET', + url: '/', + headers: { + 'Accept-Version': '1.2.1' + } + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 404) + done() + }) +}) + +test('The not found handler should not erase the Accept-Version header', (t, done) => { + t.plan(13) + const fastify = Fastify() + + fastify.addHook('onRequest', function (req, reply, done) { + t.assert.deepStrictEqual(req.headers['accept-version'], '2.x') + done() + }) + + fastify.addHook('preValidation', function (req, reply, done) { + t.assert.deepStrictEqual(req.headers['accept-version'], '2.x') + done() + }) + + fastify.addHook('preHandler', function (req, reply, done) { + t.assert.deepStrictEqual(req.headers['accept-version'], '2.x') + done() + }) + + fastify.route({ + method: 'GET', + url: '/', + constraints: { version: '1.2.0' }, + handler: (req, reply) => { + reply.send({ hello: 'world' }) + } + }) + + fastify.setNotFoundHandler(function (req, reply) { + t.assert.deepStrictEqual(req.headers['accept-version'], '2.x') + // we check if the symbol is exposed on key or not + for (const key in req.headers) { + t.assert.deepStrictEqual(typeof key, 'string') + } + + for (const key of Object.keys(req.headers)) { + t.assert.deepStrictEqual(typeof key, 'string') + } + + reply.code(404).send('not found handler') + }) + + fastify.inject({ + method: 'GET', + url: '/', + headers: { + 'Accept-Version': '2.x' + } + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(res.payload, 'not found handler') + t.assert.strictEqual(res.statusCode, 404) + done() + }) +}) + +test('Bad accept version (inject)', (t, done) => { + t.plan(4) + const fastify = Fastify() + + fastify.route({ + method: 'GET', + url: '/', + constraints: { version: '1.2.0' }, + handler: (req, reply) => { + reply.send({ hello: 'world' }) + } + }) + + fastify.inject({ + method: 'GET', + url: '/', + headers: { + 'Accept-Version': 'a.b.c' + } + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 404) + }) + + fastify.inject({ + method: 'GET', + url: '/', + headers: { + 'Accept-Version': 12 + } + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 404) + done() + }) +}) + +test('Bad accept version (server)', async t => { + t.plan(4) + const fastify = Fastify() + + fastify.route({ + method: 'GET', + url: '/', + constraints: { version: '1.2.0' }, + handler: (req, reply) => { + reply.send({ hello: 'world' }) + } + }) + + const fastifyServer = await fastify.listen({ port: 0 }) + t.after(() => { fastify.close() }) + + const result1 = await fetch(fastifyServer, { + headers: { + 'Accept-Version': 'a.b.c' + } + }) + t.assert.ok(!result1.ok) + t.assert.strictEqual(result1.status, 404) + + const result2 = await fetch(fastifyServer, { + headers: { + 'Accept-Version': '12' + } + }) + t.assert.ok(!result2.ok) + t.assert.strictEqual(result2.status, 404) +}) + +test('test log stream', (t, done) => { + t.plan(3) + const stream = split(JSON.parse) + const fastify = Fastify({ + logger: { + stream, + level: 'info' + } + }) + + fastify.get('/', { constraints: { version: '1.2.0' } }, function (req, reply) { + reply.send(new Error('kaboom')) + }) + + fastify.listen({ port: 0, host: localhost }, err => { + t.assert.ifError(err) + t.after(() => { fastify.close() }) + + http.get({ + host: fastify.server.address().hostname, + port: fastify.server.address().port, + path: '/', + method: 'GET', + headers: { + 'Accept-Version': '1.x' + } + }) + + stream.once('data', listenAtLogLine => { + stream.once('data', line => { + t.assert.strictEqual(line.req.version, '1.x') + stream.once('data', line => { + t.assert.strictEqual(line.req.version, '1.x') + done() + }) + }) + }) + }) +}) + +test('Should register a versioned route with custom versioning strategy', (t, done) => { + t.plan(8) + + const customVersioning = { + name: 'version', + storage: function () { + const versions = {} + return { + get: (version) => { return versions[version] || null }, + set: (version, store) => { versions[version] = store } + } + }, + deriveConstraint: (req, ctx) => { + return req.headers.accept + }, + mustMatchWhenDerived: true, + validate: () => true + } + + const fastify = Fastify({ + constraints: { + version: customVersioning + } + }) + + fastify.route({ + method: 'GET', + url: '/', + constraints: { version: 'application/vnd.example.api+json;version=2' }, + handler: (req, reply) => { + reply.send({ hello: 'from route v2' }) + } + }) + + fastify.route({ + method: 'GET', + url: '/', + constraints: { version: 'application/vnd.example.api+json;version=3' }, + handler: (req, reply) => { + reply.send({ hello: 'from route v3' }) + } + }) + + fastify.inject({ + method: 'GET', + url: '/', + headers: { + Accept: 'application/vnd.example.api+json;version=2' + } + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'from route v2' }) + t.assert.strictEqual(res.statusCode, 200) + }) + + fastify.inject({ + method: 'GET', + url: '/', + headers: { + Accept: 'application/vnd.example.api+json;version=3' + } + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'from route v3' }) + t.assert.strictEqual(res.statusCode, 200) + }) + + fastify.inject({ + method: 'GET', + url: '/', + headers: { + Accept: 'application/vnd.example.api+json;version=4' + } + }, (err, res) => { + t.assert.ifError(err) + t.assert.strictEqual(res.statusCode, 404) + done() + }) +}) + +test('Vary header check (for documentation example)', (t, done) => { + t.plan(8) + const fastify = Fastify() + fastify.addHook('onSend', async (req, reply) => { + if (req.headers['accept-version']) { // or the custom header you are using + let value = reply.getHeader('Vary') || '' + const header = Array.isArray(value) ? value.join(', ') : String(value) + if ((value = append(header, 'Accept-Version'))) { // or the custom header you are using + reply.header('Vary', value) + } + } + }) + + fastify.route({ + method: 'GET', + url: '/', + handler: (req, reply) => { + reply.send({ hello: 'world' }) + } + }) + + fastify.route({ + method: 'GET', + url: '/', + constraints: { version: '1.2.0' }, + handler: (req, reply) => { + reply.send({ hello: 'world' }) + } + }) + + fastify.inject({ + method: 'GET', + url: '/', + headers: { + 'Accept-Version': '1.x' + } + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers.vary, 'Accept-Version') + }) + + fastify.inject({ + method: 'GET', + url: '/' + }, (err, res) => { + t.assert.ifError(err) + t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' }) + t.assert.strictEqual(res.statusCode, 200) + t.assert.strictEqual(res.headers.vary, undefined) + done() + }) +}) diff --git a/services/slides/node_modules/fastify/test/web-api.test.js b/services/slides/node_modules/fastify/test/web-api.test.js new file mode 100644 index 0000000000000000000000000000000000000000..e4fd574ce703a593448fe47c072589cc107d5972 --- /dev/null +++ b/services/slides/node_modules/fastify/test/web-api.test.js @@ -0,0 +1,616 @@ +'use strict' + +const { test } = require('node:test') +const Fastify = require('../fastify') +const fs = require('node:fs') +const { Readable } = require('node:stream') +const { fetch: undiciFetch } = require('undici') +const http = require('node:http') +const { setTimeout: sleep } = require('node:timers/promises') + +test('should response with a ReadableStream', async (t) => { + t.plan(2) + + const fastify = Fastify() + + fastify.get('/', function (request, reply) { + const stream = fs.createReadStream(__filename) + reply.code(200).send(Readable.toWeb(stream)) + }) + + const { + statusCode, + body + } = await fastify.inject({ method: 'GET', path: '/' }) + + const expected = await fs.promises.readFile(__filename) + + t.assert.strictEqual(statusCode, 200) + t.assert.strictEqual(expected.toString(), body.toString()) +}) + +test('should response with a Response', async (t) => { + t.plan(3) + + const fastify = Fastify() + + fastify.get('/', function (request, reply) { + const stream = fs.createReadStream(__filename) + reply.send(new Response(Readable.toWeb(stream), { + status: 200, + headers: { + hello: 'world' + } + })) + }) + + const { + statusCode, + headers, + body + } = await fastify.inject({ method: 'GET', path: '/' }) + + const expected = await fs.promises.readFile(__filename) + + t.assert.strictEqual(statusCode, 200) + t.assert.strictEqual(expected.toString(), body.toString()) + t.assert.strictEqual(headers.hello, 'world') +}) + +test('should response with a Response 204', async (t) => { + t.plan(3) + + const fastify = Fastify() + + fastify.get('/', function (request, reply) { + reply.send(new Response(null, { + status: 204, + headers: { + hello: 'world' + } + })) + }) + + const { + statusCode, + headers, + body + } = await fastify.inject({ method: 'GET', path: '/' }) + + t.assert.strictEqual(statusCode, 204) + t.assert.strictEqual(body, '') + t.assert.strictEqual(headers.hello, 'world') +}) + +test('should response with a Response 304', async (t) => { + t.plan(3) + + const fastify = Fastify() + + fastify.get('/', function (request, reply) { + reply.send(new Response(null, { + status: 304, + headers: { + hello: 'world' + } + })) + }) + + const { + statusCode, + headers, + body + } = await fastify.inject({ method: 'GET', path: '/' }) + + t.assert.strictEqual(statusCode, 304) + t.assert.strictEqual(body, '') + t.assert.strictEqual(headers.hello, 'world') +}) + +test('should response with a Response without body', async (t) => { + t.plan(3) + + const fastify = Fastify() + + fastify.get('/', function (request, reply) { + reply.send(new Response(null, { + status: 200, + headers: { + hello: 'world' + } + })) + }) + + const { + statusCode, + headers, + body + } = await fastify.inject({ method: 'GET', path: '/' }) + + t.assert.strictEqual(statusCode, 200) + t.assert.strictEqual(body, '') + t.assert.strictEqual(headers.hello, 'world') +}) + +test('able to use in onSend hook - ReadableStream', async (t) => { + t.plan(4) + + const fastify = Fastify() + + fastify.get('/', function (request, reply) { + const stream = fs.createReadStream(__filename) + reply.code(500).send(Readable.toWeb(stream)) + }) + + fastify.addHook('onSend', (request, reply, payload, done) => { + t.assert.strictEqual(Object.prototype.toString.call(payload), '[object ReadableStream]') + done(null, new Response(payload, { + status: 200, + headers: { + hello: 'world' + } + })) + }) + + const { + statusCode, + headers, + body + } = await fastify.inject({ method: 'GET', path: '/' }) + + const expected = await fs.promises.readFile(__filename) + + t.assert.strictEqual(statusCode, 200) + t.assert.strictEqual(expected.toString(), body.toString()) + t.assert.strictEqual(headers.hello, 'world') +}) + +test('able to use in onSend hook - Response', async (t) => { + t.plan(4) + + const fastify = Fastify() + + fastify.get('/', function (request, reply) { + const stream = fs.createReadStream(__filename) + reply.send(new Response(Readable.toWeb(stream), { + status: 500, + headers: { + hello: 'world' + } + })) + }) + + fastify.addHook('onSend', (request, reply, payload, done) => { + t.assert.strictEqual(Object.prototype.toString.call(payload), '[object Response]') + done(null, new Response(payload.body, { + status: 200, + headers: payload.headers + })) + }) + + const { + statusCode, + headers, + body + } = await fastify.inject({ method: 'GET', path: '/' }) + + const expected = await fs.promises.readFile(__filename) + + t.assert.strictEqual(statusCode, 200) + t.assert.strictEqual(expected.toString(), body.toString()) + t.assert.strictEqual(headers.hello, 'world') +}) + +test('Error when Response.bodyUsed', async (t) => { + t.plan(4) + + const expected = await fs.promises.readFile(__filename) + + const fastify = Fastify() + + fastify.get('/', async function (request, reply) { + const stream = fs.createReadStream(__filename) + const response = new Response(Readable.toWeb(stream), { + status: 200, + headers: { + hello: 'world' + } + }) + const file = await response.text() + t.assert.strictEqual(expected.toString(), file) + t.assert.strictEqual(response.bodyUsed, true) + return reply.send(response) + }) + + const response = await fastify.inject({ method: 'GET', path: '/' }) + + t.assert.strictEqual(response.statusCode, 500) + const body = response.json() + t.assert.strictEqual(body.code, 'FST_ERR_REP_RESPONSE_BODY_CONSUMED') +}) + +test('Error when Response.body.locked', async (t) => { + t.plan(3) + + const fastify = Fastify() + + fastify.get('/', async function (request, reply) { + const stream = Readable.toWeb(fs.createReadStream(__filename)) + const response = new Response(stream, { + status: 200, + headers: { + hello: 'world' + } + }) + stream.getReader() + t.assert.strictEqual(stream.locked, true) + return reply.send(response) + }) + + const response = await fastify.inject({ method: 'GET', path: '/' }) + + t.assert.strictEqual(response.statusCode, 500) + const body = response.json() + t.assert.strictEqual(body.code, 'FST_ERR_REP_READABLE_STREAM_LOCKED') +}) + +test('Error when ReadableStream.locked', async (t) => { + t.plan(3) + + const fastify = Fastify() + + fastify.get('/', async function (request, reply) { + const stream = Readable.toWeb(fs.createReadStream(__filename)) + stream.getReader() + t.assert.strictEqual(stream.locked, true) + return reply.send(stream) + }) + + const response = await fastify.inject({ method: 'GET', path: '/' }) + + t.assert.strictEqual(response.statusCode, 500) + const body = response.json() + t.assert.strictEqual(body.code, 'FST_ERR_REP_READABLE_STREAM_LOCKED') +}) + +test('allow to pipe with fetch', async (t) => { + t.plan(2) + const abortController = new AbortController() + const { signal } = abortController + + const fastify = Fastify() + t.after(() => { + fastify.close() + abortController.abort() + }) + + fastify.get('/', function (request, reply) { + return fetch(`${fastify.listeningOrigin}/fetch`, { + method: 'GET', + signal + }) + }) + + fastify.get('/fetch', function async (request, reply) { + reply.code(200).send({ ok: true }) + }) + + await fastify.listen() + + const response = await fastify.inject({ method: 'GET', path: '/' }) + + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(response.json(), { ok: true }) +}) + +test('allow to pipe with undici.fetch', async (t) => { + t.plan(2) + const abortController = new AbortController() + const { signal } = abortController + + const fastify = Fastify() + t.after(() => { + fastify.close() + abortController.abort() + }) + + fastify.get('/', function (request, reply) { + return undiciFetch(`${fastify.listeningOrigin}/fetch`, { + method: 'GET', + signal + }) + }) + + fastify.get('/fetch', function (request, reply) { + reply.code(200).send({ ok: true }) + }) + + await fastify.listen() + + const response = await fastify.inject({ method: 'GET', path: '/' }) + + t.assert.strictEqual(response.statusCode, 200) + t.assert.deepStrictEqual(response.json(), { ok: true }) +}) + +test('WebStream error before headers sent should trigger error handler', async (t) => { + t.plan(2) + + const fastify = Fastify() + + fastify.get('/', function (request, reply) { + const stream = new ReadableStream({ + start (controller) { + controller.error(new Error('stream error')) + } + }) + reply.send(stream) + }) + + const response = await fastify.inject({ method: 'GET', path: '/' }) + + t.assert.strictEqual(response.statusCode, 500) + t.assert.strictEqual(response.json().message, 'stream error') +}) + +test('WebStream error after headers sent should destroy response', (t, done) => { + t.plan(2) + + const fastify = Fastify() + t.after(() => fastify.close()) + + fastify.get('/', function (request, reply) { + const stream = new ReadableStream({ + start (controller) { + controller.enqueue('hello') + }, + pull (controller) { + setTimeout(() => { + controller.error(new Error('stream error')) + }, 10) + } + }) + reply.header('content-type', 'text/plain').send(stream) + }) + + fastify.listen({ port: 0 }, err => { + t.assert.ifError(err) + + let finished = false + http.get(`http://localhost:${fastify.server.address().port}`, (res) => { + res.on('close', () => { + if (!finished) { + finished = true + t.assert.ok('response closed') + done() + } + }) + res.resume() + }) + }) +}) + +test('WebStream should cancel reader when response is destroyed', (t, done) => { + t.plan(2) + + const fastify = Fastify() + t.after(() => fastify.close()) + + let readerCancelled = false + + fastify.get('/', function (request, reply) { + const stream = new ReadableStream({ + start (controller) { + controller.enqueue('hello') + }, + pull (controller) { + return new Promise(() => {}) + }, + cancel () { + readerCancelled = true + } + }) + reply.header('content-type', 'text/plain').send(stream) + }) + + fastify.listen({ port: 0 }, err => { + t.assert.ifError(err) + + const req = http.get(`http://localhost:${fastify.server.address().port}`, (res) => { + res.once('data', () => { + req.destroy() + setTimeout(() => { + t.assert.strictEqual(readerCancelled, true) + done() + }, 50) + }) + }) + }) +}) + +test('WebStream should respect backpressure', async (t) => { + t.plan(3) + + const fastify = Fastify() + t.after(() => fastify.close()) + + let drainEmittedAt = 0 + let secondWriteAt = 0 + let resolveSecondWrite + const secondWrite = new Promise((resolve) => { + resolveSecondWrite = resolve + }) + + fastify.get('/', function (request, reply) { + const raw = reply.raw + const originalWrite = raw.write.bind(raw) + const bufferedChunks = [] + let wroteFirstChunk = false + + raw.once('drain', () => { + for (const bufferedChunk of bufferedChunks) { + originalWrite(bufferedChunk) + } + }) + + raw.write = function (chunk, encoding, cb) { + if (!wroteFirstChunk) { + wroteFirstChunk = true + bufferedChunks.push(Buffer.from(chunk)) + sleep(100).then(() => { + drainEmittedAt = Date.now() + raw.emit('drain') + }) + if (typeof cb === 'function') { + cb() + } + return false + } + if (!secondWriteAt) { + secondWriteAt = Date.now() + resolveSecondWrite() + } + return originalWrite(chunk, encoding, cb) + } + + const stream = new ReadableStream({ + start (controller) { + controller.enqueue(Buffer.from('chunk-1')) + }, + pull (controller) { + controller.enqueue(Buffer.from('chunk-2')) + controller.close() + } + }) + + reply.header('content-type', 'text/plain').send(stream) + }) + + await fastify.listen({ port: 0 }) + + const response = await undiciFetch(`http://localhost:${fastify.server.address().port}/`) + const bodyPromise = response.text() + + await secondWrite + await sleep(120) + const body = await bodyPromise + + t.assert.strictEqual(response.status, 200) + t.assert.strictEqual(body, 'chunk-1chunk-2') + t.assert.ok(secondWriteAt >= drainEmittedAt) +}) + +test('WebStream should stop reading on drain after response destroy', async (t) => { + t.plan(2) + + const fastify = Fastify() + t.after(() => fastify.close()) + + let cancelCalled = false + let resolveCancel + const cancelPromise = new Promise((resolve) => { + resolveCancel = resolve + }) + + fastify.get('/', function (request, reply) { + const raw = reply.raw + const originalWrite = raw.write.bind(raw) + let firstWrite = true + + raw.write = function (chunk, encoding, cb) { + if (firstWrite) { + firstWrite = false + if (typeof cb === 'function') { + cb() + } + queueMicrotask(() => { + raw.destroy() + raw.emit('drain') + }) + return false + } + return originalWrite(chunk, encoding, cb) + } + + const stream = new ReadableStream({ + start (controller) { + controller.enqueue(Buffer.from('chunk-1')) + }, + pull (controller) { + controller.enqueue(Buffer.from('chunk-2')) + controller.close() + }, + cancel () { + cancelCalled = true + resolveCancel() + } + }) + + reply.header('content-type', 'text/plain').send(stream) + }) + + await new Promise((resolve, reject) => { + fastify.listen({ port: 0 }, err => { + if (err) return reject(err) + resolve() + }) + }) + + await new Promise((resolve, reject) => { + const req = http.get(`http://localhost:${fastify.server.address().port}/`, (res) => { + res.once('close', resolve) + res.resume() + }) + req.once('error', (err) => { + if (err.code === 'ECONNRESET') { + resolve() + } else { + reject(err) + } + }) + }) + + await cancelPromise + t.assert.ok(true, 'response interrupted as expected') + t.assert.strictEqual(cancelCalled, true) +}) + +test('WebStream should warn when headers already sent', async (t) => { + t.plan(2) + + let warnCalled = false + const spyLogger = { + level: 'warn', + fatal: () => { }, + error: () => { }, + warn: (msg) => { + if (typeof msg === 'string' && msg.includes('use res.writeHead in stream mode')) { + warnCalled = true + } + }, + info: () => { }, + debug: () => { }, + trace: () => { }, + child: () => spyLogger + } + + const fastify = Fastify({ loggerInstance: spyLogger }) + t.after(() => fastify.close()) + + fastify.get('/', function (request, reply) { + reply.raw.writeHead(200, { 'content-type': 'text/plain' }) + const stream = new ReadableStream({ + start (controller) { + controller.enqueue('hello') + controller.close() + } + }) + reply.send(stream) + }) + + await fastify.listen({ port: 0 }) + + const response = await fetch(`http://localhost:${fastify.server.address().port}/`) + t.assert.strictEqual(response.status, 200) + t.assert.strictEqual(warnCalled, true) +}) diff --git a/services/slides/node_modules/fastify/test/wrap-thenable.test.js b/services/slides/node_modules/fastify/test/wrap-thenable.test.js new file mode 100644 index 0000000000000000000000000000000000000000..b906acea369aabe3f8247f9f966d072820492062 --- /dev/null +++ b/services/slides/node_modules/fastify/test/wrap-thenable.test.js @@ -0,0 +1,30 @@ +'use strict' + +const { test } = require('node:test') +const { kReplyHijacked } = require('../lib/symbols') +const wrapThenable = require('../lib/wrap-thenable') +const Reply = require('../lib/reply') + +test('should resolve immediately when reply[kReplyHijacked] is true', async t => { + await new Promise(resolve => { + const reply = {} + reply[kReplyHijacked] = true + const thenable = Promise.resolve() + wrapThenable(thenable, reply) + resolve() + }) +}) + +test('should reject immediately when reply[kReplyHijacked] is true', t => { + t.plan(1) + const reply = new Reply({}, {}, {}) + reply[kReplyHijacked] = true + reply.log = { + error: ({ err }) => { + t.assert.strictEqual(err.message, 'Reply sent already') + } + } + + const thenable = Promise.reject(new Error('Reply sent already')) + wrapThenable(thenable, reply) +}) diff --git a/services/slides/node_modules/fastify/types/content-type-parser.d.ts b/services/slides/node_modules/fastify/types/content-type-parser.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..c4c7839bceeefdcbcf12bca2a51357e5eb05bb64 --- /dev/null +++ b/services/slides/node_modules/fastify/types/content-type-parser.d.ts @@ -0,0 +1,75 @@ +import { RawServerBase, RawServerDefault, RawRequestDefaultExpression } from './utils' +import { FastifyRequest } from './request' +import { RouteGenericInterface } from './route' +import { FastifyTypeProvider, FastifyTypeProviderDefault } from './type-provider' +import { FastifySchema } from './schema' + +type ContentTypeParserDoneFunction = (err: Error | null, body?: any) => void + +/** + * Body parser method that operators on request body + */ +export type FastifyBodyParser< + RawBody extends string | Buffer, + RawServer extends RawServerBase = RawServerDefault, + RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, + RouteGeneric extends RouteGenericInterface = RouteGenericInterface, + SchemaCompiler extends FastifySchema = FastifySchema, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault +> = ((request: FastifyRequest, rawBody: RawBody, done: ContentTypeParserDoneFunction) => void) +| ((request: FastifyRequest, rawBody: RawBody) => Promise) + +/** + * Content Type Parser method that operates on request content + */ +export type FastifyContentTypeParser< + RawServer extends RawServerBase = RawServerDefault, + RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, + RouteGeneric extends RouteGenericInterface = RouteGenericInterface, + SchemaCompiler extends FastifySchema = FastifySchema, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault +> = ((request: FastifyRequest, payload: RawRequest) => Promise) +| ((request: FastifyRequest, payload: RawRequest, done: ContentTypeParserDoneFunction) => void) + +/** + * Natively, Fastify only supports 'application/json' and 'text/plain' content types. The default charset is utf-8. If you need to support different content types, you can use the addContentTypeParser API. The default JSON and/or plain text parser can be changed. + */ +export interface AddContentTypeParser< + RawServer extends RawServerBase = RawServerDefault, + RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, + RouteGeneric extends RouteGenericInterface = RouteGenericInterface, + SchemaCompiler extends FastifySchema = FastifySchema, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault +> { + ( + contentType: string | string[] | RegExp, + opts: { + bodyLimit?: number; + }, + parser: FastifyContentTypeParser + ): void; + (contentType: string | string[] | RegExp, parser: FastifyContentTypeParser): void; + ( + contentType: string | string[] | RegExp, + opts: { + parseAs: parseAs extends Buffer ? 'buffer' : 'string'; + bodyLimit?: number; + }, + parser: FastifyBodyParser + ): void; +} + +/** + * Checks for a type parser of a content type + */ +export type hasContentTypeParser = (contentType: string | RegExp) => boolean + +export type ProtoAction = 'error' | 'remove' | 'ignore' + +export type ConstructorAction = 'error' | 'remove' | 'ignore' + +export type getDefaultJsonParser = (onProtoPoisoning: ProtoAction, onConstructorPoisoning: ConstructorAction) => FastifyBodyParser + +export type removeContentTypeParser = (contentType: string | RegExp | (string | RegExp)[]) => void + +export type removeAllContentTypeParsers = () => void diff --git a/services/slides/node_modules/fastify/types/context.d.ts b/services/slides/node_modules/fastify/types/context.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..01f66d807e00f8eebf3b87d78fe47a207ad03a68 --- /dev/null +++ b/services/slides/node_modules/fastify/types/context.d.ts @@ -0,0 +1,22 @@ +import { FastifyRouteConfig } from './route' +import { ContextConfigDefault } from './utils' + +export interface FastifyContextConfig { +} + +/** + * Route context object. Properties defined here will be available in the route's handler + */ +export interface FastifyRequestContext { + /** + * @deprecated Use Request#routeOptions#config or Request#routeOptions#schema instead + */ + config: FastifyContextConfig & FastifyRouteConfig & ContextConfig; +} + +export interface FastifyReplyContext { + /** + * @deprecated Use Reply#routeOptions#config or Reply#routeOptions#schema instead + */ + config: FastifyContextConfig & FastifyRouteConfig & ContextConfig; +} diff --git a/services/slides/node_modules/fastify/types/errors.d.ts b/services/slides/node_modules/fastify/types/errors.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..13c5cf406b6dbea0c0d02ffe12b8c009077dc212 --- /dev/null +++ b/services/slides/node_modules/fastify/types/errors.d.ts @@ -0,0 +1,92 @@ +import { FastifyErrorConstructor } from '@fastify/error' + +export type FastifyErrorCodes = Record< + 'FST_ERR_NOT_FOUND' | + 'FST_ERR_OPTIONS_NOT_OBJ' | + 'FST_ERR_QSP_NOT_FN' | + 'FST_ERR_SCHEMA_CONTROLLER_BUCKET_OPT_NOT_FN' | + 'FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN' | + 'FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_OBJ' | + 'FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_ARR' | + 'FST_ERR_VALIDATION' | + 'FST_ERR_LISTEN_OPTIONS_INVALID' | + 'FST_ERR_ERROR_HANDLER_NOT_FN' | + 'FST_ERR_ERROR_HANDLER_ALREADY_SET' | + 'FST_ERR_CTP_ALREADY_PRESENT' | + 'FST_ERR_CTP_INVALID_TYPE' | + 'FST_ERR_CTP_EMPTY_TYPE' | + 'FST_ERR_CTP_INVALID_HANDLER' | + 'FST_ERR_CTP_INVALID_PARSE_TYPE' | + 'FST_ERR_CTP_BODY_TOO_LARGE' | + 'FST_ERR_CTP_INVALID_MEDIA_TYPE' | + 'FST_ERR_CTP_INVALID_CONTENT_LENGTH' | + 'FST_ERR_CTP_EMPTY_JSON_BODY' | + 'FST_ERR_CTP_INVALID_JSON_BODY' | + 'FST_ERR_CTP_INSTANCE_ALREADY_STARTED' | + 'FST_ERR_DEC_ALREADY_PRESENT' | + 'FST_ERR_DEC_DEPENDENCY_INVALID_TYPE' | + 'FST_ERR_DEC_MISSING_DEPENDENCY' | + 'FST_ERR_DEC_AFTER_START' | + 'FST_ERR_DEC_REFERENCE_TYPE' | + 'FST_ERR_DEC_UNDECLARED' | + 'FST_ERR_HOOK_INVALID_TYPE' | + 'FST_ERR_HOOK_INVALID_HANDLER' | + 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER' | + 'FST_ERR_HOOK_NOT_SUPPORTED' | + 'FST_ERR_MISSING_MIDDLEWARE' | + 'FST_ERR_HOOK_TIMEOUT' | + 'FST_ERR_LOG_INVALID_DESTINATION' | + 'FST_ERR_LOG_INVALID_LOGGER' | + 'FST_ERR_LOG_INVALID_LOGGER_INSTANCE' | + 'FST_ERR_LOG_INVALID_LOGGER_CONFIG' | + 'FST_ERR_LOG_LOGGER_AND_LOGGER_INSTANCE_PROVIDED' | + 'FST_ERR_REP_INVALID_PAYLOAD_TYPE' | + 'FST_ERR_REP_RESPONSE_BODY_CONSUMED' | + 'FST_ERR_REP_READABLE_STREAM_LOCKED' | + 'FST_ERR_REP_ALREADY_SENT' | + 'FST_ERR_REP_SENT_VALUE' | + 'FST_ERR_SEND_INSIDE_ONERR' | + 'FST_ERR_SEND_UNDEFINED_ERR' | + 'FST_ERR_BAD_STATUS_CODE' | + 'FST_ERR_BAD_TRAILER_NAME' | + 'FST_ERR_BAD_TRAILER_VALUE' | + 'FST_ERR_FAILED_ERROR_SERIALIZATION' | + 'FST_ERR_MISSING_SERIALIZATION_FN' | + 'FST_ERR_MISSING_CONTENTTYPE_SERIALIZATION_FN' | + 'FST_ERR_REQ_INVALID_VALIDATION_INVOCATION' | + 'FST_ERR_SCH_MISSING_ID' | + 'FST_ERR_SCH_ALREADY_PRESENT' | + 'FST_ERR_SCH_CONTENT_MISSING_SCHEMA' | + 'FST_ERR_SCH_DUPLICATE' | + 'FST_ERR_SCH_VALIDATION_BUILD' | + 'FST_ERR_SCH_SERIALIZATION_BUILD' | + 'FST_ERR_SCH_RESPONSE_SCHEMA_NOT_NESTED_2XX' | + 'FST_ERR_INIT_OPTS_INVALID' | + 'FST_ERR_FORCE_CLOSE_CONNECTIONS_IDLE_NOT_AVAILABLE' | + 'FST_ERR_DUPLICATED_ROUTE' | + 'FST_ERR_BAD_URL' | + 'FST_ERR_ASYNC_CONSTRAINT' | + 'FST_ERR_INVALID_URL' | + 'FST_ERR_ROUTE_OPTIONS_NOT_OBJ' | + 'FST_ERR_ROUTE_DUPLICATED_HANDLER' | + 'FST_ERR_ROUTE_HANDLER_NOT_FN' | + 'FST_ERR_ROUTE_MISSING_HANDLER' | + 'FST_ERR_ROUTE_METHOD_INVALID' | + 'FST_ERR_ROUTE_METHOD_NOT_SUPPORTED' | + 'FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED' | + 'FST_ERR_ROUTE_BODY_LIMIT_OPTION_NOT_INT' | + 'FST_ERR_HANDLER_TIMEOUT' | + 'FST_ERR_ROUTE_HANDLER_TIMEOUT_OPTION_NOT_INT' | + 'FST_ERR_ROUTE_REWRITE_NOT_STR' | + 'FST_ERR_REOPENED_CLOSE_SERVER' | + 'FST_ERR_REOPENED_SERVER' | + 'FST_ERR_INSTANCE_ALREADY_LISTENING' | + 'FST_ERR_PLUGIN_VERSION_MISMATCH' | + 'FST_ERR_PLUGIN_NOT_PRESENT_IN_INSTANCE' | + 'FST_ERR_PLUGIN_INVALID_ASYNC_HANDLER' | + 'FST_ERR_PLUGIN_CALLBACK_NOT_FN' | + 'FST_ERR_PLUGIN_NOT_VALID' | + 'FST_ERR_ROOT_PLG_BOOTED' | + 'FST_ERR_PARENT_PLUGIN_BOOTED' | + 'FST_ERR_PLUGIN_TIMEOUT' + , FastifyErrorConstructor> diff --git a/services/slides/node_modules/fastify/types/hooks.d.ts b/services/slides/node_modules/fastify/types/hooks.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..ed045f10b1f338655d41442746c274a46f4b71bb --- /dev/null +++ b/services/slides/node_modules/fastify/types/hooks.d.ts @@ -0,0 +1,875 @@ +import { Readable } from 'node:stream' +import { FastifyInstance } from './instance' +import { RouteOptions, RouteGenericInterface } from './route' +import { RawServerBase, RawServerDefault, RawRequestDefaultExpression, RawReplyDefaultExpression, ContextConfigDefault } from './utils' +import { FastifyRequest } from './request' +import { FastifyReply } from './reply' +import { FastifyError } from '@fastify/error' +import { FastifyBaseLogger } from './logger' +import { + FastifyTypeProvider, + FastifyTypeProviderDefault +} from './type-provider' +import { RegisterOptions } from './register' +import { FastifySchema } from './schema' +import { FastifyPluginOptions } from './plugin' + +type HookHandlerDoneFunction = (err?: TError) => void + +interface RequestPayload extends Readable { + receivedEncodedLength?: number; +} + +// Lifecycle Hooks + +/** + * `onRequest` is the first hook to be executed in the request lifecycle. There was no previous hook, the next hook will be `preParsing`. + * Notice: in the `onRequest` hook, request.body will always be null, because the body parsing happens before the `preHandler` hook. + */ +export interface onRequestHookHandler< + RawServer extends RawServerBase = RawServerDefault, + RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, + RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, + RouteGeneric extends RouteGenericInterface = RouteGenericInterface, + ContextConfig = ContextConfigDefault, + SchemaCompiler extends FastifySchema = FastifySchema, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, + Logger extends FastifyBaseLogger = FastifyBaseLogger +> { + ( + this: FastifyInstance, + request: FastifyRequest, + reply: FastifyReply, + done: HookHandlerDoneFunction + ): void; +} + +export interface onRequestAsyncHookHandler< + RawServer extends RawServerBase = RawServerDefault, + RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, + RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, + RouteGeneric extends RouteGenericInterface = RouteGenericInterface, + ContextConfig = ContextConfigDefault, + SchemaCompiler extends FastifySchema = FastifySchema, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, + Logger extends FastifyBaseLogger = FastifyBaseLogger +> { + ( + this: FastifyInstance, + request: FastifyRequest, + reply: FastifyReply, + ): Promise; +} + +// helper type which infers whether onRequestHookHandler or onRequestAsyncHookHandler are +// applicable based on the specified return type. +export type onRequestMetaHookHandler< + RawServer extends RawServerBase = RawServerDefault, + RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, + RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, + RouteGeneric extends RouteGenericInterface = RouteGenericInterface, + ContextConfig = ContextConfigDefault, + SchemaCompiler extends FastifySchema = FastifySchema, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, + Logger extends FastifyBaseLogger = FastifyBaseLogger, + Return extends ReturnType> + | ReturnType> + = ReturnType> +> = Return extends ReturnType> + ? onRequestHookHandler + : onRequestAsyncHookHandler + +/** + * `preParsing` is the second hook to be executed in the request lifecycle. The previous hook was `onRequest`, the next hook will be `preValidation`. + * Notice: in the `preParsing` hook, request.body will always be null, because the body parsing happens before the `preHandler` hook. + */ +export interface preParsingHookHandler< + RawServer extends RawServerBase = RawServerDefault, + RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, + RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, + RouteGeneric extends RouteGenericInterface = RouteGenericInterface, + ContextConfig = ContextConfigDefault, + SchemaCompiler extends FastifySchema = FastifySchema, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, + Logger extends FastifyBaseLogger = FastifyBaseLogger +> { + ( + this: FastifyInstance, + request: FastifyRequest, + reply: FastifyReply, + payload: RequestPayload, + done: (err?: TError | null, res?: RequestPayload) => void + ): void; +} + +export interface preParsingAsyncHookHandler< + RawServer extends RawServerBase = RawServerDefault, + RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, + RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, + RouteGeneric extends RouteGenericInterface = RouteGenericInterface, + ContextConfig = ContextConfigDefault, + SchemaCompiler extends FastifySchema = FastifySchema, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, + Logger extends FastifyBaseLogger = FastifyBaseLogger +> { + ( + this: FastifyInstance, + request: FastifyRequest, + reply: FastifyReply, + payload: RequestPayload, + ): Promise; +} + +// helper type which infers whether preParsingHookHandler or preParsingAsyncHookHandler are +// applicable based on the specified return type. +export type preParsingMetaHookHandler< + RawServer extends RawServerBase = RawServerDefault, + RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, + RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, + RouteGeneric extends RouteGenericInterface = RouteGenericInterface, + ContextConfig = ContextConfigDefault, + SchemaCompiler extends FastifySchema = FastifySchema, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, + Logger extends FastifyBaseLogger = FastifyBaseLogger, + Return extends ReturnType> + | ReturnType> + = ReturnType> +> = Return extends ReturnType> + ? preParsingHookHandler + : preParsingAsyncHookHandler + +/** + * `preValidation` is the third hook to be executed in the request lifecycle. The previous hook was `preParsing`, the next hook will be `preHandler`. + */ +export interface preValidationHookHandler< + RawServer extends RawServerBase = RawServerDefault, + RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, + RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, + RouteGeneric extends RouteGenericInterface = RouteGenericInterface, + ContextConfig = ContextConfigDefault, + SchemaCompiler extends FastifySchema = FastifySchema, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, + Logger extends FastifyBaseLogger = FastifyBaseLogger +> { + ( + this: FastifyInstance, + request: FastifyRequest, + reply: FastifyReply, + done: HookHandlerDoneFunction + ): void; +} + +export interface preValidationAsyncHookHandler< + RawServer extends RawServerBase = RawServerDefault, + RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, + RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, + RouteGeneric extends RouteGenericInterface = RouteGenericInterface, + ContextConfig = ContextConfigDefault, + SchemaCompiler extends FastifySchema = FastifySchema, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, + Logger extends FastifyBaseLogger = FastifyBaseLogger +> { + ( + this: FastifyInstance, + request: FastifyRequest, + reply: FastifyReply, + ): Promise; +} + +// helper type which infers whether preValidationHookHandler or preValidationAsyncHookHandler are +// applicable based on the specified return type. +export type preValidationMetaHookHandler< + RawServer extends RawServerBase = RawServerDefault, + RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, + RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, + RouteGeneric extends RouteGenericInterface = RouteGenericInterface, + ContextConfig = ContextConfigDefault, + SchemaCompiler extends FastifySchema = FastifySchema, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, + Logger extends FastifyBaseLogger = FastifyBaseLogger, + Return extends ReturnType> + | ReturnType> + = ReturnType> +> = Return extends ReturnType> + ? preValidationHookHandler + : preValidationAsyncHookHandler + +/** + * `preHandler` is the fourth hook to be executed in the request lifecycle. The previous hook was `preValidation`, the next hook will be `preSerialization`. + */ +export interface preHandlerHookHandler< + RawServer extends RawServerBase = RawServerDefault, + RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, + RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, + RouteGeneric extends RouteGenericInterface = RouteGenericInterface, + ContextConfig = ContextConfigDefault, + SchemaCompiler extends FastifySchema = FastifySchema, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, + Logger extends FastifyBaseLogger = FastifyBaseLogger +> { + ( + this: FastifyInstance, + request: FastifyRequest, + reply: FastifyReply, + done: HookHandlerDoneFunction + ): void; +} + +export interface preHandlerAsyncHookHandler< + RawServer extends RawServerBase = RawServerDefault, + RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, + RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, + RouteGeneric extends RouteGenericInterface = RouteGenericInterface, + ContextConfig = ContextConfigDefault, + SchemaCompiler extends FastifySchema = FastifySchema, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, + Logger extends FastifyBaseLogger = FastifyBaseLogger +> { + ( + this: FastifyInstance, + request: FastifyRequest, + reply: FastifyReply, + ): Promise; +} + +// helper type which infers whether preHandlerHookHandler or preHandlerAsyncHookHandler are +// applicable based on the specified return type. +export type preHandlerMetaHookHandler< + RawServer extends RawServerBase = RawServerDefault, + RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, + RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, + RouteGeneric extends RouteGenericInterface = RouteGenericInterface, + ContextConfig = ContextConfigDefault, + SchemaCompiler extends FastifySchema = FastifySchema, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, + Logger extends FastifyBaseLogger = FastifyBaseLogger, + Return extends ReturnType> + | ReturnType> + = ReturnType> +> = Return extends ReturnType> + ? preHandlerHookHandler + : preHandlerAsyncHookHandler + +// This is used within the `preSerialization` and `onSend` hook handlers +interface DoneFuncWithErrOrRes { + (): void; + (err: TError): void; + (err: null, res: unknown): void; +} + +/** + * `preSerialization` is the fifth hook to be executed in the request lifecycle. The previous hook was `preHandler`, the next hook will be `onSend`. + * Note: the hook is NOT called if the payload is a string, a Buffer, a stream or null. + */ +export interface preSerializationHookHandler< + PreSerializationPayload = unknown, + RawServer extends RawServerBase = RawServerDefault, + RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, + RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, + RouteGeneric extends RouteGenericInterface = RouteGenericInterface, + ContextConfig = ContextConfigDefault, + SchemaCompiler extends FastifySchema = FastifySchema, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, + Logger extends FastifyBaseLogger = FastifyBaseLogger +> { + ( + this: FastifyInstance, + request: FastifyRequest, + reply: FastifyReply, + payload: PreSerializationPayload, + done: DoneFuncWithErrOrRes + ): void; +} + +export interface preSerializationAsyncHookHandler< + PreSerializationPayload = unknown, + RawServer extends RawServerBase = RawServerDefault, + RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, + RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, + RouteGeneric extends RouteGenericInterface = RouteGenericInterface, + ContextConfig = ContextConfigDefault, + SchemaCompiler extends FastifySchema = FastifySchema, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, + Logger extends FastifyBaseLogger = FastifyBaseLogger +> { + ( + this: FastifyInstance, + request: FastifyRequest, + reply: FastifyReply, + payload: PreSerializationPayload + ): Promise; +} + +// helper type which infers whether preSerializationHookHandler or preSerializationAsyncHookHandler are +// applicable based on the specified return type. +export type preSerializationMetaHookHandler< + PreSerializationPayload = unknown, + RawServer extends RawServerBase = RawServerDefault, + RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, + RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, + RouteGeneric extends RouteGenericInterface = RouteGenericInterface, + ContextConfig = ContextConfigDefault, + SchemaCompiler extends FastifySchema = FastifySchema, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, + Logger extends FastifyBaseLogger = FastifyBaseLogger, + Return extends ReturnType> + | ReturnType> + = ReturnType> +> = Return extends ReturnType> + ? preSerializationHookHandler + : preSerializationAsyncHookHandler + +/** + * You can change the payload with the `onSend` hook. It is the sixth hook to be executed in the request lifecycle. The previous hook was `preSerialization`, the next hook will be `onResponse`. + * Note: If you change the payload, you may only change it to a string, a Buffer, a stream, or null. + */ +export interface onSendHookHandler< + OnSendPayload = unknown, + RawServer extends RawServerBase = RawServerDefault, + RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, + RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, + RouteGeneric extends RouteGenericInterface = RouteGenericInterface, + ContextConfig = ContextConfigDefault, + SchemaCompiler extends FastifySchema = FastifySchema, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, + Logger extends FastifyBaseLogger = FastifyBaseLogger +> { + ( + this: FastifyInstance, + request: FastifyRequest, + reply: FastifyReply, + payload: OnSendPayload, + done: DoneFuncWithErrOrRes + ): void; +} + +export interface onSendAsyncHookHandler< + OnSendPayload = unknown, + RawServer extends RawServerBase = RawServerDefault, + RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, + RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, + RouteGeneric extends RouteGenericInterface = RouteGenericInterface, + ContextConfig = ContextConfigDefault, + SchemaCompiler extends FastifySchema = FastifySchema, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, + Logger extends FastifyBaseLogger = FastifyBaseLogger +> { + ( + this: FastifyInstance, + request: FastifyRequest, + reply: FastifyReply, + payload: OnSendPayload, + ): Promise; +} + +// helper type which infers whether onSendHookHandler or onSendAsyncHookHandler are +// applicable based on the specified return type. +export type onSendMetaHookHandler< + OnSendPayload = unknown, + RawServer extends RawServerBase = RawServerDefault, + RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, + RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, + RouteGeneric extends RouteGenericInterface = RouteGenericInterface, + ContextConfig = ContextConfigDefault, + SchemaCompiler extends FastifySchema = FastifySchema, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, + Logger extends FastifyBaseLogger = FastifyBaseLogger, + Return extends ReturnType> + | ReturnType> + = ReturnType> +> = Return extends ReturnType> + ? onSendHookHandler + : onSendAsyncHookHandler + +/** + * `onResponse` is the seventh and last hook in the request hook lifecycle. The previous hook was `onSend`, there is no next hook. + * The onResponse hook is executed when a response has been sent, so you will not be able to send more data to the client. It can however be useful for sending data to external services, for example to gather statistics. + */ +export interface onResponseHookHandler< + RawServer extends RawServerBase = RawServerDefault, + RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, + RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, + RouteGeneric extends RouteGenericInterface = RouteGenericInterface, + ContextConfig = ContextConfigDefault, + SchemaCompiler extends FastifySchema = FastifySchema, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, + Logger extends FastifyBaseLogger = FastifyBaseLogger +> { + ( + this: FastifyInstance, + request: FastifyRequest, + reply: FastifyReply, + done: HookHandlerDoneFunction + ): void; +} + +export interface onResponseAsyncHookHandler< + RawServer extends RawServerBase = RawServerDefault, + RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, + RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, + RouteGeneric extends RouteGenericInterface = RouteGenericInterface, + ContextConfig = ContextConfigDefault, + SchemaCompiler extends FastifySchema = FastifySchema, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, + Logger extends FastifyBaseLogger = FastifyBaseLogger +> { + ( + this: FastifyInstance, + request: FastifyRequest, + reply: FastifyReply + ): Promise; +} + +// helper type which infers whether onResponseHookHandler or onResponseAsyncHookHandler are +// applicable based on the specified return type. +export type onResponseMetaHookHandler< + RawServer extends RawServerBase = RawServerDefault, + RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, + RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, + RouteGeneric extends RouteGenericInterface = RouteGenericInterface, + ContextConfig = ContextConfigDefault, + SchemaCompiler extends FastifySchema = FastifySchema, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, + Logger extends FastifyBaseLogger = FastifyBaseLogger, + Return extends ReturnType> + | ReturnType> + = ReturnType> +> = Return extends ReturnType> + ? onResponseHookHandler + : onResponseAsyncHookHandler + +/** + * `onTimeout` is useful if you need to monitor the request timed out in your service. (if the `connectionTimeout` property is set on the fastify instance) + * The onTimeout hook is executed when a request is timed out and the http socket has been hanged up. Therefore you will not be able to send data to the client. + */ +export interface onTimeoutHookHandler< + RawServer extends RawServerBase = RawServerDefault, + RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, + RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, + RouteGeneric extends RouteGenericInterface = RouteGenericInterface, + ContextConfig = ContextConfigDefault, + SchemaCompiler extends FastifySchema = FastifySchema, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, + Logger extends FastifyBaseLogger = FastifyBaseLogger +> { + ( + this: FastifyInstance, + request: FastifyRequest, + reply: FastifyReply, + done: HookHandlerDoneFunction + ): void; +} + +export interface onTimeoutAsyncHookHandler< + RawServer extends RawServerBase = RawServerDefault, + RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, + RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, + RouteGeneric extends RouteGenericInterface = RouteGenericInterface, + ContextConfig = ContextConfigDefault, + SchemaCompiler extends FastifySchema = FastifySchema, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, + Logger extends FastifyBaseLogger = FastifyBaseLogger +> { + ( + this: FastifyInstance, + request: FastifyRequest, + reply: FastifyReply + ): Promise; +} + +// helper type which infers whether onTimeoutHookHandler or onTimeoutAsyncHookHandler are +// applicable based on the specified return type. +export type onTimeoutMetaHookHandler< + RawServer extends RawServerBase = RawServerDefault, + RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, + RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, + RouteGeneric extends RouteGenericInterface = RouteGenericInterface, + ContextConfig = ContextConfigDefault, + SchemaCompiler extends FastifySchema = FastifySchema, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, + Logger extends FastifyBaseLogger = FastifyBaseLogger, + Return extends ReturnType> + | ReturnType> + = ReturnType> +> = Return extends ReturnType> + ? onTimeoutHookHandler + : onTimeoutAsyncHookHandler + +/** + * This hook is useful if you need to do some custom error logging or add some specific header in case of error. + * It is not intended for changing the error, and calling reply.send will throw an exception. + * This hook will be executed before the customErrorHandler. + * Notice: unlike the other hooks, pass an error to the done function is not supported. + */ +export interface onErrorHookHandler< + RawServer extends RawServerBase = RawServerDefault, + RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, + RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, + RouteGeneric extends RouteGenericInterface = RouteGenericInterface, + ContextConfig = ContextConfigDefault, + TError extends Error = FastifyError, + SchemaCompiler extends FastifySchema = FastifySchema, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, + Logger extends FastifyBaseLogger = FastifyBaseLogger +> { + ( + this: FastifyInstance, + request: FastifyRequest, + reply: FastifyReply, + error: TError, + done: () => void + ): void; +} + +export interface onErrorAsyncHookHandler< + RawServer extends RawServerBase = RawServerDefault, + RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, + RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, + RouteGeneric extends RouteGenericInterface = RouteGenericInterface, + ContextConfig = ContextConfigDefault, + TError extends Error = FastifyError, + SchemaCompiler extends FastifySchema = FastifySchema, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, + Logger extends FastifyBaseLogger = FastifyBaseLogger +> { + ( + this: FastifyInstance, + request: FastifyRequest, + reply: FastifyReply, + error: TError + ): Promise; +} + +// helper type which infers whether onErrorHookHandler or onErrorAsyncHookHandler are +// applicable based on the specified return type. +export type onErrorMetaHookHandler< + RawServer extends RawServerBase = RawServerDefault, + RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, + RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, + RouteGeneric extends RouteGenericInterface = RouteGenericInterface, + ContextConfig = ContextConfigDefault, + TError extends Error = FastifyError, + SchemaCompiler extends FastifySchema = FastifySchema, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, + Logger extends FastifyBaseLogger = FastifyBaseLogger, + Return extends ReturnType> + | ReturnType> + = ReturnType> +> = Return extends ReturnType> + ? onErrorHookHandler + : onErrorAsyncHookHandler + +/** + * `onRequestAbort` is useful if you need to monitor the if the client aborts the request (if the `request.raw.aborted` property is set to `true`). + * The `onRequestAbort` hook is executed when a client closes the connection before the entire request has been received. Therefore, you will not be able to send data to the client. + * Notice: client abort detection is not completely reliable. See: https://github.com/fastify/fastify/blob/main/docs/Guides/Detecting-When-Clients-Abort.md + */ +export interface onRequestAbortHookHandler< + RawServer extends RawServerBase = RawServerDefault, + RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, + RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, + RouteGeneric extends RouteGenericInterface = RouteGenericInterface, + ContextConfig = ContextConfigDefault, + SchemaCompiler extends FastifySchema = FastifySchema, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, + Logger extends FastifyBaseLogger = FastifyBaseLogger +> { + ( + this: FastifyInstance, + request: FastifyRequest, + done: HookHandlerDoneFunction + ): void; +} + +export interface onRequestAbortAsyncHookHandler< + RawServer extends RawServerBase = RawServerDefault, + RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, + RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, + RouteGeneric extends RouteGenericInterface = RouteGenericInterface, + ContextConfig = ContextConfigDefault, + SchemaCompiler extends FastifySchema = FastifySchema, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, + Logger extends FastifyBaseLogger = FastifyBaseLogger +> { + ( + this: FastifyInstance, + request: FastifyRequest, + ): Promise; +} + +// helper type which infers whether onRequestAbortHookHandler or onRequestAbortHookHandler are +// applicable based on the specified return type. +export type onRequestAbortMetaHookHandler< + RawServer extends RawServerBase = RawServerDefault, + RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, + RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, + RouteGeneric extends RouteGenericInterface = RouteGenericInterface, + ContextConfig = ContextConfigDefault, + SchemaCompiler extends FastifySchema = FastifySchema, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, + Logger extends FastifyBaseLogger = FastifyBaseLogger, + Return extends ReturnType> + | ReturnType> + = ReturnType> +> = Return extends ReturnType> + ? onRequestAbortHookHandler + : onRequestAbortAsyncHookHandler + +export type LifecycleHook = 'onRequest' +| 'preParsing' +| 'preValidation' +| 'preHandler' +| 'preSerialization' +| 'onSend' +| 'onResponse' +| 'onRequest' +| 'onError' +| 'onTimeout' +| 'onRequestAbort' + +export type LifecycleHookLookup = K extends 'onRequest' + ? onRequestHookHandler + : K extends 'preParsing' + ? preParsingHookHandler + : K extends 'preValidation' + ? preValidationHookHandler + : K extends 'preHandler' + ? preHandlerHookHandler + : K extends 'preSerialization' + ? preSerializationHookHandler + : K extends 'onSend' + ? onSendHookHandler + : K extends 'onResponse' + ? onResponseHookHandler + : K extends 'onRequest' + ? onRequestHookHandler + : K extends 'onError' + ? onErrorHookHandler + : K extends 'onTimeout' + ? onTimeoutHookHandler + : K extends 'onRequestAbort' + ? onRequestAbortHookHandler + : never + +export type LifecycleHookAsyncLookup = K extends 'onRequest' + ? onRequestAsyncHookHandler + : K extends 'preParsing' + ? preParsingAsyncHookHandler + : K extends 'preValidation' + ? preValidationAsyncHookHandler + : K extends 'preHandler' + ? preHandlerAsyncHookHandler + : K extends 'preSerialization' + ? preSerializationAsyncHookHandler + : K extends 'onSend' + ? onSendAsyncHookHandler + : K extends 'onResponse' + ? onResponseAsyncHookHandler + : K extends 'onRequest' + ? onRequestAsyncHookHandler + : K extends 'onError' + ? onErrorAsyncHookHandler + : K extends 'onTimeout' + ? onTimeoutAsyncHookHandler + : K extends 'onRequestAbort' + ? onRequestAbortAsyncHookHandler + : never + +// Application Hooks + +/** + * Triggered when a new route is registered. Listeners are passed a routeOptions object as the sole parameter. The interface is synchronous, and, as such, the listener does not get passed a callback + */ +export interface onRouteHookHandler< + RawServer extends RawServerBase = RawServerDefault, + RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, + RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, + RouteGeneric extends RouteGenericInterface = RouteGenericInterface, + ContextConfig = ContextConfigDefault, + SchemaCompiler extends FastifySchema = FastifySchema, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, + Logger extends FastifyBaseLogger = FastifyBaseLogger +> { + ( + this: FastifyInstance, + opts: RouteOptions & { routePath: string; path: string; prefix: string } + ): Promise | void; +} + +/** + * Triggered when a new plugin is registered and a new encapsulation context is created. The hook will be executed before the registered code. + * This hook can be useful if you are developing a plugin that needs to know when a plugin context is formed, and you want to operate in that specific context. + * Note: This hook will not be called if a plugin is wrapped inside fastify-plugin. + */ +export interface onRegisterHookHandler< + RawServer extends RawServerBase = RawServerDefault, + RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, + RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, + Logger extends FastifyBaseLogger = FastifyBaseLogger, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, + Options extends FastifyPluginOptions = FastifyPluginOptions +> { + ( + this: FastifyInstance, + instance: FastifyInstance, + opts: RegisterOptions & Options + ): Promise | void; +} + +/** + * Triggered when fastify.listen() or fastify.ready() is invoked to start the server. It is useful when plugins need a "ready" event, for example to load data before the server start listening for requests. + */ +export interface onReadyHookHandler< + RawServer extends RawServerBase = RawServerDefault, + RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, + RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, + Logger extends FastifyBaseLogger = FastifyBaseLogger, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault +> { + ( + this: FastifyInstance, + done: HookHandlerDoneFunction + ): void; +} + +export interface onReadyAsyncHookHandler< + RawServer extends RawServerBase = RawServerDefault, + RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, + RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, + Logger extends FastifyBaseLogger = FastifyBaseLogger, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault +> { + ( + this: FastifyInstance, + ): Promise; +} + +/** + * Triggered when fastify.listen() is invoked to start the server. It is useful when plugins need a "onListen" event, for example to run logics after the server start listening for requests. + */ +export interface onListenHookHandler< + RawServer extends RawServerBase = RawServerDefault, + RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, + RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, + Logger extends FastifyBaseLogger = FastifyBaseLogger, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault +> { + ( + this: FastifyInstance, + done: HookHandlerDoneFunction + ): void; +} + +export interface onListenAsyncHookHandler< + RawServer extends RawServerBase = RawServerDefault, + RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, + RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, + Logger extends FastifyBaseLogger = FastifyBaseLogger, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault +> { + ( + this: FastifyInstance, + ): Promise; +} +/** + * Triggered when fastify.close() is invoked to stop the server. It is useful when plugins need a "shutdown" event, for example to close an open connection to a database. + */ +export interface onCloseHookHandler< + RawServer extends RawServerBase = RawServerDefault, + RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, + RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, + Logger extends FastifyBaseLogger = FastifyBaseLogger, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault +> { + ( + this: FastifyInstance, + instance: FastifyInstance, + done: HookHandlerDoneFunction + ): void; +} + +export interface onCloseAsyncHookHandler< + RawServer extends RawServerBase = RawServerDefault, + RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, + RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, + Logger extends FastifyBaseLogger = FastifyBaseLogger, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault +> { + ( + this: FastifyInstance, + instance: FastifyInstance + ): Promise; +} + +/** + * Triggered when fastify.close() is invoked to stop the server. It is useful when plugins need to cancel some state to allow the server to close successfully. + */ +export interface preCloseHookHandler< + RawServer extends RawServerBase = RawServerDefault, + RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, + RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, + Logger extends FastifyBaseLogger = FastifyBaseLogger, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault +> { + ( + this: FastifyInstance, + done: HookHandlerDoneFunction + ): void; +} + +export interface preCloseAsyncHookHandler< + RawServer extends RawServerBase = RawServerDefault, + RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, + RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, + Logger extends FastifyBaseLogger = FastifyBaseLogger, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault +> { + ( + this: FastifyInstance, + ): Promise; +} + +export type ApplicationHook = 'onRoute' +| 'onRegister' +| 'onReady' +| 'onListen' +| 'onClose' +| 'preClose' + +export type ApplicationHookLookup = K extends 'onRegister' + ? onRegisterHookHandler + : K extends 'onReady' + ? onReadyHookHandler + : K extends 'onListen' + ? onListenHookHandler + : K extends 'onClose' + ? onCloseHookHandler + : K extends 'preClose' + ? preCloseHookHandler + : K extends 'onRoute' + ? onRouteHookHandler + : never + +export type ApplicationHookAsyncLookup = K extends 'onRegister' + ? onRegisterHookHandler + : K extends 'onReady' + ? onReadyAsyncHookHandler + : K extends 'onListen' + ? onListenAsyncHookHandler + : K extends 'onClose' + ? onCloseAsyncHookHandler + : K extends 'preClose' + ? preCloseAsyncHookHandler + : never + +export type HookLookup = K extends ApplicationHook + ? ApplicationHookLookup + : K extends LifecycleHook + ? LifecycleHookLookup + : never + +export type HookAsyncLookup = K extends ApplicationHook + ? ApplicationHookAsyncLookup + : K extends LifecycleHook + ? LifecycleHookAsyncLookup + : never diff --git a/services/slides/node_modules/fastify/types/instance.d.ts b/services/slides/node_modules/fastify/types/instance.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..0fd1baafe1329b5b9c8ea1fd24b3039bbff7aaa2 --- /dev/null +++ b/services/slides/node_modules/fastify/types/instance.d.ts @@ -0,0 +1,609 @@ +import { FastifyError } from '@fastify/error' +import { ConstraintStrategy, FindResult, HTTPVersion } from 'find-my-way' +import * as http from 'node:http' +import { InjectOptions, CallbackFunc as LightMyRequestCallback, Chain as LightMyRequestChain, Response as LightMyRequestResponse } from 'light-my-request' +import { AddressInfo } from 'node:net' +import { AddContentTypeParser, ConstructorAction, FastifyBodyParser, ProtoAction, getDefaultJsonParser, hasContentTypeParser, removeAllContentTypeParsers, removeContentTypeParser } from './content-type-parser' +import { ApplicationHook, HookAsyncLookup, HookLookup, LifecycleHook, onCloseAsyncHookHandler, onCloseHookHandler, onErrorAsyncHookHandler, onErrorHookHandler, onListenAsyncHookHandler, onListenHookHandler, onReadyAsyncHookHandler, onReadyHookHandler, onRegisterHookHandler, onRequestAbortAsyncHookHandler, onRequestAbortHookHandler, onRequestAsyncHookHandler, onRequestHookHandler, onResponseAsyncHookHandler, onResponseHookHandler, onRouteHookHandler, onSendAsyncHookHandler, onSendHookHandler, onTimeoutAsyncHookHandler, onTimeoutHookHandler, preCloseAsyncHookHandler, preCloseHookHandler, preHandlerAsyncHookHandler, preHandlerHookHandler, preParsingAsyncHookHandler, preParsingHookHandler, preSerializationAsyncHookHandler, preSerializationHookHandler, preValidationAsyncHookHandler, preValidationHookHandler } from './hooks' +import { FastifyBaseLogger, FastifyChildLoggerFactory } from './logger' +import { FastifyRegister } from './register' +import { FastifyReply } from './reply' +import { FastifyRequest } from './request' +import { RouteGenericInterface, RouteHandlerMethod, RouteOptions, RouteShorthandMethod } from './route' +import { + FastifySchema, + FastifySchemaCompiler, + FastifySchemaControllerOptions, + FastifySerializerCompiler, + SchemaErrorFormatter +} from './schema' +import { + FastifyTypeProvider, + FastifyTypeProviderDefault, + SafePromiseLike +} from './type-provider' +import { ContextConfigDefault, HTTPMethods, RawReplyDefaultExpression, RawRequestDefaultExpression, RawServerBase, RawServerDefault } from './utils' +import { FastifyRouterOptions } from '../fastify' + +export interface PrintRoutesOptions { + method?: HTTPMethods; + includeMeta?: boolean | (string | symbol)[] + commonPrefix?: boolean + includeHooks?: boolean +} + +type AsyncFunction = (...args: any) => Promise + +export interface FastifyListenOptions { + /** + * Default to `0` (picks the first available open port). + */ + port?: number; + /** + * Default to `localhost`. + */ + host?: string; + /** + * Will be ignored if `port` is specified. + * @see [Identifying paths for IPC connections](https://nodejs.org/api/net.html#identifying-paths-for-ipc-connections). + */ + path?: string; + /** + * Specify the maximum length of the queue of pending connections. + * The actual length will be determined by the OS through sysctl settings such as `tcp_max_syn_backlog` and `somaxconn` on Linux. + * Default to `511`. + */ + backlog?: number; + /** + * Default to `false`. + */ + exclusive?: boolean; + /** + * For IPC servers makes the pipe readable for all users. + * Default to `false`. + */ + readableAll?: boolean; + /** + * For IPC servers makes the pipe writable for all users. + * Default to `false`. + */ + writableAll?: boolean; + /** + * For TCP servers, setting `ipv6Only` to `true` will disable dual-stack support, i.e., binding to host `::` won't make `0.0.0.0` be bound. + * Default to `false`. + */ + ipv6Only?: boolean; + /** + * An AbortSignal that may be used to close a listening server. + * @since This option is available only in Node.js v15.6.0 and greater + */ + signal?: AbortSignal; + + /** + * Function that resolves text to log after server has been successfully started + * @param address + */ + listenTextResolver?: (address: string) => string; +} + +type NotInInterface = Key extends keyof _Interface ? never : Key +type FindMyWayVersion = RawServer extends http.Server ? HTTPVersion.V1 : HTTPVersion.V2 +type FindMyWayFindResult = FindResult> + +type GetterSetter = T | { + getter: (this: This) => T, + setter?: (this: This, value: T) => void +} + +type DecorationMethod = { + < + // Need to disable "no-use-before-define" to maintain backwards compatibility, as else decorate would suddenly mean something new + + T extends (P extends keyof This ? This[P] : unknown), + P extends string | symbol = string | symbol + >(property: P, + value: GetterSetter any + ? (this: This, ...args: Parameters) => ReturnType + : T + >, + dependencies?: string[] + ): Return; + + (property: string | symbol): Return; + + (property: string | symbol, value: null | undefined, dependencies: string[]): Return; +} + +/** + * Fastify server instance. Returned by the core `fastify()` method. + */ +export interface FastifyInstance< + RawServer extends RawServerBase = RawServerDefault, + RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, + RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, + Logger extends FastifyBaseLogger = FastifyBaseLogger, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault +> { + server: RawServer; + pluginName: string; + prefix: string; + version: string; + log: Logger; + listeningOrigin: string; + addresses(): AddressInfo[] + withTypeProvider(): FastifyInstance; + + addSchema(schema: unknown): FastifyInstance; + getSchema(schemaId: string): unknown; + getSchemas(): Record; + + after(): FastifyInstance & SafePromiseLike; + after(afterListener: (err: Error | null) => void): FastifyInstance; + + close(): Promise; + close(closeListener: () => void): undefined; + + /** Alias for {@linkcode FastifyInstance.close()} */ + + // @ts-ignore - type only available for @types/node >=17 or typescript >= 5.2 + [Symbol.asyncDispose](): Promise; + + // should be able to define something useful with the decorator getter/setter pattern using Generics to enforce the users function returns what they expect it to + decorate: DecorationMethod>; + decorateRequest: DecorationMethod>; + decorateReply: DecorationMethod>; + + getDecorator(name: string | symbol): T; + + hasDecorator(decorator: string | symbol): boolean; + hasRequestDecorator(decorator: string | symbol): boolean; + hasReplyDecorator(decorator: string | symbol): boolean; + hasPlugin(name: string): boolean; + + addConstraintStrategy(strategy: ConstraintStrategy, unknown>): void; + hasConstraintStrategy(strategyName: string): boolean; + + inject(opts: InjectOptions | string, cb: LightMyRequestCallback): void; + inject(opts: InjectOptions | string): Promise; + inject(): LightMyRequestChain; + + listen(opts: FastifyListenOptions, callback: (err: Error | null, address: string) => void): void; + listen(opts?: FastifyListenOptions): Promise; + listen(callback: (err: Error | null, address: string) => void): void; + + ready(): FastifyInstance & SafePromiseLike; + ready(readyListener: (err: Error | null) => void | Promise): FastifyInstance; + + register: FastifyRegister & SafePromiseLike>; + + routing(req: RawRequest, res: RawReply): void; + + route< + RouteGeneric extends RouteGenericInterface = RouteGenericInterface, + ContextConfig = ContextConfigDefault, + const SchemaCompiler extends FastifySchema = FastifySchema + >(opts: RouteOptions): FastifyInstance; + + delete: RouteShorthandMethod; + get: RouteShorthandMethod; + head: RouteShorthandMethod; + patch: RouteShorthandMethod; + post: RouteShorthandMethod; + put: RouteShorthandMethod; + options: RouteShorthandMethod; + propfind: RouteShorthandMethod; + proppatch: RouteShorthandMethod; + mkcalendar: RouteShorthandMethod; + mkcol: RouteShorthandMethod; + copy: RouteShorthandMethod; + move: RouteShorthandMethod; + lock: RouteShorthandMethod; + unlock: RouteShorthandMethod; + trace: RouteShorthandMethod; + report: RouteShorthandMethod; + search: RouteShorthandMethod; + all: RouteShorthandMethod; + + hasRoute< + RouteGeneric extends RouteGenericInterface = RouteGenericInterface, + ContextConfig = ContextConfigDefault, + SchemaCompiler extends FastifySchema = FastifySchema + >(opts: Pick, 'method' | 'url' | 'constraints'>): boolean; + + findRoute< + RouteGeneric extends RouteGenericInterface = RouteGenericInterface, + ContextConfig = ContextConfigDefault, + SchemaCompiler extends FastifySchema = FastifySchema + >(opts: Pick, 'method' | 'url' | 'constraints'>): Omit, 'store'>; + + // addHook: overloads + + // Lifecycle addHooks + + /** + * `onRequest` is the first hook to be executed in the request lifecycle. There was no previous hook, the next hook will be `preParsing`. + * Notice: in the `onRequest` hook, request.body will always be null, because the body parsing happens before the `preHandler` hook. + */ + addHook< + RouteGeneric extends RouteGenericInterface = RouteGenericInterface, + ContextConfig = ContextConfigDefault, + SchemaCompiler extends FastifySchema = FastifySchema, + Logger extends FastifyBaseLogger = FastifyBaseLogger, + Fn extends onRequestHookHandler | onRequestAsyncHookHandler = onRequestHookHandler + >( + name: 'onRequest', + hook: Fn extends unknown ? Fn extends AsyncFunction ? onRequestAsyncHookHandler : onRequestHookHandler : Fn, + ): FastifyInstance; + + /** + * `preParsing` is the second hook to be executed in the request lifecycle. The previous hook was `onRequest`, the next hook will be `preValidation`. + * Notice: in the `preParsing` hook, request.body will always be null, because the body parsing happens before the `preHandler` hook. + */ + addHook< + RouteGeneric extends RouteGenericInterface = RouteGenericInterface, + ContextConfig = ContextConfigDefault, + SchemaCompiler extends FastifySchema = FastifySchema, + Logger extends FastifyBaseLogger = FastifyBaseLogger, + Fn extends preParsingHookHandler | preParsingAsyncHookHandler = preParsingHookHandler + >( + name: 'preParsing', + hook: Fn extends unknown ? Fn extends AsyncFunction ? preParsingAsyncHookHandler : preParsingHookHandler : Fn, + ): FastifyInstance; + + /** + * `preValidation` is the third hook to be executed in the request lifecycle. The previous hook was `preParsing`, the next hook will be `preHandler`. + */ + addHook< + RouteGeneric extends RouteGenericInterface = RouteGenericInterface, + ContextConfig = ContextConfigDefault, + SchemaCompiler extends FastifySchema = FastifySchema, + Logger extends FastifyBaseLogger = FastifyBaseLogger, + Fn extends preValidationHookHandler | preValidationAsyncHookHandler = preValidationHookHandler + >( + name: 'preValidation', + hook: Fn extends unknown ? Fn extends AsyncFunction ? preValidationAsyncHookHandler : preValidationHookHandler : Fn, + ): FastifyInstance; + + /** + * `preHandler` is the fourth hook to be executed in the request lifecycle. The previous hook was `preValidation`, the next hook will be `preSerialization`. + */ + addHook< + RouteGeneric extends RouteGenericInterface = RouteGenericInterface, + ContextConfig = ContextConfigDefault, + SchemaCompiler extends FastifySchema = FastifySchema, + Logger extends FastifyBaseLogger = FastifyBaseLogger, + Fn extends preHandlerHookHandler | preHandlerAsyncHookHandler = preHandlerHookHandler + >( + name: 'preHandler', + hook: Fn extends unknown ? Fn extends AsyncFunction ? preHandlerAsyncHookHandler : preHandlerHookHandler : Fn, + ): FastifyInstance; + + /** + * `preSerialization` is the fifth hook to be executed in the request lifecycle. The previous hook was `preHandler`, the next hook will be `onSend`. + * Note: the hook is NOT called if the payload is a string, a Buffer, a stream or null. + */ + addHook< + PreSerializationPayload = unknown, + RouteGeneric extends RouteGenericInterface = RouteGenericInterface, + ContextConfig = ContextConfigDefault, + SchemaCompiler extends FastifySchema = FastifySchema, + Logger extends FastifyBaseLogger = FastifyBaseLogger, + Fn extends preSerializationHookHandler | preSerializationAsyncHookHandler = preSerializationHookHandler + >( + name: 'preSerialization', + hook: Fn extends unknown ? Fn extends AsyncFunction ? preSerializationAsyncHookHandler : preSerializationHookHandler : Fn, + ): FastifyInstance; + + /** + * You can change the payload with the `onSend` hook. It is the sixth hook to be executed in the request lifecycle. The previous hook was `preSerialization`, the next hook will be `onResponse`. + * Note: If you change the payload, you may only change it to a string, a Buffer, a stream, or null. + */ + addHook< + OnSendPayload = unknown, + RouteGeneric extends RouteGenericInterface = RouteGenericInterface, + ContextConfig = ContextConfigDefault, + SchemaCompiler extends FastifySchema = FastifySchema, + Logger extends FastifyBaseLogger = FastifyBaseLogger, + Fn extends onSendHookHandler | onSendAsyncHookHandler = onSendHookHandler + >( + name: 'onSend', + hook: Fn extends unknown ? Fn extends AsyncFunction ? onSendAsyncHookHandler : onSendHookHandler : Fn, + ): FastifyInstance; + + /** + * `onResponse` is the seventh and last hook in the request hook lifecycle. The previous hook was `onSend`, there is no next hook. + * The onResponse hook is executed when a response has been sent, so you will not be able to send more data to the client. It can however be useful for sending data to external services, for example to gather statistics. + */ + addHook< + RouteGeneric extends RouteGenericInterface = RouteGenericInterface, + ContextConfig = ContextConfigDefault, + SchemaCompiler extends FastifySchema = FastifySchema, + Logger extends FastifyBaseLogger = FastifyBaseLogger, + Fn extends onResponseHookHandler | onResponseAsyncHookHandler = onResponseHookHandler + >( + name: 'onResponse', + hook: Fn extends unknown ? Fn extends AsyncFunction ? onResponseAsyncHookHandler : onResponseHookHandler : Fn, + ): FastifyInstance; + + /** + * `onTimeout` is useful if you need to monitor the request timed out in your service. (if the `connectionTimeout` property is set on the fastify instance) + * The onTimeout hook is executed when a request is timed out and the http socket has been hanged up. Therefore you will not be able to send data to the client. + */ + addHook< + RouteGeneric extends RouteGenericInterface = RouteGenericInterface, + ContextConfig = ContextConfigDefault, + SchemaCompiler extends FastifySchema = FastifySchema, + Logger extends FastifyBaseLogger = FastifyBaseLogger, + Fn extends onTimeoutHookHandler | onTimeoutAsyncHookHandler = onTimeoutHookHandler + >( + name: 'onTimeout', + hook: Fn extends unknown ? Fn extends AsyncFunction ? onTimeoutAsyncHookHandler : onTimeoutHookHandler : Fn, + ): FastifyInstance; + + /** + * `onRequestAbort` is useful if you need to monitor the if the client aborts the request (if the `request.raw.aborted` property is set to `true`). + * The `onRequestAbort` hook is executed when a client closes the connection before the entire request has been received. Therefore, you will not be able to send data to the client. + * Notice: client abort detection is not completely reliable. See: https://github.com/fastify/fastify/blob/main/docs/Guides/Detecting-When-Clients-Abort.md + */ + addHook< + RouteGeneric extends RouteGenericInterface = RouteGenericInterface, + ContextConfig = ContextConfigDefault, + SchemaCompiler extends FastifySchema = FastifySchema, + Logger extends FastifyBaseLogger = FastifyBaseLogger, + Fn extends onRequestAbortHookHandler | onRequestAbortAsyncHookHandler = onRequestAbortHookHandler + >( + name: 'onRequestAbort', + hook: Fn extends unknown ? Fn extends AsyncFunction ? onRequestAbortAsyncHookHandler : onRequestAbortHookHandler : Fn, + ): FastifyInstance; + + /** + * This hook is useful if you need to do some custom error logging or add some specific header in case of error. + * It is not intended for changing the error, and calling reply.send will throw an exception. + * This hook will be executed only after the customErrorHandler has been executed, and only if the customErrorHandler sends an error back to the user (Note that the default customErrorHandler always sends the error back to the user). + * Notice: unlike the other hooks, pass an error to the done function is not supported. + */ + addHook< + RouteGeneric extends RouteGenericInterface = RouteGenericInterface, + ContextConfig = ContextConfigDefault, + SchemaCompiler extends FastifySchema = FastifySchema, + Logger extends FastifyBaseLogger = FastifyBaseLogger, + Fn extends onErrorHookHandler | onErrorAsyncHookHandler = onErrorHookHandler + >( + name: 'onError', + hook: Fn extends unknown ? Fn extends AsyncFunction ? onErrorAsyncHookHandler : onErrorHookHandler : Fn, + ): FastifyInstance; + + // Application addHooks + + /** + * Triggered when a new route is registered. Listeners are passed a routeOptions object as the sole parameter. The interface is synchronous, and, as such, the listener does not get passed a callback + */ + addHook< + RouteGeneric extends RouteGenericInterface = RouteGenericInterface, + ContextConfig = ContextConfigDefault, + SchemaCompiler extends FastifySchema = FastifySchema, + Logger extends FastifyBaseLogger = FastifyBaseLogger + >( + name: 'onRoute', + hook: onRouteHookHandler + ): FastifyInstance; + + /** + * Triggered when a new plugin is registered and a new encapsulation context is created. The hook will be executed before the registered code. + * This hook can be useful if you are developing a plugin that needs to know when a plugin context is formed, and you want to operate in that specific context. + * Note: This hook will not be called if a plugin is wrapped inside fastify-plugin. + */ + addHook( + name: 'onRegister', + hook: onRegisterHookHandler + ): FastifyInstance; + + /** + * Triggered when fastify.listen() or fastify.ready() is invoked to start the server. It is useful when plugins need a "ready" event, for example to load data before the server start listening for requests. + */ + addHook< + Fn extends onReadyHookHandler | onReadyAsyncHookHandler = onReadyHookHandler + >( + name: 'onReady', + hook: Fn extends unknown ? Fn extends AsyncFunction ? onReadyAsyncHookHandler : onReadyHookHandler : Fn, + ): FastifyInstance; + + /** + * Triggered when fastify.listen() is invoked to start the server. It is useful when plugins need a "onListen" event, for example to run logics after the server start listening for requests. + */ + addHook< + Fn extends onListenHookHandler | onListenAsyncHookHandler = onListenHookHandler + >( + name: 'onListen', + hook: Fn extends unknown ? Fn extends AsyncFunction ? onListenAsyncHookHandler : onListenHookHandler : Fn, + ): FastifyInstance; + + /** + * Triggered when fastify.close() is invoked to stop the server. It is useful when plugins need a "shutdown" event, for example to close an open connection to a database. + */ + addHook< + Fn extends onCloseHookHandler | onCloseAsyncHookHandler = onCloseHookHandler + >( + name: 'onClose', + hook: Fn extends unknown ? Fn extends AsyncFunction ? onCloseAsyncHookHandler : onCloseHookHandler : Fn, + ): FastifyInstance; + + /** + * Triggered when fastify.close() is invoked to stop the server. It is useful when plugins need to cancel some state to allow the server to close successfully. + */ + addHook< + Fn extends preCloseHookHandler | preCloseAsyncHookHandler = preCloseHookHandler + >( + name: 'preClose', + hook: Fn extends unknown ? Fn extends AsyncFunction ? preCloseAsyncHookHandler : preCloseHookHandler : Fn, + ): FastifyInstance; + + addHook< + K extends ApplicationHook | LifecycleHook, + Fn extends (...args: any) => Promise | any + > ( + name: K, + hook: Fn extends unknown ? Fn extends AsyncFunction ? HookAsyncLookup : HookLookup : Fn + ): FastifyInstance; + + /** + * Set the 404 handler + */ + setNotFoundHandler ( + handler: RouteHandlerMethod + ): FastifyInstance; + + setNotFoundHandler ( + opts: { + preValidation?: preValidationHookHandler | preValidationAsyncHookHandler | preValidationHookHandler[] | preValidationAsyncHookHandler[]; + preHandler?: preHandlerHookHandler | preHandlerAsyncHookHandler | preHandlerHookHandler[] | preHandlerAsyncHookHandler[]; + }, + handler: RouteHandlerMethod + ): FastifyInstance + + /** + * Fastify default error handler + */ + errorHandler: (error: TError, request: FastifyRequest, reply: FastifyReply) => void; + + /** + * Set a function that will be invoked whenever an exception is thrown during the request lifecycle. + */ + setErrorHandler( + handler: (this: FastifyInstance, error: TError, request: FastifyRequest, reply: FastifyReply) => any | Promise + ): FastifyInstance; + + /** + * Set a function that will generate a request-ids + */ + setGenReqId(fn: (req: RawRequestDefaultExpression) => string): FastifyInstance; + + /** + * Hook function that is called when creating a child logger instance for each request + * which allows for modifying or adding child logger bindings and logger options, or + * returning a completely custom child logger implementation. + */ + childLoggerFactory: FastifyChildLoggerFactory; + + /** + * Hook function that is called when creating a child logger instance for each request + * which allows for modifying or adding child logger bindings and logger options, or + * returning a completely custom child logger implementation. + * + * Child logger bindings have a performance advantage over per-log bindings, because + * they are pre-serialised by Pino when the child logger is created. + * + * For example: + * ``` + * function childLoggerFactory(logger, bindings, opts, rawReq) { + * // Calculate additional bindings from the request + * bindings.traceContext = rawReq.headers['x-cloud-trace-context'] + * return logger.child(bindings, opts); + * } + * ``` + */ + setChildLoggerFactory(factory: FastifyChildLoggerFactory): FastifyInstance; + + /** + * Fastify schema validator for all routes. + */ + validatorCompiler: FastifySchemaCompiler | undefined; + + /** + * Set the schema validator for all routes. + */ + setValidatorCompiler(schemaCompiler: FastifySchemaCompiler): FastifyInstance; + + /** + * Fastify schema serializer for all routes. + */ + serializerCompiler: FastifySerializerCompiler | undefined; + + /** + * Set the schema serializer for all routes. + */ + setSerializerCompiler(schemaCompiler: FastifySerializerCompiler): FastifyInstance; + + /** + * Set the schema controller for all routes. + */ + setSchemaController(schemaControllerOpts: FastifySchemaControllerOptions): FastifyInstance; + + /** + * Set the reply serializer for all routes. + */ + setReplySerializer(replySerializer: (payload: unknown, statusCode: number) => string): FastifyInstance; + + /* + * Set the schema error formatter for all routes. + */ + setSchemaErrorFormatter(errorFormatter: SchemaErrorFormatter): FastifyInstance; + /** + * Add a content type parser + */ + addContentTypeParser: AddContentTypeParser; + hasContentTypeParser: hasContentTypeParser; + /** + * Remove an existing content type parser + */ + removeContentTypeParser: removeContentTypeParser + /** + * Remove all content type parsers, including the default ones + */ + removeAllContentTypeParsers: removeAllContentTypeParsers + /** + * Returns an array of strings containing the list of supported HTTP methods + */ + supportedMethods: string[] + /** + * Add a non-standard HTTP method + * + * Methods defined by default include `GET`, `HEAD`, `TRACE`, `DELETE`, + * `OPTIONS`, `PATCH`, `PUT` and `POST` + */ + addHttpMethod(method: string, methodOptions?: { hasBody: boolean }): FastifyInstance; + /** + * Fastify default JSON parser + */ + getDefaultJsonParser: getDefaultJsonParser; + /** + * Fastify default plain text parser + */ + defaultTextParser: FastifyBodyParser; + + /** + * Prints the representation of the internal radix tree used by the router + */ + printRoutes(opts?: PrintRoutesOptions): string; + + /** + * Prints the representation of the plugin tree used by avvio, the plugin registration system + */ + printPlugins(): string; + + /** + * Frozen read-only object registering the initial options passed down by the user to the fastify instance + */ + initialConfig: Readonly<{ + connectionTimeout?: number, + keepAliveTimeout?: number, + forceCloseConnections?: boolean, + bodyLimit?: number, + caseSensitive?: boolean, + allowUnsafeRegex?: boolean, + http2?: boolean, + https?: boolean | Readonly<{ allowHTTP1: boolean }>, + ignoreTrailingSlash?: boolean, + ignoreDuplicateSlashes?: boolean, + disableRequestLogging?: boolean | ((req: FastifyRequest) => boolean), + maxParamLength?: number, + onProtoPoisoning?: ProtoAction, + onConstructorPoisoning?: ConstructorAction, + pluginTimeout?: number, + requestIdHeader?: string | false, + requestIdLogLabel?: string, + http2SessionTimeout?: number, + useSemicolonDelimiter?: boolean, + routerOptions?: FastifyRouterOptions + }> +} diff --git a/services/slides/node_modules/fastify/types/logger.d.ts b/services/slides/node_modules/fastify/types/logger.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..7ed40fef2cf14908581db4518b5bf5236de81441 --- /dev/null +++ b/services/slides/node_modules/fastify/types/logger.d.ts @@ -0,0 +1,107 @@ +import { FastifyError } from '@fastify/error' +import { FastifyInstance } from './instance' +import { FastifyReply } from './reply' +import { FastifyRequest } from './request' +import { RouteGenericInterface } from './route' +import { FastifySchema } from './schema' +import { FastifyTypeProvider, FastifyTypeProviderDefault } from './type-provider' +import { ContextConfigDefault, RawReplyDefaultExpression, RawRequestDefaultExpression, RawServerBase, RawServerDefault } from './utils' + +import type { + BaseLogger, + LogFn as FastifyLogFn, + LevelWithSilent as LogLevel, + Bindings, + ChildLoggerOptions, + LoggerOptions as PinoLoggerOptions +} from 'pino' + +export type { + FastifyLogFn, + LogLevel, + Bindings, + ChildLoggerOptions, + PinoLoggerOptions +} + +export interface FastifyBaseLogger extends Pick { + child(bindings: Bindings, options?: ChildLoggerOptions): FastifyBaseLogger +} + +// TODO delete FastifyLoggerInstance in the next major release. It seems that it is enough to have only FastifyBaseLogger. +/** + * @deprecated Use FastifyBaseLogger instead + */ +export type FastifyLoggerInstance = FastifyBaseLogger + +export interface FastifyLoggerStreamDestination { + write(msg: string): void; +} + +// TODO: once node 18 is EOL, this type can be replaced with plain FastifyReply. +/** + * Specialized reply type used for the `res` log serializer, since only `statusCode` is passed in certain cases. + */ +export type ResSerializerReply< + RawServer extends RawServerBase, + RawReply extends FastifyReply +> = Partial & Pick + +/** + * Fastify Custom Logger options. + */ +export interface FastifyLoggerOptions< + RawServer extends RawServerBase = RawServerDefault, + RawRequest extends FastifyRequest, FastifySchema, FastifyTypeProvider> = FastifyRequest, FastifySchema, FastifyTypeProviderDefault>, + RawReply extends FastifyReply, RawReplyDefaultExpression, ContextConfigDefault, FastifySchema, FastifyTypeProvider> = FastifyReply, RawReplyDefaultExpression, ContextConfigDefault, FastifySchema, FastifyTypeProviderDefault> +> { + serializers?: { + req?: (req: RawRequest) => { + method?: string; + url?: string; + version?: string; + host?: string; + remoteAddress?: string; + remotePort?: number; + [key: string]: unknown; + }; + err?: (err: FastifyError) => { + type: string; + message: string; + stack: string; + [key: string]: unknown; + }; + res?: (res: ResSerializerReply) => { + statusCode?: string | number; + [key: string]: unknown; + }; + }; + level?: string; + file?: string; + genReqId?: (req: RawRequest) => string; + stream?: FastifyLoggerStreamDestination; +} + +export interface FastifyChildLoggerFactory< + RawServer extends RawServerBase = RawServerDefault, + RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, + RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, + Logger extends FastifyBaseLogger = FastifyBaseLogger, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault +> { + /** + * @param logger The parent logger + * @param bindings The bindings object that will be passed to the child logger + * @param childLoggerOpts The logger options that will be passed to the child logger + * @param rawReq The raw request + * @this The fastify instance + * @returns The child logger instance + */ + ( + this: FastifyInstance, + logger: Logger, + bindings: Bindings, + childLoggerOpts: ChildLoggerOptions, + rawReq: RawRequest + ): Logger +} diff --git a/services/slides/node_modules/fastify/types/plugin.d.ts b/services/slides/node_modules/fastify/types/plugin.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..797b9cac15651124b082c9aa8f68e17ebd8d61de --- /dev/null +++ b/services/slides/node_modules/fastify/types/plugin.d.ts @@ -0,0 +1,44 @@ +import { FastifyInstance } from './instance' +import { RawServerBase, RawRequestDefaultExpression, RawReplyDefaultExpression, RawServerDefault } from './utils' +import { FastifyTypeProvider, FastifyTypeProviderDefault } from './type-provider' +import { FastifyBaseLogger } from './logger' + +export type FastifyPluginOptions = Record + +/** + * FastifyPluginCallback + * + * Fastify allows the user to extend its functionalities with plugins. A plugin can be a set of routes, a server decorator or whatever. To activate plugins, use the `fastify.register()` method. + */ +export type FastifyPluginCallback< + Options extends FastifyPluginOptions = Record, + Server extends RawServerBase = RawServerDefault, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, + Logger extends FastifyBaseLogger = FastifyBaseLogger +> = ( + instance: FastifyInstance, RawReplyDefaultExpression, Logger, TypeProvider>, + opts: Options, + done: (err?: Error) => void +) => void + +/** + * FastifyPluginAsync + * + * Fastify allows the user to extend its functionalities with plugins. A plugin can be a set of routes, a server decorator or whatever. To activate plugins, use the `fastify.register()` method. + */ +export type FastifyPluginAsync< + Options extends FastifyPluginOptions = Record, + Server extends RawServerBase = RawServerDefault, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, + Logger extends FastifyBaseLogger = FastifyBaseLogger +> = ( + instance: FastifyInstance, RawReplyDefaultExpression, Logger, TypeProvider>, + opts: Options +) => Promise + +/** + * Generic plugin type. + * @deprecated union type doesn't work well with type inference in TS and is therefore deprecated in favor of explicit types. Use `FastifyPluginCallback` or `FastifyPluginAsync` instead. To activate + * plugins use `FastifyRegister`. https://fastify.dev/docs/latest/Reference/TypeScript/#register + */ +export type FastifyPlugin> = FastifyPluginCallback | FastifyPluginAsync diff --git a/services/slides/node_modules/fastify/types/register.d.ts b/services/slides/node_modules/fastify/types/register.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..b6a7625f75fce96c98e3ff0507d414dc5a4a3884 --- /dev/null +++ b/services/slides/node_modules/fastify/types/register.d.ts @@ -0,0 +1,42 @@ +import { FastifyPluginOptions, FastifyPluginCallback, FastifyPluginAsync } from './plugin' +import { LogLevel } from './logger' +import { FastifyInstance } from './instance' +import { RawServerBase } from './utils' +import { FastifyBaseLogger, FastifyTypeProvider, RawServerDefault } from '../fastify' + +export interface RegisterOptions { + prefix?: string; + logLevel?: LogLevel; + logSerializers?: Record string>; +} + +export type FastifyRegisterOptions = (RegisterOptions & Options) | ((instance: FastifyInstance) => RegisterOptions & Options) + +/** + * FastifyRegister + * + * Function for adding a plugin to fastify. The options are inferred from the passed in FastifyPlugin parameter. + */ +export interface FastifyRegister { + ( + plugin: FastifyPluginCallback + ): T; + ( + plugin: FastifyPluginCallback, + opts: FastifyRegisterOptions + ): T; + ( + plugin: FastifyPluginAsync + ): T; + ( + plugin: FastifyPluginAsync, + opts: FastifyRegisterOptions + ): T; + ( + plugin: FastifyPluginCallback | FastifyPluginAsync | Promise<{ default: FastifyPluginCallback }> | Promise<{ default: FastifyPluginAsync }>, + ): T; + ( + plugin: FastifyPluginCallback | FastifyPluginAsync | Promise<{ default: FastifyPluginCallback }> | Promise<{ default: FastifyPluginAsync }>, + opts: FastifyRegisterOptions + ): T; +} diff --git a/services/slides/node_modules/fastify/types/reply.d.ts b/services/slides/node_modules/fastify/types/reply.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..ff405c366bf796987f71733d32a09ce79501dc30 --- /dev/null +++ b/services/slides/node_modules/fastify/types/reply.d.ts @@ -0,0 +1,81 @@ +import { Buffer } from 'node:buffer' +import { FastifyInstance } from './instance' +import { FastifyBaseLogger } from './logger' +import { FastifyRequest, RequestRouteOptions } from './request' +import { RouteGenericInterface } from './route' +import { FastifySchema } from './schema' +import { CallSerializerTypeProvider, FastifyReplyType, FastifyTypeProvider, FastifyTypeProviderDefault, ResolveFastifyReplyType, SendArgs } from './type-provider' +import { CodeToReplyKey, ContextConfigDefault, HttpHeader, HttpKeys, RawReplyDefaultExpression, RawRequestDefaultExpression, RawServerBase, RawServerDefault, ReplyDefault, ReplyKeysToCodes } from './utils' + +export interface ReplyGenericInterface { + Reply?: ReplyDefault; +} + +type HttpCodesReplyType = Partial> + +type ReplyTypeConstrainer> = + RouteGenericReply extends HttpCodesReplyType & Record, never> ? + Code extends keyof RouteGenericReply ? RouteGenericReply[Code] : + CodeToReplyKey extends keyof RouteGenericReply ? RouteGenericReply[CodeToReplyKey] : unknown : + RouteGenericReply + +export type ResolveReplyTypeWithRouteGeneric, + SchemaCompiler extends FastifySchema = FastifySchema, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault> = + Code extends keyof SchemaCompiler['response'] ? + CallSerializerTypeProvider : + ResolveFastifyReplyType }> +/** + * FastifyReply is an instance of the standard http or http2 reply types. + * It defaults to http.ServerResponse, and it also extends the relative reply object. + */ +export interface FastifyReply< + RouteGeneric extends RouteGenericInterface = RouteGenericInterface, + RawServer extends RawServerBase = RawServerDefault, + RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, + RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, + ContextConfig = ContextConfigDefault, + SchemaCompiler extends FastifySchema = FastifySchema, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, + ReplyType extends FastifyReplyType = ResolveFastifyReplyType +> { + readonly routeOptions: Readonly> + + raw: RawReply; + elapsedTime: number; + log: FastifyBaseLogger; + request: FastifyRequest; + server: FastifyInstance; + code : keyof SchemaCompiler['response'] extends ReplyKeysToCodes ? keyof SchemaCompiler['response'] : ReplyKeysToCodes>(statusCode: Code): FastifyReply>; + status : keyof SchemaCompiler['response'] extends ReplyKeysToCodes ? keyof SchemaCompiler['response'] : ReplyKeysToCodes>(statusCode: Code): FastifyReply>; + statusCode: number; + sent: boolean; + send(...args: SendArgs): FastifyReply; + header(key: HttpHeader, value: any): FastifyReply; + headers(values: Partial>): FastifyReply; + getHeader(key: HttpHeader): number | string | string[] | undefined; + getHeaders(): Record; + removeHeader(key: HttpHeader): FastifyReply; + hasHeader(key: HttpHeader): boolean; + redirect(url: string, statusCode?: number): FastifyReply; + writeEarlyHints(hints: Record, callback?: () => void): void; + hijack(): FastifyReply; + callNotFound(): void; + type(contentType: string): FastifyReply; + serializer(fn: (payload: any) => string): FastifyReply; + serialize(payload: any): string | ArrayBuffer | Buffer; + // Serialization Methods + getSerializationFunction(httpStatus: string, contentType?: string): ((payload: { [key: string]: unknown }) => string) | undefined; + getSerializationFunction(schema: { [key: string]: unknown }): ((payload: { [key: string]: unknown }) => string) | undefined; + compileSerializationSchema(schema: { [key: string]: unknown }, httpStatus?: string, contentType?: string): (payload: { [key: string]: unknown }) => string; + serializeInput(input: { [key: string]: unknown }, schema: { [key: string]: unknown }, httpStatus?: string, contentType?: string): string; + serializeInput(input: { [key: string]: unknown }, httpStatus: string, contentType?: string): unknown; + then(fulfilled: () => void, rejected: (err: Error) => void): void; + trailer: ( + key: string, + fn: ((reply: FastifyReply, payload: string | Buffer | null) => Promise) | ((reply: FastifyReply, payload: string | Buffer | null, done: (err: Error | null, value?: string) => void) => void) + ) => FastifyReply; + hasTrailer(key: string): boolean; + removeTrailer(key: string): FastifyReply; + getDecorator(name: string | symbol): T; +} diff --git a/services/slides/node_modules/fastify/types/request.d.ts b/services/slides/node_modules/fastify/types/request.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..a090e52cef2b2953dc1542d541c5fbba4b8550a2 --- /dev/null +++ b/services/slides/node_modules/fastify/types/request.d.ts @@ -0,0 +1,95 @@ +import { ErrorObject } from '@fastify/ajv-compiler' +import { FastifyContextConfig } from './context' +import { FastifyInstance } from './instance' +import { FastifyBaseLogger } from './logger' +import { FastifyRouteConfig, RouteGenericInterface, RouteHandlerMethod } from './route' +import { FastifySchema } from './schema' +import { FastifyRequestType, FastifyTypeProvider, FastifyTypeProviderDefault, ResolveFastifyRequestType } from './type-provider' +import { ContextConfigDefault, HTTPMethods, RawRequestDefaultExpression, RawServerBase, RawServerDefault, RequestBodyDefault, RequestHeadersDefault, RequestParamsDefault, RequestQuerystringDefault } from './utils' + +type HTTPRequestPart = 'body' | 'query' | 'querystring' | 'params' | 'headers' +export interface RequestGenericInterface { + Body?: RequestBodyDefault; + Querystring?: RequestQuerystringDefault; + Params?: RequestParamsDefault; + Headers?: RequestHeadersDefault; +} + +export interface ValidationFunction { + (input: any): boolean + errors?: null | ErrorObject[]; +} + +export interface RequestRouteOptions { + method: HTTPMethods | HTTPMethods[]; + // `url` can be `undefined` for instance when `request.is404` is true + url: string | undefined; + bodyLimit: number; + handlerTimeout: number; + attachValidation: boolean; + logLevel: string; + exposeHeadRoute: boolean; + prefixTrailingSlash: string; + config: FastifyContextConfig & FastifyRouteConfig & ContextConfig; + schema?: SchemaCompiler; // it is empty for 404 requests + handler: RouteHandlerMethod; + version?: string; +} + +/** + * FastifyRequest is an instance of the standard http or http2 request objects. + * It defaults to http.IncomingMessage, and it also extends the relative request object. + */ +export interface FastifyRequest = RawRequestDefaultExpression, + SchemaCompiler extends FastifySchema = FastifySchema, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, + ContextConfig = ContextConfigDefault, + Logger extends FastifyBaseLogger = FastifyBaseLogger, + RequestType extends FastifyRequestType = ResolveFastifyRequestType +// ^ Temporary Note: RequestType has been re-ordered to be the last argument in +// generic list. This generic argument is now considered optional as it can be +// automatically inferred from the SchemaCompiler, RouteGeneric and TypeProvider +// arguments. Implementations that already pass this argument can either omit +// the RequestType (preferred) or swap Logger and RequestType arguments when +// creating custom types of FastifyRequest. Related issue #4123 +> { + id: string; + params: RequestType['params']; // deferred inference + raw: RawRequest; + query: RequestType['query']; + headers: RawRequest['headers'] & RequestType['headers']; // this enables the developer to extend the existing http(s|2) headers list + log: Logger; + server: FastifyInstance; + body: RequestType['body']; + + /** in order for this to be used the user should ensure they have set the attachValidation option. */ + validationError?: Error & { validation: any; validationContext: string }; + + /** + * @deprecated Use `raw` property + */ + readonly req: RawRequest & RouteGeneric['Headers']; // this enables the developer to extend the existing http(s|2) headers list + readonly ip: string; + readonly ips?: string[]; + readonly host: string; + readonly port: number | null; + readonly hostname: string; + readonly url: string; + readonly originalUrl: string; + readonly protocol: 'http' | 'https'; + readonly method: string; + readonly routeOptions: Readonly> + readonly is404: boolean; + readonly socket: RawRequest['socket']; + readonly signal: AbortSignal; + + getValidationFunction(httpPart: HTTPRequestPart): ValidationFunction + getValidationFunction(schema: { [key: string]: any }): ValidationFunction + compileValidationSchema(schema: { [key: string]: any }, httpPart?: HTTPRequestPart): ValidationFunction + validateInput(input: any, schema: { [key: string]: any }, httpPart?: HTTPRequestPart): boolean + validateInput(input: any, httpPart?: HTTPRequestPart): boolean + getDecorator(name: string | symbol): T; + setDecorator(name: string | symbol, value: T): void; +} diff --git a/services/slides/node_modules/fastify/types/route.d.ts b/services/slides/node_modules/fastify/types/route.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..e12fa007fe9cb02e13a151b28d1dbae4c3d3199f --- /dev/null +++ b/services/slides/node_modules/fastify/types/route.d.ts @@ -0,0 +1,199 @@ +import { FastifyError } from '@fastify/error' +import { ConstraintStrategy } from 'find-my-way' +import { FastifyContextConfig } from './context' +import { + onErrorHookHandler, + onRequestAbortHookHandler, + onRequestHookHandler, + onResponseHookHandler, + onSendHookHandler, + onTimeoutHookHandler, + preHandlerHookHandler, + preParsingHookHandler, + preSerializationHookHandler, + preValidationHookHandler +} from './hooks' +import { FastifyInstance } from './instance' +import { FastifyBaseLogger, FastifyChildLoggerFactory, LogLevel } from './logger' +import { FastifyReply, ReplyGenericInterface } from './reply' +import { FastifyRequest, RequestGenericInterface } from './request' +import { FastifySchema, FastifySchemaCompiler, FastifySerializerCompiler, SchemaErrorFormatter } from './schema' +import { + FastifyTypeProvider, + FastifyTypeProviderDefault, + ResolveFastifyReplyReturnType +} from './type-provider' +import { ContextConfigDefault, HTTPMethods, RawReplyDefaultExpression, RawRequestDefaultExpression, RawServerBase, RawServerDefault } from './utils' + +export interface FastifyRouteConfig { + url: string; + method: HTTPMethods | HTTPMethods[]; +} + +export interface RouteGenericInterface extends RequestGenericInterface, ReplyGenericInterface { } + +export type RouteConstraintType = Omit, 'deriveConstraint'> & { + deriveConstraint(req: RawRequestDefaultExpression, ctx?: Context, done?: (err: Error, ...args: any) => any): any, +} + +export interface RouteConstraint { + version?: string + host?: RegExp | string + [name: string]: unknown +} + +/** + * Route shorthand options for the various shorthand methods + */ +type RouteShorthandHook any> = (...args: Parameters) => void | Promise + +export interface RouteShorthandOptions< + RawServer extends RawServerBase = RawServerDefault, + RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, + RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, + RouteGeneric extends RouteGenericInterface = RouteGenericInterface, + ContextConfig = ContextConfigDefault, + SchemaCompiler extends FastifySchema = FastifySchema, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, + Logger extends FastifyBaseLogger = FastifyBaseLogger +> { + schema?: SchemaCompiler, // originally FastifySchema + attachValidation?: boolean; + exposeHeadRoute?: boolean; + + validatorCompiler?: FastifySchemaCompiler>; + serializerCompiler?: FastifySerializerCompiler>; + bodyLimit?: number; + handlerTimeout?: number; + logLevel?: LogLevel; + config?: FastifyContextConfig & ContextConfig; + constraints?: RouteConstraint, + prefixTrailingSlash?: 'slash' | 'no-slash' | 'both'; + errorHandler?: ( + this: FastifyInstance, + error: FastifyError, + request: FastifyRequest, TypeProvider, ContextConfig, Logger>, + reply: FastifyReply, TypeProvider> + ) => void; + childLoggerFactory?: FastifyChildLoggerFactory; + schemaErrorFormatter?: SchemaErrorFormatter; + + // hooks + onRequest?: RouteShorthandHook, TypeProvider, Logger>> + | RouteShorthandHook, TypeProvider, Logger>>[]; + preParsing?: RouteShorthandHook, TypeProvider, Logger>> + | RouteShorthandHook, TypeProvider, Logger>>[]; + preValidation?: RouteShorthandHook, TypeProvider, Logger>> + | RouteShorthandHook, TypeProvider, Logger>>[]; + preHandler?: RouteShorthandHook, TypeProvider, Logger>> + | RouteShorthandHook, TypeProvider, Logger>>[]; + preSerialization?: RouteShorthandHook, TypeProvider, Logger>> + | RouteShorthandHook, TypeProvider, Logger>>[]; + onSend?: RouteShorthandHook, TypeProvider, Logger>> + | RouteShorthandHook, TypeProvider, Logger>>[]; + onResponse?: RouteShorthandHook, TypeProvider, Logger>> + | RouteShorthandHook, TypeProvider, Logger>>[]; + onTimeout?: RouteShorthandHook, TypeProvider, Logger>> + | RouteShorthandHook, TypeProvider, Logger>>[]; + onError?: RouteShorthandHook, TypeProvider, Logger>> + | RouteShorthandHook, TypeProvider, Logger>>[]; + onRequestAbort?: RouteShorthandHook, TypeProvider, Logger>> + | RouteShorthandHook, TypeProvider, Logger>>[]; +} +/** + * Route handler method declaration. + */ +export type RouteHandlerMethod< + RawServer extends RawServerBase = RawServerDefault, + RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, + RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, + RouteGeneric extends RouteGenericInterface = RouteGenericInterface, + ContextConfig = ContextConfigDefault, + SchemaCompiler extends FastifySchema = FastifySchema, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, + Logger extends FastifyBaseLogger = FastifyBaseLogger +> = ( + this: FastifyInstance, + request: FastifyRequest, + reply: FastifyReply + // This return type used to be a generic type argument. Due to TypeScript's inference of return types, this rendered returns unchecked. +) => ResolveFastifyReplyReturnType + +/** + * Shorthand options including the handler function property + */ +export interface RouteShorthandOptionsWithHandler< + RawServer extends RawServerBase = RawServerDefault, + RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, + RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, + RouteGeneric extends RouteGenericInterface = RouteGenericInterface, + ContextConfig = ContextConfigDefault, + SchemaCompiler extends FastifySchema = FastifySchema, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, + Logger extends FastifyBaseLogger = FastifyBaseLogger +> extends RouteShorthandOptions { + handler: RouteHandlerMethod, TypeProvider, Logger>; +} + +/** + * Fastify Router Shorthand method type that is similar to the Express/Restify approach + */ +export interface RouteShorthandMethod< + RawServer extends RawServerBase = RawServerDefault, + RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, + RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, + Logger extends FastifyBaseLogger = FastifyBaseLogger +> { + ( + path: string, + opts: RouteShorthandOptions, + handler: RouteHandlerMethod + ): FastifyInstance; + ( + path: string, + handler: RouteHandlerMethod + ): FastifyInstance; + ( + path: string, + opts: RouteShorthandOptionsWithHandler + ): FastifyInstance; +} + +/** + * Fastify route method options. + */ +export interface RouteOptions< + RawServer extends RawServerBase = RawServerDefault, + RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, + RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, + RouteGeneric extends RouteGenericInterface = RouteGenericInterface, + ContextConfig = ContextConfigDefault, + SchemaCompiler extends FastifySchema = FastifySchema, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, + Logger extends FastifyBaseLogger = FastifyBaseLogger +> extends RouteShorthandOptions { + method: HTTPMethods | HTTPMethods[]; + url: string; + handler: RouteHandlerMethod; +} + +export type RouteHandler< + RouteGeneric extends RouteGenericInterface = RouteGenericInterface, + RawServer extends RawServerBase = RawServerDefault, + RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, + RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, + ContextConfig = ContextConfigDefault, + SchemaCompiler extends FastifySchema = FastifySchema, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, + Logger extends FastifyBaseLogger = FastifyBaseLogger +> = ( + this: FastifyInstance, + request: FastifyRequest, + reply: FastifyReply +) => RouteGeneric['Reply'] | void | Promise + +export type DefaultRoute = ( + req: Request, + res: Reply, +) => void diff --git a/services/slides/node_modules/fastify/types/schema.d.ts b/services/slides/node_modules/fastify/types/schema.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..40a8dd1cb06568dbeb3755982ca7663a8ac5d57a --- /dev/null +++ b/services/slides/node_modules/fastify/types/schema.d.ts @@ -0,0 +1,61 @@ +import { ValidatorFactory } from '@fastify/ajv-compiler' +import { SerializerFactory } from '@fastify/fast-json-stringify-compiler' +import { FastifyInstance, SafePromiseLike } from '../fastify' +/** + * Schemas in Fastify follow the JSON-Schema standard. For this reason + * we have opted to not ship strict schema based types. Instead we provide + * an example in our documentation on how to solve this problem. Check it + * out here: https://github.com/fastify/fastify/blob/main/docs/Reference/TypeScript.md#json-schema + */ +export interface FastifySchema { + body?: unknown; + querystring?: unknown; + params?: unknown; + headers?: unknown; + response?: unknown; +} + +export interface FastifyRouteSchemaDef { + schema: T; + method: string; + url: string; + httpPart?: string; + httpStatus?: string; + contentType?: string; +} + +export interface FastifySchemaValidationError { + keyword: string; + instancePath: string; + schemaPath: string; + params: Record; + message?: string; +} + +export interface FastifyValidationResult { + (data: any): boolean | SafePromiseLike | { error?: Error | FastifySchemaValidationError[], value?: any } + errors?: FastifySchemaValidationError[] | null; +} + +/** + * Compiler for FastifySchema Type + */ +export type FastifySchemaCompiler = (routeSchema: FastifyRouteSchemaDef) => FastifyValidationResult + +export type FastifySerializerCompiler = (routeSchema: FastifyRouteSchemaDef) => (data: any) => string + +export interface FastifySchemaControllerOptions { + bucket?: (parentSchemas?: unknown) => { + add(schema: unknown): FastifyInstance; + getSchema(schemaId: string): unknown; + getSchemas(): Record; + }; + compilersFactory?: { + buildValidator?: ValidatorFactory; + buildSerializer?: SerializerFactory; + }; +} + +export type SchemaErrorDataVar = 'body' | 'headers' | 'params' | 'querystring' + +export type SchemaErrorFormatter = (errors: FastifySchemaValidationError[], dataVar: SchemaErrorDataVar) => Error diff --git a/services/slides/node_modules/fastify/types/server-factory.d.ts b/services/slides/node_modules/fastify/types/server-factory.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..05e743a05667ed0cd2889927803f6f9084bb2235 --- /dev/null +++ b/services/slides/node_modules/fastify/types/server-factory.d.ts @@ -0,0 +1,19 @@ +import { RawServerBase, RawServerDefault, RawReplyDefaultExpression, RawRequestDefaultExpression } from './utils' +import * as http from 'node:http' +import * as https from 'node:https' +import * as http2 from 'node:http2' + +export type FastifyServerFactoryHandler< + RawServer extends RawServerBase = RawServerDefault, + RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, + RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression +> = +RawServer extends http.Server | https.Server ? + (request: http.IncomingMessage & RawRequest, response: http.ServerResponse & RawReply) => void : + (request: http2.Http2ServerRequest & RawRequest, response: http2.Http2ServerResponse & RawReply) => void + +export interface FastifyServerFactory< + RawServer extends RawServerBase = RawServerDefault +> { + (handler: FastifyServerFactoryHandler, opts: Record): RawServer; +} diff --git a/services/slides/node_modules/fastify/types/type-provider.d.ts b/services/slides/node_modules/fastify/types/type-provider.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..002b08248cef644d10f901c934152e9bdbb26168 --- /dev/null +++ b/services/slides/node_modules/fastify/types/type-provider.d.ts @@ -0,0 +1,130 @@ +import { RouteGenericInterface } from './route' +import { FastifySchema } from './schema' +import { HttpKeys, RecordKeysToLowercase } from './utils' + +// ----------------------------------------------------------------------------------------------- +// TypeProvider +// ----------------------------------------------------------------------------------------------- + +export interface FastifyTypeProvider { + readonly schema: unknown, + readonly validator: unknown, + readonly serializer: unknown, +} + +export interface FastifyTypeProviderDefault extends FastifyTypeProvider {} + +export type CallValidatorTypeProvider = (F & { schema: S })['validator'] +export type CallSerializerTypeProvider = (F & { schema: S })['serializer'] + +// ----------------------------------------------------------------------------------------------- +// FastifyRequestType +// ----------------------------------------------------------------------------------------------- + +// Used to map undefined SchemaCompiler properties to unknown +// Without brackets, UndefinedToUnknown => unknown +type UndefinedToUnknown = [T] extends [undefined] ? unknown : T + +// union-aware keyof operator +// keyof ({ a: number} | { b: number}) => never +// KeysOf<{a: number} | {b: number}> => "a" | "b" +// this exists to allow users to override faulty type-provider logic. +type KeysOf = T extends any ? keyof T : never + +// Resolves Request types either from generic argument or Type Provider. +type ResolveRequestParams = + UndefinedToUnknown extends never ? CallValidatorTypeProvider : RouteGeneric['Params']> +type ResolveRequestQuerystring = + UndefinedToUnknown extends never ? CallValidatorTypeProvider : RouteGeneric['Querystring']> +type ResolveRequestHeaders = + UndefinedToUnknown extends never ? CallValidatorTypeProvider : RouteGeneric['Headers']> +type ResolveRequestBody = + UndefinedToUnknown extends never ? CallValidatorTypeProvider : RouteGeneric['Body']> + +// The target request type. This type is inferenced on fastify 'requests' via generic argument assignment +export interface FastifyRequestType { + params: Params, + query: Querystring, + headers: Headers, + body: Body +} + +// Resolves the FastifyRequest generic parameters +export interface ResolveFastifyRequestType extends FastifyRequestType { + params: ResolveRequestParams, + query: ResolveRequestQuerystring, + headers: RecordKeysToLowercase>, + body: ResolveRequestBody +} + +// ----------------------------------------------------------------------------------------------- +// FastifyReplyType +// ----------------------------------------------------------------------------------------------- + +// Resolves the Reply type by taking a union of response status codes and content-types +type ResolveReplyFromSchemaCompiler = { + [K1 in keyof SchemaCompiler['response']]: SchemaCompiler['response'][K1] extends { content: { [keyof: string]: { schema: unknown } } } ? ({ + [K2 in keyof SchemaCompiler['response'][K1]['content']]: CallSerializerTypeProvider + } extends infer Result ? Result[keyof Result] : unknown) : CallSerializerTypeProvider +} extends infer Result ? Result[keyof Result] : unknown + +// The target reply type. This type is inferenced on fastify 'replies' via generic argument assignment +export type FastifyReplyType = Reply + +// Resolves the Reply type either via generic argument or from response schema. This type uses a different +// resolution strategy to Requests where the Reply will infer a union of each status code type specified +// by the user. The Reply can be explicitly overridden by users providing a generic Reply type on the route. +export type ResolveFastifyReplyType = UndefinedToUnknown extends never ? ResolveReplyFromSchemaCompiler : RouteGeneric['Reply']> + +// ----------------------------------------------------------------------------------------------- +// FastifyReplyReturnType +// ----------------------------------------------------------------------------------------------- + +// Resolves the Reply return type by taking a union of response status codes in the generic argument +type ResolveReplyReturnTypeFromRouteGeneric = RouteGeneric extends { Reply: infer Return } + ? keyof Return extends HttpKeys ? Return[keyof Return] | Return : Return + : unknown + +// The target reply return type. This type is inferenced on fastify 'routes' via generic argument assignment +export type ResolveFastifyReplyReturnType< + TypeProvider extends FastifyTypeProvider, + SchemaCompiler extends FastifySchema, + RouteGeneric extends RouteGenericInterface +> = ResolveFastifyReplyType< +TypeProvider, +SchemaCompiler, +RouteGeneric +> extends infer ReplyType + ? RouteGeneric['Reply'] extends ReplyType + ? ResolveReplyReturnTypeFromRouteGeneric extends infer Return + ? Return | void | Promise + : unknown + : ReplyType | void | Promise +// review: support both async and sync return types +// (Promise | Return | Promise | void) + : unknown + +/** + * This branded type is needed to indicate APIs that return Promise-likes which can + * safely "float" (not have rejections handled by calling code). + * + * Please refer to the following Github issue for more info: + * https://github.com/fastify/fastify/issues/5498 + */ +export type SafePromiseLike = PromiseLike & { __linterBrands: 'SafePromiseLike' } + +// ----------------------------------------------------------------------------------------------- +// SendArgs +// ----------------------------------------------------------------------------------------------- + +/** + * Determines whether the send() payload parameter should be required or optional. + * - When ReplyType is unknown (default/unspecified), payload is optional + * - When ReplyType is undefined or void, payload is optional (returning undefined is valid) + * - Otherwise, payload is required + */ +export type SendArgs = unknown extends ReplyType + ? [payload?: ReplyType] + : [ReplyType] extends [undefined | void] + ? [payload?: ReplyType] + : [payload: ReplyType] diff --git a/services/slides/node_modules/fastify/types/utils.d.ts b/services/slides/node_modules/fastify/types/utils.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..c569e15526de3da70a3dfc301ecbada6a42761db --- /dev/null +++ b/services/slides/node_modules/fastify/types/utils.d.ts @@ -0,0 +1,98 @@ +import * as http from 'node:http' +import * as http2 from 'node:http2' +import * as https from 'node:https' + +type AutocompletePrimitiveBaseType = + T extends string ? string : + T extends number ? number : + T extends boolean ? boolean : + never + +export type Autocomplete = T | (AutocompletePrimitiveBaseType & Record) + +/** + * Standard HTTP method strings + * for internal use + */ +type _HTTPMethods = 'DELETE' | 'GET' | 'HEAD' | 'PATCH' | 'POST' | 'PUT' | 'OPTIONS' | +'PROPFIND' | 'PROPPATCH' | 'MKCOL' | 'COPY' | 'MOVE' | 'LOCK' | 'UNLOCK' | 'TRACE' | 'SEARCH' | 'REPORT' | 'MKCALENDAR' + +export type HTTPMethods = Autocomplete<_HTTPMethods | Lowercase<_HTTPMethods>> + +/** + * A union type of the Node.js server types from the http, https, and http2 modules. + */ +export type RawServerBase = http.Server | https.Server | http2.Http2Server | http2.Http2SecureServer + +/** + * The default server type + */ +export type RawServerDefault = http.Server + +/** + * The default request type based on the server type. Utilizes generic constraining. + */ +export type RawRequestDefaultExpression< + RawServer extends RawServerBase = RawServerDefault +> = RawServer extends http.Server | https.Server ? http.IncomingMessage + : RawServer extends http2.Http2Server | http2.Http2SecureServer ? http2.Http2ServerRequest + : never + +/** + * The default reply type based on the server type. Utilizes generic constraining. + */ +export type RawReplyDefaultExpression< + RawServer extends RawServerBase = RawServerDefault +> = RawServer extends http.Server | https.Server ? http.ServerResponse + : RawServer extends http2.Http2Server | http2.Http2SecureServer ? http2.Http2ServerResponse + : never + +export type RequestBodyDefault = unknown +export type RequestQuerystringDefault = unknown +export type RequestParamsDefault = unknown +export type RequestHeadersDefault = unknown + +export type ContextConfigDefault = unknown +export type ReplyDefault = unknown + +/** + * Helpers for determining the type of the response payload based on the code + */ + +type StringAsNumber = T extends `${infer N extends number}` ? N : never +type CodeClasses = 1 | 2 | 3 | 4 | 5 +type Digit = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 +type HttpCodes = StringAsNumber<`${CodeClasses}${Digit}${Digit}`> +type HttpKeys = HttpCodes | `${Digit}xx` +export type StatusCodeReply = { + [Key in HttpKeys]?: unknown; +} + +// weird TS quirk: https://stackoverflow.com/questions/58977876/generic-conditional-type-resolves-to-never-when-the-generic-type-is-set-to-never +export type ReplyKeysToCodes = [Key] extends [never] ? number : + Key extends HttpCodes ? Key : + Key extends `${infer X extends CodeClasses}xx` ? + StringAsNumber<`${X}${Digit}${Digit}`> : number + +export type CodeToReplyKey = `${Code}` extends `${infer FirstDigit extends CodeClasses}${number}` + ? `${FirstDigit}xx` + : never + +export type RecordKeysToLowercase = Input extends Record + ? { + [Key in keyof Input as Key extends string + ? Lowercase + : Key + ]: Input[Key]; + } + : Input + +type OmitIndexSignature = { + [K in keyof T as string extends K ? never : number extends K ? never : K]: T[K]; +} + +/** + * HTTP header strings + * Use this type only for input values, not for output values. + */ +export type HttpHeader = keyof OmitIndexSignature | (string & Record) diff --git a/services/slides/node_modules/fastq/LICENSE b/services/slides/node_modules/fastq/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..27c7bb46236e43d5217e02c1b1af452d860ff1e6 --- /dev/null +++ b/services/slides/node_modules/fastq/LICENSE @@ -0,0 +1,13 @@ +Copyright (c) 2015-2020, Matteo Collina + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/services/slides/node_modules/fastq/README.md b/services/slides/node_modules/fastq/README.md new file mode 100644 index 0000000000000000000000000000000000000000..b44cce36ef1e06da8b5987fd9c6dc508afdff166 --- /dev/null +++ b/services/slides/node_modules/fastq/README.md @@ -0,0 +1,310 @@ +# fastq + +![ci][ci-url] +[![npm version][npm-badge]][npm-url] + +Fast, in memory work queue. + +Benchmarks (1 million tasks): + +* setImmediate: 812ms +* fastq: 854ms +* async.queue: 1298ms +* neoAsync.queue: 1249ms + +Obtained on node 12.16.1, on a dedicated server. + +If you need zero-overhead series function call, check out +[fastseries](http://npm.im/fastseries). For zero-overhead parallel +function call, check out [fastparallel](http://npm.im/fastparallel). + + * Installation + * Usage + * API + * Licence & copyright + +## Install + +`npm i fastq --save` + +## Usage (callback API) + +```js +'use strict' + +const queue = require('fastq')(worker, 1) + +queue.push(42, function (err, result) { + if (err) { throw err } + console.log('the result is', result) +}) + +function worker (arg, cb) { + cb(null, arg * 2) +} +``` + +## Usage (promise API) + +```js +const queue = require('fastq').promise(worker, 1) + +async function worker (arg) { + return arg * 2 +} + +async function run () { + const result = await queue.push(42) + console.log('the result is', result) +} + +run() +``` + +### Setting "this" + +```js +'use strict' + +const that = { hello: 'world' } +const queue = require('fastq')(that, worker, 1) + +queue.push(42, function (err, result) { + if (err) { throw err } + console.log(this) + console.log('the result is', result) +}) + +function worker (arg, cb) { + console.log(this) + cb(null, arg * 2) +} +``` + +### Using with TypeScript (callback API) + +```ts +'use strict' + +import * as fastq from "fastq"; +import type { queue, done } from "fastq"; + +type Task = { + id: number +} + +const q: queue = fastq(worker, 1) + +q.push({ id: 42}) + +function worker (arg: Task, cb: done) { + console.log(arg.id) + cb(null) +} +``` + +### Using with TypeScript (promise API) + +```ts +'use strict' + +import * as fastq from "fastq"; +import type { queueAsPromised } from "fastq"; + +type Task = { + id: number +} + +const q: queueAsPromised = fastq.promise(asyncWorker, 1) + +q.push({ id: 42}).catch((err) => console.error(err)) + +async function asyncWorker (arg: Task): Promise { + // No need for a try-catch block, fastq handles errors automatically + console.log(arg.id) +} +``` + +## API + +* fastqueue() +* queue#push() +* queue#unshift() +* queue#pause() +* queue#resume() +* queue#idle() +* queue#length() +* queue#getQueue() +* queue#kill() +* queue#killAndDrain() +* queue#error() +* queue#concurrency +* queue#drain +* queue#empty +* queue#saturated +* fastqueue.promise() + +------------------------------------------------------- + +### fastqueue([that], worker, concurrency) + +Creates a new queue. + +Arguments: + +* `that`, optional context of the `worker` function. +* `worker`, worker function, it would be called with `that` as `this`, + if that is specified. +* `concurrency`, number of concurrent tasks that could be executed in + parallel. + +------------------------------------------------------- + +### queue.push(task, done) + +Add a task at the end of the queue. `done(err, result)` will be called +when the task was processed. + +------------------------------------------------------- + +### queue.unshift(task, done) + +Add a task at the beginning of the queue. `done(err, result)` will be called +when the task was processed. + +------------------------------------------------------- + +### queue.pause() + +Pause the processing of tasks. Currently worked tasks are not +stopped. + +------------------------------------------------------- + +### queue.resume() + +Resume the processing of tasks. + +------------------------------------------------------- + +### queue.idle() + +Returns `false` if there are tasks being processed or waiting to be processed. +`true` otherwise. + +------------------------------------------------------- + +### queue.length() + +Returns the number of tasks waiting to be processed (in the queue). + +------------------------------------------------------- + +### queue.getQueue() + +Returns all the tasks be processed (in the queue). Returns empty array when there are no tasks + +------------------------------------------------------- + +### queue.kill() + +Removes all tasks waiting to be processed, and reset `drain` to an empty +function. + +------------------------------------------------------- + +### queue.killAndDrain() + +Same than `kill` but the `drain` function will be called before reset to empty. + +------------------------------------------------------- + +### queue.error(handler) + +Set a global error handler. `handler(err, task)` will be called +each time a task is completed, `err` will be not null if the task has thrown an error. + +------------------------------------------------------- + +### queue.concurrency + +Property that returns the number of concurrent tasks that could be executed in +parallel. It can be altered at runtime. + +------------------------------------------------------- + +### queue.paused + +Property (Read-Only) that returns `true` when the queue is in a paused state. + +------------------------------------------------------- + +### queue.drain + +Function that will be called when the last +item from the queue has been processed by a worker. +It can be altered at runtime. + +------------------------------------------------------- + +### queue.empty + +Function that will be called when the last +item from the queue has been assigned to a worker. +It can be altered at runtime. + +------------------------------------------------------- + +### queue.saturated + +Function that will be called when the queue hits the concurrency +limit. +It can be altered at runtime. + +------------------------------------------------------- + +### fastqueue.promise([that], worker(arg), concurrency) + +Creates a new queue with `Promise` apis. It also offers all the methods +and properties of the object returned by [`fastqueue`](#fastqueue) with the modified +[`push`](#pushPromise) and [`unshift`](#unshiftPromise) methods. + +Node v10+ is required to use the promisified version. + +Arguments: +* `that`, optional context of the `worker` function. +* `worker`, worker function, it would be called with `that` as `this`, + if that is specified. It MUST return a `Promise`. +* `concurrency`, number of concurrent tasks that could be executed in + parallel. + + +#### queue.push(task) => Promise + +Add a task at the end of the queue. The returned `Promise` will be fulfilled (rejected) +when the task is completed successfully (unsuccessfully). + +This promise could be ignored as it will not lead to a `'unhandledRejection'`. + + +#### queue.unshift(task) => Promise + +Add a task at the beginning of the queue. The returned `Promise` will be fulfilled (rejected) +when the task is completed successfully (unsuccessfully). + +This promise could be ignored as it will not lead to a `'unhandledRejection'`. + + +#### queue.drained() => Promise + +Wait for the queue to be drained. The returned `Promise` will be resolved when all tasks in the queue have been processed by a worker. + +This promise could be ignored as it will not lead to a `'unhandledRejection'`. + +## License + +ISC + +[ci-url]: https://github.com/mcollina/fastq/workflows/ci/badge.svg +[npm-badge]: https://badge.fury.io/js/fastq.svg +[npm-url]: https://badge.fury.io/js/fastq diff --git a/services/slides/node_modules/fastq/SECURITY.md b/services/slides/node_modules/fastq/SECURITY.md new file mode 100644 index 0000000000000000000000000000000000000000..dd9f1d510216450b9311a3b30815fdf381baa787 --- /dev/null +++ b/services/slides/node_modules/fastq/SECURITY.md @@ -0,0 +1,15 @@ +# Security Policy + +## Supported Versions + +Use this section to tell people about which versions of your project are +currently being supported with security updates. + +| Version | Supported | +| ------- | ------------------ | +| 1.x | :white_check_mark: | +| < 1.0 | :x: | + +## Reporting a Vulnerability + +Please report all vulnerabilities at [https://github.com/mcollina/fastq/security](https://github.com/mcollina/fastq/security). diff --git a/services/slides/node_modules/fastq/bench.js b/services/slides/node_modules/fastq/bench.js new file mode 100644 index 0000000000000000000000000000000000000000..4eaa829f329690fa6cfafa0641a34e58b4ebc832 --- /dev/null +++ b/services/slides/node_modules/fastq/bench.js @@ -0,0 +1,66 @@ +'use strict' + +const max = 1000000 +const fastqueue = require('./')(worker, 1) +const { promisify } = require('util') +const immediate = promisify(setImmediate) +const qPromise = require('./').promise(immediate, 1) +const async = require('async') +const neo = require('neo-async') +const asyncqueue = async.queue(worker, 1) +const neoqueue = neo.queue(worker, 1) + +function bench (func, done) { + const key = max + '*' + func.name + let count = -1 + + console.time(key) + end() + + function end () { + if (++count < max) { + func(end) + } else { + console.timeEnd(key) + if (done) { + done() + } + } + } +} + +function benchFastQ (done) { + fastqueue.push(42, done) +} + +function benchAsyncQueue (done) { + asyncqueue.push(42, done) +} + +function benchNeoQueue (done) { + neoqueue.push(42, done) +} + +function worker (arg, cb) { + setImmediate(cb) +} + +function benchSetImmediate (cb) { + worker(42, cb) +} + +function benchFastQPromise (done) { + qPromise.push(42).then(function () { done() }, done) +} + +function runBench (done) { + async.eachSeries([ + benchSetImmediate, + benchFastQ, + benchNeoQueue, + benchAsyncQueue, + benchFastQPromise + ], bench, done) +} + +runBench(runBench) diff --git a/services/slides/node_modules/fastq/eslint.config.js b/services/slides/node_modules/fastq/eslint.config.js new file mode 100644 index 0000000000000000000000000000000000000000..57482dbb57b2d12e11db04253b91adddf6f53a1f --- /dev/null +++ b/services/slides/node_modules/fastq/eslint.config.js @@ -0,0 +1,11 @@ +const neostandard = require('neostandard') + +module.exports = [ + ...neostandard(), + { + name: 'node-0.10-compatibility', + rules: { + 'object-shorthand': 'off' // Disable ES6 object shorthand for Node.js 0.10 compatibility + } + } +] diff --git a/services/slides/node_modules/fastq/example.js b/services/slides/node_modules/fastq/example.js new file mode 100644 index 0000000000000000000000000000000000000000..665fdc8412e53ca788fb996af7b0eb6a70da94fb --- /dev/null +++ b/services/slides/node_modules/fastq/example.js @@ -0,0 +1,14 @@ +'use strict' + +/* eslint-disable no-var */ + +var queue = require('./')(worker, 1) + +queue.push(42, function (err, result) { + if (err) { throw err } + console.log('the result is', result) +}) + +function worker (arg, cb) { + cb(null, 42 * 2) +} diff --git a/services/slides/node_modules/fastq/example.mjs b/services/slides/node_modules/fastq/example.mjs new file mode 100644 index 0000000000000000000000000000000000000000..f31364a514aea4b972fe6dd908edc29c184a6019 --- /dev/null +++ b/services/slides/node_modules/fastq/example.mjs @@ -0,0 +1,9 @@ +import { promise as queueAsPromised } from './queue.js' + +const queue = queueAsPromised(worker, 1) + +console.log('the result is', await queue.push(42)) + +async function worker (arg) { + return 42 * 2 +} diff --git a/services/slides/node_modules/fastq/index.d.ts b/services/slides/node_modules/fastq/index.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..262dd0482b2250546479f07e3f0345486d915b36 --- /dev/null +++ b/services/slides/node_modules/fastq/index.d.ts @@ -0,0 +1,59 @@ +declare function fastq(context: C, worker: fastq.worker, concurrency: number): fastq.queue +declare function fastq(worker: fastq.worker, concurrency: number): fastq.queue + +declare namespace fastq { + type worker = (this: C, task: T, cb: fastq.done) => void + type asyncWorker = (this: C, task: T) => Promise + type done = (err: Error | null, result?: R) => void + type errorHandler = (err: Error, task: T) => void + + interface queue { + /** Add a task at the end of the queue. `done(err, result)` will be called when the task was processed. */ + push(task: T, done?: done): void + /** Add a task at the beginning of the queue. `done(err, result)` will be called when the task was processed. */ + unshift(task: T, done?: done): void + /** Pause the processing of tasks. Currently worked tasks are not stopped. */ + pause(): any + /** Resume the processing of tasks. */ + resume(): any + running(): number + /** Returns `false` if there are tasks being processed or waiting to be processed. `true` otherwise. */ + idle(): boolean + /** Returns the number of tasks waiting to be processed (in the queue). */ + length(): number + /** Returns all the tasks be processed (in the queue). Returns empty array when there are no tasks */ + getQueue(): T[] + /** Removes all tasks waiting to be processed, and reset `drain` to an empty function. */ + kill(): any + /** Same than `kill` but the `drain` function will be called before reset to empty. */ + killAndDrain(): any + /** Removes all tasks waiting to be processed, calls each task's callback with an abort error (rejects promises for promise-based queues), and resets `drain` to an empty function. */ + abort(): any + /** Set a global error handler. `handler(err, task)` will be called each time a task is completed, `err` will be not null if the task has thrown an error. */ + error(handler: errorHandler): void + /** Property that returns the number of concurrent tasks that could be executed in parallel. It can be altered at runtime. */ + concurrency: number + /** Property (Read-Only) that returns `true` when the queue is in a paused state. */ + readonly paused: boolean + /** Function that will be called when the last item from the queue has been processed by a worker. It can be altered at runtime. */ + drain(): any + /** Function that will be called when the last item from the queue has been assigned to a worker. It can be altered at runtime. */ + empty: () => void + /** Function that will be called when the queue hits the concurrency limit. It can be altered at runtime. */ + saturated: () => void + } + + interface queueAsPromised extends queue { + /** Add a task at the end of the queue. The returned `Promise` will be fulfilled (rejected) when the task is completed successfully (unsuccessfully). */ + push(task: T): Promise + /** Add a task at the beginning of the queue. The returned `Promise` will be fulfilled (rejected) when the task is completed successfully (unsuccessfully). */ + unshift(task: T): Promise + /** Wait for the queue to be drained. The returned `Promise` will be resolved when all tasks in the queue have been processed by a worker. */ + drained(): Promise + } + + function promise(context: C, worker: fastq.asyncWorker, concurrency: number): fastq.queueAsPromised + function promise(worker: fastq.asyncWorker, concurrency: number): fastq.queueAsPromised +} + +export = fastq diff --git a/services/slides/node_modules/fastq/package.json b/services/slides/node_modules/fastq/package.json new file mode 100644 index 0000000000000000000000000000000000000000..9e1d9ddec34ce0223a4521dd7108c2be17e41cf3 --- /dev/null +++ b/services/slides/node_modules/fastq/package.json @@ -0,0 +1,49 @@ +{ + "name": "fastq", + "version": "1.20.1", + "description": "Fast, in memory work queue", + "main": "queue.js", + "type": "commonjs", + "scripts": { + "lint": "eslint .", + "unit": "nyc --lines 100 --branches 100 --functions 100 --check-coverage --reporter=text tape test/test.js test/promise.js", + "coverage": "nyc --reporter=html --reporter=cobertura --reporter=text tape test/test.js test/promise.js", + "test:report": "npm run lint && npm run unit:report", + "test": "npm run lint && npm run unit", + "typescript": "tsc --project ./test/tsconfig.json", + "legacy": "tape test/test.js" + }, + "pre-commit": [ + "test", + "typescript" + ], + "repository": { + "type": "git", + "url": "git+https://github.com/mcollina/fastq.git" + }, + "keywords": [ + "fast", + "queue", + "async", + "worker" + ], + "author": "Matteo Collina ", + "license": "ISC", + "bugs": { + "url": "https://github.com/mcollina/fastq/issues" + }, + "homepage": "https://github.com/mcollina/fastq#readme", + "devDependencies": { + "async": "^3.1.0", + "eslint": "^9.36.0", + "neo-async": "^2.6.1", + "neostandard": "^0.12.2", + "nyc": "^17.0.0", + "pre-commit": "^1.2.2", + "tape": "^5.0.0", + "typescript": "^5.0.4" + }, + "dependencies": { + "reusify": "^1.0.4" + } +} diff --git a/services/slides/node_modules/fastq/queue.js b/services/slides/node_modules/fastq/queue.js new file mode 100644 index 0000000000000000000000000000000000000000..d0fbf20279006949feda751daf8b2360b39a2da1 --- /dev/null +++ b/services/slides/node_modules/fastq/queue.js @@ -0,0 +1,346 @@ +'use strict' + +/* eslint-disable no-var */ + +var reusify = require('reusify') + +function fastqueue (context, worker, _concurrency) { + if (typeof context === 'function') { + _concurrency = worker + worker = context + context = null + } + + if (!(_concurrency >= 1)) { + throw new Error('fastqueue concurrency must be equal to or greater than 1') + } + + var cache = reusify(Task) + var queueHead = null + var queueTail = null + var _running = 0 + var errorHandler = null + + var self = { + push: push, + drain: noop, + saturated: noop, + pause: pause, + paused: false, + + get concurrency () { + return _concurrency + }, + set concurrency (value) { + if (!(value >= 1)) { + throw new Error('fastqueue concurrency must be equal to or greater than 1') + } + _concurrency = value + + if (self.paused) return + for (; queueHead && _running < _concurrency;) { + _running++ + release() + } + }, + + running: running, + resume: resume, + idle: idle, + length: length, + getQueue: getQueue, + unshift: unshift, + empty: noop, + kill: kill, + killAndDrain: killAndDrain, + error: error, + abort: abort + } + + return self + + function running () { + return _running + } + + function pause () { + self.paused = true + } + + function length () { + var current = queueHead + var counter = 0 + + while (current) { + current = current.next + counter++ + } + + return counter + } + + function getQueue () { + var current = queueHead + var tasks = [] + + while (current) { + tasks.push(current.value) + current = current.next + } + + return tasks + } + + function resume () { + if (!self.paused) return + self.paused = false + if (queueHead === null) { + _running++ + release() + return + } + for (; queueHead && _running < _concurrency;) { + _running++ + release() + } + } + + function idle () { + return _running === 0 && self.length() === 0 + } + + function push (value, done) { + var current = cache.get() + + current.context = context + current.release = release + current.value = value + current.callback = done || noop + current.errorHandler = errorHandler + + if (_running >= _concurrency || self.paused) { + if (queueTail) { + queueTail.next = current + queueTail = current + } else { + queueHead = current + queueTail = current + self.saturated() + } + } else { + _running++ + worker.call(context, current.value, current.worked) + } + } + + function unshift (value, done) { + var current = cache.get() + + current.context = context + current.release = release + current.value = value + current.callback = done || noop + current.errorHandler = errorHandler + + if (_running >= _concurrency || self.paused) { + if (queueHead) { + current.next = queueHead + queueHead = current + } else { + queueHead = current + queueTail = current + self.saturated() + } + } else { + _running++ + worker.call(context, current.value, current.worked) + } + } + + function release (holder) { + if (holder) { + cache.release(holder) + } + var next = queueHead + if (next && _running <= _concurrency) { + if (!self.paused) { + if (queueTail === queueHead) { + queueTail = null + } + queueHead = next.next + next.next = null + worker.call(context, next.value, next.worked) + if (queueTail === null) { + self.empty() + } + } else { + _running-- + } + } else if (--_running === 0) { + self.drain() + } + } + + function kill () { + queueHead = null + queueTail = null + self.drain = noop + } + + function killAndDrain () { + queueHead = null + queueTail = null + self.drain() + self.drain = noop + } + + function abort () { + var current = queueHead + queueHead = null + queueTail = null + + while (current) { + var next = current.next + var callback = current.callback + var errorHandler = current.errorHandler + var val = current.value + var context = current.context + + // Reset the task state + current.value = null + current.callback = noop + current.errorHandler = null + + // Call error handler if present + if (errorHandler) { + errorHandler(new Error('abort'), val) + } + + // Call callback with error + callback.call(context, new Error('abort')) + + // Release the task back to the pool + current.release(current) + + current = next + } + + self.drain = noop + } + + function error (handler) { + errorHandler = handler + } +} + +function noop () {} + +function Task () { + this.value = null + this.callback = noop + this.next = null + this.release = noop + this.context = null + this.errorHandler = null + + var self = this + + this.worked = function worked (err, result) { + var callback = self.callback + var errorHandler = self.errorHandler + var val = self.value + self.value = null + self.callback = noop + if (self.errorHandler) { + errorHandler(err, val) + } + callback.call(self.context, err, result) + self.release(self) + } +} + +function queueAsPromised (context, worker, _concurrency) { + if (typeof context === 'function') { + _concurrency = worker + worker = context + context = null + } + + function asyncWrapper (arg, cb) { + worker.call(this, arg) + .then(function (res) { + cb(null, res) + }, cb) + } + + var queue = fastqueue(context, asyncWrapper, _concurrency) + + var pushCb = queue.push + var unshiftCb = queue.unshift + + queue.push = push + queue.unshift = unshift + queue.drained = drained + + return queue + + function push (value) { + var p = new Promise(function (resolve, reject) { + pushCb(value, function (err, result) { + if (err) { + reject(err) + return + } + resolve(result) + }) + }) + + // Let's fork the promise chain to + // make the error bubble up to the user but + // not lead to a unhandledRejection + p.catch(noop) + + return p + } + + function unshift (value) { + var p = new Promise(function (resolve, reject) { + unshiftCb(value, function (err, result) { + if (err) { + reject(err) + return + } + resolve(result) + }) + }) + + // Let's fork the promise chain to + // make the error bubble up to the user but + // not lead to a unhandledRejection + p.catch(noop) + + return p + } + + function drained () { + var p = new Promise(function (resolve) { + process.nextTick(function () { + if (queue.idle()) { + resolve() + } else { + var previousDrain = queue.drain + queue.drain = function () { + if (typeof previousDrain === 'function') previousDrain() + resolve() + queue.drain = previousDrain + } + } + }) + }) + + return p + } +} + +module.exports = fastqueue +module.exports.promise = queueAsPromised diff --git a/services/slides/node_modules/fastq/test/example.ts b/services/slides/node_modules/fastq/test/example.ts new file mode 100644 index 0000000000000000000000000000000000000000..a47d4419cdc04144771c747d4c0a66da990e4eaa --- /dev/null +++ b/services/slides/node_modules/fastq/test/example.ts @@ -0,0 +1,83 @@ +import * as fastq from '../' +import { promise as queueAsPromised } from '../' + +// Basic example + +const queue = fastq(worker, 1) + +queue.push('world', (err, result) => { + if (err) throw err + console.log('the result is', result) +}) + +queue.push('push without cb') + +queue.concurrency + +queue.drain() + +queue.empty = () => undefined + +console.log('the queue tasks are', queue.getQueue()) + +queue.idle() + +queue.kill() + +queue.killAndDrain() + +queue.length + +queue.pause() + +queue.resume() + +queue.running() + +queue.saturated = () => undefined + +queue.unshift('world', (err, result) => { + if (err) throw err + console.log('the result is', result) +}) + +queue.unshift('unshift without cb') + +function worker(task: any, cb: fastq.done) { + cb(null, 'hello ' + task) +} + +// Generics example + +interface GenericsContext { + base: number; +} + +const genericsQueue = fastq({ base: 6 }, genericsWorker, 1) + +genericsQueue.push(7, (err, done) => { + if (err) throw err + console.log('the result is', done) +}) + +genericsQueue.unshift(7, (err, done) => { + if (err) throw err + console.log('the result is', done) +}) + +function genericsWorker(this: GenericsContext, task: number, cb: fastq.done) { + cb(null, 'the meaning of life is ' + (this.base * task)) +} + +const queue2 = queueAsPromised(asyncWorker, 1) + +async function asyncWorker(task: any) { + return 'hello ' + task +} + +async function run () { + await queue.push(42) + await queue.unshift(42) +} + +run() diff --git a/services/slides/node_modules/fastq/test/promise.js b/services/slides/node_modules/fastq/test/promise.js new file mode 100644 index 0000000000000000000000000000000000000000..b425fda5e16440dc9798be5a50b85bd064c8b657 --- /dev/null +++ b/services/slides/node_modules/fastq/test/promise.js @@ -0,0 +1,325 @@ +'use strict' + +const test = require('tape') +const buildQueue = require('../').promise +const { promisify } = require('util') +const sleep = promisify(setTimeout) +const immediate = promisify(setImmediate) + +test('concurrency', function (t) { + t.plan(2) + t.throws(buildQueue.bind(null, worker, 0)) + t.doesNotThrow(buildQueue.bind(null, worker, 1)) + + async function worker (arg) { + return true + } +}) + +test('worker execution', async function (t) { + const queue = buildQueue(worker, 1) + + const result = await queue.push(42) + + t.equal(result, true, 'result matches') + + async function worker (arg) { + t.equal(arg, 42) + return true + } +}) + +test('limit', async function (t) { + const queue = buildQueue(worker, 1) + + const [res1, res2] = await Promise.all([queue.push(10), queue.push(0)]) + t.equal(res1, 10, 'the result matches') + t.equal(res2, 0, 'the result matches') + + async function worker (arg) { + await sleep(arg) + return arg + } +}) + +test('multiple executions', async function (t) { + const queue = buildQueue(worker, 1) + const toExec = [1, 2, 3, 4, 5] + const expected = ['a', 'b', 'c', 'd', 'e'] + let count = 0 + + await Promise.all(toExec.map(async function (task, i) { + const result = await queue.push(task) + t.equal(result, expected[i], 'the result matches') + })) + + async function worker (arg) { + t.equal(arg, toExec[count], 'arg matches') + return expected[count++] + } +}) + +test('drained', async function (t) { + const queue = buildQueue(worker, 2) + + const toExec = new Array(10).fill(10) + let count = 0 + + async function worker (arg) { + await sleep(arg) + count++ + } + + toExec.forEach(function (i) { + queue.push(i) + }) + + await queue.drained() + + t.equal(count, toExec.length) + + toExec.forEach(function (i) { + queue.push(i) + }) + + await queue.drained() + + t.equal(count, toExec.length * 2) +}) + +test('drained with exception should not throw', async function (t) { + const queue = buildQueue(worker, 2) + + const toExec = new Array(10).fill(10) + + async function worker () { + throw new Error('foo') + } + + toExec.forEach(function (i) { + queue.push(i) + }) + + await queue.drained() +}) + +test('drained with drain function', async function (t) { + let drainCalled = false + const queue = buildQueue(worker, 2) + + queue.drain = function () { + drainCalled = true + } + + const toExec = new Array(10).fill(10) + let count = 0 + + async function worker (arg) { + await sleep(arg) + count++ + } + + toExec.forEach(function () { + queue.push() + }) + + await queue.drained() + + t.equal(count, toExec.length) + t.equal(drainCalled, true) +}) + +test('drained while idle should resolve', async function (t) { + const queue = buildQueue(worker, 2) + + async function worker (arg) { + await sleep(arg) + } + + await queue.drained() +}) + +test('drained while idle should not call the drain function', async function (t) { + let drainCalled = false + const queue = buildQueue(worker, 2) + + queue.drain = function () { + drainCalled = true + } + + async function worker (arg) { + await sleep(arg) + } + + await queue.drained() + + t.equal(drainCalled, false) +}) + +test('set this', async function (t) { + t.plan(1) + const that = {} + const queue = buildQueue(that, worker, 1) + + await queue.push(42) + + async function worker (arg) { + t.equal(this, that, 'this matches') + } +}) + +test('unshift', async function (t) { + const queue = buildQueue(worker, 1) + const expected = [1, 2, 3, 4] + + await Promise.all([ + queue.push(1), + queue.push(4), + queue.unshift(3), + queue.unshift(2) + ]) + + t.is(expected.length, 0) + + async function worker (arg) { + t.equal(expected.shift(), arg, 'tasks come in order') + } +}) + +test('push with worker throwing error', async function (t) { + t.plan(5) + const q = buildQueue(async function (task, cb) { + throw new Error('test error') + }, 1) + q.error(function (err, task) { + t.ok(err instanceof Error, 'global error handler should catch the error') + t.match(err.message, /test error/, 'error message should be "test error"') + t.equal(task, 42, 'The task executed should be passed') + }) + try { + await q.push(42) + } catch (err) { + t.ok(err instanceof Error, 'push callback should catch the error') + t.match(err.message, /test error/, 'error message should be "test error"') + } +}) + +test('unshift with worker throwing error', async function (t) { + t.plan(2) + const q = buildQueue(async function (task, cb) { + throw new Error('test error') + }, 1) + try { + await q.unshift(42) + } catch (err) { + t.ok(err instanceof Error, 'push callback should catch the error') + t.match(err.message, /test error/, 'error message should be "test error"') + } +}) + +test('no unhandledRejection (push)', async function (t) { + function handleRejection () { + t.fail('unhandledRejection') + } + process.once('unhandledRejection', handleRejection) + const q = buildQueue(async function (task, cb) { + throw new Error('test error') + }, 1) + + q.push(42) + + await immediate() + process.removeListener('unhandledRejection', handleRejection) +}) + +test('no unhandledRejection (unshift)', async function (t) { + function handleRejection () { + t.fail('unhandledRejection') + } + process.once('unhandledRejection', handleRejection) + const q = buildQueue(async function (task, cb) { + throw new Error('test error') + }, 1) + + q.unshift(42) + + await immediate() + process.removeListener('unhandledRejection', handleRejection) +}) + +test('drained should resolve after async tasks complete', async function (t) { + const logs = [] + + async function processTask () { + await new Promise(resolve => setTimeout(resolve, 0)) + logs.push('processed') + } + + const queue = buildQueue(processTask, 1) + queue.drain = () => logs.push('called drain') + + queue.drained().then(() => logs.push('drained promise resolved')) + + await Promise.all([ + queue.push(), + queue.push(), + queue.push() + ]) + + t.deepEqual(logs, [ + 'processed', + 'processed', + 'processed', + 'called drain', + 'drained promise resolved' + ], 'events happened in correct order') +}) + +test('drained should handle undefined drain function', async function (t) { + const queue = buildQueue(worker, 1) + + async function worker (arg) { + await sleep(10) + return arg + } + + queue.drain = undefined + queue.push(1) + await queue.drained() + + t.pass('drained resolved successfully with undefined drain') +}) + +test('abort rejects all pending promises', async function (t) { + const queue = buildQueue(worker, 1) + const promises = [] + let rejectedCount = 0 + + // Pause queue to prevent tasks from starting + queue.pause() + + for (let i = 0; i < 10; i++) { + promises.push(queue.push(i)) + } + + queue.abort() + + // All promises should be rejected + for (const promise of promises) { + try { + await promise + t.fail('promise should have been rejected') + } catch (err) { + t.equal(err.message, 'abort', 'error message is abort') + rejectedCount++ + } + } + + t.equal(rejectedCount, 10, 'all promises were rejected') + t.equal(queue.length(), 0, 'queue is empty') + + async function worker (arg) { + await sleep(500) + return arg + } +}) diff --git a/services/slides/node_modules/fastq/test/test.js b/services/slides/node_modules/fastq/test/test.js new file mode 100644 index 0000000000000000000000000000000000000000..37e211e9f3e4c15db0537bb111c313106fda03cc --- /dev/null +++ b/services/slides/node_modules/fastq/test/test.js @@ -0,0 +1,733 @@ +'use strict' + +/* eslint-disable no-var */ + +var test = require('tape') +var buildQueue = require('../') + +test('concurrency', function (t) { + t.plan(6) + t.throws(buildQueue.bind(null, worker, 0)) + t.throws(buildQueue.bind(null, worker, NaN)) + t.doesNotThrow(buildQueue.bind(null, worker, 1)) + + var queue = buildQueue(worker, 1) + t.throws(function () { + queue.concurrency = 0 + }) + t.throws(function () { + queue.concurrency = NaN + }) + t.doesNotThrow(function () { + queue.concurrency = 2 + }) + + function worker (arg, cb) { + cb(null, true) + } +}) + +test('worker execution', function (t) { + t.plan(3) + + var queue = buildQueue(worker, 1) + + queue.push(42, function (err, result) { + t.error(err, 'no error') + t.equal(result, true, 'result matches') + }) + + function worker (arg, cb) { + t.equal(arg, 42) + cb(null, true) + } +}) + +test('limit', function (t) { + t.plan(4) + + var expected = [10, 0] + var queue = buildQueue(worker, 1) + + queue.push(10, result) + queue.push(0, result) + + function result (err, arg) { + t.error(err, 'no error') + t.equal(arg, expected.shift(), 'the result matches') + } + + function worker (arg, cb) { + setTimeout(cb, arg, null, arg) + } +}) + +test('multiple executions', function (t) { + t.plan(15) + + var queue = buildQueue(worker, 1) + var toExec = [1, 2, 3, 4, 5] + var count = 0 + + toExec.forEach(function (task) { + queue.push(task, done) + }) + + function done (err, result) { + t.error(err, 'no error') + t.equal(result, toExec[count - 1], 'the result matches') + } + + function worker (arg, cb) { + t.equal(arg, toExec[count], 'arg matches') + count++ + setImmediate(cb, null, arg) + } +}) + +test('multiple executions, one after another', function (t) { + t.plan(15) + + var queue = buildQueue(worker, 1) + var toExec = [1, 2, 3, 4, 5] + var count = 0 + + queue.push(toExec[0], done) + + function done (err, result) { + t.error(err, 'no error') + t.equal(result, toExec[count - 1], 'the result matches') + if (count < toExec.length) { + queue.push(toExec[count], done) + } + } + + function worker (arg, cb) { + t.equal(arg, toExec[count], 'arg matches') + count++ + setImmediate(cb, null, arg) + } +}) + +test('set this', function (t) { + t.plan(3) + + var that = {} + var queue = buildQueue(that, worker, 1) + + queue.push(42, function (err, result) { + t.error(err, 'no error') + t.equal(this, that, 'this matches') + }) + + function worker (arg, cb) { + t.equal(this, that, 'this matches') + cb(null, true) + } +}) + +test('drain', function (t) { + t.plan(4) + + var queue = buildQueue(worker, 1) + var worked = false + + queue.push(42, function (err, result) { + t.error(err, 'no error') + t.equal(result, true, 'result matches') + }) + + queue.drain = function () { + t.equal(true, worked, 'drained') + } + + function worker (arg, cb) { + t.equal(arg, 42) + worked = true + setImmediate(cb, null, true) + } +}) + +test('pause && resume', function (t) { + t.plan(13) + + var queue = buildQueue(worker, 1) + var worked = false + var expected = [42, 24] + + t.notOk(queue.paused, 'it should not be paused') + + queue.pause() + + queue.push(42, function (err, result) { + t.error(err, 'no error') + t.equal(result, true, 'result matches') + }) + + queue.push(24, function (err, result) { + t.error(err, 'no error') + t.equal(result, true, 'result matches') + }) + + t.notOk(worked, 'it should be paused') + t.ok(queue.paused, 'it should be paused') + + queue.resume() + queue.pause() + queue.resume() + queue.resume() // second resume is a no-op + + function worker (arg, cb) { + t.notOk(queue.paused, 'it should not be paused') + t.ok(queue.running() <= queue.concurrency, 'should respect the concurrency') + t.equal(arg, expected.shift()) + worked = true + process.nextTick(function () { cb(null, true) }) + } +}) + +test('pause in flight && resume', function (t) { + t.plan(16) + + var queue = buildQueue(worker, 1) + var expected = [42, 24, 12] + + t.notOk(queue.paused, 'it should not be paused') + + queue.push(42, function (err, result) { + t.error(err, 'no error') + t.equal(result, true, 'result matches') + t.ok(queue.paused, 'it should be paused') + process.nextTick(function () { + queue.resume() + queue.pause() + queue.resume() + }) + }) + + queue.push(24, function (err, result) { + t.error(err, 'no error') + t.equal(result, true, 'result matches') + t.notOk(queue.paused, 'it should not be paused') + }) + + queue.push(12, function (err, result) { + t.error(err, 'no error') + t.equal(result, true, 'result matches') + t.notOk(queue.paused, 'it should not be paused') + }) + + queue.pause() + + function worker (arg, cb) { + t.ok(queue.running() <= queue.concurrency, 'should respect the concurrency') + t.equal(arg, expected.shift()) + process.nextTick(function () { cb(null, true) }) + } +}) + +test('altering concurrency', function (t) { + t.plan(24) + + var queue = buildQueue(worker, 1) + + queue.push(24, workDone) + queue.push(24, workDone) + queue.push(24, workDone) + + queue.pause() + + queue.concurrency = 3 // concurrency changes are ignored while paused + queue.concurrency = 2 + + queue.resume() + + t.equal(queue.running(), 2, '2 jobs running') + + queue.concurrency = 3 + + t.equal(queue.running(), 3, '3 jobs running') + + queue.concurrency = 1 + + t.equal(queue.running(), 3, '3 jobs running') // running jobs can't be killed + + queue.push(24, workDone) + queue.push(24, workDone) + queue.push(24, workDone) + queue.push(24, workDone) + + function workDone (err, result) { + t.error(err, 'no error') + t.equal(result, true, 'result matches') + } + + function worker (arg, cb) { + t.ok(queue.running() <= queue.concurrency, 'should respect the concurrency') + setImmediate(function () { + cb(null, true) + }) + } +}) + +test('idle()', function (t) { + t.plan(12) + + var queue = buildQueue(worker, 1) + + t.ok(queue.idle(), 'queue is idle') + + queue.push(42, function (err, result) { + t.error(err, 'no error') + t.equal(result, true, 'result matches') + t.notOk(queue.idle(), 'queue is not idle') + }) + + queue.push(42, function (err, result) { + t.error(err, 'no error') + t.equal(result, true, 'result matches') + // it will go idle after executing this function + setImmediate(function () { + t.ok(queue.idle(), 'queue is now idle') + }) + }) + + t.notOk(queue.idle(), 'queue is not idle') + + function worker (arg, cb) { + t.notOk(queue.idle(), 'queue is not idle') + t.equal(arg, 42) + setImmediate(cb, null, true) + } +}) + +test('saturated', function (t) { + t.plan(9) + + var queue = buildQueue(worker, 1) + var preworked = 0 + var worked = 0 + + queue.saturated = function () { + t.pass('saturated') + t.equal(preworked, 1, 'started 1 task') + t.equal(worked, 0, 'worked zero task') + } + + queue.push(42, done) + queue.push(42, done) + + function done (err, result) { + t.error(err, 'no error') + t.equal(result, true, 'result matches') + } + + function worker (arg, cb) { + t.equal(arg, 42) + preworked++ + setImmediate(function () { + worked++ + cb(null, true) + }) + } +}) + +test('length', function (t) { + t.plan(7) + + var queue = buildQueue(worker, 1) + + t.equal(queue.length(), 0, 'nothing waiting') + queue.push(42, done) + t.equal(queue.length(), 0, 'nothing waiting') + queue.push(42, done) + t.equal(queue.length(), 1, 'one task waiting') + queue.push(42, done) + t.equal(queue.length(), 2, 'two tasks waiting') + + function done (err, result) { + t.error(err, 'no error') + } + + function worker (arg, cb) { + setImmediate(function () { + cb(null, true) + }) + } +}) + +test('getQueue', function (t) { + t.plan(10) + + var queue = buildQueue(worker, 1) + + t.equal(queue.getQueue().length, 0, 'nothing waiting') + queue.push(42, done) + t.equal(queue.getQueue().length, 0, 'nothing waiting') + queue.push(42, done) + t.equal(queue.getQueue().length, 1, 'one task waiting') + t.equal(queue.getQueue()[0], 42, 'should be equal') + queue.push(43, done) + t.equal(queue.getQueue().length, 2, 'two tasks waiting') + t.equal(queue.getQueue()[0], 42, 'should be equal') + t.equal(queue.getQueue()[1], 43, 'should be equal') + + function done (err, result) { + t.error(err, 'no error') + } + + function worker (arg, cb) { + setImmediate(function () { + cb(null, true) + }) + } +}) + +test('unshift', function (t) { + t.plan(8) + + var queue = buildQueue(worker, 1) + var expected = [1, 2, 3, 4] + + queue.push(1, done) + queue.push(4, done) + queue.unshift(3, done) + queue.unshift(2, done) + + function done (err, result) { + t.error(err, 'no error') + } + + function worker (arg, cb) { + t.equal(expected.shift(), arg, 'tasks come in order') + setImmediate(function () { + cb(null, true) + }) + } +}) + +test('unshift && empty', function (t) { + t.plan(2) + + var queue = buildQueue(worker, 1) + var completed = false + + queue.pause() + + queue.empty = function () { + t.notOk(completed, 'the task has not completed yet') + } + + queue.unshift(1, done) + + queue.resume() + + function done (err, result) { + completed = true + t.error(err, 'no error') + } + + function worker (arg, cb) { + setImmediate(function () { + cb(null, true) + }) + } +}) + +test('push && empty', function (t) { + t.plan(2) + + var queue = buildQueue(worker, 1) + var completed = false + + queue.pause() + + queue.empty = function () { + t.notOk(completed, 'the task has not completed yet') + } + + queue.push(1, done) + + queue.resume() + + function done (err, result) { + completed = true + t.error(err, 'no error') + } + + function worker (arg, cb) { + setImmediate(function () { + cb(null, true) + }) + } +}) + +test('kill', function (t) { + t.plan(5) + + var queue = buildQueue(worker, 1) + var expected = [1] + + var predrain = queue.drain + + queue.drain = function drain () { + t.fail('drain should never be called') + } + + queue.push(1, done) + queue.push(4, done) + queue.unshift(3, done) + queue.unshift(2, done) + queue.kill() + + function done (err, result) { + t.error(err, 'no error') + setImmediate(function () { + t.equal(queue.length(), 0, 'no queued tasks') + t.equal(queue.running(), 0, 'no running tasks') + t.equal(queue.drain, predrain, 'drain is back to default') + }) + } + + function worker (arg, cb) { + t.equal(expected.shift(), arg, 'tasks come in order') + setImmediate(function () { + cb(null, true) + }) + } +}) + +test('killAndDrain', function (t) { + t.plan(6) + + var queue = buildQueue(worker, 1) + var expected = [1] + + var predrain = queue.drain + + queue.drain = function drain () { + t.pass('drain has been called') + } + + queue.push(1, done) + queue.push(4, done) + queue.unshift(3, done) + queue.unshift(2, done) + queue.killAndDrain() + + function done (err, result) { + t.error(err, 'no error') + setImmediate(function () { + t.equal(queue.length(), 0, 'no queued tasks') + t.equal(queue.running(), 0, 'no running tasks') + t.equal(queue.drain, predrain, 'drain is back to default') + }) + } + + function worker (arg, cb) { + t.equal(expected.shift(), arg, 'tasks come in order') + setImmediate(function () { + cb(null, true) + }) + } +}) + +test('pause && idle', function (t) { + t.plan(11) + + var queue = buildQueue(worker, 1) + var worked = false + + t.notOk(queue.paused, 'it should not be paused') + t.ok(queue.idle(), 'should be idle') + + queue.pause() + + queue.push(42, function (err, result) { + t.error(err, 'no error') + t.equal(result, true, 'result matches') + }) + + t.notOk(worked, 'it should be paused') + t.ok(queue.paused, 'it should be paused') + t.notOk(queue.idle(), 'should not be idle') + + queue.resume() + + t.notOk(queue.paused, 'it should not be paused') + t.notOk(queue.idle(), 'it should not be idle') + + function worker (arg, cb) { + t.equal(arg, 42) + worked = true + process.nextTick(cb.bind(null, null, true)) + process.nextTick(function () { + t.ok(queue.idle(), 'is should be idle') + }) + } +}) + +test('push without cb', function (t) { + t.plan(1) + + var queue = buildQueue(worker, 1) + + queue.push(42) + + function worker (arg, cb) { + t.equal(arg, 42) + cb() + } +}) + +test('unshift without cb', function (t) { + t.plan(1) + + var queue = buildQueue(worker, 1) + + queue.unshift(42) + + function worker (arg, cb) { + t.equal(arg, 42) + cb() + } +}) + +test('push with worker throwing error', function (t) { + t.plan(5) + var q = buildQueue(function (task, cb) { + cb(new Error('test error'), null) + }, 1) + q.error(function (err, task) { + t.ok(err instanceof Error, 'global error handler should catch the error') + t.match(err.message, /test error/, 'error message should be "test error"') + t.equal(task, 42, 'The task executed should be passed') + }) + q.push(42, function (err) { + t.ok(err instanceof Error, 'push callback should catch the error') + t.match(err.message, /test error/, 'error message should be "test error"') + }) +}) + +test('unshift with worker throwing error', function (t) { + t.plan(5) + var q = buildQueue(function (task, cb) { + cb(new Error('test error'), null) + }, 1) + q.error(function (err, task) { + t.ok(err instanceof Error, 'global error handler should catch the error') + t.match(err.message, /test error/, 'error message should be "test error"') + t.equal(task, 42, 'The task executed should be passed') + }) + q.unshift(42, function (err) { + t.ok(err instanceof Error, 'unshift callback should catch the error') + t.match(err.message, /test error/, 'error message should be "test error"') + }) +}) + +test('pause/resume should trigger drain event', function (t) { + t.plan(1) + + var queue = buildQueue(worker, 1) + queue.pause() + queue.drain = function () { + t.pass('drain should be called') + } + + function worker (arg, cb) { + cb(null, true) + } + + queue.resume() +}) + +test('paused flag', function (t) { + t.plan(2) + + var queue = buildQueue(function (arg, cb) { + cb(null) + }, 1) + t.equal(queue.paused, false) + queue.pause() + t.equal(queue.paused, true) +}) + +test('abort', function (t) { + t.plan(11) + + var queue = buildQueue(worker, 1) + var abortedTasks = 0 + + var predrain = queue.drain + + queue.drain = function drain () { + t.fail('drain should never be called') + } + + // Pause queue to prevent tasks from starting + queue.pause() + queue.push(1, doneAborted) + queue.push(4, doneAborted) + queue.unshift(3, doneAborted) + queue.unshift(2, doneAborted) + + // Abort all queued tasks + queue.abort() + + // Verify state after abort + t.equal(queue.length(), 0, 'no queued tasks after abort') + t.equal(queue.drain, predrain, 'drain is back to default') + + setImmediate(function () { + t.equal(abortedTasks, 4, 'all queued tasks were aborted') + }) + + function doneAborted (err) { + t.ok(err, 'error is present') + t.equal(err.message, 'abort', 'error message is abort') + abortedTasks++ + } + + function worker (arg, cb) { + t.fail('worker should not be called') + setImmediate(function () { + cb(null, true) + }) + } +}) + +test('abort with error handler', function (t) { + t.plan(7) + + var queue = buildQueue(worker, 1) + var errorHandlerCalled = 0 + + queue.error(function (err, task) { + t.equal(err.message, 'abort', 'error handler receives abort error') + t.ok(task !== null, 'error handler receives task value') + errorHandlerCalled++ + }) + + // Pause queue to prevent tasks from starting + queue.pause() + queue.push(1, doneAborted) + queue.push(2, doneAborted) + + // Abort all queued tasks + queue.abort() + + setImmediate(function () { + t.equal(errorHandlerCalled, 2, 'error handler called for all aborted tasks') + }) + + function doneAborted (err) { + t.ok(err, 'callback receives error') + } + + function worker (arg, cb) { + t.fail('worker should not be called') + setImmediate(function () { + cb(null, true) + }) + } +}) diff --git a/services/slides/node_modules/fastq/test/tsconfig.json b/services/slides/node_modules/fastq/test/tsconfig.json new file mode 100644 index 0000000000000000000000000000000000000000..66e16e93052a3411133dcc8cc957ba86eb409b8e --- /dev/null +++ b/services/slides/node_modules/fastq/test/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "target": "es6", + "module": "commonjs", + "noEmit": true, + "strict": true + }, + "files": [ + "./example.ts" + ] +} diff --git a/services/slides/node_modules/find-my-way/.github/dependabot.yml b/services/slides/node_modules/find-my-way/.github/dependabot.yml new file mode 100644 index 0000000000000000000000000000000000000000..38d3c79056fb0f2ffa8040d0046614a71f153ce0 --- /dev/null +++ b/services/slides/node_modules/find-my-way/.github/dependabot.yml @@ -0,0 +1,34 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + commit-message: + # Prefix all commit messages with "chore: " + prefix: "chore" + schedule: + interval: "monthly" + open-pull-requests-limit: 10 + + - package-ecosystem: "npm" + directory: "/" + commit-message: + # Prefix all commit messages with "chore: " + prefix: "chore" + schedule: + interval: "monthly" + open-pull-requests-limit: 10 + groups: + # Production dependencies without breaking changes + dependencies: + dependency-type: "production" + update-types: + - "minor" + - "patch" + # Production dependencies with breaking changes + dependencies-major: + dependency-type: "production" + update-types: + - "major" + # Development dependencies + dev-dependencies: + dependency-type: "development" diff --git a/services/slides/node_modules/find-my-way/.github/workflows/node.js.yml b/services/slides/node_modules/find-my-way/.github/workflows/node.js.yml new file mode 100644 index 0000000000000000000000000000000000000000..0f09ead2930e1e0eeddc76e3ba264ba69a756328 --- /dev/null +++ b/services/slides/node_modules/find-my-way/.github/workflows/node.js.yml @@ -0,0 +1,68 @@ +name: Node CI + +on: + push: + branches: + - main + - next + pull_request: + +permissions: + contents: read + +jobs: + test: + name: Test + runs-on: ${{ matrix.os }} + + strategy: + matrix: + node-version: + - 20 + - 22 + - 24 + os: + - ubuntu-latest + - windows-latest + - macOS-latest + + steps: + - uses: actions/checkout@v6 + with: + persist-credentials: false + + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v6 + with: + check-latest: true + node-version: ${{ matrix.node-version }} + + - name: Install + run: | + npm install --ignore-scripts + + - name: Lint + run: | + npm run test:lint + + - name: Test + run: | + npm test + + - name: Type Definitions + run: | + npm run test:typescript + + automerge: + if: > + github.event_name == 'pull_request' && github.event.pull_request.user.login == 'dependabot[bot]' + needs: test + runs-on: ubuntu-latest + permissions: + actions: write + pull-requests: write + contents: write + steps: + - uses: fastify/github-action-merge-dependabot@30c3f8f14a4f7b315ba38dbc1b793d27128fef82 # v3.12.0 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/services/slides/node_modules/find-my-way/LICENSE b/services/slides/node_modules/find-my-way/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..db83ff2549f3118ac97c6eb27cc505258225f474 --- /dev/null +++ b/services/slides/node_modules/find-my-way/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017-2019 Tomas Della Vedova + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/services/slides/node_modules/find-my-way/README.md b/services/slides/node_modules/find-my-way/README.md new file mode 100644 index 0000000000000000000000000000000000000000..9c60acdf83be66adf8e402035e5e21ce4fef2abb --- /dev/null +++ b/services/slides/node_modules/find-my-way/README.md @@ -0,0 +1,885 @@ +# find-my-way + +[![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](http://standardjs.com/) [![Node CI](https://github.com/delvedor/find-my-way/actions/workflows/node.js.yml/badge.svg)](https://github.com/delvedor/find-my-way/actions/workflows/node.js.yml) [![NPM downloads](https://img.shields.io/npm/dm/find-my-way.svg?style=flat)](https://www.npmjs.com/package/find-my-way) + +A crazy fast HTTP router, internally uses an highly performant [Radix Tree](https://en.wikipedia.org/wiki/Radix_tree) (aka compact [Prefix Tree](https://en.wikipedia.org/wiki/Trie)), supports route params, wildcards, and it's framework independent. + +If you want to see a benchmark comparison with the most commonly used routers, see [here](https://github.com/delvedor/router-benchmark).
+Do you need a real-world example that uses this router? Check out [Fastify](https://github.com/fastify/fastify) or [Restify](https://github.com/restify/node-restify). + +- [Install](#install) +- [Usage](#usage) +- [API](#api) + - [FindMyWay([options])](#findmywayoptions) + - [on(method, path, [opts], handler, [store])](#onmethod-path-opts-handler-store) + - [Versioned routes](#versioned-routes) + - [default](#default) + - [custom](#custom) + - [on(methods[], path, [opts], handler, [store])](#onmethods-path-opts-handler-store) + - [Supported path formats](#supported-path-formats) + - [Match order](#match-order) + - [Supported methods](#supported-methods) + - [off(methods[], path, [constraints])](#offmethods-path-constraints) + - [off(methods, path)](#offmethods-path) + - [off(methods, path, constraints)](#offmethods-path-constraints-1) + - [off(methods[], path)](#offmethods-path-1) + - [off(methods[], path, constraints)](#offmethods-path-constraints-2) + - [findRoute (method, path, [constraints])](#findroute-method-path-constraints) + - [hasRoute (method, path, [constraints])](#hasroute-method-path-constraints) + - [lookup(request, response, [context], [done])](#lookuprequest-response-context-done) + - [find(method, path, [constraints])](#findmethod-path-constraints) + - [sanitizeUrlPath(url, [useSemicolonDelimiter])](#sanitizeurlpathurl-usesemicolondelimiter) + - [removeDuplicateSlashes(path)](#removeduplicateslashespath) + - [trimLastSlash(path)](#trimlastslashpath) + - [prettyPrint([{ method: 'GET', commonPrefix: false, includeMeta: true || [] }])](#prettyprint-commonprefix-false-includemeta-true---) + - [reset()](#reset) + - [routes](#routes) + - [Caveats](#caveats) + - [Shorthand methods](#shorthand-methods) +- [Constraints](#constraints) + - [Custom Constraint Strategies](#custom-constraint-strategies) +- [Acknowledgements](#acknowledgements) +- [License](#license) + + +## Install +``` +npm i find-my-way --save +``` + + +## Usage +```js +const http = require('http') +const router = require('find-my-way')() + +router.on('GET', '/', (req, res, params) => { + res.end('{"message":"hello world"}') +}) + +const server = http.createServer((req, res) => { + router.lookup(req, res) +}) + +server.listen(3000, err => { + if (err) throw err + console.log('Server listening on: http://localhost:3000') +}) +``` + + +## API + +#### FindMyWay([options]) +Instance a new router.
+You can pass a default route with the option `defaultRoute`. +```js +const router = require('find-my-way')({ + defaultRoute: (req, res) => { + res.statusCode = 404 + res.end() + } +}) +``` + +In case of a badly formatted url *(eg: `/hello/%world`)*, by default `find-my-way` will invoke the `defaultRoute`, unless you specify the `onBadUrl` option: +```js +const router = require('find-my-way')({ + onBadUrl: (path, req, res) => { + res.statusCode = 400 + res.end(`Bad path: ${path}`) + } +}) +``` + +Trailing slashes can be ignored by supplying the `ignoreTrailingSlash` option: +```js +const router = require('find-my-way')({ + ignoreTrailingSlash: true +}) +function handler (req, res, params) { + res.end('foo') +} +// maps "/foo/" and "/foo" to `handler` +router.on('GET', '/foo/', handler) +``` + +Duplicate slashes can be ignored by supplying the `ignoreDuplicateSlashes` option: +```js +const router = require('find-my-way')({ + ignoreDuplicateSlashes: true +}) +function handler (req, res, params) { + res.end('foo') +} +// maps "/foo", "//foo", "///foo", etc to `handler` +router.on('GET', '////foo', handler) +``` + +Note that when `ignoreTrailingSlash` and `ignoreDuplicateSlashes` are both set to true, duplicate slashes will first be removed and then trailing slashes will, meaning `//a//b//c//` will be converted to `/a/b/c`. + +You can set a custom length for parameters in parametric *(standard, regex and multi)* routes by using `maxParamLength` option, the default value is 100 characters.
+*If the maximum length limit is reached, the default route will be invoked.* +```js +const router = require('find-my-way')({ + maxParamLength: 500 +}) +``` + +If you want to handle the case where the `maxParamLength` is exceeded, you can provide a custom `onMaxParamLength` handler. This handler will be invoked if no other route (e.g. a wildcard) matches the path. +```js +const router = require('find-my-way')({ + onMaxParamLength: function (path, req, res) { + res.statusCode = 414 + res.end('URI Too Long') + } +}) +``` + +If you are using a regex based route, `find-my-way` will throw an error if detects potentially catastrophic exponential-time regular expressions *(internally uses [`safe-regex2`](https://github.com/fastify/safe-regex2))*.
+If you want to disable this behavior, pass the option `allowUnsafeRegex`. +```js +const router = require('find-my-way')({ + allowUnsafeRegex: true +}) +``` + +According to [RFC3986](https://tools.ietf.org/html/rfc3986#section-6.2.2.1), find-my-way is case sensitive by default. +You can disable this by setting the `caseSensitive` option to `false`: +in that case, all paths will be matched as lowercase, but the route parameters or wildcards will maintain their original letter casing. You can turn off case sensitivity with: + +```js +const router = require('find-my-way')({ + caseSensitive: false +}) +``` + +The default query string parser that find-my-way uses is [fast-querystring](https://www.npmjs.com/package/fast-querystring) module. You can change this default setting by passing the option querystringParser and use a custom one, such as [qs](https://www.npmjs.com/package/qs). + +```js +const qs = require('qs') +const router = require('find-my-way')({ + querystringParser: str => qs.parse(str) +}) + +router.on('GET', '/', (req, res, params, store, searchParams) => { + assert.equal(searchParams, { foo: 'bar', baz: 'faz' }) +}) + +router.lookup({ method: 'GET', url: '/?foo=bar&baz=faz' }, null) +``` + +According to [RFC3986](https://www.rfc-editor.org/rfc/rfc3986#section-3.4), find-my-way separates path and query string with `?` character. But earlier versions also used `;` as delimiter character. To support this behaviour, add the `useSemicolonDelimiter` option to `true`: + +```js +const router = require('find-my-way')({ + useSemicolonDelimiter: true +}) +``` + +You can assign a `buildPrettyMeta` function to sanitize a route's `store` object to use with the `prettyPrint` functions. This function should accept a single object and return an object. + +```js + +const privateKey = new Symbol('private key') +const store = { token: '12345', [privateKey]: 'private value' } + +const router = require('find-my-way')({ + buildPrettyMeta: route => { + const cleanMeta = Object.assign({}, route.store) + + // remove private properties + Object.keys(cleanMeta).forEach(k => { + if (typeof k === 'symbol') delete cleanMeta[k] + }) + + return cleanMeta // this will show up in the pretty print output! + } +}) + +store[privateKey] = 'private value' +router.on('GET', '/hello_world', (req, res) => {}, store) + +router.prettyPrint() + +//└── / (-) +// └── hello_world (GET) +// • (token) "12345" + +``` + + + +#### on(method, path, [opts], handler, [store]) +Register a new route. +```js +router.on('GET', '/example', (req, res, params, store, searchParams) => { + // your code +}) +``` +Last argument, `store` is used to pass an object that you can access later inside the handler function. If needed, `store` can be updated. +```js +router.on('GET', '/example', (req, res, params, store) => { + assert.equal(store, { message: 'hello world' }) +}, { message: 'hello world' }) +``` + +##### Versioned routes + +If needed, you can provide a `version` route constraint, which will allow you to declare multiple versions of the same route that are used selectively when requests ask for different version using the `Accept-Version` header. This is useful if you want to support several different behaviours for a given route and different clients select among them. + +If you never configure a versioned route, the `'Accept-Version'` header will be ignored. Remember to set a [Vary](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Vary) header in your responses with the value you are using for defining the versioning (e.g.: 'Accept-Version'), to prevent cache poisoning attacks. You can also configure this as part your Proxy/CDN. + +###### default + +The default versioning strategy follows the [semver](https://semver.org/) specification. When using `lookup`, `find-my-way` will automatically detect the `Accept-Version` header and route the request accordingly. Internally `find-my-way` uses the [`semver-store`](https://github.com/delvedor/semver-store) to get the correct version of the route; *advanced ranges* and *pre-releases* currently are not supported. + +*Be aware that using this feature will cause a degradation of the overall performances of the router.* + +```js +router.on('GET', '/example', { constraints: { version: '1.2.0' }}, (req, res, params) => { + res.end('Hello from 1.2.0!') +}) + +router.on('GET', '/example', { constraints: { version: '2.4.0' }}, (req, res, params) => { + res.end('Hello from 2.4.0!') +}) + +// The 'Accept-Version' header could be '1.2.0' as well as '*', '2.x' or '2.4.x' +``` + +If you declare multiple versions with the same *major* or *minor* `find-my-way` will always choose the highest compatible with the `Accept-Version` header value. + +###### custom +It's also possible to define a [custom versioning strategy](#custom-versioning) during the `find-my-way` initialization. In this case the logic of matching the request to the specific handler depends on the versioning strategy you use. + +##### on(methods[], path, [opts], handler, [store]) +Register a new route for each method specified in the `methods` array. +It comes handy when you need to declare multiple routes with the same handler but different methods. +```js +router.on(['GET', 'POST'], '/example', (req, res, params) => { + // your code +}) +``` + + +##### Supported path formats +To register a **parametric** path, use the *colon* before the parameter name. For **wildcard** use the *star*. +*Remember that static routes are always inserted before parametric and wildcard.* + +```js +// parametric +router.on('GET', '/example/:userId', (req, res, params) => {})) +router.on('GET', '/example/:userId/:secretToken', (req, res, params) => {})) + +// wildcard +router.on('GET', '/example/*', (req, res, params) => {})) +``` + +Regular expression routes are supported as well, but pay attention, RegExp are very expensive in term of performance!
+If you want to declare a regular expression route, you must put the regular expression inside round parenthesis after the parameter name. +```js +// parametric with regexp +router.on('GET', '/example/:file(^\\d+).png', () => {})) +``` + +It's possible to define more than one parameter within the same couple of slash ("/"). Such as: +```js +router.on('GET', '/example/near/:lat-:lng/radius/:r', (req, res, params) => {})) +``` +*Remember in this case to use the dash ("-") as parameters separator.* + +Finally it's possible to have multiple parameters with RegExp. +```js +router.on('GET', '/example/at/:hour(^\\d{2})h:minute(^\\d{2})m', (req, res, params) => {})) +``` +In this case as parameter separator it's possible to use whatever character is not matched by the regular expression. + +The last parameter can be made optional if you add a question mark ("?") at the end of the parameters name. +```js +router.on('GET', '/example/posts/:id?', (req, res, params) => {})) +``` +In this case you can request `/example/posts` as well as `/example/posts/1`. The optional param will be undefined if not specified. + +Having a route with multiple parameters may affect negatively the performance, so prefer single parameter approach whenever possible, especially on routes which are on the hot path of your application. + +**Note** that you must encode the parameters containing [reserved characters](https://www.rfc-editor.org/rfc/rfc3986#section-2.2). + + +##### Match order + +The routing algorithm matches one node at a time (where the node is a string between two slashes), +this means that it cannot know if a route is static or dynamic until it finishes to match the URL. + +The nodes are matched in the following order: + +1. static +2. parametric node with static ending +3. parametric(regex)/multi-parametric +4. parametric +5. wildcard + +So if you declare the following routes + +- `/foo/filename.png` - static route +- `/foo/:filename.png` - route with param `filename` and static ending `.png` +- `/foo/:filename.:ext` - route with two params `filename` and `ext` +- `/foo/:filename` - route with one param `filename` +- `/*` - wildcard route + +You will have next matching rules: +- the static node would have the highest priority. It will be matched only if incoming URL equals `/foo/filename.png` +- the parametric node with a static ending would have the higher priority than other parametric nodes without it. This node would match any filenames with `.png` extension. If one node static ending ends with another node static ending, the node with a longer static ending would have higher priority. + - `/foo/:filename.png.png` - higher priority, more specific route + - `/foo/:filename.png` - lower priority +- the multi-parametric node (or any regexp node) without static ending would have lower priority than parametric node with static ending and higher priority than generic parametric node. You can declare only one node like that for the same route (see [caveats](#caveats)). It would match any filenames with any extensions. +- the parametric node has lower priority than any other parametric node. It would match any filenames, even if they don't have an extension. +- the wildcard node has the lowest priority of all nodes. + +Once a url has been matched, `find-my-way` will figure out which handler registered for that path matches the request if there are any constraints. +`find-my-way` will check the most constrained handlers first, which means the handlers with the most keys in the `constraints` object. + +> If you just want a path containing a colon without declaring a parameter, use a double colon. +> For example, `/name::customVerb` will be interpreted as `/name:customVerb` + + +##### Supported methods +The router is able to route all HTTP methods defined by [`http` core module](https://nodejs.org/api/http.html#http_http_methods). + + +#### off(methods[], path, [constraints]) + +Used to deregister routes. + + +##### off(methods, path) + +If no constraint argument is passed, all routes with identical path and method are deregistered, regardless of whether +a route has constraints or not. + +```js +router.on('GET', '/example', { constraints: { host: 'fastify.io' } }) +router.on('GET', '/example', { constraints: { version: '1.x' } }) +router.on('GET', '/example') + +// Deregisters all 3 routes registered above +router.off('GET', '/example') +``` + +##### off(methods, path, constraints) + +If a constraint object is specified, only those routes are deleted that have the same constraints as well as the +identical path and method. If an empty object is passed, only unconstrained routes will be deleted. +```js +router.on('GET', '/example', { constraints: { host: 'fastify.io' } }) +router.on('GET', '/example', { constraints: { version: '1.x' } }) +router.on('GET', '/example') + +// Deregisters only the third route without constraints +router.off('GET', '/example', {}) + +// Deregisters only the first route +router.off('GET', '/example', { host: 'fastify.io' }) +``` + +##### off(methods[], path) + +Deregister a route for each method specified in the methods array. It comes handy when you need to deregister multiple +routes with the same path but different methods. As explained above, the constraints will be ignored here. + +```js +router.on('GET', '/example', { constraints: { host: 'fastify.io' } }) +router.on('POST', '/example', { constraints: { version: '1.x' } }) +router.on('PUT', '/example') + +// Deregisters all 3 routes registered above +router.off(['POST', 'GET', 'PUT'], '/example') +``` + +##### off(methods[], path, constraints) + +```js +router.on('GET', '/example', { constraints: { host: 'fastify.io' } }) // first route +router.on('POST', '/example', { constraints: { host: 'fastify.io' } }) // second route +router.on('POST', '/example', { constraints: { host: 'google.de' } }) // third route +router.on('GET', '/example') // fourth route +router.on('POST', '/example') // fifth route + +// Deregisters only first and second route +router.off(['POST', 'GET'], '/example', { host: 'fastify.io' }) + +// Deregisters only fourth and fifth route +router.off(['POST', 'GET'], '/example', {}) +``` + +#### findRoute (method, path, [constraints]) + +Finds a route by server route's path (not like `find` which finds a route by the url). Returns the route object if found, otherwise returns `null`. `findRoute` does not compare routes paths directly, instead it compares only paths patters. This means that `findRoute` will return a route even if the path passed to it does not match the route's path exactly. For example, if a route is registered with the path `/example/:param1`, `findRoute` will return the route if the path passed to it is `/example/:param2`. + +```js +const handler = (req, res, params) => { + res.end('Hello World!') +} +router.on('GET', '/:file(^\\S+).png', handler) + +router.findRoute('GET', '/:file(^\\S+).png') +// => { handler: Function, store: Object, params: ['file'] } + +router.findRoute('GET', '/:file(^\\D+).jpg') +// => null +``` + +```js +const handler = (req, res, params) => { + res.end('Hello World!') +} +router.on('GET', '/:param1', handler) + +router.findRoute('GET', '/:param1') +// => { handler: Function, store: Object, params: ['param1'] } + +router.findRoute('GET', '/:param2') +// => { handler: Function, store: Object, params: ['param1'] } +``` + +#### hasRoute (method, path, [constraints]) + +Checks if a route exists by server route's path (see `findRoute` for more details). Returns `true` if found, otherwise returns `false`. + +```js +router.on('GET', '/:file(^\\S+).png', handler) + +router.hasRoute('GET', '/:file(^\\S+).png') +// => true + +router.hasRoute('GET', '/:file(^\\D+).jpg') +// => false +``` + +#### lookup(request, response, [context], [done]) + +Start a new search, `request` and `response` are the server req/res objects.
+If a route is found it will automatically call the handler, otherwise the default route will be called.
+The url is sanitized internally, all the parameters and wildcards are decoded automatically. +```js +router.lookup(req, res) +``` + +`lookup` accepts an optional context which will be the value of `this` when executing a handler +```js +router.on('GET', '*', function(req, res) { + res.end(this.greeting); +}) +router.lookup(req, res, { greeting: 'Hello, World!' }) +``` + +`lookup` accepts an optional `done` callback for case when you use an async `deriveConstraint` function. +```js +router.on('GET', '*', function(req, res) { + res.end({ hello: 'world' }); +}) +router.lookup(req, res, (err) => { + if (err !== null) { + // handle error + } + console.log('Handler executed!!!'); +}) +``` + + +#### find(method, path, [constraints]) +Return (if present) the route registered in *method:path*.
+The path must be sanitized, all the parameters and wildcards are decoded automatically.
+An object with routing constraints should usually be passed as `constraints`, containing keys like the `host` for the request, the `version` for the route to be matched, or other custom constraint values. If the router is using the default versioning strategy, the version value should be conform to the [semver](https://semver.org/) specification. If you want to use the existing constraint strategies to derive the constraint values from an incoming request, use `lookup` instead of `find`. If no value is passed for `constraints`, the router won't match any constrained routes. If using constrained routes, passing `undefined` for the constraints leads to undefined behavior and should be avoided. + +```js +router.find('GET', '/example', { host: 'fastify.io' }) +// => { handler: Function, params: Object, store: Object} +// => null + +router.find('GET', '/example', { host: 'fastify.io', version: '1.x' }) +// => { handler: Function, params: Object, store: Object} +// => null +``` + +#### sanitizeUrlPath(url, [useSemicolonDelimiter]) +Sanitize and decode a URL path using the same logic that `lookup` uses internally. + +```js +const FindMyWay = require('find-my-way') + +const url = '/foo%20bar?foo=bar' +const path = FindMyWay.sanitizeUrlPath(url) + +console.log(path) // '/foo bar' +``` + +If you need to support `;` as a query string delimiter (for example `/foo;bar=1`), pass `useSemicolonDelimiter: true`: + +```js +const path = FindMyWay.sanitizeUrlPath('/foo;bar=1', true) +console.log(path) // '/foo' +``` + +This function will throw an error if the URL is malformed. + +#### removeDuplicateSlashes(path) +Collapse consecutive `/` characters in a path into a single slash. +This is the same normalization used internally when `ignoreDuplicateSlashes` is enabled. + +```js +const FindMyWay = require('find-my-way') + +FindMyWay.removeDuplicateSlashes('//a//b///c') +// => '/a/b/c' +``` + +#### trimLastSlash(path) +Remove one trailing slash from a path, except for the root path `/`. +This is the same normalization used internally when `ignoreTrailingSlash` is enabled. + +```js +const FindMyWay = require('find-my-way') + +FindMyWay.trimLastSlash('/a/b/') +// => '/a/b' + +FindMyWay.trimLastSlash('/') +// => '/' +``` + +When both normalizations are needed, apply them in the same order used by the router: +`removeDuplicateSlashes` first, then `trimLastSlash`. + + +#### prettyPrint([{ commonPrefix: false, includeMeta: true || [] }]) +`find-my-way` builds a tree of routes for each HTTP method. If you call the `prettyPrint` +without specifying an HTTP method, it will merge all the trees to one and print it. +The merged tree does't represent the internal router structure. Don't use it for debugging. + +```js +findMyWay.on('GET', '/test', () => {}) +findMyWay.on('GET', '/test/hello', () => {}) +findMyWay.on('GET', '/testing', () => {}) +findMyWay.on('GET', '/testing/:param', () => {}) +findMyWay.on('PUT', '/update', () => {}) + +console.log(findMyWay.prettyPrint()) +// └── / +// ├── test (GET) +// │ ├── /hello (GET) +// │ └── ing (GET) +// │ └── / +// │ └── :param (GET) +// └── update (PUT) +``` + +If you want to print the internal tree, you can specify the `method` param. +Printed tree will represent the internal router structure. Use it for debugging. + +```js +findMyWay.on('GET', '/test', () => {}) +findMyWay.on('GET', '/test/hello', () => {}) +findMyWay.on('GET', '/testing', () => {}) +findMyWay.on('GET', '/testing/:param', () => {}) +findMyWay.on('PUT', '/update', () => {}) + +console.log(findMyWay.prettyPrint({ method: 'GET' })) +// └── / +// └── test (GET) +// ├── /hello (GET) +// └── ing (GET) +// └── / +// └── :param (GET) + +console.log(findMyWay.prettyPrint({ method: 'PUT' })) +// └── / +// └── update (PUT) +``` + +`prettyPrint` accepts an optional setting to print compressed routes. This is useful +when you have a large number of routes with common prefixes. Doesn't represent the +internal router structure. **Don't use it for debugging.** + +```js +console.log(findMyWay.prettyPrint({ commonPrefix: false })) +// ├── /test (GET) +// │ ├── /hello (GET) +// │ └── ing (GET) +// │ └── /:param (GET) +// └── /update (PUT) +``` + +To include a display of the `store` data passed to individual routes, the +option `includeMeta` may be passed. If set to `true` all items will be +displayed, this can also be set to an array specifying which keys (if +present) should be displayed. This information can be further sanitized +by specifying a `buildPrettyMeta` function which consumes and returns +an object. + +```js +findMyWay.on('GET', '/test', () => {}, { onRequest: () => {}, authIDs: [1, 2, 3] }) +findMyWay.on('GET', '/test/hello', () => {}, { token: 'df123-4567' }) +findMyWay.on('GET', '/testing', () => {}) +findMyWay.on('GET', '/testing/:param', () => {}) +findMyWay.on('PUT', '/update', () => {}) + +console.log(findMyWay.prettyPrint({ commonPrefix: false, includeMeta: ['onRequest'] })) +// ├── /test (GET) +// │ • (onRequest) "onRequest()" +// │ ├── /hello (GET) +// │ └── ing (GET) +// │ └── /:param (GET) +// └── /update (PUT) + +console.log(findMyWay.prettyPrint({ commonPrefix: false, includeMeta: true })) +// ├── /test (GET) +// │ • (onRequest) "onRequest()" +// │ • (authIDs) [1,2,3] +// │ ├── /hello (GET) +// │ │ • (token) "df123-4567" +// │ └── ing (GET) +// │ └── /:param (GET) +// └── /update (PUT) +``` + + +#### reset() +Empty router. +```js +router.reset() +``` + + +#### routes +Return the all routes **registered** at moment, useful for debugging. + +```js +const findMyWay = require('find-my-way')() + +findMyWay.on('GET', '/test', () => {}) +findMyWay.on('GET', '/test/hello', () => {}) + +console.log(findMyWay.routes) +// Will print +// [ +// { +// method: 'GET', +// path: '/test', +// opts: {}, +// handler: [Function], +// store: undefined +// }, +// { +// method: 'GET', +// path: '/test/hello', +// opts: {}, +// handler: [Function], +// store: undefined +// } +// ] +``` + +#### Caveats +* It's not possible to register two routes which differs only for their parameters, because internally they would be seen as the same route. In a such case you'll get an early error during the route registration phase. An example is worth thousand words: +```js +const findMyWay = FindMyWay({ + defaultRoute: (req, res) => {} +}) + +findMyWay.on('GET', '/user/:userId(^\\d+)', (req, res, params) => {}) + +findMyWay.on('GET', '/user/:username(^[a-z]+)', (req, res, params) => {}) +// Method 'GET' already declared for route ':' +``` + + +#### Shorthand methods +If you want an even nicer api, you can also use the shorthand methods to declare your routes. + +For each HTTP supported method, there's the shorthand method. For example: +```js +router.get(path, handler [, store]) +router.delete(path, handler [, store]) +router.head(path, handler [, store]) +router.patch(path, handler [, store]) +router.post(path, handler [, store]) +router.put(path, handler [, store]) +router.options(path, handler [, store]) +// ... +``` + +If you need a route that supports *all* methods you can use the `all` api. +```js +router.all(path, handler [, store]) +``` + + + +## Constraints + +`find-my-way` supports restricting handlers to only match certain requests for the same path. This can be used to support different versions of the same route that conform to a [semver](#semver) based versioning strategy, or restricting some routes to only be available on hosts. `find-my-way` has the semver based versioning strategy and a regex based hostname constraint strategy built in. + +To constrain a route to only match sometimes, pass `constraints` to the route options when registering the route: + +```js +findMyWay.on('GET', '/', { constraints: { version: '1.0.2' } }, (req, res) => { + // will only run when the request's Accept-Version header asks for a version semver compatible with 1.0.2, like 1.x, or 1.0.x. +}) + +findMyWay.on('GET', '/', { constraints: { host: 'example.com' } }, (req, res) => { + // will only run when the request's Host header is `example.com` +}) +``` + +Constraints can be combined, and route handlers will only match if __all__ of the constraints for the handler match the request. `find-my-way` does a boolean AND with each route constraint, not an OR. + +`find-my-way` will try to match the most constrained handlers first before handler with fewer or no constraints. + + +### Custom Constraint Strategies + +Custom constraining strategies can be added and are matched against incoming requests while trying to maintain `find-my-way`'s high performance. To register a new type of constraint, you must add a new constraint strategy that knows how to match values to handlers, and that knows how to get the constraint value from a request. Register strategies when constructing a router or use the addConstraintStrategy method. + +Add a custom constrain strategy when constructing a router: + +```js +const customResponseTypeStrategy = { + // strategy name for referencing in the route handler `constraints` options + name: 'accept', + // storage factory for storing routes in the find-my-way route tree + storage: function () { + let handlers = {} + return { + get: (type) => { return handlers[type] || null }, + set: (type, store) => { handlers[type] = store } + } + }, + // function to get the value of the constraint from each incoming request + deriveConstraint: (req, ctx) => { + return req.headers['accept'] + }, + // optional flag marking if handlers without constraints can match requests that have a value for this constraint + mustMatchWhenDerived: true +} + +const router = FindMyWay({ constraints: { accept: customResponseTypeStrategy } }); +``` + +Add an async custom constrain strategy when constructing a router: +```js +const asyncCustomResponseTypeStrategy = { + // strategy name for referencing in the route handler `constraints` options + name: 'accept', + // storage factory for storing routes in the find-my-way route tree + storage: function () { + let handlers = {} + return { + get: (type) => { return handlers[type] || null }, + set: (type, store) => { handlers[type] = store } + } + }, + // function to get the value of the constraint from each incoming request + deriveConstraint: (req, ctx, done) => { + done(null, req.headers['accept']) + }, + // optional flag marking if handlers without constraints can match requests that have a value for this constraint + mustMatchWhenDerived: true +} + +const router = FindMyWay({ constraints: { accept: asyncCustomResponseTypeStrategy } }); +``` + +Add a custom constraint strategy using the `addConstraintStrategy` method: +```js +const customResponseTypeStrategy = { + // strategy name for referencing in the route handler `constraints` options + name: 'accept', + // storage factory for storing routes in the find-my-way route tree + storage: function () { + let handlers = {} + return { + get: (type) => { return handlers[type] || null }, + set: (type, store) => { handlers[type] = store } + } + }, + // function to get the value of the constraint from each incoming request + deriveConstraint: (req, ctx) => { + return req.headers['accept'] + }, + // optional flag marking if handlers without constraints can match requests that have a value for this constraint + mustMatchWhenDerived: true +} + +const router = FindMyWay(); +router.addConstraintStrategy(customResponseTypeStrategy); +``` + +Once a custom constraint strategy is registered, routes can be added that are constrained using it: + + +```js +findMyWay.on('GET', '/', { constraints: { accept: 'application/fancy+json' } }, (req, res) => { + // will only run when the request's Accept header asks for 'application/fancy+json' +}) + +findMyWay.on('GET', '/', { constraints: { accept: 'application/fancy+xml' } }, (req, res) => { + // will only run when the request's Accept header asks for 'application/fancy+xml' +}) +``` + +Constraint strategies should be careful to make the `deriveConstraint` function performant as it is run for every request matched by the router. See the `lib/strategies` directory for examples of the built in constraint strategies. + + + +By default, `find-my-way` uses a built in strategies for the version constraint that uses semantic version based matching logic, which is detailed [below](#semver). It is possible to define an alternative strategy: + +```js +const customVersioning = { + // replace the built in version strategy + name: 'version', + // provide a storage factory to store handlers in a simple way + storage: function () { + let versions = {} + return { + get: (version) => { return versions[version] || null }, + set: (version, store) => { versions[version] = store } + } + }, + deriveConstraint: (req, ctx) => { + return req.headers['accept'] + }, + mustMatchWhenDerived: true, // if the request is asking for a version, don't match un-version-constrained handlers + validate (value) { // optional validate function, validates the assigned value at route-configuration (the .on function) time (not the runtime-value) + assert(typeof value === 'string', 'Version should be a string') + } +} + +const router = FindMyWay({ constraints: { version: customVersioning } }); +``` + +The custom strategy object should contain next properties: +* `storage` - a factory function to store lists of handlers for each possible constraint value. The storage object can use domain-specific storage mechanisms to store handlers in a way that makes sense for the constraint at hand. See `lib/strategies` for examples, like the `version` constraint strategy that matches using semantic versions, or the `host` strategy that allows both exact and regex host constraints. +* `deriveConstraint` - the function to determine the value of this constraint given a request + +The signature of the functions and objects must match the one from the example above. + +*Please, be aware, if you use your own constraining strategy - you use it on your own risk. This can lead both to the performance degradation and bugs which are not related to `find-my-way` itself!* + + + +## Acknowledgements + +It is inspired by the [echo](https://github.com/labstack/echo) router, some parts have been extracted from [trekjs](https://github.com/trekjs) router. + + +#### Past sponsor + +- [LetzDoIt](http://www.letzdoitapp.com/) + + +## License +**[find-my-way - MIT](https://github.com/delvedor/find-my-way/blob/master/LICENSE)**
+**[trekjs/router - MIT](https://github.com/trekjs/router/blob/master/LICENSE)** + +Copyright © 2017-2019 Tomas Della Vedova diff --git a/services/slides/node_modules/find-my-way/benchmark/bench-thread.js b/services/slides/node_modules/find-my-way/benchmark/bench-thread.js new file mode 100644 index 0000000000000000000000000000000000000000..86ed9d7cac7aa5817270fe6a196ea2f5b88a0ec3 --- /dev/null +++ b/services/slides/node_modules/find-my-way/benchmark/bench-thread.js @@ -0,0 +1,35 @@ +'use strict' + +const { workerData: benchmark, parentPort } = require('worker_threads') + +const Benchmark = require('benchmark') +// The default number of samples for Benchmark seems to be low enough that it +// can generate results with significant variance (~2%) for this benchmark +// suite. This makes it sometimes a bit confusing to actually evaluate impact of +// changes on performance. Setting the minimum of samples to 500 results in +// significantly lower variance on my local setup for this tests suite, and +// gives me higher confidence in benchmark results. +Benchmark.options.minSamples = 500 + +const suite = Benchmark.Suite() + +const FindMyWay = require('..') +const findMyWay = new FindMyWay() + +for (const { method, url, opts } of benchmark.setupURLs) { + if (opts !== undefined) { + findMyWay.on(method, url, opts, () => true) + } else { + findMyWay.on(method, url, () => true) + } +} + +suite + .add(benchmark.name, () => { + findMyWay.lookup(...benchmark.arguments) + }) + .on('cycle', (event) => { + parentPort.postMessage(String(event.target)) + }) + .on('complete', () => {}) + .run() diff --git a/services/slides/node_modules/find-my-way/benchmark/bench.js b/services/slides/node_modules/find-my-way/benchmark/bench.js new file mode 100644 index 0000000000000000000000000000000000000000..ecd936305da41df019bc0850b3ded25086df792e --- /dev/null +++ b/services/slides/node_modules/find-my-way/benchmark/bench.js @@ -0,0 +1,156 @@ +'use strict' + +const path = require('path') +const { Worker } = require('worker_threads') + +const BENCH_THREAD_PATH = path.join(__dirname, 'bench-thread.js') + +const benchmarks = [ + { + name: 'lookup root "/" route', + setupURLs: [{ method: 'GET', url: '/' }], + arguments: [{ method: 'GET', url: '/' }] + }, + { + name: 'lookup short static route', + setupURLs: [{ method: 'GET', url: '/static' }], + arguments: [{ method: 'GET', url: '/static' }] + }, + { + name: 'lookup long static route', + setupURLs: [{ method: 'GET', url: '/static/static/static/static/static' }], + arguments: [{ method: 'GET', url: '/static/static/static/static/static' }] + }, + { + name: 'lookup long static route (common prefix)', + setupURLs: [ + { method: 'GET', url: '/static' }, + { method: 'GET', url: '/static/static' }, + { method: 'GET', url: '/static/static/static' }, + { method: 'GET', url: '/static/static/static/static' }, + { method: 'GET', url: '/static/static/static/static/static' } + ], + arguments: [{ method: 'GET', url: '/static/static/static/static/static' }] + }, + { + name: 'lookup short parametric route', + setupURLs: [{ method: 'GET', url: '/:param' }], + arguments: [{ method: 'GET', url: '/param1' }] + }, + { + name: 'lookup long parametric route', + setupURLs: [{ method: 'GET', url: '/:param' }], + arguments: [{ method: 'GET', url: '/longParamParamParamParamParamParam' }] + }, + { + name: 'lookup short parametric route (encoded unoptimized)', + setupURLs: [{ method: 'GET', url: '/:param' }], + arguments: [{ method: 'GET', url: '/param%2B' }] + }, + { + name: 'lookup short parametric route (encoded optimized)', + setupURLs: [{ method: 'GET', url: '/:param' }], + arguments: [{ method: 'GET', url: '/param%20' }] + }, + { + name: 'lookup parametric route with two short params', + setupURLs: [{ method: 'GET', url: '/:param1/:param2' }], + arguments: [{ method: 'GET', url: '/param1/param2' }] + }, + { + name: 'lookup multi-parametric route with two short params', + setupURLs: [{ method: 'GET', url: '/:param1-:param2' }], + arguments: [{ method: 'GET', url: '/param1-param2' }] + }, + { + name: 'lookup multi-parametric route with two short regex params', + setupURLs: [{ method: 'GET', url: '/:param1([a-z]*)1:param2([a-z]*)2' }], + arguments: [{ method: 'GET', url: '/param1param2' }] + }, + { + name: 'lookup long static + parametric route', + setupURLs: [{ method: 'GET', url: '/static/:param1/static/:param2/static' }], + arguments: [{ method: 'GET', url: '/static/param1/static/param2/static' }] + }, + { + name: 'lookup short wildcard route', + setupURLs: [{ method: 'GET', url: '/*' }], + arguments: [{ method: 'GET', url: '/static' }] + }, + { + name: 'lookup long wildcard route', + setupURLs: [{ method: 'GET', url: '/*' }], + arguments: [{ method: 'GET', url: '/static/static/static/static/static' }] + }, + { + name: 'lookup root route on constrained router', + setupURLs: [ + { method: 'GET', url: '/' }, + { method: 'GET', url: '/static', opts: { constraints: { version: '1.2.0' } } }, + { method: 'GET', url: '/static', opts: { constraints: { version: '2.0.0', host: 'example.com' } } }, + { method: 'GET', url: '/static', opts: { constraints: { version: '2.0.0', host: 'fastify.io' } } } + ], + arguments: [{ method: 'GET', url: '/', headers: { host: 'fastify.io' } }] + }, + { + name: 'lookup short static unconstraint route', + setupURLs: [ + { method: 'GET', url: '/static', opts: {} }, + { method: 'GET', url: '/static', opts: { constraints: { version: '2.0.0', host: 'example.com' } } }, + { method: 'GET', url: '/static', opts: { constraints: { version: '2.0.0', host: 'fastify.io' } } } + ], + arguments: [{ method: 'GET', url: '/static', headers: {} }] + }, + { + name: 'lookup short static versioned route', + setupURLs: [ + { method: 'GET', url: '/static', opts: { constraints: { version: '1.2.0' } } }, + { method: 'GET', url: '/static', opts: { constraints: { version: '2.0.0', host: 'example.com' } } }, + { method: 'GET', url: '/static', opts: { constraints: { version: '2.0.0', host: 'fastify.io' } } } + ], + arguments: [{ method: 'GET', url: '/static', headers: { 'accept-version': '1.x', host: 'fastify.io' } }] + }, + { + name: 'lookup short static constrained (version & host) route', + setupURLs: [ + { method: 'GET', url: '/static', opts: { constraints: { version: '1.2.0' } } }, + { method: 'GET', url: '/static', opts: { constraints: { version: '2.0.0', host: 'example.com' } } }, + { method: 'GET', url: '/static', opts: { constraints: { version: '2.0.0', host: 'fastify.io' } } } + ], + arguments: [{ method: 'GET', url: '/static', headers: { 'accept-version': '2.x', host: 'fastify.io' } }] + } +] + +async function runBenchmark (benchmark) { + const worker = new Worker(BENCH_THREAD_PATH, { workerData: benchmark }) + + return new Promise((resolve, reject) => { + let result = null + worker.on('error', reject) + worker.on('message', (benchResult) => { + result = benchResult + }) + worker.on('exit', (code) => { + if (code === 0) { + resolve(result) + } else { + reject(new Error(`Worker stopped with exit code ${code}`)) + } + }) + }) +} + +async function runBenchmarks () { + let maxNameLength = 0 + for (const benchmark of benchmarks) { + maxNameLength = Math.max(benchmark.name.length, maxNameLength) + } + + for (const benchmark of benchmarks) { + benchmark.name = benchmark.name.padEnd(maxNameLength, '.') + const resultMessage = await runBenchmark(benchmark) + console.log(resultMessage) + } +} + +runBenchmarks() diff --git a/services/slides/node_modules/find-my-way/benchmark/compare-branches.js b/services/slides/node_modules/find-my-way/benchmark/compare-branches.js new file mode 100644 index 0000000000000000000000000000000000000000..1bda36b435d5637f586a7ed9014aa9bb3c404f16 --- /dev/null +++ b/services/slides/node_modules/find-my-way/benchmark/compare-branches.js @@ -0,0 +1,113 @@ +'use strict' + +const { spawn } = require('child_process') + +const chalk = require('chalk') +const inquirer = require('inquirer') +const simpleGit = require('simple-git') + +const git = simpleGit(process.cwd()) + +const COMMAND = 'npm run bench' +const DEFAULT_BRANCH = 'main' +const PERCENT_THRESHOLD = 5 + +async function selectBranchName (message, branches) { + const result = await inquirer.prompt([{ + type: 'list', + name: 'branch', + choices: branches, + loop: false, + pageSize: 20, + message + }]) + return result.branch +} + +async function executeCommandOnBranch (command, branch) { + console.log(chalk.grey(`Checking out "${branch}"`)) + await git.checkout(branch) + + console.log(chalk.grey(`Execute "${command}"`)) + const childProcess = spawn(command, { stdio: 'pipe', shell: true }) + + let result = '' + childProcess.stdout.on('data', (data) => { + process.stdout.write(data.toString()) + result += data.toString() + }) + + await new Promise(resolve => childProcess.on('close', resolve)) + + console.log() + + return parseBenchmarksStdout(result) +} + +function parseBenchmarksStdout (text) { + const results = [] + + for (const line of text.split('\n')) { + const match = /^(.+?)(\.*) x (.+) ops\/sec .*$/.exec(line) + if (match !== null) { + results.push({ + name: match[1], + alignedName: match[1] + match[2], + result: parseInt(match[3].replaceAll(',', '')) + }) + } + } + + return results +} + +function compareResults (featureBranch, mainBranch) { + for (const { name, alignedName, result: mainBranchResult } of mainBranch) { + const featureBranchBenchmark = featureBranch.find(result => result.name === name) + if (featureBranchBenchmark) { + const featureBranchResult = featureBranchBenchmark.result + const percent = (featureBranchResult - mainBranchResult) * 100 / mainBranchResult + const roundedPercent = Math.round(percent * 100) / 100 + + const percentString = roundedPercent > 0 ? `+${roundedPercent}%` : `${roundedPercent}%` + const message = alignedName + percentString.padStart(7, '.') + + if (roundedPercent > PERCENT_THRESHOLD) { + console.log(chalk.green(message)) + } else if (roundedPercent < -PERCENT_THRESHOLD) { + console.log(chalk.red(message)) + } else { + console.log(message) + } + } + } +} + +(async function () { + const branches = await git.branch() + const currentBranch = branches.branches[branches.current] + + let featureBranch = null + let mainBranch = null + + if (process.argv[2] === '--ci') { + featureBranch = currentBranch.name + mainBranch = DEFAULT_BRANCH + } else { + featureBranch = await selectBranchName('Select the branch you want to compare (feature branch):', branches.all) + mainBranch = await selectBranchName('Select the branch you want to compare with (main branch):', branches.all) + } + + try { + const featureBranchResult = await executeCommandOnBranch(COMMAND, featureBranch) + const mainBranchResult = await executeCommandOnBranch(COMMAND, mainBranch) + compareResults(featureBranchResult, mainBranchResult) + } catch (error) { + console.error('Switch to origin branch due to an error', error.message) + } + + await git.checkout(currentBranch.commit) + await git.checkout(currentBranch.name) + + console.log(chalk.gray(`Back to ${currentBranch.name} ${currentBranch.commit}`)) +})() diff --git a/services/slides/node_modules/find-my-way/benchmark/uri-decoding.js b/services/slides/node_modules/find-my-way/benchmark/uri-decoding.js new file mode 100644 index 0000000000000000000000000000000000000000..83ee3a36b7bfc529379dd01e9d580c078800b7fe --- /dev/null +++ b/services/slides/node_modules/find-my-way/benchmark/uri-decoding.js @@ -0,0 +1,55 @@ +'use strict' + +const fastDecode = require('fast-decode-uri-component') + +const Benchmark = require('benchmark') +Benchmark.options.minSamples = 500 + +const suite = Benchmark.Suite() + +const uri = [ + encodeURIComponent(' /?!#@=[](),\'"'), + encodeURIComponent('algunas palabras aquí'), + encodeURIComponent('acde=bdfd'), + encodeURIComponent('много русских букв'), + encodeURIComponent('這裡有些話'), + encodeURIComponent('कुछ शब्द यहाँ'), + encodeURIComponent('✌👀🎠🎡🍺') +] + +function safeFastDecode (uri) { + if (uri.indexOf('%') < 0) return uri + try { + return fastDecode(uri) + } catch (e) { + return null // or it can be null + } +} + +function safeDecodeURIComponent (uri) { + if (uri.indexOf('%') < 0) return uri + try { + return decodeURIComponent(uri) + } catch (e) { + return null // or it can be null + } +} + +uri.forEach(function (u, i) { + suite.add(`safeDecodeURIComponent(${i}) [${u}]`, function () { + safeDecodeURIComponent(u) + }) + suite.add(`fastDecode(${i}) [${u}]`, function () { + fastDecode(u) + }) + suite.add(`safeFastDecode(${i}) [${u}]`, function () { + safeFastDecode(u) + }) +}) +suite + .on('cycle', function (event) { + console.log(String(event.target)) + }) + .on('complete', function () { + }) + .run() diff --git a/services/slides/node_modules/find-my-way/example.js b/services/slides/node_modules/find-my-way/example.js new file mode 100644 index 0000000000000000000000000000000000000000..5b428c2e03562648a2558d515b4952e16d0daff0 --- /dev/null +++ b/services/slides/node_modules/find-my-way/example.js @@ -0,0 +1,29 @@ +'use strict' + +const http = require('http') +const router = require('./')({ + defaultRoute: (req, res) => { + res.end('not found') + } +}) + +router.on('GET', '/test', (req, res, params) => { + res.end('{"hello":"world"}') +}) + +router.on('GET', '/:test', (req, res, params) => { + res.end(JSON.stringify(params)) +}) + +router.on('GET', '/text/hello', (req, res, params) => { + res.end('{"winter":"is here"}') +}) + +const server = http.createServer((req, res) => { + router.lookup(req, res) +}) + +server.listen(3000, err => { + if (err) throw err + console.log('Server listening on: http://localhost:3000') +}) diff --git a/services/slides/node_modules/find-my-way/index.d.ts b/services/slides/node_modules/find-my-way/index.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..02cd5e7c531081ec681d9fa153196944b382a8bd --- /dev/null +++ b/services/slides/node_modules/find-my-way/index.d.ts @@ -0,0 +1,247 @@ +import { IncomingMessage, ServerResponse } from 'http'; +import { Http2ServerRequest, Http2ServerResponse } from 'http2'; + +declare function Router( + config?: Router.Config +): Router.Instance; + +declare namespace Router { + enum HTTPVersion { + V1 = 'http1', + V2 = 'http2' + } + + type HTTPMethod = + | 'ACL' + | 'BIND' + | 'CHECKOUT' + | 'CONNECT' + | 'COPY' + | 'DELETE' + | 'GET' + | 'HEAD' + | 'LINK' + | 'LOCK' + | 'M-SEARCH' + | 'MERGE' + | 'MKACTIVITY' + | 'MKCALENDAR' + | 'MKCOL' + | 'MOVE' + | 'NOTIFY' + | 'OPTIONS' + | 'PATCH' + | 'POST' + | 'PROPFIND' + | 'PROPPATCH' + | 'PURGE' + | 'PUT' + | 'REBIND' + | 'REPORT' + | 'SEARCH' + | 'SOURCE' + | 'SUBSCRIBE' + | 'TRACE' + | 'UNBIND' + | 'UNLINK' + | 'UNLOCK' + | 'UNSUBSCRIBE'; + + type Req = V extends HTTPVersion.V1 ? IncomingMessage : Http2ServerRequest; + type Res = V extends HTTPVersion.V1 ? ServerResponse : Http2ServerResponse; + + type Handler = ( + req: Req, + res: Res, + params: { [k: string]: string | undefined }, + store: any, + searchParams: { [k: string]: string } + ) => any; + + type Done = (err: Error | null, result: any) => void; + + interface ConstraintStrategy { + name: string, + mustMatchWhenDerived?: boolean, + storage() : { + get(value: T) : Handler | null, + set(value: T, handler: Handler) : void, + del?(value: T) : void, + empty?() : void + }, + validate?(value: unknown): void, + deriveConstraint(req: Req, ctx?: Context) : T, + } + + type QuerystringParser = (s: string) => unknown; + + function sanitizeUrlPath(url: string, useSemicolonDelimiter?: boolean): string; + function removeDuplicateSlashes(path: string): string; + function trimLastSlash(path: string): string; + + interface Config { + ignoreTrailingSlash?: boolean; + + ignoreDuplicateSlashes?: boolean; + + allowUnsafeRegex?: boolean; + + caseSensitive?: boolean; + + maxParamLength?: number; + + querystringParser?: QuerystringParser; + + defaultRoute?( + req: Req, + res: Res + ): void; + + onBadUrl?( + path: string, + req: Req, + res: Res + ): void; + + onMaxParamLength?( + path: string, + req: Req, + res: Res + ): void; + + constraints? : { + [key: string]: ConstraintStrategy + } + } + + interface RouteOptions { + constraints?: { [key: string]: any } + } + + interface ShortHandRoute { + (path: string, handler: Handler): void; + (path: string, opts: RouteOptions, handler: Handler): void; + (path: string, handler: Handler, store: any): void; + (path: string, opts: RouteOptions, handler: Handler, store: any): void; + } + + interface FindResult { + handler: Handler; + params: { [k: string]: string | undefined }; + store: any; + searchParams: { [k: string]: string }; + } + + interface FindRouteResult { + handler: Handler; + store: any; + params: string[]; + } + + interface Instance { + on( + method: HTTPMethod | HTTPMethod[], + path: string, + handler: Handler + ): void; + on( + method: HTTPMethod | HTTPMethod[], + path: string, + options: RouteOptions, + handler: Handler + ): void; + on( + method: HTTPMethod | HTTPMethod[], + path: string, + handler: Handler, + store: any + ): void; + on( + method: HTTPMethod | HTTPMethod[], + path: string, + options: RouteOptions, + handler: Handler, + store: any + ): void; + off( + method: HTTPMethod | HTTPMethod[], + path: string, + constraints?: { [key: string]: any } + ): void; + + lookup( + req: Req, + res: Res, + ctx?: Context | Done, + done?: Done + ): any; + + find( + method: HTTPMethod, + path: string, + constraints?: { [key: string]: any } + ): FindResult | null; + + findRoute( + method: HTTPMethod, + path: string, + constraints?: { [key: string]: any } + ): FindRouteResult | null; + + hasRoute( + method: HTTPMethod, + path: string, + constraints?: { [key: string]: any } + ): boolean; + + reset(): void; + prettyPrint(): string; + prettyPrint(opts: { + method?: HTTPMethod, + commonPrefix?: boolean, + includeMeta?: boolean | (string | symbol)[] + }): string; + + hasConstraintStrategy(strategyName: string): boolean; + addConstraintStrategy(constraintStrategy: ConstraintStrategy): void; + + all: ShortHandRoute; + + acl: ShortHandRoute; + bind: ShortHandRoute; + checkout: ShortHandRoute; + connect: ShortHandRoute; + copy: ShortHandRoute; + delete: ShortHandRoute; + get: ShortHandRoute; + head: ShortHandRoute; + link: ShortHandRoute; + lock: ShortHandRoute; + 'm-search': ShortHandRoute; + merge: ShortHandRoute; + mkactivity: ShortHandRoute; + mkcalendar: ShortHandRoute; + mkcol: ShortHandRoute; + move: ShortHandRoute; + notify: ShortHandRoute; + options: ShortHandRoute; + patch: ShortHandRoute; + post: ShortHandRoute; + propfind: ShortHandRoute; + proppatch: ShortHandRoute; + purge: ShortHandRoute; + put: ShortHandRoute; + rebind: ShortHandRoute; + report: ShortHandRoute; + search: ShortHandRoute; + source: ShortHandRoute; + subscribe: ShortHandRoute; + trace: ShortHandRoute; + unbind: ShortHandRoute; + unlink: ShortHandRoute; + unlock: ShortHandRoute; + unsubscribe: ShortHandRoute; + } +} + +export = Router; diff --git a/services/slides/node_modules/find-my-way/index.js b/services/slides/node_modules/find-my-way/index.js new file mode 100644 index 0000000000000000000000000000000000000000..09650b33c243f8118b876356e9ab80c5b7e33c68 --- /dev/null +++ b/services/slides/node_modules/find-my-way/index.js @@ -0,0 +1,872 @@ +'use strict' + +/* + Char codes: + '!': 33 - ! + '#': 35 - %23 + '$': 36 - %24 + '%': 37 - %25 + '&': 38 - %26 + ''': 39 - ' + '(': 40 - ( + ')': 41 - ) + '*': 42 - * + '+': 43 - %2B + ',': 44 - %2C + '-': 45 - - + '.': 46 - . + '/': 47 - %2F + ':': 58 - %3A + ';': 59 - %3B + '=': 61 - %3D + '?': 63 - %3F + '@': 64 - %40 + '_': 95 - _ + '~': 126 - ~ +*/ + +const assert = require('node:assert') +const querystring = require('fast-querystring') +const isRegexSafe = require('safe-regex2') +const deepEqual = require('fast-deep-equal') +const { prettyPrintTree } = require('./lib/pretty-print') +const { StaticNode, NODE_TYPES } = require('./lib/node') +const Constrainer = require('./lib/constrainer') +const httpMethods = require('./lib/http-methods') +const httpMethodStrategy = require('./lib/strategies/http-method') +const { safeDecodeURI, safeDecodeURIComponent } = require('./lib/url-sanitizer') + +const FULL_PATH_REGEXP = /^https?:\/\/.*?\// +const OPTIONAL_PARAM_REGEXP = /(\/:[^/()]*?)\?(\/?)/ +const ESCAPE_REGEXP = /[.*+?^${}()|[\]\\]/g +const REMOVE_DUPLICATE_SLASHES_REGEXP = /\/\/+/g + +if (!isRegexSafe(FULL_PATH_REGEXP)) { + throw new Error('the FULL_PATH_REGEXP is not safe, update this module') +} + +if (!isRegexSafe(OPTIONAL_PARAM_REGEXP)) { + throw new Error('the OPTIONAL_PARAM_REGEXP is not safe, update this module') +} + +if (!isRegexSafe(ESCAPE_REGEXP)) { + throw new Error('the ESCAPE_REGEXP is not safe, update this module') +} + +if (!isRegexSafe(REMOVE_DUPLICATE_SLASHES_REGEXP)) { + throw new Error('the REMOVE_DUPLICATE_SLASHES_REGEXP is not safe, update this module') +} + +function Router (opts) { + if (!(this instanceof Router)) { + return new Router(opts) + } + opts = opts || {} + this._opts = opts + + if (opts.defaultRoute) { + assert(typeof opts.defaultRoute === 'function', 'The default route must be a function') + this.defaultRoute = opts.defaultRoute + } else { + this.defaultRoute = null + } + + if (opts.onBadUrl) { + assert(typeof opts.onBadUrl === 'function', 'The bad url handler must be a function') + this.onBadUrl = opts.onBadUrl + } else { + this.onBadUrl = null + } + + if (opts.buildPrettyMeta) { + assert(typeof opts.buildPrettyMeta === 'function', 'buildPrettyMeta must be a function') + this.buildPrettyMeta = opts.buildPrettyMeta + } else { + this.buildPrettyMeta = defaultBuildPrettyMeta + } + + if (opts.querystringParser) { + assert(typeof opts.querystringParser === 'function', 'querystringParser must be a function') + this.querystringParser = opts.querystringParser + } else { + this.querystringParser = (query) => query.length === 0 ? {} : querystring.parse(query) + } + + this.caseSensitive = opts.caseSensitive === undefined ? true : opts.caseSensitive + this.ignoreTrailingSlash = opts.ignoreTrailingSlash || false + this.ignoreDuplicateSlashes = opts.ignoreDuplicateSlashes || false + this.maxParamLength = opts.maxParamLength || 100 + this.onMaxParamLength = opts.onMaxParamLength || null + this.allowUnsafeRegex = opts.allowUnsafeRegex || false + this.constrainer = new Constrainer(opts.constraints) + this.useSemicolonDelimiter = opts.useSemicolonDelimiter || false + + this.routes = [] + this.trees = {} +} + +Router.prototype.on = function on (method, path, opts, handler, store) { + if (typeof opts === 'function') { + if (handler !== undefined) { + store = handler + } + handler = opts + opts = {} + } + // path validation + assert(typeof path === 'string', 'Path should be a string') + assert(path.length > 0, 'The path could not be empty') + assert(path[0] === '/' || path[0] === '*', 'The first character of a path should be `/` or `*`') + // handler validation + assert(typeof handler === 'function', 'Handler should be a function') + + // path ends with optional parameter + const optionalParamMatch = path.match(OPTIONAL_PARAM_REGEXP) + if (optionalParamMatch) { + assert(path.length === optionalParamMatch.index + optionalParamMatch[0].length, 'Optional Parameter needs to be the last parameter of the path') + + const pathFull = path.replace(OPTIONAL_PARAM_REGEXP, '$1$2') + const pathOptional = path.replace(OPTIONAL_PARAM_REGEXP, '$2') || '/' + + this.on(method, pathFull, opts, handler, store) + this.on(method, pathOptional, opts, handler, store) + return + } + + const route = path + + if (this.ignoreDuplicateSlashes) { + path = removeDuplicateSlashes(path) + } + + if (this.ignoreTrailingSlash) { + path = trimLastSlash(path) + } + + const methods = Array.isArray(method) ? method : [method] + for (const method of methods) { + assert(typeof method === 'string', 'Method should be a string') + assert(httpMethods.includes(method), `Method '${method}' is not an http method.`) + this._on(method, path, opts, handler, store, route) + } +} + +Router.prototype._on = function _on (method, path, opts, handler, store) { + let constraints = {} + if (opts.constraints !== undefined) { + assert(typeof opts.constraints === 'object' && opts.constraints !== null, 'Constraints should be an object') + if (Object.keys(opts.constraints).length !== 0) { + constraints = opts.constraints + } + } + + this.constrainer.validateConstraints(constraints) + // Let the constrainer know if any constraints are being used now + this.constrainer.noteUsage(constraints) + + // Boot the tree for this method if it doesn't exist yet + if (this.trees[method] === undefined) { + this.trees[method] = new StaticNode('/') + } + + let pattern = path + if (pattern === '*' && this.trees[method].prefix.length !== 0) { + const currentRoot = this.trees[method] + this.trees[method] = new StaticNode('') + this.trees[method].staticChildren['/'] = currentRoot + } + + let currentNode = this.trees[method] + let parentNodePathIndex = currentNode.prefix.length + + const params = [] + for (let i = 0; i <= pattern.length; i++) { + if (pattern.charCodeAt(i) === 58 && pattern.charCodeAt(i + 1) === 58) { + // It's a double colon + i++ + continue + } + + const isParametricNode = pattern.charCodeAt(i) === 58 && pattern.charCodeAt(i + 1) !== 58 + const isWildcardNode = pattern.charCodeAt(i) === 42 + + if (isParametricNode || isWildcardNode || (i === pattern.length && i !== parentNodePathIndex)) { + let staticNodePath = pattern.slice(parentNodePathIndex, i) + if (!this.caseSensitive) { + staticNodePath = staticNodePath.toLowerCase() + } + staticNodePath = staticNodePath.replaceAll('::', ':') + staticNodePath = staticNodePath.replaceAll('%', '%25') + // add the static part of the route to the tree + currentNode = currentNode.createStaticChild(staticNodePath) + } + + if (isParametricNode) { + let isRegexNode = false + let isParamSafe = true + let backtrack = '' + const regexps = [] + + let lastParamStartIndex = i + 1 + for (let j = lastParamStartIndex; ; j++) { + const charCode = pattern.charCodeAt(j) + + const isRegexParam = charCode === 40 + const isStaticPart = charCode === 45 || charCode === 46 + const isEndOfNode = charCode === 47 || j === pattern.length + + if (isRegexParam || isStaticPart || isEndOfNode) { + const paramName = pattern.slice(lastParamStartIndex, j) + params.push(paramName) + + isRegexNode = isRegexNode || isRegexParam || isStaticPart + + if (isRegexParam) { + const endOfRegexIndex = getClosingParenthensePosition(pattern, j) + const regexString = pattern.slice(j, endOfRegexIndex + 1) + + if (!this.allowUnsafeRegex) { + assert(isRegexSafe(new RegExp(regexString)), `The regex '${regexString}' is not safe!`) + } + + regexps.push(trimRegExpStartAndEnd(regexString)) + + j = endOfRegexIndex + 1 + isParamSafe = true + } else { + regexps.push(isParamSafe ? '(.*?)' : `(${backtrack}|(?:(?!${backtrack}).)*)`) + isParamSafe = false + } + + const staticPartStartIndex = j + for (; j < pattern.length; j++) { + const charCode = pattern.charCodeAt(j) + if (charCode === 47) break + if (charCode === 58) { + const nextCharCode = pattern.charCodeAt(j + 1) + if (nextCharCode === 58) j++ + else break + } + } + + let staticPart = pattern.slice(staticPartStartIndex, j) + if (staticPart) { + staticPart = staticPart.replaceAll('::', ':') + staticPart = staticPart.replaceAll('%', '%25') + regexps.push(backtrack = escapeRegExp(staticPart)) + } + + lastParamStartIndex = j + 1 + + if (isEndOfNode || pattern.charCodeAt(j) === 47 || j === pattern.length) { + const nodePattern = isRegexNode ? '()' + staticPart : staticPart + const nodePath = pattern.slice(i, j) + + pattern = pattern.slice(0, i + 1) + nodePattern + pattern.slice(j) + i += nodePattern.length + + const regex = isRegexNode ? new RegExp('^' + regexps.join('') + '$') : null + currentNode = currentNode.createParametricChild(regex, staticPart || null, nodePath) + parentNodePathIndex = i + 1 + break + } + } + } + } else if (isWildcardNode) { + // add the wildcard parameter + params.push('*') + currentNode = currentNode.createWildcardChild() + parentNodePathIndex = i + 1 + + if (i !== pattern.length - 1) { + throw new Error('Wildcard must be the last character in the route') + } + } + } + + if (!this.caseSensitive) { + pattern = pattern.toLowerCase() + } + + if (pattern === '*') { + pattern = '/*' + } + + for (const existRoute of this.routes) { + const routeConstraints = existRoute.opts.constraints || {} + if ( + existRoute.method === method && + existRoute.pattern === pattern && + deepEqual(routeConstraints, constraints) + ) { + throw new Error(`Method '${method}' already declared for route '${pattern}' with constraints '${JSON.stringify(constraints)}'`) + } + } + + const route = { method, path, pattern, params, opts, handler, store } + this.routes.push(route) + currentNode.addRoute(route, this.constrainer) +} + +Router.prototype.hasRoute = function hasRoute (method, path, constraints) { + const route = this.findRoute(method, path, constraints) + return route !== null +} + +Router.prototype.findRoute = function findNode (method, path, constraints = {}) { + if (this.trees[method] === undefined) { + return null + } + + let pattern = path + + let currentNode = this.trees[method] + let parentNodePathIndex = currentNode.prefix.length + + const params = [] + for (let i = 0; i <= pattern.length; i++) { + if (pattern.charCodeAt(i) === 58 && pattern.charCodeAt(i + 1) === 58) { + // It's a double colon + i++ + continue + } + + const isParametricNode = pattern.charCodeAt(i) === 58 && pattern.charCodeAt(i + 1) !== 58 + const isWildcardNode = pattern.charCodeAt(i) === 42 + + if (isParametricNode || isWildcardNode || (i === pattern.length && i !== parentNodePathIndex)) { + let staticNodePath = pattern.slice(parentNodePathIndex, i) + if (!this.caseSensitive) { + staticNodePath = staticNodePath.toLowerCase() + } + staticNodePath = staticNodePath.replaceAll('::', ':') + staticNodePath = staticNodePath.replaceAll('%', '%25') + // add the static part of the route to the tree + currentNode = currentNode.getStaticChild(staticNodePath) + if (currentNode === null) { + return null + } + } + + if (isParametricNode) { + let isRegexNode = false + let isParamSafe = true + let backtrack = '' + const regexps = [] + + let lastParamStartIndex = i + 1 + for (let j = lastParamStartIndex; ; j++) { + const charCode = pattern.charCodeAt(j) + + const isRegexParam = charCode === 40 + const isStaticPart = charCode === 45 || charCode === 46 + const isEndOfNode = charCode === 47 || j === pattern.length + + if (isRegexParam || isStaticPart || isEndOfNode) { + const paramName = pattern.slice(lastParamStartIndex, j) + params.push(paramName) + + isRegexNode = isRegexNode || isRegexParam || isStaticPart + + if (isRegexParam) { + const endOfRegexIndex = getClosingParenthensePosition(pattern, j) + const regexString = pattern.slice(j, endOfRegexIndex + 1) + + if (!this.allowUnsafeRegex) { + assert(isRegexSafe(new RegExp(regexString)), `The regex '${regexString}' is not safe!`) + } + + regexps.push(trimRegExpStartAndEnd(regexString)) + + j = endOfRegexIndex + 1 + isParamSafe = false + } else { + regexps.push(isParamSafe ? '(.*?)' : `(${backtrack}|(?:(?!${backtrack}).)*)`) + isParamSafe = false + } + + const staticPartStartIndex = j + for (; j < pattern.length; j++) { + const charCode = pattern.charCodeAt(j) + if (charCode === 47) break + if (charCode === 58) { + const nextCharCode = pattern.charCodeAt(j + 1) + if (nextCharCode === 58) j++ + else break + } + } + + let staticPart = pattern.slice(staticPartStartIndex, j) + if (staticPart) { + staticPart = staticPart.replaceAll('::', ':') + staticPart = staticPart.replaceAll('%', '%25') + regexps.push(backtrack = escapeRegExp(staticPart)) + } + + lastParamStartIndex = j + 1 + + if (isEndOfNode || pattern.charCodeAt(j) === 47 || j === pattern.length) { + const nodePattern = isRegexNode ? '()' + staticPart : staticPart + const nodePath = pattern.slice(i, j) + + pattern = pattern.slice(0, i + 1) + nodePattern + pattern.slice(j) + i += nodePattern.length + + const regex = isRegexNode ? new RegExp('^' + regexps.join('') + '$') : null + currentNode = currentNode.getParametricChild(regex, staticPart || null, nodePath) + if (currentNode === null) { + return null + } + parentNodePathIndex = i + 1 + break + } + } + } + } else if (isWildcardNode) { + // add the wildcard parameter + params.push('*') + currentNode = currentNode.getWildcardChild() + + parentNodePathIndex = i + 1 + + if (i !== pattern.length - 1) { + throw new Error('Wildcard must be the last character in the route') + } + } + } + + if (!this.caseSensitive) { + pattern = pattern.toLowerCase() + } + + for (const existRoute of this.routes) { + const routeConstraints = existRoute.opts.constraints || {} + if ( + existRoute.method === method && + existRoute.pattern === pattern && + deepEqual(routeConstraints, constraints) + ) { + return { + handler: existRoute.handler, + store: existRoute.store, + params: existRoute.params + } + } + } + + return null +} + +Router.prototype.hasConstraintStrategy = function (strategyName) { + return this.constrainer.hasConstraintStrategy(strategyName) +} + +Router.prototype.addConstraintStrategy = function (constraints) { + this.constrainer.addConstraintStrategy(constraints) + this._rebuild(this.routes) +} + +Router.prototype.reset = function reset () { + this.trees = {} + this.routes = [] +} + +Router.prototype.off = function off (method, path, constraints) { + // path validation + assert(typeof path === 'string', 'Path should be a string') + assert(path.length > 0, 'The path could not be empty') + assert(path[0] === '/' || path[0] === '*', 'The first character of a path should be `/` or `*`') + // options validation + assert( + typeof constraints === 'undefined' || + (typeof constraints === 'object' && !Array.isArray(constraints) && constraints !== null), + 'Constraints should be an object or undefined.') + + // path ends with optional parameter + const optionalParamMatch = path.match(OPTIONAL_PARAM_REGEXP) + if (optionalParamMatch) { + assert(path.length === optionalParamMatch.index + optionalParamMatch[0].length, 'Optional Parameter needs to be the last parameter of the path') + + const pathFull = path.replace(OPTIONAL_PARAM_REGEXP, '$1$2') + const pathOptional = path.replace(OPTIONAL_PARAM_REGEXP, '$2') + + this.off(method, pathFull, constraints) + this.off(method, pathOptional, constraints) + return + } + + if (this.ignoreDuplicateSlashes) { + path = removeDuplicateSlashes(path) + } + + if (this.ignoreTrailingSlash) { + path = trimLastSlash(path) + } + + const methods = Array.isArray(method) ? method : [method] + for (const method of methods) { + this._off(method, path, constraints) + } +} + +Router.prototype._off = function _off (method, path, constraints) { + // method validation + assert(typeof method === 'string', 'Method should be a string') + assert(httpMethods.includes(method), `Method '${method}' is not an http method.`) + + function matcherWithoutConstraints (route) { + return method !== route.method || path !== route.path + } + + function matcherWithConstraints (route) { + return matcherWithoutConstraints(route) || !deepEqual(constraints, route.opts.constraints || {}) + } + + const predicate = constraints ? matcherWithConstraints : matcherWithoutConstraints + + // Rebuild tree without the specific route + const newRoutes = this.routes.filter(predicate) + this._rebuild(newRoutes) +} + +Router.prototype.lookup = function lookup (req, res, ctx, done) { + if (typeof ctx === 'function') { + done = ctx + ctx = undefined + } + + if (done === undefined) { + const constraints = this.constrainer.deriveConstraints(req, ctx) + const handle = this.find(req.method, req.url, constraints) + return this.callHandler(handle, req, res, ctx) + } + + this.constrainer.deriveConstraints(req, ctx, (err, constraints) => { + if (err !== null) { + done(err) + return + } + + try { + const handle = this.find(req.method, req.url, constraints) + const result = this.callHandler(handle, req, res, ctx) + done(null, result) + } catch (err) { + done(err) + } + }) +} + +Router.prototype.callHandler = function callHandler (handle, req, res, ctx) { + if (handle === null) return this._defaultRoute(req, res, ctx) + return ctx === undefined + ? handle.handler(req, res, handle.params, handle.store, handle.searchParams) + : handle.handler.call(ctx, req, res, handle.params, handle.store, handle.searchParams) +} + +Router.prototype.find = function find (method, path, derivedConstraints) { + let currentNode = this.trees[method] + if (currentNode === undefined) return null + + if (path.charCodeAt(0) !== 47) { // 47 is '/' + path = path.replace(FULL_PATH_REGEXP, '/') + } + + // This must be run before sanitizeUrl as the resulting function + // .sliceParameter must be constructed with same URL string used + // throughout the rest of this function. + if (this.ignoreDuplicateSlashes) { + path = removeDuplicateSlashes(path) + } + + let sanitizedUrl + let querystring + let shouldDecodeParam + + try { + sanitizedUrl = safeDecodeURI(path, this.useSemicolonDelimiter) + path = sanitizedUrl.path + querystring = sanitizedUrl.querystring + shouldDecodeParam = sanitizedUrl.shouldDecodeParam + } catch (error) { + return this._onBadUrl(path) + } + + if (this.ignoreTrailingSlash) { + path = trimLastSlash(path) + } + + const originPath = path + + if (this.caseSensitive === false) { + path = path.toLowerCase() + } + + const maxParamLength = this.maxParamLength + + let pathIndex = currentNode.prefix.length + const params = [] + const pathLen = path.length + + const brothersNodesStack = [] + let maxParamLengthExceeded = false + + while (true) { + if (pathIndex === pathLen && currentNode.isLeafNode) { + const handle = currentNode.handlerStorage.getMatchingHandler(derivedConstraints) + if (handle !== null) { + return { + handler: handle.handler, + store: handle.store, + params: handle._createParamsObject(params), + searchParams: this.querystringParser(querystring) + } + } + } + + let node = currentNode.getNextNode(path, pathIndex, brothersNodesStack, params.length) + + if (node === null) { + if (brothersNodesStack.length === 0) { + if (maxParamLengthExceeded && this.onMaxParamLength) { + return this._onMaxParamLength(originPath) + } + return null + } + + const brotherNodeState = brothersNodesStack.pop() + pathIndex = brotherNodeState.brotherPathIndex + params.splice(brotherNodeState.paramsCount) + node = brotherNodeState.brotherNode + } + + currentNode = node + + // static route + if (currentNode.kind === NODE_TYPES.STATIC) { + pathIndex += currentNode.prefix.length + continue + } + + if (currentNode.kind === NODE_TYPES.WILDCARD) { + let param = originPath.slice(pathIndex) + if (shouldDecodeParam) { + param = safeDecodeURIComponent(param) + } + + params.push(param) + pathIndex = pathLen + continue + } + + // parametric node + let paramEndIndex = originPath.indexOf('/', pathIndex) + if (paramEndIndex === -1) { + paramEndIndex = pathLen + } + + let param = originPath.slice(pathIndex, paramEndIndex) + if (shouldDecodeParam) { + param = safeDecodeURIComponent(param) + } + + if (currentNode.isRegex) { + const matchedParameters = currentNode.regex.exec(param) + if (matchedParameters === null) { + node = null + continue + } + + let regexMaxParamLengthExceeded = false + for (let i = 1; i < matchedParameters.length; i++) { + const matchedParam = matchedParameters[i] + if (matchedParam.length > maxParamLength) { + regexMaxParamLengthExceeded = true + break + } + } + + if (regexMaxParamLengthExceeded) { + maxParamLengthExceeded = true + node = null + continue + } + + for (let i = 1; i < matchedParameters.length; i++) { + params.push(matchedParameters[i]) + } + } else { + if (param.length > maxParamLength) { + maxParamLengthExceeded = true + node = null + continue + } + params.push(param) + } + + pathIndex = paramEndIndex + } +} + +Router.prototype._rebuild = function (routes) { + this.reset() + + for (const route of routes) { + const { method, path, opts, handler, store } = route + this._on(method, path, opts, handler, store) + } +} + +Router.prototype._defaultRoute = function (req, res, ctx) { + if (this.defaultRoute !== null) { + return ctx === undefined + ? this.defaultRoute(req, res) + : this.defaultRoute.call(ctx, req, res) + } else { + res.statusCode = 404 + res.end() + } +} + +Router.prototype._onBadUrl = function (path) { + if (this.onBadUrl === null) { + return null + } + const onBadUrl = this.onBadUrl + return { + handler: (req, res, ctx) => onBadUrl(path, req, res), + params: {}, + store: null + } +} + +Router.prototype._onMaxParamLength = function (path) { + if (this.onMaxParamLength === null) { + return null + } + const onMaxParamLength = this.onMaxParamLength + return { + handler: (req, res, ctx) => onMaxParamLength(path, req, res), + params: {}, + store: null + } +} + +Router.prototype.prettyPrint = function (options = {}) { + const method = options.method + + options.buildPrettyMeta = this.buildPrettyMeta.bind(this) + + let tree = null + if (method === undefined) { + const { version, host, ...constraints } = this.constrainer.strategies + constraints[httpMethodStrategy.name] = httpMethodStrategy + + const mergedRouter = new Router({ ...this._opts, constraints }) + const mergedRoutes = this.routes.map(route => { + const constraints = { + ...route.opts.constraints, + [httpMethodStrategy.name]: route.method + } + return { ...route, method: 'MERGED', opts: { constraints } } + }) + mergedRouter._rebuild(mergedRoutes) + tree = mergedRouter.trees.MERGED + } else { + tree = this.trees[method] + } + + if (tree == null) return '(empty tree)' + return prettyPrintTree(tree, options) +} + +for (const i in httpMethods) { + /* eslint no-prototype-builtins: "off" */ + if (!httpMethods.hasOwnProperty(i)) continue + const m = httpMethods[i] + const methodName = m.toLowerCase() + + Router.prototype[methodName] = function (path, handler, store) { + return this.on(m, path, handler, store) + } +} + +Router.prototype.all = function (path, handler, store) { + this.on(httpMethods, path, handler, store) +} + +Router.sanitizeUrlPath = function sanitizeUrlPath (url, useSemicolonDelimiter) { + const decoded = safeDecodeURI(url, useSemicolonDelimiter) + if (decoded.shouldDecodeParam) { + return safeDecodeURIComponent(decoded.path) + } + return decoded.path +} + +Router.removeDuplicateSlashes = removeDuplicateSlashes +Router.trimLastSlash = trimLastSlash + +module.exports = Router + +function escapeRegExp (string) { + return string.replace(ESCAPE_REGEXP, '\\$&') +} + +function removeDuplicateSlashes (path) { + return path.indexOf('//') !== -1 ? path.replace(REMOVE_DUPLICATE_SLASHES_REGEXP, '/') : path +} + +function trimLastSlash (path) { + if (path.length > 1 && path.charCodeAt(path.length - 1) === 47) { + return path.slice(0, -1) + } + return path +} + +function trimRegExpStartAndEnd (regexString) { + // removes chars that marks start "^" and end "$" of regexp + if (regexString.charCodeAt(1) === 94) { + regexString = regexString.slice(0, 1) + regexString.slice(2) + } + + if (regexString.charCodeAt(regexString.length - 2) === 36) { + regexString = regexString.slice(0, regexString.length - 2) + regexString.slice(regexString.length - 1) + } + + return regexString +} + +function getClosingParenthensePosition (path, idx) { + // `path.indexOf()` will always return the first position of the closing parenthese, + // but it's inefficient for grouped or wrong regexp expressions. + // see issues #62 and #63 for more info + + let parentheses = 1 + + while (idx < path.length) { + idx++ + + // ignore skipped chars "\" + if (path.charCodeAt(idx) === 92) { + idx++ + continue + } + + if (path.charCodeAt(idx) === 41) { + parentheses-- + } else if (path.charCodeAt(idx) === 40) { + parentheses++ + } + + if (!parentheses) return idx + } + + throw new TypeError('Invalid regexp expression in "' + path + '"') +} + +function defaultBuildPrettyMeta (route) { + // buildPrettyMeta function must return an object, which will be parsed into key/value pairs for display + if (!route) return {} + if (!route.store) return {} + return Object.assign({}, route.store) +} diff --git a/services/slides/node_modules/find-my-way/lib/constrainer.js b/services/slides/node_modules/find-my-way/lib/constrainer.js new file mode 100644 index 0000000000000000000000000000000000000000..38e82c7baafcb76173ee9c60de752dd06756d29b --- /dev/null +++ b/services/slides/node_modules/find-my-way/lib/constrainer.js @@ -0,0 +1,172 @@ +'use strict' + +const acceptVersionStrategy = require('./strategies/accept-version') +const acceptHostStrategy = require('./strategies/accept-host') +const assert = require('node:assert') + +class Constrainer { + constructor (customStrategies) { + this.strategies = { + version: acceptVersionStrategy, + host: acceptHostStrategy + } + + this.strategiesInUse = new Set() + this.asyncStrategiesInUse = new Set() + + // validate and optimize prototypes of given custom strategies + if (customStrategies) { + for (const strategy of Object.values(customStrategies)) { + this.addConstraintStrategy(strategy) + } + } + } + + isStrategyUsed (strategyName) { + return this.strategiesInUse.has(strategyName) || + this.asyncStrategiesInUse.has(strategyName) + } + + hasConstraintStrategy (strategyName) { + const customConstraintStrategy = this.strategies[strategyName] + if (customConstraintStrategy !== undefined) { + return customConstraintStrategy.isCustom || + this.isStrategyUsed(strategyName) + } + return false + } + + addConstraintStrategy (strategy) { + assert(typeof strategy.name === 'string' && strategy.name !== '', 'strategy.name is required.') + assert(strategy.storage && typeof strategy.storage === 'function', 'strategy.storage function is required.') + assert(strategy.deriveConstraint && typeof strategy.deriveConstraint === 'function', 'strategy.deriveConstraint function is required.') + + if (this.strategies[strategy.name] && this.strategies[strategy.name].isCustom) { + throw new Error(`There already exists a custom constraint with the name ${strategy.name}.`) + } + + if (this.isStrategyUsed(strategy.name)) { + throw new Error(`There already exists a route with ${strategy.name} constraint.`) + } + + strategy.isCustom = true + strategy.isAsync = strategy.deriveConstraint.length === 3 + this.strategies[strategy.name] = strategy + + if (strategy.mustMatchWhenDerived) { + this.noteUsage({ [strategy.name]: strategy }) + } + } + + deriveConstraints (req, ctx, done) { + const constraints = this.deriveSyncConstraints(req, ctx) + + if (done === undefined) { + return constraints + } + + this.deriveAsyncConstraints(constraints, req, ctx, done) + } + + deriveSyncConstraints (req, ctx) { + return undefined + } + + // When new constraints start getting used, we need to rebuild the deriver to derive them. Do so if we see novel constraints used. + noteUsage (constraints) { + if (constraints) { + const beforeSize = this.strategiesInUse.size + for (const key in constraints) { + if (!Object.hasOwn(constraints, key)) continue + const strategy = this.strategies[key] + if (strategy.isAsync) { + this.asyncStrategiesInUse.add(key) + } else { + this.strategiesInUse.add(key) + } + } + if (beforeSize !== this.strategiesInUse.size) { + this._buildDeriveConstraints() + } + } + } + + newStoreForConstraint (constraint) { + if (!this.strategies[constraint]) { + throw new Error(`No strategy registered for constraint key ${constraint}`) + } + return this.strategies[constraint].storage() + } + + validateConstraints (constraints) { + for (const key in constraints) { + if (!Object.hasOwn(constraints, key)) continue + const value = constraints[key] + if (typeof value === 'undefined') { + throw new Error('Can\'t pass an undefined constraint value, must pass null or no key at all') + } + const strategy = this.strategies[key] + if (!strategy) { + throw new Error(`No strategy registered for constraint key ${key}`) + } + if (strategy.validate) { + strategy.validate(value) + } + } + } + + deriveAsyncConstraints (constraints, req, ctx, done) { + let asyncConstraintsCount = this.asyncStrategiesInUse.size + + if (asyncConstraintsCount === 0) { + done(null, constraints) + return + } + + constraints = constraints || {} + for (const key of this.asyncStrategiesInUse) { + const strategy = this.strategies[key] + strategy.deriveConstraint(req, ctx, (err, constraintValue) => { + if (err !== null) { + done(err) + return + } + + constraints[key] = constraintValue + + if (--asyncConstraintsCount === 0) { + done(null, constraints) + } + }) + } + } + + // Optimization: build a fast function for deriving the constraints for all the strategies at once. We inline the definitions of the version constraint and the host constraint for performance. + // If no constraining strategies are in use (no routes constrain on host, or version, or any custom strategies) then we don't need to derive constraints for each route match, so don't do anything special, and just return undefined + // This allows us to not allocate an object to hold constraint values if no constraints are defined. + _buildDeriveConstraints () { + if (this.strategiesInUse.size === 0) return + + const lines = ['return {'] + + for (const key of this.strategiesInUse) { + const strategy = this.strategies[key] + // Optimization: inline the derivation for the common built in constraints + if (!strategy.isCustom) { + if (key === 'version') { + lines.push(' version: req.headers[\'accept-version\'],') + } else { + lines.push(' host: req.headers.host || req.headers[\':authority\'],') + } + } else { + lines.push(` ${strategy.name}: this.strategies.${key}.deriveConstraint(req, ctx),`) + } + } + + lines.push('}') + + this.deriveSyncConstraints = new Function('req', 'ctx', lines.join('\n')).bind(this) // eslint-disable-line + } +} + +module.exports = Constrainer diff --git a/services/slides/node_modules/find-my-way/lib/handler-storage.js b/services/slides/node_modules/find-my-way/lib/handler-storage.js new file mode 100644 index 0000000000000000000000000000000000000000..20a0acc127d5cf0463a90ff786a7c6012029b592 --- /dev/null +++ b/services/slides/node_modules/find-my-way/lib/handler-storage.js @@ -0,0 +1,175 @@ +'use strict' + +const { NullObject } = require('./null-object') +const httpMethodStrategy = require('./strategies/http-method') + +class HandlerStorage { + constructor () { + this.unconstrainedHandler = null // optimized reference to the handler that will match most of the time + this.constraints = [] + this.handlers = [] // unoptimized list of handler objects for which the fast matcher function will be compiled + this.constrainedHandlerStores = null + } + + // This is the hot path for node handler finding -- change with care! + getMatchingHandler (derivedConstraints) { + if (derivedConstraints === undefined) { + return this.unconstrainedHandler + } + return this._getHandlerMatchingConstraints(derivedConstraints) + } + + addHandler (constrainer, route) { + const params = route.params + const constraints = route.opts.constraints || {} + + const handlerObject = { + params, + constraints, + handler: route.handler, + store: route.store || null, + _createParamsObject: this._compileCreateParamsObject(params) + } + + const constraintsNames = Object.keys(constraints) + if (constraintsNames.length === 0) { + this.unconstrainedHandler = handlerObject + } + + for (const constraint of constraintsNames) { + if (!this.constraints.includes(constraint)) { + if (constraint === 'version') { + // always check the version constraint first as it is the most selective + this.constraints.unshift(constraint) + } else { + this.constraints.push(constraint) + } + } + } + + const isMergedTree = constraintsNames.includes(httpMethodStrategy.name) + if (!isMergedTree && this.handlers.length >= 31) { + throw new Error('find-my-way supports a maximum of 31 route handlers per node when there are constraints, limit reached') + } + + this.handlers.push(handlerObject) + // Sort the most constrained handlers to the front of the list of handlers so they are tested first. + this.handlers.sort((a, b) => Object.keys(a.constraints).length - Object.keys(b.constraints).length) + + if (!isMergedTree) { + this._compileGetHandlerMatchingConstraints(constrainer, constraints) + } + } + + _compileCreateParamsObject (params) { + const fnBody = [] + + fnBody.push('const fn = function _createParamsObject (paramsArray) {') + + fnBody.push('const params = new NullObject()') + for (let i = 0; i < params.length; i++) { + fnBody.push(`params['${params[i]}'] = paramsArray[${i}]`) + } + fnBody.push('return params') + fnBody.push('}') + + fnBody.push('return fn') + + return new Function('NullObject', fnBody.join('\n'))(NullObject) // eslint-disable-line + } + + _getHandlerMatchingConstraints () { + return null + } + + // Builds a store object that maps from constraint values to a bitmap of handler indexes which pass the constraint for a value + // So for a host constraint, this might look like { "fastify.io": 0b0010, "google.ca": 0b0101 }, meaning the 3rd handler is constrainted to fastify.io, and the 2nd and 4th handlers are constrained to google.ca. + // The store's implementation comes from the strategies provided to the Router. + _buildConstraintStore (store, constraint) { + for (let i = 0; i < this.handlers.length; i++) { + const handler = this.handlers[i] + const constraintValue = handler.constraints[constraint] + if (constraintValue !== undefined) { + let indexes = store.get(constraintValue) || 0 + indexes |= 1 << i // set the i-th bit for the mask because this handler is constrained by this value https://stackoverflow.com/questions/1436438/how-do-you-set-clear-and-toggle-a-single-bit-in-javascrip + store.set(constraintValue, indexes) + } + } + } + + // Builds a bitmask for a given constraint that has a bit for each handler index that is 0 when that handler *is* constrained and 1 when the handler *isnt* constrainted. This is opposite to what might be obvious, but is just for convienience when doing the bitwise operations. + _constrainedIndexBitmask (constraint) { + let mask = 0 + for (let i = 0; i < this.handlers.length; i++) { + const handler = this.handlers[i] + const constraintValue = handler.constraints[constraint] + if (constraintValue !== undefined) { + mask |= 1 << i + } + } + return ~mask + } + + // Compile a fast function to match the handlers for this node + // The function implements a general case multi-constraint matching algorithm. + // The general idea is this: we have a bunch of handlers, each with a potentially different set of constraints, and sometimes none at all. We're given a list of constraint values and we have to use the constraint-value-comparison strategies to see which handlers match the constraint values passed in. + // We do this by asking each constraint store which handler indexes match the given constraint value for each store. Trickily, the handlers that a store says match are the handlers constrained by that store, but handlers that aren't constrained at all by that store could still match just fine. So, each constraint store can only describe matches for it, and it won't have any bearing on the handlers it doesn't care about. For this reason, we have to ask each stores which handlers match and track which have been matched (or not cared about) by all of them. + // We use bitmaps to represent these lists of matches so we can use bitwise operations to implement this efficiently. Bitmaps are cheap to allocate, let us implement this masking behaviour in one CPU instruction, and are quite compact in memory. We start with a bitmap set to all 1s representing every handler that is a match candidate, and then for each constraint, see which handlers match using the store, and then mask the result by the mask of handlers that that store applies to, and bitwise AND with the candidate list. Phew. + // We consider all this compiling function complexity to be worth it, because the naive implementation that just loops over the handlers asking which stores match is quite a bit slower. + _compileGetHandlerMatchingConstraints (constrainer) { + this.constrainedHandlerStores = {} + + for (const constraint of this.constraints) { + const store = constrainer.newStoreForConstraint(constraint) + this.constrainedHandlerStores[constraint] = store + + this._buildConstraintStore(store, constraint) + } + + const lines = [] + lines.push(` + let candidates = ${(1 << this.handlers.length) - 1} + let mask, matches + `) + for (const constraint of this.constraints) { + // Setup the mask for indexes this constraint applies to. The mask bits are set to 1 for each position if the constraint applies. + lines.push(` + mask = ${this._constrainedIndexBitmask(constraint)} + value = derivedConstraints.${constraint} + `) + + // If there's no constraint value, none of the handlers constrained by this constraint can match. Remove them from the candidates. + // If there is a constraint value, get the matching indexes bitmap from the store, and mask it down to only the indexes this constraint applies to, and then bitwise and with the candidates list to leave only matching candidates left. + const strategy = constrainer.strategies[constraint] + const matchMask = strategy.mustMatchWhenDerived ? 'matches' : '(matches | mask)' + + lines.push(` + if (value === undefined) { + candidates &= mask + } else { + matches = this.constrainedHandlerStores.${constraint}.get(value) || 0 + candidates &= ${matchMask} + } + if (candidates === 0) return null; + `) + } + + // There are some constraints that can be derived and marked as "must match", where if they are derived, they only match routes that actually have a constraint on the value, like the SemVer version constraint. + // An example: a request comes in for version 1.x, and this node has a handler that matches the path, but there's no version constraint. For SemVer, the find-my-way semantics do not match this handler to that request. + // This function is used by Nodes with handlers to match when they don't have any constrained routes to exclude request that do have must match derived constraints present. + for (const constraint in constrainer.strategies) { + if (!Object.hasOwn(constrainer.strategies, constraint)) continue + const strategy = constrainer.strategies[constraint] + if (strategy.mustMatchWhenDerived && !this.constraints.includes(constraint)) { + lines.push(`if (derivedConstraints.${constraint} !== undefined) return null`) + } + } + + // Return the highest set bit index in the candidates bitmask. + lines.push('return this.handlers[31 - Math.clz32(candidates)]') + + this._getHandlerMatchingConstraints = new Function('derivedConstraints', lines.join('\n')) // eslint-disable-line + } +} + +module.exports = HandlerStorage diff --git a/services/slides/node_modules/find-my-way/lib/http-methods.js b/services/slides/node_modules/find-my-way/lib/http-methods.js new file mode 100644 index 0000000000000000000000000000000000000000..c681f7a9af2fa86abbc5ef1e1e83cc5da58afc5c --- /dev/null +++ b/services/slides/node_modules/find-my-way/lib/http-methods.js @@ -0,0 +1,13 @@ +'use strict' + +// defined by Node.js http module, a snapshot from Node.js 22.9.0 +const httpMethods = [ + 'ACL', 'BIND', 'CHECKOUT', 'CONNECT', 'COPY', 'DELETE', + 'GET', 'HEAD', 'LINK', 'LOCK', 'M-SEARCH', 'MERGE', + 'MKACTIVITY', 'MKCALENDAR', 'MKCOL', 'MOVE', 'NOTIFY', 'OPTIONS', + 'PATCH', 'POST', 'PROPFIND', 'PROPPATCH', 'PURGE', 'PUT', 'QUERY', + 'REBIND', 'REPORT', 'SEARCH', 'SOURCE', 'SUBSCRIBE', 'TRACE', + 'UNBIND', 'UNLINK', 'UNLOCK', 'UNSUBSCRIBE' +] + +module.exports = httpMethods diff --git a/services/slides/node_modules/find-my-way/lib/node.js b/services/slides/node_modules/find-my-way/lib/node.js new file mode 100644 index 0000000000000000000000000000000000000000..f1daea7e07927373c05a5ea684777f230ecd242e --- /dev/null +++ b/services/slides/node_modules/find-my-way/lib/node.js @@ -0,0 +1,228 @@ +'use strict' + +const HandlerStorage = require('./handler-storage') + +const NODE_TYPES = { + STATIC: 0, + PARAMETRIC: 1, + WILDCARD: 2 +} + +class Node { + constructor () { + this.isLeafNode = false + this.routes = null + this.handlerStorage = null + } + + addRoute (route, constrainer) { + if (this.routes === null) { + this.routes = [] + } + if (this.handlerStorage === null) { + this.handlerStorage = new HandlerStorage() + } + this.isLeafNode = true + this.routes.push(route) + this.handlerStorage.addHandler(constrainer, route) + } +} + +class ParentNode extends Node { + constructor () { + super() + this.staticChildren = {} + } + + findStaticMatchingChild (path, pathIndex) { + const staticChild = this.staticChildren[path.charAt(pathIndex)] + if (staticChild === undefined || !staticChild.matchPrefix(path, pathIndex)) { + return null + } + return staticChild + } + + getStaticChild (path, pathIndex = 0) { + if (path.length === pathIndex) { + return this + } + + const staticChild = this.findStaticMatchingChild(path, pathIndex) + if (staticChild) { + return staticChild.getStaticChild(path, pathIndex + staticChild.prefix.length) + } + + return null + } + + createStaticChild (path) { + if (path.length === 0) { + return this + } + + let staticChild = this.staticChildren[path.charAt(0)] + if (staticChild) { + let i = 1 + for (; i < staticChild.prefix.length; i++) { + if (path.charCodeAt(i) !== staticChild.prefix.charCodeAt(i)) { + staticChild = staticChild.split(this, i) + break + } + } + return staticChild.createStaticChild(path.slice(i)) + } + + const label = path.charAt(0) + this.staticChildren[label] = new StaticNode(path) + return this.staticChildren[label] + } +} + +class StaticNode extends ParentNode { + constructor (prefix) { + super() + this.prefix = prefix + this.wildcardChild = null + this.parametricChildren = [] + this.kind = NODE_TYPES.STATIC + this._compilePrefixMatch() + } + + getParametricChild (regex) { + const regexpSource = regex && regex.source + + const parametricChild = this.parametricChildren.find(child => { + const childRegexSource = child.regex && child.regex.source + return childRegexSource === regexpSource + }) + + if (parametricChild) { + return parametricChild + } + + return null + } + + createParametricChild (regex, staticSuffix, nodePath) { + let parametricChild = this.getParametricChild(regex) + if (parametricChild) { + parametricChild.nodePaths.add(nodePath) + return parametricChild + } + + parametricChild = new ParametricNode(regex, staticSuffix, nodePath) + this.parametricChildren.push(parametricChild) + this.parametricChildren.sort((child1, child2) => { + if (!child1.isRegex) return 1 + if (!child2.isRegex) return -1 + + if (child1.staticSuffix === null) return 1 + if (child2.staticSuffix === null) return -1 + + if (child2.staticSuffix.endsWith(child1.staticSuffix)) return 1 + if (child1.staticSuffix.endsWith(child2.staticSuffix)) return -1 + + return 0 + }) + + return parametricChild + } + + getWildcardChild () { + return this.wildcardChild + } + + createWildcardChild () { + this.wildcardChild = this.getWildcardChild() || new WildcardNode() + return this.wildcardChild + } + + split (parentNode, length) { + const parentPrefix = this.prefix.slice(0, length) + const childPrefix = this.prefix.slice(length) + + this.prefix = childPrefix + this._compilePrefixMatch() + + const staticNode = new StaticNode(parentPrefix) + staticNode.staticChildren[childPrefix.charAt(0)] = this + parentNode.staticChildren[parentPrefix.charAt(0)] = staticNode + + return staticNode + } + + getNextNode (path, pathIndex, nodeStack, paramsCount) { + let node = this.findStaticMatchingChild(path, pathIndex) + let parametricBrotherNodeIndex = 0 + + if (node === null) { + if (this.parametricChildren.length === 0) { + return this.wildcardChild + } + + node = this.parametricChildren[0] + parametricBrotherNodeIndex = 1 + } + + if (this.wildcardChild !== null) { + nodeStack.push({ + paramsCount, + brotherPathIndex: pathIndex, + brotherNode: this.wildcardChild + }) + } + + for (let i = this.parametricChildren.length - 1; i >= parametricBrotherNodeIndex; i--) { + nodeStack.push({ + paramsCount, + brotherPathIndex: pathIndex, + brotherNode: this.parametricChildren[i] + }) + } + + return node + } + + _compilePrefixMatch () { + if (this.prefix.length === 1) { + this.matchPrefix = () => true + return + } + + const lines = [] + for (let i = 1; i < this.prefix.length; i++) { + const charCode = this.prefix.charCodeAt(i) + lines.push(`path.charCodeAt(i + ${i}) === ${charCode}`) + } + this.matchPrefix = new Function('path', 'i', `return ${lines.join(' && ')}`) // eslint-disable-line + } +} + +class ParametricNode extends ParentNode { + constructor (regex, staticSuffix, nodePath) { + super() + this.isRegex = !!regex + this.regex = regex || null + this.staticSuffix = staticSuffix || null + this.kind = NODE_TYPES.PARAMETRIC + + this.nodePaths = new Set([nodePath]) + } + + getNextNode (path, pathIndex) { + return this.findStaticMatchingChild(path, pathIndex) + } +} + +class WildcardNode extends Node { + constructor () { + super() + this.kind = NODE_TYPES.WILDCARD + } + + getNextNode () { + return null + } +} + +module.exports = { StaticNode, ParametricNode, WildcardNode, NODE_TYPES } diff --git a/services/slides/node_modules/find-my-way/lib/null-object.js b/services/slides/node_modules/find-my-way/lib/null-object.js new file mode 100644 index 0000000000000000000000000000000000000000..0740f04dc407da71cf9e277239fd44495a27cd6c --- /dev/null +++ b/services/slides/node_modules/find-my-way/lib/null-object.js @@ -0,0 +1,8 @@ +'use strict' + +const NullObject = function () {} +NullObject.prototype = Object.create(null) + +module.exports = { + NullObject +} diff --git a/services/slides/node_modules/find-my-way/lib/pretty-print.js b/services/slides/node_modules/find-my-way/lib/pretty-print.js new file mode 100644 index 0000000000000000000000000000000000000000..c5db18a8ee8230a90039b940e436c5d031ab94f2 --- /dev/null +++ b/services/slides/node_modules/find-my-way/lib/pretty-print.js @@ -0,0 +1,168 @@ +'use strict' + +const deepEqual = require('fast-deep-equal') + +const httpMethodStrategy = require('./strategies/http-method') +const treeDataSymbol = Symbol('treeData') + +function printObjectTree (obj, parentPrefix = '') { + let tree = '' + const keys = Object.keys(obj) + for (let i = 0; i < keys.length; i++) { + const key = keys[i] + const value = obj[key] + const isLast = i === keys.length - 1 + + const nodePrefix = isLast ? '└── ' : '├── ' + const childPrefix = isLast ? ' ' : '│ ' + + const nodeData = value[treeDataSymbol] || '' + const prefixedNodeData = nodeData.replaceAll('\n', '\n' + parentPrefix + childPrefix) + + tree += parentPrefix + nodePrefix + key + prefixedNodeData + '\n' + tree += printObjectTree(value, parentPrefix + childPrefix) + } + return tree +} + +function parseFunctionName (fn) { + let fName = fn.name || '' + + fName = fName.replace('bound', '').trim() + fName = (fName || 'anonymous') + '()' + return fName +} + +function parseMeta (meta) { + if (Array.isArray(meta)) return meta.map(m => parseMeta(m)) + if (typeof meta === 'symbol') return meta.toString() + if (typeof meta === 'function') return parseFunctionName(meta) + return meta +} + +function getRouteMetaData (route, options) { + if (!options.includeMeta) return {} + + const metaDataObject = options.buildPrettyMeta(route) + const filteredMetaData = {} + + let includeMetaKeys = options.includeMeta + if (!Array.isArray(includeMetaKeys)) { + includeMetaKeys = Reflect.ownKeys(metaDataObject) + } + + for (const metaKey of includeMetaKeys) { + if (!Object.prototype.hasOwnProperty.call(metaDataObject, metaKey)) continue + + const serializedKey = metaKey.toString() + const metaValue = metaDataObject[metaKey] + + if (metaValue !== undefined && metaValue !== null) { + const serializedValue = JSON.stringify(parseMeta(metaValue)) + filteredMetaData[serializedKey] = serializedValue + } + } + + return filteredMetaData +} + +function serializeMetaData (metaData) { + let serializedMetaData = '' + for (const [key, value] of Object.entries(metaData)) { + serializedMetaData += `\n• (${key}) ${value}` + } + return serializedMetaData +} + +// get original merged tree node route +function normalizeRoute (route) { + const constraints = { ...route.opts.constraints } + const method = constraints[httpMethodStrategy.name] + delete constraints[httpMethodStrategy.name] + return { ...route, method, opts: { constraints } } +} + +function serializeRoute (route) { + let serializedRoute = ` (${route.method})` + + const constraints = route.opts.constraints || {} + if (Object.keys(constraints).length !== 0) { + serializedRoute += ' ' + JSON.stringify(constraints) + } + + serializedRoute += serializeMetaData(route.metaData) + return serializedRoute +} + +function mergeSimilarRoutes (routes) { + return routes.reduce((mergedRoutes, route) => { + for (const nodeRoute of mergedRoutes) { + if ( + deepEqual(route.opts.constraints, nodeRoute.opts.constraints) && + deepEqual(route.metaData, nodeRoute.metaData) + ) { + nodeRoute.method += ', ' + route.method + return mergedRoutes + } + } + mergedRoutes.push(route) + return mergedRoutes + }, []) +} + +function serializeNode (node, prefix, options) { + let routes = node.routes + + if (options.method === undefined) { + routes = routes.map(normalizeRoute) + } + + routes = routes.map(route => { + route.metaData = getRouteMetaData(route, options) + return route + }) + + if (options.method === undefined) { + routes = mergeSimilarRoutes(routes) + } + + return routes.map(serializeRoute).join(`\n${prefix}`) +} + +function buildObjectTree (node, tree, prefix, options) { + if (node.isLeafNode || options.commonPrefix !== false) { + prefix = prefix || '(empty root node)' + tree = tree[prefix] = {} + + if (node.isLeafNode) { + tree[treeDataSymbol] = serializeNode(node, prefix, options) + } + + prefix = '' + } + + if (node.staticChildren) { + for (const child of Object.values(node.staticChildren)) { + buildObjectTree(child, tree, prefix + child.prefix, options) + } + } + + if (node.parametricChildren) { + for (const child of Object.values(node.parametricChildren)) { + const childPrefix = Array.from(child.nodePaths).join('|') + buildObjectTree(child, tree, prefix + childPrefix, options) + } + } + + if (node.wildcardChild) { + buildObjectTree(node.wildcardChild, tree, '*', options) + } +} + +function prettyPrintTree (root, options) { + const objectTree = {} + buildObjectTree(root, objectTree, root.prefix, options) + return printObjectTree(objectTree) +} + +module.exports = { prettyPrintTree } diff --git a/services/slides/node_modules/find-my-way/lib/strategies/accept-host.js b/services/slides/node_modules/find-my-way/lib/strategies/accept-host.js new file mode 100644 index 0000000000000000000000000000000000000000..c3cf95e9bedcc744f7d5abc3a062dd83653ed65b --- /dev/null +++ b/services/slides/node_modules/find-my-way/lib/strategies/accept-host.js @@ -0,0 +1,36 @@ +'use strict' +const assert = require('node:assert') + +function HostStorage () { + const hosts = new Map() + const regexHosts = [] + return { + get: (host) => { + const exact = hosts.get(host) + if (exact) { + return exact + } + for (const regex of regexHosts) { + if (regex.host.test(host)) { + return regex.value + } + } + }, + set: (host, value) => { + if (host instanceof RegExp) { + regexHosts.push({ host, value }) + } else { + hosts.set(host, value) + } + } + } +} + +module.exports = { + name: 'host', + mustMatchWhenDerived: false, + storage: HostStorage, + validate (value) { + assert(typeof value === 'string' || Object.prototype.toString.call(value) === '[object RegExp]', 'Host should be a string or a RegExp') + } +} diff --git a/services/slides/node_modules/find-my-way/lib/strategies/accept-version.js b/services/slides/node_modules/find-my-way/lib/strategies/accept-version.js new file mode 100644 index 0000000000000000000000000000000000000000..55c8d519bbdd88589f3a9cbe083003ecd0cf6361 --- /dev/null +++ b/services/slides/node_modules/find-my-way/lib/strategies/accept-version.js @@ -0,0 +1,64 @@ +'use strict' + +const assert = require('node:assert') + +function SemVerStore () { + if (!(this instanceof SemVerStore)) { + return new SemVerStore() + } + + this.store = new Map() + this.maxMajor = 0 + this.maxMinors = {} + this.maxPatches = {} +} + +SemVerStore.prototype.set = function (version, store) { + if (typeof version !== 'string') { + throw new TypeError('Version should be a string') + } + let [major, minor, patch] = version.split('.', 3) + + if (isNaN(major)) { + throw new TypeError('Major version must be a numeric value') + } + + major = Number(major) + minor = Number(minor) || 0 + patch = Number(patch) || 0 + + if (major >= this.maxMajor) { + this.maxMajor = major + this.store.set('x', store) + this.store.set('*', store) + this.store.set('x.x', store) + this.store.set('x.x.x', store) + } + + if (minor >= (this.maxMinors[major] || 0)) { + this.maxMinors[major] = minor + this.store.set(`${major}.x`, store) + this.store.set(`${major}.x.x`, store) + } + + if (patch >= (this.maxPatches[`${major}.${minor}`] || 0)) { + this.maxPatches[`${major}.${minor}`] = patch + this.store.set(`${major}.${minor}.x`, store) + } + + this.store.set(`${major}.${minor}.${patch}`, store) + return this +} + +SemVerStore.prototype.get = function (version) { + return this.store.get(version) +} + +module.exports = { + name: 'version', + mustMatchWhenDerived: true, + storage: SemVerStore, + validate (value) { + assert(typeof value === 'string', 'Version should be a string') + } +} diff --git a/services/slides/node_modules/find-my-way/lib/strategies/http-method.js b/services/slides/node_modules/find-my-way/lib/strategies/http-method.js new file mode 100644 index 0000000000000000000000000000000000000000..b61bde00bc24ec38b91cfdb3fe7667d3e667c23f --- /dev/null +++ b/services/slides/node_modules/find-my-way/lib/strategies/http-method.js @@ -0,0 +1,15 @@ +'use strict' + +module.exports = { + name: '__fmw_internal_strategy_merged_tree_http_method__', + storage: function () { + const handlers = new Map() + return { + get: (type) => { return handlers.get(type) || null }, + set: (type, store) => { handlers.set(type, store) } + } + }, + /* c8 ignore next 1 */ + deriveConstraint: (req) => req.method, + mustMatchWhenDerived: true +} diff --git a/services/slides/node_modules/find-my-way/lib/url-sanitizer.js b/services/slides/node_modules/find-my-way/lib/url-sanitizer.js new file mode 100644 index 0000000000000000000000000000000000000000..dcdc5e74f452d5fe4c6b308964bf4d7ef73032bd --- /dev/null +++ b/services/slides/node_modules/find-my-way/lib/url-sanitizer.js @@ -0,0 +1,105 @@ +'use strict' + +// It must spot all the chars where decodeURIComponent(x) !== decodeURI(x) +// The chars are: # $ & + , / : ; = ? @ +function decodeComponentChar (highCharCode, lowCharCode) { + if (highCharCode === 50) { + if (lowCharCode === 53) return '%' + + if (lowCharCode === 51) return '#' + if (lowCharCode === 52) return '$' + if (lowCharCode === 54) return '&' + if (lowCharCode === 66) return '+' + if (lowCharCode === 98) return '+' + if (lowCharCode === 67) return ',' + if (lowCharCode === 99) return ',' + if (lowCharCode === 70) return '/' + if (lowCharCode === 102) return '/' + return null + } + if (highCharCode === 51) { + if (lowCharCode === 65) return ':' + if (lowCharCode === 97) return ':' + if (lowCharCode === 66) return ';' + if (lowCharCode === 98) return ';' + if (lowCharCode === 68) return '=' + if (lowCharCode === 100) return '=' + if (lowCharCode === 70) return '?' + if (lowCharCode === 102) return '?' + return null + } + if (highCharCode === 52 && lowCharCode === 48) { + return '@' + } + return null +} + +/** + * Safely decodes a URI path, preserving reserved characters in querystring. + * + * @param {string} path - The full request path, possibly including querystring. + * @param {boolean} [useSemicolonDelimiter] - When true, also treat `;` as a query delimiter. + * @returns {{ path: string, querystring: string, shouldDecodeParam: boolean }} + * An object containing the decoded path, the raw querystring, and a flag indicating + * whether any path parameters contain percent-encoded reserved characters. + */ +function safeDecodeURI (path, useSemicolonDelimiter) { + let shouldDecode = false + let shouldDecodeParam = false + + let querystring = '' + + for (let i = 1; i < path.length; i++) { + const charCode = path.charCodeAt(i) + + if (charCode === 37) { + const highCharCode = path.charCodeAt(i + 1) + const lowCharCode = path.charCodeAt(i + 2) + + if (decodeComponentChar(highCharCode, lowCharCode) === null) { + shouldDecode = true + } else { + shouldDecodeParam = true + // %25 - encoded % char. We need to encode one more time to prevent double decoding + if (highCharCode === 50 && lowCharCode === 53) { + shouldDecode = true + path = path.slice(0, i + 1) + '25' + path.slice(i + 1) + i += 2 + } + i += 2 + } + // Some systems do not follow RFC and separate the path and query + // string with a `;` character (code 59), e.g. `/foo;jsessionid=123456`. + // Thus, we need to split on `;` as well as `?` and `#` if the useSemicolonDelimiter option is enabled. + } else if (charCode === 63 || charCode === 35 || (charCode === 59 && useSemicolonDelimiter)) { + querystring = path.slice(i + 1) + path = path.slice(0, i) + break + } + } + const decodedPath = shouldDecode ? decodeURI(path) : path + return { path: decodedPath, querystring, shouldDecodeParam } +} + +function safeDecodeURIComponent (uriComponent) { + const startIndex = uriComponent.indexOf('%') + if (startIndex === -1) return uriComponent + + let decoded = '' + let lastIndex = startIndex + + for (let i = startIndex; i < uriComponent.length; i++) { + if (uriComponent.charCodeAt(i) === 37) { + const highCharCode = uriComponent.charCodeAt(i + 1) + const lowCharCode = uriComponent.charCodeAt(i + 2) + + const decodedChar = decodeComponentChar(highCharCode, lowCharCode) + decoded += uriComponent.slice(lastIndex, i) + decodedChar + + lastIndex = i + 3 + } + } + return uriComponent.slice(0, startIndex) + decoded + uriComponent.slice(lastIndex) +} + +module.exports = { safeDecodeURI, safeDecodeURIComponent } diff --git a/services/slides/node_modules/find-my-way/package.json b/services/slides/node_modules/find-my-way/package.json new file mode 100644 index 0000000000000000000000000000000000000000..187854058ce2689192dfc070e7f6af105f672529 --- /dev/null +++ b/services/slides/node_modules/find-my-way/package.json @@ -0,0 +1,57 @@ +{ + "name": "find-my-way", + "version": "9.6.0", + "description": "Crazy fast http radix based router", + "main": "index.js", + "type": "commonjs", + "types": "index.d.ts", + "scripts": { + "bench": "node ./benchmark/bench.js", + "bench:cmp": "node ./benchmark/compare-branches.js", + "bench:cmp:ci": "node ./benchmark/compare-branches.js --ci", + "test:lint": "standard", + "test:typescript": "tsd", + "test": "standard && borp && npm run test:typescript" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/delvedor/find-my-way.git" + }, + "keywords": [ + "http", + "router", + "radix", + "fast", + "speed" + ], + "engines": { + "node": ">=20" + }, + "author": "Tomas Della Vedova - @delvedor (http://delved.org)", + "license": "MIT", + "bugs": { + "url": "https://github.com/delvedor/find-my-way/issues" + }, + "homepage": "https://github.com/delvedor/find-my-way#readme", + "devDependencies": { + "@types/node": "^25.0.3", + "benchmark": "^2.1.4", + "borp": "^1.0.0", + "chalk": "^5.4.1", + "inquirer": "^13.1.0", + "pre-commit": "^2.0.0", + "proxyquire": "^2.1.3", + "rfdc": "^1.3.0", + "simple-git": "^3.7.1", + "standard": "^17.0.0", + "tsd": "^0.33.0" + }, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-querystring": "^1.0.0", + "safe-regex2": "^5.0.0" + }, + "tsd": { + "directory": "test/types" + } +} diff --git a/services/slides/node_modules/find-my-way/test/case-insensitive.test.js b/services/slides/node_modules/find-my-way/test/case-insensitive.test.js new file mode 100644 index 0000000000000000000000000000000000000000..db73dc396838f07d298649c7e3ed4389569b8636 --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/case-insensitive.test.js @@ -0,0 +1,230 @@ +'use strict' + +const { test } = require('node:test') +const FindMyWay = require('../') + +test('case insensitive static routes of level 1', t => { + t.plan(1) + + const findMyWay = FindMyWay({ + caseSensitive: false, + defaultRoute: (req, res) => { + t.assert.fail('Should not be defaultRoute') + } + }) + + findMyWay.on('GET', '/woo', (req, res, params) => { + t.assert.ok('we should be here') + }) + + findMyWay.lookup({ method: 'GET', url: '/WOO', headers: {} }, null) +}) + +test('case insensitive static routes of level 2', t => { + t.plan(1) + + const findMyWay = FindMyWay({ + caseSensitive: false, + defaultRoute: (req, res) => { + t.assert.fail('Should not be defaultRoute') + } + }) + + findMyWay.on('GET', '/foo/woo', (req, res, params) => { + t.assert.ok('we should be here') + }) + + findMyWay.lookup({ method: 'GET', url: '/FoO/WOO', headers: {} }, null) +}) + +test('case insensitive static routes of level 3', t => { + t.plan(1) + + const findMyWay = FindMyWay({ + caseSensitive: false, + defaultRoute: (req, res) => { + t.assert.fail('Should not be defaultRoute') + } + }) + + findMyWay.on('GET', '/foo/bar/woo', (req, res, params) => { + t.assert.ok('we should be here') + }) + + findMyWay.lookup({ method: 'GET', url: '/Foo/bAR/WoO', headers: {} }, null) +}) + +test('parametric case insensitive', t => { + t.plan(1) + + const findMyWay = FindMyWay({ + caseSensitive: false, + defaultRoute: (req, res) => { + t.assert.fail('Should not be defaultRoute') + } + }) + + findMyWay.on('GET', '/foo/:param', (req, res, params) => { + t.assert.equal(params.param, 'bAR') + }) + + findMyWay.lookup({ method: 'GET', url: '/Foo/bAR', headers: {} }, null) +}) + +test('parametric case insensitive with a static part', t => { + t.plan(1) + + const findMyWay = FindMyWay({ + caseSensitive: false, + defaultRoute: (req, res) => { + t.assert.fail('Should not be defaultRoute') + } + }) + + findMyWay.on('GET', '/foo/my-:param', (req, res, params) => { + t.assert.equal(params.param, 'bAR') + }) + + findMyWay.lookup({ method: 'GET', url: '/Foo/MY-bAR', headers: {} }, null) +}) + +test('parametric case insensitive with capital letter', t => { + t.plan(1) + + const findMyWay = FindMyWay({ + caseSensitive: false, + defaultRoute: (req, res) => { + t.assert.fail('Should not be defaultRoute') + } + }) + + findMyWay.on('GET', '/foo/:Param', (req, res, params) => { + t.assert.equal(params.Param, 'bAR') + }) + + findMyWay.lookup({ method: 'GET', url: '/Foo/bAR', headers: {} }, null) +}) + +test('case insensitive with capital letter in static path with param', t => { + t.plan(1) + + const findMyWay = FindMyWay({ + caseSensitive: false, + defaultRoute: (req, res) => { + t.assert.fail('Should not be defaultRoute') + } + }) + + findMyWay.on('GET', '/Foo/bar/:param', (req, res, params) => { + t.assert.equal(params.param, 'baZ') + }) + + findMyWay.lookup({ method: 'GET', url: '/foo/bar/baZ', headers: {} }, null) +}) + +test('case insensitive with multiple paths containing capital letter in static path with param', t => { + /* + * This is a reproduction of the issue documented at + * https://github.com/delvedor/find-my-way/issues/96. + */ + t.plan(2) + + const findMyWay = FindMyWay({ + caseSensitive: false, + defaultRoute: (req, res) => { + t.assert.fail('Should not be defaultRoute') + } + }) + + findMyWay.on('GET', '/Foo/bar/:param', (req, res, params) => { + t.assert.equal(params.param, 'baZ') + }) + + findMyWay.on('GET', '/Foo/baz/:param', (req, res, params) => { + t.assert.equal(params.param, 'baR') + }) + + findMyWay.lookup({ method: 'GET', url: '/foo/bar/baZ', headers: {} }, null) + findMyWay.lookup({ method: 'GET', url: '/foo/baz/baR', headers: {} }, null) +}) + +test('case insensitive with multiple mixed-case params within same slash couple', t => { + t.plan(2) + + const findMyWay = FindMyWay({ + caseSensitive: false, + defaultRoute: (req, res) => { + t.assert.fail('Should not be defaultRoute') + } + }) + + findMyWay.on('GET', '/foo/:param1-:param2', (req, res, params) => { + t.assert.equal(params.param1, 'My') + t.assert.equal(params.param2, 'bAR') + }) + + findMyWay.lookup({ method: 'GET', url: '/FOO/My-bAR', headers: {} }, null) +}) + +test('case insensitive with multiple mixed-case params', t => { + t.plan(2) + + const findMyWay = FindMyWay({ + caseSensitive: false, + defaultRoute: (req, res) => { + t.assert.fail('Should not be defaultRoute') + } + }) + + findMyWay.on('GET', '/foo/:param1/:param2', (req, res, params) => { + t.assert.equal(params.param1, 'My') + t.assert.equal(params.param2, 'bAR') + }) + + findMyWay.lookup({ method: 'GET', url: '/FOO/My/bAR', headers: {} }, null) +}) + +test('case insensitive with wildcard', t => { + t.plan(1) + + const findMyWay = FindMyWay({ + caseSensitive: false, + defaultRoute: (req, res) => { + t.assert.fail('Should not be defaultRoute') + } + }) + + findMyWay.on('GET', '/foo/*', (req, res, params) => { + t.assert.equal(params['*'], 'baR') + }) + + findMyWay.lookup({ method: 'GET', url: '/FOO/baR', headers: {} }, null) +}) + +test('parametric case insensitive with multiple routes', t => { + t.plan(6) + + const findMyWay = FindMyWay({ + caseSensitive: false, + defaultRoute: (req, res) => { + t.assert.fail('Should not be defaultRoute') + } + }) + + findMyWay.on('POST', '/foo/:param/Static/:userId/Save', (req, res, params) => { + t.assert.equal(params.param, 'bAR') + t.assert.equal(params.userId, 'one') + }) + findMyWay.on('POST', '/foo/:param/Static/:userId/Update', (req, res, params) => { + t.assert.equal(params.param, 'Bar') + t.assert.equal(params.userId, 'two') + }) + findMyWay.on('POST', '/foo/:param/Static/:userId/CANCEL', (req, res, params) => { + t.assert.equal(params.param, 'bAR') + t.assert.equal(params.userId, 'THREE') + }) + + findMyWay.lookup({ method: 'POST', url: '/foo/bAR/static/one/SAVE', headers: {} }, null) + findMyWay.lookup({ method: 'POST', url: '/fOO/Bar/Static/two/update', headers: {} }, null) + findMyWay.lookup({ method: 'POST', url: '/Foo/bAR/STATIC/THREE/cAnCeL', headers: {} }, null) +}) diff --git a/services/slides/node_modules/find-my-way/test/constraint.custom-versioning.test.js b/services/slides/node_modules/find-my-way/test/constraint.custom-versioning.test.js new file mode 100644 index 0000000000000000000000000000000000000000..2e839ebfd1318fc74af50e6a0a4090f3f72a39aa --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/constraint.custom-versioning.test.js @@ -0,0 +1,130 @@ +'use strict' + +const { test } = require('node:test') +const FindMyWay = require('..') +const noop = () => { } + +const customVersioning = { + name: 'version', + // storage factory + storage: function () { + let versions = {} + return { + get: (version) => { return versions[version] || null }, + set: (version, store) => { versions[version] = store }, + del: (version) => { delete versions[version] }, + empty: () => { versions = {} } + } + }, + deriveConstraint: (req, ctx) => { + return req.headers.accept + } +} + +test('A route could support multiple versions (find) / 1', t => { + t.plan(5) + + const findMyWay = FindMyWay({ constraints: { version: customVersioning } }) + + findMyWay.on('GET', '/', { constraints: { version: 'application/vnd.example.api+json;version=2' } }, noop) + findMyWay.on('GET', '/', { constraints: { version: 'application/vnd.example.api+json;version=3' } }, noop) + + t.assert.ok(findMyWay.find('GET', '/', { version: 'application/vnd.example.api+json;version=2' })) + t.assert.ok(findMyWay.find('GET', '/', { version: 'application/vnd.example.api+json;version=3' })) + t.assert.ok(!findMyWay.find('GET', '/', { version: 'application/vnd.example.api+json;version=4' })) + t.assert.ok(!findMyWay.find('GET', '/', { version: 'application/vnd.example.api+json;version=5' })) + t.assert.ok(!findMyWay.find('GET', '/', { version: 'application/vnd.example.api+json;version=6' })) +}) + +test('A route could support multiple versions (find) / 1 (add strategy outside constructor)', t => { + t.plan(5) + + const findMyWay = FindMyWay() + + findMyWay.addConstraintStrategy(customVersioning) + + findMyWay.on('GET', '/', { constraints: { version: 'application/vnd.example.api+json;version=2' } }, noop) + findMyWay.on('GET', '/', { constraints: { version: 'application/vnd.example.api+json;version=3' } }, noop) + + t.assert.ok(findMyWay.find('GET', '/', { version: 'application/vnd.example.api+json;version=2' })) + t.assert.ok(findMyWay.find('GET', '/', { version: 'application/vnd.example.api+json;version=3' })) + t.assert.ok(!findMyWay.find('GET', '/', { version: 'application/vnd.example.api+json;version=4' })) + t.assert.ok(!findMyWay.find('GET', '/', { version: 'application/vnd.example.api+json;version=5' })) + t.assert.ok(!findMyWay.find('GET', '/', { version: 'application/vnd.example.api+json;version=6' })) +}) + +test('Overriding default strategies uses the custom deriveConstraint function', t => { + t.plan(2) + + const findMyWay = FindMyWay({ constraints: { version: customVersioning } }) + + findMyWay.on('GET', '/', { constraints: { version: 'application/vnd.example.api+json;version=2' } }, (req, res, params) => { + t.assert.equal(req.headers.accept, 'application/vnd.example.api+json;version=2') + }) + + findMyWay.on('GET', '/', { constraints: { version: 'application/vnd.example.api+json;version=3' } }, (req, res, params) => { + t.assert.equal(req.headers.accept, 'application/vnd.example.api+json;version=3') + }) + + findMyWay.lookup({ + method: 'GET', + url: '/', + headers: { accept: 'application/vnd.example.api+json;version=2' } + }) + findMyWay.lookup({ + method: 'GET', + url: '/', + headers: { accept: 'application/vnd.example.api+json;version=3' } + }) +}) + +test('Overriding default strategies uses the custom deriveConstraint function (add strategy outside constructor)', t => { + t.plan(2) + + const findMyWay = FindMyWay() + + findMyWay.addConstraintStrategy(customVersioning) + + findMyWay.on('GET', '/', { constraints: { version: 'application/vnd.example.api+json;version=2' } }, (req, res, params) => { + t.assert.equal(req.headers.accept, 'application/vnd.example.api+json;version=2') + }) + + findMyWay.on('GET', '/', { constraints: { version: 'application/vnd.example.api+json;version=3' } }, (req, res, params) => { + t.assert.equal(req.headers.accept, 'application/vnd.example.api+json;version=3') + }) + + findMyWay.lookup({ + method: 'GET', + url: '/', + headers: { accept: 'application/vnd.example.api+json;version=2' } + }) + findMyWay.lookup({ + method: 'GET', + url: '/', + headers: { accept: 'application/vnd.example.api+json;version=3' } + }) +}) + +test('Overriding custom strategies throws as error (add strategy outside constructor)', t => { + t.plan(1) + + const findMyWay = FindMyWay() + + findMyWay.addConstraintStrategy(customVersioning) + + t.assert.throws(() => findMyWay.addConstraintStrategy(customVersioning), + new Error('There already exists a custom constraint with the name version.') + ) +}) + +test('Overriding default strategies after defining a route with constraint', t => { + t.plan(1) + + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/', { constraints: { host: 'fastify.io', version: '1.0.0' } }, () => {}) + + t.assert.throws(() => findMyWay.addConstraintStrategy(customVersioning), + new Error('There already exists a route with version constraint.') + ) +}) diff --git a/services/slides/node_modules/find-my-way/test/constraint.custom.async.test.js b/services/slides/node_modules/find-my-way/test/constraint.custom.async.test.js new file mode 100644 index 0000000000000000000000000000000000000000..4cfe2b356f279accb20bbd8c5141f65bb408a777 --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/constraint.custom.async.test.js @@ -0,0 +1,111 @@ +'use strict' + +const { test } = require('node:test') +const FindMyWay = require('..') +const rfdc = require('rfdc')({ proto: true }) + +const customHeaderConstraint = { + name: 'requestedBy', + storage: function () { + const requestedBys = {} + return { + get: (requestedBy) => { return requestedBys[requestedBy] || null }, + set: (requestedBy, store) => { requestedBys[requestedBy] = store } + } + }, + deriveConstraint: (req, ctx, done) => { + if (req.headers['user-agent'] === 'wrong') { + done(new Error('wrong user-agent')) + return + } + + done(null, req.headers['user-agent']) + } +} + +test('should derive multiple async constraints', t => { + t.plan(2) + + const customHeaderConstraint2 = rfdc(customHeaderConstraint) + customHeaderConstraint2.name = 'requestedBy2' + + const router = FindMyWay({ constraints: { requestedBy: customHeaderConstraint, requestedBy2: customHeaderConstraint2 } }) + router.on('GET', '/', { constraints: { requestedBy: 'node', requestedBy2: 'node' } }, () => 'asyncHandler') + + router.lookup( + { + method: 'GET', + url: '/', + headers: { + 'user-agent': 'node' + } + }, + null, + (err, result) => { + t.assert.equal(err, null) + t.assert.equal(result, 'asyncHandler') + } + ) +}) + +test('lookup should return an error from deriveConstraint', t => { + t.plan(2) + + const router = FindMyWay({ constraints: { requestedBy: customHeaderConstraint } }) + router.on('GET', '/', { constraints: { requestedBy: 'node' } }, () => 'asyncHandler') + + router.lookup( + { + method: 'GET', + url: '/', + headers: { + 'user-agent': 'wrong' + } + }, + null, + (err, result) => { + t.assert.deepStrictEqual(err, new Error('wrong user-agent')) + t.assert.equal(result, undefined) + } + ) +}) + +test('should derive sync and async constraints', t => { + t.plan(4) + + const router = FindMyWay({ constraints: { requestedBy: customHeaderConstraint } }) + router.on('GET', '/', { constraints: { version: '1.0.0', requestedBy: 'node' } }, () => 'asyncHandlerV1') + router.on('GET', '/', { constraints: { version: '2.0.0', requestedBy: 'node' } }, () => 'asyncHandlerV2') + + router.lookup( + { + method: 'GET', + url: '/', + headers: { + 'user-agent': 'node', + 'accept-version': '1.0.0' + } + }, + null, + (err, result) => { + t.assert.equal(err, null) + t.assert.equal(result, 'asyncHandlerV1') + } + ) + + router.lookup( + { + method: 'GET', + url: '/', + headers: { + 'user-agent': 'node', + 'accept-version': '2.0.0' + } + }, + null, + (err, result) => { + t.assert.equal(err, null) + t.assert.equal(result, 'asyncHandlerV2') + } + ) +}) diff --git a/services/slides/node_modules/find-my-way/test/constraint.custom.test.js b/services/slides/node_modules/find-my-way/test/constraint.custom.test.js new file mode 100644 index 0000000000000000000000000000000000000000..91aa5548842a83da629471a2f627664f056a4141 --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/constraint.custom.test.js @@ -0,0 +1,273 @@ +'use strict' + +const { test } = require('node:test') +const FindMyWay = require('..') +const alpha = () => { } +const beta = () => { } +const gamma = () => { } +const delta = () => { } + +const customHeaderConstraint = { + name: 'requestedBy', + storage: function () { + let requestedBys = {} + return { + get: (requestedBy) => { return requestedBys[requestedBy] || null }, + set: (requestedBy, store) => { requestedBys[requestedBy] = store }, + del: (requestedBy) => { delete requestedBys[requestedBy] }, + empty: () => { requestedBys = {} } + } + }, + deriveConstraint: (req, ctx) => { + return req.headers['user-agent'] + } +} + +test('A route could support a custom constraint strategy', t => { + t.plan(3) + + const findMyWay = FindMyWay({ constraints: { requestedBy: customHeaderConstraint } }) + + findMyWay.on('GET', '/', { constraints: { requestedBy: 'curl' } }, alpha) + findMyWay.on('GET', '/', { constraints: { requestedBy: 'wget' } }, beta) + + t.assert.equal(findMyWay.find('GET', '/', { requestedBy: 'curl' }).handler, alpha) + t.assert.equal(findMyWay.find('GET', '/', { requestedBy: 'wget' }).handler, beta) + t.assert.ok(!findMyWay.find('GET', '/', { requestedBy: 'chrome' })) +}) + +test('A route could support a custom constraint strategy (add strategy outside constructor)', t => { + t.plan(3) + + const findMyWay = FindMyWay() + + findMyWay.addConstraintStrategy(customHeaderConstraint) + + findMyWay.on('GET', '/', { constraints: { requestedBy: 'curl' } }, alpha) + findMyWay.on('GET', '/', { constraints: { requestedBy: 'wget' } }, beta) + + t.assert.equal(findMyWay.find('GET', '/', { requestedBy: 'curl' }).handler, alpha) + t.assert.equal(findMyWay.find('GET', '/', { requestedBy: 'wget' }).handler, beta) + t.assert.ok(!findMyWay.find('GET', '/', { requestedBy: 'chrome' })) +}) + +test('A route could support a custom constraint strategy while versioned', t => { + t.plan(8) + + const findMyWay = FindMyWay({ constraints: { requestedBy: customHeaderConstraint } }) + + findMyWay.on('GET', '/', { constraints: { requestedBy: 'curl', version: '1.0.0' } }, alpha) + findMyWay.on('GET', '/', { constraints: { requestedBy: 'curl', version: '2.0.0' } }, beta) + findMyWay.on('GET', '/', { constraints: { requestedBy: 'wget', version: '2.0.0' } }, gamma) + findMyWay.on('GET', '/', { constraints: { requestedBy: 'wget', version: '3.0.0' } }, delta) + + t.assert.equal(findMyWay.find('GET', '/', { requestedBy: 'curl', version: '1.x' }).handler, alpha) + t.assert.equal(findMyWay.find('GET', '/', { requestedBy: 'curl', version: '2.x' }).handler, beta) + t.assert.equal(findMyWay.find('GET', '/', { requestedBy: 'wget', version: '2.x' }).handler, gamma) + t.assert.equal(findMyWay.find('GET', '/', { requestedBy: 'wget', version: '3.x' }).handler, delta) + + t.assert.ok(!findMyWay.find('GET', '/', { requestedBy: 'chrome' })) + t.assert.ok(!findMyWay.find('GET', '/', { requestedBy: 'chrome', version: '1.x' })) + + t.assert.ok(!findMyWay.find('GET', '/', { requestedBy: 'curl', version: '3.x' })) + t.assert.ok(!findMyWay.find('GET', '/', { requestedBy: 'wget', version: '1.x' })) +}) + +test('A route could support a custom constraint strategy while versioned (add strategy outside constructor)', t => { + t.plan(8) + + const findMyWay = FindMyWay() + + findMyWay.addConstraintStrategy(customHeaderConstraint) + + findMyWay.on('GET', '/', { constraints: { requestedBy: 'curl', version: '1.0.0' } }, alpha) + findMyWay.on('GET', '/', { constraints: { requestedBy: 'curl', version: '2.0.0' } }, beta) + findMyWay.on('GET', '/', { constraints: { requestedBy: 'wget', version: '2.0.0' } }, gamma) + findMyWay.on('GET', '/', { constraints: { requestedBy: 'wget', version: '3.0.0' } }, delta) + + t.assert.equal(findMyWay.find('GET', '/', { requestedBy: 'curl', version: '1.x' }).handler, alpha) + t.assert.equal(findMyWay.find('GET', '/', { requestedBy: 'curl', version: '2.x' }).handler, beta) + t.assert.equal(findMyWay.find('GET', '/', { requestedBy: 'wget', version: '2.x' }).handler, gamma) + t.assert.equal(findMyWay.find('GET', '/', { requestedBy: 'wget', version: '3.x' }).handler, delta) + + t.assert.ok(!findMyWay.find('GET', '/', { requestedBy: 'chrome' })) + t.assert.ok(!findMyWay.find('GET', '/', { requestedBy: 'chrome', version: '1.x' })) + + t.assert.ok(!findMyWay.find('GET', '/', { requestedBy: 'curl', version: '3.x' })) + t.assert.ok(!findMyWay.find('GET', '/', { requestedBy: 'wget', version: '1.x' })) +}) + +test('A route could support a custom constraint strategy while versioned and host constrained', t => { + t.plan(9) + + const findMyWay = FindMyWay({ constraints: { requestedBy: customHeaderConstraint } }) + + findMyWay.on('GET', '/', { constraints: { requestedBy: 'curl', version: '1.0.0', host: 'fastify.io' } }, alpha) + findMyWay.on('GET', '/', { constraints: { requestedBy: 'curl', version: '2.0.0', host: 'fastify.io' } }, beta) + findMyWay.on('GET', '/', { constraints: { requestedBy: 'curl', version: '2.0.0', host: 'example.io' } }, delta) + + t.assert.equal(findMyWay.find('GET', '/', { requestedBy: 'curl', version: '1.x', host: 'fastify.io' }).handler, alpha) + t.assert.equal(findMyWay.find('GET', '/', { requestedBy: 'curl', version: '2.x', host: 'fastify.io' }).handler, beta) + t.assert.equal(findMyWay.find('GET', '/', { requestedBy: 'curl', version: '2.x', host: 'example.io' }).handler, delta) + + t.assert.ok(!findMyWay.find('GET', '/', { requestedBy: 'chrome' })) + t.assert.ok(!findMyWay.find('GET', '/', { requestedBy: 'chrome', version: '1.x' })) + t.assert.ok(!findMyWay.find('GET', '/', { requestedBy: 'curl', version: '1.x' })) + t.assert.ok(!findMyWay.find('GET', '/', { requestedBy: 'curl', version: '2.x' })) + t.assert.ok(!findMyWay.find('GET', '/', { requestedBy: 'curl', version: '3.x', host: 'fastify.io' })) + t.assert.ok(!findMyWay.find('GET', '/', { requestedBy: 'curl', version: '1.x', host: 'example.io' })) +}) + +test('A route could support a custom constraint strategy while versioned and host constrained (add strategy outside constructor)', t => { + t.plan(9) + + const findMyWay = FindMyWay() + + findMyWay.addConstraintStrategy(customHeaderConstraint) + + findMyWay.on('GET', '/', { constraints: { requestedBy: 'curl', version: '1.0.0', host: 'fastify.io' } }, alpha) + findMyWay.on('GET', '/', { constraints: { requestedBy: 'curl', version: '2.0.0', host: 'fastify.io' } }, beta) + findMyWay.on('GET', '/', { constraints: { requestedBy: 'curl', version: '2.0.0', host: 'example.io' } }, delta) + + t.assert.equal(findMyWay.find('GET', '/', { requestedBy: 'curl', version: '1.x', host: 'fastify.io' }).handler, alpha) + t.assert.equal(findMyWay.find('GET', '/', { requestedBy: 'curl', version: '2.x', host: 'fastify.io' }).handler, beta) + t.assert.equal(findMyWay.find('GET', '/', { requestedBy: 'curl', version: '2.x', host: 'example.io' }).handler, delta) + + t.assert.ok(!findMyWay.find('GET', '/', { requestedBy: 'chrome' })) + t.assert.ok(!findMyWay.find('GET', '/', { requestedBy: 'chrome', version: '1.x' })) + t.assert.ok(!findMyWay.find('GET', '/', { requestedBy: 'curl', version: '1.x' })) + t.assert.ok(!findMyWay.find('GET', '/', { requestedBy: 'curl', version: '2.x' })) + t.assert.ok(!findMyWay.find('GET', '/', { requestedBy: 'curl', version: '3.x', host: 'fastify.io' })) + t.assert.ok(!findMyWay.find('GET', '/', { requestedBy: 'curl', version: '1.x', host: 'example.io' })) +}) + +test('Custom constraint strategies can set mustMatchWhenDerived flag to true which prevents matches to unconstrained routes when a constraint is derived and there are no other routes', t => { + t.plan(1) + + const findMyWay = FindMyWay({ + constraints: { + requestedBy: { + ...customHeaderConstraint, + mustMatchWhenDerived: true + } + }, + defaultRoute (req, res) { + t.assert.ok('pass') + } + }) + + findMyWay.on('GET', '/', {}, () => t.assert.assert.fail()) + + findMyWay.lookup({ method: 'GET', url: '/', headers: { 'user-agent': 'node' } }, null) +}) + +test('Custom constraint strategies can set mustMatchWhenDerived flag to true which prevents matches to unconstrained routes when a constraint is derived and there are no other routes (add strategy outside constructor)', t => { + t.plan(1) + + const findMyWay = FindMyWay({ + defaultRoute (req, res) { + t.assert.ok('pass') + } + }) + + findMyWay.addConstraintStrategy({ + ...customHeaderConstraint, + mustMatchWhenDerived: true + }) + + findMyWay.on('GET', '/', {}, () => t.assert.assert.fail()) + + findMyWay.lookup({ method: 'GET', url: '/', headers: { 'user-agent': 'node' } }, null) +}) + +test('Custom constraint strategies can set mustMatchWhenDerived flag to true which prevents matches to unconstrained routes when a constraint is derived when there are constrained routes', t => { + t.plan(1) + + const findMyWay = FindMyWay({ + constraints: { + requestedBy: { + ...customHeaderConstraint, + mustMatchWhenDerived: true + } + }, + defaultRoute (req, res) { + t.assert.ok('pass') + } + }) + + findMyWay.on('GET', '/', {}, () => t.assert.assert.fail()) + findMyWay.on('GET', '/', { constraints: { requestedBy: 'curl' } }, () => t.assert.assert.fail()) + findMyWay.on('GET', '/', { constraints: { requestedBy: 'wget' } }, () => t.assert.assert.fail()) + + findMyWay.lookup({ method: 'GET', url: '/', headers: { 'user-agent': 'node' } }, null) +}) + +test('Custom constraint strategies can set mustMatchWhenDerived flag to true which prevents matches to unconstrained routes when a constraint is derived when there are constrained routes (add strategy outside constructor)', t => { + t.plan(1) + + const findMyWay = FindMyWay({ + defaultRoute (req, res) { + t.assert.ok('pass') + } + }) + + findMyWay.addConstraintStrategy({ + ...customHeaderConstraint, + mustMatchWhenDerived: true + }) + + findMyWay.on('GET', '/', {}, () => t.assert.assert.fail()) + findMyWay.on('GET', '/', { constraints: { requestedBy: 'curl' } }, () => t.assert.assert.fail()) + findMyWay.on('GET', '/', { constraints: { requestedBy: 'wget' } }, () => t.assert.assert.fail()) + + findMyWay.lookup({ method: 'GET', url: '/', headers: { 'user-agent': 'node' } }, null) +}) + +test('Custom constraint strategies can set mustMatchWhenDerived flag to false which allows matches to unconstrained routes when a constraint is derived', t => { + t.plan(1) + + const findMyWay = FindMyWay({ + constraints: { + requestedBy: { + ...customHeaderConstraint, + mustMatchWhenDerived: false + } + }, + defaultRoute (req, res) { + t.assert.assert.fail() + } + }) + + findMyWay.on('GET', '/', {}, () => t.assert.ok('pass')) + + findMyWay.lookup({ method: 'GET', url: '/', headers: { 'user-agent': 'node' } }, null) +}) + +test('Custom constraint strategies can set mustMatchWhenDerived flag to false which allows matches to unconstrained routes when a constraint is derived (add strategy outside constructor)', t => { + t.plan(1) + + const findMyWay = FindMyWay({ + defaultRoute (req, res) { + t.assert.ok('pass') + } + }) + + findMyWay.addConstraintStrategy({ + ...customHeaderConstraint, + mustMatchWhenDerived: true + }) + + findMyWay.on('GET', '/', {}, () => t.assert.ok('pass')) + + findMyWay.lookup({ method: 'GET', url: '/', headers: { 'user-agent': 'node' } }, null) +}) + +test('Has constraint strategy method test', t => { + t.plan(2) + + const findMyWay = FindMyWay() + + t.assert.deepEqual(findMyWay.hasConstraintStrategy(customHeaderConstraint.name), false) + findMyWay.addConstraintStrategy(customHeaderConstraint) + t.assert.deepEqual(findMyWay.hasConstraintStrategy(customHeaderConstraint.name), true) +}) diff --git a/services/slides/node_modules/find-my-way/test/constraint.default-versioning.test.js b/services/slides/node_modules/find-my-way/test/constraint.default-versioning.test.js new file mode 100644 index 0000000000000000000000000000000000000000..08adf49096b6c1d60fc55128ca787724a9346913 --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/constraint.default-versioning.test.js @@ -0,0 +1,289 @@ +'use strict' + +const { test } = require('node:test') +const FindMyWay = require('..') +const noop = () => { } + +test('A route could support multiple versions (find) / 1', t => { + t.plan(7) + + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/', { constraints: { version: '1.2.3' } }, noop) + findMyWay.on('GET', '/', { constraints: { version: '3.2.0' } }, noop) + + t.assert.ok(findMyWay.find('GET', '/', { version: '1.x' })) + t.assert.ok(findMyWay.find('GET', '/', { version: '1.2.3' })) + t.assert.ok(findMyWay.find('GET', '/', { version: '3.x' })) + t.assert.ok(findMyWay.find('GET', '/', { version: '3.2.0' })) + t.assert.ok(!findMyWay.find('GET', '/', { version: '2.x' })) + t.assert.ok(!findMyWay.find('GET', '/', { version: '2.3.4' })) + t.assert.ok(!findMyWay.find('GET', '/', { version: '3.2.1' })) +}) + +test('A route could support multiple versions (find) / 2', t => { + t.plan(7) + + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/test', { constraints: { version: '1.2.3' } }, noop) + findMyWay.on('GET', '/test', { constraints: { version: '3.2.0' } }, noop) + + t.assert.ok(findMyWay.find('GET', '/test', { version: '1.x' })) + t.assert.ok(findMyWay.find('GET', '/test', { version: '1.2.3' })) + t.assert.ok(findMyWay.find('GET', '/test', { version: '3.x' })) + t.assert.ok(findMyWay.find('GET', '/test', { version: '3.2.0' })) + t.assert.ok(!findMyWay.find('GET', '/test', { version: '2.x' })) + t.assert.ok(!findMyWay.find('GET', '/test', { version: '2.3.4' })) + t.assert.ok(!findMyWay.find('GET', '/test', { version: '3.2.1' })) +}) + +test('A route could support multiple versions (find) / 3', t => { + t.plan(10) + + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/test/:id/hello', { constraints: { version: '1.2.3' } }, noop) + findMyWay.on('GET', '/test/:id/hello', { constraints: { version: '3.2.0' } }, noop) + findMyWay.on('GET', '/test/name/hello', { constraints: { version: '4.0.0' } }, noop) + + t.assert.ok(findMyWay.find('GET', '/test/1234/hello', { version: '1.x' })) + t.assert.ok(findMyWay.find('GET', '/test/1234/hello', { version: '1.2.3' })) + t.assert.ok(findMyWay.find('GET', '/test/1234/hello', { version: '3.x' })) + t.assert.ok(findMyWay.find('GET', '/test/1234/hello', { version: '3.2.0' })) + t.assert.ok(findMyWay.find('GET', '/test/name/hello', { version: '4.x' })) + t.assert.ok(findMyWay.find('GET', '/test/name/hello', { version: '3.x' })) + t.assert.ok(!findMyWay.find('GET', '/test/1234/hello', { version: '2.x' })) + t.assert.ok(!findMyWay.find('GET', '/test/1234/hello', { version: '2.3.4' })) + t.assert.ok(!findMyWay.find('GET', '/test/1234/hello', { version: '3.2.1' })) + t.assert.ok(!findMyWay.find('GET', '/test/1234/hello', { version: '4.x' })) +}) + +test('A route could support multiple versions (find) / 4', t => { + t.plan(8) + + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/test/*', { constraints: { version: '1.2.3' } }, noop) + findMyWay.on('GET', '/test/hello', { constraints: { version: '3.2.0' } }, noop) + + t.assert.ok(findMyWay.find('GET', '/test/1234/hello', { version: '1.x' })) + t.assert.ok(findMyWay.find('GET', '/test/1234/hello', { version: '1.2.3' })) + t.assert.ok(findMyWay.find('GET', '/test/hello', { version: '3.x' })) + t.assert.ok(findMyWay.find('GET', '/test/hello', { version: '3.2.0' })) + t.assert.ok(!findMyWay.find('GET', '/test/1234/hello', { version: '3.2.0' })) + t.assert.ok(!findMyWay.find('GET', '/test/1234/hello', { version: '3.x' })) + t.assert.ok(!findMyWay.find('GET', '/test/1234/hello', { version: '2.x' })) + t.assert.ok(!findMyWay.find('GET', '/test/hello', { version: '2.x' })) +}) + +test('A route could support multiple versions (find) / 5', t => { + t.plan(1) + + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/', { constraints: { version: '1.2.3' } }, () => false) + findMyWay.on('GET', '/', { constraints: { version: '3.2.0' } }, () => true) + + t.assert.ok(findMyWay.find('GET', '/', { version: '*' }).handler()) +}) + +test('Find with a version but without versioned routes', t => { + t.plan(1) + + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/', noop) + + t.assert.ok(!findMyWay.find('GET', '/', { version: '1.x' })) +}) + +test('A route could support multiple versions (lookup)', t => { + t.plan(7) + + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + const versions = ['2.x', '2.3.4', '3.2.1'] + t.assert.ok(versions.indexOf(req.headers['accept-version']) > -1) + } + }) + + findMyWay.on('GET', '/', { constraints: { version: '1.2.3' } }, (req, res) => { + const versions = ['1.x', '1.2.3'] + t.assert.ok(versions.indexOf(req.headers['accept-version']) > -1) + }) + + findMyWay.on('GET', '/', { constraints: { version: '3.2.0' } }, (req, res) => { + const versions = ['3.x', '3.2.0'] + t.assert.ok(versions.indexOf(req.headers['accept-version']) > -1) + }) + + findMyWay.lookup({ + method: 'GET', + url: '/', + headers: { 'accept-version': '1.x' } + }, null) + + findMyWay.lookup({ + method: 'GET', + url: '/', + headers: { 'accept-version': '1.2.3' } + }, null) + + findMyWay.lookup({ + method: 'GET', + url: '/', + headers: { 'accept-version': '3.x' } + }, null) + + findMyWay.lookup({ + method: 'GET', + url: '/', + headers: { 'accept-version': '3.2.0' } + }, null) + + findMyWay.lookup({ + method: 'GET', + url: '/', + headers: { 'accept-version': '2.x' } + }, null) + + findMyWay.lookup({ + method: 'GET', + url: '/', + headers: { 'accept-version': '2.3.4' } + }, null) + + findMyWay.lookup({ + method: 'GET', + url: '/', + headers: { 'accept-version': '3.2.1' } + }, null) +}) + +test('It should always choose the highest version of a route', t => { + t.plan(3) + + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/', { constraints: { version: '2.3.0' } }, (req, res) => { + t.assert.fail('We should not be here') + }) + + findMyWay.on('GET', '/', { constraints: { version: '2.4.0' } }, (req, res) => { + t.assert.ok('Yeah!') + }) + + findMyWay.on('GET', '/', { constraints: { version: '3.3.0' } }, (req, res) => { + t.assert.ok('Yeah!') + }) + + findMyWay.on('GET', '/', { constraints: { version: '3.2.0' } }, (req, res) => { + t.assert.fail('We should not be here') + }) + + findMyWay.on('GET', '/', { constraints: { version: '3.2.2' } }, (req, res) => { + t.assert.fail('We should not be here') + }) + + findMyWay.on('GET', '/', { constraints: { version: '4.4.0' } }, (req, res) => { + t.assert.fail('We should not be here') + }) + + findMyWay.on('GET', '/', { constraints: { version: '4.3.2' } }, (req, res) => { + t.assert.ok('Yeah!') + }) + + findMyWay.lookup({ + method: 'GET', + url: '/', + headers: { 'accept-version': '2.x' } + }, null) + + findMyWay.lookup({ + method: 'GET', + url: '/', + headers: { 'accept-version': '3.x' } + }, null) + + findMyWay.lookup({ + method: 'GET', + url: '/', + headers: { 'accept-version': '4.3.x' } + }, null) +}) + +test('Declare the same route with and without version', t => { + t.plan(2) + + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/', noop) + findMyWay.on('GET', '/', { constraints: { version: '1.2.0' } }, noop) + + t.assert.ok(findMyWay.find('GET', '/', { version: '1.x' })) + t.assert.ok(findMyWay.find('GET', '/', {})) +}) + +test('It should throw if you declare multiple times the same route', t => { + t.plan(1) + + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/', { constraints: { version: '1.2.3' } }, noop) + + try { + findMyWay.on('GET', '/', { constraints: { version: '1.2.3' } }, noop) + t.assert.fail('It should throw') + } catch (err) { + t.assert.equal(err.message, 'Method \'GET\' already declared for route \'/\' with constraints \'{"version":"1.2.3"}\'') + } +}) + +test('Versioning won\'t work if there are no versioned routes', t => { + t.plan(2) + + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.fail('We should not be here') + } + }) + + findMyWay.on('GET', '/', (req, res) => { + t.assert.ok('Yeah!') + }) + + findMyWay.lookup({ + method: 'GET', + url: '/', + headers: { 'accept-version': '2.x' } + }, null) + + findMyWay.lookup({ + method: 'GET', + url: '/' + }, null) +}) + +test('Unversioned routes aren\'t triggered when unknown versions are requested', t => { + t.plan(1) + + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.ok('We should be here') + } + }) + + findMyWay.on('GET', '/', (req, res) => { + t.assert.fail('unversioned route shouldnt be hit!') + }) + findMyWay.on('GET', '/', { constraints: { version: '1.0.0' } }, (req, res) => { + t.assert.fail('versioned route shouldnt be hit for wrong version!') + }) + + findMyWay.lookup({ + method: 'GET', + url: '/', + headers: { 'accept-version': '2.x' } + }, null) +}) diff --git a/services/slides/node_modules/find-my-way/test/constraint.host.test.js b/services/slides/node_modules/find-my-way/test/constraint.host.test.js new file mode 100644 index 0000000000000000000000000000000000000000..a53796f1bb50645ef193971786fe1cd45724cc15 --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/constraint.host.test.js @@ -0,0 +1,104 @@ +'use strict' + +const { test } = require('node:test') +const FindMyWay = require('..') +const alpha = () => { } +const beta = () => { } +const gamma = () => { } + +test('A route supports multiple host constraints', t => { + t.plan(4) + + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/', {}, alpha) + findMyWay.on('GET', '/', { constraints: { host: 'fastify.io' } }, beta) + findMyWay.on('GET', '/', { constraints: { host: 'example.com' } }, gamma) + + t.assert.equal(findMyWay.find('GET', '/', {}).handler, alpha) + t.assert.equal(findMyWay.find('GET', '/', { host: 'something-else.io' }).handler, alpha) + t.assert.equal(findMyWay.find('GET', '/', { host: 'fastify.io' }).handler, beta) + t.assert.equal(findMyWay.find('GET', '/', { host: 'example.com' }).handler, gamma) +}) + +test('A route supports wildcard host constraints', t => { + t.plan(4) + + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/', { constraints: { host: 'fastify.io' } }, beta) + findMyWay.on('GET', '/', { constraints: { host: /.*\.fastify\.io/ } }, gamma) + + t.assert.equal(findMyWay.find('GET', '/', { host: 'fastify.io' }).handler, beta) + t.assert.equal(findMyWay.find('GET', '/', { host: 'foo.fastify.io' }).handler, gamma) + t.assert.equal(findMyWay.find('GET', '/', { host: 'bar.fastify.io' }).handler, gamma) + t.assert.ok(!findMyWay.find('GET', '/', { host: 'example.com' })) +}) + +test('A route supports multiple host constraints (lookup)', t => { + t.plan(4) + + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/', {}, (req, res) => {}) + findMyWay.on('GET', '/', { constraints: { host: 'fastify.io' } }, (req, res) => { + t.assert.equal(req.headers.host, 'fastify.io') + }) + findMyWay.on('GET', '/', { constraints: { host: 'example.com' } }, (req, res) => { + t.assert.equal(req.headers.host, 'example.com') + }) + findMyWay.on('GET', '/', { constraints: { host: /.+\.fancy\.ca/ } }, (req, res) => { + t.assert.ok(req.headers.host.endsWith('.fancy.ca')) + }) + + findMyWay.lookup({ + method: 'GET', + url: '/', + headers: { host: 'fastify.io' } + }) + + findMyWay.lookup({ + method: 'GET', + url: '/', + headers: { host: 'example.com' } + }) + findMyWay.lookup({ + method: 'GET', + url: '/', + headers: { host: 'foo.fancy.ca' } + }) + findMyWay.lookup({ + method: 'GET', + url: '/', + headers: { host: 'bar.fancy.ca' } + }) +}) + +test('A route supports up to 31 host constraints', (t) => { + t.plan(1) + + const findMyWay = FindMyWay() + + for (let i = 0; i < 31; i++) { + const host = `h${i.toString().padStart(2, '0')}` + findMyWay.on('GET', '/', { constraints: { host } }, alpha) + } + + t.assert.equal(findMyWay.find('GET', '/', { host: 'h01' }).handler, alpha) +}) + +test('A route throws when constraint limit exceeded', (t) => { + t.plan(1) + + const findMyWay = FindMyWay() + + for (let i = 0; i < 31; i++) { + const host = `h${i.toString().padStart(2, '0')}` + findMyWay.on('GET', '/', { constraints: { host } }, alpha) + } + + t.assert.throws( + () => findMyWay.on('GET', '/', { constraints: { host: 'h31' } }, beta), + new Error('find-my-way supports a maximum of 31 route handlers per node when there are constraints, limit reached') + ) +}) diff --git a/services/slides/node_modules/find-my-way/test/constraints.test.js b/services/slides/node_modules/find-my-way/test/constraints.test.js new file mode 100644 index 0000000000000000000000000000000000000000..67bf8d8102373c2b61327d4bc61d49af5488494a --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/constraints.test.js @@ -0,0 +1,108 @@ +'use strict' + +const { test } = require('node:test') +const FindMyWay = require('..') +const alpha = () => { } +const beta = () => { } +const gamma = () => { } + +test('A route could support multiple host constraints while versioned', t => { + t.plan(6) + + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/', { constraints: { host: 'fastify.io', version: '1.1.0' } }, beta) + findMyWay.on('GET', '/', { constraints: { host: 'fastify.io', version: '2.1.0' } }, gamma) + + t.assert.equal(findMyWay.find('GET', '/', { host: 'fastify.io', version: '1.x' }).handler, beta) + t.assert.equal(findMyWay.find('GET', '/', { host: 'fastify.io', version: '1.1.x' }).handler, beta) + t.assert.equal(findMyWay.find('GET', '/', { host: 'fastify.io', version: '2.x' }).handler, gamma) + t.assert.equal(findMyWay.find('GET', '/', { host: 'fastify.io', version: '2.1.x' }).handler, gamma) + t.assert.ok(!findMyWay.find('GET', '/', { host: 'fastify.io', version: '3.x' })) + t.assert.ok(!findMyWay.find('GET', '/', { host: 'something-else.io', version: '1.x' })) +}) + +test('Constrained routes are matched before unconstrainted routes when the constrained route is added last', t => { + t.plan(3) + + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/', {}, alpha) + findMyWay.on('GET', '/', { constraints: { host: 'fastify.io' } }, beta) + + t.assert.equal(findMyWay.find('GET', '/', {}).handler, alpha) + t.assert.equal(findMyWay.find('GET', '/', { host: 'fastify.io' }).handler, beta) + t.assert.equal(findMyWay.find('GET', '/', { host: 'example.com' }).handler, alpha) +}) + +test('Constrained routes are matched before unconstrainted routes when the constrained route is added first', t => { + t.plan(3) + + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/', { constraints: { host: 'fastify.io' } }, beta) + findMyWay.on('GET', '/', {}, alpha) + + t.assert.equal(findMyWay.find('GET', '/', {}).handler, alpha) + t.assert.equal(findMyWay.find('GET', '/', { host: 'fastify.io' }).handler, beta) + t.assert.equal(findMyWay.find('GET', '/', { host: 'example.com' }).handler, alpha) +}) + +test('Routes with multiple constraints are matched before routes with one constraint when the doubly-constrained route is added last', t => { + t.plan(3) + + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/', { constraints: { host: 'fastify.io' } }, alpha) + findMyWay.on('GET', '/', { constraints: { host: 'fastify.io', version: '1.0.0' } }, beta) + + t.assert.equal(findMyWay.find('GET', '/', { host: 'fastify.io' }).handler, alpha) + t.assert.equal(findMyWay.find('GET', '/', { host: 'fastify.io', version: '1.0.0' }).handler, beta) + t.assert.equal(findMyWay.find('GET', '/', { host: 'fastify.io', version: '2.0.0' }), null) +}) + +test('Routes with multiple constraints are matched before routes with one constraint when the doubly-constrained route is added first', t => { + t.plan(3) + + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/', { constraints: { host: 'fastify.io', version: '1.0.0' } }, beta) + findMyWay.on('GET', '/', { constraints: { host: 'fastify.io' } }, alpha) + + t.assert.equal(findMyWay.find('GET', '/', { host: 'fastify.io' }).handler, alpha) + t.assert.equal(findMyWay.find('GET', '/', { host: 'fastify.io', version: '1.0.0' }).handler, beta) + t.assert.equal(findMyWay.find('GET', '/', { host: 'fastify.io', version: '2.0.0' }), null) +}) + +test('Routes with multiple constraints are matched before routes with one constraint before unconstrained routes', t => { + t.plan(3) + + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/', { constraints: { host: 'fastify.io', version: '1.0.0' } }, beta) + findMyWay.on('GET', '/', { constraints: { host: 'fastify.io' } }, alpha) + findMyWay.on('GET', '/', { constraints: {} }, gamma) + + t.assert.equal(findMyWay.find('GET', '/', { host: 'fastify.io', version: '1.0.0' }).handler, beta) + t.assert.equal(findMyWay.find('GET', '/', { host: 'fastify.io', version: '2.0.0' }), null) + t.assert.equal(findMyWay.find('GET', '/', { host: 'example.io' }).handler, gamma) +}) + +test('Has constraint strategy method test', t => { + t.plan(6) + + const findMyWay = FindMyWay() + + t.assert.deepEqual(findMyWay.hasConstraintStrategy('version'), false) + t.assert.deepEqual(findMyWay.hasConstraintStrategy('host'), false) + + findMyWay.on('GET', '/', { constraints: { host: 'fastify.io' } }, () => {}) + + t.assert.deepEqual(findMyWay.hasConstraintStrategy('version'), false) + t.assert.deepEqual(findMyWay.hasConstraintStrategy('host'), true) + + findMyWay.on('GET', '/', { constraints: { host: 'fastify.io', version: '1.0.0' } }, () => {}) + + t.assert.deepEqual(findMyWay.hasConstraintStrategy('version'), true) + t.assert.deepEqual(findMyWay.hasConstraintStrategy('host'), true) +}) diff --git a/services/slides/node_modules/find-my-way/test/custom-querystring-parser.test.js b/services/slides/node_modules/find-my-way/test/custom-querystring-parser.test.js new file mode 100644 index 0000000000000000000000000000000000000000..e2bd620a046f945f7586cc639c7ccf3ab0ea511c --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/custom-querystring-parser.test.js @@ -0,0 +1,46 @@ +'use strict' + +const { test } = require('node:test') +const querystring = require('fast-querystring') +const FindMyWay = require('../') + +test('Custom querystring parser', t => { + t.plan(2) + + const findMyWay = FindMyWay({ + querystringParser: function (str) { + t.assert.equal(str, 'foo=bar&baz=faz') + return querystring.parse(str) + } + }) + findMyWay.on('GET', '/', () => {}) + + t.assert.deepEqual(findMyWay.find('GET', '/?foo=bar&baz=faz').searchParams, { foo: 'bar', baz: 'faz' }) +}) + +test('Custom querystring parser should be called also if there is nothing to parse', t => { + t.plan(2) + + const findMyWay = FindMyWay({ + querystringParser: function (str) { + t.assert.equal(str, '') + return querystring.parse(str) + } + }) + findMyWay.on('GET', '/', () => {}) + + t.assert.deepEqual(findMyWay.find('GET', '/').searchParams, {}) +}) + +test('Querystring without value', t => { + t.plan(2) + + const findMyWay = FindMyWay({ + querystringParser: function (str) { + t.assert.equal(str, 'foo') + return querystring.parse(str) + } + }) + findMyWay.on('GET', '/', () => {}) + t.assert.deepEqual(findMyWay.find('GET', '/?foo').searchParams, { foo: '' }) +}) diff --git a/services/slides/node_modules/find-my-way/test/errors.test.js b/services/slides/node_modules/find-my-way/test/errors.test.js new file mode 100644 index 0000000000000000000000000000000000000000..9a003fe37a55d69973fa1da71f52c8d046ec01bb --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/errors.test.js @@ -0,0 +1,484 @@ +'use strict' + +const { test, describe } = require('node:test') +const FindMyWay = require('../') + +test('Method should be a string', t => { + t.plan(1) + const findMyWay = FindMyWay() + + try { + findMyWay.on(0, '/test', () => {}) + t.assert.fail('method shoukd be a string') + } catch (e) { + t.assert.equal(e.message, 'Method should be a string') + } +}) + +test('Method should be a string [ignoreTrailingSlash=true]', t => { + t.plan(1) + const findMyWay = FindMyWay({ ignoreTrailingSlash: true }) + + try { + findMyWay.on(0, '/test', () => {}) + t.assert.fail('method shoukd be a string') + } catch (e) { + t.assert.equal(e.message, 'Method should be a string') + } +}) + +test('Method should be a string [ignoreDuplicateSlashes=true]', t => { + t.plan(1) + const findMyWay = FindMyWay({ ignoreDuplicateSlashes: true }) + + try { + findMyWay.on(0, '/test', () => {}) + t.assert.fail('method shoukd be a string') + } catch (e) { + t.assert.equal(e.message, 'Method should be a string') + } +}) + +test('Method should be a string (array)', t => { + t.plan(1) + const findMyWay = FindMyWay() + + try { + findMyWay.on(['GET', 0], '/test', () => {}) + t.assert.fail('method shoukd be a string') + } catch (e) { + t.assert.equal(e.message, 'Method should be a string') + } +}) + +test('Method should be a string (array) [ignoreTrailingSlash=true]', t => { + t.plan(1) + const findMyWay = FindMyWay({ ignoreTrailingSlash: true }) + + try { + findMyWay.on(['GET', 0], '/test', () => {}) + t.assert.fail('method shoukd be a string') + } catch (e) { + t.assert.equal(e.message, 'Method should be a string') + } +}) + +test('Method should be a string (array) [ignoreDuplicateSlashes=true]', t => { + t.plan(1) + const findMyWay = FindMyWay({ ignoreDuplicateSlashes: true }) + + try { + findMyWay.on(['GET', 0], '/test', () => {}) + t.assert.fail('method shoukd be a string') + } catch (e) { + t.assert.equal(e.message, 'Method should be a string') + } +}) + +test('Path should be a string', t => { + t.plan(1) + const findMyWay = FindMyWay() + + try { + findMyWay.on('GET', 0, () => {}) + t.assert.fail('path should be a string') + } catch (e) { + t.assert.equal(e.message, 'Path should be a string') + } +}) + +test('Path should be a string [ignoreTrailingSlash=true]', t => { + t.plan(1) + const findMyWay = FindMyWay({ ignoreTrailingSlash: true }) + + try { + findMyWay.on('GET', 0, () => {}) + t.assert.fail('path should be a string') + } catch (e) { + t.assert.equal(e.message, 'Path should be a string') + } +}) + +test('Path should be a string [ignoreDuplicateSlashes=true]', t => { + t.plan(1) + const findMyWay = FindMyWay({ ignoreDuplicateSlashes: true }) + + try { + findMyWay.on('GET', 0, () => {}) + t.assert.fail('path should be a string') + } catch (e) { + t.assert.equal(e.message, 'Path should be a string') + } +}) + +test('The path could not be empty', t => { + t.plan(1) + const findMyWay = FindMyWay() + + try { + findMyWay.on('GET', '', () => {}) + t.assert.fail('The path could not be empty') + } catch (e) { + t.assert.equal(e.message, 'The path could not be empty') + } +}) + +test('The path could not be empty [ignoreTrailingSlash=true]', t => { + t.plan(1) + const findMyWay = FindMyWay({ ignoreTrailingSlash: true }) + + try { + findMyWay.on('GET', '', () => {}) + t.assert.fail('The path could not be empty') + } catch (e) { + t.assert.equal(e.message, 'The path could not be empty') + } +}) + +test('The path could not be empty [ignoreDuplicateSlashes=true]', t => { + t.plan(1) + const findMyWay = FindMyWay({ ignoreDuplicateSlashes: true }) + + try { + findMyWay.on('GET', '', () => {}) + t.assert.fail('The path could not be empty') + } catch (e) { + t.assert.equal(e.message, 'The path could not be empty') + } +}) + +test('The first character of a path should be `/` or `*`', t => { + t.plan(1) + const findMyWay = FindMyWay() + + try { + findMyWay.on('GET', 'a', () => {}) + t.assert.fail('The first character of a path should be `/` or `*`') + } catch (e) { + t.assert.equal(e.message, 'The first character of a path should be `/` or `*`') + } +}) + +test('The first character of a path should be `/` or `*` [ignoreTrailingSlash=true]', t => { + t.plan(1) + const findMyWay = FindMyWay({ ignoreTrailingSlash: true }) + + try { + findMyWay.on('GET', 'a', () => {}) + t.assert.fail('The first character of a path should be `/` or `*`') + } catch (e) { + t.assert.equal(e.message, 'The first character of a path should be `/` or `*`') + } +}) + +test('The first character of a path should be `/` or `*` [ignoreDuplicateSlashes=true]', t => { + t.plan(1) + const findMyWay = FindMyWay({ ignoreDuplicateSlashes: true }) + + try { + findMyWay.on('GET', 'a', () => {}) + t.assert.fail('The first character of a path should be `/` or `*`') + } catch (e) { + t.assert.equal(e.message, 'The first character of a path should be `/` or `*`') + } +}) + +test('Handler should be a function', t => { + t.plan(1) + const findMyWay = FindMyWay() + + try { + findMyWay.on('GET', '/test', 0) + t.assert.fail('handler should be a function') + } catch (e) { + t.assert.equal(e.message, 'Handler should be a function') + } +}) + +test('Method is not an http method.', t => { + t.plan(1) + const findMyWay = FindMyWay() + + try { + findMyWay.on('GETT', '/test', () => {}) + t.assert.fail('method is not a valid http method') + } catch (e) { + t.assert.equal(e.message, 'Method \'GETT\' is not an http method.') + } +}) + +test('Method is not an http method. (array)', t => { + t.plan(1) + const findMyWay = FindMyWay() + + try { + findMyWay.on(['POST', 'GETT'], '/test', () => {}) + t.assert.fail('method is not a valid http method') + } catch (e) { + t.assert.equal(e.message, 'Method \'GETT\' is not an http method.') + } +}) + +test('The default route must be a function', t => { + t.plan(1) + try { + FindMyWay({ + defaultRoute: '/404' + }) + t.assert.fail('default route must be a function') + } catch (e) { + t.assert.equal(e.message, 'The default route must be a function') + } +}) + +test('Method already declared', t => { + t.plan(1) + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/test', () => {}) + try { + findMyWay.on('GET', '/test', () => {}) + t.assert.fail('method already declared') + } catch (e) { + t.assert.equal(e.message, 'Method \'GET\' already declared for route \'/test\' with constraints \'{}\'') + } +}) + +test('Method already declared if * is used', t => { + t.plan(1) + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/*', () => {}) + try { + findMyWay.on('GET', '*', () => {}) + t.assert.fail('should throw error') + } catch (e) { + t.assert.equal(e.message, 'Method \'GET\' already declared for route \'/*\' with constraints \'{}\'') + } +}) + +test('Method already declared if /* is used', t => { + t.plan(1) + const findMyWay = FindMyWay() + + findMyWay.on('GET', '*', () => {}) + try { + findMyWay.on('GET', '/*', () => {}) + t.assert.fail('should throw error') + } catch (e) { + t.assert.equal(e.message, 'Method \'GET\' already declared for route \'/*\' with constraints \'{}\'') + } +}) + +describe('Method already declared [ignoreTrailingSlash=true]', t => { + test('without trailing slash', t => { + t.plan(2) + const findMyWay = FindMyWay({ ignoreTrailingSlash: true }) + + findMyWay.on('GET', '/test', () => {}) + + try { + findMyWay.on('GET', '/test', () => {}) + t.assert.fail('method already declared') + } catch (e) { + t.assert.equal(e.message, 'Method \'GET\' already declared for route \'/test\' with constraints \'{}\'') + } + + try { + findMyWay.on('GET', '/test/', () => {}) + t.assert.fail('method already declared') + } catch (e) { + t.assert.equal(e.message, 'Method \'GET\' already declared for route \'/test\' with constraints \'{}\'') + } + }) + + test('with trailing slash', t => { + t.plan(2) + const findMyWay = FindMyWay({ ignoreTrailingSlash: true }) + + findMyWay.on('GET', '/test/', () => {}) + + try { + findMyWay.on('GET', '/test', () => {}) + t.assert.fail('method already declared') + } catch (e) { + t.assert.equal(e.message, 'Method \'GET\' already declared for route \'/test\' with constraints \'{}\'') + } + + try { + findMyWay.on('GET', '/test/', () => {}) + t.assert.fail('method already declared') + } catch (e) { + t.assert.equal(e.message, 'Method \'GET\' already declared for route \'/test\' with constraints \'{}\'') + } + }) +}) + +describe('Method already declared [ignoreDuplicateSlashes=true]', t => { + test('without duplicate slashes', t => { + t.plan(2) + const findMyWay = FindMyWay({ ignoreDuplicateSlashes: true }) + + findMyWay.on('GET', '/test', () => {}) + + try { + findMyWay.on('GET', '/test', () => {}) + t.assert.fail('method already declared') + } catch (e) { + t.assert.equal(e.message, 'Method \'GET\' already declared for route \'/test\' with constraints \'{}\'') + } + + try { + findMyWay.on('GET', '//test', () => {}) + t.assert.fail('method already declared') + } catch (e) { + t.assert.equal(e.message, 'Method \'GET\' already declared for route \'/test\' with constraints \'{}\'') + } + }) + + test('with duplicate slashes', t => { + t.plan(2) + const findMyWay = FindMyWay({ ignoreDuplicateSlashes: true }) + + findMyWay.on('GET', '//test', () => {}) + + try { + findMyWay.on('GET', '/test', () => {}) + t.assert.fail('method already declared') + } catch (e) { + t.assert.equal(e.message, 'Method \'GET\' already declared for route \'/test\' with constraints \'{}\'') + } + + try { + findMyWay.on('GET', '//test', () => {}) + t.assert.fail('method already declared') + } catch (e) { + t.assert.equal(e.message, 'Method \'GET\' already declared for route \'/test\' with constraints \'{}\'') + } + }) +}) + +test('Method already declared nested route', t => { + t.plan(1) + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/test', () => {}) + findMyWay.on('GET', '/test/hello', () => {}) + findMyWay.on('GET', '/test/world', () => {}) + + try { + findMyWay.on('GET', '/test/hello', () => {}) + t.assert.fail('method already delcared in nested route') + } catch (e) { + t.assert.equal(e.message, 'Method \'GET\' already declared for route \'/test/hello\' with constraints \'{}\'') + } +}) + +describe('Method already declared nested route [ignoreTrailingSlash=true]', t => { + test('without trailing slash', t => { + t.plan(2) + const findMyWay = FindMyWay({ ignoreTrailingSlash: true }) + + findMyWay.on('GET', '/test', () => {}) + findMyWay.on('GET', '/test/hello', () => {}) + findMyWay.on('GET', '/test/world', () => {}) + + try { + findMyWay.on('GET', '/test/hello', () => {}) + t.assert.fail('method already declared') + } catch (e) { + t.assert.equal(e.message, 'Method \'GET\' already declared for route \'/test/hello\' with constraints \'{}\'') + } + + try { + findMyWay.on('GET', '/test/hello/', () => {}) + t.assert.fail('method already declared') + } catch (e) { + t.assert.equal(e.message, 'Method \'GET\' already declared for route \'/test/hello\' with constraints \'{}\'') + } + }) + + test('Method already declared with constraints', t => { + t.plan(1) + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/test', { constraints: { host: 'fastify.io' } }, () => {}) + try { + findMyWay.on('GET', '/test', { constraints: { host: 'fastify.io' } }, () => {}) + t.assert.fail('method already declared') + } catch (e) { + t.assert.equal(e.message, 'Method \'GET\' already declared for route \'/test\' with constraints \'{"host":"fastify.io"}\'') + } + }) + + test('with trailing slash', t => { + t.plan(2) + const findMyWay = FindMyWay({ ignoreTrailingSlash: true }) + + findMyWay.on('GET', '/test/', () => {}) + findMyWay.on('GET', '/test/hello/', () => {}) + findMyWay.on('GET', '/test/world/', () => {}) + + try { + findMyWay.on('GET', '/test/hello', () => {}) + t.assert.fail('method already declared') + } catch (e) { + t.assert.equal(e.message, 'Method \'GET\' already declared for route \'/test/hello\' with constraints \'{}\'') + } + + try { + findMyWay.on('GET', '/test/hello/', () => {}) + t.assert.fail('method already declared') + } catch (e) { + t.assert.equal(e.message, 'Method \'GET\' already declared for route \'/test/hello\' with constraints \'{}\'') + } + }) +}) + +describe('Method already declared nested route [ignoreDuplicateSlashes=true]', t => { + test('without duplicate slashes', t => { + t.plan(2) + const findMyWay = FindMyWay({ ignoreDuplicateSlashes: true }) + + findMyWay.on('GET', '/test', () => {}) + findMyWay.on('GET', '/test/hello', () => {}) + findMyWay.on('GET', '/test/world', () => {}) + + try { + findMyWay.on('GET', '/test/hello', () => {}) + t.assert.fail('method already declared') + } catch (e) { + t.assert.equal(e.message, 'Method \'GET\' already declared for route \'/test/hello\' with constraints \'{}\'') + } + + try { + findMyWay.on('GET', '/test//hello', () => {}) + t.assert.fail('method already declared') + } catch (e) { + t.assert.equal(e.message, 'Method \'GET\' already declared for route \'/test/hello\' with constraints \'{}\'') + } + }) + + test('with duplicate slashes', t => { + t.plan(2) + const findMyWay = FindMyWay({ ignoreDuplicateSlashes: true }) + + findMyWay.on('GET', '/test/', () => {}) + findMyWay.on('GET', '/test//hello', () => {}) + findMyWay.on('GET', '/test//world', () => {}) + + try { + findMyWay.on('GET', '/test/hello', () => {}) + t.assert.fail('method already declared') + } catch (e) { + t.assert.equal(e.message, 'Method \'GET\' already declared for route \'/test/hello\' with constraints \'{}\'') + } + + try { + findMyWay.on('GET', '/test//hello', () => {}) + t.assert.fail('method already declared') + } catch (e) { + t.assert.equal(e.message, 'Method \'GET\' already declared for route \'/test/hello\' with constraints \'{}\'') + } + }) +}) diff --git a/services/slides/node_modules/find-my-way/test/fastify-issue-3129.test.js b/services/slides/node_modules/find-my-way/test/fastify-issue-3129.test.js new file mode 100644 index 0000000000000000000000000000000000000000..b221b73e85d32c42f866d477481162a6237bf5fe --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/fastify-issue-3129.test.js @@ -0,0 +1,34 @@ +'use strict' + +const { test } = require('node:test') +const FindMyWay = require('../') + +test('contain param and wildcard together', t => { + t.plan(4) + + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.fail('we should not be here, the url is: ' + req.url) + } + }) + + findMyWay.on('GET', '/:lang/item/:id', (req, res, params) => { + t.assert.deepEqual(params.lang, 'fr') + t.assert.deepEqual(params.id, '12345') + }) + + findMyWay.on('GET', '/:lang/item/*', (req, res, params) => { + t.assert.deepEqual(params.lang, 'fr') + t.assert.deepEqual(params['*'], '12345/edit') + }) + + findMyWay.lookup( + { method: 'GET', url: '/fr/item/12345', headers: {} }, + null + ) + + findMyWay.lookup( + { method: 'GET', url: '/fr/item/12345/edit', headers: {} }, + null + ) +}) diff --git a/services/slides/node_modules/find-my-way/test/fastify-issue-3957.test.js b/services/slides/node_modules/find-my-way/test/fastify-issue-3957.test.js new file mode 100644 index 0000000000000000000000000000000000000000..5bc7892d899d14a27809c335926f236961ccee76 --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/fastify-issue-3957.test.js @@ -0,0 +1,23 @@ +'use strict' + +const { test } = require('node:test') +const FindMyWay = require('../') + +test('wildcard should not limit by maxParamLength', t => { + t.plan(1) + + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.fail('we should not be here, the url is: ' + req.url) + } + }) + + findMyWay.on('GET', '*', (req, res, params) => { + t.assert.deepEqual(params['*'], '/portfolios/b5859fb9-6c76-4db8-b3d1-337c5be3fd8b/instruments/2a694406-b43f-439d-aa11-0c814805c930/positions') + }) + + findMyWay.lookup( + { method: 'GET', url: '/portfolios/b5859fb9-6c76-4db8-b3d1-337c5be3fd8b/instruments/2a694406-b43f-439d-aa11-0c814805c930/positions', headers: {} }, + null + ) +}) diff --git a/services/slides/node_modules/find-my-way/test/find-route.test.js b/services/slides/node_modules/find-my-way/test/find-route.test.js new file mode 100644 index 0000000000000000000000000000000000000000..bb09918387d3d925878c72f630bd750fa439ab90 --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/find-route.test.js @@ -0,0 +1,275 @@ +'use strict' + +const { test } = require('node:test') +const rfdc = require('rfdc')({ proto: true }) +const FindMyWay = require('..') + +function equalRouters (t, router1, router2) { + t.assert.deepStrictEqual(router1._opts, router2._opts) + t.assert.deepEqual(router1.routes, router2.routes) + t.assert.deepEqual(JSON.stringify(router1.trees), JSON.stringify(router2.trees)) + + t.assert.deepStrictEqual(router1.constrainer.strategies, router2.constrainer.strategies) + t.assert.deepStrictEqual( + router1.constrainer.strategiesInUse, + router2.constrainer.strategiesInUse + ) + t.assert.deepStrictEqual( + router1.constrainer.asyncStrategiesInUse, + router2.constrainer.asyncStrategiesInUse + ) +} + +test('findRoute returns null if there is no routes', (t) => { + t.plan(7) + + const findMyWay = FindMyWay() + const fundMyWayClone = rfdc(findMyWay) + + const route = findMyWay.findRoute('GET', '/example') + t.assert.equal(route, null) + + equalRouters(t, findMyWay, fundMyWayClone) +}) + +test('findRoute returns handler and store for a static route', (t) => { + t.plan(9) + + const findMyWay = FindMyWay() + + const handler = () => {} + const store = { hello: 'world' } + findMyWay.on('GET', '/example', handler, store) + + const fundMyWayClone = rfdc(findMyWay) + + const route = findMyWay.findRoute('GET', '/example') + t.assert.equal(route.handler, handler) + t.assert.equal(route.store, store) + t.assert.deepEqual(route.params, []) + + equalRouters(t, findMyWay, fundMyWayClone) +}) + +test('findRoute returns null for a static route', (t) => { + t.plan(7) + + const findMyWay = FindMyWay() + + const handler = () => {} + findMyWay.on('GET', '/example', handler) + + const fundMyWayClone = rfdc(findMyWay) + + const route = findMyWay.findRoute('GET', '/example1') + t.assert.equal(route, null) + + equalRouters(t, findMyWay, fundMyWayClone) +}) + +test('findRoute returns handler and params for a parametric route', (t) => { + t.plan(8) + + const findMyWay = FindMyWay() + + const handler = () => {} + findMyWay.on('GET', '/:param', handler) + + const fundMyWayClone = rfdc(findMyWay) + + const route = findMyWay.findRoute('GET', '/:param') + t.assert.equal(route.handler, handler) + t.assert.deepEqual(route.params, ['param']) + + equalRouters(t, findMyWay, fundMyWayClone) +}) + +test('findRoute returns null for a parametric route', (t) => { + t.plan(7) + + const findMyWay = FindMyWay() + + const handler = () => {} + findMyWay.on('GET', '/foo/:param', handler) + + const fundMyWayClone = rfdc(findMyWay) + + const route = findMyWay.findRoute('GET', '/bar/:param') + t.assert.equal(route, null) + + equalRouters(t, findMyWay, fundMyWayClone) +}) + +test('findRoute returns handler and params for a parametric route with static suffix', (t) => { + t.plan(8) + + const findMyWay = FindMyWay() + + const handler = () => {} + findMyWay.on('GET', '/:param-static', handler) + + const fundMyWayClone = rfdc(findMyWay) + + const route = findMyWay.findRoute('GET', '/:param-static') + t.assert.equal(route.handler, handler) + t.assert.deepEqual(route.params, ['param']) + + equalRouters(t, findMyWay, fundMyWayClone) +}) + +test('findRoute returns null for a parametric route with static suffix', (t) => { + t.plan(7) + + const findMyWay = FindMyWay() + findMyWay.on('GET', '/:param-static1', () => {}) + + const fundMyWayClone = rfdc(findMyWay) + + const route = findMyWay.findRoute('GET', '/:param-static2') + t.assert.equal(route, null) + + equalRouters(t, findMyWay, fundMyWayClone) +}) + +test('findRoute returns handler and original params even if a param name different', (t) => { + t.plan(8) + + const findMyWay = FindMyWay() + + const handler = () => {} + findMyWay.on('GET', '/:param1', handler) + + const fundMyWayClone = rfdc(findMyWay) + + const route = findMyWay.findRoute('GET', '/:param2') + t.assert.equal(route.handler, handler) + t.assert.deepEqual(route.params, ['param1']) + + equalRouters(t, findMyWay, fundMyWayClone) +}) + +test('findRoute returns handler and params for a multi-parametric route', (t) => { + t.plan(8) + + const findMyWay = FindMyWay() + + const handler = () => {} + findMyWay.on('GET', '/:param1-:param2', handler) + + const fundMyWayClone = rfdc(findMyWay) + + const route = findMyWay.findRoute('GET', '/:param1-:param2') + t.assert.equal(route.handler, handler) + t.assert.deepEqual(route.params, ['param1', 'param2']) + + equalRouters(t, findMyWay, fundMyWayClone) +}) + +test('findRoute returns null for a multi-parametric route', (t) => { + t.plan(7) + + const findMyWay = FindMyWay() + findMyWay.on('GET', '/foo/:param1-:param2/bar1', () => {}) + + const fundMyWayClone = rfdc(findMyWay) + + const route = findMyWay.findRoute('GET', '/foo/:param1-:param2/bar2') + t.assert.equal(route, null) + + equalRouters(t, findMyWay, fundMyWayClone) +}) + +test('findRoute returns handler and regexp param for a regexp route', (t) => { + t.plan(8) + + const findMyWay = FindMyWay() + + const handler = () => {} + findMyWay.on('GET', '/:param(^\\d+$)', handler) + + const fundMyWayClone = rfdc(findMyWay) + + const route = findMyWay.findRoute('GET', '/:param(^\\d+$)') + t.assert.equal(route.handler, handler) + t.assert.deepEqual(route.params, ['param']) + + equalRouters(t, findMyWay, fundMyWayClone) +}) + +test('findRoute returns null for a regexp route', (t) => { + t.plan(7) + + const findMyWay = FindMyWay() + findMyWay.on('GET', '/:file(^\\S+).png', () => {}) + + const fundMyWayClone = rfdc(findMyWay) + + const route = findMyWay.findRoute('GET', '/:file(^\\D+).png') + t.assert.equal(route, null) + + equalRouters(t, findMyWay, fundMyWayClone) +}) + +test('findRoute returns handler and wildcard param for a wildcard route', (t) => { + t.plan(8) + + const findMyWay = FindMyWay() + + const handler = () => {} + findMyWay.on('GET', '/example/*', handler) + + const fundMyWayClone = rfdc(findMyWay) + + const route = findMyWay.findRoute('GET', '/example/*') + t.assert.equal(route.handler, handler) + t.assert.deepEqual(route.params, ['*']) + + equalRouters(t, findMyWay, fundMyWayClone) +}) + +test('findRoute returns null for a wildcard route', (t) => { + t.plan(7) + + const findMyWay = FindMyWay() + findMyWay.on('GET', '/foo1/*', () => {}) + + const fundMyWayClone = rfdc(findMyWay) + + const route = findMyWay.findRoute('GET', '/foo2/*') + t.assert.equal(route, null) + + equalRouters(t, findMyWay, fundMyWayClone) +}) + +test('findRoute returns handler for a constrained route', (t) => { + t.plan(9) + + const findMyWay = FindMyWay() + + const handler = () => {} + findMyWay.on( + 'GET', + '/example', + { constraints: { version: '1.0.0' } }, + handler + ) + + const fundMyWayClone = rfdc(findMyWay) + + { + const route = findMyWay.findRoute('GET', '/example') + t.assert.equal(route, null) + } + + { + const route = findMyWay.findRoute('GET', '/example', { version: '1.0.0' }) + t.assert.equal(route.handler, handler) + } + + { + const route = findMyWay.findRoute('GET', '/example', { version: '2.0.0' }) + t.assert.equal(route, null) + } + + equalRouters(t, findMyWay, fundMyWayClone) +}) diff --git a/services/slides/node_modules/find-my-way/test/find.test.js b/services/slides/node_modules/find-my-way/test/find.test.js new file mode 100644 index 0000000000000000000000000000000000000000..8dade0b6df79b2f9cb35d0dd9b2d528fb6caf0ac --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/find.test.js @@ -0,0 +1,16 @@ +'use strict' + +const { test } = require('node:test') +const FindMyWay = require('..') + +test('find calls can pass no constraints', t => { + t.plan(3) + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/a', () => {}) + findMyWay.on('GET', '/a/b', () => {}) + + t.assert.ok(findMyWay.find('GET', '/a')) + t.assert.ok(findMyWay.find('GET', '/a/b')) + t.assert.ok(!findMyWay.find('GET', '/a/b/c')) +}) diff --git a/services/slides/node_modules/find-my-way/test/for-in-loop.test.js b/services/slides/node_modules/find-my-way/test/for-in-loop.test.js new file mode 100644 index 0000000000000000000000000000000000000000..81b95e821df84f30be19cd85f8c5286c70eabd27 --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/for-in-loop.test.js @@ -0,0 +1,22 @@ +'use strict' + +/* eslint no-extend-native: off */ + +const { test } = require('node:test') + +// Something could extend the Array prototype +Array.prototype.test = null +test('for-in-loop', t => { + t.assert.doesNotThrow(() => { + require('../') + }) +}) + +test('ignore inherited constraint keys', t => { + const findMyWay = require('../')() + const constraints = Object.create({ tap: true }) + + t.assert.doesNotThrow(() => { + findMyWay.on('GET', '/test', { constraints }, () => {}) + }) +}) diff --git a/services/slides/node_modules/find-my-way/test/full-url.test.js b/services/slides/node_modules/find-my-way/test/full-url.test.js new file mode 100644 index 0000000000000000000000000000000000000000..ba8b8c5b4d2f309d226d1713201a8c83a899c7c3 --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/full-url.test.js @@ -0,0 +1,30 @@ +'use strict' + +const { test } = require('node:test') +const FindMyWay = require('../') + +test('full-url', t => { + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.fail('Should not be defaultRoute') + } + }) + + findMyWay.on('GET', '/a', (req, res) => { + res.end('{"message":"hello world"}') + }) + + findMyWay.on('GET', '/a/:id', (req, res) => { + res.end('{"message":"hello world"}') + }) + + t.assert.deepEqual(findMyWay.find('GET', 'http://localhost/a', { host: 'localhost' }), findMyWay.find('GET', '/a', { host: 'localhost' })) + t.assert.deepEqual(findMyWay.find('GET', 'http://localhost:8080/a', { host: 'localhost' }), findMyWay.find('GET', '/a', { host: 'localhost' })) + t.assert.deepEqual(findMyWay.find('GET', 'http://123.123.123.123/a', {}), findMyWay.find('GET', '/a', {})) + t.assert.deepEqual(findMyWay.find('GET', 'https://localhost/a', { host: 'localhost' }), findMyWay.find('GET', '/a', { host: 'localhost' })) + + t.assert.deepEqual(findMyWay.find('GET', 'http://localhost/a/100', { host: 'localhost' }), findMyWay.find('GET', '/a/100', { host: 'localhost' })) + t.assert.deepEqual(findMyWay.find('GET', 'http://localhost:8080/a/100', { host: 'localhost' }), findMyWay.find('GET', '/a/100', { host: 'localhost' })) + t.assert.deepEqual(findMyWay.find('GET', 'http://123.123.123.123/a/100', {}), findMyWay.find('GET', '/a/100', {})) + t.assert.deepEqual(findMyWay.find('GET', 'https://localhost/a/100', { host: 'localhost' }), findMyWay.find('GET', '/a/100', { host: 'localhost' })) +}) diff --git a/services/slides/node_modules/find-my-way/test/has-route.test.js b/services/slides/node_modules/find-my-way/test/has-route.test.js new file mode 100644 index 0000000000000000000000000000000000000000..db3a97c6906c185e47ab0c9ad3db1012ef7fa0bd --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/has-route.test.js @@ -0,0 +1,218 @@ +'use strict' + +const { test } = require('node:test') +const rfdc = require('rfdc')({ proto: true }) +const FindMyWay = require('..') + +function equalRouters (t, router1, router2) { + t.assert.deepStrictEqual(router1._opts, router2._opts) + t.assert.deepEqual(router1.routes, router2.routes) + t.assert.deepEqual(JSON.stringify(router1.trees), JSON.stringify(router2.trees)) + + t.assert.deepStrictEqual( + router1.constrainer.strategies, + router2.constrainer.strategies + ) + t.assert.deepStrictEqual( + router1.constrainer.strategiesInUse, + router2.constrainer.strategiesInUse + ) + t.assert.deepStrictEqual( + router1.constrainer.asyncStrategiesInUse, + router2.constrainer.asyncStrategiesInUse + ) +} + +test('hasRoute returns false if there is no routes', t => { + t.plan(7) + + const findMyWay = FindMyWay() + const fundMyWayClone = rfdc(findMyWay) + + const hasRoute = findMyWay.hasRoute('GET', '/example') + t.assert.equal(hasRoute, false) + + equalRouters(t, findMyWay, fundMyWayClone) +}) + +test('hasRoute returns true for a static route', t => { + t.plan(7) + + const findMyWay = FindMyWay() + findMyWay.on('GET', '/example', () => {}) + + const fundMyWayClone = rfdc(findMyWay) + + const hasRoute = findMyWay.hasRoute('GET', '/example') + t.assert.equal(hasRoute, true) + + equalRouters(t, findMyWay, fundMyWayClone) +}) + +test('hasRoute returns false for a static route', t => { + t.plan(7) + + const findMyWay = FindMyWay() + findMyWay.on('GET', '/example', () => {}) + + const fundMyWayClone = rfdc(findMyWay) + + const hasRoute = findMyWay.hasRoute('GET', '/example1') + t.assert.equal(hasRoute, false) + + equalRouters(t, findMyWay, fundMyWayClone) +}) + +test('hasRoute returns true for a parametric route', t => { + t.plan(7) + + const findMyWay = FindMyWay() + findMyWay.on('GET', '/:param', () => {}) + + const fundMyWayClone = rfdc(findMyWay) + + const hasRoute = findMyWay.hasRoute('GET', '/:param') + t.assert.equal(hasRoute, true) + + equalRouters(t, findMyWay, fundMyWayClone) +}) + +test('hasRoute returns false for a parametric route', t => { + t.plan(7) + + const findMyWay = FindMyWay() + findMyWay.on('GET', '/foo/:param', () => {}) + + const fundMyWayClone = rfdc(findMyWay) + + const hasRoute = findMyWay.hasRoute('GET', '/bar/:param') + t.assert.equal(hasRoute, false) + + equalRouters(t, findMyWay, fundMyWayClone) +}) + +test('hasRoute returns true for a parametric route with static suffix', t => { + t.plan(7) + + const findMyWay = FindMyWay() + findMyWay.on('GET', '/:param-static', () => {}) + + const fundMyWayClone = rfdc(findMyWay) + + const hasRoute = findMyWay.hasRoute('GET', '/:param-static') + t.assert.equal(hasRoute, true) + + equalRouters(t, findMyWay, fundMyWayClone) +}) + +test('hasRoute returns false for a parametric route with static suffix', t => { + t.plan(7) + + const findMyWay = FindMyWay() + findMyWay.on('GET', '/:param-static1', () => {}) + + const fundMyWayClone = rfdc(findMyWay) + + const hasRoute = findMyWay.hasRoute('GET', '/:param-static2') + t.assert.equal(hasRoute, false) + + equalRouters(t, findMyWay, fundMyWayClone) +}) + +test('hasRoute returns true even if a param name different', t => { + t.plan(7) + + const findMyWay = FindMyWay() + findMyWay.on('GET', '/:param1', () => {}) + + const fundMyWayClone = rfdc(findMyWay) + + const hasRoute = findMyWay.hasRoute('GET', '/:param2') + t.assert.equal(hasRoute, true) + + equalRouters(t, findMyWay, fundMyWayClone) +}) + +test('hasRoute returns true for a multi-parametric route', t => { + t.plan(7) + + const findMyWay = FindMyWay() + findMyWay.on('GET', '/:param1-:param2', () => {}) + + const fundMyWayClone = rfdc(findMyWay) + + const hasRoute = findMyWay.hasRoute('GET', '/:param1-:param2') + t.assert.equal(hasRoute, true) + + equalRouters(t, findMyWay, fundMyWayClone) +}) + +test('hasRoute returns false for a multi-parametric route', t => { + t.plan(7) + + const findMyWay = FindMyWay() + findMyWay.on('GET', '/foo/:param1-:param2/bar1', () => {}) + + const fundMyWayClone = rfdc(findMyWay) + + const hasRoute = findMyWay.hasRoute('GET', '/foo/:param1-:param2/bar2') + t.assert.equal(hasRoute, false) + + equalRouters(t, findMyWay, fundMyWayClone) +}) + +test('hasRoute returns true for a regexp route', t => { + t.plan(7) + + const findMyWay = FindMyWay() + findMyWay.on('GET', '/:param(^\\d+$)', () => {}) + + const fundMyWayClone = rfdc(findMyWay) + + const hasRoute = findMyWay.hasRoute('GET', '/:param(^\\d+$)') + t.assert.equal(hasRoute, true) + + equalRouters(t, findMyWay, fundMyWayClone) +}) + +test('hasRoute returns false for a regexp route', t => { + t.plan(7) + + const findMyWay = FindMyWay() + findMyWay.on('GET', '/:file(^\\S+).png', () => {}) + + const fundMyWayClone = rfdc(findMyWay) + + const hasRoute = findMyWay.hasRoute('GET', '/:file(^\\D+).png') + t.assert.equal(hasRoute, false) + + equalRouters(t, findMyWay, fundMyWayClone) +}) + +test('hasRoute returns true for a wildcard route', t => { + t.plan(7) + + const findMyWay = FindMyWay() + findMyWay.on('GET', '/example/*', () => {}) + + const fundMyWayClone = rfdc(findMyWay) + + const hasRoute = findMyWay.hasRoute('GET', '/example/*') + t.assert.equal(hasRoute, true) + + equalRouters(t, findMyWay, fundMyWayClone) +}) + +test('hasRoute returns false for a wildcard route', t => { + t.plan(7) + + const findMyWay = FindMyWay() + findMyWay.on('GET', '/foo1/*', () => {}) + + const fundMyWayClone = rfdc(findMyWay) + + const hasRoute = findMyWay.hasRoute('GET', '/foo2/*') + t.assert.equal(hasRoute, false) + + equalRouters(t, findMyWay, fundMyWayClone) +}) diff --git a/services/slides/node_modules/find-my-way/test/host-storage.test.js b/services/slides/node_modules/find-my-way/test/host-storage.test.js new file mode 100644 index 0000000000000000000000000000000000000000..b14f9c0030aadebb2ebb52fe85011eb07fbb75fa --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/host-storage.test.js @@ -0,0 +1,27 @@ +const acceptHostStrategy = require('../lib/strategies/accept-host') + +const { test } = require('node:test') + +test('can get hosts by exact matches', async (t) => { + const storage = acceptHostStrategy.storage() + t.assert.equal(storage.get('fastify.io'), undefined) + storage.set('fastify.io', true) + t.assert.equal(storage.get('fastify.io'), true) +}) + +test('can get hosts by regexp matches', async (t) => { + const storage = acceptHostStrategy.storage() + t.assert.equal(storage.get('fastify.io'), undefined) + storage.set(/.+fastify\.io/, true) + t.assert.equal(storage.get('foo.fastify.io'), true) + t.assert.equal(storage.get('bar.fastify.io'), true) +}) + +test('exact host matches take precendence over regexp matches', async (t) => { + const storage = acceptHostStrategy.storage() + storage.set(/.+fastify\.io/, 'wildcard') + storage.set('auth.fastify.io', 'exact') + t.assert.equal(storage.get('foo.fastify.io'), 'wildcard') + t.assert.equal(storage.get('bar.fastify.io'), 'wildcard') + t.assert.equal(storage.get('auth.fastify.io'), 'exact') +}) diff --git a/services/slides/node_modules/find-my-way/test/http2/constraint.host.test.js b/services/slides/node_modules/find-my-way/test/http2/constraint.host.test.js new file mode 100644 index 0000000000000000000000000000000000000000..4cf11131fd578796cba95f75d5e5c887c7be7de1 --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/http2/constraint.host.test.js @@ -0,0 +1,44 @@ +'use strict' + +const { test } = require('node:test') +const FindMyWay = require('../..') + +test('A route supports host constraints under http2 protocol', t => { + t.plan(3) + + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/', {}, (req, res) => { + t.assert.assert.fail() + }) + findMyWay.on('GET', '/', { constraints: { host: 'fastify.io' } }, (req, res) => { + t.assert.equal(req.headers[':authority'], 'fastify.io') + }) + findMyWay.on('GET', '/', { constraints: { host: /.+\.de/ } }, (req, res) => { + t.assert.ok(req.headers[':authority'].endsWith('.de')) + }) + + findMyWay.lookup({ + method: 'GET', + url: '/', + headers: { + ':authority': 'fastify.io' + } + }) + + findMyWay.lookup({ + method: 'GET', + url: '/', + headers: { + ':authority': 'fastify.de' + } + }) + + findMyWay.lookup({ + method: 'GET', + url: '/', + headers: { + ':authority': 'find-my-way.de' + } + }) +}) diff --git a/services/slides/node_modules/find-my-way/test/issue-101.test.js b/services/slides/node_modules/find-my-way/test/issue-101.test.js new file mode 100644 index 0000000000000000000000000000000000000000..51df3c0f1fdd233c7bf450d023f3394f5247a251 --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/issue-101.test.js @@ -0,0 +1,31 @@ +'use strict' + +const { test } = require('node:test') +const FindMyWay = require('../') + +test('Falling back for node\'s parametric brother', t => { + t.plan(3) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.fail('Should not be defaultRoute') + } + }) + + findMyWay.on('GET', '/:namespace/:type/:id', () => {}) + findMyWay.on('GET', '/:namespace/jobs/:name/run', () => {}) + + t.assert.deepEqual( + findMyWay.find('GET', '/test_namespace/test_type/test_id').params, + { namespace: 'test_namespace', type: 'test_type', id: 'test_id' } + ) + + t.assert.deepEqual( + findMyWay.find('GET', '/test_namespace/jobss/test_id').params, + { namespace: 'test_namespace', type: 'jobss', id: 'test_id' } + ) + + t.assert.deepEqual( + findMyWay.find('GET', '/test_namespace/jobs/test_id').params, + { namespace: 'test_namespace', type: 'jobs', id: 'test_id' } + ) +}) diff --git a/services/slides/node_modules/find-my-way/test/issue-104.test.js b/services/slides/node_modules/find-my-way/test/issue-104.test.js new file mode 100644 index 0000000000000000000000000000000000000000..78430e1e01815ea8c9e14f00e60ac93ab60ee4b5 --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/issue-104.test.js @@ -0,0 +1,206 @@ +'use strict' + +const { test } = require('node:test') +const FindMyWay = require('../') + +test('Nested static parametric route, url with parameter common prefix > 1', t => { + t.plan(1) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.fail('Should not be defaultRoute') + } + }) + + findMyWay.on('GET', '/a/bbbb', (req, res) => { + res.end('{"message":"hello world"}') + }) + + findMyWay.on('GET', '/a/bbaa', (req, res) => { + res.end('{"message":"hello world"}') + }) + + findMyWay.on('GET', '/a/babb', (req, res) => { + res.end('{"message":"hello world"}') + }) + + findMyWay.on('DELETE', '/a/:id', (req, res) => { + res.end('{"message":"hello world"}') + }) + + t.assert.deepEqual(findMyWay.find('DELETE', '/a/bbar').params, { id: 'bbar' }) +}) + +test('Parametric route, url with parameter common prefix > 1', t => { + t.plan(1) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.fail('Should not be defaultRoute') + } + }) + + findMyWay.on('GET', '/aaa', (req, res) => { + res.end('{"message":"hello world"}') + }) + + findMyWay.on('GET', '/aabb', (req, res) => { + res.end('{"message":"hello world"}') + }) + + findMyWay.on('GET', '/abc', (req, res) => { + res.end('{"message":"hello world"}') + }) + + findMyWay.on('GET', '/:id', (req, res) => { + res.end('{"message":"hello world"}') + }) + + t.assert.deepEqual(findMyWay.find('GET', '/aab').params, { id: 'aab' }) +}) + +test('Parametric route, url with multi parameter common prefix > 1', t => { + t.plan(1) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.fail('Should not be defaultRoute') + } + }) + + findMyWay.on('GET', '/:id/aaa/:id2', (req, res) => { + res.end('{"message":"hello world"}') + }) + + findMyWay.on('GET', '/:id/aabb/:id2', (req, res) => { + res.end('{"message":"hello world"}') + }) + + findMyWay.on('GET', '/:id/abc/:id2', (req, res) => { + res.end('{"message":"hello world"}') + }) + + findMyWay.on('GET', '/:a/:b', (req, res) => { + res.end('{"message":"hello world"}') + }) + + t.assert.deepEqual(findMyWay.find('GET', '/hello/aab').params, { a: 'hello', b: 'aab' }) +}) + +test('Mixed routes, url with parameter common prefix > 1', t => { + t.plan(11) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.fail('Should not be defaultRoute') + } + }) + + findMyWay.on('GET', '/test', (req, res, params) => { + res.end('{"hello":"world"}') + }) + + findMyWay.on('GET', '/testify', (req, res, params) => { + res.end('{"hello":"world"}') + }) + + findMyWay.on('GET', '/test/hello', (req, res, params) => { + res.end('{"hello":"world"}') + }) + + findMyWay.on('GET', '/test/hello/test', (req, res, params) => { + res.end('{"hello":"world"}') + }) + + findMyWay.on('GET', '/te/:a', (req, res, params) => { + res.end('{"hello":"world"}') + }) + + findMyWay.on('GET', '/test/hello/:b', (req, res, params) => { + res.end('{"hello":"world"}') + }) + + findMyWay.on('GET', '/:c', (req, res, params) => { + res.end('{"hello":"world"}') + }) + + findMyWay.on('GET', '/text/hello', (req, res, params) => { + res.end('{"hello":"world"}') + }) + + findMyWay.on('GET', '/text/:d', (req, res, params) => { + res.end('{"winter":"is here"}') + }) + + findMyWay.on('GET', '/text/:e/test', (req, res, params) => { + res.end('{"winter":"is here"}') + }) + + t.assert.deepEqual(findMyWay.find('GET', '/test').params, {}) + t.assert.deepEqual(findMyWay.find('GET', '/testify').params, {}) + t.assert.deepEqual(findMyWay.find('GET', '/test/hello').params, {}) + t.assert.deepEqual(findMyWay.find('GET', '/test/hello/test').params, {}) + t.assert.deepEqual(findMyWay.find('GET', '/te/hello').params, { a: 'hello' }) + t.assert.deepEqual(findMyWay.find('GET', '/te/').params, { a: '' }) + t.assert.deepEqual(findMyWay.find('GET', '/testy').params, { c: 'testy' }) + t.assert.deepEqual(findMyWay.find('GET', '/besty').params, { c: 'besty' }) + t.assert.deepEqual(findMyWay.find('GET', '/text/hellos/test').params, { e: 'hellos' }) + t.assert.deepEqual(findMyWay.find('GET', '/te/hello/'), null) + t.assert.deepEqual(findMyWay.find('GET', '/te/hellos/testy'), null) +}) + +test('Parent parametric brother should not rewrite child node parametric brother', t => { + t.plan(1) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.fail('Should not be defaultRoute') + } + }) + + findMyWay.on('GET', '/text/hello', (req, res, params) => { + res.end('{"hello":"world"}') + }) + + findMyWay.on('GET', '/text/:e/test', (req, res, params) => { + res.end('{"winter":"is here"}') + }) + + findMyWay.on('GET', '/:c', (req, res, params) => { + res.end('{"hello":"world"}') + }) + + t.assert.deepEqual(findMyWay.find('GET', '/text/hellos/test').params, { e: 'hellos' }) +}) + +test('Mixed parametric routes, with last defined route being static', t => { + t.plan(4) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.fail('Should not be defaultRoute') + } + }) + + findMyWay.on('GET', '/test', (req, res, params) => { + res.end('{"hello":"world"}') + }) + + findMyWay.on('GET', '/test/:a', (req, res, params) => { + res.end('{"hello":"world"}') + }) + + findMyWay.on('GET', '/test/hello/:b', (req, res, params) => { + res.end('{"hello":"world"}') + }) + + findMyWay.on('GET', '/test/hello/:c/test', (req, res, params) => { + res.end('{"hello":"world"}') + }) + findMyWay.on('GET', '/test/hello/:c/:k', (req, res, params) => { + res.end('{"hello":"world"}') + }) + + findMyWay.on('GET', '/test/world', (req, res, params) => { + res.end('{"hello":"world"}') + }) + + t.assert.deepEqual(findMyWay.find('GET', '/test/hello').params, { a: 'hello' }) + t.assert.deepEqual(findMyWay.find('GET', '/test/hello/world/test').params, { c: 'world' }) + t.assert.deepEqual(findMyWay.find('GET', '/test/hello/world/te').params, { c: 'world', k: 'te' }) + t.assert.deepEqual(findMyWay.find('GET', '/test/hello/world/testy').params, { c: 'world', k: 'testy' }) +}) diff --git a/services/slides/node_modules/find-my-way/test/issue-110.test.js b/services/slides/node_modules/find-my-way/test/issue-110.test.js new file mode 100644 index 0000000000000000000000000000000000000000..937585d79b7a96647152d8338d3ef0ba20bcb936 --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/issue-110.test.js @@ -0,0 +1,31 @@ +'use strict' + +const { test } = require('node:test') +const FindMyWay = require('../') + +test('Nested static parametric route, url with parameter common prefix > 1', t => { + t.plan(1) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.fail('Should not be defaultRoute') + } + }) + + findMyWay.on('GET', '/api/foo/b2', (req, res) => { + res.end('{"message":"hello world"}') + }) + + findMyWay.on('GET', '/api/foo/bar/qux', (req, res) => { + res.end('{"message":"hello world"}') + }) + + findMyWay.on('GET', '/api/foo/:id/bar', (req, res) => { + res.end('{"message":"hello world"}') + }) + + findMyWay.on('GET', '/foo', (req, res) => { + res.end('{"message":"hello world"}') + }) + + t.assert.deepEqual(findMyWay.find('GET', '/api/foo/b-123/bar').params, { id: 'b-123' }) +}) diff --git a/services/slides/node_modules/find-my-way/test/issue-132.test.js b/services/slides/node_modules/find-my-way/test/issue-132.test.js new file mode 100644 index 0000000000000000000000000000000000000000..74459f716260e140b4f88c594e9402f5acd5dbd9 --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/issue-132.test.js @@ -0,0 +1,80 @@ +'use strict' + +const { test } = require('node:test') +const FindMyWay = require('../') + +test('Wildcard mixed with dynamic and common prefix / 1', t => { + t.plan(5) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.fail('Should not be defaultRoute') + } + }) + + findMyWay.on('OPTIONS', '/*', (req, res, params) => { + t.assert.equal(req.method, 'OPTIONS') + }) + + findMyWay.on('GET', '/obj/params/*', (req, res, params) => { + t.assert.equal(req.method, 'GET') + }) + + findMyWay.on('GET', '/obj/:id', (req, res, params) => { + t.assert.equal(req.method, 'GET') + }) + + findMyWay.on('GET', '/obj_params/*', (req, res, params) => { + t.assert.equal(req.method, 'GET') + }) + + findMyWay.lookup({ method: 'OPTIONS', url: '/obj/params', headers: {} }, null) + + findMyWay.lookup({ method: 'OPTIONS', url: '/obj/params/12', headers: {} }, null) + + findMyWay.lookup({ method: 'GET', url: '/obj/params/12', headers: {} }, null) + + findMyWay.lookup({ method: 'OPTIONS', url: '/obj_params/12', headers: {} }, null) + + findMyWay.lookup({ method: 'GET', url: '/obj_params/12', headers: {} }, null) +}) + +test('Wildcard mixed with dynamic and common prefix / 2', t => { + t.plan(6) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.fail('Should not be defaultRoute') + } + }) + + findMyWay.on('OPTIONS', '/*', (req, res, params) => { + t.assert.equal(req.method, 'OPTIONS') + }) + + findMyWay.on('OPTIONS', '/obj/*', (req, res, params) => { + t.assert.equal(req.method, 'OPTIONS') + }) + + findMyWay.on('GET', '/obj/params/*', (req, res, params) => { + t.assert.equal(req.method, 'GET') + }) + + findMyWay.on('GET', '/obj/:id', (req, res, params) => { + t.assert.equal(req.method, 'GET') + }) + + findMyWay.on('GET', '/obj_params/*', (req, res, params) => { + t.assert.equal(req.method, 'GET') + }) + + findMyWay.lookup({ method: 'OPTIONS', url: '/obj_params/params', headers: {} }, null) + + findMyWay.lookup({ method: 'OPTIONS', url: '/obj/params', headers: {} }, null) + + findMyWay.lookup({ method: 'OPTIONS', url: '/obj/params/12', headers: {} }, null) + + findMyWay.lookup({ method: 'GET', url: '/obj/params/12', headers: {} }, null) + + findMyWay.lookup({ method: 'OPTIONS', url: '/obj_params/12', headers: {} }, null) + + findMyWay.lookup({ method: 'GET', url: '/obj_params/12', headers: {} }, null) +}) diff --git a/services/slides/node_modules/find-my-way/test/issue-145.test.js b/services/slides/node_modules/find-my-way/test/issue-145.test.js new file mode 100644 index 0000000000000000000000000000000000000000..90778a871c2c385da1a72878202a3800b350f50e --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/issue-145.test.js @@ -0,0 +1,24 @@ +'use strict' + +const { test } = require('node:test') +const FindMyWay = require('../') + +test('issue-145', (t) => { + t.plan(8) + + const findMyWay = FindMyWay({ ignoreTrailingSlash: true }) + + const fixedPath = function staticPath () {} + const varPath = function parameterPath () {} + findMyWay.on('GET', '/a/b', fixedPath) + findMyWay.on('GET', '/a/:pam/c', varPath) + + t.assert.equal(findMyWay.find('GET', '/a/b').handler, fixedPath) + t.assert.equal(findMyWay.find('GET', '/a/b/').handler, fixedPath) + t.assert.equal(findMyWay.find('GET', '/a/b/c').handler, varPath) + t.assert.equal(findMyWay.find('GET', '/a/b/c/').handler, varPath) + t.assert.equal(findMyWay.find('GET', '/a/foo/c').handler, varPath) + t.assert.equal(findMyWay.find('GET', '/a/foo/c/').handler, varPath) + t.assert.ok(!findMyWay.find('GET', '/a/c')) + t.assert.ok(!findMyWay.find('GET', '/a/c/')) +}) diff --git a/services/slides/node_modules/find-my-way/test/issue-149.test.js b/services/slides/node_modules/find-my-way/test/issue-149.test.js new file mode 100644 index 0000000000000000000000000000000000000000..52ff04c98c0f4c1f3f5a889bc47f4602854e1e76 --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/issue-149.test.js @@ -0,0 +1,21 @@ +'use strict' + +const { test } = require('node:test') +const FindMyWay = require('../') + +test('Falling back for node\'s parametric brother', t => { + t.plan(3) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.fail('Should not be defaultRoute') + } + }) + + findMyWay.on('GET', '/foo/:id', () => {}) + findMyWay.on('GET', '/foo/:color/:id', () => {}) + findMyWay.on('GET', '/foo/red', () => {}) + + t.assert.deepEqual(findMyWay.find('GET', '/foo/red/123').params, { color: 'red', id: '123' }) + t.assert.deepEqual(findMyWay.find('GET', '/foo/blue/123').params, { color: 'blue', id: '123' }) + t.assert.deepEqual(findMyWay.find('GET', '/foo/red').params, {}) +}) diff --git a/services/slides/node_modules/find-my-way/test/issue-151.test.js b/services/slides/node_modules/find-my-way/test/issue-151.test.js new file mode 100644 index 0000000000000000000000000000000000000000..71f0c052b392d7c6f750bd5db324567fd48562fe --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/issue-151.test.js @@ -0,0 +1,54 @@ +'use strict' + +const { test } = require('node:test') +const FindMyWay = require('../') + +test('Wildcard route should not be blocked by Parametric with different method / 1', t => { + t.plan(1) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.fail('Should not be defaultRoute') + } + }) + + findMyWay.on('OPTIONS', '/*', (req, res, params) => { + t.assert.fail('Should not be here') + }) + + findMyWay.on('OPTIONS', '/obj/*', (req, res, params) => { + t.assert.equal(req.method, 'OPTIONS') + }) + + findMyWay.on('GET', '/obj/:id', (req, res, params) => { + t.assert.fail('Should not be GET') + }) + + findMyWay.lookup({ method: 'OPTIONS', url: '/obj/params', headers: {} }, null) +}) + +test('Wildcard route should not be blocked by Parametric with different method / 2', t => { + t.plan(1) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.fail('Should not be defaultRoute') + } + }) + + findMyWay.on('OPTIONS', '/*', { version: '1.2.3' }, (req, res, params) => { + t.assert.fail('Should not be here') + }) + + findMyWay.on('OPTIONS', '/obj/*', { version: '1.2.3' }, (req, res, params) => { + t.assert.equal(req.method, 'OPTIONS') + }) + + findMyWay.on('GET', '/obj/:id', { version: '1.2.3' }, (req, res, params) => { + t.assert.fail('Should not be GET') + }) + + findMyWay.lookup({ + method: 'OPTIONS', + url: '/obj/params', + headers: { 'accept-version': '1.2.3' } + }, null) +}) diff --git a/services/slides/node_modules/find-my-way/test/issue-154.test.js b/services/slides/node_modules/find-my-way/test/issue-154.test.js new file mode 100644 index 0000000000000000000000000000000000000000..60ec264b382a09eb4e202af7c37a3bfbbaf44925 --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/issue-154.test.js @@ -0,0 +1,21 @@ +'use strict' + +const { test } = require('node:test') +const FindMyWay = require('..') +const noop = () => {} + +test('Should throw when not sending a string', t => { + t.plan(3) + + const findMyWay = FindMyWay() + + t.assert.throws(() => { + findMyWay.on('GET', '/t1', { constraints: { version: 42 } }, noop) + }) + t.assert.throws(() => { + findMyWay.on('GET', '/t2', { constraints: { version: null } }, noop) + }) + t.assert.throws(() => { + findMyWay.on('GET', '/t2', { constraints: { version: true } }, noop) + }) +}) diff --git a/services/slides/node_modules/find-my-way/test/issue-161.test.js b/services/slides/node_modules/find-my-way/test/issue-161.test.js new file mode 100644 index 0000000000000000000000000000000000000000..1fae773995a273df0d5e692a9d3dd9a658fd9a69 --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/issue-161.test.js @@ -0,0 +1,88 @@ +'use strict' + +const { test } = require('node:test') +const FindMyWay = require('../') + +test('Falling back for node\'s parametric brother without ignoreTrailingSlash', t => { + t.plan(4) + const findMyWay = FindMyWay({ + ignoreTrailingSlash: false, + defaultRoute: (req, res) => { + t.assert.fail('Should not be defaultRoute') + } + }) + + findMyWay.on('GET', '/static/param1', () => {}) + findMyWay.on('GET', '/static/param2', () => {}) + findMyWay.on('GET', '/static/:paramA/next', () => {}) + + t.assert.deepEqual(findMyWay.find('GET', '/static/param1').params, {}) + t.assert.deepEqual(findMyWay.find('GET', '/static/param2').params, {}) + t.assert.deepEqual(findMyWay.find('GET', '/static/paramOther/next').params, { paramA: 'paramOther' }) + t.assert.deepEqual(findMyWay.find('GET', '/static/param1/next').params, { paramA: 'param1' }) +}) + +test('Falling back for node\'s parametric brother with ignoreTrailingSlash', t => { + t.plan(4) + const findMyWay = FindMyWay({ + ignoreTrailingSlash: true, + defaultRoute: (req, res) => { + t.assert.fail('Should not be defaultRoute') + } + }) + + findMyWay.on('GET', '/static/param1', () => {}) + findMyWay.on('GET', '/static/param2', () => {}) + findMyWay.on('GET', '/static/:paramA/next', () => {}) + + t.assert.deepEqual(findMyWay.find('GET', '/static/param1').params, {}) + t.assert.deepEqual(findMyWay.find('GET', '/static/param2').params, {}) + t.assert.deepEqual(findMyWay.find('GET', '/static/paramOther/next').params, { paramA: 'paramOther' }) + t.assert.deepEqual(findMyWay.find('GET', '/static/param1/next').params, { paramA: 'param1' }) +}) + +test('Falling back for node\'s parametric brother without ignoreTrailingSlash', t => { + t.plan(4) + const findMyWay = FindMyWay({ + ignoreTrailingSlash: false, + defaultRoute: (req, res) => { + t.assert.fail('Should not be defaultRoute') + } + }) + + findMyWay.on('GET', '/static/param1', () => {}) + findMyWay.on('GET', '/static/param2', () => {}) + findMyWay.on('GET', '/static/:paramA/next', () => {}) + + findMyWay.on('GET', '/static/param1/next/param3', () => {}) + findMyWay.on('GET', '/static/param1/next/param4', () => {}) + findMyWay.on('GET', '/static/:paramA/next/:paramB/other', () => {}) + + t.assert.deepEqual(findMyWay.find('GET', '/static/param1/next/param3').params, {}) + t.assert.deepEqual(findMyWay.find('GET', '/static/param1/next/param4').params, {}) + t.assert.deepEqual(findMyWay.find('GET', '/static/paramOther/next/paramOther2/other').params, { paramA: 'paramOther', paramB: 'paramOther2' }) + t.assert.deepEqual(findMyWay.find('GET', '/static/param1/next/param3/other').params, { paramA: 'param1', paramB: 'param3' }) +}) + +test('Falling back for node\'s parametric brother with ignoreTrailingSlash', t => { + t.plan(4) + const findMyWay = FindMyWay({ + ignoreTrailingSlash: true, + defaultRoute: (req, res) => { + t.assert.fail('Should not be defaultRoute') + } + }) + + findMyWay.on('GET', '/static/param1', () => {}) + findMyWay.on('GET', '/static/param2', () => {}) + findMyWay.on('GET', '/static/:paramA/next', () => {}) + + findMyWay.on('GET', '/static/param1/next/param3', () => {}) + findMyWay.on('GET', '/static/param1/next/param4', () => {}) + findMyWay.on('GET', '/static/:paramA/next/:paramB/other', () => {}) + + t.assert.deepEqual(findMyWay.find('GET', '/static/param1/next/param3').params, {}) + t.assert.deepEqual(findMyWay.find('GET', '/static/param1/next/param4').params, {}) + t.assert.deepEqual(findMyWay.find('GET', '/static/paramOther/next/paramOther2/other').params, { paramA: 'paramOther', paramB: 'paramOther2' }) + t.assert.deepEqual(findMyWay.find('GET', '/static/param1/next/param3/other').params, { paramA: 'param1', paramB: 'param3' }) +}) diff --git a/services/slides/node_modules/find-my-way/test/issue-17.test.js b/services/slides/node_modules/find-my-way/test/issue-17.test.js new file mode 100644 index 0000000000000000000000000000000000000000..f9f8119e0f6b2de9e0c01b78d1043e354fa5c8ab --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/issue-17.test.js @@ -0,0 +1,397 @@ +'use strict' + +const { test } = require('node:test') +const FindMyWay = require('../') + +test('Parametric route, request.url contains dash', t => { + t.plan(1) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.fail('Should not be defaultRoute') + } + }) + + findMyWay.on('GET', '/a/:param/b', (req, res, params) => { + t.assert.equal(params.param, 'foo-bar') + }) + + findMyWay.lookup({ method: 'GET', url: '/a/foo-bar/b', headers: {} }, null) +}) + +test('Parametric route with fixed suffix', t => { + t.plan(6) + const findMyWay = FindMyWay({ + defaultRoute: () => t.assert.fail('Should not be defaultRoute') + }) + + findMyWay.on('GET', '/a/:param-static', () => {}) + findMyWay.on('GET', '/b/:param.static', () => {}) + + t.assert.deepEqual(findMyWay.find('GET', '/a/param-static', {}).params, { param: 'param' }) + t.assert.deepEqual(findMyWay.find('GET', '/b/param.static', {}).params, { param: 'param' }) + + t.assert.deepEqual(findMyWay.find('GET', '/a/param-param-static', {}).params, { param: 'param-param' }) + t.assert.deepEqual(findMyWay.find('GET', '/b/param.param.static', {}).params, { param: 'param.param' }) + + t.assert.deepEqual(findMyWay.find('GET', '/a/param.param-static', {}).params, { param: 'param.param' }) + t.assert.deepEqual(findMyWay.find('GET', '/b/param-param.static', {}).params, { param: 'param-param' }) +}) + +test('Regex param exceeds max parameter length', t => { + t.plan(1) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.ok('route not matched') + } + }) + + findMyWay.on('GET', '/a/:param(^\\w{3})', (req, res, params) => { + t.assert.fail('regex match') + }) + + findMyWay.lookup({ method: 'GET', url: '/a/fool', headers: {} }, null) +}) + +test('Parametric route with regexp and fixed suffix / 1', t => { + t.plan(4) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.ok('route not matched') + } + }) + + findMyWay.on('GET', '/a/:param(^\\w{3})bar', (req, res, params) => { + t.assert.fail('regex match') + }) + + findMyWay.lookup({ method: 'GET', url: '/a/$mebar', headers: {} }, null) + findMyWay.lookup({ method: 'GET', url: '/a/foolol', headers: {} }, null) + findMyWay.lookup({ method: 'GET', url: '/a/foobaz', headers: {} }, null) + findMyWay.lookup({ method: 'GET', url: '/a/foolbar', headers: {} }, null) +}) + +test('Parametric route with regexp and fixed suffix / 2', t => { + t.plan(1) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.fail('Should not be defaultRoute') + } + }) + + findMyWay.on('GET', '/a/:param(^\\w{3})bar', (req, res, params) => { + t.assert.equal(params.param, 'foo') + }) + + findMyWay.lookup({ method: 'GET', url: '/a/foobar', headers: {} }, null) +}) + +test('Parametric route with regexp and fixed suffix / 3', t => { + t.plan(1) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.fail('Should not be defaultRoute') + } + }) + + findMyWay.on('GET', '/a/:param(^\\w{3}-\\w{3})foo', (req, res, params) => { + t.assert.equal(params.param, 'abc-def') + }) + + findMyWay.lookup({ method: 'GET', url: '/a/abc-deffoo', headers: {} }, null) +}) + +test('Multi parametric route / 1', t => { + t.plan(4) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.fail('Should not be defaultRoute') + } + }) + + findMyWay.on('GET', '/a/:p1-:p2', (req, res, params) => { + t.assert.equal(params.p1, 'foo') + t.assert.equal(params.p2, 'bar') + }) + + findMyWay.on('GET', '/b/:p1.:p2', (req, res, params) => { + t.assert.equal(params.p1, 'foo') + t.assert.equal(params.p2, 'bar') + }) + + findMyWay.lookup({ method: 'GET', url: '/a/foo-bar', headers: {} }, null) + findMyWay.lookup({ method: 'GET', url: '/b/foo.bar', headers: {} }, null) +}) + +test('Multi parametric route / 2', t => { + t.plan(4) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.fail('Should not be defaultRoute') + } + }) + + findMyWay.on('GET', '/a/:p1-:p2', (req, res, params) => { + t.assert.equal(params.p1, 'foo-bar') + t.assert.equal(params.p2, 'baz') + }) + + findMyWay.on('GET', '/b/:p1.:p2', (req, res, params) => { + t.assert.equal(params.p1, 'foo') + t.assert.equal(params.p2, 'bar-baz') + }) + + findMyWay.lookup({ method: 'GET', url: '/a/foo-bar-baz', headers: {} }, null) + findMyWay.lookup({ method: 'GET', url: '/b/foo.bar-baz', headers: {} }, null) +}) + +test('Multi parametric route / 3', t => { + t.plan(4) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.fail('Should not be defaultRoute') + } + }) + + findMyWay.on('GET', '/a/:p_1-:$p', (req, res, params) => { + t.assert.equal(params.p_1, 'foo') + t.assert.equal(params.$p, 'bar') + }) + + findMyWay.on('GET', '/b/:p_1.:$p', (req, res, params) => { + t.assert.equal(params.p_1, 'foo') + t.assert.equal(params.$p, 'bar') + }) + + findMyWay.lookup({ method: 'GET', url: '/a/foo-bar', headers: {} }, null) + findMyWay.lookup({ method: 'GET', url: '/b/foo.bar', headers: {} }, null) +}) + +test('Multi parametric route / 4', t => { + t.plan(2) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.ok('Everything good') + } + }) + + findMyWay.on('GET', '/a/:p1-:p2', (req, res, params) => { + t.assert.fail('Should not match this route') + }) + + findMyWay.on('GET', '/b/:p1.:p2', (req, res, params) => { + t.assert.fail('Should not match this route') + }) + + findMyWay.lookup({ method: 'GET', url: '/a/foo', headers: {} }, null) + findMyWay.lookup({ method: 'GET', url: '/b/foo', headers: {} }, null) +}) + +test('Multi parametric route with regexp / 1', t => { + t.plan(2) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.fail('Should not be defaultRoute') + } + }) + + findMyWay.on('GET', '/at/:hour(^\\d+)h:minute(^\\d+)m', (req, res, params) => { + t.assert.equal(params.hour, '0') + t.assert.equal(params.minute, '42') + }) + + findMyWay.lookup({ method: 'GET', url: '/at/0h42m', headers: {} }, null) +}) + +test('Multi parametric route with colon separator', t => { + t.plan(3) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.fail('Should not be defaultRoute') + } + }) + + findMyWay.on('GET', '/:param(.*)::suffix', (req, res, params) => { + t.assert.equal(params.param, 'foo') + }) + + findMyWay.on('GET', '/:param1(.*)::suffix1-:param2(.*)::suffix2/static', (req, res, params) => { + t.assert.equal(params.param1, 'foo') + t.assert.equal(params.param2, 'bar') + }) + + findMyWay.lookup({ method: 'GET', url: '/foo:suffix', headers: {} }, null) + findMyWay.lookup({ method: 'GET', url: '/foo:suffix1-bar:suffix2/static', headers: {} }, null) +}) + +test('Multi parametric route with regexp / 2', t => { + t.plan(8) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.fail('Should not be defaultRoute') + } + }) + + findMyWay.on('GET', '/a/:uuid(^[\\d-]{19})-:user(^\\w+)', (req, res, params) => { + t.assert.equal(params.uuid, '1111-2222-3333-4444') + t.assert.equal(params.user, 'foo') + }) + + findMyWay.on('GET', '/a/:uuid(^[\\d-]{19})-:user(^\\w+)/account', (req, res, params) => { + t.assert.equal(params.uuid, '1111-2222-3333-4445') + t.assert.equal(params.user, 'bar') + }) + + findMyWay.on('GET', '/b/:uuid(^[\\d-]{19}).:user(^\\w+)', (req, res, params) => { + t.assert.equal(params.uuid, '1111-2222-3333-4444') + t.assert.equal(params.user, 'foo') + }) + + findMyWay.on('GET', '/b/:uuid(^[\\d-]{19}).:user(^\\w+)/account', (req, res, params) => { + t.assert.equal(params.uuid, '1111-2222-3333-4445') + t.assert.equal(params.user, 'bar') + }) + + findMyWay.lookup({ method: 'GET', url: '/a/1111-2222-3333-4444-foo', headers: {} }, null) + findMyWay.lookup({ method: 'GET', url: '/a/1111-2222-3333-4445-bar/account', headers: {} }, null) + + findMyWay.lookup({ method: 'GET', url: '/b/1111-2222-3333-4444.foo', headers: {} }, null) + findMyWay.lookup({ method: 'GET', url: '/b/1111-2222-3333-4445.bar/account', headers: {} }, null) +}) + +test('Multi parametric route with fixed suffix', t => { + t.plan(4) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.fail('Should not be defaultRoute') + } + }) + + findMyWay.on('GET', '/a/:p1-:p2-baz', (req, res, params) => { + t.assert.equal(params.p1, 'foo') + t.assert.equal(params.p2, 'bar') + }) + + findMyWay.on('GET', '/b/:p1.:p2-baz', (req, res, params) => { + t.assert.equal(params.p1, 'foo') + t.assert.equal(params.p2, 'bar') + }) + + findMyWay.lookup({ method: 'GET', url: '/a/foo-bar-baz', headers: {} }, null) + findMyWay.lookup({ method: 'GET', url: '/b/foo.bar-baz', headers: {} }, null) +}) + +test('Multi parametric route with regexp and fixed suffix', t => { + t.plan(4) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.fail('Should not be defaultRoute') + } + }) + + findMyWay.on('GET', '/a/:p1(^\\w+)-:p2(^\\w+)-kuux', (req, res, params) => { + t.assert.equal(params.p1, 'foo') + t.assert.equal(params.p2, 'barbaz') + }) + + findMyWay.on('GET', '/b/:p1(^\\w+).:p2(^\\w+)-kuux', (req, res, params) => { + t.assert.equal(params.p1, 'foo') + t.assert.equal(params.p2, 'barbaz') + }) + + findMyWay.lookup({ method: 'GET', url: '/a/foo-barbaz-kuux', headers: {} }, null) + findMyWay.lookup({ method: 'GET', url: '/b/foo.barbaz-kuux', headers: {} }, null) +}) + +test('Multi parametric route with wildcard', t => { + t.plan(4) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.fail('Should not be defaultRoute') + } + }) + + findMyWay.on('GET', '/a/:p1-:p2/*', (req, res, params) => { + t.assert.equal(params.p1, 'foo') + t.assert.equal(params.p2, 'bar') + }) + + findMyWay.on('GET', '/b/:p1.:p2/*', (req, res, params) => { + t.assert.equal(params.p1, 'foo') + t.assert.equal(params.p2, 'bar') + }) + + findMyWay.lookup({ method: 'GET', url: '/a/foo-bar/baz', headers: {} }, null) + findMyWay.lookup({ method: 'GET', url: '/b/foo.bar/baz', headers: {} }, null) +}) + +test('Nested multi parametric route', t => { + t.plan(6) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.fail('Should not be defaultRoute') + } + }) + + findMyWay.on('GET', '/a/:p1-:p2/b/:p3', (req, res, params) => { + t.assert.equal(params.p1, 'foo') + t.assert.equal(params.p2, 'bar') + t.assert.equal(params.p3, 'baz') + }) + + findMyWay.on('GET', '/b/:p1.:p2/b/:p3', (req, res, params) => { + t.assert.equal(params.p1, 'foo') + t.assert.equal(params.p2, 'bar') + t.assert.equal(params.p3, 'baz') + }) + + findMyWay.lookup({ method: 'GET', url: '/a/foo-bar/b/baz', headers: {} }, null) + findMyWay.lookup({ method: 'GET', url: '/b/foo.bar/b/baz', headers: {} }, null) +}) + +test('Nested multi parametric route with regexp / 1', t => { + t.plan(6) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.fail('Should not be defaultRoute') + } + }) + + findMyWay.on('GET', '/a/:p1(^\\w{3})-:p2(^\\d+)/b/:p3', (req, res, params) => { + t.assert.equal(params.p1, 'foo') + t.assert.equal(params.p2, '42') + t.assert.equal(params.p3, 'bar') + }) + + findMyWay.on('GET', '/b/:p1(^\\w{3}).:p2(^\\d+)/b/:p3', (req, res, params) => { + t.assert.equal(params.p1, 'foo') + t.assert.equal(params.p2, '42') + t.assert.equal(params.p3, 'bar') + }) + + findMyWay.lookup({ method: 'GET', url: '/a/foo-42/b/bar', headers: {} }, null) + findMyWay.lookup({ method: 'GET', url: '/b/foo.42/b/bar', headers: {} }, null) +}) + +test('Nested multi parametric route with regexp / 2', t => { + t.plan(6) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.fail('Should not be defaultRoute') + } + }) + + findMyWay.on('GET', '/a/:p1(^\\w{3})-:p2/b/:p3', (req, res, params) => { + t.assert.equal(params.p1, 'foo') + t.assert.equal(params.p2, '42') + t.assert.equal(params.p3, 'bar') + }) + + findMyWay.on('GET', '/b/:p1(^\\w{3}).:p2/b/:p3', (req, res, params) => { + t.assert.equal(params.p1, 'foo') + t.assert.equal(params.p2, '42') + t.assert.equal(params.p3, 'bar') + }) + + findMyWay.lookup({ method: 'GET', url: '/a/foo-42/b/bar', headers: {} }, null) + findMyWay.lookup({ method: 'GET', url: '/b/foo.42/b/bar', headers: {} }, null) +}) diff --git a/services/slides/node_modules/find-my-way/test/issue-175.test.js b/services/slides/node_modules/find-my-way/test/issue-175.test.js new file mode 100644 index 0000000000000000000000000000000000000000..c45236e6f08a00569da14cc63f9d04fb7c2fef59 --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/issue-175.test.js @@ -0,0 +1,80 @@ +'use strict' + +const { test } = require('node:test') +const FindMyWay = require('..') + +test('double colon is replaced with single colon, no parameters', t => { + t.plan(1) + const findMyWay = FindMyWay({ + defaultRoute: () => t.assert.fail('should not be default route') + }) + + function handler (req, res, params) { + t.assert.deepEqual(params, {}) + } + + findMyWay.on('GET', '/name::customVerb', handler) + + findMyWay.lookup({ method: 'GET', url: '/name:customVerb' }, null) +}) + +test('exactly one match for static route with colon', t => { + t.plan(2) + const findMyWay = FindMyWay() + + function handler () {} + findMyWay.on('GET', '/name::customVerb', handler) + + t.assert.equal(findMyWay.find('GET', '/name:customVerb').handler, handler) + t.assert.equal(findMyWay.find('GET', '/name:test'), null) +}) + +test('double colon is replaced with single colon, no parameters, same parent node name', t => { + t.plan(1) + const findMyWay = FindMyWay({ + defaultRoute: () => t.assert.fail('should not be default route') + }) + + findMyWay.on('GET', '/name', () => { + t.assert.fail('should not be parent route') + }) + + findMyWay.on('GET', '/name::customVerb', (req, res, params) => { + t.assert.deepEqual(params, {}) + }) + + findMyWay.lookup({ method: 'GET', url: '/name:customVerb', headers: {} }, null) +}) + +test('double colon is replaced with single colon, default route, same parent node name', t => { + t.plan(1) + const findMyWay = FindMyWay({ + defaultRoute: () => t.assert.ok('should be default route') + }) + + findMyWay.on('GET', '/name', () => { + t.assert.fail('should not be parent route') + }) + + findMyWay.on('GET', '/name::customVerb', () => { + t.assert.fail('should not be child route') + }) + + findMyWay.lookup({ method: 'GET', url: '/name:wrongCustomVerb', headers: {} }, null) +}) + +test('double colon is replaced with single colon, with parameters', t => { + t.plan(1) + const findMyWay = FindMyWay({ + defaultRoute: () => t.assert.fail('should not be default route') + }) + + findMyWay.on('GET', '/name1::customVerb1/:param1/name2::customVerb2:param2', (req, res, params) => { + t.assert.deepEqual(params, { + param1: 'value1', + param2: 'value2' + }) + }) + + findMyWay.lookup({ method: 'GET', url: '/name1:customVerb1/value1/name2:customVerb2value2', headers: {} }, null) +}) diff --git a/services/slides/node_modules/find-my-way/test/issue-182.test.js b/services/slides/node_modules/find-my-way/test/issue-182.test.js new file mode 100644 index 0000000000000000000000000000000000000000..ac4c7cbdff2f629625c50576541699fce7cfb43f --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/issue-182.test.js @@ -0,0 +1,18 @@ +'use strict' + +const { test } = require('node:test') +const FindMyWay = require('..') + +test('Set method property when splitting node', t => { + t.plan(1) + const findMyWay = FindMyWay() + + function handler (req, res, params) { + t.assert.ok() + } + + findMyWay.on('GET', '/health-a/health', handler) + findMyWay.on('GET', '/health-b/health', handler) + + t.assert.ok(!findMyWay.prettyPrint().includes('undefined')) +}) diff --git a/services/slides/node_modules/find-my-way/test/issue-190.test.js b/services/slides/node_modules/find-my-way/test/issue-190.test.js new file mode 100644 index 0000000000000000000000000000000000000000..610e8cc0e5bd225386116f3881ea09d9de830029 --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/issue-190.test.js @@ -0,0 +1,44 @@ +'use strict' + +const { test } = require('node:test') +const FindMyWay = require('../') + +test('issue-190', (t) => { + t.plan(6) + + const findMyWay = FindMyWay() + + let staticCounter = 0 + let paramCounter = 0 + const staticPath = function staticPath () { staticCounter++ } + const paramPath = function paramPath () { paramCounter++ } + const extraPath = function extraPath () { } + findMyWay.on('GET', '/api/users/award_winners', staticPath) + findMyWay.on('GET', '/api/users/admins', staticPath) + findMyWay.on('GET', '/api/users/:id', paramPath) + findMyWay.on('GET', '/api/:resourceType/foo', extraPath) + + t.assert.equal(findMyWay.find('GET', '/api/users/admins').handler, staticPath) + t.assert.equal(findMyWay.find('GET', '/api/users/award_winners').handler, staticPath) + t.assert.equal(findMyWay.find('GET', '/api/users/a766c023-34ec-40d2-923c-e8259a28d2c5').handler, paramPath) + t.assert.equal(findMyWay.find('GET', '/api/users/b766c023-34ec-40d2-923c-e8259a28d2c5').handler, paramPath) + + findMyWay.lookup({ + method: 'GET', + url: '/api/users/admins', + headers: { } + }) + findMyWay.lookup({ + method: 'GET', + url: '/api/users/award_winners', + headers: { } + }) + findMyWay.lookup({ + method: 'GET', + url: '/api/users/a766c023-34ec-40d2-923c-e8259a28d2c5', + headers: { } + }) + + t.assert.equal(staticCounter, 2) + t.assert.equal(paramCounter, 1) +}) diff --git a/services/slides/node_modules/find-my-way/test/issue-20.test.js b/services/slides/node_modules/find-my-way/test/issue-20.test.js new file mode 100644 index 0000000000000000000000000000000000000000..d1ab5ea4998d3a11cf29d44d784c3f69587c3f4d --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/issue-20.test.js @@ -0,0 +1,79 @@ +'use strict' + +const { test } = require('node:test') +const FindMyWay = require('../') + +test('Standard case', t => { + t.plan(1) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.fail('Should not be here') + } + }) + + findMyWay.on('GET', '/a/:param', (req, res, params) => { + t.assert.equal(params.param, 'perfectly-fine-route') + }) + + findMyWay.lookup({ method: 'GET', url: '/a/perfectly-fine-route', headers: {} }, null) +}) + +test('Should be 404 / 1', t => { + t.plan(1) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.ok('Everything good') + } + }) + + findMyWay.on('GET', '/a/:param', (req, res, params) => { + t.assert.fail('We should not be here') + }) + + findMyWay.lookup({ method: 'GET', url: '/a', headers: {} }, null) +}) + +test('Should be 404 / 2', t => { + t.plan(1) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.ok('Everything good') + } + }) + + findMyWay.on('GET', '/a/:param', (req, res, params) => { + t.assert.fail('We should not be here') + }) + + findMyWay.lookup({ method: 'GET', url: '/a-non-existing-route', headers: {} }, null) +}) + +test('Should be 404 / 3', t => { + t.plan(1) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.ok('Everything good') + } + }) + + findMyWay.on('GET', '/a/:param', (req, res, params) => { + t.assert.fail('We should not be here') + }) + + findMyWay.lookup({ method: 'GET', url: '/a//', headers: {} }, null) +}) + +test('Should get an empty parameter', t => { + t.plan(1) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.fail('We should not be here') + } + }) + + findMyWay.on('GET', '/a/:param', (req, res, params) => { + t.assert.equal(params.param, '') + }) + + findMyWay.lookup({ method: 'GET', url: '/a/', headers: {} }, null) +}) diff --git a/services/slides/node_modules/find-my-way/test/issue-206.test.js b/services/slides/node_modules/find-my-way/test/issue-206.test.js new file mode 100644 index 0000000000000000000000000000000000000000..39e5f0ad692887055249576ba69b5d1eb12b167b --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/issue-206.test.js @@ -0,0 +1,121 @@ +'use strict' + +const { test } = require('node:test') +const FindMyWay = require('..') + +test('Decode the URL before the routing', t => { + t.plan(8) + const findMyWay = FindMyWay() + + function space (req, res, params) {} + function percentTwenty (req, res, params) {} + function percentTwentyfive (req, res, params) {} + + findMyWay.on('GET', '/static/:pathParam', () => {}) + findMyWay.on('GET', '/[...]/a .html', space) + findMyWay.on('GET', '/[...]/a%20.html', percentTwenty) + findMyWay.on('GET', '/[...]/a%2520.html', percentTwentyfive) + + t.assert.equal(findMyWay.find('GET', '/[...]/a .html').handler, space) + t.assert.equal(findMyWay.find('GET', '/%5B...%5D/a .html').handler, space) + t.assert.equal(findMyWay.find('GET', '/[...]/a%20.html').handler, space, 'a%20 decode is a ') + t.assert.equal(findMyWay.find('GET', '/%5B...%5D/a%20.html').handler, space, 'a%20 decode is a ') + t.assert.equal(findMyWay.find('GET', '/[...]/a%2520.html').handler, percentTwenty, 'a%2520 decode is a%20') + t.assert.equal(findMyWay.find('GET', '/%5B...%5D/a%252520.html').handler, percentTwentyfive, 'a%252520.html is a%2520') + t.assert.equal(findMyWay.find('GET', '/[...]/a .html'), null, 'double space') + t.assert.equal(findMyWay.find('GET', '/static/%25E0%A4%A'), null, 'invalid encoded path param') +}) + +test('double encoding', t => { + t.plan(8) + const findMyWay = FindMyWay() + + function pathParam (req, res, params) { + t.assert.deepEqual(params, this.expect, 'path param') + t.assert.deepEqual(pathParam, this.handler, 'match handler') + } + function regexPathParam (req, res, params) { + t.assert.deepEqual(params, this.expect, 'regex param') + t.assert.deepEqual(regexPathParam, this.handler, 'match handler') + } + function wildcard (req, res, params) { + t.assert.deepEqual(params, this.expect, 'wildcard param') + t.assert.deepEqual(wildcard, this.handler, 'match handler') + } + + findMyWay.on('GET', '/:pathParam', pathParam) + findMyWay.on('GET', '/reg/:regExeParam(^.*$)', regexPathParam) + findMyWay.on('GET', '/wild/*', wildcard) + + findMyWay.lookup(get('/' + doubleEncode('reg/hash# .png')), null, + { expect: { pathParam: singleEncode('reg/hash# .png') }, handler: pathParam } + ) + findMyWay.lookup(get('/' + doubleEncode('special # $ & + , / : ; = ? @')), null, + { expect: { pathParam: singleEncode('special # $ & + , / : ; = ? @') }, handler: pathParam } + ) + findMyWay.lookup(get('/reg/' + doubleEncode('hash# .png')), null, + { expect: { regExeParam: singleEncode('hash# .png') }, handler: regexPathParam } + ) + findMyWay.lookup(get('/wild/' + doubleEncode('mail@mail.it')), null, + { expect: { '*': singleEncode('mail@mail.it') }, handler: wildcard } + ) + + function doubleEncode (str) { + return encodeURIComponent(encodeURIComponent(str)) + } + function singleEncode (str) { + return encodeURIComponent(str) + } +}) + +test('Special chars on path parameter', t => { + t.plan(10) + const findMyWay = FindMyWay() + + function pathParam (req, res, params) { + t.assert.deepEqual(params, this.expect, 'path param') + t.assert.deepEqual(pathParam, this.handler, 'match handler') + } + function regexPathParam (req, res, params) { + t.assert.deepEqual(params, this.expect, 'regex param') + t.assert.deepEqual(regexPathParam, this.handler, 'match handler') + } + function staticEncoded (req, res, params) { + t.assert.deepEqual(params, this.expect, 'static match') + t.assert.deepEqual(staticEncoded, this.handler, 'match handler') + } + + findMyWay.on('GET', '/:pathParam', pathParam) + findMyWay.on('GET', '/reg/:regExeParam(^\\d+) .png', regexPathParam) + findMyWay.on('GET', '/[...]/a%2520.html', staticEncoded) + + findMyWay.lookup(get('/%5B...%5D/a%252520.html'), null, { expect: {}, handler: staticEncoded }) + findMyWay.lookup(get('/[...].html'), null, { expect: { pathParam: '[...].html' }, handler: pathParam }) + findMyWay.lookup(get('/reg/123 .png'), null, { expect: { regExeParam: '123' }, handler: regexPathParam }) + findMyWay.lookup(get('/reg%2F123 .png'), null, { expect: { pathParam: 'reg/123 .png' }, handler: pathParam }) // en encoded / is considered a parameter + findMyWay.lookup(get('/reg/123%20.png'), null, { expect: { regExeParam: '123' }, handler: regexPathParam }) +}) + +test('Multi parametric route with encoded colon separator', t => { + t.plan(1) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.fail('Should not be defaultRoute') + } + }) + + findMyWay.on('GET', '/:param(.*)::suffix', (req, res, params) => { + t.assert.equal(params.param, 'foo-bar') + }) + + findMyWay.lookup({ method: 'GET', url: '/foo-bar%3Asuffix', headers: {} }, null) +}) + +function get (url) { + return { method: 'GET', url, headers: {} } +} + +// http://localhost:3000/parameter with / in it +// http://localhost:3000/parameter%20with%20%2F%20in%20it + +// http://localhost:3000/parameter with %252F in it diff --git a/services/slides/node_modules/find-my-way/test/issue-221.test.js b/services/slides/node_modules/find-my-way/test/issue-221.test.js new file mode 100644 index 0000000000000000000000000000000000000000..13d324f6b984e67df771cb657401b0146231846b --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/issue-221.test.js @@ -0,0 +1,50 @@ +'use strict' + +const { test } = require('node:test') +const FindMyWay = require('..') + +test('Should return correct param after switching from static route', t => { + t.plan(1) + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/prefix-:id', () => {}) + findMyWay.on('GET', '/prefix-111', () => {}) + + t.assert.deepEqual(findMyWay.find('GET', '/prefix-1111').params, { id: '1111' }) +}) + +test('Should return correct param after switching from static route', t => { + t.plan(1) + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/prefix-111', () => {}) + findMyWay.on('GET', '/prefix-:id/hello', () => {}) + + t.assert.deepEqual(findMyWay.find('GET', '/prefix-1111/hello').params, { id: '1111' }) +}) + +test('Should return correct param after switching from parametric route', t => { + t.plan(1) + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/prefix-111', () => {}) + findMyWay.on('GET', '/prefix-:id/hello', () => {}) + findMyWay.on('GET', '/:id', () => {}) + + t.assert.deepEqual(findMyWay.find('GET', '/prefix-1111-hello').params, { id: 'prefix-1111-hello' }) +}) + +test('Should return correct params after switching from parametric route', t => { + t.plan(1) + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/test/:param1/test/:param2/prefix-111', () => {}) + findMyWay.on('GET', '/test/:param1/test/:param2/prefix-:id/hello', () => {}) + findMyWay.on('GET', '/test/:param1/test/:param2/:id', () => {}) + + t.assert.deepEqual(findMyWay.find('GET', '/test/value1/test/value2/prefix-1111-hello').params, { + param1: 'value1', + param2: 'value2', + id: 'prefix-1111-hello' + }) +}) diff --git a/services/slides/node_modules/find-my-way/test/issue-234.test.js b/services/slides/node_modules/find-my-way/test/issue-234.test.js new file mode 100644 index 0000000000000000000000000000000000000000..1406b51149d1d99b7d6fd7c13b36f549571a4795 --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/issue-234.test.js @@ -0,0 +1,94 @@ +'use strict' + +const { test } = require('node:test') +const FindMyWay = require('..') + +test('Match static url without encoding option', t => { + t.plan(2) + + const findMyWay = FindMyWay() + + const handler = () => {} + + findMyWay.on('GET', '/🍌', handler) + + t.assert.deepEqual(findMyWay.find('GET', '/🍌').handler, handler) + t.assert.deepEqual(findMyWay.find('GET', '/%F0%9F%8D%8C').handler, handler) +}) + +test('Match parametric url with encoding option', t => { + t.plan(2) + + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/🍌/:param', () => {}) + + t.assert.deepEqual(findMyWay.find('GET', '/🍌/@').params, { param: '@' }) + t.assert.deepEqual(findMyWay.find('GET', '/%F0%9F%8D%8C/@').params, { param: '@' }) +}) + +test('Match encoded parametric url with encoding option', t => { + t.plan(2) + + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/🍌/:param', () => {}) + + t.assert.deepEqual(findMyWay.find('GET', '/🍌/%23').params, { param: '#' }) + t.assert.deepEqual(findMyWay.find('GET', '/%F0%9F%8D%8C/%23').params, { param: '#' }) +}) + +test('Decode url components', t => { + t.plan(3) + + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/:param1/:param2', () => {}) + + t.assert.deepEqual(findMyWay.find('GET', '/foo%23bar/foo%23bar').params, { param1: 'foo#bar', param2: 'foo#bar' }) + t.assert.deepEqual(findMyWay.find('GET', '/%F0%9F%8D%8C/%F0%9F%8D%8C').params, { param1: '🍌', param2: '🍌' }) + t.assert.deepEqual(findMyWay.find('GET', '/%F0%9F%8D%8C/foo%23bar').params, { param1: '🍌', param2: 'foo#bar' }) +}) + +test('Decode url components', t => { + t.plan(5) + + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/foo🍌bar/:param1/:param2', () => {}) + findMyWay.on('GET', '/user/:id', () => {}) + + t.assert.deepEqual(findMyWay.find('GET', '/foo%F0%9F%8D%8Cbar/foo%23bar/foo%23bar').params, { param1: 'foo#bar', param2: 'foo#bar' }) + t.assert.deepEqual(findMyWay.find('GET', '/user/maintainer+tomas').params, { id: 'maintainer+tomas' }) + t.assert.deepEqual(findMyWay.find('GET', '/user/maintainer%2Btomas').params, { id: 'maintainer+tomas' }) + t.assert.deepEqual(findMyWay.find('GET', '/user/maintainer%20tomas').params, { id: 'maintainer tomas' }) + t.assert.deepEqual(findMyWay.find('GET', '/user/maintainer%252Btomas').params, { id: 'maintainer%2Btomas' }) +}) + +test('Decode url components', t => { + t.plan(18) + + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/:param1', () => {}) + t.assert.deepEqual(findMyWay.find('GET', '/foo%23bar').params, { param1: 'foo#bar' }) + t.assert.deepEqual(findMyWay.find('GET', '/foo%24bar').params, { param1: 'foo$bar' }) + t.assert.deepEqual(findMyWay.find('GET', '/foo%26bar').params, { param1: 'foo&bar' }) + t.assert.deepEqual(findMyWay.find('GET', '/foo%2bbar').params, { param1: 'foo+bar' }) + t.assert.deepEqual(findMyWay.find('GET', '/foo%2Bbar').params, { param1: 'foo+bar' }) + t.assert.deepEqual(findMyWay.find('GET', '/foo%2cbar').params, { param1: 'foo,bar' }) + t.assert.deepEqual(findMyWay.find('GET', '/foo%2Cbar').params, { param1: 'foo,bar' }) + t.assert.deepEqual(findMyWay.find('GET', '/foo%2fbar').params, { param1: 'foo/bar' }) + t.assert.deepEqual(findMyWay.find('GET', '/foo%2Fbar').params, { param1: 'foo/bar' }) + + t.assert.deepEqual(findMyWay.find('GET', '/foo%3abar').params, { param1: 'foo:bar' }) + t.assert.deepEqual(findMyWay.find('GET', '/foo%3Abar').params, { param1: 'foo:bar' }) + t.assert.deepEqual(findMyWay.find('GET', '/foo%3bbar').params, { param1: 'foo;bar' }) + t.assert.deepEqual(findMyWay.find('GET', '/foo%3Bbar').params, { param1: 'foo;bar' }) + t.assert.deepEqual(findMyWay.find('GET', '/foo%3dbar').params, { param1: 'foo=bar' }) + t.assert.deepEqual(findMyWay.find('GET', '/foo%3Dbar').params, { param1: 'foo=bar' }) + t.assert.deepEqual(findMyWay.find('GET', '/foo%3fbar').params, { param1: 'foo?bar' }) + t.assert.deepEqual(findMyWay.find('GET', '/foo%3Fbar').params, { param1: 'foo?bar' }) + + t.assert.deepEqual(findMyWay.find('GET', '/foo%40bar').params, { param1: 'foo@bar' }) +}) diff --git a/services/slides/node_modules/find-my-way/test/issue-238.test.js b/services/slides/node_modules/find-my-way/test/issue-238.test.js new file mode 100644 index 0000000000000000000000000000000000000000..4442323524e849e3983b6a6900be90e5c3c256ac --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/issue-238.test.js @@ -0,0 +1,119 @@ +'use strict' + +const { test } = require('node:test') +const FindMyWay = require('../') + +test('Multi-parametric tricky path', t => { + t.plan(6) + const findMyWay = FindMyWay({ + defaultRoute: () => t.assert.fail('Should not be defaultRoute') + }) + + findMyWay.on('GET', '/:param1-static-:param2', () => {}) + + t.assert.deepEqual( + findMyWay.find('GET', '/param1-static-param2', {}).params, + { param1: 'param1', param2: 'param2' } + ) + t.assert.deepEqual( + findMyWay.find('GET', '/param1.1-param1.2-static-param2.1-param2.2', {}).params, + { param1: 'param1.1-param1.2', param2: 'param2.1-param2.2' } + ) + t.assert.deepEqual( + findMyWay.find('GET', '/param1-1-param1-2-static-param2-1-param2-2', {}).params, + { param1: 'param1-1-param1-2', param2: 'param2-1-param2-2' } + ) + t.assert.deepEqual( + findMyWay.find('GET', '/static-static-static', {}).params, + { param1: 'static', param2: 'static' } + ) + t.assert.deepEqual( + findMyWay.find('GET', '/static-static-static-static', {}).params, + { param1: 'static', param2: 'static-static' } + ) + t.assert.deepEqual( + findMyWay.find('GET', '/static-static1-static-static', {}).params, + { param1: 'static-static1', param2: 'static' } + ) +}) + +test('Multi-parametric nodes with different static ending 1', t => { + t.plan(4) + const findMyWay = FindMyWay({ + defaultRoute: () => t.assert.fail('Should not be defaultRoute') + }) + + const paramHandler = () => {} + const multiParamHandler = () => {} + + findMyWay.on('GET', '/v1/foo/:code', paramHandler) + findMyWay.on('GET', '/v1/foo/:code.png', multiParamHandler) + + t.assert.deepEqual(findMyWay.find('GET', '/v1/foo/hello', {}).handler, paramHandler) + t.assert.deepEqual(findMyWay.find('GET', '/v1/foo/hello', {}).params, { code: 'hello' }) + + t.assert.deepEqual(findMyWay.find('GET', '/v1/foo/hello.png', {}).handler, multiParamHandler) + t.assert.deepEqual(findMyWay.find('GET', '/v1/foo/hello.png', {}).params, { code: 'hello' }) +}) + +test('Multi-parametric nodes with different static ending 2', t => { + t.plan(4) + const findMyWay = FindMyWay({ + defaultRoute: () => t.assert.fail('Should not be defaultRoute') + }) + + const jpgHandler = () => {} + const pngHandler = () => {} + + findMyWay.on('GET', '/v1/foo/:code.jpg', jpgHandler) + findMyWay.on('GET', '/v1/foo/:code.png', pngHandler) + + t.assert.deepEqual(findMyWay.find('GET', '/v1/foo/hello.jpg', {}).handler, jpgHandler) + t.assert.deepEqual(findMyWay.find('GET', '/v1/foo/hello.jpg', {}).params, { code: 'hello' }) + + t.assert.deepEqual(findMyWay.find('GET', '/v1/foo/hello.png', {}).handler, pngHandler) + t.assert.deepEqual(findMyWay.find('GET', '/v1/foo/hello.png', {}).params, { code: 'hello' }) +}) + +test('Multi-parametric nodes with different static ending 3', t => { + t.plan(4) + const findMyWay = FindMyWay({ + defaultRoute: () => t.assert.fail('Should not be defaultRoute') + }) + + const jpgHandler = () => {} + const pngHandler = () => {} + + findMyWay.on('GET', '/v1/foo/:code.jpg/bar', jpgHandler) + findMyWay.on('GET', '/v1/foo/:code.png/bar', pngHandler) + + t.assert.deepEqual(findMyWay.find('GET', '/v1/foo/hello.jpg/bar', {}).handler, jpgHandler) + t.assert.deepEqual(findMyWay.find('GET', '/v1/foo/hello.jpg/bar', {}).params, { code: 'hello' }) + + t.assert.deepEqual(findMyWay.find('GET', '/v1/foo/hello.png/bar', {}).handler, pngHandler) + t.assert.deepEqual(findMyWay.find('GET', '/v1/foo/hello.png/bar', {}).params, { code: 'hello' }) +}) + +test('Multi-parametric nodes with different static ending 4', t => { + t.plan(6) + const findMyWay = FindMyWay({ + defaultRoute: () => t.assert.fail('Should not be defaultRoute') + }) + + const handler = () => {} + const jpgHandler = () => {} + const pngHandler = () => {} + + findMyWay.on('GET', '/v1/foo/:code/bar', handler) + findMyWay.on('GET', '/v1/foo/:code.jpg/bar', jpgHandler) + findMyWay.on('GET', '/v1/foo/:code.png/bar', pngHandler) + + t.assert.deepEqual(findMyWay.find('GET', '/v1/foo/hello/bar', {}).handler, handler) + t.assert.deepEqual(findMyWay.find('GET', '/v1/foo/hello/bar', {}).params, { code: 'hello' }) + + t.assert.deepEqual(findMyWay.find('GET', '/v1/foo/hello.jpg/bar', {}).handler, jpgHandler) + t.assert.deepEqual(findMyWay.find('GET', '/v1/foo/hello.jpg/bar', {}).params, { code: 'hello' }) + + t.assert.deepEqual(findMyWay.find('GET', '/v1/foo/hello.png/bar', {}).handler, pngHandler) + t.assert.deepEqual(findMyWay.find('GET', '/v1/foo/hello.png/bar', {}).params, { code: 'hello' }) +}) diff --git a/services/slides/node_modules/find-my-way/test/issue-240.test.js b/services/slides/node_modules/find-my-way/test/issue-240.test.js new file mode 100644 index 0000000000000000000000000000000000000000..ada2579df0a92fb0c632a9f318419e66d2b2925c --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/issue-240.test.js @@ -0,0 +1,30 @@ +'use strict' + +const { test } = require('node:test') +const FindMyWay = require('../') + +test('issue-240: .find matching', (t) => { + t.plan(14) + + const findMyWay = FindMyWay({ ignoreDuplicateSlashes: true }) + + const fixedPath = function staticPath () {} + const varPath = function parameterPath () {} + findMyWay.on('GET', '/a/b', fixedPath) + findMyWay.on('GET', '/a/:pam/c', varPath) + + t.assert.equal(findMyWay.find('GET', '/a/b').handler, fixedPath) + t.assert.equal(findMyWay.find('GET', '/a//b').handler, fixedPath) + t.assert.equal(findMyWay.find('GET', '/a/b/c').handler, varPath) + t.assert.equal(findMyWay.find('GET', '/a//b/c').handler, varPath) + t.assert.equal(findMyWay.find('GET', '/a///b/c').handler, varPath) + t.assert.equal(findMyWay.find('GET', '/a//b//c').handler, varPath) + t.assert.equal(findMyWay.find('GET', '/a///b///c').handler, varPath) + t.assert.equal(findMyWay.find('GET', '/a/foo/c').handler, varPath) + t.assert.equal(findMyWay.find('GET', '/a//foo/c').handler, varPath) + t.assert.equal(findMyWay.find('GET', '/a///foo/c').handler, varPath) + t.assert.equal(findMyWay.find('GET', '/a//foo//c').handler, varPath) + t.assert.equal(findMyWay.find('GET', '/a///foo///c').handler, varPath) + t.assert.ok(!findMyWay.find('GET', '/a/c')) + t.assert.ok(!findMyWay.find('GET', '/a//c')) +}) diff --git a/services/slides/node_modules/find-my-way/test/issue-241.test.js b/services/slides/node_modules/find-my-way/test/issue-241.test.js new file mode 100644 index 0000000000000000000000000000000000000000..39b007a0a92c2012e633923e78232f61a2ca81a6 --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/issue-241.test.js @@ -0,0 +1,32 @@ +'use strict' + +const { test } = require('node:test') +const FindMyWay = require('..') + +test('Double colon and parametric children', t => { + t.plan(2) + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/::articles', () => {}) + findMyWay.on('GET', '/:article_name', () => {}) + + t.assert.deepEqual(findMyWay.find('GET', '/:articles').params, {}) + t.assert.deepEqual(findMyWay.find('GET', '/articles_param').params, { article_name: 'articles_param' }) +}) + +test('Double colon and parametric children', t => { + t.plan(2) + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/::test::foo/:param/::articles', () => {}) + findMyWay.on('GET', '/::test::foo/:param/:article_name', () => {}) + + t.assert.deepEqual( + findMyWay.find('GET', '/:test:foo/param_value1/:articles').params, + { param: 'param_value1' } + ) + t.assert.deepEqual( + findMyWay.find('GET', '/:test:foo/param_value2/articles_param').params, + { param: 'param_value2', article_name: 'articles_param' } + ) +}) diff --git a/services/slides/node_modules/find-my-way/test/issue-247.test.js b/services/slides/node_modules/find-my-way/test/issue-247.test.js new file mode 100644 index 0000000000000000000000000000000000000000..e5a99f03049664cdfffcd4900ccacdb686c4d863 --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/issue-247.test.js @@ -0,0 +1,51 @@ +'use strict' + +const { test } = require('node:test') +const FindMyWay = require('..') + +test('If there are constraints param, router.off method support filter', t => { + t.plan(12) + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/a', { constraints: { host: '1' } }, () => {}, { name: 1 }) + findMyWay.on('GET', '/a', { constraints: { host: '2', version: '1.0.0' } }, () => {}, { name: 2 }) + findMyWay.on('GET', '/a', { constraints: { host: '2', version: '2.0.0' } }, () => {}, { name: 3 }) + + t.assert.deepEqual(findMyWay.find('GET', '/a', { host: '1' }).store, { name: 1 }) + t.assert.deepEqual(findMyWay.find('GET', '/a', { host: '2', version: '1.0.0' }).store, { name: 2 }) + t.assert.deepEqual(findMyWay.find('GET', '/a', { host: '2', version: '2.0.0' }).store, { name: 3 }) + + findMyWay.off('GET', '/a', { host: '1' }) + + t.assert.deepEqual(findMyWay.find('GET', '/a', { host: '1' }), null) + t.assert.deepEqual(findMyWay.find('GET', '/a', { host: '2', version: '1.0.0' }).store, { name: 2 }) + t.assert.deepEqual(findMyWay.find('GET', '/a', { host: '2', version: '2.0.0' }).store, { name: 3 }) + + findMyWay.off('GET', '/a', { host: '2', version: '1.0.0' }) + + t.assert.deepEqual(findMyWay.find('GET', '/a', { host: '1' }), null) + t.assert.deepEqual(findMyWay.find('GET', '/a', { host: '2', version: '1.0.0' }), null) + t.assert.deepEqual(findMyWay.find('GET', '/a', { host: '2', version: '2.0.0' }).store, { name: 3 }) + + findMyWay.off('GET', '/a', { host: '2', version: '2.0.0' }) + + t.assert.deepEqual(findMyWay.find('GET', '/a', { host: '1' }), null) + t.assert.deepEqual(findMyWay.find('GET', '/a', { host: '2', version: '1.0.0' }), null) + t.assert.deepEqual(findMyWay.find('GET', '/a', { host: '2', version: '2.0.0' }), null) +}) + +test('If there are no constraints param, router.off method remove all matched router', t => { + t.plan(4) + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/a', { constraints: { host: '1' } }, () => {}, { name: 1 }) + findMyWay.on('GET', '/a', { constraints: { host: '2' } }, () => {}, { name: 2 }) + + t.assert.deepEqual(findMyWay.find('GET', '/a', { host: '1' }).store, { name: 1 }) + t.assert.deepEqual(findMyWay.find('GET', '/a', { host: '2' }).store, { name: 2 }) + + findMyWay.off('GET', '/a') + + t.assert.deepEqual(findMyWay.find('GET', '/a', { host: '1' }), null) + t.assert.deepEqual(findMyWay.find('GET', '/a', { host: '2' }), null) +}) diff --git a/services/slides/node_modules/find-my-way/test/issue-254.test.js b/services/slides/node_modules/find-my-way/test/issue-254.test.js new file mode 100644 index 0000000000000000000000000000000000000000..9e6b46260dd93a6451193ea58ab04b6285464e9c --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/issue-254.test.js @@ -0,0 +1,31 @@ +'use strict' + +const { test } = require('node:test') +const FindMyWay = require('..') + +test('Constraints should not be overrided when multiple router is created', t => { + t.plan(1) + + const constraint = { + name: 'secret', + storage: function () { + const secrets = {} + return { + get: (secret) => { return secrets[secret] || null }, + set: (secret, store) => { secrets[secret] = store } + } + }, + deriveConstraint: (req, ctx) => { + return req.headers['x-secret'] + }, + validate () { return true } + } + + const router1 = FindMyWay({ constraints: { secret: constraint } }) + FindMyWay() + + router1.on('GET', '/', { constraints: { secret: 'alpha' } }, () => {}) + router1.find('GET', '/', { secret: 'alpha' }) + + t.assert.ok('constraints is not overrided') +}) diff --git a/services/slides/node_modules/find-my-way/test/issue-28.test.js b/services/slides/node_modules/find-my-way/test/issue-28.test.js new file mode 100644 index 0000000000000000000000000000000000000000..98848be8c3d81a6c2389dba2c2ca75d2b17bcda1 --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/issue-28.test.js @@ -0,0 +1,618 @@ +'use strict' + +const { test } = require('node:test') +const FindMyWay = require('../') + +test('wildcard (more complex test)', t => { + t.plan(3) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.fail('we should not be here, the url is: ' + req.url) + } + }) + + findMyWay.on('GET', '/test/*', (req, res, params) => { + switch (params['*']) { + case 'hello': + t.assert.ok('correct parameter') + break + case 'hello/world': + t.assert.ok('correct parameter') + break + case '': + t.assert.ok('correct parameter') + break + default: + t.assert.fail('wrong parameter: ' + params['*']) + } + }) + + findMyWay.lookup( + { method: 'GET', url: '/test/hello', headers: {} }, + null + ) + + findMyWay.lookup( + { method: 'GET', url: '/test/hello/world', headers: {} }, + null + ) + + findMyWay.lookup( + { method: 'GET', url: '/test/', headers: {} }, + null + ) +}) + +test('Wildcard inside a node with a static route but different method', t => { + t.plan(2) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.fail('we should not be here, the url is: ' + req.url) + } + }) + + findMyWay.on('GET', '/test/hello', (req, res, params) => { + t.assert.equal(req.method, 'GET') + }) + + findMyWay.on('OPTIONS', '/*', (req, res, params) => { + t.assert.equal(req.method, 'OPTIONS') + }) + + findMyWay.lookup( + { method: 'GET', url: '/test/hello', headers: {} }, + null + ) + + findMyWay.lookup( + { method: 'OPTIONS', url: '/test/hello', headers: {} }, + null + ) +}) + +test('Wildcard inside a node with a static route but different method (more complex case)', t => { + t.plan(5) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + if (req.url === '/test/helloo' && req.method === 'GET') { + t.assert.ok('Everything fine') + } else { + t.assert.fail('we should not be here, the url is: ' + req.url) + } + } + }) + + findMyWay.on('GET', '/test/hello', (req, res, params) => { + t.assert.equal(req.method, 'GET') + }) + + findMyWay.on('OPTIONS', '/*', (req, res, params) => { + t.assert.equal(req.method, 'OPTIONS') + }) + + findMyWay.lookup( + { method: 'GET', url: '/test/hello', headers: {} }, + null + ) + + findMyWay.lookup( + { method: 'GET', url: '/test/helloo', headers: {} }, + null + ) + + findMyWay.lookup( + { method: 'OPTIONS', url: '/test/', headers: {} }, + null + ) + + findMyWay.lookup( + { method: 'OPTIONS', url: '/test', headers: {} }, + null + ) + + findMyWay.lookup( + { method: 'OPTIONS', url: '/test/helloo', headers: {} }, + null + ) +}) + +test('Wildcard edge cases', t => { + t.plan(1) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.fail('we should not be here, the url is: ' + req.url) + } + }) + + findMyWay.on('GET', '/test1/foo', (req, res, params) => { + t.assert.fail('we should not be here, the url is: ' + req.url) + }) + + findMyWay.on('GET', '/test2/foo', (req, res, params) => { + t.assert.fail('we should not be here, the url is: ' + req.url) + }) + + findMyWay.on('OPTIONS', '/*', (req, res, params) => { + t.assert.equal(params['*'], 'test1/foo') + }) + + findMyWay.lookup( + { method: 'OPTIONS', url: '/test1/foo', headers: {} }, + null + ) +}) + +test('Wildcard edge cases same method', t => { + t.plan(2) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.fail('we should not be here, the url is: ' + req.url) + } + }) + + findMyWay.on('OPTIONS', '/test1/foo', (req, res, params) => { + t.assert.equal(req.method, 'OPTIONS') + }) + + findMyWay.on('OPTIONS', '/test2/foo', (req, res, params) => { + t.assert.fail('we should not be here, the url is: ' + req.url) + }) + + findMyWay.on('OPTIONS', '/*', (req, res, params) => { + t.assert.equal(params['*'], 'test/foo') + }) + + findMyWay.lookup( + { method: 'OPTIONS', url: '/test1/foo', headers: {} }, + null + ) + + findMyWay.lookup( + { method: 'OPTIONS', url: '/test/foo', headers: {} }, + null + ) +}) + +test('Wildcard and parametric edge cases', t => { + t.plan(3) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.fail('we should not be here, the url is: ' + req.url) + } + }) + + findMyWay.on('OPTIONS', '/test1/foo', (req, res, params) => { + t.assert.equal(req.method, 'OPTIONS') + }) + + findMyWay.on('OPTIONS', '/test2/foo', (req, res, params) => { + t.assert.fail('we should not be here, the url is: ' + req.url) + }) + + findMyWay.on('GET', '/:test/foo', (req, res, params) => { + t.assert.equal(params.test, 'example') + }) + + findMyWay.on('OPTIONS', '/*', (req, res, params) => { + t.assert.equal(params['*'], 'test/foo/hey') + }) + + findMyWay.lookup( + { method: 'OPTIONS', url: '/test1/foo', headers: {} }, + null + ) + + findMyWay.lookup( + { method: 'OPTIONS', url: '/test/foo/hey', headers: {} }, + null + ) + + findMyWay.lookup( + { method: 'GET', url: '/example/foo', headers: {} }, + null + ) +}) + +test('Mixed wildcard and static with same method', t => { + t.plan(1) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.fail('we should not be here, the url is: ' + req.url) + } + }) + + findMyWay.on('GET', '/foo1/bar1/baz', (req, res, params) => { + t.assert.fail('we should not be here, the url is: ' + req.url) + }) + + findMyWay.on('GET', '/foo1/bar2/baz', (req, res, params) => { + t.assert.fail('we should not be here, the url is: ' + req.url) + }) + + findMyWay.on('GET', '/foo2/bar2/baz', (req, res, params) => { + t.assert.fail('we should not be here, the url is: ' + req.url) + }) + + findMyWay.on('GET', '*', (req, res, params) => { + t.assert.equal(params['*'], '/foo1/bar1/kuux') + }) + + findMyWay.lookup( + { method: 'GET', url: '/foo1/bar1/kuux', headers: {} }, + null + ) +}) + +test('Nested wildcards case - 1', t => { + t.plan(1) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.fail('we should not be here, the url is: ' + req.url) + } + }) + + findMyWay.on('GET', '*', (req, res, params) => { + t.assert.fail('we should not be here, the url is: ' + req.url) + }) + + findMyWay.on('GET', '/foo1/*', (req, res, params) => { + t.assert.equal(params['*'], 'bar1/kuux') + }) + + findMyWay.on('GET', '/foo2/*', (req, res, params) => { + t.assert.fail('we should not be here, the url is: ' + req.url) + }) + + findMyWay.lookup( + { method: 'GET', url: '/foo1/bar1/kuux', headers: {} }, + null + ) +}) + +test('Nested wildcards case - 2', t => { + t.plan(1) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.fail('we should not be here, the url is: ' + req.url) + } + }) + + findMyWay.on('GET', '/foo2/*', (req, res, params) => { + t.assert.fail('we should not be here, the url is: ' + req.url) + }) + + findMyWay.on('GET', '/foo1/*', (req, res, params) => { + t.assert.equal(params['*'], 'bar1/kuux') + }) + + findMyWay.on('GET', '*', (req, res, params) => { + t.assert.fail('we should not be here, the url is: ' + req.url) + }) + + findMyWay.lookup( + { method: 'GET', url: '/foo1/bar1/kuux', headers: {} }, + null + ) +}) + +test('Nested wildcards with parametric and static - 1', t => { + t.plan(1) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.fail('we should not be here, the url is: ' + req.url) + } + }) + + findMyWay.on('GET', '*', (req, res, params) => { + t.assert.fail('we should not be here, the url is: ' + req.url) + }) + + findMyWay.on('GET', '/foo1/*', (req, res, params) => { + t.assert.equal(params['*'], 'bar1/kuux') + }) + + findMyWay.on('GET', '/foo2/*', (req, res, params) => { + t.assert.fail('we should not be here, the url is: ' + req.url) + }) + + findMyWay.on('GET', '/foo3/:param', (req, res, params) => { + t.assert.fail('we should not be here, the url is: ' + req.url) + }) + + findMyWay.on('GET', '/foo4/param', (req, res, params) => { + t.assert.fail('we should not be here, the url is: ' + req.url) + }) + + findMyWay.lookup( + { method: 'GET', url: '/foo1/bar1/kuux', headers: {} }, + null + ) +}) + +test('Nested wildcards with parametric and static - 2', t => { + t.plan(1) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.fail('we should not be here, the url is: ' + req.url) + } + }) + + findMyWay.on('GET', '*', (req, res, params) => { + t.assert.fail('we should not be here, the url is: ' + req.url) + }) + + findMyWay.on('GET', '/foo1/*', (req, res, params) => { + t.assert.fail('we should not be here, the url is: ' + req.url) + }) + + findMyWay.on('GET', '/foo2/*', (req, res, params) => { + t.assert.fail('we should not be here, the url is: ' + req.url) + }) + + findMyWay.on('GET', '/foo3/:param', (req, res, params) => { + t.assert.equal(params.param, 'bar1') + }) + + findMyWay.on('GET', '/foo4/param', (req, res, params) => { + t.assert.fail('we should not be here, the url is: ' + req.url) + }) + + findMyWay.lookup( + { method: 'GET', url: '/foo3/bar1', headers: {} }, + null + ) +}) + +test('Nested wildcards with parametric and static - 3', t => { + t.plan(1) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.fail('we should not be here, the url is: ' + req.url) + } + }) + + findMyWay.on('GET', '*', (req, res, params) => { + t.assert.fail('we should not be here, the url is: ' + req.url) + }) + + findMyWay.on('GET', '/foo1/*', (req, res, params) => { + t.assert.fail('we should not be here, the url is: ' + req.url) + }) + + findMyWay.on('GET', '/foo2/*', (req, res, params) => { + t.assert.fail('we should not be here, the url is: ' + req.url) + }) + + findMyWay.on('GET', '/foo3/:param', (req, res, params) => { + t.assert.fail('we should not be here, the url is: ' + req.url) + }) + + findMyWay.on('GET', '/foo4/param', (req, res, params) => { + t.assert.equal(req.url, '/foo4/param') + }) + + findMyWay.lookup( + { method: 'GET', url: '/foo4/param', headers: {} }, + null + ) +}) + +test('Nested wildcards with parametric and static - 4', t => { + t.plan(1) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.fail('we should not be here, the url is: ' + req.url) + } + }) + + findMyWay.on('GET', '*', (req, res, params) => { + t.assert.fail('we should not be here, the url is: ' + req.url) + }) + + findMyWay.on('GET', '/foo1/*', (req, res, params) => { + t.assert.fail('we should not be here, the url is: ' + req.url) + }) + + findMyWay.on('GET', '/foo2/*', (req, res, params) => { + t.assert.fail('we should not be here, the url is: ' + req.url) + }) + + findMyWay.on('GET', '/foo3/:param', (req, res, params) => { + t.assert.fail('we should not be here, the url is: ' + req.url) + }) + + findMyWay.on('GET', '/foo1/param', (req, res, params) => { + t.assert.equal(req.url, '/foo1/param') + }) + + findMyWay.lookup( + { method: 'GET', url: '/foo1/param', headers: {} }, + null + ) +}) + +test('Nested wildcards with parametric and static - 5', t => { + t.plan(1) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.fail('we should not be here, the url is: ' + req.url) + } + }) + + findMyWay.on('GET', '*', (req, res, params) => { + t.assert.fail('we should not be here, the url is: ' + req.url) + }) + + findMyWay.on('GET', '/foo1/*', (req, res, params) => { + t.assert.equal(params['*'], 'param/hello/test/long/routee') + }) + + findMyWay.on('GET', '/foo2/*', (req, res, params) => { + t.assert.fail('we should not be here, the url is: ' + req.url) + }) + + findMyWay.on('GET', '/foo3/:param', (req, res, params) => { + t.assert.fail('we should not be here, the url is: ' + req.url) + }) + + findMyWay.on('GET', '/foo1/param/hello/test/long/route', (req, res, params) => { + t.assert.fail('we should not be here, the url is: ' + req.url) + }) + + findMyWay.lookup( + { method: 'GET', url: '/foo1/param/hello/test/long/routee', headers: {} }, + null + ) +}) + +test('Nested wildcards with parametric and static - 6', t => { + t.plan(1) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.fail('we should not be here, the url is: ' + req.url) + } + }) + + findMyWay.on('GET', '*', (req, res, params) => { + t.assert.equal(params['*'], '/foo4/param/hello/test/long/routee') + }) + + findMyWay.on('GET', '/foo1/*', (req, res, params) => { + t.assert.fail('we should not be here, the url is: ' + req.url) + }) + + findMyWay.on('GET', '/foo2/*', (req, res, params) => { + t.assert.fail('we should not be here, the url is: ' + req.url) + }) + + findMyWay.on('GET', '/foo3/:param', (req, res, params) => { + t.assert.fail('we should not be here, the url is: ' + req.url) + }) + + findMyWay.on('GET', '/foo4/param/hello/test/long/route', (req, res, params) => { + t.assert.fail('we should not be here, the url is: ' + req.url) + }) + + findMyWay.lookup( + { method: 'GET', url: '/foo4/param/hello/test/long/routee', headers: {} }, + null + ) +}) + +test('Nested wildcards with parametric and static - 7', t => { + t.plan(1) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.fail('we should not be here, the url is: ' + req.url) + } + }) + + findMyWay.on('GET', '*', (req, res, params) => { + t.assert.fail('we should not be here, the url is: ' + req.url) + }) + + findMyWay.on('GET', '/foo1/*', (req, res, params) => { + t.assert.fail('we should not be here, the url is: ' + req.url) + }) + + findMyWay.on('GET', '/foo2/*', (req, res, params) => { + t.assert.fail('we should not be here, the url is: ' + req.url) + }) + + findMyWay.on('GET', '/foo3/:param', (req, res, params) => { + t.assert.equal(params.param, 'hello') + }) + + findMyWay.on('GET', '/foo3/*', (req, res, params) => { + t.assert.fail('we should not be here, the url is: ' + req.url) + }) + + findMyWay.on('GET', '/foo4/example/hello/test/long/route', (req, res, params) => { + t.assert.fail('we should not be here, the url is: ' + req.url) + }) + + findMyWay.lookup( + { method: 'GET', url: '/foo3/hello', headers: {} }, + null + ) +}) + +test('Nested wildcards with parametric and static - 8', t => { + t.plan(1) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.fail('we should not be here, the url is: ' + req.url) + } + }) + + findMyWay.on('GET', '*', (req, res, params) => { + t.assert.fail('we should not be here, the url is: ' + req.url) + }) + + findMyWay.on('GET', '/foo1/*', (req, res, params) => { + t.assert.fail('we should not be here, the url is: ' + req.url) + }) + + findMyWay.on('GET', '/foo2/*', (req, res, params) => { + t.assert.fail('we should not be here, the url is: ' + req.url) + }) + + findMyWay.on('GET', '/foo3/:param', (req, res, params) => { + t.assert.fail('we should not be here, the url is: ' + req.url) + }) + + findMyWay.on('GET', '/foo3/*', (req, res, params) => { + t.assert.equal(params['*'], 'hello/world') + }) + + findMyWay.on('GET', '/foo4/param/hello/test/long/route', (req, res, params) => { + t.assert.fail('we should not be here, the url is: ' + req.url) + }) + + findMyWay.lookup( + { method: 'GET', url: '/foo3/hello/world', headers: {} }, + null + ) +}) + +test('Wildcard node with constraints', t => { + t.plan(1) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.fail('we should not be here, the url is: ' + req.url) + } + }) + + findMyWay.on('GET', '*', { constraints: { host: 'fastify.io' } }, (req, res, params) => { + t.assert.equal(params['*'], '/foo1/foo3') + }) + + findMyWay.on('GET', '/foo1/*', { constraints: { host: 'something-else.io' } }, (req, res, params) => { + t.assert.fail('we should not be here, the url is: ' + req.url) + }) + + findMyWay.on('GET', '/foo1/foo2', (req, res, params) => { + t.assert.fail('we should not be here, the url is: ' + req.url) + }) + + findMyWay.lookup( + { method: 'GET', url: '/foo1/foo3', headers: { host: 'fastify.io' } }, + null + ) +}) + +test('Wildcard must be the last character in the route', (t) => { + t.plan(6) + + const expectedError = new Error('Wildcard must be the last character in the route') + + const findMyWay = FindMyWay() + + t.assert.throws(() => findMyWay.on('GET', '*1', () => {}), expectedError) + t.assert.throws(() => findMyWay.on('GET', '*/', () => {}), expectedError) + t.assert.throws(() => findMyWay.on('GET', '*?', () => {}), expectedError) + + t.assert.throws(() => findMyWay.on('GET', '/foo*123', () => {}), expectedError) + t.assert.throws(() => findMyWay.on('GET', '/foo*?', () => {}), expectedError) + t.assert.throws(() => findMyWay.on('GET', '/foo*/', () => {}), expectedError) +}) diff --git a/services/slides/node_modules/find-my-way/test/issue-280.test.js b/services/slides/node_modules/find-my-way/test/issue-280.test.js new file mode 100644 index 0000000000000000000000000000000000000000..b05fda82b106f61255a374b87957879da23b0263 --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/issue-280.test.js @@ -0,0 +1,14 @@ +'use strict' + +const { test } = require('node:test') +const FindMyWay = require('../') + +test('Wildcard route match when regexp route fails', (t) => { + t.plan(1) + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/:a(a)', () => {}) + findMyWay.on('GET', '/*', () => {}) + + t.assert.deepEqual(findMyWay.find('GET', '/b', {}).params, { '*': 'b' }) +}) diff --git a/services/slides/node_modules/find-my-way/test/issue-285.test.js b/services/slides/node_modules/find-my-way/test/issue-285.test.js new file mode 100644 index 0000000000000000000000000000000000000000..2fa564fdb83b94035f004f674e22729709f75e1c --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/issue-285.test.js @@ -0,0 +1,37 @@ +'use strict' + +const { test } = require('node:test') +const FindMyWay = require('../') + +test('Parametric regex match with similar routes', (t) => { + t.plan(2) + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/:a(a)', () => {}) + findMyWay.on('GET', '/:param/static', () => {}) + + t.assert.deepEqual(findMyWay.find('GET', '/a', {}).params, { a: 'a' }) + t.assert.deepEqual(findMyWay.find('GET', '/param/static', {}).params, { param: 'param' }) +}) + +test('Parametric regex match with similar routes', (t) => { + t.plan(2) + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/:a(a)', () => {}) + findMyWay.on('GET', '/:b(b)/static', () => {}) + + t.assert.deepEqual(findMyWay.find('GET', '/a', {}).params, { a: 'a' }) + t.assert.deepEqual(findMyWay.find('GET', '/b/static', {}).params, { b: 'b' }) +}) + +test('Parametric regex match with similar routes', (t) => { + t.plan(2) + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/:a(a)/static', { constraints: { version: '1.0.0' } }, () => {}) + findMyWay.on('GET', '/:b(b)/static', { constraints: { version: '2.0.0' } }, () => {}) + + t.assert.deepEqual(findMyWay.find('GET', '/a/static', { version: '1.0.0' }).params, { a: 'a' }) + t.assert.deepEqual(findMyWay.find('GET', '/b/static', { version: '2.0.0' }).params, { b: 'b' }) +}) diff --git a/services/slides/node_modules/find-my-way/test/issue-330.test.js b/services/slides/node_modules/find-my-way/test/issue-330.test.js new file mode 100644 index 0000000000000000000000000000000000000000..13b4f605f7a237790b0c754ac5d5b1e38908859d --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/issue-330.test.js @@ -0,0 +1,231 @@ +const { test } = require('node:test') +const FindMyWay = require('..') +const proxyquire = require('proxyquire') +const HandlerStorage = require('../lib/handler-storage') +const Constrainer = require('../lib/constrainer') +const { safeDecodeURIComponent } = require('../lib/url-sanitizer') +const acceptVersionStrategy = require('../lib/strategies/accept-version') +const httpMethodStrategy = require('../lib/strategies/http-method') + +test('FULL_PATH_REGEXP and OPTIONAL_PARAM_REGEXP should be considered safe', (t) => { + t.plan(1) + + t.assert.doesNotThrow(() => require('..')) +}) + +test('should throw an error for unsafe FULL_PATH_REGEXP', (t) => { + t.plan(1) + + t.assert.throws(() => proxyquire('..', { + 'safe-regex2': () => false + }), new Error('the FULL_PATH_REGEXP is not safe, update this module')) +}) + +test('Should throw an error for unsafe OPTIONAL_PARAM_REGEXP', (t) => { + t.plan(1) + + let callCount = 0 + t.assert.throws(() => proxyquire('..', { + 'safe-regex2': () => { + return ++callCount < 2 + } + }), new Error('the OPTIONAL_PARAM_REGEXP is not safe, update this module')) +}) + +test('double colon does not define parametric node', (t) => { + t.plan(2) + + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/::id', () => {}) + const route1 = findMyWay.findRoute('GET', '/::id') + t.assert.deepStrictEqual(route1.params, []) + + findMyWay.on('GET', '/:foo(\\d+)::bar', () => {}) + const route2 = findMyWay.findRoute('GET', '/:foo(\\d+)::bar') + t.assert.deepStrictEqual(route2.params, ['foo']) +}) + +test('case insensitive static routes', (t) => { + t.plan(3) + + const findMyWay = FindMyWay({ + caseSensitive: false + }) + + findMyWay.on('GET', '/foo', () => {}) + findMyWay.on('GET', '/foo/bar', () => {}) + findMyWay.on('GET', '/foo/bar/baz', () => {}) + + t.assert.ok(findMyWay.findRoute('GET', '/FoO')) + t.assert.ok(findMyWay.findRoute('GET', '/FOo/Bar')) + t.assert.ok(findMyWay.findRoute('GET', '/fOo/Bar/bAZ')) +}) + +test('wildcard must be the last character in the route', (t) => { + t.plan(3) + + const expectedError = new Error('Wildcard must be the last character in the route') + + const findMyWay = FindMyWay() + + findMyWay.on('GET', '*', () => {}) + t.assert.throws(() => findMyWay.findRoute('GET', '*1'), expectedError) + t.assert.throws(() => findMyWay.findRoute('GET', '*/'), expectedError) + t.assert.throws(() => findMyWay.findRoute('GET', '*?'), expectedError) +}) + +test('does not find the route if maxParamLength is exceeded', t => { + t.plan(2) + const findMyWay = FindMyWay({ + maxParamLength: 2 + }) + + findMyWay.on('GET', '/:id(\\d+)', () => {}) + + t.assert.equal(findMyWay.find('GET', '/123'), null) + t.assert.ok(findMyWay.find('GET', '/12')) +}) + +test('Should check if a regex is safe to use', (t) => { + t.plan(1) + + const findMyWay = FindMyWay() + + // we must pass a safe regex to register the route + // findRoute will still throws the expected assertion error if we try to access it with unsafe reggex + findMyWay.on('GET', '/test/:id(\\d+)', () => {}) + + const unSafeRegex = /(x+x+)+y/ + t.assert.throws(() => findMyWay.findRoute('GET', `/test/:id(${unSafeRegex.toString()})`), { + message: "The regex '(/(x+x+)+y/)' is not safe!" + }) +}) + +test('Disable safe regex check', (t) => { + t.plan(1) + + const findMyWay = FindMyWay({ allowUnsafeRegex: true }) + + const unSafeRegex = /(x+x+)+y/ + findMyWay.on('GET', `/test2/:id(${unSafeRegex.toString()})`, () => {}) + t.assert.doesNotThrow(() => findMyWay.findRoute('GET', `/test2/:id(${unSafeRegex.toString()})`)) +}) + +test('throws error if no strategy registered for constraint key', (t) => { + t.plan(2) + + const constrainer = new Constrainer() + const error = new Error('No strategy registered for constraint key invalid-constraint') + t.assert.throws(() => constrainer.newStoreForConstraint('invalid-constraint'), error) + t.assert.throws(() => constrainer.validateConstraints({ 'invalid-constraint': 'foo' }), error) +}) + +test('throws error if pass an undefined constraint value', (t) => { + t.plan(1) + + const constrainer = new Constrainer() + const error = new Error('Can\'t pass an undefined constraint value, must pass null or no key at all') + t.assert.throws(() => constrainer.validateConstraints({ key: undefined }), error) +}) + +test('Constrainer.noteUsage', (t) => { + t.plan(3) + + const constrainer = new Constrainer() + t.assert.equal(constrainer.strategiesInUse.size, 0) + + constrainer.noteUsage() + t.assert.equal(constrainer.strategiesInUse.size, 0) + + constrainer.noteUsage({ host: 'fastify.io' }) + t.assert.equal(constrainer.strategiesInUse.size, 1) +}) + +test('Cannot derive constraints without active strategies.', (t) => { + t.plan(1) + + const constrainer = new Constrainer() + const before = constrainer.deriveSyncConstraints + constrainer._buildDeriveConstraints() + t.assert.deepEqual(constrainer.deriveSyncConstraints, before) +}) + +test('getMatchingHandler should return null if not compiled', (t) => { + t.plan(1) + + const handlerStorage = new HandlerStorage() + t.assert.equal(handlerStorage.getMatchingHandler({ foo: 'bar' }), null) +}) + +test('safeDecodeURIComponent should replace %3x to null for every x that is not a valid lowchar', (t) => { + t.plan(1) + + t.assert.equal(safeDecodeURIComponent('Hello%3xWorld'), 'HellonullWorld') +}) + +test('SemVerStore version should be a string', (t) => { + t.plan(1) + + const Storage = acceptVersionStrategy.storage + + t.assert.throws(() => new Storage().set(1), new TypeError('Version should be a string')) +}) + +test('SemVerStore.maxMajor should increase automatically', (t) => { + t.plan(3) + + const Storage = acceptVersionStrategy.storage + const storage = new Storage() + + t.assert.equal(storage.maxMajor, 0) + + storage.set('2') + t.assert.equal(storage.maxMajor, 2) + + storage.set('1') + t.assert.equal(storage.maxMajor, 2) +}) + +test('SemVerStore.maxPatches should increase automatically', (t) => { + t.plan(3) + + const Storage = acceptVersionStrategy.storage + const storage = new Storage() + + storage.set('2.0.0') + t.assert.deepEqual(storage.maxPatches, { '2.0': 0 }) + + storage.set('2.0.2') + t.assert.deepEqual(storage.maxPatches, { '2.0': 2 }) + + storage.set('2.0.1') + t.assert.deepEqual(storage.maxPatches, { '2.0': 2 }) +}) + +test('Major version must be a numeric value', t => { + t.plan(1) + + const findMyWay = FindMyWay() + + t.assert.throws(() => findMyWay.on('GET', '/test', { constraints: { version: 'x' } }, () => {}), + new TypeError('Major version must be a numeric value')) +}) + +test('httpMethodStrategy storage handles set and get operations correctly', (t) => { + t.plan(2) + + const storage = httpMethodStrategy.storage() + + t.assert.equal(storage.get('foo'), null) + + storage.set('foo', { bar: 'baz' }) + t.assert.deepStrictEqual(storage.get('foo'), { bar: 'baz' }) +}) + +test('if buildPrettyMeta argument is undefined, will return an object', (t) => { + t.plan(1) + + const findMyWay = FindMyWay() + t.assert.deepEqual(findMyWay.buildPrettyMeta(), {}) +}) diff --git a/services/slides/node_modules/find-my-way/test/issue-44.test.js b/services/slides/node_modules/find-my-way/test/issue-44.test.js new file mode 100644 index 0000000000000000000000000000000000000000..80e4f0aa817e541173ebb7aafed263532d8adf85 --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/issue-44.test.js @@ -0,0 +1,149 @@ +'use strict' + +const { test } = require('node:test') +const FindMyWay = require('../') + +test('Parametric and static with shared prefix / 1', t => { + t.plan(1) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.fail('Should not be defaultRoute') + } + }) + + findMyWay.on('GET', '/woo', (req, res, params) => { + t.assert.fail('we should not be here') + }) + + findMyWay.on('GET', '/:param', (req, res, params) => { + t.assert.equal(params.param, 'winter') + }) + + findMyWay.lookup({ method: 'GET', url: '/winter', headers: {} }, null) +}) + +test('Parametric and static with shared prefix / 2', t => { + t.plan(1) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.fail('Should not be defaultRoute') + } + }) + + findMyWay.on('GET', '/woo', (req, res, params) => { + t.assert.ok('we should be here') + }) + + findMyWay.on('GET', '/:param', (req, res, params) => { + t.assert.fail('we should not be here') + }) + + findMyWay.lookup({ method: 'GET', url: '/woo', headers: {} }, null) +}) + +test('Parametric and static with shared prefix (nested)', t => { + t.plan(1) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.ok('We should be here') + } + }) + + findMyWay.on('GET', '/woo', (req, res, params) => { + t.assert.fail('we should not be here') + }) + + findMyWay.on('GET', '/:param', (req, res, params) => { + t.assert.fail('we should not be here') + }) + + findMyWay.lookup({ method: 'GET', url: '/winter/coming', headers: {} }, null) +}) + +test('Parametric and static with shared prefix and different suffix', t => { + t.plan(1) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.fail('We should not be here') + } + }) + + findMyWay.on('GET', '/example/shared/nested/test', (req, res, params) => { + t.assert.fail('We should not be here') + }) + + findMyWay.on('GET', '/example/:param/nested/other', (req, res, params) => { + t.assert.ok('We should be here') + }) + + findMyWay.lookup({ method: 'GET', url: '/example/shared/nested/other', headers: {} }, null) +}) + +test('Parametric and static with shared prefix (with wildcard)', t => { + t.plan(1) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.fail('Should not be defaultRoute') + } + }) + + findMyWay.on('GET', '/woo', (req, res, params) => { + t.assert.fail('we should not be here') + }) + + findMyWay.on('GET', '/:param', (req, res, params) => { + t.assert.equal(params.param, 'winter') + }) + + findMyWay.on('GET', '/*', (req, res, params) => { + t.assert.fail('we should not be here') + }) + + findMyWay.lookup({ method: 'GET', url: '/winter', headers: {} }, null) +}) + +test('Parametric and static with shared prefix (nested with wildcard)', t => { + t.plan(1) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.fail('Should not be defaultRoute') + } + }) + + findMyWay.on('GET', '/woo', (req, res, params) => { + t.assert.fail('we should not be here') + }) + + findMyWay.on('GET', '/:param', (req, res, params) => { + t.assert.fail('we should not be here') + }) + + findMyWay.on('GET', '/*', (req, res, params) => { + t.assert.equal(params['*'], 'winter/coming') + }) + + findMyWay.lookup({ method: 'GET', url: '/winter/coming', headers: {} }, null) +}) + +test('Parametric and static with shared prefix (nested with split)', t => { + t.plan(1) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.fail('we should not be here') + } + }) + + findMyWay.on('GET', '/woo', (req, res, params) => { + t.assert.fail('we should not be here') + }) + + findMyWay.on('GET', '/:param', (req, res, params) => { + t.assert.equal(params.param, 'winter') + }) + + findMyWay.on('GET', '/wo', (req, res, params) => { + t.assert.fail('we should not be here') + }) + + findMyWay.lookup({ method: 'GET', url: '/winter', headers: {} }, null) +}) diff --git a/services/slides/node_modules/find-my-way/test/issue-46.test.js b/services/slides/node_modules/find-my-way/test/issue-46.test.js new file mode 100644 index 0000000000000000000000000000000000000000..ba99726b12103032e59e071c9e027671e86bd67e --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/issue-46.test.js @@ -0,0 +1,75 @@ +'use strict' + +const { test } = require('node:test') +const FindMyWay = require('../') + +test('If the prefixLen is higher than the pathLen we should not save the wildcard child', t => { + t.plan(3) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.fail('Should not be defaultRoute') + } + }) + + findMyWay.get('/static/*', () => {}) + + t.assert.deepEqual(findMyWay.find('GET', '/static/').params, { '*': '' }) + t.assert.deepEqual(findMyWay.find('GET', '/static/hello').params, { '*': 'hello' }) + t.assert.deepEqual(findMyWay.find('GET', '/static'), null) +}) + +test('If the prefixLen is higher than the pathLen we should not save the wildcard child (mixed routes)', t => { + t.plan(3) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.fail('Should not be defaultRoute') + } + }) + + findMyWay.get('/static/*', () => {}) + findMyWay.get('/simple', () => {}) + findMyWay.get('/simple/:bar', () => {}) + findMyWay.get('/hello', () => {}) + + t.assert.deepEqual(findMyWay.find('GET', '/static/').params, { '*': '' }) + t.assert.deepEqual(findMyWay.find('GET', '/static/hello').params, { '*': 'hello' }) + t.assert.deepEqual(findMyWay.find('GET', '/static'), null) +}) + +test('If the prefixLen is higher than the pathLen we should not save the wildcard child (with a root wildcard)', t => { + t.plan(3) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.fail('Should not be defaultRoute') + } + }) + + findMyWay.get('*', () => {}) + findMyWay.get('/static/*', () => {}) + findMyWay.get('/simple', () => {}) + findMyWay.get('/simple/:bar', () => {}) + findMyWay.get('/hello', () => {}) + + t.assert.deepEqual(findMyWay.find('GET', '/static/').params, { '*': '' }) + t.assert.deepEqual(findMyWay.find('GET', '/static/hello').params, { '*': 'hello' }) + t.assert.deepEqual(findMyWay.find('GET', '/static').params, { '*': '/static' }) +}) + +test('If the prefixLen is higher than the pathLen we should not save the wildcard child (404)', t => { + t.plan(4) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.fail('Should not be defaultRoute') + } + }) + + findMyWay.get('/static/*', () => {}) + findMyWay.get('/simple', () => {}) + findMyWay.get('/simple/:bar', () => {}) + findMyWay.get('/hello', () => {}) + + t.assert.deepEqual(findMyWay.find('GET', '/stati'), null) + t.assert.deepEqual(findMyWay.find('GET', '/staticc'), null) + t.assert.deepEqual(findMyWay.find('GET', '/stati/hello'), null) + t.assert.deepEqual(findMyWay.find('GET', '/staticc/hello'), null) +}) diff --git a/services/slides/node_modules/find-my-way/test/issue-49.test.js b/services/slides/node_modules/find-my-way/test/issue-49.test.js new file mode 100644 index 0000000000000000000000000000000000000000..cf5430d712419f49fc782e47cfd6521f7bc2d278 --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/issue-49.test.js @@ -0,0 +1,108 @@ +'use strict' + +const { test } = require('node:test') +const FindMyWay = require('../') +const noop = () => {} + +test('Defining static route after parametric - 1', t => { + t.plan(3) + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/static', noop) + findMyWay.on('GET', '/:param', noop) + + t.assert.ok(findMyWay.find('GET', '/static')) + t.assert.ok(findMyWay.find('GET', '/para')) + t.assert.ok(findMyWay.find('GET', '/s')) +}) + +test('Defining static route after parametric - 2', t => { + t.plan(3) + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/:param', noop) + findMyWay.on('GET', '/static', noop) + + t.assert.ok(findMyWay.find('GET', '/static')) + t.assert.ok(findMyWay.find('GET', '/para')) + t.assert.ok(findMyWay.find('GET', '/s')) +}) + +test('Defining static route after parametric - 3', t => { + t.plan(4) + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/:param', noop) + findMyWay.on('GET', '/static', noop) + findMyWay.on('GET', '/other', noop) + + t.assert.ok(findMyWay.find('GET', '/static')) + t.assert.ok(findMyWay.find('GET', '/para')) + t.assert.ok(findMyWay.find('GET', '/s')) + t.assert.ok(findMyWay.find('GET', '/o')) +}) + +test('Defining static route after parametric - 4', t => { + t.plan(4) + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/static', noop) + findMyWay.on('GET', '/other', noop) + findMyWay.on('GET', '/:param', noop) + + t.assert.ok(findMyWay.find('GET', '/static')) + t.assert.ok(findMyWay.find('GET', '/para')) + t.assert.ok(findMyWay.find('GET', '/s')) + t.assert.ok(findMyWay.find('GET', '/o')) +}) + +test('Defining static route after parametric - 5', t => { + t.plan(4) + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/static', noop) + findMyWay.on('GET', '/:param', noop) + findMyWay.on('GET', '/other', noop) + + t.assert.ok(findMyWay.find('GET', '/static')) + t.assert.ok(findMyWay.find('GET', '/para')) + t.assert.ok(findMyWay.find('GET', '/s')) + t.assert.ok(findMyWay.find('GET', '/o')) +}) + +test('Should produce the same tree - 1', t => { + t.plan(1) + const findMyWay1 = FindMyWay() + const findMyWay2 = FindMyWay() + + findMyWay1.on('GET', '/static', noop) + findMyWay1.on('GET', '/:param', noop) + + findMyWay2.on('GET', '/:param', noop) + findMyWay2.on('GET', '/static', noop) + + t.assert.equal(findMyWay1.tree, findMyWay2.tree) +}) + +test('Should produce the same tree - 2', t => { + t.plan(3) + const findMyWay1 = FindMyWay() + const findMyWay2 = FindMyWay() + const findMyWay3 = FindMyWay() + + findMyWay1.on('GET', '/:param', noop) + findMyWay1.on('GET', '/static', noop) + findMyWay1.on('GET', '/other', noop) + + findMyWay2.on('GET', '/static', noop) + findMyWay2.on('GET', '/:param', noop) + findMyWay2.on('GET', '/other', noop) + + findMyWay3.on('GET', '/static', noop) + findMyWay3.on('GET', '/other', noop) + findMyWay3.on('GET', '/:param', noop) + + t.assert.equal(findMyWay1.tree, findMyWay2.tree) + t.assert.equal(findMyWay2.tree, findMyWay3.tree) + t.assert.equal(findMyWay1.tree, findMyWay3.tree) +}) diff --git a/services/slides/node_modules/find-my-way/test/issue-59.test.js b/services/slides/node_modules/find-my-way/test/issue-59.test.js new file mode 100644 index 0000000000000000000000000000000000000000..7a3a6d4e1239115215d80dd9fa916e503f44eb25 --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/issue-59.test.js @@ -0,0 +1,131 @@ +'use strict' + +const { test } = require('node:test') +const FindMyWay = require('../') +const noop = () => {} + +test('single-character prefix', t => { + t.plan(1) + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/b/', noop) + findMyWay.on('GET', '/b/bulk', noop) + + t.assert.equal(findMyWay.find('GET', '/bulk'), null) +}) + +test('multi-character prefix', t => { + t.plan(1) + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/bu/', noop) + findMyWay.on('GET', '/bu/bulk', noop) + + t.assert.equal(findMyWay.find('GET', '/bulk'), null) +}) + +test('static / 1', t => { + t.plan(1) + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/bb/', noop) + findMyWay.on('GET', '/bb/bulk', noop) + + t.assert.equal(findMyWay.find('GET', '/bulk'), null) +}) + +test('static / 2', t => { + t.plan(2) + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/bb/ff/', noop) + findMyWay.on('GET', '/bb/ff/bulk', noop) + + t.assert.equal(findMyWay.find('GET', '/bulk'), null) + t.assert.equal(findMyWay.find('GET', '/ff/bulk'), null) +}) + +test('static / 3', t => { + t.plan(1) + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/bb/ff/', noop) + findMyWay.on('GET', '/bb/ff/bulk', noop) + findMyWay.on('GET', '/bb/ff/gg/bulk', noop) + findMyWay.on('GET', '/bb/ff/bulk/bulk', noop) + + t.assert.equal(findMyWay.find('GET', '/bulk'), null) +}) + +test('with parameter / 1', t => { + t.plan(1) + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/:foo/', noop) + findMyWay.on('GET', '/:foo/bulk', noop) + + t.assert.equal(findMyWay.find('GET', '/bulk'), null) +}) + +test('with parameter / 2', t => { + t.plan(1) + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/bb/', noop) + findMyWay.on('GET', '/bb/:foo', noop) + + t.assert.equal(findMyWay.find('GET', '/bulk'), null) +}) + +test('with parameter / 3', t => { + t.plan(1) + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/bb/ff/', noop) + findMyWay.on('GET', '/bb/ff/:foo', noop) + + t.assert.equal(findMyWay.find('GET', '/bulk'), null) +}) + +test('with parameter / 4', t => { + t.plan(1) + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/bb/:foo/', noop) + findMyWay.on('GET', '/bb/:foo/bulk', noop) + + t.assert.equal(findMyWay.find('GET', '/bulk'), null) +}) + +test('with parameter / 5', t => { + t.plan(2) + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/bb/:foo/aa/', noop) + findMyWay.on('GET', '/bb/:foo/aa/bulk', noop) + + t.assert.equal(findMyWay.find('GET', '/bulk'), null) + t.assert.equal(findMyWay.find('GET', '/bb/foo/bulk'), null) +}) + +test('with parameter / 6', t => { + t.plan(3) + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/static/:parametric/static/:parametric', noop) + findMyWay.on('GET', '/static/:parametric/static/:parametric/bulk', noop) + + t.assert.equal(findMyWay.find('GET', '/bulk'), null) + t.assert.equal(findMyWay.find('GET', '/static/foo/bulk'), null) + t.assert.notEqual(findMyWay.find('GET', '/static/foo/static/bulk'), null) +}) + +test('wildcard / 1', t => { + t.plan(1) + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/bb/', noop) + findMyWay.on('GET', '/bb/*', noop) + + t.assert.equal(findMyWay.find('GET', '/bulk'), null) +}) diff --git a/services/slides/node_modules/find-my-way/test/issue-62.test.js b/services/slides/node_modules/find-my-way/test/issue-62.test.js new file mode 100644 index 0000000000000000000000000000000000000000..7e5bde7a3637dae3da241ad301ca5a643b649029 --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/issue-62.test.js @@ -0,0 +1,28 @@ +'use strict' + +const { test } = require('node:test') +const FindMyWay = require('../') + +const noop = function () {} + +test('issue-62', (t) => { + t.plan(2) + + const findMyWay = FindMyWay({ allowUnsafeRegex: true }) + + findMyWay.on('GET', '/foo/:id(([a-f0-9]{3},?)+)', noop) + + t.assert.ok(!findMyWay.find('GET', '/foo/qwerty')) + t.assert.ok(findMyWay.find('GET', '/foo/bac,1ea')) +}) + +test('issue-62 - escape chars', (t) => { + const findMyWay = FindMyWay() + + t.plan(2) + + findMyWay.get('/foo/:param(\\([a-f0-9]{3}\\))', noop) + + t.assert.ok(!findMyWay.find('GET', '/foo/abc')) + t.assert.ok(findMyWay.find('GET', '/foo/(abc)', {})) +}) diff --git a/services/slides/node_modules/find-my-way/test/issue-63.test.js b/services/slides/node_modules/find-my-way/test/issue-63.test.js new file mode 100644 index 0000000000000000000000000000000000000000..55bee94880fc3a52fdb8e8ee4cd3b12d0c1bc19f --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/issue-63.test.js @@ -0,0 +1,23 @@ +'use strict' + +const { test } = require('node:test') +const factory = require('../') + +const noop = function () {} + +test('issue-63', (t) => { + t.plan(2) + + const fmw = factory() + + t.assert.throws(function () { + fmw.on('GET', '/foo/:id(a', noop) + }) + + try { + fmw.on('GET', '/foo/:id(a', noop) + t.assert.fail('should fail') + } catch (err) { + t.assert.equal(err.message, 'Invalid regexp expression in "/foo/:id(a"') + } +}) diff --git a/services/slides/node_modules/find-my-way/test/issue-67.test.js b/services/slides/node_modules/find-my-way/test/issue-67.test.js new file mode 100644 index 0000000000000000000000000000000000000000..ac9cf59a4961e808be56b77d8cbef7116ebdba6c --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/issue-67.test.js @@ -0,0 +1,50 @@ +'use strict' + +const { test } = require('node:test') +const FindMyWay = require('../') +const noop = () => {} + +test('static routes', t => { + t.plan(1) + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/b/', noop) + findMyWay.on('GET', '/b/bulk', noop) + findMyWay.on('GET', '/b/ulk', noop) + + t.assert.equal(findMyWay.find('GET', '/bulk'), null) +}) + +test('parametric routes', t => { + t.plan(5) + const findMyWay = FindMyWay() + + function foo () { } + + findMyWay.on('GET', '/foo/:fooParam', foo) + findMyWay.on('GET', '/foo/bar/:barParam', noop) + findMyWay.on('GET', '/foo/search', noop) + findMyWay.on('GET', '/foo/submit', noop) + + t.assert.equal(findMyWay.find('GET', '/foo/awesome-parameter').handler, foo) + t.assert.equal(findMyWay.find('GET', '/foo/b-first-character').handler, foo) + t.assert.equal(findMyWay.find('GET', '/foo/s-first-character').handler, foo) + t.assert.equal(findMyWay.find('GET', '/foo/se-prefix').handler, foo) + t.assert.equal(findMyWay.find('GET', '/foo/sx-prefix').handler, foo) +}) + +test('parametric with common prefix', t => { + t.plan(1) + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/test', noop) + findMyWay.on('GET', '/:test', (req, res, params) => { + t.assert.deepEqual( + { test: 'text' }, + params + ) + }) + findMyWay.on('GET', '/text/hello', noop) + + findMyWay.lookup({ url: '/text', method: 'GET', headers: {} }) +}) diff --git a/services/slides/node_modules/find-my-way/test/issue-93.test.js b/services/slides/node_modules/find-my-way/test/issue-93.test.js new file mode 100644 index 0000000000000000000000000000000000000000..6e57d81d1fb63bed820a77c10658526af9fa74c2 --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/issue-93.test.js @@ -0,0 +1,19 @@ +'use strict' + +const { test } = require('node:test') +const FindMyWay = require('../') +const noop = () => {} + +test('Should keep semver store when split node', t => { + t.plan(4) + + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/t1', { constraints: { version: '1.0.0' } }, noop) + findMyWay.on('GET', '/t2', { constraints: { version: '2.1.0' } }, noop) + + t.assert.ok(findMyWay.find('GET', '/t1', { version: '1.0.0' })) + t.assert.ok(findMyWay.find('GET', '/t2', { version: '2.x' })) + t.assert.ok(!findMyWay.find('GET', '/t1', { version: '2.x' })) + t.assert.ok(!findMyWay.find('GET', '/t2', { version: '1.0.0' })) +}) diff --git a/services/slides/node_modules/find-my-way/test/lookup-async.test.js b/services/slides/node_modules/find-my-way/test/lookup-async.test.js new file mode 100644 index 0000000000000000000000000000000000000000..e1ba2c5e4744981b3024fb2c28edf11a128b4961 --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/lookup-async.test.js @@ -0,0 +1,29 @@ +'use strict' + +const { test } = require('node:test') +const FindMyWay = require('..') + +test('should return result in the done callback', t => { + t.plan(2) + + const router = FindMyWay() + router.on('GET', '/', () => 'asyncHandlerResult') + + router.lookup({ method: 'GET', url: '/' }, null, (err, result) => { + t.assert.equal(err, null) + t.assert.equal(result, 'asyncHandlerResult') + }) +}) + +test('should return an error in the done callback', t => { + t.plan(2) + + const router = FindMyWay() + const error = new Error('ASYNC_HANDLER_ERROR') + router.on('GET', '/', () => { throw error }) + + router.lookup({ method: 'GET', url: '/' }, null, (err, result) => { + t.assert.equal(err, error) + t.assert.equal(result, undefined) + }) +}) diff --git a/services/slides/node_modules/find-my-way/test/lookup.test.js b/services/slides/node_modules/find-my-way/test/lookup.test.js new file mode 100644 index 0000000000000000000000000000000000000000..f7bfa0206918d1081b485f6141636fa8d93a5a62 --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/lookup.test.js @@ -0,0 +1,58 @@ +'use strict' + +const { test } = require('node:test') +const FindMyWay = require('..') + +test('lookup calls route handler with no context', t => { + t.plan(1) + + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/example', function handle (req, res, params) { + // without context, this will be the result object returned from router.find + t.assert.equal(this.handler, handle) + }) + + findMyWay.lookup({ method: 'GET', url: '/example', headers: {} }, null) +}) + +test('lookup calls route handler with context as scope', t => { + t.plan(1) + + const findMyWay = FindMyWay() + + const ctx = { foo: 'bar' } + + findMyWay.on('GET', '/example', function handle (req, res, params) { + t.assert.equal(this, ctx) + }) + + findMyWay.lookup({ method: 'GET', url: '/example', headers: {} }, null, ctx) +}) + +test('lookup calls default route handler with no context', t => { + t.plan(1) + + const findMyWay = FindMyWay({ + defaultRoute (req, res) { + // without context, the default route's scope is the router itself + t.assert.equal(this, findMyWay) + } + }) + + findMyWay.lookup({ method: 'GET', url: '/example', headers: {} }, null) +}) + +test('lookup calls default route handler with context as scope', t => { + t.plan(1) + + const ctx = { foo: 'bar' } + + const findMyWay = FindMyWay({ + defaultRoute (req, res) { + t.assert.equal(this, ctx) + } + }) + + findMyWay.lookup({ method: 'GET', url: '/example', headers: {} }, null, ctx) +}) diff --git a/services/slides/node_modules/find-my-way/test/matching-order.test.js b/services/slides/node_modules/find-my-way/test/matching-order.test.js new file mode 100644 index 0000000000000000000000000000000000000000..1f7cb952eaad58c9c76096a1165e67df087bb0d1 --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/matching-order.test.js @@ -0,0 +1,17 @@ +'use strict' + +const { test } = require('node:test') +const FindMyWay = require('..') + +test('Matching order', t => { + t.plan(3) + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/foo/bar/static', { constraints: { host: 'test' } }, () => {}) + findMyWay.on('GET', '/foo/bar/*', () => {}) + findMyWay.on('GET', '/foo/:param/static', () => {}) + + t.assert.deepEqual(findMyWay.find('GET', '/foo/bar/static', { host: 'test' }).params, {}) + t.assert.deepEqual(findMyWay.find('GET', '/foo/bar/static').params, { '*': 'static' }) + t.assert.deepEqual(findMyWay.find('GET', '/foo/value/static').params, { param: 'value' }) +}) diff --git a/services/slides/node_modules/find-my-way/test/max-param-length.test.js b/services/slides/node_modules/find-my-way/test/max-param-length.test.js new file mode 100644 index 0000000000000000000000000000000000000000..9e397d1ef94de1fa92e0a1b1f03d89ad5f24ab89 --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/max-param-length.test.js @@ -0,0 +1,44 @@ +'use strict' + +const { test } = require('node:test') +const FindMyWay = require('../') + +test('maxParamLength default value is 500', t => { + t.plan(1) + + const findMyWay = FindMyWay() + t.assert.equal(findMyWay.maxParamLength, 100) +}) + +test('maxParamLength should set the maximum length for a parametric route', t => { + t.plan(1) + + const findMyWay = FindMyWay({ maxParamLength: 10 }) + findMyWay.on('GET', '/test/:param', () => {}) + t.assert.deepEqual(findMyWay.find('GET', '/test/123456789abcd'), null) +}) + +test('maxParamLength should set the maximum length for a parametric (regex) route', t => { + t.plan(1) + + const findMyWay = FindMyWay({ maxParamLength: 10 }) + findMyWay.on('GET', '/test/:param(^\\d+$)', () => {}) + + t.assert.deepEqual(findMyWay.find('GET', '/test/123456789abcd'), null) +}) + +test('maxParamLength should set the maximum length for a parametric (multi) route', t => { + t.plan(1) + + const findMyWay = FindMyWay({ maxParamLength: 10 }) + findMyWay.on('GET', '/test/:param-bar', () => {}) + t.assert.deepEqual(findMyWay.find('GET', '/test/123456789abcd'), null) +}) + +test('maxParamLength should set the maximum length for a parametric (regex with suffix) route', t => { + t.plan(1) + + const findMyWay = FindMyWay({ maxParamLength: 10 }) + findMyWay.on('GET', '/test/:param(^\\w{3})bar', () => {}) + t.assert.deepEqual(findMyWay.find('GET', '/test/123456789abcd'), null) +}) diff --git a/services/slides/node_modules/find-my-way/test/methods.test.js b/services/slides/node_modules/find-my-way/test/methods.test.js new file mode 100644 index 0000000000000000000000000000000000000000..58f5af1654c9670db3b8c622ccedb9bcbc85aaa0 --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/methods.test.js @@ -0,0 +1,830 @@ +'use strict' + +const { test } = require('node:test') +const FindMyWay = require('../') + +test('the router is an object with methods', t => { + t.plan(4) + + const findMyWay = FindMyWay() + + t.assert.equal(typeof findMyWay.on, 'function') + t.assert.equal(typeof findMyWay.off, 'function') + t.assert.equal(typeof findMyWay.lookup, 'function') + t.assert.equal(typeof findMyWay.find, 'function') +}) + +test('on throws for invalid method', t => { + t.plan(1) + const findMyWay = FindMyWay() + + t.assert.throws(() => { + findMyWay.on('INVALID', '/a/b') + }) +}) + +test('on throws for invalid path', t => { + t.plan(3) + const findMyWay = FindMyWay() + + // Non string + t.assert.throws(() => { + findMyWay.on('GET', 1) + }) + + // Empty + t.assert.throws(() => { + findMyWay.on('GET', '') + }) + + // Doesn't start with / or * + t.assert.throws(() => { + findMyWay.on('GET', 'invalid') + }) +}) + +test('register a route', t => { + t.plan(1) + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/test', () => { + t.assert.ok('inside the handler') + }) + + findMyWay.lookup({ method: 'GET', url: '/test', headers: {} }, null) +}) + +test('register a route with multiple methods', t => { + t.plan(2) + const findMyWay = FindMyWay() + + findMyWay.on(['GET', 'POST'], '/test', () => { + t.assert.ok('inside the handler') + }) + + findMyWay.lookup({ method: 'GET', url: '/test', headers: {} }, null) + findMyWay.lookup({ method: 'POST', url: '/test', headers: {} }, null) +}) + +test('does not register /test/*/ when ignoreTrailingSlash is true', t => { + t.plan(1) + const findMyWay = FindMyWay({ + ignoreTrailingSlash: true + }) + + findMyWay.on('GET', '/test/*', () => {}) + t.assert.equal( + findMyWay.routes.filter((r) => r.path.includes('/test')).length, + 1 + ) +}) + +test('off throws for invalid method', t => { + t.plan(1) + const findMyWay = FindMyWay() + + t.assert.throws(() => { + findMyWay.off('INVALID', '/a/b') + }) +}) + +test('off throws for invalid path', t => { + t.plan(3) + const findMyWay = FindMyWay() + + // Non string + t.assert.throws(() => { + findMyWay.off('GET', 1) + }) + + // Empty + t.assert.throws(() => { + findMyWay.off('GET', '') + }) + + // Doesn't start with / or * + t.assert.throws(() => { + findMyWay.off('GET', 'invalid') + }) +}) + +test('off with nested wildcards with parametric and static', t => { + t.plan(3) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.fail('we should not be here, the url is: ' + req.url) + } + }) + + findMyWay.on('GET', '*', (req, res, params) => { + t.assert.equal(params['*'], '/foo2/first/second') + }) + findMyWay.on('GET', '/foo1/*', () => {}) + findMyWay.on('GET', '/foo2/*', () => {}) + findMyWay.on('GET', '/foo3/:param', () => {}) + findMyWay.on('GET', '/foo3/*', () => {}) + findMyWay.on('GET', '/foo4/param/hello/test/long/route', () => {}) + + const route1 = findMyWay.find('GET', '/foo3/first/second') + t.assert.equal(route1.params['*'], 'first/second') + + findMyWay.off('GET', '/foo3/*') + + const route2 = findMyWay.find('GET', '/foo3/first/second') + t.assert.equal(route2.params['*'], '/foo3/first/second') + + findMyWay.off('GET', '/foo2/*') + findMyWay.lookup( + { method: 'GET', url: '/foo2/first/second', headers: {} }, + null + ) +}) + +test('off removes all routes when ignoreTrailingSlash is true', t => { + t.plan(6) + const findMyWay = FindMyWay({ + ignoreTrailingSlash: true + }) + + findMyWay.on('GET', '/test1/', () => {}) + t.assert.equal(findMyWay.routes.length, 1) + + findMyWay.on('GET', '/test2', () => {}) + t.assert.equal(findMyWay.routes.length, 2) + + findMyWay.off('GET', '/test1') + t.assert.equal(findMyWay.routes.length, 1) + t.assert.equal( + findMyWay.routes.filter((r) => r.path === '/test2').length, + 1 + ) + t.assert.equal( + findMyWay.routes.filter((r) => r.path === '/test2/').length, + 0 + ) + + findMyWay.off('GET', '/test2/') + t.assert.equal(findMyWay.routes.length, 0) +}) + +test('off removes all routes when ignoreDuplicateSlashes is true', t => { + t.plan(6) + const findMyWay = FindMyWay({ + ignoreDuplicateSlashes: true + }) + + findMyWay.on('GET', '//test1', () => {}) + t.assert.equal(findMyWay.routes.length, 1) + + findMyWay.on('GET', '/test2', () => {}) + t.assert.equal(findMyWay.routes.length, 2) + + findMyWay.off('GET', '/test1') + t.assert.equal(findMyWay.routes.length, 1) + t.assert.equal( + findMyWay.routes.filter((r) => r.path === '/test2').length, + 1 + ) + t.assert.equal( + findMyWay.routes.filter((r) => r.path === '//test2').length, + 0 + ) + + findMyWay.off('GET', '//test2') + t.assert.equal(findMyWay.routes.length, 0) +}) + +test('deregister a route without children', t => { + t.plan(2) + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/a', () => {}) + findMyWay.on('GET', '/a/b', () => {}) + findMyWay.off('GET', '/a/b') + + t.assert.ok(findMyWay.find('GET', '/a')) + t.assert.ok(!findMyWay.find('GET', '/a/b')) +}) + +test('deregister a route with children', t => { + t.plan(2) + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/a', () => {}) + findMyWay.on('GET', '/a/b', () => {}) + findMyWay.off('GET', '/a') + + t.assert.ok(!findMyWay.find('GET', '/a')) + t.assert.ok(findMyWay.find('GET', '/a/b')) +}) + +test('deregister a route by method', t => { + t.plan(2) + const findMyWay = FindMyWay() + + findMyWay.on(['GET', 'POST'], '/a', () => {}) + findMyWay.off('GET', '/a') + + t.assert.ok(!findMyWay.find('GET', '/a')) + t.assert.ok(findMyWay.find('POST', '/a')) +}) + +test('deregister a route with multiple methods', t => { + t.plan(2) + const findMyWay = FindMyWay() + + findMyWay.on(['GET', 'POST'], '/a', () => {}) + findMyWay.off(['GET', 'POST'], '/a') + + t.assert.ok(!findMyWay.find('GET', '/a')) + t.assert.ok(!findMyWay.find('POST', '/a')) +}) + +test('reset a router', t => { + t.plan(2) + const findMyWay = FindMyWay() + + findMyWay.on(['GET', 'POST'], '/a', () => {}) + findMyWay.reset() + + t.assert.ok(!findMyWay.find('GET', '/a')) + t.assert.ok(!findMyWay.find('POST', '/a')) +}) + +test('default route', t => { + t.plan(1) + + const findMyWay = FindMyWay({ + defaultRoute: () => { + t.assert.ok('inside the default route') + } + }) + + findMyWay.lookup({ method: 'GET', url: '/test', headers: {} }, null) +}) + +test('parametric route', t => { + t.plan(1) + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/test/:id', (req, res, params) => { + t.assert.equal(params.id, 'hello') + }) + + findMyWay.lookup({ method: 'GET', url: '/test/hello', headers: {} }, null) +}) + +test('multiple parametric route', t => { + t.plan(2) + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/test/:id', (req, res, params) => { + t.assert.equal(params.id, 'hello') + }) + + findMyWay.on('GET', '/other-test/:id', (req, res, params) => { + t.assert.equal(params.id, 'world') + }) + + findMyWay.lookup({ method: 'GET', url: '/test/hello', headers: {} }, null) + findMyWay.lookup({ method: 'GET', url: '/other-test/world', headers: {} }, null) +}) + +test('multiple parametric route with the same prefix', t => { + t.plan(2) + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/test/:id', (req, res, params) => { + t.assert.equal(params.id, 'hello') + }) + + findMyWay.on('GET', '/test/:id/world', (req, res, params) => { + t.assert.equal(params.id, 'world') + }) + + findMyWay.lookup({ method: 'GET', url: '/test/hello', headers: {} }, null) + findMyWay.lookup({ method: 'GET', url: '/test/world/world', headers: {} }, null) +}) + +test('nested parametric route', t => { + t.plan(2) + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/test/:hello/test/:world', (req, res, params) => { + t.assert.equal(params.hello, 'hello') + t.assert.equal(params.world, 'world') + }) + + findMyWay.lookup({ method: 'GET', url: '/test/hello/test/world', headers: {} }, null) +}) + +test('nested parametric route with same prefix', t => { + t.plan(3) + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/test', (req, res, params) => { + t.assert.ok('inside route') + }) + + findMyWay.on('GET', '/test/:hello/test/:world', (req, res, params) => { + t.assert.equal(params.hello, 'hello') + t.assert.equal(params.world, 'world') + }) + + findMyWay.lookup({ method: 'GET', url: '/test', headers: {} }, null) + findMyWay.lookup({ method: 'GET', url: '/test/hello/test/world', headers: {} }, null) +}) + +test('long route', t => { + t.plan(1) + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/abc/def/ghi/lmn/opq/rst/uvz', (req, res, params) => { + t.assert.ok('inside long path') + }) + + findMyWay.lookup({ method: 'GET', url: '/abc/def/ghi/lmn/opq/rst/uvz', headers: {} }, null) +}) + +test('long parametric route', t => { + t.plan(3) + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/abc/:def/ghi/:lmn/opq/:rst/uvz', (req, res, params) => { + t.assert.equal(params.def, 'def') + t.assert.equal(params.lmn, 'lmn') + t.assert.equal(params.rst, 'rst') + }) + + findMyWay.lookup({ method: 'GET', url: '/abc/def/ghi/lmn/opq/rst/uvz', headers: {} }, null) +}) + +test('long parametric route with common prefix', t => { + t.plan(9) + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/', (req, res, params) => { + throw new Error('I shoul not be here') + }) + + findMyWay.on('GET', '/abc', (req, res, params) => { + throw new Error('I shoul not be here') + }) + + findMyWay.on('GET', '/abc/:def', (req, res, params) => { + t.assert.equal(params.def, 'def') + }) + + findMyWay.on('GET', '/abc/:def/ghi/:lmn', (req, res, params) => { + t.assert.equal(params.def, 'def') + t.assert.equal(params.lmn, 'lmn') + }) + + findMyWay.on('GET', '/abc/:def/ghi/:lmn/opq/:rst', (req, res, params) => { + t.assert.equal(params.def, 'def') + t.assert.equal(params.lmn, 'lmn') + t.assert.equal(params.rst, 'rst') + }) + + findMyWay.on('GET', '/abc/:def/ghi/:lmn/opq/:rst/uvz', (req, res, params) => { + t.assert.equal(params.def, 'def') + t.assert.equal(params.lmn, 'lmn') + t.assert.equal(params.rst, 'rst') + }) + + findMyWay.lookup({ method: 'GET', url: '/abc/def', headers: {} }, null) + findMyWay.lookup({ method: 'GET', url: '/abc/def/ghi/lmn', headers: {} }, null) + findMyWay.lookup({ method: 'GET', url: '/abc/def/ghi/lmn/opq/rst', headers: {} }, null) + findMyWay.lookup({ method: 'GET', url: '/abc/def/ghi/lmn/opq/rst/uvz', headers: {} }, null) +}) + +test('common prefix', t => { + t.plan(4) + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/f', (req, res, params) => { + t.assert.ok('inside route') + }) + + findMyWay.on('GET', '/ff', (req, res, params) => { + t.assert.ok('inside route') + }) + + findMyWay.on('GET', '/ffa', (req, res, params) => { + t.assert.ok('inside route') + }) + + findMyWay.on('GET', '/ffb', (req, res, params) => { + t.assert.ok('inside route') + }) + + findMyWay.lookup({ method: 'GET', url: '/f', headers: {} }, null) + findMyWay.lookup({ method: 'GET', url: '/ff', headers: {} }, null) + findMyWay.lookup({ method: 'GET', url: '/ffa', headers: {} }, null) + findMyWay.lookup({ method: 'GET', url: '/ffb', headers: {} }, null) +}) + +test('wildcard', t => { + t.plan(1) + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/test/*', (req, res, params) => { + t.assert.equal(params['*'], 'hello') + }) + + findMyWay.lookup( + { method: 'GET', url: '/test/hello', headers: {} }, + null + ) +}) + +test('catch all wildcard', t => { + t.plan(1) + const findMyWay = FindMyWay() + + findMyWay.on('GET', '*', (req, res, params) => { + t.assert.equal(params['*'], '/test/hello') + }) + + findMyWay.lookup( + { method: 'GET', url: '/test/hello', headers: {} }, + null + ) +}) + +test('find should return the route', t => { + t.plan(1) + const findMyWay = FindMyWay() + const fn = () => {} + + findMyWay.on('GET', '/test', fn) + + t.assert.deepEqual( + findMyWay.find('GET', '/test'), + { handler: fn, params: {}, store: null, searchParams: {} } + ) +}) + +test('find should return the route with params', t => { + t.plan(1) + const findMyWay = FindMyWay() + const fn = () => {} + + findMyWay.on('GET', '/test/:id', fn) + + t.assert.deepEqual( + findMyWay.find('GET', '/test/hello'), + { handler: fn, params: { id: 'hello' }, store: null, searchParams: {} } + ) +}) + +test('find should return a null handler if the route does not exist', t => { + t.plan(1) + const findMyWay = FindMyWay() + + t.assert.deepEqual( + findMyWay.find('GET', '/test'), + null + ) +}) + +test('should decode the uri - parametric', t => { + t.plan(1) + const findMyWay = FindMyWay() + const fn = () => {} + + findMyWay.on('GET', '/test/:id', fn) + + t.assert.deepEqual( + findMyWay.find('GET', '/test/he%2Fllo'), + { handler: fn, params: { id: 'he/llo' }, store: null, searchParams: {} } + ) +}) + +test('should decode the uri - wildcard', t => { + t.plan(1) + const findMyWay = FindMyWay() + const fn = () => {} + + findMyWay.on('GET', '/test/*', fn) + + t.assert.deepEqual( + findMyWay.find('GET', '/test/he%2Fllo'), + { handler: fn, params: { '*': 'he/llo' }, store: null, searchParams: {} } + ) +}) + +test('safe decodeURIComponent', t => { + t.plan(1) + const findMyWay = FindMyWay() + const fn = () => {} + + findMyWay.on('GET', '/test/:id', fn) + + t.assert.deepEqual( + findMyWay.find('GET', '/test/hel%"Flo'), + null + ) +}) + +test('safe decodeURIComponent - nested route', t => { + t.plan(1) + const findMyWay = FindMyWay() + const fn = () => {} + + findMyWay.on('GET', '/test/hello/world/:id/blah', fn) + + t.assert.deepEqual( + findMyWay.find('GET', '/test/hello/world/hel%"Flo/blah'), + null + ) +}) + +test('safe decodeURIComponent - wildcard', t => { + t.plan(1) + const findMyWay = FindMyWay() + const fn = () => {} + + findMyWay.on('GET', '/test/*', fn) + + t.assert.deepEqual( + findMyWay.find('GET', '/test/hel%"Flo'), + null + ) +}) + +test('static routes should be inserted before parametric / 1', t => { + t.plan(1) + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/test/hello', () => { + t.assert.ok('inside correct handler') + }) + + findMyWay.on('GET', '/test/:id', () => { + t.assert.fail('wrong handler') + }) + + findMyWay.lookup({ method: 'GET', url: '/test/hello', headers: {} }, null) +}) + +test('static routes should be inserted before parametric / 2', t => { + t.plan(1) + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/test/:id', () => { + t.assert.fail('wrong handler') + }) + + findMyWay.on('GET', '/test/hello', () => { + t.assert.ok('inside correct handler') + }) + + findMyWay.lookup({ method: 'GET', url: '/test/hello', headers: {} }, null) +}) + +test('static routes should be inserted before parametric / 3', t => { + t.plan(2) + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/:id', () => { + t.assert.fail('wrong handler') + }) + + findMyWay.on('GET', '/test', () => { + t.assert.ok('inside correct handler') + }) + + findMyWay.on('GET', '/test/:id', () => { + t.assert.fail('wrong handler') + }) + + findMyWay.on('GET', '/test/hello', () => { + t.assert.ok('inside correct handler') + }) + + findMyWay.lookup({ method: 'GET', url: '/test', headers: {} }, null) + findMyWay.lookup({ method: 'GET', url: '/test/hello', headers: {} }, null) +}) + +test('static routes should be inserted before parametric / 4', t => { + t.plan(2) + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/:id', () => { + t.assert.ok('inside correct handler') + }) + + findMyWay.on('GET', '/test', () => { + t.assert.fail('wrong handler') + }) + + findMyWay.on('GET', '/test/:id', () => { + t.assert.ok('inside correct handler') + }) + + findMyWay.on('GET', '/test/hello', () => { + t.assert.fail('wrong handler') + }) + + findMyWay.lookup({ method: 'GET', url: '/test/id', headers: {} }, null) + findMyWay.lookup({ method: 'GET', url: '/id', headers: {} }, null) +}) + +test('Static parametric with shared part of the path', t => { + t.plan(2) + + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.equal(req.url, '/example/shared/nested/oopss') + } + }) + + findMyWay.on('GET', '/example/shared/nested/test', (req, res, params) => { + t.assert.fail('We should not be here') + }) + + findMyWay.on('GET', '/example/:param/nested/oops', (req, res, params) => { + t.assert.equal(params.param, 'other') + }) + + findMyWay.lookup({ method: 'GET', url: '/example/shared/nested/oopss', headers: {} }, null) + findMyWay.lookup({ method: 'GET', url: '/example/other/nested/oops', headers: {} }, null) +}) + +test('parametric route with different method', t => { + t.plan(2) + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/test/:id', (req, res, params) => { + t.assert.equal(params.id, 'hello') + }) + + findMyWay.on('POST', '/test/:other', (req, res, params) => { + t.assert.equal(params.other, 'world') + }) + + findMyWay.lookup({ method: 'GET', url: '/test/hello', headers: {} }, null) + findMyWay.lookup({ method: 'POST', url: '/test/world', headers: {} }, null) +}) + +test('params does not keep the object reference', (t, done) => { + t.plan(2) + const findMyWay = FindMyWay() + let first = true + + findMyWay.on('GET', '/test/:id', (req, res, params) => { + if (first) { + setTimeout(() => { + t.assert.equal(params.id, 'hello') + }, 10) + } else { + setTimeout(() => { + t.assert.equal(params.id, 'world') + done() + }, 10) + } + first = false + }) + + findMyWay.lookup({ method: 'GET', url: '/test/hello', headers: {} }, null) + findMyWay.lookup({ method: 'GET', url: '/test/world', headers: {} }, null) +}) + +test('Unsupported method (static)', t => { + t.plan(1) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.ok('Everything ok') + } + }) + + findMyWay.on('GET', '/', (req, res, params) => { + t.assert.fail('We should not be here') + }) + + findMyWay.lookup({ method: 'TROLL', url: '/', headers: {} }, null) +}) + +test('Unsupported method (wildcard)', t => { + t.plan(1) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.ok('Everything ok') + } + }) + + findMyWay.on('GET', '*', (req, res, params) => { + t.assert.fail('We should not be here') + }) + + findMyWay.lookup({ method: 'TROLL', url: '/hello/world', headers: {} }, null) +}) + +test('Unsupported method (static find)', t => { + t.plan(1) + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/', () => {}) + + t.assert.deepEqual(findMyWay.find('TROLL', '/'), null) +}) + +test('Unsupported method (wildcard find)', t => { + t.plan(1) + const findMyWay = FindMyWay() + + findMyWay.on('GET', '*', () => {}) + + t.assert.deepEqual(findMyWay.find('TROLL', '/hello/world'), null) +}) + +test('register all known HTTP methods', t => { + t.plan(6) + const findMyWay = FindMyWay() + + const httpMethods = require('../lib/http-methods') + const handlers = {} + for (const i in httpMethods) { + const m = httpMethods[i] + handlers[m] = function myHandler () {} + findMyWay.on(m, '/test', handlers[m]) + } + + t.assert.ok(findMyWay.find('COPY', '/test')) + t.assert.equal(findMyWay.find('COPY', '/test').handler, handlers.COPY) + + t.assert.ok(findMyWay.find('SUBSCRIBE', '/test')) + t.assert.equal(findMyWay.find('SUBSCRIBE', '/test').handler, handlers.SUBSCRIBE) + + t.assert.ok(findMyWay.find('M-SEARCH', '/test')) + t.assert.equal(findMyWay.find('M-SEARCH', '/test').handler, handlers['M-SEARCH']) +}) + +test('off removes all routes without checking constraints if no constraints are specified', t => { + t.plan(1) + + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/test', {}, (req, res) => {}) + findMyWay.on('GET', '/test', { constraints: { host: 'example.com' } }, (req, res) => {}) + + findMyWay.off('GET', '/test') + + t.assert.equal(findMyWay.routes.length, 0) +}) + +test('off removes only constrainted routes if constraints are specified', t => { + t.plan(2) + + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/test', {}, (req, res) => {}) + findMyWay.on('GET', '/test', { constraints: { host: 'example.com' } }, (req, res) => {}) + + findMyWay.off('GET', '/test', { host: 'example.com' }) + + t.assert.equal(findMyWay.routes.length, 1) + t.assert.ok(!findMyWay.routes[0].opts.constraints) +}) + +test('off removes no routes if provided constraints does not match any registered route', t => { + t.plan(1) + + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/test', {}, (req, res) => {}) + findMyWay.on('GET', '/test', { constraints: { version: '2.x' } }, (req, res) => {}) + findMyWay.on('GET', '/test', { constraints: { version: '3.x' } }, (req, res) => {}) + + findMyWay.off('GET', '/test', { version: '1.x' }) + + t.assert.equal(findMyWay.routes.length, 3) +}) + +test('off validates that constraints is an object or undefined', t => { + t.plan(6) + + const findMyWay = FindMyWay() + + t.assert.throws(() => findMyWay.off('GET', '/', 2)) + t.assert.throws(() => findMyWay.off('GET', '/', 'should throw')) + t.assert.throws(() => findMyWay.off('GET', '/', [])) + t.assert.doesNotThrow(() => findMyWay.off('GET', '/', undefined)) + t.assert.doesNotThrow(() => findMyWay.off('GET', '/', {})) + t.assert.doesNotThrow(() => findMyWay.off('GET', '/')) +}) + +test('off removes only unconstrainted route if an empty object is given as constraints', t => { + t.plan(2) + + const findMyWay = FindMyWay() + + findMyWay.get('/', {}, () => {}) + findMyWay.get('/', { constraints: { host: 'fastify.io' } }, () => {}) + + findMyWay.off('GET', '/', {}) + + t.assert.equal(findMyWay.routes.length, 1) + t.assert.equal(findMyWay.routes[0].opts.constraints.host, 'fastify.io') +}) diff --git a/services/slides/node_modules/find-my-way/test/null-object.test.js b/services/slides/node_modules/find-my-way/test/null-object.test.js new file mode 100644 index 0000000000000000000000000000000000000000..6f225b16397da37d148d4c75a2887b84dbbe4e50 --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/null-object.test.js @@ -0,0 +1,36 @@ +'use strict' + +const { test } = require('node:test') +const { NullObject } = require('../lib/null-object') + +test('NullObject', t => { + t.plan(2) + const nullObject = new NullObject() + t.assert.ok(nullObject instanceof NullObject) + t.assert.ok(typeof nullObject === 'object') +}) + +test('has no methods from generic Object class', t => { + function getAllPropertyNames (obj) { + const props = [] + + do { + Object.getOwnPropertyNames(obj).forEach(function (prop) { + if (props.indexOf(prop) === -1) { + props.push(prop) + } + }) + } while (obj = Object.getPrototypeOf(obj)) // eslint-disable-line + + return props + } + const propertyNames = getAllPropertyNames({}) + t.plan(propertyNames.length + 1) + + const nullObject = new NullObject() + + for (const propertyName of propertyNames) { + t.assert.ok(!(propertyName in nullObject), propertyName) + } + t.assert.equal(getAllPropertyNames(nullObject).length, 0) +}) diff --git a/services/slides/node_modules/find-my-way/test/on-bad-url.test.js b/services/slides/node_modules/find-my-way/test/on-bad-url.test.js new file mode 100644 index 0000000000000000000000000000000000000000..3bc211e45783bcdc34eb199ef6c7ec8c9552c041 --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/on-bad-url.test.js @@ -0,0 +1,72 @@ +'use strict' + +const { test } = require('node:test') +const FindMyWay = require('../') + +test('If onBadUrl is defined, then a bad url should be handled differently (find)', t => { + t.plan(1) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.fail('Should not be defaultRoute') + }, + onBadUrl: (path, req, res) => { + t.assert.equal(path, '/%world', { todo: 'this is not executed' }) + } + }) + + findMyWay.on('GET', '/hello/:id', (req, res) => { + t.assert.fail('Should not be here') + }) + + const handle = findMyWay.find('GET', '/hello/%world') + t.assert.notDeepStrictEqual(handle, null) +}) + +test('If onBadUrl is defined, then a bad url should be handled differently (lookup)', t => { + t.plan(1) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.fail('Should not be defaultRoute') + }, + onBadUrl: (path, req, res) => { + t.assert.equal(path, '/hello/%world') + } + }) + + findMyWay.on('GET', '/hello/:id', (req, res) => { + t.assert.fail('Should not be here') + }) + + findMyWay.lookup({ method: 'GET', url: '/hello/%world', headers: {} }, null) +}) + +test('If onBadUrl is not defined, then we should call the defaultRoute (find)', t => { + t.plan(1) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.fail('Should not be defaultRoute') + } + }) + + findMyWay.on('GET', '/hello/:id', (req, res) => { + t.assert.fail('Should not be here') + }) + + const handle = findMyWay.find('GET', '/hello/%world') + t.assert.equal(handle, null) +}) + +test('If onBadUrl is not defined, then we should call the defaultRoute (lookup)', t => { + t.plan(1) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.ok('Everything fine') + } + }) + + findMyWay.on('GET', '/hello/:id', (req, res) => { + t.assert.fail('Should not be here') + }) + + findMyWay.lookup({ method: 'GET', url: '/hello/%world', headers: {} }, null) +}) diff --git a/services/slides/node_modules/find-my-way/test/optional-params.test.js b/services/slides/node_modules/find-my-way/test/optional-params.test.js new file mode 100644 index 0000000000000000000000000000000000000000..844181d5536453627e1e75b25056d8f3d0ec5a6b --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/optional-params.test.js @@ -0,0 +1,216 @@ +'use strict' + +const { test } = require('node:test') +const FindMyWay = require('../') + +test('Test route with optional parameter', (t) => { + t.plan(2) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.fail('Should not be defaultRoute') + } + }) + + findMyWay.on('GET', '/a/:param/b/:optional?', (req, res, params) => { + if (params.optional) { + t.assert.equal(params.optional, 'foo') + } else { + t.assert.equal(params.optional, undefined) + } + }) + + findMyWay.lookup({ method: 'GET', url: '/a/foo-bar/b', headers: {} }, null) + findMyWay.lookup({ method: 'GET', url: '/a/foo-bar/b/foo', headers: {} }, null) +}) + +test('Test for duplicate route with optional param', (t) => { + t.plan(1) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.fail('Should not be defaultRoute') + } + }) + + findMyWay.on('GET', '/foo/:bar?', (req, res, params) => {}) + + try { + findMyWay.on('GET', '/foo', (req, res, params) => {}) + t.assert.fail('method is already declared for route with optional param') + } catch (e) { + t.assert.equal(e.message, 'Method \'GET\' already declared for route \'/foo\' with constraints \'{}\'') + } +}) + +test('Test for param with ? not at the end', (t) => { + t.plan(1) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.fail('Should not be defaultRoute') + } + }) + + try { + findMyWay.on('GET', '/foo/:bar?/baz', (req, res, params) => {}) + t.assert.fail('Optional Param in the middle of the path is not allowed') + } catch (e) { + t.assert.equal(e.message, 'Optional Parameter needs to be the last parameter of the path') + } +}) + +test('Multi parametric route with optional param', (t) => { + t.plan(2) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.fail('Should not be defaultRoute') + } + }) + + findMyWay.on('GET', '/a/:p1-:p2?', (req, res, params) => { + if (params.p1 && params.p2) { + t.assert.equal(params.p1, 'foo-bar') + t.assert.equal(params.p2, 'baz') + } + }) + + findMyWay.lookup({ method: 'GET', url: '/a/foo-bar-baz', headers: {} }, null) + findMyWay.lookup({ method: 'GET', url: '/a', headers: {} }, null) +}) + +test('Optional Parameter with ignoreTrailingSlash = true', (t) => { + t.plan(4) + const findMyWay = FindMyWay({ + ignoreTrailingSlash: true, + defaultRoute: (req, res) => { + t.assert.fail('Should not be defaultRoute') + } + }) + + findMyWay.on('GET', '/test/hello/:optional?', (req, res, params) => { + if (params.optional) { + t.assert.equal(params.optional, 'foo') + } else { + t.assert.equal(params.optional, undefined) + } + }) + + findMyWay.lookup({ method: 'GET', url: '/test/hello/', headers: {} }, null) + findMyWay.lookup({ method: 'GET', url: '/test/hello', headers: {} }, null) + findMyWay.lookup({ method: 'GET', url: '/test/hello/foo', headers: {} }, null) + findMyWay.lookup({ method: 'GET', url: '/test/hello/foo/', headers: {} }, null) +}) + +test('Optional Parameter with ignoreTrailingSlash = false', (t) => { + t.plan(4) + const findMyWay = FindMyWay({ + ignoreTrailingSlash: false, + defaultRoute: (req, res) => { + t.assert.equal(req.url, '/test/hello/foo/') + } + }) + + findMyWay.on('GET', '/test/hello/:optional?', (req, res, params) => { + if (req.url === '/test/hello/') { + t.assert.deepEqual(params, { optional: '' }) + } else if (req.url === '/test/hello') { + t.assert.deepEqual(params, {}) + } else if (req.url === '/test/hello/foo') { + t.assert.deepEqual(params, { optional: 'foo' }) + } + }) + + findMyWay.lookup({ method: 'GET', url: '/test/hello/', headers: {} }, null) + findMyWay.lookup({ method: 'GET', url: '/test/hello', headers: {} }, null) + findMyWay.lookup({ method: 'GET', url: '/test/hello/foo', headers: {} }, null) + findMyWay.lookup({ method: 'GET', url: '/test/hello/foo/', headers: {} }, null) +}) + +test('Optional Parameter with ignoreDuplicateSlashes = true', (t) => { + t.plan(4) + const findMyWay = FindMyWay({ + ignoreDuplicateSlashes: true, + defaultRoute: (req, res) => { + t.assert.fail('Should not be defaultRoute') + } + }) + + findMyWay.on('GET', '/test/hello/:optional?', (req, res, params) => { + if (params.optional) { + t.assert.equal(params.optional, 'foo') + } else { + t.assert.equal(params.optional, undefined) + } + }) + + findMyWay.lookup({ method: 'GET', url: '/test//hello', headers: {} }, null) + findMyWay.lookup({ method: 'GET', url: '/test/hello', headers: {} }, null) + findMyWay.lookup({ method: 'GET', url: '/test/hello/foo', headers: {} }, null) + findMyWay.lookup({ method: 'GET', url: '/test//hello//foo', headers: {} }, null) +}) + +test('Optional Parameter with ignoreDuplicateSlashes = false', (t) => { + t.plan(4) + const findMyWay = FindMyWay({ + ignoreDuplicateSlashes: false, + defaultRoute: (req, res) => { + if (req.url === '/test//hello') { + t.assert.deepEqual(req.params, undefined) + } else if (req.url === '/test//hello/foo') { + t.assert.deepEqual(req.params, undefined) + } + } + }) + + findMyWay.on('GET', '/test/hello/:optional?', (req, res, params) => { + if (req.url === '/test/hello/') { + t.assert.deepEqual(params, { optional: '' }) + } else if (req.url === '/test/hello') { + t.assert.deepEqual(params, {}) + } else if (req.url === '/test/hello/foo') { + t.assert.deepEqual(params, { optional: 'foo' }) + } + }) + + findMyWay.lookup({ method: 'GET', url: '/test//hello', headers: {} }, null) + findMyWay.lookup({ method: 'GET', url: '/test/hello', headers: {} }, null) + findMyWay.lookup({ method: 'GET', url: '/test/hello/foo', headers: {} }, null) + findMyWay.lookup({ method: 'GET', url: '/test//hello/foo', headers: {} }, null) +}) + +test('deregister a route with optional param', (t) => { + t.plan(4) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.fail('Should not be defaultRoute') + } + }) + + findMyWay.on('GET', '/a/:param/b/:optional?', (req, res, params) => {}) + + t.assert.ok(findMyWay.find('GET', '/a/:param/b')) + t.assert.ok(findMyWay.find('GET', '/a/:param/b/:optional')) + + findMyWay.off('GET', '/a/:param/b/:optional?') + + t.assert.ok(!findMyWay.find('GET', '/a/:param/b')) + t.assert.ok(!findMyWay.find('GET', '/a/:param/b/:optional')) +}) + +test('optional parameter on root', (t) => { + t.plan(2) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + t.assert.fail('Should not be defaultRoute') + } + }) + + findMyWay.on('GET', '/:optional?', (req, res, params) => { + if (params.optional) { + t.assert.equal(params.optional, 'foo') + } else { + t.assert.equal(params.optional, undefined) + } + }) + + findMyWay.lookup({ method: 'GET', url: '/', headers: {} }, null) + findMyWay.lookup({ method: 'GET', url: '/foo', headers: {} }, null) +}) diff --git a/services/slides/node_modules/find-my-way/test/params-collisions.test.js b/services/slides/node_modules/find-my-way/test/params-collisions.test.js new file mode 100644 index 0000000000000000000000000000000000000000..03fac6bc78db983329c1423236f2c45d03646bf9 --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/params-collisions.test.js @@ -0,0 +1,126 @@ +'use strict' + +const { test } = require('node:test') +const FindMyWay = require('..') + +test('should setup parametric and regexp node', t => { + t.plan(2) + + const findMyWay = FindMyWay() + + const paramHandler = () => {} + const regexpHandler = () => {} + + findMyWay.on('GET', '/foo/:bar', paramHandler) + findMyWay.on('GET', '/foo/:bar(123)', regexpHandler) + + t.assert.equal(findMyWay.find('GET', '/foo/value').handler, paramHandler) + t.assert.equal(findMyWay.find('GET', '/foo/123').handler, regexpHandler) +}) + +test('should setup parametric and multi-parametric node', t => { + t.plan(2) + + const findMyWay = FindMyWay() + + const paramHandler = () => {} + const regexpHandler = () => {} + + findMyWay.on('GET', '/foo/:bar', paramHandler) + findMyWay.on('GET', '/foo/:bar.png', regexpHandler) + + t.assert.equal(findMyWay.find('GET', '/foo/value').handler, paramHandler) + t.assert.equal(findMyWay.find('GET', '/foo/value.png').handler, regexpHandler) +}) + +test('should throw when set upping two parametric nodes', t => { + t.plan(1) + + const findMyWay = FindMyWay() + findMyWay.on('GET', '/foo/:bar', () => {}) + + t.assert.throws(() => findMyWay.on('GET', '/foo/:baz', () => {})) +}) + +test('should throw when set upping two regexp nodes', t => { + t.plan(1) + + const findMyWay = FindMyWay() + findMyWay.on('GET', '/foo/:bar(123)', () => {}) + + t.assert.throws(() => findMyWay.on('GET', '/foo/:bar(456)', () => {})) +}) + +test('should set up two parametric nodes with static ending', t => { + t.plan(2) + + const findMyWay = FindMyWay() + + const paramHandler1 = () => {} + const paramHandler2 = () => {} + + findMyWay.on('GET', '/foo/:bar.png', paramHandler1) + findMyWay.on('GET', '/foo/:bar.jpeg', paramHandler2) + + t.assert.equal(findMyWay.find('GET', '/foo/value.png').handler, paramHandler1) + t.assert.equal(findMyWay.find('GET', '/foo/value.jpeg').handler, paramHandler2) +}) + +test('should set up two regexp nodes with static ending', t => { + t.plan(2) + + const findMyWay = FindMyWay() + + const paramHandler1 = () => {} + const paramHandler2 = () => {} + + findMyWay.on('GET', '/foo/:bar(123).png', paramHandler1) + findMyWay.on('GET', '/foo/:bar(456).jpeg', paramHandler2) + + t.assert.equal(findMyWay.find('GET', '/foo/123.png').handler, paramHandler1) + t.assert.equal(findMyWay.find('GET', '/foo/456.jpeg').handler, paramHandler2) +}) + +test('node with longer static suffix should have higher priority', t => { + t.plan(2) + + const findMyWay = FindMyWay() + + const paramHandler1 = () => {} + const paramHandler2 = () => {} + + findMyWay.on('GET', '/foo/:bar.png', paramHandler1) + findMyWay.on('GET', '/foo/:bar.png.png', paramHandler2) + + t.assert.equal(findMyWay.find('GET', '/foo/value.png').handler, paramHandler1) + t.assert.equal(findMyWay.find('GET', '/foo/value.png.png').handler, paramHandler2) +}) + +test('node with longer static suffix should have higher priority', t => { + t.plan(2) + + const findMyWay = FindMyWay() + + const paramHandler1 = () => {} + const paramHandler2 = () => {} + + findMyWay.on('GET', '/foo/:bar.png.png', paramHandler2) + findMyWay.on('GET', '/foo/:bar.png', paramHandler1) + + t.assert.equal(findMyWay.find('GET', '/foo/value.png').handler, paramHandler1) + t.assert.equal(findMyWay.find('GET', '/foo/value.png.png').handler, paramHandler2) +}) + +test('should set up regexp node and node with static ending', t => { + t.plan(2) + + const regexHandler = () => {} + const multiParamHandler = () => {} + + const findMyWay = FindMyWay() + findMyWay.on('GET', '/foo/:bar(123)', regexHandler) + findMyWay.on('GET', '/foo/:bar(123).jpeg', multiParamHandler) + + t.assert.equal(findMyWay.find('GET', '/foo/123.jpeg').handler, multiParamHandler) + t.assert.equal(findMyWay.find('GET', '/foo/123').handler, regexHandler) +}) diff --git a/services/slides/node_modules/find-my-way/test/path-params-match.test.js b/services/slides/node_modules/find-my-way/test/path-params-match.test.js new file mode 100644 index 0000000000000000000000000000000000000000..3978017d76304daf466cbd91e6d0b83165308a8a --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/path-params-match.test.js @@ -0,0 +1,53 @@ +'use strict' + +const { test } = require('node:test') +const FindMyWay = require('../') + +test('path params match', (t) => { + t.plan(24) + + const findMyWay = FindMyWay({ ignoreTrailingSlash: true, ignoreDuplicateSlashes: true }) + + const b1Path = function b1StaticPath () {} + const b2Path = function b2StaticPath () {} + const cPath = function cStaticPath () {} + const paramPath = function parameterPath () {} + + findMyWay.on('GET', '/ab1', b1Path) + findMyWay.on('GET', '/ab2', b2Path) + findMyWay.on('GET', '/ac', cPath) + findMyWay.on('GET', '/:pam', paramPath) + + t.assert.equal(findMyWay.find('GET', '/ab1').handler, b1Path) + t.assert.equal(findMyWay.find('GET', '/ab1/').handler, b1Path) + t.assert.equal(findMyWay.find('GET', '//ab1').handler, b1Path) + t.assert.equal(findMyWay.find('GET', '//ab1//').handler, b1Path) + t.assert.equal(findMyWay.find('GET', '/ab2').handler, b2Path) + t.assert.equal(findMyWay.find('GET', '/ab2/').handler, b2Path) + t.assert.equal(findMyWay.find('GET', '//ab2').handler, b2Path) + t.assert.equal(findMyWay.find('GET', '//ab2//').handler, b2Path) + t.assert.equal(findMyWay.find('GET', '/ac').handler, cPath) + t.assert.equal(findMyWay.find('GET', '/ac/').handler, cPath) + t.assert.equal(findMyWay.find('GET', '//ac').handler, cPath) + t.assert.equal(findMyWay.find('GET', '//ac//').handler, cPath) + t.assert.equal(findMyWay.find('GET', '/foo').handler, paramPath) + t.assert.equal(findMyWay.find('GET', '/foo/').handler, paramPath) + t.assert.equal(findMyWay.find('GET', '//foo').handler, paramPath) + t.assert.equal(findMyWay.find('GET', '//foo//').handler, paramPath) + + const noTrailingSlashRet = findMyWay.find('GET', '/abcdef') + t.assert.equal(noTrailingSlashRet.handler, paramPath) + t.assert.deepEqual(noTrailingSlashRet.params, { pam: 'abcdef' }) + + const trailingSlashRet = findMyWay.find('GET', '/abcdef/') + t.assert.equal(trailingSlashRet.handler, paramPath) + t.assert.deepEqual(trailingSlashRet.params, { pam: 'abcdef' }) + + const noDuplicateSlashRet = findMyWay.find('GET', '/abcdef') + t.assert.equal(noDuplicateSlashRet.handler, paramPath) + t.assert.deepEqual(noDuplicateSlashRet.params, { pam: 'abcdef' }) + + const duplicateSlashRet = findMyWay.find('GET', '//abcdef') + t.assert.equal(duplicateSlashRet.handler, paramPath) + t.assert.deepEqual(duplicateSlashRet.params, { pam: 'abcdef' }) +}) diff --git a/services/slides/node_modules/find-my-way/test/path-utils.test.js b/services/slides/node_modules/find-my-way/test/path-utils.test.js new file mode 100644 index 0000000000000000000000000000000000000000..8aa0bbea09def7105981bf4864c5799a1cb400a4 --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/path-utils.test.js @@ -0,0 +1,68 @@ +'use strict' + +const { test } = require('node:test') +const FindMyWay = require('..') + +test('removeDuplicateSlashes should return the same path when there are no duplicate slashes', t => { + t.plan(1) + + const path = '/hello/world' + t.assert.equal(FindMyWay.removeDuplicateSlashes(path), '/hello/world') +}) + +test('removeDuplicateSlashes should collapse duplicate slash groups across the full path', t => { + t.plan(1) + + const path = '/hello//world///foo////bar' + t.assert.equal(FindMyWay.removeDuplicateSlashes(path), '/hello/world/foo/bar') +}) + +test('removeDuplicateSlashes should normalize a path made only of slashes', t => { + t.plan(1) + + const path = '////' + t.assert.equal(FindMyWay.removeDuplicateSlashes(path), '/') +}) + +test('removeDuplicateSlashes should keep encoded slashes untouched', t => { + t.plan(1) + + const path = '/a/%2F//b' + t.assert.equal(FindMyWay.removeDuplicateSlashes(path), '/a/%2F/b') +}) + +test('trimLastSlash should remove one trailing slash from non-root paths', t => { + t.plan(1) + + const path = '/hello/' + t.assert.equal(FindMyWay.trimLastSlash(path), '/hello') +}) + +test('trimLastSlash should leave root path untouched', t => { + t.plan(1) + + const path = '/' + t.assert.equal(FindMyWay.trimLastSlash(path), '/') +}) + +test('trimLastSlash should leave paths without trailing slash untouched', t => { + t.plan(1) + + const path = '/hello/world' + t.assert.equal(FindMyWay.trimLastSlash(path), '/hello/world') +}) + +test('trimLastSlash should remove only one trailing slash', t => { + t.plan(1) + + const path = '/hello///' + t.assert.equal(FindMyWay.trimLastSlash(path), '/hello//') +}) + +test('removeDuplicateSlashes then trimLastSlash should match router path normalization order', t => { + t.plan(1) + + const path = '//a//b//c//' + const normalized = FindMyWay.trimLastSlash(FindMyWay.removeDuplicateSlashes(path)) + t.assert.equal(normalized, '/a/b/c') +}) diff --git a/services/slides/node_modules/find-my-way/test/pretty-print-tree.test.js b/services/slides/node_modules/find-my-way/test/pretty-print-tree.test.js new file mode 100644 index 0000000000000000000000000000000000000000..39f49fd7be0bc8a877a97fccb219cd44869eb609 --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/pretty-print-tree.test.js @@ -0,0 +1,596 @@ +'use strict' + +const { test } = require('node:test') +const FindMyWay = require('../') + +test('pretty print - empty tree', t => { + t.plan(2) + + const findMyWay = FindMyWay() + const tree = findMyWay.prettyPrint({ method: 'GET' }) + + const expected = '(empty tree)' + t.assert.equal(typeof tree, 'string') + t.assert.equal(tree, expected) +}) + +test('pretty print - static routes', t => { + t.plan(2) + + const findMyWay = FindMyWay() + findMyWay.on('GET', '/test', () => {}) + findMyWay.on('GET', '/test/hello', () => {}) + findMyWay.on('GET', '/hello/world', () => {}) + + const tree = findMyWay.prettyPrint({ method: 'GET' }) + const expected = `\ +└── / + ├── test (GET) + │ └── /hello (GET) + └── hello/world (GET) +` + t.assert.equal(typeof tree, 'string') + t.assert.equal(tree, expected) +}) + +test('pretty print - parametric routes', t => { + t.plan(2) + + const findMyWay = FindMyWay() + findMyWay.on('GET', '/test', () => {}) + findMyWay.on('GET', '/test/:hello', () => {}) + findMyWay.on('GET', '/hello/:world', () => {}) + + const tree = findMyWay.prettyPrint({ method: 'GET' }) + const expected = `\ +└── / + ├── test (GET) + │ └── / + │ └── :hello (GET) + └── hello/ + └── :world (GET) +` + t.assert.equal(typeof tree, 'string') + t.assert.equal(tree, expected) +}) + +test('pretty print - parametric routes', t => { + t.plan(2) + + const findMyWay = FindMyWay() + findMyWay.on('GET', '/static', () => {}) + findMyWay.on('GET', '/static/:param/suffix1', () => {}) + findMyWay.on('GET', '/static/:param(123)/suffix2', () => {}) + findMyWay.on('GET', '/static/:param(123).end/suffix3', () => {}) + findMyWay.on('GET', '/static/:param1(123).:param2(456)/suffix4', () => {}) + + const tree = findMyWay.prettyPrint({ method: 'GET' }) + const expected = `\ +└── / + └── static (GET) + └── / + ├── :param(123).end + │ └── /suffix3 (GET) + ├── :param(123) + │ └── /suffix2 (GET) + ├── :param1(123).:param2(456) + │ └── /suffix4 (GET) + └── :param + └── /suffix1 (GET) +` + t.assert.equal(typeof tree, 'string') + t.assert.equal(tree, expected) +}) + +test('pretty print - parametric routes', t => { + t.plan(2) + + const findMyWay = FindMyWay() + findMyWay.on('GET', '/static', () => {}) + findMyWay.on('GET', '/static/:param/suffix1', () => {}) + findMyWay.on('GET', '/static/:param(123)/suffix2', () => {}) + findMyWay.on('GET', '/static/:param(123).end/suffix3', () => {}) + findMyWay.on('GET', '/static/:param1(123).:param2(456)/suffix4', () => {}) + + const tree = findMyWay.prettyPrint({ method: 'GET', commonPrefix: false }) + const expected = `\ +└── /static (GET) + ├── /:param(123).end/suffix3 (GET) + ├── /:param(123)/suffix2 (GET) + ├── /:param1(123).:param2(456)/suffix4 (GET) + └── /:param/suffix1 (GET) +` + t.assert.equal(typeof tree, 'string') + t.assert.equal(tree, expected) +}) + +test('pretty print - mixed parametric routes', t => { + t.plan(2) + + const findMyWay = FindMyWay() + findMyWay.on('GET', '/test', () => {}) + findMyWay.on('GET', '/test/:hello', () => {}) + findMyWay.on('POST', '/test/:hello', () => {}) + findMyWay.on('GET', '/test/:hello/world', () => {}) + + const tree = findMyWay.prettyPrint({ method: 'GET' }) + const expected = `\ +└── / + └── test (GET) + └── / + └── :hello (GET) + └── /world (GET) +` + t.assert.equal(typeof tree, 'string') + t.assert.equal(tree, expected) +}) + +test('pretty print - wildcard routes', t => { + t.plan(2) + + const findMyWay = FindMyWay() + findMyWay.on('GET', '/test', () => {}) + findMyWay.on('GET', '/test/*', () => {}) + findMyWay.on('GET', '/hello/*', () => {}) + + const tree = findMyWay.prettyPrint({ method: 'GET' }) + const expected = `\ +└── / + ├── test (GET) + │ └── / + │ └── * (GET) + └── hello/ + └── * (GET) +` + t.assert.equal(typeof tree, 'string') + t.assert.equal(tree, expected) +}) + +test('pretty print - parametric routes with same parent and followed by a static route which has the same prefix with the former routes', t => { + t.plan(2) + + const findMyWay = FindMyWay() + findMyWay.on('GET', '/test', () => {}) + findMyWay.on('GET', '/test/hello/:id', () => {}) + findMyWay.on('POST', '/test/hello/:id', () => {}) + findMyWay.on('GET', '/test/helloworld', () => {}) + + const tree = findMyWay.prettyPrint({ method: 'GET' }) + const expected = `\ +└── / + └── test (GET) + └── /hello + ├── / + │ └── :id (GET) + └── world (GET) +` + t.assert.equal(typeof tree, 'string') + t.assert.equal(tree, expected) +}) + +test('pretty print - constrained parametric routes', t => { + t.plan(2) + + const findMyWay = FindMyWay() + findMyWay.on('GET', '/test', () => {}) + findMyWay.on('GET', '/test', { constraints: { host: 'auth.fastify.io' } }, () => {}) + findMyWay.on('GET', '/test/:hello', () => {}) + findMyWay.on('GET', '/test/:hello', { constraints: { version: '1.1.2' } }, () => {}) + findMyWay.on('GET', '/test/:hello', { constraints: { version: '2.0.0' } }, () => {}) + + const tree = findMyWay.prettyPrint({ method: 'GET' }) + const expected = `\ +└── / + └── test (GET) + test (GET) {"host":"auth.fastify.io"} + └── / + └── :hello (GET) + :hello (GET) {"version":"1.1.2"} + :hello (GET) {"version":"2.0.0"} +` + t.assert.equal(typeof tree, 'string') + t.assert.equal(tree, expected) +}) + +test('pretty print - multiple parameters are drawn appropriately', t => { + t.plan(2) + + const findMyWay = FindMyWay() + findMyWay.on('GET', '/test', () => {}) + // routes with a nested parameter (i.e. no handler for the /:param) were breaking the display + findMyWay.on('GET', '/test/:hello/there/:ladies', () => {}) + findMyWay.on('GET', '/test/:hello/there/:ladies/and/:gents', () => {}) + findMyWay.on('GET', '/test/are/:you/:ready/to/:rock', () => {}) + + const tree = findMyWay.prettyPrint({ method: 'GET', commonPrefix: false }) + const expected = `\ +└── /test (GET) + ├── /are/:you/:ready/to/:rock (GET) + └── /:hello/there/:ladies (GET) + └── /and/:gents (GET) +` + t.assert.equal(typeof tree, 'string') + t.assert.equal(tree, expected) +}) + +test('pretty print commonPrefix - use routes array to draw flattened routes', t => { + t.plan(4) + + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/test', () => {}) + findMyWay.on('GET', '/test/hello', () => {}) + findMyWay.on('GET', '/testing', () => {}) + findMyWay.on('GET', '/testing/:param', () => {}) + findMyWay.on('GET', '/update', () => {}) + + const radixTree = findMyWay.prettyPrint({ method: 'GET', commonPrefix: true }) + const arrayTree = findMyWay.prettyPrint({ method: 'GET', commonPrefix: false }) + + const radixExpected = `\ +└── / + ├── test (GET) + │ ├── /hello (GET) + │ └── ing (GET) + │ └── / + │ └── :param (GET) + └── update (GET) +` + + const arrayExpected = `\ +├── /test (GET) +│ ├── /hello (GET) +│ └── ing (GET) +│ └── /:param (GET) +└── /update (GET) +` + + t.assert.equal(typeof radixTree, 'string') + t.assert.equal(radixTree, radixExpected) + + t.assert.equal(typeof arrayTree, 'string') + t.assert.equal(arrayTree, arrayExpected) +}) + +test('pretty print commonPrefix - handle wildcard root', t => { + t.plan(2) + + const findMyWay = FindMyWay() + + findMyWay.on('GET', '*', () => {}) + findMyWay.on('GET', '/test/hello', () => {}) + findMyWay.on('GET', '/testing', () => {}) + findMyWay.on('GET', '/testing/:param', () => {}) + findMyWay.on('PUT', '/update', () => {}) + + const arrayTree = findMyWay.prettyPrint({ method: 'GET', commonPrefix: false }) + const arrayExpected = `\ +├── /test/hello (GET) +├── /testing (GET) +│ └── /:param (GET) +└── * (GET) +` + t.assert.equal(typeof arrayTree, 'string') + t.assert.equal(arrayTree, arrayExpected) +}) + +test('pretty print commonPrefix - handle wildcard root', t => { + t.plan(2) + + const findMyWay = FindMyWay() + + findMyWay.on('GET', '*', () => {}) + findMyWay.on('GET', '/test/hello', () => {}) + findMyWay.on('GET', '/testing', () => {}) + findMyWay.on('GET', '/testing/:param', () => {}) + findMyWay.on('PUT', '/update', () => {}) + + const radixTree = findMyWay.prettyPrint({ method: 'GET' }) + const radixExpected = `\ +└── (empty root node) + ├── / + │ └── test + │ ├── /hello (GET) + │ └── ing (GET) + │ └── / + │ └── :param (GET) + └── * (GET) +` + t.assert.equal(typeof radixTree, 'string') + t.assert.equal(radixTree, radixExpected) +}) + +test('pretty print commonPrefix - handle constrained routes', t => { + t.plan(2) + + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/test', () => {}) + findMyWay.on('GET', '/test', { constraints: { host: 'auth.fastify.io' } }, () => {}) + findMyWay.on('GET', '/test/:hello', () => {}) + findMyWay.on('PUT', '/test/:hello', () => {}) + findMyWay.on('GET', '/test/:hello', { constraints: { version: '1.1.2' } }, () => {}) + findMyWay.on('GET', '/test/:hello', { constraints: { version: '2.0.0' } }, () => {}) + + const arrayTree = findMyWay.prettyPrint({ method: 'GET', commonPrefix: false }) + const arrayExpected = `\ +└── /test (GET) + /test (GET) {"host":"auth.fastify.io"} + └── /:hello (GET) + /:hello (GET) {"version":"1.1.2"} + /:hello (GET) {"version":"2.0.0"} +` + t.assert.equal(typeof arrayTree, 'string') + t.assert.equal(arrayTree, arrayExpected) +}) + +test('pretty print includeMeta - commonPrefix: true', t => { + t.plan(6) + + const findMyWay = FindMyWay() + const namedFunction = () => {} + const store = { + onRequest: [() => {}, namedFunction], + onTimeout: [() => {}], + genericMeta: 'meta', + mixedMeta: ['mixed items', { an: 'object' }], + objectMeta: { one: '1', two: 2 }, + functionMeta: namedFunction + } + + store[Symbol('symbolKey')] = Symbol('symbolValue') + + findMyWay.on('GET', '/test', () => {}, store) + findMyWay.on('GET', '/test', { constraints: { host: 'auth.fastify.io' } }, () => {}, store) + findMyWay.on('GET', '/testing/:hello', () => {}, store) + findMyWay.on('PUT', '/tested/:hello', () => {}, store) + findMyWay.on('GET', '/test/:hello', { constraints: { version: '1.1.2' } }, () => {}) + findMyWay.on('GET', '/test/:hello', { constraints: { version: '2.0.0' } }, () => {}) + + const radixTree = findMyWay.prettyPrint({ + method: 'GET', + commonPrefix: true, + includeMeta: true + }) + const radixTreeExpected = `\ +└── / + └── test (GET) + • (onRequest) ["anonymous()","namedFunction()"] + • (onTimeout) ["anonymous()"] + • (genericMeta) "meta" + • (mixedMeta) ["mixed items",{"an":"object"}] + • (objectMeta) {"one":"1","two":2} + • (functionMeta) "namedFunction()" + • (Symbol(symbolKey)) "Symbol(symbolValue)" + test (GET) {"host":"auth.fastify.io"} + • (onRequest) ["anonymous()","namedFunction()"] + • (onTimeout) ["anonymous()"] + • (genericMeta) "meta" + • (mixedMeta) ["mixed items",{"an":"object"}] + • (objectMeta) {"one":"1","two":2} + • (functionMeta) "namedFunction()" + • (Symbol(symbolKey)) "Symbol(symbolValue)" + ├── ing/ + │ └── :hello (GET) + │ • (onRequest) ["anonymous()","namedFunction()"] + │ • (onTimeout) ["anonymous()"] + │ • (genericMeta) "meta" + │ • (mixedMeta) ["mixed items",{"an":"object"}] + │ • (objectMeta) {"one":"1","two":2} + │ • (functionMeta) "namedFunction()" + │ • (Symbol(symbolKey)) "Symbol(symbolValue)" + └── / + └── :hello (GET) {"version":"1.1.2"} + :hello (GET) {"version":"2.0.0"} +` + const radixTreeSpecific = findMyWay.prettyPrint({ + method: 'GET', + commonPrefix: true, + includeMeta: ['onTimeout', 'objectMeta', 'nonExistent'] + }) + const radixTreeSpecificExpected = `\ +└── / + └── test (GET) + • (onTimeout) ["anonymous()"] + • (objectMeta) {"one":"1","two":2} + test (GET) {"host":"auth.fastify.io"} + • (onTimeout) ["anonymous()"] + • (objectMeta) {"one":"1","two":2} + ├── ing/ + │ └── :hello (GET) + │ • (onTimeout) ["anonymous()"] + │ • (objectMeta) {"one":"1","two":2} + └── / + └── :hello (GET) {"version":"1.1.2"} + :hello (GET) {"version":"2.0.0"} +` + const radixTreeNoMeta = findMyWay.prettyPrint({ + method: 'GET', + commonPrefix: true, + includeMeta: false + }) + const radixTreeNoMetaExpected = `\ +└── / + └── test (GET) + test (GET) {"host":"auth.fastify.io"} + ├── ing/ + │ └── :hello (GET) + └── / + └── :hello (GET) {"version":"1.1.2"} + :hello (GET) {"version":"2.0.0"} +` + t.assert.equal(typeof radixTree, 'string') + t.assert.equal(radixTree, radixTreeExpected) + + t.assert.equal(typeof radixTreeSpecific, 'string') + t.assert.equal(radixTreeSpecific, radixTreeSpecificExpected) + + t.assert.equal(typeof radixTreeNoMeta, 'string') + t.assert.equal(radixTreeNoMeta, radixTreeNoMetaExpected) +}) + +test('pretty print includeMeta - commonPrefix: false', t => { + t.plan(6) + + const findMyWay = FindMyWay() + const namedFunction = () => {} + const store = { + onRequest: [() => {}, namedFunction], + onTimeout: [() => {}], + genericMeta: 'meta', + mixedMeta: ['mixed items', { an: 'object' }], + objectMeta: { one: '1', two: 2 }, + functionMeta: namedFunction + } + + store[Symbol('symbolKey')] = Symbol('symbolValue') + + findMyWay.on('GET', '/test', () => {}, store) + findMyWay.on('GET', '/test', { constraints: { host: 'auth.fastify.io' } }, () => {}, store) + findMyWay.on('GET', '/testing/:hello', () => {}, store) + findMyWay.on('PUT', '/tested/:hello', () => {}, store) + findMyWay.on('GET', '/test/:hello', { constraints: { version: '1.1.2' } }, () => {}) + findMyWay.on('GET', '/test/:hello', { constraints: { version: '2.0.0' } }, () => {}) + + const arrayTree = findMyWay.prettyPrint({ + method: 'GET', + commonPrefix: false, + includeMeta: true + }) + const arrayExpected = `\ +└── /test (GET) + • (onRequest) ["anonymous()","namedFunction()"] + • (onTimeout) ["anonymous()"] + • (genericMeta) "meta" + • (mixedMeta) ["mixed items",{"an":"object"}] + • (objectMeta) {"one":"1","two":2} + • (functionMeta) "namedFunction()" + • (Symbol(symbolKey)) "Symbol(symbolValue)" + /test (GET) {"host":"auth.fastify.io"} + • (onRequest) ["anonymous()","namedFunction()"] + • (onTimeout) ["anonymous()"] + • (genericMeta) "meta" + • (mixedMeta) ["mixed items",{"an":"object"}] + • (objectMeta) {"one":"1","two":2} + • (functionMeta) "namedFunction()" + • (Symbol(symbolKey)) "Symbol(symbolValue)" + ├── ing/:hello (GET) + │ • (onRequest) ["anonymous()","namedFunction()"] + │ • (onTimeout) ["anonymous()"] + │ • (genericMeta) "meta" + │ • (mixedMeta) ["mixed items",{"an":"object"}] + │ • (objectMeta) {"one":"1","two":2} + │ • (functionMeta) "namedFunction()" + │ • (Symbol(symbolKey)) "Symbol(symbolValue)" + └── /:hello (GET) {"version":"1.1.2"} + /:hello (GET) {"version":"2.0.0"} +` + const arraySpecific = findMyWay.prettyPrint({ + method: 'GET', + commonPrefix: false, + includeMeta: ['onRequest', 'mixedMeta', 'nonExistent'] + }) + const arraySpecificExpected = `\ +└── /test (GET) + • (onRequest) ["anonymous()","namedFunction()"] + • (mixedMeta) ["mixed items",{"an":"object"}] + /test (GET) {"host":"auth.fastify.io"} + • (onRequest) ["anonymous()","namedFunction()"] + • (mixedMeta) ["mixed items",{"an":"object"}] + ├── ing/:hello (GET) + │ • (onRequest) ["anonymous()","namedFunction()"] + │ • (mixedMeta) ["mixed items",{"an":"object"}] + └── /:hello (GET) {"version":"1.1.2"} + /:hello (GET) {"version":"2.0.0"} +` + const arrayNoMeta = findMyWay.prettyPrint({ + method: 'GET', + commonPrefix: false, + includeMeta: false + }) + const arrayNoMetaExpected = `\ +└── /test (GET) + /test (GET) {"host":"auth.fastify.io"} + ├── ing/:hello (GET) + └── /:hello (GET) {"version":"1.1.2"} + /:hello (GET) {"version":"2.0.0"} +` + + t.assert.equal(typeof arrayTree, 'string') + t.assert.equal(arrayTree, arrayExpected) + + t.assert.equal(typeof arraySpecific, 'string') + t.assert.equal(arraySpecific, arraySpecificExpected) + + t.assert.equal(typeof arrayNoMeta, 'string') + t.assert.equal(arrayNoMeta, arrayNoMetaExpected) +}) + +test('pretty print includeMeta - buildPrettyMeta function', t => { + t.plan(4) + + const findMyWay = FindMyWay({ + buildPrettyMeta: route => { + return { metaKey: route.method === 'GET' ? route.path : 'not a GET route' } + } + }) + const namedFunction = () => {} + const store = { + onRequest: [() => {}, namedFunction], + onTimeout: [() => {}], + genericMeta: 'meta', + mixedMeta: ['mixed items', { an: 'object' }], + objectMeta: { one: '1', two: 2 }, + functionMeta: namedFunction + } + + store[Symbol('symbolKey')] = Symbol('symbolValue') + + findMyWay.on('GET', '/test', () => {}, store) + findMyWay.on('GET', '/test', { constraints: { host: 'auth.fastify.io' } }, () => {}, store) + findMyWay.on('GET', '/test/:hello', () => {}, store) + findMyWay.on('PUT', '/test/:hello', () => {}, store) + findMyWay.on('GET', '/test/:hello', { constraints: { version: '1.1.2' } }, () => {}) + findMyWay.on('GET', '/test/:hello', { constraints: { version: '2.0.0' } }, () => {}) + + const arrayTree = findMyWay.prettyPrint({ + method: 'GET', + commonPrefix: false, + includeMeta: true + }) + const arrayExpected = `\ +└── /test (GET) + • (metaKey) "/test" + /test (GET) {"host":"auth.fastify.io"} + • (metaKey) "/test" + └── /:hello (GET) + • (metaKey) "/test/:hello" + /:hello (GET) {"version":"1.1.2"} + • (metaKey) "/test/:hello" + /:hello (GET) {"version":"2.0.0"} + • (metaKey) "/test/:hello" +` + const radixTree = findMyWay.prettyPrint({ + method: 'GET', + includeMeta: true + }) + const radixExpected = `\ +└── / + └── test (GET) + • (metaKey) "/test" + test (GET) {"host":"auth.fastify.io"} + • (metaKey) "/test" + └── / + └── :hello (GET) + • (metaKey) "/test/:hello" + :hello (GET) {"version":"1.1.2"} + • (metaKey) "/test/:hello" + :hello (GET) {"version":"2.0.0"} + • (metaKey) "/test/:hello" +` + t.assert.equal(typeof arrayTree, 'string') + t.assert.equal(arrayTree, arrayExpected) + + t.assert.equal(typeof radixTree, 'string') + t.assert.equal(radixTree, radixExpected) +}) diff --git a/services/slides/node_modules/find-my-way/test/pretty-print.test.js b/services/slides/node_modules/find-my-way/test/pretty-print.test.js new file mode 100644 index 0000000000000000000000000000000000000000..d02e8d43e0d8876955f022025dadaab8ab37a5c5 --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/pretty-print.test.js @@ -0,0 +1,680 @@ +'use strict' + +const { test } = require('node:test') +const FindMyWay = require('../') + +test('pretty print - empty tree', t => { + t.plan(2) + + const findMyWay = FindMyWay() + const tree = findMyWay.prettyPrint() + + const expected = '(empty tree)' + t.assert.equal(typeof tree, 'string') + t.assert.equal(tree, expected) +}) + +test('pretty print - static routes', t => { + t.plan(2) + + const findMyWay = FindMyWay() + findMyWay.on('GET', '/test', () => {}) + findMyWay.on('GET', '/test/hello', () => {}) + findMyWay.on('GET', '/hello/world', () => {}) + + const tree = findMyWay.prettyPrint() + const expected = `\ +└── / + ├── test (GET) + │ └── /hello (GET) + └── hello/world (GET) +` + t.assert.equal(typeof tree, 'string') + t.assert.equal(tree, expected) +}) + +test('pretty print - parametric routes', t => { + t.plan(2) + + const findMyWay = FindMyWay() + findMyWay.on('GET', '/test', () => {}) + findMyWay.on('GET', '/test/:hello', () => {}) + findMyWay.on('GET', '/hello/:world', () => {}) + + const tree = findMyWay.prettyPrint() + const expected = `\ +└── / + ├── test (GET) + │ └── / + │ └── :hello (GET) + └── hello/ + └── :world (GET) +` + t.assert.equal(typeof tree, 'string') + t.assert.equal(tree, expected) +}) + +test('pretty print - parametric routes', t => { + t.plan(2) + + const findMyWay = FindMyWay() + findMyWay.on('GET', '/static', () => {}) + findMyWay.on('GET', '/static/:param/suffix1', () => {}) + findMyWay.on('GET', '/static/:param(123)/suffix2', () => {}) + findMyWay.on('GET', '/static/:param(123).end/suffix3', () => {}) + findMyWay.on('GET', '/static/:param1(123).:param2(456)/suffix4', () => {}) + + const tree = findMyWay.prettyPrint() + const expected = `\ +└── / + └── static (GET) + └── / + ├── :param(123).end + │ └── /suffix3 (GET) + ├── :param(123) + │ └── /suffix2 (GET) + ├── :param1(123).:param2(456) + │ └── /suffix4 (GET) + └── :param + └── /suffix1 (GET) +` + t.assert.equal(typeof tree, 'string') + t.assert.equal(tree, expected) +}) + +test('pretty print - parametric routes', t => { + t.plan(2) + + const findMyWay = FindMyWay() + findMyWay.on('GET', '/static', () => {}) + findMyWay.on('GET', '/static/:param/suffix1', () => {}) + findMyWay.on('GET', '/static/:param(123)/suffix2', () => {}) + findMyWay.on('GET', '/static/:param(123).end/suffix3', () => {}) + findMyWay.on('GET', '/static/:param1(123).:param2(456)/suffix4', () => {}) + + const tree = findMyWay.prettyPrint({ commonPrefix: false }) + const expected = `\ +└── /static (GET) + ├── /:param(123).end/suffix3 (GET) + ├── /:param(123)/suffix2 (GET) + ├── /:param1(123).:param2(456)/suffix4 (GET) + └── /:param/suffix1 (GET) +` + t.assert.equal(typeof tree, 'string') + t.assert.equal(tree, expected) +}) + +test('pretty print - mixed parametric routes', t => { + t.plan(2) + + const findMyWay = FindMyWay() + findMyWay.on('GET', '/test', () => {}) + findMyWay.on('GET', '/test/:hello', () => {}) + findMyWay.on('POST', '/test/:hello', () => {}) + findMyWay.on('GET', '/test/:hello/world', () => {}) + + const tree = findMyWay.prettyPrint() + const expected = `\ +└── / + └── test (GET) + └── / + └── :hello (GET, POST) + └── /world (GET) +` + t.assert.equal(typeof tree, 'string') + t.assert.equal(tree, expected) +}) + +test('pretty print - wildcard routes', t => { + t.plan(2) + + const findMyWay = FindMyWay() + findMyWay.on('GET', '/test', () => {}) + findMyWay.on('GET', '/test/*', () => {}) + findMyWay.on('GET', '/hello/*', () => {}) + + const tree = findMyWay.prettyPrint() + const expected = `\ +└── / + ├── test (GET) + │ └── / + │ └── * (GET) + └── hello/ + └── * (GET) +` + t.assert.equal(typeof tree, 'string') + t.assert.equal(tree, expected) +}) + +test('pretty print - parametric routes with same parent and followed by a static route which has the same prefix with the former routes', t => { + t.plan(2) + + const findMyWay = FindMyWay() + findMyWay.on('GET', '/test', () => {}) + findMyWay.on('GET', '/test/hello/:id', () => {}) + findMyWay.on('POST', '/test/hello/:id', () => {}) + findMyWay.on('GET', '/test/helloworld', () => {}) + + const tree = findMyWay.prettyPrint() + const expected = `\ +└── / + └── test (GET) + └── /hello + ├── / + │ └── :id (GET, POST) + └── world (GET) +` + t.assert.equal(typeof tree, 'string') + t.assert.equal(tree, expected) +}) + +test('pretty print - constrained parametric routes', t => { + t.plan(2) + + const findMyWay = FindMyWay() + findMyWay.on('GET', '/test', () => {}) + findMyWay.on('GET', '/test', { constraints: { host: 'auth.fastify.io' } }, () => {}) + findMyWay.on('GET', '/test/:hello', () => {}) + findMyWay.on('GET', '/test/:hello', { constraints: { version: '1.1.2' } }, () => {}) + findMyWay.on('GET', '/test/:hello', { constraints: { version: '2.0.0' } }, () => {}) + + const tree = findMyWay.prettyPrint() + const expected = `\ +└── / + └── test (GET) + test (GET) {"host":"auth.fastify.io"} + └── / + └── :hello (GET) + :hello (GET) {"version":"1.1.2"} + :hello (GET) {"version":"2.0.0"} +` + t.assert.equal(typeof tree, 'string') + t.assert.equal(tree, expected) +}) + +test('pretty print - multiple parameters are drawn appropriately', t => { + t.plan(2) + + const findMyWay = FindMyWay() + findMyWay.on('GET', '/test', () => {}) + // routes with a nested parameter (i.e. no handler for the /:param) were breaking the display + findMyWay.on('GET', '/test/:hello/there/:ladies', () => {}) + findMyWay.on('GET', '/test/:hello/there/:ladies/and/:gents', () => {}) + findMyWay.on('GET', '/test/are/:you/:ready/to/:rock', () => {}) + + const tree = findMyWay.prettyPrint({ commonPrefix: false }) + const expected = `\ +└── /test (GET) + ├── /are/:you/:ready/to/:rock (GET) + └── /:hello/there/:ladies (GET) + └── /and/:gents (GET) +` + t.assert.equal(typeof tree, 'string') + t.assert.equal(tree, expected) +}) + +test('pretty print - multiple parameters are drawn appropriately', t => { + t.plan(2) + + const findMyWay = FindMyWay() + findMyWay.on('GET', '/test', () => {}) + // routes with a nested parameter (i.e. no handler for the /:param) were breaking the display + findMyWay.on('GET', '/test/:hello/there/:ladies', () => {}) + findMyWay.on('GET', '/test/:hello/there/:ladies/and/:gents', () => {}) + findMyWay.on('GET', '/test/are/:you/:ready/to/:rock', () => {}) + + const tree = findMyWay.prettyPrint({ commonPrefix: false }) + const expected = `\ +└── /test (GET) + ├── /are/:you/:ready/to/:rock (GET) + └── /:hello/there/:ladies (GET) + └── /and/:gents (GET) +` + t.assert.equal(typeof tree, 'string') + t.assert.equal(tree, expected) +}) + +test('pretty print commonPrefix - use routes array to draw flattened routes', t => { + t.plan(4) + + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/test', () => {}) + findMyWay.on('GET', '/test/hello', () => {}) + findMyWay.on('GET', '/testing', () => {}) + findMyWay.on('GET', '/testing/:param', () => {}) + findMyWay.on('PUT', '/update', () => {}) + + const radixTree = findMyWay.prettyPrint({ commonPrefix: true }) + const arrayTree = findMyWay.prettyPrint({ commonPrefix: false }) + + const radixExpected = `\ +└── / + ├── test (GET) + │ ├── /hello (GET) + │ └── ing (GET) + │ └── / + │ └── :param (GET) + └── update (PUT) +` + + const arrayExpected = `\ +├── /test (GET) +│ ├── /hello (GET) +│ └── ing (GET) +│ └── /:param (GET) +└── /update (PUT) +` + + t.assert.equal(typeof radixTree, 'string') + t.assert.equal(radixTree, radixExpected) + + t.assert.equal(typeof arrayTree, 'string') + t.assert.equal(arrayTree, arrayExpected) +}) + +test('pretty print commonPrefix - handle wildcard root', t => { + t.plan(2) + + const findMyWay = FindMyWay() + + findMyWay.on('OPTIONS', '*', () => {}) + findMyWay.on('GET', '/test/hello', () => {}) + findMyWay.on('GET', '/testing', () => {}) + findMyWay.on('GET', '/testing/:param', () => {}) + findMyWay.on('PUT', '/update', () => {}) + + const arrayTree = findMyWay.prettyPrint({ commonPrefix: false }) + const arrayExpected = `\ +├── /test/hello (GET) +├── /testing (GET) +│ └── /:param (GET) +├── /update (PUT) +└── * (OPTIONS) +` + t.assert.equal(typeof arrayTree, 'string') + t.assert.equal(arrayTree, arrayExpected) +}) + +test('pretty print commonPrefix - handle wildcard root', t => { + t.plan(2) + + const findMyWay = FindMyWay() + + findMyWay.on('GET', '*', () => {}) + findMyWay.on('GET', '/test/hello', () => {}) + findMyWay.on('GET', '/testing', () => {}) + findMyWay.on('GET', '/testing/:param', () => {}) + findMyWay.on('PUT', '/update', () => {}) + + const radixTree = findMyWay.prettyPrint() + const radixExpected = `\ +└── (empty root node) + ├── / + │ ├── test + │ │ ├── /hello (GET) + │ │ └── ing (GET) + │ │ └── / + │ │ └── :param (GET) + │ └── update (PUT) + └── * (GET) +` + t.assert.equal(typeof radixTree, 'string') + t.assert.equal(radixTree, radixExpected) +}) + +test('pretty print commonPrefix - handle constrained routes', t => { + t.plan(2) + + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/test', () => {}) + findMyWay.on('GET', '/test', { constraints: { host: 'auth.fastify.io' } }, () => {}) + findMyWay.on('GET', '/test/:hello', () => {}) + findMyWay.on('PUT', '/test/:hello', () => {}) + findMyWay.on('GET', '/test/:hello', { constraints: { version: '1.1.2' } }, () => {}) + findMyWay.on('GET', '/test/:hello', { constraints: { version: '2.0.0' } }, () => {}) + + const arrayTree = findMyWay.prettyPrint({ commonPrefix: false }) + const arrayExpected = `\ +└── /test (GET) + /test (GET) {"host":"auth.fastify.io"} + └── /:hello (GET, PUT) + /:hello (GET) {"version":"1.1.2"} + /:hello (GET) {"version":"2.0.0"} +` + t.assert.equal(typeof arrayTree, 'string') + t.assert.equal(arrayTree, arrayExpected) +}) + +test('pretty print commonPrefix - handle method constraint', t => { + t.plan(2) + + const findMyWay = FindMyWay() + findMyWay.addConstraintStrategy({ + name: 'method', + storage: function () { + const handlers = {} + return { + get: (type) => { return handlers[type] || null }, + set: (type, store) => { handlers[type] = store } + } + }, + deriveConstraint: (req) => req.headers['x-method'], + mustMatchWhenDerived: true + }) + + findMyWay.on('GET', '/test', () => {}) + findMyWay.on('GET', '/test', { constraints: { method: 'foo' } }, () => {}) + findMyWay.on('GET', '/test/:hello', () => {}) + findMyWay.on('PUT', '/test/:hello', () => {}) + findMyWay.on('GET', '/test/:hello', { constraints: { method: 'bar' } }, () => {}) + findMyWay.on('GET', '/test/:hello', { constraints: { method: 'baz' } }, () => {}) + + const arrayTree = findMyWay.prettyPrint({ + commonPrefix: false, + methodConstraintName: 'methodOverride' + }) + + const arrayExpected = `\ +└── /test (GET) + /test (GET) {"method":"foo"} + └── /:hello (GET, PUT) + /:hello (GET) {"method":"bar"} + /:hello (GET) {"method":"baz"} +` + t.assert.equal(typeof arrayTree, 'string') + t.assert.equal(arrayTree, arrayExpected) +}) + +test('pretty print includeMeta - commonPrefix: true', t => { + t.plan(6) + + const findMyWay = FindMyWay() + const namedFunction = () => {} + const store = { + onRequest: [() => {}, namedFunction], + onTimeout: [() => {}], + genericMeta: 'meta', + mixedMeta: ['mixed items', { an: 'object' }], + objectMeta: { one: '1', two: 2 }, + functionMeta: namedFunction + } + + store[Symbol('symbolKey')] = Symbol('symbolValue') + + findMyWay.on('GET', '/test', () => {}, store) + findMyWay.on('GET', '/test', { constraints: { host: 'auth.fastify.io' } }, () => {}, store) + findMyWay.on('GET', '/testing/:hello', () => {}, store) + findMyWay.on('PUT', '/tested/:hello', () => {}, store) + findMyWay.on('GET', '/test/:hello', { constraints: { version: '1.1.2' } }, () => {}) + findMyWay.on('GET', '/test/:hello', { constraints: { version: '2.0.0' } }, () => {}) + + const radixTree = findMyWay.prettyPrint({ commonPrefix: true, includeMeta: true }) + const radixTreeExpected = `\ +└── / + └── test (GET) + • (onRequest) ["anonymous()","namedFunction()"] + • (onTimeout) ["anonymous()"] + • (genericMeta) "meta" + • (mixedMeta) ["mixed items",{"an":"object"}] + • (objectMeta) {"one":"1","two":2} + • (functionMeta) "namedFunction()" + • (Symbol(symbolKey)) "Symbol(symbolValue)" + test (GET) {"host":"auth.fastify.io"} + • (onRequest) ["anonymous()","namedFunction()"] + • (onTimeout) ["anonymous()"] + • (genericMeta) "meta" + • (mixedMeta) ["mixed items",{"an":"object"}] + • (objectMeta) {"one":"1","two":2} + • (functionMeta) "namedFunction()" + • (Symbol(symbolKey)) "Symbol(symbolValue)" + ├── ing/ + │ └── :hello (GET) + │ • (onRequest) ["anonymous()","namedFunction()"] + │ • (onTimeout) ["anonymous()"] + │ • (genericMeta) "meta" + │ • (mixedMeta) ["mixed items",{"an":"object"}] + │ • (objectMeta) {"one":"1","two":2} + │ • (functionMeta) "namedFunction()" + │ • (Symbol(symbolKey)) "Symbol(symbolValue)" + ├── ed/ + │ └── :hello (PUT) + │ • (onRequest) ["anonymous()","namedFunction()"] + │ • (onTimeout) ["anonymous()"] + │ • (genericMeta) "meta" + │ • (mixedMeta) ["mixed items",{"an":"object"}] + │ • (objectMeta) {"one":"1","two":2} + │ • (functionMeta) "namedFunction()" + │ • (Symbol(symbolKey)) "Symbol(symbolValue)" + └── / + └── :hello (GET) {"version":"1.1.2"} + :hello (GET) {"version":"2.0.0"} +` + const radixTreeSpecific = findMyWay.prettyPrint({ commonPrefix: true, includeMeta: ['onTimeout', 'objectMeta', 'nonExistent'] }) + const radixTreeSpecificExpected = `\ +└── / + └── test (GET) + • (onTimeout) ["anonymous()"] + • (objectMeta) {"one":"1","two":2} + test (GET) {"host":"auth.fastify.io"} + • (onTimeout) ["anonymous()"] + • (objectMeta) {"one":"1","two":2} + ├── ing/ + │ └── :hello (GET) + │ • (onTimeout) ["anonymous()"] + │ • (objectMeta) {"one":"1","two":2} + ├── ed/ + │ └── :hello (PUT) + │ • (onTimeout) ["anonymous()"] + │ • (objectMeta) {"one":"1","two":2} + └── / + └── :hello (GET) {"version":"1.1.2"} + :hello (GET) {"version":"2.0.0"} +` + const radixTreeNoMeta = findMyWay.prettyPrint({ commonPrefix: true, includeMeta: false }) + const radixTreeNoMetaExpected = `\ +└── / + └── test (GET) + test (GET) {"host":"auth.fastify.io"} + ├── ing/ + │ └── :hello (GET) + ├── ed/ + │ └── :hello (PUT) + └── / + └── :hello (GET) {"version":"1.1.2"} + :hello (GET) {"version":"2.0.0"} +` + t.assert.equal(typeof radixTree, 'string') + t.assert.equal(radixTree, radixTreeExpected) + + t.assert.equal(typeof radixTreeSpecific, 'string') + t.assert.equal(radixTreeSpecific, radixTreeSpecificExpected) + + t.assert.equal(typeof radixTreeNoMeta, 'string') + t.assert.equal(radixTreeNoMeta, radixTreeNoMetaExpected) +}) + +test('pretty print includeMeta - commonPrefix: false', t => { + t.plan(6) + + const findMyWay = FindMyWay() + const namedFunction = () => {} + const store = { + onRequest: [() => {}, namedFunction], + onTimeout: [() => {}], + onError: null, + onRegister: undefined, + genericMeta: 'meta', + mixedMeta: ['mixed items', { an: 'object' }], + objectMeta: { one: '1', two: 2 }, + functionMeta: namedFunction + } + + store[Symbol('symbolKey')] = Symbol('symbolValue') + + findMyWay.on('GET', '/test', () => {}, store) + findMyWay.on('GET', '/test', { constraints: { host: 'auth.fastify.io' } }, () => {}, store) + findMyWay.on('GET', '/testing/:hello', () => {}, store) + findMyWay.on('PUT', '/tested/:hello', () => {}, store) + findMyWay.on('GET', '/test/:hello', { constraints: { version: '1.1.2' } }, () => {}) + findMyWay.on('GET', '/test/:hello', { constraints: { version: '2.0.0' } }, () => {}) + + const arrayTree = findMyWay.prettyPrint({ commonPrefix: false, includeMeta: true }) + const arrayExpected = `\ +└── /test (GET) + • (onRequest) ["anonymous()","namedFunction()"] + • (onTimeout) ["anonymous()"] + • (genericMeta) "meta" + • (mixedMeta) ["mixed items",{"an":"object"}] + • (objectMeta) {"one":"1","two":2} + • (functionMeta) "namedFunction()" + • (Symbol(symbolKey)) "Symbol(symbolValue)" + /test (GET) {"host":"auth.fastify.io"} + • (onRequest) ["anonymous()","namedFunction()"] + • (onTimeout) ["anonymous()"] + • (genericMeta) "meta" + • (mixedMeta) ["mixed items",{"an":"object"}] + • (objectMeta) {"one":"1","two":2} + • (functionMeta) "namedFunction()" + • (Symbol(symbolKey)) "Symbol(symbolValue)" + ├── ing/:hello (GET) + │ • (onRequest) ["anonymous()","namedFunction()"] + │ • (onTimeout) ["anonymous()"] + │ • (genericMeta) "meta" + │ • (mixedMeta) ["mixed items",{"an":"object"}] + │ • (objectMeta) {"one":"1","two":2} + │ • (functionMeta) "namedFunction()" + │ • (Symbol(symbolKey)) "Symbol(symbolValue)" + ├── ed/:hello (PUT) + │ • (onRequest) ["anonymous()","namedFunction()"] + │ • (onTimeout) ["anonymous()"] + │ • (genericMeta) "meta" + │ • (mixedMeta) ["mixed items",{"an":"object"}] + │ • (objectMeta) {"one":"1","two":2} + │ • (functionMeta) "namedFunction()" + │ • (Symbol(symbolKey)) "Symbol(symbolValue)" + └── /:hello (GET) {"version":"1.1.2"} + /:hello (GET) {"version":"2.0.0"} +` + const arraySpecific = findMyWay.prettyPrint({ commonPrefix: false, includeMeta: ['onRequest', 'mixedMeta', 'nonExistent'] }) + const arraySpecificExpected = `\ +└── /test (GET) + • (onRequest) ["anonymous()","namedFunction()"] + • (mixedMeta) ["mixed items",{"an":"object"}] + /test (GET) {"host":"auth.fastify.io"} + • (onRequest) ["anonymous()","namedFunction()"] + • (mixedMeta) ["mixed items",{"an":"object"}] + ├── ing/:hello (GET) + │ • (onRequest) ["anonymous()","namedFunction()"] + │ • (mixedMeta) ["mixed items",{"an":"object"}] + ├── ed/:hello (PUT) + │ • (onRequest) ["anonymous()","namedFunction()"] + │ • (mixedMeta) ["mixed items",{"an":"object"}] + └── /:hello (GET) {"version":"1.1.2"} + /:hello (GET) {"version":"2.0.0"} +` + const arrayNoMeta = findMyWay.prettyPrint({ commonPrefix: false, includeMeta: false }) + const arrayNoMetaExpected = `\ +└── /test (GET) + /test (GET) {"host":"auth.fastify.io"} + ├── ing/:hello (GET) + ├── ed/:hello (PUT) + └── /:hello (GET) {"version":"1.1.2"} + /:hello (GET) {"version":"2.0.0"} +` + + t.assert.equal(typeof arrayTree, 'string') + t.assert.equal(arrayTree, arrayExpected) + + t.assert.equal(typeof arraySpecific, 'string') + t.assert.equal(arraySpecific, arraySpecificExpected) + + t.assert.equal(typeof arrayNoMeta, 'string') + t.assert.equal(arrayNoMeta, arrayNoMetaExpected) +}) + +test('pretty print includeMeta - buildPrettyMeta function', t => { + t.plan(4) + + const findMyWay = FindMyWay({ + buildPrettyMeta: route => { + return { metaKey: route.method === 'PUT' ? 'Hide PUT route path' : route.path } + } + }) + const namedFunction = () => {} + const store = { + onRequest: [() => {}, namedFunction], + onTimeout: [() => {}], + genericMeta: 'meta', + mixedMeta: ['mixed items', { an: 'object' }], + objectMeta: { one: '1', two: 2 }, + functionMeta: namedFunction + } + + store[Symbol('symbolKey')] = Symbol('symbolValue') + + findMyWay.on('GET', '/test', () => {}, store) + findMyWay.on('GET', '/test', { constraints: { host: 'auth.fastify.io' } }, () => {}, store) + findMyWay.on('GET', '/test/:hello', () => {}, store) + findMyWay.on('PUT', '/test/:hello', () => {}, store) + findMyWay.on('POST', '/test/:hello', () => {}, store) + findMyWay.on('GET', '/test/:hello', { constraints: { version: '1.1.2' } }, () => {}) + findMyWay.on('GET', '/test/:hello', { constraints: { version: '2.0.0' } }, () => {}) + + const arrayTree = findMyWay.prettyPrint({ commonPrefix: false, includeMeta: true }) + const arrayExpected = `\ +└── /test (GET) + • (metaKey) "/test" + /test (GET) {"host":"auth.fastify.io"} + • (metaKey) "/test" + └── /:hello (GET, POST) + • (metaKey) "/test/:hello" + /:hello (PUT) + • (metaKey) "Hide PUT route path" + /:hello (GET) {"version":"1.1.2"} + • (metaKey) "/test/:hello" + /:hello (GET) {"version":"2.0.0"} + • (metaKey) "/test/:hello" +` + const radixTree = findMyWay.prettyPrint({ includeMeta: true }) + const radixExpected = `\ +└── / + └── test (GET) + • (metaKey) "/test" + test (GET) {"host":"auth.fastify.io"} + • (metaKey) "/test" + └── / + └── :hello (GET, POST) + • (metaKey) "/test/:hello" + :hello (PUT) + • (metaKey) "Hide PUT route path" + :hello (GET) {"version":"1.1.2"} + • (metaKey) "/test/:hello" + :hello (GET) {"version":"2.0.0"} + • (metaKey) "/test/:hello" +` + t.assert.equal(typeof arrayTree, 'string') + t.assert.equal(arrayTree, arrayExpected) + + t.assert.equal(typeof radixTree, 'string') + t.assert.equal(radixTree, radixExpected) +}) + +test('pretty print - print all methods', t => { + t.plan(2) + + const findMyWay = FindMyWay() + findMyWay.all('/test', () => {}) + + const tree = findMyWay.prettyPrint() + const expected = `\ +└── / + └── test (ACL, BIND, CHECKOUT, CONNECT, COPY, DELETE, GET, HEAD, LINK, LOCK, \ +M-SEARCH, MERGE, MKACTIVITY, MKCALENDAR, MKCOL, MOVE, NOTIFY, OPTIONS, PATCH, \ +POST, PROPFIND, PROPPATCH, PURGE, PUT, QUERY, REBIND, REPORT, SEARCH, SOURCE, \ +SUBSCRIBE, TRACE, UNBIND, UNLINK, UNLOCK, UNSUBSCRIBE) +` + t.assert.equal(typeof tree, 'string') + t.assert.equal(tree, expected) +}) diff --git a/services/slides/node_modules/find-my-way/test/querystring.test.js b/services/slides/node_modules/find-my-way/test/querystring.test.js new file mode 100644 index 0000000000000000000000000000000000000000..5b89521d9d12e9cc39c3cad4b7e620bb1547d90e --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/querystring.test.js @@ -0,0 +1,54 @@ +'use strict' + +const { test } = require('node:test') +const FindMyWay = require('../') + +test('should sanitize the url - query', t => { + t.plan(2) + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/test', (req, res, params, store, query) => { + t.assert.deepEqual(query, { hello: 'world' }) + t.assert.ok('inside the handler') + }) + + findMyWay.lookup({ method: 'GET', url: '/test?hello=world', headers: {} }, null) +}) + +test('should sanitize the url - hash', t => { + t.plan(2) + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/test', (req, res, params, store, query) => { + t.assert.deepEqual(query, { hello: '' }) + t.assert.ok('inside the handler') + }) + + findMyWay.lookup({ method: 'GET', url: '/test#hello', headers: {} }, null) +}) + +test('handles path and query separated by ; with useSemicolonDelimiter enabled', t => { + t.plan(2) + const findMyWay = FindMyWay({ + useSemicolonDelimiter: true + }) + + findMyWay.on('GET', '/test', (req, res, params, store, query) => { + t.assert.deepEqual(query, { jsessionid: '123456' }) + t.assert.ok('inside the handler') + }) + + findMyWay.lookup({ method: 'GET', url: '/test;jsessionid=123456', headers: {} }, null) +}) + +test('handles path and query separated by ? using ; in the path', t => { + t.plan(2) + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/test;jsessionid=123456', (req, res, params, store, query) => { + t.assert.deepEqual(query, { foo: 'bar' }) + t.assert.ok('inside the handler') + }) + + findMyWay.lookup({ method: 'GET', url: '/test;jsessionid=123456?foo=bar', headers: {} }, null) +}) diff --git a/services/slides/node_modules/find-my-way/test/regex.test.js b/services/slides/node_modules/find-my-way/test/regex.test.js new file mode 100644 index 0000000000000000000000000000000000000000..f73b97bd9f98e7d9c1652545abcc01dfff15e06f --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/regex.test.js @@ -0,0 +1,269 @@ +'use strict' + +const { test } = require('node:test') +const FindMyWay = require('../') + +test('route with matching regex', t => { + t.plan(1) + const findMyWay = FindMyWay({ + defaultRoute: () => { + t.assert.fail('route not matched') + } + }) + + findMyWay.on('GET', '/test/:id(^\\d+$)', () => { + t.assert.ok('regex match') + }) + + findMyWay.lookup({ method: 'GET', url: '/test/12', headers: {} }, null) +}) + +test('route without matching regex', t => { + t.plan(1) + const findMyWay = FindMyWay({ + defaultRoute: () => { + t.assert.ok('route not matched') + } + }) + + findMyWay.on('GET', '/test/:id(^\\d+$)', () => { + t.assert.fail('regex match') + }) + + findMyWay.lookup({ method: 'GET', url: '/test/test', headers: {} }, null) +}) + +test('route with an extension regex 2', t => { + t.plan(2) + const findMyWay = FindMyWay({ + defaultRoute: (req) => { + t.assert.fail(`route not matched: ${req.url}`) + } + }) + findMyWay.on('GET', '/test/S/:file(^\\S+).png', () => { + t.assert.ok('regex match') + }) + findMyWay.on('GET', '/test/D/:file(^\\D+).png', () => { + t.assert.ok('regex match') + }) + findMyWay.lookup({ method: 'GET', url: '/test/S/foo.png', headers: {} }, null) + findMyWay.lookup({ method: 'GET', url: '/test/D/foo.png', headers: {} }, null) +}) + +test('nested route with matching regex', t => { + t.plan(1) + const findMyWay = FindMyWay({ + defaultRoute: () => { + t.assert.fail('route not matched') + } + }) + + findMyWay.on('GET', '/test/:id(^\\d+$)/hello', () => { + t.assert.ok('regex match') + }) + + findMyWay.lookup({ method: 'GET', url: '/test/12/hello', headers: {} }, null) +}) + +test('mixed nested route with matching regex', t => { + t.plan(2) + const findMyWay = FindMyWay({ + defaultRoute: () => { + t.assert.fail('route not matched') + } + }) + + findMyWay.on('GET', '/test/:id(^\\d+$)/hello/:world', (req, res, params) => { + t.assert.equal(params.id, '12') + t.assert.equal(params.world, 'world') + }) + + findMyWay.lookup({ method: 'GET', url: '/test/12/hello/world', headers: {} }, null) +}) + +test('mixed nested route with double matching regex', t => { + t.plan(2) + const findMyWay = FindMyWay({ + defaultRoute: () => { + t.assert.fail('route not matched') + } + }) + + findMyWay.on('GET', '/test/:id(^\\d+$)/hello/:world(^\\d+$)', (req, res, params) => { + t.assert.equal(params.id, '12') + t.assert.equal(params.world, '15') + }) + + findMyWay.lookup({ method: 'GET', url: '/test/12/hello/15', headers: {} }, null) +}) + +test('mixed nested route without double matching regex', t => { + t.plan(1) + const findMyWay = FindMyWay({ + defaultRoute: () => { + t.assert.ok('route not matched') + } + }) + + findMyWay.on('GET', '/test/:id(^\\d+$)/hello/:world(^\\d+$)', (req, res, params) => { + t.assert.fail('route mathed') + }) + + findMyWay.lookup({ method: 'GET', url: '/test/12/hello/test', headers: {} }, null) +}) + +test('route with an extension regex', t => { + t.plan(1) + const findMyWay = FindMyWay({ + defaultRoute: () => { + t.assert.fail('route not matched') + } + }) + + findMyWay.on('GET', '/test/:file(^\\d+).png', () => { + t.assert.ok('regex match') + }) + + findMyWay.lookup({ method: 'GET', url: '/test/12.png', headers: {} }, null) +}) + +test('route with an extension regex - no match', t => { + t.plan(1) + const findMyWay = FindMyWay({ + defaultRoute: () => { + t.assert.ok('route not matched') + } + }) + + findMyWay.on('GET', '/test/:file(^\\d+).png', () => { + t.assert.fail('regex match') + }) + + findMyWay.lookup({ method: 'GET', url: '/test/aa.png', headers: {} }, null) +}) + +test('safe decodeURIComponent', t => { + t.plan(1) + const findMyWay = FindMyWay({ + defaultRoute: () => { + t.assert.ok('route not matched') + } + }) + + findMyWay.on('GET', '/test/:id(^\\d+$)', () => { + t.assert.fail('we should not be here') + }) + + t.assert.deepEqual( + findMyWay.find('GET', '/test/hel%"Flo', {}), + null + ) +}) + +test('Should check if a regex is safe to use', t => { + t.plan(13) + + const noop = () => {} + + // https://github.com/substack/safe-regex/blob/master/test/regex.js + const good = [ + /\bOakland\b/, + /\b(Oakland|San Francisco)\b/i, + /^\d+1337\d+$/i, + /^\d+(1337|404)\d+$/i, + /^\d+(1337|404)*\d+$/i, + RegExp(Array(26).join('a?') + Array(26).join('a')) + ] + + const bad = [ + /^(a?){25}(a){25}$/, + RegExp(Array(27).join('a?') + Array(27).join('a')), + /(x+x+)+y/, + /foo|(x+x+)+y/, + /(a+){10}y/, + /(a+){2}y/, + /(.*){1,32000}[bc]/ + ] + + const findMyWay = FindMyWay() + + good.forEach(regex => { + try { + findMyWay.on('GET', `/test/:id(${regex.toString()})`, noop) + t.assert.ok('ok') + findMyWay.off('GET', `/test/:id(${regex.toString()})`) + } catch (err) { + t.assert.fail(err) + } + }) + + bad.forEach(regex => { + try { + findMyWay.on('GET', `/test/:id(${regex.toString()})`, noop) + t.assert.fail('should throw') + } catch (err) { + t.assert.ok(err) + } + }) +}) + +test('Disable safe regex check', t => { + t.plan(13) + + const noop = () => {} + + // https://github.com/substack/safe-regex/blob/master/test/regex.js + const good = [ + /\bOakland\b/, + /\b(Oakland|San Francisco)\b/i, + /^\d+1337\d+$/i, + /^\d+(1337|404)\d+$/i, + /^\d+(1337|404)*\d+$/i, + RegExp(Array(26).join('a?') + Array(26).join('a')) + ] + + const bad = [ + /^(a?){25}(a){25}$/, + RegExp(Array(27).join('a?') + Array(27).join('a')), + /(x+x+)+y/, + /foo|(x+x+)+y/, + /(a+){10}y/, + /(a+){2}y/, + /(.*){1,32000}[bc]/ + ] + + const findMyWay = FindMyWay({ allowUnsafeRegex: true }) + + good.forEach(regex => { + try { + findMyWay.on('GET', `/test/:id(${regex.toString()})`, noop) + t.assert.ok('ok') + findMyWay.off('GET', `/test/:id(${regex.toString()})`) + } catch (err) { + t.assert.fail(err) + } + }) + + bad.forEach(regex => { + try { + findMyWay.on('GET', `/test/:id(${regex.toString()})`, noop) + t.assert.ok('ok') + findMyWay.off('GET', `/test/:id(${regex.toString()})`) + } catch (err) { + t.assert.fail(err) + } + }) +}) + +test('prevent back-tracking', { timeout: 20 }, (t) => { + t.plan(0) + + const findMyWay = FindMyWay({ + defaultRoute: () => { + t.assert.fail('route not matched') + } + }) + + findMyWay.on('GET', '/:foo-:bar-', (req, res, params) => {}) + findMyWay.find('GET', '/' + '-'.repeat(16000) + 'a', { host: 'fastify.io' }) +}) diff --git a/services/slides/node_modules/find-my-way/test/repro-issue-414.test.js b/services/slides/node_modules/find-my-way/test/repro-issue-414.test.js new file mode 100644 index 0000000000000000000000000000000000000000..26ebc7287e45191de2a2b4f869a6c5a09963e247 --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/repro-issue-414.test.js @@ -0,0 +1,57 @@ +'use strict' + +const { test } = require('node:test') +const FindMyWay = require('../') + +test('should return null when maxParamLength is exceeded (current behavior)', t => { + t.plan(1) + const findMyWay = FindMyWay({ maxParamLength: 5 }) + findMyWay.on('GET', '/test/:param', () => 'param') + + const handle = findMyWay.find('GET', '/test/123456') + t.assert.equal(handle, null) +}) + +test('should still match other routes if one parametric route exceeds maxParamLength (static)', t => { + t.plan(2) + const findMyWay = FindMyWay({ maxParamLength: 5 }) + findMyWay.on('GET', '/test/:param', () => 'param') + findMyWay.on('GET', '/test/special', () => 'special') + + const handle = findMyWay.find('GET', '/test/special') + t.assert.ok(handle) + t.assert.equal(handle.handler(), 'special') +}) + +test('should fail to match any route if the only candidate exceeds maxParamLength', t => { + t.plan(1) + const findMyWay = FindMyWay({ maxParamLength: 5 }) + findMyWay.on('GET', '/test/:param', () => 'param') + + const handle = findMyWay.find('GET', '/test/123456789') + t.assert.equal(handle, null) +}) + +test('should match wildcard if parametric exceeds maxParamLength', t => { + t.plan(2) + const findMyWay = FindMyWay({ maxParamLength: 5 }) + findMyWay.on('GET', '/test/:param', () => 'param') + findMyWay.on('GET', '/test/*', () => 'wildcard') + + const handle = findMyWay.find('GET', '/test/123456789') + t.assert.ok(handle) + t.assert.equal(handle.handler(), 'wildcard') +}) + +test('should return custom onMaxParamLength handler if provided and no other route matches', t => { + t.plan(2) + const findMyWay = FindMyWay({ + maxParamLength: 5, + onMaxParamLength: (path, req, res) => 'custom error' + }) + findMyWay.on('GET', '/test/:param', () => 'param') + + const handle = findMyWay.find('GET', '/test/123456') + t.assert.ok(handle) + t.assert.equal(handle.handler(), 'custom error') +}) diff --git a/services/slides/node_modules/find-my-way/test/routes-registered.test.js b/services/slides/node_modules/find-my-way/test/routes-registered.test.js new file mode 100644 index 0000000000000000000000000000000000000000..dd5eef2ec4e374efd15f0573d6f696babb8086ce --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/routes-registered.test.js @@ -0,0 +1,45 @@ +'use strict' + +const { test } = require('node:test') +const FindMyWay = require('../') + +function initializeRoutes (router, handler, quantity) { + for (const x of Array(quantity).keys()) { + router.on('GET', '/test-route-' + x, handler) + } + return router +} + +test('verify routes registered', t => { + const assertPerTest = 5 + const quantity = 5 + // 1 (check length) + quantity of routes * quantity of tests per route + t.plan(1 + (quantity * assertPerTest)) + + let findMyWay = FindMyWay() + const defaultHandler = (req, res, params) => res.end(JSON.stringify({ hello: 'world' })) + + findMyWay = initializeRoutes(findMyWay, defaultHandler, quantity) + t.assert.equal(findMyWay.routes.length, quantity) + findMyWay.routes.forEach((route, idx) => { + t.assert.equal(route.method, 'GET') + t.assert.equal(route.path, '/test-route-' + idx) + t.assert.deepStrictEqual(route.opts, {}) + t.assert.equal(route.handler, defaultHandler) + t.assert.equal(route.store, undefined) + }) +}) + +test('verify routes registered and deregister', t => { + // 1 (check length) + quantity of routes * quantity of tests per route + t.plan(2) + + let findMyWay = FindMyWay() + const quantity = 2 + const defaultHandler = (req, res, params) => res.end(JSON.stringify({ hello: 'world' })) + + findMyWay = initializeRoutes(findMyWay, defaultHandler, quantity) + t.assert.equal(findMyWay.routes.length, quantity) + findMyWay.off('GET', '/test-route-0') + t.assert.equal(findMyWay.routes.length, quantity - 1) +}) diff --git a/services/slides/node_modules/find-my-way/test/server.test.js b/services/slides/node_modules/find-my-way/test/server.test.js new file mode 100644 index 0000000000000000000000000000000000000000..55087ac7b6824deeb85c3f0b5d925482f9c0fbb7 --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/server.test.js @@ -0,0 +1,368 @@ +'use strict' + +const { test } = require('node:test') +const http = require('http') +const FindMyWay = require('../') + +test('basic router with http server', (t, done) => { + t.plan(6) + const findMyWay = FindMyWay() + findMyWay.on('GET', '/test', (req, res, params) => { + t.assert.ok(req) + t.assert.ok(res) + t.assert.ok(params) + res.end(JSON.stringify({ hello: 'world' })) + }) + + const server = http.createServer((req, res) => { + findMyWay.lookup(req, res) + }) + + server.listen(0, async err => { + t.assert.ifError(err) + server.unref() + + const res = await fetch(`http://localhost:${server.address().port}/test`) + + t.assert.equal(res.status, 200) + t.assert.deepEqual(await res.json(), { hello: 'world' }) + done() + }) +}) + +test('router with params with http server', (t, done) => { + t.plan(6) + const findMyWay = FindMyWay() + findMyWay.on('GET', '/test/:id', (req, res, params) => { + t.assert.ok(req) + t.assert.ok(res) + t.assert.equal(params.id, 'hello') + res.end(JSON.stringify({ hello: 'world' })) + }) + + const server = http.createServer((req, res) => { + findMyWay.lookup(req, res) + }) + + server.listen(0, async err => { + t.assert.ifError(err) + server.unref() + + const res = await fetch(`http://localhost:${server.address().port}/test/hello`) + + t.assert.equal(res.status, 200) + t.assert.deepEqual(await res.json(), { hello: 'world' }) + done() + }) +}) + +test('default route', (t, done) => { + t.plan(2) + const findMyWay = FindMyWay({ + defaultRoute: (req, res) => { + res.statusCode = 404 + res.end() + } + }) + + const server = http.createServer((req, res) => { + findMyWay.lookup(req, res) + }) + + server.listen(0, async err => { + t.assert.ifError(err) + server.unref() + + const res = await fetch(`http://localhost:${server.address().port}`) + t.assert.equal(res.status, 404) + done() + }) +}) + +test('automatic default route', (t, done) => { + t.plan(2) + const findMyWay = FindMyWay() + + const server = http.createServer((req, res) => { + findMyWay.lookup(req, res) + }) + + server.listen(0, async err => { + t.assert.ifError(err) + server.unref() + + const res = await fetch(`http://localhost:${server.address().port}`) + t.assert.equal(res.status, 404) + done() + }) +}) + +test('maps two routes when trailing slash should be trimmed', (t, done) => { + t.plan(21) + const findMyWay = FindMyWay({ + ignoreTrailingSlash: true + }) + + findMyWay.on('GET', '/test/', (req, res, params) => { + t.assert.ok(req) + t.assert.ok(res) + t.assert.ok(params) + res.end('test') + }) + + findMyWay.on('GET', '/othertest', (req, res, params) => { + t.assert.ok(req) + t.assert.ok(res) + t.assert.ok(params) + res.end('othertest') + }) + + const server = http.createServer((req, res) => { + findMyWay.lookup(req, res) + }) + + server.listen(0, async err => { + t.assert.ifError(err) + server.unref() + + const baseURL = 'http://localhost:' + server.address().port + + let res = await fetch(`${baseURL}/test/`) + t.assert.equal(res.status, 200) + t.assert.deepEqual(await res.text(), 'test') + + res = await fetch(`${baseURL}/test`) + t.assert.equal(res.status, 200) + t.assert.deepEqual(await res.text(), 'test') + + res = await fetch(`${baseURL}/othertest`) + t.assert.equal(res.status, 200) + t.assert.deepEqual(await res.text(), 'othertest') + + res = await fetch(`${baseURL}/othertest/`) + t.assert.equal(res.status, 200) + t.assert.deepEqual(await res.text(), 'othertest') + + done() + }) +}) + +test('does not trim trailing slash when ignoreTrailingSlash is false', (t, done) => { + t.plan(7) + const findMyWay = FindMyWay({ + ignoreTrailingSlash: false + }) + + findMyWay.on('GET', '/test/', (req, res, params) => { + t.assert.ok(req) + t.assert.ok(res) + t.assert.ok(params) + res.end('test') + }) + + const server = http.createServer((req, res) => { + findMyWay.lookup(req, res) + }) + + server.listen(0, async err => { + t.assert.ifError(err) + server.unref() + + const baseURL = 'http://localhost:' + server.address().port + + let res = await fetch(`${baseURL}/test/`) + t.assert.equal(res.status, 200) + t.assert.deepEqual(await res.text(), 'test') + + res = await fetch(`${baseURL}/test`) + t.assert.equal(res.status, 404) + + done() + }) +}) + +test('does not map // when ignoreTrailingSlash is true', (t, done) => { + t.plan(7) + const findMyWay = FindMyWay({ + ignoreTrailingSlash: false + }) + + findMyWay.on('GET', '/', (req, res, params) => { + t.assert.ok(req) + t.assert.ok(res) + t.assert.ok(params) + res.end('test') + }) + + const server = http.createServer((req, res) => { + findMyWay.lookup(req, res) + }) + + server.listen(0, async err => { + t.assert.ifError(err) + server.unref() + + const baseURL = 'http://localhost:' + server.address().port + + let res = await fetch(`${baseURL}/`) + t.assert.equal(res.status, 200) + t.assert.deepEqual(await res.text(), 'test') + + res = await fetch(`${baseURL}//`) + t.assert.equal(res.status, 404) + + done() + }) +}) + +test('maps two routes when duplicate slashes should be trimmed', (t, done) => { + t.plan(21) + const findMyWay = FindMyWay({ + ignoreDuplicateSlashes: true + }) + + findMyWay.on('GET', '//test', (req, res, params) => { + t.assert.ok(req) + t.assert.ok(res) + t.assert.ok(params) + res.end('test') + }) + + findMyWay.on('GET', '/othertest', (req, res, params) => { + t.assert.ok(req) + t.assert.ok(res) + t.assert.ok(params) + res.end('othertest') + }) + + const server = http.createServer((req, res) => { + findMyWay.lookup(req, res) + }) + + server.listen(0, async err => { + t.assert.ifError(err) + server.unref() + + const baseURL = 'http://localhost:' + server.address().port + + let res = await fetch(`${baseURL}//test`) + t.assert.equal(res.status, 200) + t.assert.deepEqual(await res.text(), 'test') + + res = await fetch(`${baseURL}/test`) + t.assert.equal(res.status, 200) + t.assert.deepEqual(await res.text(), 'test') + + res = await fetch(`${baseURL}/othertest`) + t.assert.equal(res.status, 200) + t.assert.deepEqual(await res.text(), 'othertest') + + res = await fetch(`${baseURL}//othertest`) + t.assert.equal(res.status, 200) + t.assert.deepEqual(await res.text(), 'othertest') + + done() + }) +}) + +test('does not trim duplicate slashes when ignoreDuplicateSlashes is false', (t, done) => { + t.plan(7) + const findMyWay = FindMyWay({ + ignoreDuplicateSlashes: false + }) + + findMyWay.on('GET', '//test', (req, res, params) => { + t.assert.ok(req) + t.assert.ok(res) + t.assert.ok(params) + res.end('test') + }) + + const server = http.createServer((req, res) => { + findMyWay.lookup(req, res) + }) + + server.listen(0, async err => { + t.assert.ifError(err) + server.unref() + + const baseURL = 'http://localhost:' + server.address().port + + let res = await fetch(`${baseURL}//test`) + t.assert.equal(res.status, 200) + t.assert.deepEqual(await res.text(), 'test') + + res = await fetch(`${baseURL}/test`) + t.assert.equal(res.status, 404) + + done() + }) +}) + +test('does map // when ignoreDuplicateSlashes is true', (t, done) => { + t.plan(11) + const findMyWay = FindMyWay({ + ignoreDuplicateSlashes: true + }) + + findMyWay.on('GET', '/', (req, res, params) => { + t.assert.ok(req) + t.assert.ok(res) + t.assert.ok(params) + res.end('test') + }) + + const server = http.createServer((req, res) => { + findMyWay.lookup(req, res) + }) + + server.listen(0, async err => { + t.assert.ifError(err) + server.unref() + + const baseURL = 'http://localhost:' + server.address().port + + let res = await fetch(`${baseURL}/`) + t.assert.equal(res.status, 200) + t.assert.deepEqual(await res.text(), 'test') + + res = await fetch(`${baseURL}//`) + t.assert.equal(res.status, 200) + t.assert.deepEqual(await res.text(), 'test') + + done() + }) +}) + +test('versioned routes', (t, done) => { + t.plan(3) + + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/test', { constraints: { version: '1.2.3' } }, (req, res, params) => { + res.end('ok') + }) + + const server = http.createServer((req, res) => { + findMyWay.lookup(req, res) + }) + + server.listen(0, async err => { + t.assert.ifError(err) + server.unref() + + let res = await fetch(`http://localhost:${server.address().port}/test`, { + headers: { 'Accept-Version': '1.2.3' } + }) + + t.assert.equal(res.status, 200) + + res = await fetch(`http://localhost:${server.address().port}/test`, { + headers: { 'Accept-Version': '2.x' } + }) + + t.assert.equal(res.status, 404) + + done() + }) +}) diff --git a/services/slides/node_modules/find-my-way/test/shorthands.test.js b/services/slides/node_modules/find-my-way/test/shorthands.test.js new file mode 100644 index 0000000000000000000000000000000000000000..f2998a20162f93a15527d06f117072275cfc53c9 --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/shorthands.test.js @@ -0,0 +1,44 @@ +'use strict' + +const httpMethods = require('../lib/http-methods') +const { describe, test } = require('node:test') +const FindMyWay = require('../') + +describe('should support shorthand', t => { + for (const i in httpMethods) { + const m = httpMethods[i] + const methodName = m.toLowerCase() + + test('`.' + methodName + '`', t => { + t.plan(1) + const findMyWay = FindMyWay() + + findMyWay[methodName]('/test', () => { + t.assert.ok('inside the handler') + }) + + findMyWay.lookup({ method: m, url: '/test', headers: {} }, null) + }) + } +}) + +test('should support `.all` shorthand', t => { + t.plan(11) + const findMyWay = FindMyWay() + + findMyWay.all('/test', () => { + t.assert.ok('inside the handler') + }) + + findMyWay.lookup({ method: 'GET', url: '/test', headers: {} }, null) + findMyWay.lookup({ method: 'DELETE', url: '/test', headers: {} }, null) + findMyWay.lookup({ method: 'HEAD', url: '/test', headers: {} }, null) + findMyWay.lookup({ method: 'PATCH', url: '/test', headers: {} }, null) + findMyWay.lookup({ method: 'POST', url: '/test', headers: {} }, null) + findMyWay.lookup({ method: 'PUT', url: '/test', headers: {} }, null) + findMyWay.lookup({ method: 'OPTIONS', url: '/test', headers: {} }, null) + findMyWay.lookup({ method: 'TRACE', url: '/test', headers: {} }, null) + findMyWay.lookup({ method: 'CONNECT', url: '/test', headers: {} }, null) + findMyWay.lookup({ method: 'COPY', url: '/test', headers: {} }, null) + findMyWay.lookup({ method: 'SUBSCRIBE', url: '/test', headers: {} }, null) +}) diff --git a/services/slides/node_modules/find-my-way/test/store.test.js b/services/slides/node_modules/find-my-way/test/store.test.js new file mode 100644 index 0000000000000000000000000000000000000000..51ca8c5412f972e82b6cd156aa664f24cee470ce --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/store.test.js @@ -0,0 +1,49 @@ +'use strict' + +const { test } = require('node:test') +const FindMyWay = require('../') + +test('handler should have the store object', t => { + t.plan(1) + const findMyWay = FindMyWay() + + findMyWay.on('GET', '/test', (req, res, params, store) => { + t.assert.equal(store.hello, 'world') + }, { hello: 'world' }) + + findMyWay.lookup({ method: 'GET', url: '/test', headers: {} }, null) +}) + +test('find a store object', t => { + t.plan(1) + const findMyWay = FindMyWay() + const fn = () => {} + + findMyWay.on('GET', '/test', fn, { hello: 'world' }) + + t.assert.deepEqual(findMyWay.find('GET', '/test'), { + handler: fn, + params: {}, + store: { hello: 'world' }, + searchParams: {} + }) +}) + +test('update the store', t => { + t.plan(2) + const findMyWay = FindMyWay() + let bool = false + + findMyWay.on('GET', '/test', (req, res, params, store) => { + if (!bool) { + t.assert.equal(store.hello, 'world') + store.hello = 'hello' + bool = true + findMyWay.lookup({ method: 'GET', url: '/test', headers: {} }, null) + } else { + t.assert.equal(store.hello, 'hello') + } + }, { hello: 'world' }) + + findMyWay.lookup({ method: 'GET', url: '/test', headers: {} }, null) +}) diff --git a/services/slides/node_modules/find-my-way/test/types/router.test-d.ts b/services/slides/node_modules/find-my-way/test/types/router.test-d.ts new file mode 100644 index 0000000000000000000000000000000000000000..3d206292e4a0a39e97d635ba74eeea77420a5219 --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/types/router.test-d.ts @@ -0,0 +1,188 @@ +import { expectType } from 'tsd' +import Router from '../../' +import { Http2ServerRequest, Http2ServerResponse } from 'http2' +import { IncomingMessage, ServerResponse } from 'http' + +let http1Req!: IncomingMessage; +let http1Res!: ServerResponse; +let http2Req!: Http2ServerRequest; +let http2Res!: Http2ServerResponse; +let ctx!: { req: IncomingMessage; res: ServerResponse }; +let done!: (err: Error | null, result: any) => void; + +expectType(Router.sanitizeUrlPath('/hello/%20world?foo=bar')) +expectType(Router.sanitizeUrlPath('/hello/%23world;foo=bar', true)) +expectType(Router.removeDuplicateSlashes('//hello///world')) +expectType(Router.trimLastSlash('/hello/')) + +// HTTP1 +{ + let handler!: Router.Handler + const router = Router({ + ignoreTrailingSlash: true, + ignoreDuplicateSlashes: true, + allowUnsafeRegex: false, + caseSensitive: false, + maxParamLength: 42, + querystringParser: (queryString) => {}, + defaultRoute (http1Req, http1Res) {}, + onBadUrl (path, http1Req, http1Res) {}, + onMaxParamLength (path, http1Req, http1Res) {}, + constraints: { + foo: { + name: 'foo', + mustMatchWhenDerived: true, + storage () { + return { + get (version) { return handler }, + set (version, handler) {} + } + }, + deriveConstraint(req) { return '1.0.0' }, + validate(value) { if (typeof value === "string") { throw new Error("invalid")} } + } + } + }) + expectType>(router) + + expectType(router.on('GET', '/', () => {})) + expectType(router.on(['GET', 'POST'], '/', () => {})) + expectType(router.on('GET', '/', { constraints: { version: '1.0.0' }}, () => {})) + expectType(router.on('GET', '/', () => {}, {})) + expectType(router.on('GET', '/', {constraints: { version: '1.0.0' }}, () => {}, {})) + + expectType(router.get('/', () => {})) + expectType(router.get('/', { constraints: { version: '1.0.0' }}, () => {})) + expectType(router.get('/', () => {}, {})) + expectType(router.get('/', { constraints: { version: '1.0.0' }}, () => {}, {})) + + expectType(router.off('GET', '/')) + expectType(router.off(['GET', 'POST'], '/')) + + expectType(router.lookup(http1Req, http1Res)) + expectType(router.lookup(http1Req, http1Res, done)); + expectType(router.lookup(http1Req, http1Res, ctx, done)); + expectType | null>(router.find('GET', '/')) + expectType | null>(router.find('GET', '/', {})) + expectType | null>(router.find('GET', '/', {version: '1.0.0'})) + + expectType | null>(router.findRoute('GET', '/')); + expectType | null>(router.findRoute('GET', '/', {})); + expectType | null>(router.findRoute('GET', '/', {version: '1.0.0'})); + + expectType(router.reset()) + expectType(router.prettyPrint()) + expectType(router.prettyPrint({ method: 'GET' })) + expectType(router.prettyPrint({ commonPrefix: false })) + expectType(router.prettyPrint({ commonPrefix: true })) + expectType(router.prettyPrint({ includeMeta: true })) + expectType(router.prettyPrint({ includeMeta: ['test', Symbol('test')] })) +} + +// HTTP2 +{ + const constraints: { [key: string]: Router.ConstraintStrategy } = { + foo: { + name: 'foo', + mustMatchWhenDerived: true, + storage () { + return { + get (version) { return handler }, + set (version, handler) {} + } + }, + deriveConstraint(req) { return '1.0.0' }, + validate(value) { if (typeof value === "string") { throw new Error("invalid")} } + } + } + + let handler!: Router.Handler + const router = Router({ + ignoreTrailingSlash: true, + ignoreDuplicateSlashes: true, + allowUnsafeRegex: false, + caseSensitive: false, + maxParamLength: 42, + querystringParser: (queryString) => {}, + defaultRoute (http1Req, http1Res) {}, + onBadUrl (path, http1Req, http1Res) {}, + constraints + }) + expectType>(router) + + expectType(router.on('GET', '/', () => {})) + expectType(router.on(['GET', 'POST'], '/', () => {})) + expectType(router.on('GET', '/', { constraints: { version: '1.0.0' }}, () => {})) + expectType(router.on('GET', '/', () => {}, {})) + expectType(router.on('GET', '/', { constraints: { version: '1.0.0' }}, () => {}, {})) + + expectType(router.addConstraintStrategy(constraints.foo)) + + expectType(router.get('/', () => {})) + expectType(router.get('/', { constraints: { version: '1.0.0' }}, () => {})) + expectType(router.get('/', () => {}, {})) + expectType(router.get('/', { constraints: { version: '1.0.0' }}, () => {}, {})) + + expectType(router.off('GET', '/')) + expectType(router.off(['GET', 'POST'], '/')) + + expectType(router.lookup(http2Req, http2Res)) + expectType(router.lookup(http2Req, http2Res, done)); + expectType(router.lookup(http2Req, http2Res, ctx, done)); + expectType | null>(router.find('GET', '/', {})) + expectType | null>(router.find('GET', '/', {version: '1.0.0', host: 'fastify.io'})) + + expectType(router.reset()) + expectType(router.prettyPrint()) + +} + +// Custom Constraint +{ + let handler!: Router.Handler + + interface AcceptAndContentType { accept?: string, contentType?: string } + + const customConstraintWithObject: Router.ConstraintStrategy = { + name: "customConstraintWithObject", + deriveConstraint(req: Router.Req, ctx: Context | undefined): AcceptAndContentType { + return { + accept: req.headers.accept, + contentType: req.headers["content-type"] + } + }, + validate(value: unknown): void {}, + storage () { + return { + get (version) { return handler }, + set (version, handler) {} + } + } + } + + const storageWithObject = customConstraintWithObject.storage() + const acceptAndContentType: AcceptAndContentType = { accept: 'application/json', contentType: 'application/xml' } + + expectType(customConstraintWithObject.deriveConstraint(http1Req, http1Res)) + expectType | null>(storageWithObject.get(acceptAndContentType)); + expectType(storageWithObject.set(acceptAndContentType, () => {})); + + const customConstraintWithDefault: Router.ConstraintStrategy = { + name: "customConstraintWithObject", + deriveConstraint(req: Router.Req, ctx: Context | undefined): string { + return req.headers.accept ?? '' + }, + storage () { + return { + get (version) { return handler }, + set (version, handler) {} + } + } + } + + const storageWithDefault = customConstraintWithDefault.storage() + + expectType(customConstraintWithDefault.deriveConstraint(http1Req, http1Res)) + expectType | null>(storageWithDefault.get('')); + expectType(storageWithDefault.set('', () => {})); +} diff --git a/services/slides/node_modules/find-my-way/test/url-sanitizer.test.js b/services/slides/node_modules/find-my-way/test/url-sanitizer.test.js new file mode 100644 index 0000000000000000000000000000000000000000..1dcad2599b5eadc0e53a8d23c338e1dd2ece411a --- /dev/null +++ b/services/slides/node_modules/find-my-way/test/url-sanitizer.test.js @@ -0,0 +1,43 @@ +'use strict' + +const { test } = require('node:test') +const FindMyWay = require('..') + +test('sanitizeUrlPath should decode reserved characters inside params and strip querystring', t => { + t.plan(1) + + const url = '/%65ncod%65d?foo=bar' + const sanitized = FindMyWay.sanitizeUrlPath(url) + + t.assert.equal(sanitized, '/encoded') +}) + +test('sanitizeUrlPath should decode non-reserved characters but keep reserved encoded when not in params', t => { + t.plan(1) + + const url = '/hello/%20world?foo=bar' + const sanitized = FindMyWay.sanitizeUrlPath(url) + + t.assert.equal(sanitized, '/hello/ world') +}) + +test('sanitizeUrlPath should treat semicolon as queryparameter delimiter when enabled', t => { + t.plan(2) + + const url = '/hello/%23world;foo=bar' + + const sanitizedWithDelimiter = FindMyWay.sanitizeUrlPath(url, true) + t.assert.equal(sanitizedWithDelimiter, '/hello/#world') + + const sanitizedWithoutDelimiter = FindMyWay.sanitizeUrlPath(url, false) + t.assert.equal(sanitizedWithoutDelimiter, '/hello/#world;foo=bar') +}) + +test('sanitizeUrlPath trigger an error if the url is invalid', t => { + t.plan(1) + + const url = '/Hello%3xWorld/world' + t.assert.throws(() => { + FindMyWay.sanitizeUrlPath(url) + }, 'URIError: URI malformed') +}) diff --git a/services/slides/node_modules/https/package.json b/services/slides/node_modules/https/package.json new file mode 100644 index 0000000000000000000000000000000000000000..d63f9ce72daf3490cc4a6b345af6ac00fbdc189b --- /dev/null +++ b/services/slides/node_modules/https/package.json @@ -0,0 +1,15 @@ +{ + "name": "https", + "version": "1.0.0", + "description": "https mediation", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [ + "https", + "mediation" + ], + "author": "hardus van der berg (http://www.sunfork.com)", + "license": "ISC" +} diff --git a/services/slides/node_modules/image-size/LICENSE b/services/slides/node_modules/image-size/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..8bdffcff7d22994d4a32e4f6afead300d2b01377 --- /dev/null +++ b/services/slides/node_modules/image-size/LICENSE @@ -0,0 +1,9 @@ +The MIT License (MIT) + +Copyright © 2013-Present Aditya Yadav, http://netroy.in + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/services/slides/node_modules/image-size/Readme.md b/services/slides/node_modules/image-size/Readme.md new file mode 100644 index 0000000000000000000000000000000000000000..b02c008de323bb87929a3fed870a0a386c199b04 --- /dev/null +++ b/services/slides/node_modules/image-size/Readme.md @@ -0,0 +1,184 @@ +# image-size + +[![Build Status](https://circleci.com/gh/image-size/image-size.svg?style=shield)](https://circleci.com/gh/image-size/image-size) +[![Package Version](https://img.shields.io/npm/v/image-size.svg)](https://www.npmjs.com/package/image-size) +[![Downloads](https://img.shields.io/npm/dm/image-size.svg)](http://npm-stat.com/charts.html?package=image-size&author=&from=&to=) + +A [Node](https://nodejs.org/en/) module to get dimensions of any image file + +## Supported formats + +- BMP +- CUR +- DDS +- GIF +- HEIC (HEIF, AVCI, AVIF) +- ICNS +- ICO +- J2C +- JPEG-2000 (JP2) +- JPEG +- JPEG-XL +- KTX (1 and 2) +- PNG +- PNM (PAM, PBM, PFM, PGM, PPM) +- PSD +- SVG +- TGA +- TIFF +- WebP + +## Programmatic Usage + +```shell +npm install image-size --save +``` + +or + +```shell +yarn add image-size +``` + +### Synchronous + +```javascript +const sizeOf = require("image-size") +const dimensions = sizeOf("images/funny-cats.png") +console.log(dimensions.width, dimensions.height) +``` + +### Asynchronous + +```javascript +const sizeOf = require("image-size") +sizeOf("images/funny-cats.png", function (err, dimensions) { + console.log(dimensions.width, dimensions.height) +}) +``` + +NOTE: The asynchronous version doesn't work if the input is a Buffer. Use synchronous version instead. + +Also, the asynchronous functions have a default concurrency limit of **100** +To change this limit, you can call the `setConcurrency` function like this: + +```javascript +const sizeOf = require("image-size") +sizeOf.setConcurrency(123456) +``` + +### Using promises (nodejs 10.x+) + +```javascript +const { promisify } = require("util") +const sizeOf = promisify(require("image-size")) +sizeOf("images/funny-cats.png") + .then((dimensions) => { + console.log(dimensions.width, dimensions.height) + }) + .catch((err) => console.error(err)) +``` + +### Async/Await (Typescript & ES7) + +```javascript +const { promisify } = require("util") +const sizeOf = promisify(require("image-size"))(async () => { + try { + const dimensions = await sizeOf("images/funny-cats.png") + console.log(dimensions.width, dimensions.height) + } catch (err) { + console.error(err) + } +})().then((c) => console.log(c)) +``` + +### Multi-size + +If the target file is an icon (.ico) or a cursor (.cur), the `width` and `height` will be the ones of the first found image. + +An additional `images` array is available and returns the dimensions of all the available images + +```javascript +const sizeOf = require("image-size") +const images = sizeOf("images/multi-size.ico").images +for (const dimensions of images) { + console.log(dimensions.width, dimensions.height) +} +``` + +### Using a URL + +```javascript +const url = require("url") +const http = require("http") + +const sizeOf = require("image-size") + +const imgUrl = "http://my-amazing-website.com/image.jpeg" +const options = url.parse(imgUrl) + +http.get(options, function (response) { + const chunks = [] + response + .on("data", function (chunk) { + chunks.push(chunk) + }) + .on("end", function () { + const buffer = Buffer.concat(chunks) + console.log(sizeOf(buffer)) + }) +}) +``` + +You can optionally check the buffer lengths & stop downloading the image after a few kilobytes. +**You don't need to download the entire image** + +### Disabling certain image types + +```javascript +const imageSize = require("image-size") +imageSize.disableTypes(["tiff", "ico"]) +``` + +### Disabling all file-system reads + +```javascript +const imageSize = require("image-size") +imageSize.disableFS(true) +``` + +### JPEG image orientation + +If the orientation is present in the JPEG EXIF metadata, it will be returned by the function. The orientation value is a [number between 1 and 8](https://exiftool.org/TagNames/EXIF.html#:~:text=0x0112,8%20=%20Rotate%20270%20CW) representing a type of orientation. + +```javascript +const sizeOf = require("image-size") +const dimensions = sizeOf("images/photo.jpeg") +console.log(dimensions.orientation) +``` + +## Command-Line Usage (CLI) + +```shell +npm install image-size --global +``` + +or + +```shell +yarn global add image-size +``` + +followed by + +```shell +image-size image1 [image2] [image3] ... +``` + +## Credits + +not a direct port, but an attempt to have something like +[dabble's imagesize](https://github.com/dabble/imagesize/blob/master/lib/image_size.rb) as a node module. + +## [Contributors](Contributors.md) diff --git a/services/slides/node_modules/image-size/bin/image-size.js b/services/slides/node_modules/image-size/bin/image-size.js new file mode 100644 index 0000000000000000000000000000000000000000..f8ba67694e30bf0cddd1028f2cfde28ae0cae6f6 --- /dev/null +++ b/services/slides/node_modules/image-size/bin/image-size.js @@ -0,0 +1,53 @@ +#!/usr/bin/env node +/* eslint-disable @typescript-eslint/no-var-requires */ +'use strict' + +const fs = require('fs') +const path = require('path') +const { imageSize } = require('..') + +const files = process.argv.slice(2) + +if (!files.length) { + console.error('Usage: image-size image1 [image2] [image3] ...') + process.exit(-1) +} + +const red = ['\x1B[31m', '\x1B[39m'] +// const bold = ['\x1B[1m', '\x1B[22m'] +const grey = ['\x1B[90m', '\x1B[39m'] +const green = ['\x1B[32m', '\x1B[39m'] + +function colorize(text, color) { + return color[0] + text + color[1] +} + +files.forEach(function (image) { + try { + if (fs.existsSync(path.resolve(image))) { + const greyX = colorize('x', grey) + const greyImage = colorize(image, grey) + const size = imageSize(image) + const sizes = size.images || [size] + sizes.forEach((size) => { + let greyType = '' + if (size.type) { + greyType = colorize(' (' + size.type + ')', grey) + } + console.info( + colorize(size.width, green) + + greyX + + colorize(size.height, green) + + ' - ' + + greyImage + + greyType, + ) + }) + } else { + console.error("file doesn't exist - ", image) + } + } catch (e) { + // console.error(e.stack) + console.error(colorize(e.message, red), '-', image) + } +}) diff --git a/services/slides/node_modules/image-size/dist/detector.d.ts b/services/slides/node_modules/image-size/dist/detector.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..d16665dba867a3a0266f8a25939e7f028fb569e1 --- /dev/null +++ b/services/slides/node_modules/image-size/dist/detector.d.ts @@ -0,0 +1,2 @@ +import type { imageType } from './types/index'; +export declare function detector(input: Uint8Array): imageType | undefined; diff --git a/services/slides/node_modules/image-size/dist/detector.js b/services/slides/node_modules/image-size/dist/detector.js new file mode 100644 index 0000000000000000000000000000000000000000..6b9ef6f78b905eed1469035e979b10bf8172b411 --- /dev/null +++ b/services/slides/node_modules/image-size/dist/detector.js @@ -0,0 +1,30 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.detector = void 0; +const index_1 = require("./types/index"); +const keys = Object.keys(index_1.typeHandlers); +// This map helps avoid validating for every single image type +const firstBytes = { + 0x38: 'psd', + 0x42: 'bmp', + 0x44: 'dds', + 0x47: 'gif', + 0x49: 'tiff', + 0x4d: 'tiff', + 0x52: 'webp', + 0x69: 'icns', + 0x89: 'png', + 0xff: 'jpg', +}; +function detector(input) { + const byte = input[0]; + if (byte in firstBytes) { + const type = firstBytes[byte]; + if (type && index_1.typeHandlers[type].validate(input)) { + return type; + } + } + const finder = (key) => index_1.typeHandlers[key].validate(input); + return keys.find(finder); +} +exports.detector = detector; diff --git a/services/slides/node_modules/image-size/dist/index.d.ts b/services/slides/node_modules/image-size/dist/index.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..091ae255c88220f9b785decce0266f34f59c6722 --- /dev/null +++ b/services/slides/node_modules/image-size/dist/index.d.ts @@ -0,0 +1,10 @@ +import type { imageType } from './types/index'; +import type { ISizeCalculationResult } from './types/interface'; +type CallbackFn = (e: Error | null, r?: ISizeCalculationResult) => void; +export default imageSize; +export declare function imageSize(input: Uint8Array | string): ISizeCalculationResult; +export declare function imageSize(input: string, callback: CallbackFn): void; +export declare const disableFS: (v: boolean) => void; +export declare const disableTypes: (types: imageType[]) => void; +export declare const setConcurrency: (c: number) => void; +export declare const types: string[]; diff --git a/services/slides/node_modules/image-size/dist/index.js b/services/slides/node_modules/image-size/dist/index.js new file mode 100644 index 0000000000000000000000000000000000000000..6b0ca64fd536ea49da2908707b8f749153e5d98c --- /dev/null +++ b/services/slides/node_modules/image-size/dist/index.js @@ -0,0 +1,129 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.types = exports.setConcurrency = exports.disableTypes = exports.disableFS = exports.imageSize = void 0; +const fs = require("fs"); +const path = require("path"); +const queue_1 = require("queue"); +const index_1 = require("./types/index"); +const detector_1 = require("./detector"); +// Maximum input size, with a default of 512 kilobytes. +// TO-DO: make this adaptive based on the initial signature of the image +const MaxInputSize = 512 * 1024; +// This queue is for async `fs` operations, to avoid reaching file-descriptor limits +const queue = new queue_1.default({ concurrency: 100, autostart: true }); +const globalOptions = { + disabledFS: false, + disabledTypes: [], +}; +/** + * Return size information based on an Uint8Array + * + * @param {Uint8Array} input + * @param {String} filepath + * @returns {Object} + */ +function lookup(input, filepath) { + // detect the file type.. don't rely on the extension + const type = (0, detector_1.detector)(input); + if (typeof type !== 'undefined') { + if (globalOptions.disabledTypes.indexOf(type) > -1) { + throw new TypeError('disabled file type: ' + type); + } + // find an appropriate handler for this file type + if (type in index_1.typeHandlers) { + const size = index_1.typeHandlers[type].calculate(input, filepath); + if (size !== undefined) { + size.type = size.type ?? type; + return size; + } + } + } + // throw up, if we don't understand the file + throw new TypeError('unsupported file type: ' + type + ' (file: ' + filepath + ')'); +} +/** + * Reads a file into an Uint8Array. + * @param {String} filepath + * @returns {Promise} + */ +async function readFileAsync(filepath) { + const handle = await fs.promises.open(filepath, 'r'); + try { + const { size } = await handle.stat(); + if (size <= 0) { + throw new Error('Empty file'); + } + const inputSize = Math.min(size, MaxInputSize); + const input = new Uint8Array(inputSize); + await handle.read(input, 0, inputSize, 0); + return input; + } + finally { + await handle.close(); + } +} +/** + * Synchronously reads a file into an Uint8Array, blocking the nodejs process. + * + * @param {String} filepath + * @returns {Uint8Array} + */ +function readFileSync(filepath) { + // read from the file, synchronously + const descriptor = fs.openSync(filepath, 'r'); + try { + const { size } = fs.fstatSync(descriptor); + if (size <= 0) { + throw new Error('Empty file'); + } + const inputSize = Math.min(size, MaxInputSize); + const input = new Uint8Array(inputSize); + fs.readSync(descriptor, input, 0, inputSize, 0); + return input; + } + finally { + fs.closeSync(descriptor); + } +} +// eslint-disable-next-line @typescript-eslint/no-use-before-define +module.exports = exports = imageSize; // backwards compatibility +exports.default = imageSize; +/** + * @param {Uint8Array|string} input - Uint8Array or relative/absolute path of the image file + * @param {Function=} [callback] - optional function for async detection + */ +function imageSize(input, callback) { + // Handle Uint8Array input + if (input instanceof Uint8Array) { + return lookup(input); + } + // input should be a string at this point + if (typeof input !== 'string' || globalOptions.disabledFS) { + throw new TypeError('invalid invocation. input should be a Uint8Array'); + } + // resolve the file path + const filepath = path.resolve(input); + if (typeof callback === 'function') { + queue.push(() => readFileAsync(filepath) + .then((input) => process.nextTick(callback, null, lookup(input, filepath))) + .catch(callback)); + } + else { + const input = readFileSync(filepath); + return lookup(input, filepath); + } +} +exports.imageSize = imageSize; +const disableFS = (v) => { + globalOptions.disabledFS = v; +}; +exports.disableFS = disableFS; +const disableTypes = (types) => { + globalOptions.disabledTypes = types; +}; +exports.disableTypes = disableTypes; +const setConcurrency = (c) => { + queue.concurrency = c; +}; +exports.setConcurrency = setConcurrency; +exports.types = Object.keys(index_1.typeHandlers); diff --git a/services/slides/node_modules/image-size/dist/types/bmp.d.ts b/services/slides/node_modules/image-size/dist/types/bmp.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..be1d22e1efe8282cca950674da0d6b392c548c87 --- /dev/null +++ b/services/slides/node_modules/image-size/dist/types/bmp.d.ts @@ -0,0 +1,2 @@ +import type { IImage } from './interface'; +export declare const BMP: IImage; diff --git a/services/slides/node_modules/image-size/dist/types/bmp.js b/services/slides/node_modules/image-size/dist/types/bmp.js new file mode 100644 index 0000000000000000000000000000000000000000..6f53b50d250fa80f109f8ae8ae7029708b3623db --- /dev/null +++ b/services/slides/node_modules/image-size/dist/types/bmp.js @@ -0,0 +1,11 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.BMP = void 0; +const utils_1 = require("./utils"); +exports.BMP = { + validate: (input) => (0, utils_1.toUTF8String)(input, 0, 2) === 'BM', + calculate: (input) => ({ + height: Math.abs((0, utils_1.readInt32LE)(input, 22)), + width: (0, utils_1.readUInt32LE)(input, 18), + }), +}; diff --git a/services/slides/node_modules/image-size/dist/types/cur.d.ts b/services/slides/node_modules/image-size/dist/types/cur.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..fc0dada3302f19e490867aaa3a75026ddeeee028 --- /dev/null +++ b/services/slides/node_modules/image-size/dist/types/cur.d.ts @@ -0,0 +1,2 @@ +import type { IImage } from './interface'; +export declare const CUR: IImage; diff --git a/services/slides/node_modules/image-size/dist/types/cur.js b/services/slides/node_modules/image-size/dist/types/cur.js new file mode 100644 index 0000000000000000000000000000000000000000..7bcc51259861bb2664fbf35284246feb056d99ad --- /dev/null +++ b/services/slides/node_modules/image-size/dist/types/cur.js @@ -0,0 +1,17 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.CUR = void 0; +const ico_1 = require("./ico"); +const utils_1 = require("./utils"); +const TYPE_CURSOR = 2; +exports.CUR = { + validate(input) { + const reserved = (0, utils_1.readUInt16LE)(input, 0); + const imageCount = (0, utils_1.readUInt16LE)(input, 4); + if (reserved !== 0 || imageCount === 0) + return false; + const imageType = (0, utils_1.readUInt16LE)(input, 2); + return imageType === TYPE_CURSOR; + }, + calculate: (input) => ico_1.ICO.calculate(input), +}; diff --git a/services/slides/node_modules/image-size/dist/types/dds.d.ts b/services/slides/node_modules/image-size/dist/types/dds.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..46e924645186cfc946644314b8cb25357a20f748 --- /dev/null +++ b/services/slides/node_modules/image-size/dist/types/dds.d.ts @@ -0,0 +1,2 @@ +import type { IImage } from './interface'; +export declare const DDS: IImage; diff --git a/services/slides/node_modules/image-size/dist/types/dds.js b/services/slides/node_modules/image-size/dist/types/dds.js new file mode 100644 index 0000000000000000000000000000000000000000..67f0b79d65d360397f810473bf442f906dab7c9b --- /dev/null +++ b/services/slides/node_modules/image-size/dist/types/dds.js @@ -0,0 +1,11 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.DDS = void 0; +const utils_1 = require("./utils"); +exports.DDS = { + validate: (input) => (0, utils_1.readUInt32LE)(input, 0) === 0x20534444, + calculate: (input) => ({ + height: (0, utils_1.readUInt32LE)(input, 12), + width: (0, utils_1.readUInt32LE)(input, 16), + }), +}; diff --git a/services/slides/node_modules/image-size/dist/types/gif.d.ts b/services/slides/node_modules/image-size/dist/types/gif.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..68445984f9394375c0f660cf00e1207f961fa9b2 --- /dev/null +++ b/services/slides/node_modules/image-size/dist/types/gif.d.ts @@ -0,0 +1,2 @@ +import type { IImage } from './interface'; +export declare const GIF: IImage; diff --git a/services/slides/node_modules/image-size/dist/types/gif.js b/services/slides/node_modules/image-size/dist/types/gif.js new file mode 100644 index 0000000000000000000000000000000000000000..d826c5c6f1db0897cfa30abc64b10ad033be8023 --- /dev/null +++ b/services/slides/node_modules/image-size/dist/types/gif.js @@ -0,0 +1,12 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.GIF = void 0; +const utils_1 = require("./utils"); +const gifRegexp = /^GIF8[79]a/; +exports.GIF = { + validate: (input) => gifRegexp.test((0, utils_1.toUTF8String)(input, 0, 6)), + calculate: (input) => ({ + height: (0, utils_1.readUInt16LE)(input, 8), + width: (0, utils_1.readUInt16LE)(input, 6), + }), +}; diff --git a/services/slides/node_modules/image-size/dist/types/heif.d.ts b/services/slides/node_modules/image-size/dist/types/heif.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..3d8893753ab5010df146f59761598142e536b372 --- /dev/null +++ b/services/slides/node_modules/image-size/dist/types/heif.d.ts @@ -0,0 +1,2 @@ +import type { IImage } from './interface'; +export declare const HEIF: IImage; diff --git a/services/slides/node_modules/image-size/dist/types/heif.js b/services/slides/node_modules/image-size/dist/types/heif.js new file mode 100644 index 0000000000000000000000000000000000000000..7997d3f159d60da91f73b498e59714298e0effd1 --- /dev/null +++ b/services/slides/node_modules/image-size/dist/types/heif.js @@ -0,0 +1,40 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.HEIF = void 0; +const utils_1 = require("./utils"); +const brandMap = { + avif: 'avif', + mif1: 'heif', + msf1: 'heif', // heif-sequence + heic: 'heic', + heix: 'heic', + hevc: 'heic', // heic-sequence + hevx: 'heic', // heic-sequence +}; +exports.HEIF = { + validate(input) { + const boxType = (0, utils_1.toUTF8String)(input, 4, 8); + if (boxType !== 'ftyp') + return false; + const ftypBox = (0, utils_1.findBox)(input, 'ftyp', 0); + if (!ftypBox) + return false; + const brand = (0, utils_1.toUTF8String)(input, ftypBox.offset + 8, ftypBox.offset + 12); + return brand in brandMap; + }, + calculate(input) { + // Based on https://nokiatech.github.io/heif/technical.html + const metaBox = (0, utils_1.findBox)(input, 'meta', 0); + const iprpBox = metaBox && (0, utils_1.findBox)(input, 'iprp', metaBox.offset + 12); + const ipcoBox = iprpBox && (0, utils_1.findBox)(input, 'ipco', iprpBox.offset + 8); + const ispeBox = ipcoBox && (0, utils_1.findBox)(input, 'ispe', ipcoBox.offset + 8); + if (ispeBox) { + return { + height: (0, utils_1.readUInt32BE)(input, ispeBox.offset + 16), + width: (0, utils_1.readUInt32BE)(input, ispeBox.offset + 12), + type: (0, utils_1.toUTF8String)(input, 8, 12), + }; + } + throw new TypeError('Invalid HEIF, no size found'); + }, +}; diff --git a/services/slides/node_modules/image-size/dist/types/icns.d.ts b/services/slides/node_modules/image-size/dist/types/icns.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..40701d4e901afc879292d6f09bf43a413033ad0f --- /dev/null +++ b/services/slides/node_modules/image-size/dist/types/icns.d.ts @@ -0,0 +1,2 @@ +import type { IImage } from './interface'; +export declare const ICNS: IImage; diff --git a/services/slides/node_modules/image-size/dist/types/icns.js b/services/slides/node_modules/image-size/dist/types/icns.js new file mode 100644 index 0000000000000000000000000000000000000000..f2bfafef3723cb423b110304815e56e3f97f81e0 --- /dev/null +++ b/services/slides/node_modules/image-size/dist/types/icns.js @@ -0,0 +1,101 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.ICNS = void 0; +const utils_1 = require("./utils"); +/** + * ICNS Header + * + * | Offset | Size | Purpose | + * | 0 | 4 | Magic literal, must be "icns" (0x69, 0x63, 0x6e, 0x73) | + * | 4 | 4 | Length of file, in bytes, msb first. | + * + */ +const SIZE_HEADER = 4 + 4; // 8 +const FILE_LENGTH_OFFSET = 4; // MSB => BIG ENDIAN +/** + * Image Entry + * + * | Offset | Size | Purpose | + * | 0 | 4 | Icon type, see OSType below. | + * | 4 | 4 | Length of data, in bytes (including type and length), msb first. | + * | 8 | n | Icon data | + */ +const ENTRY_LENGTH_OFFSET = 4; // MSB => BIG ENDIAN +const ICON_TYPE_SIZE = { + ICON: 32, + 'ICN#': 32, + // m => 16 x 16 + 'icm#': 16, + icm4: 16, + icm8: 16, + // s => 16 x 16 + 'ics#': 16, + ics4: 16, + ics8: 16, + is32: 16, + s8mk: 16, + icp4: 16, + // l => 32 x 32 + icl4: 32, + icl8: 32, + il32: 32, + l8mk: 32, + icp5: 32, + ic11: 32, + // h => 48 x 48 + ich4: 48, + ich8: 48, + ih32: 48, + h8mk: 48, + // . => 64 x 64 + icp6: 64, + ic12: 32, + // t => 128 x 128 + it32: 128, + t8mk: 128, + ic07: 128, + // . => 256 x 256 + ic08: 256, + ic13: 256, + // . => 512 x 512 + ic09: 512, + ic14: 512, + // . => 1024 x 1024 + ic10: 1024, +}; +function readImageHeader(input, imageOffset) { + const imageLengthOffset = imageOffset + ENTRY_LENGTH_OFFSET; + return [ + (0, utils_1.toUTF8String)(input, imageOffset, imageLengthOffset), + (0, utils_1.readUInt32BE)(input, imageLengthOffset), + ]; +} +function getImageSize(type) { + const size = ICON_TYPE_SIZE[type]; + return { width: size, height: size, type }; +} +exports.ICNS = { + validate: (input) => (0, utils_1.toUTF8String)(input, 0, 4) === 'icns', + calculate(input) { + const inputLength = input.length; + const fileLength = (0, utils_1.readUInt32BE)(input, FILE_LENGTH_OFFSET); + let imageOffset = SIZE_HEADER; + let imageHeader = readImageHeader(input, imageOffset); + let imageSize = getImageSize(imageHeader[0]); + imageOffset += imageHeader[1]; + if (imageOffset === fileLength) + return imageSize; + const result = { + height: imageSize.height, + images: [imageSize], + width: imageSize.width, + }; + while (imageOffset < fileLength && imageOffset < inputLength) { + imageHeader = readImageHeader(input, imageOffset); + imageSize = getImageSize(imageHeader[0]); + imageOffset += imageHeader[1]; + result.images.push(imageSize); + } + return result; + }, +}; diff --git a/services/slides/node_modules/image-size/dist/types/ico.d.ts b/services/slides/node_modules/image-size/dist/types/ico.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..a378b827db6ad147ac65f83e8b01f7f2a6001c79 --- /dev/null +++ b/services/slides/node_modules/image-size/dist/types/ico.d.ts @@ -0,0 +1,2 @@ +import type { IImage } from './interface'; +export declare const ICO: IImage; diff --git a/services/slides/node_modules/image-size/dist/types/ico.js b/services/slides/node_modules/image-size/dist/types/ico.js new file mode 100644 index 0000000000000000000000000000000000000000..0c630a0871114347027de7cd7bb270be07d8f49b --- /dev/null +++ b/services/slides/node_modules/image-size/dist/types/ico.js @@ -0,0 +1,68 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.ICO = void 0; +const utils_1 = require("./utils"); +const TYPE_ICON = 1; +/** + * ICON Header + * + * | Offset | Size | Purpose | + * | 0 | 2 | Reserved. Must always be 0. | + * | 2 | 2 | Image type: 1 for icon (.ICO) image, 2 for cursor (.CUR) image. Other values are invalid. | + * | 4 | 2 | Number of images in the file. | + * + */ +const SIZE_HEADER = 2 + 2 + 2; // 6 +/** + * Image Entry + * + * | Offset | Size | Purpose | + * | 0 | 1 | Image width in pixels. Can be any number between 0 and 255. Value 0 means width is 256 pixels. | + * | 1 | 1 | Image height in pixels. Can be any number between 0 and 255. Value 0 means height is 256 pixels. | + * | 2 | 1 | Number of colors in the color palette. Should be 0 if the image does not use a color palette. | + * | 3 | 1 | Reserved. Should be 0. | + * | 4 | 2 | ICO format: Color planes. Should be 0 or 1. | + * | | | CUR format: The horizontal coordinates of the hotspot in number of pixels from the left. | + * | 6 | 2 | ICO format: Bits per pixel. | + * | | | CUR format: The vertical coordinates of the hotspot in number of pixels from the top. | + * | 8 | 4 | The size of the image's data in bytes | + * | 12 | 4 | The offset of BMP or PNG data from the beginning of the ICO/CUR file | + * + */ +const SIZE_IMAGE_ENTRY = 1 + 1 + 1 + 1 + 2 + 2 + 4 + 4; // 16 +function getSizeFromOffset(input, offset) { + const value = input[offset]; + return value === 0 ? 256 : value; +} +function getImageSize(input, imageIndex) { + const offset = SIZE_HEADER + imageIndex * SIZE_IMAGE_ENTRY; + return { + height: getSizeFromOffset(input, offset + 1), + width: getSizeFromOffset(input, offset), + }; +} +exports.ICO = { + validate(input) { + const reserved = (0, utils_1.readUInt16LE)(input, 0); + const imageCount = (0, utils_1.readUInt16LE)(input, 4); + if (reserved !== 0 || imageCount === 0) + return false; + const imageType = (0, utils_1.readUInt16LE)(input, 2); + return imageType === TYPE_ICON; + }, + calculate(input) { + const nbImages = (0, utils_1.readUInt16LE)(input, 4); + const imageSize = getImageSize(input, 0); + if (nbImages === 1) + return imageSize; + const imgs = [imageSize]; + for (let imageIndex = 1; imageIndex < nbImages; imageIndex += 1) { + imgs.push(getImageSize(input, imageIndex)); + } + return { + height: imageSize.height, + images: imgs, + width: imageSize.width, + }; + }, +}; diff --git a/services/slides/node_modules/image-size/dist/types/index.d.ts b/services/slides/node_modules/image-size/dist/types/index.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..e338d1f8d75aaac6d93a06185c0fad682fd2f821 --- /dev/null +++ b/services/slides/node_modules/image-size/dist/types/index.d.ts @@ -0,0 +1,23 @@ +export declare const typeHandlers: { + bmp: import("./interface").IImage; + cur: import("./interface").IImage; + dds: import("./interface").IImage; + gif: import("./interface").IImage; + heif: import("./interface").IImage; + icns: import("./interface").IImage; + ico: import("./interface").IImage; + j2c: import("./interface").IImage; + jp2: import("./interface").IImage; + jpg: import("./interface").IImage; + jxl: import("./interface").IImage; + 'jxl-stream': import("./interface").IImage; + ktx: import("./interface").IImage; + png: import("./interface").IImage; + pnm: import("./interface").IImage; + psd: import("./interface").IImage; + svg: import("./interface").IImage; + tga: import("./interface").IImage; + tiff: import("./interface").IImage; + webp: import("./interface").IImage; +}; +export type imageType = keyof typeof typeHandlers; diff --git a/services/slides/node_modules/image-size/dist/types/index.js b/services/slides/node_modules/image-size/dist/types/index.js new file mode 100644 index 0000000000000000000000000000000000000000..f7cce831e173b7d6291461c8945ab13a51e4f2e8 --- /dev/null +++ b/services/slides/node_modules/image-size/dist/types/index.js @@ -0,0 +1,46 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.typeHandlers = void 0; +// load all available handlers explicitly for browserify support +const bmp_1 = require("./bmp"); +const cur_1 = require("./cur"); +const dds_1 = require("./dds"); +const gif_1 = require("./gif"); +const heif_1 = require("./heif"); +const icns_1 = require("./icns"); +const ico_1 = require("./ico"); +const j2c_1 = require("./j2c"); +const jp2_1 = require("./jp2"); +const jpg_1 = require("./jpg"); +const jxl_1 = require("./jxl"); +const jxl_stream_1 = require("./jxl-stream"); +const ktx_1 = require("./ktx"); +const png_1 = require("./png"); +const pnm_1 = require("./pnm"); +const psd_1 = require("./psd"); +const svg_1 = require("./svg"); +const tga_1 = require("./tga"); +const tiff_1 = require("./tiff"); +const webp_1 = require("./webp"); +exports.typeHandlers = { + bmp: bmp_1.BMP, + cur: cur_1.CUR, + dds: dds_1.DDS, + gif: gif_1.GIF, + heif: heif_1.HEIF, + icns: icns_1.ICNS, + ico: ico_1.ICO, + j2c: j2c_1.J2C, + jp2: jp2_1.JP2, + jpg: jpg_1.JPG, + jxl: jxl_1.JXL, + 'jxl-stream': jxl_stream_1.JXLStream, + ktx: ktx_1.KTX, + png: png_1.PNG, + pnm: pnm_1.PNM, + psd: psd_1.PSD, + svg: svg_1.SVG, + tga: tga_1.TGA, + tiff: tiff_1.TIFF, + webp: webp_1.WEBP, +}; diff --git a/services/slides/node_modules/image-size/dist/types/interface.d.ts b/services/slides/node_modules/image-size/dist/types/interface.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..96dc89bad163c3f50808fcfa938829643fce1231 --- /dev/null +++ b/services/slides/node_modules/image-size/dist/types/interface.d.ts @@ -0,0 +1,13 @@ +export interface ISize { + width: number | undefined; + height: number | undefined; + orientation?: number; + type?: string; +} +export type ISizeCalculationResult = { + images?: ISize[]; +} & ISize; +export interface IImage { + validate: (input: Uint8Array) => boolean; + calculate: (input: Uint8Array, filepath?: string) => ISizeCalculationResult; +} diff --git a/services/slides/node_modules/image-size/dist/types/interface.js b/services/slides/node_modules/image-size/dist/types/interface.js new file mode 100644 index 0000000000000000000000000000000000000000..c8ad2e549bdc6801e0d1c80b0308d4b9bd4985ce --- /dev/null +++ b/services/slides/node_modules/image-size/dist/types/interface.js @@ -0,0 +1,2 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/services/slides/node_modules/image-size/dist/types/j2c.d.ts b/services/slides/node_modules/image-size/dist/types/j2c.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..f745c7b5ec9f7ecdbc0343f853728ebaf6e50d87 --- /dev/null +++ b/services/slides/node_modules/image-size/dist/types/j2c.d.ts @@ -0,0 +1,2 @@ +import type { IImage } from './interface'; +export declare const J2C: IImage; diff --git a/services/slides/node_modules/image-size/dist/types/j2c.js b/services/slides/node_modules/image-size/dist/types/j2c.js new file mode 100644 index 0000000000000000000000000000000000000000..fba00fcc959ae07a37aea020377c5c39ce659c5c --- /dev/null +++ b/services/slides/node_modules/image-size/dist/types/j2c.js @@ -0,0 +1,12 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.J2C = void 0; +const utils_1 = require("./utils"); +exports.J2C = { + // TODO: this doesn't seem right. SIZ marker doesn't have to be right after the SOC + validate: (input) => (0, utils_1.readUInt32BE)(input, 0) === 0xff4fff51, + calculate: (input) => ({ + height: (0, utils_1.readUInt32BE)(input, 12), + width: (0, utils_1.readUInt32BE)(input, 8), + }), +}; diff --git a/services/slides/node_modules/image-size/dist/types/jp2.d.ts b/services/slides/node_modules/image-size/dist/types/jp2.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..ce53bb3b1ff6fbdfd9637d0bee45215c8568bcc2 --- /dev/null +++ b/services/slides/node_modules/image-size/dist/types/jp2.d.ts @@ -0,0 +1,2 @@ +import type { IImage } from './interface'; +export declare const JP2: IImage; diff --git a/services/slides/node_modules/image-size/dist/types/jp2.js b/services/slides/node_modules/image-size/dist/types/jp2.js new file mode 100644 index 0000000000000000000000000000000000000000..8af3f77cb51aabc112203ed6cf2d5339caaf65a7 --- /dev/null +++ b/services/slides/node_modules/image-size/dist/types/jp2.js @@ -0,0 +1,27 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.JP2 = void 0; +const utils_1 = require("./utils"); +exports.JP2 = { + validate(input) { + const boxType = (0, utils_1.toUTF8String)(input, 4, 8); + if (boxType !== 'jP ') + return false; + const ftypBox = (0, utils_1.findBox)(input, 'ftyp', 0); + if (!ftypBox) + return false; + const brand = (0, utils_1.toUTF8String)(input, ftypBox.offset + 8, ftypBox.offset + 12); + return brand === 'jp2 '; + }, + calculate(input) { + const jp2hBox = (0, utils_1.findBox)(input, 'jp2h', 0); + const ihdrBox = jp2hBox && (0, utils_1.findBox)(input, 'ihdr', jp2hBox.offset + 8); + if (ihdrBox) { + return { + height: (0, utils_1.readUInt32BE)(input, ihdrBox.offset + 8), + width: (0, utils_1.readUInt32BE)(input, ihdrBox.offset + 12), + }; + } + throw new TypeError('Unsupported JPEG 2000 format'); + }, +}; diff --git a/services/slides/node_modules/image-size/dist/types/jpg.d.ts b/services/slides/node_modules/image-size/dist/types/jpg.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..68fc201693a6f4818c79a35592b04e3d56043ee0 --- /dev/null +++ b/services/slides/node_modules/image-size/dist/types/jpg.d.ts @@ -0,0 +1,2 @@ +import type { IImage } from './interface'; +export declare const JPG: IImage; diff --git a/services/slides/node_modules/image-size/dist/types/jpg.js b/services/slides/node_modules/image-size/dist/types/jpg.js new file mode 100644 index 0000000000000000000000000000000000000000..e6f0ecec398fcb1f32f392fa8fdbb35cc79e76eb --- /dev/null +++ b/services/slides/node_modules/image-size/dist/types/jpg.js @@ -0,0 +1,123 @@ +"use strict"; +// NOTE: we only support baseline and progressive JPGs here +// due to the structure of the loader class, we only get a buffer +// with a maximum size of 4096 bytes. so if the SOF marker is outside +// if this range we can't detect the file size correctly. +Object.defineProperty(exports, "__esModule", { value: true }); +exports.JPG = void 0; +const utils_1 = require("./utils"); +const EXIF_MARKER = '45786966'; +const APP1_DATA_SIZE_BYTES = 2; +const EXIF_HEADER_BYTES = 6; +const TIFF_BYTE_ALIGN_BYTES = 2; +const BIG_ENDIAN_BYTE_ALIGN = '4d4d'; +const LITTLE_ENDIAN_BYTE_ALIGN = '4949'; +// Each entry is exactly 12 bytes +const IDF_ENTRY_BYTES = 12; +const NUM_DIRECTORY_ENTRIES_BYTES = 2; +function isEXIF(input) { + return (0, utils_1.toHexString)(input, 2, 6) === EXIF_MARKER; +} +function extractSize(input, index) { + return { + height: (0, utils_1.readUInt16BE)(input, index), + width: (0, utils_1.readUInt16BE)(input, index + 2), + }; +} +function extractOrientation(exifBlock, isBigEndian) { + // TODO: assert that this contains 0x002A + // let STATIC_MOTOROLA_TIFF_HEADER_BYTES = 2 + // let TIFF_IMAGE_FILE_DIRECTORY_BYTES = 4 + // TODO: derive from TIFF_IMAGE_FILE_DIRECTORY_BYTES + const idfOffset = 8; + // IDF osset works from right after the header bytes + // (so the offset includes the tiff byte align) + const offset = EXIF_HEADER_BYTES + idfOffset; + const idfDirectoryEntries = (0, utils_1.readUInt)(exifBlock, 16, offset, isBigEndian); + for (let directoryEntryNumber = 0; directoryEntryNumber < idfDirectoryEntries; directoryEntryNumber++) { + const start = offset + + NUM_DIRECTORY_ENTRIES_BYTES + + directoryEntryNumber * IDF_ENTRY_BYTES; + const end = start + IDF_ENTRY_BYTES; + // Skip on corrupt EXIF blocks + if (start > exifBlock.length) { + return; + } + const block = exifBlock.slice(start, end); + const tagNumber = (0, utils_1.readUInt)(block, 16, 0, isBigEndian); + // 0x0112 (decimal: 274) is the `orientation` tag ID + if (tagNumber === 274) { + const dataFormat = (0, utils_1.readUInt)(block, 16, 2, isBigEndian); + if (dataFormat !== 3) { + return; + } + // unsinged int has 2 bytes per component + // if there would more than 4 bytes in total it's a pointer + const numberOfComponents = (0, utils_1.readUInt)(block, 32, 4, isBigEndian); + if (numberOfComponents !== 1) { + return; + } + return (0, utils_1.readUInt)(block, 16, 8, isBigEndian); + } + } +} +function validateExifBlock(input, index) { + // Skip APP1 Data Size + const exifBlock = input.slice(APP1_DATA_SIZE_BYTES, index); + // Consider byte alignment + const byteAlign = (0, utils_1.toHexString)(exifBlock, EXIF_HEADER_BYTES, EXIF_HEADER_BYTES + TIFF_BYTE_ALIGN_BYTES); + // Ignore Empty EXIF. Validate byte alignment + const isBigEndian = byteAlign === BIG_ENDIAN_BYTE_ALIGN; + const isLittleEndian = byteAlign === LITTLE_ENDIAN_BYTE_ALIGN; + if (isBigEndian || isLittleEndian) { + return extractOrientation(exifBlock, isBigEndian); + } +} +function validateInput(input, index) { + // index should be within buffer limits + if (index > input.length) { + throw new TypeError('Corrupt JPG, exceeded buffer limits'); + } +} +exports.JPG = { + validate: (input) => (0, utils_1.toHexString)(input, 0, 2) === 'ffd8', + calculate(input) { + // Skip 4 chars, they are for signature + input = input.slice(4); + let orientation; + let next; + while (input.length) { + // read length of the next block + const i = (0, utils_1.readUInt16BE)(input, 0); + // Every JPEG block must begin with a 0xFF + if (input[i] !== 0xff) { + input = input.slice(1); + continue; + } + if (isEXIF(input)) { + orientation = validateExifBlock(input, i); + } + // ensure correct format + validateInput(input, i); + // 0xFFC0 is baseline standard(SOF) + // 0xFFC1 is baseline optimized(SOF) + // 0xFFC2 is progressive(SOF2) + next = input[i + 1]; + if (next === 0xc0 || next === 0xc1 || next === 0xc2) { + const size = extractSize(input, i + 5); + // TODO: is orientation=0 a valid answer here? + if (!orientation) { + return size; + } + return { + height: size.height, + orientation, + width: size.width, + }; + } + // move to the next block + input = input.slice(i + 2); + } + throw new TypeError('Invalid JPG, no size found'); + }, +}; diff --git a/services/slides/node_modules/image-size/dist/types/jxl-stream.d.ts b/services/slides/node_modules/image-size/dist/types/jxl-stream.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..1c502bb2e57a34c7bf87415326d37f95ac815031 --- /dev/null +++ b/services/slides/node_modules/image-size/dist/types/jxl-stream.d.ts @@ -0,0 +1,2 @@ +import type { IImage } from './interface'; +export declare const JXLStream: IImage; diff --git a/services/slides/node_modules/image-size/dist/types/jxl-stream.js b/services/slides/node_modules/image-size/dist/types/jxl-stream.js new file mode 100644 index 0000000000000000000000000000000000000000..e73316b41f05c3fadc7c0b5f931bfa59b9171331 --- /dev/null +++ b/services/slides/node_modules/image-size/dist/types/jxl-stream.js @@ -0,0 +1,45 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.JXLStream = void 0; +const utils_1 = require("./utils"); +const bit_reader_1 = require("../utils/bit-reader"); +function calculateImageDimension(reader, isSmallImage) { + if (isSmallImage) { + // Small images are multiples of 8 pixels, up to 256 pixels + return 8 * (1 + reader.getBits(5)); + } + else { + // Larger images use a variable bit-length encoding + const sizeClass = reader.getBits(2); + const extraBits = [9, 13, 18, 30][sizeClass]; + return 1 + reader.getBits(extraBits); + } +} +function calculateImageWidth(reader, isSmallImage, widthMode, height) { + if (isSmallImage && widthMode === 0) { + // Small square images + return 8 * (1 + reader.getBits(5)); + } + else if (widthMode === 0) { + // Non-small images with explicitly coded width + return calculateImageDimension(reader, false); + } + else { + // Images with width derived from height and aspect ratio + const aspectRatios = [1, 1.2, 4 / 3, 1.5, 16 / 9, 5 / 4, 2]; + return Math.floor(height * aspectRatios[widthMode - 1]); + } +} +exports.JXLStream = { + validate: (input) => { + return (0, utils_1.toHexString)(input, 0, 2) === 'ff0a'; + }, + calculate(input) { + const reader = new bit_reader_1.BitReader(input, 'little-endian'); + const isSmallImage = reader.getBits(1) === 1; + const height = calculateImageDimension(reader, isSmallImage); + const widthMode = reader.getBits(3); + const width = calculateImageWidth(reader, isSmallImage, widthMode, height); + return { width, height }; + }, +}; diff --git a/services/slides/node_modules/image-size/dist/types/jxl.d.ts b/services/slides/node_modules/image-size/dist/types/jxl.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..cdec897e1aef1b768fddbf037cbb0e9d02bf99fa --- /dev/null +++ b/services/slides/node_modules/image-size/dist/types/jxl.d.ts @@ -0,0 +1,2 @@ +import type { IImage } from './interface'; +export declare const JXL: IImage; diff --git a/services/slides/node_modules/image-size/dist/types/jxl.js b/services/slides/node_modules/image-size/dist/types/jxl.js new file mode 100644 index 0000000000000000000000000000000000000000..f557b0aea7e679d2cb84d9915e957dd2254eba97 --- /dev/null +++ b/services/slides/node_modules/image-size/dist/types/jxl.js @@ -0,0 +1,59 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.JXL = void 0; +const utils_1 = require("./utils"); +const jxl_stream_1 = require("./jxl-stream"); +/** Extracts the codestream from a containerized JPEG XL image */ +function extractCodestream(input) { + const jxlcBox = (0, utils_1.findBox)(input, 'jxlc', 0); + if (jxlcBox) { + return input.slice(jxlcBox.offset + 8, jxlcBox.offset + jxlcBox.size); + } + const partialStreams = extractPartialStreams(input); + if (partialStreams.length > 0) { + return concatenateCodestreams(partialStreams); + } + return undefined; +} +/** Extracts partial codestreams from jxlp boxes */ +function extractPartialStreams(input) { + const partialStreams = []; + let offset = 0; + while (offset < input.length) { + const jxlpBox = (0, utils_1.findBox)(input, 'jxlp', offset); + if (!jxlpBox) + break; + partialStreams.push(input.slice(jxlpBox.offset + 12, jxlpBox.offset + jxlpBox.size)); + offset = jxlpBox.offset + jxlpBox.size; + } + return partialStreams; +} +/** Concatenates partial codestreams into a single codestream */ +function concatenateCodestreams(partialCodestreams) { + const totalLength = partialCodestreams.reduce((acc, curr) => acc + curr.length, 0); + const codestream = new Uint8Array(totalLength); + let position = 0; + for (const partial of partialCodestreams) { + codestream.set(partial, position); + position += partial.length; + } + return codestream; +} +exports.JXL = { + validate: (input) => { + const boxType = (0, utils_1.toUTF8String)(input, 4, 8); + if (boxType !== 'JXL ') + return false; + const ftypBox = (0, utils_1.findBox)(input, 'ftyp', 0); + if (!ftypBox) + return false; + const brand = (0, utils_1.toUTF8String)(input, ftypBox.offset + 8, ftypBox.offset + 12); + return brand === 'jxl '; + }, + calculate(input) { + const codestream = extractCodestream(input); + if (codestream) + return jxl_stream_1.JXLStream.calculate(codestream); + throw new Error('No codestream found in JXL container'); + }, +}; diff --git a/services/slides/node_modules/image-size/dist/types/ktx.d.ts b/services/slides/node_modules/image-size/dist/types/ktx.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..48fb6c95e6a9d98516324dfa013cfa8b63878cdb --- /dev/null +++ b/services/slides/node_modules/image-size/dist/types/ktx.d.ts @@ -0,0 +1,2 @@ +import type { IImage } from './interface'; +export declare const KTX: IImage; diff --git a/services/slides/node_modules/image-size/dist/types/ktx.js b/services/slides/node_modules/image-size/dist/types/ktx.js new file mode 100644 index 0000000000000000000000000000000000000000..e3f6381d2cd4d0830f6bc868457ac050dc92ce53 --- /dev/null +++ b/services/slides/node_modules/image-size/dist/types/ktx.js @@ -0,0 +1,19 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.KTX = void 0; +const utils_1 = require("./utils"); +exports.KTX = { + validate: (input) => { + const signature = (0, utils_1.toUTF8String)(input, 1, 7); + return ['KTX 11', 'KTX 20'].includes(signature); + }, + calculate: (input) => { + const type = input[5] === 0x31 ? 'ktx' : 'ktx2'; + const offset = type === 'ktx' ? 36 : 20; + return { + height: (0, utils_1.readUInt32LE)(input, offset + 4), + width: (0, utils_1.readUInt32LE)(input, offset), + type, + }; + }, +}; diff --git a/services/slides/node_modules/image-size/dist/types/png.d.ts b/services/slides/node_modules/image-size/dist/types/png.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..53415c0148859911050d9573c6ffc44d3bdd62c0 --- /dev/null +++ b/services/slides/node_modules/image-size/dist/types/png.d.ts @@ -0,0 +1,2 @@ +import type { IImage } from './interface'; +export declare const PNG: IImage; diff --git a/services/slides/node_modules/image-size/dist/types/png.js b/services/slides/node_modules/image-size/dist/types/png.js new file mode 100644 index 0000000000000000000000000000000000000000..b8aff59e826b07841e7b8c9560f71cf88d553724 --- /dev/null +++ b/services/slides/node_modules/image-size/dist/types/png.js @@ -0,0 +1,35 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.PNG = void 0; +const utils_1 = require("./utils"); +const pngSignature = 'PNG\r\n\x1a\n'; +const pngImageHeaderChunkName = 'IHDR'; +// Used to detect "fried" png's: http://www.jongware.com/pngdefry.html +const pngFriedChunkName = 'CgBI'; +exports.PNG = { + validate(input) { + if (pngSignature === (0, utils_1.toUTF8String)(input, 1, 8)) { + let chunkName = (0, utils_1.toUTF8String)(input, 12, 16); + if (chunkName === pngFriedChunkName) { + chunkName = (0, utils_1.toUTF8String)(input, 28, 32); + } + if (chunkName !== pngImageHeaderChunkName) { + throw new TypeError('Invalid PNG'); + } + return true; + } + return false; + }, + calculate(input) { + if ((0, utils_1.toUTF8String)(input, 12, 16) === pngFriedChunkName) { + return { + height: (0, utils_1.readUInt32BE)(input, 36), + width: (0, utils_1.readUInt32BE)(input, 32), + }; + } + return { + height: (0, utils_1.readUInt32BE)(input, 20), + width: (0, utils_1.readUInt32BE)(input, 16), + }; + }, +}; diff --git a/services/slides/node_modules/image-size/dist/types/pnm.d.ts b/services/slides/node_modules/image-size/dist/types/pnm.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..13950cb49a40e86a3cfa75e763aee07972da6f73 --- /dev/null +++ b/services/slides/node_modules/image-size/dist/types/pnm.d.ts @@ -0,0 +1,2 @@ +import type { IImage } from './interface'; +export declare const PNM: IImage; diff --git a/services/slides/node_modules/image-size/dist/types/pnm.js b/services/slides/node_modules/image-size/dist/types/pnm.js new file mode 100644 index 0000000000000000000000000000000000000000..d61295a7935f6a7b9d03a823e16600361d747b2f --- /dev/null +++ b/services/slides/node_modules/image-size/dist/types/pnm.js @@ -0,0 +1,72 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.PNM = void 0; +const utils_1 = require("./utils"); +const PNMTypes = { + P1: 'pbm/ascii', + P2: 'pgm/ascii', + P3: 'ppm/ascii', + P4: 'pbm', + P5: 'pgm', + P6: 'ppm', + P7: 'pam', + PF: 'pfm', +}; +const handlers = { + default: (lines) => { + let dimensions = []; + while (lines.length > 0) { + const line = lines.shift(); + if (line[0] === '#') { + continue; + } + dimensions = line.split(' '); + break; + } + if (dimensions.length === 2) { + return { + height: parseInt(dimensions[1], 10), + width: parseInt(dimensions[0], 10), + }; + } + else { + throw new TypeError('Invalid PNM'); + } + }, + pam: (lines) => { + const size = {}; + while (lines.length > 0) { + const line = lines.shift(); + if (line.length > 16 || line.charCodeAt(0) > 128) { + continue; + } + const [key, value] = line.split(' '); + if (key && value) { + size[key.toLowerCase()] = parseInt(value, 10); + } + if (size.height && size.width) { + break; + } + } + if (size.height && size.width) { + return { + height: size.height, + width: size.width, + }; + } + else { + throw new TypeError('Invalid PAM'); + } + }, +}; +exports.PNM = { + validate: (input) => (0, utils_1.toUTF8String)(input, 0, 2) in PNMTypes, + calculate(input) { + const signature = (0, utils_1.toUTF8String)(input, 0, 2); + const type = PNMTypes[signature]; + // TODO: this probably generates garbage. move to a stream based parser + const lines = (0, utils_1.toUTF8String)(input, 3).split(/[\r\n]+/); + const handler = handlers[type] || handlers.default; + return handler(lines); + }, +}; diff --git a/services/slides/node_modules/image-size/dist/types/psd.d.ts b/services/slides/node_modules/image-size/dist/types/psd.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..5f5c141646cee04d105fcf9d55382589dd11d591 --- /dev/null +++ b/services/slides/node_modules/image-size/dist/types/psd.d.ts @@ -0,0 +1,2 @@ +import type { IImage } from './interface'; +export declare const PSD: IImage; diff --git a/services/slides/node_modules/image-size/dist/types/psd.js b/services/slides/node_modules/image-size/dist/types/psd.js new file mode 100644 index 0000000000000000000000000000000000000000..6b328569fbada342badb6705e005b9e27254d837 --- /dev/null +++ b/services/slides/node_modules/image-size/dist/types/psd.js @@ -0,0 +1,11 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.PSD = void 0; +const utils_1 = require("./utils"); +exports.PSD = { + validate: (input) => (0, utils_1.toUTF8String)(input, 0, 4) === '8BPS', + calculate: (input) => ({ + height: (0, utils_1.readUInt32BE)(input, 14), + width: (0, utils_1.readUInt32BE)(input, 18), + }), +}; diff --git a/services/slides/node_modules/image-size/dist/types/svg.d.ts b/services/slides/node_modules/image-size/dist/types/svg.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..0a10be82402a31928b4d0c04577386c696d9fba1 --- /dev/null +++ b/services/slides/node_modules/image-size/dist/types/svg.d.ts @@ -0,0 +1,2 @@ +import type { IImage } from './interface'; +export declare const SVG: IImage; diff --git a/services/slides/node_modules/image-size/dist/types/svg.js b/services/slides/node_modules/image-size/dist/types/svg.js new file mode 100644 index 0000000000000000000000000000000000000000..fb80a9730b0878047a0293a4ce6183c539f0357f --- /dev/null +++ b/services/slides/node_modules/image-size/dist/types/svg.js @@ -0,0 +1,90 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.SVG = void 0; +const utils_1 = require("./utils"); +const svgReg = /"']|"[^"]*"|'[^']*')*>/; +const extractorRegExps = { + height: /\sheight=(['"])([^%]+?)\1/, + root: svgReg, + viewbox: /\sviewBox=(['"])(.+?)\1/i, + width: /\swidth=(['"])([^%]+?)\1/, +}; +const INCH_CM = 2.54; +const units = { + in: 96, + cm: 96 / INCH_CM, + em: 16, + ex: 8, + m: (96 / INCH_CM) * 100, + mm: 96 / INCH_CM / 10, + pc: 96 / 72 / 12, + pt: 96 / 72, + px: 1, +}; +const unitsReg = new RegExp(`^([0-9.]+(?:e\\d+)?)(${Object.keys(units).join('|')})?$`); +function parseLength(len) { + const m = unitsReg.exec(len); + if (!m) { + return undefined; + } + return Math.round(Number(m[1]) * (units[m[2]] || 1)); +} +function parseViewbox(viewbox) { + const bounds = viewbox.split(' '); + return { + height: parseLength(bounds[3]), + width: parseLength(bounds[2]), + }; +} +function parseAttributes(root) { + const width = root.match(extractorRegExps.width); + const height = root.match(extractorRegExps.height); + const viewbox = root.match(extractorRegExps.viewbox); + return { + height: height && parseLength(height[2]), + viewbox: viewbox && parseViewbox(viewbox[2]), + width: width && parseLength(width[2]), + }; +} +function calculateByDimensions(attrs) { + return { + height: attrs.height, + width: attrs.width, + }; +} +function calculateByViewbox(attrs, viewbox) { + const ratio = viewbox.width / viewbox.height; + if (attrs.width) { + return { + height: Math.floor(attrs.width / ratio), + width: attrs.width, + }; + } + if (attrs.height) { + return { + height: attrs.height, + width: Math.floor(attrs.height * ratio), + }; + } + return { + height: viewbox.height, + width: viewbox.width, + }; +} +exports.SVG = { + // Scan only the first kilo-byte to speed up the check on larger files + validate: (input) => svgReg.test((0, utils_1.toUTF8String)(input, 0, 1000)), + calculate(input) { + const root = (0, utils_1.toUTF8String)(input).match(extractorRegExps.root); + if (root) { + const attrs = parseAttributes(root[0]); + if (attrs.width && attrs.height) { + return calculateByDimensions(attrs); + } + if (attrs.viewbox) { + return calculateByViewbox(attrs, attrs.viewbox); + } + } + throw new TypeError('Invalid SVG'); + }, +}; diff --git a/services/slides/node_modules/image-size/dist/types/tga.d.ts b/services/slides/node_modules/image-size/dist/types/tga.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..449496362ebd1ccd85e503693cbdc0266b73d3b7 --- /dev/null +++ b/services/slides/node_modules/image-size/dist/types/tga.d.ts @@ -0,0 +1,2 @@ +import type { IImage } from './interface'; +export declare const TGA: IImage; diff --git a/services/slides/node_modules/image-size/dist/types/tga.js b/services/slides/node_modules/image-size/dist/types/tga.js new file mode 100644 index 0000000000000000000000000000000000000000..ea371dc37dae9835b7331b4bc1f0e02383e7e760 --- /dev/null +++ b/services/slides/node_modules/image-size/dist/types/tga.js @@ -0,0 +1,15 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.TGA = void 0; +const utils_1 = require("./utils"); +exports.TGA = { + validate(input) { + return (0, utils_1.readUInt16LE)(input, 0) === 0 && (0, utils_1.readUInt16LE)(input, 4) === 0; + }, + calculate(input) { + return { + height: (0, utils_1.readUInt16LE)(input, 14), + width: (0, utils_1.readUInt16LE)(input, 12), + }; + }, +}; diff --git a/services/slides/node_modules/image-size/dist/types/tiff.d.ts b/services/slides/node_modules/image-size/dist/types/tiff.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..4d6ecbc6a7c3920eaab86054bb71d9471c0c30bd --- /dev/null +++ b/services/slides/node_modules/image-size/dist/types/tiff.d.ts @@ -0,0 +1,2 @@ +import type { IImage } from './interface'; +export declare const TIFF: IImage; diff --git a/services/slides/node_modules/image-size/dist/types/tiff.js b/services/slides/node_modules/image-size/dist/types/tiff.js new file mode 100644 index 0000000000000000000000000000000000000000..cf1564cbe6f1d55356f33d776d2b2d64c9add2b9 --- /dev/null +++ b/services/slides/node_modules/image-size/dist/types/tiff.js @@ -0,0 +1,95 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.TIFF = void 0; +// based on http://www.compix.com/fileformattif.htm +// TO-DO: support big-endian as well +const fs = require("fs"); +const utils_1 = require("./utils"); +// Read IFD (image-file-directory) into a buffer +function readIFD(input, filepath, isBigEndian) { + const ifdOffset = (0, utils_1.readUInt)(input, 32, 4, isBigEndian); + // read only till the end of the file + let bufferSize = 1024; + const fileSize = fs.statSync(filepath).size; + if (ifdOffset + bufferSize > fileSize) { + bufferSize = fileSize - ifdOffset - 10; + } + // populate the buffer + const endBuffer = new Uint8Array(bufferSize); + const descriptor = fs.openSync(filepath, 'r'); + fs.readSync(descriptor, endBuffer, 0, bufferSize, ifdOffset); + fs.closeSync(descriptor); + return endBuffer.slice(2); +} +// TIFF values seem to be messed up on Big-Endian, this helps +function readValue(input, isBigEndian) { + const low = (0, utils_1.readUInt)(input, 16, 8, isBigEndian); + const high = (0, utils_1.readUInt)(input, 16, 10, isBigEndian); + return (high << 16) + low; +} +// move to the next tag +function nextTag(input) { + if (input.length > 24) { + return input.slice(12); + } +} +// Extract IFD tags from TIFF metadata +function extractTags(input, isBigEndian) { + const tags = {}; + let temp = input; + while (temp && temp.length) { + const code = (0, utils_1.readUInt)(temp, 16, 0, isBigEndian); + const type = (0, utils_1.readUInt)(temp, 16, 2, isBigEndian); + const length = (0, utils_1.readUInt)(temp, 32, 4, isBigEndian); + // 0 means end of IFD + if (code === 0) { + break; + } + else { + // 256 is width, 257 is height + // if (code === 256 || code === 257) { + if (length === 1 && (type === 3 || type === 4)) { + tags[code] = readValue(temp, isBigEndian); + } + // move to the next tag + temp = nextTag(temp); + } + } + return tags; +} +// Test if the TIFF is Big Endian or Little Endian +function determineEndianness(input) { + const signature = (0, utils_1.toUTF8String)(input, 0, 2); + if ('II' === signature) { + return 'LE'; + } + else if ('MM' === signature) { + return 'BE'; + } +} +const signatures = [ + // '492049', // currently not supported + '49492a00', // Little endian + '4d4d002a', // Big Endian + // '4d4d002a', // BigTIFF > 4GB. currently not supported +]; +exports.TIFF = { + validate: (input) => signatures.includes((0, utils_1.toHexString)(input, 0, 4)), + calculate(input, filepath) { + if (!filepath) { + throw new TypeError("Tiff doesn't support buffer"); + } + // Determine BE/LE + const isBigEndian = determineEndianness(input) === 'BE'; + // read the IFD + const ifdBuffer = readIFD(input, filepath, isBigEndian); + // extract the tags from the IFD + const tags = extractTags(ifdBuffer, isBigEndian); + const width = tags[256]; + const height = tags[257]; + if (!width || !height) { + throw new TypeError('Invalid Tiff. Missing tags'); + } + return { height, width }; + }, +}; diff --git a/services/slides/node_modules/image-size/dist/types/utils.d.ts b/services/slides/node_modules/image-size/dist/types/utils.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..7c79ca8271489d4405bb98559dfe7f13fc50eade --- /dev/null +++ b/services/slides/node_modules/image-size/dist/types/utils.d.ts @@ -0,0 +1,15 @@ +export declare const toUTF8String: (input: Uint8Array, start?: number, end?: number) => string; +export declare const toHexString: (input: Uint8Array, start?: number, end?: number) => string; +export declare const readInt16LE: (input: Uint8Array, offset?: number) => number; +export declare const readUInt16BE: (input: Uint8Array, offset?: number) => number; +export declare const readUInt16LE: (input: Uint8Array, offset?: number) => number; +export declare const readUInt24LE: (input: Uint8Array, offset?: number) => number; +export declare const readInt32LE: (input: Uint8Array, offset?: number) => number; +export declare const readUInt32BE: (input: Uint8Array, offset?: number) => number; +export declare const readUInt32LE: (input: Uint8Array, offset?: number) => number; +export declare function readUInt(input: Uint8Array, bits: 16 | 32, offset: number, isBigEndian: boolean): number; +export declare function findBox(input: Uint8Array, boxName: string, offset: number): { + name: string; + offset: number; + size: number; +} | undefined; diff --git a/services/slides/node_modules/image-size/dist/types/utils.js b/services/slides/node_modules/image-size/dist/types/utils.js new file mode 100644 index 0000000000000000000000000000000000000000..5224bbafe87551ac415cb3de234820ccc0ff6e2c --- /dev/null +++ b/services/slides/node_modules/image-size/dist/types/utils.js @@ -0,0 +1,75 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.findBox = exports.readUInt = exports.readUInt32LE = exports.readUInt32BE = exports.readInt32LE = exports.readUInt24LE = exports.readUInt16LE = exports.readUInt16BE = exports.readInt16LE = exports.toHexString = exports.toUTF8String = void 0; +const decoder = new TextDecoder(); +const toUTF8String = (input, start = 0, end = input.length) => decoder.decode(input.slice(start, end)); +exports.toUTF8String = toUTF8String; +const toHexString = (input, start = 0, end = input.length) => input + .slice(start, end) + .reduce((memo, i) => memo + ('0' + i.toString(16)).slice(-2), ''); +exports.toHexString = toHexString; +const readInt16LE = (input, offset = 0) => { + const val = input[offset] + input[offset + 1] * 2 ** 8; + return val | ((val & (2 ** 15)) * 0x1fffe); +}; +exports.readInt16LE = readInt16LE; +const readUInt16BE = (input, offset = 0) => input[offset] * 2 ** 8 + input[offset + 1]; +exports.readUInt16BE = readUInt16BE; +const readUInt16LE = (input, offset = 0) => input[offset] + input[offset + 1] * 2 ** 8; +exports.readUInt16LE = readUInt16LE; +const readUInt24LE = (input, offset = 0) => input[offset] + input[offset + 1] * 2 ** 8 + input[offset + 2] * 2 ** 16; +exports.readUInt24LE = readUInt24LE; +const readInt32LE = (input, offset = 0) => input[offset] + + input[offset + 1] * 2 ** 8 + + input[offset + 2] * 2 ** 16 + + (input[offset + 3] << 24); +exports.readInt32LE = readInt32LE; +const readUInt32BE = (input, offset = 0) => input[offset] * 2 ** 24 + + input[offset + 1] * 2 ** 16 + + input[offset + 2] * 2 ** 8 + + input[offset + 3]; +exports.readUInt32BE = readUInt32BE; +const readUInt32LE = (input, offset = 0) => input[offset] + + input[offset + 1] * 2 ** 8 + + input[offset + 2] * 2 ** 16 + + input[offset + 3] * 2 ** 24; +exports.readUInt32LE = readUInt32LE; +// Abstract reading multi-byte unsigned integers +const methods = { + readUInt16BE: exports.readUInt16BE, + readUInt16LE: exports.readUInt16LE, + readUInt32BE: exports.readUInt32BE, + readUInt32LE: exports.readUInt32LE, +}; +function readUInt(input, bits, offset, isBigEndian) { + offset = offset || 0; + const endian = isBigEndian ? 'BE' : 'LE'; + const methodName = ('readUInt' + bits + endian); + return methods[methodName](input, offset); +} +exports.readUInt = readUInt; +function readBox(input, offset) { + if (input.length - offset < 4) + return; + const boxSize = (0, exports.readUInt32BE)(input, offset); + if (input.length - offset < boxSize) + return; + return { + name: (0, exports.toUTF8String)(input, 4 + offset, 8 + offset), + offset, + size: boxSize, + }; +} +function findBox(input, boxName, offset) { + while (offset < input.length) { + const box = readBox(input, offset); + if (!box) + break; + if (box.name === boxName) + return box; + // Fix the infinite loop by ensuring offset always increases + // If box.size is 0, advance by at least 8 bytes (the size of the box header) + offset += box.size > 0 ? box.size : 8; + } +} +exports.findBox = findBox; diff --git a/services/slides/node_modules/image-size/dist/types/webp.d.ts b/services/slides/node_modules/image-size/dist/types/webp.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..5012ead53431ac830b9b689c46b3953ac928bfd2 --- /dev/null +++ b/services/slides/node_modules/image-size/dist/types/webp.d.ts @@ -0,0 +1,2 @@ +import type { IImage } from './interface'; +export declare const WEBP: IImage; diff --git a/services/slides/node_modules/image-size/dist/types/webp.js b/services/slides/node_modules/image-size/dist/types/webp.js new file mode 100644 index 0000000000000000000000000000000000000000..d1186e17ba49f41b2a785b55c0b183cf56f1c082 --- /dev/null +++ b/services/slides/node_modules/image-size/dist/types/webp.js @@ -0,0 +1,60 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.WEBP = void 0; +const utils_1 = require("./utils"); +function calculateExtended(input) { + return { + height: 1 + (0, utils_1.readUInt24LE)(input, 7), + width: 1 + (0, utils_1.readUInt24LE)(input, 4), + }; +} +function calculateLossless(input) { + return { + height: 1 + + (((input[4] & 0xf) << 10) | (input[3] << 2) | ((input[2] & 0xc0) >> 6)), + width: 1 + (((input[2] & 0x3f) << 8) | input[1]), + }; +} +function calculateLossy(input) { + // `& 0x3fff` returns the last 14 bits + // TO-DO: include webp scaling in the calculations + return { + height: (0, utils_1.readInt16LE)(input, 8) & 0x3fff, + width: (0, utils_1.readInt16LE)(input, 6) & 0x3fff, + }; +} +exports.WEBP = { + validate(input) { + const riffHeader = 'RIFF' === (0, utils_1.toUTF8String)(input, 0, 4); + const webpHeader = 'WEBP' === (0, utils_1.toUTF8String)(input, 8, 12); + const vp8Header = 'VP8' === (0, utils_1.toUTF8String)(input, 12, 15); + return riffHeader && webpHeader && vp8Header; + }, + calculate(input) { + const chunkHeader = (0, utils_1.toUTF8String)(input, 12, 16); + input = input.slice(20, 30); + // Extended webp stream signature + if (chunkHeader === 'VP8X') { + const extendedHeader = input[0]; + const validStart = (extendedHeader & 0xc0) === 0; + const validEnd = (extendedHeader & 0x01) === 0; + if (validStart && validEnd) { + return calculateExtended(input); + } + else { + // TODO: breaking change + throw new TypeError('Invalid WebP'); + } + } + // Lossless webp stream signature + if (chunkHeader === 'VP8 ' && input[0] !== 0x2f) { + return calculateLossy(input); + } + // Lossy webp stream signature + const signature = (0, utils_1.toHexString)(input, 3, 6); + if (chunkHeader === 'VP8L' && signature !== '9d012a') { + return calculateLossless(input); + } + throw new TypeError('Invalid WebP'); + }, +}; diff --git a/services/slides/node_modules/image-size/dist/utils/bit-reader.d.ts b/services/slides/node_modules/image-size/dist/utils/bit-reader.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..4e59226ecd8f013b92d0bc03e35c0a9e2178d6ad --- /dev/null +++ b/services/slides/node_modules/image-size/dist/utils/bit-reader.d.ts @@ -0,0 +1,10 @@ +/** This class helps read Uint8Array bit-by-bit */ +export declare class BitReader { + private readonly input; + private readonly endianness; + private byteOffset; + private bitOffset; + constructor(input: Uint8Array, endianness: 'big-endian' | 'little-endian'); + /** Reads a specified number of bits, and move the offset */ + getBits(length?: number): number; +} diff --git a/services/slides/node_modules/image-size/dist/utils/bit-reader.js b/services/slides/node_modules/image-size/dist/utils/bit-reader.js new file mode 100644 index 0000000000000000000000000000000000000000..3546348eae3ca06b98c5446c1c17ba77f342555c --- /dev/null +++ b/services/slides/node_modules/image-size/dist/utils/bit-reader.js @@ -0,0 +1,44 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.BitReader = void 0; +/** This class helps read Uint8Array bit-by-bit */ +class BitReader { + constructor(input, endianness) { + this.input = input; + this.endianness = endianness; + // Skip the first 16 bits (2 bytes) of signature + this.byteOffset = 2; + this.bitOffset = 0; + } + /** Reads a specified number of bits, and move the offset */ + getBits(length = 1) { + let result = 0; + let bitsRead = 0; + while (bitsRead < length) { + if (this.byteOffset >= this.input.length) { + throw new Error('Reached end of input'); + } + const currentByte = this.input[this.byteOffset]; + const bitsLeft = 8 - this.bitOffset; + const bitsToRead = Math.min(length - bitsRead, bitsLeft); + if (this.endianness === 'little-endian') { + const mask = (1 << bitsToRead) - 1; + const bits = (currentByte >> this.bitOffset) & mask; + result |= bits << bitsRead; + } + else { + const mask = ((1 << bitsToRead) - 1) << (8 - this.bitOffset - bitsToRead); + const bits = (currentByte & mask) >> (8 - this.bitOffset - bitsToRead); + result = (result << bitsToRead) | bits; + } + bitsRead += bitsToRead; + this.bitOffset += bitsToRead; + if (this.bitOffset === 8) { + this.byteOffset++; + this.bitOffset = 0; + } + } + return result; + } +} +exports.BitReader = BitReader; diff --git a/services/slides/node_modules/image-size/package.json b/services/slides/node_modules/image-size/package.json new file mode 100644 index 0000000000000000000000000000000000000000..c241dd15dc9629540633f01a4662258750e5aafe --- /dev/null +++ b/services/slides/node_modules/image-size/package.json @@ -0,0 +1,81 @@ +{ + "name": "image-size", + "version": "1.2.1", + "description": "get dimensions of any image file", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "files": [ + "dist", + "bin/image-size.js" + ], + "engines": { + "node": ">=16.x" + }, + "packageManager": "yarn@4.0.2", + "bin": "bin/image-size.js", + "scripts": { + "lint": "eslint bin lib specs", + "format": "prettier --write lib specs eslint.config.mjs", + "test": "nyc mocha", + "clean": "rm -rf dist docs", + "generate-docs": "typedoc", + "build": "tsc", + "prepack": "yarn clean && yarn build" + }, + "keywords": [ + "image", + "size", + "dimensions", + "resolution", + "width", + "height", + "avif", + "bmp", + "cur", + "gif", + "heic", + "heif", + "icns", + "ico", + "jpeg", + "jxl", + "png", + "psd", + "svg", + "tga", + "tiff", + "webp" + ], + "repository": "git://github.com/image-size/image-size.git", + "author": "netroy (http://netroy.in/)", + "license": "MIT", + "devDependencies": { + "@eslint/js": "9.5.0", + "@types/chai": "4.3.16", + "@types/eslint__js": "8.42.3", + "@types/glob": "8.1.0", + "@types/mocha": "10.0.7", + "@types/node": "18.19.39", + "@types/sinon": "17.0.3", + "chai": "4.4.1", + "eslint": "8.57.0", + "eslint-config-prettier": "9.1.0", + "eslint-plugin-prettier": "5.1.3", + "glob": "10.4.2", + "mocha": "10.2.0", + "nyc": "15.1.0", + "prettier": "3.3.2", + "sinon": "17.0.1", + "ts-node": "10.9.2", + "typedoc": "0.25.13", + "typescript": "5.4.5", + "typescript-eslint": "7.13.1" + }, + "nyc": { + "include": "lib", + "exclude": "specs/*.spec.ts" + }, + "dependencies": { + "queue": "6.0.2" + } +} diff --git a/services/slides/node_modules/immediate/LICENSE.txt b/services/slides/node_modules/immediate/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..88c18c279879a844cf6ee0803dabcdefa02f599a --- /dev/null +++ b/services/slides/node_modules/immediate/LICENSE.txt @@ -0,0 +1,20 @@ +Copyright (c) 2012 Barnesandnoble.com, llc, Donavon West, Domenic Denicola, Brian Cavalier + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/services/slides/node_modules/immediate/README.md b/services/slides/node_modules/immediate/README.md new file mode 100644 index 0000000000000000000000000000000000000000..d661ae8374f65393767f89fbb71655b9aac2d061 --- /dev/null +++ b/services/slides/node_modules/immediate/README.md @@ -0,0 +1,93 @@ +# immediate [![Build Status](https://travis-ci.org/calvinmetcalf/immediate.svg?branch=master)](https://travis-ci.org/calvinmetcalf/immediate) + +``` +npm install immediate --save +``` + +then + +```js +var immediate = require("immediate"); + +immediate(function () { + // this will run soon +}); + +immediate(function (arg1, arg2) { + // get your args like in iojs +}, thing1, thing2); +``` + +## Introduction + +**immediate** is a microtask library, decended from [NobleJS's setImmediate](https://github.com/NobleJS/setImmediate), but including ideas from [Cujo's When](https://github.com/cujojs/when) and [RSVP][RSVP]. + +immediate takes the tricks from setImmedate and RSVP and combines them with the schedualer inspired (vaugly) by whens. + +Note versions 2.6.5 and earlier were strictly speaking a 'macrotask' library not a microtask one, [see this for the difference](https://github.com/YuzuJS/setImmediate#macrotasks-and-microtasks), if you need a macrotask library, [I got you covered](https://github.com/calvinmetcalf/macrotask). + +Several new features were added in versions 3.1.0 and 3.2.0 to maintain parity with +process.nextTick, but the 3.0.x series is still being kept up to date if you just need +the small barebones version. + + +## The Tricks + +### `process.nextTick` + +Note that we check for *actual* Node.js environments, not emulated ones like those produced by browserify or similar. + +### `MutationObserver` + +This is what [RSVP][RSVP] uses, it's very fast, details on [MDN](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver). + + +### `MessageChannel` + +Unfortunately, `postMessage` has completely different semantics inside web workers, and so cannot be used there. So we +turn to [`MessageChannel`][MessageChannel], which has worse browser support, but does work inside a web worker. + +### ` + + + + + + + diff --git a/services/slides/node_modules/pino/lib/caller.js b/services/slides/node_modules/pino/lib/caller.js new file mode 100644 index 0000000000000000000000000000000000000000..f39e08781cebd0d4c2808f8f5497ed08e3521b98 --- /dev/null +++ b/services/slides/node_modules/pino/lib/caller.js @@ -0,0 +1,30 @@ +'use strict' + +function noOpPrepareStackTrace (_, stack) { + return stack +} + +module.exports = function getCallers () { + const originalPrepare = Error.prepareStackTrace + Error.prepareStackTrace = noOpPrepareStackTrace + const stack = new Error().stack + Error.prepareStackTrace = originalPrepare + + if (!Array.isArray(stack)) { + return undefined + } + + const entries = stack.slice(2) + + const fileNames = [] + + for (const entry of entries) { + if (!entry) { + continue + } + + fileNames.push(entry.getFileName()) + } + + return fileNames +} diff --git a/services/slides/node_modules/pino/lib/constants.js b/services/slides/node_modules/pino/lib/constants.js new file mode 100644 index 0000000000000000000000000000000000000000..f91f73157aafc69ce94e2e4256d6cdb083d5e1e0 --- /dev/null +++ b/services/slides/node_modules/pino/lib/constants.js @@ -0,0 +1,28 @@ +/** + * Represents default log level values + * + * @enum {number} + */ +const DEFAULT_LEVELS = { + trace: 10, + debug: 20, + info: 30, + warn: 40, + error: 50, + fatal: 60 +} + +/** + * Represents sort order direction: `ascending` or `descending` + * + * @enum {string} + */ +const SORTING_ORDER = { + ASC: 'ASC', + DESC: 'DESC' +} + +module.exports = { + DEFAULT_LEVELS, + SORTING_ORDER +} diff --git a/services/slides/node_modules/pino/lib/deprecations.js b/services/slides/node_modules/pino/lib/deprecations.js new file mode 100644 index 0000000000000000000000000000000000000000..806c5362e70e6629272cbbf12ed23d9bf9b6a093 --- /dev/null +++ b/services/slides/node_modules/pino/lib/deprecations.js @@ -0,0 +1,8 @@ +'use strict' + +const warning = require('process-warning')() +module.exports = warning + +// const warnName = 'PinoWarning' + +// warning.create(warnName, 'PINODEP010', 'A new deprecation') diff --git a/services/slides/node_modules/pino/lib/levels.js b/services/slides/node_modules/pino/lib/levels.js new file mode 100644 index 0000000000000000000000000000000000000000..67e6a99dbe9663a215cedabeeec93bf57f5662fb --- /dev/null +++ b/services/slides/node_modules/pino/lib/levels.js @@ -0,0 +1,241 @@ +'use strict' +/* eslint no-prototype-builtins: 0 */ +const { + lsCacheSym, + levelValSym, + useOnlyCustomLevelsSym, + streamSym, + formattersSym, + hooksSym, + levelCompSym +} = require('./symbols') +const { noop, genLog } = require('./tools') +const { DEFAULT_LEVELS, SORTING_ORDER } = require('./constants') + +const levelMethods = { + fatal: (hook) => { + const logFatal = genLog(DEFAULT_LEVELS.fatal, hook) + return function (...args) { + const stream = this[streamSym] + logFatal.call(this, ...args) + if (typeof stream.flushSync === 'function') { + try { + stream.flushSync() + } catch (e) { + // https://github.com/pinojs/pino/pull/740#discussion_r346788313 + } + } + } + }, + error: (hook) => genLog(DEFAULT_LEVELS.error, hook), + warn: (hook) => genLog(DEFAULT_LEVELS.warn, hook), + info: (hook) => genLog(DEFAULT_LEVELS.info, hook), + debug: (hook) => genLog(DEFAULT_LEVELS.debug, hook), + trace: (hook) => genLog(DEFAULT_LEVELS.trace, hook) +} + +const nums = Object.keys(DEFAULT_LEVELS).reduce((o, k) => { + o[DEFAULT_LEVELS[k]] = k + return o +}, {}) + +const initialLsCache = Object.keys(nums).reduce((o, k) => { + o[k] = '{"level":' + Number(k) + return o +}, {}) + +function genLsCache (instance) { + const formatter = instance[formattersSym].level + const { labels } = instance.levels + const cache = {} + for (const label in labels) { + const level = formatter(labels[label], Number(label)) + cache[label] = JSON.stringify(level).slice(0, -1) + } + instance[lsCacheSym] = cache + return instance +} + +function isStandardLevel (level, useOnlyCustomLevels) { + if (useOnlyCustomLevels) { + return false + } + + switch (level) { + case 'fatal': + case 'error': + case 'warn': + case 'info': + case 'debug': + case 'trace': + return true + default: + return false + } +} + +function setLevel (level) { + const { labels, values } = this.levels + if (typeof level === 'number') { + if (labels[level] === undefined) throw Error('unknown level value' + level) + level = labels[level] + } + if (values[level] === undefined) throw Error('unknown level ' + level) + const preLevelVal = this[levelValSym] + const levelVal = this[levelValSym] = values[level] + const useOnlyCustomLevelsVal = this[useOnlyCustomLevelsSym] + const levelComparison = this[levelCompSym] + const hook = this[hooksSym].logMethod + + for (const key in values) { + if (levelComparison(values[key], levelVal) === false) { + this[key] = noop + continue + } + this[key] = isStandardLevel(key, useOnlyCustomLevelsVal) ? levelMethods[key](hook) : genLog(values[key], hook) + } + + this.emit( + 'level-change', + level, + levelVal, + labels[preLevelVal], + preLevelVal, + this + ) +} + +function getLevel (level) { + const { levels, levelVal } = this + // protection against potential loss of Pino scope from serializers (edge case with circular refs - https://github.com/pinojs/pino/issues/833) + return (levels && levels.labels) ? levels.labels[levelVal] : '' +} + +function isLevelEnabled (logLevel) { + const { values } = this.levels + const logLevelVal = values[logLevel] + return logLevelVal !== undefined && this[levelCompSym](logLevelVal, this[levelValSym]) +} + +/** + * Determine if the given `current` level is enabled by comparing it + * against the current threshold (`expected`). + * + * @param {SORTING_ORDER} direction comparison direction "ASC" or "DESC" + * @param {number} current current log level number representation + * @param {number} expected threshold value to compare with + * @returns {boolean} + */ +function compareLevel (direction, current, expected) { + if (direction === SORTING_ORDER.DESC) { + return current <= expected + } + + return current >= expected +} + +/** + * Create a level comparison function based on `levelComparison` + * it could a default function which compares levels either in "ascending" or "descending" order or custom comparison function + * + * @param {SORTING_ORDER | Function} levelComparison sort levels order direction or custom comparison function + * @returns Function + */ +function genLevelComparison (levelComparison) { + if (typeof levelComparison === 'string') { + return compareLevel.bind(null, levelComparison) + } + + return levelComparison +} + +function mappings (customLevels = null, useOnlyCustomLevels = false) { + const customNums = customLevels + /* eslint-disable */ + ? Object.keys(customLevels).reduce((o, k) => { + o[customLevels[k]] = k + return o + }, {}) + : null + /* eslint-enable */ + + const labels = Object.assign( + Object.create(Object.prototype, { Infinity: { value: 'silent' } }), + useOnlyCustomLevels ? null : nums, + customNums + ) + const values = Object.assign( + Object.create(Object.prototype, { silent: { value: Infinity } }), + useOnlyCustomLevels ? null : DEFAULT_LEVELS, + customLevels + ) + return { labels, values } +} + +function assertDefaultLevelFound (defaultLevel, customLevels, useOnlyCustomLevels) { + if (typeof defaultLevel === 'number') { + const values = [].concat( + Object.keys(customLevels || {}).map(key => customLevels[key]), + useOnlyCustomLevels ? [] : Object.keys(nums).map(level => +level), + Infinity + ) + if (!values.includes(defaultLevel)) { + throw Error(`default level:${defaultLevel} must be included in custom levels`) + } + return + } + + const labels = Object.assign( + Object.create(Object.prototype, { silent: { value: Infinity } }), + useOnlyCustomLevels ? null : DEFAULT_LEVELS, + customLevels + ) + if (!(defaultLevel in labels)) { + throw Error(`default level:${defaultLevel} must be included in custom levels`) + } +} + +function assertNoLevelCollisions (levels, customLevels) { + const { labels, values } = levels + for (const k in customLevels) { + if (k in values) { + throw Error('levels cannot be overridden') + } + if (customLevels[k] in labels) { + throw Error('pre-existing level values cannot be used for new levels') + } + } +} + +/** + * Validates whether `levelComparison` is correct + * + * @throws Error + * @param {SORTING_ORDER | Function} levelComparison - value to validate + * @returns + */ +function assertLevelComparison (levelComparison) { + if (typeof levelComparison === 'function') { + return + } + + if (typeof levelComparison === 'string' && Object.values(SORTING_ORDER).includes(levelComparison)) { + return + } + + throw new Error('Levels comparison should be one of "ASC", "DESC" or "function" type') +} + +module.exports = { + initialLsCache, + genLsCache, + levelMethods, + getLevel, + setLevel, + isLevelEnabled, + mappings, + assertNoLevelCollisions, + assertDefaultLevelFound, + genLevelComparison, + assertLevelComparison +} diff --git a/services/slides/node_modules/pino/lib/meta.js b/services/slides/node_modules/pino/lib/meta.js new file mode 100644 index 0000000000000000000000000000000000000000..a08fefddc6f3ea253ff82578e87c0ee3d6c908e7 --- /dev/null +++ b/services/slides/node_modules/pino/lib/meta.js @@ -0,0 +1,3 @@ +'use strict' + +module.exports = { version: '10.3.1' } diff --git a/services/slides/node_modules/pino/lib/multistream.js b/services/slides/node_modules/pino/lib/multistream.js new file mode 100644 index 0000000000000000000000000000000000000000..42cdbfb8487ad9f1497c12ae0422575e221a977f --- /dev/null +++ b/services/slides/node_modules/pino/lib/multistream.js @@ -0,0 +1,203 @@ +'use strict' + +const metadata = Symbol.for('pino.metadata') +const { DEFAULT_LEVELS } = require('./constants') + +const DEFAULT_INFO_LEVEL = DEFAULT_LEVELS.info + +function multistream (streamsArray, opts) { + streamsArray = streamsArray || [] + opts = opts || { dedupe: false } + + const streamLevels = Object.create(DEFAULT_LEVELS) + streamLevels.silent = Infinity + if (opts.levels && typeof opts.levels === 'object') { + Object.keys(opts.levels).forEach(i => { + streamLevels[i] = opts.levels[i] + }) + } + + const res = { + write, + add, + remove, + emit, + flushSync, + end, + minLevel: 0, + lastId: 0, + streams: [], + clone, + [metadata]: true, + streamLevels + } + + if (Array.isArray(streamsArray)) { + streamsArray.forEach(add, res) + } else { + add.call(res, streamsArray) + } + + // clean this object up + // or it will stay allocated forever + // as it is closed on the following closures + streamsArray = null + + return res + + // we can exit early because the streams are ordered by level + function write (data) { + let dest + const level = this.lastLevel + const { streams } = this + // for handling situation when several streams has the same level + let recordedLevel = 0 + let stream + + // if dedupe set to true we send logs to the stream with the highest level + // therefore, we have to change sorting order + for (let i = initLoopVar(streams.length, opts.dedupe); checkLoopVar(i, streams.length, opts.dedupe); i = adjustLoopVar(i, opts.dedupe)) { + dest = streams[i] + if (dest.level <= level) { + if (recordedLevel !== 0 && recordedLevel !== dest.level) { + break + } + stream = dest.stream + if (stream[metadata]) { + const { lastTime, lastMsg, lastObj, lastLogger } = this + stream.lastLevel = level + stream.lastTime = lastTime + stream.lastMsg = lastMsg + stream.lastObj = lastObj + stream.lastLogger = lastLogger + } + stream.write(data) + if (opts.dedupe) { + recordedLevel = dest.level + } + } else if (!opts.dedupe) { + break + } + } + } + + function emit (...args) { + for (const { stream } of this.streams) { + if (typeof stream.emit === 'function') { + stream.emit(...args) + } + } + } + + function flushSync () { + for (const { stream } of this.streams) { + if (typeof stream.flushSync === 'function') { + stream.flushSync() + } + } + } + + function add (dest) { + if (!dest) { + return res + } + + // Check that dest implements either StreamEntry or DestinationStream + const isStream = typeof dest.write === 'function' || dest.stream + const stream_ = dest.write ? dest : dest.stream + // This is necessary to provide a meaningful error message, otherwise it throws somewhere inside write() + if (!isStream) { + throw Error('stream object needs to implement either StreamEntry or DestinationStream interface') + } + + const { streams, streamLevels } = this + + let level + if (typeof dest.levelVal === 'number') { + level = dest.levelVal + } else if (typeof dest.level === 'string') { + level = streamLevels[dest.level] + } else if (typeof dest.level === 'number') { + level = dest.level + } else { + level = DEFAULT_INFO_LEVEL + } + + const dest_ = { + stream: stream_, + level, + levelVal: undefined, + id: ++res.lastId + } + + streams.unshift(dest_) + streams.sort(compareByLevel) + + this.minLevel = streams[0].level + + return res + } + + function remove (id) { + const { streams } = this + const index = streams.findIndex(s => s.id === id) + + if (index >= 0) { + streams.splice(index, 1) + streams.sort(compareByLevel) + this.minLevel = streams.length > 0 ? streams[0].level : -1 + } + + return res + } + + function end () { + for (const { stream } of this.streams) { + if (typeof stream.flushSync === 'function') { + stream.flushSync() + } + stream.end() + } + } + + function clone (level) { + const streams = new Array(this.streams.length) + + for (let i = 0; i < streams.length; i++) { + streams[i] = { + level, + stream: this.streams[i].stream + } + } + + return { + write, + add, + remove, + minLevel: level, + streams, + clone, + emit, + flushSync, + [metadata]: true + } + } +} + +function compareByLevel (a, b) { + return a.level - b.level +} + +function initLoopVar (length, dedupe) { + return dedupe ? length - 1 : 0 +} + +function adjustLoopVar (i, dedupe) { + return dedupe ? i - 1 : i + 1 +} + +function checkLoopVar (i, length, dedupe) { + return dedupe ? i >= 0 : i < length +} + +module.exports = multistream diff --git a/services/slides/node_modules/pino/lib/proto.js b/services/slides/node_modules/pino/lib/proto.js new file mode 100644 index 0000000000000000000000000000000000000000..a6ba7226909c1911232f7084b3c3aa5be4d3c780 --- /dev/null +++ b/services/slides/node_modules/pino/lib/proto.js @@ -0,0 +1,256 @@ +'use strict' + +/* eslint no-prototype-builtins: 0 */ + +const { EventEmitter } = require('node:events') +const { + lsCacheSym, + levelValSym, + setLevelSym, + getLevelSym, + chindingsSym, + mixinSym, + asJsonSym, + writeSym, + mixinMergeStrategySym, + timeSym, + timeSliceIndexSym, + streamSym, + serializersSym, + formattersSym, + errorKeySym, + messageKeySym, + useOnlyCustomLevelsSym, + needsMetadataGsym, + redactFmtSym, + stringifySym, + formatOptsSym, + stringifiersSym, + msgPrefixSym, + hooksSym +} = require('./symbols') +const { + getLevel, + setLevel, + isLevelEnabled, + mappings, + initialLsCache, + genLsCache, + assertNoLevelCollisions +} = require('./levels') +const { + asChindings, + asJson, + buildFormatters, + stringify, + noop +} = require('./tools') +const { + version +} = require('./meta') +const redaction = require('./redaction') + +// note: use of class is satirical +// https://github.com/pinojs/pino/pull/433#pullrequestreview-127703127 +const constructor = class Pino {} +const prototype = { + constructor, + child, + bindings, + setBindings, + flush, + isLevelEnabled, + version, + get level () { return this[getLevelSym]() }, + set level (lvl) { this[setLevelSym](lvl) }, + get levelVal () { return this[levelValSym] }, + set levelVal (n) { throw Error('levelVal is read-only') }, + get msgPrefix () { return this[msgPrefixSym] }, + get [Symbol.toStringTag] () { return 'Pino' }, + [lsCacheSym]: initialLsCache, + [writeSym]: write, + [asJsonSym]: asJson, + [getLevelSym]: getLevel, + [setLevelSym]: setLevel +} + +Object.setPrototypeOf(prototype, EventEmitter.prototype) + +// exporting and consuming the prototype object using factory pattern fixes scoping issues with getters when serializing +module.exports = function () { + return Object.create(prototype) +} + +const resetChildingsFormatter = bindings => bindings +function child (bindings, options) { + if (!bindings) { + throw Error('missing bindings for child Pino') + } + const serializers = this[serializersSym] + const formatters = this[formattersSym] + const instance = Object.create(this) + + // If an `options` object was not supplied, we can improve + // the performance of child creation by skipping + // the checks for set options and simply return + // a baseline instance. + if (options == null) { + if (instance[formattersSym].bindings !== resetChildingsFormatter) { + instance[formattersSym] = buildFormatters( + formatters.level, + resetChildingsFormatter, + formatters.log + ) + } + + instance[chindingsSym] = asChindings(instance, bindings) + + if (this.onChild !== noop) { + this.onChild(instance) + } + + return instance + } + + if (options.hasOwnProperty('serializers') === true) { + instance[serializersSym] = Object.create(null) + + for (const k in serializers) { + instance[serializersSym][k] = serializers[k] + } + const parentSymbols = Object.getOwnPropertySymbols(serializers) + /* eslint no-var: off */ + for (var i = 0; i < parentSymbols.length; i++) { + const ks = parentSymbols[i] + instance[serializersSym][ks] = serializers[ks] + } + + for (const bk in options.serializers) { + instance[serializersSym][bk] = options.serializers[bk] + } + const bindingsSymbols = Object.getOwnPropertySymbols(options.serializers) + for (var bi = 0; bi < bindingsSymbols.length; bi++) { + const bks = bindingsSymbols[bi] + instance[serializersSym][bks] = options.serializers[bks] + } + } else instance[serializersSym] = serializers + if (options.hasOwnProperty('formatters')) { + const { level, bindings: chindings, log } = options.formatters + instance[formattersSym] = buildFormatters( + level || formatters.level, + chindings || resetChildingsFormatter, + log || formatters.log + ) + } else { + instance[formattersSym] = buildFormatters( + formatters.level, + resetChildingsFormatter, + formatters.log + ) + } + if (options.hasOwnProperty('customLevels') === true) { + assertNoLevelCollisions(this.levels, options.customLevels) + instance.levels = mappings(options.customLevels, instance[useOnlyCustomLevelsSym]) + genLsCache(instance) + } + + // redact must place before asChindings and only replace if exist + if ((typeof options.redact === 'object' && options.redact !== null) || Array.isArray(options.redact)) { + instance.redact = options.redact // replace redact directly + const stringifiers = redaction(instance.redact, stringify) + const formatOpts = { stringify: stringifiers[redactFmtSym] } + instance[stringifySym] = stringify + instance[stringifiersSym] = stringifiers + instance[formatOptsSym] = formatOpts + } + + if (typeof options.msgPrefix === 'string') { + instance[msgPrefixSym] = (this[msgPrefixSym] || '') + options.msgPrefix + } + + instance[chindingsSym] = asChindings(instance, bindings) + if ((options.level !== undefined && options.level !== this.level) || options.hasOwnProperty('customLevels')) { + const childLevel = options.level || this.level + instance[setLevelSym](childLevel) + } + this.onChild(instance) + return instance +} + +function bindings () { + const chindings = this[chindingsSym] + const chindingsJson = `{${chindings.substr(1)}}` // at least contains ,"pid":7068,"hostname":"myMac" + const bindingsFromJson = JSON.parse(chindingsJson) + delete bindingsFromJson.pid + delete bindingsFromJson.hostname + return bindingsFromJson +} + +function setBindings (newBindings) { + const chindings = asChindings(this, newBindings) + this[chindingsSym] = chindings +} + +/** + * Default strategy for creating `mergeObject` from arguments and the result from `mixin()`. + * Fields from `mergeObject` have higher priority in this strategy. + * + * @param {Object} mergeObject The object a user has supplied to the logging function. + * @param {Object} mixinObject The result of the `mixin` method. + * @return {Object} + */ +function defaultMixinMergeStrategy (mergeObject, mixinObject) { + return Object.assign(mixinObject, mergeObject) +} + +function write (_obj, msg, num) { + const t = this[timeSym]() + const mixin = this[mixinSym] + const errorKey = this[errorKeySym] + const messageKey = this[messageKeySym] + const mixinMergeStrategy = this[mixinMergeStrategySym] || defaultMixinMergeStrategy + let obj + const streamWriteHook = this[hooksSym].streamWrite + + if (_obj === undefined || _obj === null) { + obj = {} + } else if (_obj instanceof Error) { + obj = { [errorKey]: _obj } + if (msg === undefined) { + msg = _obj.message + } + } else { + obj = _obj + if (msg === undefined && _obj[messageKey] === undefined && _obj[errorKey]) { + msg = _obj[errorKey].message + } + } + + if (mixin) { + obj = mixinMergeStrategy(obj, mixin(obj, num, this)) + } + + const s = this[asJsonSym](obj, msg, num, t) + + const stream = this[streamSym] + if (stream[needsMetadataGsym] === true) { + stream.lastLevel = num + stream.lastObj = obj + stream.lastMsg = msg + stream.lastTime = t.slice(this[timeSliceIndexSym]) + stream.lastLogger = this // for child loggers + } + stream.write(streamWriteHook ? streamWriteHook(s) : s) +} + +function flush (cb) { + if (cb != null && typeof cb !== 'function') { + throw Error('callback must be a function') + } + + const stream = this[streamSym] + + if (typeof stream.flush === 'function') { + stream.flush(cb || noop) + } else if (cb) cb() +} diff --git a/services/slides/node_modules/pino/lib/redaction.js b/services/slides/node_modules/pino/lib/redaction.js new file mode 100644 index 0000000000000000000000000000000000000000..4bcb6cacbb6b0c6835b35aa431c88f460f53a1f8 --- /dev/null +++ b/services/slides/node_modules/pino/lib/redaction.js @@ -0,0 +1,114 @@ +'use strict' + +const Redact = require('@pinojs/redact') +const { redactFmtSym, wildcardFirstSym } = require('./symbols') + +// Custom rx regex equivalent to fast-redact's rx +const rx = /[^.[\]]+|\[([^[\]]*?)\]/g + +const CENSOR = '[Redacted]' +const strict = false // TODO should this be configurable? + +function redaction (opts, serialize) { + const { paths, censor, remove } = handle(opts) + + const shape = paths.reduce((o, str) => { + rx.lastIndex = 0 + const first = rx.exec(str) + const next = rx.exec(str) + + // ns is the top-level path segment, brackets + quoting removed. + let ns = first[1] !== undefined + ? first[1].replace(/^(?:"|'|`)(.*)(?:"|'|`)$/, '$1') + : first[0] + + if (ns === '*') { + ns = wildcardFirstSym + } + + // top level key: + if (next === null) { + o[ns] = null + return o + } + + // path with at least two segments: + // if ns is already redacted at the top level, ignore lower level redactions + if (o[ns] === null) { + return o + } + + const { index } = next + const nextPath = `${str.substr(index, str.length - 1)}` + + o[ns] = o[ns] || [] + + // shape is a mix of paths beginning with literal values and wildcard + // paths [ "a.b.c", "*.b.z" ] should reduce to a shape of + // { "a": [ "b.c", "b.z" ], *: [ "b.z" ] } + // note: "b.z" is in both "a" and * arrays because "a" matches the wildcard. + // (* entry has wildcardFirstSym as key) + if (ns !== wildcardFirstSym && o[ns].length === 0) { + // first time ns's get all '*' redactions so far + o[ns].push(...(o[wildcardFirstSym] || [])) + } + + if (ns === wildcardFirstSym) { + // new * path gets added to all previously registered literal ns's. + Object.keys(o).forEach(function (k) { + if (o[k]) { + o[k].push(nextPath) + } + }) + } + + o[ns].push(nextPath) + return o + }, {}) + + // the redactor assigned to the format symbol key + // provides top level redaction for instances where + // an object is interpolated into the msg string + const result = { + [redactFmtSym]: Redact({ paths, censor, serialize, strict, remove }) + } + + const topCensor = (...args) => { + return typeof censor === 'function' ? serialize(censor(...args)) : serialize(censor) + } + + return [...Object.keys(shape), ...Object.getOwnPropertySymbols(shape)].reduce((o, k) => { + // top level key: + if (shape[k] === null) { + o[k] = (value) => topCensor(value, [k]) + } else { + const wrappedCensor = typeof censor === 'function' + ? (value, path) => { + return censor(value, [k, ...path]) + } + : censor + o[k] = Redact({ + paths: shape[k], + censor: wrappedCensor, + serialize, + strict, + remove + }) + } + return o + }, result) +} + +function handle (opts) { + if (Array.isArray(opts)) { + opts = { paths: opts, censor: CENSOR } + return opts + } + let { paths, censor = CENSOR, remove } = opts + if (Array.isArray(paths) === false) { throw Error('pino – redact must contain an array of strings') } + if (remove === true) censor = undefined + + return { paths, censor, remove } +} + +module.exports = redaction diff --git a/services/slides/node_modules/pino/lib/symbols.js b/services/slides/node_modules/pino/lib/symbols.js new file mode 100644 index 0000000000000000000000000000000000000000..69f1a9d2569f8bfee0a79de42e21db50b11edd16 --- /dev/null +++ b/services/slides/node_modules/pino/lib/symbols.js @@ -0,0 +1,74 @@ +'use strict' + +const setLevelSym = Symbol('pino.setLevel') +const getLevelSym = Symbol('pino.getLevel') +const levelValSym = Symbol('pino.levelVal') +const levelCompSym = Symbol('pino.levelComp') +const useLevelLabelsSym = Symbol('pino.useLevelLabels') +const useOnlyCustomLevelsSym = Symbol('pino.useOnlyCustomLevels') +const mixinSym = Symbol('pino.mixin') + +const lsCacheSym = Symbol('pino.lsCache') +const chindingsSym = Symbol('pino.chindings') + +const asJsonSym = Symbol('pino.asJson') +const writeSym = Symbol('pino.write') +const redactFmtSym = Symbol('pino.redactFmt') + +const timeSym = Symbol('pino.time') +const timeSliceIndexSym = Symbol('pino.timeSliceIndex') +const streamSym = Symbol('pino.stream') +const stringifySym = Symbol('pino.stringify') +const stringifySafeSym = Symbol('pino.stringifySafe') +const stringifiersSym = Symbol('pino.stringifiers') +const endSym = Symbol('pino.end') +const formatOptsSym = Symbol('pino.formatOpts') +const messageKeySym = Symbol('pino.messageKey') +const errorKeySym = Symbol('pino.errorKey') +const nestedKeySym = Symbol('pino.nestedKey') +const nestedKeyStrSym = Symbol('pino.nestedKeyStr') +const mixinMergeStrategySym = Symbol('pino.mixinMergeStrategy') +const msgPrefixSym = Symbol('pino.msgPrefix') + +const wildcardFirstSym = Symbol('pino.wildcardFirst') + +// public symbols, no need to use the same pino +// version for these +const serializersSym = Symbol.for('pino.serializers') +const formattersSym = Symbol.for('pino.formatters') +const hooksSym = Symbol.for('pino.hooks') +const needsMetadataGsym = Symbol.for('pino.metadata') + +module.exports = { + setLevelSym, + getLevelSym, + levelValSym, + levelCompSym, + useLevelLabelsSym, + mixinSym, + lsCacheSym, + chindingsSym, + asJsonSym, + writeSym, + serializersSym, + redactFmtSym, + timeSym, + timeSliceIndexSym, + streamSym, + stringifySym, + stringifySafeSym, + stringifiersSym, + endSym, + formatOptsSym, + messageKeySym, + errorKeySym, + nestedKeySym, + wildcardFirstSym, + needsMetadataGsym, + useOnlyCustomLevelsSym, + formattersSym, + hooksSym, + nestedKeyStrSym, + mixinMergeStrategySym, + msgPrefixSym +} diff --git a/services/slides/node_modules/pino/lib/time.js b/services/slides/node_modules/pino/lib/time.js new file mode 100644 index 0000000000000000000000000000000000000000..8275674245b23d8eeefc62c7efd9f74aa6af9f07 --- /dev/null +++ b/services/slides/node_modules/pino/lib/time.js @@ -0,0 +1,39 @@ +'use strict' + +const nullTime = () => '' + +const epochTime = () => `,"time":${Date.now()}` + +const unixTime = () => `,"time":${Math.round(Date.now() / 1000.0)}` + +const isoTime = () => `,"time":"${new Date(Date.now()).toISOString()}"` // using Date.now() for testability + +const NS_PER_MS = 1_000_000n +const NS_PER_SEC = 1_000_000_000n + +const startWallTimeNs = BigInt(Date.now()) * NS_PER_MS +const startHrTime = process.hrtime.bigint() + +const isoTimeNano = () => { + const elapsedNs = process.hrtime.bigint() - startHrTime + const currentTimeNs = startWallTimeNs + elapsedNs + + const secondsSinceEpoch = currentTimeNs / NS_PER_SEC + const nanosWithinSecond = currentTimeNs % NS_PER_SEC + + const msSinceEpoch = Number(secondsSinceEpoch * 1000n + nanosWithinSecond / 1_000_000n) + const date = new Date(msSinceEpoch) + + const year = date.getUTCFullYear() + const month = (date.getUTCMonth() + 1).toString().padStart(2, '0') + const day = date.getUTCDate().toString().padStart(2, '0') + const hours = date.getUTCHours().toString().padStart(2, '0') + const minutes = date.getUTCMinutes().toString().padStart(2, '0') + const seconds = date.getUTCSeconds().toString().padStart(2, '0') + + return `,"time":"${year}-${month}-${day}T${hours}:${minutes}:${seconds}.${nanosWithinSecond + .toString() + .padStart(9, '0')}Z"` +} + +module.exports = { nullTime, epochTime, unixTime, isoTime, isoTimeNano } diff --git a/services/slides/node_modules/pino/lib/tools.js b/services/slides/node_modules/pino/lib/tools.js new file mode 100644 index 0000000000000000000000000000000000000000..5b4af61f22548c850fd79ab5e981e84c85541054 --- /dev/null +++ b/services/slides/node_modules/pino/lib/tools.js @@ -0,0 +1,427 @@ +'use strict' + +/* eslint no-prototype-builtins: 0 */ + +const diagChan = require('node:diagnostics_channel') +const format = require('quick-format-unescaped') +const { mapHttpRequest, mapHttpResponse } = require('pino-std-serializers') +const SonicBoom = require('sonic-boom') +const onExit = require('on-exit-leak-free') +const { + lsCacheSym, + chindingsSym, + writeSym, + serializersSym, + formatOptsSym, + endSym, + stringifiersSym, + stringifySym, + stringifySafeSym, + wildcardFirstSym, + nestedKeySym, + formattersSym, + messageKeySym, + errorKeySym, + nestedKeyStrSym, + msgPrefixSym +} = require('./symbols') +const { isMainThread } = require('worker_threads') +const transport = require('./transport') +const [nodeMajor] = process.versions.node.split('.').map(v => Number(v)) + +const asJsonChan = diagChan.tracingChannel('pino_asJson') + +// JSON.stringify is faster in node 25+. +const asString = nodeMajor >= 25 ? str => JSON.stringify(str) : _asString + +function noop () { +} + +function genLog (level, hook) { + if (!hook) return LOG + + return function hookWrappedLog (...args) { + hook.call(this, args, LOG, level) + } + + function LOG (o, ...n) { + if (typeof o === 'object') { + let msg = o + if (o !== null) { + if (o.method && o.headers && o.socket) { + o = mapHttpRequest(o) + } else if (typeof o.setHeader === 'function') { + o = mapHttpResponse(o) + } + } + let formatParams + if (msg === null && n.length === 0) { + formatParams = [null] + } else { + msg = n.shift() + formatParams = n + } + // We do not use a coercive check for `msg` as it is + // measurably slower than the explicit checks. + if (typeof this[msgPrefixSym] === 'string' && msg !== undefined && msg !== null) { + msg = this[msgPrefixSym] + msg + } + this[writeSym](o, format(msg, formatParams, this[formatOptsSym]), level) + } else { + let msg = o === undefined ? n.shift() : o + + // We do not use a coercive check for `msg` as it is + // measurably slower than the explicit checks. + if (typeof this[msgPrefixSym] === 'string' && msg !== undefined && msg !== null) { + msg = this[msgPrefixSym] + msg + } + this[writeSym](null, format(msg, n, this[formatOptsSym]), level) + } + } +} + +// magically escape strings for json +// relying on their charCodeAt +// everything below 32 needs JSON.stringify() +// 34 and 92 happens all the time, so we +// have a fast case for them +function _asString (str) { + let result = '' + let last = 0 + let found = false + let point = 255 + const l = str.length + if (l > 100) { + return JSON.stringify(str) + } + for (var i = 0; i < l && point >= 32; i++) { + point = str.charCodeAt(i) + if (point === 34 || point === 92) { + result += str.slice(last, i) + '\\' + last = i + found = true + } + } + if (!found) { + result = str + } else { + result += str.slice(last) + } + return point < 32 ? JSON.stringify(str) : '"' + result + '"' +} + +/** + * `asJson` wraps `_asJson` in order to facilitate generating diagnostics. + * + * @param {object} obj The merging object passed to the log method. + * @param {string} msg The log message passed to the log method. + * @param {number} num The log level number. + * @param {number} time The log time in milliseconds. + * + * @returns {string} + */ +function asJson (obj, msg, num, time) { + if (asJsonChan.hasSubscribers === false) { + return _asJson.call(this, obj, msg, num, time) + } + + const store = { instance: this, arguments } + return asJsonChan.traceSync(_asJson, store, this, obj, msg, num, time) +} + +/** + * `_asJson` parses all collected data and generates the finalized newline + * delimited JSON string. + * + * @param {object} obj The merging object passed to the log method. + * @param {string} msg The log message passed to the log method. + * @param {number} num The log level number. + * @param {number} time The log time in milliseconds. + * + * @returns {string} The finalized log string terminated with a newline. + * @private + */ +function _asJson (obj, msg, num, time) { + const stringify = this[stringifySym] + const stringifySafe = this[stringifySafeSym] + const stringifiers = this[stringifiersSym] + const end = this[endSym] + const chindings = this[chindingsSym] + const serializers = this[serializersSym] + const formatters = this[formattersSym] + const messageKey = this[messageKeySym] + const errorKey = this[errorKeySym] + let data = this[lsCacheSym][num] + time + + // we need the child bindings added to the output first so instance logged + // objects can take precedence when JSON.parse-ing the resulting log line + data = data + chindings + + let value + if (formatters.log) { + obj = formatters.log(obj) + } + const wildcardStringifier = stringifiers[wildcardFirstSym] + let propStr = '' + for (const key in obj) { + value = obj[key] + if (Object.prototype.hasOwnProperty.call(obj, key) && value !== undefined) { + if (serializers[key]) { + value = serializers[key](value) + } else if (key === errorKey && serializers.err) { + value = serializers.err(value) + } + + const stringifier = stringifiers[key] || wildcardStringifier + + switch (typeof value) { + case 'undefined': + case 'function': + continue + case 'number': + /* eslint no-fallthrough: "off" */ + if (Number.isFinite(value) === false) { + value = null + } + // this case explicitly falls through to the next one + case 'boolean': + if (stringifier) value = stringifier(value) + break + case 'string': + value = (stringifier || asString)(value) + break + default: + value = (stringifier || stringify)(value, stringifySafe) + } + if (value === undefined) continue + const strKey = asString(key) + propStr += ',' + strKey + ':' + value + } + } + + let msgStr = '' + if (msg !== undefined) { + value = serializers[messageKey] ? serializers[messageKey](msg) : msg + const stringifier = stringifiers[messageKey] || wildcardStringifier + + switch (typeof value) { + case 'function': + break + case 'number': + if (Number.isFinite(value) === false) { + value = null + } + // this case explicitly falls through to the next one + case 'boolean': + if (stringifier) value = stringifier(value) + msgStr = ',"' + messageKey + '":' + value + break + case 'string': + value = (stringifier || asString)(value) + msgStr = ',"' + messageKey + '":' + value + break + default: + value = (stringifier || stringify)(value, stringifySafe) + msgStr = ',"' + messageKey + '":' + value + } + } + + if (this[nestedKeySym] && propStr) { + // place all the obj properties under the specified key + // the nested key is already formatted from the constructor + return data + this[nestedKeyStrSym] + propStr.slice(1) + '}' + msgStr + end + } else { + return data + propStr + msgStr + end + } +} + +function asChindings (instance, bindings) { + let value + let data = instance[chindingsSym] + const stringify = instance[stringifySym] + const stringifySafe = instance[stringifySafeSym] + const stringifiers = instance[stringifiersSym] + const wildcardStringifier = stringifiers[wildcardFirstSym] + const serializers = instance[serializersSym] + const formatter = instance[formattersSym].bindings + bindings = formatter(bindings) + + for (const key in bindings) { + value = bindings[key] + const valid = (key.length < 5 || (key !== 'level' && + key !== 'serializers' && + key !== 'formatters' && + key !== 'customLevels')) && + bindings.hasOwnProperty(key) && + value !== undefined + if (valid === true) { + value = serializers[key] ? serializers[key](value) : value + value = (stringifiers[key] || wildcardStringifier || stringify)(value, stringifySafe) + if (value === undefined) continue + data += ',"' + key + '":' + value + } + } + return data +} + +function hasBeenTampered (stream) { + return stream.write !== stream.constructor.prototype.write +} + +function buildSafeSonicBoom (opts) { + const stream = new SonicBoom(opts) + stream.on('error', filterBrokenPipe) + // If we are sync: false, we must flush on exit + if (!opts.sync && isMainThread) { + onExit.register(stream, autoEnd) + + stream.on('close', function () { + onExit.unregister(stream) + }) + } + return stream + + function filterBrokenPipe (err) { + // Impossible to replicate across all operating systems + /* istanbul ignore next */ + if (err.code === 'EPIPE') { + // If we get EPIPE, we should stop logging here + // however we have no control to the consumer of + // SonicBoom, so we just overwrite the write method + stream.write = noop + stream.end = noop + stream.flushSync = noop + stream.destroy = noop + return + } + stream.removeListener('error', filterBrokenPipe) + stream.emit('error', err) + } +} + +function autoEnd (stream, eventName) { + // This check is needed only on some platforms + /* istanbul ignore next */ + if (stream.destroyed) { + return + } + + if (eventName === 'beforeExit') { + // We still have an event loop, let's use it + stream.flush() + stream.on('drain', function () { + stream.end() + }) + } else { + // For some reason istanbul is not detecting this, but it's there + /* istanbul ignore next */ + // We do not have an event loop, so flush synchronously + stream.flushSync() + } +} + +function createArgsNormalizer (defaultOptions) { + return function normalizeArgs (instance, caller, opts = {}, stream) { + // support stream as a string + if (typeof opts === 'string') { + stream = buildSafeSonicBoom({ dest: opts }) + opts = {} + } else if (typeof stream === 'string') { + if (opts && opts.transport) { + throw Error('only one of option.transport or stream can be specified') + } + stream = buildSafeSonicBoom({ dest: stream }) + } else if (opts instanceof SonicBoom || opts.writable || opts._writableState) { + stream = opts + opts = {} + } else if (opts.transport) { + if (opts.transport instanceof SonicBoom || opts.transport.writable || opts.transport._writableState) { + throw Error('option.transport do not allow stream, please pass to option directly. e.g. pino(transport)') + } + if (opts.transport.targets && opts.transport.targets.length && opts.formatters && typeof opts.formatters.level === 'function') { + throw Error('option.transport.targets do not allow custom level formatters') + } + + let customLevels + if (opts.customLevels) { + customLevels = opts.useOnlyCustomLevels ? opts.customLevels : Object.assign({}, opts.levels, opts.customLevels) + } + stream = transport({ caller, ...opts.transport, levels: customLevels }) + } + opts = Object.assign({}, defaultOptions, opts) + opts.serializers = Object.assign({}, defaultOptions.serializers, opts.serializers) + opts.formatters = Object.assign({}, defaultOptions.formatters, opts.formatters) + + if (opts.prettyPrint) { + throw new Error('prettyPrint option is no longer supported, see the pino-pretty package (https://github.com/pinojs/pino-pretty)') + } + + const { enabled, onChild } = opts + if (enabled === false) opts.level = 'silent' + if (!onChild) opts.onChild = noop + if (!stream) { + if (!hasBeenTampered(process.stdout)) { + // If process.stdout.fd is undefined, it means that we are running + // in a worker thread. Let's assume we are logging to file descriptor 1. + stream = buildSafeSonicBoom({ fd: process.stdout.fd || 1 }) + } else { + stream = process.stdout + } + } + return { opts, stream } + } +} + +function stringify (obj, stringifySafeFn) { + try { + return JSON.stringify(obj) + } catch (_) { + try { + const stringify = stringifySafeFn || this[stringifySafeSym] + return stringify(obj) + } catch (_) { + return '"[unable to serialize, circular reference is too complex to analyze]"' + } + } +} + +function buildFormatters (level, bindings, log) { + return { + level, + bindings, + log + } +} + +/** + * Convert a string integer file descriptor to a proper native integer + * file descriptor. + * + * @param {string} destination The file descriptor string to attempt to convert. + * + * @returns {Number} + */ +function normalizeDestFileDescriptor (destination) { + const fd = Number(destination) + if (typeof destination === 'string' && Number.isFinite(fd)) { + return fd + } + // destination could be undefined if we are in a worker + if (destination === undefined) { + // This is stdout in UNIX systems + return 1 + } + return destination +} + +module.exports = { + noop, + buildSafeSonicBoom, + asChindings, + asJson, + genLog, + createArgsNormalizer, + stringify, + buildFormatters, + normalizeDestFileDescriptor +} diff --git a/services/slides/node_modules/pino/lib/transport-stream.js b/services/slides/node_modules/pino/lib/transport-stream.js new file mode 100644 index 0000000000000000000000000000000000000000..22cb37e0a8c0f2e8114c30d9e4cb9f4dd1a85d81 --- /dev/null +++ b/services/slides/node_modules/pino/lib/transport-stream.js @@ -0,0 +1,56 @@ +'use strict' + +const { realImport, realRequire } = require('real-require') + +module.exports = loadTransportStreamBuilder + +/** + * Loads & returns a function to build transport streams + * @param {string} target + * @returns {Promise>} + * @throws {Error} In case the target module does not export a function + */ +async function loadTransportStreamBuilder (target) { + let fn + try { + const toLoad = target.startsWith('file://') ? target : 'file://' + target + + if (toLoad.endsWith('.ts') || toLoad.endsWith('.cts')) { + // TODO: add support for the TSM modules loader ( https://github.com/lukeed/tsm ). + if (process[Symbol.for('ts-node.register.instance')]) { + realRequire('ts-node/register') + } else if (process.env && process.env.TS_NODE_DEV) { + realRequire('ts-node-dev') + } + // TODO: Support ES imports once tsc, tap & ts-node provide better compatibility guarantees. + fn = realRequire(decodeURIComponent(target)) + } else { + fn = (await realImport(toLoad)) + } + } catch (error) { + // See this PR for details: https://github.com/pinojs/thread-stream/pull/34 + if ((error.code === 'ENOTDIR' || error.code === 'ERR_MODULE_NOT_FOUND')) { + fn = realRequire(target) + } else if (error.code === undefined || error.code === 'ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING') { + // When bundled with pkg, an undefined error is thrown when called with realImport + // When bundled with pkg and using node v20, an ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING error is thrown when called with realImport + // More info at: https://github.com/pinojs/thread-stream/issues/143 + try { + fn = realRequire(decodeURIComponent(target)) + } catch { + throw error + } + } else { + throw error + } + } + + // Depending on how the default export is performed, and on how the code is + // transpiled, we may find cases of two nested "default" objects. + // See https://github.com/pinojs/pino/issues/1243#issuecomment-982774762 + if (typeof fn === 'object') fn = fn.default + if (typeof fn === 'object') fn = fn.default + if (typeof fn !== 'function') throw Error('exported worker is not a function') + + return fn +} diff --git a/services/slides/node_modules/pino/lib/transport.js b/services/slides/node_modules/pino/lib/transport.js new file mode 100644 index 0000000000000000000000000000000000000000..686a0256be3d15b6c52edabff40ae0aa35bc0e89 --- /dev/null +++ b/services/slides/node_modules/pino/lib/transport.js @@ -0,0 +1,289 @@ +'use strict' + +const { createRequire } = require('module') +const { existsSync } = require('node:fs') +const getCallers = require('./caller') +const { join, isAbsolute, sep } = require('node:path') +const { fileURLToPath } = require('node:url') +const sleep = require('atomic-sleep') +const onExit = require('on-exit-leak-free') +const ThreadStream = require('thread-stream') + +function setupOnExit (stream) { + // This is leak free, it does not leave event handlers + onExit.register(stream, autoEnd) + onExit.registerBeforeExit(stream, flush) + + stream.on('close', function () { + onExit.unregister(stream) + }) +} + +// Check if preload flags exist in execArgv. +// During preload phase (require.main undefined), we pass empty execArgv to prevent infinite worker spawning. +// We don't try to filter and pass other flags because many (like --stack-trace-limit, --tls-cipher-list) +// aren't valid for worker threads and would cause ERR_WORKER_INVALID_EXEC_ARGV. +function hasPreloadFlags () { + const execArgv = process.execArgv + for (let i = 0; i < execArgv.length; i++) { + const arg = execArgv[i] + if (arg === '--import' || arg === '--require' || arg === '-r') { + return true + } + if (arg.startsWith('--import=') || arg.startsWith('--require=') || arg.startsWith('-r=')) { + return true + } + } + return false +} + +function sanitizeNodeOptions (nodeOptions) { + const tokens = nodeOptions.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) + if (!tokens) { + return nodeOptions + } + + const sanitized = [] + let changed = false + + for (let i = 0; i < tokens.length; i++) { + const token = tokens[i] + + if (token === '--require' || token === '-r' || token === '--import') { + const next = tokens[i + 1] + if (next && shouldDropPreload(next)) { + changed = true + i++ + continue + } + + sanitized.push(token) + if (next) { + sanitized.push(next) + i++ + } + continue + } + + if (token.startsWith('--require=') || token.startsWith('-r=') || token.startsWith('--import=')) { + const value = token.slice(token.indexOf('=') + 1) + if (shouldDropPreload(value)) { + changed = true + continue + } + } + + sanitized.push(token) + } + + return changed ? sanitized.join(' ') : nodeOptions +} + +function shouldDropPreload (value) { + const unquoted = stripQuotes(value) + if (!unquoted) { + return false + } + + let path = unquoted + if (path.startsWith('file://')) { + try { + path = fileURLToPath(path) + } catch { + return false + } + } + + return isAbsolute(path) && !existsSync(path) +} + +function stripQuotes (value) { + const first = value[0] + const last = value[value.length - 1] + + if ((first === '"' && last === '"') || (first === "'" && last === "'")) { + return value.slice(1, -1) + } + + return value +} + +function buildStream (filename, workerData, workerOpts, sync, name) { + // When pino is loaded during a preload phase (via --import or --require), + // pass empty execArgv to prevent infinite spawning. Each worker would + // otherwise re-run the preload, creating another transport. + if (!workerOpts.execArgv && hasPreloadFlags() && require.main === undefined) { + workerOpts = { + ...workerOpts, + execArgv: [] + } + } + + if (!workerOpts.env && process.env.NODE_OPTIONS) { + const nodeOptions = sanitizeNodeOptions(process.env.NODE_OPTIONS) + if (nodeOptions !== process.env.NODE_OPTIONS) { + workerOpts = { + ...workerOpts, + env: { + ...process.env, + NODE_OPTIONS: nodeOptions + } + } + } + } + + workerOpts = { ...workerOpts, name } + + const stream = new ThreadStream({ + filename, + workerData, + workerOpts, + sync + }) + + stream.on('ready', onReady) + stream.on('close', function () { + process.removeListener('exit', onExit) + }) + + process.on('exit', onExit) + + function onReady () { + process.removeListener('exit', onExit) + stream.unref() + + if (workerOpts.autoEnd !== false) { + setupOnExit(stream) + } + } + + function onExit () { + /* istanbul ignore next */ + if (stream.closed) { + return + } + stream.flushSync() + // Apparently there is a very sporadic race condition + // that in certain OS would prevent the messages to be flushed + // because the thread might not have been created still. + // Unfortunately we need to sleep(100) in this case. + sleep(100) + stream.end() + } + + return stream +} + +function autoEnd (stream) { + stream.ref() + stream.flushSync() + stream.end() + stream.once('close', function () { + stream.unref() + }) +} + +function flush (stream) { + stream.flushSync() +} + +function transport (fullOptions) { + const { pipeline, targets, levels, dedupe, worker = {}, caller = getCallers(), sync = false } = fullOptions + + const options = { + ...fullOptions.options + } + + // Backwards compatibility + const callers = typeof caller === 'string' ? [caller] : caller + + // This will be eventually modified by bundlers + const bundlerOverrides = (typeof globalThis === 'object' && + Object.prototype.hasOwnProperty.call(globalThis, '__bundlerPathsOverrides') && + globalThis.__bundlerPathsOverrides && + typeof globalThis.__bundlerPathsOverrides === 'object') + ? globalThis.__bundlerPathsOverrides + : Object.create(null) + + let target = fullOptions.target + + if (target && targets) { + throw new Error('only one of target or targets can be specified') + } + + if (targets) { + target = bundlerOverrides['pino-worker'] || join(__dirname, 'worker.js') + options.targets = targets.filter(dest => dest.target).map((dest) => { + return { + ...dest, + target: fixTarget(dest.target) + } + }) + options.pipelines = targets.filter(dest => dest.pipeline).map((dest) => { + return dest.pipeline.map((t) => { + return { + ...t, + level: dest.level, // duplicate the pipeline `level` property defined in the upper level + target: fixTarget(t.target) + } + }) + }) + } else if (pipeline) { + target = bundlerOverrides['pino-worker'] || join(__dirname, 'worker.js') + options.pipelines = [pipeline.map((dest) => { + return { + ...dest, + target: fixTarget(dest.target) + } + })] + } + + if (levels) { + options.levels = levels + } + + if (dedupe) { + options.dedupe = dedupe + } + + options.pinoWillSendConfig = true + + const name = (targets || pipeline) ? 'pino.transport' : target + return buildStream(fixTarget(target), options, worker, sync, name) + + function fixTarget (origin) { + origin = bundlerOverrides[origin] || origin + + if (isAbsolute(origin) || origin.indexOf('file://') === 0) { + return origin + } + + if (origin === 'pino/file') { + return join(__dirname, '..', 'file.js') + } + + let fixTarget + + for (const filePath of callers) { + try { + const context = filePath === 'node:repl' + ? process.cwd() + sep + : filePath + + fixTarget = createRequire(context).resolve(origin) + break + } catch (err) { + // Silent catch + continue + } + } + + if (!fixTarget) { + throw new Error(`unable to determine transport target for "${origin}"`) + } + + return fixTarget + } +} + +module.exports = transport diff --git a/services/slides/node_modules/pino/lib/worker.js b/services/slides/node_modules/pino/lib/worker.js new file mode 100644 index 0000000000000000000000000000000000000000..0bc035a00e4546fb6b5a927ca7f84021edcbb4b6 --- /dev/null +++ b/services/slides/node_modules/pino/lib/worker.js @@ -0,0 +1,194 @@ +'use strict' + +const EE = require('node:events') +const { pipeline, PassThrough } = require('node:stream') +const pino = require('../pino.js') +const build = require('pino-abstract-transport') +const loadTransportStreamBuilder = require('./transport-stream') + +// This file is not checked by the code coverage tool, +// as it is not reliable. + +/* istanbul ignore file */ + +/* + * > Multiple targets & pipelines + * + * + * ┌─────────────────────────────────────────────────┐ ┌─────┐ + * │ │ │ p │ + * │ │ │ i │ + * │ target │ │ n │ + * │ │ ────────────────────────────────┼────┤ o │ + * │ targets │ target │ │ . │ + * │ ────────────► │ ────────────────────────────────┼────┤ m │ source + * │ │ target │ │ u │ │ + * │ │ ────────────────────────────────┼────┤ l │ │write + * │ │ │ │ t │ ▼ + * │ │ pipeline ┌───────────────┐ │ │ i │ ┌────────┐ + * │ │ ──────────► │ PassThrough ├───┼────┤ s ├──────┤ │ + * │ │ └───────────────┘ │ │ t │ write│ Thread │ + * │ │ │ │ r │◄─────┤ Stream │ + * │ │ pipeline ┌───────────────┐ │ │ e │ │ │ + * │ │ ──────────► │ PassThrough ├───┼────┤ a │ └────────┘ + * │ └───────────────┘ │ │ m │ + * │ │ │ │ + * └─────────────────────────────────────────────────┘ └─────┘ + * + * + * + * > One single pipeline or target + * + * + * source + * │ + * ┌────────────────────────────────────────────────┐ │write + * │ │ ▼ + * │ │ ┌────────┐ + * │ targets │ target │ │ │ + * │ ────────────► │ ──────────────────────────────┤ │ │ + * │ │ │ │ │ + * │ ├──────┤ │ + * │ │ │ │ + * │ │ │ │ + * │ OR │ │ │ + * │ │ │ │ + * │ │ │ │ + * │ ┌──────────────┐ │ │ │ + * │ targets │ pipeline │ │ │ │ Thread │ + * │ ────────────► │ ────────────►│ PassThrough ├─┤ │ Stream │ + * │ │ │ │ │ │ │ + * │ └──────────────┘ │ │ │ + * │ │ │ │ + * │ OR │ write│ │ + * │ │◄─────┤ │ + * │ │ │ │ + * │ ┌──────────────┐ │ │ │ + * │ pipeline │ │ │ │ │ + * │ ──────────────►│ PassThrough ├────────────────┤ │ │ + * │ │ │ │ │ │ + * │ └──────────────┘ │ └────────┘ + * │ │ + * │ │ + * └────────────────────────────────────────────────┘ + */ + +module.exports = async function ({ targets, pipelines, levels, dedupe }) { + const targetStreams = [] + + // Process targets + if (targets && targets.length) { + targets = await Promise.all(targets.map(async (t) => { + const fn = await loadTransportStreamBuilder(t.target) + const stream = await fn(t.options) + return { + level: t.level, + stream + } + })) + + targetStreams.push(...targets) + } + + // Process pipelines + if (pipelines && pipelines.length) { + pipelines = await Promise.all( + pipelines.map(async (p) => { + let level + const pipeDests = await Promise.all( + p.map(async (t) => { + // level assigned to pipeline is duplicated over all its targets, just store it + level = t.level + const fn = await loadTransportStreamBuilder(t.target) + const stream = await fn(t.options) + return stream + } + )) + + return { + level, + stream: createPipeline(pipeDests) + } + }) + ) + targetStreams.push(...pipelines) + } + + // Skip building the multistream step if either one single pipeline or target is defined and + // return directly the stream instance back to TreadStream. + // This is equivalent to define either: + // + // pino.transport({ target: ... }) + // + // OR + // + // pino.transport({ pipeline: ... }) + if (targetStreams.length === 1) { + return targetStreams[0].stream + } else { + return build(process, { + parse: 'lines', + metadata: true, + close (err, cb) { + let expected = 0 + for (const transport of targetStreams) { + expected++ + transport.stream.on('close', closeCb) + transport.stream.end() + } + + function closeCb () { + if (--expected === 0) { + cb(err) + } + } + } + }) + } + + // TODO: Why split2 was not used for pipelines? + function process (stream) { + const multi = pino.multistream(targetStreams, { levels, dedupe }) + // TODO manage backpressure + stream.on('data', function (chunk) { + const { lastTime, lastMsg, lastObj, lastLevel } = this + multi.lastLevel = lastLevel + multi.lastTime = lastTime + multi.lastMsg = lastMsg + multi.lastObj = lastObj + + // TODO handle backpressure + multi.write(chunk + '\n') + }) + } + + /** + * Creates a pipeline using the provided streams and return an instance of `PassThrough` stream + * as a source for the pipeline. + * + * @param {(TransformStream|WritableStream)[]} streams An array of streams. + * All intermediate streams in the array *MUST* be `Transform` streams and only the last one `Writable`. + * @returns A `PassThrough` stream instance representing the source stream of the pipeline + */ + function createPipeline (streams) { + const ee = new EE() + const stream = new PassThrough({ + autoDestroy: true, + destroy (_, cb) { + ee.on('error', cb) + ee.on('closed', cb) + } + }) + + pipeline(stream, ...streams, function (err) { + if (err && err.code !== 'ERR_STREAM_PREMATURE_CLOSE') { + ee.emit('error', err) + return + } + + ee.emit('closed') + }) + + return stream + } +} diff --git a/services/slides/node_modules/pino/package.json b/services/slides/node_modules/pino/package.json new file mode 100644 index 0000000000000000000000000000000000000000..b294dd4e0ed5ca9b541e306c16c90abeb6c1594e --- /dev/null +++ b/services/slides/node_modules/pino/package.json @@ -0,0 +1,120 @@ +{ + "name": "pino", + "version": "10.3.1", + "description": "super fast, all natural json logger", + "main": "pino.js", + "type": "commonjs", + "types": "pino.d.ts", + "browser": "./browser.js", + "scripts": { + "borp": "borp --timeout 60000 --coverage --check-coverage --lines 95 --functions 95 --branches 95 --statements 95", + "docs": "docsify serve", + "browser-test": "airtap --local 8080 test/browser*test.js", + "lint": "eslint .", + "prepublishOnly": "node test/internals/version.test.js", + "test": "npm run lint && npm run transpile && npm run borp && jest test/jest && npm run test-types", + "test-ci": "npm run lint && npm run transpile && npm run borp && npm run test-types", + "test-ci-pnpm": "pnpm run lint && npm run transpile && borp --timeout 60000 && pnpm run test-types", + "test-ci-yarn-pnp": "yarn run lint && npm run transpile && borp --timeout 60000", + "test-types": "tsc && tsd && ts-node test/types/pino.ts && attw --pack .", + "test:smoke": "smoker smoke:pino && smoker smoke:browser && smoker smoke:file", + "smoke:pino": "node ./pino.js", + "smoke:browser": "node ./browser.js", + "smoke:file": "node ./file.js", + "transpile": "node ./test/fixtures/ts/transpile.cjs", + "cov-ui": "tap --ts --coverage-report=html", + "bench": "node benchmarks/utils/runbench all", + "bench-basic": "node benchmarks/utils/runbench basic", + "bench-object": "node benchmarks/utils/runbench object", + "bench-deep-object": "node benchmarks/utils/runbench deep-object", + "bench-multi-arg": "node benchmarks/utils/runbench multi-arg", + "bench-long-string": "node benchmarks/utils/runbench long-string", + "bench-child": "node benchmarks/utils/runbench child", + "bench-child-child": "node benchmarks/utils/runbench child-child", + "bench-child-creation": "node benchmarks/utils/runbench child-creation", + "bench-formatters": "node benchmarks/utils/runbench formatters", + "update-bench-doc": "node benchmarks/utils/generate-benchmark-doc > docs/benchmarks.md" + }, + "bin": { + "pino": "./bin.js" + }, + "precommit": "test", + "repository": { + "type": "git", + "url": "git+https://github.com/pinojs/pino.git" + }, + "keywords": [ + "fast", + "logger", + "stream", + "json" + ], + "author": "Matteo Collina ", + "contributors": [ + "David Mark Clements ", + "James Sumners ", + "Thomas Watson Steen (https://twitter.com/wa7son)" + ], + "license": "MIT", + "bugs": { + "url": "https://github.com/pinojs/pino/issues" + }, + "homepage": "https://getpino.io", + "devDependencies": { + "@arethetypeswrong/cli": "^0.18.1", + "@matteo.collina/tspl": "^0.2.0", + "@types/flush-write-stream": "^1.0.0", + "@types/node": "^25.0.3", + "airtap": "5.0.0", + "bole": "^5.0.5", + "borp": "^0.21.0", + "bunyan": "^1.8.14", + "debug": "^4.3.4", + "docsify-cli": "^4.4.4", + "eslint": "^9.37.0", + "eslint-plugin-import": "^2.26.0", + "eslint-plugin-n": "17.23.2", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-promise": "^6.0.0", + "execa": "^5.0.0", + "fastbench": "^1.0.1", + "flush-write-stream": "^2.0.0", + "import-fresh": "^3.2.1", + "jest": "^30.0.3", + "log": "^6.0.0", + "loglevel": "^1.6.7", + "midnight-smoker": "1.1.1", + "neostandard": "^0.12.2", + "pino-pretty": "^13.0.0", + "pre-commit": "^1.2.2", + "proxyquire": "^2.1.3", + "pump": "^3.0.0", + "rimraf": "^6.0.1", + "semver": "^7.3.7", + "split2": "^4.0.0", + "steed": "^1.1.3", + "strip-ansi": "^6.0.0", + "tape": "^5.5.3", + "through2": "^4.0.0", + "ts-node": "^10.9.1", + "tsd": "^0.33.0", + "typescript": "~5.9.2", + "winston": "^3.7.2" + }, + "dependencies": { + "atomic-sleep": "^1.0.0", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^3.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^5.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "@pinojs/redact": "^0.4.0", + "sonic-boom": "^4.0.1", + "thread-stream": "^4.0.0" + }, + "tsd": { + "directory": "test/types" + } +} diff --git a/services/slides/node_modules/pino/pino.d.ts b/services/slides/node_modules/pino/pino.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..0e2cd8da35d094503aafea6577580e3b1bb43251 --- /dev/null +++ b/services/slides/node_modules/pino/pino.d.ts @@ -0,0 +1,908 @@ +// Project: https://github.com/pinojs/pino.git, http://getpino.io +// Definitions by: Peter Snider +// BendingBender +// Christian Rackerseder +// GP +// Alex Ferrando +// Oleksandr Sidko +// Harris Lummis +// Raoul Jaeckel +// Cory Donkin +// Adam Vigneaux +// Austin Beer +// Michel Nemnom +// Igor Savin +// James Bromwell + +import type { EventEmitter } from "events"; +import * as pinoStdSerializers from "pino-std-serializers"; +import type { SonicBoom, SonicBoomOpts } from "sonic-boom"; +import ThreadStream from "thread-stream"; +import type { WorkerOptions } from "worker_threads"; + +declare namespace pino { + //// Non-exported types and interfaces + + type TimeFn = () => string; + type MixinFn = (mergeObject: object, level: number, logger:Logger) => object; + type MixinMergeStrategyFn = (mergeObject: object, mixinObject: object) => object; + + type CustomLevelLogger = { + /** + * Define additional logging levels. + */ + customLevels: { [level in CustomLevels]: number }; + /** + * Use only defined `customLevels` and omit Pino's levels. + */ + useOnlyCustomLevels: UseOnlyCustomLevels; + } & { + // This will override default log methods + [K in Exclude]: UseOnlyCustomLevels extends true ? never : LogFn; + } & { + [level in CustomLevels]: LogFn; + }; + + /** + * A synchronous callback that will run on each creation of a new child. + * @param child: The newly created child logger instance. + */ + type OnChildCallback = (child: Logger) => void + + export interface redactOptions { + paths: string[]; + censor?: string | ((value: unknown, path: string[]) => unknown); + remove?: boolean; + } + + export interface LoggerExtras extends EventEmitter { + /** + * Exposes the Pino package version. Also available on the exported pino function. + */ + readonly version: string; + + levels: LevelMapping; + + /** + * Outputs the level as a string instead of integer. + */ + useLevelLabels: boolean; + /** + * Returns the integer value for the logger instance's logging level. + */ + levelVal: number; + + /** + * Creates a child logger, setting all key-value pairs in `bindings` as properties in the log lines. All serializers will be applied to the given pair. + * Child loggers use the same output stream as the parent and inherit the current log level of the parent at the time they are spawned. + * From v2.x.x the log level of a child is mutable (whereas in v1.x.x it was immutable), and can be set independently of the parent. + * If a `level` property is present in the object passed to `child` it will override the child logger level. + * + * @param bindings: an object of key-value pairs to include in log lines as properties. + * @param options: an options object that will override child logger inherited options. + * @returns a child logger instance. + */ + child(bindings: Bindings, options?: ChildLoggerOptions): Logger; + + /** + * This can be used to modify the callback function on creation of a new child. + */ + onChild: OnChildCallback; + + /** + * Registers a listener function that is triggered when the level is changed. + * Note: When browserified, this functionality will only be available if the `events` module has been required elsewhere + * (e.g. if you're using streams in the browser). This allows for a trade-off between bundle size and functionality. + * + * @param event: only ever fires the `'level-change'` event + * @param listener: The listener is passed four arguments: `levelLabel`, `levelValue`, `previousLevelLabel`, `previousLevelValue`. + */ + on(event: "level-change", listener: LevelChangeEventListener): this; + addListener(event: "level-change", listener: LevelChangeEventListener): this; + once(event: "level-change", listener: LevelChangeEventListener): this; + prependListener(event: "level-change", listener: LevelChangeEventListener): this; + prependOnceListener(event: "level-change", listener: LevelChangeEventListener): this; + removeListener(event: "level-change", listener: LevelChangeEventListener): this; + + /** + * A utility method for determining if a given log level will write to the destination. + */ + isLevelEnabled(level: LevelWithSilentOrString): boolean; + + /** + * Returns an object containing all the current bindings, cloned from the ones passed in via logger.child(). + */ + bindings(): Bindings; + + /** + * Adds to the bindings of this logger instance. + * Note: Does not overwrite bindings. Can potentially result in duplicate keys in log lines. + * + * @param bindings: an object of key-value pairs to include in log lines as properties. + */ + setBindings(bindings: Bindings): void; + + /** + * Flushes the content of the buffer when using pino.destination({ sync: false }). + * call the callback when finished + */ + flush(cb?: (err?: Error) => void): void; + } + + //// Exported types and interfaces + export interface BaseLogger { + /** + * Set this property to the desired logging level. In order of priority, available levels are: + * + * - 'fatal' + * - 'error' + * - 'warn' + * - 'info' + * - 'debug' + * - 'trace' + * + * The logging level is a __minimum__ level. For instance if `logger.level` is `'info'` then all `'fatal'`, `'error'`, `'warn'`, + * and `'info'` logs will be enabled. + * + * You can pass `'silent'` to disable logging. + */ + level: LevelWithSilentOrString; + + /** + * Log at `'fatal'` level the given msg. If the first argument is an object, all its properties will be included in the JSON line. + * If more args follows `msg`, these will be used to format `msg` using `util.format`. + * + * @typeParam T: the interface of the object being serialized. Default is object. + * @param obj: object to be serialized + * @param msg: the log message to write + * @param ...args: format string values when `msg` is a format string + */ + fatal: LogFn; + /** + * Log at `'error'` level the given msg. If the first argument is an object, all its properties will be included in the JSON line. + * If more args follows `msg`, these will be used to format `msg` using `util.format`. + * + * @typeParam T: the interface of the object being serialized. Default is object. + * @param obj: object to be serialized + * @param msg: the log message to write + * @param ...args: format string values when `msg` is a format string + */ + error: LogFn; + /** + * Log at `'warn'` level the given msg. If the first argument is an object, all its properties will be included in the JSON line. + * If more args follows `msg`, these will be used to format `msg` using `util.format`. + * + * @typeParam T: the interface of the object being serialized. Default is object. + * @param obj: object to be serialized + * @param msg: the log message to write + * @param ...args: format string values when `msg` is a format string + */ + warn: LogFn; + /** + * Log at `'info'` level the given msg. If the first argument is an object, all its properties will be included in the JSON line. + * If more args follows `msg`, these will be used to format `msg` using `util.format`. + * + * @typeParam T: the interface of the object being serialized. Default is object. + * @param obj: object to be serialized + * @param msg: the log message to write + * @param ...args: format string values when `msg` is a format string + */ + info: LogFn; + /** + * Log at `'debug'` level the given msg. If the first argument is an object, all its properties will be included in the JSON line. + * If more args follows `msg`, these will be used to format `msg` using `util.format`. + * + * @typeParam T: the interface of the object being serialized. Default is object. + * @param obj: object to be serialized + * @param msg: the log message to write + * @param ...args: format string values when `msg` is a format string + */ + debug: LogFn; + /** + * Log at `'trace'` level the given msg. If the first argument is an object, all its properties will be included in the JSON line. + * If more args follows `msg`, these will be used to format `msg` using `util.format`. + * + * @typeParam T: the interface of the object being serialized. Default is object. + * @param obj: object to be serialized + * @param msg: the log message to write + * @param ...args: format string values when `msg` is a format string + */ + trace: LogFn; + /** + * Noop function. + */ + silent: LogFn; + + /** + * Get `msgPrefix` of the logger instance. + * + * See {@link https://github.com/pinojs/pino/blob/main/docs/api.md#msgprefix-string}. + */ + get msgPrefix(): string | undefined; + } + + export type Bindings = Record; + + export type Level = "fatal" | "error" | "warn" | "info" | "debug" | "trace"; + export type LevelOrString = Level | (string & {}); + export type LevelWithSilent = Level | "silent"; + export type LevelWithSilentOrString = LevelWithSilent | (string & {}); + + export type SerializerFn = (value: any) => any; + export type WriteFn = (o: object) => void; + + export type LevelChangeEventListener = ( + lvl: LevelWithSilentOrString, + val: number, + prevLvl: LevelWithSilentOrString, + prevVal: number, + logger: Logger + ) => void; + + export type LogDescriptor = Record; + + export type Logger = BaseLogger & LoggerExtras & CustomLevelLogger; + + export type SerializedError = pinoStdSerializers.SerializedError; + export type SerializedResponse = pinoStdSerializers.SerializedResponse; + export type SerializedRequest = pinoStdSerializers.SerializedRequest; + + + export interface TransportTargetOptions> { + target: string + options?: TransportOptions + level?: LevelWithSilentOrString + } + + export interface TransportBaseOptions> { + options?: TransportOptions + worker?: WorkerOptions & { autoEnd?: boolean} + } + + export interface TransportSingleOptions> extends TransportBaseOptions{ + target: string + } + + export interface TransportPipelineOptions> extends TransportBaseOptions{ + pipeline: TransportSingleOptions[] + level?: LevelWithSilentOrString + } + + export interface TransportMultiOptions> extends TransportBaseOptions{ + targets: readonly (TransportTargetOptions|TransportPipelineOptions)[], + levels?: Record + dedupe?: boolean + } + + export interface MultiStreamOptions { + levels?: Record + dedupe?: boolean + } + + export interface DestinationStream { + write(msg: string): void; + } + + interface DestinationStreamHasMetadata { + [symbols.needsMetadataGsym]: true; + lastLevel: number; + lastTime: string; + lastMsg: string; + lastObj: object; + lastLogger: Logger; + } + + export type DestinationStreamWithMetadata = DestinationStream & ({ [symbols.needsMetadataGsym]?: false } | DestinationStreamHasMetadata); + + export interface StreamEntry { + stream: DestinationStream + level?: TLevel + } + + export interface MultiStreamRes { + write: (data: any) => void, + add: (dest: StreamEntry | DestinationStream) => MultiStreamRes, + flushSync: () => void, + minLevel: number, + streams: StreamEntry[], + clone(level: TLevel): MultiStreamRes, + } + + export interface LevelMapping { + /** + * Returns the mappings of level names to their respective internal number representation. + */ + values: { [level: string]: number }; + /** + * Returns the mappings of level internal level numbers to their string representations. + */ + labels: { [level: number]: string }; + } + + type PlaceholderSpecifier = 'd' | 's' | 'j' | 'o' | 'O'; + type PlaceholderTypeMapping = T extends 'd' + ? number + : T extends 's' + ? unknown + : T extends 'j' | 'o' | 'O' + ? {} | null + : never; + + type ParseLogFnArgs< + T, + Acc extends unknown[] = [], + > = T extends `${infer _}%${infer Placeholder}${infer Rest}` + ? Placeholder extends PlaceholderSpecifier + ? ParseLogFnArgs]> + : ParseLogFnArgs + : Acc; + + export interface LogFnFields {} + + export interface LogFn { + // Simple case: When first argument is always a string message, use parsed arguments directly + (msg: TMsg, ...args: ParseLogFnArgs): void; + // Complex case: When first argument can be any type - if it's a string, no message needed; otherwise require a message + (obj: [T] extends [object] ? T & LogFnFields : T, msg?: T extends string ? never: TMsg, ...args: ParseLogFnArgs | []): void; + // Complex case with type safety: Same as above but ensures ParseLogFnArgs is a valid tuple before using it + (obj: [T] extends [object] ? T & LogFnFields : T, msg?: T extends string ? never : TMsg, ...args: ParseLogFnArgs extends [unknown, ...unknown[]] ? ParseLogFnArgs : unknown[]): void; + } + + export interface LoggerOptions { + transport?: TransportSingleOptions | TransportMultiOptions | TransportPipelineOptions + /** + * Avoid error causes by circular references in the object tree. Default: `true`. + */ + safe?: boolean; + /** + * The name of the logger. Default: `undefined`. + */ + name?: string; + /** + * an object containing functions for custom serialization of objects. + * These functions should return an JSONifiable object and they should never throw. When logging an object, + * each top-level property matching the exact key of a serializer will be serialized using the defined serializer. + */ + serializers?: { [key: string]: SerializerFn }; + /** + * Enables or disables the inclusion of a timestamp in the log message. If a function is supplied, it must + * synchronously return a JSON string representation of the time. If set to `false`, no timestamp will be included in the output. + * See stdTimeFunctions for a set of available functions for passing in as a value for this option. + * Caution: any sort of formatted time will significantly slow down Pino's performance. + */ + timestamp?: TimeFn | boolean; + /** + * One of the supported levels or `silent` to disable logging. Any other value defines a custom level and + * requires supplying a level value via `levelVal`. Default: 'info'. + */ + level?: LevelWithSilentOrString; + + /** + * Use this option to define additional logging levels. + * The keys of the object correspond the namespace of the log level, and the values should be the numerical value of the level. + */ + customLevels?: { [level in CustomLevels]: number }; + + /** + * Use this option to only use defined `customLevels` and omit Pino's levels. + * Logger's default `level` must be changed to a value in `customLevels` in order to use `useOnlyCustomLevels` + * Warning: this option may not be supported by downstream transports. + */ + useOnlyCustomLevels?: UseOnlyCustomLevels; + + /** + * Use this option to define custom comparison of log levels. + * Useful to compare custom log levels or non-standard level values. + * Default: "ASC" + */ + levelComparison?: "ASC" | "DESC" | ((current: number, expected: number) => boolean); + + /** + * If provided, the `mixin` function is called each time one of the active logging methods + * is called. The function must synchronously return an object. The properties of the + * returned object will be added to the logged JSON. + */ + mixin?: MixinFn; + + /** + * If provided, the `mixinMergeStrategy` function is called each time one of the active + * logging methods is called. The first parameter is the value `mergeObject` or an empty object, + * the second parameter is the value resulting from `mixin()` or an empty object. + * The function must synchronously return an object. + */ + mixinMergeStrategy?: MixinMergeStrategyFn + + /** + * As an array, the redact option specifies paths that should have their values redacted from any log output. + * + * Each path must be a string using a syntax which corresponds to JavaScript dot and bracket notation. + * + * If an object is supplied, three options can be specified: + * + * paths (String[]): Required. An array of paths + * censor (String): Optional. A value to overwrite key which are to be redacted. Default: '[Redacted]' + * remove (Boolean): Optional. Instead of censoring the value, remove both the key and the value. Default: false + */ + redact?: string[] | redactOptions; + + /** + * When defining a custom log level via level, set to an integer value to define the new level. Default: `undefined`. + */ + levelVal?: number; + /** + * The string key for the 'message' in the JSON object. Default: "msg". + */ + messageKey?: string; + /** + * The string key for the 'error' in the JSON object. Default: "err". + */ + errorKey?: string; + /** + * The string key to place any logged object under. + */ + nestedKey?: string; + /** + * Enables logging. Default: `true`. + */ + enabled?: boolean; + /** + * Browser only, see http://getpino.io/#/docs/browser. + */ + browser?: { + /** + * The `asObject` option will create a pino-like log object instead of passing all arguments to a console + * method. When `write` is set, `asObject` will always be true. + * + * @example + * pino.info('hi') // creates and logs {msg: 'hi', level: 30, time: } + */ + asObject?: boolean; + /** + * The `asObjectBindingsOnly` option is similar to `asObject` but will keep the message and arguments + * unformatted. This allows to defer formatting the message to the actual call to `console` methods, + * where browsers then have richer formatting in their devtools than when pino will format the message to + * a string first. + * + * @example + * pino.info('hello %s', 'world') // creates and logs {level: 30, time: }, 'hello %s', 'world' + */ + asObjectBindingsOnly?: boolean; + formatters?: { + /** + * Changes the shape of the log level. + * The default shape is { level: number }. + */ + level?: (label: string, number: number) => object; + /** + * Changes the shape of the log object. + */ + log?: (object: Record) => Record; + } + /** + * When true, attempts to capture and include the caller location (file:line:column). + * In object mode, adds a `caller` string property to the logged object. + * Otherwise, appends the caller string as an extra console argument. + * This is a browser-only, best-effort feature. + */ + reportCaller?: boolean; + /** + * Instead of passing log messages to `console.log` they can be passed to a supplied function. If `write` is + * set to a single function, all logging objects are passed to this function. If `write` is an object, it + * can have methods that correspond to the levels. When a message is logged at a given level, the + * corresponding method is called. If a method isn't present, the logging falls back to using the `console`. + * + * @example + * const pino = require('pino')({ + * browser: { + * write: (o) => { + * // do something with o + * } + * } + * }) + * + * @example + * const pino = require('pino')({ + * browser: { + * write: { + * info: function (o) { + * //process info log object + * }, + * error: function (o) { + * //process error log object + * } + * } + * } + * }) + */ + write?: + | WriteFn + | ({ + fatal?: WriteFn; + error?: WriteFn; + warn?: WriteFn; + info?: WriteFn; + debug?: WriteFn; + trace?: WriteFn; + } & { [logLevel: string]: WriteFn }); + + /** + * The serializers provided to `pino` are ignored by default in the browser, including the standard + * serializers provided with Pino. Since the default destination for log messages is the console, values + * such as `Error` objects are enhanced for inspection, which they otherwise wouldn't be if the Error + * serializer was enabled. We can turn all serializers on or we can selectively enable them via an array. + * + * When `serialize` is `true` the standard error serializer is also enabled (see + * {@link https://github.com/pinojs/pino/blob/master/docs/api.md#pino-stdserializers}). This is a global + * serializer which will apply to any `Error` objects passed to the logger methods. + * + * If `serialize` is an array the standard error serializer is also automatically enabled, it can be + * explicitly disabled by including a string in the serialize array: `!stdSerializers.err` (see example). + * + * The `serialize` array also applies to any child logger serializers (see + * {@link https://github.com/pinojs/pino/blob/master/docs/api.md#bindingsserializers-object} for how to + * set child-bound serializers). + * + * Unlike server pino the serializers apply to every object passed to the logger method, if the `asObject` + * option is `true`, this results in the serializers applying to the first object (as in server pino). + * + * For more info on serializers see + * {@link https://github.com/pinojs/pino/blob/master/docs/api.md#serializers-object}. + * + * @example + * const pino = require('pino')({ + * browser: { + * serialize: true + * } + * }) + * + * @example + * const pino = require('pino')({ + * serializers: { + * custom: myCustomSerializer, + * another: anotherSerializer + * }, + * browser: { + * serialize: ['custom'] + * } + * }) + * // following will apply myCustomSerializer to the custom property, + * // but will not apply anotherSerializer to another key + * pino.info({custom: 'a', another: 'b'}) + * + * @example + * const pino = require('pino')({ + * serializers: { + * custom: myCustomSerializer, + * another: anotherSerializer + * }, + * browser: { + * serialize: ['!stdSerializers.err', 'custom'] //will not serialize Errors, will serialize `custom` keys + * } + * }) + */ + serialize?: boolean | string[]; + + /** + * Options for transmission of logs. + * + * @example + * const pino = require('pino')({ + * browser: { + * transmit: { + * level: 'warn', + * send: function (level, logEvent) { + * if (level === 'warn') { + * // maybe send the logEvent to a separate endpoint + * // or maybe analyse the messages further before sending + * } + * // we could also use the `logEvent.level.value` property to determine + * // numerical value + * if (logEvent.level.value >= 50) { // covers error and fatal + * + * // send the logEvent somewhere + * } + * } + * } + * } + * }) + */ + transmit?: { + /** + * Specifies the minimum level (inclusive) of when the `send` function should be called, if not supplied + * the `send` function will be called based on the main logging `level` (set via `options.level`, + * defaulting to `info`). + */ + level?: LevelOrString; + /** + * Remotely record log messages. + * + * @description Called after writing the log message. + */ + send: (level: Level, logEvent: LogEvent) => void; + }; + /** + * The disabled option will disable logging in browser if set to true, by default it is set to false. + * + * @example + * const pino = require('pino')({browser: {disabled: true}}) + */ + disabled?: boolean; + }; + /** + * key-value object added as child logger to each log line. If set to null the base child logger is not added + */ + base?: { [key: string]: any } | null; + + /** + * An object containing functions for formatting the shape of the log lines. + * These functions should return a JSONifiable object and should never throw. + * These functions allow for full customization of the resulting log lines. + * For example, they can be used to change the level key name or to enrich the default metadata. + */ + formatters?: { + /** + * Changes the shape of the log level. + * The default shape is { level: number }. + * The function takes two arguments, the label of the level (e.g. 'info') and the numeric value (e.g. 30). + */ + level?: (label: string, number: number) => object; + /** + * Changes the shape of the bindings. + * The default shape is { pid, hostname }. + * The function takes a single argument, the bindings object. + * It will be called every time a child logger is created. + */ + bindings?: (bindings: Bindings) => object; + /** + * Changes the shape of the log object. + * This function will be called every time one of the log methods (such as .info) is called. + * All arguments passed to the log method, except the message, will be pass to this function. + * By default it does not change the shape of the log object. + */ + log?: (object: Record) => Record; + }; + + /** + * A string that would be prefixed to every message (and child message) + */ + msgPrefix?: string + + /** + * An object mapping to hook functions. Hook functions allow for customizing internal logger operations. + * Hook functions must be synchronous functions. + */ + hooks?: { + /** + * Allows for manipulating the parameters passed to logger methods. The signature for this hook is + * logMethod (args, method, level) {}, where args is an array of the arguments that were passed to the + * log method and method is the log method itself, and level is the log level. This hook must invoke the method function by + * using apply, like so: method.apply(this, newArgumentsArray). + */ + logMethod?: (this: Logger, args: Parameters, method: LogFn, level: number) => void; + + /** + * Allows for manipulating the stringified JSON log output just before writing to various transports. + * This function must return a string and must be valid JSON. + */ + streamWrite?: (s: string) => string; + }; + + /** + * Stringification limit at a specific nesting depth when logging circular object. Default: `5`. + */ + depthLimit?: number + + /** + * Stringification limit of properties/elements when logging a specific object/array with circular references. Default: `100`. + */ + edgeLimit?: number + + /** + * Optional child creation callback. + */ + onChild?: OnChildCallback; + + /** + * logs newline delimited JSON with `\r\n` instead of `\n`. Default: `false`. + */ + crlf?: boolean; + } + + export interface ChildLoggerOptions { + level?: LevelOrString; + serializers?: { [key: string]: SerializerFn }; + customLevels?: { [level in CustomLevels]: number }; + formatters?: { + level?: (label: string, number: number) => object; + bindings?: (bindings: Bindings) => object; + log?: (object: object) => object; + }; + redact?: string[] | redactOptions; + msgPrefix?: string + } + + /** + * A data structure representing a log message, it represents the arguments passed to a logger statement, the level + * at which they were logged and the hierarchy of child bindings. + * + * @description By default serializers are not applied to log output in the browser, but they will always be applied + * to `messages` and `bindings` in the `logEvent` object. This allows us to ensure a consistent format for all + * values between server and client. + */ + export interface LogEvent { + /** + * Unix epoch timestamp in milliseconds, the time is taken from the moment the logger method is called. + */ + ts: number; + /** + * All arguments passed to logger method, (for instance `logger.info('a', 'b', 'c')` would result in `messages` + * array `['a', 'b', 'c']`). + */ + messages: any[]; + /** + * Represents each child logger (if any), and the relevant bindings. + * + * @description For instance, given `logger.child({a: 1}).child({b: 2}).info({c: 3})`, the bindings array would + * hold `[{a: 1}, {b: 2}]` and the `messages` array would be `[{c: 3}]`. The `bindings` are ordered according to + * their position in the child logger hierarchy, with the lowest index being the top of the hierarchy. + */ + bindings: Bindings[]; + /** + * Holds the `label` (for instance `info`), and the corresponding numerical `value` (for instance `30`). + * This could be important in cases where client side level values and labels differ from server side. + */ + level: { + label: string; + value: number; + }; + } + + + + //// Top level variable (const) exports + + /** + * Provides functions for serializing objects common to many projects. + */ + export const stdSerializers: typeof pinoStdSerializers; + + /** + * Holds the current log format version (as output in the v property of each log record). + */ + export const levels: LevelMapping; + export const symbols: { + readonly setLevelSym: unique symbol; + readonly getLevelSym: unique symbol; + readonly levelValSym: unique symbol; + readonly useLevelLabelsSym: unique symbol; + readonly mixinSym: unique symbol; + readonly lsCacheSym: unique symbol; + readonly chindingsSym: unique symbol; + readonly asJsonSym: unique symbol; + readonly writeSym: unique symbol; + readonly serializersSym: unique symbol; + readonly redactFmtSym: unique symbol; + readonly timeSym: unique symbol; + readonly timeSliceIndexSym: unique symbol; + readonly streamSym: unique symbol; + readonly stringifySym: unique symbol; + readonly stringifySafeSym: unique symbol; + readonly stringifiersSym: unique symbol; + readonly endSym: unique symbol; + readonly formatOptsSym: unique symbol; + readonly messageKeySym: unique symbol; + readonly errorKeySym: unique symbol; + readonly nestedKeySym: unique symbol; + readonly wildcardFirstSym: unique symbol; + readonly needsMetadataGsym: unique symbol; + readonly useOnlyCustomLevelsSym: unique symbol; + readonly formattersSym: unique symbol; + readonly hooksSym: unique symbol; + }; + + /** + * Exposes the Pino package version. Also available on the logger instance. + */ + export const version: string; + + /** + * Provides functions for generating the timestamp property in the log output. You can set the `timestamp` option during + * initialization to one of these functions to adjust the output format. Alternatively, you can specify your own time function. + * A time function must synchronously return a string that would be a valid component of a JSON string. For example, + * the default function returns a string like `,"time":1493426328206`. + */ + export const stdTimeFunctions: { + /** + * The default time function for Pino. Returns a string like `,"time":1493426328206`. + */ + epochTime: TimeFn; + /* + * Returns the seconds since Unix epoch + */ + unixTime: TimeFn; + /** + * Returns an empty string. This function is used when the `timestamp` option is set to `false`. + */ + nullTime: TimeFn; + /* + * Returns ISO 8601-formatted time in UTC + */ + isoTime: TimeFn; + /* + * Returns RFC 3339-formatted time in UTC + */ + isoTimeNano: TimeFn; + }; + + //// Exported functions + + /** + * Create a Pino Destination instance: a stream-like object with significantly more throughput (over 30%) than a standard Node.js stream. + * @param [dest]: The `destination` parameter, can be a file descriptor, a file path, or an object with `dest` property pointing to a fd or path. + * An ordinary Node.js `stream` file descriptor can be passed as the destination (such as the result of `fs.createWriteStream`) + * but for peak log writing performance, it is strongly recommended to use `pino.destination` to create the destination stream. + * @returns A Sonic-Boom stream to be used as destination for the pino function + */ + export function destination( + dest?: number | object | string | DestinationStream | NodeJS.WritableStream | SonicBoomOpts, + ): SonicBoom; + + export function transport>( + options: TransportSingleOptions | TransportMultiOptions | TransportPipelineOptions + ): ThreadStream + + export function multistream( + streamsArray: (DestinationStream | StreamEntry)[] | DestinationStream | StreamEntry, + opts?: MultiStreamOptions + ): MultiStreamRes + + //// Nested version of default export for TypeScript/Babel compatibility + + /** + * @param [optionsOrStream]: an options object or a writable stream where the logs will be written. It can also receive some log-line metadata, if the + * relative protocol is enabled. Default: process.stdout + * @returns a new logger instance. + */ + function pino(optionsOrStream?: LoggerOptions | DestinationStream): Logger; + + /** + * @param [options]: an options object + * @param [stream]: a writable stream where the logs will be written. It can also receive some log-line metadata, if the + * relative protocol is enabled. Default: process.stdout + * @returns a new logger instance. + */ + function pino(options: LoggerOptions, stream?: DestinationStream | undefined): Logger; + + /** + * Attach selected static members to the nested callable export, so that + * `const { pino } = require('pino')` exposes them (e.g. `pino.stdTimeFunctions`). + */ + namespace pino { + const stdTimeFunctions: { + epochTime: TimeFn; + unixTime: TimeFn; + nullTime: TimeFn; + isoTime: TimeFn; + isoTimeNano: TimeFn; + }; + } +} + +//// Callable default export + +/** + * @param [optionsOrStream]: an options object or a writable stream where the logs will be written. It can also receive some log-line metadata, if the + * relative protocol is enabled. Default: process.stdout + * @returns a new logger instance. + */ +declare function pino(optionsOrStream?: pino.LoggerOptions | pino.DestinationStream): pino.Logger; + +/** + * @param [options]: an options object + * @param [stream]: a writable stream where the logs will be written. It can also receive some log-line metadata, if the + * relative protocol is enabled. Default: process.stdout + * @returns a new logger instance. + */ +declare function pino(options: pino.LoggerOptions, stream?: pino.DestinationStream | undefined): pino.Logger; + +export = pino; diff --git a/services/slides/node_modules/pino/pino.js b/services/slides/node_modules/pino/pino.js new file mode 100644 index 0000000000000000000000000000000000000000..cabeaf6d6eb4aa833e27d24c7c5179f7ed7b7e7d --- /dev/null +++ b/services/slides/node_modules/pino/pino.js @@ -0,0 +1,234 @@ +'use strict' + +const os = require('node:os') +const stdSerializers = require('pino-std-serializers') +const caller = require('./lib/caller') +const redaction = require('./lib/redaction') +const time = require('./lib/time') +const proto = require('./lib/proto') +const symbols = require('./lib/symbols') +const { configure } = require('safe-stable-stringify') +const { assertDefaultLevelFound, mappings, genLsCache, genLevelComparison, assertLevelComparison } = require('./lib/levels') +const { DEFAULT_LEVELS, SORTING_ORDER } = require('./lib/constants') +const { + createArgsNormalizer, + asChindings, + buildSafeSonicBoom, + buildFormatters, + stringify, + normalizeDestFileDescriptor, + noop +} = require('./lib/tools') +const { version } = require('./lib/meta') +const { + chindingsSym, + redactFmtSym, + serializersSym, + timeSym, + timeSliceIndexSym, + streamSym, + stringifySym, + stringifySafeSym, + stringifiersSym, + setLevelSym, + endSym, + formatOptsSym, + messageKeySym, + errorKeySym, + nestedKeySym, + mixinSym, + levelCompSym, + useOnlyCustomLevelsSym, + formattersSym, + hooksSym, + nestedKeyStrSym, + mixinMergeStrategySym, + msgPrefixSym +} = symbols +const { epochTime, nullTime } = time +const { pid } = process +const hostname = os.hostname() +const defaultErrorSerializer = stdSerializers.err +const defaultOptions = { + level: 'info', + levelComparison: SORTING_ORDER.ASC, + levels: DEFAULT_LEVELS, + messageKey: 'msg', + errorKey: 'err', + nestedKey: null, + enabled: true, + base: { pid, hostname }, + serializers: Object.assign(Object.create(null), { + err: defaultErrorSerializer + }), + formatters: Object.assign(Object.create(null), { + bindings (bindings) { + return bindings + }, + level (label, number) { + return { level: number } + } + }), + hooks: { + logMethod: undefined, + streamWrite: undefined + }, + timestamp: epochTime, + name: undefined, + redact: null, + customLevels: null, + useOnlyCustomLevels: false, + depthLimit: 5, + edgeLimit: 100 +} + +const normalize = createArgsNormalizer(defaultOptions) + +const serializers = Object.assign(Object.create(null), stdSerializers) + +function pino (...args) { + const instance = {} + const { opts, stream } = normalize(instance, caller(), ...args) + + if (opts.level && typeof opts.level === 'string' && DEFAULT_LEVELS[opts.level.toLowerCase()] !== undefined) opts.level = opts.level.toLowerCase() + + const { + redact, + crlf, + serializers, + timestamp, + messageKey, + errorKey, + nestedKey, + base, + name, + level, + customLevels, + levelComparison, + mixin, + mixinMergeStrategy, + useOnlyCustomLevels, + formatters, + hooks, + depthLimit, + edgeLimit, + onChild, + msgPrefix + } = opts + + const stringifySafe = configure({ + maximumDepth: depthLimit, + maximumBreadth: edgeLimit + }) + + const allFormatters = buildFormatters( + formatters.level, + formatters.bindings, + formatters.log + ) + + const stringifyFn = stringify.bind({ + [stringifySafeSym]: stringifySafe + }) + const stringifiers = redact ? redaction(redact, stringifyFn) : {} + const formatOpts = redact + ? { stringify: stringifiers[redactFmtSym] } + : { stringify: stringifyFn } + const end = '}' + (crlf ? '\r\n' : '\n') + const coreChindings = asChindings.bind(null, { + [chindingsSym]: '', + [serializersSym]: serializers, + [stringifiersSym]: stringifiers, + [stringifySym]: stringify, + [stringifySafeSym]: stringifySafe, + [formattersSym]: allFormatters + }) + + let chindings = '' + if (base !== null) { + if (name === undefined) { + chindings = coreChindings(base) + } else { + chindings = coreChindings(Object.assign({}, base, { name })) + } + } + + const time = (timestamp instanceof Function) + ? timestamp + : (timestamp ? epochTime : nullTime) + const timeSliceIndex = time().indexOf(':') + 1 + + if (useOnlyCustomLevels && !customLevels) throw Error('customLevels is required if useOnlyCustomLevels is set true') + if (mixin && typeof mixin !== 'function') throw Error(`Unknown mixin type "${typeof mixin}" - expected "function"`) + if (msgPrefix && typeof msgPrefix !== 'string') throw Error(`Unknown msgPrefix type "${typeof msgPrefix}" - expected "string"`) + + assertDefaultLevelFound(level, customLevels, useOnlyCustomLevels) + const levels = mappings(customLevels, useOnlyCustomLevels) + + if (typeof stream.emit === 'function') { + stream.emit('message', { code: 'PINO_CONFIG', config: { levels, messageKey, errorKey } }) + } + + assertLevelComparison(levelComparison) + const levelCompFunc = genLevelComparison(levelComparison) + + Object.assign(instance, { + levels, + [levelCompSym]: levelCompFunc, + [useOnlyCustomLevelsSym]: useOnlyCustomLevels, + [streamSym]: stream, + [timeSym]: time, + [timeSliceIndexSym]: timeSliceIndex, + [stringifySym]: stringify, + [stringifySafeSym]: stringifySafe, + [stringifiersSym]: stringifiers, + [endSym]: end, + [formatOptsSym]: formatOpts, + [messageKeySym]: messageKey, + [errorKeySym]: errorKey, + [nestedKeySym]: nestedKey, + // protect against injection + [nestedKeyStrSym]: nestedKey ? `,${JSON.stringify(nestedKey)}:{` : '', + [serializersSym]: serializers, + [mixinSym]: mixin, + [mixinMergeStrategySym]: mixinMergeStrategy, + [chindingsSym]: chindings, + [formattersSym]: allFormatters, + [hooksSym]: hooks, + silent: noop, + onChild, + [msgPrefixSym]: msgPrefix + }) + + Object.setPrototypeOf(instance, proto()) + + genLsCache(instance) + + instance[setLevelSym](level) + + return instance +} + +module.exports = pino + +module.exports.destination = (dest = process.stdout.fd) => { + if (typeof dest === 'object') { + dest.dest = normalizeDestFileDescriptor(dest.dest || process.stdout.fd) + return buildSafeSonicBoom(dest) + } else { + return buildSafeSonicBoom({ dest: normalizeDestFileDescriptor(dest), minLength: 0 }) + } +} + +module.exports.transport = require('./lib/transport') +module.exports.multistream = require('./lib/multistream') + +module.exports.levels = mappings() +module.exports.stdSerializers = serializers +module.exports.stdTimeFunctions = Object.assign({}, time) +module.exports.symbols = symbols +module.exports.version = version + +// Enables default and name export with TypeScript and Babel +module.exports.default = pino +module.exports.pino = pino diff --git a/services/slides/node_modules/pino/test/basic.test.js b/services/slides/node_modules/pino/test/basic.test.js new file mode 100644 index 0000000000000000000000000000000000000000..411598f5cd08712d42a493bd94f84efa8790c620 --- /dev/null +++ b/services/slides/node_modules/pino/test/basic.test.js @@ -0,0 +1,886 @@ +'use strict' + +const os = require('node:os') +const { readFileSync } = require('node:fs') +const test = require('node:test') +const assert = require('node:assert') + +const { sink, check, match, once, watchFileCreated, file } = require('./helper') +const pino = require('../') +const { version } = require('../package.json') +const { pid } = process +const hostname = os.hostname() + +test('pino version is exposed on export', () => { + assert.equal(pino.version, version) +}) + +test('pino version is exposed on instance', () => { + const instance = pino() + assert.equal(instance.version, version) +}) + +test('child instance exposes pino version', () => { + const child = pino().child({ foo: 'bar' }) + assert.equal(child.version, version) +}) + +test('bindings are exposed on every instance', () => { + const instance = pino() + assert.deepEqual(instance.bindings(), {}) +}) + +test('bindings contain the name and the child bindings', () => { + const instance = pino({ name: 'basicTest', level: 'info' }).child({ foo: 'bar' }).child({ a: 2 }) + assert.deepEqual(instance.bindings(), { name: 'basicTest', foo: 'bar', a: 2 }) +}) + +test('set bindings on instance', () => { + const instance = pino({ name: 'basicTest', level: 'info' }) + instance.setBindings({ foo: 'bar' }) + assert.deepEqual(instance.bindings(), { name: 'basicTest', foo: 'bar' }) +}) + +test('newly set bindings overwrite old bindings', () => { + const instance = pino({ name: 'basicTest', level: 'info', base: { foo: 'bar' } }) + instance.setBindings({ foo: 'baz' }) + assert.deepEqual(instance.bindings(), { name: 'basicTest', foo: 'baz' }) +}) + +test('set bindings on child instance', () => { + const child = pino({ name: 'basicTest', level: 'info' }).child({}) + child.setBindings({ foo: 'bar' }) + assert.deepEqual(child.bindings(), { name: 'basicTest', foo: 'bar' }) +}) + +test('child should have bindings set by parent', () => { + const instance = pino({ name: 'basicTest', level: 'info' }) + instance.setBindings({ foo: 'bar' }) + const child = instance.child({}) + assert.deepEqual(child.bindings(), { name: 'basicTest', foo: 'bar' }) +}) + +test('child should not share bindings of parent set after child creation', () => { + const instance = pino({ name: 'basicTest', level: 'info' }) + const child = instance.child({}) + instance.setBindings({ foo: 'bar' }) + assert.deepEqual(instance.bindings(), { name: 'basicTest', foo: 'bar' }) + assert.deepEqual(child.bindings(), { name: 'basicTest' }) +}) + +function levelTest (name, level) { + test(`${name} logs as ${level}`, async () => { + const stream = sink() + const instance = pino(stream) + instance.level = name + instance[name]('hello world') + check(assert.equal, await once(stream, 'data'), level, 'hello world') + }) + + test(`passing objects at level ${name}`, async () => { + const stream = sink() + const instance = pino(stream) + instance.level = name + const obj = { hello: 'world' } + instance[name](obj) + + const result = await once(stream, 'data') + assert.equal(new Date(result.time) <= new Date(), true, 'time is greater than Date.now()') + assert.equal(result.pid, pid) + assert.equal(result.hostname, hostname) + assert.equal(result.level, level) + assert.equal(result.hello, 'world') + assert.deepEqual(Object.keys(obj), ['hello']) + }) + + test(`passing an object and a string at level ${name}`, async () => { + const stream = sink() + const instance = pino(stream) + instance.level = name + const obj = { hello: 'world' } + instance[name](obj, 'a string') + const result = await once(stream, 'data') + assert.equal(new Date(result.time) <= new Date(), true, 'time is greater than Date.now()') + delete result.time + assert.deepEqual(result, { + pid, + hostname, + level, + msg: 'a string', + hello: 'world' + }) + assert.deepEqual(Object.keys(obj), ['hello']) + }) + + test(`passing a undefined and a string at level ${name}`, async () => { + const stream = sink() + const instance = pino(stream) + instance.level = name + instance[name](undefined, 'a string') + const result = await once(stream, 'data') + assert.equal(new Date(result.time) <= new Date(), true, 'time is greater than Date.now()') + delete result.time + assert.deepEqual(result, { + pid, + hostname, + level, + msg: 'a string' + }) + }) + + test(`overriding object key by string at level ${name}`, async () => { + const stream = sink() + const instance = pino(stream) + instance.level = name + instance[name]({ hello: 'world', msg: 'object' }, 'string') + const result = await once(stream, 'data') + assert.equal(new Date(result.time) <= new Date(), true, 'time is greater than Date.now()') + delete result.time + assert.deepEqual(result, { + pid, + hostname, + level, + msg: 'string', + hello: 'world' + }) + }) + + test(`formatting logs as ${name}`, async () => { + const stream = sink() + const instance = pino(stream) + instance.level = name + instance[name]('hello %d', 42) + const result = await once(stream, 'data') + check(assert.equal, result, level, 'hello 42') + }) + + test(`formatting a symbol at level ${name}`, async () => { + const stream = sink() + const instance = pino(stream) + instance.level = name + + const sym = Symbol('foo') + instance[name]('hello %s', sym) + + const result = await once(stream, 'data') + + check(assert.equal, result, level, 'hello Symbol(foo)') + }) + + test(`passing error with a serializer at level ${name}`, async () => { + const stream = sink() + const err = new Error('myerror') + const instance = pino({ + serializers: { + err: pino.stdSerializers.err + } + }, stream) + instance.level = name + instance[name]({ err }) + const result = await once(stream, 'data') + assert.equal(new Date(result.time) <= new Date(), true, 'time is greater than Date.now()') + delete result.time + assert.deepEqual(result, { + pid, + hostname, + level, + err: { + type: 'Error', + message: err.message, + stack: err.stack + }, + msg: err.message + }) + }) + + test(`child logger for level ${name}`, async () => { + const stream = sink() + const instance = pino(stream) + instance.level = name + const child = instance.child({ hello: 'world' }) + child[name]('hello world') + const result = await once(stream, 'data') + assert.equal(new Date(result.time) <= new Date(), true, 'time is greater than Date.now()') + delete result.time + assert.deepEqual(result, { + pid, + hostname, + level, + msg: 'hello world', + hello: 'world' + }) + }) +} + +levelTest('fatal', 60) +levelTest('error', 50) +levelTest('warn', 40) +levelTest('info', 30) +levelTest('debug', 20) +levelTest('trace', 10) + +test('serializers can return undefined to strip field', async () => { + const stream = sink() + const instance = pino({ + serializers: { + test () { return undefined } + } + }, stream) + + instance.info({ test: 'sensitive info' }) + const result = await once(stream, 'data') + assert.equal('test' in result, false) +}) + +test('streams receive a message event with PINO_CONFIG', (t, end) => { + const stream = sink() + stream.once('message', (message) => { + match(message, { + code: 'PINO_CONFIG', + config: { + errorKey: 'err', + levels: { + labels: { + 10: 'trace', + 20: 'debug', + 30: 'info', + 40: 'warn', + 50: 'error', + 60: 'fatal' + }, + values: { + debug: 20, + error: 50, + fatal: 60, + info: 30, + trace: 10, + warn: 40 + } + }, + messageKey: 'msg' + } + }) + end() + }) + pino(stream) +}) + +test('does not explode with a circular ref', () => { + const stream = sink() + const instance = pino(stream) + const b = {} + const a = { + hello: b + } + b.a = a // circular ref + assert.doesNotThrow(() => instance.info(a)) +}) + +test('set the name', async () => { + const stream = sink() + const instance = pino({ + name: 'hello' + }, stream) + instance.fatal('this is fatal') + const result = await once(stream, 'data') + assert.equal(new Date(result.time) <= new Date(), true, 'time is greater than Date.now()') + delete result.time + assert.deepEqual(result, { + pid, + hostname, + level: 60, + name: 'hello', + msg: 'this is fatal' + }) +}) + +test('set the messageKey', async () => { + const stream = sink() + const message = 'hello world' + const messageKey = 'fooMessage' + const instance = pino({ + messageKey + }, stream) + instance.info(message) + const result = await once(stream, 'data') + assert.equal(new Date(result.time) <= new Date(), true, 'time is greater than Date.now()') + delete result.time + assert.deepEqual(result, { + pid, + hostname, + level: 30, + fooMessage: message + }) +}) + +test('set the nestedKey', async () => { + const stream = sink() + const object = { hello: 'world' } + const nestedKey = 'stuff' + const instance = pino({ + nestedKey + }, stream) + instance.info(object) + const result = await once(stream, 'data') + assert.equal(new Date(result.time) <= new Date(), true, 'time is greater than Date.now()') + delete result.time + assert.deepEqual(result, { + pid, + hostname, + level: 30, + stuff: object + }) +}) + +test('set undefined properties', async () => { + const stream = sink() + const instance = pino(stream) + instance.info({ hello: 'world', property: undefined }) + const result = await once(stream, 'data') + assert.equal(new Date(result.time) <= new Date(), true, 'time is greater than Date.now()') + delete result.time + assert.deepEqual(result, { + pid, + hostname, + level: 30, + hello: 'world' + }) +}) + +test('prototype properties are not logged', async () => { + const stream = sink() + const instance = pino(stream) + instance.info(Object.create({ hello: 'world' })) + const { hello } = await once(stream, 'data') + assert.equal(hello, undefined) +}) + +test('set the base', async () => { + const stream = sink() + const instance = pino({ + base: { + a: 'b' + } + }, stream) + + instance.fatal('this is fatal') + const result = await once(stream, 'data') + assert.equal(new Date(result.time) <= new Date(), true, 'time is greater than Date.now()') + delete result.time + assert.deepEqual(result, { + a: 'b', + level: 60, + msg: 'this is fatal' + }) +}) + +test('set the base to null', async () => { + const stream = sink() + const instance = pino({ + base: null + }, stream) + instance.fatal('this is fatal') + const result = await once(stream, 'data') + assert.equal(new Date(result.time) <= new Date(), true, 'time is greater than Date.now()') + delete result.time + assert.deepEqual(result, { + level: 60, + msg: 'this is fatal' + }) +}) + +test('set the base to null and use a formatter', async () => { + const stream = sink() + const instance = pino({ + base: null, + formatters: { + log (input) { + return Object.assign({}, input, { additionalMessage: 'using pino' }) + } + } + }, stream) + instance.fatal('this is fatal too') + const result = await once(stream, 'data') + assert.equal(new Date(result.time) <= new Date(), true, 'time is greater than Date.now()') + delete result.time + assert.deepEqual(result, { + level: 60, + msg: 'this is fatal too', + additionalMessage: 'using pino' + }) +}) + +test('throw if creating child without bindings', () => { + const stream = sink() + const instance = pino(stream) + try { + instance.child() + assert.fail('it should throw') + } catch (err) { + assert.equal(err.message, 'missing bindings for child Pino') + } +}) + +test('correctly escapes msg strings with stray double quote at end', async () => { + const stream = sink() + const instance = pino({ + name: 'hello' + }, stream) + + instance.fatal('this contains "') + const result = await once(stream, 'data') + delete result.time + assert.deepEqual(result, { + pid, + hostname, + level: 60, + name: 'hello', + msg: 'this contains "' + }) +}) + +test('correctly escape msg strings with unclosed double quote', async () => { + const stream = sink() + const instance = pino({ + name: 'hello' + }, stream) + instance.fatal('" this contains') + const result = await once(stream, 'data') + delete result.time + assert.deepEqual(result, { + pid, + hostname, + level: 60, + name: 'hello', + msg: '" this contains' + }) +}) + +test('correctly escape quote in a key', async () => { + const stream = sink() + const instance = pino(stream) + const obj = { 'some"obj': 'world' } + instance.info(obj, 'a string') + const result = await once(stream, 'data') + delete result.time + assert.deepEqual(result, { + level: 30, + pid, + hostname, + msg: 'a string', + 'some"obj': 'world' + }) + assert.deepEqual(Object.keys(obj), ['some"obj']) +}) + +// https://github.com/pinojs/pino/issues/139 +test('object and format string', async () => { + const stream = sink() + const instance = pino(stream) + instance.info({}, 'foo %s', 'bar') + + const result = await once(stream, 'data') + delete result.time + assert.deepEqual(result, { + pid, + hostname, + level: 30, + msg: 'foo bar' + }) +}) + +test('object and format string property', async () => { + const stream = sink() + const instance = pino(stream) + instance.info({ answer: 42 }, 'foo %s', 'bar') + const result = await once(stream, 'data') + delete result.time + assert.deepEqual(result, { + pid, + hostname, + level: 30, + msg: 'foo bar', + answer: 42 + }) +}) + +test('correctly strip undefined when returned from toJSON', async () => { + const stream = sink() + const instance = pino({ + test: 'this' + }, stream) + instance.fatal({ test: { toJSON () { return undefined } } }) + const result = await once(stream, 'data') + assert.equal('test' in result, false) +}) + +test('correctly supports stderr', (t, end) => { + // stderr inherits from Stream, rather than Writable + const dest = { + writable: true, + write (result) { + result = JSON.parse(result) + delete result.time + assert.deepEqual(result, { + pid, + hostname, + level: 60, + msg: 'a message' + }) + end() + } + } + const instance = pino(dest) + instance.fatal('a message') +}) + +test('normalize number to string', async () => { + const stream = sink() + const instance = pino(stream) + instance.info(1) + const result = await once(stream, 'data') + delete result.time + assert.deepEqual(result, { + pid, + hostname, + level: 30, + msg: '1' + }) +}) + +test('normalize number to string with an object', async () => { + const stream = sink() + const instance = pino(stream) + instance.info({ answer: 42 }, 1) + const result = await once(stream, 'data') + delete result.time + assert.deepEqual(result, { + pid, + hostname, + level: 30, + msg: '1', + answer: 42 + }) +}) + +test('handles objects with null prototype', async () => { + const stream = sink() + const instance = pino(stream) + const o = Object.create(null) + o.test = 'test' + instance.info(o) + const result = await once(stream, 'data') + delete result.time + assert.deepEqual(result, { + pid, + hostname, + level: 30, + test: 'test' + }) +}) + +test('pino.destination', async () => { + const tmp = file() + const instance = pino(pino.destination(tmp)) + instance.info('hello') + await watchFileCreated(tmp) + const result = JSON.parse(readFileSync(tmp).toString()) + delete result.time + assert.deepEqual(result, { + pid, + hostname, + level: 30, + msg: 'hello' + }) +}) + +test('auto pino.destination with a string', async () => { + const tmp = file() + const instance = pino(tmp) + instance.info('hello') + await watchFileCreated(tmp) + const result = JSON.parse(readFileSync(tmp).toString()) + delete result.time + assert.deepEqual(result, { + pid, + hostname, + level: 30, + msg: 'hello' + }) +}) + +test('auto pino.destination with a string as second argument', async () => { + const tmp = file() + const instance = pino(null, tmp) + instance.info('hello') + await watchFileCreated(tmp) + const result = JSON.parse(readFileSync(tmp).toString()) + delete result.time + assert.deepEqual(result, { + pid, + hostname, + level: 30, + msg: 'hello' + }) +}) + +test('does not override opts with a string as second argument', async () => { + const tmp = file() + const instance = pino({ + timestamp: () => ',"time":"none"' + }, tmp) + instance.info('hello') + await watchFileCreated(tmp) + const result = JSON.parse(readFileSync(tmp).toString()) + assert.deepEqual(result, { + pid, + hostname, + level: 30, + time: 'none', + msg: 'hello' + }) +}) + +// https://github.com/pinojs/pino/issues/222 +test('children with same names render in correct order', async () => { + const stream = sink() + const root = pino(stream) + root.child({ a: 1 }).child({ a: 2 }).info({ a: 3 }) + const { a } = await once(stream, 'data') + assert.equal(a, 3, 'last logged object takes precedence') +}) + +test('use `safe-stable-stringify` to avoid circular dependencies', async () => { + const stream = sink() + const root = pino(stream) + // circular depth + const obj = {} + obj.a = obj + root.info(obj) + const { a } = await once(stream, 'data') + assert.deepEqual(a, { a: '[Circular]' }) +}) + +test('correctly log non circular objects', async () => { + const stream = sink() + const root = pino(stream) + const obj = {} + let parent = obj + for (let i = 0; i < 10; i++) { + parent.node = {} + parent = parent.node + } + root.info(obj) + const { node } = await once(stream, 'data') + assert.deepEqual(node, { node: { node: { node: { node: { node: { node: { node: { node: { node: {} } } } } } } } } }) +}) + +test('safe-stable-stringify must be used when interpolating', async () => { + const stream = sink() + const instance = pino(stream) + + const o = { a: { b: {} } } + o.a.b.c = o.a.b + instance.info('test %j', o) + + const { msg } = await once(stream, 'data') + assert.equal(msg, 'test {"a":{"b":{"c":"[Circular]"}}}') +}) + +test('throws when setting useOnlyCustomLevels without customLevels', () => { + assert.throws( + () => { + pino({ + useOnlyCustomLevels: true + }) + }, + /customLevels is required if useOnlyCustomLevels is set true/ + ) +}) + +test('correctly log Infinity', async () => { + const stream = sink() + const instance = pino(stream) + + const o = { num: Infinity } + instance.info(o) + + const { num } = await once(stream, 'data') + assert.equal(num, null) +}) + +test('correctly log -Infinity', async () => { + const stream = sink() + const instance = pino(stream) + + const o = { num: -Infinity } + instance.info(o) + + const { num } = await once(stream, 'data') + assert.equal(num, null) +}) + +test('correctly log NaN', async () => { + const stream = sink() + const instance = pino(stream) + + const o = { num: NaN } + instance.info(o) + + const { num } = await once(stream, 'data') + assert.equal(num, null) +}) + +test('offers a .default() method to please typescript', async () => { + assert.equal(pino.default, pino) + + const stream = sink() + const instance = pino.default(stream) + instance.info('hello world') + check(assert.equal, await once(stream, 'data'), 30, 'hello world') +}) + +test('correctly skip function', async () => { + const stream = sink() + const instance = pino(stream) + + const o = { num: NaN } + instance.info(o, () => {}) + + const { msg } = await once(stream, 'data') + assert.equal(msg, undefined) +}) + +test('correctly skip Infinity', async () => { + const stream = sink() + const instance = pino(stream) + + const o = { num: NaN } + instance.info(o, Infinity) + + const { msg } = await once(stream, 'data') + assert.equal(msg, null) +}) + +test('correctly log number', async () => { + const stream = sink() + const instance = pino(stream) + + const o = { num: NaN } + instance.info(o, 42) + + const { msg } = await once(stream, 'data') + assert.equal(msg, 42) +}) + +test('nestedKey should not be used for non-objects', async () => { + const stream = sink() + const message = 'hello' + const nestedKey = 'stuff' + const instance = pino({ + nestedKey + }, stream) + instance.info(message) + const result = await once(stream, 'data') + delete result.time + assert.deepStrictEqual(result, { + pid, + hostname, + level: 30, + msg: message + }) +}) + +test('throws if prettyPrint is passed in as an option', async () => { + assert.throws( + () => { + pino({ + prettyPrint: true + }) + }, + Error('prettyPrint option is no longer supported, see the pino-pretty package (https://github.com/pinojs/pino-pretty)') + ) +}) + +test('Should invoke `onChild` with the newly created child', () => { + let innerChild + const child = pino({ + onChild: (instance) => { + innerChild = instance + } + }).child({ foo: 'bar' }) + assert.equal(child, innerChild) +}) + +test('logger message should have the prefix message that defined in the logger creation', async () => { + const stream = sink() + const logger = pino({ + msgPrefix: 'My name is Bond ' + }, stream) + assert.equal(logger.msgPrefix, 'My name is Bond ') + logger.info('James Bond') + const { msg } = await once(stream, 'data') + assert.equal(msg, 'My name is Bond James Bond') +}) + +test('child message should have the prefix message that defined in the child creation', async () => { + const stream = sink() + const instance = pino(stream) + const child = instance.child({}, { msgPrefix: 'My name is Bond ' }) + child.info('James Bond') + const { msg } = await once(stream, 'data') + assert.equal(msg, 'My name is Bond James Bond') +}) + +test('child message should have the prefix message that defined in the child creation when logging with log meta', async () => { + const stream = sink() + const instance = pino(stream) + const child = instance.child({}, { msgPrefix: 'My name is Bond ' }) + child.info({ hello: 'world' }, 'James Bond') + const { msg, hello } = await once(stream, 'data') + assert.equal(hello, 'world') + assert.equal(msg, 'My name is Bond James Bond') +}) + +test('logged message should not have the prefix when not providing any message', async () => { + const stream = sink() + const instance = pino(stream) + const child = instance.child({}, { msgPrefix: 'This should not be shown ' }) + child.info({ hello: 'world' }) + const { msg, hello } = await once(stream, 'data') + assert.equal(hello, 'world') + assert.equal(msg, undefined) +}) + +test('child message should append parent prefix to current prefix that defined in the child creation', async () => { + const stream = sink() + const instance = pino({ + msgPrefix: 'My name is Bond ' + }, stream) + const child = instance.child({}, { msgPrefix: 'James ' }) + child.info('Bond') + assert.equal(child.msgPrefix, 'My name is Bond James ') + const { msg } = await once(stream, 'data') + assert.equal(msg, 'My name is Bond James Bond') +}) + +test('child message should inherent parent prefix', async () => { + const stream = sink() + const instance = pino({ + msgPrefix: 'My name is Bond ' + }, stream) + const child = instance.child({}) + child.info('James Bond') + const { msg } = await once(stream, 'data') + assert.equal(msg, 'My name is Bond James Bond') +}) + +test('grandchild message should inherent parent prefix', async () => { + const stream = sink() + const instance = pino(stream) + const child = instance.child({}, { msgPrefix: 'My name is Bond ' }) + const grandchild = child.child({}) + grandchild.info('James Bond') + const { msg } = await once(stream, 'data') + assert.equal(msg, 'My name is Bond James Bond') +}) diff --git a/services/slides/node_modules/pino/test/broken-pipe.test.js b/services/slides/node_modules/pino/test/broken-pipe.test.js new file mode 100644 index 0000000000000000000000000000000000000000..de20b3f46b428e1dad07ee3bf59a1c1d38f3e03b --- /dev/null +++ b/services/slides/node_modules/pino/test/broken-pipe.test.js @@ -0,0 +1,59 @@ +'use strict' + +const test = require('node:test') +const assert = require('node:assert') +const { join } = require('node:path') +const { fork } = require('node:child_process') +const tspl = require('@matteo.collina/tspl') +const { once } = require('./helper') +const pino = require('..') + +if (process.platform === 'win32') { + console.log('skipping on windows') + process.exit(0) +} + +if (process.env.CITGM) { + // This looks like a some form of limitations of the CITGM test runner + // or the HW/SW we run it on. This file can hang on Node.js v18.x. + // The failure does not reproduce locally or on our CI. + // Skipping it is the only way to keep pino in CITGM. + // https://github.com/nodejs/citgm/pull/1002#issuecomment-1751942988 + console.log('Skipping on Node.js core CITGM because it hangs on v18.x') + process.exit(0) +} + +function testFile (file) { + file = join('fixtures', 'broken-pipe', file) + test(file, async () => { + const child = fork(join(__dirname, file), { silent: true }) + child.stdout.destroy() + + child.stderr.pipe(process.stdout) + + const res = await once(child, 'close') + assert.equal(res, 0) // process exits successfully + }) +} + +testFile('basic.js') +testFile('destination.js') +testFile('syncfalse.js') + +test('let error pass through', async (t) => { + const plan = tspl(t, { plan: 3 }) + const stream = pino.destination({ sync: true }) + + // side effect of the pino constructor is that it will set an + // event handler for error + pino(stream) + + process.nextTick(() => stream.emit('error', new Error('kaboom'))) + process.nextTick(() => stream.emit('error', new Error('kaboom'))) + + stream.on('error', (err) => { + plan.equal(err.message, 'kaboom') + }) + + await plan +}) diff --git a/services/slides/node_modules/pino/test/browser-child.test.js b/services/slides/node_modules/pino/test/browser-child.test.js new file mode 100644 index 0000000000000000000000000000000000000000..679261a780eb017723b146154a176dae2a826f74 --- /dev/null +++ b/services/slides/node_modules/pino/test/browser-child.test.js @@ -0,0 +1,132 @@ +'use strict' +const test = require('tape') +const pino = require('../browser') + +test('child has parent level', ({ end, same, is }) => { + const instance = pino({ + level: 'error', + browser: {} + }) + + const child = instance.child({}) + + same(child.level, instance.level) + end() +}) + +test('child can set level at creation time', ({ end, same, is }) => { + const instance = pino({ + level: 'error', + browser: {} + }) + + const child = instance.child({}, { level: 'info' }) // first bindings, then options + + same(child.level, 'info') + end() +}) + +test('changing child level does not affect parent', ({ end, same, is }) => { + const instance = pino({ + level: 'error', + browser: {} + }) + + const child = instance.child({}) + child.level = 'info' + + same(instance.level, 'error') + end() +}) + +test('child should log, if its own level allows it', ({ end, same, is }) => { + const expected = [ + { + level: 30, + msg: 'this is info' + }, + { + level: 40, + msg: 'this is warn' + }, + { + level: 50, + msg: 'this is an error' + } + ] + const instance = pino({ + level: 'error', + browser: { + write (actual) { + checkLogObjects(is, same, actual, expected.shift()) + } + } + }) + + const child = instance.child({}) + child.level = 'info' + + child.debug('this is debug') + child.info('this is info') + child.warn('this is warn') + child.error('this is an error') + + same(expected.length, 0, 'not all messages were read') + end() +}) + +test('changing child log level should not affect parent log behavior', ({ end, same, is }) => { + const expected = [ + { + level: 50, + msg: 'this is an error' + }, + { + level: 60, + msg: 'this is fatal' + } + ] + const instance = pino({ + level: 'error', + browser: { + write (actual) { + checkLogObjects(is, same, actual, expected.shift()) + } + } + }) + + const child = instance.child({}) + child.level = 'info' + + instance.warn('this is warn') + instance.error('this is an error') + instance.fatal('this is fatal') + + same(expected.length, 0, 'not all messages were read') + end() +}) + +test('onChild callback should be called when new child is created', ({ end, pass, plan }) => { + plan(1) + const instance = pino({ + level: 'error', + browser: {}, + onChild: (_child) => { + pass('onChild callback was called') + end() + } + }) + + instance.child({}) +}) + +function checkLogObjects (is, same, actual, expected) { + is(actual.time <= Date.now(), true, 'time is greater than Date.now()') + + const actualCopy = Object.assign({}, actual) + const expectedCopy = Object.assign({}, expected) + delete actualCopy.time + delete expectedCopy.time + + same(actualCopy, expectedCopy) +} diff --git a/services/slides/node_modules/pino/test/browser-disabled.test.js b/services/slides/node_modules/pino/test/browser-disabled.test.js new file mode 100644 index 0000000000000000000000000000000000000000..36d1b1172b120af885e204c02dc2d1e613a7bfc3 --- /dev/null +++ b/services/slides/node_modules/pino/test/browser-disabled.test.js @@ -0,0 +1,87 @@ +'use strict' +const test = require('tape') +const pino = require('../browser') + +test('set browser opts disabled to true', ({ end, same }) => { + const instance = pino({ + browser: { + disabled: true, + write (actual) { + checkLogObjects(same, actual, []) + } + } + }) + instance.info('hello world') + instance.error('this is an error') + instance.fatal('this is fatal') + + end() +}) + +test('set browser opts disabled to false', ({ end, same }) => { + const expected = [ + { + level: 30, + msg: 'hello world' + }, + { + level: 50, + msg: 'this is an error' + }, + { + level: 60, + msg: 'this is fatal' + } + ] + const instance = pino({ + browser: { + disabled: false, + write (actual) { + checkLogObjects(same, actual, expected.shift()) + } + } + }) + instance.info('hello world') + instance.error('this is an error') + instance.fatal('this is fatal') + + end() +}) + +test('disabled is not set in browser opts', ({ end, same }) => { + const expected = [ + { + level: 30, + msg: 'hello world' + }, + { + level: 50, + msg: 'this is an error' + }, + { + level: 60, + msg: 'this is fatal' + } + ] + const instance = pino({ + browser: { + write (actual) { + checkLogObjects(same, actual, expected.shift()) + } + } + }) + instance.info('hello world') + instance.error('this is an error') + instance.fatal('this is fatal') + + end() +}) + +function checkLogObjects (same, actual, expected, is) { + const actualCopy = Object.assign({}, actual) + const expectedCopy = Object.assign({}, expected) + delete actualCopy.time + delete expectedCopy.time + + same(actualCopy, expectedCopy) +} diff --git a/services/slides/node_modules/pino/test/browser-early-console-freeze.test.js b/services/slides/node_modules/pino/test/browser-early-console-freeze.test.js new file mode 100644 index 0000000000000000000000000000000000000000..942abfa6f20b56828e1e7d4930c7806702dad50e --- /dev/null +++ b/services/slides/node_modules/pino/test/browser-early-console-freeze.test.js @@ -0,0 +1,12 @@ +'use strict' +Object.freeze(console) +const test = require('tape') +const pino = require('../browser') + +test('silent level', ({ end, fail, pass }) => { + pino({ + level: 'silent', + browser: { } + }) + end() +}) diff --git a/services/slides/node_modules/pino/test/browser-is-level-enabled.test.js b/services/slides/node_modules/pino/test/browser-is-level-enabled.test.js new file mode 100644 index 0000000000000000000000000000000000000000..045c613a8a6ed7ecdba1615366506957444eeabf --- /dev/null +++ b/services/slides/node_modules/pino/test/browser-is-level-enabled.test.js @@ -0,0 +1,101 @@ +'use strict' + +const { describe, test } = require('node:test') +const assert = require('node:assert') +const pino = require('../browser') + +const customLevels = { + trace: 10, + debug: 20, + info: 30, + warn: 40, + error: 50, + fatal: 60 +} + +describe('Default levels suite', () => { + test('can check if current level enabled', async () => { + const log = pino({ level: 'debug' }) + assert.equal(true, log.isLevelEnabled('debug')) + }) + + test('can check if current level enabled when as object', async () => { + const log = pino({ asObject: true, level: 'debug' }) + assert.equal(true, log.isLevelEnabled('debug')) + }) + + test('can check if level enabled after level set', async () => { + const log = pino() + assert.equal(false, log.isLevelEnabled('debug')) + log.level = 'debug' + assert.equal(true, log.isLevelEnabled('debug')) + }) + + test('can check if higher level enabled', async () => { + const log = pino({ level: 'debug' }) + assert.equal(true, log.isLevelEnabled('error')) + }) + + test('can check if lower level is disabled', async () => { + const log = pino({ level: 'error' }) + assert.equal(false, log.isLevelEnabled('trace')) + }) + + test('ASC: can check if child has current level enabled', async () => { + const log = pino().child({}, { level: 'debug' }) + assert.equal(true, log.isLevelEnabled('debug')) + assert.equal(true, log.isLevelEnabled('error')) + assert.equal(false, log.isLevelEnabled('trace')) + }) + + test('can check if custom level is enabled', async () => { + const log = pino({ + customLevels: { foo: 35 }, + level: 'debug' + }) + assert.equal(true, log.isLevelEnabled('foo')) + assert.equal(true, log.isLevelEnabled('error')) + assert.equal(false, log.isLevelEnabled('trace')) + }) +}) + +describe('Custom levels suite', () => { + test('can check if current level enabled', async () => { + const log = pino({ level: 'debug', customLevels }) + assert.equal(true, log.isLevelEnabled('debug')) + }) + + test('can check if level enabled after level set', async () => { + const log = pino({ customLevels }) + assert.equal(false, log.isLevelEnabled('debug')) + log.level = 'debug' + assert.equal(true, log.isLevelEnabled('debug')) + }) + + test('can check if higher level enabled', async () => { + const log = pino({ level: 'debug', customLevels }) + assert.equal(true, log.isLevelEnabled('error')) + }) + + test('can check if lower level is disabled', async () => { + const log = pino({ level: 'error', customLevels }) + assert.equal(false, log.isLevelEnabled('trace')) + }) + + test('can check if child has current level enabled', async () => { + const log = pino().child({ customLevels }, { level: 'debug' }) + assert.equal(true, log.isLevelEnabled('debug')) + assert.equal(true, log.isLevelEnabled('error')) + assert.equal(false, log.isLevelEnabled('trace')) + }) + + test('can check if custom level is enabled', async () => { + const log = pino({ + customLevels: { foo: 35, ...customLevels }, + level: 'debug' + }) + assert.equal(true, log.isLevelEnabled('foo')) + assert.equal(true, log.isLevelEnabled('error')) + assert.equal(false, log.isLevelEnabled('trace')) + }) +}) diff --git a/services/slides/node_modules/pino/test/browser-levels.test.js b/services/slides/node_modules/pino/test/browser-levels.test.js new file mode 100644 index 0000000000000000000000000000000000000000..a992905428b2ebeee5c8480c32b8b41e16dc2bf0 --- /dev/null +++ b/services/slides/node_modules/pino/test/browser-levels.test.js @@ -0,0 +1,241 @@ +'use strict' +const test = require('tape') +const pino = require('../browser') + +test('set the level by string', ({ end, same, is }) => { + const expected = [ + { + level: 50, + msg: 'this is an error' + }, + { + level: 60, + msg: 'this is fatal' + } + ] + const instance = pino({ + browser: { + write (actual) { + checkLogObjects(is, same, actual, expected.shift()) + } + } + }) + + instance.level = 'error' + instance.info('hello world') + instance.error('this is an error') + instance.fatal('this is fatal') + + end() +}) + +test('set the level by string. init with silent', ({ end, same, is }) => { + const expected = [ + { + level: 50, + msg: 'this is an error' + }, + { + level: 60, + msg: 'this is fatal' + } + ] + const instance = pino({ + level: 'silent', + browser: { + write (actual) { + checkLogObjects(is, same, actual, expected.shift()) + } + } + }) + + instance.level = 'error' + instance.info('hello world') + instance.error('this is an error') + instance.fatal('this is fatal') + + end() +}) + +test('set the level by string. init with silent and transmit', ({ end, same, is }) => { + const expected = [ + { + level: 50, + msg: 'this is an error' + }, + { + level: 60, + msg: 'this is fatal' + } + ] + const instance = pino({ + level: 'silent', + browser: { + write (actual) { + checkLogObjects(is, same, actual, expected.shift()) + } + }, + transmit: { + send () {} + } + }) + + instance.level = 'error' + instance.info('hello world') + instance.error('this is an error') + instance.fatal('this is fatal') + + end() +}) + +test('set the level via constructor', ({ end, same, is }) => { + const expected = [ + { + level: 50, + msg: 'this is an error' + }, + { + level: 60, + msg: 'this is fatal' + } + ] + const instance = pino({ + level: 'error', + browser: { + write (actual) { + checkLogObjects(is, same, actual, expected.shift()) + } + } + }) + + instance.info('hello world') + instance.error('this is an error') + instance.fatal('this is fatal') + + end() +}) + +test('set custom level and use it', ({ end, same, is }) => { + const expected = [ + { + level: 31, + msg: 'this is a custom level' + } + ] + const instance = pino({ + customLevels: { + success: 31 + }, + browser: { + write (actual) { + checkLogObjects(is, same, actual, expected.shift()) + } + } + }) + + instance.success('this is a custom level') + + end() +}) + +test('the wrong level throws', ({ end, throws }) => { + const instance = pino() + throws(() => { + instance.level = 'kaboom' + }) + end() +}) + +test('the wrong level by number throws', ({ end, throws }) => { + const instance = pino() + throws(() => { + instance.levelVal = 55 + }) + end() +}) + +test('exposes level string mappings', ({ end, is }) => { + is(pino.levels.values.error, 50) + end() +}) + +test('exposes level number mappings', ({ end, is }) => { + is(pino.levels.labels[50], 'error') + end() +}) + +test('returns level integer', ({ end, is }) => { + const instance = pino({ level: 'error' }) + is(instance.levelVal, 50) + end() +}) + +test('silent level via constructor', ({ end, fail }) => { + const instance = pino({ + level: 'silent', + browser: { + write () { + fail('no data should be logged') + } + } + }) + + Object.keys(pino.levels.values).forEach((level) => { + instance[level]('hello world') + }) + + end() +}) + +test('silent level by string', ({ end, fail }) => { + const instance = pino({ + browser: { + write () { + fail('no data should be logged') + } + } + }) + + instance.level = 'silent' + + Object.keys(pino.levels.values).forEach((level) => { + instance[level]('hello world') + }) + + end() +}) + +test('exposed levels', ({ end, same }) => { + same(Object.keys(pino.levels.values), [ + 'fatal', + 'error', + 'warn', + 'info', + 'debug', + 'trace' + ]) + end() +}) + +test('exposed labels', ({ end, same }) => { + same(Object.keys(pino.levels.labels), [ + '10', + '20', + '30', + '40', + '50', + '60' + ]) + end() +}) + +function checkLogObjects (is, same, actual, expected) { + is(actual.time <= Date.now(), true, 'time is greater than Date.now()') + + const actualCopy = Object.assign({}, actual) + const expectedCopy = Object.assign({}, expected) + delete actualCopy.time + delete expectedCopy.time + + same(actualCopy, expectedCopy) +} diff --git a/services/slides/node_modules/pino/test/browser-serializers.test.js b/services/slides/node_modules/pino/test/browser-serializers.test.js new file mode 100644 index 0000000000000000000000000000000000000000..07cfa60e04033dc0ed582026cbfecdd380a1a99e --- /dev/null +++ b/services/slides/node_modules/pino/test/browser-serializers.test.js @@ -0,0 +1,352 @@ +'use strict' +// eslint-disable-next-line +if (typeof $1 !== 'undefined') $1 = arguments.callee.caller.arguments[0] + +const test = require('tape') +const fresh = require('import-fresh') +const pino = require('../browser') + +const parentSerializers = { + test: () => 'parent' +} + +const childSerializers = { + test: () => 'child' +} + +test('serializers override values', ({ end, is }) => { + const parent = pino({ + serializers: parentSerializers, + browser: { + serialize: true, + write (o) { + is(o.test, 'parent') + end() + } + } + }) + + parent.fatal({ test: 'test' }) +}) + +test('without the serialize option, serializers do not override values', ({ end, is }) => { + const parent = pino({ + serializers: parentSerializers, + browser: { + write (o) { + is(o.test, 'test') + end() + } + } + }) + + parent.fatal({ test: 'test' }) +}) + +if (process.title !== 'browser') { + test('if serialize option is true, standard error serializer is auto enabled', ({ end, same }) => { + const err = Error('test') + err.code = 'test' + err.type = 'Error' // get that cov + const expect = pino.stdSerializers.err(err) + + const consoleError = console.error + console.error = function (err) { + same(err, expect) + } + + const logger = fresh('../browser')({ + browser: { serialize: true } + }) + + console.error = consoleError + + logger.fatal(err) + end() + }) + + test('if serialize option is array, standard error serializer is auto enabled', ({ end, same }) => { + const err = Error('test') + err.code = 'test' + const expect = pino.stdSerializers.err(err) + + const consoleError = console.error + console.error = function (err) { + same(err, expect) + } + + const logger = fresh('../browser', require)({ + browser: { serialize: [] } + }) + + console.error = consoleError + + logger.fatal(err) + end() + }) + + test('if serialize option is array containing !stdSerializers.err, standard error serializer is disabled', ({ end, is }) => { + const err = Error('test') + err.code = 'test' + const expect = err + + const consoleError = console.error + console.error = function (err) { + is(err, expect) + } + + const logger = fresh('../browser', require)({ + browser: { serialize: ['!stdSerializers.err'] } + }) + + console.error = consoleError + + logger.fatal(err) + end() + }) + + test('in browser, serializers apply to all objects', ({ end, is }) => { + const consoleError = console.error + console.error = function (test, test2, test3, test4, test5) { + is(test.key, 'serialized') + is(test2.key2, 'serialized2') + is(test5.key3, 'serialized3') + } + + const logger = fresh('../browser', require)({ + serializers: { + key: () => 'serialized', + key2: () => 'serialized2', + key3: () => 'serialized3' + }, + browser: { serialize: true } + }) + + console.error = consoleError + + logger.fatal({ key: 'test' }, { key2: 'test' }, 'str should skip', [{ foo: 'array should skip' }], { key3: 'test' }) + end() + }) + + test('serialize can be an array of selected serializers', ({ end, is }) => { + const consoleError = console.error + console.error = function (test, test2, test3, test4, test5) { + is(test.key, 'test') + is(test2.key2, 'serialized2') + is(test5.key3, 'test') + } + + const logger = fresh('../browser', require)({ + serializers: { + key: () => 'serialized', + key2: () => 'serialized2', + key3: () => 'serialized3' + }, + browser: { serialize: ['key2'] } + }) + + console.error = consoleError + + logger.fatal({ key: 'test' }, { key2: 'test' }, 'str should skip', [{ foo: 'array should skip' }], { key3: 'test' }) + end() + }) + + test('serialize filter applies to child loggers', ({ end, is }) => { + const consoleError = console.error + console.error = function (binding, test, test2, test3, test4, test5) { + is(test.key, 'test') + is(test2.key2, 'serialized2') + is(test5.key3, 'test') + } + + const logger = fresh('../browser', require)({ + browser: { serialize: ['key2'] } + }) + + console.error = consoleError + + logger.child({ + aBinding: 'test' + }, { + serializers: { + key: () => 'serialized', + key2: () => 'serialized2', + key3: () => 'serialized3' + } + }).fatal({ key: 'test' }, { key2: 'test' }, 'str should skip', [{ foo: 'array should skip' }], { key3: 'test' }) + end() + }) + + test('serialize filter applies to child loggers through bindings', ({ end, is }) => { + const consoleError = console.error + console.error = function (binding, test, test2, test3, test4, test5) { + is(test.key, 'test') + is(test2.key2, 'serialized2') + is(test5.key3, 'test') + } + + const logger = fresh('../browser', require)({ + browser: { serialize: ['key2'] } + }) + + console.error = consoleError + + logger.child({ + aBinding: 'test', + serializers: { + key: () => 'serialized', + key2: () => 'serialized2', + key3: () => 'serialized3' + } + }).fatal({ key: 'test' }, { key2: 'test' }, 'str should skip', [{ foo: 'array should skip' }], { key3: 'test' }) + end() + }) + + test('parent serializers apply to child bindings', ({ end, is }) => { + const consoleError = console.error + console.error = function (binding) { + is(binding.key, 'serialized') + } + + const logger = fresh('../browser', require)({ + serializers: { + key: () => 'serialized' + }, + browser: { serialize: true } + }) + + console.error = consoleError + + logger.child({ key: 'test' }).fatal({ test: 'test' }) + end() + }) + + test('child serializers apply to child bindings', ({ end, is }) => { + const consoleError = console.error + console.error = function (binding) { + is(binding.key, 'serialized') + } + + const logger = fresh('../browser', require)({ + browser: { serialize: true } + }) + + console.error = consoleError + + logger.child({ + key: 'test' + }, { + serializers: { + key: () => 'serialized' + } + }).fatal({ test: 'test' }) + end() + }) +} + +test('child does not overwrite parent serializers', ({ end, is }) => { + let c = 0 + const parent = pino({ + serializers: parentSerializers, + browser: { + serialize: true, + write (o) { + c++ + if (c === 1) is(o.test, 'parent') + if (c === 2) { + is(o.test, 'child') + end() + } + } + } + }) + const child = parent.child({}, { serializers: childSerializers }) + + parent.fatal({ test: 'test' }) + child.fatal({ test: 'test' }) +}) + +test('children inherit parent serializers', ({ end, is }) => { + const parent = pino({ + serializers: parentSerializers, + browser: { + serialize: true, + write (o) { + is(o.test, 'parent') + } + } + }) + + const child = parent.child({ a: 'property' }) + child.fatal({ test: 'test' }) + end() +}) + +test('children serializers get called', ({ end, is }) => { + const parent = pino({ + browser: { + serialize: true, + write (o) { + is(o.test, 'child') + } + } + }) + + const child = parent.child({ a: 'property' }, { serializers: childSerializers }) + + child.fatal({ test: 'test' }) + end() +}) + +test('children serializers get called when inherited from parent', ({ end, is }) => { + const parent = pino({ + serializers: parentSerializers, + browser: { + serialize: true, + write: (o) => { + is(o.test, 'pass') + } + } + }) + + const child = parent.child({}, { serializers: { test: () => 'pass' } }) + + child.fatal({ test: 'fail' }) + end() +}) + +test('non overridden serializers are available in the children', ({ end, is }) => { + const pSerializers = { + onlyParent: () => 'parent', + shared: () => 'parent' + } + + const cSerializers = { + shared: () => 'child', + onlyChild: () => 'child' + } + + let c = 0 + + const parent = pino({ + serializers: pSerializers, + browser: { + serialize: true, + write (o) { + c++ + if (c === 1) is(o.shared, 'child') + if (c === 2) is(o.onlyParent, 'parent') + if (c === 3) is(o.onlyChild, 'child') + if (c === 4) is(o.onlyChild, 'test') + } + } + }) + + const child = parent.child({}, { serializers: cSerializers }) + + child.fatal({ shared: 'test' }) + child.fatal({ onlyParent: 'test' }) + child.fatal({ onlyChild: 'test' }) + parent.fatal({ onlyChild: 'test' }) + end() +}) diff --git a/services/slides/node_modules/pino/test/browser-timestamp.test.js b/services/slides/node_modules/pino/test/browser-timestamp.test.js new file mode 100644 index 0000000000000000000000000000000000000000..994d83535ff1cf03c7a3f1b8290ed104503ffe49 --- /dev/null +++ b/services/slides/node_modules/pino/test/browser-timestamp.test.js @@ -0,0 +1,88 @@ +'use strict' +const test = require('tape') +const pino = require('../browser') + +Date.now = () => 1599400603614 + +test('null timestamp', ({ end, is }) => { + const instance = pino({ + timestamp: pino.stdTimeFunctions.nullTime, + browser: { + asObject: true, + write: function (o) { + is(o.time, undefined) + } + } + }) + instance.info('hello world') + end() +}) + +test('iso timestamp', ({ end, is }) => { + const instance = pino({ + timestamp: pino.stdTimeFunctions.isoTime, + browser: { + asObject: true, + write: function (o) { + is(o.time, '2020-09-06T13:56:43.614Z') + } + } + }) + instance.info('hello world') + end() +}) + +test('epoch timestamp', ({ end, is }) => { + const instance = pino({ + timestamp: pino.stdTimeFunctions.epochTime, + browser: { + asObject: true, + write: function (o) { + is(o.time, 1599400603614) + } + } + }) + instance.info('hello world') + end() +}) + +test('unix timestamp', ({ end, is }) => { + const instance = pino({ + timestamp: pino.stdTimeFunctions.unixTime, + browser: { + asObject: true, + write: function (o) { + is(o.time, Math.round(1599400603614 / 1000.0)) + } + } + }) + instance.info('hello world') + end() +}) + +test('epoch timestamp by default', ({ end, is }) => { + const instance = pino({ + browser: { + asObject: true, + write: function (o) { + is(o.time, 1599400603614) + } + } + }) + instance.info('hello world') + end() +}) + +test('not print timestamp if the option is false', ({ end, is }) => { + const instance = pino({ + timestamp: false, + browser: { + asObject: true, + write: function (o) { + is(o.time, undefined) + } + } + }) + instance.info('hello world') + end() +}) diff --git a/services/slides/node_modules/pino/test/browser-transmit.test.js b/services/slides/node_modules/pino/test/browser-transmit.test.js new file mode 100644 index 0000000000000000000000000000000000000000..d5063ca8eae7b75e46c8c136d6d5d9ad5141f6ac --- /dev/null +++ b/services/slides/node_modules/pino/test/browser-transmit.test.js @@ -0,0 +1,417 @@ +'use strict' +const test = require('tape') +const pino = require('../browser') + +function noop () {} + +test('throws if transmit object does not have send function', ({ end, throws }) => { + throws(() => { + pino({ browser: { transmit: {} } }) + }) + + throws(() => { + pino({ browser: { transmit: { send: 'not a func' } } }) + }) + + end() +}) + +test('calls send function after write', ({ end, is }) => { + let c = 0 + const logger = pino({ + browser: { + write: () => { + c++ + }, + transmit: { + send () { is(c, 1) } + } + } + }) + + logger.fatal({ test: 'test' }) + end() +}) + +test('passes send function the logged level', ({ end, is }) => { + const logger = pino({ + browser: { + write () {}, + transmit: { + send (level) { + is(level, 'fatal') + } + } + } + }) + + logger.fatal({ test: 'test' }) + end() +}) + +test('passes send function message strings in logEvent object when asObject is not set', ({ end, same, is }) => { + const logger = pino({ + browser: { + write: noop, + transmit: { + send (level, { messages }) { + is(messages[0], 'test') + is(messages[1], 'another test') + } + } + } + }) + + logger.fatal('test', 'another test') + + end() +}) + +test('passes send function message objects in logEvent object when asObject is not set', ({ end, same, is }) => { + const logger = pino({ + browser: { + write: noop, + transmit: { + send (level, { messages }) { + same(messages[0], { test: 'test' }) + is(messages[1], 'another test') + } + } + } + }) + + logger.fatal({ test: 'test' }, 'another test') + + end() +}) + +test('passes send function message strings in logEvent object when asObject is set', ({ end, same, is }) => { + const logger = pino({ + browser: { + asObject: true, + write: noop, + transmit: { + send (level, { messages }) { + is(messages[0], 'test') + is(messages[1], 'another test') + } + } + } + }) + + logger.fatal('test', 'another test') + + end() +}) + +test('passes send function message objects in logEvent object when asObject is set', ({ end, same, is }) => { + const logger = pino({ + browser: { + asObject: true, + write: noop, + transmit: { + send (level, { messages }) { + same(messages[0], { test: 'test' }) + is(messages[1], 'another test') + } + } + } + }) + + logger.fatal({ test: 'test' }, 'another test') + + end() +}) + +test('supplies a timestamp (ts) in logEvent object which is exactly the same as the `time` property in asObject mode', ({ end, is }) => { + let expected + const logger = pino({ + browser: { + asObject: true, // implicit because `write`, but just to be explicit + write (o) { + expected = o.time + }, + transmit: { + send (level, logEvent) { + is(logEvent.ts, expected) + } + } + } + }) + + logger.fatal('test') + end() +}) + +test('passes send function child bindings via logEvent object', ({ end, same, is }) => { + const logger = pino({ + browser: { + write: noop, + transmit: { + send (level, logEvent) { + const messages = logEvent.messages + const bindings = logEvent.bindings + same(bindings[0], { first: 'binding' }) + same(bindings[1], { second: 'binding2' }) + same(messages[0], { test: 'test' }) + is(messages[1], 'another test') + } + } + } + }) + + logger + .child({ first: 'binding' }) + .child({ second: 'binding2' }) + .fatal({ test: 'test' }, 'another test') + end() +}) + +test('passes send function level:{label, value} via logEvent object', ({ end, is }) => { + const logger = pino({ + browser: { + write: noop, + transmit: { + send (level, logEvent) { + const label = logEvent.level.label + const value = logEvent.level.value + + is(label, 'fatal') + is(value, 60) + } + } + } + }) + + logger.fatal({ test: 'test' }, 'another test') + end() +}) + +test('calls send function according to transmit.level', ({ end, is }) => { + let c = 0 + const logger = pino({ + browser: { + write: noop, + transmit: { + level: 'error', + send (level) { + c++ + if (c === 1) is(level, 'error') + if (c === 2) is(level, 'fatal') + } + } + } + }) + logger.warn('ignored') + logger.error('test') + logger.fatal('test') + end() +}) + +test('transmit.level defaults to logger level', ({ end, is }) => { + let c = 0 + const logger = pino({ + level: 'error', + browser: { + write: noop, + transmit: { + send (level) { + c++ + if (c === 1) is(level, 'error') + if (c === 2) is(level, 'fatal') + } + } + } + }) + logger.warn('ignored') + logger.error('test') + logger.fatal('test') + end() +}) + +test('transmit.level is effective even if lower than logger level', ({ end, is }) => { + let c = 0 + const logger = pino({ + level: 'error', + browser: { + write: noop, + transmit: { + level: 'info', + send (level) { + c++ + if (c === 1) is(level, 'warn') + if (c === 2) is(level, 'error') + if (c === 3) is(level, 'fatal') + } + } + } + }) + logger.warn('ignored') + logger.error('test') + logger.fatal('test') + end() +}) + +test('applies all serializers to messages and bindings (serialize:false - default)', ({ end, same, is }) => { + const logger = pino({ + serializers: { + first: () => 'first', + second: () => 'second', + test: () => 'serialize it' + }, + browser: { + write: noop, + transmit: { + send (level, logEvent) { + const messages = logEvent.messages + const bindings = logEvent.bindings + same(bindings[0], { first: 'first' }) + same(bindings[1], { second: 'second' }) + same(messages[0], { test: 'serialize it' }) + is(messages[1].type, 'Error') + } + } + } + }) + + logger + .child({ first: 'binding' }) + .child({ second: 'binding2' }) + .fatal({ test: 'test' }, Error()) + end() +}) + +test('applies all serializers to messages and bindings (serialize:true)', ({ end, same, is }) => { + const logger = pino({ + serializers: { + first: () => 'first', + second: () => 'second', + test: () => 'serialize it' + }, + browser: { + serialize: true, + write: noop, + transmit: { + send (level, logEvent) { + const messages = logEvent.messages + const bindings = logEvent.bindings + same(bindings[0], { first: 'first' }) + same(bindings[1], { second: 'second' }) + same(messages[0], { test: 'serialize it' }) + is(messages[1].type, 'Error') + } + } + } + }) + + logger + .child({ first: 'binding' }) + .child({ second: 'binding2' }) + .fatal({ test: 'test' }, Error()) + end() +}) + +test('extracts correct bindings and raw messages over multiple transmits', ({ end, same, is }) => { + let messages = null + let bindings = null + + const logger = pino({ + browser: { + write: noop, + transmit: { + send (level, logEvent) { + messages = logEvent.messages + bindings = logEvent.bindings + } + } + } + }) + + const child = logger.child({ child: true }) + const grandchild = child.child({ grandchild: true }) + + logger.fatal({ test: 'parent:test1' }) + logger.fatal({ test: 'parent:test2' }) + same([], bindings) + same([{ test: 'parent:test2' }], messages) + + child.fatal({ test: 'child:test1' }) + child.fatal({ test: 'child:test2' }) + same([{ child: true }], bindings) + same([{ test: 'child:test2' }], messages) + + grandchild.fatal({ test: 'grandchild:test1' }) + grandchild.fatal({ test: 'grandchild:test2' }) + same([{ child: true }, { grandchild: true }], bindings) + same([{ test: 'grandchild:test2' }], messages) + + end() +}) + +test('does not log below configured level', ({ end, is }) => { + let message = null + const logger = pino({ + level: 'info', + browser: { + write (o) { + message = o.msg + }, + transmit: { + send () { } + } + } + }) + + logger.debug('this message is silent') + is(message, null) + + end() +}) + +test('silent level prevents logging even with transmit', ({ end, fail }) => { + const logger = pino({ + level: 'silent', + browser: { + write () { + fail('no data should be logged by the write method') + }, + transmit: { + send () { + fail('no data should be logged by the send method') + } + } + } + }) + + Object.keys(pino.levels.values).forEach((level) => { + logger[level]('ignored') + }) + + end() +}) + +test('does not call send when transmit.level is set to silent', ({ end, fail, is }) => { + let c = 0 + const logger = pino({ + level: 'trace', + browser: { + write () { + c++ + }, + transmit: { + level: 'silent', + send () { + fail('no data should be logged by the transmit method') + } + } + } + }) + + const levels = Object.keys(pino.levels.values) + levels.forEach((level) => { + logger[level]('message') + }) + + is(c, levels.length, 'write must be called exactly once per level') + end() +}) diff --git a/services/slides/node_modules/pino/test/browser.test.js b/services/slides/node_modules/pino/test/browser.test.js new file mode 100644 index 0000000000000000000000000000000000000000..9bbde2b7fb7ede1c4450d13fde47b9ad73177944 --- /dev/null +++ b/services/slides/node_modules/pino/test/browser.test.js @@ -0,0 +1,698 @@ +'use strict' +const test = require('tape') +const fresh = require('import-fresh') +const pinoStdSerializers = require('pino-std-serializers') +const pino = require('../browser') + +levelTest('fatal') +levelTest('error') +levelTest('warn') +levelTest('info') +levelTest('debug') +levelTest('trace') + +test('silent level', ({ end, fail, pass }) => { + const instance = pino({ + level: 'silent', + browser: { write: fail } + }) + instance.info('test') + const child = instance.child({ test: 'test' }) + child.info('msg-test') + // use setTimeout because setImmediate isn't supported in most browsers + setTimeout(() => { + pass() + end() + }, 0) +}) + +test('enabled false', ({ end, fail, pass }) => { + const instance = pino({ + enabled: false, + browser: { write: fail } + }) + instance.info('test') + const child = instance.child({ test: 'test' }) + child.info('msg-test') + // use setTimeout because setImmediate isn't supported in most browsers + setTimeout(() => { + pass() + end() + }, 0) +}) + +test('throw if creating child without bindings', ({ end, throws }) => { + const instance = pino() + throws(() => instance.child()) + end() +}) + +test('stubs write, flush and ee methods on instance', ({ end, ok, is }) => { + const instance = pino() + + ok(isFunc(instance.setMaxListeners)) + ok(isFunc(instance.getMaxListeners)) + ok(isFunc(instance.emit)) + ok(isFunc(instance.addListener)) + ok(isFunc(instance.on)) + ok(isFunc(instance.prependListener)) + ok(isFunc(instance.once)) + ok(isFunc(instance.prependOnceListener)) + ok(isFunc(instance.removeListener)) + ok(isFunc(instance.removeAllListeners)) + ok(isFunc(instance.listeners)) + ok(isFunc(instance.listenerCount)) + ok(isFunc(instance.eventNames)) + ok(isFunc(instance.write)) + ok(isFunc(instance.flush)) + + is(instance.on(), undefined) + + end() +}) + +test('exposes levels object', ({ end, same }) => { + same(pino.levels, { + values: { + fatal: 60, + error: 50, + warn: 40, + info: 30, + debug: 20, + trace: 10 + }, + labels: { + 10: 'trace', + 20: 'debug', + 30: 'info', + 40: 'warn', + 50: 'error', + 60: 'fatal' + } + }) + + end() +}) + +test('exposes faux stdSerializers', ({ end, ok, same }) => { + ok(pino.stdSerializers) + // make sure faux stdSerializers match pino-std-serializers + for (const serializer in pinoStdSerializers) { + ok(pino.stdSerializers[serializer], `pino.stdSerializers.${serializer}`) + } + // confirm faux methods return empty objects + same(pino.stdSerializers.req(), {}) + same(pino.stdSerializers.mapHttpRequest(), {}) + same(pino.stdSerializers.mapHttpResponse(), {}) + same(pino.stdSerializers.res(), {}) + // confirm wrapping function is a passthrough + const noChange = { foo: 'bar', fuz: 42 } + same(pino.stdSerializers.wrapRequestSerializer(noChange), noChange) + same(pino.stdSerializers.wrapResponseSerializer(noChange), noChange) + end() +}) + +test('exposes err stdSerializer', ({ end, ok }) => { + ok(pino.stdSerializers.err) + ok(pino.stdSerializers.err(Error())) + end() +}) + +consoleMethodTest('error') +consoleMethodTest('fatal', 'error') +consoleMethodTest('warn') +consoleMethodTest('info') +consoleMethodTest('debug') +consoleMethodTest('trace') +absentConsoleMethodTest('error', 'log') +absentConsoleMethodTest('warn', 'error') +absentConsoleMethodTest('info', 'log') +absentConsoleMethodTest('debug', 'log') +absentConsoleMethodTest('trace', 'log') + +// do not run this with airtap +if (process.title !== 'browser') { + test('in absence of console, log methods become noops', ({ end, ok }) => { + const console = global.console + delete global.console + const instance = fresh('../browser')() + global.console = console + ok(fnName(instance.log).match(/noop/)) + ok(fnName(instance.fatal).match(/noop/)) + ok(fnName(instance.error).match(/noop/)) + ok(fnName(instance.warn).match(/noop/)) + ok(fnName(instance.info).match(/noop/)) + ok(fnName(instance.debug).match(/noop/)) + ok(fnName(instance.trace).match(/noop/)) + end() + }) +} + +test('opts.browser.asObject logs pino-like object to console', ({ end, ok, is }) => { + const info = console.info + console.info = function (o) { + is(o.level, 30) + is(o.msg, 'test') + ok(o.time) + console.info = info + } + const instance = require('../browser')({ + browser: { + asObject: true + } + }) + + instance.info('test') + end() +}) + +test('opts.browser.asObject uses opts.messageKey in logs', ({ end, ok, is }) => { + const messageKey = 'message' + const instance = require('../browser')({ + messageKey, + browser: { + asObject: true, + write: function (o) { + is(o.level, 30) + is(o[messageKey], 'test') + ok(o.time) + } + } + }) + + instance.info('test') + end() +}) + +test('opts.browser.asObjectBindingsOnly passes the bindings but keep the message unformatted', ({ end, ok, is, deepEqual }) => { + const messageKey = 'message' + const instance = require('../browser')({ + messageKey, + browser: { + asObjectBindingsOnly: true, + write: function (o, msg, ...args) { + is(o.level, 30) + ok(o.time) + is(msg, 'test %s') + deepEqual(args, ['foo']) + } + } + }) + + instance.info('test %s', 'foo') + end() +}) + +test('opts.browser.formatters (level) logs pino-like object to console', ({ end, ok, is }) => { + const info = console.info + console.info = function (o) { + is(o.level, 30) + is(o.label, 'info') + is(o.msg, 'test') + ok(o.time) + console.info = info + } + const instance = require('../browser')({ + browser: { + formatters: { + level (label, number) { + return { label, level: number } + } + } + } + }) + + instance.info('test') + end() +}) + +test('opts.browser.formatters (log) logs pino-like object to console', ({ end, ok, is }) => { + const info = console.info + console.info = function (o) { + is(o.level, 30) + is(o.msg, 'test') + is(o.hello, 'world') + is(o.newField, 'test') + ok(o.time, `Logged at ${o.time}`) + console.info = info + } + const instance = require('../browser')({ + browser: { + formatters: { + log (o) { + return { ...o, newField: 'test', time: `Logged at ${o.time}` } + } + } + } + }) + + instance.info({ hello: 'world' }, 'test') + end() +}) + +test('opts.browser.reportCaller adds caller in asObject mode', ({ end, ok }) => { + const instance = require('../browser')({ + browser: { + asObject: true, + reportCaller: true, + write: function (o) { + ok(typeof o.caller === 'string' && o.caller.length > 0, 'has caller string') + ok(/:\\d+:\\d+/.test(o.caller) || /:\d+:\d+/.test(o.caller), `caller has line:col pattern: ${o.caller}`) + } + } + }) + + instance.info('test') + end() +}) + +// NOTE: Default (non-object) mode caller string is covered in docs +// and manually verified. Keeping the test minimal to avoid cross-env flakiness. + +test('opts.browser.serialize and opts.browser.transmit only serializes log data once', ({ end, ok, is }) => { + const instance = require('../browser')({ + serializers: { + extras (data) { + return { serializedExtras: data } + } + }, + browser: { + serialize: ['extras'], + transmit: { + level: 'info', + send (level, o) { + is(o.messages[0].extras.serializedExtras, 'world') + } + } + } + }) + + instance.info({ extras: 'world' }, 'test') + end() +}) + +test('opts.browser.serialize and opts.asObject only serializes log data once', ({ end, ok, is }) => { + const instance = require('../browser')({ + serializers: { + extras (data) { + return { serializedExtras: data } + } + }, + browser: { + serialize: ['extras'], + asObject: true, + write: function (o) { + is(o.extras.serializedExtras, 'world') + } + } + }) + + instance.info({ extras: 'world' }, 'test') + end() +}) + +test('opts.browser.serialize, opts.asObject and opts.browser.transmit only serializes log data once', ({ end, ok, is }) => { + const instance = require('../browser')({ + serializers: { + extras (data) { + return { serializedExtras: data } + } + }, + browser: { + serialize: ['extras'], + asObject: true, + transmit: { + send (level, o) { + is(o.messages[0].extras.serializedExtras, 'world') + } + } + } + }) + + instance.info({ extras: 'world' }, 'test') + end() +}) + +test('opts.browser.write func log single string', ({ end, ok, is }) => { + const instance = pino({ + browser: { + write: function (o) { + is(o.level, 30) + is(o.msg, 'test') + ok(o.time) + } + } + }) + instance.info('test') + + end() +}) + +test('opts.browser.write func string joining', ({ end, ok, is }) => { + const instance = pino({ + browser: { + write: function (o) { + is(o.level, 30) + is(o.msg, 'test test2 test3') + ok(o.time) + } + } + }) + instance.info('test %s %s', 'test2', 'test3') + + end() +}) + +test('opts.browser.write func string joining when asObject is true', ({ end, ok, is }) => { + const instance = pino({ + browser: { + asObject: true, + write: function (o) { + is(o.level, 30) + is(o.msg, 'test test2 test3') + ok(o.time) + } + } + }) + instance.info('test %s %s', 'test2', 'test3') + + end() +}) + +test('opts.browser.write func string object joining', ({ end, ok, is }) => { + const instance = pino({ + browser: { + write: function (o) { + is(o.level, 30) + is(o.msg, 'test {"test":"test2"} {"test":"test3"}') + ok(o.time) + } + } + }) + instance.info('test %j %j', { test: 'test2' }, { test: 'test3' }) + + end() +}) + +test('opts.browser.write func string object joining when asObject is true', ({ end, ok, is }) => { + const instance = pino({ + browser: { + asObject: true, + write: function (o) { + is(o.level, 30) + is(o.msg, 'test {"test":"test2"} {"test":"test3"}') + ok(o.time) + } + } + }) + instance.info('test %j %j', { test: 'test2' }, { test: 'test3' }) + + end() +}) + +test('opts.browser.write func string interpolation', ({ end, ok, is }) => { + const instance = pino({ + browser: { + write: function (o) { + is(o.level, 30) + is(o.msg, 'test2 test ({"test":"test3"})') + ok(o.time) + } + } + }) + instance.info('%s test (%j)', 'test2', { test: 'test3' }) + + end() +}) + +test('opts.browser.write func number', ({ end, ok, is }) => { + const instance = pino({ + browser: { + write: function (o) { + is(o.level, 30) + is(o.msg, 1) + ok(o.time) + } + } + }) + instance.info(1) + + end() +}) + +test('opts.browser.write func log single object', ({ end, ok, is }) => { + const instance = pino({ + browser: { + write: function (o) { + is(o.level, 30) + is(o.test, 'test') + ok(o.time) + } + } + }) + instance.info({ test: 'test' }) + + end() +}) + +test('opts.browser.write obj writes to methods corresponding to level', ({ end, ok, is }) => { + const instance = pino({ + browser: { + write: { + error: function (o) { + is(o.level, 50) + is(o.test, 'test') + ok(o.time) + } + } + } + }) + instance.error({ test: 'test' }) + + end() +}) + +test('opts.browser.asObject/write supports child loggers', ({ end, ok, is }) => { + const instance = pino({ + browser: { + write (o) { + is(o.level, 30) + is(o.test, 'test') + is(o.msg, 'msg-test') + ok(o.time) + } + } + }) + const child = instance.child({ test: 'test' }) + child.info('msg-test') + + end() +}) + +test('opts.browser.asObject/write supports child child loggers', ({ end, ok, is }) => { + const instance = pino({ + browser: { + write (o) { + is(o.level, 30) + is(o.test, 'test') + is(o.foo, 'bar') + is(o.msg, 'msg-test') + ok(o.time) + } + } + }) + const child = instance.child({ test: 'test' }).child({ foo: 'bar' }) + child.info('msg-test') + + end() +}) + +test('opts.browser.asObject/write supports child child child loggers', ({ end, ok, is }) => { + const instance = pino({ + browser: { + write (o) { + is(o.level, 30) + is(o.test, 'test') + is(o.foo, 'bar') + is(o.baz, 'bop') + is(o.msg, 'msg-test') + ok(o.time) + } + } + }) + const child = instance.child({ test: 'test' }).child({ foo: 'bar' }).child({ baz: 'bop' }) + child.info('msg-test') + + end() +}) + +test('opts.browser.asObject defensively mitigates naughty numbers', ({ end, pass }) => { + const instance = pino({ + browser: { asObject: true, write: () => {} } + }) + const child = instance.child({ test: 'test' }) + child._childLevel = -10 + child.info('test') + pass() // if we reached here, there was no infinite loop, so, .. pass. + + end() +}) + +test('opts.browser.write obj falls back to console where a method is not supplied', ({ end, ok, is }) => { + const info = console.info + console.info = (o) => { + is(o.level, 30) + is(o.msg, 'test') + ok(o.time) + console.info = info + } + const instance = require('../browser')({ + browser: { + write: { + error (o) { + is(o.level, 50) + is(o.test, 'test') + ok(o.time) + } + } + } + }) + instance.error({ test: 'test' }) + instance.info('test') + + end() +}) + +function levelTest (name) { + test(name + ' logs', ({ end, is }) => { + const msg = 'hello world' + sink(name, (args) => { + is(args[0], msg) + end() + }) + pino({ level: name })[name](msg) + }) + + test('passing objects at level ' + name, ({ end, is }) => { + const msg = { hello: 'world' } + sink(name, (args) => { + is(args[0], msg) + end() + }) + pino({ level: name })[name](msg) + }) + + test('passing an object and a string at level ' + name, ({ end, is }) => { + const a = { hello: 'world' } + const b = 'a string' + sink(name, (args) => { + is(args[0], a) + is(args[1], b) + end() + }) + pino({ level: name })[name](a, b) + }) + + test('formatting logs as ' + name, ({ end, is }) => { + sink(name, (args) => { + is(args[0], 'hello %d') + is(args[1], 42) + end() + }) + pino({ level: name })[name]('hello %d', 42) + }) + + test('passing error at level ' + name, ({ end, is }) => { + const err = new Error('myerror') + sink(name, (args) => { + is(args[0], err) + end() + }) + pino({ level: name })[name](err) + }) + + test('passing error with a serializer at level ' + name, ({ end, is }) => { + // in browser - should have no effect (should not crash) + const err = new Error('myerror') + sink(name, (args) => { + is(args[0].err, err) + end() + }) + const instance = pino({ + level: name, + serializers: { + err: pino.stdSerializers.err + } + }) + instance[name]({ err }) + }) + + test('child logger for level ' + name, ({ end, is }) => { + const msg = 'hello world' + const parent = { hello: 'world' } + sink(name, (args) => { + is(args[0], parent) + is(args[1], msg) + end() + }) + const instance = pino({ level: name }) + const child = instance.child(parent) + child[name](msg) + }) + + test('child-child logger for level ' + name, ({ end, is }) => { + const msg = 'hello world' + const grandParent = { hello: 'world' } + const parent = { hello: 'you' } + sink(name, (args) => { + is(args[0], grandParent) + is(args[1], parent) + is(args[2], msg) + end() + }) + const instance = pino({ level: name }) + const child = instance.child(grandParent).child(parent) + child[name](msg) + }) +} + +function consoleMethodTest (level, method) { + if (!method) method = level + test('pino().' + level + ' uses console.' + method, ({ end, is }) => { + sink(method, (args) => { + is(args[0], 'test') + end() + }) + const instance = require('../browser')({ level }) + instance[level]('test') + }) +} + +function absentConsoleMethodTest (method, fallback) { + test('in absence of console.' + method + ', console.' + fallback + ' is used', ({ end, is }) => { + const fn = console[method] + console[method] = undefined + sink(fallback, function (args) { + is(args[0], 'test') + end() + console[method] = fn + }) + const instance = require('../browser')({ level: method }) + instance[method]('test') + }) +} + +function isFunc (fn) { return typeof fn === 'function' } +function fnName (fn) { + const rx = /^\s*function\s*([^(]*)/i + const match = rx.exec(fn) + return match && match[1] +} +function sink (method, fn) { + if (method === 'fatal') method = 'error' + const orig = console[method] + console[method] = function () { + console[method] = orig + fn(Array.prototype.slice.call(arguments)) + } +} diff --git a/services/slides/node_modules/pino/test/complex-objects.test.js b/services/slides/node_modules/pino/test/complex-objects.test.js new file mode 100644 index 0000000000000000000000000000000000000000..0149531e6f40b883c96fc01f4d4f815045e4bbe6 --- /dev/null +++ b/services/slides/node_modules/pino/test/complex-objects.test.js @@ -0,0 +1,36 @@ +'use strict' + +const test = require('node:test') +const assert = require('node:assert') +const { PassThrough } = require('node:stream') + +const { sink, once } = require('./helper') +const pino = require('../') + +test('Proxy and stream objects', async () => { + const s = new PassThrough() + s.resume() + s.write('', () => {}) + const obj = { s, p: new Proxy({}, { get () { throw new Error('kaboom') } }) } + const stream = sink() + const instance = pino(stream) + instance.info({ obj }) + + const result = await once(stream, 'data') + + assert.equal(result.obj, '[unable to serialize, circular reference is too complex to analyze]') +}) + +test('Proxy and stream objects', async () => { + const s = new PassThrough() + s.resume() + s.write('', () => {}) + const obj = { s, p: new Proxy({}, { get () { throw new Error('kaboom') } }) } + const stream = sink() + const instance = pino(stream) + instance.info(obj) + + const result = await once(stream, 'data') + + assert.equal(result.p, '[unable to serialize, circular reference is too complex to analyze]') +}) diff --git a/services/slides/node_modules/pino/test/crlf.test.js b/services/slides/node_modules/pino/test/crlf.test.js new file mode 100644 index 0000000000000000000000000000000000000000..56728a54c03a591cef0a60a0371cbbeb322149ec --- /dev/null +++ b/services/slides/node_modules/pino/test/crlf.test.js @@ -0,0 +1,34 @@ +'use strict' + +const test = require('node:test') +const assert = require('node:assert') + +const writer = require('flush-write-stream') +const pino = require('../') + +function capture () { + const ws = writer((chunk, enc, cb) => { + ws.data += chunk.toString() + cb() + }) + ws.data = '' + return ws +} + +test('pino uses LF by default', async () => { + const stream = capture() + const logger = pino(stream) + logger.info('foo') + logger.error('bar') + assert.ok(/foo[^\r\n]+\n[^\r\n]+bar[^\r\n]+\n/.test(stream.data)) +}) + +test('pino can log CRLF', async () => { + const stream = capture() + const logger = pino({ + crlf: true + }, stream) + logger.info('foo') + logger.error('bar') + assert.ok(/foo[^\n]+\r\n[^\n]+bar[^\n]+\r\n/.test(stream.data)) +}) diff --git a/services/slides/node_modules/pino/test/custom-levels.test.js b/services/slides/node_modules/pino/test/custom-levels.test.js new file mode 100644 index 0000000000000000000000000000000000000000..dae729273c00ca2aa7f2de8d6289de26f1520535 --- /dev/null +++ b/services/slides/node_modules/pino/test/custom-levels.test.js @@ -0,0 +1,267 @@ +'use strict' + +/* eslint no-prototype-builtins: 0 */ + +const test = require('node:test') +const assert = require('node:assert') + +const { sink, once } = require('./helper') +const pino = require('../') + +// Silence all warnings for this test +process.removeAllListeners('warning') +process.on('warning', () => {}) + +test('adds additional levels', async () => { + const stream = sink() + const logger = pino({ + customLevels: { + foo: 35, + bar: 45 + } + }, stream) + + logger.foo('test') + const { level } = await once(stream, 'data') + assert.equal(level, 35) +}) + +test('custom levels does not override default levels', async () => { + const stream = sink() + const logger = pino({ + customLevels: { + foo: 35 + } + }, stream) + + logger.info('test') + const { level } = await once(stream, 'data') + assert.equal(level, 30) +}) + +test('default levels can be redefined using custom levels', async () => { + const stream = sink() + const logger = pino({ + customLevels: { + info: 35, + debug: 45 + }, + useOnlyCustomLevels: true + }, stream) + + assert.equal(logger.hasOwnProperty('info'), true) + + logger.info('test') + const { level } = await once(stream, 'data') + assert.equal(level, 35) +}) + +test('custom levels overrides default level label if use useOnlyCustomLevels', async () => { + const stream = sink() + const logger = pino({ + customLevels: { + foo: 35 + }, + useOnlyCustomLevels: true, + level: 'foo' + }, stream) + + assert.equal(logger.hasOwnProperty('info'), false) +}) + +test('custom levels overrides default level value if use useOnlyCustomLevels', async () => { + const stream = sink() + const logger = pino({ + customLevels: { + foo: 35 + }, + useOnlyCustomLevels: true, + level: 35 + }, stream) + + assert.equal(logger.hasOwnProperty('info'), false) +}) + +test('custom levels are inherited by children', async () => { + const stream = sink() + const logger = pino({ + customLevels: { + foo: 35 + } + }, stream) + + logger.child({ childMsg: 'ok' }).foo('test') + const { msg, childMsg, level } = await once(stream, 'data') + assert.equal(level, 35) + assert.equal(childMsg, 'ok') + assert.equal(msg, 'test') +}) + +test('custom levels can be specified on child bindings', async () => { + const stream = sink() + const logger = pino(stream).child({ + childMsg: 'ok' + }, { + customLevels: { + foo: 35 + } + }) + + logger.foo('test') + const { msg, childMsg, level } = await once(stream, 'data') + assert.equal(level, 35) + assert.equal(childMsg, 'ok') + assert.equal(msg, 'test') +}) + +test('customLevels property child bindings does not get logged', async () => { + const stream = sink() + const logger = pino(stream).child({ + childMsg: 'ok' + }, { + customLevels: { + foo: 35 + } + }) + + logger.foo('test') + const { customLevels } = await once(stream, 'data') + assert.equal(customLevels, undefined) +}) + +test('throws when specifying pre-existing parent labels via child bindings', async () => { + const stream = sink() + assert.throws( + () => pino({ + customLevels: { + foo: 35 + } + }, stream).child({}, { + customLevels: { + foo: 45 + } + }), + /levels cannot be overridden/ + ) +}) + +test('throws when specifying pre-existing parent values via child bindings', async () => { + const stream = sink() + assert.throws( + () => pino({ + customLevels: { + foo: 35 + } + }, stream).child({}, { + customLevels: { + bar: 35 + } + }), + /pre-existing level values cannot be used for new levels/ + ) +}) + +test('throws when specifying core values via child bindings', async () => { + const stream = sink() + assert.throws( + () => pino(stream).child({}, { + customLevels: { + foo: 30 + } + }), + /pre-existing level values cannot be used for new levels/ + ) +}) + +test('throws when useOnlyCustomLevels is set true without customLevels', async () => { + const stream = sink() + assert.throws( + () => pino({ + useOnlyCustomLevels: true + }, stream), + /customLevels is required if useOnlyCustomLevels is set true/ + ) +}) + +test('custom level on one instance does not affect other instances', async () => { + pino({ + customLevels: { + foo: 37 + } + }) + assert.equal(typeof pino().foo, 'undefined') +}) + +test('setting level below or at custom level will successfully log', async () => { + const stream = sink() + const instance = pino({ customLevels: { foo: 35 } }, stream) + instance.level = 'foo' + instance.info('nope') + instance.foo('bar') + const { msg } = await once(stream, 'data') + assert.equal(msg, 'bar') +}) + +test('custom level below level threshold will not log', async () => { + const stream = sink() + const instance = pino({ customLevels: { foo: 15 } }, stream) + instance.level = 'info' + instance.info('bar') + instance.foo('nope') + const { msg } = await once(stream, 'data') + assert.equal(msg, 'bar') +}) + +test('does not share custom level state across siblings', async () => { + const stream = sink() + const logger = pino(stream) + logger.child({}, { + customLevels: { foo: 35 } + }) + assert.doesNotThrow(() => { + logger.child({}, { + customLevels: { foo: 35 } + }) + }) +}) + +test('custom level does not affect the levels serializer', async () => { + const stream = sink() + const logger = pino({ + customLevels: { + foo: 35, + bar: 45 + }, + formatters: { + level (label, number) { + return { priority: number } + } + } + }, stream) + + logger.foo('test') + const { priority } = await once(stream, 'data') + assert.equal(priority, 35) +}) + +test('When useOnlyCustomLevels is set to true, the level formatter should only get custom levels', async () => { + const stream = sink() + const logger = pino({ + customLevels: { + answer: 42 + }, + useOnlyCustomLevels: true, + level: 42, + formatters: { + level (label, number) { + assert.equal(label, 'answer') + assert.equal(number, 42) + return { level: number } + } + } + }, stream) + + logger.answer('test') + const { level } = await once(stream, 'data') + assert.equal(level, 42) +}) diff --git a/services/slides/node_modules/pino/test/diagnostics.test.js b/services/slides/node_modules/pino/test/diagnostics.test.js new file mode 100644 index 0000000000000000000000000000000000000000..2291fa898408f436985dc13d09732beb70fa44d1 --- /dev/null +++ b/services/slides/node_modules/pino/test/diagnostics.test.js @@ -0,0 +1,107 @@ +'use strict' + +const test = require('node:test') +const os = require('node:os') +const diagChan = require('node:diagnostics_channel') +const { AsyncLocalStorage } = require('node:async_hooks') +const { Writable } = require('node:stream') +const tspl = require('@matteo.collina/tspl') +const pino = require('../pino') + +const hostname = os.hostname() +const { pid } = process +const AS_JSON_START = 'tracing:pino_asJson:start' +const AS_JSON_END = 'tracing:pino_asJson:end' + +// Skip tests if diagnostics_channel.tracingChannel is not available (Node < 18.19) +const skip = typeof diagChan.tracingChannel !== 'function' + +test.beforeEach(ctx => { + ctx.pino = { + ts: 1757512800000, // 2025-09-10T10:00:00.000-05:00 + now: Date.now + } + + Date.now = () => ctx.pino.ts + + ctx.pino.dest = new Writable({ + objectMode: true, + write (data, enc, cb) { + cb() + } + }) +}) + +test.afterEach(ctx => { + Date.now = ctx.pino.now +}) + +test('asJson emits events', { skip }, async (t) => { + const plan = tspl(t, { plan: 8 }) + const { dest } = t.pino + const logger = pino({}, dest) + const expectedArguments = [ + {}, + 'testing', + 30, + `,"time":${t.pino.ts}` + ] + + let startEvent + diagChan.subscribe(AS_JSON_START, startHandler) + diagChan.subscribe(AS_JSON_END, endHandler) + + logger.info('testing') + await plan + + diagChan.unsubscribe(AS_JSON_START, startHandler) + diagChan.unsubscribe(AS_JSON_END, endHandler) + + function startHandler (event) { + startEvent = event + plan.equal(Object.prototype.toString.call(event.instance), '[object Pino]') + plan.equal(event.instance === logger, true) + plan.deepStrictEqual(Array.from(event.arguments ?? []), expectedArguments) + } + + function endHandler (event) { + plan.equal(Object.prototype.toString.call(event.instance), '[object Pino]') + plan.equal(event.instance === logger, true) + plan.deepStrictEqual(Array.from(event.arguments ?? []), expectedArguments) + plan.equal( + event.result, + `{"level":30,"time":${t.pino.ts},"pid":${pid},"hostname":"${hostname}","msg":"testing"}\n` + ) + + plan.equal(event.arguments === startEvent.arguments, true, 'same event object is supplied to both events') + } +}) + +test('asJson context is not lost', { skip }, async (t) => { + const plan = tspl(t, { plan: 2 }) + const { dest } = t.pino + const logger = pino({}, dest) + const asyncLocalStorage = new AsyncLocalStorage() + const localStore = { foo: 'bar' } + + diagChan.subscribe(AS_JSON_START, startHandler) + diagChan.subscribe(AS_JSON_END, endHandler) + + asyncLocalStorage.run(localStore, () => { + logger.info('testing') + }) + await plan + + diagChan.unsubscribe(AS_JSON_START, startHandler) + diagChan.unsubscribe(AS_JSON_END, endHandler) + + function startHandler () { + const store = asyncLocalStorage.getStore() + plan.equal(store === localStore, true) + } + + function endHandler () { + const store = asyncLocalStorage.getStore() + plan.equal(store === localStore, true) + } +}) diff --git a/services/slides/node_modules/pino/test/error-key.test.js b/services/slides/node_modules/pino/test/error-key.test.js new file mode 100644 index 0000000000000000000000000000000000000000..532d5e460941f92577ad52c91c1c9d539182f612 --- /dev/null +++ b/services/slides/node_modules/pino/test/error-key.test.js @@ -0,0 +1,37 @@ +'use strict' + +const test = require('node:test') +const assert = require('node:assert') + +const { sink, once } = require('./helper') +const stdSerializers = require('pino-std-serializers') +const pino = require('../') + +test('set the errorKey with error serializer', async () => { + const stream = sink() + const errorKey = 'error' + const instance = pino({ + errorKey, + serializers: { [errorKey]: stdSerializers.err } + }, stream) + instance.error(new ReferenceError('test')) + const o = await once(stream, 'data') + assert.equal(typeof o[errorKey], 'object') + assert.equal(o[errorKey].type, 'ReferenceError') + assert.equal(o[errorKey].message, 'test') + assert.equal(typeof o[errorKey].stack, 'string') +}) + +test('set the errorKey without error serializer', async () => { + const stream = sink() + const errorKey = 'error' + const instance = pino({ + errorKey + }, stream) + instance.error(new ReferenceError('test')) + const o = await once(stream, 'data') + assert.equal(typeof o[errorKey], 'object') + assert.equal(o[errorKey].type, 'ReferenceError') + assert.equal(o[errorKey].message, 'test') + assert.equal(typeof o[errorKey].stack, 'string') +}) diff --git a/services/slides/node_modules/pino/test/error.test.js b/services/slides/node_modules/pino/test/error.test.js new file mode 100644 index 0000000000000000000000000000000000000000..9f0a94c7cd56bd9fab997cfb1c82f970f45cc8a1 --- /dev/null +++ b/services/slides/node_modules/pino/test/error.test.js @@ -0,0 +1,403 @@ +'use strict' + +/* eslint no-prototype-builtins: 0 */ + +const test = require('node:test') +const assert = require('node:assert') +const os = require('node:os') +const tspl = require('@matteo.collina/tspl') + +const { sink, once } = require('./helper') +const pino = require('../') + +const { pid } = process +const hostname = os.hostname() +const level = 50 +const name = 'error' + +test('err is serialized with additional properties set on the Error object', async () => { + const stream = sink() + const err = Object.assign(new Error('myerror'), { foo: 'bar' }) + const instance = pino(stream) + instance.level = name + instance[name](err) + const result = await once(stream, 'data') + assert.ok(new Date(result.time) <= new Date(), 'time is greater than Date.now()') + delete result.time + assert.deepEqual(result, { + pid, + hostname, + level, + err: { + type: 'Error', + message: err.message, + stack: err.stack, + foo: err.foo + }, + msg: err.message + }) +}) + +test('type should be detected based on constructor', async () => { + class Bar extends Error {} + const stream = sink() + const err = new Bar('myerror') + const instance = pino(stream) + instance.level = name + instance[name](err) + const result = await once(stream, 'data') + assert.ok(new Date(result.time) <= new Date(), 'time is greater than Date.now()') + delete result.time + assert.deepEqual(result, { + pid, + hostname, + level, + err: { + type: 'Bar', + message: err.message, + stack: err.stack + }, + msg: err.message + }) +}) + +test('type, message and stack should be first level properties', async () => { + const stream = sink() + const err = Object.assign(new Error('foo'), { foo: 'bar' }) + const instance = pino(stream) + instance.level = name + instance[name](err) + + const result = await once(stream, 'data') + assert.ok(new Date(result.time) <= new Date(), 'time is greater than Date.now()') + delete result.time + assert.deepEqual(result, { + pid, + hostname, + level, + err: { + type: 'Error', + message: err.message, + stack: err.stack, + foo: err.foo + }, + msg: err.message + }) +}) + +test('err serializer', async () => { + const stream = sink() + const err = Object.assign(new Error('myerror'), { foo: 'bar' }) + const instance = pino({ + serializers: { + err: pino.stdSerializers.err + } + }, stream) + + instance.level = name + instance[name]({ err }) + const result = await once(stream, 'data') + assert.ok(new Date(result.time) <= new Date(), 'time is greater than Date.now()') + delete result.time + assert.deepEqual(result, { + pid, + hostname, + level, + err: { + type: 'Error', + message: err.message, + stack: err.stack, + foo: err.foo + }, + msg: err.message + }) +}) + +test('an error with statusCode property is not confused for a http response', async () => { + const stream = sink() + const err = Object.assign(new Error('StatusCodeErr'), { statusCode: 500 }) + const instance = pino(stream) + + instance.level = name + instance[name](err) + const result = await once(stream, 'data') + + assert.ok(new Date(result.time) <= new Date(), 'time is greater than Date.now()') + delete result.time + assert.deepEqual(result, { + pid, + hostname, + level, + err: { + type: 'Error', + message: err.message, + stack: err.stack, + statusCode: err.statusCode + }, + msg: err.message + }) +}) + +test('stack is omitted if it is not set on err', async (t) => { + const plan = tspl(t, { plan: 2 }) + const err = new Error('myerror') + delete err.stack + const instance = pino(sink(function (chunk, enc, cb) { + plan.ok(new Date(chunk.time) <= new Date(), 'time is greater than Date.now()') + delete chunk.time + plan.equal(chunk.hasOwnProperty('stack'), false) + cb() + })) + + instance.level = name + instance[name](err) + + await plan +}) + +test('correctly ignores toString on errors', async () => { + const err = new Error('myerror') + err.toString = () => undefined + const stream = sink() + const instance = pino({ + test: 'this' + }, stream) + instance.fatal(err) + const result = await once(stream, 'data') + delete result.time + assert.deepEqual(result, { + pid, + hostname, + level: 60, + err: { + type: 'Error', + message: err.message, + stack: err.stack + }, + msg: err.message + }) +}) + +test('assign mixin()', async () => { + const err = new Error('myerror') + const stream = sink() + const instance = pino({ + mixin () { + return { hello: 'world' } + } + }, stream) + instance.fatal(err) + const result = await once(stream, 'data') + delete result.time + assert.deepEqual(result, { + pid, + hostname, + level: 60, + hello: 'world', + err: { + type: 'Error', + message: err.message, + stack: err.stack + }, + msg: err.message + }) +}) + +test('no err serializer', async () => { + const err = new Error('myerror') + const stream = sink() + const instance = pino({ + serializers: {} + }, stream) + instance.fatal(err) + const result = await once(stream, 'data') + delete result.time + assert.deepEqual(result, { + pid, + hostname, + level: 60, + err: { + type: 'Error', + message: err.message, + stack: err.stack + }, + msg: err.message + }) +}) + +test('empty serializer', async () => { + const err = new Error('myerror') + const stream = sink() + const instance = pino({ + serializers: { + err () {} + } + }, stream) + instance.fatal(err) + const result = await once(stream, 'data') + delete result.time + assert.deepEqual(result, { + pid, + hostname, + level: 60, + msg: err.message + }) +}) + +test('assign mixin()', async () => { + const err = new Error('myerror') + const stream = sink() + const instance = pino({ + mixin () { + return { hello: 'world' } + } + }, stream) + instance.fatal(err) + const result = await once(stream, 'data') + delete result.time + assert.deepEqual(result, { + pid, + hostname, + level: 60, + hello: 'world', + err: { + type: 'Error', + message: err.message, + stack: err.stack + }, + msg: err.message + }) +}) + +test('no err serializer', async () => { + const err = new Error('myerror') + const stream = sink() + const instance = pino({ + serializers: {} + }, stream) + instance.fatal(err) + const result = await once(stream, 'data') + delete result.time + assert.deepEqual(result, { + pid, + hostname, + level: 60, + err: { + type: 'Error', + message: err.message, + stack: err.stack + }, + msg: err.message + }) +}) + +test('empty serializer', async () => { + const err = new Error('myerror') + const stream = sink() + const instance = pino({ + serializers: { + err () {} + } + }, stream) + instance.fatal(err) + const result = await once(stream, 'data') + delete result.time + assert.deepEqual(result, { + pid, + hostname, + level: 60, + msg: err.message + }) +}) + +test('correctly adds error information when nestedKey is used', async () => { + const err = new Error('myerror') + err.toString = () => undefined + const stream = sink() + const instance = pino({ + test: 'this', + nestedKey: 'obj' + }, stream) + instance.fatal(err) + const result = await once(stream, 'data') + delete result.time + assert.deepEqual(result, { + pid, + hostname, + level: 60, + obj: { + err: { + type: 'Error', + stack: err.stack, + message: err.message + } + }, + msg: err.message + }) +}) + +test('correctly adds msg on error when nestedKey is used', async () => { + const err = new Error('myerror') + err.toString = () => undefined + const stream = sink() + const instance = pino({ + test: 'this', + nestedKey: 'obj' + }, stream) + instance.fatal(err, 'msg message') + const result = await once(stream, 'data') + delete result.time + assert.deepEqual(result, { + pid, + hostname, + level: 60, + obj: { + err: { + type: 'Error', + stack: err.stack, + message: err.message + } + }, + msg: 'msg message' + }) +}) + +test('msg should take precedence over error message on mergingObject', async () => { + const err = new Error('myerror') + const stream = sink() + const instance = pino(stream) + instance.error({ msg: 'my message', err }) + const result = await once(stream, 'data') + delete result.time + assert.deepEqual(result, { + pid, + hostname, + level: 50, + err: { + type: 'Error', + stack: err.stack, + message: err.message + }, + msg: 'my message' + }) +}) + +test('considers messageKey when giving msg precedence over error', async () => { + const err = new Error('myerror') + const stream = sink() + const instance = pino({ messageKey: 'message' }, stream) + instance.error({ message: 'my message', err }) + const result = await once(stream, 'data') + delete result.time + assert.deepEqual(result, { + pid, + hostname, + level: 50, + err: { + type: 'Error', + stack: err.stack, + message: err.message + }, + message: 'my message' + }) +}) diff --git a/services/slides/node_modules/pino/test/escaping.test.js b/services/slides/node_modules/pino/test/escaping.test.js new file mode 100644 index 0000000000000000000000000000000000000000..ea7db20c753fa1356059ae414fd72c2d747c3b52 --- /dev/null +++ b/services/slides/node_modules/pino/test/escaping.test.js @@ -0,0 +1,93 @@ +'use strict' + +const test = require('node:test') +const assert = require('node:assert') +const os = require('node:os') + +const { sink, once } = require('./helper') +const pino = require('../') + +const { pid } = process +const hostname = os.hostname() + +function testEscape (ch, key) { + test('correctly escape ' + ch, async () => { + const stream = sink() + const instance = pino({ + name: 'hello' + }, stream) + instance.fatal('this contains ' + key) + const result = await once(stream, 'data') + delete result.time + assert.deepEqual(result, { + pid, + hostname, + level: 60, + name: 'hello', + msg: 'this contains ' + key + }) + }) +} + +testEscape('\\n', '\n') +testEscape('\\/', '/') +testEscape('\\\\', '\\') +testEscape('\\r', '\r') +testEscape('\\t', '\t') +testEscape('\\b', '\b') + +const toEscape = [ + '\u0000', // NUL Null character + '\u0001', // SOH Start of Heading + '\u0002', // STX Start of Text + '\u0003', // ETX End-of-text character + '\u0004', // EOT End-of-transmission character + '\u0005', // ENQ Enquiry character + '\u0006', // ACK Acknowledge character + '\u0007', // BEL Bell character + '\u0008', // BS Backspace + '\u0009', // HT Horizontal tab + '\u000A', // LF Line feed + '\u000B', // VT Vertical tab + '\u000C', // FF Form feed + '\u000D', // CR Carriage return + '\u000E', // SO Shift Out + '\u000F', // SI Shift In + '\u0010', // DLE Data Link Escape + '\u0011', // DC1 Device Control 1 + '\u0012', // DC2 Device Control 2 + '\u0013', // DC3 Device Control 3 + '\u0014', // DC4 Device Control 4 + '\u0015', // NAK Negative-acknowledge character + '\u0016', // SYN Synchronous Idle + '\u0017', // ETB End of Transmission Block + '\u0018', // CAN Cancel character + '\u0019', // EM End of Medium + '\u001A', // SUB Substitute character + '\u001B', // ESC Escape character + '\u001C', // FS File Separator + '\u001D', // GS Group Separator + '\u001E', // RS Record Separator + '\u001F' // US Unit Separator +] + +toEscape.forEach((key) => { + testEscape(JSON.stringify(key), key) +}) + +test('correctly escape `hello \\u001F world \\n \\u0022`', async () => { + const stream = sink() + const instance = pino({ + name: 'hello' + }, stream) + instance.fatal('hello \u001F world \n \u0022') + const result = await once(stream, 'data') + delete result.time + assert.deepEqual(result, { + pid, + hostname, + level: 60, + name: 'hello', + msg: 'hello \u001F world \n \u0022' + }) +}) diff --git a/services/slides/node_modules/pino/test/esm/esm.mjs b/services/slides/node_modules/pino/test/esm/esm.mjs new file mode 100644 index 0000000000000000000000000000000000000000..e32c2a9c4d99b1cbef34593e8c1e8ea836d9ee54 --- /dev/null +++ b/services/slides/node_modules/pino/test/esm/esm.mjs @@ -0,0 +1,14 @@ +import test from 'node:test' +import assert from 'node:assert' + +import pino from '../../pino.js' +import helper from '../helper.js' + +const { sink, check, once } = helper + +test('esm support', async () => { + const stream = sink() + const instance = pino(stream) + instance.info('hello world') + check(assert.equal, await once(stream, 'data'), 30, 'hello world') +}) diff --git a/services/slides/node_modules/pino/test/esm/index.test.js b/services/slides/node_modules/pino/test/esm/index.test.js new file mode 100644 index 0000000000000000000000000000000000000000..f627b7735c4f3306f17aa2af2cbf78d389c9ba44 --- /dev/null +++ b/services/slides/node_modules/pino/test/esm/index.test.js @@ -0,0 +1,21 @@ +'use strict' + +// Node v8 throw a `SyntaxError: Unexpected token import` +// even if this branch is never touched in the code, +// by using `eval` we can avoid this issue. +// eslint-disable-next-line + new Function('module', 'return import(module)')('./esm.mjs').catch((err) => { + process.nextTick(() => { + throw err + }) +}) + +// Node v8 throw a `SyntaxError: Unexpected token import` +// even if this branch is never touched in the code, +// by using `eval` we can avoid this issue. +// eslint-disable-next-line + new Function('module', 'return import(module)')('./named-exports.mjs').catch((err) => { + process.nextTick(() => { + throw err + }) +}) diff --git a/services/slides/node_modules/pino/test/esm/named-exports.mjs b/services/slides/node_modules/pino/test/esm/named-exports.mjs new file mode 100644 index 0000000000000000000000000000000000000000..c338b0362b1d7f9e0f352fa279cae87f1ee464cb --- /dev/null +++ b/services/slides/node_modules/pino/test/esm/named-exports.mjs @@ -0,0 +1,29 @@ +import test from 'node:test' +import assert from 'node:assert' +import { hostname } from 'node:os' +import { readFileSync } from 'node:fs' + +import { sink, check, once, watchFileCreated, file } from '../helper.js' +import { pino, destination } from '../../pino.js' + +test('named exports support', async () => { + const stream = sink() + const instance = pino(stream) + instance.info('hello world') + check(assert.equal, await once(stream, 'data'), 30, 'hello world') +}) + +test('destination', async () => { + const tmp = file() + const instance = pino(destination(tmp)) + instance.info('hello') + await watchFileCreated(tmp) + const result = JSON.parse(readFileSync(tmp).toString()) + delete result.time + assert.deepEqual(result, { + pid: process.pid, + hostname, + level: 30, + msg: 'hello' + }) +}) diff --git a/services/slides/node_modules/pino/test/exit.test.js b/services/slides/node_modules/pino/test/exit.test.js new file mode 100644 index 0000000000000000000000000000000000000000..4e3f6b6e4458bd75289c010fbd3313ef810320d5 --- /dev/null +++ b/services/slides/node_modules/pino/test/exit.test.js @@ -0,0 +1,79 @@ +'use strict' + +const test = require('node:test') +const assert = require('node:assert') +const { join } = require('node:path') + +const execa = require('execa') +const writer = require('flush-write-stream') +const { once } = require('./helper') + +// https://github.com/pinojs/pino/issues/542 +test('pino.destination log everything when calling process.exit(0)', async () => { + let actual = '' + const child = execa(process.argv[0], [join(__dirname, 'fixtures', 'destination-exit.js')]) + + child.stdout.pipe(writer((s, enc, cb) => { + actual += s + cb() + })) + + await once(child, 'close') + + assert.equal(actual.match(/hello/) != null, true) + assert.equal(actual.match(/world/) != null, true) +}) + +test('pino with no args log everything when calling process.exit(0)', async () => { + let actual = '' + const child = execa(process.argv[0], [join(__dirname, 'fixtures', 'default-exit.js')]) + + child.stdout.pipe(writer((s, enc, cb) => { + actual += s + cb() + })) + + await once(child, 'close') + + assert.equal(actual.match(/hello/) != null, true) + assert.equal(actual.match(/world/) != null, true) +}) + +test('sync false logs everything when calling process.exit(0)', async () => { + let actual = '' + const child = execa(process.argv[0], [join(__dirname, 'fixtures', 'syncfalse-exit.js')]) + + child.stdout.pipe(writer((s, enc, cb) => { + actual += s + cb() + })) + + await once(child, 'close') + + assert.equal(actual.match(/hello/) != null, true) + assert.equal(actual.match(/world/) != null, true) +}) + +test('sync false logs everything when calling flushSync', async () => { + let actual = '' + const child = execa(process.argv[0], [join(__dirname, 'fixtures', 'syncfalse-flush-exit.js')]) + + child.stdout.pipe(writer((s, enc, cb) => { + actual += s + cb() + })) + + await once(child, 'close') + + assert.equal(actual.match(/hello/) != null, true) + assert.equal(actual.match(/world/) != null, true) +}) + +test('transports exits gracefully when logging in exit', async () => { + const child = execa(process.argv[0], [join(__dirname, 'fixtures', 'transport-with-on-exit.js')]) + child.stdout.resume() + + const code = await once(child, 'close') + + assert.equal(code, 0) +}) diff --git a/services/slides/node_modules/pino/test/fixtures/broken-pipe/basic.js b/services/slides/node_modules/pino/test/fixtures/broken-pipe/basic.js new file mode 100644 index 0000000000000000000000000000000000000000..cc33c9b873d65eb0876a9257ad3f24f14e0c22b5 --- /dev/null +++ b/services/slides/node_modules/pino/test/fixtures/broken-pipe/basic.js @@ -0,0 +1,9 @@ +'use strict' + +global.process = { __proto__: process, pid: 123456 } +Date.now = function () { return 1459875739796 } +require('node:os').hostname = function () { return 'abcdefghijklmnopqr' } + +const pino = require('../../..')() + +pino.info('hello world') diff --git a/services/slides/node_modules/pino/test/fixtures/broken-pipe/destination.js b/services/slides/node_modules/pino/test/fixtures/broken-pipe/destination.js new file mode 100644 index 0000000000000000000000000000000000000000..701f686331d197ff7f02b1d1df796ea0d2a4a2ca --- /dev/null +++ b/services/slides/node_modules/pino/test/fixtures/broken-pipe/destination.js @@ -0,0 +1,10 @@ +'use strict' + +global.process = { __proto__: process, pid: 123456 } +Date.now = function () { return 1459875739796 } +require('node:os').hostname = function () { return 'abcdefghijklmnopqr' } + +const pino = require('../../..') +const logger = pino(pino.destination()) + +logger.info('hello world') diff --git a/services/slides/node_modules/pino/test/fixtures/broken-pipe/syncfalse.js b/services/slides/node_modules/pino/test/fixtures/broken-pipe/syncfalse.js new file mode 100644 index 0000000000000000000000000000000000000000..de71431fc654b0a7d8aa60c7d87bcf5d743f9a79 --- /dev/null +++ b/services/slides/node_modules/pino/test/fixtures/broken-pipe/syncfalse.js @@ -0,0 +1,12 @@ +'use strict' + +global.process = { __proto__: process, pid: 123456 } +Date.now = function () { return 1459875739796 } +require('node:os').hostname = function () { return 'abcdefghijklmnopqr' } + +const pino = require('../../..') +const logger = pino(pino.destination({ sync: false })) + +for (var i = 0; i < 1000; i++) { + logger.info('hello world') +} diff --git a/services/slides/node_modules/pino/test/fixtures/console-transport.js b/services/slides/node_modules/pino/test/fixtures/console-transport.js new file mode 100644 index 0000000000000000000000000000000000000000..9974ebcb92ae07def1374b66e536d748b0cf7c70 --- /dev/null +++ b/services/slides/node_modules/pino/test/fixtures/console-transport.js @@ -0,0 +1,13 @@ +const { Writable } = require('node:stream') + +module.exports = (options) => { + const myTransportStream = new Writable({ + autoDestroy: true, + write (chunk, enc, cb) { + // apply a transform and send to stdout + console.log(chunk.toString().toUpperCase()) + cb() + } + }) + return myTransportStream +} diff --git a/services/slides/node_modules/pino/test/fixtures/crashing-transport.js b/services/slides/node_modules/pino/test/fixtures/crashing-transport.js new file mode 100644 index 0000000000000000000000000000000000000000..1f3d46ee952d3be746f094a83dd97a5faa8cbdb5 --- /dev/null +++ b/services/slides/node_modules/pino/test/fixtures/crashing-transport.js @@ -0,0 +1,13 @@ +const { Writable } = require('node:stream') + +module.exports = () => + new Writable({ + autoDestroy: true, + write (chunk, enc, cb) { + setImmediate(() => { + /* eslint-disable no-empty */ + for (let i = 0; i < 1e3; i++) {} + process.exit(0) + }) + } + }) diff --git a/services/slides/node_modules/pino/test/fixtures/default-exit.js b/services/slides/node_modules/pino/test/fixtures/default-exit.js new file mode 100644 index 0000000000000000000000000000000000000000..3fd2a0e1772d1c1fbf413b66660128c7fbf3efba --- /dev/null +++ b/services/slides/node_modules/pino/test/fixtures/default-exit.js @@ -0,0 +1,8 @@ +global.process = { __proto__: process, pid: 123456 } +Date.now = function () { return 1459875739796 } +require('node:os').hostname = function () { return 'abcdefghijklmnopqr' } +const pino = require(require.resolve('./../../')) +const logger = pino() +logger.info('hello') +logger.info('world') +process.exit(0) diff --git a/services/slides/node_modules/pino/test/fixtures/destination-exit.js b/services/slides/node_modules/pino/test/fixtures/destination-exit.js new file mode 100644 index 0000000000000000000000000000000000000000..63c6d69765c8c9a2c97c2f4e6fa19b94ab355689 --- /dev/null +++ b/services/slides/node_modules/pino/test/fixtures/destination-exit.js @@ -0,0 +1,8 @@ +global.process = { __proto__: process, pid: 123456 } +Date.now = function () { return 1459875739796 } +require('node:os').hostname = function () { return 'abcdefghijklmnopqr' } +const pino = require(require.resolve('./../../')) +const logger = pino({}, pino.destination(1)) +logger.info('hello') +logger.info('world') +process.exit(0) diff --git a/services/slides/node_modules/pino/test/fixtures/eval/index.js b/services/slides/node_modules/pino/test/fixtures/eval/index.js new file mode 100644 index 0000000000000000000000000000000000000000..1d45ad03866b5ca6ff4b58a8c66912ae660eca71 --- /dev/null +++ b/services/slides/node_modules/pino/test/fixtures/eval/index.js @@ -0,0 +1,13 @@ +/* eslint-disable no-eval */ + +eval(` +const pino = require('../../../') + +const logger = pino( + pino.transport({ + target: 'pino/file' + }) +) + +logger.info('done!') +`) diff --git a/services/slides/node_modules/pino/test/fixtures/eval/node_modules/14-files.js b/services/slides/node_modules/pino/test/fixtures/eval/node_modules/14-files.js new file mode 100644 index 0000000000000000000000000000000000000000..32a20daed77dd10eee80d6d0de225d19c86e755e --- /dev/null +++ b/services/slides/node_modules/pino/test/fixtures/eval/node_modules/14-files.js @@ -0,0 +1,3 @@ +const file1 = require("./file1.js") + +file1() diff --git a/services/slides/node_modules/pino/test/fixtures/eval/node_modules/2-files.js b/services/slides/node_modules/pino/test/fixtures/eval/node_modules/2-files.js new file mode 100644 index 0000000000000000000000000000000000000000..8c665edabe2c72066aef16bfcf2a7c0978a438ea --- /dev/null +++ b/services/slides/node_modules/pino/test/fixtures/eval/node_modules/2-files.js @@ -0,0 +1,3 @@ +const file12 = require("./file12.js") + +file12() diff --git a/services/slides/node_modules/pino/test/fixtures/eval/node_modules/file1.js b/services/slides/node_modules/pino/test/fixtures/eval/node_modules/file1.js new file mode 100644 index 0000000000000000000000000000000000000000..4ce13fbb1b694ee195e3c9ce5ce7f0e159730e16 --- /dev/null +++ b/services/slides/node_modules/pino/test/fixtures/eval/node_modules/file1.js @@ -0,0 +1,5 @@ +const file2 = require("./file2.js") + +module.exports = function () { + file2() +} diff --git a/services/slides/node_modules/pino/test/fixtures/eval/node_modules/file10.js b/services/slides/node_modules/pino/test/fixtures/eval/node_modules/file10.js new file mode 100644 index 0000000000000000000000000000000000000000..136f0e0c9de939c8155a07a203a7394cf62e58e9 --- /dev/null +++ b/services/slides/node_modules/pino/test/fixtures/eval/node_modules/file10.js @@ -0,0 +1,5 @@ +const file11 = require("./file11.js") + +module.exports = function () { + file11() +} diff --git a/services/slides/node_modules/pino/test/fixtures/eval/node_modules/file11.js b/services/slides/node_modules/pino/test/fixtures/eval/node_modules/file11.js new file mode 100644 index 0000000000000000000000000000000000000000..f8a731b80eec0ca073249768d24f3a443d4ce8a7 --- /dev/null +++ b/services/slides/node_modules/pino/test/fixtures/eval/node_modules/file11.js @@ -0,0 +1,5 @@ +const file12 = require("./file12.js") + +module.exports = function () { + file12() +} diff --git a/services/slides/node_modules/pino/test/fixtures/eval/node_modules/file12.js b/services/slides/node_modules/pino/test/fixtures/eval/node_modules/file12.js new file mode 100644 index 0000000000000000000000000000000000000000..e8e330f8460e8d778bcb76fda52072a801bb3770 --- /dev/null +++ b/services/slides/node_modules/pino/test/fixtures/eval/node_modules/file12.js @@ -0,0 +1,5 @@ +const file13 = require("./file13.js") + +module.exports = function () { + file13() +} diff --git a/services/slides/node_modules/pino/test/fixtures/eval/node_modules/file13.js b/services/slides/node_modules/pino/test/fixtures/eval/node_modules/file13.js new file mode 100644 index 0000000000000000000000000000000000000000..6db9a61f753d00556c9161a63bb22f64a7c0b4bd --- /dev/null +++ b/services/slides/node_modules/pino/test/fixtures/eval/node_modules/file13.js @@ -0,0 +1,5 @@ +const file14 = require("./file14.js") + +module.exports = function () { + file14() +} diff --git a/services/slides/node_modules/pino/test/fixtures/eval/node_modules/file14.js b/services/slides/node_modules/pino/test/fixtures/eval/node_modules/file14.js new file mode 100644 index 0000000000000000000000000000000000000000..443ca7f80ae07302b7c75416e7acf661854e1d92 --- /dev/null +++ b/services/slides/node_modules/pino/test/fixtures/eval/node_modules/file14.js @@ -0,0 +1,11 @@ +const pino = require("../../../../"); + +module.exports = function() { + const logger = pino( + pino.transport({ + target: 'pino/file' + }) + ) + + logger.info('done!') +} diff --git a/services/slides/node_modules/pino/test/fixtures/eval/node_modules/file2.js b/services/slides/node_modules/pino/test/fixtures/eval/node_modules/file2.js new file mode 100644 index 0000000000000000000000000000000000000000..46877d5bd87d5055b62ee9ac6882bf32b0d6c5a0 --- /dev/null +++ b/services/slides/node_modules/pino/test/fixtures/eval/node_modules/file2.js @@ -0,0 +1,5 @@ +const file3 = require("./file3.js") + +module.exports = function () { + file3() +} diff --git a/services/slides/node_modules/pino/test/fixtures/eval/node_modules/file3.js b/services/slides/node_modules/pino/test/fixtures/eval/node_modules/file3.js new file mode 100644 index 0000000000000000000000000000000000000000..3a6ac78daa4f6d679a2d008ee78c27182137cd5c --- /dev/null +++ b/services/slides/node_modules/pino/test/fixtures/eval/node_modules/file3.js @@ -0,0 +1,5 @@ +const file4 = require("./file4.js") + +module.exports = function () { + file4() +} diff --git a/services/slides/node_modules/pino/test/fixtures/eval/node_modules/file4.js b/services/slides/node_modules/pino/test/fixtures/eval/node_modules/file4.js new file mode 100644 index 0000000000000000000000000000000000000000..b679e24df3cf3077b691bda524fe103d4c390902 --- /dev/null +++ b/services/slides/node_modules/pino/test/fixtures/eval/node_modules/file4.js @@ -0,0 +1,5 @@ +const file5 = require("./file5.js") + +module.exports = function () { + file5() +} diff --git a/services/slides/node_modules/pino/test/fixtures/eval/node_modules/file5.js b/services/slides/node_modules/pino/test/fixtures/eval/node_modules/file5.js new file mode 100644 index 0000000000000000000000000000000000000000..06cd045299447e3a919acc207b340c6137963c99 --- /dev/null +++ b/services/slides/node_modules/pino/test/fixtures/eval/node_modules/file5.js @@ -0,0 +1,5 @@ +const file6 = require("./file6.js") + +module.exports = function () { + file6() +} diff --git a/services/slides/node_modules/pino/test/fixtures/eval/node_modules/file6.js b/services/slides/node_modules/pino/test/fixtures/eval/node_modules/file6.js new file mode 100644 index 0000000000000000000000000000000000000000..3abf1dcbd3dcd09bfe03a8746945cf4e8ce2b5c0 --- /dev/null +++ b/services/slides/node_modules/pino/test/fixtures/eval/node_modules/file6.js @@ -0,0 +1,5 @@ +const file7 = require("./file7.js") + +module.exports = function () { + file7() +} diff --git a/services/slides/node_modules/pino/test/fixtures/eval/node_modules/file7.js b/services/slides/node_modules/pino/test/fixtures/eval/node_modules/file7.js new file mode 100644 index 0000000000000000000000000000000000000000..4d2f488ce8eced694735b4e75907ce9568c200d9 --- /dev/null +++ b/services/slides/node_modules/pino/test/fixtures/eval/node_modules/file7.js @@ -0,0 +1,5 @@ +const file8 = require("./file8.js") + +module.exports = function () { + file8() +} diff --git a/services/slides/node_modules/pino/test/fixtures/eval/node_modules/file8.js b/services/slides/node_modules/pino/test/fixtures/eval/node_modules/file8.js new file mode 100644 index 0000000000000000000000000000000000000000..e87f177a240b3d58d97c20e0e5d30bd70dd6b83f --- /dev/null +++ b/services/slides/node_modules/pino/test/fixtures/eval/node_modules/file8.js @@ -0,0 +1,5 @@ +const file9 = require("./file9.js") + +module.exports = function () { + file9() +} diff --git a/services/slides/node_modules/pino/test/fixtures/eval/node_modules/file9.js b/services/slides/node_modules/pino/test/fixtures/eval/node_modules/file9.js new file mode 100644 index 0000000000000000000000000000000000000000..0164926f7c7045ccc7da16b40e5b7df317f6483d --- /dev/null +++ b/services/slides/node_modules/pino/test/fixtures/eval/node_modules/file9.js @@ -0,0 +1,5 @@ +const file10 = require("./file10.js") + +module.exports = function () { + file10() +} diff --git a/services/slides/node_modules/pino/test/fixtures/noop-transport.js b/services/slides/node_modules/pino/test/fixtures/noop-transport.js new file mode 100644 index 0000000000000000000000000000000000000000..745504a13205f71fe88420d24fe07850bd641515 --- /dev/null +++ b/services/slides/node_modules/pino/test/fixtures/noop-transport.js @@ -0,0 +1,10 @@ +const { Writable } = require('node:stream') + +module.exports = () => { + return new Writable({ + autoDestroy: true, + write (chunk, enc, cb) { + cb() + } + }) +} diff --git a/services/slides/node_modules/pino/test/fixtures/pretty/null-prototype.js b/services/slides/node_modules/pino/test/fixtures/pretty/null-prototype.js new file mode 100644 index 0000000000000000000000000000000000000000..c88e686bc96bd1334d9d70cf64ff4064cd539504 --- /dev/null +++ b/services/slides/node_modules/pino/test/fixtures/pretty/null-prototype.js @@ -0,0 +1,8 @@ +global.process = { __proto__: process, pid: 123456 } +Date.now = function () { return 1459875739796 } +require('node:os').hostname = function () { return 'abcdefghijklmnopqr' } +const pino = require(require.resolve('./../../../')) +const log = pino({ prettyPrint: true }) +const obj = Object.create(null) +Object.assign(obj, { foo: 'bar' }) +log.info(obj, 'hello') diff --git a/services/slides/node_modules/pino/test/fixtures/stdout-hack-protection.js b/services/slides/node_modules/pino/test/fixtures/stdout-hack-protection.js new file mode 100644 index 0000000000000000000000000000000000000000..525ef6246c7bf4a79ce39b42bf8084bb24080cc5 --- /dev/null +++ b/services/slides/node_modules/pino/test/fixtures/stdout-hack-protection.js @@ -0,0 +1,11 @@ +global.process = { __proto__: process, pid: 123456 } + +const write = process.stdout.write.bind(process.stdout) +process.stdout.write = function (chunk) { + write('hack ' + chunk) +} + +Date.now = function () { return 1459875739796 } +require('node:os').hostname = function () { return 'abcdefghijklmnopqr' } +const pino = require(require.resolve('../../'))() +pino.info('me') diff --git a/services/slides/node_modules/pino/test/fixtures/syncfalse-child.js b/services/slides/node_modules/pino/test/fixtures/syncfalse-child.js new file mode 100644 index 0000000000000000000000000000000000000000..f858b3d7742d3e2db9b1e811683500b80d24a335 --- /dev/null +++ b/services/slides/node_modules/pino/test/fixtures/syncfalse-child.js @@ -0,0 +1,6 @@ +global.process = { __proto__: process, pid: 123456 } +Date.now = function () { return 1459875739796 } +require('node:os').hostname = function () { return 'abcdefghijklmnopqr' } +const pino = require(require.resolve('./../../')) +const asyncLogger = pino(pino.destination({ sync: false })).child({ hello: 'world' }) +asyncLogger.info('h') diff --git a/services/slides/node_modules/pino/test/fixtures/syncfalse-exit.js b/services/slides/node_modules/pino/test/fixtures/syncfalse-exit.js new file mode 100644 index 0000000000000000000000000000000000000000..fb09eab7d1454dce5f07d535b4412907a78cee7c --- /dev/null +++ b/services/slides/node_modules/pino/test/fixtures/syncfalse-exit.js @@ -0,0 +1,9 @@ +global.process = { __proto__: process, pid: 123456 } +Date.now = function () { return 1459875739796 } +require('node:os').hostname = function () { return 'abcdefghijklmnopqr' } +const pino = require(require.resolve('./../../')) +const dest = pino.destination({ dest: 1, minLength: 4096, sync: false }) +const logger = pino({}, dest) +logger.info('hello') +logger.info('world') +process.exit(0) diff --git a/services/slides/node_modules/pino/test/fixtures/syncfalse-flush-exit.js b/services/slides/node_modules/pino/test/fixtures/syncfalse-flush-exit.js new file mode 100644 index 0000000000000000000000000000000000000000..bf9cb4f53813fb944310c57cc1ea7b35b0074d44 --- /dev/null +++ b/services/slides/node_modules/pino/test/fixtures/syncfalse-flush-exit.js @@ -0,0 +1,10 @@ +global.process = { __proto__: process, pid: 123456 } +Date.now = function () { return 1459875739796 } +require('node:os').hostname = function () { return 'abcdefghijklmnopqr' } +const pino = require(require.resolve('./../../')) +const dest = pino.destination({ dest: 1, minLength: 4096, sync: false }) +const logger = pino({}, dest) +logger.info('hello') +logger.info('world') +dest.flushSync() +process.exit(0) diff --git a/services/slides/node_modules/pino/test/fixtures/syncfalse.js b/services/slides/node_modules/pino/test/fixtures/syncfalse.js new file mode 100644 index 0000000000000000000000000000000000000000..4d367523ebb72c1bf65102eda89fad1bcd647a0f --- /dev/null +++ b/services/slides/node_modules/pino/test/fixtures/syncfalse.js @@ -0,0 +1,6 @@ +global.process = { __proto__: process, pid: 123456 } +Date.now = function () { return 1459875739796 } +require('node:os').hostname = function () { return 'abcdefghijklmnopqr' } +const pino = require(require.resolve('./../../')) +const asyncLogger = pino(pino.destination({ minLength: 4096, sync: false })) +asyncLogger.info('h') diff --git a/services/slides/node_modules/pino/test/fixtures/syntax-error-esm.mjs b/services/slides/node_modules/pino/test/fixtures/syntax-error-esm.mjs new file mode 100644 index 0000000000000000000000000000000000000000..021d53bac285f6d3d2bd617c9252a03bd11bd577 --- /dev/null +++ b/services/slides/node_modules/pino/test/fixtures/syntax-error-esm.mjs @@ -0,0 +1,2 @@ +// This is a syntax error +import diff --git a/services/slides/node_modules/pino/test/fixtures/to-file-transport-with-transform.js b/services/slides/node_modules/pino/test/fixtures/to-file-transport-with-transform.js new file mode 100644 index 0000000000000000000000000000000000000000..89cf465ece6e1662e975f12bd77a82175eab0f20 --- /dev/null +++ b/services/slides/node_modules/pino/test/fixtures/to-file-transport-with-transform.js @@ -0,0 +1,20 @@ +'use strict' + +const fs = require('node:fs') +const { once } = require('node:events') +const { Transform } = require('node:stream') + +async function run (opts) { + if (!opts.destination) throw new Error('kaboom') + const stream = fs.createWriteStream(opts.destination) + await once(stream, 'open') + const t = new Transform({ + transform (chunk, enc, cb) { + setImmediate(cb, null, chunk.toString().toUpperCase()) + } + }) + t.pipe(stream) + return t +} + +module.exports = run diff --git a/services/slides/node_modules/pino/test/fixtures/to-file-transport.js b/services/slides/node_modules/pino/test/fixtures/to-file-transport.js new file mode 100644 index 0000000000000000000000000000000000000000..09f12742d5ace461c8ee33b938148e86b3100f6b --- /dev/null +++ b/services/slides/node_modules/pino/test/fixtures/to-file-transport.js @@ -0,0 +1,13 @@ +'use strict' + +const fs = require('node:fs') +const { once } = require('node:events') + +async function run (opts) { + if (!opts.destination) throw new Error('kaboom') + const stream = fs.createWriteStream(opts.destination) + await once(stream, 'open') + return stream +} + +module.exports = run diff --git a/services/slides/node_modules/pino/test/fixtures/to-file-transport.mjs b/services/slides/node_modules/pino/test/fixtures/to-file-transport.mjs new file mode 100644 index 0000000000000000000000000000000000000000..4925d3bf4e980575e656952bb79ef1aa4dc24d3e --- /dev/null +++ b/services/slides/node_modules/pino/test/fixtures/to-file-transport.mjs @@ -0,0 +1,8 @@ +import { createWriteStream } from 'node:fs' +import { once } from 'node:events' + +export default async function run (opts) { + const stream = createWriteStream(opts.destination) + await once(stream, 'open') + return stream +} diff --git a/services/slides/node_modules/pino/test/fixtures/transport-exit-immediately-with-async-dest.js b/services/slides/node_modules/pino/test/fixtures/transport-exit-immediately-with-async-dest.js new file mode 100644 index 0000000000000000000000000000000000000000..9837e33a6c3eff1d4dd1f254f6ed2d3684934ea0 --- /dev/null +++ b/services/slides/node_modules/pino/test/fixtures/transport-exit-immediately-with-async-dest.js @@ -0,0 +1,16 @@ +'use strict' + +const pino = require('../..') +const transport = pino.transport({ + target: './to-file-transport-with-transform.js', + options: { + destination: process.argv[2] + } +}) +const logger = pino(transport) + +logger.info('Hello') + +logger.info('World') + +process.exit(0) diff --git a/services/slides/node_modules/pino/test/fixtures/transport-exit-immediately.js b/services/slides/node_modules/pino/test/fixtures/transport-exit-immediately.js new file mode 100644 index 0000000000000000000000000000000000000000..5be55e4eccbd670c043328707b1e5aa4cfb418a0 --- /dev/null +++ b/services/slides/node_modules/pino/test/fixtures/transport-exit-immediately.js @@ -0,0 +1,11 @@ +'use strict' + +const pino = require('../..') +const transport = pino.transport({ + target: 'pino/file' +}) +const logger = pino(transport) + +logger.info('Hello') + +process.exit(0) diff --git a/services/slides/node_modules/pino/test/fixtures/transport-exit-on-ready.js b/services/slides/node_modules/pino/test/fixtures/transport-exit-on-ready.js new file mode 100644 index 0000000000000000000000000000000000000000..1520db54b901750896204ce941b03db6b8118081 --- /dev/null +++ b/services/slides/node_modules/pino/test/fixtures/transport-exit-on-ready.js @@ -0,0 +1,12 @@ +'use strict' + +const pino = require('../..') +const transport = pino.transport({ + target: 'pino/file' +}) +const logger = pino(transport) + +transport.on('ready', function () { + logger.info('Hello') + process.exit(0) +}) diff --git a/services/slides/node_modules/pino/test/fixtures/transport-invalid-node-options.js b/services/slides/node_modules/pino/test/fixtures/transport-invalid-node-options.js new file mode 100644 index 0000000000000000000000000000000000000000..06bc73921f3e03cb78109acf2ca31e3aa660a9ab --- /dev/null +++ b/services/slides/node_modules/pino/test/fixtures/transport-invalid-node-options.js @@ -0,0 +1,26 @@ +'use strict' + +const pino = require('../../') +const { join } = require('node:path') + +const destination = process.argv[2] + +process.env.NODE_OPTIONS = `--require ${join(__dirname, 'this-file-does-not-exist.js')}` + +const transport = pino.transport({ + target: join(__dirname, 'to-file-transport.js'), + options: { destination } +}) + +const logger = pino(transport) +transport.on('ready', () => { + logger.info('hello with invalid node options preload') + setTimeout(() => { + transport.end() + }, 50) +}) + +transport.on('error', (err) => { + process.stderr.write(`${err.stack}\n`) + process.exitCode = 1 +}) diff --git a/services/slides/node_modules/pino/test/fixtures/transport-main.js b/services/slides/node_modules/pino/test/fixtures/transport-main.js new file mode 100644 index 0000000000000000000000000000000000000000..cb02005cd0bd9c591032552e7d49368c8dcb3c7d --- /dev/null +++ b/services/slides/node_modules/pino/test/fixtures/transport-main.js @@ -0,0 +1,9 @@ +'use strict' + +const { join } = require('node:path') +const pino = require('../..') +const transport = pino.transport({ + target: join(__dirname, 'transport-worker.js') +}) +const logger = pino(transport) +logger.info('Hello') diff --git a/services/slides/node_modules/pino/test/fixtures/transport-many-lines.js b/services/slides/node_modules/pino/test/fixtures/transport-many-lines.js new file mode 100644 index 0000000000000000000000000000000000000000..d8bb5e3af8a5691d2b92e672f562346b3badcc66 --- /dev/null +++ b/services/slides/node_modules/pino/test/fixtures/transport-many-lines.js @@ -0,0 +1,29 @@ +'use strict' + +const pino = require('../..') +const transport = pino.transport({ + targets: [{ + level: 'info', + target: 'pino/file', + options: { + destination: process.argv[2] + } + }] +}) +const logger = pino(transport) + +const toWrite = 1000000 +transport.on('ready', run) + +let total = 0 + +function run () { + if (total++ === 8) { + return + } + + for (let i = 0; i < toWrite; i++) { + logger.info(`hello ${i}`) + } + transport.once('drain', run) +} diff --git a/services/slides/node_modules/pino/test/fixtures/transport-preload-main.mjs b/services/slides/node_modules/pino/test/fixtures/transport-preload-main.mjs new file mode 100644 index 0000000000000000000000000000000000000000..4bbc9bca397e1697a9fc265b5452eaff06a250f7 --- /dev/null +++ b/services/slides/node_modules/pino/test/fixtures/transport-preload-main.mjs @@ -0,0 +1,13 @@ +'use strict' + +// This is the main script that runs after the preload +// It imports the logger from the preload and logs a message + +import { log } from './transport-preload.mjs' + +log.info('hello from main') + +// Wait a bit for the transport to flush +setTimeout(() => { + process.exit(0) +}, 500) diff --git a/services/slides/node_modules/pino/test/fixtures/transport-preload.mjs b/services/slides/node_modules/pino/test/fixtures/transport-preload.mjs new file mode 100644 index 0000000000000000000000000000000000000000..28aa50ef38b7fb7cdd87fac4028f6324e84dee05 --- /dev/null +++ b/services/slides/node_modules/pino/test/fixtures/transport-preload.mjs @@ -0,0 +1,13 @@ +'use strict' + +import pino from '../../pino.js' +import { join } from 'node:path' + +const log = pino({ + transport: { + target: join(import.meta.dirname, 'to-file-transport.js'), + options: { destination: process.argv[2] } + } +}) + +export { log } diff --git a/services/slides/node_modules/pino/test/fixtures/transport-string-stdout.js b/services/slides/node_modules/pino/test/fixtures/transport-string-stdout.js new file mode 100644 index 0000000000000000000000000000000000000000..64d8ac1ea598aee8f70cefe5c24bb51cf458db8a --- /dev/null +++ b/services/slides/node_modules/pino/test/fixtures/transport-string-stdout.js @@ -0,0 +1,9 @@ +'use strict' + +const pino = require('../..') +const transport = pino.transport({ + target: 'pino/file', + options: { destination: '1' } +}) +const logger = pino(transport) +logger.info('Hello') diff --git a/services/slides/node_modules/pino/test/fixtures/transport-transform.js b/services/slides/node_modules/pino/test/fixtures/transport-transform.js new file mode 100644 index 0000000000000000000000000000000000000000..4950236c647a0452698f761622ac2918e38c39c8 --- /dev/null +++ b/services/slides/node_modules/pino/test/fixtures/transport-transform.js @@ -0,0 +1,21 @@ +'use strict' + +const build = require('pino-abstract-transport') +const { pipeline, Transform } = require('node:stream') +module.exports = (options) => { + return build(function (source) { + const myTransportStream = new Transform({ + autoDestroy: true, + objectMode: true, + transform (chunk, enc, cb) { + chunk.service = 'pino' + this.push(JSON.stringify(chunk)) + cb() + } + }) + pipeline(source, myTransportStream, () => {}) + return myTransportStream + }, { + enablePipelining: true + }) +} diff --git a/services/slides/node_modules/pino/test/fixtures/transport-uses-pino-config.js b/services/slides/node_modules/pino/test/fixtures/transport-uses-pino-config.js new file mode 100644 index 0000000000000000000000000000000000000000..0c87c949143e2e6f76ece3a3b0f483956ebe6927 --- /dev/null +++ b/services/slides/node_modules/pino/test/fixtures/transport-uses-pino-config.js @@ -0,0 +1,33 @@ +'use strict' + +const build = require('pino-abstract-transport') +const { pipeline, Transform } = require('node:stream') +module.exports = () => { + return build(function (source) { + const myTransportStream = new Transform({ + autoDestroy: true, + objectMode: true, + transform (chunk, enc, cb) { + const { + time, + level, + [source.messageKey]: body, + [source.errorKey]: error, + ...attributes + } = chunk + this.push(JSON.stringify({ + severityText: source.levels.labels[level], + body, + attributes, + ...(error && { error }) + })) + cb() + } + }) + pipeline(source, myTransportStream, () => {}) + return myTransportStream + }, { + enablePipelining: true, + expectPinoConfig: true + }) +} diff --git a/services/slides/node_modules/pino/test/fixtures/transport-with-on-exit.js b/services/slides/node_modules/pino/test/fixtures/transport-with-on-exit.js new file mode 100644 index 0000000000000000000000000000000000000000..655a17395af8633de582636f47717015b67f9b99 --- /dev/null +++ b/services/slides/node_modules/pino/test/fixtures/transport-with-on-exit.js @@ -0,0 +1,12 @@ +'use strict' +const pino = require('../..') +const log = pino({ + transport: { + target: 'pino/file', + options: { destination: 1 } + } +}) +log.info('hello world!') +process.on('exit', (code) => { + log.info('Exiting peacefully') +}) diff --git a/services/slides/node_modules/pino/test/fixtures/transport-worker-data.js b/services/slides/node_modules/pino/test/fixtures/transport-worker-data.js new file mode 100644 index 0000000000000000000000000000000000000000..1e0e7a8dcb22dd2adb3b4ced7681dc9368634784 --- /dev/null +++ b/services/slides/node_modules/pino/test/fixtures/transport-worker-data.js @@ -0,0 +1,19 @@ +'use strict' + +const { parentPort, workerData } = require('worker_threads') +const { Writable } = require('node:stream') + +module.exports = (options) => { + const myTransportStream = new Writable({ + autoDestroy: true, + write (chunk, enc, cb) { + parentPort.postMessage({ + code: 'EVENT', + name: 'workerData', + args: [workerData] + }) + cb() + } + }) + return myTransportStream +} diff --git a/services/slides/node_modules/pino/test/fixtures/transport-worker-name.js b/services/slides/node_modules/pino/test/fixtures/transport-worker-name.js new file mode 100644 index 0000000000000000000000000000000000000000..00573c99a6e969692f01f33f593d40838fb60431 --- /dev/null +++ b/services/slides/node_modules/pino/test/fixtures/transport-worker-name.js @@ -0,0 +1,24 @@ +'use strict' + +const { parentPort, threadName } = require('worker_threads') +const { Writable } = require('node:stream') + +let sent = false + +module.exports = (options) => { + const myTransportStream = new Writable({ + autoDestroy: true, + write (chunk, enc, cb) { + if (!sent) { + sent = true + parentPort.postMessage({ + code: 'EVENT', + name: 'workerThreadName', + args: [threadName] + }) + } + cb() + } + }) + return myTransportStream +} diff --git a/services/slides/node_modules/pino/test/fixtures/transport-worker.js b/services/slides/node_modules/pino/test/fixtures/transport-worker.js new file mode 100644 index 0000000000000000000000000000000000000000..8964b263195806c674f025bb456abd4a5c5c2a16 --- /dev/null +++ b/services/slides/node_modules/pino/test/fixtures/transport-worker.js @@ -0,0 +1,15 @@ +'use strict' + +const { Writable } = require('node:stream') +const fs = require('node:fs') +module.exports = (options) => { + const myTransportStream = new Writable({ + autoDestroy: true, + write (chunk, enc, cb) { + // Bypass console.log() to avoid flakiness + fs.writeSync(1, chunk.toString()) + cb() + } + }) + return myTransportStream +} diff --git a/services/slides/node_modules/pino/test/fixtures/transport-wrong-export-type.js b/services/slides/node_modules/pino/test/fixtures/transport-wrong-export-type.js new file mode 100644 index 0000000000000000000000000000000000000000..ed0affd58fe76bf7433ce2847cfab4bdb603ac9a --- /dev/null +++ b/services/slides/node_modules/pino/test/fixtures/transport-wrong-export-type.js @@ -0,0 +1,3 @@ +module.exports = { + completelyUnrelatedProperty: 'Just a very incorrect transport worker implementation' +} diff --git a/services/slides/node_modules/pino/test/fixtures/transport/index.js b/services/slides/node_modules/pino/test/fixtures/transport/index.js new file mode 100644 index 0000000000000000000000000000000000000000..f255858de72751b25dfc1ad91cd85f1be60d2a01 --- /dev/null +++ b/services/slides/node_modules/pino/test/fixtures/transport/index.js @@ -0,0 +1,12 @@ +'use strict' + +const fs = require('node:fs') +const { once } = require('node:events') + +async function run (opts) { + const stream = fs.createWriteStream(opts.destination) + await once(stream, 'open') + return stream +} + +module.exports = run diff --git a/services/slides/node_modules/pino/test/fixtures/transport/package.json b/services/slides/node_modules/pino/test/fixtures/transport/package.json new file mode 100644 index 0000000000000000000000000000000000000000..26beeaaeaaa4bde4c2ec0148a8e4e5793ae5712e --- /dev/null +++ b/services/slides/node_modules/pino/test/fixtures/transport/package.json @@ -0,0 +1,5 @@ +{ + "name": "transport", + "version": "0.0.1", + "main": "./index.js" +} diff --git a/services/slides/node_modules/pino/test/fixtures/ts/to-file-transport-native.mts b/services/slides/node_modules/pino/test/fixtures/ts/to-file-transport-native.mts new file mode 100644 index 0000000000000000000000000000000000000000..04785bbe2f43a2cd8148462298109eed4b25af82 --- /dev/null +++ b/services/slides/node_modules/pino/test/fixtures/ts/to-file-transport-native.mts @@ -0,0 +1,15 @@ +import * as fs from 'node:fs' +import { once } from 'node:events' + +interface TransportOptions { + destination?: fs.PathLike +} + +async function run (opts: TransportOptions): Promise { + if (!opts.destination) throw new Error('destination is required') + const stream = fs.createWriteStream(opts.destination, { encoding: 'utf8' }) + await once(stream, 'open') + return stream +} + +export default run diff --git a/services/slides/node_modules/pino/test/fixtures/ts/to-file-transport-with-transform.ts b/services/slides/node_modules/pino/test/fixtures/ts/to-file-transport-with-transform.ts new file mode 100644 index 0000000000000000000000000000000000000000..ebb7165ed48ff53932937f422552f18cc4f6f2c7 --- /dev/null +++ b/services/slides/node_modules/pino/test/fixtures/ts/to-file-transport-with-transform.ts @@ -0,0 +1,18 @@ +import * as fs from 'node:fs' +import { once } from 'node:events' +import { Transform } from 'node:stream' + +async function run (opts: { destination?: fs.PathLike }): Promise { + if (!opts.destination) throw new Error('kaboom') + const stream = fs.createWriteStream(opts.destination) + await once(stream, 'open') + const t = new Transform({ + transform (chunk, enc, cb) { + setImmediate(cb, null, chunk.toString().toUpperCase()) + } + }) + t.pipe(stream) + return t +} + +export default run diff --git a/services/slides/node_modules/pino/test/fixtures/ts/to-file-transport.ts b/services/slides/node_modules/pino/test/fixtures/ts/to-file-transport.ts new file mode 100644 index 0000000000000000000000000000000000000000..18606062126285141c654ef3f9cfea4e609c30b9 --- /dev/null +++ b/services/slides/node_modules/pino/test/fixtures/ts/to-file-transport.ts @@ -0,0 +1,11 @@ +import * as fs from 'node:fs' +import { once } from 'node:events' + +async function run (opts: { destination?: fs.PathLike }): Promise { + if (!opts.destination) throw new Error('kaboom') + const stream = fs.createWriteStream(opts.destination, { encoding: 'utf8' }) + await once(stream, 'open') + return stream +} + +export default run diff --git a/services/slides/node_modules/pino/test/fixtures/ts/transpile.cjs b/services/slides/node_modules/pino/test/fixtures/ts/transpile.cjs new file mode 100644 index 0000000000000000000000000000000000000000..6c2af6783e15dd769f7dc4a7cdab6d52af4bc56c --- /dev/null +++ b/services/slides/node_modules/pino/test/fixtures/ts/transpile.cjs @@ -0,0 +1,36 @@ +#!/usr/bin/env node + +const execa = require('execa') +const fs = require('node:fs') + +const existsSync = fs.existsSync +const stat = fs.promises.stat + +// Hardcoded parameters +const esVersions = ['es5', 'es6', 'es2017', 'esnext'] +const filesToTranspile = ['to-file-transport.ts'] + +async function transpile () { + process.chdir(__dirname) + + for (const sourceFileName of filesToTranspile) { + const sourceStat = await stat(sourceFileName) + + for (const esVersion of esVersions) { + const intermediateFileName = sourceFileName.replace(/\.ts$/, '.js') + const targetFileName = sourceFileName.replace(/\.ts$/, `.${esVersion}.cjs`) + + const shouldTranspile = !existsSync(targetFileName) || (await stat(targetFileName)).mtimeMs < sourceStat.mtimeMs + + if (shouldTranspile) { + await execa('tsc', ['--target', esVersion, '--module', 'commonjs', sourceFileName]) + await execa('mv', [intermediateFileName, targetFileName]) + } + } + } +} + +transpile().catch(err => { + process.exitCode = 1 + throw err +}) diff --git a/services/slides/node_modules/pino/test/fixtures/ts/transport-exit-immediately-with-async-dest.ts b/services/slides/node_modules/pino/test/fixtures/ts/transport-exit-immediately-with-async-dest.ts new file mode 100644 index 0000000000000000000000000000000000000000..f3e6f2ebc34eb2ba5de777a138eb2aa0543ba929 --- /dev/null +++ b/services/slides/node_modules/pino/test/fixtures/ts/transport-exit-immediately-with-async-dest.ts @@ -0,0 +1,15 @@ +import pino from '../../..' +import { join } from 'node:path' + +const transport = pino.transport({ + target: join(__dirname, 'to-file-transport-with-transform.ts'), + options: { + destination: process.argv[2] + } +}) +const logger = pino(transport) + +logger.info('Hello') +logger.info('World') + +process.exit(0) diff --git a/services/slides/node_modules/pino/test/fixtures/ts/transport-exit-immediately.ts b/services/slides/node_modules/pino/test/fixtures/ts/transport-exit-immediately.ts new file mode 100644 index 0000000000000000000000000000000000000000..21f2ab70374ec52e98480830c05d1d5225c87a17 --- /dev/null +++ b/services/slides/node_modules/pino/test/fixtures/ts/transport-exit-immediately.ts @@ -0,0 +1,10 @@ +import pino from '../../..' + +const transport = pino.transport({ + target: 'pino/file' +}) +const logger = pino(transport) + +logger.info('Hello') + +process.exit(0) diff --git a/services/slides/node_modules/pino/test/fixtures/ts/transport-exit-on-ready.ts b/services/slides/node_modules/pino/test/fixtures/ts/transport-exit-on-ready.ts new file mode 100644 index 0000000000000000000000000000000000000000..a1f6a842bcc60f2be61883a1155da2540491447e --- /dev/null +++ b/services/slides/node_modules/pino/test/fixtures/ts/transport-exit-on-ready.ts @@ -0,0 +1,11 @@ +import pino from '../../..' + +const transport = pino.transport({ + target: 'pino/file' +}) +const logger = pino(transport) + +transport.on('ready', function () { + logger.info('Hello') + process.exit(0) +}) diff --git a/services/slides/node_modules/pino/test/fixtures/ts/transport-main.ts b/services/slides/node_modules/pino/test/fixtures/ts/transport-main.ts new file mode 100644 index 0000000000000000000000000000000000000000..f31f88cdb7063bab4efacc47594eecc93b975a97 --- /dev/null +++ b/services/slides/node_modules/pino/test/fixtures/ts/transport-main.ts @@ -0,0 +1,8 @@ +import { join } from 'node:path' +import pino from '../../..' + +const transport = pino.transport({ + target: join(__dirname, 'transport-worker.ts') +}) +const logger = pino(transport) +logger.info('Hello') diff --git a/services/slides/node_modules/pino/test/fixtures/ts/transport-string-stdout.ts b/services/slides/node_modules/pino/test/fixtures/ts/transport-string-stdout.ts new file mode 100644 index 0000000000000000000000000000000000000000..0c9cfa7d7a62cfecbcbb5b03ad2196d1ef75b253 --- /dev/null +++ b/services/slides/node_modules/pino/test/fixtures/ts/transport-string-stdout.ts @@ -0,0 +1,8 @@ +import pino from '../../..' + +const transport = pino.transport({ + target: 'pino/file', + options: { destination: '1' } +}) +const logger = pino(transport) +logger.info('Hello') diff --git a/services/slides/node_modules/pino/test/fixtures/ts/transport-worker.ts b/services/slides/node_modules/pino/test/fixtures/ts/transport-worker.ts new file mode 100644 index 0000000000000000000000000000000000000000..80612919466d54fc7e88812bbcfe4b7b199e981a --- /dev/null +++ b/services/slides/node_modules/pino/test/fixtures/ts/transport-worker.ts @@ -0,0 +1,14 @@ +import { Writable } from 'node:stream' + +export default (): Writable => { + const myTransportStream = new Writable({ + autoDestroy: true, + write (chunk, _enc, cb) { + console.log(chunk.toString()) + cb() + }, + defaultEncoding: 'utf8' + }) + + return myTransportStream +} diff --git a/services/slides/node_modules/pino/test/formatters.test.js b/services/slides/node_modules/pino/test/formatters.test.js new file mode 100644 index 0000000000000000000000000000000000000000..bb7ae00c351a405eecb0305a599dd85b489c881d --- /dev/null +++ b/services/slides/node_modules/pino/test/formatters.test.js @@ -0,0 +1,364 @@ +'use strict' +/* eslint no-prototype-builtins: 0 */ + +const test = require('node:test') +const assert = require('node:assert') +const { hostname } = require('node:os') +const { join } = require('node:path') +const { readFile } = require('node:fs').promises +const tspl = require('@matteo.collina/tspl') + +const { sink, match, once, watchFileCreated, file } = require('./helper') +const pino = require('../') + +test('level formatter', async () => { + const stream = sink() + const logger = pino({ + formatters: { + level (label, number) { + return { + log: { + level: label + } + } + } + } + }, stream) + + const o = once(stream, 'data') + logger.info('hello world') + match(await o, { + log: { + level: 'info' + } + }) +}) + +test('bindings formatter', async () => { + const stream = sink() + const logger = pino({ + formatters: { + bindings (bindings) { + return { + process: { + pid: bindings.pid + }, + host: { + name: bindings.hostname + } + } + } + } + }, stream) + + const o = once(stream, 'data') + logger.info('hello world') + match(await o, { + process: { + pid: process.pid + }, + host: { + name: hostname() + } + }) +}) + +test('no bindings formatter', async () => { + const stream = sink() + const logger = pino({ + formatters: { + bindings (bindings) { + return null + } + } + }, stream) + + const o = once(stream, 'data') + logger.info('hello world') + const log = await o + assert.equal(log.hasOwnProperty('pid'), false) + assert.equal(log.hasOwnProperty('hostname'), false) + match(log, { msg: 'hello world' }) +}) + +test('log formatter', async (t) => { + const plan = tspl(t, { plan: 1 }) + const stream = sink() + const logger = pino({ + formatters: { + log (obj) { + plan.equal(obj.hasOwnProperty('msg'), false) + return { hello: 'world', ...obj } + } + } + }, stream) + + const o = once(stream, 'data') + logger.info({ foo: 'bar', nested: { object: true } }, 'hello world') + match(await o, { + hello: 'world', + foo: 'bar', + nested: { object: true } + }) + + await plan +}) + +test('Formatters combined', async () => { + const stream = sink() + const logger = pino({ + formatters: { + level (label, number) { + return { + log: { + level: label + } + } + }, + bindings (bindings) { + return { + process: { + pid: bindings.pid + }, + host: { + name: bindings.hostname + } + } + }, + log (obj) { + return { hello: 'world', ...obj } + } + } + }, stream) + + const o = once(stream, 'data') + logger.info({ foo: 'bar', nested: { object: true } }, 'hello world') + match(await o, { + log: { + level: 'info' + }, + process: { + pid: process.pid + }, + host: { + name: hostname() + }, + hello: 'world', + foo: 'bar', + nested: { object: true } + }) +}) + +test('Formatters in child logger', async () => { + const stream = sink() + const logger = pino({ + formatters: { + level (label, number) { + return { + log: { + level: label + } + } + }, + bindings (bindings) { + return { + process: { + pid: bindings.pid + }, + host: { + name: bindings.hostname + } + } + }, + log (obj) { + return { hello: 'world', ...obj } + } + } + }, stream) + + const child = logger.child({ + foo: 'bar', + nested: { object: true } + }, { + formatters: { + bindings (bindings) { + return { ...bindings, faz: 'baz' } + } + } + }) + + const o = once(stream, 'data') + child.info('hello world') + match(await o, { + log: { + level: 'info' + }, + process: { + pid: process.pid + }, + host: { + name: hostname() + }, + hello: 'world', + foo: 'bar', + nested: { object: true }, + faz: 'baz' + }) +}) + +test('Formatters without bindings in child logger', async () => { + const stream = sink() + const logger = pino({ + formatters: { + level (label, number) { + return { + log: { + level: label + } + } + }, + bindings (bindings) { + return { + process: { + pid: bindings.pid + }, + host: { + name: bindings.hostname + } + } + }, + log (obj) { + return { hello: 'world', ...obj } + } + } + }, stream) + + const child = logger.child({ + foo: 'bar', + nested: { object: true } + }, { + formatters: { + log (obj) { + return { other: 'stuff', ...obj } + } + } + }) + + const o = once(stream, 'data') + child.info('hello world') + match(await o, { + log: { + level: 'info' + }, + process: { + pid: process.pid + }, + host: { + name: hostname() + }, + foo: 'bar', + other: 'stuff', + nested: { object: true } + }) +}) + +test('elastic common schema format', async () => { + const stream = sink() + const ecs = { + formatters: { + level (label, number) { + return { + log: { + level: label, + logger: 'pino' + } + } + }, + bindings (bindings) { + return { + process: { + pid: bindings.pid + }, + host: { + name: bindings.hostname + } + } + }, + log (obj) { + return { ecs: { version: '1.4.0' }, ...obj } + } + }, + messageKey: 'message', + timestamp: () => `,"@timestamp":"${new Date(Date.now()).toISOString()}"` + } + + const logger = pino({ ...ecs }, stream) + + const o = once(stream, 'data') + logger.info({ foo: 'bar' }, 'hello world') + const log = await o + assert.equal(typeof log['@timestamp'], 'string') + match(log, { + log: { level: 'info', logger: 'pino' }, + process: { pid: process.pid }, + host: { name: hostname() }, + ecs: { version: '1.4.0' }, + foo: 'bar', + message: 'hello world' + }) +}) + +test('formatter with transport', async (t) => { + const plan = tspl(t, { plan: 1 }) + const destination = file() + const logger = pino({ + formatters: { + log (obj) { + plan.equal(obj.hasOwnProperty('msg'), false) + return { hello: 'world', ...obj } + } + }, + transport: { + targets: [ + { + target: join(__dirname, 'fixtures', 'to-file-transport.js'), + options: { destination } + } + ] + } + }) + + logger.info({ foo: 'bar', nested: { object: true } }, 'hello world') + await watchFileCreated(destination) + const result = JSON.parse(await readFile(destination)) + delete result.time + match(result, { + hello: 'world', + foo: 'bar', + nested: { object: true } + }) +}) + +test('throws when custom level formatter is used with transport.targets', async () => { + assert.throws( + () => { + pino({ + formatters: { + level (label) { + return label + } + }, + transport: { + targets: [ + { + target: 'pino/file', + options: { destination: 'foo.log' } + } + ] + } + } + ) + }, + Error('option.transport.targets do not allow custom level formatters') + ) +}) diff --git a/services/slides/node_modules/pino/test/helper.d.ts b/services/slides/node_modules/pino/test/helper.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..7c13101e891b0196752824e289f874445fbc3af4 --- /dev/null +++ b/services/slides/node_modules/pino/test/helper.d.ts @@ -0,0 +1,4 @@ +import type { PathLike } from 'node:fs' + +export declare function watchFileCreated (filename: PathLike): Promise +export declare function watchForWrite (filename: PathLike, testString: string): Promise diff --git a/services/slides/node_modules/pino/test/helper.js b/services/slides/node_modules/pino/test/helper.js new file mode 100644 index 0000000000000000000000000000000000000000..23c1803cb57ff9b2942296233d4a4e8a8a91c729 --- /dev/null +++ b/services/slides/node_modules/pino/test/helper.js @@ -0,0 +1,155 @@ +'use strict' + +const crypto = require('node:crypto') +const { join } = require('node:path') +const os = require('node:os') +const { existsSync, readFileSync, statSync, unlinkSync } = require('node:fs') +const writer = require('flush-write-stream') +const split = require('split2') + +const pid = process.pid +const hostname = os.hostname() +const { tmpdir } = os + +const isWin = process.platform === 'win32' +const isYarnPnp = process.versions.pnp !== undefined + +function getPathToNull () { + return isWin ? '\\\\.\\NUL' : '/dev/null' +} + +function once (emitter, name) { + return new Promise((resolve, reject) => { + if (name !== 'error') emitter.once('error', reject) + emitter.once(name, (...args) => { + emitter.removeListener('error', reject) + resolve(...args) + }) + }) +} + +function sink (func) { + const result = split((data) => { + try { + return JSON.parse(data) + } catch (err) { + console.log(err) + console.log(data) + } + }) + if (func) result.pipe(writer.obj(func)) + return result +} + +function check (is, chunk, level, msg) { + is(new Date(chunk.time) <= new Date(), true, 'time is greater than Date.now()') + delete chunk.time + is(chunk.pid, pid) + is(chunk.hostname, hostname) + is(chunk.level, level) + is(chunk.msg, msg) +} + +function sleep (ms) { + return new Promise((resolve) => { + setTimeout(resolve, ms) + }) +} + +function watchFileCreated (filename) { + return new Promise((resolve, reject) => { + const TIMEOUT = process.env.PINO_TEST_WAIT_WATCHFILE_TIMEOUT || 10000 + const INTERVAL = 100 + const threshold = TIMEOUT / INTERVAL + let counter = 0 + const interval = setInterval(() => { + const exists = existsSync(filename) + // On some CI runs file is created but not filled + if (exists && statSync(filename).size !== 0) { + clearInterval(interval) + resolve() + } else if (counter <= threshold) { + counter++ + } else { + clearInterval(interval) + reject(new Error( + `${filename} hasn't been created within ${TIMEOUT} ms. ` + + (exists ? 'File exist, but still empty.' : 'File not yet created.') + )) + } + }, INTERVAL) + }) +} + +function watchForWrite (filename, testString) { + return new Promise((resolve, reject) => { + const TIMEOUT = process.env.PINO_TEST_WAIT_WRITE_TIMEOUT || 10000 + const INTERVAL = 100 + const threshold = TIMEOUT / INTERVAL + let counter = 0 + const interval = setInterval(() => { + if (readFileSync(filename).includes(testString)) { + clearInterval(interval) + resolve() + } else if (counter <= threshold) { + counter++ + } else { + clearInterval(interval) + reject(new Error(`'${testString}' hasn't been written to ${filename} within ${TIMEOUT} ms.`)) + } + }, INTERVAL) + }) +} + +let files = [] + +function file () { + const hash = crypto.randomBytes(12).toString('hex') + const file = join(tmpdir(), `pino-${pid}-${hash}`) + files.push(file) + return file +} + +process.on('beforeExit', () => { + if (files.length === 0) return + for (const file of files) { + try { + unlinkSync(file) + } catch (e) { + } + } + files = [] +}) + +/** + * match is a bare-bones object shape matcher. We should be able to replace + * this with `assert.partialDeepStrictEqual` when v22 is our minimum. + * + * @param {object} found + * @param {object} expected + */ +function match (found, expected) { + for (const [key, value] of Object.entries(expected)) { + if (Object.prototype.toString.call(value) === '[object Object]') { + match(found[key], value) + continue + } + if (value !== found[key]) { + throw Error(`expected "${value}" but found "${found[key]}"`) + } + } +} + +module.exports = { + check, + file, + getPathToNull, + isWin, + isYarnPnp, + match, + once, + sink, + sleep, + watchFileCreated, + watchForWrite +} diff --git a/services/slides/node_modules/pino/test/hooks.test.js b/services/slides/node_modules/pino/test/hooks.test.js new file mode 100644 index 0000000000000000000000000000000000000000..a54df0347e261905dc8df27e2aa018ec8b878388 --- /dev/null +++ b/services/slides/node_modules/pino/test/hooks.test.js @@ -0,0 +1,114 @@ +'use strict' + +const { describe, test } = require('node:test') +const tspl = require('@matteo.collina/tspl') + +const { sink, match, once } = require('./helper') +const pino = require('../') + +describe('log method hook', () => { + test('gets invoked', async t => { + const plan = tspl(t, { plan: 7 }) + + const stream = sink() + const logger = pino({ + hooks: { + logMethod (args, method, level) { + plan.equal(Array.isArray(args), true) + plan.equal(typeof level, 'number') + plan.equal(args.length, 3) + plan.equal(level, this.levels.values.info) + plan.deepEqual(args, ['a', 'b', 'c']) + + plan.equal(typeof method, 'function') + plan.equal(method.name, 'LOG') + + method.apply(this, [args.join('-')]) + } + } + }, stream) + + const o = once(stream, 'data') + logger.info('a', 'b', 'c') + match(await o, { msg: 'a-b-c' }) + }) + + test('fatal method invokes hook', async t => { + const plan = tspl(t, { plan: 1 }) + + const stream = sink() + const logger = pino({ + hooks: { + logMethod (args, method) { + plan.ok(true) + method.apply(this, [args.join('-')]) + } + } + }, stream) + + const o = once(stream, 'data') + logger.fatal('a') + match(await o, { msg: 'a' }) + }) + + test('children get the hook', async t => { + const plan = tspl(t, { plan: 2 }) + + const stream = sink() + const root = pino({ + hooks: { + logMethod (args, method) { + plan.ok(true) + method.apply(this, [args.join('-')]) + } + } + }, stream) + const child = root.child({ child: 'one' }) + const grandchild = child.child({ child: 'two' }) + + let o = once(stream, 'data') + child.info('a', 'b') + match(await o, { msg: 'a-b' }) + + o = once(stream, 'data') + grandchild.info('c', 'd') + match(await o, { msg: 'c-d' }) + }) + + test('get log level', async t => { + const plan = tspl(t, { plan: 2 }) + + const stream = sink() + const logger = pino({ + hooks: { + logMethod (args, method, level) { + plan.equal(typeof level, 'number') + plan.equal(level, this.levels.values.error) + + method.apply(this, [args.join('-')]) + } + } + }, stream) + + const o = once(stream, 'data') + logger.error('a') + match(await o, { msg: 'a' }) + }) +}) + +describe('streamWrite hook', () => { + test('gets invoked', async () => { + const stream = sink() + const logger = pino({ + hooks: { + streamWrite (s) { + return s.replaceAll('redact-me', 'XXX') + } + } + }, stream) + + const o = once(stream, 'data') + logger.info('hide redact-me in this string') + match(await o, { msg: 'hide XXX in this string' }) + }) +}) diff --git a/services/slides/node_modules/pino/test/http.test.js b/services/slides/node_modules/pino/test/http.test.js new file mode 100644 index 0000000000000000000000000000000000000000..72b400b774a9bfce980f2f21ef203b1ef51c86e1 --- /dev/null +++ b/services/slides/node_modules/pino/test/http.test.js @@ -0,0 +1,214 @@ +'use strict' + +const test = require('node:test') +const http = require('node:http') +const os = require('node:os') +const tspl = require('@matteo.collina/tspl') + +const { sink, once } = require('./helper') +const pino = require('../') + +const { pid } = process +const hostname = os.hostname() + +test('http request support', async (t) => { + const plan = tspl(t, { plan: 3 }) + let originalReq + const instance = pino(sink((chunk, enc) => { + plan.ok(new Date(chunk.time) <= new Date(), 'time is greater than Date.now()') + delete chunk.time + plan.deepEqual(chunk, { + pid, + hostname, + level: 30, + msg: 'my request', + req: { + method: originalReq.method, + url: originalReq.url, + headers: originalReq.headers, + remoteAddress: originalReq.socket.remoteAddress, + remotePort: originalReq.socket.remotePort + } + }) + })) + + const server = http.createServer((req, res) => { + originalReq = req + instance.info(req, 'my request') + res.end('hello') + }) + server.unref() + server.listen() + const err = await once(server, 'listening') + plan.equal(err, undefined) + const res = await once(http.get('http://localhost:' + server.address().port), 'response') + res.resume() + server.close() + + await plan +}) + +test('http request support via serializer', async (t) => { + const plan = tspl(t, { plan: 3 }) + let originalReq + const instance = pino({ + serializers: { + req: pino.stdSerializers.req + } + }, sink((chunk, enc) => { + plan.ok(new Date(chunk.time) <= new Date(), 'time is greater than Date.now()') + delete chunk.time + plan.deepEqual(chunk, { + pid, + hostname, + level: 30, + msg: 'my request', + req: { + method: originalReq.method, + url: originalReq.url, + headers: originalReq.headers, + remoteAddress: originalReq.socket.remoteAddress, + remotePort: originalReq.socket.remotePort + } + }) + })) + + const server = http.createServer(function (req, res) { + originalReq = req + instance.info({ req }, 'my request') + res.end('hello') + }) + server.unref() + server.listen() + const err = await once(server, 'listening') + plan.equal(err, undefined) + + const res = await once(http.get('http://localhost:' + server.address().port), 'response') + res.resume() + server.close() + + await plan +}) + +test('http response support', async (t) => { + const plan = tspl(t, { plan: 3 }) + let originalRes + const instance = pino(sink((chunk, enc) => { + plan.ok(new Date(chunk.time) <= new Date(), 'time is greater than Date.now()') + delete chunk.time + plan.deepEqual(chunk, { + pid, + hostname, + level: 30, + msg: 'my response', + res: { + statusCode: originalRes.statusCode, + headers: originalRes.getHeaders() + } + }) + })) + + const server = http.createServer(function (req, res) { + originalRes = res + res.end('hello') + instance.info(res, 'my response') + }) + server.unref() + server.listen() + const err = await once(server, 'listening') + + plan.equal(err, undefined) + + const res = await once(http.get('http://localhost:' + server.address().port), 'response') + res.resume() + server.close() + + await plan +}) + +test('http response support via a serializer', async (t) => { + const plan = tspl(t, { plan: 3 }) + const instance = pino({ + serializers: { + res: pino.stdSerializers.res + } + }, sink((chunk, enc) => { + plan.ok(new Date(chunk.time) <= new Date(), 'time is greater than Date.now()') + delete chunk.time + plan.deepEqual(chunk, { + pid, + hostname, + level: 30, + msg: 'my response', + res: { + statusCode: 200, + headers: { + 'x-single': 'y', + 'x-multi': [1, 2] + } + } + }) + })) + + const server = http.createServer(function (req, res) { + res.setHeader('x-single', 'y') + res.setHeader('x-multi', [1, 2]) + res.end('hello') + instance.info({ res }, 'my response') + }) + + server.unref() + server.listen() + const err = await once(server, 'listening') + plan.equal(err, undefined) + + const res = await once(http.get('http://localhost:' + server.address().port), 'response') + res.resume() + server.close() + + await plan +}) + +test('http request support via serializer in a child', async (t) => { + const plan = tspl(t, { plan: 3 }) + let originalReq + const instance = pino({ + serializers: { + req: pino.stdSerializers.req + } + }, sink((chunk, enc) => { + plan.ok(new Date(chunk.time) <= new Date(), 'time is greater than Date.now()') + delete chunk.time + plan.deepEqual(chunk, { + pid, + hostname, + level: 30, + msg: 'my request', + req: { + method: originalReq.method, + url: originalReq.url, + headers: originalReq.headers, + remoteAddress: originalReq.socket.remoteAddress, + remotePort: originalReq.socket.remotePort + } + }) + })) + + const server = http.createServer(function (req, res) { + originalReq = req + const child = instance.child({ req }) + child.info('my request') + res.end('hello') + }) + + server.unref() + server.listen() + const err = await once(server, 'listening') + plan.equal(err, undefined) + + const res = await once(http.get('http://localhost:' + server.address().port), 'response') + res.resume() + server.close() + + await plan +}) diff --git a/services/slides/node_modules/pino/test/internals/version.test.js b/services/slides/node_modules/pino/test/internals/version.test.js new file mode 100644 index 0000000000000000000000000000000000000000..8d260bd09c56d49b292aaffe501582a890fc48fb --- /dev/null +++ b/services/slides/node_modules/pino/test/internals/version.test.js @@ -0,0 +1,17 @@ +'use strict' + +const test = require('node:test') +const assert = require('node:assert') +const fs = require('node:fs') +const path = require('node:path') + +const pino = require('../..')() + +test('should be the same as package.json', () => { + const json = JSON.parse( + fs.readFileSync(path.join(__dirname, '..', '..', 'package.json')) + .toString('utf8') + ) + + assert.equal(pino.version, json.version) +}) diff --git a/services/slides/node_modules/pino/test/is-level-enabled.test.js b/services/slides/node_modules/pino/test/is-level-enabled.test.js new file mode 100644 index 0000000000000000000000000000000000000000..d55bfaf13206d6b5687e8e3646c6bb33456e8cfa --- /dev/null +++ b/services/slides/node_modules/pino/test/is-level-enabled.test.js @@ -0,0 +1,179 @@ +'use strict' + +const { describe, test } = require('node:test') +const assert = require('node:assert') + +const pino = require('../') + +const descLevels = { + trace: 60, + debug: 50, + info: 40, + warn: 30, + error: 20, + fatal: 10 +} + +const ascLevels = { + trace: 10, + debug: 20, + info: 30, + warn: 40, + error: 50, + fatal: 60 +} + +describe('Default levels suite', () => { + test('can check if current level enabled', async () => { + const log = pino({ level: 'debug' }) + assert.equal(true, log.isLevelEnabled('debug')) + }) + + test('can check if level enabled after level set', async () => { + const log = pino() + assert.equal(false, log.isLevelEnabled('debug')) + log.level = 'debug' + assert.equal(true, log.isLevelEnabled('debug')) + }) + + test('can check if higher level enabled', async () => { + const log = pino({ level: 'debug' }) + assert.equal(true, log.isLevelEnabled('error')) + }) + + test('can check if lower level is disabled', async () => { + const log = pino({ level: 'error' }) + assert.equal(false, log.isLevelEnabled('trace')) + }) + + test('ASC: can check if child has current level enabled', async () => { + const log = pino().child({}, { level: 'debug' }) + assert.equal(true, log.isLevelEnabled('debug')) + assert.equal(true, log.isLevelEnabled('error')) + assert.equal(false, log.isLevelEnabled('trace')) + }) + + test('can check if custom level is enabled', async () => { + const log = pino({ + customLevels: { foo: 35 }, + level: 'debug' + }) + assert.equal(true, log.isLevelEnabled('foo')) + assert.equal(true, log.isLevelEnabled('error')) + assert.equal(false, log.isLevelEnabled('trace')) + }) +}) + +describe('Ascending levels suite', () => { + const customLevels = ascLevels + const levelComparison = 'ASC' + + test('can check if current level enabled', async () => { + const log = pino({ level: 'debug', levelComparison, customLevels, useOnlyCustomLevels: true }) + assert.equal(true, log.isLevelEnabled('debug')) + }) + + test('can check if level enabled after level set', async () => { + const log = pino({ levelComparison, customLevels, useOnlyCustomLevels: true }) + assert.equal(false, log.isLevelEnabled('debug')) + log.level = 'debug' + assert.equal(true, log.isLevelEnabled('debug')) + }) + + test('can check if higher level enabled', async () => { + const log = pino({ level: 'debug', levelComparison, customLevels, useOnlyCustomLevels: true }) + assert.equal(true, log.isLevelEnabled('error')) + }) + + test('can check if lower level is disabled', async () => { + const log = pino({ level: 'error', customLevels, useOnlyCustomLevels: true }) + assert.equal(false, log.isLevelEnabled('trace')) + }) + + test('can check if child has current level enabled', async () => { + const log = pino().child({ levelComparison, customLevels, useOnlyCustomLevels: true }, { level: 'debug' }) + assert.equal(true, log.isLevelEnabled('debug')) + assert.equal(true, log.isLevelEnabled('error')) + assert.equal(false, log.isLevelEnabled('trace')) + }) + + test('can check if custom level is enabled', async () => { + const log = pino({ + levelComparison, + useOnlyCustomLevels: true, + customLevels: { foo: 35, ...customLevels }, + level: 'debug' + }) + assert.equal(true, log.isLevelEnabled('foo')) + assert.equal(true, log.isLevelEnabled('error')) + assert.equal(false, log.isLevelEnabled('trace')) + }) +}) + +describe('Descending levels suite', () => { + const customLevels = descLevels + const levelComparison = 'DESC' + + test('can check if current level enabled', async () => { + const log = pino({ level: 'debug', levelComparison, customLevels, useOnlyCustomLevels: true }) + assert.equal(true, log.isLevelEnabled('debug')) + }) + + test('can check if level enabled after level set', async () => { + const log = pino({ levelComparison, customLevels, useOnlyCustomLevels: true }) + assert.equal(false, log.isLevelEnabled('debug')) + log.level = 'debug' + assert.equal(true, log.isLevelEnabled('debug')) + }) + + test('can check if higher level enabled', async () => { + const log = pino({ level: 'debug', levelComparison, customLevels, useOnlyCustomLevels: true }) + assert.equal(true, log.isLevelEnabled('error')) + }) + + test('can check if lower level is disabled', async () => { + const log = pino({ level: 'error', levelComparison, customLevels, useOnlyCustomLevels: true }) + assert.equal(false, log.isLevelEnabled('trace')) + }) + + test('can check if child has current level enabled', async () => { + const log = pino({ levelComparison, customLevels, useOnlyCustomLevels: true }).child({}, { level: 'debug' }) + assert.equal(true, log.isLevelEnabled('debug')) + assert.equal(true, log.isLevelEnabled('error')) + assert.equal(false, log.isLevelEnabled('trace')) + }) + + test('can check if custom level is enabled', async () => { + const log = pino({ + levelComparison, + customLevels: { foo: 35, ...customLevels }, + useOnlyCustomLevels: true, + level: 'debug' + }) + assert.equal(true, log.isLevelEnabled('foo')) + assert.equal(true, log.isLevelEnabled('error')) + assert.equal(false, log.isLevelEnabled('trace')) + }) +}) + +describe('Custom levels comparison', () => { + test('Custom comparison returns true cause level is enabled', async () => { + const log = pino({ level: 'error', levelComparison: () => true }) + assert.equal(true, log.isLevelEnabled('debug')) + }) + + test('Custom comparison returns false cause level is disabled', async () => { + const log = pino({ level: 'error', levelComparison: () => false }) + assert.equal(false, log.isLevelEnabled('debug')) + }) + + test('Custom comparison returns true cause child level is enabled', async () => { + const log = pino({ levelComparison: () => true }).child({ level: 'error' }) + assert.equal(true, log.isLevelEnabled('debug')) + }) + + test('Custom comparison returns false cause child level is disabled', async () => { + const log = pino({ levelComparison: () => false }).child({ level: 'error' }) + assert.equal(false, log.isLevelEnabled('debug')) + }) +}) diff --git a/services/slides/node_modules/pino/test/jest/basic.spec.js b/services/slides/node_modules/pino/test/jest/basic.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..46f381b2e7021602eac98b3ed86fdcae573bfcd1 --- /dev/null +++ b/services/slides/node_modules/pino/test/jest/basic.spec.js @@ -0,0 +1,10 @@ +/* global test */ +const pino = require('../../pino') + +test('transport should work in jest', function () { + pino({ + transport: { + target: 'pino-pretty' + } + }) +}) diff --git a/services/slides/node_modules/pino/test/levels.test.js b/services/slides/node_modules/pino/test/levels.test.js new file mode 100644 index 0000000000000000000000000000000000000000..904aef125ac95280120d317426719e596d79447a --- /dev/null +++ b/services/slides/node_modules/pino/test/levels.test.js @@ -0,0 +1,810 @@ +'use strict' + +const { describe, test } = require('node:test') +const assert = require('node:assert') +const tspl = require('@matteo.collina/tspl') + +const { sink, once, check } = require('./helper') +const pino = require('../') + +const levelsLib = require('../lib/levels') + +// Silence all warnings for this test +process.removeAllListeners('warning') +process.on('warning', () => {}) + +test('set the level by string', async () => { + const expected = [{ + level: 50, + msg: 'this is an error' + }, { + level: 60, + msg: 'this is fatal' + }] + const stream = sink() + const instance = pino(stream) + instance.level = 'error' + instance.info('hello world') + instance.error('this is an error') + instance.fatal('this is fatal') + const result = await once(stream, 'data') + const current = expected.shift() + check(assert.equal, result, current.level, current.msg) +}) + +test('the wrong level throws', async () => { + const instance = pino() + assert.throws(() => { + instance.level = 'kaboom' + }) +}) + +test('set the level by number', async () => { + const expected = [{ + level: 50, + msg: 'this is an error' + }, { + level: 60, + msg: 'this is fatal' + }] + const stream = sink() + const instance = pino(stream) + + instance.level = 50 + instance.info('hello world') + instance.error('this is an error') + instance.fatal('this is fatal') + const result = await once(stream, 'data') + const current = expected.shift() + check(assert.equal, result, current.level, current.msg) +}) + +test('exposes level string mappings', async () => { + assert.equal(pino.levels.values.error, 50) +}) + +test('exposes level number mappings', async () => { + assert.equal(pino.levels.labels[50], 'error') +}) + +test('returns level integer', async () => { + const instance = pino({ level: 'error' }) + assert.equal(instance.levelVal, 50) +}) + +test('child returns level integer', async () => { + const parent = pino({ level: 'error' }) + const child = parent.child({ foo: 'bar' }) + assert.equal(child.levelVal, 50) +}) + +test('set the level via exported pino function', async () => { + const expected = [{ + level: 50, + msg: 'this is an error' + }, { + level: 60, + msg: 'this is fatal' + }] + const stream = sink() + const instance = pino({ level: 'error' }, stream) + + instance.info('hello world') + instance.error('this is an error') + instance.fatal('this is fatal') + const result = await once(stream, 'data') + const current = expected.shift() + check(assert.equal, result, current.level, current.msg) +}) + +test('level-change event', async (t) => { + const plan = tspl(t, { plan: 8 }) + const instance = pino() + function handle (lvl, val, prevLvl, prevVal, logger) { + plan.equal(lvl, 'trace') + plan.equal(val, 10) + plan.equal(prevLvl, 'info') + plan.equal(prevVal, 30) + plan.equal(logger, instance) + } + instance.on('level-change', handle) + instance.level = 'trace' + instance.removeListener('level-change', handle) + instance.level = 'info' + + let count = 0 + + const l1 = () => count++ + const l2 = () => count++ + const l3 = () => count++ + instance.on('level-change', l1) + instance.on('level-change', l2) + instance.on('level-change', l3) + + instance.level = 'trace' + instance.removeListener('level-change', l3) + instance.level = 'fatal' + instance.removeListener('level-change', l1) + instance.level = 'debug' + instance.removeListener('level-change', l2) + instance.level = 'info' + + plan.equal(count, 6) + + instance.once('level-change', (lvl, val, prevLvl, prevVal, logger) => plan.equal(logger, instance)) + instance.level = 'info' + const child = instance.child({}) + instance.once('level-change', (lvl, val, prevLvl, prevVal, logger) => plan.equal(logger, child)) + child.level = 'trace' + + await plan +}) + +test('enable', async (t) => { + const instance = pino({ + level: 'trace', + enabled: false + }, sink((result, enc) => { + throw Error('no data should be logged') + })) + + Object.keys(pino.levels.values).forEach((level) => { + instance[level]('hello world') + }) +}) + +test('silent level', async () => { + const instance = pino({ + level: 'silent' + }, sink((result, enc) => { + throw Error('no data should be logged') + })) + + Object.keys(pino.levels.values).forEach((level) => { + instance[level]('hello world') + }) +}) + +test('set silent via Infinity', async () => { + const instance = pino({ + level: Infinity + }, sink((result, enc) => { + throw Error('no data should be logged') + })) + + Object.keys(pino.levels.values).forEach((level) => { + instance[level]('hello world') + }) +}) + +test('exposed levels', async () => { + assert.deepEqual(Object.keys(pino.levels.values), [ + 'trace', + 'debug', + 'info', + 'warn', + 'error', + 'fatal' + ]) +}) + +test('exposed labels', async () => { + assert.deepEqual(Object.keys(pino.levels.labels), [ + '10', + '20', + '30', + '40', + '50', + '60' + ]) +}) + +test('setting level in child', async (t) => { + const plan = tspl(t, { plan: 10 }) + const expected = [{ + level: 50, + msg: 'this is an error' + }, { + level: 60, + msg: 'this is fatal' + }] + const instance = pino(sink((result, enc, cb) => { + const current = expected.shift() + check(plan.equal, result, current.level, current.msg) + cb() + })).child({ level: 30 }) + + instance.level = 'error' + instance.info('hello world') + instance.error('this is an error') + instance.fatal('this is fatal') + + await plan +}) + +test('setting level by assigning a number to level', async () => { + const instance = pino() + assert.equal(instance.levelVal, 30) + assert.equal(instance.level, 'info') + instance.level = 50 + assert.equal(instance.levelVal, 50) + assert.equal(instance.level, 'error') +}) + +test('setting level by number to unknown value results in a throw', async () => { + const instance = pino() + assert.throws(() => { instance.level = 973 }) +}) + +test('setting level by assigning a known label to level', async () => { + const instance = pino() + assert.equal(instance.levelVal, 30) + assert.equal(instance.level, 'info') + instance.level = 'error' + assert.equal(instance.levelVal, 50) + assert.equal(instance.level, 'error') +}) + +test('levelVal is read only', async () => { + const instance = pino() + assert.throws(() => { instance.levelVal = 20 }) +}) + +test('produces labels when told to', async (t) => { + const plan = tspl(t, { plan: 5 }) + const expected = [{ + level: 'info', + msg: 'hello world' + }] + const instance = pino({ + formatters: { + level (label, number) { + return { level: label } + } + } + }, sink((result, enc, cb) => { + const current = expected.shift() + check(plan.equal, result, current.level, current.msg) + cb() + })) + + instance.info('hello world') + + await plan +}) + +test('resets levels from labels to numbers', async (t) => { + const plan = tspl(t, { plan: 5 }) + const expected = [{ + level: 30, + msg: 'hello world' + }] + pino({ useLevelLabels: true }) + const instance = pino({ useLevelLabels: false }, sink((result, enc, cb) => { + const current = expected.shift() + check(plan.equal, result, current.level, current.msg) + cb() + })) + + instance.info('hello world') + + await plan +}) + +test('changes label naming when told to', async (t) => { + const plan = tspl(t, { plan: 2 }) + const expected = [{ + priority: 30, + msg: 'hello world' + }] + const instance = pino({ + formatters: { + level (label, number) { + return { priority: number } + } + } + }, sink((result, enc, cb) => { + const current = expected.shift() + plan.equal(result.priority, current.priority) + plan.equal(result.msg, current.msg) + cb() + })) + + instance.info('hello world') + + await plan +}) + +test('children produce labels when told to', async (t) => { + const plan = tspl(t, { plan: 10 }) + const expected = [ + { + level: 'info', + msg: 'child 1' + }, + { + level: 'info', + msg: 'child 2' + } + ] + const instance = pino({ + formatters: { + level (label, number) { + return { level: label } + } + } + }, sink((result, enc, cb) => { + const current = expected.shift() + check(plan.equal, result, current.level, current.msg) + cb() + })) + + const child1 = instance.child({ name: 'child1' }) + const child2 = child1.child({ name: 'child2' }) + + child1.info('child 1') + child2.info('child 2') + + await plan +}) + +test('produces labels for custom levels', async (t) => { + const plan = tspl(t, { plan: 10 }) + const expected = [ + { + level: 'info', + msg: 'hello world' + }, + { + level: 'foo', + msg: 'foobar' + } + ] + const opts = { + formatters: { + level (label, number) { + return { level: label } + } + }, + customLevels: { + foo: 35 + } + } + const instance = pino(opts, sink((result, enc, cb) => { + const current = expected.shift() + check(plan.equal, result, current.level, current.msg) + cb() + })) + + instance.info('hello world') + instance.foo('foobar') + + await plan +}) + +test('setting levelKey does not affect labels when told to', async (t) => { + const plan = tspl(t, { plan: 1 }) + const instance = pino( + { + formatters: { + level (label, number) { + return { priority: label } + } + } + }, + sink((result, enc, cb) => { + plan.equal(result.priority, 'info') + cb() + }) + ) + + instance.info('hello world') + + await plan +}) + +test('throws when creating a default label that does not exist in logger levels', async () => { + const defaultLevel = 'foo' + assert.throws( + () => { + pino({ + customLevels: { + bar: 5 + }, + level: defaultLevel + }) + }, + Error(`default level:${defaultLevel} must be included in custom levels`) + ) +}) + +test('throws when creating a default value that does not exist in logger levels', async () => { + const defaultLevel = 15 + assert.throws( + () => { + pino({ + customLevels: { + bar: 5 + }, + level: defaultLevel + }) + }, + Error(`default level:${defaultLevel} must be included in custom levels`) + ) +}) + +test('throws when creating a default value that does not exist in logger levels', async ({ equal, throws }) => { + assert.throws( + () => { + pino({ + customLevels: { + foo: 5 + }, + useOnlyCustomLevels: true + }) + }, + /default level:info must be included in custom levels/ + ) +}) + +test('passes when creating a default value that exists in logger levels', async () => { + pino({ + level: 30 + }) +}) + +test('log null value when message is null', async () => { + const expected = { + msg: null, + level: 30 + } + + const stream = sink() + const instance = pino(stream) + instance.level = 'info' + instance.info(null) + + const result = await once(stream, 'data') + check(assert.equal, result, expected.level, expected.msg) +}) + +test('formats when base param is null', async () => { + const expected = { + msg: 'a string', + level: 30 + } + + const stream = sink() + const instance = pino(stream) + instance.level = 'info' + instance.info(null, 'a %s', 'string') + + const result = await once(stream, 'data') + check(assert.equal, result, expected.level, expected.msg) +}) + +test('fatal method sync-flushes the destination if sync flushing is available', async (t) => { + const plan = tspl(t, { plan: 2 }) + const stream = sink() + stream.flushSync = () => { + plan.ok('destination flushed') + } + const instance = pino(stream) + instance.fatal('this is fatal') + await once(stream, 'data') + plan.doesNotThrow(() => { + stream.flushSync = undefined + instance.fatal('this is fatal') + }) + + await plan +}) + +test('fatal method should call async when sync-flushing fails', async (t) => { + const plan = tspl(t, { plan: 1 }) + const messages = [ + 'this is fatal 1' + ] + const stream = sink((result) => assert.equal(result.msg, messages.shift())) + stream.flushSync = () => { throw new Error('Error') } + stream.flush = () => { throw Error('flush should be called') } + + const instance = pino(stream) + plan.doesNotThrow(() => instance.fatal(messages[0])) + + await plan +}) + +test('calling silent method on logger instance', async () => { + const instance = pino({ level: 'silent' }, sink((result, enc) => { + throw Error('no data should be logged') + })) + instance.silent('hello world') +}) + +test('calling silent method on child logger', async () => { + const child = pino({ level: 'silent' }, sink((result, enc) => { + throw Error('no data should be logged') + })).child({}) + child.silent('hello world') +}) + +test('changing level from info to silent and back to info', async () => { + const expected = { + level: 30, + msg: 'hello world' + } + const stream = sink() + const instance = pino({ level: 'info' }, stream) + + instance.level = 'silent' + instance.info('hello world') + let result = stream.read() + assert.equal(result, null) + + instance.level = 'info' + instance.info('hello world') + result = await once(stream, 'data') + check(assert.equal, result, expected.level, expected.msg) +}) + +test('changing level from info to silent and back to info in child logger', async () => { + const expected = { + level: 30, + msg: 'hello world' + } + const stream = sink() + const child = pino({ level: 'info' }, stream).child({}) + + child.level = 'silent' + child.info('hello world') + let result = stream.read() + assert.equal(result, null) + + child.level = 'info' + child.info('hello world') + result = await once(stream, 'data') + check(assert.equal, result, expected.level, expected.msg) +}) + +describe('changing level respects level comparison set to', () => { + const ascLevels = { + debug: 1, + info: 2, + warn: 3 + } + + const descLevels = { + debug: 3, + info: 2, + warn: 1 + } + + const expected = { + level: 2, + msg: 'hello world' + } + + test('ASC in parent logger', async () => { + const customLevels = ascLevels + const levelComparison = 'ASC' + + const stream = sink() + const logger = pino({ levelComparison, customLevels, useOnlyCustomLevels: true, level: 'info' }, stream) + + logger.level = 'warn' + logger.info('hello world') + let result = stream.read() + assert.equal(result, null) + + logger.level = 'debug' + logger.info('hello world') + result = await once(stream, 'data') + check(assert.equal, result, expected.level, expected.msg) + }) + + test('DESC in parent logger', async () => { + const customLevels = descLevels + const levelComparison = 'DESC' + + const stream = sink() + const logger = pino({ levelComparison, customLevels, useOnlyCustomLevels: true, level: 'info' }, stream) + + logger.level = 'warn' + logger.info('hello world') + let result = stream.read() + assert.equal(result, null) + + logger.level = 'debug' + logger.info('hello world') + result = await once(stream, 'data') + check(assert.equal, result, expected.level, expected.msg) + }) + + test('custom function in parent logger', async () => { + const customLevels = { + info: 2, + debug: 345, + warn: 789 + } + const levelComparison = (current, expected) => { + if (expected === customLevels.warn) return false + return true + } + + const stream = sink() + const logger = pino({ levelComparison, customLevels, useOnlyCustomLevels: true, level: 'info' }, stream) + + logger.level = 'warn' + logger.info('hello world') + let result = stream.read() + assert.equal(result, null) + + logger.level = 'debug' + logger.info('hello world') + result = await once(stream, 'data') + check(assert.equal, result, expected.level, expected.msg) + }) + + test('ASC in child logger', async () => { + const customLevels = ascLevels + const levelComparison = 'ASC' + + const stream = sink() + const logger = pino({ levelComparison, customLevels, useOnlyCustomLevels: true, level: 'info' }, stream).child({ }) + + logger.level = 'warn' + logger.info('hello world') + let result = stream.read() + assert.equal(result, null) + + logger.level = 'debug' + logger.info('hello world') + result = await once(stream, 'data') + check(assert.equal, result, expected.level, expected.msg) + }) + + test('DESC in parent logger', async () => { + const customLevels = descLevels + const levelComparison = 'DESC' + + const stream = sink() + const logger = pino({ levelComparison, customLevels, useOnlyCustomLevels: true, level: 'info' }, stream).child({ }) + + logger.level = 'warn' + logger.info('hello world') + let result = stream.read() + assert.equal(result, null) + + logger.level = 'debug' + logger.info('hello world') + result = await once(stream, 'data') + check(assert.equal, result, expected.level, expected.msg) + }) + + test('custom function in child logger', async () => { + const customLevels = { + info: 2, + debug: 345, + warn: 789 + } + const levelComparison = (current, expected) => { + if (expected === customLevels.warn) return false + return true + } + + const stream = sink() + const logger = pino({ levelComparison, customLevels, useOnlyCustomLevels: true, level: 'info' }, stream).child({ }) + + logger.level = 'warn' + logger.info('hello world') + let result = stream.read() + assert.equal(result, null) + + logger.level = 'debug' + logger.info('hello world') + result = await once(stream, 'data') + check(assert.equal, result, expected.level, expected.msg) + }) +}) + +test('changing level respects level comparison DESC', async () => { + const customLevels = { + warn: 1, + info: 2, + debug: 3 + } + + const levelComparison = 'DESC' + + const expected = { + level: 2, + msg: 'hello world' + } + + const stream = sink() + const logger = pino({ levelComparison, customLevels, useOnlyCustomLevels: true, level: 'info' }, stream) + + logger.level = 'warn' + logger.info('hello world') + let result = stream.read() + assert.equal(result, null) + + logger.level = 'debug' + logger.info('hello world') + result = await once(stream, 'data') + check(assert.equal, result, expected.level, expected.msg) +}) + +// testing for potential loss of Pino constructor scope from serializers - an edge case with circular refs see: https://github.com/pinojs/pino/issues/833 +test('trying to get levels when `this` is no longer a Pino instance returns an empty string', async () => { + const notPinoInstance = { some: 'object', getLevel: levelsLib.getLevel } + const blankedLevelValue = notPinoInstance.getLevel() + assert.equal(blankedLevelValue, '') +}) + +test('accepts capital letter for INFO level', async () => { + const stream = sink() + const logger = pino({ + level: 'INFO' + }, stream) + + logger.info('test') + const { level } = await once(stream, 'data') + assert.equal(level, 30) +}) + +test('accepts capital letter for FATAL level', async () => { + const stream = sink() + const logger = pino({ + level: 'FATAL' + }, stream) + + logger.fatal('test') + const { level } = await once(stream, 'data') + assert.equal(level, 60) +}) + +test('accepts capital letter for ERROR level', async () => { + const stream = sink() + const logger = pino({ + level: 'ERROR' + }, stream) + + logger.error('test') + const { level } = await once(stream, 'data') + assert.equal(level, 50) +}) + +test('accepts capital letter for WARN level', async () => { + const stream = sink() + const logger = pino({ + level: 'WARN' + }, stream) + + logger.warn('test') + const { level } = await once(stream, 'data') + assert.equal(level, 40) +}) + +test('accepts capital letter for DEBUG level', async () => { + const stream = sink() + const logger = pino({ + level: 'DEBUG' + }, stream) + + logger.debug('test') + const { level } = await once(stream, 'data') + assert.equal(level, 20) +}) + +test('accepts capital letter for TRACE level', async () => { + const stream = sink() + const logger = pino({ + level: 'TRACE' + }, stream) + + logger.trace('test') + const { level } = await once(stream, 'data') + assert.equal(level, 10) +}) diff --git a/services/slides/node_modules/pino/test/metadata.test.js b/services/slides/node_modules/pino/test/metadata.test.js new file mode 100644 index 0000000000000000000000000000000000000000..f4f088be68e94a4bf4dc38743323f3cdc86c11ab --- /dev/null +++ b/services/slides/node_modules/pino/test/metadata.test.js @@ -0,0 +1,120 @@ +'use strict' + +const test = require('node:test') +const os = require('node:os') +const tspl = require('@matteo.collina/tspl') + +const pino = require('../') + +const { pid } = process +const hostname = os.hostname() + +test('metadata works', async (t) => { + const plan = tspl(t, { plan: 7 }) + const now = Date.now() + const instance = pino({}, { + [Symbol.for('pino.metadata')]: true, + write (chunk) { + plan.equal(instance, this.lastLogger) + plan.equal(30, this.lastLevel) + plan.equal('a msg', this.lastMsg) + plan.ok(Number(this.lastTime) >= now) + plan.deepEqual(this.lastObj, { hello: 'world' }) + const result = JSON.parse(chunk) + plan.ok(new Date(result.time) <= new Date(), 'time is greater than Date.now()') + delete result.time + plan.deepEqual(result, { + pid, + hostname, + level: 30, + hello: 'world', + msg: 'a msg' + }) + } + }) + + instance.info({ hello: 'world' }, 'a msg') + + await plan +}) + +test('child loggers works', async (t) => { + const plan = tspl(t, { plan: 6 }) + const instance = pino({}, { + [Symbol.for('pino.metadata')]: true, + write (chunk) { + plan.equal(child, this.lastLogger) + plan.equal(30, this.lastLevel) + plan.equal('a msg', this.lastMsg) + plan.deepEqual(this.lastObj, { from: 'child' }) + const result = JSON.parse(chunk) + plan.ok(new Date(result.time) <= new Date(), 'time is greater than Date.now()') + delete result.time + plan.deepEqual(result, { + pid, + hostname, + level: 30, + hello: 'world', + from: 'child', + msg: 'a msg' + }) + } + }) + + const child = instance.child({ hello: 'world' }) + child.info({ from: 'child' }, 'a msg') + + await plan +}) + +test('without object', async (t) => { + const plan = tspl(t, { plan: 6 }) + const instance = pino({}, { + [Symbol.for('pino.metadata')]: true, + write (chunk) { + plan.equal(instance, this.lastLogger) + plan.equal(30, this.lastLevel) + plan.equal('a msg', this.lastMsg) + plan.deepEqual({ }, this.lastObj) + const result = JSON.parse(chunk) + plan.ok(new Date(result.time) <= new Date(), 'time is greater than Date.now()') + delete result.time + plan.deepEqual(result, { + pid, + hostname, + level: 30, + msg: 'a msg' + }) + } + }) + + instance.info('a msg') + + await plan +}) + +test('without msg', async (t) => { + const plan = tspl(t, { plan: 6 }) + const instance = pino({}, { + [Symbol.for('pino.metadata')]: true, + write (chunk) { + plan.equal(instance, this.lastLogger) + plan.equal(30, this.lastLevel) + plan.equal(undefined, this.lastMsg) + plan.deepEqual({ hello: 'world' }, this.lastObj) + const result = JSON.parse(chunk) + plan.ok(new Date(result.time) <= new Date(), 'time is greater than Date.now()') + delete result.time + plan.deepEqual(result, { + pid, + hostname, + level: 30, + hello: 'world' + }) + } + }) + + instance.info({ hello: 'world' }) + + await plan +}) diff --git a/services/slides/node_modules/pino/test/mixin-merge-strategy.test.js b/services/slides/node_modules/pino/test/mixin-merge-strategy.test.js new file mode 100644 index 0000000000000000000000000000000000000000..d78cbe5faed7dbb5fb5eaf63a5d2644b86f1c415 --- /dev/null +++ b/services/slides/node_modules/pino/test/mixin-merge-strategy.test.js @@ -0,0 +1,57 @@ +'use strict' + +const test = require('node:test') +const assert = require('node:assert') + +const { sink, once } = require('./helper') +const pino = require('../') + +const level = 50 +const name = 'error' + +test('default merge strategy', async () => { + const stream = sink() + const instance = pino({ + base: {}, + mixin () { + return { tag: 'k8s' } + } + }, stream) + instance.level = name + instance[name]({ + tag: 'local' + }, 'test') + const result = await once(stream, 'data') + assert.ok(new Date(result.time) <= new Date(), 'time is greater than Date.now()') + delete result.time + assert.deepEqual(result, { + level, + msg: 'test', + tag: 'local' + }) +}) + +test('custom merge strategy with mixin priority', async () => { + const stream = sink() + const instance = pino({ + base: {}, + mixin () { + return { tag: 'k8s' } + }, + mixinMergeStrategy (mergeObject, mixinObject) { + return Object.assign(mergeObject, mixinObject) + } + }, stream) + instance.level = name + instance[name]({ + tag: 'local' + }, 'test') + const result = await once(stream, 'data') + assert.ok(new Date(result.time) <= new Date(), 'time is greater than Date.now()') + delete result.time + assert.deepEqual(result, { + level, + msg: 'test', + tag: 'k8s' + }) +}) diff --git a/services/slides/node_modules/pino/test/mixin.test.js b/services/slides/node_modules/pino/test/mixin.test.js new file mode 100644 index 0000000000000000000000000000000000000000..b944683e820b7244f5d1a8262ca4f1e742b99982 --- /dev/null +++ b/services/slides/node_modules/pino/test/mixin.test.js @@ -0,0 +1,241 @@ +'use strict' + +const test = require('node:test') +const assert = require('node:assert') +const os = require('node:os') +const tspl = require('@matteo.collina/tspl') + +const { sink, once } = require('./helper') +const pino = require('../') + +const { pid } = process +const hostname = os.hostname() +const level = 50 +const name = 'error' + +test('mixin object is included', async () => { + let n = 0 + const stream = sink() + const instance = pino({ + mixin () { + return { hello: ++n } + } + }, stream) + instance.level = name + instance[name]('test') + const result = await once(stream, 'data') + assert.ok(new Date(result.time) <= new Date(), 'time is greater than Date.now()') + delete result.time + assert.deepEqual(result, { + pid, + hostname, + level, + msg: 'test', + hello: 1 + }) +}) + +test('mixin object is new every time', async (t) => { + const plan = tspl(t, { plan: 6 }) + + let n = 0 + const stream = sink() + const instance = pino({ + mixin () { + return { hello: n } + } + }, stream) + instance.level = name + + while (++n < 4) { + const msg = `test #${n}` + stream.pause() + instance[name](msg) + stream.resume() + const result = await once(stream, 'data') + plan.ok(new Date(result.time) <= new Date(), 'time is greater than Date.now()') + delete result.time + plan.deepEqual(result, { + pid, + hostname, + level, + msg, + hello: n + }) + } + + await plan +}) + +test('mixin object is not called if below log level', async () => { + const stream = sink() + const instance = pino({ + mixin () { + throw Error('should not call mixin function') + } + }, stream) + instance.level = 'error' + instance.info('test') +}) + +test('mixin object + logged object', async () => { + const stream = sink() + const instance = pino({ + mixin () { + return { foo: 1, bar: 2 } + } + }, stream) + instance.level = name + instance[name]({ bar: 3, baz: 4 }) + const result = await once(stream, 'data') + assert.ok(new Date(result.time) <= new Date(), 'time is greater than Date.now()') + delete result.time + assert.deepEqual(result, { + pid, + hostname, + level, + foo: 1, + bar: 3, + baz: 4 + }) +}) + +test('mixin not a function', async () => { + const stream = sink() + assert.throws(function () { + pino({ mixin: 'not a function' }, stream) + }) +}) + +test('mixin can use context', async (t) => { + const plan = tspl(t, { plan: 3 }) + const stream = sink() + const instance = pino({ + mixin (context) { + plan.ok(context !== null, 'context should be defined') + plan.ok(context !== undefined, 'context should be defined') + plan.deepEqual(context, { + message: '123', + stack: 'stack' + }) + return Object.assign({ + error: context.message, + stack: context.stack + }) + } + }, stream) + instance.level = name + instance[name]({ + message: '123', + stack: 'stack' + }, 'test') + + await plan +}) + +test('mixin works without context', async (t) => { + const plan = tspl(t, { plan: 3 }) + const stream = sink() + const instance = pino({ + mixin (context) { + plan.ok(context !== null, 'context is still defined w/o passing mergeObject') + plan.ok(context !== undefined, 'context is still defined w/o passing mergeObject') + plan.deepEqual(context, {}) + return { + something: true + } + } + }, stream) + instance.level = name + instance[name]('test') + + await plan +}) + +test('mixin can use level number', async (t) => { + const plan = tspl(t, { plan: 3 }) + const stream = sink() + const instance = pino({ + mixin (context, num) { + plan.ok(num !== null, 'level should be defined') + plan.ok(num !== undefined, 'level should be defined') + plan.deepEqual(num, level) + return Object.assign({ + error: context.message, + stack: context.stack + }) + } + }, stream) + instance.level = name + instance[name]({ + message: '123', + stack: 'stack' + }, 'test') + + await plan +}) + +test('mixin receives logger as third parameter', async (t) => { + const plan = tspl(t, { plan: 3 }) + const stream = sink() + const instance = pino({ + mixin (context, num, logger) { + plan.ok(logger !== null, 'logger should be defined') + plan.ok(logger !== undefined, 'logger should be defined') + plan.deepEqual(logger, instance) + return { ...context, num } + } + }, stream) + instance.level = name + instance[name]({ + message: '123' + }, 'test') + + await plan +}) + +test('mixin receives child logger', async (t) => { + const plan = tspl(t, { plan: 3 }) + const stream = sink() + let child = null + const instance = pino({ + mixin (context, num, logger) { + plan.ok(logger !== null, 'logger should be defined') + plan.ok(logger !== undefined, 'logger should be defined') + plan.deepEqual(logger.expected, child.expected) + return { ...context, num } + } + }, stream) + instance.level = name + instance.expected = false + child = instance.child({}) + child.expected = true + child[name]({ + message: '123' + }, 'test') + + await plan +}) + +test('mixin receives logger even if child exists', async (t) => { + const plan = tspl(t, { plan: 3 }) + const stream = sink() + let child = null + const instance = pino({ + mixin (context, num, logger) { + plan.ok(logger !== null, 'logger should be defined') + plan.ok(logger !== undefined, 'logger should be defined') + plan.deepEqual(logger.expected, instance.expected) + return { ...context, num } + } + }, stream) + instance.level = name + instance.expected = false + child = instance.child({}) + child.expected = true + instance[name]({ + message: '123' + }, 'test') + + await plan +}) diff --git a/services/slides/node_modules/pino/test/multistream.test.js b/services/slides/node_modules/pino/test/multistream.test.js new file mode 100644 index 0000000000000000000000000000000000000000..178cfbb2efa3a391943b5fef0b4b0de77f8142bb --- /dev/null +++ b/services/slides/node_modules/pino/test/multistream.test.js @@ -0,0 +1,729 @@ +'use strict' + +const test = require('node:test') +const assert = require('node:assert') +const { readFileSync } = require('node:fs') +const { join } = require('node:path') +const proxyquire = require('proxyquire') +const strip = require('strip-ansi') +const tspl = require('@matteo.collina/tspl') + +const writeStream = require('flush-write-stream') +const pino = require('../') +const multistream = pino.multistream +const { file, sink } = require('./helper') + +test('sends to multiple streams using string levels', async () => { + let messageCount = 0 + const stream = writeStream(function (data, enc, cb) { + messageCount += 1 + cb() + }) + const streams = [ + { stream }, + { level: 'debug', stream }, + { level: 'trace', stream }, + { level: 'fatal', stream }, + { level: 'silent', stream } + ] + const log = pino({ + level: 'trace' + }, multistream(streams)) + log.info('info stream') + log.debug('debug stream') + log.fatal('fatal stream') + assert.equal(messageCount, 9) +}) + +test('sends to multiple streams using custom levels', async () => { + let messageCount = 0 + const stream = writeStream(function (data, enc, cb) { + messageCount += 1 + cb() + }) + const streams = [ + { stream }, + { level: 'debug', stream }, + { level: 'trace', stream }, + { level: 'fatal', stream }, + { level: 'silent', stream } + ] + const log = pino({ + level: 'trace' + }, multistream(streams)) + log.info('info stream') + log.debug('debug stream') + log.fatal('fatal stream') + assert.equal(messageCount, 9) +}) + +test('sends to multiple streams using optionally predefined levels', async () => { + let messageCount = 0 + const stream = writeStream(function (data, enc, cb) { + messageCount += 1 + cb() + }) + const opts = { + levels: { + silent: Infinity, + fatal: 60, + error: 50, + warn: 50, + info: 30, + debug: 20, + trace: 10 + } + } + const streams = [ + { stream }, + { level: 'trace', stream }, + { level: 'debug', stream }, + { level: 'info', stream }, + { level: 'warn', stream }, + { level: 'error', stream }, + { level: 'fatal', stream }, + { level: 'silent', stream } + ] + const mstream = multistream(streams, opts) + const log = pino({ + level: 'trace' + }, mstream) + log.trace('trace stream') + log.debug('debug stream') + log.info('info stream') + log.warn('warn stream') + log.error('error stream') + log.fatal('fatal stream') + log.silent('silent stream') + assert.equal(messageCount, 24) +}) + +test('sends to multiple streams using number levels', async () => { + let messageCount = 0 + const stream = writeStream(function (data, enc, cb) { + messageCount += 1 + cb() + }) + const streams = [ + { stream }, + { level: 20, stream }, + { level: 60, stream } + ] + const log = pino({ + level: 'debug' + }, multistream(streams)) + log.info('info stream') + log.debug('debug stream') + log.fatal('fatal stream') + assert.equal(messageCount, 6) +}) + +test('level include higher levels', async () => { + let messageCount = 0 + const stream = writeStream(function (data, enc, cb) { + messageCount += 1 + cb() + }) + const log = pino({}, multistream([{ level: 'info', stream }])) + log.fatal('message') + assert.equal(messageCount, 1) +}) + +test('supports multiple arguments', async (t) => { + const plan = tspl(t, { plan: 2 }) + const messages = [] + const stream = writeStream(function (data, enc, cb) { + messages.push(JSON.parse(data)) + if (messages.length === 2) { + const msg1 = messages[0] + plan.equal(msg1.msg, 'foo bar baz foobar') + + const msg2 = messages[1] + plan.equal(msg2.msg, 'foo bar baz foobar barfoo foofoo') + } + cb() + }) + const log = pino({}, multistream({ stream })) + log.info('%s %s %s %s', 'foo', 'bar', 'baz', 'foobar') // apply not invoked + log.info('%s %s %s %s %s %s', 'foo', 'bar', 'baz', 'foobar', 'barfoo', 'foofoo') // apply invoked + + await plan +}) + +test('supports children', async (t) => { + const plan = tspl(t, { plan: 2 }) + const stream = writeStream(function (data, enc, cb) { + const input = JSON.parse(data) + plan.equal(input.msg, 'child stream') + plan.equal(input.child, 'one') + cb() + }) + const streams = [ + { stream } + ] + const log = pino({}, multistream(streams)).child({ child: 'one' }) + log.info('child stream') + + await plan +}) + +test('supports grandchildren', async (t) => { + const plan = tspl(t, { plan: 9 }) + const messages = [] + const stream = writeStream(function (data, enc, cb) { + messages.push(JSON.parse(data)) + if (messages.length === 3) { + const msg1 = messages[0] + plan.equal(msg1.msg, 'grandchild stream') + plan.equal(msg1.child, 'one') + plan.equal(msg1.grandchild, 'two') + + const msg2 = messages[1] + plan.equal(msg2.msg, 'grandchild stream') + plan.equal(msg2.child, 'one') + plan.equal(msg2.grandchild, 'two') + + const msg3 = messages[2] + plan.equal(msg3.msg, 'debug grandchild') + plan.equal(msg3.child, 'one') + plan.equal(msg3.grandchild, 'two') + } + cb() + }) + const streams = [ + { stream }, + { level: 'debug', stream } + ] + const log = pino({ + level: 'debug' + }, multistream(streams)).child({ child: 'one' }).child({ grandchild: 'two' }) + log.info('grandchild stream') + log.debug('debug grandchild') + + await plan +}) + +test('supports custom levels', (t, end) => { + const stream = writeStream(function (data, enc, cb) { + assert.equal(JSON.parse(data).msg, 'bar') + end() + }) + const log = pino({ + customLevels: { + foo: 35 + } + }, multistream([{ level: 35, stream }])) + log.foo('bar') +}) + +test('supports pretty print', async (t) => { + const plan = tspl(t, { plan: 2 }) + const stream = writeStream(function (data, enc, cb) { + plan.equal(strip(data.toString()).match(/INFO.*: pretty print/) != null, true) + cb() + }) + + const safeBoom = proxyquire('pino-pretty/lib/utils/build-safe-sonic-boom.js', { + 'sonic-boom': function () { + plan.ok('sonic created') + stream.flushSync = () => {} + stream.flush = () => {} + return stream + } + }) + const nested = proxyquire('pino-pretty/lib/utils/index.js', { + './build-safe-sonic-boom.js': safeBoom + }) + const pretty = proxyquire('pino-pretty', { + './lib/utils/index.js': nested + }) + + const log = pino({ + level: 'debug', + name: 'helloName' + }, multistream([ + { stream: pretty() } + ])) + + log.info('pretty print') + + await plan +}) + +test('emit propagates events to each stream', async (t) => { + const plan = tspl(t, { plan: 3 }) + const handler = function (data) { + plan.equal(data.msg, 'world') + } + const streams = [sink(), sink(), sink()] + streams.forEach(function (s) { + s.once('hello', handler) + }) + const stream = multistream(streams) + stream.emit('hello', { msg: 'world' }) + + await plan +}) + +test('children support custom levels', async (t) => { + const plan = tspl(t, { plan: 1 }) + const stream = writeStream(function (data, enc, cb) { + plan.equal(JSON.parse(data).msg, 'bar') + }) + const parent = pino({ + customLevels: { + foo: 35 + } + }, multistream([{ level: 35, stream }])) + const child = parent.child({ child: 'yes' }) + child.foo('bar') + + await plan +}) + +test('levelVal overrides level', async () => { + let messageCount = 0 + const stream = writeStream(function (data, enc, cb) { + messageCount += 1 + cb() + }) + const streams = [ + { stream }, + { level: 'blabla', levelVal: 15, stream }, + { level: 60, stream } + ] + const log = pino({ + level: 'debug' + }, multistream(streams)) + log.info('info stream') + log.debug('debug stream') + log.fatal('fatal stream') + assert.equal(messageCount, 6) +}) + +test('forwards metadata', async (t) => { + const plan = tspl(t, { plan: 4 }) + const streams = [ + { + stream: { + [Symbol.for('pino.metadata')]: true, + write (chunk) { + plan.equal(log, this.lastLogger) + plan.equal(30, this.lastLevel) + plan.deepEqual({ hello: 'world' }, this.lastObj) + plan.deepEqual('a msg', this.lastMsg) + } + } + } + ] + + const log = pino({ + level: 'debug' + }, multistream(streams)) + + log.info({ hello: 'world' }, 'a msg') + + await plan +}) + +test('forward name', async (t) => { + const plan = tspl(t, { plan: 2 }) + const streams = [ + { + stream: { + [Symbol.for('pino.metadata')]: true, + write (chunk) { + const line = JSON.parse(chunk) + plan.equal(line.name, 'helloName') + plan.equal(line.hello, 'world') + } + } + } + ] + + const log = pino({ + level: 'debug', + name: 'helloName' + }, multistream(streams)) + + log.info({ hello: 'world' }, 'a msg') + + await plan +}) + +test('forward name with child', async (t) => { + const plan = tspl(t, { plan: 3 }) + const streams = [ + { + stream: { + write (chunk) { + const line = JSON.parse(chunk) + plan.equal(line.name, 'helloName') + plan.equal(line.hello, 'world') + plan.equal(line.component, 'aComponent') + } + } + } + ] + + const log = pino({ + level: 'debug', + name: 'helloName' + }, multistream(streams)).child({ component: 'aComponent' }) + + log.info({ hello: 'world' }, 'a msg') + + await plan +}) + +test('clone generates a new multistream with all stream at the same level', async (t) => { + const plan = tspl(t, { plan: 14 }) + let messageCount = 0 + const stream = writeStream(function (data, enc, cb) { + messageCount += 1 + cb() + }) + const streams = [ + { stream }, + { level: 'debug', stream }, + { level: 'trace', stream }, + { level: 'fatal', stream } + ] + const ms = multistream(streams) + const clone = ms.clone(30) + + // eslint-disable-next-line eqeqeq + plan.equal(clone != ms, true) + + clone.streams.forEach((s, i) => { + // eslint-disable-next-line eqeqeq + plan.equal(s != streams[i], true) + plan.equal(s.stream, streams[i].stream) + plan.equal(s.level, 30) + }) + + const log = pino({ + level: 'trace' + }, clone) + + log.info('info stream') + log.debug('debug message not counted') + log.fatal('fatal stream') + plan.equal(messageCount, 8) + + await plan +}) + +test('one stream', async () => { + let messageCount = 0 + const stream = writeStream(function (data, enc, cb) { + messageCount += 1 + cb() + }) + const log = pino({ + level: 'trace' + }, multistream({ stream, level: 'fatal' })) + log.info('info stream') + log.debug('debug stream') + log.fatal('fatal stream') + assert.equal(messageCount, 1) +}) + +test('dedupe', async () => { + let messageCount = 0 + const stream1 = writeStream(function (data, enc, cb) { + messageCount -= 1 + cb() + }) + + const stream2 = writeStream(function (data, enc, cb) { + messageCount += 1 + cb() + }) + + const streams = [ + { + stream: stream1, + level: 'info' + }, + { + stream: stream2, + level: 'fatal' + } + ] + + const log = pino({ + level: 'trace' + }, multistream(streams, { dedupe: true })) + log.info('info stream') + log.fatal('fatal stream') + log.fatal('fatal stream') + assert.equal(messageCount, 1) +}) + +test('dedupe when logs have different levels', async () => { + let messageCount = 0 + const stream1 = writeStream(function (data, enc, cb) { + messageCount += 1 + cb() + }) + + const stream2 = writeStream(function (data, enc, cb) { + messageCount += 2 + cb() + }) + + const streams = [ + { + stream: stream1, + level: 'info' + }, + { + stream: stream2, + level: 'error' + } + ] + + const log = pino({ + level: 'trace' + }, multistream(streams, { dedupe: true })) + + log.info('info stream') + log.warn('warn stream') + log.error('error streams') + log.fatal('fatal streams') + assert.equal(messageCount, 6) +}) + +test('dedupe when some streams has the same level', async () => { + let messageCount = 0 + const stream1 = writeStream(function (data, enc, cb) { + messageCount -= 1 + cb() + }) + + const stream2 = writeStream(function (data, enc, cb) { + messageCount += 1 + cb() + }) + + const stream3 = writeStream(function (data, enc, cb) { + messageCount += 1 + cb() + }) + + const streams = [ + { + stream: stream1, + level: 'info' + }, + { + stream: stream2, + level: 'fatal' + }, + { + stream: stream3, + level: 'fatal' + } + ] + + const log = pino({ + level: 'trace' + }, multistream(streams, { dedupe: true })) + log.info('info stream') + log.fatal('fatal streams') + log.fatal('fatal streams') + assert.equal(messageCount, 3) +}) + +test('no stream', async () => { + const log = pino({ + level: 'trace' + }, multistream()) + log.info('info stream') + log.debug('debug stream') + log.fatal('fatal stream') +}) + +test('one stream', async () => { + let messageCount = 0 + const stream = writeStream(function (data, enc, cb) { + messageCount += 1 + cb() + }) + const log = pino({ + level: 'trace' + }, multistream(stream)) + log.info('info stream') + log.debug('debug stream') + log.fatal('fatal stream') + assert.equal(messageCount, 2) +}) + +test('add a stream', async () => { + let messageCount = 0 + const stream = writeStream(function (data, enc, cb) { + messageCount += 1 + cb() + }) + + const log = pino({ + level: 'trace' + }, multistream().add(stream)) + log.info('info stream') + log.debug('debug stream') + log.fatal('fatal stream') + assert.equal(messageCount, 2) +}) + +test('remove a stream', async () => { + let messageCount1 = 0 + let messageCount2 = 0 + let messageCount3 = 0 + + const stream1 = writeStream(function (data, enc, cb) { + messageCount1 += 1 + cb() + }) + + const stream2 = writeStream(function (data, enc, cb) { + messageCount2 += 1 + cb() + }) + + const stream3 = writeStream(function (data, enc, cb) { + messageCount3 += 1 + cb() + }) + + const multi = multistream() + const log = pino({ level: 'trace', sync: true }, multi) + + multi.add(stream1) + const id1 = multi.lastId + + multi.add(stream2) + const id2 = multi.lastId + + multi.add(stream3) + const id3 = multi.lastId + + log.info('line') + multi.remove(id1) + + log.info('line') + multi.remove(id2) + + log.info('line') + multi.remove(id3) + + log.info('line') + multi.remove(Math.floor(Math.random() * 1000)) // non-existing id + + assert.equal(messageCount1, 1) + assert.equal(messageCount2, 2) + assert.equal(messageCount3, 3) +}) + +test('multistream.add throws if not a stream', async () => { + try { + pino({ + level: 'trace' + }, multistream().add({})) + } catch (_) { + } +}) + +test('multistream throws if not a stream', async () => { + try { + pino({ + level: 'trace' + }, multistream({})) + } catch (_) { + } +}) + +test('multistream.write should not throw if one stream fails', async () => { + let messageCount = 0 + const stream = writeStream(function (data, enc, cb) { + messageCount += 1 + cb() + }) + const noopStream = pino.transport({ + target: join(__dirname, 'fixtures', 'noop-transport.js') + }) + // eslint-disable-next-line + noopStream.on('error', function (err) { + // something went wrong while writing to noop stream, ignoring! + }) + const log = pino({ + level: 'trace' + }, + multistream([ + { + level: 'trace', + stream + }, + { + level: 'debug', + stream: noopStream + } + ]) + ) + log.debug('0') + noopStream.end() + // noop stream is ending, should emit an error but not throw + log.debug('1') + log.debug('2') + assert.equal(messageCount, 3) +}) + +test('flushSync', async (t) => { + const plan = tspl(t, { plan: 2 }) + const tmp = file() + const destination = pino.destination({ dest: tmp, sync: false, minLength: 4096 }) + const stream = multistream([{ level: 'info', stream: destination }]) + const log = pino({ level: 'info' }, stream) + destination.on('ready', () => { + log.info('foo') + log.info('bar') + stream.flushSync() + plan.equal(readFileSync(tmp, { encoding: 'utf-8' }).split('\n').length - 1, 2) + log.info('biz') + stream.flushSync() + plan.equal(readFileSync(tmp, { encoding: 'utf-8' }).split('\n').length - 1, 3) + }) + + await plan +}) + +test('ends all streams', async (t) => { + const plan = tspl(t, { plan: 7 }) + const stream = writeStream(function (data, enc, cb) { + plan.ok('message') + cb() + }) + stream.flushSync = function () { + plan.ok('flushSync') + } + // stream2 has no flushSync + const stream2 = writeStream(function (data, enc, cb) { + plan.ok('message2') + cb() + }) + const streams = [ + { stream }, + { level: 'debug', stream }, + { level: 'trace', stream: stream2 }, + { level: 'fatal', stream }, + { level: 'silent', stream } + ] + const multi = multistream(streams) + const log = pino({ + level: 'trace' + }, multi) + log.info('info stream') + multi.end() + + await plan +}) diff --git a/services/slides/node_modules/pino/test/redact.test.js b/services/slides/node_modules/pino/test/redact.test.js new file mode 100644 index 0000000000000000000000000000000000000000..456a7f5abc01413eb83203c1f1be8ed7b386bb31 --- /dev/null +++ b/services/slides/node_modules/pino/test/redact.test.js @@ -0,0 +1,893 @@ +'use strict' + +const test = require('node:test') +const assert = require('node:assert') + +const { sink, once } = require('./helper') +const pino = require('../') + +test('redact option – throws if not array', async () => { + assert.throws(() => { + pino({ redact: 'req.headers.cookie' }) + }) +}) + +test('redact option – throws if array does not only contain strings', async () => { + assert.throws(() => { + pino({ redact: ['req.headers.cookie', {}] }) + }) +}) + +test('redact option – throws if array contains an invalid path', async () => { + assert.throws(() => { + pino({ redact: ['req,headers.cookie'] }) + }) +}) + +test('redact.paths option – throws if not array', async () => { + assert.throws(() => { + pino({ redact: { paths: 'req.headers.cookie' } }) + }) +}) + +test('redact.paths option – throws if array does not only contain strings', async () => { + assert.throws(() => { + pino({ redact: { paths: ['req.headers.cookie', {}] } }) + }) +}) + +test('redact.paths option – throws if array contains an invalid path', async () => { + assert.throws(() => { + pino({ redact: { paths: ['req,headers.cookie'] } }) + }) +}) + +test('redact option – top level key', async () => { + const stream = sink() + const instance = pino({ redact: ['key'] }, stream) + instance.info({ + key: { redact: 'me' } + }) + const { key } = await once(stream, 'data') + assert.equal(key, '[Redacted]') +}) + +test('redact option – top level key next level key', async () => { + const stream = sink() + const instance = pino({ redact: ['key', 'key.foo'] }, stream) + instance.info({ + key: { redact: 'me' } + }) + const { key } = await once(stream, 'data') + assert.equal(key, '[Redacted]') +}) + +test('redact option – next level key then top level key', async () => { + const stream = sink() + const instance = pino({ redact: ['key.foo', 'key'] }, stream) + instance.info({ + key: { redact: 'me' } + }) + const { key } = await once(stream, 'data') + assert.equal(key, '[Redacted]') +}) + +test('redact option – object', async () => { + const stream = sink() + const instance = pino({ redact: ['req.headers.cookie'] }, stream) + instance.info({ + req: { + id: 7915, + method: 'GET', + url: '/', + headers: { + host: 'localhost:3000', + connection: 'keep-alive', + cookie: 'SESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1;' + }, + remoteAddress: '::ffff:127.0.0.1', + remotePort: 58022 + } + }) + const { req } = await once(stream, 'data') + assert.equal(req.headers.cookie, '[Redacted]') +}) + +test('redact option – child object', async () => { + const stream = sink() + const instance = pino({ redact: ['req.headers.cookie'] }, stream) + instance.child({ + req: { + id: 7915, + method: 'GET', + url: '/', + headers: { + host: 'localhost:3000', + connection: 'keep-alive', + cookie: 'SESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1;' + }, + remoteAddress: '::ffff:127.0.0.1', + remotePort: 58022 + } + }).info('message completed') + const { req } = await once(stream, 'data') + assert.equal(req.headers.cookie, '[Redacted]') +}) + +test('redact option – interpolated object', async () => { + const stream = sink() + const instance = pino({ redact: ['req.headers.cookie'] }, stream) + + instance.info('test %j', { + req: { + id: 7915, + method: 'GET', + url: '/', + headers: { + host: 'localhost:3000', + connection: 'keep-alive', + cookie: 'SESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1;' + }, + remoteAddress: '::ffff:127.0.0.1', + remotePort: 58022 + } + }) + const { msg } = await once(stream, 'data') + assert.equal(JSON.parse(msg.replace(/test /, '')).req.headers.cookie, '[Redacted]') +}) + +test('redact.paths option – object', async () => { + const stream = sink() + const instance = pino({ redact: { paths: ['req.headers.cookie'] } }, stream) + instance.info({ + req: { + id: 7915, + method: 'GET', + url: '/', + headers: { + host: 'localhost:3000', + connection: 'keep-alive', + cookie: 'SESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1;' + }, + remoteAddress: '::ffff:127.0.0.1', + remotePort: 58022 + } + }) + const { req } = await once(stream, 'data') + assert.equal(req.headers.cookie, '[Redacted]') +}) + +test('redact.paths option – child object', async () => { + const stream = sink() + const instance = pino({ redact: { paths: ['req.headers.cookie'] } }, stream) + instance.child({ + req: { + id: 7915, + method: 'GET', + url: '/', + headers: { + host: 'localhost:3000', + connection: 'keep-alive', + cookie: 'SESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1;' + }, + remoteAddress: '::ffff:127.0.0.1', + remotePort: 58022 + } + }).info('message completed') + const { req } = await once(stream, 'data') + assert.equal(req.headers.cookie, '[Redacted]') +}) + +test('redact.paths option – interpolated object', async () => { + const stream = sink() + const instance = pino({ redact: { paths: ['req.headers.cookie'] } }, stream) + + instance.info('test %j', { + req: { + id: 7915, + method: 'GET', + url: '/', + headers: { + host: 'localhost:3000', + connection: 'keep-alive', + cookie: 'SESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1;' + }, + remoteAddress: '::ffff:127.0.0.1', + remotePort: 58022 + } + }) + const { msg } = await once(stream, 'data') + assert.equal(JSON.parse(msg.replace(/test /, '')).req.headers.cookie, '[Redacted]') +}) + +test('redact.censor option – sets the redact value', async () => { + const stream = sink() + const instance = pino({ redact: { paths: ['req.headers.cookie'], censor: 'test' } }, stream) + instance.info({ + req: { + id: 7915, + method: 'GET', + url: '/', + headers: { + host: 'localhost:3000', + connection: 'keep-alive', + cookie: 'SESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1;' + }, + remoteAddress: '::ffff:127.0.0.1', + remotePort: 58022 + } + }) + const { req } = await once(stream, 'data') + assert.equal(req.headers.cookie, 'test') +}) + +test('redact.censor option – can be a function that accepts value and path arguments', async () => { + const stream = sink() + const instance = pino({ redact: { paths: ['topLevel'], censor: (value, path) => value + ' ' + path.join('.') } }, stream) + instance.info({ + topLevel: 'test' + }) + const { topLevel } = await once(stream, 'data') + assert.equal(topLevel, 'test topLevel') +}) + +test('redact.censor option – can be a function that accepts value and path arguments (nested path)', async () => { + const stream = sink() + const instance = pino({ redact: { paths: ['req.headers.cookie'], censor: (value, path) => value + ' ' + path.join('.') } }, stream) + instance.info({ + req: { + id: 7915, + method: 'GET', + url: '/', + headers: { + host: 'localhost:3000', + connection: 'keep-alive', + cookie: 'SESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1;' + }, + remoteAddress: '::ffff:127.0.0.1', + remotePort: 58022 + } + }) + const { req } = await once(stream, 'data') + assert.equal(req.headers.cookie, 'SESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1; req.headers.cookie') +}) + +test('redact.remove option – removes both key and value', async () => { + const stream = sink() + const instance = pino({ redact: { paths: ['req.headers.cookie'], remove: true } }, stream) + instance.info({ + req: { + id: 7915, + method: 'GET', + url: '/', + headers: { + host: 'localhost:3000', + connection: 'keep-alive', + cookie: 'SESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1;' + }, + remoteAddress: '::ffff:127.0.0.1', + remotePort: 58022 + } + }) + const { req } = await once(stream, 'data') + assert.equal('cookie' in req.headers, false) +}) + +test('redact.remove – top level key - object value', async () => { + const stream = sink() + const instance = pino({ redact: { paths: ['key'], remove: true } }, stream) + instance.info({ + key: { redact: 'me' } + }) + const o = await once(stream, 'data') + assert.equal('key' in o, false) +}) + +test('redact.remove – top level key - number value', async () => { + const stream = sink() + const instance = pino({ redact: { paths: ['key'], remove: true } }, stream) + instance.info({ + key: 1 + }) + const o = await once(stream, 'data') + assert.equal('key' in o, false) +}) + +test('redact.remove – top level key - boolean value', async () => { + const stream = sink() + const instance = pino({ redact: { paths: ['key'], remove: true } }, stream) + instance.info({ + key: false + }) + const o = await once(stream, 'data') + assert.equal('key' in o, false) +}) + +test('redact.remove – top level key in child logger', async () => { + const stream = sink() + const opts = { redact: { paths: ['key'], remove: true } } + const instance = pino(opts, stream).child({ key: { redact: 'me' } }) + instance.info('test') + const o = await once(stream, 'data') + assert.equal('key' in o, false) +}) + +test('redact.paths preserves original object values after the log write', async () => { + const stream = sink() + const instance = pino({ redact: ['req.headers.cookie'] }, stream) + const obj = { + req: { + id: 7915, + method: 'GET', + url: '/', + headers: { + host: 'localhost:3000', + connection: 'keep-alive', + cookie: 'SESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1;' + }, + remoteAddress: '::ffff:127.0.0.1', + remotePort: 58022 + } + } + instance.info(obj) + const o = await once(stream, 'data') + assert.equal(o.req.headers.cookie, '[Redacted]') + assert.equal(obj.req.headers.cookie, 'SESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1;') +}) + +test('redact.paths preserves original object values after the log write', async () => { + const stream = sink() + const instance = pino({ redact: { paths: ['req.headers.cookie'] } }, stream) + const obj = { + req: { + id: 7915, + method: 'GET', + url: '/', + headers: { + host: 'localhost:3000', + connection: 'keep-alive', + cookie: 'SESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1;' + }, + remoteAddress: '::ffff:127.0.0.1', + remotePort: 58022 + } + } + instance.info(obj) + const o = await once(stream, 'data') + assert.equal(o.req.headers.cookie, '[Redacted]') + assert.equal(obj.req.headers.cookie, 'SESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1;') +}) + +test('redact.censor preserves original object values after the log write', async () => { + const stream = sink() + const instance = pino({ redact: { paths: ['req.headers.cookie'], censor: 'test' } }, stream) + const obj = { + req: { + id: 7915, + method: 'GET', + url: '/', + headers: { + host: 'localhost:3000', + connection: 'keep-alive', + cookie: 'SESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1;' + }, + remoteAddress: '::ffff:127.0.0.1', + remotePort: 58022 + } + } + instance.info(obj) + const o = await once(stream, 'data') + assert.equal(o.req.headers.cookie, 'test') + assert.equal(obj.req.headers.cookie, 'SESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1;') +}) + +test('redact.remove preserves original object values after the log write', async () => { + const stream = sink() + const instance = pino({ redact: { paths: ['req.headers.cookie'], remove: true } }, stream) + const obj = { + req: { + id: 7915, + method: 'GET', + url: '/', + headers: { + host: 'localhost:3000', + connection: 'keep-alive', + cookie: 'SESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1;' + }, + remoteAddress: '::ffff:127.0.0.1', + remotePort: 58022 + } + } + instance.info(obj) + const o = await once(stream, 'data') + assert.equal('cookie' in o.req.headers, false) + assert.equal('cookie' in obj.req.headers, true) +}) + +test('redact – supports last position wildcard paths', async () => { + const stream = sink() + const instance = pino({ redact: ['req.headers.*'] }, stream) + instance.info({ + req: { + id: 7915, + method: 'GET', + url: '/', + headers: { + host: 'localhost:3000', + connection: 'keep-alive', + cookie: 'SESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1;' + }, + remoteAddress: '::ffff:127.0.0.1', + remotePort: 58022 + } + }) + const { req } = await once(stream, 'data') + assert.equal(req.headers.cookie, '[Redacted]') + assert.equal(req.headers.host, '[Redacted]') + assert.equal(req.headers.connection, '[Redacted]') +}) + +test('redact – supports first position wildcard paths', async () => { + const stream = sink() + const instance = pino({ redact: ['*.headers'] }, stream) + instance.info({ + req: { + id: 7915, + method: 'GET', + url: '/', + headers: { + host: 'localhost:3000', + connection: 'keep-alive', + cookie: 'SESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1;' + }, + remoteAddress: '::ffff:127.0.0.1', + remotePort: 58022 + } + }) + const { req } = await once(stream, 'data') + assert.equal(req.headers, '[Redacted]') +}) + +test('redact – supports first position wildcards before other paths', async () => { + const stream = sink() + const instance = pino({ redact: ['*.headers.cookie', 'req.id'] }, stream) + instance.info({ + req: { + id: 7915, + method: 'GET', + url: '/', + headers: { + host: 'localhost:3000', + connection: 'keep-alive', + cookie: 'SESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1;' + }, + remoteAddress: '::ffff:127.0.0.1', + remotePort: 58022 + } + }) + const { req } = await once(stream, 'data') + assert.equal(req.headers.cookie, '[Redacted]') + assert.equal(req.id, '[Redacted]') +}) + +test('redact – supports first position wildcards after other paths', async () => { + const stream = sink() + const instance = pino({ redact: ['req.id', '*.headers.cookie'] }, stream) + instance.info({ + req: { + id: 7915, + method: 'GET', + url: '/', + headers: { + host: 'localhost:3000', + connection: 'keep-alive', + cookie: 'SESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1;' + }, + remoteAddress: '::ffff:127.0.0.1', + remotePort: 58022 + } + }) + const { req } = await once(stream, 'data') + assert.equal(req.headers.cookie, '[Redacted]') + assert.equal(req.id, '[Redacted]') +}) + +test('redact – supports first position wildcards after top level keys', async () => { + const stream = sink() + const instance = pino({ redact: ['key', '*.headers.cookie'] }, stream) + instance.info({ + req: { + id: 7915, + method: 'GET', + url: '/', + headers: { + host: 'localhost:3000', + connection: 'keep-alive', + cookie: 'SESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1;' + }, + remoteAddress: '::ffff:127.0.0.1', + remotePort: 58022 + } + }) + const { req } = await once(stream, 'data') + assert.equal(req.headers.cookie, '[Redacted]') +}) + +test('redact – supports top level wildcard', async () => { + const stream = sink() + const instance = pino({ redact: ['*'] }, stream) + instance.info({ + req: { + id: 7915, + method: 'GET', + url: '/', + headers: { + host: 'localhost:3000', + connection: 'keep-alive', + cookie: 'SESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1;' + }, + remoteAddress: '::ffff:127.0.0.1', + remotePort: 58022 + } + }) + const { req } = await once(stream, 'data') + assert.equal(req, '[Redacted]') +}) + +test('redact – supports top level wildcard with a censor function', async () => { + const stream = sink() + const instance = pino({ + redact: { + paths: ['*'], + censor: () => '[Redacted]' + } + }, stream) + instance.info({ + req: { + id: 7915, + method: 'GET', + url: '/', + headers: { + host: 'localhost:3000', + connection: 'keep-alive', + cookie: 'SESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1;' + }, + remoteAddress: '::ffff:127.0.0.1', + remotePort: 58022 + } + }) + const { req } = await once(stream, 'data') + assert.equal(req, '[Redacted]') +}) + +test('redact – supports top level wildcard and leading wildcard', async () => { + const stream = sink() + const instance = pino({ redact: ['*', '*.req'] }, stream) + instance.info({ + req: { + id: 7915, + method: 'GET', + url: '/', + headers: { + host: 'localhost:3000', + connection: 'keep-alive', + cookie: 'SESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1;' + }, + remoteAddress: '::ffff:127.0.0.1', + remotePort: 58022 + } + }) + const { req } = await once(stream, 'data') + assert.equal(req, '[Redacted]') +}) + +test('redact – supports intermediate wildcard paths', async () => { + const stream = sink() + const instance = pino({ redact: ['req.*.cookie'] }, stream) + instance.info({ + req: { + id: 7915, + method: 'GET', + url: '/', + headers: { + host: 'localhost:3000', + connection: 'keep-alive', + cookie: 'SESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1;' + }, + remoteAddress: '::ffff:127.0.0.1', + remotePort: 58022 + } + }) + const { req } = await once(stream, 'data') + assert.equal(req.headers.cookie, '[Redacted]') +}) + +test('redacts numbers at the top level', async () => { + const stream = sink() + const instance = pino({ redact: ['id'] }, stream) + const obj = { + id: 7915 + } + instance.info(obj) + const o = await once(stream, 'data') + assert.equal(o.id, '[Redacted]') +}) + +test('redacts booleans at the top level', async () => { + const stream = sink() + const instance = pino({ redact: ['maybe'] }, stream) + const obj = { + maybe: true + } + instance.info(obj) + const o = await once(stream, 'data') + assert.equal(o.maybe, '[Redacted]') +}) + +test('redacts strings at the top level', async () => { + const stream = sink() + const instance = pino({ redact: ['s'] }, stream) + const obj = { + s: 's' + } + instance.info(obj) + const o = await once(stream, 'data') + assert.equal(o.s, '[Redacted]') +}) + +test('does not redact primitives if not objects', async () => { + const stream = sink() + const instance = pino({ redact: ['a.b'] }, stream) + const obj = { + a: 42 + } + instance.info(obj) + const o = await once(stream, 'data') + assert.equal(o.a, 42) +}) + +test('redacts null at the top level', async () => { + const stream = sink() + const instance = pino({ redact: ['n'] }, stream) + const obj = { + n: null + } + instance.info(obj) + const o = await once(stream, 'data') + assert.equal(o.n, '[Redacted]') +}) + +test('supports bracket notation', async () => { + const stream = sink() + const instance = pino({ redact: ['a["b.b"]'] }, stream) + const obj = { + a: { 'b.b': 'c' } + } + instance.info(obj) + const o = await once(stream, 'data') + assert.equal(o.a['b.b'], '[Redacted]') +}) + +test('supports bracket notation with further nesting', async () => { + const stream = sink() + const instance = pino({ redact: ['a["b.b"].c'] }, stream) + const obj = { + a: { 'b.b': { c: 'd' } } + } + instance.info(obj) + const o = await once(stream, 'data') + assert.equal(o.a['b.b'].c, '[Redacted]') +}) + +test('supports bracket notation with empty string as path segment', async () => { + const stream = sink() + const instance = pino({ redact: ['a[""].c'] }, stream) + const obj = { + a: { '': { c: 'd' } } + } + instance.info(obj) + const o = await once(stream, 'data') + assert.equal(o.a[''].c, '[Redacted]') +}) + +test('supports leading bracket notation (single quote)', async () => { + const stream = sink() + const instance = pino({ redact: ['[\'a.a\'].b'] }, stream) + const obj = { + 'a.a': { b: 'c' } + } + instance.info(obj) + const o = await once(stream, 'data') + assert.equal(o['a.a'].b, '[Redacted]') +}) + +test('supports leading bracket notation (double quote)', async () => { + const stream = sink() + const instance = pino({ redact: ['["a.a"].b'] }, stream) + const obj = { + 'a.a': { b: 'c' } + } + instance.info(obj) + const o = await once(stream, 'data') + assert.equal(o['a.a'].b, '[Redacted]') +}) + +test('supports leading bracket notation (backtick quote)', async () => { + const stream = sink() + const instance = pino({ redact: ['[`a.a`].b'] }, stream) + const obj = { + 'a.a': { b: 'c' } + } + instance.info(obj) + const o = await once(stream, 'data') + assert.equal(o['a.a'].b, '[Redacted]') +}) + +test('supports leading bracket notation (single-segment path)', async () => { + const stream = sink() + const instance = pino({ redact: ['[`a.a`]'] }, stream) + const obj = { + 'a.a': { b: 'c' } + } + instance.info(obj) + const o = await once(stream, 'data') + assert.equal(o['a.a'], '[Redacted]') +}) + +test('supports leading bracket notation (single-segment path, wildcard)', async () => { + const stream = sink() + const instance = pino({ redact: ['[*]'] }, stream) + const obj = { + 'a.a': { b: 'c' } + } + instance.info(obj) + const o = await once(stream, 'data') + assert.equal(o['a.a'], '[Redacted]') +}) + +test('child bindings are redacted using wildcard path', async () => { + const stream = sink() + const instance = pino({ redact: ['*.headers.cookie'] }, stream) + instance.child({ + req: { + method: 'GET', + url: '/', + headers: { + cookie: 'SESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1;' + } + } + }).info('message completed') + const { req } = await once(stream, 'data') + assert.equal(req.headers.cookie, '[Redacted]') +}) + +test('child bindings are redacted using wildcard and plain path keys', async () => { + const stream = sink() + const instance = pino({ redact: ['req.method', '*.headers.cookie'] }, stream) + instance.child({ + req: { + method: 'GET', + url: '/', + headers: { + cookie: 'SESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1;' + } + } + }).info('message completed') + const { req } = await once(stream, 'data') + assert.equal(req.headers.cookie, '[Redacted]') + assert.equal(req.method, '[Redacted]') +}) + +test('redacts boolean at the top level', async () => { + const stream = sink() + const instance = pino({ redact: ['msg'] }, stream) + const obj = { + s: 's' + } + instance.info(obj, true) + const o = await once(stream, 'data') + assert.equal(o.s, 's') + assert.equal(o.msg, '[Redacted]') +}) + +test('child can customize redact', async () => { + const stream = sink() + const instance = pino({ redact: ['req.method', '*.headers.cookie'] }, stream) + instance.child({ + req: { + method: 'GET', + url: '/', + headers: { + cookie: 'SESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1;' + } + } + }, { + redact: ['req.url'] + }).info('message completed') + const { req } = await once(stream, 'data') + assert.equal(req.headers.cookie, 'SESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1;') + assert.equal(req.method, 'GET') + assert.equal(req.url, '[Redacted]') +}) + +test('child can remove parent redact by array', async () => { + const stream = sink() + const instance = pino({ redact: ['req.method', '*.headers.cookie'] }, stream) + instance.child({ + req: { + method: 'GET', + url: '/', + headers: { + cookie: 'SESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1;' + } + } + }, { + redact: [] + }).info('message completed') + const { req } = await once(stream, 'data') + assert.equal(req.headers.cookie, 'SESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1;') + assert.equal(req.method, 'GET') +}) + +test('redact safe stringify', async () => { + const stream = sink() + const instance = pino({ redact: { paths: ['that.secret'] } }, stream) + + instance.info({ + that: { + secret: 'please hide me', + myBigInt: 123n + }, + other: { + mySecondBigInt: 222n + } + }) + const { that, other } = await once(stream, 'data') + assert.equal(that.secret, '[Redacted]') + assert.equal(that.myBigInt, 123) + assert.equal(other.mySecondBigInt, 222) +}) + +test('censor function should not be called for non-existent nested paths (issue #2313)', async () => { + const stream = sink() + const censorCalls = [] + + const instance = pino({ + redact: { + paths: ['a.b.c', 'req.authorization', 'url'], + censor (value, path) { + censorCalls.push({ value, path: path.join('.') }) + if (typeof value !== 'string') { + return '***' + } + return '***' + } + } + }, stream) + + // Test case 1: parent exists but nested path doesn't + censorCalls.length = 0 + instance.info({ req: { id: 'test' } }, 'test message') + await once(stream, 'data') + assert.equal(censorCalls.length, 0, 'censor should not be called when req.authorization does not exist') + + // Test case 2: parent exists but deeply nested path doesn't + censorCalls.length = 0 + instance.info({ a: { d: 'test' } }, 'test message') + await once(stream, 'data') + assert.equal(censorCalls.length, 0, 'censor should not be called when a.b.c does not exist') + + // Test case 3: multiple parent keys exist but nested paths don't + censorCalls.length = 0 + instance.info({ a: { c: 'should-not-show-me' }, req: { id: 'test' } }, 'test message') + await once(stream, 'data') + assert.equal(censorCalls.length, 0, 'censor should not be called when neither a.b.c nor req.authorization exist') + + // Test case 4: verify censor IS called when path exists + censorCalls.length = 0 + instance.info({ req: { authorization: 'bearer token' } }, 'test message') + await once(stream, 'data') + assert.equal(censorCalls.length, 1, 'censor should be called when req.authorization exists') + assert.equal(censorCalls[0].path, 'req.authorization') + assert.equal(censorCalls[0].value, 'bearer token') +}) diff --git a/services/slides/node_modules/pino/test/serializers.test.js b/services/slides/node_modules/pino/test/serializers.test.js new file mode 100644 index 0000000000000000000000000000000000000000..b8400716b2f925a7a8af7ba206b0735152ed9190 --- /dev/null +++ b/services/slides/node_modules/pino/test/serializers.test.js @@ -0,0 +1,257 @@ +'use strict' + +const test = require('node:test') +const assert = require('node:assert') +const stdSerializers = require('pino-std-serializers') + +const { sink, once } = require('./helper') +const pino = require('../') + +const parentSerializers = { + test: () => 'parent' +} + +const childSerializers = { + test: () => 'child' +} + +test('default err namespace error serializer', async () => { + const stream = sink() + const parent = pino(stream) + + parent.info({ err: ReferenceError('test') }) + const o = await once(stream, 'data') + assert.equal(typeof o.err, 'object') + assert.equal(o.err.type, 'ReferenceError') + assert.equal(o.err.message, 'test') + assert.equal(typeof o.err.stack, 'string') +}) + +test('custom serializer overrides default err namespace error serializer', async () => { + const stream = sink() + const parent = pino({ + serializers: { + err: (e) => ({ + t: e.constructor.name, + m: e.message, + s: e.stack + }) + } + }, stream) + + parent.info({ err: ReferenceError('test') }) + const o = await once(stream, 'data') + assert.equal(typeof o.err, 'object') + assert.equal(o.err.t, 'ReferenceError') + assert.equal(o.err.m, 'test') + assert.equal(typeof o.err.s, 'string') +}) + +test('custom serializer overrides default err namespace error serializer when nestedKey is on', async () => { + const stream = sink() + const parent = pino({ + nestedKey: 'obj', + serializers: { + err: (e) => { + return { + t: e.constructor.name, + m: e.message, + s: e.stack + } + } + } + }, stream) + + parent.info({ err: ReferenceError('test') }) + const o = await once(stream, 'data') + assert.equal(typeof o.obj.err, 'object') + assert.equal(o.obj.err.t, 'ReferenceError') + assert.equal(o.obj.err.m, 'test') + assert.equal(typeof o.obj.err.s, 'string') +}) + +test('null overrides default err namespace error serializer', async () => { + const stream = sink() + const parent = pino({ serializers: { err: null } }, stream) + + parent.info({ err: ReferenceError('test') }) + const o = await once(stream, 'data') + assert.equal(typeof o.err, 'object') + assert.equal(typeof o.err.type, 'undefined') + assert.equal(typeof o.err.message, 'undefined') + assert.equal(typeof o.err.stack, 'undefined') +}) + +test('undefined overrides default err namespace error serializer', async () => { + const stream = sink() + const parent = pino({ serializers: { err: undefined } }, stream) + + parent.info({ err: ReferenceError('test') }) + const o = await once(stream, 'data') + assert.equal(typeof o.err, 'object') + assert.equal(typeof o.err.type, 'undefined') + assert.equal(typeof o.err.message, 'undefined') + assert.equal(typeof o.err.stack, 'undefined') +}) + +test('serializers override values', async () => { + const stream = sink() + const parent = pino({ serializers: parentSerializers }, stream) + parent.child({}, { serializers: childSerializers }) + + parent.fatal({ test: 'test' }) + const o = await once(stream, 'data') + assert.equal(o.test, 'parent') +}) + +test('child does not overwrite parent serializers', async () => { + const stream = sink() + const parent = pino({ serializers: parentSerializers }, stream) + const child = parent.child({}, { serializers: childSerializers }) + + parent.fatal({ test: 'test' }) + + const o = once(stream, 'data') + assert.equal((await o).test, 'parent') + const o2 = once(stream, 'data') + child.fatal({ test: 'test' }) + assert.equal((await o2).test, 'child') +}) + +test('Symbol.for(\'pino.serializers\')', async () => { + const stream = sink() + const expected = Object.assign({ + err: stdSerializers.err + }, parentSerializers) + const parent = pino({ serializers: parentSerializers }, stream) + const child = parent.child({ a: 'property' }) + + assert.deepEqual(parent[Symbol.for('pino.serializers')], expected) + assert.deepEqual(child[Symbol.for('pino.serializers')], expected) + assert.equal(parent[Symbol.for('pino.serializers')], child[Symbol.for('pino.serializers')]) + + const child2 = parent.child({}, { + serializers: { + a + } + }) + + function a () { + return 'hello' + } + + // eslint-disable-next-line eqeqeq + assert.equal(child2[Symbol.for('pino.serializers')] != parentSerializers, true) + assert.equal(child2[Symbol.for('pino.serializers')].a, a) + assert.equal(child2[Symbol.for('pino.serializers')].test, parentSerializers.test) +}) + +test('children inherit parent serializers', async () => { + const stream = sink() + const parent = pino({ serializers: parentSerializers }, stream) + + const child = parent.child({ a: 'property' }) + child.fatal({ test: 'test' }) + const o = await once(stream, 'data') + assert.equal(o.test, 'parent') +}) + +test('children inherit parent Symbol serializers', async () => { + const stream = sink() + const symbolSerializers = { + [Symbol.for('b')]: b + } + const expected = Object.assign({ + err: stdSerializers.err + }, symbolSerializers) + const parent = pino({ serializers: symbolSerializers }, stream) + + assert.deepEqual(parent[Symbol.for('pino.serializers')], expected) + + const child = parent.child({}, { + serializers: { + [Symbol.for('a')]: a, + a + } + }) + + function a () { + return 'hello' + } + + function b () { + return 'world' + } + + assert.deepEqual(child[Symbol.for('pino.serializers')].a, a) + assert.deepEqual(child[Symbol.for('pino.serializers')][Symbol.for('b')], b) + assert.deepEqual(child[Symbol.for('pino.serializers')][Symbol.for('a')], a) +}) + +test('children serializers get called', async () => { + const stream = sink() + const parent = pino({ + test: 'this' + }, stream) + + const child = parent.child({ a: 'property' }, { serializers: childSerializers }) + + child.fatal({ test: 'test' }) + const o = await once(stream, 'data') + assert.equal(o.test, 'child') +}) + +test('children serializers get called when inherited from parent', async () => { + const stream = sink() + const parent = pino({ + test: 'this', + serializers: parentSerializers + }, stream) + + const child = parent.child({}, { serializers: { test: function () { return 'pass' } } }) + + child.fatal({ test: 'fail' }) + const o = await once(stream, 'data') + assert.equal(o.test, 'pass') +}) + +test('non-overridden serializers are available in the children', async () => { + const stream = sink() + const pSerializers = { + onlyParent: function () { return 'parent' }, + shared: function () { return 'parent' } + } + + const cSerializers = { + shared: function () { return 'child' }, + onlyChild: function () { return 'child' } + } + + const parent = pino({ serializers: pSerializers }, stream) + + const child = parent.child({}, { serializers: cSerializers }) + + const o = once(stream, 'data') + child.fatal({ shared: 'test' }) + assert.equal((await o).shared, 'child') + const o2 = once(stream, 'data') + child.fatal({ onlyParent: 'test' }) + assert.equal((await o2).onlyParent, 'parent') + const o3 = once(stream, 'data') + child.fatal({ onlyChild: 'test' }) + assert.equal((await o3).onlyChild, 'child') + const o4 = once(stream, 'data') + parent.fatal({ onlyChild: 'test' }) + assert.equal((await o4).onlyChild, 'test') +}) + +test('custom serializer for messageKey', async () => { + const stream = sink() + const instance = pino({ serializers: { msg: () => '422' } }, stream) + + const o = { num: NaN } + instance.info(o, 42) + + const { msg } = await once(stream, 'data') + assert.equal(msg, '422') +}) diff --git a/services/slides/node_modules/pino/test/stdout-protection.test.js b/services/slides/node_modules/pino/test/stdout-protection.test.js new file mode 100644 index 0000000000000000000000000000000000000000..d557e5dbc166d461069f45128dbef66399e5bcd8 --- /dev/null +++ b/services/slides/node_modules/pino/test/stdout-protection.test.js @@ -0,0 +1,41 @@ +'use strict' + +const test = require('node:test') +const assert = require('node:assert') +const { join } = require('node:path') +const { fork } = require('node:child_process') +const writer = require('flush-write-stream') + +const { once } = require('./helper') +const pino = require('..') + +test('do not use SonicBoom is someone tampered with process.stdout.write', async () => { + let actual = '' + const child = fork(join(__dirname, 'fixtures', 'stdout-hack-protection.js'), { silent: true }) + + child.stdout.pipe(writer((s, enc, cb) => { + actual += s + cb() + })) + await once(child, 'close') + assert.equal(actual.match(/^hack/) != null, true) +}) + +test('do not use SonicBoom is someone has passed process.stdout to pino', async () => { + const logger = pino(process.stdout) + assert.equal(logger[pino.symbols.streamSym], process.stdout) +}) + +test('do not crash if process.stdout has no fd', async (t) => { + const fd = process.stdout.fd + delete process.stdout.fd + t.after(function () { process.stdout.fd = fd }) + pino() +}) + +test('use fd=1 if process.stdout has no fd in pino.destination() (worker case)', async (t) => { + const fd = process.stdout.fd + delete process.stdout.fd + t.after(function () { process.stdout.fd = fd }) + pino.destination() +}) diff --git a/services/slides/node_modules/pino/test/syncfalse.test.js b/services/slides/node_modules/pino/test/syncfalse.test.js new file mode 100644 index 0000000000000000000000000000000000000000..d39c1dca21d3c089d56388294ccca90d3557049b --- /dev/null +++ b/services/slides/node_modules/pino/test/syncfalse.test.js @@ -0,0 +1,186 @@ +'use strict' + +const test = require('node:test') +const assert = require('node:assert') +const os = require('node:os') +const { promises: { readFile }, createWriteStream } = require('node:fs') +const { join } = require('node:path') +const { fork } = require('node:child_process') +const writer = require('flush-write-stream') +const { + once, + getPathToNull, + file, + watchFileCreated +} = require('./helper') +const { promisify } = require('node:util') +const tspl = require('@matteo.collina/tspl') + +const sleep = promisify(setTimeout) + +test('asynchronous logging', async (t) => { + const now = Date.now + const hostname = os.hostname + const proc = process + global.process = { + __proto__: process, + pid: 123456 + } + Date.now = () => 1459875739796 + os.hostname = () => 'abcdefghijklmnopqr' + delete require.cache[require.resolve('../')] + const pino = require('../') + let expected = '' + let actual = '' + const normal = pino(writer((s, enc, cb) => { + expected += s + cb() + })) + + const dest = createWriteStream(getPathToNull()) + dest.write = (s) => { + actual += s + } + const asyncLogger = pino(dest) + + let i = 44 + while (i--) { + normal.info('h') + asyncLogger.info('h') + } + + const expected2 = expected.split('\n')[0] + let actual2 = '' + + const child = fork(join(__dirname, '/fixtures/syncfalse.js'), { silent: true }) + child.stdout.pipe(writer((s, enc, cb) => { + actual2 += s + cb() + })) + await once(child, 'close') + // Wait for the last write to be flushed + await sleep(100) + assert.equal(actual, expected) + assert.equal(actual2.trim(), expected2) + + t.after(() => { + os.hostname = hostname + Date.now = now + global.process = proc + }) +}) + +test('sync false with child', async (t) => { + const now = Date.now + const hostname = os.hostname + const proc = process + global.process = { + __proto__: process, + pid: 123456 + } + Date.now = function () { + return 1459875739796 + } + os.hostname = function () { + return 'abcdefghijklmnopqr' + } + delete require.cache[require.resolve('../')] + const pino = require('../') + let expected = '' + let actual = '' + const normal = pino(writer((s, enc, cb) => { + expected += s + cb() + })).child({ hello: 'world' }) + + const dest = createWriteStream(getPathToNull()) + dest.write = function (s) { + actual += s + } + const asyncLogger = pino(dest).child({ hello: 'world' }) + + let i = 500 + while (i--) { + normal.info('h') + asyncLogger.info('h') + } + + asyncLogger.flush() + + const expected2 = expected.split('\n')[0] + let actual2 = '' + + const child = fork(join(__dirname, '/fixtures/syncfalse-child.js'), { silent: true }) + child.stdout.pipe(writer((s, enc, cb) => { + actual2 += s + cb() + })) + await once(child, 'close') + assert.equal(actual, expected) + assert.equal(actual2.trim(), expected2) + + t.after(() => { + os.hostname = hostname + Date.now = now + global.process = proc + }) +}) + +test('flush does nothing with sync true (default)', async () => { + const instance = require('..')() + assert.equal(instance.flush(), undefined) +}) + +test('should still call flush callback even when does nothing with sync true (default)', async (t) => { + const plan = tspl(t, { plan: 3 }) + const instance = require('..')() + instance.flush((...args) => { + plan.ok('flush called') + plan.deepEqual(args, []) + + // next tick to make flush not called more than once + process.nextTick(() => { + plan.ok('flush next tick called') + }) + }) + + await plan +}) + +test('should call the flush callback when flushed the data for async logger', async () => { + const outputPath = file() + async function getOutputLogLines () { + return (await readFile(outputPath)).toString().trim().split('\n').map(JSON.parse) + } + + const pino = require('../') + + const instance = pino({}, pino.destination({ + dest: outputPath, + + // to make sure it does not flush on its own + minLength: 4096 + })) + const flushPromise = promisify(instance.flush).bind(instance) + + instance.info('hello') + await flushPromise() + await watchFileCreated(outputPath) + + const [firstFlushData] = await getOutputLogLines() + + assert.equal(firstFlushData.msg, 'hello') + + // should not flush this as no data accumulated that's bigger than min length + instance.info('world') + + // Making sure data is not flushed yet + const afterLogData = await getOutputLogLines() + assert.equal(afterLogData.length, 1) + + await flushPromise() + + // Making sure data is not flushed yet + const afterSecondFlush = (await getOutputLogLines())[1] + assert.equal(afterSecondFlush.msg, 'world') +}) diff --git a/services/slides/node_modules/pino/test/timestamp-nano.test.js b/services/slides/node_modules/pino/test/timestamp-nano.test.js new file mode 100644 index 0000000000000000000000000000000000000000..ce6ecece5a5a3d143d951fb658c37a1753ecce20 --- /dev/null +++ b/services/slides/node_modules/pino/test/timestamp-nano.test.js @@ -0,0 +1,37 @@ +'use strict' + +/* eslint no-prototype-builtins: 0 */ + +const test = require('node:test') +const assert = require('node:assert') + +const { sink, once } = require('./helper') + +test('pino.stdTimeFunctions.isoTimeNano returns RFC 3339 timestamps', async () => { + // Mock Date.now at module initialization time + const now = Date.now + Date.now = () => new Date('2025-08-01T15:03:45.000000000Z').getTime() + + // Mock process.hrtime.bigint at module initialization time + const hrTimeBigint = process.hrtime.bigint + process.hrtime.bigint = () => 100000000000000n + + const pino = require('../') + + const opts = { + timestamp: pino.stdTimeFunctions.isoTimeNano + } + const stream = sink() + + // Mock process.hrtime.bigint at invocation time, add 1 day to the timestamp + process.hrtime.bigint = () => 100000000000000n + 86400012345678n + + const instance = pino(opts, stream) + instance.info('foobar') + const result = await once(stream, 'data') + assert.equal(result.hasOwnProperty('time'), true) + assert.equal(result.time, '2025-08-02T15:03:45.012345678Z') + + Date.now = now + process.hrtime.bigint = hrTimeBigint +}) diff --git a/services/slides/node_modules/pino/test/timestamp.test.js b/services/slides/node_modules/pino/test/timestamp.test.js new file mode 100644 index 0000000000000000000000000000000000000000..f7d4a014325386f3aa02d881fe717f6ef12112f9 --- /dev/null +++ b/services/slides/node_modules/pino/test/timestamp.test.js @@ -0,0 +1,124 @@ +'use strict' + +/* eslint no-prototype-builtins: 0 */ + +const test = require('node:test') +const assert = require('node:assert') + +const { sink, once } = require('./helper') +const pino = require('../') + +test('pino exposes standard time functions', async () => { + assert.ok(pino.stdTimeFunctions) + assert.ok(pino.stdTimeFunctions.epochTime) + assert.ok(pino.stdTimeFunctions.unixTime) + assert.ok(pino.stdTimeFunctions.nullTime) + assert.ok(pino.stdTimeFunctions.isoTime) + assert.ok(pino.stdTimeFunctions.isoTimeNano) +}) + +test('pino accepts external time functions', async () => { + const opts = { + timestamp: () => ',"time":"none"' + } + const stream = sink() + const instance = pino(opts, stream) + instance.info('foobar') + const result = await once(stream, 'data') + assert.equal(result.hasOwnProperty('time'), true) + assert.equal(result.time, 'none') +}) + +test('pino accepts external time functions with custom label', async () => { + const opts = { + timestamp: () => ',"custom-time-label":"none"' + } + const stream = sink() + const instance = pino(opts, stream) + instance.info('foobar') + const result = await once(stream, 'data') + assert.equal(result.hasOwnProperty('custom-time-label'), true) + assert.equal(result['custom-time-label'], 'none') +}) + +test('inserts timestamp by default', async ({ ok, equal }) => { + const stream = sink() + const instance = pino(stream) + instance.info('foobar') + const result = await once(stream, 'data') + assert.equal(result.hasOwnProperty('time'), true) + assert.ok(new Date(result.time) <= new Date(), 'time is greater than timestamp') + assert.equal(result.msg, 'foobar') +}) + +test('omits timestamp when timestamp option is false', async () => { + const stream = sink() + const instance = pino({ timestamp: false }, stream) + instance.info('foobar') + const result = await once(stream, 'data') + assert.equal(result.hasOwnProperty('time'), false) + assert.equal(result.msg, 'foobar') +}) + +test('inserts timestamp when timestamp option is true', async ({ ok, equal }) => { + const stream = sink() + const instance = pino({ timestamp: true }, stream) + instance.info('foobar') + const result = await once(stream, 'data') + assert.equal(result.hasOwnProperty('time'), true) + assert.ok(new Date(result.time) <= new Date(), 'time is greater than timestamp') + assert.equal(result.msg, 'foobar') +}) + +test('child inserts timestamp by default', async ({ ok, equal }) => { + const stream = sink() + const logger = pino(stream) + const instance = logger.child({ component: 'child' }) + instance.info('foobar') + const result = await once(stream, 'data') + assert.equal(result.hasOwnProperty('time'), true) + assert.ok(new Date(result.time) <= new Date(), 'time is greater than timestamp') + assert.equal(result.msg, 'foobar') +}) + +test('child omits timestamp with option', async () => { + const stream = sink() + const logger = pino({ timestamp: false }, stream) + const instance = logger.child({ component: 'child' }) + instance.info('foobar') + const result = await once(stream, 'data') + assert.equal(result.hasOwnProperty('time'), false) + assert.equal(result.msg, 'foobar') +}) + +test('pino.stdTimeFunctions.unixTime returns seconds based timestamps', async () => { + const opts = { + timestamp: pino.stdTimeFunctions.unixTime + } + const stream = sink() + const instance = pino(opts, stream) + const now = Date.now + Date.now = () => 1531069919686 + instance.info('foobar') + const result = await once(stream, 'data') + assert.equal(result.hasOwnProperty('time'), true) + assert.equal(result.time, 1531069920) + Date.now = now +}) + +test('pino.stdTimeFunctions.isoTime returns ISO 8601 timestamps', async () => { + const opts = { + timestamp: pino.stdTimeFunctions.isoTime + } + const stream = sink() + const instance = pino(opts, stream) + const ms = 1531069919686 + const now = Date.now + Date.now = () => ms + const iso = new Date(ms).toISOString() + instance.info('foobar') + const result = await once(stream, 'data') + assert.equal(result.hasOwnProperty('time'), true) + assert.equal(result.time, iso) + Date.now = now +}) diff --git a/services/slides/node_modules/pino/test/transport-stream.test.js b/services/slides/node_modules/pino/test/transport-stream.test.js new file mode 100644 index 0000000000000000000000000000000000000000..865d9b8d3f4f333a73b068375e06df990015aaa8 --- /dev/null +++ b/services/slides/node_modules/pino/test/transport-stream.test.js @@ -0,0 +1,40 @@ +'use strict' + +const test = require('node:test') +const proxyquire = require('proxyquire') +const tspl = require('@matteo.collina/tspl') + +test('should import', async (t) => { + const plan = tspl(t, { plan: 2 }) + const mockRealRequire = (target) => { + return { + default: { + default: () => { + plan.equal(target, 'pino-pretty') + return Promise.resolve() + } + } + } + } + const mockRealImport = async () => { + await Promise.resolve() + throw Object.assign(new Error(), { code: 'ERR_MODULE_NOT_FOUND' }) + } + + const loadTransportStreamBuilder = proxyquire( + '../lib/transport-stream.js', + { + 'real-require': { + realRequire: mockRealRequire, + realImport: mockRealImport + } + } + ) + + const fn = await loadTransportStreamBuilder('pino-pretty') + + await fn() + plan.ok('returned promise resolved') + + await plan +}) diff --git a/services/slides/node_modules/pino/test/transport/big.test.js b/services/slides/node_modules/pino/test/transport/big.test.js new file mode 100644 index 0000000000000000000000000000000000000000..fef70b7755b5212a108eb5be2fd8a2f831082dfa --- /dev/null +++ b/services/slides/node_modules/pino/test/transport/big.test.js @@ -0,0 +1,42 @@ +'use strict' + +const test = require('node:test') +const assert = require('node:assert') +const { join } = require('node:path') +const { createReadStream } = require('node:fs') +const { promisify } = require('node:util') +const stream = require('node:stream') +const execa = require('execa') +const split = require('split2') + +const { file } = require('../helper') + +const pipeline = promisify(stream.pipeline) +const { Writable } = stream +const sleep = promisify(setTimeout) + +const skip = process.env.CI || process.env.CITGM + +test('eight million lines', { skip }, async () => { + const destination = file() + await execa(process.argv[0], [join(__dirname, '..', 'fixtures', 'transport-many-lines.js'), destination]) + + if (process.platform !== 'win32') { + try { + await execa('sync') // Wait for the file to be written to disk + } catch { + // Just a fallback, this should be unreachable + } + } + await sleep(1_000) // It seems that sync is not enough (even in POSIX systems) + + const toWrite = 8 * 1_000_000 + let count = 0 + await pipeline(createReadStream(destination), split(), new Writable({ + write (chunk, enc, cb) { + count++ + cb() + } + })) + assert.equal(count, toWrite) +}) diff --git a/services/slides/node_modules/pino/test/transport/bundlers-support.test.js b/services/slides/node_modules/pino/test/transport/bundlers-support.test.js new file mode 100644 index 0000000000000000000000000000000000000000..45ddfd5315a6f0cbf70c23d050ad60cb063b81a4 --- /dev/null +++ b/services/slides/node_modules/pino/test/transport/bundlers-support.test.js @@ -0,0 +1,99 @@ +'use strict' + +const test = require('node:test') +const assert = require('node:assert') +const os = require('node:os') +const { join } = require('node:path') +const { readFile } = require('node:fs').promises + +const { watchFileCreated, file } = require('../helper') +const pino = require('../../pino') + +const { pid } = process +const hostname = os.hostname() + +test('pino.transport with destination overridden by bundler', async (t) => { + globalThis.__bundlerPathsOverrides = { + foobar: join(__dirname, '..', 'fixtures', 'to-file-transport.js') + } + + const destination = file() + const transport = pino.transport({ + target: 'foobar', + options: { destination } + }) + t.after(transport.end.bind(transport)) + const instance = pino(transport) + instance.info('hello') + await watchFileCreated(destination) + const result = JSON.parse(await readFile(destination)) + delete result.time + assert.deepEqual(result, { + pid, + hostname, + level: 30, + msg: 'hello' + }) + + globalThis.__bundlerPathsOverrides = undefined +}) + +test('pino.transport with worker destination overridden by bundler', async (t) => { + globalThis.__bundlerPathsOverrides = { + 'pino-worker': join(__dirname, '..', '..', 'lib/worker.js') + } + + const destination = file() + const transport = pino.transport({ + targets: [ + { + target: join(__dirname, '..', 'fixtures', 'to-file-transport.js'), + options: { destination } + } + ] + }) + t.after(transport.end.bind(transport)) + const instance = pino(transport) + instance.info('hello') + await watchFileCreated(destination) + const result = JSON.parse(await readFile(destination)) + delete result.time + assert.deepEqual(result, { + pid, + hostname, + level: 30, + msg: 'hello' + }) + + globalThis.__bundlerPathsOverrides = undefined +}) + +test('pino.transport with worker destination overridden by bundler and mjs transport', async (t) => { + globalThis.__bundlerPathsOverrides = { + 'pino-worker': join(__dirname, '..', '..', 'lib/worker.js') + } + + const destination = file() + const transport = pino.transport({ + targets: [ + { + target: join(__dirname, '..', 'fixtures', 'ts', 'to-file-transport.es2017.cjs'), + options: { destination } + } + ] + }) + t.after(transport.end.bind(transport)) + const instance = pino(transport) + instance.info('hello') + await watchFileCreated(destination) + const result = JSON.parse(await readFile(destination)) + delete result.time + assert.deepEqual(result, { + pid, + hostname, + level: 30, + msg: 'hello' + }) + + globalThis.__bundlerPathsOverrides = undefined +}) diff --git a/services/slides/node_modules/pino/test/transport/caller.test.js b/services/slides/node_modules/pino/test/transport/caller.test.js new file mode 100644 index 0000000000000000000000000000000000000000..b5f22c036412468a44fa6c2f7f7933cde7e93eb6 --- /dev/null +++ b/services/slides/node_modules/pino/test/transport/caller.test.js @@ -0,0 +1,24 @@ +'use strict' + +const test = require('node:test') +const assert = require('node:assert') +const { join } = require('node:path') +const execa = require('execa') + +test('when using a custom transport outside node_modules, the first file outside node_modules should be used', async function () { + const evalApp = join(__dirname, '../', '/fixtures/eval/index.js') + const { stdout } = await execa(process.argv[0], [evalApp]) + assert.match(stdout, /done!/) +}) + +test('when using a custom transport where some files in stacktrace are in the node_modules, the first file outside node_modules should be used', async function () { + const evalApp = join(__dirname, '../', '/fixtures/eval/node_modules/2-files.js') + const { stdout } = await execa(process.argv[0], [evalApp]) + assert.match(stdout, /done!/) +}) + +test('when using a custom transport where all files in stacktrace are in the node_modules, the first file inside node_modules should be used', async function () { + const evalApp = join(__dirname, '../', '/fixtures/eval/node_modules/14-files.js') + const { stdout } = await execa(process.argv[0], [evalApp]) + assert.match(stdout, /done!/) +}) diff --git a/services/slides/node_modules/pino/test/transport/core.test.js b/services/slides/node_modules/pino/test/transport/core.test.js new file mode 100644 index 0000000000000000000000000000000000000000..feec847cfeb0c36da740cbfc28a05cb7479bbe46 --- /dev/null +++ b/services/slides/node_modules/pino/test/transport/core.test.js @@ -0,0 +1,733 @@ +'use strict' + +const test = require('node:test') +const assert = require('node:assert') +const os = require('node:os') +const { join } = require('node:path') +const { once } = require('node:events') +const { setImmediate: immediate } = require('node:timers/promises') +const { readFile, writeFile } = require('node:fs').promises +const url = require('url') +const strip = require('strip-ansi') +const execa = require('execa') +const writer = require('flush-write-stream') +const rimraf = require('rimraf') +const tspl = require('@matteo.collina/tspl') + +const { match, watchFileCreated, watchForWrite, file } = require('../helper') +const pino = require('../../') + +const { tmpdir } = os +const pid = process.pid +const hostname = os.hostname() + +test('pino.transport with file', async (t) => { + const destination = file() + const transport = pino.transport({ + target: join(__dirname, '..', 'fixtures', 'to-file-transport.js'), + options: { destination } + }) + t.after(transport.end.bind(transport)) + const instance = pino(transport) + instance.info('hello') + await watchFileCreated(destination) + const result = JSON.parse(await readFile(destination)) + delete result.time + assert.deepEqual(result, { + pid, + hostname, + level: 30, + msg: 'hello' + }) +}) + +test('pino.transport with file (no options + error handling)', async () => { + const transport = pino.transport({ + target: join(__dirname, '..', 'fixtures', 'to-file-transport.js') + }) + const [err] = await once(transport, 'error') + assert.equal(err.message, 'kaboom') +}) + +test('pino.transport with file URL', async (t) => { + const destination = file() + const transport = pino.transport({ + target: url.pathToFileURL(join(__dirname, '..', 'fixtures', 'to-file-transport.js')).href, + options: { destination } + }) + t.after(transport.end.bind(transport)) + const instance = pino(transport) + instance.info('hello') + await watchFileCreated(destination) + const result = JSON.parse(await readFile(destination)) + delete result.time + assert.deepEqual(result, { + pid, + hostname, + level: 30, + msg: 'hello' + }) +}) + +test('pino.transport errors if file does not exists', (t, end) => { + const instance = pino.transport({ + target: join(__dirname, '..', 'fixtures', 'non-existent-file'), + worker: { + stdin: true, + stdout: true, + stderr: true + } + }) + instance.on('error', function () { + assert.ok('error received') + end() + }) +}) + +test('pino.transport errors if transport worker module does not export a function', async (t) => { + // TODO: add case for non-pipelined single target (needs changes in thread-stream) + const plan = tspl(t, { plan: 2 }) + const manyTargetsInstance = pino.transport({ + targets: [{ + level: 'info', + target: join(__dirname, '..', 'fixtures', 'transport-wrong-export-type.js') + }, { + level: 'info', + target: join(__dirname, '..', 'fixtures', 'transport-wrong-export-type.js') + }] + }) + manyTargetsInstance.on('error', function (e) { + plan.equal(e.message, 'exported worker is not a function') + }) + + const pipelinedInstance = pino.transport({ + pipeline: [{ + target: join(__dirname, '..', 'fixtures', 'transport-wrong-export-type.js') + }] + }) + pipelinedInstance.on('error', function (e) { + plan.equal(e.message, 'exported worker is not a function') + }) + + await plan +}) + +test('pino.transport with esm', async (t) => { + const destination = file() + const transport = pino.transport({ + target: join(__dirname, '..', 'fixtures', 'to-file-transport.mjs'), + options: { destination } + }) + const instance = pino(transport) + t.after(transport.end.bind(transport)) + instance.info('hello') + await watchFileCreated(destination) + const result = JSON.parse(await readFile(destination)) + delete result.time + assert.deepEqual(result, { + pid, + hostname, + level: 30, + msg: 'hello' + }) +}) + +test('pino.transport with two files', async (t) => { + const dest1 = file() + const dest2 = file() + const transport = pino.transport({ + targets: [{ + level: 'info', + target: 'file://' + join(__dirname, '..', 'fixtures', 'to-file-transport.js'), + options: { destination: dest1 } + }, { + level: 'info', + target: join(__dirname, '..', 'fixtures', 'to-file-transport.js'), + options: { destination: dest2 } + }] + }) + t.after(transport.end.bind(transport)) + const instance = pino(transport) + instance.info('hello') + await Promise.all([watchFileCreated(dest1), watchFileCreated(dest2)]) + const result1 = JSON.parse(await readFile(dest1)) + delete result1.time + assert.deepEqual(result1, { + pid, + hostname, + level: 30, + msg: 'hello' + }) + const result2 = JSON.parse(await readFile(dest2)) + delete result2.time + assert.deepEqual(result2, { + pid, + hostname, + level: 30, + msg: 'hello' + }) +}) + +test('pino.transport with two files and custom levels', async (t) => { + const dest1 = file() + const dest2 = file() + const transport = pino.transport({ + targets: [{ + level: 'info', + target: join(__dirname, '..', 'fixtures', 'to-file-transport.js'), + options: { destination: dest1 } + }, { + level: 'foo', + target: join(__dirname, '..', 'fixtures', 'to-file-transport.js'), + options: { destination: dest2 } + }], + levels: { trace: 10, debug: 20, info: 30, warn: 40, error: 50, fatal: 60, foo: 25 } + }) + t.after(transport.end.bind(transport)) + const instance = pino(transport) + instance.info('hello') + await Promise.all([watchFileCreated(dest1), watchFileCreated(dest2)]) + const result1 = JSON.parse(await readFile(dest1)) + delete result1.time + assert.deepEqual(result1, { + pid, + hostname, + level: 30, + msg: 'hello' + }) + const result2 = JSON.parse(await readFile(dest2)) + delete result2.time + assert.deepEqual(result2, { + pid, + hostname, + level: 30, + msg: 'hello' + }) +}) + +test('pino.transport without specifying default levels', async (t) => { + const dest = file() + const transport = pino.transport({ + targets: [{ + level: 'foo', + target: join(__dirname, '..', 'fixtures', 'to-file-transport.js'), + options: { destination: dest } + }], + levels: { foo: 25 } + }) + t.after(transport.end.bind(transport)) + const instance = pino(transport) + instance.info('hello') + await Promise.all([watchFileCreated(dest)]) + const result1 = JSON.parse(await readFile(dest)) + delete result1.time + assert.deepEqual(result1, { + pid, + hostname, + level: 30, + msg: 'hello' + }) +}) + +test('pino.transport with two files and dedupe', async (t) => { + const dest1 = file() + const dest2 = file() + const transport = pino.transport({ + dedupe: true, + targets: [{ + level: 'info', + target: join(__dirname, '..', 'fixtures', 'to-file-transport.js'), + options: { destination: dest1 } + }, { + level: 'error', + target: join(__dirname, '..', 'fixtures', 'to-file-transport.js'), + options: { destination: dest2 } + }] + }) + t.after(transport.end.bind(transport)) + const instance = pino(transport) + instance.info('hello') + instance.error('world') + await Promise.all([watchFileCreated(dest1), watchFileCreated(dest2)]) + const result1 = JSON.parse(await readFile(dest1)) + delete result1.time + assert.deepEqual(result1, { + pid, + hostname, + level: 30, + msg: 'hello' + }) + const result2 = JSON.parse(await readFile(dest2)) + delete result2.time + assert.deepEqual(result2, { + pid, + hostname, + level: 50, + msg: 'world' + }) +}) + +test('pino.transport with an array including a pino-pretty destination', async (t) => { + const dest1 = file() + const dest2 = file() + const transport = pino.transport({ + targets: [{ + level: 'info', + target: 'pino/file', + options: { + destination: dest1 + } + }, { + level: 'info', + target: 'pino-pretty', + options: { + destination: dest2 + } + }] + }) + t.after(transport.end.bind(transport)) + const instance = pino(transport) + instance.info('hello') + await Promise.all([watchFileCreated(dest1), watchFileCreated(dest2)]) + const result1 = JSON.parse(await readFile(dest1)) + delete result1.time + assert.deepEqual(result1, { + pid, + hostname, + level: 30, + msg: 'hello' + }) + const actual = (await readFile(dest2)).toString() + assert.match(strip(actual), /\[.*\] INFO.*hello/) +}) + +test('no transport.end()', async (t) => { + const destination = file() + const transport = pino.transport({ + target: join(__dirname, '..', 'fixtures', 'to-file-transport.js'), + options: { destination } + }) + const instance = pino(transport) + instance.info('hello') + await watchFileCreated(destination) + const result = JSON.parse(await readFile(destination)) + delete result.time + assert.deepEqual(result, { + pid, + hostname, + level: 30, + msg: 'hello' + }) +}) + +test('autoEnd = false', async (t) => { + const destination = file() + const count = process.listenerCount('exit') + const transport = pino.transport({ + target: join(__dirname, '..', 'fixtures', 'to-file-transport.js'), + options: { destination }, + worker: { autoEnd: false } + }) + t.after(transport.end.bind(transport)) + await once(transport, 'ready') + + const instance = pino(transport) + instance.info('hello') + + await watchFileCreated(destination) + + assert.equal(count, process.listenerCount('exit')) + + const result = JSON.parse(await readFile(destination)) + delete result.time + assert.deepEqual(result, { + pid, + hostname, + level: 30, + msg: 'hello' + }) +}) + +test('pino.transport with target and targets', async () => { + assert.throws( + () => { + pino.transport({ + target: '/a/file', + targets: [{ + target: '/a/file' + }] + }) + }, + /only one of target or targets can be specified/ + ) +}) + +test('pino.transport with target pino/file', async (t) => { + const destination = file() + const transport = pino.transport({ + target: 'pino/file', + options: { destination } + }) + t.after(transport.end.bind(transport)) + const instance = pino(transport) + instance.info('hello') + await watchFileCreated(destination) + const result = JSON.parse(await readFile(destination)) + delete result.time + assert.deepEqual(result, { + pid, + hostname, + level: 30, + msg: 'hello' + }) +}) + +test('pino.transport with target pino/file and mkdir option', async (t) => { + const folder = join(tmpdir(), `pino-${process.pid}-mkdir-transport-file`) + const destination = join(folder, 'log.txt') + t.after(() => { + try { + rimraf.sync(folder) + } catch (err) { + // ignore + } + }) + const transport = pino.transport({ + target: 'pino/file', + options: { destination, mkdir: true } + }) + t.after(transport.end.bind(transport)) + const instance = pino(transport) + instance.info('hello') + await watchFileCreated(destination) + const result = JSON.parse(await readFile(destination)) + delete result.time + assert.deepEqual(result, { + pid, + hostname, + level: 30, + msg: 'hello' + }) +}) + +test('pino.transport with target pino/file and append option', async (t) => { + const destination = file() + await writeFile(destination, JSON.stringify({ pid, hostname, time: Date.now(), level: 30, msg: 'hello' })) + const transport = pino.transport({ + target: 'pino/file', + options: { destination, append: false } + }) + t.after(transport.end.bind(transport)) + const instance = pino(transport) + instance.info('goodbye') + await watchForWrite(destination, '"goodbye"') + const result = JSON.parse(await readFile(destination)) + delete result.time + assert.deepEqual(result, { + pid, + hostname, + level: 30, + msg: 'goodbye' + }) +}) + +test('pino.transport should error with unknown target', async () => { + assert.throws( + () => { + pino.transport({ + target: 'origin', + caller: 'unknown-file.js' + }) + }, + /unable to determine transport target for "origin"/ + ) +}) + +test('pino.transport with target pino-pretty', async (t) => { + const destination = file() + const transport = pino.transport({ + target: 'pino-pretty', + options: { destination } + }) + t.after(transport.end.bind(transport)) + const instance = pino(transport) + instance.info('hello') + await watchFileCreated(destination) + const actual = await readFile(destination, 'utf8') + assert.match(strip(actual), /\[.*\] INFO.*hello/) +}) + +test('sets worker data informing the transport that pino will send its config', async (t) => { + const plan = tspl(t, { plan: 1 }) + const transport = pino.transport({ + target: join(__dirname, '..', 'fixtures', 'transport-worker-data.js') + }) + t.after(transport.end.bind(transport)) + const instance = pino(transport) + transport.once('workerData', (workerData) => { + match(workerData.workerData, { pinoWillSendConfig: true }) + plan.ok('passed') + }) + instance.info('hello') + + await plan +}) + +test('sets worker data informing the transport that pino will send its config (frozen file)', async (t) => { + const plan = tspl(t, { plan: 1 }) + const config = { + transport: { + target: join(__dirname, '..', 'fixtures', 'transport-worker-data.js'), + options: {} + } + } + Object.freeze(config) + Object.freeze(config.transport) + Object.freeze(config.transport.options) + const instance = pino(config) + const transport = instance[pino.symbols.streamSym] + t.after(transport.end.bind(transport)) + transport.once('workerData', (workerData) => { + match(workerData.workerData, { pinoWillSendConfig: true }) + plan.ok('passed') + }) + instance.info('hello') + + await plan +}) + +test('stdout in worker', async () => { + let actual = '' + const child = execa(process.argv[0], [join(__dirname, '..', 'fixtures', 'transport-main.js')]) + + for await (const chunk of child.stdout) { + actual += chunk + } + assert.equal(strip(actual).match(/Hello/) != null, true) +}) + +test('log and exit on ready', async () => { + let actual = '' + const child = execa(process.argv[0], [join(__dirname, '..', 'fixtures', 'transport-exit-on-ready.js')]) + + child.stdout.pipe(writer((s, enc, cb) => { + actual += s + cb() + })) + await once(child, 'close') + await immediate() + assert.equal(strip(actual).match(/Hello/) != null, true) +}) + +test('log and exit before ready', async () => { + let actual = '' + const child = execa(process.argv[0], [join(__dirname, '..', 'fixtures', 'transport-exit-immediately.js')]) + + child.stdout.pipe(writer((s, enc, cb) => { + actual += s + cb() + })) + await once(child, 'close') + await immediate() + assert.equal(strip(actual).match(/Hello/) != null, true) +}) + +test('log and exit before ready with async dest', async () => { + const destination = file() + const child = execa(process.argv[0], [join(__dirname, '..', 'fixtures', 'transport-exit-immediately-with-async-dest.js'), destination]) + + await once(child, 'exit') + + const actual = await readFile(destination, 'utf8') + assert.equal(strip(actual).match(/HELLO/) != null, true) + assert.equal(strip(actual).match(/WORLD/) != null, true) +}) + +test('string integer destination', async () => { + let actual = '' + const child = execa(process.argv[0], [join(__dirname, '..', 'fixtures', 'transport-string-stdout.js')]) + + child.stdout.pipe(writer((s, enc, cb) => { + actual += s + cb() + })) + await once(child, 'close') + await immediate() + assert.equal(strip(actual).match(/Hello/) != null, true) +}) + +test('pino transport options with target', async (t) => { + const destination = file() + const instance = pino({ + transport: { + target: 'pino/file', + options: { destination } + } + }) + const transportStream = instance[pino.symbols.streamSym] + t.after(transportStream.end.bind(transportStream)) + instance.info('transport option test') + await watchFileCreated(destination) + const result = JSON.parse(await readFile(destination)) + delete result.time + assert.deepEqual(result, { + pid, + hostname, + level: 30, + msg: 'transport option test' + }) +}) + +test('pino transport options with targets', async (t) => { + const dest1 = file() + const dest2 = file() + const instance = pino({ + transport: { + targets: [ + { target: 'pino/file', options: { destination: dest1 } }, + { target: 'pino/file', options: { destination: dest2 } } + ] + } + }) + const transportStream = instance[pino.symbols.streamSym] + t.after(transportStream.end.bind(transportStream)) + instance.info('transport option test') + + await Promise.all([watchFileCreated(dest1), watchFileCreated(dest2)]) + const result1 = JSON.parse(await readFile(dest1)) + delete result1.time + assert.deepEqual(result1, { + pid, + hostname, + level: 30, + msg: 'transport option test' + }) + const result2 = JSON.parse(await readFile(dest2)) + delete result2.time + assert.deepEqual(result2, { + pid, + hostname, + level: 30, + msg: 'transport option test' + }) +}) + +test('transport options with target and targets', async () => { + assert.throws( + () => { + pino({ + transport: { + target: {}, + targets: {} + } + }) + }, + /only one of target or targets can be specified/ + ) +}) + +test('transport options with target and stream', async () => { + assert.throws( + () => { + pino({ + transport: { + target: {} + } + }, '/log/null') + }, + /only one of option.transport or stream can be specified/ + ) +}) + +test('transport options with stream', async (t) => { + const dest1 = file() + const transportStream = pino.transport({ target: 'pino/file', options: { destination: dest1 } }) + t.after(transportStream.end.bind(transportStream)) + assert.throws( + () => { + pino({ + transport: transportStream + }) + }, + Error('option.transport do not allow stream, please pass to option directly. e.g. pino(transport)') + ) +}) + +test('pino.transport handles prototype pollution of __bundlerPathsOverrides', async (t) => { + // eslint-disable-next-line no-extend-native + Object.prototype.__bundlerPathsOverrides = { 'pino/file': '/malicious/path' } + t.after(() => { + delete Object.prototype.__bundlerPathsOverrides + }) + + const destination = file() + const transport = pino.transport({ + target: 'pino/file', + options: { destination } + }) + t.after(transport.end.bind(transport)) + + const instance = pino(transport) + instance.info('hello') + await watchFileCreated(destination) + const result = JSON.parse(await readFile(destination)) + delete result.time + assert.deepEqual(result, { + pid, + hostname, + level: 30, + msg: 'hello' + }) +}) + +const hasThreadName = 'threadName' in require('worker_threads') + +test('pino.transport with single target sets worker thread name to target', { skip: !hasThreadName }, async (t) => { + const plan = tspl(t, { plan: 1 }) + const transport = pino.transport({ + target: join(__dirname, '..', 'fixtures', 'transport-worker-name.js') + }) + t.after(transport.end.bind(transport)) + const instance = pino(transport) + transport.once('workerThreadName', (name) => { + plan.equal(name, join(__dirname, '..', 'fixtures', 'transport-worker-name.js')) + }) + instance.info('hello') + + await plan +}) + +test('pino.transport with targets sets worker thread name to pino.transport', { skip: !hasThreadName }, async (t) => { + const plan = tspl(t, { plan: 1 }) + const transport = pino.transport({ + targets: [{ + level: 'info', + target: join(__dirname, '..', 'fixtures', 'transport-worker-name.js') + }] + }) + t.after(transport.end.bind(transport)) + const instance = pino(transport) + transport.once('workerThreadName', (name) => { + plan.equal(name, 'pino.transport') + }) + instance.info('hello') + + await plan +}) + +test('pino.transport with pipeline sets worker thread name to pino.transport', { skip: !hasThreadName }, async (t) => { + const plan = tspl(t, { plan: 1 }) + const transport = pino.transport({ + pipeline: [{ + target: join(__dirname, '..', 'fixtures', 'transport-worker-name.js') + }] + }) + t.after(transport.end.bind(transport)) + const instance = pino(transport) + transport.once('workerThreadName', (name) => { + plan.equal(name, 'pino.transport') + }) + instance.info('hello') + + await plan +}) diff --git a/services/slides/node_modules/pino/test/transport/core.transpiled.test.ts b/services/slides/node_modules/pino/test/transport/core.transpiled.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..804d29b819474090d65a769f6dd5a05d8d05d62b --- /dev/null +++ b/services/slides/node_modules/pino/test/transport/core.transpiled.test.ts @@ -0,0 +1,114 @@ +import test from 'node:test' +import assert from 'node:assert' +import * as os from 'node:os' +import { join } from 'node:path' +import fs from 'node:fs' +import * as url from 'node:url' + +import { watchFileCreated } from '../helper' +import pino from '../../' + +const readFile = fs.promises.readFile + +const { pid } = process +const hostname = os.hostname() + +// A subset of the test from core.test.js, we don't need all of them to check for compatibility +function runTests (esVersion: string): void { + test(`(ts -> ${esVersion}) pino.transport with file`, async (t) => { + const destination = join( + os.tmpdir(), + '_' + Math.random().toString(36).substr(2, 9) + ) + const transport = pino.transport({ + target: join(__dirname, '..', 'fixtures', 'ts', `to-file-transport.${esVersion}.cjs`), + options: { destination } + }) + t.after(transport.end.bind(transport)) + const instance = pino(transport) + instance.info('hello') + await watchFileCreated(destination) + const result = JSON.parse(await readFile(destination, { encoding: 'utf8' })) + delete result.time + assert.deepEqual(result, { + pid, + hostname, + level: 30, + msg: 'hello' + }) + }) + + test(`(ts -> ${esVersion}) pino.transport with file URL`, async (t) => { + const destination = join( + os.tmpdir(), + '_' + Math.random().toString(36).substr(2, 9) + ) + const transport = pino.transport({ + target: url.pathToFileURL(join(__dirname, '..', 'fixtures', 'ts', `to-file-transport.${esVersion}.cjs`)).href, + options: { destination } + }) + t.after(transport.end.bind(transport)) + const instance = pino(transport) + instance.info('hello') + await watchFileCreated(destination) + const result = JSON.parse(await readFile(destination, { encoding: 'utf8' })) + delete result.time + assert.deepEqual(result, { + pid, + hostname, + level: 30, + msg: 'hello' + }) + }) + + test(`(ts -> ${esVersion}) pino.transport with two files`, async (t) => { + const dest1 = join( + os.tmpdir(), + '_' + Math.random().toString(36).substr(2, 9) + ) + const dest2 = join( + os.tmpdir(), + '_' + Math.random().toString(36).substr(2, 9) + ) + const transport = pino.transport({ + targets: [{ + level: 'info', + target: join(__dirname, '..', 'fixtures', 'ts', `to-file-transport.${esVersion}.cjs`), + options: { destination: dest1 } + }, { + level: 'info', + target: join(__dirname, '..', 'fixtures', 'ts', `to-file-transport.${esVersion}.cjs`), + options: { destination: dest2 } + }] + }) + + t.after(transport.end.bind(transport)) + + const instance = pino(transport) + instance.info('hello') + + await Promise.all([watchFileCreated(dest1), watchFileCreated(dest2)]) + + const result1 = JSON.parse(await readFile(dest1, { encoding: 'utf8' })) + delete result1.time + assert.deepEqual(result1, { + pid, + hostname, + level: 30, + msg: 'hello' + }) + const result2 = JSON.parse(await readFile(dest2, { encoding: 'utf8' })) + delete result2.time + assert.deepEqual(result2, { + pid, + hostname, + level: 30, + msg: 'hello' + }) + }) +} + +runTests('es5') +runTests('es6') +runTests('es2017') +runTests('esnext') diff --git a/services/slides/node_modules/pino/test/transport/crash.test.js b/services/slides/node_modules/pino/test/transport/crash.test.js new file mode 100644 index 0000000000000000000000000000000000000000..10cac0159639f79862c1263944958ca05f464018 --- /dev/null +++ b/services/slides/node_modules/pino/test/transport/crash.test.js @@ -0,0 +1,36 @@ +'use strict' + +const test = require('node:test') +const assert = require('node:assert') +const { join } = require('node:path') +const { once } = require('node:events') +const { setImmediate: immediate } = require('node:timers/promises') + +const pino = require('../../') + +test('pino.transport emits error if the worker exits with 0 unexpectably', async (t) => { + // This test will take 10s, because flushSync waits for 10s + const transport = pino.transport({ + target: join(__dirname, '..', 'fixtures', 'crashing-transport.js'), + sync: true + }) + t.after(transport.end.bind(transport)) + + await once(transport, 'ready') + + let maybeError + transport.on('error', (err) => { + maybeError = err + }) + + const logger = pino(transport) + for (let i = 0; i < 100000; i++) { + logger.info('hello') + } + + await once(transport.worker, 'exit') + + await immediate() + + assert.equal(maybeError.message, 'the worker has exited') +}) diff --git a/services/slides/node_modules/pino/test/transport/module-link.test.js b/services/slides/node_modules/pino/test/transport/module-link.test.js new file mode 100644 index 0000000000000000000000000000000000000000..32023f215100640a8d866737ed7b3dcad49bea97 --- /dev/null +++ b/services/slides/node_modules/pino/test/transport/module-link.test.js @@ -0,0 +1,241 @@ +'use strict' + +const test = require('node:test') +const assert = require('node:assert') +const os = require('node:os') +const { join } = require('node:path') +const { readFile, symlink, unlink, mkdir, writeFile } = require('node:fs').promises +const { once } = require('node:events') +const execa = require('execa') +const rimraf = require('rimraf') + +const { isWin, isYarnPnp, watchFileCreated, file } = require('../helper') +const pino = require('../../') + +const { pid } = process +const hostname = os.hostname() + +async function installTransportModule (target) { + if (isYarnPnp) { + return + } + try { + await uninstallTransportModule() + } catch {} + + if (!target) { + target = join(__dirname, '..', '..') + } + + await symlink( + join(__dirname, '..', 'fixtures', 'transport'), + join(target, 'node_modules', 'transport') + ) +} + +async function uninstallTransportModule () { + if (isYarnPnp) { + return + } + await unlink(join(__dirname, '..', '..', 'node_modules', 'transport')) +} + +// TODO make this test pass on Windows +test('pino.transport with package', { skip: isWin }, async (t) => { + const destination = file() + + await installTransportModule() + + const transport = pino.transport({ + target: 'transport', + options: { destination } + }) + + t.after(async () => { + await uninstallTransportModule() + transport.end() + }) + const instance = pino(transport) + instance.info('hello') + await watchFileCreated(destination) + const result = JSON.parse(await readFile(destination)) + delete result.time + assert.deepEqual(result, { + pid, + hostname, + level: 30, + msg: 'hello' + }) +}) + +// TODO make this test pass on Windows +test('pino.transport with package as a target', { skip: isWin }, async (t) => { + const destination = file() + + await installTransportModule() + + const transport = pino.transport({ + targets: [{ + target: 'transport', + options: { destination } + }] + }) + t.after(async () => { + await uninstallTransportModule() + transport.end() + }) + const instance = pino(transport) + instance.info('hello') + await watchFileCreated(destination) + const result = JSON.parse(await readFile(destination)) + delete result.time + assert.deepEqual(result, { + pid, + hostname, + level: 30, + msg: 'hello' + }) +}) + +// TODO make this test pass on Windows +test('pino({ transport })', { skip: isWin || isYarnPnp }, async (t) => { + const folder = join( + os.tmpdir(), + '_' + Math.random().toString(36).substr(2, 9) + ) + + t.after(() => { + rimraf.sync(folder) + }) + + const destination = join(folder, 'output') + + await mkdir(join(folder, 'node_modules'), { recursive: true }) + + // Link pino + await symlink( + join(__dirname, '..', '..'), + join(folder, 'node_modules', 'pino') + ) + + await installTransportModule(folder) + + const toRun = join(folder, 'index.js') + + const toRunContent = ` + const pino = require('pino') + const logger = pino({ + transport: { + target: 'transport', + options: { destination: '${destination}' } + } + }) + logger.info('hello') + ` + + await writeFile(toRun, toRunContent) + + const child = execa(process.argv[0], [toRun]) + + await once(child, 'close') + + const result = JSON.parse(await readFile(destination)) + delete result.time + assert.deepEqual(result, { + pid: child.pid, + hostname, + level: 30, + msg: 'hello' + }) +}) + +// TODO make this test pass on Windows +test('pino({ transport }) from a wrapped dependency', { skip: isWin || isYarnPnp }, async (t) => { + const folder = join( + os.tmpdir(), + '_' + Math.random().toString(36).substr(2, 9) + ) + + const wrappedFolder = join( + os.tmpdir(), + '_' + Math.random().toString(36).substr(2, 9) + ) + + const destination = join(folder, 'output') + + await mkdir(join(folder, 'node_modules'), { recursive: true }) + await mkdir(join(wrappedFolder, 'node_modules'), { recursive: true }) + + t.after(() => { + rimraf.sync(wrappedFolder) + rimraf.sync(folder) + }) + + // Link pino + await symlink( + join(__dirname, '..', '..'), + join(wrappedFolder, 'node_modules', 'pino') + ) + + // Link get-caller-file + await symlink( + join(__dirname, '..', '..', 'node_modules', 'get-caller-file'), + join(wrappedFolder, 'node_modules', 'get-caller-file') + ) + + // Link wrapped + await symlink( + wrappedFolder, + join(folder, 'node_modules', 'wrapped') + ) + + await installTransportModule(folder) + + const pkgjsonContent = { + name: 'pino' + } + + await writeFile(join(wrappedFolder, 'package.json'), JSON.stringify(pkgjsonContent)) + + const wrapped = join(wrappedFolder, 'index.js') + + const wrappedContent = ` + const pino = require('pino') + const getCaller = require('get-caller-file') + + module.exports = function build () { + const logger = pino({ + transport: { + caller: getCaller(), + target: 'transport', + options: { destination: '${destination}' } + } + }) + return logger + } + ` + + await writeFile(wrapped, wrappedContent) + + const toRun = join(folder, 'index.js') + + const toRunContent = ` + const logger = require('wrapped')() + logger.info('hello') + ` + + await writeFile(toRun, toRunContent) + + const child = execa(process.argv[0], [toRun]) + + await once(child, 'close') + + const result = JSON.parse(await readFile(destination)) + delete result.time + assert.deepEqual(result, { + pid: child.pid, + hostname, + level: 30, + msg: 'hello' + }) +}) diff --git a/services/slides/node_modules/pino/test/transport/native-type-stripping.test.mjs b/services/slides/node_modules/pino/test/transport/native-type-stripping.test.mjs new file mode 100644 index 0000000000000000000000000000000000000000..ed1d784fd0b2a5aee9734709f16e8bb93d272345 --- /dev/null +++ b/services/slides/node_modules/pino/test/transport/native-type-stripping.test.mjs @@ -0,0 +1,151 @@ +import test from 'node:test' +import assert from 'node:assert' +import * as os from 'node:os' +import { join } from 'node:path' +import fs from 'node:fs' +import * as url from 'node:url' + +import { watchFileCreated } from '../helper.js' + +const readFile = fs.promises.readFile + +const { pid } = process +const hostname = os.hostname() + +// Check if Node.js supports native type stripping (Node.js 22+) +function supportsTypeStripping () { + const major = parseInt(process.versions.node.split('.')[0], 10) + return major >= 22 +} + +// Only run these tests on Node.js 22+ +const skipTests = !supportsTypeStripping() +const skipMessage = 'Native TypeScript type stripping not supported (requires Node.js 22+)' + +test('pino.transport with native TypeScript file', { skip: skipTests ? skipMessage : false }, async (t) => { + const destination = join( + os.tmpdir(), + '_' + Math.random().toString(36).substr(2, 9) + ) + + // We need to dynamically import pino to ensure worker thread inherits flags + const { default: pino } = await import('../../pino.js') + + const transport = pino.transport({ + target: join(import.meta.dirname || url.fileURLToPath(new URL('.', import.meta.url)), '..', 'fixtures', 'ts', 'to-file-transport-native.mts'), + options: { destination } + }) + + t.after(() => { + transport.end() + try { + fs.unlinkSync(destination) + } catch {} + }) + + const instance = pino(transport) + instance.info('hello from native TypeScript transport') + + await watchFileCreated(destination) + const result = JSON.parse(await readFile(destination, { encoding: 'utf8' })) + delete result.time + + assert.deepEqual(result, { + pid, + hostname, + level: 30, + msg: 'hello from native TypeScript transport' + }) +}) + +test('pino.transport with native TypeScript file URL', { skip: skipTests ? skipMessage : false }, async (t) => { + const destination = join( + os.tmpdir(), + '_' + Math.random().toString(36).substr(2, 9) + ) + + const { default: pino } = await import('../../pino.js') + + const transport = pino.transport({ + target: url.pathToFileURL(join(import.meta.dirname || url.fileURLToPath(new URL('.', import.meta.url)), '..', 'fixtures', 'ts', 'to-file-transport-native.mts')).href, + options: { destination } + }) + + t.after(() => { + transport.end() + try { + fs.unlinkSync(destination) + } catch {} + }) + + const instance = pino(transport) + instance.info('hello from file URL transport') + + await watchFileCreated(destination) + const result = JSON.parse(await readFile(destination, { encoding: 'utf8' })) + delete result.time + + assert.deepEqual(result, { + pid, + hostname, + level: 30, + msg: 'hello from file URL transport' + }) +}) + +test('pino.transport with multiple native TypeScript targets', { skip: skipTests ? skipMessage : false }, async (t) => { + const dest1 = join( + os.tmpdir(), + '_' + Math.random().toString(36).substr(2, 9) + ) + const dest2 = join( + os.tmpdir(), + '_' + Math.random().toString(36).substr(2, 9) + ) + + const { default: pino } = await import('../../pino.js') + const fixtureDir = join(import.meta.dirname || url.fileURLToPath(new URL('.', import.meta.url)), '..', 'fixtures', 'ts') + + const transport = pino.transport({ + targets: [{ + level: 'info', + target: join(fixtureDir, 'to-file-transport-native.mts'), + options: { destination: dest1 } + }, { + level: 'info', + target: join(fixtureDir, 'to-file-transport-native.mts'), + options: { destination: dest2 } + }] + }) + + t.after(() => { + transport.end() + try { + fs.unlinkSync(dest1) + fs.unlinkSync(dest2) + } catch {} + }) + + const instance = pino(transport) + instance.info('hello from multiple targets') + + await Promise.all([watchFileCreated(dest1), watchFileCreated(dest2)]) + + const result1 = JSON.parse(await readFile(dest1, { encoding: 'utf8' })) + delete result1.time + assert.deepEqual(result1, { + pid, + hostname, + level: 30, + msg: 'hello from multiple targets' + }) + + const result2 = JSON.parse(await readFile(dest2, { encoding: 'utf8' })) + delete result2.time + assert.deepEqual(result2, { + pid, + hostname, + level: 30, + msg: 'hello from multiple targets' + }) +}) diff --git a/services/slides/node_modules/pino/test/transport/node-options.test.js b/services/slides/node_modules/pino/test/transport/node-options.test.js new file mode 100644 index 0000000000000000000000000000000000000000..6e69419cb355210ac5bf2ff91e6fc17bdf2fdf34 --- /dev/null +++ b/services/slides/node_modules/pino/test/transport/node-options.test.js @@ -0,0 +1,116 @@ +'use strict' + +const test = require('node:test') +const assert = require('node:assert') +const { EventEmitter } = require('node:events') +const { join } = require('node:path') +const { pathToFileURL } = require('node:url') +const proxyquire = require('proxyquire') + +function buildTransportWithFakeThreadStream () { + let lastCtorOpts + + class FakeThreadStream extends EventEmitter { + constructor (opts) { + super() + this._closed = false + lastCtorOpts = opts + } + + unref () {} + ref () {} + flushSync () {} + end () { + this._closed = true + this.emit('close') + } + + get closed () { + return this._closed + } + } + + const transport = proxyquire('../../lib/transport', { + 'thread-stream': FakeThreadStream + }) + + return { + transport, + getLastCtorOpts () { + return lastCtorOpts + } + } +} + +test('pino.transport sanitizes missing absolute preload in NODE_OPTIONS', () => { + const previous = process.env.NODE_OPTIONS + const missing = join(__dirname, '..', 'fixtures', 'missing-preload.js') + process.env.NODE_OPTIONS = `--require ${missing} --trace-warnings` + + const { transport, getLastCtorOpts } = buildTransportWithFakeThreadStream() + transport({ target: join(__dirname, '..', 'fixtures', 'to-file-transport.js') }) + + assert.equal(getLastCtorOpts().workerOpts.env.NODE_OPTIONS, '--trace-warnings') + + if (previous === undefined) { + delete process.env.NODE_OPTIONS + } else { + process.env.NODE_OPTIONS = previous + } +}) + +test('pino.transport sanitizes missing file:// preload in NODE_OPTIONS', () => { + const previous = process.env.NODE_OPTIONS + const missingFileUrl = pathToFileURL(join(__dirname, '..', 'fixtures', 'missing-import.mjs')).href + process.env.NODE_OPTIONS = `--import=${missingFileUrl}` + + const { transport, getLastCtorOpts } = buildTransportWithFakeThreadStream() + transport({ target: join(__dirname, '..', 'fixtures', 'to-file-transport.js') }) + + assert.equal(getLastCtorOpts().workerOpts.env.NODE_OPTIONS, '') + + if (previous === undefined) { + delete process.env.NODE_OPTIONS + } else { + process.env.NODE_OPTIONS = previous + } +}) + +test('pino.transport keeps relative preload flags in NODE_OPTIONS', () => { + const previous = process.env.NODE_OPTIONS + process.env.NODE_OPTIONS = '--require ./relative-preload.js' + + const { transport, getLastCtorOpts } = buildTransportWithFakeThreadStream() + transport({ target: join(__dirname, '..', 'fixtures', 'to-file-transport.js') }) + + assert.equal(getLastCtorOpts().workerOpts.env, undefined) + + if (previous === undefined) { + delete process.env.NODE_OPTIONS + } else { + process.env.NODE_OPTIONS = previous + } +}) + +test('pino.transport does not override explicit worker.env', () => { + const previous = process.env.NODE_OPTIONS + process.env.NODE_OPTIONS = `--require ${join(__dirname, '..', 'fixtures', 'missing-preload.js')}` + + const explicitEnv = { NODE_OPTIONS: '--trace-warnings' } + + const { transport, getLastCtorOpts } = buildTransportWithFakeThreadStream() + transport({ + target: join(__dirname, '..', 'fixtures', 'to-file-transport.js'), + worker: { + env: explicitEnv + } + }) + + assert.equal(getLastCtorOpts().workerOpts.env, explicitEnv) + + if (previous === undefined) { + delete process.env.NODE_OPTIONS + } else { + process.env.NODE_OPTIONS = previous + } +}) diff --git a/services/slides/node_modules/pino/test/transport/pipeline.test.js b/services/slides/node_modules/pino/test/transport/pipeline.test.js new file mode 100644 index 0000000000000000000000000000000000000000..8e86a8958c771d5e40c069a0ecd06dc24792d592 --- /dev/null +++ b/services/slides/node_modules/pino/test/transport/pipeline.test.js @@ -0,0 +1,137 @@ +'use strict' + +const test = require('node:test') +const assert = require('node:assert') +const os = require('node:os') +const { join } = require('node:path') +const { readFile } = require('node:fs').promises + +const { watchFileCreated, file } = require('../helper') +const pino = require('../../') +const { DEFAULT_LEVELS } = require('../../lib/constants') + +const { pid } = process +const hostname = os.hostname() + +test('pino.transport with a pipeline', async (t) => { + const destination = file() + const transport = pino.transport({ + pipeline: [{ + target: join(__dirname, '..', 'fixtures', 'transport-transform.js') + }, { + target: join(__dirname, '..', 'fixtures', 'to-file-transport.js'), + options: { destination } + }] + }) + t.after(transport.end.bind(transport)) + const instance = pino(transport) + instance.info('hello') + await watchFileCreated(destination) + const result = JSON.parse(await readFile(destination)) + delete result.time + assert.deepEqual(result, { + pid, + hostname, + level: DEFAULT_LEVELS.info, + msg: 'hello', + service: 'pino' // this property was added by the transform + }) +}) + +test('pino.transport with targets containing pipelines', async (t) => { + const destinationA = file() + const destinationB = file() + const transport = pino.transport({ + targets: [ + { + target: join(__dirname, '..', 'fixtures', 'to-file-transport.js'), + options: { destination: destinationA } + }, + { + pipeline: [ + { + target: join(__dirname, '..', 'fixtures', 'transport-transform.js') + }, + { + target: join(__dirname, '..', 'fixtures', 'to-file-transport.js'), + options: { destination: destinationB } + } + ] + } + ] + }) + + t.after(transport.end.bind(transport)) + const instance = pino(transport) + instance.info('hello') + await watchFileCreated(destinationA) + await watchFileCreated(destinationB) + const resultA = JSON.parse(await readFile(destinationA)) + const resultB = JSON.parse(await readFile(destinationB)) + delete resultA.time + delete resultB.time + assert.deepEqual(resultA, { + pid, + hostname, + level: DEFAULT_LEVELS.info, + msg: 'hello' + }) + assert.deepEqual(resultB, { + pid, + hostname, + level: DEFAULT_LEVELS.info, + msg: 'hello', + service: 'pino' // this property was added by the transform + }) +}) + +test('pino.transport with targets containing pipelines with levels defined and dedupe', async (t) => { + const destinationA = file() + const destinationB = file() + const transport = pino.transport({ + targets: [ + { + target: join(__dirname, '..', 'fixtures', 'to-file-transport.js'), + options: { destination: destinationA }, + level: DEFAULT_LEVELS.info + }, + { + pipeline: [ + { + target: join(__dirname, '..', 'fixtures', 'transport-transform.js') + }, + { + target: join(__dirname, '..', 'fixtures', 'to-file-transport.js'), + options: { destination: destinationB } + } + ], + level: DEFAULT_LEVELS.error + } + ], + dedupe: true + }) + + t.after(transport.end.bind(transport)) + const instance = pino(transport) + instance.info('hello info') + instance.error('hello error') + await watchFileCreated(destinationA) + await watchFileCreated(destinationB) + const resultA = JSON.parse(await readFile(destinationA)) + const resultB = JSON.parse(await readFile(destinationB)) + delete resultA.time + delete resultB.time + assert.deepEqual(resultA, { + pid, + hostname, + level: DEFAULT_LEVELS.info, + msg: 'hello info' + }) + assert.deepEqual(resultB, { + pid, + hostname, + level: DEFAULT_LEVELS.error, + msg: 'hello error', + service: 'pino' // this property was added by the transform + }) +}) diff --git a/services/slides/node_modules/pino/test/transport/preload.test.js b/services/slides/node_modules/pino/test/transport/preload.test.js new file mode 100644 index 0000000000000000000000000000000000000000..dbfc5b4594925172384489a3833e520f37cae41a --- /dev/null +++ b/services/slides/node_modules/pino/test/transport/preload.test.js @@ -0,0 +1,54 @@ +'use strict' + +const test = require('node:test') +const assert = require('node:assert') +const { join } = require('node:path') +const { pathToFileURL } = require('node:url') +const { readFile } = require('node:fs').promises +const execa = require('execa') + +const { file, watchFileCreated } = require('../helper') + +test('pino.transport works when loaded via --import=preload', async () => { + const destination = file() + const preload = pathToFileURL(join(__dirname, '..', 'fixtures', 'transport-preload.mjs')).href + const main = join(__dirname, '..', 'fixtures', 'transport-preload-main.mjs') + + await execa(process.argv[0], [ + `--import=${preload}`, + main, + destination + ], { timeout: 10000 }) + + await watchFileCreated(destination) + const result = JSON.parse(await readFile(destination)) + assert.equal(result.msg, 'hello from main') +}) + +test('pino.transport works when loaded via --import preload (space separated)', async () => { + const destination = file() + const preload = pathToFileURL(join(__dirname, '..', 'fixtures', 'transport-preload.mjs')).href + const main = join(__dirname, '..', 'fixtures', 'transport-preload-main.mjs') + + await execa(process.argv[0], [ + '--import', + preload, + main, + destination + ], { timeout: 10000 }) + + await watchFileCreated(destination) + const result = JSON.parse(await readFile(destination)) + assert.equal(result.msg, 'hello from main') +}) + +test('pino.transport ignores missing absolute preload from NODE_OPTIONS in worker', async () => { + const destination = file() + const main = join(__dirname, '..', 'fixtures', 'transport-invalid-node-options.js') + + await execa(process.argv[0], [main, destination], { timeout: 10000 }) + + await watchFileCreated(destination) + const result = JSON.parse(await readFile(destination)) + assert.equal(result.msg, 'hello with invalid node options preload') +}) diff --git a/services/slides/node_modules/pino/test/transport/repl.test.js b/services/slides/node_modules/pino/test/transport/repl.test.js new file mode 100644 index 0000000000000000000000000000000000000000..cdb7ae00ee5d5fcf83300e33f893121196001325 --- /dev/null +++ b/services/slides/node_modules/pino/test/transport/repl.test.js @@ -0,0 +1,15 @@ +'use strict' + +const test = require('node:test') +const assert = require('node:assert') +const proxyquire = require('proxyquire') + +test('pino.transport resolves targets in REPL', async () => { + // Arrange + const transport = proxyquire('../../lib/transport', { + './caller': () => ['node:repl'] + }) + + // Act / Assert + assert.doesNotThrow(() => transport({ target: 'pino-pretty' })) +}) diff --git a/services/slides/node_modules/pino/test/transport/sync-false.test.js b/services/slides/node_modules/pino/test/transport/sync-false.test.js new file mode 100644 index 0000000000000000000000000000000000000000..7c868e319c71c6934d3bce7e7a520c4cef12ca74 --- /dev/null +++ b/services/slides/node_modules/pino/test/transport/sync-false.test.js @@ -0,0 +1,67 @@ +'use strict' + +const test = require('node:test') +const assert = require('node:assert') +const os = require('node:os') +const { join } = require('node:path') +const { readFile } = require('node:fs').promises +const { promisify } = require('node:util') + +const pino = require('../..') +const { watchFileCreated, watchForWrite, file } = require('../helper') + +const { pid } = process +const hostname = os.hostname() + +test('thread-stream async flush', async () => { + const destination = file() + const transport = pino.transport({ + target: join(__dirname, '..', 'fixtures', 'to-file-transport.js'), + options: { destination } + }) + const instance = pino(transport) + instance.info('hello') + + assert.equal(instance.flush(), undefined) + + await watchFileCreated(destination) + const result = JSON.parse(await readFile(destination)) + delete result.time + assert.deepEqual(result, { + pid, + hostname, + level: 30, + msg: 'hello' + }) +}) + +test('thread-stream async flush should call the passed callback', async () => { + const outputPath = file() + async function getOutputLogLines () { + return (await readFile(outputPath)).toString().trim().split('\n').map(JSON.parse) + } + const transport = pino.transport({ + target: join(__dirname, '..', 'fixtures', 'to-file-transport.js'), + options: { destination: outputPath } + }) + const instance = pino(transport) + const flushPromise = promisify(instance.flush).bind(instance) + + instance.info('hello') + await flushPromise() + await watchFileCreated(outputPath) + + const [firstFlushData] = await getOutputLogLines() + + assert.equal(firstFlushData.msg, 'hello') + + instance.info('world') + + await flushPromise() + await watchForWrite(outputPath, 'world') + + // After flush, both messages should be present + const afterSecondFlush = await getOutputLogLines() + assert.equal(afterSecondFlush.length, 2) + assert.equal(afterSecondFlush[1].msg, 'world') +}) diff --git a/services/slides/node_modules/pino/test/transport/sync-true.test.js b/services/slides/node_modules/pino/test/transport/sync-true.test.js new file mode 100644 index 0000000000000000000000000000000000000000..3624549be5b2f930c08bd72b7e0508a3fbc48c65 --- /dev/null +++ b/services/slides/node_modules/pino/test/transport/sync-true.test.js @@ -0,0 +1,57 @@ +'use strict' + +const test = require('node:test') +const assert = require('node:assert') +const { join } = require('node:path') +const { readFileSync } = require('node:fs') + +const { file } = require('../helper') +const pino = require('../..') + +test('thread-stream sync true should log synchronously', async () => { + const outputPath = file() + + function getOutputLogLines () { + return (readFileSync(outputPath)).toString().trim().split('\n').map(JSON.parse) + } + + const transport = pino.transport({ + target: join(__dirname, '..', 'fixtures', 'to-file-transport.js'), + options: { destination: outputPath, flush: true }, + sync: true + }) + const instance = pino(transport) + + var value = { message: 'sync' } + instance.info(value) + instance.info(value) + instance.info(value) + instance.info(value) + instance.info(value) + instance.info(value) + let interrupt = false + let flushData + let loopCounter = 0 + + // Start a synchronous loop + while (!interrupt && loopCounter < (process.env.MAX_TEST_LOOP_ITERATION || 20000)) { + try { + loopCounter++ + const data = getOutputLogLines() + flushData = data + if (data) { + interrupt = true + break + } + } catch (error) { + // File may not exist yet + // Wait till MAX_TEST_LOOP_ITERATION iterations + } + } + + if (!interrupt) { + throw new Error('Sync loop did not get interrupt') + } + + assert.equal(flushData.length, 6) +}) diff --git a/services/slides/node_modules/pino/test/transport/targets.test.js b/services/slides/node_modules/pino/test/transport/targets.test.js new file mode 100644 index 0000000000000000000000000000000000000000..c7a80fa092fee79afbb171d7146813fd77bfd9af --- /dev/null +++ b/services/slides/node_modules/pino/test/transport/targets.test.js @@ -0,0 +1,48 @@ +'use strict' + +const test = require('node:test') +const { join } = require('node:path') +const Writable = require('node:stream').Writable +const proxyquire = require('proxyquire') +const tspl = require('@matteo.collina/tspl') +const pino = require('../../pino') + +test('file-target mocked', async function (t) { + const plan = tspl(t, { plan: 1 }) + let ret + const fileTarget = proxyquire('../../file', { + './pino': { + destination (opts) { + plan.deepEqual(opts, { dest: 1, sync: false }) + + ret = new Writable() + ret.fd = opts.dest + + process.nextTick(() => { + ret.emit('ready') + }) + + return ret + } + } + }) + + await fileTarget() + await plan +}) + +test('pino.transport with syntax error', async (t) => { + const plan = tspl(t, { plan: 1 }) + const transport = pino.transport({ + targets: [{ + target: join(__dirname, '..', 'fixtures', 'syntax-error-esm.mjs') + }] + }) + t.after(transport.end.bind(transport)) + + transport.on('error', (err) => { + plan.deepEqual(err, new SyntaxError('Unexpected end of input')) + }) + + await plan +}) diff --git a/services/slides/node_modules/pino/test/transport/uses-pino-config.test.js b/services/slides/node_modules/pino/test/transport/uses-pino-config.test.js new file mode 100644 index 0000000000000000000000000000000000000000..75468124418ea615072392e9c03d3a7fb08156c5 --- /dev/null +++ b/services/slides/node_modules/pino/test/transport/uses-pino-config.test.js @@ -0,0 +1,166 @@ +'use strict' + +const test = require('node:test') +const assert = require('node:assert') +const os = require('node:os') +const { join } = require('node:path') +const { readFile } = require('node:fs').promises +const writeStream = require('flush-write-stream') + +const { watchFileCreated, file } = require('../helper') +const pino = require('../../') + +const { pid } = process +const hostname = os.hostname() + +function serializeError (error) { + return { + type: error.name, + message: error.message, + stack: error.stack + } +} + +function parseLogs (buffer) { + return JSON.parse(`[${buffer.toString().replace(/}{/g, '},{')}]`) +} + +test('transport uses pino config', async (t) => { + const destination = file() + const transport = pino.transport({ + pipeline: [{ + target: join(__dirname, '..', 'fixtures', 'transport-uses-pino-config.js') + }, { + target: 'pino/file', + options: { destination } + }] + }) + t.after(transport.end.bind(transport)) + const instance = pino({ + messageKey: 'customMessageKey', + errorKey: 'customErrorKey', + customLevels: { custom: 35 } + }, transport) + + const error = new Error('bar') + instance.custom('foo') + instance.error(error) + await watchFileCreated(destination) + const result = parseLogs(await readFile(destination)) + + assert.deepEqual(result, [{ + severityText: 'custom', + body: 'foo', + attributes: { + pid, + hostname + } + }, { + severityText: 'error', + body: 'bar', + attributes: { + pid, + hostname + }, + error: serializeError(error) + }]) +}) + +test('transport uses pino config without customizations', async (t) => { + const destination = file() + const transport = pino.transport({ + pipeline: [{ + target: join(__dirname, '..', 'fixtures', 'transport-uses-pino-config.js') + }, { + target: 'pino/file', + options: { destination } + }] + }) + t.after(transport.end.bind(transport)) + const instance = pino(transport) + + const error = new Error('qux') + instance.info('baz') + instance.error(error) + await watchFileCreated(destination) + const result = parseLogs(await readFile(destination)) + + assert.deepEqual(result, [{ + severityText: 'info', + body: 'baz', + attributes: { + pid, + hostname + } + }, { + severityText: 'error', + body: 'qux', + attributes: { + pid, + hostname + }, + error: serializeError(error) + }]) +}) + +test('transport uses pino config with multistream', async (t) => { + const destination = file() + const messages = [] + const stream = writeStream(function (data, enc, cb) { + const message = JSON.parse(data) + delete message.time + messages.push(message) + cb() + }) + const transport = pino.transport({ + pipeline: [{ + target: join(__dirname, '..', 'fixtures', 'transport-uses-pino-config.js') + }, { + target: 'pino/file', + options: { destination } + }] + }) + t.after(transport.end.bind(transport)) + const instance = pino({ + messageKey: 'customMessageKey', + errorKey: 'customErrorKey', + customLevels: { custom: 35 } + }, pino.multistream([transport, { stream }])) + + const error = new Error('buzz') + const serializedError = serializeError(error) + instance.custom('fizz') + instance.error(error) + await watchFileCreated(destination) + const result = parseLogs(await readFile(destination)) + + assert.deepEqual(result, [{ + severityText: 'custom', + body: 'fizz', + attributes: { + pid, + hostname + } + }, { + severityText: 'error', + body: 'buzz', + attributes: { + pid, + hostname + }, + error: serializedError + }]) + + assert.deepEqual(messages, [{ + level: 35, + pid, + hostname, + customMessageKey: 'fizz' + }, { + level: 50, + pid, + hostname, + customErrorKey: serializedError, + customMessageKey: 'buzz' + }]) +}) diff --git a/services/slides/node_modules/pino/test/types/pino-import.test-d.cts b/services/slides/node_modules/pino/test/types/pino-import.test-d.cts new file mode 100644 index 0000000000000000000000000000000000000000..e0f941a4c24d1d56451d93544a9c236846c2f762 --- /dev/null +++ b/services/slides/node_modules/pino/test/types/pino-import.test-d.cts @@ -0,0 +1,30 @@ +import { expectType } from "tsd"; + +import * as pinoStar from "../../pino"; +import { default as P, default as pino, pino as pinoNamed } from '../../pino'; +import pinoCjsImport = require ("../../pino"); +const pinoCjs = require("../../pino"); +const { P: pinoCjsNamed } = require('pino') + +const log = pino(); +expectType(log.info); +expectType(log.error); + +expectType(pinoNamed()); +expectType(pinoNamed()); +expectType(pinoStar.default()); +expectType(pinoStar.pino()); +// expectType(pinoCjsImport.default()); +expectType(pinoCjsImport.pino()); +expectType(pinoCjsNamed()); +expectType(pinoCjs()); +expectType(pinoNamed.stdTimeFunctions.isoTimeNano) +expectType(pinoNamed.stdTimeFunctions.isoTimeNano()) + +const levelChangeEventListener: P.LevelChangeEventListener = ( + lvl: P.LevelWithSilent | string, + val: number, + prevLvl: P.LevelWithSilent | string, + prevVal: number, +) => {} +expectType(levelChangeEventListener) diff --git a/services/slides/node_modules/pino/test/types/pino-multistream.test-d.ts b/services/slides/node_modules/pino/test/types/pino-multistream.test-d.ts new file mode 100644 index 0000000000000000000000000000000000000000..a89b78f835ac0d4a6bffcb3a3e969d0b044fa705 --- /dev/null +++ b/services/slides/node_modules/pino/test/types/pino-multistream.test-d.ts @@ -0,0 +1,28 @@ +import { expectType, expectNotType } from 'tsd' + +import { createWriteStream } from 'node:fs' + +import pino, { multistream } from '../../pino' + +const streams = [ + { stream: process.stdout }, + { stream: createWriteStream('') }, + { level: 'error' as const, stream: process.stderr }, + { level: 'fatal' as const, stream: process.stderr }, +] + +expectType(pino.multistream(process.stdout)) +expectType(pino.multistream([createWriteStream('')])) +expectType>(pino.multistream({ level: 'error' as const, stream: process.stderr })) +expectType>(pino.multistream([{ level: 'fatal' as const, stream: createWriteStream('') }])) + +expectType>(pino.multistream(streams)) +expectType>(pino.multistream(streams, {})) +expectType>(pino.multistream(streams, { levels: { info: 30 } })) +expectType>(pino.multistream(streams, { dedupe: true })) +expectType>(pino.multistream(streams[0]).add(streams[1])) +expectType>(multistream(streams)) +expectType>(multistream(streams).clone('error')) +expectNotType>(multistream(streams).clone('error')) + +expectType(multistream(process.stdout)) diff --git a/services/slides/node_modules/pino/test/types/pino-top-export.test-d.ts b/services/slides/node_modules/pino/test/types/pino-top-export.test-d.ts new file mode 100644 index 0000000000000000000000000000000000000000..ed25de3bdc0bf46d6d74882dcf9234912d29a3c6 --- /dev/null +++ b/services/slides/node_modules/pino/test/types/pino-top-export.test-d.ts @@ -0,0 +1,34 @@ +import { expectType, expectAssignable } from 'tsd' +import type { SonicBoom } from 'sonic-boom' + +import pino, { + destination, + type LevelMapping, + levels, + type Logger, + multistream, + type MultiStreamRes, + type SerializedError, + stdSerializers, + stdTimeFunctions, + symbols, + transport, + version, +} from '../../pino' + +expectType(destination('')) +expectType(levels) +expectType(multistream(process.stdout)) +expectType(stdSerializers.err({} as any)) +expectType(stdTimeFunctions.isoTime()) +expectType(stdTimeFunctions.isoTimeNano()) +expectType(version) + +// Can't test against `unique symbol`, see https://github.com/SamVerschueren/tsd/issues/49 +expectAssignable(symbols.endSym) + +// TODO: currently returns (aliased) `any`, waiting for strong typed `thread-stream` +transport({ + target: '#pino/pretty', + options: { some: 'options for', the: 'transport' } +}) diff --git a/services/slides/node_modules/pino/test/types/pino-transport.test-d.ts b/services/slides/node_modules/pino/test/types/pino-transport.test-d.ts new file mode 100644 index 0000000000000000000000000000000000000000..9001d640a4490586c2375a1640363b604d03c0a3 --- /dev/null +++ b/services/slides/node_modules/pino/test/types/pino-transport.test-d.ts @@ -0,0 +1,156 @@ +import pino from '../../pino' +import { expectType } from 'tsd' + +// Single +const transport = pino.transport({ + target: '#pino/pretty', + options: { some: 'options for', the: 'transport' } +}) +pino(transport) + +expectType(pino({ + transport: { + target: 'pino-pretty' + }, +})) + +// Multiple +const transports = pino.transport({ + targets: [ + { + level: 'info', + target: '#pino/pretty', + options: { some: 'options for', the: 'transport' } + }, + { + level: 'trace', + target: '#pino/file', + options: { destination: './test.log' } + } + ] +}) +pino(transports) + +expectType(pino({ + transport: { + targets: [ + { + level: 'info', + target: '#pino/pretty', + options: { some: 'options for', the: 'transport' } + }, + { + level: 'trace', + target: '#pino/file', + options: { destination: './test.log' } + } + ] + }, +})) + +const transportsWithCustomLevels = pino.transport({ + targets: [ + { + level: 'info', + target: '#pino/pretty', + options: { some: 'options for', the: 'transport' } + }, + { + level: 'foo', + target: '#pino/file', + options: { destination: './test.log' } + } + ], + levels: { foo: 35 } +}) +pino(transports) + +expectType(pino({ + transport: { + targets: [ + { + level: 'info', + target: '#pino/pretty', + options: { some: 'options for', the: 'transport' } + }, + { + level: 'trace', + target: '#pino/file', + options: { destination: './test.log' } + } + ], + levels: { foo: 35 } + }, +})) + +const transportsWithoutOptions = pino.transport({ + targets: [ + { target: '#pino/pretty' }, + { target: '#pino/file' } + ], + levels: { foo: 35 } +}) +pino(transports) + +expectType(pino({ + transport: { + targets: [ + { target: '#pino/pretty' }, + { target: '#pino/file' } + ], + levels: { foo: 35 } + }, +})) + +const pipelineTransport = pino.transport({ + pipeline: [{ + target: './my-transform.js' + }, { + // Use target: 'pino/file' to write to stdout + // without any change. + target: 'pino-pretty' + }] +}) +pino(pipelineTransport) + +expectType(pino({ + transport: { + pipeline: [{ + target: './my-transform.js' + }, { + // Use target: 'pino/file' to write to stdout + // without any change. + target: 'pino-pretty' + }] + } +})) + +type TransportConfig = { + id: string +} + +// Custom transport params +const customTransport = pino.transport({ + target: 'custom', + options: { id: 'abc' } +}) +pino(customTransport) + +// Worker +pino.transport({ + target: 'custom', + worker: { + argv: ['a', 'b'], + stdin: false, + stderr: true, + stdout: false, + autoEnd: true, + }, + options: { id: 'abc' } +}) + +// Dedupe +pino.transport({ + targets: [], + dedupe: true, +}) diff --git a/services/slides/node_modules/pino/test/types/pino-type-only.test-d.ts b/services/slides/node_modules/pino/test/types/pino-type-only.test-d.ts new file mode 100644 index 0000000000000000000000000000000000000000..e43fa7a68bae843bb1fd7a1044b0cafee7c6db6b --- /dev/null +++ b/services/slides/node_modules/pino/test/types/pino-type-only.test-d.ts @@ -0,0 +1,75 @@ +import { expectAssignable, expectType, expectNotAssignable } from 'tsd' + +import pino from '../../' +import type { + LevelWithSilent, + Logger, + LogFn, + DestinationStreamWithMetadata, + Level, + LevelOrString, + LevelWithSilentOrString, + LoggerExtras, + LoggerOptions, +} from '../../pino' + +// NB: can also use `import * as pino`, but that form is callable as `pino()` +// under `esModuleInterop: false` or `pino.default()` under `esModuleInterop: true`. +const log = pino() +expectAssignable(log) +expectType(log) +expectType(log.info) + +expectType>([log.level]) + +const level: Level = 'debug' +expectAssignable(level) + +const levelWithSilent: LevelWithSilent = 'silent' +expectAssignable(levelWithSilent) + +const levelOrString: LevelOrString = 'myCustomLevel' +expectAssignable(levelOrString) +expectNotAssignable(levelOrString) +expectNotAssignable(levelOrString) +expectAssignable(levelOrString) + +const levelWithSilentOrString: LevelWithSilentOrString = 'myCustomLevel' +expectAssignable(levelWithSilentOrString) +expectNotAssignable(levelWithSilentOrString) +expectNotAssignable(levelWithSilentOrString) +expectAssignable(levelWithSilentOrString) + +function createStream (): DestinationStreamWithMetadata { + return { write () {} } +} + +const stream = createStream() +// Argh. TypeScript doesn't seem to narrow unless we assign the symbol like so, and tsd seems to +// break without annotating the type explicitly +const needsMetadata: typeof pino.symbols.needsMetadataGsym = pino.symbols.needsMetadataGsym +if (stream[needsMetadata]) { + expectType(stream.lastLevel) +} + +const loggerOptions: LoggerOptions = { + browser: { + formatters: { + log (obj) { + return obj + }, + level (label, number) { + return { label, number } + }, + }, + }, +} + +expectType(loggerOptions) + +// Reference: https://github.com/pinojs/pino/issues/2285 +const someConst = 'test' as const +pino().error({}, someConst) +const someFunc = (someConst: T) => { + pino().error({}, someConst) +} diff --git a/services/slides/node_modules/pino/test/types/pino.test-d.ts b/services/slides/node_modules/pino/test/types/pino.test-d.ts new file mode 100644 index 0000000000000000000000000000000000000000..b18876c2dcae32421db3fc3a2fd10cfbbda5ae59 --- /dev/null +++ b/services/slides/node_modules/pino/test/types/pino.test-d.ts @@ -0,0 +1,683 @@ +import { IncomingMessage, ServerResponse } from 'http' +import { mock } from 'node:test' +import { Socket } from 'net' +import { expectError, expectType } from 'tsd' +import pino, { LogFn, LoggerOptions } from '../../' +import Logger = pino.Logger + +const log = pino() +const info = log.info +const error = log.error + +info('hello world') +error('this is at error level') + +// primitive types +info('simple string') +info(true) +info(42) +info(3.14) +info(null) +info(undefined) + +// object types +info({ a: 1, b: '2' }) +info(new Error()) +info(new Date()) +info([]) +info(new Map()) +info(new Set()) + +// placeholder messages +info('Hello %s', 'world') +info('The answer is %d', 42) +info('The object is %o', { a: 1, b: '2' }) +info('The json is %j', { a: 1, b: '2' }) +info('The object is %O', { a: 1, b: '2' }) +info('The answer is %d and the question is %s with %o', 42, 'unknown', { + correct: 'order', +}) +info('Missing placeholder is fine %s') + +// %s placeholder supports all primitive types +info('Boolean %s', true) +info('Boolean %s', false) +info('Number %s', 123) +info('Number %s', 3.14) +info('BigInt %s', BigInt(123)) +info('Null %s', null) +info('Undefined %s', undefined) +info('Symbol %s', Symbol('test')) +info('String %s', 'hello') + +// %s placeholder with multiple primitives +info('Multiple primitives %s %s %s', true, 42, 'world') +info( + 'All primitive types %s %s %s %s %s %s %s', + 'string', + 123, + true, + BigInt(123), + null, + undefined, + Symbol('test') +) +declare const errorOrString: string | Error +info(errorOrString) + +// %o placeholder supports primitives too (except undefined) +info('Boolean %o', true) +info('Boolean %o', false) +info('Number %o', 123) +info('Number %o', 3.14) +info('BigInt %o', BigInt(123)) +info('Null %o', null) +info('Symbol %o', Symbol('test')) +info('String %o', 'hello') + +// placeholder messages type errors +expectError(info('The answer is %d', 'not a number')) +expectError( + info( + 'The answer is %d and the question is %s with %o', + 'unknown', + { incorrect: 'order' }, + 42 + ) +) +expectError(info('Extra message %s', 'after placeholder', 'not allowed')) + +// object types with messages +info({ obj: 42 }, 'hello world') +info({ obj: 42, b: 2 }, 'hello world') +info({ obj: { aa: 'bbb' } }, 'another') +info({ a: 1, b: '2' }, 'hello world with %s', 'extra data') + +// Extra message after placeholder +expectError(info({ a: 1, b: '2' }, 'hello world with %d', 2, 'extra')) + +// metadata with messages type passes, because of custom toString method +// We can't detect if the object has a custom toString method that returns a string +info({ a: 1, b: '2' }, 'hello world with %s', {}) + +// metadata after message +expectError(info('message', { a: 1, b: '2' })) + +// multiple strings without placeholder +expectError(info('string1', 'string2')) +expectError(info('string1', 'string2', 'string3')) + +setImmediate(info, 'after setImmediate') +error(new Error('an error')) + +const writeSym = pino.symbols.writeSym + +const testUniqSymbol = { + [pino.symbols.needsMetadataGsym]: true, +}[pino.symbols.needsMetadataGsym] + +const log2: pino.Logger = pino({ + name: 'myapp', + safe: true, + serializers: { + req: pino.stdSerializers.req, + res: pino.stdSerializers.res, + err: pino.stdSerializers.err, + }, +}) + +pino({ + write (o) {}, +}) + +pino({ + mixin () { + return { customName: 'unknown', customId: 111 } + }, +}) + +pino({ + mixin: () => ({ customName: 'unknown', customId: 111 }), +}) + +pino({ + mixin: (context: object) => ({ customName: 'unknown', customId: 111 }), +}) + +pino({ + mixin: (context: object, level: number) => ({ + customName: 'unknown', + customId: 111, + }), +}) + +pino({ + redact: { paths: [], censor: 'SECRET' }, +}) + +pino({ + redact: { paths: [], censor: () => 'SECRET' }, +}) + +pino({ + redact: { paths: [], censor: (value) => value }, +}) + +pino({ + redact: { paths: [], censor: (value, path) => path.join() }, +}) + +pino({ + redact: { + paths: [], + censor: (value): string => 'SECRET', + }, +}) + +expectError( + pino({ + redact: { paths: [], censor: (value: string) => value }, + }) +) + +pino({ + depthLimit: 1, +}) + +pino({ + edgeLimit: 1, +}) + +pino({ + browser: { + write (o) {}, + }, +}) + +pino({ + browser: { + write: { + info (o) {}, + error (o) {}, + }, + serialize: true, + asObject: true, + transmit: { + level: 'fatal', + send: (level, logEvent) => { + level + logEvent.bindings + logEvent.level + logEvent.ts + logEvent.messages + }, + }, + disabled: false, + }, +}) + +pino({ + browser: { + asObjectBindingsOnly: true, + }, +}) + +pino({}, undefined) + +pino({ base: null }) +if ('pino' in log) console.log(`pino version: ${log.pino}`) + +expectType(log.flush()) +log.flush((err?: Error) => undefined) +log.child({ a: 'property' }).info('hello child!') +log.level = 'error' +log.info('nope') +const child = log.child({ foo: 'bar' }) +child.info('nope again') +child.level = 'info' +child.info('hooray') +log.info('nope nope nope') +log.child({ foo: 'bar' }, { level: 'debug' }).debug('debug!') +child.bindings() +const customSerializers = { + test () { + return 'this is my serializer' + }, +} +pino() + .child({}, { serializers: customSerializers }) + .info({ test: 'should not show up' }) +const child2 = log.child({ father: true }) +const childChild = child2.child({ baby: true }) +const childRedacted = pino().child({}, { redact: ['path'] }) +childRedacted.info({ + msg: 'logged with redacted properties', + path: 'Not shown', +}) +const childAnotherRedacted = pino().child( + {}, + { + redact: { + paths: ['anotherPath'], + censor: 'Not the log you\re looking for', + }, + } +) +childAnotherRedacted.info({ + msg: 'another logged with redacted properties', + anotherPath: 'Not shown', +}) + +log.level = 'info' +if (log.levelVal === 30) { + console.log('logger level is `info`') +} + +const listener = (lvl: any, val: any, prevLvl: any, prevVal: any) => { + console.log(lvl, val, prevLvl, prevVal) +} +log.on('level-change', (lvl, val, prevLvl, prevVal, logger) => { + console.log(lvl, val, prevLvl, prevVal) +}) +log.level = 'trace' +log.removeListener('level-change', listener) +log.level = 'info' + +pino.levels.values.error === 50 +pino.levels.labels[50] === 'error' + +const logstderr: pino.Logger = pino(process.stderr) +logstderr.error('on stderr instead of stdout') + +log.useLevelLabels = true +log.info('lol') +log.level === 'info' +const isEnabled: boolean = log.isLevelEnabled('info') + +const redacted = pino({ + redact: ['path'], +}) + +redacted.info({ + msg: 'logged with redacted properties', + path: 'Not shown', +}) + +const anotherRedacted = pino({ + redact: { + paths: ['anotherPath'], + censor: 'Not the log you\re looking for', + }, +}) + +anotherRedacted.info({ + msg: 'another logged with redacted properties', + anotherPath: 'Not shown', +}) + +const withTimeFn = pino({ + timestamp: pino.stdTimeFunctions.isoTime, +}) + +const withRFC3339TimeFn = pino({ + timestamp: pino.stdTimeFunctions.isoTimeNano, +}) + +const withNestedKey = pino({ + nestedKey: 'payload', +}) + +const withHooks = pino({ + hooks: { + logMethod (args, method, level) { + expectType(this) + return method.apply(this, args) + }, + streamWrite (s) { + expectType(s) + return s.replaceAll('secret-key', 'xxx') + }, + }, +}) + +// Properties/types imported from pino-std-serializers +const wrappedErrSerializer = pino.stdSerializers.wrapErrorSerializer( + (err: pino.SerializedError) => { + return { ...err, newProp: 'foo' } + } +) +const wrappedReqSerializer = pino.stdSerializers.wrapRequestSerializer( + (req: pino.SerializedRequest) => { + return { ...req, newProp: 'foo' } + } +) +const wrappedResSerializer = pino.stdSerializers.wrapResponseSerializer( + (res: pino.SerializedResponse) => { + return { ...res, newProp: 'foo' } + } +) + +const socket = new Socket() +const incomingMessage = new IncomingMessage(socket) +const serverResponse = new ServerResponse(incomingMessage) + +const mappedHttpRequest: { req: pino.SerializedRequest } = + pino.stdSerializers.mapHttpRequest(incomingMessage) +const mappedHttpResponse: { res: pino.SerializedResponse } = + pino.stdSerializers.mapHttpResponse(serverResponse) + +const serializedErr: pino.SerializedError = pino.stdSerializers.err( + new Error() +) +const serializedReq: pino.SerializedRequest = + pino.stdSerializers.req(incomingMessage) +const serializedRes: pino.SerializedResponse = + pino.stdSerializers.res(serverResponse) + +/** + * Destination static method + */ +const destinationViaDefaultArgs = pino.destination() +const destinationViaStrFileDescriptor = pino.destination('/log/path') +const destinationViaNumFileDescriptor = pino.destination(2) +const destinationViaStream = pino.destination(process.stdout) +const destinationViaOptionsObject = pino.destination({ + dest: '/log/path', + sync: false, +}) + +pino(destinationViaDefaultArgs) +pino({ name: 'my-logger' }, destinationViaDefaultArgs) +pino(destinationViaStrFileDescriptor) +pino({ name: 'my-logger' }, destinationViaStrFileDescriptor) +pino(destinationViaNumFileDescriptor) +pino({ name: 'my-logger' }, destinationViaNumFileDescriptor) +pino(destinationViaStream) +pino({ name: 'my-logger' }, destinationViaStream) +pino(destinationViaOptionsObject) +pino({ name: 'my-logger' }, destinationViaOptionsObject) + +try { + throw new Error('Some error') +} catch (err) { + log.error(err) +} + +interface StrictShape { + activity: string; + err?: unknown; +} + +info({ + activity: 'Required property', +}) + +const logLine: pino.LogDescriptor = { + level: 20, + msg: 'A log message', + time: new Date().getTime(), + aCustomProperty: true, +} + +interface CustomLogger extends pino.Logger { + customMethod(msg: string, ...args: unknown[]): void; +} + +const serializerFunc: pino.SerializerFn = () => {} +const writeFunc: pino.WriteFn = () => {} + +interface CustomBaseLogger extends pino.BaseLogger { + child(): CustomBaseLogger; +} + +const customBaseLogger: CustomBaseLogger = { + level: 'info', + fatal () {}, + error () {}, + warn () {}, + info () {}, + debug () {}, + trace () {}, + silent () {}, + child () { + return this + }, + msgPrefix: 'prefix', +} + +// custom levels +const log3 = pino({ customLevels: { myLevel: 100 } }) +expectError(log3.log()) +log3.level = 'myLevel' +log3.myLevel('') +log3.child({}).myLevel('') + +log3.on('level-change', (lvl, val, prevLvl, prevVal, instance) => { + instance.myLevel('foo') +}) + +const clog3 = log3.child({}, { customLevels: { childLevel: 120 } }) +// child inherit parent +clog3.myLevel('') +// child itself +clog3.childLevel('') +const cclog3 = clog3.child({}, { customLevels: { childLevel2: 130 } }) +// child inherit root +cclog3.myLevel('') +// child inherit parent +cclog3.childLevel('') +// child itself +cclog3.childLevel2('') + +const ccclog3 = clog3.child({}) +expectError(ccclog3.nonLevel('')) + +const withChildCallback = pino({ + onChild: (child: Logger) => {}, +}) +withChildCallback.onChild = (child: Logger) => {} + +pino({ + crlf: true, +}) + +const customLevels = { foo: 99, bar: 42 } + +const customLevelLogger = pino({ customLevels }) + +type CustomLevelLogger = typeof customLevelLogger +type CustomLevelLoggerLevels = pino.Level | keyof typeof customLevels + +const fn = (logger: Pick) => {} + +const customLevelChildLogger = customLevelLogger.child({ name: 'child' }) + +fn(customLevelChildLogger) // missing foo typing + +// unknown option +expectError( + pino({ + hello: 'world', + }) +) + +// unknown option +expectError( + pino({ + hello: 'world', + customLevels: { + log: 30, + }, + }) +) + +function dangerous () { + throw Error('foo') +} + +try { + dangerous() +} catch (err) { + log.error(err) +} + +try { + dangerous() +} catch (err) { + log.error({ err }) +} + +const bLogger = pino({ + customLevels: { + log: 5, + }, + level: 'log', + transport: { + target: 'pino-pretty', + options: { + colorize: true, + }, + }, +}) + +// Test that we can properly extract parameters from the log fn type +type LogParam = Parameters +const [param1, param2, param3, param4]: LogParam = [ + { multiple: 'params' }, + 'should', + 'be', + 'accepted', +] + +expectType(param1) +expectType(param2) +expectType(param3) +expectType(param4) + +const logger = mock.fn() +logger.mock.calls[0].arguments[1]?.includes('I should be able to get params') + +const hooks: LoggerOptions['hooks'] = { + logMethod (this, parameters, method) { + if (parameters.length >= 2) { + const [parameter1, parameter2, ...remainingParameters] = parameters + if (typeof parameter1 === 'string') { + return method.apply(this, [ + parameter2, + parameter1, + ...remainingParameters, + ]) + } + return method.apply(this, [parameter2]) + } + + return method.apply(this, parameters) + }, +} + +expectType>( + pino({ + customLevels: { + log: 5, + }, + level: 'log', + transport: { + target: 'pino-pretty', + options: { + colorize: true, + }, + }, + }) +) + +const parentLogger1 = pino( + { + customLevels: { myLevel: 90 }, + onChild: (child) => { + const a = child.myLevel + }, + }, + process.stdout +) +parentLogger1.onChild = (child) => { + child.myLevel('') +} + +const childLogger1 = parentLogger1.child({}) +childLogger1.myLevel('') +expectError(childLogger1.doesntExist('')) + +const parentLogger2 = pino({}, process.stdin) +expectError( + (parentLogger2.onChild = (child) => { + const b = child.doesntExist + }) +) + +const childLogger2 = parentLogger2.child({}) +expectError(childLogger2.doesntExist) + +expectError( + pino( + { + onChild: (child) => { + const a = child.doesntExist + }, + }, + process.stdout + ) +) + +const pinoWithoutLevelsSorting = pino({}) +const pinoWithDescSortingLevels = pino({ levelComparison: 'DESC' }) +const pinoWithAscSortingLevels = pino({ levelComparison: 'ASC' }) +const pinoWithCustomSortingLevels = pino({ levelComparison: () => false }) +// with wrong level comparison direction +expectError(pino({ levelComparison: 'SOME' }), process.stdout) +// with wrong level comparison type +expectError(pino({ levelComparison: 123 }), process.stdout) +// with wrong custom level comparison return type +expectError(pino({ levelComparison: () => null }), process.stdout) +expectError(pino({ levelComparison: () => 1 }), process.stdout) +expectError(pino({ levelComparison: () => 'string' }), process.stdout) + +const customLevelsOnlyOpts = { + useOnlyCustomLevels: true, + customLevels: { + customDebug: 10, + info: 20, // to make sure the default names are also available for override + customNetwork: 30, + customError: 40, + }, + level: 'customDebug', +} satisfies LoggerOptions + +const loggerWithCustomLevelOnly = pino(customLevelsOnlyOpts) +loggerWithCustomLevelOnly.customDebug('test3') +loggerWithCustomLevelOnly.info('test4') +loggerWithCustomLevelOnly.customError('test5') +loggerWithCustomLevelOnly.customNetwork('test6') + +expectError(loggerWithCustomLevelOnly.fatal('test')) +expectError(loggerWithCustomLevelOnly.error('test')) +expectError(loggerWithCustomLevelOnly.warn('test')) +expectError(loggerWithCustomLevelOnly.debug('test')) +expectError(loggerWithCustomLevelOnly.trace('test')) + +// Module extension +declare module '../../' { + interface LogFnFields { + bannedField?: never; + typeCheckedField?: string; + } +} + +info({ typeCheckedField: 'bar' }) +expectError(info({ bannedField: 'bar' })) +expectError(info({ typeCheckedField: 123 })) + +const someGenericFunction = ( + arg: Record +) => { + info(arg) +} diff --git a/services/slides/node_modules/pino/test/types/pino.ts b/services/slides/node_modules/pino/test/types/pino.ts new file mode 100644 index 0000000000000000000000000000000000000000..dbb8804a303683af0204f34230153340bcf58bf1 --- /dev/null +++ b/services/slides/node_modules/pino/test/types/pino.ts @@ -0,0 +1,91 @@ +import { join } from 'node:path' +import { tmpdir } from 'node:os' +import pinoPretty from 'pino-pretty' +// Test both default ("Pino") and named ("pino") imports. +import Pino, { type LoggerOptions, type StreamEntry, pino, multistream, transport } from '../../pino' + +const destination = join( + tmpdir(), + '_' + Math.random().toString(36).substr(2, 9) +) + +// Single +const transport1 = transport({ + target: 'pino-pretty', + options: { some: 'options for', the: 'transport' } +}) +const logger = pino(transport1) +logger.setBindings({ some: 'bindings' }) +logger.info('test2') +logger.flush() +const loggerDefault = Pino(transport1) +loggerDefault.setBindings({ some: 'bindings' }) +loggerDefault.info('test2') +loggerDefault.flush() + +const transport2 = transport({ + target: 'pino-pretty', +}) +const logger2 = pino(transport2) +logger2.info('test2') +const logger2Default = Pino(transport2) +logger2Default.info('test2') + +// Multiple + +const transports = transport({ + targets: [ + { + level: 'info', + target: 'pino-pretty', + options: { some: 'options for', the: 'transport' } + }, + { + level: 'trace', + target: 'pino/file', + options: { destination } + } + ] +}) +const loggerMulti = pino(transports) +loggerMulti.info('test2') + +// custom levels + +const customLevels = { + customDebug: 1, + info: 2, + customNetwork: 3, + customError: 4, +} + +type CustomLevels = keyof typeof customLevels + +const pinoOpts = { + useOnlyCustomLevels: true, + customLevels, + level: 'customDebug', +} satisfies LoggerOptions + +const multistreamOpts = { + dedupe: true, + levels: customLevels +} + +const streams: StreamEntry[] = [ + { level: 'customDebug', stream: pinoPretty() }, + { level: 'info', stream: pinoPretty() }, + { level: 'customNetwork', stream: pinoPretty() }, + { level: 'customError', stream: pinoPretty() }, +] + +const loggerCustomLevel = pino(pinoOpts, multistream(streams, multistreamOpts)) +loggerCustomLevel.customDebug('test3') +loggerCustomLevel.info('test4') +loggerCustomLevel.customError('test5') +loggerCustomLevel.customNetwork('test6') +const loggerCustomLevelDefault = Pino(pinoOpts, multistream(streams, multistreamOpts)) +loggerCustomLevelDefault.customDebug('test3') +loggerCustomLevelDefault.info('test4') +loggerCustomLevelDefault.customError('test5') +loggerCustomLevelDefault.customNetwork('test6') diff --git a/services/slides/node_modules/pino/tsconfig.json b/services/slides/node_modules/pino/tsconfig.json new file mode 100644 index 0000000000000000000000000000000000000000..9c80d8f11ccc439f29b00581832d523aea12b168 --- /dev/null +++ b/services/slides/node_modules/pino/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "es6", + "lib": [ "es2015", "dom" ], + "module": "commonjs", + "noEmit": true, + "strict": true, + "esModuleInterop": true + }, + "exclude": [ + "./test/types/*.test-d.ts", + "./*.d.ts" + ] +} diff --git a/services/slides/node_modules/pptxgenjs/LICENSE b/services/slides/node_modules/pptxgenjs/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..0b21750ad94fcfc08e723c81d337fd66e5b35964 --- /dev/null +++ b/services/slides/node_modules/pptxgenjs/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015-2022 Brent Ely + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/services/slides/node_modules/pptxgenjs/README.md b/services/slides/node_modules/pptxgenjs/README.md new file mode 100644 index 0000000000000000000000000000000000000000..0250a6ff496d90f52d470b7e2caa51076a0ddcec --- /dev/null +++ b/services/slides/node_modules/pptxgenjs/README.md @@ -0,0 +1,284 @@ +

PptxGenJS

+
+ Create JavaScript PowerPoint Presentations +
+

+ + PptxGenJS Sample Slides + +

+
+ +[![Known Vulnerabilities](https://snyk.io/test/npm/pptxgenjs/badge.svg)](https://snyk.io/test/npm/pptxgenjs) [![npm downloads](https://img.shields.io/npm/dm/pptxgenjs.svg)](https://www.npmjs.com/package/pptxgenjs) [![jsdelivr downloads](https://data.jsdelivr.com/v1/package/gh/gitbrent/pptxgenjs/badge)](https://www.jsdelivr.com/package/gh/gitbrent/pptxgenjs) [![typescripts definitions](https://img.shields.io/npm/types/pptxgenjs)](https://img.shields.io/npm/types/pptxgenjs) + +# Table of Contents + +- [Table of Contents](#table-of-contents) +- [Introduction](#introduction) +- [Features](#features) + - [Works Everywhere](#works-everywhere) + - [Full Featured](#full-featured) + - [Simple and Powerful](#simple-and-powerful) + - [Export Your Way](#export-your-way) + - [HTML to PowerPoint](#html-to-powerpoint) +- [Live Demos](#live-demos) +- [Installation](#installation) + - [Npm](#npm) + - [Yarn](#yarn) + - [CDN](#cdn) + - [Download](#download) + - [Additional Builds](#additional-builds) +- [Documentation](#documentation) + - [Quick Start Guide](#quick-start-guide) + - [Angular/React, ES6, TypeScript](#angularreact-es6-typescript) + - [Script/Web Browser](#scriptweb-browser) + - [Library API](#library-api) + - [HTML-to-PowerPoint Feature](#html-to-powerpoint-feature) +- [Library Ports](#library-ports) +- [Issues / Suggestions](#issues--suggestions) +- [Need Help?](#need-help) +- [Contributors](#contributors) +- [Sponsor Us](#sponsor-us) +- [License](#license) + +# Introduction + +This library creates Open Office XML (OOXML) Presentations which are compatible with Microsoft PowerPoint, Apple Keynote, and other applications. + +# Features + +## Works Everywhere + +- Every modern desktop and mobile browser is supported +- Integrates with Node, Angular, React, and Electron +- Compatible with PowerPoint, Keynote, and more + +## Full Featured + +- All major object types are available (charts, shapes, tables, etc.) +- Master Slides for academic/corporate branding +- SVG images, animated gifs, YouTube videos, RTL text, and Asian fonts + +## Simple and Powerful + +- The absolute easiest PowerPoint library to use +- Learn as you code will full typescript definitions included +- Tons of demo code comes included (over 75 slides of features) + +## Export Your Way + +- Exports files direct to client browsers with proper MIME-type +- Other export formats available: base64, blob, stream, etc. +- Presentation compression options and more + +## HTML to PowerPoint + +- Includes powerful [HTML-to-PowerPoint](#html-to-powerpoint-feature) feature to transform HTML tables into presentations with a single line of code + +# Live Demos + +Visit the demos page to create a simple presentation to see how easy it is to use pptxgenjs, or check out the complete demo which showcases every available feature. + +- [PptxGenJS Demos](https://gitbrent.github.io/PptxGenJS/demos/) + +# Installation + +## Npm + +[PptxGenJS NPM Home](https://www.npmjs.com/package/pptxgenjs) + +```bash +npm install pptxgenjs --save +``` + +## Yarn + +```bash +yarn add pptxgenjs +``` + +## CDN + +[jsDelivr Home](https://www.jsdelivr.com/package/gh/gitbrent/pptxgenjs) + +Bundle: Modern Browsers and IE11 + +```html + +``` + +Min files: Modern Browsers + +```html + + +``` + +## Download + +[GitHub Latest Release](https://github.com/gitbrent/PptxGenJS/releases/latest) + +Bundle: Modern Browsers + +- Use the bundle for IE11 support + +```html + +``` + +Min files: Modern Browsers + +```html + + +``` + +## Additional Builds + +- CommonJS: `dist/pptxgen.cjs.js` +- ES Module: `dist/pptxgen.es.js` + +--- + +# Documentation + +## Quick Start Guide + +PptxGenJS PowerPoint presentations are created via JavaScript by following 4 basic steps: + +### Angular/React, ES6, TypeScript + +```typescript +import pptxgen from "pptxgenjs"; + +// 1. Create a new Presentation +let pres = new pptxgen(); + +// 2. Add a Slide +let slide = pres.addSlide(); + +// 3. Add one or more objects (Tables, Shapes, Images, Text and Media) to the Slide +let textboxText = "Hello World from PptxGenJS!"; +let textboxOpts = { x: 1, y: 1, color: "363636" }; +slide.addText(textboxText, textboxOpts); + +// 4. Save the Presentation +pres.writeFile(); +``` + +### Script/Web Browser + +```javascript +// 1. Create a new Presentation +let pres = new PptxGenJS(); + +// 2. Add a Slide +let slide = pres.addSlide(); + +// 3. Add one or more objects (Tables, Shapes, Images, Text and Media) to the Slide +let textboxText = "Hello World from PptxGenJS!"; +let textboxOpts = { x: 1, y: 1, color: "363636" }; +slide.addText(textboxText, textboxOpts); + +// 4. Save the Presentation +pres.writeFile(); +``` + +That's really all there is to it! + +--- + +## Library API + +Full documentation and code examples are available + +- [Creating a Presentation](https://gitbrent.github.io/PptxGenJS/docs/usage-pres-create/) +- [Presentation Options](https://gitbrent.github.io/PptxGenJS/docs/usage-pres-options/) +- [Adding a Slide](https://gitbrent.github.io/PptxGenJS/docs/usage-add-slide/) +- [Slide Options](https://gitbrent.github.io/PptxGenJS/docs/usage-slide-options/) +- [Saving a Presentation](https://gitbrent.github.io/PptxGenJS/docs/usage-saving/) +- [Master Slides](https://gitbrent.github.io/PptxGenJS/docs/masters/) +- [Adding Charts](https://gitbrent.github.io/PptxGenJS/docs/api-charts/) +- [Adding Images](https://gitbrent.github.io/PptxGenJS/docs/api-images/) +- [Adding Media](https://gitbrent.github.io/PptxGenJS/docs/api-media/) +- [Adding Shapes](https://gitbrent.github.io/PptxGenJS/docs/api-shapes/) +- [Adding Tables](https://gitbrent.github.io/PptxGenJS/docs/api-tables/) +- [Adding Text](https://gitbrent.github.io/PptxGenJS/docs/api-text/) +- [Speaker Notes](https://gitbrent.github.io/PptxGenJS/docs/speaker-notes/) +- [Using Scheme Colors](https://gitbrent.github.io/PptxGenJS/docs/shapes-and-schemes/) +- [Integration with Other Libraries](https://gitbrent.github.io/PptxGenJS/docs/integration/) + +--- + +## HTML-to-PowerPoint Feature + +Easily convert HTML tables to PowerPoint presentations in a single call. + +```javascript +let pptx = new PptxGenJS(); +pptx.tableToSlides("tableElementId"); +pptx.writeFile({ fileName: "html2pptx-demo.pptx" }); +``` + +Learn more: + +- [HTML-to-PowerPoint Docs/Demo](https://gitbrent.github.io/PptxGenJS/html2pptx/) + +--- + +# Library Ports + +React: [react-pptx](https://github.com/wyozi/react-pptx) - thanks to [Joonas](https://github.com/wyozi)! + +--- + +# Issues / Suggestions + +Please file issues or suggestions on the [issues page on github](https://github.com/gitbrent/PptxGenJS/issues/new), or even better, [submit a pull request](https://github.com/gitbrent/PptxGenJS/pulls). Feedback is always welcome! + +When reporting issues, please include a code snippet or a link demonstrating the problem. +Here is a small [jsFiddle](https://jsfiddle.net/gitbrent/L1uctxm0/) that is already configured and uses the latest PptxGenJS code. + +--- + +# Need Help? + +Sometimes implementing a new library can be a difficult task and the slightest mistake will keep something from working. We've all been there! + +If you are having issues getting a presentation to generate, check out the code in the `demos` directory. There +are demos for both client browsers, node and react that contain working examples of every available library feature. + +- Use a pre-configured jsFiddle to test with: [PptxGenJS Fiddle](https://jsfiddle.net/gitbrent/L1uctxm0/) +- [View questions tagged `PptxGenJS` on StackOverflow](https://stackoverflow.com/questions/tagged/pptxgenjs?sort=votes&pageSize=50). If you can't find your question, [ask it yourself](https://stackoverflow.com/questions/ask?tags=PptxGenJS) - be sure to tag it `PptxGenJS`. + +--- + +# Contributors + +Thank you to everyone for the issues, contributions and suggestions! ❤️ + +Special Thanks: + +- [Dzmitry Dulko](https://github.com/DzmitryDulko) - Getting the project published on NPM +- [Michal Kacerovský](https://github.com/kajda90) - New Master Slide Layouts and Chart expertise +- [Connor Bowman](https://github.com/conbow) - Adding Placeholders +- [Reima Frgos](https://github.com/ReimaFrgos) - Multiple chart and general functionality patches +- [Matt King](https://github.com/kyrrigle) - Chart expertise +- [Mike Wilcox](https://github.com/clubajax) - Chart expertise +- [Joonas](https://github.com/wyozi) - React port + +PowerPoint shape definitions and some XML code via [Officegen Project](https://github.com/Ziv-Barber/officegen) + +--- + +# Sponsor Us + +If you find this library useful, please consider sponsoring us through a [donation](https://gitbrent.github.io/PptxGenJS/sponsor/) + +--- + +# License + +Copyright © 2015-present [Brent Ely](https://github.com/gitbrent/PptxGenJS) + +[MIT](https://github.com/gitbrent/PptxGenJS/blob/master/LICENSE) diff --git a/services/slides/node_modules/pptxgenjs/dist/pptxgen.bundle.js b/services/slides/node_modules/pptxgenjs/dist/pptxgen.bundle.js new file mode 100644 index 0000000000000000000000000000000000000000..1ff971b3a9684bf3d2592883f1832e6992043154 --- /dev/null +++ b/services/slides/node_modules/pptxgenjs/dist/pptxgen.bundle.js @@ -0,0 +1,3 @@ +/* PptxGenJS 3.12.0 @ 2023-03-20T03:12:31.375Z */ +!function(t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).JSZip=t()}(function(){return function r(a,o,i){function s(e,t){if(!o[e]){if(!a[e]){var n="function"==typeof require&&require;if(!t&&n)return n(e,!0);if(A)return A(e,!0);t=new Error("Cannot find module '"+e+"'");throw t.code="MODULE_NOT_FOUND",t}n=o[e]={exports:{}};a[e][0].call(n.exports,function(t){return s(a[e][1][t]||t)},n,n.exports,r,a,o,i)}return o[e].exports}for(var A="function"==typeof require&&require,t=0;t>4,o=1>6:64,i=2>2)+p.charAt(a)+p.charAt(o)+p.charAt(i));return s.join("")},n.decode=function(t){var e,n,r,a,o,i=0,s=0;if("data:"===t.substr(0,"data:".length))throw new Error("Invalid base64 input, it looks like a data url.");var A,l=3*(t=t.replace(/[^A-Za-z0-9\+\/\=]/g,"")).length/4;if(t.charAt(t.length-1)===p.charAt(64)&&l--,t.charAt(t.length-2)===p.charAt(64)&&l--,l%1!=0)throw new Error("Invalid base64 input, bad content length.");for(A=new(c.uint8array?Uint8Array:Array)(0|l);i>4,n=(15&a)<<4|(a=p.indexOf(t.charAt(i++)))>>2,r=(3&a)<<6|(o=p.indexOf(t.charAt(i++))),A[s++]=e,64!==a&&(A[s++]=n),64!==o&&(A[s++]=r);return A}},{"./support":30,"./utils":32}],2:[function(t,e,n){"use strict";var r=t("./external"),a=t("./stream/DataWorker"),o=t("./stream/Crc32Probe"),i=t("./stream/DataLengthProbe");function s(t,e,n,r,a){this.compressedSize=t,this.uncompressedSize=e,this.crc32=n,this.compression=r,this.compressedContent=a}s.prototype={getContentWorker:function(){var t=new a(r.Promise.resolve(this.compressedContent)).pipe(this.compression.uncompressWorker()).pipe(new i("data_length")),e=this;return t.on("end",function(){if(this.streamInfo.data_length!==e.uncompressedSize)throw new Error("Bug : uncompressed data size mismatch")}),t},getCompressedWorker:function(){return new a(r.Promise.resolve(this.compressedContent)).withStreamInfo("compressedSize",this.compressedSize).withStreamInfo("uncompressedSize",this.uncompressedSize).withStreamInfo("crc32",this.crc32).withStreamInfo("compression",this.compression)}},s.createWorkerFrom=function(t,e,n){return t.pipe(new o).pipe(new i("uncompressedSize")).pipe(e.compressWorker(n)).pipe(new i("compressedSize")).withStreamInfo("compression",e)},e.exports=s},{"./external":6,"./stream/Crc32Probe":25,"./stream/DataLengthProbe":26,"./stream/DataWorker":27}],3:[function(t,e,n){"use strict";var r=t("./stream/GenericWorker");n.STORE={magic:"\0\0",compressWorker:function(t){return new r("STORE compression")},uncompressWorker:function(){return new r("STORE decompression")}},n.DEFLATE=t("./flate")},{"./flate":7,"./stream/GenericWorker":28}],4:[function(t,e,n){"use strict";var r=t("./utils"),i=function(){for(var t=[],e=0;e<256;e++){for(var n=e,r=0;r<8;r++)n=1&n?3988292384^n>>>1:n>>>1;t[e]=n}return t}();e.exports=function(t,e){return void 0!==t&&t.length?("string"!==r.getTypeOf(t)?function(t,e,n){var r=i,a=0+n;t^=-1;for(var o=0;o>>8^r[255&(t^e[o])];return-1^t}:function(t,e,n){var r=i,a=0+n;t^=-1;for(var o=0;o>>8^r[255&(t^e.charCodeAt(o))];return-1^t})(0|e,t,t.length):0}},{"./utils":32}],5:[function(t,e,n){"use strict";n.base64=!1,n.binary=!1,n.dir=!1,n.createFolders=!0,n.date=null,n.compression=null,n.compressionOptions=null,n.comment=null,n.unixPermissions=null,n.dosPermissions=null},{}],6:[function(t,e,n){"use strict";t="undefined"!=typeof Promise?Promise:t("lie");e.exports={Promise:t}},{lie:37}],7:[function(t,e,n){"use strict";var r="undefined"!=typeof Uint8Array&&"undefined"!=typeof Uint16Array&&"undefined"!=typeof Uint32Array,a=t("pako"),o=t("./utils"),i=t("./stream/GenericWorker"),s=r?"uint8array":"array";function A(t,e){i.call(this,"FlateWorker/"+t),this._pako=null,this._pakoAction=t,this._pakoOptions=e,this.meta={}}n.magic="\b\0",o.inherits(A,i),A.prototype.processChunk=function(t){this.meta=t.meta,null===this._pako&&this._createPako(),this._pako.push(o.transformTo(s,t.data),!1)},A.prototype.flush=function(){i.prototype.flush.call(this),null===this._pako&&this._createPako(),this._pako.push([],!0)},A.prototype.cleanUp=function(){i.prototype.cleanUp.call(this),this._pako=null},A.prototype._createPako=function(){this._pako=new a[this._pakoAction]({raw:!0,level:this._pakoOptions.level||-1});var e=this;this._pako.onData=function(t){e.push({data:t,meta:e.meta})}},n.compressWorker=function(t){return new A("Deflate",t)},n.uncompressWorker=function(){return new A("Inflate",{})}},{"./stream/GenericWorker":28,"./utils":32,pako:38}],8:[function(t,e,n){"use strict";function v(t,e){for(var n="",r=0;r>>=8;return n}function r(t,e,n,r,a,o){var i=t.file,s=t.compression,A=o!==b.utf8encode,l=y.transformTo("string",o(i.name)),c=y.transformTo("string",b.utf8encode(i.name)),u=i.comment,o=y.transformTo("string",o(u)),p=y.transformTo("string",b.utf8encode(u)),f=c.length!==i.name.length,u=p.length!==u.length,d="",h=i.dir,g=i.date,m={crc32:0,compressedSize:0,uncompressedSize:0},n=(e&&!n||(m.crc32=t.crc32,m.compressedSize=t.compressedSize,m.uncompressedSize=t.uncompressedSize),0);e&&(n|=8),A||!f&&!u||(n|=2048);t=0,e=0,h&&(t|=16),"UNIX"===a?(e=798,t|=(65535&(i.unixPermissions||(h?16893:33204)))<<16):(e=20,t|=63&(i.dosPermissions||0)),A=g.getUTCHours(),A=(A=((A<<=6)|g.getUTCMinutes())<<5)|g.getUTCSeconds()/2,a=g.getUTCFullYear()-1980,a=(a=((a<<=4)|g.getUTCMonth()+1)<<5)|g.getUTCDate(),f&&(d+="up"+v((h=v(1,1)+v(w(l),4)+c).length,2)+h),u&&(d+="uc"+v((i=v(1,1)+v(w(o),4)+p).length,2)+i),g="",g=(g=(g=(g=(g=(g=(g=(g=(g=(g+="\n\0")+v(n,2))+s.magic)+v(A,2))+v(a,2))+v(m.crc32,4))+v(m.compressedSize,4))+v(m.uncompressedSize,4))+v(l.length,2))+v(d.length,2);return{fileRecord:x.LOCAL_FILE_HEADER+g+l+d,dirRecord:x.CENTRAL_FILE_HEADER+v(e,2)+g+v(o.length,2)+"\0\0\0\0"+v(t,4)+v(r,4)+l+d+o}}var y=t("../utils"),a=t("../stream/GenericWorker"),b=t("../utf8"),w=t("../crc32"),x=t("../signature");function o(t,e,n,r){a.call(this,"ZipFileWorker"),this.bytesWritten=0,this.zipComment=e,this.zipPlatform=n,this.encodeFileName=r,this.streamFiles=t,this.accumulate=!1,this.contentBuffer=[],this.dirRecords=[],this.currentSourceOffset=0,this.entriesCount=0,this.currentFile=null,this._sources=[]}y.inherits(o,a),o.prototype.push=function(t){var e=t.meta.percent||0,n=this.entriesCount,r=this._sources.length;this.accumulate?this.contentBuffer.push(t):(this.bytesWritten+=t.data.length,a.prototype.push.call(this,{data:t.data,meta:{currentFile:this.currentFile,percent:n?(e+100*(n-r-1))/n:100}}))},o.prototype.openedSource=function(t){this.currentSourceOffset=this.bytesWritten,this.currentFile=t.file.name;var e=this.streamFiles&&!t.file.dir;e?(t=r(t,e,!1,this.currentSourceOffset,this.zipPlatform,this.encodeFileName),this.push({data:t.fileRecord,meta:{percent:0}})):this.accumulate=!0},o.prototype.closedSource=function(t){this.accumulate=!1;var e=this.streamFiles&&!t.file.dir,n=r(t,e,!0,this.currentSourceOffset,this.zipPlatform,this.encodeFileName);if(this.dirRecords.push(n.dirRecord),e)this.push({data:x.DATA_DESCRIPTOR+v((e=t).crc32,4)+v(e.compressedSize,4)+v(e.uncompressedSize,4),meta:{percent:100}});else for(this.push({data:n.fileRecord,meta:{percent:0}});this.contentBuffer.length;)this.push(this.contentBuffer.shift());this.currentFile=null},o.prototype.flush=function(){for(var t=this.bytesWritten,e=0;e=this.index;e--)n=(n<<8)+this.byteAt(e);return this.index+=t,n},readString:function(t){return r.transformTo("string",this.readData(t))},readData:function(t){},lastIndexOfSignature:function(t){},readAndCheckSignature:function(t){},readDate:function(){var t=this.readInt(4);return new Date(Date.UTC(1980+(t>>25&127),(t>>21&15)-1,t>>16&31,t>>11&31,t>>5&63,(31&t)<<1))}},e.exports=a},{"../utils":32}],19:[function(t,e,n){"use strict";var r=t("./Uint8ArrayReader");function a(t){r.call(this,t)}t("../utils").inherits(a,r),a.prototype.readData=function(t){this.checkOffset(t);var e=this.data.slice(this.zero+this.index,this.zero+this.index+t);return this.index+=t,e},e.exports=a},{"../utils":32,"./Uint8ArrayReader":21}],20:[function(t,e,n){"use strict";var r=t("./DataReader");function a(t){r.call(this,t)}t("../utils").inherits(a,r),a.prototype.byteAt=function(t){return this.data.charCodeAt(this.zero+t)},a.prototype.lastIndexOfSignature=function(t){return this.data.lastIndexOf(t)-this.zero},a.prototype.readAndCheckSignature=function(t){return t===this.readData(4)},a.prototype.readData=function(t){this.checkOffset(t);var e=this.data.slice(this.zero+this.index,this.zero+this.index+t);return this.index+=t,e},e.exports=a},{"../utils":32,"./DataReader":18}],21:[function(t,e,n){"use strict";var r=t("./ArrayReader");function a(t){r.call(this,t)}t("../utils").inherits(a,r),a.prototype.readData=function(t){var e;return this.checkOffset(t),0===t?new Uint8Array(0):(e=this.data.subarray(this.zero+this.index,this.zero+this.index+t),this.index+=t,e)},e.exports=a},{"../utils":32,"./ArrayReader":17}],22:[function(t,e,n){"use strict";var r=t("../utils"),a=t("../support"),o=t("./ArrayReader"),i=t("./StringReader"),s=t("./NodeBufferReader"),A=t("./Uint8ArrayReader");e.exports=function(t){var e=r.getTypeOf(t);return r.checkSupport(e),"string"!==e||a.uint8array?"nodebuffer"===e?new s(t):a.uint8array?new A(r.transformTo("uint8array",t)):new o(r.transformTo("array",t)):new i(t)}},{"../support":30,"../utils":32,"./ArrayReader":17,"./NodeBufferReader":19,"./StringReader":20,"./Uint8ArrayReader":21}],23:[function(t,e,n){"use strict";n.LOCAL_FILE_HEADER="PK",n.CENTRAL_FILE_HEADER="PK",n.CENTRAL_DIRECTORY_END="PK",n.ZIP64_CENTRAL_DIRECTORY_LOCATOR="PK",n.ZIP64_CENTRAL_DIRECTORY_END="PK",n.DATA_DESCRIPTOR="PK\b"},{}],24:[function(t,e,n){"use strict";var r=t("./GenericWorker"),a=t("../utils");function o(t){r.call(this,"ConvertWorker to "+t),this.destType=t}a.inherits(o,r),o.prototype.processChunk=function(t){this.push({data:a.transformTo(this.destType,t.data),meta:t.meta})},e.exports=o},{"../utils":32,"./GenericWorker":28}],25:[function(t,e,n){"use strict";var r=t("./GenericWorker"),a=t("../crc32");function o(){r.call(this,"Crc32Probe"),this.withStreamInfo("crc32",0)}t("../utils").inherits(o,r),o.prototype.processChunk=function(t){this.streamInfo.crc32=a(t.data,this.streamInfo.crc32||0),this.push(t)},e.exports=o},{"../crc32":4,"../utils":32,"./GenericWorker":28}],26:[function(t,e,n){"use strict";var r=t("../utils"),a=t("./GenericWorker");function o(t){a.call(this,"DataLengthProbe for "+t),this.propName=t,this.withStreamInfo(t,0)}r.inherits(o,a),o.prototype.processChunk=function(t){var e;t&&(e=this.streamInfo[this.propName]||0,this.streamInfo[this.propName]=e+t.data.length),a.prototype.processChunk.call(this,t)},e.exports=o},{"../utils":32,"./GenericWorker":28}],27:[function(t,e,n){"use strict";var r=t("../utils"),a=t("./GenericWorker");function o(t){a.call(this,"DataWorker");var e=this;this.dataIsReady=!1,this.index=0,this.max=0,this.data=null,this.type="",this._tickScheduled=!1,t.then(function(t){e.dataIsReady=!0,e.data=t,e.max=t&&t.length||0,e.type=r.getTypeOf(t),e.isPaused||e._tickAndRepeat()},function(t){e.error(t)})}r.inherits(o,a),o.prototype.cleanUp=function(){a.prototype.cleanUp.call(this),this.data=null},o.prototype.resume=function(){return!!a.prototype.resume.call(this)&&(!this._tickScheduled&&this.dataIsReady&&(this._tickScheduled=!0,r.delay(this._tickAndRepeat,[],this)),!0)},o.prototype._tickAndRepeat=function(){this._tickScheduled=!1,this.isPaused||this.isFinished||(this._tick(),this.isFinished)||(r.delay(this._tickAndRepeat,[],this),this._tickScheduled=!0)},o.prototype._tick=function(){if(this.isPaused||this.isFinished)return!1;var t=null,e=Math.min(this.max,this.index+16384);if(this.index>=this.max)return this.end();switch(this.type){case"string":t=this.data.substring(this.index,e);break;case"uint8array":t=this.data.subarray(this.index,e);break;case"array":case"nodebuffer":t=this.data.slice(this.index,e)}return this.index=e,this.push({data:t,meta:{percent:this.max?this.index/this.max*100:0}})},e.exports=o},{"../utils":32,"./GenericWorker":28}],28:[function(t,e,n){"use strict";function r(t){this.name=t||"default",this.streamInfo={},this.generatedError=null,this.extraStreamInfo={},this.isPaused=!0,this.isFinished=!1,this.isLocked=!1,this._listeners={data:[],end:[],error:[]},this.previous=null}r.prototype={push:function(t){this.emit("data",t)},end:function(){if(this.isFinished)return!1;this.flush();try{this.emit("end"),this.cleanUp(),this.isFinished=!0}catch(t){this.emit("error",t)}return!0},error:function(t){return!this.isFinished&&(this.isPaused?this.generatedError=t:(this.isFinished=!0,this.emit("error",t),this.previous&&this.previous.error(t),this.cleanUp()),!0)},on:function(t,e){return this._listeners[t].push(e),this},cleanUp:function(){this.streamInfo=this.generatedError=this.extraStreamInfo=null,this._listeners=[]},emit:function(t,e){if(this._listeners[t])for(var n=0;n "+t:t}},e.exports=r},{}],29:[function(t,e,n){"use strict";var l=t("../utils"),a=t("./ConvertWorker"),o=t("./GenericWorker"),c=t("../base64"),r=t("../support"),i=t("../external"),s=null;if(r.nodestream)try{s=t("../nodejs/NodejsStreamOutputAdapter")}catch(t){}function A(t,e,n){var r=e;switch(e){case"blob":case"arraybuffer":r="uint8array";break;case"base64":r="string"}try{this._internalType=r,this._outputType=e,this._mimeType=n,l.checkSupport(r),this._worker=t.pipe(new a(r)),t.lock()}catch(t){this._worker=new o("error"),this._worker.error(t)}}A.prototype={accumulate:function(t){return s=this,A=t,new i.Promise(function(e,n){var r=[],a=s._internalType,o=s._outputType,i=s._mimeType;s.on("data",function(t,e){r.push(t),A&&A(e)}).on("error",function(t){r=[],n(t)}).on("end",function(){try{var t=function(t,e,n){switch(t){case"blob":return l.newBlob(l.transformTo("arraybuffer",e),n);case"base64":return c.encode(e);default:return l.transformTo(t,e)}}(o,function(t,e){for(var n=0,r=null,a=0,o=0;o>>6:(n<65536?e[a++]=224|n>>>12:(e[a++]=240|n>>>18,e[a++]=128|n>>>12&63),e[a++]=128|n>>>6&63),e[a++]=128|63&n);return e},a.utf8decode=function(t){if(l.nodebuffer)return A.transformTo("nodebuffer",t).toString("utf-8");for(var e,n,r,a=t=A.transformTo(l.uint8array?"uint8array":"array",t),o=a.length,i=new Array(2*o),s=e=0;s>10&1023,i[e++]=56320|1023&n)}return i.length!==e&&(i.subarray?i=i.subarray(0,e):i.length=e),A.applyFromCharCode(i)},A.inherits(o,n),o.prototype.processChunk=function(t){var e=A.transformTo(l.uint8array?"uint8array":"array",t.data),n=(this.leftOver&&this.leftOver.length&&(l.uint8array?(n=e,(e=new Uint8Array(n.length+this.leftOver.length)).set(this.leftOver,0),e.set(n,this.leftOver.length)):e=this.leftOver.concat(e),this.leftOver=null),function(t,e){for(var n=(e=(e=e||t.length)>t.length?t.length:e)-1;0<=n&&128==(192&t[n]);)n--;return!(n<0)&&0!==n&&n+u[t[n]]>e?n:e}(e)),r=e;n!==e.length&&(l.uint8array?(r=e.subarray(0,n),this.leftOver=e.subarray(n,e.length)):(r=e.slice(0,n),this.leftOver=e.slice(n,e.length))),this.push({data:a.utf8decode(r),meta:t.meta})},o.prototype.flush=function(){this.leftOver&&this.leftOver.length&&(this.push({data:a.utf8decode(this.leftOver),meta:{}}),this.leftOver=null)},a.Utf8DecodeWorker=o,A.inherits(i,n),i.prototype.processChunk=function(t){this.push({data:a.utf8encode(t.data),meta:t.meta})},a.Utf8EncodeWorker=i},{"./nodejsUtils":14,"./stream/GenericWorker":28,"./support":30,"./utils":32}],32:[function(t,e,i){"use strict";var s=t("./support"),A=t("./base64"),n=t("./nodejsUtils"),r=t("set-immediate-shim"),l=t("./external");function a(t){return t}function c(t,e){for(var n=0;n>8;this.dir=!!(16&this.externalFileAttributes),0==t&&(this.dosPermissions=63&this.externalFileAttributes),3==t&&(this.unixPermissions=this.externalFileAttributes>>16&65535),this.dir||"/"!==this.fileNameStr.slice(-1)||(this.dir=!0)},parseZIP64ExtraField:function(t){var e;this.extraFields[1]&&(e=r(this.extraFields[1].value),this.uncompressedSize===a.MAX_VALUE_32BITS&&(this.uncompressedSize=e.readInt(8)),this.compressedSize===a.MAX_VALUE_32BITS&&(this.compressedSize=e.readInt(8)),this.localHeaderOffset===a.MAX_VALUE_32BITS&&(this.localHeaderOffset=e.readInt(8)),this.diskNumberStart===a.MAX_VALUE_32BITS)&&(this.diskNumberStart=e.readInt(4))},readExtraFields:function(t){var e,n,r,a=t.index+this.extraFieldsLength;for(this.extraFields||(this.extraFields={});t.index+4>>6:(n<65536?e[a++]=224|n>>>12:(e[a++]=240|n>>>18,e[a++]=128|n>>>12&63),e[a++]=128|n>>>6&63),e[a++]=128|63&n);return e},n.buf2binstring=function(t){return c(t,t.length)},n.binstring2buf=function(t){for(var e=new A.Buf8(t.length),n=0,r=e.length;n>10&1023,i[n++]=56320|1023&r)}return c(i,n)},n.utf8border=function(t,e){for(var n=(e=(e=e||t.length)>t.length?t.length:e)-1;0<=n&&128==(192&t[n]);)n--;return!(n<0)&&0!==n&&n+l[t[n]]>e?n:e}},{"./common":41}],43:[function(t,e,n){"use strict";e.exports=function(t,e,n,r){for(var a=65535&t|0,o=t>>>16&65535|0,i=0;0!==n;){for(n-=i=2e3>>1:n>>>1;t[e]=n}return t}();e.exports=function(t,e,n,r){var a=s,o=r+n;t^=-1;for(var i=r;i>>8^a[255&(t^e[i])];return-1^t}},{}],46:[function(t,R,e){"use strict";var s,u=t("../utils/common"),A=t("./trees"),p=t("./adler32"),f=t("./crc32"),n=t("./messages"),l=0,c=0,d=-2,r=2,h=8,a=286,o=30,i=19,O=2*a+1,M=15,g=3,m=258,v=m+g+1,y=42,b=113;function w(t,e){return t.msg=n[e],e}function x(t){return(t<<1)-(4t.avail_out?t.avail_out:n)&&(u.arraySet(t.output,e.pending_buf,e.pending_out,n,t.next_out),t.next_out+=n,e.pending_out+=n,t.total_out+=n,t.avail_out-=n,e.pending-=n,0===e.pending)&&(e.pending_out=0)}function S(t,e){A._tr_flush_block(t,0<=t.block_start?t.block_start:-1,t.strstart-t.block_start,e),t.block_start=t.strstart,P(t.strm)}function L(t,e){t.pending_buf[t.pending++]=e}function E(t,e){t.pending_buf[t.pending++]=e>>>8&255,t.pending_buf[t.pending++]=255&e}function T(t,e){var n,r,a=t.max_chain_length,o=t.strstart,i=t.prev_length,s=t.nice_match,A=t.strstart>t.w_size-v?t.strstart-(t.w_size-v):0,l=t.window,c=t.w_mask,u=t.prev,p=t.strstart+m,f=l[o+i-1],d=l[o+i];t.prev_length>=t.good_match&&(a>>=2),s>t.lookahead&&(s=t.lookahead);do{if(l[(n=e)+i]===d&&l[n+i-1]===f&&l[n]===l[o]&&l[++n]===l[o+1]){for(o+=2,n++;l[++o]===l[++n]&&l[++o]===l[++n]&&l[++o]===l[++n]&&l[++o]===l[++n]&&l[++o]===l[++n]&&l[++o]===l[++n]&&l[++o]===l[++n]&&l[++o]===l[++n]&&oA&&0!=--a);return i<=t.lookahead?i:t.lookahead}function B(t){var e,n,r,a,o,i,s,A,l,c=t.w_size;do{if(A=t.window_size-t.lookahead-t.strstart,t.strstart>=c+(c-v)){for(u.arraySet(t.window,t.window,c,c,0),t.match_start-=c,t.strstart-=c,t.block_start-=c,e=n=t.hash_size;r=t.head[--e],t.head[e]=c<=r?r-c:0,--n;);for(e=n=c;r=t.prev[--e],t.prev[e]=c<=r?r-c:0,--n;);A+=c}if(0===t.strm.avail_in)break;if(o=t.strm,i=t.window,s=t.strstart+t.lookahead,l=void 0,n=0===(l=(A=A)<(l=o.avail_in)?A:l)?0:(o.avail_in-=l,u.arraySet(i,o.input,o.next_in,l,s),1===o.state.wrap?o.adler=p(o.adler,i,l,s):2===o.state.wrap&&(o.adler=f(o.adler,i,l,s)),o.next_in+=l,o.total_in+=l,l),t.lookahead+=n,t.lookahead+t.insert>=g)for(a=t.strstart-t.insert,t.ins_h=t.window[a],t.ins_h=(t.ins_h<=g&&(t.ins_h=(t.ins_h<=g)if(r=A._tr_tally(t,t.strstart-t.match_start,t.match_length-g),t.lookahead-=t.match_length,t.match_length<=t.max_lazy_match&&t.lookahead>=g){for(t.match_length--;t.strstart++,t.ins_h=(t.ins_h<=g&&(t.ins_h=(t.ins_h<=g&&t.match_length<=t.prev_length){for(a=t.strstart+t.lookahead-g,r=A._tr_tally(t,t.strstart-1-t.prev_match,t.prev_length-g),t.lookahead-=t.prev_length-1,t.prev_length-=2;++t.strstart<=a&&(t.ins_h=(t.ins_h<t.pending_buf_size-5&&(n=t.pending_buf_size-5);;){if(t.lookahead<=1){if(B(t),0===t.lookahead&&e===l)return 1;if(0===t.lookahead)break}t.strstart+=t.lookahead,t.lookahead=0;var r=t.block_start+n;if((0===t.strstart||t.strstart>=r)&&(t.lookahead=t.strstart-r,t.strstart=r,S(t,!1),0===t.strm.avail_out))return 1;if(t.strstart-t.block_start>=t.w_size-v&&(S(t,!1),0===t.strm.avail_out))return 1}return t.insert=0,4===e?(S(t,!0),0===t.strm.avail_out?3:4):(t.strstart>t.block_start&&(S(t,!1),t.strm.avail_out),1)}),new k(4,4,8,4,D),new k(4,5,16,8,D),new k(4,6,32,32,D),new k(4,4,16,16,_),new k(8,16,32,32,_),new k(8,16,128,128,_),new k(8,32,128,256,_),new k(32,128,258,1024,_),new k(32,258,258,4096,_)],e.deflateInit=function(t,e){return I(t,e,h,15,8,0)},e.deflateInit2=I,e.deflateReset=F,e.deflateResetKeep=N,e.deflateSetHeader=function(t,e){return!t||!t.state||2!==t.state.wrap?d:(t.state.gzhead=e,c)},e.deflate=function(t,e){var n,r,a,o;if(!t||!t.state||5>8&255),L(r,r.gzhead.time>>16&255),L(r,r.gzhead.time>>24&255),L(r,9===r.level?2:2<=r.strategy||r.level<2?4:0),L(r,255&r.gzhead.os),r.gzhead.extra&&r.gzhead.extra.length&&(L(r,255&r.gzhead.extra.length),L(r,r.gzhead.extra.length>>8&255)),r.gzhead.hcrc&&(t.adler=f(t.adler,r.pending_buf,r.pending,0)),r.gzindex=0,r.status=69):(L(r,0),L(r,0),L(r,0),L(r,0),L(r,0),L(r,9===r.level?2:2<=r.strategy||r.level<2?4:0),L(r,3),r.status=b)):(i=h+(r.w_bits-8<<4)<<8,i|=(2<=r.strategy||r.level<2?0:r.level<6?1:6===r.level?2:3)<<6,0!==r.strstart&&(i|=32),i+=31-i%31,r.status=b,E(r,i),0!==r.strstart&&(E(r,t.adler>>>16),E(r,65535&t.adler)),t.adler=1)),69===r.status)if(r.gzhead.extra){for(a=r.pending;r.gzindex<(65535&r.gzhead.extra.length)&&(r.pending!==r.pending_buf_size||(r.gzhead.hcrc&&r.pending>a&&(t.adler=f(t.adler,r.pending_buf,r.pending-a,a)),P(t),a=r.pending,r.pending!==r.pending_buf_size));)L(r,255&r.gzhead.extra[r.gzindex]),r.gzindex++;r.gzhead.hcrc&&r.pending>a&&(t.adler=f(t.adler,r.pending_buf,r.pending-a,a)),r.gzindex===r.gzhead.extra.length&&(r.gzindex=0,r.status=73)}else r.status=73;if(73===r.status)if(r.gzhead.name){a=r.pending;do{if(r.pending===r.pending_buf_size&&(r.gzhead.hcrc&&r.pending>a&&(t.adler=f(t.adler,r.pending_buf,r.pending-a,a)),P(t),a=r.pending,r.pending===r.pending_buf_size)){o=1;break}}while(o=r.gzindexa&&(t.adler=f(t.adler,r.pending_buf,r.pending-a,a)),0===o&&(r.gzindex=0,r.status=91)}else r.status=91;if(91===r.status)if(r.gzhead.comment){a=r.pending;do{if(r.pending===r.pending_buf_size&&(r.gzhead.hcrc&&r.pending>a&&(t.adler=f(t.adler,r.pending_buf,r.pending-a,a)),P(t),a=r.pending,r.pending===r.pending_buf_size)){o=1;break}}while(o=r.gzindexa&&(t.adler=f(t.adler,r.pending_buf,r.pending-a,a)),0===o&&(r.status=103)}else r.status=103;if(103===r.status&&(r.gzhead.hcrc?(r.pending+2>r.pending_buf_size&&P(t),r.pending+2<=r.pending_buf_size&&(L(r,255&t.adler),L(r,t.adler>>8&255),t.adler=0,r.status=b)):r.status=b),0!==r.pending){if(P(t),0===t.avail_out)return r.last_flush=-1,c}else if(0===t.avail_in&&x(e)<=x(n)&&4!==e)return w(t,-5);if(666===r.status&&0!==t.avail_in)return w(t,-5);if(0!==t.avail_in||0!==r.lookahead||e!==l&&666!==r.status){var i=2===r.strategy?function(t,e){for(var n;;){if(0===t.lookahead&&(B(t),0===t.lookahead)){if(e===l)return 1;break}if(t.match_length=0,n=A._tr_tally(t,0,t.window[t.strstart]),t.lookahead--,t.strstart++,n&&(S(t,!1),0===t.strm.avail_out))return 1}return t.insert=0,4===e?(S(t,!0),0===t.strm.avail_out?3:4):t.last_lit&&(S(t,!1),0===t.strm.avail_out)?1:2}(r,e):3===r.strategy?function(t,e){for(var n,r,a,o,i=t.window;;){if(t.lookahead<=m){if(B(t),t.lookahead<=m&&e===l)return 1;if(0===t.lookahead)break}if(t.match_length=0,t.lookahead>=g&&0t.lookahead&&(t.match_length=t.lookahead)}if(t.match_length>=g?(n=A._tr_tally(t,1,t.match_length-g),t.lookahead-=t.match_length,t.strstart+=t.match_length,t.match_length=0):(n=A._tr_tally(t,0,t.window[t.strstart]),t.lookahead--,t.strstart++),n&&(S(t,!1),0===t.strm.avail_out))return 1}return t.insert=0,4===e?(S(t,!0),0===t.strm.avail_out?3:4):t.last_lit&&(S(t,!1),0===t.strm.avail_out)?1:2}(r,e):s[r.level].func(r,e);if(3!==i&&4!==i||(r.status=666),1===i||3===i)return 0===t.avail_out&&(r.last_flush=-1),c;if(2===i&&(1===e?A._tr_align(r):5!==e&&(A._tr_stored_block(r,0,0,!1),3===e)&&(C(r.head),0===r.lookahead)&&(r.strstart=0,r.block_start=0,r.insert=0),P(t),0===t.avail_out))return r.last_flush=-1,c}return 4!==e||!(r.wrap<=0)&&(2===r.wrap?(L(r,255&t.adler),L(r,t.adler>>8&255),L(r,t.adler>>16&255),L(r,t.adler>>24&255),L(r,255&t.total_in),L(r,t.total_in>>8&255),L(r,t.total_in>>16&255),L(r,t.total_in>>24&255)):(E(r,t.adler>>>16),E(r,65535&t.adler)),P(t),0=n.w_size&&(0===o&&(C(n.head),n.strstart=0,n.block_start=0,n.insert=0),A=new u.Buf8(n.w_size),u.arraySet(A,e,l-n.w_size,n.w_size,0),e=A,l=n.w_size),A=t.avail_in,i=t.next_in,s=t.input,t.avail_in=l,t.next_in=0,t.input=e,B(n);n.lookahead>=g;){for(r=n.strstart,a=n.lookahead-(g-1);n.ins_h=(n.ins_h<>>=r=n>>>24,x-=r,0==(r=n>>>16&255))f[p++]=65535&n;else{if(!(16&r)){if(0==(64&r)){n=C[(65535&n)+(w&(1<>>=r,x-=r),x<15&&(w+=c[l++]<>>=r=n>>>24,x-=r,!(16&(r=n>>>16&255))){if(0==(64&r)){n=P[(65535&n)+(w&(1<>>=r,x-=r,(r=p-d)>3,w&=(1<<(x-=a<<3))-1,t.next_in=l,t.next_out=p,t.avail_in=l>>24&255)+(t>>>8&65280)+((65280&t)<<8)+((255&t)<<24)}function o(){this.mode=0,this.last=!1,this.wrap=0,this.havedict=!1,this.flags=0,this.dmax=0,this.check=0,this.total=0,this.head=null,this.wbits=0,this.wsize=0,this.whave=0,this.wnext=0,this.window=null,this.hold=0,this.bits=0,this.length=0,this.offset=0,this.extra=0,this.lencode=null,this.distcode=null,this.lenbits=0,this.distbits=0,this.ncode=0,this.nlen=0,this.ndist=0,this.have=0,this.next=null,this.lens=new _.Buf16(320),this.work=new _.Buf16(288),this.lendyn=null,this.distdyn=null,this.sane=0,this.back=0,this.was=0}function i(t){var e;return t&&t.state?(e=t.state,t.total_in=t.total_out=e.total=0,t.msg="",e.wrap&&(t.adler=1&e.wrap),e.mode=M,e.last=0,e.havedict=0,e.dmax=32768,e.head=null,e.hold=0,e.bits=0,e.lencode=e.lendyn=new _.Buf32(r),e.distcode=e.distdyn=new _.Buf32(a),e.sane=1,e.back=-1,R):O}function s(t){var e;return t&&t.state?((e=t.state).wsize=0,e.whave=0,e.wnext=0,i(t)):O}function A(t,e){var n,r;return!t||!t.state||(r=t.state,e<0?(n=0,e=-e):(n=1+(e>>4),e<48&&(e&=15)),e&&(e<8||15=t.wsize?(_.arraySet(t.window,e,n-t.wsize,t.wsize,0),t.wnext=0,t.whave=t.wsize):(r<(a=t.wsize-t.wnext)&&(a=r),_.arraySet(t.window,e,n-r,a,t.wnext),(r-=a)?(_.arraySet(t.window,e,n-r,r,0),t.wnext=r,t.whave=t.wsize):(t.wnext+=a,t.wnext===t.wsize&&(t.wnext=0),t.whave>>8&255,n.check=N(n.check,E,2,0),c=l=0,n.mode=2;else if(n.flags=0,n.head&&(n.head.done=!1),!(1&n.wrap)||(((255&l)<<8)+(l>>8))%31)t.msg="incorrect header check",n.mode=30;else if(8!=(15&l))t.msg="unknown compression method",n.mode=30;else{if(c-=4,x=8+(15&(l>>>=4)),0===n.wbits)n.wbits=x;else if(x>n.wbits){t.msg="invalid window size",n.mode=30;break}n.dmax=1<>8&1),512&n.flags&&(E[0]=255&l,E[1]=l>>>8&255,n.check=N(n.check,E,2,0)),c=l=0,n.mode=3;case 3:for(;c<32;){if(0===s)break t;s--,l+=r[o++]<>>8&255,E[2]=l>>>16&255,E[3]=l>>>24&255,n.check=N(n.check,E,4,0)),c=l=0,n.mode=4;case 4:for(;c<16;){if(0===s)break t;s--,l+=r[o++]<>8),512&n.flags&&(E[0]=255&l,E[1]=l>>>8&255,n.check=N(n.check,E,2,0)),c=l=0,n.mode=5;case 5:if(1024&n.flags){for(;c<16;){if(0===s)break t;s--,l+=r[o++]<>>8&255,n.check=N(n.check,E,2,0)),c=l=0}else n.head&&(n.head.extra=null);n.mode=6;case 6:if(1024&n.flags&&((f=s<(f=n.length)?s:f)&&(n.head&&(x=n.head.extra_len-n.length,n.head.extra||(n.head.extra=new Array(n.head.extra_len)),_.arraySet(n.head.extra,r,o,f,x)),512&n.flags&&(n.check=N(n.check,r,f,o)),s-=f,o+=f,n.length-=f),n.length))break t;n.length=0,n.mode=7;case 7:if(2048&n.flags){if(0===s)break t;for(f=0;x=r[o+f++],n.head&&x&&n.length<65536&&(n.head.name+=String.fromCharCode(x)),x&&f>9&1,n.head.done=!0),t.adler=n.check=0,n.mode=12;break;case 10:for(;c<32;){if(0===s)break t;s--,l+=r[o++]<>>=7&c,c-=7&c,n.mode=27;else{for(;c<3;){if(0===s)break t;s--,l+=r[o++]<>>=1)){case 0:n.mode=14;break;case 1:B=D=void 0;var B,D=n;if(G){for(U=new _.Buf32(512),j=new _.Buf32(32),B=0;B<144;)D.lens[B++]=8;for(;B<256;)D.lens[B++]=9;for(;B<280;)D.lens[B++]=7;for(;B<288;)D.lens[B++]=8;for(I(1,D.lens,0,288,U,0,D.work,{bits:9}),B=0;B<32;)D.lens[B++]=5;I(2,D.lens,0,32,j,0,D.work,{bits:5}),G=!1}if(D.lencode=U,D.lenbits=9,D.distcode=j,D.distbits=5,n.mode=20,6!==e)break;l>>>=2,c-=2;break t;case 2:n.mode=17;break;case 3:t.msg="invalid block type",n.mode=30}l>>>=2,c-=2}break;case 14:for(l>>>=7&c,c-=7&c;c<32;){if(0===s)break t;s--,l+=r[o++]<>>16^65535)){t.msg="invalid stored block lengths",n.mode=30;break}if(n.length=65535&l,c=l=0,n.mode=15,6===e)break t;case 15:n.mode=16;case 16:if(f=n.length){if(0===(f=A<(f=s>>=5,c-=5,n.ndist=1+(31&l),l>>>=5,c-=5,n.ncode=4+(15&l),l>>>=4,c-=4,286>>=3,c-=3}for(;n.have<19;)n.lens[T[n.have++]]=0;if(n.lencode=n.lendyn,n.lenbits=7,P={bits:n.lenbits},C=I(0,n.lens,0,19,n.lencode,0,n.work,P),n.lenbits=P.bits,C){t.msg="invalid code lengths set",n.mode=30;break}n.have=0,n.mode=19;case 19:for(;n.have>>16&255,v=65535&L,!((g=L>>>24)<=c);){if(0===s)break t;s--,l+=r[o++]<>>=g,c-=g,n.lens[n.have++]=v;else{if(16===v){for(S=g+2;c>>=g,c-=g,0===n.have){t.msg="invalid bit length repeat",n.mode=30;break}x=n.lens[n.have-1],f=3+(3&l),l>>>=2,c-=2}else if(17===v){for(S=g+3;c>>=g)),l>>>=3,c=c-g-3}else{for(S=g+7;c>>=g)),l>>>=7,c=c-g-7}if(n.have+f>n.nlen+n.ndist){t.msg="invalid bit length repeat",n.mode=30;break}for(;f--;)n.lens[n.have++]=x}}if(30===n.mode)break;if(0===n.lens[256]){t.msg="invalid code -- missing end-of-block",n.mode=30;break}if(n.lenbits=9,P={bits:n.lenbits},C=I(1,n.lens,0,n.nlen,n.lencode,0,n.work,P),n.lenbits=P.bits,C){t.msg="invalid literal/lengths set",n.mode=30;break}if(n.distbits=6,n.distcode=n.distdyn,P={bits:n.distbits},C=I(2,n.lens,n.nlen,n.ndist,n.distcode,0,n.work,P),n.distbits=P.bits,C){t.msg="invalid distances set",n.mode=30;break}if(n.mode=20,6===e)break t;case 20:n.mode=21;case 21:if(6<=s&&258<=A){t.next_out=i,t.avail_out=A,t.next_in=o,t.avail_in=s,n.hold=l,n.bits=c,F(t,p),i=t.next_out,a=t.output,A=t.avail_out,o=t.next_in,r=t.input,s=t.avail_in,l=n.hold,c=n.bits,12===n.mode&&(n.back=-1);break}for(n.back=0;m=(L=n.lencode[l&(1<>>16&255,v=65535&L,!((g=L>>>24)<=c);){if(0===s)break t;s--,l+=r[o++]<>y)])>>>16&255,v=65535&L,!(y+(g=L>>>24)<=c);){if(0===s)break t;s--,l+=r[o++]<>>=y,c-=y,n.back+=y}if(l>>>=g,c-=g,n.back+=g,n.length=v,0===m){n.mode=26;break}if(32&m){n.back=-1,n.mode=12;break}if(64&m){t.msg="invalid literal/length code",n.mode=30;break}n.extra=15&m,n.mode=22;case 22:if(n.extra){for(S=n.extra;c>>=n.extra,c-=n.extra,n.back+=n.extra}n.was=n.length,n.mode=23;case 23:for(;m=(L=n.distcode[l&(1<>>16&255,v=65535&L,!((g=L>>>24)<=c);){if(0===s)break t;s--,l+=r[o++]<>y)])>>>16&255,v=65535&L,!(y+(g=L>>>24)<=c);){if(0===s)break t;s--,l+=r[o++]<>>=y,c-=y,n.back+=y}if(l>>>=g,c-=g,n.back+=g,64&m){t.msg="invalid distance code",n.mode=30;break}n.offset=v,n.extra=15&m,n.mode=24;case 24:if(n.extra){for(S=n.extra;c>>=n.extra,c-=n.extra,n.back+=n.extra}if(n.offset>n.dmax){t.msg="invalid distance too far back",n.mode=30;break}n.mode=25;case 25:if(0===A)break t;if(n.offset>(f=p-A)){if((f=n.offset-f)>n.whave&&n.sane){t.msg="invalid distance too far back",n.mode=30;break}d=f>n.wnext?(f-=n.wnext,n.wsize-f):n.wnext-f,f>n.length&&(f=n.length),h=n.window}else h=a,d=i-n.offset,f=n.length;for(A-=f=Af?(h=k[N+i[y]],T[B+i[y]]):(h=96,0),A=1<<(d=v-P),b=l=1<>P)+(l-=A)]=d<<24|h<<16|g|0,0!==l;);for(A=1<>=1;if(0!==A?E=(E&A-1)+A:E=0,y++,0==--D[v]){if(v===w)break;v=e[n+i[y]]}if(x>>7)]}function o(t,e){t.pending_buf[t.pending++]=255&e,t.pending_buf[t.pending++]=e>>>8&255}function C(t,e,n){t.bi_valid>r-n?(t.bi_buf|=e<>r-t.bi_valid,t.bi_valid+=n-r):(t.bi_buf|=e<>>=1,n<<=1,0<--e;);return n>>>1}function L(t,e,n){for(var r,a=new Array(16),o=0,i=1;i<=15;i++)a[i]=o=o+n[i-1]<<1;for(r=0;r<=e;r++){var s=t[2*r+1];0!==s&&(t[2*r]=S(a[s]++,s))}}function E(t){for(var e=0;e<286;e++)t.dyn_ltree[2*e]=0;for(e=0;e<30;e++)t.dyn_dtree[2*e]=0;for(e=0;e<19;e++)t.bl_tree[2*e]=0;t.dyn_ltree[512]=1,t.opt_len=t.static_len=0,t.last_lit=t.matches=0}function T(t){8>1;1<=n;n--)B(t,o,n);for(a=A;n=t.heap[1],t.heap[1]=t.heap[t.heap_len--],B(t,o,1),r=t.heap[1],t.heap[--t.heap_max]=n,t.heap[--t.heap_max]=r,o[2*a]=o[2*n]+o[2*r],t.depth[a]=(t.depth[n]>=t.depth[r]?t.depth[n]:t.depth[r])+1,o[2*n+1]=o[2*r+1]=a,t.heap[1]=a++,B(t,o,1),2<=t.heap_len;);t.heap[--t.heap_max]=t.heap[1];for(var c,u,p,f,d,h=t,g=e.dyn_tree,m=e.max_code,v=e.stat_desc.static_tree,y=e.stat_desc.has_stree,b=e.stat_desc.extra_bits,w=e.stat_desc.extra_base,x=e.stat_desc.max_length,C=0,P=0;P<=15;P++)h.bl_count[P]=0;for(g[2*h.heap[h.heap_max]+1]=0,c=h.heap_max+1;c<573;c++)x<(P=g[2*g[2*(u=h.heap[c])+1]+1]+1)&&(P=x,C++),g[2*u+1]=P,m>=7;i<30;i++)for(y[i]=a<<7,e=0;e<1<>>=1)if(1&e&&0!==t.dyn_ltree[2*n])return 0;if(0!==t.dyn_ltree[18]||0!==t.dyn_ltree[20]||0!==t.dyn_ltree[26])return 1;for(n=32;n<256;n++)if(0!==t.dyn_ltree[2*n])return 1;return 0}(t)),_(t,t.l_desc),_(t,t.d_desc),s=function(t){var e;for(k(t,t.dyn_ltree,t.l_desc.max_code),k(t,t.dyn_dtree,t.d_desc.max_code),_(t,t.bl_desc),e=18;3<=e&&0===t.bl_tree[2*c[e]+1];e--);return t.opt_len+=3*(e+1)+5+5+4,e}(t),a=t.opt_len+3+7>>>3,(o=t.static_len+3+7>>>3)<=a&&(a=o)):a=o=n+5,n+4<=a&&-1!==e)I(t,e,n,r);else if(4===t.strategy||o===a)C(t,2+(r?1:0),3),D(t,u,p);else{C(t,4+(r?1:0),3);var A=t,l=(e=t.l_desc.max_code+1,n=t.d_desc.max_code+1,s+1);for(C(A,e-257,5),C(A,n-1,5),C(A,l-4,4),i=0;i>>8&255,t.pending_buf[t.d_buf+2*t.last_lit+1]=255&e,t.pending_buf[t.l_buf+t.last_lit]=255&n,t.last_lit++,0===e?t.dyn_ltree[2*n]++:(t.matches++,e--,t.dyn_ltree[2*(d[n]+256+1)]++,t.dyn_dtree[2*x(e)]++),t.last_lit===t.lit_bufsize-1},e._tr_align=function(t){C(t,2,3),P(t,256,u),16===(t=t).bi_valid?(o(t,t.bi_buf),t.bi_buf=0,t.bi_valid=0):8<=t.bi_valid&&(t.pending_buf[t.pending++]=255&t.bi_buf,t.bi_buf>>=8,t.bi_valid-=8)}},{"../utils/common":41}],53:[function(t,e,n){"use strict";e.exports=function(){this.input=null,this.next_in=0,this.avail_in=0,this.total_in=0,this.output=null,this.next_out=0,this.avail_out=0,this.total_out=0,this.msg="",this.state=null,this.data_type=2,this.adler=0}},{}],54:[function(t,e,n){"use strict";e.exports="function"==typeof setImmediate?setImmediate:function(){var t=[].slice.apply(arguments);t.splice(1,0,0),setTimeout.apply(null,t)}},{}]},{},[10])(10)})}.call(this,void 0!==n?n:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}]},{},[1])(1)})}.call(this,void 0!==n?n:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}]},{},[1])(1)})}.call(this,void 0!==n?n:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}]},{},[1])(1)})}.call(this,void 0!==n?n:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}]},{},[1])(1)})}.call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}]},{},[1])(1)}),function r(a,o,i){function s(e,t){if(!o[e]){if(!a[e]){var n="function"==typeof require&&require;if(!t&&n)return n(e,!0);if(A)return A(e,!0);t=new Error("Cannot find module '"+e+"'");throw t.code="MODULE_NOT_FOUND",t}n=o[e]={exports:{}};a[e][0].call(n.exports,function(t){return s(a[e][1][t]||t)},n,n.exports,r,a,o,i)}return o[e].exports}for(var A="function"==typeof require&&require,t=0;ti;)o.call(t,r=a[i++])&&e.push(r);return e}},{104:104,107:107,108:108}],62:[function(t,e,n){function f(t,e,n){var r,a,o,i=t&f.F,s=t&f.G,A=t&f.P,l=t&f.B,c=s?d:t&f.S?d[e]||(d[e]={}):(d[e]||{})[y],u=s?h:h[e]||(h[e]={}),p=u[y]||(u[y]={});for(r in n=s?e:n)a=((o=!i&&c&&void 0!==c[r])?c:n)[r],o=l&&o?v(a,d):A&&"function"==typeof a?v(Function.call,a):a,c&&m(c,r,a,t&f.U),u[r]!=a&&g(u,r,o),A&&p[r]!=a&&(p[r]=a)}var d=t(70),h=t(52),g=t(72),m=t(118),v=t(54),y="prototype";d.core=h,f.F=1,f.G=2,f.S=4,f.P=8,f.B=16,f.W=32,f.U=64,f.R=128,e.exports=f},{118:118,52:52,54:54,70:70,72:72}],63:[function(t,e,n){var r=t(152)("match");e.exports=function(e){var n=/./;try{"/./"[e](n)}catch(t){try{return n[r]=!1,!"/./"[e](n)}catch(t){}}return!0}},{152:152}],64:[function(t,e,n){arguments[4][23][0].apply(n,arguments)},{23:23}],65:[function(t,e,n){"use strict";t(248);var r,A=t(118),l=t(72),c=t(64),u=t(57),p=t(152),f=t(120),d=p("species"),h=!c(function(){var t=/./;return t.exec=function(){var t=[];return t.groups={a:"7"},t},"7"!=="".replace(t,"$")}),g=(r=(t=/(?:)/).exec,t.exec=function(){return r.apply(this,arguments)},2===(t="ab".split(t)).length&&"a"===t[0]&&"b"===t[1]);e.exports=function(n,t,e){var o,r,a=p(n),i=!c(function(){var t={};return t[a]=function(){return 7},7!=""[n](t)}),s=i?!c(function(){var t=!1,e=/a/;return e.exec=function(){return t=!0,null},"split"===n&&(e.constructor={},e.constructor[d]=function(){return e}),e[a](""),!t}):void 0;i&&s&&("replace"!==n||h)&&("split"!==n||g)||(o=/./[a],e=(s=e(u,a,""[n],function(t,e,n,r,a){return e.exec===f?i&&!a?{done:!0,value:o.call(e,n,r)}:{done:!0,value:t.call(n,e,r)}:{done:!1}}))[0],r=s[1],A(String.prototype,n,e),l(RegExp.prototype,a,2==t?function(t,e){return r.call(t,this,e)}:function(t){return r.call(t,this)}))}},{118:118,120:120,152:152,248:248,57:57,64:64,72:72}],66:[function(t,e,n){"use strict";var r=t(38);e.exports=function(){var t=r(this),e="";return t.global&&(e+="g"),t.ignoreCase&&(e+="i"),t.multiline&&(e+="m"),t.unicode&&(e+="u"),t.sticky&&(e+="y"),e}},{38:38}],67:[function(t,e,n){"use strict";var d=t(79),h=t(81),g=t(141),m=t(54),v=t(152)("isConcatSpreadable");e.exports=function t(e,n,r,a,o,i,s,A){for(var l,c,u=o,p=0,f=!!s&&m(s,A,3);pdocument.F=Object<\/script>"),t.close(),l=t.F;e--;)delete l[A][i[e]];return l()};t.exports=Object.create||function(t,e){var n;return null!==t?(r[A]=a(t),n=new r,r[A]=null,n[s]=t):n=l(),void 0===e?n:o(n,e)}},{100:100,125:125,38:38,59:59,60:60,73:73}],99:[function(t,e,n){arguments[4][29][0].apply(n,arguments)},{143:143,29:29,38:38,58:58,74:74}],100:[function(t,e,n){var i=t(99),s=t(38),A=t(107);e.exports=t(58)?Object.defineProperties:function(t,e){s(t);for(var n,r=A(e),a=r.length,o=0;oa;)!i(r,n=e[a++])||~A(o,n)||o.push(n);return o}},{125:125,140:140,41:41,71:71}],107:[function(t,e,n){var r=t(106),a=t(60);e.exports=Object.keys||function(t){return r(t,a)}},{106:106,60:60}],108:[function(t,e,n){n.f={}.propertyIsEnumerable},{}],109:[function(t,e,n){var a=t(62),o=t(52),i=t(64);e.exports=function(t,e){var n=(o.Object||{})[t]||Object[t],r={};r[t]=e(n),a(a.S+a.F*i(function(){n(1)}),"Object",r)}},{52:52,62:62,64:64}],110:[function(t,e,n){var A=t(58),l=t(107),c=t(140),u=t(108).f;e.exports=function(s){return function(t){for(var e,n=c(t),r=l(n),a=r.length,o=0,i=[];o>>0||(o.test(t)?16:10))}:r},{134:134,135:135,70:70}],114:[function(t,e,n){e.exports=function(t){try{return{e:!1,v:t()}}catch(t){return{e:!0,v:t}}}},{}],115:[function(t,e,n){var r=t(38),a=t(81),o=t(96);e.exports=function(t,e){return r(t),a(e)&&e.constructor===t?e:((0,(t=o.f(t)).resolve)(e),t.promise)}},{38:38,81:81,96:96}],116:[function(t,e,n){arguments[4][30][0].apply(n,arguments)},{30:30}],117:[function(t,e,n){var a=t(118);e.exports=function(t,e,n){for(var r in e)a(t,r,e[r],n);return t}},{118:118}],118:[function(t,e,n){var o=t(70),i=t(72),s=t(71),A=t(147)("src"),r=t(69),a="toString",l=(""+r).split(a);t(52).inspectSource=function(t){return r.call(t)},(e.exports=function(t,e,n,r){var a="function"==typeof n;a&&!s(n,"name")&&i(n,"name",e),t[e]!==n&&(a&&!s(n,A)&&i(n,A,t[e]?""+t[e]:l.join(String(e))),t===o?t[e]=n:r?t[e]?t[e]=n:i(t,e,n):(delete t[e],i(t,e,n)))})(Function.prototype,a,function(){return"function"==typeof this&&this[A]||r.call(this)})},{147:147,52:52,69:69,70:70,71:71,72:72}],119:[function(t,e,n){"use strict";var r=t(47),a=RegExp.prototype.exec;e.exports=function(t,e){var n=t.exec;if("function"==typeof n){n=n.call(t,e);if("object"!=typeof n)throw new TypeError("RegExp exec method returned something other than an Object or null");return n}if("RegExp"!==r(t))throw new TypeError("RegExp#exec called on incompatible receiver");return a.call(t,e)}},{47:47}],120:[function(t,e,n){"use strict";var r,a,i=t(66),s=RegExp.prototype.exec,A=String.prototype.replace,t=s,l="lastIndex",c=(a=/b*/g,s.call(r=/a/,"a"),s.call(a,"a"),0!==r[l]||0!==a[l]),u=void 0!==/()??/.exec("")[1];e.exports=t=c||u?function(t){var e,n,r,a,o=this;return u&&(n=new RegExp("^"+o.source+"$(?!\\s)",i.call(o))),c&&(e=o[l]),r=s.call(o,t),c&&r&&(o[l]=o.global?r.index+r[0].length:e),u&&r&&1"+t+""}var a=t(62),o=t(64),i=t(57),s=/"/g;e.exports=function(e,t){var n={};n[e]=t(r),a(a.P+a.F*o(function(){var t=""[e]('"');return t!==t.toLowerCase()||3e&&(a=a.slice(0,e)),r?a+t:t+a)}},{133:133,141:141,57:57}],133:[function(t,e,n){"use strict";var a=t(139),o=t(57);e.exports=function(t){var e=String(o(this)),n="",r=a(t);if(r<0||r==1/0)throw RangeError("Count can't be negative");for(;0>>=1)&&(e+=e))1&r&&(n+=e);return n}},{139:139,57:57}],134:[function(t,e,n){function r(t,e,n){var r={},a=i(function(){return!!s[t]()||"​…"!="​…"[t]()}),e=r[t]=a?e(c):s[t];n&&(r[n]=e),o(o.P+o.F*a,"String",r)}var o=t(62),a=t(57),i=t(64),s=t(135),t="["+s+"]",A=RegExp("^"+t+t+"*"),l=RegExp(t+t+"*$"),c=r.trim=function(t,e){return t=String(a(t)),1&e&&(t=t.replace(A,"")),t=2&e?t.replace(l,""):t};e.exports=r},{135:135,57:57,62:62,64:64}],135:[function(t,e,n){e.exports="\t\n\v\f\r   ᠎              \u2028\u2029\ufeff"},{}],136:[function(t,e,n){function r(){var t,e=+this;m.hasOwnProperty(e)&&(t=m[e],delete m[e],t())}function a(t){r.call(t.data)}var o,i=t(54),s=t(76),A=t(73),l=t(59),c=t(70),u=c.process,p=c.setImmediate,f=c.clearImmediate,d=c.MessageChannel,h=c.Dispatch,g=0,m={},v="onreadystatechange";p&&f||(p=function(t){for(var e=[],n=1;n>1,l=23===e?w(2,-24)-w(2,-77):0,c=0,u=t<0||0===t&&1/t<0?1:0;for((t=G(t))!=t||t===y?(a=t!=t?1:0,r=n):(r=Q(W(t)/Y),t*(o=w(2,-r))<1&&(r--,o*=2),2<=(t+=1<=r+A?l/o:l*w(2,1-A))*o&&(r++,o/=2),n<=r+A?(a=0,r=n):1<=r+A?(a=(t*o-1)*w(2,e),r+=A):(a=t*w(2,A-1)*w(2,e),r=0));8<=e;i[c++]=255&a,a/=256,e-=8);for(r=r<>1,s=a-7,A=n-1,a=t[A--],l=127&a;for(a>>=7;0>=-s,s+=e;0>8&255]}function D(t){return[255&t,t>>8&255,t>>16&255,t>>24&255]}function X(t){return S(t,52,8)}function H(t){return S(t,23,4)}function _(t,e,n){U(t[f],e,{get:function(){return this[n]}})}function k(t,e,n,r){n=c(+n);if(n+e>t[C])throw v(d);var a=t[x]._b,n=n+t[P],t=a.slice(n,n+e);return r?t:t.reverse()}function N(t,e,n,r,a,o){n=c(+n);if(n+e>t[C])throw v(d);for(var i=t[x]._b,s=n+t[P],A=r(+a),l=0;lV;)(F=I[V++])in h||o(h,F,b[F]);O||(s.constructor=h)}var l=new g(new h(2)),q=g[f].setInt8;l.setInt8(0,2147483648),l.setInt8(1,2147483649),!l.getInt8(0)&&l.getInt8(1)||i(g[f],{setInt8:function(t,e){q.call(this,t,e<<24>>24)},setUint8:function(t,e){q.call(this,t,e<<24>>24)}},!0)}else h=function(t){A(this,h,u);t=c(t);this._b=j.call(new Array(t),0),this[C]=t},g=function(t,e,n){A(this,g,p),A(t,h,p);var r=t[C],e=M(e);if(e<0||r>24},getUint8:function(t){return k(this,1,t)[0]},getInt16:function(t){t=k(this,2,t,arguments[1]);return(t[1]<<8|t[0])<<16>>16},getUint16:function(t){t=k(this,2,t,arguments[1]);return t[1]<<8|t[0]},getInt32:function(t){return E(k(this,4,t,arguments[1]))},getUint32:function(t){return E(k(this,4,t,arguments[1]))>>>0},getFloat32:function(t){return L(k(this,4,t,arguments[1]),23,4)},getFloat64:function(t){return L(k(this,8,t,arguments[1]),52,8)},setInt8:function(t,e){N(this,1,t,T,e)},setUint8:function(t,e){N(this,1,t,T,e)},setInt16:function(t,e){N(this,2,t,B,e,arguments[2])},setUint16:function(t,e){N(this,2,t,B,e,arguments[2])},setInt32:function(t,e){N(this,4,t,D,e,arguments[2])},setUint32:function(t,e){N(this,4,t,D,e,arguments[2])},setFloat32:function(t,e){N(this,4,t,H,e,arguments[2])},setFloat64:function(t,e){N(this,8,t,X,e,arguments[2])}});t(h,u),t(g,p),o(g[f],a.VIEW,!0),e[u]=h,e[p]=g},{103:103,117:117,124:124,138:138,139:139,141:141,146:146,37:37,40:40,58:58,64:64,70:70,72:72,89:89,99:99}],146:[function(t,e,n){for(var r,a=t(70),o=t(72),t=t(147),i=t("typed_array"),s=t("view"),t=!(!a.ArrayBuffer||!a.DataView),A=t,l=0,c="Int8Array,Uint8Array,Uint8ClampedArray,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array".split(",");l<9;)(r=a[c[l++]])?(o(r.prototype,i,!0),o(r.prototype,s,!0)):A=!1;e.exports={ABV:t,CONSTR:A,TYPED:i,VIEW:s}},{147:147,70:70,72:72}],147:[function(t,e,n){var r=0,a=Math.random();e.exports=function(t){return"Symbol(".concat(void 0===t?"":t,")_",(++r+a).toString(36))}},{}],148:[function(t,e,n){t=t(70).navigator;e.exports=t&&t.userAgent||""},{70:70}],149:[function(t,e,n){var r=t(81);e.exports=function(t,e){if(r(t)&&t._t===e)return t;throw TypeError("Incompatible receiver, "+e+" required!")}},{81:81}],150:[function(t,e,n){var r=t(70),a=t(52),o=t(89),i=t(151),s=t(99).f;e.exports=function(t){var e=a.Symbol||(a.Symbol=!o&&r.Symbol||{});"_"==t.charAt(0)||t in e||s(e,t,{value:i.f(t)})}},{151:151,52:52,70:70,89:89,99:99}],151:[function(t,e,n){n.f=t(152)},{152:152}],152:[function(t,e,n){var r=t(126)("wks"),a=t(147),o=t(70).Symbol,i="function"==typeof o;(e.exports=function(t){return r[t]||(r[t]=i&&o[t]||(i?o:a)("Symbol."+t))}).store=r},{126:126,147:147,70:70}],153:[function(t,e,n){var r=t(47),a=t(152)("iterator"),o=t(88);e.exports=t(52).getIteratorMethod=function(t){if(null!=t)return t[a]||t["@@iterator"]||o[r(t)]}},{152:152,47:47,52:52,88:88}],154:[function(t,e,n){var r=t(62);r(r.P,"Array",{copyWithin:t(39)}),t(35)("copyWithin")},{35:35,39:39,62:62}],155:[function(t,e,n){"use strict";var r=t(62),a=t(42)(4);r(r.P+r.F*!t(128)([].every,!0),"Array",{every:function(t){return a(this,t,arguments[1])}})},{128:128,42:42,62:62}],156:[function(t,e,n){var r=t(62);r(r.P,"Array",{fill:t(40)}),t(35)("fill")},{35:35,40:40,62:62}],157:[function(t,e,n){"use strict";var r=t(62),a=t(42)(2);r(r.P+r.F*!t(128)([].filter,!0),"Array",{filter:function(t){return a(this,t,arguments[1])}})},{128:128,42:42,62:62}],158:[function(t,e,n){"use strict";var r=t(62),a=t(42)(6),o="findIndex",i=!0;o in[]&&Array(1)[o](function(){i=!1}),r(r.P+r.F*i,"Array",{findIndex:function(t){return a(this,t,1=t.length?(this._t=void 0,a(1)):a(0,"keys"==e?n:"values"==e?t[n]:[n,t[n]])},"values"),o.Arguments=o.Array,r("keys"),r("values"),r("entries")},{140:140,35:35,85:85,87:87,88:88}],165:[function(t,e,n){"use strict";var r=t(62),a=t(140),o=[].join;r(r.P+r.F*(t(77)!=Object||!t(128)(o)),"Array",{join:function(t){return o.call(a(this),void 0===t?",":t)}})},{128:128,140:140,62:62,77:77}],166:[function(t,e,n){"use strict";var r=t(62),a=t(140),o=t(139),i=t(141),s=[].lastIndexOf,A=!!s&&1/[1].lastIndexOf(1,-0)<0;r(r.P+r.F*(A||!t(128)(s)),"Array",{lastIndexOf:function(t){if(A)return s.apply(this,arguments)||0;var e=a(this),n=i(e.length),r=n-1;for((r=1>>=0)?31-Math.floor(Math.log(t+.5)*Math.LOG2E):32}})},{62:62}],189:[function(t,e,n){var t=t(62),r=Math.exp;t(t.S,"Math",{cosh:function(t){return(r(t=+t)+r(-t))/2}})},{62:62}],190:[function(t,e,n){var r=t(62),t=t(90);r(r.S+r.F*(t!=Math.expm1),"Math",{expm1:t})},{62:62,90:90}],191:[function(t,e,n){var r=t(62);r(r.S,"Math",{fround:t(91)})},{62:62,91:91}],192:[function(t,e,n){var t=t(62),A=Math.abs;t(t.S,"Math",{hypot:function(t,e){for(var n,r,a=0,o=0,i=arguments.length,s=0;o>>16)*r+n*(65535&e>>>16)<<16>>>0)}})},{62:62,64:64}],194:[function(t,e,n){t=t(62);t(t.S,"Math",{log10:function(t){return Math.log(t)*Math.LOG10E}})},{62:62}],195:[function(t,e,n){var r=t(62);r(r.S,"Math",{log1p:t(92)})},{62:62,92:92}],196:[function(t,e,n){t=t(62);t(t.S,"Math",{log2:function(t){return Math.log(t)/Math.LN2}})},{62:62}],197:[function(t,e,n){var r=t(62);r(r.S,"Math",{sign:t(93)})},{62:62,93:93}],198:[function(t,e,n){var r=t(62),a=t(90),o=Math.exp;r(r.S+r.F*t(64)(function(){return-2e-17!=!Math.sinh(-2e-17)}),"Math",{sinh:function(t){return Math.abs(t=+t)<1?(a(t)-a(-t))/2:(o(t-1)-o(-t-1))*(Math.E/2)}})},{62:62,64:64,90:90}],199:[function(t,e,n){var r=t(62),a=t(90),o=Math.exp;r(r.S,"Math",{tanh:function(t){var e=a(t=+t),n=a(-t);return e==1/0?1:n==1/0?-1:(e-n)/(o(t)+o(-t))}})},{62:62,90:90}],200:[function(t,e,n){t=t(62);t(t.S,"Math",{trunc:function(t){return(0x;x++)o(h,y=w[x])&&!o(b,y)&&p(b,y,u(h,y));(b.prototype=g).constructor=b,t(118)(a,d,b)}},{101:101,103:103,118:118,134:134,143:143,48:48,58:58,64:64,70:70,71:71,75:75,98:98,99:99}],202:[function(t,e,n){t=t(62);t(t.S,"Number",{EPSILON:Math.pow(2,-52)})},{62:62}],203:[function(t,e,n){var r=t(62),a=t(70).isFinite;r(r.S,"Number",{isFinite:function(t){return"number"==typeof t&&a(t)}})},{62:62,70:70}],204:[function(t,e,n){var r=t(62);r(r.S,"Number",{isInteger:t(80)})},{62:62,80:80}],205:[function(t,e,n){t=t(62);t(t.S,"Number",{isNaN:function(t){return t!=t}})},{62:62}],206:[function(t,e,n){var r=t(62),a=t(80),o=Math.abs;r(r.S,"Number",{isSafeInteger:function(t){return a(t)&&o(t)<=9007199254740991}})},{62:62,80:80}],207:[function(t,e,n){t=t(62);t(t.S,"Number",{MAX_SAFE_INTEGER:9007199254740991})},{62:62}],208:[function(t,e,n){t=t(62);t(t.S,"Number",{MIN_SAFE_INTEGER:-9007199254740991})},{62:62}],209:[function(t,e,n){var r=t(62),t=t(112);r(r.S+r.F*(Number.parseFloat!=t),"Number",{parseFloat:t})},{112:112,62:62}],210:[function(t,e,n){var r=t(62),t=t(113);r(r.S+r.F*(Number.parseInt!=t),"Number",{parseInt:t})},{113:113,62:62}],211:[function(t,e,n){"use strict";function s(t,e){for(var n=-1,r=e;++n<6;)i[n]=(r+=t*i[n])%1e7,r=o(r/1e7)}function A(t){for(var e=6,n=0;0<=--e;)i[e]=o((n+=i[e])/t),n=n%t*1e7}function l(){for(var t,e=6,n="";0<=--e;)""===n&&0!==e&&0===i[e]||(t=String(i[e]),n=""===n?t:n+f.call("0",7-t.length)+t);return n}function c(t,e,n){return 0===e?n:e%2==1?c(t,e-1,n*t):c(t*t,e/2,n)}var r=t(62),u=t(139),p=t(34),f=t(133),a=1..toFixed,o=Math.floor,i=[0,0,0,0,0,0],d="Number.toFixed: incorrect invocation!";r(r.P+r.F*(!!a&&("0.000"!==8e-5.toFixed(3)||"1"!==.9.toFixed(0)||"1.25"!==1.255.toFixed(2)||"1000000000000000128"!==0xde0b6b3a7640080.toFixed(0))||!t(64)(function(){a.call({})})),"Number",{toFixed:function(t){var e,n,r,a=p(this,d),t=u(t),o="",i="0";if(t<0||20n;){a=void 0;o=void 0;i=void 0;s=void 0;A=void 0;l=void 0;c=void 0;var r=f[n++];var a,o,i,s=e?r.ok:r.fail,A=r.resolve,l=r.reject,c=r.domain;try{s?(e||(2==u._h&&g(u),u._h=1),!0===s?a=t:(c&&c.enter(),a=s(t),c&&(c.exit(),i=!0)),a===r.promise?l(T("Promise-chain cycle")):(o=d(a))?o.call(a,A,l):A(a)):l(t)}catch(r){c&&!i&&c.exit(),l(r)}}u._c=[],u._n=!1,p&&!u._h&&h(u)}))}function o(t){var e=this;e._d||(e._d=!0,(e=e._w||e)._v=t,e._s=2,e._a||(e._a=e._c.slice()),a(e,!0))}function h(a){w.call(c,function(){var t,e,n=a._v,r=F(a);if(r&&(t=P(function(){k?B.emit("unhandledRejection",n,a):(e=c.onunhandledrejection)?e({promise:a,reason:n}):(e=c.console)&&e.error&&e.error("Unhandled promise rejection",n)}),a._h=k||F(a)?2:1),a._a=void 0,r&&t.e)throw t.v})}function g(e){w.call(c,function(){var t;k?B.emit("rejectionHandled",e):(t=c.onrejectionhandled)&&t({promise:e,reason:e._v})})}var e,i,s,A,l=n(89),c=n(70),u=n(54),t=n(47),p=n(62),f=n(81),m=n(33),v=n(37),y=n(68),b=n(127),w=n(136).set,x=n(95)(),C=n(96),P=n(114),S=n(148),L=n(115),E="Promise",T=c.TypeError,B=c.process,D=B&&B.versions,M=D&&D.v8||"",_=c[E],k="process"==t(B),N=i=C.f,D=!!function(){try{var t=_.resolve(1),e=(t.constructor={})[n(152)("species")]=function(t){t(r,r)};return(k||"function"==typeof PromiseRejectionEvent)&&t.then(r)instanceof e&&0!==M.indexOf("6.6")&&-1===S.indexOf("Chrome/66")}catch(t){}}(),F=function(t){return 1!==t._h&&0===(t._a||t._c).length},I=function(t){var n,r=this;if(!r._d){r._d=!0,r=r._w||r;try{if(r===t)throw T("Promise can't be resolved itself");(n=d(t))?x(function(){var e={_w:r,_d:!1};try{n.call(t,u(I,e,1),u(o,e,1))}catch(t){o.call(e,t)}}):(r._v=t,r._s=1,a(r,!1))}catch(t){o.call({_w:r,_d:!1},t)}}};D||(_=function(t){v(this,_,E,"_h"),m(t),e.call(this);try{t(u(I,this,1),u(o,this,1))}catch(t){o.call(this,t)}},(e=function(t){this._c=[],this._a=void 0,this._s=0,this._d=!1,this._v=void 0,this._h=0,this._n=!1}).prototype=n(117)(_.prototype,{then:function(t,e){var n=N(b(this,_));return n.ok="function"!=typeof t||t,n.fail="function"==typeof e&&e,n.domain=k?B.domain:void 0,this._c.push(n),this._a&&this._a.push(n),this._s&&a(this,!1),n.promise},catch:function(t){return this.then(void 0,t)}}),s=function(){var t=new e;this.promise=t,this.resolve=u(I,t,1),this.reject=u(o,t,1)},C.f=N=function(t){return t===_||t===A?new s:i(t)}),p(p.G+p.W+p.F*!D,{Promise:_}),n(124)(_,E),n(123)(E),A=n(52)[E],p(p.S+p.F*!D,E,{reject:function(t){var e=N(this);return(0,e.reject)(t),e.promise}}),p(p.S+p.F*(l||!D),E,{resolve:function(t){return L(l&&this===A?_:this,t)}}),p(p.S+p.F*!(D&&n(86)(function(t){_.all(t).catch(r)})),E,{all:function(t){var i=this,e=N(i),s=e.resolve,A=e.reject,n=P(function(){var r=[],a=0,o=1;y(t,!1,function(t){var e=a++,n=!1;r.push(void 0),o++,i.resolve(t).then(function(t){n||(n=!0,r[e]=t,--o)||s(r)},A)}),--o||s(r)});return n.e&&A(n.v),e.promise},race:function(t){var e=this,n=N(e),r=n.reject,a=P(function(){y(t,!1,function(t){e.resolve(t).then(n.resolve,r)})});return a.e&&r(a.v),n.promise}})},{114:114,115:115,117:117,123:123,124:124,127:127,136:136,148:148,152:152,33:33,37:37,47:47,52:52,54:54,62:62,68:68,70:70,81:81,86:86,89:89,95:95,96:96}],233:[function(t,e,n){var r=t(62),a=t(33),o=t(38),i=(t(70).Reflect||{}).apply,s=Function.apply;r(r.S+r.F*!t(64)(function(){i(function(){})}),"Reflect",{apply:function(t,e,n){t=a(t),n=o(n);return i?i(t,e,n):s.call(t,e,n)}})},{33:33,38:38,62:62,64:64,70:70}],234:[function(t,e,n){var r=t(62),a=t(98),o=t(33),i=t(38),s=t(81),A=t(64),l=t(46),c=(t(70).Reflect||{}).construct,u=A(function(){function t(){}return!(c(function(){},[],t)instanceof t)}),p=!A(function(){c(function(){})});r(r.S+r.F*(u||p),"Reflect",{construct:function(t,e){o(t),i(e);var n=arguments.length<3?t:o(arguments[2]);if(p&&!u)return c(t,e,n);if(t==n){switch(e.length){case 0:return new t;case 1:return new t(e[0]);case 2:return new t(e[0],e[1]);case 3:return new t(e[0],e[1],e[2]);case 4:return new t(e[0],e[1],e[2],e[3])}var r=[null];return r.push.apply(r,e),new(l.apply(t,r))}r=n.prototype,n=a(s(r)?r:Object.prototype),r=Function.apply.call(t,n,e);return s(r)?r:n}})},{33:33,38:38,46:46,62:62,64:64,70:70,81:81,98:98}],235:[function(t,e,n){var r=t(99),a=t(62),o=t(38),i=t(143);a(a.S+a.F*t(64)(function(){Reflect.defineProperty(r.f({},1,{value:1}),1,{value:2})}),"Reflect",{defineProperty:function(t,e,n){o(t),e=i(e,!0),o(n);try{return r.f(t,e,n),!0}catch(t){return!1}}})},{143:143,38:38,62:62,64:64,99:99}],236:[function(t,e,n){var r=t(62),a=t(101).f,o=t(38);r(r.S,"Reflect",{deleteProperty:function(t,e){var n=a(o(t),e);return!(n&&!n.configurable)&&delete t[e]}})},{101:101,38:38,62:62}],237:[function(t,e,n){"use strict";function r(t){this._t=o(t),this._i=0;var e,n=this._k=[];for(e in t)n.push(e)}var a=t(62),o=t(38);t(84)(r,"Object",function(){var t,e=this._k;do{if(this._i>=e.length)return{value:void 0,done:!0}}while(!((t=e[this._i++])in this._t));return{value:t,done:!1}}),a(a.S,"Reflect",{enumerate:function(t){return new r(t)}})},{38:38,62:62,84:84}],238:[function(t,e,n){var r=t(101),a=t(62),o=t(38);a(a.S,"Reflect",{getOwnPropertyDescriptor:function(t,e){return r.f(o(t),e)}})},{101:101,38:38,62:62}],239:[function(t,e,n){var r=t(62),a=t(105),o=t(38);r(r.S,"Reflect",{getPrototypeOf:function(t){return a(o(t))}})},{105:105,38:38,62:62}],240:[function(t,e,n){var o=t(101),i=t(105),s=t(71),r=t(62),A=t(81),l=t(38);r(r.S,"Reflect",{get:function t(e,n){var r,a=arguments.length<3?e:arguments[2];return l(e)===a?e[n]:(r=o.f(e,n))?s(r,"value")?r.value:void 0!==r.get?r.get.call(a):void 0:A(r=i(e))?t(r,n,a):void 0}})},{101:101,105:105,38:38,62:62,71:71,81:81}],241:[function(t,e,n){t=t(62);t(t.S,"Reflect",{has:function(t,e){return e in t}})},{62:62}],242:[function(t,e,n){var r=t(62),a=t(38),o=Object.isExtensible;r(r.S,"Reflect",{isExtensible:function(t){return a(t),!o||o(t)}})},{38:38,62:62}],243:[function(t,e,n){var r=t(62);r(r.S,"Reflect",{ownKeys:t(111)})},{111:111,62:62}],244:[function(t,e,n){var r=t(62),a=t(38),o=Object.preventExtensions;r(r.S,"Reflect",{preventExtensions:function(t){a(t);try{return o&&o(t),!0}catch(t){return!1}}})},{38:38,62:62}],245:[function(t,e,n){var r=t(62),a=t(122);a&&r(r.S,"Reflect",{setPrototypeOf:function(t,e){a.check(t,e);try{return a.set(t,e),!0}catch(t){return!1}}})},{122:122,62:62}],246:[function(t,e,n){var s=t(99),A=t(101),l=t(105),c=t(71),r=t(62),u=t(116),p=t(38),f=t(81);r(r.S,"Reflect",{set:function t(e,n,r){var a,o=arguments.length<4?e:arguments[3],i=A.f(p(e),n);if(!i){if(f(a=l(e)))return t(a,n,r,o);i=u(0)}if(c(i,"value")){if(!1===i.writable||!f(o))return!1;if(a=A.f(o,n)){if(a.get||a.set||!1===a.writable)return!1;a.value=r,s.f(o,n,a)}else s.f(o,n,u(0,r));return!0}return void 0!==i.set&&(i.set.call(o,r),!0)}})},{101:101,105:105,116:116,38:38,62:62,71:71,81:81,99:99}],247:[function(t,e,n){var r=t(70),o=t(75),a=t(99).f,i=t(103).f,s=t(82),A=t(66),l=d=r.RegExp,c=d.prototype,u=/a/g,p=/a/g,f=new d(u)!==u;if(t(58)&&(!f||t(64)(function(){return p[t(152)("match")]=!1,d(u)!=u||d(p)==p||"/a/i"!=d(u,"i")}))){for(var d=function(t,e){var n=this instanceof d,r=s(t),a=void 0===e;return!n&&r&&t.constructor===d&&a?t:o(f?new l(r&&!a?t.source:t,e):l((r=t instanceof d)?t.source:t,r&&a?A.call(t):e),n?this:c,d)},h=i(l),g=0;h.length>g;)!function(e){e in d||a(d,e,{configurable:!0,get:function(){return l[e]},set:function(t){l[e]=t}})}(h[g++]);(c.constructor=d).prototype=c,t(118)(r,"RegExp",d)}t(123)("RegExp")},{103:103,118:118,123:123,152:152,58:58,64:64,66:66,70:70,75:75,82:82,99:99}],248:[function(t,e,n){"use strict";var r=t(120);t(62)({target:"RegExp",proto:!0,forced:r!==/./.exec},{exec:r})},{120:120,62:62}],249:[function(t,e,n){t(58)&&"g"!=/./g.flags&&t(99).f(RegExp.prototype,"flags",{configurable:!0,get:t(66)})},{58:58,66:66,99:99}],250:[function(t,e,n){"use strict";var c=t(38),u=t(141),p=t(36),f=t(119);t(65)("match",1,function(r,a,A,l){return[function(t){var e=r(this),n=null==t?void 0:t[a];return void 0!==n?n.call(t,e):new RegExp(t)[a](String(e))},function(t){var e=l(A,t,this);if(e.done)return e.value;var n=c(t),r=String(this);if(!n.global)return f(n,r);for(var a=n.unicode,o=[],i=n.lastIndex=0;null!==(s=f(n,r));){var s=String(s[0]);""===(o[i]=s)&&(n.lastIndex=p(r,u(n.lastIndex),a)),i++}return 0===i?null:o}]})},{119:119,141:141,36:36,38:38,65:65}],251:[function(t,e,n){"use strict";var x=t(38),C=t(142),P=t(141),S=t(139),L=t(36),E=t(119),T=Math.max,B=Math.min,D=Math.floor,_=/\$([$&`']|\d\d?|<[^>]*>)/g,k=/\$([$&`']|\d\d?)/g;t(65)("replace",2,function(a,o,b,w){return[function(t,e){var n=a(this),r=null==t?void 0:t[o];return void 0!==r?r.call(t,n,e):b.call(String(n),t,e)},function(t,e){var n=w(b,t,this,e);if(n.done)return n.value;var r,a=x(t),o=String(this),i="function"==typeof e,s=(i||(e=String(e)),a.global);s&&(r=a.unicode,a.lastIndex=0);for(var A=[];;){var l=E(a,o);if(null===l)break;if(A.push(l),!s)break;""===String(l[0])&&(a.lastIndex=L(o,P(a.lastIndex),r))}for(var c,u="",p=0,f=0;f>>0,c=new RegExp(t.source,s+"g");(r=p.call(c,n))&&!(A<(a=c[P])&&(i.push(n.slice(A,r.index)),1>>0;if(0==s)return[];if(0===r.length)return null===w(i,r)?[r]:[];for(var A=0,l=0,c=[];l>10),e%1024+56320))}return n.join("")}})},{137:137,62:62}],266:[function(t,e,n){"use strict";var r=t(62),a=t(130);r(r.P+r.F*t(63)("includes"),"String",{includes:function(t){return!!~a(this,t,"includes").indexOf(t,1=t.length?{value:void 0,done:!0}:(t=r(t,e),this._i+=t.length,{value:t,done:!1})})},{129:129,85:85}],269:[function(t,e,n){"use strict";t(131)("link",function(e){return function(t){return e(this,"a","href",t)}})},{131:131}],270:[function(t,e,n){var r=t(62),i=t(140),s=t(141);r(r.S,"String",{raw:function(t){for(var e=i(t.raw),n=s(e.length),r=arguments.length,a=[],o=0;oa;)l(B,e=n[a++])||e==E||e==z||r.push(e);return r}function i(t){for(var e,n=t===_,r=Z(n?D:m(t)),a=[],o=0;r.length>o;)!l(B,e=r[o++])||n&&!l(_,e)||a.push(B[e]);return a}function s(t,e,n){return t===_&&s(D,e,n),g(t),e=v(e,!0),g(n),(l(B,e)?(n.enumerable?(l(t,E)&&t[E][e]&&(t[E][e]=!1),n=b(n,{enumerable:y(0,!1)})):(l(t,E)||x(t,E,y(1,{})),t[E][e]=!0),F):x)(t,e,n)}var A=t(70),l=t(71),c=t(58),u=t(62),M=t(118),z=t(94).KEY,p=t(64),f=t(126),d=t(124),U=t(147),h=t(152),j=t(151),G=t(150),Q=t(61),W=t(79),g=t(38),Y=t(81),X=t(142),m=t(140),v=t(143),y=t(116),b=t(98),H=t(102),V=t(101),w=t(104),q=t(99),J=t(107),K=V.f,x=q.f,Z=H.f,C=A.Symbol,P=A.JSON,S=P&&P.stringify,L="prototype",E=h("_hidden"),$=h("toPrimitive"),tt={}.propertyIsEnumerable,T=f("symbol-registry"),B=f("symbols"),D=f("op-symbols"),_=Object[L],f="function"==typeof C&&!!w.f,k=A.QObject,N=!k||!k[L]||!k[L].findChild,F=c&&p(function(){return 7!=b(x({},"a",{get:function(){return x(this,"a",{value:7}).a}})).a})?function(t,e,n){var r=K(_,e);r&&delete _[e],x(t,e,n),r&&t!==_&&x(_,e,r)}:x,I=f&&"symbol"==typeof C.iterator?function(t){return"symbol"==typeof t}:function(t){return t instanceof C};f||(M((C=function(){if(this instanceof C)throw TypeError("Symbol is not a constructor!");var e=U(0nt;)h(et[nt++]);for(var rt=J(h.store),at=0;rt.length>at;)G(rt[at++]);u(u.S+u.F*!f,"Symbol",{for:function(t){return l(T,t+="")?T[t]:T[t]=C(t)},keyFor:function(t){if(!I(t))throw TypeError(t+" is not a symbol!");for(var e in T)if(T[e]===t)return e},useSetter:function(){N=!0},useSimple:function(){N=!1}}),u(u.S+u.F*!f,"Object",{create:function(t,e){return void 0===e?b(t):n(b(t),e)},defineProperty:s,defineProperties:n,getOwnPropertyDescriptor:a,getOwnPropertyNames:o,getOwnPropertySymbols:i});k=p(function(){w.f(1)});u(u.S+u.F*k,"Object",{getOwnPropertySymbols:function(t){return w.f(X(t))}}),P&&u(u.S+u.F*(!f||p(function(){var t=C();return"[null]"!=S([t])||"{}"!=S({a:t})||"{}"!=S(Object(t))})),"JSON",{stringify:function(t){for(var e,n,r=[t],a=1;as;)void 0!==(n=a(r,e=o[s++]))&&u(i,e,n);return i}})},{101:101,111:111,140:140,53:53,62:62}],296:[function(t,e,n){var r=t(62),a=t(110)(!1);r(r.S,"Object",{values:function(t){return a(t)}})},{110:110,62:62}],297:[function(t,e,n){"use strict";var r=t(62),a=t(52),o=t(70),i=t(127),s=t(115);r(r.P+r.R,"Promise",{finally:function(e){var n=i(this,a.Promise||o.Promise),t="function"==typeof e;return this.then(t?function(t){return s(n,e()).then(function(){return t})}:e,t?function(t){return s(n,e()).then(function(){throw t})}:e)}})},{115:115,127:127,52:52,62:62,70:70}],298:[function(t,e,n){"use strict";var r=t(62),a=t(132),t=t(148),t=/Version\/10\.\d+(\.\d+)?( Mobile\/\w+)? Safari\//.test(t);r(r.P+r.F*t,"String",{padEnd:function(t){return a(this,t,1s[0]&&e[1]/g,">").replace(/"/g,""").replace(/'/g,"'")}function I(t){return"number"==typeof t&&100").concat(e,""):"")}function z(t){var e="solid",n="",r="",a="";return t&&("string"==typeof t?n=t:(t.type&&(e=t.type),t.color&&(n=t.color),t.alpha&&(r+='')),t.transparency&&(r+=''))),a+="solid"===e?"".concat(M(n,r),""):""),a}function g(t){return t._rels.length+t._relsChart.length+t._relsMedia.length+1}function vt(t){if(t&&"object"==typeof t)return"outer"!==t.type&&"inner"!==t.type&&"none"!==t.type&&(console.warn("Warning: shadow.type options are `outer`, `inner` or `none`."),t.type="outer"),t.angle&&((isNaN(Number(t.angle))||t.angle<0||359 ".concat(JSON.stringify(l))),s.push(l),l=[]),0o&&(i.push(e),e=[],n=""),e.push(t),n+=t.text.toString()}),0=i&&(i=t._lineHeight)}),c maxH) => ".concat((u/k).toFixed(2)," + ").concat((A._lineHeight/k).toFixed(2)," > ").concat(c/k)),console.log("|-----------------------------------------------------------------------|\n\n")),0n&&(n=t._lineHeight)}),v.rows.push(e),u+=n}),l=a[o]),A._lines.shift());Array.isArray(l.text)&&(A?l.text=l.text.concat(A):0===l.text.length&&(l.text=l.text.concat({_type:_.tablecell,text:""}))),o===p.length-1&&(u+=i),o=o \n'),i.file("_rels/.rels",'\n'),i.file("docProps/app.xml",'Microsoft Macintosh Excel0falseWorksheets1Sheet1falsefalsefalse16.0300\n'),i.file("docProps/core.xml",'PptxGenJSPptxGenJS'+(new Date).toISOString()+''+(new Date).toISOString()+""),i.file("xl/_rels/workbook.xml.rels",''),i.file("xl/styles.xml",'\n'),i.file("xl/theme/theme1.xml",''),i.file("xl/workbook.xml",'\n'),i.file("xl/worksheets/_rels/sheet1.xml.rels",'\n'),''),c=(m.opts._type===b.BUBBLE||m.opts._type===b.BUBBLE3D?l+=''):m.opts._type===b.SCATTER?l+=''):l=A?(r=g.length,g[0].labels.forEach(function(t){return r+=t.filter(function(t){return t&&""!==t}).length}),l+'')+""):(t=g.length+g[0].labels.length*g[0].labels[0].length+g[0].labels.length,a=g.length+g[0].labels.length*g[0].labels[0].length+1,l+'')+''),m.opts._type===b.BUBBLE||m.opts._type===b.BUBBLE3D?g.forEach(function(t,e){0===e?l+="X-Axis":l=(l+="".concat(F(t.name||"Y-Axis".concat(e)),""))+"".concat(F("Size".concat(e)),"")}):g.forEach(function(t){l+="".concat(F((t.name||" ").replace("X-Axis","X-Values")),"")}),m.opts._type!==b.BUBBLE&&m.opts._type!==b.BUBBLE3D&&m.opts._type!==b.SCATTER&&g[0].labels.slice().reverse().forEach(function(t){t.filter(function(t){return t&&""!==t}).forEach(function(t){l+="".concat(F(t),"")})}),l+="\n",i.file("xl/sharedStrings.xml",l),''),u=(m.opts._type===b.BUBBLE||m.opts._type===b.BUBBLE3D?(c=(c+=''))+''),o=1,g.forEach(function(t,e){0===e?c+=''):(c+=''),o++,c+=''))})):m.opts._type===b.SCATTER?(c=(c+='
'))+''),g.forEach(function(t,e){c+='')})):(c=(c+='
'))+''),g[0].labels.forEach(function(t,e){c+='')}),g.forEach(function(t,e){c+='')})),c=(c+="")+''+"
",i.file("xl/tables/table1.xml",c),'');if(u+='',m.opts._type===b.BUBBLE||m.opts._type===b.BUBBLE3D?u+=''):m.opts._type===b.SCATTER?u+=''):u+=''),u=u+''+'',m.opts._type===b.BUBBLE||m.opts._type===b.BUBBLE3D){for(var u=(u+="")+'')+'0',p=1;p').concat(p,"");u+="",g[0].values.forEach(function(t,e){u=(u+=''))+'').concat(t,"");for(var n=2,r=1;r').concat(g[r].values[e]||"",""))+'').concat(g[r].sizes[e]||"",""),n++;u+=""})}else if(m.opts._type===b.SCATTER){u=(u+="")+'');for(p=0;p').concat(p,"");u+="",g[0].values.forEach(function(t,e){u=(u+=''))+'').concat(t,"");for(var n=1;n').concat(g[n].values[e]||0===g[n].values[e]?g[n].values[e]:"","");u+=""})}else if(u+="",A){u+='');for(p=0;p0');for(p=g[0].labels.length-1;p').concat(p,"");u+="";for(var f=g.length,d=g[0].labels[0].length,h=g[0].labels.length,p=0;p');var r=f,a=g[0].labels.slice().reverse();a.forEach(function(t,e){t[n]&&(t=0===e?1:a[e-1].filter(function(t){return t&&""!==t}).length,r+=t,u+='').concat(r,""))});for(var t=0;t').concat(g[t].values[n]||0,"");u+=""}(p)}else{u+=''),g[0].labels.forEach(function(t,e){u+='0')});for(var p=0;p').concat(p+1,"");u+="",g[0].labels[0].forEach(function(t,e){u+='');for(var n=g[0].labels.length-1;0<=n;n--)u=(u+=''))+"".concat(g.length+e+1,"")+"";for(var r=0;r').concat(g[r].values[e]||"","");u+=""})}u+='\n',i.file("xl/worksheets/sheet1.xml",u),i.generateAsync({type:"base64"}).then(function(t){v.file("ppt/embeddings/Microsoft_Excel_Worksheet".concat(m.globalId,".xlsx"),t,{base64:!0}),v.file("ppt/charts/_rels/"+m.fileName+".rels",''+'')+""),v.file("ppt/charts/".concat(m.fileName),function(a){var t,o='',i=!1;o=(o+='')+'')+"",a.opts.showTitle?o=o+_t({title:a.opts.title||"Chart Title",color:a.opts.titleColor,fontFace:a.opts.titleFontFace,fontSize:a.opts.titleFontSize||et,titleAlign:a.opts.titleAlign,titleBold:a.opts.titleBold,titlePos:a.opts.titlePos,titleRotate:a.opts.titleRotate},a.opts.x,a.opts.y)+'':o+='';a.opts._type===b.BAR3D&&(o+=''));o+="",a.opts.layout?o=(o=(o=(o=(o=(o=(o=(o+="")+' ')+' ')+' ')+' ')+' ')+' ')+" ":o+="";Array.isArray(a.opts._type)?a.opts._type.forEach(function(t){var e=y(y({},a.opts),t.options),n=e.secondaryValAxis?st:S,r=e.secondaryCatAxis?lt:At;i=i||e.secondaryValAxis,o+=Tt(t.type,t.data,e,n,r)}):o+=Tt(a.opts._type,a.data,a.opts,S,At);if(a.opts._type!==b.PIE&&a.opts._type!==b.DOUGHNUT){if(a.opts.valAxes&&1 ')+' ')+' ')+' ')+("none"!==e.serGridLine.style?kt(e.serGridLine):""),e.showSerAxisTitle&&(r+=_t({color:e.serAxisTitleColor,fontFace:e.serAxisTitleFontFace,fontSize:e.serAxisTitleFontSize,titleRotate:e.serAxisTitleRotate,title:e.serAxisTitle||"Axis Title"}));r=(r=(r=(r=(r=(r=(r=(r=(r=(r=(r=(r=(r=(r=(r=(r+=' '))+' ')+' '))+' ')+(e.serAxisLineShow?"".concat(M(e.serAxisLineColor||x.color),""):"")+' ')+" ")+" ")+" ")+" ")+' '))+" ".concat(M(e.serAxisLabelColor||C),""))+' '))+" ")+' ')+" ")+' ',e.serAxisLabelFrequency&&(r+=' ');e.serLabelFormatCode&&(["serAxisBaseTimeUnit","serAxisMajorTimeUnit","serAxisMinorTimeUnit"].forEach(function(t){!e[t]||"string"==typeof e[t]&&["days","months","years"].includes(t.toLowerCase())||(console.warn('"'.concat(t,"\" must be one of: 'days','months','years' !")),e[t]=null)}),e.serAxisBaseTimeUnit&&(r+=' ')),e.serAxisMajorTimeUnit&&(r+=' ')),e.serAxisMinorTimeUnit&&(r+=' ')),e.serAxisMajorUnit&&(r+=' ')),e.serAxisMinorUnit)&&(r+=' '));return r+=""}(a.opts,ct,S))),null!=(t=a.opts)&&t.catAxes&&null!=(t=a.opts)&&t.catAxes[1]&&(o+=Bt(y(y({},a.opts),a.opts.catAxes[1]),lt,st))}a.opts.showDataTable&&(o=(o=(o=(o=(o=(o=(o=(o=(o=(o=(o=(o=(o=(o=(o=(o+="")+' '))+' '))+' '))+' '))+" ")+' ')+" ")+' ')+' ')+' '))+' ')+' ')+" ")+' ')+" ");o=(o=(o=(o+=" ")+(null!=(t=a.opts.plotArea.fill)&&t.color?z(a.opts.plotArea.fill):""))+(a.opts.plotArea.border?'').concat(z(a.opts.plotArea.border.color),""):"")+" ")+" ",a.opts.showLegend&&(o=(o+="")+'',(a.opts.legendFontFace||a.opts.legendFontSize||a.opts.legendColor)&&(o=(o=(o=(o+="")+" ")+" ")+(a.opts.legendFontSize?''):""),a.opts.legendColor&&(o+=z(a.opts.legendColor)),a.opts.legendFontFace&&(o+=''),a.opts.legendFontFace&&(o+=''),o=(o=(o+=" ")+' ')+" "),o+="");o=(o+=' ')+' ',a.opts._type===b.SCATTER&&(o+='');return o=(o=(o=(o=(o+="")+(null!=(t=a.opts.chartArea.fill)&&t.color?z(a.opts.chartArea.fill):""))+(a.opts.chartArea.border?'').concat(z(a.opts.chartArea.border.color),""):""))+" ")+''}(m)),e("")}).catch(function(t){n(t)})})];case 1:return[2,t.sent()]}})})}function Tt(r,a,o,t,e){var i=-1,s=1,n=null,A="";switch(r){case b.AREA:case b.BAR:case b.BAR3D:case b.LINE:case b.RADAR:A+=""),r===b.AREA&&"stacked"===o.barGrouping&&(A+=''),r!==b.BAR&&r!==b.BAR3D||(A=(A+='')+''),r===b.RADAR&&(A+=''),A+='',a.forEach(function(t){i++,A=(A=(A=(A+="")+' ')+" ")+" Sheet1!$"+L(t._dataIndex+t.labels.length+1)+"$1")+' '+F(t.name)+" ";var e=o.chartColors?o.chartColors[i%o.chartColors.length]:null;A+=" ","transparent"===e?A+="":o.chartColorsOpacity?A+=""+M(e,''))+"":A+=""+M(e)+"",r===b.LINE||r===b.RADAR?0===o.lineSize?A+="":A=(A+='').concat(M(e),""))+('':o.dataBorder&&(A+='').concat(M(o.dataBorder.color),'')),A=A+v(o.shadow,c)+' ',r!==b.RADAR&&(A=(A+="")+''),o.dataLabelBkgrdColors&&(A+="".concat(M(e),"")),A=(A=(A=(A+="")+''))+"".concat(M(o.dataLabelColor||C),""))+'')+"",o.dataLabelPosition&&(A+='')),A=(A=(A=(A+='')+''))+''))+'')+""),r!==b.LINE&&r!==b.RADAR||(A=(A+="")+' ',o.lineDataSymbolSize&&(A+='')),A=(A=(A+=" ")+" ".concat(M(o.chartColors[t._dataIndex+1>o.chartColors.length?Math.floor(Math.random()*o.chartColors.length):t._dataIndex]),""))+' ').concat(M(o.lineDataSymbolLineColor||e),'')+" "),r!==b.BAR&&r!==b.BAR3D||1!==a.length||!(o.chartColors&&o.chartColors!==pt&&1")+' ')+' ',0===o.lineSize?A+="":A=r===b.BAR?(A+="")+' ':(A+=" ")+' ',A=A+v(o.shadow,c)+" "}),A+="",o.catLabelFormatCode?(A=(A=(A=(A+=" ")+" Sheet1!$A$2:$A$".concat(t.labels[0].length+1,"")+" ")+" "+(o.catLabelFormatCode||"General")+"")+' '),t.labels[0].forEach(function(t,e){return A+='').concat(F(t),"")}),A+=" "):(A=(A=(A+=" ")+" Sheet1!$A$2:$".concat(L(t.labels.length),"$").concat(t.labels[0].length+1,"")+" ")+' '),t.labels.forEach(function(t){A+="",t.forEach(function(t,e){return A+='').concat(F(t),"")}),A+=""}),A+=" "),A=(A=(A=(A=A+""+" ")+"Sheet1!$".concat(L(t._dataIndex+t.labels.length+1),"$2:$").concat(L(t._dataIndex+t.labels.length+1),"$").concat(t.labels[0].length+1,"")+" ")+" "+(o.valLabelFormatCode||o.dataTableFormatCode||"General")+"")+' '),t.values.forEach(function(t,e){return A+='').concat(t||0===t?t:"","")}),A+=" ",r===b.LINE&&(A+=''),A+=""}),A=(A=(A=(A=(A+=" ")+' ')+" ")+' '))+" "+M(o.dataLabelColor||C)+"")+' ',o.dataLabelPosition&&(A+=' '),A=(A=(A=(A+=' ')+' ')+' ')+' ')+" ",r===b.BAR?A=(A+=' '))+' '):r===b.BAR3D?A=(A=(A+=' '))+' '))+(' ':r===b.LINE&&(A+=' '),A=(A+=''))+"");break;case b.SCATTER:A=(A+="")+''+'',i=-1,a.filter(function(t,e){return 0")+' '))+' ')+" ")+" Sheet1!$".concat(L(t+2),"$1"))+' '+F(n.name)+" ";var r,e=o.chartColors[i%o.chartColors.length];"transparent"===e?A+="":o.chartColorsOpacity?A+=""+M(e,'')+"":A+=""+M(e)+"",0===o.lineSize?A+="":A=(A+='').concat(M(e),""))+''),A=(A=(A+=v(o.shadow,c))+" "+"")+' ',o.lineDataSymbolSize&&(A+='')),A=(A=(A+="")+"".concat(M(o.chartColors[t+1>o.chartColors.length?Math.floor(Math.random()*o.chartColors.length):t]),""))+'').concat(M(o.lineDataSymbolLineColor||o.chartColors[i%o.chartColors.length]),'')+"",o.showLabel&&(r=ht("-xxxx-xxxx-xxxx-xxxxxxxxxxxx"),!n.labels[0]||"custom"!==o.dataLabelFormatScatter&&"customXY"!==o.dataLabelFormatScatter||(A+="",n.labels[0].forEach(function(t,e){"custom"!==o.dataLabelFormatScatter&&"customXY"!==o.dataLabelFormatScatter||(A=(A=(A=(A+=" ")+' ')+" ")+' ')+" "+F(t)+" ",A=("customXY"!==o.dataLabelFormatScatter||/^ *$/.test(t)?A:(A=(A=(A=(A=(A=(A=(A=(A=(A=(A+=" ")+' ( ')+' ')+' ')+" ["+F(n.name)+" ")+' , ')+' ')+' ')+" ["+F(n.name)+"] ")+' ) ')+' ')+" ",o.dataLabelPosition&&(A+=' '),A=(A+=' ')+' ')+" ")}),A+=""),"XY"===o.dataLabelFormatScatter)&&(A+=' ',o.dataLabelPosition&&(A+=' '),A=(A=(A=(A+=' ')+' '))+' '))+' ')+' '),1===a.length&&o.chartColors!==pt&&n.values.forEach(function(t,e){t=t<0?o.invertedColors||o.chartColors||pt:o.chartColors||[];A=(A+=" ")+' ')+' ',0===o.lineSize?A+="":A=(A+="")+' ',A=A+v(o.shadow,c)+" "}),A=(A=(A+=" ")+" Sheet1!$A$2:$A$".concat(a[0].values.length+1,"")+" General")+' '),a[0].values.forEach(function(t,e){A+='').concat(t||0===t?t:"","")}),A=(A=(A+=" ")+" Sheet1!$".concat(L(t+2),"$2:$").concat(L(t+2),"$").concat(a[0].values.length+1,"")+" General")+' '),a[0].values.forEach(function(t,e){A+='').concat(n.values[e]||0===n.values[e]?n.values[e]:"","")}),A=(A+=" ")+''}),A=(A=(A=(A=(A+=" ")+' ')+" ")+' '))+" "+M(o.dataLabelColor||C)+"")+' ',o.dataLabelPosition&&(A+=' '),A=(A=(A+=' ')+' ')+' ',A=(A+=''))+("");break;case b.BUBBLE:case b.BUBBLE3D:A=A+""+'',i=-1,a.filter(function(t,e){return 0")+' '))+' ')+" ")+" Sheet1!$"+L(s+1)+"$1")+' '+F(n.name)+" ";t=o.chartColors[i%o.chartColors.length];"transparent"===t?A+="":o.chartColorsOpacity?A+="".concat(M(t,''),""):A+=""+M(t)+"",0===o.lineSize?A+="":o.dataBorder?A+='').concat(M(o.dataBorder.color),''):A=(A+='').concat(M(t),""))+''),A=A+v(o.shadow,c)+"",A=(A=(A+=" ")+" Sheet1!$A$2:$A$".concat(a[0].values.length+1,"")+" General")+' '),a[0].values.forEach(function(t,e){A+='').concat(t||0===t?t:"","")}),A=(A+=" ")+"Sheet1!$".concat(L(s+1),"$2:$").concat(L(s+1),"$").concat(a[0].values.length+1,""),s++,A=(A+=" General")+' '),a[0].values.forEach(function(t,e){A+='').concat(n.values[e]||0===n.values[e]?n.values[e]:"","")}),A=(A+=" ")+"Sheet1!$".concat(L(s+1),"$2:$").concat(L(s+1),"$").concat(n.sizes.length+1,""),s++,A=(A+=" General")+' '),n.sizes.forEach(function(t,e){A+='').concat(t||"","")}),A=(A+=" ")+' '}),A=(A=(A=(A=(A+="")+'')+"")+''))+"".concat(M(o.dataLabelColor||C),""))+'')+"",o.dataLabelPosition&&(A+='')),A=(A=(A=(A=(A+='')+''))+'')+' ')+' ')+'')+"";break;case b.DOUGHNUT:case b.PIE:n=a[0],A=(A=(A=(A=(A=(A=(A=(A=(A=A+("")+' ')+""+' ')+' '+" ")+" "+" Sheet1!$B$1")+" "+' ')+(' '+F(n.name)+""))+" "+" ")+" "+" ")+' '+' ',o.dataNoEffects?A+="":A+=v(o.shadow,c),A+=" ",n.labels[0].forEach(function(t,e){A=(A=(A+="")+' ')+' ')+"".concat(M(o.chartColors[e+1>o.chartColors.length?Math.floor(Math.random()*o.chartColors.length):e]),""),o.dataBorder&&(A+='').concat(M(o.dataBorder.color),'')),A=A+v(o.shadow,c)+" "}),A+="",n.labels[0].forEach(function(t,e){A=(A=(A=(A=(A=(A+="")+' '))+' ')+" ")+' '))+" "+M(o.dataLabelColor||C)+"")+' ')+" ",r===b.PIE&&o.dataLabelPosition&&(A+='')),A=(A=(A=(A=(A+=' ')+' ')+' ')+' ')+' '}),A=(A=(A=(A=(A=(A=(A=(A=(A=(A=(A=(A=(A=(A=A+' ')+" ")+" "+" ")+" "+" ")+' ')+' ')+" "+" ")+" "+" ")+(r===b.PIE?'':""))+' '+' ')+' '+' ')+' '+' ')+' ')+"")+""+" ")+" Sheet1!$A$2:$A$".concat(n.labels[0].length+1,"")+" ")+' '),n.labels[0].forEach(function(t,e){A+='').concat(F(t),"")}),A=(A=(A=(A=(A+=" ")+" "+"")+" "+" ")+" Sheet1!$B$2:$B$".concat(n.labels[0].length+1,"")+" ")+' '),n.values.forEach(function(t,e){A+='').concat(t||0===t?t:"","")}),A=(A=(A=A+" "+" ")+" "+" ")+' '),r===b.DOUGHNUT&&(A+='')),A+="";break;default:A+=""}return A}function Bt(e,t,n){var r="";return e._type===b.SCATTER||e._type===b.BUBBLE||e._type===b.BUBBLE3D?r+="":r+="",r=(r+=' ')+" "+(''),!e.catAxisMaxVal&&0!==e.catAxisMaxVal||(r+='')),!e.catAxisMinVal&&0!==e.catAxisMinVal||(r+='')),r=(r=(r=r+""+(' '))+(' '))+("none"!==e.catGridLine.style?kt(e.catGridLine):""),e.showCatAxisTitle&&(r+=_t({color:e.catAxisTitleColor,fontFace:e.catAxisTitleFontFace,fontSize:e.catAxisTitleFontSize,titleRotate:e.catAxisTitleRotate,title:e.catAxisTitle||"Axis Title"})),e._type===b.SCATTER||e._type===b.BUBBLE||e._type===b.BUBBLE3D?r+=' ':r+=' ',e._type===b.SCATTER?r+=' ':r=(r=(r+=' ')+' ')+' ',r=(r=(r=(r=(r=(r+=" ")+' '))+(e.catAxisLineShow?""+M(e.catAxisLineColor||x.color)+"":""))+(' '))+" "+" ")+" "+" ",e.catAxisLabelRotate?r+=''):r+="",r=(r=(r=(r=(r=(r=(r=(r=(r=(r=(r+=" ")+" "+" ")+' '))+(" "+M(e.catAxisLabelColor||C)+""))+(' '))+" "+" ")+(' ')+" ")+" "+(' '))+" '))+' '+' ')+' '),e.catAxisLabelFrequency&&(r+=' '),(e.catLabelFormatCode||e._type===b.SCATTER||e._type===b.BUBBLE||e._type===b.BUBBLE3D)&&(e.catLabelFormatCode&&(["catAxisBaseTimeUnit","catAxisMajorTimeUnit","catAxisMinorTimeUnit"].forEach(function(t){!e[t]||"string"==typeof e[t]&&["days","months","years"].includes(e[t].toLowerCase())||(console.warn('"'.concat(t,"\" must be one of: 'days','months','years' !")),e[t]=null)}),e.catAxisBaseTimeUnit&&(r+=''),e.catAxisMajorTimeUnit&&(r+=''),e.catAxisMinorTimeUnit)&&(r+=''),e.catAxisMajorUnit&&(r+='')),e.catAxisMinorUnit)&&(r+='')),e._type===b.SCATTER||e._type===b.BUBBLE||e._type===b.BUBBLE3D?r+="":r+="",r}function Dt(t,e){var n=e===S?"col"===t.barDir?"l":"b":"col"!==t.barDir?"r":"t",r=(e===st&&(n="r"),e===S?At:lt),a="",a=(a+="")+(' ')+" ";return t.valAxisLogScaleBase&&(a+='')),a+='',!t.valAxisMaxVal&&0!==t.valAxisMaxVal||(a+='')),!t.valAxisMinVal&&0!==t.valAxisMinVal||(a+='')),a=(a+=" ")+' ')+(' '),"none"!==t.valGridLine.style&&(a+=kt(t.valGridLine)),t.showValAxisTitle&&(a+=_t({color:t.valAxisTitleColor,fontFace:t.valAxisTitleFontFace,fontSize:t.valAxisTitleFontSize,titleRotate:t.valAxisTitleRotate,title:t.valAxisTitle||"Axis Title"})),a+=''),t._type===b.SCATTER?a+=' ':a=(a=(a+=' ')+' ')+' ',a=(a=(a=(a=(a=(a=(a=(a=(a=(a=(a=(a=(a=(a+=" ")+' '))+(t.valAxisLineShow?""+M(t.valAxisLineColor||x.color)+"":""))+(' '))+" "+" ")+" "+" ")+" ")+" ")+" "+" ")+' '))+(" "+M(t.valAxisLabelColor||C)+""))+(' '))+" "+" ")+(' ')+" ")+" "+(' '),"number"==typeof t.catAxisCrossesAt?a+=' '):"string"==typeof t.catAxisCrossesAt?a+=' ':a+=' ',a+=' ',t.valAxisMajorUnit&&(a+=' ')),t.valAxisDisplayUnit&&(a+='').concat(t.valAxisDisplayUnitLabel?"":"","")),a+=""}function _t(t,e,n){var r="left"===t.titleAlign||"right"===t.titleAlign?''):"",a=t.titleRotate?''):"",o=t.fontSize?'sz="'.concat(Math.round(100*t.fontSize),'"'):"",i=t.titleBold?1:0,s="";return t.titlePos&&"number"==typeof t.titlePos.x&&"number"==typeof t.titlePos.y&&(1<=(e=0===(e=t.titlePos.x+e)?0:e*(e/5)/10)&&(e/=10),.1<=e&&(e/=10),1<=(n=0===(n=t.titlePos.y+n)?0:n*(n/5)/10)&&(n/=10),.1<=n&&(n/=10),s='')),"\n \n \n ".concat(a,"\n \n \n ").concat(r,"\n \n ').concat(M(t.color||C),'\n \n \n \n \n \n ').concat(M(t.color||C),'\n \n \n ').concat(F(t.title)||"","\n \n \n \n \n ").concat(s,'\n \n ')}function L(t){t-=1;return t<=25?ut[t]:"".concat(ut[Math.floor(t/ut.length-1)]).concat(ut[t%ut.length])}function v(t,e){var n,r,a,o,i,s;return t?"object"!=typeof t?(console.warn("`shadow` options must be an object. Ex: `{shadow: {type:'none'}}`"),""):(n="",t=(e=y(y({},e),t)).type||"outer",r=R(e.blur),a=R(e.offset),o=Math.round(6e4*e.angle),i=e.color,s=Math.round(1e5*e.opacity),e=e.rotateWithShape?1:0,(n=(n=(n+="'))+''))+''))+"")+""):""}function kt(t){var e="";return(e+=" ")+' ')+(' ')+(' ')+" "+" "+""}function Nt(t){if(t&&"flat"!==t){if("square"===t)return"sq";if("round"===t)return"rnd";throw new Error("Invalid chart line cap: ".concat(t))}return"flat"}function Ft(t){var o="undefined"!=typeof require&&"undefined"==typeof window?require("fs"):null,i="undefined"!=typeof require&&"undefined"==typeof window?require("https"):null,e=[],s=t._relsMedia.filter(function(t){return"online"!==t.type&&!t.data&&(!t.path||t.path&&!t.path.includes("preencoded"))}),n=[];return s.forEach(function(t){n.includes(t.path)?t.isDuplicate=!0:(t.isDuplicate=!1,n.push(t.path))}),s.filter(function(t){return!t.isDuplicate}).forEach(function(a){e.push(new Promise(function(n,r){var e;if(o&&0!==a.path.indexOf("http"))try{var t=o.readFileSync(a.path);a.data=Buffer.from(t).toString("base64"),s.filter(function(t){return t.isDuplicate&&t.path===a.path}).forEach(function(t){return t.data=a.data}),n("done")}catch(t){a.data=h,s.filter(function(t){return t.isDuplicate&&t.path===a.path}).forEach(function(t){return t.data=a.data}),r(new Error('ERROR: Unable to read media: "'.concat(a.path,'"\n').concat(String(t))))}else o&&i&&0===a.path.indexOf("http")?i.get(a.path,function(t){var e="";t.setEncoding("binary"),t.on("data",function(t){return e+=t}),t.on("end",function(){a.data=Buffer.from(e,"binary").toString("base64"),s.filter(function(t){return t.isDuplicate&&t.path===a.path}).forEach(function(t){return t.data=a.data}),n("done")}),t.on("error",function(t){a.data=h,s.filter(function(t){return t.isDuplicate&&t.path===a.path}).forEach(function(t){return t.data=a.data}),r(new Error("ERROR! Unable to load image (https.get): ".concat(a.path)))})}):((e=new XMLHttpRequest).onload=function(){var t=new FileReader;t.onloadend=function(){a.data=t.result,s.filter(function(t){return t.isDuplicate&&t.path===a.path}).forEach(function(t){return t.data=a.data}),a.isSvgPng?It(a).then(function(){n("done")}).catch(function(t){r(t)}):n("done")},t.readAsDataURL(e.response)},e.onerror=function(t){a.data=h,s.filter(function(t){return t.isDuplicate&&t.path===a.path}).forEach(function(t){return t.data=a.data}),r(new Error("ERROR! Unable to load image (xhr.onerror): ".concat(a.path)))},e.open("GET",a.path),e.responseType="blob",e.send())}))}),t._relsMedia.filter(function(t){return t.isSvgPng&&t.data}).forEach(function(t){o?(t.data=h,e.push(Promise.resolve().then(function(){return"done"}))):e.push(It(t))}),e}function It(a){return u(this,void 0,void 0,function(){return p(this,function(t){switch(t.label){case 0:return[4,new Promise(function(n,e){var r=new Image;r.onload=function(){r.width+r.height===0&&r.onerror("h/w=0");var t=document.createElement("CANVAS"),e=t.getContext("2d");t.width=r.width,t.height=r.height,e.drawImage(r,0,0);try{a.data=t.toDataURL(a.type),n("done")}catch(t){r.onerror(t)}},r.onerror=function(t){a.data=h,e(new Error("ERROR! Unable to load image (image.onerror): ".concat(a.path)))},r.src="string"==typeof a.data?a.data:h})];case 1:return[2,t.sent()]}})})}var Rt={cover:function(t,e){var t=t.h/t.w,n=t')},contain:function(t,e){var t=t.h/t.w,n=t')},crop:function(t,e){var n=e.x,r=t.w-(e.x+e.w),a=e.y,e=t.h-(e.y+e.h),n=Math.round(n/t.w*1e5),r=Math.round(r/t.w*1e5),a=Math.round(a/t.h*1e5),e=Math.round(e/t.h*1e5);return'')}};function Ot(T){var t,B=T._name?'':"",D=1;return T._bkgdImgRid?B+=''):null!=(t=T.background)&&t.color?B+="".concat(z(T.background),""):!T.bkgd&&T._name&&T._name===nt&&(B+=''),B=(B=B+""+'')+''+'',T._slideObjects.forEach(function(r,t){var e,n,A,a,o,i,s,l,c=0,u=0,p=N("75%","X",T._presLayout),f=0,d="",h=null,g=0,m=0,v=null,y=null==(e=r.options)?void 0:e.sizing,b=null==(e=r.options)?void 0:e.rounding,w=(void 0!==T._slideLayout&&void 0!==T._slideLayout._slideObjects&&r.options&&r.options.placeholder&&(n=T._slideLayout._slideObjects.filter(function(t){return t.options.placeholder===r.options.placeholder})[0]),r.options=r.options||{},void 0!==r.options.x&&(c=N(r.options.x,"X",T._presLayout)),void 0!==r.options.y&&(u=N(r.options.y,"Y",T._presLayout)),p=void 0!==r.options.w?N(r.options.w,"X",T._presLayout):p),x=f=void 0!==r.options.h?N(r.options.h,"Y",T._presLayout):f;switch(n&&(!n.options.x&&0!==n.options.x||(c=N(n.options.x,"X",T._presLayout)),!n.options.y&&0!==n.options.y||(u=N(n.options.y,"Y",T._presLayout)),!n.options.w&&0!==n.options.w||(p=N(n.options.w,"X",T._presLayout)),!n.options.h&&0!==n.options.h||(f=N(n.options.h,"Y",T._presLayout))),r.options.flipH&&(d+=' flipH="1"'),r.options.flipV&&(d+=' flipV="1"'),r.options.rotate&&(d+=' rot="'.concat(O(r.options.rotate),'"')),r._type){case _.table:if(h=r.arrTabRows,A=r.options,h[m=g=0].forEach(function(t){a=t.options||null,g+=null!==a&&a.colspan?Number(a.colspan):1}),v=''),v=(v+=' ')+'')+'',Array.isArray(A.colW)){v+="";for(var C=0;C')}}else{m=A.colW||k,r.options.w&&!A.colW&&(m=Math.round(("number"==typeof r.options.w?r.options.w:1)/g)),v+="";for(var S=0;S')}v+="",h.forEach(function(a){for(var o,i,t=0;t'),t.forEach(function(t){var e,n,r,a,o,i={rowSpan:1<(null==(s=t.options)?void 0:s.rowspan)?t.options.rowspan:void 0,gridSpan:1<(null==(s=t.options)?void 0:s.colspan)?t.options.colspan:void 0,vMerge:t._vmerge?1:void 0,hMerge:t._hmerge?1:void 0},s=(s=Object.keys(i).map(function(t){return[t,i[t]]}).filter(function(t){return t[0],!!t[1]}).map(function(t){var e=t[0],t=t[1];return"".concat(String(e),'="').concat(String(t),'"')}).join(" "))&&" "+s;t._hmerge||t._vmerge?v+=""):(e=t.options||{},t.options=e,["align","bold","border","color","fill","fontFace","fontSize","margin","underline","valign"].forEach(function(t){A[t]&&!e[t]&&0!==e[t]&&(e[t]=A[t])}),n=e.valign?' anchor="'.concat(e.valign.replace(/^c$/i,"ctr").replace(/^m$/i,"ctr").replace("center","ctr").replace("middle","ctr").replace("top","t").replace("btm","b").replace("bottom","b"),'"'):"",r=(r=(null!=(r=null==(r=t._optImp)?void 0:r.fill)&&r.color?t._optImp.fill.color:null!=(r=t._optImp)&&r.fill&&"string"==typeof t._optImp.fill?t._optImp.fill:"")||e.fill?e.fill:"")?z(r):"",a=0===e.margin||e.margin?e.margin:Z,o="",o=1<=(a=Array.isArray(a)||"number"!=typeof a?a:[a,a,a,a])[0]?' marL="'.concat(R(a[3]),'" marR="').concat(R(a[1]),'" marT="').concat(R(a[0]),'" marB="').concat(R(a[2]),'"'):' marL="'.concat(I(a[3]),'" marR="').concat(I(a[1]),'" marT="').concat(I(a[0]),'" marB="').concat(I(a[2]),'"'),v+="").concat(jt(t),""),e.border&&Array.isArray(e.border)&&[{idx:3,name:"lnL"},{idx:1,name:"lnR"},{idx:0,name:"lnT"},{idx:2,name:"lnB"}].forEach(function(t){"none"!==e.border[t.idx].type?v=(v=(v=(v+="'))+"".concat(M(e.border[t.idx].color),""))+''))+""):v+="")}),v=v+r+" ")}),v+=""}),B+=v=(v=v+" "+" ")+" "+"",D++;break;case _.text:case _.placeholder:if(r.options.line||0!==f||(f=.3*k),r.options._bodyProp||(r.options._bodyProp={}),r.options.margin&&Array.isArray(r.options.margin)?(r.options._bodyProp.lIns=R(r.options.margin[0]||0),r.options._bodyProp.rIns=R(r.options.margin[1]||0),r.options._bodyProp.bIns=R(r.options.margin[2]||0),r.options._bodyProp.tIns=R(r.options.margin[3]||0)):"number"==typeof r.options.margin&&(r.options._bodyProp.lIns=R(r.options.margin),r.options._bodyProp.rIns=R(r.options.margin),r.options._bodyProp.bIns=R(r.options.margin),r.options._bodyProp.tIns=R(r.options.margin)),B=(B+="")+''),null!=(o=r.options.hyperlink)&&o.url&&(B+='')),null!=(o=r.options.hyperlink)&&o.slide&&(B+='')),B=(B=(B=(B=(B=(B+="")+("':"/>")))+"".concat("placeholder"===r._type?Gt(r):Gt(n),"")+"")+""))+''))+''),"custGeom"===r.shape)B=(B+='')+''),null!=(o=r.options.points)&&o.forEach(function(t,e){if("curve"in t)switch(t.curve.type){case"arc":B+='');break;case"cubic":B+='\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t');break;case"quadratic":B+='\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t')}else"close"in t?B+="":t.moveTo||0===e?B+=''):B+='')}),B+="";else{if(B+='',r.options.rectRadius)B+='');else if(r.options.angleRange){for(var L=0;L<2;L++){var E=r.options.angleRange[L];B+='')}r.options.arcThicknessRatio&&(B+=''))}B+=""}B+=r.options.fill?z(r.options.fill):"",r.options.line&&(B+=r.options.line.width?''):"",r.options.line.color&&(B+=z(r.options.line)),r.options.line.dashType&&(B+='')),r.options.line.beginArrowType&&(B+='')),r.options.line.endArrowType&&(B+='')),B+=""),r.options.shadow&&"none"!==r.options.shadow.type&&(r.options.shadow.type=r.options.shadow.type||"outer",r.options.shadow.blur=R(r.options.shadow.blur||8),r.options.shadow.offset=R(r.options.shadow.offset||4),r.options.shadow.angle=Math.round(6e4*(r.options.shadow.angle||270)),r.options.shadow.opacity=Math.round(1e5*(r.options.shadow.opacity||.75)),r.options.shadow.color=r.options.shadow.color||ot.color,B=(B=(B=(B+="")+" '))+' '))+' ')+" "),B=(B+="")+jt(r)+"";break;case _.image:B=(B=B+""+" ")+''),null!=(o=r.hyperlink)&&o.url&&(B+='')),null!=(o=r.hyperlink)&&o.slide&&(B+='')),B=(B=(B=B+" "+' ')+(" "+Gt(n)+""))+" "+"",B=(T._relsMedia||[]).filter(function(t){return t.rId===r.imageRid})[0]&&"svg"===(T._relsMedia||[]).filter(function(t){return t.rId===r.imageRid})[0].extn?(B=(B+=''))+(r.options.transparency?' '):"")+' ')+' ')+" ":(B+=''))+(r.options.transparency?''):"")+"",null!=y&&y.type?(o=y.w?N(y.w,"X",T._presLayout):p,i=y.h?N(y.h,"Y",T._presLayout):f,s=N(y.x||0,"X",T._presLayout),l=N(y.y||0,"Y",T._presLayout),B+=Rt[y.type]({w:w,h:x},{w:o,h:i,x:s,y:l}),w=o,x=i):B+=" ",B=(B=(B=(B=(B+="")+""+(" "))+' '))+' ')+" ")+' '),r.options.shadow&&"none"!==r.options.shadow.type&&(r.options.shadow.type=r.options.shadow.type||"outer",r.options.shadow.blur=R(r.options.shadow.blur||8),r.options.shadow.offset=R(r.options.shadow.offset||4),r.options.shadow.angle=Math.round(6e4*(r.options.shadow.angle||270)),r.options.shadow.opacity=Math.round(1e5*(r.options.shadow.opacity||.75)),r.options.shadow.color=r.options.shadow.color||ot.color,B=(B=(B=(B=(B+="")+"'))+''))+''))+"")+""),B=B+""+"";break;case _.media:B="online"===r.mtype?(B=(B=(B=(B+=" ")+'')+" ")+' ')+" ")+' ')+" ")+" ')+' ':(B=(B=(B=(B=(B+=" ")+'')+' ')+' ')+' ')+' ')+" ")+' ')+" ")+" ')+' ';break;case _.chart:B=(B=(B=(B=(B=(B=(B=B+""+" ")+' ')+" ")+" ".concat(Gt(n),"")+" ")+' '))+' '+' ')+' ')+" ")+" "+"";break;default:B+=""}}),T._slideNumberProps&&(T._slideNumberProps.align||(T._slideNumberProps.align="left"),B=(B+=' ')+""+'')+'')+' '),T._slideNumberProps.color&&(B+=z(T._slideNumberProps.color)),T._slideNumberProps.fontFace&&(B+='')),B+=""),B+="",T._slideNumberProps.align.startsWith("l")?B+='':T._slideNumberProps.align.startsWith("c")?B+='':T._slideNumberProps.align.startsWith("r")?B+='':B+='',B=(B+=''))+"".concat(T._slideNum,'')+""),B=B+""+""}function Mt(t,e){var n=0,r=''+d+'';return t._rels.forEach(function(t){n=Math.max(n,t.rId),t.type.toLowerCase().includes("hyperlink")?"slide"===t.data?r+=''):r+=''):t.type.toLowerCase().includes("notesSlide")&&(r+=''))}),(t._relsChart||[]).forEach(function(t){n=Math.max(n,t.rId),r+='')}),(t._relsMedia||[]).forEach(function(t){var e=t.rId.toString();n=Math.max(n,t.rId),t.type.toLowerCase().includes("image")?r+='':t.type.toLowerCase().includes("audio")?r.includes(' Target="'+t.Target+'"')?r+='':r+='':t.type.toLowerCase().includes("video")?r.includes(' Target="'+t.Target+'"')?r+='':r+='':t.type.toLowerCase().includes("online")&&(r.includes(' Target="'+t.Target+'"')?r+='':r+='')}),e.forEach(function(t,e){r+='')}),r+=""}function zt(t,e){var n,r,a="",o="",i="",s="",A=e?"a:lvl1pPr":"a:pPr",l=R(V),c="<".concat(A).concat(t.options.rtlMode?' rtl="1" ':"");if(t.options.align)switch(t.options.align){case"left":c+=' algn="l"';break;case"right":c+=' algn="r"';break;case"center":c+=' algn="ctr"';break;case"justify":c+=' algn="just"';break;default:c+=""}return t.options.lineSpacing?o=''):t.options.lineSpacingMultiple&&(o='')),t.options.indentLevel&&!isNaN(Number(t.options.indentLevel))&&0')),t.options.paraSpaceAfter&&!isNaN(Number(t.options.paraSpaceAfter))&&0')),"object"==typeof t.options.bullet?(null!=(r=null==(r=null==t?void 0:t.options)?void 0:r.bullet)&&r.indent&&(l=R(t.options.bullet.indent)),t.options.bullet.type?"number"===t.options.bullet.type.toString().toLowerCase()&&(c+=' marL="'.concat(t.options.indentLevel&&0')):a=t.options.bullet.characterCode?(n="&#x".concat(t.options.bullet.characterCode,";"),/^[0-9A-Fa-f]{4}$/.test(t.options.bullet.characterCode)||(console.warn("Warning: `bullet.characterCode should be a 4-digit unicode charatcer (ex: 22AB)`!"),n=f.DEFAULT),c+=' marL="'.concat(t.options.indentLevel&&0'):t.options.bullet.code?(n="&#x".concat(t.options.bullet.code,";"),/^[0-9A-Fa-f]{4}$/.test(t.options.bullet.code)||(console.warn("Warning: `bullet.code should be a 4-digit hex code (ex: 22AB)`!"),n=f.DEFAULT),c+=' marL="'.concat(t.options.indentLevel&&0'):(c+=' marL="'.concat(t.options.indentLevel&&0'))):t.options.bullet?(c+=' marL="'.concat(t.options.indentLevel&&0')):t.options.bullet||(c+=' indent="0" marL="0"',a=""),t.options.tabStops&&Array.isArray(t.options.tabStops)&&(r=t.options.tabStops.map(function(t){return'')}).join(""),s="".concat(r,"")),c+=">"+o+i+a+s,e&&(c+=Ut(t.options,!0)),c+=""}function Ut(t,e){var n,r,a,o,i="",e=e?"a:defRPr":"a:rPr",i=(i=(i=(i=(i+="<"+e+' lang="'+(t.lang||"en-US")+'"'+(t.lang?' altLang="en-US"':""))+(t.fontSize?' sz="'.concat(Math.round(100*t.fontSize),'"'):""))+(null!=t&&t.bold?' b="'.concat(t.bold?"1":"0",'"'):""))+(null!=t&&t.italic?' i="'.concat(t.italic?"1":"0",'"'):""))+(null!=t&&t.strike?' strike="'.concat("string"==typeof t.strike?t.strike:"sngStrike",'"'):"");if("object"==typeof t.underline&&null!=(n=t.underline)&&n.style?i+=' u="'.concat(t.underline.style,'"'):"string"==typeof t.underline?i+=' u="'.concat(String(t.underline),'"'):t.hyperlink&&(i+=' u="sng"'),t.baseline?i+=' baseline="'.concat(Math.round(50*t.baseline),'"'):t.subscript?i+=' baseline="-40000"':t.superscript&&(i+=' baseline="30000"'),i=i+(t.charSpacing?' spc="'.concat(Math.round(100*t.charSpacing),'" kern="0"'):"")+' dirty="0">',(t.color||t.fontFace||t.outline||"object"==typeof t.underline&&t.underline.color)&&(t.outline&&"object"==typeof t.outline&&(i+='').concat(z(t.outline.color||"FFFFFF"),"")),t.color&&(i+=z({color:t.color,transparency:t.transparency})),t.highlight&&(i+="".concat(M(t.highlight),"")),"object"==typeof t.underline&&t.underline.color&&(i+="".concat(z(t.underline.color),"")),t.glow&&(i+="".concat((n=t.glow,a="",r=y(y({},r=it),n),n=Math.round(r.size*w),o=r.color,r=Math.round(1e5*r.opacity),(a+=''))+M(o,''))+""),"")),t.fontFace)&&(i+='')),t.hyperlink){if("object"!=typeof t.hyperlink)throw new Error("ERROR: text `hyperlink` option should be an object. Ex: `hyperlink:{url:'https://github.com'}` ");if(!t.hyperlink.url&&!t.hyperlink.slide)throw new Error("ERROR: 'hyperlink requires either `url` or `slide`'");t.hyperlink.url?i+='":"/>"):t.hyperlink.slide&&(i+='":"/>")),t.color&&(i+=' ')}return i+="")}function jt(n){var o,t,e,r,a,i=n.options||{},s=[],A=[];return!i||n._type===_.tablecell||void 0!==n.text&&null!==n.text?(o=n._type===_.tablecell?"":"",o+=(e="":"resize"===t.options.fit&&(e+="")),t.options.shrinkText&&(e+=""),e=e+(t.options._bodyProp.autoFit?"":"")+""):e+=' wrap="square" rtlCol="0">',t._type===_.tablecell?"":e),0===i.h&&i.line&&i.align?o+='':"placeholder"===n._type?o+="".concat(zt(n,!0),""):o+="","string"==typeof n.text||"number"==typeof n.text?s.push({text:n.text.toString(),options:i||{}}):n.text&&!Array.isArray(n.text)&&"object"==typeof n.text&&Object.keys(n.text).includes("text")?s.push({text:n.text||"",options:n.options||{}}):Array.isArray(n.text)&&(s=n.text.map(function(t){return{text:t.text,options:t.options}})),s.forEach(function(e,t){e.text||(e.text=""),e.options=e.options||i||{},0===t&&e.options&&!e.options.bullet&&i.bullet&&(e.options.bullet=i.bullet),"string"!=typeof e.text&&"number"!=typeof e.text||(e.text=e.text.toString().replace(/\r*\n/g,d)),e.text.includes(d)&&null===e.text.match(/\n$/g)?e.text.split(d).forEach(function(t){e.options.breakLine=!0,A.push({text:t,options:e.options})}):A.push(e)}),r=[],a=[],A.forEach(function(t,e){0",""),n.options.align=n.options.align||i.align,n.options.lineSpacing=n.options.lineSpacing||i.lineSpacing,n.options.lineSpacingMultiple=n.options.lineSpacingMultiple||i.lineSpacingMultiple,n.options.indentLevel=n.options.indentLevel||i.indentLevel,n.options.paraSpaceBefore=n.options.paraSpaceBefore||i.paraSpaceBefore,n.options.paraSpaceAfter=n.options.paraSpaceAfter||i.paraSpaceAfter,a=zt(n,!1),o+=a.replace("",""),Object.entries(i).filter(function(t){var e=t[0];return t[1],!(n.options.hyperlink&&"color"===e)}).forEach(function(t){var e=t[0],t=t[1];"bullet"===e||n.options[e]||(n.options[e]=t)}),o+=(t=n).text?"".concat(Ut(t.options,!1),"").concat(F(t.text),""):"",(!n.text&&i.fontSize||n.options.fontSize)&&(r=!0,i.fontSize=i.fontSize||n.options.fontSize)}),n._type===_.tablecell&&(i.fontSize||i.fontFace)?i.fontFace?o=(o=(o=(o+='')+''))+''))+'')+"":o+='':o+=r?'':''),o+=""}),o+=n._type===_.tablecell?"":""):""}function Gt(t){var e,n;return t?(e=null!=(e=t.options)&&e._placeholderIdx?t.options._placeholderIdx:"",n=(n=null!=(n=t.options)&&n._placeholderType?t.options._placeholderType:"")&&a[n]?a[n].toString():"","")):""}function Qt(t){return''.concat(d,'').concat(F((e="",t._slideObjects.forEach(function(t){t._type===_.notes&&(e+=null!=t&&t.text&&t.text[0]?t.text[0].text:"")}),e.replace(/\r*\n/g,d))),'').concat(t._slideNum,'');var e}function Wt(t,e,n){return Mt(t[n-1],[{target:"../slideLayouts/slideLayout".concat(function(t,e,n){for(var r=0;r'+d)+'')+'')+'')+'')+'',r.forEach(function(t){(t._relsMedia||[]).forEach(function(t){"image"===t.type||"online"===t.type||"chart"===t.type||"m4v"===t.extn||i.includes(t.type)||(i+='')})}),i=(i+='')+'',r.forEach(function(t,e){i=(i+=''))+''),t._relsChart.forEach(function(t){i+='')})}),i=(i+='')+'',a.forEach(function(t,e){i+=''),(t._relsChart||[]).forEach(function(t){i+=' '})}),r.forEach(function(t,e){i+='')}),o._relsChart.forEach(function(t){i+=' '}),o._relsMedia.forEach(function(t){"image"===t.type||"online"===t.type||"chart"===t.type||"m4v"===t.extn||i.includes(t.type)||(i+=' ')}),i=(i+=' ')+' ')),l.file("_rels/.rels",''.concat(d,'\n\t\t\n\t\t\n\t\t\n\t\t')),l.file("docProps/app.xml",(a=this.slides,r=this.company,''.concat(d,'\n\t0\n\t0\n\tMicrosoft Office PowerPoint\n\tOn-screen Show (16:9)\n\t0\n\t').concat(a.length,"\n\t").concat(a.length,'\n\t0\n\t0\n\tfalse\n\t\n\t\t\n\t\t\tFonts Used\n\t\t\t2\n\t\t\tTheme\n\t\t\t1\n\t\t\tSlide Titles\n\t\t\t').concat(a.length,'\n\t\t\n\t\n\t\n\t\t\n\t\t\tArial\n\t\t\tCalibri\n\t\t\tOffice Theme\n\t\t\t').concat(a.map(function(t,e){return"Slide ".concat(e+1,"")}).join(""),"\n\t\t\n\t\n\t").concat(r,"\n\tfalse\n\tfalse\n\tfalse\n\t16.0000\n\t"))),l.file("docProps/core.xml",(o=this.title,a=this.subject,r=this.author,e=this.revision,'\n\t\n\t\t'.concat(F(o),"\n\t\t").concat(F(a),"\n\t\t").concat(F(r),"\n\t\t").concat(F(r),"\n\t\t").concat(e,'\n\t\t').concat((new Date).toISOString().replace(/\.\d\d\dZ/,"Z"),'\n\t\t').concat((new Date).toISOString().replace(/\.\d\d\dZ/,"Z"),"\n\t"))),l.file("ppt/_rels/presentation.xml.rels",function(t){for(var e=1,n=(n=''+d)+''+'',r=1;r<=t.length;r++)n+='');return n+='')+'')+'')+'')+'')+""}(this.slides)),l.file("ppt/theme/theme1.xml",(a=null!=(a=(o=this).theme)&&a.headFontFace?''):'',o=null!=(r=o.theme)&&r.bodyFontFace?''):'',''.concat(a,'').concat(o,''))),l.file("ppt/presentation.xml",function(t){var e=(e=''.concat(d)+''))+''+"";t.slides.forEach(function(t){return e+='')}),e=(e=(e=(e+="")+''))+''))+'')+"";for(var n=1;n<10;n++)e+="')+''+"");return e+="",t.sections&&0',t.sections.forEach(function(t){e+=''),t._slides.forEach(function(t){return e+='')}),e+=""}),e+=''),e+=""}(this)),l.file("ppt/presProps.xml",''.concat(d,'')),l.file("ppt/tableStyles.xml",''.concat(d,'')),l.file("ppt/viewProps.xml",''.concat(d,'')),this.slideLayouts.forEach(function(t,e){l.file("ppt/slideLayouts/slideLayout".concat(e+1,".xml"),'\n\t\t\n\t\t'.concat(Ot(t),"\n\t\t")),l.file("ppt/slideLayouts/_rels/slideLayout".concat(e+1,".xml.rels"),(t=e+1,Mt(s.slideLayouts[t-1],[{target:"../slideMasters/slideMaster1.xml",type:"http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideMaster"}])))}),this.slides.forEach(function(t,e){var n;l.file("ppt/slides/slide".concat(e+1,".xml"),(n=t,''.concat(d)+'")+"".concat(Ot(n))+"")),l.file("ppt/slides/_rels/slide".concat(e+1,".xml.rels"),Wt(s.slides,s.slideLayouts,e+1)),l.file("ppt/notesSlides/notesSlide".concat(e+1,".xml"),Qt(t)),l.file("ppt/notesSlides/_rels/notesSlide".concat(e+1,".xml.rels"),'\n\t\t\n\t\t\t\n\t\t\t\n\t\t'))}),l.file("ppt/slideMasters/slideMaster1.xml",(n=this.masterSlide,e=(e=this.slideLayouts).map(function(t,e){return'')}),r=''+d,(r+='')+Ot(n)+''+e.join("")+' ')),l.file("ppt/slideMasters/_rels/slideMaster1.xml.rels",(a=this.masterSlide,(o=(o=this.slideLayouts).map(function(t,e){return{target:"../slideLayouts/slideLayout".concat(e+1,".xml"),type:"http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout"}})).push({target:"../theme/theme1.xml",type:"http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme"}),Mt(a,o))),l.file("ppt/notesMasters/notesMaster1.xml",''.concat(d,'7/23/19Click to edit Master text stylesSecond levelThird levelFourth levelFifth level‹#›')),l.file("ppt/notesMasters/_rels/notesMaster1.xml.rels",''.concat(d,'\n\t\t\n\t\t')),this.slideLayouts.forEach(function(t){s.createChartMediaRels(t,l,A)}),this.slides.forEach(function(t){s.createChartMediaRels(t,l,A)}),this.createChartMediaRels(this.masterSlide,l,A),[4,Promise.all(A).then(function(){return u(s,void 0,void 0,function(){return p(this,function(t){switch(t.label){case 0:return"STREAM"!==c.outputType?[3,2]:[4,l.generateAsync({type:"nodebuffer",compression:c.compression?"DEFLATE":"STORE"})];case 1:return[2,t.sent()];case 2:return c.outputType?[4,l.generateAsync({type:c.outputType})]:[3,4];case 3:return[2,t.sent()];case 4:return[4,l.generateAsync({type:"blob",compression:c.compression?"DEFLATE":"STORE"})];case 5:return[2,t.sent()]}})})})];case 1:return[2,t.sent()]}var n,e,r,a,o,i})})})];case 1:return[2,t.sent()]}})})};this.LAYOUTS={LAYOUT_4x3:{name:"screen4x3",width:9144e3,height:6858e3},LAYOUT_16x9:{name:"screen16x9",width:9144e3,height:5143500},LAYOUT_16x10:{name:"screen16x10",width:9144e3,height:5715e3},LAYOUT_WIDE:{name:"custom",width:12192e3,height:6858e3}},this._author="PptxGenJS",this._company="PptxGenJS",this._revision="1",this._subject="PptxGenJS Presentation",this._title="PptxGenJS Presentation",this._presLayout={name:this.LAYOUTS[l].name,_sizeW:this.LAYOUTS[l].width,_sizeH:this.LAYOUTS[l].height,width:this.LAYOUTS[l].width,height:this.LAYOUTS[l].height},this._rtlMode=!1,this._slideLayouts=[{_margin:at,_name:nt,_presLayout:this._presLayout,_rels:[],_relsChart:[],_relsMedia:[],_slide:null,_slideNum:1e3,_slideNumberProps:null,_slideObjects:[]}],this._slides=[],this._sections=[],this._masterSlide={addChart:null,addImage:null,addMedia:null,addNotes:null,addShape:null,addTable:null,addText:null,_name:null,_presLayout:this._presLayout,_rId:null,_rels:[],_relsChart:[],_relsMedia:[],_slideId:null,_slideLayout:null,_slideNum:null,_slideNumberProps:null,_slideObjects:[]}}return Object.defineProperty(n.prototype,"layout",{get:function(){return this._layout},set:function(t){var e=this.LAYOUTS[t];if(!e)throw new Error("UNKNOWN-LAYOUT");this._layout=t,this._presLayout=e},enumerable:!1,configurable:!0}),Object.defineProperty(n.prototype,"version",{get:function(){return this._version},enumerable:!1,configurable:!0}),Object.defineProperty(n.prototype,"author",{get:function(){return this._author},set:function(t){this._author=t},enumerable:!1,configurable:!0}),Object.defineProperty(n.prototype,"company",{get:function(){return this._company},set:function(t){this._company=t},enumerable:!1,configurable:!0}),Object.defineProperty(n.prototype,"revision",{get:function(){return this._revision},set:function(t){this._revision=t},enumerable:!1,configurable:!0}),Object.defineProperty(n.prototype,"subject",{get:function(){return this._subject},set:function(t){this._subject=t},enumerable:!1,configurable:!0}),Object.defineProperty(n.prototype,"theme",{get:function(){return this._theme},set:function(t){this._theme=t},enumerable:!1,configurable:!0}),Object.defineProperty(n.prototype,"title",{get:function(){return this._title},set:function(t){this._title=t},enumerable:!1,configurable:!0}),Object.defineProperty(n.prototype,"rtlMode",{get:function(){return this._rtlMode},set:function(t){this._rtlMode=t},enumerable:!1,configurable:!0}),Object.defineProperty(n.prototype,"masterSlide",{get:function(){return this._masterSlide},enumerable:!1,configurable:!0}),Object.defineProperty(n.prototype,"slides",{get:function(){return this._slides},enumerable:!1,configurable:!0}),Object.defineProperty(n.prototype,"sections",{get:function(){return this._sections},enumerable:!1,configurable:!0}),Object.defineProperty(n.prototype,"slideLayouts",{get:function(){return this._slideLayouts},enumerable:!1,configurable:!0}),Object.defineProperty(n.prototype,"AlignH",{get:function(){return this._alignH},enumerable:!1,configurable:!0}),Object.defineProperty(n.prototype,"AlignV",{get:function(){return this._alignV},enumerable:!1,configurable:!0}),Object.defineProperty(n.prototype,"ChartType",{get:function(){return this._chartType},enumerable:!1,configurable:!0}),Object.defineProperty(n.prototype,"OutputType",{get:function(){return this._outputType},enumerable:!1,configurable:!0}),Object.defineProperty(n.prototype,"presLayout",{get:function(){return this._presLayout},enumerable:!1,configurable:!0}),Object.defineProperty(n.prototype,"SchemeColor",{get:function(){return this._schemeColor},enumerable:!1,configurable:!0}),Object.defineProperty(n.prototype,"ShapeType",{get:function(){return this._shapeType},enumerable:!1,configurable:!0}),Object.defineProperty(n.prototype,"charts",{get:function(){return this._charts},enumerable:!1,configurable:!0}),Object.defineProperty(n.prototype,"colors",{get:function(){return this._colors},enumerable:!1,configurable:!0}),Object.defineProperty(n.prototype,"shapes",{get:function(){return this._shapes},enumerable:!1,configurable:!0}),n.prototype.stream=function(e){return u(this,void 0,void 0,function(){return p(this,function(t){switch(t.label){case 0:return[4,this.exportPresentation({compression:null==e?void 0:e.compression,outputType:"STREAM"})];case 1:return[2,t.sent()]}})})},n.prototype.write=function(r){return u(this,void 0,void 0,function(){var e,n;return p(this,function(t){switch(t.label){case 0:return e="object"==typeof r&&null!=r&&r.outputType?r.outputType:r||null,n=!("object"!=typeof r||null==r||!r.compression)&&r.compression,[4,this.exportPresentation({compression:n,outputType:e})];case 1:return[2,t.sent()]}})})},n.prototype.writeFile=function(r){return u(this,void 0,void 0,function(){var a,e,n,o,i=this;return p(this,function(t){switch(t.label){case 0:return a="undefined"!=typeof require&&"undefined"==typeof window?require("fs"):null,"string"==typeof r&&console.log("Warning: `writeFile(filename)` is deprecated - please use `WriteFileProps` argument (v3.5.0)"),e="object"==typeof r&&null!=r&&r.fileName?r.fileName:"string"==typeof r?r:"",n=!("object"!=typeof r||null==r||!r.compression)&&r.compression,o=e?e.toString().toLowerCase().endsWith(".pptx")?e:e+".pptx":"Presentation.pptx",[4,this.exportPresentation({compression:n,outputType:a?"nodebuffer":null}).then(function(r){return u(i,void 0,void 0,function(){return p(this,function(t){switch(t.label){case 0:return a?[4,new Promise(function(e,n){a.writeFile(o,r,function(t){t?n(t):e(o)})})]:[3,2];case 1:return[2,t.sent()];case 2:return[4,this.writeFileToBrowser(o,r)];case 3:return[2,t.sent()]}})})})];case 1:return[2,t.sent()]}})})},n.prototype.addSection=function(t){t?t.title||console.warn("addSection requires a title"):console.warn("addSection requires an argument");var e={_type:"user",_slides:[],title:t.title};t.order?this.sections.splice(t.order,0,e):this._sections.push(e)},n.prototype.addSlide=function(e){var n="string"==typeof e?e:null!=e&&e.masterName?e.masterName:"",t={_name:this.LAYOUTS[l].name,_presLayout:this.presLayout,_rels:[],_relsChart:[],_relsMedia:[],_slideNum:this.slides.length+1},r=(n&&(r=this.slideLayouts.filter(function(t){return t._name===n})[0])&&(t=r),new Lt({addSlide:this.addNewSlide,getSlide:this.getSlide,presLayout:this.presLayout,setSlideNum:this.setSlideNumber,slideId:this.slides.length+256,slideRId:this.slides.length+2,slideNumber:this.slides.length+1,slideLayout:t}));return this._slides.push(r),null!=e&&e.sectionTitle?(t=this.sections.filter(function(t){return t.title===e.sectionTitle})[0])?t._slides.push(r):console.warn('addSlide: unable to find section with title: "'.concat(e.sectionTitle,'"')):this.sections&&0 opts.y = ").concat(i.y)),n.addTable(t.rows,{x:i.x||p[3],y:i.y,w:Number(a)/k,colW:c,autoPage:!1}),i.addImage&&(i.addImage.options=i.addImage.options||{},i.addImage.image&&(i.addImage.image.path||i.addImage.image.data)?n.addImage({path:i.addImage.image.path,data:i.addImage.image.data,x:i.addImage.options.x,y:i.addImage.options.y,w:i.addImage.options.w,h:i.addImage.options.h}):console.warn("Warning: tableToSlides.addImage requires either `path` or `data`")),i.addShape&&n.addShape(i.addShape.shapeName,i.addShape.options||{}),i.addTable&&n.addTable(i.addTable.rows,i.addTable.options||{}),i.addText&&n.addText(i.addText.text,i.addText.options||{})})},n}(); +//# sourceMappingURL=pptxgen.bundle.js.map diff --git a/services/slides/node_modules/pptxgenjs/dist/pptxgen.bundle.js.map b/services/slides/node_modules/pptxgenjs/dist/pptxgen.bundle.js.map new file mode 100644 index 0000000000000000000000000000000000000000..42ce838a9cac3971e36fd30e915ce2d69b28a301 --- /dev/null +++ b/services/slides/node_modules/pptxgenjs/dist/pptxgen.bundle.js.map @@ -0,0 +1 @@ +{"version":3,"names":[],"mappings":"","sources":["pptxgen.bundle.js"],"sourcesContent":["/* PptxGenJS 3.12.0 @ 2023-03-20T03:12:31.375Z */\n!function(t){\"object\"==typeof exports&&\"undefined\"!=typeof module?module.exports=t():\"function\"==typeof define&&define.amd?define([],t):(\"undefined\"!=typeof window?window:\"undefined\"!=typeof global?global:\"undefined\"!=typeof self?self:this).JSZip=t()}(function(){return function r(a,o,i){function s(e,t){if(!o[e]){if(!a[e]){var n=\"function\"==typeof require&&require;if(!t&&n)return n(e,!0);if(A)return A(e,!0);t=new Error(\"Cannot find module '\"+e+\"'\");throw t.code=\"MODULE_NOT_FOUND\",t}n=o[e]={exports:{}};a[e][0].call(n.exports,function(t){return s(a[e][1][t]||t)},n,n.exports,r,a,o,i)}return o[e].exports}for(var A=\"function\"==typeof require&&require,t=0;t>4,o=1>6:64,i=2>2)+p.charAt(a)+p.charAt(o)+p.charAt(i));return s.join(\"\")},n.decode=function(t){var e,n,r,a,o,i=0,s=0;if(\"data:\"===t.substr(0,\"data:\".length))throw new Error(\"Invalid base64 input, it looks like a data url.\");var A,l=3*(t=t.replace(/[^A-Za-z0-9\\+\\/\\=]/g,\"\")).length/4;if(t.charAt(t.length-1)===p.charAt(64)&&l--,t.charAt(t.length-2)===p.charAt(64)&&l--,l%1!=0)throw new Error(\"Invalid base64 input, bad content length.\");for(A=new(c.uint8array?Uint8Array:Array)(0|l);i>4,n=(15&a)<<4|(a=p.indexOf(t.charAt(i++)))>>2,r=(3&a)<<6|(o=p.indexOf(t.charAt(i++))),A[s++]=e,64!==a&&(A[s++]=n),64!==o&&(A[s++]=r);return A}},{\"./support\":30,\"./utils\":32}],2:[function(t,e,n){\"use strict\";var r=t(\"./external\"),a=t(\"./stream/DataWorker\"),o=t(\"./stream/Crc32Probe\"),i=t(\"./stream/DataLengthProbe\");function s(t,e,n,r,a){this.compressedSize=t,this.uncompressedSize=e,this.crc32=n,this.compression=r,this.compressedContent=a}s.prototype={getContentWorker:function(){var t=new a(r.Promise.resolve(this.compressedContent)).pipe(this.compression.uncompressWorker()).pipe(new i(\"data_length\")),e=this;return t.on(\"end\",function(){if(this.streamInfo.data_length!==e.uncompressedSize)throw new Error(\"Bug : uncompressed data size mismatch\")}),t},getCompressedWorker:function(){return new a(r.Promise.resolve(this.compressedContent)).withStreamInfo(\"compressedSize\",this.compressedSize).withStreamInfo(\"uncompressedSize\",this.uncompressedSize).withStreamInfo(\"crc32\",this.crc32).withStreamInfo(\"compression\",this.compression)}},s.createWorkerFrom=function(t,e,n){return t.pipe(new o).pipe(new i(\"uncompressedSize\")).pipe(e.compressWorker(n)).pipe(new i(\"compressedSize\")).withStreamInfo(\"compression\",e)},e.exports=s},{\"./external\":6,\"./stream/Crc32Probe\":25,\"./stream/DataLengthProbe\":26,\"./stream/DataWorker\":27}],3:[function(t,e,n){\"use strict\";var r=t(\"./stream/GenericWorker\");n.STORE={magic:\"\\0\\0\",compressWorker:function(t){return new r(\"STORE compression\")},uncompressWorker:function(){return new r(\"STORE decompression\")}},n.DEFLATE=t(\"./flate\")},{\"./flate\":7,\"./stream/GenericWorker\":28}],4:[function(t,e,n){\"use strict\";var r=t(\"./utils\"),i=function(){for(var t=[],e=0;e<256;e++){for(var n=e,r=0;r<8;r++)n=1&n?3988292384^n>>>1:n>>>1;t[e]=n}return t}();e.exports=function(t,e){return void 0!==t&&t.length?(\"string\"!==r.getTypeOf(t)?function(t,e,n){var r=i,a=0+n;t^=-1;for(var o=0;o>>8^r[255&(t^e[o])];return-1^t}:function(t,e,n){var r=i,a=0+n;t^=-1;for(var o=0;o>>8^r[255&(t^e.charCodeAt(o))];return-1^t})(0|e,t,t.length):0}},{\"./utils\":32}],5:[function(t,e,n){\"use strict\";n.base64=!1,n.binary=!1,n.dir=!1,n.createFolders=!0,n.date=null,n.compression=null,n.compressionOptions=null,n.comment=null,n.unixPermissions=null,n.dosPermissions=null},{}],6:[function(t,e,n){\"use strict\";t=\"undefined\"!=typeof Promise?Promise:t(\"lie\");e.exports={Promise:t}},{lie:37}],7:[function(t,e,n){\"use strict\";var r=\"undefined\"!=typeof Uint8Array&&\"undefined\"!=typeof Uint16Array&&\"undefined\"!=typeof Uint32Array,a=t(\"pako\"),o=t(\"./utils\"),i=t(\"./stream/GenericWorker\"),s=r?\"uint8array\":\"array\";function A(t,e){i.call(this,\"FlateWorker/\"+t),this._pako=null,this._pakoAction=t,this._pakoOptions=e,this.meta={}}n.magic=\"\\b\\0\",o.inherits(A,i),A.prototype.processChunk=function(t){this.meta=t.meta,null===this._pako&&this._createPako(),this._pako.push(o.transformTo(s,t.data),!1)},A.prototype.flush=function(){i.prototype.flush.call(this),null===this._pako&&this._createPako(),this._pako.push([],!0)},A.prototype.cleanUp=function(){i.prototype.cleanUp.call(this),this._pako=null},A.prototype._createPako=function(){this._pako=new a[this._pakoAction]({raw:!0,level:this._pakoOptions.level||-1});var e=this;this._pako.onData=function(t){e.push({data:t,meta:e.meta})}},n.compressWorker=function(t){return new A(\"Deflate\",t)},n.uncompressWorker=function(){return new A(\"Inflate\",{})}},{\"./stream/GenericWorker\":28,\"./utils\":32,pako:38}],8:[function(t,e,n){\"use strict\";function v(t,e){for(var n=\"\",r=0;r>>=8;return n}function r(t,e,n,r,a,o){var i=t.file,s=t.compression,A=o!==b.utf8encode,l=y.transformTo(\"string\",o(i.name)),c=y.transformTo(\"string\",b.utf8encode(i.name)),u=i.comment,o=y.transformTo(\"string\",o(u)),p=y.transformTo(\"string\",b.utf8encode(u)),f=c.length!==i.name.length,u=p.length!==u.length,d=\"\",h=i.dir,g=i.date,m={crc32:0,compressedSize:0,uncompressedSize:0},n=(e&&!n||(m.crc32=t.crc32,m.compressedSize=t.compressedSize,m.uncompressedSize=t.uncompressedSize),0);e&&(n|=8),A||!f&&!u||(n|=2048);t=0,e=0,h&&(t|=16),\"UNIX\"===a?(e=798,t|=(65535&(i.unixPermissions||(h?16893:33204)))<<16):(e=20,t|=63&(i.dosPermissions||0)),A=g.getUTCHours(),A=(A=((A<<=6)|g.getUTCMinutes())<<5)|g.getUTCSeconds()/2,a=g.getUTCFullYear()-1980,a=(a=((a<<=4)|g.getUTCMonth()+1)<<5)|g.getUTCDate(),f&&(d+=\"up\"+v((h=v(1,1)+v(w(l),4)+c).length,2)+h),u&&(d+=\"uc\"+v((i=v(1,1)+v(w(o),4)+p).length,2)+i),g=\"\",g=(g=(g=(g=(g=(g=(g=(g=(g=(g+=\"\\n\\0\")+v(n,2))+s.magic)+v(A,2))+v(a,2))+v(m.crc32,4))+v(m.compressedSize,4))+v(m.uncompressedSize,4))+v(l.length,2))+v(d.length,2);return{fileRecord:x.LOCAL_FILE_HEADER+g+l+d,dirRecord:x.CENTRAL_FILE_HEADER+v(e,2)+g+v(o.length,2)+\"\\0\\0\\0\\0\"+v(t,4)+v(r,4)+l+d+o}}var y=t(\"../utils\"),a=t(\"../stream/GenericWorker\"),b=t(\"../utf8\"),w=t(\"../crc32\"),x=t(\"../signature\");function o(t,e,n,r){a.call(this,\"ZipFileWorker\"),this.bytesWritten=0,this.zipComment=e,this.zipPlatform=n,this.encodeFileName=r,this.streamFiles=t,this.accumulate=!1,this.contentBuffer=[],this.dirRecords=[],this.currentSourceOffset=0,this.entriesCount=0,this.currentFile=null,this._sources=[]}y.inherits(o,a),o.prototype.push=function(t){var e=t.meta.percent||0,n=this.entriesCount,r=this._sources.length;this.accumulate?this.contentBuffer.push(t):(this.bytesWritten+=t.data.length,a.prototype.push.call(this,{data:t.data,meta:{currentFile:this.currentFile,percent:n?(e+100*(n-r-1))/n:100}}))},o.prototype.openedSource=function(t){this.currentSourceOffset=this.bytesWritten,this.currentFile=t.file.name;var e=this.streamFiles&&!t.file.dir;e?(t=r(t,e,!1,this.currentSourceOffset,this.zipPlatform,this.encodeFileName),this.push({data:t.fileRecord,meta:{percent:0}})):this.accumulate=!0},o.prototype.closedSource=function(t){this.accumulate=!1;var e=this.streamFiles&&!t.file.dir,n=r(t,e,!0,this.currentSourceOffset,this.zipPlatform,this.encodeFileName);if(this.dirRecords.push(n.dirRecord),e)this.push({data:x.DATA_DESCRIPTOR+v((e=t).crc32,4)+v(e.compressedSize,4)+v(e.uncompressedSize,4),meta:{percent:100}});else for(this.push({data:n.fileRecord,meta:{percent:0}});this.contentBuffer.length;)this.push(this.contentBuffer.shift());this.currentFile=null},o.prototype.flush=function(){for(var t=this.bytesWritten,e=0;e=this.index;e--)n=(n<<8)+this.byteAt(e);return this.index+=t,n},readString:function(t){return r.transformTo(\"string\",this.readData(t))},readData:function(t){},lastIndexOfSignature:function(t){},readAndCheckSignature:function(t){},readDate:function(){var t=this.readInt(4);return new Date(Date.UTC(1980+(t>>25&127),(t>>21&15)-1,t>>16&31,t>>11&31,t>>5&63,(31&t)<<1))}},e.exports=a},{\"../utils\":32}],19:[function(t,e,n){\"use strict\";var r=t(\"./Uint8ArrayReader\");function a(t){r.call(this,t)}t(\"../utils\").inherits(a,r),a.prototype.readData=function(t){this.checkOffset(t);var e=this.data.slice(this.zero+this.index,this.zero+this.index+t);return this.index+=t,e},e.exports=a},{\"../utils\":32,\"./Uint8ArrayReader\":21}],20:[function(t,e,n){\"use strict\";var r=t(\"./DataReader\");function a(t){r.call(this,t)}t(\"../utils\").inherits(a,r),a.prototype.byteAt=function(t){return this.data.charCodeAt(this.zero+t)},a.prototype.lastIndexOfSignature=function(t){return this.data.lastIndexOf(t)-this.zero},a.prototype.readAndCheckSignature=function(t){return t===this.readData(4)},a.prototype.readData=function(t){this.checkOffset(t);var e=this.data.slice(this.zero+this.index,this.zero+this.index+t);return this.index+=t,e},e.exports=a},{\"../utils\":32,\"./DataReader\":18}],21:[function(t,e,n){\"use strict\";var r=t(\"./ArrayReader\");function a(t){r.call(this,t)}t(\"../utils\").inherits(a,r),a.prototype.readData=function(t){var e;return this.checkOffset(t),0===t?new Uint8Array(0):(e=this.data.subarray(this.zero+this.index,this.zero+this.index+t),this.index+=t,e)},e.exports=a},{\"../utils\":32,\"./ArrayReader\":17}],22:[function(t,e,n){\"use strict\";var r=t(\"../utils\"),a=t(\"../support\"),o=t(\"./ArrayReader\"),i=t(\"./StringReader\"),s=t(\"./NodeBufferReader\"),A=t(\"./Uint8ArrayReader\");e.exports=function(t){var e=r.getTypeOf(t);return r.checkSupport(e),\"string\"!==e||a.uint8array?\"nodebuffer\"===e?new s(t):a.uint8array?new A(r.transformTo(\"uint8array\",t)):new o(r.transformTo(\"array\",t)):new i(t)}},{\"../support\":30,\"../utils\":32,\"./ArrayReader\":17,\"./NodeBufferReader\":19,\"./StringReader\":20,\"./Uint8ArrayReader\":21}],23:[function(t,e,n){\"use strict\";n.LOCAL_FILE_HEADER=\"PK\u0003\u0004\",n.CENTRAL_FILE_HEADER=\"PK\u0001\u0002\",n.CENTRAL_DIRECTORY_END=\"PK\u0005\u0006\",n.ZIP64_CENTRAL_DIRECTORY_LOCATOR=\"PK\u0006\u0007\",n.ZIP64_CENTRAL_DIRECTORY_END=\"PK\u0006\u0006\",n.DATA_DESCRIPTOR=\"PK\u0007\\b\"},{}],24:[function(t,e,n){\"use strict\";var r=t(\"./GenericWorker\"),a=t(\"../utils\");function o(t){r.call(this,\"ConvertWorker to \"+t),this.destType=t}a.inherits(o,r),o.prototype.processChunk=function(t){this.push({data:a.transformTo(this.destType,t.data),meta:t.meta})},e.exports=o},{\"../utils\":32,\"./GenericWorker\":28}],25:[function(t,e,n){\"use strict\";var r=t(\"./GenericWorker\"),a=t(\"../crc32\");function o(){r.call(this,\"Crc32Probe\"),this.withStreamInfo(\"crc32\",0)}t(\"../utils\").inherits(o,r),o.prototype.processChunk=function(t){this.streamInfo.crc32=a(t.data,this.streamInfo.crc32||0),this.push(t)},e.exports=o},{\"../crc32\":4,\"../utils\":32,\"./GenericWorker\":28}],26:[function(t,e,n){\"use strict\";var r=t(\"../utils\"),a=t(\"./GenericWorker\");function o(t){a.call(this,\"DataLengthProbe for \"+t),this.propName=t,this.withStreamInfo(t,0)}r.inherits(o,a),o.prototype.processChunk=function(t){var e;t&&(e=this.streamInfo[this.propName]||0,this.streamInfo[this.propName]=e+t.data.length),a.prototype.processChunk.call(this,t)},e.exports=o},{\"../utils\":32,\"./GenericWorker\":28}],27:[function(t,e,n){\"use strict\";var r=t(\"../utils\"),a=t(\"./GenericWorker\");function o(t){a.call(this,\"DataWorker\");var e=this;this.dataIsReady=!1,this.index=0,this.max=0,this.data=null,this.type=\"\",this._tickScheduled=!1,t.then(function(t){e.dataIsReady=!0,e.data=t,e.max=t&&t.length||0,e.type=r.getTypeOf(t),e.isPaused||e._tickAndRepeat()},function(t){e.error(t)})}r.inherits(o,a),o.prototype.cleanUp=function(){a.prototype.cleanUp.call(this),this.data=null},o.prototype.resume=function(){return!!a.prototype.resume.call(this)&&(!this._tickScheduled&&this.dataIsReady&&(this._tickScheduled=!0,r.delay(this._tickAndRepeat,[],this)),!0)},o.prototype._tickAndRepeat=function(){this._tickScheduled=!1,this.isPaused||this.isFinished||(this._tick(),this.isFinished)||(r.delay(this._tickAndRepeat,[],this),this._tickScheduled=!0)},o.prototype._tick=function(){if(this.isPaused||this.isFinished)return!1;var t=null,e=Math.min(this.max,this.index+16384);if(this.index>=this.max)return this.end();switch(this.type){case\"string\":t=this.data.substring(this.index,e);break;case\"uint8array\":t=this.data.subarray(this.index,e);break;case\"array\":case\"nodebuffer\":t=this.data.slice(this.index,e)}return this.index=e,this.push({data:t,meta:{percent:this.max?this.index/this.max*100:0}})},e.exports=o},{\"../utils\":32,\"./GenericWorker\":28}],28:[function(t,e,n){\"use strict\";function r(t){this.name=t||\"default\",this.streamInfo={},this.generatedError=null,this.extraStreamInfo={},this.isPaused=!0,this.isFinished=!1,this.isLocked=!1,this._listeners={data:[],end:[],error:[]},this.previous=null}r.prototype={push:function(t){this.emit(\"data\",t)},end:function(){if(this.isFinished)return!1;this.flush();try{this.emit(\"end\"),this.cleanUp(),this.isFinished=!0}catch(t){this.emit(\"error\",t)}return!0},error:function(t){return!this.isFinished&&(this.isPaused?this.generatedError=t:(this.isFinished=!0,this.emit(\"error\",t),this.previous&&this.previous.error(t),this.cleanUp()),!0)},on:function(t,e){return this._listeners[t].push(e),this},cleanUp:function(){this.streamInfo=this.generatedError=this.extraStreamInfo=null,this._listeners=[]},emit:function(t,e){if(this._listeners[t])for(var n=0;n \"+t:t}},e.exports=r},{}],29:[function(t,e,n){\"use strict\";var l=t(\"../utils\"),a=t(\"./ConvertWorker\"),o=t(\"./GenericWorker\"),c=t(\"../base64\"),r=t(\"../support\"),i=t(\"../external\"),s=null;if(r.nodestream)try{s=t(\"../nodejs/NodejsStreamOutputAdapter\")}catch(t){}function A(t,e,n){var r=e;switch(e){case\"blob\":case\"arraybuffer\":r=\"uint8array\";break;case\"base64\":r=\"string\"}try{this._internalType=r,this._outputType=e,this._mimeType=n,l.checkSupport(r),this._worker=t.pipe(new a(r)),t.lock()}catch(t){this._worker=new o(\"error\"),this._worker.error(t)}}A.prototype={accumulate:function(t){return s=this,A=t,new i.Promise(function(e,n){var r=[],a=s._internalType,o=s._outputType,i=s._mimeType;s.on(\"data\",function(t,e){r.push(t),A&&A(e)}).on(\"error\",function(t){r=[],n(t)}).on(\"end\",function(){try{var t=function(t,e,n){switch(t){case\"blob\":return l.newBlob(l.transformTo(\"arraybuffer\",e),n);case\"base64\":return c.encode(e);default:return l.transformTo(t,e)}}(o,function(t,e){for(var n=0,r=null,a=0,o=0;o>>6:(n<65536?e[a++]=224|n>>>12:(e[a++]=240|n>>>18,e[a++]=128|n>>>12&63),e[a++]=128|n>>>6&63),e[a++]=128|63&n);return e},a.utf8decode=function(t){if(l.nodebuffer)return A.transformTo(\"nodebuffer\",t).toString(\"utf-8\");for(var e,n,r,a=t=A.transformTo(l.uint8array?\"uint8array\":\"array\",t),o=a.length,i=new Array(2*o),s=e=0;s>10&1023,i[e++]=56320|1023&n)}return i.length!==e&&(i.subarray?i=i.subarray(0,e):i.length=e),A.applyFromCharCode(i)},A.inherits(o,n),o.prototype.processChunk=function(t){var e=A.transformTo(l.uint8array?\"uint8array\":\"array\",t.data),n=(this.leftOver&&this.leftOver.length&&(l.uint8array?(n=e,(e=new Uint8Array(n.length+this.leftOver.length)).set(this.leftOver,0),e.set(n,this.leftOver.length)):e=this.leftOver.concat(e),this.leftOver=null),function(t,e){for(var n=(e=(e=e||t.length)>t.length?t.length:e)-1;0<=n&&128==(192&t[n]);)n--;return!(n<0)&&0!==n&&n+u[t[n]]>e?n:e}(e)),r=e;n!==e.length&&(l.uint8array?(r=e.subarray(0,n),this.leftOver=e.subarray(n,e.length)):(r=e.slice(0,n),this.leftOver=e.slice(n,e.length))),this.push({data:a.utf8decode(r),meta:t.meta})},o.prototype.flush=function(){this.leftOver&&this.leftOver.length&&(this.push({data:a.utf8decode(this.leftOver),meta:{}}),this.leftOver=null)},a.Utf8DecodeWorker=o,A.inherits(i,n),i.prototype.processChunk=function(t){this.push({data:a.utf8encode(t.data),meta:t.meta})},a.Utf8EncodeWorker=i},{\"./nodejsUtils\":14,\"./stream/GenericWorker\":28,\"./support\":30,\"./utils\":32}],32:[function(t,e,i){\"use strict\";var s=t(\"./support\"),A=t(\"./base64\"),n=t(\"./nodejsUtils\"),r=t(\"set-immediate-shim\"),l=t(\"./external\");function a(t){return t}function c(t,e){for(var n=0;n>8;this.dir=!!(16&this.externalFileAttributes),0==t&&(this.dosPermissions=63&this.externalFileAttributes),3==t&&(this.unixPermissions=this.externalFileAttributes>>16&65535),this.dir||\"/\"!==this.fileNameStr.slice(-1)||(this.dir=!0)},parseZIP64ExtraField:function(t){var e;this.extraFields[1]&&(e=r(this.extraFields[1].value),this.uncompressedSize===a.MAX_VALUE_32BITS&&(this.uncompressedSize=e.readInt(8)),this.compressedSize===a.MAX_VALUE_32BITS&&(this.compressedSize=e.readInt(8)),this.localHeaderOffset===a.MAX_VALUE_32BITS&&(this.localHeaderOffset=e.readInt(8)),this.diskNumberStart===a.MAX_VALUE_32BITS)&&(this.diskNumberStart=e.readInt(4))},readExtraFields:function(t){var e,n,r,a=t.index+this.extraFieldsLength;for(this.extraFields||(this.extraFields={});t.index+4>>6:(n<65536?e[a++]=224|n>>>12:(e[a++]=240|n>>>18,e[a++]=128|n>>>12&63),e[a++]=128|n>>>6&63),e[a++]=128|63&n);return e},n.buf2binstring=function(t){return c(t,t.length)},n.binstring2buf=function(t){for(var e=new A.Buf8(t.length),n=0,r=e.length;n>10&1023,i[n++]=56320|1023&r)}return c(i,n)},n.utf8border=function(t,e){for(var n=(e=(e=e||t.length)>t.length?t.length:e)-1;0<=n&&128==(192&t[n]);)n--;return!(n<0)&&0!==n&&n+l[t[n]]>e?n:e}},{\"./common\":41}],43:[function(t,e,n){\"use strict\";e.exports=function(t,e,n,r){for(var a=65535&t|0,o=t>>>16&65535|0,i=0;0!==n;){for(n-=i=2e3>>1:n>>>1;t[e]=n}return t}();e.exports=function(t,e,n,r){var a=s,o=r+n;t^=-1;for(var i=r;i>>8^a[255&(t^e[i])];return-1^t}},{}],46:[function(t,R,e){\"use strict\";var s,u=t(\"../utils/common\"),A=t(\"./trees\"),p=t(\"./adler32\"),f=t(\"./crc32\"),n=t(\"./messages\"),l=0,c=0,d=-2,r=2,h=8,a=286,o=30,i=19,O=2*a+1,M=15,g=3,m=258,v=m+g+1,y=42,b=113;function w(t,e){return t.msg=n[e],e}function x(t){return(t<<1)-(4t.avail_out?t.avail_out:n)&&(u.arraySet(t.output,e.pending_buf,e.pending_out,n,t.next_out),t.next_out+=n,e.pending_out+=n,t.total_out+=n,t.avail_out-=n,e.pending-=n,0===e.pending)&&(e.pending_out=0)}function S(t,e){A._tr_flush_block(t,0<=t.block_start?t.block_start:-1,t.strstart-t.block_start,e),t.block_start=t.strstart,P(t.strm)}function L(t,e){t.pending_buf[t.pending++]=e}function E(t,e){t.pending_buf[t.pending++]=e>>>8&255,t.pending_buf[t.pending++]=255&e}function T(t,e){var n,r,a=t.max_chain_length,o=t.strstart,i=t.prev_length,s=t.nice_match,A=t.strstart>t.w_size-v?t.strstart-(t.w_size-v):0,l=t.window,c=t.w_mask,u=t.prev,p=t.strstart+m,f=l[o+i-1],d=l[o+i];t.prev_length>=t.good_match&&(a>>=2),s>t.lookahead&&(s=t.lookahead);do{if(l[(n=e)+i]===d&&l[n+i-1]===f&&l[n]===l[o]&&l[++n]===l[o+1]){for(o+=2,n++;l[++o]===l[++n]&&l[++o]===l[++n]&&l[++o]===l[++n]&&l[++o]===l[++n]&&l[++o]===l[++n]&&l[++o]===l[++n]&&l[++o]===l[++n]&&l[++o]===l[++n]&&oA&&0!=--a);return i<=t.lookahead?i:t.lookahead}function B(t){var e,n,r,a,o,i,s,A,l,c=t.w_size;do{if(A=t.window_size-t.lookahead-t.strstart,t.strstart>=c+(c-v)){for(u.arraySet(t.window,t.window,c,c,0),t.match_start-=c,t.strstart-=c,t.block_start-=c,e=n=t.hash_size;r=t.head[--e],t.head[e]=c<=r?r-c:0,--n;);for(e=n=c;r=t.prev[--e],t.prev[e]=c<=r?r-c:0,--n;);A+=c}if(0===t.strm.avail_in)break;if(o=t.strm,i=t.window,s=t.strstart+t.lookahead,l=void 0,n=0===(l=(A=A)<(l=o.avail_in)?A:l)?0:(o.avail_in-=l,u.arraySet(i,o.input,o.next_in,l,s),1===o.state.wrap?o.adler=p(o.adler,i,l,s):2===o.state.wrap&&(o.adler=f(o.adler,i,l,s)),o.next_in+=l,o.total_in+=l,l),t.lookahead+=n,t.lookahead+t.insert>=g)for(a=t.strstart-t.insert,t.ins_h=t.window[a],t.ins_h=(t.ins_h<=g&&(t.ins_h=(t.ins_h<=g)if(r=A._tr_tally(t,t.strstart-t.match_start,t.match_length-g),t.lookahead-=t.match_length,t.match_length<=t.max_lazy_match&&t.lookahead>=g){for(t.match_length--;t.strstart++,t.ins_h=(t.ins_h<=g&&(t.ins_h=(t.ins_h<=g&&t.match_length<=t.prev_length){for(a=t.strstart+t.lookahead-g,r=A._tr_tally(t,t.strstart-1-t.prev_match,t.prev_length-g),t.lookahead-=t.prev_length-1,t.prev_length-=2;++t.strstart<=a&&(t.ins_h=(t.ins_h<t.pending_buf_size-5&&(n=t.pending_buf_size-5);;){if(t.lookahead<=1){if(B(t),0===t.lookahead&&e===l)return 1;if(0===t.lookahead)break}t.strstart+=t.lookahead,t.lookahead=0;var r=t.block_start+n;if((0===t.strstart||t.strstart>=r)&&(t.lookahead=t.strstart-r,t.strstart=r,S(t,!1),0===t.strm.avail_out))return 1;if(t.strstart-t.block_start>=t.w_size-v&&(S(t,!1),0===t.strm.avail_out))return 1}return t.insert=0,4===e?(S(t,!0),0===t.strm.avail_out?3:4):(t.strstart>t.block_start&&(S(t,!1),t.strm.avail_out),1)}),new k(4,4,8,4,D),new k(4,5,16,8,D),new k(4,6,32,32,D),new k(4,4,16,16,_),new k(8,16,32,32,_),new k(8,16,128,128,_),new k(8,32,128,256,_),new k(32,128,258,1024,_),new k(32,258,258,4096,_)],e.deflateInit=function(t,e){return I(t,e,h,15,8,0)},e.deflateInit2=I,e.deflateReset=F,e.deflateResetKeep=N,e.deflateSetHeader=function(t,e){return!t||!t.state||2!==t.state.wrap?d:(t.state.gzhead=e,c)},e.deflate=function(t,e){var n,r,a,o;if(!t||!t.state||5>8&255),L(r,r.gzhead.time>>16&255),L(r,r.gzhead.time>>24&255),L(r,9===r.level?2:2<=r.strategy||r.level<2?4:0),L(r,255&r.gzhead.os),r.gzhead.extra&&r.gzhead.extra.length&&(L(r,255&r.gzhead.extra.length),L(r,r.gzhead.extra.length>>8&255)),r.gzhead.hcrc&&(t.adler=f(t.adler,r.pending_buf,r.pending,0)),r.gzindex=0,r.status=69):(L(r,0),L(r,0),L(r,0),L(r,0),L(r,0),L(r,9===r.level?2:2<=r.strategy||r.level<2?4:0),L(r,3),r.status=b)):(i=h+(r.w_bits-8<<4)<<8,i|=(2<=r.strategy||r.level<2?0:r.level<6?1:6===r.level?2:3)<<6,0!==r.strstart&&(i|=32),i+=31-i%31,r.status=b,E(r,i),0!==r.strstart&&(E(r,t.adler>>>16),E(r,65535&t.adler)),t.adler=1)),69===r.status)if(r.gzhead.extra){for(a=r.pending;r.gzindex<(65535&r.gzhead.extra.length)&&(r.pending!==r.pending_buf_size||(r.gzhead.hcrc&&r.pending>a&&(t.adler=f(t.adler,r.pending_buf,r.pending-a,a)),P(t),a=r.pending,r.pending!==r.pending_buf_size));)L(r,255&r.gzhead.extra[r.gzindex]),r.gzindex++;r.gzhead.hcrc&&r.pending>a&&(t.adler=f(t.adler,r.pending_buf,r.pending-a,a)),r.gzindex===r.gzhead.extra.length&&(r.gzindex=0,r.status=73)}else r.status=73;if(73===r.status)if(r.gzhead.name){a=r.pending;do{if(r.pending===r.pending_buf_size&&(r.gzhead.hcrc&&r.pending>a&&(t.adler=f(t.adler,r.pending_buf,r.pending-a,a)),P(t),a=r.pending,r.pending===r.pending_buf_size)){o=1;break}}while(o=r.gzindexa&&(t.adler=f(t.adler,r.pending_buf,r.pending-a,a)),0===o&&(r.gzindex=0,r.status=91)}else r.status=91;if(91===r.status)if(r.gzhead.comment){a=r.pending;do{if(r.pending===r.pending_buf_size&&(r.gzhead.hcrc&&r.pending>a&&(t.adler=f(t.adler,r.pending_buf,r.pending-a,a)),P(t),a=r.pending,r.pending===r.pending_buf_size)){o=1;break}}while(o=r.gzindexa&&(t.adler=f(t.adler,r.pending_buf,r.pending-a,a)),0===o&&(r.status=103)}else r.status=103;if(103===r.status&&(r.gzhead.hcrc?(r.pending+2>r.pending_buf_size&&P(t),r.pending+2<=r.pending_buf_size&&(L(r,255&t.adler),L(r,t.adler>>8&255),t.adler=0,r.status=b)):r.status=b),0!==r.pending){if(P(t),0===t.avail_out)return r.last_flush=-1,c}else if(0===t.avail_in&&x(e)<=x(n)&&4!==e)return w(t,-5);if(666===r.status&&0!==t.avail_in)return w(t,-5);if(0!==t.avail_in||0!==r.lookahead||e!==l&&666!==r.status){var i=2===r.strategy?function(t,e){for(var n;;){if(0===t.lookahead&&(B(t),0===t.lookahead)){if(e===l)return 1;break}if(t.match_length=0,n=A._tr_tally(t,0,t.window[t.strstart]),t.lookahead--,t.strstart++,n&&(S(t,!1),0===t.strm.avail_out))return 1}return t.insert=0,4===e?(S(t,!0),0===t.strm.avail_out?3:4):t.last_lit&&(S(t,!1),0===t.strm.avail_out)?1:2}(r,e):3===r.strategy?function(t,e){for(var n,r,a,o,i=t.window;;){if(t.lookahead<=m){if(B(t),t.lookahead<=m&&e===l)return 1;if(0===t.lookahead)break}if(t.match_length=0,t.lookahead>=g&&0t.lookahead&&(t.match_length=t.lookahead)}if(t.match_length>=g?(n=A._tr_tally(t,1,t.match_length-g),t.lookahead-=t.match_length,t.strstart+=t.match_length,t.match_length=0):(n=A._tr_tally(t,0,t.window[t.strstart]),t.lookahead--,t.strstart++),n&&(S(t,!1),0===t.strm.avail_out))return 1}return t.insert=0,4===e?(S(t,!0),0===t.strm.avail_out?3:4):t.last_lit&&(S(t,!1),0===t.strm.avail_out)?1:2}(r,e):s[r.level].func(r,e);if(3!==i&&4!==i||(r.status=666),1===i||3===i)return 0===t.avail_out&&(r.last_flush=-1),c;if(2===i&&(1===e?A._tr_align(r):5!==e&&(A._tr_stored_block(r,0,0,!1),3===e)&&(C(r.head),0===r.lookahead)&&(r.strstart=0,r.block_start=0,r.insert=0),P(t),0===t.avail_out))return r.last_flush=-1,c}return 4!==e||!(r.wrap<=0)&&(2===r.wrap?(L(r,255&t.adler),L(r,t.adler>>8&255),L(r,t.adler>>16&255),L(r,t.adler>>24&255),L(r,255&t.total_in),L(r,t.total_in>>8&255),L(r,t.total_in>>16&255),L(r,t.total_in>>24&255)):(E(r,t.adler>>>16),E(r,65535&t.adler)),P(t),0=n.w_size&&(0===o&&(C(n.head),n.strstart=0,n.block_start=0,n.insert=0),A=new u.Buf8(n.w_size),u.arraySet(A,e,l-n.w_size,n.w_size,0),e=A,l=n.w_size),A=t.avail_in,i=t.next_in,s=t.input,t.avail_in=l,t.next_in=0,t.input=e,B(n);n.lookahead>=g;){for(r=n.strstart,a=n.lookahead-(g-1);n.ins_h=(n.ins_h<>>=r=n>>>24,x-=r,0==(r=n>>>16&255))f[p++]=65535&n;else{if(!(16&r)){if(0==(64&r)){n=C[(65535&n)+(w&(1<>>=r,x-=r),x<15&&(w+=c[l++]<>>=r=n>>>24,x-=r,!(16&(r=n>>>16&255))){if(0==(64&r)){n=P[(65535&n)+(w&(1<>>=r,x-=r,(r=p-d)>3,w&=(1<<(x-=a<<3))-1,t.next_in=l,t.next_out=p,t.avail_in=l>>24&255)+(t>>>8&65280)+((65280&t)<<8)+((255&t)<<24)}function o(){this.mode=0,this.last=!1,this.wrap=0,this.havedict=!1,this.flags=0,this.dmax=0,this.check=0,this.total=0,this.head=null,this.wbits=0,this.wsize=0,this.whave=0,this.wnext=0,this.window=null,this.hold=0,this.bits=0,this.length=0,this.offset=0,this.extra=0,this.lencode=null,this.distcode=null,this.lenbits=0,this.distbits=0,this.ncode=0,this.nlen=0,this.ndist=0,this.have=0,this.next=null,this.lens=new _.Buf16(320),this.work=new _.Buf16(288),this.lendyn=null,this.distdyn=null,this.sane=0,this.back=0,this.was=0}function i(t){var e;return t&&t.state?(e=t.state,t.total_in=t.total_out=e.total=0,t.msg=\"\",e.wrap&&(t.adler=1&e.wrap),e.mode=M,e.last=0,e.havedict=0,e.dmax=32768,e.head=null,e.hold=0,e.bits=0,e.lencode=e.lendyn=new _.Buf32(r),e.distcode=e.distdyn=new _.Buf32(a),e.sane=1,e.back=-1,R):O}function s(t){var e;return t&&t.state?((e=t.state).wsize=0,e.whave=0,e.wnext=0,i(t)):O}function A(t,e){var n,r;return!t||!t.state||(r=t.state,e<0?(n=0,e=-e):(n=1+(e>>4),e<48&&(e&=15)),e&&(e<8||15=t.wsize?(_.arraySet(t.window,e,n-t.wsize,t.wsize,0),t.wnext=0,t.whave=t.wsize):(r<(a=t.wsize-t.wnext)&&(a=r),_.arraySet(t.window,e,n-r,a,t.wnext),(r-=a)?(_.arraySet(t.window,e,n-r,r,0),t.wnext=r,t.whave=t.wsize):(t.wnext+=a,t.wnext===t.wsize&&(t.wnext=0),t.whave>>8&255,n.check=N(n.check,E,2,0),c=l=0,n.mode=2;else if(n.flags=0,n.head&&(n.head.done=!1),!(1&n.wrap)||(((255&l)<<8)+(l>>8))%31)t.msg=\"incorrect header check\",n.mode=30;else if(8!=(15&l))t.msg=\"unknown compression method\",n.mode=30;else{if(c-=4,x=8+(15&(l>>>=4)),0===n.wbits)n.wbits=x;else if(x>n.wbits){t.msg=\"invalid window size\",n.mode=30;break}n.dmax=1<>8&1),512&n.flags&&(E[0]=255&l,E[1]=l>>>8&255,n.check=N(n.check,E,2,0)),c=l=0,n.mode=3;case 3:for(;c<32;){if(0===s)break t;s--,l+=r[o++]<>>8&255,E[2]=l>>>16&255,E[3]=l>>>24&255,n.check=N(n.check,E,4,0)),c=l=0,n.mode=4;case 4:for(;c<16;){if(0===s)break t;s--,l+=r[o++]<>8),512&n.flags&&(E[0]=255&l,E[1]=l>>>8&255,n.check=N(n.check,E,2,0)),c=l=0,n.mode=5;case 5:if(1024&n.flags){for(;c<16;){if(0===s)break t;s--,l+=r[o++]<>>8&255,n.check=N(n.check,E,2,0)),c=l=0}else n.head&&(n.head.extra=null);n.mode=6;case 6:if(1024&n.flags&&((f=s<(f=n.length)?s:f)&&(n.head&&(x=n.head.extra_len-n.length,n.head.extra||(n.head.extra=new Array(n.head.extra_len)),_.arraySet(n.head.extra,r,o,f,x)),512&n.flags&&(n.check=N(n.check,r,f,o)),s-=f,o+=f,n.length-=f),n.length))break t;n.length=0,n.mode=7;case 7:if(2048&n.flags){if(0===s)break t;for(f=0;x=r[o+f++],n.head&&x&&n.length<65536&&(n.head.name+=String.fromCharCode(x)),x&&f>9&1,n.head.done=!0),t.adler=n.check=0,n.mode=12;break;case 10:for(;c<32;){if(0===s)break t;s--,l+=r[o++]<>>=7&c,c-=7&c,n.mode=27;else{for(;c<3;){if(0===s)break t;s--,l+=r[o++]<>>=1)){case 0:n.mode=14;break;case 1:B=D=void 0;var B,D=n;if(G){for(U=new _.Buf32(512),j=new _.Buf32(32),B=0;B<144;)D.lens[B++]=8;for(;B<256;)D.lens[B++]=9;for(;B<280;)D.lens[B++]=7;for(;B<288;)D.lens[B++]=8;for(I(1,D.lens,0,288,U,0,D.work,{bits:9}),B=0;B<32;)D.lens[B++]=5;I(2,D.lens,0,32,j,0,D.work,{bits:5}),G=!1}if(D.lencode=U,D.lenbits=9,D.distcode=j,D.distbits=5,n.mode=20,6!==e)break;l>>>=2,c-=2;break t;case 2:n.mode=17;break;case 3:t.msg=\"invalid block type\",n.mode=30}l>>>=2,c-=2}break;case 14:for(l>>>=7&c,c-=7&c;c<32;){if(0===s)break t;s--,l+=r[o++]<>>16^65535)){t.msg=\"invalid stored block lengths\",n.mode=30;break}if(n.length=65535&l,c=l=0,n.mode=15,6===e)break t;case 15:n.mode=16;case 16:if(f=n.length){if(0===(f=A<(f=s>>=5,c-=5,n.ndist=1+(31&l),l>>>=5,c-=5,n.ncode=4+(15&l),l>>>=4,c-=4,286>>=3,c-=3}for(;n.have<19;)n.lens[T[n.have++]]=0;if(n.lencode=n.lendyn,n.lenbits=7,P={bits:n.lenbits},C=I(0,n.lens,0,19,n.lencode,0,n.work,P),n.lenbits=P.bits,C){t.msg=\"invalid code lengths set\",n.mode=30;break}n.have=0,n.mode=19;case 19:for(;n.have>>16&255,v=65535&L,!((g=L>>>24)<=c);){if(0===s)break t;s--,l+=r[o++]<>>=g,c-=g,n.lens[n.have++]=v;else{if(16===v){for(S=g+2;c>>=g,c-=g,0===n.have){t.msg=\"invalid bit length repeat\",n.mode=30;break}x=n.lens[n.have-1],f=3+(3&l),l>>>=2,c-=2}else if(17===v){for(S=g+3;c>>=g)),l>>>=3,c=c-g-3}else{for(S=g+7;c>>=g)),l>>>=7,c=c-g-7}if(n.have+f>n.nlen+n.ndist){t.msg=\"invalid bit length repeat\",n.mode=30;break}for(;f--;)n.lens[n.have++]=x}}if(30===n.mode)break;if(0===n.lens[256]){t.msg=\"invalid code -- missing end-of-block\",n.mode=30;break}if(n.lenbits=9,P={bits:n.lenbits},C=I(1,n.lens,0,n.nlen,n.lencode,0,n.work,P),n.lenbits=P.bits,C){t.msg=\"invalid literal/lengths set\",n.mode=30;break}if(n.distbits=6,n.distcode=n.distdyn,P={bits:n.distbits},C=I(2,n.lens,n.nlen,n.ndist,n.distcode,0,n.work,P),n.distbits=P.bits,C){t.msg=\"invalid distances set\",n.mode=30;break}if(n.mode=20,6===e)break t;case 20:n.mode=21;case 21:if(6<=s&&258<=A){t.next_out=i,t.avail_out=A,t.next_in=o,t.avail_in=s,n.hold=l,n.bits=c,F(t,p),i=t.next_out,a=t.output,A=t.avail_out,o=t.next_in,r=t.input,s=t.avail_in,l=n.hold,c=n.bits,12===n.mode&&(n.back=-1);break}for(n.back=0;m=(L=n.lencode[l&(1<>>16&255,v=65535&L,!((g=L>>>24)<=c);){if(0===s)break t;s--,l+=r[o++]<>y)])>>>16&255,v=65535&L,!(y+(g=L>>>24)<=c);){if(0===s)break t;s--,l+=r[o++]<>>=y,c-=y,n.back+=y}if(l>>>=g,c-=g,n.back+=g,n.length=v,0===m){n.mode=26;break}if(32&m){n.back=-1,n.mode=12;break}if(64&m){t.msg=\"invalid literal/length code\",n.mode=30;break}n.extra=15&m,n.mode=22;case 22:if(n.extra){for(S=n.extra;c>>=n.extra,c-=n.extra,n.back+=n.extra}n.was=n.length,n.mode=23;case 23:for(;m=(L=n.distcode[l&(1<>>16&255,v=65535&L,!((g=L>>>24)<=c);){if(0===s)break t;s--,l+=r[o++]<>y)])>>>16&255,v=65535&L,!(y+(g=L>>>24)<=c);){if(0===s)break t;s--,l+=r[o++]<>>=y,c-=y,n.back+=y}if(l>>>=g,c-=g,n.back+=g,64&m){t.msg=\"invalid distance code\",n.mode=30;break}n.offset=v,n.extra=15&m,n.mode=24;case 24:if(n.extra){for(S=n.extra;c>>=n.extra,c-=n.extra,n.back+=n.extra}if(n.offset>n.dmax){t.msg=\"invalid distance too far back\",n.mode=30;break}n.mode=25;case 25:if(0===A)break t;if(n.offset>(f=p-A)){if((f=n.offset-f)>n.whave&&n.sane){t.msg=\"invalid distance too far back\",n.mode=30;break}d=f>n.wnext?(f-=n.wnext,n.wsize-f):n.wnext-f,f>n.length&&(f=n.length),h=n.window}else h=a,d=i-n.offset,f=n.length;for(A-=f=Af?(h=k[N+i[y]],T[B+i[y]]):(h=96,0),A=1<<(d=v-P),b=l=1<>P)+(l-=A)]=d<<24|h<<16|g|0,0!==l;);for(A=1<>=1;if(0!==A?E=(E&A-1)+A:E=0,y++,0==--D[v]){if(v===w)break;v=e[n+i[y]]}if(x>>7)]}function o(t,e){t.pending_buf[t.pending++]=255&e,t.pending_buf[t.pending++]=e>>>8&255}function C(t,e,n){t.bi_valid>r-n?(t.bi_buf|=e<>r-t.bi_valid,t.bi_valid+=n-r):(t.bi_buf|=e<>>=1,n<<=1,0<--e;);return n>>>1}function L(t,e,n){for(var r,a=new Array(16),o=0,i=1;i<=15;i++)a[i]=o=o+n[i-1]<<1;for(r=0;r<=e;r++){var s=t[2*r+1];0!==s&&(t[2*r]=S(a[s]++,s))}}function E(t){for(var e=0;e<286;e++)t.dyn_ltree[2*e]=0;for(e=0;e<30;e++)t.dyn_dtree[2*e]=0;for(e=0;e<19;e++)t.bl_tree[2*e]=0;t.dyn_ltree[512]=1,t.opt_len=t.static_len=0,t.last_lit=t.matches=0}function T(t){8>1;1<=n;n--)B(t,o,n);for(a=A;n=t.heap[1],t.heap[1]=t.heap[t.heap_len--],B(t,o,1),r=t.heap[1],t.heap[--t.heap_max]=n,t.heap[--t.heap_max]=r,o[2*a]=o[2*n]+o[2*r],t.depth[a]=(t.depth[n]>=t.depth[r]?t.depth[n]:t.depth[r])+1,o[2*n+1]=o[2*r+1]=a,t.heap[1]=a++,B(t,o,1),2<=t.heap_len;);t.heap[--t.heap_max]=t.heap[1];for(var c,u,p,f,d,h=t,g=e.dyn_tree,m=e.max_code,v=e.stat_desc.static_tree,y=e.stat_desc.has_stree,b=e.stat_desc.extra_bits,w=e.stat_desc.extra_base,x=e.stat_desc.max_length,C=0,P=0;P<=15;P++)h.bl_count[P]=0;for(g[2*h.heap[h.heap_max]+1]=0,c=h.heap_max+1;c<573;c++)x<(P=g[2*g[2*(u=h.heap[c])+1]+1]+1)&&(P=x,C++),g[2*u+1]=P,m>=7;i<30;i++)for(y[i]=a<<7,e=0;e<1<>>=1)if(1&e&&0!==t.dyn_ltree[2*n])return 0;if(0!==t.dyn_ltree[18]||0!==t.dyn_ltree[20]||0!==t.dyn_ltree[26])return 1;for(n=32;n<256;n++)if(0!==t.dyn_ltree[2*n])return 1;return 0}(t)),_(t,t.l_desc),_(t,t.d_desc),s=function(t){var e;for(k(t,t.dyn_ltree,t.l_desc.max_code),k(t,t.dyn_dtree,t.d_desc.max_code),_(t,t.bl_desc),e=18;3<=e&&0===t.bl_tree[2*c[e]+1];e--);return t.opt_len+=3*(e+1)+5+5+4,e}(t),a=t.opt_len+3+7>>>3,(o=t.static_len+3+7>>>3)<=a&&(a=o)):a=o=n+5,n+4<=a&&-1!==e)I(t,e,n,r);else if(4===t.strategy||o===a)C(t,2+(r?1:0),3),D(t,u,p);else{C(t,4+(r?1:0),3);var A=t,l=(e=t.l_desc.max_code+1,n=t.d_desc.max_code+1,s+1);for(C(A,e-257,5),C(A,n-1,5),C(A,l-4,4),i=0;i>>8&255,t.pending_buf[t.d_buf+2*t.last_lit+1]=255&e,t.pending_buf[t.l_buf+t.last_lit]=255&n,t.last_lit++,0===e?t.dyn_ltree[2*n]++:(t.matches++,e--,t.dyn_ltree[2*(d[n]+256+1)]++,t.dyn_dtree[2*x(e)]++),t.last_lit===t.lit_bufsize-1},e._tr_align=function(t){C(t,2,3),P(t,256,u),16===(t=t).bi_valid?(o(t,t.bi_buf),t.bi_buf=0,t.bi_valid=0):8<=t.bi_valid&&(t.pending_buf[t.pending++]=255&t.bi_buf,t.bi_buf>>=8,t.bi_valid-=8)}},{\"../utils/common\":41}],53:[function(t,e,n){\"use strict\";e.exports=function(){this.input=null,this.next_in=0,this.avail_in=0,this.total_in=0,this.output=null,this.next_out=0,this.avail_out=0,this.total_out=0,this.msg=\"\",this.state=null,this.data_type=2,this.adler=0}},{}],54:[function(t,e,n){\"use strict\";e.exports=\"function\"==typeof setImmediate?setImmediate:function(){var t=[].slice.apply(arguments);t.splice(1,0,0),setTimeout.apply(null,t)}},{}]},{},[10])(10)})}.call(this,void 0!==n?n:\"undefined\"!=typeof self?self:\"undefined\"!=typeof window?window:{})},{}]},{},[1])(1)})}.call(this,void 0!==n?n:\"undefined\"!=typeof self?self:\"undefined\"!=typeof window?window:{})},{}]},{},[1])(1)})}.call(this,void 0!==n?n:\"undefined\"!=typeof self?self:\"undefined\"!=typeof window?window:{})},{}]},{},[1])(1)})}.call(this,void 0!==n?n:\"undefined\"!=typeof self?self:\"undefined\"!=typeof window?window:{})},{}]},{},[1])(1)})}.call(this,\"undefined\"!=typeof global?global:\"undefined\"!=typeof self?self:\"undefined\"!=typeof window?window:{})},{}]},{},[1])(1)}),function r(a,o,i){function s(e,t){if(!o[e]){if(!a[e]){var n=\"function\"==typeof require&&require;if(!t&&n)return n(e,!0);if(A)return A(e,!0);t=new Error(\"Cannot find module '\"+e+\"'\");throw t.code=\"MODULE_NOT_FOUND\",t}n=o[e]={exports:{}};a[e][0].call(n.exports,function(t){return s(a[e][1][t]||t)},n,n.exports,r,a,o,i)}return o[e].exports}for(var A=\"function\"==typeof require&&require,t=0;ti;)o.call(t,r=a[i++])&&e.push(r);return e}},{104:104,107:107,108:108}],62:[function(t,e,n){function f(t,e,n){var r,a,o,i=t&f.F,s=t&f.G,A=t&f.P,l=t&f.B,c=s?d:t&f.S?d[e]||(d[e]={}):(d[e]||{})[y],u=s?h:h[e]||(h[e]={}),p=u[y]||(u[y]={});for(r in n=s?e:n)a=((o=!i&&c&&void 0!==c[r])?c:n)[r],o=l&&o?v(a,d):A&&\"function\"==typeof a?v(Function.call,a):a,c&&m(c,r,a,t&f.U),u[r]!=a&&g(u,r,o),A&&p[r]!=a&&(p[r]=a)}var d=t(70),h=t(52),g=t(72),m=t(118),v=t(54),y=\"prototype\";d.core=h,f.F=1,f.G=2,f.S=4,f.P=8,f.B=16,f.W=32,f.U=64,f.R=128,e.exports=f},{118:118,52:52,54:54,70:70,72:72}],63:[function(t,e,n){var r=t(152)(\"match\");e.exports=function(e){var n=/./;try{\"/./\"[e](n)}catch(t){try{return n[r]=!1,!\"/./\"[e](n)}catch(t){}}return!0}},{152:152}],64:[function(t,e,n){arguments[4][23][0].apply(n,arguments)},{23:23}],65:[function(t,e,n){\"use strict\";t(248);var r,A=t(118),l=t(72),c=t(64),u=t(57),p=t(152),f=t(120),d=p(\"species\"),h=!c(function(){var t=/./;return t.exec=function(){var t=[];return t.groups={a:\"7\"},t},\"7\"!==\"\".replace(t,\"$
\")}),g=(r=(t=/(?:)/).exec,t.exec=function(){return r.apply(this,arguments)},2===(t=\"ab\".split(t)).length&&\"a\"===t[0]&&\"b\"===t[1]);e.exports=function(n,t,e){var o,r,a=p(n),i=!c(function(){var t={};return t[a]=function(){return 7},7!=\"\"[n](t)}),s=i?!c(function(){var t=!1,e=/a/;return e.exec=function(){return t=!0,null},\"split\"===n&&(e.constructor={},e.constructor[d]=function(){return e}),e[a](\"\"),!t}):void 0;i&&s&&(\"replace\"!==n||h)&&(\"split\"!==n||g)||(o=/./[a],e=(s=e(u,a,\"\"[n],function(t,e,n,r,a){return e.exec===f?i&&!a?{done:!0,value:o.call(e,n,r)}:{done:!0,value:t.call(n,e,r)}:{done:!1}}))[0],r=s[1],A(String.prototype,n,e),l(RegExp.prototype,a,2==t?function(t,e){return r.call(t,this,e)}:function(t){return r.call(t,this)}))}},{118:118,120:120,152:152,248:248,57:57,64:64,72:72}],66:[function(t,e,n){\"use strict\";var r=t(38);e.exports=function(){var t=r(this),e=\"\";return t.global&&(e+=\"g\"),t.ignoreCase&&(e+=\"i\"),t.multiline&&(e+=\"m\"),t.unicode&&(e+=\"u\"),t.sticky&&(e+=\"y\"),e}},{38:38}],67:[function(t,e,n){\"use strict\";var d=t(79),h=t(81),g=t(141),m=t(54),v=t(152)(\"isConcatSpreadable\");e.exports=function t(e,n,r,a,o,i,s,A){for(var l,c,u=o,p=0,f=!!s&&m(s,A,3);pdocument.F=Object<\\/script>\"),t.close(),l=t.F;e--;)delete l[A][i[e]];return l()};t.exports=Object.create||function(t,e){var n;return null!==t?(r[A]=a(t),n=new r,r[A]=null,n[s]=t):n=l(),void 0===e?n:o(n,e)}},{100:100,125:125,38:38,59:59,60:60,73:73}],99:[function(t,e,n){arguments[4][29][0].apply(n,arguments)},{143:143,29:29,38:38,58:58,74:74}],100:[function(t,e,n){var i=t(99),s=t(38),A=t(107);e.exports=t(58)?Object.defineProperties:function(t,e){s(t);for(var n,r=A(e),a=r.length,o=0;oa;)!i(r,n=e[a++])||~A(o,n)||o.push(n);return o}},{125:125,140:140,41:41,71:71}],107:[function(t,e,n){var r=t(106),a=t(60);e.exports=Object.keys||function(t){return r(t,a)}},{106:106,60:60}],108:[function(t,e,n){n.f={}.propertyIsEnumerable},{}],109:[function(t,e,n){var a=t(62),o=t(52),i=t(64);e.exports=function(t,e){var n=(o.Object||{})[t]||Object[t],r={};r[t]=e(n),a(a.S+a.F*i(function(){n(1)}),\"Object\",r)}},{52:52,62:62,64:64}],110:[function(t,e,n){var A=t(58),l=t(107),c=t(140),u=t(108).f;e.exports=function(s){return function(t){for(var e,n=c(t),r=l(n),a=r.length,o=0,i=[];o>>0||(o.test(t)?16:10))}:r},{134:134,135:135,70:70}],114:[function(t,e,n){e.exports=function(t){try{return{e:!1,v:t()}}catch(t){return{e:!0,v:t}}}},{}],115:[function(t,e,n){var r=t(38),a=t(81),o=t(96);e.exports=function(t,e){return r(t),a(e)&&e.constructor===t?e:((0,(t=o.f(t)).resolve)(e),t.promise)}},{38:38,81:81,96:96}],116:[function(t,e,n){arguments[4][30][0].apply(n,arguments)},{30:30}],117:[function(t,e,n){var a=t(118);e.exports=function(t,e,n){for(var r in e)a(t,r,e[r],n);return t}},{118:118}],118:[function(t,e,n){var o=t(70),i=t(72),s=t(71),A=t(147)(\"src\"),r=t(69),a=\"toString\",l=(\"\"+r).split(a);t(52).inspectSource=function(t){return r.call(t)},(e.exports=function(t,e,n,r){var a=\"function\"==typeof n;a&&!s(n,\"name\")&&i(n,\"name\",e),t[e]!==n&&(a&&!s(n,A)&&i(n,A,t[e]?\"\"+t[e]:l.join(String(e))),t===o?t[e]=n:r?t[e]?t[e]=n:i(t,e,n):(delete t[e],i(t,e,n)))})(Function.prototype,a,function(){return\"function\"==typeof this&&this[A]||r.call(this)})},{147:147,52:52,69:69,70:70,71:71,72:72}],119:[function(t,e,n){\"use strict\";var r=t(47),a=RegExp.prototype.exec;e.exports=function(t,e){var n=t.exec;if(\"function\"==typeof n){n=n.call(t,e);if(\"object\"!=typeof n)throw new TypeError(\"RegExp exec method returned something other than an Object or null\");return n}if(\"RegExp\"!==r(t))throw new TypeError(\"RegExp#exec called on incompatible receiver\");return a.call(t,e)}},{47:47}],120:[function(t,e,n){\"use strict\";var r,a,i=t(66),s=RegExp.prototype.exec,A=String.prototype.replace,t=s,l=\"lastIndex\",c=(a=/b*/g,s.call(r=/a/,\"a\"),s.call(a,\"a\"),0!==r[l]||0!==a[l]),u=void 0!==/()??/.exec(\"\")[1];e.exports=t=c||u?function(t){var e,n,r,a,o=this;return u&&(n=new RegExp(\"^\"+o.source+\"$(?!\\\\s)\",i.call(o))),c&&(e=o[l]),r=s.call(o,t),c&&r&&(o[l]=o.global?r.index+r[0].length:e),u&&r&&1\"+t+\"\"}var a=t(62),o=t(64),i=t(57),s=/\"/g;e.exports=function(e,t){var n={};n[e]=t(r),a(a.P+a.F*o(function(){var t=\"\"[e]('\"');return t!==t.toLowerCase()||3e&&(a=a.slice(0,e)),r?a+t:t+a)}},{133:133,141:141,57:57}],133:[function(t,e,n){\"use strict\";var a=t(139),o=t(57);e.exports=function(t){var e=String(o(this)),n=\"\",r=a(t);if(r<0||r==1/0)throw RangeError(\"Count can't be negative\");for(;0>>=1)&&(e+=e))1&r&&(n+=e);return n}},{139:139,57:57}],134:[function(t,e,n){function r(t,e,n){var r={},a=i(function(){return!!s[t]()||\"​…\"!=\"​…\"[t]()}),e=r[t]=a?e(c):s[t];n&&(r[n]=e),o(o.P+o.F*a,\"String\",r)}var o=t(62),a=t(57),i=t(64),s=t(135),t=\"[\"+s+\"]\",A=RegExp(\"^\"+t+t+\"*\"),l=RegExp(t+t+\"*$\"),c=r.trim=function(t,e){return t=String(a(t)),1&e&&(t=t.replace(A,\"\")),t=2&e?t.replace(l,\"\"):t};e.exports=r},{135:135,57:57,62:62,64:64}],135:[function(t,e,n){e.exports=\"\\t\\n\\v\\f\\r   ᠎              \\u2028\\u2029\\ufeff\"},{}],136:[function(t,e,n){function r(){var t,e=+this;m.hasOwnProperty(e)&&(t=m[e],delete m[e],t())}function a(t){r.call(t.data)}var o,i=t(54),s=t(76),A=t(73),l=t(59),c=t(70),u=c.process,p=c.setImmediate,f=c.clearImmediate,d=c.MessageChannel,h=c.Dispatch,g=0,m={},v=\"onreadystatechange\";p&&f||(p=function(t){for(var e=[],n=1;n>1,l=23===e?w(2,-24)-w(2,-77):0,c=0,u=t<0||0===t&&1/t<0?1:0;for((t=G(t))!=t||t===y?(a=t!=t?1:0,r=n):(r=Q(W(t)/Y),t*(o=w(2,-r))<1&&(r--,o*=2),2<=(t+=1<=r+A?l/o:l*w(2,1-A))*o&&(r++,o/=2),n<=r+A?(a=0,r=n):1<=r+A?(a=(t*o-1)*w(2,e),r+=A):(a=t*w(2,A-1)*w(2,e),r=0));8<=e;i[c++]=255&a,a/=256,e-=8);for(r=r<>1,s=a-7,A=n-1,a=t[A--],l=127&a;for(a>>=7;0>=-s,s+=e;0>8&255]}function D(t){return[255&t,t>>8&255,t>>16&255,t>>24&255]}function X(t){return S(t,52,8)}function H(t){return S(t,23,4)}function _(t,e,n){U(t[f],e,{get:function(){return this[n]}})}function k(t,e,n,r){n=c(+n);if(n+e>t[C])throw v(d);var a=t[x]._b,n=n+t[P],t=a.slice(n,n+e);return r?t:t.reverse()}function N(t,e,n,r,a,o){n=c(+n);if(n+e>t[C])throw v(d);for(var i=t[x]._b,s=n+t[P],A=r(+a),l=0;lV;)(F=I[V++])in h||o(h,F,b[F]);O||(s.constructor=h)}var l=new g(new h(2)),q=g[f].setInt8;l.setInt8(0,2147483648),l.setInt8(1,2147483649),!l.getInt8(0)&&l.getInt8(1)||i(g[f],{setInt8:function(t,e){q.call(this,t,e<<24>>24)},setUint8:function(t,e){q.call(this,t,e<<24>>24)}},!0)}else h=function(t){A(this,h,u);t=c(t);this._b=j.call(new Array(t),0),this[C]=t},g=function(t,e,n){A(this,g,p),A(t,h,p);var r=t[C],e=M(e);if(e<0||r>24},getUint8:function(t){return k(this,1,t)[0]},getInt16:function(t){t=k(this,2,t,arguments[1]);return(t[1]<<8|t[0])<<16>>16},getUint16:function(t){t=k(this,2,t,arguments[1]);return t[1]<<8|t[0]},getInt32:function(t){return E(k(this,4,t,arguments[1]))},getUint32:function(t){return E(k(this,4,t,arguments[1]))>>>0},getFloat32:function(t){return L(k(this,4,t,arguments[1]),23,4)},getFloat64:function(t){return L(k(this,8,t,arguments[1]),52,8)},setInt8:function(t,e){N(this,1,t,T,e)},setUint8:function(t,e){N(this,1,t,T,e)},setInt16:function(t,e){N(this,2,t,B,e,arguments[2])},setUint16:function(t,e){N(this,2,t,B,e,arguments[2])},setInt32:function(t,e){N(this,4,t,D,e,arguments[2])},setUint32:function(t,e){N(this,4,t,D,e,arguments[2])},setFloat32:function(t,e){N(this,4,t,H,e,arguments[2])},setFloat64:function(t,e){N(this,8,t,X,e,arguments[2])}});t(h,u),t(g,p),o(g[f],a.VIEW,!0),e[u]=h,e[p]=g},{103:103,117:117,124:124,138:138,139:139,141:141,146:146,37:37,40:40,58:58,64:64,70:70,72:72,89:89,99:99}],146:[function(t,e,n){for(var r,a=t(70),o=t(72),t=t(147),i=t(\"typed_array\"),s=t(\"view\"),t=!(!a.ArrayBuffer||!a.DataView),A=t,l=0,c=\"Int8Array,Uint8Array,Uint8ClampedArray,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array\".split(\",\");l<9;)(r=a[c[l++]])?(o(r.prototype,i,!0),o(r.prototype,s,!0)):A=!1;e.exports={ABV:t,CONSTR:A,TYPED:i,VIEW:s}},{147:147,70:70,72:72}],147:[function(t,e,n){var r=0,a=Math.random();e.exports=function(t){return\"Symbol(\".concat(void 0===t?\"\":t,\")_\",(++r+a).toString(36))}},{}],148:[function(t,e,n){t=t(70).navigator;e.exports=t&&t.userAgent||\"\"},{70:70}],149:[function(t,e,n){var r=t(81);e.exports=function(t,e){if(r(t)&&t._t===e)return t;throw TypeError(\"Incompatible receiver, \"+e+\" required!\")}},{81:81}],150:[function(t,e,n){var r=t(70),a=t(52),o=t(89),i=t(151),s=t(99).f;e.exports=function(t){var e=a.Symbol||(a.Symbol=!o&&r.Symbol||{});\"_\"==t.charAt(0)||t in e||s(e,t,{value:i.f(t)})}},{151:151,52:52,70:70,89:89,99:99}],151:[function(t,e,n){n.f=t(152)},{152:152}],152:[function(t,e,n){var r=t(126)(\"wks\"),a=t(147),o=t(70).Symbol,i=\"function\"==typeof o;(e.exports=function(t){return r[t]||(r[t]=i&&o[t]||(i?o:a)(\"Symbol.\"+t))}).store=r},{126:126,147:147,70:70}],153:[function(t,e,n){var r=t(47),a=t(152)(\"iterator\"),o=t(88);e.exports=t(52).getIteratorMethod=function(t){if(null!=t)return t[a]||t[\"@@iterator\"]||o[r(t)]}},{152:152,47:47,52:52,88:88}],154:[function(t,e,n){var r=t(62);r(r.P,\"Array\",{copyWithin:t(39)}),t(35)(\"copyWithin\")},{35:35,39:39,62:62}],155:[function(t,e,n){\"use strict\";var r=t(62),a=t(42)(4);r(r.P+r.F*!t(128)([].every,!0),\"Array\",{every:function(t){return a(this,t,arguments[1])}})},{128:128,42:42,62:62}],156:[function(t,e,n){var r=t(62);r(r.P,\"Array\",{fill:t(40)}),t(35)(\"fill\")},{35:35,40:40,62:62}],157:[function(t,e,n){\"use strict\";var r=t(62),a=t(42)(2);r(r.P+r.F*!t(128)([].filter,!0),\"Array\",{filter:function(t){return a(this,t,arguments[1])}})},{128:128,42:42,62:62}],158:[function(t,e,n){\"use strict\";var r=t(62),a=t(42)(6),o=\"findIndex\",i=!0;o in[]&&Array(1)[o](function(){i=!1}),r(r.P+r.F*i,\"Array\",{findIndex:function(t){return a(this,t,1=t.length?(this._t=void 0,a(1)):a(0,\"keys\"==e?n:\"values\"==e?t[n]:[n,t[n]])},\"values\"),o.Arguments=o.Array,r(\"keys\"),r(\"values\"),r(\"entries\")},{140:140,35:35,85:85,87:87,88:88}],165:[function(t,e,n){\"use strict\";var r=t(62),a=t(140),o=[].join;r(r.P+r.F*(t(77)!=Object||!t(128)(o)),\"Array\",{join:function(t){return o.call(a(this),void 0===t?\",\":t)}})},{128:128,140:140,62:62,77:77}],166:[function(t,e,n){\"use strict\";var r=t(62),a=t(140),o=t(139),i=t(141),s=[].lastIndexOf,A=!!s&&1/[1].lastIndexOf(1,-0)<0;r(r.P+r.F*(A||!t(128)(s)),\"Array\",{lastIndexOf:function(t){if(A)return s.apply(this,arguments)||0;var e=a(this),n=i(e.length),r=n-1;for((r=1>>=0)?31-Math.floor(Math.log(t+.5)*Math.LOG2E):32}})},{62:62}],189:[function(t,e,n){var t=t(62),r=Math.exp;t(t.S,\"Math\",{cosh:function(t){return(r(t=+t)+r(-t))/2}})},{62:62}],190:[function(t,e,n){var r=t(62),t=t(90);r(r.S+r.F*(t!=Math.expm1),\"Math\",{expm1:t})},{62:62,90:90}],191:[function(t,e,n){var r=t(62);r(r.S,\"Math\",{fround:t(91)})},{62:62,91:91}],192:[function(t,e,n){var t=t(62),A=Math.abs;t(t.S,\"Math\",{hypot:function(t,e){for(var n,r,a=0,o=0,i=arguments.length,s=0;o>>16)*r+n*(65535&e>>>16)<<16>>>0)}})},{62:62,64:64}],194:[function(t,e,n){t=t(62);t(t.S,\"Math\",{log10:function(t){return Math.log(t)*Math.LOG10E}})},{62:62}],195:[function(t,e,n){var r=t(62);r(r.S,\"Math\",{log1p:t(92)})},{62:62,92:92}],196:[function(t,e,n){t=t(62);t(t.S,\"Math\",{log2:function(t){return Math.log(t)/Math.LN2}})},{62:62}],197:[function(t,e,n){var r=t(62);r(r.S,\"Math\",{sign:t(93)})},{62:62,93:93}],198:[function(t,e,n){var r=t(62),a=t(90),o=Math.exp;r(r.S+r.F*t(64)(function(){return-2e-17!=!Math.sinh(-2e-17)}),\"Math\",{sinh:function(t){return Math.abs(t=+t)<1?(a(t)-a(-t))/2:(o(t-1)-o(-t-1))*(Math.E/2)}})},{62:62,64:64,90:90}],199:[function(t,e,n){var r=t(62),a=t(90),o=Math.exp;r(r.S,\"Math\",{tanh:function(t){var e=a(t=+t),n=a(-t);return e==1/0?1:n==1/0?-1:(e-n)/(o(t)+o(-t))}})},{62:62,90:90}],200:[function(t,e,n){t=t(62);t(t.S,\"Math\",{trunc:function(t){return(0x;x++)o(h,y=w[x])&&!o(b,y)&&p(b,y,u(h,y));(b.prototype=g).constructor=b,t(118)(a,d,b)}},{101:101,103:103,118:118,134:134,143:143,48:48,58:58,64:64,70:70,71:71,75:75,98:98,99:99}],202:[function(t,e,n){t=t(62);t(t.S,\"Number\",{EPSILON:Math.pow(2,-52)})},{62:62}],203:[function(t,e,n){var r=t(62),a=t(70).isFinite;r(r.S,\"Number\",{isFinite:function(t){return\"number\"==typeof t&&a(t)}})},{62:62,70:70}],204:[function(t,e,n){var r=t(62);r(r.S,\"Number\",{isInteger:t(80)})},{62:62,80:80}],205:[function(t,e,n){t=t(62);t(t.S,\"Number\",{isNaN:function(t){return t!=t}})},{62:62}],206:[function(t,e,n){var r=t(62),a=t(80),o=Math.abs;r(r.S,\"Number\",{isSafeInteger:function(t){return a(t)&&o(t)<=9007199254740991}})},{62:62,80:80}],207:[function(t,e,n){t=t(62);t(t.S,\"Number\",{MAX_SAFE_INTEGER:9007199254740991})},{62:62}],208:[function(t,e,n){t=t(62);t(t.S,\"Number\",{MIN_SAFE_INTEGER:-9007199254740991})},{62:62}],209:[function(t,e,n){var r=t(62),t=t(112);r(r.S+r.F*(Number.parseFloat!=t),\"Number\",{parseFloat:t})},{112:112,62:62}],210:[function(t,e,n){var r=t(62),t=t(113);r(r.S+r.F*(Number.parseInt!=t),\"Number\",{parseInt:t})},{113:113,62:62}],211:[function(t,e,n){\"use strict\";function s(t,e){for(var n=-1,r=e;++n<6;)i[n]=(r+=t*i[n])%1e7,r=o(r/1e7)}function A(t){for(var e=6,n=0;0<=--e;)i[e]=o((n+=i[e])/t),n=n%t*1e7}function l(){for(var t,e=6,n=\"\";0<=--e;)\"\"===n&&0!==e&&0===i[e]||(t=String(i[e]),n=\"\"===n?t:n+f.call(\"0\",7-t.length)+t);return n}function c(t,e,n){return 0===e?n:e%2==1?c(t,e-1,n*t):c(t*t,e/2,n)}var r=t(62),u=t(139),p=t(34),f=t(133),a=1..toFixed,o=Math.floor,i=[0,0,0,0,0,0],d=\"Number.toFixed: incorrect invocation!\";r(r.P+r.F*(!!a&&(\"0.000\"!==8e-5.toFixed(3)||\"1\"!==.9.toFixed(0)||\"1.25\"!==1.255.toFixed(2)||\"1000000000000000128\"!==0xde0b6b3a7640080.toFixed(0))||!t(64)(function(){a.call({})})),\"Number\",{toFixed:function(t){var e,n,r,a=p(this,d),t=u(t),o=\"\",i=\"0\";if(t<0||20n;){a=void 0;o=void 0;i=void 0;s=void 0;A=void 0;l=void 0;c=void 0;var r=f[n++];var a,o,i,s=e?r.ok:r.fail,A=r.resolve,l=r.reject,c=r.domain;try{s?(e||(2==u._h&&g(u),u._h=1),!0===s?a=t:(c&&c.enter(),a=s(t),c&&(c.exit(),i=!0)),a===r.promise?l(T(\"Promise-chain cycle\")):(o=d(a))?o.call(a,A,l):A(a)):l(t)}catch(r){c&&!i&&c.exit(),l(r)}}u._c=[],u._n=!1,p&&!u._h&&h(u)}))}function o(t){var e=this;e._d||(e._d=!0,(e=e._w||e)._v=t,e._s=2,e._a||(e._a=e._c.slice()),a(e,!0))}function h(a){w.call(c,function(){var t,e,n=a._v,r=F(a);if(r&&(t=P(function(){k?B.emit(\"unhandledRejection\",n,a):(e=c.onunhandledrejection)?e({promise:a,reason:n}):(e=c.console)&&e.error&&e.error(\"Unhandled promise rejection\",n)}),a._h=k||F(a)?2:1),a._a=void 0,r&&t.e)throw t.v})}function g(e){w.call(c,function(){var t;k?B.emit(\"rejectionHandled\",e):(t=c.onrejectionhandled)&&t({promise:e,reason:e._v})})}var e,i,s,A,l=n(89),c=n(70),u=n(54),t=n(47),p=n(62),f=n(81),m=n(33),v=n(37),y=n(68),b=n(127),w=n(136).set,x=n(95)(),C=n(96),P=n(114),S=n(148),L=n(115),E=\"Promise\",T=c.TypeError,B=c.process,D=B&&B.versions,M=D&&D.v8||\"\",_=c[E],k=\"process\"==t(B),N=i=C.f,D=!!function(){try{var t=_.resolve(1),e=(t.constructor={})[n(152)(\"species\")]=function(t){t(r,r)};return(k||\"function\"==typeof PromiseRejectionEvent)&&t.then(r)instanceof e&&0!==M.indexOf(\"6.6\")&&-1===S.indexOf(\"Chrome/66\")}catch(t){}}(),F=function(t){return 1!==t._h&&0===(t._a||t._c).length},I=function(t){var n,r=this;if(!r._d){r._d=!0,r=r._w||r;try{if(r===t)throw T(\"Promise can't be resolved itself\");(n=d(t))?x(function(){var e={_w:r,_d:!1};try{n.call(t,u(I,e,1),u(o,e,1))}catch(t){o.call(e,t)}}):(r._v=t,r._s=1,a(r,!1))}catch(t){o.call({_w:r,_d:!1},t)}}};D||(_=function(t){v(this,_,E,\"_h\"),m(t),e.call(this);try{t(u(I,this,1),u(o,this,1))}catch(t){o.call(this,t)}},(e=function(t){this._c=[],this._a=void 0,this._s=0,this._d=!1,this._v=void 0,this._h=0,this._n=!1}).prototype=n(117)(_.prototype,{then:function(t,e){var n=N(b(this,_));return n.ok=\"function\"!=typeof t||t,n.fail=\"function\"==typeof e&&e,n.domain=k?B.domain:void 0,this._c.push(n),this._a&&this._a.push(n),this._s&&a(this,!1),n.promise},catch:function(t){return this.then(void 0,t)}}),s=function(){var t=new e;this.promise=t,this.resolve=u(I,t,1),this.reject=u(o,t,1)},C.f=N=function(t){return t===_||t===A?new s:i(t)}),p(p.G+p.W+p.F*!D,{Promise:_}),n(124)(_,E),n(123)(E),A=n(52)[E],p(p.S+p.F*!D,E,{reject:function(t){var e=N(this);return(0,e.reject)(t),e.promise}}),p(p.S+p.F*(l||!D),E,{resolve:function(t){return L(l&&this===A?_:this,t)}}),p(p.S+p.F*!(D&&n(86)(function(t){_.all(t).catch(r)})),E,{all:function(t){var i=this,e=N(i),s=e.resolve,A=e.reject,n=P(function(){var r=[],a=0,o=1;y(t,!1,function(t){var e=a++,n=!1;r.push(void 0),o++,i.resolve(t).then(function(t){n||(n=!0,r[e]=t,--o)||s(r)},A)}),--o||s(r)});return n.e&&A(n.v),e.promise},race:function(t){var e=this,n=N(e),r=n.reject,a=P(function(){y(t,!1,function(t){e.resolve(t).then(n.resolve,r)})});return a.e&&r(a.v),n.promise}})},{114:114,115:115,117:117,123:123,124:124,127:127,136:136,148:148,152:152,33:33,37:37,47:47,52:52,54:54,62:62,68:68,70:70,81:81,86:86,89:89,95:95,96:96}],233:[function(t,e,n){var r=t(62),a=t(33),o=t(38),i=(t(70).Reflect||{}).apply,s=Function.apply;r(r.S+r.F*!t(64)(function(){i(function(){})}),\"Reflect\",{apply:function(t,e,n){t=a(t),n=o(n);return i?i(t,e,n):s.call(t,e,n)}})},{33:33,38:38,62:62,64:64,70:70}],234:[function(t,e,n){var r=t(62),a=t(98),o=t(33),i=t(38),s=t(81),A=t(64),l=t(46),c=(t(70).Reflect||{}).construct,u=A(function(){function t(){}return!(c(function(){},[],t)instanceof t)}),p=!A(function(){c(function(){})});r(r.S+r.F*(u||p),\"Reflect\",{construct:function(t,e){o(t),i(e);var n=arguments.length<3?t:o(arguments[2]);if(p&&!u)return c(t,e,n);if(t==n){switch(e.length){case 0:return new t;case 1:return new t(e[0]);case 2:return new t(e[0],e[1]);case 3:return new t(e[0],e[1],e[2]);case 4:return new t(e[0],e[1],e[2],e[3])}var r=[null];return r.push.apply(r,e),new(l.apply(t,r))}r=n.prototype,n=a(s(r)?r:Object.prototype),r=Function.apply.call(t,n,e);return s(r)?r:n}})},{33:33,38:38,46:46,62:62,64:64,70:70,81:81,98:98}],235:[function(t,e,n){var r=t(99),a=t(62),o=t(38),i=t(143);a(a.S+a.F*t(64)(function(){Reflect.defineProperty(r.f({},1,{value:1}),1,{value:2})}),\"Reflect\",{defineProperty:function(t,e,n){o(t),e=i(e,!0),o(n);try{return r.f(t,e,n),!0}catch(t){return!1}}})},{143:143,38:38,62:62,64:64,99:99}],236:[function(t,e,n){var r=t(62),a=t(101).f,o=t(38);r(r.S,\"Reflect\",{deleteProperty:function(t,e){var n=a(o(t),e);return!(n&&!n.configurable)&&delete t[e]}})},{101:101,38:38,62:62}],237:[function(t,e,n){\"use strict\";function r(t){this._t=o(t),this._i=0;var e,n=this._k=[];for(e in t)n.push(e)}var a=t(62),o=t(38);t(84)(r,\"Object\",function(){var t,e=this._k;do{if(this._i>=e.length)return{value:void 0,done:!0}}while(!((t=e[this._i++])in this._t));return{value:t,done:!1}}),a(a.S,\"Reflect\",{enumerate:function(t){return new r(t)}})},{38:38,62:62,84:84}],238:[function(t,e,n){var r=t(101),a=t(62),o=t(38);a(a.S,\"Reflect\",{getOwnPropertyDescriptor:function(t,e){return r.f(o(t),e)}})},{101:101,38:38,62:62}],239:[function(t,e,n){var r=t(62),a=t(105),o=t(38);r(r.S,\"Reflect\",{getPrototypeOf:function(t){return a(o(t))}})},{105:105,38:38,62:62}],240:[function(t,e,n){var o=t(101),i=t(105),s=t(71),r=t(62),A=t(81),l=t(38);r(r.S,\"Reflect\",{get:function t(e,n){var r,a=arguments.length<3?e:arguments[2];return l(e)===a?e[n]:(r=o.f(e,n))?s(r,\"value\")?r.value:void 0!==r.get?r.get.call(a):void 0:A(r=i(e))?t(r,n,a):void 0}})},{101:101,105:105,38:38,62:62,71:71,81:81}],241:[function(t,e,n){t=t(62);t(t.S,\"Reflect\",{has:function(t,e){return e in t}})},{62:62}],242:[function(t,e,n){var r=t(62),a=t(38),o=Object.isExtensible;r(r.S,\"Reflect\",{isExtensible:function(t){return a(t),!o||o(t)}})},{38:38,62:62}],243:[function(t,e,n){var r=t(62);r(r.S,\"Reflect\",{ownKeys:t(111)})},{111:111,62:62}],244:[function(t,e,n){var r=t(62),a=t(38),o=Object.preventExtensions;r(r.S,\"Reflect\",{preventExtensions:function(t){a(t);try{return o&&o(t),!0}catch(t){return!1}}})},{38:38,62:62}],245:[function(t,e,n){var r=t(62),a=t(122);a&&r(r.S,\"Reflect\",{setPrototypeOf:function(t,e){a.check(t,e);try{return a.set(t,e),!0}catch(t){return!1}}})},{122:122,62:62}],246:[function(t,e,n){var s=t(99),A=t(101),l=t(105),c=t(71),r=t(62),u=t(116),p=t(38),f=t(81);r(r.S,\"Reflect\",{set:function t(e,n,r){var a,o=arguments.length<4?e:arguments[3],i=A.f(p(e),n);if(!i){if(f(a=l(e)))return t(a,n,r,o);i=u(0)}if(c(i,\"value\")){if(!1===i.writable||!f(o))return!1;if(a=A.f(o,n)){if(a.get||a.set||!1===a.writable)return!1;a.value=r,s.f(o,n,a)}else s.f(o,n,u(0,r));return!0}return void 0!==i.set&&(i.set.call(o,r),!0)}})},{101:101,105:105,116:116,38:38,62:62,71:71,81:81,99:99}],247:[function(t,e,n){var r=t(70),o=t(75),a=t(99).f,i=t(103).f,s=t(82),A=t(66),l=d=r.RegExp,c=d.prototype,u=/a/g,p=/a/g,f=new d(u)!==u;if(t(58)&&(!f||t(64)(function(){return p[t(152)(\"match\")]=!1,d(u)!=u||d(p)==p||\"/a/i\"!=d(u,\"i\")}))){for(var d=function(t,e){var n=this instanceof d,r=s(t),a=void 0===e;return!n&&r&&t.constructor===d&&a?t:o(f?new l(r&&!a?t.source:t,e):l((r=t instanceof d)?t.source:t,r&&a?A.call(t):e),n?this:c,d)},h=i(l),g=0;h.length>g;)!function(e){e in d||a(d,e,{configurable:!0,get:function(){return l[e]},set:function(t){l[e]=t}})}(h[g++]);(c.constructor=d).prototype=c,t(118)(r,\"RegExp\",d)}t(123)(\"RegExp\")},{103:103,118:118,123:123,152:152,58:58,64:64,66:66,70:70,75:75,82:82,99:99}],248:[function(t,e,n){\"use strict\";var r=t(120);t(62)({target:\"RegExp\",proto:!0,forced:r!==/./.exec},{exec:r})},{120:120,62:62}],249:[function(t,e,n){t(58)&&\"g\"!=/./g.flags&&t(99).f(RegExp.prototype,\"flags\",{configurable:!0,get:t(66)})},{58:58,66:66,99:99}],250:[function(t,e,n){\"use strict\";var c=t(38),u=t(141),p=t(36),f=t(119);t(65)(\"match\",1,function(r,a,A,l){return[function(t){var e=r(this),n=null==t?void 0:t[a];return void 0!==n?n.call(t,e):new RegExp(t)[a](String(e))},function(t){var e=l(A,t,this);if(e.done)return e.value;var n=c(t),r=String(this);if(!n.global)return f(n,r);for(var a=n.unicode,o=[],i=n.lastIndex=0;null!==(s=f(n,r));){var s=String(s[0]);\"\"===(o[i]=s)&&(n.lastIndex=p(r,u(n.lastIndex),a)),i++}return 0===i?null:o}]})},{119:119,141:141,36:36,38:38,65:65}],251:[function(t,e,n){\"use strict\";var x=t(38),C=t(142),P=t(141),S=t(139),L=t(36),E=t(119),T=Math.max,B=Math.min,D=Math.floor,_=/\\$([$&`']|\\d\\d?|<[^>]*>)/g,k=/\\$([$&`']|\\d\\d?)/g;t(65)(\"replace\",2,function(a,o,b,w){return[function(t,e){var n=a(this),r=null==t?void 0:t[o];return void 0!==r?r.call(t,n,e):b.call(String(n),t,e)},function(t,e){var n=w(b,t,this,e);if(n.done)return n.value;var r,a=x(t),o=String(this),i=\"function\"==typeof e,s=(i||(e=String(e)),a.global);s&&(r=a.unicode,a.lastIndex=0);for(var A=[];;){var l=E(a,o);if(null===l)break;if(A.push(l),!s)break;\"\"===String(l[0])&&(a.lastIndex=L(o,P(a.lastIndex),r))}for(var c,u=\"\",p=0,f=0;f>>0,c=new RegExp(t.source,s+\"g\");(r=p.call(c,n))&&!(A<(a=c[P])&&(i.push(n.slice(A,r.index)),1>>0;if(0==s)return[];if(0===r.length)return null===w(i,r)?[r]:[];for(var A=0,l=0,c=[];l>10),e%1024+56320))}return n.join(\"\")}})},{137:137,62:62}],266:[function(t,e,n){\"use strict\";var r=t(62),a=t(130);r(r.P+r.F*t(63)(\"includes\"),\"String\",{includes:function(t){return!!~a(this,t,\"includes\").indexOf(t,1=t.length?{value:void 0,done:!0}:(t=r(t,e),this._i+=t.length,{value:t,done:!1})})},{129:129,85:85}],269:[function(t,e,n){\"use strict\";t(131)(\"link\",function(e){return function(t){return e(this,\"a\",\"href\",t)}})},{131:131}],270:[function(t,e,n){var r=t(62),i=t(140),s=t(141);r(r.S,\"String\",{raw:function(t){for(var e=i(t.raw),n=s(e.length),r=arguments.length,a=[],o=0;oa;)l(B,e=n[a++])||e==E||e==z||r.push(e);return r}function i(t){for(var e,n=t===_,r=Z(n?D:m(t)),a=[],o=0;r.length>o;)!l(B,e=r[o++])||n&&!l(_,e)||a.push(B[e]);return a}function s(t,e,n){return t===_&&s(D,e,n),g(t),e=v(e,!0),g(n),(l(B,e)?(n.enumerable?(l(t,E)&&t[E][e]&&(t[E][e]=!1),n=b(n,{enumerable:y(0,!1)})):(l(t,E)||x(t,E,y(1,{})),t[E][e]=!0),F):x)(t,e,n)}var A=t(70),l=t(71),c=t(58),u=t(62),M=t(118),z=t(94).KEY,p=t(64),f=t(126),d=t(124),U=t(147),h=t(152),j=t(151),G=t(150),Q=t(61),W=t(79),g=t(38),Y=t(81),X=t(142),m=t(140),v=t(143),y=t(116),b=t(98),H=t(102),V=t(101),w=t(104),q=t(99),J=t(107),K=V.f,x=q.f,Z=H.f,C=A.Symbol,P=A.JSON,S=P&&P.stringify,L=\"prototype\",E=h(\"_hidden\"),$=h(\"toPrimitive\"),tt={}.propertyIsEnumerable,T=f(\"symbol-registry\"),B=f(\"symbols\"),D=f(\"op-symbols\"),_=Object[L],f=\"function\"==typeof C&&!!w.f,k=A.QObject,N=!k||!k[L]||!k[L].findChild,F=c&&p(function(){return 7!=b(x({},\"a\",{get:function(){return x(this,\"a\",{value:7}).a}})).a})?function(t,e,n){var r=K(_,e);r&&delete _[e],x(t,e,n),r&&t!==_&&x(_,e,r)}:x,I=f&&\"symbol\"==typeof C.iterator?function(t){return\"symbol\"==typeof t}:function(t){return t instanceof C};f||(M((C=function(){if(this instanceof C)throw TypeError(\"Symbol is not a constructor!\");var e=U(0nt;)h(et[nt++]);for(var rt=J(h.store),at=0;rt.length>at;)G(rt[at++]);u(u.S+u.F*!f,\"Symbol\",{for:function(t){return l(T,t+=\"\")?T[t]:T[t]=C(t)},keyFor:function(t){if(!I(t))throw TypeError(t+\" is not a symbol!\");for(var e in T)if(T[e]===t)return e},useSetter:function(){N=!0},useSimple:function(){N=!1}}),u(u.S+u.F*!f,\"Object\",{create:function(t,e){return void 0===e?b(t):n(b(t),e)},defineProperty:s,defineProperties:n,getOwnPropertyDescriptor:a,getOwnPropertyNames:o,getOwnPropertySymbols:i});k=p(function(){w.f(1)});u(u.S+u.F*k,\"Object\",{getOwnPropertySymbols:function(t){return w.f(X(t))}}),P&&u(u.S+u.F*(!f||p(function(){var t=C();return\"[null]\"!=S([t])||\"{}\"!=S({a:t})||\"{}\"!=S(Object(t))})),\"JSON\",{stringify:function(t){for(var e,n,r=[t],a=1;as;)void 0!==(n=a(r,e=o[s++]))&&u(i,e,n);return i}})},{101:101,111:111,140:140,53:53,62:62}],296:[function(t,e,n){var r=t(62),a=t(110)(!1);r(r.S,\"Object\",{values:function(t){return a(t)}})},{110:110,62:62}],297:[function(t,e,n){\"use strict\";var r=t(62),a=t(52),o=t(70),i=t(127),s=t(115);r(r.P+r.R,\"Promise\",{finally:function(e){var n=i(this,a.Promise||o.Promise),t=\"function\"==typeof e;return this.then(t?function(t){return s(n,e()).then(function(){return t})}:e,t?function(t){return s(n,e()).then(function(){throw t})}:e)}})},{115:115,127:127,52:52,62:62,70:70}],298:[function(t,e,n){\"use strict\";var r=t(62),a=t(132),t=t(148),t=/Version\\/10\\.\\d+(\\.\\d+)?( Mobile\\/\\w+)? Safari\\//.test(t);r(r.P+r.F*t,\"String\",{padEnd:function(t){return a(this,t,1s[0]&&e[1]/g,\">\").replace(/\"/g,\""\").replace(/'/g,\"'\")}function I(t){return\"number\"==typeof t&&100\").concat(e,\"\"):\"\")}function z(t){var e=\"solid\",n=\"\",r=\"\",a=\"\";return t&&(\"string\"==typeof t?n=t:(t.type&&(e=t.type),t.color&&(n=t.color),t.alpha&&(r+='')),t.transparency&&(r+=''))),a+=\"solid\"===e?\"\".concat(M(n,r),\"\"):\"\"),a}function g(t){return t._rels.length+t._relsChart.length+t._relsMedia.length+1}function vt(t){if(t&&\"object\"==typeof t)return\"outer\"!==t.type&&\"inner\"!==t.type&&\"none\"!==t.type&&(console.warn(\"Warning: shadow.type options are `outer`, `inner` or `none`.\"),t.type=\"outer\"),t.angle&&((isNaN(Number(t.angle))||t.angle<0||359n?n=R(t.options.margin[0]):null!=f&&f.margin&&f.margin[0]&&R(f.margin[0])>n&&(n=R(f.margin[0])),null!=(e=t.options)&&e.margin&&t.options.margin[2]&&R(t.options.margin[2])>r?r=R(t.options.margin[2]):null!=f&&f.margin&&f.margin[2]&&R(f.margin[2])>r&&(r=R(f.margin[2]))):(null!=(e=t.options)&&e.margin&&t.options.margin[0]&&I(t.options.margin[0])>n?n=I(t.options.margin[0]):null!=f&&f.margin&&f.margin[0]&&I(f.margin[0])>n&&(n=I(f.margin[0])),null!=(e=t.options)&&e.margin&&t.options.margin[2]&&I(t.options.margin[2])>r?r=I(t.options.margin[2]):null!=f&&f.margin&&f.margin[2]&&I(f.margin[2])>r&&(r=I(f.margin[2])))}),h(),u+=n+r,f.verbose&&0===e&&console.log(\"| SLIDE [\".concat(d.length,\"]: emuSlideTabH ...... = \").concat((c/k).toFixed(1),\" \")),t.forEach(function(n,r){var t,a,e,o,i,s,A,l,c={_type:_.tablecell,_lines:null,_lineHeight:I((null!=(c=n.options)&&c.fontSize?n.options.fontSize:f.fontSize||P)*(H+(f.autoPageLineWeight||0))/100),text:[],options:n.options},u=(c.options.rowspan&&(c._lineHeight=0),c.options.autoPageCharWeight=f.autoPageCharWeight||null,f.colW[r]);n.options.colspan&&Array.isArray(f.colW)&&(u=f.colW.filter(function(t,e){return r<=e&&e \".concat(JSON.stringify(l))),s.push(l),l=[]),0o&&(i.push(e),e=[],n=\"\"),e.push(t),n+=t.text.toString()}),0=i&&(i=t._lineHeight)}),c maxH) => \".concat((u/k).toFixed(2),\" + \").concat((A._lineHeight/k).toFixed(2),\" > \").concat(c/k)),console.log(\"|-----------------------------------------------------------------------|\\n\\n\")),0n&&(n=t._lineHeight)}),v.rows.push(e),u+=n}),l=a[o]),A._lines.shift());Array.isArray(l.text)&&(A?l.text=l.text.concat(A):0===l.text.length&&(l.text=l.text.concat({_type:_.tablecell,text:\"\"}))),o===p.length-1&&(u+=i),o=o \\n'),i.file(\"_rels/.rels\",'\\n'),i.file(\"docProps/app.xml\",'Microsoft Macintosh Excel0falseWorksheets1Sheet1falsefalsefalse16.0300\\n'),i.file(\"docProps/core.xml\",'PptxGenJSPptxGenJS'+(new Date).toISOString()+''+(new Date).toISOString()+\"\"),i.file(\"xl/_rels/workbook.xml.rels\",''),i.file(\"xl/styles.xml\",'\\n'),i.file(\"xl/theme/theme1.xml\",''),i.file(\"xl/workbook.xml\",'\\n'),i.file(\"xl/worksheets/_rels/sheet1.xml.rels\",'\\n'),''),c=(m.opts._type===b.BUBBLE||m.opts._type===b.BUBBLE3D?l+=''):m.opts._type===b.SCATTER?l+=''):l=A?(r=g.length,g[0].labels.forEach(function(t){return r+=t.filter(function(t){return t&&\"\"!==t}).length}),l+'')+\"\"):(t=g.length+g[0].labels.length*g[0].labels[0].length+g[0].labels.length,a=g.length+g[0].labels.length*g[0].labels[0].length+1,l+'')+''),m.opts._type===b.BUBBLE||m.opts._type===b.BUBBLE3D?g.forEach(function(t,e){0===e?l+=\"X-Axis\":l=(l+=\"\".concat(F(t.name||\"Y-Axis\".concat(e)),\"\"))+\"\".concat(F(\"Size\".concat(e)),\"\")}):g.forEach(function(t){l+=\"\".concat(F((t.name||\" \").replace(\"X-Axis\",\"X-Values\")),\"\")}),m.opts._type!==b.BUBBLE&&m.opts._type!==b.BUBBLE3D&&m.opts._type!==b.SCATTER&&g[0].labels.slice().reverse().forEach(function(t){t.filter(function(t){return t&&\"\"!==t}).forEach(function(t){l+=\"\".concat(F(t),\"\")})}),l+=\"\\n\",i.file(\"xl/sharedStrings.xml\",l),''),u=(m.opts._type===b.BUBBLE||m.opts._type===b.BUBBLE3D?(c=(c+=''))+''),o=1,g.forEach(function(t,e){0===e?c+=''):(c+=''),o++,c+=''))})):m.opts._type===b.SCATTER?(c=(c+='
'))+''),g.forEach(function(t,e){c+='')})):(c=(c+='
'))+''),g[0].labels.forEach(function(t,e){c+='')}),g.forEach(function(t,e){c+='')})),c=(c+=\"\")+''+\"
\",i.file(\"xl/tables/table1.xml\",c),'');if(u+='',m.opts._type===b.BUBBLE||m.opts._type===b.BUBBLE3D?u+=''):m.opts._type===b.SCATTER?u+=''):u+=''),u=u+''+'',m.opts._type===b.BUBBLE||m.opts._type===b.BUBBLE3D){for(var u=(u+=\"\")+'')+'0',p=1;p').concat(p,\"\");u+=\"\",g[0].values.forEach(function(t,e){u=(u+=''))+'').concat(t,\"\");for(var n=2,r=1;r').concat(g[r].values[e]||\"\",\"\"))+'').concat(g[r].sizes[e]||\"\",\"\"),n++;u+=\"\"})}else if(m.opts._type===b.SCATTER){u=(u+=\"\")+'');for(p=0;p').concat(p,\"\");u+=\"\",g[0].values.forEach(function(t,e){u=(u+=''))+'').concat(t,\"\");for(var n=1;n').concat(g[n].values[e]||0===g[n].values[e]?g[n].values[e]:\"\",\"\");u+=\"\"})}else if(u+=\"\",A){u+='');for(p=0;p0');for(p=g[0].labels.length-1;p').concat(p,\"\");u+=\"\";for(var f=g.length,d=g[0].labels[0].length,h=g[0].labels.length,p=0;p');var r=f,a=g[0].labels.slice().reverse();a.forEach(function(t,e){t[n]&&(t=0===e?1:a[e-1].filter(function(t){return t&&\"\"!==t}).length,r+=t,u+='').concat(r,\"\"))});for(var t=0;t').concat(g[t].values[n]||0,\"\");u+=\"\"}(p)}else{u+=''),g[0].labels.forEach(function(t,e){u+='0')});for(var p=0;p').concat(p+1,\"\");u+=\"\",g[0].labels[0].forEach(function(t,e){u+='');for(var n=g[0].labels.length-1;0<=n;n--)u=(u+=''))+\"\".concat(g.length+e+1,\"\")+\"\";for(var r=0;r').concat(g[r].values[e]||\"\",\"\");u+=\"\"})}u+='\\n',i.file(\"xl/worksheets/sheet1.xml\",u),i.generateAsync({type:\"base64\"}).then(function(t){v.file(\"ppt/embeddings/Microsoft_Excel_Worksheet\".concat(m.globalId,\".xlsx\"),t,{base64:!0}),v.file(\"ppt/charts/_rels/\"+m.fileName+\".rels\",''+'')+\"\"),v.file(\"ppt/charts/\".concat(m.fileName),function(a){var t,o='',i=!1;o=(o+='')+'')+\"\",a.opts.showTitle?o=o+_t({title:a.opts.title||\"Chart Title\",color:a.opts.titleColor,fontFace:a.opts.titleFontFace,fontSize:a.opts.titleFontSize||et,titleAlign:a.opts.titleAlign,titleBold:a.opts.titleBold,titlePos:a.opts.titlePos,titleRotate:a.opts.titleRotate},a.opts.x,a.opts.y)+'':o+='';a.opts._type===b.BAR3D&&(o+=''));o+=\"\",a.opts.layout?o=(o=(o=(o=(o=(o=(o=(o+=\"\")+' ')+' ')+' ')+' ')+' ')+' ')+\" \":o+=\"\";Array.isArray(a.opts._type)?a.opts._type.forEach(function(t){var e=y(y({},a.opts),t.options),n=e.secondaryValAxis?st:S,r=e.secondaryCatAxis?lt:At;i=i||e.secondaryValAxis,o+=Tt(t.type,t.data,e,n,r)}):o+=Tt(a.opts._type,a.data,a.opts,S,At);if(a.opts._type!==b.PIE&&a.opts._type!==b.DOUGHNUT){if(a.opts.valAxes&&1 ')+' ')+' ')+' ')+(\"none\"!==e.serGridLine.style?kt(e.serGridLine):\"\"),e.showSerAxisTitle&&(r+=_t({color:e.serAxisTitleColor,fontFace:e.serAxisTitleFontFace,fontSize:e.serAxisTitleFontSize,titleRotate:e.serAxisTitleRotate,title:e.serAxisTitle||\"Axis Title\"}));r=(r=(r=(r=(r=(r=(r=(r=(r=(r=(r=(r=(r=(r=(r=(r+=' '))+' ')+' '))+' ')+(e.serAxisLineShow?\"\".concat(M(e.serAxisLineColor||x.color),\"\"):\"\")+' ')+\" \")+\" \")+\" \")+\" \")+' '))+\" \".concat(M(e.serAxisLabelColor||C),\"\"))+' '))+\" \")+' ')+\" \")+' ',e.serAxisLabelFrequency&&(r+=' ');e.serLabelFormatCode&&([\"serAxisBaseTimeUnit\",\"serAxisMajorTimeUnit\",\"serAxisMinorTimeUnit\"].forEach(function(t){!e[t]||\"string\"==typeof e[t]&&[\"days\",\"months\",\"years\"].includes(t.toLowerCase())||(console.warn('\"'.concat(t,\"\\\" must be one of: 'days','months','years' !\")),e[t]=null)}),e.serAxisBaseTimeUnit&&(r+=' ')),e.serAxisMajorTimeUnit&&(r+=' ')),e.serAxisMinorTimeUnit&&(r+=' ')),e.serAxisMajorUnit&&(r+=' ')),e.serAxisMinorUnit)&&(r+=' '));return r+=\"\"}(a.opts,ct,S))),null!=(t=a.opts)&&t.catAxes&&null!=(t=a.opts)&&t.catAxes[1]&&(o+=Bt(y(y({},a.opts),a.opts.catAxes[1]),lt,st))}a.opts.showDataTable&&(o=(o=(o=(o=(o=(o=(o=(o=(o=(o=(o=(o=(o=(o=(o=(o+=\"\")+' '))+' '))+' '))+' '))+\" \")+' ')+\" \")+' ')+' ')+' '))+' ')+' ')+\" \")+' ')+\" \");o=(o=(o=(o+=\" \")+(null!=(t=a.opts.plotArea.fill)&&t.color?z(a.opts.plotArea.fill):\"\"))+(a.opts.plotArea.border?'').concat(z(a.opts.plotArea.border.color),\"\"):\"\")+\" \")+\" \",a.opts.showLegend&&(o=(o+=\"\")+'',(a.opts.legendFontFace||a.opts.legendFontSize||a.opts.legendColor)&&(o=(o=(o=(o+=\"\")+\" \")+\" \")+(a.opts.legendFontSize?''):\"\"),a.opts.legendColor&&(o+=z(a.opts.legendColor)),a.opts.legendFontFace&&(o+=''),a.opts.legendFontFace&&(o+=''),o=(o=(o+=\" \")+' ')+\" \"),o+=\"\");o=(o+=' ')+' ',a.opts._type===b.SCATTER&&(o+='');return o=(o=(o=(o=(o+=\"\")+(null!=(t=a.opts.chartArea.fill)&&t.color?z(a.opts.chartArea.fill):\"\"))+(a.opts.chartArea.border?'').concat(z(a.opts.chartArea.border.color),\"\"):\"\"))+\" \")+''}(m)),e(\"\")}).catch(function(t){n(t)})})];case 1:return[2,t.sent()]}})})}function Tt(r,a,o,t,e){var i=-1,s=1,n=null,A=\"\";switch(r){case b.AREA:case b.BAR:case b.BAR3D:case b.LINE:case b.RADAR:A+=\"\"),r===b.AREA&&\"stacked\"===o.barGrouping&&(A+=''),r!==b.BAR&&r!==b.BAR3D||(A=(A+='')+''),r===b.RADAR&&(A+=''),A+='',a.forEach(function(t){i++,A=(A=(A=(A+=\"\")+' ')+\" \")+\" Sheet1!$\"+L(t._dataIndex+t.labels.length+1)+\"$1\")+' '+F(t.name)+\" \";var e=o.chartColors?o.chartColors[i%o.chartColors.length]:null;A+=\" \",\"transparent\"===e?A+=\"\":o.chartColorsOpacity?A+=\"\"+M(e,''))+\"\":A+=\"\"+M(e)+\"\",r===b.LINE||r===b.RADAR?0===o.lineSize?A+=\"\":A=(A+='').concat(M(e),\"\"))+('':o.dataBorder&&(A+='').concat(M(o.dataBorder.color),'')),A=A+v(o.shadow,c)+' ',r!==b.RADAR&&(A=(A+=\"\")+''),o.dataLabelBkgrdColors&&(A+=\"\".concat(M(e),\"\")),A=(A=(A=(A+=\"\")+''))+\"\".concat(M(o.dataLabelColor||C),\"\"))+'')+\"\",o.dataLabelPosition&&(A+='')),A=(A=(A=(A+='')+''))+''))+'')+\"\"),r!==b.LINE&&r!==b.RADAR||(A=(A+=\"\")+' ',o.lineDataSymbolSize&&(A+='')),A=(A=(A+=\" \")+\" \".concat(M(o.chartColors[t._dataIndex+1>o.chartColors.length?Math.floor(Math.random()*o.chartColors.length):t._dataIndex]),\"\"))+' ').concat(M(o.lineDataSymbolLineColor||e),'')+\" \"),r!==b.BAR&&r!==b.BAR3D||1!==a.length||!(o.chartColors&&o.chartColors!==pt&&1\")+' ')+' ',0===o.lineSize?A+=\"\":A=r===b.BAR?(A+=\"\")+' ':(A+=\" \")+' ',A=A+v(o.shadow,c)+\" \"}),A+=\"\",o.catLabelFormatCode?(A=(A=(A=(A+=\" \")+\" Sheet1!$A$2:$A$\".concat(t.labels[0].length+1,\"\")+\" \")+\" \"+(o.catLabelFormatCode||\"General\")+\"\")+' '),t.labels[0].forEach(function(t,e){return A+='').concat(F(t),\"\")}),A+=\" \"):(A=(A=(A+=\" \")+\" Sheet1!$A$2:$\".concat(L(t.labels.length),\"$\").concat(t.labels[0].length+1,\"\")+\" \")+' '),t.labels.forEach(function(t){A+=\"\",t.forEach(function(t,e){return A+='').concat(F(t),\"\")}),A+=\"\"}),A+=\" \"),A=(A=(A=(A=A+\"\"+\" \")+\"Sheet1!$\".concat(L(t._dataIndex+t.labels.length+1),\"$2:$\").concat(L(t._dataIndex+t.labels.length+1),\"$\").concat(t.labels[0].length+1,\"\")+\" \")+\" \"+(o.valLabelFormatCode||o.dataTableFormatCode||\"General\")+\"\")+' '),t.values.forEach(function(t,e){return A+='').concat(t||0===t?t:\"\",\"\")}),A+=\" \",r===b.LINE&&(A+=''),A+=\"\"}),A=(A=(A=(A=(A+=\" \")+' ')+\" \")+' '))+\" \"+M(o.dataLabelColor||C)+\"\")+' ',o.dataLabelPosition&&(A+=' '),A=(A=(A=(A+=' ')+' ')+' ')+' ')+\" \",r===b.BAR?A=(A+=' '))+' '):r===b.BAR3D?A=(A=(A+=' '))+' '))+(' ':r===b.LINE&&(A+=' '),A=(A+=''))+\"\");break;case b.SCATTER:A=(A+=\"\")+''+'',i=-1,a.filter(function(t,e){return 0\")+' '))+' ')+\" \")+\" Sheet1!$\".concat(L(t+2),\"$1\"))+' '+F(n.name)+\" \";var r,e=o.chartColors[i%o.chartColors.length];\"transparent\"===e?A+=\"\":o.chartColorsOpacity?A+=\"\"+M(e,'')+\"\":A+=\"\"+M(e)+\"\",0===o.lineSize?A+=\"\":A=(A+='').concat(M(e),\"\"))+''),A=(A=(A+=v(o.shadow,c))+\" \"+\"\")+' ',o.lineDataSymbolSize&&(A+='')),A=(A=(A+=\"\")+\"\".concat(M(o.chartColors[t+1>o.chartColors.length?Math.floor(Math.random()*o.chartColors.length):t]),\"\"))+'').concat(M(o.lineDataSymbolLineColor||o.chartColors[i%o.chartColors.length]),'')+\"\",o.showLabel&&(r=ht(\"-xxxx-xxxx-xxxx-xxxxxxxxxxxx\"),!n.labels[0]||\"custom\"!==o.dataLabelFormatScatter&&\"customXY\"!==o.dataLabelFormatScatter||(A+=\"\",n.labels[0].forEach(function(t,e){\"custom\"!==o.dataLabelFormatScatter&&\"customXY\"!==o.dataLabelFormatScatter||(A=(A=(A=(A+=\" \")+' ')+\" \")+' ')+\" \"+F(t)+\" \",A=(\"customXY\"!==o.dataLabelFormatScatter||/^ *$/.test(t)?A:(A=(A=(A=(A=(A=(A=(A=(A=(A=(A+=\" \")+' ( ')+' ')+' ')+\" [\"+F(n.name)+\" \")+' , ')+' ')+' ')+\" [\"+F(n.name)+\"] \")+' ) ')+' ')+\" \",o.dataLabelPosition&&(A+=' '),A=(A+=' ')+' ')+\" \")}),A+=\"\"),\"XY\"===o.dataLabelFormatScatter)&&(A+=' ',o.dataLabelPosition&&(A+=' '),A=(A=(A=(A+=' ')+' '))+' '))+' ')+' '),1===a.length&&o.chartColors!==pt&&n.values.forEach(function(t,e){t=t<0?o.invertedColors||o.chartColors||pt:o.chartColors||[];A=(A+=\" \")+' ')+' ',0===o.lineSize?A+=\"\":A=(A+=\"\")+' ',A=A+v(o.shadow,c)+\" \"}),A=(A=(A+=\" \")+\" Sheet1!$A$2:$A$\".concat(a[0].values.length+1,\"\")+\" General\")+' '),a[0].values.forEach(function(t,e){A+='').concat(t||0===t?t:\"\",\"\")}),A=(A=(A+=\" \")+\" Sheet1!$\".concat(L(t+2),\"$2:$\").concat(L(t+2),\"$\").concat(a[0].values.length+1,\"\")+\" General\")+' '),a[0].values.forEach(function(t,e){A+='').concat(n.values[e]||0===n.values[e]?n.values[e]:\"\",\"\")}),A=(A+=\" \")+''}),A=(A=(A=(A=(A+=\" \")+' ')+\" \")+' '))+\" \"+M(o.dataLabelColor||C)+\"\")+' ',o.dataLabelPosition&&(A+=' '),A=(A=(A+=' ')+' ')+' ',A=(A+=''))+(\"\");break;case b.BUBBLE:case b.BUBBLE3D:A=A+\"\"+'',i=-1,a.filter(function(t,e){return 0\")+' '))+' ')+\" \")+\" Sheet1!$\"+L(s+1)+\"$1\")+' '+F(n.name)+\" \";t=o.chartColors[i%o.chartColors.length];\"transparent\"===t?A+=\"\":o.chartColorsOpacity?A+=\"\".concat(M(t,''),\"\"):A+=\"\"+M(t)+\"\",0===o.lineSize?A+=\"\":o.dataBorder?A+='').concat(M(o.dataBorder.color),''):A=(A+='').concat(M(t),\"\"))+''),A=A+v(o.shadow,c)+\"\",A=(A=(A+=\" \")+\" Sheet1!$A$2:$A$\".concat(a[0].values.length+1,\"\")+\" General\")+' '),a[0].values.forEach(function(t,e){A+='').concat(t||0===t?t:\"\",\"\")}),A=(A+=\" \")+\"Sheet1!$\".concat(L(s+1),\"$2:$\").concat(L(s+1),\"$\").concat(a[0].values.length+1,\"\"),s++,A=(A+=\" General\")+' '),a[0].values.forEach(function(t,e){A+='').concat(n.values[e]||0===n.values[e]?n.values[e]:\"\",\"\")}),A=(A+=\" \")+\"Sheet1!$\".concat(L(s+1),\"$2:$\").concat(L(s+1),\"$\").concat(n.sizes.length+1,\"\"),s++,A=(A+=\" General\")+' '),n.sizes.forEach(function(t,e){A+='').concat(t||\"\",\"\")}),A=(A+=\" \")+' '}),A=(A=(A=(A=(A+=\"\")+'')+\"\")+''))+\"\".concat(M(o.dataLabelColor||C),\"\"))+'')+\"\",o.dataLabelPosition&&(A+='')),A=(A=(A=(A=(A+='')+''))+'')+' ')+' ')+'')+\"\";break;case b.DOUGHNUT:case b.PIE:n=a[0],A=(A=(A=(A=(A=(A=(A=(A=(A=A+(\"\")+' ')+\"\"+' ')+' '+\" \")+\" \"+\" Sheet1!$B$1\")+\" \"+' ')+(' '+F(n.name)+\"\"))+\" \"+\" \")+\" \"+\" \")+' '+' ',o.dataNoEffects?A+=\"\":A+=v(o.shadow,c),A+=\" \",n.labels[0].forEach(function(t,e){A=(A=(A+=\"\")+' ')+' ')+\"\".concat(M(o.chartColors[e+1>o.chartColors.length?Math.floor(Math.random()*o.chartColors.length):e]),\"\"),o.dataBorder&&(A+='').concat(M(o.dataBorder.color),'')),A=A+v(o.shadow,c)+\" \"}),A+=\"\",n.labels[0].forEach(function(t,e){A=(A=(A=(A=(A=(A+=\"\")+' '))+' ')+\" \")+' '))+\" \"+M(o.dataLabelColor||C)+\"\")+' ')+\" \",r===b.PIE&&o.dataLabelPosition&&(A+='')),A=(A=(A=(A=(A+=' ')+' ')+' ')+' ')+' '}),A=(A=(A=(A=(A=(A=(A=(A=(A=(A=(A=(A=(A=(A=A+' ')+\" \")+\" \"+\" \")+\" \"+\" \")+' ')+' ')+\" \"+\" \")+\" \"+\" \")+(r===b.PIE?'':\"\"))+' '+' ')+' '+' ')+' '+' ')+' ')+\"\")+\"\"+\" \")+\" Sheet1!$A$2:$A$\".concat(n.labels[0].length+1,\"\")+\" \")+' '),n.labels[0].forEach(function(t,e){A+='').concat(F(t),\"\")}),A=(A=(A=(A=(A+=\" \")+\" \"+\"\")+\" \"+\" \")+\" Sheet1!$B$2:$B$\".concat(n.labels[0].length+1,\"\")+\" \")+' '),n.values.forEach(function(t,e){A+='').concat(t||0===t?t:\"\",\"\")}),A=(A=(A=A+\" \"+\" \")+\" \"+\" \")+' '),r===b.DOUGHNUT&&(A+='')),A+=\"\";break;default:A+=\"\"}return A}function Bt(e,t,n){var r=\"\";return e._type===b.SCATTER||e._type===b.BUBBLE||e._type===b.BUBBLE3D?r+=\"\":r+=\"\",r=(r+=' ')+\" \"+(''),!e.catAxisMaxVal&&0!==e.catAxisMaxVal||(r+='')),!e.catAxisMinVal&&0!==e.catAxisMinVal||(r+='')),r=(r=(r=r+\"\"+(' '))+(' '))+(\"none\"!==e.catGridLine.style?kt(e.catGridLine):\"\"),e.showCatAxisTitle&&(r+=_t({color:e.catAxisTitleColor,fontFace:e.catAxisTitleFontFace,fontSize:e.catAxisTitleFontSize,titleRotate:e.catAxisTitleRotate,title:e.catAxisTitle||\"Axis Title\"})),e._type===b.SCATTER||e._type===b.BUBBLE||e._type===b.BUBBLE3D?r+=' ':r+=' ',e._type===b.SCATTER?r+=' ':r=(r=(r+=' ')+' ')+' ',r=(r=(r=(r=(r=(r+=\" \")+' '))+(e.catAxisLineShow?\"\"+M(e.catAxisLineColor||x.color)+\"\":\"\"))+(' '))+\" \"+\" \")+\" \"+\" \",e.catAxisLabelRotate?r+=''):r+=\"\",r=(r=(r=(r=(r=(r=(r=(r=(r=(r=(r+=\" \")+\" \"+\" \")+' '))+(\" \"+M(e.catAxisLabelColor||C)+\"\"))+(' '))+\" \"+\" \")+(' ')+\" \")+\" \"+(' '))+\" '))+' '+' ')+' '),e.catAxisLabelFrequency&&(r+=' '),(e.catLabelFormatCode||e._type===b.SCATTER||e._type===b.BUBBLE||e._type===b.BUBBLE3D)&&(e.catLabelFormatCode&&([\"catAxisBaseTimeUnit\",\"catAxisMajorTimeUnit\",\"catAxisMinorTimeUnit\"].forEach(function(t){!e[t]||\"string\"==typeof e[t]&&[\"days\",\"months\",\"years\"].includes(e[t].toLowerCase())||(console.warn('\"'.concat(t,\"\\\" must be one of: 'days','months','years' !\")),e[t]=null)}),e.catAxisBaseTimeUnit&&(r+=''),e.catAxisMajorTimeUnit&&(r+=''),e.catAxisMinorTimeUnit)&&(r+=''),e.catAxisMajorUnit&&(r+='')),e.catAxisMinorUnit)&&(r+='')),e._type===b.SCATTER||e._type===b.BUBBLE||e._type===b.BUBBLE3D?r+=\"\":r+=\"\",r}function Dt(t,e){var n=e===S?\"col\"===t.barDir?\"l\":\"b\":\"col\"!==t.barDir?\"r\":\"t\",r=(e===st&&(n=\"r\"),e===S?At:lt),a=\"\",a=(a+=\"\")+(' ')+\" \";return t.valAxisLogScaleBase&&(a+='')),a+='',!t.valAxisMaxVal&&0!==t.valAxisMaxVal||(a+='')),!t.valAxisMinVal&&0!==t.valAxisMinVal||(a+='')),a=(a+=\" \")+' ')+(' '),\"none\"!==t.valGridLine.style&&(a+=kt(t.valGridLine)),t.showValAxisTitle&&(a+=_t({color:t.valAxisTitleColor,fontFace:t.valAxisTitleFontFace,fontSize:t.valAxisTitleFontSize,titleRotate:t.valAxisTitleRotate,title:t.valAxisTitle||\"Axis Title\"})),a+=''),t._type===b.SCATTER?a+=' ':a=(a=(a+=' ')+' ')+' ',a=(a=(a=(a=(a=(a=(a=(a=(a=(a=(a=(a=(a=(a+=\" \")+' '))+(t.valAxisLineShow?\"\"+M(t.valAxisLineColor||x.color)+\"\":\"\"))+(' '))+\" \"+\" \")+\" \"+\" \")+\" \")+\" \")+\" \"+\" \")+' '))+(\" \"+M(t.valAxisLabelColor||C)+\"\"))+(' '))+\" \"+\" \")+(' ')+\" \")+\" \"+(' '),\"number\"==typeof t.catAxisCrossesAt?a+=' '):\"string\"==typeof t.catAxisCrossesAt?a+=' ':a+=' ',a+=' ',t.valAxisMajorUnit&&(a+=' ')),t.valAxisDisplayUnit&&(a+='').concat(t.valAxisDisplayUnitLabel?\"\":\"\",\"\")),a+=\"\"}function _t(t,e,n){var r=\"left\"===t.titleAlign||\"right\"===t.titleAlign?''):\"\",a=t.titleRotate?''):\"\",o=t.fontSize?'sz=\"'.concat(Math.round(100*t.fontSize),'\"'):\"\",i=t.titleBold?1:0,s=\"\";return t.titlePos&&\"number\"==typeof t.titlePos.x&&\"number\"==typeof t.titlePos.y&&(1<=(e=0===(e=t.titlePos.x+e)?0:e*(e/5)/10)&&(e/=10),.1<=e&&(e/=10),1<=(n=0===(n=t.titlePos.y+n)?0:n*(n/5)/10)&&(n/=10),.1<=n&&(n/=10),s='')),\"\\n \\n \\n \".concat(a,\"\\n \\n \\n \").concat(r,\"\\n \\n ').concat(M(t.color||C),'\\n \\n \\n \\n \\n \\n ').concat(M(t.color||C),'\\n \\n \\n ').concat(F(t.title)||\"\",\"\\n \\n \\n \\n \\n \").concat(s,'\\n \\n ')}function L(t){t-=1;return t<=25?ut[t]:\"\".concat(ut[Math.floor(t/ut.length-1)]).concat(ut[t%ut.length])}function v(t,e){var n,r,a,o,i,s;return t?\"object\"!=typeof t?(console.warn(\"`shadow` options must be an object. Ex: `{shadow: {type:'none'}}`\"),\"\"):(n=\"\",t=(e=y(y({},e),t)).type||\"outer\",r=R(e.blur),a=R(e.offset),o=Math.round(6e4*e.angle),i=e.color,s=Math.round(1e5*e.opacity),e=e.rotateWithShape?1:0,(n=(n=(n+=\"'))+''))+''))+\"\")+\"\"):\"\"}function kt(t){var e=\"\";return(e+=\" \")+' ')+(' ')+(' ')+\" \"+\" \"+\"\"}function Nt(t){if(t&&\"flat\"!==t){if(\"square\"===t)return\"sq\";if(\"round\"===t)return\"rnd\";throw new Error(\"Invalid chart line cap: \".concat(t))}return\"flat\"}function Ft(t){var o=\"undefined\"!=typeof require&&\"undefined\"==typeof window?require(\"fs\"):null,i=\"undefined\"!=typeof require&&\"undefined\"==typeof window?require(\"https\"):null,e=[],s=t._relsMedia.filter(function(t){return\"online\"!==t.type&&!t.data&&(!t.path||t.path&&!t.path.includes(\"preencoded\"))}),n=[];return s.forEach(function(t){n.includes(t.path)?t.isDuplicate=!0:(t.isDuplicate=!1,n.push(t.path))}),s.filter(function(t){return!t.isDuplicate}).forEach(function(a){e.push(new Promise(function(n,r){var e;if(o&&0!==a.path.indexOf(\"http\"))try{var t=o.readFileSync(a.path);a.data=Buffer.from(t).toString(\"base64\"),s.filter(function(t){return t.isDuplicate&&t.path===a.path}).forEach(function(t){return t.data=a.data}),n(\"done\")}catch(t){a.data=h,s.filter(function(t){return t.isDuplicate&&t.path===a.path}).forEach(function(t){return t.data=a.data}),r(new Error('ERROR: Unable to read media: \"'.concat(a.path,'\"\\n').concat(String(t))))}else o&&i&&0===a.path.indexOf(\"http\")?i.get(a.path,function(t){var e=\"\";t.setEncoding(\"binary\"),t.on(\"data\",function(t){return e+=t}),t.on(\"end\",function(){a.data=Buffer.from(e,\"binary\").toString(\"base64\"),s.filter(function(t){return t.isDuplicate&&t.path===a.path}).forEach(function(t){return t.data=a.data}),n(\"done\")}),t.on(\"error\",function(t){a.data=h,s.filter(function(t){return t.isDuplicate&&t.path===a.path}).forEach(function(t){return t.data=a.data}),r(new Error(\"ERROR! Unable to load image (https.get): \".concat(a.path)))})}):((e=new XMLHttpRequest).onload=function(){var t=new FileReader;t.onloadend=function(){a.data=t.result,s.filter(function(t){return t.isDuplicate&&t.path===a.path}).forEach(function(t){return t.data=a.data}),a.isSvgPng?It(a).then(function(){n(\"done\")}).catch(function(t){r(t)}):n(\"done\")},t.readAsDataURL(e.response)},e.onerror=function(t){a.data=h,s.filter(function(t){return t.isDuplicate&&t.path===a.path}).forEach(function(t){return t.data=a.data}),r(new Error(\"ERROR! Unable to load image (xhr.onerror): \".concat(a.path)))},e.open(\"GET\",a.path),e.responseType=\"blob\",e.send())}))}),t._relsMedia.filter(function(t){return t.isSvgPng&&t.data}).forEach(function(t){o?(t.data=h,e.push(Promise.resolve().then(function(){return\"done\"}))):e.push(It(t))}),e}function It(a){return u(this,void 0,void 0,function(){return p(this,function(t){switch(t.label){case 0:return[4,new Promise(function(n,e){var r=new Image;r.onload=function(){r.width+r.height===0&&r.onerror(\"h/w=0\");var t=document.createElement(\"CANVAS\"),e=t.getContext(\"2d\");t.width=r.width,t.height=r.height,e.drawImage(r,0,0);try{a.data=t.toDataURL(a.type),n(\"done\")}catch(t){r.onerror(t)}},r.onerror=function(t){a.data=h,e(new Error(\"ERROR! Unable to load image (image.onerror): \".concat(a.path)))},r.src=\"string\"==typeof a.data?a.data:h})];case 1:return[2,t.sent()]}})})}var Rt={cover:function(t,e){var t=t.h/t.w,n=t')},contain:function(t,e){var t=t.h/t.w,n=t')},crop:function(t,e){var n=e.x,r=t.w-(e.x+e.w),a=e.y,e=t.h-(e.y+e.h),n=Math.round(n/t.w*1e5),r=Math.round(r/t.w*1e5),a=Math.round(a/t.h*1e5),e=Math.round(e/t.h*1e5);return'')}};function Ot(T){var t,B=T._name?'':\"\",D=1;return T._bkgdImgRid?B+=''):null!=(t=T.background)&&t.color?B+=\"\".concat(z(T.background),\"\"):!T.bkgd&&T._name&&T._name===nt&&(B+=''),B=(B=B+\"\"+'')+''+'',T._slideObjects.forEach(function(r,t){var e,n,A,a,o,i,s,l,c=0,u=0,p=N(\"75%\",\"X\",T._presLayout),f=0,d=\"\",h=null,g=0,m=0,v=null,y=null==(e=r.options)?void 0:e.sizing,b=null==(e=r.options)?void 0:e.rounding,w=(void 0!==T._slideLayout&&void 0!==T._slideLayout._slideObjects&&r.options&&r.options.placeholder&&(n=T._slideLayout._slideObjects.filter(function(t){return t.options.placeholder===r.options.placeholder})[0]),r.options=r.options||{},void 0!==r.options.x&&(c=N(r.options.x,\"X\",T._presLayout)),void 0!==r.options.y&&(u=N(r.options.y,\"Y\",T._presLayout)),p=void 0!==r.options.w?N(r.options.w,\"X\",T._presLayout):p),x=f=void 0!==r.options.h?N(r.options.h,\"Y\",T._presLayout):f;switch(n&&(!n.options.x&&0!==n.options.x||(c=N(n.options.x,\"X\",T._presLayout)),!n.options.y&&0!==n.options.y||(u=N(n.options.y,\"Y\",T._presLayout)),!n.options.w&&0!==n.options.w||(p=N(n.options.w,\"X\",T._presLayout)),!n.options.h&&0!==n.options.h||(f=N(n.options.h,\"Y\",T._presLayout))),r.options.flipH&&(d+=' flipH=\"1\"'),r.options.flipV&&(d+=' flipV=\"1\"'),r.options.rotate&&(d+=' rot=\"'.concat(O(r.options.rotate),'\"')),r._type){case _.table:if(h=r.arrTabRows,A=r.options,h[m=g=0].forEach(function(t){a=t.options||null,g+=null!==a&&a.colspan?Number(a.colspan):1}),v=''),v=(v+=' ')+'')+'',Array.isArray(A.colW)){v+=\"\";for(var C=0;C')}}else{m=A.colW||k,r.options.w&&!A.colW&&(m=Math.round((\"number\"==typeof r.options.w?r.options.w:1)/g)),v+=\"\";for(var S=0;S')}v+=\"\",h.forEach(function(a){for(var o,i,t=0;t'),t.forEach(function(t){var e,n,r,a,o,i={rowSpan:1<(null==(s=t.options)?void 0:s.rowspan)?t.options.rowspan:void 0,gridSpan:1<(null==(s=t.options)?void 0:s.colspan)?t.options.colspan:void 0,vMerge:t._vmerge?1:void 0,hMerge:t._hmerge?1:void 0},s=(s=Object.keys(i).map(function(t){return[t,i[t]]}).filter(function(t){return t[0],!!t[1]}).map(function(t){var e=t[0],t=t[1];return\"\".concat(String(e),'=\"').concat(String(t),'\"')}).join(\" \"))&&\" \"+s;t._hmerge||t._vmerge?v+=\"\"):(e=t.options||{},t.options=e,[\"align\",\"bold\",\"border\",\"color\",\"fill\",\"fontFace\",\"fontSize\",\"margin\",\"underline\",\"valign\"].forEach(function(t){A[t]&&!e[t]&&0!==e[t]&&(e[t]=A[t])}),n=e.valign?' anchor=\"'.concat(e.valign.replace(/^c$/i,\"ctr\").replace(/^m$/i,\"ctr\").replace(\"center\",\"ctr\").replace(\"middle\",\"ctr\").replace(\"top\",\"t\").replace(\"btm\",\"b\").replace(\"bottom\",\"b\"),'\"'):\"\",r=(r=(null!=(r=null==(r=t._optImp)?void 0:r.fill)&&r.color?t._optImp.fill.color:null!=(r=t._optImp)&&r.fill&&\"string\"==typeof t._optImp.fill?t._optImp.fill:\"\")||e.fill?e.fill:\"\")?z(r):\"\",a=0===e.margin||e.margin?e.margin:Z,o=\"\",o=1<=(a=Array.isArray(a)||\"number\"!=typeof a?a:[a,a,a,a])[0]?' marL=\"'.concat(R(a[3]),'\" marR=\"').concat(R(a[1]),'\" marT=\"').concat(R(a[0]),'\" marB=\"').concat(R(a[2]),'\"'):' marL=\"'.concat(I(a[3]),'\" marR=\"').concat(I(a[1]),'\" marT=\"').concat(I(a[0]),'\" marB=\"').concat(I(a[2]),'\"'),v+=\"\").concat(jt(t),\"\"),e.border&&Array.isArray(e.border)&&[{idx:3,name:\"lnL\"},{idx:1,name:\"lnR\"},{idx:0,name:\"lnT\"},{idx:2,name:\"lnB\"}].forEach(function(t){\"none\"!==e.border[t.idx].type?v=(v=(v=(v+=\"'))+\"\".concat(M(e.border[t.idx].color),\"\"))+''))+\"\"):v+=\"\")}),v=v+r+\" \")}),v+=\"\"}),B+=v=(v=v+\" \"+\" \")+\" \"+\"\",D++;break;case _.text:case _.placeholder:if(r.options.line||0!==f||(f=.3*k),r.options._bodyProp||(r.options._bodyProp={}),r.options.margin&&Array.isArray(r.options.margin)?(r.options._bodyProp.lIns=R(r.options.margin[0]||0),r.options._bodyProp.rIns=R(r.options.margin[1]||0),r.options._bodyProp.bIns=R(r.options.margin[2]||0),r.options._bodyProp.tIns=R(r.options.margin[3]||0)):\"number\"==typeof r.options.margin&&(r.options._bodyProp.lIns=R(r.options.margin),r.options._bodyProp.rIns=R(r.options.margin),r.options._bodyProp.bIns=R(r.options.margin),r.options._bodyProp.tIns=R(r.options.margin)),B=(B+=\"\")+''),null!=(o=r.options.hyperlink)&&o.url&&(B+='')),null!=(o=r.options.hyperlink)&&o.slide&&(B+='')),B=(B=(B=(B=(B=(B+=\"\")+(\"':\"/>\")))+\"\".concat(\"placeholder\"===r._type?Gt(r):Gt(n),\"\")+\"\")+\"\"))+''))+''),\"custGeom\"===r.shape)B=(B+='')+''),null!=(o=r.options.points)&&o.forEach(function(t,e){if(\"curve\"in t)switch(t.curve.type){case\"arc\":B+='');break;case\"cubic\":B+='\\n\\t\\t\\t\\t\\t\\t\\t\\t\\t\\n\\t\\t\\t\\t\\t\\t\\t\\t\\t\\n\\t\\t\\t\\t\\t\\t\\t\\t\\t\\n\\t\\t\\t\\t\\t\\t\\t\\t\\t');break;case\"quadratic\":B+='\\n\\t\\t\\t\\t\\t\\t\\t\\t\\t\\n\\t\\t\\t\\t\\t\\t\\t\\t\\t\\n\\t\\t\\t\\t\\t\\t\\t\\t\\t')}else\"close\"in t?B+=\"\":t.moveTo||0===e?B+=''):B+='')}),B+=\"\";else{if(B+='',r.options.rectRadius)B+='');else if(r.options.angleRange){for(var L=0;L<2;L++){var E=r.options.angleRange[L];B+='')}r.options.arcThicknessRatio&&(B+=''))}B+=\"\"}B+=r.options.fill?z(r.options.fill):\"\",r.options.line&&(B+=r.options.line.width?''):\"\",r.options.line.color&&(B+=z(r.options.line)),r.options.line.dashType&&(B+='')),r.options.line.beginArrowType&&(B+='')),r.options.line.endArrowType&&(B+='')),B+=\"\"),r.options.shadow&&\"none\"!==r.options.shadow.type&&(r.options.shadow.type=r.options.shadow.type||\"outer\",r.options.shadow.blur=R(r.options.shadow.blur||8),r.options.shadow.offset=R(r.options.shadow.offset||4),r.options.shadow.angle=Math.round(6e4*(r.options.shadow.angle||270)),r.options.shadow.opacity=Math.round(1e5*(r.options.shadow.opacity||.75)),r.options.shadow.color=r.options.shadow.color||ot.color,B=(B=(B=(B+=\"\")+\" '))+' '))+' ')+\" \"),B=(B+=\"\")+jt(r)+\"\";break;case _.image:B=(B=B+\"\"+\" \")+''),null!=(o=r.hyperlink)&&o.url&&(B+='')),null!=(o=r.hyperlink)&&o.slide&&(B+='')),B=(B=(B=B+\" \"+' ')+(\" \"+Gt(n)+\"\"))+\" \"+\"\",B=(T._relsMedia||[]).filter(function(t){return t.rId===r.imageRid})[0]&&\"svg\"===(T._relsMedia||[]).filter(function(t){return t.rId===r.imageRid})[0].extn?(B=(B+=''))+(r.options.transparency?' '):\"\")+' ')+' ')+\" \":(B+=''))+(r.options.transparency?''):\"\")+\"\",null!=y&&y.type?(o=y.w?N(y.w,\"X\",T._presLayout):p,i=y.h?N(y.h,\"Y\",T._presLayout):f,s=N(y.x||0,\"X\",T._presLayout),l=N(y.y||0,\"Y\",T._presLayout),B+=Rt[y.type]({w:w,h:x},{w:o,h:i,x:s,y:l}),w=o,x=i):B+=\" \",B=(B=(B=(B=(B+=\"\")+\"\"+(\" \"))+' '))+' ')+\" \")+' '),r.options.shadow&&\"none\"!==r.options.shadow.type&&(r.options.shadow.type=r.options.shadow.type||\"outer\",r.options.shadow.blur=R(r.options.shadow.blur||8),r.options.shadow.offset=R(r.options.shadow.offset||4),r.options.shadow.angle=Math.round(6e4*(r.options.shadow.angle||270)),r.options.shadow.opacity=Math.round(1e5*(r.options.shadow.opacity||.75)),r.options.shadow.color=r.options.shadow.color||ot.color,B=(B=(B=(B=(B+=\"\")+\"'))+''))+''))+\"\")+\"\"),B=B+\"\"+\"\";break;case _.media:B=\"online\"===r.mtype?(B=(B=(B=(B+=\" \")+'')+\" \")+' ')+\" \")+' ')+\" \")+\" ')+' ':(B=(B=(B=(B=(B+=\" \")+'')+' ')+' ')+' ')+' ')+\" \")+' ')+\" \")+\" ')+' ';break;case _.chart:B=(B=(B=(B=(B=(B=(B=B+\"\"+\" \")+' ')+\" \")+\" \".concat(Gt(n),\"\")+\" \")+' '))+' '+' ')+' ')+\" \")+\" \"+\"\";break;default:B+=\"\"}}),T._slideNumberProps&&(T._slideNumberProps.align||(T._slideNumberProps.align=\"left\"),B=(B+=' ')+\"\"+'')+'')+' \",(T._slideNumberProps.fontFace||T._slideNumberProps.fontSize||T._slideNumberProps.color)&&(B+=''),T._slideNumberProps.color&&(B+=z(T._slideNumberProps.color)),T._slideNumberProps.fontFace&&(B+='')),B+=\"\"),B+=\"\",T._slideNumberProps.align.startsWith(\"l\")?B+='':T._slideNumberProps.align.startsWith(\"c\")?B+='':T._slideNumberProps.align.startsWith(\"r\")?B+='':B+='',B=(B+=''))+\"\".concat(T._slideNum,'')+\"\"),B=B+\"\"+\"\"}function Mt(t,e){var n=0,r=''+d+'';return t._rels.forEach(function(t){n=Math.max(n,t.rId),t.type.toLowerCase().includes(\"hyperlink\")?\"slide\"===t.data?r+=''):r+=''):t.type.toLowerCase().includes(\"notesSlide\")&&(r+=''))}),(t._relsChart||[]).forEach(function(t){n=Math.max(n,t.rId),r+='')}),(t._relsMedia||[]).forEach(function(t){var e=t.rId.toString();n=Math.max(n,t.rId),t.type.toLowerCase().includes(\"image\")?r+='':t.type.toLowerCase().includes(\"audio\")?r.includes(' Target=\"'+t.Target+'\"')?r+='':r+='':t.type.toLowerCase().includes(\"video\")?r.includes(' Target=\"'+t.Target+'\"')?r+='':r+='':t.type.toLowerCase().includes(\"online\")&&(r.includes(' Target=\"'+t.Target+'\"')?r+='':r+='')}),e.forEach(function(t,e){r+='')}),r+=\"\"}function zt(t,e){var n,r,a=\"\",o=\"\",i=\"\",s=\"\",A=e?\"a:lvl1pPr\":\"a:pPr\",l=R(V),c=\"<\".concat(A).concat(t.options.rtlMode?' rtl=\"1\" ':\"\");if(t.options.align)switch(t.options.align){case\"left\":c+=' algn=\"l\"';break;case\"right\":c+=' algn=\"r\"';break;case\"center\":c+=' algn=\"ctr\"';break;case\"justify\":c+=' algn=\"just\"';break;default:c+=\"\"}return t.options.lineSpacing?o=''):t.options.lineSpacingMultiple&&(o='')),t.options.indentLevel&&!isNaN(Number(t.options.indentLevel))&&0')),t.options.paraSpaceAfter&&!isNaN(Number(t.options.paraSpaceAfter))&&0')),\"object\"==typeof t.options.bullet?(null!=(r=null==(r=null==t?void 0:t.options)?void 0:r.bullet)&&r.indent&&(l=R(t.options.bullet.indent)),t.options.bullet.type?\"number\"===t.options.bullet.type.toString().toLowerCase()&&(c+=' marL=\"'.concat(t.options.indentLevel&&0')):a=t.options.bullet.characterCode?(n=\"&#x\".concat(t.options.bullet.characterCode,\";\"),/^[0-9A-Fa-f]{4}$/.test(t.options.bullet.characterCode)||(console.warn(\"Warning: `bullet.characterCode should be a 4-digit unicode charatcer (ex: 22AB)`!\"),n=f.DEFAULT),c+=' marL=\"'.concat(t.options.indentLevel&&0'):t.options.bullet.code?(n=\"&#x\".concat(t.options.bullet.code,\";\"),/^[0-9A-Fa-f]{4}$/.test(t.options.bullet.code)||(console.warn(\"Warning: `bullet.code should be a 4-digit hex code (ex: 22AB)`!\"),n=f.DEFAULT),c+=' marL=\"'.concat(t.options.indentLevel&&0'):(c+=' marL=\"'.concat(t.options.indentLevel&&0'))):t.options.bullet?(c+=' marL=\"'.concat(t.options.indentLevel&&0')):t.options.bullet||(c+=' indent=\"0\" marL=\"0\"',a=\"\"),t.options.tabStops&&Array.isArray(t.options.tabStops)&&(r=t.options.tabStops.map(function(t){return'')}).join(\"\"),s=\"\".concat(r,\"\")),c+=\">\"+o+i+a+s,e&&(c+=Ut(t.options,!0)),c+=\"\"}function Ut(t,e){var n,r,a,o,i=\"\",e=e?\"a:defRPr\":\"a:rPr\",i=(i=(i=(i=(i+=\"<\"+e+' lang=\"'+(t.lang||\"en-US\")+'\"'+(t.lang?' altLang=\"en-US\"':\"\"))+(t.fontSize?' sz=\"'.concat(Math.round(100*t.fontSize),'\"'):\"\"))+(null!=t&&t.bold?' b=\"'.concat(t.bold?\"1\":\"0\",'\"'):\"\"))+(null!=t&&t.italic?' i=\"'.concat(t.italic?\"1\":\"0\",'\"'):\"\"))+(null!=t&&t.strike?' strike=\"'.concat(\"string\"==typeof t.strike?t.strike:\"sngStrike\",'\"'):\"\");if(\"object\"==typeof t.underline&&null!=(n=t.underline)&&n.style?i+=' u=\"'.concat(t.underline.style,'\"'):\"string\"==typeof t.underline?i+=' u=\"'.concat(String(t.underline),'\"'):t.hyperlink&&(i+=' u=\"sng\"'),t.baseline?i+=' baseline=\"'.concat(Math.round(50*t.baseline),'\"'):t.subscript?i+=' baseline=\"-40000\"':t.superscript&&(i+=' baseline=\"30000\"'),i=i+(t.charSpacing?' spc=\"'.concat(Math.round(100*t.charSpacing),'\" kern=\"0\"'):\"\")+' dirty=\"0\">',(t.color||t.fontFace||t.outline||\"object\"==typeof t.underline&&t.underline.color)&&(t.outline&&\"object\"==typeof t.outline&&(i+='').concat(z(t.outline.color||\"FFFFFF\"),\"\")),t.color&&(i+=z({color:t.color,transparency:t.transparency})),t.highlight&&(i+=\"\".concat(M(t.highlight),\"\")),\"object\"==typeof t.underline&&t.underline.color&&(i+=\"\".concat(z(t.underline.color),\"\")),t.glow&&(i+=\"\".concat((n=t.glow,a=\"\",r=y(y({},r=it),n),n=Math.round(r.size*w),o=r.color,r=Math.round(1e5*r.opacity),(a+=''))+M(o,''))+\"\"),\"\")),t.fontFace)&&(i+='')),t.hyperlink){if(\"object\"!=typeof t.hyperlink)throw new Error(\"ERROR: text `hyperlink` option should be an object. Ex: `hyperlink:{url:'https://github.com'}` \");if(!t.hyperlink.url&&!t.hyperlink.slide)throw new Error(\"ERROR: 'hyperlink requires either `url` or `slide`'\");t.hyperlink.url?i+='\":\"/>\"):t.hyperlink.slide&&(i+='\":\"/>\")),t.color&&(i+=' ')}return i+=\"\")}function jt(n){var o,t,e,r,a,i=n.options||{},s=[],A=[];return!i||n._type===_.tablecell||void 0!==n.text&&null!==n.text?(o=n._type===_.tablecell?\"\":\"\",o+=(e=\"\",t.options.fit&&(\"none\"===t.options.fit?e+=\"\":\"shrink\"===t.options.fit?e+=\"\":\"resize\"===t.options.fit&&(e+=\"\")),t.options.shrinkText&&(e+=\"\"),e=e+(t.options._bodyProp.autoFit?\"\":\"\")+\"\"):e+=' wrap=\"square\" rtlCol=\"0\">',t._type===_.tablecell?\"\":e),0===i.h&&i.line&&i.align?o+='':\"placeholder\"===n._type?o+=\"\".concat(zt(n,!0),\"\"):o+=\"\",\"string\"==typeof n.text||\"number\"==typeof n.text?s.push({text:n.text.toString(),options:i||{}}):n.text&&!Array.isArray(n.text)&&\"object\"==typeof n.text&&Object.keys(n.text).includes(\"text\")?s.push({text:n.text||\"\",options:n.options||{}}):Array.isArray(n.text)&&(s=n.text.map(function(t){return{text:t.text,options:t.options}})),s.forEach(function(e,t){e.text||(e.text=\"\"),e.options=e.options||i||{},0===t&&e.options&&!e.options.bullet&&i.bullet&&(e.options.bullet=i.bullet),\"string\"!=typeof e.text&&\"number\"!=typeof e.text||(e.text=e.text.toString().replace(/\\r*\\n/g,d)),e.text.includes(d)&&null===e.text.match(/\\n$/g)?e.text.split(d).forEach(function(t){e.options.breakLine=!0,A.push({text:t,options:e.options})}):A.push(e)}),r=[],a=[],A.forEach(function(t,e){0\",\"\"),n.options.align=n.options.align||i.align,n.options.lineSpacing=n.options.lineSpacing||i.lineSpacing,n.options.lineSpacingMultiple=n.options.lineSpacingMultiple||i.lineSpacingMultiple,n.options.indentLevel=n.options.indentLevel||i.indentLevel,n.options.paraSpaceBefore=n.options.paraSpaceBefore||i.paraSpaceBefore,n.options.paraSpaceAfter=n.options.paraSpaceAfter||i.paraSpaceAfter,a=zt(n,!1),o+=a.replace(\"\",\"\"),Object.entries(i).filter(function(t){var e=t[0];return t[1],!(n.options.hyperlink&&\"color\"===e)}).forEach(function(t){var e=t[0],t=t[1];\"bullet\"===e||n.options[e]||(n.options[e]=t)}),o+=(t=n).text?\"\".concat(Ut(t.options,!1),\"\").concat(F(t.text),\"\"):\"\",(!n.text&&i.fontSize||n.options.fontSize)&&(r=!0,i.fontSize=i.fontSize||n.options.fontSize)}),n._type===_.tablecell&&(i.fontSize||i.fontFace)?i.fontFace?o=(o=(o=(o+='')+''))+''))+'')+\"\":o+='':o+=r?'':''),o+=\"\"}),o+=n._type===_.tablecell?\"\":\"\"):\"\"}function Gt(t){var e,n;return t?(e=null!=(e=t.options)&&e._placeholderIdx?t.options._placeholderIdx:\"\",n=(n=null!=(n=t.options)&&n._placeholderType?t.options._placeholderType:\"\")&&a[n]?a[n].toString():\"\",\"\")):\"\"}function Qt(t){return''.concat(d,'').concat(F((e=\"\",t._slideObjects.forEach(function(t){t._type===_.notes&&(e+=null!=t&&t.text&&t.text[0]?t.text[0].text:\"\")}),e.replace(/\\r*\\n/g,d))),'').concat(t._slideNum,'');var e}function Wt(t,e,n){return Mt(t[n-1],[{target:\"../slideLayouts/slideLayout\".concat(function(t,e,n){for(var r=0;r'+d)+'')+'')+'')+'')+'',r.forEach(function(t){(t._relsMedia||[]).forEach(function(t){\"image\"===t.type||\"online\"===t.type||\"chart\"===t.type||\"m4v\"===t.extn||i.includes(t.type)||(i+='')})}),i=(i+='')+'',r.forEach(function(t,e){i=(i+=''))+''),t._relsChart.forEach(function(t){i+='')})}),i=(i+='')+'',a.forEach(function(t,e){i+=''),(t._relsChart||[]).forEach(function(t){i+=' '})}),r.forEach(function(t,e){i+='')}),o._relsChart.forEach(function(t){i+=' '}),o._relsMedia.forEach(function(t){\"image\"===t.type||\"online\"===t.type||\"chart\"===t.type||\"m4v\"===t.extn||i.includes(t.type)||(i+=' ')}),i=(i+=' ')+' ')),l.file(\"_rels/.rels\",''.concat(d,'\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t')),l.file(\"docProps/app.xml\",(a=this.slides,r=this.company,''.concat(d,'\\n\\t0\\n\\t0\\n\\tMicrosoft Office PowerPoint\\n\\tOn-screen Show (16:9)\\n\\t0\\n\\t').concat(a.length,\"\\n\\t\").concat(a.length,'\\n\\t0\\n\\t0\\n\\tfalse\\n\\t\\n\\t\\t\\n\\t\\t\\tFonts Used\\n\\t\\t\\t2\\n\\t\\t\\tTheme\\n\\t\\t\\t1\\n\\t\\t\\tSlide Titles\\n\\t\\t\\t').concat(a.length,'\\n\\t\\t\\n\\t\\n\\t\\n\\t\\t\\n\\t\\t\\tArial\\n\\t\\t\\tCalibri\\n\\t\\t\\tOffice Theme\\n\\t\\t\\t').concat(a.map(function(t,e){return\"Slide \".concat(e+1,\"\")}).join(\"\"),\"\\n\\t\\t\\n\\t\\n\\t\").concat(r,\"\\n\\tfalse\\n\\tfalse\\n\\tfalse\\n\\t16.0000\\n\\t\"))),l.file(\"docProps/core.xml\",(o=this.title,a=this.subject,r=this.author,e=this.revision,'\\n\\t\\n\\t\\t'.concat(F(o),\"\\n\\t\\t\").concat(F(a),\"\\n\\t\\t\").concat(F(r),\"\\n\\t\\t\").concat(F(r),\"\\n\\t\\t\").concat(e,'\\n\\t\\t').concat((new Date).toISOString().replace(/\\.\\d\\d\\dZ/,\"Z\"),'\\n\\t\\t').concat((new Date).toISOString().replace(/\\.\\d\\d\\dZ/,\"Z\"),\"\\n\\t\"))),l.file(\"ppt/_rels/presentation.xml.rels\",function(t){for(var e=1,n=(n=''+d)+''+'',r=1;r<=t.length;r++)n+='');return n+='')+'')+'')+'')+'')+\"\"}(this.slides)),l.file(\"ppt/theme/theme1.xml\",(a=null!=(a=(o=this).theme)&&a.headFontFace?''):'',o=null!=(r=o.theme)&&r.bodyFontFace?''):'',''.concat(a,'').concat(o,''))),l.file(\"ppt/presentation.xml\",function(t){var e=(e=''.concat(d)+''))+''+\"\";t.slides.forEach(function(t){return e+='')}),e=(e=(e=(e+=\"\")+''))+''))+'')+\"\";for(var n=1;n<10;n++)e+=\"')+''+\"\");return e+=\"\",t.sections&&0',t.sections.forEach(function(t){e+=''),t._slides.forEach(function(t){return e+='')}),e+=\"\"}),e+=''),e+=\"\"}(this)),l.file(\"ppt/presProps.xml\",''.concat(d,'')),l.file(\"ppt/tableStyles.xml\",''.concat(d,'')),l.file(\"ppt/viewProps.xml\",''.concat(d,'')),this.slideLayouts.forEach(function(t,e){l.file(\"ppt/slideLayouts/slideLayout\".concat(e+1,\".xml\"),'\\n\\t\\t\\n\\t\\t'.concat(Ot(t),\"\\n\\t\\t\")),l.file(\"ppt/slideLayouts/_rels/slideLayout\".concat(e+1,\".xml.rels\"),(t=e+1,Mt(s.slideLayouts[t-1],[{target:\"../slideMasters/slideMaster1.xml\",type:\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideMaster\"}])))}),this.slides.forEach(function(t,e){var n;l.file(\"ppt/slides/slide\".concat(e+1,\".xml\"),(n=t,''.concat(d)+'\")+\"\".concat(Ot(n))+\"\")),l.file(\"ppt/slides/_rels/slide\".concat(e+1,\".xml.rels\"),Wt(s.slides,s.slideLayouts,e+1)),l.file(\"ppt/notesSlides/notesSlide\".concat(e+1,\".xml\"),Qt(t)),l.file(\"ppt/notesSlides/_rels/notesSlide\".concat(e+1,\".xml.rels\"),'\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t'))}),l.file(\"ppt/slideMasters/slideMaster1.xml\",(n=this.masterSlide,e=(e=this.slideLayouts).map(function(t,e){return'')}),r=''+d,(r+='')+Ot(n)+''+e.join(\"\")+' ')),l.file(\"ppt/slideMasters/_rels/slideMaster1.xml.rels\",(a=this.masterSlide,(o=(o=this.slideLayouts).map(function(t,e){return{target:\"../slideLayouts/slideLayout\".concat(e+1,\".xml\"),type:\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout\"}})).push({target:\"../theme/theme1.xml\",type:\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme\"}),Mt(a,o))),l.file(\"ppt/notesMasters/notesMaster1.xml\",''.concat(d,'7/23/19Click to edit Master text stylesSecond levelThird levelFourth levelFifth level‹#›')),l.file(\"ppt/notesMasters/_rels/notesMaster1.xml.rels\",''.concat(d,'\\n\\t\\t\\n\\t\\t')),this.slideLayouts.forEach(function(t){s.createChartMediaRels(t,l,A)}),this.slides.forEach(function(t){s.createChartMediaRels(t,l,A)}),this.createChartMediaRels(this.masterSlide,l,A),[4,Promise.all(A).then(function(){return u(s,void 0,void 0,function(){return p(this,function(t){switch(t.label){case 0:return\"STREAM\"!==c.outputType?[3,2]:[4,l.generateAsync({type:\"nodebuffer\",compression:c.compression?\"DEFLATE\":\"STORE\"})];case 1:return[2,t.sent()];case 2:return c.outputType?[4,l.generateAsync({type:c.outputType})]:[3,4];case 3:return[2,t.sent()];case 4:return[4,l.generateAsync({type:\"blob\",compression:c.compression?\"DEFLATE\":\"STORE\"})];case 5:return[2,t.sent()]}})})})];case 1:return[2,t.sent()]}var n,e,r,a,o,i})})})];case 1:return[2,t.sent()]}})})};this.LAYOUTS={LAYOUT_4x3:{name:\"screen4x3\",width:9144e3,height:6858e3},LAYOUT_16x9:{name:\"screen16x9\",width:9144e3,height:5143500},LAYOUT_16x10:{name:\"screen16x10\",width:9144e3,height:5715e3},LAYOUT_WIDE:{name:\"custom\",width:12192e3,height:6858e3}},this._author=\"PptxGenJS\",this._company=\"PptxGenJS\",this._revision=\"1\",this._subject=\"PptxGenJS Presentation\",this._title=\"PptxGenJS Presentation\",this._presLayout={name:this.LAYOUTS[l].name,_sizeW:this.LAYOUTS[l].width,_sizeH:this.LAYOUTS[l].height,width:this.LAYOUTS[l].width,height:this.LAYOUTS[l].height},this._rtlMode=!1,this._slideLayouts=[{_margin:at,_name:nt,_presLayout:this._presLayout,_rels:[],_relsChart:[],_relsMedia:[],_slide:null,_slideNum:1e3,_slideNumberProps:null,_slideObjects:[]}],this._slides=[],this._sections=[],this._masterSlide={addChart:null,addImage:null,addMedia:null,addNotes:null,addShape:null,addTable:null,addText:null,_name:null,_presLayout:this._presLayout,_rId:null,_rels:[],_relsChart:[],_relsMedia:[],_slideId:null,_slideLayout:null,_slideNum:null,_slideNumberProps:null,_slideObjects:[]}}return Object.defineProperty(n.prototype,\"layout\",{get:function(){return this._layout},set:function(t){var e=this.LAYOUTS[t];if(!e)throw new Error(\"UNKNOWN-LAYOUT\");this._layout=t,this._presLayout=e},enumerable:!1,configurable:!0}),Object.defineProperty(n.prototype,\"version\",{get:function(){return this._version},enumerable:!1,configurable:!0}),Object.defineProperty(n.prototype,\"author\",{get:function(){return this._author},set:function(t){this._author=t},enumerable:!1,configurable:!0}),Object.defineProperty(n.prototype,\"company\",{get:function(){return this._company},set:function(t){this._company=t},enumerable:!1,configurable:!0}),Object.defineProperty(n.prototype,\"revision\",{get:function(){return this._revision},set:function(t){this._revision=t},enumerable:!1,configurable:!0}),Object.defineProperty(n.prototype,\"subject\",{get:function(){return this._subject},set:function(t){this._subject=t},enumerable:!1,configurable:!0}),Object.defineProperty(n.prototype,\"theme\",{get:function(){return this._theme},set:function(t){this._theme=t},enumerable:!1,configurable:!0}),Object.defineProperty(n.prototype,\"title\",{get:function(){return this._title},set:function(t){this._title=t},enumerable:!1,configurable:!0}),Object.defineProperty(n.prototype,\"rtlMode\",{get:function(){return this._rtlMode},set:function(t){this._rtlMode=t},enumerable:!1,configurable:!0}),Object.defineProperty(n.prototype,\"masterSlide\",{get:function(){return this._masterSlide},enumerable:!1,configurable:!0}),Object.defineProperty(n.prototype,\"slides\",{get:function(){return this._slides},enumerable:!1,configurable:!0}),Object.defineProperty(n.prototype,\"sections\",{get:function(){return this._sections},enumerable:!1,configurable:!0}),Object.defineProperty(n.prototype,\"slideLayouts\",{get:function(){return this._slideLayouts},enumerable:!1,configurable:!0}),Object.defineProperty(n.prototype,\"AlignH\",{get:function(){return this._alignH},enumerable:!1,configurable:!0}),Object.defineProperty(n.prototype,\"AlignV\",{get:function(){return this._alignV},enumerable:!1,configurable:!0}),Object.defineProperty(n.prototype,\"ChartType\",{get:function(){return this._chartType},enumerable:!1,configurable:!0}),Object.defineProperty(n.prototype,\"OutputType\",{get:function(){return this._outputType},enumerable:!1,configurable:!0}),Object.defineProperty(n.prototype,\"presLayout\",{get:function(){return this._presLayout},enumerable:!1,configurable:!0}),Object.defineProperty(n.prototype,\"SchemeColor\",{get:function(){return this._schemeColor},enumerable:!1,configurable:!0}),Object.defineProperty(n.prototype,\"ShapeType\",{get:function(){return this._shapeType},enumerable:!1,configurable:!0}),Object.defineProperty(n.prototype,\"charts\",{get:function(){return this._charts},enumerable:!1,configurable:!0}),Object.defineProperty(n.prototype,\"colors\",{get:function(){return this._colors},enumerable:!1,configurable:!0}),Object.defineProperty(n.prototype,\"shapes\",{get:function(){return this._shapes},enumerable:!1,configurable:!0}),n.prototype.stream=function(e){return u(this,void 0,void 0,function(){return p(this,function(t){switch(t.label){case 0:return[4,this.exportPresentation({compression:null==e?void 0:e.compression,outputType:\"STREAM\"})];case 1:return[2,t.sent()]}})})},n.prototype.write=function(r){return u(this,void 0,void 0,function(){var e,n;return p(this,function(t){switch(t.label){case 0:return e=\"object\"==typeof r&&null!=r&&r.outputType?r.outputType:r||null,n=!(\"object\"!=typeof r||null==r||!r.compression)&&r.compression,[4,this.exportPresentation({compression:n,outputType:e})];case 1:return[2,t.sent()]}})})},n.prototype.writeFile=function(r){return u(this,void 0,void 0,function(){var a,e,n,o,i=this;return p(this,function(t){switch(t.label){case 0:return a=\"undefined\"!=typeof require&&\"undefined\"==typeof window?require(\"fs\"):null,\"string\"==typeof r&&console.log(\"Warning: `writeFile(filename)` is deprecated - please use `WriteFileProps` argument (v3.5.0)\"),e=\"object\"==typeof r&&null!=r&&r.fileName?r.fileName:\"string\"==typeof r?r:\"\",n=!(\"object\"!=typeof r||null==r||!r.compression)&&r.compression,o=e?e.toString().toLowerCase().endsWith(\".pptx\")?e:e+\".pptx\":\"Presentation.pptx\",[4,this.exportPresentation({compression:n,outputType:a?\"nodebuffer\":null}).then(function(r){return u(i,void 0,void 0,function(){return p(this,function(t){switch(t.label){case 0:return a?[4,new Promise(function(e,n){a.writeFile(o,r,function(t){t?n(t):e(o)})})]:[3,2];case 1:return[2,t.sent()];case 2:return[4,this.writeFileToBrowser(o,r)];case 3:return[2,t.sent()]}})})})];case 1:return[2,t.sent()]}})})},n.prototype.addSection=function(t){t?t.title||console.warn(\"addSection requires a title\"):console.warn(\"addSection requires an argument\");var e={_type:\"user\",_slides:[],title:t.title};t.order?this.sections.splice(t.order,0,e):this._sections.push(e)},n.prototype.addSlide=function(e){var n=\"string\"==typeof e?e:null!=e&&e.masterName?e.masterName:\"\",t={_name:this.LAYOUTS[l].name,_presLayout:this.presLayout,_rels:[],_relsChart:[],_relsMedia:[],_slideNum:this.slides.length+1},r=(n&&(r=this.slideLayouts.filter(function(t){return t._name===n})[0])&&(t=r),new Lt({addSlide:this.addNewSlide,getSlide:this.getSlide,presLayout:this.presLayout,setSlideNum:this.setSlideNumber,slideId:this.slides.length+256,slideRId:this.slides.length+2,slideNumber:this.slides.length+1,slideLayout:t}));return this._slides.push(r),null!=e&&e.sectionTitle?(t=this.sections.filter(function(t){return t.title===e.sectionTitle})[0])?t._slides.push(r):console.warn('addSlide: unable to find section with title: \"'.concat(e.sectionTitle,'\"')):this.sections&&0 opts.y = \").concat(i.y)),n.addTable(t.rows,{x:i.x||p[3],y:i.y,w:Number(a)/k,colW:c,autoPage:!1}),i.addImage&&(i.addImage.options=i.addImage.options||{},i.addImage.image&&(i.addImage.image.path||i.addImage.image.data)?n.addImage({path:i.addImage.image.path,data:i.addImage.image.data,x:i.addImage.options.x,y:i.addImage.options.y,w:i.addImage.options.w,h:i.addImage.options.h}):console.warn(\"Warning: tableToSlides.addImage requires either `path` or `data`\")),i.addShape&&n.addShape(i.addShape.shapeName,i.addShape.options||{}),i.addTable&&n.addTable(i.addTable.rows,i.addTable.options||{}),i.addText&&n.addText(i.addText.text,i.addText.options||{})})},n}();"],"file":"pptxgen.bundle.js"} \ No newline at end of file diff --git a/services/slides/node_modules/pptxgenjs/dist/pptxgen.cjs.js b/services/slides/node_modules/pptxgenjs/dist/pptxgen.cjs.js new file mode 100644 index 0000000000000000000000000000000000000000..f849abe37a4aff180213d19f89ede35acd47d962 --- /dev/null +++ b/services/slides/node_modules/pptxgenjs/dist/pptxgen.cjs.js @@ -0,0 +1,7445 @@ +/* PptxGenJS 3.12.0 @ 2023-03-20T03:12:31.353Z */ +'use strict'; + +var JSZip = require('jszip'); + +function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } + +var JSZip__default = /*#__PURE__*/_interopDefaultLegacy(JSZip); + +/****************************************************************************** +Copyright (c) Microsoft Corporation. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. +***************************************************************************** */ + +var __assign = function() { + __assign = Object.assign || function __assign(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); +}; + +function __awaiter(thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +} + +function __generator(thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (g && (g = 0, op[0] && (_ = 0)), _) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +} + +function __spreadArray(to, from, pack) { + if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { + if (ar || !(i in from)) { + if (!ar) ar = Array.prototype.slice.call(from, 0, i); + ar[i] = from[i]; + } + } + return to.concat(ar || Array.prototype.slice.call(from)); +} + +/** + * PptxGenJS Enums + * NOTE: `enum` wont work for objects, so use `Object.freeze` + */ +// CONST +var EMU = 914400; // One (1) inch (OfficeXML measures in EMU (English Metric Units)) +var ONEPT = 12700; // One (1) point (pt) +var CRLF = '\r\n'; // AKA: Chr(13) & Chr(10) +var LAYOUT_IDX_SERIES_BASE = 2147483649; +var REGEX_HEX_COLOR = /^[0-9a-fA-F]{6}$/; +var LINEH_MODIFIER = 1.67; // AKA: Golden Ratio Typography +var DEF_BULLET_MARGIN = 27; +var DEF_CELL_BORDER = { type: 'solid', color: '666666', pt: 1 }; +var DEF_CELL_MARGIN_IN = [0.05, 0.1, 0.05, 0.1]; // "Normal" margins in PPT-2021 ("Narrow" is `0.05` for all 4) +var DEF_CHART_BORDER = { type: 'solid', color: '363636', pt: 1 }; +var DEF_CHART_GRIDLINE = { color: '888888', style: 'solid', size: 1, cap: 'flat' }; +var DEF_FONT_COLOR = '000000'; +var DEF_FONT_SIZE = 12; +var DEF_FONT_TITLE_SIZE = 18; +var DEF_PRES_LAYOUT = 'LAYOUT_16x9'; +var DEF_PRES_LAYOUT_NAME = 'DEFAULT'; +var DEF_SHAPE_LINE_COLOR = '333333'; +var DEF_SHAPE_SHADOW = { type: 'outer', blur: 3, offset: 23000 / 12700, angle: 90, color: '000000', opacity: 0.35, rotateWithShape: true }; +var DEF_SLIDE_MARGIN_IN = [0.5, 0.5, 0.5, 0.5]; // TRBL-style +var DEF_TEXT_SHADOW = { type: 'outer', blur: 8, offset: 4, angle: 270, color: '000000', opacity: 0.75 }; +var DEF_TEXT_GLOW = { size: 8, color: 'FFFFFF', opacity: 0.75 }; +var AXIS_ID_VALUE_PRIMARY = '2094734552'; +var AXIS_ID_VALUE_SECONDARY = '2094734553'; +var AXIS_ID_CATEGORY_PRIMARY = '2094734554'; +var AXIS_ID_CATEGORY_SECONDARY = '2094734555'; +var AXIS_ID_SERIES_PRIMARY = '2094734556'; +var LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(''); +var BARCHART_COLORS = [ + 'C0504D', + '4F81BD', + '9BBB59', + '8064A2', + '4BACC6', + 'F79646', + '628FC6', + 'C86360', + 'C0504D', + '4F81BD', + '9BBB59', + '8064A2', + '4BACC6', + 'F79646', + '628FC6', + 'C86360' +]; +var PIECHART_COLORS = [ + '5DA5DA', + 'FAA43A', + '60BD68', + 'F17CB0', + 'B2912F', + 'B276B2', + 'DECF3F', + 'F15854', + 'A7A7A7', + '5DA5DA', + 'FAA43A', + '60BD68', + 'F17CB0', + 'B2912F', + 'B276B2', + 'DECF3F', + 'F15854', + 'A7A7A7', +]; +var TEXT_HALIGN; +(function (TEXT_HALIGN) { + TEXT_HALIGN["left"] = "left"; + TEXT_HALIGN["center"] = "center"; + TEXT_HALIGN["right"] = "right"; + TEXT_HALIGN["justify"] = "justify"; +})(TEXT_HALIGN || (TEXT_HALIGN = {})); +var TEXT_VALIGN; +(function (TEXT_VALIGN) { + TEXT_VALIGN["b"] = "b"; + TEXT_VALIGN["ctr"] = "ctr"; + TEXT_VALIGN["t"] = "t"; +})(TEXT_VALIGN || (TEXT_VALIGN = {})); +var SLDNUMFLDID = '{F7021451-1387-4CA6-816F-3879F97B5CBC}'; +// ENUM +// TODO: 3.5 or v4.0: rationalize ts-def exported enum names/case! +// NOTE: First tsdef enum named correctly (shapes -> 'Shape', colors -> 'Color'), etc. +var OutputType; +(function (OutputType) { + OutputType["arraybuffer"] = "arraybuffer"; + OutputType["base64"] = "base64"; + OutputType["binarystring"] = "binarystring"; + OutputType["blob"] = "blob"; + OutputType["nodebuffer"] = "nodebuffer"; + OutputType["uint8array"] = "uint8array"; +})(OutputType || (OutputType = {})); +var ChartType; +(function (ChartType) { + ChartType["area"] = "area"; + ChartType["bar"] = "bar"; + ChartType["bar3d"] = "bar3D"; + ChartType["bubble"] = "bubble"; + ChartType["bubble3d"] = "bubble3D"; + ChartType["doughnut"] = "doughnut"; + ChartType["line"] = "line"; + ChartType["pie"] = "pie"; + ChartType["radar"] = "radar"; + ChartType["scatter"] = "scatter"; +})(ChartType || (ChartType = {})); +var ShapeType; +(function (ShapeType) { + ShapeType["accentBorderCallout1"] = "accentBorderCallout1"; + ShapeType["accentBorderCallout2"] = "accentBorderCallout2"; + ShapeType["accentBorderCallout3"] = "accentBorderCallout3"; + ShapeType["accentCallout1"] = "accentCallout1"; + ShapeType["accentCallout2"] = "accentCallout2"; + ShapeType["accentCallout3"] = "accentCallout3"; + ShapeType["actionButtonBackPrevious"] = "actionButtonBackPrevious"; + ShapeType["actionButtonBeginning"] = "actionButtonBeginning"; + ShapeType["actionButtonBlank"] = "actionButtonBlank"; + ShapeType["actionButtonDocument"] = "actionButtonDocument"; + ShapeType["actionButtonEnd"] = "actionButtonEnd"; + ShapeType["actionButtonForwardNext"] = "actionButtonForwardNext"; + ShapeType["actionButtonHelp"] = "actionButtonHelp"; + ShapeType["actionButtonHome"] = "actionButtonHome"; + ShapeType["actionButtonInformation"] = "actionButtonInformation"; + ShapeType["actionButtonMovie"] = "actionButtonMovie"; + ShapeType["actionButtonReturn"] = "actionButtonReturn"; + ShapeType["actionButtonSound"] = "actionButtonSound"; + ShapeType["arc"] = "arc"; + ShapeType["bentArrow"] = "bentArrow"; + ShapeType["bentUpArrow"] = "bentUpArrow"; + ShapeType["bevel"] = "bevel"; + ShapeType["blockArc"] = "blockArc"; + ShapeType["borderCallout1"] = "borderCallout1"; + ShapeType["borderCallout2"] = "borderCallout2"; + ShapeType["borderCallout3"] = "borderCallout3"; + ShapeType["bracePair"] = "bracePair"; + ShapeType["bracketPair"] = "bracketPair"; + ShapeType["callout1"] = "callout1"; + ShapeType["callout2"] = "callout2"; + ShapeType["callout3"] = "callout3"; + ShapeType["can"] = "can"; + ShapeType["chartPlus"] = "chartPlus"; + ShapeType["chartStar"] = "chartStar"; + ShapeType["chartX"] = "chartX"; + ShapeType["chevron"] = "chevron"; + ShapeType["chord"] = "chord"; + ShapeType["circularArrow"] = "circularArrow"; + ShapeType["cloud"] = "cloud"; + ShapeType["cloudCallout"] = "cloudCallout"; + ShapeType["corner"] = "corner"; + ShapeType["cornerTabs"] = "cornerTabs"; + ShapeType["cube"] = "cube"; + ShapeType["curvedDownArrow"] = "curvedDownArrow"; + ShapeType["curvedLeftArrow"] = "curvedLeftArrow"; + ShapeType["curvedRightArrow"] = "curvedRightArrow"; + ShapeType["curvedUpArrow"] = "curvedUpArrow"; + ShapeType["custGeom"] = "custGeom"; + ShapeType["decagon"] = "decagon"; + ShapeType["diagStripe"] = "diagStripe"; + ShapeType["diamond"] = "diamond"; + ShapeType["dodecagon"] = "dodecagon"; + ShapeType["donut"] = "donut"; + ShapeType["doubleWave"] = "doubleWave"; + ShapeType["downArrow"] = "downArrow"; + ShapeType["downArrowCallout"] = "downArrowCallout"; + ShapeType["ellipse"] = "ellipse"; + ShapeType["ellipseRibbon"] = "ellipseRibbon"; + ShapeType["ellipseRibbon2"] = "ellipseRibbon2"; + ShapeType["flowChartAlternateProcess"] = "flowChartAlternateProcess"; + ShapeType["flowChartCollate"] = "flowChartCollate"; + ShapeType["flowChartConnector"] = "flowChartConnector"; + ShapeType["flowChartDecision"] = "flowChartDecision"; + ShapeType["flowChartDelay"] = "flowChartDelay"; + ShapeType["flowChartDisplay"] = "flowChartDisplay"; + ShapeType["flowChartDocument"] = "flowChartDocument"; + ShapeType["flowChartExtract"] = "flowChartExtract"; + ShapeType["flowChartInputOutput"] = "flowChartInputOutput"; + ShapeType["flowChartInternalStorage"] = "flowChartInternalStorage"; + ShapeType["flowChartMagneticDisk"] = "flowChartMagneticDisk"; + ShapeType["flowChartMagneticDrum"] = "flowChartMagneticDrum"; + ShapeType["flowChartMagneticTape"] = "flowChartMagneticTape"; + ShapeType["flowChartManualInput"] = "flowChartManualInput"; + ShapeType["flowChartManualOperation"] = "flowChartManualOperation"; + ShapeType["flowChartMerge"] = "flowChartMerge"; + ShapeType["flowChartMultidocument"] = "flowChartMultidocument"; + ShapeType["flowChartOfflineStorage"] = "flowChartOfflineStorage"; + ShapeType["flowChartOffpageConnector"] = "flowChartOffpageConnector"; + ShapeType["flowChartOnlineStorage"] = "flowChartOnlineStorage"; + ShapeType["flowChartOr"] = "flowChartOr"; + ShapeType["flowChartPredefinedProcess"] = "flowChartPredefinedProcess"; + ShapeType["flowChartPreparation"] = "flowChartPreparation"; + ShapeType["flowChartProcess"] = "flowChartProcess"; + ShapeType["flowChartPunchedCard"] = "flowChartPunchedCard"; + ShapeType["flowChartPunchedTape"] = "flowChartPunchedTape"; + ShapeType["flowChartSort"] = "flowChartSort"; + ShapeType["flowChartSummingJunction"] = "flowChartSummingJunction"; + ShapeType["flowChartTerminator"] = "flowChartTerminator"; + ShapeType["folderCorner"] = "folderCorner"; + ShapeType["frame"] = "frame"; + ShapeType["funnel"] = "funnel"; + ShapeType["gear6"] = "gear6"; + ShapeType["gear9"] = "gear9"; + ShapeType["halfFrame"] = "halfFrame"; + ShapeType["heart"] = "heart"; + ShapeType["heptagon"] = "heptagon"; + ShapeType["hexagon"] = "hexagon"; + ShapeType["homePlate"] = "homePlate"; + ShapeType["horizontalScroll"] = "horizontalScroll"; + ShapeType["irregularSeal1"] = "irregularSeal1"; + ShapeType["irregularSeal2"] = "irregularSeal2"; + ShapeType["leftArrow"] = "leftArrow"; + ShapeType["leftArrowCallout"] = "leftArrowCallout"; + ShapeType["leftBrace"] = "leftBrace"; + ShapeType["leftBracket"] = "leftBracket"; + ShapeType["leftCircularArrow"] = "leftCircularArrow"; + ShapeType["leftRightArrow"] = "leftRightArrow"; + ShapeType["leftRightArrowCallout"] = "leftRightArrowCallout"; + ShapeType["leftRightCircularArrow"] = "leftRightCircularArrow"; + ShapeType["leftRightRibbon"] = "leftRightRibbon"; + ShapeType["leftRightUpArrow"] = "leftRightUpArrow"; + ShapeType["leftUpArrow"] = "leftUpArrow"; + ShapeType["lightningBolt"] = "lightningBolt"; + ShapeType["line"] = "line"; + ShapeType["lineInv"] = "lineInv"; + ShapeType["mathDivide"] = "mathDivide"; + ShapeType["mathEqual"] = "mathEqual"; + ShapeType["mathMinus"] = "mathMinus"; + ShapeType["mathMultiply"] = "mathMultiply"; + ShapeType["mathNotEqual"] = "mathNotEqual"; + ShapeType["mathPlus"] = "mathPlus"; + ShapeType["moon"] = "moon"; + ShapeType["noSmoking"] = "noSmoking"; + ShapeType["nonIsoscelesTrapezoid"] = "nonIsoscelesTrapezoid"; + ShapeType["notchedRightArrow"] = "notchedRightArrow"; + ShapeType["octagon"] = "octagon"; + ShapeType["parallelogram"] = "parallelogram"; + ShapeType["pentagon"] = "pentagon"; + ShapeType["pie"] = "pie"; + ShapeType["pieWedge"] = "pieWedge"; + ShapeType["plaque"] = "plaque"; + ShapeType["plaqueTabs"] = "plaqueTabs"; + ShapeType["plus"] = "plus"; + ShapeType["quadArrow"] = "quadArrow"; + ShapeType["quadArrowCallout"] = "quadArrowCallout"; + ShapeType["rect"] = "rect"; + ShapeType["ribbon"] = "ribbon"; + ShapeType["ribbon2"] = "ribbon2"; + ShapeType["rightArrow"] = "rightArrow"; + ShapeType["rightArrowCallout"] = "rightArrowCallout"; + ShapeType["rightBrace"] = "rightBrace"; + ShapeType["rightBracket"] = "rightBracket"; + ShapeType["round1Rect"] = "round1Rect"; + ShapeType["round2DiagRect"] = "round2DiagRect"; + ShapeType["round2SameRect"] = "round2SameRect"; + ShapeType["roundRect"] = "roundRect"; + ShapeType["rtTriangle"] = "rtTriangle"; + ShapeType["smileyFace"] = "smileyFace"; + ShapeType["snip1Rect"] = "snip1Rect"; + ShapeType["snip2DiagRect"] = "snip2DiagRect"; + ShapeType["snip2SameRect"] = "snip2SameRect"; + ShapeType["snipRoundRect"] = "snipRoundRect"; + ShapeType["squareTabs"] = "squareTabs"; + ShapeType["star10"] = "star10"; + ShapeType["star12"] = "star12"; + ShapeType["star16"] = "star16"; + ShapeType["star24"] = "star24"; + ShapeType["star32"] = "star32"; + ShapeType["star4"] = "star4"; + ShapeType["star5"] = "star5"; + ShapeType["star6"] = "star6"; + ShapeType["star7"] = "star7"; + ShapeType["star8"] = "star8"; + ShapeType["stripedRightArrow"] = "stripedRightArrow"; + ShapeType["sun"] = "sun"; + ShapeType["swooshArrow"] = "swooshArrow"; + ShapeType["teardrop"] = "teardrop"; + ShapeType["trapezoid"] = "trapezoid"; + ShapeType["triangle"] = "triangle"; + ShapeType["upArrow"] = "upArrow"; + ShapeType["upArrowCallout"] = "upArrowCallout"; + ShapeType["upDownArrow"] = "upDownArrow"; + ShapeType["upDownArrowCallout"] = "upDownArrowCallout"; + ShapeType["uturnArrow"] = "uturnArrow"; + ShapeType["verticalScroll"] = "verticalScroll"; + ShapeType["wave"] = "wave"; + ShapeType["wedgeEllipseCallout"] = "wedgeEllipseCallout"; + ShapeType["wedgeRectCallout"] = "wedgeRectCallout"; + ShapeType["wedgeRoundRectCallout"] = "wedgeRoundRectCallout"; +})(ShapeType || (ShapeType = {})); +/** + * TODO: FUTURE: v4.0: rename to `ThemeColor` + */ +var SchemeColor; +(function (SchemeColor) { + SchemeColor["text1"] = "tx1"; + SchemeColor["text2"] = "tx2"; + SchemeColor["background1"] = "bg1"; + SchemeColor["background2"] = "bg2"; + SchemeColor["accent1"] = "accent1"; + SchemeColor["accent2"] = "accent2"; + SchemeColor["accent3"] = "accent3"; + SchemeColor["accent4"] = "accent4"; + SchemeColor["accent5"] = "accent5"; + SchemeColor["accent6"] = "accent6"; +})(SchemeColor || (SchemeColor = {})); +var AlignH; +(function (AlignH) { + AlignH["left"] = "left"; + AlignH["center"] = "center"; + AlignH["right"] = "right"; + AlignH["justify"] = "justify"; +})(AlignH || (AlignH = {})); +var AlignV; +(function (AlignV) { + AlignV["top"] = "top"; + AlignV["middle"] = "middle"; + AlignV["bottom"] = "bottom"; +})(AlignV || (AlignV = {})); +var SHAPE_TYPE; +(function (SHAPE_TYPE) { + SHAPE_TYPE["ACTION_BUTTON_BACK_OR_PREVIOUS"] = "actionButtonBackPrevious"; + SHAPE_TYPE["ACTION_BUTTON_BEGINNING"] = "actionButtonBeginning"; + SHAPE_TYPE["ACTION_BUTTON_CUSTOM"] = "actionButtonBlank"; + SHAPE_TYPE["ACTION_BUTTON_DOCUMENT"] = "actionButtonDocument"; + SHAPE_TYPE["ACTION_BUTTON_END"] = "actionButtonEnd"; + SHAPE_TYPE["ACTION_BUTTON_FORWARD_OR_NEXT"] = "actionButtonForwardNext"; + SHAPE_TYPE["ACTION_BUTTON_HELP"] = "actionButtonHelp"; + SHAPE_TYPE["ACTION_BUTTON_HOME"] = "actionButtonHome"; + SHAPE_TYPE["ACTION_BUTTON_INFORMATION"] = "actionButtonInformation"; + SHAPE_TYPE["ACTION_BUTTON_MOVIE"] = "actionButtonMovie"; + SHAPE_TYPE["ACTION_BUTTON_RETURN"] = "actionButtonReturn"; + SHAPE_TYPE["ACTION_BUTTON_SOUND"] = "actionButtonSound"; + SHAPE_TYPE["ARC"] = "arc"; + SHAPE_TYPE["BALLOON"] = "wedgeRoundRectCallout"; + SHAPE_TYPE["BENT_ARROW"] = "bentArrow"; + SHAPE_TYPE["BENT_UP_ARROW"] = "bentUpArrow"; + SHAPE_TYPE["BEVEL"] = "bevel"; + SHAPE_TYPE["BLOCK_ARC"] = "blockArc"; + SHAPE_TYPE["CAN"] = "can"; + SHAPE_TYPE["CHART_PLUS"] = "chartPlus"; + SHAPE_TYPE["CHART_STAR"] = "chartStar"; + SHAPE_TYPE["CHART_X"] = "chartX"; + SHAPE_TYPE["CHEVRON"] = "chevron"; + SHAPE_TYPE["CHORD"] = "chord"; + SHAPE_TYPE["CIRCULAR_ARROW"] = "circularArrow"; + SHAPE_TYPE["CLOUD"] = "cloud"; + SHAPE_TYPE["CLOUD_CALLOUT"] = "cloudCallout"; + SHAPE_TYPE["CORNER"] = "corner"; + SHAPE_TYPE["CORNER_TABS"] = "cornerTabs"; + SHAPE_TYPE["CROSS"] = "plus"; + SHAPE_TYPE["CUBE"] = "cube"; + SHAPE_TYPE["CURVED_DOWN_ARROW"] = "curvedDownArrow"; + SHAPE_TYPE["CURVED_DOWN_RIBBON"] = "ellipseRibbon"; + SHAPE_TYPE["CURVED_LEFT_ARROW"] = "curvedLeftArrow"; + SHAPE_TYPE["CURVED_RIGHT_ARROW"] = "curvedRightArrow"; + SHAPE_TYPE["CURVED_UP_ARROW"] = "curvedUpArrow"; + SHAPE_TYPE["CURVED_UP_RIBBON"] = "ellipseRibbon2"; + SHAPE_TYPE["CUSTOM_GEOMETRY"] = "custGeom"; + SHAPE_TYPE["DECAGON"] = "decagon"; + SHAPE_TYPE["DIAGONAL_STRIPE"] = "diagStripe"; + SHAPE_TYPE["DIAMOND"] = "diamond"; + SHAPE_TYPE["DODECAGON"] = "dodecagon"; + SHAPE_TYPE["DONUT"] = "donut"; + SHAPE_TYPE["DOUBLE_BRACE"] = "bracePair"; + SHAPE_TYPE["DOUBLE_BRACKET"] = "bracketPair"; + SHAPE_TYPE["DOUBLE_WAVE"] = "doubleWave"; + SHAPE_TYPE["DOWN_ARROW"] = "downArrow"; + SHAPE_TYPE["DOWN_ARROW_CALLOUT"] = "downArrowCallout"; + SHAPE_TYPE["DOWN_RIBBON"] = "ribbon"; + SHAPE_TYPE["EXPLOSION1"] = "irregularSeal1"; + SHAPE_TYPE["EXPLOSION2"] = "irregularSeal2"; + SHAPE_TYPE["FLOWCHART_ALTERNATE_PROCESS"] = "flowChartAlternateProcess"; + SHAPE_TYPE["FLOWCHART_CARD"] = "flowChartPunchedCard"; + SHAPE_TYPE["FLOWCHART_COLLATE"] = "flowChartCollate"; + SHAPE_TYPE["FLOWCHART_CONNECTOR"] = "flowChartConnector"; + SHAPE_TYPE["FLOWCHART_DATA"] = "flowChartInputOutput"; + SHAPE_TYPE["FLOWCHART_DECISION"] = "flowChartDecision"; + SHAPE_TYPE["FLOWCHART_DELAY"] = "flowChartDelay"; + SHAPE_TYPE["FLOWCHART_DIRECT_ACCESS_STORAGE"] = "flowChartMagneticDrum"; + SHAPE_TYPE["FLOWCHART_DISPLAY"] = "flowChartDisplay"; + SHAPE_TYPE["FLOWCHART_DOCUMENT"] = "flowChartDocument"; + SHAPE_TYPE["FLOWCHART_EXTRACT"] = "flowChartExtract"; + SHAPE_TYPE["FLOWCHART_INTERNAL_STORAGE"] = "flowChartInternalStorage"; + SHAPE_TYPE["FLOWCHART_MAGNETIC_DISK"] = "flowChartMagneticDisk"; + SHAPE_TYPE["FLOWCHART_MANUAL_INPUT"] = "flowChartManualInput"; + SHAPE_TYPE["FLOWCHART_MANUAL_OPERATION"] = "flowChartManualOperation"; + SHAPE_TYPE["FLOWCHART_MERGE"] = "flowChartMerge"; + SHAPE_TYPE["FLOWCHART_MULTIDOCUMENT"] = "flowChartMultidocument"; + SHAPE_TYPE["FLOWCHART_OFFLINE_STORAGE"] = "flowChartOfflineStorage"; + SHAPE_TYPE["FLOWCHART_OFFPAGE_CONNECTOR"] = "flowChartOffpageConnector"; + SHAPE_TYPE["FLOWCHART_OR"] = "flowChartOr"; + SHAPE_TYPE["FLOWCHART_PREDEFINED_PROCESS"] = "flowChartPredefinedProcess"; + SHAPE_TYPE["FLOWCHART_PREPARATION"] = "flowChartPreparation"; + SHAPE_TYPE["FLOWCHART_PROCESS"] = "flowChartProcess"; + SHAPE_TYPE["FLOWCHART_PUNCHED_TAPE"] = "flowChartPunchedTape"; + SHAPE_TYPE["FLOWCHART_SEQUENTIAL_ACCESS_STORAGE"] = "flowChartMagneticTape"; + SHAPE_TYPE["FLOWCHART_SORT"] = "flowChartSort"; + SHAPE_TYPE["FLOWCHART_STORED_DATA"] = "flowChartOnlineStorage"; + SHAPE_TYPE["FLOWCHART_SUMMING_JUNCTION"] = "flowChartSummingJunction"; + SHAPE_TYPE["FLOWCHART_TERMINATOR"] = "flowChartTerminator"; + SHAPE_TYPE["FOLDED_CORNER"] = "folderCorner"; + SHAPE_TYPE["FRAME"] = "frame"; + SHAPE_TYPE["FUNNEL"] = "funnel"; + SHAPE_TYPE["GEAR_6"] = "gear6"; + SHAPE_TYPE["GEAR_9"] = "gear9"; + SHAPE_TYPE["HALF_FRAME"] = "halfFrame"; + SHAPE_TYPE["HEART"] = "heart"; + SHAPE_TYPE["HEPTAGON"] = "heptagon"; + SHAPE_TYPE["HEXAGON"] = "hexagon"; + SHAPE_TYPE["HORIZONTAL_SCROLL"] = "horizontalScroll"; + SHAPE_TYPE["ISOSCELES_TRIANGLE"] = "triangle"; + SHAPE_TYPE["LEFT_ARROW"] = "leftArrow"; + SHAPE_TYPE["LEFT_ARROW_CALLOUT"] = "leftArrowCallout"; + SHAPE_TYPE["LEFT_BRACE"] = "leftBrace"; + SHAPE_TYPE["LEFT_BRACKET"] = "leftBracket"; + SHAPE_TYPE["LEFT_CIRCULAR_ARROW"] = "leftCircularArrow"; + SHAPE_TYPE["LEFT_RIGHT_ARROW"] = "leftRightArrow"; + SHAPE_TYPE["LEFT_RIGHT_ARROW_CALLOUT"] = "leftRightArrowCallout"; + SHAPE_TYPE["LEFT_RIGHT_CIRCULAR_ARROW"] = "leftRightCircularArrow"; + SHAPE_TYPE["LEFT_RIGHT_RIBBON"] = "leftRightRibbon"; + SHAPE_TYPE["LEFT_RIGHT_UP_ARROW"] = "leftRightUpArrow"; + SHAPE_TYPE["LEFT_UP_ARROW"] = "leftUpArrow"; + SHAPE_TYPE["LIGHTNING_BOLT"] = "lightningBolt"; + SHAPE_TYPE["LINE_CALLOUT_1"] = "borderCallout1"; + SHAPE_TYPE["LINE_CALLOUT_1_ACCENT_BAR"] = "accentCallout1"; + SHAPE_TYPE["LINE_CALLOUT_1_BORDER_AND_ACCENT_BAR"] = "accentBorderCallout1"; + SHAPE_TYPE["LINE_CALLOUT_1_NO_BORDER"] = "callout1"; + SHAPE_TYPE["LINE_CALLOUT_2"] = "borderCallout2"; + SHAPE_TYPE["LINE_CALLOUT_2_ACCENT_BAR"] = "accentCallout2"; + SHAPE_TYPE["LINE_CALLOUT_2_BORDER_AND_ACCENT_BAR"] = "accentBorderCallout2"; + SHAPE_TYPE["LINE_CALLOUT_2_NO_BORDER"] = "callout2"; + SHAPE_TYPE["LINE_CALLOUT_3"] = "borderCallout3"; + SHAPE_TYPE["LINE_CALLOUT_3_ACCENT_BAR"] = "accentCallout3"; + SHAPE_TYPE["LINE_CALLOUT_3_BORDER_AND_ACCENT_BAR"] = "accentBorderCallout3"; + SHAPE_TYPE["LINE_CALLOUT_3_NO_BORDER"] = "callout3"; + SHAPE_TYPE["LINE_CALLOUT_4"] = "borderCallout3"; + SHAPE_TYPE["LINE_CALLOUT_4_ACCENT_BAR"] = "accentCallout3"; + SHAPE_TYPE["LINE_CALLOUT_4_BORDER_AND_ACCENT_BAR"] = "accentBorderCallout3"; + SHAPE_TYPE["LINE_CALLOUT_4_NO_BORDER"] = "callout3"; + SHAPE_TYPE["LINE"] = "line"; + SHAPE_TYPE["LINE_INVERSE"] = "lineInv"; + SHAPE_TYPE["MATH_DIVIDE"] = "mathDivide"; + SHAPE_TYPE["MATH_EQUAL"] = "mathEqual"; + SHAPE_TYPE["MATH_MINUS"] = "mathMinus"; + SHAPE_TYPE["MATH_MULTIPLY"] = "mathMultiply"; + SHAPE_TYPE["MATH_NOT_EQUAL"] = "mathNotEqual"; + SHAPE_TYPE["MATH_PLUS"] = "mathPlus"; + SHAPE_TYPE["MOON"] = "moon"; + SHAPE_TYPE["NON_ISOSCELES_TRAPEZOID"] = "nonIsoscelesTrapezoid"; + SHAPE_TYPE["NOTCHED_RIGHT_ARROW"] = "notchedRightArrow"; + SHAPE_TYPE["NO_SYMBOL"] = "noSmoking"; + SHAPE_TYPE["OCTAGON"] = "octagon"; + SHAPE_TYPE["OVAL"] = "ellipse"; + SHAPE_TYPE["OVAL_CALLOUT"] = "wedgeEllipseCallout"; + SHAPE_TYPE["PARALLELOGRAM"] = "parallelogram"; + SHAPE_TYPE["PENTAGON"] = "homePlate"; + SHAPE_TYPE["PIE"] = "pie"; + SHAPE_TYPE["PIE_WEDGE"] = "pieWedge"; + SHAPE_TYPE["PLAQUE"] = "plaque"; + SHAPE_TYPE["PLAQUE_TABS"] = "plaqueTabs"; + SHAPE_TYPE["QUAD_ARROW"] = "quadArrow"; + SHAPE_TYPE["QUAD_ARROW_CALLOUT"] = "quadArrowCallout"; + SHAPE_TYPE["RECTANGLE"] = "rect"; + SHAPE_TYPE["RECTANGULAR_CALLOUT"] = "wedgeRectCallout"; + SHAPE_TYPE["REGULAR_PENTAGON"] = "pentagon"; + SHAPE_TYPE["RIGHT_ARROW"] = "rightArrow"; + SHAPE_TYPE["RIGHT_ARROW_CALLOUT"] = "rightArrowCallout"; + SHAPE_TYPE["RIGHT_BRACE"] = "rightBrace"; + SHAPE_TYPE["RIGHT_BRACKET"] = "rightBracket"; + SHAPE_TYPE["RIGHT_TRIANGLE"] = "rtTriangle"; + SHAPE_TYPE["ROUNDED_RECTANGLE"] = "roundRect"; + SHAPE_TYPE["ROUNDED_RECTANGULAR_CALLOUT"] = "wedgeRoundRectCallout"; + SHAPE_TYPE["ROUND_1_RECTANGLE"] = "round1Rect"; + SHAPE_TYPE["ROUND_2_DIAG_RECTANGLE"] = "round2DiagRect"; + SHAPE_TYPE["ROUND_2_SAME_RECTANGLE"] = "round2SameRect"; + SHAPE_TYPE["SMILEY_FACE"] = "smileyFace"; + SHAPE_TYPE["SNIP_1_RECTANGLE"] = "snip1Rect"; + SHAPE_TYPE["SNIP_2_DIAG_RECTANGLE"] = "snip2DiagRect"; + SHAPE_TYPE["SNIP_2_SAME_RECTANGLE"] = "snip2SameRect"; + SHAPE_TYPE["SNIP_ROUND_RECTANGLE"] = "snipRoundRect"; + SHAPE_TYPE["SQUARE_TABS"] = "squareTabs"; + SHAPE_TYPE["STAR_10_POINT"] = "star10"; + SHAPE_TYPE["STAR_12_POINT"] = "star12"; + SHAPE_TYPE["STAR_16_POINT"] = "star16"; + SHAPE_TYPE["STAR_24_POINT"] = "star24"; + SHAPE_TYPE["STAR_32_POINT"] = "star32"; + SHAPE_TYPE["STAR_4_POINT"] = "star4"; + SHAPE_TYPE["STAR_5_POINT"] = "star5"; + SHAPE_TYPE["STAR_6_POINT"] = "star6"; + SHAPE_TYPE["STAR_7_POINT"] = "star7"; + SHAPE_TYPE["STAR_8_POINT"] = "star8"; + SHAPE_TYPE["STRIPED_RIGHT_ARROW"] = "stripedRightArrow"; + SHAPE_TYPE["SUN"] = "sun"; + SHAPE_TYPE["SWOOSH_ARROW"] = "swooshArrow"; + SHAPE_TYPE["TEAR"] = "teardrop"; + SHAPE_TYPE["TRAPEZOID"] = "trapezoid"; + SHAPE_TYPE["UP_ARROW"] = "upArrow"; + SHAPE_TYPE["UP_ARROW_CALLOUT"] = "upArrowCallout"; + SHAPE_TYPE["UP_DOWN_ARROW"] = "upDownArrow"; + SHAPE_TYPE["UP_DOWN_ARROW_CALLOUT"] = "upDownArrowCallout"; + SHAPE_TYPE["UP_RIBBON"] = "ribbon2"; + SHAPE_TYPE["U_TURN_ARROW"] = "uturnArrow"; + SHAPE_TYPE["VERTICAL_SCROLL"] = "verticalScroll"; + SHAPE_TYPE["WAVE"] = "wave"; +})(SHAPE_TYPE || (SHAPE_TYPE = {})); +var CHART_TYPE; +(function (CHART_TYPE) { + CHART_TYPE["AREA"] = "area"; + CHART_TYPE["BAR"] = "bar"; + CHART_TYPE["BAR3D"] = "bar3D"; + CHART_TYPE["BUBBLE"] = "bubble"; + CHART_TYPE["BUBBLE3D"] = "bubble3D"; + CHART_TYPE["DOUGHNUT"] = "doughnut"; + CHART_TYPE["LINE"] = "line"; + CHART_TYPE["PIE"] = "pie"; + CHART_TYPE["RADAR"] = "radar"; + CHART_TYPE["SCATTER"] = "scatter"; +})(CHART_TYPE || (CHART_TYPE = {})); +var SCHEME_COLOR_NAMES; +(function (SCHEME_COLOR_NAMES) { + SCHEME_COLOR_NAMES["TEXT1"] = "tx1"; + SCHEME_COLOR_NAMES["TEXT2"] = "tx2"; + SCHEME_COLOR_NAMES["BACKGROUND1"] = "bg1"; + SCHEME_COLOR_NAMES["BACKGROUND2"] = "bg2"; + SCHEME_COLOR_NAMES["ACCENT1"] = "accent1"; + SCHEME_COLOR_NAMES["ACCENT2"] = "accent2"; + SCHEME_COLOR_NAMES["ACCENT3"] = "accent3"; + SCHEME_COLOR_NAMES["ACCENT4"] = "accent4"; + SCHEME_COLOR_NAMES["ACCENT5"] = "accent5"; + SCHEME_COLOR_NAMES["ACCENT6"] = "accent6"; +})(SCHEME_COLOR_NAMES || (SCHEME_COLOR_NAMES = {})); +var MASTER_OBJECTS; +(function (MASTER_OBJECTS) { + MASTER_OBJECTS["chart"] = "chart"; + MASTER_OBJECTS["image"] = "image"; + MASTER_OBJECTS["line"] = "line"; + MASTER_OBJECTS["rect"] = "rect"; + MASTER_OBJECTS["text"] = "text"; + MASTER_OBJECTS["placeholder"] = "placeholder"; +})(MASTER_OBJECTS || (MASTER_OBJECTS = {})); +var SLIDE_OBJECT_TYPES; +(function (SLIDE_OBJECT_TYPES) { + SLIDE_OBJECT_TYPES["chart"] = "chart"; + SLIDE_OBJECT_TYPES["hyperlink"] = "hyperlink"; + SLIDE_OBJECT_TYPES["image"] = "image"; + SLIDE_OBJECT_TYPES["media"] = "media"; + SLIDE_OBJECT_TYPES["online"] = "online"; + SLIDE_OBJECT_TYPES["placeholder"] = "placeholder"; + SLIDE_OBJECT_TYPES["table"] = "table"; + SLIDE_OBJECT_TYPES["tablecell"] = "tablecell"; + SLIDE_OBJECT_TYPES["text"] = "text"; + SLIDE_OBJECT_TYPES["notes"] = "notes"; +})(SLIDE_OBJECT_TYPES || (SLIDE_OBJECT_TYPES = {})); +var PLACEHOLDER_TYPES; +(function (PLACEHOLDER_TYPES) { + PLACEHOLDER_TYPES["title"] = "title"; + PLACEHOLDER_TYPES["body"] = "body"; + PLACEHOLDER_TYPES["image"] = "pic"; + PLACEHOLDER_TYPES["chart"] = "chart"; + PLACEHOLDER_TYPES["table"] = "tbl"; + PLACEHOLDER_TYPES["media"] = "media"; +})(PLACEHOLDER_TYPES || (PLACEHOLDER_TYPES = {})); +/** + * NOTE: 20170304: BULLET_TYPES: Only default is used so far. I'd like to combine the two pieces of code that use these before implementing these as options + * Since we close

within the text object bullets, its slightly more difficult than combining into a func and calling to get the paraProp + * and i'm not sure if anyone will even use these... so, skipping for now. + */ +var BULLET_TYPES; +(function (BULLET_TYPES) { + BULLET_TYPES["DEFAULT"] = "•"; + BULLET_TYPES["CHECK"] = "✓"; + BULLET_TYPES["STAR"] = "★"; + BULLET_TYPES["TRIANGLE"] = "▶"; +})(BULLET_TYPES || (BULLET_TYPES = {})); +// IMAGES (base64) +var IMG_BROKEN = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAB3CAYAAAD1oOVhAAAGAUlEQVR4Xu2dT0xcRRzHf7tAYSsc0EBSIq2xEg8mtTGebVzEqOVIolz0siRE4gGTStqKwdpWsXoyGhMuyAVJOHBgqyvLNgonDkabeCBYW/8kTUr0wsJC+Wfm0bfuvn37Znbem9mR9303mJnf/Pb7ed95M7PDI5JIJPYJV5EC7e3t1N/fT62trdqViQCIu+bVgpIHEo/Hqbe3V/sdYVKHyWSSZmZm8ilVA0oeyNjYmEnaVC2Xvr6+qg5fAOJAz4DU1dURGzFSqZRVqtMpAFIGyMjICC0vL9PExIRWKADiAYTNshYWFrRCARAOEFZcCKWtrY0GBgaUTYkBRACIE4rKZwqACALR5RQAqQCIDqcASIVAVDsFQCSAqHQKgEgCUeUUAPEBRIVTAMQnEBvK5OQkbW9vk991CoAEAMQJxc86BUACAhKUUwAkQCBBOAVAAgbi1ykAogCIH6cAiCIgsk4BEIVAZJwCIIqBVLqiBxANQFgXS0tLND4+zl08AogmIG5OSSQS1gGKwgtANAIRcQqAaAbCe6YASBWA2E6xDyeyDUl7+AKQMkDYYevm5mZHabA/Li4uUiaTsYLau8QA4gLE/hU7wajyYtv1hReDAiAOxQcHBymbzark4BkbQKom/X8dp9Npmpqasn4BIAYAYSnYp+4BBEAMUcCwNOCQsAKZnp62NtQOw8WmwT09PUo+ijaHsOMx7GppaaH6+nolH0Z10K2tLVpdXbW6UfV3mNqBdHd3U1NTk2rtlMRfW1uj2dlZAFGirkRQAJEQTWUTAFGprkRsAJEQTWUTAFGprkRsAJEQTWUTAFGprkRsAJEQTWUTAFGprkRsAJEQTWUTAFGprkRsAJEQTWUTAGHqrm8caPzQ0WC1logbeiC7X3xJm0PvUmRzh45cuki1588FAmVn9BO6P3yF9utrqGH0MtW82S8UN9RA9v/4k7InjhcJFTs/TLVXLwmJV67S7vD7tHF5pKi46fYdosdOcOOGG8j1OcqefbFEJD9Q3GCwDhqT31HklS4A8VRgfYM2Op6k3bt/BQJl58J7lPvwg5JYNccepaMry0LPqFA7hCm39+NNyp2J0172b19QysGINj5CsRtpij57musOViH0QPJQXn6J9u7dlYJSFkbrMYolrwvDAJAC+WWdEpQz7FTgECeUCpzi6YxvvqXoM6eEhqnCSgDikEzUKUE7Aw7xuHctKB5OYU3dZlNR9syQdAaAcAYTC0pXF+39c09o2Ik+3EqxVKqiB7hbYAxZkk4pbBaEM+AQofv+wTrFwylBOQNABIGwavdfe4O2pg5elO+86l99nY58/VUF0byrYsjiSFluNlXYrOHcBar7+EogUADEQ0YRGHbzoKAASBkg2+9cpM1rV0tK2QOcXW7bLEFAARAXIF4w2DrDWoeUWaf4hQIgDiA8GPZ2iNfi0Q8UACkAIgrDbrJ385eDxaPLLrEsFAB5oG6lMPJQPLZZZKAACBGVhcG2Q+bmuLu2nk55e4jqPv1IeEoceiBeX7s2zCa5MAqdstl91vfXwaEGsv/rb5TtOFk6tWXOuJGh6KmnhO9sayrMninPx103JBtXblHkice58cINZP4Hyr5wpkgkdiChEmc4FWazLzenNKa/p0jncwDiqcD6BuWePk07t1asatZGoYQzSqA4nFJ7soNiP/+EUyfc25GI2GG53dHPrKo1g/1Cw4pIXLrzO+1c+/wg7tBbFDle/EbQcjFCPWQJCau5EoBoFpzXHYDwFNJcDiCaBed1ByA8hTSXA4hmwXndAQhPIc3lAKJZcF53AMJTSHM5gGgWnNcdgPAU0lwOIJoF53UHIDyFNJcfSiCdnZ0Ui8U0SxlMd7lcjubn561gh+Y1scFIU/0o/3sgeLO12E2k7UXKYumgFoAYdg8ACIAYpoBh6cAhAGKYAoalA4cAiGEKGJYOHAIghilgWDpwCIAYpoBh6cAhAGKYAoalA4cAiGEKGJYOHAIghilgWDpwCIAYpoBh6ZQ4JB6PKzviYthnNy4d9h+1M5mMlVckkUjsG5dhiBMCEMPg/wuOfrZZ/RSywQAAAABJRU5ErkJggg=='; +var IMG_PLAYBTN = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAB4AAAAVnCAYAAACzfHDVAAAAYHpUWHRSYXcgcHJvZmlsZSB0eXBlIGV4aWYAAHjaVcjJDYAwDEXBu6ughBfH+YnLQSwSHVA+Yrkwx7HtPHabHuEWrQ+lBBAZ6TMweBWoCwUH8quZH6VWFXVT696zxp12ARkVFEqn8wB8AAAACXBIWXMAAC4jAAAuIwF4pT92AADZLklEQVR42uzdd5hV9Z0/8M+dmcsUZmDovYOhKCiKYhR7JJuoSTCWGFI0WUxijBoTTXazVlyza4maYm9rTRSJigVsqCDNQhHBAogKCEgRMjMMU+7vj93sL8kqClLmnPt6PY+PeXZM9vP9vO8jZ+Y955xMfJLjorBrRMuSgmiViyjN1Ee2oSCyucbIBAAAAAAAAADbXaYgcoWNUZcrirpMbdRsysa69wbF+rggGrf439vSF7seF12aFUTnxvoosGIAAAAAAACAXacgoqEgF++/VRgr4r5o+Kh/pvD//F8uiII+LaPrum/EXzqui2b1ddHGKgEAAAAAAAB2rVxEQWMmWrQtjHZlA6N2w2tR84//zP8pgHu3ib6NBdG+zdqorK6KVUXZaB85j3sGAAAAAAAAaAoaG6OwIBdtyneP2PBabPzbr/1dAdx3VHRtyESHiIhcYzQrLo7WmVzkcjmPgAYAAAAAAABoSgpy0eIfS+D/LYD7fy3abC6Inn/7X2hsjELlLwAAAAAAAEDT9D8lcM1fHwddFBFxyAVR9M686PVp/gfqayKiJiLqLBMAAAAAAABgh8hGRGlEUekn/6PFEb3ikNgQk6O+KCJi6dzoksv83/cB/1X9xoiaJdmoWxlRV1dk2QAAAAAAAAA7QTZbH9muERX96v7n9t7/q6Exinq3i86LI94pjOOisHUu+uYykfmof7h+Y8Sa6aVRt74gGhs9DRoAAAAAAABgZ2lsLIi69QWxeUUmSjs0/vedwR8hk4uydSfE+wVd6qOyMfMx7/mtj9jwUtbjngEAAAAAAAB2obrqolg7IxtR/9Ffb4wo7P5GtCwobRaVH/c/UvNmNuqqPfIZAAAAAAAAYFerqy6KmjezH/v1ktpoVZBr/PgCeMN7yl8AAAAAAACApmJLHW5jUVQWNDSP+Q3ZeLco4i9/+8X6teHRzwAAAAAAAABNSd3/dLn/oLAoqqIuVhXFxhhSGB/xqGjlLwAAAAAAAECTU1eTjaK/KXSLIv7SWB+bc5ko9YxnAAAAAAAAgATJFv393bz1EeV//c8F1gMAAAAAAACQDgpgAAAAAAAAgJRQAAMAAAAAAACkhAIYAAAAAAAAICUUwAAAAAAAAAApoQAGAAAAAAAASAkFMAAAAAAAAEBKKIABAAAAAAAAUkIBDAAAAAAAAJASCmAAAAAAAACAlFAAAwAAAAAAAKSEAhgAAAAAAAAgJRTAAAAAAAAAACmhAAYAAAAAAABICQUwAAAAAAAAQEoogAEAAAAAAABSQgEMAAAAAAAAkBIKYAAAAAAAAICUUAADAAAAAAAApIQCGAAAAAAAACAlFMAAAAAAAAAAKaEABgAAAAAAAEgJBTAAAAAAAABASiiAAQAAAAAAAFJCAQwAAAAAAACQEgpgAAAAAAAAgJRQAAMAAAAAAACkhAIYAAAAAAAAICUUwAAAAAAAAAApoQAGAAAAAAAASAkFMAAAAAAAAEBKKIABAAAAAAAAUkIBDAAAAAAAAJASCmAAAAAAAACAlFAAAwAAAAAAAKSEAhgAAAAAAAAgJRTAAAAAAAAAACmhAAYAAAAAAABICQUwAAAAAAAAQEoogAEAAAAAAABSQgEMAAAAAAAAkBIKYAAAAAAAAICUUAADAAAAAAAApIQCGAAAAAAAACAlFMAAAAAAAAAAKaEABgAAAAAAAEgJBTAAAAAAAABASiiAAQAAAAAAAFJCAQwAAAAAAACQEgpgAAAAAAAAgJRQAAMAAAAAAACkhAIYAAAAAAAAICUUwAAAAAAAAAApoQAGAAAAAAAASAkFMAAAAAAAAEBKKIABAAAAAAAAUkIBDAAAAAAAAJASCmAAAAAAAACAlFAAAwAAAAAAAKSEAhgAAAAAAAAgJRTAAAAAAAAAACmhAAYAAAAAAABICQUwAAAAAAAAQEoogAEAAAAAAABSQgEMAAAAAAAAkBIKYAAAAAAAAICUUAADAAAAAAAApIQCGAAAAAAAACAlFMAAAAAAAAAAKaEABgAAAAAAAEgJBTAAAAAAAABASiiAAQAAAAAAAFJCAQwAAAAAAACQEgpgAAAAAAAAgJRQAAMAAAAAAACkhAIYAAAAAAAAICUUwAAAAAAAAAApoQAGAAAAAAAASAkFMAAAAAAAAEBKKIABAAAAAAAAUkIBDAAAAAAAAJASCmAAAAAAAACAlFAAAwAAAAAAAKSEAhgAAAAAAAAgJRTAAAAAAAAAACmhAAYAAAAAAABICQUwAAAAAAAAQEoogAEAAAAAAABSQgEMAAAAAAAAkBIKYAAAAAAAAICUUAADAAAAAAAApIQCGAAAAAAAACAlFMAAAAAAAAAAKaEABgAAAAAAAEgJBTAAAAAAAABASiiAAQAAAAAAAFJCAQwAAAAAAACQEgpgAAAAAAAAgJRQAAMAAAAAAACkhAIYAAAAAAAAICUUwAAAAAAAAAApoQAGAAAAAAAASAkFMAAAAAAAAEBKKIABAAAAAAAAUkIBDAAAAAAAAJASCmAAAAAAAACAlFAAAwAAAAAAAKSEAhgAAAAAAAAgJRTAAAAAAAAAACmhAAYAAAAAAABICQUwAAAAAAAAQEoogAEAAAAAAABSQgEMAAAAAAAAkBIKYAAAAAAAAICUUAADAAAAAAAApIQCGAAAAAAAACAlFMAAAAAAAAAAKaEABgAAAAAAAEgJBTAAAAAAAABASiiAAQAAAAAAAFJCAQwAAAAAAACQEgpgAAAAAAAAgJRQAAMAAAAAAACkhAIYAAAAAAAAICUUwAAAAAAAAAApoQAGAAAAAAAASAkFMAAAAAAAAEBKKIABAAAAAAAAUkIBDAAAAAAAAJASCmAAAAAAAACAlFAAAwAAAAAAAKSEAhgAAAAAAAAgJRTAAAAAAAAAACmhAAYAAAAAAABICQUwAAAAAAAAQEoogAEAAAAAAABSQgEMAAAAAAAAkBIKYAAAAAAAAICUUAADAAAAAAAApIQCGAAAAAAAACAlFMAAAAAAAAAAKaEABgAAAAAAAEgJBTAAAAAAAABASiiAAQAAAAAAAFJCAQwAAAAAAACQEgpgAAAAAAAAgJRQAAMAAAAAAACkhAIYAAAAAAAAICUUwAAAAAAAAAApoQAGAAAAAAAASAkFMAAAAAAAAEBKKIABAAAAAAAAUkIBDAAAAAAAAJASCmAAAAAAAACAlFAAAwAAAAAAAKSEAhgAAAAAAAAgJRTAAAAAAAAAACmhAAYAAAAAAABICQUwAAAAAAAAQEoogAEAAAAAAABSQgEMAAAAAAAAkBIKYAAAAAAAAICUUAADAAAAAAAApIQCGAAAAAAAACAlFMAAAAAAAAAAKaEABgAAAAAAAEgJBTAAAAAAAABASiiAAQAAAAAAAFJCAQwAAAAAAACQEgpgAAAAAAAAgJRQAAMAAAAAAACkhAIYAAAAAAAAICUUwAAAAAAAAAApoQAGAAAAAAAASAkFMAAAAAAAAEBKKIABAAAAAAAAUkIBDAAAAAAAAJASCmAAAAAAAACAlFAAAwAAAAAAAKSEAhgAAAAAAAAgJRTAAAAAAAAAACmhAAYAAAAAAABICQUwAAAAAAAAQEoogAEAAAAAAABSQgEMAAAAAAAAkBIKYAAAAAAAAICUUAADAAAAAAAApIQCGAAAAAAAACAlFMAAAAAAAAAAKaEABgAAAAAAAEgJBTAAAAAAAABASiiAAQAAAAAAAFJCAQwAAAAAAACQEgpgAAAAAAAAgJRQAAMAAAAAAACkhAIYAAAAAAAAICUUwAAAAAAAAAApoQAGAAAAAAAASAkFMAAAAAAAAEBKKIABAAAAAAAAUkIBDAAAAAAAAJASCmAAAAAAAACAlFAAAwAAAAAAAKSEAhgAAAAAAAAgJRTAAAAAAAAAACmhAAYAAAAAAABICQUwAAAAAAAAQEoogAEAAAAAAABSQgEMAAAAAAAAkBIKYAAAAAAAAICUUAADAAAAAAAApIQCGAAAAAAAACAlFMAAAAAAAAAAKaEABgAAAAAAAEgJBTAAAAAAAABASiiAAQAAAAAAAFJCAQwAAAAAAACQEgpgAAAAAAAAgJRQAAMAAAAAAACkhAIYAAAAAAAAICUUwAAAAAAAAAApoQAGAAAAAAAASAkFMAAAAAAAAEBKKIABAAAAAAAAUkIBDAAAAAAAAJASCmAAAAAAAACAlFAAAwAAAAAAAKSEAhgAAAAAAAAgJRTAAAAAAAAAACmhAAYAAAAAAABICQUwAAAAAAAAQEoogAEAAAAAAABSQgEMAAAAAAAAkBIKYAAAAAAAAICUUAADAAAAAAAApIQCGAAAAAAAACAlFMAAAAAAAAAAKaEABgAAAAAAAEgJBTAAAAAAAABASiiAAQAAAAAAAFJCAQwAAAAAAACQEgpgAAAAAAAAgJRQAAMAAAAAAACkhAIYAAAAAAAAICUUwAAAAAAAAAApoQAGAAAAAAAASAkFMAAAAAAAAEBKKIABAAAAAAAAUkIBDAAAAAAAAJASCmAAAAAAAACAlFAAAwAAAAAAAKSEAhgAAAAAAAAgJRTAAAAAAAAAACmhAAYAAAAAAABICQUwAAAAAAAAQEoogAEAAAAAAABSQgEMAAAAAAAAkBIKYAAAAAAAAICUUAADAAAAAAAApIQCGAAAAAAAACAlFMAAAAAAAAAAKaEABgAAAAAAAEgJBTAAAAAAAABASiiAAQAAAAAAAFJCAQwAAAAAAACQEgpgAAAAAAAAgJRQAAMAAAAAAACkhAIYAAAAAAAAICUUwAAAAAAAAAApoQAGAAAAAAAASAkFMAAAAAAAAEBKKIABAAAAAAAAUkIBDAAAAAAAAJASCmAAAAAAAACAlFAAAwAAAAAAAKSEAhgAAAAAAAAgJRTAAAAAAAAAACmhAAYAAAAAAABICQUwAAAAAAAAQEoogAEAAAAAAABSQgEMAAAAAAAAkBIKYAAAAAAAAICUUAADAAAAAAAApIQCGAAAAAAAACAlFMAAAAAAAAAAKaEABgAAAAAAAEgJBTAAAAAAAABASiiAAQAAAAAAAFJCAQwAAAAAAACQEgpgAAAAAAAAgJRQAAMAAAAAAACkhAIYAAAAAAAAICUUwAAAAAAAAAApoQAGAAAAAAAASAkFMAAAAAAAAEBKKIABAAAAAAAAUkIBDAAAAAAAAJASCmAAAAAAAACAlFAAAwAAAAAAAKREkRUAAACwrUpLSwuGDRvWfMCAAS26du3avKysrLiioqKkZcuWzZs1a1bcvHnz0tLS0rJsNtusuLi4ebNmzUoLCgo+8/eijY2N9Zs3b66pra2tqqur21xTU1NdVVVVs2nTptqNGzdWbdiwoeYvf/nL5hUrVlQtWLBgw6xZs6pqamoaJQYAAEDaKYABAACIiIghQ4aUHnTQQW379u3bql27dq3at2/fpkWLFq2bN29eWVpa2qpZs2bNCwsLm2ez2fLCwsLyoqKi8sLCwtKknK+hoaG6vr6+qqGh4S91dXV/aWhoqNq8eXNVTU3NuqqqqvUbNmxYu2rVqjWrV69e99Zbb6177rnnPpgzZ06NTwYAAABJogAGAADIA8OGDWt+xBFHdBwwYECnLl26dGjdunXHFi1adCgtLe1YUlLSvlmzZq0KCgqK07yDwsLCssLCwrKIaPdp/zuNjY21mzdvXrdp06ZVNTU172/YsGHl2rVr31+2bNnKBQsWrHjyySffnzVrVpVPGAAAAE1Fpuexsd9HfaF+ZcSal0ptCAAAIAE6deqUPf744zvtueeeXbp3796lbdu2XSorKzuXlpZ2KS0t7VBYWFhhSztGQ0PDxpqampU1NTXL169fv+yDDz5Y9s477yybPXv2sj/96U8rVqxYUWdLAAAAbE9t9q6Jog4f/TUFMAAAQEJks9nMt7/97Y4jRozo1bdv397t2rXrXl5e3rWsrKxzcXFx+4gosKUmp7G2tnZVTU3Nso0bNy5btWrV0tdff/2tJ598cvG999672noAAADYFgpgAACAhPne977X6a9Fb/v27Xu1bNmyV1lZWa8kvXOXLauvr9/wl7/8ZdG6desWL1u2bNHChQsX/fGPf1w8derUjbYDAADAliiAAQAAmqhsNps59dRTuxx66KH9+/Tp87n27dv3Ly8v719UVOSRzXlq06ZNKzZu3Pj6+++//8abb775xqOPPvrG3XffvcpmAAAA+CsFMAAAQBNx6qmndvniF784qHfv3v3btWv3uYqKis8VFhaW2wxbUl9fv37Dhg1vfPDBB68vXrz4jccee2z+jTfeuNxmAAAA8pMCGAAAYBc45phjWn/rW9/aq3///kPatGnTv6Kiop9HOLO9NDQ0VG/cuPGtNWvWLFy4cOGcO+6445WHHnporc0AAACknwIYAABgJzjjjDO6f+lLX9qrV69eg1u3bj2orKysR0RkbIadJFddXb103bp18xcvXjz30UcffeXqq69+x1oAAADSRwEMAACwnZWWlhb86le/2u3QQw8d1r17931btmw5qLCwsMxmaEoaGhqqP/zww/nvvPPOzGeeeWbW2LFj36ipqWm0GQAAgGRTAAMAAGwHP/7xj7t+9atf3bdXr15D27Ztu1c2m21jKyRJXV3dmg8++OCVRYsWvfznP/95xh/+8IdltgIAAJA8CmAAAIBtcOKJJ7Y75ZRTDujXr9+w1q1bD81ms61shTSpq6tbt3bt2pfffPPNWbfccsvUe++9d7WtAAAANH0KYAAAgE+hoqKi4IILLhg0YsSI/bp27bpfy5YtB2YymUKbIR/kcrmGDz/8cP6777474/nnn59x4YUXvrZx40aPiwYAAGiCFMAAAAAf4/jjj2/7/e9//8D+/fsf2Lp1630KCgpKbAUiGhsbN61fv37eW2+9NeWGG2545u67715lKwAAAE2DAhgAAOB/ZLPZzAUXXPC5I4888sDu3bsfWFFRsVtEFNgMbFl1dfWSd999d8qsWbNmnnvuuS+vW7euwVYAAAB2DQUwAACQ10pLSwsuvfTSQYcccsjBXbt2HVFWVtbDVmDb1dbWrnr//fdfmDp16uRf/vKXL65evbreVgAAAHYeBTAAAJB3Bg0aVHrBBRd8fs899zywQ4cOBxQVFbWwFdj+Ghsba9euXTtrzpw5T59//vmTX3755WpbAQAA2LEUwAAAQF4YNmxY8/POO+/gIUOGHOZ9vrDz/W0ZfNFFFz07a9asKlsBAADY/hTAAABAarVq1arwyiuv3HfEiBEjO3TocFBhYWGZrcCu19DQUP3+++8/O2XKlIk/+clPZm7cuLHRVgAAALYPBTAAAJAqrVq1Kvztb3+7/3777Xd4x44dRxQWFpbbCjRdDQ0NG99///0pM2bMeOqHP/zhC8pgAACAz0YBDAAApMJZZ53V45vf/OaRvXr1GllaWtrVRiB5ampq3l28ePHEO++8c9LVV1/9jo0AAABsPQUwAACQWMOHDy+/6KKLvjB48OCjW7RoMdBGID0+/PDDV+fNmzfhvPPOe3L69Ol/sREAAIBPRwEMAAAkSqtWrQpvuOGGQ/bbb79/atOmzX6ZTCZrK5BeuVyubs2aNTNmzJjx2JgxYyavW7euwVYAAAA+ngIYAABIhB//+Mddv/e9732lZ8+e/1RcXNzWRiD/1NbWfvD2228/dssttzz029/+9l0bAQAA+L8UwAAAQJNVUVFRcO21137+4IMPPrZ169b7ZTKZAlsBIqJxzZo1M59//vnxp5122hR3BQMAAPx/CmAAAKDJOeWUUzqefvrpx/bu3ftL2Wy2jY0AH6e+vn7j0qVLH/vd7373x+uvv36ZjQAAAPlOAQwAADQJ2Ww2c+uttx5wyCGHnNC6deu9I8LdvsDWaFy7du1L06ZN+/OPfvSjZ1evXl1vJQAAQD5SAAMAALtU//79S6655pp/2nPPPY8tLy/vayPAZ1VTU7NswYIF488999wHp06dutFGAACAfKIABgAAdomf//znPU855ZQTu3btemRhYWGZjQDbW2NjY92KFSuevOWWW+689NJLF9kIAACQDxTAAADATuMxz8Cusn79+rlPP/30f5188slT6+rqcjYCAACklQIYAADY4fr27Vv8hz/84a+Pee5nI8CuUlNT8+68efPu/8EPfvDgwoULN9kIAACQNgpgAABghxkyZEjpNddc89XBgwefWFxc3MFGgKaitrZ21dy5c+/5yU9+8uc5c+bU2AgAAJAWWyqAPYoNAADYJqNHj+4wb968n06ZMuXRYcOGnaH8BZqa4uLi9sOGDTtjypQpj86bN++nJ510UntbAQAA0s4dwAAAwFY599xze33/+9//dufOnY/IZDJZGwGSIpfL1S1fvvzJG2644fbLLrvsbRsBAACSyiOgAQCAz+y8887r+53vfOfbHTt2PDyTyRTaCJBUuVyuYcWKFU/cdNNN//XrX/96sY0AAABJowAGAAC22WWXXTboG9/4xg9at249zDaAtFm7du2su++++9pzzjnnNdsAAACSQgEMAABsNcUvkE8UwQAAQJIogAEAgE9N8Qvks7Vr18665557rvv5z38+3zYAAICmaksFcGHlwOj6UV9orIqoWZG1PQAAyBO/+MUvet9xxx3nHHrooT8pLS3tYiNAPiotLe2y7777HvP973+/X1lZ2ZIpU6assxUAAKCpKetcHwXlH/01BTAAAOS5M844o/u99957zpe//OWflZeX94qIjK0AeS5TXl7e8+CDDx71/e9/v3dEvDVjxowPrQUAAGgqFMAAAMD/ceKJJ7a77777fjJq1Kh/KS8v7xOKX4B/lCkvL+99+OGHj/rWt77VfvXq1Qvnz59fbS0AAMCutqUC2DuAAQAgzwwdOrTs+uuvP6l///4nFRYWltkI20NjY2Ns2rQpqquro6amJurr62PTpk2xefPmqK+vj+rq6qivr4/NmzfHpk2boqGhYZv/fxUWFkZJSUk0a9YsioqKoqysLIqKiqJZs2ZRUlISRUVFUVpa+r9/FRQUCIjtoqGhoeq11167a8yYMffMmTOnxkYAAIBdZUvvAFYAAwBAnujUqVP2nnvuGbXXXnudnM1mK22Ej9PQ0BAbN26MDRs2/J+/Nm7cGBs3boyamprYtGlTbNq0KWpqaqK2trbJnqe4uDhKSkqitLT0f/9eUVERFRUV0aJFi//zV0VFRRQWFvog8LHq6urWvvjii7eceOKJf169enW9jQAAADubAhgAAPLcXXfdddAXv/jF00tLS7vZRn7L5XKxYcOGWLt2baxbty7Wrl37d3+tW7cuNmzYkPd7atGiRbRu3TpatWoVrVu3jjZt2vzvf27dunW0aNHCh4morq5e+sgjj1zzne98Z6ptAAAAO5MCGAAA8tTVV189+MQTTzyzoqJioG3kj8bGxli5cmUsX748Pvjgg1i9evX//n3t2rXR2NhoSZ9RYWFhtGrVKtq1axdt27b937937tw5OnTo4LHTeWbDhg3z77333qvOPPPMebYBAADsDApgAADIM1/72tfaXHrppad27979qIjQRKVUQ0NDrFq1KlasWBHvv//+//595cqVTfqRzGlXXFwcHTp0iI4dO0bnzp2jY8eO0alTp2jXrp1HS6dYLpdrfOeddx76+c9/fv2ECRPW2QgAALAjKYABACBP9OrVq9ldd931jT322OM7hYWFZTaSHh9++GG88847sXTp0njvvfdixYoVsXr16mhoaLCchCgsLIz27dtHp06dolu3btG9e/fo3r27x0mnTENDQ9W8efNu++Y3v/nHJUuWbLYRAABgR1AAAwBAHrjrrrtG/NM//dOZJSUlXWwj2davXx9Lly6Nd955539L3w8//NBiUqqysvJ/y+C//tWqVSuLSbiamppljz322G9Gjx49xTYAAIDtTQEMAAAp9qtf/arPD3/4w5+1atVqL9tIno0bN8aSJUvirbfeikWLFsV7770XmzZtspg8V1JSEl27do0+ffpE3759o3fv3lFeXm4xCbRu3bqXr7322ivGjh27yDYAAIDtRQEMAAApNGjQoNI77rjju7vttttJBQUFWRtJhtWrV8ebb74ZixcvjiVLlsTy5cujsbHRYtiigoKC6Ny5c/Tu3Tt69+4d/fr1i7Zt21pMQjQ2Nta98cYbd33rW9+6ff78+TU2AgAAfFYKYAAASJHS0tKCBx988Jj99tvvn7PZbBsbaboaGhri7bffjrfeeisWLFgQS5YscXcv201FRUX06tUr+vbtG3379o2ePXtGYWGhxTRhdXV1a2bMmHHjV77ylYdqamr85gcAALDNFMAAAJASp59+erdf/vKX51ZWVu5jG03T6tWr47XXXouFCxfGm2++GRs3brQUdooWLVpE3759Y8CAATFw4EB3CDdh69evf/E//uM//vPqq69+xzYAAIBtoQAGAICEGzRoUOm99977w969ex+byWTc4teErF+/PubNmxcLFiyIN954Q+FLk9GiRYvo169fDBgwIPbYY4+orKy0lCYkl8s1LF68eNyJJ554rcdCAwAAW0sBDAAACXbNNdcMOemkk35RVlbWyzZ2vVwuF++++27MnTs3XnvttViyZIl3+NLkFRQURK9evWLQoEExePDg6Natm6U0EdXV1UvuvvvuX//kJz+ZYxsAAMCnpQAGAIAEOuqoo1r99re//VmHDh0Ot41da9OmTTF79uyYO3duLFy4MKqqqiyFRGvevHn0798/Bg8eHHvuuWeUlJRYyi62cuXKp04//fTLJ0yYsM42AACAT6IABgCAhBk3btwRRxxxxFnZbLaNbewaVVVVMXfu3Jg7d27Mnz8/amtrLYVUKi4ujoEDB8bgwYNj8ODBUV5ebim7SF1d3ZqnnnrqqlGjRj1hGwAAwJYogAEAICFOOeWUjhdddNEvW7duvZ9t7HwrV66MWbNmxdy5c+Odd96JXC5nKeSdzp07x9577x3Dhg2LDh06WMgusHbt2hnnnXfepbfccsv7tgEAAHwUBTAAADRxpaWlBU899dQ3Bw8e/L2CggLPYt2JVqxYES+99FK89NJLsXz5cguBv/HXMnjvvfeOTp06WchO1NjYuGnu3Lk3H3744XfV1NR40TgAAPB3FMAAANCEjR49usOll176yzZt2gy3jZ1j/fr18eKLL8bMmTNj6dKlFgKfQs+ePWPfffeNYcOGRYsWLSxkJ1mzZs0L55577q/vvvvuVbYBAAD8lQIYAACaoIqKioKJEyd+c/Dgwd8vKCgotpEda8OGDfHiiy/G9OnTlb7wGfXo0SOGDx8ew4YNi4qKCgvZwdwNDAAA/CMFMAAANDGnnHJKx7Fjx/5rZWXlMNvYcerr6+PVV1+NGTNmxLx586Kurs5SYDvKZrMxZMiQ2HfffWP33XePwsJCS9mB1q5dO+MXv/jFv995550rbQMAAPKbAhgAAJqIbDabeeKJJ47fZ599fuSu3x0jl8vFwoULY/r06TF79uzYtGmTpcBOUFpaGkOGDInhw4fHgAEDLGQHaWhoqJ42bdo1Rx555J9tAwAA8pcCGAAAmoDjjz++7ZVXXvmr1q1be9fvDrBmzZqYNm1azJw5M1audHMc7EodO3aMz3/+87H//vt7X/CO+3fetDPPPPOScePGfWAbAACQfxTAAACwi9100037HXvssf9WXFzc1ja2n1wuF6+99lo8//zzMW/evKivr7cUaEKKiopizz33jBEjRsTnPve5yGQylrId1dbWrvrjH/948Q9+8INZtgEAAPlFAQwAALvIkCFDSu+///5zunTp8k+2sf2sXbs2Jk+eHNOnT48PP/zQQiABKisrY8SIEXHIIYdEeXm5hWxHy5Yte+zrX//6f86ZM6fGNgAAID9sqQAurBwYXT/qC41VETUrsrYHAADb6IILLtjt97///VVt2rQZZhvbx+LFi2P8+PFx9913xxtvvBG1tbWWAgmxadOmeOONN+LZZ5+NtWvXRps2bTweejtp0aJFv5NOOumg0tLSuc8+++xaGwEAgPQr61wfBR/zu7XuAAYAgO0sm81mJk2a9PVhw4b9pKCgwG9VfkZ1dXUxY8aMeOaZZ+K9996zEEiRfv36xSGHHBJDhw6NgoICC/mMGhsbN8+YMeOaL37xi+Pq6upyNgIAAOnlEdAAALCTHH/88W2vuuqqCyorK/exjc9mzZo18dRTT8XUqVNj06ZNFgIpVlFREZ///OfjsMMOi8rKSgv5jNavXz/r9NNPv3DcuHEf2AYAAKSTAhgAAHaC22677fNf+9rXzstms5W2se0WLVoUjz/+eMybNy9yOTewQT4pKiqKIUOGxBFHHBG9e/e2kM+grq5u3QMPPHDRySefPM02AAAgfRTAAACwA1VUVBQ8/fTTpwwcOPCUTCbjGabbIJfLxauvvhpPPvlkLFy40EIgz2UymRgwYEAcccQRMWjQIAvZ9n+3Ns6fP/+Www8//JaNGzc22ggAAKTHlgrgwsqB0fWjvtBYFVGzwuvKAABgS0488cR2EyZMuLx79+5fzmQyGRvZOo2NjTFr1qy49dZb48knn4wPPvC0UuC/rV69OmbMmBFz5syJ0tLS6NSpU/jX7NbJZDKZ9u3bD/3+978/dPny5TNfffXValsBAIB0KOtcHwXlH/O9gDuAAQBg29x66637H3vssRcWFRW1sI2tU1NTE0899VQ8++yzsWHDBgsBPlGLFi3i4IMPjsMPPzxKS/28YmvV19d/OG7cuPNPPvnk6bYBAADJ5xHQAACwHWWz2cyzzz77rSFDhvzAI5+3zqZNm2Ly5Mnx1FNPKX6BbdKiRYs47LDD4pBDDlEEb6VcLtfwyiuvXHfooYfeWVdX5yXrAACQYApgAADYTo455pjW11133cWVlZV728ant2HDhnj88cdjypQpUVtbayHAZ1ZcXBwHHnhgfPGLX4wWLTyIYWusWbNm2re//e3zn3nmGb+JAwAACeUdwAAAsB1cfvnlu1900UW/LS8v72cbn05VVVVMmDAhbrnllnjzzTejoaHBUoDtoqGhIZYsWRLPPfdc1NTURI8ePSKb9XOMT6OsrKzb17/+9SPbtm0774knnlhtIwAAkMDreu8ABgCAz+bhhx/+8qGHHnpOQUFBsW18sk2bNsUzzzwTTzzxRFRVVVkIsMOVl5fHkUceGYccckgUF/tX9afR2Ni46emnn/71Mccc87htAABAsngENAAAbKN27doVTZ48+YxevXodZxufrK6uLp5++umYOHGi4hfYJSoqKuKLX/xiHHzwwe4I/pQWLVr0x4MOOuiadevWeUwDAAAkhEdAAwDANjj22GPbPvzww7/p2LHjobaxZXV1dfHkk0/GddddF3Pnzo26ujpLAXaJzZs3x2uvvRbPPfdcRET06NEjCgsLLWYLWrduvfv3vve9fd9+++1pCxYsqLYRAABo+rb0CGgFMAAAfITLL7989wsuuOB3zZs372UbH6+xsTGmTJkS119/fbzyyiuKX6DJ2Lx5cyxYsCCmT58excXF0a1bt8hkMhbzMUpKSjp8+ctfPrJt27ZzvBcYAACaPu8ABgCArTB+/Pgjv/CFL/xLQUFBiW18vAULFsT48eNj6dKllgE0eT169IivfOUrMWjQIMvYgsbGxpqJEydecuyxxz5pGwAA0HR5BzAAAHwK7dq1K3ruued+1qNHj6/axsdbtGhR3H///bF48WLLABKnV69ecdxxx0WfPn0sYwuWLl3654MOOujy1atX19sGAAA0Pd4BDAAAn2DYsGHNn3766V936tTpC7bx0TZs2BD33Xdf/PGPf4y1a9daCJBI69evj2nTpsW6deuiZ8+eUVLiYQ8fpbKysv+3v/3t/lOmTJmyfPlyz/cHAIAmxjuAAQBgC372s5/1uP76669t0aKF54J+hJqamhg/fnzcfPPN8fbbb0cul7MUINFyuVy888478cwzz0RVVVX07t07slk/A/lHZWVl3U488cTD6+rqZkyfPv1DGwEAgCZ0va4ABgCAj3bFFVfscdZZZ11dXFzcwTb+Xi6XixkzZsR1110XCxYsiMbGRksBUqWxsTGWLFkSM2bMiPLy8ujSpUtkMhmL+RvZbLbFQQcddHibNm1mP/HEE6ttBAAAmoYtFcDeAQwAQN6aNGnSqAMOOODsTCZTaBt/b9GiRXHPPffEu+++axlA3ujWrVucdNJJ0bt3b8v4B7lcrm7y5Mm//vKXv/yIbQAAwK63pXcAK4ABAMg7paWlBTNnzjyzT58+x9vG39uwYUOMGzcuZsyY4VHPQF7KZDKx3377xde//vWoqKiwkH+waNGiP+27775X1dTUeCwEAADsQgpgAAD4H926dctOnjz5V506dRppG/9fLpeLqVOnxp///OfYuHGjhQB5r6KiIkaNGhX777+/x0L/g+XLlz9+6KGHXvLuu+/W2QYAAOwaWyqAvQMYAIC8MXz48PInnnjiynbt2o2wjf/vnXfeiWuvvTaee+652Lx5s4UARMTmzZtjzpw58dprr0XPnj2jRYsWlvI/Kioq+n7rW98aMnXq1Ofee+89f3AAAMAusKV3ACuAAQDIC9/+9rc73n777X9o0aLFANv4b1VVVXHXXXfFvffeG+vXr7cQgI+wbt26eP7552P9+vWx2267RVFRkaVERElJSefjjjvuoA8++GDKK6+88hcbAQCAnUsBDABAXjv//PP7XXzxxX8oKSnpbBv/bfr06XHttdfGokWLLAPgU3jnnXdi2rRp0bp16+jc2R8nERHZbLbyC1/4whElJSUvTp48eY2NAADAzqMABgAgb/3ud7/b60c/+tFVRUVFrWwjYs2aNXHzzTfHpEmTora21kIAtkJtbW289NJL8c4770Tfvn2jtLQ073dSWFhYNnz48C/26dNn4UMPPbTMpwQAAHYOBTAAAHnp1ltv3f+b3/zmfxYWFjbP913kcrl4/vnn4/rrr4/ly5f7cAB8BitXroxp06ZFRUVFdOvWLTKZTF7vo6CgIDto0KBDBw0atOiBBx54xycEAAB2vC0VwJmex8Z+H/WF+pURa17ym6wAACTTww8//KXDDjvsXzKZTN6/rPGDDz6I22+/Pd544w0fDIDtbMCAAfGtb30r2rRpk/e7yOVyjVOmTPn1yJEjH/LJAACAHavN3jVR1OGjv6YABgAgdV555ZXTPve5z30r3/fQ0NAQjz32WDz++ONRV1fngwGwg2Sz2Tj66KPjC1/4QhQUFOT9Pl5//fU79tprr9/7ZAAAwI6jAAYAIC9ks9nMyy+/fFafPn2Oz/ddvPvuu3HbbbfFe++954MBsJN069YtvvOd70S3bt3yfhdLliy5f5999rmypqam0ScDAAC2PwUwAACpV1paWjBr1qyzevfufVw+7yGXy8WTTz4ZDz74oLt+AXaBbDYbxxxzTBxxxBF5fzfw0qVLHxg6dOjlSmAAANj+FMAAAKRar169mk2ePHlsu3btDsrnPaxcuTJuueWWePvtt30oAHaxnj17ximnnBIdOnTI6z2sXr16yiGHHPIvS5Ys2exTAQAA28+WCuDCyoHR9aO+0FgVUbMia3sAADRpQ4cOLXvqqacub9Omzf75uoNcLhfPPPNMXH/99bF27VofCoAmYP369TFlypQoKSmJnj17RiaTycs9NG/evPtJJ500ZPLkyc+sWLHCoykAAGA7KetcHwXlH/01BTAAAIk1ZMiQ0kceeeSKVq1a7Z2vO6iuro7bb789nnjiiWhs9IRNgKaksbEx5s+fH++//34MGDAgstn8/DlLaWlpp6997WuDn3rqqadXrlxZ75MBAACfnQIYAIDUOfTQQ1s8+OCDv2/ZsuUe+bqDOXPmxNVXX+2RzwBN3PLly+OFF16Ijh075u0joUtLSzudcMIJ+7/00ktPv/3227U+FQAA8NkogAEASJVhw4Y1v++++37TsmXLQfl4/vr6+hg/fnz88Y9/jNpaP0MHSILNmzfHiy++GJs3b47ddtstCgoK8m4HxcXFbY866qg9n3vuuaeXL1/ucdAAAPAZKIABAEiNI488snLcuHG/b9GixcB8PP97770XV111VcyZM8eHASCBFi1aFC+//HL069cvWrRokXfnLykp6XDcccftP2fOnGcWLVq0yScCAAC2jQIYAIBUOPLIIyvvvPPO35aXl++Wj+d/+umn48Ybb4wPP/zQhwEgwf7yl7/ECy+8ECUlJdGrV6+8O3+zZs3aHHXUUfspgQEAYNspgAEASLxjjz227W233faH5s2b98m3s1dVVcXNN98cTz31VDQ2NvowAKRAY2NjzJ8/P5YtWxYDBgyIZs2a5dX5mzVr1uaYY4458M0333xm4cKFNT4RAACwdRTAAAAk2qGHHtritttuuzofy9+33347rrnmmli8eLEPAkAKvf/++/HKK69Enz59orKyMq/Ons1mK4888sh9Zs6c+dTSpUs3+zQAAMCnpwAGACCxjjjiiJb33nvvteXl5f3y6dy5XC4mTZoUN998c1RVVfkgAKRYVVVVTJ06NbLZbPTp0ycymUzenL24uLjtV7/61c+/8sorTy1evLjWpwEAAD4dBTAAAIl06KGHtrj33nt/l2/lb3V1ddx0000xefLkyOVyPggAeSCXy8WCBQvi3Xffjd133z2y2fz5mUyzZs1aH3300fvNmDHjSXcCAwDAp6MABgAgcYYOHVo2fvz4qysqKgbk07mXLVsWV111lUc+A+SplStXxiuvvBKf+9znoqKiIm/O3axZszZHH3300GeeeebJFStW1PkkAADAlimAAQBIlCFDhpQ++uij17Rs2XL3fDr31KlT49prr42NGzf6EADksaqqqpg+fXq0bds2unTpkjfnLikpaT9q1KihTz755JMrV66s90kAAICPt6UCuMB6AABoSjp16pSdMGHCv1dWVu6RL2dubGyMcePGxR133BF1dW56AiCitrY2br755hg/fnw0NjbmzbkrKyv3mDBhwr9369bNXQkAALCNFMAAADQZrVq1Kpw+ffolbdq02T9fzlxdXR2/+93vYtKkSd73C8DfyeVy8fjjj8fvf//7qK6uzptzt2nTZv8pU6Zc0qpVq0KfAgAA2HoKYAAAmoSKioqC2bNnX9KuXbuD8uXMS5cujYsuuijmz5/vAwDAx3r11VfjoosuiqVLl+bNmdu1a3fQ7Nmz/72iosLPrgAAYCu5iAYAoEmYOXPmz9q1a3dIvpz35ZdfjiuuuCLWrVsnfAA+0bp16+KKK66Il19+OW/O3K5du4Nnzpz5M+kDAMDWUQADALDLvfjii2N69OgxKh/Omsvl4oEHHogbbrghamtrhQ/Ap1ZbWxs33HBDPPDAA3nz2oAePXqMevHFF8dIHwAAPj0FMAAAu9SkSZO+NnDgwFPy4ax1dXVx8803x8SJE73vF4BtksvlYuLEiXHLLbdEXV1dXpx54MCBJ0+aNOlr0gcAgE9HAQwAwC7z6KOPHnXggQeekw9nXbduXfz617+OWbNmCR6Az2zmzJnx61//Ol9eJZA58MADz3n00UePkjwAAHyywsqB0fWjvtBYFVGzImtDAADsEDfeeOO+Rx999EWZTKYw7Wddvnx5XHXVVbFy5UrBA7DdbNiwIWbPnh0DBw6MioqKtB8307179/179uz56sMPP7xc+gAA5LuyzvVRUP7RX1MAAwCw011xxRV7fPe7372qoKCgWdrPOmfOnPjtb38bGzduFDwA2111dXVMmzYtOnfuHB07dkz1WTOZTOHuu+9+eJs2bV6aNGnSKukDAJDPFMAAADQZZ5xxRvef/exnvy0sLCxP+1knTJgQd999d9TX1wsegB2moaEhXnrppchms9G3b99UnzWTyRTttddeB/3lL395dubMmRukDwBAvlIAAwDQJBx00EEVf/jDH64pLi7ulOZz5nK5eOCBB+Kxxx4TOgA77c+eBQsWRF1dXfTv3z8ymUxqz1pQUFBywAEHDJs+ffqkpUuXbpY+AAD5aEsFcIH1AACwMwwaNKj0vvvuu7qsrKxXms9ZV1cX1113XUyaNEnoAOx0EydOjOuvvz7q6upSfc6ysrJef/rTn67u379/idQBAODvKYABANjhKioqCh577LGLKyoqBqb5nNXV1XHNNdfE7NmzhQ7ALvPKK6/ElVdeGVVVVak+Z4sWLQZOnDhxbEVFhZ9vAQDA33CBDADADjdz5syftW3b9sA0n3HdunVx2WWXxRtvvCFwAHa5xYsXx2WXXRZr165N9TnbtWt34MyZM38mcQAA+P8UwAAA7FBPPvnkqB49eoxK8xlXrVoVV1xxRSxfvlzgADQZK1asiCuuuCJWrlyZ6nP26NFj1KRJk0ZJHAAA/lth5cDo+lFfaKyKqFmRtSEAALbZjTfeuO+XvvSlCzOZTGp/8fDdd9+NK6+8MtatWydwAJqc6urqmDVrVvTv3z8qKytTe85u3boN79mz57yHH37Yb2MBAJAXyjrXR0H5R39NAQwAwA5x3nnn9T311FOvLigoKE7rGV977bW45pprorq6WuAANFmbN2+OGTNmRI8ePaJ9+/apPGMmkykYNGjQIYWFhVOee+45v5UFAEDqKYABANipjjrqqFb/8R//8YdmzZq1SusZX3755bj++uujrq5O4AA0eQ0NDfHSSy9Fp06dolOnTqk8Y0FBQXbYsGGfnz9//qQ33nhjk9QBAEizLRXA3gEMAMB21a1bt+wNN9zwnyUlJR3TesYpU6bEjTfeGPX19QIHIDHq6+vjxhtvjKlTp6b2jCUlJZ1uuOGG/+jWrZu7GgAAyFsKYAAAtqunn376XyorK/dI6/kmTZoUd955ZzQ2NgobgMRpbGyMO+64I5588snUnrGysnLw008//UtpAwCQrxTAAABsN88///w3unTp8k9pPd/EiRNj3LhxkcvlhA1AYuVyubj//vtTXQJ36dLlS88+++yJ0gYAIB95BzAAANvFTTfdNPzII488L5PJZNJ4vsceeyzGjx8vaABS47XXXotmzZpF3759U3m+zp0779urV695Dz/88DJpAwCQNlt6B7ACGACAz+wXv/hF7x/+8IdXFxQUNEvj+R544IF45JFHBA1A6ixYsCDq6upiwIABqTtbJpPJDBo06ODGxsbnpk6dul7aAACkiQIYAIAd5oADDqj43e9+99tmzZq1TeP5xo0bF5MmTRI0AKm1aNGi2Lx5cwwcODB1ZysoKMjut99+w5577rnH33vvvc3SBgAgLbZUAHsHMAAA2yybzWbuvPPOfyktLe2exvNNmDBB+QtAXpg0aVI89NBDqTxbaWlpj3vuuedfstlsRtIAAOQDBTAAANvs+eef/06HDh0OTePZHn744Xj44YeFDEDeeOSRR+LPf/5zKs/WoUOHw5599tlvSxkAgHygAAYAYJvcd999hw8ePPjUNJ7t/vvvjwkTJggZgLzz2GOPxX333ZfKs+25554/+NOf/nSYlAEASDvvAAYAYKudccYZ3ceMGXN5QUFBcdrONnHixHjkkUeEDEDeWrx4cWSz2ejbt2/ajpbp06fPvn/5y18mz5w5c4OkAQBIsi29A1gBDADAVhk2bFjzG2+88Q/NmjVrl7azPfroo6l99CUAbI2FCxdGUVFR9OvXL1XnKigoKD7wwAP3e/LJJx9dsWJFnaQBAEiqLRXAHgENAMBWuffee39ZWlraPW3nevzxx+PBBx8UMAD8jz//+c8xceLE1J2rtLS0x3333fdLCQMAkFYKYAAAPrVJkyaN6tSp0xEpPFeMHz9ewADwD8aPHx+TJ09O3bk6der0hUmTJn1VwgAApJFHQAMA8Kmcd955fU888cR/z2QyRWk618yZM+Puu+8WMAB8jNdeey06duwYnTt3TtW5unbtuk9BQcHzzz333DopAwCQNN4BDADAZ3LEEUe0vOKKK67NZrOVaTrXyy+/HDfffHPkcjkhA8DHyOVyMXv27OjSpUt06tQpNefKZDJF++yzz/CpU6c+9u67726WNAAASeIdwAAAbLNsNpu55ZZb/q2kpKRjms61YMGCuPnmm6OxsVHIAPAJGhsb4+abb44333wzVecqLS3tcvfdd5+fzWYzUgYAIC0UwAAAbNGkSZO+3rZt2wPTdKZly5bFDTfcEPX19QIGgE+prq4urr322li+fHmqztWuXbsDH3/88VESBgAgLTwCGgCAj3XZZZcN+upXvzo2k8mk5hcH33///bjyyiujqqpKwACwlerq6uLll1+OIUOGRHl5eWrO1aVLl31LS0unPvPMM2ukDABAEngENAAAW61///4lJ5988q8ymUxRWs60YcOG+P3vfx8bN24UMABso40bN8bvfve7VP15WlBQkP3hD394ft++fYslDABA4q9vrQAAgI/y4IMPnl1WVtYrLeeprq6O3/zmN7Fq1SrhAsBntGrVqrjyyiujuro6NWcqKyvr8/DDD58lXQAAkk4BDADA/zF+/Pgju3XrdnRazlNfX5/KdxYCwK60fPnyuO6666K+vj41Z+rRo8dXx40bd4R0AQBIMgUwAAB/53vf+16nI4444py0nCeXy8Vtt90Wb7zxhnABYDt7/fXX47bbbotcLpeaMx155JHnfvvb3+4oXQAAkkoBDADA/6qoqCi4+OKLLywsLCxPy5nGjx8fs2bNEi4A7CCzZs2Khx56KDXnKSwsrPj1r399QUVFhZ+bAQCQSC5kAQD4XxMnThxdWVk5OC3nef7552PixImCBYAd7LHHHosXXnghNeeprKzc89FHHz1RsgAAJFFh5cDo+lFfaKyKqFmRtSEAgDxxwQUX7DZq1KgLM5lMYRrO8+qrr8Ytt9ySqkdSAkBT/7O3d+/e0a5du1Scp2PHjkNzudxzU6ZMWSddAACamrLO9VHwMc/wcwcwAADRt2/f4h//+McXZzKZVPwG4HvvvRc33HBDNDY2ChcAdpKGhoa47rrrYtmyZak4T0FBQfbss88e27dv32LpAgCQqGtZKwAAYPz48T8qKyvrkYazbNiwIX7/+99HbW2tYAFgJ9u0aVP8/ve/j40bN6biPGVlZb3GjRs3RrIAACSJAhgAIM/ddNNNw/v06XN8Gs5SX18f1157baxdu1awALCLrFmzJq699tqor69PxXn69ev3jd///vdDJQsAQFIogAEA8thBBx1Uceyxx/5rRGTScJ477rgjFi9eLFgA2MUWLVoUd955Z1qOU/CNb3zj34YNG9ZcsgAAJOIC1goAAPLXzTfffFZxcXG7NJxl4sSJMX36dKECQBMxbdq0mDRpUirOUlJS0unOO+88Q6oAACSBAhgAIE/913/914FdunT5UhrO8tprr8Wf//xnoQJAEzN+/PhYsGBBKs7SrVu3o2+66abhUgUAoKlTAAMA5KEvfelLlV/5yld+lYazrFixIq6//vpobGwULAA0MY2NjXHdddfFihUr0nCczHHHHfergw46qEKyAAA0ZQpgAIA8dPXVV5+ezWYrk36OmpqauPbaa2PTpk1CBYAmatOmTXHttddGTU1N4s+SzWbb3njjjT+RKgAATZkCGAAgz9x6663Du3Tp8uWknyOXy8Utt9wSK1euFCoANHErV66MW2+9NXK5XOLP4lHQAAA0dQpgAIA8MnTo0LKvfvWrv0jDWSZMmBBz584VKgAkxJw5c+Kxxx5LxVlGjRr1i6FDh5ZJFQCApkgBDACQR+64444fFRcXd0z6OV5++eV45JFHBAoACfPQQw+l4he4SkpKOt5xxx0/lCgAAE2RAhgAIE9cfvnlu/fs2XNU0s/xwQcfxB133JGKR0gCQL7J5XJx2223xZo1axJ/lp49ex57+eWX7y5VAACaGgUwAEAe6NatW/a73/3uv2YymURf/9XX18cNN9wQ1dXVQgWAhKqqqoobb7wx6uvrE32OTCZT8N3vfvdX3bp1y0oVAICmRAEMAJAHxo8ff0pZWVmvpJ/jnnvuiaVLlwoUABJuyZIlcd999yX+HGVlZT3Hjx9/ikQBAGhKFMAAACn385//vOeAAQNGJ/0c06dPjylTpggUAFJi8uTJMWPGjMSfY8CAAaN//vOf95QoAABNhQIYACDFstls5qyzzjo3k8kk+tGEK1asiLvvvlugAJAyd911V6xYsSLRZ8hkMtmzzjrr3Gw2m5EoAABNgQIYACDFxo0b98XKysq9knyG2trauOGGG6K2tlagAJAyf/1zfvPmzYk+R2Vl5V7jxo0bKVEAAJoCBTAAQEoNHz68/OCDDz4t6ee4//77Y/ny5QIFgJRavnx5jBs3LvHnGDFixI+HDRvWXKIAAOxqCmAAgJS69dZbT8tms22TfIYZM2bEc889J0wASLnJkyfHzJkzE32G4uLitrfffvtp0gQAYFdTAAMApNBVV121R48ePb6S5DOsXLky7rrrLmECQJ64++6744MPPkj0GXr27PnVK664Yg9pAgCwKymAAQBSprS0tOAb3/jGT5N8rdfY2Bi333679/4CQB6pqamJ2267LRobG5N8jIJvfvObZ5aWlvqZGwAAu+6i1AoAANJlwoQJX6uoqBiQ5DOMHz8+Fi1aJEwAyDNvvvlmPPjgg4k+Q4sWLQY9+OCDx0gTAIBdRQEMAJAiRx55ZOWwYcN+kOQzzJ07N5544glhAkCemjhxYixYsCDRZxg+fPiPjjjiiJbSBABgV1AAAwCkyBVXXHFyUVFRRVLnr6qqijvvvDNyuZwwASBP5XK5uP3226O6ujqxZygqKmrxm9/85mRpAgCwKyiAAQBS4vzzz+/Xu3fv45J8httvvz0+/PBDYQJAnlu3bl3cfvvtiT5D7969jz///PP7SRMAgJ1NAQwAkALZbDZz6qmn/jyTyST2+m769OkxZ84cYQIAERExe/bsmDFjRmLnz2QyBaeeeurPs9lsRpoAAOxMCmAAgBT44x//eERlZeXgpM6/du3auPfeewUJAPyde+65J9atW5fY+SsrKwf/6U9/+oIkAQDYmRTAAAAJ17dv3+JDDjnkR0k+w9133x01NTXCBAD+Tk1NTdx9992JPsPBBx/8o759+xZLEwCAnUUBDACQcHfdddc3S0pKOiV1/smTJ8e8efMECQB8pLlz58azzz6b2PlLSko63nPPPd+SJAAAO4sCGAAgwb70pS9VDhw48KSkzr9mzZoYP368IAGALXrggQdizZo1iZ2/f//+Jx111FGtJAkAwM6gAAYASLArrrji1MLCwvIkzp7L5eK2226LTZs2CRIA2KJNmzbFbbfdFrlcLpHzFxYWll1++eU/kCQAADuDAhgAIKF+8Ytf9O7evftXkjr/s88+G2+88YYgAYBP5Y033ojnn38+sfN369bt6F/96ld9JAkAwI6mAAYASKgf/vCHP8pkMom8nvvggw/igQceECIAsFXGjRsX69atS+TsmUym4NRTT/2xFAEA2NEUwAAACXTdddcNa9eu3YFJnD2Xy8Udd9wRtbW1ggQAtsqmTZvizjvvTOz8bdq02f+mm27aT5IAAOxICmAAgIQpLS0t+NrXvnZ6Uud/4YUXYuHChYIEALbJq6++GjNmzEjs/Mccc8zpFRUVfiYHAMAO42ITACBhbr/99oMrKip2S+LsGzZsiHHjxgkRAPhM7r///qiqqkrk7OXl5X3/67/+6wgpAgCwoyiAAQASpKKiouCwww47Nanz33vvvYn9YS0A0HRs2LAh7r///sTOf9BBB/1zq1atCiUJAMCOoAAGAEiQ+++//+iysrKeSZx9zpw58dJLLwkRANguXnjhhViwYEEiZy8tLe32xz/+8StSBABgR1AAAwAkRN++fYv33Xfff07i7LW1tXHvvfcKEQDYru6+++6oq6tL5Oz77bffKf379y+RIgAA25sCGAAgIW6++eZRxcXFbZM4+yOPPBJr164VIgCwXa1atSoee+yxRM6ezWbb3njjjV+TIgAA25sCGAAgAYYOHVq21157fSeJs7/33nvxxBNPCBEA2CEmTpwYK1asSOTsQ4YM+c7QoUPLpAgAwPakAAYASIBrr732xKKiosqkzZ3L5eKee+6JxsZGIQIAO0R9fX3cddddkcvlEjd7UVFR5bXXXnuCFAEA2J4UwAAATdwBBxxQMWDAgG8kcfYZM2bEW2+9JUQAYId6880348UXX0zk7AMGDPjG8OHDy6UIAMD2ogAGAGjirrrqqhOKiooqkjb3pk2b4oEHHhAgALBT3H///VFbW5u4uYuKilpcffXV7gIGAGC7UQADADRhBx10UEX//v0Teffvww8/HB9++KEQAYCdYv369TFhwoREzj5w4MBvHHDAARVSBABge1AAAwA0Yf/5n/95bGFhYfOkzb1q1aqYPHmyAAGAnerpp5+O1atXJ27uwsLC8ssuu2yUBAEA2B4UwAAATdQBBxxQMWjQoNFJnP3uu++O+vp6IQIAO1V9fX3cddddiZx99913/+bQoUPLpAgAwGelAAYAaKIuv/zyYwsLC8uTNvfcuXNjwYIFAgQAdokFCxbE3LlzEzd3UVFRi9/97ndflyAAAJ+VAhgAoAkaOnRo2aBBgxL37t+6urr405/+JEAAYJf605/+FHV1dYmbe/fdd//mkCFDSiUIAMBnoQAGAGiCfvOb33ylqKioZdLmfu655xL53j0AIF1Wr14dzz33XOLmLioqann11VcfLUEAAD4LBTAAQBPTq1evZoMHD/5m0uaurq6ORx55RIAAQJPwyCOPRHV1deLmHjJkyLe6deuWlSAAANtKAQwA0MTcdNNNxxQXF7dN2twTJkyIqqoqAQIATUJVVVUifzmtuLi43a233uouYAAAtpkCGACgCWnVqlXhXnvtdVLS5l61alU8++yzAgQAmpTJkyfHqlWrEjf30KFDR7dq1apQggAAbAsFMABAE3LLLbccXlJS0jlpcz/44INRX18vQACgSamvr48HH3wwcXOXlJR0vummmw6VIAAA20IBDADQRGSz2cwBBxzw7aTNvWjRonjppZcECAA0SS+99FIsXrw4cXOPGDHiO9lsNiNBAAC2lgIYAKCJuOaaa/YuLy/vm7S5H3roocjlcgIEAJqkXC6XyLuAy8vL+1111VV7SRAAgK2lAAYAaCK+8pWvfDdpM8+bNy8WLlwoPACgSVu4cGG8+uqrrg8BAMgLCmAAgCbgsssuG1RZWblPkmbO5XIxfvx44QEAifDAAw8k7qklrVu33veSSy7pLz0AALaGAhgAoAkYNWrUCUmbefbs2bFs2TLhAQCJsGzZsnjllVcSN/cJJ5xwovQAANgaCmAAgF3sn//5nzt37NjxiCTN3NjYGA888IDwAIBEGT9+fDQ0NCRq5k6dOn1h9OjRHaQHAMCnpQAGANjFfvSjH30tk8kk6rps2rRpsWrVKuEBAImyatWqeOGFFxI1cyaTKfzpT386SnoAAHxaCmAAgF1o0KBBpX369Plqkmaur6+PCRMmCA8ASKQJEyZEXV1dombu27fvV/r27VssPQAAPg0FMADALnTZZZcdXlRUVJGkmadOnRpr164VHgCQSOvXr48pU6YkauaioqLK3/zmN0dIDwCAT0MBDACwi2Sz2cy+++57UpJmrqurc/cvAJB4jz76aOLuAt5///1PymazGekBAPBJFMAAALvI1VdfPbSsrKx3kmaeMmVKbNiwQXgAQKJt2LAhnn/++UTNXFZW1ueqq67aS3oAAHwSBTAAwC7y5S9/+bgkzVtfXx8TJ04UHACQCo8//nji7gL+0pe+dLzkAAD4JApgAIBdYPTo0R3atm07IkkzT5s2LdatWyc8ACAVPvzww5g+fXqiZm7fvv2I0aNHd5AeAABbogAGANgFfvrTn47KZDKFSZm3vr4+HnnkEcEBAKnyyCOPRH19fWLmzWQyhT/96U+/JjkAALZEAQwAsJN16tQp26dPn6OTNLO7fwGANFq3bl1MmzYtUTP36dPnmE6dOmWlBwDAx1EAAwDsZFddddUB2Wy2dVLmbWxsjEmTJgmOVOvYsWN06OCJmgD5aNKkSdHY2JiYebPZbOurrrrqAMkBAPBxFMAAADvZiBEjvp6keV988cVYtWqV4Ei1Ll26xIUXXhinnXZadO3a1UIA8siqVavipZdecj0JAEBqKIABAHaiM844o3tlZeXeSZk3l8vFxIkTBUdeyGQyMXjw4PjVr34VY8aMcUcwQB55/PHHI5fLJWbeysrKvc8444zukgMA4KMogAEAdqJTTjnlqxGRScq8CxYsiPfee09w5JVMJhN77713XHjhhTFmzJho3769pQCk3HvvvRcLFy5M1B9X/3NdCQAA/4cCGABgJ+nVq1ezXr16fTlJM3v3L/nsr0XwBRdcECeffHK0bdvWUgBSLGnXPb169fpyr169mkkOAIB/pAAGANhJrrjiioOLiopaJmXeBN4JAztEYWFhDB8+PC688MIYPXp0VFZWWgpACi1YsCCWLVuWmHmLiopaXnnllYdIDgCAf6QABgDYSYYPH/6VJM2btHfhwY5WVFQUI0aMiEsuuSRGjx4dLVu2tBSAFMnlcvH4448naub99tvvK5IDAOAfKYABAHaC0aNHd6isrByalHnXrl0bL7/8suDgI/y1CL744ovjhBNOiBYtWlgKQEq89NJLsW7dusTMW1lZudfo0aM7SA4AgL+lAAYA2AlOP/30o5J07fXMM89EQ0OD4GALiouL47DDDouxY8fGqFGjoqyszFIAEq6hoSGeeeaZJI1c8D/XmQAA8P8vEq0AAGDHymazmX79+n05KfPW1tbGlClTBAefUnFxcYwcOTIuvfTSGDVqVJSWlloKQII9//zzUVtbm5h5+/Xr9+VsNpuRHAAAf6UABgDYwX7zm9/sWVJS0jkp886YMSOqq6sFB1uppKQkRo4cGZdcckkcffTRUVJSYikACVRdXR0zZ85M0p8/na+44orBkgMA4K8UwAAAO9gXvvCFLyVl1lwuF08//bTQ4DNo3rx5HHXUUXHJJZfEyJEjI5vNWgpAwjz11FORy+USM++RRx75ZakBAPBXCmAAgB1oyJAhpZ07dz4iKfO+/vrrsWLFCsHBdlBeXh6jRo2KSy+9VBEMkDArVqyI119/PTHzdunS5fD+/ft79AQAABGhAAYA2KHGjh17aGFhYWJeCOruX9j+KioqYtSoUXHxxRfH4YcfHkVFRZYC4LpouyosLGz+H//xHwdLDQCACAUwAMAOteeeex6ZlFnXrl0b8+bNExrsIK1atYrjjz8+LrroohgxYkQUFPh2DKApmzdvXqxZsyYx8+61115HSg0AgAgFMADADnPMMce0bt269b5Jmfe5556LxsZGwcEO1qZNmxg9enRcfPHFimCAJqyxsTGee+65JP35MvyYY45pLTkAAPykAQBgBznzzDMPz2Qyibjeqq+vj6lTpwoNdqK2bdvG6NGj47zzzovhw4crggGaoBdeeCHq6+sTMWsmkyk844wzDpUaAAB+wgAAsIP079//C0mZdc6cObFhwwahwS7QqVOnOPnkk+Pf/u3fYu+9945MJmMpAE3Ehg0bYvbs2YmZd8CAAR4DDQCAAhgAYEf43ve+16mysnKPpMybpMcbQlp17tw5xowZE7/61a8UwQBNyPPPP5+YWSsrKwd/73vf6yQ1AID8pgAGANgBTj755CMiIhHtzcqVK+P1118XGjQRXbt2jTFjxsQ555wTgwcPthCAXez111+PlStXJmXczMknn3y41AAA8psCGABgB+jXr19iHv88ZcqUyOVyQoMmpnfv3nHaaafFOeecE/3797cQgF0kl8vFlClTknQd6jHQAAB5TgEMALCdnX766d0qKip2S8Ks9fX1MW3aNKFBE9anT58466yz4pxzzonddtvNQgB2gWnTpkV9fX0iZq2oqNjt9NNP7yY1AID8pQAGANjORo8efURSZp03b15s3LhRaJAAffr0ibPPPjvOPPPM6Nmzp4UA7EQbN26MefPmuR4FACARFMAAANtZr169EvPetSQ9zhD4bwMGDIhf/vKXceaZZ0b37t0tBGAnmTp1apKuRw+TGABA/lIAAwBsR2eccUb38vLyvkmYdf369fHaa68JDRJqwIAB8S//8i9x2mmnRbdunvQJsKPNnz8/Pvzww0TMWl5e3u9HP/pRF6kBAOQnBTAAwHZ03HHHHZSUWWfMmBGNjY1CgwTLZDIxePDg+Nd//dcYM2ZMdOjQwVIAdpDGxsaYMWNGYub9xje+cYjUAADykwIYAGA76tOnz8FJmDOXyyXqMYbAlmUymdh7773jwgsvjDFjxkT79u0tBWAHeOGFF5J0XXqIxAAA8pMCGABgOznppJPat2zZcvckzLpkyZJYuXKl0CBl/loEX3DBBXHyySdH27ZtLQVgO1qxYkW8/fbbiZi1srJy0PHHH+8PAgCAPKQABgDYTr773e8eGBGZJMyapMcXAluvsLAwhg8fHhdeeGGMHj06KisrLQVgO5k+fXpSRi34/ve/f6DEAADyjwIYAGA72X333Q9Nwpz19fUxc+ZMgUEeKCoqihEjRsQll1wSo0ePjpYtW1oKwGc0c+bMqK+vT8SsAwcOPFRiAAD5RwEMALAdHHTQQRUtW7bcKwmzLly4MKqrq4UGeeSvRfDFF18cJ5xwQrRo0cJSALZRVVVVvP7664mYtVWrVkOHDx9eLjUAgPyiAAYA2A7OPvvsz2cymaIkzOrxz5C/iouL47DDDouxY8fGqFGjoqyszFIAtkFSnqaSyWSy55577uclBgCQXxTAAADbwe67735AEuasra2NOXPmCAzyXHFxcYwcOTIuvfRSRTDANpg9e3bU1dUlYtY99tjjAIkBAOQXBTAAwGfUqlWrwnbt2u2fhFnnzZsXtbW1QgMiIqKkpCRGjhwZY8eOjaOPPjpKSkosBeBT2LRpU8ybNy8Rs7Zv337/iooKPwMEAMgjLv4AAD6jCy+8cPeioqKKJMz64osvCgz4P5o3bx5HHXVUXHLJJTFy5MjIZrOWAvAJZs2alYg5i4qKWlx88cWDJAYAkD8UwAAAn9GBBx6YiMfqVVdXJ+ZOFWDXKC8vj1GjRsWll16qCAb4BPPmzYuamppEzHrQQQd5DDQAQB5RAAMAfEZdu3YdnoQ5582bF/X19QIDPlFFRUWMGjUqLr744jj88MOjqKjIUgD+QV1dXbz66quJmLVLly77SwwAIH8ogAEAPoNTTjmlY3l5+W5JmPXll18WGLBVWrVqFccff3xcdNFFMWLEiCgo8C0kwN966aWXEjFnRUXFbieddFJ7iQEA5AffvQMAfAYnnnji55MwZ21tbcyfP19gwDZp06ZNjB49OsaOHasIBvgb8+fPj9ra2iSMmvnud7/7eYkBAOQH37UDAHwGn/vc5/ZLwpwLFy6Muro6gQGfyV+L4PPOOy+GDx+uCAby3ubNm2PhwoWJmLVfv37DJQYAkB98tw4AsI1atWpV2Lp1672TMKvHPwPbU6dOneLkk0+Oc889NwYNGmQhQF6bPXt2IuZs06bN3hUVFX4WCACQB1z0AQBso/PPP39gYWFheVOfs76+PubMmSMwYLvr2bNn/OQnP4nzzjsv9t5778hkMpYC5J3Zs2dHfX19k5+zqKio4vzzzx8oMQCA9FMAAwBso/3333/fJMz5+uuvR01NjcCAHaZLly4xZsyYOOecc2Lw4MEWAuSV6urqeOONNxIx64EHHriPxAAA0k8BDACwjbp27ZqIxz/PnTtXWMBO0bt37zjttNPinHPOif79+1sIkDeScr3VvXv3vaUFAJB+CmAAgG0wZMiQ0srKyj2a+py5XM7jn4Gdrk+fPnHWWWfFOeecE7vttpuFAKk3e/bsyOVyTX7Oli1b7jlo0KBSiQEApJsCGABgG5x55pl7ZjKZbFOfc9myZbFu3TqBAbtEnz594uyzz44zzzwzevbsaSFAaq1bty6WL1/e5OfMZDLZs846a4jEAADSrcgKAAC23tChQ4clYc558+YJC9jlBgwYEAMGDIgFCxbE+PHjY+nSpZYCpM68efOiS5cuTX7OffbZZ5+ImC4xAID0cgcwAMA26Nix4z5JmHP+/PnCApqMAQMGxC9/+cs47bTTolu3bhYCpEpSrrs6deq0j7QAANJNAQwAsJWOOOKIlhUVFf2a+pxVVVWxaNEigQFNSiaTicGDB8e//uu/xpgxY6JDhw6WAqTCW2+9FVVVVU1+zoqKis8deuihLSQGAJBeCmAAgK108sknD46ITFOfc/78+dHY2CgwoEnKZDKx9957x4UXXhhjxoyJ9u3bWwqQaI2NjbFgwYJE/Cv4u9/97h4SAwBILwUwAMBW2n333fdMwpze/wskwV+L4AsuuCBOPvnkaNu2raUAiZWU66/BgwfvKS0AgPQqsgIAgK3Trl27wU19xlwul5Q7UAAiIqKwsDCGDx8e++yzT0ybNi0mTJgQ69evtxggURYsWBC5XC4ymab9sJgOHToMlhYAQHq5AxgAYCsMGjSotGXLlgOa+pzvvfdebNy4UWBA4hQVFcWIESPikksuidGjR0fLli0tBUiMDz/8MJYtW9bk52zZsuXA/v37l0gMACCdFMAAAFvhxz/+8aBMJtPkn6Li7l8g6f5aBI8dOzZOOOGEaNGihaUAibBw4cImP2Mmk8n+5Cc/GSAtAIB0UgADAGyFvffee88kzJmEHzwCfBrNmjWLww47LMaOHRujRo2KsrIySwGatKT8Il5SrmsBANh63gEMALAVunbtOqSpz1hfXx9vvvmmsIBUKS4ujpEjR8bBBx8czz77bDz++ONRXV1tMUCT8+abb0Z9fX0UFTXtH7t16dJlT2kBAKSTO4ABAD6lioqKgoqKikFNfc4lS5bE5s2bBQakUklJSYwcOTLGjh0bRx99dJSUeIUl0LTU1tbG0qVLm/ycLVu2HFRaWupngwAAKeQiDwDgUzr77LP7FhYWNvlnj7722mvCAlKvefPmcdRRR8Ull1wSI0eOjGbNmlkK4HpsKxQWFpafffbZvaQFAJA+CmAAgE9p//3375+EOV9//XVhAXmjvLw8Ro0aFf/+7/8eI0eOjGw2aymA67FP6fOf//xAaQEApI8CGADgU+rRo8fuTX3G2traePvtt4UF5J2KiooYNWpUXHzxxXH44Yc3+XdvAum2ePHiRLySo1evXoOkBQCQPgpgAIBPqXXr1k3+DoklS5ZEQ0ODsIC81apVqzj++OPj4osvjhEjRkRBgW97gZ2voaEhlixZ0uTnbNOmjQIYACCFfCcMAPApDBkypLR58+a9m/qcb775prAAIqJ169YxevToGDt2rCIYcF32MZo3b95n0KBBpdICAEgX3wEDAHwKp556av9MJtPkr53eeustYQH8jTZt2sTo0aPjvPPOi+HDhyuCAddlfyOTyRT84Ac/+Jy0AADSxXe+AACfwuDBg5v84/Hq6+tj0aJFwgL4CJ06dYqTTz45/u3f/i323nvvyGQylgLsUIsXL07Eqzn23HPPgdICAEgXBTAAwKfQpUuXAU19xnfeeSfq6uqEBbAFnTt3jjFjxiiCgR2utrY23n333SRc53oPMABAyiiAAQA+hZYtW/Zv6jN6/DPAp9elS5cYM2ZMnHvuuTF48GALAfL2+iwJ17kAAGwdBTAAwCcYPnx4eUlJSeemPqfHPwNsvV69esVpp50W55xzTvTvrwMB8u/6rLS0tPPw4cPLpQUAkB4KYACAT/Ctb31rt4ho8s8IXbx4sbAAtlGfPn3irLPOinPOOSd22203CwG2i4T8gl7m29/+dj9pAQCkhwIYAOAT7L777k2+CVi7dm1s2LBBWACfUZ8+feLss8+OM888M3r27GkhwGfy4Ycfxrp165r8nAMHDlQAAwCkSJEVAABsWadOnZr8D8TefvttQQFsRwMGDIgBAwbEggULYvz48bF06VJLAbb5Oq1Vq1audwEA2GkUwAAAn6CyslIBDJCnBgwYEP3794958+bFQw89FO+++66lAFtlyZIlsddeezX1613PvgcASBEFMADAFnTq1CnbvHnzXk19ziVLlggLYAfJZDIxePDg2GOPPeLll1+OBx98MFauXGkxQGqu05o3b967Xbt2RatXr66XGABA8nkHMADAFowZM6ZnJpPJNuUZGxsbPZoUYCfIZDKx9957x4UXXhhjxoyJ9u3bWwrwiZYuXRqNjY1NesaCgoLsqaee2kNaAADp4A5gAIAt2Hvvvfs29RlXrlwZtbW1wgLYSf5aBO+5554xa9asmDBhQqxevdpigI9UW1sb77//fnTu3LlJzzls2LC+EbFIYgAAyecOYACALejRo0eTL4DfeecdQQHsAoWFhTF8+PC48MILY/To0VFZWWkpQGKv15Jw3QsAwKejAAYA2ILWrVs3+ff/vvvuu4IC2IUKCwtjxIgRcckll8To0aOjZcuWlgIk7notCde9AAB8Oh4BDQCwBc2bN+/Z1GdUAAM0kW+wi4pixIgRsd9++8WUKVPiscceiw0bNlgMEO+9914SrnsVwAAAKeEOYACAj9G/f/+SkpKSjk19TgUwQNPSrFmzOOyww2Ls2LExatSoKCsrsxTIc0m4XistLe3Ut2/fYmkBACSfAhgA4GOccMIJ3Zr69dK6deuiqqpKWABNUHFxcYwcOTJ+/etfK4Ihz1VVVcX69eub+pgF3/zmN7tLCwAg+RTAAAAfY8iQIT2b+oxJeJwgQL77axE8duzYOProo6OkpMRSIA8l4botCde/AAB8MgUwAMDH6N69e8+mPqPHPwMkR/PmzeOoo46KSy65JEaOHBnNmjWzFMgjSbhuS8L1LwAAn0wBDADwMVq1atWjqc+4bNkyQQEkTHl5eYwaNSr+/d//PUaOHBnZbNZSIA8k4botCde/AAB8MgUwAMDHqKio6NXUZ1y+fLmgAJL750yMGjUqLr744jj88MOjqKjIUiDFknDd1rJly16SAgBIPgUwAMBHyGazmbKysq5NecbGxsZYtWqVsAASrlWrVnH88cfHxRdfHCNGjIiCAt+qQxqtWrUqGhsbm/SMJSUlXbPZbEZaAADJ5rtKAICPcNxxx7UrKCgobsozrl69Ourr64UFkBKtW7eO0aNHx9ixYxXBkEJ1dXXxwQcfNOkZCwoKio877rh20gIASDbfTQIAfITPf/7zXZr6jO+//76gAFKoTZs2MXr06Dj//PNj+PDhimBIkRUrVrgOBgBgh/NdJP+PvTuPr7I888d/nSwEkhD2HUQEUVRAoIiouCtq64Jabd1arVorbqO2tlXbaavTOu38Rqffdmpbu9rWpYogsqgFRXCttAIKArJDgAAJBLKQ5JzfH8WO4+DOcp6T9/v18jWvTv657ut6hNvnk/t+AICd2G+//bL+xVcSXiAC8PF17do1Lr300rj99ttj2LBhkUq5lRWSLgn7tyTsgwEAeH8FWgAA8H917txZAAxAVujevXtceeWVsXr16njiiSdi9uzZkclkNAYSKAn7tyTsgwEAeH8CYACAnWjXrp0roAHIKj169Igrr7wyli5dGpMmTYo5c+ZoCiRMEvZvSdgHAwDw/gTAAAA7UVxc3D3baxQAAzRPffr0ibFjx8aSJUti/PjxsWDBAk2BhEjC/i0J+2AAAN6fbwADAOxESUlJz2yur7q6Ourq6gwKoBnbb7/94l/+5V/ia1/7WhxwwAEaAglQV1cX1dXV9sEAAOxWAmAAgHc5/PDDSwsKCtpmc40VFRUGBUBERPTt2zduvPHGuOGGG2LffffVEMhy2b6PKygoaDt8+PASkwIASC4BMADAu5x44oldsr3GDRs2GBQA/8uAAQPiG9/4Rtxwww3Ru3dvDQH7uE+yH+5qUgAAyeUbwAAA79KvX7+sD4DXr19vUADs1IABA+LAAw+MuXPnxoQJE2LlypWaAlkkCTe5HHDAAV0i4i3TAgBIJgEwAMC7dO/evXO21+gEMADvJ5VKxaBBg2LgwIExe/bsGD9+fKxbt05jwD4uZ/bDAAC8NwEwAMC7tG/fvlO21ygABuDDSKVSMWzYsBg6dGjMnj07HnvsMbdIwF6WhBPASdgPAwDw3gTAAADv0rp166w/8ZCEF4cAZI+3g+BDDz00XnnllZg4caK/S8A+LtH7YQAA3psAGADgXUpKSrL6xENjY2Ns3rzZoAD4yPLz8+Pwww+P4cOHx/PPPx8TJ06MqqoqjYE9aPPmzdHY2BgFBdn7Wi7b98MAALw/ATAAwLu0bNmySzbXV1lZGZlMxqAA+Njy8/Nj1KhRMXLkyHjhhRcEwbAHZTKZqKqqio4dO9oPAwCwWwiAAQDepaioKKuvvKusrDQkAHaJgoKCGDVqVIwYMSJmzpwZkydPji1btmgM7IH9XDYHwNm+HwYA4P3laQEAwP8YPnx4SX5+fkk21ygABmBXa9GiRRx//PFxxx13xNlnnx0lJSWaAs14P5efn18yfPhwfxAAACSUABgA4B2OOOKIDtleo+//ArC7FBUVxejRo+P73/9+nH322VFcXKwpsBsk4cr1JOyLAQDYOQEwAMA79O3bt1221+gEMAC729tB8B133BGnn356tGrVSlOgme3n9ttvv7YmBQCQTAJgAIB36NSpkwAYAHYoKSmJz3zmM3HnnXfG6NGjo0WLFpoCzWQ/l4R9MQAAOycABgB4hw4dOrTN9hqTcGUgALmlpKQkzj777PjOd74To0aNivz8fE2BHN/PJWFfDADAzgmAAQDeoaysrG221ygABmBvad++fVx00UVx5513xgknnBCFhYWaAjm6nysrK3MCGAAgoQTAAADvUFJS0j6b68tkMlFdXW1QAOxV7dq1i/POOy+++93vxqhRoyIvz+sF+CiSsJ8rLS0VAAMAJJT/QgMAeIfi4uK22VxfXV1dNDY2GhQAWeHtE8F33HGHIBg+gsbGxqirq7MvBgBgt/BfZgAA79CqVausPung9C8A2ahDhw5x0UUXxbe//e04/PDDBcGQA/u6oqIiJ4ABABLKf5EBALxDQUGBABgAPqauXbvGpZdeGt/61rdi2LBhkUqlNAUSuq9r0aJFW1MCAEimAi0AAPgfhYWFZdlc39atWw0JgKzXrVu3uPLKK2P16tXxxBNPxOzZsyOTyWgMJGhfl+37YgAA3psAGADgnZujgoLW2VyfE8AAJEmPHj3iyiuvjKVLl8akSZNizpw5mgIJ2ddl+74YAID35gpoAIAdWrdunZefn98ym2sUAAOQRH369ImxY8fGLbfcEgMGDNAQSMC+Lj8/v1WrVq28OwQASCCbOACAHQYNGlQSEVn9scJt27YZFACJtd9++8UNN9wQX/va1+KAAw7QEJq1BOzr8gYPHlxsUgAAySMABgDY4YADDijJ9hpramoMCoDE69u3b9x4441xww03xL777qshNEu1tbVZX2P//v1LTQoAIHl8AxgAYIeePXtm/QuuJLwoBIAPa8CAATFgwICYP39+jBs3LpYvX64pNBtJ2Nf16NGjxKQAAJJHAAwAsEOnTp0EwACwFwwYMCAOPPDAmDt3bkyYMCFWrlypKeS8JOzrunbtKgAGAEggATAAwA5lZWVZ/4Krrq7OoADISalUKgYNGhQDBw6M2bNnx4QJE2Lt2rUaQ85KQgDcpk0bV0ADACSQABgAYIeysjIngAFgL0ulUjFs2LAYOnRozJ49O8aPHx/r1q3TGHKOABgAgN1FAAwAsENJSUlxttfoBDAAzcXbQfCQIUPi5ZdfjokTJ0ZFRYXGkDOSEAAnYX8MAMD/JQAGANihqKioKNtrrKmpMSgAmpW8vLw4/PDDY/jw4fH888/HE088EZWVlRpD4iUhAG7RokWRSQEAJI8AGABgh8LCwhbZXF86nY7t27cbFADNUn5+fowaNSpGjhwZL7zwQkycODGqqqo0hsTavn17ZDKZSKVSWVtjixYtWpgUAEDyCIABAHbI9gC4oaHBkABo9goKCmLUqFExYsSImDlzZkyePDm2bNmiMSROJpOJhoaGyOaMtbCw0AlgAIAk/neTFgAA7NgYFRRk9QuuxsZGQwKAHVq0aBHHH398HHnkkfHMM8/E1KlTY9u2bRpDomR7AJzt+2MAAN5jH6cFAAA7NkZZ/oLL9c8A8H8VFRXF6NGj49hjj41nnnkmpkyZEjU1NRpDImT7DS8FBQWugAYASCABMADA2xujLH/B5QpoAHhvbwfBRx11VEyfPj2efvrpqK2t1RiymgAYAIDdIU8LAAD+QQAMAMlXUlISn/nMZ+LOO++M0aNHZ/X1uiAABgBgdxAAAwDskO1XQAuAAeDDKykpibPPPjv+7d/+LUaPHh2FhYWagv3dR5Sfn9/SlAAAkkcADADw9sYoL88JYADIMa1bt46zzz47vve978UJJ5wgCMb+7iPIz8/3LwwAQAIJgAEAdkilUlm9N2psbDQkAPiY2rVrF+edd15897vfjRNOOCEKCgo0Bfu7D94f55sSAEDyCIABAHbI9gA4nU4bEgB8Qu3bt/9nEDxq1KjIy/NqBPu799kfp0wJACB5/FcOAMAOXnABQPPRoUOHuOiii+J73/ueIJi9JpPJZHuJ/sUAAEggmzgAgP+R1QFwAl4QAkDidOzYMS666KL41re+FYcffnj4fTDs796xOc7yG3IAANg5mzgAgITsjQTAALD7dOvWLS699NL41re+FcOGDRMEs0dk+xXQeXl5/kUAAEigAi0AAPiHbH/BJQAGgN2ve/fuceWVV8ayZcviiSeeiDlz5mgKzXl/5/AIAEACCYABAHbIZDJOAAMAERGx7777xtixY2PJkiUxYcKEmD9/vqZgfwwAQCIIgAEA/ocr7gCA/2W//faLG264Id56660YP358vPnmm5rCLpPtV0Cn3IUOAJBIAmAAgB2y/QVXtr8gBIBc1rdv37jxxhvjrbfeinHjxsWiRYs0hU/MFdAAANjEAQDsXln9Bs4BDADY+/r27Rs333xz3HDDDdG7d28NIdf3d75BAgCQQE4AAwDskO0nMATAAJA9BgwYEAMGDIj58+fHI488EitXrtQUcnF/5woaAIAEcgIYAGCHVCqVzvL6DAkAssyAAQPi1ltvjbFjx0bPnj01hJza32UScEc1AAD/lxPAAAD/QwAMAHysv6MHDRoUBx98cDz//PMxadKk2LRpk8aQ+P1dtv+CJAAAO+cEMADADul0dr/fEgADQHarr6+PioqK2LZtm2aQE/u7dDrtBDAAQAI5AQwA8D+cAAYAPrK6urp4+umnY9q0acJfcm1/5wQwAEACCYABAP6HEw4AwIfW0NAQ06ZNiyeffDK2bt2qIXxkCfgGsAAYACCBBMAAADtkMpmsDoDz8ny9AwCywdvB71NPPRXV1dUaQs7u7wTAAADJJAAGANgh219wCYABYO9qbGyMGTNmxJNPPhmVlZUawieWn5+f9VtkUwIASB4BMADADplMpiGb6yssLDQkANgL0ul0zJo1KyZPnhwbN27UEHaZgoLsfjXX1NTUaEoAAAncZ2oBAMA/NDY2bs/m+gTAALBnpdPpePnll2Py5Mmxdu1aDWGXa9GiRbb/O1BvSgAAySMABgDYoampSQAMAEQmk4nZs2fH448/HuXl5RpCs93fNTY2CoABABJIAAwAsENDQ0NWv+ASAAPA7vV28PvEE0/E6tWrNYTdLtuvgM72G3IAAHiPfaYWAAD8gyugAaD5mjNnTkyaNCmWLl2qGewx2X4FtAAYACCZBMAAADs0NTU5AQwAzcyCBQtiwoQJ8dZbb2kG9nfv0tDQIAAGAEggATAAwA7Z/oJLAAwAu87ChQtj/PjxsXjxYs1gr8n2K6Cz/RckAQB4j32mFgAA/EO2B8AFBQWRl5cX6XTasADgY1q+fHmMGzcu5s+frxnsVXl5eVkfAG/fvt0JYACABBIAAwDs0NDQkPUnHFq1ahXbtm0zLAD4iFauXBmPPPKI4Jes2tclYH8sAAYASCABMADADrW1tXXZXqMAGAA+mnXr1sX48eNj9uzZkclkNISs2tdlu7q6ulqTAgBIHgEwAMAOW7du3ZrtNSbhRSEAZIP169fHY489JvjFvu4TqK6u3mpSAADJIwAGANihqqpKAAwACbdhw4Z4/PHH45VXXommpiYNwb7uE6isrHT1DABAAgmAAQB22LRpU9a/4GrZsqVBAcBOVFVVxcSJE+OFF16IxsZGDSHrJSEA3rRpkxPAAAAJJAAGANhh3bp1WR8AOwEMAP/bli1bYsKECYJfEicJ+7ry8nIBMABAAgmAAQB2WLZsmSugASAhqqurY/LkyTFz5syor6/XEBInCfu6pUuXCoABABJIAAwAsMP8+fOz/gRwcXGxQQHQrNXU1MSUKVPimWeeEfySaEnY173++uu+AQwAkEACYACAHRYsWFCXyWQaUqlUYbbW2Lp1a4MCoFmqq6uLp59+OqZNmxbbtsmkSL5s39el0+mGpUuXbjcpAIDkEQADALxDU1PTtoKCgrbZWp8AGIDmZvv27TF9+vR48sknY+tWt9GSO7J9X9fU1ORfOACAhBIAAwC8Q0NDw9ZsDoBLS0sNCYDm8ndyTJs2LZ566qmorq7WEHJOtu/rGhsb/YsHAJBQAmAAgHeor6+vbNWqVc9src8JYAByXWNjY8yYMSOefPLJqKys1BByVrbv6+rr66tMCQAgmQTAAADv0NDQkNVvmgXAAOSqdDods2bNismTJ8fGjRs1hJyX7fu6bN8XAwDw3gTAAADvUFdXV5XN9ZWWlkYqlYpMJmNYAOSETCYTr732Wjz++OOxatUqDaFZSKVSUVJSktU11tbWVpkUAEAyCYABAN5h27Ztm7K5vvz8/GjVqlXU1NQYFgCJlslkYvbs2fH4449HeXm5htCstGrVKvLz87O6xq1btzoBDACQUAJgAIB3qK6u3pztNZaVlQmAAUist4PfiRMnxpo1azSEZqmsrCzra9y2bVuVSQEAJJMAGADgHaqqqjZle43t2rWLtWvXGhYAiTNnzpyYNGlSLF26VDNo1tq1a5f1NW7atMkJYACALNbQWBgFjQ0REZFKRSavMJre/pkAGADgHSoqKqqyvcYkvDAEgHdasGBBTJgwId566y3NgITs5zZs2CAABgDIYoUFDf9MejMRqab0/+S+AmAAgHdYtWpV1r/oatu2rUEBkAgLFy6M8ePHx+LFizUD3iEJAfDq1aurTAoAIJkEwAAA77BgwYKsD4CdAAYg2y1fvjzGjRsX8+fP1wzYiST8Ql8S9sUAAOycABgA4B2eeOKJjZlMpimVSuVna41OAAOQrVauXBmPPPKI4Bc+QLb/Ql8mk2l64oknNpoUAEAyCYABAN6huro6vX379g1FRUVdsrVGJ4AByDZr166NCRMmxOzZsyOTyWgIJHw/t3379g3V1dVpkwIASCYBMADAu9TV1a0XAAPAB1u/fn089thjgl/Isf1cXV3delMCAEguATAAwLvU1dVVtGnTJmvrKykpiRYtWsT27dsNC4C9oqKiIiZOnBivvPJKNDU1aQh8BEVFRVFcXJz1+2GTAgBILgEwAMC7bN26dV2XLll7ADhSqVR07Ngx1qxZY1gA7FFVVVUxceLEeP755wW/8DF17NgxUqlU1u+HTQoAILkEwAAA71JVVZX1Jx46deokAAZgj9m8eXM8/vjj8cILL0RjY6OGwCfcx9kPAwCwOwmAAQDeZf369Vn/zbMkvDgEIPm2bNkSU6ZMiZkzZ0Z9fb2GwC7QsWNH+2EAAHYrATAAwLusXr066088JOHFIQDJVVNTE1OmTIlnnnlG8Au7WBJ+kW/VqlUCYACABBMAAwC8y9///ves/+aZABiA3aG2tjYmT54czz77bNTV1WkINNN93KuvvioABgBIMAEwAMC7PPzww+t//OMfN6RSqcJsrbFz584GBcAus3379pg+fXpMnTo1tm3bpiGwG2X7CeB0Ot3w8MMPC4ABABJMAAwA8C7V1dXpurq68latWu2TrTV26NAh8vLyIp1OGxgAH1tDQ0NMmzYtnnrqqaiurtYQ2M3y8vKiQ4cOWV1jfX39mtraWptMAIAEEwADAOxEbW3tmmwOgAsKCqJdu3axceNGwwLgI2tsbIwZM2bEk08+GZWVlRoCe0j79u2joCC7X8fV1NSUmxQAQLIJgAEAdmLz5s2r2rdvn9U1duvWTQAMwEeSTqdj1qxZMXnyZH+HwF7av2W7LVu2rDQpAIBkEwADAOzEpk2bVvfp0yera+zWrVvMmzfPsAD4QG8Hv1OmTIkNGzZoCOzF/Vu227BhwxqTAgBINgEwAMBOrFixYvWwYcOyusYkvEAEYO/KZDLx0ksvxZQpU6K83K2usLd17do162tctWrVKpMCAEg2ATAAwE7Mnz9/9ZgxY7K6xiS8QARg78hkMjF79uyYOHFirFnjMB9kiyT8At+8efP8oQEAkHACYACAnRg3btyab37zm5mISGVrjU4AA7Azc+bMiSeeeCKWLVumGZBlEvALfJlx48atNikAgGQTAAMA7MTrr79e29DQsKmwsLBDttZYXFwcZWVlsWXLFgMDIBYsWBDjx4+PJUuWaAZkobKysiguLs7qGhsaGjYuWLCgzrQAAJJNAAwA8B62bt26vF27dh2yucauXbsKgAGauYULF8b48eNj8eLFmgFZLAm3t2zbtm25SQEAJJ8AGADgPVRVVS1t167d0GyusWfPnrFw4ULDAmiGli1bFo899ljMnz9fMyABevbsmfU1VlZWLjUpAIDkEwADALyHdevWLevTp09W15iEF4kA7ForVqyIRx99VPALCZOEfdvatWuXmRQAQPIJgAEA3sPChQuXHX744VldY69evQwKoJlYtWpVjB8/PubOnRuZTEZDIGGSsG9buHDhMpMCAEg+ATAAwHuYNm3a0ksuuSSra+zevXvk5+dHU1OTgQHkqHXr1sX48eNj9uzZgl9IqIKCgkR8A/jpp59eZloAADmw/9QCAICde+ihhzbcd999W/Pz80uzdjNXUBBdunSJNWvWGBhAjqmoqIiJEyfGyy+/HOl0WkMgwbp27RoFBdn9Gq6xsbH6kUce2WBaAADJJwAGAHgf27ZtW15WVnZwNtfYq1cvATBADqmqqoqJEyfG888/74YHyBFJ+P7vtm3blpsUAEBuEAADALyPLVu2LMv2ALhnz57x0ksvGRZAwm3evDkef/zxeOGFF6KxsVFDIIck4fu/W7ZsWWpSAAC5QQAMAPA+1q9fvyzbT2z06NHDoAASbMuWLTFlypSYOXNm1NfXawjkoCTs19avX7/MpAAAcoMAGADgfSxYsGDh0KFDs7rGfffdN1KpVGQyGQMDSJCampqYMmVKPPPMM4JfyGGpVCr23XffrK9z/vz5C00LACA3CIABAN7Ho48++uYFF1yQ1TWWlJRE586dY926dQYGkAC1tbUxefLkePbZZ6Ourk5DIMd17do1WrVqlfV1/vnPf15kWgAAuUEADADwPiZNmlRVX1+/oaioqGM217nvvvsKgAGy3Pbt22P69OkxderU2LZtm4ZAM9GnT5+sr7G+vr7iySefrDItAIDcIAAGAPgAW7duXZTtAXCfPn3ipZdeMiyALNTQ0BDTpk2Lp556KqqrqzUEmpkkXP+8detWp38BAHKIABgA4ANUVFQs7NChw8hsrjEJLxYBmpvGxsaYMWNGPPnkk1FZWakh0EwlYZ9WUVHh+78AADlEAAwA8AGWLl266MADD8zqGnv16hUFBQXR2NhoYAB7WTqdjlmzZsWkSZNi06ZNGgLNWGFhYfTs2TMR+13TAgDIHQJgAIAPMHPmzEWnnnpqdm/qCgqiZ8+esWzZMgMD2EveDn4nT54cGzdu1BAg9tlnn8jPz0/CfnexaQEA5I48LQAAeH+//OUvV6bT6bpsr7NPnz6GBbAXZDKZePHFF+O73/1u3H///cJf4J+ScP1zOp2u++Uvf7nStAAAcocTwAAAH6C6ujpdXV29uE2bNodkc539+vWL6dOnGxjAHpLJZGL27NkxceLEWLNmjYYAO92fJWCvu7i6ujptWgAAuUMADADwIWzYsGFetgfA/fv3NyiAPeTVV1+NSZMmxapVqzQD2KlUKpWI/dmGDRvmmhYAQG4RAAMAfAiLFy9+o2/fvlldY1lZWXTu3DnWr19vYAC7yYIFC2L8+PGxZMkSzQDeV5cuXaK0tDQJ+9z5pgUAkFsEwAAAH8JTTz31+ujRo7O+zv33318ADLAbLFy4MMaPHx+LFy/WDOBD78uSYMqUKa+bFgBAbsnTAgCAD/aLX/xiTWNjY1W215mUF40ASbFs2bK4++674z/+4z+Ev8BHkoTv/zY0NFTee++9q00LACC3OAEMAPAhNDQ0ZDZv3jy/Q4cOI7O5TgEwwK6xYsWKePTRR2P+fDejArm7L9uyZYs/5AAAcpAAGADgQ1q3bl3WB8AdO3aMNm3axObNmw0M4GNYtWpVjB8/PubOnRuZTEZDgI+lbdu20aFDh0Tsb00LACD3CIABAD6kefPmzTvooIOyvs4DDzwwXnrpJQMD+AjWrVsX48ePj9mzZwt+gV2yH0uCuXPnzjMtAIDcIwAGAPiQ/vznP88/77zzsr7OAw44QAAM8CFVVFTEuHHjBL/ALt+PJcHDDz/sBDAAQA4SAAMAfEgTJ06srK2tXdGqVat9srnOgw8+2LAAPkBVVVVMnDgxnn/++WhqatIQYJdKwq0xNTU1yydNmlRlWgAAuUcADADwEWzYsOHvvXr1yuoAuG3bttG1a9dYu3atgQG8y+bNm+Pxxx+PF154IRobGzUE2OW6desWbdu2TcS+1rQAAHKTABgA4CNYuHDha7169Toj2+scMGCAABjgHbZs2RJTpkyJ5557LrZv364hwG6TlO//Lly48O+mBQCQmwTAAAAfwcSJE/9+wgknZH2dBx54YEyfPt3AgGavpqYmpkyZEs8880zU19drCLDbDRgwIBF1jh8//u+mBQCQmwTAAAAfwb333rv6Bz/4wfqioqLO2VznAQccEHl5eZFOpw0NaJZqa2tj8uTJ8eyzz0ZdXZ2GAHtEXl5e9O/fP+vrrK+vX3ffffeVmxgAQG4SAAMAfESVlZVzu3btmtXHgFu1ahX77LNPLFu2zMCAZqWuri6efvrpmDZtWmzbtk1DgD1qn332iVatWmV9nZs2bZpjWgAAuUsADADwES1dunR2tgfAERGDBg0SAAPNRkNDQ0ybNi2eeuqpqK6u1hBgr+2/kuCtt976m2kBAOQuATAAwEc0ffr0v48cOTLr6xw4cGBMmDDBwICc1tDQEM8991w8+eSTUVlZqSHAXt9/JcG0adP+bloAALlLAAwA8BH9x3/8x9JbbrmlOj8/v3U219mrV68oKyuLLVu2GBqQc9LpdMyaNSsmTZoUmzZt0hBgrysrK4tevXplfZ2NjY1b7rnnnmUmBgCQuwTAAAAfUW1tbXrDhg1/7dKly3HZXGcqlYqBAwfGrFmzDA3IGW8Hv5MnT46NGzdqCJA1Bg4cGKlUKuvr3Lhx4yu1tbVpEwMAyF0CYACAj+Gtt956JdsD4IgQAAM5I51Ox8svvxxTpkyJ8vJyDQGyct+VBIsWLXrFtAAAcpsAGADgYxg/fvwrRxxxRNbXedBBB0VBQUE0NjYaGpBImUwmZs+eHRMnTow1a9ZoCJCVCgoK4qCDDkpErY888ogAGAAgx+VpAQDAR/fjH/94ZX19/fpsr7OoqCj69etnYEAivfrqq3HHHXfEz3/+c+EvkNX69esXRUVFWV9nXV1d+b333rvaxAAAcpsTwAAAH9OGDRte6dGjx6ezvc5BgwbFggULDAxIjCVLlsSECRNi/vz5mgEkwuDBgxNR5/r1653+BQBoBgTAAAAf07x5815OQgA8bNiwePjhhyOTyRgakNXefPPNmDBhQixevFgzgMRIpVIxdOjQRNQ6d+7cl0wMACD3CYABAD6m++677+XRo0dnIiKVzXW2bds2evfuHcuWLTM0ICstW7YsHnvsMSd+gUTq06dPtG3bNgmlpu+9996/mhgAQO4TAAMAfEwTJ06s3Lp165LS0tK+2V7rkCFDBMBA1lmxYkU8+uijgl8g0YYMGZKIOqurqxc+/fTTm00MACD3CYABAD6B8vLyl/fff/+sD4AHDx4c48aNMzAgK6xcuTImTJgQc+fOdT09kHhJ+f7vmjVrfP8XAKCZEAADAHwCM2fOfG7//ff/fLbX2a1bt+jWrVuUl5cbGrDXrFu3LsaPHx+zZ88W/AI5oWfPntGlS5dE1DpjxoznTAwAoHkQAAMAfAK33Xbba5dcckl1fn5+62yvdciQIQJgYK9Yv359PPbYY4JfIOck5frnxsbGzbfddts8EwMAaB4EwAAAn0BlZWXThg0b/tqlS5fjsr3WQw89NCZNmmRowJ78MzKeeOKJeP7556OpqUlDgJxz6KGHJqLOioqKV6qrq9MmBgDQPAiAAQA+oXnz5s1MQgDcu3dv10ADe0RVVVVMnDgxXnjhhWhsbNQQICd17949evbsmZT9quufAQCakTwtAAD4ZP77v/97VkQk4kTFpz71KQMDdpstW7bEQw89FLfffns899xzwl8gpw0fPjwRdWYymfTdd9/9gokBADQfTgADAHxCkyZNqtqyZcuCsrKyg7K91uHDh8fjjz9uaMAuVVNTE1OmTIlnnnkm6uvrNQTIealUKg477LBE1Lply5bXp0+fvsXUAACaDwEwAMAusHz58lkDBw7M+gC4S5cu0atXr1i5cqWhAZ9YbW1tTJ48OZ599tmoq6vTEKDZ6N27d3Ts2DEx+1QTAwBoXgTAAAC7wLPPPvvCwIEDr0hCrcOGDRMAA59IXV1dPP300zFt2rTYtm2bhgDNzrBhwxJT61/+8pcXTQwAoHnxDWAAgF3g1ltvnV9fX782CbUefvjhkUqlDA34yBoaGmLq1Klx6623xuOPPy78BZqlJF3/XFdXt/rWW29dYGoAAM2LE8AAALtAQ0NDZs2aNc/16dPns9lea7t27aJPnz6xZMkSgwM+7J9xMW3atHjqqaeiurpaQ4Bmbb/99ou2bdsmotbVq1fPNDEAgOZHAAwAsIs8++yz05IQAEdEHHHEEQJg4AOl0+mYNWtWTJo0KTZt2qQhABFx5JFHJqbW6dOnTzMxAIDmxxXQAAC7yC233PJaQ0NDZRJqHT58eLRo0cLQgJ1Kp9Px3HPPxW233Rb333+/8Bdgh6KiovjUpz6ViFobGho23HLLLXNNDQCg+XECGABgF6murk6Xl5c/t88++5yR7bW2bNkyDj300Hj55ZcNDvindDodL7/8ckyZMiXKy8s1BOBdhgwZEkVFRYmodc2aNc/V1tamTQ0AoPkRAAMA7EIvvvjiM0kIgCMiRo4cKQAGIiIik8nE7NmzY+LEibFmzRoNAXif/VNSzJo161kTAwBongTAAAC70O233/7KOeecszU/P78022sdMGBAtG/f3tWu0My9+uqrMWnSpFi1apVmALyPjh07xgEHHJCIWhsbG6u/8Y1v/NXUAACaJwEwAMAutHLlyob169fP6tat2+hsrzWVSsXhhx8ekyZNMjhohubMmROTJ0+OJUuWaAbAh3D44YdHKpVKRK3r16+fVVFR0WhqAADNU54WAADsWq+++mpirts77LDDDAyamTfffDP+/d//PX7yk58IfwE+pFQqFSNGjEhMva+88sozpgYA0Hw5AQwAsIvddNNNz5166qnV+fn5rbO91m7dukX//v1j4cKFBgc5btmyZfHYY4/F/PnzNQPgIzrggAOic+fOiai1sbFxy4033jjL1AAAmi8BMADALrZy5cqG8vLyGT179vx0Euo9+uijBcCQw5YvXx7jxo0T/AJ8wv1SUpSXlz9TXl7eYGoAAM2XABgAYDeYMWPGUxdccEEiAuAhQ4ZE69ato7q62uAgh6xcuTImTJgQc+fOjUwmoyEAH1ObNm3i0EMPTUy9zz777FOmBgDQvPkGMADAbvDVr371lYaGhk1JqLWgoCCOOOIIQ4McsW7duvj5z38ed955Z8yZM0f4C/AJjRw5MvLz8xNRa0NDw8abbrrpVVMDAGjenAAGANgNKisrm1atWjW9T58+5ySh3qOPPjqefPJJQREk2Pr16+Oxxx6L2bNn+3cZYBdJpVIxatSoxNS7cuXKadXV1WmTAwBo3pwABgDYTaZNm5aY6/c6duwYAwYMMDRIoA0bNsSvf/3r+Nd//dd49dVXhb8Au9CAAQOiY8eOian36aefftLUAAAQAAMA7CZf+9rX5tTX11ckpd6jjjrK0CBBqqqq4v77749vf/vb8eKLL0ZTU5OmAOxiRx55ZGJqra+vX/eNb3zjdVMDAMAV0AAAu0ltbW16xYoVT++///6fT0K9hx56aLRt2zaqqqoMD7LYli1bYsqUKfHcc8/F9u3bNQRgN2nbtm0MGTIkMfUuX778qdraWtc/AwDgBDAAwO70xz/+cUJSas3Pz4/jjjvO0CBL1dTUxKOPPhq33XZb/OUvfxH+Auxmxx57bOTn5yel3Myvf/3rCaYGAECEABgAYLe66667llZXV89PSr1HH310tGjRwuAgi7wd/H7jG9+IqVOnRn19vaYA7GYtWrSIo48+OjH1btmy5Y177rlnhckBABDhCmgAgN3u9ddfn3T44YcPSEKtxcXFcdhhh8XMmTMNDvayurq6ePrpp2PatGmxbds2DQHYgw477LAoKSlJTL3z5s17wtQAAHibE8AAALvZ9773vanpdLohKfWecMIJkUqlDA72koaGhpg6dWrceuut8fjjjwt/AfawVCoVJ5xwQmLqTafT27/73e8+ZXIAALzNCWAAgN1s+vTpWyoqKmZ26dIlER/Y7d69e/Tv3z/efPNNw4M9qKGhIaZNmxZPPfVUVFdXawjAXnLAAQdE9+7dE1NvRUXFczNmzPAXBwAA/+QEMADAHjBr1qxEXcuXpFMvkHTpdDqee+65uP322+PRRx8V/gLsZccff3yi6p0xY8YkUwMA4J2cAAYA2AO++tWvvnT66adXFRYWtk1CvQMHDoyOHTvGhg0bDA92k3Q6HbNmzYrJkyfHxo0bNQQgC3Ts2DEGDhyYmHobGhoqb7755pdMDgCAd3ICGABgDygvL29YsWLF1MRsEvPy4sQTTzQ42A3S6XS8+OKL8Z3vfCfuv/9+4S9AFjnppJMiLy85r8tWrFgxpaKiotHkAAB4JwEwAMAe8qtf/erRiMgkpd6jjjoqysrKDA52kUwmE6+++mp873vfi1//+texdu1aTQHIImVlZXHUUUcl6q+W//7v//6zyQEA8G4CYACAPeQ///M/l1dWVv4tKfUWFhbGMcccY3CwC7wd/P785z+PNWvWaAhAFjruuOOioCA5X0urqqqa/dOf/nS1yQEA8G4CYACAPeill14al6R6jzvuuCgqKjI4+JjmzJkTd911V/z85z+P1au9owfIVkVFRYn7xbcXXnhhnMkBALAzBVoAALDnjB079pkFCxZUFhYWtktCvSUlJXHEEUfE9OnTDQ8+gjfffDPGjx8fb731lmYAJMCRRx4ZJSUliam3oaFh0zXXXPOsyQEAsDMCYACAPai8vLxh6dKlE/v3739xUmo+8cQT49lnn410Om2A8AEWLVoUjz32WCxevFgzABIiLy8vTjzxxETVvGTJkifKy8sbTA8AgJ3ucbUAAGDP+u1vfzsxIjJJqbdjx44xdOhQg4P3sXz58rj77rvjRz/6kfAXIGGGDRsWHTp0SFLJmd/85jePmxwAAO9FAAwAsIf953/+5/JNmza9kqSaTz/99EilUoYH77Jy5cr4yU9+Et///vdj/vz5GgKQMHl5eXHGGWckquZNmza9fM8996wwPQAA3osroAEA9oKXXnpp/KmnnnpYUurt2rVrDBkyJGbPnm14EBHr1q2L8ePHx+zZsyOTyWgIQEINHTo0OnfunKiaX3jhhQkmBwDA+xEAAwDsBZdffvkzS5YsWVdUVNQlKTWfccYZ8be//U3YRbO2fv36eOyxxwS/ADkglUrF6aefnqia6+rq1lx22WXTTQ8AgPcjAAYA2AsqKyub5s+f/8ihhx56dVJq7tatm1PANFsbNmyIxx9/PF555ZVoamrSEIAc8KlPfSq6du2aqJrfeOONcdXV1WnTAwDg/fgGMADAXvL1r399XDqdrktSzb4FTHNTVVUV999/f3z729+OF198UfgLkCNSqVR8+tOfTlTN6XS69pvf/OZjpgcAwAdxAhgAYC+ZMWNG9Zo1a/7Ss2fPxLx97N69ewwcODDmzJljgOS0LVu2xIQJE+KFF16IxsZGDQHIMYceemh069YtUTWvXr36qRkzZlSbHgAAH8QJYACAvejXv/71HyMiUR8SPeuss5wCJmdt27YtHn300bjtttviueeeE/4C5KC8vLwYM2ZM0srO/OpXv/qT6QEA8KH2vFoAALD3fP/733+rqqoqUR/V7dGjR3zqU58yPHJKfX19TJ06Nb71rW/F1KlTo76+XlMActSIESOiS5cuiap506ZNf73rrruWmh4AAB+GABgAYC975plnHkpazWeccUbk5dlKkjvmzZsXjz76aGzdulUzAHJYQUFBnH766Ymre9q0aQ+aHgAAH5a3dgAAe9nYsWNn1tfXr01SzZ07d47DDjvM8ACARBk5cmR06NAhUTXX1dWtHjt27POmBwDAhyUABgDYyyorK5tee+21Pyat7jPPPDMKCgoMEABIhBYtWiTy9O/s2bP/UF1dnTZBAAA+LAEwAEAWuOqqqyY0NjZWJanm9u3bx9FHH214AEAiHHfccdGmTZtE1dzQ0LDxiiuumGh6AAB8FAJgAIAssGDBgrqFCxc+lrS6R48eHYWFhQYIAGS1li1bxsknn5y4uhcuXDhu6dKl200QAICPQgAMAJAlvv71r/8pnU7XJqnmtm3bximnnGJ4AEBWO+2006K0tDRRNTc1NdV+7Wtfe8j0AAD4qATAAABZ4umnn968fPnyxF3xN3r06GjXrp0BAgBZqUOHDnH88ccnru5ly5ZNmD59+hYTBADgoxIAAwBkkbvvvvtPmUymKUk1FxYWxumnn254AEBWOvPMMxP3yYpMJtN41113/dH0AAD4OATAAABZ5Be/+MWatWvXTkta3UcccUT06tXLAAGArNK7d+847LDDEld3eXn5X+6///51JggAwMchAAYAyDIPP/zwn5JWcyqVijPPPNPwAICsMmbMmEilUomr+8EHH/yT6QEA8HEJgAEAsszXv/71NzZs2DAraXUPHDgwDj74YAMEALLCoEGDYsCAAYmru6KiYuatt966wAQBAPi4BMAAAFlo3Lhxv01i3WPGjIm8PFtMAGDvysvLizFjxiSy9j//+c+/NUEAAD7RflgLAACyz/XXXz+nqqrqr0mru1evXnHUUUcZIACwVx1zzDHRvXv3xNW9adOmV2666aa5JggAwCchAAYAyFJ/+tOf7k1i3WPGjInS0lIDBAD2ijZt2sRZZ52VyNofeOCBe00QAIBPSgAMAJClbrrpprlJPAVcXFwcZ555pgECAHvFWWedFS1btkxc3Zs2bXrl5ptvnmeCAAB8UgJgAIAsNm7cuF8lse5Ro0ZF7969DRAA2KP69OkTI0eOTGTtjz322K9MEACAXUEADACQxcaOHTu7qqrqb0mrO5VKxfnnnx+pVMoQAYA9tv/4/Oc/n8j9R2Vl5d+uueaav5kiAAC7ggAYACDLTZ069bdJrLtv374xZMgQAwQA9ojDDjsssTeQTJ48+TcmCADAriIABgDIcpdeeumLVVVVryax9s9//vNRXFxsiADAblVaWhrnn39+Imuvqqr66+WXX/6SKQIAsKsIgAEAEuChhx76WRLrLisri9NPP90AAYDd6qyzzoqSkpIklp753e9+91MTBABgVxIAAwAkwA033DB3w4YNs5JY+3HHHRd9+vQxRABgt+jbt28cddRRiay9oqJi1te//vU3TBEAgF1JAAwAkBA///nPfxoR6aTVnUql4vOf/3zk5dl6AgC7Vn5+flx00UWRSqWSWH76F7/4xX+bIgAAu5q3cAAACXHHHXe8tW7duulJrL13795xzDHHGCIAsEudcMIJ0b1790TWXl5e/pc77rjjLVMEAGBXEwADACTI3XfffW8mk2lKYu1nnXVWtG3b1hABgF2iQ4cOcfrppyey9kwm03T33Xf/3BQBANgdBMAAAAlyzz33rCgvL386ibW3bNkyzj33XEMEAHaJc889N1q0aJHI2tesWTP1xz/+8UpTBABgdxAAAwAkzA9/+MOfZzKZhiTWPnz48Bg0aJAhAgCfyKGHHhpDhw5NZO3pdLrhBz/4wS9MEQCA3UUADACQMPfee+/qRYsWPZDU+i+++OIoKSkxSADgY2ndunVcfPHFia1/4cKFf7jvvvvKTRIAgN1FAAwAkECXXXbZrxsaGjYlsfaysjJXQQMAH9u5554bpaWliay9oaFh4+WXX/47UwQAYHcSAAMAJNDs2bNrXn311V8ntf4jjjgiDj74YIMEAD6SwYMHx+GHH57Y+l955ZX7Zs+eXWOSAADsTgJgAICEOueccx6tqalZmtT6L7roomjZsqVBAgAfSsuWLeNzn/tcYuuvqalZMmbMmMdMEgCA3U0ADACQUJWVlU3Tpk37RVLrb9++fZx++ukGCQB8KGeccUa0b98+sfU/+eST91ZXV6dNEgCA3U0ADACQYOedd960qqqqV5Ja/wknnOAqaADgAx188MFx/PHHJ7b+TZs2vXzBBRc8a5IAAOwJAmAAgIT74x//eG9EZJJYeyqVigsuuMBV0ADAe2rZsmVccMEFkUqlkrqEzP333/8zkwQAYE8RAAMAJNzNN988b9WqVU8ktf6OHTsm+nt+AMDudcEFF0THjh0TW/+KFSse//rXv/6GSQIAsKcIgAEAcsCNN974k6ampq1JrX/kyJExdOhQgwQA/pdPfepTMWLEiMTW39TUVH3zzTf/t0kCALAnCYABAHLAxIkTK//+97//KslruPDCC6OsrMwwAYCIiGjTpk18/vOfT/QaZs+efd/EiRMrTRMAgD1JAAwAkCPOPvvsh2pqapYntf7S0tK46KKLDBIAiFQqFV/84hejtLQ0sWuoqal566yzznrYNAEA2NMEwAAAOaKioqJx0qRJP07yGgYPHhwjR440TABo5o444og46KCDEr2GJ5544qeVlZVNpgkAwJ4mAAYAyCGXXHLJzIqKihlJXsMFF1wQ3bp1M0wAaKZ69uyZ+KufKyoqZnzhC1+YZZoAAOwNAmAAgBzzb//2b/ek0+ntSa2/RYsWceWVV0ZhYaFhAkAzU1hYGF/60pcSvQ9Ip9Pb/+3f/u0e0wQAYG8RAAMA5Jh777139aJFix5M8hq6d+8eZ555pmECQDNzxhlnRPfu3RO9hsWLFz947733rjZNAAD2FgEwAEAO+uxnP/vL2traRL94PPHEE2Pw4MGGCQDNxKBBg+Kkk05K9Bpqa2tXn3vuub80TQAA9iYBMABADlq8eHH9uHHj/j3Ja0ilUnHJJZdE27ZtDRQAclybNm3ikksuiVQqleh1jBs37t8XL15cb6IAAOxNAmAAgBx1+eWXv1RRUTEjyWsoLS2NL3zhC4l/GQwAvLe3f+mrdevWiV5HRUXFM5dffvlLJgoAwN4mAAYAyGE33HDDXU1NTVuTvIaDDjrI94ABIId95jOfiUMOOSTRa2hqaqq+4YYbfmiaAABkAwEwAEAOGzdu3MbZs2cn/jt0p5xyiu8BA0AOOuSQQ+LTn/504tfx17/+9efjxo3baKIAAGQDATAAQI77zGc+81B1dfXCJK8hlUrFF7/4xejQoYOBAkCO6NixY3zpS19K/KceNm/ePO+00057xEQBAMgWAmAAgBxXXV2dfuCBB34UEekkr6O4uDguvfTSyMuzhQWApMvPz4/LLrssiouLk76U9P333/+ftbW1aVMFACBbeHsGANAMXH/99XMWLVr0YNLXsf/++8e5555roACQcOedd1707ds38etYuHDhH7/61a++bqIAAGQTATAAQDNxySWX/Lyurq486es4/vjjfQ8YABJs2LBhccwxxyR+HXV1dWsuvPDC+0wUAIBsIwAGAGgmXnvttdoHHnjguxGRSfI6UqlUfOlLX4oePXoYKgAkTO/evePSSy9N/Hd/IyLzwAMPfO/111+vNVUAALKNABgAoBm5+uqr/7Z06dJHk76OoqKiGDt2bJSWlhoqACRE69at46qrrorCwsLEr2X58uXjrr766r+ZKgAA2UgADADQzJx33nn/r66ubnXS19GhQ4e4/PLLIy/PlhYAsl1eXl5cfvnl0b59+8Svpb6+ft0ll1zyE1MFACBr999aAADQvLz++uu1Dz744Pcj4VdBR0QMGDAgzjrrLEMFgCw3ZsyYOPDAA3NiLY899tgPXnnllW2mCgBAthIAAwA0Q1/5ylf+umbNmqm5sJaTTz45Dj30UEMFgCw1ZMiQOOmkk3JiLWvXrv3LpZde+oKpAgCQzQTAAADN1GWXXfYf9fX1FUlfRyqViksvvTS6d+9uqACQZXr06BFf/OIXI5VKJX4tDQ0Nm6655pofmioAANlOAAwA0EzNmDGj+oEHHvhO5MBV0C1btozrr78+2rZta7AAkCXatWsX1113XbRs2TIXlpN56KGH/nXSpElVJgsAQLYTAAMANGNf+cpX/rpkyZI/58Ja2rZtG1dffXW0aNHCYAFgL2vRokVcffXVOfPLWUuXLn30iiuueNlkAQBIAgEwAEAzd+655/6ktrZ2eS6spXfv3jlzzSQAJNXbn2fYZ599cmI9tbW1y88555wfmywAAEkhAAYAaOYWLFhQ97Of/ezbmUymMRfWM2zYsDjllFMMFgD2kk9/+tMxdOjQnFhLJpNp/NnPfvbtBQsW1JksAABJIQAGACBuvfXWBfPnz78/V9Zz5plnxuDBgw0WAPaw4cOHx2c+85mcWc/8+fN/d+utty4wWQAAkkQADABARESMGTPmvpqamrdyYS2pVCouu+yy6Nmzp8ECwB7Su3fvuPjii3PmUwxbt25ddPrpp//aZAEASBoBMAAAERGxcuXKhh/+8Ie3pdPpnLjisGXLlvEv//Iv0aVLF8MFgN2sS5cucf3110dRUVFOrKepqanmu9/97jfKy8sbTBcAgKQRAAMA8E933XXX0ueff/6eXFlPaWlpXHvttVFWVma4ALCblJWVxXXXXRclJSU5s6aZM2fe/f/+3/9bZboAACSRABgAgP/l5JNPHldeXv50rqynU6dOMXbs2Jw5kQQA2aSoqCiuueaa6NixY86sqby8/MlTTz11gukCAJBUAmAAAP6PSy655K66urq1ubKefffdN6644orIy7P9BYBdJS8vL6688sro3bt3zqyprq6u/JJLLvmh6QIAkOi9uhYAAPBus2bNqn7ooYfujIh0rqxp4MCBcd555xkuAOwi559/fhxyyCG5tKT0gw8+eOesWbOqTRcAgCTLb3tQ9NzpjndbRG15oQ4BADRTEydOXDNmzJi8Tp06Dc2VNfXp0yfy8/PjzTffNGAA+ATOOuusOOmkk3JqTa+//vp9Z5555kTTBQAgCYq7N0Ze6c5/5gQwAADv6dRTT/31li1bXs+lNZ122mlx9NFHGy4AfEzHHntsnHrqqTm1pi1btrxx2mmn/cZ0AQDIBQJgAADeU0VFReONN974jcbGxqpcWtcFF1wQRx55pAEDwEd05JFHxuc+97mcWlNjY2PVzTff/I2KiopGEwYAIBcIgAEAeF9//OMf1z/yyCPfiRz6HnAqlYqLLroohgwZYsAA8CENHTo0LrrookilUrm0rPQjjzzynfvvv3+dCQMAkCsEwAAAfKBLL730hQULFvwupzbCeXnxpS99Kfbff38DBoAPcNBBB8WXvvSlyMvLrVdJCxYs+N2ll176ggkDAJBLBMAAAHwoo0eP/mVVVdXcXFpTYWFhfOUrX4kePXoYMAC8h169esUVV1wRBQUFObWuqqqqOaNHj/6lCQMAkGsEwAAAfCgVFRWNV1111S0NDQ0bcmldJSUlcfPNN8c+++xjyADwLr17946bbropiouLc2pdDQ0NG6666qqv++4vAAC5SAAMAMCHNmHChE3333//tzKZTDqX1lVcXBzXXXdddO/e3ZABYIfu3bvHtddeG61atcqpdWUymfT999//rQkTJmwyZQAAcpEAGACAj2Ts2LGz58+f/9tcW1fr1q3juuuuiw4dOhgyAM1ehw4d4rrrrovWrVvn3Nrmz5//m7Fjx842ZQAAcpUAGACAj+y44477xaZNm17MtXW1a9cubrzxxmjXrp0hA9BstW3bNmf/Pty4ceOLxx13nO/+AgCQ0wTAAAB8ZNXV1emLL774W3V1datzbW0dO3aMG2+8Mdq0aWPQADQ7ZWVlceONN0bHjh1zbm21tbWrL7zwwturq6vTJg0AQC7Lb3tQ9NzZD9LbImrLC3UIAICdWrZsWf327dtfOvbYY0/Ny8trkUtrKykpiaFDh8Zrr70WNTU1hg1As9ChQ4f42te+Fp06dcq5tTU1NW39zne+M/bBBx9cb9IAAOSC4u6NkVe6858JgAEA+NhefPHFzYcccsiyAQMGnBgRqZzaRBcXx5AhQ4TAADQLHTt2jJtuuik6dOiQi8tLjx8//ravfvWrc0waAIBc8X4BsCugAQD4RC688MIZb7zxxm9ycW3t27ePm266KSdPQgHA2zp16pTL4W+88cYbv77wwgufM2kAAJoLATAAAJ/YqFGjfrFhw4aZubi2t0Pgzp07GzQAOadz585x0003Rfv27XNyfRs2bJg5atSo+0waAIDmRAAMAMAnVltbm77sssu+V1dXtzoX19euXbu44YYbomPHjoYNQM7o0KFDXH/99dGuXbtc3Z+s/sIXvvDd2tratGkDANCc+AYwAAC7xJIlS+oj4pVRo0admpeX1yLX1ldcXBxDhw6NuXPnxrZt2wwcgETr0qVL3HjjjTl77XNTU1P1nXfeec0f/vCHdaYNAEAuer9vAAuAAQDYZWbNmlXVo0ePuYceeujoVCqVn2vra9WqVYwYMSIWLVoUlZWVBg5AIu23335x0003RVlZWU6uL51ON/z617++4fbbb3/TtAEAyFUCYAAA9phJkyatPeqoozbsu+++R+fi+goLC2P48OGxbNmy2LBhg4EDkCgDBgyIa6+9Nlq1apWza5w2bdq/XXLJJc+ZNgAAuez9AmDfAAYAYJc77bTTHn/rrbcezNX1FRUVxTXXXBNDhgwxbAASY8iQIXHNNddEUVFRzq5xwYIFvzv99NOfMG0AAJozATAAALvFEUcccc+GDRtm5ur6CgoK4sorr4wjjjjCsAFIwt/LceWVV0ZBQUHOrrG8vPypESNG/LdpAwDQ3AmAAQDYLaqrq9MXXXTRd2pra1fk7GY6Ly8uvvjiOPLIIw0cgKw1atSouPjiiyMvL3dfA23dunXxZz/72e83NDRkTBwAgObON4ABANhtli9fvr2mpuaFY4899uT8/PyWubjGVCoVgwYNikwmE4sWLTJ0ALLK6aefHueee26kUqmcXWN9fX3FDTfccM3UqVOrTBwAgObi/b4BLAAGAGC3evnll7cUFBS8cMQRR4zOy8trkYtrTKVSccABB0SnTp1i7ty5kck4fATA3lVYWBhXXHFFHHPMMTm9zsbGxuo777zzKz/72c9WmzoAAM2JABgAgL1qxowZlb169Xp98ODBJ6dSqfxcXWfPnj2jb9++8fe//z0aGxsNHoC9olWrVnH11VfHwIEDc3qd6XS64be//e2Nt9122wJTBwCguREAAwCw1z3xxBPlw4cPX9OvX79jIyJn76Hs2LFjDBw4MObMmRN1dXUGD8Ae1a5du7jxxhujT58+ub7U9JQpU779xS9+8XlTBwCgOXq/ADhPewAA2FPGjBkz9Y033vh1rq+zZ8+eceONN0bHjh0NHYA9pkuXLnHTTTdF9+7dc36tc+fO/eU555zzF1MHAID/SwAMAMAe9alPfernS5cufTjX19mlS5e49dZb48ADDzR0AHa7gQMHxje/+c3o1KlTzq/1rbfeemjEiBG/MnUAANg5ATAAAHvcsccee8/GjRtfyPV1FhcXx7XXXhsjRowwdAB2m8MPPzyuuuqqaNmyZc6vdePGjc8fffTR95g6AAC8NwEwAAB7XEVFReNxxx339crKyr/l+loLCgrisssui/PPPz9SqZThA7DLpFKpOP/88+PSSy+NgoKCnF/vpk2bXj7ssMNuqaysbDJ9AAB4bwJgAAD2isWLF9efddZZN1dXV7/ZHNZ7/PHHx5e//OUoKioyfAA+sRYtWsSXv/zlOP7445vFequrq98cM2bMN8rLyxtMHwAA3l9+24Oi585+kN4WUVteqEMAAOw2a9asaVi1atWs0aNHH1dQUNA619fbrVu3OOCAA2LevHlRX1/vAQDgYykrK4trrrkmDjrooGax3rq6uvKxY8de89RTT202fQAA+Ifi7o2RV7rznwmAAQDYq+bNm1ezevXqZ04++eTjCwoKSnN9ve3atYuRI0fG8uXLY+PGjR4AAD6S/v37x0033RRdu3ZtFuutr69fd9111335T3/6U4XpAwDA/xAAAwCQ1ebMmbMtlUq9fOSRR56Ul5eX83ckt2jRIkaMGBG1tbWxdOlSDwAAH8rxxx8fX/rSl5rN5wQaGxu33HXXXdf99Kc/XWn6AADwvwmAAQDIejNnzqzs2bPn64MHDz4plUrl5/p6U6lUHHLIIdGqVatYsGBBZDIZDwEAO5WXlxef/exn4/TTT49UKtUs1pxOp7f//ve//+o3vvGN1z0BAADwfwmAAQBIhEmTJpXvs88+8wYOHHhCKpUqaA5r3m+//WLAgAExd+5c3wUG4P8oKyuL6667LoYNG9Zs1pxOp7f/8Y9/vOmqq676qycAAAB2TgAMAEBiTJw4cc3BBx+85MADDzwulUrlNYc1t2/fPoYOHRqLFi2KLVu2eAgAiIiIffbZJ66//vro2bNns1lzJpNpnDBhwm1f/OIXn/cEAADAexMAAwCQKI8++ujy/fff/42DDjrohOZwHXRERHFxcRx11FHR2NgYb731locAoJkbPXp0XHHFFVFSUtJs1pxOpxsefvjhr15yySWzPAEAAPD+BMAAACTO+PHjVw0cOHDpAQcccGxzOQmcSqViwIAB0aVLl3jjjTeiqanJgwDQzBQVFcWll14aJ554YrP53m/EP07+Pv7447dffPHFMz0FAADwwQTAAAAk0iOPPLLs0EMPXbb//vs3mxA4IqJHjx4xZMiQePPNN2Pr1q0eBIBmonv37vEv//IvccABBzSrdWcymaYnnnji9s997nPPeAoAAODDEQADAJBYDz/88NJjjjlmY+/evY+KiGZzFKq0tDSGDx8eq1evjvXr13sQAHLcwIED45prrol27do1t6VnZsyY8YOzzjprqqcAAAA+PAEwAACJdv/99795zDHHVO6zzz5HRDMKgVu0aBGHHXZYtGzZMhYuXBjpdNrDAJBjCgoK4pxzzonzzz8/WrRo0dyWn37uuefuOuWUUyZ4EgAA4KMRAAMAkHi///3v5w8bNmxF3759j2lO10GnUqno27dvDB06NBYvXhxbtmzxMADkiJ49e8YNN9wQgwcPblbf+434x7XPU6ZM+fbpp58+2ZMAAAAfnQAYAICc8OCDDy4ZNmzYin79+jWrEDgionXr1nHEEUdEfX19LF261MMAkGCpVCpOOOGEuOKKK6JNmzbNbv07wt9vnXPOOX/xNAAAwMcjAAYAIGc89NBDS4YNG7a8X79+xza3EDg/Pz8OPvjg6NWrV8yfPz8aGho8EAAJU1JSEpdffnmccMIJkZ+f3+zWn8lkGp944olvffazn53maQAAgI9PAAwAQE556KGHlo4cOXJdnz59RqWa252ZEdG1a9cYNmxYLFu2LCorKz0QAAnRt2/fuO6662K//fZrluvPZDLpp59++rvnnHPO054GAAD4ZATAAADknD/96U+LDj300KX7779/s7sOOiKiuLg4jjzyyCgpKYk333wz0um0hwIgSxUUFMRnP/vZuPDCC6OkpKRZ9iCdTjc88sgjt5x//vnTPREAAPDJCYABAMhJDz/88NId3wQelUqlmt09mqlUKvr06RMHH3xwLFy4MLZt2+ahAMgynTt3jrFjx8bQoUOjGV5aERH/CH+feOKJ2y+88MLnPBEAALBrCIABAMhZDz300JIePXq8NmjQoGPz8vJaNMcetG3bNkaNGhVNTU2xZMkSDwVAFkilUjF69Oi48soro0OHDs22D01NTdt++9vf/stll132oqcCAAB2HQEwAAA5bdKkSeXdu3efM3jw4GYbAufn58eAAQOiV69esWDBgti+fbsHA2Avad26dVx66aVx/PHHR35+frPtQ2NjY/WvfvWrf7nuuute81QAAMCuJQAGACDnTZ48eW1jY+OMI4444uiCgoKS5tqHrl27xqhRo2Lbtm2xcuVKDwbAHpRKpWLUqFExduzY6NWrV7PuRX19/fo77rjjK9/61rcWejIAAGDXEwADANAsPP/881UbNmx45rjjjjuqsLCwrLn2obCwMAYNGhT77bdfLF68OGpraz0cALtZ+/bt44orrogTTzwxCgub9/uU2tralTfffPPVP/nJT1Z7MgAAYPcQAAMA0Gz87W9/27p27doZJ5xwwsjCwsK2zbkXnTp1ipEjR0Z1dbXTwAC70ciRI+Pqq6+OHj16NPte1NTULL/hhhuu/e1vf7vOkwEAALuPABgAgGbltdde2zpv3rynTznllKFFRUWdmnMvCgsL49BDD4399tsvFi1a5DQwwC709qnfk08+udmf+o2I2Lx587yLL774unHjxm30dAAAwO71fgFwat9zYsTOftC4LmLjq610DwCAxOrVq1fhs88++69du3Y9QTciGhoaYurUqTF58uRobGzUEICPqaCgIE499dQYPXq04HeH8vLyp4499tjvrly5skE3AABg9+swrDYKuuz8ZwJgAAByWuvWrfNeeumlm/fdd9+zdeMfVq9eHffff38sWbJEMwA+ov322y8uuugi1z2/w8KFC38/fPjwnzY0NGR0AwAA9gwBMAAAzd7zzz9/8aGHHnp1RKR0IyKTycTMmTPjz3/+c9TV1WkIwAcoKSmJ8847L0aMGBGplL9Kdki//PLL9xx77LEPagUAAOxZ7xcA+wYwAADNwn333Tfn+OOP39yrV6/DQwgcqVQqevfuHcOHD49169ZFRUWFhwTgPRxyyCExduzY6N+/v/B3h0wm0/jMM8/828knnzxONwAAYM97v28AC4ABAGg2fve7370xePDgJf369Ts6lUrl60hEcXFxjBgxInr06BFLly6N2tpaTQHYoUOHDvGFL3whzjzzzCguLtaQHZqammoeeOCBWz73uc9N1w0AANg7BMAAALDDww8/vKygoOC54cOHH1FQUFCqI//QrVu3OO6446K0tDQWL14cTU1NmgI0Wy1btoxzzjknLr300ujevbuGvENtbe2K22677arbbrvtDd0AAIC9RwAMAADv8Oyzz25avHjx0yeddNKQoqKiTjryD3l5edGnT58YOXJkbN26NVatWqUpQLNz+OGHx1e+8pUYMGBA5OXlacg7VFVV/e3CCy+8/oEHHvDdAAAA2MsEwAAA8C7z58+vefbZZ58+44wz+hcXF/fSkf/RsmXLGDJkSOyzzz6xdOnSqKmp0RQg53Xs2DG++MUvximnnBItW7bUkHdZt27dMyeffPI3Xn755W26AQAAe9/7BcCpfc+JETv7QeO6iI2vttI9AAByWmFhYer555//0sEHH3y5bvxfTU1N8fzzz8f48eOjurpaQ4Cc07p16zjzzDPjyCOPdOJ35zJ///vff3rMMcfc39DQkNEOAADIDh2G1UZBl53/TAAMAAARMWnSpM8cc8wxt6RSKdfg7ERNTU1MmTIlpk2bFg0NDRoCJF5hYWGccsopcdJJJ0VRUZGG7EQ6na6fOnXqd88555y/6AYAAGSX9wuAXQENAAAR8Yc//GHhfvvt98aAAQOOysvLkwS8S2FhYQwYMCCGDh0amzZtinXr1mkKkFiDBw+Oq666KoYOHRoFBQUashONjY1Vv//972/5whe+MEs3AAAg+/gGMAAAfAgTJkxYXV1dPf3II4/8VGFhYTsd+b9KS0vjsMMOi/79+8eaNWti8+bNmgIkRu/evePyyy+PU045JUpLSzXkPWzdunXxLbfccs33vve9hboBAADZyTeAAQDgI+jTp0+LJ5988us9evQ4TTfe3/z58+ORRx6JlStXagaQtXr16hXnnHNODBgwQDM+wKpVq5444YQTfrBy5Ur3/QMAQBbzDWAAAPgYnnnmmfOHDx9+fSqVytON95bJZGL27Nkxbty4qKio0BAga3Tu3DnOOuusGDp0aKRSKQ15/z/Lm2bNmvXDk08++THdAACA7OcbwAAA8DH85je/eX3//fd//cADDzzSd4HfWyqViu7du8cxxxwT7dq1i2XLlkV9fb3GAHtN27Zt49xzz42LL744evToIfz9AI2NjdUPPvjgLZ/97Gf/ohsAAJAMvgEMAAAf0/jx41dFxPOHHXbYiMLCwjIdeW95eXnRu3fvOOqoo6KgoCBWrlwZjY2NGgPsMcXFxXHKKafEl770pejbt2/k5bnA4YPU1tau+MEPfnD9LbfcMk83AAAgQf/94xvAAADwyQwePLjVo48++s1u3bqdpBsfTn19fTzzzDMxderU2LZtm4YAu01ZWVmceuqpceSRR0ZRkQsbPqyVK1c+/ulPf/pHixcvdm0DAAAkjG8AAwDALvLkk0+edeSRR96USqVcl/MhCYKB3eXtE7/HHnus4PcjyGQyDbNmzfoP3/sFAIDk8g1gAADYRX7/+98v6NGjx2uHHHLIyPz8fL8x+SEUFBREv3794qijjoq8vLxYtWqVq6GBT6Rly5ZxwgknxBVXXBEHHXRQFBQUaMqH1NDQsOG3v/3t1z7/+c8/oxsAAJBcroAGAIBd7Lzzzut4991339m2bdvBuvHR1NTUxLPPPhvTpk2LLVu2aAjwoZWVlcXxxx8fxxxzTBQXF2vIR1RVVfW3a6+99vZHHnlkg24AAECyuQIaAAB2g06dOhVMmzZtbN++fT8XESkd+WgaGhri+eefjyeffDI2bJBFAO/7522cdNJJccQRR0RhodvKPobMokWL/nTsscf+pLKyskk7AAAg+QTAAACwG/3hD38Ydfrpp99WUFDQRjc+unQ6Ha+++mpMnTo1Vq5cqSHAP/Xq1StOOeWUGDp0aOTl5WnIx9DY2Fg1YcKEOy666KKZugEAALlDAAwAALvZaaed1vbee+/9docOHUbqxse3fPnymDZtWrz88suRTqc1BJqhvLy8OOyww+L444+P3r17a8gnsHHjxue//OUvf3fSpElVugEAALlFAAwAAHtAYWFh6qmnnjpv+PDh16RSKXeUfgIbNmyIGTNmxHPPPRc1NTUaAs1AcXFxjBo1Ko4++ujo2LGjhnwCmUym4ZVXXvl/J5100kMNDQ0ZHQEAgNzzfgFwftuDoufOfpDeFlFb7p0VAAB8WOl0On7zm9+8Xlpa+uLgwYM/VVhYWKYrH09xcXEMGDAgjj322GjTpk2sXbs2amtrNQZyUIcOHeKMM86ISy+9NAYOHBjFxcWa8gnU1tau+slPfnLjxRdf/IybFAAAIHcVd2+MvNKd/8wJYAAA2A1OPPHENvfdd9+tnTp1Olo3PrnGxsb429/+Fs8++2wsWrRIQyAH9OvXL44++ugYNmxYFBQUaMguUFFR8cwXv/jFf5s+ffoW3QAAgNzmCmgAANhLHn744RNGjx799YKCgta6sWusX78+Zs6cGc8//3xUV1drCCRIaWlpHHnkkXHUUUdF586dNWQXaWxs3DJ16tS7PvvZz/5FNwAAoHkQAAMAwF50ySWXdP3+97//rXbt2g3VjV2nsbExXnvttXjuuedi/vz5GgJZbMCAATFq1KgYPHiw0767WFVV1atf//rXv/e73/1urW4AAEDzIQAGAIC9rF27dvlTp0699OCDD740lUrl68iutXz58nj++efj5ZdfjpqaGg2BLFBSUhLDhw+PI444Inr37q0hu1gmk2mcN2/efSeeeOJvq6urfewXAACaGQEwAABkia9//ev73Xjjjf9aWlraXzd2vXQ6HW+++WY899xz8dprr0VjY6OmwB5UUFAQgwcPjlGjRsUBBxwQeXl5mrIbVFdXL/zP//zPf/3BD36wRDcAAKB5EgADAEAWOfjgg1v9+c9/vrZ3795jIiKlI7tHZWVlvPjii/HCCy/EunXrNAR2o65du8bIkSPj8MMPj7Zt22rI7pNZunTpn88888z/t3jx4nrtAACA5ksADAAAWejuu+8eePHFF9/WqlUrd6PuZuXl5fHqq6/GSy+9FOvXr9cQ2AU6d+4cI0aMiGHDhkW3bt00ZDerqalZ9tvf/vbOm266aa5uAAAAAmAAAMhS/fr1K3r44Ycv79+//4WpVMpdqXvA8uXL46WXXoqXX345qqurNQQ+grKyshg+fHiMGDHCd333kEwmk164cOEfzj777F8sXbp0u44AAAARAmAAAMh6P/3pT4d97nOf+2bLli176Mae0dDQEHPnzo2//vWvMXfu3Ni+Xa4CO9OyZcsYNGhQDBs2LA455JAoKCjQlD2ktrZ21R//+Mc7rr322r/rBgAA8E4CYAAASIA+ffq0ePTRR69wGnjPS6fTsXTp0nj11VedDIaIaNu2bQwbNiyGDRsWffr0ibw8fyTtSZlMpuG11177+ZlnnvmnioqKRh0BAADeTQAMAAAJ8uMf//jQCy644JutWrXaRzf2vLdPBs+ePTvmzp0bdXV1mkKzUFZWFoMHD45hw4ZF//79Iz8/X1P2gpqammX333//nTfccINv/QIAAO9JAAwAAAnTrl27/HHjxp37qU996qq8vDwb870kk8nEihUrYu7cuTFnzpxYsWJFZDIZjSEnpFKp6Nu3bwwbNiwGDRoUHTt21JS9qKmpqfbVV1/92ZgxY/5cWVnZpCMAAMD7EQADAEBCffnLX+5x++23f7V9+/aH68bet2XLlnjjjTdizpw5MW/evKivr9cUEqVly5Zx8MEHx6BBg+KQQw6J0tJSTckCGzdufPG73/3uv//iF79YoxsAAMCHIQAGAIAEKywsTE2cOPGMkSNHXlNQUNBaR7JDXV1dvPnmm/HGG2/EG2+8EevXr9cUslKXLl3ioIMOioMOOigOOOCAKCoq0pQs0djYuGXmzJk/PvPMMyc2NDS4XgAAAPjQBMAAAJADjjzyyNY/+9nPrujbt++5EZGnI9mluro6Fi5cGPPnz4958+ZFZWWlprBXtGvXLg455JAYMGBA9O/fP1q39nsjWSj91ltv/fmqq676xaxZs6q1AwAA+KgEwAAAkEN++ctfjhgzZsyNrVq16q0b2SmdTsfKlStj0aJFsXDhwli8eHFs27ZNY9gtSkpKol+/ftG/f//Yf//9o1evXpGX53dEslVNTc3yRx999EdXXnnlK7oBAAB8XAJgAADIMd26dSt8+OGHPzd48ODL8vPzbdyzXCaTiTVr1sTChQtj0aJFsWjRotiyZYvG8LGUlZVF//79/xn6du/ePVKplMZkuaampprXXnvtV2PGjHmgoqKiUUcAAIBPQgAMAAA56rjjjiv7r//6r8tdC508mzdvjuXLl8eKFSti+fLlsXjx4qipqdEY/pfi4uLo169f9O7dO/bZZ5/Yd999o6ysTGMSJJPJpJcsWfLn66677pfTp0/3mx8AAMAuIQAGAIAc99Of/nTIueeee3NpaWlf3UimxsbGWLlyZSxdujSWLVsWy5cvj3Xr1kUmk9GcZiKVSkWXLl1in332iT59+kSfPn2iV69eUVBQoDkJtXXr1sUPPvjgj6699tq/6wYAALArCYABAKAZaNeuXf64cePOGTp06BUFBQWtdST56uvrY+XKlf88KbxixYpYu3ZtpNNpzUm4vLy86Nq1a+yzzz7//KdXr17RsmVLzckBjY2NW/7617/+4pxzznm0srKySUcAAIBdTQAMAADNyIknntjmnnvuuXzfffcdk0qlHB3MMdu3b481a9bEmjVrYu3atbF27dooLy+PDRs2CIazUF5eXnTs2DG6desWXbt2jW7dukW3bt2iR48eUVhYqEE5JpPJNC5ZsuRR1z0DAAC7mwAYAACaoa9+9av7Xnfdddd16NDhCN3IfY2Njf8MhNetWxfr1q2LioqKWLduXWzbtk2DdrPS0tLo3LnzP//p0qVLdO3aNbp27eoK52Ziw4YNM//rv/7rxz/60Y+W6wYAALC7CYABAKAZ++UvfznirLPOuq64uNj3gZupmpqaWL9+/T//qaioiMrKyqisrIxNmzZFY2OjJn2AgoKCaN++fbRr1y7at28fHTt2jC5dukSnTp2ic+fOUVxcrEnN1NatW98aP378PVdcccXLugEAAOwpAmAAAGjm2rVrl//ggw+eOWLEiCsLCwvb6gjvtHnz5n+GwZs2bYrKysqorq6OLVu2xJYtW6K6ujqqq6sjk8nk3NpTqVS0bt06WrduHWVlZdGmTZsoLS39X2Fvu3btok2bNh4U/peGhobKl1566efnnHPO+OrqavevAwAAe5QAGAAAiIiIoUOHFt97770XHHjggRfk5+c7ssiHlk6n/xkEV1dXR01Nzfv+k8lkora2NtLpdNTX10dTU1PU1dXt0u8U5+XlRcuWLSM/Pz+KiooiLy8vWrVqFalUKoqLi3f6T0lJSbRq1eqfgW9paWnk5eUZMB9aU1NTzYIFC/745S9/+Y+zZ8+u0REAAGBvEAADAAD/y2mnndb2rrvuurRPnz5n5+XlFeoIe9LbgfDbtm/f/r7XUBcUFESLFi3++b/fDnxhT8pkMg1LliwZd8stt/xq0qRJVToCAADsTQJgAABgp0477bS2d95554X777//5wTBAP9XJpNpWLhw4QO33nrrHwS/AABAtni/ADi/7UHRc2c/SG+LqC33/gcAAHLZokWL6u69995X0un0swcddFCnkpKS3roC8A8VFRUz/7//7/+77aKLLpq6aNGiOh0BAACyRXH3xsgr3fnPBMAAAEDMnDmz8u67736qqalpWr9+/Ypbt27dN5VKpXQGaG4ymUx6zZo1U+65555/Peeccx6cOXNmpa4AAADZRgAMAAB8KDNnzqz88Y9//Gw6nZ4uCAaak7eD37vvvvtfzz///McEvwAAQDYTAAMAAB+JIBhoLgS/AABAEgmAAQCAj+XtILisrOyFvn37diwuLu4VEYJgIBdkKioqnrv33nu/PWbMmEcEvwAAQJK8XwCc2vecGLGzHzSui9j4aivdAwAA/unqq6/u8ZWvfOX8Pn36nJWXl9dCR4CkSafT9UuXLh3/X//1Xw/84he/WKMjAABAEnUYVhsFXXb+MwEwAADwkZ1xxhntb7/99rMPPPDA8/Pz81vrCJDtmpqaqhcsWPDgd77znUcmTpzotC8AAJBoAmAAAGC3GD58eMkPf/jDzwwePPjioqKijjoCZJv6+voNr7322u9vvPHGx2fPnl2jIwAAQC4QAAMAALvV0KFDi++5556zDjnkkPOKioq66giwt9XV1a19/fXXH7z++uvHC34BAIBcIwAGAAD2iFatWuXdc889w0455ZTzO3bseJSOAHtYZsOGDbOmTJny4PXXX/9qbW1tWksAAIBcJAAGAAD2uH/913/tf/7555/dq1ev0/Ly8lroCLC7pNPp+pUrV05+4IEHHvnOd76zSEcAAIBcJwAGAAD2mjPOOKP97bfffvYBBxzw2YKCgjY6AuwqjY2NVW+++eafv/e97z06YcKETToCAAA0FwJgAABgrxs+fHjJ97///dGDBg0aU1paur+OAB9XdXX1wtdee+3Rr371q1Nfe+21Wh0BAACaGwEwAACQVb761a/ue8EFF3y6b9++ZxUUFLTWEeCDNDY2bnnrrbfG/+EPf5j4ox/9aLmOAAAAzZkAGAAAyEpDhw4t/sEPfnDy4MGDz27dunV/HQHerbq6+s2XX375weuuu+7ppUuXbtcRAAAAATAAAJDlCgsLU3ffffeQ0aNHn9G1a9fj8vLyinQFmq90Ol1XXl4+bcqUKROuvfbav+sIAADA/yYABgAAEqNPnz4t/v3f/33UyJEjz2rfvv2nIiKlK9AsZDZt2vTXF1544bGvfe1rzzntCwAA8N4EwAAAQCJddNFFXa6++uqTDzzwwLNbtmzZTUcg99TV1a1ZsGDBuJ/+9KdP3n///et0BAAA4IMJgAEAgETr1q1b4d13333k4YcffmqHDh2OyMvLK9QVSK50Ot2wcePGWbNmzZp8/fXXz6qoqGjUFQAAgA9PAAwAAOSMo48+uvWtt956/CGHHHJKu3btBkdEnq5AIqQrKytfmzdv3uQ777xz+owZM6q1BAAA4OMRAAMAADnpuOOOK/vGN75x/CGHHHJa27ZtB4bvBUO2yVRVVc2dN2/epO9///vTpk+fvkVLAAAAPjkBMAAAkPNuu+22vmedddZJffr0Oa5Vq1a9dQT2ntra2uXLly+f/uijjz51xx13vKUjAAAAu5YAGAAAaFa+9KUvdbv44ouP7t+//wlOBsMekamqqpq7cOHCv/z+97+fcd9995VrCQAAwO4jAAYAAJqtSy65pOtll112jDAYdrl0VVXVvIULF/7lV7/61bO/+93v1moJAADAniEABgAAiIirr766x/nnnz9q//33P7JNmzZDUqlUga7Ah5fJZBoqKyv/vnjx4uf+9Kc/zbr33ntX6woAAMCeJwAGAAB4l379+hV97WtfGzRy5MhRPXv2PLaoqKizrsD/VV9fv37VqlXPvPDCC8/9+7//+5zFixfX6woAAMDeJQAGAAB4H61bt8678847Bx599NFHde/efURpaen+4apomq/M1q1bF69Zs+bF5557btY3v/nNOdXV1WltAQAAyB4CYAAAgI9g8ODBrcaOHXvI8OHDD+vevfvw1q1bHxACYXJXprq6+s01a9a88sorr7z8k5/8ZN5rr71Wqy0AAADZSwAMAADwCVx//fX7nHHGGYf169dvePv27Yfm5+e31hWSrLGxsXrjxo2vvvXWWy+PHz/+lR//+McrdQUAACA5BMAAAAC70Je//OUe55xzzvA+ffoM7tix45CioqKuukI2q6+vX7t27doXFy9ePGfixImv3Xvvvat1BQAAILkEwAAAALvRl7/85R6f+cxnBvfr129Qly5dDm/ZsqVAmL2qrq5u7bp16wS+AAAAOUoADAAAsIe0atUq75prrtnn2GOPPXi//fY7uH379oeUlpb2TaVS+brDbpKuqalZtnHjxnlvvfXW3GeffXbef/3Xfy2vra1Naw0AAEBuEgADAADsRQceeGDLq6+++oAhQ4Yc3LNnz4Pbtm17sGuj+biampqqq6qqXi8vL583Z86ceb/85S/nvfjii1t1BgAAoPkQAAMAAGSZoUOHFn/xi1/cf9CgQQf26NHjwHbt2h3YqlWr3qlUKk932CFdU1OzvLKycsHq1asXzJkzZ8Gf//znJTNmzKjWGgAAgOZNAAwAAJAAxx13XNkFF1xw4MEHH3xA165dDywtLd23pKRkn1QqVag7uS2TyTRs27ZtRXV19dJ169YtfOONNxZOmDBh4YQJEzbpDgAAAO8mAAYAAEiw8847r+OJJ57Yp3///vt16dKlT5s2bfZr3bp1v/z8/GLdSZampqaa6urqxZs3b16ybt26pQsXLlzy9NNPL33ooYc26A4AAAAflgAYAAAgx7Rr1y7/kksu6TFkyJCe++67b89OnTr1Kisr61VcXNyzZcuW3VKpVL4u7R2ZTKaprq6uvKamZtWWLVtWVlRUrFy6dOnK2bNnr7r//vvXVFZWNukSAAAAn4QAGAAAoBnp1KlTwec+97luQ4cO7bnPPvv0aNeuXafWrVt3Li4u7tqyZcvORUVFnfLy8lro1MeTTqe319fXr6+rq6uoqalZW11dvb6ysnL9ihUrVr/66qurHnzwwbUVFRWNOgUAAMDuIgAGAADgfznjjDPaH3bYYZ369OnTuUuXLp3Kysral5SUtC0uLu5YVFTUrqioqG2LFi065OfnlzaXnjQ1NW3dvn37xvr6+qr6+vrKmpqaDdu2bavasmXLpnXr1lUsXbp0/Ysvvrh+4sSJlZ4gAAAA9iYBMAAAAB9Lr169CkeNGtXuoIMOatexY8ey9u3bl7Zp06Z1SUlJWXFxcetWrVq1btGiRVlRUVHrwsLC1hGRX1hYWBoR+QUFBSV5eXkFeXl5u/0/LtPpdG06nW5sbGzcFhFNDQ0NW3f83+r6+vrq7du3b6mtra2uqamp3rZtW/XmzZurN23aVL1hw4YtCxYsqJo1a1bl0qVLt5s4AAAASSAABgAAYK/q169fUffu3Vvss88+xSUlJQVv//8LCwtT3bp1+8BTxuXl5VsbGhoyb//vbdu2Na5YsaJmzZo12xcvXlyvwwAAADQn7xcAF2gPAAAAu9vixYvrdwS11boBAAAAu0+eFgAAAAAAAADkBgEwAAAAAAAAQI4QAAMAAAAAAADkCAEwAAAAAAAAQI4QAAMAAAAAAADkCAEwAAAAAAAAQI4QAAMAAAAAAADkCAEwAAAAAAAAQI4QAAMAAAAAAADkCAEwAAAAAAAAQI4QAAMAAAAAAADkCAEwAAAAAAAAQI4QAAMAAAAAAADkCAEwAAAAAAAAQI4o0AIAAAAAAACA5GhoLIyCxoaIiEilIpNXGE1v/0wADAAAAAAAAJAghQUN/0x6MxGppvT/5L6ugAYAAAAAAADIEQJgAAAAAAAAgBwhAAYAAAAAAADIEQJgAAAAAAAAgBwhAAYAAAAAAADIEQJgAAAAAAAAgBwhAAYAAAAAAADIEQJgAAAAAAAAgBwhAAYAAAAAAADIEQJgAAAAAAAAgBwhAAYAAAAAAADIEQJgAAAAAAAAgBwhAAYAAAAAAADIEQJgAAAAAAAAgBwhAAYAAAAAAADIEQJgAAAAAAAAgBwhAAYAAAAAAADIEQJgAAAAAAAAgBwhAAYAAAAAAADIEQJgAAAAAAAAgBwhAAYAAAAAAADIEQJgAAAAAAAAgBwhAAYAAAAAAADIEQJgAAAAAAAAgBwhAAYAAAAAAADIEQJgAAAAAAAAgBwhAAYAAAAAAADIEQJgAAAAAAAAgBwhAAYAAAAAAADIEQJgAAAAAAAAgBwhAAYAAAAAAADIEQJgAAAAAAAAgBwhAAYAAAAAAADIEQJgAAAAAAAAgBwhAAYAAAAAAADIEQJgAAAAAAAAgBwhAAYAAAAAAADIEQJgAAAAAAAAgBwhAAYAAAAAAADIEQJgAAAAAAAAgBwhAAYAAAAAAADIEQJgAAAAAAAAgBwhAAYAAAAAAADIEQJgAAAAAAAAgBwhAAYAAAAAAADIEQJgAAAAAAAAgBwhAAYAAAAAAADIEQJgAAAAAAAAgBwhAAYAAAAAAADIEQJgAAAAAAAAgBwhAAYAAAAAAADIEQJgAAAAAAAAgBwhAAYAAAAAAADIEQJgAAAAAAAAgBwhAAYAAAAAAADIEQJgAAAAAAAAgBwhAAYAAAAAAADIEQJgAAAAAAAAgBwhAAYAAAAAAADIEQJgAAAAAAAAgBwhAAYAAAAAAADIEQJgAAAAAAAAgBwhAAYAAAAAAADIEQJgAAAAAAAAgBwhAAYAAAAAAADIEQJgAAAAAAAAgBwhAAYAAAAAAADIEQJgAAAAAAAAgBwhAAYAAAAAAADIEQJgAAAAAAAAgBwhAAYAAAAAAADIEQJgAAAAAAAAgBwhAAYAAAAAAADIEQJgAAAAAAAAgBwhAAYAAAAAAADIEQJgAAAAAAAAgBwhAAYAAAAAAADIEQJgAAAAAAAAgBwhAAYAAAAAAADIEQJgAAAAAAAAgBwhAAYAAAAAAADIEQJgAAAAAAAAgBwhAAYAAAAAAADIEQJgAAAAAAAAgBwhAAYAAAAAAADIEQJgAAAAAAAAgBwhAAYAAAAAAADIEQJgAAAAAAAAgBwhAAYAAAAAAADIEQJgAAAAAAAAgBwhAAYAAAAAAADIEQJgAAAAAAAAgBwhAAYAAAAAAADIEQJgAAAAAAAAgBwhAAYAAAAAAADIEQJgAAAAAAAAgBwhAAYAAAAAAADIEQJgAAAAAAAAgBwhAAYAAAAAAADIEQJgAAAAAAAAgBwhAAYAAAAAAADIEQJgAAAAAAAAgBwhAAYAAAAAAADIEQJgAAAAAAAAgBwhAAYAAAAAAADIEQJgAAAAAAAAgBwhAIb/v5272ZHiusM4/FZ1NUkz9sQwOF4EyZJtpJCwysa5jSy4n1xPEqRIuQFvvfGSgIwBOzGRQAQERnx0d1UW0cgWGvKxsMGvnmfVdc7/1OJsf+oCAAAAAACAEgIwAAAAAAAAQAkBGAAAAAAAAKCEAAwAAAAAAABQQgAGAAAAAAAAKCEAAwAAAAAAAJQQgAEAAAAAAABKCMAAAAAAAAAAJQRgAAAAAAAAgBICMAAAAAAAAEAJARgAAAAAAACghAAMAAAAAAAAUEIABgAAAAAAACghAAMAAAAAAACUEIABAAAAAAAASgjAAAAAAAAAACUEYAAAAAAAAIASAjAAAAAAAABACQEYAAAAAAAAoIQADAAAAAAAAFBCAAYAAAAAAAAoIQADAAAAAAAAlBCAAQAAAAAAAEoIwAAAAAAAAAAlBGAAAAAAAACAEgIwAAAAAAAAQAkBGAAAAAAAAKCEAAwAAAAAAABQQgAGAAAAAAAAKCEAAwAAAAAAAJQQgAEAAAAAAABKCMAAAAAAAAAAJQRgAAAAAAAAgBICMAAAAAAAAEAJARgAAAAAAACghAAMAAAAAAAAUEIABgAAAAAAACghAAMAAAAAAACUEIABAAAAAAAASgjAAAAAAAAAACUEYAAAAAAAAIASAjAAAAAAAABACQEYAAAAAAAAoIQADAAAAAAAAFBCAAYAAAAAAAAoIQADAAAAAAAAlBCAAQAAAAAAAEoIwAAAAAAAAAAlBGAAAAAAAACAEgIwAAAAAAAAQAkBGAAAAAAAAKCEAAwAAAAAAABQQgAGAAAAAAAAKCEAAwAAAAAAAJQQgAEAAAAAAABKCMAAAAAAAAAAJQRgAAAAAAAAgBICMAAAAAAAAEAJARgAAAAAAACghAAMAAAAAAAAUEIABgAAAAAAACghAAMAAAAAAACUEIABAAAAAAAASgjAAAAAAAAAACUEYAAAAAAAAIASAjAAAAAAAABACQEYAAAAAAAAoIQADAAAAAAAAFBCAAYAAAAAAAAoIQADAAAAAAAAlBCAAQAAAAAAAEoIwAAAAAAAAAAlBGAAAAAAAACAEgIwAAAAAAAAQAkBGAAAAAAAAKCEAAwAAAAAAABQQgAGAAAAAAAAKCEAAwAAAAAAAJQQgAEAAAAAAABKCMAAAAAAAAAAJQRgAAAAAAAAgBICMAAAAAAAAEAJARgAAAAAAACghAAMAAAAAAAAUEIABgAAAAAAACghAAMAAAAAAACUEIABAAAAAAAASgjAAAAAAAAAACUEYAAAAAAAAIASAjAAAAAAAABACQEYAAAAAAAAoIQADAAAAAAAAFBCAAYAAAAAAAAoIQADAAAAAAAAlBCAAQAAAAAAAEoIwAAAAAAAAAAlBGAAAAAAAACAEgIwAAAAAAAAQAkBGAAAAAAAAKCEAAwAAAAAAABQQgAGAAAAAAAAKCEAAwAAAAAAAJQQgAEAAAAAAABKCMAAAAAAAAAAJQRgAAAAAAAAgBICMAAAAAAAAEAJARgAAAAAAACghAAMAAAAAAAAUEIABgAAAAAAACghAAMAAAAAAACUEIABAAAAAAAASgjAAAAAAAAAACUEYAAAAAAAAIASAjAAAAAAAABACQEYAAAAAAAAoIQADAAAAAAAAFBCAAYAAAAAAAAoIQADAAAAAAAAlBCAAQAAAAAAAEoIwAAAAAAAAAAlBGAAAAAAAACAEgIwAAAAAAAAQAkBGAAAAAAAAKCEAAwAAAAAAABQQgAGAAAAAAAAKCEAAwAAAAAAAJQQgAEAAAAAAABKCMAAAAAAAAAAJQRgAAAAAAAAgBICMAAAAAAAAEAJARgAAAAAAACghAAMAAAAAAAAUEIABgAAAAAAACghAAMAAAAAAACUEIABAAAAAAAASgjAAAAAAAAAACUEYAAAAAAAAIASAjAAAAAAAABACQEYAAAAAAAAoIQADAAAAAAAAFBCAAYAAAAAAAAoIQADAAAAAAAAlBCAAQAAAAAAAEoIwAAAAAAAAAAlBGAAAAAAAACAEgIwAAAAAAAAQAkBGAAAAAAAAKCEAAwAAAAAAABQQgAGAAAAAAAAKCEAAwAAAAAAAJQQgAEAAAAAAABKCMAAAAAAAAAAJQRgAAAAAAAAgBICMAAAAAAAAEAJARgAAAAAAACghAAMAAAAAAAAUEIABgAAAAAAACghAAMAAAAAAACUEIABAAAAAAAASgjAAAAAAAAAACUEYAAAAAAAAIASAjAAAAAAAABACQEYAAAAAAAAoIQADAAAAAAAAFBCAAYAAAAAAAAoIQADAAAAAAAAlBCAAQAAAAAAAEoIwAAAAAAAAAAlBGAAAAAAAACAEgIwAAAAAAAAQAkBGAAAAAAAAKCEAAwAAAAAAABQQgAGAAAAAAAAKCEAAwAAAAAAAJQQgAEAAAAAAABKCMAAAAAAAAAAJQRgAAAAAAAAgBICMAAAAAAAAEAJARgAAAAAAACghAAMAAAAAAAAUEIABgAAAAAAACghAAMAAAAAAACUEIABAAAAAAAASgjAAAAAAAAAACUEYAAAAAAAAIASAjAAAAAAAABACQEYAAAAAAAAoIQADAAAAAAAAFBCAAYAAAAAAAAoIQADAAAAAAAAlBCAAQAAAAAAAEoIwAAAAAAAAAAlBGAAAAAAAACAEgIwAAAAAAAAQAkBGAAAAAAAAKCEAAwAAAAAAABQQgAGAAAAAAAAKCEAAwAAAAAAAJQQgAEAAAAAAABKCMAAAAAAAAAAJQRgAAAAAAAAgBICMAAAAAAAAEAJARgAAAAAAACghAAMAAAAAAAAUEIABgAAAAAAACghAAMAAAAAAACUEIABAAAAAAAASgjAAAAAAAAAACUEYAAAAAAAAIASAjAAAAAAAABACQEYAAAAAAAAoIQADAAAAAAAAFBCAAYAAAAAAAAoMY3JP//9K8My551lzpAk293a7QAAAAAAAAD8iIw3r+SL7TpPxuSt4/ibJJuz20QDBgAAAAAAAHhzrJPp6NvHYcyyjLl7dJTPbl/Jp6sPLufSsM+5ec7q5YOrF0OeP/SVaAAAAAAAAIA3weH7u6zfm79dWDIMSw6225w785vsx3XyzasOby5ssz69c4sAAAAAAAAAr9n69C6bC9sT93a7rKdnORin5Otxynzi1JQcfiwCAwAAAAAAALxO69O7HH68TaaT98cp85R8vbp3Nfszv8q0LHn7xMF1sjk/ZzUPmZ/MmWefhAYAAAAAAAD4oayPdjn7223Gn7x6ZrXPnetX8mBKkpv3cufDd/Pufn5FL56SzcVtNheT3dNt8tQlAwAAAAAAAHyv1sm0ySv/9XtsNWZ3I/lHkgzHi7/4XY7WYz5yiwAAAAAAAAA/Ivtcv/3nPEyS1fHa42t5+vNLyX7JoRsCAAAAAAAAePPt1/nbV3/KvePn1Xc371/NYxEYAAAAAAAA4M0yTHk2rvNo2WdzvHZqzN9v/SF3vju3evng/at5fPDLPDu1yuGyZHSVAAAAAAAAAK/XOGe4ueSvZ4e8M8xZbVe5ceuPufvy3Oqkw4+v5emDX+fuuTlLhhwsEYIBAAAAAAAAXpclGR8OuXP0TR4cPM/9z/+SRyfNDf/1TZezOp/87KdjzizJZkhO7edMy/w/nAUAAAAAAADg/zaMWcZkP8/ZLsmLacr2/MV8+cnvs/tP5/4FmLjAq1ifcioAAAAASUVORK5CYII='; + +/** + * PptxGenJS: Utility Methods + */ +/** + * Translates any type of `x`/`y`/`w`/`h` prop to EMU + * - guaranteed to return a result regardless of undefined, null, etc. (0) + * - {number} - 12800 (EMU) + * - {number} - 0.5 (inches) + * - {string} - "75%" + * @param {number|string} size - numeric ("5.5") or percentage ("90%") + * @param {'X' | 'Y'} xyDir - direction + * @param {PresLayout} layout - presentation layout + * @returns {number} calculated size + */ +function getSmartParseNumber(size, xyDir, layout) { + // FIRST: Convert string numeric value if reqd + if (typeof size === 'string' && !isNaN(Number(size))) + size = Number(size); + // CASE 1: Number in inches + // Assume any number less than 100 is inches + if (typeof size === 'number' && size < 100) + return inch2Emu(size); + // CASE 2: Number is already converted to something other than inches + // Assume any number greater than 100 sure isnt inches! Just return it (assume value is EMU already). + if (typeof size === 'number' && size >= 100) + return size; + // CASE 3: Percentage (ex: '50%') + if (typeof size === 'string' && size.includes('%')) { + if (xyDir && xyDir === 'X') + return Math.round((parseFloat(size) / 100) * layout.width); + if (xyDir && xyDir === 'Y') + return Math.round((parseFloat(size) / 100) * layout.height); + // Default: Assume width (x/cx) + return Math.round((parseFloat(size) / 100) * layout.width); + } + // LAST: Default value + return 0; +} +/** + * Basic UUID Generator Adapted + * @link https://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript#answer-2117523 + * @param {string} uuidFormat - UUID format + * @returns {string} UUID + */ +function getUuid(uuidFormat) { + return uuidFormat.replace(/[xy]/g, function (c) { + var r = (Math.random() * 16) | 0; + var v = c === 'x' ? r : (r & 0x3) | 0x8; + return v.toString(16); + }); +} +/** + * Replace special XML characters with HTML-encoded strings + * @param {string} xml - XML string to encode + * @returns {string} escaped XML + */ +function encodeXmlEntities(xml) { + // NOTE: Dont use short-circuit eval here as value c/b "0" (zero) etc.! + if (typeof xml === 'undefined' || xml == null) + return ''; + return xml.toString().replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"').replace(/'/g, '''); +} +/** + * Convert inches into EMU + * @param {number|string} inches - as string or number + * @returns {number} EMU value + */ +function inch2Emu(inches) { + // NOTE: Provide Caller Safety: Numbers may get conv<->conv during flight, so be kind and do some simple checks to ensure inches were passed + // Any value over 100 damn sure isnt inches, so lets assume its in EMU already, therefore, just return the same value + if (typeof inches === 'number' && inches > 100) + return inches; + if (typeof inches === 'string') + inches = Number(inches.replace(/in*/gi, '')); + return Math.round(EMU * inches); +} +/** + * Convert `pt` into points (using `ONEPT`) + * @param {number|string} pt + * @returns {number} value in points (`ONEPT`) + */ +function valToPts(pt) { + var points = Number(pt) || 0; + return isNaN(points) ? 0 : Math.round(points * ONEPT); +} +/** + * Convert degrees (0..360) to PowerPoint `rot` value + * @param {number} d degrees + * @returns {number} calculated `rot` value + */ +function convertRotationDegrees(d) { + d = d || 0; + return Math.round((d > 360 ? d - 360 : d) * 60000); +} +/** + * Converts component value to hex value + * @param {number} c - component color + * @returns {string} hex string + */ +function componentToHex(c) { + var hex = c.toString(16); + return hex.length === 1 ? '0' + hex : hex; +} +/** + * Converts RGB colors from css selectors to Hex for Presentation colors + * @param {number} r - red value + * @param {number} g - green value + * @param {number} b - blue value + * @returns {string} XML string + */ +function rgbToHex(r, g, b) { + return (componentToHex(r) + componentToHex(g) + componentToHex(b)).toUpperCase(); +} +/** TODO: FUTURE: TODO-4.0: + * @date 2022-04-10 + * @tldr this s/b a private method with all current calls switched to `genXmlColorSelection()` + * @desc lots of code calls this method + * @example [gen-charts.tx] `strXml += '' + createColorElement(seriesColor, ``) + ''` + * Thi sis wrong. We s/b calling `genXmlColorSelection()` instead as it returns `BLAH`!! + */ +/** + * Create either a `a:schemeClr` - (scheme color) or `a:srgbClr` (hexa representation). + * @param {string|SCHEME_COLORS} colorStr - hexa representation (eg. "FFFF00") or a scheme color constant (eg. pptx.SchemeColor.ACCENT1) + * @param {string} innerElements - additional elements that adjust the color and are enclosed by the color element + * @returns {string} XML string + */ +function createColorElement(colorStr, innerElements) { + var colorVal = (colorStr || '').replace('#', ''); + if (!REGEX_HEX_COLOR.test(colorVal) && + colorVal !== SchemeColor.background1 && + colorVal !== SchemeColor.background2 && + colorVal !== SchemeColor.text1 && + colorVal !== SchemeColor.text2 && + colorVal !== SchemeColor.accent1 && + colorVal !== SchemeColor.accent2 && + colorVal !== SchemeColor.accent3 && + colorVal !== SchemeColor.accent4 && + colorVal !== SchemeColor.accent5 && + colorVal !== SchemeColor.accent6) { + console.warn("\"".concat(colorVal, "\" is not a valid scheme color or hex RGB! \"").concat(DEF_FONT_COLOR, "\" used instead. Only provide 6-digit RGB or 'pptx.SchemeColor' values!")); + colorVal = DEF_FONT_COLOR; + } + var tagName = REGEX_HEX_COLOR.test(colorVal) ? 'srgbClr' : 'schemeClr'; + var colorAttr = 'val="' + (REGEX_HEX_COLOR.test(colorVal) ? colorVal.toUpperCase() : colorVal) + '"'; + return innerElements ? "").concat(innerElements, "") : ""); +} +/** + * Creates `a:glow` element + * @param {TextGlowProps} options glow properties + * @param {TextGlowProps} defaults defaults for unspecified properties in `opts` + * @see http://officeopenxml.com/drwSp-effects.php + * { size: 8, color: 'FFFFFF', opacity: 0.75 }; + */ +function createGlowElement(options, defaults) { + var strXml = ''; + var opts = __assign(__assign({}, defaults), options); + var size = Math.round(opts.size * ONEPT); + var color = opts.color; + var opacity = Math.round(opts.opacity * 100000); + strXml += ""); + strXml += createColorElement(color, "")); + strXml += ''; + return strXml; +} +/** + * Create color selection + * @param {Color | ShapeFillProps | ShapeLineProps} props fill props + * @returns XML string + */ +function genXmlColorSelection(props) { + var fillType = 'solid'; + var colorVal = ''; + var internalElements = ''; + var outText = ''; + if (props) { + if (typeof props === 'string') + colorVal = props; + else { + if (props.type) + fillType = props.type; + if (props.color) + colorVal = props.color; + if (props.alpha) + internalElements += ""); // DEPRECATED: @deprecated v3.3.0 + if (props.transparency) + internalElements += ""); + } + switch (fillType) { + case 'solid': + outText += "".concat(createColorElement(colorVal, internalElements), ""); + break; + default: // @note need a statement as having only "break" is removed by rollup, then tiggers "no-default" js-linter + outText += ''; + break; + } + } + return outText; +} +/** + * Get a new rel ID (rId) for charts, media, etc. + * @param {PresSlide} target - the slide to use + * @returns {number} count of all current rels plus 1 for the caller to use as its "rId" + */ +function getNewRelId(target) { + return target._rels.length + target._relsChart.length + target._relsMedia.length + 1; +} +/** + * Checks shadow options passed by user and performs corrections if needed. + * @param {ShadowProps} ShadowProps - shadow options + */ +function correctShadowOptions(ShadowProps) { + if (!ShadowProps || typeof ShadowProps !== 'object') { + // console.warn("`shadow` options must be an object. Ex: `{shadow: {type:'none'}}`") + return; + } + // OPT: `type` + if (ShadowProps.type !== 'outer' && ShadowProps.type !== 'inner' && ShadowProps.type !== 'none') { + console.warn('Warning: shadow.type options are `outer`, `inner` or `none`.'); + ShadowProps.type = 'outer'; + } + // OPT: `angle` + if (ShadowProps.angle) { + // A: REALITY-CHECK + if (isNaN(Number(ShadowProps.angle)) || ShadowProps.angle < 0 || ShadowProps.angle > 359) { + console.warn('Warning: shadow.angle can only be 0-359'); + ShadowProps.angle = 270; + } + // B: ROBUST: Cast any type of valid arg to int: '12', 12.3, etc. -> 12 + ShadowProps.angle = Math.round(Number(ShadowProps.angle)); + } + // OPT: `opacity` + if (ShadowProps.opacity) { + // A: REALITY-CHECK + if (isNaN(Number(ShadowProps.opacity)) || ShadowProps.opacity < 0 || ShadowProps.opacity > 1) { + console.warn('Warning: shadow.opacity can only be 0-1'); + ShadowProps.opacity = 0.75; + } + // B: ROBUST: Cast any type of valid arg to int: '12', 12.3, etc. -> 12 + ShadowProps.opacity = Number(ShadowProps.opacity); + } + // OPT: `color` + if (ShadowProps.color) { + // INCORRECT FORMAT + if (ShadowProps.color.startsWith('#')) { + console.warn('Warning: shadow.color should not include hash (#) character, , e.g. "FF0000"'); + ShadowProps.color = ShadowProps.color.replace('#', ''); + } + } + return ShadowProps; +} + +/** + * PptxGenJS: Table Generation + */ +/** + * Break cell text into lines based upon table column width (e.g.: Magic Happens Here(tm)) + * @param {TableCell} cell - table cell + * @param {number} colWidth - table column width (inches) + * @return {TableRow[]} - cell's text objects grouped into lines + */ +function parseTextToLines(cell, colWidth, verbose) { + var _a, _b; + // FYI: CPL = Width / (font-size / font-constant) + // FYI: CHAR:2.3, colWidth:10, fontSize:12 => CPL=138, (actual chars per line in PPT)=145 [14.5 CPI] + // FYI: CHAR:2.3, colWidth:7 , fontSize:12 => CPL= 97, (actual chars per line in PPT)=100 [14.3 CPI] + // FYI: CHAR:2.3, colWidth:9 , fontSize:16 => CPL= 96, (actual chars per line in PPT)=84 [ 9.3 CPI] + var FOCO = 2.3 + (((_a = cell.options) === null || _a === void 0 ? void 0 : _a.autoPageCharWeight) ? cell.options.autoPageCharWeight : 0); // Character Constant + var CPL = Math.floor((colWidth / ONEPT) * EMU) / ((((_b = cell.options) === null || _b === void 0 ? void 0 : _b.fontSize) ? cell.options.fontSize : DEF_FONT_SIZE) / FOCO); // Chars-Per-Line + var parsedLines = []; + var inputCells = []; + var inputLines1 = []; + var inputLines2 = []; + /* + if (cell.options && cell.options.autoPageCharWeight) { + let CHR1 = 2.3 + (cell.options && cell.options.autoPageCharWeight ? cell.options.autoPageCharWeight : 0) // Character Constant + let CPL1 = ((colWidth / ONEPT) * EMU) / ((cell.options && cell.options.fontSize ? cell.options.fontSize : DEF_FONT_SIZE) / CHR1) // Chars-Per-Line + console.log(`cell.options.autoPageCharWeight: '${cell.options.autoPageCharWeight}' => CPL: ${CPL1}`) + let CHR2 = 2.3 + 0 + let CPL2 = ((colWidth / ONEPT) * EMU) / ((cell.options && cell.options.fontSize ? cell.options.fontSize : DEF_FONT_SIZE) / CHR2) // Chars-Per-Line + console.log(`cell.options.autoPageCharWeight: '0' => CPL: ${CPL2}`) + } + */ + /** + * EX INPUTS: `cell.text` + * - string....: "Account Name Column" + * - object....: { text:"Account Name Column" } + * - object[]..: [{ text:"Account Name", options:{ bold:true } }, { text:" Column" }] + * - object[]..: [{ text:"Account Name", options:{ breakLine:true } }, { text:"Input" }] + */ + /** + * EX OUTPUTS: + * - string....: [{ text:"Account Name Column" }] + * - object....: [{ text:"Account Name Column" }] + * - object[]..: [{ text:"Account Name", options:{ breakLine:true } }, { text:"Input" }] + * - object[]..: [{ text:"Account Name", options:{ breakLine:true } }, { text:"Input" }] + */ + // STEP 1: Ensure inputCells is an array of TableCells + if (cell.text && cell.text.toString().trim().length === 0) { + // Allow a single space/whitespace as cell text (user-requested feature) + inputCells.push({ _type: SLIDE_OBJECT_TYPES.tablecell, text: ' ' }); + } + else if (typeof cell.text === 'number' || typeof cell.text === 'string') { + inputCells.push({ _type: SLIDE_OBJECT_TYPES.tablecell, text: (cell.text || '').toString().trim() }); + } + else if (Array.isArray(cell.text)) { + inputCells = cell.text; + } + if (verbose) { + console.log('[1/4] inputCells'); + inputCells.forEach(function (cell, idx) { return console.log("[1/4] [".concat(idx + 1, "] cell: ").concat(JSON.stringify(cell))); }); + // console.log('...............................................\n\n') + } + // STEP 2: Group table cells into lines based on "\n" or `breakLine` prop + /** + * - EX: `[{ text:"Input Output" }, { text:"Extra" }]` == 1 line + * - EX: `[{ text:"Input" }, { text:"Output", options:{ breakLine:true } }]` == 1 line + * - EX: `[{ text:"Input\nOutput" }]` == 2 lines + * - EX: `[{ text:"Input", options:{ breakLine:true } }, { text:"Output" }]` == 2 lines + */ + var newLine = []; + inputCells.forEach(function (cell) { + var _a; + // (this is always true, we just constructed them above, but we need to tell typescript b/c type is still string||Cell[]) + if (typeof cell.text === 'string') { + if (cell.text.split('\n').length > 1) { + cell.text.split('\n').forEach(function (textLine) { + newLine.push({ + _type: SLIDE_OBJECT_TYPES.tablecell, + text: textLine, + options: __assign(__assign({}, cell.options), { breakLine: true }), + }); + }); + } + else { + newLine.push({ + _type: SLIDE_OBJECT_TYPES.tablecell, + text: cell.text.trim(), + options: cell.options, + }); + } + if ((_a = cell.options) === null || _a === void 0 ? void 0 : _a.breakLine) { + if (verbose) + console.log("inputCells: new line > ".concat(JSON.stringify(newLine))); + inputLines1.push(newLine); + newLine = []; + } + } + // Flush buffer + if (newLine.length > 0) { + inputLines1.push(newLine); + newLine = []; + } + }); + if (verbose) { + console.log("[2/4] inputLines1 (".concat(inputLines1.length, ")")); + inputLines1.forEach(function (line, idx) { return console.log("[2/4] [".concat(idx + 1, "] line: ").concat(JSON.stringify(line))); }); + // console.log('...............................................\n\n') + } + // STEP 3: Tokenize every text object into words (then it's really easy to assemble lines below without having to break text, add its `options`, etc.) + inputLines1.forEach(function (line) { + line.forEach(function (cell) { + var lineCells = []; + var cellTextStr = String(cell.text); // force convert to string (compiled JS is better with this than a cast) + var lineWords = cellTextStr.split(' '); + lineWords.forEach(function (word, idx) { + var cellProps = __assign({}, cell.options); + // IMPORTANT: Handle `breakLine` prop - we cannot apply to each word - only apply to very last word! + if (cellProps === null || cellProps === void 0 ? void 0 : cellProps.breakLine) + cellProps.breakLine = idx + 1 === lineWords.length; + lineCells.push({ _type: SLIDE_OBJECT_TYPES.tablecell, text: word + (idx + 1 < lineWords.length ? ' ' : ''), options: cellProps }); + }); + inputLines2.push(lineCells); + }); + }); + if (verbose) { + console.log("[3/4] inputLines2 (".concat(inputLines2.length, ")")); + inputLines2.forEach(function (line) { return console.log("[3/4] line: ".concat(JSON.stringify(line))); }); + // console.log('...............................................\n\n') + } + // STEP 4: Group cells/words into lines based upon space consumed by word letters + inputLines2.forEach(function (line) { + var lineCells = []; + var strCurrLine = ''; + line.forEach(function (word) { + // A: create new line when horizontal space is exhausted + if (strCurrLine.length + word.text.length > CPL) { + // if (verbose) console.log(`STEP 4: New line added: (${strCurrLine.length} + ${word.text.length} > ${CPL})`); + parsedLines.push(lineCells); + lineCells = []; + strCurrLine = ''; + } + // B: add current word to line cells + lineCells.push(word); + // C: add current word to `strCurrLine` which we use to keep track of line's char length + strCurrLine += word.text.toString(); + }); + // Flush buffer: Only create a line when there's text to avoid empty row + if (lineCells.length > 0) + parsedLines.push(lineCells); + }); + if (verbose) { + console.log("[4/4] parsedLines (".concat(parsedLines.length, ")")); + parsedLines.forEach(function (line, idx) { return console.log("[4/4] [Line ".concat(idx + 1, "]:\n").concat(JSON.stringify(line))); }); + console.log('...............................................\n\n'); + } + // Done: + return parsedLines; +} +/** + * Takes an array of table rows and breaks into an array of slides, which contain the calculated amount of table rows that fit on that slide + * @param {TableCell[][]} tableRows - table rows + * @param {TableToSlidesProps} tableProps - table2slides properties + * @param {PresLayout} presLayout - presentation layout + * @param {SlideLayout} masterSlide - master slide + * @return {TableRowSlide[]} array of table rows + */ +function getSlidesForTableRows(tableRows, tableProps, presLayout, masterSlide) { + if (tableRows === void 0) { tableRows = []; } + if (tableProps === void 0) { tableProps = {}; } + var arrInchMargins = DEF_SLIDE_MARGIN_IN; + var emuSlideTabW = EMU * 1; + var emuSlideTabH = EMU * 1; + var emuTabCurrH = 0; + var numCols = 0; + var tableRowSlides = []; + var tablePropX = getSmartParseNumber(tableProps.x, 'X', presLayout); + var tablePropY = getSmartParseNumber(tableProps.y, 'Y', presLayout); + var tablePropW = getSmartParseNumber(tableProps.w, 'X', presLayout); + var tablePropH = getSmartParseNumber(tableProps.h, 'Y', presLayout); + var tableCalcW = tablePropW; + function calcSlideTabH() { + var emuStartY = 0; + if (tableRowSlides.length === 0) + emuStartY = tablePropY || inch2Emu(arrInchMargins[0]); + if (tableRowSlides.length > 0) + emuStartY = inch2Emu(tableProps.autoPageSlideStartY || tableProps.newSlideStartY || arrInchMargins[0]); + emuSlideTabH = (tablePropH || presLayout.height) - emuStartY - inch2Emu(arrInchMargins[2]); + // console.log(`| startY .......................................... = ${(emuStartY / EMU).toFixed(1)}`) + // console.log(`| emuSlideTabH .................................... = ${(emuSlideTabH / EMU).toFixed(1)}`) + if (tableRowSlides.length > 1) { + // D: RULE: Use margins for starting point after the initial Slide, not `opt.y` (ISSUE #43, ISSUE #47, ISSUE #48) + if (typeof tableProps.autoPageSlideStartY === 'number') { + emuSlideTabH = (tablePropH || presLayout.height) - inch2Emu(tableProps.autoPageSlideStartY + arrInchMargins[2]); + } + else if (typeof tableProps.newSlideStartY === 'number') { + // @deprecated v3.3.0 + emuSlideTabH = (tablePropH || presLayout.height) - inch2Emu(tableProps.newSlideStartY + arrInchMargins[2]); + } + else if (tablePropY) { + emuSlideTabH = (tablePropH || presLayout.height) - inch2Emu((tablePropY / EMU < arrInchMargins[0] ? tablePropY / EMU : arrInchMargins[0]) + arrInchMargins[2]); + // Use whichever is greater: area between margins or the table H provided (dont shrink usable area - the whole point of over-riding Y on paging is to *increase* usable space) + if (emuSlideTabH < tablePropH) + emuSlideTabH = tablePropH; + } + } + } + if (tableProps.verbose) { + console.log('[[VERBOSE MODE]]'); + console.log('|-- TABLE PROPS --------------------------------------------------------|'); + console.log("| presLayout.width ................................ = ".concat((presLayout.width / EMU).toFixed(1))); + console.log("| presLayout.height ............................... = ".concat((presLayout.height / EMU).toFixed(1))); + console.log("| tableProps.x .................................... = ".concat(typeof tableProps.x === 'number' ? (tableProps.x / EMU).toFixed(1) : tableProps.x)); + console.log("| tableProps.y .................................... = ".concat(typeof tableProps.y === 'number' ? (tableProps.y / EMU).toFixed(1) : tableProps.y)); + console.log("| tableProps.w .................................... = ".concat(typeof tableProps.w === 'number' ? (tableProps.w / EMU).toFixed(1) : tableProps.w)); + console.log("| tableProps.h .................................... = ".concat(typeof tableProps.h === 'number' ? (tableProps.h / EMU).toFixed(1) : tableProps.h)); + console.log("| tableProps.slideMargin .......................... = ".concat(tableProps.slideMargin ? String(tableProps.slideMargin) : '')); + console.log("| tableProps.margin ............................... = ".concat(String(tableProps.margin))); + console.log("| tableProps.colW ................................. = ".concat(String(tableProps.colW))); + console.log("| tableProps.autoPageSlideStartY .................. = ".concat(tableProps.autoPageSlideStartY)); + console.log("| tableProps.autoPageCharWeight ................... = ".concat(tableProps.autoPageCharWeight)); + console.log('|-- CALCULATIONS -------------------------------------------------------|'); + console.log("| tablePropX ...................................... = ".concat(tablePropX / EMU)); + console.log("| tablePropY ...................................... = ".concat(tablePropY / EMU)); + console.log("| tablePropW ...................................... = ".concat(tablePropW / EMU)); + console.log("| tablePropH ...................................... = ".concat(tablePropH / EMU)); + console.log("| tableCalcW ...................................... = ".concat(tableCalcW / EMU)); + } + // STEP 1: Calculate margins + { + // Important: Use default size as zero cell margin is causing our tables to be too large and touch bottom of slide! + if (!tableProps.slideMargin && tableProps.slideMargin !== 0) + tableProps.slideMargin = DEF_SLIDE_MARGIN_IN[0]; + if (masterSlide && typeof masterSlide._margin !== 'undefined') { + if (Array.isArray(masterSlide._margin)) + arrInchMargins = masterSlide._margin; + else if (!isNaN(Number(masterSlide._margin))) { + arrInchMargins = [Number(masterSlide._margin), Number(masterSlide._margin), Number(masterSlide._margin), Number(masterSlide._margin)]; + } + } + else if (tableProps.slideMargin || tableProps.slideMargin === 0) { + if (Array.isArray(tableProps.slideMargin)) + arrInchMargins = tableProps.slideMargin; + else if (!isNaN(tableProps.slideMargin)) + arrInchMargins = [tableProps.slideMargin, tableProps.slideMargin, tableProps.slideMargin, tableProps.slideMargin]; + } + if (tableProps.verbose) + console.log("| arrInchMargins .................................. = [".concat(arrInchMargins.join(', '), "]")); + } + // STEP 2: Calculate number of columns + { + // NOTE: Cells may have a colspan, so merely taking the length of the [0] (or any other) row is not + // ....: sufficient to determine column count. Therefore, check each cell for a colspan and total cols as reqd + var firstRow = tableRows[0] || []; + firstRow.forEach(function (cell) { + if (!cell) + cell = { _type: SLIDE_OBJECT_TYPES.tablecell }; + var cellOpts = cell.options || null; + numCols += Number((cellOpts === null || cellOpts === void 0 ? void 0 : cellOpts.colspan) ? cellOpts.colspan : 1); + }); + if (tableProps.verbose) + console.log("| numCols ......................................... = ".concat(numCols)); + } + // STEP 3: Calculate width using tableProps.colW if possible + if (!tablePropW && tableProps.colW) { + tableCalcW = Array.isArray(tableProps.colW) ? tableProps.colW.reduce(function (p, n) { return p + n; }) * EMU : tableProps.colW * numCols || 0; + if (tableProps.verbose) + console.log("| tableCalcW ...................................... = ".concat(tableCalcW / EMU)); + } + // STEP 4: Calculate usable width now that total usable space is known (`emuSlideTabW`) + { + emuSlideTabW = tableCalcW || inch2Emu((tablePropX ? tablePropX / EMU : arrInchMargins[1]) + arrInchMargins[3]); + if (tableProps.verbose) + console.log("| emuSlideTabW .................................... = ".concat((emuSlideTabW / EMU).toFixed(1))); + } + // STEP 5: Calculate column widths if not provided (emuSlideTabW will be used below to determine lines-per-col) + if (!tableProps.colW || !Array.isArray(tableProps.colW)) { + if (tableProps.colW && !isNaN(Number(tableProps.colW))) { + var arrColW_1 = []; + var firstRow = tableRows[0] || []; + firstRow.forEach(function () { return arrColW_1.push(tableProps.colW); }); + tableProps.colW = []; + arrColW_1.forEach(function (val) { + if (Array.isArray(tableProps.colW)) + tableProps.colW.push(val); + }); + } + else { + // No column widths provided? Then distribute cols. + tableProps.colW = []; + for (var iCol = 0; iCol < numCols; iCol++) { + tableProps.colW.push(emuSlideTabW / EMU / numCols); + } + } + } + // STEP 6: **MAIN** Iterate over rows, add table content, create new slides as rows overflow + var newTableRowSlide = { rows: [] }; + tableRows.forEach(function (row, iRow) { + // A: Row variables + var rowCellLines = []; + var maxCellMarTopEmu = 0; + var maxCellMarBtmEmu = 0; + // B: Create new row in data model, calc `maxCellMar*` + var currTableRow = []; + row.forEach(function (cell) { + var _a, _b, _c, _d; + currTableRow.push({ + _type: SLIDE_OBJECT_TYPES.tablecell, + text: [], + options: cell.options, + }); + /** FUTURE: DEPRECATED: + * - Backwards-Compat: Oops! Discovered we were still using points for cell margin before v3.8.0 (UGH!) + * - We cant introduce a breaking change before v4.0, so... + */ + if (cell.options.margin && cell.options.margin[0] >= 1) { + if (((_a = cell.options) === null || _a === void 0 ? void 0 : _a.margin) && cell.options.margin[0] && valToPts(cell.options.margin[0]) > maxCellMarTopEmu) + maxCellMarTopEmu = valToPts(cell.options.margin[0]); + else if ((tableProps === null || tableProps === void 0 ? void 0 : tableProps.margin) && tableProps.margin[0] && valToPts(tableProps.margin[0]) > maxCellMarTopEmu) + maxCellMarTopEmu = valToPts(tableProps.margin[0]); + if (((_b = cell.options) === null || _b === void 0 ? void 0 : _b.margin) && cell.options.margin[2] && valToPts(cell.options.margin[2]) > maxCellMarBtmEmu) + maxCellMarBtmEmu = valToPts(cell.options.margin[2]); + else if ((tableProps === null || tableProps === void 0 ? void 0 : tableProps.margin) && tableProps.margin[2] && valToPts(tableProps.margin[2]) > maxCellMarBtmEmu) + maxCellMarBtmEmu = valToPts(tableProps.margin[2]); + } + else { + if (((_c = cell.options) === null || _c === void 0 ? void 0 : _c.margin) && cell.options.margin[0] && inch2Emu(cell.options.margin[0]) > maxCellMarTopEmu) + maxCellMarTopEmu = inch2Emu(cell.options.margin[0]); + else if ((tableProps === null || tableProps === void 0 ? void 0 : tableProps.margin) && tableProps.margin[0] && inch2Emu(tableProps.margin[0]) > maxCellMarTopEmu) + maxCellMarTopEmu = inch2Emu(tableProps.margin[0]); + if (((_d = cell.options) === null || _d === void 0 ? void 0 : _d.margin) && cell.options.margin[2] && inch2Emu(cell.options.margin[2]) > maxCellMarBtmEmu) + maxCellMarBtmEmu = inch2Emu(cell.options.margin[2]); + else if ((tableProps === null || tableProps === void 0 ? void 0 : tableProps.margin) && tableProps.margin[2] && inch2Emu(tableProps.margin[2]) > maxCellMarBtmEmu) + maxCellMarBtmEmu = inch2Emu(tableProps.margin[2]); + } + }); + // C: Calc usable vertical space/table height. Set default value first, adjust below when necessary. + calcSlideTabH(); + emuTabCurrH += maxCellMarTopEmu + maxCellMarBtmEmu; // Start row height with margins + if (tableProps.verbose && iRow === 0) + console.log("| SLIDE [".concat(tableRowSlides.length, "]: emuSlideTabH ...... = ").concat((emuSlideTabH / EMU).toFixed(1), " ")); + // D: --==[[ BUILD DATA SET ]]==-- (iterate over cells: split text into lines[], set `lineHeight`) + row.forEach(function (cell, iCell) { + var _a; + var newCell = { + _type: SLIDE_OBJECT_TYPES.tablecell, + _lines: null, + _lineHeight: inch2Emu(((((_a = cell.options) === null || _a === void 0 ? void 0 : _a.fontSize) ? cell.options.fontSize : tableProps.fontSize ? tableProps.fontSize : DEF_FONT_SIZE) * + (LINEH_MODIFIER + (tableProps.autoPageLineWeight ? tableProps.autoPageLineWeight : 0))) / + 100), + text: [], + options: cell.options, + }; + // E-1: Exempt cells with `rowspan` from increasing lineHeight (or we could create a new slide when unecessary!) + if (newCell.options.rowspan) + newCell._lineHeight = 0; + // E-2: The parseTextToLines method uses `autoPageCharWeight`, so inherit from table options + newCell.options.autoPageCharWeight = tableProps.autoPageCharWeight ? tableProps.autoPageCharWeight : null; + // E-3: **MAIN** Parse cell contents into lines based upon col width, font, etc + var totalColW = tableProps.colW[iCell]; + if (cell.options.colspan && Array.isArray(tableProps.colW)) { + totalColW = tableProps.colW.filter(function (_cell, idx) { return idx >= iCell && idx < idx + cell.options.colspan; }).reduce(function (prev, curr) { return prev + curr; }); + } + // E-4: Create lines based upon available column width + newCell._lines = parseTextToLines(cell, totalColW, false); + // E-5: Add cell to array + rowCellLines.push(newCell); + }); + /** E: --==[[ PAGE DATA SET ]]==-- + * Add text one-line-a-time to this row's cells until: lines are exhausted OR table height limit is hit + * + * Design: + * - Building cells L-to-R/loop style wont work as one could be 100 lines and another 1 line + * - Therefore, build the whole row, one-line-at-a-time, across each table columns + * - Then, when the vertical size limit is hit is by any of the cells, make a new slide and continue adding any remaining lines + * + * Implementation: + * - `rowCellLines` is an array of cells, one for each column in the table, with each cell containing an array of lines + * + * Sample Data: + * - `rowCellLines` ..: [ TableCell, TableCell, TableCell ] + * - `TableCell` .....: { _type: 'tablecell', _lines: TableCell[], _lineHeight: 10 } + * - `_lines` ........: [ {_type: 'tablecell', text: 'cell-1,line-1', options: {…}}, {_type: 'tablecell', text: 'cell-1,line-2', options: {…}} } + * - `_lines` is TableCell[] (the 1-N words in the line) + * { + * _lines: [{ text:'cell-1,line-1' }, { text:'cell-1,line-2' }], // TOTAL-CELL-HEIGHT = 2 + * _lines: [{ text:'cell-2,line-1' }, { text:'cell-2,line-2' }], // TOTAL-CELL-HEIGHT = 2 + * _lines: [{ text:'cell-3,line-1' }, { text:'cell-3,line-2' }, { text:'cell-3,line-3' }, { text:'cell-3,line-4' }], // TOTAL-CELL-HEIGHT = 4 + * } + * + * Example: 2 rows, with the firstrow overflowing onto a new slide + * SLIDE 1: + * |--------|--------|--------|--------| + * | line-1 | line-1 | line-1 | line-1 | + * | | | line-2 | | + * | | | line-3 | | + * |--------|--------|--------|--------| + * + * SLIDE 2: + * |--------|--------|--------|--------| + * | | | line-4 | | + * |--------|--------|--------|--------| + * | line-1 | line-1 | line-1 | line-1 | + * |--------|--------|--------|--------| + */ + if (tableProps.verbose) + console.log("\n| SLIDE [".concat(tableRowSlides.length, "]: ROW [").concat(iRow, "]: START...")); + var currCellIdx = 0; + var emuLineMaxH = 0; + var isDone = false; + while (!isDone) { + var srcCell = rowCellLines[currCellIdx]; + var tgtCell = currTableRow[currCellIdx]; // NOTE: may be redefined below (a new row may be created, thus changing this value) + // 1: calc emuLineMaxH + rowCellLines.forEach(function (cell) { + if (cell._lineHeight >= emuLineMaxH) + emuLineMaxH = cell._lineHeight; + }); + // 2: create a new slide if there is insufficient room for the current row + if (emuTabCurrH + emuLineMaxH > emuSlideTabH) { + if (tableProps.verbose) { + console.log('\n|-----------------------------------------------------------------------|'); + // prettier-ignore + console.log("|-- NEW SLIDE CREATED (currTabH+currLineH > maxH) => ".concat((emuTabCurrH / EMU).toFixed(2), " + ").concat((srcCell._lineHeight / EMU).toFixed(2), " > ").concat(emuSlideTabH / EMU)); + console.log('|-----------------------------------------------------------------------|\n\n'); + } + // A: add current row slide or it will be lost (only if it has rows and text) + if (currTableRow.length > 0 && currTableRow.map(function (cell) { return cell.text.length; }).reduce(function (p, n) { return p + n; }) > 0) + newTableRowSlide.rows.push(currTableRow); + // B: add current slide to Slides array + tableRowSlides.push(newTableRowSlide); + // C: reset working/curr slide to hold rows as they're created + var newRows = []; + newTableRowSlide = { rows: newRows }; + // D: reset working/curr row + currTableRow = []; + row.forEach(function (cell) { return currTableRow.push({ _type: SLIDE_OBJECT_TYPES.tablecell, text: [], options: cell.options }); }); + // E: Calc usable vertical space/table height now as we may still be in the same row and code above ("C: Calc usable vertical space/table height.") calc may now be invalid + calcSlideTabH(); + emuTabCurrH += maxCellMarTopEmu + maxCellMarBtmEmu; // Start row height with margins + if (tableProps.verbose) + console.log("| SLIDE [".concat(tableRowSlides.length, "]: emuSlideTabH ...... = ").concat((emuSlideTabH / EMU).toFixed(1), " ")); + // F: reset current table height for this new Slide + emuTabCurrH = 0; + // G: handle repeat headers option /or/ Add new empty row to continue current lines into + if ((tableProps.addHeaderToEach || tableProps.autoPageRepeatHeader) && tableProps._arrObjTabHeadRows) { + tableProps._arrObjTabHeadRows.forEach(function (row) { + var newHeadRow = []; + var maxLineHeight = 0; + row.forEach(function (cell) { + newHeadRow.push(cell); + if (cell._lineHeight > maxLineHeight) + maxLineHeight = cell._lineHeight; + }); + newTableRowSlide.rows.push(newHeadRow); + emuTabCurrH += maxLineHeight; // TODO: what about margins? dont we need to include cell margin in line height? + }); + } + // WIP: NEW: TEST THIS!! + tgtCell = currTableRow[currCellIdx]; + } + // 3: set array of words that comprise this line + var currLine = srcCell._lines.shift(); + // 4: create new line by adding all words from curr line (or add empty if there are no words to avoid "needs repair" issue triggered when cells have null content) + if (Array.isArray(tgtCell.text)) { + if (currLine) + tgtCell.text = tgtCell.text.concat(currLine); + else if (tgtCell.text.length === 0) + tgtCell.text = tgtCell.text.concat({ _type: SLIDE_OBJECT_TYPES.tablecell, text: '' }); + // IMPORTANT: ^^^ add empty if there are no words to avoid "needs repair" issue triggered when cells have null content + } + // 5: increase table height by the curr line height (if we're on the last column) + if (currCellIdx === rowCellLines.length - 1) + emuTabCurrH += emuLineMaxH; + // 6: advance column/cell index (or circle back to first one to continue adding lines) + currCellIdx = currCellIdx < rowCellLines.length - 1 ? currCellIdx + 1 : 0; + // 7: done? + var brent = rowCellLines.map(function (cell) { return cell._lines.length; }).reduce(function (prev, next) { return prev + next; }); + if (brent === 0) + isDone = true; + } + // F: Flush/capture row buffer before it resets at the top of this loop + if (currTableRow.length > 0) + newTableRowSlide.rows.push(currTableRow); + if (tableProps.verbose) { + console.log("- SLIDE [".concat(tableRowSlides.length, "]: ROW [").concat(iRow, "]: ...COMPLETE ...... emuTabCurrH = ").concat((emuTabCurrH / EMU).toFixed(2), " ( emuSlideTabH = ").concat((emuSlideTabH / EMU).toFixed(2), " )")); + } + }); + // STEP 7: Flush buffer / add final slide + tableRowSlides.push(newTableRowSlide); + if (tableProps.verbose) { + console.log('\n|================================================|'); + console.log("| FINAL: tableRowSlides.length = ".concat(tableRowSlides.length)); + tableRowSlides.forEach(function (slide) { return console.log(slide); }); + console.log('|================================================|\n\n'); + } + // LAST: + return tableRowSlides; +} +/** + * Reproduces an HTML table as a PowerPoint table - including column widths, style, etc. - creates 1 or more slides as needed + * @param {PptxGenJS} pptx - pptxgenjs instance + * @param {string} tabEleId - HTMLElementID of the table + * @param {ITableToSlidesOpts} options - array of options (e.g.: tabsize) + * @param {SlideLayout} masterSlide - masterSlide + */ +function genTableToSlides(pptx, tabEleId, options, masterSlide) { + if (options === void 0) { options = {}; } + var opts = options || {}; + opts.slideMargin = opts.slideMargin || opts.slideMargin === 0 ? opts.slideMargin : 0.5; + var emuSlideTabW = opts.w || pptx.presLayout.width; + var arrObjTabHeadRows = []; + var arrObjTabBodyRows = []; + var arrObjTabFootRows = []; + var arrColW = []; + var arrTabColW = []; + var arrInchMargins = [0.5, 0.5, 0.5, 0.5]; // TRBL-style + var intTabW = 0; + // REALITY-CHECK: + if (!document.getElementById(tabEleId)) + throw new Error('tableToSlides: Table ID "' + tabEleId + '" does not exist!'); + // STEP 1: Set margins + if (masterSlide === null || masterSlide === void 0 ? void 0 : masterSlide._margin) { + if (Array.isArray(masterSlide._margin)) + arrInchMargins = masterSlide._margin; + else if (!isNaN(masterSlide._margin)) + arrInchMargins = [masterSlide._margin, masterSlide._margin, masterSlide._margin, masterSlide._margin]; + opts.slideMargin = arrInchMargins; + } + else if (opts === null || opts === void 0 ? void 0 : opts.slideMargin) { + if (Array.isArray(opts.slideMargin)) + arrInchMargins = opts.slideMargin; + else if (!isNaN(opts.slideMargin)) + arrInchMargins = [opts.slideMargin, opts.slideMargin, opts.slideMargin, opts.slideMargin]; + } + emuSlideTabW = (opts.w ? inch2Emu(opts.w) : pptx.presLayout.width) - inch2Emu(arrInchMargins[1] + arrInchMargins[3]); + if (opts.verbose) { + console.log('[[VERBOSE MODE]]'); + console.log('|-- `tableToSlides` ----------------------------------------------------|'); + console.log("| tableProps.h .................................... = ".concat(opts.h)); + console.log("| tableProps.w .................................... = ".concat(opts.w)); + console.log("| pptx.presLayout.width ........................... = ".concat((pptx.presLayout.width / EMU).toFixed(1))); + console.log("| pptx.presLayout.height .......................... = ".concat((pptx.presLayout.height / EMU).toFixed(1))); + console.log("| emuSlideTabW .................................... = ".concat((emuSlideTabW / EMU).toFixed(1))); + } + // STEP 2: Grab table col widths - just find the first availble row, either thead/tbody/tfoot, others may have colspans, who cares, we only need col widths from 1 + var firstRowCells = document.querySelectorAll("#".concat(tabEleId, " tr:first-child th")); + if (firstRowCells.length === 0) + firstRowCells = document.querySelectorAll("#".concat(tabEleId, " tr:first-child td")); + firstRowCells.forEach(function (cell) { + if (cell.getAttribute('colspan')) { + // Guesstimate (divide evenly) col widths + // NOTE: both j$query and vanilla selectors return {0} when table is not visible) + for (var idxc = 0; idxc < Number(cell.getAttribute('colspan')); idxc++) { + arrTabColW.push(Math.round(cell.offsetWidth / Number(cell.getAttribute('colspan')))); + } + } + else { + arrTabColW.push(cell.offsetWidth); + } + }); + arrTabColW.forEach(function (colW) { + intTabW += colW; + }); + // STEP 3: Calc/Set column widths by using same column width percent from HTML table + arrTabColW.forEach(function (colW, idxW) { + var intCalcWidth = Number(((Number(emuSlideTabW) * ((colW / intTabW) * 100)) / 100 / EMU).toFixed(2)); + var intMinWidth = 0; + var colSelectorMin = document.querySelector("#".concat(tabEleId, " thead tr:first-child th:nth-child(").concat(idxW + 1, ")")); + if (colSelectorMin) + intMinWidth = Number(colSelectorMin.getAttribute('data-pptx-min-width')); + var colSelectorSet = document.querySelector("#".concat(tabEleId, " thead tr:first-child th:nth-child(").concat(idxW + 1, ")")); + if (colSelectorSet) + intMinWidth = Number(colSelectorSet.getAttribute('data-pptx-width')); + arrColW.push((intMinWidth > intCalcWidth ? intMinWidth : intCalcWidth)); + }); + if (opts.verbose) { + console.log("| arrColW ......................................... = [".concat(arrColW.join(', '), "]")); + } + // STEP 4: Iterate over each table element and create data arrays (text and opts) + // NOTE: We create 3 arrays instead of one so we can loop over body then show header/footer rows on first and last page + var tableParts = ['thead', 'tbody', 'tfoot']; + tableParts.forEach(function (part) { + document.querySelectorAll("#".concat(tabEleId, " ").concat(part, " tr")).forEach(function (row) { + var arrObjTabCells = []; + Array.from(row.cells).forEach(function (cell) { + // A: Get RGB text/bkgd colors + var arrRGB1 = window.getComputedStyle(cell).getPropertyValue('color').replace(/\s+/gi, '').replace('rgba(', '').replace('rgb(', '').replace(')', '').split(','); + var arrRGB2 = window + .getComputedStyle(cell) + .getPropertyValue('background-color') + .replace(/\s+/gi, '') + .replace('rgba(', '') + .replace('rgb(', '') + .replace(')', '') + .split(','); + if ( + // NOTE: (ISSUE#57): Default for unstyled tables is black bkgd, so use white instead + window.getComputedStyle(cell).getPropertyValue('background-color') === 'rgba(0, 0, 0, 0)' || + window.getComputedStyle(cell).getPropertyValue('transparent')) { + arrRGB2 = ['255', '255', '255']; + } + // B: Create option object + var cellOpts = { + align: null, + bold: !!(window.getComputedStyle(cell).getPropertyValue('font-weight') === 'bold' || + Number(window.getComputedStyle(cell).getPropertyValue('font-weight')) >= 500), + border: null, + color: rgbToHex(Number(arrRGB1[0]), Number(arrRGB1[1]), Number(arrRGB1[2])), + fill: { color: rgbToHex(Number(arrRGB2[0]), Number(arrRGB2[1]), Number(arrRGB2[2])) }, + fontFace: (window.getComputedStyle(cell).getPropertyValue('font-family') || '').split(',')[0].replace(/"/g, '').replace('inherit', '').replace('initial', '') || + null, + fontSize: Number(window.getComputedStyle(cell).getPropertyValue('font-size').replace(/[a-z]/gi, '')), + margin: null, + colspan: Number(cell.getAttribute('colspan')) || null, + rowspan: Number(cell.getAttribute('rowspan')) || null, + valign: null, + }; + if (['left', 'center', 'right', 'start', 'end'].includes(window.getComputedStyle(cell).getPropertyValue('text-align'))) { + var align = window.getComputedStyle(cell).getPropertyValue('text-align').replace('start', 'left').replace('end', 'right'); + cellOpts.align = align === 'center' ? 'center' : align === 'left' ? 'left' : align === 'right' ? 'right' : null; + } + if (['top', 'middle', 'bottom'].includes(window.getComputedStyle(cell).getPropertyValue('vertical-align'))) { + var valign = window.getComputedStyle(cell).getPropertyValue('vertical-align'); + cellOpts.valign = valign === 'top' ? 'top' : valign === 'middle' ? 'middle' : valign === 'bottom' ? 'bottom' : null; + } + // C: Add padding [margin] (if any) + // NOTE: Margins translate: px->pt 1:1 (e.g.: a 20px padded cell looks the same in PPTX as 20pt Text Inset/Padding) + if (window.getComputedStyle(cell).getPropertyValue('padding-left')) { + cellOpts.margin = [0, 0, 0, 0]; + var sidesPad = ['padding-top', 'padding-right', 'padding-bottom', 'padding-left']; + sidesPad.forEach(function (val, idxs) { + cellOpts.margin[idxs] = Math.round(Number(window.getComputedStyle(cell).getPropertyValue(val).replace(/\D/gi, ''))); + }); + } + // D: Add border (if any) + if (window.getComputedStyle(cell).getPropertyValue('border-top-width') || + window.getComputedStyle(cell).getPropertyValue('border-right-width') || + window.getComputedStyle(cell).getPropertyValue('border-bottom-width') || + window.getComputedStyle(cell).getPropertyValue('border-left-width')) { + cellOpts.border = [null, null, null, null]; + var sidesBor = ['top', 'right', 'bottom', 'left']; + sidesBor.forEach(function (val, idxb) { + var intBorderW = Math.round(Number(window + .getComputedStyle(cell) + .getPropertyValue('border-' + val + '-width') + .replace('px', ''))); + var arrRGB = []; + arrRGB = window + .getComputedStyle(cell) + .getPropertyValue('border-' + val + '-color') + .replace(/\s+/gi, '') + .replace('rgba(', '') + .replace('rgb(', '') + .replace(')', '') + .split(','); + var strBorderC = rgbToHex(Number(arrRGB[0]), Number(arrRGB[1]), Number(arrRGB[2])); + cellOpts.border[idxb] = { pt: intBorderW, color: strBorderC }; + }); + } + // LAST: Add cell + arrObjTabCells.push({ + _type: SLIDE_OBJECT_TYPES.tablecell, + text: cell.innerText, + options: cellOpts, + }); + }); + switch (part) { + case 'thead': + arrObjTabHeadRows.push(arrObjTabCells); + break; + case 'tbody': + arrObjTabBodyRows.push(arrObjTabCells); + break; + case 'tfoot': + arrObjTabFootRows.push(arrObjTabCells); + break; + default: + console.log("table parsing: unexpected table part: ".concat(part)); + break; + } + }); + }); + // STEP 5: Break table into Slides as needed + // Pass head-rows as there is an option to add to each table and the parse func needs this data to fulfill that option + opts._arrObjTabHeadRows = arrObjTabHeadRows || null; + opts.colW = arrColW; + getSlidesForTableRows(__spreadArray(__spreadArray(__spreadArray([], arrObjTabHeadRows, true), arrObjTabBodyRows, true), arrObjTabFootRows, true), opts, pptx.presLayout, masterSlide).forEach(function (slide, idxTr) { + // A: Create new Slide + var newSlide = pptx.addSlide({ masterName: opts.masterSlideName || null }); + // B: DESIGN: Reset `y` to startY or margin after first Slide (ISSUE#43, ISSUE#47, ISSUE#48) + if (idxTr === 0) + opts.y = opts.y || arrInchMargins[0]; + if (idxTr > 0) + opts.y = opts.autoPageSlideStartY || opts.newSlideStartY || arrInchMargins[0]; + if (opts.verbose) + console.log("| opts.autoPageSlideStartY: ".concat(opts.autoPageSlideStartY, " / arrInchMargins[0]: ").concat(arrInchMargins[0], " => opts.y = ").concat(opts.y)); + // C: Add table to Slide + newSlide.addTable(slide.rows, { x: opts.x || arrInchMargins[3], y: opts.y, w: Number(emuSlideTabW) / EMU, colW: arrColW, autoPage: false }); + // D: Add any additional objects + if (opts.addImage) { + opts.addImage.options = opts.addImage.options || {}; + if (!opts.addImage.image || (!opts.addImage.image.path && !opts.addImage.image.data)) { + console.warn('Warning: tableToSlides.addImage requires either `path` or `data`'); + } + else { + newSlide.addImage({ + path: opts.addImage.image.path, + data: opts.addImage.image.data, + x: opts.addImage.options.x, + y: opts.addImage.options.y, + w: opts.addImage.options.w, + h: opts.addImage.options.h, + }); + } + } + if (opts.addShape) + newSlide.addShape(opts.addShape.shapeName, opts.addShape.options || {}); + if (opts.addTable) + newSlide.addTable(opts.addTable.rows, opts.addTable.options || {}); + if (opts.addText) + newSlide.addText(opts.addText.text, opts.addText.options || {}); + }); +} + +/** + * PptxGenJS: Slide Object Generators + */ +/** counter for included charts (used for index in their filenames) */ +var _chartCounter = 0; +/** + * Transforms a slide definition to a slide object that is then passed to the XML transformation process. + * @param {SlideMasterProps} props - slide definition + * @param {PresSlide|SlideLayout} target - empty slide object that should be updated by the passed definition + */ +function createSlideMaster(props, target) { + // STEP 1: Add background if either the slide or layout has background props + // if (props.background || target.background) addBackgroundDefinition(props.background, target) + if (props.bkgd) + target.bkgd = props.bkgd; // DEPRECATED: (remove in v4.0.0) + // STEP 2: Add all Slide Master objects in the order they were given + if (props.objects && Array.isArray(props.objects) && props.objects.length > 0) { + props.objects.forEach(function (object, idx) { + var key = Object.keys(object)[0]; + var tgt = target; + if (MASTER_OBJECTS[key] && key === 'chart') + addChartDefinition(tgt, object[key].type, object[key].data, object[key].opts); + else if (MASTER_OBJECTS[key] && key === 'image') + addImageDefinition(tgt, object[key]); + else if (MASTER_OBJECTS[key] && key === 'line') + addShapeDefinition(tgt, SHAPE_TYPE.LINE, object[key]); + else if (MASTER_OBJECTS[key] && key === 'rect') + addShapeDefinition(tgt, SHAPE_TYPE.RECTANGLE, object[key]); + else if (MASTER_OBJECTS[key] && key === 'text') + addTextDefinition(tgt, [{ text: object[key].text }], object[key].options, false); + else if (MASTER_OBJECTS[key] && key === 'placeholder') { + // TODO: 20180820: Check for existing `name`? + object[key].options.placeholder = object[key].options.name; + delete object[key].options.name; // remap name for earier handling internally + object[key].options._placeholderType = object[key].options.type; + delete object[key].options.type; // remap name for earier handling internally + object[key].options._placeholderIdx = 100 + idx; + addTextDefinition(tgt, [{ text: object[key].text }], object[key].options, true); + // TODO: ISSUE#599 - only text is suported now (add more below) + // else if (object[key].image) addImageDefinition(tgt, object[key].image) + /* 20200120: So... image placeholders go into the "slideLayoutN.xml" file and addImage doesnt do this yet... + + + + + + + + + + + */ + } + }); + } + // STEP 3: Add Slide Numbers (NOTE: Do this last so numbers are not covered by objects!) + if (props.slideNumber && typeof props.slideNumber === 'object') + target._slideNumberProps = props.slideNumber; +} +/** + * Generate the chart based on input data. + * OOXML Chart Spec: ISO/IEC 29500-1:2016(E) + * + * @param {CHART_NAME | IChartMulti[]} `type` should belong to: 'column', 'pie' + * @param {[]} `data` a JSON object with follow the following format + * @param {IChartOptsLib} `opt` chart options + * @param {PresSlide} `target` slide object that the chart will be added to + * @return {object} chart object + * { + * title: 'eSurvey chart', + * data: [ + * { + * name: 'Income', + * labels: ['2005', '2006', '2007', '2008', '2009'], + * values: [23.5, 26.2, 30.1, 29.5, 24.6] + * }, + * { + * name: 'Expense', + * labels: ['2005', '2006', '2007', '2008', '2009'], + * values: [18.1, 22.8, 23.9, 25.1, 25] + * } + * ] + * } + */ +function addChartDefinition(target, type, data, opt) { + var _a; + function correctGridLineOptions(glOpts) { + if (!glOpts || glOpts.style === 'none') + return; + if (glOpts.size !== undefined && (isNaN(Number(glOpts.size)) || glOpts.size <= 0)) { + console.warn('Warning: chart.gridLine.size must be greater than 0.'); + delete glOpts.size; // delete prop to used defaults + } + if (glOpts.style && !['solid', 'dash', 'dot'].includes(glOpts.style)) { + console.warn('Warning: chart.gridLine.style options: `solid`, `dash`, `dot`.'); + delete glOpts.style; + } + if (glOpts.cap && !['flat', 'square', 'round'].includes(glOpts.cap)) { + console.warn('Warning: chart.gridLine.cap options: `flat`, `square`, `round`.'); + delete glOpts.cap; + } + } + var chartId = ++_chartCounter; + var resultObject = { + _type: null, + text: null, + options: null, + chartRid: null, + }; + // DESIGN: `type` can an object (ex: `pptx.charts.DOUGHNUT`) or an array of chart objects + // EX: addChartDefinition([ { type:pptx.charts.BAR, data:{name:'', labels:[], values[]} }, {} ]) + // Multi-Type Charts + var tmpOpt = null; + var tmpData = []; + if (Array.isArray(type)) { + // For multi-type charts there needs to be data for each type, + // as well as a single data source for non-series operations. + // The data is indexed below to keep the data in order when segmented + // into types. + type.forEach(function (obj) { + tmpData = tmpData.concat(obj.data); + }); + tmpOpt = data || opt; + } + else { + tmpData = data; + tmpOpt = opt; + } + tmpData.forEach(function (item, i) { + item._dataIndex = i; + // Converts the 'labels' array from string[] to string[][] (or the respective primitive type), if needed + if (item.labels !== undefined && !Array.isArray(item.labels[0])) { + item.labels = [item.labels]; + } + }); + var options = tmpOpt && typeof tmpOpt === 'object' ? tmpOpt : {}; + // STEP 1: TODO: check for reqd fields, correct type, etc + // `type` exists in CHART_TYPE + // Array.isArray(data) + /* + if ( Array.isArray(rel.data) && rel.data.length > 0 && typeof rel.data[0] === 'object' + && rel.data[0].labels && Array.isArray(rel.data[0].labels) + && rel.data[0].values && Array.isArray(rel.data[0].values) ) { + obj = rel.data[0]; + } + else { + console.warn("USAGE: addChart( 'pie', [ {name:'Sales', labels:['Jan','Feb'], values:[10,20]} ], {x:1, y:1} )"); + return; + } + */ + // STEP 2: Set default options/decode user options + // A: Core + options._type = type; + options.x = typeof options.x !== 'undefined' && options.x != null && !isNaN(Number(options.x)) ? options.x : 1; + options.y = typeof options.y !== 'undefined' && options.y != null && !isNaN(Number(options.y)) ? options.y : 1; + options.w = options.w || '50%'; + options.h = options.h || '50%'; + options.objectName = options.objectName + ? encodeXmlEntities(options.objectName) + : "Chart ".concat(target._slideObjects.filter(function (obj) { return obj._type === SLIDE_OBJECT_TYPES.chart; }).length); + // B: Options: misc + if (!['bar', 'col'].includes(options.barDir || '')) + options.barDir = 'col'; + // barGrouping: "21.2.3.17 ST_Grouping (Grouping)" + // barGrouping must be handled before data label validation as it can affect valid label positioning + if (options._type === CHART_TYPE.AREA) { + if (!['stacked', 'standard', 'percentStacked'].includes(options.barGrouping || '')) + options.barGrouping = 'standard'; + } + if (options._type === CHART_TYPE.BAR) { + if (!['clustered', 'stacked', 'percentStacked'].includes(options.barGrouping || '')) + options.barGrouping = 'clustered'; + } + if (options._type === CHART_TYPE.BAR3D) { + if (!['clustered', 'stacked', 'standard', 'percentStacked'].includes(options.barGrouping || '')) + options.barGrouping = 'standard'; + } + if ((_a = options.barGrouping) === null || _a === void 0 ? void 0 : _a.includes('tacked')) { + if (!options.barGapWidthPct) + options.barGapWidthPct = 50; + } + // Clean up and validate data label positions + // REFERENCE: https://docs.microsoft.com/en-us/openspecs/office_standards/ms-oi29500/e2b1697c-7adc-463d-9081-3daef72f656f?redirectedfrom=MSDN + if (options.dataLabelPosition) { + if (options._type === CHART_TYPE.AREA || options._type === CHART_TYPE.BAR3D || options._type === CHART_TYPE.DOUGHNUT || options._type === CHART_TYPE.RADAR) { + delete options.dataLabelPosition; + } + if (options._type === CHART_TYPE.PIE) { + if (!['bestFit', 'ctr', 'inEnd', 'outEnd'].includes(options.dataLabelPosition)) + delete options.dataLabelPosition; + } + if (options._type === CHART_TYPE.BUBBLE || options._type === CHART_TYPE.BUBBLE3D || options._type === CHART_TYPE.LINE || options._type === CHART_TYPE.SCATTER) { + if (!['b', 'ctr', 'l', 'r', 't'].includes(options.dataLabelPosition)) + delete options.dataLabelPosition; + } + if (options._type === CHART_TYPE.BAR) { + if (!['stacked', 'percentStacked'].includes(options.barGrouping || '')) { + if (!['ctr', 'inBase', 'inEnd'].includes(options.dataLabelPosition)) + delete options.dataLabelPosition; + } + if (!['clustered'].includes(options.barGrouping || '')) { + if (!['ctr', 'inBase', 'inEnd', 'outEnd'].includes(options.dataLabelPosition)) + delete options.dataLabelPosition; + } + } + } + options.dataLabelBkgrdColors = options.dataLabelBkgrdColors || !options.dataLabelBkgrdColors ? options.dataLabelBkgrdColors : false; + if (!['b', 'l', 'r', 't', 'tr'].includes(options.legendPos || '')) + options.legendPos = 'r'; + // 3D bar: ST_Shape + if (!['cone', 'coneToMax', 'box', 'cylinder', 'pyramid', 'pyramidToMax'].includes(options.bar3DShape || '')) + options.bar3DShape = 'box'; + // lineDataSymbol: http://www.datypic.com/sc/ooxml/a-val-32.html + // Spec has [plus,star,x] however neither PPT2013 nor PPT-Online support them + if (!['circle', 'dash', 'diamond', 'dot', 'none', 'square', 'triangle'].includes(options.lineDataSymbol || '')) + options.lineDataSymbol = 'circle'; + if (!['gap', 'span'].includes(options.displayBlanksAs || '')) + options.displayBlanksAs = 'span'; + if (!['standard', 'marker', 'filled'].includes(options.radarStyle || '')) + options.radarStyle = 'standard'; + options.lineDataSymbolSize = options.lineDataSymbolSize && !isNaN(options.lineDataSymbolSize) ? options.lineDataSymbolSize : 6; + options.lineDataSymbolLineSize = options.lineDataSymbolLineSize && !isNaN(options.lineDataSymbolLineSize) ? valToPts(options.lineDataSymbolLineSize) : valToPts(0.75); + // `layout` allows the override of PPT defaults to maximize space + if (options.layout) { + ['x', 'y', 'w', 'h'].forEach(function (key) { + var val = options.layout[key]; + if (isNaN(Number(val)) || val < 0 || val > 1) { + console.warn('Warning: chart.layout.' + key + ' can only be 0-1'); + // eslint-disable-next-line @typescript-eslint/no-dynamic-delete + delete options.layout[key]; // remove invalid value so that default will be used + } + }); + } + // Set gridline defaults + options.catGridLine = options.catGridLine || (options._type === CHART_TYPE.SCATTER ? { color: 'D9D9D9', size: 1 } : { style: 'none' }); + options.valGridLine = options.valGridLine || (options._type === CHART_TYPE.SCATTER ? { color: 'D9D9D9', size: 1 } : {}); + options.serGridLine = options.serGridLine || (options._type === CHART_TYPE.SCATTER ? { color: 'D9D9D9', size: 1 } : { style: 'none' }); + correctGridLineOptions(options.catGridLine); + correctGridLineOptions(options.valGridLine); + correctGridLineOptions(options.serGridLine); + correctShadowOptions(options.shadow); + // C: Options: plotArea + options.showDataTable = options.showDataTable || !options.showDataTable ? options.showDataTable : false; + options.showDataTableHorzBorder = options.showDataTableHorzBorder || !options.showDataTableHorzBorder ? options.showDataTableHorzBorder : true; + options.showDataTableVertBorder = options.showDataTableVertBorder || !options.showDataTableVertBorder ? options.showDataTableVertBorder : true; + options.showDataTableOutline = options.showDataTableOutline || !options.showDataTableOutline ? options.showDataTableOutline : true; + options.showDataTableKeys = options.showDataTableKeys || !options.showDataTableKeys ? options.showDataTableKeys : true; + options.showLabel = options.showLabel || !options.showLabel ? options.showLabel : false; + options.showLegend = options.showLegend || !options.showLegend ? options.showLegend : false; + options.showPercent = options.showPercent || !options.showPercent ? options.showPercent : true; + options.showTitle = options.showTitle || !options.showTitle ? options.showTitle : false; + options.showValue = options.showValue || !options.showValue ? options.showValue : false; + options.showLeaderLines = options.showLeaderLines || !options.showLeaderLines ? options.showLeaderLines : false; + options.catAxisLineShow = typeof options.catAxisLineShow !== 'undefined' ? options.catAxisLineShow : true; + options.valAxisLineShow = typeof options.valAxisLineShow !== 'undefined' ? options.valAxisLineShow : true; + options.serAxisLineShow = typeof options.serAxisLineShow !== 'undefined' ? options.serAxisLineShow : true; + options.v3DRotX = !isNaN(options.v3DRotX) && options.v3DRotX >= -90 && options.v3DRotX <= 90 ? options.v3DRotX : 30; + options.v3DRotY = !isNaN(options.v3DRotY) && options.v3DRotY >= 0 && options.v3DRotY <= 360 ? options.v3DRotY : 30; + options.v3DRAngAx = options.v3DRAngAx || !options.v3DRAngAx ? options.v3DRAngAx : true; + options.v3DPerspective = !isNaN(options.v3DPerspective) && options.v3DPerspective >= 0 && options.v3DPerspective <= 240 ? options.v3DPerspective : 30; + // D: Options: chart + options.barGapWidthPct = !isNaN(options.barGapWidthPct) && options.barGapWidthPct >= 0 && options.barGapWidthPct <= 1000 ? options.barGapWidthPct : 150; + options.barGapDepthPct = !isNaN(options.barGapDepthPct) && options.barGapDepthPct >= 0 && options.barGapDepthPct <= 1000 ? options.barGapDepthPct : 150; + options.chartColors = Array.isArray(options.chartColors) + ? options.chartColors + : options._type === CHART_TYPE.PIE || options._type === CHART_TYPE.DOUGHNUT + ? PIECHART_COLORS + : BARCHART_COLORS; + options.chartColorsOpacity = options.chartColorsOpacity && !isNaN(options.chartColorsOpacity) ? options.chartColorsOpacity : null; + // DEPRECATED: v3.11.0 - use `plotArea.border` vvv + options.border = options.border && typeof options.border === 'object' ? options.border : null; + if (options.border && (!options.border.pt || isNaN(options.border.pt))) + options.border.pt = DEF_CHART_BORDER.pt; + if (options.border && (!options.border.color || typeof options.border.color !== 'string')) + options.border.color = DEF_CHART_BORDER.color; + // DEPRECATED: (remove above in v4.0) ^^^ + options.plotArea = options.plotArea || {}; + options.plotArea.border = options.plotArea.border && typeof options.plotArea.border === 'object' ? options.plotArea.border : null; + if (options.plotArea.border && (!options.plotArea.border.pt || isNaN(options.plotArea.border.pt))) + options.plotArea.border.pt = DEF_CHART_BORDER.pt; + if (options.plotArea.border && (!options.plotArea.border.color || typeof options.plotArea.border.color !== 'string')) { + options.plotArea.border.color = DEF_CHART_BORDER.color; + } + if (options.border) + options.plotArea.border = options.border; // @deprecated [[remove in v4.0]] + options.plotArea.fill = options.plotArea.fill || { color: null, transparency: null }; + if (options.fill) + options.plotArea.fill.color = options.fill; // @deprecated [[remove in v4.0]] + // + options.chartArea = options.chartArea || {}; + options.chartArea.border = options.chartArea.border && typeof options.chartArea.border === 'object' ? options.chartArea.border : null; + if (options.chartArea.border) { + options.chartArea.border = { + color: options.chartArea.border.color || DEF_CHART_BORDER.color, + pt: options.chartArea.border.pt || DEF_CHART_BORDER.pt, + }; + } + options.chartArea.roundedCorners = typeof options.chartArea.roundedCorners === 'boolean' ? options.chartArea.roundedCorners : true; + // + options.dataBorder = options.dataBorder && typeof options.dataBorder === 'object' ? options.dataBorder : null; + if (options.dataBorder && (!options.dataBorder.pt || isNaN(options.dataBorder.pt))) + options.dataBorder.pt = 0.75; + if (options.dataBorder && (!options.dataBorder.color || typeof options.dataBorder.color !== 'string' || options.dataBorder.color.length !== 6)) { + options.dataBorder.color = 'F9F9F9'; + } + // + if (!options.dataLabelFormatCode && options._type === CHART_TYPE.SCATTER) + options.dataLabelFormatCode = 'General'; + if (!options.dataLabelFormatCode && (options._type === CHART_TYPE.PIE || options._type === CHART_TYPE.DOUGHNUT)) { + options.dataLabelFormatCode = options.showPercent ? '0%' : 'General'; + } + options.dataLabelFormatCode = options.dataLabelFormatCode && typeof options.dataLabelFormatCode === 'string' ? options.dataLabelFormatCode : '#,##0'; + // + // Set default format for Scatter chart labels to custom string if not defined + if (!options.dataLabelFormatScatter && options._type === CHART_TYPE.SCATTER) + options.dataLabelFormatScatter = 'custom'; + // + options.lineSize = typeof options.lineSize === 'number' ? options.lineSize : 2; + options.valAxisMajorUnit = typeof options.valAxisMajorUnit === 'number' ? options.valAxisMajorUnit : null; + if (options._type === CHART_TYPE.AREA || options._type === CHART_TYPE.BAR || options._type === CHART_TYPE.BAR3D || options._type === CHART_TYPE.LINE) { + options.catAxisMultiLevelLabels = !!options.catAxisMultiLevelLabels; + } + else { + delete options.catAxisMultiLevelLabels; + } + // STEP 4: Set props + resultObject._type = 'chart'; + resultObject.options = options; + resultObject.chartRid = getNewRelId(target); + // STEP 5: Add this chart to this Slide Rels (rId/rels count spans all slides! Count all images to get next rId) + target._relsChart.push({ + rId: getNewRelId(target), + data: tmpData, + opts: options, + type: options._type, + globalId: chartId, + fileName: "chart".concat(chartId, ".xml"), + Target: "/ppt/charts/chart".concat(chartId, ".xml"), + }); + target._slideObjects.push(resultObject); + return resultObject; +} +/** + * Adds an image object to a slide definition. + * This method can be called with only two args (opt, target) - this is supposed to be the only way in future. + * @param {ImageProps} `opt` - object containing `path`/`data`, `x`, `y`, etc. + * @param {PresSlide} `target` - slide that the image should be added to (if not specified as the 2nd arg) + * @note: Remote images (eg: "http://whatev.com/blah"/from web and/or remote server arent supported yet - we'd need to create an , load it, then send to canvas + * @see: https://stackoverflow.com/questions/164181/how-to-fetch-a-remote-image-to-display-in-a-canvas) + */ +function addImageDefinition(target, opt) { + var newObject = { + _type: null, + text: null, + options: null, + image: null, + imageRid: null, + hyperlink: null, + }; + // FIRST: Set vars for this image (object param replaces positional args in 1.1.0) + var intPosX = opt.x || 0; + var intPosY = opt.y || 0; + var intWidth = opt.w || 0; + var intHeight = opt.h || 0; + var sizing = opt.sizing || null; + var objHyperlink = opt.hyperlink || ''; + var strImageData = opt.data || ''; + var strImagePath = opt.path || ''; + var imageRelId = getNewRelId(target); + var objectName = opt.objectName ? encodeXmlEntities(opt.objectName) : "Image ".concat(target._slideObjects.filter(function (obj) { return obj._type === SLIDE_OBJECT_TYPES.image; }).length); + // REALITY-CHECK: + if (!strImagePath && !strImageData) { + console.error('ERROR: addImage() requires either \'data\' or \'path\' parameter!'); + return null; + } + else if (strImagePath && typeof strImagePath !== 'string') { + console.error("ERROR: addImage() 'path' should be a string, ex: {path:'/img/sample.png'} - you sent ".concat(String(strImagePath))); + return null; + } + else if (strImageData && typeof strImageData !== 'string') { + console.error("ERROR: addImage() 'data' should be a string, ex: {data:'image/png;base64,NMP[...]'} - you sent ".concat(String(strImageData))); + return null; + } + else if (strImageData && typeof strImageData === 'string' && !strImageData.toLowerCase().includes('base64,')) { + console.error('ERROR: Image `data` value lacks a base64 header! Ex: \'image/png;base64,NMP[...]\')'); + return null; + } + // STEP 1: Set extension + // NOTE: Split to address URLs with params (eg: `path/brent.jpg?someParam=true`) + var strImgExtn = (strImagePath + .substring(strImagePath.lastIndexOf('/') + 1) + .split('?')[0] + .split('.') + .pop() + .split('#')[0] || 'png').toLowerCase(); + // However, pre-encoded images can be whatever mime-type they want (and good for them!) + if (strImageData && /image\/(\w+);/.exec(strImageData) && /image\/(\w+);/.exec(strImageData).length > 0) { + strImgExtn = /image\/(\w+);/.exec(strImageData)[1]; + } + else if (strImageData === null || strImageData === void 0 ? void 0 : strImageData.toLowerCase().includes('image/svg+xml')) { + strImgExtn = 'svg'; + } + // STEP 2: Set type/path + newObject._type = SLIDE_OBJECT_TYPES.image; + newObject.image = strImagePath || 'preencoded.png'; + // STEP 3: Set image properties & options + // FIXME: Measure actual image when no intWidth/intHeight params passed + // ....: This is an async process: we need to make getSizeFromImage use callback, then set H/W... + // if ( !intWidth || !intHeight ) { var imgObj = getSizeFromImage(strImagePath); + newObject.options = { + x: intPosX || 0, + y: intPosY || 0, + w: intWidth || 1, + h: intHeight || 1, + altText: opt.altText || '', + rounding: typeof opt.rounding === 'boolean' ? opt.rounding : false, + sizing: sizing, + placeholder: opt.placeholder, + rotate: opt.rotate || 0, + flipV: opt.flipV || false, + flipH: opt.flipH || false, + transparency: opt.transparency || 0, + objectName: objectName, + shadow: correctShadowOptions(opt.shadow), + }; + // STEP 4: Add this image to this Slide Rels (rId/rels count spans all slides! Count all images to get next rId) + if (strImgExtn === 'svg') { + // SVG files consume *TWO* rId's: (a png version and the svg image) + // + // + target._relsMedia.push({ + path: strImagePath || strImageData + 'png', + type: 'image/png', + extn: 'png', + data: strImageData || '', + rId: imageRelId, + Target: "../media/image-".concat(target._slideNum, "-").concat(target._relsMedia.length + 1, ".png"), + isSvgPng: true, + svgSize: { w: getSmartParseNumber(newObject.options.w, 'X', target._presLayout), h: getSmartParseNumber(newObject.options.h, 'Y', target._presLayout) }, + }); + newObject.imageRid = imageRelId; + target._relsMedia.push({ + path: strImagePath || strImageData, + type: 'image/svg+xml', + extn: strImgExtn, + data: strImageData || '', + rId: imageRelId + 1, + Target: "../media/image-".concat(target._slideNum, "-").concat(target._relsMedia.length + 1, ".").concat(strImgExtn), + }); + newObject.imageRid = imageRelId + 1; + } + else { + // PERF: Duplicate media should reuse existing `Target` value and not create an additional copy + var dupeItem = target._relsMedia.filter(function (item) { return item.path && item.path === strImagePath && item.type === 'image/' + strImgExtn && !item.isDuplicate; })[0]; + target._relsMedia.push({ + path: strImagePath || 'preencoded.' + strImgExtn, + type: 'image/' + strImgExtn, + extn: strImgExtn, + data: strImageData || '', + rId: imageRelId, + isDuplicate: !!(dupeItem === null || dupeItem === void 0 ? void 0 : dupeItem.Target), + Target: (dupeItem === null || dupeItem === void 0 ? void 0 : dupeItem.Target) ? dupeItem.Target : "../media/image-".concat(target._slideNum, "-").concat(target._relsMedia.length + 1, ".").concat(strImgExtn), + }); + newObject.imageRid = imageRelId; + } + // STEP 5: Hyperlink support + if (typeof objHyperlink === 'object') { + if (!objHyperlink.url && !objHyperlink.slide) + throw new Error('ERROR: `hyperlink` option requires either: `url` or `slide`'); + else { + imageRelId++; + target._rels.push({ + type: SLIDE_OBJECT_TYPES.hyperlink, + data: objHyperlink.slide ? 'slide' : 'dummy', + rId: imageRelId, + Target: objHyperlink.url || objHyperlink.slide.toString(), + }); + objHyperlink._rId = imageRelId; + newObject.hyperlink = objHyperlink; + } + } + // STEP 6: Add object to slide + target._slideObjects.push(newObject); +} +/** + * Adds a media object to a slide definition. + * @param {PresSlide} `target` - slide object that the media will be added to + * @param {MediaProps} `opt` - media options + */ +function addMediaDefinition(target, opt) { + var intPosX = opt.x || 0; + var intPosY = opt.y || 0; + var intSizeX = opt.w || 2; + var intSizeY = opt.h || 2; + var strData = opt.data || ''; + var strLink = opt.link || ''; + var strPath = opt.path || ''; + var strType = opt.type || 'audio'; + var strExtn = ''; + var strCover = opt.cover || IMG_PLAYBTN; + var objectName = opt.objectName ? encodeXmlEntities(opt.objectName) : "Media ".concat(target._slideObjects.filter(function (obj) { return obj._type === SLIDE_OBJECT_TYPES.media; }).length); + var slideData = { _type: SLIDE_OBJECT_TYPES.media }; + // STEP 1: REALITY-CHECK + if (!strPath && !strData && strType !== 'online') { + throw new Error('addMedia() error: either `data` or `path` are required!'); + } + else if (strData && !strData.toLowerCase().includes('base64,')) { + throw new Error('addMedia() error: `data` value lacks a base64 header! Ex: \'video/mpeg;base64,NMP[...]\')'); + } + else if (strCover && !strCover.toLowerCase().includes('base64,')) { + throw new Error('addMedia() error: `cover` value lacks a base64 header! Ex: \'data:image/png;base64,iV[...]\')'); + } + // Online Video: requires `link` + if (strType === 'online' && !strLink) { + throw new Error('addMedia() error: online videos require `link` value'); + } + // FIXME: 20190707 + // strType = strData ? strData.split(';')[0].split('/')[0] : strType + strExtn = opt.extn || (strData ? strData.split(';')[0].split('/')[1] : strPath.split('.').pop()) || 'mp3'; + // STEP 2: Set type, media + slideData.mtype = strType; + slideData.media = strPath || 'preencoded.mov'; + slideData.options = {}; + // STEP 3: Set media properties & options + slideData.options.x = intPosX; + slideData.options.y = intPosY; + slideData.options.w = intSizeX; + slideData.options.h = intSizeY; + slideData.options.objectName = objectName; + // STEP 4: Add this media to this Slide Rels (rId/rels count spans all slides! Count all media to get next rId) + /** + * NOTE: + * - rId starts at 2 (hence the intRels+1 below) as slideLayout.xml is rId=1! + * + * NOTE: + * - Audio/Video files consume *TWO* rId's: + * + * + */ + if (strType === 'online') { + var relId1 = getNewRelId(target); + // A: Add video + target._relsMedia.push({ + path: strPath || 'preencoded' + strExtn, + data: 'dummy', + type: 'online', + extn: strExtn, + rId: relId1, + Target: strLink, + }); + slideData.mediaRid = relId1; + // B: Add cover (preview/overlay) image + target._relsMedia.push({ + path: 'preencoded.png', + data: strCover, + type: 'image/png', + extn: 'png', + rId: getNewRelId(target), + Target: "../media/image-".concat(target._slideNum, "-").concat(target._relsMedia.length + 1, ".png"), + }); + } + else { + // PERF: Duplicate media should reuse existing `Target` value and not create an additional copy + var dupeItem = target._relsMedia.filter(function (item) { return item.path && item.path === strPath && item.type === strType + '/' + strExtn && !item.isDuplicate; })[0]; + // A: "relationships/video" + var relId1 = getNewRelId(target); + target._relsMedia.push({ + path: strPath || 'preencoded' + strExtn, + type: strType + '/' + strExtn, + extn: strExtn, + data: strData || '', + rId: relId1, + isDuplicate: !!(dupeItem === null || dupeItem === void 0 ? void 0 : dupeItem.Target), + Target: (dupeItem === null || dupeItem === void 0 ? void 0 : dupeItem.Target) ? dupeItem.Target : "../media/media-".concat(target._slideNum, "-").concat(target._relsMedia.length + 1, ".").concat(strExtn), + }); + slideData.mediaRid = relId1; + // B: "relationships/media" + target._relsMedia.push({ + path: strPath || 'preencoded' + strExtn, + type: strType + '/' + strExtn, + extn: strExtn, + data: strData || '', + rId: getNewRelId(target), + isDuplicate: !!(dupeItem === null || dupeItem === void 0 ? void 0 : dupeItem.Target), + Target: (dupeItem === null || dupeItem === void 0 ? void 0 : dupeItem.Target) ? dupeItem.Target : "../media/media-".concat(target._slideNum, "-").concat(target._relsMedia.length + 0, ".").concat(strExtn), + }); + // C: Add cover (preview/overlay) image + target._relsMedia.push({ + path: 'preencoded.png', + type: 'image/png', + extn: 'png', + data: strCover, + rId: getNewRelId(target), + Target: "../media/image-".concat(target._slideNum, "-").concat(target._relsMedia.length + 1, ".png"), + }); + } + // LAST + target._slideObjects.push(slideData); +} +/** + * Adds Notes to a slide. + * @param {PresSlide} `target` slide object + * @param {string} `notes` + * @since 2.3.0 + */ +function addNotesDefinition(target, notes) { + target._slideObjects.push({ + _type: SLIDE_OBJECT_TYPES.notes, + text: [{ text: notes }], + }); +} +/** + * Adds a shape object to a slide definition. + * @param {PresSlide} target slide object that the shape should be added to + * @param {SHAPE_NAME} shapeName shape name + * @param {ShapeProps} opts shape options + */ +function addShapeDefinition(target, shapeName, opts) { + var options = typeof opts === 'object' ? opts : {}; + options.line = options.line || { type: 'none' }; + var newObject = { + _type: SLIDE_OBJECT_TYPES.text, + shape: shapeName || SHAPE_TYPE.RECTANGLE, + options: options, + text: null, + }; + // Reality check + if (!shapeName) + throw new Error('Missing/Invalid shape parameter! Example: `addShape(pptxgen.shapes.LINE, {x:1, y:1, w:1, h:1});`'); + // 1: ShapeLineProps defaults + var newLineOpts = { + type: options.line.type || 'solid', + color: options.line.color || DEF_SHAPE_LINE_COLOR, + transparency: options.line.transparency || 0, + width: options.line.width || 1, + dashType: options.line.dashType || 'solid', + beginArrowType: options.line.beginArrowType || null, + endArrowType: options.line.endArrowType || null, + }; + if (typeof options.line === 'object' && options.line.type !== 'none') + options.line = newLineOpts; + // 2: Set options defaults + options.x = options.x || (options.x === 0 ? 0 : 1); + options.y = options.y || (options.y === 0 ? 0 : 1); + options.w = options.w || (options.w === 0 ? 0 : 1); + options.h = options.h || (options.h === 0 ? 0 : 1); + options.objectName = options.objectName + ? encodeXmlEntities(options.objectName) + : "Shape ".concat(target._slideObjects.filter(function (obj) { return obj._type === SLIDE_OBJECT_TYPES.text; }).length); + // 3: Handle line (lots of deprecated opts) + if (typeof options.line === 'string') { + var tmpOpts = newLineOpts; + tmpOpts.color = String(options.line); // @deprecated `options.line` string (was line color) + options.line = tmpOpts; + } + if (typeof options.lineSize === 'number') + options.line.width = options.lineSize; // @deprecated (part of `ShapeLineProps` now) + if (typeof options.lineDash === 'string') + options.line.dashType = options.lineDash; // @deprecated (part of `ShapeLineProps` now) + if (typeof options.lineHead === 'string') + options.line.beginArrowType = options.lineHead; // @deprecated (part of `ShapeLineProps` now) + if (typeof options.lineTail === 'string') + options.line.endArrowType = options.lineTail; // @deprecated (part of `ShapeLineProps` now) + // 4: Create hyperlink rels + createHyperlinkRels(target, newObject); + // LAST: Add object to slide + target._slideObjects.push(newObject); +} +/** + * Adds a table object to a slide definition. + * @param {PresSlide} target - slide object that the table should be added to + * @param {TableRow[]} tableRows - table data + * @param {TableProps} options - table options + * @param {SlideLayout} slideLayout - Slide layout + * @param {PresLayout} presLayout - Presentation layout + * @param {Function} addSlide - method + * @param {Function} getSlide - method + */ +function addTableDefinition(target, tableRows, options, slideLayout, presLayout, addSlide, getSlide) { + var slides = [target]; // Create array of Slides as more may be added by auto-paging + var opt = options && typeof options === 'object' ? options : {}; + opt.objectName = opt.objectName ? encodeXmlEntities(opt.objectName) : "Table ".concat(target._slideObjects.filter(function (obj) { return obj._type === SLIDE_OBJECT_TYPES.table; }).length); + // STEP 1: REALITY-CHECK + { + // A: check for empty + if (tableRows === null || tableRows.length === 0 || !Array.isArray(tableRows)) { + throw new Error('addTable: Array expected! EX: \'slide.addTable( [rows], {options} );\' (https://gitbrent.github.io/PptxGenJS/docs/api-tables.html)'); + } + // B: check for non-well-formatted array (ex: rows=['a','b'] instead of [['a','b']]) + if (!tableRows[0] || !Array.isArray(tableRows[0])) { + throw new Error('addTable: \'rows\' should be an array of cells! EX: \'slide.addTable( [ [\'A\'], [\'B\'], {text:\'C\',options:{align:\'center\'}} ] );\' (https://gitbrent.github.io/PptxGenJS/docs/api-tables.html)'); + } + // TODO: FUTURE: This is wacky and wont function right (shows .w value when there is none from demo.js?!) 20191219 + /* + if (opt.w && opt.colW) { + console.warn('addTable: please use either `colW` or `w` - not both (table will use `colW` and ignore `w`)') + console.log(`${opt.w} ${opt.colW}`) + } + */ + } + // STEP 2: Transform `tableRows` into well-formatted TableCell's + // tableRows can be object or plain text array: `[{text:'cell 1'}, {text:'cell 2', options:{color:'ff0000'}}]` | `["cell 1", "cell 2"]` + var arrRows = []; + tableRows.forEach(function (row) { + var newRow = []; + if (Array.isArray(row)) { + row.forEach(function (cell) { + // A: + var newCell = { + _type: SLIDE_OBJECT_TYPES.tablecell, + text: '', + options: typeof cell === 'object' && cell.options ? cell.options : {}, + }; + // B: + if (typeof cell === 'string' || typeof cell === 'number') + newCell.text = cell.toString(); + else if (cell.text) { + // Cell can contain complex text type, or string, or number + if (typeof cell.text === 'string' || typeof cell.text === 'number') + newCell.text = cell.text.toString(); + else if (cell.text) + newCell.text = cell.text; + // Capture options + if (cell.options && typeof cell.options === 'object') + newCell.options = cell.options; + } + // C: Set cell borders + newCell.options.border = newCell.options.border || opt.border || [{ type: 'none' }, { type: 'none' }, { type: 'none' }, { type: 'none' }]; + var cellBorder = newCell.options.border; + // CASE 1: border interface is: BorderOptions | [BorderOptions, BorderOptions, BorderOptions, BorderOptions] + if (!Array.isArray(cellBorder) && typeof cellBorder === 'object') + newCell.options.border = [cellBorder, cellBorder, cellBorder, cellBorder]; + // Handle: [null, null, {type:'solid'}, null] + if (!newCell.options.border[0]) + newCell.options.border[0] = { type: 'none' }; + if (!newCell.options.border[1]) + newCell.options.border[1] = { type: 'none' }; + if (!newCell.options.border[2]) + newCell.options.border[2] = { type: 'none' }; + if (!newCell.options.border[3]) + newCell.options.border[3] = { type: 'none' }; + // set complete BorderOptions for all sides + var arrSides = [0, 1, 2, 3]; + arrSides.forEach(function (idx) { + newCell.options.border[idx] = { + type: newCell.options.border[idx].type || DEF_CELL_BORDER.type, + color: newCell.options.border[idx].color || DEF_CELL_BORDER.color, + pt: typeof newCell.options.border[idx].pt === 'number' ? newCell.options.border[idx].pt : DEF_CELL_BORDER.pt, + }; + }); + // LAST: + newRow.push(newCell); + }); + } + else { + console.log('addTable: tableRows has a bad row. A row should be an array of cells. You provided:'); + console.log(row); + } + arrRows.push(newRow); + }); + // STEP 3: Set options + opt.x = getSmartParseNumber(opt.x || (opt.x === 0 ? 0 : EMU / 2), 'X', presLayout); + opt.y = getSmartParseNumber(opt.y || (opt.y === 0 ? 0 : EMU / 2), 'Y', presLayout); + if (opt.h) + opt.h = getSmartParseNumber(opt.h, 'Y', presLayout); // NOTE: Dont set default `h` - leaving it null triggers auto-rowH in `makeXMLSlide()` + opt.fontSize = opt.fontSize || DEF_FONT_SIZE; + opt.margin = opt.margin === 0 || opt.margin ? opt.margin : DEF_CELL_MARGIN_IN; + if (typeof opt.margin === 'number') + opt.margin = [Number(opt.margin), Number(opt.margin), Number(opt.margin), Number(opt.margin)]; + if (!opt.color) + opt.color = opt.color || DEF_FONT_COLOR; // Set default color if needed (table option > inherit from Slide > default to black) + if (typeof opt.border === 'string') { + console.warn('addTable `border` option must be an object. Ex: `{border: {type:\'none\'}}`'); + opt.border = null; + } + else if (Array.isArray(opt.border)) { + [0, 1, 2, 3].forEach(function (idx) { + opt.border[idx] = opt.border[idx] + ? { type: opt.border[idx].type || DEF_CELL_BORDER.type, color: opt.border[idx].color || DEF_CELL_BORDER.color, pt: opt.border[idx].pt || DEF_CELL_BORDER.pt } + : { type: 'none' }; + }); + } + opt.autoPage = typeof opt.autoPage === 'boolean' ? opt.autoPage : false; + opt.autoPageRepeatHeader = typeof opt.autoPageRepeatHeader === 'boolean' ? opt.autoPageRepeatHeader : false; + opt.autoPageHeaderRows = typeof opt.autoPageHeaderRows !== 'undefined' && !isNaN(Number(opt.autoPageHeaderRows)) ? Number(opt.autoPageHeaderRows) : 1; + opt.autoPageLineWeight = typeof opt.autoPageLineWeight !== 'undefined' && !isNaN(Number(opt.autoPageLineWeight)) ? Number(opt.autoPageLineWeight) : 0; + if (opt.autoPageLineWeight) { + if (opt.autoPageLineWeight > 1) + opt.autoPageLineWeight = 1; + else if (opt.autoPageLineWeight < -1) + opt.autoPageLineWeight = -1; + } + // autoPage ^^^ + // Set/Calc table width + // Get slide margins - start with default values, then adjust if master or slide margins exist + var arrTableMargin = DEF_SLIDE_MARGIN_IN; + // Case 1: Master margins + if (slideLayout && typeof slideLayout._margin !== 'undefined') { + if (Array.isArray(slideLayout._margin)) + arrTableMargin = slideLayout._margin; + else if (!isNaN(Number(slideLayout._margin))) { + arrTableMargin = [Number(slideLayout._margin), Number(slideLayout._margin), Number(slideLayout._margin), Number(slideLayout._margin)]; + } + } + // Case 2: Table margins + /* FIXME: add `_margin` option to slide options + else if ( addNewSlide._margin ) { + if ( Array.isArray(addNewSlide._margin) ) arrTableMargin = addNewSlide._margin; + else if ( !isNaN(Number(addNewSlide._margin)) ) arrTableMargin = [Number(addNewSlide._margin), Number(addNewSlide._margin), Number(addNewSlide._margin), Number(addNewSlide._margin)]; + } + */ + /** + * Calc table width depending upon what data we have - several scenarios exist (including bad data, eg: colW doesnt match col count) + * The API does not require a `w` value, but XML generation does, hence, code to calc a width below using colW value(s) + */ + if (opt.colW) { + var firstRowColCnt = arrRows[0].reduce(function (totalLen, c) { + var _a; + if (((_a = c === null || c === void 0 ? void 0 : c.options) === null || _a === void 0 ? void 0 : _a.colspan) && typeof c.options.colspan === 'number') { + totalLen += c.options.colspan; + } + else { + totalLen += 1; + } + return totalLen; + }, 0); + if (typeof opt.colW === 'string' || typeof opt.colW === 'number') { + // Ex: `colW = 3` or `colW = '3'` + opt.w = Math.floor(Number(opt.colW) * firstRowColCnt); + opt.colW = null; // IMPORTANT: Unset `colW` so table is created using `opt.w`, which will evenly divide cols + } + else if (opt.colW && Array.isArray(opt.colW) && opt.colW.length === 1 && firstRowColCnt > 1) { + // Ex: `colW=[3]` but with >1 cols (same as above, user is saying "use this width for all") + opt.w = Math.floor(Number(opt.colW) * firstRowColCnt); + opt.colW = null; // IMPORTANT: Unset `colW` so table is created using `opt.w`, which will evenly divide cols + } + else if (opt.colW && Array.isArray(opt.colW) && opt.colW.length !== firstRowColCnt) { + // Err: Mismatched colW and cols count + console.warn('addTable: mismatch: (colW.length != data.length) Therefore, defaulting to evenly distributed col widths.'); + opt.colW = null; + } + } + else if (opt.w) { + opt.w = getSmartParseNumber(opt.w, 'X', presLayout); + } + else { + opt.w = Math.floor(presLayout._sizeW / EMU - arrTableMargin[1] - arrTableMargin[3]); + } + // STEP 4: Convert units to EMU now (we use different logic in makeSlide->table - smartCalc is not used) + if (opt.x && opt.x < 20) + opt.x = inch2Emu(opt.x); + if (opt.y && opt.y < 20) + opt.y = inch2Emu(opt.y); + if (opt.w && opt.w < 20) + opt.w = inch2Emu(opt.w); + if (opt.h && opt.h < 20) + opt.h = inch2Emu(opt.h); + // STEP 5: Loop over cells: transform each to ITableCell; check to see whether to unset `autoPage` while here + arrRows.forEach(function (row) { + row.forEach(function (cell, idy) { + // A: Transform cell data if needed + /* Table rows can be an object or plain text - transform into object when needed + // EX: + var arrTabRows1 = [ + [ { text:'A1\nA2', options:{rowspan:2, fill:'99FFCC'} } ] + ,[ 'B2', 'C2', 'D2', 'E2' ] + ] + */ + if (typeof cell === 'number' || typeof cell === 'string') { + // Grab table formatting `opts` to use here so text style/format inherits as it should + row[idy] = { _type: SLIDE_OBJECT_TYPES.tablecell, text: String(row[idy]), options: opt }; + } + else if (typeof cell === 'object') { + // ARG0: `text` + if (typeof cell.text === 'number') + row[idy].text = row[idy].text.toString(); + else if (typeof cell.text === 'undefined' || cell.text === null) + row[idy].text = ''; + // ARG1: `options`: ensure options exists + row[idy].options = cell.options || {}; + // Set type to tabelcell + row[idy]._type = SLIDE_OBJECT_TYPES.tablecell; + } + // B: Check for fine-grained formatting, disable auto-page when found + // Since genXmlTextBody already checks for text array ( text:[{},..{}] ) we're done! + // Text in individual cells will be formatted as they are added by calls to genXmlTextBody within table builder + // if (cell.text && Array.isArray(cell.text)) opt.autoPage = false + // TODO: FIXME: WIP: 20210807: We cant do this anymore + }); + }); + // If autoPage = true, we need to return references to newly created slides if any + var newAutoPagedSlides = []; + // STEP 6: Auto-Paging: (via {options} and used internally) + // (used internally by `tableToSlides()` to not engage recursion - we've already paged the table data, just add this one) + if (opt && !opt.autoPage) { + // Create hyperlink rels (IMPORTANT: Wait until table has been shredded across Slides or all rels will end-up on Slide 1!) + createHyperlinkRels(target, arrRows); + // Add slideObjects (NOTE: Use `extend` to avoid mutation) + target._slideObjects.push({ + _type: SLIDE_OBJECT_TYPES.table, + arrTabRows: arrRows, + options: Object.assign({}, opt), + }); + } + else { + if (opt.autoPageRepeatHeader) + opt._arrObjTabHeadRows = arrRows.filter(function (_row, idx) { return idx < opt.autoPageHeaderRows; }); + // Loop over rows and create 1-N tables as needed (ISSUE#21) + getSlidesForTableRows(arrRows, opt, presLayout, slideLayout).forEach(function (slide, idx) { + // A: Create new Slide when needed, otherwise, use existing (NOTE: More than 1 table can be on a Slide, so we will go up AND down the Slide chain) + if (!getSlide(target._slideNum + idx)) + slides.push(addSlide({ masterName: (slideLayout === null || slideLayout === void 0 ? void 0 : slideLayout._name) || null })); + // B: Reset opt.y to `option`/`margin` after first Slide (ISSUE#43, ISSUE#47, ISSUE#48) + if (idx > 0) + opt.y = inch2Emu(opt.autoPageSlideStartY || opt.newSlideStartY || arrTableMargin[0]); + // C: Add this table to new Slide + { + var newSlide = getSlide(target._slideNum + idx); + opt.autoPage = false; + // Create hyperlink rels (IMPORTANT: Wait until table has been shredded across Slides or all rels will end-up on Slide 1!) + createHyperlinkRels(newSlide, slide.rows); + // Add rows to new slide + newSlide.addTable(slide.rows, Object.assign({}, opt)); + // Add reference to the new slide so it can be returned, but don't add the first one because the user already has a reference to that one. + if (idx > 0) + newAutoPagedSlides.push(newSlide); + } + }); + } + return newAutoPagedSlides; +} +/** + * Adds a text object to a slide definition. + * @param {PresSlide} target - slide object that the text should be added to + * @param {string|TextProps[]} text text string or object + * @param {TextPropsOptions} opts text options + * @param {boolean} isPlaceholder whether this a placeholder object + * @since: 1.0.0 + */ +function addTextDefinition(target, text, opts, isPlaceholder) { + var newObject = { + _type: isPlaceholder ? SLIDE_OBJECT_TYPES.placeholder : SLIDE_OBJECT_TYPES.text, + shape: (opts === null || opts === void 0 ? void 0 : opts.shape) || SHAPE_TYPE.RECTANGLE, + text: !text || text.length === 0 ? [{ text: '', options: null }] : text, + options: opts || {}, + }; + function cleanOpts(itemOpts) { + // STEP 1: Set some options + { + // A.1: Color (placeholders should inherit their colors or override them, so don't default them) + if (!itemOpts.placeholder) { + itemOpts.color = itemOpts.color || newObject.options.color || target.color || DEF_FONT_COLOR; + } + // A.2: Placeholder should inherit their bullets or override them, so don't default them + if (itemOpts.placeholder || isPlaceholder) { + itemOpts.bullet = itemOpts.bullet || false; + } + // A.3: Text targeting a placeholder need to inherit the placeholders options (eg: margin, valign, etc.) (Issue #640) + if (itemOpts.placeholder && target._slideLayout && target._slideLayout._slideObjects) { + var placeHold = target._slideLayout._slideObjects.filter(function (item) { return item._type === 'placeholder' && item.options && item.options.placeholder && item.options.placeholder === itemOpts.placeholder; })[0]; + if (placeHold === null || placeHold === void 0 ? void 0 : placeHold.options) + itemOpts = __assign(__assign({}, itemOpts), placeHold.options); + } + // A.4: Other options + itemOpts.objectName = itemOpts.objectName + ? encodeXmlEntities(itemOpts.objectName) + : "Text ".concat(target._slideObjects.filter(function (obj) { return obj._type === SLIDE_OBJECT_TYPES.text; }).length); + // B: + if (itemOpts.shape === SHAPE_TYPE.LINE) { + // ShapeLineProps defaults + var newLineOpts = { + type: itemOpts.line.type || 'solid', + color: itemOpts.line.color || DEF_SHAPE_LINE_COLOR, + transparency: itemOpts.line.transparency || 0, + width: itemOpts.line.width || 1, + dashType: itemOpts.line.dashType || 'solid', + beginArrowType: itemOpts.line.beginArrowType || null, + endArrowType: itemOpts.line.endArrowType || null, + }; + if (typeof itemOpts.line === 'object') + itemOpts.line = newLineOpts; + // 3: Handle line (lots of deprecated opts) + if (typeof itemOpts.line === 'string') { + var tmpOpts = newLineOpts; + if (typeof itemOpts.line === 'string') + tmpOpts.color = itemOpts.line; // @deprecated [remove in v4.0] + // tmpOpts.color = itemOpts.line!.toString() // @deprecated `itemOpts.line`:[string] (was line color) + itemOpts.line = tmpOpts; + } + if (typeof itemOpts.lineSize === 'number') + itemOpts.line.width = itemOpts.lineSize; // @deprecated (part of `ShapeLineProps` now) + if (typeof itemOpts.lineDash === 'string') + itemOpts.line.dashType = itemOpts.lineDash; // @deprecated (part of `ShapeLineProps` now) + if (typeof itemOpts.lineHead === 'string') + itemOpts.line.beginArrowType = itemOpts.lineHead; // @deprecated (part of `ShapeLineProps` now) + if (typeof itemOpts.lineTail === 'string') + itemOpts.line.endArrowType = itemOpts.lineTail; // @deprecated (part of `ShapeLineProps` now) + } + // C: Line opts + itemOpts.line = itemOpts.line || {}; + itemOpts.lineSpacing = itemOpts.lineSpacing && !isNaN(itemOpts.lineSpacing) ? itemOpts.lineSpacing : null; + itemOpts.lineSpacingMultiple = itemOpts.lineSpacingMultiple && !isNaN(itemOpts.lineSpacingMultiple) ? itemOpts.lineSpacingMultiple : null; + // D: Transform text options to bodyProperties as thats how we build XML + itemOpts._bodyProp = itemOpts._bodyProp || {}; + itemOpts._bodyProp.autoFit = itemOpts.autoFit || false; // DEPRECATED: (3.3.0) If true, shape will collapse to text size (Fit To shape) + itemOpts._bodyProp.anchor = !itemOpts.placeholder ? TEXT_VALIGN.ctr : null; // VALS: [t,ctr,b] + itemOpts._bodyProp.vert = itemOpts.vert || null; // VALS: [eaVert,horz,mongolianVert,vert,vert270,wordArtVert,wordArtVertRtl] + itemOpts._bodyProp.wrap = typeof itemOpts.wrap === 'boolean' ? itemOpts.wrap : true; + // E: Inset + // @deprecated 3.10.0 (`inset` - use `margin`) + if ((itemOpts.inset && !isNaN(Number(itemOpts.inset))) || itemOpts.inset === 0) { + itemOpts._bodyProp.lIns = inch2Emu(itemOpts.inset); + itemOpts._bodyProp.rIns = inch2Emu(itemOpts.inset); + itemOpts._bodyProp.tIns = inch2Emu(itemOpts.inset); + itemOpts._bodyProp.bIns = inch2Emu(itemOpts.inset); + } + // F: Transform @deprecated props + if (typeof itemOpts.underline === 'boolean' && itemOpts.underline === true) + itemOpts.underline = { style: 'sng' }; + } + // STEP 2: Transform `align`/`valign` to XML values, store in _bodyProp for XML gen + { + if ((itemOpts.align || '').toLowerCase().indexOf('c') === 0) + itemOpts._bodyProp.align = TEXT_HALIGN.center; + else if ((itemOpts.align || '').toLowerCase().indexOf('l') === 0) + itemOpts._bodyProp.align = TEXT_HALIGN.left; + else if ((itemOpts.align || '').toLowerCase().indexOf('r') === 0) + itemOpts._bodyProp.align = TEXT_HALIGN.right; + else if ((itemOpts.align || '').toLowerCase().indexOf('j') === 0) + itemOpts._bodyProp.align = TEXT_HALIGN.justify; + if ((itemOpts.valign || '').toLowerCase().indexOf('b') === 0) + itemOpts._bodyProp.anchor = TEXT_VALIGN.b; + else if ((itemOpts.valign || '').toLowerCase().indexOf('m') === 0) + itemOpts._bodyProp.anchor = TEXT_VALIGN.ctr; + else if ((itemOpts.valign || '').toLowerCase().indexOf('t') === 0) + itemOpts._bodyProp.anchor = TEXT_VALIGN.t; + } + // STEP 3: ROBUST: Set rational values for some shadow props if needed + correctShadowOptions(itemOpts.shadow); + return itemOpts; + } + // STEP 1: Create/Clean object options + newObject.options = cleanOpts(newObject.options); + // STEP 2: Create/Clean text options + newObject.text.forEach(function (item) { return (item.options = cleanOpts(item.options || {})); }); + // STEP 3: Create hyperlinks + createHyperlinkRels(target, newObject.text || ''); + // LAST: Add object to Slide + target._slideObjects.push(newObject); +} +/** + * Adds placeholder objects to slide + * @param {PresSlide} slide - slide object containing layouts + */ +function addPlaceholdersToSlideLayouts(slide) { + // Add all placeholders on this Slide that dont already exist + (slide._slideLayout._slideObjects || []).forEach(function (slideLayoutObj) { + if (slideLayoutObj._type === SLIDE_OBJECT_TYPES.placeholder) { + // A: Search for this placeholder on Slide before we add + // NOTE: Check to ensure a placeholder does not already exist on the Slide + // They are created when they have been populated with text (ex: `slide.addText('Hi', { placeholder:'title' });`) + if (slide._slideObjects.filter(function (slideObj) { return slideObj.options && slideObj.options.placeholder === slideLayoutObj.options.placeholder; }).length === 0) { + addTextDefinition(slide, [{ text: '' }], slideLayoutObj.options, false); + } + } + }); +} +/* -------------------------------------------------------------------------------- */ +/** + * Adds a background image or color to a slide definition. + * @param {BackgroundProps} props - color string or an object with image definition + * @param {PresSlide} target - slide object that the background is set to + */ +function addBackgroundDefinition(props, target) { + var _a; + // A: @deprecated + if (target.bkgd) { + if (!target.background) + target.background = {}; + if (typeof target.bkgd === 'string') + target.background.color = target.bkgd; + else { + if (target.bkgd.data) + target.background.data = target.bkgd.data; + if (target.bkgd.path) + target.background.path = target.bkgd.path; + if (target.bkgd.src) + target.background.path = target.bkgd.src; // @deprecated (drop in 4.x) + } + } + if ((_a = target.background) === null || _a === void 0 ? void 0 : _a.fill) + target.background.color = target.background.fill; + // B: Handle media + if (props && (props.path || props.data)) { + // Allow the use of only the data key (`path` isnt reqd) + props.path = props.path || 'preencoded.png'; + var strImgExtn = (props.path.split('.').pop() || 'png').split('?')[0]; // Handle "blah.jpg?width=540" etc. + if (strImgExtn === 'jpg') + strImgExtn = 'jpeg'; // base64-encoded jpg's come out as "data:image/jpeg;base64,/9j/[...]", so correct exttnesion to avoid content warnings at PPT startup + target._relsMedia = target._relsMedia || []; + var intRels = target._relsMedia.length + 1; + // NOTE: `Target` cannot have spaces (eg:"Slide 1-image-1.jpg") or a "presentation is corrupt" warning comes up + target._relsMedia.push({ + path: props.path, + type: SLIDE_OBJECT_TYPES.image, + extn: strImgExtn, + data: props.data || null, + rId: intRels, + Target: "../media/".concat((target._name || '').replace(/\s+/gi, '-'), "-image-").concat(target._relsMedia.length + 1, ".").concat(strImgExtn), + }); + target._bkgdImgRid = intRels; + } +} +/** + * Parses text/text-objects from `addText()` and `addTable()` methods; creates 'hyperlink'-type Slide Rels for each hyperlink found + * @param {PresSlide} target - slide object that any hyperlinks will be be added to + * @param {number | string | TextProps | TextProps[] | ITableCell[][]} text - text to parse + */ +function createHyperlinkRels(target, text) { + var textObjs = []; + // Only text objects can have hyperlinks, bail when text param is plain text + if (typeof text === 'string' || typeof text === 'number') + return; + // IMPORTANT: "else if" Array.isArray must come before typeof===object! Otherwise, code will exhaust recursion! + else if (Array.isArray(text)) + textObjs = text; + else if (typeof text === 'object') + textObjs = [text]; + textObjs.forEach(function (text) { + // `text` can be an array of other `text` objects (table cell word-level formatting), continue parsing using recursion + if (Array.isArray(text)) { + createHyperlinkRels(target, text); + } + else if (Array.isArray(text.text)) { + // this handles TableCells with hyperlinks + createHyperlinkRels(target, text.text); + } + else if (text && typeof text === 'object' && text.options && text.options.hyperlink && !text.options.hyperlink._rId) { + if (typeof text.options.hyperlink !== 'object') + console.log('ERROR: text `hyperlink` option should be an object. Ex: `hyperlink: {url:\'https://github.com\'}` '); + else if (!text.options.hyperlink.url && !text.options.hyperlink.slide) + console.log('ERROR: \'hyperlink requires either: `url` or `slide`\''); + else { + var relId = getNewRelId(target); + target._rels.push({ + type: SLIDE_OBJECT_TYPES.hyperlink, + data: text.options.hyperlink.slide ? 'slide' : 'dummy', + rId: relId, + Target: encodeXmlEntities(text.options.hyperlink.url) || text.options.hyperlink.slide.toString(), + }); + text.options.hyperlink._rId = relId; + } + } + }); +} + +/** + * PptxGenJS: Slide Class + */ +var Slide = /** @class */ (function () { + function Slide(params) { + var _a; + this.addSlide = params.addSlide; + this.getSlide = params.getSlide; + this._name = "Slide ".concat(params.slideNumber); + this._presLayout = params.presLayout; + this._rId = params.slideRId; + this._rels = []; + this._relsChart = []; + this._relsMedia = []; + this._setSlideNum = params.setSlideNum; + this._slideId = params.slideId; + this._slideLayout = params.slideLayout || null; + this._slideNum = params.slideNumber; + this._slideObjects = []; + /** NOTE: Slide Numbers: In order for Slide Numbers to function they need to be in all 3 files: master/layout/slide + * `defineSlideMaster` and `addNewSlide.slideNumber` will add {slideNumber} to `this.masterSlide` and `this.slideLayouts` + * so, lastly, add to the Slide now. + */ + this._slideNumberProps = ((_a = this._slideLayout) === null || _a === void 0 ? void 0 : _a._slideNumberProps) ? this._slideLayout._slideNumberProps : null; + } + Object.defineProperty(Slide.prototype, "bkgd", { + get: function () { + return this._bkgd; + }, + set: function (value) { + this._bkgd = value; + if (!this._background || !this._background.color) { + if (!this._background) + this._background = {}; + if (typeof value === 'string') + this._background.color = value; + } + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(Slide.prototype, "background", { + get: function () { + return this._background; + }, + set: function (props) { + this._background = props; + // Add background (image data/path must be captured before `exportPresentation()` is called) + if (props) + addBackgroundDefinition(props, this); + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(Slide.prototype, "color", { + get: function () { + return this._color; + }, + set: function (value) { + this._color = value; + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(Slide.prototype, "hidden", { + get: function () { + return this._hidden; + }, + set: function (value) { + this._hidden = value; + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(Slide.prototype, "slideNumber", { + get: function () { + return this._slideNumberProps; + }, + /** + * @type {SlideNumberProps} + */ + set: function (value) { + // NOTE: Slide Numbers: In order for Slide Numbers to function they need to be in all 3 files: master/layout/slide + this._slideNumberProps = value; + this._setSlideNum(value); + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(Slide.prototype, "newAutoPagedSlides", { + get: function () { + return this._newAutoPagedSlides; + }, + enumerable: false, + configurable: true + }); + /** + * Add chart to Slide + * @param {CHART_NAME|IChartMulti[]} type - chart type + * @param {object[]} data - data object + * @param {IChartOpts} options - chart options + * @return {Slide} this Slide + */ + Slide.prototype.addChart = function (type, data, options) { + // FUTURE: TODO-VERSION-4: Remove first arg - only take data and opts, with "type" required on opts + // Set `_type` on IChartOptsLib as its what is used as object is passed around + var optionsWithType = options || {}; + optionsWithType._type = type; + addChartDefinition(this, type, data, options); + return this; + }; + /** + * Add image to Slide + * @param {ImageProps} options - image options + * @return {Slide} this Slide + */ + Slide.prototype.addImage = function (options) { + addImageDefinition(this, options); + return this; + }; + /** + * Add media (audio/video) to Slide + * @param {MediaProps} options - media options + * @return {Slide} this Slide + */ + Slide.prototype.addMedia = function (options) { + addMediaDefinition(this, options); + return this; + }; + /** + * Add speaker notes to Slide + * @docs https://gitbrent.github.io/PptxGenJS/docs/speaker-notes.html + * @param {string} notes - notes to add to slide + * @return {Slide} this Slide + */ + Slide.prototype.addNotes = function (notes) { + addNotesDefinition(this, notes); + return this; + }; + /** + * Add shape to Slide + * @param {SHAPE_NAME} shapeName - shape name + * @param {ShapeProps} options - shape options + * @return {Slide} this Slide + */ + Slide.prototype.addShape = function (shapeName, options) { + // NOTE: As of v3.1.0,