diff --git a/.github/workflows/backend-tests.yml b/.github/workflows/backend-tests.yml new file mode 100644 index 00000000..8aaec8ce --- /dev/null +++ b/.github/workflows/backend-tests.yml @@ -0,0 +1,40 @@ +name: Backend Tests + +on: + pull_request: + paths: + - 'backend/**' + - 'config/distros.json' + - '.github/workflows/backend-tests.yml' + push: + branches: + - master + paths: + - 'backend/**' + - 'config/distros.json' + - '.github/workflows/backend-tests.yml' + +jobs: + test: + runs-on: ubuntu-latest + + defaults: + run: + working-directory: backend + + steps: + - name: Checkout repository + uses: actions/checkout@v5 + + - name: Set up Node.js + uses: actions/setup-node@v5 + with: + node-version: '22' + cache: 'npm' + cache-dependency-path: backend/package-lock.json + + - name: Install dependencies + run: npm ci + + - name: Run backend tests + run: npm test diff --git a/backend/app.js b/backend/app.js new file mode 100644 index 00000000..9d8627d5 --- /dev/null +++ b/backend/app.js @@ -0,0 +1,294 @@ +import express from 'express'; +import cors from 'cors'; +import swaggerJsdoc from 'swagger-jsdoc'; +import swaggerUi from 'swagger-ui-express'; +import { SUPPORTED_DISTROS, MAX_RECORDS_TO_SEND } from './config.js'; +import { buildDistroBitMap, getTables, stringifyBigInts } from './utils.js'; + +const createCorsMiddleware = () => { + const allowedOrigins = process.env.ALLOWED_ORIGINS + ? process.env.ALLOWED_ORIGINS.split(',') + : ['http://localhost:3000']; + + return cors({ + origin: (origin, callback) => { + if (!origin) return callback(null, true); + if (allowedOrigins.indexOf(origin) === -1) { + const msg = 'The CORS policy for this site does not allow access from the specified Origin.'; + return callback(new Error(msg), false); + } + return callback(null, true); + } + }); +}; + +const createSwaggerMiddleware = () => { + const swaggerOptions = { + definition: { + openapi: '3.0.0', + info: { + title: 'Software Discovery Tool API', + version: '1.0.0', + description: 'REST API for the Open Mainframe Software Discovery Tool. Replaces the Flask backend.', + }, + servers: [{ url: `http://localhost:${process.env.PORT || 5000}` }], + }, + apis: ['./app.js'], + }; + const swaggerSpec = swaggerJsdoc(swaggerOptions); + return [swaggerUi.serve, swaggerUi.setup(swaggerSpec)]; +}; + +export const createApp = (pool) => { + const app = express(); + const distroBitMap = buildDistroBitMap(SUPPORTED_DISTROS); + + app.use(createCorsMiddleware()); + app.use(express.json()); + app.use('/api-docs', ...createSwaggerMiddleware()); + + const setNoCacheHeaders = (res) => { + res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate'); + res.setHeader('Pragma', 'no-cache'); + res.setHeader('Expires', '0'); + }; + + const getSupportedDistrosHandler = (req, res) => { + setNoCacheHeaders(res); + res.json(stringifyBigInts(distroBitMap)); + }; + + /** + * @swagger + * /getSupportedDistros: + * get: + * summary: Returns a mapping of supported distributions and their versions with bitmask flags. + * responses: + * 200: + * description: A JSON object containing the distribution bit map. + * content: + * application/json: + * schema: + * type: object + */ + app.get('/getSupportedDistros', getSupportedDistrosHandler); + + /** + * @swagger + * /sdt/getSupportedDistros: + * get: + * summary: Returns a mapping of supported distributions and their versions with bitmask flags (legacy path). + * responses: + * 200: + * description: A JSON object containing the distribution bit map. + * content: + * application/json: + * schema: + * type: object + */ + app.get('/sdt/getSupportedDistros', getSupportedDistrosHandler); + + /** + * @swagger + * /searchPackages: + * get: + * summary: Search for software packages across multiple distributions. + * parameters: + * - in: query + * name: search_term + * schema: + * type: string + * required: true + * description: The term to search for. + * - in: query + * name: exact_match + * schema: + * type: boolean + * description: Whether to perform an exact match on the package name. + * - in: query + * name: search_bit_flag + * schema: + * type: string + * description: A bitmask representing the selected distributions and versions. + * - in: query + * name: page_number + * schema: + * type: integer + * description: The page number for pagination. + * - in: query + * name: search_description + * schema: + * type: boolean + * description: Whether to search within package descriptions. + * - in: query + * name: limit + * schema: + * type: integer + * description: Number of records to return per page. + * responses: + * 200: + * description: A paginated list of matching packages. + * content: + * application/json: + * schema: + * type: object + * properties: + * total_packages: + * type: integer + * current_page: + * type: integer + * last_page: + * type: integer + * more_available: + * type: boolean + * packages: + * type: array + * items: + * type: object + * 500: + * description: Server error. + */ + + /** + * @swagger + * /sdt/searchPackages: + * get: + * summary: Search for software packages across multiple distributions (legacy path). + * parameters: + * - in: query + * name: search_term + * schema: + * type: string + * required: true + * description: The term to search for. + * - in: query + * name: exact_match + * schema: + * type: boolean + * description: Whether to perform an exact match on the package name. + * - in: query + * name: search_bit_flag + * schema: + * type: string + * description: A bitmask representing the selected distributions and versions. + * - in: query + * name: page_number + * schema: + * type: integer + * description: The page number for pagination. + * - in: query + * name: search_description + * schema: + * type: boolean + * description: Whether to search within package descriptions. + * - in: query + * name: limit + * schema: + * type: integer + * description: Number of records to return per page. + * responses: + * 200: + * description: A paginated list of matching packages. + * content: + * application/json: + * schema: + * type: object + * 500: + * description: Server error. + */ + app.get(['/searchPackages', '/sdt/searchPackages'], async (req, res) => { + const searchTerm = (req.query.search_term || '').trim(); + const exactMatch = req.query.exact_match === 'true'; + const searchDescription = req.query.search_description === 'true'; + const searchBitFlag = req.query.search_bit_flag || '0'; + const pageNumber = parseInt(req.query.page_number || '0'); + const limit = Math.min(parseInt(req.query.limit || MAX_RECORDS_TO_SEND), 500); + + if (isNaN(pageNumber) || pageNumber < 0) { + return res.status(400).json({ error: 'Invalid page_number' }); + } + + if (isNaN(limit) || limit <= 0) { + return res.status(400).json({ error: 'Invalid limit' }); + } + + try { + BigInt(searchBitFlag); + } catch (e) { + return res.status(400).json({ error: 'Invalid search_bit_flag' }); + } + + if (!searchTerm) { + return res.json({ + total_packages: 0, + current_page: 0, + last_page: 0, + more_available: false, + packages: [] + }); + } + + try { + const tables = getTables(searchBitFlag, distroBitMap, SUPPORTED_DISTROS); + if (tables.length === 0) { + return res.json({ + total_packages: 0, + current_page: pageNumber, + last_page: 0, + more_available: false, + packages: [] + }); + } + + let sqlSearchTerm = searchTerm; + if (!exactMatch) { + if (sqlSearchTerm.includes('*')) { + sqlSearchTerm = sqlSearchTerm.replace(/\*/g, '%'); + } else { + sqlSearchTerm = `%${sqlSearchTerm}%`; + } + } + + const subQuery = exactMatch + ? 'SELECT packageName, description, version, osName FROM ?? WHERE packageName = ?' + : (searchDescription + ? 'SELECT packageName, description, version, osName FROM ?? WHERE (packageName LIKE ? OR description LIKE ?)' + : 'SELECT packageName, description, version, osName FROM ?? WHERE packageName LIKE ?'); + + const unionQuery = tables.map(() => `(${subQuery})`).join(' UNION ALL '); + const countQuery = `SELECT COUNT(*) as total FROM (${unionQuery}) AS combined`; + + const queryParams = []; + tables.forEach(table => { + queryParams.push(table); + queryParams.push(sqlSearchTerm); + if (!exactMatch && searchDescription) { + queryParams.push(sqlSearchTerm); + } + }); + + const [countResult] = await pool.query(countQuery, queryParams); + const totalLength = countResult[0].total; + + const dataQuery = `${unionQuery} LIMIT ? OFFSET ?`; + const dataParams = [...queryParams, limit, pageNumber * limit]; + + const [results] = await pool.query(dataQuery, dataParams); + const lastPage = Math.ceil(totalLength / limit); + + setNoCacheHeaders(res); + res.json({ + total_packages: totalLength, + current_page: pageNumber, + last_page: lastPage, + more_available: (pageNumber + 1) * limit < totalLength, + packages: results + }); + } catch (err) { + console.error(err); + res.status(500).json({ error: 'Internal server error' }); + } + }); + + return app; +}; diff --git a/backend/index.js b/backend/index.js index f1731b5f..400a6632 100644 --- a/backend/index.js +++ b/backend/index.js @@ -1,40 +1,6 @@ -import express from 'express'; import mysql from 'mysql2/promise'; -import cors from 'cors'; import 'dotenv/config'; -import swaggerJsdoc from 'swagger-jsdoc'; -import swaggerUi from 'swagger-ui-express'; -import { SUPPORTED_DISTROS, MAX_RECORDS_TO_SEND } from './config.js'; - -const app = express(); -const allowedOrigins = process.env.ALLOWED_ORIGINS ? process.env.ALLOWED_ORIGINS.split(',') : ['http://localhost:3000']; -app.use(cors({ - origin: (origin, callback) => { - // allow requests with no origin (like mobile apps or curl requests) - if (!origin) return callback(null, true); - if (allowedOrigins.indexOf(origin) === -1) { - const msg = 'The CORS policy for this site does not allow access from the specified Origin.'; - return callback(new Error(msg), false); - } - return callback(null, true); - } -})); -app.use(express.json()); - -const swaggerOptions = { - definition: { - openapi: '3.0.0', - info: { - title: 'Software Discovery Tool API', - version: '1.0.0', - description: 'REST API for the Open Mainframe Software Discovery Tool. Replaces the Flask backend.', - }, - servers: [{ url: `http://localhost:${process.env.PORT || 5000}` }], - }, - apis: ['./index.js'], -}; -const swaggerSpec = swaggerJsdoc(swaggerOptions); -app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec)); +import { createApp } from './app.js'; const pool = mysql.createPool({ host: process.env.DB_HOST, @@ -46,288 +12,9 @@ const pool = mysql.createPool({ queueLimit: 0 }); -// Calculate DISTRO_BIT_MAP -const DISTRO_BIT_MAP = {}; -let bitFlag = 1n; -for (const distroName of Object.keys(SUPPORTED_DISTROS)) { - const versions = Object.keys(SUPPORTED_DISTROS[distroName]).sort(); - for (const version of versions) { - if (!DISTRO_BIT_MAP[distroName]) { - DISTRO_BIT_MAP[distroName] = {}; - } - DISTRO_BIT_MAP[distroName][version] = bitFlag; - bitFlag *= 2n; - } -} - -const getTables = (searchBit) => { - const ans = []; - for (const distroName of Object.keys(SUPPORTED_DISTROS)) { - const versions = Object.keys(SUPPORTED_DISTROS[distroName]).sort(); - for (const version of versions) { - const b = DISTRO_BIT_MAP[distroName][version]; - if ((b & BigInt(searchBit)) > 0n) { - ans.push(SUPPORTED_DISTROS[distroName][version]); - } - } - } - return ans; -}; - -// Helper function to stringify BigInts in an object -const stringifyBigInts = (obj) => { - return JSON.parse(JSON.stringify(obj, (key, value) => - typeof value === 'bigint' ? value.toString() : value - )); -}; - -// Replicate routes - -/** - * @swagger - * /getSupportedDistros: - * get: - * summary: Returns a mapping of supported distributions and their versions with bitmask flags. - * responses: - * 200: - * description: A JSON object containing the distribution bit map. - * content: - * application/json: - * schema: - * type: object - */ -app.get('/getSupportedDistros', (req, res) => { - res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate'); - res.setHeader('Pragma', 'no-cache'); - res.setHeader('Expires', '0'); - res.json(stringifyBigInts(DISTRO_BIT_MAP)); -}); - -/** - * @swagger - * /sdt/getSupportedDistros: - * get: - * summary: Returns a mapping of supported distributions and their versions with bitmask flags (legacy path). - * responses: - * 200: - * description: A JSON object containing the distribution bit map. - * content: - * application/json: - * schema: - * type: object - */ -app.get('/sdt/getSupportedDistros', (req, res) => { - res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate'); - res.setHeader('Pragma', 'no-cache'); - res.setHeader('Expires', '0'); - res.json(stringifyBigInts(DISTRO_BIT_MAP)); -}); - -/** - * @swagger - * /searchPackages: - * get: - * summary: Search for software packages across multiple distributions. - * parameters: - * - in: query - * name: search_term - * schema: - * type: string - * required: true - * description: The term to search for. - * - in: query - * name: exact_match - * schema: - * type: boolean - * description: Whether to perform an exact match on the package name. - * - in: query - * name: search_bit_flag - * schema: - * type: string - * description: A bitmask representing the selected distributions and versions. - * - in: query - * name: page_number - * schema: - * type: integer - * description: The page number for pagination. - * - in: query - * name: search_description - * schema: - * type: boolean - * description: Whether to search within package descriptions. - * - in: query - * name: limit - * schema: - * type: integer - * description: Number of records to return per page. - * responses: - * 200: - * description: A paginated list of matching packages. - * content: - * application/json: - * schema: - * type: object - * properties: - * total_packages: - * type: integer - * current_page: - * type: integer - * last_page: - * type: integer - * more_available: - * type: boolean - * packages: - * type: array - * items: - * type: object - * 500: - * description: Server error. - */ - -/** - * @swagger - * /sdt/searchPackages: - * get: - * summary: Search for software packages across multiple distributions (legacy path). - * parameters: - * - in: query - * name: search_term - * schema: - * type: string - * required: true - * description: The term to search for. - * - in: query - * name: exact_match - * schema: - * type: boolean - * description: Whether to perform an exact match on the package name. - * - in: query - * name: search_bit_flag - * schema: - * type: string - * description: A bitmask representing the selected distributions and versions. - * - in: query - * name: page_number - * schema: - * type: integer - * description: The page number for pagination. - * - in: query - * name: search_description - * schema: - * type: boolean - * description: Whether to search within package descriptions. - * - in: query - * name: limit - * schema: - * type: integer - * description: Number of records to return per page. - * responses: - * 200: - * description: A paginated list of matching packages. - * content: - * application/json: - * schema: - * type: object - * 500: - * description: Server error. - */ -app.get(['/searchPackages', '/sdt/searchPackages'], async (req, res) => { - const searchTerm = (req.query.search_term || '').trim(); - const exactMatch = req.query.exact_match === 'true'; - const searchDescription = req.query.search_description === 'true'; - const searchBitFlag = req.query.search_bit_flag || '0'; - const pageNumber = parseInt(req.query.page_number || '0'); - const limit = Math.min(parseInt(req.query.limit || MAX_RECORDS_TO_SEND), 500); // Max 500 for safety - - if (isNaN(pageNumber) || pageNumber < 0) { - return res.status(400).json({ error: 'Invalid page_number' }); - } - - if (isNaN(limit) || limit <= 0) { - return res.status(400).json({ error: 'Invalid limit' }); - } - - try { - BigInt(searchBitFlag); - } catch (e) { - return res.status(400).json({ error: 'Invalid search_bit_flag' }); - } - - if (!searchTerm) { - return res.json({ - total_packages: 0, - current_page: 0, - last_page: 0, - more_available: false, - packages: [] - }); - } - - try { - const tables = getTables(searchBitFlag); - if (tables.length === 0) { - return res.json({ - total_packages: 0, - current_page: pageNumber, - last_page: 0, - more_available: false, - packages: [] - }); - } - - let sqlSearchTerm = searchTerm; - if (!exactMatch) { - if (sqlSearchTerm.includes('*')) { - sqlSearchTerm = sqlSearchTerm.replace(/\*/g, '%'); - } else { - sqlSearchTerm = `%${sqlSearchTerm}%`; - } - } - - const subQuery = exactMatch - ? 'SELECT packageName, description, version, osName FROM ?? WHERE packageName = ?' - : (searchDescription - ? 'SELECT packageName, description, version, osName FROM ?? WHERE (packageName LIKE ? OR description LIKE ?)' - : 'SELECT packageName, description, version, osName FROM ?? WHERE packageName LIKE ?'); - - const unionQuery = tables.map(() => `(${subQuery})`).join(' UNION ALL '); - const countQuery = `SELECT COUNT(*) as total FROM (${unionQuery}) AS combined`; - - const queryParams = []; - tables.forEach(table => { - queryParams.push(table); - queryParams.push(sqlSearchTerm); - if (!exactMatch && searchDescription) { - queryParams.push(sqlSearchTerm); - } - }); - - const [countResult] = await pool.query(countQuery, queryParams); - const totalLength = countResult[0].total; - - const dataQuery = `${unionQuery} LIMIT ? OFFSET ?`; - const dataParams = [...queryParams, limit, pageNumber * limit]; - - const [results] = await pool.query(dataQuery, dataParams); - const lastPage = Math.ceil(totalLength / limit); - - res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate'); - res.setHeader('Pragma', 'no-cache'); - res.setHeader('Expires', '0'); - res.json({ - total_packages: totalLength, - current_page: pageNumber, - last_page: lastPage, - more_available: (pageNumber + 1) * limit < totalLength, - packages: results - }); - } catch (err) { - console.error(err); - res.status(500).json({ error: 'Internal server error' }); - } -}); - +const app = createApp(pool); export default app; + const PORT = process.env.PORT || 5000; if (process.env.NODE_ENV !== 'test') { app.listen(PORT, () => { diff --git a/backend/jest.config.js b/backend/jest.config.js new file mode 100644 index 00000000..aa1f513c --- /dev/null +++ b/backend/jest.config.js @@ -0,0 +1,8 @@ +/** @type {import('jest').Config} */ +export default { + testEnvironment: 'node', + setupFiles: ['/tests/setup.js'], + testMatch: ['**/tests/**/*.test.js'], + transform: {}, + extensionsToTreatAsEsm: [], +}; diff --git a/backend/package-lock.json b/backend/package-lock.json index 461ab8f5..026a85df 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -3,7 +3,6 @@ "version": "1.0.0", "lockfileVersion": 3, "requires": true, - "type": "module", "packages": { "": { "name": "backend", @@ -13,12 +12,12 @@ "cors": "^2.8.6", "dotenv": "^17.4.2", "express": "^5.2.1", - "mysql2": "^3.22.1", - "swagger-jsdoc": "^6.2.8", + "mysql2": "^3.24.4", + "swagger-jsdoc": "^6.3.0", "swagger-ui-express": "^5.0.1" }, "devDependencies": { - "jest": "^30.4.2", + "jest": "^30.5.1", "nodemon": "^3.1.14", "supertest": "^7.2.2" } @@ -306,9 +305,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.29.8", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", - "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", + "version": "7.29.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.9.tgz", + "integrity": "sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==", "dev": true, "license": "MIT", "dependencies": { @@ -615,40 +614,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@emnapi/core": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", - "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.1", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", - "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", - "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@isaacs/cliui": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-9.0.0.tgz", @@ -686,9 +651,9 @@ } }, "node_modules/@jest/console": { - "version": "30.5.1", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.5.1.tgz", - "integrity": "sha512-u5Ncuc+gXVUwjNMFQOnphHo2Qx2DxyC8Dvpmf4HCx4y0kvSkRRNiQYRixrvOP4V7y52JcSuNxqochCt0PpdHVg==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.5.2.tgz", + "integrity": "sha512-e8sQC4pCMHPBrPX7Hk8TJr+DM5dkUnImSTNag8SEWFAFAF6xIz4AJvCKapnrdHnZxep4bxw4PhG+ZbimYBMyKw==", "dev": true, "license": "MIT", "dependencies": { @@ -704,17 +669,17 @@ } }, "node_modules/@jest/core": { - "version": "30.5.1", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.5.1.tgz", - "integrity": "sha512-BL9g6CJUUhIbdoAflz/Va658erSSXUIvU8XUYiWNTbZljJjZ5yaC9EJ9/dQ19rpbYV3ZOK4sBACqhPaTyhoUIA==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.5.2.tgz", + "integrity": "sha512-/jqvFWoJF7LV1a6AUpYMbMm+2yIYsHJfR474H0SCzr9k8tnO/jl9wRg6zSG6lxgf6EozNlCG4amFzWsbZSvvZA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "30.5.1", + "@jest/console": "30.5.2", "@jest/pattern": "30.5.0", - "@jest/reporters": "30.5.1", - "@jest/test-result": "30.5.1", - "@jest/transform": "30.5.1", + "@jest/reporters": "30.5.2", + "@jest/test-result": "30.5.2", + "@jest/transform": "30.5.2", "@jest/types": "30.5.1", "@types/node": "*", "ansi-escapes": "^4.3.2", @@ -724,18 +689,18 @@ "fast-json-stable-stringify": "^2.1.0", "graceful-fs": "^4.2.11", "jest-changed-files": "30.5.1", - "jest-config": "30.5.1", + "jest-config": "30.5.2", "jest-haste-map": "30.5.1", "jest-message-util": "30.5.1", "jest-regex-util": "30.5.0", "jest-resolve": "30.5.1", - "jest-resolve-dependencies": "30.5.1", - "jest-runner": "30.5.1", - "jest-runtime": "30.5.1", - "jest-snapshot": "30.5.1", + "jest-resolve-dependencies": "30.5.2", + "jest-runner": "30.5.2", + "jest-runtime": "30.5.2", + "jest-snapshot": "30.5.2", "jest-util": "30.5.1", "jest-validate": "30.5.1", - "jest-watcher": "30.5.1", + "jest-watcher": "30.5.2", "pretty-format": "30.5.1", "slash": "^3.0.0" }, @@ -762,39 +727,39 @@ } }, "node_modules/@jest/environment": { - "version": "30.5.1", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.5.1.tgz", - "integrity": "sha512-eYJAkOsrpwDXPcoLNEG6lN9Zo3Cy35pxnVQD74vyIfsi/Q8wB/lZFsEjU4wrecOgT4ZviQDZaX/cFIJyMdMxTw==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.5.2.tgz", + "integrity": "sha512-nwFPOUMbmsjNNRCpnJpB9HbSZu4C07wDDds++5j+j1MezzPLxMPDdbdUVjr2o4RULDOValnDd70taPFI1EtxAg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/fake-timers": "30.5.1", + "@jest/fake-timers": "30.5.2", "@jest/types": "30.5.1", "@types/node": "*", - "jest-mock": "30.5.1" + "jest-mock": "30.5.2" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/expect": { - "version": "30.5.1", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.5.1.tgz", - "integrity": "sha512-uOGd40P/COyUp9xHf5jeGiGJC2/ANg+2+Tk9/xN5/LxmlY/r/gxsPrx3DTEtaRZsLAX4wgNSLEDELZ5bmVs2bA==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.5.2.tgz", + "integrity": "sha512-2E+1Pg6jJxbRjLNLpciC71iri/3qsKJL222/aXJEFwC0VdUSvfS+JZQQLEVjUYYc8t8pQu2MGE3cF2+bNTYRsg==", "dev": true, "license": "MIT", "dependencies": { - "expect": "30.5.1", - "jest-snapshot": "30.5.1" + "expect": "30.5.2", + "jest-snapshot": "30.5.2" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/expect-utils": { - "version": "30.5.1", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.5.1.tgz", - "integrity": "sha512-WcRWhHQdTMRDpyWKZ/6MINBmovI7zeD+bL8wFjCncRV3NQOwKy1X45IfyblfHR4k/XciIlNEdFL9QjFO+HNKOg==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.5.2.tgz", + "integrity": "sha512-d/HOLSrJUlBIl6n3LzOa7cjW3xdePm5H5gQw2lixZ/0h157l9sw3mLblzFqnPcTbhDzhRQdBOX3A3KcG73nm5Q==", "dev": true, "license": "MIT", "dependencies": { @@ -805,9 +770,9 @@ } }, "node_modules/@jest/fake-timers": { - "version": "30.5.1", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.5.1.tgz", - "integrity": "sha512-rEkV6YzpBXo/L9cnj2ibyuYZuyGiNWaPUsyCu3HYJbqCV4jnEiKmf7hId20z8eKjZ0JQ9jtIYKxRSmYB/OYSsA==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.5.2.tgz", + "integrity": "sha512-GKk0h0tKT5SpDPxDhPg3r9mm5s8/YCBzVNMOGdRiQXwUiMsjU10mVPlMaQstH6/cu50vOb8N9oQUMETHuwwxKw==", "dev": true, "license": "MIT", "dependencies": { @@ -815,7 +780,7 @@ "@sinonjs/fake-timers": "^15.4.0", "@types/node": "*", "jest-message-util": "30.5.1", - "jest-mock": "30.5.1", + "jest-mock": "30.5.2", "jest-util": "30.5.1" }, "engines": { @@ -833,16 +798,16 @@ } }, "node_modules/@jest/globals": { - "version": "30.5.1", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.5.1.tgz", - "integrity": "sha512-VhqvQ251XIC7pk46YymI3HCeBLOdvriDw9zibekodAHEwoVvbVVgpU5H13GvmyOAzxtZCfsm1uvzqkaqJf2I+g==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.5.2.tgz", + "integrity": "sha512-62zfS+dL+M5aTNHXCNLyqD+j4oqxTIzDYrZbpqMP8Yn+gvlAGyzC6BSPNb1ZmFOakV1RtFYX/O3FCQXSWmQdgg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.5.1", - "@jest/expect": "30.5.1", + "@jest/environment": "30.5.2", + "@jest/expect": "30.5.2", "@jest/types": "30.5.1", - "jest-mock": "30.5.1" + "jest-mock": "30.5.2" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -879,16 +844,16 @@ "license": "MIT" }, "node_modules/@jest/reporters": { - "version": "30.5.1", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.5.1.tgz", - "integrity": "sha512-RbUXIfv85KxitJn4l3MpAoMilkvXe2QCO5lXxHWftywo/VdZ5vkEoHRDgphcxP6qmoIkUaN8/UZj6NhdkIFdJg==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.5.2.tgz", + "integrity": "sha512-ev6E9iG73twQcfAfoTnviuYkr3yOrYbzAtMFCimv2fxHXPIp9PBt2u1wNMt0aYWgl8FPwIcS8oqRJg6alnht4Q==", "dev": true, "license": "MIT", "dependencies": { "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "30.5.1", - "@jest/test-result": "30.5.1", - "@jest/transform": "30.5.1", + "@jest/console": "30.5.2", + "@jest/test-result": "30.5.2", + "@jest/transform": "30.5.2", "@jest/types": "30.5.1", "@jridgewell/trace-mapping": "^0.3.31", "@types/node": "*", @@ -951,9 +916,9 @@ } }, "node_modules/@jest/source-map": { - "version": "30.5.0", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-30.5.0.tgz", - "integrity": "sha512-xWpTJP9D0bDFGbPGT8XuWSwwha/iHADyyKzUnMx4UbdgnHugxrDaQFO4RZ8x4ZsFzRP6pNii8uvlgKCDxCIuDg==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-30.5.2.tgz", + "integrity": "sha512-c5CehvkbfHX6yyNg7bMn2ylqlp9AHXuZ/3hPH/Lh5bxD/vefd/lV9HBjMqXef9yQNqkdwyZvkLSVSwB38hy8Qw==", "dev": true, "license": "MIT", "dependencies": { @@ -967,13 +932,13 @@ } }, "node_modules/@jest/test-result": { - "version": "30.5.1", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.5.1.tgz", - "integrity": "sha512-A/1S6ZBdpic50E0pxLgvaB9XNPL4k7AksmG69OO2oiotxciWZwHkbOec+qbIi+uUtIIByOVlXJUl97oZUsz+Jw==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.5.2.tgz", + "integrity": "sha512-eO6YU5rsLfs60ne694e0IAmRgeBnoA8mu0nlxXDbl/1j5km0o2vO9/VbbUIKIH+WRKKEI+ELgwraRGn2Lhep8g==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "30.5.1", + "@jest/console": "30.5.2", "@jest/types": "30.5.1", "@types/istanbul-lib-coverage": "^2.0.6", "collect-v8-coverage": "^1.0.2" @@ -983,13 +948,13 @@ } }, "node_modules/@jest/test-sequencer": { - "version": "30.5.1", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.5.1.tgz", - "integrity": "sha512-SHcPnrjdVRYJv6y6l4JUTy4Jccu7zmO6BmiOfOA34UiVBto22s19WiDC0A/2qfM7MWB/qdcoqfSIRMitpjTT4A==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.5.2.tgz", + "integrity": "sha512-bmij8jZyYAC47ExT2GeP7PcrlwSNS+Bp3KeRuBH88gpcxqDv6fljN8PemS4J/lLZ79xb6mpaennQTTq0zUAVVg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/test-result": "30.5.1", + "@jest/test-result": "30.5.2", "graceful-fs": "^4.2.11", "jest-haste-map": "30.5.1", "slash": "^3.0.0" @@ -999,9 +964,9 @@ } }, "node_modules/@jest/transform": { - "version": "30.5.1", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.5.1.tgz", - "integrity": "sha512-EDnDhn0jleU9ZhpVoA4gvqw+Ev0iw/r5upNT2b79RiwiaTiYAMMhvNJP3lmocjIe5J2ZkoNr0B3K/J6O5GIm4Q==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.5.2.tgz", + "integrity": "sha512-LBGwmaE886C41jDZ65eqR1YbXM7642MK3ZhTowZd5+xDlrDLuf4ePuDdDmVSvcr8NTVJ3MqxLkMbUCzuigUaoQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1093,28 +1058,6 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.3.tgz", - "integrity": "sha512-UMduMbqO5s5zF2NkNacMT/yK5Y5QiKvWr2+50bzIIxFDwVJ2h49b+oyjaCGPhJxd2/gC2x39EHv/gHVuu36x2Q==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@tybys/wasm-util": "^0.10.3" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=23.5.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - }, - "peerDependencies": { - "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.4", - "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.4" - } - }, "node_modules/@noble/hashes": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", @@ -1173,255 +1116,6 @@ "@parcel/watcher-win32-x64": "2.6.0" } }, - "node_modules/@parcel/watcher-android-arm64": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.6.0.tgz", - "integrity": "sha512-trgpLSCKRC/huFjXX/Smh+0sWe4+YtKfktIToiMl59ghz7z+qkH6kMvNnUbLyRs9N11t8l4svSCs1+5B3rOAhA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-darwin-arm64": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.6.0.tgz", - "integrity": "sha512-Y3QV0gl7Q1zbfueunkWIERICbEojQFCgpyG7YqOGNFLsckXyI1xu9mAIUpKY9QBYzBtSkN8dBPwd3yiAO9ovMw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-darwin-x64": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.6.0.tgz", - "integrity": "sha512-Ohv6OpzhUfKYD7Beb8kDvG0jbIxORCYY1JRdZnaBtnjjkJxgD7ZVL0nw2sCYd0yTMKTvz3nnTnOF3cDifK+kvw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-freebsd-x64": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.6.0.tgz", - "integrity": "sha512-5HmXvDgs8VK+74jF9y9/2FE3/OnlcKmc56tjmSrEuZjpSZOGL+fvAu+HKJBdPs9uwoP2hE6TlSUpXZ/C5jUFmQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-arm-glibc": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.6.0.tgz", - "integrity": "sha512-Ps/hui3A+vMbjdqlqAowK2ZL8+BO8dBjxeWXj6npTBs3jx4wWmbPpaLuqwrQrSqIVMCnpWo238bJ1U37GhQOYg==", - "cpu": [ - "arm" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-arm-musl": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.6.0.tgz", - "integrity": "sha512-9c6AUHgHoG+IY88MRIHupztQiQnrbqHYQjkM2btA+Bf/wQnQMuiD0Wfk1EVv3TlNT3x41uU71rn6E4xh/+zvkw==", - "cpu": [ - "arm" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-arm64-glibc": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.6.0.tgz", - "integrity": "sha512-yHRqS2owEXe6Hic9z6Mh1ECsCd+ODVOGvZDyciqRd21+v+o+DnXMOrw50DSpIG2sb8GPEaPPmfeCAWKPJdq46g==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-arm64-musl": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.6.0.tgz", - "integrity": "sha512-WhB2e/V7rqdHHWZusBSPuy5Ei8S6lSz6FE5TKKQz5h3a0O+C+mhY7vxU9b/stqvMb8beLnPY82ZrFTLKs+SrKA==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-x64-glibc": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.6.0.tgz", - "integrity": "sha512-ulGE6x6Oz6iAwg75T8YQSoguBWasniIbX+QWpaYPcCnDOpdWX3k+4xbEYPZVLxOuoJI+svJJPD3sEj8G7lrQ3A==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-x64-musl": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.6.0.tgz", - "integrity": "sha512-tkBYKt7YQrjIJWYDnto2YgO8MRkjlMTSNoRHzsXinBqbLdeOM3L32wPZJvIZxqaLMfSlS/4sUjH/6STVP/XDLw==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-win32-arm64": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.6.0.tgz", - "integrity": "sha512-gIZAP23jaHjGWasY/TY6yL7NHFClf0Ga7FN+iINvk+KN94rhm94lYZhFsbYFNcA04/onvGD9kKmiJLJB2HbNwQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, "node_modules/@parcel/watcher-win32-x64": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.6.0.tgz", @@ -1514,17 +1208,6 @@ "@sinonjs/commons": "^3.0.1" } }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", - "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -1643,335 +1326,6 @@ "dev": true, "license": "ISC" }, - "node_modules/@unrs/resolver-binding-android-arm-eabi": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.12.2.tgz", - "integrity": "sha512-g5T90pqg1bo/7mytQx6F4iBNC0Wsh9cu+z9veDbFjc7HjpesJFWD7QMS0NGStXM075+7dJPPVvBbpZlnrdpi/w==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@unrs/resolver-binding-android-arm64": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.12.2.tgz", - "integrity": "sha512-YGCRZv/9GLhwmz6mYDeTsm/92BAyR28l6c2ReweVW5pWgfsitWLY8upvfRlGdoyD8HjeTHSYJWyZGD4KJA/nFQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@unrs/resolver-binding-darwin-arm64": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.12.2.tgz", - "integrity": "sha512-u9DiNT1auQMO20A9SyTuG3wUgQWB9Z7KjAg0uFuCDR1FsAY8A0CG2S6JpHS1xwm/w1G08bjXZDcyOCjv1WAm2w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@unrs/resolver-binding-darwin-x64": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.12.2.tgz", - "integrity": "sha512-f7rPLi/T1HVKZu/u6t87lroib16n8vrSzcyxI7lg4BGO9UF26KhQL44sd9eOUgrTYhvRXtWOIZT5PejdPyJfUA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@unrs/resolver-binding-freebsd-x64": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.12.2.tgz", - "integrity": "sha512-BpcOjWCJub6nRZUS2zA20pmLvjtqAtGejETaIyRLiZiQf++cbrjltLA5NN/xaXfqeOBOSlMFbemIl5/S5tljmg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.12.2.tgz", - "integrity": "sha512-vZTDvdSISZjJx66OzJqtsOhzifbqRjbmI1Mnu49fQDwog5GtDI4QidRiEAYbZCRj9C8YZEW+3ZjqsyS9GR4k2A==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.12.2.tgz", - "integrity": "sha512-BiPI+IrIlwcW4nLLMM21+B1dFPzd55yAVgVGrdgDjNef+ch03GdxrcyaIz8X9SsQirh/kCQ7mviyWlMxdh2D7g==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.12.2.tgz", - "integrity": "sha512-zJc0H99FEPoFfSrNpa91HYfxzfAJCr502oxNK1cfdC9hlaFI43RT+JFCann9JUgZmLzzntChHyn13Sgn9ljHNg==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-arm64-musl": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.12.2.tgz", - "integrity": "sha512-KQ3Lki6l+Pz1k/eBipN41ES+YUK30beLGb9YqcB1O542cyLCNE6GaxrfcY3T6EezmGGk84wb5XyO9loTM9tkcA==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-loong64-gnu": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-loong64-gnu/-/resolver-binding-linux-loong64-gnu-1.12.2.tgz", - "integrity": "sha512-3SJGEh1DborhG6pyxvhPzCT4bbSIVihsvgJc13P1bHG7KLdNDaF9T3gsTwFc7Jw/5Y5/iWOjkEx7Zy0NvCGX3Q==", - "cpu": [ - "loong64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-loong64-musl": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-loong64-musl/-/resolver-binding-linux-loong64-musl-1.12.2.tgz", - "integrity": "sha512-jiuG/Obbel7uw1PwHNFfrkiKhLAF6mnyZ6aWlOAVN9WqKm8v0OFGnciJIHu8+CMvXLQ8AD51LPzAoUfT21D5Ew==", - "cpu": [ - "loong64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.12.2.tgz", - "integrity": "sha512-q7xRvVpmcfeL+LlZg8Pbbo6QaTZwDU5BaGZbwfhkEsXJn3Was8xYfE0RBH266xZt0rM6B7i8xAYIvjthuUIWHg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.12.2.tgz", - "integrity": "sha512-0CVdx6lcnT3Q9inOH8tsMIOJ6ImndllMjqJHg8RLVdB7Vq4SfkEXl9mCSsVNuNA4MCYycRicCUxPCabVHJRr6A==", - "cpu": [ - "riscv64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.12.2.tgz", - "integrity": "sha512-iOwlRo9vnp6R6ohHQS11n0NnfdXx/omhkocmIfaPRpQhKZ+3BDMkkdRVh53qjkFkpPddf+FETA28NwGN7l5l+w==", - "cpu": [ - "riscv64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.12.2.tgz", - "integrity": "sha512-HYJtLfXq94q8iZNFT1lknx258wlkkWhZeUXJRqzKBBUJ00CvZ+N33zgbCqimLjsyw5Va6uUxhVa12mI+kaveEw==", - "cpu": [ - "s390x" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-x64-gnu": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.12.2.tgz", - "integrity": "sha512-mPsUhunKKDih5O96Y6enDQyHc1SqBPlY1E/SfMWDM3EdJ95Z9CArPeCVwCCqbP45ljvivdEk8Fxn+SIb1rDAJQ==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-x64-musl": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.12.2.tgz", - "integrity": "sha512-azrt6+5ydLd8Vt210AAFis/lZevSfPw93EJRIJG+xPu4WCJ8K0kppCTpMyLPcKT7H15M4Jnt2tMp5bOvCkRC6A==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-openharmony-arm64": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-openharmony-arm64/-/resolver-binding-openharmony-arm64-1.12.2.tgz", - "integrity": "sha512-YZ9hP4O0X9PQb8eO980qmLNGH4zT3I9+SZTdt0Pr0YyuGQhYKoOZkV02VzrzyOZJ5xIJ3UFIenKkUkGg8GjgWQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] - }, - "node_modules/@unrs/resolver-binding-wasm32-wasi": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.12.2.tgz", - "integrity": "sha512-tYFDIkMxSflfEc/h92ZWNsZlHSwgimbNHSO3PL2JWQHfCuC2q316jMyYU9TIWZsFK2bQwyK5VAdYgn8ygPj69A==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "1.10.0", - "@emnapi/runtime": "1.10.0", - "@napi-rs/wasm-runtime": "^1.1.4" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.12.2.tgz", - "integrity": "sha512-qzNyg3xL0VPQmCaUh+N5jSitce6k+uCBfMDesWRnlULOZaqUkaJ0ybdT+UqlAWJoQjuqfIU/0Ptx9bteN4D82g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.12.2.tgz", - "integrity": "sha512-WD9sY00OfpHVGfsnHZoA8jVT+esS/Bg8z8jzxp5BnDCjjwsuKsPQrzswwpFy4J1AUJbXPRfkpcX0mXrzeXW79g==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, "node_modules/@unrs/resolver-binding-win32-x64-msvc": { "version": "1.12.2", "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.12.2.tgz", @@ -2119,13 +1473,13 @@ } }, "node_modules/babel-jest": { - "version": "30.5.1", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.5.1.tgz", - "integrity": "sha512-ge1xUVZS91ml09YRMgRGgeKJ4YJpcOiuwteAxFBYLugQyp7cRw+hHej6Ho0vPjvLrjq60bb7JPHH9LAMA1/krA==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.5.2.tgz", + "integrity": "sha512-ES8WeJ2fNWPEDunyAtUfJ12nHyaWUloCHdkfK2oW5uSoQSUmjNKdWiGaXJITJwvFEWoD/evHEg/QoCXAvLaQqQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/transform": "30.5.1", + "@jest/transform": "30.5.2", "@types/babel__core": "^7.20.5", "babel-plugin-istanbul": "^8.0.0", "babel-preset-jest": "30.5.0", @@ -2227,9 +1581,9 @@ } }, "node_modules/baseline-browser-mapping": { - "version": "2.11.21", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.21.tgz", - "integrity": "sha512-uh8vpY/1/YyFkunIDFH/12p7/7VdPKA1hejMVEbdkEaWnUz0Hesvx5EbiU6XxjyHZIOju+ZMbQJkRh+es3/spQ==", + "version": "2.11.25", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.25.tgz", + "integrity": "sha512-gMmEShwwq7FJqMwvfRwvCl00v4kN+KOfJqXn+f4nrufak5gNHJOksd/60Dvjuz7sI8Y5WiSFBa8FEYr+zoyqCw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -2253,34 +1607,21 @@ } }, "node_modules/body-parser": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", - "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", "license": "MIT", "dependencies": { "bytes": "^3.1.2", - "content-type": "^2.0.0", + "content-type": "^1.0.5", "debug": "^4.4.3", - "http-errors": "^2.0.1", - "iconv-lite": "^0.7.2", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", - "qs": "^6.15.2", - "raw-body": "^3.0.2", - "type-is": "^2.1.0" - }, - "engines": { - "node": ">=18" + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/body-parser/node_modules/content-type": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", - "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", - "license": "MIT", "engines": { "node": ">=18" }, @@ -2315,9 +1656,9 @@ } }, "node_modules/browserslist": { - "version": "4.28.9", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.9.tgz", - "integrity": "sha512-EWazOblFYUvlGZcfGhPUPmYh3nikUxBVb+y9MJun5f3hBi812X+8MSQTujLBtgK3cf51fJWbWfOjyeO954d+Eg==", + "version": "4.29.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.29.0.tgz", + "integrity": "sha512-3GSvyjvDI4Dur1Meg2BekJquu5uF+9R9a1+5M1Mde192eZoXbeXjzgOsgqPS2V8D5wrrip0gR5Hf/GhWQ9ZzaA==", "dev": true, "funding": [ { @@ -2335,11 +1676,11 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.11.20", + "baseline-browser-mapping": "^2.11.23", "caniuse-lite": "^1.0.30001810", - "electron-to-chromium": "^1.5.420", - "node-releases": "^2.0.54", - "update-browserslist-db": "^1.3.2" + "electron-to-chromium": "^1.5.427", + "node-releases": "^2.0.55", + "update-browserslist-db": "^1.3.3" }, "bin": { "browserslist": "cli.js" @@ -2855,9 +2196,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.426", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.426.tgz", - "integrity": "sha512-2Gcq6inCQs/AqfHP3f5ftCzk+pqeW2VlA1LgmPqEj2hfBO8HiZRspNYkD4HzFBe4u6lGqca4BspFr6Ix4Q400g==", + "version": "1.5.433", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.433.tgz", + "integrity": "sha512-5lCAbyZBjtmUt/RAGHRqrL2q0oEFRThDAsZHHDn9XHa89Qw7gMYOeSicBTy+AHfvo0r6vwsZvqNJTQIQy1BLzA==", "dev": true, "license": "ISC" }, @@ -3046,17 +2387,17 @@ } }, "node_modules/expect": { - "version": "30.5.1", - "resolved": "https://registry.npmjs.org/expect/-/expect-30.5.1.tgz", - "integrity": "sha512-m8YrYgvKe9+9gEnWEuKz+qCGfHqkrff7PPfyDnOFkjsfYRKqiYyOxDFMFieCGAuhtk/VNm63tvNgKZVzVy+Hvg==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/expect/-/expect-30.5.2.tgz", + "integrity": "sha512-0LNuMvs8/5mRYhnCR4k+lGV7fqPMs6Bnksda4S3zsCUmdxBpAn4zU4NNzXdq1pMKe/H4W5t/zVIDSJ/H3e0vDA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/expect-utils": "30.5.1", + "@jest/expect-utils": "30.5.2", "@jest/get-type": "30.5.0", - "jest-matcher-utils": "30.5.1", + "jest-matcher-utils": "30.5.2", "jest-message-util": "30.5.1", - "jest-mock": "30.5.1", + "jest-mock": "30.5.2", "jest-util": "30.5.1" }, "engines": { @@ -3127,9 +2468,9 @@ "license": "MIT" }, "node_modules/fast-uri": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.7.tgz", - "integrity": "sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==", + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.8.tgz", + "integrity": "sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==", "funding": [ { "type": "github", @@ -3229,17 +2570,17 @@ } }, "node_modules/form-data": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", - "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", "dev": true, "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.4", - "mime-types": "^2.1.35" + "hasown": "^2.0.2", + "mime-types": "^2.1.12" }, "engines": { "node": ">= 6" @@ -3304,21 +2645,6 @@ "node": ">= 0.8" } }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -3506,9 +2832,9 @@ } }, "node_modules/hasown": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", - "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -3836,16 +3162,16 @@ } }, "node_modules/jest": { - "version": "30.5.1", - "resolved": "https://registry.npmjs.org/jest/-/jest-30.5.1.tgz", - "integrity": "sha512-3qrR8+ZXFnn7y0H2yjWQNkGGBLBY4zTRoTMAq9zJcgwLLtlyonfsCLviIXK9xuE2KgIyM+M36AFcoK2DgFR36w==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest/-/jest-30.5.2.tgz", + "integrity": "sha512-3gnpdBIza8ijCl3iMV9ChE3hSt1+46865qqod863gQtJr1DixOkAU5DoGbi3u+XlRcw++BhZQMu/y4xBdHBvSQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/core": "30.5.1", + "@jest/core": "30.5.2", "@jest/types": "30.5.1", "import-local": "^3.2.0", - "jest-cli": "30.5.1" + "jest-cli": "30.5.2" }, "bin": { "jest": "bin/jest.js" @@ -3878,26 +3204,26 @@ } }, "node_modules/jest-circus": { - "version": "30.5.1", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.5.1.tgz", - "integrity": "sha512-NgliezXQ6yznqR4W5Gqw++0cZSbBZlF0NknNIAE5VmSJKWOtmWJmWnBq3TSkUOVBTPrT7FGGhVdA8DLWnxo2Sw==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.5.2.tgz", + "integrity": "sha512-vffFgjs5JSJ9q4ds1vNW3klKYPJfYfIY61LT/zaZgvTeASJOWZUt8LBfHgIcV4rwxlPBLl/ePBGccHEclS4PlQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.5.1", - "@jest/expect": "30.5.1", - "@jest/test-result": "30.5.1", + "@jest/environment": "30.5.2", + "@jest/expect": "30.5.2", + "@jest/test-result": "30.5.2", "@jest/types": "30.5.1", "@types/node": "*", "chalk": "^4.1.2", "co": "^4.6.0", "dedent": "^1.6.0", "is-generator-fn": "^2.1.0", - "jest-each": "30.5.1", - "jest-matcher-utils": "30.5.1", + "jest-each": "30.5.2", + "jest-matcher-utils": "30.5.2", "jest-message-util": "30.5.1", - "jest-runtime": "30.5.1", - "jest-snapshot": "30.5.1", + "jest-runtime": "30.5.2", + "jest-snapshot": "30.5.2", "jest-util": "30.5.1", "p-limit": "^3.1.0", "pretty-format": "30.5.1", @@ -3910,19 +3236,19 @@ } }, "node_modules/jest-cli": { - "version": "30.5.1", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.5.1.tgz", - "integrity": "sha512-uwNYepWgaNBCplm42fCIUZTf/tIEHIy5AYvx7eL1BrwIgyjPt+2poouR23UO2yopIP+kV8oZFHfv+l4pg/8prQ==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.5.2.tgz", + "integrity": "sha512-YYYxUUVjFgPvgEleKNKMaYGIFgR3l3832Cl0kRL/oCiDZ03v7wImUtquHl2OOBCCNgprjZgBN5bczS/JklPBDg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/core": "30.5.1", - "@jest/test-result": "30.5.1", + "@jest/core": "30.5.2", + "@jest/test-result": "30.5.2", "@jest/types": "30.5.1", "chalk": "^4.1.2", "exit-x": "^0.2.2", "import-local": "^3.2.0", - "jest-config": "30.5.1", + "jest-config": "30.5.2", "jest-util": "30.5.1", "jest-validate": "30.5.1", "yargs": "^17.7.2" @@ -3943,29 +3269,29 @@ } }, "node_modules/jest-config": { - "version": "30.5.1", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.5.1.tgz", - "integrity": "sha512-L8PKM2X/ngG8PxfLMqglKGZjylPgw84bVPUlMY9W/o76TnLqPWrmQgsaT0HrheGdRtpGE5FbmREA0Kvb+Qx5gQ==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.5.2.tgz", + "integrity": "sha512-U6221OZekabePvGcGPf4raIBKMvYQWFHvhobDJRojOYaFMj2HaGytAdknjDhYc6JTz2fdHW9+6k0wWUDaNeOFQ==", "dev": true, "license": "MIT", "dependencies": { "@babel/core": "^7.27.4", "@jest/get-type": "30.5.0", "@jest/pattern": "30.5.0", - "@jest/test-sequencer": "30.5.1", + "@jest/test-sequencer": "30.5.2", "@jest/types": "30.5.1", - "babel-jest": "30.5.1", + "babel-jest": "30.5.2", "chalk": "^4.1.2", "ci-info": "^4.2.0", "deepmerge": "^4.3.1", "glob": "^13.0.6", "graceful-fs": "^4.2.11", - "jest-circus": "30.5.1", + "jest-circus": "30.5.2", "jest-docblock": "30.5.0", - "jest-environment-node": "30.5.1", + "jest-environment-node": "30.5.2", "jest-regex-util": "30.5.0", "jest-resolve": "30.5.1", - "jest-runner": "30.5.1", + "jest-runner": "30.5.2", "jest-util": "30.5.1", "jest-validate": "30.5.1", "parse-json": "^5.2.0", @@ -3994,9 +3320,9 @@ } }, "node_modules/jest-diff": { - "version": "30.5.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.5.1.tgz", - "integrity": "sha512-e3cNNMpv8Kh20MjjphTXs+3Vz7DQyLM1nft7KJhnh46atFhjVJRa+0Hq0beywuwsACtMQUBihQkFl8zxb7gt1Q==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.5.2.tgz", + "integrity": "sha512-rBtHnI6BWTiWj3lM7fOLVfChx7R9B0u9VV3PRHawKs79x6ZjW1QoSrKenaigNGWlvhtoi2BqvDbABDie+Os3xQ==", "dev": true, "license": "MIT", "dependencies": { @@ -4023,15 +3349,16 @@ } }, "node_modules/jest-each": { - "version": "30.5.1", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.5.1.tgz", - "integrity": "sha512-S1af0TU4v1EZ/AUlkFs/sxf/5KGsbAT9kRgdyoMX/x72y7C8ZEgETE5o1TPnbKRnrbprc5FR/moYLfdRmqEjsQ==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.5.2.tgz", + "integrity": "sha512-GRrW+7fmmgmkvyfEBqc1QFdWnF1Ex5yG6Y6AWL4TTxnkWlKIT98dsKh3P3UuWm6v4XsQwVE6DEzXIPMzWJ3ZbA==", "dev": true, "license": "MIT", "dependencies": { "@jest/get-type": "30.5.0", "@jest/types": "30.5.1", "chalk": "^4.1.2", + "jest-regex-util": "30.5.0", "jest-util": "30.5.1", "pretty-format": "30.5.1" }, @@ -4040,17 +3367,17 @@ } }, "node_modules/jest-environment-node": { - "version": "30.5.1", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.5.1.tgz", - "integrity": "sha512-LrPj3sPMjsQoOB3jrb8p/sa+XkSFNKo60TTsyc+EB2kxQJHgbwElpXnx1yX25fdFn5958FIObRtcLSHyV8VIAw==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.5.2.tgz", + "integrity": "sha512-Ciz4KgTGIbojxSKpuFImgWJj5EyqfE8xMq9NxWmOXnQywHHS03H9Mv+vaCB29J5pr3JBrY27WbRV18rBdjh8Dg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.5.1", - "@jest/fake-timers": "30.5.1", + "@jest/environment": "30.5.2", + "@jest/fake-timers": "30.5.2", "@jest/types": "30.5.1", "@types/node": "*", - "jest-mock": "30.5.1", + "jest-mock": "30.5.2", "jest-util": "30.5.1", "jest-validate": "30.5.1" }, @@ -4127,15 +3454,15 @@ } }, "node_modules/jest-matcher-utils": { - "version": "30.5.1", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.5.1.tgz", - "integrity": "sha512-aroZVqwOz/wC2y6pC+obgFWKV9viaQWQTTSB6W5H55+egtUczIfXR/rxExTv92xD/ADYwpqaYfVWx1aqwKg7FA==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.5.2.tgz", + "integrity": "sha512-lFkB365PTIHOhPzwrb9T6gvlDnPpOZ7/c3ewOKKB7bzztqjVrtlyRL2MkyfJXpyY2u2SKw001JKrTx14fBUFcQ==", "dev": true, "license": "MIT", "dependencies": { "@jest/get-type": "30.5.0", "chalk": "^4.1.2", - "jest-diff": "30.5.1", + "jest-diff": "30.5.2", "pretty-format": "30.5.1" }, "engines": { @@ -4178,13 +3505,13 @@ } }, "node_modules/jest-mock": { - "version": "30.5.1", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.5.1.tgz", - "integrity": "sha512-9fVjc3leUpGID2/by/LU4Dvdcp7PFh9LlxS3QRWK3ABm+KtvEVsG/AEGeLY3gKOZsjkBxyfwGltoAVlW7dygHg==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.5.2.tgz", + "integrity": "sha512-KTHb37Fhj8xY8qPFvTFaFJHcdsumQp+ExsUlgoX232Agmc706gagBs53+CYvulV7MVQo9AgXOUxsqzzVBoJlrA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/expect-utils": "30.5.1", + "@jest/expect-utils": "30.5.2", "@jest/types": "30.5.1", "@types/node": "*", "jest-util": "30.5.1" @@ -4223,31 +3550,31 @@ } }, "node_modules/jest-resolve-dependencies": { - "version": "30.5.1", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.5.1.tgz", - "integrity": "sha512-JKpXGONDcTaunrNVn8KCl6qAnwl06jIkvv70lhq0Ze47lsPx9sH5HoeEUiXX6iFGnliHN/qpIbQ0l38wrVmXaw==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.5.2.tgz", + "integrity": "sha512-GMI0DP5enX9Z2qW2zFy1bOkrCfWAOPRJQ7qHt0pdfrxBPsgWxglMPrRSK2TX/wRWYxcPRSLFg3Z88qPOzHx7FQ==", "dev": true, "license": "MIT", "dependencies": { "jest-regex-util": "30.5.0", - "jest-snapshot": "30.5.1" + "jest-snapshot": "30.5.2" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-runner": { - "version": "30.5.1", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.5.1.tgz", - "integrity": "sha512-FPlQE4+mwnFxXmpPrSi836KV2ZzvK1g6/nPCT8o5BcoDUUNJQeHo1/Qdkoe/QeoL4M7OeJpbnGUhYKhC1VMdaQ==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.5.2.tgz", + "integrity": "sha512-/B6px7hiiNhVSRwuv9AGn0nawYvwsHeQZ3ItTd5Gp2diFd0EOKaWVeWqcbw3L88vx1GfpCKjQkxWItZCTl11Rw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "30.5.1", - "@jest/environment": "30.5.1", - "@jest/source-map": "30.5.0", - "@jest/test-result": "30.5.1", - "@jest/transform": "30.5.1", + "@jest/console": "30.5.2", + "@jest/environment": "30.5.2", + "@jest/source-map": "30.5.2", + "@jest/test-result": "30.5.2", + "@jest/transform": "30.5.2", "@jest/types": "30.5.1", "@types/node": "*", "chalk": "^4.1.2", @@ -4255,14 +3582,14 @@ "exit-x": "^0.2.2", "graceful-fs": "^4.2.11", "jest-docblock": "30.5.0", - "jest-environment-node": "30.5.1", + "jest-environment-node": "30.5.2", "jest-haste-map": "30.5.1", "jest-leak-detector": "30.5.1", "jest-message-util": "30.5.1", "jest-resolve": "30.5.1", - "jest-runtime": "30.5.1", + "jest-runtime": "30.5.2", "jest-util": "30.5.1", - "jest-watcher": "30.5.1", + "jest-watcher": "30.5.2", "jest-worker": "30.5.1", "p-limit": "^3.1.0" }, @@ -4271,18 +3598,18 @@ } }, "node_modules/jest-runtime": { - "version": "30.5.1", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.5.1.tgz", - "integrity": "sha512-UB88+NRkK2Tw/OqV7dofYcyiUGrVZtD41k/N0xQOs9fG//57XHK7JLWG52HD1UYpUAhjK4xm6DBvFX3yWudsMA==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.5.2.tgz", + "integrity": "sha512-0paUAHBlheai10A3XSy9Xh+YBG0IROy+/qzcDPzQ/nbbXrznQzeWy5qniICWK6d3W3scaRQCVAbXdcK0ibBq4w==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.5.1", - "@jest/fake-timers": "30.5.1", - "@jest/globals": "30.5.1", - "@jest/source-map": "30.5.0", - "@jest/test-result": "30.5.1", - "@jest/transform": "30.5.1", + "@jest/environment": "30.5.2", + "@jest/fake-timers": "30.5.2", + "@jest/globals": "30.5.2", + "@jest/source-map": "30.5.2", + "@jest/test-result": "30.5.2", + "@jest/transform": "30.5.2", "@jest/types": "30.5.1", "@types/node": "*", "chalk": "^4.1.2", @@ -4293,10 +3620,10 @@ "graceful-fs": "^4.2.11", "jest-haste-map": "30.5.1", "jest-message-util": "30.5.1", - "jest-mock": "30.5.1", + "jest-mock": "30.5.2", "jest-regex-util": "30.5.0", "jest-resolve": "30.5.1", - "jest-snapshot": "30.5.1", + "jest-snapshot": "30.5.2", "jest-util": "30.5.1", "slash": "^3.0.0", "strip-bom": "^4.0.0" @@ -4306,9 +3633,9 @@ } }, "node_modules/jest-snapshot": { - "version": "30.5.1", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.5.1.tgz", - "integrity": "sha512-cNWFdSb5xuDGl8hKkAZJ3YtI/PzHpAPFV+HUXWIOG8rMhpDTLVbvAc2d2wRicoYw2wGsJGLaurJT6BLC97bLXQ==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.5.2.tgz", + "integrity": "sha512-jKIdes25AnAH73dOCi2qE6J6vHJ6L2vyBS11M89kT50DVJcLTf2AOjvcc+2vVaN8lUbY5v2R83aqXo3Ro9fqYA==", "dev": true, "license": "MIT", "dependencies": { @@ -4317,17 +3644,17 @@ "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.27.1", "@babel/types": "^7.27.3", - "@jest/expect-utils": "30.5.1", + "@jest/expect-utils": "30.5.2", "@jest/get-type": "30.5.0", "@jest/snapshot-utils": "30.5.1", - "@jest/transform": "30.5.1", + "@jest/transform": "30.5.2", "@jest/types": "30.5.1", "babel-preset-current-node-syntax": "^1.2.0", "chalk": "^4.1.2", - "expect": "30.5.1", + "expect": "30.5.2", "graceful-fs": "^4.2.11", - "jest-diff": "30.5.1", - "jest-matcher-utils": "30.5.1", + "jest-diff": "30.5.2", + "jest-matcher-utils": "30.5.2", "jest-message-util": "30.5.1", "jest-util": "30.5.1", "pretty-format": "30.5.1", @@ -4401,13 +3728,13 @@ } }, "node_modules/jest-watcher": { - "version": "30.5.1", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.5.1.tgz", - "integrity": "sha512-+FHJ7C+S7b3ySfhA1aFmoa6TztsnwZVv84ycPRn0tVN93np2EU4b8C/KadqA3l6igdjgLBTnUotab9ER0HLG8A==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.5.2.tgz", + "integrity": "sha512-rqRgg4R11GA9m1UsEZwQeixHsgZReCn6TOHAHtIH33yXrIvoX0OGPA20Yzzjkz7X40fZvcRSxEpQppYPcWzmcQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/test-result": "30.5.1", + "@jest/test-result": "30.5.2", "@jest/types": "30.5.1", "@types/node": "*", "ansi-escapes": "^4.3.2", @@ -4811,9 +4138,9 @@ "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.55", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.55.tgz", - "integrity": "sha512-mIrE/Cw9y+9Au6dS5vDKDhQza9YvG6w+ZrS6X+ZzA7yFW/soAeaups4Qzn1bL6g5FVy8WtP79+0j82oPIbqRjQ==", + "version": "2.0.56", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.56.tgz", + "integrity": "sha512-x0InOIyzgdk+eyaWaRJFH5snEtiImgBgblZ2CyPrLmqqcuMQkEvcDPHbzqbD8eDsSeJbVOjn+crzyzHaM4D+/A==", "dev": true, "license": "MIT", "engines": { @@ -5062,9 +4389,9 @@ } }, "node_modules/path-scurry/node_modules/lru-cache": { - "version": "11.5.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", - "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", + "version": "11.5.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.3.tgz", + "integrity": "sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==", "license": "BlueOak-1.0.0", "engines": { "node": "20 || >=22" @@ -5500,9 +4827,9 @@ "license": "BSD-3-Clause" }, "node_modules/sql-escaper": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/sql-escaper/-/sql-escaper-1.5.1.tgz", - "integrity": "sha512-4toX5E1fQbBrpfXidaHnF0669nkAdETeIPTs2SUjxxD7RRIs9ICG4gtpmfc68JCEKehsdwLFqBu9VlQqZ1P1gg==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/sql-escaper/-/sql-escaper-1.5.2.tgz", + "integrity": "sha512-6CKD38c31SENivxOADeMNLdukOnUxUcflKtzVWzace7Riv1v7cAEym5Cx9Q7gYZ3ezIJI7ZpqecQq8cqNeSSFg==", "license": "MIT", "engines": { "bun": ">=1.0.0", @@ -5841,9 +5168,9 @@ "license": "MIT" }, "node_modules/test-exclude/node_modules/brace-expansion": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", - "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz", + "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==", "dev": true, "license": "MIT", "dependencies": { @@ -6019,14 +5346,6 @@ "nodetouch": "bin/nodetouch.js" } }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD", - "optional": true - }, "node_modules/type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", @@ -6051,34 +5370,17 @@ } }, "node_modules/type-is": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", - "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", "license": "MIT", "dependencies": { - "content-type": "^2.0.0", + "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" }, "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/type-is/node_modules/content-type": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", - "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "node": ">= 0.6" } }, "node_modules/undefsafe": { @@ -6142,9 +5444,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.2.tgz", - "integrity": "sha512-UQ+MSxlhRm1bzjhU+DcuXfjFO1FzNtqhK5+9Yvlp90ItDLk5vT932A0rFu619nf7RVS+Y/VeaUW1jaRDqZ8VJw==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.3.tgz", + "integrity": "sha512-pJ2sYawQS0R/WI928Gj5GlPhTGzbMelq0+4INtSYNDV9ErKJcX6xjGWkoG/VnB3dpUm00zALaqkrUD77pO5TDQ==", "dev": true, "funding": [ { diff --git a/backend/package.json b/backend/package.json index 51608b7b..920af796 100644 --- a/backend/package.json +++ b/backend/package.json @@ -7,7 +7,7 @@ "scripts": { "start": "node index.js", "dev": "nodemon index.js", - "test": "NODE_OPTIONS='--experimental-vm-modules' jest" + "test": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js" }, "keywords": [], "author": "", diff --git a/backend/tests/api.test.js b/backend/tests/api.test.js deleted file mode 100644 index d391fd73..00000000 --- a/backend/tests/api.test.js +++ /dev/null @@ -1,18 +0,0 @@ -import request from 'supertest'; -import app from '../index.js'; - -describe('API Endpoints', () => { - test('GET /getSupportedDistros should return distribution data', async () => { - const res = await request(app).get('/getSupportedDistros'); - expect(res.statusCode).toEqual(200); - expect(res.body).toHaveProperty('Fedora'); - expect(res.body.Fedora).toHaveProperty('Fedora 40'); - }); - - test('GET /searchPackages without search term should return empty results', async () => { - const res = await request(app).get('/searchPackages'); - expect(res.statusCode).toEqual(200); - expect(res.body.total_packages).toEqual(0); - expect(res.body.packages).toEqual([]); - }); -}); diff --git a/backend/tests/getSupportedDistros.test.js b/backend/tests/getSupportedDistros.test.js new file mode 100644 index 00000000..2383889c --- /dev/null +++ b/backend/tests/getSupportedDistros.test.js @@ -0,0 +1,36 @@ +import request from 'supertest'; +import { createApp } from '../app.js'; +import { createMockPool } from './helpers/mockPool.js'; + +describe('GET /getSupportedDistros', () => { + const app = createApp(createMockPool()); + + test('returns distribution bitmask data', async () => { + const res = await request(app).get('/getSupportedDistros'); + + expect(res.statusCode).toEqual(200); + expect(res.body).toHaveProperty('Fedora'); + expect(res.body.Fedora).toHaveProperty('Fedora 42'); + expect(typeof res.body.Fedora['Fedora 42']).toBe('string'); + }); + + test('sets no-cache response headers', async () => { + const res = await request(app).get('/getSupportedDistros'); + + expect(res.headers['cache-control']).toBe('no-cache, no-store, must-revalidate'); + expect(res.headers.pragma).toBe('no-cache'); + expect(res.headers.expires).toBe('0'); + }); +}); + +describe('GET /sdt/getSupportedDistros', () => { + const app = createApp(createMockPool()); + + test('returns the same distribution data on the legacy path', async () => { + const res = await request(app).get('/sdt/getSupportedDistros'); + + expect(res.statusCode).toEqual(200); + expect(res.body).toHaveProperty('Debian'); + expect(res.body.Debian).toHaveProperty('12(Bookworm)'); + }); +}); diff --git a/backend/tests/helpers/mockPool.js b/backend/tests/helpers/mockPool.js new file mode 100644 index 00000000..25512814 --- /dev/null +++ b/backend/tests/helpers/mockPool.js @@ -0,0 +1,30 @@ +import { jest } from '@jest/globals'; + +/** + * Create a mock MySQL pool for integration tests. + */ +export const createMockPool = (queryImpl = defaultQueryImpl) => ({ + query: jest.fn(queryImpl) +}); + +const defaultQueryImpl = async (sql) => { + if (sql.includes('COUNT(*)')) { + return [[{ total: 0 }]]; + } + return [[]]; +}; + +export const mockSearchResults = (total, packages = []) => { + return async (sql) => { + if (sql.includes('COUNT(*)')) { + return [[{ total }]]; + } + return [packages]; + }; +}; + +export const mockDbFailure = () => { + return async () => { + throw new Error('Database connection failed'); + }; +}; diff --git a/backend/tests/searchPackages.test.js b/backend/tests/searchPackages.test.js new file mode 100644 index 00000000..6060595d --- /dev/null +++ b/backend/tests/searchPackages.test.js @@ -0,0 +1,128 @@ +import request from 'supertest'; +import { createApp } from '../app.js'; +import { createMockPool, mockDbFailure, mockSearchResults } from './helpers/mockPool.js'; + +describe('GET /searchPackages', () => { + test('returns empty results when search_term is missing', async () => { + const app = createApp(createMockPool()); + const res = await request(app).get('/searchPackages'); + + expect(res.statusCode).toEqual(200); + expect(res.body).toEqual({ + total_packages: 0, + current_page: 0, + last_page: 0, + more_available: false, + packages: [] + }); + }); + + test('returns empty results when search_term is blank', async () => { + const app = createApp(createMockPool()); + const res = await request(app).get('/searchPackages?search_term=%20%20'); + + expect(res.statusCode).toEqual(200); + expect(res.body.total_packages).toEqual(0); + expect(res.body.packages).toEqual([]); + }); + + test('returns 400 for invalid page_number', async () => { + const app = createApp(createMockPool()); + const res = await request(app).get('/searchPackages?search_term=nginx&page_number=-1'); + + expect(res.statusCode).toEqual(400); + expect(res.body).toEqual({ error: 'Invalid page_number' }); + }); + + test('returns 400 for non-numeric page_number', async () => { + const app = createApp(createMockPool()); + const res = await request(app).get('/searchPackages?search_term=nginx&page_number=abc'); + + expect(res.statusCode).toEqual(400); + expect(res.body).toEqual({ error: 'Invalid page_number' }); + }); + + test('returns 400 for invalid limit', async () => { + const app = createApp(createMockPool()); + const res = await request(app).get('/searchPackages?search_term=nginx&limit=0'); + + expect(res.statusCode).toEqual(400); + expect(res.body).toEqual({ error: 'Invalid limit' }); + }); + + test('returns 400 for invalid search_bit_flag', async () => { + const app = createApp(createMockPool()); + const res = await request(app).get('/searchPackages?search_term=nginx&search_bit_flag=not-a-number'); + + expect(res.statusCode).toEqual(400); + expect(res.body).toEqual({ error: 'Invalid search_bit_flag' }); + }); + + test('returns empty results when no distros match the search bitmask', async () => { + const app = createApp(createMockPool()); + const res = await request(app).get('/searchPackages?search_term=nginx&search_bit_flag=0'); + + expect(res.statusCode).toEqual(200); + expect(res.body).toEqual({ + total_packages: 0, + current_page: 0, + last_page: 0, + more_available: false, + packages: [] + }); + }); + + test('returns paginated package results from the database', async () => { + const packages = [ + { + packageName: 'nginx', + description: 'High performance web server', + version: '1.24.0', + osName: 'Fedora 42' + } + ]; + const pool = createMockPool(mockSearchResults(1, packages)); + const app = createApp(pool); + + const res = await request(app) + .get('/searchPackages') + .query({ + search_term: 'nginx', + search_bit_flag: '2048', + page_number: '0', + limit: '10' + }); + + expect(res.statusCode).toEqual(200); + expect(res.body.total_packages).toEqual(1); + expect(res.body.current_page).toEqual(0); + expect(res.body.last_page).toEqual(1); + expect(res.body.more_available).toEqual(false); + expect(res.body.packages).toEqual(packages); + expect(pool.query).toHaveBeenCalledTimes(2); + }); + + test('returns 500 when the database query fails', async () => { + const app = createApp(createMockPool(mockDbFailure())); + + const res = await request(app) + .get('/searchPackages') + .query({ + search_term: 'nginx', + search_bit_flag: '2048' + }); + + expect(res.statusCode).toEqual(500); + expect(res.body).toEqual({ error: 'Internal server error' }); + }); +}); + +describe('GET /sdt/searchPackages', () => { + test('supports the legacy search path', async () => { + const app = createApp(createMockPool()); + const res = await request(app).get('/sdt/searchPackages'); + + expect(res.statusCode).toEqual(200); + expect(res.body.packages).toEqual([]); + }); +}); diff --git a/backend/tests/setup.js b/backend/tests/setup.js new file mode 100644 index 00000000..f7534b31 --- /dev/null +++ b/backend/tests/setup.js @@ -0,0 +1 @@ +process.env.NODE_ENV = 'test'; diff --git a/backend/tests/utils.test.js b/backend/tests/utils.test.js new file mode 100644 index 00000000..00df5b8f --- /dev/null +++ b/backend/tests/utils.test.js @@ -0,0 +1,59 @@ +import { buildDistroBitMap, getTables, stringifyBigInts } from '../utils.js'; +import { SUPPORTED_DISTROS } from '../config.js'; + +describe('buildDistroBitMap', () => { + test('assigns sequential powers of two to each distro version', () => { + const sampleDistros = { + Alpha: { 'Alpha 1': 'alpha_1', 'Alpha 2': 'alpha_2' }, + Beta: { 'Beta 1': 'beta_1' } + }; + + const bitMap = buildDistroBitMap(sampleDistros); + + expect(bitMap.Alpha['Alpha 1']).toBe(1n); + expect(bitMap.Alpha['Alpha 2']).toBe(2n); + expect(bitMap.Beta['Beta 1']).toBe(4n); + }); + + test('includes configured distros from distros.json', () => { + const bitMap = buildDistroBitMap(SUPPORTED_DISTROS); + + expect(bitMap).toHaveProperty('Fedora'); + expect(Object.keys(bitMap.Fedora).length).toBeGreaterThan(0); + }); +}); + +describe('getTables', () => { + const sampleDistros = { + Alpha: { 'Alpha 1': 'alpha_1', 'Alpha 2': 'alpha_2' }, + Beta: { 'Beta 1': 'beta_1' } + }; + const bitMap = buildDistroBitMap(sampleDistros); + + test('returns all tables when all bits are set', () => { + const tables = getTables('7', bitMap, sampleDistros); + expect(tables).toEqual(['alpha_1', 'alpha_2', 'beta_1']); + }); + + test('returns only tables matching the search bitmask', () => { + const tables = getTables('2', bitMap, sampleDistros); + expect(tables).toEqual(['alpha_2']); + }); + + test('returns an empty list when no bits match', () => { + const tables = getTables('0', bitMap, sampleDistros); + expect(tables).toEqual([]); + }); +}); + +describe('stringifyBigInts', () => { + test('converts BigInt values to strings', () => { + const result = stringifyBigInts({ flag: 42n, nested: { value: 7n } }); + expect(result).toEqual({ flag: '42', nested: { value: '7' } }); + }); + + test('leaves non-BigInt values unchanged', () => { + const result = stringifyBigInts({ name: 'Fedora', count: 3 }); + expect(result).toEqual({ name: 'Fedora', count: 3 }); + }); +}); diff --git a/backend/utils.js b/backend/utils.js new file mode 100644 index 00000000..f8c0e63c --- /dev/null +++ b/backend/utils.js @@ -0,0 +1,48 @@ +/** + * Build a bitmask map of supported distributions and versions. + */ +export const buildDistroBitMap = (supportedDistros) => { + const distroBitMap = {}; + let bitFlag = 1n; + + for (const distroName of Object.keys(supportedDistros)) { + const versions = Object.keys(supportedDistros[distroName]).sort(); + for (const version of versions) { + if (!distroBitMap[distroName]) { + distroBitMap[distroName] = {}; + } + distroBitMap[distroName][version] = bitFlag; + bitFlag *= 2n; + } + } + + return distroBitMap; +}; + +/** + * Return database table names matching the given search bitmask. + */ +export const getTables = (searchBit, distroBitMap, supportedDistros) => { + const tables = []; + + for (const distroName of Object.keys(supportedDistros)) { + const versions = Object.keys(supportedDistros[distroName]).sort(); + for (const version of versions) { + const bit = distroBitMap[distroName][version]; + if ((bit & BigInt(searchBit)) > 0n) { + tables.push(supportedDistros[distroName][version]); + } + } + } + + return tables; +}; + +/** + * Serialize objects containing BigInt values to JSON-safe form. + */ +export const stringifyBigInts = (obj) => { + return JSON.parse(JSON.stringify(obj, (key, value) => + typeof value === 'bigint' ? value.toString() : value + )); +};