From eb551d1c4ab13d7ae86a7086e5d413c73296472d Mon Sep 17 00:00:00 2001 From: Bill Church Date: Thu, 22 Aug 2024 01:34:18 +0000 Subject: [PATCH] chore: create additional tests --- tests/errors.test.js | 88 ++++++++++++++++++++++++ tests/logger.test.js | 48 +++++++++++++ tests/ssh.test.js | 160 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 296 insertions(+) create mode 100644 tests/errors.test.js create mode 100644 tests/logger.test.js create mode 100644 tests/ssh.test.js diff --git a/tests/errors.test.js b/tests/errors.test.js new file mode 100644 index 0000000..0d641ef --- /dev/null +++ b/tests/errors.test.js @@ -0,0 +1,88 @@ +const { + WebSSH2Error, + ConfigError, + SSHConnectionError, + handleError +} = require("../app/errors") +const { logError } = require("../app/logger") +const { HTTP, MESSAGES } = require("../app/constants") + +jest.mock("../app/logger", () => ({ + logError: jest.fn(), + createNamespacedDebug: jest.fn(() => jest.fn()) +})) + +describe("errors", () => { + afterEach(() => { + jest.clearAllMocks() + }) + + describe("WebSSH2Error", () => { + it("should create a WebSSH2Error with correct properties", () => { + const error = new WebSSH2Error("Test error", "TEST_CODE") + expect(error).toBeInstanceOf(Error) + expect(error.name).toBe("WebSSH2Error") + expect(error.message).toBe("Test error") + expect(error.code).toBe("TEST_CODE") + }) + }) + + describe("ConfigError", () => { + it("should create a ConfigError with correct properties", () => { + const error = new ConfigError("Config error") + expect(error).toBeInstanceOf(WebSSH2Error) + expect(error.name).toBe("ConfigError") + expect(error.message).toBe("Config error") + expect(error.code).toBe(MESSAGES.CONFIG_ERROR) + }) + }) + + describe("SSHConnectionError", () => { + it("should create a SSHConnectionError with correct properties", () => { + const error = new SSHConnectionError("SSH connection error") + expect(error).toBeInstanceOf(WebSSH2Error) + expect(error.name).toBe("SSHConnectionError") + expect(error.message).toBe("SSH connection error") + expect(error.code).toBe(MESSAGES.SSH_CONNECTION_ERROR) + }) + }) + + describe("handleError", () => { + const mockRes = { + status: jest.fn(() => mockRes), + json: jest.fn() + } + + it("should handle WebSSH2Error correctly", () => { + const error = new WebSSH2Error("Test error", "TEST_CODE") + handleError(error, mockRes) + + expect(logError).toHaveBeenCalledWith("Test error", error) + expect(mockRes.status).toHaveBeenCalledWith(HTTP.INTERNAL_SERVER_ERROR) + expect(mockRes.json).toHaveBeenCalledWith({ + error: "Test error", + code: "TEST_CODE" + }) + }) + + it("should handle generic Error correctly", () => { + const error = new Error("Generic error") + handleError(error, mockRes) + + expect(logError).toHaveBeenCalledWith(MESSAGES.UNEXPECTED_ERROR, error) + expect(mockRes.status).toHaveBeenCalledWith(HTTP.INTERNAL_SERVER_ERROR) + expect(mockRes.json).toHaveBeenCalledWith({ + error: MESSAGES.UNEXPECTED_ERROR + }) + }) + + it("should not send response if res is not provided", () => { + const error = new Error("No response error") + handleError(error) + + expect(logError).toHaveBeenCalledWith(MESSAGES.UNEXPECTED_ERROR, error) + expect(mockRes.status).not.toHaveBeenCalled() + expect(mockRes.json).not.toHaveBeenCalled() + }) + }) +}) diff --git a/tests/logger.test.js b/tests/logger.test.js new file mode 100644 index 0000000..392d563 --- /dev/null +++ b/tests/logger.test.js @@ -0,0 +1,48 @@ +// server +// tests/logger.test.js + +const createDebug = require("debug") +const { createNamespacedDebug, logError } = require("../app/logger") + +jest.mock("debug") + +describe("logger", () => { + beforeEach(() => { + jest.clearAllMocks() + console.error = jest.fn() + }) + + describe("createNamespacedDebug", () => { + it("should create a debug function with the correct namespace", () => { + const mockDebug = jest.fn() + createDebug.mockReturnValue(mockDebug) + + const result = createNamespacedDebug("test") + + expect(createDebug).toHaveBeenCalledWith("webssh2:test") + expect(result).toBe(mockDebug) + }) + }) + + describe("logError", () => { + it("should log an error message without an error object", () => { + const message = "Test error message" + + logError(message) + + expect(console.error).toHaveBeenCalledWith(message) + expect(console.error).toHaveBeenCalledTimes(1) + }) + + it("should log an error message with an error object", () => { + const message = "Test error message" + const error = new Error("Test error") + + logError(message, error) + + expect(console.error).toHaveBeenCalledWith(message) + expect(console.error).toHaveBeenCalledWith("ERROR: Error: Test error") + expect(console.error).toHaveBeenCalledTimes(2) + }) + }) +}) diff --git a/tests/ssh.test.js b/tests/ssh.test.js new file mode 100644 index 0000000..f320c36 --- /dev/null +++ b/tests/ssh.test.js @@ -0,0 +1,160 @@ +// server +// tests/ssh.test.js + +const SSH2 = require("ssh2") +const SSHConnection = require("../app/ssh") +const { SSHConnectionError } = require("../app/errors") +const { maskSensitiveData } = require("../app/utils") + +jest.mock("ssh2") +jest.mock("../app/logger", () => ({ + createNamespacedDebug: jest.fn(() => jest.fn()), + logError: jest.fn() +})) +jest.mock("../app/utils", () => ({ + maskSensitiveData: jest.fn(data => data) +})) +jest.mock("../app/errors", () => ({ + SSHConnectionError: jest.fn(function(message) { + this.message = message + }), + handleError: jest.fn() +})) + +describe("SSHConnection", () => { + let sshConnection + let mockConfig + let mockSSH2Client + + beforeEach(() => { + mockConfig = { + ssh: { + algorithms: { + kex: ["algo1", "algo2"], + cipher: ["cipher1", "cipher2"], + serverHostKey: ["ssh-rsa", "ssh-dss"], + hmac: ["hmac1", "hmac2"], + compress: ["none", "zlib"] + }, + readyTimeout: 20000, + keepaliveInterval: 60000, + keepaliveCountMax: 10 + } + } + sshConnection = new SSHConnection(mockConfig) + mockSSH2Client = { + on: jest.fn(), + connect: jest.fn(), + shell: jest.fn(), + end: jest.fn() + } + SSH2.Client.mockImplementation(() => mockSSH2Client) + }) + + afterEach(() => { + jest.clearAllMocks() + }) + + describe("connect", () => { + // ... previous tests ... + + it("should handle connection errors", () => { + const mockCreds = { + host: "example.com", + port: 22, + username: "user", + password: "pass" + } + + mockSSH2Client.on.mockImplementation((event, callback) => { + if (event === "error") { + callback(new Error("Connection failed")) + } + }) + + return sshConnection.connect(mockCreds).catch(error => { + expect(error).toBeInstanceOf(SSHConnectionError) + expect(error.message).toBe("SSH Connection error: Connection failed") + }) + }) + }) + + describe("shell", () => { + beforeEach(() => { + sshConnection.conn = mockSSH2Client + }) + + it("should open a shell successfully", () => { + const mockStream = { + on: jest.fn(), + stderr: { on: jest.fn() } + } + + mockSSH2Client.shell.mockImplementation((options, callback) => { + callback(null, mockStream) + }) + + return sshConnection.shell().then(result => { + expect(result).toBe(mockStream) + expect(sshConnection.stream).toBe(mockStream) + }) + }) + + it("should handle shell errors", () => { + mockSSH2Client.shell.mockImplementation((options, callback) => { + callback(new Error("Shell error")) + }) + + return sshConnection.shell().catch(error => { + expect(error.message).toBe("Shell error") + }) + }) + }) + + describe("resizeTerminal", () => { + it("should resize the terminal if stream exists", () => { + const mockStream = { + setWindow: jest.fn() + } + sshConnection.stream = mockStream + + sshConnection.resizeTerminal(80, 24) + + expect(mockStream.setWindow).toHaveBeenCalledWith(80, 24) + }) + + it("should not resize if stream does not exist", () => { + sshConnection.stream = null + + sshConnection.resizeTerminal(80, 24) + + // No error should be thrown + }) + }) + + describe("end", () => { + it("should end the stream and connection", () => { + const mockStream = { + end: jest.fn() + } + sshConnection.stream = mockStream + sshConnection.conn = mockSSH2Client + + sshConnection.end() + + expect(mockStream.end).toHaveBeenCalled() + expect(mockSSH2Client.end).toHaveBeenCalled() + expect(sshConnection.stream).toBeNull() + expect(sshConnection.conn).toBeNull() + }) + + it("should handle ending when stream and connection do not exist", () => { + sshConnection.stream = null + sshConnection.conn = null + + sshConnection.end() + + // No error should be thrown + }) + }) +})