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 }