# Subsystem: tests (page 1 of 5)
Pages: [KB_tests.md](KB_tests.md), [KB_tests_p2.md](KB_tests_p2.md), [KB_tests_p3.md](KB_tests_p3.md), [KB_tests_p4.md](KB_tests_p4.md), [KB_tests_p5.md](KB_tests_p5.md)

## tests/conftest.py
- Doc: Pytest configuration and shared fixtures for the estorides test suite.
- Layer: testing
- Language: py

## tests/test_active_recon.py
- Doc: ATDD + BDD tests for estorides_core.active_recon.
- Layer: testing
- Language: py
- Symbols:
  - `TestNmapResult` (class, line 24) `class TestNmapResult`
  - `TestNiktoResult` (class, line 45) `class TestNiktoResult`
  - `TestSqlmapResult` (class, line 58) `class TestSqlmapResult`
  - `TestDnsreconResult` (class, line 71) `class TestDnsreconResult`
  - `TestTheHarvesterResult` (class, line 84) `class TestTheHarvesterResult`
  - `TestResultTypes` (class, line 97) `class TestResultTypes`
  - `TestErrorResultsFlowThrough` (class, line 135) `class TestErrorResultsFlowThrough`
  - `test_run_nmap_returns_result` (method, line 25) `def test_run_nmap_returns_result(self)`
  - `test_nmap_result_has_to_dict` (method, line 29) `def test_nmap_result_has_to_dict(self)`
  - `test_nmap_result_to_entities_is_list` (method, line 38) `def test_nmap_result_to_entities_is_list(self)`
  - `test_run_nikto_returns_result` (method, line 46) `def test_run_nikto_returns_result(self)`
  - `test_nikto_result_has_to_dict` (method, line 50) `def test_nikto_result_has_to_dict(self)`
  - `test_run_sqlmap_returns_result` (method, line 59) `def test_run_sqlmap_returns_result(self)`
  - `test_sqlmap_result_has_to_dict` (method, line 63) `def test_sqlmap_result_has_to_dict(self)`
  - `test_run_dnsrecon_returns_result` (method, line 72) `def test_run_dnsrecon_returns_result(self)`
  - `test_dnsrecon_result_has_to_dict` (method, line 76) `def test_dnsrecon_result_has_to_dict(self)`
  - `test_run_theHarvester_returns_result` (method, line 85) `def test_run_theHarvester_returns_result(self)`
  - `test_harvester_result_has_to_dict` (method, line 89) `def test_harvester_result_has_to_dict(self)`
  - `test_nmap_result_is_dataclass` (method, line 98) `def test_nmap_result_is_dataclass(self)`
  - `test_nikto_result_is_dataclass` (method, line 106) `def test_nikto_result_is_dataclass(self)`
  - `test_sqlmap_result_is_dataclass` (method, line 113) `def test_sqlmap_result_is_dataclass(self)`
  - `test_dnsrecon_result_is_dataclass` (method, line 120) `def test_dnsrecon_result_is_dataclass(self)`
  - `test_harvester_result_is_dataclass` (method, line 127) `def test_harvester_result_is_dataclass(self)`
  - `test_nmap_error_result_has_empty_entities` (method, line 136) `def test_nmap_error_result_has_empty_entities(self)`
- Depends on: `estorides_core/active_recon.py`, `estorides_core/tool_runner.py`

## tests/test_async_client.py
- Doc: BDD tests for egress anonymisation wiring (`estorides_core.async_client`).
- Layer: testing
- Language: py
- Symbols:
  - `TestSocksDetection` (class, line 16) `class TestSocksDetection`
  - `TestRedaction` (class, line 23) `class TestRedaction`
  - `TestEffectiveProxies` (class, line 33) `class TestEffectiveProxies`
  - `TestRotation` (class, line 45) `class TestRotation`
  - `TestFailClosed` (class, line 63) `class TestFailClosed`
  - `test_schemes` (method, line 17) `def test_schemes(self)`
  - `test_hides_credentials_keeps_host` (method, line 24) `def test_hides_credentials_keeps_host(self)`
  - `test_passthrough_without_credentials` (method, line 29) `def test_passthrough_without_credentials(self)`
  - `test_explicit_wins` (method, line 34) `def test_explicit_wins(self, monkeypatch)`
  - `test_none_by_default` (method, line 39) `def test_none_by_default(self, monkeypatch)`
  - `test_round_robin` (method, line 46) `def test_round_robin(self)`
  - `test_direct_has_no_proxy` (method, line 57) `def test_direct_has_no_proxy(self)`
  - `test_socks_without_backend_raises` (method, line 64) `def test_socks_without_backend_raises(self, monkeypatch)`
  - `_enter_and_rotate` (method, line 49) `def _enter_and_rotate()`
  - `_enter_socks` (method, line 68) `def _enter_socks()`
- Depends on: `estorides_core/async_client.py`, `estorides_core/config.py`

