diff --git a/audit-resolve.json b/audit-resolve.json index 928fe2fb..99fe4d03 100644 --- a/audit-resolve.json +++ b/audit-resolve.json @@ -1,17 +1,9 @@ { "decisions": { - "1300|@mojaloop/central-ledger>hapi-swagger>handlebars": { - "decision": "postpone", - "madeAt": 1573224238328 - }, - "1300|nyc>istanbul-reports>handlebars": { - "decision": "postpone", - "madeAt": 1573224238328 - }, "1482|@hapi/hapi": { "decision": "ignore", - "madeAt": 1582123771440, - "expiresAt": 1582728550777 + "madeAt": 1582888595088, + "expiresAt": 1585480556972 } }, "rules": {}, diff --git a/config/default.json b/config/default.json index 03c6c575..e5c16632 100644 --- a/config/default.json +++ b/config/default.json @@ -18,11 +18,21 @@ "CREATE_RETRY_INTERVAL_MILLIS": 200, "DEBUG": false }, + "WINDOW_AGGREGATION": { + "RETRY_COUNT": 3, + "RETRY_INTERVAL": 3000 + }, "TRANSFER_VALIDITY_SECONDS": "432000", "HUB_PARTICIPANT": { "ID": 1, "NAME": "Hub" }, + "HANDLERS": { + "DISABLED": false, + "API": { + "DISABLED": false + } + }, "KAFKA": { "TOPIC_TEMPLATES": { "GENERAL_TOPIC_TEMPLATE": { @@ -30,6 +40,33 @@ "REGEX": "topic-(.*)-(.*)" } }, + "CONSUMER": { + "SETTLEMENTWINDOW": { + "CLOSE": { + "config": { + "options": { + "mode": 2, + "batchSize": 1, + "pollFrequency": 10, + "recursiveTimeout": 100, + "messageCharset": "utf8", + "messageAsJSON": true, + "sync": true, + "consumeTimeout": 1000 + }, + "rdkafkaConf": { + "client.id": "cs-con-setlementwindow-close", + "group.id": "cs-group-setlementwindow-close", + "metadata.broker.list": "localhost:9092", + "socket.keepalive.enable": true + }, + "topicConf": { + "auto.offset.reset": "earliest" + } + } + } + } + }, "PRODUCER": { "NOTIFICATION": { "EVENT": { @@ -56,6 +93,26 @@ } } } + }, + "SETTLEMENTWINDOW": { + "CLOSE": { + "config": { + "options": { + "messageCharset": "utf8" + }, + "rdkafkaConf": { + "metadata.broker.list": "localhost:9092", + "client.id": "cs-prod-setlementwindow-close", + "event_cb": true, + "dr_cb": true, + "socket.keepalive.enable": true, + "queue.buffering.max.messages": 10000000 + }, + "topicConf": { + "request.required.acks": "all" + } + } + } } } } diff --git a/package-lock.json b/package-lock.json index 64d35c8e..35a64566 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "central-settlement", - "version": "9.2.1", + "version": "9.2.2", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -14,18 +14,18 @@ } }, "@babel/core": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.8.4.tgz", - "integrity": "sha512-0LiLrB2PwrVI+a2/IEskBopDYSd8BCb3rOvH7D5tzoWd696TBEduBvuLVm4Nx6rltrLZqvI3MCalB2K2aVzQjA==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.8.6.tgz", + "integrity": "sha512-Sheg7yEJD51YHAvLEV/7Uvw95AeWqYPL3Vk3zGujJKIhJ+8oLw2ALaf3hbucILhKsgSoADOvtKRJuNVdcJkOrg==", "dev": true, "requires": { "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.8.4", + "@babel/generator": "^7.8.6", "@babel/helpers": "^7.8.4", - "@babel/parser": "^7.8.4", - "@babel/template": "^7.8.3", - "@babel/traverse": "^7.8.4", - "@babel/types": "^7.8.3", + "@babel/parser": "^7.8.6", + "@babel/template": "^7.8.6", + "@babel/traverse": "^7.8.6", + "@babel/types": "^7.8.6", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.1", @@ -48,12 +48,12 @@ } }, "@babel/generator": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.4.tgz", - "integrity": "sha512-PwhclGdRpNAf3IxZb0YVuITPZmmrXz9zf6fH8lT4XbrmfQKr6ryBzhv593P5C6poJRciFCL/eHGW2NuGrgEyxA==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.6.tgz", + "integrity": "sha512-4bpOR5ZBz+wWcMeVtcf7FbjcFzCp+817z2/gHNncIRcM9MmKzUhtWCYAq27RAfUrAFwb+OCG1s9WEaVxfi6cjg==", "dev": true, "requires": { - "@babel/types": "^7.8.3", + "@babel/types": "^7.8.6", "jsesc": "^2.5.1", "lodash": "^4.17.13", "source-map": "^0.5.0" @@ -111,34 +111,34 @@ } }, "@babel/parser": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz", - "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.6.tgz", + "integrity": "sha512-trGNYSfwq5s0SgM1BMEB8hX3NDmO7EP2wsDGDexiaKMB92BaRpS+qZfpkMqUBhcsOTBwNy9B/jieo4ad/t/z2g==", "dev": true }, "@babel/template": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.3.tgz", - "integrity": "sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.6.tgz", + "integrity": "sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg==", "dev": true, "requires": { "@babel/code-frame": "^7.8.3", - "@babel/parser": "^7.8.3", - "@babel/types": "^7.8.3" + "@babel/parser": "^7.8.6", + "@babel/types": "^7.8.6" } }, "@babel/traverse": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.4.tgz", - "integrity": "sha512-NGLJPZwnVEyBPLI+bl9y9aSnxMhsKz42so7ApAv9D+b4vAFPpY013FTS9LdKxcABoIYFU52HcYga1pPlx454mg==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.6.tgz", + "integrity": "sha512-2B8l0db/DPi8iinITKuo7cbPznLCEk0kCxDoB9/N6gGNg/gxOXiR/IcymAFPiBwk5w6TtQ27w4wpElgp9btR9A==", "dev": true, "requires": { "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.8.4", + "@babel/generator": "^7.8.6", "@babel/helper-function-name": "^7.8.3", "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/parser": "^7.8.4", - "@babel/types": "^7.8.3", + "@babel/parser": "^7.8.6", + "@babel/types": "^7.8.6", "debug": "^4.1.0", "globals": "^11.1.0", "lodash": "^4.17.13" @@ -162,9 +162,9 @@ } }, "@babel/types": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", - "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.6.tgz", + "integrity": "sha512-wqz7pgWMIrht3gquyEFPVXeXCti72Rm8ep9b5tQKz9Yg9LzJA3HxosF1SB3Kc81KD1A3XBkkVYtJvCKS2Z/QrA==", "dev": true, "requires": { "esutils": "^2.0.2", @@ -172,6 +172,15 @@ "to-fast-properties": "^2.0.0" } }, + "@grpc/proto-loader": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.5.3.tgz", + "integrity": "sha512-8qvUtGg77G2ZT2HqdqYoM/OY97gQd/0crSG34xNmZ4ZOsv3aQT/FQV9QfZPazTGna6MIoyUd+u6AxsoZjJ/VMQ==", + "requires": { + "lodash.camelcase": "^4.3.0", + "protobufjs": "^6.8.6" + } + }, "@hapi/accept": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/@hapi/accept/-/accept-3.2.4.tgz", @@ -305,37 +314,28 @@ } }, "@hapi/catbox": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/@hapi/catbox/-/catbox-11.0.1.tgz", - "integrity": "sha512-CsdannMSzWqLcJ7rXT55JGAzoR+BPXesKn9POOrF0A0wsumbUwHP7vxBUH/21YitcM/dLxjUfphkRAQT+XaoyQ==", + "version": "10.2.3", + "resolved": "https://registry.npmjs.org/@hapi/catbox/-/catbox-10.2.3.tgz", + "integrity": "sha512-kN9hXO4NYyOHW09CXiuj5qW1syc/0XeVOBsNNk0Tz89wWNQE5h21WF+VsfAw3uFR8swn/Wj3YEVBnWqo82m/JQ==", "requires": { - "@hapi/boom": "9.x.x", - "@hapi/hoek": "9.x.x", - "@hapi/joi": "17.x.x", - "@hapi/podium": "4.x.x" + "@hapi/boom": "7.x.x", + "@hapi/hoek": "8.x.x", + "@hapi/joi": "16.x.x", + "@hapi/podium": "3.x.x" }, "dependencies": { - "@hapi/formula": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@hapi/formula/-/formula-2.0.0.tgz", - "integrity": "sha512-V87P8fv7PI0LH7LiVi8Lkf3x+KCO7pQozXRssAHNXXL9L1K+uyu4XypLXwxqVDKgyQai6qj3/KteNlrqDx4W5A==" - }, - "@hapi/joi": { - "version": "17.1.0", - "resolved": "https://registry.npmjs.org/@hapi/joi/-/joi-17.1.0.tgz", - "integrity": "sha512-ob67RcPlwRWxBzLCnWvcwx5qbwf88I3ykD7gcJLWOTRfLLgosK7r6aeChz4thA3XRvuBfI0KB1tPVl2EQFlPXw==", + "@hapi/boom": { + "version": "7.4.11", + "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-7.4.11.tgz", + "integrity": "sha512-VSU/Cnj1DXouukYxxkes4nNJonCnlogHvIff1v1RVoN4xzkKhMXX+GRmb3NyH1iar10I9WFPDv2JPwfH3GaV0A==", "requires": { - "@hapi/address": "^4.0.0", - "@hapi/formula": "^2.0.0", - "@hapi/hoek": "^9.0.0", - "@hapi/pinpoint": "^2.0.0", - "@hapi/topo": "^5.0.0" + "@hapi/hoek": "8.x.x" } }, - "@hapi/pinpoint": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@hapi/pinpoint/-/pinpoint-2.0.0.tgz", - "integrity": "sha512-vzXR5MY7n4XeIvLpfl3HtE3coZYO4raKXW766R6DZw/6aLqR26iuZ109K7a0NtF2Db0jxqh7xz2AxkUwpUFybw==" + "@hapi/hoek": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-8.5.1.tgz", + "integrity": "sha512-yN7kbciD87WzLGc5539Tn0sApjyiGHAJgKvG9W8C7O+6c7qmoQMfVs0W4bX17eqz6C78QJqqFrtgdK5EWf6Qow==" } } }, @@ -400,9 +400,9 @@ "integrity": "sha512-Bsfp/+1Gyf70eGtnIgmScvrH8sSypO3TcK3Zf0QdHnzn/ACnAkI6KLtGACmNRPEzzIy+W7aJX5E+1fc9GwIABQ==" }, "@hapi/formula": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@hapi/formula/-/formula-1.2.0.tgz", - "integrity": "sha512-UFbtbGPjstz0eWHb+ga/GM3Z9EzqKXFWIbSOFURU0A/Gku0Bky4bCk9/h//K2Xr3IrCfjFNhMm4jyZ5dbCewGA==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@hapi/formula/-/formula-2.0.0.tgz", + "integrity": "sha512-V87P8fv7PI0LH7LiVi8Lkf3x+KCO7pQozXRssAHNXXL9L1K+uyu4XypLXwxqVDKgyQai6qj3/KteNlrqDx4W5A==" }, "@hapi/good": { "version": "9.0.0", @@ -415,11 +415,6 @@ "pumpify": "1.x.x" }, "dependencies": { - "@hapi/formula": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@hapi/formula/-/formula-2.0.0.tgz", - "integrity": "sha512-V87P8fv7PI0LH7LiVi8Lkf3x+KCO7pQozXRssAHNXXL9L1K+uyu4XypLXwxqVDKgyQai6qj3/KteNlrqDx4W5A==" - }, "@hapi/joi": { "version": "17.1.0", "resolved": "https://registry.npmjs.org/@hapi/joi/-/joi-17.1.0.tgz", @@ -431,11 +426,6 @@ "@hapi/pinpoint": "^2.0.0", "@hapi/topo": "^5.0.0" } - }, - "@hapi/pinpoint": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@hapi/pinpoint/-/pinpoint-2.0.0.tgz", - "integrity": "sha512-vzXR5MY7n4XeIvLpfl3HtE3coZYO4raKXW766R6DZw/6aLqR26iuZ109K7a0NtF2Db0jxqh7xz2AxkUwpUFybw==" } } }, @@ -477,31 +467,6 @@ "@hapi/hoek": "8.x.x" } }, - "@hapi/catbox": { - "version": "10.2.3", - "resolved": "https://registry.npmjs.org/@hapi/catbox/-/catbox-10.2.3.tgz", - "integrity": "sha512-kN9hXO4NYyOHW09CXiuj5qW1syc/0XeVOBsNNk0Tz89wWNQE5h21WF+VsfAw3uFR8swn/Wj3YEVBnWqo82m/JQ==", - "requires": { - "@hapi/boom": "7.x.x", - "@hapi/hoek": "8.x.x", - "@hapi/joi": "16.x.x", - "@hapi/podium": "3.x.x" - }, - "dependencies": { - "@hapi/joi": { - "version": "16.1.8", - "resolved": "https://registry.npmjs.org/@hapi/joi/-/joi-16.1.8.tgz", - "integrity": "sha512-wAsVvTPe+FwSrsAurNt5vkg3zo+TblvC5Bb1zMVK6SJzZqw9UrJnexxR+76cpePmtUZKHAPxcQ2Bf7oVHyahhg==", - "requires": { - "@hapi/address": "^2.1.2", - "@hapi/formula": "^1.2.0", - "@hapi/hoek": "^8.2.4", - "@hapi/pinpoint": "^1.0.2", - "@hapi/topo": "^3.1.3" - } - } - } - }, "@hapi/catbox-memory": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/@hapi/catbox-memory/-/catbox-memory-4.1.1.tgz", @@ -527,29 +492,6 @@ "@hapi/topo": "3.x.x" } }, - "@hapi/podium": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/@hapi/podium/-/podium-3.4.3.tgz", - "integrity": "sha512-QJlnYLEYZWlKQ9fSOtuUcpANyoVGwT68GA9P0iQQCAetBK0fI+nbRBt58+aMixoifczWZUthuGkNjqKxgPh/CQ==", - "requires": { - "@hapi/hoek": "8.x.x", - "@hapi/joi": "16.x.x" - }, - "dependencies": { - "@hapi/joi": { - "version": "16.1.8", - "resolved": "https://registry.npmjs.org/@hapi/joi/-/joi-16.1.8.tgz", - "integrity": "sha512-wAsVvTPe+FwSrsAurNt5vkg3zo+TblvC5Bb1zMVK6SJzZqw9UrJnexxR+76cpePmtUZKHAPxcQ2Bf7oVHyahhg==", - "requires": { - "@hapi/address": "^2.1.2", - "@hapi/formula": "^1.2.0", - "@hapi/hoek": "^8.2.4", - "@hapi/pinpoint": "^1.0.2", - "@hapi/topo": "^3.1.3" - } - } - } - }, "@hapi/topo": { "version": "3.1.6", "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-3.1.6.tgz", @@ -603,11 +545,6 @@ "lru-cache": "4.1.x" }, "dependencies": { - "@hapi/address": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.1.4.tgz", - "integrity": "sha512-QD1PhQk+s31P1ixsX0H0Suoupp3VMXzIVMSwobR3F3MSUO2YCV0B7xqLcUw/Bh8yuvd3LhpyqLQWTNcRmp6IdQ==" - }, "@hapi/boom": { "version": "7.4.11", "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-7.4.11.tgz", @@ -616,54 +553,10 @@ "@hapi/hoek": "8.x.x" } }, - "@hapi/formula": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@hapi/formula/-/formula-1.2.0.tgz", - "integrity": "sha512-UFbtbGPjstz0eWHb+ga/GM3Z9EzqKXFWIbSOFURU0A/Gku0Bky4bCk9/h//K2Xr3IrCfjFNhMm4jyZ5dbCewGA==" - }, "@hapi/hoek": { "version": "8.5.1", "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-8.5.1.tgz", "integrity": "sha512-yN7kbciD87WzLGc5539Tn0sApjyiGHAJgKvG9W8C7O+6c7qmoQMfVs0W4bX17eqz6C78QJqqFrtgdK5EWf6Qow==" - }, - "@hapi/joi": { - "version": "16.1.8", - "resolved": "https://registry.npmjs.org/@hapi/joi/-/joi-16.1.8.tgz", - "integrity": "sha512-wAsVvTPe+FwSrsAurNt5vkg3zo+TblvC5Bb1zMVK6SJzZqw9UrJnexxR+76cpePmtUZKHAPxcQ2Bf7oVHyahhg==", - "requires": { - "@hapi/address": "^2.1.2", - "@hapi/formula": "^1.2.0", - "@hapi/hoek": "^8.2.4", - "@hapi/pinpoint": "^1.0.2", - "@hapi/topo": "^3.1.3" - } - }, - "@hapi/pinpoint": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@hapi/pinpoint/-/pinpoint-1.0.2.tgz", - "integrity": "sha512-dtXC/WkZBfC5vxscazuiJ6iq4j9oNx1SHknmIr8hofarpKUZKmlUVYVIhNVzIEgK5Wrc4GMHL5lZtt1uS2flmQ==" - }, - "@hapi/topo": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-3.1.6.tgz", - "integrity": "sha512-tAag0jEcjwH+P2quUfipd7liWCNX2F8NvYjQp2wtInsZxnMlypdw0FtAOLxtvvkO+GSRRbmNi8m/5y42PQJYCQ==", - "requires": { - "@hapi/hoek": "^8.3.0" - } - }, - "lru-cache": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", - "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - } - }, - "yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" } } }, @@ -711,11 +604,21 @@ "resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.1.4.tgz", "integrity": "sha512-QD1PhQk+s31P1ixsX0H0Suoupp3VMXzIVMSwobR3F3MSUO2YCV0B7xqLcUw/Bh8yuvd3LhpyqLQWTNcRmp6IdQ==" }, + "@hapi/formula": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@hapi/formula/-/formula-1.2.0.tgz", + "integrity": "sha512-UFbtbGPjstz0eWHb+ga/GM3Z9EzqKXFWIbSOFURU0A/Gku0Bky4bCk9/h//K2Xr3IrCfjFNhMm4jyZ5dbCewGA==" + }, "@hapi/hoek": { "version": "8.5.1", "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-8.5.1.tgz", "integrity": "sha512-yN7kbciD87WzLGc5539Tn0sApjyiGHAJgKvG9W8C7O+6c7qmoQMfVs0W4bX17eqz6C78QJqqFrtgdK5EWf6Qow==" }, + "@hapi/pinpoint": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@hapi/pinpoint/-/pinpoint-1.0.2.tgz", + "integrity": "sha512-dtXC/WkZBfC5vxscazuiJ6iq4j9oNx1SHknmIr8hofarpKUZKmlUVYVIhNVzIEgK5Wrc4GMHL5lZtt1uS2flmQ==" + }, "@hapi/topo": { "version": "3.1.6", "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-3.1.6.tgz", @@ -794,40 +697,23 @@ } }, "@hapi/pinpoint": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@hapi/pinpoint/-/pinpoint-1.0.2.tgz", - "integrity": "sha512-dtXC/WkZBfC5vxscazuiJ6iq4j9oNx1SHknmIr8hofarpKUZKmlUVYVIhNVzIEgK5Wrc4GMHL5lZtt1uS2flmQ==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@hapi/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-vzXR5MY7n4XeIvLpfl3HtE3coZYO4raKXW766R6DZw/6aLqR26iuZ109K7a0NtF2Db0jxqh7xz2AxkUwpUFybw==" }, "@hapi/podium": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@hapi/podium/-/podium-4.0.0.tgz", - "integrity": "sha512-2T74rkS4OZjbsSdx4jIMbjb3cF/X6BpEE2kar71ascgL+9lf+5eczirlw3WWQ9ng2YDU469IVrADd6LYMzhEdw==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/@hapi/podium/-/podium-3.4.3.tgz", + "integrity": "sha512-QJlnYLEYZWlKQ9fSOtuUcpANyoVGwT68GA9P0iQQCAetBK0fI+nbRBt58+aMixoifczWZUthuGkNjqKxgPh/CQ==", "requires": { - "@hapi/hoek": "9.x.x", - "@hapi/joi": "17.x.x" + "@hapi/hoek": "8.x.x", + "@hapi/joi": "16.x.x" }, "dependencies": { - "@hapi/formula": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@hapi/formula/-/formula-2.0.0.tgz", - "integrity": "sha512-V87P8fv7PI0LH7LiVi8Lkf3x+KCO7pQozXRssAHNXXL9L1K+uyu4XypLXwxqVDKgyQai6qj3/KteNlrqDx4W5A==" - }, - "@hapi/joi": { - "version": "17.1.0", - "resolved": "https://registry.npmjs.org/@hapi/joi/-/joi-17.1.0.tgz", - "integrity": "sha512-ob67RcPlwRWxBzLCnWvcwx5qbwf88I3ykD7gcJLWOTRfLLgosK7r6aeChz4thA3XRvuBfI0KB1tPVl2EQFlPXw==", - "requires": { - "@hapi/address": "^4.0.0", - "@hapi/formula": "^2.0.0", - "@hapi/hoek": "^9.0.0", - "@hapi/pinpoint": "^2.0.0", - "@hapi/topo": "^5.0.0" - } - }, - "@hapi/pinpoint": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@hapi/pinpoint/-/pinpoint-2.0.0.tgz", - "integrity": "sha512-vzXR5MY7n4XeIvLpfl3HtE3coZYO4raKXW766R6DZw/6aLqR26iuZ109K7a0NtF2Db0jxqh7xz2AxkUwpUFybw==" + "@hapi/hoek": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-8.5.1.tgz", + "integrity": "sha512-yN7kbciD87WzLGc5539Tn0sApjyiGHAJgKvG9W8C7O+6c7qmoQMfVs0W4bX17eqz6C78QJqqFrtgdK5EWf6Qow==" } } }, @@ -960,11 +846,6 @@ "@hapi/joi": "16.x.x" }, "dependencies": { - "@hapi/address": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.1.4.tgz", - "integrity": "sha512-QD1PhQk+s31P1ixsX0H0Suoupp3VMXzIVMSwobR3F3MSUO2YCV0B7xqLcUw/Bh8yuvd3LhpyqLQWTNcRmp6IdQ==" - }, "@hapi/boom": { "version": "7.4.11", "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-7.4.11.tgz", @@ -973,40 +854,10 @@ "@hapi/hoek": "8.x.x" } }, - "@hapi/formula": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@hapi/formula/-/formula-1.2.0.tgz", - "integrity": "sha512-UFbtbGPjstz0eWHb+ga/GM3Z9EzqKXFWIbSOFURU0A/Gku0Bky4bCk9/h//K2Xr3IrCfjFNhMm4jyZ5dbCewGA==" - }, "@hapi/hoek": { "version": "8.5.1", "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-8.5.1.tgz", "integrity": "sha512-yN7kbciD87WzLGc5539Tn0sApjyiGHAJgKvG9W8C7O+6c7qmoQMfVs0W4bX17eqz6C78QJqqFrtgdK5EWf6Qow==" - }, - "@hapi/joi": { - "version": "16.1.8", - "resolved": "https://registry.npmjs.org/@hapi/joi/-/joi-16.1.8.tgz", - "integrity": "sha512-wAsVvTPe+FwSrsAurNt5vkg3zo+TblvC5Bb1zMVK6SJzZqw9UrJnexxR+76cpePmtUZKHAPxcQ2Bf7oVHyahhg==", - "requires": { - "@hapi/address": "^2.1.2", - "@hapi/formula": "^1.2.0", - "@hapi/hoek": "^8.2.4", - "@hapi/pinpoint": "^1.0.2", - "@hapi/topo": "^3.1.3" - } - }, - "@hapi/pinpoint": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@hapi/pinpoint/-/pinpoint-1.0.2.tgz", - "integrity": "sha512-dtXC/WkZBfC5vxscazuiJ6iq4j9oNx1SHknmIr8hofarpKUZKmlUVYVIhNVzIEgK5Wrc4GMHL5lZtt1uS2flmQ==" - }, - "@hapi/topo": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-3.1.6.tgz", - "integrity": "sha512-tAag0jEcjwH+P2quUfipd7liWCNX2F8NvYjQp2wtInsZxnMlypdw0FtAOLxtvvkO+GSRRbmNi8m/5y42PQJYCQ==", - "requires": { - "@hapi/hoek": "^8.3.0" - } } } }, @@ -1109,9 +960,9 @@ "optional": true }, "@mojaloop/central-ledger": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/@mojaloop/central-ledger/-/central-ledger-9.2.0.tgz", - "integrity": "sha512-/6xob06eKemdeW8MfqzzG6VRXiEJnh/KeQIk4VRIqh3rxuMEvr+Wh6xbduzgDl6QznO2dPhR9zbmWC/dHPvOHA==", + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/@mojaloop/central-ledger/-/central-ledger-9.2.2.tgz", + "integrity": "sha512-3sAAeDCmpaBcRDiCnlKUj6DkWbph5aXILM/WVMAl/LZGIe9EnpBgPf0/gT5j4TR0XUkgm1DYijCgZ72Cvfztsg==", "requires": { "@hapi/good": "9.0.0", "@hapi/hapi": "19.1.1", @@ -1119,13 +970,13 @@ "@hapi/joi": "17.1.0", "@hapi/vision": "6.0.0", "@mojaloop/central-object-store": "9.1.0-snapshot", - "@mojaloop/central-services-database": "9.1.0", + "@mojaloop/central-services-database": "9.2.0", "@mojaloop/central-services-error-handling": "9.1.0", "@mojaloop/central-services-health": "9.2.0", "@mojaloop/central-services-logger": "9.1.0", "@mojaloop/central-services-metrics": "9.1.0", - "@mojaloop/central-services-shared": "9.1.4", - "@mojaloop/central-services-stream": "9.1.0", + "@mojaloop/central-services-shared": "9.2.0", + "@mojaloop/central-services-stream": "9.2.0", "@mojaloop/event-sdk": "9.2.0", "@mojaloop/forensic-logging-client": "8.3.0", "@mojaloop/ml-number": "8.2.0", @@ -1142,11 +993,11 @@ "glob": "7.1.6", "hapi-auth-basic": "5.0.0", "hapi-auth-bearer-token": "6.2.1", - "hapi-swagger": "12.1.0", + "hapi-swagger": "12.1.1", "knex": "0.20.10", "lodash": "4.17.15", "moment": "2.24.0", - "mongoose": "5.9.1", + "mongoose": "5.9.2", "mysql": "2.18.1", "npm-run-all": "4.1.5", "rc": "1.2.8", @@ -1154,15 +1005,6 @@ "uuid4": "1.1.4" }, "dependencies": { - "@grpc/proto-loader": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.5.3.tgz", - "integrity": "sha512-8qvUtGg77G2ZT2HqdqYoM/OY97gQd/0crSG34xNmZ4ZOsv3aQT/FQV9QfZPazTGna6MIoyUd+u6AxsoZjJ/VMQ==", - "requires": { - "lodash.camelcase": "^4.3.0", - "protobufjs": "^6.8.6" - } - }, "@hapi/accept": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/@hapi/accept/-/accept-5.0.1.tgz", @@ -1212,6 +1054,17 @@ "@hapi/hoek": "9.x.x" } }, + "@hapi/catbox": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/@hapi/catbox/-/catbox-11.0.1.tgz", + "integrity": "sha512-CsdannMSzWqLcJ7rXT55JGAzoR+BPXesKn9POOrF0A0wsumbUwHP7vxBUH/21YitcM/dLxjUfphkRAQT+XaoyQ==", + "requires": { + "@hapi/boom": "9.x.x", + "@hapi/hoek": "9.x.x", + "@hapi/joi": "17.x.x", + "@hapi/podium": "4.x.x" + } + }, "@hapi/content": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/@hapi/content/-/content-5.0.2.tgz", @@ -1233,22 +1086,6 @@ "resolved": "https://registry.npmjs.org/@hapi/file/-/file-2.0.0.tgz", "integrity": "sha512-WSrlgpvEqgPWkI18kkGELEZfXr0bYLtr16iIN4Krh9sRnzBZN6nnWxHFxtsnP684wueEySBbXPDg/WfA9xJdBQ==" }, - "@hapi/formula": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@hapi/formula/-/formula-2.0.0.tgz", - "integrity": "sha512-V87P8fv7PI0LH7LiVi8Lkf3x+KCO7pQozXRssAHNXXL9L1K+uyu4XypLXwxqVDKgyQai6qj3/KteNlrqDx4W5A==" - }, - "@hapi/good": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/@hapi/good/-/good-9.0.0.tgz", - "integrity": "sha512-jt6mEzFfY+jzE/IbvNVTTHcqKE9RP609MXKff1Pj4VPCnCSG8UVUtTdr1nM6UFN02NvntqShlpeZi4o+RgN35g==", - "requires": { - "@hapi/hoek": "9.x.x", - "@hapi/joi": "17.x.x", - "@hapi/oppsy": "3.x.x", - "pumpify": "1.x.x" - } - }, "@hapi/hapi": { "version": "19.1.1", "resolved": "https://registry.npmjs.org/@hapi/hapi/-/hapi-19.1.1.tgz", @@ -1339,14 +1176,6 @@ "@hapi/vise": "4.x.x" } }, - "@hapi/oppsy": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@hapi/oppsy/-/oppsy-3.0.0.tgz", - "integrity": "sha512-0kfUEAqIi21GzFVK2snMO07znMEBiXb+/pOx1dmgOO9TuvFstcfmHU5i56aDfiFP2DM5WzQCU2UWc2gK1lMDhQ==", - "requires": { - "@hapi/hoek": "9.x.x" - } - }, "@hapi/pez": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/@hapi/pez/-/pez-5.0.2.tgz", @@ -1359,10 +1188,15 @@ "@hapi/nigel": "4.x.x" } }, - "@hapi/pinpoint": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@hapi/pinpoint/-/pinpoint-2.0.0.tgz", - "integrity": "sha512-vzXR5MY7n4XeIvLpfl3HtE3coZYO4raKXW766R6DZw/6aLqR26iuZ109K7a0NtF2Db0jxqh7xz2AxkUwpUFybw==" + "@hapi/podium": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@hapi/podium/-/podium-4.1.0.tgz", + "integrity": "sha512-k/n0McAu8PvonfQRLyKKUvvdb+Gh/O5iAeIwv535Hpxw9B1qZcrYdZyWtHZ8O5PkA9/b/Kk+BdvtgcxeKMB/2g==", + "requires": { + "@hapi/hoek": "9.x.x", + "@hapi/joi": "17.x.x", + "@hapi/teamwork": "4.x.x" + } }, "@hapi/shot": { "version": "5.0.0", @@ -1444,1051 +1278,315 @@ "@hapi/hoek": "9.x.x" } }, - "@mojaloop/central-object-store": { - "version": "9.1.0-snapshot", - "resolved": "https://registry.npmjs.org/@mojaloop/central-object-store/-/central-object-store-9.1.0-snapshot.tgz", - "integrity": "sha512-7AUE59CNxHlss7h68nhflSxqzWrJrxRkUPY17+gmxFtgbeSGvEdr8AkJ1IROcqLcNTojD5UF2xSJUZ+SpBVj9Q==", + "hapi-swagger": { + "version": "12.1.1", + "resolved": "https://registry.npmjs.org/hapi-swagger/-/hapi-swagger-12.1.1.tgz", + "integrity": "sha512-gk2QKv7f71Y8N5ZKb1i76TD86gGg6BA+oZ94UBV2ycdi3K6hJ6iqJxB0vZEIiChxiGUx1Vqud+8pYwrONPEhhw==", "requires": { - "@mojaloop/central-services-logger": "9.1.0", - "mongoose": "5.8.11" + "@hapi/boom": "^8.0.1", + "@hapi/hoek": "^9.0.2", + "@hapi/joi": "^17.1.0", + "handlebars": "^4.5.3", + "http-status": "^1.0.1", + "json-schema-ref-parser": "^6.1.0", + "swagger-parser": "4.0.2", + "swagger-ui-dist": "^3.22.1" }, "dependencies": { - "mongoose": { - "version": "5.8.11", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.8.11.tgz", - "integrity": "sha512-Yz0leNEJsAtNtMTxTDEadacLWt58gaVeBVL3c1Z3vaBoc159aJqlf+T8jaL9mAdBxKndF5YWhh6Q719xac7cjA==", + "@hapi/boom": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-8.0.1.tgz", + "integrity": "sha512-SnBM2GzEYEA6AGFKXBqNLWXR3uNBui0bkmklYXX1gYtevVhDTy2uakwkSauxvIWMtlANGRhzChYg95If3FWCwA==", "requires": { - "bson": "~1.1.1", - "kareem": "2.3.1", - "mongodb": "3.4.1", - "mongoose-legacy-pluralize": "1.0.2", - "mpath": "0.6.0", - "mquery": "3.2.2", - "ms": "2.1.2", - "regexp-clone": "1.0.0", - "safe-buffer": "5.1.2", - "sift": "7.0.1", - "sliced": "1.0.1" + "@hapi/hoek": "8.x.x" + }, + "dependencies": { + "@hapi/hoek": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-8.5.1.tgz", + "integrity": "sha512-yN7kbciD87WzLGc5539Tn0sApjyiGHAJgKvG9W8C7O+6c7qmoQMfVs0W4bX17eqz6C78QJqqFrtgdK5EWf6Qow==" + } } } } }, - "@mojaloop/central-services-metrics": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@mojaloop/central-services-metrics/-/central-services-metrics-9.1.0.tgz", - "integrity": "sha512-pE+zgVduOORYipJsUTa3ugm930Uzm8dnLxIkJEpBFoZWPqknDMTSZFvy2PU3g/hft2+IIuLSlOzHC7YxNdWBRA==", - "requires": { - "prom-client": "11.5.3" - } - }, - "@mojaloop/event-sdk": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/@mojaloop/event-sdk/-/event-sdk-9.2.0.tgz", - "integrity": "sha512-CpdmsSAjieE+053gxbS+H1eeEiuXX2x0qDXhiGWjynMeEWdZhZsZnTACRDAKcxdqZjJdo2ioVw8XVWqaPeyZtA==", + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "requires": { - "@grpc/proto-loader": "0.5.3", - "@mojaloop/central-services-logger": "9.1.0", - "brototype": "0.0.6", - "error-callsites": "2.0.2", - "grpc": "1.24.2", - "lodash": "4.17.15", - "moment": "2.24.0", - "parse-strings-in-object": "2.0.0", - "protobufjs": "6.8.8", - "rc": "1.2.8", - "serialize-error": "4.1.0", - "sinon": "8.1.1", - "traceparent": "1.0.0", - "tslib": "1.10.0", - "uuid4": "1.1.4" + "yallist": "^3.0.2" } }, - "@mojaloop/forensic-logging-client": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/@mojaloop/forensic-logging-client/-/forensic-logging-client-8.3.0.tgz", - "integrity": "sha512-kDWZeD4Xj7LzaTtr/kbaulGJSQVaOi2PVW/G2NWMuKARMnYcAbpF+m5uXcinz5Iun0CHtVrNVfh4/PFhvQ73Bg==", + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + } + } + }, + "@mojaloop/central-object-store": { + "version": "9.1.0-snapshot", + "resolved": "https://registry.npmjs.org/@mojaloop/central-object-store/-/central-object-store-9.1.0-snapshot.tgz", + "integrity": "sha512-7AUE59CNxHlss7h68nhflSxqzWrJrxRkUPY17+gmxFtgbeSGvEdr8AkJ1IROcqLcNTojD5UF2xSJUZ+SpBVj9Q==", + "requires": { + "@mojaloop/central-services-logger": "9.1.0", + "mongoose": "5.8.11" + }, + "dependencies": { + "mongoose": { + "version": "5.8.11", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.8.11.tgz", + "integrity": "sha512-Yz0leNEJsAtNtMTxTDEadacLWt58gaVeBVL3c1Z3vaBoc159aJqlf+T8jaL9mAdBxKndF5YWhh6Q719xac7cjA==", "requires": { - "@mojaloop/central-services-logger": "8.1.2", - "bluebird": "3.7.1" - }, - "dependencies": { - "@mojaloop/central-services-logger": { - "version": "8.1.2", - "resolved": "https://registry.npmjs.org/@mojaloop/central-services-logger/-/central-services-logger-8.1.2.tgz", - "integrity": "sha512-wNnr07xcJNAy+KX2C8Djb6ubeH2c1KkfXMyMJz+/dKrfqyVcqcI0RuhneERZrJMI5Ah4X9Sjcuz+LqH9HQoW/w==", - "requires": { - "winston": "3.2.1" - } - } + "bson": "~1.1.1", + "kareem": "2.3.1", + "mongodb": "3.4.1", + "mongoose-legacy-pluralize": "1.0.2", + "mpath": "0.6.0", + "mquery": "3.2.2", + "ms": "2.1.2", + "regexp-clone": "1.0.0", + "safe-buffer": "5.1.2", + "sift": "7.0.1", + "sliced": "1.0.1" + } + } + } + }, + "@mojaloop/central-services-database": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/@mojaloop/central-services-database/-/central-services-database-9.2.0.tgz", + "integrity": "sha512-MEdXBBv4VFheaxe4629fIZjhlxNN7oHAnBPjkOjyTuZaZO4C8toycpw8qWZydjMxyKIbqybkjnzFBSbIj97FRA==", + "requires": { + "knex": "0.20.10", + "lodash": "4.17.15", + "mysql": "2.18.1" + } + }, + "@mojaloop/central-services-error-handling": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/@mojaloop/central-services-error-handling/-/central-services-error-handling-9.1.0.tgz", + "integrity": "sha512-6tljAx/3kITegnVgCxpOMGtzsrtSLRjNbcwXH1QidKtGeic1MsQIJgQIJuQcQlXGrIVDpJ/4JyOpfKCOh+AxwQ==", + "requires": { + "@mojaloop/sdk-standard-components": "8.6.9", + "lodash": "4.17.15" + } + }, + "@mojaloop/central-services-health": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/@mojaloop/central-services-health/-/central-services-health-9.2.0.tgz", + "integrity": "sha512-5f2c3XOS1Os78wCB7sPDOgbKCDjsIIykMJ9+vFpuMQkWpQsaSdZA0TiQ0fzqWssO60/S+cjqvNH+oN4ZwBd6sA==", + "requires": { + "@hapi/hapi": "19.1.1", + "@mojaloop/central-services-error-handling": "9.1.0", + "@mojaloop/central-services-logger": "9.1.0", + "@mojaloop/central-services-shared": "9.1.4", + "tslib": "1.10.0" + }, + "dependencies": { + "@hapi/accept": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@hapi/accept/-/accept-5.0.1.tgz", + "integrity": "sha512-fMr4d7zLzsAXo28PRRQPXR1o2Wmu+6z+VY1UzDp0iFo13Twj8WePakwXBiqn3E1aAlTpSNzCXdnnQXFhst8h8Q==", + "requires": { + "@hapi/boom": "9.x.x", + "@hapi/hoek": "9.x.x" } }, - "@sinonjs/formatio": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-4.0.1.tgz", - "integrity": "sha512-asIdlLFrla/WZybhm0C8eEzaDNNrzymiTqHMeJl6zPW2881l3uuVRpm0QlRQEjqYWv6CcKMGYME3LbrLJsORBw==", + "@hapi/ammo": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@hapi/ammo/-/ammo-5.0.1.tgz", + "integrity": "sha512-FbCNwcTbnQP4VYYhLNGZmA76xb2aHg9AMPiy18NZyWMG310P5KdFGyA9v2rm5ujrIny77dEEIkMOwl0Xv+fSSA==", "requires": { - "@sinonjs/commons": "^1", - "@sinonjs/samsam": "^4.2.0" + "@hapi/hoek": "9.x.x" } }, - "@sinonjs/samsam": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-4.2.2.tgz", - "integrity": "sha512-z9o4LZUzSD9Hl22zV38aXNykgFeVj8acqfFabCY6FY83n/6s/XwNJyYYldz6/9lBJanpno9h+oL6HTISkviweA==", + "@hapi/b64": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@hapi/b64/-/b64-5.0.0.tgz", + "integrity": "sha512-ngu0tSEmrezoiIaNGG6rRvKOUkUuDdf4XTPnONHGYfSGRmDqPZX5oJL6HAdKTo1UQHECbdB4OzhWrfgVppjHUw==", "requires": { - "@sinonjs/commons": "^1.6.0", - "lodash.get": "^4.4.2", - "type-detect": "^4.0.8" + "@hapi/hoek": "9.x.x" } }, - "bignumber.js": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz", - "integrity": "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A==", - "optional": true + "@hapi/bounce": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@hapi/bounce/-/bounce-2.0.0.tgz", + "integrity": "sha512-JesW92uyzOOyuzJKjoLHM1ThiOvHPOLDHw01YV8yh5nCso7sDwJho1h0Ad2N+E62bZyz46TG3xhAi/78Gsct6A==", + "requires": { + "@hapi/boom": "9.x.x", + "@hapi/hoek": "9.x.x" + } }, - "bluebird": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.1.tgz", - "integrity": "sha512-DdmyoGCleJnkbp3nkbxTLJ18rjDsE4yCggEwKNXkeV123sPNfOCYeDoeuOY+F2FrSjO1YXcTU+dsy96KMy+gcg==" + "@hapi/bourne": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-2.0.0.tgz", + "integrity": "sha512-WEezM1FWztfbzqIUbsDzFRVMxSoLy3HugVcux6KDDtTqzPsLE8NDRHfXvev66aH1i2oOKKar3/XDjbvh/OUBdg==" }, - "commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==" + "@hapi/call": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@hapi/call/-/call-8.0.0.tgz", + "integrity": "sha512-4xHIWWqaIDQlVU88XAnomACSoC7iWUfaLfdu2T7I0y+HFFwZUrKKGfwn6ik4kwKsJRMnOliG3UXsF8V/94+Lkg==", + "requires": { + "@hapi/address": "4.x.x", + "@hapi/boom": "9.x.x", + "@hapi/hoek": "9.x.x" + } }, - "cron": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/cron/-/cron-1.8.2.tgz", - "integrity": "sha512-Gk2c4y6xKEO8FSAUTklqtfSr7oTq0CiPQeLBG5Fl0qoXpZyMcj1SG59YL+hqq04bu6/IuEA7lMkYDAplQNKkyg==", + "@hapi/catbox": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/@hapi/catbox/-/catbox-11.0.1.tgz", + "integrity": "sha512-CsdannMSzWqLcJ7rXT55JGAzoR+BPXesKn9POOrF0A0wsumbUwHP7vxBUH/21YitcM/dLxjUfphkRAQT+XaoyQ==", "requires": { - "moment-timezone": "^0.5.x" + "@hapi/boom": "9.x.x", + "@hapi/hoek": "9.x.x", + "@hapi/joi": "17.x.x", + "@hapi/podium": "4.x.x" } }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "@hapi/content": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@hapi/content/-/content-5.0.2.tgz", + "integrity": "sha512-mre4dl1ygd4ZyOH3tiYBrOUBzV7Pu/EOs8VLGf58vtOEECWed8Uuw6B4iR9AN/8uQt42tB04qpVaMyoMQh0oMw==", "requires": { - "ms": "^2.1.1" + "@hapi/boom": "9.x.x" } }, - "docdash": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/docdash/-/docdash-1.2.0.tgz", - "integrity": "sha512-IYZbgYthPTspgqYeciRJNPhSwL51yer7HAwDXhF5p+H7mTDbPvY3PCk/QDjNxdPCpWkaJVFC4t7iCNB/t9E5Kw==" + "@hapi/cryptiles": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@hapi/cryptiles/-/cryptiles-5.0.0.tgz", + "integrity": "sha512-Yq43ti9N51Z7jbm0Q7YVCcofA+4Gh5wsBX/jZ++Z+FM8GYfBQ1WmI9ufZSL+BVX8vRxzDkdQ2fKoG6cxOQlnVQ==", + "requires": { + "@hapi/boom": "9.x.x" + } }, - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "@hapi/file": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@hapi/file/-/file-2.0.0.tgz", + "integrity": "sha512-WSrlgpvEqgPWkI18kkGELEZfXr0bYLtr16iIN4Krh9sRnzBZN6nnWxHFxtsnP684wueEySBbXPDg/WfA9xJdBQ==" + }, + "@hapi/hapi": { + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/@hapi/hapi/-/hapi-19.1.1.tgz", + "integrity": "sha512-rpQzSs0XsHSF7usM4qdJJ0Bcmhs9stWhUW3OiamW33bw4qL8q3uEgUKB9KH8ODmluCAkkXOQ0X0Dh9t94E5VIw==", "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "@hapi/accept": "^5.0.1", + "@hapi/ammo": "^5.0.1", + "@hapi/boom": "9.x.x", + "@hapi/bounce": "2.x.x", + "@hapi/call": "8.x.x", + "@hapi/catbox": "11.x.x", + "@hapi/catbox-memory": "5.x.x", + "@hapi/heavy": "7.x.x", + "@hapi/hoek": "9.x.x", + "@hapi/joi": "17.x.x", + "@hapi/mimos": "5.x.x", + "@hapi/podium": "4.x.x", + "@hapi/shot": "5.x.x", + "@hapi/somever": "3.x.x", + "@hapi/statehood": "^7.0.2", + "@hapi/subtext": "^7.0.3", + "@hapi/teamwork": "4.x.x", + "@hapi/topo": "5.x.x" } }, - "grpc": { - "version": "1.24.2", - "resolved": "https://registry.npmjs.org/grpc/-/grpc-1.24.2.tgz", - "integrity": "sha512-EG3WH6AWMVvAiV15d+lr+K77HJ/KV/3FvMpjKjulXHbTwgDZkhkcWbwhxFAoTdxTkQvy0WFcO3Nog50QBbHZWw==", + "@hapi/heavy": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@hapi/heavy/-/heavy-7.0.0.tgz", + "integrity": "sha512-n/nheUG6zNleWkjY+3fzV3VJIAumUCaa/WoTmurjqlYY5JgC5ZKOpvP7tWi8rXmKZhbcXgjH3fHFoM55LoBT7g==", "requires": { - "@types/bytebuffer": "^5.0.40", - "lodash.camelcase": "^4.3.0", - "lodash.clone": "^4.5.0", - "nan": "^2.13.2", - "node-pre-gyp": "^0.14.0", - "protobufjs": "^5.0.3" - }, - "dependencies": { - "abbrev": { - "version": "1.1.1", - "bundled": true - }, - "ansi-regex": { - "version": "2.1.1", - "bundled": true - }, - "aproba": { - "version": "1.2.0", - "bundled": true - }, - "are-we-there-yet": { - "version": "1.1.5", - "bundled": true, - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - } - }, - "balanced-match": { - "version": "1.0.0", - "bundled": true - }, - "brace-expansion": { - "version": "1.1.11", - "bundled": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "chownr": { - "version": "1.1.3", - "bundled": true - }, - "code-point-at": { - "version": "1.1.0", - "bundled": true - }, - "concat-map": { - "version": "0.0.1", - "bundled": true - }, - "console-control-strings": { - "version": "1.1.0", - "bundled": true - }, - "core-util-is": { - "version": "1.0.2", - "bundled": true - }, - "debug": { - "version": "3.2.6", - "bundled": true, - "requires": { - "ms": "^2.1.1" - } - }, - "deep-extend": { - "version": "0.6.0", - "bundled": true - }, - "delegates": { - "version": "1.0.0", - "bundled": true - }, - "detect-libc": { - "version": "1.0.3", - "bundled": true - }, - "fs-minipass": { - "version": "1.2.7", - "bundled": true, - "requires": { - "minipass": "^2.6.0" - } - }, - "fs.realpath": { - "version": "1.0.0", - "bundled": true - }, - "gauge": { - "version": "2.7.4", - "bundled": true, - "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" - } - }, - "glob": { - "version": "7.1.4", - "bundled": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "has-unicode": { - "version": "2.0.1", - "bundled": true - }, - "iconv-lite": { - "version": "0.4.24", - "bundled": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ignore-walk": { - "version": "3.0.3", - "bundled": true, - "requires": { - "minimatch": "^3.0.4" - } - }, - "inflight": { - "version": "1.0.6", - "bundled": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "bundled": true - }, - "ini": { - "version": "1.3.5", - "bundled": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "bundled": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "isarray": { - "version": "1.0.0", - "bundled": true - }, - "minimatch": { - "version": "3.0.4", - "bundled": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.0", - "bundled": true - }, - "minipass": { - "version": "2.9.0", - "bundled": true, - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - }, - "minizlib": { - "version": "1.3.3", - "bundled": true, - "requires": { - "minipass": "^2.9.0" - } - }, - "mkdirp": { - "version": "0.5.1", - "bundled": true, - "requires": { - "minimist": "0.0.8" - }, - "dependencies": { - "minimist": { - "version": "0.0.8", - "bundled": true - } - } - }, - "ms": { - "version": "2.1.2", - "bundled": true - }, - "needle": { - "version": "2.4.0", - "bundled": true, - "requires": { - "debug": "^3.2.6", - "iconv-lite": "^0.4.4", - "sax": "^1.2.4" - } - }, - "node-pre-gyp": { - "version": "0.14.0", - "bundled": true, - "requires": { - "detect-libc": "^1.0.2", - "mkdirp": "^0.5.1", - "needle": "^2.2.1", - "nopt": "^4.0.1", - "npm-packlist": "^1.1.6", - "npmlog": "^4.0.2", - "rc": "^1.2.7", - "rimraf": "^2.6.1", - "semver": "^5.3.0", - "tar": "^4.4.2" - } - }, - "nopt": { - "version": "4.0.1", - "bundled": true, - "requires": { - "abbrev": "1", - "osenv": "^0.1.4" - } - }, - "npm-bundled": { - "version": "1.0.6", - "bundled": true - }, - "npm-packlist": { - "version": "1.4.6", - "bundled": true, - "requires": { - "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1" - } - }, - "npmlog": { - "version": "4.1.2", - "bundled": true, - "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "bundled": true - }, - "object-assign": { - "version": "4.1.1", - "bundled": true - }, - "once": { - "version": "1.4.0", - "bundled": true, - "requires": { - "wrappy": "1" - } - }, - "os-homedir": { - "version": "1.0.2", - "bundled": true - }, - "os-tmpdir": { - "version": "1.0.2", - "bundled": true - }, - "osenv": { - "version": "0.1.5", - "bundled": true, - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "bundled": true - }, - "process-nextick-args": { - "version": "2.0.1", - "bundled": true - }, - "protobufjs": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-5.0.3.tgz", - "integrity": "sha512-55Kcx1MhPZX0zTbVosMQEO5R6/rikNXd9b6RQK4KSPcrSIIwoXTtebIczUrXlwaSrbz4x8XUVThGPob1n8I4QA==", - "requires": { - "ascli": "~1", - "bytebuffer": "~5", - "glob": "^7.0.5", - "yargs": "^3.10.0" - } - }, - "rc": { - "version": "1.2.8", - "bundled": true, - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - } - }, - "readable-stream": { - "version": "2.3.6", - "bundled": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "rimraf": { - "version": "2.7.1", - "bundled": true, - "requires": { - "glob": "^7.1.3" - } - }, - "safe-buffer": { - "version": "5.1.2", - "bundled": true - }, - "safer-buffer": { - "version": "2.1.2", - "bundled": true - }, - "sax": { - "version": "1.2.4", - "bundled": true - }, - "semver": { - "version": "5.7.1", - "bundled": true - }, - "set-blocking": { - "version": "2.0.0", - "bundled": true - }, - "signal-exit": { - "version": "3.0.2", - "bundled": true - }, - "string-width": { - "version": "1.0.2", - "bundled": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "string_decoder": { - "version": "1.1.1", - "bundled": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "bundled": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "bundled": true - }, - "tar": { - "version": "4.4.13", - "bundled": true, - "requires": { - "chownr": "^1.1.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.8.6", - "minizlib": "^1.2.1", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.2", - "yallist": "^3.0.3" - } - }, - "util-deprecate": { - "version": "1.0.2", - "bundled": true - }, - "wide-align": { - "version": "1.1.3", - "bundled": true, - "requires": { - "string-width": "^1.0.2 || 2" - } - }, - "wrappy": { - "version": "1.0.2", - "bundled": true - }, - "yallist": { - "version": "3.1.1", - "bundled": true - } - } - }, - "hapi-swagger": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/hapi-swagger/-/hapi-swagger-12.1.0.tgz", - "integrity": "sha512-SXFUMqTnNnXeXilHWOLZPnSMRDqwMQSk0cASqBDGCdH5ORsk+amkaGpBrZSRPO3UELpCMSsk4qgdqhJl6Slqyg==", - "requires": { - "@hapi/boom": "^8.0.1", - "@hapi/hoek": "^9.0.2", - "@hapi/joi": "^17.1.0", - "handlebars": "^4.5.3", - "http-status": "^1.0.1", - "json-schema-ref-parser": "^6.1.0", - "swagger-parser": "4.0.2", - "swagger-ui-dist": "^3.22.1" - }, - "dependencies": { - "@hapi/boom": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-8.0.1.tgz", - "integrity": "sha512-SnBM2GzEYEA6AGFKXBqNLWXR3uNBui0bkmklYXX1gYtevVhDTy2uakwkSauxvIWMtlANGRhzChYg95If3FWCwA==", - "requires": { - "@hapi/hoek": "8.x.x" - }, - "dependencies": { - "@hapi/hoek": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-8.5.1.tgz", - "integrity": "sha512-yN7kbciD87WzLGc5539Tn0sApjyiGHAJgKvG9W8C7O+6c7qmoQMfVs0W4bX17eqz6C78QJqqFrtgdK5EWf6Qow==" - } - } - } - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "interpret": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.0.0.tgz", - "integrity": "sha512-e0/LknJ8wpMMhTiWcjivB+ESwIuvHnBSlBbmP/pSb8CQJldoj1p2qv7xGZ/+BtbTziYRFSz8OsvdbiX45LtYQA==" - }, - "knex": { - "version": "0.20.10", - "resolved": "https://registry.npmjs.org/knex/-/knex-0.20.10.tgz", - "integrity": "sha512-07D6fvY5NdvrfRPmkLLG+OrHvmAy55OX7eXkN8TMiOOI5lWJh1dC2zKjeEQJqUILMOsTnZCGqTKGaRm4t1E9xg==", - "requires": { - "bluebird": "^3.7.2", - "colorette": "1.1.0", - "commander": "^4.1.1", - "debug": "4.1.1", - "esm": "^3.2.25", - "getopts": "2.2.5", - "inherits": "~2.0.4", - "interpret": "^2.0.0", - "liftoff": "3.1.0", - "lodash": "^4.17.15", - "mkdirp": "^0.5.1", - "pg-connection-string": "2.1.0", - "tarn": "^2.0.0", - "tildify": "2.0.0", - "uuid": "^3.4.0", - "v8flags": "^3.1.3" - }, - "dependencies": { - "bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" - } + "@hapi/boom": "9.x.x", + "@hapi/hoek": "9.x.x", + "@hapi/joi": "17.x.x" } }, - "mongodb": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.4.1.tgz", - "integrity": "sha512-juqt5/Z42J4DcE7tG7UdVaTKmUC6zinF4yioPfpeOSNBieWSK6qCY+0tfGQcHLKrauWPDdMZVROHJOa8q2pWsA==", + "@hapi/iron": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@hapi/iron/-/iron-6.0.0.tgz", + "integrity": "sha512-zvGvWDufiTGpTJPG1Y/McN8UqWBu0k/xs/7l++HVU535NLHXsHhy54cfEMdW7EjwKfbBfM9Xy25FmTiobb7Hvw==", "requires": { - "bson": "^1.1.1", - "require_optional": "^1.0.1", - "safe-buffer": "^5.1.2", - "saslprep": "^1.0.0" + "@hapi/b64": "5.x.x", + "@hapi/boom": "9.x.x", + "@hapi/bourne": "2.x.x", + "@hapi/cryptiles": "5.x.x", + "@hapi/hoek": "9.x.x" } }, - "mongoose": { - "version": "5.9.1", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.9.1.tgz", - "integrity": "sha512-qgS31/nZ63vpr8yBg6w8vaV8ITxwrF2ioNW5AakXmqvVBaOsI0xpDd5QBowESy2InDTk+iDaN5SNgSxGG6GntQ==", + "@hapi/joi": { + "version": "17.1.0", + "resolved": "https://registry.npmjs.org/@hapi/joi/-/joi-17.1.0.tgz", + "integrity": "sha512-ob67RcPlwRWxBzLCnWvcwx5qbwf88I3ykD7gcJLWOTRfLLgosK7r6aeChz4thA3XRvuBfI0KB1tPVl2EQFlPXw==", "requires": { - "bson": "~1.1.1", - "kareem": "2.3.1", - "mongodb": "3.5.3", - "mongoose-legacy-pluralize": "1.0.2", - "mpath": "0.6.0", - "mquery": "3.2.2", - "ms": "2.1.2", - "regexp-clone": "1.0.0", - "safe-buffer": "5.1.2", - "sift": "7.0.1", - "sliced": "1.0.1" - }, - "dependencies": { - "mongodb": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.5.3.tgz", - "integrity": "sha512-II7P7A3XUdPiXRgcN96qIoRa1oesM6qLNZkzfPluNZjVkgQk3jnQwOT6/uDk4USRDTTLjNFw2vwfmbRGTA7msg==", - "requires": { - "bl": "^2.2.0", - "bson": "^1.1.1", - "denque": "^1.4.1", - "require_optional": "^1.0.1", - "safe-buffer": "^5.1.2", - "saslprep": "^1.0.0" - } - } + "@hapi/address": "^4.0.0", + "@hapi/formula": "^2.0.0", + "@hapi/hoek": "^9.0.0", + "@hapi/pinpoint": "^2.0.0", + "@hapi/topo": "^5.0.0" } }, - "mysql": { - "version": "2.18.1", - "resolved": "https://registry.npmjs.org/mysql/-/mysql-2.18.1.tgz", - "integrity": "sha512-Bca+gk2YWmqp2Uf6k5NFEurwY/0td0cpebAucFpY/3jhrwrVGuxU2uQFCHjU19SJfje0yQvi+rVWdq78hR5lig==", - "optional": true, + "@hapi/mimos": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@hapi/mimos/-/mimos-5.0.0.tgz", + "integrity": "sha512-EVS6wJYeE73InTlPWt+2e3Izn319iIvffDreci3qDNT+t3lA5ylJ0/SoTaID8e0TPNUkHUSsgJZXEmLHvoYzrA==", "requires": { - "bignumber.js": "9.0.0", - "readable-stream": "2.3.7", - "safe-buffer": "5.1.2", - "sqlstring": "2.3.1" + "@hapi/hoek": "9.x.x", + "mime-db": "1.x.x" } }, - "nise": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/nise/-/nise-3.0.1.tgz", - "integrity": "sha512-fYcH9y0drBGSoi88kvhpbZEsenX58Yr+wOJ4/Mi1K4cy+iGP/a73gNoyNhu5E9QxPdgTlVChfIaAlnyOy/gHUA==", + "@hapi/nigel": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@hapi/nigel/-/nigel-4.0.0.tgz", + "integrity": "sha512-Bqs1pjcDnDQo/XGoiCCNHWTFcMzPbz3L4KU04njeFQMzzEmsojMRX7TX+PezQYCMKtHJOtMg0bHxZyMGqYtbSA==", "requires": { - "@sinonjs/commons": "^1.7.0", - "@sinonjs/formatio": "^4.0.1", - "@sinonjs/text-encoding": "^0.7.1", - "just-extend": "^4.0.2", - "lolex": "^5.0.1", - "path-to-regexp": "^1.7.0" + "@hapi/hoek": "9.x.x", + "@hapi/vise": "4.x.x" } }, - "prom-client": { - "version": "11.5.3", - "resolved": "https://registry.npmjs.org/prom-client/-/prom-client-11.5.3.tgz", - "integrity": "sha512-iz22FmTbtkyL2vt0MdDFY+kWof+S9UB/NACxSn2aJcewtw+EERsen0urSkZ2WrHseNdydsvcxCTAnPcSMZZv4Q==", + "@hapi/pez": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@hapi/pez/-/pez-5.0.2.tgz", + "integrity": "sha512-jr1lAm8mE7J2IBxvDIuDI1qy2aAsoaD2jxOUd/7JRg/Vmrzco8HdKhtz4fKk6KHU6zbbsAp5m5aSWWVTUrag7g==", "requires": { - "tdigest": "^0.1.1" + "@hapi/b64": "5.x.x", + "@hapi/boom": "9.x.x", + "@hapi/content": "^5.0.2", + "@hapi/hoek": "9.x.x", + "@hapi/nigel": "4.x.x" } }, - "sinon": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-8.1.1.tgz", - "integrity": "sha512-E+tWr3acRdoe1nXbHMu86SSqA1WGM7Yw3jZRLvlCMnXwTHP8lgFFVn5BnKnF26uc5SfZ3D7pA9sN7S3Y2jG4Ew==", + "@hapi/podium": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@hapi/podium/-/podium-4.1.0.tgz", + "integrity": "sha512-k/n0McAu8PvonfQRLyKKUvvdb+Gh/O5iAeIwv535Hpxw9B1qZcrYdZyWtHZ8O5PkA9/b/Kk+BdvtgcxeKMB/2g==", "requires": { - "@sinonjs/commons": "^1.7.0", - "@sinonjs/formatio": "^4.0.1", - "@sinonjs/samsam": "^4.2.2", - "diff": "^4.0.2", - "lolex": "^5.1.2", - "nise": "^3.0.1", - "supports-color": "^7.1.0" + "@hapi/hoek": "9.x.x", + "@hapi/joi": "17.x.x", + "@hapi/teamwork": "4.x.x" } }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "@hapi/shot": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@hapi/shot/-/shot-5.0.0.tgz", + "integrity": "sha512-JXddnJkRh3Xhv9lY1tA+TSIUaoODKbdNIPL/M8WFvFQKOttmGaDeqTW5e8Gf01LtLI7L5DraLMULHjrK1+YNFg==", "requires": { - "has-flag": "^4.0.0" + "@hapi/hoek": "9.x.x", + "@hapi/joi": "17.x.x" } - } - } - }, - "@mojaloop/central-services-database": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@mojaloop/central-services-database/-/central-services-database-9.1.0.tgz", - "integrity": "sha512-Uw9h8YMhTdUUNUlQKi/qOrg5g5+fV6WOZhlvzMVy3T0W6lc+UDSQss5tZTlpA1zdC8/gzxhiPuLIEX1PnCzJAQ==", - "requires": { - "knex": "0.20.9", - "lodash": "4.17.15", - "mysql": "2.18.1" - }, - "dependencies": { - "bignumber.js": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz", - "integrity": "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A==" }, - "commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==" - }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "@hapi/somever": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@hapi/somever/-/somever-3.0.0.tgz", + "integrity": "sha512-Upw/kmKotC9iEmK4y047HMYe4LDKsE5NWfjgX41XNKmFvxsQL7OiaCWVhuyyhU0ShDGBfIAnCH8jZr49z/JzZA==", "requires": { - "ms": "^2.1.1" - } - }, - "interpret": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.0.0.tgz", - "integrity": "sha512-e0/LknJ8wpMMhTiWcjivB+ESwIuvHnBSlBbmP/pSb8CQJldoj1p2qv7xGZ/+BtbTziYRFSz8OsvdbiX45LtYQA==" - }, - "knex": { - "version": "0.20.9", - "resolved": "https://registry.npmjs.org/knex/-/knex-0.20.9.tgz", - "integrity": "sha512-Vv2OTcJSZ8z3G5wXZfeLRgdaf+bab2lpKgLBgRJCaVfFX/fPmrP4M1p/ciLR0fZtHpYFtOBUggs9cSjWuj8rAw==", - "requires": { - "bluebird": "^3.7.2", - "colorette": "1.1.0", - "commander": "^4.1.1", - "debug": "4.1.1", - "esm": "^3.2.25", - "getopts": "2.2.5", - "inherits": "~2.0.4", - "interpret": "^2.0.0", - "liftoff": "3.1.0", - "lodash": "^4.17.15", - "mkdirp": "^0.5.1", - "pg-connection-string": "2.1.0", - "tarn": "^2.0.0", - "tildify": "2.0.0", - "uuid": "^3.4.0", - "v8flags": "^3.1.3" + "@hapi/bounce": "2.x.x", + "@hapi/hoek": "9.x.x" } }, - "mysql": { - "version": "2.18.1", - "resolved": "https://registry.npmjs.org/mysql/-/mysql-2.18.1.tgz", - "integrity": "sha512-Bca+gk2YWmqp2Uf6k5NFEurwY/0td0cpebAucFpY/3jhrwrVGuxU2uQFCHjU19SJfje0yQvi+rVWdq78hR5lig==", - "requires": { - "bignumber.js": "9.0.0", - "readable-stream": "2.3.7", - "safe-buffer": "5.1.2", - "sqlstring": "2.3.1" - } - } - } - }, - "@mojaloop/central-services-error-handling": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@mojaloop/central-services-error-handling/-/central-services-error-handling-9.1.0.tgz", - "integrity": "sha512-6tljAx/3kITegnVgCxpOMGtzsrtSLRjNbcwXH1QidKtGeic1MsQIJgQIJuQcQlXGrIVDpJ/4JyOpfKCOh+AxwQ==", - "requires": { - "@mojaloop/sdk-standard-components": "8.6.9", - "lodash": "4.17.15" - }, - "dependencies": { - "@mojaloop/sdk-standard-components": { - "version": "8.6.9", - "resolved": "https://registry.npmjs.org/@mojaloop/sdk-standard-components/-/sdk-standard-components-8.6.9.tgz", - "integrity": "sha512-2S1pmYm8rdAlgrzUhS67+vMpKWWWxYnq2p/Hd4idrWHehSz6C4b1oxF/e4wx4SxVbFkMwgdXQt4Dy4tvoswhbQ==", - "requires": { - "base64url": "^3.0.1", - "ilp-packet": "2.2.0", - "jsonwebtoken": "^8.5.1", - "jws": "^3.2.2", - "request": "^2.34", - "request-promise-native": "^1.0.7" - } - } - } - }, - "@mojaloop/central-services-health": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/@mojaloop/central-services-health/-/central-services-health-9.2.0.tgz", - "integrity": "sha512-5f2c3XOS1Os78wCB7sPDOgbKCDjsIIykMJ9+vFpuMQkWpQsaSdZA0TiQ0fzqWssO60/S+cjqvNH+oN4ZwBd6sA==", - "requires": { - "@hapi/hapi": "19.1.1", - "@mojaloop/central-services-error-handling": "9.1.0", - "@mojaloop/central-services-logger": "9.1.0", - "@mojaloop/central-services-shared": "9.1.4", - "tslib": "1.10.0" - }, - "dependencies": { - "@hapi/accept": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@hapi/accept/-/accept-5.0.1.tgz", - "integrity": "sha512-fMr4d7zLzsAXo28PRRQPXR1o2Wmu+6z+VY1UzDp0iFo13Twj8WePakwXBiqn3E1aAlTpSNzCXdnnQXFhst8h8Q==", - "requires": { - "@hapi/boom": "9.x.x", - "@hapi/hoek": "9.x.x" - } - }, - "@hapi/ammo": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@hapi/ammo/-/ammo-5.0.1.tgz", - "integrity": "sha512-FbCNwcTbnQP4VYYhLNGZmA76xb2aHg9AMPiy18NZyWMG310P5KdFGyA9v2rm5ujrIny77dEEIkMOwl0Xv+fSSA==", - "requires": { - "@hapi/hoek": "9.x.x" - } - }, - "@hapi/b64": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@hapi/b64/-/b64-5.0.0.tgz", - "integrity": "sha512-ngu0tSEmrezoiIaNGG6rRvKOUkUuDdf4XTPnONHGYfSGRmDqPZX5oJL6HAdKTo1UQHECbdB4OzhWrfgVppjHUw==", - "requires": { - "@hapi/hoek": "9.x.x" - } - }, - "@hapi/bounce": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@hapi/bounce/-/bounce-2.0.0.tgz", - "integrity": "sha512-JesW92uyzOOyuzJKjoLHM1ThiOvHPOLDHw01YV8yh5nCso7sDwJho1h0Ad2N+E62bZyz46TG3xhAi/78Gsct6A==", - "requires": { - "@hapi/boom": "9.x.x", - "@hapi/hoek": "9.x.x" - } - }, - "@hapi/bourne": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-2.0.0.tgz", - "integrity": "sha512-WEezM1FWztfbzqIUbsDzFRVMxSoLy3HugVcux6KDDtTqzPsLE8NDRHfXvev66aH1i2oOKKar3/XDjbvh/OUBdg==" - }, - "@hapi/call": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@hapi/call/-/call-8.0.0.tgz", - "integrity": "sha512-4xHIWWqaIDQlVU88XAnomACSoC7iWUfaLfdu2T7I0y+HFFwZUrKKGfwn6ik4kwKsJRMnOliG3UXsF8V/94+Lkg==", - "requires": { - "@hapi/address": "4.x.x", - "@hapi/boom": "9.x.x", - "@hapi/hoek": "9.x.x" - } - }, - "@hapi/content": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@hapi/content/-/content-5.0.2.tgz", - "integrity": "sha512-mre4dl1ygd4ZyOH3tiYBrOUBzV7Pu/EOs8VLGf58vtOEECWed8Uuw6B4iR9AN/8uQt42tB04qpVaMyoMQh0oMw==", - "requires": { - "@hapi/boom": "9.x.x" - } - }, - "@hapi/cryptiles": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@hapi/cryptiles/-/cryptiles-5.0.0.tgz", - "integrity": "sha512-Yq43ti9N51Z7jbm0Q7YVCcofA+4Gh5wsBX/jZ++Z+FM8GYfBQ1WmI9ufZSL+BVX8vRxzDkdQ2fKoG6cxOQlnVQ==", - "requires": { - "@hapi/boom": "9.x.x" - } - }, - "@hapi/file": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@hapi/file/-/file-2.0.0.tgz", - "integrity": "sha512-WSrlgpvEqgPWkI18kkGELEZfXr0bYLtr16iIN4Krh9sRnzBZN6nnWxHFxtsnP684wueEySBbXPDg/WfA9xJdBQ==" - }, - "@hapi/formula": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@hapi/formula/-/formula-2.0.0.tgz", - "integrity": "sha512-V87P8fv7PI0LH7LiVi8Lkf3x+KCO7pQozXRssAHNXXL9L1K+uyu4XypLXwxqVDKgyQai6qj3/KteNlrqDx4W5A==" - }, - "@hapi/hapi": { - "version": "19.1.1", - "resolved": "https://registry.npmjs.org/@hapi/hapi/-/hapi-19.1.1.tgz", - "integrity": "sha512-rpQzSs0XsHSF7usM4qdJJ0Bcmhs9stWhUW3OiamW33bw4qL8q3uEgUKB9KH8ODmluCAkkXOQ0X0Dh9t94E5VIw==", - "requires": { - "@hapi/accept": "^5.0.1", - "@hapi/ammo": "^5.0.1", - "@hapi/boom": "9.x.x", - "@hapi/bounce": "2.x.x", - "@hapi/call": "8.x.x", - "@hapi/catbox": "11.x.x", - "@hapi/catbox-memory": "5.x.x", - "@hapi/heavy": "7.x.x", - "@hapi/hoek": "9.x.x", - "@hapi/joi": "17.x.x", - "@hapi/mimos": "5.x.x", - "@hapi/podium": "4.x.x", - "@hapi/shot": "5.x.x", - "@hapi/somever": "3.x.x", - "@hapi/statehood": "^7.0.2", - "@hapi/subtext": "^7.0.3", - "@hapi/teamwork": "4.x.x", - "@hapi/topo": "5.x.x" - } - }, - "@hapi/heavy": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@hapi/heavy/-/heavy-7.0.0.tgz", - "integrity": "sha512-n/nheUG6zNleWkjY+3fzV3VJIAumUCaa/WoTmurjqlYY5JgC5ZKOpvP7tWi8rXmKZhbcXgjH3fHFoM55LoBT7g==", - "requires": { - "@hapi/boom": "9.x.x", - "@hapi/hoek": "9.x.x", - "@hapi/joi": "17.x.x" - } - }, - "@hapi/iron": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@hapi/iron/-/iron-6.0.0.tgz", - "integrity": "sha512-zvGvWDufiTGpTJPG1Y/McN8UqWBu0k/xs/7l++HVU535NLHXsHhy54cfEMdW7EjwKfbBfM9Xy25FmTiobb7Hvw==", - "requires": { - "@hapi/b64": "5.x.x", - "@hapi/boom": "9.x.x", - "@hapi/bourne": "2.x.x", - "@hapi/cryptiles": "5.x.x", - "@hapi/hoek": "9.x.x" - } - }, - "@hapi/joi": { - "version": "17.1.0", - "resolved": "https://registry.npmjs.org/@hapi/joi/-/joi-17.1.0.tgz", - "integrity": "sha512-ob67RcPlwRWxBzLCnWvcwx5qbwf88I3ykD7gcJLWOTRfLLgosK7r6aeChz4thA3XRvuBfI0KB1tPVl2EQFlPXw==", - "requires": { - "@hapi/address": "^4.0.0", - "@hapi/formula": "^2.0.0", - "@hapi/hoek": "^9.0.0", - "@hapi/pinpoint": "^2.0.0", - "@hapi/topo": "^5.0.0" - } - }, - "@hapi/mimos": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@hapi/mimos/-/mimos-5.0.0.tgz", - "integrity": "sha512-EVS6wJYeE73InTlPWt+2e3Izn319iIvffDreci3qDNT+t3lA5ylJ0/SoTaID8e0TPNUkHUSsgJZXEmLHvoYzrA==", - "requires": { - "@hapi/hoek": "9.x.x", - "mime-db": "1.x.x" - } - }, - "@hapi/nigel": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@hapi/nigel/-/nigel-4.0.0.tgz", - "integrity": "sha512-Bqs1pjcDnDQo/XGoiCCNHWTFcMzPbz3L4KU04njeFQMzzEmsojMRX7TX+PezQYCMKtHJOtMg0bHxZyMGqYtbSA==", - "requires": { - "@hapi/hoek": "9.x.x", - "@hapi/vise": "4.x.x" - } - }, - "@hapi/pez": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@hapi/pez/-/pez-5.0.2.tgz", - "integrity": "sha512-jr1lAm8mE7J2IBxvDIuDI1qy2aAsoaD2jxOUd/7JRg/Vmrzco8HdKhtz4fKk6KHU6zbbsAp5m5aSWWVTUrag7g==", - "requires": { - "@hapi/b64": "5.x.x", - "@hapi/boom": "9.x.x", - "@hapi/content": "^5.0.2", - "@hapi/hoek": "9.x.x", - "@hapi/nigel": "4.x.x" - } - }, - "@hapi/pinpoint": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@hapi/pinpoint/-/pinpoint-2.0.0.tgz", - "integrity": "sha512-vzXR5MY7n4XeIvLpfl3HtE3coZYO4raKXW766R6DZw/6aLqR26iuZ109K7a0NtF2Db0jxqh7xz2AxkUwpUFybw==" - }, - "@hapi/shot": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@hapi/shot/-/shot-5.0.0.tgz", - "integrity": "sha512-JXddnJkRh3Xhv9lY1tA+TSIUaoODKbdNIPL/M8WFvFQKOttmGaDeqTW5e8Gf01LtLI7L5DraLMULHjrK1+YNFg==", - "requires": { - "@hapi/hoek": "9.x.x", - "@hapi/joi": "17.x.x" - } - }, - "@hapi/somever": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@hapi/somever/-/somever-3.0.0.tgz", - "integrity": "sha512-Upw/kmKotC9iEmK4y047HMYe4LDKsE5NWfjgX41XNKmFvxsQL7OiaCWVhuyyhU0ShDGBfIAnCH8jZr49z/JzZA==", - "requires": { - "@hapi/bounce": "2.x.x", - "@hapi/hoek": "9.x.x" - } - }, - "@hapi/statehood": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@hapi/statehood/-/statehood-7.0.2.tgz", - "integrity": "sha512-+0VNxysQu+UYzkfvAXq3X4aN65TnUwiR7gsq2cQ/4Rq26nCJjHAfrkYReEeshU2hPmJ3m5QuaBzyDqRm8WOpyg==", + "@hapi/statehood": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@hapi/statehood/-/statehood-7.0.2.tgz", + "integrity": "sha512-+0VNxysQu+UYzkfvAXq3X4aN65TnUwiR7gsq2cQ/4Rq26nCJjHAfrkYReEeshU2hPmJ3m5QuaBzyDqRm8WOpyg==", "requires": { "@hapi/boom": "9.x.x", "@hapi/bounce": "2.x.x", @@ -2499,705 +1597,105 @@ "@hapi/joi": "17.x.x" } }, - "@hapi/subtext": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/@hapi/subtext/-/subtext-7.0.3.tgz", - "integrity": "sha512-CekDizZkDGERJ01C0+TzHlKtqdXZxzSWTOaH6THBrbOHnsr3GY+yiMZC+AfNCypfE17RaIakGIAbpL2Tk1z2+A==", - "requires": { - "@hapi/boom": "9.x.x", - "@hapi/bourne": "2.x.x", - "@hapi/content": "^5.0.2", - "@hapi/file": "2.x.x", - "@hapi/hoek": "9.x.x", - "@hapi/pez": "^5.0.1", - "@hapi/wreck": "17.x.x" - } - }, - "@hapi/teamwork": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@hapi/teamwork/-/teamwork-4.0.0.tgz", - "integrity": "sha512-V6xYOrr5aFv/IJqNPneaYCu8vuGTKisamqHVRS3JJnbZr18TrpXdsJOYk9pjPhFti+M2YETPebQLUr820N5NoQ==" - }, - "@hapi/vise": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@hapi/vise/-/vise-4.0.0.tgz", - "integrity": "sha512-eYyLkuUiFZTer59h+SGy7hUm+qE9p+UemePTHLlIWppEd+wExn3Df5jO04bFQTm7nleF5V8CtuYQYb+VFpZ6Sg==", - "requires": { - "@hapi/hoek": "9.x.x" - } - }, - "@hapi/wreck": { - "version": "17.0.0", - "resolved": "https://registry.npmjs.org/@hapi/wreck/-/wreck-17.0.0.tgz", - "integrity": "sha512-d8lqCinbKyDByn7GzJDRDbitddhIEydNm44UcAMejfhEH3o4IYvKYq6K8cAqXbilXPuvZc0ErlUOg9SDdgRtMw==", - "requires": { - "@hapi/boom": "9.x.x", - "@hapi/bourne": "2.x.x", - "@hapi/hoek": "9.x.x" - } - } - } - }, - "@mojaloop/central-services-logger": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@mojaloop/central-services-logger/-/central-services-logger-9.1.0.tgz", - "integrity": "sha512-V45TPKI+0mqrSuQBOGH01yVotldu/6XCjsNKZ0M+82Kwhb0enQFSb4CKUr6TLjZINQfrNzQOofaSCJx8sUeSpg==", - "requires": { - "parse-strings-in-object": "2.0.0", - "rc": "1.2.8", - "winston": "3.2.1" - } - }, - "@mojaloop/central-services-shared": { - "version": "9.1.4", - "resolved": "https://registry.npmjs.org/@mojaloop/central-services-shared/-/central-services-shared-9.1.4.tgz", - "integrity": "sha512-2TirKMS8VrBlYWxq15LMDp0kC9SDxfRF9Ateyhov6yprSaHtHLpmcLc/CQ+deWchS1N66v/bpW2urx07VY2T0A==", - "requires": { - "@hapi/catbox": "11.0.1", - "@hapi/catbox-memory": "5.0.0", - "@mojaloop/central-services-error-handling": "9.1.0", - "@mojaloop/central-services-logger": "9.1.0", - "@mojaloop/central-services-metrics": "9.1.0", - "@mojaloop/event-sdk": "8.8.1-snapshot.2", - "axios": "0.19.2", - "base64url": "3.0.1", - "clone": "2.1.2", - "data-urls": "2.0.0", - "immutable": "3.8.2", - "lodash": "4.17.15", - "mustache": "4.0.0", - "raw-body": "2.4.1" - }, - "dependencies": { - "@grpc/proto-loader": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.5.3.tgz", - "integrity": "sha512-8qvUtGg77G2ZT2HqdqYoM/OY97gQd/0crSG34xNmZ4ZOsv3aQT/FQV9QfZPazTGna6MIoyUd+u6AxsoZjJ/VMQ==", - "requires": { - "lodash.camelcase": "^4.3.0", - "protobufjs": "^6.8.6" - } - }, - "@mojaloop/central-services-metrics": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@mojaloop/central-services-metrics/-/central-services-metrics-9.1.0.tgz", - "integrity": "sha512-pE+zgVduOORYipJsUTa3ugm930Uzm8dnLxIkJEpBFoZWPqknDMTSZFvy2PU3g/hft2+IIuLSlOzHC7YxNdWBRA==", - "requires": { - "prom-client": "11.5.3" - } - }, - "@mojaloop/event-sdk": { - "version": "8.8.1-snapshot.2", - "resolved": "https://registry.npmjs.org/@mojaloop/event-sdk/-/event-sdk-8.8.1-snapshot.2.tgz", - "integrity": "sha512-ufq+PCbASUfVA24jJ7yFKBUNVVeayop9Us8eKh05Ow5AD+JNfoulVDKPnhqDplTuRQ8Jss88u2kjPmcx9B8IXA==", - "requires": { - "@grpc/proto-loader": "0.5.3", - "@mojaloop/central-services-logger": "8.6.0", - "@types/protobufjs": "6.0.0", - "brototype": "0.0.6", - "error-callsites": "2.0.2", - "grpc": "1.24.2", - "lodash": "4.17.15", - "moment": "2.24.0", - "parse-strings-in-object": "2.0.0", - "protobufjs": "6.8.8", - "rc": "1.2.8", - "serialize-error": "4.1.0", - "sinon": "8.0.4", - "traceparent": "1.0.0", - "tslib": "1.10.0", - "uuid4": "1.1.4", - "winston": "3.2.1" - }, - "dependencies": { - "@mojaloop/central-services-logger": { - "version": "8.6.0", - "resolved": "https://registry.npmjs.org/@mojaloop/central-services-logger/-/central-services-logger-8.6.0.tgz", - "integrity": "sha512-TPr6gnYxJfeUjOccDVM10uibPCF4magYhUUbK8xGuPHUfZNAN1uxKGVdkWoDvHvu5ZCZPolie+12ANogAQIH9Q==", - "requires": { - "parse-strings-in-object": "1.2.0", - "rc": "1.2.8", - "winston": "3.2.1" - }, - "dependencies": { - "parse-strings-in-object": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/parse-strings-in-object/-/parse-strings-in-object-1.2.0.tgz", - "integrity": "sha512-lI8XgBWZ5COL0G2G6MsLqPSc4X/SnYPw5jGDiins/qzdOdlY493j/niCY9UiJqWjDoeY20k7vhEXvKRHTy6Dfw==" - } - } - } - } - }, - "@sinonjs/formatio": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-4.0.1.tgz", - "integrity": "sha512-asIdlLFrla/WZybhm0C8eEzaDNNrzymiTqHMeJl6zPW2881l3uuVRpm0QlRQEjqYWv6CcKMGYME3LbrLJsORBw==", - "requires": { - "@sinonjs/commons": "^1", - "@sinonjs/samsam": "^4.2.0" - } - }, - "@sinonjs/samsam": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-4.2.2.tgz", - "integrity": "sha512-z9o4LZUzSD9Hl22zV38aXNykgFeVj8acqfFabCY6FY83n/6s/XwNJyYYldz6/9lBJanpno9h+oL6HTISkviweA==", - "requires": { - "@sinonjs/commons": "^1.6.0", - "lodash.get": "^4.4.2", - "type-detect": "^4.0.8" - } - }, - "axios": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz", - "integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==", - "requires": { - "follow-redirects": "1.5.10" - } - }, - "data-urls": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", - "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", - "requires": { - "abab": "^2.0.3", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.0.0" - } - }, - "diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==" - }, - "grpc": { - "version": "1.24.2", - "resolved": "https://registry.npmjs.org/grpc/-/grpc-1.24.2.tgz", - "integrity": "sha512-EG3WH6AWMVvAiV15d+lr+K77HJ/KV/3FvMpjKjulXHbTwgDZkhkcWbwhxFAoTdxTkQvy0WFcO3Nog50QBbHZWw==", - "requires": { - "@types/bytebuffer": "^5.0.40", - "lodash.camelcase": "^4.3.0", - "lodash.clone": "^4.5.0", - "nan": "^2.13.2", - "node-pre-gyp": "^0.14.0", - "protobufjs": "^5.0.3" - }, - "dependencies": { - "abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" - }, - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" - }, - "aproba": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" - }, - "are-we-there-yet": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", - "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - } - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "chownr": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.3.tgz", - "integrity": "sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw==" - }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" - }, - "console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" - }, - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "requires": { - "ms": "^2.1.1" - } - }, - "deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" - }, - "delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" - }, - "detect-libc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", - "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" - }, - "fs-minipass": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", - "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", - "requires": { - "minipass": "^2.6.0" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" - }, - "gauge": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", - "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", - "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" - } - }, - "glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ignore-walk": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz", - "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==", - "requires": { - "minimatch": "^3.0.4" - } - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" - }, - "minipass": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", - "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - }, - "minizlib": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", - "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", - "requires": { - "minipass": "^2.9.0" - } - }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "requires": { - "minimist": "0.0.8" - }, - "dependencies": { - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" - } - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "needle": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/needle/-/needle-2.4.0.tgz", - "integrity": "sha512-4Hnwzr3mi5L97hMYeNl8wRW/Onhy4nUKR/lVemJ8gJedxxUyBLm9kkrDColJvoSfwi0jCNhD+xCdOtiGDQiRZg==", - "requires": { - "debug": "^3.2.6", - "iconv-lite": "^0.4.4", - "sax": "^1.2.4" - } - }, - "node-pre-gyp": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.14.0.tgz", - "integrity": "sha512-+CvDC7ZttU/sSt9rFjix/P05iS43qHCOOGzcr3Ry99bXG7VX953+vFyEuph/tfqoYu8dttBkE86JSKBO2OzcxA==", - "requires": { - "detect-libc": "^1.0.2", - "mkdirp": "^0.5.1", - "needle": "^2.2.1", - "nopt": "^4.0.1", - "npm-packlist": "^1.1.6", - "npmlog": "^4.0.2", - "rc": "^1.2.7", - "rimraf": "^2.6.1", - "semver": "^5.3.0", - "tar": "^4.4.2" - } - }, - "nopt": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", - "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", - "requires": { - "abbrev": "1", - "osenv": "^0.1.4" - } - }, - "npm-bundled": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.6.tgz", - "integrity": "sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==" - }, - "npm-packlist": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.6.tgz", - "integrity": "sha512-u65uQdb+qwtGvEJh/DgQgW1Xg7sqeNbmxYyrvlNznaVTjV3E5P6F/EFjM+BVHXl7JJlsdG8A64M0XI8FI/IOlg==", - "requires": { - "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1" - } - }, - "npmlog": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", - "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", - "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "requires": { - "wrappy": "1" - } - }, - "os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" - }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" - }, - "osenv": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", - "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" - }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" - }, - "protobufjs": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-5.0.3.tgz", - "integrity": "sha512-55Kcx1MhPZX0zTbVosMQEO5R6/rikNXd9b6RQK4KSPcrSIIwoXTtebIczUrXlwaSrbz4x8XUVThGPob1n8I4QA==", - "requires": { - "ascli": "~1", - "bytebuffer": "~5", - "glob": "^7.0.5", - "yargs": "^3.10.0" - } - }, - "rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - } - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "requires": { - "glob": "^7.1.3" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" - }, - "signal-exit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" - }, - "tar": { - "version": "4.4.13", - "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz", - "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==", - "requires": { - "chownr": "^1.1.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.8.6", - "minizlib": "^1.2.1", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.2", - "yallist": "^3.0.3" - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" - }, - "wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", - "requires": { - "string-width": "^1.0.2 || 2" - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - }, - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" - } - } - }, - "has-flag": { + "@hapi/subtext": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@hapi/subtext/-/subtext-7.0.3.tgz", + "integrity": "sha512-CekDizZkDGERJ01C0+TzHlKtqdXZxzSWTOaH6THBrbOHnsr3GY+yiMZC+AfNCypfE17RaIakGIAbpL2Tk1z2+A==", + "requires": { + "@hapi/boom": "9.x.x", + "@hapi/bourne": "2.x.x", + "@hapi/content": "^5.0.2", + "@hapi/file": "2.x.x", + "@hapi/hoek": "9.x.x", + "@hapi/pez": "^5.0.1", + "@hapi/wreck": "17.x.x" + } + }, + "@hapi/teamwork": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + "resolved": "https://registry.npmjs.org/@hapi/teamwork/-/teamwork-4.0.0.tgz", + "integrity": "sha512-V6xYOrr5aFv/IJqNPneaYCu8vuGTKisamqHVRS3JJnbZr18TrpXdsJOYk9pjPhFti+M2YETPebQLUr820N5NoQ==" }, - "lolex": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/lolex/-/lolex-5.1.2.tgz", - "integrity": "sha512-h4hmjAvHTmd+25JSwrtTIuwbKdwg5NzZVRMLn9saij4SZaepCrTCxPr35H/3bjwfMJtN+t3CX8672UIkglz28A==", + "@hapi/vise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@hapi/vise/-/vise-4.0.0.tgz", + "integrity": "sha512-eYyLkuUiFZTer59h+SGy7hUm+qE9p+UemePTHLlIWppEd+wExn3Df5jO04bFQTm7nleF5V8CtuYQYb+VFpZ6Sg==", "requires": { - "@sinonjs/commons": "^1.7.0" + "@hapi/hoek": "9.x.x" } }, - "nise": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/nise/-/nise-3.0.1.tgz", - "integrity": "sha512-fYcH9y0drBGSoi88kvhpbZEsenX58Yr+wOJ4/Mi1K4cy+iGP/a73gNoyNhu5E9QxPdgTlVChfIaAlnyOy/gHUA==", + "@hapi/wreck": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/@hapi/wreck/-/wreck-17.0.0.tgz", + "integrity": "sha512-d8lqCinbKyDByn7GzJDRDbitddhIEydNm44UcAMejfhEH3o4IYvKYq6K8cAqXbilXPuvZc0ErlUOg9SDdgRtMw==", "requires": { - "@sinonjs/commons": "^1.7.0", - "@sinonjs/formatio": "^4.0.1", - "@sinonjs/text-encoding": "^0.7.1", - "just-extend": "^4.0.2", - "lolex": "^5.0.1", - "path-to-regexp": "^1.7.0" + "@hapi/boom": "9.x.x", + "@hapi/bourne": "2.x.x", + "@hapi/hoek": "9.x.x" + } + }, + "@mojaloop/central-services-shared": { + "version": "9.1.4", + "resolved": "https://registry.npmjs.org/@mojaloop/central-services-shared/-/central-services-shared-9.1.4.tgz", + "integrity": "sha512-2TirKMS8VrBlYWxq15LMDp0kC9SDxfRF9Ateyhov6yprSaHtHLpmcLc/CQ+deWchS1N66v/bpW2urx07VY2T0A==", + "requires": { + "@hapi/catbox": "11.0.1", + "@hapi/catbox-memory": "5.0.0", + "@mojaloop/central-services-error-handling": "9.1.0", + "@mojaloop/central-services-logger": "9.1.0", + "@mojaloop/central-services-metrics": "9.1.0", + "@mojaloop/event-sdk": "8.8.1-snapshot.2", + "axios": "0.19.2", + "base64url": "3.0.1", + "clone": "2.1.2", + "data-urls": "2.0.0", + "immutable": "3.8.2", + "lodash": "4.17.15", + "mustache": "4.0.0", + "raw-body": "2.4.1" } }, - "prom-client": { - "version": "11.5.3", - "resolved": "https://registry.npmjs.org/prom-client/-/prom-client-11.5.3.tgz", - "integrity": "sha512-iz22FmTbtkyL2vt0MdDFY+kWof+S9UB/NACxSn2aJcewtw+EERsen0urSkZ2WrHseNdydsvcxCTAnPcSMZZv4Q==", + "@mojaloop/event-sdk": { + "version": "8.8.1-snapshot.2", + "resolved": "https://registry.npmjs.org/@mojaloop/event-sdk/-/event-sdk-8.8.1-snapshot.2.tgz", + "integrity": "sha512-ufq+PCbASUfVA24jJ7yFKBUNVVeayop9Us8eKh05Ow5AD+JNfoulVDKPnhqDplTuRQ8Jss88u2kjPmcx9B8IXA==", "requires": { - "tdigest": "^0.1.1" + "@grpc/proto-loader": "0.5.3", + "@mojaloop/central-services-logger": "8.6.0", + "@types/protobufjs": "6.0.0", + "brototype": "0.0.6", + "error-callsites": "2.0.2", + "grpc": "1.24.2", + "lodash": "4.17.15", + "moment": "2.24.0", + "parse-strings-in-object": "2.0.0", + "protobufjs": "6.8.8", + "rc": "1.2.8", + "serialize-error": "4.1.0", + "sinon": "8.0.4", + "traceparent": "1.0.0", + "tslib": "1.10.0", + "uuid4": "1.1.4", + "winston": "3.2.1" + }, + "dependencies": { + "@mojaloop/central-services-logger": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/@mojaloop/central-services-logger/-/central-services-logger-8.6.0.tgz", + "integrity": "sha512-TPr6gnYxJfeUjOccDVM10uibPCF4magYhUUbK8xGuPHUfZNAN1uxKGVdkWoDvHvu5ZCZPolie+12ANogAQIH9Q==", + "requires": { + "parse-strings-in-object": "1.2.0", + "rc": "1.2.8", + "winston": "3.2.1" + }, + "dependencies": { + "parse-strings-in-object": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/parse-strings-in-object/-/parse-strings-in-object-1.2.0.tgz", + "integrity": "sha512-lI8XgBWZ5COL0G2G6MsLqPSc4X/SnYPw5jGDiins/qzdOdlY493j/niCY9UiJqWjDoeY20k7vhEXvKRHTy6Dfw==" + } + } + } } }, "sinon": { @@ -3213,70 +1711,159 @@ "nise": "^3.0.1", "supports-color": "^7.1.0" } - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + } + } + }, + "@mojaloop/central-services-logger": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/@mojaloop/central-services-logger/-/central-services-logger-9.1.0.tgz", + "integrity": "sha512-V45TPKI+0mqrSuQBOGH01yVotldu/6XCjsNKZ0M+82Kwhb0enQFSb4CKUr6TLjZINQfrNzQOofaSCJx8sUeSpg==", + "requires": { + "parse-strings-in-object": "2.0.0", + "rc": "1.2.8", + "winston": "3.2.1" + } + }, + "@mojaloop/central-services-metrics": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/@mojaloop/central-services-metrics/-/central-services-metrics-9.1.0.tgz", + "integrity": "sha512-pE+zgVduOORYipJsUTa3ugm930Uzm8dnLxIkJEpBFoZWPqknDMTSZFvy2PU3g/hft2+IIuLSlOzHC7YxNdWBRA==", + "requires": { + "prom-client": "11.5.3" + } + }, + "@mojaloop/central-services-shared": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/@mojaloop/central-services-shared/-/central-services-shared-9.2.0.tgz", + "integrity": "sha512-RFVXd6hwR66iQzwnU5st68cQBCiJjXHqiQp6sjkUJ+1ElqQjhnDMZJcLM5KAfkpSxDmTwwqqKuLhBYFZN5Jj7A==", + "requires": { + "@hapi/catbox": "11.0.1", + "@hapi/catbox-memory": "5.0.0", + "@mojaloop/central-services-error-handling": "9.1.0", + "@mojaloop/central-services-logger": "9.1.0", + "@mojaloop/central-services-metrics": "9.1.0", + "@mojaloop/event-sdk": "9.2.0", + "axios": "0.19.2", + "base64url": "3.0.1", + "clone": "2.1.2", + "data-urls": "2.0.0", + "immutable": "3.8.2", + "lodash": "4.17.15", + "mustache": "4.0.0", + "raw-body": "2.4.1" + }, + "dependencies": { + "@hapi/catbox": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/@hapi/catbox/-/catbox-11.0.1.tgz", + "integrity": "sha512-CsdannMSzWqLcJ7rXT55JGAzoR+BPXesKn9POOrF0A0wsumbUwHP7vxBUH/21YitcM/dLxjUfphkRAQT+XaoyQ==", "requires": { - "has-flag": "^4.0.0" + "@hapi/boom": "9.x.x", + "@hapi/hoek": "9.x.x", + "@hapi/joi": "17.x.x", + "@hapi/podium": "4.x.x" } }, - "tr46": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.0.0.tgz", - "integrity": "sha512-LrErSqfhdUw73AC/eXV2fEmNkvgSYxfm5lvxnLvuVgoVDknvD28Pa5FeDGc8RuVouDxUD3GnHHFv7xnBp7As5w==", + "@hapi/joi": { + "version": "17.1.0", + "resolved": "https://registry.npmjs.org/@hapi/joi/-/joi-17.1.0.tgz", + "integrity": "sha512-ob67RcPlwRWxBzLCnWvcwx5qbwf88I3ykD7gcJLWOTRfLLgosK7r6aeChz4thA3XRvuBfI0KB1tPVl2EQFlPXw==", "requires": { - "punycode": "^2.1.1" + "@hapi/address": "^4.0.0", + "@hapi/formula": "^2.0.0", + "@hapi/hoek": "^9.0.0", + "@hapi/pinpoint": "^2.0.0", + "@hapi/topo": "^5.0.0" } }, - "webidl-conversions": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", - "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==" - }, - "whatwg-url": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.0.0.tgz", - "integrity": "sha512-41ou2Dugpij8/LPO5Pq64K5q++MnRCBpEHvQr26/mArEKTkCV5aoXIqyhuYtE0pkqScXwhf2JP57rkRTYM29lQ==", + "@hapi/podium": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@hapi/podium/-/podium-4.1.0.tgz", + "integrity": "sha512-k/n0McAu8PvonfQRLyKKUvvdb+Gh/O5iAeIwv535Hpxw9B1qZcrYdZyWtHZ8O5PkA9/b/Kk+BdvtgcxeKMB/2g==", "requires": { - "lodash.sortby": "^4.7.0", - "tr46": "^2.0.0", - "webidl-conversions": "^5.0.0" + "@hapi/hoek": "9.x.x", + "@hapi/joi": "17.x.x", + "@hapi/teamwork": "4.x.x" } + }, + "@hapi/teamwork": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@hapi/teamwork/-/teamwork-4.0.0.tgz", + "integrity": "sha512-V6xYOrr5aFv/IJqNPneaYCu8vuGTKisamqHVRS3JJnbZr18TrpXdsJOYk9pjPhFti+M2YETPebQLUr820N5NoQ==" } } }, "@mojaloop/central-services-stream": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@mojaloop/central-services-stream/-/central-services-stream-9.1.0.tgz", - "integrity": "sha512-QgwPElwQdK4UpKZpMty1TiJdR57pnrQRGMUN8XlQ3KvDoaVHSdJLnd56h32NEHm5anW4z/LVxzUnbCPRo2PqJw==", + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/@mojaloop/central-services-stream/-/central-services-stream-9.2.0.tgz", + "integrity": "sha512-RONjSGsrL6rfeD7KkXF013LCo/eicbkAnOFwZyZcEN+d4zM467kx0kmW4zJrQoKr9660CmfCksTkqtrGnud4Tg==", "requires": { "@mojaloop/central-services-error-handling": "9.1.0", "@mojaloop/central-services-logger": "9.1.0", - "async": "3.1.1", + "async": "3.2.0", "events": "3.1.0", "node-rdkafka": "2.7.4" + } + }, + "@mojaloop/event-sdk": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/@mojaloop/event-sdk/-/event-sdk-9.2.0.tgz", + "integrity": "sha512-CpdmsSAjieE+053gxbS+H1eeEiuXX2x0qDXhiGWjynMeEWdZhZsZnTACRDAKcxdqZjJdo2ioVw8XVWqaPeyZtA==", + "requires": { + "@grpc/proto-loader": "0.5.3", + "@mojaloop/central-services-logger": "9.1.0", + "brototype": "0.0.6", + "error-callsites": "2.0.2", + "grpc": "1.24.2", + "lodash": "4.17.15", + "moment": "2.24.0", + "parse-strings-in-object": "2.0.0", + "protobufjs": "6.8.8", + "rc": "1.2.8", + "serialize-error": "4.1.0", + "sinon": "8.1.1", + "traceparent": "1.0.0", + "tslib": "1.10.0", + "uuid4": "1.1.4" }, "dependencies": { - "async": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/async/-/async-3.1.1.tgz", - "integrity": "sha512-X5Dj8hK1pJNC2Wzo2Rcp9FBVdJMGRR/S7V+lH46s8GVFhtbo5O4Le5GECCF/8PISVdkUA6mMPvgz7qTTD1rf1g==" - }, - "events": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.1.0.tgz", - "integrity": "sha512-Rv+u8MLHNOdMjTAFeT3nCjHn2aGlx435FP/sDHNaRhDEMwyI/aB22Kj2qIN8R0cw3z28psEQLYwxVKLsKrMgWg==" - }, - "node-rdkafka": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/node-rdkafka/-/node-rdkafka-2.7.4.tgz", - "integrity": "sha512-415Hfu2SkAo9+mSj3xxw+dHwJqC0jRY0D/OKqUdPfvEE1r4p+N0OfbsAmmSR5FSxZFRFTY3j+/tGntp6ssqHWw==", + "sinon": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-8.1.1.tgz", + "integrity": "sha512-E+tWr3acRdoe1nXbHMu86SSqA1WGM7Yw3jZRLvlCMnXwTHP8lgFFVn5BnKnF26uc5SfZ3D7pA9sN7S3Y2jG4Ew==", + "requires": { + "@sinonjs/commons": "^1.7.0", + "@sinonjs/formatio": "^4.0.1", + "@sinonjs/samsam": "^4.2.2", + "diff": "^4.0.2", + "lolex": "^5.1.2", + "nise": "^3.0.1", + "supports-color": "^7.1.0" + } + } + } + }, + "@mojaloop/forensic-logging-client": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/@mojaloop/forensic-logging-client/-/forensic-logging-client-8.3.0.tgz", + "integrity": "sha512-kDWZeD4Xj7LzaTtr/kbaulGJSQVaOi2PVW/G2NWMuKARMnYcAbpF+m5uXcinz5Iun0CHtVrNVfh4/PFhvQ73Bg==", + "requires": { + "@mojaloop/central-services-logger": "8.1.2", + "bluebird": "3.7.1" + }, + "dependencies": { + "@mojaloop/central-services-logger": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/@mojaloop/central-services-logger/-/central-services-logger-8.1.2.tgz", + "integrity": "sha512-wNnr07xcJNAy+KX2C8Djb6ubeH2c1KkfXMyMJz+/dKrfqyVcqcI0RuhneERZrJMI5Ah4X9Sjcuz+LqH9HQoW/w==", "requires": { - "bindings": "^1.3.1", - "nan": "^2.14.0" + "winston": "3.2.1" } + }, + "bluebird": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.1.tgz", + "integrity": "sha512-DdmyoGCleJnkbp3nkbxTLJ18rjDsE4yCggEwKNXkeV123sPNfOCYeDoeuOY+F2FrSjO1YXcTU+dsy96KMy+gcg==" } } }, @@ -3286,13 +1873,19 @@ "integrity": "sha512-9g08YprQ/6MdI3SfW+EGJ2uQ7oWhXw7vxTo55BUoVzrOnZckXXuJy/jsZSADjUAyQtm/+Bcl9mrRaOGNeS8ayA==", "requires": { "bignumber.js": "9.0.0" - }, - "dependencies": { - "bignumber.js": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz", - "integrity": "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A==" - } + } + }, + "@mojaloop/sdk-standard-components": { + "version": "8.6.9", + "resolved": "https://registry.npmjs.org/@mojaloop/sdk-standard-components/-/sdk-standard-components-8.6.9.tgz", + "integrity": "sha512-2S1pmYm8rdAlgrzUhS67+vMpKWWWxYnq2p/Hd4idrWHehSz6C4b1oxF/e4wx4SxVbFkMwgdXQt4Dy4tvoswhbQ==", + "requires": { + "base64url": "^3.0.1", + "ilp-packet": "2.2.0", + "jsonwebtoken": "^8.5.1", + "jws": "^3.2.2", + "request": "^2.34", + "request-promise-native": "^1.0.7" } }, "@now-ims/hapi-now-auth": { @@ -3401,9 +1994,9 @@ "dev": true }, "@sinonjs/commons": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.7.0.tgz", - "integrity": "sha512-qbk9AP+cZUsKdW1GJsBpxPKFmCJ0T8swwzVje3qFd+AkQb74Q/tiuzrdfFg8AD2g5HH/XbE/I8Uc1KYHVYWfhg==", + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.7.1.tgz", + "integrity": "sha512-Debi3Baff1Qu1Unc3mjJ96MgpbwTn43S1+9yJ0llWygPwDNu2aaWBD6yc9y/Z8XDRNhx7U+u2UDg2OGQXkclUQ==", "requires": { "type-detect": "4.0.8" } @@ -3418,33 +2011,18 @@ } }, "@sinonjs/formatio": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-5.0.0.tgz", - "integrity": "sha512-ejFRrFNMaTAmhg9u1lYKJQxDocowta6KQKFnBE7XtZb/AAPlLkWQQSaqwlGYnDWQ6paXzyM1vbMhLAujSFiVPw==", - "dev": true, + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-4.0.1.tgz", + "integrity": "sha512-asIdlLFrla/WZybhm0C8eEzaDNNrzymiTqHMeJl6zPW2881l3uuVRpm0QlRQEjqYWv6CcKMGYME3LbrLJsORBw==", "requires": { "@sinonjs/commons": "^1", "@sinonjs/samsam": "^4.2.0" - }, - "dependencies": { - "@sinonjs/samsam": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-4.2.2.tgz", - "integrity": "sha512-z9o4LZUzSD9Hl22zV38aXNykgFeVj8acqfFabCY6FY83n/6s/XwNJyYYldz6/9lBJanpno9h+oL6HTISkviweA==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.6.0", - "lodash.get": "^4.4.2", - "type-detect": "^4.0.8" - } - } } }, "@sinonjs/samsam": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-5.0.1.tgz", - "integrity": "sha512-iSZdE68szyFvV8ReYve6t4gAA1rLVwGyyhWBg9qrz8VAn1FH141gdg0NJcMrAJ069rD2XM2KQzY8ZNDgmTfBQA==", - "dev": true, + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-4.2.2.tgz", + "integrity": "sha512-z9o4LZUzSD9Hl22zV38aXNykgFeVj8acqfFabCY6FY83n/6s/XwNJyYYldz6/9lBJanpno9h+oL6HTISkviweA==", "requires": { "@sinonjs/commons": "^1.6.0", "lodash.get": "^4.4.2", @@ -3465,6 +2043,12 @@ "defer-to-connect": "^1.0.1" } }, + "@tootallnate/once": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.0.0.tgz", + "integrity": "sha512-KYyTT/T6ALPkIRd2Ge080X/BsXvy9O0hcWTtMWkPvwAwF99+vn6Dv4GzrFT/Nn1LePr+FFDbRXXlqmsy9lw2zA==", + "dev": true + }, "@types/bytebuffer": { "version": "5.0.40", "resolved": "https://registry.npmjs.org/@types/bytebuffer/-/bytebuffer-5.0.40.tgz", @@ -3486,9 +2070,9 @@ "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==" }, "@types/node": { - "version": "10.17.15", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.15.tgz", - "integrity": "sha512-daFGV9GSs6USfPgxceDA8nlSe48XrVCJfDeYm7eokxq/ye7iuOH87hKXgMtEAVLFapkczbZsx868PMDT1Y0a6A==" + "version": "10.17.16", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.16.tgz", + "integrity": "sha512-A4283YSA1OmnIivcpy/4nN86YlnKRiQp8PYwI2KdPCONEBN093QTb0gCtERtkLyVNGKKIGazTZ2nAmVzQU51zA==" }, "@types/protobufjs": { "version": "6.0.0", @@ -3516,16 +2100,30 @@ "dev": true }, "acorn-jsx": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.1.0.tgz", - "integrity": "sha512-tMUqwBWfLFbJbizRmEcWSLw6HnFzfdJs2sOJEOwwtVPMoH/0Ay+E703oZz78VSXZiiDcZrQ5XKjPIUQixhmgVw==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.2.0.tgz", + "integrity": "sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ==", "dev": true }, "agent-base": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-5.1.1.tgz", - "integrity": "sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==", - "dev": true + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.0.tgz", + "integrity": "sha512-j1Q7cSCqN+AwrmDd+pzgqc0/NpC655x2bUf5ZjRIO77DcNBFmh+OgRNzF6OKdCC9RSCb19fGd99+bhXFdkRNqw==", + "dev": true, + "requires": { + "debug": "4" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } }, "agentkeepalive": { "version": "4.1.0", @@ -3560,9 +2158,9 @@ } }, "ajv": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.11.0.tgz", - "integrity": "sha512-nCprB/0syFYy9fVYU1ox1l2KN8S9I+tziH8D4zdZuLT3N6RMlGSGt5FSTpAiHB/Whv8Qs1cWHma1aMKZyaHRKA==", + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz", + "integrity": "sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw==", "requires": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -3791,11 +2389,16 @@ "dev": true }, "async": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz", + "integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==" + }, + "async-retry": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.1.tgz", + "integrity": "sha512-aiieFW/7h3hY0Bq5d+ktDBejxuwR78vRu9hDUdR8rNhSaQ29VzPL4AoIRG7D/c7tdenwOcKvgPM6tIxB3cB6HA==", "requires": { - "lodash": "^4.17.14" + "retry": "0.12.0" } }, "asynckit": { @@ -3862,6 +2465,14 @@ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz", "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==" }, + "axios": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz", + "integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==", + "requires": { + "follow-redirects": "1.5.10" + } + }, "babel-code-frame": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", @@ -3974,6 +2585,11 @@ "tweetnacl": "^0.14.3" } }, + "bignumber.js": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz", + "integrity": "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A==" + }, "binary-extensions": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz", @@ -4018,11 +2634,6 @@ "resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.1.4.tgz", "integrity": "sha512-QD1PhQk+s31P1ixsX0H0Suoupp3VMXzIVMSwobR3F3MSUO2YCV0B7xqLcUw/Bh8yuvd3LhpyqLQWTNcRmp6IdQ==" }, - "@hapi/bourne": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-1.3.2.tgz", - "integrity": "sha512-1dVNHT76Uu5N3eJNTYcvxee+jzX4Z9lfciqRRHCU27ihbUcYi+iSc2iml5Ke1LXe1SyJCLA0+14Jh4tXJgOppA==" - }, "@hapi/hoek": { "version": "8.5.1", "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-8.5.1.tgz", @@ -4198,19 +2809,17 @@ "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" }, "cacache": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-14.0.0.tgz", - "integrity": "sha512-+Nr/BnA/tjAUXza9gH8F+FSP+1HvWqCKt4c95dQr4EDVJVafbzmPZpLKCkLYexs6vSd2B/1TOXrAoNnqVPfvRA==", + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.0.0.tgz", + "integrity": "sha512-L0JpXHhplbJSiDGzyJJnJCTL7er7NzbBgxzVqLswEb4bO91Zbv17OUMuUeu/q0ZwKn3V+1HM4wb9tO4eVE/K8g==", "dev": true, "requires": { "chownr": "^1.1.2", - "figgy-pudding": "^3.5.1", "fs-minipass": "^2.0.0", "glob": "^7.1.4", - "graceful-fs": "^4.2.2", "infer-owner": "^1.0.4", "lru-cache": "^5.1.1", - "minipass": "^3.0.0", + "minipass": "^3.1.1", "minipass-collect": "^1.0.2", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.2", @@ -4219,11 +2828,20 @@ "p-map": "^3.0.0", "promise-inflight": "^1.0.1", "rimraf": "^2.7.1", - "ssri": "^7.0.0", - "tar": "^6.0.0", + "ssri": "^8.0.0", + "tar": "^6.0.1", "unique-filename": "^1.1.1" }, "dependencies": { + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, "mkdirp": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.3.tgz", @@ -4238,6 +2856,12 @@ "requires": { "glob": "^7.1.3" } + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true } } }, @@ -4327,9 +2951,9 @@ "dev": true }, "write-file-atomic": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.1.tgz", - "integrity": "sha512-JPStrIyyVJ6oCSz/691fAjFtefZ6q+fP6tm+OS4Qw6o+TGQxNp1ziY2PgS+X/m0V8OWhZiO/m4xSj+Pr4RrZvw==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", "dev": true, "requires": { "imurmurhash": "^0.1.4", @@ -4400,6 +3024,21 @@ "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" + }, + "dependencies": { + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } } }, "chance": { @@ -4676,10 +3315,9 @@ } }, "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "optional": true + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==" }, "commondir": { "version": "1.0.1", @@ -4776,6 +3414,14 @@ "capture-stack-trace": "^1.0.0" } }, + "cron": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/cron/-/cron-1.8.2.tgz", + "integrity": "sha512-Gk2c4y6xKEO8FSAUTklqtfSr7oTq0CiPQeLBG5Fl0qoXpZyMcj1SG59YL+hqq04bu6/IuEA7lMkYDAplQNKkyg==", + "requires": { + "moment-timezone": "^0.5.x" + } + }, "cross-spawn": { "version": "6.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", @@ -4802,6 +3448,16 @@ "assert-plus": "^1.0.0" } }, + "data-urls": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", + "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", + "requires": { + "abab": "^2.0.3", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^8.0.0" + } + }, "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", @@ -5043,6 +3699,11 @@ "@korzio/djv-draft-04": "^2.0.1" } }, + "docdash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/docdash/-/docdash-1.2.0.tgz", + "integrity": "sha512-IYZbgYthPTspgqYeciRJNPhSwL51yer7HAwDXhF5p+H7mTDbPvY3PCk/QDjNxdPCpWkaJVFC4t7iCNB/t9E5Kw==" + }, "doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -5259,14 +3920,11 @@ "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", "dev": true }, - "es6-promisify": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", - "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", - "dev": true, - "requires": { - "es6-promise": "^4.0.3" - } + "escape-goat": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz", + "integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==", + "dev": true }, "escape-string-regexp": { "version": "1.0.5", @@ -5691,6 +4349,11 @@ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true }, + "events": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.1.0.tgz", + "integrity": "sha512-Rv+u8MLHNOdMjTAFeT3nCjHn2aGlx435FP/sDHNaRhDEMwyI/aB22Kj2qIN8R0cw3z28psEQLYwxVKLsKrMgWg==" + }, "events-to-array": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/events-to-array/-/events-to-array-1.1.2.tgz", @@ -5722,22 +4385,6 @@ "shebang-command": "^1.2.0", "which": "^1.2.9" } - }, - "lru-cache": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", - "dev": true, - "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - } - }, - "yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", - "dev": true } } }, @@ -6028,13 +4675,13 @@ } }, "find-cache-dir": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.2.0.tgz", - "integrity": "sha512-1JKclkYYsf1q9WIJKLZa9S9muC+08RIjzAlLrK4QcYLJMS6mk9yombQ9qf+zJ7H9LS800k0s44L4sDq9VYzqyg==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.0.tgz", + "integrity": "sha512-PtXtQb7IrD8O+h6Cq1dbpJH5NzD8+9keN1zZ0YlpDzl1PwXEJEBj6u1Xa92t1Hwluoozd9TNKul5Hi2iqpsWwg==", "dev": true, "requires": { "commondir": "^1.0.1", - "make-dir": "^3.0.0", + "make-dir": "^3.0.2", "pkg-dir": "^4.1.0" }, "dependencies": { @@ -6315,6 +4962,12 @@ "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", "dev": true }, + "get-port": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz", + "integrity": "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==", + "dev": true + }, "get-stdin": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-7.0.0.tgz", @@ -6346,9 +4999,9 @@ } }, "glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -6468,6 +5121,435 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==" }, + "grpc": { + "version": "1.24.2", + "resolved": "https://registry.npmjs.org/grpc/-/grpc-1.24.2.tgz", + "integrity": "sha512-EG3WH6AWMVvAiV15d+lr+K77HJ/KV/3FvMpjKjulXHbTwgDZkhkcWbwhxFAoTdxTkQvy0WFcO3Nog50QBbHZWw==", + "requires": { + "@types/bytebuffer": "^5.0.40", + "lodash.camelcase": "^4.3.0", + "lodash.clone": "^4.5.0", + "nan": "^2.13.2", + "node-pre-gyp": "^0.14.0", + "protobufjs": "^5.0.3" + }, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "bundled": true + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true + }, + "aproba": { + "version": "1.2.0", + "bundled": true + }, + "are-we-there-yet": { + "version": "1.1.5", + "bundled": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "chownr": { + "version": "1.1.3", + "bundled": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true + }, + "debug": { + "version": "3.2.6", + "bundled": true, + "requires": { + "ms": "^2.1.1" + } + }, + "deep-extend": { + "version": "0.6.0", + "bundled": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true + }, + "detect-libc": { + "version": "1.0.3", + "bundled": true + }, + "fs-minipass": { + "version": "1.2.7", + "bundled": true, + "requires": { + "minipass": "^2.6.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "glob": { + "version": "7.1.4", + "bundled": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true + }, + "iconv-lite": { + "version": "0.4.24", + "bundled": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ignore-walk": { + "version": "3.0.3", + "bundled": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "bundled": true + }, + "ini": { + "version": "1.3.5", + "bundled": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "bundled": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.0", + "bundled": true + }, + "minipass": { + "version": "2.9.0", + "bundled": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.3.3", + "bundled": true, + "requires": { + "minipass": "^2.9.0" + } + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "requires": { + "minimist": "0.0.8" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "bundled": true + } + } + }, + "ms": { + "version": "2.1.2", + "bundled": true + }, + "needle": { + "version": "2.4.0", + "bundled": true, + "requires": { + "debug": "^3.2.6", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + } + }, + "node-pre-gyp": { + "version": "0.14.0", + "bundled": true, + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.1", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.2.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4.4.2" + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "npm-bundled": { + "version": "1.0.6", + "bundled": true + }, + "npm-packlist": { + "version": "1.4.6", + "bundled": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, + "npmlog": { + "version": "4.1.2", + "bundled": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "requires": { + "wrappy": "1" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true + }, + "osenv": { + "version": "0.1.5", + "bundled": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true + }, + "process-nextick-args": { + "version": "2.0.1", + "bundled": true + }, + "protobufjs": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-5.0.3.tgz", + "integrity": "sha512-55Kcx1MhPZX0zTbVosMQEO5R6/rikNXd9b6RQK4KSPcrSIIwoXTtebIczUrXlwaSrbz4x8XUVThGPob1n8I4QA==", + "requires": { + "ascli": "~1", + "bytebuffer": "~5", + "glob": "^7.0.5", + "yargs": "^3.10.0" + } + }, + "rc": { + "version": "1.2.8", + "bundled": true, + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + } + }, + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "rimraf": { + "version": "2.7.1", + "bundled": true, + "requires": { + "glob": "^7.1.3" + } + }, + "safe-buffer": { + "version": "5.1.2", + "bundled": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true + }, + "sax": { + "version": "1.2.4", + "bundled": true + }, + "semver": { + "version": "5.7.1", + "bundled": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true + }, + "tar": { + "version": "4.4.13", + "bundled": true, + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.8.6", + "minizlib": "^1.2.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.3" + } + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true + }, + "wide-align": { + "version": "1.1.3", + "bundled": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true + }, + "yallist": { + "version": "3.1.1", + "bundled": true + } + } + }, "handlebars": { "version": "4.7.3", "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.3.tgz", @@ -6532,11 +5614,6 @@ } } }, - "@hapi/bourne": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-1.3.2.tgz", - "integrity": "sha512-1dVNHT76Uu5N3eJNTYcvxee+jzX4Z9lfciqRRHCU27ihbUcYi+iSc2iml5Ke1LXe1SyJCLA0+14Jh4tXJgOppA==" - }, "@hapi/hoek": { "version": "6.2.4", "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-6.2.4.tgz", @@ -6685,9 +5762,9 @@ } }, "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" }, "has-symbols": { "version": "1.0.1", @@ -6767,9 +5844,9 @@ } }, "hosted-git-info": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.5.tgz", - "integrity": "sha512-kssjab8CvdXfcXMXVcvsXum4Hwdq9XGtRD3TteMEvEbq0LXyiNQr6AprqKqfeaDXze7SxWvRxdpwE6ku7ikLkg==" + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.7.tgz", + "integrity": "sha512-ChkjQtKJ3GI6SsI4O5jwr8q8EPrWCnxuc4Tbx+vRI5x6mDOpjKKltNo1lRlszw3xwgTOSns1ZRBiMmmwpcvLxg==" }, "html-escaper": { "version": "2.0.0", @@ -6796,12 +5873,13 @@ } }, "http-proxy-agent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-3.0.0.tgz", - "integrity": "sha512-uGuJaBWQWDQCJI5ip0d/VTYZW0nRrlLWXA4A7P1jrsa+f77rW2yXz315oBt6zGCF6l8C2tlMxY7ffULCj+5FhA==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", + "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", "dev": true, "requires": { - "agent-base": "5", + "@tootallnate/once": "1", + "agent-base": "6", "debug": "4" }, "dependencies": { @@ -6832,12 +5910,12 @@ "integrity": "sha512-mBnIohUwRw9NyXMEMMv8/GANnzEYUj0Y8d3uL01zDWFkxUjYyZ6rgCaAI2zZ1Wb34Oqtbx/nFZolPRDc8Xlm5A==" }, "https-proxy-agent": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz", - "integrity": "sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", + "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", "dev": true, "requires": { - "agent-base": "5", + "agent-base": "6", "debug": "4" }, "dependencies": { @@ -7069,6 +6147,11 @@ } } }, + "interpret": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.0.0.tgz", + "integrity": "sha512-e0/LknJ8wpMMhTiWcjivB+ESwIuvHnBSlBbmP/pSb8CQJldoj1p2qv7xGZ/+BtbTziYRFSz8OsvdbiX45LtYQA==" + }, "invert-kv": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", @@ -7528,12 +6611,6 @@ "supports-color": "^7.1.0" }, "dependencies": { - "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 - }, "make-dir": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.2.tgz", @@ -7548,15 +6625,6 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } } } }, @@ -7711,14 +6779,6 @@ "dev": true, "requires": { "minimist": "^1.2.0" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - } } }, "jsonify": { @@ -7778,9 +6838,9 @@ } }, "just-extend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.0.2.tgz", - "integrity": "sha512-FrLwOgm+iXrPV+5zDU6Jqu4gCRXbWEQg2O3SKONsWE4w7AXFRkryS53bpWdaL9cNol+AmR3AEYz6kn+o0fCPnw==" + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.1.0.tgz", + "integrity": "sha512-ApcjaOdVTJ7y4r08xI5wIqpvwS48Q0PBG4DJROcEkH1f8MdAiNFyFxz3xoL0LWAVwjrwPYZdVHHxhRHcx/uGLA==" }, "jwa": { "version": "1.4.1", @@ -7826,6 +6886,39 @@ "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", "dev": true }, + "knex": { + "version": "0.20.10", + "resolved": "https://registry.npmjs.org/knex/-/knex-0.20.10.tgz", + "integrity": "sha512-07D6fvY5NdvrfRPmkLLG+OrHvmAy55OX7eXkN8TMiOOI5lWJh1dC2zKjeEQJqUILMOsTnZCGqTKGaRm4t1E9xg==", + "requires": { + "bluebird": "^3.7.2", + "colorette": "1.1.0", + "commander": "^4.1.1", + "debug": "4.1.1", + "esm": "^3.2.25", + "getopts": "2.2.5", + "inherits": "~2.0.4", + "interpret": "^2.0.0", + "liftoff": "3.1.0", + "lodash": "^4.17.15", + "mkdirp": "^0.5.1", + "pg-connection-string": "2.1.0", + "tarn": "^2.0.0", + "tildify": "2.0.0", + "uuid": "^3.4.0", + "v8flags": "^3.1.3" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + } + } + }, "kuler": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/kuler/-/kuler-1.0.1.tgz", @@ -8020,11 +7113,12 @@ "dev": true }, "lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", "requires": { - "yallist": "^3.0.2" + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" } }, "make-dir": { @@ -8037,16 +7131,16 @@ } }, "make-fetch-happen": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-7.1.1.tgz", - "integrity": "sha512-7fNjiOXNZhNGQzG5P15nU97aZQtzPU2GVgVd7pnqnl5gnpLzMAD8bAe5YG4iW2s0PTqaZy9xGv4Wfqe872kRNQ==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-8.0.2.tgz", + "integrity": "sha512-jRqI9zjLyz8ufXfLSbEObJ6a8sv8geeKYEPFpI+b39JjYU14MZtCiJGazSWPZMjCm7161b4r57N/na5fBXpooQ==", "dev": true, "requires": { "agentkeepalive": "^4.1.0", - "cacache": "^14.0.0", - "http-cache-semantics": "^4.0.3", - "http-proxy-agent": "^3.0.0", - "https-proxy-agent": "^4.0.0", + "cacache": "^15.0.0", + "http-cache-semantics": "^4.0.4", + "http-proxy-agent": "^4.0.1", + "https-proxy-agent": "^5.0.0", "is-lambda": "^1.0.1", "lru-cache": "^5.1.1", "minipass": "^3.0.0", @@ -8055,8 +7149,25 @@ "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.2", "promise-retry": "^1.1.1", - "socks-proxy-agent": "^4.0.0", - "ssri": "^7.0.1" + "socks-proxy-agent": "^5.0.0", + "ssri": "^8.0.0" + }, + "dependencies": { + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + } } }, "make-iterator": { @@ -8190,9 +7301,9 @@ } }, "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" }, "minipass": { "version": "3.1.1", @@ -8313,6 +7424,13 @@ "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "requires": { "minimist": "0.0.8" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + } } }, "module-not-found-error": { @@ -8327,13 +7445,57 @@ "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==" }, "moment-timezone": { - "version": "0.5.27", - "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.27.tgz", - "integrity": "sha512-EIKQs7h5sAsjhPCqN6ggx6cEbs94GK050254TIJySD1bzoM5JTYDwAU1IoVOeTOL6Gm27kYJ51/uuvq1kIlrbw==", + "version": "0.5.28", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.28.tgz", + "integrity": "sha512-TDJkZvAyKIVWg5EtVqRzU97w0Rb0YVbfpqyjgu6GwXCAohVRqwZjf4fOzDE6p1Ch98Sro/8hQQi65WDXW5STPw==", "requires": { "moment": ">= 2.9.0" } }, + "mongodb": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.4.1.tgz", + "integrity": "sha512-juqt5/Z42J4DcE7tG7UdVaTKmUC6zinF4yioPfpeOSNBieWSK6qCY+0tfGQcHLKrauWPDdMZVROHJOa8q2pWsA==", + "requires": { + "bson": "^1.1.1", + "require_optional": "^1.0.1", + "safe-buffer": "^5.1.2", + "saslprep": "^1.0.0" + } + }, + "mongoose": { + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.9.2.tgz", + "integrity": "sha512-Sa1qfqBvUfAgsrXpZjbBoIx8PEDUJSKF5Ous8gnBFI7TPiueSgJjg6GRA7A0teU8AB/vd0h8rl1rD5RQNfWhIw==", + "requires": { + "bson": "~1.1.1", + "kareem": "2.3.1", + "mongodb": "3.5.3", + "mongoose-legacy-pluralize": "1.0.2", + "mpath": "0.6.0", + "mquery": "3.2.2", + "ms": "2.1.2", + "regexp-clone": "1.0.0", + "safe-buffer": "5.1.2", + "sift": "7.0.1", + "sliced": "1.0.1" + }, + "dependencies": { + "mongodb": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.5.3.tgz", + "integrity": "sha512-II7P7A3XUdPiXRgcN96qIoRa1oesM6qLNZkzfPluNZjVkgQk3jnQwOT6/uDk4USRDTTLjNFw2vwfmbRGTA7msg==", + "requires": { + "bl": "^2.2.0", + "bson": "^1.1.1", + "denque": "^1.4.1", + "require_optional": "^1.0.1", + "safe-buffer": "^5.1.2", + "saslprep": "^1.0.0" + } + } + } + }, "mongoose-legacy-pluralize": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/mongoose-legacy-pluralize/-/mongoose-legacy-pluralize-1.0.2.tgz", @@ -8393,6 +7555,17 @@ "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", "dev": true }, + "mysql": { + "version": "2.18.1", + "resolved": "https://registry.npmjs.org/mysql/-/mysql-2.18.1.tgz", + "integrity": "sha512-Bca+gk2YWmqp2Uf6k5NFEurwY/0td0cpebAucFpY/3jhrwrVGuxU2uQFCHjU19SJfje0yQvi+rVWdq78hR5lig==", + "requires": { + "bignumber.js": "9.0.0", + "readable-stream": "2.3.7", + "safe-buffer": "5.1.2", + "sqlstring": "2.3.1" + } + }, "nan": { "version": "2.14.0", "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", @@ -8439,40 +7612,16 @@ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" }, "nise": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/nise/-/nise-4.0.1.tgz", - "integrity": "sha512-10PKL272rqg80o2RsWcTT6X9cDYqJ4kXqPTf8yCXPc9hbphZSDmbiG5FqUNeR5nouKCQMM24ld45kgYnBdx2rw==", - "dev": true, + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/nise/-/nise-3.0.1.tgz", + "integrity": "sha512-fYcH9y0drBGSoi88kvhpbZEsenX58Yr+wOJ4/Mi1K4cy+iGP/a73gNoyNhu5E9QxPdgTlVChfIaAlnyOy/gHUA==", "requires": { "@sinonjs/commons": "^1.7.0", - "@sinonjs/fake-timers": "^6.0.0", "@sinonjs/formatio": "^4.0.1", "@sinonjs/text-encoding": "^0.7.1", "just-extend": "^4.0.2", + "lolex": "^5.0.1", "path-to-regexp": "^1.7.0" - }, - "dependencies": { - "@sinonjs/formatio": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-4.0.1.tgz", - "integrity": "sha512-asIdlLFrla/WZybhm0C8eEzaDNNrzymiTqHMeJl6zPW2881l3uuVRpm0QlRQEjqYWv6CcKMGYME3LbrLJsORBw==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1", - "@sinonjs/samsam": "^4.2.0" - } - }, - "@sinonjs/samsam": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-4.2.2.tgz", - "integrity": "sha512-z9o4LZUzSD9Hl22zV38aXNykgFeVj8acqfFabCY6FY83n/6s/XwNJyYYldz6/9lBJanpno9h+oL6HTISkviweA==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.6.0", - "lodash.get": "^4.4.2", - "type-detect": "^4.0.8" - } - } } }, "node-alias": { @@ -8527,6 +7676,15 @@ "process-on-spawn": "^1.0.0" } }, + "node-rdkafka": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/node-rdkafka/-/node-rdkafka-2.7.4.tgz", + "integrity": "sha512-415Hfu2SkAo9+mSj3xxw+dHwJqC0jRY0D/OKqUdPfvEE1r4p+N0OfbsAmmSR5FSxZFRFTY3j+/tGntp6ssqHWw==", + "requires": { + "bindings": "^1.3.1", + "nan": "^2.14.0" + } + }, "nodemon": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.2.tgz", @@ -8553,6 +7711,21 @@ "requires": { "ms": "^2.1.1" } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } } } }, @@ -8614,15 +7787,15 @@ } }, "npm-check-updates": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-check-updates/-/npm-check-updates-4.0.1.tgz", - "integrity": "sha512-rDrKAqhQuTYq2EkndroPMZGA9N6tpTotOVOIJoxRa3ZKnb/mOcq2TZv4A4LLSM8+9kZlP+sBwE+XAGh8wWZw/w==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/npm-check-updates/-/npm-check-updates-4.0.2.tgz", + "integrity": "sha512-EVJzwOIIdm58ug0vInAlkvkvfX603Szl614c/FFpttzexXG8KXQXj+Cf0GJ+9HEfBb8hPc8rLk4Csq9h7kL+ig==", "dev": true, "requires": { "chalk": "^3.0.0", "cint": "^8.2.1", "cli-table": "^0.3.1", - "commander": "^4.0.1", + "commander": "^4.1.1", "fast-diff": "^1.2.0", "find-up": "4.1.0", "get-stdin": "^7.0.0", @@ -8630,15 +7803,15 @@ "libnpmconfig": "^1.2.1", "lodash": "^4.17.15", "node-alias": "^1.0.4", - "pacote": "^10.2.0", + "pacote": "^11.1.0", "progress": "^2.0.3", - "prompts": "^2.3.0", + "prompts": "^2.3.1", "rc-config-loader": "^3.0.0", "requireg": "^0.2.2", - "semver": "^6.3.0", + "semver": "^7.1.3", "semver-utils": "^1.1.4", "spawn-please": "^0.3.0", - "update-notifier": "^3.0.1" + "update-notifier": "^4.1.0" }, "dependencies": { "ansi-align": { @@ -8648,6 +7821,19 @@ "dev": true, "requires": { "string-width": "^3.0.0" + }, + "dependencies": { + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + } } }, "ansi-regex": { @@ -8667,71 +7853,19 @@ } }, "boxen": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-3.2.0.tgz", - "integrity": "sha512-cU4J/+NodM3IHdSL2yN8bqYqnmlBTidDR4RC7nJs61ZmtGz8VZzM3HLQX0zY5mrSmPtR3xWwsq2jOUQqFZN8+A==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-4.2.0.tgz", + "integrity": "sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ==", "dev": true, "requires": { "ansi-align": "^3.0.0", "camelcase": "^5.3.1", - "chalk": "^2.4.2", + "chalk": "^3.0.0", "cli-boxes": "^2.2.0", - "string-width": "^3.0.0", - "term-size": "^1.2.0", - "type-fest": "^0.3.0", - "widest-line": "^2.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } + "string-width": "^4.1.0", + "term-size": "^2.1.0", + "type-fest": "^0.8.1", + "widest-line": "^3.1.0" } }, "camelcase": { @@ -8777,24 +7911,33 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "dev": true - }, "configstore": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/configstore/-/configstore-4.0.0.tgz", - "integrity": "sha512-CmquAXFBocrzaSM8mtGPMM/HiWmyIpr4CcJl/rgY2uCObZ/S7cKU0silxslqJejl+t/T9HS8E0PUNQD81JGUEQ==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", + "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", "dev": true, "requires": { - "dot-prop": "^4.1.0", + "dot-prop": "^5.2.0", "graceful-fs": "^4.1.2", - "make-dir": "^1.0.0", - "unique-string": "^1.0.0", - "write-file-atomic": "^2.0.0", - "xdg-basedir": "^3.0.0" + "make-dir": "^3.0.0", + "unique-string": "^2.0.0", + "write-file-atomic": "^3.0.0", + "xdg-basedir": "^4.0.0" + } + }, + "crypto-random-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", + "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", + "dev": true + }, + "dot-prop": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz", + "integrity": "sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A==", + "dev": true, + "requires": { + "is-obj": "^2.0.0" } }, "emoji-regex": { @@ -8822,6 +7965,15 @@ "pump": "^3.0.0" } }, + "global-dirs": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-2.0.1.tgz", + "integrity": "sha512-5HqUqdhkEovj2Of/ms3IeS/EekcO54ytHRLV4PEY2rhRwrHXLQjeVEES0Lhka0xwNDtGYn58wyC4s5+MHsOO6A==", + "dev": true, + "requires": { + "ini": "^1.3.5" + } + }, "got": { "version": "9.6.0", "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", @@ -8841,12 +7993,6 @@ "url-parse-lax": "^3.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 - }, "is-ci": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", @@ -8862,10 +8008,32 @@ "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", "dev": true }, + "is-installed-globally": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.3.1.tgz", + "integrity": "sha512-oiEcGoQbGc+3/iijAijrK2qFpkNoNjsHOm/5V5iaeydyrS/hnwaRCEgH5cpW0P3T1lSjV5piB7S5b5lEugNLhg==", + "dev": true, + "requires": { + "global-dirs": "^2.0.1", + "is-path-inside": "^3.0.1" + } + }, "is-npm": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-3.0.0.tgz", - "integrity": "sha512-wsigDr1Kkschp2opC4G3yA6r9EgVA6NjRpWzIi9axXqeIaAATPRJc4uLujXe3Nd9uO8KoDyA4MD6aZSeXTADhA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-4.0.0.tgz", + "integrity": "sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig==", + "dev": true + }, + "is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true + }, + "is-path-inside": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.2.tgz", + "integrity": "sha512-/2UGPSgmtqwo1ktx8NDHjuPwZWmHhO+gj0f93EkhLB5RgW9RZevWYYlIkS6zePc6U2WpOdQYIwHe9YC4DWEBVg==", "dev": true }, "latest-version": { @@ -8886,6 +8054,23 @@ "p-locate": "^4.1.0" } }, + "make-dir": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.2.tgz", + "integrity": "sha512-rYKABKutXa6vXTXhoV18cBE7PaewPXHe/Bdq4v+ZLMhxbWApkFFplT0LcbMW+6BbjnQXzZ/sAvSE/JdguApG5w==", + "dev": true, + "requires": { + "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, "p-locate": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", @@ -8905,6 +8090,14 @@ "registry-auth-token": "^4.0.0", "registry-url": "^5.0.0", "semver": "^6.2.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } } }, "path-exists": { @@ -8948,20 +8141,66 @@ } }, "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.1.3.tgz", + "integrity": "sha512-ekM0zfiA9SCBlsKa2X1hxyxiI4L3B6EbVJkkdgQXnSEEaHlGdvyodMruTiulSRWMMB4NeIuYNMC9rTKTz97GxA==", "dev": true }, + "semver-diff": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz", + "integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==", + "dev": true, + "requires": { + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", "dev": true, "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + } } }, "strip-ansi": { @@ -8973,85 +8212,46 @@ "ansi-regex": "^4.1.0" } }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "term-size": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.0.tgz", + "integrity": "sha512-a6sumDlzyHVJWb8+YofY4TW112G6p2FCPEAFk+59gIYHv3XHRhm9ltVQ9kli4hNWeQBwSpe8cRN25x0ROunMOw==", + "dev": true + }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + }, + "unique-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", + "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", "dev": true, "requires": { - "has-flag": "^4.0.0" + "crypto-random-string": "^2.0.0" } }, "update-notifier": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-3.0.1.tgz", - "integrity": "sha512-grrmrB6Zb8DUiyDIaeRTBCkgISYUgETNe7NglEbVsrLWXeESnlCSP50WfRSj/GmzMPl6Uchj24S/p80nP/ZQrQ==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-4.1.0.tgz", + "integrity": "sha512-w3doE1qtI0/ZmgeoDoARmI5fjDoT93IfKgEGqm26dGUOh8oNpaSTsGNdYRN/SjOuo10jcJGwkEL3mroKzktkew==", "dev": true, "requires": { - "boxen": "^3.0.0", - "chalk": "^2.0.1", - "configstore": "^4.0.0", + "boxen": "^4.2.0", + "chalk": "^3.0.0", + "configstore": "^5.0.1", "has-yarn": "^2.1.0", "import-lazy": "^2.1.0", "is-ci": "^2.0.0", - "is-installed-globally": "^0.1.0", - "is-npm": "^3.0.0", + "is-installed-globally": "^0.3.1", + "is-npm": "^4.0.0", "is-yarn-global": "^0.3.0", "latest-version": "^5.0.0", - "semver-diff": "^2.0.0", - "xdg-basedir": "^3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } + "pupa": "^2.0.1", + "semver-diff": "^3.1.1", + "xdg-basedir": "^4.0.0" } }, "url-parse-lax": { @@ -9062,6 +8262,50 @@ "requires": { "prepend-http": "^2.0.0" } + }, + "widest-line": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", + "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", + "dev": true, + "requires": { + "string-width": "^4.0.0" + } + }, + "write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "xdg-basedir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", + "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", + "dev": true + } + } + }, + "npm-install-checks": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-4.0.0.tgz", + "integrity": "sha512-09OmyDkNLYwqKPOnbI8exiOZU2GVVmQp7tgez2BPi5OZC8M82elDAps7sxC4l//uSUtotWqoEIDwjRvWH4qz8w==", + "dev": true, + "requires": { + "semver": "^7.1.1" + }, + "dependencies": { + "semver": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.1.3.tgz", + "integrity": "sha512-ekM0zfiA9SCBlsKa2X1hxyxiI4L3B6EbVJkkdgQXnSEEaHlGdvyodMruTiulSRWMMB4NeIuYNMC9rTKTz97GxA==", + "dev": true } } }, @@ -9084,19 +8328,34 @@ }, "dependencies": { "hosted-git-info": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-3.0.2.tgz", - "integrity": "sha512-ezZMWtHXm7Eb7Rq4Mwnx2vs79WUx2QmRg3+ZqeGroKzfDO+EprOcgRPYghsOP9JuYBfK18VojmRTGCg8Ma+ktw==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-3.0.4.tgz", + "integrity": "sha512-4oT62d2jwSDBbLLFLZE+1vPuQ1h8p9wjrJ8Mqx5TjsyWmBMV5B13eJqn8pvluqubLf3cJPTfiYCIwNwDNmzScQ==", "dev": true, "requires": { "lru-cache": "^5.1.1" } }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, "semver": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.1.3.tgz", "integrity": "sha512-ekM0zfiA9SCBlsKa2X1hxyxiI4L3B6EbVJkkdgQXnSEEaHlGdvyodMruTiulSRWMMB4NeIuYNMC9rTKTz97GxA==", "dev": true + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true } } }, @@ -9110,31 +8369,15 @@ "ignore-walk": "^3.0.3", "npm-bundled": "^1.0.1", "npm-normalize-package-bin": "^1.0.1" - }, - "dependencies": { - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - } } }, "npm-pick-manifest": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-5.0.0.tgz", - "integrity": "sha512-YUW9xObM7Y1OkQ/gSmU5VQyI3vCkG5lwOrdycw0dpj9/3dE8h9CKY8tVyHTIp50+mV8jOAGH4m4Lts7zz2rN4Q==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-6.0.0.tgz", + "integrity": "sha512-PdJpXMvjqt4nftNEDpCgjBUF8yI3Q3MyuAmVB9nemnnCg32F4BPL/JFBfdj8DubgHCYUFQhtLWmBPvdsFtjWMg==", "dev": true, "requires": { - "figgy-pudding": "^3.5.1", + "npm-install-checks": "^4.0.0", "npm-package-arg": "^8.0.0", "semver": "^7.0.0" }, @@ -9148,34 +8391,34 @@ } }, "npm-registry-fetch": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-6.0.2.tgz", - "integrity": "sha512-fffWUYR6J5u11URMuCSOsrr35YO3lNa41ckzIj1XPaznsRTFemIcLCU59A347xQcliUFSB2CJJeQVy5OiIVBcg==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-8.0.0.tgz", + "integrity": "sha512-975WwLvZjX97y9UWWQ8nAyr7bw02s9xKPHqvEm5T900LQsB1HXb8Gb9ebYtCBLSX+K8gSOrO5KS/9yV/naLZmQ==", "dev": true, "requires": { "@npmcli/ci-detect": "^1.0.0", - "figgy-pudding": "^3.4.1", "lru-cache": "^5.1.1", - "make-fetch-happen": "^7.1.0", + "make-fetch-happen": "^8.0.2", "minipass": "^3.0.0", "minipass-fetch": "^1.1.2", "minipass-json-stream": "^1.0.1", "minizlib": "^2.0.0", - "npm-package-arg": "^8.0.0", - "safe-buffer": "^5.2.0", - "semver": "^7.0.0" + "npm-package-arg": "^8.0.0" }, "dependencies": { - "safe-buffer": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", - "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==", - "dev": true + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } }, - "semver": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.1.3.tgz", - "integrity": "sha512-ekM0zfiA9SCBlsKa2X1hxyxiI4L3B6EbVJkkdgQXnSEEaHlGdvyodMruTiulSRWMMB4NeIuYNMC9rTKTz97GxA==", + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true } } @@ -9310,20 +8553,6 @@ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true }, - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, "is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -9637,6 +8866,13 @@ "requires": { "minimist": "~0.0.1", "wordwrap": "~0.0.2" + }, + "dependencies": { + "minimist": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", + "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=" + } } }, "optionator": { @@ -9776,14 +9012,14 @@ } }, "pacote": { - "version": "10.3.2", - "resolved": "https://registry.npmjs.org/pacote/-/pacote-10.3.2.tgz", - "integrity": "sha512-Hem2RkLAHhNaJSbhjouhbCAXlinNsv9W75s6JNxv9GypIjFkHtxCBoV6+GYBPttVOpZqnTAHmYRLs8yc2X2Dnw==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-11.1.0.tgz", + "integrity": "sha512-JcMmHiK6h6rcncj2HLayiyJZg28iJXJafXcmEGw2NjKH3WE8ZgSwyMZs7+f+aliPD57PDhB31IEgUtLXp0YZxA==", "dev": true, "requires": { "@npmcli/installed-package-contents": "^1.0.5", - "cacache": "^14.0.0", - "chownr": "^1.1.3", + "cacache": "^15.0.0", + "chownr": "^1.1.4", "fs-minipass": "^2.1.0", "infer-owner": "^1.0.4", "lru-cache": "^5.1.1", @@ -9791,19 +9027,28 @@ "minipass-fetch": "^1.2.1", "mkdirp": "^1.0.3", "npm-package-arg": "^8.0.0", - "npm-packlist": "^2.0.3", - "npm-pick-manifest": "^5.0.0", - "npm-registry-fetch": "^6.0.0", + "npm-packlist": "^2.1.0", + "npm-pick-manifest": "^6.0.0", + "npm-registry-fetch": "^8.0.0", "osenv": "^0.1.5", "promise-inflight": "^1.0.1", "promise-retry": "^1.1.1", "read-package-json-fast": "^1.1.3", - "semver": "^7.1.1", - "ssri": "^7.1.0", - "tar": "^6.0.0", + "semver": "^7.1.3", + "ssri": "^8.0.0", + "tar": "^6.0.1", "which": "^2.0.2" }, "dependencies": { + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, "mkdirp": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.3.tgz", @@ -9824,6 +9069,12 @@ "requires": { "isexe": "^2.0.0" } + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true } } }, @@ -10104,16 +9355,6 @@ "which": "^1.2.9" } }, - "lru-cache": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", - "dev": true, - "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - } - }, "which": { "version": "1.2.14", "resolved": "https://registry.npmjs.org/which/-/which-1.2.14.tgz", @@ -10122,12 +9363,6 @@ "requires": { "isexe": "^2.0.0" } - }, - "yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", - "dev": true } } }, @@ -10163,6 +9398,14 @@ "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true }, + "prom-client": { + "version": "11.5.3", + "resolved": "https://registry.npmjs.org/prom-client/-/prom-client-11.5.3.tgz", + "integrity": "sha512-iz22FmTbtkyL2vt0MdDFY+kWof+S9UB/NACxSn2aJcewtw+EERsen0urSkZ2WrHseNdydsvcxCTAnPcSMZZv4Q==", + "requires": { + "tdigest": "^0.1.1" + } + }, "promise-inflight": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", @@ -10177,6 +9420,14 @@ "requires": { "err-code": "^1.0.0", "retry": "^0.10.0" + }, + "dependencies": { + "retry": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.10.1.tgz", + "integrity": "sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q=", + "dev": true + } } }, "prompts": { @@ -10278,6 +9529,15 @@ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, + "pupa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pupa/-/pupa-2.0.1.tgz", + "integrity": "sha512-hEJH0s8PXLY/cdXh66tNEQGndDrIKNqNC5xmrysZy3i5C3oEoLna7YAOad+7u125+zH1HNXUmGEkrhb3c2VriA==", + "dev": true, + "requires": { + "escape-goat": "^2.0.0" + } + }, "qs": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", @@ -10326,13 +9586,6 @@ "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" - } } }, "rc-config-loader": { @@ -10359,9 +9612,9 @@ } }, "react-is": { - "version": "16.12.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.12.0.tgz", - "integrity": "sha512-rPCkf/mWBtKc97aLL9/txD8DZdemK0vkA3JMLShjlJB3Pj3s+lpf1KaBzMfQrAmhMQB0n1cU/SUGgKKBCe837Q==", + "version": "16.13.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.0.tgz", + "integrity": "sha512-GFMtL0vHkiBv9HluwNZTggSn/sCyEt9n02aM0dSAjGGyqyNlAyftYm4phPxdvCigG15JreC5biwxCgTAJZ7yAA==", "dev": true }, "read": { @@ -10789,10 +10042,9 @@ "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==" }, "retry": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.10.1.tgz", - "integrity": "sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q=", - "dev": true + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=" }, "rewire": { "version": "4.0.1", @@ -11045,16 +10297,6 @@ "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", "dev": true }, - "lru-cache": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", - "dev": true, - "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - } - }, "mimic-fn": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", @@ -11142,12 +10384,6 @@ "requires": { "mkdirp": "^0.5.1" } - }, - "yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", - "dev": true } } }, @@ -11161,9 +10397,9 @@ } }, "run-async": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", - "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.0.tgz", + "integrity": "sha512-xJTbh/d7Lm7SBhc1tNvTpeCHaEzoyxPrqNlvSdMfBTYwaY++UJFyXUOxAtsRUXjlqOfj8luNaR9vjCh4KeV+pg==", "dev": true, "requires": { "is-promise": "^2.1.0" @@ -11347,19 +10583,40 @@ "supports-color": "^7.1.0" }, "dependencies": { - "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 + "@sinonjs/formatio": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-5.0.1.tgz", + "integrity": "sha512-KaiQ5pBf1MpS09MuA0kp6KBQt2JUOQycqVG1NZXvzeaXe5LGFqAKueIS0bw4w0P9r7KuBSVdUk5QjXsUdu2CxQ==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1", + "@sinonjs/samsam": "^5.0.2" + } }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "@sinonjs/samsam": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-5.0.2.tgz", + "integrity": "sha512-p3yrEVB5F/1wI+835n+X8llOGRgV8+jw5BHQ/cJoLBUXXZ5U8Tr5ApwPc4L4av/vjla48kVPoN0t6dykQm+Rvg==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.6.0", + "@sinonjs/formatio": "^5.0.0", + "lodash.get": "^4.4.2", + "type-detect": "^4.0.8" + } + }, + "nise": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/nise/-/nise-4.0.2.tgz", + "integrity": "sha512-ALDnm0pTTyeGdbg5FCpWGd58Nmp3qO8d8x+dU2Fw8lApeJTEBSjkBZZM4S8t6GpKh+czxkfM/TKxpRMroZzwOg==", "dev": true, "requires": { - "has-flag": "^4.0.0" + "@sinonjs/commons": "^1.7.0", + "@sinonjs/fake-timers": "^6.0.0", + "@sinonjs/formatio": "^5.0.1", + "@sinonjs/text-encoding": "^0.7.1", + "just-extend": "^4.0.2", + "path-to-regexp": "^1.7.0" } } } @@ -11521,22 +10778,23 @@ } }, "socks-proxy-agent": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-4.0.2.tgz", - "integrity": "sha512-NT6syHhI9LmuEMSK6Kd2V7gNv5KFZoLE7V5udWmn0de+3Mkj3UMA/AJPLyeNUVmElCurSHtUdM3ETpR3z770Wg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-5.0.0.tgz", + "integrity": "sha512-lEpa1zsWCChxiynk+lCycKuC502RxDWLKJZoIhnxrWNjLSDGYRFflHA1/228VkRcnv9TIb8w98derGbpKxJRgA==", "dev": true, "requires": { - "agent-base": "~4.2.1", - "socks": "~2.3.2" + "agent-base": "6", + "debug": "4", + "socks": "^2.3.3" }, "dependencies": { - "agent-base": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz", - "integrity": "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==", + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "dev": true, "requires": { - "es6-promisify": "^5.0.0" + "ms": "^2.1.1" } } } @@ -11717,12 +10975,11 @@ } }, "ssri": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-7.1.0.tgz", - "integrity": "sha512-77/WrDZUWocK0mvA5NTRQyveUf+wsrIc6vyrxpS8tVvYBcX215QbafrJR3KtkpskIzoFLqqNuuYQvxaMjXJ/0g==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.0.tgz", + "integrity": "sha512-aq/pz989nxVYwn16Tsbj1TqFpD5LLrQxHf5zaHuieFV+R0Bbr4y8qUsOA45hXT/N4/9UNXTarBjnjVmjSOVaAA==", "dev": true, "requires": { - "figgy-pudding": "^3.5.1", "minipass": "^3.1.1" } }, @@ -11967,14 +11224,6 @@ "get-stdin": "^7.0.0", "minimist": "^1.1.0", "pkg-conf": "^3.1.0" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - } } }, "static-extend": { @@ -12092,11 +11341,11 @@ "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" }, "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", "requires": { - "has-flag": "^3.0.0" + "has-flag": "^4.0.0" } }, "swagger-methods": { @@ -12316,12 +11565,6 @@ "xtend": "~4.0.0" }, "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - }, "tap-parser": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/tap-parser/-/tap-parser-1.2.2.tgz", @@ -12395,26 +11638,6 @@ "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", "dev": true }, - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - }, "resolve": { "version": "1.14.2", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.14.2.tgz", @@ -12672,6 +11895,14 @@ "punycode": "^2.1.1" } }, + "tr46": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.0.2.tgz", + "integrity": "sha512-3n1qG+/5kg+jrbTzwAykB5yRYtQCTqOGKq5U5PE3b0a1/mzo6snDhjGS0zJVJunO0NrT3Dg1MLy5TjWP/UJppg==", + "requires": { + "punycode": "^2.1.1" + } + }, "traceparent": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/traceparent/-/traceparent-1.0.0.tgz", @@ -12747,6 +11978,12 @@ "source-map": "~0.6.1" }, "dependencies": { + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "optional": true + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -12993,11 +12230,26 @@ "defaults": "^1.0.3" } }, + "webidl-conversions": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", + "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==" + }, "whatwg-mimetype": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==" }, + "whatwg-url": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.0.0.tgz", + "integrity": "sha512-41ou2Dugpij8/LPO5Pq64K5q++MnRCBpEHvQr26/mArEKTkCV5aoXIqyhuYtE0pkqScXwhf2JP57rkRTYM29lQ==", + "requires": { + "lodash.sortby": "^4.7.0", + "tr46": "^2.0.0", + "webidl-conversions": "^5.0.0" + } + }, "which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", @@ -13075,6 +12327,14 @@ "winston-transport": "^4.3.0" }, "dependencies": { + "async": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", + "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "requires": { + "lodash": "^4.17.14" + } + }, "readable-stream": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", @@ -13179,9 +12439,9 @@ "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=" }, "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" }, "yargs": { "version": "3.32.0", diff --git a/package.json b/package.json index cf1b33aa..8a1b1a34 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "central-settlement", "description": "Central settlements hosted by a scheme to record and make settlements", - "version": "9.2.1", + "version": "9.2.2", "license": "Apache-2.0", "private": false, "author": "ModusBox", @@ -28,15 +28,17 @@ "@hapi/hapi": "18.4.0", "@hapi/inert": "5.2.2", "@hapi/vision": "5.5.4", - "@mojaloop/central-ledger": "9.2.0", - "@mojaloop/central-services-database": "9.1.0", + "@mojaloop/central-ledger": "9.2.2", + "@mojaloop/central-services-database": "9.2.0", "@mojaloop/central-services-error-handling": "9.1.0", "@mojaloop/central-services-health": "9.2.0", "@mojaloop/central-services-logger": "9.1.0", - "@mojaloop/central-services-shared": "9.1.4", - "@mojaloop/central-services-stream": "9.1.0", + "@mojaloop/central-services-shared": "9.2.0", + "@mojaloop/central-services-stream": "9.2.0", "@mojaloop/ml-number": "8.2.0", "@now-ims/hapi-now-auth": "2.0.1", + "async": "3.2.0", + "async-retry": "1.3.1", "blipp": "4.0.1", "hapi-auth-bearer-token": "6.2.1", "hapi-openapi": "1.2.6", @@ -51,10 +53,11 @@ "bluebird": "3.7.2", "eslint": "6.8.0", "faucet": "0.0.1", + "get-port": "5.1.1", "node-fetch": "2.6.0", "nodemon": "2.0.2", "npm-audit-resolver": "2.2.0", - "npm-check-updates": "4.0.1", + "npm-check-updates": "4.0.2", "nyc": "15.0.0", "pre-commit": "1.2.2", "proxyquire": "2.1.3", @@ -72,18 +75,20 @@ "test" ], "scripts": { - "start": "node src/server.js", - "dev": "nodemon src/server.js", + "start": "run-p start:api", + "start:api": "node src/api/index.js", + "watch:api": "nodemon src/api/index.js", + "regenerate": "yo swaggerize:test --framework hapi --apiPath './src/interface/swagger.yaml'", "standard": "standard", + "lint": "eslint .", "test": "npm run test:unit | faucet", + "test:all": "run-s test", "test:unit": "tape 'test/unit/**/*.js'", "test:xunit": "tape 'test/unit/**/*.js' | tap-xunit", - "lint": "eslint .", - "test:coverage-check": "npm run test:coverage && nyc check-coverage", "test:coverage": "nyc --reporter=lcov --reporter=text-summary tapes -- 'test/unit/**/**.test.js'", + "test:coverage-check": "npm run test:coverage && nyc check-coverage", "test:int": "tape 'test/integration/**/*.test.js'", "test:integration": "sh ./test/integration-runner.sh ./test/integration-runner.env", - "regenerate": "yo swaggerize:test --framework hapi --apiPath './src/interface/swagger.json'", "audit:resolve": "SHELL=sh resolve-audit", "audit:check": "SHELL=sh check-audit", "dep:check": "npx ncu -e 2", diff --git a/src/handlers/health.js b/src/api/handlers/health.js similarity index 84% rename from src/handlers/health.js rename to src/api/handlers/health.js index d8283626..7e231ff3 100644 --- a/src/handlers/health.js +++ b/src/api/handlers/health.js @@ -22,26 +22,27 @@ * Gates Foundation - Name Surname + * ModusBox + - Georgi Georgiev + * VesselsTech - Lewis Daly - -------------- ******/ - 'use strict' -const Logger = require('@mojaloop/central-services-logger') const HealthCheck = require('@mojaloop/central-services-shared').HealthCheck.HealthCheck const { defaultHealthHandler } = require('@mojaloop/central-services-health') -const Path = require('path') - -const packageJson = require('../../package.json') -const { getSubServiceHealthDatastore } = require('./lib/healthCheck/subServiceHealth') -Logger.info('path ', Path.basename(__filename)) +const packageJson = require('../../../package.json') +const { + getSubServiceHealthDatastore, + getSubServiceHealthBroker +} = require('../../lib/healthCheck/subServiceHealth') const healthCheck = new HealthCheck(packageJson, [ - getSubServiceHealthDatastore + getSubServiceHealthDatastore, + getSubServiceHealthBroker ]) module.exports = { diff --git a/src/handlers/settlementWindows.js b/src/api/handlers/settlementWindows.js similarity index 84% rename from src/handlers/settlementWindows.js rename to src/api/handlers/settlementWindows.js index bbf54ed4..a2d2e762 100644 --- a/src/handlers/settlementWindows.js +++ b/src/api/handlers/settlementWindows.js @@ -22,20 +22,18 @@ * Gates Foundation - Name Surname - * Valentin Genev - * Deon Botha - * Rajiv Mothilal - * Miguel de Barros + * ModusBox + - Deon Botha + - Georgi Georgiev + - Miguel de Barros + - Rajiv Mothilal + - Valentin Genev -------------- ******/ - 'use strict' const ErrorHandler = require('@mojaloop/central-services-error-handling') -const Logger = require('@mojaloop/central-services-logger') -const Path = require('path') -const settlementWindows = require('./../domain/settlementWindow') -Logger.info('path ', Path.basename(__filename)) +const settlementWindows = require('../../domain/settlementWindow/index') /** * Operations on /settlementWindows diff --git a/src/handlers/settlementWindows/{id}.js b/src/api/handlers/settlementWindows/{id}.js similarity index 87% rename from src/handlers/settlementWindows/{id}.js rename to src/api/handlers/settlementWindows/{id}.js index cb6f0ec0..f4409a6e 100644 --- a/src/handlers/settlementWindows/{id}.js +++ b/src/api/handlers/settlementWindows/{id}.js @@ -22,17 +22,16 @@ * Gates Foundation - Name Surname - * Valentin Genev - * Deon Botha - * Rajiv Mothilal - * Miguel de Barros - + * ModusBox + - Deon Botha + - Miguel de Barros + - Rajiv Mothilal + - Valentin Genev -------------- ******/ - 'use strict' -const settlementWindow = require('../../domain/settlementWindow/index') +const settlementWindow = require('../../../domain/settlementWindow/index') const ErrorHandler = require('@mojaloop/central-services-error-handling') /** @@ -65,11 +64,11 @@ module.exports = { * responses: 200, 400, 401, 404, 415, default */ post: async function closeSettlementWindow (request) { - const { state, reason } = request.payload + const { reason } = request.payload const settlementWindowId = request.params.id try { const Enums = await request.server.methods.enums('settlementWindowStates') - return await settlementWindow.close({ settlementWindowId, state, reason }, Enums) + return await settlementWindow.process({ settlementWindowId, reason }, Enums) } catch (err) { request.server.log('error', err) return ErrorHandler.Factory.reformatFSPIOPError(err) diff --git a/src/handlers/settlements.js b/src/api/handlers/settlements.js similarity index 84% rename from src/handlers/settlements.js rename to src/api/handlers/settlements.js index f0e2d6f7..5614f5a2 100644 --- a/src/handlers/settlements.js +++ b/src/api/handlers/settlements.js @@ -22,23 +22,18 @@ * Gates Foundation - Name Surname - * Georgi Georgiev - * Valentin Genev - * Deon Botha - * Rajiv Mothilal - * Miguel de Barros - + * ModusBox + - Deon Botha + - Georgi Georgiev + - Miguel de Barros + - Rajiv Mothilal + - Valentin Genev -------------- ******/ - 'use strict' const ErrorHandler = require('@mojaloop/central-services-error-handling') -const Logger = require('@mojaloop/central-services-logger') -const Path = require('path') -const Settlements = require('./../domain/settlement') - -Logger.info('path ', Path.basename(__filename)) +const Settlements = require('../../domain/settlement/index') /** * Operations on /settlements @@ -71,11 +66,14 @@ module.exports = { post: async function createSettlementEvent (request, h) { try { const Enums = { + ledgerEntryTypes: await request.server.methods.enums('ledgerEntryTypes'), + settlementDelay: await request.server.methods.enums('settlementDelay'), + settlementGranularity: await request.server.methods.enums('settlementGranularity'), + settlementInterchange: await request.server.methods.enums('settlementInterchange'), settlementStates: await request.server.methods.enums('settlementStates'), settlementWindowStates: await request.server.methods.enums('settlementWindowStates'), - transferStates: await request.server.methods.enums('transferStates'), transferParticipantRoleTypes: await request.server.methods.enums('transferParticipantRoleTypes'), - ledgerEntryTypes: await request.server.methods.enums('ledgerEntryTypes') + transferStates: await request.server.methods.enums('transferStates') } const settlementResult = await Settlements.settlementEventTrigger(request.payload, Enums) return h.response(settlementResult) diff --git a/src/handlers/settlements/{id}.js b/src/api/handlers/settlements/{id}.js similarity index 90% rename from src/handlers/settlements/{id}.js rename to src/api/handlers/settlements/{id}.js index e6859b12..881266ff 100644 --- a/src/handlers/settlements/{id}.js +++ b/src/api/handlers/settlements/{id}.js @@ -22,24 +22,18 @@ * Gates Foundation - Name Surname - * Georgi Georgiev - * Valentin Genev - * Deon Botha - * Rajiv Mothilal - * Miguel de Barros - + * ModusBox + - Deon Botha + - Georgi Georgiev + - Miguel de Barros + - Rajiv Mothilal + - Valentin Genev -------------- ******/ - 'use strict' const ErrorHandler = require('@mojaloop/central-services-error-handling') - -const Settlements = require('../../domain/settlement') -const Logger = require('@mojaloop/central-services-logger') -const Path = require('path') - -Logger.info('path ', Path.basename(__filename)) +const Settlements = require('../../../domain/settlement/index') /** * Operations on /settlements/{id} diff --git a/src/handlers/settlements/{settlementId}/participants/{participantId}.js b/src/api/handlers/settlements/{sid}/participants/{pid}.js similarity index 86% rename from src/handlers/settlements/{settlementId}/participants/{participantId}.js rename to src/api/handlers/settlements/{sid}/participants/{pid}.js index 9b737e4b..fedcf426 100644 --- a/src/handlers/settlements/{settlementId}/participants/{participantId}.js +++ b/src/api/handlers/settlements/{sid}/participants/{pid}.js @@ -22,23 +22,18 @@ * Gates Foundation - Name Surname - * Georgi Georgiev - * Valentin Genev - * Deon Botha - * Rajiv Mothilal - * Miguel de Barros - + * ModusBox + - Deon Botha + - Georgi Georgiev + - Miguel de Barros + - Rajiv Mothilal + - Valentin Genev -------------- ******/ - 'use strict' const ErrorHandler = require('@mojaloop/central-services-error-handling') -const Path = require('path') -const Logger = require('@mojaloop/central-services-logger') -const Settlements = require('../../../../domain/settlement') - -Logger.info('path ', Path.basename(__filename)) +const Settlements = require('../../../../../domain/settlement/index') /** * Operations on /settlements/{settlementId}/participants/{participantId} @@ -58,7 +53,8 @@ module.exports = { settlementWindowStates: await request.server.methods.enums('settlementWindowStates'), ledgerAccountTypes: await request.server.methods.enums('ledgerAccountTypes') } - const { settlementId, participantId } = request.params + const settlementId = request.params.sid + const participantId = request.params.pid const result = await Settlements.getByIdParticipantAccount({ settlementId, participantId }, Enums) return h.response(result) } catch (err) { @@ -75,8 +71,8 @@ module.exports = { * responses: 200, 400, 401, 404, 415, default */ put: async function updateSettlementById (request) { - const settlementId = request.params.settlementId - const participantId = request.params.participantId + const settlementId = request.params.sid + const participantId = request.params.pid try { const p = request.payload const universalPayload = { diff --git a/src/handlers/settlements/{settlementId}/participants/{participantId}/accounts/{accountId}.js b/src/api/handlers/settlements/{sid}/participants/{pid}/accounts/{aid}.js similarity index 87% rename from src/handlers/settlements/{settlementId}/participants/{participantId}/accounts/{accountId}.js rename to src/api/handlers/settlements/{sid}/participants/{pid}/accounts/{aid}.js index c5186430..090dafdf 100644 --- a/src/handlers/settlements/{settlementId}/participants/{participantId}/accounts/{accountId}.js +++ b/src/api/handlers/settlements/{sid}/participants/{pid}/accounts/{aid}.js @@ -16,18 +16,18 @@ their names indented and be marked with a '-'. Email address can be added optionally within square brackets . * Gates Foundation -- Name Surname + - Name Surname - * Georgi Georgiev - * Valentin Genev - * Deon Botha + * ModusBox + - Deon Botha + - Georgi Georgiev + - Valentin Genev -------------- ******/ - 'use strict' const ErrorHandler = require('@mojaloop/central-services-error-handling') -const Settlements = require('../../../../../../domain/settlement') +const Settlements = require('../../../../../../../domain/settlement/index') /** * Operations on /settlements/{settlementId}/participants/{participantId}/accounts/{accountId} @@ -47,7 +47,9 @@ module.exports = { settlementWindowStates: await request.server.methods.enums('settlementWindowStates'), ledgerAccountTypes: await request.server.methods.enums('ledgerAccountTypes') } - const { settlementId, participantId, accountId } = request.params + const settlementId = request.params.sid + const participantId = request.params.pid + const accountId = request.params.aid const result = await Settlements.getByIdParticipantAccount({ settlementId, participantId, accountId }, Enums) return h.response(result) } catch (err) { @@ -64,9 +66,9 @@ module.exports = { * responses: 200, 400, 401, 404, 415, default */ put: async function updateSettlementById (request) { - const settlementId = request.params.settlementId - const participantId = request.params.participantId - const accountId = request.params.accountId + const settlementId = request.params.sid + const participantId = request.params.pid + const accountId = request.params.aid try { const accounts = [Object.assign({}, request.payload, { id: accountId })] const universalPayload = { diff --git a/src/api/index.js b/src/api/index.js new file mode 100644 index 00000000..118dd9a1 --- /dev/null +++ b/src/api/index.js @@ -0,0 +1,40 @@ +/***** + License + -------------- + Copyright © 2017 Bill & Melinda Gates Foundation + The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + + Contributors + -------------- + This is the official list of the Mojaloop project contributors for this file. + Names of the original copyright holders (individuals or organizations) + should be listed with a '*' in the first column. People who have + contributed from an organization can be listed under the organization + that actually holds the copyright for their contributions (see the + Gates Foundation organization for an example). Those individuals should have + their names indented and be marked with a '-'. Email address can be added + optionally within square brackets . + + * Gates Foundation + - Name Surname + + * ModusBox + - Georgi Georgiev + -------------- + ******/ +'use strict' + +const Setup = require('../shared/setup') +const Config = require('../../src/lib/config') +const Routes = require('./routes') + +module.exports = Setup.initialize({ + service: 'api', + port: Config.PORT, + runHandlers: !Config.HANDLERS_DISABLED, + modules: [Routes] +}) diff --git a/src/utils/cloneDeep.js b/src/api/routes.js similarity index 71% rename from src/utils/cloneDeep.js rename to src/api/routes.js index af4063bf..72092860 100644 --- a/src/utils/cloneDeep.js +++ b/src/api/routes.js @@ -3,8 +3,11 @@ -------------- Copyright © 2017 Bill & Melinda Gates Foundation The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + Contributors -------------- This is the official list of the Mojaloop project contributors for this file. @@ -15,31 +18,23 @@ Gates Foundation organization for an example). Those individuals should have their names indented and be marked with a '-'. Email address can be added optionally within square brackets . + * Gates Foundation - Name Surname - * Georgi Georgiev + * ModusBox + - Georgi Georgiev -------------- ******/ +'use strict' -const cloneDeep = (obj) => { - let newObj - if (typeof obj !== 'object') return obj - if (!obj) return obj - if (Object.prototype.toString.apply(obj) === '[object Array]') { - newObj = [] - for (let i = 0; i < obj.length; i += 1) { - newObj[i] = cloneDeep(obj[i]) - } - return newObj - } - newObj = {} - for (const i in obj) { - if (Object.prototype.hasOwnProperty.call(obj, i)) { - newObj[i] = cloneDeep(obj[i]) - } +const HapiOpenAPI = require('hapi-openapi') +const Path = require('path') + +module.exports = { + plugin: HapiOpenAPI, + options: { + api: Path.resolve(__dirname, '../interface/swagger.json'), + handlers: Path.resolve(__dirname, './handlers') } - return newObj } - -module.exports = cloneDeep diff --git a/src/domain/settlement/index.js b/src/domain/settlement/index.js index a7666319..b4da442c 100644 --- a/src/domain/settlement/index.js +++ b/src/domain/settlement/index.js @@ -22,22 +22,25 @@ * Gates Foundation - Name Surname - * Georgi Georgiev - * Valentin Genev - * Deon Botha - * Rajiv Mothilal - * Miguel de Barros + * ModusBox + - Deon Botha + - Georgi Georgiev + - Miguel de Barros + - Rajiv Mothilal + - Valentin Genev -------------- ******/ - 'use strict' +const arrayDiff = require('lodash').difference const SettlementModel = require('../../models/settlement') +const SettlementModelModel = require('../../models/settlement/settlementModel') +const SettlementWindowContentModel = require('../../models/settlementWindowContent') const SettlementWindowModel = require('../../models/settlementWindow') const ErrorHandler = require('@mojaloop/central-services-error-handling') -const prepareParticipantsResult = function (participantCurrenciesList) { +const prepareParticipantsResult = (participantCurrenciesList) => { const participantAccounts = {} for (const account of participantCurrenciesList) { const { id } = account @@ -67,6 +70,20 @@ const prepareParticipantsResult = function (participantCurrenciesList) { return Array.from(Object.keys(participantAccounts).map(participantId => participantAccounts[participantId])) } +const groupSettlementWindowContentBySettlementWindow = (records) => { + const settlementWindows = {} + for (const record of records) { + const id = record.settlementWindowId + delete record.settlementWindowId + if (id in settlementWindows) { + settlementWindows[id].push(record) + } else { + settlementWindows[id] = [record] + } + } + return settlementWindows +} + module.exports = { getById: async function ({ settlementId }, enums) { const settlement = await SettlementModel.getById({ settlementId }, enums) @@ -171,34 +188,47 @@ module.exports = { }, settlementEventTrigger: async function (params, enums) { - const settlementWindowsIdList = params.settlementWindows - const reason = params.reason - const idList = settlementWindowsIdList.map(v => v.id) - // validate windows state - const settlementWindows = await SettlementWindowModel.getByListOfIds(idList, enums.settlementWindowStates) - if (settlementWindows && settlementWindows.length !== idList.length) { - throw ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.VALIDATION_ERROR, 'At least one provided settlement window does not exist') + // validate settlement model + const { settlementModel, reason, settlementWindows } = params + const settlementModelData = await SettlementModelModel.getByName(settlementModel) + if (!settlementModelData) { + throw ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.VALIDATION_ERROR, 'Settlement model not found') + } else if (settlementModelData.settlementGranularityId === enums.settlementGranularity.GROSS || + settlementModelData.settlementDelayId === enums.settlementDelay.IMMEDIATE) { + throw ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.VALIDATION_ERROR, 'Settlement can not be created for GROSS or IMMEDIATE models') } - for (const settlementWindow of settlementWindows) { - const { state } = settlementWindow - if (state !== enums.settlementWindowStates.CLOSED && - state !== enums.settlementWindowStates.ABORTED) { - throw ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.VALIDATION_ERROR, 'At least one settlement window is not in closed or aborted state') - } + // validate windows content + const idList = settlementWindows.map(v => v.id) + const applicableWindows = await SettlementWindowModel.getByListOfIds(idList, settlementModelData, enums.settlementWindowStates) + const applicableIdList = applicableWindows.map(v => v.settlementWindowId) + const nonApplicableIdList = arrayDiff(idList, applicableIdList) + if (nonApplicableIdList.length) { + throw ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.VALIDATION_ERROR, `Inapplicable windows ${nonApplicableIdList.join(', ')}`) } - const settlementId = await SettlementModel.triggerEvent({ idList, reason }, enums) + + // settlement event trigger + const settlementId = await SettlementModel.triggerSettlementEvent({ idList, reason }, settlementModelData, enums) + + // retrieve resulting data for response const settlement = await SettlementModel.getById({ settlementId }) const settlementWindowsList = await SettlementWindowModel.getBySettlementId({ settlementId }) + const settlementWindowContentAll = await SettlementWindowContentModel.getBySettlementId(settlementId) + const settlementWindowsContent = groupSettlementWindowContentBySettlementWindow(settlementWindowContentAll) + const settlementWindowsWithContent = settlementWindowsList.map(record => { + record.content = settlementWindowsContent[record.id] + return record + }) const participantCurrenciesList = await SettlementModel.settlementParticipantCurrency.getParticipantCurrencyBySettlementId({ settlementId }) const participants = prepareParticipantsResult(participantCurrenciesList) return { id: settlement.settlementId, + settlementModel, state: settlement.state, reason: settlement.reason, createdDate: settlement.createdDate, changedDate: settlement.changedDate, - settlementWindows: settlementWindowsList, + settlementWindows: settlementWindowsWithContent, participants } }, diff --git a/src/domain/settlementWindow/index.js b/src/domain/settlementWindow/index.js index 68b5c190..12ffc1b3 100644 --- a/src/domain/settlementWindow/index.js +++ b/src/domain/settlementWindow/index.js @@ -18,43 +18,86 @@ * Gates Foundation - Name Surname - * Georgi Georgiev - * Valentin Genev - * Deon Botha - * Rajiv Mothilal - * Miguel de Barros + * ModusBox + - Deon Botha + - Georgi Georgiev + - Miguel de Barros + - Rajiv Mothilal + - Valentin Genev + - Lazola Lucas -------------- ******/ -const settlementWindowModel = require('../../models/settlementWindow') -const hasFilters = require('./../../utils/truthyProperty') +const Config = require('../../lib/config') +const Enum = require('@mojaloop/central-services-shared').Enum const ErrorHandler = require('@mojaloop/central-services-error-handling') +const hasFilters = require('./../../utils/truthyProperty') +const Producer = require('@mojaloop/central-services-stream').Util.Producer +const KafkaUtil = require('@mojaloop/central-services-shared').Util.Kafka +const SettlementWindowModel = require('../../models/settlementWindow') +const SettlementWindowContentModel = require('../../models/settlementWindowContent') +const StreamingProtocol = require('@mojaloop/central-services-shared').Util.StreamingProtocol +const Uuid = require('uuid4') module.exports = { - getById: async function (params, enums) { - const settlementWindow = await settlementWindowModel.getById(params, enums) - if (settlementWindow) return settlementWindow - else { - throw ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.VALIDATION_ERROR, `settlementWindowId: ${params.settlementWindowId} not found`) + getById: async function (params, enums, options) { + const settlementWindow = await SettlementWindowModel.getById(params) + + if (settlementWindow) { + const settlementWindowContent = await SettlementWindowContentModel.getBySettlementWindowId(settlementWindow.settlementWindowId) + + if (settlementWindowContent) { + settlementWindow.content = settlementWindowContent + return settlementWindow + } else { + throw ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.INTERNAL_SERVER_ERROR, `No records for settlementWidowContentId : ${params.settlementWindowId} found`) + } + } else { + throw ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.INTERNAL_SERVER_ERROR, `No record for settlementWindowId: ${params.settlementWindowId} found`) } }, getByParams: async function (params, enums) { // 4 filters - at least one should be used - if (hasFilters(params.query) && Object.keys(params.query).length < 5) { - const settlementWindows = await settlementWindowModel.getByParams(params, enums) + if (hasFilters(params.query) && Object.keys(params.query).length < 6) { + const settlementWindows = await SettlementWindowModel.getByParams(params, enums) if (settlementWindows && settlementWindows.length > 0) { + for (const settlementWindow of settlementWindows) { + const settlementWindowContent = await SettlementWindowContentModel.getBySettlementWindowId(settlementWindow.settlementWindowId) + if (settlementWindowContent) { + settlementWindow.content = settlementWindowContent + } else { + throw ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.INTERNAL_SERVER_ERROR, `No records for settlementWidowContentId : ${settlementWindow.settlementWindowId} found`) + } + } return settlementWindows } else { throw ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.VALIDATION_ERROR, `settlementWindow by filters: ${JSON.stringify(params.query).replace(/"/g, '')} not found`) } } else { - throw ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.VALIDATION_ERROR, 'Use at least one parameter: participantId, state, fromDateTime, toDateTime') + throw ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.VALIDATION_ERROR, 'Use at least one parameter: participantId, state, fromDateTime, toDateTime, currency') } }, - close: async function (params, enums) { - const settlementWindowId = await settlementWindowModel.close(params, enums) - return settlementWindowModel.getById({ settlementWindowId }, enums) + process: async function (params, enums) { + const settlementWindowId = await SettlementWindowModel.process(params, enums) + + const messageId = Uuid() + const eventId = Uuid() + const state = StreamingProtocol.createEventState(Enum.Events.EventStatus.SUCCESS.status, Enum.Events.EventStatus.SUCCESS.code, Enum.Events.EventStatus.SUCCESS.description) + const event = StreamingProtocol.createEventMetadata(Enum.Events.Event.Type.SETTLEMENT_WINDOW, Enum.Events.Event.Action.CLOSE, state) + const metadata = StreamingProtocol.createMetadata(eventId, event) + const messageProtocol = StreamingProtocol.createMessage(messageId, Enum.Http.Headers.FSPIOP.SWITCH.value, Enum.Http.Headers.FSPIOP.SWITCH.value, metadata, undefined, params) + const topicConfig = KafkaUtil.createGeneralTopicConf(Config.KAFKA_CONFIG.TOPIC_TEMPLATES.GENERAL_TOPIC_TEMPLATE.TEMPLATE, Enum.Events.Event.Type.SETTLEMENT_WINDOW, Enum.Events.Event.Action.CLOSE) + const kafkaConfig = KafkaUtil.getKafkaConfig(Config.KAFKA_CONFIG, Enum.Kafka.Config.PRODUCER, Enum.Events.Event.Type.SETTLEMENT_WINDOW.toUpperCase(), Enum.Events.Event.Action.CLOSE.toUpperCase()) + + await Producer.produceMessage(messageProtocol, topicConfig, kafkaConfig) + + return SettlementWindowModel.getById({ settlementWindowId }, enums) + }, + + close: async function (settlementWindowId, reason) { + await SettlementWindowModel.close(settlementWindowId, reason) + return SettlementWindowModel.getById({ settlementWindowId }) } } diff --git a/src/handlers/index.js b/src/handlers/index.js new file mode 100644 index 00000000..5cd0b942 --- /dev/null +++ b/src/handlers/index.js @@ -0,0 +1,80 @@ +/***** + License + -------------- + Copyright © 2017 Bill & Melinda Gates Foundation + The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + + Contributors + -------------- + This is the official list of the Mojaloop project contributors for this file. + Names of the original copyright holders (individuals or organizations) + should be listed with a '*' in the first column. People who have + contributed from an organization can be listed under the organization + that actually holds the copyright for their contributions (see the + Gates Foundation organization for an example). Those individuals should have + their names indented and be marked with a '-'. Email address can be added + optionally within square brackets . + + * Gates Foundation + - Name Surname + + * ModusBox + - Georgi Georgiev + - Miguel de Barros + -------------- + ******/ +'use strict' + +/** + * @module Handlers CLI Startup + */ + +const Logger = require('@mojaloop/central-services-logger') +const Config = require('../lib/config') +const Setup = require('../shared/setup') +const PJson = require('../../package.json') +const { Command } = require('commander') +const Routes = require('../api/routes') + +const Program = new Command() + +Program + .version(PJson.version) + .description('CLI to manage Handlers') + +Program.command('handler') // sub-command name, coffeeType = type, required + .alias('h') // alternative sub-command is `h` + .description('Start a specified Handler') // command description + .option('--settlementwindow', 'Start the Settlement Window Handler') + // function to execute when command is uses + .action(async (args) => { + const handlerList = [] + if (args.settlementwindow && typeof args.settlementwindow === 'boolean') { + Logger.debug('CLI: Executing --settlementwindow') + const handler = { + type: 'settlementwindow', + enabled: true + } + handlerList.push(handler) + } + + module.exports = Setup.initialize({ + service: 'handler', + port: Config.PORT, + modules: [Routes], + handlers: handlerList, + runHandlers: true + }) + }) + +if (Array.isArray(process.argv) && process.argv.length > 2) { + // parse command line vars + Program.parse(process.argv) +} else { + // display default help + Program.help() +} diff --git a/src/handlers/register.js b/src/handlers/register.js new file mode 100644 index 00000000..540f11eb --- /dev/null +++ b/src/handlers/register.js @@ -0,0 +1,80 @@ +/***** + * @file This registers all handlers for the central-settlement API + License + -------------- + Copyright © 2017 Bill & Melinda Gates Foundation + The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + + Contributors + -------------- + This is the official list of the Mojaloop project contributors for this file. + Names of the original copyright holders (individuals or organizations) + should be listed with a '*' in the first column. People who have + contributed from an organization can be listed under the organization + that actually holds the copyright for their contributions (see the + Gates Foundation organization for an example). Those individuals should have + their names indented and be marked with a '-'. Email address can be added + optionally within square brackets . + + * Gates Foundation + - Name Surname + + * ModusBox + - Georgi Georgiev + -------------- + ******/ +'use strict' + +const ErrorHandling = require('@mojaloop/central-services-error-handling') +const Logger = require('@mojaloop/central-services-logger') +const requireGlob = require('require-glob') +const SettlementWindowHandlers = require('./settlementWindow/handler') + +/** + * @module src/handlers + */ + +/** + * @function RegisterAllHandlers + * + * @async + * @description Registers all handlers by using the require-glob to retrieve all handler exports in sub directories and access the registerAllHandlers() + * in each of them. Every handler in the sub-folders must have a registerAllHandlers() function + * @returns {boolean} - Returns a boolean: true if successful, or throws and error if failed + */ + +const registerAllHandlers = async () => { + try { + const modules = await requireGlob(['./**/handler.js']) + Logger.info(JSON.stringify(modules)) + for (const key in modules) { + Logger.info(`Registering handler module[${key}]: ${JSON.stringify(modules[key])}`) + if (Object.prototype.hasOwnProperty.call(modules[key], 'handler')) { + const handlerObject = modules[key] + Logger.info(JSON.stringify(handlerObject.handler)) + await handlerObject.handler.registerAllHandlers() + } else { + for (const i in modules[key]) { + const handlerObject = modules[key][i] + Logger.info(JSON.stringify(handlerObject.handler)) + await handlerObject.handler.registerAllHandlers() + } + } + } + return true + } catch (err) { + throw ErrorHandling.Factory.reformatFSPIOPError(err) + } +} + +module.exports = { + registerAllHandlers, + settlementWindow: { + registerAllHandlers: SettlementWindowHandlers.registerAllHandlers, + registerSettlementWindowHandler: SettlementWindowHandlers.registerSettlementWindowHandler + } +} diff --git a/src/handlers/settlementWindow/handler.js b/src/handlers/settlementWindow/handler.js new file mode 100644 index 00000000..cd0a9361 --- /dev/null +++ b/src/handlers/settlementWindow/handler.js @@ -0,0 +1,156 @@ +/***** + License + -------------- + Copyright © 2017 Bill & Melinda Gates Foundation + The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + + Contributors + -------------- + This is the official list of the Mojaloop project contributors for this file. + Names of the original copyright holders (individuals or organizations) + should be listed with a '*' in the first column. People who have + contributed from an organization can be listed under the organization + that actually holds the copyright for their contributions (see the + Gates Foundation organization for an example). Those individuals should have + their names indented and be marked with a '-'. Email address can be added + optionally within square brackets . + + * Gates Foundation + - Name Surname + + * ModusBox + - Georgi Georgiev + -------------- + ******/ +'use strict' + +/** + * @module src/handlers/transfers + */ + +const Config = require('../../lib/config') +const Consumer = require('@mojaloop/central-services-stream').Util.Consumer +const Enum = require('@mojaloop/central-services-shared').Enum +const ErrorHandling = require('@mojaloop/central-services-error-handling') +const Kafka = require('@mojaloop/central-services-shared').Util.Kafka +const Logger = require('@mojaloop/central-services-logger') +const Producer = require('@mojaloop/central-services-stream').Util.Producer +const retry = require('async-retry') +const SettlementWindowService = require('../../domain/settlementWindow') +const Utility = require('@mojaloop/central-services-shared').Util + +const location = { module: 'SettlementWindowHandler', method: '', path: '' } // var object used as pointer + +const consumerCommit = true +const fromSwitch = true + +const retryDelay = Config.WINDOW_AGGREGATION_RETRY_INTERVAL +const retryCount = Config.WINDOW_AGGREGATION_RETRY_COUNT +const retryOpts = { + retries: retryCount, + minTimeout: retryDelay, + maxTimeout: retryDelay +} + +const closeSettlementWindow = async (error, messages) => { + if (error) { + Logger.error(error) + throw ErrorHandling.Factory.reformatFSPIOPError(error) + } + let message = {} + try { + Logger.info(Utility.breadcrumb(location, { method: 'closeSettlementWindow' })) + if (Array.isArray(messages)) { + message = messages[0] + } else { + message = messages + } + const payload = message.value.content.payload + const metadata = message.value.metadata + const action = metadata.event.action + + const kafkaTopic = message.topic + const params = { message, kafkaTopic, decodedPayload: payload, consumer: Consumer, producer: Producer } + + const actionLetter = action === Enum.Events.Event.Action.CLOSE ? Enum.Events.ActionLetter.close + : Enum.Events.ActionLetter.unknown + + if (!payload) { + Logger.info(Utility.breadcrumb(location, `missingPayload--${actionLetter}1`)) + const fspiopError = ErrorHandling.Factory.createInternalServerFSPIOPError('Settlement window handler missing payload') + const eventDetail = { functionality: Enum.Events.Event.Type.NOTIFICATION, action: Enum.Events.Event.Action.SETTLEMENT_WINDOW } + await Kafka.proceed(Config.KAFKA_CONFIG, params, { consumerCommit, fspiopError: fspiopError.toApiErrorObject(Config.ERROR_HANDLING), eventDetail, fromSwitch }) + throw fspiopError + } + const settlementWindowId = payload.settlementWindowId + const reason = payload.reason + Logger.info(Utility.breadcrumb(location, 'validationPassed')) + await Kafka.commitMessageSync(Consumer, kafkaTopic, message) + + await retry(async () => { // use bail(new Error('to break before max retries')) + const settlementWindow = await SettlementWindowService.close(settlementWindowId, reason) + if (!settlementWindow || settlementWindow.state !== Enum.Settlements.SettlementWindowState.CLOSED) { + Logger.info(Utility.breadcrumb(location, { path: 'windowCloseRetry' })) + const errorDescription = `Settlement window close failed after max retry count ${retryCount} has been exhausted in ${retryCount * retryDelay / 1000}s` + throw ErrorHandling.Factory.createFSPIOPError(ErrorHandling.Enums.FSPIOPErrorCodes.INTERNAL_SERVER_ERROR, errorDescription) + } + Logger.info(Utility.breadcrumb(location, `done--${actionLetter}2`)) + return true + }, retryOpts) + return true + } catch (err) { + Logger.error(`${Utility.breadcrumb(location)}::${err.message}--0`) + return true + } +} + +/** + * @function registerSettlementWindowHandler + * + * @async + * @description Registers SettlementWindowHandler for processing windows closure. Gets Kafka config from default.json + * Calls createHandler to register the handler against the Stream Processing API + * @returns {boolean} - Returns a boolean: true if successful, or throws and error if failed + */ +const registerSettlementWindowHandler = async () => { + try { + const settlementWindowHandler = { + command: closeSettlementWindow, + topicName: Kafka.transformGeneralTopicName(Config.KAFKA_CONFIG.TOPIC_TEMPLATES.GENERAL_TOPIC_TEMPLATE.TEMPLATE, Enum.Events.Event.Type.SETTLEMENT_WINDOW, Enum.Events.Event.Action.CLOSE), + config: Kafka.getKafkaConfig(Config.KAFKA_CONFIG, Enum.Kafka.Config.CONSUMER, Enum.Events.Event.Type.SETTLEMENT_WINDOW.toUpperCase(), Enum.Events.Event.Action.CLOSE.toUpperCase()) + } + settlementWindowHandler.config.rdkafkaConf['client.id'] = settlementWindowHandler.topicName + await Consumer.createHandler(settlementWindowHandler.topicName, settlementWindowHandler.config, settlementWindowHandler.command) + return true + } catch (err) { + Logger.error(err) + throw ErrorHandling.Factory.reformatFSPIOPError(err) + } +} + +/** + * @function RegisterAllHandlers + * + * @async + * @description Registers all handlers + * + * @returns {boolean} - Returns a boolean: true if successful, or throws and error if failed + */ +const registerAllHandlers = async () => { + try { + await registerSettlementWindowHandler() + return true + } catch (err) { + throw ErrorHandling.Factory.reformatFSPIOPError(err) + } +} + +module.exports = { + closeSettlementWindow, + registerAllHandlers, + registerSettlementWindowHandler +} diff --git a/src/interface/swagger.json b/src/interface/swagger.json index fc3466ad..3965582d 100644 --- a/src/interface/swagger.json +++ b/src/interface/swagger.json @@ -1,7 +1,7 @@ { "swagger": "2.0", "info": { - "version": "1.1.0", + "version": "1.9.1", "title": "MOJALOOP Central Settlements", "description": "Mojaloop API for Settlements." }, @@ -101,7 +101,7 @@ "application/json" ], "deprecated": false, - "description": "If the settlementWindow is open, it can be closed and a new window is created. If it is already closed, return an error message. Returns the new settlement window.", + "description": "Closes requested window and opens a new one.", "summary": "closeSettlementWindow", "operationId": "closeSettlementWindow", "parameters": [ @@ -210,7 +210,15 @@ "format": "date-time", "description": "The end date for query (relates to central-ledger.settlementWindow.createdDate). Can be used together with `fromDateTime'. eg 2017-07-21T17:32:28Z\n", "required": false + }, + { + "in": "query", + "name": "currency", + "type": "string", + "description": "A valid currency to filter on.\n", + "required": false } + ], "tags": [ "getSettlementWindowsByParams" @@ -392,7 +400,7 @@ "application/json" ], "deprecated": false, - "description": "Trigger the creation of a settlement, that does the calculation of the net settlement position per participant and marks all transfers in the affected windows as PENDING_SETTLEMENT. Returned dataset is the net settlement report for the settlementWindow", + "description": "Triggers settlement creation. Returns settlement report.", "summary": "createSettlement", "operationId": "createSettlement", "parameters": [ @@ -581,7 +589,7 @@ } } }, - "/settlements/{settlementId}/participants/{participantId}": { + "/settlements/{sid}/participants/{pid}": { "get": { "produces": [ "application/json" @@ -593,14 +601,14 @@ "parameters": [ { "in": "path", - "name": "settlementId", + "name": "sid", "required": true, "type": "integer", "description": "A valid Settlement Id.\n" }, { "in": "path", - "name": "participantId", + "name": "pid", "required": true, "type": "integer", "description": "A valid Participant Id.\n" @@ -656,20 +664,20 @@ "application/json" ], "deprecated": false, - "description": "Acknowledgement of settlement by updating with Settlements Id and Participant Id.", + "description": "Acknowledgement of settlement by updating the reason and state by SP.", "summary": "updateSettlementBySettlementParticipant", "operationId": "updateSettlementBySettlementParticipant", "parameters": [ { "in": "path", - "name": "settlementId", + "name": "sid", "description": "A valid Settlement Id.", "required": true, "type": "integer" }, { "in": "path", - "name": "participantId", + "name": "pid", "description": "A valid Participant Id.", "required": true, "type": "integer" @@ -727,7 +735,7 @@ } } }, - "/settlements/{settlementId}/participants/{participantId}/accounts/{accountId}": { + "/settlements/{sid}/participants/{pid}/accounts/{aid}": { "get": { "produces": [ "application/json" @@ -739,24 +747,24 @@ "parameters": [ { "in": "path", - "name": "settlementId", + "name": "sid", "required": true, "type": "integer", "description": "A valid Settlement Id.\n" }, { "in": "path", - "name": "participantId", + "name": "pid", "required": true, "type": "integer", "description": "A valid Participant Id.\n" }, { "in": "path", - "name": "accountId", + "name": "aid", "required": true, "type": "integer", - "description": "A valid Account Id. \n" + "description": "A valid Account Id.\n" } ], "tags": [ @@ -809,27 +817,27 @@ "application/json" ], "deprecated": false, - "description": "Acknowledgement of settlement by updating the reason and state by Settlements Id, Participant Id and accounts Id.", + "description": "Acknowledgement of settlement by updating the reason and state by SPA.", "summary": "updateSettlementBySettlementParticipantAccount", "operationId": "updateSettlementBySettlementParticipantAccount", "parameters": [ { "in": "path", - "name": "settlementId", + "name": "sid", "description": "A valid Settlement Id.", "required": true, "type": "integer" }, { "in": "path", - "name": "participantId", + "name": "pid", "description": "A valid Participant Id.", "required": true, "type": "integer" }, { "in": "path", - "name": "accountId", + "name": "aid", "description": "A valid Account Id.", "required": true, "type": "integer" @@ -1058,6 +1066,9 @@ "SettlementEventPayload": { "type": "object", "properties": { + "settlementModel": { + "type": "string" + }, "reason": { "type": "string" }, @@ -1071,6 +1082,7 @@ } }, "required": [ + "settlementModel", "reason", "settlementWindows" ] @@ -1103,11 +1115,50 @@ }, "changedDate": { "type": "string" + }, + "content": { + "type": "array", + "items": { + "$ref": "#/definitions/SettlementWindowContent" + } + } + }, + "required": [ + "id", + "state", + "createdDate" + ] + }, + "SettlementWindowContent": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "state": { + "type": "string" + }, + "ledgerAccountType": { + "type": "string" + }, + "currencyId": { + "type": "string" + }, + "createdDate": { + "type": "string" + }, + "changedDate": { + "type": "string" + }, + "settlementId": { + "type": "integer" } }, "required": [ "id", "state", + "ledgerAccountType", + "currencyId", "createdDate" ] }, diff --git a/src/lib/config.js b/src/lib/config.js index 31518912..50502d41 100644 --- a/src/lib/config.js +++ b/src/lib/config.js @@ -36,8 +36,14 @@ module.exports = { }, debug: RC.DATABASE.DEBUG }, + WINDOW_AGGREGATION_RETRY_COUNT: RC.WINDOW_AGGREGATION.RETRY_COUNT, + WINDOW_AGGREGATION_RETRY_INTERVAL: RC.WINDOW_AGGREGATION.RETRY_INTERVAL, TRANSFER_VALIDITY_SECONDS: RC.TRANSFER_VALIDITY_SECONDS, HUB_ID: RC.HUB_PARTICIPANT.ID, HUB_NAME: RC.HUB_PARTICIPANT.NAME, + HANDLERS: RC.HANDLERS, + HANDLERS_API: RC.HANDLERS.API, + HANDLERS_API_DISABLED: RC.HANDLERS.API.DISABLED, + HANDLERS_DISABLED: RC.HANDLERS.DISABLED, KAFKA_CONFIG: RC.KAFKA } diff --git a/src/handlers/lib/healthCheck/subServiceHealth.js b/src/lib/healthCheck/subServiceHealth.js similarity index 72% rename from src/handlers/lib/healthCheck/subServiceHealth.js rename to src/lib/healthCheck/subServiceHealth.js index e7d09535..81d8d7a4 100644 --- a/src/handlers/lib/healthCheck/subServiceHealth.js +++ b/src/lib/healthCheck/subServiceHealth.js @@ -19,6 +19,9 @@ * Gates Foundation - Name Surname + * ModusBox + - Georgi Georgiev -------------- ******/ @@ -26,8 +29,34 @@ const { statusEnum, serviceName } = require('@mojaloop/central-services-shared').HealthCheck.HealthCheckEnums const Logger = require('@mojaloop/central-services-logger') +const Consumer = require('@mojaloop/central-services-stream').Util.Consumer + +const MigrationLockModel = require('../../models/misc/migrationLock') + +/** + * @function getSubServiceHealthBroker + * + * @description + * Gets the health for the broker, by checking that the consumer is + * connected for each topic + * + * @returns Promise The SubService health object for the broker + */ +const getSubServiceHealthBroker = async () => { + const consumerTopics = Consumer.getListOfTopics() + let status = statusEnum.OK + try { + await Promise.all(consumerTopics.map(t => Consumer.isConnected(t))) + } catch (err) { + Logger.debug(`getSubServiceHealthBroker failed with error ${err.message}.`) + status = statusEnum.DOWN + } -const MigrationLockModel = require('../../../models/misc/migrationLock') + return { + name: serviceName.broker, + status + } +} /** * @function getSubServiceHealthDatastore @@ -58,5 +87,6 @@ const getSubServiceHealthDatastore = async () => { } module.exports = { + getSubServiceHealthBroker, getSubServiceHealthDatastore } diff --git a/src/handlers/lib/kafka/index.js b/src/lib/kafka/index.js similarity index 100% rename from src/handlers/lib/kafka/index.js rename to src/lib/kafka/index.js diff --git a/src/handlers/lib/kafka/producer.js b/src/lib/kafka/producer.js similarity index 100% rename from src/handlers/lib/kafka/producer.js rename to src/lib/kafka/producer.js diff --git a/src/handlers/lib/utility.js b/src/lib/utility.js similarity index 99% rename from src/handlers/lib/utility.js rename to src/lib/utility.js index a1ee7e8b..28b6a282 100644 --- a/src/handlers/lib/utility.js +++ b/src/lib/utility.js @@ -36,12 +36,12 @@ * @module src/handlers/lib */ -const Config = require('../../lib/config') +const Config = require('./config') const Mustache = require('mustache') const KafkaConfig = Config.KAFKA_CONFIG const Logger = require('@mojaloop/central-services-logger') const Uuid = require('uuid4') -const Kafka = require('./kafka') +const Kafka = require('./kafka/index') const ErrorHandler = require('@mojaloop/central-services-error-handling') /** diff --git a/src/models/lib/enums.js b/src/models/lib/enums.js index 1af17bb4..b476af35 100644 --- a/src/models/lib/enums.js +++ b/src/models/lib/enums.js @@ -18,12 +18,12 @@ * Gates Foundation - Name Surname - * Georgi Georgiev - * Valentin Genev - * Deon Botha + * ModusBox + - Deon Botha + - Georgi Georgiev + - Valentin Genev -------------- ******/ - 'use strict' const Db = require('../../lib/db') @@ -35,50 +35,6 @@ module.exports = { } }, - settlementWindowStates: async function () { - const settlementWindowStateEnum = {} - const settlementWindowStateEnumsList = await Db.settlementWindowState.find({}) - if (settlementWindowStateEnumsList) { - for (const state of settlementWindowStateEnumsList) { - settlementWindowStateEnum[`${state.enumeration}`] = state.settlementWindowStateId - } - return settlementWindowStateEnum - } - }, - settlementStates: async function () { - const settlementStateEnum = {} - - const settlementStateEnumsList = await Db.settlementState.find({}) - if (settlementStateEnumsList) { - for (const state of settlementStateEnumsList) { - settlementStateEnum[`${state.enumeration}`] = state.settlementStateId - } - return settlementStateEnum - } - }, - transferStates: async function () { - const transferStateEnum = {} - const transferStateEnumsList = await Db.transferState.find({}) - if (transferStateEnumsList) { - for (const state of transferStateEnumsList) { - transferStateEnum[`${state.transferStateId}`] = state.transferStateId - } - return transferStateEnum - } - }, - transferStateEnums: async function () { - const transferStateEnum = {} - const transferStateEnumsList = await Db.transferState.find({}) - if (transferStateEnumsList) { - for (const state of transferStateEnumsList) { - // apply distinct even though final result would contain distinct values - if (!transferStateEnum[`${state.enumeration}`]) { - transferStateEnum[`${state.enumeration}`] = state.enumeration - } - } - return transferStateEnum - } - }, ledgerAccountTypes: async function () { const ledgerAccountTypeEnum = {} const ledgerAccountTypeEnumsList = await Db.ledgerAccountType.find({}) @@ -99,6 +55,103 @@ module.exports = { return ledgerEntryTypeEnum } }, + participantLimitTypes: async function () { + const participantLimitTypeEnum = {} + const participantLimitTypeEnumsList = await Db.participantLimitType.find({}) + if (participantLimitTypeEnumsList) { + for (const state of participantLimitTypeEnumsList) { + participantLimitTypeEnum[`${state.name}`] = state.participantLimitTypeId + } + return participantLimitTypeEnum + } + }, + settlementDelay: async function () { + const settlementDelayName = {} + + const settlementDelayNamesList = await Db.settlementDelay.find({}) + if (settlementDelayNamesList) { + for (const record of settlementDelayNamesList) { + settlementDelayName[`${record.name}`] = record.settlementDelayId + } + return settlementDelayName + } + }, + settlementDelayEnums: async function () { + const settlementDelayEnum = {} + + const settlementDelayEnumsList = await Db.settlementDelay.find({}) + if (settlementDelayEnumsList) { + for (const record of settlementDelayEnumsList) { + settlementDelayEnum[`${record.name}`] = record.name + } + return settlementDelayEnum + } + }, + settlementGranularity: async function () { + const settlementGranularityName = {} + + const settlementGranularityNamesList = await Db.settlementGranularity.find({}) + if (settlementGranularityNamesList) { + for (const record of settlementGranularityNamesList) { + settlementGranularityName[`${record.name}`] = record.settlementGranularityId + } + return settlementGranularityName + } + }, + settlementGranularityEnums: async function () { + const settlementGranularityEnum = {} + + const settlementGranularityEnumsList = await Db.settlementGranularity.find({}) + if (settlementGranularityEnumsList) { + for (const record of settlementGranularityEnumsList) { + settlementGranularityEnum[`${record.name}`] = record.name + } + return settlementGranularityEnum + } + }, + settlementInterchange: async function () { + const settlementInterchangeName = {} + + const settlementInterchangeNamesList = await Db.settlementInterchange.find({}) + if (settlementInterchangeNamesList) { + for (const record of settlementInterchangeNamesList) { + settlementInterchangeName[`${record.name}`] = record.settlementInterchangeId + } + return settlementInterchangeName + } + }, + settlementInterchangeEnums: async function () { + const settlementInterchangeEnum = {} + + const settlementInterchangeEnumsList = await Db.settlementInterchange.find({}) + if (settlementInterchangeEnumsList) { + for (const record of settlementInterchangeEnumsList) { + settlementInterchangeEnum[`${record.name}`] = record.name + } + return settlementInterchangeEnum + } + }, + settlementStates: async function () { + const settlementStateEnum = {} + + const settlementStateEnumsList = await Db.settlementState.find({}) + if (settlementStateEnumsList) { + for (const state of settlementStateEnumsList) { + settlementStateEnum[`${state.enumeration}`] = state.settlementStateId + } + return settlementStateEnum + } + }, + settlementWindowStates: async function () { + const settlementWindowStateEnum = {} + const settlementWindowStateEnumsList = await Db.settlementWindowState.find({}) + if (settlementWindowStateEnumsList) { + for (const state of settlementWindowStateEnumsList) { + settlementWindowStateEnum[`${state.enumeration}`] = state.settlementWindowStateId + } + return settlementWindowStateEnum + } + }, transferParticipantRoleTypes: async function () { const transferParticipantRoleTypeEnum = {} const transferParticipantRoleTypeEnumsList = await Db.transferParticipantRoleType.find({}) @@ -109,14 +162,27 @@ module.exports = { return transferParticipantRoleTypeEnum } }, - participantLimitTypes: async function () { - const participantLimitTypeEnum = {} - const participantLimitTypeEnumsList = await Db.participantLimitType.find({}) - if (participantLimitTypeEnumsList) { - for (const state of participantLimitTypeEnumsList) { - participantLimitTypeEnum[`${state.name}`] = state.participantLimitTypeId + transferStateEnums: async function () { + const transferStateEnum = {} + const transferStateEnumsList = await Db.transferState.find({}) + if (transferStateEnumsList) { + for (const state of transferStateEnumsList) { + // apply distinct even though final result would contain distinct values + if (!transferStateEnum[`${state.enumeration}`]) { + transferStateEnum[`${state.enumeration}`] = state.enumeration + } } - return participantLimitTypeEnum + return transferStateEnum + } + }, + transferStates: async function () { + const transferStateEnum = {} + const transferStateEnumsList = await Db.transferState.find({}) + if (transferStateEnumsList) { + for (const state of transferStateEnumsList) { + transferStateEnum[`${state.transferStateId}`] = state.transferStateId + } + return transferStateEnum } } } diff --git a/src/models/settlement/facade.js b/src/models/settlement/facade.js index 7555da88..68889d35 100644 --- a/src/models/settlement/facade.js +++ b/src/models/settlement/facade.js @@ -18,14 +18,15 @@ * Gates Foundation - Name Surname - * Georgi Georgiev - * Valentin Genev - * Deon Botha + * ModusBox + - Deon Botha + - Georgi Georgiev + - Valentin Genev -------------- ******/ - 'use strict' +const arrayDiff = require('lodash').difference const ErrorHandler = require('@mojaloop/central-services-error-handling') const MLNumber = require('@mojaloop/ml-number') const Db = require('../../lib/db') @@ -33,9 +34,46 @@ const Uuid = require('uuid4') const Crypto = require('crypto') const Config = require('../../lib/config') const ParticipantFacade = require('@mojaloop/central-ledger/src/models/participant/facade') -const cloneDeep = require('../../utils/cloneDeep') const Enums = require('../lib/enums') -const Utility = require('../../handlers/lib/utility') +const Utility = require('../../lib/utility') + +const groupByWindowsWithContent = (records) => { + const settlementWindowsAssoc = {} + for (const record of records) { + const id = record.settlementWindowId + if (id in settlementWindowsAssoc) { + settlementWindowsAssoc[id].content.push({ + id: record.settlementWindowContentId, + state: record.state, + ledgerAccountType: record.ledgerAccountType, + currencyId: record.currencyId, + createdDate: record.createdDate, + changedDate: record.changedDate + }) + } else { + settlementWindowsAssoc[id] = { + id, + state: record.settlementWindowStateId, + reason: record.reason, + createdDate: record.createdDate1, + changedDate: record.changedDate1, + content: [{ + id: record.settlementWindowContentId, + state: record.state, + ledgerAccountType: record.ledgerAccountType, + currencyId: record.currencyId, + createdDate: record.createdDate, + changedDate: record.changedDate + }] + } + } + } + const settlementWindows = [] + for (const key of Object.keys(settlementWindowsAssoc)) { + settlementWindows.push(settlementWindowsAssoc[key]) + } + return settlementWindows +} const getNotificationMessage = function (action, destination, payload) { return { @@ -279,8 +317,8 @@ const settlementTransfersReserve = async function (settlementId, transactionTime .first() .transacting(trx) - // TODO: notify dfsp for NDC change - // TODO: insert new limit with correct value for startAfterParticipantPositionChangeId + // TODO:: notify dfsp for NDC change + // TODO:: insert new limit with correct value for startAfterParticipantPositionChangeId await ParticipantFacade.adjustLimits(dfspAccountId, { type: 'NET_DEBIT_CAP', value: new MLNumber(netDebitCap).add(dfspAmount).toNumber() @@ -719,7 +757,8 @@ const Facade = { // seq-settlement-6.2.5, step 3 const settlementData = await knex('settlement AS s') .join('settlementStateChange AS ssc', 'ssc.settlementStateChangeId', 's.currentStateChangeId') - .select('s.settlementId', 'ssc.settlementStateId', 'ssc.reason', 'ssc.createdDate') + .join('settlementModel AS sm', 'sm.settlementModelId', 's.settlementModelId') + .select('s.settlementId', 'ssc.settlementStateId', 'ssc.reason', 'ssc.createdDate', 'sm.autoPositionReset') .where('s.settlementId', settlementId) .first() .transacting(trx) @@ -728,6 +767,9 @@ const Facade = { if (!settlementData) { throw ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.VALIDATION_ERROR, 'Settlement not found') } else { + const autoPositionReset = settlementData.autoPositionReset + delete settlementData.autoPositionReset + // seq-settlement-6.2.5, step 5 const settlementAccountList = await knex('settlementParticipantCurrency AS spc') .leftJoin('settlementParticipantCurrencyStateChange AS spcsc', 'spcsc.settlementParticipantCurrencyStateChangeId', 'spc.currentStateChangeId') @@ -739,23 +781,6 @@ const Facade = { .forUpdate() // seq-settlement-6.2.5, step 7 - const windowsList = await knex('settlementSettlementWindow AS ssw') - .join('settlementWindow AS sw', 'sw.settlementWindowId', 'ssw.settlementWindowId') - .join('settlementWindowStateChange AS swsc', 'swsc.settlementWindowStateChangeId', 'sw.currentStateChangeId') - .select('sw.settlementWindowId', 'swsc.settlementWindowStateId', 'swsc.reason', 'sw.createdDate') - .where('ssw.settlementId', settlementId) - .transacting(trx) - .forUpdate() - - // seq-settlement-6.2.5, step 9 - const windowsAccountsList = await knex('settlementTransferParticipant') - .select() - .distinct('settlementWindowId', 'participantCurrencyId') - .where({ settlementId }) - .transacting(trx) - .forUpdate() - - // seq-settlement-6.2.5, step 11 const settlementAccounts = { pendingSettlementCount: 0, psTransfersRecordedCount: 0, @@ -763,14 +788,16 @@ const Facade = { psTransfersCommittedCount: 0, settledCount: 0, abortedCount: 0, - unknownCount: 0 + unknownCount: 0, + settledIdList: [], + changedIdList: [] } const allAccounts = new Map() let pid // participantId let aid // accountId let state - // seq-settlement-6.2.5, step 12 + // seq-settlement-6.2.5, step 8 for (const account of settlementAccountList) { pid = account.participantId aid = account.participantCurrencyId @@ -788,7 +815,7 @@ const Facade = { key: account.key } - // seq-settlement-6.2.5, step 13 + // seq-settlement-6.2.5, step 9 switch (state) { case enums.settlementStates.PENDING_SETTLEMENT: { settlementAccounts.pendingSettlementCount++ @@ -820,210 +847,130 @@ const Facade = { } } } - // seq-settlement-6.2.5, step 14 + // seq-settlement-6.2.5, step 10 // let settlementAccountsInit = Object.assign({}, settlementAccounts) - // seq-settlement-6.2.5, step 15 - const allWindows = new Map() - for (const { settlementWindowId, settlementWindowStateId, reason, createdDate } of windowsList) { - allWindows[settlementWindowId] = { settlementWindowId, settlementWindowStateId, reason, createdDate } - } - - // seq-settlement-6.2.5, step 16 - const windowsAccounts = new Map() - const accountsWindows = new Map() - for (const record of windowsAccountsList) { - const wid = record.settlementWindowId - const aid = record.participantCurrencyId - const state = allAccounts[aid].state - accountsWindows[aid] = accountsWindows[aid] ? accountsWindows[aid] : { - id: aid, - windows: [] - } - accountsWindows[aid].windows.push(wid) - windowsAccounts[wid] = windowsAccounts[wid] ? windowsAccounts[wid] : { - id: wid, - pendingSettlementCount: 0, - psTransfersRecordedCount: 0, - psTransfersReservedCount: 0, - psTransfersCommittedCount: 0, - settledCount: 0, - abortedCount: 0 - } - switch (state) { - case enums.settlementStates.PENDING_SETTLEMENT: { - windowsAccounts[wid].pendingSettlementCount++ - break - } - case enums.settlementStates.PS_TRANSFERS_RECORDED: { - windowsAccounts[wid].psTransfersRecordedCount++ - break - } - case enums.settlementStates.PS_TRANSFERS_RESERVED: { - windowsAccounts[wid].psTransfersReservedCount++ - break - } - case enums.settlementStates.PS_TRANSFERS_COMMITTED: { - windowsAccounts[wid].psTransfersCommittedCount++ - break - } - case enums.settlementStates.SETTLED: { - windowsAccounts[wid].settledCount++ - break - } - case enums.settlementStates.ABORTED: { - windowsAccounts[wid].abortedCount++ - break - } - default: { - break - } - } - } - - // seq-settlement-6.2.5, step 17 - const windowsAccountsInit = cloneDeep(windowsAccounts) + // seq-settlement-6.2.5, step 10 const participants = [] - const affectedWindows = [] const settlementParticipantCurrencyStateChange = [] const processedAccounts = [] - // seq-settlement-6.2.5, step 18 + + // seq-settlement-6.2.5, step 11 for (let participant in payload.participants) { - if (Object.prototype.hasOwnProperty.call(payload.participants, participant)) { - const participantPayload = payload.participants[participant] - participants.push({ id: participantPayload.id, accounts: [] }) - const pi = participants.length - 1 - participant = participants[pi] - // seq-settlement-6.2.5, step 19 - for (const account in participantPayload.accounts) { - if (Object.prototype.hasOwnProperty.call(participantPayload.accounts, account)) { - const accountPayload = participantPayload.accounts[account] - if (allAccounts[accountPayload.id] === undefined) { - participant.accounts.push({ - id: accountPayload.id, - errorInformation: ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.CLIENT_ERROR, 'Account not found').toApiErrorObject().errorInformation - }) - // seq-settlement-6.2.5, step 21 - } else if (participantPayload.id !== allAccounts[accountPayload.id].participantId) { - processedAccounts.push(accountPayload.id) - participant.accounts.push({ - id: accountPayload.id, - errorInformation: ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.CLIENT_ERROR, 'Participant and account mismatch').toApiErrorObject().errorInformation - }) - // seq-settlement-6.2.5, step 22 - } else if (processedAccounts.indexOf(accountPayload.id) > -1) { - participant.accounts.push({ - id: accountPayload.id, - state: allAccounts[accountPayload.id].state, - reason: allAccounts[accountPayload.id].reason, - createdDate: allAccounts[accountPayload.id].createdDate, - netSettlementAmount: allAccounts[accountPayload.id].netSettlementAmount, - errorInformation: ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.CLIENT_ERROR, 'Account already processed once').toApiErrorObject().errorInformation - }) - // seq-settlement-6.2.5, step 23 - } else if (allAccounts[accountPayload.id].state === accountPayload.state) { - processedAccounts.push(accountPayload.id) - participant.accounts.push({ - id: accountPayload.id, - state: accountPayload.state, - reason: accountPayload.reason, - externalReference: accountPayload.externalReference, - createdDate: transactionTimestamp, - netSettlementAmount: allAccounts[accountPayload.id].netSettlementAmount - }) - settlementParticipantCurrencyStateChange.push({ - settlementParticipantCurrencyId: allAccounts[accountPayload.id].key, - settlementStateId: accountPayload.state, - reason: accountPayload.reason, - externalReference: accountPayload.externalReference - }) - allAccounts[accountPayload.id].reason = accountPayload.reason - allAccounts[accountPayload.id].createdDate = transactionTimestamp - // seq-settlement-6.2.5, step 24 - } else if ((settlementData.settlementStateId === enums.settlementStates.PENDING_SETTLEMENT && accountPayload.state === enums.settlementStates.PS_TRANSFERS_RECORDED) || - (settlementData.settlementStateId === enums.settlementStates.PS_TRANSFERS_RECORDED && accountPayload.state === enums.settlementStates.PS_TRANSFERS_RESERVED) || - (settlementData.settlementStateId === enums.settlementStates.PS_TRANSFERS_RESERVED && accountPayload.state === enums.settlementStates.PS_TRANSFERS_COMMITTED) || - ((settlementData.settlementStateId === enums.settlementStates.PS_TRANSFERS_COMMITTED || settlementData.settlementStateId === enums.settlementStates.SETTLING) && - accountPayload.state === enums.settlementStates.SETTLED)) { - processedAccounts.push(accountPayload.id) - participant.accounts.push({ - id: accountPayload.id, - state: accountPayload.state, - reason: accountPayload.reason, - externalReference: accountPayload.externalReference, - createdDate: transactionTimestamp, - netSettlementAmount: allAccounts[accountPayload.id].netSettlementAmount - }) - const spcsc = { - settlementParticipantCurrencyId: allAccounts[accountPayload.id].key, - settlementStateId: accountPayload.state, - reason: accountPayload.reason, - externalReference: accountPayload.externalReference, - createdDate: transactionTimestamp - } - if (accountPayload.state === enums.settlementStates.PS_TRANSFERS_RECORDED) { - spcsc.settlementTransferId = Uuid() - } - settlementParticipantCurrencyStateChange.push(spcsc) - - if (accountPayload.state === enums.settlementStates.PS_TRANSFERS_RECORDED) { - settlementAccounts.pendingSettlementCount-- - settlementAccounts.psTransfersRecordedCount++ - } else if (accountPayload.state === enums.settlementStates.PS_TRANSFERS_RESERVED) { - settlementAccounts.psTransfersRecordedCount-- - settlementAccounts.psTransfersReservedCount++ - } else if (accountPayload.state === enums.settlementStates.PS_TRANSFERS_COMMITTED) { - settlementAccounts.psTransfersReservedCount-- - settlementAccounts.psTransfersCommittedCount++ - } else /* if (accountPayload.state === enums.settlementStates.SETTLED) */ { // disabled because else path not taken - settlementAccounts.psTransfersCommittedCount-- - settlementAccounts.settledCount++ - } - allAccounts[accountPayload.id].state = accountPayload.state - allAccounts[accountPayload.id].reason = accountPayload.reason - allAccounts[accountPayload.id].externalReference = accountPayload.externalReference - allAccounts[accountPayload.id].createdDate = transactionTimestamp - let settlementWindowId - for (const aw in accountsWindows[accountPayload.id].windows) { - settlementWindowId = accountsWindows[accountPayload.id].windows[aw] - if (accountPayload.state === enums.settlementStates.PS_TRANSFERS_RECORDED) { - windowsAccounts[settlementWindowId].pendingSettlementCount-- - windowsAccounts[settlementWindowId].psTransfersRecordedCount++ - } else if (accountPayload.state === enums.settlementStates.PS_TRANSFERS_RESERVED) { - windowsAccounts[settlementWindowId].psTransfersRecordedCount-- - windowsAccounts[settlementWindowId].psTransfersReservedCount++ - } else if (accountPayload.state === enums.settlementStates.PS_TRANSFERS_COMMITTED) { - windowsAccounts[settlementWindowId].psTransfersReservedCount-- - windowsAccounts[settlementWindowId].psTransfersCommittedCount++ - } else /* if (accountPayload.state === enums.settlementStates.SETTLED) */ { // disabled because else path not taken - windowsAccounts[settlementWindowId].psTransfersCommittedCount-- - windowsAccounts[settlementWindowId].settledCount++ - } - if (affectedWindows.indexOf(settlementWindowId) < 0) { - affectedWindows.push(settlementWindowId) - } - } - // seq-settlement-6.2.5, step 25 - } else { - participant.accounts.push({ - id: accountPayload.id, - state: allAccounts[accountPayload.id].state, - reason: allAccounts[accountPayload.id].reason, - createdDate: allAccounts[accountPayload.id].createdDate, - netSettlementAmount: allAccounts[accountPayload.id].netSettlementAmount, - errorInformation: ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.CLIENT_ERROR, 'State change not allowed').toApiErrorObject().errorInformation - }) - } + const participantPayload = payload.participants[participant] + participants.push({ id: participantPayload.id, accounts: [] }) + const pi = participants.length - 1 + participant = participants[pi] + // seq-settlement-6.2.5, step 12 + for (const account in participantPayload.accounts) { + const accountPayload = participantPayload.accounts[account] + // seq-settlement-6.2.5, step 13 + if (allAccounts[accountPayload.id] === undefined) { + participant.accounts.push({ + id: accountPayload.id, + errorInformation: ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.CLIENT_ERROR, 'Account not found').toApiErrorObject().errorInformation + }) + // seq-settlement-6.2.5, step 14 + } else if (participantPayload.id !== allAccounts[accountPayload.id].participantId) { + processedAccounts.push(accountPayload.id) + participant.accounts.push({ + id: accountPayload.id, + errorInformation: ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.CLIENT_ERROR, 'Participant and account mismatch').toApiErrorObject().errorInformation + }) + // seq-settlement-6.2.5, step 15 + } else if (processedAccounts.indexOf(accountPayload.id) > -1) { + participant.accounts.push({ + id: accountPayload.id, + state: allAccounts[accountPayload.id].state, + reason: allAccounts[accountPayload.id].reason, + createdDate: allAccounts[accountPayload.id].createdDate, + netSettlementAmount: allAccounts[accountPayload.id].netSettlementAmount, + errorInformation: ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.CLIENT_ERROR, 'Account already processed once').toApiErrorObject().errorInformation + }) + // seq-settlement-6.2.5, step 16 + } else if (allAccounts[accountPayload.id].state === accountPayload.state) { + processedAccounts.push(accountPayload.id) + participant.accounts.push({ + id: accountPayload.id, + state: accountPayload.state, + reason: accountPayload.reason, + externalReference: accountPayload.externalReference, + createdDate: transactionTimestamp, + netSettlementAmount: allAccounts[accountPayload.id].netSettlementAmount + }) + settlementParticipantCurrencyStateChange.push({ + settlementParticipantCurrencyId: allAccounts[accountPayload.id].key, + settlementStateId: accountPayload.state, + reason: accountPayload.reason, + externalReference: accountPayload.externalReference + }) + allAccounts[accountPayload.id].reason = accountPayload.reason + allAccounts[accountPayload.id].createdDate = transactionTimestamp + // seq-settlement-6.2.5, step 17 + } else if ((settlementData.settlementStateId === enums.settlementStates.PENDING_SETTLEMENT && accountPayload.state === enums.settlementStates.PS_TRANSFERS_RECORDED) || + (settlementData.settlementStateId === enums.settlementStates.PS_TRANSFERS_RECORDED && accountPayload.state === enums.settlementStates.PS_TRANSFERS_RESERVED) || + (settlementData.settlementStateId === enums.settlementStates.PS_TRANSFERS_RESERVED && accountPayload.state === enums.settlementStates.PS_TRANSFERS_COMMITTED) || + ((settlementData.settlementStateId === enums.settlementStates.PS_TRANSFERS_COMMITTED || settlementData.settlementStateId === enums.settlementStates.SETTLING) && + accountPayload.state === enums.settlementStates.SETTLED)) { + processedAccounts.push(accountPayload.id) + participant.accounts.push({ + id: accountPayload.id, + state: accountPayload.state, + reason: accountPayload.reason, + externalReference: accountPayload.externalReference, + createdDate: transactionTimestamp, + netSettlementAmount: allAccounts[accountPayload.id].netSettlementAmount + }) + const spcsc = { + settlementParticipantCurrencyId: allAccounts[accountPayload.id].key, + settlementStateId: accountPayload.state, + reason: accountPayload.reason, + externalReference: accountPayload.externalReference, + createdDate: transactionTimestamp + } + if (accountPayload.state === enums.settlementStates.PS_TRANSFERS_RECORDED) { + spcsc.settlementTransferId = Uuid() + } + settlementParticipantCurrencyStateChange.push(spcsc) + + if (accountPayload.state === enums.settlementStates.PS_TRANSFERS_RECORDED) { + settlementAccounts.pendingSettlementCount-- + settlementAccounts.psTransfersRecordedCount++ + } else if (accountPayload.state === enums.settlementStates.PS_TRANSFERS_RESERVED) { + settlementAccounts.psTransfersRecordedCount-- + settlementAccounts.psTransfersReservedCount++ + } else if (accountPayload.state === enums.settlementStates.PS_TRANSFERS_COMMITTED) { + settlementAccounts.psTransfersReservedCount-- + settlementAccounts.psTransfersCommittedCount++ + } else /* if (accountPayload.state === enums.settlementStates.SETTLED) */ { // disabled as else path is never taken + settlementAccounts.psTransfersCommittedCount-- + settlementAccounts.settledCount++ + settlementAccounts.settledIdList.push(accountPayload.id) } + settlementAccounts.changedIdList.push(accountPayload.id) + allAccounts[accountPayload.id].state = accountPayload.state + allAccounts[accountPayload.id].reason = accountPayload.reason + allAccounts[accountPayload.id].externalReference = accountPayload.externalReference + allAccounts[accountPayload.id].createdDate = transactionTimestamp + // seq-settlement-6.2.5, step 18 + } else { + participant.accounts.push({ + id: accountPayload.id, + state: allAccounts[accountPayload.id].state, + reason: allAccounts[accountPayload.id].reason, + createdDate: allAccounts[accountPayload.id].createdDate, + netSettlementAmount: allAccounts[accountPayload.id].netSettlementAmount, + errorInformation: ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.CLIENT_ERROR, 'State change not allowed').toApiErrorObject().errorInformation + }) } } } let insertPromises = [] let updatePromises = [] - // seq-settlement-6.2.5, step 26 + // seq-settlement-6.2.5, step 19 for (const spcsc of settlementParticipantCurrencyStateChange) { // Switched to insert from batchInsert because only LAST_INSERT_ID is returned - // TODO: PoC - batchInsert + select inserted ids vs multiple inserts without select + // TODO:: PoC - batchInsert + select inserted ids vs multiple inserts without select const spcscCopy = Object.assign({}, spcsc) delete spcscCopy.settlementTransferId insertPromises.push( @@ -1033,7 +980,7 @@ const Facade = { ) } const settlementParticipantCurrencyStateChangeIdList = (await Promise.all(insertPromises)).map(v => v[0]) - // seq-settlement-6.2.5, step 29 + // seq-settlement-6.2.5, step 21 for (const i in settlementParticipantCurrencyStateChangeIdList) { const updatedColumns = { currentStateChangeId: settlementParticipantCurrencyStateChangeIdList[i] } if (settlementParticipantCurrencyStateChange[i].settlementTransferId) { @@ -1048,60 +995,126 @@ const Facade = { } await Promise.all(updatePromises) - if (settlementData.settlementStateId === enums.settlementStates.PENDING_SETTLEMENT) { - await Facade.settlementTransfersPrepare(settlementId, transactionTimestamp, enums, trx) - } else if (settlementData.settlementStateId === enums.settlementStates.PS_TRANSFERS_RECORDED) { - await Facade.settlementTransfersReserve(settlementId, transactionTimestamp, enums, trx) - } else if (settlementData.settlementStateId === enums.settlementStates.PS_TRANSFERS_RESERVED) { - await Facade.settlementTransfersCommit(settlementId, transactionTimestamp, enums, trx) + if (autoPositionReset) { + if (settlementData.settlementStateId === enums.settlementStates.PENDING_SETTLEMENT) { + await Facade.settlementTransfersPrepare(settlementId, transactionTimestamp, enums, trx) + } else if (settlementData.settlementStateId === enums.settlementStates.PS_TRANSFERS_RECORDED) { + await Facade.settlementTransfersReserve(settlementId, transactionTimestamp, enums, trx) + } else if (settlementData.settlementStateId === enums.settlementStates.PS_TRANSFERS_RESERVED) { + await Facade.settlementTransfersCommit(settlementId, transactionTimestamp, enums, trx) + } } - const settlementWindowStateChange = [] - const settlementWindows = [] // response object - let windowAccounts, windowAccountsInit - for (const aw in affectedWindows) { - windowAccounts = windowsAccounts[affectedWindows[aw]] - windowAccountsInit = windowsAccountsInit[affectedWindows[aw]] - - if (windowAccounts.pendingSettlementCount !== windowAccountsInit.pendingSettlementCount || - windowAccounts.psTransfersRecordedCount !== windowAccountsInit.psTransfersRecordedCount || - windowAccounts.psTransfersReservedCount !== windowAccountsInit.psTransfersReservedCount || - windowAccounts.psTransfersCommittedCount !== windowAccountsInit.psTransfersCommittedCount || - windowAccounts.settledCount !== windowAccountsInit.settledCount) { // this condition is never reached because always any of the previous is true - settlementWindows.push(allWindows[affectedWindows[aw]]) - - if (windowAccounts.psTransfersCommittedCount === 0 && - windowAccounts.abortedCount === 0 && - windowAccounts.settledCount > 0) { - allWindows[affectedWindows[aw]].settlementWindowStateId = enums.settlementWindowStates.SETTLED - allWindows[affectedWindows[aw]].reason = 'All window settlement accounts are settled' - allWindows[affectedWindows[aw]].createdDate = transactionTimestamp - settlementWindowStateChange.push(allWindows[affectedWindows[aw]]) + // seq-settlement-6.2.5, step 23 + if (settlementAccounts.settledIdList.length > 0) { + await knex('settlementContentAggregation').transacting(trx) + .where('settlementId', settlementId) + .whereIn('participantCurrencyId', settlementAccounts.settledIdList) + .update('currentStateId', enums.settlementWindowStates.SETTLED) + + // check for settled content + const scaContentToCheck = await knex('settlementContentAggregation').transacting(trx) + .where('settlementId', settlementId) + .whereIn('participantCurrencyId', settlementAccounts.settledIdList) + .distinct('settlementWindowContentId') + const contentIdCheckList = scaContentToCheck.map(v => v.settlementWindowContentId) + const unsettledContent = await knex('settlementContentAggregation').transacting(trx) + .whereIn('settlementWindowContentId', contentIdCheckList) + .whereNot('currentStateId', enums.settlementWindowStates.SETTLED) + .distinct('settlementWindowContentId') + const unsettledContentIdList = unsettledContent.map(v => v.settlementWindowContentId) + const settledContentIdList = arrayDiff(contentIdCheckList, unsettledContentIdList) + + // persist settled content + insertPromises = [] + for (const settlementWindowContentId of settledContentIdList) { + const swcsc = { + settlementWindowContentId, + settlementWindowStateId: enums.settlementWindowStates.SETTLED, + reason: 'All content aggregation records are SETTLED' } + insertPromises.push( + knex('settlementWindowContentStateChange').transacting(trx) + .insert(swcsc) + ) } + const settlementWindowContentStateChangeIdList = (await Promise.all(insertPromises)).map(v => v[0]) + updatePromises = [] + for (const i in settlementWindowContentStateChangeIdList) { + const updatedColumns = { currentStateChangeId: settlementWindowContentStateChangeIdList[i] } + updatePromises.push( + knex('settlementWindowContent').transacting(trx) + .where('settlementWindowContentId', settledContentIdList[i]) + .update(updatedColumns) + ) + } + await Promise.all(updatePromises) + + // check for settled windows + const windowsToCheck = await knex('settlementWindowContent').transacting(trx) + .whereIn('settlementWindowContentId', settledContentIdList) + .distinct('settlementWindowId') + const windowIdCheckList = windowsToCheck.map(v => v.settlementWindowId) + const unsettledWindows = await knex('settlementWindowContent AS swc').transacting(trx) + .join('settlementWindowContentStateChange AS swcsc', 'swcsc.settlementWindowContentStateChangeId', 'swc.currentStateChangeId') + .whereIn('swc.settlementWindowId', windowIdCheckList) + .whereNot('swcsc.settlementWindowStateId', enums.settlementWindowStates.SETTLED) + .distinct('swc.settlementWindowId') + const unsettledWindowIdList = unsettledWindows.map(v => v.settlementWindowId) + const settledWindowIdList = arrayDiff(windowIdCheckList, unsettledWindowIdList) + + // persist settled windows + insertPromises = [] + for (const settlementWindowId of settledWindowIdList) { + const swsc = { + settlementWindowId, + settlementWindowStateId: enums.settlementWindowStates.SETTLED, + reason: 'All settlement window content is SETTLED' + } + insertPromises.push( + knex('settlementWindowStateChange').transacting(trx) + .insert(swsc) + ) + } + const settlementWindowStateChangeIdList = (await Promise.all(insertPromises)).map(v => v[0]) + updatePromises = [] + for (const i in settlementWindowStateChangeIdList) { + const updatedColumns = { currentStateChangeId: settlementWindowStateChangeIdList[i] } + updatePromises.push( + knex('settlementWindow').transacting(trx) + .where('settlementWindowId', settledWindowIdList[i]) + .update(updatedColumns) + ) + } + await Promise.all(updatePromises) } - // seq-settlement-6.2.5, step 30 - insertPromises = [] - for (const swsc of settlementWindowStateChange) { - insertPromises.push( - knex('settlementWindowStateChange') - .insert(swsc) - .transacting(trx) - ) - } - const settlementWindowStateChangeIdList = (await Promise.all(insertPromises)).map(v => v[0]) - // seq-settlement-6.2.5, step 33 - updatePromises = [] - for (const i in settlementWindowStateChangeIdList) { - updatePromises.push( - knex('settlementWindow') - .where('settlementWindowId', settlementWindowStateChange[i].settlementWindowId) - .update({ currentStateChangeId: settlementWindowStateChangeIdList[i] }) - .transacting(trx) + + // seq-settlement-6.2.5, step 24 + const processedContent = await knex('settlementContentAggregation AS sca').transacting(trx) + .join('settlementWindowContent AS swc', 'swc.settlementWindowContentId', 'sca.settlementWindowContentId') + .join('settlementWindowContentStateChange AS swcsc', 'swcsc.settlementWindowContentStateChangeId', 'swc.currentStateChangeId') + .join('ledgerAccountType AS lat', 'lat.ledgerAccountTypeId', 'swc.ledgerAccountTypeId') + .join('settlementWindow AS sw', 'sw.settlementWindowId', 'swc.settlementWindowId') + .join('settlementWindowStateChange AS swsc', 'swsc.settlementWindowStateChangeId', 'sw.currentStateChangeId') + .whereIn('sca.participantCurrencyId', settlementAccounts.changedIdList) + .where('sca.settlementId', settlementId) + .distinct( + 'sw.settlementWindowId', + 'swsc.settlementWindowStateId', + 'swsc.reason', + 'sw.createdDate AS createdDate1', + 'swsc.createdDate AS changedDate1', + 'swc.settlementWindowContentId', + 'swcsc.settlementWindowStateId AS state', + 'lat.name AS ledgerAccountType', + 'swc.currencyId', + 'swc.createdDate', + 'swcsc.createdDate AS changedDate' ) - } - await Promise.all(updatePromises) + .orderBy(['sw.settlementWindowId', 'swc.settlementWindowContentId']) + const settlementWindows = groupByWindowsWithContent(processedContent) + // seq-settlement-6.2.5, step post-26 let settlementStateChanged = true if (settlementData.settlementStateId === enums.settlementStates.PENDING_SETTLEMENT && settlementAccounts.pendingSettlementCount === 0) { @@ -1129,14 +1142,15 @@ const Facade = { settlementStateChanged = false } + // seq-settlement-6.2.5, step pre-27 if (settlementStateChanged) { settlementData.createdDate = transactionTimestamp - // seq-settlement-6.2.5, step 34 + // seq-settlement-6.2.5, step 27 const settlementStateChangeId = await knex('settlementStateChange') .insert(settlementData) .transacting(trx) - // seq-settlement-6.2.5, step 36 + // seq-settlement-6.2.5, step 29 await knex('settlement') .where('settlementId', settlementData.settlementId) .update({ currentStateChangeId: settlementStateChangeId }) @@ -1144,11 +1158,12 @@ const Facade = { } await trx.commit + return { id: settlementId, state: settlementData.settlementStateId, createdDate: settlementData.createdDate, - settlementWindows: settlementWindows, + settlementWindows, participants } } @@ -1230,7 +1245,7 @@ const Facade = { // seq-settlement-6.2.6, step 12 for (const sal of settlementAccountList) { // Switched to insert from batchInsert because only LAST_INSERT_ID is returned - // TODO: PoC - batchInsert + select inserted ids vs multiple inserts without select + // TODO:: PoC - batchInsert + select inserted ids vs multiple inserts without select const spcsc = { settlementParticipantCurrencyId: sal.key, settlementStateId: enums.settlementStates.ABORTED, @@ -1362,14 +1377,19 @@ const Facade = { }) }, - knexTriggerEvent: async function ({ idList, reason }, enums = {}) { + triggerSettlementEvent: async function ({ idList, reason }, settlementModel, enums = {}) { const knex = await Db.getKnex() - // Open transaction + // begin transaction return knex.transaction(async (trx) => { try { // insert new settlement const transactionTimestamp = new Date().toISOString().replace(/[TZ]/g, ' ').trim() - const settlementId = await knex('settlement').insert({ reason, createdDate: transactionTimestamp }).transacting(trx) + const settlementId = await knex('settlement').transacting(trx) + .insert({ + reason, + createdDate: transactionTimestamp, + settlementModelId: settlementModel.settlementModelId + }) const settlementSettlementWindowList = idList.map(settlementWindowId => { return { settlementId, @@ -1377,45 +1397,70 @@ const Facade = { createdDate: transactionTimestamp } }) + + // associate settlement windows with the settlement await knex.batchInsert('settlementSettlementWindow', settlementSettlementWindowList).transacting(trx) - /* let settlementTransferParticipantIdList = */ - let builder = knex - .from(knex.raw('settlementTransferParticipant (settlementId, settlementWindowId, participantCurrencyId, transferParticipantRoleTypeId, ledgerEntryTypeId, createdDate, amount)')) - .insert(function () { - this.from('settlementSettlementWindow AS ssw') - .join('transferFulfilment AS tf', 'tf.settlementWindowId', 'ssw.settlementWindowId') - .join('transferStateChange AS tsc', function () { - this.on('tsc.transferId', 'tf.transferId') - .on('tsc.transferStateId', knex.raw('?', [enums.transferStates.COMMITTED])) - }) - .join('transferParticipant AS tp', function () { - this.on('tp.transferId', 'tf.transferId') - }) - .where('ssw.settlementId', settlementId[0]) - .groupBy('ssw.settlementWindowId', 'tp.participantCurrencyId', 'tp.transferParticipantRoleTypeId', 'tp.ledgerEntryTypeId') - .select(knex.raw('? AS ??', [settlementId, 'settlementId']), - 'ssw.settlementWindowId', - 'tp.participantCurrencyId', - 'tp.transferParticipantRoleTypeId', - 'tp.ledgerEntryTypeId', - knex.raw('? AS ??', [transactionTimestamp, 'createdDate'])) - .sum('tp.amount AS amount') - }) - .transacting(trx) - await builder - builder = knex - .from(knex.raw('settlementParticipantCurrency (settlementId, participantCurrencyId, netAmount)')) + // retrieve affected settlementWindowContent + const swcIdList = await knex('settlementWindow AS sw').transacting(trx) + .join('settlementWindowStateChange AS swsc', 'swsc.settlementWindowStateChangeId', 'sw.currentStateChangeId') + .join('settlementWindowContent AS swc', 'swc.settlementWindowId', 'sw.settlementWindowId') + .join('settlementWindowContentStateChange AS swcsc', 'swcsc.settlementWindowContentStateChangeId', 'swc.currentStateChangeId') + .whereRaw(`sw.settlementWindowId IN (${idList})`) + .where('swc.ledgerAccountTypeId', settlementModel.ledgerAccountTypeId) + .where('swc.currencyId', knex.raw('COALESCE(?, swc.currencyId)', settlementModel.currencyId)) + .whereIn('swsc.settlementWindowStateId', [enums.settlementWindowStates.CLOSED, enums.settlementWindowStates.ABORTED, enums.settlementWindowStates.PENDING_SETTLEMENT]) + .whereIn('swcsc.settlementWindowStateId', [enums.settlementWindowStates.CLOSED, enums.settlementWindowStates.ABORTED]) + .distinct('swc.settlementWindowContentId') + const swcIdArray = swcIdList.map(record => record.settlementWindowContentId) + + // bind requested settlementWindowContent and settlementContentAggregation records + await knex('settlementWindowContent').transacting(trx) + .whereIn('settlementWindowContentId', swcIdArray) + .update({ settlementId }) + await knex('settlementContentAggregation').transacting(trx) + .whereIn('settlementWindowContentId', swcIdArray) + .update({ settlementId, currentStateId: enums.settlementWindowStates.PENDING_SETTLEMENT }) + + // change settlementWindowContent records state + const settlementWindowContentStateChangeList = swcIdArray.map(value => { + return { + settlementWindowContentId: value, + settlementWindowStateId: enums.settlementStates.PENDING_SETTLEMENT, + reason, + createdDate: transactionTimestamp + } + }) + let insertPromises = [] + for (const swcsc of settlementWindowContentStateChangeList) { + insertPromises.push( + knex('settlementWindowContentStateChange').transacting(trx) + .insert(swcsc) + ) + } + const settlementWindowContentStateChangeIdList = (await Promise.all(insertPromises)).map(v => v[0]) + let updatePromises = [] + for (let index = 0; index < swcIdArray.length; index++) { + updatePromises.push(await knex('settlementWindowContent').transacting(trx) + .where('settlementWindowContentId', swcIdArray[index]) + .update({ currentStateChangeId: settlementWindowContentStateChangeIdList[index] })) + } + await Promise.all(updatePromises) + + // aggregate and insert settlement net amounts + const builder = knex + .from(knex.raw('settlementParticipantCurrency (settlementId, participantCurrencyId, createdDate, netAmount)')) .insert(function () { - this.from('settlementTransferParticipant AS stp') - .whereRaw('stp.settlementId = ?', settlementId[0]) - .groupBy('stp.settlementId', 'stp.participantCurrencyId') - .select('stp.settlementId', 'stp.participantCurrencyId') - .sum('stp.amount AS netAmount') - }, 'settlementParticipantCurrencyId') + this.from('settlementContentAggregation AS sca') + .whereRaw('sca.settlementId = ?', settlementId[0]) + .groupBy('sca.settlementId', 'sca.participantCurrencyId') + .select('sca.settlementId', 'sca.participantCurrencyId', knex.raw('? AS createdDate', transactionTimestamp)) + .sum('sca.amount AS netAmount') + }) .transacting(trx) await builder + // change settlementParticipantCurrency records state const settlementParticipantCurrencyList = await knex('settlementParticipantCurrency').select('settlementParticipantCurrencyId').where('settlementId', settlementId).transacting(trx) const settlementParticipantCurrencyIdList = [] const settlementParticipantCurrencyStateChangeList = settlementParticipantCurrencyList.map(value => { @@ -1427,55 +1472,53 @@ const Facade = { createdDate: transactionTimestamp } }) - - let insertPromises = [] + insertPromises = [] for (const spcsc of settlementParticipantCurrencyStateChangeList) { insertPromises.push( - knex('settlementParticipantCurrencyStateChange') + knex('settlementParticipantCurrencyStateChange').transacting(trx) .insert(spcsc) - .transacting(trx) ) } const settlementParticipantCurrencyStateChangeIdList = (await Promise.all(insertPromises)).map(v => v[0]) - - let updatePromises = [] + updatePromises = [] for (const index in settlementParticipantCurrencyIdList) { - updatePromises.push(knex('settlementParticipantCurrency') - .transacting(trx) + updatePromises.push(knex('settlementParticipantCurrency').transacting(trx) .where('settlementParticipantCurrencyId', settlementParticipantCurrencyIdList[index]) - .update({ - currentStateChangeId: settlementParticipantCurrencyStateChangeIdList[index] - })) + .update({ currentStateChangeId: settlementParticipantCurrencyStateChangeIdList[index] })) } await Promise.all(updatePromises) - const settlementWindowStateChangeList = idList.map(value => { + + // set state of CLOSED and ABORTED windows to PENDING_SETTLEMENT, skip already in PENDING_SETTLEMENT state + const windowsStateToBeUpdatedIdList = await knex('settlementWindow AS sw').transacting(trx) + .join('settlementWindowStateChange AS swsc', 'swsc.settlementWindowStateChangeId', 'sw.currentStateChangeId') + .whereIn('sw.settlementWindowId', idList) + .whereIn('swsc.settlementWindowStateId', [enums.settlementWindowStates.CLOSED, enums.settlementWindowStates.ABORTED]) + .select('sw.settlementWindowId') + const settlementWindowStateChangeList = windowsStateToBeUpdatedIdList.map(record => { return { - settlementWindowId: value, + settlementWindowId: record.settlementWindowId, settlementWindowStateId: enums.settlementStates.PENDING_SETTLEMENT, reason, createdDate: transactionTimestamp } }) - insertPromises = [] for (const swsc of settlementWindowStateChangeList) { insertPromises.push( - knex('settlementWindowStateChange') + knex('settlementWindowStateChange').transacting(trx) .insert(swsc) - .transacting(trx) ) } const settlementWindowStateChangeIdList = (await Promise.all(insertPromises)).map(v => v[0]) - updatePromises = [] - for (let index = 0; index < idList.length; index++) { + for (let index = 0; index < settlementWindowStateChangeList.length; index++) { updatePromises.push(await knex('settlementWindow').transacting(trx) - .where('settlementWindowId', idList[index]) - .update({ - currentStateChangeId: settlementWindowStateChangeIdList[index] - })) + .where('settlementWindowId', settlementWindowStateChangeList[index].settlementWindowId) + .update({ currentStateChangeId: settlementWindowStateChangeIdList[index] })) } await Promise.all(updatePromises) + + // initiate settlement state to PENDING_SETTLEMENT const settlementStateChangeId = await knex('settlementStateChange').transacting(trx) .insert({ settlementId, diff --git a/src/models/settlement/index.js b/src/models/settlement/index.js index 7febd9d5..aaf76042 100644 --- a/src/models/settlement/index.js +++ b/src/models/settlement/index.js @@ -22,15 +22,14 @@ * Gates Foundation - Name Surname - * Georgi Georgiev - * Valentin Genev - * Deon Botha - * Rajiv Mothilal - * Miguel de Barros - + * ModusBox + - Deon Botha + - Georgi Georgiev + - Miguel de Barros + - Rajiv Mothilal + - Valentin Genev -------------- ******/ - 'use strict' const settlementFacade = require('./facade') @@ -41,7 +40,7 @@ const settlementParticipantCurrencyModel = require('./settlementParticipantCurre module.exports = { create: settlementModel.create, - triggerEvent: settlementFacade.knexTriggerEvent, + triggerSettlementEvent: settlementFacade.triggerSettlementEvent, getByParams: settlementFacade.getByParams, getById: settlementFacade.getById, putById: settlementFacade.putById, diff --git a/src/server.js b/src/models/settlement/settlementModel.js similarity index 85% rename from src/server.js rename to src/models/settlement/settlementModel.js index c0fa2695..68e41b87 100644 --- a/src/server.js +++ b/src/models/settlement/settlementModel.js @@ -18,12 +18,18 @@ * Gates Foundation - Name Surname - * Georgi Georgiev + * ModusBox + - Georgi Georgiev -------------- ******/ - 'use strict' -const Setup = require('./setup') +const Db = require('../../lib/db') + +const getByName = async (name) => { + return Db.settlementModel.findOne({ name, isActive: 1 }) +} -module.exports = Setup.initialize() +module.exports = { + getByName +} diff --git a/src/models/settlementWindow/facade.js b/src/models/settlementWindow/facade.js index f3b7ad70..0984d58e 100644 --- a/src/models/settlementWindow/facade.js +++ b/src/models/settlementWindow/facade.js @@ -18,16 +18,17 @@ * Gates Foundation - Name Surname - * Georgi Georgiev - * Valentin Genev - * Deon Botha + * ModusBox + - Deon Botha + - Georgi Georgiev + - Valentin Genev -------------- ******/ - 'use strict' const Db = require('../../lib/db') const ErrorHandler = require('@mojaloop/central-services-error-handling') +const Enum = require('@mojaloop/central-services-shared').Enum const Facade = { getById: async function ({ settlementWindowId }) { @@ -46,28 +47,44 @@ const Facade = { }) }, - getByListOfIds: async function (listOfIds) { + getTransfersCount: async function ({ settlementWindowId }) { + return Db.transferFulfilment.query(builder => { + return builder + .count('* as cnt') + .first() + .where('transferFulfilment.settlementWindowId', settlementWindowId) + }) + }, + + getByListOfIds: async function (listOfIds, settlementModel, winStateEnum) { + const knex = await Db.getKnex() return Db.settlementWindow.query(builder => { - const build = builder - .leftJoin('settlementWindowStateChange AS swsc', 'swsc.settlementWindowStateChangeId', 'settlementWindow.currentStateChangeId') - .select( + const b = builder + .join('settlementWindowStateChange AS swsc', 'swsc.settlementWindowStateChangeId', 'settlementWindow.currentStateChangeId') + .join('settlementWindowContent AS swc', 'swc.settlementWindowId', 'settlementWindow.settlementWindowId') + .join('settlementWindowContentStateChange AS swcsc', 'swcsc.settlementWindowContentStateChangeId', 'swc.currentStateChangeId') + .whereRaw(`settlementWindow.settlementWindowId IN (${listOfIds})`) + .where('swc.ledgerAccountTypeId', settlementModel.ledgerAccountTypeId) + .where('swc.currencyId', knex.raw('COALESCE(?, swc.currencyId)', settlementModel.currencyId)) + .whereIn('swsc.settlementWindowStateId', [winStateEnum.CLOSED, winStateEnum.ABORTED, winStateEnum.PENDING_SETTLEMENT]) + .whereIn('swcsc.settlementWindowStateId', [winStateEnum.CLOSED, winStateEnum.ABORTED]) + .distinct( 'settlementWindow.settlementWindowId', - 'swsc.settlementWindowStateId as state', - 'swsc.reason as reason', - 'settlementWindow.createdDate as createdDate', - 'swsc.createdDate as changedDate' + 'swsc.settlementWindowStateId as state' ) - .whereRaw(`settlementWindow.settlementWindowId IN (${listOfIds})`) - return build + return b }) }, getByParams: async function ({ query }) { - const { participantId, state, fromDateTime, toDateTime } = query + const { participantId, state, fromDateTime, toDateTime, currency } = query return Db.settlementWindow.query(builder => { if (!participantId) { const b = builder .leftJoin('settlementWindowStateChange AS swsc', 'swsc.settlementWindowStateChangeId', 'settlementWindow.currentStateChangeId') + .leftJoin('transferFulfilment AS tf', 'tf.settlementWindowId', 'settlementWindow.settlementWindowId') + .leftJoin('transferParticipant AS tp', 'tp.transferId', 'tf.transferId') + .leftJoin('participantCurrency AS pc', 'pc.participantCurrencyId', 'tp.participantCurrencyId') .select( 'settlementWindow.settlementWindowId', 'swsc.settlementWindowStateId as state', @@ -79,6 +96,7 @@ const Facade = { if (state) { b.where('swsc.settlementWindowStateId', state) } if (fromDateTime) { b.where('settlementWindow.createdDate', '>=', fromDateTime) } if (toDateTime) { b.where('settlementWindow.createdDate', '<=', toDateTime) } + if (currency) { b.where('pc.currencyId', currency) } return b } else { const b = builder @@ -98,25 +116,29 @@ const Facade = { if (state) { b.where('swsc.settlementWindowStateId', state) } if (fromDateTime) { b.where('settlementWindow.createdDate', '>=', fromDateTime) } if (toDateTime) { b.where('settlementWindow.createdDate', '<=', toDateTime) } + if (currency) { b.where('pc.currencyId', currency) } return b } }) }, - close: async function ({ settlementWindowId, state, reason }, enums = {}) { + process: async function ({ settlementWindowId, reason }, enums = {}) { const knex = await Db.getKnex() const settlementWindowCurrentState = await Facade.getById({ settlementWindowId }) + const transfersCount = (await Facade.getTransfersCount({ settlementWindowId })).cnt if (!settlementWindowCurrentState) { throw ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.VALIDATION_ERROR, `Window ${settlementWindowId} does not exist`) } if (settlementWindowCurrentState && settlementWindowCurrentState.state !== enums.OPEN) { throw ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.VALIDATION_ERROR, `Window ${settlementWindowId} is not open`) + } if (transfersCount === 0) { + throw ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.VALIDATION_ERROR, `Window ${settlementWindowId} is empty`) } else { return knex.transaction(async (trx) => { try { const transactionTimestamp = new Date() const settlementWindowStateChangeId = await knex('settlementWindowStateChange').transacting(trx) .insert({ - settlementWindowStateId: enums[state.toUpperCase()], + settlementWindowStateId: enums.PROCESSING, reason, settlementWindowId, createdDate: transactionTimestamp @@ -148,6 +170,111 @@ const Facade = { }) } }, + + close: async function (settlementWindowId, reason) { + const knex = await Db.getKnex() + const settlementWindowCurrentState = await Facade.getById({ settlementWindowId }) + if (!settlementWindowCurrentState) { + throw ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.VALIDATION_ERROR, `Window ${settlementWindowId} does not exist`) + } if (settlementWindowCurrentState && settlementWindowCurrentState.state !== Enum.Settlements.SettlementWindowState.PROCESSING) { + throw ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.VALIDATION_ERROR, `Window ${settlementWindowId} is not in processing state`) + } else { + return knex.transaction(async (trx) => { + try { + const transactionTimestamp = new Date() + + // Insert settlementWindowContent + let builder = knex + .from(knex.raw('settlementWindowContent (settlementWindowId, ledgerAccountTypeId, currencyId, createdDate)')) + .insert(function () { + this.from('transferFulfilment AS tf') + .join('transferParticipant AS tp', 'tp.transferId', 'tf.transferId') + .join('participantCurrency AS pc', 'pc.participantCurrencyId', 'tp.participantCurrencyId') + .where('tf.settlementWindowId', settlementWindowId) + .distinct('tf.settlementWindowId', 'pc.ledgerAccountTypeId', 'pc.currencyId', + knex.raw('? AS ??', [transactionTimestamp, 'createdDate'])) + }) + .transacting(trx) + await builder + + // Insert settlementContentAggregation + builder = knex + .from(knex.raw('settlementContentAggregation (settlementWindowContentId, participantCurrencyId, transferParticipantRoleTypeId, ledgerEntryTypeId, currentStateId, createdDate, amount)')) + .insert(function () { + this.from('transferFulfilment AS tf') + .join('transferParticipant AS tp', 'tp.transferId', 'tf.transferId') + .join('participantCurrency AS pc', 'pc.participantCurrencyId', 'tp.participantCurrencyId') + .join('settlementWindowContent AS swc', function () { + this.on('swc.settlementWindowId', 'tf.settlementWindowId') + .on('swc.ledgerAccountTypeId', 'pc.ledgerAccountTypeId') + .on('swc.currencyId', 'pc.currencyId') + }) + .where('tf.settlementWindowId', settlementWindowId) + .groupBy('swc.settlementWindowContentId', 'pc.participantCurrencyId', 'tp.transferParticipantRoleTypeId', 'tp.ledgerEntryTypeId') + .select('swc.settlementWindowContentId', 'pc.participantCurrencyId', 'tp.transferParticipantRoleTypeId', 'tp.ledgerEntryTypeId', + knex.raw('? AS ??', [Enum.Settlements.SettlementWindowState.CLOSED, 'settlementWindowStateId']), + knex.raw('? AS ??', [transactionTimestamp, 'createdDate'])) + .sum('tp.amount AS amount') + }) + .transacting(trx) + await builder + + // Insert settlementWindowContentStateChange + builder = knex + .from(knex.raw('settlementWindowContentStateChange (settlementWindowContentId, settlementWindowStateId, reason, createdDate)')) + .insert(function () { + this.from('settlementWindowContent AS swc') + .where('swc.settlementWindowId', settlementWindowId) + .select('swc.settlementWindowContentId', + knex.raw('? AS ??', [Enum.Settlements.SettlementWindowState.CLOSED, 'settlementWindowStateId']), + knex.raw('? AS ??', [reason, 'reason']), + knex.raw('? AS ??', [transactionTimestamp, 'createdDate'])) + }) + .transacting(trx) + await builder + + // Update settlementWindowContent pointers to current states, inserted by previous command + const settlementWindowContentStateChangeList = await knex('settlementWindowContentStateChange AS swcsc') + .join('settlementWindowContent AS swc', 'swc.settlementWindowContentId', 'swcsc.settlementWindowContentId') + .select('swc.settlementWindowContentId', 'swcsc.settlementWindowContentStateChangeId') + .where('swc.settlementWindowId', settlementWindowId) + .transacting(trx) + const updatePromises = [] + for (const i in settlementWindowContentStateChangeList) { + const updatedColumns = { currentStateChangeId: settlementWindowContentStateChangeList[i].settlementWindowContentStateChangeId } + updatePromises.push( + knex('settlementWindowContent') + .where('settlementWindowContentId', settlementWindowContentStateChangeList[i].settlementWindowContentId) + .update(updatedColumns) + .transacting(trx) + ) + } + await Promise.all(updatePromises) + + const settlementWindowStateChangeId = await knex('settlementWindowStateChange').transacting(trx) + .insert({ + settlementWindowStateId: Enum.Settlements.SettlementWindowState.CLOSED, + reason, + settlementWindowId, + createdDate: transactionTimestamp + }) + await knex('settlementWindow').transacting(trx) + .where({ settlementWindowId }) + .update({ currentStateChangeId: settlementWindowStateChangeId }) + + await trx.commit + return true + } catch (err) { + await trx.rollback + throw ErrorHandler.Factory.reformatFSPIOPError(err) + } + }) + .catch((err) => { + throw ErrorHandler.Factory.reformatFSPIOPError(err) + }) + } + }, + getBySettlementId: async function ({ settlementId }) { return Db.settlementSettlementWindow.query(builder => { return builder diff --git a/src/models/settlementWindow/index.js b/src/models/settlementWindow/index.js index a4c17276..8a321451 100644 --- a/src/models/settlementWindow/index.js +++ b/src/models/settlementWindow/index.js @@ -18,14 +18,15 @@ * Gates Foundation - Name Surname - * Georgi Georgiev - * Valentin Genev - * Deon Botha - * Rajiv Mothilal - * Miguel de Barros - + * ModuxBox + - Deon Botha + - Georgi Georgiev + - Miguel de Barros + - Rajiv Mothilal + - Valentin Genev -------------- ******/ +'use strict' const Facade = require('./facade') const settlementWindowStateChange = require('./settlementWindowStateChange') @@ -33,8 +34,9 @@ const settlementWindowStateChange = require('./settlementWindowStateChange') module.exports = { getById: Facade.getById, getByParams: Facade.getByParams, + process: Facade.process, close: Facade.close, getByListOfIds: Facade.getByListOfIds, getBySettlementId: Facade.getBySettlementId, - createSettlementWindow: settlementWindowStateChange.create + createSettlementWindowState: settlementWindowStateChange.create } diff --git a/src/models/settlementWindow/settlementWindowStateChange.js b/src/models/settlementWindow/settlementWindowStateChange.js index 9287766d..95cd5d37 100644 --- a/src/models/settlementWindow/settlementWindowStateChange.js +++ b/src/models/settlementWindow/settlementWindowStateChange.js @@ -18,12 +18,12 @@ * Gates Foundation - Name Surname - * Georgi Georgiev - * Valentin Genev - * Deon Botha + * ModusBox + - Deon Botha + - Georgi Georgiev + - Valentin Genev -------------- ******/ - 'use strict' const Db = require('../../lib/db') diff --git a/src/models/settlementWindowContent/facade.js b/src/models/settlementWindowContent/facade.js new file mode 100644 index 00000000..171e57da --- /dev/null +++ b/src/models/settlementWindowContent/facade.js @@ -0,0 +1,66 @@ +/***** + License + -------------- + Copyright © 2017 Bill & Melinda Gates Foundation + The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + Contributors + -------------- + This is the official list of the Mojaloop project contributors for this file. + Names of the original copyright holders (individuals or organizations) + should be listed with a '*' in the first column. People who have + contributed from an organization can be listed under the organization + that actually holds the copyright for their contributions (see the + Gates Foundation organization for an example). Those individuals should have + their names indented and be marked with a '-'. Email address can be added + optionally within square brackets . + * Gates Foundation + - Name Surname + + * ModusBox + - Georgi Georgiev + -------------- + ******/ +'use strict' + +const Db = require('../../lib/db') + +const Facade = { + getApplicableByWindowIdList: async function (idList, settlementModel, winStateEnum) { + const knex = await Db.getKnex() + return Db.settlementWindow.query(builder => { + const b = builder + .join('settlementWindowStateChange AS swsc', 'swsc.settlementWindowStateChangeId', 'settlementWindow.currentStateChangeId') + .join('settlementWindowContent AS swc', 'swc.settlementWindowId', 'settlementWindow.settlementWindowId') + .join('settlementWindowContentStateChange AS swcsc', 'swcsc.settlementWindowContentStateChangeId', 'swc.currentStateChangeId') + .whereRaw(`settlementWindow.settlementWindowId IN (${idList})`) + .where('swc.ledgerAccountTypeId', settlementModel.ledgerAccountTypeId) + .where('swc.currencyId', knex.raw('COALESCE(?, swc.currencyId)', settlementModel.currencyId)) + .whereIn('swsc.settlementWindowStateId', [winStateEnum.CLOSED, winStateEnum.ABORTED, winStateEnum.PENDING_SETTLEMENT]) + .whereIn('swcsc.settlementWindowStateId', [winStateEnum.CLOSED, winStateEnum.ABORTED]) + .distinct('swc.settlementWindowContentId') + return b + }) + }, + getBySettlementId: async (id) => { + const knex = await Db.getKnex() + return knex('settlementWindowContent AS swc') + .join('settlementWindowContentStateChange AS swcsc', 'swcsc.settlementWindowContentStateChangeId', 'swc.currentStateChangeId') + .join('ledgerAccountType AS lat', 'lat.ledgerAccountTypeId', 'swc.ledgerAccountTypeId') + .where('swc.settlementId', id) + .select('swc.settlementWindowContentId AS id', 'swc.settlementWindowId', 'swcsc.settlementWindowStateId AS state', + 'lat.name AS ledgerAccountType', 'swc.currencyId', 'swc.createdDate', 'swcsc.createdDate AS changedDate') + }, + getBySettlementWindowId: async (id) => { + const knex = await Db.getKnex() + return knex('settlementWindowContent AS swc') + .join('settlementWindowContentStateChange AS swcsc', 'swcsc.settlementWindowContentStateChangeId', 'swc.currentStateChangeId') + .join('ledgerAccountType AS lat', 'lat.ledgerAccountTypeId', 'swc.ledgerAccountTypeId') + .where('swc.settlementWindowId', id) + .select('swc.settlementWindowContentId AS id', 'swcsc.settlementWindowStateId AS state', + 'lat.name AS ledgerAccountType', 'swc.currencyId', 'swc.createdDate', 'swcsc.createdDate AS changedDate', 'swc.settlementId') + } +} + +module.exports = Facade diff --git a/src/models/settlementWindowContent/index.js b/src/models/settlementWindowContent/index.js new file mode 100644 index 00000000..4b264e85 --- /dev/null +++ b/src/models/settlementWindowContent/index.js @@ -0,0 +1,35 @@ +/***** + License + -------------- + Copyright © 2017 Bill & Melinda Gates Foundation + The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + Contributors + -------------- + This is the official list of the Mojaloop project contributors for this file. + Names of the original copyright holders (individuals or organizations) + should be listed with a '*' in the first column. People who have + contributed from an organization can be listed under the organization + that actually holds the copyright for their contributions (see the + Gates Foundation organization for an example). Those individuals should have + their names indented and be marked with a '-'. Email address can be added + optionally within square brackets . + * Gates Foundation + - Name Surname + + * ModuxBox + - Georgi Georgiev + -------------- + ******/ +'use strict' + +const Facade = require('./facade') +const SettlementWindowContentStateChangeModel = require('./settlementWindowContentStateChange') + +module.exports = { + createSettlementWindowContentState: SettlementWindowContentStateChangeModel.create, + getApplicableByWindowIdList: Facade.getApplicableByWindowIdList, + getBySettlementId: Facade.getBySettlementId, + getBySettlementWindowId: Facade.getBySettlementWindowId +} diff --git a/src/models/settlementWindowContent/settlementWindowContentStateChange.js b/src/models/settlementWindowContent/settlementWindowContentStateChange.js new file mode 100644 index 00000000..c551be25 --- /dev/null +++ b/src/models/settlementWindowContent/settlementWindowContentStateChange.js @@ -0,0 +1,49 @@ +/***** + License + -------------- + Copyright © 2017 Bill & Melinda Gates Foundation + The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + Contributors + -------------- + This is the official list of the Mojaloop project contributors for this file. + Names of the original copyright holders (individuals or organizations) + should be listed with a '*' in the first column. People who have + contributed from an organization can be listed under the organization + that actually holds the copyright for their contributions (see the + Gates Foundation organization for an example). Those individuals should have + their names indented and be marked with a '-'. Email address can be added + optionally within square brackets . + * Gates Foundation + - Name Surname + + * ModusBox + - Georgi Georgiev + -------------- + ******/ +'use strict' + +const Db = require('../../lib/db') + +const create = async ({ settlementWindowContentId, state, reason }, enums = {}) => { + return Db.settlementWindowContentStateChange.insert({ + settlementWindowContentId, + settlementWindowStateId: enums[state.toUpperCase()], + reason + }) +} + +const getBySettlementWindowContentId = async (id) => { + const knex = await Db.getKnex() + return knex('settlementWindowContentStateChange') + .where('settlementWindowContentId', id) + .orderBy('settlementWindowContentStateChangeId', 'desc') + .select('*') + .first() +} + +module.exports = { + create, + getBySettlementWindowContentId +} diff --git a/src/setup.js b/src/setup.js deleted file mode 100644 index 30a6a761..00000000 --- a/src/setup.js +++ /dev/null @@ -1,141 +0,0 @@ -/***** - License - -------------- - Copyright © 2017 Bill & Melinda Gates Foundation - The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. - Contributors - -------------- - This is the official list of the Mojaloop project contributors for this file. - Names of the original copyright holders (individuals or organizations) - should be listed with a '*' in the first column. People who have - contributed from an organization can be listed under the organization - that actually holds the copyright for their contributions (see the - Gates Foundation organization for an example). Those individuals should have - their names indented and be marked with a '-'. Email address can be added - optionally within square brackets . - * Gates Foundation - - Name Surname - - * Georgi Georgiev - * Valentin Genev - * Deon Botha - * Rajiv Mothilal - * Miguel de Barros - -------------- - ******/ - -'use strict' - -const Hapi = require('@hapi/hapi') -const HapiOpenAPI = require('hapi-openapi') -const Path = require('path') -const Boom = require('@hapi/boom') -const ErrorHandling = require('@mojaloop/central-services-error-handling') -const Db = require('./lib/db') -const Enums = require('./models/lib/enums') -const Config = require('./lib/config') -const Plugins = require('./plugins') - -// TODO: add to common config -const openAPIOptions = { - api: Path.resolve(__dirname, './interface/swagger.json'), - handlers: Path.resolve(__dirname, './handlers') -} - -const defaultConfig = { - port: Config.PORT, - routes: { - validate: { - options: ErrorHandling.validateRoutes(), - failAction: async (request, h, err) => { - throw Boom.boomify(err) - } - }, - payload: { - parse: true, - output: 'stream' - } - }, - cache: [ - { - provider: { - constructor: require('@hapi/catbox-memory'), - options: { - partition: 'cache' - } - }, - name: 'memCache' - } - ] -} - -const getEnums = (id) => { - return Enums[id]() -} - -async function connectDatabase () { - return Db.connect(Config.DATABASE) -} - -const createServer = async function (config, openAPIPluginOptions) { - try { - const server = new Hapi.Server(config) - await connectDatabase() - await server.register([{ - plugin: HapiOpenAPI, - options: openAPIPluginOptions - }, - { - plugin: require('./utils/logger-plugin') - }]) - - server.method({ - name: 'enums', - method: getEnums, - options: { - cache: { - cache: 'memCache', - expiresIn: 20 * 1000, - generateTimeout: 30 * 1000 - } - } - }) - - server.ext([ - { - type: 'onPreHandler', - method: (request, h) => { - server.log('request', request) - return h.continue - } - } - ]) - await Plugins.registerPlugins(server) - await server.start() - return server - } catch (e) { - console.error(e) - } -} - -const initialize = async (config = defaultConfig, openAPIPluginOptions = openAPIOptions) => { - const server = await createServer(config, openAPIPluginOptions) - if (server) { - try { - server.plugins.openapi.setHost(server.info.host + ':' + server.info.port) - server.log('info', `Server running on ${server.info.host}:${server.info.port}`) - return server - } catch (e) { - server.log('error', e.message) - } - } -} - -module.exports = { - initialize, - __testonly__: { - getEnums - } -} diff --git a/src/plugins.js b/src/shared/plugins.js similarity index 95% rename from src/plugins.js rename to src/shared/plugins.js index aeb24cc2..eccf4da6 100644 --- a/src/plugins.js +++ b/src/shared/plugins.js @@ -17,12 +17,14 @@ optionally within square brackets . * Gates Foundation - Name Surname + + * ModusBox + - Georgi Georgiev -------------- ******/ - 'use strict' -const Package = require('../package') +const Package = require('../../package') const Inert = require('@hapi/inert') const Vision = require('@hapi/vision') const Blipp = require('blipp') diff --git a/src/shared/setup.js b/src/shared/setup.js new file mode 100644 index 00000000..4c28db1c --- /dev/null +++ b/src/shared/setup.js @@ -0,0 +1,212 @@ +/***** + License + -------------- + Copyright © 2017 Bill & Melinda Gates Foundation + The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + Contributors + -------------- + This is the official list of the Mojaloop project contributors for this file. + Names of the original copyright holders (individuals or organizations) + should be listed with a '*' in the first column. People who have + contributed from an organization can be listed under the organization + that actually holds the copyright for their contributions (see the + Gates Foundation organization for an example). Those individuals should have + their names indented and be marked with a '-'. Email address can be added + optionally within square brackets . + * Gates Foundation + - Name Surname + + * ModusBox + - Deon Botha + - Georgi Georgiev + - Miguel de Barros + - Rajiv Mothilal + - Valentin Genev + -------------- + ******/ +'use strict' + +const Boom = require('@hapi/boom') +const Config = require('../lib/config') +const Db = require('../lib/db') +const Enums = require('../models/lib/enums') +const ErrorHandling = require('@mojaloop/central-services-error-handling') +const Hapi = require('@hapi/hapi') +const Logger = require('@mojaloop/central-services-logger') +const Plugins = require('./plugins') +const RegisterHandlers = require('../handlers/register') + +const getEnums = (id) => { + return Enums[id]() +} + +async function connectDatabase () { + return Db.connect(Config.DATABASE) +} + +const createServer = async function (port, modules) { + try { + const server = new Hapi.Server({ + port, + routes: { + validate: { + options: ErrorHandling.validateRoutes(), + failAction: async (request, h, err) => { + throw Boom.boomify(err) + } + }, + payload: { + parse: true, + output: 'stream' + } + }, + cache: [ + { + provider: { + constructor: require('@hapi/catbox-memory'), + options: { + partition: 'cache' + } + }, + name: 'memCache' + } + ] + }) + await connectDatabase() + + server.method({ + name: 'enums', + method: getEnums, + options: { + cache: { + cache: 'memCache', + expiresIn: 20 * 1000, + generateTimeout: 30 * 1000 + } + } + }) + + server.ext([ + { + type: 'onPreHandler', + method: (request, h) => { + server.log('request', request) + return h.continue + } + } + ]) + await Plugins.registerPlugins(server) + await server.register(modules) + await server.start() + + try { + server.plugins.openapi.setHost(server.info.host + ':' + server.info.port) + server.log('info', `Server running on ${server.info.host}:${server.info.port}`) + return server + } catch (e) { + server.log('error', e.message) + throw e + } + } catch (e) { + console.error(e) + } +} + +/** + * @function createHandlers + * + * @description Create method to register specific Handlers specified by the Module list as part of the Setup process + * + * @typedef handler + * @type {Object} + * @property {string} type The type of Handler to be registered + * @property {boolean} enabled True|False to indicate if the Handler should be registered + * @property {string[]} [fspList] List of FSPs to be registered + * + * @param {handler[]} handlers List of Handlers to be registered + * @returns {Promise} Returns true if Handlers were registered + */ +const createHandlers = async (handlers) => { + let handlerIndex + const registerdHandlers = { + connection: {}, + register: {}, + ext: {}, + start: new Date(), + info: {}, + handlers: handlers + } + + for (handlerIndex in handlers) { + var handler = handlers[handlerIndex] + if (handler.enabled) { + Logger.info(`Handler Setup - Registering ${JSON.stringify(handler)}!`) + switch (handler.type) { + case 'settlementwindow': + await RegisterHandlers.settlementWindow.registerSettlementWindowHandler() + break + default: + var error = `Handler Setup - ${JSON.stringify(handler)} is not a valid handler to register!` + Logger.error(error) + throw ErrorHandling.Factory.reformatFSPIOPError(error) + } + } + } + + return registerdHandlers +} + +/** + * @function initialize + * + * @description Setup method for API, Admin and Handlers. Note that the Migration scripts are called before connecting to the database to ensure all new tables are loaded properly. + * + * @typedef handler + * @type {Object} + * @property {string} type The type of Handler to be registered + * @property {boolean} enabled True|False to indicate if the Handler should be registered + * @property {string[]} [fspList] List of FSPs to be registered + * + * @param {string} service Name of service to start. Available choices are 'api', 'admin', 'handler' + * @param {number} port Port to start the HTTP Server on + * @param {object[]} modules List of modules to be loaded by the HTTP Server + * @param {boolean} runMigrations True to run Migration script, false to ignore them, only applicable for service types that are NOT 'handler' + * @param {boolean} runHandlers True to start Handlers, false to ignore them + * @param {handler[]} handlers List of Handlers to be registered + * @returns {object} Returns HTTP Server object + */ +const initialize = async function ({ service, port, modules = [], runHandlers = false, handlers = [] }) { + let server + switch (service) { + case 'api': + server = await createServer(port, modules) + break + case 'handler': + if (!Config.HANDLERS_API_DISABLED) { + server = await createServer(port, modules) + } + break + default: + Logger.error(`No valid service type ${service} found!`) + throw ErrorHandling.Factory.createFSPIOPError(ErrorHandling.Enums.FSPIOPErrorCodes.VALIDATION_ERROR, `No valid service type ${service} found!`) + } + if (runHandlers) { + if (Array.isArray(handlers) && handlers.length > 0) { + await createHandlers(handlers) + } else { + await RegisterHandlers.registerAllHandlers() + } + } + + return server +} + +module.exports = { + initialize, + createServer, + __testonly__: { + getEnums + } +} diff --git a/src/utils/logger-plugin.js b/src/utils/logger-plugin.js deleted file mode 100644 index fb709a9e..00000000 --- a/src/utils/logger-plugin.js +++ /dev/null @@ -1,64 +0,0 @@ -/***** - License - -------------- - Copyright © 2017 Bill & Melinda Gates Foundation - The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. - Contributors - -------------- - This is the official list of the Mojaloop project contributors for this file. - Names of the original copyright holders (individuals or organizations) - should be listed with a '*' in the first column. People who have - contributed from an organization can be listed under the organization - that actually holds the copyright for their contributions (see the - Gates Foundation organization for an example). Those individuals should have - their names indented and be marked with a '-'. Email address can be added - optionally within square brackets . - * Gates Foundation - * Georgi Georgiev - * Valentin Genev - * Deon Botha - * Rajiv Mothilal - * Miguel de Barros - - -------------- - ******/ - -const Logger = require('@mojaloop/central-services-logger') -const checkEmpty = require('./truthyProperty') -module.exports.plugin = { - name: 'logger-plugin', - register: async function (server) { - server.events.on('log', function (event) { - if (event.error) { - event.data = event.error - } - if (Array.isArray(event.tags) && event.tags.length === 1 && event.tags[0]) { - if (event.tags[0] !== 'info') { - if (!(event.data instanceof Error)) { - Logger.info(`::::::: ${event.tags[0].toUpperCase()} :::::::`) - if (event.tags[0] === 'request') { - const request = event.data - Logger.info(`:: ${request.method.toUpperCase()} ${request.path}`) - checkEmpty(request.payload) && Logger.info(`:: Payload: ${JSON.stringify(request.payload)}`) - checkEmpty(request.params) && Logger.info(`:: Params: ${JSON.stringify(request.params)}`) - checkEmpty(request.query) && Logger.info(`:: Query: ${JSON.stringify(request.query)}`) - } else if (event.tags[0] === 'response') { - Logger.info(`:: Payload: \n${JSON.stringify(event.data.source, null, 2)}`) - } else { - Logger.info(`::::::: ${event.tags[0].toUpperCase()} :::::::`) - Logger.info(event.data) - } - } else { - const error = event.data - Logger.info(`::::::: ${event.tags[0].toUpperCase()} :::::::\n ${error.stack}`) - } - Logger.info(`::: END OF ${event.tags[0].toUpperCase()} ::::`) - } else { - Logger.info(event.data) - } - } - }) - } -} diff --git a/test/integration-config-centralledger.json b/test/integration-config-centralledger.json index bc3ae6a0..8cd5fcfa 100644 --- a/test/integration-config-centralledger.json +++ b/test/integration-config-centralledger.json @@ -47,7 +47,7 @@ }, "ERROR_HANDLING": { "includeCauseExtension": true, - "truncateCause": false + "truncateExtensions": false }, "HANDLERS": { "DISABLED": false, @@ -73,7 +73,10 @@ }, "config": { "timeout": 5000, - "prefix": "moja_cl_" + "prefix": "moja_cl_", + "defaultLabels": { + "serviceName": "central-service" + } } } }, @@ -249,29 +252,6 @@ } } }, - "REJECT": { - "config": { - "options": { - "mode": 2, - "batchSize": 1, - "pollFrequency": 10, - "recursiveTimeout": 100, - "messageCharset": "utf8", - "messageAsJSON": true, - "sync": true, - "consumeTimeout": 1000 - }, - "rdkafkaConf": { - "client.id": "cl-con-transfer-reject", - "group.id": "cl-group-transfer-reject", - "metadata.broker.list": "kafka-int:9092", - "socket.keepalive.enable": true - }, - "topicConf": { - "auto.offset.reset": "earliest" - } - } - }, "POSITION": { "config": { "options": { @@ -380,24 +360,6 @@ } } }, - "REJECT": { - "config": { - "options": { - "messageCharset": "utf8" - }, - "rdkafkaConf": { - "metadata.broker.list": "kafka-int:9092", - "client.id": "cl-prod-transfer-reject", - "event_cb": true, - "dr_cb": true, - "socket.keepalive.enable": true, - "queue.buffering.max.messages": 10000000 - }, - "topicConf": { - "request.required.acks": "all" - } - } - }, "POSITION": { "config": { "options": { diff --git a/test/integration-config-centralsettlement.json b/test/integration-config-centralsettlement.json index 440b9e04..10c31fba 100644 --- a/test/integration-config-centralsettlement.json +++ b/test/integration-config-centralsettlement.json @@ -18,11 +18,21 @@ "CREATE_RETRY_INTERVAL_MILLIS": 200, "DEBUG": false }, + "WINDOW_AGGREGATION": { + "RETRY_COUNT": 3, + "RETRY_INTERVAL": 3000 + }, "TRANSFER_VALIDITY_SECONDS": "432000", "HUB_PARTICIPANT": { "ID": 1, "NAME": "Hub" }, + "HANDLERS": { + "DISABLED": false, + "API": { + "DISABLED": false + } + }, "KAFKA": { "TOPIC_TEMPLATES": { "GENERAL_TOPIC_TEMPLATE": { @@ -30,6 +40,33 @@ "REGEX": "topic-(.*)-(.*)" } }, + "CONSUMER": { + "SETTLEMENTWINDOW": { + "CLOSE": { + "config": { + "options": { + "mode": 2, + "batchSize": 1, + "pollFrequency": 10, + "recursiveTimeout": 100, + "messageCharset": "utf8", + "messageAsJSON": true, + "sync": true, + "consumeTimeout": 1000 + }, + "rdkafkaConf": { + "client.id": "cs-con-setlementwindow-close", + "group.id": "cs-group-setlementwindow-close", + "metadata.broker.list": "db-int:9092", + "socket.keepalive.enable": true + }, + "topicConf": { + "auto.offset.reset": "earliest" + } + } + } + } + }, "PRODUCER": { "NOTIFICATION": { "EVENT": { @@ -56,6 +93,26 @@ } } } + }, + "SETTLEMENTWINDOW": { + "CLOSE": { + "config": { + "options": { + "messageCharset": "utf8" + }, + "rdkafkaConf": { + "metadata.broker.list": "kafka-int:9092", + "client.id": "cs-prod-setlementwindow-close", + "event_cb": true, + "dr_cb": true, + "socket.keepalive.enable": true, + "queue.buffering.max.messages": 10000000 + }, + "topicConf": { + "request.required.acks": "all" + } + } + } } } } diff --git a/test/integration/handlers/health.test.js b/test/integration/handlers/health.test.js index c85def43..5d92584b 100644 --- a/test/integration/handlers/health.test.js +++ b/test/integration/handlers/health.test.js @@ -34,7 +34,7 @@ const { createRequest, unwrapResponse } = require('../../util') -const healthHandler = require('../../../src/handlers/health') +const healthHandler = require('../../../src/api/handlers/health') Test('Health Handler', async handlersTest => { await handlersTest.test('registerAllHandlers should', async registerAllHandlers => { @@ -62,7 +62,8 @@ Test('Health Handler', async handlersTest => { } const expectedStatus = 200 const expectedServices = [ - { name: 'datastore', status: 'OK' } + { name: 'datastore', status: 'OK' }, + { name: 'broker', status: 'OK' } ] // Act diff --git a/test/integration/helpers/models.js b/test/integration/helpers/models.js index 8db641d7..46e58c85 100644 --- a/test/integration/helpers/models.js +++ b/test/integration/helpers/models.js @@ -18,7 +18,8 @@ * Gates Foundation - Name Surname - * Georgi Georgiev + * ModusBox + - Georgi Georgiev -------------- ******/ @@ -28,5 +29,34 @@ const Db = require('../../../src/lib/db') module.exports = { getTransferParticipantsByTransferId: async function (transferId) { return Db.transferParticipant.find({ transferId }) + }, + settlementModel: { + create: async (record) => { + return Db.settlementModel.insert(record) + } + }, + settlementWindowContent: { + getById: async (id) => { + return Db.settlementWindowContent.findOne({ settlementWindowContentId: id }) + }, + getByParams: async (params) => { + return Db.settlementWindowContent.find(params) + } + }, + settlementWindowContentStateChange: { + getBySettlementWindowContentId: async (id) => { + return Db.settlementWindowContentStateChange.query(async builder => { + return builder + .where({ settlementWindowContentId: id }) + .select('*') + .orderBy('settlementWindowContentStateChangeId', 'desc') + .first() + }) + } + }, + settlementWindowContentAggregation: { + getBySettlementWindowContentId: async (id) => { + return Db.settlementContentAggregation.find({ settlementWindowContentId: id }) + } } } diff --git a/test/integration/helpers/transferData.js b/test/integration/helpers/transferData.js index 0cf7f23a..3fba0201 100644 --- a/test/integration/helpers/transferData.js +++ b/test/integration/helpers/transferData.js @@ -18,7 +18,8 @@ * Gates Foundation - Name Surname - * Georgi Georgiev + * ModusBox + - Georgi Georgiev -------------- ******/ @@ -39,345 +40,361 @@ const sleep = (ms) => { setTimeout(resolve, ms) }) } +const currencies = ['USD', 'TZS'] /** * The following services must be running: * central-ledger, ml-api-adapter, simulator, mysql, kafka */ -module.exports = () => { - Test('PrepareTransferData should', prepareTransferDataTest => { - const URI_PREFIX = 'http' - const CENTRAL_LEDGER_HOST = TestConfig.CENTRAL_LEDGER_HOST - const CENTRAL_LEDGER_PORT = TestConfig.CENTRAL_LEDGER_PORT - const CENTRAL_LEDGER_BASE = '' - const ML_API_ADAPTER_HOST = TestConfig.ML_API_ADAPTER_HOST - const ML_API_ADAPTER_PORT = TestConfig.ML_API_ADAPTER_PORT - const ML_API_ADAPTER_BASE = '' - const SIMULATOR_REMOTE_HOST = TestConfig.SIMULATOR_REMOTE_HOST - const SIMULATOR_REMOTE_PORT = TestConfig.SIMULATOR_REMOTE_PORT - const SIMULATOR_HOST = TestConfig.SIMULATOR_HOST - const SIMULATOR_PORT = TestConfig.SIMULATOR_PORT - const SIMULATOR_CORR_ENDPOINT = '/payeefsp/correlationid' - const payerFsp = `fsp${rand8()}` - const payeeFsp = `fsp${rand8()}` - const fspList = [ - { - fspName: payerFsp, - endpointBase: `${URI_PREFIX}://${SIMULATOR_REMOTE_HOST}:${SIMULATOR_REMOTE_PORT}/payerfsp` - }, - { - fspName: payeeFsp, - endpointBase: `${URI_PREFIX}://${SIMULATOR_REMOTE_HOST}:${SIMULATOR_REMOTE_PORT}/payeefsp` +module.exports = { + currencies, + setup: () => { + Test('PrepareTransferData should', prepareTransferDataTest => { + const URI_PREFIX = 'http' + const CENTRAL_LEDGER_HOST = TestConfig.CENTRAL_LEDGER_HOST + const CENTRAL_LEDGER_PORT = TestConfig.CENTRAL_LEDGER_PORT + const CENTRAL_LEDGER_BASE = '' + const ML_API_ADAPTER_HOST = TestConfig.ML_API_ADAPTER_HOST + const ML_API_ADAPTER_PORT = TestConfig.ML_API_ADAPTER_PORT + const ML_API_ADAPTER_BASE = '' + const SIMULATOR_REMOTE_HOST = TestConfig.SIMULATOR_REMOTE_HOST + const SIMULATOR_REMOTE_PORT = TestConfig.SIMULATOR_REMOTE_PORT + const SIMULATOR_HOST = TestConfig.SIMULATOR_HOST + const SIMULATOR_PORT = TestConfig.SIMULATOR_PORT + const SIMULATOR_CORR_ENDPOINT = '/payeefsp/correlationid' + const payerFsp = `fsp${rand8()}` + const payeeFsp = `fsp${rand8()}` + const fspList = [ + { + fspName: payerFsp, + endpointBase: `${URI_PREFIX}://${SIMULATOR_REMOTE_HOST}:${SIMULATOR_REMOTE_PORT}/payerfsp` + }, + { + fspName: payeeFsp, + endpointBase: `${URI_PREFIX}://${SIMULATOR_REMOTE_HOST}:${SIMULATOR_REMOTE_PORT}/payeefsp` + } + ] + const currencies = ['USD', 'TZS'] + const transfers = [] + for (const currency of currencies) { + transfers.push({ + transferId: Uuid(), + amount: { + amount: (10 + Math.floor(Math.random() * 9000) / 100).toString().substr(0, 5), // transfer amount between 10.00 and 100 + currency + }, + ilpPacket: 'AQAAAAAAAABkEGcuZXdwMjEuaWQuODAwMjCCAhd7InRyYW5zYWN0aW9uSWQiOiJmODU0NzdkYi0xMzVkLTRlMDgtYThiNy0xMmIyMmQ4MmMwZDYiLCJxdW90ZUlkIjoiOWU2NGYzMjEtYzMyNC00ZDI0LTg5MmYtYzQ3ZWY0ZThkZTkxIiwicGF5ZWUiOnsicGFydHlJZEluZm8iOnsicGFydHlJZFR5cGUiOiJNU0lTRE4iLCJwYXJ0eUlkZW50aWZpZXIiOiIyNTYxMjM0NTYiLCJmc3BJZCI6IjIxIn19LCJwYXllciI6eyJwYXJ0eUlkSW5mbyI6eyJwYXJ0eUlkVHlwZSI6Ik1TSVNETiIsInBhcnR5SWRlbnRpZmllciI6IjI1NjIwMTAwMDAxIiwiZnNwSWQiOiIyMCJ9LCJwZXJzb25hbEluZm8iOnsiY29tcGxleE5hbWUiOnsiZmlyc3ROYW1lIjoiTWF0cyIsImxhc3ROYW1lIjoiSGFnbWFuIn0sImRhdGVPZkJpcnRoIjoiMTk4My0xMC0yNSJ9fSwiYW1vdW50Ijp7ImFtb3VudCI6IjEwMCIsImN1cnJlbmN5IjoiVVNEIn0sInRyYW5zYWN0aW9uVHlwZSI6eyJzY2VuYXJpbyI6IlRSQU5TRkVSIiwiaW5pdGlhdG9yIjoiUEFZRVIiLCJpbml0aWF0b3JUeXBlIjoiQ09OU1VNRVIifSwibm90ZSI6ImhlaiJ9', + ilpCondition: 'HOr22-H3AfTDHrSkPjJtVPRdKouuMkDXTR4ejlQa8Ks' + }) } - ] - const currency = 'USD' - const transferId = Uuid() - const transferAmount = (10 + Math.floor(Math.random() * 9000) / 100).toString().substr(0, 5) // transfer amount between 10.00 and 100 - const ilpPacket = 'AQAAAAAAAABkEGcuZXdwMjEuaWQuODAwMjCCAhd7InRyYW5zYWN0aW9uSWQiOiJmODU0NzdkYi0xMzVkLTRlMDgtYThiNy0xMmIyMmQ4MmMwZDYiLCJxdW90ZUlkIjoiOWU2NGYzMjEtYzMyNC00ZDI0LTg5MmYtYzQ3ZWY0ZThkZTkxIiwicGF5ZWUiOnsicGFydHlJZEluZm8iOnsicGFydHlJZFR5cGUiOiJNU0lTRE4iLCJwYXJ0eUlkZW50aWZpZXIiOiIyNTYxMjM0NTYiLCJmc3BJZCI6IjIxIn19LCJwYXllciI6eyJwYXJ0eUlkSW5mbyI6eyJwYXJ0eUlkVHlwZSI6Ik1TSVNETiIsInBhcnR5SWRlbnRpZmllciI6IjI1NjIwMTAwMDAxIiwiZnNwSWQiOiIyMCJ9LCJwZXJzb25hbEluZm8iOnsiY29tcGxleE5hbWUiOnsiZmlyc3ROYW1lIjoiTWF0cyIsImxhc3ROYW1lIjoiSGFnbWFuIn0sImRhdGVPZkJpcnRoIjoiMTk4My0xMC0yNSJ9fSwiYW1vdW50Ijp7ImFtb3VudCI6IjEwMCIsImN1cnJlbmN5IjoiVVNEIn0sInRyYW5zYWN0aW9uVHlwZSI6eyJzY2VuYXJpbyI6IlRSQU5TRkVSIiwiaW5pdGlhdG9yIjoiUEFZRVIiLCJpbml0aWF0b3JUeXBlIjoiQ09OU1VNRVIifSwibm90ZSI6ImhlaiJ9' - const ilpCondition = 'HOr22-H3AfTDHrSkPjJtVPRdKouuMkDXTR4ejlQa8Ks' - const localEnum = { - transferStates: { - COMMITTED: 'COMMITTED' + const localEnum = { + transferStates: { + COMMITTED: 'COMMITTED' + } } - } - const sleepMilliseconds = 1000 - - let sandbox - prepareTransferDataTest.beforeEach(test => { - sandbox = Sinon.createSandbox() - test.end() - }) - prepareTransferDataTest.afterEach(test => { - sandbox.restore() - test.end() - }) + const sleepMilliseconds = 1000 - prepareTransferDataTest.test('check if Hub accounts exists', async test => { - try { - const url = `${URI_PREFIX}://${CENTRAL_LEDGER_HOST}:${CENTRAL_LEDGER_PORT}${CENTRAL_LEDGER_BASE}/participants/Hub/accounts?currency=${currency}` - const opts = { method: 'GET' } - const res = await fetch(url, opts) - test.equal(res.status, 200, 'returned 200 OK') - - const response = await res.json() - let hubReconciliationAccountExists = false - let hubMLNSAccountExists = false - if (response && response.length) { - hubReconciliationAccountExists = response.findIndex(account => { - return account.ledgerAccountType === 'HUB_RECONCILIATION' - }) >= 0 - hubMLNSAccountExists = response.findIndex(account => { - return account.ledgerAccountType === 'HUB_MULTILATERAL_SETTLEMENT' - }) >= 0 - } + let sandbox + prepareTransferDataTest.beforeEach(test => { + sandbox = Sinon.createSandbox() + test.end() + }) + prepareTransferDataTest.afterEach(test => { + sandbox.restore() + test.end() + }) - if (hubReconciliationAccountExists) { - test.pass(`${currency} HUB_RECONCILIATION found`) - } else { - try { - const url = `${URI_PREFIX}://${CENTRAL_LEDGER_HOST}:${CENTRAL_LEDGER_PORT}${CENTRAL_LEDGER_BASE}/participants/Hub/accounts` - const headers = { - 'Content-Type': 'application/json' - } - const body = { - currency: 'USD', - type: 'HUB_RECONCILIATION' - } - const opts = { - method: 'POST', - headers, - body: JSON.stringify(body) - } + prepareTransferDataTest.test('check if Hub accounts exists', async test => { + try { + for (const currency of currencies) { + const url = `${URI_PREFIX}://${CENTRAL_LEDGER_HOST}:${CENTRAL_LEDGER_PORT}${CENTRAL_LEDGER_BASE}/participants/Hub/accounts?currency=${currency}` + const opts = { method: 'GET' } const res = await fetch(url, opts) - test.equal(res.status, 201, 'returned 201 Created') - } catch (err) { - Logger.error(`creating HUB_RECONCILIATION failed with error - ${err}`) - test.fail() - } - } + test.equal(res.status, 200, 'returned 200 OK') - if (hubMLNSAccountExists) { - test.pass(`${currency} HUB_MULTILATERAL_SETTLEMENT found`) - } else { - try { - const url = `${URI_PREFIX}://${CENTRAL_LEDGER_HOST}:${CENTRAL_LEDGER_PORT}${CENTRAL_LEDGER_BASE}/participants/Hub/accounts` - const headers = { - 'Content-Type': 'application/json' - } - const body = { - currency: 'USD', - type: 'HUB_MULTILATERAL_SETTLEMENT' - } - const opts = { - method: 'POST', - headers, - body: JSON.stringify(body) + const response = await res.json() + let hubReconciliationAccountExists = false + let hubMLNSAccountExists = false + if (response && response.length) { + hubReconciliationAccountExists = response.findIndex(account => { + return account.ledgerAccountType === 'HUB_RECONCILIATION' + }) >= 0 + hubMLNSAccountExists = response.findIndex(account => { + return account.ledgerAccountType === 'HUB_MULTILATERAL_SETTLEMENT' + }) >= 0 } - const res = await fetch(url, opts) - test.equal(res.status, 201, 'returned 201 Created') - } catch (err) { - Logger.error(`creating HUB_MULTILATERAL_SETTLEMENT failed with error - ${err}`) - test.fail() - } - } - test.end() - } catch (err) { - Logger.error(`prepareTransferDataTest failed with error - ${err}`) - test.fail() - test.end() - } - }) + if (hubReconciliationAccountExists) { + test.pass(`${currency} HUB_RECONCILIATION found`) + } else { + try { + const url = `${URI_PREFIX}://${CENTRAL_LEDGER_HOST}:${CENTRAL_LEDGER_PORT}${CENTRAL_LEDGER_BASE}/participants/Hub/accounts` + const headers = { + 'Content-Type': 'application/json' + } + const body = { + currency, + type: 'HUB_RECONCILIATION' + } + const opts = { + method: 'POST', + headers, + body: JSON.stringify(body) + } + const res = await fetch(url, opts) + test.equal(res.status, 201, 'returned 201 Created') + } catch (err) { + Logger.error(`creating HUB_RECONCILIATION failed with error - ${err}`) + test.fail() + } + } - prepareTransferDataTest.test('add participant and participant account', async test => { - for (const fsp of fspList) { - try { - const url = `${URI_PREFIX}://${CENTRAL_LEDGER_HOST}:${CENTRAL_LEDGER_PORT}${CENTRAL_LEDGER_BASE}/participants` - const headers = { - 'Content-Type': 'application/json' - } - const body = { - name: fsp.fspName, - currency: currency - } - const opts = { - method: 'POST', - headers, - body: JSON.stringify(body) + if (hubMLNSAccountExists) { + test.pass(`${currency} HUB_MULTILATERAL_SETTLEMENT found`) + } else { + try { + const url = `${URI_PREFIX}://${CENTRAL_LEDGER_HOST}:${CENTRAL_LEDGER_PORT}${CENTRAL_LEDGER_BASE}/participants/Hub/accounts` + const headers = { + 'Content-Type': 'application/json' + } + const body = { + currency, + type: 'HUB_MULTILATERAL_SETTLEMENT' + } + const opts = { + method: 'POST', + headers, + body: JSON.stringify(body) + } + const res = await fetch(url, opts) + test.equal(res.status, 201, 'returned 201 Created') + } catch (err) { + Logger.error(`creating HUB_MULTILATERAL_SETTLEMENT failed with error - ${err}`) + test.fail() + } + } } - const res = await fetch(url, opts) - test.equal(res.status, 201, `returned 201 Created for ${fsp.fspName}`) + test.end() } catch (err) { Logger.error(`prepareTransferDataTest failed with error - ${err}`) test.fail() test.end() } - } - test.end() - }) + }) - prepareTransferDataTest.test('add participant account limits', async test => { - for (const fsp of fspList) { + prepareTransferDataTest.test('add participant and participant account', async test => { try { - const url = `${URI_PREFIX}://${CENTRAL_LEDGER_HOST}:${CENTRAL_LEDGER_PORT}${CENTRAL_LEDGER_BASE}/participants/${fsp.fspName}/initialPositionAndLimits` - const headers = { - 'Content-Type': 'application/json' - } - const body = { - currency: currency, - limit: { - type: 'NET_DEBIT_CAP', - value: 1000 - }, - initialPosition: 0 - } - const opts = { - method: 'POST', - headers, - body: JSON.stringify(body) + for (const currency of currencies) { + for (const fsp of fspList) { + const url = `${URI_PREFIX}://${CENTRAL_LEDGER_HOST}:${CENTRAL_LEDGER_PORT}${CENTRAL_LEDGER_BASE}/participants` + const headers = { + 'Content-Type': 'application/json' + } + const body = { + name: fsp.fspName, + currency + } + const opts = { + method: 'POST', + headers, + body: JSON.stringify(body) + } + const res = await fetch(url, opts) + test.equal(res.status, 201, `returned 201 Created for ${fsp.fspName}`) + } } - const res = await fetch(url, opts) - test.equal(res.status, 201, `returned 201 created limits for ${fsp.fspName}`) + test.end() } catch (err) { Logger.error(`prepareTransferDataTest failed with error - ${err}`) test.fail() test.end() } - } - test.end() - }) + }) - prepareTransferDataTest.test('add participant FSPIOP_CALLBACK_URL_TRANSFER_POST endpoint', async test => { - const headers = { - 'Content-Type': 'application/json' - } - for (const fsp of fspList) { + prepareTransferDataTest.test('add participant account limits', async test => { try { - const url = `${URI_PREFIX}://${CENTRAL_LEDGER_HOST}:${CENTRAL_LEDGER_PORT}${CENTRAL_LEDGER_BASE}/participants/${fsp.fspName}/endpoints` - const body = { - type: 'FSPIOP_CALLBACK_URL_TRANSFER_POST', - value: `${fsp.endpointBase}/transfers` - } - const opts = { - method: 'POST', - headers, - body: JSON.stringify(body) + for (const currency of currencies) { + for (const fsp of fspList) { + const url = `${URI_PREFIX}://${CENTRAL_LEDGER_HOST}:${CENTRAL_LEDGER_PORT}${CENTRAL_LEDGER_BASE}/participants/${fsp.fspName}/initialPositionAndLimits` + const headers = { + 'Content-Type': 'application/json' + } + const body = { + currency, + limit: { + type: 'NET_DEBIT_CAP', + value: 1000 + }, + initialPosition: 0 + } + const opts = { + method: 'POST', + headers, + body: JSON.stringify(body) + } + const res = await fetch(url, opts) + test.equal(res.status, 201, `returned 201 created limits for ${fsp.fspName}`) + } } - const res = await fetch(url, opts) - test.equal(res.status, 201, `returned 201 created endpoint for ${fsp.fspName}`) + test.end() } catch (err) { Logger.error(`prepareTransferDataTest failed with error - ${err}`) test.fail() test.end() } - } - test.end() - }) + }) - prepareTransferDataTest.test('add participant FSPIOP_CALLBACK_URL_TRANSFER_PUT endpoint', async test => { - const headers = { - 'Content-Type': 'application/json' - } - for (const fsp of fspList) { + prepareTransferDataTest.test('add participant FSPIOP_CALLBACK_URL_TRANSFER_POST endpoint', async test => { try { - const url = `${URI_PREFIX}://${CENTRAL_LEDGER_HOST}:${CENTRAL_LEDGER_PORT}${CENTRAL_LEDGER_BASE}/participants/${fsp.fspName}/endpoints` - const body = { - type: 'FSPIOP_CALLBACK_URL_TRANSFER_PUT', - value: `${fsp.endpointBase}/transfers/{{transferId}}` + const headers = { + 'Content-Type': 'application/json' } - const opts = { - method: 'POST', - headers, - body: JSON.stringify(body) + for (const fsp of fspList) { + const url = `${URI_PREFIX}://${CENTRAL_LEDGER_HOST}:${CENTRAL_LEDGER_PORT}${CENTRAL_LEDGER_BASE}/participants/${fsp.fspName}/endpoints` + const body = { + type: 'FSPIOP_CALLBACK_URL_TRANSFER_POST', + value: `${fsp.endpointBase}/transfers` + } + const opts = { + method: 'POST', + headers, + body: JSON.stringify(body) + } + const res = await fetch(url, opts) + test.equal(res.status, 201, `returned 201 created endpoint for ${fsp.fspName}`) } - const res = await fetch(url, opts) - test.equal(res.status, 201, `returned 201 created endpoint for ${fsp.fspName}`) + test.end() } catch (err) { Logger.error(`prepareTransferDataTest failed with error - ${err}`) test.fail() test.end() } - } - test.end() - }) + }) - prepareTransferDataTest.test('create FSPIOP_CALLBACK_URL_TRANSFER_ERROR endpoint', async test => { - const headers = { - 'Content-Type': 'application/json' - } - for (const fsp of fspList) { - try { - const url = `${URI_PREFIX}://${CENTRAL_LEDGER_HOST}:${CENTRAL_LEDGER_PORT}${CENTRAL_LEDGER_BASE}/participants/${fsp.fspName}/endpoints` + prepareTransferDataTest.test('add participant FSPIOP_CALLBACK_URL_TRANSFER_PUT endpoint', async test => { + const headers = { + 'Content-Type': 'application/json' + } + for (const fsp of fspList) { + try { + const url = `${URI_PREFIX}://${CENTRAL_LEDGER_HOST}:${CENTRAL_LEDGER_PORT}${CENTRAL_LEDGER_BASE}/participants/${fsp.fspName}/endpoints` + const body = { + type: 'FSPIOP_CALLBACK_URL_TRANSFER_PUT', + value: `${fsp.endpointBase}/transfers/{{transferId}}` + } + const opts = { + method: 'POST', + headers, + body: JSON.stringify(body) + } + const res = await fetch(url, opts) + test.equal(res.status, 201, `returned 201 created endpoint for ${fsp.fspName}`) + } catch (err) { + Logger.error(`prepareTransferDataTest failed with error - ${err}`) + test.fail() + test.end() + } + } + test.end() + }) + + prepareTransferDataTest.test('create FSPIOP_CALLBACK_URL_TRANSFER_ERROR endpoint', async test => { + const headers = { + 'Content-Type': 'application/json' + } + for (const fsp of fspList) { + try { + const url = `${URI_PREFIX}://${CENTRAL_LEDGER_HOST}:${CENTRAL_LEDGER_PORT}${CENTRAL_LEDGER_BASE}/participants/${fsp.fspName}/endpoints` + const body = { + type: 'FSPIOP_CALLBACK_URL_TRANSFER_ERROR', + value: `${fsp.endpointBase}/transfers/{{transferId}}/error` + } + const opts = { + method: 'POST', + headers, + body: JSON.stringify(body) + } + const res = await fetch(url, opts) + test.equal(res.status, 201, `returned 201 created endpoint for ${fsp.fspName}`) + } catch (err) { + Logger.error(`prepareTransferDataTest failed with error - ${err}`) + test.fail() + test.end() + } + } + test.end() + }) + + for (const transfer of transfers) { + prepareTransferDataTest.test(`create a transfer for the amount of ${transfer.amount.amount} ${transfer.amount.currency}`, async test => { + const currentDateGMT = new Date().toGMTString() + const expirationDate = new Date((new Date()).getTime() + (24 * 60 * 60 * 1000)) + + const headers = { + Accept: 'application/vnd.interoperability.transfers+json;version=1.0', + 'Content-Type': 'application/vnd.interoperability.transfers+json;version=1.0', + Date: currentDateGMT, + 'FSPIOP-Source': payerFsp, + 'FSPIOP-Destination': payeeFsp + } + const url = `${URI_PREFIX}://${ML_API_ADAPTER_HOST}:${ML_API_ADAPTER_PORT}${ML_API_ADAPTER_BASE}/transfers` const body = { - type: 'FSPIOP_CALLBACK_URL_TRANSFER_ERROR', - value: `${fsp.endpointBase}/transfers/{{transferId}}/error` + transferId: transfer.transferId, + payerFsp, + payeeFsp, + amount: transfer.amount, + ilpPacket: transfer.ilpPacket, + condition: transfer.ilpCondition, + expiration: expirationDate.toISOString(), + extensionList: { + extension: [{ + key: 'prepare', + value: 'description' + }] + } } const opts = { method: 'POST', headers, body: JSON.stringify(body) } - const res = await fetch(url, opts) - test.equal(res.status, 201, `returned 201 created endpoint for ${fsp.fspName}`) - } catch (err) { - Logger.error(`prepareTransferDataTest failed with error - ${err}`) - test.fail() - test.end() - } - } - test.end() - }) - - prepareTransferDataTest.test(`create a transfer for the amount of ${transferAmount} ${currency}`, async test => { - const currentDateGMT = new Date().toGMTString() - const expirationDate = new Date((new Date()).getTime() + (24 * 60 * 60 * 1000)) - - const headers = { - Accept: 'application/vnd.interoperability.transfers+json;version=1.0', - 'Content-Type': 'application/vnd.interoperability.transfers+json;version=1.0', - Date: currentDateGMT, - 'FSPIOP-Source': payerFsp, - 'FSPIOP-Destination': payeeFsp - } - const url = `${URI_PREFIX}://${ML_API_ADAPTER_HOST}:${ML_API_ADAPTER_PORT}${ML_API_ADAPTER_BASE}/transfers` - const body = { - transferId, - payerFsp, - payeeFsp, - amount: { - currency, - amount: transferAmount - }, - ilpPacket, - condition: ilpCondition, - expiration: expirationDate.toISOString(), - extensionList: { - extension: [{ - key: 'prepare', - value: 'description' - }] - } - } - const opts = { - method: 'POST', - headers, - body: JSON.stringify(body) - } - const simulatorUrl = `${URI_PREFIX}://${SIMULATOR_HOST}:${SIMULATOR_PORT}${SIMULATOR_CORR_ENDPOINT}/${transferId}` + const simulatorUrl = `${URI_PREFIX}://${SIMULATOR_HOST}:${SIMULATOR_PORT}${SIMULATOR_CORR_ENDPOINT}/${transfer.transferId}` - try { - const res = await fetch(url, opts) - test.equal(res.status, 202, 'transfer PREPARE request returned 202 Accepted') - - let transferCommitted = false - for (let i = 0; i < 10; i++) { - const simulatorRes = await fetch(simulatorUrl) try { - const simulatorResponse = await simulatorRes.json() - if (simulatorResponse && simulatorResponse.transferState === localEnum.transferStates.COMMITTED) { - transferCommitted = true - break + const res = await fetch(url, opts) + test.equal(res.status, 202, 'transfer PREPARE request returned 202 Accepted') + + let transferCommitted = false + for (let i = 0; i < 10; i++) { + const simulatorRes = await fetch(simulatorUrl) + try { + const simulatorResponse = await simulatorRes.json() + if (simulatorResponse && simulatorResponse.transferState === localEnum.transferStates.COMMITTED) { + transferCommitted = true + break + } + } catch (err) { + if (err.type === 'invalid-json') { + Logger.info(`Transfer not processed yet. Awaiting ${sleepMilliseconds} ms...`) + } else { + Logger.info(err.message) + throw err + } + } + await sleep(sleepMilliseconds) } + test.ok(transferCommitted, 'transfer successfully COMMITTED by payee fsp') + test.end() } catch (err) { - if (err.type === 'invalid-json') { - Logger.info(`Transfer not processed yet. Awaiting ${sleepMilliseconds} ms...`) - } else { - Logger.info(err.message) - throw err - } + Logger.error(`prepareTransferDataTest failed with error - ${err}`) + test.fail() + test.end() } - await sleep(sleepMilliseconds) - } - test.ok(transferCommitted, 'transfer successfully COMMITTED by payee fsp') - test.end() - } catch (err) { - Logger.error(`prepareTransferDataTest failed with error - ${err}`) - test.fail() - test.end() + }) } - }) - prepareTransferDataTest.end() - }) + prepareTransferDataTest.end() + }) + } } diff --git a/test/integration/settlementTransfer.test.js b/test/integration/settlementTransfer.test.js index c562ab49..4d1e99f8 100644 --- a/test/integration/settlementTransfer.test.js +++ b/test/integration/settlementTransfer.test.js @@ -18,7 +18,8 @@ * Gates Foundation - Name Surname - * Georgi Georgiev + * ModusBox + - Georgi Georgiev -------------- ******/ 'use strict' @@ -27,7 +28,7 @@ const Test = require('tapes')(require('tape')) const Sinon = require('sinon') const Logger = require('@mojaloop/central-services-logger') const MLNumber = require('@mojaloop/ml-number') -const PrepareTransferData = require('./helpers/transferData') +const TransferData = require('./helpers/transferData') const Models = require('./helpers/models') const Config = require('../../src/lib/config') const Db = require('../../src/lib/db') @@ -36,13 +37,15 @@ const SettlementService = require('../../src/domain/settlement') const Enums = require('../../src/models/lib/enums') const SettlementWindowStateChangeModel = require('../../src/models/settlementWindow/settlementWindowStateChange') const SettlementModel = require('../../src/models/settlement/settlement') +const SettlementModelModel = require('../../src/models/settlement/settlementModel') const SettlementStateChangeModel = require('../../src/models/settlement/settlementStateChange') const SettlementParticipantCurrencyModel = require('../../src/models/settlement/settlementParticipantCurrency') const TransferModel = require('@mojaloop/central-ledger/src/models/transfer/transfer') const TransferStateChangeModel = require('@mojaloop/central-ledger/src/models/transfer/transferStateChange') const ParticipantPositionModel = require('@mojaloop/central-ledger/src/models/position/participantPosition') -const Producer = require('../../src/handlers/lib/kafka/producer') -// require('leaked-handles').set({ fullStack: true, timeout: 15000, debugSockets: true }) +const Producer = require('../../src/lib/kafka/producer') +const StreamProducer = require('@mojaloop/central-services-stream').Util.Producer +// require('leaked-handles').set({ fullStack: true, timeout: 5000, debugSockets: true }) const currency = 'USD' let netSettlementSenderId @@ -53,19 +56,43 @@ let netSettlementAmount let netSenderSettlementTransferId let netRecipientSettlementTransferId +const settlementModels = [ + { + name: 'DEFERRED_NET', + settlementGranularityId: 2, // NET + settlementInterchangeId: 2, // MULTILATERAL + settlementDelayId: 2, // DEFERRED + ledgerAccountTypeId: 1, // POSITION + autoPositionReset: true, + currencyId: null + }, + { + name: 'DEFERRED_NET_USD', + settlementGranularityId: 2, // NET + settlementInterchangeId: 2, // MULTILATERAL + settlementDelayId: 2, // DEFERRED + ledgerAccountTypeId: 1, // POSITION + autoPositionReset: true, + currencyId: 'USD' + } +] + const getEnums = async () => { return { - settlementWindowStates: await Enums.settlementWindowStates(), - settlementStates: await Enums.settlementStates(), - transferStates: await Enums.transferStates(), ledgerAccountTypes: await Enums.ledgerAccountTypes(), ledgerEntryTypes: await Enums.ledgerEntryTypes(), + participantLimitTypes: await Enums.participantLimitTypes(), + settlementDelay: await Enums.settlementDelay(), + settlementGranularity: await Enums.settlementGranularity(), + settlementInterchange: await Enums.settlementInterchange(), + settlementStates: await Enums.settlementStates(), + settlementWindowStates: await Enums.settlementWindowStates(), transferParticipantRoleTypes: await Enums.transferParticipantRoleTypes(), - participantLimitTypes: await Enums.participantLimitTypes() + transferStates: await Enums.transferStates() } } -PrepareTransferData() +TransferData.setup() Test('SettlementTransfer should', async settlementTransferTest => { await Db.connect(Config.DATABASE) @@ -83,21 +110,66 @@ Test('SettlementTransfer should', async settlementTransferTest => { test.end() }) - await settlementTransferTest.test('close current window should', async test => { + await settlementTransferTest.test('init settlement models for integration testing:', async test => { + try { + for (const model of settlementModels) { + const record = await SettlementModelModel.getByName(model.name) + if (record && record.name === model.name) { + model.settlementModelId = record.settlementModelId + test.pass(`Settlement model ${model.name} already exists`) + } else { + const id = await Models.settlementModel.create(model) + const record1 = await SettlementModelModel.getByName(model.name) + if (record1 && record1.name === model.name && record1.settlementModelId === id) { + model.settlementModelId = id + test.pass(`Settlement model ${model.name} has been successfully inserted with id = ${id}`) + } else { + throw new Error(`Settlement model ${model.name} could not be instantiated`) + } + } + } + test.end() + } catch (err) { + Logger.error(`settlementTransferTest failed with error - ${err}`) + test.fail() + test.end() + } + }) + + await settlementTransferTest.test('close the current window:', async test => { try { let params = { query: { state: enums.settlementWindowStates.OPEN } } - let res = await SettlementWindowService.getByParams(params) // method to be verified - settlementWindowId = res[0].settlementWindowId + const res1 = await SettlementWindowService.getByParams(params) // method to be verified + settlementWindowId = res1[0].settlementWindowId test.ok(settlementWindowId > 0, 'retrieve the OPEN window') params = { settlementWindowId: settlementWindowId, state: enums.settlementWindowStates.CLOSED, reason: 'text' } - res = await SettlementWindowService.close(params, enums.settlementWindowStates) // method to be verified - test.ok(res, 'close settlement window operation success') + const res2 = await SettlementWindowService.process(params, enums.settlementWindowStates) + const res3 = await SettlementWindowService.close(params.settlementWindowId, params.reason) + test.ok(res3, 'close settlement window operation success') const closedWindow = await SettlementWindowStateChangeModel.getBySettlementWindowId(settlementWindowId) - const openWindow = await SettlementWindowStateChangeModel.getBySettlementWindowId(res.settlementWindowId) + const openWindow = await SettlementWindowStateChangeModel.getBySettlementWindowId(res2.settlementWindowId) test.equal(closedWindow.settlementWindowStateId, enums.settlementWindowStates.CLOSED, `window id ${settlementWindowId} is CLOSED`) - test.equal(openWindow.settlementWindowStateId, enums.settlementWindowStates.OPEN, `window id ${res.settlementWindowId} is OPEN`) + test.equal(openWindow.settlementWindowStateId, enums.settlementWindowStates.OPEN, `window id ${res2.settlementWindowId} is OPEN`) + + for (const currency of TransferData.currencies) { + const settlementWindowContentData = await Models.settlementWindowContent.getByParams({ settlementWindowId, currencyId: currency }) + const id = settlementWindowContentData[0].settlementWindowContentId + test.equal(settlementWindowContentData.length, 1, `window content id ${id} has been created`) + test.equal(settlementWindowContentData[0].settlementId, null, `window content id ${id} has not been assigned to a settlement yet`) + + const settlementWindowContentStateChange = await Models.settlementWindowContentStateChange.getBySettlementWindowContentId(id) + test.equal(settlementWindowContentStateChange.settlementWindowStateId, 'CLOSED', `window content id ${id} state is CLOSED`) + test.equal(settlementWindowContentStateChange.settlementWindowContentStateChangeId, settlementWindowContentData[0].currentStateChangeId, 'state pointer is up-to-date') + + const settlementContentAggregationData = await Models.settlementWindowContentAggregation.getBySettlementWindowContentId(id) + test.ok(settlementContentAggregationData.length > 0, `a total of ${settlementContentAggregationData.length} content aggregation records have been generated for window content ${id}`) + for (const sca of settlementContentAggregationData) { + test.equal(sca.currentStateId, 'CLOSED', `content aggregation id ${sca.settlementContentAggregationId} state is CLOSED`) + test.equal(sca.settlementId, null, `content aggregation id ${sca.settlementContentAggregationId} has not been assigned to a settlement yet`) + } + } test.end() } catch (err) { @@ -107,9 +179,10 @@ Test('SettlementTransfer should', async settlementTransferTest => { } }) - await settlementTransferTest.test('create settlement should', async test => { + await settlementTransferTest.test('create a settlement:', async test => { try { const params = { + settlementModel: settlementModels[1].name, reason: 'reason', settlementWindows: [ { @@ -120,6 +193,15 @@ Test('SettlementTransfer should', async settlementTransferTest => { settlementData = await SettlementService.settlementEventTrigger(params, enums) // method to be verified test.ok(settlementData, 'settlementEventTrigger operation success') + const sId = settlementData.id + test.equal(settlementData.settlementWindows.length, 1, `settlement id ${sId} holds one window`) + test.ok(settlementData.settlementWindows[0].content.length > 0, 'settlement window has content') + test.equal(settlementData.settlementWindows[0].content[0].state, 'PENDING_SETTLEMENT', 'settlement window content state is PENDING_SETTLEMENT') + + const swcId = settlementData.settlementWindows[0].content[0].id + const settlementWindowContent = await Models.settlementWindowContent.getById(swcId) + test.equal(settlementWindowContent.settlementId, settlementData.id, `window content id ${swcId} has been assigned to settlement id ${sId}`) + const settlementWindow = await SettlementWindowStateChangeModel.getBySettlementWindowId(settlementWindowId) test.equal(settlementWindow.settlementWindowStateId, enums.settlementWindowStates.PENDING_SETTLEMENT, `window id ${settlementWindowId} is PENDING_SETTLEMENT`) @@ -136,7 +218,7 @@ Test('SettlementTransfer should', async settlementTransferTest => { } }) - await settlementTransferTest.test('PS_TRANSFERS_RECORDED for PAYER', async test => { + await settlementTransferTest.test('PS_TRANSFERS_RECORDED for PAYER:', async test => { try { // read and store settlement participant and account data needed in remaining tests let participantFilter = settlementData.participants.filter(participant => { @@ -204,7 +286,7 @@ Test('SettlementTransfer should', async settlementTransferTest => { } }) - await settlementTransferTest.test('PS_TRANSFERS_RECORDED for PAYEE', async test => { + await settlementTransferTest.test('PS_TRANSFERS_RECORDED for PAYEE:', async test => { try { const externalReferenceSample = 'tr0123456789' const params = { @@ -257,7 +339,7 @@ Test('SettlementTransfer should', async settlementTransferTest => { } }) - await settlementTransferTest.test('PS_TRANSFERS_RESERVED for PAYER & PAYEE', async test => { + await settlementTransferTest.test('PS_TRANSFERS_RESERVED for PAYER & PAYEE:', async test => { try { const params = { participants: [ @@ -318,7 +400,7 @@ Test('SettlementTransfer should', async settlementTransferTest => { } }) - await settlementTransferTest.test('PS_TRANSFERS_COMMITTED for PAYER & PAYEE', async test => { + await settlementTransferTest.test('PS_TRANSFERS_COMMITTED for PAYER & PAYEE:', async test => { try { const params = { participants: [ @@ -479,8 +561,74 @@ Test('SettlementTransfer should', async settlementTransferTest => { const settlementState = await SettlementStateChangeModel.getBySettlementId(settlementData.id) test.equal(settlementState.settlementStateId, enums.settlementStates.SETTLED, 'settlement state is SETTLED') + const windowContentState = await Models.settlementWindowContentStateChange.getBySettlementWindowContentId(res.settlementWindows[0].content[0].id) + test.equal(windowContentState.settlementWindowStateId, 'SETTLED', 'settlement window content state is SETTLED') + const window = await SettlementWindowStateChangeModel.getBySettlementWindowId(settlementWindowId) - test.equal(window.settlementWindowStateId, enums.settlementWindowStates.SETTLED, 'window is SETTLED') + test.equal(window.settlementWindowStateId, enums.settlementWindowStates.PENDING_SETTLEMENT, 'window is PENDING_SETTLEMENT because there is more window content to settle') + + test.end() + } catch (err) { + Logger.error(`settlementTransferTest failed with error - ${err}`) + test.fail() + test.end() + } + }) + + await settlementTransferTest.test('create a settlement with previous window remaining content and settle it:', async test => { + try { + const settlementStates = [enums.settlementStates.PS_TRANSFERS_RECORDED, enums.settlementStates.PS_TRANSFERS_RESERVED, enums.settlementStates.PS_TRANSFERS_COMMITTED, enums.settlementStates.SETTLED] + + let params = { + settlementModel: settlementModels[0].name, + reason: 'reason', + settlementWindows: [ + { + id: settlementWindowId + } + ] + } + settlementData = await SettlementService.settlementEventTrigger(params, enums) + test.ok(settlementData, 'settlementEventTrigger operation success') + + let res + for (const state of settlementStates) { + params = { + participants: [ + { + id: settlementData.participants[0].id, + accounts: [ + { + id: settlementData.participants[0].accounts[0].id, + reason: `Settlement to ${state} state`, + state + } + ] + }, + { + id: settlementData.participants[1].id, + accounts: [ + { + id: settlementData.participants[1].accounts[0].id, + reason: `Settlement to ${state} state`, + state + } + ] + } + ] + } + res = await SettlementService.putById(settlementData.id, params, enums) + test.ok(res, `settlement putById operation success for ${state} state`) + } + + const settlementState = await SettlementStateChangeModel.getBySettlementId(settlementData.id) + test.equal(settlementState.settlementStateId, enums.settlementStates.SETTLED, 'settlement state is SETTLED') + + const windowContentState = await Models.settlementWindowContentStateChange.getBySettlementWindowContentId(res.settlementWindows[0].content[0].id) + test.equal(windowContentState.settlementWindowStateId, 'SETTLED', 'settlement window content state is SETTLED') + + const window = await SettlementWindowStateChangeModel.getBySettlementWindowId(res.settlementWindows[0].id) + test.equal(window.settlementWindowStateId, enums.settlementWindowStates.SETTLED, 'window is SETTLED because there is no more window content to settle') test.end() } catch (err) { @@ -496,6 +644,8 @@ Test('SettlementTransfer should', async settlementTransferTest => { test.pass('database connection closed') await Producer.getProducer('topic-notification-event').disconnect() test.pass('producer to topic-notification-event disconnected') + await StreamProducer.getProducer('topic-settlementwindow-close').disconnect() + test.pass('producer to topic-settlementwindow-close disconnected') test.end() } catch (err) { Logger.error(`settlementTransferTest failed with error - ${err}`) diff --git a/test/unit/handlers/health.test.js b/test/unit/api/handlers/health.test.js similarity index 93% rename from test/unit/handlers/health.test.js rename to test/unit/api/handlers/health.test.js index 125d9fee..80ea4ac4 100644 --- a/test/unit/handlers/health.test.js +++ b/test/unit/api/handlers/health.test.js @@ -26,14 +26,13 @@ const Test = require('tapes')(require('tape')) const Sinon = require('sinon') -const src = '../../../src' -const getHealth = require(`${src}/handlers/health`).get -const MigrationLockModel = require(`${src}/models/misc/migrationLock`) +const getHealth = require('../../../../src/api/handlers/health').get +const MigrationLockModel = require('../../../../src/models/misc/migrationLock') const { createRequest, unwrapResponse -} = require('../../util') +} = require('../../../util/index') Test('/health', async healthTest => { let sandbox diff --git a/test/unit/utils/cloneDeep.test.js b/test/unit/api/handlers/index.test.js similarity index 52% rename from test/unit/utils/cloneDeep.test.js rename to test/unit/api/handlers/index.test.js index 328a3036..67050fb3 100644 --- a/test/unit/utils/cloneDeep.test.js +++ b/test/unit/api/handlers/index.test.js @@ -18,47 +18,57 @@ * Gates Foundation - Name Surname - * Georgi Georgiev + * Modusbox + - Deon Botha + - Georgi Georgiev -------------- ******/ 'use strict' const Test = require('tapes')(require('tape')) -const Logger = require('@mojaloop/central-services-logger') -const cloneDeep = require('../../../src/utils/cloneDeep') +const Sinon = require('sinon') -Test('cloneDeep utility', (cloneDeepTest) => { - const input = { - prop1: 'value', - prop2: { - prop21: 'deep21' - }, - prop3: null, - prop4: [1, 2, 3], - prop5: Object.create({ notOwn: true }) - } +const Config = require('../../../../src/lib/config') +const Routes = require('../../../../src/api/routes') +const Setup = require('../../../../src/shared/setup') - cloneDeepTest.test('should copy object', test => { - try { - const result = cloneDeep(input) - test.deepEqual(result, input, 'result matches the input') - result.prop2.prop21 = 'test' - test.notDeepEqual(result, input, 'result does not match the input after deep change') - const objAssign = Object.assign({}, input) - test.deepEqual(objAssign, input, 'object assign copied the object') - objAssign.prop2.prop21 = 'test' - test.deepEqual(objAssign, input, 'change in objAssign deep property affected the input') - test.deepEqual(result, input, 'now result matches the input') - test.ok(input.prop5.notOwn, 'not own property present in the input') - test.notOk(result.prop5.notOwn, 'not own property not copied') - test.end() - } catch (err) { - Logger.error(`cloneDeep failed with error - ${err}`) - test.fail() +Test('Api index', indexTest => { + let sandbox + + indexTest.beforeEach(test => { + sandbox = Sinon.createSandbox() + sandbox.stub(Setup) + test.end() + }) + + indexTest.afterEach(test => { + sandbox.restore() + test.end() + }) + + indexTest.test('export should', exportTest => { + exportTest.test('initialize server', async function (test) { + const server = { + start: sandbox.stub(), + info: { + uri: '' + } + } + + server.start.returns(Promise.resolve({})) + Setup.initialize.returns(Promise.resolve(server)) + await require('../../../../src/api/index') + test.ok(Setup.initialize.calledWith({ + service: 'api', + port: Config.PORT, + runHandlers: !Config.HANDLERS_DISABLED, + modules: [Routes] + })) test.end() - } + }) + exportTest.end() }) - cloneDeepTest.end() + indexTest.end() }) diff --git a/test/unit/handlers/settlementWindows.test.js b/test/unit/api/handlers/settlementWindows.test.js similarity index 94% rename from test/unit/handlers/settlementWindows.test.js rename to test/unit/api/handlers/settlementWindows.test.js index 413359c3..0db8a472 100644 --- a/test/unit/handlers/settlementWindows.test.js +++ b/test/unit/api/handlers/settlementWindows.test.js @@ -24,12 +24,12 @@ const Test = require('tapes')(require('tape')) const Sinon = require('sinon') -const Mockgen = require('../../data/mockgen.js') -const InitServer = require('./../../../src/setup').initialize -const Enums = require('./../../../src/models/lib/enums') +const Mockgen = require('../../../data/mockgen.js') +const Base = require('../../base') +const Enums = require('../../../../src/models/lib/enums') const Logger = require('@mojaloop/central-services-logger') -const settlementWindows = require('./../../../src/domain/settlementWindow') -const Db = require('./../../../src/lib/db') +const settlementWindows = require('../../../../src/domain/settlementWindow/index') +const Db = require('../../../../src/lib/db') /** * Test for /settlementWindows */ @@ -47,7 +47,7 @@ Test('/settlementWindows', async (settlementWindowTest) => { settlementWindowTest.beforeEach(async t => { sandbox = Sinon.createSandbox() sandbox.stub(Db, 'connect').returns(Promise.resolve({})) - server = await InitServer() + server = await Base.setup() t.end() }) diff --git a/test/unit/handlers/settlementWindows/{id}.test.js b/test/unit/api/handlers/settlementWindows/{id}.test.js similarity index 94% rename from test/unit/handlers/settlementWindows/{id}.test.js rename to test/unit/api/handlers/settlementWindows/{id}.test.js index 4ce79b1d..4c3d997b 100644 --- a/test/unit/handlers/settlementWindows/{id}.test.js +++ b/test/unit/api/handlers/settlementWindows/{id}.test.js @@ -24,12 +24,12 @@ const Test = require('tapes')(require('tape')) const Sinon = require('sinon') -const Mockgen = require('./../../../data/mockgen.js') -const InitServer = require('./../../../../src/setup').initialize -const Enums = require('./../../../../src/models/lib/enums') +const Mockgen = require('../../../../data/mockgen.js') +const Base = require('../../../base') +const Enums = require('../../../../../src/models/lib/enums') const Logger = require('@mojaloop/central-services-logger') -const settlementWindows = require('./../../../../src/domain/settlementWindow') -const Db = require('./../../../../src/lib/db') +const settlementWindows = require('../../../../../src/domain/settlementWindow/index') +const Db = require('../../../../../src/lib/db') /** * Test for /settlementWindows @@ -48,7 +48,7 @@ Test('/settlementWindows/{id}', async (settlementWindowTest) => { settlementWindowTest.beforeEach(async t => { sandbox = Sinon.createSandbox() sandbox.stub(Db, 'connect').returns(Promise.resolve({})) - server = await InitServer() + server = await Base.setup() t.end() }) @@ -150,9 +150,9 @@ Test('/settlementWindows/{id}', async (settlementWindowTest) => { t.end() } }) - await settlementWindowTest.test('test settlements put operation', async (t) => { + await settlementWindowTest.test('test settlementWindows post operation', async (t) => { sandbox.stub(Enums, 'settlementWindowStates').returns({}) - sandbox.stub(settlementWindows, 'close').returns({}) + sandbox.stub(settlementWindows, 'process').returns({}) try { const requests = new Promise((resolve, reject) => { Mockgen().requests({ diff --git a/test/unit/handlers/settlements.test.js b/test/unit/api/handlers/settlements.test.js similarity index 94% rename from test/unit/handlers/settlements.test.js rename to test/unit/api/handlers/settlements.test.js index 707bc915..2aeda64a 100644 --- a/test/unit/handlers/settlements.test.js +++ b/test/unit/api/handlers/settlements.test.js @@ -24,12 +24,12 @@ const Test = require('tapes')(require('tape')) const Sinon = require('sinon') -const Mockgen = require('../../data/mockgen.js') -const InitServer = require('./../../../src/setup').initialize -const Enums = require('./../../../src/models/lib/enums') +const Mockgen = require('../../../data/mockgen.js') +const Base = require('../../base') +const Enums = require('../../../../src/models/lib/enums') const Logger = require('@mojaloop/central-services-logger') -const settlement = require('./../../../src/domain/settlement') -const Db = require('./../../../src/lib/db') +const settlement = require('../../../../src/domain/settlement/index') +const Db = require('../../../../src/lib/db') /** * Test for /settlementWindows */ @@ -47,7 +47,7 @@ Test('/settlements', async (settlementTest) => { settlementTest.beforeEach(async t => { sandbox = Sinon.createSandbox() sandbox.stub(Db, 'connect').returns(Promise.resolve({})) - server = await InitServer() + server = await Base.setup() t.end() }) @@ -149,12 +149,15 @@ Test('/settlements', async (settlementTest) => { t.end() } }) - await settlementTest.test('test settlements put operation', async (t) => { + await settlementTest.test('test settlements post operation', async (t) => { + sandbox.stub(Enums, 'ledgerEntryTypes').returns({}) + sandbox.stub(Enums, 'settlementDelay').returns({}) + sandbox.stub(Enums, 'settlementGranularity').returns({}) + sandbox.stub(Enums, 'settlementInterchange').returns({}) sandbox.stub(Enums, 'settlementStates').returns({}) sandbox.stub(Enums, 'settlementWindowStates').returns({}) - sandbox.stub(Enums, 'transferStates').returns({}) sandbox.stub(Enums, 'transferParticipantRoleTypes').returns({}) - sandbox.stub(Enums, 'ledgerEntryTypes').returns({}) + sandbox.stub(Enums, 'transferStates').returns({}) sandbox.stub(settlement, 'settlementEventTrigger').returns({}) try { const requests = new Promise((resolve, reject) => { diff --git a/test/unit/handlers/settlements/{id}.test.js b/test/unit/api/handlers/settlements/{id}.test.js similarity index 98% rename from test/unit/handlers/settlements/{id}.test.js rename to test/unit/api/handlers/settlements/{id}.test.js index 8191feef..f77c74bf 100644 --- a/test/unit/handlers/settlements/{id}.test.js +++ b/test/unit/api/handlers/settlements/{id}.test.js @@ -24,12 +24,12 @@ const Test = require('tapes')(require('tape')) const Sinon = require('sinon') -const Mockgen = require('./../../../data/mockgen.js') -const InitServer = require('./../../../../src/setup').initialize -const Enums = require('./../../../../src/models/lib/enums') +const Mockgen = require('../../../../data/mockgen.js') +const Base = require('../../../base') +const Enums = require('../../../../../src/models/lib/enums') const Logger = require('@mojaloop/central-services-logger') -const settlement = require('./../../../../src/domain/settlement') -const Db = require('./../../../../src/lib/db') +const settlement = require('../../../../../src/domain/settlement/index') +const Db = require('../../../../../src/lib/db') /** * Test for /settlementWindows @@ -48,7 +48,7 @@ Test('/settlements/{id}', async (settlementTest) => { settlementTest.beforeEach(async t => { sandbox = Sinon.createSandbox() sandbox.stub(Db, 'connect').returns(Promise.resolve({})) - server = await InitServer() + server = await Base.setup() t.end() }) diff --git a/test/unit/handlers/settlements/{settlementId}/participants/{participantId}.test.js b/test/unit/api/handlers/settlements/{settlementId}/participants/{participantId}.test.js similarity index 92% rename from test/unit/handlers/settlements/{settlementId}/participants/{participantId}.test.js rename to test/unit/api/handlers/settlements/{settlementId}/participants/{participantId}.test.js index e89bdddd..3bf4ddfe 100644 --- a/test/unit/handlers/settlements/{settlementId}/participants/{participantId}.test.js +++ b/test/unit/api/handlers/settlements/{settlementId}/participants/{participantId}.test.js @@ -31,20 +31,20 @@ const Test = require('tapes')(require('tape')) const Sinon = require('sinon') -const Mockgen = require('../../../../../data/mockgen.js') -const InitServer = require('./../../../../../../src/setup').initialize -const Enums = require('./../../../../../../src/models/lib/enums') +const Mockgen = require('../../../../../../data/mockgen.js') +const Base = require('../../../../../base') +const Enums = require('../../../../../../../src/models/lib/enums') const Logger = require('@mojaloop/central-services-logger') -const settlement = require('./../../../../../../src/domain/settlement') -const Db = require('./../../../../../../src/lib/db') +const settlement = require('../../../../../../../src/domain/settlement/index') +const Db = require('../../../../../../../src/lib/db') -Test('/settlements/{settlementId}/participants/{participantId}', async (settlementTest) => { +Test('/settlements/{sid}/participants/{pid}', async (settlementTest) => { let server let sandbox settlementTest.beforeEach(async t => { sandbox = Sinon.createSandbox() sandbox.stub(Db, 'connect').returns(Promise.resolve({})) - server = await InitServer() + server = await Base.setup() t.end() }) @@ -61,7 +61,7 @@ Test('/settlements/{settlementId}/participants/{participantId}', async (settleme try { const requests = new Promise((resolve, reject) => { Mockgen().requests({ - path: '/settlements/{settlementId}/participants/{participantId}', + path: '/settlements/{sid}/participants/{pid}', operation: 'get' }, function (error, mock) { return error ? reject(error) : resolve(mock) @@ -109,7 +109,7 @@ Test('/settlements/{settlementId}/participants/{participantId}', async (settleme try { const requests = new Promise((resolve, reject) => { Mockgen().requests({ - path: '/settlements/{settlementId}/participants/{participantId}', + path: '/settlements/{sid}/participants/{pid}', operation: 'get' }, function (error, mock) { return error ? reject(error) : resolve(mock) @@ -162,7 +162,7 @@ Test('/settlements/{settlementId}/participants/{participantId}', async (settleme try { const requests = new Promise((resolve, reject) => { Mockgen().requests({ - path: '/settlements/{settlementId}/participants/{participantId}', + path: '/settlements/{sid}/participants/{pid}', operation: 'put' }, function (error, mock) { return error ? reject(error) : resolve(mock) @@ -220,7 +220,7 @@ Test('/settlements/{settlementId}/participants/{participantId}', async (settleme try { const requests = new Promise((resolve, reject) => { Mockgen().requests({ - path: '/settlements/{settlementId}/participants/{participantId}', + path: '/settlements/{sid}/participants/{pid}', operation: 'put' }, function (error, mock) { return error ? reject(error) : resolve(mock) diff --git a/test/unit/handlers/settlements/{settlementId}/participants/{participantId}/accounts/{accountId}.test.js b/test/unit/api/handlers/settlements/{settlementId}/participants/{participantId}/accounts/{accountId}.test.js similarity index 91% rename from test/unit/handlers/settlements/{settlementId}/participants/{participantId}/accounts/{accountId}.test.js rename to test/unit/api/handlers/settlements/{settlementId}/participants/{participantId}/accounts/{accountId}.test.js index faae8fb9..8b2de799 100644 --- a/test/unit/handlers/settlements/{settlementId}/participants/{participantId}/accounts/{accountId}.test.js +++ b/test/unit/api/handlers/settlements/{settlementId}/participants/{participantId}/accounts/{accountId}.test.js @@ -27,20 +27,20 @@ const Test = require('tapes')(require('tape')) const Sinon = require('sinon') -const Mockgen = require('../../../../../../../data/mockgen.js') -const InitServer = require('./../../../../../../../../src/setup').initialize -const Enums = require('./../../../../../../../../src/models/lib/enums') +const Mockgen = require('../../../../../../../../data/mockgen.js') +const Base = require('../../../../../../../base') +const Enums = require('../../../../../../../../../src/models/lib/enums') const Logger = require('@mojaloop/central-services-logger') -const settlement = require('./../../../../../../../../src/domain/settlement') -const Db = require('./../../../../../../../../src/lib/db') +const settlement = require('../../../../../../../../../src/domain/settlement/index') +const Db = require('../../../../../../../../../src/lib/db') -Test('/settlements/{settlementId}/participants/{participantId}/account/{accountId}', async (settlementTest) => { +Test('/settlements/{sid}/participants/{pid}/account/{aid}', async (settlementTest) => { let server let sandbox settlementTest.beforeEach(async t => { sandbox = Sinon.createSandbox() sandbox.stub(Db, 'connect').returns(Promise.resolve({})) - server = await InitServer() + server = await Base.setup() t.end() }) @@ -57,7 +57,7 @@ Test('/settlements/{settlementId}/participants/{participantId}/account/{accountI try { const requests = new Promise((resolve, reject) => { Mockgen().requests({ - path: '/settlements/{settlementId}/participants/{participantId}/accounts/{accountId}', + path: '/settlements/{sid}/participants/{pid}/accounts/{aid}', operation: 'get' }, function (error, mock) { return error ? reject(error) : resolve(mock) @@ -105,7 +105,7 @@ Test('/settlements/{settlementId}/participants/{participantId}/account/{accountI try { const requests = new Promise((resolve, reject) => { Mockgen().requests({ - path: '/settlements/{settlementId}/participants/{participantId}/accounts/{accountId}', + path: '/settlements/{sid}/participants/{pid}/accounts/{aid}', operation: 'get' }, function (error, mock) { return error ? reject(error) : resolve(mock) @@ -158,7 +158,7 @@ Test('/settlements/{settlementId}/participants/{participantId}/account/{accountI try { const requests = new Promise((resolve, reject) => { Mockgen().requests({ - path: '/settlements/{settlementId}/participants/{participantId}/accounts/{accountId}', + path: '/settlements/{sid}/participants/{pid}/accounts/{aid}', operation: 'put' }, function (error, mock) { return error ? reject(error) : resolve(mock) @@ -212,7 +212,7 @@ Test('/settlements/{settlementId}/participants/{participantId}/account/{accountI try { const requests = new Promise((resolve, reject) => { Mockgen().requests({ - path: '/settlements/{settlementId}/participants/{participantId}/accounts/{accountId}', + path: '/settlements/{sid}/participants/{pid}/accounts/{aid}', operation: 'put' }, function (error, mock) { return error ? reject(error) : resolve(mock) diff --git a/test/unit/base.js b/test/unit/base.js new file mode 100644 index 00000000..c0e84528 --- /dev/null +++ b/test/unit/base.js @@ -0,0 +1,42 @@ +/***** + License + -------------- + Copyright © 2017 Bill & Melinda Gates Foundation + The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + + Contributors + -------------- + This is the official list of the Mojaloop project contributors for this file. + Names of the original copyright holders (individuals or organizations) + should be listed with a '*' in the first column. People who have + contributed from an organization can be listed under the organization + that actually holds the copyright for their contributions (see the + Gates Foundation organization for an example). Those individuals should have + their names indented and be marked with a '-'. Email address can be added + optionally within square brackets . + + * Gates Foundation + - Name Surname + + * ModusBox + - Georgi Georgiev + -------------- + ******/ +'use strict' + +const ServerSetup = require('../../src/shared/setup') +const ApiRoutes = require('../../src/api/routes') +const getPort = require('get-port') + +const setup = async () => { + const port = await getPort() + return ServerSetup.createServer(port, [ApiRoutes]) +} + +module.exports = { + setup +} diff --git a/test/unit/domain/settlement/index.test.js b/test/unit/domain/settlement/index.test.js index 5ed39b4a..36f7003a 100644 --- a/test/unit/domain/settlement/index.test.js +++ b/test/unit/domain/settlement/index.test.js @@ -18,19 +18,21 @@ * Gates Foundation - Name Surname - * Georgi Georgiev - * Valentin Genev + * ModusBox + - Georgi Georgiev + - Valentin Genev -------------- ******/ - 'use strict' -const Test = require('tapes')(require('tape')) -const Sinon = require('sinon') const Logger = require('@mojaloop/central-services-logger') -const SettlementService = require('../../../../src/domain/settlement') +const Sinon = require('sinon') const SettlementModel = require('../../../../src/models/settlement') +const SettlementModelModel = require('../../../../src/models/settlement/settlementModel') +const SettlementService = require('../../../../src/domain/settlement') +const SettlementWindowContentModel = require('../../../../src/models/settlementWindowContent') const SettlementWindowModel = require('../../../../src/models/settlementWindow') +const Test = require('tapes')(require('tape')) Test('SettlementService', async (settlementServiceTest) => { let sandbox @@ -243,6 +245,7 @@ Test('SettlementService', async (settlementServiceTest) => { await settlementServiceTest.test('settlementEventTrigger should', async settlementEventTriggerTest => { try { const params = { + settlementModel: 'DEFERRED_NET', reason: 'settlement trigger', settlementWindows: [ { id: 1 }, @@ -252,13 +255,36 @@ Test('SettlementService', async (settlementServiceTest) => { const enums = { settlementWindowStates: { CLOSED: 'CLOSED' + }, + settlementGranularity: { + NET: 'NET' + }, + settlementInterchange: { + MULTILATERAL: 'MULTILATERAL' + }, + settlementDelay: { + DEFERRED: 'DEFERRED' } } const options = { logger: Logger } - const settlementWindowsMock = [{ state: 'CLOSED' }, { state: 'CLOSED' }] + const settlementModelDataMock = [null, { + settlementGranularityId: 'GROSS' + }, { + settlementGranularityId: 'NET', + settlementInterchangeId: 'BILATERAL' + }, { + settlementGranularityId: 'NET', + settlementInterchangeId: 'MULTILATERAL', + settlementDelayId: 'IMMEDIATE' + }, { + settlementGranularityId: 'NET', + settlementInterchangeId: 'MULTILATERAL', + settlementDelayId: 'DEFERRED' + }] + const settlementWindowsMock = [{ settlementWindowId: 1, state: 'CLOSED' }, { settlementWindowId: 2, state: 'CLOSED' }] const settlementIdMock = 1 const settlementMock = { settlementId: settlementIdMock, @@ -278,6 +304,22 @@ Test('SettlementService', async (settlementServiceTest) => { id: 2, state: 'PENDING_SETTLEMENT' }] + const settlementWindowContentMock = [{ + settlementWindowId: 1, + ledgerAccountTypeId: 1, + currencyId: 'USD', + settlementId: null + }, { + settlementWindowId: 1, + ledgerAccountTypeId: 6, + currencyId: 'USD', + settlementId: null + }, { + settlementWindowId: 2, + ledgerAccountTypeId: 1, + currencyId: 'USD', + settlementId: null + }] const participantCurrenciesListMock = [{ id: 1, participantCurrencyId: 1, @@ -298,10 +340,12 @@ Test('SettlementService', async (settlementServiceTest) => { await settlementEventTriggerTest.test('create new settlement and return it', async test => { try { + SettlementModelModel.getByName = sandbox.stub().returns(settlementModelDataMock[settlementModelDataMock.length - 1]) SettlementWindowModel.getByListOfIds = sandbox.stub().returns(settlementWindowsMock) - SettlementModel.triggerEvent = sandbox.stub().returns(settlementIdMock) + SettlementModel.triggerSettlementEvent = sandbox.stub().returns(settlementIdMock) SettlementModel.getById = sandbox.stub().returns(settlementMock) SettlementWindowModel.getBySettlementId = sandbox.stub().returns(settlementWindowsListMock) + SettlementWindowContentModel.getBySettlementId = sandbox.stub().returns(settlementWindowContentMock) SettlementModel.settlementParticipantCurrency = { getParticipantCurrencyBySettlementId: sandbox.stub().returns(participantCurrenciesListMock) } @@ -309,8 +353,9 @@ Test('SettlementService', async (settlementServiceTest) => { test.ok(result, 'Result returned') const idList = [1, 2] const reason = params.reason - test.ok(SettlementWindowModel.getByListOfIds.withArgs(idList, enums.settlementWindowStates).calledOnce, 'SettlementWindowModel.getByListOfIds with args ... called once') - test.ok(SettlementModel.triggerEvent.withArgs({ idList, reason }, enums).calledOnce, 'SettlementModel.triggerEvent with args ... called once') + const settlementModelData = settlementModelDataMock[settlementModelDataMock.length - 1] + test.ok(SettlementWindowModel.getByListOfIds.withArgs(idList, settlementModelData, enums.settlementWindowStates).calledOnce, 'SettlementWindowModel.getByListOfIds with args ... called once') + test.ok(SettlementModel.triggerSettlementEvent.withArgs({ idList, reason }, settlementModelData, enums).calledOnce, 'SettlementModel.triggerEvent with args ... called once') test.ok(SettlementWindowModel.getBySettlementId.withArgs({ settlementId: settlementIdMock }).calledOnce, 'SettlementWindowModel.getBySettlementId with args ... called once') test.ok(SettlementModel.settlementParticipantCurrency.getParticipantCurrencyBySettlementId.withArgs({ settlementId: settlementIdMock }).calledOnce, 'SettlementModel.spc.getParticipantCurrencyBySettlementId w/ args ... called once') test.equal(result.participants[0].accounts[1].state, participantCurrenciesListMock[1].state, 'Result property matched') @@ -330,20 +375,33 @@ Test('SettlementService', async (settlementServiceTest) => { test.end() } catch (err) { Logger.error(`settlementEventTriggerTest failed with error - ${err}`) - test.equal(err.message, 'At least one provided settlement window does not exist', `Error "${err.message}" thrown`) + test.equal(err.message, 'Inapplicable windows 1, 2', `Error "${err.message}" thrown`) + test.end() + } + }) + + await settlementEventTriggerTest.test('throw when settlement model is not found', async test => { + try { + SettlementModelModel.getByName = sandbox.stub().returns(settlementModelDataMock[0]) + await SettlementService.settlementEventTrigger(params, enums) + test.fail('Error not thrown!') + test.end() + } catch (err) { + Logger.error(`settlementEventTriggerTest failed with error - ${err}`) + test.equal(err.message, 'Settlement model not found', `Error "${err.message}" thrown`) test.end() } }) - await settlementEventTriggerTest.test('throw if any input window is not CLOSED', async test => { + await settlementEventTriggerTest.test('throw when invalid settlement model is specified', async test => { try { - SettlementWindowModel.getByListOfIds = sandbox.stub().returns([{ state: 'CLOSED' }, { state: 'OPEN' }]) + SettlementModelModel.getByName = sandbox.stub().returns(settlementModelDataMock[1]) await SettlementService.settlementEventTrigger(params, enums) test.fail('Error not thrown!') test.end() } catch (err) { Logger.error(`settlementEventTriggerTest failed with error - ${err}`) - test.equal(err.message, 'At least one settlement window is not in closed or aborted state', `Error "${err.message}" thrown`) + test.equal(err.message, 'Settlement can not be created for GROSS or IMMEDIATE models', `Error "${err.message}" thrown`) test.end() } }) diff --git a/test/unit/domain/settlementWindow/index.test.js b/test/unit/domain/settlementWindow/index.test.js index 419f7fe3..a8bafdc5 100644 --- a/test/unit/domain/settlementWindow/index.test.js +++ b/test/unit/domain/settlementWindow/index.test.js @@ -20,6 +20,7 @@ * Georgi Georgiev * Valentin Genev + * Lazola Lucas -------------- ******/ @@ -30,6 +31,8 @@ const Sinon = require('sinon') const Logger = require('@mojaloop/central-services-logger') const SettlementWindowService = require('../../../../src/domain/settlementWindow') const SettlementWindowModel = require('../../../../src/models/settlementWindow') +const SettlementWindowContentModel = require('../../../../src/models/settlementWindowContent') +const Producer = require('@mojaloop/central-services-stream').Util.Producer Test('SettlementWindowService', async (settlementWindowServiceTest) => { let sandbox @@ -49,14 +52,17 @@ Test('SettlementWindowService', async (settlementWindowServiceTest) => { const params = { settlementWindowId: 1 } const enums = {} const options = { logger: Logger } - const settlementWindowMock = { settlementWindowId: 1 } + const settlementWindowMock = { settlementWindowId: 1, content: { id: 11 } } + const settlementWindowContentMock = { id: 11 } await getByIdTest.test('return settlement window', async test => { try { + SettlementWindowContentModel.getBySettlementWindowId = sandbox.stub().returns(settlementWindowContentMock) SettlementWindowModel.getById = sandbox.stub().returns(settlementWindowMock) + const result = await SettlementWindowService.getById(params, enums, options) test.ok(result, 'Result returned') - test.ok(SettlementWindowModel.getById.withArgs(params, enums).calledOnce, 'SettlementWindowModel.getById with args ... called once') + test.ok(SettlementWindowModel.getById.withArgs(params).calledOnce, 'SettlementWindowModel.getById with args ... called once') SettlementWindowModel.getById = sandbox.stub().returns() try { @@ -64,7 +70,72 @@ Test('SettlementWindowService', async (settlementWindowServiceTest) => { test.fail('Error expected, but not thrown!') } catch (err) { test.ok(err instanceof Error, `Error ${err.message} thrown`) - test.ok(SettlementWindowModel.getById.withArgs(params, enums).calledOnce, 'SettlementWindowModel.getById with args ... called once') + test.ok(SettlementWindowModel.getById.withArgs(params).calledOnce, 'SettlementWindowModel.getById with args ... called once') + } + test.end() + } catch (err) { + Logger.error(`getByIdTest failed with error - ${err}`) + test.fail() + test.end() + } + }) + + await getByIdTest.end() + } catch (err) { + Logger.error(`settlementWindowServiceTest failed with error - ${err}`) + getByIdTest.fail() + getByIdTest.end() + } + }) + + await settlementWindowServiceTest.test('getById should throw an error if no settlement window content is undefined', async getByIdTest => { + try { + const params = { settlementWindowId: 1 } + const enums = {} + const settlementWindowMock = { settlementWindowId: 1, content: { id: 11 } } + + await getByIdTest.test('Throw an error when settlement window content is undefined', async test => { + try { + SettlementWindowContentModel.getBySettlementWindowId = sandbox.stub().returns(undefined) + SettlementWindowModel.getById = sandbox.stub().returns(settlementWindowMock) + try { + await SettlementWindowService.getById(params, enums) + test.fail('Error expected, but not thrown!') + } catch (err) { + test.equal(err.message, 'No records for settlementWidowContentId : 1 found') + } + test.end() + } catch (err) { + Logger.error(`getByIdTest failed with error - ${err}`) + test.fail() + test.end() + } + }) + + await getByIdTest.end() + } catch (err) { + Logger.error(`settlementWindowServiceTest failed with error - ${err}`) + getByIdTest.fail() + getByIdTest.end() + } + }) + + await settlementWindowServiceTest.test('getById should throw an error if no settlement window content is found', async getByIdTest => { + try { + const params = { settlementWindowId: 1 } + const enums = {} + const settlementWindowMock = { settlementWindowId: 1, content: { } } + const settlementWindowContentMock = [] + + await getByIdTest.test('Throw an error when settlement window content is undefined', async test => { + try { + SettlementWindowContentModel.getBySettlementId = sandbox.stub().returns(settlementWindowContentMock) + SettlementWindowModel.getById = sandbox.stub().returns(settlementWindowMock) + try { + await SettlementWindowService.getById(params, enums) + test.pass('Error expected, but not thrown!') + } catch (err) { + test.equal(err.message, 'No records for settlementWidowContentId : 1 found') } test.end() } catch (err) { @@ -88,9 +159,13 @@ Test('SettlementWindowService', async (settlementWindowServiceTest) => { const enums = {} const options = { logger: Logger } const settlementWindowsMock = [{ settlementWindowId: 1 }, { settlementWindowId: 2 }] + const settlementWindowMock = { settlementWindowId: 1, content: { id: 11 } } + const settlementWindowContentMock = { id: 11 } await getByParamsTest.test('return settlement windows', async test => { try { + SettlementWindowContentModel.getBySettlementWindowId = sandbox.stub().returns(settlementWindowContentMock) + SettlementWindowModel.getById = sandbox.stub().returns(settlementWindowMock) SettlementWindowModel.getByParams = sandbox.stub().returns(settlementWindowsMock) const result = await SettlementWindowService.getByParams(params, enums, options) test.ok(result, 'Result returned') @@ -129,45 +204,128 @@ Test('SettlementWindowService', async (settlementWindowServiceTest) => { } }) - await settlementWindowServiceTest.test('close should', async closeTest => { + await settlementWindowServiceTest.test('getByParams should fail when no content is found', async getByParamsTest => { + try { + let params = { query: { participantId: 1, state: 'PENDING_SETTLEMENT' } } + const enums = {} + const options = { logger: Logger } + const settlementWindowsMock = [{ settlementWindowId: 1 }, { settlementWindowId: 2 }] + const settlementWindowMock = { settlementWindowId: 1, content: { id: 11 } } + + await getByParamsTest.test('return settlement windows', async test => { + try { + SettlementWindowContentModel.getBySettlementWindowId = sandbox.stub().returns(undefined) + SettlementWindowModel.getById = sandbox.stub().returns(settlementWindowMock) + SettlementWindowModel.getByParams = sandbox.stub().returns(settlementWindowsMock) + try { + await SettlementWindowService.getByParams(params, enums, options) + test.fail('Error expected, but not thrown!') + } catch (err) { + test.equal(err.message, 'No records for settlementWidowContentId : 1 found') + } + SettlementWindowModel.getByParams = sandbox.stub().returns() + try { + await SettlementWindowService.getByParams(params, enums) + test.fail('Error expected, but not thrown!') + } catch (err) { + test.ok(err instanceof Error, `Error "${err.message}" thrown as expected`) + test.ok(SettlementWindowModel.getByParams.withArgs(params, enums).calledOnce, 'SettlementWindowModel.getByParams with args ... called once') + } + + params = { query: {} } + try { + await SettlementWindowService.getByParams(params, enums) + test.fail('Error expected, but not thrown!') + } catch (err) { + test.pass(`Error "${err.message.substr(0, 50)} ..." thrown as expected`) + } + + test.end() + } catch (err) { + Logger.error(`getByParamsTest failed with error - ${err}`) + test.fail() + test.end() + } + }) + + await getByParamsTest.end() + } catch (err) { + Logger.error(`settlementWindowServiceTest failed with error - ${err}`) + getByParamsTest.fail() + getByParamsTest.end() + } + }) + + await settlementWindowServiceTest.test('process should', async processTest => { try { const params = { id: 1 } const enums = {} const options = { logger: Logger } const settlementWindowIdMock = 1 - const settlementWindowMock = { settlementWindowId: settlementWindowIdMock, state: 'CLOSED' } + const settlementWindowMock = { settlementWindowId: settlementWindowIdMock, state: 'PROCESSING' } + Producer.produceMessage = sandbox.stub() - await closeTest.test('close settlement window and return it', async test => { + await processTest.test('process settlement window and return it', async test => { try { - SettlementWindowModel.close = sandbox.stub().returns(settlementWindowIdMock) + SettlementWindowModel.process = sandbox.stub().returns(settlementWindowIdMock) + SettlementWindowModel.getById = sandbox.stub().returns(settlementWindowMock) - const result = await SettlementWindowService.close(params, enums, options) + + const result = await SettlementWindowService.process(params, enums, options) test.ok(result, 'Result returned') - test.ok(SettlementWindowModel.close.withArgs(params, enums).calledOnce, 'SettlementWindowModel.close with args ... called once') + test.ok(SettlementWindowModel.process.withArgs(params, enums).calledOnce, 'SettlementWindowModel.process with args ... called once') test.ok(SettlementWindowModel.getById.withArgs({ settlementWindowId: settlementWindowIdMock }, enums).calledOnce, 'SettlementWindowModel.getById with args ... called once') - SettlementWindowModel.close = sandbox.stub().throws(new Error('Error occurred')) + SettlementWindowModel.process = sandbox.stub().throws(new Error('Error occurred')) try { - await SettlementWindowService.close(params, enums) + await SettlementWindowService.process(params, enums) test.fail('Error expected, but not thrown!') } catch (err) { test.equal(err.message, 'Error occurred', `Error "${err.message}" thrown as expected`) - test.ok(SettlementWindowModel.close.withArgs(params, enums).calledOnce, 'SettlementWindowModel.close with args ... called once') + test.ok(SettlementWindowModel.process.withArgs(params, enums).calledOnce, 'SettlementWindowModel.process with args ... called once') } test.end() } catch (err) { - Logger.error(`closeTest failed with error - ${err}`) + Logger.error(`processTest failed with error - ${err}`) test.fail() test.end() } }) - await closeTest.end() + await processTest.end() + } catch (err) { + Logger.error(`settlementWindowServiceTest failed with error - ${err}`) + processTest.fail() + processTest.end() + } + }) + + await settlementWindowServiceTest.test('close should', async processTest => { + try { + const settlementWindowIdMock = 1 + const settlementWindowMock = { settlementWindowId: settlementWindowIdMock, state: 'PROCESSING' } + Producer.produceMessage = sandbox.stub() + + await processTest.test('close settlement window and return it', async test => { + try { + SettlementWindowModel.close = sandbox.stub().returns(settlementWindowIdMock) + SettlementWindowModel.getById = sandbox.stub().returns(settlementWindowMock) + + const result = await SettlementWindowService.close(settlementWindowIdMock, '') + test.ok(result, 'Result returned') + test.end() + } catch (err) { + Logger.error(`processTest failed with error - ${err}`) + test.fail() + test.end() + } + }) + await processTest.end() } catch (err) { Logger.error(`settlementWindowServiceTest failed with error - ${err}`) - closeTest.fail() - closeTest.end() + processTest.fail() + processTest.end() } }) diff --git a/test/unit/handlers/index.test.js b/test/unit/handlers/index.test.js new file mode 100644 index 00000000..1cc6d768 --- /dev/null +++ b/test/unit/handlers/index.test.js @@ -0,0 +1,132 @@ +/***** + License + -------------- + Copyright © 2017 Bill & Melinda Gates Foundation + The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + Contributors + -------------- + This is the official list of the Mojaloop project contributors for this file. + Names of the original copyright holders (individuals or organizations) + should be listed with a '*' in the first column. People who have + contributed from an organization can be listed under the organization + that actually holds the copyright for their contributions (see the + Gates Foundation organization for an example). Those individuals should have + their names indented and be marked with a '-'. Email address can be added + optionally within square brackets . + * Gates Foundation + - Name Surname + + * Modusbox + - Deon Botha + - Georgi Georgiev + -------------- + ******/ + +'use strict' + +const Test = require('tapes')(require('tape')) +const Sinon = require('sinon') +const Config = require('../../../src/lib/config') +const Proxyquire = require('proxyquire') +const Routes = require('../../../src/api/routes') + +Test('cli', async (cliTest) => { + cliTest.test('Commander should', async (commanderTest) => { + let sandbox + let SetupStub + + commanderTest.beforeEach(test => { + sandbox = Sinon.createSandbox() + SetupStub = { + initialize: sandbox.stub().returns(Promise.resolve()) + } + process.argv = [] + Proxyquire.noPreserveCache() // enable no caching for module requires + test.end() + }) + + commanderTest.afterEach(test => { + sandbox.restore() + Proxyquire.preserveCache() + test.end() + }) + + commanderTest.test('start all handlers up via switches', async test => { + const argv = [ + 'node', + 'index.js', + 'handler', + '--settlementwindow' + ] + process.argv = argv + const settlementwindowHandler = { + type: 'settlementwindow', + enabled: true + } + const handlerList = [ + settlementwindowHandler + ] + const initOptions = { + service: 'handler', + port: Config.PORT, + modules: [Routes], + handlers: handlerList, + runHandlers: true + } + + const Handlers = Proxyquire('../../../src/handlers', { + '../shared/setup': SetupStub + }) + test.ok(Handlers) + test.ok(SetupStub.initialize.calledWith(initOptions)) + test.end() + }) + + commanderTest.test('init server without handlers', async test => { + const argv = [ + 'node', + 'index.js', + 'handler' + ] + process.argv = argv + const handlerList = [] + const initOptions = { + service: 'handler', + port: Config.PORT, + modules: [Routes], + handlers: handlerList, + runHandlers: true + } + + const Handlers = Proxyquire('../../../src/handlers', { + '../shared/setup': SetupStub + }) + test.ok(Handlers) + test.ok(SetupStub.initialize.calledWith(initOptions)) + test.end() + }) + + commanderTest.test('display help with invalid args', async test => { + const argv = [ + 'node', + 'index.js' + ] + process.argv = argv + sandbox.stub(process, 'exit') + + const Handlers = Proxyquire('../../../src/handlers', { + '../shared/setup': SetupStub + }) + test.ok(Handlers) + test.notok(SetupStub.initialize.called) + test.ok(process.exit.called) + test.end() + }) + + commanderTest.end() + }) + + cliTest.end() +}) diff --git a/test/unit/handlers/lib/healthCheck/subServiceHealth.test.js b/test/unit/handlers/lib/healthCheck/subServiceHealth.test.js deleted file mode 100644 index 19da1887..00000000 --- a/test/unit/handlers/lib/healthCheck/subServiceHealth.test.js +++ /dev/null @@ -1,75 +0,0 @@ -'use strict' - -const Test = require('tapes')(require('tape')) -const Sinon = require('sinon') -const { statusEnum, serviceName } = require('@mojaloop/central-services-shared').HealthCheck.HealthCheckEnums - -const MigrationLockModel = require('../../../../../src/models/misc/migrationLock') - -const { - getSubServiceHealthDatastore -} = require('../../../../../src/handlers/lib/healthCheck/subServiceHealth') - -Test('SubServiceHealth test', function (subServiceHealthTest) { - let sandbox - - subServiceHealthTest.beforeEach(t => { - sandbox = Sinon.createSandbox() - - t.end() - }) - - subServiceHealthTest.afterEach(t => { - sandbox.restore() - t.end() - }) - - subServiceHealthTest.test('getSubServiceHealthDatastore', datastoreTest => { - datastoreTest.test('datastore test passes when the database is not migration locked', async test => { - // Arrange - sandbox.stub(MigrationLockModel, 'getIsMigrationLocked').returns(false) - const expected = { name: serviceName.datastore, status: statusEnum.OK } - - // Act - const result = await getSubServiceHealthDatastore() - - // Assert - test.deepEqual(result, expected, 'getSubServiceHealthDatastore should match expected result') - test.ok(MigrationLockModel.getIsMigrationLocked.called) - test.end() - }) - - datastoreTest.test('datastore test fails when the database is migration locked', async test => { - // Arrange - - sandbox.stub(MigrationLockModel, 'getIsMigrationLocked').returns(true) - const expected = { name: serviceName.datastore, status: statusEnum.DOWN } - - // Act - const result = await getSubServiceHealthDatastore() - - // Assert - test.deepEqual(result, expected, 'getSubServiceHealthDatastore should match expected result') - test.ok(MigrationLockModel.getIsMigrationLocked.called) - test.end() - }) - - datastoreTest.test('datastore test fails when getIsMigrationLocked throws', async test => { - // Arrange - sandbox.stub(MigrationLockModel, 'getIsMigrationLocked').throws(new Error('Error connecting to db')) - const expected = { name: serviceName.datastore, status: statusEnum.DOWN } - - // Act - const result = await getSubServiceHealthDatastore() - - // Assert - test.deepEqual(result, expected, 'getSubServiceHealthDatastore should match expected result') - test.ok(MigrationLockModel.getIsMigrationLocked.called) - test.end() - }) - - datastoreTest.end() - }) - - subServiceHealthTest.end() -}) diff --git a/test/unit/handlers/register.test.js b/test/unit/handlers/register.test.js new file mode 100644 index 00000000..0a61278c --- /dev/null +++ b/test/unit/handlers/register.test.js @@ -0,0 +1,111 @@ +/***** + License + -------------- + Copyright © 2017 Bill & Melinda Gates Foundation + The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + Contributors + -------------- + This is the official list of the Mojaloop project contributors for this file. + Names of the original copyright holders (individuals or organizations) + should be listed with a '*' in the first column. People who have + contributed from an organization can be listed under the organization + that actually holds the copyright for their contributions (see the + Gates Foundation organization for an example). Those individuals should have + their names indented and be marked with a '-'. Email address can be added + optionally within square brackets . + * Gates Foundation + - + * Modusbox + - Name Surname + - Georgi Georgiev + - Deon Botha + -------------- + ******/ + +'use strict' + +const Test = require('tapes')(require('tape')) +const Sinon = require('sinon') +const Handlers = require('../../../src/handlers/register') +const Proxyquire = require('proxyquire') + +const SettlementWindowHandlers = require('../../../src/handlers/settlementWindow/handler') + +Test('handlers', handlersTest => { + let sandbox + + handlersTest.beforeEach(test => { + sandbox = Sinon.createSandbox() + sandbox.stub(SettlementWindowHandlers, 'registerAllHandlers').returns(Promise.resolve(true)) + test.end() + }) + + handlersTest.afterEach(test => { + sandbox.restore() + test.end() + }) + + handlersTest.test('handlers test should', registerAllTest => { + registerAllTest.test('register all handlers', async (test) => { + const result = await Handlers.registerAllHandlers() + test.equal(result, true) + test.end() + }) + + registerAllTest.test('throw error on Handlers.registerAllHandlers', async (test) => { + const errorMessage = 'require-glob Stub ERROR' + const HandlersStub = Proxyquire('../../../src/handlers/register', { + 'require-glob': sandbox.stub().throws(new Error(errorMessage)) + }) + + try { + await HandlersStub.registerAllHandlers() + test.fail('Error not thrown') + test.end() + } catch (e) { + test.equal(e.message, errorMessage) + test.pass('Error thrown') + test.end() + } + }) + + registerAllTest.test('Register without correct "handler" module', async (test) => { + const HandlersStub = Proxyquire('../../../src/handlers/register', { + 'require-glob': sandbox.stub().resolves([{ + settlementWindow: { + handler: { + test1: {} + } + } + }]) + }) + + try { + await HandlersStub.registerAllHandlers() + test.fail('Error thrown') + test.end() + } catch (e) { + test.pass() + test.end() + } + }) + + registerAllTest.test('throw error when transfer handler throws error', async (test) => { + try { + sandbox.stub(SettlementWindowHandlers, 'registerAllHandlers').throws(new Error()) + await Handlers.registerAllHandlers() + test.fail('Error not thrown') + test.end() + } catch (e) { + test.pass('Error thrown') + test.end() + } + }) + + registerAllTest.end() + }) + + handlersTest.end() +}) diff --git a/test/unit/handlers/settlementWindow/handler.test.js b/test/unit/handlers/settlementWindow/handler.test.js new file mode 100644 index 00000000..85a72783 --- /dev/null +++ b/test/unit/handlers/settlementWindow/handler.test.js @@ -0,0 +1,334 @@ +/***** + License + -------------- + Copyright © 2017 Bill & Melinda Gates Foundation + The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + Contributors + -------------- + This is the official list of the Mojaloop project contributors for this file. + Names of the original copyright holders (individuals or organizations) + should be listed with a '*' in the first column. People who have + contributed from an organization can be listed under the organization + that actually holds the copyright for their contributions (see the + Gates Foundation organization for an example). Those individuals should have + their names indented and be marked with a '-'. Email address can be added + optionally within square brackets . + * Gates Foundation + - Name Surname + + * ModusBox + - Georgi Georgiev + - Lazola Lucas + -------------- + ******/ +'use strict' + +const Test = require('tapes')(require('tape')) +const Sinon = require('sinon') +const Util = require('@mojaloop/central-services-shared').Util +const Kafka = require('@mojaloop/central-services-shared').Util.Kafka +const Consumer = require('@mojaloop/central-services-stream').Util.Consumer +const KafkaConsumer = require('@mojaloop/central-services-stream').Kafka.Consumer +const Uuid = require('uuid4') +const Enum = require('@mojaloop/central-services-shared').Enum +const SettlementWindowService = require('../../../../src/domain/settlementWindow/index') +const SettlementWindowHandler = require('../../../../src/handlers/settlementWindow/handler') +const Proxyquire = require('proxyquire') + +const payload = { + settlementWindowId: '3', + reason: 'test' +} +const transfer = { + transferId: 'b51ec534-ee48-4575-b6a9-ead2955b8999', + payerFsp: 'dfsp1', + payeeFsp: 'dfsp2', + amount: { + currency: 'USD', + amount: '433.88' + }, + ilpPacket: 'AYIBgQAAAAAAAASwNGxldmVsb25lLmRmc3AxLm1lci45T2RTOF81MDdqUUZERmZlakgyOVc4bXFmNEpLMHlGTFGCAUBQU0svMS4wCk5vbmNlOiB1SXlweUYzY3pYSXBFdzVVc05TYWh3CkVuY3J5cHRpb246IG5vbmUKUGF5bWVudC1JZDogMTMyMzZhM2ItOGZhOC00MTYzLTg0NDctNGMzZWQzZGE5OGE3CgpDb250ZW50LUxlbmd0aDogMTM1CkNvbnRlbnQtVHlwZTogYXBwbGljYXRpb24vanNvbgpTZW5kZXItSWRlbnRpZmllcjogOTI4MDYzOTEKCiJ7XCJmZWVcIjowLFwidHJhbnNmZXJDb2RlXCI6XCJpbnZvaWNlXCIsXCJkZWJpdE5hbWVcIjpcImFsaWNlIGNvb3BlclwiLFwiY3JlZGl0TmFtZVwiOlwibWVyIGNoYW50XCIsXCJkZWJpdElkZW50aWZpZXJcIjpcIjkyODA2MzkxXCJ9IgA', + condition: 'YlK5TZyhflbXaDRPtR5zhCu8FrbgvrQwwmzuH0iQ0AI', + expiration: '2016-05-24T08:38:08.699-04:00', + extensionList: { + extension: [ + { + key: 'key1', + value: 'value1' + }, + { + key: 'key2', + value: 'value2' + } + ] + } +} + +const messageProtocol = { + id: Uuid(), + from: transfer.payerFsp, + to: transfer.payeeFsp, + type: 'application/json', + content: { + headers: { 'fspiop-destination': transfer.payerFsp }, + uriParams: { id: transfer.transferId }, + payload + }, + metadata: { + event: { + id: Uuid(), + type: 'settlement', + action: 'close', + createdAt: new Date(), + state: { + status: 'success', + code: 0 + } + } + }, + pp: '' +} + +const messageProtocolActionNotClosed = { + id: Uuid(), + from: transfer.payerFsp, + to: transfer.payeeFsp, + type: 'application/json', + content: { + headers: { 'fspiop-destination': transfer.payerFsp }, + uriParams: { id: transfer.transferId }, + payload + }, + metadata: { + event: { + id: Uuid(), + type: 'settlement', + action: 'notClose', + createdAt: new Date(), + state: { + status: 'success', + code: 0 + } + } + }, + pp: '' +} + +const messageProtocolMissingPayload = { + id: Uuid(), + from: transfer.payerFsp, + to: transfer.payeeFsp, + type: 'application/json', + content: { + headers: { 'fspiop-destination': transfer.payerFsp }, + uriParams: { id: transfer.transferId } + }, + metadata: { + event: { + id: Uuid(), + type: 'settlement', + action: 'close', + createdAt: new Date(), + state: { + status: 'success', + code: 0 + } + } + }, + pp: '' +} + +const topicName = 'topic-test' + +const messages = [ + { + topic: topicName, + value: messageProtocol + } +] + +const messagesActionNotClosed = [ + { + topic: topicName, + value: messageProtocolActionNotClosed + } +] +const messagesMissingPayload = [ + { + topic: topicName, + value: messageProtocolMissingPayload + } +] + +const config = { + options: { + mode: 2, + batchSize: 1, + pollFrequency: 10, + recursiveTimeout: 100, + messageCharset: 'utf8', + messageAsJSON: true, + sync: true, + consumeTimeout: 1000 + }, + rdkafkaConf: { + 'client.id': 'kafka-test', + debug: 'all', + 'group.id': 'central-ledger-kafka', + 'metadata.broker.list': 'localhost:9092', + 'enable.auto.commit': false + } +} + +const settlementWindow = { + state: Enum.Settlements.SettlementWindowState.CLOSED +} + +const command = () => {} + +Test('SettlementWindowHandler', async (settlementWindowHandlerTest) => { + let sandbox + + settlementWindowHandlerTest.beforeEach(test => { + sandbox = Sinon.createSandbox() + sandbox.stub(KafkaConsumer.prototype, 'constructor').returns(Promise.resolve()) + sandbox.stub(KafkaConsumer.prototype, 'connect').returns(Promise.resolve()) + sandbox.stub(KafkaConsumer.prototype, 'consume').returns(Promise.resolve()) + sandbox.stub(KafkaConsumer.prototype, 'commitMessageSync').returns(Promise.resolve()) + sandbox.stub(Consumer, 'getConsumer').returns({ + commitMessageSync: async function () { + return true + } + }) + sandbox.stub(Consumer, 'isConsumerAutoCommitEnabled').returns(false) + sandbox.stub(Kafka) + sandbox.stub(Util.StreamingProtocol) + sandbox.stub(SettlementWindowService) + Kafka.produceGeneralMessage.returns(Promise.resolve()) + test.end() + }) + + settlementWindowHandlerTest.afterEach(test => { + sandbox.restore() + test.end() + }) + + settlementWindowHandlerTest.test('closeSettlementWindow should', closeSettlementWindowTest => { + closeSettlementWindowTest.test('close the SettlementWindow when messages is in array', async (test) => { + const localMessages = Util.clone(messages) + await Consumer.createHandler(topicName, config, command) + Kafka.transformAccountToTopicName.returns(topicName) + Kafka.proceed.returns(true) + SettlementWindowService.close.returns(Promise.resolve(settlementWindow)) + const result = await SettlementWindowHandler.closeSettlementWindow(null, localMessages) + test.equal(result, true) + test.end() + }) + closeSettlementWindowTest.test('close the SettlementWindow when there is a single message', async (test) => { + const localMessages = Util.clone(messages) + await Consumer.createHandler(topicName, config, command) + Kafka.transformAccountToTopicName.returns(topicName) + Kafka.proceed.returns(true) + SettlementWindowService.close.returns(Promise.resolve(settlementWindow)) + const result = await SettlementWindowHandler.closeSettlementWindow(null, localMessages[0]) + test.equal(result, true) + test.end() + }) + closeSettlementWindowTest.test('retry when the settlement window state is not CLOSED', async (test) => { + const openSettlementWindow = { + state: Enum.Settlements.SettlementWindowState.OPEN + } + const localMessages = Util.clone(messages) + await Consumer.createHandler(topicName, config, command) + Kafka.transformAccountToTopicName.returns(topicName) + Kafka.proceed.returns(true) + SettlementWindowService.close.returns(Promise.resolve(openSettlementWindow)) + + const retryStub = sandbox.stub().callsArg(0) + const SettlementWindowHandlerProxy = Proxyquire('../../../../src/handlers/settlementWindow/handler', { + 'async-retry': retryStub + }) + + const result = await SettlementWindowHandlerProxy.closeSettlementWindow(null, localMessages[0]) + test.equal(result, true) + test.end() + }) + closeSettlementWindowTest.test('retry when the settlement window state is not CLOSED and the action is not closed', async (test) => { + const openSettlementWindow = { + state: Enum.Settlements.SettlementWindowState.OPEN + } + const localMessages = Util.clone(messagesActionNotClosed) + await Consumer.createHandler(topicName, config, command) + Kafka.transformAccountToTopicName.returns(topicName) + Kafka.proceed.returns(true) + SettlementWindowService.close.returns(Promise.resolve(openSettlementWindow)) + + const retryStub = sandbox.stub().callsArg(0) + const SettlementWindowHandlerProxy = Proxyquire('../../../../src/handlers/settlementWindow/handler', { + 'async-retry': retryStub + }) + + const result = await SettlementWindowHandlerProxy.closeSettlementWindow(null, localMessages[0]) + test.equal(result, true) + test.end() + }) + closeSettlementWindowTest.test('throw error when there is an error', async (test) => { + try { + await Consumer.createHandler(topicName, config, command) + Kafka.proceed.returns(false) + SettlementWindowService.close.returns(Promise.resolve(settlementWindow)) + await SettlementWindowHandler.closeSettlementWindow(new Error(), null) + test.fail('should throw') + test.end() + } catch (e) { + test.ok('Error is thrown') + test.end() + } + }) + + closeSettlementWindowTest.test('throw -Settlement window handler missing payload- when the payload is missing', async (test) => { + const localMessages = Util.clone(messagesMissingPayload) + await Consumer.createHandler(topicName, config, command) + Kafka.transformAccountToTopicName.returns(topicName) + Kafka.proceed.returns(true) + SettlementWindowService.close.returns(Promise.resolve(settlementWindow)) + const result = await SettlementWindowHandler.closeSettlementWindow(null, localMessages) + test.equal(result, true) + test.end() + }) + + closeSettlementWindowTest.end() + }) + settlementWindowHandlerTest.test('registerAllHandlers should', registerAllHandlersTest => { + registerAllHandlersTest.test('register all consumers on Kafka', async (test) => { + await Consumer.createHandler(topicName, config, command) + Kafka.transformAccountToTopicName.returns(topicName) + Kafka.proceed.returns(true) + Kafka.transformGeneralTopicName.returns(topicName) + Kafka.getKafkaConfig.returns(config) + const result = await SettlementWindowHandler.registerAllHandlers() + test.equal(result, true) + test.end() + }) + settlementWindowHandlerTest.test('throw error registerAllHandlers', async (test) => { + try { + await Consumer.createHandler(topicName, config, command) + Kafka.transformAccountToTopicName.returns(topicName) + Kafka.proceed.returns(true) + Kafka.transformGeneralTopicName.returns(topicName) + Kafka.getKafkaConfig.throws(new Error()) + + await SettlementWindowHandler.registerAllHandlers() + test.fail('Error not thrown') + test.end() + } catch (e) { + test.pass('Error thrown') + test.end() + } + }) + registerAllHandlersTest.end() + }) + settlementWindowHandlerTest.end() +}) diff --git a/test/unit/lib/healthCheck/subServiceHealth.test.js b/test/unit/lib/healthCheck/subServiceHealth.test.js new file mode 100644 index 00000000..c9ac8628 --- /dev/null +++ b/test/unit/lib/healthCheck/subServiceHealth.test.js @@ -0,0 +1,150 @@ +/***** + License + -------------- + Copyright © 2017 Bill & Melinda Gates Foundation + The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + Contributors + -------------- + This is the official list of the Mojaloop project contributors for this file. + Names of the original copyright holders (individuals or organizations) + should be listed with a '*' in the first column. People who have + contributed from an organization can be listed under the organization + that actually holds the copyright for their contributions (see the + Gates Foundation organization for an example). Those individuals should have + their names indented and be marked with a '-'. Email address can be added + optionally within square brackets . + * Gates Foundation + - Name Surname + + * ModusBox + - Georgi Georgiev + -------------- + ******/ +'use strict' + +const Test = require('tapes')(require('tape')) +const Sinon = require('sinon') + +const Consumer = require('@mojaloop/central-services-stream').Util.Consumer +const { statusEnum, serviceName } = require('@mojaloop/central-services-shared').HealthCheck.HealthCheckEnums + +const MigrationLockModel = require('../../../../src/models/misc/migrationLock') + +const { + getSubServiceHealthBroker, + getSubServiceHealthDatastore +} = require('../../../../src/lib/healthCheck/subServiceHealth') + +Test('SubServiceHealth test', function (subServiceHealthTest) { + let sandbox + + subServiceHealthTest.beforeEach(t => { + sandbox = Sinon.createSandbox() + sandbox.stub(Consumer, 'getListOfTopics') + sandbox.stub(Consumer, 'isConnected') + t.end() + }) + + subServiceHealthTest.afterEach(t => { + sandbox.restore() + t.end() + }) + + subServiceHealthTest.test('getSubServiceHealthDatastore', datastoreTest => { + datastoreTest.test('datastore test passes when the database is not migration locked', async test => { + // Arrange + sandbox.stub(MigrationLockModel, 'getIsMigrationLocked').returns(false) + const expected = { name: serviceName.datastore, status: statusEnum.OK } + + // Act + const result = await getSubServiceHealthDatastore() + + // Assert + test.deepEqual(result, expected, 'getSubServiceHealthDatastore should match expected result') + test.ok(MigrationLockModel.getIsMigrationLocked.called) + test.end() + }) + + datastoreTest.test('datastore test fails when the database is migration locked', async test => { + // Arrange + + sandbox.stub(MigrationLockModel, 'getIsMigrationLocked').returns(true) + const expected = { name: serviceName.datastore, status: statusEnum.DOWN } + + // Act + const result = await getSubServiceHealthDatastore() + + // Assert + test.deepEqual(result, expected, 'getSubServiceHealthDatastore should match expected result') + test.ok(MigrationLockModel.getIsMigrationLocked.called) + test.end() + }) + + datastoreTest.test('datastore test fails when getIsMigrationLocked throws', async test => { + // Arrange + sandbox.stub(MigrationLockModel, 'getIsMigrationLocked').throws(new Error('Error connecting to db')) + const expected = { name: serviceName.datastore, status: statusEnum.DOWN } + + // Act + const result = await getSubServiceHealthDatastore() + + // Assert + test.deepEqual(result, expected, 'getSubServiceHealthDatastore should match expected result') + test.ok(MigrationLockModel.getIsMigrationLocked.called) + test.end() + }) + + datastoreTest.end() + }) + + subServiceHealthTest.test('getSubServiceHealthBroker', brokerTest => { + brokerTest.test('broker test passes when there are no topics', async test => { + // Arrange + Consumer.getListOfTopics.returns([]) + const expected = { name: serviceName.broker, status: statusEnum.OK } + + // Act + const result = await getSubServiceHealthBroker() + + // Assert + test.deepEqual(result, expected, 'getSubServiceHealthBroker should match expected result') + test.end() + }) + + brokerTest.test('broker test fails when one broker cannot connect', async test => { + // Arrange + Consumer.getListOfTopics.returns(['admin1', 'admin2']) + Consumer.isConnected.throws(new Error('Not connected!')) + const expected = { name: serviceName.broker, status: statusEnum.DOWN } + + // Act + const result = await getSubServiceHealthBroker() + + // Assert + test.deepEqual(result, expected, 'getSubServiceHealthBroker should match expected result') + test.end() + }) + + brokerTest.test('Passes when it connects', async test => { + // Arrange + Consumer.getListOfTopics.returns(['admin1', 'admin2']) + Consumer.isConnected.returns(Promise.resolve(true)) + const expected = { name: serviceName.broker, status: statusEnum.OK } + + // Act + const result = await getSubServiceHealthBroker() + + // Assert + test.deepEqual(result, expected, 'getSubServiceHealthBroker should match expected result') + test.end() + }) + + brokerTest.end() + }) + + subServiceHealthTest.end() +}) diff --git a/test/unit/handlers/lib/kafka/producer.test.js b/test/unit/lib/kafka/producer.test.js similarity index 98% rename from test/unit/handlers/lib/kafka/producer.test.js rename to test/unit/lib/kafka/producer.test.js index 0c2443a9..0c3fa63b 100644 --- a/test/unit/handlers/lib/kafka/producer.test.js +++ b/test/unit/lib/kafka/producer.test.js @@ -29,12 +29,11 @@ ******/ 'use strict' -const src = '../../../../../src/' const Sinon = require('sinon') const rewire = require('rewire') const Test = require('tapes')(require('tape')) const KafkaProducer = require('@mojaloop/central-services-stream').Kafka.Producer -const Producer = require(`${src}/handlers/lib/kafka/producer`) +const Producer = require('../../../../src/lib/kafka/producer') const P = require('bluebird') const Uuid = require('uuid4') const Logger = require('@mojaloop/central-services-logger') @@ -247,7 +246,7 @@ Test('Producer', producerTest => { getProducerStub.withArgs(topicNameFailure).throws(`No producer found for topic ${topicNameFailure}`) // lets rewire the producer import - const KafkaProducerProxy = rewire(`${src}/handlers/lib/kafka/producer`) + const KafkaProducerProxy = rewire('../../../../src/lib/kafka/producer') // lets override the getProducer method within the import KafkaProducerProxy.__set__('getProducer', getProducerStub) diff --git a/test/unit/handlers/lib/utility.test.js b/test/unit/lib/utility.test.js similarity index 98% rename from test/unit/handlers/lib/utility.test.js rename to test/unit/lib/utility.test.js index 50c427a4..d1d3245f 100644 --- a/test/unit/handlers/lib/utility.test.js +++ b/test/unit/lib/utility.test.js @@ -7,7 +7,7 @@ const P = require('bluebird') const Uuid = require('uuid4') const KafkaProducer = require('@mojaloop/central-services-stream').Kafka.Producer -const Utility = require('../../../../src/handlers/lib/utility') +const Utility = require('../../../src/lib/utility') const NOTIFICATION = 'notification' const EVENT = 'event' diff --git a/test/unit/models/lib/enums.test.js b/test/unit/models/lib/enums.test.js index 62d5cf4e..47f3e7b9 100644 --- a/test/unit/models/lib/enums.test.js +++ b/test/unit/models/lib/enums.test.js @@ -396,5 +396,251 @@ Test('Enums', async (enumsTest) => { } }) + await enumsTest.test('settlementDelay should', async settlementDelayTest => { + try { + await settlementDelayTest.test('return', async test => { + try { + const delays = [ + { settlementDelayId: 1, name: 'IMMEDIATE' } + ] + Db.settlementDelay = { find: sandbox.stub().returns(delays) } + let settlementDelays = await Enums.settlementDelay() + test.equal(Object.keys(settlementDelays).length, delays.length, 'settlement delays') + Db.settlementDelay.find = sandbox.stub().returns(undefined) + settlementDelays = await Enums.settlementDelay() + test.notOk(settlementDelays, 'undefined when no record is returned') + test.end() + } catch (err) { + Logger.error(`settlementDelay failed with error - ${err}`) + test.fail() + test.end() + } + }) + + await settlementDelayTest.test('throw error if database is unavailable', async test => { + try { + Db.settlementDelay = { find: sandbox.stub().throws(new Error('Database unavailable')) } + await Enums.settlementDelay() + test.fail('Error not thrown!') + test.end() + } catch (err) { + Logger.error(`settlementDelay failed with error - ${err}`) + test.pass('Error thrown') + test.end() + } + }) + await settlementDelayTest.end() + } catch (err) { + Logger.error(`enumsTest failed with error - ${err}`) + settlementDelayTest.fail() + settlementDelayTest.end() + } + }) + + await enumsTest.test('settlementDelayEnum should', async settlementDelayEnumTest => { + try { + await settlementDelayEnumTest.test('return', async test => { + try { + const delays = [ + { settlementDelayId: 1, name: 'IMMEDIATE' } + ] + Db.settlementDelay = { find: sandbox.stub().returns(delays) } + let settlementDelayEnum = await Enums.settlementDelayEnums() + test.equal(Object.keys(settlementDelayEnum).length, delays.length, 'settlement delay enum') + Db.settlementDelay.find = sandbox.stub().returns(undefined) + settlementDelayEnum = await Enums.settlementDelayEnums() + test.notOk(settlementDelayEnum, 'undefined when no record is returned') + test.end() + } catch (err) { + Logger.error(`settlementDelay failed with error - ${err}`) + test.fail() + test.end() + } + }) + + await settlementDelayEnumTest.test('throw error if database is unavailable', async test => { + try { + Db.settlementDelay = { find: sandbox.stub().throws(new Error('Database unavailable')) } + await Enums.settlementDelayEnums() + test.fail('Error not thrown!') + test.end() + } catch (err) { + Logger.error(`settlementDelay failed with error - ${err}`) + test.pass('Error thrown') + test.end() + } + }) + await settlementDelayEnumTest.end() + } catch (err) { + Logger.error(`enumsTest failed with error - ${err}`) + settlementDelayEnumTest.fail() + settlementDelayEnumTest.end() + } + }) + + await enumsTest.test('settlementGranularity should', async settlementGranularityTest => { + try { + await settlementGranularityTest.test('return', async test => { + try { + const granularityList = [ + { settlementGranularityId: 1, name: 'GROSS' } + ] + Db.settlementGranularity = { find: sandbox.stub().returns(granularityList) } + let settlementGranularityList = await Enums.settlementGranularity() + test.equal(Object.keys(settlementGranularityList).length, granularityList.length, 'settlement granularity list') + Db.settlementGranularity.find = sandbox.stub().returns(undefined) + settlementGranularityList = await Enums.settlementGranularity() + test.notOk(settlementGranularityList, 'undefined when no record is returned') + test.end() + } catch (err) { + Logger.error(`settlementGranularity failed with error - ${err}`) + test.fail() + test.end() + } + }) + + await settlementGranularityTest.test('throw error if database is unavailable', async test => { + try { + Db.settlementGranularity = { find: sandbox.stub().throws(new Error('Database unavailable')) } + await Enums.settlementGranularity() + test.fail('Error not thrown!') + test.end() + } catch (err) { + Logger.error(`settlementGranularity failed with error - ${err}`) + test.pass('Error thrown') + test.end() + } + }) + await settlementGranularityTest.end() + } catch (err) { + Logger.error(`enumsTest failed with error - ${err}`) + settlementGranularityTest.fail() + settlementGranularityTest.end() + } + }) + + await enumsTest.test('settlementGranularityEnum should', async settlementGranularityEnumTest => { + try { + await settlementGranularityEnumTest.test('return', async test => { + try { + const granularityList = [ + { settlementGranularityId: 1, name: 'GROSS' } + ] + Db.settlementGranularity = { find: sandbox.stub().returns(granularityList) } + let settlementGranularityEnum = await Enums.settlementGranularityEnums() + test.equal(Object.keys(settlementGranularityEnum).length, granularityList.length, 'settlement Granularity enum') + Db.settlementGranularity.find = sandbox.stub().returns(undefined) + settlementGranularityEnum = await Enums.settlementGranularityEnums() + test.notOk(settlementGranularityEnum, 'undefined when no record is returned') + test.end() + } catch (err) { + Logger.error(`settlementGranularity failed with error - ${err}`) + test.fail() + test.end() + } + }) + + await settlementGranularityEnumTest.test('throw error if database is unavailable', async test => { + try { + Db.settlementGranularity = { find: sandbox.stub().throws(new Error('Database unavailable')) } + await Enums.settlementGranularityEnums() + test.fail('Error not thrown!') + test.end() + } catch (err) { + Logger.error(`settlementGranularity failed with error - ${err}`) + test.pass('Error thrown') + test.end() + } + }) + await settlementGranularityEnumTest.end() + } catch (err) { + Logger.error(`enumsTest failed with error - ${err}`) + settlementGranularityEnumTest.fail() + settlementGranularityEnumTest.end() + } + }) + + await enumsTest.test('settlementInterchange should', async settlementInterchangeTest => { + try { + await settlementInterchangeTest.test('return', async test => { + try { + const interchangeList = [ + { settlementInterchangeId: 1, name: 'BILATERAL' } + ] + Db.settlementInterchange = { find: sandbox.stub().returns(interchangeList) } + let settlementInterchangeList = await Enums.settlementInterchange() + test.equal(Object.keys(settlementInterchangeList).length, interchangeList.length, 'settlement interchange list') + Db.settlementInterchange.find = sandbox.stub().returns(undefined) + settlementInterchangeList = await Enums.settlementInterchange() + test.notOk(settlementInterchangeList, 'undefined when no record is returned') + test.end() + } catch (err) { + Logger.error(`settlementInterchange failed with error - ${err}`) + test.fail() + test.end() + } + }) + + await settlementInterchangeTest.test('throw error if database is unavailable', async test => { + try { + Db.settlementInterchange = { find: sandbox.stub().throws(new Error('Database unavailable')) } + await Enums.settlementInterchange() + test.fail('Error not thrown!') + test.end() + } catch (err) { + Logger.error(`settlementInterchange failed with error - ${err}`) + test.pass('Error thrown') + test.end() + } + }) + await settlementInterchangeTest.end() + } catch (err) { + Logger.error(`enumsTest failed with error - ${err}`) + settlementInterchangeTest.fail() + settlementInterchangeTest.end() + } + }) + + await enumsTest.test('settlementInterchangeEnum should', async settlementInterchangeEnumTest => { + try { + await settlementInterchangeEnumTest.test('return', async test => { + try { + const interchangeList = [ + { settlementInterchangeId: 1, name: 'GROSS' } + ] + Db.settlementInterchange = { find: sandbox.stub().returns(interchangeList) } + let settlementInterchangeEnum = await Enums.settlementInterchangeEnums() + test.equal(Object.keys(settlementInterchangeEnum).length, interchangeList.length, 'settlement interchange enum') + Db.settlementInterchange.find = sandbox.stub().returns(undefined) + settlementInterchangeEnum = await Enums.settlementInterchangeEnums() + test.notOk(settlementInterchangeEnum, 'undefined when no record is returned') + test.end() + } catch (err) { + Logger.error(`settlementInterchange failed with error - ${err}`) + test.fail() + test.end() + } + }) + + await settlementInterchangeEnumTest.test('throw error if database is unavailable', async test => { + try { + Db.settlementInterchange = { find: sandbox.stub().throws(new Error('Database unavailable')) } + await Enums.settlementInterchangeEnums() + test.fail('Error not thrown!') + test.end() + } catch (err) { + Logger.error(`settlementInterchange failed with error - ${err}`) + test.pass('Error thrown') + test.end() + } + }) + await settlementInterchangeEnumTest.end() + } catch (err) { + Logger.error(`enumsTest failed with error - ${err}`) + settlementInterchangeEnumTest.fail() + settlementInterchangeEnumTest.end() + } + }) + await enumsTest.end() }) diff --git a/test/unit/models/settlement/facade.test.js b/test/unit/models/settlement/facade.test.js index 021e2d0c..7408a893 100644 --- a/test/unit/models/settlement/facade.test.js +++ b/test/unit/models/settlement/facade.test.js @@ -18,11 +18,11 @@ * Gates Foundation - Name Surname - * Georgi Georgiev - * Valentin Genev + * ModusBoc + - Georgi Georgiev + - Valentin Genev -------------- ******/ - 'use strict' const Test = require('tapes')(require('tape')) @@ -32,7 +32,7 @@ const Logger = require('@mojaloop/central-services-logger') const SettlementFacade = require('../../../../src/models/settlement/facade') const ParticipantFacade = require('@mojaloop/central-ledger/src/models/participant/facade') const Uuid = require('uuid4') -const Utility = require('../../../../src/handlers/lib/utility') +const Utility = require('../../../../src/lib/utility') const FSPIOPError = require('@mojaloop/central-services-error-handling').Factory.FSPIOPError Test('Settlement facade', async (settlementFacadeTest) => { @@ -174,7 +174,7 @@ Test('Settlement facade', async (settlementFacadeTest) => { ] } ] - payload.knexTriggerEvent = { + payload.triggerSettlementEvent = { idList: [1, 2], reason: 'text' } @@ -218,7 +218,8 @@ Test('Settlement facade', async (settlementFacadeTest) => { settlementWindowStates: { PENDING_SETTLEMENT: 'PENDING_SETTLEMENT', SETTLED: 'SETTLED', - ABORTED: 'ABORTED' + ABORTED: 'ABORTED', + CLOSED: 'CLOSED' }, participantLimitTypes: { NET_DEBIT_CAP: 'NET_DEBIT_CAP' @@ -342,7 +343,8 @@ Test('Settlement facade', async (settlementFacadeTest) => { settlementId: 1, settlementStateId: 'PENDING_SETTLEMENT', reason: 'reason', - createdDate: now + createdDate: now, + autoPositionReset: true }, settlementAccountList: [ { @@ -521,14 +523,52 @@ Test('Settlement facade', async (settlementFacadeTest) => { currencyId: 'USD', participantCurrencyId: 1 } - ] + ], + processedContent: [{ + settlementWindowId: 1, + settlementWindowStateId: 'PENDING_SETTLEEMENT', + reason: 'unit testing', + createdDate1: 'date', + changedDate1: 'date', + settlementWindowContentId: 1, + state: 'PENDING_SETTLEMENT', + ledgerAccountType: 1, + currencyId: 'USD', + createdDate: 'date', + changedDate: 'date' + }, { + settlementWindowId: 1, + settlementWindowStateId: 'PENDING_SETTLEEMENT', + reason: 'unit testing', + createdDate1: 'date', + changedDate1: 'date', + settlementWindowContentId: 1, + state: 'PENDING_SETTLEMENT', + ledgerAccountType: 6, + currencyId: 'USD', + createdDate: 'date', + changedDate: 'date' + }, { + settlementWindowId: 2, + settlementWindowStateId: 'PENDING_SETTLEEMENT', + reason: 'unit testing', + createdDate1: 'date', + changedDate1: 'date', + settlementWindowContentId: 1, + state: 'PENDING_SETTLEMENT', + ledgerAccountType: 1, + currencyId: 'USD', + createdDate: 'date', + changedDate: 'date' + }] }, { settlementData: { settlementId: 1, settlementStateId: 'PS_TRANSFERS_RECORDED', reason: 'reason', - createdDate: now + createdDate: now, + autoPositionReset: true }, settlementAccountList: [ { @@ -562,14 +602,52 @@ Test('Settlement facade', async (settlementFacadeTest) => { currencyId: 'USD', participantCurrencyId: 1 } - ] + ], + processedContent: [{ + settlementWindowId: 1, + settlementWindowStateId: 'PENDING_SETTLEEMENT', + reason: 'unit testing', + createdDate1: 'date', + changedDate1: 'date', + settlementWindowContentId: 1, + state: 'PENDING_SETTLEMENT', + ledgerAccountType: 1, + currencyId: 'USD', + createdDate: 'date', + changedDate: 'date' + }, { + settlementWindowId: 1, + settlementWindowStateId: 'PENDING_SETTLEEMENT', + reason: 'unit testing', + createdDate1: 'date', + changedDate1: 'date', + settlementWindowContentId: 1, + state: 'PENDING_SETTLEMENT', + ledgerAccountType: 6, + currencyId: 'USD', + createdDate: 'date', + changedDate: 'date' + }, { + settlementWindowId: 2, + settlementWindowStateId: 'PENDING_SETTLEEMENT', + reason: 'unit testing', + createdDate1: 'date', + changedDate1: 'date', + settlementWindowContentId: 1, + state: 'PENDING_SETTLEMENT', + ledgerAccountType: 1, + currencyId: 'USD', + createdDate: 'date', + changedDate: 'date' + }] }, { settlementData: { settlementId: 1, settlementStateId: 'PS_TRANSFERS_RESERVED', reason: 'reason', - createdDate: now + createdDate: now, + autoPositionReset: true }, settlementAccountList: [ { @@ -603,14 +681,52 @@ Test('Settlement facade', async (settlementFacadeTest) => { currencyId: 'USD', participantCurrencyId: 1 } - ] + ], + processedContent: [{ + settlementWindowId: 1, + settlementWindowStateId: 'PENDING_SETTLEEMENT', + reason: 'unit testing', + createdDate1: 'date', + changedDate1: 'date', + settlementWindowContentId: 1, + state: 'PENDING_SETTLEMENT', + ledgerAccountType: 1, + currencyId: 'USD', + createdDate: 'date', + changedDate: 'date' + }, { + settlementWindowId: 1, + settlementWindowStateId: 'PENDING_SETTLEEMENT', + reason: 'unit testing', + createdDate1: 'date', + changedDate1: 'date', + settlementWindowContentId: 1, + state: 'PENDING_SETTLEMENT', + ledgerAccountType: 6, + currencyId: 'USD', + createdDate: 'date', + changedDate: 'date' + }, { + settlementWindowId: 2, + settlementWindowStateId: 'PENDING_SETTLEEMENT', + reason: 'unit testing', + createdDate1: 'date', + changedDate1: 'date', + settlementWindowContentId: 1, + state: 'PENDING_SETTLEMENT', + ledgerAccountType: 1, + currencyId: 'USD', + createdDate: 'date', + changedDate: 'date' + }] }, { settlementData: { settlementId: 1, settlementStateId: 'PS_TRANSFERS_COMMITTED', reason: 'reason', - createdDate: now + createdDate: now, + autoPositionReset: true }, settlementAccountList: [ { @@ -644,14 +760,68 @@ Test('Settlement facade', async (settlementFacadeTest) => { currencyId: 'USD', participantCurrencyId: 1 } - ] + ], + processedContent: [{ + settlementWindowId: 1, + settlementWindowStateId: 'PENDING_SETTLEEMENT', + reason: 'unit testing', + createdDate1: 'date', + changedDate1: 'date', + settlementWindowContentId: 1, + state: 'PENDING_SETTLEMENT', + ledgerAccountType: 1, + currencyId: 'USD', + createdDate: 'date', + changedDate: 'date' + }, { + settlementWindowId: 1, + settlementWindowStateId: 'PENDING_SETTLEEMENT', + reason: 'unit testing', + createdDate1: 'date', + changedDate1: 'date', + settlementWindowContentId: 1, + state: 'PENDING_SETTLEMENT', + ledgerAccountType: 6, + currencyId: 'USD', + createdDate: 'date', + changedDate: 'date' + }, { + settlementWindowId: 2, + settlementWindowStateId: 'PENDING_SETTLEEMENT', + reason: 'unit testing', + createdDate1: 'date', + changedDate1: 'date', + settlementWindowContentId: 1, + state: 'PENDING_SETTLEMENT', + ledgerAccountType: 1, + currencyId: 'USD', + createdDate: 'date', + changedDate: 'date' + }], + scaContentToCheck: [{ + settlementWindowContentId: 1 + }, { + settlementWindowContentId: 2 + }], + unsettledContent: [{ + settlementWindowContentId: 2 + }], + windowsToCheck: [{ + settlementWindowId: 1 + }, { + settlementWindowId: 2 + }], + unsettledWindows: [{ + settlementWindowId: 2 + }] }, { settlementData: { settlementId: 1, settlementStateId: 'PENDING_SETTLEMENT', reason: 'reason', - createdDate: now + createdDate: now, + autoPositionReset: true }, settlementAccountList: [ { @@ -685,14 +855,52 @@ Test('Settlement facade', async (settlementFacadeTest) => { currencyId: 'USD', participantCurrencyId: 1 } - ] + ], + processedContent: [{ + settlementWindowId: 1, + settlementWindowStateId: 'PENDING_SETTLEEMENT', + reason: 'unit testing', + createdDate1: 'date', + changedDate1: 'date', + settlementWindowContentId: 1, + state: 'PENDING_SETTLEMENT', + ledgerAccountType: 1, + currencyId: 'USD', + createdDate: 'date', + changedDate: 'date' + }, { + settlementWindowId: 1, + settlementWindowStateId: 'PENDING_SETTLEEMENT', + reason: 'unit testing', + createdDate1: 'date', + changedDate1: 'date', + settlementWindowContentId: 1, + state: 'PENDING_SETTLEMENT', + ledgerAccountType: 6, + currencyId: 'USD', + createdDate: 'date', + changedDate: 'date' + }, { + settlementWindowId: 2, + settlementWindowStateId: 'PENDING_SETTLEEMENT', + reason: 'unit testing', + createdDate1: 'date', + changedDate1: 'date', + settlementWindowContentId: 1, + state: 'PENDING_SETTLEMENT', + ledgerAccountType: 1, + currencyId: 'USD', + createdDate: 'date', + changedDate: 'date' + }] }, { settlementData: { settlementId: 1, settlementStateId: 'PS_TRANSFERS_COMMITTED', reason: 'reason', - createdDate: now + createdDate: now, + autoPositionReset: false }, settlementAccountList: [ { @@ -735,10 +943,47 @@ Test('Settlement facade', async (settlementFacadeTest) => { currencyId: 'USD', participantCurrencyId: 1 } - ] + ], + processedContent: [{ + settlementWindowId: 1, + settlementWindowStateId: 'PENDING_SETTLEEMENT', + reason: 'unit testing', + createdDate1: 'date', + changedDate1: 'date', + settlementWindowContentId: 1, + state: 'PENDING_SETTLEMENT', + ledgerAccountType: 1, + currencyId: 'USD', + createdDate: 'date', + changedDate: 'date' + }, { + settlementWindowId: 1, + settlementWindowStateId: 'PENDING_SETTLEEMENT', + reason: 'unit testing', + createdDate1: 'date', + changedDate1: 'date', + settlementWindowContentId: 1, + state: 'PENDING_SETTLEMENT', + ledgerAccountType: 6, + currencyId: 'USD', + createdDate: 'date', + changedDate: 'date' + }, { + settlementWindowId: 2, + settlementWindowStateId: 'PENDING_SETTLEEMENT', + reason: 'unit testing', + createdDate1: 'date', + changedDate1: 'date', + settlementWindowContentId: 1, + state: 'PENDING_SETTLEMENT', + ledgerAccountType: 1, + currencyId: 'USD', + createdDate: 'date', + changedDate: 'date' + }] } ] - stubData.knexTriggerEvent = { + stubData.triggerSettlementEvent = { settlementId: 1, settlementParticipantCurrencyList: [ { @@ -757,7 +1002,13 @@ Test('Settlement facade', async (settlementFacadeTest) => { { settlementWindowStateChangeId: 4 } - ] + ], + swcIdList: [{ + settlementWindowContentId: 1 + }], + windowsStateToBeUpdatedIdList: [{ + settlementWindowId: 1 + }] } await settlementFacadeTest.test('settlementTransfersPrepare should', async settlementTransfersPrepareTest => { @@ -1985,6 +2236,110 @@ Test('Settlement facade', async (settlementFacadeTest) => { } }) + await settlementTransfersAbortTest.test('throw error and rollback when called from a transaction', async test => { + try { + const settlementId = 1 + const transactionTimestamp = new Date().toISOString().replace(/[TZ]/g, ' ').trim() + + const knexStub = sandbox.stub() + sandbox.stub(Db, 'getKnex').returns(knexStub) + const trxStub = sandbox.stub() + knexStub.transaction = sandbox.stub().callsArgWith(0, trxStub) + trxStub.rollback = sandbox.stub() + + knexStub.raw = sandbox.stub() + const context = sandbox.stub() + context.on = sandbox.stub().returns({ + andOn: sandbox.stub() + }) + const join1Stub = sandbox.stub().callsArgOn(1, context) + const leftJoin1Stub = sandbox.stub().callsArgOn(1, context) + const leftJoin2Stub = sandbox.stub().callsArgOn(1, context) + const join2Stub = sandbox.stub().callsArgOn(1, context) + const join3Stub = sandbox.stub().callsArgOn(1, context) + knexStub.returns({ + join: join1Stub.returns({ + leftJoin: sandbox.stub().returns({ + leftJoin: leftJoin1Stub.returns({ + leftJoin: sandbox.stub().returns({ + leftJoin: leftJoin2Stub.returns({ + join: join2Stub.returns({ + join: sandbox.stub().returns({ + join: sandbox.stub().returns({ + join: join3Stub.returns({ + select: sandbox.stub().returns({ + where: sandbox.stub().returns({ + whereNull: sandbox.stub().returns({ + transacting: sandbox.stub().returns( + Promise.resolve( + stubData.settlementTransfersAbort.settlementTransferList + ) + ) + }) + }) + }) + }) + }) + }) + }) + }) + }) + + }) + }) + }), + insert: sandbox.stub().returns({ + transacting: sandbox.stub().throws(new Error('Insert failed')) + }) + }) + knexStub.withArgs('participantPosition').returns({ + select: sandbox.stub().returns({ + where: sandbox.stub().returns({ + first: sandbox.stub().returns({ + transacting: sandbox.stub().returns({ + forUpdate: sandbox.stub().returns( + Promise.resolve({ + participantPositionId: 1, + positionValue: 800, + reservedValue: 0 + }) + ) + }) + }) + }) + }), + update: sandbox.stub().returns({ + where: sandbox.stub().returns({ + transacting: sandbox.stub() + }) + }) + }) + knexStub.withArgs('participantLimit').returns({ + select: sandbox.stub().returns({ + where: sandbox.stub().returns({ + andWhere: sandbox.stub().returns({ + first: sandbox.stub().returns({ + transacting: sandbox.stub().returns({ + forUpdate: sandbox.stub().returns( + Promise.resolve({ netDebitCap: 1000 }) + ) + }) + }) + }) + }) + }) + }) + + await SettlementFacade.settlementTransfersAbort(settlementId, transactionTimestamp, enums, trxStub) + test.fail('Error not thrown!') + test.end() + } catch (err) { + Logger.error(`settlementTransfersAbort failed with error - ${err}`) + test.pass('Error thrown') + test.end() + } + }) + await settlementTransfersAbortTest.end() } catch (err) { Logger.error(`settlementFacadeTest failed with error - ${err}`) @@ -2436,13 +2791,15 @@ Test('Settlement facade', async (settlementFacadeTest) => { Db.getKnex.returns(knexStub) knexStub.returns({ join: sandbox.stub().returns({ - select: sandbox.stub().returns({ - where: sandbox.stub().returns({ - first: sandbox.stub().returns({ - transacting: sandbox.stub().returns({ - forUpdate: sandbox.stub().returns( - Promise.resolve(undefined) - ) + join: sandbox.stub().returns({ + select: sandbox.stub().returns({ + where: sandbox.stub().returns({ + first: sandbox.stub().returns({ + transacting: sandbox.stub().returns({ + forUpdate: sandbox.stub().returns( + Promise.resolve(undefined) + ) + }) }) }) }) @@ -2450,14 +2807,13 @@ Test('Settlement facade', async (settlementFacadeTest) => { }) }) - // const result = await SettlementFacade.putById(1, payload.putById[0], enums) - // test.ok(result instanceof FSPIOPError, 'Error is returned') + test.fail('Error not thrown!') test.end() } catch (err) { test.ok(err instanceof FSPIOPError) - // Logger.error(`putById failed with error - ${err}`) - // test.fail() + test.equal(err.message, 'Settlement not found', 'error message matched') + Logger.error(`putById failed with error - ${err}`) test.end() } }) @@ -2473,20 +2829,16 @@ Test('Settlement facade', async (settlementFacadeTest) => { Db.getKnex.returns(knexStub) knexStub.returns({ join: sandbox.stub().returns({ - select: sandbox.stub().returns({ - where: sandbox.stub().returns({ - first: sandbox.stub().returns({ - transacting: sandbox.stub().returns({ - forUpdate: sandbox.stub().returns( - Promise.resolve(stubData.putById[0].settlementData) - ) - }) - }) - }) - }), join: sandbox.stub().returns({ select: sandbox.stub().returns({ where: sandbox.stub().returns({ + first: sandbox.stub().returns({ + transacting: sandbox.stub().returns({ + forUpdate: sandbox.stub().returns( + Promise.resolve(stubData.putById[0].settlementData) + ) + }) + }), transacting: sandbox.stub().returns({ forUpdate: sandbox.stub().returns( Promise.resolve(stubData.putById[0].windowsList) @@ -2544,6 +2896,25 @@ Test('Settlement facade', async (settlementFacadeTest) => { Promise.resolve() ) }) + }), + transacting: sandbox.stub().returns({ + join: sandbox.stub().returns({ + join: sandbox.stub().returns({ + join: sandbox.stub().returns({ + join: sandbox.stub().returns({ + join: sandbox.stub().returns({ + whereIn: sandbox.stub().returns({ + where: sandbox.stub().returns({ + distinct: sandbox.stub().returns({ + orderBy: sandbox.stub().returns(stubData.putById[0].processedContent) + }) + }) + }) + }) + }) + }) + }) + }) }) }) SettlementFacade.settlementTransfersPrepare = sandbox.stub() @@ -2552,11 +2923,9 @@ Test('Settlement facade', async (settlementFacadeTest) => { const result = await SettlementFacade.putById(1, payload.putById[0], enums) test.ok(result, 'Result returned') - test.equal(knexStub.callCount, 10, 'Knex called 10 times') + test.equal(knexStub.callCount, 9, 'Knex called 9 times') test.equal(result.state, 'PENDING_SETTLEMENT', 'Settlement should remain in PENDING_SETTLEMENT state') test.equal(result.settlementWindows.length, 2, 'Exactly two settlement windows are expected to be affected') - test.equal(result.settlementWindows[0].settlementWindowStateId, 'PENDING_SETTLEMENT', 'First window is PENDING_SETTLEMENT') - test.equal(result.settlementWindows[1].settlementWindowStateId, 'ABORTED', 'Second window is ABORTED') test.equal(result.participants.length, 2, 'Two participants are affected') test.equal(result.participants[0].accounts.length, 6, 'Six accounts for first participant are affected') test.equal(result.participants[1].accounts.length, 1, 'One account for second participant is affected') @@ -2593,20 +2962,16 @@ Test('Settlement facade', async (settlementFacadeTest) => { Db.getKnex.returns(knexStub) knexStub.returns({ join: sandbox.stub().returns({ - select: sandbox.stub().returns({ - where: sandbox.stub().returns({ - first: sandbox.stub().returns({ - transacting: sandbox.stub().returns({ - forUpdate: sandbox.stub().returns( - Promise.resolve(stubData.putById[1].settlementData) - ) - }) - }) - }) - }), join: sandbox.stub().returns({ select: sandbox.stub().returns({ where: sandbox.stub().returns({ + first: sandbox.stub().returns({ + transacting: sandbox.stub().returns({ + forUpdate: sandbox.stub().returns( + Promise.resolve(stubData.putById[1].settlementData) + ) + }) + }), transacting: sandbox.stub().returns({ forUpdate: sandbox.stub().returns( Promise.resolve(stubData.putById[1].windowsList) @@ -2664,6 +3029,25 @@ Test('Settlement facade', async (settlementFacadeTest) => { Promise.resolve() ) }) + }), + transacting: sandbox.stub().returns({ + join: sandbox.stub().returns({ + join: sandbox.stub().returns({ + join: sandbox.stub().returns({ + join: sandbox.stub().returns({ + join: sandbox.stub().returns({ + whereIn: sandbox.stub().returns({ + where: sandbox.stub().returns({ + distinct: sandbox.stub().returns({ + orderBy: sandbox.stub().returns(stubData.putById[1].processedContent) + }) + }) + }) + }) + }) + }) + }) + }) }) }) SettlementFacade.settlementTransfersPrepare = sandbox.stub() @@ -2691,20 +3075,16 @@ Test('Settlement facade', async (settlementFacadeTest) => { Db.getKnex.returns(knexStub) knexStub.returns({ join: sandbox.stub().returns({ - select: sandbox.stub().returns({ - where: sandbox.stub().returns({ - first: sandbox.stub().returns({ - transacting: sandbox.stub().returns({ - forUpdate: sandbox.stub().returns( - Promise.resolve(stubData.putById[2].settlementData) - ) - }) - }) - }) - }), join: sandbox.stub().returns({ select: sandbox.stub().returns({ where: sandbox.stub().returns({ + first: sandbox.stub().returns({ + transacting: sandbox.stub().returns({ + forUpdate: sandbox.stub().returns( + Promise.resolve(stubData.putById[2].settlementData) + ) + }) + }), transacting: sandbox.stub().returns({ forUpdate: sandbox.stub().returns( Promise.resolve(stubData.putById[2].windowsList) @@ -2762,6 +3142,25 @@ Test('Settlement facade', async (settlementFacadeTest) => { Promise.resolve() ) }) + }), + transacting: sandbox.stub().returns({ + join: sandbox.stub().returns({ + join: sandbox.stub().returns({ + join: sandbox.stub().returns({ + join: sandbox.stub().returns({ + join: sandbox.stub().returns({ + whereIn: sandbox.stub().returns({ + where: sandbox.stub().returns({ + distinct: sandbox.stub().returns({ + orderBy: sandbox.stub().returns(stubData.putById[2].processedContent) + }) + }) + }) + }) + }) + }) + }) + }) }) }) SettlementFacade.settlementTransfersPrepare = sandbox.stub() @@ -2789,20 +3188,16 @@ Test('Settlement facade', async (settlementFacadeTest) => { Db.getKnex.returns(knexStub) knexStub.returns({ join: sandbox.stub().returns({ - select: sandbox.stub().returns({ - where: sandbox.stub().returns({ - first: sandbox.stub().returns({ - transacting: sandbox.stub().returns({ - forUpdate: sandbox.stub().returns( - Promise.resolve(stubData.putById[3].settlementData) - ) - }) - }) - }) - }), join: sandbox.stub().returns({ select: sandbox.stub().returns({ where: sandbox.stub().returns({ + first: sandbox.stub().returns({ + transacting: sandbox.stub().returns({ + forUpdate: sandbox.stub().returns( + Promise.resolve(stubData.putById[3].settlementData) + ) + }) + }), transacting: sandbox.stub().returns({ forUpdate: sandbox.stub().returns( Promise.resolve(stubData.putById[3].windowsList) @@ -2860,6 +3255,50 @@ Test('Settlement facade', async (settlementFacadeTest) => { Promise.resolve() ) }) + }), + transacting: sandbox.stub().returns({ + join: sandbox.stub().returns({ + join: sandbox.stub().returns({ + join: sandbox.stub().returns({ + join: sandbox.stub().returns({ + join: sandbox.stub().returns({ + whereIn: sandbox.stub().returns({ + where: sandbox.stub().returns({ + distinct: sandbox.stub().returns({ + orderBy: sandbox.stub().returns(stubData.putById[3].processedContent) + }) + }) + }) + }) + }) + }) + }), + whereIn: sandbox.stub().returns({ + whereNot: sandbox.stub().returns({ + distinct: sandbox.stub().returns(stubData.putById[3].unsettledWindows) + }) + }) + }), + where: sandbox.stub().returns({ + whereIn: sandbox.stub().returns({ + update: sandbox.stub(), + distinct: sandbox.stub().returns(stubData.putById[3].scaContentToCheck) + }), + update: sandbox.stub().returns({ + transacting: sandbox.stub().returns( + Promise.resolve() + ) + }) + }), + whereIn: sandbox.stub().returns({ + whereNot: sandbox.stub().returns({ + distinct: sandbox.stub().returns(stubData.putById[3].unsettledContent) + }), + distinct: sandbox.stub().returns(stubData.putById[3].windowsToCheck) + }), + insert: sandbox.stub().returns( + Promise.resolve([21, 22, 23]) + ) }) }) SettlementFacade.settlementTransfersPrepare = sandbox.stub() @@ -2887,20 +3326,16 @@ Test('Settlement facade', async (settlementFacadeTest) => { Db.getKnex.returns(knexStub) knexStub.returns({ join: sandbox.stub().returns({ - select: sandbox.stub().returns({ - where: sandbox.stub().returns({ - first: sandbox.stub().returns({ - transacting: sandbox.stub().returns({ - forUpdate: sandbox.stub().returns( - Promise.resolve(stubData.putById[4].settlementData) - ) - }) - }) - }) - }), join: sandbox.stub().returns({ select: sandbox.stub().returns({ where: sandbox.stub().returns({ + first: sandbox.stub().returns({ + transacting: sandbox.stub().returns({ + forUpdate: sandbox.stub().returns( + Promise.resolve(stubData.putById[4].settlementData) + ) + }) + }), transacting: sandbox.stub().returns({ forUpdate: sandbox.stub().returns( Promise.resolve(stubData.putById[4].windowsList) @@ -2958,6 +3393,25 @@ Test('Settlement facade', async (settlementFacadeTest) => { Promise.resolve() ) }) + }), + transacting: sandbox.stub().returns({ + join: sandbox.stub().returns({ + join: sandbox.stub().returns({ + join: sandbox.stub().returns({ + join: sandbox.stub().returns({ + join: sandbox.stub().returns({ + whereIn: sandbox.stub().returns({ + where: sandbox.stub().returns({ + distinct: sandbox.stub().returns({ + orderBy: sandbox.stub().returns(stubData.putById[4].processedContent) + }) + }) + }) + }) + }) + }) + }) + }) }) }) SettlementFacade.settlementTransfersPrepare = sandbox.stub() @@ -2985,20 +3439,16 @@ Test('Settlement facade', async (settlementFacadeTest) => { Db.getKnex.returns(knexStub) knexStub.returns({ join: sandbox.stub().returns({ - select: sandbox.stub().returns({ - where: sandbox.stub().returns({ - first: sandbox.stub().returns({ - transacting: sandbox.stub().returns({ - forUpdate: sandbox.stub().returns( - Promise.resolve(stubData.putById[5].settlementData) - ) - }) - }) - }) - }), join: sandbox.stub().returns({ select: sandbox.stub().returns({ where: sandbox.stub().returns({ + first: sandbox.stub().returns({ + transacting: sandbox.stub().returns({ + forUpdate: sandbox.stub().returns( + Promise.resolve(stubData.putById[5].settlementData) + ) + }) + }), transacting: sandbox.stub().returns({ forUpdate: sandbox.stub().returns( Promise.resolve(stubData.putById[5].windowsList) @@ -3056,6 +3506,50 @@ Test('Settlement facade', async (settlementFacadeTest) => { Promise.resolve() ) }) + }), + transacting: sandbox.stub().returns({ + join: sandbox.stub().returns({ + join: sandbox.stub().returns({ + join: sandbox.stub().returns({ + join: sandbox.stub().returns({ + join: sandbox.stub().returns({ + whereIn: sandbox.stub().returns({ + where: sandbox.stub().returns({ + distinct: sandbox.stub().returns({ + orderBy: sandbox.stub().returns(stubData.putById[3].processedContent) + }) + }) + }) + }) + }) + }) + }), + whereIn: sandbox.stub().returns({ + whereNot: sandbox.stub().returns({ + distinct: sandbox.stub().returns(stubData.putById[3].unsettledWindows) + }) + }) + }), + where: sandbox.stub().returns({ + whereIn: sandbox.stub().returns({ + update: sandbox.stub(), + distinct: sandbox.stub().returns(stubData.putById[3].scaContentToCheck) + }), + update: sandbox.stub().returns({ + transacting: sandbox.stub().returns( + Promise.resolve() + ) + }) + }), + whereIn: sandbox.stub().returns({ + whereNot: sandbox.stub().returns({ + distinct: sandbox.stub().returns(stubData.putById[3].unsettledContent) + }), + distinct: sandbox.stub().returns(stubData.putById[3].windowsToCheck) + }), + insert: sandbox.stub().returns( + Promise.resolve([21, 22, 23]) + ) }) }) SettlementFacade.settlementTransfersPrepare = sandbox.stub() @@ -3609,10 +4103,11 @@ Test('Settlement facade', async (settlementFacadeTest) => { } }) - await settlementFacadeTest.test('knexTriggerEvent should', async knexTriggerEventTest => { + await settlementFacadeTest.test('triggerSettlementEvent should', async triggerSettlementEventTest => { try { - await knexTriggerEventTest.test('create new settlement', async test => { + await triggerSettlementEventTest.test('create new settlement', async test => { try { + const insertedSettlementId = 1 sandbox.stub(Db, 'getKnex') const knexStub = sandbox.stub() const trxStub = sandbox.stub() @@ -3622,18 +4117,18 @@ Test('Settlement facade', async (settlementFacadeTest) => { knexStub.returns({ insert: sandbox.stub().returns({ transacting: sandbox.stub().returns( - Promise.resolve(stubData.knexTriggerEvent.settlementId) + Promise.resolve(stubData.triggerSettlementEvent.settlementId) ) }), select: sandbox.stub().returns({ where: sandbox.stub().returns({ transacting: sandbox.stub().returns( - Promise.resolve(stubData.knexTriggerEvent.settlementParticipantCurrencyList) + Promise.resolve(stubData.triggerSettlementEvent.settlementParticipantCurrencyList) ) }), whereIn: sandbox.stub().returns({ transacting: sandbox.stub().returns( - Promise.resolve(stubData.knexTriggerEvent.settlementParticipantCurrencyStateChangeIdList) + Promise.resolve(stubData.triggerSettlementEvent.settlementParticipantCurrencyStateChangeIdList) ) }) }), @@ -3644,11 +4139,36 @@ Test('Settlement facade', async (settlementFacadeTest) => { select: sandbox.stub().returns({ whereIn: sandbox.stub().returns({ andWhere: sandbox.stub().returns( - Promise.resolve(stubData.knexTriggerEvent.settlementWindowStateChangeIdList) + Promise.resolve(stubData.triggerSettlementEvent.settlementWindowStateChangeIdList) ) }) }), - insert: sandbox.stub() + insert: sandbox.stub().returns(insertedSettlementId), + join: sandbox.stub().returns({ + join: sandbox.stub().returns({ + join: sandbox.stub().returns({ + whereRaw: sandbox.stub().returns({ + where: sandbox.stub().returns({ + where: sandbox.stub().returns({ + whereIn: sandbox.stub().returns({ + whereIn: sandbox.stub().returns({ + distinct: sandbox.stub().returns(stubData.triggerSettlementEvent.swcIdList) + }) + }) + }) + }) + }) + }) + }), + whereIn: sandbox.stub().returns({ + whereIn: sandbox.stub().returns({ + select: sandbox.stub().returns(stubData.triggerSettlementEvent.windowsStateToBeUpdatedIdList) + }) + }) + }), + whereIn: sandbox.stub().returns({ + update: sandbox.stub() + }) }) }) knexStub.batchInsert = sandbox.stub().returns({ @@ -3692,19 +4212,20 @@ Test('Settlement facade', async (settlementFacadeTest) => { transacting: sandbox.stub() }) }) + const settlementModel = 'DEFERRED_NET' - const settlementId = await SettlementFacade.knexTriggerEvent(payload.knexTriggerEvent, enums) - test.equal(settlementId, 1, 'settlementId returned') - test.equal(knexStub.callCount, 10, 'Knex called 10 times') + const settlementId = await SettlementFacade.triggerSettlementEvent(payload.triggerSettlementEvent, settlementModel, enums) + test.equal(settlementId, insertedSettlementId, 'settlementId returned') + test.equal(knexStub.callCount, 14, 'Knex called 14 times') test.end() } catch (err) { - Logger.error(`knexTriggerEvent failed with error - ${err}`) + Logger.error(`triggerSettlementEvent failed with error - ${err}`) test.fail() test.end() } }) - await knexTriggerEventTest.test('throw error if settlement insert fails', async test => { + await triggerSettlementEventTest.test('throw error if settlement insert fails', async test => { try { sandbox.stub(Db, 'getKnex') const knexStub = sandbox.stub() @@ -3720,21 +4241,21 @@ Test('Settlement facade', async (settlementFacadeTest) => { }) }) - await SettlementFacade.knexTriggerEvent(payload.knexTriggerEvent) + await SettlementFacade.triggerSettlementEvent(payload.triggerSettlementEvent) test.fail('Error not thrown!') test.end() } catch (err) { - Logger.error(`knexTriggerEvent failed with error - ${err}`) + Logger.error(`triggerSettlementEvent failed with error - ${err}`) test.pass('Error thrown') test.end() } }) - await knexTriggerEventTest.end() + await triggerSettlementEventTest.end() } catch (err) { Logger.error(`settlementFacadeTest failed with error - ${err}`) - knexTriggerEventTest.fail() - knexTriggerEventTest.end() + triggerSettlementEventTest.fail() + triggerSettlementEventTest.end() } }) diff --git a/test/unit/server.test.js b/test/unit/models/settlement/settlementModel.test.js similarity index 61% rename from test/unit/server.test.js rename to test/unit/models/settlement/settlementModel.test.js index 4dcef03e..a6662847 100644 --- a/test/unit/server.test.js +++ b/test/unit/models/settlement/settlementModel.test.js @@ -18,7 +18,8 @@ * Gates Foundation - Name Surname - * Georgi Georgiev + * ModusBox + - Georgi Georgiev -------------- ******/ @@ -26,43 +27,44 @@ const Test = require('tapes')(require('tape')) const Sinon = require('sinon') +const Db = require('../../../../src/lib/db') const Logger = require('@mojaloop/central-services-logger') -const Proxyquire = require('proxyquire') +const Model = require('../../../../src/models/settlement/settlementModel.js') -Test('Server', (serverTest) => { +Test('SettlementModelModel', async (settlementModelModelTest) => { let sandbox - serverTest.beforeEach(test => { - try { - sandbox = Sinon.createSandbox() - } catch (err) { - Logger.error(`serverTest failed with error - ${err}`) - console.error(err.message) - } - test.end() + settlementModelModelTest.beforeEach(t => { + sandbox = Sinon.createSandbox() + t.end() }) - serverTest.afterEach(test => { + settlementModelModelTest.afterEach(t => { sandbox.restore() - test.end() + t.end() }) - serverTest.test('should import setup and initialize', test => { + settlementModelModelTest.test('getByName should return the settlementModel', async test => { try { - const initStub = sandbox.stub() - Proxyquire('../../src/server', { - './setup': { - initialize: initStub - } - }) - test.ok(initStub.withArgs().calledOnce) + const name = 'DEFERRED_NET' + const settlementModel = { + settlementModelId: 1, + name + } + Db.settlementModel = { + findOne: sandbox.stub() + } + Db.settlementModel.findOne.withArgs({ name, isActive: 1 }).returns(settlementModel) + + const result = await Model.getByName(name) + test.deepEqual(result, settlementModel, 'Results Match') test.end() - } catch (err) { - Logger.error(`serverTest failed with error - ${err}`) - test.fail() + } catch (e) { + Logger.error(e) + test.fail('Error Thrown') test.end() } }) - serverTest.end() + settlementModelModelTest.end() }) diff --git a/test/unit/models/settlement/settlementStateChange.test.js b/test/unit/models/settlement/settlementStateChange.test.js index 4c775851..a104b8a8 100644 --- a/test/unit/models/settlement/settlementStateChange.test.js +++ b/test/unit/models/settlement/settlementStateChange.test.js @@ -18,10 +18,10 @@ * Gates Foundation - Name Surname - * Georgi Georgiev + * ModusBox + - Georgi Georgiev -------------- ******/ - 'use strict' const Test = require('tapes')(require('tape')) @@ -43,7 +43,7 @@ Test('SettlementStateChangeModel', async (SettlementStateChangeModelTest) => { test.end() }) - await SettlementStateChangeModelTest.test('settlementModel should', async getBySettlementIdTest => { + await SettlementStateChangeModelTest.test('settlementStateChangeModel should', async getBySettlementIdTest => { try { await getBySettlementIdTest.test('throw error', async test => { try { diff --git a/test/unit/models/settlementWindow/facade.test.js b/test/unit/models/settlementWindow/facade.test.js index 9daad714..5a973f48 100644 --- a/test/unit/models/settlementWindow/facade.test.js +++ b/test/unit/models/settlementWindow/facade.test.js @@ -56,12 +56,16 @@ Test('Settlement Window facade', async (settlementWindowFacadeTest) => { Db.settlementWindow = { query: sandbox.stub() } + Db.transferFulfilment = { + query: sandbox.stub() + } Db.settlementSettlementWindow = { query: sandbox.stub() } builderStub = sandbox.stub() Db.settlementWindow.query.callsArgWith(0, builderStub) Db.settlementSettlementWindow.query.callsArgWith(0, builderStub) + Db.transferFulfilment.query.callsArgWith(0, builderStub) builderStub.leftJoin = sandbox.stub() builderStub.join = sandbox.stub() selectStub = sandbox.stub() @@ -70,6 +74,7 @@ Test('Settlement Window facade', async (settlementWindowFacadeTest) => { whereRawStub = sandbox.stub() orderByStub = sandbox.stub() distinctStub = sandbox.stub() + selectStubResult = { first: firstStub.returns({ where: whereStub @@ -100,6 +105,23 @@ Test('Settlement Window facade', async (settlementWindowFacadeTest) => { }) }) }) + builderStub.join.returns({ + join: sandbox.stub().returns({ + join: sandbox.stub().returns({ + whereRaw: sandbox.stub().returns({ + where: sandbox.stub().returns({ + where: sandbox.stub().returns({ + whereIn: sandbox.stub().returns({ + whereIn: sandbox.stub().returns({ + distinct: selectStub.returns(selectStubResult) + }) + }) + }) + }) + }) + }) + }) + }) test.end() }) @@ -165,6 +187,17 @@ Test('Settlement Window facade', async (settlementWindowFacadeTest) => { try { const listOfIds = [1, 2] const enums = {} + const settlementModelMock = { + currencyId: 'USD', + isActive: 1, + ledgerAccountTypeId: 1, + name: 'DEFERRED_NET_USD', + requireLiquidityCheck: 1, + settlementDelayId: 2, + settlementGranularityId: 2, + settlementInterchangeId: 2, + settlementModelId: 2 + } const settlementWindowResultStub = [{ settlementWindowId: 1, state: 'SETTLED' @@ -176,17 +209,14 @@ Test('Settlement Window facade', async (settlementWindowFacadeTest) => { await getByListOfIdsTest.test('retrieve settlement windows data by list of ids', async test => { try { + const knexStub = sandbox.stub() + Db.getKnex = sandbox.stub().returns(knexStub) + knexStub.raw = sandbox.stub() Db.settlementWindow.query.returns(Promise.resolve(settlementWindowResultStub)) - const result = await SettlementWindowFacade.getByListOfIds(listOfIds, enums) + const result = await SettlementWindowFacade.getByListOfIds(listOfIds, settlementModelMock, enums) test.ok(result, 'Result returned') - test.ok(builderStub.leftJoin.withArgs('settlementWindowStateChange AS swsc', 'swsc.settlementWindowStateChangeId', 'settlementWindow.currentStateChangeId').calledOnce) - test.ok(selectStub.withArgs('settlementWindow.settlementWindowId', - 'swsc.settlementWindowStateId as state', - 'swsc.reason as reason', - 'settlementWindow.createdDate as createdDate', - 'swsc.createdDate as changedDate').calledOnce) - test.ok(whereRawStub.withArgs(`settlementWindow.settlementWindowId IN (${listOfIds})`).calledOnce) + test.ok(builderStub.join.withArgs('settlementWindowStateChange AS swsc', 'swsc.settlementWindowStateChangeId', 'settlementWindow.currentStateChangeId').calledOnce) test.end() } catch (err) { Logger.error(`getByListOfIds failed with error - ${err}`) @@ -198,8 +228,11 @@ Test('Settlement Window facade', async (settlementWindowFacadeTest) => { await getByListOfIdsTest.test('throw error if database is unavailable', async test => { try { e = new Error('Database unavailable') + const knexStub = sandbox.stub() + Db.getKnex = sandbox.stub().returns(knexStub) + knexStub.raw = sandbox.stub() Db.settlementWindow.query.throws(e) - await SettlementWindowFacade.getByListOfIds(listOfIds) + await SettlementWindowFacade.getByListOfIds(listOfIds, settlementModelMock, enums) test.fail('Error not thrown!') test.end() } catch (err) { @@ -223,7 +256,8 @@ Test('Settlement Window facade', async (settlementWindowFacadeTest) => { const state = 'PENDING_SETTLEMENT' const fromDateTime = new Date('01-01-1970').toISOString() const toDateTime = new Date().toISOString() - let query = { participantId, state, fromDateTime, toDateTime } + const currency = 'USD' + let query = { participantId, state, fromDateTime, toDateTime, currency } const settlementWindowResultStub = [{ settlementWindowId: 1, state: 'PENDING_SETTLEMENT' @@ -255,6 +289,7 @@ Test('Settlement Window facade', async (settlementWindowFacadeTest) => { test.ok(whereStub.withArgs('swsc.settlementWindowStateId', state).calledOnce) test.ok(whereStub.withArgs('settlementWindow.createdDate', '>=', fromDateTime).calledOnce) test.ok(whereStub.withArgs('settlementWindow.createdDate', '<=', toDateTime).calledOnce) + test.ok(whereStub.withArgs('pc.currencyId', currency).calledOnce) test.end() } catch (err) { Logger.error(`getByParams failed with error - ${err}`) @@ -297,10 +332,13 @@ Test('Settlement Window facade', async (settlementWindowFacadeTest) => { try { Db.settlementWindow.query.returns(Promise.resolve(settlementWindowResultStub)) - query = { state, fromDateTime, toDateTime } + query = { state, fromDateTime, toDateTime, currency } const result = await SettlementWindowFacade.getByParams({ query }, enums) test.ok(result, 'Result returned') test.ok(builderStub.leftJoin.withArgs('settlementWindowStateChange AS swsc', 'swsc.settlementWindowStateChangeId', 'settlementWindow.currentStateChangeId').calledOnce) + test.ok(leftJoin2Stub.withArgs('transferFulfilment AS tf', 'tf.settlementWindowId', 'settlementWindow.settlementWindowId').calledOnce) + test.ok(leftJoin3Stub.withArgs('transferParticipant AS tp', 'tp.transferId', 'tf.transferId').calledOnce) + test.ok(leftJoin4Stub.withArgs('participantCurrency AS pc', 'pc.participantCurrencyId', 'tp.participantCurrencyId').calledOnce) test.ok(selectStub.withArgs('settlementWindow.settlementWindowId', 'swsc.settlementWindowStateId as state', 'swsc.reason as reason', @@ -311,6 +349,7 @@ Test('Settlement Window facade', async (settlementWindowFacadeTest) => { test.ok(whereStub.withArgs('swsc.settlementWindowStateId', state).calledOnce) test.ok(whereStub.withArgs('settlementWindow.createdDate', '>=', fromDateTime).calledOnce) test.ok(whereStub.withArgs('settlementWindow.createdDate', '<=', toDateTime).calledOnce) + test.ok(whereStub.withArgs('pc.currencyId', currency).calledOnce) test.end() } catch (err) { Logger.error(`getByParams failed with error - ${err}`) @@ -367,16 +406,17 @@ Test('Settlement Window facade', async (settlementWindowFacadeTest) => { } }) - await settlementWindowFacadeTest.test('close should', async closeTest => { + await settlementWindowFacadeTest.test('process should', async processTest => { try { - const settlementWindowCurrentStateMock = { state: 'OPEN' } + let settlementWindowCurrentStateMock = { state: 'OPEN' } + const transfersCountMock = { cnt: 1 } const settlementWindowId = 1 - const state = 'CLOSED' + const state = 'PROCESSING' const reason = 'close reason text' const params = { settlementWindowId, state, reason } const enums = { OPEN: 'OPEN' } - await closeTest.test('close the specified open window and open a new one', async test => { + await processTest.test('process the specified open window and open a new one', async test => { try { Db.getKnex = sandbox.stub() const knexStub = sandbox.stub() @@ -404,9 +444,11 @@ Test('Settlement Window facade', async (settlementWindowFacadeTest) => { }) SettlementWindowFacade.getById = sandbox.stub().returns(settlementWindowCurrentStateMock) - const result = await SettlementWindowFacade.close(params, enums) + sandbox.stub(SettlementWindowFacade, 'getTransfersCount').returns(transfersCountMock) + const result = await SettlementWindowFacade.process(params, enums) test.ok(result, 'Result returned') test.ok(SettlementWindowFacade.getById.withArgs({ settlementWindowId }).calledOnce) + test.ok(SettlementWindowFacade.getTransfersCount.withArgs({ settlementWindowId }).calledOnce) test.ok(knexStub.withArgs('settlementWindowStateChange').calledTwice) test.equal(transactingStub.withArgs(trxStub).callCount, 5) test.ok(insertStub.withArgs({ @@ -425,54 +467,103 @@ Test('Settlement Window facade', async (settlementWindowFacadeTest) => { reason, createdDate: now }).calledOnce) - test.end() test.ok(whereStub.withArgs({ settlementWindowId: newSettlementWindowIdMock }).calledOnce) test.ok(updateStub.withArgs({ currentStateChangeId: newSettlementWindowStateChangeIdMock }).calledOnce) - try { insertStub.onCall(3).throws(new Error('Insert into settlementWindowStateChange failed')) - await SettlementWindowFacade.close(params, enums) + await SettlementWindowFacade.process(params, enums) test.fail('Error expected, but not thrown!') + test.end() } catch (err) { test.pass(`Error "${err.message}" thrown as expected`) + test.end() } } catch (err) { - Logger.error(`close failed with error - ${err}`) - test.fail() + Logger.error(`process failed with error - ${err}`) test.end() } }) - await closeTest.test('throw error if the requested window is not open', async test => { + await processTest.test('throw error if the requested window is not open', async test => { try { - settlementWindowCurrentStateMock.state = 'CLOSED' + const settlementWindowResultStub = () => { return { cnt: 1 } } + Db.transferFulfilment = { + query: settlementWindowResultStub + } + + settlementWindowCurrentStateMock = { state: 'INVALID' } SettlementWindowFacade.getById = sandbox.stub().returns(settlementWindowCurrentStateMock) - await SettlementWindowFacade.close(params) + await SettlementWindowFacade.process(params) test.fail('Error not thrown!') + test.end() } catch (err) { - Logger.error(`close failed with error - ${err}`) + Logger.error(`process failed with error - ${err}`) test.ok(err instanceof Error, `Error "${err.message}" thrown as expected`) test.end() } }) - await closeTest.test('throw error if the requested window does not exist', async test => { + await processTest.test('throw error if transfer count is 0', async test => { try { - SettlementWindowFacade.getById = sandbox.stub().returns(undefined) - await SettlementWindowFacade.close(params) + const settlementWindowResultStub = () => { return { cnt: 0 } } + Db.transferFulfilment = { + query: settlementWindowResultStub + } + + settlementWindowCurrentStateMock = { state: 'OPEN' } + SettlementWindowFacade.getById = sandbox.stub().returns(settlementWindowCurrentStateMock) + await SettlementWindowFacade.process(params, enums) test.fail('Error not thrown!') + test.end() } catch (err) { - Logger.error(`close failed with error - ${err}`) + Logger.error(`process failed with error - ${err}`) test.ok(err instanceof Error, `Error "${err.message}" thrown as expected`) test.end() } }) - await closeTest.end() + await processTest.test('roll back when critical error occurred', async test => { + try { + const settlementWindowResultStub = () => { return { cnt: 1 } } + Db.transferFulfilment = { + query: settlementWindowResultStub + } + + settlementWindowCurrentStateMock = { state: 'OPEN' } + SettlementWindowFacade.getById = sandbox.stub().returns(settlementWindowCurrentStateMock) + await SettlementWindowFacade.process(params, enums) + test.fail('Error not thrown!') + test.end() + } catch (err) { + Logger.error(`process failed with error - ${err}`) + test.ok(err instanceof Error, `Error "${err.message}" thrown as expected`) + test.end() + } + }) + + await processTest.test('throw error if the requested window state is undefined does not exist', async test => { + try { + const settlementWindowResultStub = () => { return { cnt: 1 } } + + Db.transferFulfilment = { + query: settlementWindowResultStub + } + settlementWindowCurrentStateMock = undefined + SettlementWindowFacade.getById = sandbox.stub().returns(settlementWindowCurrentStateMock) + await SettlementWindowFacade.process(params) + test.fail('Error not thrown!') + test.end() + } catch (err) { + Logger.error(`process failed with error - ${err}`) + test.ok(err instanceof Error, `Error "${err.message}" thrown as expected`) + test.end() + } + }) + await processTest.end() } catch (err) { Logger.error(`settlementFacadeTest failed with error - ${err}`) - closeTest.fail() - closeTest.end() + processTest.fail() + processTest.end() } }) @@ -536,5 +627,219 @@ Test('Settlement Window facade', async (settlementWindowFacadeTest) => { } }) + await settlementWindowFacadeTest.test('close should', async closeTest => { + try { + const settlementWindowId = 1 + const reason = 'close reason text' + const params = { settlementWindowId, reason } + const enums = { OPEN: 'OPEN' } + + await closeTest.test('close the specified open window will throw an error if the current state is undefined.', async test => { + try { + const knexStub = sandbox.stub() + const trxStub = sandbox.stub() + trxStub.commit = sandbox.stub() + knexStub.transaction = sandbox.stub().callsArgWith(0, trxStub) + + SettlementWindowFacade.getById = sandbox.stub().returns(undefined) + const result = await SettlementWindowFacade.close(params, enums) + test.ok(result, 'Result returned') + test.ok(SettlementWindowFacade.getById.withArgs({ settlementWindowId }).calledOnce) + test.end() + } catch (err) { + Logger.error('Close settlementwindow failed with error : ' + err) + test.pass('Error thrown as expected') + test.end() + } + }) + + await closeTest.test('close the specified open window will throw an error if the current state is not "PROCESSING".', async test => { + try { + const knexStub = sandbox.stub() + const trxStub = sandbox.stub() + trxStub.commit = sandbox.stub() + knexStub.transaction = sandbox.stub().callsArgWith(0, trxStub) + + SettlementWindowFacade.getById = sandbox.stub().returns('INVALID STATE') + const result = await SettlementWindowFacade.close(params, enums) + test.ok(result, 'Result returned') + test.ok(SettlementWindowFacade.getById.withArgs({ settlementWindowId }).calledOnce) + test.end() + } catch (err) { + Logger.error('Close settlementwindow failed with error : ' + err) + test.pass('Error thrown as expected') + test.end() + } + }) + + await closeTest.test('close the specified open window should roll back on error.', async test => { + try { + const knexStub = sandbox.stub() + const trxStub = sandbox.stub() + trxStub.commit = sandbox.stub() + knexStub.transaction = sandbox.stub().callsArgWith(0, trxStub) + const settlementWindowCurrentStateMock = { state: 'PROCESSING' } + + SettlementWindowFacade.getById = sandbox.stub().returns(settlementWindowCurrentStateMock) + const result = await SettlementWindowFacade.close(params, enums) + test.ok(result, 'Result returned') + test.end() + } catch (err) { + Logger.error('Close settlementwindow failed with error : ' + err) + test.pass('Error thrown as expected') + test.end() + } + }) + + await closeTest.test('close the specified open window successfully.', async test => { + try { + const knexStub = sandbox.stub() + knexStub.raw = sandbox.stub() + Db.getKnex.returns(knexStub) + const trxStub = sandbox.stub() + knexStub.transaction = sandbox.stub().callsArgWith(0, trxStub) + const settlementWindowCurrentStateMock = { state: 'PROCESSING' } + const context = sandbox.stub() + const context2 = sandbox.stub() + + Db.transferParticipant = { + join: sandbox.stub() + } + + Db.participantCurrency = { + join: sandbox.stub() + } + + Db.settlementWindowContent = { + join: sandbox.stub() + } + + Db.transferParticipant.join.callsArgWith(0, builderStub) + Db.participantCurrency.join.callsArgWith(0, builderStub) + Db.settlementWindowContent.join.callsArgWith(0, builderStub) + + const infoDataStub = { + settlementWindowContentId: 4, + settlementWindowContentStateChangeId: 4 + } + + // Insert settlementContentAggregation + context2.on = sandbox.stub().returns({ + on: sandbox.stub().returns({ + on: sandbox.stub() + }) + }) + context.from = sandbox.stub().returns({ + join: sandbox.stub().returns({ + join: sandbox.stub().returns({ + where: sandbox.stub().returns({ + distinct: sandbox.stub() + }), + join: sandbox.stub().callsArgOn(1, context2).returns({ + where: sandbox.stub().returns({ + groupBy: sandbox.stub().returns({ + select: sandbox.stub().returns({ + sum: sandbox.stub() + }) + }) + }) + }) + }) + }), + where: sandbox.stub().returns({ + select: sandbox.stub() + }) + }) + + knexStub.from = sandbox.stub().returns({ + insert: sandbox.stub().callsArgOn(0, context).returns({ transacting: sandbox.stub() }) + }) + + // Update settlementWindowContent pointers to current states, inserted by previous command + knexStub.withArgs('settlementWindowContentStateChange AS swcsc').returns({ + join: sandbox.stub().returns({ + select: sandbox.stub().returns({ + where: sandbox.stub().returns({ + transacting: sandbox.stub().returns(Promise.resolve(infoDataStub)) + }) + }) + }) + }) + knexStub.withArgs('settlementWindowContent').returns({ + where: sandbox.stub().returns({ + update: sandbox.stub().returns({ + transacting: sandbox.stub() + }) + }) + }) + + knexStub.withArgs('settlementWindowStateChange').returns({ + transacting: sandbox.stub().returns({ + insert: sandbox.stub().returns(1) + }) + }) + knexStub.withArgs('settlementWindow').returns({ + transacting: sandbox.stub().returns({ + where: sandbox.stub().returns({ + update: sandbox.stub().returns(1) + }) + }) + }) + + SettlementWindowFacade.getById = sandbox.stub().returns(settlementWindowCurrentStateMock) + const result = await SettlementWindowFacade.close(params, enums) + test.ok(result, true) + test.end() + } catch (err) { + Logger.error('Close settlementwindow failed with error : ' + err) + test.pass('Error thrown incorrectly') + test.end() + } + }) + await closeTest.end() + } catch (err) { + Logger.error(`settlementFacadeTest failed with error - ${err}`) + closeTest.fail() + closeTest.end() + } + }) + + await settlementWindowFacadeTest.test('getTransfersCount should', async closeTest => { + try { + await closeTest.test('should return the number of transfers in a window.', async test => { + try { + const knexStub = sandbox.stub() + const trxStub = sandbox.stub() + trxStub.commit = sandbox.stub() + knexStub.transaction = sandbox.stub().callsArgWith(0, trxStub) + const firstStub = sandbox.stub() + const whereStub = sandbox.stub() + const builderStub = sandbox.stub() + builderStub.count = sandbox.stub() + builderStub.count.returns({ + first: firstStub.returns({ + where: whereStub.returns(1) + }) + }) + Db.transferFulfilment.query.callsArgWith(0, builderStub) + + const result = await SettlementWindowFacade.getTransfersCount({ settlementWindowId: 1 }) + test.ok(result, 'Result returned') + test.end() + } catch (err) { + Logger.error('getTransferCount failed with error : ' + err) + test.pass('Error thrown as expected') + test.end() + } + }) + + await closeTest.end() + } catch (err) { + Logger.error(`settlementFacadeTest failed with error - ${err}`) + closeTest.fail() + closeTest.end() + } + }) + settlementWindowFacadeTest.end() }) diff --git a/test/unit/models/settlementWindowContent/facade.test.js b/test/unit/models/settlementWindowContent/facade.test.js new file mode 100644 index 00000000..e53b2740 --- /dev/null +++ b/test/unit/models/settlementWindowContent/facade.test.js @@ -0,0 +1,215 @@ +/***** + License + -------------- + Copyright © 2017 Bill & Melinda Gates Foundation + The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + Contributors + -------------- + This is the official list of the Mojaloop project contributors for this file. + Names of the original copyright holders (individuals or organizations) + should be listed with a '*' in the first column. People who have + contributed from an organization can be listed under the organization + that actually holds the copyright for their contributions (see the + Gates Foundation organization for an example). Those individuals should have + their names indented and be marked with a '-'. Email address can be added + optionally within square brackets . + * Gates Foundation + - Name Surname + + * Georgi Georgiev + -------------- + ******/ + +'use strict' + +const Test = require('tapes')(require('tape')) +const Sinon = require('sinon') +const Logger = require('@mojaloop/central-services-logger') +const SettlementWindowContentFacade = require('../../../../src/models/settlementWindowContent/facade') +const Db = require('../../../../src/lib/db') + +Test('SettlementWindowContentFacade', async (settlementWindowContentModelTest) => { + let sandbox + + settlementWindowContentModelTest.beforeEach(test => { + sandbox = Sinon.createSandbox() + test.end() + }) + + settlementWindowContentModelTest.afterEach(test => { + sandbox.restore() + test.end() + }) + + await settlementWindowContentModelTest.test('settlementWindowContentModel should', async getApplicableByWindowIdListTest => { + try { + await getApplicableByWindowIdListTest.test('return applicable content by windows id list', async test => { + try { + const idList = [1, 2] + const settlementModel = { + ledgerAccountTypeId: 1, + currencyId: 'USD' + } + const winStateEnum = { + CLOSED: 'CLOSED', + ABORTED: 'ABORTED', + PENDING_SETTLEMENT: 'PENDING_SETTLEMENT' + } + const applicableContentMock = [{ + settlementWindowContentId: 1 + }, { + settlementWindowContentId: 2 + }, { + settlementWindowContentId: 3 + }] + + Db.getKnex = sandbox.stub() + const knexStub = sandbox.stub() + knexStub.raw = sandbox.stub() + Db.getKnex.returns(knexStub) + + const builderStub = sandbox.stub() + Db.settlementWindow = { + query: sandbox.stub() + } + Db.settlementWindow.query.callsArgWith(0, builderStub) + const whereRawStub = sandbox.stub() + const where1Stub = sandbox.stub() + const where2Stub = sandbox.stub() + const whereIn1Stub = sandbox.stub() + const whereIn2Stub = sandbox.stub() + builderStub.join = sandbox.stub().returns({ + join: sandbox.stub().returns({ + join: sandbox.stub().returns({ + whereRaw: whereRawStub.returns({ + where: where1Stub.returns({ + where: where2Stub.returns({ + whereIn: whereIn1Stub.returns({ + whereIn: whereIn2Stub.returns({ + distinct: sandbox.stub().returns(applicableContentMock) + }) + }) + }) + }) + }) + }) + }) + }) + + const result = await SettlementWindowContentFacade.getApplicableByWindowIdList(idList, settlementModel, winStateEnum) + test.ok(result, 'Result returned') + test.ok(builderStub.join.withArgs('settlementWindowStateChange AS swsc', 'swsc.settlementWindowStateChangeId', 'settlementWindow.currentStateChangeId').calledOnce, 'join with args ... called once') + test.ok(whereRawStub.withArgs(`settlementWindow.settlementWindowId IN (${idList})`).calledOnce, 'whereRaw with args ... called once') + test.ok(where1Stub.withArgs('swc.ledgerAccountTypeId', settlementModel.ledgerAccountTypeId).calledOnce, 'where with args ... called once') + test.equal(result, applicableContentMock, 'Result matched') + + test.end() + } catch (err) { + Logger.error(`getApplicableByWindowIdListTest failed with error - ${err}`) + test.fail() + test.end() + } + }) + + await getApplicableByWindowIdListTest.end() + } catch (err) { + Logger.error(`settlementWindowContentModelTest failed with error - ${err}`) + getApplicableByWindowIdListTest.fail() + getApplicableByWindowIdListTest.end() + } + }) + + await settlementWindowContentModelTest.test('getBySettlementWindowContentId should', async getBySettlementWindowContentIdTest => { + try { + await getBySettlementWindowContentIdTest.test('return content by settlement id', async test => { + try { + const settlementId = 1 + const settlementWindowContent = [{ + id: 1, + settlementWindowId: 1, + state: 'PENDING_SETTLEMENT', + ledgerAccountType: 1, + currencyId: 'USD', + createdDate: 'date', + changedDate: 'date' + }, { + id: 2, + settlementWindowId: 1, + state: 'SETTLED', + ledgerAccountType: 6, + currencyId: 'USD', + createdDate: 'date', + changedDate: 'date' + }] + Db.getKnex = sandbox.stub() + const knexStub = sandbox.stub() + Db.getKnex.returns(knexStub) + knexStub.returns({ + join: sandbox.stub().returns({ + join: sandbox.stub().returns({ + where: sandbox.stub().returns({ + select: sandbox.stub().returns(settlementWindowContent) + }) + }) + }) + }) + const result = await SettlementWindowContentFacade.getBySettlementId(settlementId) + test.deepEqual(result, settlementWindowContent, 'results match') + test.end() + } catch (err) { + test.pass('Error thrown') + test.end() + } + }) + await getBySettlementWindowContentIdTest.test('return content by settlement window id', async test => { + try { + const settlementId = 1 + const settlementWindowContent = [{ + id: 1, + settlementWindowId: 1, + state: 'PENDING_SETTLEMENT', + ledgerAccountType: 1, + currencyId: 'USD', + createdDate: 'date', + changedDate: 'date' + }, { + id: 2, + settlementWindowId: 1, + state: 'SETTLED', + ledgerAccountType: 6, + currencyId: 'USD', + createdDate: 'date', + changedDate: 'date' + }] + Db.getKnex = sandbox.stub() + const knexStub = sandbox.stub() + Db.getKnex.returns(knexStub) + knexStub.returns({ + join: sandbox.stub().returns({ + join: sandbox.stub().returns({ + where: sandbox.stub().returns({ + select: sandbox.stub().returns(settlementWindowContent) + }) + }) + }) + }) + const result = await SettlementWindowContentFacade.getBySettlementWindowId(settlementId) + test.deepEqual(result, settlementWindowContent, 'results match') + test.end() + } catch (err) { + test.pass('Error thrown') + test.end() + } + }) + await getBySettlementWindowContentIdTest.end() + } catch (err) { + Logger.error(`getBySettlementWindowContentIdTest failed with error - ${err}`) + getBySettlementWindowContentIdTest.fail() + getBySettlementWindowContentIdTest.end() + } + }) + + await settlementWindowContentModelTest.end() +}) diff --git a/test/unit/models/settlementWindowContent/settlementWindowContentStateChange.test.js b/test/unit/models/settlementWindowContent/settlementWindowContentStateChange.test.js new file mode 100644 index 00000000..38b68476 --- /dev/null +++ b/test/unit/models/settlementWindowContent/settlementWindowContentStateChange.test.js @@ -0,0 +1,139 @@ +/***** + License + -------------- + Copyright © 2017 Bill & Melinda Gates Foundation + The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + Contributors + -------------- + This is the official list of the Mojaloop project contributors for this file. + Names of the original copyright holders (individuals or organizations) + should be listed with a '*' in the first column. People who have + contributed from an organization can be listed under the organization + that actually holds the copyright for their contributions (see the + Gates Foundation organization for an example). Those individuals should have + their names indented and be marked with a '-'. Email address can be added + optionally within square brackets . + * Gates Foundation + - Name Surname + + * ModusBox + - Georgi Georgiev + -------------- + ******/ +'use strict' + +const Test = require('tapes')(require('tape')) +const Sinon = require('sinon') +const Logger = require('@mojaloop/central-services-logger') +const SettlementWindowContentStateChangeModel = require('../../../../src/models/settlementWindowContent/settlementWindowContentStateChange') +const Db = require('../../../../src/lib/db') + +Test('SettlementModel', async (settlementWindowContentStateChangeModelTest) => { + let sandbox + + settlementWindowContentStateChangeModelTest.beforeEach(test => { + sandbox = Sinon.createSandbox() + test.end() + }) + + settlementWindowContentStateChangeModelTest.afterEach(test => { + sandbox.restore() + test.end() + }) + + await settlementWindowContentStateChangeModelTest.test('settlementWindowContentStateChangeModel should', async createTest => { + try { + await createTest.test('return insert state to database', async test => { + try { + const settlementWindowContentId = 1 + const state = 'PENDING_SETTLEMENT' + const reason = 'reason text' + const enums = { + PENDING_SETTLEMENT: 'PENDING_SETTLEMENT' + } + + Db.settlementWindowContentStateChange = { + insert: sandbox.stub().returns(true) + } + + const result = await SettlementWindowContentStateChangeModel.create({ settlementWindowContentId, state, reason }, enums) + test.ok(result, 'Result returned and matched') + test.ok(Db.settlementWindowContentStateChange.insert.withArgs({ + settlementWindowContentId, + settlementWindowStateId: enums[state.toUpperCase()], + reason + }).calledOnce, 'insert with args ... called once') + + Db.settlementWindowContentStateChange.insert = sandbox.stub().throws(new Error('Error occurred')) + try { + await SettlementWindowContentStateChangeModel.create({ settlementWindowContentId, state, reason }) + test.fail('Error expected, but not thrown!') + } catch (err) { + test.equal(err.message, 'Error occurred', `Error "${err.message}" thrown as expected`) + } + + test.end() + } catch (err) { + Logger.error(`createTest failed with error - ${err}`) + test.fail() + test.end() + } + }) + + await createTest.end() + } catch (err) { + Logger.error(`settlementWindowContentStateChangeModelTest failed with error - ${err}`) + createTest.fail() + createTest.end() + } + }) + + await settlementWindowContentStateChangeModelTest.test('getBySettlementWindowContentId should', async getBySettlementWindowContentIdTest => { + try { + await getBySettlementWindowContentIdTest.test('return settlement windows state change record', async test => { + try { + const settlementWindowContentId = 1 + const settlementWindowContentStateChange = { + settlementWindowContentStateChangeId: 25, + settlementWindowContentId, + settlementWindowStateId: 'CLOSED', + reason: 'text', + createdDate: '2019-02-18T16:47:35.000Z' + } + Db.getKnex = sandbox.stub() + const knexStub = sandbox.stub() + Db.getKnex.returns(knexStub) + const whereStub = sandbox.stub() + const orderByStub = sandbox.stub() + const selectStub = sandbox.stub() + const firstStub = sandbox.stub() + knexStub.returns({ + where: whereStub.returns({ + orderBy: orderByStub.returns({ + select: selectStub.returns({ + first: firstStub.returns(settlementWindowContentStateChange) + }) + }) + }) + }) + const result = await SettlementWindowContentStateChangeModel.getBySettlementWindowContentId(settlementWindowContentId) + test.deepEqual(result, settlementWindowContentStateChange, 'results match') + test.end() + } catch (err) { + test.pass('Error thrown') + test.end() + } + }) + + await getBySettlementWindowContentIdTest.end() + } catch (err) { + Logger.error(`getBySettlementWindowContentIdTest failed with error - ${err}`) + getBySettlementWindowContentIdTest.fail() + getBySettlementWindowContentIdTest.end() + } + }) + + await settlementWindowContentStateChangeModelTest.end() +}) diff --git a/test/unit/setup.test.js b/test/unit/setup.test.js deleted file mode 100644 index a544e0cd..00000000 --- a/test/unit/setup.test.js +++ /dev/null @@ -1,191 +0,0 @@ -/***** - License - -------------- - Copyright © 2017 Bill & Melinda Gates Foundation - The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. - Contributors - -------------- - This is the official list of the Mojaloop project contributors for this file. - Names of the original copyright holders (individuals or organizations) - should be listed with a '*' in the first column. People who have - contributed from an organization can be listed under the organization - that actually holds the copyright for their contributions (see the - Gates Foundation organization for an example). Those individuals should have - their names indented and be marked with a '-'. Email address can be added - optionally within square brackets . - * Gates Foundation - - Name Surname - - * Georgi Georgiev - * Valentin Genev - -------------- - ******/ - -'use strict' - -const Test = require('tapes')(require('tape')) -const Sinon = require('sinon') -const Logger = require('@mojaloop/central-services-logger') -const Proxyquire = require('proxyquire') -const Path = require('path') -const Config = require('../../src/lib/config') - -Test('Server Setup', async setupTest => { - let sandbox - let serverStub - let HapiStub - let HapiOpenAPIStub - let PathStub - let DbStub - let EnumsStub - let ConfigStub - let EngineStub - let SetupProxy - - setupTest.beforeEach(test => { - try { - sandbox = Sinon.createSandbox() - - serverStub = { - register: sandbox.stub(), - method: sandbox.stub(), - start: sandbox.stub(), - log: sandbox.stub(), - plugins: { - openapi: { - setHost: sandbox.stub() - } - }, - info: { - host: Config.HOSTNAME, - port: Config.PORT - }, - ext: sandbox.stub() - } - HapiStub = { - Server: sandbox.stub().returns(serverStub) - } - DbStub = { - connect: sandbox.stub().returns(Promise.resolve()) - } - HapiOpenAPIStub = sandbox.stub() - PathStub = Path - EnumsStub = [sandbox.stub()] - ConfigStub = Config - EngineStub = sandbox.stub() - - SetupProxy = Proxyquire('../../src/setup', { - '@hapi/catbox-memory': EngineStub, - '@hapi/hapi': HapiStub, - 'hapi-openapi': HapiOpenAPIStub, - path: PathStub, - './lib/db': DbStub, - './models/lib/enums': EnumsStub, - './lib/config': ConfigStub - }) - } catch (err) { - Logger.error(`setupTest failed with error - ${err}`) - console.error(err.message) - } - test.end() - }) - - setupTest.afterEach(test => { - sandbox.restore() - test.end() - }) - - await setupTest.test('init should', async initTest => { - try { - await initTest.test('test 1', async test => { - try { - const errorToThrow = new Error('Throw Boom error') - - const HapiStubThrowError = { - Server: sandbox.stub().callsFake((opt) => { - opt.routes.validate.failAction(sandbox.stub(), sandbox.stub(), errorToThrow) - return serverStub - }) - } - - const SetupProxy1 = Proxyquire('../../src/setup', { - '@hapi/catbox-memory': EngineStub, - '@hapi/hapi': HapiStubThrowError, - 'hapi-openapi': HapiOpenAPIStub, - path: PathStub, - './lib/db': DbStub, - './models/lib/enums': EnumsStub, - './lib/config': ConfigStub - }) - - const server = await SetupProxy1.initialize() - test.ok(server, 'return server object') - test.ok(HapiStubThrowError.Server.calledOnce, 'Hapi.Server called once') - test.ok(DbStub.connect.calledOnce, 'Db.connect called once') - test.equal(serverStub.register.callCount, 7, 'server.register called 7 times') - test.ok(serverStub.method.calledOnce, 'server.method called once') - test.ok(serverStub.start.calledOnce, 'server.start called once') - test.ok(serverStub.plugins.openapi.setHost.calledOnce, 'server.plugins.openapi.setHost called once') - test.ok(serverStub.ext.calledOnce, 'server.ext called once') - test.end() - } catch (err) { - Logger.error(`init failed with error - ${err}`) - test.fail() - test.end() - } - }) - - await initTest.test('should catch errors and console.error output', async test => { - try { - const e = new Error('Database unavailable') - DbStub.connect = sandbox.stub().throws(e) - const consoleErrorStub = sandbox.stub(console, 'error') - await SetupProxy.initialize() - test.ok(consoleErrorStub.withArgs(e).calledOnce) - consoleErrorStub.restore() - test.end() - } catch (err) { - Logger.error(`init failed with error - ${err}`) - test.fail() - test.end() - } - }) - - await initTest.test('should catch errors after createServer and use server.log', async test => { - try { - const e = new Error('setHost error') - serverStub.plugins.openapi.setHost = sandbox.stub().throws(e) - await SetupProxy.initialize() - test.ok(serverStub.log.withArgs('error', e.message).calledOnce) - test.end() - } catch (err) { - Logger.error(`init failed with error - ${err}`) - test.fail() - test.end() - } - }) - - await initTest.test('invoke server method enums', async test => { - try { - await SetupProxy.__testonly__.getEnums(0) - test.ok(EnumsStub[0].withArgs().calledOnce) - test.end() - } catch (err) { - Logger.error(`init failed with error - ${err}`) - test.fail() - test.end() - } - }) - - await initTest.end() - } catch (err) { - Logger.error(`setupTest failed with error - ${err}`) - initTest.fail() - initTest.end() - } - }) - - await setupTest.end() -}) diff --git a/test/unit/shared/setup.test.js b/test/unit/shared/setup.test.js new file mode 100644 index 00000000..1f077d4a --- /dev/null +++ b/test/unit/shared/setup.test.js @@ -0,0 +1,432 @@ +/***** + License + -------------- + Copyright © 2017 Bill & Melinda Gates Foundation + The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + Contributors + -------------- + This is the official list of the Mojaloop project contributors for this file. + Names of the original copyright holders (individuals or organizations) + should be listed with a '*' in the first column. People who have + contributed from an organization can be listed under the organization + that actually holds the copyright for their contributions (see the + Gates Foundation organization for an example). Those individuals should have + their names indented and be marked with a '-'. Email address can be added + optionally within square brackets . + * Gates Foundation + - Name Surname + + * Georgi Georgiev + * Valentin Genev + -------------- + ******/ + +'use strict' + +const Config = require('../../../src/lib/config') +const getPort = require('get-port') +const Logger = require('@mojaloop/central-services-logger') +const Path = require('path') +const Proxyquire = require('proxyquire') +const Sinon = require('sinon') +const Test = require('tapes')(require('tape')) + +Test('Server Setup', async setupTest => { + let sandbox + let serverStub + let HapiStub + let HapiOpenAPIStub + let PathStub + let DbStub + let EnumsStub + let ConfigStub + let EngineStub + let SetupProxy + let RegisterHandlersStub + + setupTest.beforeEach(test => { + try { + sandbox = Sinon.createSandbox() + + RegisterHandlersStub = { + registerAllHandlers: sandbox.stub().returns(Promise.resolve()), + settlementWindow: { + registerSettlementWindowHandler: sandbox.stub().returns(Promise.resolve()) + } + } + + serverStub = { + register: sandbox.stub(), + method: sandbox.stub(), + start: sandbox.stub(), + log: sandbox.stub(), + plugins: { + openapi: { + setHost: sandbox.stub() + } + }, + info: { + host: Config.HOSTNAME, + port: Config.PORT + }, + ext: sandbox.stub() + } + HapiStub = { + Server: sandbox.stub().returns(serverStub) + } + DbStub = { + connect: sandbox.stub().returns(Promise.resolve()) + } + HapiOpenAPIStub = sandbox.stub() + PathStub = Path + EnumsStub = [sandbox.stub()] + ConfigStub = Config + EngineStub = sandbox.stub() + + SetupProxy = Proxyquire('../../../src/shared/setup', { + '@hapi/catbox-memory': EngineStub, + '@hapi/hapi': HapiStub, + 'hapi-openapi': HapiOpenAPIStub, + path: PathStub, + '../lib/db': DbStub, + '../models/lib/enums': EnumsStub, + '../lib/config': ConfigStub + }) + } catch (err) { + Logger.error(`setupTest failed with error - ${err}`) + console.error(err.message) + } + test.end() + }) + + setupTest.afterEach(test => { + sandbox.restore() + test.end() + }) + + await setupTest.test('init should', async initTest => { + try { + await initTest.test('test 1 - API', async test => { + try { + const errorToThrow = new Error('Throw Boom error') + + const HapiStubThrowError = { + Server: sandbox.stub().callsFake((opt) => { + opt.routes.validate.failAction(sandbox.stub(), sandbox.stub(), errorToThrow) + return serverStub + }) + } + + const SetupProxy1 = Proxyquire('../../../src/shared/setup', { + '@hapi/catbox-memory': EngineStub, + '@hapi/hapi': HapiStubThrowError, + 'hapi-openapi': HapiOpenAPIStub, + path: PathStub, + '../lib/db': DbStub, + '../models/lib/enums': EnumsStub, + '../lib/config': ConfigStub + }) + + const port = await getPort() + const server = await SetupProxy1.initialize({ service: 'api', port }) + test.ok(server, 'return server object') + test.ok(HapiStubThrowError.Server.calledOnce, 'Hapi.Server called once') + test.ok(DbStub.connect.calledOnce, 'Db.connect called once') + test.equal(serverStub.register.callCount, 7, 'server.register called 7 times') + test.ok(serverStub.method.calledOnce, 'server.method called once') + test.ok(serverStub.start.calledOnce, 'server.start called once') + test.ok(serverStub.plugins.openapi.setHost.calledOnce, 'server.plugins.openapi.setHost called once') + test.ok(serverStub.ext.calledOnce, 'server.ext called once') + test.end() + } catch (err) { + Logger.error(`init failed with error - ${err}`) + test.fail() + test.end() + } + }) + + await initTest.test('test 1 - handler', async test => { + try { + const errorToThrow = new Error('Throw Boom error') + + const HapiStubThrowError = { + Server: sandbox.stub().callsFake((opt) => { + opt.routes.validate.failAction(sandbox.stub(), sandbox.stub(), errorToThrow) + return serverStub + }) + } + + const SetupProxy1 = Proxyquire('../../../src/shared/setup', { + '@hapi/catbox-memory': EngineStub, + '@hapi/hapi': HapiStubThrowError, + 'hapi-openapi': HapiOpenAPIStub, + path: PathStub, + '../lib/db': DbStub, + '../models/lib/enums': EnumsStub, + '../lib/config': ConfigStub + }) + + const port = await getPort() + const server = await SetupProxy1.initialize({ service: 'handler', port }) + test.ok(server, 'return server object') + test.ok(HapiStubThrowError.Server.calledOnce, 'Hapi.Server called once') + test.ok(DbStub.connect.calledOnce, 'Db.connect called once') + test.equal(serverStub.register.callCount, 7, 'server.register called 7 times') + test.ok(serverStub.method.calledOnce, 'server.method called once') + test.ok(serverStub.start.calledOnce, 'server.start called once') + test.ok(serverStub.plugins.openapi.setHost.calledOnce, 'server.plugins.openapi.setHost called once') + test.end() + } catch (err) { + Logger.error(`init failed with error - ${err}`) + test.fail() + test.end() + } + }) + + await initTest.test('test 2 - handler', async test => { + try { + const errorToThrow = new Error('Throw Boom error') + + const HapiStubThrowError = { + Server: sandbox.stub().callsFake((opt) => { + opt.routes.validate.failAction(sandbox.stub(), sandbox.stub(), errorToThrow) + return serverStub + }) + } + + const Config2Stub = Object.assign({}, ConfigStub) + Config2Stub.HANDLERS_API_DISABLED = true + const SetupProxy1 = Proxyquire('../../../src/shared/setup', { + '@hapi/catbox-memory': EngineStub, + '@hapi/hapi': HapiStubThrowError, + 'hapi-openapi': HapiOpenAPIStub, + path: PathStub, + '../lib/db': DbStub, + '../models/lib/enums': EnumsStub, + '../lib/config': Config2Stub + }) + + const port = await getPort() + const server = await SetupProxy1.initialize({ service: 'handler', port }) + test.notok(server, 'not create server object') + test.end() + } catch (err) { + Logger.error(`init failed with error - ${err}`) + test.fail() + test.end() + } + }) + + await initTest.test('test - handler service type, run handlers true and a handler list', async test => { + try { + const errorToThrow = new Error('Throw Boom error') + + const HapiStubThrowError = { + Server: sandbox.stub().callsFake((opt) => { + opt.routes.validate.failAction(sandbox.stub(), sandbox.stub(), errorToThrow) + return serverStub + }) + } + + const SetupProxy1 = Proxyquire('../../../src/shared/setup', { + '../handlers/register': RegisterHandlersStub, + '@hapi/catbox-memory': EngineStub, + '@hapi/hapi': HapiStubThrowError, + 'hapi-openapi': HapiOpenAPIStub, + path: PathStub, + '../lib/db': DbStub, + '../models/lib/enums': EnumsStub, + '../lib/config': ConfigStub + }) + + const settlementwindowHandler = { + type: 'settlementwindow', + enabled: true + } + const fakeHandler = { + type: 'fake', + enabled: false + } + + const modulesList = [ + settlementwindowHandler, + fakeHandler + ] + + const port = await getPort() + const server = await SetupProxy1.initialize({ service: 'handler', port, modules: [], runHandlers: true, handlers: modulesList }) + test.ok(server, 'return server object') + test.ok(RegisterHandlersStub.settlementWindow.registerSettlementWindowHandler.called) + test.end() + } catch (err) { + Logger.error(`init failed with error - ${err}`) + test.fail(`Should have not received an error: ${err}`) + test.end() + } + }) + + await initTest.test('test - handler service type, run handlers true a handler list and incorrect handler type', async test => { + try { + const errorToThrow = new Error('Throw Boom error') + + const HapiStubThrowError = { + Server: sandbox.stub().callsFake((opt) => { + opt.routes.validate.failAction(sandbox.stub(), sandbox.stub(), errorToThrow) + return serverStub + }) + } + + const SetupProxy1 = Proxyquire('../../../src/shared/setup', { + '../handlers/register': RegisterHandlersStub, + '@hapi/catbox-memory': EngineStub, + '@hapi/hapi': HapiStubThrowError, + 'hapi-openapi': HapiOpenAPIStub, + path: PathStub, + '../lib/db': DbStub, + '../models/lib/enums': EnumsStub, + '../lib/config': ConfigStub + }) + + const settlementwindowHandler = { + type: 'invalidWindow', + enabled: true + } + + const modulesList = [ + settlementwindowHandler + ] + + const port = await getPort() + const server = await SetupProxy1.initialize({ service: 'handler', port, modules: [], runHandlers: true, handlers: modulesList }) + test.ok(server, 'return server object') + test.ok(RegisterHandlersStub.settlementWindow.registerSettlementWindowHandler.called) + test.end() + } catch (err) { + Logger.error(`init failed with error - ${err}`) + test.pass(`Should have failed with an error: ${err}`) + test.end() + } + }) + + await initTest.test('test - handler service type and run handlers true', async test => { + try { + const errorToThrow = new Error('Throw Boom error') + + const HapiStubThrowError = { + Server: sandbox.stub().callsFake((opt) => { + opt.routes.validate.failAction(sandbox.stub(), sandbox.stub(), errorToThrow) + return serverStub + }) + } + + const SetupProxy1 = Proxyquire('../../../src/shared/setup', { + '../handlers/register': RegisterHandlersStub, + '@hapi/catbox-memory': EngineStub, + '@hapi/hapi': HapiStubThrowError, + 'hapi-openapi': HapiOpenAPIStub, + path: PathStub, + '../lib/db': DbStub, + '../models/lib/enums': EnumsStub, + '../lib/config': ConfigStub + }) + + const port = await getPort() + const server = await SetupProxy1.initialize({ service: 'handler', port, modules: [], runHandlers: true }) + test.ok(server, 'return server object') + test.ok(RegisterHandlersStub.registerAllHandlers.called) + test.end() + } catch (err) { + Logger.error(`init failed with error - ${err}`) + test.fail(`Should have not received an error: ${err}`) + test.end() + } + }) + + await initTest.test('test 1 - invalid service type', async test => { + try { + const errorToThrow = new Error('No valid service type') + + const HapiStubThrowError = { + Server: sandbox.stub().callsFake((opt) => { + opt.routes.validate.failAction(sandbox.stub(), sandbox.stub(), errorToThrow) + return serverStub + }) + } + + const SetupProxy1 = Proxyquire('../../../src/shared/setup', { + '@hapi/catbox-memory': EngineStub, + '@hapi/hapi': HapiStubThrowError, + 'hapi-openapi': HapiOpenAPIStub, + path: PathStub, + '../lib/db': DbStub, + '../models/lib/enums': EnumsStub, + '../lib/config': ConfigStub + }) + + const port = await getPort() + const server = await SetupProxy1.initialize({ service: 'invalid', port }) + test.fail(server, 'Invalid service type') + test.end() + } catch (err) { + Logger.error(`init failed with error - ${err}`) + test.pass() + test.end() + } + }) + + await initTest.test('should catch errors and console.error output', async test => { + try { + const e = new Error('Database unavailable') + DbStub.connect = sandbox.stub().throws(e) + const consoleErrorStub = sandbox.stub(console, 'error') + const port = await getPort() + await SetupProxy.initialize({ service: 'api', port }) + test.ok(consoleErrorStub.withArgs(e).calledOnce) + consoleErrorStub.restore() + test.end() + } catch (err) { + Logger.error(`init failed with error - ${err}`) + test.fail() + test.end() + } + }) + + await initTest.test('should catch errors after server.start and use server.log', async test => { + try { + const e = new Error('setHost error') + serverStub.plugins.openapi.setHost = sandbox.stub().throws(e) + const port = await getPort() + await SetupProxy.initialize({ service: 'api', port }) + test.ok(serverStub.log.withArgs('error', e.message).calledOnce) + test.end() + } catch (err) { + Logger.error(`init failed with error - ${err}`) + test.fail() + test.end() + } + }) + + await initTest.test('invoke server method enums', async test => { + try { + await SetupProxy.__testonly__.getEnums(0) + test.ok(EnumsStub[0].withArgs().calledOnce) + test.end() + } catch (err) { + Logger.error(`init failed with error - ${err}`) + test.fail() + test.end() + } + }) + + await initTest.end() + } catch (err) { + Logger.error(`setupTest failed with error - ${err}`) + initTest.fail() + initTest.end() + } + }) + + await setupTest.end() +}) diff --git a/test/unit/utils/logger-plugin.test.js b/test/unit/utils/logger-plugin.test.js deleted file mode 100644 index 764a9d71..00000000 --- a/test/unit/utils/logger-plugin.test.js +++ /dev/null @@ -1,238 +0,0 @@ -/***** - License - -------------- - Copyright © 2017 Bill & Melinda Gates Foundation - The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. - Contributors - -------------- - This is the official list of the Mojaloop project contributors for this file. - Names of the original copyright holders (individuals or organizations) - should be listed with a '*' in the first column. People who have - contributed from an organization can be listed under the organization - that actually holds the copyright for their contributions (see the - Gates Foundation organization for an example). Those individuals should have - their names indented and be marked with a '-'. Email address can be added - optionally within square brackets . - * Gates Foundation - - Name Surname - - * Georgi Georgiev - * Valentin Genev - -------------- - ******/ - -'use strict' - -const Test = require('tapes')(require('tape')) -const Sinon = require('sinon') -const Logger = require('@mojaloop/central-services-logger') -const Proxyquire = require('proxyquire') -// const checkEmpty = require('./../../../src/utils/truthyProperty') - -Test('loggerPlugin utility', async (loggerPluginTest) => { - let sandbox - - loggerPluginTest.beforeEach(test => { - sandbox = Sinon.createSandbox() - test.end() - }) - - loggerPluginTest.afterEach(test => { - sandbox.restore() - test.end() - }) - - await loggerPluginTest.test('should register logger-plugin', async test => { - try { - const serverStub = sandbox.stub() - serverStub.events = { - on: sandbox.stub() - } - const eventMock = { - tags: ['tagged'], - data: 'data' - } - const loggerInfoStub = sandbox.stub() - const loggerPluginProxy = Proxyquire('../../../src/utils/logger-plugin', { - '@mojaloop/central-services-logger': { - info: loggerInfoStub - } - }) - serverStub.events.on.callsArgWith(1, eventMock) - await loggerPluginProxy.plugin.register(serverStub) - delete eventMock.tags - await loggerPluginProxy.plugin.register(serverStub) - test.ok(!(serverStub.events.on.notCalled), 'server.events.on is called once') - test.ok(!(loggerInfoStub.notCalled), 'Logger.info with arg event.data is called once') - test.end() - } catch (err) { - Logger.error(`create failed with error - ${err}`) - test.fail() - test.end() - } - }) - - await loggerPluginTest.test('should log all logs', async test => { - try { - const serverStub = sandbox.stub() - serverStub.events = { - on: sandbox.stub() - } - const eventMock = { - tags: ['info'], - data: 'data' - } - const loggerInfoStub = sandbox.stub() - const loggerPluginProxy = Proxyquire('../../../src/utils/logger-plugin', { - '@mojaloop/central-services-logger': { - info: loggerInfoStub - } - }) - serverStub.events.on.callsArgWith(1, eventMock) - await loggerPluginProxy.plugin.register(serverStub) - delete eventMock.tags - await loggerPluginProxy.plugin.register(serverStub) - test.ok(!(serverStub.events.on.notCalled), 'server.events.on is called once') - test.ok(!(loggerInfoStub.notCalled), 'Logger.info with arg event.data is called once') - test.end() - } catch (err) { - Logger.error(`create failed with error - ${err}`) - test.fail() - test.end() - } - }) - - await loggerPluginTest.test('should log errors', async test => { - try { - const serverStub = sandbox.stub() - serverStub.events = { - on: sandbox.stub() - } - const eventMock = { - tags: ['error'], - error: new Error('error') - } - const loggerInfoStub = sandbox.stub() - const loggerPluginProxy = Proxyquire('../../../src/utils/logger-plugin', { - '@mojaloop/central-services-logger': { - info: loggerInfoStub - } - }) - serverStub.events.on.callsArgWith(1, eventMock) - await loggerPluginProxy.plugin.register(serverStub) - delete eventMock.tags - await loggerPluginProxy.plugin.register(serverStub) - test.ok(!(serverStub.events.on.notCalled), 'server.events.on is called once') - test.ok(!(loggerInfoStub.notCalled), 'Logger.info with arg event.data is called once') - test.end() - } catch (err) { - Logger.error(`create failed with error - ${err}`) - test.fail() - test.end() - } - }) - - await loggerPluginTest.test('should log for requests', async test => { - try { - const serverStub = sandbox.stub() - serverStub.events = { - on: sandbox.stub() - } - const eventMock = { - tags: ['request'], - data: { - method: 'method', - path: 'path' - } - } - const checkEmpty = Sinon.stub() - const loggerInfoStub = sandbox.stub() - const loggerPluginProxy = Proxyquire('../../../src/utils/logger-plugin', { - '@mojaloop/central-services-logger': { - info: loggerInfoStub - } - }) - checkEmpty.returns(1) - serverStub.events.on.callsArgWith(1, eventMock) - await loggerPluginProxy.plugin.register(serverStub) - delete eventMock.tags - await loggerPluginProxy.plugin.register(serverStub) - test.ok(!(serverStub.events.on.notCalled), 'server.events.on is called once') - test.ok(!(loggerInfoStub.notCalled), 'Logger.info with arg event.data is called once') - test.end() - } catch (err) { - Logger.error(`create failed with error - ${err}`) - test.fail() - test.end() - } - }) - - await loggerPluginTest.test('should log empty requests', async test => { - try { - const serverStub = sandbox.stub() - serverStub.events = { - on: sandbox.stub() - } - const eventMock = { - tags: ['request'], - data: { - method: 'method', - path: 'path' - } - } - const checkEmpty = Sinon.stub() - const loggerInfoStub = sandbox.stub() - const loggerPluginProxy = Proxyquire('../../../src/utils/logger-plugin', { - '@mojaloop/central-services-logger': { - info: loggerInfoStub - } - }) - checkEmpty.returns(0) - serverStub.events.on.callsArgWith(1, eventMock) - await loggerPluginProxy.plugin.register(serverStub) - delete eventMock.tags - await loggerPluginProxy.plugin.register(serverStub) - test.ok(!(serverStub.events.on.notCalled), 'server.events.on is called once') - test.ok(!(loggerInfoStub.notCalled), 'Logger.info with arg event.data is called once') - test.end() - } catch (err) { - Logger.error(`create failed with error - ${err}`) - test.fail() - test.end() - } - }) - - await loggerPluginTest.test('should log empty requests', async test => { - try { - const serverStub = sandbox.stub() - serverStub.events = { - on: sandbox.stub() - } - const eventMock = { - tags: ['response'], - data: 'data' - } - const loggerInfoStub = sandbox.stub() - const loggerPluginProxy = Proxyquire('../../../src/utils/logger-plugin', { - '@mojaloop/central-services-logger': { - info: loggerInfoStub - } - }) - serverStub.events.on.callsArgWith(1, eventMock) - await loggerPluginProxy.plugin.register(serverStub) - delete eventMock.tags - await loggerPluginProxy.plugin.register(serverStub) - test.ok(!(serverStub.events.on.notCalled), 'server.events.on is called once') - test.ok(!(loggerInfoStub.notCalled), 'Logger.info with arg event.data is called once') - test.end() - } catch (err) { - Logger.error(`create failed with error - ${err}`) - test.fail() - test.end() - } - }) - - loggerPluginTest.end() -})