Skip to content

Commit

Permalink
test(express/client): constructor arg validation
Browse files Browse the repository at this point in the history
  • Loading branch information
h-sifat committed Nov 6, 2022
1 parent 8a0de2a commit dc12336
Show file tree
Hide file tree
Showing 2 changed files with 191 additions and 135 deletions.
6 changes: 6 additions & 0 deletions src/express/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
splitDataIntoChunks,
} from "../util";
import { defaults } from "./defaults";
import { assert } from "handy-types";

type OptionalArgs = Partial<
Pick<GeneralRequestPayload, "query" | "headers" | "body">
Expand Down Expand Up @@ -104,6 +105,11 @@ export class ExpressIPCClient
validateDelimiter(this.#delimiter);

this.#socketRoot = arg.socketRoot || tmpdir();
assert<string>("non_empty_string", this.#socketRoot, {
name: "socketRoot",
code: "INVALID_SOCKET_ROOT",
});

this.#path = makeSocketPath({
path: arg.path as any,
socketRoot: this.#socketRoot,
Expand Down
320 changes: 185 additions & 135 deletions tests/express/client.test.ts
Original file line number Diff line number Diff line change
@@ -1,157 +1,207 @@
import { tmpdir } from "os";
import { createServer, Server, Socket } from "net";
import { randomUUID } from "crypto";
import { makeSocketPath } from "../../src/util";
import { defaults } from "../../src/express/defaults";
import {
ExpressIPCClient,
MANUAL_SOCKET_CLOSE_ERROR,
SOCKET_ENDED_ERROR,
MANUAL_SOCKET_CLOSE_ERROR,
ExpressIPCClientConstructor_Argument,
} from "../../src/express/client";
import { GeneralRequestResponse } from "../../src/interface";

const socketPath = makeSocketPath({
socketRoot: tmpdir(),
path: { namespace: "testing_express_ipc_client", id: randomUUID() },
});

let server: Server;
let clientSocket: Socket;
let expressClient: ExpressIPCClient;
import { tmpdir } from "os";
import { randomUUID } from "crypto";
import { makeSocketPath } from "../../src/util";
import { createServer, Server, Socket } from "net";
import { defaults } from "../../src/express/defaults";
import { GeneralRequestResponse } from "../../src/interface";
import path from "path";

