Skip to content

Commit

Permalink
feat(inputs): add origin and port validators
Browse files Browse the repository at this point in the history
  • Loading branch information
javier-sierra-sngular committed Oct 23, 2023
1 parent faa452d commit 3cdf5eb
Show file tree
Hide file tree
Showing 5 changed files with 161 additions and 8 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
"prepare": "npx husky install",
"types": "tsc --build --pretty",
"prepublishOnly": "npm run types",
"release": "npx semantic-release"
"release": "npx semantic-release",
"reset": "rm -rf .apimockrc && rm -rf .api-mock-runner"
},
"author": "",
"license": "MIT",
Expand Down
49 changes: 49 additions & 0 deletions src/services/inquirer-validators.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import fs from 'fs';
import { verifyRemoteOrigin } from './utils.js';

/**
* @typedef {import('./user-flow-steps.js').Schema} Schema
*/

export const errorMessages = Object.freeze({
origin: {
INVALID: 'Enter a valid remote origin (https:// or git@) or local path',
},
port: {
IN_USE: 'Port already in use',
INVALID: 'Enter a valid port number between 0 and 65535',
},
});

/**
* Validate if the input is a valid local path or remote origin
* @function originValidator
* @param {string} value - The value to validate
* @returns {boolean|string} True if the value is valid, otherwise a string with the error message
*/
export function originValidator(value) {
const isLocalPath = fs.existsSync(value);
const isRemoteOrigin = verifyRemoteOrigin(value);
const result = isLocalPath || isRemoteOrigin || errorMessages.origin.INVALID;
return result;
}

/**
* Validate if the input is a valid port number
* @function portValidator
* @param {string} input - The value to validate
* @param {Schema[]} selectedSchemas - The current schema
* @returns {boolean|string} True if the value is valid, otherwise a string with the error message
*/
export function portValidator(input, selectedSchemas) {
const numericInput = Number(input);
const isInteger = Number.isInteger(numericInput);
if (!isInteger || input < 0 || input > 65535) {
return errorMessages.port.INVALID;
}
const isPortAlreadySelected = selectedSchemas.some((schema) => schema.port === numericInput);
if (isPortAlreadySelected) {
return errorMessages.port.IN_USE;
}
return true;
}
20 changes: 13 additions & 7 deletions src/services/user-flow-steps.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { input, confirm, checkbox } from '@inquirer/prompts';
import { checkbox, confirm, input } from '@inquirer/prompts';
import * as fs from 'node:fs';
import OpenApiMocker from 'open-api-mocker';
import cloneGitRepository from '../services/clone-git-repository.js';
import findOasFromDir from '../services/find-oas-from-dir.js';
import { addToGitignore, verifyRemoteOrigin, TEMP_FOLDER_NAME, RC_FILE_NAME } from './utils.js';
import { originValidator, portValidator } from './inquirer-validators.js';
import { RC_FILE_NAME, TEMP_FOLDER_NAME, addToGitignore, verifyRemoteOrigin } from './utils.js';

/**
* @typedef {Object} Config
* @property {string} schemasOrigin - The origin of the schemas (local or remote)
Expand All @@ -15,7 +17,7 @@ import { addToGitignore, verifyRemoteOrigin, TEMP_FOLDER_NAME, RC_FILE_NAME } fr
* User flow when the config file already exists
* @async
* @function initWithConfigFile
* @returns {Promise<Config>} A object with the initial values from the user
* @returns {Promise<Config>} An object with the initial values from the user
*/
async function initWithConfigFile() {
const existingConfig = JSON.parse(fs.readFileSync(`${process.cwd()}/${RC_FILE_NAME}`));
Expand Down Expand Up @@ -85,24 +87,26 @@ async function startMockServer(selectedSchemas) {
console.log();
}
}

/**
* get initial values from user
* @async
* @function getOrigin
* @returns {Promise<string>} The origin of the schemas (local or remote)
*/
async function getOrigin() {
// TODO: Add input validation
const schemasOrigin = await input({
message: 'Enter the repo url or relative path',
message: 'Enter a remote origin (https:// or git@) or local path',
validate: originValidator,
});
return schemasOrigin;
}

/**
* Start flow without config
* @async
* @function init
* @returns {Promise<Config>} A object with the complete config
* @returns {Promise<Config>} An object with the complete config
*/
async function init() {
const schemasOrigin = await startNewFlow();
Expand All @@ -114,6 +118,7 @@ async function init() {
choices: schemas.map((schema) => {
return { name: schema.fileName, value: schema.filePath };
}),
// TODO: pending validation to ensure that at least one schema is selected. Waiting next inquirer release.
});

