import { describe, expect, it } from 'vitest'; // ── Tests: SSHKaos parameter validation ────────────────────────────── // // SSHKaos.create() requires a live SSH connection to proceed past // connectClient(). These tests verify the parameter validation and // shell quoting logic without needing a real SSH server. describe('e2e: SSH mock tests', () => { describe('SSHKaos.create() parameter validation', () => { it('missing host -> connect attempt with empty host', async () => { // SSHKaos.create() passes the host directly to ssh2's connect(). // With an empty/undefined host, ssh2 will fail to connect. // We verify the error is thrown. const { SSHKaos } = await import('#/ssh'); await expect( SSHKaos.create({ host: '', username: 'testuser', }), ).rejects.toThrow(); }); it('missing username -> connect attempt with empty username', async () => { const { SSHKaos } = await import('#/ssh'); await expect( SSHKaos.create({ host: '127.0.0.1', port: 99999, // Use an unlikely port to ensure fast failure username: '', }), ).rejects.toThrow(); }); it('invalid port -> connection error', async () => { const { SSHKaos } = await import('#/ssh'); // Port 1 is unlikely to have an SSH server; should fail quickly await expect( SSHKaos.create({ host: '127.0.0.1', port: 1, username: 'testuser', }), ).rejects.toThrow(); }); }); describe('shell quoting logic', () => { // We test the shellQuote function indirectly by examining its behavior. // The function is not exported, but we can verify its contract: // - Empty string -> '' // - Safe chars -> unchanged // - Special chars -> single-quoted with embedded quote escaping it('shellQuote rules verified via regex match', () => { // The shellQuote regex for safe chars: /^[A-Za-z0-9_./:=@%^,+-]+$/ const safeRegex = /^[A-Za-z0-9_./:=@%^,+-]+$/; // These should be considered safe (no quoting needed) expect(safeRegex.test('simple')).toBe(true); expect(safeRegex.test('/usr/bin/node')).toBe(true); expect(safeRegex.test('key=value')).toBe(true); expect(safeRegex.test('file.txt')).toBe(true); // These should need quoting expect(safeRegex.test('')).toBe(false); expect(safeRegex.test('hello world')).toBe(false); expect(safeRegex.test("it's")).toBe(false); expect(safeRegex.test('$HOME')).toBe(false); expect(safeRegex.test('a|b')).toBe(false); expect(safeRegex.test('a"b')).toBe(false); expect(safeRegex.test('a;b')).toBe(false); expect(safeRegex.test('a&b')).toBe(false); expect(safeRegex.test('a`b')).toBe(false); }); it('shellQuote single-quote escaping pattern is correct', () => { // shellQuote wraps in single quotes, replacing ' with '"'"' // Verify the pattern: 'text'"'"'more' handles embedded quotes const input = "it's a test"; const escaped = "'" + input.replaceAll("'", "'\"'\"'") + "'"; expect(escaped).toBe("'it'\"'\"'s a test'"); // This should be safe to use in a shell command expect(escaped).toContain("'\"'\"'"); }); it('special characters that need quoting', () => { const specialChars = [' ', "'", '"', '$', '|', '&', ';', '`', '(', ')', '{', '}', '<', '>']; const safeRegex = /^[A-Za-z0-9_./:=@%^,+-]+$/; for (const ch of specialChars) { expect(safeRegex.test(`arg${ch}value`)).toBe(false); } }); it('empty string quoting produces two single quotes', () => { // shellQuote('') should return "''" const emptyQuoted = "''"; expect(emptyQuoted).toBe("''"); expect(emptyQuoted.length).toBe(2); }); }); describe('SSHKaosOptions type constraints', () => { it('options structure has required fields', () => { // Verify at compile time and runtime that SSHKaosOptions // requires host and username const validOptions = { host: 'example.com', username: 'user', }; expect(validOptions.host).toBe('example.com'); expect(validOptions.username).toBe('user'); }); it('options with all optional fields', () => { const fullOptions = { host: 'example.com', port: 2222, username: 'user', password: 'pass', keyPaths: ['/path/to/key'], keyContents: ['ssh-rsa AAAA...'], cwd: '/home/user', }; expect(fullOptions.port).toBe(2222); expect(fullOptions.password).toBe('pass'); expect(fullOptions.keyPaths).toHaveLength(1); expect(fullOptions.keyContents).toHaveLength(1); expect(fullOptions.cwd).toBe('/home/user'); }); }); });