*/
package org.sonar.api.server.rule;
-import org.apache.commons.lang.StringUtils;
-import org.codehaus.staxmate.SMInputFactory;
-import org.codehaus.staxmate.in.SMHierarchicCursor;
-import org.codehaus.staxmate.in.SMInputCursor;
-import org.sonar.api.server.ServerSide;
-import org.sonar.api.rule.RuleStatus;
-import org.sonar.api.rule.Severity;
-import org.sonar.check.Cardinality;
-
-import javax.xml.stream.XMLInputFactory;
-import javax.xml.stream.XMLStreamException;
-
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
+import javax.annotation.Nullable;
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLStreamException;
+import org.codehaus.staxmate.SMInputFactory;
+import org.codehaus.staxmate.in.SMHierarchicCursor;
+import org.codehaus.staxmate.in.SMInputCursor;
+import org.sonar.api.rule.RuleStatus;
+import org.sonar.api.rule.Severity;
+import org.sonar.api.server.ServerSide;
+import org.sonar.api.server.debt.DebtRemediationFunction;
+import org.sonar.check.Cardinality;
+
+import static java.lang.String.format;
+import static org.apache.commons.lang.StringUtils.equalsIgnoreCase;
+import static org.apache.commons.lang.StringUtils.isNotBlank;
+import static org.apache.commons.lang.StringUtils.trim;
/**
- * Helper class to load {@link RulesDefinition} extension point from a XML file.
+ * Loads definitions of rules from a XML file.
*
- * <h3>Example</h3>
+ * <h3>Usage</h3>
* <pre>
- * public class MyRules implements RulesDefinition {
+ * public class MyJsRulesDefinition implements RulesDefinition {
*
+ * private static final String PATH = "my-js-rules.xml";
* private final RulesDefinitionXmlLoader xmlLoader;
*
- * public MyRules(RulesDefinitionXmlLoader xmlLoader) {
+ * public MyJsRulesDefinition(RulesDefinitionXmlLoader xmlLoader) {
* this.xmlLoader = xmlLoader;
* }
*
* {@literal @}Override
* public void define(Context context) {
- * NewRepository repository = context.createRepository("my-repo", "my-lang");
- * xmlLoader.load(repository, getClass().getResourceAsStream("/my-rules.xml"), StandardCharsets.UTF_8.name());
- * repository.done();
+ * try (Reader reader = new InputStreamReader(getClass().getResourceAsStream(PATH), StandardCharsets.UTF_8)) {
+ * NewRepository repository = context.createRepository("my_js", "js").setName("My Javascript Analyzer");
+ * xmlLoader.load(repository, reader);
+ * repository.done();
+ * } catch (IOException e) {
+ * throw new IllegalStateException(String.format("Fail to read file %s", PATH), e);
+ * }
* }
* }
* </pre>
* <pre>
* <rules>
* <rule>
- * <key>the-required-rule-key</key>*
- * <name>The required purpose of the rule</name>
- ** <description>
- * <![CDATA[Required HTML description]]>
+ * <!-- required key -->
+ * <key>the-rule-key</key>
+ *
+ * <!-- required name -->
+ * <name>The purpose of the rule</name>
+ *
+ * <!-- required description, in HTML format -->
+ * <description>
+ * <![CDATA[The HTML description]]>
* </description>
*
* <!-- Optional key for configuration of some rule engines -->
* <param>
* <key>the-param-key</key>
* <description>
- * <![CDATA[the optional param description]]>
+ * <![CDATA[the optional description, in HTML format]]>
* </description>
- * <!-- Optional field to define the default value used when enabling the rule in a Quality profile -->
+ * <!-- Optional default value, used when enabling the rule in a Quality profile -->
* <defaultValue>42</defaultValue>
* </param>
* <param>
* <key>another-param</key>
* </param>
*
- * <!-- Deprecated field, replaced by "internalKey" -->
+ * <!-- SQALE debt - key of sub-characteristic -->
+ * <!-- See {@link org.sonar.api.server.rule.RulesDefinition.SubCharacteristics} for core supported values.
+ * Any other values can be used. If sub-characteristic does not exist at runtime in the SQALE model,
+ * then the rule is created without any sub-characteristic. -->
+ * <!-- Since 5.3 -->
+ * <debtSubCharacteristic>MODULARITY</debtSubCharacteristic>
+ *
+ * <!-- SQALE debt - type of debt remediation function -->
+ * <!-- See enum {@link org.sonar.api.server.debt.DebtRemediationFunction.Type} for supported values -->
+ * <!-- Since 5.3 -->
+ * <debtRemediationFunction>LINEAR_OFFSET</debtRemediationFunction>
+ *
+ * <!-- SQALE debt - raw description of the "effort to fix", used for some types of remediation functions. -->
+ * <!-- See {@link org.sonar.api.server.rule.RulesDefinition.NewRule#setEffortToFixDescription(String)} -->
+ * <!-- Since 5.3 -->
+ * <effortToFixDescription>Effort to test one uncovered condition</effortToFixDescription>
+ *
+ * <!-- SQALE debt - coefficient of debt remediation function. Must be defined only for some function types. -->
+ * <!-- See {@link org.sonar.api.server.rule.RulesDefinition.DebtRemediationFunctions} -->
+ * <!-- Since 5.3 -->
+ * <debtRemediationFunctionCoefficient>10min</debtRemediationFunctionCoefficient>
+ *
+ * <!-- SQALE debt - offset of debt remediation function. Must be defined only for some function types. -->
+ * <!-- See {@link org.sonar.api.server.rule.RulesDefinition.DebtRemediationFunctions} -->
+ * <!-- Since 5.3 -->
+ * <debtRemediationFunctionOffset>2min</debtRemediationFunctionOffset>
+ *
+ * <!-- deprecated field, replaced by "internalKey" -->
* <configKey>Checker/TreeWalker/LocalVariableName</configKey>
*
- * <!-- Deprecated field, replaced by "severity" -->
+ * <!-- deprecated field, replaced by "severity" -->
* <priority>BLOCKER</priority>
* </rule>
* </rules>
* </pre>
*
+ * <h3>XML Example</h3>
+ * <pre>
+ * <rules>
+ * <rule>
+ * <key>S1442</key>
+ * <name>"alert(...)" should not be used</name>
+ * <description>alert(...) can be useful for debugging during development, but ...</description>
+ * <tag>cwe</tag>
+ * <tag>security</tag>
+ * <tag>user-experience</tag>
+ * <debtSubCharacteristic>SECURITY_FEATURES</debtSubCharacteristic>
+ * <debtRemediationFunction>CONSTANT_ISSUE</debtRemediationFunction>
+ * <debtRemediationFunctionOffset>10min</debtRemediationFunctionOffset>
+ * </rule>
+ *
+ * <!-- another rules... -->
+ * </rules>
+ * </pre>
+ *
* @see org.sonar.api.server.rule.RulesDefinition
* @since 4.3
*/
@ServerSide
public class RulesDefinitionXmlLoader {
+ /**
+ * Loads rules by reading the XML input stream. The input stream is not always closed by the method, so it
+ * should be handled by the caller.
+ * @since 4.3
+ */
public void load(RulesDefinition.NewRepository repo, InputStream input, String encoding) {
load(repo, input, Charset.forName(encoding));
}
}
}
+ /**
+ * Loads rules by reading the XML input stream. The reader is not closed by the method, so it
+ * should be handled by the caller.
+ * @since 4.3
+ */
public void load(RulesDefinition.NewRepository repo, Reader reader) {
XMLInputFactory xmlFactory = XMLInputFactory.newInstance();
xmlFactory.setProperty(XMLInputFactory.IS_COALESCING, Boolean.TRUE);
String description = null;
String internalKey = null;
String severity = Severity.defaultSeverity();
- String status = null;
- Cardinality cardinality = Cardinality.SINGLE;
+ RuleStatus status = RuleStatus.defaultStatus();
+ boolean template = false;
+ String effortToFixDescription = null;
+ String debtSubCharacteristic = null;
+ String debtRemediationFunction = null;
+ String debtRemediationFunctionOffset = null;
+ String debtRemediationFunctionCoeff = null;
List<ParamStruct> params = new ArrayList<>();
List<String> tags = new ArrayList<>();
/* BACKWARD COMPATIBILITY WITH VERY OLD FORMAT */
String keyAttribute = ruleC.getAttrValue("key");
- if (StringUtils.isNotBlank(keyAttribute)) {
- key = StringUtils.trim(keyAttribute);
+ if (isNotBlank(keyAttribute)) {
+ key = trim(keyAttribute);
}
String priorityAttribute = ruleC.getAttrValue("priority");
- if (StringUtils.isNotBlank(priorityAttribute)) {
- severity = StringUtils.trim(priorityAttribute);
+ if (isNotBlank(priorityAttribute)) {
+ severity = trim(priorityAttribute);
}
SMInputCursor cursor = ruleC.childElementCursor();
while (cursor.getNext() != null) {
String nodeName = cursor.getLocalName();
- if (StringUtils.equalsIgnoreCase("name", nodeName)) {
- name = StringUtils.trim(cursor.collectDescendantText(false));
+ if (equalsIgnoreCase("name", nodeName)) {
+ name = trim(cursor.collectDescendantText(false));
- } else if (StringUtils.equalsIgnoreCase("description", nodeName)) {
- description = StringUtils.trim(cursor.collectDescendantText(false));
+ } else if (equalsIgnoreCase("description", nodeName)) {
+ description = trim(cursor.collectDescendantText(false));
- } else if (StringUtils.equalsIgnoreCase("key", nodeName)) {
- key = StringUtils.trim(cursor.collectDescendantText(false));
+ } else if (equalsIgnoreCase("key", nodeName)) {
+ key = trim(cursor.collectDescendantText(false));
- } else if (StringUtils.equalsIgnoreCase("configKey", nodeName)) {
+ } else if (equalsIgnoreCase("configKey", nodeName)) {
// deprecated field, replaced by internalKey
- internalKey = StringUtils.trim(cursor.collectDescendantText(false));
+ internalKey = trim(cursor.collectDescendantText(false));
- } else if (StringUtils.equalsIgnoreCase("internalKey", nodeName)) {
- internalKey = StringUtils.trim(cursor.collectDescendantText(false));
+ } else if (equalsIgnoreCase("internalKey", nodeName)) {
+ internalKey = trim(cursor.collectDescendantText(false));
- } else if (StringUtils.equalsIgnoreCase("priority", nodeName)) {
+ } else if (equalsIgnoreCase("priority", nodeName)) {
// deprecated field, replaced by severity
- severity = StringUtils.trim(cursor.collectDescendantText(false));
+ severity = trim(cursor.collectDescendantText(false));
+
+ } else if (equalsIgnoreCase("severity", nodeName)) {
+ severity = trim(cursor.collectDescendantText(false));
+
+ } else if (equalsIgnoreCase("cardinality", nodeName)) {
+ template = Cardinality.MULTIPLE == Cardinality.valueOf(trim(cursor.collectDescendantText(false)));
+
+ } else if (equalsIgnoreCase("effortToFixDescription", nodeName)) {
+ effortToFixDescription = trim(cursor.collectDescendantText(false));
- } else if (StringUtils.equalsIgnoreCase("severity", nodeName)) {
- severity = StringUtils.trim(cursor.collectDescendantText(false));
+ } else if (equalsIgnoreCase("debtRemediationFunction", nodeName)) {
+ debtRemediationFunction = trim(cursor.collectDescendantText(false));
- } else if (StringUtils.equalsIgnoreCase("cardinality", nodeName)) {
- cardinality = Cardinality.valueOf(StringUtils.trim(cursor.collectDescendantText(false)));
+ } else if (equalsIgnoreCase("debtRemediationFunctionOffset", nodeName)) {
+ debtRemediationFunctionOffset = trim(cursor.collectDescendantText(false));
- } else if (StringUtils.equalsIgnoreCase("status", nodeName)) {
- status = StringUtils.trim(cursor.collectDescendantText(false));
+ } else if (equalsIgnoreCase("debtRemediationFunctionCoefficient", nodeName)) {
+ debtRemediationFunctionCoeff = trim(cursor.collectDescendantText(false));
- } else if (StringUtils.equalsIgnoreCase("param", nodeName)) {
+ } else if (equalsIgnoreCase("debtSubCharacteristic", nodeName)) {
+ debtSubCharacteristic = trim(cursor.collectDescendantText(false));
+
+ } else if (equalsIgnoreCase("status", nodeName)) {
+ String s = trim(cursor.collectDescendantText(false));
+ if (s != null) {
+ status = RuleStatus.valueOf(s);
+ }
+
+ } else if (equalsIgnoreCase("param", nodeName)) {
params.add(processParameter(cursor));
- } else if (StringUtils.equalsIgnoreCase("tag", nodeName)) {
- tags.add(StringUtils.trim(cursor.collectDescendantText(false)));
+ } else if (equalsIgnoreCase("tag", nodeName)) {
+ tags.add(trim(cursor.collectDescendantText(false)));
}
}
- RulesDefinition.NewRule rule = repo.createRule(key)
- .setHtmlDescription(description)
- .setSeverity(severity)
- .setName(name)
- .setInternalKey(internalKey)
- .setTags(tags.toArray(new String[tags.size()]))
- .setTemplate(cardinality == Cardinality.MULTIPLE);
- if (status != null) {
- rule.setStatus(RuleStatus.valueOf(status));
+
+ try {
+ RulesDefinition.NewRule rule = repo.createRule(key)
+ .setHtmlDescription(description)
+ .setSeverity(severity)
+ .setName(name)
+ .setInternalKey(internalKey)
+ .setTags(tags.toArray(new String[tags.size()]))
+ .setTemplate(template)
+ .setStatus(status)
+ .setEffortToFixDescription(effortToFixDescription)
+ .setDebtSubCharacteristic(debtSubCharacteristic);
+ fillRemediationFunction(rule, debtRemediationFunction, debtRemediationFunctionOffset, debtRemediationFunctionCoeff);
+ fillParams(rule, params);
+ } catch (Exception e) {
+ throw new IllegalArgumentException(format("Fail to load the rule with key [%s:%s]", repo.key(), key), e);
+ }
+ }
+
+ private void fillRemediationFunction(RulesDefinition.NewRule rule, @Nullable String debtRemediationFunction,
+ @Nullable String functionOffset, @Nullable String functionCoeff) {
+ if (isNotBlank(debtRemediationFunction)) {
+ DebtRemediationFunction.Type functionType = DebtRemediationFunction.Type.valueOf(debtRemediationFunction);
+ rule.setDebtRemediationFunction(rule.debtRemediationFunctions().create(functionType, functionCoeff, functionOffset));
}
+ }
+
+ private void fillParams(RulesDefinition.NewRule rule, List<ParamStruct> params) {
for (ParamStruct param : params) {
rule.createParam(param.key)
- .setDefaultValue(param.defaultValue)
- .setType(param.type)
- .setDescription(param.description);
+ .setDefaultValue(param.defaultValue)
+ .setType(param.type)
+ .setDescription(param.description);
}
}
// BACKWARD COMPATIBILITY WITH DEPRECATED FORMAT
String keyAttribute = ruleC.getAttrValue("key");
- if (StringUtils.isNotBlank(keyAttribute)) {
- param.key = StringUtils.trim(keyAttribute);
+ if (isNotBlank(keyAttribute)) {
+ param.key = trim(keyAttribute);
}
// BACKWARD COMPATIBILITY WITH DEPRECATED FORMAT
String typeAttribute = ruleC.getAttrValue("type");
- if (StringUtils.isNotBlank(typeAttribute)) {
+ if (isNotBlank(typeAttribute)) {
param.type = RuleParamType.parse(typeAttribute);
}
SMInputCursor paramC = ruleC.childElementCursor();
while (paramC.getNext() != null) {
String propNodeName = paramC.getLocalName();
- String propText = StringUtils.trim(paramC.collectDescendantText(false));
- if (StringUtils.equalsIgnoreCase("key", propNodeName)) {
+ String propText = trim(paramC.collectDescendantText(false));
+ if (equalsIgnoreCase("key", propNodeName)) {
param.key = propText;
- } else if (StringUtils.equalsIgnoreCase("description", propNodeName)) {
+ } else if (equalsIgnoreCase("description", propNodeName)) {
param.description = propText;
- } else if (StringUtils.equalsIgnoreCase("type", propNodeName)) {
+ } else if (equalsIgnoreCase("type", propNodeName)) {
param.type = RuleParamType.parse(propText);
- } else if (StringUtils.equalsIgnoreCase("defaultValue", propNodeName)) {
+ } else if (equalsIgnoreCase("defaultValue", propNodeName)) {
param.defaultValue = propText;
}
}
*/
package org.sonar.api.server.rule;
+import java.io.InputStream;
+import java.io.StringReader;
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.StandardCharsets;
import org.apache.commons.io.IOUtils;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.sonar.api.rule.RuleStatus;
import org.sonar.api.rule.Severity;
-
-import java.io.InputStream;
-import java.io.UnsupportedEncodingException;
-import java.nio.charset.StandardCharsets;
+import org.sonar.api.server.debt.DebtRemediationFunction;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.fail;
public class RulesDefinitionXmlLoaderTest {
@org.junit.Rule
- public final ExpectedException thrown = ExpectedException.none();
+ public final ExpectedException expectedException = ExpectedException.none();
+
+ RulesDefinitionXmlLoader underTest = new RulesDefinitionXmlLoader();
private RulesDefinition.Repository load(InputStream input, String encoding) {
RulesDefinition.Context context = new RulesDefinition.Context();
RulesDefinition.NewRepository newRepository = context.createRepository("squid", "java");
- new RulesDefinitionXmlLoader().load(newRepository, input, encoding);
+ underTest.load(newRepository, input, encoding);
+ newRepository.done();
+ return context.repository("squid");
+ }
+
+ private RulesDefinition.Repository load(String xml) {
+ RulesDefinition.Context context = new RulesDefinition.Context();
+ RulesDefinition.NewRepository newRepository = context.createRepository("squid", "java");
+ underTest.load(newRepository, new StringReader(xml));
newRepository.done();
return context.repository("squid");
}
@Test
public void parse_xml() {
- InputStream input = getClass().getResourceAsStream("/org/sonar/api/server/rule/RulesDefinitionXmlLoaderTest/rules.xml");
+ InputStream input = getClass().getResourceAsStream("RulesDefinitionXmlLoaderTest/rules.xml");
RulesDefinition.Repository repository = load(input, StandardCharsets.UTF_8.name());
assertThat(repository.rules()).hasSize(2);
@Test
public void fail_if_missing_rule_key() {
- thrown.expect(IllegalStateException.class);
+ expectedException.expect(IllegalStateException.class);
load(IOUtils.toInputStream("<rules><rule><name>Foo</name></rule></rules>"), StandardCharsets.UTF_8.name());
}
@Test
public void fail_if_missing_property_key() {
- thrown.expect(IllegalStateException.class);
+ expectedException.expect(IllegalStateException.class);
load(IOUtils.toInputStream("<rules><rule><key>foo</key><name>Foo</name><param></param></rule></rules>"), StandardCharsets.UTF_8.name());
}
@Test
public void fail_on_invalid_rule_parameter_type() {
- thrown.expect(IllegalStateException.class);
+ expectedException.expect(IllegalStateException.class);
load(IOUtils.toInputStream("<rules><rule><key>foo</key><name>Foo</name><param><key>key</key><type>INVALID</type></param></rule></rules>"), StandardCharsets.UTF_8.name());
}
@Test
public void fail_if_invalid_xml() {
- thrown.expect(IllegalStateException.class);
- thrown.expectMessage("XML is not valid");
+ expectedException.expect(IllegalStateException.class);
+ expectedException.expectMessage("XML is not valid");
- InputStream input = getClass().getResourceAsStream("/org/sonar/api/server/rule/RulesDefinitionXmlLoaderTest/invalid.xml");
+ InputStream input = getClass().getResourceAsStream("RulesDefinitionXmlLoaderTest/invalid.xml");
load(input, StandardCharsets.UTF_8.name());
}
@Test
public void test_utf8_encoding() throws UnsupportedEncodingException {
- InputStream input = getClass().getResourceAsStream("/org/sonar/api/server/rule/RulesDefinitionXmlLoaderTest/utf8.xml");
+ InputStream input = getClass().getResourceAsStream("RulesDefinitionXmlLoaderTest/utf8.xml");
RulesDefinition.Repository repository = load(input, StandardCharsets.UTF_8.name());
assertThat(repository.rules()).hasSize(1);
@Test
public void support_deprecated_format() {
// the deprecated format uses some attributes instead of nodes
- InputStream input = getClass().getResourceAsStream("/org/sonar/api/server/rule/RulesDefinitionXmlLoaderTest/deprecated.xml");
+ InputStream input = getClass().getResourceAsStream("RulesDefinitionXmlLoaderTest/deprecated.xml");
RulesDefinition.Repository repository = load(input, StandardCharsets.UTF_8.name());
assertThat(repository.rules()).hasSize(1);
assertThat(rule.htmlDescription()).isEqualTo("Count methods");
assertThat(rule.param("minMethodsCount")).isNotNull();
}
+
+ @Test
+ public void test_linear_remediation_function() throws Exception {
+ String xml = "" +
+ "<rules>" +
+ " <rule>" +
+ " <key>1</key>" +
+ " <name>One</name>" +
+ " <description>Desc</description>" +
+
+ " <effortToFixDescription>lines</effortToFixDescription>" +
+ " <debtSubCharacteristic>BUG</debtSubCharacteristic>" +
+ " <debtRemediationFunction>LINEAR</debtRemediationFunction>" +
+ " <debtRemediationFunctionCoefficient>2d 3h</debtRemediationFunctionCoefficient>" +
+ " </rule>" +
+ "</rules>";
+ RulesDefinition.Rule rule = load(xml).rule("1");
+ assertThat(rule.debtSubCharacteristic()).isEqualTo("BUG");
+ assertThat(rule.effortToFixDescription()).isEqualTo("lines");
+ DebtRemediationFunction function = rule.debtRemediationFunction();
+ assertThat(function).isNotNull();
+ assertThat(function.type()).isEqualTo(DebtRemediationFunction.Type.LINEAR);
+ assertThat(function.coefficient()).isEqualTo("2d3h");
+ assertThat(function.offset()).isNull();
+ }
+
+ @Test
+ public void test_linear_with_offset_remediation_function() {
+ String xml = "" +
+ "<rules>" +
+ " <rule>" +
+ " <key>1</key>" +
+ " <name>One</name>" +
+ " <description>Desc</description>" +
+
+ " <effortToFixDescription>lines</effortToFixDescription>" +
+ " <debtSubCharacteristic>BUG</debtSubCharacteristic>" +
+ " <debtRemediationFunction>LINEAR_OFFSET</debtRemediationFunction>" +
+ " <debtRemediationFunctionCoefficient>2d 3h</debtRemediationFunctionCoefficient>" +
+ " <debtRemediationFunctionOffset>5min</debtRemediationFunctionOffset>" +
+ " </rule>" +
+ "</rules>";
+ RulesDefinition.Rule rule = load(xml).rule("1");
+ assertThat(rule.effortToFixDescription()).isEqualTo("lines");
+ assertThat(rule.debtSubCharacteristic()).isEqualTo("BUG");
+ DebtRemediationFunction function = rule.debtRemediationFunction();
+ assertThat(function).isNotNull();
+ assertThat(function.type()).isEqualTo(DebtRemediationFunction.Type.LINEAR_OFFSET);
+ assertThat(function.coefficient()).isEqualTo("2d3h");
+ assertThat(function.offset()).isEqualTo("5min");
+ }
+
+ @Test
+ public void test_constant_remediation_function() {
+ String xml = "" +
+ "<rules>" +
+ " <rule>" +
+ " <key>1</key>" +
+ " <name>One</name>" +
+ " <description>Desc</description>" +
+ " <debtSubCharacteristic>BUG</debtSubCharacteristic>" +
+ " <debtRemediationFunction>CONSTANT_ISSUE</debtRemediationFunction>" +
+ " <debtRemediationFunctionOffset>5min</debtRemediationFunctionOffset>" +
+ " </rule>" +
+ "</rules>";
+ RulesDefinition.Rule rule = load(xml).rule("1");
+ assertThat(rule.debtSubCharacteristic()).isEqualTo("BUG");
+ DebtRemediationFunction function = rule.debtRemediationFunction();
+ assertThat(function).isNotNull();
+ assertThat(function.type()).isEqualTo(DebtRemediationFunction.Type.CONSTANT_ISSUE);
+ assertThat(function.coefficient()).isNull();
+ assertThat(function.offset()).isEqualTo("5min");
+ }
+
+ @Test
+ public void fail_if_invalid_remediation_function() {
+ try {
+ load("" +
+ "<rules>" +
+ " <rule>" +
+ " <key>1</key>" +
+ " <name>One</name>" +
+ " <description>Desc</description>" +
+ " <debtSubCharacteristic>BUG</debtSubCharacteristic>" +
+ " <debtRemediationFunction>UNKNOWN</debtRemediationFunction>" +
+ " </rule>" +
+ "</rules>");
+ fail();
+ } catch (IllegalArgumentException e) {
+ assertThat(e).hasMessageContaining("Fail to load the rule with key [squid:1]");
+ assertThat(e.getCause()).hasMessageContaining("No enum constant org.sonar.api.server.debt.DebtRemediationFunction.Type.UNKNOWN");
+ }
+ }
+
+ @Test
+ public void fail_if_sub_characteristic_is_missing() {
+ try {
+ load("<rules>" +
+ " <rule>" +
+ " <key>1</key>" +
+ " <name>One</name>" +
+ " <description>Desc</description>" +
+ " <debtRemediationFunction>LINEAR</debtRemediationFunction>" +
+ " <debtRemediationFunctionCoefficient>1min</debtRemediationFunctionCoefficient>" +
+ " </rule>" +
+ "</rules>");
+ } catch (IllegalStateException e) {
+ assertThat(e).hasMessageContaining("Both debt sub-characteristic and debt remediation function should be defined on rule '[repository=squid, key=1]");
+ }
+ }
}