1const t = require('tap') 2const setupMockNpm = require('../../fixtures/mock-npm') 3const tmock = require('../../fixtures/tmock') 4 5const setupOtplease = async (t, { otp = {}, ...rest }, fn) => { 6 const readUserInfo = { 7 otp: async () => '1234', 8 } 9 10 const webAuth = async (opener) => { 11 opener() 12 return '1234' 13 } 14 15 const otplease = tmock(t, '{LIB}/utils/otplease.js', { 16 '{LIB}/utils/read-user-info.js': readUserInfo, 17 '{LIB}/utils/open-url-prompt.js': () => {}, 18 '{LIB}/utils/web-auth': webAuth, 19 }) 20 21 const { npm } = await setupMockNpm(t, rest) 22 23 return await otplease(npm, otp, fn) 24} 25 26t.test('returns function results on success', async (t) => { 27 const result = await setupOtplease(t, {}, () => 'test string') 28 t.equal('test string', result) 29}) 30 31t.test('returns function results on otp success', async (t) => { 32 const fn = ({ otp }) => { 33 if (otp) { 34 return 'success' 35 } 36 throw Object.assign(new Error('nope'), { code: 'EOTP' }) 37 } 38 39 const result = await setupOtplease(t, { 40 globals: { 41 'process.stdin': { isTTY: true }, 42 'process.stdout': { isTTY: true }, 43 }, 44 }, fn) 45 46 t.equal('success', result) 47}) 48 49t.test('prompts for otp for EOTP', async (t) => { 50 let called = false 51 52 const fn = async (opts) => { 53 if (!called) { 54 called = true 55 throw Object.assign(new Error('nope'), { code: 'EOTP' }) 56 } 57 return opts 58 } 59 60 const result = await setupOtplease(t, { 61 otp: { some: 'prop' }, 62 globals: { 63 'process.stdin': { isTTY: true }, 64 'process.stdout': { isTTY: true }, 65 }, 66 }, fn) 67 68 t.strictSame(result, { some: 'prop', otp: '1234' }) 69}) 70 71t.test('returns function results on webauth success', async (t) => { 72 const fn = ({ otp }) => { 73 if (otp) { 74 return 'success' 75 } 76 throw Object.assign(new Error('nope'), { 77 code: 'EOTP', 78 body: { 79 authUrl: 'https://www.example.com/auth', 80 doneUrl: 'https://www.example.com/done', 81 }, 82 }) 83 } 84 85 const result = await setupOtplease(t, { 86 config: { browser: 'firefox' }, 87 globals: { 88 'process.stdin': { isTTY: true }, 89 'process.stdout': { isTTY: true }, 90 }, 91 }, fn) 92 93 t.equal('success', result) 94}) 95 96t.test('prompts for otp for 401', async (t) => { 97 let called = false 98 99 const fn = async (opts) => { 100 if (!called) { 101 called = true 102 throw Object.assign(new Error('nope'), { 103 code: 'E401', 104 body: 'one-time pass required', 105 }) 106 } 107 108 return opts 109 } 110 111 const result = await setupOtplease(t, { 112 globals: { 113 'process.stdin': { isTTY: true }, 114 'process.stdout': { isTTY: true }, 115 }, 116 }, fn) 117 118 t.strictSame(result, { otp: '1234' }) 119}) 120 121t.test('does not prompt for non-otp errors', async (t) => { 122 const fn = async (opts) => { 123 throw new Error('nope') 124 } 125 126 await t.rejects(setupOtplease(t, { 127 globals: { 128 'process.stdin': { isTTY: true }, 129 'process.stdout': { isTTY: true }, 130 }, 131 }, fn), { message: 'nope' }, 'rejects with the original error') 132}) 133 134t.test('does not prompt if stdin or stdout is not a tty', async (t) => { 135 const fn = async (opts) => { 136 throw Object.assign(new Error('nope'), { code: 'EOTP' }) 137 } 138 139 await t.rejects(setupOtplease(t, { 140 globals: { 141 'process.stdin': { isTTY: false }, 142 'process.stdout': { isTTY: false }, 143 }, 144 }, fn), { message: 'nope' }, 'rejects with the original error') 145}) 146