From c6dfc5f8c85894bac55ba219856255a4883fdab1 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Tue, 18 Jan 2022 18:56:54 +0100 Subject: [PATCH] Convert tests to jest, added tls cert/macaroon file support --- .env.sample | 10 ++- package.json | 7 +- src/controllers/channel.controller.ts | 2 +- src/services/lnd-service.ts | 54 +++++++++--- test/app.test.ts | 17 ++-- test/controllers/channel.controller.test.ts | 22 ++--- test/controllers/main.controller.test.ts | 22 ++--- test/controllers/node.controller.test.ts | 95 ++++++++++++++------- test/helper.ts | 49 ++++++----- test/services/lnd-service.test.ts | 12 +-- yarn.lock | 33 ++++++- 11 files changed, 209 insertions(+), 114 deletions(-) diff --git a/.env.sample b/.env.sample index b3c0ca3..b913703 100644 --- a/.env.sample +++ b/.env.sample @@ -1,6 +1,8 @@ PORT=7464 -ALLOWED_HOSTS=['*'] MACAROON=ABC -LND_REST_API=localhost:8080 -REJECT_UNAUTHORIZED=false -TLS_CERT_BASE64=ABCD= \ No newline at end of file +LND_REST_API_WS=wss://localhost:8080 +LND_REST_API=https://localhost:8080 +REJECT_UNAUTHORIZED=0 +TLS_CERT_BASE64=ABCD= +#MACAROON_FILE=readonly.macaroon +#TLS_CERT_FILE=tls.cert \ No newline at end of file diff --git a/package.json b/package.json index 72429e2..7848834 100644 --- a/package.json +++ b/package.json @@ -35,19 +35,22 @@ "@types/node": "^17.0.8", "@typescript-eslint/eslint-plugin": "^5.8.1", "@typescript-eslint/parser": "^5.8.1", + "axios-mock-adapter": "^1.20.0", "concurrently": "^7.0.0", "eslint": "^8.5.0", "eslint-config-prettier": "^8.3.0", "fastify-tsconfig": "^1.0.1", "jest": "^27.4.7", + "jest-websocket-mock": "^2.2.1", + "mock-socket": "^9.1.0", "prettier": "^2.5.1", "ts-jest": "^27.1.2", "ts-node": "^10.4.0" }, "scripts": { - "start": "ts-node src/index.ts", + "start": "ts-node src/server.ts", "build:ts": "tsc", "lint": "eslint . --ext .ts", - "test": "npm run build:ts && tsc -p test/tsconfig.json && tap --no-coverage-map --no-check-coverage --ts test/**/*.test.ts" + "test": "jest" } } diff --git a/src/controllers/channel.controller.ts b/src/controllers/channel.controller.ts index 8efe03a..41da282 100644 --- a/src/controllers/channel.controller.ts +++ b/src/controllers/channel.controller.ts @@ -38,7 +38,7 @@ export default class ChannelController { .emit('channel', chanInfo); }) .catch(() => { - console.log('Error sending channel update'); + console.log(`Error sending channel update for ${channel.chan_id}`); }); } }); diff --git a/src/services/lnd-service.ts b/src/services/lnd-service.ts index 2df2821..0451945 100644 --- a/src/services/lnd-service.ts +++ b/src/services/lnd-service.ts @@ -1,4 +1,5 @@ import * as https from 'https'; +import {existsSync, readFileSync} from 'fs'; import axios from 'axios'; import {Subject} from 'rxjs'; @@ -8,14 +9,39 @@ import dotenv from 'dotenv'; dotenv.config(); -const decodedTlsCert = Buffer.from( - process.env.TLS_CERT_BASE64 || '', - 'base64', -).toString('ascii'); +let tlsCertData: string; +let macaroonData: string; +let rejectUnauthorized = true; + +// Check if files exists, otherwise try to read base64 data +if (existsSync(process.env.MACAROON_FILE)) { + macaroonData = readFileSync(process.env.MACAROON_FILE).toString('hex'); +} else { + macaroonData = process.env.MACAROON || ''; +} + +if (existsSync(process.env.TLS_CERT_FILE)) { + tlsCertData = readFileSync(process.env.TLS_CERT_FILE).toString('ascii'); +} else { + const tlsCertData = Buffer.from( + process.env.TLS_CERT_BASE64 || '', + 'base64', + ).toString('ascii'); +} + +if (tlsCertData === '' || macaroonData === '') { + throw new Error( + 'TLS Certificate or Macaroon could not be loaded, this is required to run ringtools-ts-server', + ); +} + +if (process.env.REJECT_UNAUTHORIZED) { + rejectUnauthorized = Boolean(Number(process.env.REJECT_UNAUTHORIZED)); +} const httpsAgent = new https.Agent({ - rejectUnauthorized: Boolean(process.env.REJECT_UNAUTHORIZED) || true, - ca: decodedTlsCert, + rejectUnauthorized, + ca: tlsCertData, }); axios.defaults.httpsAgent = httpsAgent; @@ -40,20 +66,24 @@ export class LndService { channelUpdateSubject: Subject = new Subject(); nodeUpdateSubject: Subject = new Subject(); closedChannelSubject: Subject = new Subject(); + lndRestApiWsUrl!: string; lndRestApiUrl!: string; + macaroon!: string; constructor() {} @Initializer() init() { - this.lndRestApiUrl = process.env.LND_REST_API || 'localhost:8080'; - this.macaroon = process.env.MACAROON || ''; + this.lndRestApiWsUrl = process.env.LND_REST_API_WS || 'ws://localhost:8080'; + this.lndRestApiUrl = process.env.LND_REST_API || 'http://localhost:8080'; + + this.macaroon = macaroonData; this.ws = new WebSocket( - `wss://${this.lndRestApiUrl}/v1/graph/subscribe?method=GET`, + `${this.lndRestApiWsUrl}/v1/graph/subscribe?method=GET`, { - rejectUnauthorized: Boolean(process.env.REJECT_UNAUTHORIZED) || true, - ca: decodedTlsCert, + rejectUnauthorized, + ca: tlsCertData, headers: { 'Grpc-Metadata-Macaroon': this.macaroon, }, @@ -99,7 +129,7 @@ export class LndService { } private doLndGet(endPoint: string) { - return axios.get(`https://${this.lndRestApiUrl}/${endPoint}`, { + return axios.get(`${this.lndRestApiUrl}/${endPoint}`, { responseType: 'json', headers: { diff --git a/test/app.test.ts b/test/app.test.ts index 1098724..27c5ba4 100644 --- a/test/app.test.ts +++ b/test/app.test.ts @@ -1,13 +1,14 @@ -import tap from 'tap'; - import {testBuild} from './helper'; -tap.test('requests the "/" route', async (t: any) => { - const app = await testBuild(t); - const response = await app.inject({ - method: 'GET', - url: '/', +describe('app tests', () => { + const app = testBuild(); + + test('requests the "/" route', async () => { + const res = await app.inject({ + method: 'GET', + url: '/', + }); + expect(res.statusCode).toBe(404); }); - t.equal(response.statusCode, 200, 'returns a status code of 200'); }); diff --git a/test/controllers/channel.controller.test.ts b/test/controllers/channel.controller.test.ts index 2ebaa2e..fa78bc3 100644 --- a/test/controllers/channel.controller.test.ts +++ b/test/controllers/channel.controller.test.ts @@ -1,18 +1,14 @@ -import tap from 'tap'; +import {testBuild} from '../helper'; -import {testBuild} from '../../test/helper'; +describe('channel controller tests', () => { + const app = testBuild(); -tap.test('Non-numeric channels should return 404', async (t: any) => { - t.plan(1); + test('Non-numeric channels should return 404', async () => { + const response = await app.inject({ + url: '/channel/abcdefg', + method: 'GET', + }); - const app = await testBuild(t); - - const response = await app.inject({ - url: '/channel/abcdefg', - method: 'GET', + expect(response.statusCode).toEqual(404); }); - - t.equal(response.statusCode, 404, 'returns a status code of 404'); - - t.teardown(() => app.close()); }); diff --git a/test/controllers/main.controller.test.ts b/test/controllers/main.controller.test.ts index 3fb0429..47b46dd 100644 --- a/test/controllers/main.controller.test.ts +++ b/test/controllers/main.controller.test.ts @@ -1,18 +1,14 @@ -import tap from 'tap'; +import {testBuild} from '../helper'; -import {testBuild} from '../../test/helper'; +describe('main controller tests', () => { + const app = testBuild(); -tap.test('Main controller should do nothing', async (t: any) => { - t.plan(1); + test('Main controller should do nothing', async () => { + const response = await app.inject({ + url: '/', + method: 'GET', + }); - const app = await testBuild(t); - - const response = await app.inject({ - url: '/', - method: 'GET', + expect(response.statusCode).toBe(404); }); - - t.equal(response.statusCode, 404, 'returns a status code of 404'); - - t.teardown(() => app.close()); }); diff --git a/test/controllers/node.controller.test.ts b/test/controllers/node.controller.test.ts index 24c60c4..4febdc8 100644 --- a/test/controllers/node.controller.test.ts +++ b/test/controllers/node.controller.test.ts @@ -1,42 +1,73 @@ -import tap from 'tap'; +import {mock, testBuild} from '../helper'; -import {testBuild} from '../helper'; +describe('node controller tests', () => { + const app = testBuild(); -tap.test('Too short pubkeys should return 404', async (t: any) => { - t.plan(1); - const app = await testBuild(t); - const response = await app.inject({ - url: '/node/abcdefg', - method: 'GET', + test('Too short pubkeys should return 404', (done) => { + app + .inject({ + url: '/node/abcdefg', + method: 'GET', + }) + .then((response) => { + expect(response.statusCode).toBe(404); + done(); + }); }); - t.equal(response.statusCode, 404, 'returns a status code of 404'); - - t.teardown(() => app.close()); -}); - -tap.test('Too long pubkeys should return 404', async (t: any) => { - t.plan(1); - const app = await testBuild(t); - const response = await app.inject({ - url: '/node/0380b3dbdf090cacee19eb4dc7a82630bd3de8b12608dd7bee971fb3cd2a5ae2fcd', - method: 'GET', + test('Too long pubkeys should return 404', (done) => { + app + .inject({ + url: '/node/0380b3dbdf090cacee19eb4dc7a82630bd3de8b12608dd7bee971fb3cd2a5ae2fcd', + method: 'GET', + }) + .then((response) => { + expect(response.statusCode).toBe(404); + done(); + }); }); - t.equal(response.statusCode, 404, 'returns a status code of 404'); - - t.teardown(() => app.close()); -}); - -tap.test('Node URI should return 404', async (t: any) => { - t.plan(1); - const app = await testBuild(t); - const response = await app.inject({ - url: '/node/0380b3dbdf090cacee19eb4dc7a82630bd3de8b12608dd7bee971fb3cd2a5ae2fc@[2a04:52c0:103:c1e3::1]:9735', - method: 'GET', + test('Node URI should return 404', (done) => { + app + .inject({ + url: '/node/0380b3dbdf090cacee19eb4dc7a82630bd3de8b12608dd7bee971fb3cd2a5ae2fc@[2a04:52c0:103:c1e3::1]:9735', + method: 'GET', + }) + .then((response) => { + expect(response.statusCode).toBe(404); + done(); + }); }); - t.equal(response.statusCode, 404, 'returns a status code of 404'); + test('Node URI should return 404', (done) => { + app + .inject({ + url: '/node/0380b3dbdf090cacee19eb4dc7a82630bd3de8b12608dd7bee971fb3cd2a5ae2fc@[2a04:52c0:103:c1e3::1]:9735', + method: 'GET', + }) + .then((response) => { + expect(response.statusCode).toBe(404); + done(); + }); + }); - t.teardown(() => app.close()); + + // FIXME: Fix WebSocket mock + // test('Valid Node URI should give nodeInfo', (done) => { + // mock.onGet(/^\/node\/\d+/).reply((config) => { + // console.log("Mock!", config.url); + // return [200, {}] + // }); + + + // app + // .inject({ + // url: '/node/0205a19356bbb7482057356aef070285a2ce6141d2448545210e9d575b57eddd37', + // method: 'GET', + // }) + // .then((response) => { + // expect(response.statusCode).toBe(200); + // done(); + // }); + // }); }); diff --git a/test/helper.ts b/test/helper.ts index b2aeb41..7b8d365 100644 --- a/test/helper.ts +++ b/test/helper.ts @@ -2,37 +2,42 @@ import Fastify from 'fastify' import fp from 'fastify-plugin' import { build } from '../src/app' -import * as tap from 'tap'; +import MockAdapter from "axios-mock-adapter"; +import axios from "axios"; -export type Test = typeof tap['Test']['prototype']; +process.env['REJECT_UNAUTHORIZED'] = '0'; +process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '0'; +process.env['LND_REST_API_WS'] = 'ws://localhost:8080'; + +const edgeMockData = {"channel_id":"760383758935523329","chan_point":"0000000000000000000000000000000000000000000000000000000000000000:0","last_update":1642475331,"node1_pub":"0205a19356bbb7482057356aef070285a2ce6141d2448545210e9d575b57eddd37","node2_pub":"03d1b19ebc6cdce5685bad391f266d077f0cacd2e5c1f46824e1427ce7dba2f630","capacity":"0","node1_policy":{"time_lock_delta":42,"min_htlc":"1000","fee_base_msat":"1","fee_rate_milli_msat":"100","disabled":false,"max_htlc_msat":"990000000","last_update":1642475331},"node2_policy":{"time_lock_delta":40,"min_htlc":"1000","fee_base_msat":"0","fee_rate_milli_msat":"100","disabled":false,"max_htlc_msat":"990000000","last_update":1642460307}}; -// Fill in this config with all the configurations -// needed for testing the application async function config () { return {} } -// Automatically build and tear down our instance -async function testBuild (t: Test) { - const app = Fastify() +let mock; - // fastify-plugin ensures that all decorators - // are exposed for testing purposes, this is - // different from the production setup - void app.register(fp(build), await config()) +export function testBuild() { + const app = Fastify(); - await app.ready(); + beforeAll(async () => { + void app.register(fp(build), await config()); + await app.ready(); + }); - // Tear down our app after we are done - t.teardown(async() => { - if (app) - await app.close() - }) + beforeEach(() => { + mock = new MockAdapter(axios); + mock.onAny(/^\/v1\/graph\/edge\/\d+/, edgeMockData); + }); + + afterEach(() => { + if (mock) + mock.reset(); + }); - return app + afterAll(() => app.close()); + + return app; } -export { - config, - testBuild -} +export { mock }; \ No newline at end of file diff --git a/test/services/lnd-service.test.ts b/test/services/lnd-service.test.ts index 864cbcf..260919c 100644 --- a/test/services/lnd-service.test.ts +++ b/test/services/lnd-service.test.ts @@ -1,9 +1,9 @@ -import tap from 'tap'; import { LndService } from '../../src/services/lnd-service'; -tap.test('LndService should instantiate', async (t: any) => { - t.plan(1) - const lndService = new LndService(); +describe('LndService tests', () => { + test('LndService should instantiate', async () => { + const lndService = new LndService(); - t.type(lndService, LndService); -}); + expect(lndService).toBeInstanceOf(LndService); + }); +}); \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index b213ad0..b748060 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1443,6 +1443,15 @@ axe-core@^4.3.5: resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.3.5.tgz#78d6911ba317a8262bfee292aeafcc1e04b49cc5" integrity sha512-WKTW1+xAzhMS5dJsxWkliixlO/PqC4VhmO9T4juNYcaTg9jzWiJsou6m5pxWYGfigWbwzJWeFY6z47a+4neRXA== +axios-mock-adapter@^1.20.0: + version "1.20.0" + resolved "https://registry.yarnpkg.com/axios-mock-adapter/-/axios-mock-adapter-1.20.0.tgz#21f5b4b625306f43e8c05673616719da86e20dcb" + integrity sha512-shZRhTjLP0WWdcvHKf3rH3iW9deb3UdKbdnKUoHmmsnBhVXN3sjPJM6ZvQ2r/ywgvBVQrMnjrSyQab60G1sr2w== + dependencies: + fast-deep-equal "^3.1.3" + is-blob "^2.1.0" + is-buffer "^2.0.5" + axios@^0.24.0: version "0.24.0" resolved "https://registry.yarnpkg.com/axios/-/axios-0.24.0.tgz#804e6fa1e4b9c5288501dd9dff56a7a0940d20d6" @@ -3484,6 +3493,11 @@ is-binary-path@~2.1.0: dependencies: binary-extensions "^2.0.0" +is-blob@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-blob/-/is-blob-2.1.0.tgz#e36cd82c90653f1e1b930f11baf9c64216a05385" + integrity sha512-SZ/fTft5eUhQM6oF/ZaASFDEdbFVe89Imltn9uZr03wdKMcWNVYSMjQPFtg05QuNkt5l5c135ElvXEQG0rk4tw== + is-boolean-object@^1.1.0: version "1.1.2" resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" @@ -3492,6 +3506,11 @@ is-boolean-object@^1.1.0: call-bind "^1.0.2" has-tostringtag "^1.0.0" +is-buffer@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.5.tgz#ebc252e400d22ff8d77fa09888821a24a658c191" + integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ== + is-callable@^1.1.4, is-callable@^1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.4.tgz#47301d58dd0259407865547853df6d61fe471945" @@ -3802,7 +3821,7 @@ jest-config@^27.4.7: pretty-format "^27.4.6" slash "^3.0.0" -jest-diff@^27.0.0, jest-diff@^27.4.6: +jest-diff@^27.0.0, jest-diff@^27.0.2, jest-diff@^27.4.6: version "27.4.6" resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-27.4.6.tgz#93815774d2012a2cbb6cf23f84d48c7a2618f98d" integrity sha512-zjaB0sh0Lb13VyPsd92V7HkqF6yKRH9vm33rwBt7rPYrpQvS1nCvlIy2pICbKta+ZjWngYLNn4cCK4nyZkjS/w== @@ -4108,6 +4127,13 @@ jest-watcher@^27.4.6: jest-util "^27.4.2" string-length "^4.0.1" +jest-websocket-mock@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/jest-websocket-mock/-/jest-websocket-mock-2.2.1.tgz#db4fc63733c9fc549c1fd0f79e6db8b3f947af1d" + integrity sha512-fhsGLXrPfs06PhHoxqOSA9yZ6Rb4qYrf4Wcm7/nfRzjlrf1gIeuhYUkzMRjjE0TMQ37SwkmeLanwrZY4ZaNp8g== + dependencies: + jest-diff "^27.0.2" + jest-worker@^27.4.6: version "27.4.6" resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.4.6.tgz#5d2d93db419566cb680752ca0792780e71b3273e" @@ -4515,6 +4541,11 @@ mkdirp@^1.0.4: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== +mock-socket@^9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/mock-socket/-/mock-socket-9.1.0.tgz#583f5984aa5759909c1b0f43676c669060722596" + integrity sha512-zNsH8h0D7buVMDZ2X1GyFYso9A1X1Co/TDfFs0AIKhSLkJeh381HYESNl/mL6BzmQpNOxZVnNhEDS1OWBrS+cQ== + mri@1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/mri/-/mri-1.1.4.tgz#7cb1dd1b9b40905f1fac053abe25b6720f44744a"