diff --git a/ggshield/core/cache.py b/ggshield/core/cache.py index 7bfa9ecfdd..e8de355e0e 100644 --- a/ggshield/core/cache.py +++ b/ggshield/core/cache.py @@ -2,12 +2,9 @@ from pathlib import Path from typing import Any, Dict, List -from pygitguardian.models import PolicyBreak - from ggshield.core import ui from ggshield.core.constants import CACHE_PATH from ggshield.core.errors import UnexpectedError -from ggshield.core.filter import get_ignore_sha from ggshield.core.types import IgnoredMatch @@ -74,18 +71,18 @@ def save(self) -> None: def purge(self) -> None: self.last_found_secrets = [] - def add_found_policy_break(self, policy_break: PolicyBreak, filename: str) -> None: - if policy_break.is_secret: - ignore_sha = get_ignore_sha(policy_break) - if not any( - last_found.match == ignore_sha for last_found in self.last_found_secrets - ): - self.last_found_secrets.append( - IgnoredMatch( - name=f"{policy_break.break_type} - {filename}", - match=get_ignore_sha(policy_break), - ) + def add_found_policy_break( + self, break_type: str, ignore_sha: str, filename: str + ) -> None: + if not any( + last_found.match == ignore_sha for last_found in self.last_found_secrets + ): + self.last_found_secrets.append( + IgnoredMatch( + name=f"{break_type} - {filename}", + match=ignore_sha, ) + ) class ReadOnlyCache(Cache): diff --git a/ggshield/core/filter.py b/ggshield/core/filter.py index 109d5ce347..91521e9456 100644 --- a/ggshield/core/filter.py +++ b/ggshield/core/filter.py @@ -2,7 +2,7 @@ import math import operator import re -from typing import Dict, Iterable, List, Pattern, Set +from typing import Iterable, Pattern, Set from click import UsageError from pygitguardian.models import Match, PolicyBreak @@ -60,20 +60,6 @@ def get_ignore_sha(policy_break: PolicyBreak) -> str: return hashlib.sha256(hashable.encode("UTF-8")).hexdigest() -def group_policy_breaks_by_ignore_sha( - policy_breaks: List[PolicyBreak], -) -> Dict[str, List[PolicyBreak]]: - """ - Group policy breaks by their ignore sha. - """ - sha_dict: Dict[str, List[PolicyBreak]] = {} - for policy_break in policy_breaks: - ignore_sha = get_ignore_sha(policy_break) - sha_dict.setdefault(ignore_sha, []).append(policy_break) - - return sha_dict - - def translate_user_pattern(pattern: str) -> str: """ Translate the user pattern into a regex. This function assumes that the given diff --git a/ggshield/verticals/secret/extended_match.py b/ggshield/verticals/secret/extended_match.py index e0aa3c3e12..be008e87a4 100644 --- a/ggshield/verticals/secret/extended_match.py +++ b/ggshield/verticals/secret/extended_match.py @@ -140,3 +140,17 @@ def __repr__(self) -> str: f"post_line_end:{self.post_line_end}", ] ) + + def __eq__(self, other: Any) -> bool: + if not isinstance(other, ExtendedMatch): + return False + return ( + self.span == other.span + and self.lines_before_secret == other.lines_before_secret + and self.lines_with_secret == other.lines_with_secret + and self.lines_after_secret == other.lines_after_secret + and self.pre_line_start == other.pre_line_start + and self.pre_line_end == other.pre_line_end + and self.post_line_start == other.post_line_start + and self.post_line_end == other.post_line_end + ) diff --git a/ggshield/verticals/secret/output/secret_gitlab_webui_output_handler.py b/ggshield/verticals/secret/output/secret_gitlab_webui_output_handler.py index a200aafb65..9b10bce408 100644 --- a/ggshield/verticals/secret/output/secret_gitlab_webui_output_handler.py +++ b/ggshield/verticals/secret/output/secret_gitlab_webui_output_handler.py @@ -1,13 +1,11 @@ -from pygitguardian.models import PolicyBreak - from ggshield.core.filter import censor_match from ggshield.core.text_utils import pluralize, translate_validity -from ..secret_scan_collection import SecretScanCollection +from ..secret_scan_collection import Secret, SecretScanCollection from .secret_output_handler import SecretOutputHandler -def format_policy_break(policy_break: PolicyBreak) -> str: +def format_policy_break(policy_break: Secret) -> str: """Returns a string with the policy name, validity and a comma-separated, double-quoted, censored version of all `policy_break` matches. diff --git a/ggshield/verticals/secret/output/secret_json_output_handler.py b/ggshield/verticals/secret/output/secret_json_output_handler.py index fe78ce67c2..64de72f643 100644 --- a/ggshield/verticals/secret/output/secret_json_output_handler.py +++ b/ggshield/verticals/secret/output/secret_json_output_handler.py @@ -1,12 +1,17 @@ from typing import Any, Dict, List, cast from pygitguardian.client import VERSIONS -from pygitguardian.models import PolicyBreak, SecretIncident +from pygitguardian.models import SecretIncident -from ggshield.core.filter import group_policy_breaks_by_ignore_sha from ggshield.verticals.secret.extended_match import ExtendedMatch -from ..secret_scan_collection import Error, Result, SecretScanCollection +from ..secret_scan_collection import ( + Error, + Result, + Secret, + SecretScanCollection, + group_secrets_by_ignore_sha, +) from .schemas import JSONScanCollectionSchema from .secret_output_handler import SecretOutputHandler @@ -74,7 +79,7 @@ def process_result( "total_occurrences": 0, "total_incidents": 0, } - sha_dict = group_policy_breaks_by_ignore_sha(result.policy_breaks) + sha_dict = group_secrets_by_ignore_sha(result.policy_breaks) result_dict["total_incidents"] = len(sha_dict) if not self.show_secrets: @@ -105,7 +110,7 @@ def process_error(error: Error) -> Dict[str, Any]: def serialized_policy_break( self, ignore_sha: str, - policy_breaks: List[PolicyBreak], + policy_breaks: List[Secret], incident_details: Dict[str, SecretIncident], ) -> Dict[str, Any]: flattened_dict: Dict[str, Any] = { @@ -136,7 +141,7 @@ def serialized_policy_break( def serialize_policy_break_matches( self, - policy_break: PolicyBreak, + policy_break: Secret, ) -> List[Dict[str, Any]]: """ Serialize policy_break matches. The method uses MatchSpan to get the start and diff --git a/ggshield/verticals/secret/output/secret_sarif_output_handler.py b/ggshield/verticals/secret/output/secret_sarif_output_handler.py index b5ef79faba..62ea215c6d 100644 --- a/ggshield/verticals/secret/output/secret_sarif_output_handler.py +++ b/ggshield/verticals/secret/output/secret_sarif_output_handler.py @@ -2,14 +2,13 @@ from typing import Any, Dict, Iterable, List, cast from pygitguardian.client import VERSIONS -from pygitguardian.models import PolicyBreak, SecretIncident +from pygitguardian.models import SecretIncident from ggshield import __version__ as ggshield_version -from ggshield.core.filter import get_ignore_sha from ggshield.core.match_span import MatchSpan from ..extended_match import ExtendedMatch -from ..secret_scan_collection import Result, SecretScanCollection +from ..secret_scan_collection import Result, Secret, SecretScanCollection from .secret_output_handler import SecretOutputHandler @@ -66,7 +65,7 @@ def _create_sarif_results( def _create_sarif_result_dict( url: str, - policy_break: PolicyBreak, + policy_break: Secret, incident_details: Dict[str, SecretIncident], ) -> Dict[str, Any]: # Prepare message with links to the related location for each match @@ -98,7 +97,7 @@ def _create_sarif_result_dict( for id, m in enumerate(extended_matches) ], "partialFingerprints": { - "secret/v1": get_ignore_sha(policy_break), + "secret/v1": policy_break.get_ignore_sha(), }, } if policy_break.incident_url: diff --git a/ggshield/verticals/secret/output/secret_text_output_handler.py b/ggshield/verticals/secret/output/secret_text_output_handler.py index b628df4024..c083dcb129 100644 --- a/ggshield/verticals/secret/output/secret_text_output_handler.py +++ b/ggshield/verticals/secret/output/secret_text_output_handler.py @@ -3,10 +3,8 @@ from typing import Dict, List, Optional, Tuple from pygitguardian.client import VERSIONS -from pygitguardian.models import PolicyBreak from ggshield.core.constants import IncidentStatus -from ggshield.core.filter import group_policy_breaks_by_ignore_sha from ggshield.core.lines import Line, get_offset, get_padding from ggshield.core.text_utils import ( STYLE, @@ -17,7 +15,13 @@ ) from ..extended_match import ExtendedMatch -from ..secret_scan_collection import IgnoreReason, Result, SecretScanCollection +from ..secret_scan_collection import ( + IgnoreKind, + Result, + Secret, + SecretScanCollection, + group_secrets_by_ignore_sha, +) from .secret_output_handler import SecretOutputHandler @@ -71,9 +75,7 @@ def _process_scan_impl(self, scan: SecretScanCollection) -> str: ) known_secrets_count = sum( - result.ignored_policy_breaks_count_by_reason.get( - IgnoreReason.KNOWN_SECRET, 0 - ) + result.ignored_policy_breaks_count_by_kind.get(IgnoreKind.KNOWN_SECRET, 0) for result in scan.get_all_results() ) if self.ignore_known_secrets and known_secrets_count > 0: @@ -118,14 +120,14 @@ def process_result(self, result: Result) -> str: """ result_buf = StringIO() - sha_dict = group_policy_breaks_by_ignore_sha(result.policy_breaks) + sha_dict = group_secrets_by_ignore_sha(result.policy_breaks) if not self.show_secrets: result.censor() number_of_displayed_secrets = 0 number_of_hidden_secrets = sum( - result.ignored_policy_breaks_count_by_reason.values() + result.ignored_policy_breaks_count_by_kind.values() ) for ignore_sha, policy_breaks in sha_dict.items(): number_of_displayed_secrets += 1 @@ -255,7 +257,7 @@ def leak_message_located( def flatten_policy_breaks_by_line( - policy_breaks: List[PolicyBreak], + policy_breaks: List[Secret], ) -> List[Tuple[Line, List[ExtendedMatch]]]: """ flatten_policy_breaks_by_line turns a list of policy breaks into a list of @@ -283,7 +285,7 @@ def flatten_policy_breaks_by_line( def policy_break_header( - policy_breaks: List[PolicyBreak], + policy_breaks: List[Secret], ignore_sha: str, known_secret: bool = False, ) -> str: @@ -310,8 +312,10 @@ def policy_break_header( {indent}Incident URL: {policy_breaks[0].incident_url if known_secret and policy_break.incident_url else "N/A"} {indent}Secret SHA: {ignore_sha} """ - if policy_break.is_excluded: - message += f"{indent}Ignored: {policy_break.exclude_reason}\n" + if policy_break.ignore_reason is not None: + message += ( + f"{indent}Ignored: {policy_break.ignore_reason.to_human_readable()}\n" + ) return message + "\n" diff --git a/ggshield/verticals/secret/secret_scan_collection.py b/ggshield/verticals/secret/secret_scan_collection.py index 6a11299e0d..0f8dfacd4e 100644 --- a/ggshield/verticals/secret/secret_scan_collection.py +++ b/ggshield/verticals/secret/secret_scan_collection.py @@ -1,3 +1,5 @@ +import hashlib +import operator from dataclasses import dataclass, field from enum import Enum from pathlib import Path @@ -17,51 +19,109 @@ from pygitguardian.models import ( Detail, DiffKind, - Match, PolicyBreak, ScanResult, SecretIncident, ) from ggshield.core.config.user_config import SecretConfig -from ggshield.core.errors import UnexpectedError, handle_api_error +from ggshield.core.errors import handle_api_error from ggshield.core.filter import is_in_ignored_matches -from ggshield.core.lines import Line, get_lines_from_content +from ggshield.core.lines import get_lines_from_content from ggshield.core.scan import Scannable from ggshield.utils.git_shell import Filemode from ggshield.verticals.secret.extended_match import ExtendedMatch -class IgnoreReason(str, Enum): +class IgnoreKind(str, Enum): IGNORED_MATCH = "Match ignored via local .gitguardian yaml" IGNORED_DETECTOR = "Detector ignored via local .gitguardian yaml" KNOWN_SECRET = "Secret is known in dashboard and --ignore-known-secrets is used" NOT_INTRODUCED = "Secret was not in added in commit" BACKEND_EXCLUDED = "Excluded by dashboard" - def __str__(self): - return self.value + +@dataclass(frozen=True) +class IgnoreReason: + kind: IgnoreKind + detail: Optional[str] = None + + def to_human_readable(self): + res = f"{self.kind.value}" + if self.detail: + res += f"({self.detail})" + return res def compute_ignore_reason( policy_break: PolicyBreak, secret_config: SecretConfig -) -> Optional[str]: +) -> Optional[IgnoreReason]: """Computes the possible ignore reason associated with a PolicyBreak""" ignore_reason = None if policy_break.diff_kind in {DiffKind.DELETION, DiffKind.CONTEXT}: - ignore_reason = IgnoreReason.NOT_INTRODUCED + ignore_reason = IgnoreReason(IgnoreKind.NOT_INTRODUCED) elif policy_break.is_excluded: - ignore_reason = f"Excluded from backend ({policy_break.exclude_reason})" + ignore_reason = IgnoreReason( + IgnoreKind.BACKEND_EXCLUDED, policy_break.exclude_reason + ) elif is_in_ignored_matches(policy_break, secret_config.ignored_matches or []): - ignore_reason = IgnoreReason.IGNORED_MATCH + ignore_reason = IgnoreReason(IgnoreKind.IGNORED_MATCH) elif policy_break.break_type in secret_config.ignored_detectors: - ignore_reason = IgnoreReason.IGNORED_DETECTOR + ignore_reason = IgnoreReason(IgnoreKind.IGNORED_DETECTOR) elif secret_config.ignore_known_secrets and policy_break.known_secret: - ignore_reason = IgnoreReason.KNOWN_SECRET + ignore_reason = IgnoreReason(IgnoreKind.KNOWN_SECRET) return ignore_reason +@dataclass +class Secret: + """Like PolicyBreak""" + + break_type: str + validity: str + known_secret: bool + incident_url: Optional[str] + matches: List[ExtendedMatch] + ignore_reason: Optional[IgnoreReason] + diff_kind: Optional[DiffKind] + + @property + def policy(self) -> str: + return "Secrets detection" + + @property + def is_ignored(self) -> bool: + return self.ignore_reason is not None + + @property + def is_secret(self) -> bool: + return True + + def get_ignore_sha(self) -> str: + hashable = "".join( + [ + f"{match.match},{match.match_type}" + for match in sorted(self.matches, key=operator.attrgetter("match_type")) + ] + ) + + return hashlib.sha256(hashable.encode("UTF-8")).hexdigest() + + +def group_secrets_by_ignore_sha( + secrets: List[Secret], +) -> Dict[str, List[Secret]]: + """ + Group policy breaks by their ignore sha. + """ + sha_dict: Dict[str, List[Secret]] = {} + for secret in secrets: + sha_dict.setdefault(secret.get_ignore_sha(), []).append(secret) + + return sha_dict + + @dataclass class Result: """ @@ -73,25 +133,13 @@ class Result: filemode: Filemode path: Path url: str - policy_breaks: List[PolicyBreak] - ignored_policy_breaks_count_by_reason: Counter[str] + policy_breaks: List[Secret] + ignored_policy_breaks_count_by_kind: Counter[IgnoreKind] @property def is_on_patch(self) -> bool: return self.filemode != Filemode.FILE - def enrich_matches(self, lines: List[Line]) -> None: - if len(lines) == 0: - raise UnexpectedError("Parsing of scan result failed.") - for policy_break in self.policy_breaks: - policy_break.matches = cast( - List[Match], - [ - ExtendedMatch.from_match(match, lines, self.is_on_patch) - for match in policy_break.matches - ], - ) - def censor(self) -> None: for policy_break in self.policy_breaks: for extended_match in policy_break.matches: @@ -110,31 +158,45 @@ def from_scan_result( - replace matches by ExtendedMatches """ - to_keep = [] - ignored_policy_breaks_count_by_reason = Counter() + to_keep: List[Tuple[PolicyBreak, Optional[IgnoreReason]]] = [] + ignored_policy_breaks_count_by_kind = Counter() for policy_break in scan_result.policy_breaks: ignore_reason = compute_ignore_reason(policy_break, secret_config) if ignore_reason is not None: if secret_config.all_secrets: - policy_break.exclude_reason = ignore_reason - policy_break.is_excluded = True - to_keep.append(policy_break) + to_keep.append((policy_break, ignore_reason)) else: - ignored_policy_breaks_count_by_reason[ignore_reason] += 1 + ignored_policy_breaks_count_by_kind[ignore_reason.kind] += 1 else: - to_keep.append(policy_break) + to_keep.append((policy_break, None)) result = Result( filename=file.filename, filemode=file.filemode, path=file.path, url=file.url, - policy_breaks=to_keep, - ignored_policy_breaks_count_by_reason=ignored_policy_breaks_count_by_reason, + policy_breaks=[], + ignored_policy_breaks_count_by_kind=ignored_policy_breaks_count_by_kind, ) lines = get_lines_from_content(file.content, file.filemode) - result.enrich_matches(lines) + secrets = [ + Secret( + validity=policy_break.validity, + known_secret=policy_break.known_secret, + incident_url=policy_break.incident_url, + break_type=policy_break.break_type, + matches=[ + ExtendedMatch.from_match(match, lines, result.is_on_patch) + for match in policy_break.matches + ], + ignore_reason=ignore_reason, + diff_kind=policy_break.diff_kind, + ) + for policy_break, ignore_reason in to_keep + ] + + result.policy_breaks = secrets return result diff --git a/ggshield/verticals/secret/secret_scanner.py b/ggshield/verticals/secret/secret_scanner.py index dff5eac3ec..159fb30224 100644 --- a/ggshield/verticals/secret/secret_scanner.py +++ b/ggshield/verticals/secret/secret_scanner.py @@ -215,7 +215,11 @@ def _collect_results( for file, scan_result in zip(chunk, scan.scan_results): result = Result.from_scan_result(file, scan_result, self.secret_config) for policy_break in result.policy_breaks: - self.cache.add_found_policy_break(policy_break, file.filename) + self.cache.add_found_policy_break( + policy_break.break_type, + policy_break.get_ignore_sha(), + file.filename, + ) results.append(result) self.cache.save() diff --git a/tests/unit/cmd/test_ignore.py b/tests/unit/cmd/test_ignore.py index 9c9c97c98c..21a8dea52d 100644 --- a/tests/unit/cmd/test_ignore.py +++ b/tests/unit/cmd/test_ignore.py @@ -9,6 +9,7 @@ from ggshield.core.cache import Cache from ggshield.core.config import Config from ggshield.core.errors import ExitCode +from ggshield.core.filter import get_ignore_sha from ggshield.core.scan import Commit, ScanContext, ScanMode from ggshield.core.types import IgnoredMatch from ggshield.verticals.secret import SecretScanner @@ -212,26 +213,14 @@ def test_do_not_duplicate_last_found_secrets(client, isolated_fs): ) cache = Cache() - cache.add_found_policy_break(policy_break, "a") - cache.add_found_policy_break(policy_break, "b") - - assert len(cache.last_found_secrets) == 1 - - -def test_do_not_add_policy_breaks_to_last_found(client, isolated_fs): - """ - GIVEN 1 policy breaks on different files with the same ignore sha - WHEN add_found_policy_break is called - THEN only one element should be added - """ - policy_break = PolicyBreak( - "a", "gitignore", None, [Match("apikey", "apikey", 0, 0, 0, 0)] + cache.add_found_policy_break( + policy_break.break_type, get_ignore_sha(policy_break), "a" + ) + cache.add_found_policy_break( + policy_break.break_type, get_ignore_sha(policy_break), "b" ) - cache = Cache() - - cache.add_found_policy_break(policy_break, "a") - assert len(cache.last_found_secrets) == 0 + assert len(cache.last_found_secrets) == 1 def test_ignore_last_found_preserve_previous_config(client, isolated_fs): diff --git a/tests/unit/verticals/secret/output/test_json_output.py b/tests/unit/verticals/secret/output/test_json_output.py index 9536c165dd..fd0479b79c 100644 --- a/tests/unit/verticals/secret/output/test_json_output.py +++ b/tests/unit/verticals/secret/output/test_json_output.py @@ -13,7 +13,6 @@ from voluptuous import Required, validators from ggshield.core.config.user_config import SecretConfig -from ggshield.core.filter import group_policy_breaks_by_ignore_sha from ggshield.core.scan import Commit, ScanContext, ScanMode, StringScannable from ggshield.core.scan.file import File from ggshield.utils.git_shell import Filemode @@ -27,6 +26,7 @@ SecretJSONOutputHandler, SecretOutputHandler, ) +from ggshield.verticals.secret.secret_scan_collection import group_secrets_by_ignore_sha from tests.unit.conftest import ( _MULTILINE_SECRET_FILE, _MULTIPLE_SECRETS_PATCH, @@ -425,7 +425,7 @@ def test_json_output_for_patch( assert all( ignore_sha in json_flat_results for result in results.results - for ignore_sha in group_policy_breaks_by_ignore_sha(result.policy_breaks) + for ignore_sha in group_secrets_by_ignore_sha(result.policy_breaks) ) diff --git a/tests/unit/verticals/secret/output/test_text_output.py b/tests/unit/verticals/secret/output/test_text_output.py index bbf9d1825f..90ce192e26 100644 --- a/tests/unit/verticals/secret/output/test_text_output.py +++ b/tests/unit/verticals/secret/output/test_text_output.py @@ -5,7 +5,6 @@ import pytest from ggshield.core.config.user_config import SecretConfig -from ggshield.core.filter import group_policy_breaks_by_ignore_sha from ggshield.core.scan import StringScannable from ggshield.utils.git_shell import Filemode from ggshield.verticals.secret import Result, Results, SecretScanCollection @@ -13,6 +12,7 @@ from ggshield.verticals.secret.output.secret_text_output_handler import ( format_line_count_break, ) +from ggshield.verticals.secret.secret_scan_collection import group_secrets_by_ignore_sha from tests.unit.conftest import ( _MULTI_SECRET_ONE_LINE_PATCH, _MULTI_SECRET_ONE_LINE_PATCH_OVERLAY, @@ -148,7 +148,7 @@ def test_leak_message(result_input, snapshot, show_secrets, verbose): # all ignore sha should be in the output assert all( ignore_sha in output - for ignore_sha in group_policy_breaks_by_ignore_sha(result_input.policy_breaks) + for ignore_sha in group_secrets_by_ignore_sha(result_input.policy_breaks) ) diff --git a/tests/unit/verticals/secret/test_scan_repo.py b/tests/unit/verticals/secret/test_scan_repo.py index 862d37653d..20d20b75e3 100644 --- a/tests/unit/verticals/secret/test_scan_repo.py +++ b/tests/unit/verticals/secret/test_scan_repo.py @@ -239,6 +239,29 @@ def test_scan_2_commits_file_association(secret_scanner_mock): assert len(scan_collection.scans) == 2 + values1 = sorted( + scan_collection.scans[0].results.results, key=lambda f: f.filename + )[0] + + values2 = sorted( + [ + Result.from_scan_result( + file1_3, policy_breaks_file_1_3, secret_config=SecretConfig() + ), + Result.from_scan_result( + file1_1, policy_breaks_file_1_1, secret_config=SecretConfig() + ), + ], + key=lambda f: f.filename, + )[0] + + # print(values1.policy_breaks[0].matches[0]) + print(type(values1.policy_breaks[0].matches[0])) + # print(values2.policy_breaks[0].matches[0]) + print(type(values2.policy_breaks[0].matches[0])) + + assert values1.policy_breaks[0].matches[0] == values2.policy_breaks[0].matches[0] + assert values1.policy_breaks[0] == values2.policy_breaks[0] # out of 3 files, only 2 results were returned assert sorted( scan_collection.scans[0].results.results, key=lambda f: f.filename diff --git a/tests/unit/verticals/secret/test_secret_scan_collection.py b/tests/unit/verticals/secret/test_secret_scan_collection.py index e172e32558..13ed4b735d 100644 --- a/tests/unit/verticals/secret/test_secret_scan_collection.py +++ b/tests/unit/verticals/secret/test_secret_scan_collection.py @@ -9,6 +9,7 @@ from ggshield.core.types import IgnoredMatch from ggshield.verticals.secret import Results from ggshield.verticals.secret.secret_scan_collection import ( + IgnoreKind, IgnoreReason, Result, compute_ignore_reason, @@ -125,15 +126,14 @@ def test_create_result_removes_ignored_matches_bis(all_secrets): ) if all_secrets: assert len(result.policy_breaks) == 2 - assert result.policy_breaks[0].is_excluded is True - assert result.policy_breaks[1].is_excluded is False + assert result.policy_breaks[0].is_ignored is True + assert result.policy_breaks[1].is_ignored is False else: + print(result.policy_breaks) + print(result.ignored_policy_breaks_count_by_kind) assert len(result.policy_breaks) == 1 - assert result.policy_breaks[0].is_excluded is False - assert ( - result.ignored_policy_breaks_count_by_reason[IgnoreReason.IGNORED_MATCH] - == 1 - ) + assert result.policy_breaks[0].is_ignored is False + assert result.ignored_policy_breaks_count_by_kind[IgnoreKind.IGNORED_MATCH] == 1 class TestComputeIgnoreReason: @@ -146,7 +146,9 @@ def test_ignore_excluded(self): policy_break = PolicyBreakFactory( is_excluded=True, exclude_reason="BACKEND_REASON" ) - assert "BACKEND_REASON" in compute_ignore_reason(policy_break, SecretConfig()) + assert compute_ignore_reason(policy_break, SecretConfig()) == IgnoreReason( + IgnoreKind.BACKEND_EXCLUDED, "BACKEND_REASON" + ) def test_ignore_ignored_match(self): """ diff --git a/tests/unit/verticals/secret/test_secret_scanner.py b/tests/unit/verticals/secret/test_secret_scanner.py index 4aa8304e66..9e5ecb9bc1 100644 --- a/tests/unit/verticals/secret/test_secret_scanner.py +++ b/tests/unit/verticals/secret/test_secret_scanner.py @@ -349,7 +349,7 @@ def test_scan_ignore_known_secrets(scan_mock: Mock, client, ignore_known_secrets """ scannable = StringScannable(url="localhost", content="known\nunknown") known_secret = PolicyBreak( - break_type="a", + break_type="known", policy="Secrets detection", validity="valid", known_secret=True, @@ -365,7 +365,7 @@ def test_scan_ignore_known_secrets(scan_mock: Mock, client, ignore_known_secrets ], ) unknown_secret = PolicyBreak( - break_type="a", + break_type="unknown", policy="Secrets detection", validity="valid", known_secret=False, @@ -401,9 +401,14 @@ def test_scan_ignore_known_secrets(scan_mock: Mock, client, ignore_known_secrets results = scanner.scan([scannable], scanner_ui=Mock()) if ignore_known_secrets: - assert results.results[0].policy_breaks == [unknown_secret] + assert [pbreak.break_type for pbreak in results.results[0].policy_breaks] == [ + "unknown" + ] else: - assert results.results[0].policy_breaks == [known_secret, unknown_secret] + assert [pbreak.break_type for pbreak in results.results[0].policy_breaks] == [ + "known", + "unknown", + ] @patch("pygitguardian.GGClient.multi_content_scan") @@ -482,4 +487,6 @@ def test_all_secrets_is_used(scan_mock: Mock, client): secret_config=SecretConfig(), ) results = scanner.scan([scannable], scanner_ui=Mock()) - assert results.results[0].policy_breaks == [secret] + assert [pbreak.break_type for pbreak in results.results[0].policy_breaks] == [ + "not-excluded" + ]