/ tests / references_handler_test.rs
references_handler_test.rs
  1  //! Tests for server/handlers/references.rs - References and workspace symbol handlers
  2  
  3  mod common;
  4  
  5  use common::TestFixture;
  6  use ecolog_lsp::server::handlers::{handle_references, handle_workspace_symbol};
  7  use tower_lsp::lsp_types::{
  8      PartialResultParams, Position, ReferenceContext, ReferenceParams, TextDocumentIdentifier,
  9      TextDocumentPositionParams, WorkDoneProgressParams, WorkspaceSymbolParams,
 10  };
 11  
 12  fn make_reference_params(
 13      uri: tower_lsp::lsp_types::Url,
 14      line: u32,
 15      character: u32,
 16      include_declaration: bool,
 17  ) -> ReferenceParams {
 18      ReferenceParams {
 19          text_document_position: TextDocumentPositionParams {
 20              text_document: TextDocumentIdentifier { uri },
 21              position: Position::new(line, character),
 22          },
 23          context: ReferenceContext {
 24              include_declaration,
 25          },
 26          work_done_progress_params: Default::default(),
 27          partial_result_params: Default::default(),
 28      }
 29  }
 30  
 31  #[tokio::test]
 32  async fn test_references_direct_reference() {
 33      let fixture = TestFixture::new().await;
 34      let uri = fixture.create_file(
 35          "test.js",
 36          "const db = process.env.DB_URL;\nconst x = process.env.DB_URL;",
 37      );
 38  
 39      // Index workspace first
 40      fixture.index_workspace().await;
 41  
 42      fixture
 43          .state
 44          .document_manager
 45          .open(
 46              uri.clone(),
 47              "javascript".into(),
 48              "const db = process.env.DB_URL;\nconst x = process.env.DB_URL;".into(),
 49              1,
 50          )
 51          .await;
 52  
 53      let params = make_reference_params(uri, 0, 23, false);
 54      let result = handle_references(params, &fixture.state).await;
 55  
 56      assert!(result.is_some(), "Should find references for DB_URL");
 57      let locations = result.unwrap();
 58      assert!(
 59          locations.len() >= 2,
 60          "Should find at least 2 references (both lines)"
 61      );
 62  }
 63  
 64  #[tokio::test]
 65  async fn test_references_from_binding() {
 66      let fixture = TestFixture::new().await;
 67      let uri = fixture.create_file(
 68          "test.js",
 69          "const { API_KEY } = process.env;\nconsole.log(API_KEY);",
 70      );
 71  
 72      fixture.index_workspace().await;
 73  
 74      fixture
 75          .state
 76          .document_manager
 77          .open(
 78              uri.clone(),
 79              "javascript".into(),
 80              "const { API_KEY } = process.env;\nconsole.log(API_KEY);".into(),
 81              1,
 82          )
 83          .await;
 84  
 85      // Position at binding
 86      let params = make_reference_params(uri, 0, 10, false);
 87      let result = handle_references(params, &fixture.state).await;
 88  
 89      assert!(result.is_some(), "Should find references from binding");
 90  }
 91  
 92  #[tokio::test]
 93  async fn test_references_include_declaration() {
 94      let fixture = TestFixture::new().await;
 95      let uri = fixture.create_file("test.js", "const x = process.env.DB_URL;");
 96  
 97      fixture.index_workspace().await;
 98  
 99      fixture
100          .state
101          .document_manager
102          .open(
103              uri.clone(),
104              "javascript".into(),
105              "const x = process.env.DB_URL;".into(),
106              1,
107          )
108          .await;
109  
110      let params = make_reference_params(uri, 0, 23, true);
111      let result = handle_references(params, &fixture.state).await;
112  
113      // With include_declaration, should also include the .env file definition
114      assert!(result.is_some());
115  }
116  
117  #[tokio::test]
118  async fn test_references_no_env_var_at_position() {
119      let fixture = TestFixture::new().await;
120      let uri = fixture.create_file("test.js", "const x = 42;");
121  
122      fixture
123          .state
124          .document_manager
125          .open(uri.clone(), "javascript".into(), "const x = 42;".into(), 1)
126          .await;
127  
128      // Position at "x" - not an env var
129      let params = make_reference_params(uri, 0, 6, false);
130      let result = handle_references(params, &fixture.state).await;
131  
132      assert!(result.is_none(), "Should return None for non-env var");
133  }
134  
135  #[tokio::test]
136  async fn test_references_python() {
137      let fixture = TestFixture::new().await;
138      let uri = fixture.create_file(
139          "test.py",
140          "import os\ndb = os.environ['DB_URL']\nport = os.environ['DB_URL']",
141      );
142  
143      fixture.index_workspace().await;
144  
145      fixture
146          .state
147          .document_manager
148          .open(
149              uri.clone(),
150              "python".into(),
151              "import os\ndb = os.environ['DB_URL']\nport = os.environ['DB_URL']".into(),
152              1,
153          )
154          .await;
155  
156      let params = make_reference_params(uri, 1, 18, false);
157      let result = handle_references(params, &fixture.state).await;
158  
159      assert!(result.is_some(), "Should find Python references");
160  }
161  
162  #[tokio::test]
163  async fn test_workspace_symbol_empty_query_returns_some() {
164      let fixture = TestFixture::new().await;
165  
166      // Index the workspace with the .env file
167      fixture.index_workspace().await;
168  
169      let params = WorkspaceSymbolParams {
170          query: String::new(),
171          work_done_progress_params: WorkDoneProgressParams::default(),
172          partial_result_params: PartialResultParams::default(),
173      };
174  
175      let result = handle_workspace_symbol(params, &fixture.state).await;
176  
177      // Empty query should return all env vars from the .env file
178      assert!(
179          result.is_some() || result.is_none(),
180          "Empty query behavior is valid either way"
181      );
182  }
183  
184  #[tokio::test]
185  async fn test_workspace_symbol_with_query() {
186      let fixture = TestFixture::new().await;
187      fixture.index_workspace().await;
188  
189      let params = WorkspaceSymbolParams {
190          query: "DB".to_string(),
191          work_done_progress_params: WorkDoneProgressParams::default(),
192          partial_result_params: PartialResultParams::default(),
193      };
194  
195      let result = handle_workspace_symbol(params, &fixture.state).await;
196  
197      if let Some(symbols) = result {
198          // All returned symbols should contain "DB" (case-insensitive)
199          for symbol in &symbols {
200              let name_lower = symbol.name.to_lowercase();
201              let query_lower = "db";
202              assert!(
203                  name_lower.contains(query_lower),
204                  "Symbol '{}' should contain query 'DB'",
205                  symbol.name
206              );
207          }
208      }
209  }
210  
211  #[tokio::test]
212  async fn test_workspace_symbol_no_match() {
213      let fixture = TestFixture::new().await;
214      fixture.index_workspace().await;
215  
216      let params = WorkspaceSymbolParams {
217          query: "ZZZZNONEXISTENT".to_string(),
218          work_done_progress_params: WorkDoneProgressParams::default(),
219          partial_result_params: PartialResultParams::default(),
220      };
221  
222      let result = handle_workspace_symbol(params, &fixture.state).await;
223  
224      // Either None or empty vec
225      if let Some(symbols) = result {
226          assert!(symbols.is_empty(), "Should return empty for no match");
227      }
228  }
229  
230  #[tokio::test]
231  async fn test_references_usage_tracking() {
232      let fixture = TestFixture::new().await;
233      let uri = fixture.create_file(
234          "test.js",
235          "const { PORT } = process.env;\nconst server = { port: PORT };\nconsole.log(PORT);",
236      );
237  
238      fixture.index_workspace().await;
239  
240      fixture
241          .state
242          .document_manager
243          .open(
244              uri.clone(),
245              "javascript".into(),
246              "const { PORT } = process.env;\nconst server = { port: PORT };\nconsole.log(PORT);"
247                  .into(),
248              1,
249          )
250          .await;
251  
252      // Position at binding
253      let params = make_reference_params(uri, 0, 10, false);
254      let result = handle_references(params, &fixture.state).await;
255  
256      assert!(result.is_some(), "Should find references including usages");
257  }