## tests/test_audit_log.py
- Doc: AuditLog rotation: file size cap, in-place .N rotation (issue #48).
- Layer: testing
- Language: py
- Symbols:
  - `_ev` (function, line 12) `def _ev(ts)`
  - `test_audit_log_appends` (function, line 22) `def test_audit_log_appends(tmp_path)`
  - `test_audit_log_rotates_when_cap_exceeded` (function, line 31) `def test_audit_log_rotates_when_cap_exceeded(tmp_path)`
  - `test_audit_log_rotation_respects_keep_count` (function, line 48) `def test_audit_log_rotation_respects_keep_count(tmp_path)`
  - `test_audit_log_no_rotation_when_disabled` (function, line 61) `def test_audit_log_no_rotation_when_disabled(tmp_path)`
- Depends on: `estorides_core/audit.py`

## tests/test_auth_gate.py
- Doc: Auth gate: bearer-token protection for sensitive /api/* routes.
- Layer: testing
- Language: py
- Symbols:
  - `app_with_gate` (function, line 22) `def app_with_gate(monkeypatch)`
  - `test_gate_auto_generates_token_when_unset` (function, line 41) `def test_gate_auto_generates_token_when_unset(monkeypatch)`
  - `test_gate_on_rejects_anonymous` (function, line 53) `def test_gate_on_rejects_anonymous(app_with_gate)`
  - `test_gate_on_accepts_bearer_header` (function, line 61) `def test_gate_on_accepts_bearer_header(app_with_gate)`
  - `test_gate_on_accepts_alt_header` (function, line 68) `def test_gate_on_accepts_alt_header(app_with_gate)`
  - `test_gate_on_accepts_cookie` (function, line 74) `def test_gate_on_accepts_cookie(app_with_gate)`
  - `test_gate_on_rejects_wrong_token` (function, line 81) `def test_gate_on_rejects_wrong_token(app_with_gate)`
  - `test_gate_on_auto_generated_token_in_meta` (function, line 87) `def test_gate_on_auto_generated_token_in_meta(monkeypatch)`
  - `test_gate_on_exposes_token_for_index_meta` (function, line 95) `def test_gate_on_exposes_token_for_index_meta()`
  - `private` (function, line 34) `def private()`
- Depends on: `estorides_core/web_security.py`

## tests/test_case_crypto.py
- Doc: M4 case_crypto BDD red.
- Layer: testing
- Language: py
- Symbols:
  - `_fresh_store` (function, line 5) `def _fresh_store(tmp_path, monkeypatch, key)`
  - `test_disabled_stores_plaintext` (function, line 22) `def test_disabled_stores_plaintext(tmp_path, monkeypatch)`
  - `test_enabled_roundtrip` (function, line 33) `def test_enabled_roundtrip(tmp_path, monkeypatch)`
  - `test_bad_key_falls_back` (function, line 58) `def test_bad_key_falls_back(tmp_path, monkeypatch)`
  - `test_mixed_rows_and_tamper` (function, line 69) `def test_mixed_rows_and_tamper(tmp_path, monkeypatch)`
- Depends on: `estorides_core/case_crypto.py`, `estorides_core/cases.py`

## tests/test_central_config.py
- Doc: M6 RED tests: central config for orchestrator + tool_install.
- Layer: testing
- Language: py
- Symbols:
  - `test_config_defaults` (function, line 7) `def test_config_defaults()`
  - `test_run_defaults_track_config` (function, line 20) `def test_run_defaults_track_config()`
  - `test_tool_install_survives_malformed_env` (function, line 29) `def test_tool_install_survives_malformed_env(monkeypatch)`
- Depends on: `estorides_core/__init__.py`, `estorides_core/orchestrator.py`, `estorides_core/tool_install.py`

## tests/test_change_detection.py
- Doc: ATDD + BDD tests for estorides_core.change_detection.
- Layer: testing
- Language: py
- Symbols:
  - `_entity` (function, line 28) `def _entity(eid, etype, value)`
  - `TestNewEntity` (class, line 56) `class TestNewEntity`
  - `TestPropertyChanged` (class, line 99) `class TestPropertyChanged`
  - `TestFirstRunBeforeIsNone` (class, line 125) `class TestFirstRunBeforeIsNone`
  - `TestAfterIsNone` (class, line 147) `class TestAfterIsNone`
  - `TestDisappearedWithGrace` (class, line 166) `class TestDisappearedWithGrace`
  - `TestSourceAdded` (class, line 199) `class TestSourceAdded`
  - `TestMinReliabilityFiltersSources` (class, line 226) `class TestMinReliabilityFiltersSources`
  - `TestMaxChangesBounds` (class, line 255) `class TestMaxChangesBounds`
  - `TestProgrammerErrorRaises` (class, line 281) `class TestProgrammerErrorRaises`
  - `TestHostilePropertyKey` (class, line 310) `class TestHostilePropertyKey`
  - `TestDeterminism` (class, line 346) `class TestDeterminism`
  - `TestSourceRemoved` (class, line 386) `class TestSourceRemoved`
  - `TestEdgeChanges` (class, line 436) `class TestEdgeChanges`
  - `TestConfidenceShifted` (class, line 488) `class TestConfidenceShifted`
  - `TestBoundedSmoke` (class, line 528) `class TestBoundedSmoke`
  - `test_one_new_entity_emits_one_new_change` (method, line 59) `def test_one_new_entity_emits_one_new_change(self)`
  - `test_new_change_score_in_high_band` (method, line 84) `def test_new_change_score_in_high_band(self)`
  - `test_property_change_emits_one_change` (method, line 102) `def test_property_change_emits_one_change(self)`
  - `test_before_none_reports_all_as_new` (method, line 128) `def test_before_none_reports_all_as_new(self)`
  - `test_after_none_returns_empty_report` (method, line 150) `def test_after_none_returns_empty_report(self)`
  - `test_disappeared_within_grace_is_ignored` (method, line 169) `def test_disappeared_within_grace_is_ignored(self)`
  - `test_disappeared_outside_grace_emits_change` (method, line 181) `def test_disappeared_outside_grace_emits_change(self)`
  - `test_new_source_on_existing_entity_emits_source_added` (method, line 202) `def test_new_source_on_existing_entity_emits_source_added(self)`
  - `test_min_reliability_e_excludes_f_source` (method, line 229) `def test_min_reliability_e_excludes_f_source(self)`
  - `test_max_changes_caps_output` (method, line 258) `def test_max_changes_caps_output(self)`
  - `test_entity_id_empty_raises` (method, line 284) `def test_entity_id_empty_raises(self)`
  - `test_entity_type_empty_raises` (method, line 288) `def test_entity_type_empty_raises(self)`
  - `test_entity_value_empty_raises` (method, line 292) `def test_entity_value_empty_raises(self)`
  - `test_min_change_score_out_of_range_raises` (method, line 296) `def test_min_change_score_out_of_range_raises(self)`
  - `test_max_changes_too_small_raises` (method, line 302) `def test_max_changes_too_small_raises(self)`
  - `test_hostile_key_does_not_crash` (method, line 322) `def test_hostile_key_does_not_crash(self, hostile_key)`
  - `test_same_input_same_ids_and_scores` (method, line 349) `def test_same_input_same_ids_and_scores(self)`
  - `test_input_order_does_not_affect_output` (method, line 366) `def test_input_order_does_not_affect_output(self)`
  - `test_source_removed_emits_change` (method, line 389) `def test_source_removed_emits_change(self)`
  - `test_source_removed_filtered_by_min_score` (method, line 410) `def test_source_removed_filtered_by_min_score(self)`
  - `test_edge_added_emits_change` (method, line 439) `def test_edge_added_emits_change(self)`
  - `test_edge_removed_emits_change` (method, line 462) `def test_edge_removed_emits_change(self)`
  - `test_large_confidence_shift_emits_change` (method, line 491) `def test_large_confidence_shift_emits_change(self)`
  - `test_small_confidence_shift_ignored` (method, line 509) `def test_small_confidence_shift_ignored(self)`
  - `test_change_is_frozen` (method, line 531) `def test_change_is_frozen(self)`
  - `test_diff_is_frozen` (method, line 541) `def test_diff_is_frozen(self)`
  - `test_change_report_is_frozen` (method, line 546) `def test_change_report_is_frozen(self)`
- Depends on: `estorides_core/change_detection.py`, `estorides_core/reliability_scoring.py`

## tests/test_cli_watch.py
- Doc: BDD / regression tests for the `estorides watch add` CLI path.
- Layer: testing
- Language: py
- Symbols:
  - `_FakeScheduler` (class, line 24) `class _FakeScheduler`
  - `cli_env` (method, line 47) `def cli_env(monkeypatch, tmp_path)`
  - `TestCW1Parser` (class, line 59) `class TestCW1Parser`
  - `TestCW2Command` (class, line 69) `class TestCW2Command`
  - `TestCW3Validation` (class, line 92) `class TestCW3Validation`
  - `TestCW4RunnerFactory` (class, line 102) `class TestCW4RunnerFactory`
  - `__init__` (method, line 27) `def __init__(self)`
  - `has_runner` (method, line 34) `def has_runner(self)`
  - `set_runner` (method, line 37) `def set_runner(self, runner)`
  - `start` (method, line 41) `def start(self)`
  - `test_watch_add_accepts_opsec_flags` (method, line 60) `def test_watch_add_accepts_opsec_flags(self)`
  - `test_watch_add_persists_and_starts` (method, line 70) `def test_watch_add_persists_and_starts(self, cli_env)`
  - `test_watch_add_wires_runner_once` (method, line 82) `def test_watch_add_wires_runner_once(self, cli_env)`
  - `test_interval_below_minimum_rejected` (method, line 93) `def test_interval_below_minimum_rejected(self, cli_env)`
  - `test_runner_factory_returns_awaitable_callable` (method, line 103) `def test_runner_factory_returns_awaitable_callable(self, cli_env)`
  - `test_scheduler_has_runner_property` (method, line 109) `def test_scheduler_has_runner_property(self, tmp_path)`
- Depends on: `estorides_cli.py`, `estorides_core/monitoring.py`

## tests/test_cloud_asset_discovery.py
- Doc: ATDD + BDD tests for estorides_core.cloud_asset_discovery.
- Layer: testing
- Language: py
- Symbols:
  - `_make_asset` (function, line 16) `def _make_asset(provider, url, accessible, listing)`
  - `TestPublicS3Bucket` (class, line 27) `class TestPublicS3Bucket`
  - `TestBucketWithListing` (class, line 40) `class TestBucketWithListing`
  - `TestNoAssets` (class, line 58) `class TestNoAssets`
  - `TestReadOnlyMethods` (class, line 68) `class TestReadOnlyMethods`
  - `TestCloudFrontDetection` (class, line 77) `class TestCloudFrontDetection`
  - `TestFirebaseDiscovery` (class, line 90) `class TestFirebaseDiscovery`
  - `TestBucketNamePermutations` (class, line 103) `class TestBucketNamePermutations`
  - `TestRateLimiting` (class, line 127) `class TestRateLimiting`
  - `test_accessible_bucket_returned` (method, line 28) `def test_accessible_bucket_returned(self)`
  - `test_listing_bucket_has_files` (method, line 41) `def test_listing_bucket_has_files(self)`
  - `test_empty_when_no_cloud_assets` (method, line 59) `def test_empty_when_no_cloud_assets(self)`
  - `test_assess_bucket_uses_get_only` (method, line 69) `def test_assess_bucket_uses_get_only(self)`
  - `test_cloudfront_cname_detected` (method, line 78) `def test_cloudfront_cname_detected(self)`
  - `test_firebase_accessible` (method, line 91) `def test_firebase_accessible(self)`
  - `test_generates_common_permutations` (method, line 104) `def test_generates_common_permutations(self)`
  - `test_strips_tld_for_permutations` (method, line 114) `def test_strips_tld_for_permutations(self)`
  - `test_rate_limit_constant_defined` (method, line 128) `def test_rate_limit_constant_defined(self)`
- Depends on: `estorides_core/cloud_asset_discovery.py`

## tests/test_code_exposure.py
- Doc: ATDD + BDD tests for estorides_core.code_exposure.
- Layer: testing
- Language: py
- Symbols:
  - `TestAwsKeyDetection` (class, line 17) `class TestAwsKeyDetection`
  - `TestNoFindings` (class, line 38) `class TestNoFindings`
  - `TestRateLimited` (class, line 47) `class TestRateLimited`
  - `TestInternalUrlDetection` (class, line 54) `class TestInternalUrlDetection`
  - `TestEnvFileDetection` (class, line 62) `class TestEnvFileDetection`
  - `TestPlaceholderCredentials` (class, line 70) `class TestPlaceholderCredentials`
  - `TestMultiPlatform` (class, line 81) `class TestMultiPlatform`
  - `TestSnippetBounded` (class, line 95) `class TestSnippetBounded`
  - `TestClassifyFindingTypes` (class, line 103) `class TestClassifyFindingTypes`
  - `test_detects_aws_key_as_critical` (method, line 18) `def test_detects_aws_key_as_critical(self)`
  - `test_validates_aws_key_format` (method, line 32) `def test_validates_aws_key_format(self)`
  - `test_empty_when_no_repos` (method, line 39) `def test_empty_when_no_repos(self)`
  - `test_returns_partial_on_rate_limit` (method, line 48) `def test_returns_partial_on_rate_limit(self)`
  - `test_internal_url_is_high_severity` (method, line 55) `def test_internal_url_is_high_severity(self)`
  - `test_env_file_is_critical_config` (method, line 63) `def test_env_file_is_critical_config(self)`
  - `test_placeholder_marked_info` (method, line 71) `def test_placeholder_marked_info(self)`
  - `test_example_key_marked_info` (method, line 75) `def test_example_key_marked_info(self)`
  - `test_aggregates_multi_source` (method, line 82) `def test_aggregates_multi_source(self)`
  - `test_snippet_under_200_chars` (method, line 96) `def test_snippet_under_200_chars(self)`
  - `test_ssh_key_detection` (method, line 104) `def test_ssh_key_detection(self)`
  - `test_api_key_detection` (method, line 109) `def test_api_key_detection(self)`
- Depends on: `estorides_core/code_exposure.py`

## tests/test_config_env.py
- Doc: BDD / regression tests for fault-tolerant configuration parsing.
- Layer: testing
- Language: py
- Symbols:
  - `_run_with_env` (function, line 23) `def _run_with_env(env_overrides, code)`
  - `TestCE1TypedReaders` (class, line 33) `class TestCE1TypedReaders`
  - `TestCE2MalformedFallsBack` (class, line 47) `class TestCE2MalformedFallsBack`
  - `TestCE3ImportNeverCrashes` (class, line 76) `class TestCE3ImportNeverCrashes`
  - `TestCE4CsvStripped` (class, line 92) `class TestCE4CsvStripped`
  - `test_env_int_parses` (method, line 34) `def test_env_int_parses(self, monkeypatch)`
  - `test_env_float_parses` (method, line 40) `def test_env_float_parses(self, monkeypatch)`
  - `test_env_int_garbage` (method, line 48) `def test_env_int_garbage(self, monkeypatch)`
  - `test_env_float_garbage` (method, line 54) `def test_env_float_garbage(self, monkeypatch)`
  - `test_env_bool_unknown_uses_default` (method, line 60) `def test_env_bool_unknown_uses_default(self, monkeypatch)`
  - `test_env_bool_tokens` (method, line 67) `def test_env_bool_tokens(self, monkeypatch)`
  - `test_malformed_numeric_env_does_not_break_import` (method, line 77) `def test_malformed_numeric_env_does_not_break_import(self)`
  - `test_exact_dedup_keys_are_stripped` (method, line 93) `def test_exact_dedup_keys_are_stripped(self)`
- Depends on: `estorides_core/config.py`

## tests/test_csp_safe_styles.py
- Doc: CSP-safe styles: regression tests for the inline-style bug introduced by the CSP tightening in...
- Layer: testing
- Language: py
- Symbols:
  - `_strip_template_jinja` (function, line 41) `def _strip_template_jinja(template_text)`
  - `_strip_js_comments_and_strings_outside_templates` (function, line 55) `def _strip_js_comments_and_strings_outside_templates(js_text)`
  - `test_index_html_has_no_style_attribute` (function, line 90) `def test_index_html_has_no_style_attribute()`
  - `test_estorides_js_has_no_style_in_template_literals` (function, line 111) `def test_estorides_js_has_no_style_in_template_literals()`
  - `test_offscreen_element_uses_hidden_attribute` (function, line 145) `def test_offscreen_element_uses_hidden_attribute(element_id)`
  - `test_css_has_required_class` (function, line 189) `def test_css_has_required_class(selector)`
  - `test_csp_policy_does_not_relax_for_unsafe_inline` (function, line 201) `def test_csp_policy_does_not_relax_for_unsafe_inline()`
  - `test_csp_policy_is_unchanged_after_refactor` (function, line 225) `def test_csp_policy_is_unchanged_after_refactor()`
  - `test_dynamic_cluster_color_uses_cssom_assignment` (function, line 252) `def test_dynamic_cluster_color_uses_cssom_assignment()`
  - `test_dynamic_kind_color_uses_cssom_assignment` (function, line 272) `def test_dynamic_kind_color_uses_cssom_assignment()`
  - `test_rendered_template_has_no_style_attribute_and_uses_hidden` (function, line 289) `def test_rendered_template_has_no_style_attribute_and_uses_hidden()`
- Depends on: `estorides_core/search_telemetry.py`, `estorides_core/web_security.py`

## tests/test_encrypted_export.py
- Doc: Encrypted export: plaintext must be removed after encryption.
- Layer: testing
- Language: py
- Symbols:
  - `_FakeCompleted` (class, line 21) `class _FakeCompleted`
  - `_kg_with_one_node` (method, line 27) `def _kg_with_one_node()`
  - `_patch_age_ok` (method, line 33) `def _patch_age_ok()`
  - `test_stix_encrypted_removes_plaintext` (method, line 43) `def test_stix_encrypted_removes_plaintext(tmp_path)`
  - `test_misp_encrypted_removes_plaintext` (method, line 52) `def test_misp_encrypted_removes_plaintext(tmp_path)`
  - `test_stix_encrypted_removes_plaintext_on_failure` (method, line 61) `def test_stix_encrypted_removes_plaintext_on_failure(tmp_path)`
  - `__init__` (method, line 22) `def __init__(self, rc, stderr)`
  - `_run` (method, line 35) `def _run(cmd, stdin, stdout, stderr, check)`
  - `_run_fail` (method, line 66) `def _run_fail(cmd, stdin, stdout, stderr, check)`
- Depends on: `estorides_core/entity_extraction.py`, `estorides_core/knowledge_graph.py`, `estorides_export/encryption.py`

## tests/test_entity_extraction.py
- Doc: BDD / regression tests for entity extraction and fuzzy merge.  - EE1: `types=[]` extracts...
- Layer: testing
- Language: py
- Symbols:
  - `TestEE1TypesFilter` (class, line 12) `class TestEE1TypesFilter`
  - `TestEE2FuzzyCanonical` (class, line 28) `class TestEE2FuzzyCanonical`
  - `test_none_means_all` (method, line 15) `def test_none_means_all(self)`
  - `test_empty_list_means_nothing` (method, line 20) `def test_empty_list_means_nothing(self)`
  - `test_subset_restricts` (method, line 23) `def test_subset_restricts(self)`
  - `test_most_observed_spelling_wins` (method, line 29) `def test_most_observed_spelling_wins(self)`
  - `test_merge_unions_sources` (method, line 41) `def test_merge_unions_sources(self)`
- Depends on: `estorides_core/entity_extraction.py`

## tests/test_entity_resolution.py
- Doc: BDD tests for estorides_core.entity_resolution.
- Layer: testing
- Language: py
- Symbols:
  - `_ent` (function, line 28) `def _ent(etype, value, source, confidence)`
  - `_by_value` (function, line 32) `def _by_value(result, value)`
  - `TestTransliteration` (class, line 42) `class TestTransliteration`
  - `TestJaroWinkler` (class, line 68) `class TestJaroWinkler`
  - `TestNormalization` (class, line 89) `class TestNormalization`
  - `TestCanonicalId` (class, line 133) `class TestCanonicalId`
  - `TestCrossScriptPersonFusion` (class, line 155) `class TestCrossScriptPersonFusion`
  - `TestDomainCaseVariantMerge` (class, line 194) `class TestDomainCaseVariantMerge`
  - `TestLookAlikeDomainsSurfaceAsLink` (class, line 221) `class TestLookAlikeDomainsSurfaceAsLink`
  - `TestDeterministicTypeNoFuzzyMatch` (class, line 246) `class TestDeterministicTypeNoFuzzyMatch`
  - `TestIdenticalIpsMerge` (class, line 272) `class TestIdenticalIpsMerge`
  - `TestNearIpsNeverFuse` (class, line 289) `class TestNearIpsNeverFuse`
  - `TestOrgSuffixFolding` (class, line 309) `class TestOrgSuffixFolding`
  - `TestDistinctPersonsStaySeparate` (class, line 326) `class TestDistinctPersonsStaySeparate`
  - `TestCanonicalEntityRoundtrip` (class, line 347) `class TestCanonicalEntityRoundtrip`
  - `TestEmptyInput` (class, line 382) `class TestEmptyInput`
  - `TestCanonicalIdDeterministic` (class, line 394) `class TestCanonicalIdDeterministic`
  - `TestDifferentInputDifferentId` (class, line 406) `class TestDifferentInputDifferentId`
  - `TestEdgeCases` (class, line 418) `class TestEdgeCases`
  - `TestCrossRunStability` (class, line 448) `class TestCrossRunStability`
  - `test_cyrillic_to_latin` (method, line 45) `def test_cyrillic_to_latin(self)`
  - `test_greek_accented_to_latin` (method, line 48) `def test_greek_accented_to_latin(self)`
  - `test_diacritic_fold` (method, line 51) `def test_diacritic_fold(self)`
  - `test_consonant_skeleton_arabic_matches_latin` (method, line 54) `def test_consonant_skeleton_arabic_matches_latin(self)`
  - `test_consonant_skeleton_gemination` (method, line 57) `def test_consonant_skeleton_gemination(self)`
  - `test_distinct_names_have_distinct_skeletons` (method, line 60) `def test_distinct_names_have_distinct_skeletons(self)`
  - `test_non_latin_detector` (method, line 63) `def test_non_latin_detector(self)`
  - `test_identical_strings_score_one` (method, line 71) `def test_identical_strings_score_one(self)`
  - `test_empty_pair_scores_zero` (method, line 74) `def test_empty_pair_scores_zero(self)`
  - `test_classic_jaro_winkler_bound` (method, line 77) `def test_classic_jaro_winkler_bound(self)`
  - `test_dissimilar_strings_score_low` (method, line 81) `def test_dissimilar_strings_score_low(self)`
  - `test_scores_stay_in_unit_interval` (method, line 84) `def test_scores_stay_in_unit_interval(self)`
  - `test_ipv4_normalised` (method, line 92) `def test_ipv4_normalised(self)`
  - `test_ipv6_compressed` (method, line 95) `def test_ipv6_compressed(self)`
  - `test_hash_lowered` (method, line 101) `def test_hash_lowered(self)`
  - `test_cve_uppered` (method, line 107) `def test_cve_uppered(self)`
  - `test_domain_strips_scheme_www_path` (method, line 110) `def test_domain_strips_scheme_www_path(self)`
  - `test_person_order_independent` (method, line 116) `def test_person_order_independent(self)`
  - `test_org_suffix_stripped` (method, line 121) `def test_org_suffix_stripped(self)`
  - `test_asn_normalised` (method, line 126) `def test_asn_normalised(self)`
  - `test_email_lowered` (method, line 129) `def test_email_lowered(self)`
  - `test_deterministic` (method, line 136) `def test_deterministic(self)`
  - `test_different_values_different_ids` (method, line 141) `def test_different_values_different_ids(self)`
  - `test_id_format` (method, line 146) `def test_id_format(self)`
  - `test_three_spellings_fuse` (method, line 158) `def test_three_spellings_fuse(self)`
  - `test_fused_identity_carries_all_sources` (method, line 169) `def test_fused_identity_carries_all_sources(self)`
  - `test_cross_script_flagged_in_attributes` (method, line 180) `def test_cross_script_flagged_in_attributes(self)`
  - `test_domain_case_variants_merge` (method, line 197) `def test_domain_case_variants_merge(self)`
  - `test_domain_merge_is_exact` (method, line 207) `def test_domain_merge_is_exact(self)`
  - `test_look_alike_domains_stay_separate` (method, line 224) `def test_look_alike_domains_stay_separate(self)`
  - `test_look_alike_domains_produce_same_as_link` (method, line 233) `def test_look_alike_domains_produce_same_as_link(self)`
  - `test_deterministic_near_miss_never_matches` (method, line 249) `def test_deterministic_near_miss_never_matches(self)`
  - `test_score_pair_deterministic_mismatch` (method, line 259) `def test_score_pair_deterministic_mismatch(self)`
  - `test_identical_ips_merge` (method, line 275) `def test_identical_ips_merge(self)`
  - `test_near_ips_stay_separate` (method, line 292) `def test_near_ips_stay_separate(self)`
  - `test_org_suffix_variants_merge` (method, line 312) `def test_org_suffix_variants_merge(self)`
  - `test_distinct_persons_not_absorbed` (method, line 329) `def test_distinct_persons_not_absorbed(self)`
  - `test_to_dict_serialises` (method, line 350) `def test_to_dict_serialises(self)`
  - `test_to_entity_projects_legacy` (method, line 361) `def test_to_entity_projects_legacy(self)`
  - `test_resolution_result_has_one_entity` (method, line 373) `def test_resolution_result_has_one_entity(self)`
  - `test_empty_input_returns_empty` (method, line 385) `def test_empty_input_returns_empty(self)`
  - `test_same_normalised_same_id` (method, line 397) `def test_same_normalised_same_id(self)`
  - `test_different_normalised_different_id` (method, line 409) `def test_different_normalised_different_id(self)`
  - `test_blank_value_does_not_crash` (method, line 421) `def test_blank_value_does_not_crash(self)`
  - `test_whitespace_only_handled` (method, line 425) `def test_whitespace_only_handled(self)`
  - `test_single_entity_produces_one_canonical` (method, line 429) `def test_single_entity_produces_one_canonical(self)`
  - `test_confidence_boosted_by_multiple_sources` (method, line 434) `def test_confidence_boosted_by_multiple_sources(self)`
  - `test_cross_run_id_stability` (method, line 451) `def test_cross_run_id_stability(self)`
- Depends on: `estorides_core/entity_extraction.py`, `estorides_core/entity_resolution.py`, `estorides_core/entity_store.py`, `estorides_core/transliteration.py`

## tests/test_envutil.py
- Doc: BDD tests for the shared env readers (spec/envutil.md).
- Layer: testing
- Language: py
- Symbols:
  - `TestEnvInt` (class, line 7) `class TestEnvInt`
  - `TestEnvFloat` (class, line 20) `class TestEnvFloat`
  - `TestEnvBool` (class, line 28) `class TestEnvBool`
  - `TestEnvCsv` (class, line 40) `class TestEnvCsv`
  - `test_parse` (method, line 8) `def test_parse(self, monkeypatch)`
  - `test_malformed` (method, line 12) `def test_malformed(self, monkeypatch)`
  - `test_absent` (method, line 16) `def test_absent(self)`
  - `test_parse_and_malformed` (method, line 21) `def test_parse_and_malformed(self, monkeypatch)`
  - `test_tokens` (method, line 29) `def test_tokens(self, monkeypatch)`
  - `test_unknown_uses_default` (method, line 35) `def test_unknown_uses_default(self, monkeypatch)`
  - `test_strip_and_filter` (method, line 41) `def test_strip_and_filter(self, monkeypatch)`
  - `test_default` (method, line 45) `def test_default(self)`

## tests/test_event_bus.py
- Doc: M2a event_bus BDD red.
- Layer: testing
- Language: py
- Symbols:
  - `test_subscribe_publish_order_and_count` (function, line 7) `def test_subscribe_publish_order_and_count()`
  - `test_raising_handler_does_not_block_others` (function, line 19) `def test_raising_handler_does_not_block_others()`
  - `test_payload_isolation_between_handlers` (function, line 34) `def test_payload_isolation_between_handlers()`
  - `test_invalid_inputs_raise` (function, line 45) `def test_invalid_inputs_raise()`
  - `test_singleton_and_unsubscribe` (function, line 57) `def test_singleton_and_unsubscribe()`
  - `bad` (function, line 22) `def bad(p)`
- Depends on: `estorides_core/event_bus.py`

## tests/test_fusion_analytics.py
- Doc: ATDD + BDD tests for estorides_core.fusion_analytics.
- Layer: testing
- Language: py
- Symbols:
  - `store_and_analytics` (function, line 27) `def store_and_analytics(tmp_path)`
  - `_populate_evilcorp` (function, line 42) `def _populate_evilcorp(store)`
  - `_register_source` (function, line 57) `def _register_source(store, name)`
  - `TestEntityTimeline` (class, line 64) `class TestEntityTimeline`
  - `TestEntitySummary` (class, line 87) `class TestEntitySummary`
  - `TestEntityTimelineNonexistent` (class, line 109) `class TestEntityTimelineNonexistent`
  - `TestSourceStats` (class, line 118) `class TestSourceStats`
  - `TestMultiSourceConsensus` (class, line 155) `class TestMultiSourceConsensus`
  - `TestCorroboratedProperties` (class, line 176) `class TestCorroboratedProperties`
  - `TestEntitySearch` (class, line 195) `class TestEntitySearch`
  - `TestTopChanged` (class, line 230) `class TestTopChanged`
  - `TestSourceCorroborationMatrix` (class, line 250) `class TestSourceCorroborationMatrix`
  - `TestWithNoneStore` (class, line 268) `class TestWithNoneStore`
  - `TestBoundaryConditions` (class, line 281) `class TestBoundaryConditions`
  - `test_returns_full_timeline` (method, line 65) `def test_returns_full_timeline(self, store_and_analytics)`
  - `test_nonexistent_eid_returns_none` (method, line 79) `def test_nonexistent_eid_returns_none(self, store_and_analytics)`
  - `test_returns_summary_stats` (method, line 88) `def test_returns_summary_stats(self, store_and_analytics)`
  - `test_nonexistent_eid_returns_none` (method, line 101) `def test_nonexistent_eid_returns_none(self, store_and_analytics)`
  - `test_returns_none` (method, line 110) `def test_returns_none(self, store_and_analytics)`
  - `test_returns_source_metrics` (method, line 119) `def test_returns_source_metrics(self, store_and_analytics)`
  - `test_nonexistent_source_returns_none` (method, line 135) `def test_nonexistent_source_returns_none(self, store_and_analytics)`
  - `test_success_rate_correct` (method, line 139) `def test_success_rate_correct(self, store_and_analytics)`
  - `test_consensus_picks_majority_value` (method, line 156) `def test_consensus_picks_majority_value(self, store_and_analytics)`
  - `test_nonexistent_key_returns_empty` (method, line 165) `def test_nonexistent_key_returns_empty(self, store_and_analytics)`
  - `test_filters_by_min_sources` (method, line 177) `def test_filters_by_min_sources(self, store_and_analytics)`
  - `test_min_sources_one_returns_all` (method, line 185) `def test_min_sources_one_returns_all(self, store_and_analytics)`
  - `test_search_by_term` (method, line 196) `def test_search_by_term(self, store_and_analytics)`
  - `test_search_no_results` (method, line 208) `def test_search_no_results(self, store_and_analytics)`
  - `test_search_filter_by_type` (method, line 213) `def test_search_filter_by_type(self, store_and_analytics)`
  - `test_search_with_confidence_and_source_filters` (method, line 219) `def test_search_with_confidence_and_source_filters(self, store_and_analytics)`
  - `test_returns_recently_active_entities` (method, line 231) `def test_returns_recently_active_entities(self, store_and_analytics)`
  - `test_empty_window_returns_empty` (method, line 241) `def test_empty_window_returns_empty(self, store_and_analytics)`
  - `test_returns_pairs_with_shared_counts` (method, line 251) `def test_returns_pairs_with_shared_counts(self, store_and_analytics)`
  - `test_all_methods_return_empty` (method, line 269) `def test_all_methods_return_empty(self)`
  - `test_min_sources_zero_treated_as_one` (method, line 282) `def test_min_sources_zero_treated_as_one(self, store_and_analytics)`
  - `test_negative_days_treated_as_one` (method, line 288) `def test_negative_days_treated_as_one(self, store_and_analytics)`
- Depends on: `estorides_core/fusion_analytics.py`, `estorides_core/fusion_store.py`

## tests/test_graph_bundle.py
- Doc: graph_bundle: pestana Bundles circular 2D + esferica 3D estilo ReadMenator.
- Layer: testing
- Language: py
- Symbols:
  - `_force` (function, line 26) `def _force()`
  - `test_s1_two_communities_circle_and_sphere` (function, line 62) `def test_s1_two_communities_circle_and_sphere()`
  - `test_s2_empty_and_unassigned` (function, line 83) `def test_s2_empty_and_unassigned()`
  - `test_s3_scaffolding_dropped_and_bad_node_raises` (function, line 104) `def test_s3_scaffolding_dropped_and_bad_node_raises()`
  - `test_s4_hostile_labels_truncated_and_bytes_raise` (function, line 124) `def test_s4_hostile_labels_truncated_and_bytes_raise()`
  - `test_s5_deterministic_and_settings_match_readmenator` (function, line 149) `def test_s5_deterministic_and_settings_match_readmenator()`
  - `test_s7_circle_curves_endpoints_and_beta` (function, line 169) `def test_s7_circle_curves_endpoints_and_beta()`
  - `test_s7_sphere_caps_hubs_and_fibonacci` (function, line 205) `def test_s7_sphere_caps_hubs_and_fibonacci()`
  - `test_s7_helpers_unit` (function, line 228) `def test_s7_helpers_unit()`
  - `test_s8_settings_full_defaults_and_env` (function, line 253) `def test_s8_settings_full_defaults_and_env(monkeypatch)`
  - `test_s8_exact_payload_shape` (function, line 288) `def test_s8_exact_payload_shape()`
  - `test_s8_lenient_fields_and_ignored_nodes` (function, line 308) `def test_s8_lenient_fields_and_ignored_nodes()`
  - `test_s9_typeerror_messages_asserted` (function, line 351) `def test_s9_typeerror_messages_asserted()`
  - `test_s9_bare_entity_defaults_and_idless` (function, line 374) `def test_s9_bare_entity_defaults_and_idless()`
  - `test_s9_family_fallback_color_and_order` (function, line 397) `def test_s9_family_fallback_color_and_order()`
  - `test_s9_unit_edge_norms` (function, line 418) `def test_s9_unit_edge_norms()`
  - `test_s9_fibonacci_exact` (function, line 427) `def test_s9_fibonacci_exact()`
  - `test_s9_sphere_caps_exact_and_defaults` (function, line 443) `def test_s9_sphere_caps_exact_and_defaults()`
  - `test_s9_default_layout_checksum_and_length` (function, line 457) `def test_s9_default_layout_checksum_and_length()`
  - `test_s9_all_env_vars_propagate` (function, line 474) `def test_s9_all_env_vars_propagate(monkeypatch)`
  - `test_s10_full_literal_output` (function, line 507) `def test_s10_full_literal_output()`
  - `test_s10_empty_label_falls_back_to_id_and_findings` (function, line 539) `def test_s10_empty_label_falls_back_to_id_and_findings()`
  - `test_s10_odd_community_ids` (function, line 553) `def test_s10_odd_community_ids()`
  - `test_s10_scaffold_between_entities_dropped` (function, line 580) `def test_s10_scaffold_between_entities_dropped()`
  - `test_s10_community_colors_survive_bad_id` (function, line 599) `def test_s10_community_colors_survive_bad_id()`
  - `test_s10_env_divergence_changes_layout` (function, line 623) `def test_s10_env_divergence_changes_layout(monkeypatch)`
  - `test_s10_bspline_short_branch_exact` (function, line 636) `def test_s10_bspline_short_branch_exact()`
  - `test_s10_beta_clamp_and_skip_order` (function, line 651) `def test_s10_beta_clamp_and_skip_order()`
  - `test_s10_empty_layouts_full_and_absolute_leaves` (function, line 668) `def test_s10_empty_layouts_full_and_absolute_leaves()`
  - `test_s10_three_group_caps_and_tie_and_axis` (function, line 692) `def test_s10_three_group_caps_and_tie_and_axis()`
- Depends on: `estorides_core/__init__.py`, `estorides_core/graph_bundle.py`, `estorides_core/graph_force.py`


Next: [KB_tests_p2.md](KB_tests_p2.md)
