From 6052251d3266c9bc7a25e39d2b1aa4066bb50c8e Mon Sep 17 00:00:00 2001 From: Markus Olsson Date: Mon, 3 Jun 2024 11:47:33 +0200 Subject: [PATCH] Test that stdin gets forwarded correctly Co-Authored-By: Sergio Padrino <1083228+sergiou87@users.noreply.github.com> --- index.d.ts | 3 ++ index.js | 17 +++++++ test/desktop-trampoline.test.js | 90 +++++++++++++++++++++++++-------- 3 files changed, 89 insertions(+), 21 deletions(-) diff --git a/index.d.ts b/index.d.ts index 1368680..6ce6768 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,5 +1,8 @@ export function getDesktopAskpassTrampolinePath(): string export function getDesktopAskpassTrampolineFilename(): string +export function getDesktopCredentialHelperTrampolinePath(): string +export function getDesktopCredentialHelperTrampolineFilename(): string + export function getSSHWrapperPath(): string export function getSSHWrapperFilename(): string diff --git a/index.js b/index.js index 71351e0..be164b0 100644 --- a/index.js +++ b/index.js @@ -15,6 +15,21 @@ function getDesktopAskpassTrampolineFilename() { : 'desktop-askpass-trampoline' } +function getDesktopCredentialHelperTrampolinePath() { + return Path.join( + __dirname, + 'build', + 'Release', + getDesktopCredentialHelperTrampolineFilename() + ) +} + +function getDesktopCredentialHelperTrampolineFilename() { + return process.platform === 'win32' + ? 'desktop-credential-helper-trampoline.exe' + : 'desktop-credential-helper-trampoline' +} + function getSSHWrapperPath() { return Path.join(__dirname, 'build', 'Release', getSSHWrapperFilename()) } @@ -26,6 +41,8 @@ function getSSHWrapperFilename() { module.exports = { getDesktopAskpassTrampolinePath, getDesktopAskpassTrampolineFilename, + getDesktopCredentialHelperTrampolinePath, + getDesktopCredentialHelperTrampolineFilename, getSSHWrapperPath, getSSHWrapperFilename, } diff --git a/test/desktop-trampoline.test.js b/test/desktop-trampoline.test.js index fc258a1..0f455e4 100644 --- a/test/desktop-trampoline.test.js +++ b/test/desktop-trampoline.test.js @@ -2,46 +2,67 @@ const { stat, access } = require('fs').promises const { constants } = require('fs') const { execFile } = require('child_process') const { promisify } = require('util') -const { getDesktopAskpassTrampolinePath } = require('../index') +const { getDesktopAskpassTrampolinePath, getDesktopCredentialHelperTrampolinePath } = require('../index') const split2 = require('split2') const { createServer } = require('net') -const trampolinePath = getDesktopAskpassTrampolinePath() +const askPassTrampolinePath = getDesktopAskpassTrampolinePath() +const helperTrampolinePath = getDesktopCredentialHelperTrampolinePath() const run = promisify(execFile) describe('desktop-trampoline', () => { it('exists and is a regular file', async () => - expect((await stat(trampolinePath)).isFile()).toBe(true)) + expect((await stat(askPassTrampolinePath)).isFile()).toBe(true)) it('can be executed by current process', () => - access(trampolinePath, constants.X_OK)) + access(askPassTrampolinePath, constants.X_OK)) it('fails when required environment variables are missing', () => - expect(run(trampolinePath, ['Username'])).rejects.toThrow()) + expect(run(askPassTrampolinePath, ['Username'])).rejects.toThrow()) - it('forwards arguments and valid environment variables correctly', async () => { + const captureSession = () => { const output = [] + let resolveOutput = null + + const outputPromise = new Promise(resolve => { + resolveOutput = resolve + }) + const server = createServer(socket => { + let timeoutId = null socket.pipe(split2(/\0/)).on('data', data => { output.push(data.toString('utf8')) - }) - // Don't send anything and just close the socket after the trampoline is - // done forwarding data. - socket.end() + // Hack: consider the session finished after 100ms of inactivity. + // In a real-world scenario, you'd have to parse the data to know when + // the session is finished. + if (timeoutId !== null) { + clearTimeout(timeoutId) + timeoutId = null + } + timeoutId = setTimeout(() => { + resolveOutput(output) + socket.end() + server.close() + }, 100) + }) }) - server.unref() - const startTrampolineServer = async () => { - return new Promise((resolve, reject) => { - server.on('error', e => reject(e)) - server.listen(0, '127.0.0.1', () => { - resolve(server.address().port) - }) + const serverPortPromise = new Promise((resolve, reject) => { + server.on('error', e => reject(e)) + server.listen(0, '127.0.0.1', () => { + resolve(server.address().port) }) - } + }) + + return [serverPortPromise, outputPromise] + } + + it('forwards arguments and valid environment variables correctly', async () => { + + const [portPromise, outputPromise] = captureSession() + const port = await portPromise - const port = await startTrampolineServer() const env = { DESKTOP_TRAMPOLINE_TOKEN: '123456', DESKTOP_PORT: port, @@ -49,8 +70,9 @@ describe('desktop-trampoline', () => { } const opts = { env } - await run(trampolinePath, ['baz'], opts) + await run(askPassTrampolinePath, ['baz'], opts) + const output = await outputPromise const outputArguments = output.slice(1, 2) expect(outputArguments).toStrictEqual(['baz']) // output[2] is the number of env variables @@ -58,7 +80,33 @@ describe('desktop-trampoline', () => { const outputEnv = output.slice(3, 3 + envc) expect(outputEnv).toHaveLength(1) expect(outputEnv).toContain('DESKTOP_TRAMPOLINE_TOKEN=123456') + }) - server.close() + it('forwards stdin when running in credential-helper mode', async () => { + + const [portPromise, outputPromise] = captureSession() + const port = await portPromise + + const cp = run(helperTrampolinePath, ['get'], { env: { DESKTOP_PORT: port } }) + cp.child.stdin.end('oh hai\n') + + await cp + + const output = await outputPromise + expect(output.at(-1)).toBe('oh hai\n') + }) + + it('doesn\'t forward stdin when running in askpass mode', async () => { + + const [portPromise, outputPromise] = captureSession() + const port = await portPromise + + const cp = run(askPassTrampolinePath, ['get'], { env: { DESKTOP_PORT: port } }) + cp.child.stdin.end('oh hai\n') + + await cp + + const output = await outputPromise + expect(output.at(-1)).toBe('') }) })