/ tests / bindings_integration.rs
bindings_integration.rs
  1  use abundantis::source::remote::ProviderManager;
  2  use abundantis::Abundantis;
  3  use ecolog_lsp::analysis::{
  4      DocumentManager, ModuleResolver, QueryEngine, WorkspaceIndex, WorkspaceIndexer,
  5  };
  6  use ecolog_lsp::languages::LanguageRegistry;
  7  use ecolog_lsp::server::config::ConfigManager;
  8  use ecolog_lsp::server::handlers::{compute_diagnostics, handle_hover};
  9  use ecolog_lsp::server::state::ServerState;
 10  use std::fs::{self, File};
 11  use std::io::Write;
 12  use std::sync::Arc;
 13  use std::time::{SystemTime, UNIX_EPOCH};
 14  use tower_lsp::lsp_types::{
 15      HoverParams, Position, TextDocumentIdentifier, TextDocumentPositionParams, Url,
 16  };
 17  
 18  #[tokio::test]
 19  async fn test_bindings_integration() {
 20      let timestamp = SystemTime::now()
 21          .duration_since(UNIX_EPOCH)
 22          .unwrap()
 23          .as_nanos();
 24      let temp_dir = std::env::temp_dir().join(format!("ecolog_integ_test_{}", timestamp));
 25      fs::create_dir_all(&temp_dir).unwrap();
 26  
 27      let env_path = temp_dir.join(".env");
 28      let mut env_file = File::create(&env_path).unwrap();
 29      writeln!(env_file, "DB_URL=postgres://localhost").unwrap();
 30      writeln!(env_file, "API_KEY=secret_key").unwrap();
 31      writeln!(env_file, "JSON_BLOB=some_data").unwrap();
 32  
 33      let mut registry = LanguageRegistry::new();
 34      registry.register(Arc::new(ecolog_lsp::languages::javascript::JavaScript));
 35      registry.register(Arc::new(ecolog_lsp::languages::typescript::TypeScript));
 36      registry.register(Arc::new(ecolog_lsp::languages::python::Python));
 37      let languages = Arc::new(registry);
 38  
 39      let query_engine = Arc::new(QueryEngine::new());
 40      let document_manager = Arc::new(DocumentManager::new(
 41          query_engine.clone(),
 42          languages.clone(),
 43      ));
 44      let config_manager = Arc::new(ConfigManager::new());
 45      let core = Arc::new(
 46          Abundantis::builder()
 47              .root(&temp_dir)
 48              .build()
 49              .await
 50              .expect("Failed to build Abundantis"),
 51      );
 52      let workspace_index = Arc::new(WorkspaceIndex::new());
 53      let module_resolver = Arc::new(ModuleResolver::new(temp_dir.clone()));
 54      let indexer = Arc::new(WorkspaceIndexer::new(
 55          Arc::clone(&workspace_index),
 56          query_engine,
 57          Arc::clone(&languages),
 58          temp_dir.clone(),
 59      ));
 60  
 61      let providers_config = abundantis::config::ProvidersConfig::default();
 62      let provider_manager = Arc::new(ProviderManager::new(providers_config));
 63  
 64      let state = ServerState::new(
 65          document_manager,
 66          languages,
 67          core,
 68          config_manager,
 69          workspace_index,
 70          indexer,
 71          module_resolver,
 72          provider_manager,
 73      );
 74  
 75      let js_path = temp_dir.join("bracket.js");
 76      let js_content = r#"
 77  const a = process.env['JSON_BLOB'];
 78  a;
 79  "#;
 80      let mut f = File::create(&js_path).unwrap();
 81      write!(f, "{}", js_content).unwrap();
 82      let uri_js = Url::from_file_path(&js_path).unwrap();
 83  
 84      state
 85          .document_manager
 86          .open(
 87              uri_js.clone(),
 88              "javascript".to_string(),
 89              js_content.to_string(),
 90              0,
 91          )
 92          .await;
 93      tokio::time::sleep(std::time::Duration::from_millis(200)).await;
 94  
 95      let hover = handle_hover(
 96          HoverParams {
 97              text_document_position_params: TextDocumentPositionParams {
 98                  text_document: TextDocumentIdentifier {
 99                      uri: uri_js.clone(),
100                  },
101                  position: Position::new(2, 0),
102              },
103              work_done_progress_params: Default::default(),
104          },
105          &state,
106      )
107      .await;
108  
109      assert!(hover.is_some(), "JS Bracket Access Hover failed");
110      assert!(format!("{:?}", hover.unwrap()).contains("JSON_BLOB"));
111  
112      let ts_path = temp_dir.join("destruct.ts");
113      let ts_content = r#"
114  const { API_KEY } = process.env;
115  API_KEY;
116  "#;
117      let mut f = File::create(&ts_path).unwrap();
118      write!(f, "{}", ts_content).unwrap();
119      let uri_ts = Url::from_file_path(&ts_path).unwrap();
120  
121      state
122          .document_manager
123          .open(
124              uri_ts.clone(),
125              "typescript".to_string(),
126              ts_content.to_string(),
127              0,
128          )
129          .await;
130      tokio::time::sleep(std::time::Duration::from_millis(200)).await;
131  
132      let hover = handle_hover(
133          HoverParams {
134              text_document_position_params: TextDocumentPositionParams {
135                  text_document: TextDocumentIdentifier {
136                      uri: uri_ts.clone(),
137                  },
138                  position: Position::new(2, 0),
139              },
140              work_done_progress_params: Default::default(),
141          },
142          &state,
143      )
144      .await;
145  
146      assert!(hover.is_some(), "TS Destructuring Hover failed");
147      assert!(format!("{:?}", hover.unwrap()).contains("API_KEY"));
148  
149      let ts_bracket_path = temp_dir.join("bracket.ts");
150      let ts_bracket_content = r#"
151  const b = process.env['JSON_BLOB'];
152  b;
153  "#;
154      let mut f = File::create(&ts_bracket_path).unwrap();
155      write!(f, "{}", ts_bracket_content).unwrap();
156      let uri_ts_bracket = Url::from_file_path(&ts_bracket_path).unwrap();
157  
158      state
159          .document_manager
160          .open(
161              uri_ts_bracket.clone(),
162              "typescript".to_string(),
163              ts_bracket_content.to_string(),
164              0,
165          )
166          .await;
167      tokio::time::sleep(std::time::Duration::from_millis(200)).await;
168  
169      let hover = handle_hover(
170          HoverParams {
171              text_document_position_params: TextDocumentPositionParams {
172                  text_document: TextDocumentIdentifier {
173                      uri: uri_ts_bracket.clone(),
174                  },
175                  position: Position::new(2, 0),
176              },
177              work_done_progress_params: Default::default(),
178          },
179          &state,
180      )
181      .await;
182  
183      assert!(hover.is_some(), "TS Bracket Access Hover failed");
184      assert!(format!("{:?}", hover.unwrap()).contains("JSON_BLOB"));
185  
186      let scope_path = temp_dir.join("scope.js");
187      let scope_content = r#"
188  function test() {
189    const secret = process.env.API_KEY;
190  }
191  secret;
192  "#;
193      let mut f = File::create(&scope_path).unwrap();
194      write!(f, "{}", scope_content).unwrap();
195      let uri_scope = Url::from_file_path(&scope_path).unwrap();
196  
197      state
198          .document_manager
199          .open(
200              uri_scope.clone(),
201              "javascript".to_string(),
202              scope_content.to_string(),
203              0,
204          )
205          .await;
206      tokio::time::sleep(std::time::Duration::from_millis(200)).await;
207  
208      let hover = handle_hover(
209          HoverParams {
210              text_document_position_params: TextDocumentPositionParams {
211                  text_document: TextDocumentIdentifier {
212                      uri: uri_scope.clone(),
213                  },
214                  position: Position::new(4, 0),
215              },
216              work_done_progress_params: Default::default(),
217          },
218          &state,
219      )
220      .await;
221  
222      assert!(
223          hover.is_none(),
224          "Scope Isolation Failed: Should not hover out-of-scope variable"
225      );
226  
227      let alias_path = temp_dir.join("alias_msg.js");
228      let alias_content = r#"
229  const env = process.env;
230  env;
231  "#;
232      let mut f = File::create(&alias_path).unwrap();
233      write!(f, "{}", alias_content).unwrap();
234      let uri_alias = Url::from_file_path(&alias_path).unwrap();
235  
236      state
237          .document_manager
238          .open(
239              uri_alias.clone(),
240              "javascript".to_string(),
241              alias_content.to_string(),
242              0,
243          )
244          .await;
245      tokio::time::sleep(std::time::Duration::from_millis(200)).await;
246  
247      let hover_decl = handle_hover(
248          HoverParams {
249              text_document_position_params: TextDocumentPositionParams {
250                  text_document: TextDocumentIdentifier {
251                      uri: uri_alias.clone(),
252                  },
253                  position: Position::new(1, 6),
254              },
255              work_done_progress_params: Default::default(),
256          },
257          &state,
258      )
259      .await;
260  
261      println!("Alias Decl Hover: {:?}", hover_decl);
262      assert!(
263          hover_decl.is_some(),
264          "Expected hover for object alias declaration"
265      );
266      let hover_str = format!("{:?}", hover_decl.unwrap());
267      assert!(
268          hover_str.contains("Environment Object"),
269          "Detailed message should indicate Environment Object, got: {}",
270          hover_str
271      );
272      assert!(
273          !hover_str.contains("(undefined)"),
274          "Should not show (undefined) for object alias"
275      );
276  
277      let _ = fs::remove_dir_all(&temp_dir);
278  }
279  
280  #[tokio::test]
281  async fn test_destructuring_diagnostics() {
282      let timestamp = SystemTime::now()
283          .duration_since(UNIX_EPOCH)
284          .unwrap()
285          .as_nanos();
286      let temp_dir = std::env::temp_dir().join(format!("ecolog_destruct_diag_{}", timestamp));
287      fs::create_dir_all(&temp_dir).unwrap();
288  
289      let env_path = temp_dir.join(".env");
290      let mut env_file = File::create(&env_path).unwrap();
291      writeln!(env_file, "DB_URL=postgres://localhost").unwrap();
292  
293      let mut registry = LanguageRegistry::new();
294      registry.register(Arc::new(ecolog_lsp::languages::javascript::JavaScript));
295      let languages = Arc::new(registry);
296  
297      let query_engine = Arc::new(QueryEngine::new());
298      let document_manager = Arc::new(DocumentManager::new(
299          query_engine.clone(),
300          languages.clone(),
301      ));
302      let config_manager = Arc::new(ConfigManager::new());
303      let core = Arc::new(
304          Abundantis::builder()
305              .root(&temp_dir)
306              .build()
307              .await
308              .expect("Failed to build Abundantis"),
309      );
310      let workspace_index = Arc::new(WorkspaceIndex::new());
311      let module_resolver = Arc::new(ModuleResolver::new(temp_dir.clone()));
312      let indexer = Arc::new(WorkspaceIndexer::new(
313          Arc::clone(&workspace_index),
314          query_engine,
315          Arc::clone(&languages),
316          temp_dir.clone(),
317      ));
318  
319      let providers_config = abundantis::config::ProvidersConfig::default();
320      let provider_manager = Arc::new(ProviderManager::new(providers_config));
321  
322      let state = ServerState::new(
323          document_manager,
324          languages,
325          core,
326          config_manager,
327          workspace_index,
328          indexer,
329          module_resolver,
330          provider_manager,
331      );
332  
333      let js_direct = temp_dir.join("direct.js");
334      let js_direct_content = "const a = process.env.UNDEFINED_VAR;";
335      let mut f = File::create(&js_direct).unwrap();
336      write!(f, "{}", js_direct_content).unwrap();
337      let uri_direct = Url::from_file_path(&js_direct).unwrap();
338  
339      state
340          .document_manager
341          .open(
342              uri_direct.clone(),
343              "javascript".to_string(),
344              js_direct_content.to_string(),
345              0,
346          )
347          .await;
348      tokio::time::sleep(std::time::Duration::from_millis(100)).await;
349  
350      let diagnostics_direct = compute_diagnostics(&uri_direct, &state).await;
351      println!("Direct access diagnostics: {:?}", diagnostics_direct);
352      assert!(
353          !diagnostics_direct.is_empty(),
354          "Should have diagnostic for undefined UNDEFINED_VAR in direct access"
355      );
356      assert!(
357          diagnostics_direct
358              .iter()
359              .any(|d| d.message.contains("UNDEFINED_VAR")),
360          "Diagnostic should mention UNDEFINED_VAR"
361      );
362  
363      let js_destruct = temp_dir.join("destruct.js");
364      let js_destruct_content = "const { UNDEFINED_VAR } = process.env;";
365      let mut f = File::create(&js_destruct).unwrap();
366      write!(f, "{}", js_destruct_content).unwrap();
367      let uri_destruct = Url::from_file_path(&js_destruct).unwrap();
368  
369      state
370          .document_manager
371          .open(
372              uri_destruct.clone(),
373              "javascript".to_string(),
374              js_destruct_content.to_string(),
375              0,
376          )
377          .await;
378      tokio::time::sleep(std::time::Duration::from_millis(100)).await;
379  
380      let diagnostics_destruct = compute_diagnostics(&uri_destruct, &state).await;
381      println!("Destructuring diagnostics: {:?}", diagnostics_destruct);
382      assert!(
383          !diagnostics_destruct.is_empty(),
384          "Should have diagnostic for undefined UNDEFINED_VAR in destructuring"
385      );
386      assert!(
387          diagnostics_destruct
388              .iter()
389              .any(|d| d.message.contains("UNDEFINED_VAR")),
390          "Diagnostic should mention UNDEFINED_VAR"
391      );
392  
393      let js_renamed = temp_dir.join("renamed.js");
394      let js_renamed_content = "const { UNDEFINED_VAR: myVar } = process.env;";
395      let mut f = File::create(&js_renamed).unwrap();
396      write!(f, "{}", js_renamed_content).unwrap();
397      let uri_renamed = Url::from_file_path(&js_renamed).unwrap();
398  
399      state
400          .document_manager
401          .open(
402              uri_renamed.clone(),
403              "javascript".to_string(),
404              js_renamed_content.to_string(),
405              0,
406          )
407          .await;
408      tokio::time::sleep(std::time::Duration::from_millis(100)).await;
409  
410      let diagnostics_renamed = compute_diagnostics(&uri_renamed, &state).await;
411      println!(
412          "Renamed destructuring diagnostics: {:?}",
413          diagnostics_renamed
414      );
415      assert!(
416          !diagnostics_renamed.is_empty(),
417          "Should have diagnostic for undefined UNDEFINED_VAR in renamed destructuring"
418      );
419      assert!(
420          diagnostics_renamed
421              .iter()
422              .any(|d| d.message.contains("UNDEFINED_VAR")),
423          "Diagnostic should mention UNDEFINED_VAR"
424      );
425  
426      let js_defined = temp_dir.join("defined.js");
427      let js_defined_content = "const { DB_URL } = process.env;";
428      let mut f = File::create(&js_defined).unwrap();
429      write!(f, "{}", js_defined_content).unwrap();
430      let uri_defined = Url::from_file_path(&js_defined).unwrap();
431  
432      state
433          .document_manager
434          .open(
435              uri_defined.clone(),
436              "javascript".to_string(),
437              js_defined_content.to_string(),
438              0,
439          )
440          .await;
441      tokio::time::sleep(std::time::Duration::from_millis(100)).await;
442  
443      let diagnostics_defined = compute_diagnostics(&uri_defined, &state).await;
444      println!("Defined var diagnostics: {:?}", diagnostics_defined);
445      assert!(
446          diagnostics_defined.is_empty()
447              || !diagnostics_defined
448                  .iter()
449                  .any(|d| d.message.contains("DB_URL")),
450          "Should NOT have diagnostic for defined DB_URL"
451      );
452  
453      let _ = fs::remove_dir_all(&temp_dir);
454  }