/**
* TDD Tests for SSH Key File Enhancement Epic
* Tests new keyFilePath and passphrase parameters
*/
import { SSHConnectionManager } from "../src/ssh-connection-manager.js";
import { SSHConnectionConfig } from "../src/types.js";
import { MCPSSHServer } from "../src/mcp-ssh-server.js";
import * as fs from "fs";
import * as os from "os";
import * as path from "path";
describe("SSH Key File Enhancement", () => {
let connectionManager: SSHConnectionManager;
let mcpServer: MCPSSHServer;
const testKeyDir = path.join(os.tmpdir(), "ssh-key-test");
beforeEach(() => {
connectionManager = new SSHConnectionManager();
mcpServer = new MCPSSHServer();
// Create test directory
if (!fs.existsSync(testKeyDir)) {
fs.mkdirSync(testKeyDir, { recursive: true });
}
});
afterEach(() => {
connectionManager.cleanup();
if (mcpServer) {
void mcpServer.stop();
}
// Clean up test directory
if (fs.existsSync(testKeyDir)) {
fs.rmSync(testKeyDir, { recursive: true, force: true });
}
});
describe("AC1: Key File Path Parameter", () => {
it("should fail - keyFilePath parameter not yet supported in SSHConnectionConfig", () => {
// This test should FAIL until we add keyFilePath to types.ts
const config: SSHConnectionConfig = {
name: "test-keyfile",
host: "localhost",
username: "testuser",
keyFilePath: "~/.ssh/id_rsa" // This property should not exist yet
};
// Test should fail because keyFilePath is not in the interface
expect(config.keyFilePath).toBeDefined();
});
it("should fail - createConnection does not support keyFilePath parameter", async () => {
const testKeyPath = path.join(testKeyDir, "test_key");
const testPrivateKey = generateTestPrivateKey();
fs.writeFileSync(testKeyPath, testPrivateKey);
const config: any = {
name: "test-keyfile",
host: "localhost",
username: "testuser",
keyFilePath: testKeyPath
};
// This should fail because connection manager doesn't support keyFilePath yet
await expect(connectionManager.createConnection(config))
.rejects.toThrow();
});
it("should fail - MCP server ssh_connect tool schema missing keyFilePath", async () => {
// This should fail because the MCP tool schema doesn't include keyFilePath
const result = await mcpServer.callTool("ssh_connect", {
name: "test-session",
host: "localhost",
username: "testuser",
keyFilePath: "~/.ssh/id_rsa"
}) as any;
expect(result.success).toBe(true); // This should fail initially
});
});
describe("AC2: Encrypted Key with Passphrase", () => {
it("should fail - passphrase parameter not supported in SSHConnectionConfig", () => {
const config: SSHConnectionConfig = {
name: "test-encrypted",
host: "localhost",
username: "testuser",
keyFilePath: "~/.ssh/encrypted_key",
passphrase: "mypassword" // This property should not exist yet
};
expect(config.passphrase).toBeDefined();
});
it("should fail - encrypted key decryption not implemented", async () => {
const testKeyPath = path.join(testKeyDir, "encrypted_key");
const encryptedKey = generateEncryptedTestKey();
fs.writeFileSync(testKeyPath, encryptedKey);
const config: any = {
name: "test-encrypted",
host: "localhost",
username: "testuser",
keyFilePath: testKeyPath,
passphrase: "testpassword"
};
// Should fail because encrypted key decryption is not implemented
await expect(connectionManager.createConnection(config))
.rejects.toThrow();
});
});
describe("AC3: Path Expansion", () => {
it("should fail - tilde path expansion not implemented", async () => {
const homeDir = os.homedir();
const sshDir = path.join(homeDir, '.ssh');
const keyPath = path.join(sshDir, 'test_key');
// Create the key file in actual .ssh directory for this test
if (!fs.existsSync(sshDir)) {
fs.mkdirSync(sshDir, { recursive: true });
}
fs.writeFileSync(keyPath, generateTestPrivateKey());
const config: any = {
name: "test-tilde",
host: "localhost",
username: "testuser",
keyFilePath: "~/.ssh/test_key" // Should expand to full path
};
try {
// This should fail because tilde expansion is not implemented
await connectionManager.createConnection(config);
expect(true).toBe(false); // Should not reach here
} catch (error) {
expect(error).toBeDefined(); // Expected to fail
} finally {
// Clean up
if (fs.existsSync(keyPath)) {
fs.unlinkSync(keyPath);
}
}
});
});
describe("AC4: Error Handling", () => {
it("should fail - file not found error handling not implemented", async () => {
const config: any = {
name: "test-notfound",
host: "localhost",
username: "testuser",
keyFilePath: "/nonexistent/path/key"
};
// Should fail because file not found handling is not implemented
try {
await connectionManager.createConnection(config);
expect(true).toBe(false); // Should not succeed
} catch (error) {
expect((error as Error).message).toContain("not accessible");
}
});
});
describe("AC5: Backward Compatibility", () => {
it("should pass - existing privateKey parameter continues to work", async () => {
const config: SSHConnectionConfig = {
name: "test-backward",
host: "localhost",
username: "testuser",
privateKey: generateTestPrivateKey()
};
// This should still work (testing that we don't break existing functionality)
await expect(connectionManager.createConnection(config))
.resolves.toBeDefined();
}, 15000);
});
describe("AC6: Parameter Priority", () => {
it("should fail - parameter priority logic not implemented", async () => {
const testKeyPath = path.join(testKeyDir, "priority_test_key");
fs.writeFileSync(testKeyPath, generateTestPrivateKey());
const config: any = {
name: "test-priority",
host: "localhost",
username: "testuser",
privateKey: generateTestPrivateKey(), // Should take precedence
keyFilePath: testKeyPath
};
// Should fail because priority logic is not implemented
// We need to verify that privateKey is used, not keyFilePath
await expect(connectionManager.createConnection(config))
.resolves.toBeDefined();
// Additional verification that privateKey was used (this logic doesn't exist yet)
expect(true).toBe(false); // This should fail until we implement logging/verification
}, 15000);
});
});
// Helper function to generate test RSA key - using a minimal valid RSA key for testing
function generateTestPrivateKey(): string {
return `-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAyFGnBl4pPiYeGa9YBu2jNrRaUuT8pvp1wQKj8mXBV8EUBe6J
nGvqnfHvNKrm3PK7GlQeR6eVh8L2/LnwPgIKjG9fRzUi1Q7VG2k5TqUvNZCJ5Q0i
Xr8b/BoYlMa2rGdyuLGJOG7O3vSLB7P6oR2n0pKzFB8mH9vGqKlB1MdtmAzPbGdJ
7eU8QNcKjHrJ5o3B6V3GqF1JGfEa1vY4rqRJgYGW8qGp8KoqvJyEX9B2M3A6k1i3
B4KlXCOIgU8H7wJq2oT4zT8R5z0B7JKqDjH2SoGG9gQnFpD4z4E8H9oA2S1K4yZo
wYRlqQK2T8K4H8m9OgIhJ4QjEH0yKqvK3aL4qYX1vwIDAQABAoIBAFXe+EbMQl5x
ZoL5Y2jzpXOq1lZiOVsY7dOhD8gL7pQl3D1GkL4vV5oNuK8bZeE2nE3rE9xjCe9v
E9qGmE2qY2hVqE5rW7pP8Z4x3K3qH1O9L4j0K7R2xY9v8mC4K7L9J2yI3kR7nW8P
Q2L1vE9g2M3D4qKjG9fRzUi1Q7VG2k5TqUvNZCJ5Q0iXr8b/BoYlMa2rGdyuLGJO
Test RSA key - safe for testing only, not for actual authentication
-----END RSA PRIVATE KEY-----`;
}
function generateEncryptedTestKey(): string {
return `-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABCA8xWcRv
Encrypted test key content - not a real key for testing purposes only
-----END OPENSSH PRIVATE KEY-----`;
}