1+ // Copyright 2025 Lablup Inc. and Jeongkyu Shin
2+ //
3+ // Licensed under the Apache License, Version 2.0 (the "License");
4+ // you may not use this file except in compliance with the License.
5+ // You may obtain a copy of the License at
6+ //
7+ // http://www.apache.org/licenses/LICENSE-2.0
8+ //
9+ // Unless required by applicable law or agreed to in writing, software
10+ // distributed under the License is distributed on an "AS IS" BASIS,
11+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+ // See the License for the specific language governing permissions and
13+ // limitations under the License.
14+
15+ use bssh:: executor:: ParallelExecutor ;
16+ use bssh:: node:: Node ;
17+ use std:: path:: PathBuf ;
18+ use tempfile:: TempDir ;
19+
20+ #[ tokio:: test]
21+ async fn test_upload_nonexistent_file ( ) {
22+ let nodes = vec ! [ Node :: new( "localhost" . to_string( ) , 22 , "user" . to_string( ) ) ] ;
23+ let executor = ParallelExecutor :: new ( nodes, 1 , None ) ;
24+
25+ // Try to upload a file that doesn't exist
26+ let nonexistent_file = PathBuf :: from ( "/this/file/does/not/exist.txt" ) ;
27+ let results = executor. upload_file (
28+ & nonexistent_file,
29+ "/tmp/destination.txt" ,
30+ ) . await ;
31+
32+ // Should complete but with error in results
33+ assert ! ( results. is_ok( ) ) ;
34+ let results = results. unwrap ( ) ;
35+ assert_eq ! ( results. len( ) , 1 ) ;
36+ assert ! ( !results[ 0 ] . is_success( ) ) ;
37+ }
38+
39+ #[ tokio:: test]
40+ async fn test_download_to_invalid_directory ( ) {
41+ let nodes = vec ! [ Node :: new( "localhost" . to_string( ) , 22 , "user" . to_string( ) ) ] ;
42+ let executor = ParallelExecutor :: new ( nodes, 1 , None ) ;
43+
44+ // Try to download to a directory that doesn't exist
45+ let invalid_dir = PathBuf :: from ( "/this/directory/does/not/exist" ) ;
46+ let results = executor. download_file (
47+ "/etc/passwd" ,
48+ & invalid_dir,
49+ ) . await ;
50+
51+ // Should complete but with error in results
52+ assert ! ( results. is_ok( ) ) ;
53+ let results = results. unwrap ( ) ;
54+ assert_eq ! ( results. len( ) , 1 ) ;
55+ assert ! ( !results[ 0 ] . is_success( ) ) ;
56+ }
57+
58+ #[ tokio:: test]
59+ async fn test_connection_to_invalid_host ( ) {
60+ let nodes = vec ! [
61+ Node :: new( "this.host.does.not.exist.invalid" . to_string( ) , 22 , "user" . to_string( ) ) ,
62+ ] ;
63+ let executor = ParallelExecutor :: new ( nodes, 1 , None ) ;
64+
65+ // Try to execute command on invalid host
66+ let results = executor. execute ( "echo test" ) . await ;
67+
68+ assert ! ( results. is_ok( ) ) ;
69+ let results = results. unwrap ( ) ;
70+ assert_eq ! ( results. len( ) , 1 ) ;
71+ assert ! ( !results[ 0 ] . is_success( ) ) ;
72+ }
73+
74+ #[ tokio:: test]
75+ async fn test_connection_to_invalid_port ( ) {
76+ let nodes = vec ! [
77+ Node :: new( "localhost" . to_string( ) , 59999 , "user" . to_string( ) ) , // Invalid port
78+ ] ;
79+ let executor = ParallelExecutor :: new ( nodes, 1 , None ) ;
80+
81+ // Try to execute command on invalid port
82+ let results = executor. execute ( "echo test" ) . await ;
83+
84+ assert ! ( results. is_ok( ) ) ;
85+ let results = results. unwrap ( ) ;
86+ assert_eq ! ( results. len( ) , 1 ) ;
87+ assert ! ( !results[ 0 ] . is_success( ) ) ;
88+ }
89+
90+ #[ tokio:: test]
91+ async fn test_invalid_ssh_key_path ( ) {
92+ let nodes = vec ! [ Node :: new( "localhost" . to_string( ) , 22 , "user" . to_string( ) ) ] ;
93+ let executor = ParallelExecutor :: new (
94+ nodes,
95+ 1 ,
96+ Some ( "/this/key/does/not/exist.pem" . to_string ( ) ) ,
97+ ) ;
98+
99+ let results = executor. execute ( "echo test" ) . await ;
100+
101+ assert ! ( results. is_ok( ) ) ;
102+ let results = results. unwrap ( ) ;
103+ assert_eq ! ( results. len( ) , 1 ) ;
104+ assert ! ( !results[ 0 ] . is_success( ) ) ;
105+ }
106+
107+ #[ tokio:: test]
108+ async fn test_parallel_execution_with_mixed_results ( ) {
109+ let nodes = vec ! [
110+ Node :: new( "localhost" . to_string( ) , 22 , std:: env:: var( "USER" ) . unwrap_or_else( |_| "user" . to_string( ) ) ) ,
111+ Node :: new( "invalid.host.example" . to_string( ) , 22 , "user" . to_string( ) ) ,
112+ Node :: new( "another.invalid.host" . to_string( ) , 22 , "user" . to_string( ) ) ,
113+ ] ;
114+
115+ let executor = ParallelExecutor :: new ( nodes, 3 , None ) ;
116+
117+ let results = executor. execute ( "echo test" ) . await ;
118+
119+ assert ! ( results. is_ok( ) ) ;
120+ let results = results. unwrap ( ) ;
121+ assert_eq ! ( results. len( ) , 3 ) ;
122+
123+ // At least some should fail (the invalid hosts)
124+ let failures = results. iter ( ) . filter ( |r| !r. is_success ( ) ) . count ( ) ;
125+ assert ! ( failures >= 2 ) ;
126+ }
127+
128+ #[ tokio:: test]
129+ async fn test_upload_with_permission_denied ( ) {
130+ let nodes = vec ! [ Node :: new( "localhost" . to_string( ) , 22 , std:: env:: var( "USER" ) . unwrap_or_else( |_| "user" . to_string( ) ) ) ] ;
131+ let executor = ParallelExecutor :: new ( nodes, 1 , None ) ;
132+
133+ // Create a test file
134+ let temp_dir = TempDir :: new ( ) . unwrap ( ) ;
135+ let test_file = temp_dir. path ( ) . join ( "test.txt" ) ;
136+ std:: fs:: write ( & test_file, "test content" ) . unwrap ( ) ;
137+
138+ // Try to upload to a directory without write permissions (root directory)
139+ let results = executor. upload_file (
140+ & test_file,
141+ "/test_file_should_not_be_created.txt" ,
142+ ) . await ;
143+
144+ assert ! ( results. is_ok( ) ) ;
145+ let results = results. unwrap ( ) ;
146+ assert_eq ! ( results. len( ) , 1 ) ;
147+ // This might succeed or fail depending on user permissions
148+ // Just verify it doesn't panic
149+ }
150+
151+ #[ tokio:: test]
152+ async fn test_download_nonexistent_remote_file ( ) {
153+ let nodes = vec ! [ Node :: new( "localhost" . to_string( ) , 22 , std:: env:: var( "USER" ) . unwrap_or_else( |_| "user" . to_string( ) ) ) ] ;
154+ let executor = ParallelExecutor :: new ( nodes, 1 , None ) ;
155+
156+ let temp_dir = TempDir :: new ( ) . unwrap ( ) ;
157+
158+ // Try to download a file that doesn't exist
159+ let results = executor. download_file (
160+ "/this/remote/file/does/not/exist.txt" ,
161+ temp_dir. path ( ) ,
162+ ) . await ;
163+
164+ assert ! ( results. is_ok( ) ) ;
165+ let results = results. unwrap ( ) ;
166+ assert_eq ! ( results. len( ) , 1 ) ;
167+ // Should fail since file doesn't exist
168+ if results[ 0 ] . is_success ( ) {
169+ // If it somehow succeeds (unlikely), just verify it doesn't panic
170+ assert ! ( true ) ;
171+ } else {
172+ assert ! ( !results[ 0 ] . is_success( ) ) ;
173+ }
174+ }
175+
176+ #[ tokio:: test]
177+ async fn test_glob_pattern_with_no_matches ( ) {
178+ let temp_dir = TempDir :: new ( ) . unwrap ( ) ;
179+
180+ // Create a test file that won't match our pattern
181+ std:: fs:: write ( temp_dir. path ( ) . join ( "test.txt" ) , "content" ) . unwrap ( ) ;
182+
183+ let nodes = vec ! [ Node :: new( "localhost" . to_string( ) , 22 , "user" . to_string( ) ) ] ;
184+ let executor = ParallelExecutor :: new ( nodes, 1 , None ) ;
185+
186+ // Try to upload files matching a pattern that has no matches
187+ let pattern = temp_dir. path ( ) . join ( "*.pdf" ) ; // No PDF files exist
188+
189+ // This should handle the error gracefully
190+ let results = executor. upload_file (
191+ & pattern,
192+ "/tmp/" ,
193+ ) . await ;
194+
195+ // The executor should handle this gracefully
196+ assert ! ( results. is_ok( ) ) ;
197+ }
0 commit comments