-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
test(express/client): constructor arg validation
- Loading branch information
Showing
2 changed files
with
191 additions
and
135 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
}); | ||
}); |