chore: create initial tests
This commit is contained in:
parent
0899cb0efa
commit
1a5ebc649d
8 changed files with 331 additions and 131 deletions
|
@ -2,9 +2,14 @@ extends:
|
||||||
- airbnb-base
|
- airbnb-base
|
||||||
- prettier
|
- prettier
|
||||||
- plugin:node/recommended
|
- plugin:node/recommended
|
||||||
|
- plugin:jest/recommended
|
||||||
|
|
||||||
plugins:
|
plugins:
|
||||||
- prettier
|
- prettier
|
||||||
|
- jest
|
||||||
|
|
||||||
|
env:
|
||||||
|
jest/globals: true
|
||||||
|
|
||||||
rules:
|
rules:
|
||||||
prettier/prettier: error
|
prettier/prettier: error
|
||||||
|
@ -14,4 +19,10 @@ rules:
|
||||||
no-process-exit: off
|
no-process-exit: off
|
||||||
object-shorthand: off
|
object-shorthand: off
|
||||||
class-methods-use-this: off
|
class-methods-use-this: off
|
||||||
semi: [2, never]
|
semi: [2, never]
|
||||||
|
|
||||||
|
overrides:
|
||||||
|
- files:
|
||||||
|
- "**/*.test.js"
|
||||||
|
env:
|
||||||
|
jest: true
|
122
app/config.js
122
app/config.js
|
@ -1,8 +1,12 @@
|
||||||
|
// server
|
||||||
|
// app/config.js
|
||||||
|
|
||||||
const path = require("path")
|
const path = require("path")
|
||||||
const fs = require("fs")
|
const fs = require("fs")
|
||||||
const readConfig = require("read-config-ng")
|
const readConfig = require("read-config-ng")
|
||||||
const Ajv = require("ajv")
|
const Ajv = require("ajv")
|
||||||
const { deepMerge, generateSecureSecret } = require("./utils")
|
const { deepMerge, validateConfig } = require("./utils")
|
||||||
|
const { generateSecureSecret } = require("./crypto-utils")
|
||||||
const { createNamespacedDebug } = require("./logger")
|
const { createNamespacedDebug } = require("./logger")
|
||||||
const { ConfigError, handleError } = require("./errors")
|
const { ConfigError, handleError } = require("./errors")
|
||||||
const { DEFAULTS } = require("./constants")
|
const { DEFAULTS } = require("./constants")
|
||||||
|
@ -67,127 +71,11 @@ const defaultConfig = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Schema for validating the config
|
|
||||||
*/
|
|
||||||
const configSchema = {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
listen: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
ip: { type: "string", format: "ipv4" },
|
|
||||||
port: { type: "integer", minimum: 1, maximum: 65535 }
|
|
||||||
},
|
|
||||||
required: ["ip", "port"]
|
|
||||||
},
|
|
||||||
http: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
origins: {
|
|
||||||
type: "array",
|
|
||||||
items: { type: "string" }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
required: ["origins"]
|
|
||||||
},
|
|
||||||
user: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
name: { type: ["string", "null"] },
|
|
||||||
password: { type: ["string", "null"] }
|
|
||||||
},
|
|
||||||
required: ["name", "password"]
|
|
||||||
},
|
|
||||||
ssh: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
host: { type: ["string", "null"] },
|
|
||||||
port: { type: "integer", minimum: 1, maximum: 65535 },
|
|
||||||
term: { type: "string" },
|
|
||||||
readyTimeout: { type: "integer" },
|
|
||||||
keepaliveInterval: { type: "integer" },
|
|
||||||
keepaliveCountMax: { type: "integer" }
|
|
||||||
},
|
|
||||||
required: [
|
|
||||||
"host",
|
|
||||||
"port",
|
|
||||||
"term",
|
|
||||||
"readyTimeout",
|
|
||||||
"keepaliveInterval",
|
|
||||||
"keepaliveCountMax"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
header: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
text: { type: ["string", "null"] },
|
|
||||||
background: { type: "string" }
|
|
||||||
},
|
|
||||||
required: ["text", "background"]
|
|
||||||
},
|
|
||||||
options: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
challengeButton: { type: "boolean" },
|
|
||||||
autoLog: { type: "boolean" },
|
|
||||||
allowReauth: { type: "boolean" },
|
|
||||||
allowReconnect: { type: "boolean" },
|
|
||||||
allowReplay: { type: "boolean" }
|
|
||||||
},
|
|
||||||
required: ["challengeButton", "allowReauth", "allowReplay"]
|
|
||||||
},
|
|
||||||
algorithms: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
kex: {
|
|
||||||
type: "array",
|
|
||||||
items: { type: "string" }
|
|
||||||
},
|
|
||||||
cipher: {
|
|
||||||
type: "array",
|
|
||||||
items: { type: "string" }
|
|
||||||
},
|
|
||||||
hmac: {
|
|
||||||
type: "array",
|
|
||||||
items: { type: "string" }
|
|
||||||
},
|
|
||||||
compress: {
|
|
||||||
type: "array",
|
|
||||||
items: { type: "string" }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
required: ["kex", "cipher", "hmac", "compress"]
|
|
||||||
},
|
|
||||||
session: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
secret: { type: "string" },
|
|
||||||
name: { type: "string" }
|
|
||||||
},
|
|
||||||
required: ["secret", "name"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
required: ["listen", "http", "user", "ssh", "header", "options", "algorithms"]
|
|
||||||
}
|
|
||||||
|
|
||||||
function getConfigPath() {
|
function getConfigPath() {
|
||||||
const nodeRoot = path.dirname(require.main.filename)
|
const nodeRoot = path.dirname(require.main.filename)
|
||||||
return path.join(nodeRoot, "config.json")
|
return path.join(nodeRoot, "config.json")
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateConfig(config) {
|
|
||||||
const ajv = new Ajv()
|
|
||||||
const validate = ajv.compile(configSchema)
|
|
||||||
const valid = validate(config)
|
|
||||||
if (!valid) {
|
|
||||||
throw new Error(
|
|
||||||
`Config validation error: ${ajv.errorsText(validate.errors)}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return config
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadConfig() {
|
function loadConfig() {
|
||||||
const configPath = getConfigPath()
|
const configPath = getConfigPath()
|
||||||
|
|
||||||
|
|
104
app/configSchema.js
Normal file
104
app/configSchema.js
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
/**
|
||||||
|
* Schema for validating the config
|
||||||
|
*/
|
||||||
|
const configSchema = {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
listen: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
ip: { type: "string", format: "ipv4" },
|
||||||
|
port: { type: "integer", minimum: 1, maximum: 65535 }
|
||||||
|
},
|
||||||
|
required: ["ip", "port"]
|
||||||
|
},
|
||||||
|
http: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
origins: {
|
||||||
|
type: "array",
|
||||||
|
items: { type: "string" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: ["origins"]
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
name: { type: ["string", "null"] },
|
||||||
|
password: { type: ["string", "null"] }
|
||||||
|
},
|
||||||
|
required: ["name", "password"]
|
||||||
|
},
|
||||||
|
ssh: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
host: { type: ["string", "null"] },
|
||||||
|
port: { type: "integer", minimum: 1, maximum: 65535 },
|
||||||
|
term: { type: "string" },
|
||||||
|
readyTimeout: { type: "integer" },
|
||||||
|
keepaliveInterval: { type: "integer" },
|
||||||
|
keepaliveCountMax: { type: "integer" }
|
||||||
|
},
|
||||||
|
required: [
|
||||||
|
"host",
|
||||||
|
"port",
|
||||||
|
"term",
|
||||||
|
"readyTimeout",
|
||||||
|
"keepaliveInterval",
|
||||||
|
"keepaliveCountMax"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
header: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
text: { type: ["string", "null"] },
|
||||||
|
background: { type: "string" }
|
||||||
|
},
|
||||||
|
required: ["text", "background"]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
challengeButton: { type: "boolean" },
|
||||||
|
autoLog: { type: "boolean" },
|
||||||
|
allowReauth: { type: "boolean" },
|
||||||
|
allowReconnect: { type: "boolean" },
|
||||||
|
allowReplay: { type: "boolean" }
|
||||||
|
},
|
||||||
|
required: ["challengeButton", "allowReauth", "allowReplay"]
|
||||||
|
},
|
||||||
|
algorithms: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
kex: {
|
||||||
|
type: "array",
|
||||||
|
items: { type: "string" }
|
||||||
|
},
|
||||||
|
cipher: {
|
||||||
|
type: "array",
|
||||||
|
items: { type: "string" }
|
||||||
|
},
|
||||||
|
hmac: {
|
||||||
|
type: "array",
|
||||||
|
items: { type: "string" }
|
||||||
|
},
|
||||||
|
compress: {
|
||||||
|
type: "array",
|
||||||
|
items: { type: "string" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: ["kex", "cipher", "hmac", "compress"]
|
||||||
|
},
|
||||||
|
session: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
secret: { type: "string" },
|
||||||
|
name: { type: "string" }
|
||||||
|
},
|
||||||
|
required: ["secret", "name"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: ["listen", "http", "user", "ssh", "header", "options", "algorithms"]
|
||||||
|
}
|
||||||
|
module.exports = configSchema
|
16
app/crypto-utils.js
Normal file
16
app/crypto-utils.js
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
// server
|
||||||
|
// app/crypto-utils.js
|
||||||
|
|
||||||
|
const crypto = require("crypto")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a secure random session secret
|
||||||
|
* @returns {string} A random 32-byte hex string
|
||||||
|
*/
|
||||||
|
function generateSecureSecret() {
|
||||||
|
return crypto.randomBytes(32).toString("hex")
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
generateSecureSecret
|
||||||
|
}
|
14
app/utils.js
14
app/utils.js
|
@ -1,12 +1,11 @@
|
||||||
// server
|
// server
|
||||||
// /app/utils.js
|
// /app/utils.js
|
||||||
const validator = require("validator")
|
const validator = require("validator")
|
||||||
const crypto = require("crypto")
|
|
||||||
const Ajv = require("ajv")
|
const Ajv = require("ajv")
|
||||||
const maskObject = require("jsmasker")
|
const maskObject = require("jsmasker")
|
||||||
const { createNamespacedDebug } = require("./logger")
|
const { createNamespacedDebug } = require("./logger")
|
||||||
const { DEFAULTS, MESSAGES } = require("./constants")
|
const { DEFAULTS, MESSAGES } = require("./constants")
|
||||||
const { configSchema } = require("./config")
|
const configSchema = require("./configSchema")
|
||||||
|
|
||||||
const debug = createNamespacedDebug("utils")
|
const debug = createNamespacedDebug("utils")
|
||||||
|
|
||||||
|
@ -34,14 +33,6 @@ function deepMerge(target, source) {
|
||||||
return output
|
return output
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates a secure random session secret
|
|
||||||
* @returns {string} A random 32-byte hex string
|
|
||||||
*/
|
|
||||||
function generateSecureSecret() {
|
|
||||||
return crypto.randomBytes(32).toString("hex")
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines if a given host is an IP address or a hostname.
|
* Determines if a given host is an IP address or a hostname.
|
||||||
* If it's a hostname, it escapes it for safety.
|
* If it's a hostname, it escapes it for safety.
|
||||||
|
@ -99,7 +90,7 @@ function getValidatedPort(portInput) {
|
||||||
* @returns {boolean} - Returns true if the credentials are valid, otherwise false.
|
* @returns {boolean} - Returns true if the credentials are valid, otherwise false.
|
||||||
*/
|
*/
|
||||||
function isValidCredentials(creds) {
|
function isValidCredentials(creds) {
|
||||||
return (
|
return !!(
|
||||||
creds &&
|
creds &&
|
||||||
typeof creds.username === "string" &&
|
typeof creds.username === "string" &&
|
||||||
typeof creds.password === "string" &&
|
typeof creds.password === "string" &&
|
||||||
|
@ -189,7 +180,6 @@ function maskSensitiveData(obj, options) {
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
deepMerge,
|
deepMerge,
|
||||||
generateSecureSecret,
|
|
||||||
getValidatedHost,
|
getValidatedHost,
|
||||||
getValidatedPort,
|
getValidatedPort,
|
||||||
isValidCredentials,
|
isValidCredentials,
|
||||||
|
|
|
@ -49,7 +49,12 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node index.js",
|
"start": "node index.js",
|
||||||
"lint": "eslint src test",
|
"lint": "eslint src test",
|
||||||
"watch": "node_modules/.bin/nodemon index.js"
|
"watch": "node_modules/.bin/nodemon index.js",
|
||||||
|
"test": "jest"
|
||||||
|
},
|
||||||
|
"jest": {
|
||||||
|
"testEnvironment": "node",
|
||||||
|
"testMatch": ["**/tests/**/*.test.js"]
|
||||||
},
|
},
|
||||||
"standard": {
|
"standard": {
|
||||||
"ignore": [
|
"ignore": [
|
||||||
|
@ -62,8 +67,10 @@
|
||||||
"eslint-config-airbnb-base": "^13.2.0",
|
"eslint-config-airbnb-base": "^13.2.0",
|
||||||
"eslint-config-prettier": "^4.3.0",
|
"eslint-config-prettier": "^4.3.0",
|
||||||
"eslint-plugin-import": "^2.18.2",
|
"eslint-plugin-import": "^2.18.2",
|
||||||
|
"eslint-plugin-jest": "^22.0.0",
|
||||||
"eslint-plugin-node": "^8.0.0",
|
"eslint-plugin-node": "^8.0.0",
|
||||||
"eslint-plugin-prettier": "^2.7.0",
|
"eslint-plugin-prettier": "^2.7.0",
|
||||||
|
"jest": "^23.6.0",
|
||||||
"nodemon": "^1.12.1",
|
"nodemon": "^1.12.1",
|
||||||
"prettier": "^1.19.1",
|
"prettier": "^1.19.1",
|
||||||
"prettier-eslint": "^8.8.2",
|
"prettier-eslint": "^8.8.2",
|
||||||
|
|
11
tests/crypto-utils.test.js
Normal file
11
tests/crypto-utils.test.js
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
// server
|
||||||
|
// tests/crypto-utils.test.js
|
||||||
|
|
||||||
|
const { generateSecureSecret } = require("../app/crypto-utils")
|
||||||
|
|
||||||
|
describe("generateSecureSecret", () => {
|
||||||
|
it("should generate a 64-character hex string", () => {
|
||||||
|
const secret = generateSecureSecret()
|
||||||
|
expect(secret).toMatch(/^[0-9a-f]{64}$/)
|
||||||
|
})
|
||||||
|
})
|
173
tests/utils.test.js
Normal file
173
tests/utils.test.js
Normal file
|
@ -0,0 +1,173 @@
|
||||||
|
// server
|
||||||
|
// tests/utils.test.js
|
||||||
|
|
||||||
|
const {
|
||||||
|
deepMerge,
|
||||||
|
getValidatedHost,
|
||||||
|
getValidatedPort,
|
||||||
|
isValidCredentials,
|
||||||
|
maskSensitiveData,
|
||||||
|
modifyHtml,
|
||||||
|
validateConfig,
|
||||||
|
validateSshTerm
|
||||||
|
} = require("../app/utils")
|
||||||
|
|
||||||
|
describe("utils", () => {
|
||||||
|
describe("deepMerge", () => {
|
||||||
|
it("should merge two objects deeply", () => {
|
||||||
|
const obj1 = { a: { b: 1 }, c: 2 }
|
||||||
|
const obj2 = { a: { d: 3 }, e: 4 }
|
||||||
|
const result = deepMerge(obj1, obj2)
|
||||||
|
expect(result).toEqual({ a: { b: 1, d: 3 }, c: 2, e: 4 })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("getValidatedHost", () => {
|
||||||
|
it("should return IP address unchanged", () => {
|
||||||
|
expect(getValidatedHost("192.168.1.1")).toBe("192.168.1.1")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should escape hostname", () => {
|
||||||
|
expect(getValidatedHost("example.com")).toBe("example.com")
|
||||||
|
expect(getValidatedHost("<script>alert('xss')</script>")).toBe(
|
||||||
|
"<script>alert('xss')</script>"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("getValidatedPort", () => {
|
||||||
|
it("should return valid port number", () => {
|
||||||
|
expect(getValidatedPort("22")).toBe(22)
|
||||||
|
expect(getValidatedPort("8080")).toBe(8080)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should return default port for invalid input", () => {
|
||||||
|
expect(getValidatedPort("invalid")).toBe(22)
|
||||||
|
expect(getValidatedPort("0")).toBe(22)
|
||||||
|
expect(getValidatedPort("65536")).toBe(22)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("isValidCredentials", () => {
|
||||||
|
it("should return true for valid credentials", () => {
|
||||||
|
const validCreds = {
|
||||||
|
username: "user",
|
||||||
|
password: "pass",
|
||||||
|
host: "example.com",
|
||||||
|
port: 22
|
||||||
|
}
|
||||||
|
expect(isValidCredentials(validCreds)).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should return false for invalid credentials", () => {
|
||||||
|
expect(isValidCredentials(null)).toBe(false)
|
||||||
|
expect(isValidCredentials({})).toBe(false)
|
||||||
|
expect(isValidCredentials({ username: "user" })).toBe(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("maskSensitiveData", () => {
|
||||||
|
it("should mask sensitive data", () => {
|
||||||
|
const data = {
|
||||||
|
username: "user",
|
||||||
|
password: "secret",
|
||||||
|
token: "12345"
|
||||||
|
}
|
||||||
|
const masked = maskSensitiveData(data)
|
||||||
|
expect(masked.username).toBe("user")
|
||||||
|
expect(masked.password).not.toBe("secret")
|
||||||
|
expect(masked.token).not.toBe("12345")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("modifyHtml", () => {
|
||||||
|
it("should modify HTML content", () => {
|
||||||
|
const html = "window.webssh2Config = null;"
|
||||||
|
const config = { key: "value" }
|
||||||
|
const content = `window.webssh2Config = ${JSON.stringify(config)};`
|
||||||
|
const modified = modifyHtml(html, config)
|
||||||
|
expect(modified).toContain('window.webssh2Config = {"key":"value"};')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("validateConfig", () => {
|
||||||
|
it("should validate correct config", () => {
|
||||||
|
const validConfig = {
|
||||||
|
listen: {
|
||||||
|
ip: "0.0.0.0",
|
||||||
|
port: 2222
|
||||||
|
},
|
||||||
|
http: {
|
||||||
|
origins: ["http://localhost:8080"]
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
name: null,
|
||||||
|
password: null,
|
||||||
|
privatekey: null
|
||||||
|
},
|
||||||
|
ssh: {
|
||||||
|
host: null,
|
||||||
|
port: 22,
|
||||||
|
localAddress: null,
|
||||||
|
localPort: null,
|
||||||
|
term: "xterm-color",
|
||||||
|
readyTimeout: 20000,
|
||||||
|
keepaliveInterval: 120000,
|
||||||
|
keepaliveCountMax: 10,
|
||||||
|
allowedSubnets: []
|
||||||
|
},
|
||||||
|
header: {
|
||||||
|
text: null,
|
||||||
|
background: "green"
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
challengeButton: true,
|
||||||
|
autoLog: false,
|
||||||
|
allowReauth: true,
|
||||||
|
allowReconnect: true,
|
||||||
|
allowReplay: true
|
||||||
|
},
|
||||||
|
algorithms: {
|
||||||
|
kex: [
|
||||||
|
"ecdh-sha2-nistp256",
|
||||||
|
"ecdh-sha2-nistp384",
|
||||||
|
"ecdh-sha2-nistp521",
|
||||||
|
"diffie-hellman-group-exchange-sha256",
|
||||||
|
"diffie-hellman-group14-sha1"
|
||||||
|
],
|
||||||
|
cipher: [
|
||||||
|
"aes128-ctr",
|
||||||
|
"aes192-ctr",
|
||||||
|
"aes256-ctr",
|
||||||
|
"aes128-gcm",
|
||||||
|
"aes128-gcm@openssh.com",
|
||||||
|
"aes256-gcm",
|
||||||
|
"aes256-gcm@openssh.com",
|
||||||
|
"aes256-cbc"
|
||||||
|
],
|
||||||
|
hmac: ["hmac-sha2-256", "hmac-sha2-512", "hmac-sha1"],
|
||||||
|
compress: ["none", "zlib@openssh.com", "zlib"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(() => validateConfig(validConfig)).not.toThrow()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should throw error for invalid config", () => {
|
||||||
|
const invalidConfig = {}
|
||||||
|
expect(() => validateConfig(invalidConfig)).toThrow()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("validateSshTerm", () => {
|
||||||
|
it("should return valid SSH term", () => {
|
||||||
|
expect(validateSshTerm("xterm")).toBe("xterm")
|
||||||
|
expect(validateSshTerm("xterm-256color")).toBe("xterm-256color")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should return null for invalid SSH term", () => {
|
||||||
|
expect(validateSshTerm("")).toBe(null)
|
||||||
|
expect(validateSshTerm("<script>alert('xss')</script>")).toBe(null)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
Loading…
Reference in a new issue