From 4f9d626c21aa6ab61baa06e4f5e16bb6ae5eb14e Mon Sep 17 00:00:00 2001 From: Baha Date: Mon, 6 Dec 2021 07:36:19 +0000 Subject: [PATCH 01/11] post release version(v1.1.3) updated to v1.1.4 travis.yml file format and stage names fixed --- .travis.yml | 14 ++++++++------ openapi/openapi.yml | 2 +- package.json | 2 +- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index 17a970a..594b0e8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,33 +23,35 @@ script: - npm run build jobs: include: - - stage: test + - stage: lint name: lint script: npm run lint - name: openapi + - stage: test openapi + name: test-openapi script: npm run test:openapi + - stage: generate openapi name: openapi typescript client generation script: bash client/generate.sh - stage: build name: docker test build script: /bin/bash travis/docker-functions.sh docker_build $VERSION node_js: 10 - - stage: publish + - stage: publish alpha docker name: docker publish alpha script: /bin/bash travis/docker-functions.sh docker_build $VERSION publish if: branch = env(DEV_BRANCH) AND type = push node_js: 10 - - stage: publish + - stage: publish alpha npm name: openapi typescript client alpha script: bash client/generate.sh publish if: branch = env(DEV_BRANCH) AND type = push node_js: 10 - - stage: release + - stage: release docker name: docker publish release script: /bin/bash travis/docker-functions.sh docker_build $VERSION release if: branch = env(RELEASE_BRANCH) AND type = api AND commit_message = env(RELEASE_MESSAGE) node_js: 10 - - stage: release + - stage: release npm name: openapi typescript client release script: bash client/generate.sh release if: branch = env(RELEASE_BRANCH) AND type = api AND commit_message = env(RELEASE_MESSAGE) diff --git a/openapi/openapi.yml b/openapi/openapi.yml index 31a64dc..cc8a21f 100644 --- a/openapi/openapi.yml +++ b/openapi/openapi.yml @@ -1,7 +1,7 @@ openapi: 3.0.3 info: title: Symbol Statistic Service API Documentation - version: '1.1.3' + version: '1.1.4' servers: - url: 'https://testnet.symbol.services' description: Testnet server diff --git a/package.json b/package.json index 2d82dff..aae1727 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "symbol-statistics-service", - "version": "1.1.3", + "version": "1.1.4", "description": "", "scripts": { "dev": "nodemon", From 0e7f10660d44caccaf1f966d12d74925dc6ab2bb Mon Sep 17 00:00:00 2001 From: Baha Date: Sun, 26 Dec 2021 20:17:22 +0000 Subject: [PATCH 02/11] fix: node discovery extended, empty host info bug fixed, logging enhanced --- package-lock.json | 29 +++++++- package.json | 2 + src/config/config.json | 1 + src/config/index.ts | 5 ++ src/models/Node.ts | 5 +- src/services/ApiNodeService.ts | 8 +- src/services/ChainHeightMonitor.ts | 2 +- src/services/DataBase.ts | 16 ++-- src/services/HostInfo.ts | 2 +- src/services/NodeMonitor.ts | 113 +++++++++++++++++++++-------- src/utils.ts | 5 ++ 11 files changed, 143 insertions(+), 45 deletions(-) diff --git a/package-lock.json b/package-lock.json index 53e0e21..c069b6a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,12 @@ { "name": "symbol-statistics-service", - "version": "1.1.3", + "version": "1.1.4", "lockfileVersion": 2, "requires": true, "packages": { "": { - "version": "1.1.3", + "name": "symbol-statistics-service", + "version": "1.1.4", "dependencies": { "@types/cors": "^2.8.6", "@types/express": "^4.17.6", @@ -15,6 +16,7 @@ "cors": "^2.8.5", "dotenv": "^8.2.0", "express": "^4.17.1", + "humanize-duration": "^3.27.1", "module-alias": "^2.2.2", "mongoose": "^5.9.16", "symbol-sdk": "^1.0.3", @@ -25,6 +27,7 @@ }, "devDependencies": { "@openapitools/openapi-generator-cli": "^2.4.15", + "@types/humanize-duration": "^3.27.0", "@types/mongoose": "^5.7.14", "@types/node": "^14.14.10", "@types/winston": "^2.4.4", @@ -518,6 +521,12 @@ "@types/range-parser": "*" } }, + "node_modules/@types/humanize-duration": { + "version": "3.27.0", + "resolved": "https://registry.npmjs.org/@types/humanize-duration/-/humanize-duration-3.27.0.tgz", + "integrity": "sha512-ivv1EIdXz20vHPB9xftPvogUEjGLSSlgz2fipK2yyHQZvZeIgUQBZc23Kcuxa4zGbiUcRtr36Sw96CF+TO30Fw==", + "dev": true + }, "node_modules/@types/json-schema": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz", @@ -2960,6 +2969,11 @@ "integrity": "sha512-EC2utToWl4RKfs5zd36Mxq7nzHHBuomZboI0yYL6Y0RmBgT7Sgkq4rQ0ezFTYoIsSs7Tm9SJe+o2FcAg6GBhGA==", "dev": true }, + "node_modules/humanize-duration": { + "version": "3.27.1", + "resolved": "https://registry.npmjs.org/humanize-duration/-/humanize-duration-3.27.1.tgz", + "integrity": "sha512-jCVkMl+EaM80rrMrAPl96SGG4NRac53UyI1o/yAzebDntEY6K6/Fj2HOjdPg8omTqIe5Y0wPBai2q5xXrIbarA==" + }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -6683,6 +6697,12 @@ "@types/range-parser": "*" } }, + "@types/humanize-duration": { + "version": "3.27.0", + "resolved": "https://registry.npmjs.org/@types/humanize-duration/-/humanize-duration-3.27.0.tgz", + "integrity": "sha512-ivv1EIdXz20vHPB9xftPvogUEjGLSSlgz2fipK2yyHQZvZeIgUQBZc23Kcuxa4zGbiUcRtr36Sw96CF+TO30Fw==", + "dev": true + }, "@types/json-schema": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz", @@ -8534,6 +8554,11 @@ "integrity": "sha512-EC2utToWl4RKfs5zd36Mxq7nzHHBuomZboI0yYL6Y0RmBgT7Sgkq4rQ0ezFTYoIsSs7Tm9SJe+o2FcAg6GBhGA==", "dev": true }, + "humanize-duration": { + "version": "3.27.1", + "resolved": "https://registry.npmjs.org/humanize-duration/-/humanize-duration-3.27.1.tgz", + "integrity": "sha512-jCVkMl+EaM80rrMrAPl96SGG4NRac53UyI1o/yAzebDntEY6K6/Fj2HOjdPg8omTqIe5Y0wPBai2q5xXrIbarA==" + }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", diff --git a/package.json b/package.json index aae1727..e28de5c 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ }, "devDependencies": { "@openapitools/openapi-generator-cli": "^2.4.15", + "@types/humanize-duration": "^3.27.0", "@types/mongoose": "^5.7.14", "@types/node": "^14.14.10", "@types/winston": "^2.4.4", @@ -49,6 +50,7 @@ "cors": "^2.8.5", "dotenv": "^8.2.0", "express": "^4.17.1", + "humanize-duration": "^3.27.1", "module-alias": "^2.2.2", "mongoose": "^5.9.16", "symbol-sdk": "^1.0.3", diff --git a/src/config/config.json b/src/config/config.json index f9dc274..be448ae 100644 --- a/src/config/config.json +++ b/src/config/config.json @@ -17,6 +17,7 @@ "PEER_NODE_PORT": 7900, "REQUEST_TIMEOUT": 5000, "NUMBER_OF_NODE_REQUEST_CHUNK": 10, + "NODE_PEERS_REQUEST_CHUNK_SIZE": 50, "PREFERRED_NODES": ["*.symboldev.network"], "MIN_PARTNER_NODE_VERSION": 16777728 } diff --git a/src/config/index.ts b/src/config/index.ts index 4724bab..233456e 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -18,6 +18,7 @@ interface Symbol { interface Monitor { NODE_MONITOR_SCHEDULE_INTERVAL: number; NUMBER_OF_NODE_REQUEST_CHUNK: number; + NODE_PEERS_REQUEST_CHUNK_SIZE: number; CHAIN_HEIGHT_MONITOR_SCHEDULE_INTERVAL: number; GEOLOCATION_MONITOR_SCHEDULE_INTERVAL: number; API_NODE_PORT: number; @@ -49,6 +50,7 @@ export const symbol: Symbol = { export const monitor: Monitor = { NODE_MONITOR_SCHEDULE_INTERVAL: Number(process.env.NODE_MONITOR_SCHEDULE_INTERVAL) || config.NODE_MONITOR_SCHEDULE_INTERVAL, NUMBER_OF_NODE_REQUEST_CHUNK: Number(process.env.NUMBER_OF_NODE_REQUEST_CHUNK) || config.NUMBER_OF_NODE_REQUEST_CHUNK, + NODE_PEERS_REQUEST_CHUNK_SIZE: Number(process.env.NODE_PEERS_REQUEST_CHUNK_SIZE) || config.NODE_PEERS_REQUEST_CHUNK_SIZE, CHAIN_HEIGHT_MONITOR_SCHEDULE_INTERVAL: Number(process.env.CHAIN_HEIGHT_MONITOR_SCHEDULE_INTERVAL) || config.CHAIN_HEIGHT_MONITOR_SCHEDULE_INTERVAL, GEOLOCATION_MONITOR_SCHEDULE_INTERVAL: @@ -85,6 +87,9 @@ export const verifyConfig = (cfg: Config): boolean => { if (isNaN(cfg.monitor.NUMBER_OF_NODE_REQUEST_CHUNK) || cfg.monitor.NUMBER_OF_NODE_REQUEST_CHUNK < 0) error = 'Invalid "NUMBER_OF_NODE_REQUEST_CHUNK"'; + if (isNaN(cfg.monitor.NODE_PEERS_REQUEST_CHUNK_SIZE) || cfg.monitor.NODE_PEERS_REQUEST_CHUNK_SIZE < 0) + error = 'Invalid "NODE_PEERS_REQUEST_CHUNK_SIZE"'; + if (isNaN(cfg.monitor.API_NODE_PORT) || cfg.monitor.API_NODE_PORT <= 0 || cfg.monitor.API_NODE_PORT >= 10000) error = 'Invalid "API_NODE_PORT"'; diff --git a/src/models/Node.ts b/src/models/Node.ts index 014c92f..580fe41 100644 --- a/src/models/Node.ts +++ b/src/models/Node.ts @@ -50,6 +50,7 @@ const NodeSchema: Schema = new Schema({ type: Number, required: false, }, + required: false, }, apiStatus: { webSocket: { @@ -236,7 +237,9 @@ NodeSchema.set('toObject', { export const Node = mongoose.model('Node', NodeSchema); export const validateNodeModel = (node: any): boolean => { - if (!node || typeof node !== 'object') return false; + if (!node || typeof node !== 'object') { + return false; + } return !new Node(node).validateSync(); }; diff --git a/src/services/ApiNodeService.ts b/src/services/ApiNodeService.ts index e4dd96f..5b7c6c6 100644 --- a/src/services/ApiNodeService.ts +++ b/src/services/ApiNodeService.ts @@ -163,7 +163,7 @@ export class ApiNodeService { try { return (await HTTP.get(`${hostUrl}/node/info`)).data; } catch (e) { - logger.error(`Fail to request /node/info: ${hostUrl}`, e); + logger.error(`[getNodeInfo] Fail to request /node/info: ${hostUrl}`, e); return null; } }; @@ -172,7 +172,7 @@ export class ApiNodeService { try { return (await HTTP.get(`${hostUrl}/chain/info`)).data; } catch (e) { - logger.error(`Fail to request /chain/info: ${hostUrl}`, e); + logger.error(`[getNodeChainInfo] Fail to request /chain/info: ${hostUrl}`, e); return null; } }; @@ -183,7 +183,7 @@ export class ApiNodeService { return nodeServerInfo.serverInfo; } catch (e) { - logger.error(`Fail to request /node/server: ${hostUrl}`, e); + logger.error(`[getNodeServer] Fail to request /node/server: ${hostUrl}`, e); return null; } }; @@ -194,7 +194,7 @@ export class ApiNodeService { return health.status; } catch (e) { - logger.error(`Fail to request /node/health: ${hostUrl}`, e); + logger.error(`[getNodeHealth] Fail to request /node/health: ${hostUrl}`, e); return null; } }; diff --git a/src/services/ChainHeightMonitor.ts b/src/services/ChainHeightMonitor.ts index eb112cd..57ac996 100644 --- a/src/services/ChainHeightMonitor.ts +++ b/src/services/ChainHeightMonitor.ts @@ -61,7 +61,7 @@ export class ChainHeightMonitor { try { this.nodeList = (await DataBase.getNodeList()).filter((node) => isAPIRole(node.roles)); } catch (e) { - logger.error('Failed to get node list. Use nodes from config'); + logger.error('[getNodeList] Failed to get node list. Use nodes from config'); for (const node of symbol.NODES) { const url = new URL(node); const hostUrl = await ApiNodeService.buildHostUrl(url.hostname); diff --git a/src/services/DataBase.ts b/src/services/DataBase.ts index 9b91526..48ddcf0 100644 --- a/src/services/DataBase.ts +++ b/src/services/DataBase.ts @@ -109,28 +109,30 @@ export class DataBase { collectionName: string, ) => { const prevState = await model.find().exec(); - let error = Error(); try { await model.deleteMany(); } catch (e) { - logger.error(`Update collection "${collectionName}" failed. Error during "model.deleteMany()". ${error.message}`); - throw error; + const msg = `Update collection "${collectionName}" failed. Error during "model.deleteMany()". ${e.message}`; + + logger.error(msg); + throw new Error(msg); } try { await model.insertMany(documents); } catch (e) { - logger.error(`Update collection "${collectionName}" failed. Error during "model.insertMany()". ${error.message}`); - await model.insertMany(prevState); - throw error; + const msg = `Update collection "${collectionName}" failed. Error during "model.insertMany()". ${e.message}`; + + logger.error(msg); + throw new Error(msg); } const currentState = await model.find().exec(); if (documents.length !== currentState.length) { logger.error( - `Update collection "${collectionName}" failed. Collectin.length(${currentState.length}) !== documentsToInsert.length(${documents.length})`, + `Update collection "${collectionName}" failed. Collection.length(${currentState.length}) !== documentsToInsert.length(${documents.length})`, ); await model.insertMany(prevState); throw new Error(`Failed to update collection "${collectionName}. Length verification failed`); diff --git a/src/services/HostInfo.ts b/src/services/HostInfo.ts index e929d69..bc2f499 100644 --- a/src/services/HostInfo.ts +++ b/src/services/HostInfo.ts @@ -43,7 +43,7 @@ export class HostInfo { zip: data.zip, }; } catch (e) { - logger.error(`Failed to get host ${host} info ${e.message}`); + logger.error(`[getHostDetail] Failed to get host ${host} info ${e.message}`); return null; } }; diff --git a/src/services/NodeMonitor.ts b/src/services/NodeMonitor.ts index a7a58df..e8cb3f8 100644 --- a/src/services/NodeMonitor.ts +++ b/src/services/NodeMonitor.ts @@ -13,7 +13,7 @@ import { Logger } from '@src/infrastructure'; import { INode, validateNodeModel } from '@src/models/Node'; import { symbol, monitor } from '@src/config'; -import { isAPIRole, isPeerRole, basename, splitArray, sleep } from '@src/utils'; +import { isAPIRole, isPeerRole, basename, splitArray, showDuration } from '@src/utils'; const logger: winston.Logger = Logger.getLogger(basename(__filename)); @@ -24,6 +24,7 @@ export class NodeMonitor { private isRunning: boolean; private interval: number; private nodeInfoChunks: number; + private nodePeersChunkSize: number; private nodeInfoDelay: number; private networkIdentifier: number; private generationHashSeed: string; @@ -39,6 +40,7 @@ export class NodeMonitor { this.isRunning = false; this.interval = _interval || 300000; this.nodeInfoChunks = monitor.NUMBER_OF_NODE_REQUEST_CHUNK; + this.nodePeersChunkSize = monitor.NODE_PEERS_REQUEST_CHUNK_SIZE; this.nodeInfoDelay = 1000; this.networkIdentifier = 0; this.generationHashSeed = ''; @@ -81,31 +83,29 @@ export class NodeMonitor { }; private getNodeList = async (): Promise => { - logger.info(`Getting node list`); + logger.info(`[getNodeList] Getting node list...`); + const startTime = new Date().getTime(); // Fetch node list from config nodes + logger.info(`[getNodeList] Initial node list: ${symbol.NODES.join(', ')}`); for (const nodeUrl of symbol.NODES) { const peers = await this.fetchNodePeersByURL(nodeUrl); this.addNodesToList(peers); } - // Nested fetch node list from current nodeList[] - const nodeListPromises = this.nodeList.map(async (node) => { - if (isAPIRole(node.roles)) { - const hostUrl = await ApiNodeService.buildHostUrl(node.host); + // Fetch node list from database + const nodesFromDb = (await DataBase.getNodeList().then((nodes) => nodes.map((n) => n.toJSON()))) || []; - return this.fetchNodePeersByURL(hostUrl); - } - - return []; - }); - - const arrayOfNodeList = await Promise.all(nodeListPromises); - const nodeList: INode[] = arrayOfNodeList.reduce((accumulator, value) => accumulator.concat(value), []); + logger.info(`[getNodeList] Nodes count from DB: ${nodesFromDb.length}`); + // adding the nodes from DB to the node list + this.addNodesToList(nodesFromDb); - this.addNodesToList(nodeList); + await this.fetchAndAddNodeListPeers(); + logger.info( + `[getNodeList] Total node count: ${this.nodeList.length}, time elapsed: [${showDuration(startTime - new Date().getTime())}]`, + ); return Promise.resolve(); }; @@ -124,49 +124,102 @@ export class NodeMonitor { if (Array.isArray(nodePeers.data)) nodeList = [...nodePeers.data]; } catch (e) { - logger.error(`FetchNodePeersByURL. Failed to get /node/peers from "${hostUrl}". ${e.message}`); + logger.error(`[FetchNodePeersByURL] Failed to get /node/peers from "${hostUrl}". ${e.message}`); } return nodeList; }; + /** + * Fetch peers from current node list and add them to the node list. + */ + private fetchAndAddNodeListPeers = async (): Promise => { + const apiNodeList = this.nodeList.filter((node) => isAPIRole(node.roles)); + + logger.info( + `[fetchAndAddNodeListPeers] Getting peers from nodes, total nodes: ${this.nodeList.length}, api nodes: ${apiNodeList.length}`, + ); + const nodeListChunks = splitArray(apiNodeList, this.nodePeersChunkSize); + + let numOfNodesProcessed = 0; + + for (const nodes of nodeListChunks) { + logger.info( + `[fetchAndAddNodeListPeers] Getting peer list for chunk of ${nodes.length} nodes, progress: ${ + numOfNodesProcessed + '/' + apiNodeList.length + }`, + ); + const nodePeersPromises = [...nodes].map(async (node) => + this.fetchNodePeersByURL(await ApiNodeService.buildHostUrl(node.host)), + ); + const arrayOfPeerList = await Promise.all(nodePeersPromises); + const peers: INode[] = arrayOfPeerList.reduce((accumulator, value) => accumulator.concat(value), []); + + this.addNodesToList(peers); + numOfNodesProcessed += nodes.length; + } + }; + private getNodeListInfo = async () => { - logger.info(`Getting node from peers total ${this.nodeList.length} nodes`); + const startTime = new Date().getTime(); + const nodeCount = this.nodeList.length; + + logger.info(`[getNodeListInfo] Getting node from peers, total nodes: ${nodeCount}`); const nodeListChunks = splitArray(this.nodeList, this.nodeInfoChunks); this.nodeList = []; - for (const nodes of nodeListChunks) { - logger.info(`Getting node info for chunk of ${nodes.length} nodes`); + let numOfNodesProcessed = 0; + for (const nodes of nodeListChunks) { + logger.info( + `[getNodeListInfo] Getting node info for chunk of ${nodes.length} nodes, progress: ${ + numOfNodesProcessed + '/' + nodeCount + }`, + ); const nodeInfoPromises = [...nodes].map((node) => this.getNodeInfo(node)); + const arrayOfNodeInfo = await Promise.all(nodeInfoPromises); + + logger.info(`[getNodeListInfo] Number of nodeInfo:${arrayOfNodeInfo.length} in the chunk of ${nodes.length}`); + this.addNodesToList(arrayOfNodeInfo); + numOfNodesProcessed += nodes.length; - this.addNodesToList((await Promise.all(nodeInfoPromises)) as INode[]); - await sleep(this.nodeInfoDelay); + //await sleep(this.nodeInfoDelay); } this.nodeList.forEach((node) => this.nodesStats.addToStats(node)); + logger.info( + `[getNodeListInfo] Total node count(after nodeInfo): ${this.nodeList.length}, time elapsed: [${showDuration( + startTime - new Date().getTime(), + )}]`, + ); }; private async getNodeInfo(node: INode): Promise { let nodeWithInfo: INode = { ...node }; + const nodeHost = node.host; try { - const hostDetail = await HostInfo.getHostDetailCached(node.host); + const hostDetail = await HostInfo.getHostDetailCached(nodeHost); - if (hostDetail) nodeWithInfo.hostDetail = hostDetail; + if (hostDetail) { + nodeWithInfo.hostDetail = hostDetail; + } if (isPeerRole(nodeWithInfo.roles)) { - nodeWithInfo.peerStatus = await PeerNodeService.getStatus(node.host, node.port); + nodeWithInfo.peerStatus = await PeerNodeService.getStatus(nodeHost, node.port); } if (isAPIRole(nodeWithInfo.roles)) { - const hostUrl = await ApiNodeService.buildHostUrl(nodeWithInfo.host); + const hostUrl = await ApiNodeService.buildHostUrl(nodeHost); // Get node info and overwrite info from /node/peers const nodeStatus = await ApiNodeService.getNodeInfo(hostUrl); if (nodeStatus) { Object.assign(nodeWithInfo, nodeStatus); + if (!nodeWithInfo.host) { + nodeWithInfo.host = nodeHost; + } } // Request API Status, if node belong to the network @@ -178,9 +231,11 @@ export class NodeMonitor { } } } catch (e) { - logger.error(`GetNodeInfo. Failed to fetch info for "${nodeWithInfo.host}". ${e.message}`); + logger.error(`[getNodeInfo] Failed to fetch info for "${nodeWithInfo.host}". ${e.message}`); } - + logger.info( + `[getNodeInfo] NodeHost values before:${nodeHost} and after:${nodeWithInfo.host} and hostDetail.host:${nodeWithInfo.hostDetail?.host}`, + ); return nodeWithInfo; } @@ -248,9 +303,9 @@ export class NodeMonitor { node.networkGenerationHashSeed !== this.generationHashSeed || !!this.nodeList.find((addedNode) => addedNode.publicKey === node.publicKey) || !validateNodeModel(node) - ) + ) { return; - + } this.nodeList.push(node); }); }; diff --git a/src/utils.ts b/src/utils.ts index 18a9918..63ec6c9 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,5 +1,6 @@ import { INode } from '@src/models/Node'; import * as path from 'path'; +import * as humanizeDuration from 'humanize-duration'; export const stringToArray = (str: string | undefined): Array => { let result = null; @@ -58,3 +59,7 @@ export const splitArray = (array: Array, chunks: number): Array => all[ch] = [].concat(all[ch] || [], one); return all; }, []); + +export const showDuration = (durationMs: number): string => { + return humanizeDuration(durationMs); +}; From 7ff025737d14a173ec4835fc61c4c01ef62c8ee1 Mon Sep 17 00:00:00 2001 From: Baha Date: Mon, 27 Dec 2021 09:49:32 +0000 Subject: [PATCH 03/11] fix: chunking logic refactored and extracted to utils, chainHeight task refactored to process chunks --- src/config/config.json | 1 + src/config/index.ts | 5 +++ src/services/ChainHeightMonitor.ts | 50 +++++++++++++++--------------- src/services/NodeMonitor.ts | 43 ++++++------------------- src/utils.ts | 32 +++++++++++++++++++ 5 files changed, 73 insertions(+), 58 deletions(-) diff --git a/src/config/config.json b/src/config/config.json index be448ae..06cf14f 100644 --- a/src/config/config.json +++ b/src/config/config.json @@ -18,6 +18,7 @@ "REQUEST_TIMEOUT": 5000, "NUMBER_OF_NODE_REQUEST_CHUNK": 10, "NODE_PEERS_REQUEST_CHUNK_SIZE": 50, + "CHAIN_HEIGHT_REQUEST_CHUNK_SIZE": 10, "PREFERRED_NODES": ["*.symboldev.network"], "MIN_PARTNER_NODE_VERSION": 16777728 } diff --git a/src/config/index.ts b/src/config/index.ts index 233456e..013dd4d 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -19,6 +19,7 @@ interface Monitor { NODE_MONITOR_SCHEDULE_INTERVAL: number; NUMBER_OF_NODE_REQUEST_CHUNK: number; NODE_PEERS_REQUEST_CHUNK_SIZE: number; + CHAIN_HEIGHT_REQUEST_CHUNK_SIZE: number; CHAIN_HEIGHT_MONITOR_SCHEDULE_INTERVAL: number; GEOLOCATION_MONITOR_SCHEDULE_INTERVAL: number; API_NODE_PORT: number; @@ -51,6 +52,7 @@ export const monitor: Monitor = { NODE_MONITOR_SCHEDULE_INTERVAL: Number(process.env.NODE_MONITOR_SCHEDULE_INTERVAL) || config.NODE_MONITOR_SCHEDULE_INTERVAL, NUMBER_OF_NODE_REQUEST_CHUNK: Number(process.env.NUMBER_OF_NODE_REQUEST_CHUNK) || config.NUMBER_OF_NODE_REQUEST_CHUNK, NODE_PEERS_REQUEST_CHUNK_SIZE: Number(process.env.NODE_PEERS_REQUEST_CHUNK_SIZE) || config.NODE_PEERS_REQUEST_CHUNK_SIZE, + CHAIN_HEIGHT_REQUEST_CHUNK_SIZE: Number(process.env.CHAIN_HEIGHT_REQUEST_CHUNK_SIZE) || config.CHAIN_HEIGHT_REQUEST_CHUNK_SIZE, CHAIN_HEIGHT_MONITOR_SCHEDULE_INTERVAL: Number(process.env.CHAIN_HEIGHT_MONITOR_SCHEDULE_INTERVAL) || config.CHAIN_HEIGHT_MONITOR_SCHEDULE_INTERVAL, GEOLOCATION_MONITOR_SCHEDULE_INTERVAL: @@ -90,6 +92,9 @@ export const verifyConfig = (cfg: Config): boolean => { if (isNaN(cfg.monitor.NODE_PEERS_REQUEST_CHUNK_SIZE) || cfg.monitor.NODE_PEERS_REQUEST_CHUNK_SIZE < 0) error = 'Invalid "NODE_PEERS_REQUEST_CHUNK_SIZE"'; + if (isNaN(cfg.monitor.CHAIN_HEIGHT_REQUEST_CHUNK_SIZE) || cfg.monitor.CHAIN_HEIGHT_REQUEST_CHUNK_SIZE < 0) + error = 'Invalid "CHAIN_HEIGHT_REQUEST_CHUNK_SIZE"'; + if (isNaN(cfg.monitor.API_NODE_PORT) || cfg.monitor.API_NODE_PORT <= 0 || cfg.monitor.API_NODE_PORT >= 10000) error = 'Invalid "API_NODE_PORT"'; diff --git a/src/services/ChainHeightMonitor.ts b/src/services/ChainHeightMonitor.ts index 57ac996..19ba5db 100644 --- a/src/services/ChainHeightMonitor.ts +++ b/src/services/ChainHeightMonitor.ts @@ -5,8 +5,8 @@ import { Logger } from '@src/infrastructure'; import { INode } from '@src/models/Node'; import { INodeHeightStats } from '@src/models/NodeHeightStats'; -import { symbol } from '@src/config'; -import { isAPIRole, basename, sleep } from '@src/utils'; +import { symbol, monitor } from '@src/config'; +import { isAPIRole, basename, sleep, runTaskInChunks } from '@src/utils'; const logger: winston.Logger = Logger.getLogger(basename(__filename)); @@ -80,32 +80,32 @@ export class ChainHeightMonitor { private getNodeChainHeight = async () => { logger.info(`Getting height stats for ${this.nodeList.length} nodes`); - const nodes: INode[] = this.nodeList; - const nodeChainInfoPromises = nodes.map((node) => { - const isHttps = node.apiStatus?.isHttpsEnabled; - const protocol = isHttps ? 'https:' : 'http:'; - const port = isHttps ? 3001 : 3000; - const hostUrl = `${protocol}//${node.host}:${port}`; - - return ApiNodeService.getNodeChainInfo(hostUrl); - }); - const nodeChainInfoList = await Promise.all(nodeChainInfoPromises); - - for (let chainInfo of nodeChainInfoList) { - try { - if (chainInfo) { - if (this.heights[chainInfo.height]) this.heights[chainInfo.height]++; - else this.heights[chainInfo.height] = 1; - - if (this.finalizedHeights[chainInfo.latestFinalizedBlock.height]) - this.finalizedHeights[chainInfo.latestFinalizedBlock.height]++; - else this.finalizedHeights[chainInfo.latestFinalizedBlock.height] = 1; + await runTaskInChunks(this.nodeList, monitor.CHAIN_HEIGHT_REQUEST_CHUNK_SIZE, logger, 'getNodeChainHeight', async (nodes) => { + const nodeChainInfoPromises = nodes.map((node) => { + const isHttps = node.apiStatus?.isHttpsEnabled; + const protocol = isHttps ? 'https:' : 'http:'; + const port = isHttps ? 3001 : 3000; + + const hostUrl = `${protocol}//${node.host}:${port}`; + + return ApiNodeService.getNodeChainInfo(hostUrl); + }); + const nodeChainInfoList = await Promise.all(nodeChainInfoPromises); + + for (let chainInfo of nodeChainInfoList) { + try { + if (chainInfo) { + this.heights[chainInfo.height] = (this.heights[chainInfo.height] || 0) + 1; + this.finalizedHeights[chainInfo.latestFinalizedBlock.height] = + (this.finalizedHeights[chainInfo.latestFinalizedBlock.height] || 0) + 1; + } + } catch (e) { + logger.error(`Node chain height monitor failed. ${e.message}`); } - } catch (e) { - logger.error(`Node chain height monitor failed. ${e.message}`); } - } + return nodeChainInfoList; + }); }; private clear = () => { diff --git a/src/services/NodeMonitor.ts b/src/services/NodeMonitor.ts index e8cb3f8..3858e64 100644 --- a/src/services/NodeMonitor.ts +++ b/src/services/NodeMonitor.ts @@ -13,7 +13,7 @@ import { Logger } from '@src/infrastructure'; import { INode, validateNodeModel } from '@src/models/Node'; import { symbol, monitor } from '@src/config'; -import { isAPIRole, isPeerRole, basename, splitArray, showDuration } from '@src/utils'; +import { isAPIRole, isPeerRole, basename, showDuration, runTaskInChunks } from '@src/utils'; const logger: winston.Logger = Logger.getLogger(basename(__filename)); @@ -139,25 +139,16 @@ export class NodeMonitor { logger.info( `[fetchAndAddNodeListPeers] Getting peers from nodes, total nodes: ${this.nodeList.length}, api nodes: ${apiNodeList.length}`, ); - const nodeListChunks = splitArray(apiNodeList, this.nodePeersChunkSize); - let numOfNodesProcessed = 0; - - for (const nodes of nodeListChunks) { - logger.info( - `[fetchAndAddNodeListPeers] Getting peer list for chunk of ${nodes.length} nodes, progress: ${ - numOfNodesProcessed + '/' + apiNodeList.length - }`, - ); - const nodePeersPromises = [...nodes].map(async (node) => - this.fetchNodePeersByURL(await ApiNodeService.buildHostUrl(node.host)), + await runTaskInChunks(this.nodeList, this.nodePeersChunkSize, logger, 'fetchAndAddNodeListPeers', async (nodes) => { + const arrayOfPeerList = await Promise.all( + [...nodes].map(async (node) => this.fetchNodePeersByURL(await ApiNodeService.buildHostUrl(node.host))), ); - const arrayOfPeerList = await Promise.all(nodePeersPromises); const peers: INode[] = arrayOfPeerList.reduce((accumulator, value) => accumulator.concat(value), []); this.addNodesToList(peers); - numOfNodesProcessed += nodes.length; - } + return peers; + }); }; private getNodeListInfo = async () => { @@ -165,27 +156,15 @@ export class NodeMonitor { const nodeCount = this.nodeList.length; logger.info(`[getNodeListInfo] Getting node from peers, total nodes: ${nodeCount}`); - const nodeListChunks = splitArray(this.nodeList, this.nodeInfoChunks); - - this.nodeList = []; - - let numOfNodesProcessed = 0; - for (const nodes of nodeListChunks) { - logger.info( - `[getNodeListInfo] Getting node info for chunk of ${nodes.length} nodes, progress: ${ - numOfNodesProcessed + '/' + nodeCount - }`, - ); + await runTaskInChunks(this.nodeList, this.nodeInfoChunks, logger, 'getNodeListInfo', async (nodes) => { const nodeInfoPromises = [...nodes].map((node) => this.getNodeInfo(node)); const arrayOfNodeInfo = await Promise.all(nodeInfoPromises); - logger.info(`[getNodeListInfo] Number of nodeInfo:${arrayOfNodeInfo.length} in the chunk of ${nodes.length}`); this.addNodesToList(arrayOfNodeInfo); - numOfNodesProcessed += nodes.length; + return arrayOfNodeInfo; + }); - //await sleep(this.nodeInfoDelay); - } this.nodeList.forEach((node) => this.nodesStats.addToStats(node)); logger.info( `[getNodeListInfo] Total node count(after nodeInfo): ${this.nodeList.length}, time elapsed: [${showDuration( @@ -233,9 +212,7 @@ export class NodeMonitor { } catch (e) { logger.error(`[getNodeInfo] Failed to fetch info for "${nodeWithInfo.host}". ${e.message}`); } - logger.info( - `[getNodeInfo] NodeHost values before:${nodeHost} and after:${nodeWithInfo.host} and hostDetail.host:${nodeWithInfo.hostDetail?.host}`, - ); + return nodeWithInfo; } diff --git a/src/utils.ts b/src/utils.ts index 63ec6c9..c8a0eb8 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,6 +1,7 @@ import { INode } from '@src/models/Node'; import * as path from 'path'; import * as humanizeDuration from 'humanize-duration'; +import winston = require('winston'); export const stringToArray = (str: string | undefined): Array => { let result = null; @@ -63,3 +64,34 @@ export const splitArray = (array: Array, chunks: number): Array => export const showDuration = (durationMs: number): string => { return humanizeDuration(durationMs); }; + +export const runTaskInChunks = async ( + list: T[], + chunkSize: number, + logger: winston.Logger, + loggingMethod: string, + asyncTask: (subList: T[]) => Promise, +) => { + const chunks: T[][] = splitArray(list, chunkSize); + + logger.info( + `[${loggingMethod}] Running the task for chunks, Total Size: ${list.length}, Chunk size: ${chunkSize}, Chunk count: ${Math.ceil( + list.length / chunkSize, + )}`, + ); + + let numOfNodesProcessed = 0, + i = 0; + + for (const chunk of chunks) { + logger.info( + `[${loggingMethod}] Working on chunk #${++i}/${chunks.length}, size: ${chunk.length}, progress: ${numOfNodesProcessed}/${ + list.length + }`, + ); + const arrayOfTaskResults = await asyncTask(chunk); + + logger.info(`[${loggingMethod}] Number of results:${arrayOfTaskResults.length} in the chunk of ${chunk.length}`); + numOfNodesProcessed += chunk.length; + } +}; From 5c4b7cce103402bcbaa22bc531fc2ff52c0c9512 Mon Sep 17 00:00:00 2001 From: Baha Date: Mon, 27 Dec 2021 22:14:47 +0000 Subject: [PATCH 04/11] fix: nodeList skipping item on update fixed, logging further improved --- src/services/ChainHeightMonitor.ts | 2 +- src/services/NodeMonitor.ts | 19 ++++++++++++++++--- src/utils.ts | 17 ++++++++++++----- 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/src/services/ChainHeightMonitor.ts b/src/services/ChainHeightMonitor.ts index 19ba5db..8f05942 100644 --- a/src/services/ChainHeightMonitor.ts +++ b/src/services/ChainHeightMonitor.ts @@ -93,7 +93,7 @@ export class ChainHeightMonitor { }); const nodeChainInfoList = await Promise.all(nodeChainInfoPromises); - for (let chainInfo of nodeChainInfoList) { + for (const chainInfo of nodeChainInfoList) { try { if (chainInfo) { this.heights[chainInfo.height] = (this.heights[chainInfo.height] || 0) + 1; diff --git a/src/services/NodeMonitor.ts b/src/services/NodeMonitor.ts index 3858e64..30044de 100644 --- a/src/services/NodeMonitor.ts +++ b/src/services/NodeMonitor.ts @@ -55,6 +55,8 @@ export class NodeMonitor { public start = async () => { logger.info(`Start`); + const startTime = new Date().getTime(); + try { this.isRunning = true; this.clear(); @@ -69,8 +71,13 @@ export class NodeMonitor { await this.cacheCollection(); setTimeout(() => this.start(), this.interval); } + logger.info(`[start] Node monitor task finished, time elapsed: [${showDuration(startTime - new Date().getTime())}]`); } catch (e) { - logger.error(`Unhandled error during a loop. ${e.message}. Restarting NodeMonitor..`); + logger.error( + `[start] Node monitor task failed [error: ${e.message}], time elapsed: [${showDuration( + startTime - new Date().getTime(), + )}], Restarting Node monitor task...`, + ); this.stop(); this.start(); } @@ -278,12 +285,18 @@ export class NodeMonitor { if ( node.networkIdentifier !== this.networkIdentifier || node.networkGenerationHashSeed !== this.generationHashSeed || - !!this.nodeList.find((addedNode) => addedNode.publicKey === node.publicKey) || !validateNodeModel(node) ) { return; } - this.nodeList.push(node); + const nodeInx = this.nodeList.findIndex((addedNode) => addedNode.publicKey === node.publicKey); + + if (nodeInx > -1) { + // already in the list then update + this.nodeList[nodeInx] = node; + } else { + this.nodeList.push(node); + } }); }; } diff --git a/src/utils.ts b/src/utils.ts index c8a0eb8..236970b 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -72,11 +72,18 @@ export const runTaskInChunks = async ( loggingMethod: string, asyncTask: (subList: T[]) => Promise, ) => { + if (!list?.length) { + return []; + } + if (chunkSize < 1) { + throw new Error(`Invalid chunkSize value[${chunkSize}]`); + } const chunks: T[][] = splitArray(list, chunkSize); + const listSize = list.length; logger.info( - `[${loggingMethod}] Running the task for chunks, Total Size: ${list.length}, Chunk size: ${chunkSize}, Chunk count: ${Math.ceil( - list.length / chunkSize, + `[${loggingMethod}] Running the task for chunks, Total Size: ${listSize}, Chunk size: ${chunkSize}, Chunk count: ${Math.ceil( + listSize / chunkSize, )}`, ); @@ -85,9 +92,9 @@ export const runTaskInChunks = async ( for (const chunk of chunks) { logger.info( - `[${loggingMethod}] Working on chunk #${++i}/${chunks.length}, size: ${chunk.length}, progress: ${numOfNodesProcessed}/${ - list.length - }`, + `[${loggingMethod}] Working on chunk #${++i}/${chunks.length}, size: ${ + chunk.length + }, progress: ${numOfNodesProcessed}/${listSize}`, ); const arrayOfTaskResults = await asyncTask(chunk); From 1fb1330b63569ecbb216309546a4ebe99acd63f6 Mon Sep 17 00:00:00 2001 From: Baha Date: Wed, 29 Dec 2021 14:43:12 +0000 Subject: [PATCH 05/11] fix: apiNodeList regression fixed --- src/services/NodeMonitor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/NodeMonitor.ts b/src/services/NodeMonitor.ts index 30044de..11b8f89 100644 --- a/src/services/NodeMonitor.ts +++ b/src/services/NodeMonitor.ts @@ -147,7 +147,7 @@ export class NodeMonitor { `[fetchAndAddNodeListPeers] Getting peers from nodes, total nodes: ${this.nodeList.length}, api nodes: ${apiNodeList.length}`, ); - await runTaskInChunks(this.nodeList, this.nodePeersChunkSize, logger, 'fetchAndAddNodeListPeers', async (nodes) => { + await runTaskInChunks(apiNodeList, this.nodePeersChunkSize, logger, 'fetchAndAddNodeListPeers', async (nodes) => { const arrayOfPeerList = await Promise.all( [...nodes].map(async (node) => this.fetchNodePeersByURL(await ApiNodeService.buildHostUrl(node.host))), ); From c9a692889f33cbc4b483c2d3037c9ec8d3fae50b Mon Sep 17 00:00:00 2001 From: Baha Date: Mon, 10 Jan 2022 15:45:17 +0000 Subject: [PATCH 06/11] fix: preferred filter fix, Config.PREFERRED_NODES is empty by default --- package.json | 1 + src/config/config.json | 2 +- src/config/index.ts | 2 -- src/routes/index.ts | 7 +++++-- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index e28de5c..631f36b 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "description": "", "scripts": { "dev": "nodemon", + "debug": "node --inspect-brk=0.0.0.0:9229 --require ts-node/register src/app.ts", "build": "shx rm -rf ./dist/ && tsc && shx cp -r openapi dist/openapi", "build:clients": "bash scripts/build-clients.sh", "start": "node dist/app.js", diff --git a/src/config/config.json b/src/config/config.json index 06cf14f..a158d10 100644 --- a/src/config/config.json +++ b/src/config/config.json @@ -19,6 +19,6 @@ "NUMBER_OF_NODE_REQUEST_CHUNK": 10, "NODE_PEERS_REQUEST_CHUNK_SIZE": 50, "CHAIN_HEIGHT_REQUEST_CHUNK_SIZE": 10, - "PREFERRED_NODES": ["*.symboldev.network"], + "PREFERRED_NODES": [], "MIN_PARTNER_NODE_VERSION": 16777728 } diff --git a/src/config/index.ts b/src/config/index.ts index 013dd4d..c109bc6 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -81,8 +81,6 @@ export const verifyConfig = (cfg: Config): boolean => { error = 'Invalid "NODES"'; } - if (cfg.symbol.PREFERRED_NODES.length === 0) error = 'Invalid "PREFERRED NODES"'; - if (isNaN(cfg.monitor.NODE_MONITOR_SCHEDULE_INTERVAL) || cfg.monitor.NODE_MONITOR_SCHEDULE_INTERVAL < 0) error = 'Invalid "NODE_MONITOR_SCHEDULE_INTERVAL"'; diff --git a/src/routes/index.ts b/src/routes/index.ts index 33b5153..e4bc767 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -37,10 +37,13 @@ export class Routes { } // ?filter=preferred - // it filter by host / domain name config by admin. + // it filters by host / domain name config by admin. if (filter === NodeFilter.Preferred) { + if (!symbol.PREFERRED_NODES?.length) { + return Promise.resolve(res.send([])); + } Object.assign(searchCriteria.filter, { - host: { $in: symbol.PREFERRED_NODES.map((node) => new RegExp(`^.${node}`, 'i')) }, + host: { $in: symbol.PREFERRED_NODES.map((node) => new RegExp(`^${node}`, 'i')) }, 'apiStatus.isAvailable': true, 'apiStatus.nodeStatus.apiNode': 'up', 'apiStatus.nodeStatus.db': 'up', From 9745334cbf77a09bd8aa3c923598bb0cb4630dd1 Mon Sep 17 00:00:00 2001 From: Baha Date: Tue, 11 Jan 2022 15:27:14 +0000 Subject: [PATCH 07/11] fix: clean up stale nodes from database --- .travis.yml | 3 + package-lock.json | 1429 ++++++++++++++++++++++++++++++++++- package.json | 11 +- src/config/config.json | 3 +- src/config/index.ts | 2 + src/constants.ts | 6 + src/models/Node.ts | 5 + src/services/NodeMonitor.ts | 35 +- test/NodeMonitor.test.ts | 93 +++ tsconfig.json | 6 +- 10 files changed, 1545 insertions(+), 48 deletions(-) create mode 100644 src/constants.ts create mode 100644 test/NodeMonitor.test.ts diff --git a/.travis.yml b/.travis.yml index 594b0e8..f4fe4e1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,6 +26,9 @@ jobs: - stage: lint name: lint script: npm run lint + - stage: test + name: test + script: npm run test - stage: test openapi name: test-openapi script: npm run test:openapi diff --git a/package-lock.json b/package-lock.json index c069b6a..705b5a0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,21 +27,27 @@ }, "devDependencies": { "@openapitools/openapi-generator-cli": "^2.4.15", + "@types/chai": "^4.3.0", "@types/humanize-duration": "^3.27.0", + "@types/mocha": "^9.0.0", "@types/mongoose": "^5.7.14", - "@types/node": "^14.14.10", + "@types/sinon": "^10.0.6", "@types/winston": "^2.4.4", "@types/ws": "^8.2.0", "@typescript-eslint/eslint-plugin": "^4.4.0", "@typescript-eslint/parser": "^4.4.0", + "chai": "^4.3.4", "eslint": "^7.10.0", "eslint-config-prettier": "^6.12.0", "eslint-plugin-prettier": "^3.1.4", + "mocha": "^8.4.0", "nodemon": "^2.0.3", "openapi-to-postmanv2": "^2.6.0", "prettier": "^2.1.2", "shx": "^0.3.3", + "sinon": "^12.0.1", "swagger-cli": "^4.0.4", + "ts-mocha": "^8.0.0", "ts-node": "^8.10.1", "tslint": "^6.1.2", "typescript": "^3.8.3", @@ -458,6 +464,41 @@ "node": ">=6" } }, + "node_modules/@sinonjs/commons": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", + "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-7.1.2.tgz", + "integrity": "sha512-iQADsW4LBMISqZ6Ci1dupJL9pprqwcVFTcOsEmQOEhW+KLCVn/Y4Jrvg2k19fIHCp+iFprriYPTdRcQR8NbUPg==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^1.7.0" + } + }, + "node_modules/@sinonjs/samsam": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-6.0.2.tgz", + "integrity": "sha512-jxPRPp9n93ci7b8hMfJOFDPRLFYadN6FSpeROFTR4UNF4i5b+EK6m4QXPO46BDhFgRy1JuS87zAnFOzCUwMJcQ==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^1.6.0", + "lodash.get": "^4.4.2", + "type-detect": "^4.0.8" + } + }, + "node_modules/@sinonjs/text-encoding": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz", + "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", + "dev": true + }, "node_modules/@szmarczak/http-timer": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", @@ -487,6 +528,12 @@ "@types/node": "*" } }, + "node_modules/@types/chai": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.0.tgz", + "integrity": "sha512-/ceqdqeRraGolFTcfoXNiqjyQhZzbINDngeoAq9GoHa8PPK1yNzTaxWjA6BFWp5Ua9JpXEMSS4s5i9tS0hOJtw==", + "dev": true + }, "node_modules/@types/connect": { "version": "3.4.34", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.34.tgz", @@ -533,11 +580,24 @@ "integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==", "dev": true }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", + "dev": true, + "optional": true + }, "node_modules/@types/mime": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.3.tgz", "integrity": "sha512-Jus9s4CDbqwocc5pOAnh8ShfrnMcPHuJYzVcSUU7lrh8Ni5HuIqX3oilL86p3dlTrk0LzHRCgA/GQ7uNCw6l2Q==" }, + "node_modules/@types/mocha": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.0.0.tgz", + "integrity": "sha512-scN0hAWyLVAvLR9AyW7HoFF5sJZglyBsbPuHO4fv7JRvfmPBMfp1ozWqOf/e4wwPNxezBZXRfWzMb6iFLgEVRA==", + "dev": true + }, "node_modules/@types/mongodb": { "version": "3.6.3", "resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.6.3.tgz", @@ -558,9 +618,9 @@ } }, "node_modules/@types/node": { - "version": "14.14.21", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.21.tgz", - "integrity": "sha512-cHYfKsnwllYhjOzuC5q1VpguABBeecUp24yFluHpn/BQaVxB1CuQ1FSRZCzrPxrkIfWISXV2LbeoBthLWg0+0A==" + "version": "14.18.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.5.tgz", + "integrity": "sha512-LMy+vDDcQR48EZdEx5wRX1q/sEl6NdGuHXPnfeL8ixkwCOSZ2qnIyIZmcCbdX0MeRqHhAcHmX+haCbrS8Run+A==" }, "node_modules/@types/qs": { "version": "6.9.5", @@ -581,6 +641,15 @@ "@types/node": "*" } }, + "node_modules/@types/sinon": { + "version": "10.0.6", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-10.0.6.tgz", + "integrity": "sha512-6EF+wzMWvBNeGrfP3Nx60hhx+FfwSg1JJBLAAP/IdIUq0EYkqCYf70VT3PhuhPX9eLD+Dp+lNdpb/ZeHG8Yezg==", + "dev": true, + "dependencies": { + "@sinonjs/fake-timers": "^7.1.0" + } + }, "node_modules/@types/tcp-ping": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/@types/tcp-ping/-/tcp-ping-0.1.1.tgz", @@ -897,6 +966,12 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@ungap/promise-all-settled": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", + "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", + "dev": true + }, "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -1022,9 +1097,9 @@ } }, "node_modules/anymatch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", - "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", "dev": true, "dependencies": { "normalize-path": "^3.0.0", @@ -1063,6 +1138,24 @@ "node": ">=8" } }, + "node_modules/arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/astral-regex": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", @@ -1250,6 +1343,12 @@ "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=" }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, "node_modules/bson": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.6.tgz", @@ -1386,6 +1485,23 @@ "resolved": "https://registry.npmjs.org/catbuffer-typescript/-/catbuffer-typescript-1.0.1.tgz", "integrity": "sha512-IyC2bmBEMRY96/NMsAer+qMTSa6yAwKfGIbpYPDPnlSb4UguNOlSabbCFH0CDQfhWuO6wqH97xGCuB4qY3OCwA==" }, + "node_modules/chai": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.4.tgz", + "integrity": "sha512-yS5H68VYOCtN1cjfwumDSuzn/9c+yza4f3reKXlE5rUg7SFcCEy90gJvydNgOYtblyf4Zi6jIWRnXOgErta0KA==", + "dev": true, + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "pathval": "^1.1.1", + "type-detect": "^4.0.5" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -1438,25 +1554,34 @@ "node": ">=4.0.0" } }, + "node_modules/check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/chokidar": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.0.tgz", - "integrity": "sha512-JgQM9JS92ZbFR4P90EvmzNpSGhpPBGBSj10PILeDyYFwp4h2/D9OM03wsJ4zW1fEp4ka2DGrnUeD7FuvQ2aZ2Q==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", + "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", "dev": true, "dependencies": { - "anymatch": "~3.1.1", + "anymatch": "~3.1.2", "braces": "~3.0.2", - "glob-parent": "~5.1.0", + "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", - "readdirp": "~3.5.0" + "readdirp": "~3.6.0" }, "engines": { "node": ">= 8.10.0" }, "optionalDependencies": { - "fsevents": "~2.3.1" + "fsevents": "~2.3.2" } }, "node_modules/ci-info": { @@ -1852,6 +1977,18 @@ "node": ">=4" } }, + "node_modules/deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "dev": true, + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=0.12" + } + }, "node_modules/deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", @@ -2593,6 +2730,15 @@ "node": ">=8" } }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } + }, "node_modules/flat-cache": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", @@ -2673,9 +2819,9 @@ "dev": true }, "node_modules/fsevents": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.1.tgz", - "integrity": "sha512-YR47Eg4hChJGAB1O3yEAOkGO+rlzutoICGqGo9EZ4lKWokzZRSyIW1QmTzqjtw8MJdj9srP869CuWw/hyzSiBw==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", "dev": true, "hasInstallScript": true, "optional": true, @@ -2709,6 +2855,15 @@ "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/get-stdin": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", @@ -2849,6 +3004,15 @@ "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", "dev": true }, + "node_modules/growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true, + "engines": { + "node": ">=4.x" + } + }, "node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -2926,6 +3090,15 @@ "minimalistic-assert": "^1.0.1" } }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, "node_modules/hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", @@ -3266,6 +3439,15 @@ "node": ">=8" } }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/is-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", @@ -3393,6 +3575,19 @@ "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", "dev": true }, + "node_modules/json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "optional": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, "node_modules/jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -3405,6 +3600,12 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/just-extend": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", + "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", + "dev": true + }, "node_modules/kareem": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.2.tgz", @@ -3750,6 +3951,257 @@ "mkdirp": "bin/cmd.js" } }, + "node_modules/mocha": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.4.0.tgz", + "integrity": "sha512-hJaO0mwDXmZS4ghXsvPVriOhsxQ7ofcpQdm8dE+jISUOKopitvnXFQmpRR7jd2K6VBG6E26gU3IAbXXGIbu4sQ==", + "dev": true, + "dependencies": { + "@ungap/promise-all-settled": "1.1.2", + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.1", + "debug": "4.3.1", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.1.6", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "4.0.0", + "log-symbols": "4.0.0", + "minimatch": "3.0.4", + "ms": "2.1.3", + "nanoid": "3.1.20", + "serialize-javascript": "5.0.1", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "which": "2.0.2", + "wide-align": "1.1.3", + "workerpool": "6.1.0", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha" + }, + "engines": { + "node": ">= 10.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mochajs" + } + }, + "node_modules/mocha/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/mocha/node_modules/chokidar": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", + "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.5.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.1" + } + }, + "node_modules/mocha/node_modules/debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/mocha/node_modules/debug/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/mocha/node_modules/diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/mocha/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/mocha/node_modules/js-yaml": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.0.0.tgz", + "integrity": "sha512-pqon0s+4ScYUvX30wxQi3PogGFAlUyH0awepWvwkj4jD4v+ova3RiYw8bmA6x2rDrEaj8i/oWKoRxpVNW+Re8Q==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/mocha/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/log-symbols": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", + "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/mocha/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/readdirp": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", + "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/mocha/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/mocha/node_modules/yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/module-alias": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/module-alias/-/module-alias-2.2.2.tgz", @@ -3906,6 +4358,18 @@ "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", "dev": true }, + "node_modules/nanoid": { + "version": "3.1.20", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.20.tgz", + "integrity": "sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw==", + "dev": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -3920,6 +4384,34 @@ "node": ">= 0.6" } }, + "node_modules/nise": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.0.tgz", + "integrity": "sha512-W5WlHu+wvo3PaKLsJJkgPup2LrsXCcm7AWwyNZkUnn5rwPkuPBi3Iwk5SQtN0mv+K65k7nKKjwNQ30wg3wLAQQ==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^1.7.0", + "@sinonjs/fake-timers": "^7.0.4", + "@sinonjs/text-encoding": "^0.7.1", + "just-extend": "^4.0.2", + "path-to-regexp": "^1.7.0" + } + }, + "node_modules/nise/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "node_modules/nise/node_modules/path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "dev": true, + "dependencies": { + "isarray": "0.0.1" + } + }, "node_modules/node-fetch": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", @@ -4442,6 +4934,15 @@ "node": ">=8" } }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/picomatch": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", @@ -4718,9 +5219,9 @@ } }, "node_modules/readdirp": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", - "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", "dev": true, "dependencies": { "picomatch": "^2.2.1" @@ -5031,6 +5532,15 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" }, + "node_modules/serialize-javascript": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", + "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, "node_modules/serve-static": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", @@ -5158,6 +5668,63 @@ "is-arrayish": "^0.3.1" } }, + "node_modules/sinon": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-12.0.1.tgz", + "integrity": "sha512-iGu29Xhym33ydkAT+aNQFBINakjq69kKO6ByPvTsm3yyIACfyQttRTP03aBP/I8GfhFmLzrnKwNNkr0ORb1udg==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^1.8.3", + "@sinonjs/fake-timers": "^8.1.0", + "@sinonjs/samsam": "^6.0.2", + "diff": "^5.0.0", + "nise": "^5.1.0", + "supports-color": "^7.2.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/sinon" + } + }, + "node_modules/sinon/node_modules/@sinonjs/fake-timers": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz", + "integrity": "sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^1.7.0" + } + }, + "node_modules/sinon/node_modules/diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/sinon/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/sinon/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -5287,6 +5854,16 @@ "node": ">=8" } }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true, + "optional": true, + "engines": { + "node": ">=4" + } + }, "node_modules/strip-hex-prefix": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/strip-hex-prefix/-/strip-hex-prefix-1.0.0.tgz", @@ -5624,6 +6201,67 @@ "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz", "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==" }, + "node_modules/ts-mocha": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/ts-mocha/-/ts-mocha-8.0.0.tgz", + "integrity": "sha512-Kou1yxTlubLnD5C3unlCVO7nh0HERTezjoVhVw/M5S1SqoUec0WgllQvPk3vzPMc6by8m6xD1uR1yRf8lnVUbA==", + "dev": true, + "dependencies": { + "ts-node": "7.0.1" + }, + "bin": { + "ts-mocha": "bin/ts-mocha" + }, + "engines": { + "node": ">= 6.X.X" + }, + "optionalDependencies": { + "tsconfig-paths": "^3.5.0" + }, + "peerDependencies": { + "mocha": "^3.X.X || ^4.X.X || ^5.X.X || ^6.X.X || ^7.X.X || ^8.X.X" + } + }, + "node_modules/ts-mocha/node_modules/diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/ts-mocha/node_modules/ts-node": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-7.0.1.tgz", + "integrity": "sha512-BVwVbPJRspzNh2yfslyT1PSbl5uIk03EZlb493RKHN4qej/D06n1cEhjlOJG69oFsE7OT8XjpTUcYf6pKTLMhw==", + "dev": true, + "dependencies": { + "arrify": "^1.0.0", + "buffer-from": "^1.1.0", + "diff": "^3.1.0", + "make-error": "^1.1.1", + "minimist": "^1.2.0", + "mkdirp": "^0.5.1", + "source-map-support": "^0.5.6", + "yn": "^2.0.0" + }, + "bin": { + "ts-node": "dist/bin.js" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/ts-mocha/node_modules/yn": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yn/-/yn-2.0.0.tgz", + "integrity": "sha1-5a2ryKz0CPY4X8dklWhMiOavaJo=", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/ts-node": { "version": "8.10.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.10.2.tgz", @@ -5649,6 +6287,19 @@ "typescript": ">=2.7" } }, + "node_modules/tsconfig-paths": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.12.0.tgz", + "integrity": "sha512-e5adrnOYT6zqVnWqZu7i/BQ3BnhzvGbjEjejFXO20lKIKpwTaupkCPgEfv4GZK1IBciJUEhYs3J3p75FdaTFVg==", + "dev": true, + "optional": true, + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.0", + "strip-bom": "^3.0.0" + } + }, "node_modules/tslib": { "version": "1.13.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", @@ -5755,6 +6406,15 @@ "node": ">= 0.8.0" } }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/type-fest": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", @@ -6062,6 +6722,58 @@ "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", "dev": true }, + "node_modules/wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, + "dependencies": { + "string-width": "^1.0.2 || 2" + } + }, + "node_modules/wide-align/node_modules/ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/wide-align/node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/wide-align/node_modules/string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "dependencies": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/wide-align/node_modules/strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "dependencies": { + "ansi-regex": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/widest-line": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", @@ -6127,6 +6839,12 @@ "node": ">=0.10.0" } }, + "node_modules/workerpool": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.0.tgz", + "integrity": "sha512-toV7q9rWNYha963Pl/qyeZ6wG+3nnsyvolaNUS8+R5Wtw6qJPTxIlOP1ZSvcGhEJw+l3HMMmtiNo9Gl61G4GVg==", + "dev": true + }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -6280,6 +6998,45 @@ "node": ">=10" } }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yargs-unparser/node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/yargs/node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -6298,6 +7055,18 @@ "node": ">=6" } }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/z-schema": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/z-schema/-/z-schema-4.2.3.tgz", @@ -6637,6 +7406,41 @@ "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", "dev": true }, + "@sinonjs/commons": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", + "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/fake-timers": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-7.1.2.tgz", + "integrity": "sha512-iQADsW4LBMISqZ6Ci1dupJL9pprqwcVFTcOsEmQOEhW+KLCVn/Y4Jrvg2k19fIHCp+iFprriYPTdRcQR8NbUPg==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0" + } + }, + "@sinonjs/samsam": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-6.0.2.tgz", + "integrity": "sha512-jxPRPp9n93ci7b8hMfJOFDPRLFYadN6FSpeROFTR4UNF4i5b+EK6m4QXPO46BDhFgRy1JuS87zAnFOzCUwMJcQ==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.6.0", + "lodash.get": "^4.4.2", + "type-detect": "^4.0.8" + } + }, + "@sinonjs/text-encoding": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz", + "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", + "dev": true + }, "@szmarczak/http-timer": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", @@ -6663,6 +7467,12 @@ "@types/node": "*" } }, + "@types/chai": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.0.tgz", + "integrity": "sha512-/ceqdqeRraGolFTcfoXNiqjyQhZzbINDngeoAq9GoHa8PPK1yNzTaxWjA6BFWp5Ua9JpXEMSS4s5i9tS0hOJtw==", + "dev": true + }, "@types/connect": { "version": "3.4.34", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.34.tgz", @@ -6709,11 +7519,24 @@ "integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==", "dev": true }, + "@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", + "dev": true, + "optional": true + }, "@types/mime": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.3.tgz", "integrity": "sha512-Jus9s4CDbqwocc5pOAnh8ShfrnMcPHuJYzVcSUU7lrh8Ni5HuIqX3oilL86p3dlTrk0LzHRCgA/GQ7uNCw6l2Q==" }, + "@types/mocha": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.0.0.tgz", + "integrity": "sha512-scN0hAWyLVAvLR9AyW7HoFF5sJZglyBsbPuHO4fv7JRvfmPBMfp1ozWqOf/e4wwPNxezBZXRfWzMb6iFLgEVRA==", + "dev": true + }, "@types/mongodb": { "version": "3.6.3", "resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.6.3.tgz", @@ -6734,9 +7557,9 @@ } }, "@types/node": { - "version": "14.14.21", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.21.tgz", - "integrity": "sha512-cHYfKsnwllYhjOzuC5q1VpguABBeecUp24yFluHpn/BQaVxB1CuQ1FSRZCzrPxrkIfWISXV2LbeoBthLWg0+0A==" + "version": "14.18.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.5.tgz", + "integrity": "sha512-LMy+vDDcQR48EZdEx5wRX1q/sEl6NdGuHXPnfeL8ixkwCOSZ2qnIyIZmcCbdX0MeRqHhAcHmX+haCbrS8Run+A==" }, "@types/qs": { "version": "6.9.5", @@ -6757,6 +7580,15 @@ "@types/node": "*" } }, + "@types/sinon": { + "version": "10.0.6", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-10.0.6.tgz", + "integrity": "sha512-6EF+wzMWvBNeGrfP3Nx60hhx+FfwSg1JJBLAAP/IdIUq0EYkqCYf70VT3PhuhPX9eLD+Dp+lNdpb/ZeHG8Yezg==", + "dev": true, + "requires": { + "@sinonjs/fake-timers": "^7.1.0" + } + }, "@types/tcp-ping": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/@types/tcp-ping/-/tcp-ping-0.1.1.tgz", @@ -6956,6 +7788,12 @@ "eslint-visitor-keys": "^2.0.0" } }, + "@ungap/promise-all-settled": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", + "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", + "dev": true + }, "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -7044,9 +7882,9 @@ } }, "anymatch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", - "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", "dev": true, "requires": { "normalize-path": "^3.0.0", @@ -7079,6 +7917,18 @@ "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true }, + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true + }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true + }, "astral-regex": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", @@ -7224,6 +8074,12 @@ "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=" }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, "bson": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.6.tgz", @@ -7321,6 +8177,20 @@ "resolved": "https://registry.npmjs.org/catbuffer-typescript/-/catbuffer-typescript-1.0.1.tgz", "integrity": "sha512-IyC2bmBEMRY96/NMsAer+qMTSa6yAwKfGIbpYPDPnlSb4UguNOlSabbCFH0CDQfhWuO6wqH97xGCuB4qY3OCwA==" }, + "chai": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.4.tgz", + "integrity": "sha512-yS5H68VYOCtN1cjfwumDSuzn/9c+yza4f3reKXlE5rUg7SFcCEy90gJvydNgOYtblyf4Zi6jIWRnXOgErta0KA==", + "dev": true, + "requires": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "pathval": "^1.1.1", + "type-detect": "^4.0.5" + } + }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -7360,20 +8230,26 @@ "integrity": "sha512-6dVyOOYjpfFcL1Y4qChrAoQLRHvj2ziyhcm0QJlhOcAhykL/k1kTUPbeo+87MNRTRdk2OIIsIXbuF3x2wi5EXg==", "dev": true }, + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true + }, "chokidar": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.0.tgz", - "integrity": "sha512-JgQM9JS92ZbFR4P90EvmzNpSGhpPBGBSj10PILeDyYFwp4h2/D9OM03wsJ4zW1fEp4ka2DGrnUeD7FuvQ2aZ2Q==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", + "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", "dev": true, "requires": { - "anymatch": "~3.1.1", + "anymatch": "~3.1.2", "braces": "~3.0.2", - "fsevents": "~2.3.1", - "glob-parent": "~5.1.0", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", - "readdirp": "~3.5.0" + "readdirp": "~3.6.0" } }, "ci-info": { @@ -7691,6 +8567,15 @@ "mimic-response": "^1.0.0" } }, + "deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "dev": true, + "requires": { + "type-detect": "^4.0.0" + } + }, "deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", @@ -8280,6 +9165,12 @@ "path-exists": "^4.0.0" } }, + "flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true + }, "flat-cache": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", @@ -8334,9 +9225,9 @@ "dev": true }, "fsevents": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.1.tgz", - "integrity": "sha512-YR47Eg4hChJGAB1O3yEAOkGO+rlzutoICGqGo9EZ4lKWokzZRSyIW1QmTzqjtw8MJdj9srP869CuWw/hyzSiBw==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", "dev": true, "optional": true }, @@ -8357,6 +9248,12 @@ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true }, + "get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "dev": true + }, "get-stdin": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", @@ -8461,6 +9358,12 @@ "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", "dev": true }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -8514,6 +9417,12 @@ "minimalistic-assert": "^1.0.1" } }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, "hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", @@ -8772,6 +9681,12 @@ "integrity": "sha512-/2UGPSgmtqwo1ktx8NDHjuPwZWmHhO+gj0f93EkhLB5RgW9RZevWYYlIkS6zePc6U2WpOdQYIwHe9YC4DWEBVg==", "dev": true }, + "is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true + }, "is-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", @@ -8881,6 +9796,16 @@ "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", "dev": true }, + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "optional": true, + "requires": { + "minimist": "^1.2.0" + } + }, "jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -8891,6 +9816,12 @@ "universalify": "^2.0.0" } }, + "just-extend": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", + "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", + "dev": true + }, "kareem": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.2.tgz", @@ -9170,6 +10101,183 @@ "minimist": "^1.2.5" } }, + "mocha": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.4.0.tgz", + "integrity": "sha512-hJaO0mwDXmZS4ghXsvPVriOhsxQ7ofcpQdm8dE+jISUOKopitvnXFQmpRR7jd2K6VBG6E26gU3IAbXXGIbu4sQ==", + "dev": true, + "requires": { + "@ungap/promise-all-settled": "1.1.2", + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.1", + "debug": "4.3.1", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.1.6", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "4.0.0", + "log-symbols": "4.0.0", + "minimatch": "3.0.4", + "ms": "2.1.3", + "nanoid": "3.1.20", + "serialize-javascript": "5.0.1", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "which": "2.0.2", + "wide-align": "1.1.3", + "workerpool": "6.1.0", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "dependencies": { + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "chokidar": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", + "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", + "dev": true, + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.3.1", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.5.0" + } + }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + }, + "dependencies": { + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "js-yaml": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.0.0.tgz", + "integrity": "sha512-pqon0s+4ScYUvX30wxQi3PogGFAlUyH0awepWvwkj4jD4v+ova3RiYw8bmA6x2rDrEaj8i/oWKoRxpVNW+Re8Q==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "log-symbols": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", + "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", + "dev": true, + "requires": { + "chalk": "^4.0.0" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "readdirp": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", + "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true + } + } + }, "module-alias": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/module-alias/-/module-alias-2.2.2.tgz", @@ -9275,6 +10383,12 @@ "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", "dev": true }, + "nanoid": { + "version": "3.1.20", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.20.tgz", + "integrity": "sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw==", + "dev": true + }, "natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -9286,6 +10400,36 @@ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" }, + "nise": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.0.tgz", + "integrity": "sha512-W5WlHu+wvo3PaKLsJJkgPup2LrsXCcm7AWwyNZkUnn5rwPkuPBi3Iwk5SQtN0mv+K65k7nKKjwNQ30wg3wLAQQ==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0", + "@sinonjs/fake-timers": "^7.0.4", + "@sinonjs/text-encoding": "^0.7.1", + "just-extend": "^4.0.2", + "path-to-regexp": "^1.7.0" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "dev": true, + "requires": { + "isarray": "0.0.1" + } + } + } + }, "node-fetch": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", @@ -9692,6 +10836,12 @@ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "dev": true }, + "pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true + }, "picomatch": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", @@ -9903,9 +11053,9 @@ } }, "readdirp": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", - "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", "dev": true, "requires": { "picomatch": "^2.2.1" @@ -10136,6 +11286,15 @@ } } }, + "serialize-javascript": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", + "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, "serve-static": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", @@ -10228,6 +11387,52 @@ "is-arrayish": "^0.3.1" } }, + "sinon": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-12.0.1.tgz", + "integrity": "sha512-iGu29Xhym33ydkAT+aNQFBINakjq69kKO6ByPvTsm3yyIACfyQttRTP03aBP/I8GfhFmLzrnKwNNkr0ORb1udg==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.8.3", + "@sinonjs/fake-timers": "^8.1.0", + "@sinonjs/samsam": "^6.0.2", + "diff": "^5.0.0", + "nise": "^5.1.0", + "supports-color": "^7.2.0" + }, + "dependencies": { + "@sinonjs/fake-timers": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz", + "integrity": "sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0" + } + }, + "diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -10330,6 +11535,13 @@ "ansi-regex": "^5.0.0" } }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true, + "optional": true + }, "strip-hex-prefix": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/strip-hex-prefix/-/strip-hex-prefix-1.0.0.tgz", @@ -10588,6 +11800,46 @@ "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz", "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==" }, + "ts-mocha": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/ts-mocha/-/ts-mocha-8.0.0.tgz", + "integrity": "sha512-Kou1yxTlubLnD5C3unlCVO7nh0HERTezjoVhVw/M5S1SqoUec0WgllQvPk3vzPMc6by8m6xD1uR1yRf8lnVUbA==", + "dev": true, + "requires": { + "ts-node": "7.0.1", + "tsconfig-paths": "^3.5.0" + }, + "dependencies": { + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "ts-node": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-7.0.1.tgz", + "integrity": "sha512-BVwVbPJRspzNh2yfslyT1PSbl5uIk03EZlb493RKHN4qej/D06n1cEhjlOJG69oFsE7OT8XjpTUcYf6pKTLMhw==", + "dev": true, + "requires": { + "arrify": "^1.0.0", + "buffer-from": "^1.1.0", + "diff": "^3.1.0", + "make-error": "^1.1.1", + "minimist": "^1.2.0", + "mkdirp": "^0.5.1", + "source-map-support": "^0.5.6", + "yn": "^2.0.0" + } + }, + "yn": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yn/-/yn-2.0.0.tgz", + "integrity": "sha1-5a2ryKz0CPY4X8dklWhMiOavaJo=", + "dev": true + } + } + }, "ts-node": { "version": "8.10.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.10.2.tgz", @@ -10601,6 +11853,19 @@ "yn": "3.1.1" } }, + "tsconfig-paths": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.12.0.tgz", + "integrity": "sha512-e5adrnOYT6zqVnWqZu7i/BQ3BnhzvGbjEjejFXO20lKIKpwTaupkCPgEfv4GZK1IBciJUEhYs3J3p75FdaTFVg==", + "dev": true, + "optional": true, + "requires": { + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.0", + "strip-bom": "^3.0.0" + } + }, "tslib": { "version": "1.13.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", @@ -10687,6 +11952,12 @@ "prelude-ls": "^1.2.1" } }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, "type-fest": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", @@ -10935,6 +12206,48 @@ "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", "dev": true }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, + "requires": { + "string-width": "^1.0.2 || 2" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, "widest-line": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", @@ -10987,6 +12300,12 @@ "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", "dev": true }, + "workerpool": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.0.tgz", + "integrity": "sha512-toV7q9rWNYha963Pl/qyeZ6wG+3nnsyvolaNUS8+R5Wtw6qJPTxIlOP1ZSvcGhEJw+l3HMMmtiNo9Gl61G4GVg==", + "dev": true + }, "wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -11113,12 +12432,44 @@ "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", "dev": true }, + "yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "requires": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "dependencies": { + "camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true + }, + "decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true + } + } + }, "yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", "dev": true }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true + }, "z-schema": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/z-schema/-/z-schema-4.2.3.tgz", diff --git a/package.json b/package.json index 631f36b..243e16a 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,8 @@ "lint": "eslint --cache src/ --ext .ts", "lint:fix": "eslint src/ --ext .ts --fix", "style:fix": "npm run prettier && npm run lint:fix", - "test:openapi": "swagger-cli validate openapi/openapi.yml" + "test:openapi": "swagger-cli validate openapi/openapi.yml", + "test": "ts-mocha --paths ./test/**.test.ts" }, "main": "dist/app.js", "typings": "dist/index.d.ts", @@ -22,21 +23,27 @@ }, "devDependencies": { "@openapitools/openapi-generator-cli": "^2.4.15", + "@types/chai": "^4.3.0", "@types/humanize-duration": "^3.27.0", + "@types/mocha": "^9.0.0", "@types/mongoose": "^5.7.14", - "@types/node": "^14.14.10", + "@types/sinon": "^10.0.6", "@types/winston": "^2.4.4", "@types/ws": "^8.2.0", "@typescript-eslint/eslint-plugin": "^4.4.0", "@typescript-eslint/parser": "^4.4.0", + "chai": "^4.3.4", "eslint": "^7.10.0", "eslint-config-prettier": "^6.12.0", "eslint-plugin-prettier": "^3.1.4", + "mocha": "^8.4.0", "nodemon": "^2.0.3", "openapi-to-postmanv2": "^2.6.0", "prettier": "^2.1.2", "shx": "^0.3.3", + "sinon": "^12.0.1", "swagger-cli": "^4.0.4", + "ts-mocha": "^8.0.0", "ts-node": "^8.10.1", "tslint": "^6.1.2", "typescript": "^3.8.3", diff --git a/src/config/config.json b/src/config/config.json index a158d10..fcb03a9 100644 --- a/src/config/config.json +++ b/src/config/config.json @@ -20,5 +20,6 @@ "NODE_PEERS_REQUEST_CHUNK_SIZE": 50, "CHAIN_HEIGHT_REQUEST_CHUNK_SIZE": 10, "PREFERRED_NODES": [], - "MIN_PARTNER_NODE_VERSION": 16777728 + "MIN_PARTNER_NODE_VERSION": 16777728, + "KEEP_STALE_NODES_FOR_HOURS": 72 } diff --git a/src/config/index.ts b/src/config/index.ts index c109bc6..448d0af 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -25,6 +25,7 @@ interface Monitor { API_NODE_PORT: number; PEER_NODE_PORT: number; REQUEST_TIMEOUT: number; + KEEP_STALE_NODES_FOR_HOURS: number; } export interface Config { @@ -60,6 +61,7 @@ export const monitor: Monitor = { API_NODE_PORT: Number(process.env.API_NODE_PORT) || config.API_NODE_PORT, PEER_NODE_PORT: Number(process.env.PEER_NODE_PORT) || config.PEER_NODE_PORT, REQUEST_TIMEOUT: Number(process.env.REQUEST_TIMEOUT) || config.REQUEST_TIMEOUT, + KEEP_STALE_NODES_FOR_HOURS: Number(process.env.KEEP_STALE_NODES_FOR_HOURS) || config.KEEP_STALE_NODES_FOR_HOURS, }; export const verifyConfig = (cfg: Config): boolean => { diff --git a/src/constants.ts b/src/constants.ts new file mode 100644 index 0000000..eafbad3 --- /dev/null +++ b/src/constants.ts @@ -0,0 +1,6 @@ +export class Constants { + public static TIME_UNIT_SECOND = 1000; + public static TIME_UNIT_MINUTE = Constants.TIME_UNIT_SECOND * 60; + public static TIME_UNIT_HOUR = Constants.TIME_UNIT_MINUTE * 60; + public static TIME_UNIT_DAY = Constants.TIME_UNIT_HOUR * 24; +} diff --git a/src/models/Node.ts b/src/models/Node.ts index 580fe41..c2aa647 100644 --- a/src/models/Node.ts +++ b/src/models/Node.ts @@ -16,6 +16,7 @@ export interface INode { peerStatus?: PeerStatus; apiStatus?: ApiStatus; hostDetail?: IHostDetail; + lastAvailable?: Date; } export interface NodeDocument extends INode, Document {} @@ -211,6 +212,10 @@ const NodeSchema: Schema = new Schema({ }, required: false, }, + lastAvailable: { + type: Date, + required: false, + }, }); NodeSchema.index( diff --git a/src/services/NodeMonitor.ts b/src/services/NodeMonitor.ts index 11b8f89..6d68173 100644 --- a/src/services/NodeMonitor.ts +++ b/src/services/NodeMonitor.ts @@ -14,6 +14,8 @@ import { Logger } from '@src/infrastructure'; import { INode, validateNodeModel } from '@src/models/Node'; import { symbol, monitor } from '@src/config'; import { isAPIRole, isPeerRole, basename, showDuration, runTaskInChunks } from '@src/utils'; +import humanizeDuration = require('humanize-duration'); +import { Constants } from '@src/constants'; const logger: winston.Logger = Logger.getLogger(basename(__filename)); @@ -241,6 +243,7 @@ export class NodeMonitor { logger.info(`Update collection`); const prevNodeList = await DataBase.getNodeList(); + this.nodeList = this.removeUnavailableNodes(this.nodeList); try { await DataBase.updateNodeList(this.nodeList); await DataBase.updateNodesStats(this.nodesStats); @@ -251,7 +254,31 @@ export class NodeMonitor { } else logger.error(`Failed to update collection. Collection length = ${this.nodeList.length}`); }; - private cacheCollection = async (): Promise => { + private removeUnavailableNodes(nodes: INode[]): INode[] { + const unavailableNodes = nodes.filter((n) => !this.checkNodeAvailable(n)); + + logger.info( + `[updateCollection] Removing unavailable nodes[${unavailableNodes + .map((n) => n.host) + .join(', ')}], available ones in the last ${humanizeDuration( + monitor.KEEP_STALE_NODES_FOR_HOURS * Constants.TIME_UNIT_HOUR, + )} are kept.`, + ); + return nodes + .filter((n) => !unavailableNodes.some((un) => un.publicKey === n.publicKey)) + .map((n) => ({ ...n, lastAvailable: new Date() })); + } + + private checkNodeAvailable = (node: INode): boolean => { + return !( + isAPIRole(node.roles) && + !node.apiStatus?.isAvailable && + node.lastAvailable && + new Date().getTime() > node.lastAvailable.getTime() + monitor.KEEP_STALE_NODES_FOR_HOURS * 1000 * 60 * 60 + ); + }; + + private async cacheCollection(): Promise { try { const nodeList = await DataBase.getNodeList(); @@ -259,9 +286,9 @@ export class NodeMonitor { } catch (e) { logger.error('Failed to cache Node collection to memory. ' + e.message); } - }; + } - private fetchAndSetNetworkInfo = async (): Promise => { + private async fetchAndSetNetworkInfo(): Promise { for (const nodeUrl of symbol.NODES) { const url = new URL(nodeUrl); const hostUrl = await ApiNodeService.buildHostUrl(url.hostname); @@ -278,7 +305,7 @@ export class NodeMonitor { } logger.info(`Network identifier not found in ${symbol.NODES}, using default ${this.networkIdentifier}`); - }; + } private addNodesToList = (nodes: INode[]) => { nodes.forEach((node: INode) => { diff --git a/test/NodeMonitor.test.ts b/test/NodeMonitor.test.ts new file mode 100644 index 0000000..7c774c2 --- /dev/null +++ b/test/NodeMonitor.test.ts @@ -0,0 +1,93 @@ +import { Constants } from '@src/constants'; +import { NodeMonitor } from '@src/services/NodeMonitor'; +import { expect } from 'chai'; +import { stub } from 'sinon'; +import { RoleType } from 'symbol-sdk'; + +describe('NodeMonitor', () => { + describe('removeUnavailableNodes', () => { + stub(NodeMonitor.prototype, 'cacheCollection' as any); + const nodeMonitor = new NodeMonitor(0); + + it('should remove stale nodes and keep fresh ones', () => { + const nodes = [ + { + publicKey: 'pkFresh', + host: 'hostFresh', + lastAvailable: new Date(new Date().getTime() - 1 * Constants.TIME_UNIT_DAY), + roles: RoleType.ApiNode + RoleType.PeerNode, + apiStatus: { + isAvailable: false, + }, + }, + { + publicKey: 'pkStale', + host: 'hostStale', + lastAvailable: new Date(new Date().getTime() - 4 * Constants.TIME_UNIT_DAY), + roles: RoleType.ApiNode + RoleType.PeerNode, + apiStatus: { + isAvailable: false, + }, + }, + ]; + const result = (nodeMonitor as any).removeUnavailableNodes(nodes); + + expect(result.length).to.be.equal(1); + expect(result[0].publicKey).to.be.equal('pkFresh'); + }); + + it('should keep a resurrecting node', () => { + const nodes = [ + { + publicKey: 'pkFresh', + host: 'hostFresh', + lastAvailable: new Date(new Date().getTime() - 3 * Constants.TIME_UNIT_DAY + 1 * Constants.TIME_UNIT_HOUR), + roles: RoleType.ApiNode + RoleType.PeerNode, + apiStatus: { + isAvailable: true, + }, + }, + { + publicKey: 'pkStale', + host: 'hostStale', + lastAvailable: new Date(new Date().getTime() - 4 * Constants.TIME_UNIT_DAY), + roles: RoleType.ApiNode + RoleType.PeerNode, + apiStatus: { + isAvailable: false, + }, + }, + ]; + const result = (nodeMonitor as any).removeUnavailableNodes(nodes); + + expect(result.length).to.be.equal(1); + expect(result[0].publicKey).to.be.equal('pkFresh'); + }); + + it('should keep peer nodes and remove stale api nodes', () => { + const nodes = [ + { + publicKey: 'pkPeer', + host: 'hostPeer', + lastAvailable: new Date(new Date().getTime() - 4 * Constants.TIME_UNIT_DAY), + roles: RoleType.PeerNode, + apiStatus: { + isAvailable: false, + }, + }, + { + publicKey: 'pkApi', + host: 'hostApi', + lastAvailable: new Date(new Date().getTime() - 4 * Constants.TIME_UNIT_DAY), + roles: RoleType.ApiNode + RoleType.PeerNode, + apiStatus: { + isAvailable: false, + }, + }, + ]; + const result = (nodeMonitor as any).removeUnavailableNodes(nodes); + + expect(result.length).to.be.equal(1); + expect(result[0].publicKey).to.be.equal('pkPeer'); + }); + }); +}); diff --git a/tsconfig.json b/tsconfig.json index eb9c35a..36d4b2a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,16 +13,18 @@ "importHelpers": true, "types": [ "node", + "mocha" ], "typeRoots": [ "node_modules/@types" ], "paths": { - "@src/*": [ "src/*" ], + "@src/*": [ "src/*" ] } }, "include": [ - "./src/**/*.ts" + "./src/**/*.ts", + "./test/**/*.test.ts" ], "exclude": [ "./src/public/" From 957d3bb26deed670343c06c05349ab540e4e6f03 Mon Sep 17 00:00:00 2001 From: Baha Date: Tue, 11 Jan 2022 21:04:12 +0000 Subject: [PATCH 08/11] dual node is now cosidered active when one is available, unit tests updated --- src/services/NodeMonitor.ts | 21 ++++++-- test/NodeMonitor.test.ts | 100 +++++++++++++++++++++++++++++------- 2 files changed, 97 insertions(+), 24 deletions(-) diff --git a/src/services/NodeMonitor.ts b/src/services/NodeMonitor.ts index 6d68173..cbfb7ce 100644 --- a/src/services/NodeMonitor.ts +++ b/src/services/NodeMonitor.ts @@ -270,11 +270,22 @@ export class NodeMonitor { } private checkNodeAvailable = (node: INode): boolean => { - return !( - isAPIRole(node.roles) && - !node.apiStatus?.isAvailable && - node.lastAvailable && - new Date().getTime() > node.lastAvailable.getTime() + monitor.KEEP_STALE_NODES_FOR_HOURS * 1000 * 60 * 60 + let available = true; + + if (isAPIRole(node.roles) && isPeerRole(node.roles)) { + // in dual node mode, we consider the node is available if any of the two (REST and Peer) is available + available = !!node.apiStatus?.isAvailable || !!node.peerStatus?.isAvailable; + } else if (isAPIRole(node.roles)) { + available = !!node.apiStatus?.isAvailable; + } else if (isPeerRole(node.roles)) { + available = !!node.peerStatus?.isAvailable; + } + return ( + available || + !( + !!node.lastAvailable && + new Date().getTime() > node.lastAvailable.getTime() + monitor.KEEP_STALE_NODES_FOR_HOURS * Constants.TIME_UNIT_HOUR + ) ); }; diff --git a/test/NodeMonitor.test.ts b/test/NodeMonitor.test.ts index 7c774c2..059cf1d 100644 --- a/test/NodeMonitor.test.ts +++ b/test/NodeMonitor.test.ts @@ -1,3 +1,4 @@ +import { monitor } from '@src/config'; import { Constants } from '@src/constants'; import { NodeMonitor } from '@src/services/NodeMonitor'; import { expect } from 'chai'; @@ -5,8 +6,10 @@ import { stub } from 'sinon'; import { RoleType } from 'symbol-sdk'; describe('NodeMonitor', () => { - describe('removeUnavailableNodes', () => { + describe('removeUnavailableNodes when KEEP_STALE_NODES_FOR_HOURS is 3 days', () => { stub(NodeMonitor.prototype, 'cacheCollection' as any); + stub(monitor, 'KEEP_STALE_NODES_FOR_HOURS').value(72); // 3 days + const nodeMonitor = new NodeMonitor(0); it('should remove stale nodes and keep fresh ones', () => { @@ -36,47 +39,106 @@ describe('NodeMonitor', () => { expect(result[0].publicKey).to.be.equal('pkFresh'); }); - it('should keep a resurrecting node', () => { - const nodes = [ + it("dual node - should refresh the stale node if at least one of two(API, Peer)'s status is available else remove the node", () => { + const getNode = (apiAvailable: boolean, peerAvailable: boolean) => [ { publicKey: 'pkFresh', host: 'hostFresh', - lastAvailable: new Date(new Date().getTime() - 3 * Constants.TIME_UNIT_DAY + 1 * Constants.TIME_UNIT_HOUR), + lastAvailable: new Date(new Date().getTime() - 4 * Constants.TIME_UNIT_DAY), roles: RoleType.ApiNode + RoleType.PeerNode, apiStatus: { - isAvailable: true, + isAvailable: apiAvailable, + }, + peerStatus: { + isAvailable: peerAvailable, }, }, + ]; + const resultApiOK_PeerNOT = (nodeMonitor as any).removeUnavailableNodes(getNode(true, false)); + + expect(resultApiOK_PeerNOT.length).to.be.equal(1); + expect(resultApiOK_PeerNOT[0].publicKey).to.be.equal('pkFresh'); + + const resultApiNOT_PeerOK = (nodeMonitor as any).removeUnavailableNodes(getNode(false, true)); + + expect(resultApiNOT_PeerOK.length).to.be.equal(1); + expect(resultApiNOT_PeerOK[0].publicKey).to.be.equal('pkFresh'); + + const resultApiOK_PeerOK = (nodeMonitor as any).removeUnavailableNodes(getNode(true, true)); + + expect(resultApiOK_PeerOK.length).to.be.equal(1); + expect(resultApiOK_PeerOK[0].publicKey).to.be.equal('pkFresh'); + + const resultApiNOT_PeerNOT = (nodeMonitor as any).removeUnavailableNodes(getNode(false, false)); + + expect(resultApiNOT_PeerNOT.length).to.be.equal(0); + }); + + it('api only - should keep the node in the list when fresh and available', () => { + const nodes = [ { - publicKey: 'pkStale', - host: 'hostStale', - lastAvailable: new Date(new Date().getTime() - 4 * Constants.TIME_UNIT_DAY), - roles: RoleType.ApiNode + RoleType.PeerNode, + publicKey: 'pkApiOnly', + host: 'apiOnly', + lastAvailable: new Date(), + roles: RoleType.ApiNode, apiStatus: { - isAvailable: false, + isAvailable: true, }, }, ]; const result = (nodeMonitor as any).removeUnavailableNodes(nodes); expect(result.length).to.be.equal(1); - expect(result[0].publicKey).to.be.equal('pkFresh'); + expect(result[0].publicKey).to.be.equal('pkApiOnly'); }); - it('should keep peer nodes and remove stale api nodes', () => { + it('peer only - should keep the node in the list when fresh and available', () => { const nodes = [ { - publicKey: 'pkPeer', - host: 'hostPeer', - lastAvailable: new Date(new Date().getTime() - 4 * Constants.TIME_UNIT_DAY), + publicKey: 'pkPeerOnly', + host: 'peerOnly', + lastAvailable: new Date(), roles: RoleType.PeerNode, + peerStatus: { + isAvailable: true, + }, + }, + ]; + const result = (nodeMonitor as any).removeUnavailableNodes(nodes); + + expect(result.length).to.be.equal(1); + expect(result[0].publicKey).to.be.equal('pkPeerOnly'); + }); + + it('voting only - should keep the node in the list', () => { + const nodes = [ + { + publicKey: 'pkVotingOnly', + host: 'votingOnly', + lastAvailable: new Date(new Date().getTime() - 3 * Constants.TIME_UNIT_DAY + 1 * Constants.TIME_UNIT_HOUR), + roles: RoleType.VotingNode, + }, + ]; + const result = (nodeMonitor as any).removeUnavailableNodes(nodes); + + expect(result.length).to.be.equal(1); + expect(result[0].publicKey).to.be.equal('pkVotingOnly'); + }); + + it('should keep a resurrecting node', () => { + const nodes = [ + { + publicKey: 'pkFresh', + host: 'hostFresh', + lastAvailable: new Date(new Date().getTime() - 3 * Constants.TIME_UNIT_DAY + 1 * Constants.TIME_UNIT_HOUR), + roles: RoleType.ApiNode + RoleType.PeerNode, apiStatus: { - isAvailable: false, + isAvailable: true, }, }, { - publicKey: 'pkApi', - host: 'hostApi', + publicKey: 'pkStale', + host: 'hostStale', lastAvailable: new Date(new Date().getTime() - 4 * Constants.TIME_UNIT_DAY), roles: RoleType.ApiNode + RoleType.PeerNode, apiStatus: { @@ -87,7 +149,7 @@ describe('NodeMonitor', () => { const result = (nodeMonitor as any).removeUnavailableNodes(nodes); expect(result.length).to.be.equal(1); - expect(result[0].publicKey).to.be.equal('pkPeer'); + expect(result[0].publicKey).to.be.equal('pkFresh'); }); }); }); From a5a17ee58f6c2e63f527edebfcba9470b3beeb47 Mon Sep 17 00:00:00 2001 From: Baha Date: Fri, 14 Jan 2022 07:12:43 +0000 Subject: [PATCH 09/11] fix: tsconfig.build.json added to exclude tests from build but keep for tests --- package.json | 2 +- tsconfig.build.json | 31 +++++++++++++++++++++++++++++++ tsconfig.json | 30 ++---------------------------- 3 files changed, 34 insertions(+), 29 deletions(-) create mode 100644 tsconfig.build.json diff --git a/package.json b/package.json index 243e16a..52b40d3 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "scripts": { "dev": "nodemon", "debug": "node --inspect-brk=0.0.0.0:9229 --require ts-node/register src/app.ts", - "build": "shx rm -rf ./dist/ && tsc && shx cp -r openapi dist/openapi", + "build": "shx rm -rf ./dist/ && tsc -p tsconfig.build.json && shx cp -r openapi dist/openapi", "build:clients": "bash scripts/build-clients.sh", "start": "node dist/app.js", "version": "echo $npm_package_version", diff --git a/tsconfig.build.json b/tsconfig.build.json new file mode 100644 index 0000000..f092552 --- /dev/null +++ b/tsconfig.build.json @@ -0,0 +1,31 @@ +{ + "compilerOptions": { + "module": "commonjs", + "strict": true, + "baseUrl": "./", + "outDir": "dist", + "removeComments": true, + "experimentalDecorators": true, + "target": "es6", + "emitDecoratorMetadata": true, + "moduleResolution": "node", + "resolveJsonModule": true, + "importHelpers": true, + "types": [ + "node", + "mocha" + ], + "typeRoots": [ + "node_modules/@types" + ], + "paths": { + "@src/*": [ "src/*" ] + } + }, + "include": [ + "./src/**/*.ts", + ], + "exclude": [ + "./src/public/" + ] +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 36d4b2a..a63b5e9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,32 +1,6 @@ { - "compilerOptions": { - "module": "commonjs", - "strict": true, - "baseUrl": "./", - "outDir": "dist", - "removeComments": true, - "experimentalDecorators": true, - "target": "es6", - "emitDecoratorMetadata": true, - "moduleResolution": "node", - "resolveJsonModule": true, - "importHelpers": true, - "types": [ - "node", - "mocha" - ], - "typeRoots": [ - "node_modules/@types" - ], - "paths": { - "@src/*": [ "src/*" ] - } - }, + "extends": "./tsconfig.build.json", "include": [ - "./src/**/*.ts", - "./test/**/*.test.ts" + "./test/**/*.test.ts", ], - "exclude": [ - "./src/public/" - ] } \ No newline at end of file From bd2aa3a9fb9a95df4e8f87284e5294ffd1915adf Mon Sep 17 00:00:00 2001 From: Baha Date: Fri, 14 Jan 2022 22:18:15 +0000 Subject: [PATCH 10/11] fix: stale nodes lastAvailable field update issue fixed --- src/services/NodeMonitor.ts | 62 +++++++++++++++++++++++-------------- src/utils.ts | 10 ++++++ test/NodeMonitor.test.ts | 27 +++++++++------- 3 files changed, 64 insertions(+), 35 deletions(-) diff --git a/src/services/NodeMonitor.ts b/src/services/NodeMonitor.ts index cbfb7ce..0158ce8 100644 --- a/src/services/NodeMonitor.ts +++ b/src/services/NodeMonitor.ts @@ -13,7 +13,7 @@ import { Logger } from '@src/infrastructure'; import { INode, validateNodeModel } from '@src/models/Node'; import { symbol, monitor } from '@src/config'; -import { isAPIRole, isPeerRole, basename, showDuration, runTaskInChunks } from '@src/utils'; +import { isAPIRole, isPeerRole, basename, showDuration, runTaskInChunks, splitByPredicate } from '@src/utils'; import humanizeDuration = require('humanize-duration'); import { Constants } from '@src/constants'; @@ -95,6 +95,13 @@ export class NodeMonitor { logger.info(`[getNodeList] Getting node list...`); const startTime = new Date().getTime(); + // Fetch node list from database + const nodesFromDb = (await DataBase.getNodeList().then((nodes) => nodes.map((n) => n.toJSON()))) || []; + + logger.info(`[getNodeList] Nodes count from DB: ${nodesFromDb.length}`); + // adding the nodes from DB to the node list + this.addNodesToList(nodesFromDb); + // Fetch node list from config nodes logger.info(`[getNodeList] Initial node list: ${symbol.NODES.join(', ')}`); for (const nodeUrl of symbol.NODES) { @@ -103,13 +110,6 @@ export class NodeMonitor { this.addNodesToList(peers); } - // Fetch node list from database - const nodesFromDb = (await DataBase.getNodeList().then((nodes) => nodes.map((n) => n.toJSON()))) || []; - - logger.info(`[getNodeList] Nodes count from DB: ${nodesFromDb.length}`); - // adding the nodes from DB to the node list - this.addNodesToList(nodesFromDb); - await this.fetchAndAddNodeListPeers(); logger.info( @@ -243,7 +243,7 @@ export class NodeMonitor { logger.info(`Update collection`); const prevNodeList = await DataBase.getNodeList(); - this.nodeList = this.removeUnavailableNodes(this.nodeList); + this.nodeList = this.removeStaleNodesAndUpdateLastAvailable(this.nodeList); try { await DataBase.updateNodeList(this.nodeList); await DataBase.updateNodesStats(this.nodesStats); @@ -254,21 +254,32 @@ export class NodeMonitor { } else logger.error(`Failed to update collection. Collection length = ${this.nodeList.length}`); }; - private removeUnavailableNodes(nodes: INode[]): INode[] { - const unavailableNodes = nodes.filter((n) => !this.checkNodeAvailable(n)); + private removeStaleNodesAndUpdateLastAvailable(nodes: INode[]): INode[] { + let { filtered: availableNodes, unfiltered: unavailableNodes } = splitByPredicate((n) => this.checkNodeAvailable(n), nodes); + + // set last available time for available nodes + availableNodes = availableNodes.map((n) => ({ ...n, lastAvailable: new Date() })); + + let { filtered: staleNodes, unfiltered: soonTobeStaleNodes } = splitByPredicate((n) => this.checkNodeStale(n), unavailableNodes); logger.info( - `[updateCollection] Removing unavailable nodes[${unavailableNodes + `[updateCollection] Removing stale nodes[${staleNodes .map((n) => n.host) .join(', ')}], available ones in the last ${humanizeDuration( monitor.KEEP_STALE_NODES_FOR_HOURS * Constants.TIME_UNIT_HOUR, )} are kept.`, ); - return nodes - .filter((n) => !unavailableNodes.some((un) => un.publicKey === n.publicKey)) - .map((n) => ({ ...n, lastAvailable: new Date() })); + return [...availableNodes, ...soonTobeStaleNodes]; } + private checkNodeStale = (node: INode): boolean => { + return ( + !this.checkNodeAvailable(node) && + !!node.lastAvailable && + new Date().getTime() > node.lastAvailable.getTime() + monitor.KEEP_STALE_NODES_FOR_HOURS * Constants.TIME_UNIT_HOUR + ); + }; + private checkNodeAvailable = (node: INode): boolean => { let available = true; @@ -280,13 +291,7 @@ export class NodeMonitor { } else if (isPeerRole(node.roles)) { available = !!node.peerStatus?.isAvailable; } - return ( - available || - !( - !!node.lastAvailable && - new Date().getTime() > node.lastAvailable.getTime() + monitor.KEEP_STALE_NODES_FOR_HOURS * Constants.TIME_UNIT_HOUR - ) - ); + return available; }; private async cacheCollection(): Promise { @@ -330,9 +335,18 @@ export class NodeMonitor { const nodeInx = this.nodeList.findIndex((addedNode) => addedNode.publicKey === node.publicKey); if (nodeInx > -1) { - // already in the list then update - this.nodeList[nodeInx] = node; + // already in the list then update and keep the last available time + let lastAvailable = this.nodeList[nodeInx].lastAvailable || node.lastAvailable; + + if (lastAvailable === undefined && !this.checkNodeAvailable(node)) { + lastAvailable = new Date(); + } + + this.nodeList[nodeInx] = { ...node, lastAvailable }; } else { + if (!node.lastAvailable && !this.checkNodeAvailable(node)) { + node.lastAvailable = new Date(); + } this.nodeList.push(node); } }); diff --git a/src/utils.ts b/src/utils.ts index 236970b..20a07c1 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -102,3 +102,13 @@ export const runTaskInChunks = async ( numOfNodesProcessed += chunk.length; } }; + +export const splitByPredicate = (predicate: (item: T) => boolean, arr: T[]): { filtered: T[]; unfiltered: T[] } => { + return arr.reduce( + (res, item: T) => { + res[predicate(item) ? 'filtered' : 'unfiltered'].push(item); + return res; + }, + { filtered: [] as T[], unfiltered: [] as T[] }, + ); +}; diff --git a/test/NodeMonitor.test.ts b/test/NodeMonitor.test.ts index 059cf1d..0217094 100644 --- a/test/NodeMonitor.test.ts +++ b/test/NodeMonitor.test.ts @@ -6,18 +6,19 @@ import { stub } from 'sinon'; import { RoleType } from 'symbol-sdk'; describe('NodeMonitor', () => { - describe('removeUnavailableNodes when KEEP_STALE_NODES_FOR_HOURS is 3 days', () => { + describe('removeStaleNodesAndUpdateLastAvailable when KEEP_STALE_NODES_FOR_HOURS is 3 days', () => { stub(NodeMonitor.prototype, 'cacheCollection' as any); stub(monitor, 'KEEP_STALE_NODES_FOR_HOURS').value(72); // 3 days const nodeMonitor = new NodeMonitor(0); it('should remove stale nodes and keep fresh ones', () => { + const freshNodeLastAvailable = new Date(new Date().getTime() - 1 * Constants.TIME_UNIT_DAY); const nodes = [ { publicKey: 'pkFresh', host: 'hostFresh', - lastAvailable: new Date(new Date().getTime() - 1 * Constants.TIME_UNIT_DAY), + lastAvailable: freshNodeLastAvailable, roles: RoleType.ApiNode + RoleType.PeerNode, apiStatus: { isAvailable: false, @@ -33,10 +34,11 @@ describe('NodeMonitor', () => { }, }, ]; - const result = (nodeMonitor as any).removeUnavailableNodes(nodes); + const result = (nodeMonitor as any).removeStaleNodesAndUpdateLastAvailable(nodes); expect(result.length).to.be.equal(1); expect(result[0].publicKey).to.be.equal('pkFresh'); + expect(result[0].lastAvailable).to.be.eq(freshNodeLastAvailable); }); it("dual node - should refresh the stale node if at least one of two(API, Peer)'s status is available else remove the node", () => { @@ -54,22 +56,25 @@ describe('NodeMonitor', () => { }, }, ]; - const resultApiOK_PeerNOT = (nodeMonitor as any).removeUnavailableNodes(getNode(true, false)); + const resultApiOK_PeerNOT = (nodeMonitor as any).removeStaleNodesAndUpdateLastAvailable(getNode(true, false)); expect(resultApiOK_PeerNOT.length).to.be.equal(1); expect(resultApiOK_PeerNOT[0].publicKey).to.be.equal('pkFresh'); + expect(resultApiOK_PeerNOT[0].lastAvailable).to.be.greaterThan(new Date(new Date().getTime() - 1 * Constants.TIME_UNIT_MINUTE)); - const resultApiNOT_PeerOK = (nodeMonitor as any).removeUnavailableNodes(getNode(false, true)); + const resultApiNOT_PeerOK = (nodeMonitor as any).removeStaleNodesAndUpdateLastAvailable(getNode(false, true)); expect(resultApiNOT_PeerOK.length).to.be.equal(1); expect(resultApiNOT_PeerOK[0].publicKey).to.be.equal('pkFresh'); + expect(resultApiNOT_PeerOK[0].lastAvailable).to.be.greaterThan(new Date(new Date().getTime() - 1 * Constants.TIME_UNIT_MINUTE)); - const resultApiOK_PeerOK = (nodeMonitor as any).removeUnavailableNodes(getNode(true, true)); + const resultApiOK_PeerOK = (nodeMonitor as any).removeStaleNodesAndUpdateLastAvailable(getNode(true, true)); expect(resultApiOK_PeerOK.length).to.be.equal(1); expect(resultApiOK_PeerOK[0].publicKey).to.be.equal('pkFresh'); + expect(resultApiOK_PeerOK[0].lastAvailable).to.be.greaterThan(new Date(new Date().getTime() - 1 * Constants.TIME_UNIT_MINUTE)); - const resultApiNOT_PeerNOT = (nodeMonitor as any).removeUnavailableNodes(getNode(false, false)); + const resultApiNOT_PeerNOT = (nodeMonitor as any).removeStaleNodesAndUpdateLastAvailable(getNode(false, false)); expect(resultApiNOT_PeerNOT.length).to.be.equal(0); }); @@ -86,7 +91,7 @@ describe('NodeMonitor', () => { }, }, ]; - const result = (nodeMonitor as any).removeUnavailableNodes(nodes); + const result = (nodeMonitor as any).removeStaleNodesAndUpdateLastAvailable(nodes); expect(result.length).to.be.equal(1); expect(result[0].publicKey).to.be.equal('pkApiOnly'); @@ -104,7 +109,7 @@ describe('NodeMonitor', () => { }, }, ]; - const result = (nodeMonitor as any).removeUnavailableNodes(nodes); + const result = (nodeMonitor as any).removeStaleNodesAndUpdateLastAvailable(nodes); expect(result.length).to.be.equal(1); expect(result[0].publicKey).to.be.equal('pkPeerOnly'); @@ -119,7 +124,7 @@ describe('NodeMonitor', () => { roles: RoleType.VotingNode, }, ]; - const result = (nodeMonitor as any).removeUnavailableNodes(nodes); + const result = (nodeMonitor as any).removeStaleNodesAndUpdateLastAvailable(nodes); expect(result.length).to.be.equal(1); expect(result[0].publicKey).to.be.equal('pkVotingOnly'); @@ -146,7 +151,7 @@ describe('NodeMonitor', () => { }, }, ]; - const result = (nodeMonitor as any).removeUnavailableNodes(nodes); + const result = (nodeMonitor as any).removeStaleNodesAndUpdateLastAvailable(nodes); expect(result.length).to.be.equal(1); expect(result[0].publicKey).to.be.equal('pkFresh'); From 92a2aa0e40bb0668c6e114cf872632617e3991a1 Mon Sep 17 00:00:00 2001 From: Baha Date: Tue, 18 Jan 2022 15:33:19 +0000 Subject: [PATCH 11/11] Changelog updated for v1.1.4 release --- CHANGELOG.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b119a6..dc533a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,19 @@ All notable changes to this project will be documented in this file. The changelog format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). +## [v1.1.4][v1.1.4] - 18-Jan-2022 + +Package | Version | Link +---|---|--- +REST Core| v2.4.0 | [catapult-rest][catapult-rest@v2.4.0] +SDK Core| v1.0.3 | [symbol-sdk][symbol-sdk@v1.0.3] + +### Fix +- `tsconfig.build.json` added to exclude test files from build but keep for tests [#128](https://github.com/symbol/statistics-service/pull/128) +- `Config.KEEP_STALE_NODES_FOR_HOURS` is added to clean up stale nodes from database [#127](https://github.com/symbol/statistics-service/pull/127) +- Preferred nodes filter fixed, `Config.PREFERRED_NODES` is now empty by default (will be set in the testnet/mainnet environment) [#126](https://github.com/symbol/statistics-service/pull/126) +- Node discovery extended, empty host info bug fixed, logging enhanced [#121](https://github.com/symbol/statistics-service/pull/121) + ## [v1.1.3][v1.1.3] - 30-Nov-2021 Package | Version | Link @@ -142,6 +155,8 @@ REST Core| v2.1.0 | [catapult-rest](https://github.com/nemtech/catapult-rest/rel ### Fixes - Cors error. [#13](https://github.com/nemgrouplimited/symbol-statistics-service/issues/13) +[v1.1.4]: https://github.com/symbol/statistics-service/releases/tag/v1.1.4 +[v1.1.3]: https://github.com/symbol/statistics-service/releases/tag/v1.1.3 [v1.1.2]: https://github.com/symbol/statistics-service/releases/tag/v1.1.2 [v1.1.1]: https://github.com/symbol/statistics-service/releases/tag/v1.1.1 [v1.1.0]: https://github.com/symbol/statistics-service/releases/tag/v1.1.0