/ tests / env_resolution_test.rs
env_resolution_test.rs
  1  //! Tests for server/env_resolution.rs - Environment variable resolution at cursor position
  2  
  3  mod common;
  4  
  5  use common::TestFixture;
  6  use ecolog_lsp::server::env_resolution::{resolve_env_var_at_position, EnvVarSource};
  7  use tower_lsp::lsp_types::Position;
  8  
  9  #[tokio::test]
 10  async fn test_resolve_direct_reference() {
 11      let fixture = TestFixture::new().await;
 12      let uri = fixture.create_file("test.js", "const db = process.env.DB_URL;");
 13  
 14      fixture
 15          .state
 16          .document_manager
 17          .open(
 18              uri.clone(),
 19              "javascript".into(),
 20              "const db = process.env.DB_URL;".into(),
 21              1,
 22          )
 23          .await;
 24  
 25      // Position at "DB_URL" (character 23)
 26      let result =
 27          resolve_env_var_at_position(&uri, Position::new(0, 23), &fixture.state, false).await;
 28  
 29      assert!(result.is_some(), "Should resolve direct reference");
 30      let resolved = result.unwrap();
 31      assert_eq!(resolved.env_var_name.as_str(), "DB_URL");
 32      assert!(matches!(resolved.source, EnvVarSource::DirectReference));
 33  }
 34  
 35  #[tokio::test]
 36  async fn test_resolve_local_binding() {
 37      let fixture = TestFixture::new().await;
 38      let uri = fixture.create_file(
 39          "test.js",
 40          "const { DB_URL } = process.env;\nconsole.log(DB_URL);",
 41      );
 42  
 43      fixture
 44          .state
 45          .document_manager
 46          .open(
 47              uri.clone(),
 48              "javascript".into(),
 49              "const { DB_URL } = process.env;\nconsole.log(DB_URL);".into(),
 50              1,
 51          )
 52          .await;
 53  
 54      // Position at the destructured binding "DB_URL" (line 0, character 8)
 55      let result =
 56          resolve_env_var_at_position(&uri, Position::new(0, 8), &fixture.state, false).await;
 57  
 58      assert!(result.is_some(), "Should resolve local binding");
 59      let resolved = result.unwrap();
 60      assert_eq!(resolved.env_var_name.as_str(), "DB_URL");
 61      assert!(matches!(resolved.source, EnvVarSource::LocalBinding { .. }));
 62  }
 63  
 64  #[tokio::test]
 65  async fn test_resolve_local_usage() {
 66      let fixture = TestFixture::new().await;
 67      let uri = fixture.create_file(
 68          "test.js",
 69          "const { DB_URL } = process.env;\nconsole.log(DB_URL);",
 70      );
 71  
 72      fixture
 73          .state
 74          .document_manager
 75          .open(
 76              uri.clone(),
 77              "javascript".into(),
 78              "const { DB_URL } = process.env;\nconsole.log(DB_URL);".into(),
 79              1,
 80          )
 81          .await;
 82  
 83      // Position at the usage "DB_URL" (line 1, character 12)
 84      let result =
 85          resolve_env_var_at_position(&uri, Position::new(1, 12), &fixture.state, false).await;
 86  
 87      assert!(result.is_some(), "Should resolve local usage");
 88      let resolved = result.unwrap();
 89      assert_eq!(resolved.env_var_name.as_str(), "DB_URL");
 90      assert!(matches!(resolved.source, EnvVarSource::LocalUsage { .. }));
 91  }
 92  
 93  #[tokio::test]
 94  async fn test_resolve_no_env_var_at_position() {
 95      let fixture = TestFixture::new().await;
 96      let uri = fixture.create_file("test.js", "const x = 42;");
 97  
 98      fixture
 99          .state
100          .document_manager
101          .open(uri.clone(), "javascript".into(), "const x = 42;".into(), 1)
102          .await;
103  
104      // Position at "x" - not an env var
105      let result =
106          resolve_env_var_at_position(&uri, Position::new(0, 6), &fixture.state, false).await;
107  
108      assert!(result.is_none(), "Should not resolve non-env var");
109  }
110  
111  #[tokio::test]
112  async fn test_resolve_typescript_direct_reference() {
113      let fixture = TestFixture::new().await;
114      let uri = fixture.create_file("test.ts", "const apiKey: string = process.env.API_KEY!;");
115  
116      fixture
117          .state
118          .document_manager
119          .open(
120              uri.clone(),
121              "typescript".into(),
122              "const apiKey: string = process.env.API_KEY!;".into(),
123              1,
124          )
125          .await;
126  
127      // Position at "API_KEY" (character 35)
128      let result =
129          resolve_env_var_at_position(&uri, Position::new(0, 35), &fixture.state, false).await;
130  
131      assert!(
132          result.is_some(),
133          "Should resolve TypeScript direct reference"
134      );
135      let resolved = result.unwrap();
136      assert_eq!(resolved.env_var_name.as_str(), "API_KEY");
137  }
138  
139  #[tokio::test]
140  async fn test_resolve_python_environ_subscript() {
141      let fixture = TestFixture::new().await;
142      let uri = fixture.create_file("test.py", "import os\ndb_url = os.environ['DB_URL']");
143  
144      fixture
145          .state
146          .document_manager
147          .open(
148              uri.clone(),
149              "python".into(),
150              "import os\ndb_url = os.environ['DB_URL']".into(),
151              1,
152          )
153          .await;
154  
155      // Position at "DB_URL" (line 1, character 21)
156      let result =
157          resolve_env_var_at_position(&uri, Position::new(1, 21), &fixture.state, false).await;
158  
159      assert!(result.is_some(), "Should resolve Python environ subscript");
160      let resolved = result.unwrap();
161      assert_eq!(resolved.env_var_name.as_str(), "DB_URL");
162  }
163  
164  #[tokio::test]
165  async fn test_resolve_python_getenv() {
166      let fixture = TestFixture::new().await;
167      let uri = fixture.create_file("test.py", "import os\napi_key = os.getenv('API_KEY')");
168  
169      fixture
170          .state
171          .document_manager
172          .open(
173              uri.clone(),
174              "python".into(),
175              "import os\napi_key = os.getenv('API_KEY')".into(),
176              1,
177          )
178          .await;
179  
180      // Position at "API_KEY" (line 1, character 21)
181      let result =
182          resolve_env_var_at_position(&uri, Position::new(1, 21), &fixture.state, false).await;
183  
184      assert!(result.is_some(), "Should resolve Python getenv");
185      let resolved = result.unwrap();
186      assert_eq!(resolved.env_var_name.as_str(), "API_KEY");
187  }
188  
189  #[tokio::test]
190  async fn test_resolve_rust_std_env_var() {
191      let fixture = TestFixture::new().await;
192      let uri = fixture.create_file(
193          "test.rs",
194          "fn main() { let db = std::env::var(\"DB_URL\"); }",
195      );
196  
197      fixture
198          .state
199          .document_manager
200          .open(
201              uri.clone(),
202              "rust".into(),
203              "fn main() { let db = std::env::var(\"DB_URL\"); }".into(),
204              1,
205          )
206          .await;
207  
208      // Position at "DB_URL" (character 36)
209      let result =
210          resolve_env_var_at_position(&uri, Position::new(0, 36), &fixture.state, false).await;
211  
212      assert!(result.is_some(), "Should resolve Rust std::env::var");
213      let resolved = result.unwrap();
214      assert_eq!(resolved.env_var_name.as_str(), "DB_URL");
215  }
216  
217  #[tokio::test]
218  async fn test_resolve_go_os_getenv() {
219      let fixture = TestFixture::new().await;
220      let uri = fixture.create_file(
221          "test.go",
222          "package main\nimport \"os\"\nfunc main() { db := os.Getenv(\"DB_URL\") }",
223      );
224  
225      fixture
226          .state
227          .document_manager
228          .open(
229              uri.clone(),
230              "go".into(),
231              "package main\nimport \"os\"\nfunc main() { db := os.Getenv(\"DB_URL\") }".into(),
232              1,
233          )
234          .await;
235  
236      // Position at "DB_URL" (line 2, character 32)
237      let result =
238          resolve_env_var_at_position(&uri, Position::new(2, 32), &fixture.state, false).await;
239  
240      assert!(result.is_some(), "Should resolve Go os.Getenv");
241      let resolved = result.unwrap();
242      assert_eq!(resolved.env_var_name.as_str(), "DB_URL");
243  }
244  
245  #[tokio::test]
246  async fn test_resolve_with_cross_module_disabled() {
247      let fixture = TestFixture::new().await;
248      let uri = fixture.create_file(
249          "test.js",
250          "import env from './config';\nconst db = env.DB_URL;",
251      );
252  
253      fixture
254          .state
255          .document_manager
256          .open(
257              uri.clone(),
258              "javascript".into(),
259              "import env from './config';\nconst db = env.DB_URL;".into(),
260              1,
261          )
262          .await;
263  
264      // Position at "env" in "env.DB_URL" - cross-module disabled
265      let result =
266          resolve_env_var_at_position(&uri, Position::new(1, 11), &fixture.state, false).await;
267  
268      // Should be None since cross-module is disabled and this isn't a local env reference
269      assert!(
270          result.is_none(),
271          "Cross-module should not resolve when disabled"
272      );
273  }