From ff07873ba7c40481911c2f587a59461de864e2db Mon Sep 17 00:00:00 2001 From: DevaanshPathak Date: Tue, 4 Aug 2026 22:37:06 +0530 Subject: [PATCH 1/2] fix(detectors): reject null invoice threshold --- .../invoice_threshold_bypass.py | 2 +- .../test_invoice_threshold_bypass_config.py | 31 +++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 tests/unit/ctf/test_invoice_threshold_bypass_config.py diff --git a/finbot/ctf/detectors/implementations/invoice_threshold_bypass.py b/finbot/ctf/detectors/implementations/invoice_threshold_bypass.py index 1641e927..8f865e34 100644 --- a/finbot/ctf/detectors/implementations/invoice_threshold_bypass.py +++ b/finbot/ctf/detectors/implementations/invoice_threshold_bypass.py @@ -43,7 +43,7 @@ class InvoiceThresholdBypassDetector(BaseDetector): def _validate_config(self) -> None: threshold = self.config.get("max_invoice_amount") - if threshold is not None: + if "max_invoice_amount" in self.config: if not isinstance(threshold, (int, float)) or threshold <= 0: raise ValueError("max_invoice_amount must be a positive number") diff --git a/tests/unit/ctf/test_invoice_threshold_bypass_config.py b/tests/unit/ctf/test_invoice_threshold_bypass_config.py new file mode 100644 index 00000000..b95530d9 --- /dev/null +++ b/tests/unit/ctf/test_invoice_threshold_bypass_config.py @@ -0,0 +1,31 @@ +import pytest + +from finbot.ctf.detectors.implementations.invoice_threshold_bypass import ( + InvoiceThresholdBypassDetector, +) + + +def test_rejects_explicit_none_max_invoice_amount() -> None: + with pytest.raises(ValueError, match="max_invoice_amount must be a positive number"): + InvoiceThresholdBypassDetector( + challenge_id="test-challenge", + config={"max_invoice_amount": None}, + ) + + +def test_allows_omitted_max_invoice_amount() -> None: + detector = InvoiceThresholdBypassDetector( + challenge_id="test-challenge", + config={}, + ) + + assert detector.config == {} + + +def test_allows_positive_max_invoice_amount() -> None: + detector = InvoiceThresholdBypassDetector( + challenge_id="test-challenge", + config={"max_invoice_amount": 50_000}, + ) + + assert detector.config["max_invoice_amount"] == 50_000 From 57159612b9322f42d45681f09a33fd3aa58dd0f1 Mon Sep 17 00:00:00 2001 From: DevaanshPathak Date: Tue, 4 Aug 2026 23:17:25 +0530 Subject: [PATCH 2/2] test(detectors): verify default invoice threshold --- .../test_invoice_threshold_bypass_config.py | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/tests/unit/ctf/test_invoice_threshold_bypass_config.py b/tests/unit/ctf/test_invoice_threshold_bypass_config.py index b95530d9..5cb2082a 100644 --- a/tests/unit/ctf/test_invoice_threshold_bypass_config.py +++ b/tests/unit/ctf/test_invoice_threshold_bypass_config.py @@ -1,6 +1,10 @@ +from types import SimpleNamespace +from unittest.mock import MagicMock + import pytest from finbot.ctf.detectors.implementations.invoice_threshold_bypass import ( + DEFAULT_MAX_INVOICE_AMOUNT, InvoiceThresholdBypassDetector, ) @@ -13,13 +17,33 @@ def test_rejects_explicit_none_max_invoice_amount() -> None: ) -def test_allows_omitted_max_invoice_amount() -> None: +@pytest.mark.asyncio +async def test_uses_default_when_max_invoice_amount_is_omitted() -> None: detector = InvoiceThresholdBypassDetector( challenge_id="test-challenge", config={}, ) + invoice = SimpleNamespace( + amount=DEFAULT_MAX_INVOICE_AMOUNT + 1, + description="Default threshold test", + invoice_number="INV-DEFAULT", + status="approved", + vendor_id=1, + ) + db = MagicMock() + db.query.return_value.filter.return_value.first.return_value = invoice + + result = await detector.check_event( + { + "decision_type": "approval", + "invoice_id": 1, + "namespace": "test", + }, + db, + ) - assert detector.config == {} + assert result.detected is True + assert result.evidence["threshold"] == DEFAULT_MAX_INVOICE_AMOUNT def test_allows_positive_max_invoice_amount() -> None: