Files
xahau.js/test/mockRippled.ts
Mayukha Vadari b8be6c2f1b refactor: improves mock rippled structure (#1569)
* better error handling + tests

* fix tests

* change addResponse to take a string instead of a Request

* remove unneeded change

* respond to comments

* fix tests, re-lint

* improve error message
2021-10-04 14:10:11 -04:00

160 lines
4.5 KiB
TypeScript

import { EventEmitter2 } from "eventemitter2";
import _ from "lodash";
import { Server as WebSocketServer } from "ws";
import type { Request } from "../src";
import { getFreePort } from "./testUtils";
function createResponse(request: { id: number | string }, response: object) {
if (!("type" in response) && !("error" in response)) {
throw new Error(
`Bad response format. Must contain \`type\` or \`error\`. ${JSON.stringify(
response
)}`
);
}
return JSON.stringify({ ...response, id: request.id });
}
function ping(conn, request) {
setTimeout(() => {
conn.send(
createResponse(request, {
result: {},
status: "success",
type: "response",
})
);
}, 1000 * 2);
}
// We mock out WebSocketServer in these tests and add a lot of custom
// properties not defined on the normal WebSocketServer object.
type MockedWebSocketServer = any;
export function createMockRippled(port) {
const mock = new WebSocketServer({ port }) as MockedWebSocketServer;
Object.assign(mock, EventEmitter2.prototype);
mock.responses = {};
mock.suppressOutput = false;
mock.on("connection", function (this: MockedWebSocketServer, conn: any) {
this.socket = conn;
conn.on("message", function (requestJSON) {
let request;
try {
request = JSON.parse(requestJSON);
if (request.id == null) {
throw new Error("Request has no id");
}
if (request.command === "ping") {
ping(conn, request);
} else if (request.command === "test_command") {
mock.testCommand(conn, request);
} else if (request.command in mock.responses) {
conn.send(createResponse(request, mock.getResponse(request)));
} else {
throw new Error(
`No event handler registered in mock rippled for ${request.command}`
);
}
} catch (err) {
if (!mock.suppressOutput) {
console.error(`Error: ${err.message}`);
}
if (request != null) {
conn.send(
createResponse(request, {
type: "response",
status: "error",
error: err.message,
})
);
}
}
});
});
// Adds a mocked response
// If an object is passed in for `response`, then the response is static for the command
// If a function is passed in for `response`, then the response can be determined by the exact request shape
mock.addResponse = (
command: string,
response: object | ((r: Request) => object)
) => {
if (typeof command !== "string") {
throw new Error("command is not a string");
}
if (
typeof response === "object" &&
!("type" in response) &&
!("error" in response)
) {
throw new Error(
`Bad response format. Must contain \`type\` or \`error\`. ${JSON.stringify(
response
)}`
);
}
mock.responses[command] = response;
};
mock.getResponse = (request: Request): object => {
if (!(request.command in mock.responses)) {
throw new Error(`No handler for ${request.command}`);
}
const functionOrObject = mock.responses[request.command];
if (typeof functionOrObject === "function") {
return functionOrObject(request);
}
return functionOrObject;
};
mock.testCommand = function testCommand(conn, request) {
if (request.data.disconnectIn) {
setTimeout(conn.terminate.bind(conn), request.data.disconnectIn);
conn.send(
createResponse(request, {
status: "success",
type: "response",
result: {},
})
);
} else if (request.data.openOnOtherPort) {
getFreePort().then((newPort) => {
createMockRippled(newPort);
conn.send(
createResponse(request, {
status: "success",
type: "response",
result: { port: newPort },
})
);
});
} else if (request.data.closeServerAndReopen) {
setTimeout(() => {
conn.terminate();
mock.close.call(mock, () => {
setTimeout(() => {
createMockRippled(port);
}, request.data.closeServerAndReopen);
});
}, 10);
} else if (request.data.unrecognizedResponse) {
conn.send(
createResponse(request, {
status: "unrecognized",
type: "response",
result: {},
})
);
} else if (request.data.closeServer) {
conn.close();
}
};
return mock;
}