Skip to content

Commit

Permalink
test: add lib/logout.js unit tests
Browse files Browse the repository at this point in the history
- Fixed config.delete to remove from user config on logout
- Tidy up lib/logout.js code
- Added test/lib/logout.js

PR-URL: #1698
Credit: @ruyadorno
Close: #1698
Reviewed-by: @isaacs
  • Loading branch information
ruyadorno authored and isaacs committed Aug 21, 2020
1 parent fcf5fb0 commit 88e4241
Show file tree
Hide file tree
Showing 2 changed files with 286 additions and 11 deletions.
36 changes: 25 additions & 11 deletions lib/logout.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,32 @@
'use strict'

const eu = encodeURIComponent
const getAuth = require('npm-registry-fetch/auth.js')
const log = require('npmlog')
const npm = require('./npm.js')
const getAuth = require('npm-registry-fetch/auth.js')
const npmFetch = require('npm-registry-fetch')
const npm = require('./npm.js')
const usageUtil = require('./utils/usage.js')

const usage = usageUtil('logout', 'npm logout [--registry=<url>] [--scope=<@scope>]')
const completion = require('./utils/completion/none.js')

const usage = usageUtil(
'logout',
'npm logout [--registry=<url>] [--scope=<@scope>]'
)

const cmd = (args, cb) => logout(args).then(() => cb()).catch(cb)

