11// Regression test: the modular tool handlers (exec, db, deploy, monitoring,
2- // systemctl, journalctl, cat, tail, transfer, etc.) all pass the SSHManager
3- // instance returned by getConnection() into stream-exec.js's streamExecCommand,
4- // which then calls `client.exec(...)`. SSHManager wraps an ssh2 Client; if
5- // .exec isn't exposed as a passthrough, every tool fails at runtime with
6- // "client.exec is not a function" while every unit test still passes.
7- //
8- // This test asserts the passthrough exists and forwards arguments correctly.
2+ // systemctl, journalctl, cat, tail, transfer, tunnels, etc.) all expect the
3+ // node-ssh2 Client surface (.exec, .sftp, .forwardOut), but getConnection()
4+ // returns an SSHManager wrapper. Without passthroughs every call fails at
5+ // runtime with "client.{exec,sftp,forwardOut} is not a function" or hangs
6+ // silently — while every existing unit test passes (they mock SSH).
97
108import SSHManager from '../src/ssh-manager.js' ;
119
@@ -17,28 +15,62 @@ function assert(cond, msg) {
1715 else { console . log ( ` [FAIL] ${ msg } ` ) ; failed ++ ; }
1816}
1917
20- // 1. The method exists
2118const mgr = new SSHManager ( { host : 'x' , user : 'x' } ) ;
19+ mgr . connected = true ;
20+
21+ // --- exec passthrough ---
2222assert ( typeof mgr . exec === 'function' , 'SSHManager.exec is a function' ) ;
2323
24- // 2. It forwards (cmd, cb) to the underlying ssh2 client
2524let captured = null ;
2625mgr . client = {
27- exec ( cmd , optsOrCb , maybeCb ) {
28- captured = { cmd , optsOrCb , maybeCb } ;
29- } ,
26+ exec ( cmd , optsOrCb , maybeCb ) { captured = { cmd , optsOrCb , maybeCb } ; } ,
27+ sftp ( cb ) { captured = { sftpCb : cb } ; } ,
28+ forwardOut ( srcA , srcP , dstA , dstP , cb ) { captured = { srcA , srcP , dstA , dstP , cb } ; } ,
3029} ;
30+
3131mgr . exec ( 'uname -a' , ( ) => { } ) ;
32- assert ( captured ?. cmd === 'uname -a' , 'forwards command string' ) ;
33- assert ( typeof captured ?. optsOrCb === 'function' , 'forwards callback as 2nd arg when no options given ' ) ;
34- assert ( captured ?. maybeCb === undefined , 'no 3rd arg when no options given ' ) ;
32+ assert ( captured ?. cmd === 'uname -a' , 'exec forwards command string' ) ;
33+ assert ( typeof captured ?. optsOrCb === 'function' , 'exec forwards callback as 2nd arg without options' ) ;
34+ assert ( captured ?. maybeCb === undefined , 'exec no 3rd arg without options' ) ;
3535
36- // 3. It forwards (cmd, opts, cb) when options are provided
3736captured = null ;
3837mgr . exec ( 'echo hi' , { pty : true } , ( ) => { } ) ;
39- assert ( captured ?. cmd === 'echo hi' , 'forwards command with options' ) ;
40- assert ( captured ?. optsOrCb ?. pty === true , 'forwards options object' ) ;
41- assert ( typeof captured ?. maybeCb === 'function' , 'forwards callback as 3rd arg when options given' ) ;
38+ assert ( captured ?. cmd === 'echo hi' , 'exec forwards command with options' ) ;
39+ assert ( captured ?. optsOrCb ?. pty === true , 'exec forwards options object' ) ;
40+ assert ( typeof captured ?. maybeCb === 'function' , 'exec forwards callback as 3rd arg with options' ) ;
41+
42+ // --- sftp passthrough ---
43+ assert ( typeof mgr . sftp === 'function' , 'SSHManager.sftp is a function' ) ;
44+ captured = null ;
45+ const sftpCb = ( err , sftp ) => { } ;
46+ mgr . sftp ( sftpCb ) ;
47+ assert ( captured ?. sftpCb === sftpCb , 'sftp forwards callback to underlying client' ) ;
48+
49+ // --- forwardOut callback-style (used by tunnel-tools.js) ---
50+ captured = null ;
51+ const fwdCb = ( ) => { } ;
52+ mgr . forwardOut ( '127.0.0.1' , 1234 , 'remote.host' , 22 , fwdCb ) ;
53+ assert ( captured ?. cb === fwdCb , 'forwardOut callback-style passes cb to underlying client' ) ;
54+ assert ( captured ?. srcA === '127.0.0.1' && captured ?. dstP === 22 , 'forwardOut callback-style passes src/dst args' ) ;
55+
56+ // --- forwardOut Promise-style (used by index.js for proxy jumps) ---
57+ let resolvedStream = null ;
58+ mgr . client . forwardOut = ( srcA , srcP , dstA , dstP , cb ) => cb ( null , { tag : 'mockStream' } ) ;
59+ const promise = mgr . forwardOut ( '127.0.0.1' , 0 , 'jump.host' , 22 ) ;
60+ assert ( promise && typeof promise . then === 'function' , 'forwardOut Promise-style returns a Promise' ) ;
61+ await promise . then ( s => { resolvedStream = s ; } ) ;
62+ assert ( resolvedStream ?. tag === 'mockStream' , 'forwardOut Promise-style resolves with the stream' ) ;
63+
64+ // --- error paths ---
65+ mgr . connected = false ;
66+ const fwdRejection = mgr . forwardOut ( '127.0.0.1' , 0 , 'jump.host' , 22 ) ;
67+ let rejected = false ;
68+ await fwdRejection . catch ( ( ) => { rejected = true ; } ) ;
69+ assert ( rejected , 'forwardOut Promise-style rejects when not connected' ) ;
70+
71+ let cbErr = null ;
72+ mgr . forwardOut ( '127.0.0.1' , 0 , 'h' , 22 , e => { cbErr = e ; } ) ;
73+ assert ( cbErr instanceof Error , 'forwardOut callback-style invokes cb with error when not connected' ) ;
4274
4375console . log ( `\n${ passed } passed, ${ failed } failed` ) ;
4476process . exit ( failed === 0 ? 0 : 1 ) ;
0 commit comments