diff --git a/tests/common/test_cmd_check_iocs.py b/tests/common/test_cmd_check_iocs.py new file mode 100644 index 0000000..1bdd232 --- /dev/null +++ b/tests/common/test_cmd_check_iocs.py @@ -0,0 +1,139 @@ +# Mobile Verification Toolkit (MVT) +# Copyright (c) 2021-2026 The MVT Authors. +# Use of this software is governed by the MVT License 1.1 that can be found at +# https://license.mvt.re/1.1/ + +import json +import logging + +import pytest +from click.testing import CliRunner + +from mvt.android.cli import cli as android_cli +from mvt.android.command_modules import ANDROID_CHECK_IOCS_MODULES +from mvt.common.cmd_check_iocs import CmdCheckIOCS +from mvt.common.module import MVTModule +from mvt.ios.cli import cli as ios_cli +from mvt.ios.command_modules import IOS_CHECK_IOCS_MODULES + +# Keep the banner of the group callback from checking for updates online. +OFFLINE = ["--disable-update-check", "--disable-indicator-update-check"] + + +class CustomResultsModule(MVTModule): + """A custom module which declares the check-iocs pair of both platforms.""" + + slug = "custom_results" + supported_commands = ( + ("ios", "check-backup"), + ("ios", "check-iocs"), + ("android", "check-iocs"), + ) + + checked: list = [] + + def run(self) -> None: + pass + + def check_indicators(self) -> None: + self.checked.append(list(self.results)) + + +class BackupOnlyModule(MVTModule): + """A custom module which does not declare check-iocs.""" + + slug = "backup_only" + supported_commands = (("ios", "check-backup"),) + + def check_indicators(self) -> None: + raise AssertionError("must not be re-checked") + + +@pytest.mark.parametrize( + "platform, builtin_modules", + [("ios", IOS_CHECK_IOCS_MODULES), ("android", ANDROID_CHECK_IOCS_MODULES)], +) +def test_check_iocs_rechecks_the_stored_results_of_custom_modules( + platform, builtin_modules, tmp_path, caplog +): + # check-iocs matches every .json in the results folder to the module + # with that slug, custom modules included, and runs its check_indicators() + # again over the stored results. + results = [{"domain": "example.org"}] + (tmp_path / "custom_results.json").write_text(json.dumps(results)) + (tmp_path / "backup_only.json").write_text(json.dumps(results)) + CustomResultsModule.checked.clear() + + cmd = CmdCheckIOCS( + target_path=str(tmp_path), + custom_modules=[CustomResultsModule, BackupOnlyModule], + platform=platform, + ) + cmd.modules = builtin_modules + + with caplog.at_level(logging.INFO): + cmd.run() + + assert CustomResultsModule.checked == [results] + assert ( + 'Loading results from "custom_results.json" with module CustomResultsModule' + in caplog.text + ) + # A module declaring only check-backup is not part of check-iocs. + assert "backup_only.json" not in caplog.text + + +def test_check_iocs_lists_custom_modules_declaring_the_command(caplog): + cmd = CmdCheckIOCS( + custom_modules=[CustomResultsModule, BackupOnlyModule], + platform="ios", + ) + cmd.modules = IOS_CHECK_IOCS_MODULES + + with caplog.at_level(logging.INFO): + cmd.list_modules() + + assert "CustomResultsModule" in caplog.text + assert "BackupOnlyModule" not in caplog.text + + +LOADED_MODULE = ''' +from mvt.common.module import MVTModule + + +class LoadedResultsModule(MVTModule): + """A module loaded from a file with --load-module.""" + + slug = "loaded_results" + supported_commands = (("ios", "check-iocs"), ("android", "check-iocs")) + + def run(self) -> None: + pass + + def check_indicators(self) -> None: + self.log.warning("loaded module checked %d results", len(self.results)) +''' + + +@pytest.mark.parametrize("cli", [ios_cli, android_cli], ids=["mvt-ios", "mvt-android"]) +def test_check_iocs_loads_custom_modules_from_a_file_on_each_cli(cli, tmp_path, caplog): + module_path = tmp_path / "loaded_module.py" + module_path.write_text(LOADED_MODULE) + results_folder = tmp_path / "results" + results_folder.mkdir() + (results_folder / "loaded_results.json").write_text(json.dumps([{"a": 1}])) + + with caplog.at_level(logging.INFO): + result = CliRunner().invoke( + cli, + [ + *OFFLINE, + "check-iocs", + "--load-module", + str(module_path), + str(results_folder), + ], + ) + + assert result.exit_code == 0, result.output + assert "loaded module checked 1 results" in caplog.text