From 82a2437c0967871d6b942105c98c3382978cad29 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Thu, 6 Aug 2020 00:30:36 +0530 Subject: towards integration tests with fault injection --- .../taler-integrationtests/src/faultInjection.ts | 222 +++++++++++++++++++++ 1 file changed, 222 insertions(+) create mode 100644 packages/taler-integrationtests/src/faultInjection.ts (limited to 'packages/taler-integrationtests/src/faultInjection.ts') diff --git a/packages/taler-integrationtests/src/faultInjection.ts b/packages/taler-integrationtests/src/faultInjection.ts new file mode 100644 index 000000000..a9c249fd0 --- /dev/null +++ b/packages/taler-integrationtests/src/faultInjection.ts @@ -0,0 +1,222 @@ +/* + This file is part of GNU Taler + (C) 2020 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see + */ + +/** + * Fault injection proxy. + * + * @author Florian Dold + */ + +/** + * Imports + */ +import * as http from "http"; +import { URL } from "url"; +import { + GlobalTestState, + ExchangeService, + BankService, + ExchangeServiceInterface, +} from "./harness"; + +export interface FaultProxyConfig { + inboundPort: number; + targetPort: number; +} + +/** + * Fault injection context. Modified by fault injection functions. + */ +export interface FaultInjectionRequestContext { + requestUrl: string; + method: string; + requestHeaders: Record; + requestBody?: Buffer; + dropRequest: boolean; +} + +export interface FaultInjectionResponseContext { + request: FaultInjectionRequestContext; + statusCode: number; + responseHeaders: Record; + responseBody: Buffer | undefined; + dropResponse: boolean; +} + +export interface FaultSpec { + modifyRequest?: (ctx: FaultInjectionRequestContext) => void; + modifyResponse?: (ctx: FaultInjectionResponseContext) => void; +} + +export class FaultProxy { + constructor( + private globalTestState: GlobalTestState, + private faultProxyConfig: FaultProxyConfig, + ) {} + + private currentFaultSpecs: FaultSpec[] = []; + + start() { + const server = http.createServer((req, res) => { + const requestChunks: Buffer[] = []; + const requestUrl = `http://locahost:${this.faultProxyConfig.inboundPort}${req.url}`; + console.log("request for", new URL(requestUrl)); + req.on("data", (chunk) => { + requestChunks.push(chunk); + }); + req.on("end", () => { + console.log("end of data"); + let requestBuffer: Buffer | undefined; + if (requestChunks.length > 0) { + requestBuffer = Buffer.concat(requestChunks); + } + console.log("full request body", requestBuffer); + + const faultReqContext: FaultInjectionRequestContext = { + dropRequest: false, + method: req.method!!, + requestHeaders: req.headers, + requestUrl, + requestBody: requestBuffer, + }; + + for (const faultSpec of this.currentFaultSpecs) { + if (faultSpec.modifyRequest) { + faultSpec.modifyRequest(faultReqContext); + } + } + + if (faultReqContext.dropRequest) { + res.destroy(); + return; + } + + const faultedUrl = new URL(faultReqContext.requestUrl); + + const proxyRequest = http.request({ + method: faultReqContext.method, + host: "localhost", + port: this.faultProxyConfig.targetPort, + path: faultedUrl.pathname + faultedUrl.search, + headers: faultReqContext.requestHeaders, + }); + + console.log( + `proxying request to target path '${ + faultedUrl.pathname + faultedUrl.search + }'`, + ); + + if (faultReqContext.requestBody) { + proxyRequest.write(faultReqContext.requestBody); + } + proxyRequest.end(); + proxyRequest.on("response", (proxyResp) => { + console.log("gotten response from target", proxyResp.statusCode); + const respChunks: Buffer[] = []; + proxyResp.on("data", (proxyRespData) => { + respChunks.push(proxyRespData); + }); + proxyResp.on("end", () => { + console.log("end of target response"); + let responseBuffer: Buffer | undefined; + if (respChunks.length > 0) { + responseBuffer = Buffer.concat(respChunks); + } + const faultRespContext: FaultInjectionResponseContext = { + request: faultReqContext, + dropResponse: false, + responseBody: responseBuffer, + responseHeaders: proxyResp.headers, + statusCode: proxyResp.statusCode!!, + }; + for (const faultSpec of this.currentFaultSpecs) { + const modResponse = faultSpec.modifyResponse; + if (modResponse) { + modResponse(faultRespContext); + } + } + if (faultRespContext.dropResponse) { + req.destroy(); + return; + } + if (faultRespContext.responseBody) { + // We must accomodate for potentially changed content length + faultRespContext.responseHeaders[ + "content-length" + ] = `${faultRespContext.responseBody.byteLength}`; + } + console.log("writing response head"); + res.writeHead( + faultRespContext.statusCode, + http.STATUS_CODES[faultRespContext.statusCode], + faultRespContext.responseHeaders, + ); + if (faultRespContext.responseBody) { + res.write(faultRespContext.responseBody); + } + res.end(); + }); + }); + }); + }); + + server.listen(this.faultProxyConfig.inboundPort); + this.globalTestState.servers.push(server); + } + + addFault(f: FaultSpec) { + this.currentFaultSpecs.push(f); + } + + clearFault() { + this.currentFaultSpecs = []; + } +} + +export class FaultInjectedExchangeService implements ExchangeServiceInterface { + baseUrl: string; + port: number; + faultProxy: FaultProxy; + + get name(): string { + return this.innerExchange.name; + } + + get masterPub(): string { + return this.innerExchange.masterPub; + } + + private innerExchange: ExchangeService; + + constructor( + t: GlobalTestState, + e: ExchangeService, + proxyInboundPort: number, + ) { + this.innerExchange = e; + this.faultProxy = new FaultProxy(t, { + inboundPort: proxyInboundPort, + targetPort: e.port, + }); + this.faultProxy.start(); + + const exchangeUrl = new URL(e.baseUrl); + exchangeUrl.port = `${proxyInboundPort}`; + this.baseUrl = exchangeUrl.href; + this.port = proxyInboundPort; + } +} -- cgit v1.2.3