@@ -284,6 +284,97 @@ func TestRealWorldModuleMapping(t *testing.T) {
284284 }
285285}
286286
287+ func TestAddLoadedDynamicModules (t * testing.T ) {
288+ // Test scenario: modules loaded via load_module but not in configure args
289+ // This simulates the real-world case where external modules are installed
290+ // and loaded dynamically without being compiled into nginx
291+
292+ // We can't directly test addLoadedDynamicModules since it depends on getNginxT()
293+ // But we can test the logic by simulating the behavior
294+
295+ testLoadModuleOutput := `
296+ # Configuration file /etc/nginx/modules-enabled/50-mod-stream.conf:
297+ load_module modules/ngx_stream_module.so;
298+ # Configuration file /etc/nginx/modules-enabled/70-mod-stream-geoip2.conf:
299+ load_module modules/ngx_stream_geoip2_module.so;
300+ load_module "modules/ngx_http_geoip2_module.so";
301+ `
302+
303+ // Test the regex and normalization logic
304+ loadModuleRe := GetLoadModuleRegex ()
305+ matches := loadModuleRe .FindAllStringSubmatch (testLoadModuleOutput , - 1 )
306+
307+ expectedModules := map [string ]bool {
308+ "stream" : false ,
309+ "stream_geoip2" : false ,
310+ "http_geoip2" : false ,
311+ }
312+
313+ t .Logf ("Found %d load_module matches" , len (matches ))
314+
315+ for _ , match := range matches {
316+ if len (match ) > 1 {
317+ loadModuleName := match [1 ]
318+ normalizedName := normalizeModuleNameFromLoadModule (loadModuleName )
319+
320+ t .Logf ("Load module: %s -> normalized: %s" , loadModuleName , normalizedName )
321+
322+ if _ , expected := expectedModules [normalizedName ]; expected {
323+ expectedModules [normalizedName ] = true
324+ } else {
325+ t .Errorf ("Unexpected module found: %s (from %s)" , normalizedName , loadModuleName )
326+ }
327+ }
328+ }
329+
330+ // Check that all expected modules were found
331+ for moduleName , found := range expectedModules {
332+ if ! found {
333+ t .Errorf ("Expected module %s was not found" , moduleName )
334+ }
335+ }
336+ }
337+
338+ func TestExternalModuleDiscovery (t * testing.T ) {
339+ // Test the complete normalization pipeline for external modules
340+ testCases := []struct {
341+ name string
342+ loadModuleName string
343+ expectedResult string
344+ }{
345+ {
346+ name : "stream_geoip2 module" ,
347+ loadModuleName : "ngx_stream_geoip2_module" ,
348+ expectedResult : "stream_geoip2" ,
349+ },
350+ {
351+ name : "http_geoip2 module" ,
352+ loadModuleName : "ngx_http_geoip2_module" ,
353+ expectedResult : "http_geoip2" ,
354+ },
355+ {
356+ name : "custom third-party module" ,
357+ loadModuleName : "ngx_http_custom_module" ,
358+ expectedResult : "http_custom" ,
359+ },
360+ {
361+ name : "simple module name" ,
362+ loadModuleName : "ngx_custom_module" ,
363+ expectedResult : "custom" ,
364+ },
365+ }
366+
367+ for _ , tc := range testCases {
368+ t .Run (tc .name , func (t * testing.T ) {
369+ result := normalizeModuleNameFromLoadModule (tc .loadModuleName )
370+ if result != tc .expectedResult {
371+ t .Errorf ("normalizeModuleNameFromLoadModule(%s) = %s, expected %s" ,
372+ tc .loadModuleName , result , tc .expectedResult )
373+ }
374+ })
375+ }
376+ }
377+
287378func TestGetModuleMapping (t * testing.T ) {
288379 // This test verifies that GetModuleMapping function works without errors
289380 // Since it depends on nginx being available, we'll just test that it doesn't panic
0 commit comments