Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add logout command #2401

Merged
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions .nycrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,23 @@ function configOverrides (testType) {
branches: 20,
functions: 35,
lines: 35,
exclude: ['lib/crypt.js', 'lib/login']
exclude: ['lib/crypt.js', 'lib/login', 'lib/logout']
};
case 'library':
return {
statements: 55,
branches: 40,
functions: 55,
lines: 55,
exclude: ['lib/crypt.js', 'lib/login']
exclude: ['lib/crypt.js', 'lib/login', 'lib/logout']
};
case 'unit':
return {
statements: 70,
branches: 55,
functions: 75,
lines: 75
lines: 75,
exclude: ['lib/logout']
};
default:
return {}
Expand Down
13 changes: 13 additions & 0 deletions bin/newman.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const _ = require('lodash'),
newman = require('../'),
util = require('./util'),
login = require('../lib/login'),
logout = require('../lib/logout'),

RUN_COMMAND = 'run';

Expand Down Expand Up @@ -105,6 +106,18 @@ program
});
});

program.command('logout')
.description('Dereference an API-Key from its alias.')
.action(() => {
logout((err) => {
if (err) {
console.error(`error: ${err.message || err}\n`);
err.help && console.error(err.help);
process.exit(1);
}
});
});

program.on('--help', function () {
console.info('\nTo get available options for a command:');
console.info(' newman <command> -h');
Expand Down
143 changes: 143 additions & 0 deletions lib/logout/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
const _ = require('lodash'),
waterfall = require('async/waterfall'),
colors = require('colors/safe'),
readline = require('readline'),
rcfile = require('../config/rc-file'),
env = require('../config/process-env'),
print = require('../print'),
util = require('../util'),

ALIAS = 'alias',
DEFAULT = 'default',

ALIAS_INPUT_PROMPT = 'Select the alias',
NO_ALIAS_AVAILABLE = 'No aliases are available.',
SUCCESS_MESSAGE = (alias) => { return `${alias} logged out successfully.`; },
PROFILE_OPTION = (profile) => { return ` ${profile.alias}`; },
HIGHLIGHTED_PROFILE_OPTION = (profile) => { return ' * ' + colors.green(profile.alias); };

/**
* Displays all the available aliases and deletes the selected alias from the home-rc-file.
*
* @param {Function} callback - The callback to be invoked after the process
* @returns {*}
*/
module.exports = (callback) => {
let fileData,
alias;

waterfall([
// get the data from home-rc-file
// this is done in the beginning to prevent errors caused by file-load in between the logout-process
rcfile.load,

// load the session alias from the environment
(data, next) => {
fileData = data; // store the file-data for later use

// @todo: Replace with the updated process-env
next(null, env.postmanApiKeyAlias);
},

// display all the options and facilitate navigation between the same
(sessionAlias, next) => {
let profiles,
currentProfile;

fileData.login && (profiles = fileData.login._profiles);

if (_.isEmpty(profiles)) {
return next(NO_ALIAS_AVAILABLE);
}

if (profiles.length === 1) {
alias = _.head(profiles).alias; // note the selected alias for later use

return next(null);
}

// the initial option will be either the session-alias or 'default'
[currentProfile] = _.filter(profiles, [ALIAS, sessionAlias || DEFAULT]);
!currentProfile && (currentProfile = _.head(profiles));

// display the prompt and all the choices
console.info(colors.yellow.bold(ALIAS_INPUT_PROMPT));
_.forEach(profiles, (profile) => {
if (profile === currentProfile) {
console.info(HIGHLIGHTED_PROFILE_OPTION(profile));
}
else {
console.info(PROFILE_OPTION(profile));
}
});

// move the highlighted option by the required amount
var displace = (change) => {
let currentIndex = _.findIndex(profiles, currentProfile),
nextIndex = (currentIndex + change + profiles.length) % profiles.length;

// remove the highlight from the current profile
print.displacedWrite(PROFILE_OPTION(currentProfile), 0, -(profiles.length - currentIndex));
currentProfile = profiles[nextIndex];
// highlight the updated profile
print.displacedWrite(HIGHLIGHTED_PROFILE_OPTION(currentProfile), 0, -(profiles.length - nextIndex));
},

// keypress listener to navigate between the alias options
keyPressListener = (_str, key) => {
switch (key.name) {
case 'up':
displace(-1);
break;

case 'down':
displace(1);
break;

case 'return':
process.stdin.removeListener('keypress', keyPressListener);

process.stdin.isTTY && process.stdin.setRawMode(false); // reset the behaviour of stdin
process.stdin.pause(); // else the program won't terminate

alias = currentProfile.alias; // note the selected alias for later use

return next(null);

case 'c':
// terminate the process on ctrl-C
if (key.ctrl) { process.exit(1); }

break;

// no default
}
};

process.stdin.isTTY && process.stdin.setRawMode(true); // listen to one key at a time
readline.emitKeypressEvents(process.stdin);

process.stdin.on('keypress', keyPressListener);
util.signal(ALIAS);
},

// delete the data related to the alias selected
(next) => {
fileData.login._profiles = _.reject(fileData.login._profiles, [ALIAS, alias]);

return next(null, fileData);
},

// write the edited file data back
rcfile.store

], (err) => {
if (err) {
return callback(err);
}

console.info(SUCCESS_MESSAGE(alias));

return callback(null);
});
};
19 changes: 19 additions & 0 deletions lib/print/index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
var format = require('util').format,
readline = require('readline'),
cliUtils = require('../reporters/cli/cli-utils'),
SPC = ' ',
BS = '\b',
LF = '\n',
CLEAR_LINE = '\u001b[K', // clears the remaining part of the line
WAIT_FRAMES = (/^win/).test(process.platform) ?
['\u2015', '\\', '|', '/'] :
['⠄', '⠆', '⠇', '⠋', '⠙', '⠸', '⠰', '⠠', '⠰', '⠸', '⠙', '⠋', '⠇', '⠆'],
Expand Down Expand Up @@ -34,6 +36,23 @@ print.print = function () {
return print;
};

/**
* Moves the cursor by the required amount, prints the data and resets the cursor to its initial position
*
* @param {String} data - The data to be printed
* @param {Number} dx - The displacement in the x direction
* @param {Number} dy - The displacement in the y direction assuming downwards as positive
* @returns {*}
*/
print.displacedWrite = function (data, dx, dy) {
readline.moveCursor(process.stdout, dx, dy);

// firstly clear the existing data, then write the required data
print(CLEAR_LINE + data);

readline.moveCursor(process.stdout, -dx - data.length, -dy);
};

/**
* Print with a line feed at the end.
*
Expand Down
157 changes: 157 additions & 0 deletions test/cli/logout.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
/* eslint-disable no-process-env */
const fs = require('fs'),
// eslint-disable-next-line security/detect-child-process
child = require('child_process'),
liquidJSON = require('liquid-json'),
sh = require('shelljs'),
join = require('path').join,
colors = require('colors/safe'),

DEFAULT = 'default',
TEST_ALIAS = 'testalias',

DOWN_ARROW = '\u001b[B',
CARRIAGE_RETURN = '\r',

ALIAS_INPUT_PROMPT = 'Select the alias',
SUCCESS_MESSAGE = (alias) => { return `${alias} logged out successfully.`; },
ALIAS_OPTION = (alias) => { return ` ${alias}`; },
HIGHLIGHTED_ALIAS_OPTION = (alias) => { return ' * ' + colors.green(alias); },
NO_ALIAS_AVAILABLE = 'No aliases are available.';

describe('Logout command', function () {
let outDir = join(__dirname, '..', '..', 'out'),
configDir = join(outDir, '.postman'),
rcFile = join(configDir, 'newmanrc'),
isWin = (/^win/).test(process.platform),
homeDir = process.env[isWin ? 'userprofile' : 'HOME'],
proc;

before(function () {
// change the home directory to alter the location of the rc file
process.env[isWin ? 'userprofile' : 'HOME'] = outDir;
});

after(function () {
// update the home directory back to its original value
process.env[isWin ? 'userprofile' : 'HOME'] = homeDir;
});

beforeEach(function () {
sh.test('-d', outDir) && sh.rm('-rf', outDir);
sh.mkdir('-p', outDir);
});

afterEach(function () {
// make sure the child-process is terminated
if (proc) {
proc.removeAllListeners();
proc.kill();
}
sh.rm('-rf', outDir);
});

it('should display help with -h option', function (done) {
proc = exec('node ./bin/newman.js logout -h', (code, stdout) => {
expect(code, 'should have exit code of 0').to.equal(0);
expect(stdout).to.match(/Usage: newman logout*/);
done();
});
});

it('should log out the alias directly if only one alias exists', function (done) {
let initialData = {
login: {
_profiles: [
{ alias: TEST_ALIAS }
]
}
},
finalData = {
login: {
_profiles: []
}
};

fs.mkdirSync(configDir);
fs.writeFileSync(rcFile, JSON.stringify(initialData, null, 2), { mode: 0o600 });

proc = exec('node ./bin/newman.js logout', function (code, stdout, stderr) {
expect(code, 'should have exit code of 0').to.equal(0);
expect(stdout, 'should indicate the deleted alias').to.contain(SUCCESS_MESSAGE(TEST_ALIAS));
expect(stderr).to.be.empty;

fs.readFile(rcFile, (err, data) => {
expect(err).to.be.null;
data = liquidJSON.parse(data.toString());
expect(data).to.eql(finalData);
done();
});
});
});

it('should show all the choices if there are more than one available', function (done) {
let initialData = {
login: {
_profiles: [
{ alias: TEST_ALIAS },
{ alias: DEFAULT }
]
}
},
finalData = {
login: {
_profiles: [
{ alias: DEFAULT }
]
}
},
stdout = '';

fs.mkdirSync(configDir);
fs.writeFileSync(rcFile, JSON.stringify(initialData, null, 2), { mode: 0o600 });

proc = child.spawn('node', ['./bin/newman.js', 'logout'], {
stdio: ['pipe', 'pipe', 'pipe', 'ipc'] // to enable inter process communication through messages
});

proc.stdout.on('data', (data) => {
stdout += data;
});

// navigate between the options and choose the required alias when prompted for
proc.on('message', () => {
expect(stdout, 'should contain the prompt for alias input').to.contain(ALIAS_INPUT_PROMPT);
expect(stdout, 'should highlight the current option').to.contain(HIGHLIGHTED_ALIAS_OPTION(DEFAULT));
expect(stdout, 'should include \'testalias\' in the list of options').to.contain(ALIAS_OPTION(TEST_ALIAS));

// go to the next option and press enter
proc.stdin.write(DOWN_ARROW + CARRIAGE_RETURN);
proc.stdin.end();
});

proc.on('close', (code) => {
expect(code, 'should have exit code of 0').to.equal(0);
expect(stdout, 'should contain the prompt for alias input').to.contain(ALIAS_INPUT_PROMPT);
expect(stdout, 'should highlight the chosen option').to.contain(HIGHLIGHTED_ALIAS_OPTION(TEST_ALIAS));
expect(stdout, 'should not highlight the previous option').to.contain(HIGHLIGHTED_ALIAS_OPTION(DEFAULT));
expect(stdout, 'should include \'default\' in the list of options').to.contain(ALIAS_OPTION(DEFAULT));
expect(stdout, 'should contain the success message').to.contain(SUCCESS_MESSAGE(TEST_ALIAS));

fs.readFile(rcFile, (err, data) => {
expect(err).to.be.null;
data = liquidJSON.parse(data.toString());
expect(data).to.eql(finalData);
done();
});
});
});

it('should print an error if no aliases exist', function (done) {
proc = exec('node ./bin/newman.js logout', function (code, _stdout, stderr) {
expect(code, 'should have exit code of 1').to.equal(1);
expect(stderr, 'should display the error message').to.contain(NO_ALIAS_AVAILABLE);
done();
});
});
});
Loading