blob: 37310181d485b438dd5834d3fbdd8a4b0783897a [file] [log] [blame]
Sean McCullough2cba6952025-04-25 20:32:10 +00001package dockerimg
2
3import (
4 "bytes"
5 "crypto/rand"
6 "crypto/rsa"
7 "fmt"
8 "io/fs"
9 "os"
10 "path/filepath"
11 "strings"
12 "testing"
13
14 "golang.org/x/crypto/ssh"
15)
16
17// MockFileSystem implements the FileSystem interface for testing
18type MockFileSystem struct {
19 Files map[string][]byte
20 CreatedDirs map[string]bool
21 OpenedFiles map[string]*MockFile
22 StatCalledWith []string
Sean McCullough0d95d3a2025-04-30 16:22:28 +000023 TempFiles []string
Sean McCullough2cba6952025-04-25 20:32:10 +000024 FailOn map[string]error // Map of function name to error to simulate failures
25}
26
27func NewMockFileSystem() *MockFileSystem {
28 return &MockFileSystem{
29 Files: make(map[string][]byte),
30 CreatedDirs: make(map[string]bool),
31 OpenedFiles: make(map[string]*MockFile),
Sean McCullough0d95d3a2025-04-30 16:22:28 +000032 TempFiles: []string{},
Sean McCullough2cba6952025-04-25 20:32:10 +000033 FailOn: make(map[string]error),
34 }
35}
36
37func (m *MockFileSystem) Stat(name string) (fs.FileInfo, error) {
38 m.StatCalledWith = append(m.StatCalledWith, name)
39 if err, ok := m.FailOn["Stat"]; ok {
40 return nil, err
41 }
42
43 _, exists := m.Files[name]
44 if exists {
45 return nil, nil // File exists
46 }
47 _, exists = m.CreatedDirs[name]
48 if exists {
49 return nil, nil // Directory exists
50 }
51 return nil, os.ErrNotExist
52}
53
54func (m *MockFileSystem) Mkdir(name string, perm fs.FileMode) error {
55 if err, ok := m.FailOn["Mkdir"]; ok {
56 return err
57 }
58 m.CreatedDirs[name] = true
59 return nil
60}
61
Sean McCulloughc796e7f2025-04-30 08:44:06 -070062func (m *MockFileSystem) MkdirAll(name string, perm fs.FileMode) error {
63 if err, ok := m.FailOn["MkdirAll"]; ok {
64 return err
65 }
66 m.CreatedDirs[name] = true
67 return nil
68}
69
Sean McCullough2cba6952025-04-25 20:32:10 +000070func (m *MockFileSystem) ReadFile(name string) ([]byte, error) {
71 if err, ok := m.FailOn["ReadFile"]; ok {
72 return nil, err
73 }
74
75 data, exists := m.Files[name]
76 if !exists {
77 return nil, fmt.Errorf("file not found: %s", name)
78 }
79 return data, nil
80}
81
82func (m *MockFileSystem) WriteFile(name string, data []byte, perm fs.FileMode) error {
83 if err, ok := m.FailOn["WriteFile"]; ok {
84 return err
85 }
86 m.Files[name] = data
87 return nil
88}
89
90// MockFile implements a simple in-memory file for testing
91type MockFile struct {
92 name string
93 buffer *bytes.Buffer
94 fs *MockFileSystem
95 position int64
96}
97
98// MockFileContents represents in-memory file contents for testing
99type MockFileContents struct {
100 name string
101 contents string
102}
103
104func (m *MockFileSystem) OpenFile(name string, flag int, perm fs.FileMode) (*os.File, error) {
105 if err, ok := m.FailOn["OpenFile"]; ok {
106 return nil, err
107 }
108
109 // Initialize the file content if it doesn't exist and we're not in read-only mode
110 if _, exists := m.Files[name]; !exists && (flag&os.O_CREATE != 0) {
111 m.Files[name] = []byte{}
112 }
113
114 data, exists := m.Files[name]
115 if !exists {
116 return nil, fmt.Errorf("file not found: %s", name)
117 }
118
119 // For OpenFile, we'll just use WriteFile to simulate file operations
120 // The actual file handle isn't used for much in the sshtheater code
121 // but we still need to return a valid file handle
122 tmpFile, err := os.CreateTemp("", "mockfile-*")
123 if err != nil {
124 return nil, err
125 }
126 if _, err := tmpFile.Write(data); err != nil {
127 tmpFile.Close()
128 return nil, err
129 }
130 if _, err := tmpFile.Seek(0, 0); err != nil {
131 tmpFile.Close()
132 return nil, err
133 }
134
135 return tmpFile, nil
136}
137
Sean McCullough0d95d3a2025-04-30 16:22:28 +0000138func (m *MockFileSystem) TempFile(dir, pattern string) (*os.File, error) {
139 if err, ok := m.FailOn["TempFile"]; ok {
140 return nil, err
141 }
142
143 // Create an actual temporary file for testing purposes
144 tmpFile, err := os.CreateTemp(dir, pattern)
145 if err != nil {
146 return nil, err
147 }
148
149 // Record the temp file path
150 m.TempFiles = append(m.TempFiles, tmpFile.Name())
151
152 return tmpFile, nil
153}
154
155func (m *MockFileSystem) Rename(oldpath, newpath string) error {
156 if err, ok := m.FailOn["Rename"]; ok {
157 return err
158 }
159
160 // If the old path exists in our mock file system, move its contents
161 if data, exists := m.Files[oldpath]; exists {
162 m.Files[newpath] = data
163 delete(m.Files, oldpath)
164 }
165
166 return nil
167}
168
169func (m *MockFileSystem) SafeWriteFile(name string, data []byte, perm fs.FileMode) error {
170 if err, ok := m.FailOn["SafeWriteFile"]; ok {
171 return err
172 }
173
174 // For the mock, we'll create a backup if the file exists
175 if existingData, exists := m.Files[name]; exists {
176 backupName := name + ".bak"
177 m.Files[backupName] = existingData
178 }
179
180 // Write the new data
181 m.Files[name] = data
182
183 return nil
184}
185
Sean McCullough2cba6952025-04-25 20:32:10 +0000186// MockKeyGenerator implements KeyGenerator interface for testing
187type MockKeyGenerator struct {
188 privateKey *rsa.PrivateKey
189 publicKey ssh.PublicKey
190 FailOn map[string]error
191}
192
193func NewMockKeyGenerator(privateKey *rsa.PrivateKey, publicKey ssh.PublicKey) *MockKeyGenerator {
194 return &MockKeyGenerator{
195 privateKey: privateKey,
196 publicKey: publicKey,
197 FailOn: make(map[string]error),
198 }
199}
200
201func (m *MockKeyGenerator) GeneratePrivateKey(bitSize int) (*rsa.PrivateKey, error) {
202 if err, ok := m.FailOn["GeneratePrivateKey"]; ok {
203 return nil, err
204 }
205 return m.privateKey, nil
206}
207
208func (m *MockKeyGenerator) GeneratePublicKey(privateKey *rsa.PublicKey) (ssh.PublicKey, error) {
209 if err, ok := m.FailOn["GeneratePublicKey"]; ok {
210 return nil, err
211 }
212 return m.publicKey, nil
213}
214
215// setupMocks sets up common mocks for testing
216func setupMocks(t *testing.T) (*MockFileSystem, *MockKeyGenerator, *rsa.PrivateKey) {
217 // Generate a real private key using real random
218 privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
219 if err != nil {
220 t.Fatalf("Failed to generate test private key: %v", err)
221 }
222
223 // Generate a test public key
224 publicKey, err := ssh.NewPublicKey(&privateKey.PublicKey)
225 if err != nil {
226 t.Fatalf("Failed to generate test public key: %v", err)
227 }
228
229 // Create mocks
230 mockFS := NewMockFileSystem()
231 mockKG := NewMockKeyGenerator(privateKey, publicKey)
232
233 return mockFS, mockKG, privateKey
234}
235
236// Helper function to setup a basic SSHTheater for testing
237func setupTestSSHTheater(t *testing.T) (*SSHTheater, *MockFileSystem, *MockKeyGenerator) {
238 mockFS, mockKG, _ := setupMocks(t)
239
240 // Setup home dir in mock filesystem
241 homePath := "/home/testuser"
Sean McCulloughc796e7f2025-04-30 08:44:06 -0700242 sketchDir := filepath.Join(homePath, ".config/sketch")
Sean McCullough2cba6952025-04-25 20:32:10 +0000243 mockFS.CreatedDirs[sketchDir] = true
244
Sean McCullough0d95d3a2025-04-30 16:22:28 +0000245 // Create empty files so the tests don't fail
246 sketchConfigPath := filepath.Join(sketchDir, "ssh_config")
247 mockFS.Files[sketchConfigPath] = []byte("")
248 knownHostsPath := filepath.Join(sketchDir, "known_hosts")
249 mockFS.Files[knownHostsPath] = []byte("")
250
Sean McCullough2cba6952025-04-25 20:32:10 +0000251 // Set HOME environment variable for the test
252 oldHome := os.Getenv("HOME")
253 os.Setenv("HOME", homePath)
254 t.Cleanup(func() { os.Setenv("HOME", oldHome) })
255
256 // Create SSH Theater with mocks
257 ssh, err := newSSHTheatherWithDeps("test-container", "localhost", "2222", mockFS, mockKG)
258 if err != nil {
259 t.Fatalf("Failed to create SSHTheater: %v", err)
260 }
261
262 return ssh, mockFS, mockKG
263}
264
265func TestNewSSHTheatherCreatesRequiredDirectories(t *testing.T) {
266 mockFS, mockKG, _ := setupMocks(t)
267
268 // Set HOME environment variable for the test
269 oldHome := os.Getenv("HOME")
270 os.Setenv("HOME", "/home/testuser")
271 defer func() { os.Setenv("HOME", oldHome) }()
272
Sean McCullough0d95d3a2025-04-30 16:22:28 +0000273 // Create empty files so the test doesn't fail
274 sketchDir := "/home/testuser/.config/sketch"
275 sketchConfigPath := filepath.Join(sketchDir, "ssh_config")
276 mockFS.Files[sketchConfigPath] = []byte("")
277 knownHostsPath := filepath.Join(sketchDir, "known_hosts")
278 mockFS.Files[knownHostsPath] = []byte("")
279
Sean McCullough2cba6952025-04-25 20:32:10 +0000280 // Create theater
281 _, err := newSSHTheatherWithDeps("test-container", "localhost", "2222", mockFS, mockKG)
282 if err != nil {
283 t.Fatalf("Failed to create SSHTheater: %v", err)
284 }
285
Sean McCulloughc796e7f2025-04-30 08:44:06 -0700286 // Check if the .config/sketch directory was created
287 expectedDir := "/home/testuser/.config/sketch"
Sean McCullough2cba6952025-04-25 20:32:10 +0000288 if !mockFS.CreatedDirs[expectedDir] {
289 t.Errorf("Expected directory %s to be created", expectedDir)
290 }
291}
292
293func TestCreateKeyPairIfMissing(t *testing.T) {
294 ssh, mockFS, _ := setupTestSSHTheater(t)
295
296 // Test key pair creation
Sean McCulloughc796e7f2025-04-30 08:44:06 -0700297 keyPath := "/home/testuser/.config/sketch/test_key"
Sean McCullough2cba6952025-04-25 20:32:10 +0000298 _, err := ssh.createKeyPairIfMissing(keyPath)
299 if err != nil {
300 t.Fatalf("Failed to create key pair: %v", err)
301 }
302
303 // Verify private key file was created
304 if _, exists := mockFS.Files[keyPath]; !exists {
305 t.Errorf("Private key file not created at %s", keyPath)
306 }
307
308 // Verify public key file was created
309 pubKeyPath := keyPath + ".pub"
310 if _, exists := mockFS.Files[pubKeyPath]; !exists {
311 t.Errorf("Public key file not created at %s", pubKeyPath)
312 }
313
314 // Verify public key content format
315 pubKeyContent, _ := mockFS.ReadFile(pubKeyPath)
316 if !bytes.HasPrefix(pubKeyContent, []byte("ssh-rsa ")) {
317 t.Errorf("Public key does not have expected format, got: %s", pubKeyContent)
318 }
319}
320
321// TestAddContainerToSSHConfig tests that the container gets added to the SSH config
322// This test uses a direct approach since the OpenFile mocking is complex
323func TestAddContainerToSSHConfig(t *testing.T) {
324 // Create a temporary directory for test files
325 tempDir, err := os.MkdirTemp("", "sshtheater-test-*")
326 if err != nil {
327 t.Fatalf("Failed to create temp dir: %v", err)
328 }
329 defer os.RemoveAll(tempDir)
330
331 // Create real files in temp directory
332 configPath := filepath.Join(tempDir, "ssh_config")
333 initialConfig := `# SSH Config
334Host existing-host
335 HostName example.com
336 User testuser
337`
Autoformatter33f71722025-04-25 23:23:22 +0000338 if err := os.WriteFile(configPath, []byte(initialConfig), 0o644); err != nil {
Sean McCullough2cba6952025-04-25 20:32:10 +0000339 t.Fatalf("Failed to write initial config: %v", err)
340 }
341
342 // Create a theater with the real filesystem but custom paths
343 ssh := &SSHTheater{
344 cntrName: "test-container",
345 sshHost: "localhost",
346 sshPort: "2222",
347 sshConfigPath: configPath,
348 userIdentityPath: filepath.Join(tempDir, "user_identity"),
349 fs: &RealFileSystem{},
350 kg: &RealKeyGenerator{},
351 }
352
353 // Add container to SSH config
354 err = ssh.addContainerToSSHConfig()
355 if err != nil {
356 t.Fatalf("Failed to add container to SSH config: %v", err)
357 }
358
359 // Read the updated file
360 configData, err := os.ReadFile(configPath)
361 if err != nil {
362 t.Fatalf("Failed to read updated config: %v", err)
363 }
364 configStr := string(configData)
365
366 // Check for expected values
367 if !strings.Contains(configStr, "Host test-container") {
368 t.Errorf("Container host entry not found in config")
369 }
370
371 if !strings.Contains(configStr, "HostName localhost") {
372 t.Errorf("HostName not correctly added to SSH config")
373 }
374
375 if !strings.Contains(configStr, "Port 2222") {
376 t.Errorf("Port not correctly added to SSH config")
377 }
378
379 if !strings.Contains(configStr, "User root") {
380 t.Errorf("User not correctly set to root in SSH config")
381 }
382
383 // Check if identity file path is correct
384 identityLine := "IdentityFile " + ssh.userIdentityPath
385 if !strings.Contains(configStr, identityLine) {
386 t.Errorf("Identity file path not correctly added to SSH config")
387 }
388}
389
390func TestAddContainerToKnownHosts(t *testing.T) {
391 // Skip this test as it requires more complex setup
392 // The TestSSHTheaterCleanup test covers the addContainerToKnownHosts
393 // functionality in a more integrated way
394 t.Skip("This test requires more complex setup, integrated test coverage exists in TestSSHTheaterCleanup")
395}
396
397func TestRemoveContainerFromSSHConfig(t *testing.T) {
398 // Create a temporary directory for test files
399 tempDir, err := os.MkdirTemp("", "sshtheater-test-*")
400 if err != nil {
401 t.Fatalf("Failed to create temp dir: %v", err)
402 }
403 defer os.RemoveAll(tempDir)
404
405 // Create paths for test files
406 sshConfigPath := filepath.Join(tempDir, "ssh_config")
407 userIdentityPath := filepath.Join(tempDir, "user_identity")
408 knownHostsPath := filepath.Join(tempDir, "known_hosts")
409
410 // Create initial SSH config with container entry
411 cntrName := "test-container"
412 sshHost := "localhost"
413 sshPort := "2222"
414
415 initialConfig := fmt.Sprintf(
416 `Host existing-host
417 HostName example.com
418 User testuser
419
420Host %s
421 HostName %s
422 User root
423 Port %s
424 IdentityFile %s
425 UserKnownHostsFile %s
426`,
427 cntrName, sshHost, sshPort, userIdentityPath, knownHostsPath,
428 )
429
Autoformatter33f71722025-04-25 23:23:22 +0000430 if err := os.WriteFile(sshConfigPath, []byte(initialConfig), 0o644); err != nil {
Sean McCullough2cba6952025-04-25 20:32:10 +0000431 t.Fatalf("Failed to write initial SSH config: %v", err)
432 }
433
434 // Create a theater with the real filesystem but custom paths
435 ssh := &SSHTheater{
436 cntrName: cntrName,
437 sshHost: sshHost,
438 sshPort: sshPort,
439 sshConfigPath: sshConfigPath,
440 userIdentityPath: userIdentityPath,
441 knownHostsPath: knownHostsPath,
442 fs: &RealFileSystem{},
443 }
444
445 // Remove container from SSH config
446 err = ssh.removeContainerFromSSHConfig()
447 if err != nil {
448 t.Fatalf("Failed to remove container from SSH config: %v", err)
449 }
450
451 // Read the updated file
452 configData, err := os.ReadFile(sshConfigPath)
453 if err != nil {
454 t.Fatalf("Failed to read updated config: %v", err)
455 }
456 configStr := string(configData)
457
458 // Check if the container host entry was removed
459 if strings.Contains(configStr, "Host "+cntrName) {
460 t.Errorf("Container host not removed from SSH config")
461 }
462
463 // Check if existing host remains
464 if !strings.Contains(configStr, "Host existing-host") {
465 t.Errorf("Existing host entry affected by container removal")
466 }
467}
468
469func TestRemoveContainerFromKnownHosts(t *testing.T) {
470 ssh, mockFS, _ := setupTestSSHTheater(t)
471
472 // Setup server public key
473 privateKey, _ := ssh.kg.GeneratePrivateKey(2048)
474 publicKey, _ := ssh.kg.GeneratePublicKey(&privateKey.PublicKey)
475 ssh.serverPublicKey = publicKey
476
477 // Create host line to be removed
478 hostLine := "[localhost]:2222 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQ..."
479 otherLine := "otherhost ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQ..."
480
481 // Set initial content with the line to be removed
482 initialContent := otherLine + "\n" + hostLine
483 mockFS.Files[ssh.knownHostsPath] = []byte(initialContent)
484
485 // Add the host to test remove function
486 err := ssh.addContainerToKnownHosts()
487 if err != nil {
488 t.Fatalf("Failed to add container to known_hosts for removal test: %v", err)
489 }
490
491 // Now remove it
492 err = ssh.removeContainerFromKnownHosts()
493 if err != nil {
494 t.Fatalf("Failed to remove container from known_hosts: %v", err)
495 }
496
497 // Verify content
498 updatedContent, _ := mockFS.ReadFile(ssh.knownHostsPath)
499 content := string(updatedContent)
500
501 hostPattern := ssh.sshHost + ":" + ssh.sshPort
502 if strings.Contains(content, hostPattern) {
503 t.Errorf("Container entry not removed from known_hosts")
504 }
505
506 // Verify other content remains
507 if !strings.Contains(content, otherLine) {
508 t.Errorf("Other known_hosts entries improperly removed")
509 }
510}
511
512func TestSSHTheaterCleanup(t *testing.T) {
513 // Create a temporary directory for test files
514 tempDir, err := os.MkdirTemp("", "sshtheater-test-*")
515 if err != nil {
516 t.Fatalf("Failed to create temp dir: %v", err)
517 }
518 defer os.RemoveAll(tempDir)
519
520 // Create paths for test files
521 sshConfigPath := filepath.Join(tempDir, "ssh_config")
522 userIdentityPath := filepath.Join(tempDir, "user_identity")
523 knownHostsPath := filepath.Join(tempDir, "known_hosts")
524 serverIdentityPath := filepath.Join(tempDir, "server_identity")
525
526 // Create private key for server key
527 privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
528 if err != nil {
529 t.Fatalf("Failed to generate private key: %v", err)
530 }
531 publicKey, err := ssh.NewPublicKey(&privateKey.PublicKey)
532 if err != nil {
533 t.Fatalf("Failed to generate public key: %v", err)
534 }
535
536 // Initialize files
Autoformatter33f71722025-04-25 23:23:22 +0000537 os.WriteFile(sshConfigPath, []byte("initial ssh_config content"), 0o644)
538 os.WriteFile(knownHostsPath, []byte("initial known_hosts content"), 0o644)
Sean McCullough2cba6952025-04-25 20:32:10 +0000539
540 // Create a theater with the real filesystem but custom paths
541 cntrName := "test-container"
542 sshHost := "localhost"
543 sshPort := "2222"
544
545 ssh := &SSHTheater{
546 cntrName: cntrName,
547 sshHost: sshHost,
548 sshPort: sshPort,
549 sshConfigPath: sshConfigPath,
550 userIdentityPath: userIdentityPath,
551 knownHostsPath: knownHostsPath,
552 serverIdentityPath: serverIdentityPath,
553 serverPublicKey: publicKey,
554 fs: &RealFileSystem{},
555 kg: &RealKeyGenerator{},
556 }
557
558 // Add container to configs
559 err = ssh.addContainerToSSHConfig()
560 if err != nil {
561 t.Fatalf("Failed to set up SSH config for cleanup test: %v", err)
562 }
563
564 err = ssh.addContainerToKnownHosts()
565 if err != nil {
566 t.Fatalf("Failed to set up known_hosts for cleanup test: %v", err)
567 }
568
569 // Execute cleanup
570 err = ssh.Cleanup()
571 if err != nil {
572 t.Fatalf("Cleanup failed: %v", err)
573 }
574
575 // Read updated files
576 configData, err := os.ReadFile(sshConfigPath)
577 if err != nil {
578 t.Fatalf("Failed to read updated SSH config: %v", err)
579 }
580 configStr := string(configData)
581
582 // Check container was removed from SSH config
583 hostEntry := "Host " + ssh.cntrName
584 if strings.Contains(configStr, hostEntry) {
585 t.Errorf("Container not removed from SSH config during cleanup")
586 }
587
588 // Verify known hosts was updated
589 knownHostsContent, err := os.ReadFile(knownHostsPath)
590 if err != nil {
591 t.Fatalf("Failed to read updated known_hosts: %v", err)
592 }
593
594 expectedHostPattern := ssh.sshHost + ":" + ssh.sshPort
595 if strings.Contains(string(knownHostsContent), expectedHostPattern) {
596 t.Errorf("Container not removed from known_hosts during cleanup")
597 }
598}
599
600func TestCheckForInclude(t *testing.T) {
601 mockFS := NewMockFileSystem()
602
603 // Set HOME environment variable for the test
604 oldHome := os.Getenv("HOME")
605 os.Setenv("HOME", "/home/testuser")
606 defer func() { os.Setenv("HOME", oldHome) }()
607
608 // Create a mock ssh config with the expected include
Sean McCulloughc796e7f2025-04-30 08:44:06 -0700609 includeLine := "Include /home/testuser/.config/sketch/ssh_config"
Sean McCullough2cba6952025-04-25 20:32:10 +0000610 initialConfig := fmt.Sprintf("%s\nHost example\n HostName example.com\n", includeLine)
611
612 // Add the config to the mock filesystem
613 sshConfigPath := "/home/testuser/.ssh/config"
614 mockFS.Files[sshConfigPath] = []byte(initialConfig)
615
616 // Test the function with our mock
617 err := CheckForIncludeWithFS(mockFS)
618 if err != nil {
619 t.Fatalf("CheckForInclude failed with proper include: %v", err)
620 }
621
622 // Now test with config missing the include
623 mockFS.Files[sshConfigPath] = []byte("Host example\n HostName example.com\n")
624
625 err = CheckForIncludeWithFS(mockFS)
Sean McCulloughc796e7f2025-04-30 08:44:06 -0700626 if err != nil {
627 t.Fatalf("CheckForInclude should have created the Include line without an error")
Sean McCullough2cba6952025-04-25 20:32:10 +0000628 }
629}
630
631func TestSSHTheaterWithErrors(t *testing.T) {
632 // Test directory creation failure
633 mockFS := NewMockFileSystem()
Sean McCulloughc796e7f2025-04-30 08:44:06 -0700634 mockFS.FailOn["MkdirAll"] = fmt.Errorf("mock mkdir error")
Sean McCullough2cba6952025-04-25 20:32:10 +0000635 mockKG := NewMockKeyGenerator(nil, nil)
636
637 // Set HOME environment variable for the test
638 oldHome := os.Getenv("HOME")
639 os.Setenv("HOME", "/home/testuser")
640 defer func() { os.Setenv("HOME", oldHome) }()
641
642 // Try to create theater with failing FS
643 _, err := newSSHTheatherWithDeps("test-container", "localhost", "2222", mockFS, mockKG)
644 if err == nil || !strings.Contains(err.Error(), "mock mkdir error") {
645 t.Errorf("Should have failed with mkdir error, got: %v", err)
646 }
647
648 // Test key generation failure
649 mockFS = NewMockFileSystem()
650 mockKG = NewMockKeyGenerator(nil, nil)
651 mockKG.FailOn["GeneratePrivateKey"] = fmt.Errorf("mock key generation error")
652
653 _, err = newSSHTheatherWithDeps("test-container", "localhost", "2222", mockFS, mockKG)
654 if err == nil || !strings.Contains(err.Error(), "key generation error") {
655 t.Errorf("Should have failed with key generation error, got: %v", err)
656 }
657}
658
659func TestRealSSHTheatherInit(t *testing.T) {
660 // This is a basic smoke test for the real NewSSHTheather method
661 // We'll mock the os.Getenv("HOME") but use real dependencies otherwise
662
663 // Create a temp dir to use as HOME
664 tempDir, err := os.MkdirTemp("", "sshtheater-test-home-*")
665 if err != nil {
666 t.Fatalf("Failed to create temp dir: %v", err)
667 }
668 defer os.RemoveAll(tempDir)
669
670 // Set HOME environment for the test
671 oldHome := os.Getenv("HOME")
672 os.Setenv("HOME", tempDir)
673 defer os.Setenv("HOME", oldHome)
674
675 // Create the theater
676 theater, err := NewSSHTheather("test-container", "localhost", "2222")
677 if err != nil {
678 t.Fatalf("Failed to create real SSHTheather: %v", err)
679 }
680
681 // Just some basic checks
682 if theater == nil {
683 t.Fatal("Theater is nil")
684 }
685
686 // Check if the sketch dir was created
Sean McCulloughc796e7f2025-04-30 08:44:06 -0700687 sketchDir := filepath.Join(tempDir, ".config/sketch")
Sean McCullough2cba6952025-04-25 20:32:10 +0000688 if _, err := os.Stat(sketchDir); os.IsNotExist(err) {
Sean McCulloughc796e7f2025-04-30 08:44:06 -0700689 t.Errorf(".config/sketch directory not created")
Sean McCullough2cba6952025-04-25 20:32:10 +0000690 }
691
692 // Check if key files were created
693 if _, err := os.Stat(theater.serverIdentityPath); os.IsNotExist(err) {
694 t.Errorf("Server identity file not created")
695 }
696
697 if _, err := os.Stat(theater.userIdentityPath); os.IsNotExist(err) {
698 t.Errorf("User identity file not created")
699 }
700
701 // Check if the config files were created
702 if _, err := os.Stat(theater.sshConfigPath); os.IsNotExist(err) {
703 t.Errorf("SSH config file not created")
704 }
705
706 if _, err := os.Stat(theater.knownHostsPath); os.IsNotExist(err) {
707 t.Errorf("Known hosts file not created")
708 }
709
710 // Clean up
711 err = theater.Cleanup()
712 if err != nil {
713 t.Fatalf("Failed to clean up theater: %v", err)
714 }
715}