const logout = async args => {
const reg = npmFetch.pickRegistry('foo', npm.flatOptions)
const logout = async (args) => {
const { registry, scope } = npm.flatOptions
const regRef = `${scope}:registry`
let reg = registry

if (scope) {
const scopedRef = npm.flatOptions[regRef]
reg = scopedRef || reg
}

const auth = getAuth(reg, npm.flatOptions)

if (auth.token) {
log.verbose('logout', `clearing token for ${reg}`)
await npmFetch(`/-/user/token/${eu(auth.token)}`, {
Expand All @@ -23,16 +37,16 @@ const logout = async args => {
} else if (auth.username || auth.password) {
log.verbose('logout', `clearing user credentials for ${reg}`)
} else {
throw Object.assign(new Error(`not logged in to ${reg}, so can't log out!`), {
code: 'ENEEDAUTH'
})
const msg = `not logged in to ${reg}, so can't log out!`
throw Object.assign(new Error(msg), { code: 'ENEEDAUTH' })
}

const scope = npm.config.get('scope')
if (scope) {
npm.config.del(`${scope}:registry`)
npm.config.delete(regRef, 'user')
}

npm.config.clearCredentialsByURI(reg)

await npm.config.save('user')
}

Expand Down
261 changes: 261 additions & 0 deletions test/lib/logout.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
const requireInject = require('require-inject')
const { test } = require('tap')

const _flatOptions = {
registry: 'https://registry.npmjs.org/',
scope: ''
}

const config = {}
const npmlog = {}

let result = null
const npmFetch = (url, opts) => {
result = { url, opts }
}

const mocks = {
npmlog,
'npm-registry-fetch': npmFetch,
'../../lib/npm.js': {
flatOptions: _flatOptions,
config
}
}

const logout = requireInject('../../lib/logout.js', mocks)

test('token logout', async (t) => {
t.plan(6)

_flatOptions.token = '@foo/'

npmlog.verbose = (title, msg) => {
t.equal(title, 'logout', 'should have correcct log prefix')
t.equal(
msg,
'clearing token for https://registry.npmjs.org/',
'should log message with correct registry'
)
}

config.clearCredentialsByURI = (registry) => {
t.equal(
registry,
'https://registry.npmjs.org/',
'should clear credentials from the expected registry'
)
}

config.save = (type) => {
t.equal(type, 'user', 'should save to user config')
}

await new Promise((res, rej) => {
logout([], (err) => {
t.ifError(err, 'should not error out')

t.deepEqual(
result,
{
url: '/-/user/token/%40foo%2F',
opts: {
registry: 'https://registry.npmjs.org/',
scope: '',
token: '@foo/',
method: 'DELETE',
ignoreBody: true
}
},
'should call npm-registry-fetch with expected values'
)

delete _flatOptions.token
result = null
mocks['npm-registry-fetch'] = null
config.clearCredentialsByURI = null
config.delete = null
config.save = null
npmlog.verbose = null

res()
})
})
})

test('token scoped logout', async (t) => {
t.plan(8)

_flatOptions.token = '@foo/'
_flatOptions.scope = '@myscope'
_flatOptions['@myscope:registry'] = 'https://diff-registry.npmjs.com/'

npmlog.verbose = (title, msg) => {
t.equal(title, 'logout', 'should have correcct log prefix')
t.equal(
msg,
'clearing token for https://diff-registry.npmjs.com/',
'should log message with correct registry'
)
}

config.clearCredentialsByURI = (registry) => {
t.equal(
registry,
'https://diff-registry.npmjs.com/',
'should clear credentials from the expected registry'
)
}

config.delete = (ref, type) => {
t.equal(
ref,
'@myscope:registry',
'should delete scoped registyr from config'
)
t.equal(type, 'user', 'should delete from user config')
}

config.save = (type) => {
t.equal(type, 'user', 'should save to user config')
}

await new Promise((res, rej) => {
logout([], (err) => {
t.ifError(err, 'should not error out')

t.deepEqual(
result,
{
url: '/-/user/token/%40foo%2F',
opts: {
registry: 'https://registry.npmjs.org/',
'@myscope:registry': 'https://diff-registry.npmjs.com/',
scope: '@myscope',
token: '@foo/',
method: 'DELETE',
ignoreBody: true
}
},
'should call npm-registry-fetch with expected values'
)

_flatOptions.scope = ''
delete _flatOptions['@myscope:registry']
delete _flatOptions.token
result = null
mocks['npm-registry-fetch'] = null
config.clearCredentialsByURI = null
config.delete = null
config.save = null
npmlog.verbose = null

res()
})
})
})

test('user/pass logout', async (t) => {
t.plan(3)

_flatOptions.username = 'foo'
_flatOptions.password = 'bar'

npmlog.verbose = (title, msg) => {
t.equal(title, 'logout', 'should have correcct log prefix')
t.equal(
msg,
'clearing user credentials for https://registry.npmjs.org/',
'should log message with correct registry'
)
}

config.clearCredentialsByURI = () => null
config.save = () => null

await new Promise((res, rej) => {
logout([], (err) => {
t.ifError(err, 'should not error out')

delete _flatOptions.username
delete _flatOptions.password
config.clearCredentialsByURI = null
config.save = null
npmlog.verbose = null

res()
})
})
})

test('missing credentials', (t) => {
logout([], (err) => {
t.match(
err.message,
/not logged in to https:\/\/registry.npmjs.org\/, so can't log out!/,
'should throw with expected message'
)
t.equal(err.code, 'ENEEDAUTH', 'should throw with expected error code')
t.end()
})
})

test('ignore invalid scoped registry config', async (t) => {
t.plan(5)

_flatOptions.token = '@foo/'
_flatOptions.scope = '@myscope'
_flatOptions['@myscope:registry'] = ''

npmlog.verbose = (title, msg) => {
t.equal(title, 'logout', 'should have correcct log prefix')
t.equal(
msg,
'clearing token for https://registry.npmjs.org/',
'should log message with correct registry'
)
}

config.clearCredentialsByURI = (registry) => {
t.equal(
registry,
'https://registry.npmjs.org/',
'should clear credentials from the expected registry'
)
}

config.delete = () => null
config.save = () => null

await new Promise((res, rej) => {
logout([], (err) => {
t.ifError(err, 'should not error out')

t.deepEqual(
result,
{
url: '/-/user/token/%40foo%2F',
opts: {
registry: 'https://registry.npmjs.org/',
scope: '@myscope',
'@myscope:registry': '',
token: '@foo/',
method: 'DELETE',
ignoreBody: true
}
},
'should call npm-registry-fetch with expected values'
)

delete _flatOptions.token
result = null
mocks['npm-registry-fetch'] = null
config.clearCredentialsByURI = null
config.delete = null
config.save = null
npmlog.verbose = null

res()
})
})
})

0 comments on commit 88e4241

Please sign in to comment.