modules_test.go 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. package nginx
  2. import (
  3. "regexp"
  4. "testing"
  5. )
  6. func TestModuleNameNormalization(t *testing.T) {
  7. testCases := []struct {
  8. name string
  9. loadModuleName string
  10. expectedNormalized string
  11. configureArgName string
  12. expectedLoadName string
  13. }{
  14. {
  15. name: "stream module",
  16. loadModuleName: "ngx_stream_module",
  17. expectedNormalized: "stream",
  18. configureArgName: "stream",
  19. expectedLoadName: "ngx_stream_module",
  20. },
  21. {
  22. name: "http_geoip module",
  23. loadModuleName: "ngx_http_geoip_module",
  24. expectedNormalized: "http_geoip",
  25. configureArgName: "http_geoip_module",
  26. expectedLoadName: "ngx_http_geoip_module",
  27. },
  28. {
  29. name: "stream_geoip module",
  30. loadModuleName: "ngx_stream_geoip_module",
  31. expectedNormalized: "stream_geoip",
  32. configureArgName: "stream_geoip_module",
  33. expectedLoadName: "ngx_stream_geoip_module",
  34. },
  35. {
  36. name: "http_image_filter module",
  37. loadModuleName: "ngx_http_image_filter_module",
  38. expectedNormalized: "http_image_filter",
  39. configureArgName: "http_image_filter_module",
  40. expectedLoadName: "ngx_http_image_filter_module",
  41. },
  42. {
  43. name: "mail module",
  44. loadModuleName: "ngx_mail_module",
  45. expectedNormalized: "mail",
  46. configureArgName: "mail",
  47. expectedLoadName: "ngx_mail_module",
  48. },
  49. }
  50. for _, tc := range testCases {
  51. t.Run(tc.name, func(t *testing.T) {
  52. // Test normalization from load_module name
  53. normalizedFromLoad := normalizeModuleNameFromLoadModule(tc.loadModuleName)
  54. if normalizedFromLoad != tc.expectedNormalized {
  55. t.Errorf("normalizeModuleNameFromLoadModule(%s) = %s, expected %s",
  56. tc.loadModuleName, normalizedFromLoad, tc.expectedNormalized)
  57. }
  58. // Test normalization from configure argument name
  59. normalizedFromConfigure := normalizeModuleNameFromConfigure(tc.configureArgName)
  60. if normalizedFromConfigure != tc.expectedNormalized {
  61. t.Errorf("normalizeModuleNameFromConfigure(%s) = %s, expected %s",
  62. tc.configureArgName, normalizedFromConfigure, tc.expectedNormalized)
  63. }
  64. // Test getting expected load_module name
  65. expectedLoad := getExpectedLoadModuleName(tc.configureArgName)
  66. if expectedLoad != tc.expectedLoadName {
  67. t.Errorf("getExpectedLoadModuleName(%s) = %s, expected %s",
  68. tc.configureArgName, expectedLoad, tc.expectedLoadName)
  69. }
  70. })
  71. }
  72. }
  73. func TestGetLoadModuleRegex(t *testing.T) {
  74. testCases := []struct {
  75. name string
  76. input string
  77. expected []string // expected module names
  78. }{
  79. {
  80. name: "quoted absolute path",
  81. input: `load_module "/usr/local/nginx/modules/ngx_stream_module.so";`,
  82. expected: []string{"ngx_stream_module"},
  83. },
  84. {
  85. name: "unquoted relative path",
  86. input: `load_module modules/ngx_http_upstream_fair_module.so;`,
  87. expected: []string{"ngx_http_upstream_fair_module"},
  88. },
  89. {
  90. name: "quoted relative path",
  91. input: `load_module "modules/ngx_http_geoip_module.so";`,
  92. expected: []string{"ngx_http_geoip_module"},
  93. },
  94. {
  95. name: "unquoted absolute path",
  96. input: `load_module /etc/nginx/modules/ngx_http_cache_purge_module.so;`,
  97. expected: []string{"ngx_http_cache_purge_module"},
  98. },
  99. {
  100. name: "multiple modules",
  101. input: `load_module "/path/ngx_module1.so";\nload_module modules/ngx_module2.so;`,
  102. expected: []string{"ngx_module1", "ngx_module2"},
  103. },
  104. {
  105. name: "with extra whitespace",
  106. input: `load_module "modules/ngx_test_module.so" ;`,
  107. expected: []string{"ngx_test_module"},
  108. },
  109. {
  110. name: "no matches",
  111. input: `some other nginx config`,
  112. expected: []string{},
  113. },
  114. }
  115. regex := GetLoadModuleRegex()
  116. for _, tc := range testCases {
  117. t.Run(tc.name, func(t *testing.T) {
  118. matches := regex.FindAllStringSubmatch(tc.input, -1)
  119. if len(matches) != len(tc.expected) {
  120. t.Errorf("Expected %d matches, got %d", len(tc.expected), len(matches))
  121. return
  122. }
  123. for i, match := range matches {
  124. if len(match) < 2 {
  125. t.Errorf("Match %d should have at least 2 groups, got %d", i, len(match))
  126. continue
  127. }
  128. moduleName := match[1]
  129. expectedModule := tc.expected[i]
  130. if moduleName != expectedModule {
  131. t.Errorf("Expected module name %s, got %s", expectedModule, moduleName)
  132. }
  133. }
  134. })
  135. }
  136. }
  137. func TestModulesLoaded(t *testing.T) {
  138. text := `
  139. load_module "/usr/local/nginx/modules/ngx_stream_module.so";
  140. load_module modules/ngx_http_upstream_fair_module.so;
  141. load_module "modules/ngx_http_geoip_module.so";
  142. load_module /etc/nginx/modules/ngx_http_cache_purge_module.so;
  143. `
  144. loadModuleRe := GetLoadModuleRegex()
  145. matches := loadModuleRe.FindAllStringSubmatch(text, -1)
  146. t.Log("matches", matches)
  147. // Expected module names
  148. expectedModules := []string{
  149. "ngx_stream_module",
  150. "ngx_http_upstream_fair_module",
  151. "ngx_http_geoip_module",
  152. "ngx_http_cache_purge_module",
  153. }
  154. if len(matches) != len(expectedModules) {
  155. t.Errorf("Expected %d matches, got %d", len(expectedModules), len(matches))
  156. }
  157. for i, match := range matches {
  158. if len(match) < 2 {
  159. t.Errorf("Match %d should have at least 2 groups, got %d", i, len(match))
  160. continue
  161. }
  162. moduleName := match[1]
  163. expectedModule := expectedModules[i]
  164. t.Logf("Match %d: %s", i, moduleName)
  165. if moduleName != expectedModule {
  166. t.Errorf("Expected module name %s, got %s", expectedModule, moduleName)
  167. }
  168. }
  169. }
  170. func TestRealWorldModuleMapping(t *testing.T) {
  171. // Simulate real nginx configuration scenarios
  172. testScenarios := []struct {
  173. name string
  174. configureArg string // from nginx -V output
  175. loadModuleStmt string // from nginx -T output
  176. expectedNormalized string // internal representation
  177. }{
  178. {
  179. name: "stream module - basic",
  180. configureArg: "--with-stream",
  181. loadModuleStmt: `load_module "/usr/lib/nginx/modules/ngx_stream_module.so";`,
  182. expectedNormalized: "stream",
  183. },
  184. {
  185. name: "stream module - dynamic",
  186. configureArg: "--with-stream=dynamic",
  187. loadModuleStmt: `load_module modules/ngx_stream_module.so;`,
  188. expectedNormalized: "stream",
  189. },
  190. {
  191. name: "http_geoip module",
  192. configureArg: "--with-http_geoip_module=dynamic",
  193. loadModuleStmt: `load_module "modules/ngx_http_geoip_module.so";`,
  194. expectedNormalized: "http_geoip",
  195. },
  196. {
  197. name: "stream_geoip module",
  198. configureArg: "--with-stream_geoip_module=dynamic",
  199. loadModuleStmt: `load_module /usr/lib/nginx/modules/ngx_stream_geoip_module.so;`,
  200. expectedNormalized: "stream_geoip",
  201. },
  202. {
  203. name: "http_image_filter module",
  204. configureArg: "--with-http_image_filter_module=dynamic",
  205. loadModuleStmt: `load_module modules/ngx_http_image_filter_module.so;`,
  206. expectedNormalized: "http_image_filter",
  207. },
  208. {
  209. name: "mail module",
  210. configureArg: "--with-mail=dynamic",
  211. loadModuleStmt: `load_module "modules/ngx_mail_module.so";`,
  212. expectedNormalized: "mail",
  213. },
  214. }
  215. for _, scenario := range testScenarios {
  216. t.Run(scenario.name, func(t *testing.T) {
  217. // Test configure argument parsing
  218. paramRe := regexp.MustCompile(`--with-([a-zA-Z0-9_-]+)(?:_module)?(?:=([^"'\s]+|"[^"]*"|'[^']*'))?`)
  219. configMatches := paramRe.FindAllStringSubmatch(scenario.configureArg, -1)
  220. if len(configMatches) == 0 {
  221. t.Errorf("Failed to parse configure argument: %s", scenario.configureArg)
  222. return
  223. }
  224. configModuleName := configMatches[0][1]
  225. normalizedConfigName := normalizeModuleNameFromConfigure(configModuleName)
  226. // Test load_module statement parsing
  227. loadModuleRe := GetLoadModuleRegex()
  228. loadMatches := loadModuleRe.FindAllStringSubmatch(scenario.loadModuleStmt, -1)
  229. if len(loadMatches) == 0 {
  230. t.Errorf("Failed to parse load_module statement: %s", scenario.loadModuleStmt)
  231. return
  232. }
  233. loadModuleName := loadMatches[0][1]
  234. normalizedLoadName := normalizeModuleNameFromLoadModule(loadModuleName)
  235. // Verify both normalize to the same expected value
  236. if normalizedConfigName != scenario.expectedNormalized {
  237. t.Errorf("Configure arg normalization: expected %s, got %s",
  238. scenario.expectedNormalized, normalizedConfigName)
  239. }
  240. if normalizedLoadName != scenario.expectedNormalized {
  241. t.Errorf("Load module normalization: expected %s, got %s",
  242. scenario.expectedNormalized, normalizedLoadName)
  243. }
  244. // Verify they match each other (this is the key test)
  245. if normalizedConfigName != normalizedLoadName {
  246. t.Errorf("Normalization mismatch: config=%s, load=%s",
  247. normalizedConfigName, normalizedLoadName)
  248. }
  249. t.Logf("✓ %s: config=%s -> load=%s -> normalized=%s",
  250. scenario.name, configModuleName, loadModuleName, scenario.expectedNormalized)
  251. })
  252. }
  253. }
  254. func TestGetModuleMapping(t *testing.T) {
  255. // This test verifies that GetModuleMapping function works without errors
  256. // Since it depends on nginx being available, we'll just test that it doesn't panic
  257. defer func() {
  258. if r := recover(); r != nil {
  259. t.Errorf("GetModuleMapping panicked: %v", r)
  260. }
  261. }()
  262. mapping := GetModuleMapping()
  263. // The mapping should be a valid map (could be empty if nginx is not available)
  264. if mapping == nil {
  265. t.Error("GetModuleMapping returned nil")
  266. }
  267. t.Logf("GetModuleMapping returned %d entries", len(mapping))
  268. // If there are entries, verify they have the expected structure
  269. for moduleName, moduleInfo := range mapping {
  270. if moduleInfo == nil {
  271. t.Errorf("Module %s has nil info", moduleName)
  272. continue
  273. }
  274. requiredFields := []string{"normalized", "expected_load_module", "dynamic", "loaded", "params"}
  275. for _, field := range requiredFields {
  276. if _, exists := moduleInfo[field]; !exists {
  277. t.Errorf("Module %s missing field %s", moduleName, field)
  278. }
  279. }
  280. }
  281. }