From e4fdb86b0c3dea8e7c68e1f4295ac7e7535b9403 Mon Sep 17 00:00:00 2001 From: wkoot <3715211+wkoot@users.noreply.github.com> Date: Wed, 28 Feb 2024 22:34:43 +0100 Subject: [PATCH] Test based on config data --- .github/workflows/docker-image.yml | 2 +- docker/docker-compose.ci.yml | 2 +- src/config.json | 92 +++++++++---------- tests/test_smoke.py | 136 ++++++++++++++++++++++++----- 4 files changed, 162 insertions(+), 70 deletions(-) diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 6f83912..882ca3d 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -9,7 +9,7 @@ on: env: CODE: "PROJ1" - RULES: "+csharpsquid:S104;-ts:S1561" + RULES: "+csharpsquid:S104;-ts:S1561;+Web:WhiteSpaceAroundCheck" jobs: build: diff --git a/docker/docker-compose.ci.yml b/docker/docker-compose.ci.yml index 9f40cc1..bad45af 100644 --- a/docker/docker-compose.ci.yml +++ b/docker/docker-compose.ci.yml @@ -11,7 +11,7 @@ services: SONAR_JDBC_PASSWORD: "sonar_pass" SONARQUBE_PASSWORD: "admin123" PROJECT_CODE: "PROJ1" - PROJECT_RULES: "+csharpsquid:S104;-ts:S1561" + PROJECT_RULES: "+csharpsquid:S104;-ts:S1561;+Web:WhiteSpaceAroundCheck" db: environment: diff --git a/src/config.json b/src/config.json index 1f4f9eb..ea3933a 100644 --- a/src/config.json +++ b/src/config.json @@ -53,111 +53,111 @@ }, "rules": { "cs": [ - "+types=SECURITY_HOTSPOT,VULNERABILITY # Enable these types by default", + "+types=SECURITY_HOTSPOT,VULNERABILITY", "+csharpsquid:S104 # NCSS; used by Quality-time (https://quality-time.readthedocs.io/en/latest/reference.html#long-units)", + "+csharpsquid:S1067 # Too complex expression; NOT used by Quality-time", "+csharpsquid:S107|max=5 # Too many parameters; used by Quality-time (https://quality-time.readthedocs.io/en/latest/reference.html#many-parameters)", + "+csharpsquid:S109 # Magic numbers; NOT used by Quality-time", + "+csharpsquid:S1151 # Switch case clauses should not have too many lines of code. Used by Quality-time", "+csharpsquid:S125 # Commented code; used by Quality-time (https://quality-time.readthedocs.io/en/latest/reference.html#commented-out-code)", + "+csharpsquid:S1309 # Violation suppression; used by Quality-time (https://quality-time.readthedocs.io/en/latest/reference.html#suppressed-violations)", "+csharpsquid:S134 # Depth of nesting; NOT used by Quality-time", - "+csharpsquid:S1067 # Too complex expression; NOT used by Quality-time", - "+csharpsquid:S109 # Magic numbers; NOT used by Quality-time", "+csharpsquid:S138|max=20 # Methods with too many lines; used by Quality-time (https://quality-time.readthedocs.io/en/latest/reference.html#long-units)", - "+csharpsquid:S1309 # Violation suppression; used by Quality-time (https://quality-time.readthedocs.io/en/latest/reference.html#suppressed-violations)", - "+csharpsquid:S1541|maximumFunctionComplexityThreshold=10 # Used by Quality-time (https://quality-time.readthedocs.io/en/latest/reference.html#complex-units)", - "+csharpsquid:S1151 # Switch case clauses should not have too many lines of code. Used by Quality-time" + "+csharpsquid:S1541|maximumFunctionComplexityThreshold=10 # Used by Quality-time (https://quality-time.readthedocs.io/en/latest/reference.html#complex-units)" ], "java": [ - "+types=SECURITY_HOTSPOT,VULNERABILITY # Enable these types by default", - "+java:S1541|Threshold=10 # Used by Quality-time (https://quality-time.readthedocs.io/en/latest/reference.html#complex-units)", + "+types=SECURITY_HOTSPOT,VULNERABILITY", "+java:NoSonar # Used by Quality-time to report on suppressed violations (https://quality-time.readthedocs.io/en/latest/reference.html#suppressed-violations)", - "+java:S1309 # Used by Quality-time to report on suppressed violations (https://quality-time.readthedocs.io/en/latest/reference.html#suppressed-violations)", - "+java:S1310 # Used by Quality-time to report on suppressed violations (https://quality-time.readthedocs.io/en/latest/reference.html#suppressed-violations)", - "+java:S1315 # Used by Quality-time to report on suppressed violations (https://quality-time.readthedocs.io/en/latest/reference.html#suppressed-violations)", + "+java:S104 # Files should not have too many lines of code. Used by Quality-time", "+java:S1067 # Expression too complex; NOT used by Quality-time", - "+java:S109 # Magic numbers; NOT used by Quality-time", - "+java:S138|max=20 # Methods with too many lines; used by Quality-time (https://quality-time.readthedocs.io/en/latest/reference.html#long-units)", "+java:S107|max=5 # Too many parameters; used by Quality-time (https://quality-time.readthedocs.io/en/latest/reference.html#many-parameters)", - "+java:S125 # Used by Quality-time (https://quality-time.readthedocs.io/en/latest/reference.html#commented-out-code)", - "+java:S104 # Files should not have too many lines of code. Used by Quality-time", + "+java:S109 # Magic numbers; NOT used by Quality-time", "+java:S1151 # Switch case clauses should not have too many lines of code. Used by Quality-time", "+java:S1188 # Anonymous classes should not have too many lines. Used by Quality-time", + "+java:S125 # Used by Quality-time (https://quality-time.readthedocs.io/en/latest/reference.html#commented-out-code)", + "+java:S1309 # Used by Quality-time to report on suppressed violations (https://quality-time.readthedocs.io/en/latest/reference.html#suppressed-violations)", + "+java:S1310 # Used by Quality-time to report on suppressed violations (https://quality-time.readthedocs.io/en/latest/reference.html#suppressed-violations)", + "+java:S1315 # Used by Quality-time to report on suppressed violations (https://quality-time.readthedocs.io/en/latest/reference.html#suppressed-violations)", + "+java:S138|max=20 # Methods with too many lines; used by Quality-time (https://quality-time.readthedocs.io/en/latest/reference.html#long-units)", + "+java:S1541|Threshold=10 # Used by Quality-time (https://quality-time.readthedocs.io/en/latest/reference.html#complex-units)", "+java:S2972 # Inner classes should not have too many lines of code. Used by Quality-time", "+java:S5612 # Lambdas should not have too many lines. Used by Quality-time" ], "js": [ - "+types=SECURITY_HOTSPOT,VULNERABILITY # Enable these types by default", - "+javascript:S1541|maximumFunctionComplexityThreshold=10 # Used by Quality-time (https://quality-time.readthedocs.io/en/latest/reference.html#complex-units)", - "+javascript:S134 # NOT used by Quality-time", - "+javascript:S1067 # Expression too complex; NOT used by Quality-time", + "+types=SECURITY_HOTSPOT,VULNERABILITY", + "+javascript:S104 # Files should not have too many lines of code. Used by Quality-time", "+javascript:S106 # Console logging should not be used; NOT used by Quality-time", + "+javascript:S1067 # Expression too complex; NOT used by Quality-time", "+javascript:S107|maximumFunctionParameters=5 # Too many parameters; used by Quality-time (https://quality-time.readthedocs.io/en/latest/reference.html#many-parameters)", "+javascript:S109 # Magic numbers; NOT used by Quality-time", - "+javascript:S138|max=20 # Methods with too many lines; used by Quality-time (https://quality-time.readthedocs.io/en/latest/reference.html#long-units)", "+javascript:S125 # Used by Quality-time (https://quality-time.readthedocs.io/en/latest/reference.html#commented-out-code)", - "+javascript:S104 # Files should not have too many lines of code. Used by Quality-time", + "+javascript:S134 # NOT used by Quality-time", + "+javascript:S138|max=20 # Methods with too many lines; used by Quality-time (https://quality-time.readthedocs.io/en/latest/reference.html#long-units)", + "+javascript:S1541|maximumFunctionComplexityThreshold=10 # Used by Quality-time (https://quality-time.readthedocs.io/en/latest/reference.html#complex-units)", "# Missing: NoSonar, NCSS, Parameters" ], "kotlin": [ - "+types=SECURITY_HOTSPOT,VULNERABILITY # Enable these types by default", + "+types=SECURITY_HOTSPOT,VULNERABILITY", + "+kotlin:S104 # Files should not have too many lines of code. Used by Quality-time", "+kotlin:S1067 # Expression too complex; NOT used by Quality-time", - "+kotlin:S138|max=20 # Methods with too many lines; used by Quality-time (https://quality-time.readthedocs.io/en/latest/reference.html#long-units)", "+kotlin:S107|Max=5 # Too many parameters; used by Quality-time (https://quality-time.readthedocs.io/en/latest/reference.html#many-parameters)", - "+kotlin:S104 # Files should not have too many lines of code. Used by Quality-time", "+kotlin:S1151 # When clauses should not have too many lines of code. Used by Quality-time", + "+kotlin:S138|max=20 # Methods with too many lines; used by Quality-time (https://quality-time.readthedocs.io/en/latest/reference.html#long-units)", "+kotlin:S5612 # Lambdas should not have too many lines. Used by Quality-time" ], "py": [ - "+types=SECURITY_HOTSPOT,VULNERABILITY # Enable these types by default", + "+types=SECURITY_HOTSPOT,VULNERABILITY", + "+python:FunctionComplexity|maximumFunctionComplexityThreshold=10 # Used by Quality-time (https://quality-time.readthedocs.io/en/latest/reference.html#complex-units)", + "+python:NoSonar # Used by Quality-time to report on suppressed violations (https://quality-time.readthedocs.io/en/latest/reference.html#suppressed-violations)", "+python:S104 # Too many lines of code in file; NOT used by Quality-time", "+python:S107|max=5 # Too many parameters; used by Quality-time (https://quality-time.readthedocs.io/en/latest/reference.html#many-parameters)", - "+python:NoSonar # Used by Quality-time to report on suppressed violations (https://quality-time.readthedocs.io/en/latest/reference.html#suppressed-violations)", "+python:S125 # Commented code; used by Quality-time (https://quality-time.readthedocs.io/en/latest/reference.html#commented-out-code)", "+python:S134 # Too deep nesting; NOT used by Quality-time", - "+python:S138|max=20 # Methods with too many lines; used by Quality-time (https://quality-time.readthedocs.io/en/latest/reference.html#long-units)", - "+python:FunctionComplexity|maximumFunctionComplexityThreshold=10 # Used by Quality-time (https://quality-time.readthedocs.io/en/latest/reference.html#complex-units)" + "+python:S138|max=20 # Methods with too many lines; used by Quality-time (https://quality-time.readthedocs.io/en/latest/reference.html#long-units)" ], "swift": [ - "+types=SECURITY_HOTSPOT,VULNERABILITY # Enable these types by default", - "+swift:S1541|Threshold=10 # Used by Quality-time (https://github.com/ICTU/quality-time/blob/master/docs/METRICS_AND_SOURCES.md#complex-units-from-sonarqube)", + "+types=SECURITY_HOTSPOT,VULNERABILITY", + "+swift:S104 # Files should not have too many lines of code. Used by Quality-time", "+swift:S1067 # Expression too complex; NOT used by Quality-time", - "+swift:S138|max=20 # Methods with too many lines; used by Quality-time (https://github.com/ICTU/quality-time/blob/master/docs/METRICS_AND_SOURCES.md#long-units-from-sonarqube)", "+swift:S107|functionMax=5 # Too many parameters; used by Quality-time (https://github.com/ICTU/quality-time/blob/master/docs/METRICS_AND_SOURCES.md#many-parameters-from-sonarqube)", - "+swift:S125 # Used by Quality-time (https://github.com/ICTU/quality-time/blob/master/docs/METRICS_AND_SOURCES.md#commented-out-code-from-sonarqube)", - "+swift:S104 # Files should not have too many lines of code. Used by Quality-time", "+swift:S1151 # Switch case clauses should not have too many lines of code. Used by Quality-time", "+swift:S1188 # Closures should not have too many lines. Used by Quality-time", + "+swift:S125 # Used by Quality-time (https://github.com/ICTU/quality-time/blob/master/docs/METRICS_AND_SOURCES.md#commented-out-code-from-sonarqube)", + "+swift:S138|max=20 # Methods with too many lines; used by Quality-time (https://github.com/ICTU/quality-time/blob/master/docs/METRICS_AND_SOURCES.md#long-units-from-sonarqube)", + "+swift:S1541|Threshold=10 # Used by Quality-time (https://github.com/ICTU/quality-time/blob/master/docs/METRICS_AND_SOURCES.md#complex-units-from-sonarqube)", "+swift:S2042 # Classes should not have too many lines of code. Used by Quality-time" ], "ts": [ - "+types=SECURITY_HOTSPOT,VULNERABILITY # Enable these types by default", - "+typescript:S109 # Magic number; NOT used by Quality-time", + "+types=SECURITY_HOTSPOT,VULNERABILITY", "+typescript:S104 # File length; NOT used by Quality-time", "+typescript:S106 # Console logging; NOT used by Quality-time", - "+typescript:S107|maximumFunctionParameters=5 # Too many parameters; used by Quality-time (https://quality-time.readthedocs.io/en/latest/reference.html#many-parameters)", "+typescript:S1067 # Expression too complex; NOT used by Quality-time", - "+typescript:S1541|Threshold=10 # Used by Quality-time (https://quality-time.readthedocs.io/en/latest/reference.html#complex-units)", + "+typescript:S107|maximumFunctionParameters=5 # Too many parameters; used by Quality-time (https://quality-time.readthedocs.io/en/latest/reference.html#many-parameters)", + "+typescript:S109 # Magic number; NOT used by Quality-time", "+typescript:S138|max=20 # Methods with too many lines; used by Quality-time (https://quality-time.readthedocs.io/en/latest/reference.html#long-units)", + "+typescript:S1541|Threshold=10 # Used by Quality-time (https://quality-time.readthedocs.io/en/latest/reference.html#complex-units)", "+typescript:S4204 # The 'any' type should not be used; NOT used by Quality-time", "-typescript:S4328 # reason: the rule does not recognize 'local' imports" ], "vbnet": [ - "+types=SECURITY_HOTSPOT,VULNERABILITY # Enable these types by default", - "+vbnet:S1541|maximumFunctionComplexityThreshold=10 # Too complex function, procedure or property; Used by Quality-time (https://quality-time.readthedocs.io/en/latest/reference.html#complex-units)", + "+types=SECURITY_HOTSPOT,VULNERABILITY", + "+vbnet:S104 # Files should not have too many lines of code. Used by Quality-time", "+vbnet:S1067 # Expression too complex; NOT used by Quality-time", "+vbnet:S107|max=5 # Too many parameters; used by Quality-time (https://quality-time.readthedocs.io/en/latest/reference.html#many-parameters)", - "+vbnet:S138|max=20 # Methods with too many lines; used by Quality-time (https://quality-time.readthedocs.io/en/latest/reference.html#long-units)", - "+vbnet:S104 # Files should not have too many lines of code. Used by Quality-time", "+vbnet:S1151 # Select Case clauses should not have too many lines of code. Used by Quality-time", + "+vbnet:S138|max=20 # Methods with too many lines; used by Quality-time (https://quality-time.readthedocs.io/en/latest/reference.html#long-units)", + "+vbnet:S1541|maximumFunctionComplexityThreshold=10 # Too complex function, procedure or property; Used by Quality-time (https://quality-time.readthedocs.io/en/latest/reference.html#complex-units)", "# Missing: method length, NoSonar, too many parameters, commented loc" ], "web": [ - "+types=SECURITY_HOTSPOT,VULNERABILITY # Enable these types by default", - "+Web:ComplexityCheck # NOT used by Quality-time. Rule is deprecated, see https://rules.sonarsource.com/html/RSPEC-1908?search=complexity", - "+Web:LongJavaScriptCheck # Used by Quality-time (https://quality-time.readthedocs.io/en/latest/reference.html#long-units)", + "+types=SECURITY_HOTSPOT,VULNERABILITY", "+Web:AvoidCommentedOutCodeCheck # Used by Quality-time (https://quality-time.readthedocs.io/en/latest/reference.html#commented-out-code)", - "+Web:FileLengthCheck # Files should not have too many lines. Used by Quality-time" + "+Web:ComplexityCheck # NOT used by Quality-time. Rule is deprecated, see https://rules.sonarsource.com/html/RSPEC-1908?search=complexity", + "+Web:FileLengthCheck # Files should not have too many lines. Used by Quality-time", + "+Web:LongJavaScriptCheck # Used by Quality-time (https://quality-time.readthedocs.io/en/latest/reference.html#long-units)" ], "yaml": [ - "+types=SECURITY_HOTSPOT,VULNERABILITY # Enable these types by default", + "+types=SECURITY_HOTSPOT,VULNERABILITY", "+ansible:ANSIBLE0002", "+ansible:ANSIBLE0004", "+ansible:ANSIBLE0005", diff --git a/tests/test_smoke.py b/tests/test_smoke.py index f80a9db..4c98500 100644 --- a/tests/test_smoke.py +++ b/tests/test_smoke.py @@ -1,38 +1,130 @@ +from json import loads from os import getenv from unittest import TestCase, skipUnless +import requests from sonarqube import SonarQubeClient PROJECT_CODE = getenv("PROJECT_CODE") PROJECT_RULES = getenv("PROJECT_RULES") +CONFIG_FILE = getenv("CONFIG_FILE", "/src/config.json") -class SonarTest(TestCase): - def setUp(self) -> None: +class FunctionalTest(TestCase): + @classmethod + def setUpClass(cls): + """Expose sonar api and credentials to tests.""" + with open(CONFIG_FILE, "r") as config_file: + cls.config_json = loads(config_file.read()) sonar_port = getenv("SONAR_PORT", "9000") sonar_base_url = f"http://localhost:{sonar_port}" sonar_pass = getenv("SONARQUBE_PASSWORD", "admin") - self.sonar_client = SonarQubeClient(sonarqube_url=sonar_base_url, username="admin", password=sonar_pass) + cls.sonar_client = SonarQubeClient(sonarqube_url=sonar_base_url, username="admin", password=sonar_pass) + cls.sonar_api = f"{sonar_base_url}/api" + cls.sonar_auth = ("admin", sonar_pass) - def test_java_profile(self): - java_quality_profiles = self.sonar_client.qualityprofiles.search_quality_profiles(language="java") - java_profile_names = [profile["name"] for profile in java_quality_profiles["profiles"]] - self.assertIn("Sonar way", java_profile_names) + def _search_profiles(self, **kwargs): + """Convenience wrapper for sonar api quality profile search.""" + return self.sonar_client.qualityprofiles.search_quality_profiles(**kwargs) - @skipUnless(PROJECT_CODE, "PROJECT_CODE was not passed") - def test_csharpsquid_profile(self): - search_result = self.sonar_client.qualityprofiles.search_quality_profiles( - defaults="true", language="cs", qualityProfile=f"{PROJECT_CODE}-ictu-cs-profile-v9.13.0-20231222" - ) - self.assertEqual(len(search_result['profiles']), 1) - cs_profile_key = search_result['profiles'][0]['key'] - self.assertIsNotNone(cs_profile_key) # TODO - check activated rules within profile instead + def _search_rules(self, **kwargs): + """Convenience wrapper for sonar api rule search.""" + return self.sonar_client.rules.search_rules(**kwargs) + + def test_sonar_way_profile_remains(self): + """Check that the 'Sonar way' profile remains when a profile is defined for the language.""" + java_profiles = self._search_profiles(language="java") + self.assertIn("Sonar way", [profile["name"] for profile in java_profiles["profiles"]]) + version_profile = f"ictu-{self.config_json['profiles']['java']['version']}-{self.config_json['rules_version']}" + self.assertIn(version_profile, [profile["name"] for profile in java_profiles["profiles"]]) @skipUnless(PROJECT_CODE, "PROJECT_CODE was not passed") - def test_ts_profile(self): - search_result = self.sonar_client.qualityprofiles.search_quality_profiles( - defaults="true", language="ts", qualityProfile=f"{PROJECT_CODE}-ictu-ts-profile-v10.9.0-20231222" - ) - self.assertEqual(len(search_result['profiles']), 1) - ts_profile_key = search_result['profiles'][0]['key'] - self.assertIsNotNone(ts_profile_key) # TODO - check activated rules within profile instead + @skipUnless(PROJECT_RULES, "PROJECT_RULES was not passed") + def test_project_override_profile(self): + """Check that overridden rule activation is applied.""" + overridden_key = "Web:WhiteSpaceAroundCheck" + self.assertTrue(any([rule_line == f"+{overridden_key}" for rule_line in PROJECT_RULES.split(";")])) + + version_profile = f"ictu-{self.config_json['profiles']['web']['version']}-{self.config_json['rules_version']}" + web_profiles = self._search_profiles( + defaults="true", language="web", qualityProfile=f"{PROJECT_CODE}-{version_profile}" + ) # also verify that the overridden profile is the default + web_profile_key = next(profile["key"] for profile in web_profiles["profiles"]) + web_rule = self._search_rules(activation="true", qprofile=web_profile_key, rule_key=overridden_key) + self.assertEqual(1, web_rule["total"]) + + # Verify that it indeed differs from the custom profile + web_profiles = self._search_profiles(defaults="false", language="web", qualityProfile=version_profile) + web_profile_key = next(profile["key"] for profile in web_profiles["profiles"]) + web_rule = self._search_rules(activation="false", qprofile=web_profile_key, rule_key=overridden_key) + self.assertEqual(1, web_rule["total"]) + + def test_type_profile(self): + """Check that custom profile rule type activation is applied.""" + self.assertIn("types=", self.config_json["rules"]["web"][0]) + overridden_key = "Web:AvoidHtmlCommentCheck" + self.assertFalse(next(overridden_key in rule_line for rule_line in self.config_json["rules"]["web"])) + + version_profile = f"ictu-{self.config_json['profiles']['web']['version']}-{self.config_json['rules_version']}" + web_profiles = self._search_profiles(language="web", qualityProfile=version_profile) + web_profile_key = next(profile["key"] for profile in web_profiles["profiles"]) + web_rule = self._search_rules(activation="true", qprofile=web_profile_key, rule_key=overridden_key) + self.assertEqual(1, web_rule["total"]) + + # Verify that it indeed differs from the Sonar way + sonar_way_web = self._search_profiles(language="web", qualityProfile="Sonar way") + sonar_way_key = next(p["key"] for p in sonar_way_web["profiles"]) + sonar_way_rule = self._search_rules(activation="false", qprofile=sonar_way_key, rule_key=overridden_key) + self.assertEqual(1, sonar_way_rule["total"]) + + def test_rule_params_in_profile(self): + """Check that custom profile rule params are applied.""" + cs_param_rule_lines = [rule_line for rule_line in self.config_json["rules"]["cs"] if "|" in rule_line] + self.assertNotEqual([], cs_param_rule_lines) + + version_profile = f"ictu-{self.config_json['profiles']['cs']['version']}-{self.config_json['rules_version']}" + cs_profile_search = self._search_profiles(language="cs", qualityProfile=version_profile) + self.assertEqual(1, len(cs_profile_search["profiles"])) + + # Fetch changelog api with requests, because library does not expose it + changelog_api = f"{self.sonar_api}/qualityprofiles/changelog" + change_history_params = {"language": "cs", "qualityProfile": version_profile, "ps": 500} + api_result = requests.get(changelog_api, auth=self.sonar_auth, params=change_history_params).json() + self.assertGreater(change_history_params["ps"], api_result["total"]) + + # Verify that each param override has a corresponding changelog event + profile_changes = api_result["events"] + for cs_rule_line in cs_param_rule_lines: + rule_key, rule_params_str = cs_rule_line[1:].split()[0].split("|") + rule_changes = [change for change in profile_changes if change["ruleKey"] == rule_key] + for rule_param_str in rule_params_str.split(";"): + rule_params = rule_param_str.split("=") + param_dict = {rule_params[0]: rule_params[1]} + self.assertTrue(next( + param_dict.items() <= rule_change.get('params', {}).items() for rule_change in rule_changes + )) + + # Verify that it indeed differs from the Sonar way + self.assertTrue(next(event for event in profile_changes if event["action"] == "UPDATED")) + + def test_profile_rule_deactivation(self): + """Check that custom profile rule deactivation is applied.""" + ts_rule_lines = [rule_line for rule_line in self.config_json["rules"]["ts"] if "types=" not in rule_line] + self.assertTrue(any([rule_line.startswith("-") for rule_line in ts_rule_lines])) + self.assertTrue(any([rule_line.startswith("+") for rule_line in ts_rule_lines])) + + version_profile = f"ictu-{self.config_json['profiles']['ts']['version']}-{self.config_json['rules_version']}" + ts_profile_search = self._search_profiles(language="ts", qualityProfile=version_profile) + ts_profile_key = next(profile["key"] for profile in ts_profile_search["profiles"]) + + for ts_rule_line in ts_rule_lines: + activation = "yes" if ts_rule_line[0] == "+" else "no" + rule_key = ts_rule_line[1:].split()[0].split("|")[0] + ts_rule = self._search_rules(activation=activation, qprofile=ts_profile_key, rule_key=rule_key) + self.assertEqual(1, ts_rule["total"]) + + # Verify that it indeed differs from the Sonar way + sonar_way_ts = self._search_profiles(language="ts", qualityProfile="Sonar way") + sonar_way_key = next(p["key"] for p in sonar_way_ts["profiles"]) + sonar_way_rule = self._search_rules(qprofile=sonar_way_key, rule_key="swift:S104") + self.assertEqual(0, sonar_way_rule["total"]) # is not listed as a disabled key