Files
theHarvester/tests/lib/test_active_evidence.py
MattandGitHub 1fd5749e52 Add an authenticated durable run API v1 (#2511)
* Model active evidence in result persistence

* Expose active action diagnostics

* Persist truthful DNS action evidence

* Persist direct action evidence

* feat: add authenticated durable run API v1

* Make API file interchange JSONL-only

* Harden API evidence boundaries

* Remove API rate limiter

* Unify API runs with result persistence

* Support target action runs

* Keep DNS resolver selection action-neutral

* Add action-neutral CLI resolver selection

* Preserve API evidence across JSONL round trips

* Complete HarvestView API run parity

* Harden HarvestView run API contracts

* Remove obsolete bundled network snapshots

Delete the unused bundled AWS IP-range and resolver snapshots while preserving operator-supplied resolver file input.

* refactor: canonicalize URL results

* docs: define a bounded test budget

* Standardize hostname and IP result names

* fix(api): avoid duplicate evidence conflicts
2026-08-10 00:08:04 -04:00

111 lines
4.0 KiB
Python

from datetime import UTC, datetime
import pytest
from theHarvester.lib.active_evidence import ActionExecution, ActionObservation, ActiveEvidence, ArtifactReference
def screenshot_artifact() -> ArtifactReference:
return ArtifactReference(
kind='screenshot',
subject_kind='hostname',
subject_value='api.example.com',
path='screenshots/api.example.com.png',
media_type='image/png',
size_bytes=3,
sha256='0' * 64,
created_at=datetime(2026, 8, 9, 12, 0, tzinfo=UTC),
)
def test_active_evidence_owns_action_results_and_artifacts() -> None:
evidence = ActiveEvidence(
executions=(
ActionExecution.finish(
action='dns-resolve',
status='completed',
duration_ms=12.5,
groups={'ip': ['192.0.2.10', '192.0.2.10']},
),
ActionExecution.finish(
action='screenshot',
status='completed',
duration_ms=4.0,
groups={},
artifacts=(screenshot_artifact(),),
),
ActionExecution.finish(action='takeover', status='completed', duration_ms=2.0, groups={}),
)
)
assert evidence.executions[0].result_count == 1
assert evidence.executions[0].observations == (ActionObservation('ip', '192.0.2.10'),)
assert evidence.executions[1].result_count == 0
assert evidence.executions[1].artifacts == (screenshot_artifact(),)
def test_active_evidence_rejects_duplicate_actions() -> None:
with pytest.raises(ValueError, match='action executions must be unique'):
ActiveEvidence(
executions=(
ActionExecution('dns-resolve', 'completed', 1.0),
ActionExecution('dns-resolve', 'failed', 2.0, error_type='RuntimeError'),
)
)
def test_action_execution_rejects_noncanonical_observations_and_artifacts() -> None:
observation = ActionObservation('hostname', 'api.example.com')
artifact = screenshot_artifact()
with pytest.raises(ValueError, match='action observations must be deduplicated and sorted'):
ActionExecution('dns-brute', 'completed', 1.0, observations=(observation, observation))
with pytest.raises(ValueError, match='artifacts must be deduplicated and sorted'):
ActionExecution('screenshot', 'completed', 1.0, artifacts=(artifact, artifact))
def test_active_evidence_rejects_screenshot_results() -> None:
with pytest.raises(ValueError, match='screenshots must be stored as artifacts'):
ActionExecution.finish(
action='screenshot',
status='completed',
duration_ms=1.0,
groups={'screenshot': ['https://api.example.com']},
)
with pytest.raises(ValueError, match='known non-empty result'):
ArtifactReference(
kind='screenshot',
subject_kind='screenshot',
subject_value='https://api.example.com',
path='screenshots/api.example.com.png',
media_type='image/png',
size_bytes=3,
sha256='0' * 64,
created_at=datetime(2026, 8, 9, 12, 0, tzinfo=UTC),
)
def test_artifact_reference_rejects_invalid_metadata() -> None:
with pytest.raises(ValueError, match='sha256'):
ArtifactReference(
kind='screenshot',
subject_kind='hostname',
subject_value='api.example.com',
path='screenshots/api.example.com.png',
media_type='image/png',
size_bytes=3,
sha256='not-a-hash',
created_at=datetime(2026, 8, 9, 12, 0, tzinfo=UTC),
)
with pytest.raises(ValueError, match='timezone-aware'):
ArtifactReference(
kind='screenshot',
subject_kind='hostname',
subject_value='api.example.com',
path='screenshots/api.example.com.png',
media_type='image/png',
size_bytes=3,
sha256='0' * 64,
created_at=datetime(2026, 8, 9, 12, 0),
)