"""Tests for GuardDuty S3 upload malware scanning.""" from __future__ import annotations import os from unittest.mock import MagicMock, patch import gradio as gr import pytest from botocore.exceptions import ClientError from tools import malware_scan as ms @pytest.fixture(autouse=True) def _reset_scan_cache(): ms.clear_scan_cache() yield ms.clear_scan_cache() def test_malware_scan_enabled_requires_all_flags(monkeypatch): import tools.config as config monkeypatch.setattr(config, "SCAN_UPLOADS_FOR_MALWARE", True) monkeypatch.setattr(config, "RUN_AWS_FUNCTIONS", True) monkeypatch.setattr(config, "MALWARE_SCAN_S3_BUCKET", "scan-bucket") assert ms.malware_scan_enabled() is True monkeypatch.setattr(config, "SCAN_UPLOADS_FOR_MALWARE", False) assert ms.malware_scan_enabled() is False monkeypatch.setattr(config, "SCAN_UPLOADS_FOR_MALWARE", True) monkeypatch.setattr(config, "MALWARE_SCAN_S3_BUCKET", "") assert ms.malware_scan_enabled() is False def test_normalize_gradio_file_paths(): assert ms.normalize_gradio_file_paths(None) == [] assert ms.normalize_gradio_file_paths("/tmp/a.pdf") == [ os.path.abspath("/tmp/a.pdf") ] assert ms.normalize_gradio_file_paths({"name": "/tmp/b.pdf"}) == [ os.path.abspath("/tmp/b.pdf") ] class _FileObj: name = "/tmp/c.pdf" assert ms.normalize_gradio_file_paths(_FileObj()) == [os.path.abspath("/tmp/c.pdf")] def test_scan_local_file_noop_when_disabled(monkeypatch, tmp_path): sample = tmp_path / "doc.pdf" sample.write_bytes(b"%PDF-1.4") monkeypatch.setattr(ms, "malware_scan_enabled", lambda: False) ms.scan_local_file_for_malware(str(sample)) def test_scan_local_file_clean_deletes_staging_object(monkeypatch, tmp_path): sample = tmp_path / "clean.pdf" sample.write_bytes(b"clean") monkeypatch.setattr(ms, "malware_scan_enabled", lambda: True) monkeypatch.setattr(ms, "MALWARE_SCAN_S3_BUCKET", "scan-bucket", raising=False) monkeypatch.setattr(ms, "MALWARE_SCAN_POLL_INTERVAL_SEC", 0.01, raising=False) monkeypatch.setattr(ms, "MALWARE_SCAN_TIMEOUT_SEC", 1.0, raising=False) s3 = MagicMock() s3.get_object_tagging.return_value = { "TagSet": [ {"Key": ms.GUARDDUTY_MALWARE_SCAN_TAG_KEY, "Value": "NO_THREATS_FOUND"} ] } with patch("tools.malware_scan.boto3.client", return_value=s3): ms.scan_local_file_for_malware(str(sample)) s3.upload_file.assert_called_once() s3.delete_object.assert_called_once() assert ms.already_scanned_clean(str(sample)) def test_scan_local_file_threat_deletes_s3(monkeypatch, tmp_path): sample = tmp_path / "bad.pdf" sample.write_bytes(b"eicar") monkeypatch.setattr(ms, "malware_scan_enabled", lambda: True) monkeypatch.setattr(ms, "MALWARE_SCAN_S3_BUCKET", "scan-bucket", raising=False) monkeypatch.setattr(ms, "MALWARE_SCAN_POLL_INTERVAL_SEC", 0.01, raising=False) monkeypatch.setattr(ms, "MALWARE_SCAN_TIMEOUT_SEC", 1.0, raising=False) s3 = MagicMock() s3.get_object_tagging.return_value = { "TagSet": [{"Key": ms.GUARDDUTY_MALWARE_SCAN_TAG_KEY, "Value": "THREATS_FOUND"}] } with patch("tools.malware_scan.boto3.client", return_value=s3): with pytest.raises(ms.MalwareScanRejectedError): ms.scan_local_file_for_malware(str(sample)) s3.delete_object.assert_called_once() assert sample.exists() def test_scan_gradio_file_upload_threat_deletes_local(monkeypatch, tmp_path): sample = tmp_path / "bad.pdf" sample.write_bytes(b"eicar") monkeypatch.setattr(ms, "malware_scan_enabled", lambda: True) monkeypatch.setattr(ms, "MALWARE_SCAN_S3_BUCKET", "scan-bucket", raising=False) monkeypatch.setattr(ms, "MALWARE_SCAN_POLL_INTERVAL_SEC", 0.01, raising=False) monkeypatch.setattr(ms, "MALWARE_SCAN_TIMEOUT_SEC", 1.0, raising=False) s3 = MagicMock() s3.get_object_tagging.return_value = { "TagSet": [{"Key": ms.GUARDDUTY_MALWARE_SCAN_TAG_KEY, "Value": "THREATS_FOUND"}] } with patch("tools.malware_scan.boto3.client", return_value=s3): with pytest.raises(gr.Error): ms.scan_gradio_file_upload(str(sample)) s3.delete_object.assert_called_once() assert not sample.exists() def test_scan_local_file_failed_status_fail_closed(monkeypatch, tmp_path): sample = tmp_path / "failed.pdf" sample.write_bytes(b"x") monkeypatch.setattr(ms, "malware_scan_enabled", lambda: True) monkeypatch.setattr(ms, "MALWARE_SCAN_S3_BUCKET", "scan-bucket", raising=False) monkeypatch.setattr(ms, "MALWARE_SCAN_POLL_INTERVAL_SEC", 0.01, raising=False) monkeypatch.setattr(ms, "MALWARE_SCAN_TIMEOUT_SEC", 1.0, raising=False) s3 = MagicMock() s3.get_object_tagging.return_value = { "TagSet": [{"Key": ms.GUARDDUTY_MALWARE_SCAN_TAG_KEY, "Value": "FAILED"}] } with patch("tools.malware_scan.boto3.client", return_value=s3): with pytest.raises(ms.MalwareScanRejectedError): ms.scan_local_file_for_malware(str(sample)) s3.delete_object.assert_called_once() def test_scan_local_file_timeout_deletes_s3(monkeypatch, tmp_path): sample = tmp_path / "slow.pdf" sample.write_bytes(b"x") monkeypatch.setattr(ms, "malware_scan_enabled", lambda: True) monkeypatch.setattr(ms, "MALWARE_SCAN_S3_BUCKET", "scan-bucket", raising=False) monkeypatch.setattr(ms, "MALWARE_SCAN_POLL_INTERVAL_SEC", 0.01, raising=False) monkeypatch.setattr(ms, "MALWARE_SCAN_TIMEOUT_SEC", 0.02, raising=False) s3 = MagicMock() s3.get_object_tagging.return_value = {"TagSet": []} with patch("tools.malware_scan.boto3.client", return_value=s3): with pytest.raises(ms.MalwareScanRejectedError, match="timed out"): ms.scan_local_file_for_malware(str(sample)) s3.delete_object.assert_called_once() def test_scan_local_file_poll_exception_still_deletes_s3(monkeypatch, tmp_path): sample = tmp_path / "err.pdf" sample.write_bytes(b"x") monkeypatch.setattr(ms, "malware_scan_enabled", lambda: True) monkeypatch.setattr(ms, "MALWARE_SCAN_S3_BUCKET", "scan-bucket", raising=False) monkeypatch.setattr(ms, "MALWARE_SCAN_POLL_INTERVAL_SEC", 0.01, raising=False) monkeypatch.setattr(ms, "MALWARE_SCAN_TIMEOUT_SEC", 1.0, raising=False) s3 = MagicMock() s3.get_object_tagging.side_effect = RuntimeError("aws down") with patch("tools.malware_scan.boto3.client", return_value=s3): with pytest.raises(ms.MalwareScanRejectedError, match="timed out"): ms.scan_local_file_for_malware(str(sample)) s3.delete_object.assert_called_once() def test_multi_file_second_failure_cleans_first_staging(monkeypatch, tmp_path): first = tmp_path / "one.pdf" second = tmp_path / "two.pdf" first.write_bytes(b"1") second.write_bytes(b"2") monkeypatch.setattr(ms, "malware_scan_enabled", lambda: True) monkeypatch.setattr(ms, "MALWARE_SCAN_S3_BUCKET", "scan-bucket", raising=False) monkeypatch.setattr(ms, "MALWARE_SCAN_POLL_INTERVAL_SEC", 0.01, raising=False) monkeypatch.setattr(ms, "MALWARE_SCAN_TIMEOUT_SEC", 1.0, raising=False) s3 = MagicMock() def _tagging(*_args, **_kwargs): key = _kwargs.get("Key", "") if key.endswith("one.pdf") or "one.pdf" in key: return { "TagSet": [ { "Key": ms.GUARDDUTY_MALWARE_SCAN_TAG_KEY, "Value": "NO_THREATS_FOUND", } ] } return { "TagSet": [ {"Key": ms.GUARDDUTY_MALWARE_SCAN_TAG_KEY, "Value": "THREATS_FOUND"} ] } s3.get_object_tagging.side_effect = _tagging with patch("tools.malware_scan.boto3.client", return_value=s3): with pytest.raises(gr.Error): ms.scan_gradio_file_upload([str(first), str(second)]) assert s3.delete_object.call_count == 2 assert not second.exists() assert first.exists() def test_scan_gradio_file_upload_invalidates_stale_cache(monkeypatch, tmp_path): sample = tmp_path / "cached.pdf" sample.write_bytes(b"cached") ms.mark_scanned_clean(str(sample)) monkeypatch.setattr(ms, "malware_scan_enabled", lambda: True) monkeypatch.setattr(ms, "MALWARE_SCAN_S3_BUCKET", "scan-bucket", raising=False) monkeypatch.setattr(ms, "MALWARE_SCAN_POLL_INTERVAL_SEC", 0.01, raising=False) monkeypatch.setattr(ms, "MALWARE_SCAN_TIMEOUT_SEC", 1.0, raising=False) s3 = MagicMock() s3.get_object_tagging.return_value = { "TagSet": [ {"Key": ms.GUARDDUTY_MALWARE_SCAN_TAG_KEY, "Value": "NO_THREATS_FOUND"} ] } with patch("tools.malware_scan.boto3.client", return_value=s3): ms.scan_gradio_file_upload(str(sample)) s3.upload_file.assert_called_once() def test_scan_gradio_file_upload_shows_checking_info(monkeypatch, tmp_path): sample = tmp_path / "scan.pdf" sample.write_bytes(b"scan") monkeypatch.setattr(ms, "malware_scan_enabled", lambda: True) monkeypatch.setattr(ms, "MALWARE_SCAN_S3_BUCKET", "scan-bucket", raising=False) monkeypatch.setattr(ms, "MALWARE_SCAN_SHOW_CHECKING_INFO", True, raising=False) monkeypatch.setattr(ms, "MALWARE_SCAN_POLL_INTERVAL_SEC", 0.01, raising=False) monkeypatch.setattr(ms, "MALWARE_SCAN_TIMEOUT_SEC", 1.0, raising=False) s3 = MagicMock() s3.get_object_tagging.return_value = { "TagSet": [ {"Key": ms.GUARDDUTY_MALWARE_SCAN_TAG_KEY, "Value": "NO_THREATS_FOUND"} ] } with patch("tools.malware_scan.boto3.client", return_value=s3): with patch("tools.malware_scan.gr.Info") as mock_info: with patch("builtins.print") as mock_print: ms.scan_gradio_file_upload(str(sample)) mock_print.assert_any_call( ms.MALWARE_SCAN_CHECKING_INFO_MESSAGE, flush=True ) mock_print.assert_any_call( ms.MALWARE_SCAN_SUCCESS_INFO_MESSAGE, flush=True ) mock_info.assert_any_call(ms.MALWARE_SCAN_CHECKING_INFO_MESSAGE) mock_info.assert_any_call(ms.MALWARE_SCAN_SUCCESS_INFO_MESSAGE) assert mock_info.call_count == 2 def test_scan_gradio_file_upload_shows_success_info(monkeypatch, tmp_path): sample = tmp_path / "scan.pdf" sample.write_bytes(b"scan") monkeypatch.setattr(ms, "malware_scan_enabled", lambda: True) monkeypatch.setattr(ms, "MALWARE_SCAN_S3_BUCKET", "scan-bucket", raising=False) monkeypatch.setattr(ms, "MALWARE_SCAN_SHOW_CHECKING_INFO", True, raising=False) monkeypatch.setattr(ms, "MALWARE_SCAN_POLL_INTERVAL_SEC", 0.01, raising=False) monkeypatch.setattr(ms, "MALWARE_SCAN_TIMEOUT_SEC", 1.0, raising=False) s3 = MagicMock() s3.get_object_tagging.return_value = { "TagSet": [ {"Key": ms.GUARDDUTY_MALWARE_SCAN_TAG_KEY, "Value": "NO_THREATS_FOUND"} ] } with patch("tools.malware_scan.boto3.client", return_value=s3): with patch("tools.malware_scan.gr.Info") as mock_info: ms.scan_gradio_file_upload(str(sample)) assert mock_info.call_args_list == [ ((ms.MALWARE_SCAN_CHECKING_INFO_MESSAGE,),), ((ms.MALWARE_SCAN_SUCCESS_INFO_MESSAGE,),), ] def test_scan_gradio_file_upload_hides_checking_info_when_disabled( monkeypatch, tmp_path ): sample = tmp_path / "scan.pdf" sample.write_bytes(b"scan") monkeypatch.setattr(ms, "malware_scan_enabled", lambda: True) monkeypatch.setattr(ms, "MALWARE_SCAN_S3_BUCKET", "scan-bucket", raising=False) monkeypatch.setattr(ms, "MALWARE_SCAN_SHOW_CHECKING_INFO", False, raising=False) monkeypatch.setattr(ms, "MALWARE_SCAN_POLL_INTERVAL_SEC", 0.01, raising=False) monkeypatch.setattr(ms, "MALWARE_SCAN_TIMEOUT_SEC", 1.0, raising=False) s3 = MagicMock() s3.get_object_tagging.return_value = { "TagSet": [ {"Key": ms.GUARDDUTY_MALWARE_SCAN_TAG_KEY, "Value": "NO_THREATS_FOUND"} ] } with patch("tools.malware_scan.boto3.client", return_value=s3): with patch("tools.malware_scan.gr.Info") as mock_info: ms.scan_gradio_file_upload(str(sample)) mock_info.assert_not_called() def test_scan_gradio_file_upload_s3_upload_failure_shows_error(monkeypatch, tmp_path): sample = tmp_path / "denied.pdf" sample.write_bytes(b"data") monkeypatch.setattr(ms, "malware_scan_enabled", lambda: True) monkeypatch.setattr(ms, "MALWARE_SCAN_S3_BUCKET", "scan-bucket", raising=False) s3 = MagicMock() s3.upload_file.side_effect = ClientError( {"Error": {"Code": "AccessDenied", "Message": "Access Denied"}}, "PutObject", ) with patch("tools.malware_scan.boto3.client", return_value=s3): with pytest.raises(gr.Error, match="configuration or permissions"): ms.scan_gradio_file_upload(str(sample)) assert not sample.exists() def test_require_files_malware_scanned_blocks_unclean(monkeypatch, tmp_path): sample = tmp_path / "unclean.pdf" sample.write_bytes(b"x") monkeypatch.setattr(ms, "malware_scan_enabled", lambda: True) with patch("tools.malware_scan.scan_gradio_file_upload"): with pytest.raises(gr.Error, match="must pass malware scanning"): ms.require_files_malware_scanned(str(sample)) def test_require_files_malware_scanned_scans_unclean_paths(monkeypatch, tmp_path): sample = tmp_path / "unclean.pdf" sample.write_bytes(b"x") monkeypatch.setattr(ms, "malware_scan_enabled", lambda: True) def _scan_and_mark_clean(file_input): for path in ms.normalize_gradio_file_paths(file_input): ms.mark_scanned_clean(path) with patch( "tools.malware_scan.scan_gradio_file_upload", side_effect=_scan_and_mark_clean ): ms.require_files_malware_scanned(str(sample)) def test_scan_local_file_s3_upload_failure_raises_service_error(monkeypatch, tmp_path): sample = tmp_path / "denied.pdf" sample.write_bytes(b"data") monkeypatch.setattr(ms, "malware_scan_enabled", lambda: True) monkeypatch.setattr(ms, "MALWARE_SCAN_S3_BUCKET", "scan-bucket", raising=False) s3 = MagicMock() s3.upload_file.side_effect = ClientError( {"Error": {"Code": "AccessDenied", "Message": "Access Denied"}}, "PutObject", ) with patch("tools.malware_scan.boto3.client", return_value=s3): with pytest.raises( ms.MalwareScanServiceError, match="configuration or permissions" ): ms.scan_local_file_for_malware(str(sample)) s3.delete_object.assert_not_called() assert sample.exists() def test_read_scan_status_access_denied_raises_service_error(): s3 = MagicMock() s3.get_object_tagging.side_effect = ClientError( {"Error": {"Code": "AccessDenied", "Message": "Access Denied"}}, "GetObjectTagging", ) with pytest.raises( ms.MalwareScanServiceError, match="configuration or permissions" ): ms._read_scan_status(s3, "scan-bucket", "key.pdf") def test_make_malware_scan_disable_outputs_noop_when_disabled(monkeypatch): monkeypatch.setattr(ms, "malware_scan_enabled", lambda: False) disable = ms.make_malware_scan_disable_outputs(1) assert disable() == gr.update() disable_two = ms.make_malware_scan_disable_outputs(2) assert disable_two() == (gr.update(), gr.update()) def test_make_malware_scan_disable_outputs_disables_buttons(monkeypatch): monkeypatch.setattr(ms, "malware_scan_enabled", lambda: True) disable = ms.make_malware_scan_disable_outputs(1) assert disable() == gr.update(interactive=False) disable_two = ms.make_malware_scan_disable_outputs(2) assert disable_two() == ( gr.update(interactive=False), gr.update(interactive=False), ) def test_make_malware_scan_enable_outputs_single_button(): enable = ms.make_malware_scan_enable_outputs(1) assert enable() == gr.update(interactive=True) def test_make_malware_scan_upload_failure_outputs_clears_file_and_enables(): handler = ms.make_malware_scan_upload_failure_outputs(1) assert handler() == (gr.update(value=None), gr.update(interactive=True)) def test_trusted_bundled_example_skips_malware_scan(monkeypatch, tmp_path): example_dir = tmp_path / "example_data" example_dir.mkdir() sample = example_dir / "demo.pdf" sample.write_bytes(b"%PDF-1.4") monkeypatch.setattr( "tools.example_data_paths.resolve_example_data_dirs", lambda: [example_dir.resolve()], ) monkeypatch.setattr(ms, "malware_scan_enabled", lambda: True) from tools.example_data_paths import ( is_bundled_example_file, is_trusted_bundled_example_path, ) assert is_trusted_bundled_example_path(str(sample)) is True assert is_bundled_example_file(str(sample)) is True assert ms.path_is_malware_clean(str(sample)) is True with patch("tools.malware_scan.boto3.client") as mock_client: ms.scan_gradio_file_upload(str(sample)) mock_client.assert_not_called() def test_gradio_temp_copy_of_bundled_example_skips_malware_scan(monkeypatch, tmp_path): example_dir = tmp_path / "example_data" example_dir.mkdir() bundled = example_dir / "demo.pdf" bundled.write_bytes(b"%PDF-1.4 example") gradio_copy = tmp_path / "gradio_tmp" / "abc123_demo.pdf" gradio_copy.parent.mkdir() gradio_copy.write_bytes(b"%PDF-1.4 example") monkeypatch.setattr( "tools.example_data_paths.resolve_example_data_dirs", lambda: [example_dir.resolve()], ) monkeypatch.setattr(ms, "malware_scan_enabled", lambda: True) from tools.example_data_paths import is_bundled_example_file assert is_bundled_example_file(str(gradio_copy)) is True assert ms.path_is_malware_clean(str(gradio_copy)) is True with patch("tools.malware_scan.boto3.client") as mock_client: ms.scan_gradio_file_upload(str(gradio_copy)) mock_client.assert_not_called() def test_require_files_malware_scanned_allows_bundled_example(monkeypatch, tmp_path): example_dir = tmp_path / "example_data" example_dir.mkdir() sample = example_dir / "demo.pdf" sample.write_bytes(b"x") monkeypatch.setattr( "tools.example_data_paths.resolve_example_data_dirs", lambda: [example_dir.resolve()], ) monkeypatch.setattr(ms, "malware_scan_enabled", lambda: True) ms.require_files_malware_scanned(str(sample)) def test_ensure_upload_scanned_for_malware_skips_when_cached(monkeypatch, tmp_path): sample = tmp_path / "cached.pdf" sample.write_bytes(b"cached") ms.mark_scanned_clean(str(sample)) monkeypatch.setattr(ms, "malware_scan_enabled", lambda: True) with patch("tools.malware_scan.scan_local_file_for_malware") as mock_scan: ms.ensure_upload_scanned_for_malware(str(sample)) mock_scan.assert_not_called() def test_normalize_gradio_file_paths_listfiles(tmp_path): from gradio.data_classes import FileData, ListFiles sample = tmp_path / "doc.pdf" sample.write_bytes(b"%PDF-1.4") lf = ListFiles(root=[FileData(path=str(sample), orig_name="doc.pdf")]) assert ms.normalize_gradio_file_paths(lf) == [os.path.abspath(str(sample))] def test_clear_scan_cache_for_path_drops_all_mtimes(tmp_path): sample = tmp_path / "doc.pdf" sample.write_bytes(b"v1") ms.mark_scanned_clean(str(sample)) assert ms.already_scanned_clean(str(sample)) ms.clear_scan_cache_for_path(str(sample)) assert not ms.already_scanned_clean(str(sample)) def test_handle_gradio_file_deleted_clears_cache(monkeypatch, tmp_path): sample = tmp_path / "removed.pdf" sample.write_bytes(b"removed") ms.mark_scanned_clean(str(sample)) monkeypatch.setattr(ms, "malware_scan_enabled", lambda: True) class _Deleted: file = type("F", (), {"path": str(sample), "name": None})() ms.handle_gradio_file_deleted(_Deleted()) assert not ms.already_scanned_clean(str(sample)) def test_make_malware_scan_upload_start_scans_then_disables(monkeypatch, tmp_path): sample = tmp_path / "upload.pdf" sample.write_bytes(b"upload") monkeypatch.setattr(ms, "malware_scan_enabled", lambda: True) with patch("tools.malware_scan.scan_gradio_file_upload") as mock_scan: start = ms.make_malware_scan_upload_start(1) result = start(str(sample)) mock_scan.assert_called_once_with(str(sample)) assert result == gr.update(interactive=False) def test_scan_gradio_file_upload_skips_bundled_example(monkeypatch, tmp_path, caplog): import logging example_dir = tmp_path / "example_data" example_dir.mkdir() sample = example_dir / "demo.pdf" sample.write_bytes(b"%PDF-1.4 example") monkeypatch.setattr( "tools.example_data_paths.resolve_example_data_dirs", lambda: [example_dir.resolve()], ) monkeypatch.setattr(ms, "malware_scan_enabled", lambda: True) with caplog.at_level(logging.INFO): with patch("tools.malware_scan.boto3.client") as mock_client: ms.scan_gradio_file_upload(str(sample)) mock_client.assert_not_called() assert "Skipping malware scan for 1 already-clean path(s)" in caplog.text def test_make_malware_scan_upload_failure_outputs_zero_buttons_clears_file(): handler = ms.make_malware_scan_upload_failure_outputs(0) assert handler() == gr.update(value=None) def test_bind_malware_scan_upload_wires_chain(): file_input = MagicMock() button = MagicMock() upload_event = MagicMock() success_event = MagicMock() file_input.upload.return_value = upload_event upload_event.success.return_value = success_event ms.bind_malware_scan_upload(file_input, button) file_input.upload.assert_called_once() upload_event.success.assert_called_once() success_event.failure.assert_called_once() failure_kwargs = success_event.failure.call_args.kwargs assert failure_kwargs["outputs"] == [file_input, button] def test_bind_malware_scan_upload_requires_button(): with pytest.raises(ValueError, match="at least one button"): ms.bind_malware_scan_upload(MagicMock(), []) def test_mark_app_generated_files_malware_clean(monkeypatch, tmp_path): sample = tmp_path / "ocr_output.csv" sample.write_text("page,text\n1,hello\n") monkeypatch.setattr(ms, "malware_scan_enabled", lambda: True) assert not ms.already_scanned_clean(str(sample)) ms.mark_app_generated_files_malware_clean([str(sample)]) assert ms.already_scanned_clean(str(sample)) def test_mark_app_generated_files_malware_clean_noop_when_disabled( monkeypatch, tmp_path ): sample = tmp_path / "ocr_output.csv" sample.write_text("page,text\n") monkeypatch.setattr(ms, "malware_scan_enabled", lambda: False) ms.mark_app_generated_files_malware_clean([str(sample)]) assert not ms.already_scanned_clean(str(sample)) def test_custom_regex_load_requires_malware_scan(tmp_path): sample = tmp_path / "allow.csv" sample.write_text("term_one\n") class _File: name = str(sample) from tools.helper_functions import custom_regex_load with patch("tools.malware_scan.require_files_malware_scanned") as mock_req: text, values = custom_regex_load([_File()], "allow_list") mock_req.assert_called_once() assert "allow list file loaded" in text assert values == ["term_one"] def test_custom_regex_load_skips_require_when_empty(): from tools.helper_functions import custom_regex_load with patch("tools.malware_scan.require_files_malware_scanned") as mock_req: text, values = custom_regex_load([], "allow_list") mock_req.assert_not_called() assert text == "No file provided." assert values == [] def test_run_duplicate_analysis_requires_malware_scan(tmp_path): import pandas as pd sample = tmp_path / "ocr.csv" sample.write_text("page,text\n1,hello\n") try: from tools.find_duplicate_pages import run_duplicate_analysis except ModuleNotFoundError as exc: pytest.skip(f"NLP optional deps missing: {exc}") with patch( "tools.malware_scan.require_files_malware_scanned", side_effect=gr.Error("blocked"), ): with pytest.raises(gr.Error, match="blocked"): run_duplicate_analysis( [str(sample)], 0.95, 10, 1, True, pd.DataFrame(), [], ) def test_merge_csv_files_requires_malware_scan(tmp_path): sample = tmp_path / "review.csv" sample.write_text("page,label,color,xmin,ymin,xmax,ymax\n1,a,#000,0,0,1,1\n") from tools.helper_functions import merge_csv_files with patch( "tools.malware_scan.require_files_malware_scanned", side_effect=gr.Error("blocked"), ): with pytest.raises(gr.Error, match="blocked"): merge_csv_files([str(sample)], output_folder=str(tmp_path) + os.sep) def test_combine_review_pdf_files_requires_malware_scan(tmp_path): sample = tmp_path / "doc_redactions_for_review.pdf" sample.write_bytes(b"%PDF-1.4") from tools.file_conversion import combine_review_pdf_files with patch( "tools.malware_scan.require_files_malware_scanned", side_effect=gr.Error("blocked"), ): with pytest.raises(gr.Error, match="blocked"): combine_review_pdf_files( [str(sample)], output_folder=str(tmp_path) + os.sep ) def test_anonymise_files_requires_malware_scan(tmp_path): sample = tmp_path / "data.csv" sample.write_text("col\nvalue\n") try: from tools.data_anonymise import anonymise_files_with_open_text except ModuleNotFoundError as exc: pytest.skip(f"NLP optional deps missing: {exc}") with patch( "tools.malware_scan.require_files_malware_scanned", side_effect=gr.Error("blocked"), ): with pytest.raises(gr.Error, match="blocked"): anonymise_files_with_open_text( [str(sample)], "", "replace with 'REDACTED'", ["col"], ["PERSON"], ) def test_run_tabular_duplicate_detection_requires_malware_scan(tmp_path): sample = tmp_path / "data.csv" sample.write_text("col\nvalue\n") try: from tools.find_duplicate_tabular import run_tabular_duplicate_detection except ModuleNotFoundError as exc: pytest.skip(f"NLP optional deps missing: {exc}") with patch( "tools.malware_scan.require_files_malware_scanned", side_effect=gr.Error("blocked"), ): with pytest.raises(gr.Error, match="blocked"): run_tabular_duplicate_detection( [str(sample)], 0.9, 1, ["col"], output_folder=str(tmp_path) + os.sep ) def test_summarise_document_wrapper_requires_malware_scan(tmp_path): import pandas as pd sample = tmp_path / "ocr.csv" sample.write_text("page,line,text\n1,1,hello\n") from tools.summaries import summarise_document_wrapper with patch( "tools.malware_scan.require_files_malware_scanned", side_effect=gr.Error("blocked"), ): with pytest.raises(gr.Error, match="blocked"): summarise_document_wrapper( pd.DataFrame(), str(tmp_path), "local", "", 0.6, "doc", "", "", "", "", "", "bullets", "", 10, [str(sample)], )