const selectedSchemas = await askForPorts(schemasToMock);
Expand Down Expand Up @@ -145,6 +150,7 @@ async function askForPorts(schemaPaths) {
const port = await input({
message: `Select a port for ${schemaPath}`,
default: suggestedPort,
validate: (input) => portValidator(input, selectedSchemas),
});
const portNumber = parseInt(port);
const schema = { path: schemaPath, port: portNumber };
Expand All @@ -154,4 +160,4 @@ async function askForPorts(schemaPaths) {
return selectedSchemas;
}

export { initWithConfigFile, startMockServer, init };
export { init, initWithConfigFile, startMockServer };
7 changes: 7 additions & 0 deletions src/services/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,10 @@ export function verifyRemoteOrigin(origin) {
const isOriginRemote = isOriginRemoteRegex.test(origin);
return isOriginRemote;
}

export default {
RC_FILE_NAME,
TEMP_FOLDER_NAME,
addToGitignore,
verifyRemoteOrigin,
};
90 changes: 90 additions & 0 deletions test/unit/services/inquirer-validators.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { expect, use } from 'chai';
import fs from 'fs';
import { stub } from 'sinon';
import sinonChai from 'sinon-chai';
import { errorMessages, originValidator, portValidator } from '../../../src/services/inquirer-validators.js';

use(sinonChai);

describe('unit:inquirer-validators', () => {
describe('originValidator', () => {
const validRemoteHttpsOrigin = 'https://github.com/user/repo.git';
const validRemoteGitOrigin = 'git@github.com:user/repo.git';
const validLocalPath = '/path/to/local';

it('should return true if the value is a valid local path', () => {
let existsSyncStub = stub(fs, 'existsSync');
existsSyncStub.withArgs(validLocalPath).returns(true);
expect(originValidator(validLocalPath)).to.be.true;
existsSyncStub.restore();
});

it('should return true if the value is a valid remote origin with https', () => {
expect(originValidator(validRemoteHttpsOrigin)).to.be.true;
});

it('should return true if the value is a valid remote origin with git@', () => {
expect(originValidator(validRemoteGitOrigin)).to.be.true;
});

it('should return an error message if the value is not a valid local path nor remote origin', () => {
expect(originValidator('invalid-value')).to.equal(errorMessages.origin.INVALID);
});

it('should return an error message if the value is a valid remote origin with a starting space', () => {
expect(originValidator(` ${validRemoteGitOrigin}`)).to.equal(errorMessages.origin.INVALID);
});

it('should return an error message if the value is a valid remote origin with a starting tab', () => {
expect(originValidator(`\t${validRemoteGitOrigin}`)).to.equal(errorMessages.origin.INVALID);
});

it('should return an error message if the value is a valid remote origin with a starting newline character', () => {
expect(originValidator(`\n${validRemoteGitOrigin}`)).to.equal(errorMessages.origin.INVALID);
});

it('should return an error message if the value is a valid remote origin with an ending space', () => {
expect(originValidator(`${validRemoteGitOrigin} `)).to.equal(errorMessages.origin.INVALID);
});

it('should return an error message if the value is a valid remote origin with an ending tab', () => {
expect(originValidator(`${validRemoteGitOrigin}\t`)).to.equal(errorMessages.origin.INVALID);
});

it('should return an error message if the value is a valid remote origin with an ending newline character', () => {
expect(originValidator(`${validRemoteGitOrigin}\n`)).to.equal(errorMessages.origin.INVALID);
});
});

describe('portValidator', () => {
const selectedSchemas = [
{ path: 'irrelevant', port: 5000 },
{ path: 'irrelevant', port: 5001 },
];

it('should return true if the value is an integer between 0 and 65535 and not already selected', () => {
expect(portValidator('0', selectedSchemas)).to.be.true;
expect(portValidator('65535', selectedSchemas)).to.be.true;
expect(portValidator('5002', selectedSchemas)).to.be.true;
});

it('should return an error message if the value is not an integer', () => {
expect(portValidator('not an integer', selectedSchemas)).to.equal(errorMessages.port.INVALID);
expect(portValidator('3.14', selectedSchemas)).to.equal(errorMessages.port.INVALID);
});

it('should return an error message if the value is less than 0', () => {
expect(portValidator('-1', selectedSchemas)).to.equal(errorMessages.port.INVALID);
expect(portValidator('-100', selectedSchemas)).to.equal(errorMessages.port.INVALID);
});

it('should return an error message if the value is greater than 65535', () => {
expect(portValidator('65536', selectedSchemas)).to.equal(errorMessages.port.INVALID);
expect(portValidator('100000', selectedSchemas)).to.equal(errorMessages.port.INVALID);
});

it('should return an error message if the value is already selected', () => {
expect(portValidator('5000', selectedSchemas)).to.equal(errorMessages.port.IN_USE);
});
});
});

0 comments on commit 3cdf5eb

Please # to comment.