/ tests / diagnostics_handler_test.rs
diagnostics_handler_test.rs
  1  //! Tests for server/handlers/diagnostics.rs - Diagnostics handler
  2  
  3  mod common;
  4  
  5  use common::TestFixture;
  6  use ecolog_lsp::server::handlers::compute_diagnostics;
  7  use std::fs;
  8  use tower_lsp::lsp_types::Url;
  9  
 10  #[tokio::test]
 11  async fn test_diagnostics_undefined_env_var() {
 12      let fixture = TestFixture::new().await;
 13      let uri = fixture.create_file("test.js", "const x = process.env.UNDEFINED_VAR;");
 14  
 15      fixture
 16          .state
 17          .document_manager
 18          .open(
 19              uri.clone(),
 20              "javascript".into(),
 21              "const x = process.env.UNDEFINED_VAR;".into(),
 22              1,
 23          )
 24          .await;
 25  
 26      let diagnostics = compute_diagnostics(&uri, &fixture.state).await;
 27  
 28      assert!(
 29          !diagnostics.is_empty(),
 30          "Should have diagnostic for undefined var"
 31      );
 32      assert!(diagnostics
 33          .iter()
 34          .any(|d| d.message.contains("UNDEFINED_VAR")));
 35  }
 36  
 37  #[tokio::test]
 38  async fn test_diagnostics_defined_env_var_no_warning() {
 39      let fixture = TestFixture::new().await;
 40      let uri = fixture.create_file("test.js", "const x = process.env.DB_URL;");
 41  
 42      fixture
 43          .state
 44          .document_manager
 45          .open(
 46              uri.clone(),
 47              "javascript".into(),
 48              "const x = process.env.DB_URL;".into(),
 49              1,
 50          )
 51          .await;
 52  
 53      let diagnostics = compute_diagnostics(&uri, &fixture.state).await;
 54  
 55      // DB_URL is defined in .env, so no diagnostics
 56      let undefined_warnings = diagnostics
 57          .iter()
 58          .filter(|d| d.message.contains("not defined"))
 59          .count();
 60      assert_eq!(undefined_warnings, 0, "Should not warn for defined var");
 61  }
 62  
 63  #[tokio::test]
 64  async fn test_diagnostics_multiple_undefined() {
 65      let fixture = TestFixture::new().await;
 66      let uri = fixture.create_file(
 67          "test.js",
 68          "const a = process.env.UNDEFINED_A;\nconst b = process.env.UNDEFINED_B;",
 69      );
 70  
 71      fixture
 72          .state
 73          .document_manager
 74          .open(
 75              uri.clone(),
 76              "javascript".into(),
 77              "const a = process.env.UNDEFINED_A;\nconst b = process.env.UNDEFINED_B;".into(),
 78              1,
 79          )
 80          .await;
 81  
 82      let diagnostics = compute_diagnostics(&uri, &fixture.state).await;
 83  
 84      // Should have warnings for both undefined vars
 85      assert!(diagnostics.len() >= 2, "Should have at least 2 diagnostics");
 86  }
 87  
 88  #[tokio::test]
 89  async fn test_diagnostics_python_undefined() {
 90      let fixture = TestFixture::new().await;
 91      let uri = fixture.create_file("test.py", "import os\nx = os.environ['UNDEFINED_VAR']");
 92  
 93      fixture
 94          .state
 95          .document_manager
 96          .open(
 97              uri.clone(),
 98              "python".into(),
 99              "import os\nx = os.environ['UNDEFINED_VAR']".into(),
100              1,
101          )
102          .await;
103  
104      let diagnostics = compute_diagnostics(&uri, &fixture.state).await;
105  
106      assert!(
107          !diagnostics.is_empty(),
108          "Should have diagnostic for Python undefined var"
109      );
110  }
111  
112  #[tokio::test]
113  async fn test_diagnostics_destructuring_undefined() {
114      let fixture = TestFixture::new().await;
115      let uri = fixture.create_file("test.js", "const { UNDEFINED_VAR } = process.env;");
116  
117      fixture
118          .state
119          .document_manager
120          .open(
121              uri.clone(),
122              "javascript".into(),
123              "const { UNDEFINED_VAR } = process.env;".into(),
124              1,
125          )
126          .await;
127  
128      let diagnostics = compute_diagnostics(&uri, &fixture.state).await;
129  
130      assert!(
131          !diagnostics.is_empty(),
132          "Should have diagnostic for destructured undefined var"
133      );
134  }
135  
136  #[tokio::test]
137  async fn test_diagnostics_env_file_double_equals() {
138      let fixture = TestFixture::new().await;
139  
140      // Create a .env file with error - use .env.local pattern which is in default config
141      let env_path = fixture.temp_dir.join(".env.local");
142      fs::write(&env_path, "KEY==value\n").unwrap();
143      let uri = Url::from_file_path(&env_path).unwrap();
144  
145      fixture
146          .state
147          .document_manager
148          .open(uri.clone(), "env".into(), "KEY==value\n".into(), 1)
149          .await;
150  
151      let diagnostics = compute_diagnostics(&uri, &fixture.state).await;
152  
153      // Should detect double equals error
154      assert!(!diagnostics.is_empty(), "Should detect .env syntax error");
155  }
156  
157  #[tokio::test]
158  async fn test_diagnostics_env_file_valid() {
159      let fixture = TestFixture::new().await;
160  
161      // The fixture's .env file should be valid
162      let env_path = fixture.temp_dir.join(".env");
163      let uri = Url::from_file_path(&env_path).unwrap();
164  
165      let content = fs::read_to_string(&env_path).unwrap();
166      fixture
167          .state
168          .document_manager
169          .open(uri.clone(), "env".into(), content, 1)
170          .await;
171  
172      let diagnostics = compute_diagnostics(&uri, &fixture.state).await;
173  
174      // Valid .env should have no diagnostics
175      assert!(
176          diagnostics.is_empty(),
177          "Valid .env should have no diagnostics"
178      );
179  }
180  
181  #[tokio::test]
182  async fn test_diagnostics_document_not_found() {
183      let fixture = TestFixture::new().await;
184      let uri = Url::parse("file:///nonexistent/file.js").unwrap();
185  
186      // Don't open the document - test diagnostics for non-existent doc
187      let diagnostics = compute_diagnostics(&uri, &fixture.state).await;
188  
189      assert!(
190          diagnostics.is_empty(),
191          "Should return empty for non-existent document"
192      );
193  }
194  
195  #[tokio::test]
196  async fn test_diagnostics_object_alias_property_access() {
197      let fixture = TestFixture::new().await;
198      let uri = fixture.create_file(
199          "test.js",
200          "const env = process.env;\nconst x = env.UNDEFINED_PROP;",
201      );
202  
203      fixture
204          .state
205          .document_manager
206          .open(
207              uri.clone(),
208              "javascript".into(),
209              "const env = process.env;\nconst x = env.UNDEFINED_PROP;".into(),
210              1,
211          )
212          .await;
213  
214      let diagnostics = compute_diagnostics(&uri, &fixture.state).await;
215  
216      // Should detect undefined property access on env object alias
217      assert!(
218          !diagnostics.is_empty(),
219          "Should detect undefined property on env alias"
220      );
221  }
222  
223  #[tokio::test]
224  async fn test_diagnostics_mixed_defined_undefined() {
225      let fixture = TestFixture::new().await;
226      let uri = fixture.create_file(
227          "test.js",
228          "const db = process.env.DB_URL;\nconst x = process.env.UNDEFINED_VAR;",
229      );
230  
231      fixture
232          .state
233          .document_manager
234          .open(
235              uri.clone(),
236              "javascript".into(),
237              "const db = process.env.DB_URL;\nconst x = process.env.UNDEFINED_VAR;".into(),
238              1,
239          )
240          .await;
241  
242      let diagnostics = compute_diagnostics(&uri, &fixture.state).await;
243  
244      // Only undefined should have warning
245      let messages: Vec<_> = diagnostics.iter().map(|d| &d.message).collect();
246      assert!(
247          !messages.iter().any(|m| m.contains("DB_URL")),
248          "Should not warn for DB_URL"
249      );
250      assert!(
251          messages.iter().any(|m| m.contains("UNDEFINED_VAR")),
252          "Should warn for UNDEFINED_VAR"
253      );
254  }