Files
rengine/tests/api/test_bounty_terms.py
T
Yogesh Ojha cfda12f149 feat(whats-new): a timeline of what changed since the last visit
What's new lists only what changed: rescans that found new critical or
high findings, new programs, assets added to or leaving scope, bounty
table and rule changes, certificate hosts from watches, and targets a
scope or watch added. Events group by day on a rail, with a divider at
the Caught up mark. Each rescan names its worst new checks, and every
count opens the scan filtered to exactly those findings.

Program syncs now record payout, safe harbor and 2FA changes, and per
asset tier, bounty eligibility, severity cap and instruction changes.
The public feed sync diffs known programs for these too.

The search cache no longer stores a page that carries an error, so a
timed-out search no longer answers 0 rows for ten minutes.
2026-09-24 20:49:34 +05:30

98 lines
2.9 KiB
Python

from __future__ import annotations
import pytest
from shared.definitions.bounty_programs import BountyEvent
from shared.models.bounty_program import BountyProgram
from shared.services.bounty_programs import scope_changes, terms_changes
pytestmark = pytest.mark.api
KEY = ("DOMAIN", "app.acme.com")
def _program(**fields) -> BountyProgram:
base = {
"platform": "intigriti",
"source": "feed",
"handle": "acme",
"name": "Acme",
"program_state": "public",
"submission_state": "open",
}
return BountyProgram(**{**base, **fields})
def _asset(**fields) -> dict:
return {
"scope_state": "in_scope",
"tier": None,
"eligible_for_bounty": None,
"max_severity": None,
"instruction": None,
**fields,
}
def test_a_payout_range_change_is_one_event():
program = _program(min_payout=500, max_payout=5000, payout_currency="EUR")
row = {"source": "feed", "min_payout": 1000, "max_payout": 10000}
assert terms_changes(program, row) == [
(BountyEvent.PAYOUT_CHANGED.value, "EUR 500 to 5,000 → EUR 1,000 to 10,000")
]
def test_a_payout_first_reported_is_not_a_change():
program = _program(min_payout=None, max_payout=None)
row = {"source": "feed", "min_payout": 100, "max_payout": 1000}
assert terms_changes(program, row) == []
def test_safe_harbor_and_2fa_changes_are_one_rules_event():
program = _program(safe_harbor="partial", requires_2fa=False)
row = {"source": "feed", "safe_harbor": "full", "requires_2fa": True}
assert terms_changes(program, row) == [
(BountyEvent.RULES_CHANGED.value, "Safe harbor partial → full · 2FA required")
]
def test_a_source_switch_reports_no_terms():
program = _program(min_payout=500, max_payout=5000, safe_harbor="full")
row = {"source": "api", "min_payout": None, "max_payout": 100, "safe_harbor": None}
assert terms_changes(program, row) == []
def test_an_asset_tier_change_is_a_bounty_event():
program = _program()
events = scope_changes(
program, {KEY: _asset(tier="Tier 2")}, {KEY: _asset(tier="Tier 1")}
)
assert [(e.kind, e.asset_identifier, e.detail) for e in events] == [
(BountyEvent.ASSET_BOUNTY_CHANGED.value, "app.acme.com", "Tier 2 → Tier 1")
]
def test_rewritten_instructions_are_a_rules_event():
program = _program()
events = scope_changes(
program,
{KEY: _asset(instruction="No automated scanning")},
{KEY: _asset(instruction="Automated scanning up to 5 req/s")},
)
assert [(e.kind, e.detail) for e in events] == [
(BountyEvent.ASSET_RULES_CHANGED.value, "Automated scanning up to 5 req/s")
]
def test_an_unchanged_asset_reports_nothing():
program = _program()
same = _asset(tier="Tier 1", instruction="Be nice")
assert scope_changes(program, {KEY: same}, {KEY: dict(same)}) == []