diff --git a/jest.config.js b/jest.config.js index 5c602bd5..bed97c45 100644 --- a/jest.config.js +++ b/jest.config.js @@ -16,4 +16,5 @@ module.exports = { moduleNameMapper: { '^.+\\.module\\.(css|sass|scss)$': 'identity-obj-proxy', }, + modulePaths: ['/'], }; diff --git a/package-lock.json b/package-lock.json index 5a54df6d..08b9a6ae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4630,6 +4630,15 @@ "@types/node": "*" } }, + "@types/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-invOmosX0DqbpA+cE2yoHGUlF/blyf7nB0OGYBBiH27crcVm5NmFaZkLP4Ta1hGaesckCi5lVLlydNJCxkTOSg==", + "dev": true, + "requires": { + "@types/express": "*" + } + }, "@types/eslint-visitor-keys": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", @@ -5636,6 +5645,88 @@ "tslib": "^1.9.3" } }, + "apollo-server": { + "version": "2.14.4", + "resolved": "https://registry.npmjs.org/apollo-server/-/apollo-server-2.14.4.tgz", + "integrity": "sha512-5RRs/UnzZMK+QGqCE8Wyfy5vNBFPmweFlkMRs966pM+6orN/g2GxxypKRsGa2rit2Wz0wki8vw+MjI80t2lPvg==", + "dev": true, + "requires": { + "apollo-server-core": "^2.14.4", + "apollo-server-express": "^2.14.4", + "express": "^4.0.0", + "graphql-subscriptions": "^1.0.0", + "graphql-tools": "^4.0.0" + }, + "dependencies": { + "@apollographql/graphql-playground-html": { + "version": "1.6.26", + "resolved": "https://registry.npmjs.org/@apollographql/graphql-playground-html/-/graphql-playground-html-1.6.26.tgz", + "integrity": "sha512-XAwXOIab51QyhBxnxySdK3nuMEUohhDsHQ5Rbco/V1vjlP75zZ0ZLHD9dTpXTN8uxKxopb2lUvJTq+M4g2Q0HQ==", + "dev": true, + "requires": { + "xss": "^1.0.6" + } + }, + "apollo-engine-reporting": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/apollo-engine-reporting/-/apollo-engine-reporting-2.0.1.tgz", + "integrity": "sha512-3OYYk7DqNuJ5xKYnyLy5O2n506jYSryim8WqzBTn9MRphRamwPFjHYQm+akPA60AubXrWnYa6A8euMAiQU0ttA==", + "dev": true, + "requires": { + "apollo-engine-reporting-protobuf": "^0.5.1", + "apollo-graphql": "^0.4.0", + "apollo-server-caching": "^0.5.1", + "apollo-server-env": "^2.4.4", + "apollo-server-errors": "^2.4.1", + "apollo-server-plugin-base": "^0.9.0", + "apollo-server-types": "^0.5.0", + "async-retry": "^1.2.1", + "uuid": "^8.0.0" + } + }, + "apollo-server-core": { + "version": "2.14.4", + "resolved": "https://registry.npmjs.org/apollo-server-core/-/apollo-server-core-2.14.4.tgz", + "integrity": "sha512-aAfsvbJ2YrqAXDBgcBQocOmQJ5DkeOnEYQ6ADdkkDNU68V5yBRkAHLTOzPfbUlGHVrnOH8PT1FIVWwu5mBgkVA==", + "dev": true, + "requires": { + "@apollographql/apollo-tools": "^0.4.3", + "@apollographql/graphql-playground-html": "1.6.26", + "@types/graphql-upload": "^8.0.0", + "@types/ws": "^7.0.0", + "apollo-cache-control": "^0.11.0", + "apollo-datasource": "^0.7.1", + "apollo-engine-reporting": "^2.0.1", + "apollo-server-caching": "^0.5.1", + "apollo-server-env": "^2.4.4", + "apollo-server-errors": "^2.4.1", + "apollo-server-plugin-base": "^0.9.0", + "apollo-server-types": "^0.5.0", + "apollo-tracing": "^0.11.0", + "fast-json-stable-stringify": "^2.0.0", + "graphql-extensions": "^0.12.3", + "graphql-tag": "^2.9.2", + "graphql-tools": "^4.0.0", + "graphql-upload": "^8.0.2", + "loglevel": "^1.6.7", + "sha.js": "^2.4.11", + "subscriptions-transport-ws": "^0.9.11", + "ws": "^6.0.0" + } + }, + "graphql-extensions": { + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/graphql-extensions/-/graphql-extensions-0.12.3.tgz", + "integrity": "sha512-W7iT0kzlwTiZU7fXfw9IgWnsqVj7EFLd0/wVcZZRAbR8L3f4+YsGls0oxKdsrvYBnbG347BXKQmIyo6GTEk4XA==", + "dev": true, + "requires": { + "@apollographql/apollo-tools": "^0.4.3", + "apollo-server-env": "^2.4.4", + "apollo-server-types": "^0.5.0" + } + } + } + }, "apollo-server-caching": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/apollo-server-caching/-/apollo-server-caching-0.5.1.tgz", @@ -5687,6 +5778,111 @@ "resolved": "https://registry.npmjs.org/apollo-server-errors/-/apollo-server-errors-2.4.1.tgz", "integrity": "sha512-7oEd6pUxqyWYUbQ9TA8tM0NU/3aGtXSEibo6+txUkuHe7QaxfZ2wHRp+pfT1LC1K3RXYjKj61/C2xEO19s3Kdg==" }, + "apollo-server-express": { + "version": "2.14.4", + "resolved": "https://registry.npmjs.org/apollo-server-express/-/apollo-server-express-2.14.4.tgz", + "integrity": "sha512-g0ml0NGmghvJhTiXMR0HqDD8eTz77zdgzDG2XoqNoRehtVIsZq8fmKTagVt9cUKCKKiBPUF+4tqAGD9lnprUdw==", + "dev": true, + "requires": { + "@apollographql/graphql-playground-html": "1.6.26", + "@types/accepts": "^1.3.5", + "@types/body-parser": "1.19.0", + "@types/cors": "^2.8.4", + "@types/express": "4.17.4", + "accepts": "^1.3.5", + "apollo-server-core": "^2.14.4", + "apollo-server-types": "^0.5.0", + "body-parser": "^1.18.3", + "cors": "^2.8.4", + "express": "^4.17.1", + "graphql-subscriptions": "^1.0.0", + "graphql-tools": "^4.0.0", + "parseurl": "^1.3.2", + "subscriptions-transport-ws": "^0.9.16", + "type-is": "^1.6.16" + }, + "dependencies": { + "@apollographql/graphql-playground-html": { + "version": "1.6.26", + "resolved": "https://registry.npmjs.org/@apollographql/graphql-playground-html/-/graphql-playground-html-1.6.26.tgz", + "integrity": "sha512-XAwXOIab51QyhBxnxySdK3nuMEUohhDsHQ5Rbco/V1vjlP75zZ0ZLHD9dTpXTN8uxKxopb2lUvJTq+M4g2Q0HQ==", + "dev": true, + "requires": { + "xss": "^1.0.6" + } + }, + "@types/express": { + "version": "4.17.4", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.4.tgz", + "integrity": "sha512-DO1L53rGqIDUEvOjJKmbMEQ5Z+BM2cIEPy/eV3En+s166Gz+FeuzRerxcab757u/U4v4XF4RYrZPmqKa+aY/2w==", + "dev": true, + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "*", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "apollo-engine-reporting": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/apollo-engine-reporting/-/apollo-engine-reporting-2.0.1.tgz", + "integrity": "sha512-3OYYk7DqNuJ5xKYnyLy5O2n506jYSryim8WqzBTn9MRphRamwPFjHYQm+akPA60AubXrWnYa6A8euMAiQU0ttA==", + "dev": true, + "requires": { + "apollo-engine-reporting-protobuf": "^0.5.1", + "apollo-graphql": "^0.4.0", + "apollo-server-caching": "^0.5.1", + "apollo-server-env": "^2.4.4", + "apollo-server-errors": "^2.4.1", + "apollo-server-plugin-base": "^0.9.0", + "apollo-server-types": "^0.5.0", + "async-retry": "^1.2.1", + "uuid": "^8.0.0" + } + }, + "apollo-server-core": { + "version": "2.14.4", + "resolved": "https://registry.npmjs.org/apollo-server-core/-/apollo-server-core-2.14.4.tgz", + "integrity": "sha512-aAfsvbJ2YrqAXDBgcBQocOmQJ5DkeOnEYQ6ADdkkDNU68V5yBRkAHLTOzPfbUlGHVrnOH8PT1FIVWwu5mBgkVA==", + "dev": true, + "requires": { + "@apollographql/apollo-tools": "^0.4.3", + "@apollographql/graphql-playground-html": "1.6.26", + "@types/graphql-upload": "^8.0.0", + "@types/ws": "^7.0.0", + "apollo-cache-control": "^0.11.0", + "apollo-datasource": "^0.7.1", + "apollo-engine-reporting": "^2.0.1", + "apollo-server-caching": "^0.5.1", + "apollo-server-env": "^2.4.4", + "apollo-server-errors": "^2.4.1", + "apollo-server-plugin-base": "^0.9.0", + "apollo-server-types": "^0.5.0", + "apollo-tracing": "^0.11.0", + "fast-json-stable-stringify": "^2.0.0", + "graphql-extensions": "^0.12.3", + "graphql-tag": "^2.9.2", + "graphql-tools": "^4.0.0", + "graphql-upload": "^8.0.2", + "loglevel": "^1.6.7", + "sha.js": "^2.4.11", + "subscriptions-transport-ws": "^0.9.11", + "ws": "^6.0.0" + } + }, + "graphql-extensions": { + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/graphql-extensions/-/graphql-extensions-0.12.3.tgz", + "integrity": "sha512-W7iT0kzlwTiZU7fXfw9IgWnsqVj7EFLd0/wVcZZRAbR8L3f4+YsGls0oxKdsrvYBnbG347BXKQmIyo6GTEk4XA==", + "dev": true, + "requires": { + "@apollographql/apollo-tools": "^0.4.3", + "apollo-server-env": "^2.4.4", + "apollo-server-types": "^0.5.0" + } + } + } + }, "apollo-server-micro": { "version": "2.14.2", "resolved": "https://registry.npmjs.org/apollo-server-micro/-/apollo-server-micro-2.14.2.tgz", @@ -5707,6 +5903,84 @@ "apollo-server-types": "^0.5.0" } }, + "apollo-server-testing": { + "version": "2.14.4", + "resolved": "https://registry.npmjs.org/apollo-server-testing/-/apollo-server-testing-2.14.4.tgz", + "integrity": "sha512-4yaqj0eMpic9DhYsEG+lyAdLwRPacnU50DsBWlG6ckqhDlcIxCsJ+5zeDxUZRUeOorlDwxXOtHwRaTlPs0sj8g==", + "dev": true, + "requires": { + "apollo-server-core": "^2.14.4" + }, + "dependencies": { + "@apollographql/graphql-playground-html": { + "version": "1.6.26", + "resolved": "https://registry.npmjs.org/@apollographql/graphql-playground-html/-/graphql-playground-html-1.6.26.tgz", + "integrity": "sha512-XAwXOIab51QyhBxnxySdK3nuMEUohhDsHQ5Rbco/V1vjlP75zZ0ZLHD9dTpXTN8uxKxopb2lUvJTq+M4g2Q0HQ==", + "dev": true, + "requires": { + "xss": "^1.0.6" + } + }, + "apollo-engine-reporting": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/apollo-engine-reporting/-/apollo-engine-reporting-2.0.1.tgz", + "integrity": "sha512-3OYYk7DqNuJ5xKYnyLy5O2n506jYSryim8WqzBTn9MRphRamwPFjHYQm+akPA60AubXrWnYa6A8euMAiQU0ttA==", + "dev": true, + "requires": { + "apollo-engine-reporting-protobuf": "^0.5.1", + "apollo-graphql": "^0.4.0", + "apollo-server-caching": "^0.5.1", + "apollo-server-env": "^2.4.4", + "apollo-server-errors": "^2.4.1", + "apollo-server-plugin-base": "^0.9.0", + "apollo-server-types": "^0.5.0", + "async-retry": "^1.2.1", + "uuid": "^8.0.0" + } + }, + "apollo-server-core": { + "version": "2.14.4", + "resolved": "https://registry.npmjs.org/apollo-server-core/-/apollo-server-core-2.14.4.tgz", + "integrity": "sha512-aAfsvbJ2YrqAXDBgcBQocOmQJ5DkeOnEYQ6ADdkkDNU68V5yBRkAHLTOzPfbUlGHVrnOH8PT1FIVWwu5mBgkVA==", + "dev": true, + "requires": { + "@apollographql/apollo-tools": "^0.4.3", + "@apollographql/graphql-playground-html": "1.6.26", + "@types/graphql-upload": "^8.0.0", + "@types/ws": "^7.0.0", + "apollo-cache-control": "^0.11.0", + "apollo-datasource": "^0.7.1", + "apollo-engine-reporting": "^2.0.1", + "apollo-server-caching": "^0.5.1", + "apollo-server-env": "^2.4.4", + "apollo-server-errors": "^2.4.1", + "apollo-server-plugin-base": "^0.9.0", + "apollo-server-types": "^0.5.0", + "apollo-tracing": "^0.11.0", + "fast-json-stable-stringify": "^2.0.0", + "graphql-extensions": "^0.12.3", + "graphql-tag": "^2.9.2", + "graphql-tools": "^4.0.0", + "graphql-upload": "^8.0.2", + "loglevel": "^1.6.7", + "sha.js": "^2.4.11", + "subscriptions-transport-ws": "^0.9.11", + "ws": "^6.0.0" + } + }, + "graphql-extensions": { + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/graphql-extensions/-/graphql-extensions-0.12.3.tgz", + "integrity": "sha512-W7iT0kzlwTiZU7fXfw9IgWnsqVj7EFLd0/wVcZZRAbR8L3f4+YsGls0oxKdsrvYBnbG347BXKQmIyo6GTEk4XA==", + "dev": true, + "requires": { + "@apollographql/apollo-tools": "^0.4.3", + "apollo-server-env": "^2.4.4", + "apollo-server-types": "^0.5.0" + } + } + } + }, "apollo-server-types": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/apollo-server-types/-/apollo-server-types-0.5.0.tgz", @@ -9205,6 +9479,12 @@ "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==" }, + "cssfilter": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/cssfilter/-/cssfilter-0.0.10.tgz", + "integrity": "sha1-xtJnJjKi5cg+AT5oZKQs6N79IK4=", + "dev": true + }, "cssnano": { "version": "4.1.10", "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-4.1.10.tgz", @@ -12537,6 +12817,15 @@ "yup": "^0.27.0" } }, + "graphql-subscriptions": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/graphql-subscriptions/-/graphql-subscriptions-1.1.0.tgz", + "integrity": "sha512-6WzlBFC0lWmXJbIVE8OgFgXIP4RJi3OQgTPa0DVMsDXdpRDjTsM1K9wfl5HSYX7R87QAGlvcv2Y4BIZa/ItonA==", + "dev": true, + "requires": { + "iterall": "^1.2.1" + } + }, "graphql-tag": { "version": "2.10.3", "resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.10.3.tgz", @@ -14637,6 +14926,16 @@ } } }, + "jest-fetch-mock": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/jest-fetch-mock/-/jest-fetch-mock-3.0.3.tgz", + "integrity": "sha512-Ux1nWprtLrdrH4XwE7O7InRY6psIi3GOsqNESJgMJ+M5cv4A8Lh7SN9d2V2kKRZ8ebAfcd1LNyZguAOb6JiDqw==", + "dev": true, + "requires": { + "cross-fetch": "^3.0.4", + "promise-polyfill": "^8.1.3" + } + }, "jest-get-type": { "version": "25.2.6", "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-25.2.6.tgz", @@ -19999,6 +20298,12 @@ "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=" }, + "promise-polyfill": { + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.1.3.tgz", + "integrity": "sha512-MG5r82wBzh7pSKDRa9y+vllNHz3e3d4CNj1PQE4BQYxLme0gKYYBm9YENq+UkEikyZ0XbiGWxYlVw3Rl9O/U8g==", + "dev": true + }, "prompts": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.3.2.tgz", @@ -24407,6 +24712,16 @@ "@babel/runtime-corejs3": "^7.8.3" } }, + "xss": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/xss/-/xss-1.0.7.tgz", + "integrity": "sha512-A9v7tblGvxu8TWXQC9rlpW96a+LN1lyw6wyhpTmmGW+FwRMactchBR3ROKSi33UPCUcUHSu8s9YP6F+K3Mw//w==", + "dev": true, + "requires": { + "commander": "^2.20.3", + "cssfilter": "0.0.10" + } + }, "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/package.json b/package.json index ed69b954..71c021f9 100644 --- a/package.json +++ b/package.json @@ -106,6 +106,8 @@ "@types/styled-theming": "^2.2.3", "@typescript-eslint/eslint-plugin": "^3.1.0", "@typescript-eslint/parser": "^3.1.0", + "apollo-server": "^2.14.4", + "apollo-server-testing": "^2.14.4", "babel-jest": "^26.0.1", "babel-loader": "^8.1.0", "babel-plugin-inline-react-svg": "^1.1.1", @@ -124,6 +126,7 @@ "fast-diff": "^1.2.0", "husky": "^4.2.5", "jest": "^26.0.1", + "jest-fetch-mock": "^3.0.3", "lint-staged": "^10.2.9", "prettier": "^2.0.5", "standard-version": "^8.0.0", diff --git a/server/helpers/helpers.ts b/server/helpers/helpers.ts index ec9246c9..cd4d1d05 100644 --- a/server/helpers/helpers.ts +++ b/server/helpers/helpers.ts @@ -11,9 +11,9 @@ import AES from 'crypto-js/aes'; import CryptoJS from 'crypto-js'; import { logger } from './logger'; -const { serverRuntimeConfig, publicRuntimeConfig } = getConfig(); -const { nodeEnv } = serverRuntimeConfig; -const { noClient } = publicRuntimeConfig; +const { serverRuntimeConfig, publicRuntimeConfig } = getConfig() || {}; +const { nodeEnv } = serverRuntimeConfig || {}; +const { noClient } = publicRuntimeConfig || {}; type LndAuthType = { cert: string; @@ -37,7 +37,7 @@ export const getCorrectAuth = ( auth: AuthType, context: ContextType ): LndAuthType => { - if (auth.type === 'test' && nodeEnv === 'development') { + if (auth.type === 'test' && nodeEnv !== 'production') { return { host: process.env.TEST_HOST, macaroon: process.env.TEST_MACAROON, diff --git a/server/helpers/logger.ts b/server/helpers/logger.ts index 46dc1c72..b8d37f2c 100644 --- a/server/helpers/logger.ts +++ b/server/helpers/logger.ts @@ -1,37 +1,23 @@ import { createLogger, format, transports } from 'winston'; import getConfig from 'next/config'; -const { serverRuntimeConfig } = getConfig(); +const { serverRuntimeConfig = {} } = getConfig() || {}; const { logLevel } = serverRuntimeConfig; -const combinedFormat = - // nodeEnv === 'development' ? - format.combine( - format.label({ label: 'THUB' }), - format.splat(), - format.colorize(), - format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), - format.printf( - (info: any) => - `${info.timestamp} ${info.level} [${info.label}]: ${info.message}` - ) - ); -// : format.combine( -// format.label({ -// label: path.basename( -// process && process.mainModule ? process.mainModule.filename : '' -// ), -// }), -// format.splat(), -// format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), -// format.printf( -// (info: any) => -// `${info.timestamp} ${info.level} [${info.label}]: ${info.message}` -// ) -// ); +const combinedFormat = format.combine( + format.label({ label: 'THUB' }), + format.splat(), + format.colorize(), + format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), + format.printf( + (info: any) => + `${info.timestamp} ${info.level} [${info.label}]: ${info.message}` + ) +); export const logger = createLogger({ level: logLevel, format: combinedFormat, transports: [new transports.Console()], + silent: process.env.NODE_ENV === 'test' ? true : false, }); diff --git a/server/schema/account/__snapshots__/account.test.ts.snap b/server/schema/account/__snapshots__/account.test.ts.snap new file mode 100644 index 00000000..a43edb74 --- /dev/null +++ b/server/schema/account/__snapshots__/account.test.ts.snap @@ -0,0 +1,73 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Account Resolvers getServerAccounts full accounts 1`] = ` +Object { + "data": Object { + "getServerAccounts": Array [ + Object { + "id": "accountID", + "loggedIn": true, + "name": "account", + "type": "server", + }, + Object { + "id": "sso", + "loggedIn": true, + "name": "SSO Account", + "type": "sso", + }, + ], + }, + "errors": undefined, + "extensions": undefined, + "http": Object { + "headers": Headers { + Symbol(map): Object {}, + }, + }, +} +`; + +exports[`Account Resolvers getServerAccounts without SSO 1`] = ` +Object { + "data": Object { + "getServerAccounts": Array [ + Object { + "id": "accountID", + "loggedIn": true, + "name": "account", + "type": "server", + }, + ], + }, + "errors": undefined, + "extensions": undefined, + "http": Object { + "headers": Headers { + Symbol(map): Object {}, + }, + }, +} +`; + +exports[`Account Resolvers getServerAccounts without accounts 1`] = ` +Object { + "data": Object { + "getServerAccounts": Array [ + Object { + "id": "accountID", + "loggedIn": true, + "name": "account", + "type": "server", + }, + ], + }, + "errors": undefined, + "extensions": undefined, + "http": Object { + "headers": Headers { + Symbol(map): Object {}, + }, + }, +} +`; diff --git a/server/schema/account/account.test.ts b/server/schema/account/account.test.ts new file mode 100644 index 00000000..74f0835b --- /dev/null +++ b/server/schema/account/account.test.ts @@ -0,0 +1,43 @@ +import testServer from 'server/tests/testServer'; +import { GET_SERVER_ACCOUNTS } from 'src/graphql/queries/getServerAccounts'; +import { ContextMockNoSSO } from 'server/tests/testMocks'; + +jest.mock('ln-service'); + +describe('Account Resolvers', () => { + describe('getServerAccounts', () => { + test('full accounts', async () => { + const { query } = testServer(); + + const res = await query({ + query: GET_SERVER_ACCOUNTS, + variables: { auth: { type: 'test' } }, + }); + + expect(res.errors).toBe(undefined); + expect(res).toMatchSnapshot(); + }); + test('without SSO', async () => { + const { query } = testServer(ContextMockNoSSO); + + const res = await query({ + query: GET_SERVER_ACCOUNTS, + variables: { auth: { type: 'test' } }, + }); + + expect(res.errors).toBe(undefined); + expect(res).toMatchSnapshot(); + }); + test('without accounts', async () => { + const { query } = testServer(ContextMockNoSSO); + + const res = await query({ + query: GET_SERVER_ACCOUNTS, + variables: { auth: { type: 'test' } }, + }); + + expect(res.errors).toBe(undefined); + expect(res).toMatchSnapshot(); + }); + }); +}); diff --git a/server/schema/account/resolvers.ts b/server/schema/account/resolvers.ts index f2d45196..ffe95437 100644 --- a/server/schema/account/resolvers.ts +++ b/server/schema/account/resolvers.ts @@ -7,7 +7,7 @@ export const accountResolvers = { Query: { getServerAccounts: async ( _: undefined, - params: any, + __: undefined, context: ContextType ) => { const { ip, accounts, account, sso, ssoVerified } = context; diff --git a/server/schema/auth/resolvers.ts b/server/schema/auth/resolvers.ts index 47482b02..78f8e694 100644 --- a/server/schema/auth/resolvers.ts +++ b/server/schema/auth/resolvers.ts @@ -8,8 +8,8 @@ import cookie from 'cookie'; import { requestLimiter } from 'server/helpers/rateLimiter'; import AES from 'crypto-js/aes'; -const { serverRuntimeConfig } = getConfig(); -const { cookiePath, nodeEnv } = serverRuntimeConfig; +const { serverRuntimeConfig } = getConfig() || {}; +const { cookiePath, nodeEnv } = serverRuntimeConfig || {}; export const authResolvers = { Query: { diff --git a/server/schema/bitcoin/__snapshots__/bitcoin.test.ts.snap b/server/schema/bitcoin/__snapshots__/bitcoin.test.ts.snap new file mode 100644 index 00000000..fb773e6a --- /dev/null +++ b/server/schema/bitcoin/__snapshots__/bitcoin.test.ts.snap @@ -0,0 +1,69 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Bitcoin Resolvers getBitcoinFees failure 1`] = ` +Object { + "data": Object { + "getBitcoinFees": null, + }, + "errors": Array [ + [GraphQLError: Problem getting Bitcoin fees.], + ], + "extensions": undefined, + "http": Object { + "headers": Headers { + Symbol(map): Object {}, + }, + }, +} +`; + +exports[`Bitcoin Resolvers getBitcoinFees success 1`] = ` +Object { + "data": Object { + "getBitcoinFees": Object { + "fast": 3, + "halfHour": 2, + "hour": 1, + }, + }, + "errors": undefined, + "extensions": undefined, + "http": Object { + "headers": Headers { + Symbol(map): Object {}, + }, + }, +} +`; + +exports[`Bitcoin Resolvers getBitcoinPrice failure 1`] = ` +Object { + "data": Object { + "getBitcoinPrice": null, + }, + "errors": Array [ + [GraphQLError: Problem getting Bitcoin price.], + ], + "extensions": undefined, + "http": Object { + "headers": Headers { + Symbol(map): Object {}, + }, + }, +} +`; + +exports[`Bitcoin Resolvers getBitcoinPrice success 1`] = ` +Object { + "data": Object { + "getBitcoinPrice": "{\\"price\\":\\"price\\"}", + }, + "errors": undefined, + "extensions": undefined, + "http": Object { + "headers": Headers { + Symbol(map): Object {}, + }, + }, +} +`; diff --git a/server/schema/bitcoin/bitcoin.test.ts b/server/schema/bitcoin/bitcoin.test.ts new file mode 100644 index 00000000..9bf545f8 --- /dev/null +++ b/server/schema/bitcoin/bitcoin.test.ts @@ -0,0 +1,75 @@ +import testServer from 'server/tests/testServer'; +import fetchMock from 'jest-fetch-mock'; +import { GraphQLError } from 'graphql'; +import { GET_BITCOIN_PRICE } from 'src/graphql/queries/getBitcoinPrice'; +import { GET_BITCOIN_FEES } from 'src/graphql/queries/getBitcoinFees'; + +describe('Bitcoin Resolvers', () => { + beforeEach(() => { + fetchMock.resetMocks(); + }); + describe('getBitcoinPrice', () => { + test('success', async () => { + fetchMock.mockResponseOnce(JSON.stringify({ price: 'price' })); + const { query } = testServer(); + + const res = await query({ + query: GET_BITCOIN_PRICE, + }); + + expect(res.errors).toBe(undefined); + + expect(fetchMock).toBeCalledWith('https://blockchain.info/ticker'); + expect(res).toMatchSnapshot(); + }); + test('failure', async () => { + fetchMock.mockRejectOnce(new Error('Error')); + const { query } = testServer(); + + const res = await query({ + query: GET_BITCOIN_PRICE, + }); + + expect(res.errors).toStrictEqual([ + new GraphQLError('Problem getting Bitcoin price.'), + ]); + expect(res).toMatchSnapshot(); + }); + }); + describe('getBitcoinFees', () => { + test('success', async () => { + fetchMock.mockResponseOnce( + JSON.stringify({ + fastestFee: 3, + halfHourFee: 2, + hourFee: 1, + }) + ); + const { query } = testServer(); + + const res = await query({ + query: GET_BITCOIN_FEES, + }); + + expect(res.errors).toBe(undefined); + + expect(fetchMock).toBeCalledWith( + 'https://bitcoinfees.earn.com/api/v1/fees/recommended' + ); + expect(res).toMatchSnapshot(); + }); + test('failure', async () => { + fetchMock.mockRejectOnce(new Error('Error')); + const { query } = testServer(); + + const res = await query({ + query: GET_BITCOIN_FEES, + }); + + expect(res.errors).toStrictEqual([ + new GraphQLError('Problem getting Bitcoin fees.'), + ]); + expect(res).toMatchSnapshot(); + }); + }); +}); diff --git a/server/schema/bitcoin/resolvers.ts b/server/schema/bitcoin/resolvers.ts index 739aa6dd..72a91f42 100644 --- a/server/schema/bitcoin/resolvers.ts +++ b/server/schema/bitcoin/resolvers.ts @@ -7,7 +7,7 @@ export const bitcoinResolvers = { Query: { getBitcoinPrice: async ( _: undefined, - params: any, + __: undefined, context: ContextType ) => { await requestLimiter(context.ip, 'bitcoinPrice'); diff --git a/server/schema/chain/__snapshots__/chain.test.ts.snap b/server/schema/chain/__snapshots__/chain.test.ts.snap new file mode 100644 index 00000000..48ad444a --- /dev/null +++ b/server/schema/chain/__snapshots__/chain.test.ts.snap @@ -0,0 +1,31 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Chain Resolvers getChainBalance 1`] = ` +Object { + "data": Object { + "getChainBalance": 1000000, + }, + "errors": undefined, + "extensions": undefined, + "http": Object { + "headers": Headers { + Symbol(map): Object {}, + }, + }, +} +`; + +exports[`Chain Resolvers getPendingChainBalance 1`] = ` +Object { + "data": Object { + "getPendingChainBalance": 1000000, + }, + "errors": undefined, + "extensions": undefined, + "http": Object { + "headers": Headers { + Symbol(map): Object {}, + }, + }, +} +`; diff --git a/server/schema/chain/chain.test.ts b/server/schema/chain/chain.test.ts new file mode 100644 index 00000000..1d4f2109 --- /dev/null +++ b/server/schema/chain/chain.test.ts @@ -0,0 +1,36 @@ +import { AuthMock } from 'server/tests/testMocks'; +import testServer from 'server/tests/testServer'; +import gql from 'graphql-tag'; + +jest.mock('ln-service'); + +describe('Chain Resolvers', () => { + test('getChainBalance', async () => { + const getChainBalance = gql` + query($auth: authType!) { + getChainBalance(auth: $auth) + } + `; + const { query } = testServer(); + const res = await query({ + query: getChainBalance, + variables: AuthMock, + }); + expect(res.errors).toBe(undefined); + expect(res).toMatchSnapshot(); + }); + test('getPendingChainBalance', async () => { + const getPendingChainBalance = gql` + query($auth: authType!) { + getPendingChainBalance(auth: $auth) + } + `; + const { query } = testServer(); + const res = await query({ + query: getPendingChainBalance, + variables: AuthMock, + }); + expect(res.errors).toBe(undefined); + expect(res).toMatchSnapshot(); + }); +}); diff --git a/server/schema/hodlhodl/resolvers.ts b/server/schema/hodlhodl/resolvers.ts index a4b8ef7f..a4b29ccb 100644 --- a/server/schema/hodlhodl/resolvers.ts +++ b/server/schema/hodlhodl/resolvers.ts @@ -5,8 +5,8 @@ import { logger } from 'server/helpers/logger'; import { appUrls } from 'server/utils/appUrls'; import { getHodlParams } from 'server/helpers/hodlHelpers'; -const { serverRuntimeConfig } = getConfig(); -const { hodlKey } = serverRuntimeConfig; +const { serverRuntimeConfig } = getConfig() || {}; +const { hodlKey } = serverRuntimeConfig || {}; const defaultQuery = { filters: {}, diff --git a/server/schema/index.ts b/server/schema/index.ts index 7bddbccf..f65e94af 100644 --- a/server/schema/index.ts +++ b/server/schema/index.ts @@ -37,6 +37,7 @@ import { githubResolvers } from './github/resolvers'; import { routeTypes } from './route/types'; import { generalResolvers } from './resolvers'; import { macaroonResolvers } from './macaroon/resolvers'; +import { networkResolvers } from './network/resolvers'; const typeDefs = [ generalTypes, @@ -80,7 +81,8 @@ const resolvers = merge( transactionResolvers, healthResolvers, githubResolvers, - macaroonResolvers + macaroonResolvers, + networkResolvers ); export default makeExecutableSchema({ typeDefs, resolvers }); diff --git a/server/schema/lnpay/__snapshots__/lnpay.test.ts.snap b/server/schema/lnpay/__snapshots__/lnpay.test.ts.snap new file mode 100644 index 00000000..2836993d --- /dev/null +++ b/server/schema/lnpay/__snapshots__/lnpay.test.ts.snap @@ -0,0 +1,68 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`LnPay Resolvers getLnPay failure 1`] = ` +Object { + "data": Object { + "getLnPay": null, + }, + "errors": Array [ + [GraphQLError: NoLnPayInvoice], + ], + "extensions": undefined, + "http": Object { + "headers": Headers { + Symbol(map): Object {}, + }, + }, +} +`; + +exports[`LnPay Resolvers getLnPay success 1`] = ` +Object { + "data": Object { + "getLnPay": "paymentRequest", + }, + "errors": undefined, + "extensions": undefined, + "http": Object { + "headers": Headers { + Symbol(map): Object {}, + }, + }, +} +`; + +exports[`LnPay Resolvers getLnPayInfo failure 1`] = ` +Object { + "data": Object { + "getLnPayInfo": null, + }, + "errors": Array [ + [GraphQLError: NoLnPay], + ], + "extensions": undefined, + "http": Object { + "headers": Headers { + Symbol(map): Object {}, + }, + }, +} +`; + +exports[`LnPay Resolvers getLnPayInfo success 1`] = ` +Object { + "data": Object { + "getLnPayInfo": Object { + "max": 1000, + "min": 1, + }, + }, + "errors": undefined, + "extensions": undefined, + "http": Object { + "headers": Headers { + Symbol(map): Object {}, + }, + }, +} +`; diff --git a/server/schema/lnpay/lnpay.test.ts b/server/schema/lnpay/lnpay.test.ts new file mode 100644 index 00000000..4d52b368 --- /dev/null +++ b/server/schema/lnpay/lnpay.test.ts @@ -0,0 +1,68 @@ +import testServer from 'server/tests/testServer'; +import { GET_LN_PAY } from 'src/graphql/queries/getLnPay'; +import fetchMock from 'jest-fetch-mock'; +import { GraphQLError } from 'graphql'; +import { GET_LN_PAY_INFO } from 'src/graphql/queries/getLnPayInfo'; + +describe('LnPay Resolvers', () => { + beforeEach(() => { + fetchMock.resetMocks(); + }); + describe('getLnPay', () => { + test('success', async () => { + fetchMock.mockResponseOnce(JSON.stringify({ pr: 'paymentRequest' })); + const { query } = testServer(); + + const res = await query({ + query: GET_LN_PAY, + variables: { amount: 100 }, + }); + + expect(res.errors).toBe(undefined); + + expect(fetchMock).toBeCalledWith( + 'https://thunderhub.io/api/lnpay?amount=100' + ); + expect(res).toMatchSnapshot(); + }); + test('failure', async () => { + fetchMock.mockRejectOnce(new Error('Error')); + const { query } = testServer(); + + const res = await query({ + query: GET_LN_PAY, + variables: { amount: 100 }, + }); + + expect(res.errors).toStrictEqual([new GraphQLError('NoLnPayInvoice')]); + expect(res).toMatchSnapshot(); + }); + }); + describe('getLnPayInfo', () => { + test('success', async () => { + fetchMock.mockResponseOnce( + JSON.stringify({ maxSendable: 1000, minSendable: 1 }) + ); + const { query } = testServer(); + + const res = await query({ + query: GET_LN_PAY_INFO, + }); + + expect(res.errors).toBe(undefined); + expect(fetchMock).toBeCalledWith('https://thunderhub.io/api/lnpay'); + expect(res).toMatchSnapshot(); + }); + test('failure', async () => { + fetchMock.mockRejectOnce(new Error('Error')); + const { query } = testServer(); + + const res = await query({ + query: GET_LN_PAY_INFO, + }); + + expect(res.errors).toStrictEqual([new GraphQLError('NoLnPay')]); + expect(res).toMatchSnapshot(); + }); + }); +}); diff --git a/server/schema/network/__snapshots__/network.test.ts.snap b/server/schema/network/__snapshots__/network.test.ts.snap new file mode 100644 index 00000000..3f9f51aa --- /dev/null +++ b/server/schema/network/__snapshots__/network.test.ts.snap @@ -0,0 +1,25 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Network Resolvers getNetworkInfo success 1`] = ` +Object { + "data": Object { + "getNetworkInfo": Object { + "averageChannelSize": "1000", + "channelCount": 123, + "maxChannelSize": 456, + "medianChannelSize": 789, + "minChannelSize": 100, + "nodeCount": 10000, + "notRecentlyUpdatedPolicyCount": 10, + "totalCapacity": "10000", + }, + }, + "errors": undefined, + "extensions": undefined, + "http": Object { + "headers": Headers { + Symbol(map): Object {}, + }, + }, +} +`; diff --git a/server/schema/network/network.test.ts b/server/schema/network/network.test.ts new file mode 100644 index 00000000..2d3256b4 --- /dev/null +++ b/server/schema/network/network.test.ts @@ -0,0 +1,20 @@ +import testServer from 'server/tests/testServer'; +import { GET_NETWORK_INFO } from 'src/graphql/queries/getNetworkInfo'; + +jest.mock('ln-service'); + +describe('Network Resolvers', () => { + describe('getNetworkInfo', () => { + test('success', async () => { + const { query } = testServer(); + + const res = await query({ + query: GET_NETWORK_INFO, + variables: { auth: { type: 'test' } }, + }); + + expect(res.errors).toBe(undefined); + expect(res).toMatchSnapshot(); + }); + }); +}); diff --git a/server/schema/network/resolvers.ts b/server/schema/network/resolvers.ts index 55d8c3e6..db91130b 100644 --- a/server/schema/network/resolvers.ts +++ b/server/schema/network/resolvers.ts @@ -1,12 +1,8 @@ -import { getNetworkInfo as getLnNetworkInfo } from 'ln-service'; +import { getNetworkInfo } from 'ln-service'; import { ContextType } from 'server/types/apiTypes'; -import { logger } from 'server/helpers/logger'; import { requestLimiter } from 'server/helpers/rateLimiter'; -import { - getAuthLnd, - getErrorMsg, - getCorrectAuth, -} from 'server/helpers/helpers'; +import { getLnd } from 'server/helpers/helpers'; +import { to } from 'server/helpers/async'; interface NetworkInfoProps { average_channel_size: number; @@ -24,26 +20,20 @@ export const networkResolvers = { getNetworkInfo: async (_: undefined, params: any, context: ContextType) => { await requestLimiter(context.ip, 'networkInfo'); - const auth = getCorrectAuth(params.auth, context); - const lnd = getAuthLnd(auth); + const lnd = getLnd(params.auth, context); - try { - const info: NetworkInfoProps = await getLnNetworkInfo({ lnd }); + const info: NetworkInfoProps = await to(getNetworkInfo({ lnd })); - return { - averageChannelSize: info.average_channel_size, - channelCount: info.channel_count, - maxChannelSize: info.max_channel_size, - medianChannelSize: info.median_channel_size, - minChannelSize: info.min_channel_size, - nodeCount: info.node_count, - notRecentlyUpdatedPolicyCount: info.not_recently_updated_policy_count, - totalCapacity: info.total_capacity, - }; - } catch (error) { - logger.error('Error getting network info: %o', error); - throw new Error(getErrorMsg(error)); - } + return { + averageChannelSize: info.average_channel_size, + channelCount: info.channel_count, + maxChannelSize: info.max_channel_size, + medianChannelSize: info.median_channel_size, + minChannelSize: info.min_channel_size, + nodeCount: info.node_count, + notRecentlyUpdatedPolicyCount: info.not_recently_updated_policy_count, + totalCapacity: info.total_capacity, + }; }, }, }; diff --git a/server/schema/node/types.ts b/server/schema/node/types.ts index 08f75572..a89f80e1 100644 --- a/server/schema/node/types.ts +++ b/server/schema/node/types.ts @@ -21,7 +21,7 @@ export const nodeTypes = gql` closed_channels_count: Int alias: String current_block_hash: String - current_block_height: Boolean + current_block_height: Int is_synced_to_chain: Boolean is_synced_to_graph: Boolean latest_block_at: String diff --git a/server/tests/__mocks__/ln-service.ts b/server/tests/__mocks__/ln-service.ts new file mode 100644 index 00000000..ba4fd50b --- /dev/null +++ b/server/tests/__mocks__/ln-service.ts @@ -0,0 +1,91 @@ +export const getNetworkInfo = () => + Promise.resolve({ + average_channel_size: 1000, + channel_count: 123, + max_channel_size: 456, + median_channel_size: 789, + min_channel_size: 100, + node_count: 10000, + not_recently_updated_policy_count: 10, + total_capacity: 10000, + }); + +export const getWalletInfo = () => + Promise.resolve({ + chains: ['abc', 'def'], + color: 'color', + active_channels_count: 1, + alias: 'TestNode', + current_block_hash: 'asd', + current_block_height: 123456, + is_synced_to_chain: true, + is_synced_to_graph: true, + latest_block_at: 'time', + peers_count: 1, + pending_channels_count: 2, + public_key: 'key', + uris: ['uri', 'uri2'], + version: 'version', + }); + +export const getClosedChannels = () => + Promise.resolve({ + channels: [ + { + capacity: 10000, + close_confirm_height: 123, + close_transaction_id: 'fghijk', + final_local_balance: 123, + final_time_locked_balance: 123, + id: 'id', + is_breach_close: false, + is_cooperative_close: true, + is_funding_cancel: false, + is_local_force_close: false, + is_remote_force_close: false, + partner_public_key: 'abcdef', + transaction_id: '123', + transaction_vout: 1, + }, + { + capacity: 10000, + close_confirm_height: 123, + close_transaction_id: 'fghijk', + final_local_balance: 123, + final_time_locked_balance: 123, + id: 'id', + is_breach_close: false, + is_cooperative_close: true, + is_funding_cancel: false, + is_local_force_close: false, + is_remote_force_close: false, + partner_public_key: 'abcdef', + transaction_id: '123', + transaction_vout: 1, + }, + { + capacity: 10000, + close_confirm_height: 123, + close_transaction_id: 'fghijk', + final_local_balance: 123, + final_time_locked_balance: 123, + id: 'id', + is_breach_close: false, + is_cooperative_close: true, + is_funding_cancel: false, + is_local_force_close: false, + is_remote_force_close: false, + partner_public_key: 'abcdef', + transaction_id: '123', + transaction_vout: 1, + }, + ], + }); + +export const getChainBalance = () => + Promise.resolve({ chain_balance: 1000000 }); + +export const getPendingChainBalance = () => + Promise.resolve({ pending_chain_balance: 1000000 }); + +export const authenticatedLndGrpc = jest.fn().mockReturnValue({}); diff --git a/server/tests/testMocks.ts b/server/tests/testMocks.ts new file mode 100644 index 00000000..8dc84d31 --- /dev/null +++ b/server/tests/testMocks.ts @@ -0,0 +1,75 @@ +import { ServerResponse } from 'http'; +import { ContextType } from 'server/types/apiTypes'; + +export const AuthMock = { + auth: { + type: 'test', + }, +}; + +export const ContextMock: ContextType = { + ip: '1.2.3.4', + secret: '123456789', + ssoVerified: true, + account: { + id: 'accountID', + password: 'password', + }, + sso: { + macaroon: 'macaroon', + cert: 'cert', + host: 'host', + }, + accounts: [ + { + name: 'account', + id: 'accountID', + host: 'host', + macaroon: 'macaroon', + cert: 'cert', + }, + ], + res: {} as ServerResponse, +}; + +export const ContextMockNoAccounts: ContextType = { + ip: '1.2.3.4', + secret: '123456789', + ssoVerified: true, + account: { + id: 'accountID', + password: 'password', + }, + sso: { + macaroon: 'macaroon', + cert: 'cert', + host: 'host', + }, + accounts: [], + res: {} as ServerResponse, +}; + +export const ContextMockNoSSO: ContextType = { + ip: '1.2.3.4', + secret: '123456789', + ssoVerified: true, + account: { + id: 'accountID', + password: 'password', + }, + sso: { + macaroon: null, + cert: null, + host: null, + }, + accounts: [ + { + name: 'account', + id: 'accountID', + host: 'host', + macaroon: 'macaroon', + cert: 'cert', + }, + ], + res: {} as ServerResponse, +}; diff --git a/server/tests/testServer.ts b/server/tests/testServer.ts new file mode 100644 index 00000000..69830b50 --- /dev/null +++ b/server/tests/testServer.ts @@ -0,0 +1,13 @@ +import { + createTestClient, + ApolloServerTestClient, +} from 'apollo-server-testing'; +import { ApolloServer } from 'apollo-server'; +import schema from 'server/schema'; +import { ContextMock } from 'server/tests/testMocks'; + +export default function testServer(context?: any): ApolloServerTestClient { + return createTestClient( + new ApolloServer({ schema, context: context || ContextMock }) + ); +} diff --git a/setupTests.js b/setupTests.js index 666127af..91550372 100644 --- a/setupTests.js +++ b/setupTests.js @@ -1 +1,4 @@ import '@testing-library/jest-dom/extend-expect'; +import fetchMock from 'jest-fetch-mock'; + +fetchMock.enableMocks(); diff --git a/src/graphql/types.ts b/src/graphql/types.ts index 0eb6eda6..cc3671ad 100644 --- a/src/graphql/types.ts +++ b/src/graphql/types.ts @@ -401,7 +401,7 @@ export type NodeInfoType = { closed_channels_count?: Maybe; alias?: Maybe; current_block_hash?: Maybe; - current_block_height?: Maybe; + current_block_height?: Maybe; is_synced_to_chain?: Maybe; is_synced_to_graph?: Maybe; latest_block_at?: Maybe;