|
@@ -0,0 +1,319 @@
|
|
|
+package nginx
|
|
|
+
|
|
|
+import (
|
|
|
+ "regexp"
|
|
|
+ "testing"
|
|
|
+)
|
|
|
+
|
|
|
+func TestModuleNameNormalization(t *testing.T) {
|
|
|
+ testCases := []struct {
|
|
|
+ name string
|
|
|
+ loadModuleName string
|
|
|
+ expectedNormalized string
|
|
|
+ configureArgName string
|
|
|
+ expectedLoadName string
|
|
|
+ }{
|
|
|
+ {
|
|
|
+ name: "stream module",
|
|
|
+ loadModuleName: "ngx_stream_module",
|
|
|
+ expectedNormalized: "stream",
|
|
|
+ configureArgName: "stream",
|
|
|
+ expectedLoadName: "ngx_stream_module",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "http_geoip module",
|
|
|
+ loadModuleName: "ngx_http_geoip_module",
|
|
|
+ expectedNormalized: "http_geoip",
|
|
|
+ configureArgName: "http_geoip_module",
|
|
|
+ expectedLoadName: "ngx_http_geoip_module",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "stream_geoip module",
|
|
|
+ loadModuleName: "ngx_stream_geoip_module",
|
|
|
+ expectedNormalized: "stream_geoip",
|
|
|
+ configureArgName: "stream_geoip_module",
|
|
|
+ expectedLoadName: "ngx_stream_geoip_module",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "http_image_filter module",
|
|
|
+ loadModuleName: "ngx_http_image_filter_module",
|
|
|
+ expectedNormalized: "http_image_filter",
|
|
|
+ configureArgName: "http_image_filter_module",
|
|
|
+ expectedLoadName: "ngx_http_image_filter_module",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "mail module",
|
|
|
+ loadModuleName: "ngx_mail_module",
|
|
|
+ expectedNormalized: "mail",
|
|
|
+ configureArgName: "mail",
|
|
|
+ expectedLoadName: "ngx_mail_module",
|
|
|
+ },
|
|
|
+ }
|
|
|
+
|
|
|
+ for _, tc := range testCases {
|
|
|
+ t.Run(tc.name, func(t *testing.T) {
|
|
|
+ // Test normalization from load_module name
|
|
|
+ normalizedFromLoad := normalizeModuleNameFromLoadModule(tc.loadModuleName)
|
|
|
+ if normalizedFromLoad != tc.expectedNormalized {
|
|
|
+ t.Errorf("normalizeModuleNameFromLoadModule(%s) = %s, expected %s",
|
|
|
+ tc.loadModuleName, normalizedFromLoad, tc.expectedNormalized)
|
|
|
+ }
|
|
|
+
|
|
|
+ // Test normalization from configure argument name
|
|
|
+ normalizedFromConfigure := normalizeModuleNameFromConfigure(tc.configureArgName)
|
|
|
+ if normalizedFromConfigure != tc.expectedNormalized {
|
|
|
+ t.Errorf("normalizeModuleNameFromConfigure(%s) = %s, expected %s",
|
|
|
+ tc.configureArgName, normalizedFromConfigure, tc.expectedNormalized)
|
|
|
+ }
|
|
|
+
|
|
|
+ // Test getting expected load_module name
|
|
|
+ expectedLoad := getExpectedLoadModuleName(tc.configureArgName)
|
|
|
+ if expectedLoad != tc.expectedLoadName {
|
|
|
+ t.Errorf("getExpectedLoadModuleName(%s) = %s, expected %s",
|
|
|
+ tc.configureArgName, expectedLoad, tc.expectedLoadName)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func TestGetLoadModuleRegex(t *testing.T) {
|
|
|
+ testCases := []struct {
|
|
|
+ name string
|
|
|
+ input string
|
|
|
+ expected []string // expected module names
|
|
|
+ }{
|
|
|
+ {
|
|
|
+ name: "quoted absolute path",
|
|
|
+ input: `load_module "/usr/local/nginx/modules/ngx_stream_module.so";`,
|
|
|
+ expected: []string{"ngx_stream_module"},
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "unquoted relative path",
|
|
|
+ input: `load_module modules/ngx_http_upstream_fair_module.so;`,
|
|
|
+ expected: []string{"ngx_http_upstream_fair_module"},
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "quoted relative path",
|
|
|
+ input: `load_module "modules/ngx_http_geoip_module.so";`,
|
|
|
+ expected: []string{"ngx_http_geoip_module"},
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "unquoted absolute path",
|
|
|
+ input: `load_module /etc/nginx/modules/ngx_http_cache_purge_module.so;`,
|
|
|
+ expected: []string{"ngx_http_cache_purge_module"},
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "multiple modules",
|
|
|
+ input: `load_module "/path/ngx_module1.so";\nload_module modules/ngx_module2.so;`,
|
|
|
+ expected: []string{"ngx_module1", "ngx_module2"},
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "with extra whitespace",
|
|
|
+ input: `load_module "modules/ngx_test_module.so" ;`,
|
|
|
+ expected: []string{"ngx_test_module"},
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "no matches",
|
|
|
+ input: `some other nginx config`,
|
|
|
+ expected: []string{},
|
|
|
+ },
|
|
|
+ }
|
|
|
+
|
|
|
+ regex := GetLoadModuleRegex()
|
|
|
+
|
|
|
+ for _, tc := range testCases {
|
|
|
+ t.Run(tc.name, func(t *testing.T) {
|
|
|
+ matches := regex.FindAllStringSubmatch(tc.input, -1)
|
|
|
+
|
|
|
+ if len(matches) != len(tc.expected) {
|
|
|
+ t.Errorf("Expected %d matches, got %d", len(tc.expected), len(matches))
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ for i, match := range matches {
|
|
|
+ if len(match) < 2 {
|
|
|
+ t.Errorf("Match %d should have at least 2 groups, got %d", i, len(match))
|
|
|
+ continue
|
|
|
+ }
|
|
|
+
|
|
|
+ moduleName := match[1]
|
|
|
+ expectedModule := tc.expected[i]
|
|
|
+
|
|
|
+ if moduleName != expectedModule {
|
|
|
+ t.Errorf("Expected module name %s, got %s", expectedModule, moduleName)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func TestModulesLoaded(t *testing.T) {
|
|
|
+ text := `
|
|
|
+load_module "/usr/local/nginx/modules/ngx_stream_module.so";
|
|
|
+load_module modules/ngx_http_upstream_fair_module.so;
|
|
|
+load_module "modules/ngx_http_geoip_module.so";
|
|
|
+load_module /etc/nginx/modules/ngx_http_cache_purge_module.so;
|
|
|
+`
|
|
|
+
|
|
|
+ loadModuleRe := GetLoadModuleRegex()
|
|
|
+ matches := loadModuleRe.FindAllStringSubmatch(text, -1)
|
|
|
+
|
|
|
+ t.Log("matches", matches)
|
|
|
+
|
|
|
+ // Expected module names
|
|
|
+ expectedModules := []string{
|
|
|
+ "ngx_stream_module",
|
|
|
+ "ngx_http_upstream_fair_module",
|
|
|
+ "ngx_http_geoip_module",
|
|
|
+ "ngx_http_cache_purge_module",
|
|
|
+ }
|
|
|
+
|
|
|
+ if len(matches) != len(expectedModules) {
|
|
|
+ t.Errorf("Expected %d matches, got %d", len(expectedModules), len(matches))
|
|
|
+ }
|
|
|
+
|
|
|
+ for i, match := range matches {
|
|
|
+ if len(match) < 2 {
|
|
|
+ t.Errorf("Match %d should have at least 2 groups, got %d", i, len(match))
|
|
|
+ continue
|
|
|
+ }
|
|
|
+
|
|
|
+ moduleName := match[1]
|
|
|
+ expectedModule := expectedModules[i]
|
|
|
+
|
|
|
+ t.Logf("Match %d: %s", i, moduleName)
|
|
|
+
|
|
|
+ if moduleName != expectedModule {
|
|
|
+ t.Errorf("Expected module name %s, got %s", expectedModule, moduleName)
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func TestRealWorldModuleMapping(t *testing.T) {
|
|
|
+ // Simulate real nginx configuration scenarios
|
|
|
+ testScenarios := []struct {
|
|
|
+ name string
|
|
|
+ configureArg string // from nginx -V output
|
|
|
+ loadModuleStmt string // from nginx -T output
|
|
|
+ expectedNormalized string // internal representation
|
|
|
+ }{
|
|
|
+ {
|
|
|
+ name: "stream module - basic",
|
|
|
+ configureArg: "--with-stream",
|
|
|
+ loadModuleStmt: `load_module "/usr/lib/nginx/modules/ngx_stream_module.so";`,
|
|
|
+ expectedNormalized: "stream",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "stream module - dynamic",
|
|
|
+ configureArg: "--with-stream=dynamic",
|
|
|
+ loadModuleStmt: `load_module modules/ngx_stream_module.so;`,
|
|
|
+ expectedNormalized: "stream",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "http_geoip module",
|
|
|
+ configureArg: "--with-http_geoip_module=dynamic",
|
|
|
+ loadModuleStmt: `load_module "modules/ngx_http_geoip_module.so";`,
|
|
|
+ expectedNormalized: "http_geoip",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "stream_geoip module",
|
|
|
+ configureArg: "--with-stream_geoip_module=dynamic",
|
|
|
+ loadModuleStmt: `load_module /usr/lib/nginx/modules/ngx_stream_geoip_module.so;`,
|
|
|
+ expectedNormalized: "stream_geoip",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "http_image_filter module",
|
|
|
+ configureArg: "--with-http_image_filter_module=dynamic",
|
|
|
+ loadModuleStmt: `load_module modules/ngx_http_image_filter_module.so;`,
|
|
|
+ expectedNormalized: "http_image_filter",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "mail module",
|
|
|
+ configureArg: "--with-mail=dynamic",
|
|
|
+ loadModuleStmt: `load_module "modules/ngx_mail_module.so";`,
|
|
|
+ expectedNormalized: "mail",
|
|
|
+ },
|
|
|
+ }
|
|
|
+
|
|
|
+ for _, scenario := range testScenarios {
|
|
|
+ t.Run(scenario.name, func(t *testing.T) {
|
|
|
+ // Test configure argument parsing
|
|
|
+ paramRe := regexp.MustCompile(`--with-([a-zA-Z0-9_-]+)(?:_module)?(?:=([^"'\s]+|"[^"]*"|'[^']*'))?`)
|
|
|
+ configMatches := paramRe.FindAllStringSubmatch(scenario.configureArg, -1)
|
|
|
+
|
|
|
+ if len(configMatches) == 0 {
|
|
|
+ t.Errorf("Failed to parse configure argument: %s", scenario.configureArg)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ configModuleName := configMatches[0][1]
|
|
|
+ normalizedConfigName := normalizeModuleNameFromConfigure(configModuleName)
|
|
|
+
|
|
|
+ // Test load_module statement parsing
|
|
|
+ loadModuleRe := GetLoadModuleRegex()
|
|
|
+ loadMatches := loadModuleRe.FindAllStringSubmatch(scenario.loadModuleStmt, -1)
|
|
|
+
|
|
|
+ if len(loadMatches) == 0 {
|
|
|
+ t.Errorf("Failed to parse load_module statement: %s", scenario.loadModuleStmt)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ loadModuleName := loadMatches[0][1]
|
|
|
+ normalizedLoadName := normalizeModuleNameFromLoadModule(loadModuleName)
|
|
|
+
|
|
|
+ // Verify both normalize to the same expected value
|
|
|
+ if normalizedConfigName != scenario.expectedNormalized {
|
|
|
+ t.Errorf("Configure arg normalization: expected %s, got %s",
|
|
|
+ scenario.expectedNormalized, normalizedConfigName)
|
|
|
+ }
|
|
|
+
|
|
|
+ if normalizedLoadName != scenario.expectedNormalized {
|
|
|
+ t.Errorf("Load module normalization: expected %s, got %s",
|
|
|
+ scenario.expectedNormalized, normalizedLoadName)
|
|
|
+ }
|
|
|
+
|
|
|
+ // Verify they match each other (this is the key test)
|
|
|
+ if normalizedConfigName != normalizedLoadName {
|
|
|
+ t.Errorf("Normalization mismatch: config=%s, load=%s",
|
|
|
+ normalizedConfigName, normalizedLoadName)
|
|
|
+ }
|
|
|
+
|
|
|
+ t.Logf("✓ %s: config=%s -> load=%s -> normalized=%s",
|
|
|
+ scenario.name, configModuleName, loadModuleName, scenario.expectedNormalized)
|
|
|
+ })
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func TestGetModuleMapping(t *testing.T) {
|
|
|
+ // This test verifies that GetModuleMapping function works without errors
|
|
|
+ // Since it depends on nginx being available, we'll just test that it doesn't panic
|
|
|
+ defer func() {
|
|
|
+ if r := recover(); r != nil {
|
|
|
+ t.Errorf("GetModuleMapping panicked: %v", r)
|
|
|
+ }
|
|
|
+ }()
|
|
|
+
|
|
|
+ mapping := GetModuleMapping()
|
|
|
+
|
|
|
+ // The mapping should be a valid map (could be empty if nginx is not available)
|
|
|
+ if mapping == nil {
|
|
|
+ t.Error("GetModuleMapping returned nil")
|
|
|
+ }
|
|
|
+
|
|
|
+ t.Logf("GetModuleMapping returned %d entries", len(mapping))
|
|
|
+
|
|
|
+ // If there are entries, verify they have the expected structure
|
|
|
+ for moduleName, moduleInfo := range mapping {
|
|
|
+ if moduleInfo == nil {
|
|
|
+ t.Errorf("Module %s has nil info", moduleName)
|
|
|
+ continue
|
|
|
+ }
|
|
|
+
|
|
|
+ requiredFields := []string{"normalized", "expected_load_module", "dynamic", "loaded", "params"}
|
|
|
+ for _, field := range requiredFields {
|
|
|
+ if _, exists := moduleInfo[field]; !exists {
|
|
|
+ t.Errorf("Module %s missing field %s", moduleName, field)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|