Skip to content

Commit 1eb6577

Browse files
committed
test(rules): add unit tests for template rule configurations and active rule handling
1 parent c4b3330 commit 1eb6577

5 files changed

Lines changed: 715 additions & 0 deletions

File tree

build.gradle.kts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ dependencies {
3737
testImplementation(kotlin("test"))
3838
}
3939

40+
tasks.test {
41+
useJUnitPlatform()
42+
}
43+
4044
application {
4145
mainClass.set("br.com.felipezorzo.zpa.cli.MainKt")
4246
}
Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
package br.com.felipezorzo.zpa.cli
2+
3+
import br.com.felipezorzo.zpa.cli.config.BaseRuleCategory
4+
import br.com.felipezorzo.zpa.cli.config.ConfigFile
5+
import br.com.felipezorzo.zpa.cli.config.RuleConfiguration
6+
import br.com.felipezorzo.zpa.cli.config.RuleLevel
7+
import br.com.felipezorzo.zpa.cli.config.RuleOptions
8+
import br.com.felipezorzo.zpa.cli.rules.CliActiveRules
9+
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
10+
import com.felipebz.zpa.CustomAnnotationBasedRulesDefinition
11+
import com.felipebz.zpa.checks.XPathCheck
12+
import com.felipebz.zpa.rules.ActiveRuleConfiguration
13+
import com.felipebz.zpa.rules.Repository
14+
import com.felipebz.zpa.rules.RuleMetadataLoader
15+
import com.felipebz.zpa.rules.ZpaChecks
16+
import java.nio.file.Files
17+
import kotlin.test.Test
18+
import kotlin.test.assertEquals
19+
import kotlin.test.assertNotSame
20+
import kotlin.test.assertTrue
21+
22+
class TemplateRuleInstancesTest {
23+
24+
@Test
25+
fun zpaChecksCreatesIndependentVisitorsForTwoInstancesOfOneTemplate() {
26+
val repository = xpathRepository()
27+
val config = templateConfig()
28+
val activeRules = configuredActiveRules(config, repository)
29+
30+
val checks = ZpaChecks(activeRules, repository.key, RuleMetadataLoader())
31+
.addAnnotatedChecks(listOf(XPathCheck::class.java))
32+
.all()
33+
.filterIsInstance<XPathCheck>()
34+
.associateBy { it.activeRule.ruleKey.rule }
35+
36+
assertEquals(setOf("FirstXPath", "SecondXPath"), checks.keys)
37+
38+
val first = checks.getValue("FirstXPath")
39+
val second = checks.getValue("SecondXPath")
40+
assertNotSame(first, second)
41+
42+
assertEquals("//SELECT_COLUMN/MULTIPLICATION", first.xpathQuery)
43+
assertEquals("message A", first.message)
44+
assertEquals("MINOR", first.activeRule.severity)
45+
assertEquals("zpa:FirstXPath", first.activeRule.ruleKey.toString())
46+
assertEquals("XPath", first.activeRule.templateRuleKey)
47+
48+
assertEquals("//SELECT_COLUMN/MULTIPLICATION", second.xpathQuery)
49+
assertEquals("message B", second.message)
50+
assertEquals("MAJOR", second.activeRule.severity)
51+
assertEquals("zpa:SecondXPath", second.activeRule.ruleKey.toString())
52+
assertEquals("XPath", second.activeRule.templateRuleKey)
53+
assertTrue(first.activeRule !== second.activeRule)
54+
}
55+
56+
@Test
57+
fun explicitTemplateReferenceOnTheOriginalRuleCreatesOnlyOneCheck() {
58+
val repository = xpathRepository()
59+
val config = ConfigFile(
60+
base = BaseRuleCategory.NONE,
61+
rules = mapOf(
62+
"XPath" to options(
63+
level = RuleLevel.MINOR,
64+
templateRuleKey = "XPath",
65+
parameters = mapOf(
66+
"xpathQuery" to "//SELECT_COLUMN/MULTIPLICATION",
67+
"message" to "message"
68+
)
69+
)
70+
)
71+
)
72+
73+
val checks = ZpaChecks(configuredActiveRules(config, repository), repository.key, RuleMetadataLoader())
74+
.addAnnotatedChecks(listOf(XPathCheck::class.java))
75+
.all()
76+
.filterIsInstance<XPathCheck>()
77+
78+
assertEquals(1, checks.size)
79+
assertEquals("zpa:XPath", checks.single().activeRule.ruleKey.toString())
80+
}
81+
82+
@Test
83+
fun cliProducesTwoDistinctGenericIssuesWithIndependentConfiguration() {
84+
val root = Files.createTempDirectory("zpa-cli-template-instances").toFile()
85+
try {
86+
val sourceDirectory = root.resolve("sources").apply { mkdirs() }
87+
sourceDirectory.resolve("multiplication.sql").writeText(
88+
"SELECT * FROM table_name;\n"
89+
)
90+
91+
val configPath = root.resolve("config.json").apply {
92+
writeText(
93+
"""
94+
{
95+
"base": "none",
96+
"rules": {
97+
"FirstXPath": {
98+
"level": "minor",
99+
"templateRuleKey": "XPath",
100+
"parameters": {
101+
"xpathQuery": "//SELECT_COLUMN/MULTIPLICATION",
102+
"message": "message A"
103+
}
104+
},
105+
"SecondXPath": {
106+
"level": "major",
107+
"templateRuleKey": "zpa:XPath",
108+
"parameters": {
109+
"xpathQuery": "//SELECT_COLUMN/MULTIPLICATION",
110+
"message": "message B"
111+
}
112+
}
113+
}
114+
}
115+
""".trimIndent()
116+
)
117+
}
118+
val issuesPath = root.resolve("issues.json")
119+
120+
Main(
121+
Arguments().apply {
122+
sources = sourceDirectory.absolutePath
123+
configFile = configPath.absolutePath
124+
outputFormat = GENERIC_ISSUE_FORMAT
125+
outputFile = issuesPath.absolutePath
126+
extensions = "sql"
127+
}
128+
).run()
129+
130+
val issues = jacksonObjectMapper()
131+
.readTree(issuesPath)
132+
.get("issues")
133+
.elements()
134+
.asSequence()
135+
.map { issue ->
136+
Triple(
137+
issue.get("ruleId").asText(),
138+
issue.get("severity").asText(),
139+
issue.get("primaryLocation").get("message").asText()
140+
)
141+
}
142+
.toList()
143+
.sortedBy { it.first }
144+
145+
assertEquals(
146+
listOf(
147+
Triple("zpa:FirstXPath", "MINOR", "message A"),
148+
Triple("zpa:SecondXPath", "MAJOR", "message B")
149+
),
150+
issues
151+
)
152+
} finally {
153+
root.deleteRecursively()
154+
}
155+
}
156+
157+
private fun xpathRepository(): Repository {
158+
val repository = Repository("zpa")
159+
CustomAnnotationBasedRulesDefinition.load(
160+
repository,
161+
"plsqlopen",
162+
listOf(XPathCheck::class.java),
163+
RuleMetadataLoader()
164+
)
165+
return repository
166+
}
167+
168+
private fun templateConfig(): ConfigFile {
169+
return ConfigFile(
170+
base = BaseRuleCategory.NONE,
171+
rules = mapOf(
172+
"FirstXPath" to options(
173+
level = RuleLevel.MINOR,
174+
templateRuleKey = "XPath",
175+
parameters = mapOf(
176+
"xpathQuery" to "//SELECT_COLUMN/MULTIPLICATION",
177+
"message" to "message A"
178+
)
179+
),
180+
"SecondXPath" to options(
181+
level = RuleLevel.MAJOR,
182+
templateRuleKey = "zpa:XPath",
183+
parameters = mapOf(
184+
"xpathQuery" to "//SELECT_COLUMN/MULTIPLICATION",
185+
"message" to "message B"
186+
)
187+
)
188+
)
189+
)
190+
}
191+
192+
private fun configuredActiveRules(config: ConfigFile, repository: Repository): CliActiveRules {
193+
return CliActiveRules(config)
194+
.addRepository(repository)
195+
.addRuleConfigurer { repo, rule, configuration: ActiveRuleConfiguration ->
196+
var ruleConfig = config.rules["${repo.key}:${rule.key}"] ?: config.rules[rule.key]
197+
if (config.base == BaseRuleCategory.DEFAULT && rule.isActivatedByDefault) {
198+
ruleConfig = ruleConfig ?: RuleConfiguration()
199+
}
200+
201+
if (ruleConfig == null || ruleConfig.options.level == RuleLevel.OFF) {
202+
return@addRuleConfigurer false
203+
}
204+
205+
if (ruleConfig.options.level != RuleLevel.ON) {
206+
configuration.severity = ruleConfig.options.level.toString()
207+
}
208+
configuration.parameters.putAll(ruleConfig.options.parameters)
209+
true
210+
}
211+
}
212+
213+
private fun options(
214+
level: RuleLevel,
215+
templateRuleKey: String,
216+
parameters: Map<String, String>
217+
): RuleConfiguration {
218+
return RuleConfiguration().apply {
219+
this.options = RuleOptions().apply {
220+
this.level = level
221+
this.templateRuleKey = templateRuleKey
222+
this.parameters = parameters
223+
}
224+
}
225+
}
226+
}
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
package br.com.felipezorzo.zpa.cli.config
2+
3+
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
4+
import java.io.File
5+
import kotlin.test.Test
6+
import kotlin.test.assertEquals
7+
import kotlin.test.assertFalse
8+
import kotlin.test.assertTrue
9+
10+
class ConfigFileTest {
11+
12+
private val mapper = jacksonObjectMapper()
13+
14+
@Test
15+
fun templateRuleKeyIsOptionalAndDeserializesFromRuleOptions() {
16+
val config = mapper.readValue(
17+
"""
18+
{
19+
"rules": {
20+
"FirstXPath": {
21+
"level": "minor",
22+
"parameters": {
23+
"xpathQuery": "//SELECT_COLUMN/MULTIPLICATION",
24+
"message": "message A"
25+
},
26+
"templateRuleKey": "zpa:XPath"
27+
}
28+
}
29+
}
30+
""".trimIndent(),
31+
ConfigFile::class.java
32+
)
33+
34+
val options = config.rules.getValue("FirstXPath").options
35+
assertEquals(RuleLevel.MINOR, options.level)
36+
assertEquals(
37+
mapOf(
38+
"xpathQuery" to "//SELECT_COLUMN/MULTIPLICATION",
39+
"message" to "message A"
40+
),
41+
options.parameters
42+
)
43+
assertEquals("zpa:XPath", options.templateRuleKey)
44+
45+
val legacyOptions = mapper.readValue(
46+
"""{"rules":{"XPath":"minor"}}""",
47+
ConfigFile::class.java
48+
).rules.getValue("XPath").options
49+
assertEquals(RuleLevel.MINOR, legacyOptions.level)
50+
assertTrue(legacyOptions.parameters.isEmpty())
51+
assertEquals(null, legacyOptions.templateRuleKey)
52+
}
53+
54+
@Test
55+
fun legacyObjectConfigurationRetainsLevelAndParameters() {
56+
val config = mapper.readValue(
57+
"""
58+
{
59+
"rules": {
60+
"XPath": {
61+
"level": "minor",
62+
"parameters": {
63+
"xpathQuery": "//STATEMENT",
64+
"message": "Avoid statements"
65+
}
66+
}
67+
}
68+
}
69+
""".trimIndent(),
70+
ConfigFile::class.java
71+
)
72+
73+
val options = config.rules.getValue("XPath").options
74+
assertEquals(RuleLevel.MINOR, options.level)
75+
assertEquals("//STATEMENT", options.parameters.getValue("xpathQuery"))
76+
assertEquals("Avoid statements", options.parameters.getValue("message"))
77+
assertEquals(null, options.templateRuleKey)
78+
79+
val serialized = mapper.writeValueAsString(config)
80+
assertFalse(serialized.contains("templateRuleKey"))
81+
assertEquals(
82+
"//STATEMENT",
83+
mapper.readTree(serialized).get("rules").get("XPath").get("parameters").get("xpathQuery").asText()
84+
)
85+
}
86+
87+
@Test
88+
fun templateRuleKeySurvivesSerializationRoundTrip() {
89+
val config = ConfigFile(
90+
base = BaseRuleCategory.NONE,
91+
rules = mapOf(
92+
"FirstXPath" to RuleConfiguration().apply {
93+
options = RuleOptions().apply {
94+
level = RuleLevel.MAJOR
95+
templateRuleKey = "zpa:XPath"
96+
}
97+
}
98+
)
99+
)
100+
101+
val serialized = mapper.writeValueAsString(config)
102+
val serializedRule = mapper.readTree(serialized).get("rules").get("FirstXPath")
103+
assertTrue(serializedRule.isObject)
104+
assertEquals("zpa:XPath", serializedRule.get("templateRuleKey").asText())
105+
106+
val roundTrip = mapper.readValue(serialized, ConfigFile::class.java)
107+
assertEquals(RuleLevel.MAJOR, roundTrip.rules.getValue("FirstXPath").options.level)
108+
assertEquals("zpa:XPath", roundTrip.rules.getValue("FirstXPath").options.templateRuleKey)
109+
}
110+
111+
@Test
112+
fun schemaDocumentsTemplateRuleKeyAsAnOptionalString() {
113+
val schema = mapper.readTree(File("schema.json"))
114+
val templateRuleKey = schema.get("\$defs")
115+
.get("RuleOptions")
116+
.get("properties")
117+
.get("templateRuleKey")
118+
119+
assertEquals("string", templateRuleKey.get("type").asText())
120+
assertTrue(templateRuleKey.get("description").asText().contains("repository:rule"))
121+
}
122+
}

0 commit comments

Comments
 (0)