Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -224,8 +224,9 @@ fetched target object — a report, forum post, credential, indicator, or any ot

When a target cannot be fetched (no link, no known SDK route, forbidden, or an unexpected error),
the alert is still returned by default with `target=None` and a `status` explaining why, so nothing
is silently lost. Pass `skip_missing_targets=True` to omit such alerts instead. Marketplace alerts
are always skipped silently.
is silently lost. Pass `skip_missing_targets=True` to omit such alerts instead. Marketplace hits
have no SDK route yet, so they come back as bare alerts with `status=UNRESOLVABLE` (or are omitted
when `skip_missing_targets=True`), the same as any other unresolvable target.

```python
from verity471.helpers import fetch_alert_targets, AlertTarget, AlertTargetStatus
Expand All @@ -238,7 +239,7 @@ from verity471.helpers import fetch_alert_targets, AlertTarget, AlertTargetStatu
| `alerts_response` | `StreamingAlertsResponse` | *(required)* | The page returned by `AlertsApi.get_alerts_stream()`. |
| `api_client` | `ApiClient` | *(required)* | An active `ApiClient` instance (must share credentials with the alerts call). |
| `raise_on_error` | `bool` | `False` | When `True`, re-raise unexpected errors (and the missing-link error) instead of recording them on the result. |
| `skip_missing_targets` | `bool` | `False` | When `True`, alerts whose target cannot be fetched are omitted from the result. When `False` (default), they are returned with `target=None` and a failure `status`. Marketplace alerts are always skipped regardless. |
| `skip_missing_targets` | `bool` | `False` | When `True`, alerts whose target cannot be fetched are omitted from the result. When `False` (default), they are returned with `target=None` and a failure `status`. Marketplace hits (no SDK route yet) are treated like any other unresolvable target. |

### Returns

Expand Down
5 changes: 5 additions & 0 deletions pyrightconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"venvPath": ".",
"venv": ".venv",
"exclude": ["**/node_modules", "**/__pycache__", "**/.*"]
}
17 changes: 15 additions & 2 deletions tests/test_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,12 +257,25 @@ def test_empty_alerts_returns_empty_list(self, call_url_mock, _watchers_mock):
assert result == []
call_url_mock.assert_not_called()

def test_marketplace_url_is_skipped(self, call_url_mock, _watchers_mock):
def test_marketplace_url_returns_bare_alert(self, call_url_mock, watchers_mock):
# Marketplace hits have no SDK route, so they resolve as UnresolvableURL
# and come back as bare alerts (target=None) rather than being dropped.
call_url_mock.side_effect = UnresolvableURL("no route")
_no_watchers(watchers_mock)
alert = _mock_alert(url=_MARKETPLACE_URL)
with verity471.ApiClient(configuration) as api_client:
result = fetch_alert_targets(_alerts_response(alert), api_client)
assert len(result) == 1
assert result[0].alert is alert
assert result[0].target is None
assert result[0].status == AlertTargetStatus.UNRESOLVABLE

def test_marketplace_url_omitted_when_skipping(self, call_url_mock, _watchers_mock):
call_url_mock.side_effect = UnresolvableURL("no route")
alert = _mock_alert(url=_MARKETPLACE_URL)
with verity471.ApiClient(configuration) as api_client:
result = fetch_alert_targets(_alerts_response(alert), api_client, skip_missing_targets=True)
assert result == []
call_url_mock.assert_not_called()

def test_missing_link_is_skipped(self, _call_url_mock, _watchers_mock):
alert = _mock_alert()
Expand Down
8 changes: 4 additions & 4 deletions verity471/helpers/alerts.py
Original file line number Diff line number Diff line change
Expand Up @@ -327,8 +327,10 @@ def fetch_alert_targets(
default (``False``) the alert is still returned with ``target=None`` and a
non-``OK`` :class:`AlertTargetStatus` (plus a ``status_reason``) so callers
can see it failed and why; when ``True`` such alerts are omitted from the
result entirely. Marketplace alerts are always skipped silently, regardless
of either flag.
result entirely. Marketplace alerts have no SDK route yet, so they are
treated like any other unresolvable target: returned as bare alerts with
``status=UNRESOLVABLE`` by default, or omitted when *skip_missing_targets*
is ``True``.

Args:
alerts_response: The page returned by :meth:`AlertsApi.get_alerts_stream`.
Expand All @@ -354,8 +356,6 @@ def fetch_alert_targets(
"""
def _fetch(alert: StreamingWatcherAlert) -> AlertTarget | None:
url = alert.links.verity_api.href if (alert.links and alert.links.verity_api) else None
if url and "/integrations/marketplaces/" in url:
return None
if not url:
if raise_on_error:
raise ValueError("Alert %s has no verity_api link" % alert.source_id)
Expand Down
Loading