beforeAll(async () => {
await (() =>
new Promise((resolve) => {
server = createServer((_socket) => {
clientSocket = _socket;
resolve();
});
describe("Error Handline", () => {
const socketPath = makeSocketPath({
socketRoot: tmpdir(),
path: { namespace: "testing_express_ipc_client", id: randomUUID() },
});

server.listen(socketPath, () => {
expressClient = new ExpressIPCClient({ path: socketPath });
});
}) as Promise<void>)();
});
let server: Server;
let clientSocket: Socket;
let expressClient: ExpressIPCClient;

beforeAll(async () => {
await (() =>
new Promise((resolve) => {
server = createServer((_socket) => {
clientSocket = _socket;
resolve();
});

server.listen(socketPath, () => {
expressClient = new ExpressIPCClient({ path: socketPath });
});
}) as Promise<void>)();
});

beforeEach(() => {
clientSocket.removeAllListeners("end");
clientSocket.removeAllListeners("data");
clientSocket.removeAllListeners("close");
clientSocket.removeAllListeners("error");
beforeEach(() => {
clientSocket.removeAllListeners("end");
clientSocket.removeAllListeners("data");
clientSocket.removeAllListeners("close");
clientSocket.removeAllListeners("error");

expressClient.removeAllListeners("error");
});
expressClient.removeAllListeners("error");
});

afterAll(() => {
server.close();
expressClient.close();
});
afterAll(() => {
server.close();
expressClient.close();
});

describe("Handling Invalid Responses", () => {
{
const errorCode = "INVALID_RESPONSE:NOT_JSON";
it(`emits an error event with an ewc "${errorCode}" if the server responds with invalid json`, (done) => {
expressClient.on("error", (error) => {
try {
expect(error).toMatchObject({
code: errorCode,
message: expect.any(String),
});

done();
} catch (ex) {
done(ex);
}
describe("Invalid Response", () => {
{
const errorCode = "INVALID_RESPONSE:NOT_JSON";
it(`emits an error event with an ewc "${errorCode}" if the server responds with invalid json`, (done) => {
expressClient.on("error", (error) => {
try {
expect(error).toMatchObject({
code: errorCode,
message: expect.any(String),
});

done();
} catch (ex) {
done(ex);
}
});

clientSocket.write(`invalid_json${defaults.delimiter}`);
});

clientSocket.write(`invalid_json${defaults.delimiter}`);
});
}

{
const errorCode = "INVALID_RESPONSE:UNKNOWN_ID";

it(`emits an error event with an ewc "${errorCode}" if the server responds with unknown request id`, (done) => {
const response: GeneralRequestResponse = Object.freeze({
metadata: { id: "1424524", category: "general", isError: false },
payload: { headers: {}, body: {} },
}

{
const errorCode = "INVALID_RESPONSE:UNKNOWN_ID";

it(`emits an error event with an ewc "${errorCode}" if the server responds with unknown request id`, (done) => {
const response: GeneralRequestResponse = Object.freeze({
metadata: { id: "1424524", category: "general", isError: false },
payload: { headers: {}, body: {} },
});

expressClient.on("error", (error) => {
try {
expect(error).toMatchObject({
code: errorCode,
message: expect.any(String),
});

done();
} catch (ex) {
done(ex);
}
});

clientSocket.write(`${JSON.stringify(response)}${defaults.delimiter}`);
});
}
});

expressClient.on("error", (error) => {
try {
expect(error).toMatchObject({
code: errorCode,
message: expect.any(String),
});

done();
} catch (ex) {
done(ex);
}
});
describe("Socket Error", () => {
it(`rejects all enqueued requests if an error occurs`, (done) => {
expressClient
.get("/users")
.then(() => {
// the request should not resolve as we'll close the client
try {
expect(1).not.toBe(1);
done();
} catch (ex) {
done(ex);
}
})
.catch((error) => {
try {
expect(error).toMatchObject({
code: MANUAL_SOCKET_CLOSE_ERROR.code,
message: MANUAL_SOCKET_CLOSE_ERROR.message,
});
done();
} catch (ex) {
done(ex);
}
});

// manually destroying the underlying client socket
expressClient.close();
});

clientSocket.write(`${JSON.stringify(response)}${defaults.delimiter}`);
it(`rejects all enqueued requests if the socket connection is ended by the server`, (done) => {
jest.setTimeout(10_000);

expressClient
.get("/users")
.then(() => {
// the request should not resolve as we'll end the socket
try {
expect(1).not.toBe(1);
done();
} catch (ex) {
done(ex);
}
})
.catch((error) => {
try {
expect(error).toMatchObject({
code: SOCKET_ENDED_ERROR.code,
message: SOCKET_ENDED_ERROR.message,
});

done();
} catch (ex) {
done(ex);
}
});

// manually ending the client socket from the server side
clientSocket.end();
});
}
});
});

describe("Error Handling", () => {
it(`rejects all enqueued requests if an error occurs`, (done) => {
expressClient
.get("/users")
.then(() => {
// the request should not resolve as we'll close the client
try {
expect(1).not.toBe(1);
done();
} catch (ex) {
done(ex);
}
})
.catch((error) => {
try {
expect(error).toMatchObject({
code: MANUAL_SOCKET_CLOSE_ERROR.code,
message: MANUAL_SOCKET_CLOSE_ERROR.message,
});
done();
} catch (ex) {
done(ex);
}
});

// manually destroying the underlying client socket
expressClient.close();
describe("Constructor Arg validation", () => {
const validArg: ExpressIPCClientConstructor_Argument = Object.freeze({
delimiter: "\f",
path: "/tmp/test.sock",
socketRoot: path.join(tmpdir(), "socket"),
});

it(`rejects all enqueued requests if the socket connection is ended by the server`, (done) => {
jest.setTimeout(10_000);

expressClient
.get("/users")
.then(() => {
// the request should not resolve as we'll end the socket
try {
expect(1).not.toBe(1);
done();
} catch (ex) {
done(ex);
}
})
.catch((error) => {
try {
expect(error).toMatchObject({
code: SOCKET_ENDED_ERROR.code,
message: SOCKET_ENDED_ERROR.message,
});

done();
} catch (ex) {
done(ex);
}
});

// manually ending the client socket from the server side
clientSocket.end();
it.each([
{
arg: { ...validArg, delimiter: ["not_a_string"] },
case: "delimiter is not a non_empty_string",
errorCode: "INVALID_DELIMITER",
},
{
arg: { ...validArg, delimiter: "not_a_char" },
case: "delimiter is not a character",
errorCode: "INVALID_DELIMITER:NOT_CHAR",
},
{
arg: { ...validArg, socketRoot: 23241 },
case: "socketRoot is not of type non_empty_string",
errorCode: "INVALID_SOCKET_ROOT",
},
{
arg: { ...validArg, path: "" },
case: "path is not of type non_empty_string or plain_object",
errorCode: "INVALID_PATH",
},
{
arg: { ...validArg, path: null },
case: "path is not of type non_empty_string or plain_object",
errorCode: "INVALID_PATH",
},
])(`throws ewc "$errorCode" if $case`, ({ arg, errorCode }) => {
expect.assertions(1);

try {
// @ts-ignore
new ExpressIPCClient(arg);
} catch (ex) {
expect(ex.code).toBe(errorCode);
}
});
});

0 comments on commit dc12336

Please sign in to comment.