config_test.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672
  1. package config
  2. import (
  3. "errors"
  4. "testing"
  5. "github.com/google/go-cmp/cmp"
  6. "github.com/google/go-cmp/cmp/cmpopts"
  7. "github.com/spf13/viper"
  8. "github.com/stretchr/testify/assert"
  9. "github.com/stretchr/testify/require"
  10. "github.com/zricethezav/gitleaks/v8/regexp"
  11. )
  12. const configPath = "../testdata/config/"
  13. var regexComparer = func(x, y *regexp.Regexp) bool {
  14. if x == nil || y == nil {
  15. return x == y
  16. }
  17. return x.String() == y.String()
  18. }
  19. type translateCase struct {
  20. // Configuration file basename to load, from `../testdata/config/`.
  21. cfgName string
  22. // Expected result.
  23. cfg Config
  24. // Rules to compare.
  25. rules []string
  26. // Error to expect.
  27. wantError error
  28. }
  29. func TestTranslate(t *testing.T) {
  30. tests := []translateCase{
  31. // Valid
  32. {
  33. cfgName: "generic",
  34. cfg: Config{
  35. Title: "gitleaks config",
  36. Rules: map[string]Rule{"generic-api-key": {
  37. RuleID: "generic-api-key",
  38. Description: "Generic API Key",
  39. Regex: regexp.MustCompile(`(?i)(?:key|api|token|secret|client|passwd|password|auth|access)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([0-9a-z\-_.=]{10,150})(?:['|\"|\n|\r|\s|\x60|;]|$)`),
  40. Entropy: 3.5,
  41. Keywords: []string{"key", "api", "token", "secret", "client", "passwd", "password", "auth", "access"},
  42. Tags: []string{},
  43. }},
  44. },
  45. },
  46. {
  47. cfgName: "valid/rule_path_only",
  48. cfg: Config{
  49. Rules: map[string]Rule{"python-files-only": {
  50. RuleID: "python-files-only",
  51. Description: "Python Files",
  52. Path: regexp.MustCompile(`.py`),
  53. Keywords: []string{},
  54. Tags: []string{},
  55. }},
  56. },
  57. },
  58. {
  59. cfgName: "valid/rule_regex_escaped_character_group",
  60. cfg: Config{
  61. Rules: map[string]Rule{"pypi-upload-token": {
  62. RuleID: "pypi-upload-token",
  63. Description: "PyPI upload token",
  64. Regex: regexp.MustCompile(`pypi-AgEIcHlwaS5vcmc[A-Za-z0-9\-_]{50,1000}`),
  65. Keywords: []string{},
  66. Tags: []string{"key", "pypi"},
  67. }},
  68. },
  69. },
  70. {
  71. cfgName: "valid/rule_entropy_group",
  72. cfg: Config{
  73. Rules: map[string]Rule{"discord-api-key": {
  74. RuleID: "discord-api-key",
  75. Description: "Discord API key",
  76. Regex: regexp.MustCompile(`(?i)(discord[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"]([a-h0-9]{64})['\"]`),
  77. Entropy: 3.5,
  78. SecretGroup: 3,
  79. Keywords: []string{},
  80. Tags: []string{},
  81. }},
  82. },
  83. },
  84. // Invalid
  85. {
  86. cfgName: "invalid/rule_missing_id",
  87. cfg: Config{},
  88. wantError: errors.New("rule |id| is missing or empty, description: Discord API key, regex: (?i)(discord[a-z0-9_ .\\-,]{0,25})(=|>|:=|\\|\\|:|<=|=>|:).{0,5}['\\\"]([a-h0-9]{64})['\\\"]"),
  89. },
  90. {
  91. cfgName: "invalid/rule_no_regex_or_path",
  92. cfg: Config{},
  93. wantError: errors.New("discord-api-key: both |regex| and |path| are empty, this rule will have no effect"),
  94. },
  95. {
  96. cfgName: "invalid/rule_bad_entropy_group",
  97. cfg: Config{},
  98. wantError: errors.New("discord-api-key: invalid regex secret group 5, max regex secret group 3"),
  99. },
  100. }
  101. for _, tt := range tests {
  102. t.Run(tt.cfgName, func(t *testing.T) {
  103. testTranslate(t, tt)
  104. })
  105. }
  106. }
  107. func TestTranslateAllowlists(t *testing.T) {
  108. tests := []translateCase{
  109. // Global
  110. {
  111. cfgName: "valid/allowlist_global_old_compat",
  112. cfg: Config{
  113. Rules: map[string]Rule{},
  114. Allowlists: []*Allowlist{
  115. {
  116. StopWords: []string{"0989c462-69c9-49fa-b7d2-30dc5c576a97"},
  117. },
  118. },
  119. },
  120. },
  121. {
  122. cfgName: "valid/allowlist_global_multiple",
  123. cfg: Config{
  124. Rules: map[string]Rule{
  125. "test": {
  126. RuleID: "test",
  127. Regex: regexp.MustCompile(`token = "(.+)"`),
  128. Keywords: []string{},
  129. Tags: []string{},
  130. },
  131. },
  132. Allowlists: []*Allowlist{
  133. {
  134. Regexes: []*regexp.Regexp{regexp.MustCompile("^changeit$")},
  135. },
  136. {
  137. MatchCondition: AllowlistMatchAnd,
  138. Paths: []*regexp.Regexp{regexp.MustCompile("^node_modules/.*")},
  139. StopWords: []string{"mock"},
  140. },
  141. },
  142. },
  143. },
  144. {
  145. cfgName: "valid/allowlist_global_target_rules",
  146. cfg: Config{
  147. Rules: map[string]Rule{
  148. "github-app-token": {
  149. RuleID: "github-app-token",
  150. Regex: regexp.MustCompile(`(?:ghu|ghs)_[0-9a-zA-Z]{36}`),
  151. Tags: []string{},
  152. Keywords: []string{},
  153. Allowlists: []*Allowlist{
  154. {
  155. Paths: []*regexp.Regexp{regexp.MustCompile(`(?:^|/)@octokit/auth-token/README\.md$`)},
  156. },
  157. },
  158. },
  159. "github-oauth": {
  160. RuleID: "github-oauth",
  161. Regex: regexp.MustCompile(`gho_[0-9a-zA-Z]{36}`),
  162. Tags: []string{},
  163. Keywords: []string{},
  164. Allowlists: nil,
  165. },
  166. "github-pat": {
  167. RuleID: "github-pat",
  168. Regex: regexp.MustCompile(`ghp_[0-9a-zA-Z]{36}`),
  169. Tags: []string{},
  170. Keywords: []string{},
  171. Allowlists: []*Allowlist{
  172. {
  173. Paths: []*regexp.Regexp{regexp.MustCompile(`(?:^|/)@octokit/auth-token/README\.md$`)},
  174. },
  175. },
  176. },
  177. },
  178. Allowlists: []*Allowlist{
  179. {
  180. Regexes: []*regexp.Regexp{regexp.MustCompile(".*fake.*")},
  181. },
  182. },
  183. },
  184. },
  185. {
  186. cfgName: "valid/allowlist_global_regex",
  187. cfg: Config{
  188. Rules: map[string]Rule{},
  189. Allowlists: []*Allowlist{
  190. {
  191. MatchCondition: AllowlistMatchOr,
  192. Regexes: []*regexp.Regexp{regexp.MustCompile("AKIALALEM.L33243OLIA")},
  193. },
  194. },
  195. },
  196. },
  197. {
  198. cfgName: "invalid/allowlist_global_empty",
  199. cfg: Config{},
  200. wantError: errors.New("[[allowlists]] must contain at least one check for: commits, paths, regexes, or stopwords"),
  201. },
  202. {
  203. cfgName: "invalid/allowlist_global_old_and_new",
  204. cfg: Config{},
  205. wantError: errors.New("[allowlist] is deprecated, it cannot be used alongside [[allowlists]]"),
  206. },
  207. {
  208. cfgName: "invalid/allowlist_global_target_rule_id",
  209. cfg: Config{},
  210. wantError: errors.New("[[allowlists]] target rule ID 'github-pat' does not exist"),
  211. },
  212. {
  213. cfgName: "invalid/allowlist_global_regextarget",
  214. cfg: Config{},
  215. wantError: errors.New("[[allowlists]] unknown allowlist |regexTarget| 'mtach' (expected 'match', 'line')"),
  216. },
  217. // Rule
  218. {
  219. cfgName: "valid/allowlist_rule_old_compat",
  220. cfg: Config{
  221. Rules: map[string]Rule{"example": {
  222. RuleID: "example",
  223. Regex: regexp.MustCompile(`example\d+`),
  224. Tags: []string{},
  225. Keywords: []string{},
  226. Allowlists: []*Allowlist{
  227. {
  228. MatchCondition: AllowlistMatchOr,
  229. Regexes: []*regexp.Regexp{regexp.MustCompile("123")},
  230. },
  231. },
  232. }},
  233. },
  234. },
  235. {
  236. cfgName: "valid/allowlist_rule_regex",
  237. cfg: Config{
  238. Title: "simple config with allowlist for aws",
  239. Rules: map[string]Rule{"aws-access-key": {
  240. RuleID: "aws-access-key",
  241. Description: "AWS Access Key",
  242. Regex: regexp.MustCompile("(?:A3T[A-Z0-9]|AKIA|ASIA|ABIA|ACCA)[A-Z0-9]{16}"),
  243. Keywords: []string{},
  244. Tags: []string{"key", "AWS"},
  245. Allowlists: []*Allowlist{
  246. {
  247. MatchCondition: AllowlistMatchOr,
  248. Regexes: []*regexp.Regexp{regexp.MustCompile("AKIALALEMEL33243OLIA")},
  249. },
  250. },
  251. }},
  252. },
  253. },
  254. {
  255. cfgName: "valid/allowlist_rule_commit",
  256. cfg: Config{
  257. Title: "simple config with allowlist for a specific commit",
  258. Rules: map[string]Rule{"aws-access-key": {
  259. RuleID: "aws-access-key",
  260. Description: "AWS Access Key",
  261. Regex: regexp.MustCompile("(?:A3T[A-Z0-9]|AKIA|ASIA|ABIA|ACCA)[A-Z0-9]{16}"),
  262. Keywords: []string{},
  263. Tags: []string{"key", "AWS"},
  264. Allowlists: []*Allowlist{
  265. {
  266. MatchCondition: AllowlistMatchOr,
  267. Commits: []string{"allowthiscommit"},
  268. },
  269. },
  270. }},
  271. },
  272. },
  273. {
  274. cfgName: "valid/allowlist_rule_path",
  275. cfg: Config{
  276. Title: "simple config with allowlist for .go files",
  277. Rules: map[string]Rule{"aws-access-key": {
  278. RuleID: "aws-access-key",
  279. Description: "AWS Access Key",
  280. Regex: regexp.MustCompile("(?:A3T[A-Z0-9]|AKIA|ASIA|ABIA|ACCA)[A-Z0-9]{16}"),
  281. Keywords: []string{},
  282. Tags: []string{"key", "AWS"},
  283. Allowlists: []*Allowlist{
  284. {
  285. MatchCondition: AllowlistMatchOr,
  286. Paths: []*regexp.Regexp{regexp.MustCompile(".go")},
  287. },
  288. },
  289. }},
  290. },
  291. },
  292. {
  293. cfgName: "invalid/allowlist_rule_empty",
  294. cfg: Config{},
  295. wantError: errors.New("example: [[rules.allowlists]] must contain at least one check for: commits, paths, regexes, or stopwords"),
  296. },
  297. {
  298. cfgName: "invalid/allowlist_rule_old_and_new",
  299. cfg: Config{},
  300. wantError: errors.New("example: [rules.allowlist] is deprecated, it cannot be used alongside [[rules.allowlist]]"),
  301. },
  302. {
  303. cfgName: "invalid/allowlist_rule_regextarget",
  304. cfg: Config{},
  305. wantError: errors.New("example: [[rules.allowlists]] unknown allowlist |regexTarget| 'mtach' (expected 'match', 'line')"),
  306. },
  307. }
  308. for _, tt := range tests {
  309. t.Run(tt.cfgName, func(t *testing.T) {
  310. testTranslate(t, tt)
  311. })
  312. }
  313. }
  314. func TestTranslateExtend(t *testing.T) {
  315. tests := []translateCase{
  316. // Valid
  317. {
  318. cfgName: "valid/extend",
  319. cfg: Config{
  320. Rules: map[string]Rule{
  321. "aws-access-key": {
  322. RuleID: "aws-access-key",
  323. Description: "AWS Access Key",
  324. Regex: regexp.MustCompile("(?:A3T[A-Z0-9]|AKIA|ASIA|ABIA|ACCA)[A-Z0-9]{16}"),
  325. Keywords: []string{},
  326. Tags: []string{"key", "AWS"},
  327. },
  328. "aws-secret-key": {
  329. RuleID: "aws-secret-key",
  330. Description: "AWS Secret Key",
  331. Regex: regexp.MustCompile(`(?i)aws_(.{0,20})?=?.[\'\"0-9a-zA-Z\/+]{40}`),
  332. Keywords: []string{},
  333. Tags: []string{"key", "AWS"},
  334. },
  335. "aws-secret-key-again": {
  336. RuleID: "aws-secret-key-again",
  337. Description: "AWS Secret Key",
  338. Regex: regexp.MustCompile(`(?i)aws_(.{0,20})?=?.[\'\"0-9a-zA-Z\/+]{40}`),
  339. Keywords: []string{},
  340. Tags: []string{"key", "AWS"},
  341. },
  342. },
  343. },
  344. },
  345. {
  346. cfgName: "valid/extend_disabled",
  347. cfg: Config{
  348. Title: "gitleaks extend disable",
  349. Rules: map[string]Rule{
  350. "aws-secret-key": {
  351. RuleID: "aws-secret-key",
  352. Regex: regexp.MustCompile(`(?i)aws_(.{0,20})?=?.[\'\"0-9a-zA-Z\/+]{40}`),
  353. Tags: []string{"key", "AWS"},
  354. Keywords: []string{},
  355. },
  356. "pypi-upload-token": {
  357. RuleID: "pypi-upload-token",
  358. Regex: regexp.MustCompile(`pypi-AgEIcHlwaS5vcmc[A-Za-z0-9\-_]{50,1000}`),
  359. Tags: []string{},
  360. Keywords: []string{},
  361. },
  362. },
  363. },
  364. },
  365. {
  366. cfgName: "valid/extend_rule_no_regexpath",
  367. cfg: Config{
  368. Rules: map[string]Rule{
  369. "aws-secret-key-again-again": {
  370. RuleID: "aws-secret-key-again-again",
  371. Description: "AWS Secret Key",
  372. Regex: regexp.MustCompile(`(?i)aws_(.{0,20})?=?.[\'\"0-9a-zA-Z\/+]{40}`),
  373. Keywords: []string{},
  374. Tags: []string{"key", "AWS"},
  375. Allowlists: []*Allowlist{
  376. {
  377. Description: "False positive. Keys used for colors match the rule, and should be excluded.",
  378. MatchCondition: AllowlistMatchOr,
  379. Paths: []*regexp.Regexp{regexp.MustCompile(`something.py`)},
  380. },
  381. },
  382. },
  383. },
  384. },
  385. },
  386. {
  387. cfgName: "valid/extend_rule_override_description",
  388. rules: []string{"aws-access-key"},
  389. cfg: Config{
  390. Title: "override a built-in rule's description",
  391. Rules: map[string]Rule{"aws-access-key": {
  392. RuleID: "aws-access-key",
  393. Description: "Puppy Doggy",
  394. Regex: regexp.MustCompile("(?:A3T[A-Z0-9]|AKIA|ASIA|ABIA|ACCA)[A-Z0-9]{16}"),
  395. Keywords: []string{},
  396. Tags: []string{"key", "AWS"},
  397. },
  398. },
  399. },
  400. },
  401. {
  402. cfgName: "valid/extend_rule_override_path",
  403. rules: []string{"aws-access-key"},
  404. cfg: Config{
  405. Title: "override a built-in rule's path",
  406. Rules: map[string]Rule{"aws-access-key": {
  407. RuleID: "aws-access-key",
  408. Description: "AWS Access Key",
  409. Regex: regexp.MustCompile("(?:A3T[A-Z0-9]|AKIA|ASIA|ABIA|ACCA)[A-Z0-9]{16}"),
  410. Path: regexp.MustCompile("(?:puppy)"),
  411. Keywords: []string{},
  412. Tags: []string{"key", "AWS"},
  413. },
  414. },
  415. },
  416. },
  417. {
  418. cfgName: "valid/extend_rule_override_regex",
  419. rules: []string{"aws-access-key"},
  420. cfg: Config{
  421. Title: "override a built-in rule's regex",
  422. Rules: map[string]Rule{"aws-access-key": {
  423. RuleID: "aws-access-key",
  424. Description: "AWS Access Key",
  425. Regex: regexp.MustCompile("(?:a)"),
  426. Keywords: []string{},
  427. Tags: []string{"key", "AWS"},
  428. },
  429. },
  430. },
  431. },
  432. {
  433. cfgName: "valid/extend_rule_override_secret_group",
  434. rules: []string{"aws-access-key"},
  435. cfg: Config{
  436. Title: "override a built-in rule's secretGroup",
  437. Rules: map[string]Rule{"aws-access-key": {
  438. RuleID: "aws-access-key",
  439. Description: "AWS Access Key",
  440. Regex: regexp.MustCompile("(a)(a)"),
  441. SecretGroup: 2,
  442. Keywords: []string{},
  443. Tags: []string{"key", "AWS"},
  444. },
  445. },
  446. },
  447. },
  448. {
  449. cfgName: "valid/extend_rule_override_entropy",
  450. rules: []string{"aws-access-key"},
  451. cfg: Config{
  452. Title: "override a built-in rule's entropy",
  453. Rules: map[string]Rule{"aws-access-key": {
  454. RuleID: "aws-access-key",
  455. Description: "AWS Access Key",
  456. Regex: regexp.MustCompile("(?:A3T[A-Z0-9]|AKIA|ASIA|ABIA|ACCA)[A-Z0-9]{16}"),
  457. Entropy: 999.0,
  458. Keywords: []string{},
  459. Tags: []string{"key", "AWS"},
  460. },
  461. },
  462. },
  463. },
  464. {
  465. cfgName: "valid/extend_rule_override_keywords",
  466. rules: []string{"aws-access-key"},
  467. cfg: Config{
  468. Title: "override a built-in rule's keywords",
  469. Rules: map[string]Rule{"aws-access-key": {
  470. RuleID: "aws-access-key",
  471. Description: "AWS Access Key",
  472. Regex: regexp.MustCompile("(?:A3T[A-Z0-9]|AKIA|ASIA|ABIA|ACCA)[A-Z0-9]{16}"),
  473. Keywords: []string{"puppy"},
  474. Tags: []string{"key", "AWS"},
  475. },
  476. },
  477. },
  478. },
  479. {
  480. cfgName: "valid/extend_rule_override_tags",
  481. rules: []string{"aws-access-key"},
  482. cfg: Config{
  483. Title: "override a built-in rule's tags",
  484. Rules: map[string]Rule{"aws-access-key": {
  485. RuleID: "aws-access-key",
  486. Description: "AWS Access Key",
  487. Regex: regexp.MustCompile("(?:A3T[A-Z0-9]|AKIA|ASIA|ABIA|ACCA)[A-Z0-9]{16}"),
  488. Keywords: []string{},
  489. Tags: []string{"key", "AWS", "puppy"},
  490. },
  491. },
  492. },
  493. },
  494. {
  495. cfgName: "valid/extend_rule_allowlist_or",
  496. cfg: Config{
  497. Title: "gitleaks extended 3",
  498. Rules: map[string]Rule{
  499. "aws-secret-key-again-again": {
  500. RuleID: "aws-secret-key-again-again",
  501. Description: "AWS Secret Key",
  502. Regex: regexp.MustCompile(`(?i)aws_(.{0,20})?=?.[\'\"0-9a-zA-Z\/+]{40}`),
  503. Keywords: []string{},
  504. Tags: []string{"key", "AWS"},
  505. Allowlists: []*Allowlist{
  506. {
  507. MatchCondition: AllowlistMatchOr,
  508. StopWords: []string{"fake"},
  509. },
  510. {
  511. MatchCondition: AllowlistMatchOr,
  512. Commits: []string{"abcdefg1"},
  513. Paths: []*regexp.Regexp{regexp.MustCompile(`ignore\.xaml`)},
  514. Regexes: []*regexp.Regexp{regexp.MustCompile(`foo.+bar`)},
  515. RegexTarget: "line",
  516. StopWords: []string{"example"},
  517. },
  518. },
  519. },
  520. },
  521. },
  522. },
  523. {
  524. cfgName: "valid/extend_rule_allowlist_and",
  525. cfg: Config{
  526. Title: "gitleaks extended 3",
  527. Rules: map[string]Rule{
  528. "aws-secret-key-again-again": {
  529. RuleID: "aws-secret-key-again-again",
  530. Description: "AWS Secret Key",
  531. Regex: regexp.MustCompile(`(?i)aws_(.{0,20})?=?.[\'\"0-9a-zA-Z\/+]{40}`),
  532. Keywords: []string{},
  533. Tags: []string{"key", "AWS"},
  534. Allowlists: []*Allowlist{
  535. {
  536. MatchCondition: AllowlistMatchOr,
  537. StopWords: []string{"fake"},
  538. },
  539. {
  540. MatchCondition: AllowlistMatchAnd,
  541. Commits: []string{"abcdefg1"},
  542. Paths: []*regexp.Regexp{regexp.MustCompile(`ignore\.xaml`)},
  543. Regexes: []*regexp.Regexp{regexp.MustCompile(`foo.+bar`)},
  544. RegexTarget: "line",
  545. StopWords: []string{"example"},
  546. },
  547. },
  548. },
  549. },
  550. },
  551. },
  552. // Invalid
  553. {
  554. cfgName: "invalid/extend_invalid_ruleid",
  555. wantError: errors.New("rule |id| is missing or empty"),
  556. },
  557. }
  558. for _, tt := range tests {
  559. t.Run(tt.cfgName, func(t *testing.T) {
  560. testTranslate(t, tt)
  561. })
  562. }
  563. }
  564. func testTranslate(t *testing.T, test translateCase) {
  565. t.Helper()
  566. t.Cleanup(func() {
  567. extendDepth = 0
  568. viper.Reset()
  569. })
  570. viper.AddConfigPath(configPath)
  571. viper.SetConfigName(test.cfgName)
  572. viper.SetConfigType("toml")
  573. err := viper.ReadInConfig()
  574. require.NoError(t, err)
  575. var vc ViperConfig
  576. err = viper.Unmarshal(&vc)
  577. require.NoError(t, err)
  578. cfg, err := vc.Translate()
  579. if err != nil {
  580. if test.wantError != nil {
  581. assert.EqualError(t, err, test.wantError.Error())
  582. } else {
  583. require.NoError(t, err)
  584. }
  585. } else {
  586. if test.wantError != nil {
  587. t.Fatalf("expected error but got none: %v", test.wantError)
  588. return
  589. }
  590. }
  591. if len(test.rules) > 0 {
  592. rules := make(map[string]Rule)
  593. for _, name := range test.rules {
  594. rules[name] = cfg.Rules[name]
  595. }
  596. cfg.Rules = rules
  597. }
  598. opts := cmp.Options{
  599. cmp.Comparer(regexComparer),
  600. cmpopts.IgnoreUnexported(Rule{}, Allowlist{}),
  601. }
  602. if diff := cmp.Diff(test.cfg.Title, cfg.Title); diff != "" {
  603. t.Errorf("%s diff: (-want +got)\n%s", test.cfgName, diff)
  604. }
  605. if diff := cmp.Diff(test.cfg.Rules, cfg.Rules, opts); diff != "" {
  606. t.Errorf("%s diff: (-want +got)\n%s", test.cfgName, diff)
  607. }
  608. if diff := cmp.Diff(test.cfg.Allowlists, cfg.Allowlists, opts); diff != "" {
  609. t.Errorf("%s diff: (-want +got)\n%s", test.cfgName, diff)
  610. }
  611. }
  612. func TestExtendedRuleKeywordsAreDowncase(t *testing.T) {
  613. tests := []struct {
  614. name string
  615. cfgName string
  616. expectedKeywords string
  617. }{
  618. {
  619. name: "Extend base rule that includes AWS keyword with new attribute",
  620. cfgName: "valid/extend_base_rule_including_keywords_with_attribute",
  621. expectedKeywords: "aws",
  622. },
  623. {
  624. name: "Extend base with a new rule with CMS keyword",
  625. cfgName: "valid/extend_rule_new",
  626. expectedKeywords: "cms",
  627. },
  628. }
  629. for _, tt := range tests {
  630. t.Run(tt.name, func(t *testing.T) {
  631. t.Cleanup(func() {
  632. viper.Reset()
  633. })
  634. viper.AddConfigPath(configPath)
  635. viper.SetConfigName(tt.cfgName)
  636. viper.SetConfigType("toml")
  637. err := viper.ReadInConfig()
  638. require.NoError(t, err)
  639. var vc ViperConfig
  640. err = viper.Unmarshal(&vc)
  641. require.NoError(t, err)
  642. cfg, err := vc.Translate()
  643. require.NoError(t, err)
  644. _, exists := cfg.Keywords[tt.expectedKeywords]
  645. require.Truef(t, exists, "The expected keyword %s did not exist as a key of cfg.Keywords", tt.expectedKeywords)
  646. })
  647. }
  648. }