mirror of
https://github.com/mvt-project/mvt.git
synced 2026-10-29 01:06:27 +01:00
Compare commits
9
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
df540d7c41 | ||
|
|
706e81dff2 | ||
|
|
3ba15716a2 | ||
|
|
8068569dce | ||
|
|
6e9408e30c | ||
|
|
db809cbc46 | ||
|
|
f4bfc39c27 | ||
|
|
a645913376 | ||
|
|
4990b22259 |
No files matched your search
@@ -16,7 +16,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ['3.10', '3.11', '3.12', '3.13', '3.14']
|
python-version: ['3.10', '3.11', '3.12', '3.13', '3.14', '3.15']
|
||||||
env:
|
env:
|
||||||
# Takes precedence over .python-version, which otherwise makes `uv run`
|
# Takes precedence over .python-version, which otherwise makes `uv run`
|
||||||
# rebuild the venv with 3.10 and test every matrix entry on 3.10.
|
# rebuild the venv with 3.10 and test every matrix entry on 3.10.
|
||||||
|
|||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
# Create main image
|
# Create main image
|
||||||
FROM ubuntu:24.04 AS main
|
FROM ubuntu:24.04 AS main
|
||||||
COPY --from=ghcr.io/astral-sh/uv:0.11.8 /uv /uvx /usr/local/bin/
|
COPY --from=ghcr.io/astral-sh/uv:0.13.5 /uv /uvx /usr/local/bin/
|
||||||
|
|
||||||
LABEL org.opencontainers.image.url="https://mvt.re"
|
LABEL org.opencontainers.image.url="https://mvt.re"
|
||||||
LABEL org.opencontainers.image.documentation="https://docs.mvt.re"
|
LABEL org.opencontainers.image.documentation="https://docs.mvt.re"
|
||||||
@@ -62,4 +62,4 @@ WORKDIR /home/cases
|
|||||||
RUN echo 'echo "Mobile Verification Toolkit @ Docker\n------------------------------------\n\nYou can find information about how to use this image for Android (https://github.com/mvt-project/mvt/tree/master/docs/android) and iOS (https://github.com/mvt-project/mvt/tree/master/docs/ios) in the official docs of the project.\n"' >> ~/.bashrc \
|
RUN echo 'echo "Mobile Verification Toolkit @ Docker\n------------------------------------\n\nYou can find information about how to use this image for Android (https://github.com/mvt-project/mvt/tree/master/docs/android) and iOS (https://github.com/mvt-project/mvt/tree/master/docs/ios) in the official docs of the project.\n"' >> ~/.bashrc \
|
||||||
&& echo 'echo "Note that to perform the debug via USB you might need to give the Docker image access to the USB using \"docker run -it --privileged -v /dev/bus/usb:/dev/bus/usb mvt\" or, preferably, the \"--device=\" parameter.\n"' >> ~/.bashrc
|
&& echo 'echo "Note that to perform the debug via USB you might need to give the Docker image access to the USB using \"docker run -it --privileged -v /dev/bus/usb:/dev/bus/usb mvt\" or, preferably, the \"--device=\" parameter.\n"' >> ~/.bashrc
|
||||||
|
|
||||||
CMD /bin/bash
|
CMD [ "/bin/bash" ]
|
||||||
+3
-3
@@ -1,6 +1,6 @@
|
|||||||
# Create main image
|
# Create main image
|
||||||
FROM python:3.10.14-alpine3.20 AS main
|
FROM python:3.11.17-alpine3.23 AS main
|
||||||
COPY --from=ghcr.io/astral-sh/uv:0.11.8 /uv /uvx /usr/local/bin/
|
COPY --from=ghcr.io/astral-sh/uv:0.13.5 /uv /uvx /usr/local/bin/
|
||||||
|
|
||||||
LABEL org.opencontainers.image.url="https://mvt.re"
|
LABEL org.opencontainers.image.url="https://mvt.re"
|
||||||
LABEL org.opencontainers.image.documentation="https://docs.mvt.re"
|
LABEL org.opencontainers.image.documentation="https://docs.mvt.re"
|
||||||
@@ -8,7 +8,7 @@ LABEL org.opencontainers.image.source="https://github.com/mvt-project/mvt"
|
|||||||
LABEL org.opencontainers.image.title="Mobile Verification Toolkit (Android)"
|
LABEL org.opencontainers.image.title="Mobile Verification Toolkit (Android)"
|
||||||
LABEL org.opencontainers.image.description="MVT is a forensic tool to look for signs of infection in smartphone devices."
|
LABEL org.opencontainers.image.description="MVT is a forensic tool to look for signs of infection in smartphone devices."
|
||||||
LABEL org.opencontainers.image.licenses="MVT License 1.1"
|
LABEL org.opencontainers.image.licenses="MVT License 1.1"
|
||||||
LABEL org.opencontainers.image.base.name=docker.io/library/python:3.10.14-alpine3.20
|
LABEL org.opencontainers.image.base.name=docker.io/library/python:3.11.17-alpine3.23
|
||||||
|
|
||||||
# Install runtime dependencies
|
# Install runtime dependencies
|
||||||
RUN apk add --no-cache \
|
RUN apk add --no-cache \
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
# Main image
|
# Main image
|
||||||
# ----------
|
# ----------
|
||||||
FROM ubuntu:24.04 AS main
|
FROM ubuntu:24.04 AS main
|
||||||
COPY --from=ghcr.io/astral-sh/uv:0.11.8 /uv /uvx /usr/local/bin/
|
COPY --from=ghcr.io/astral-sh/uv:0.13.5 /uv /uvx /usr/local/bin/
|
||||||
|
|
||||||
LABEL org.opencontainers.image.url="https://mvt.re"
|
LABEL org.opencontainers.image.url="https://mvt.re"
|
||||||
LABEL org.opencontainers.image.documentation="https://docs.mvt.re"
|
LABEL org.opencontainers.image.documentation="https://docs.mvt.re"
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
+20
@@ -18,8 +18,28 @@ markdown_extensions:
|
|||||||
use_pygments: true
|
use_pygments: true
|
||||||
theme:
|
theme:
|
||||||
name: material
|
name: material
|
||||||
|
logo: mvt.png
|
||||||
|
favicon: favicon.png
|
||||||
features:
|
features:
|
||||||
- tabs
|
- tabs
|
||||||
|
palette:
|
||||||
|
- media: "(prefers-color-scheme)"
|
||||||
|
primary: black
|
||||||
|
toggle:
|
||||||
|
icon: material/link
|
||||||
|
name: Switch to light mode
|
||||||
|
- media: "(prefers-color-scheme: light)"
|
||||||
|
scheme: default
|
||||||
|
primary: black
|
||||||
|
toggle:
|
||||||
|
icon: material/toggle-switch
|
||||||
|
name: Switch to dark mode
|
||||||
|
- media: "(prefers-color-scheme: dark)"
|
||||||
|
scheme: slate
|
||||||
|
primary: black
|
||||||
|
toggle:
|
||||||
|
icon: material/toggle-switch-off
|
||||||
|
name: Switch to system preference
|
||||||
plugins:
|
plugins:
|
||||||
- search
|
- search
|
||||||
- mkdocstrings
|
- mkdocstrings
|
||||||
|
|||||||
+12
-4
@@ -160,7 +160,9 @@ def check_adb(ctx):
|
|||||||
"zone is read from persist.sys.timezone in the bugreport itself."
|
"zone is read from persist.sys.timezone in the bugreport itself."
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@click.option("--verbose", "-v", is_flag=True, help=HELP_MSG_VERBOSE_COMMAND)
|
@click.option(
|
||||||
|
"--verbose", "-v", is_flag=True, hidden=True, help=HELP_MSG_VERBOSE_COMMAND
|
||||||
|
)
|
||||||
@click.argument("BUGREPORT_PATH", type=click.Path(exists=True))
|
@click.argument("BUGREPORT_PATH", type=click.Path(exists=True))
|
||||||
@click.pass_context
|
@click.pass_context
|
||||||
def check_bugreport(
|
def check_bugreport(
|
||||||
@@ -235,7 +237,9 @@ def check_bugreport(
|
|||||||
)
|
)
|
||||||
@click.option("--non-interactive", "-n", is_flag=True, help=HELP_MSG_NONINTERACTIVE)
|
@click.option("--non-interactive", "-n", is_flag=True, help=HELP_MSG_NONINTERACTIVE)
|
||||||
@click.option("--backup-password", "-p", help=HELP_MSG_ANDROID_BACKUP_PASSWORD)
|
@click.option("--backup-password", "-p", help=HELP_MSG_ANDROID_BACKUP_PASSWORD)
|
||||||
@click.option("--verbose", "-v", is_flag=True, help=HELP_MSG_VERBOSE_COMMAND)
|
@click.option(
|
||||||
|
"--verbose", "-v", is_flag=True, hidden=True, help=HELP_MSG_VERBOSE_COMMAND
|
||||||
|
)
|
||||||
@click.argument("BACKUP_PATH", type=click.Path(exists=True))
|
@click.argument("BACKUP_PATH", type=click.Path(exists=True))
|
||||||
@click.pass_context
|
@click.pass_context
|
||||||
def check_backup(
|
def check_backup(
|
||||||
@@ -310,7 +314,9 @@ def check_backup(
|
|||||||
)
|
)
|
||||||
@click.option("--non-interactive", "-n", is_flag=True, help=HELP_MSG_NONINTERACTIVE)
|
@click.option("--non-interactive", "-n", is_flag=True, help=HELP_MSG_NONINTERACTIVE)
|
||||||
@click.option("--backup-password", "-p", help=HELP_MSG_ANDROID_BACKUP_PASSWORD)
|
@click.option("--backup-password", "-p", help=HELP_MSG_ANDROID_BACKUP_PASSWORD)
|
||||||
@click.option("--verbose", "-v", is_flag=True, help=HELP_MSG_VERBOSE_COMMAND)
|
@click.option(
|
||||||
|
"--verbose", "-v", is_flag=True, hidden=True, help=HELP_MSG_VERBOSE_COMMAND
|
||||||
|
)
|
||||||
@click.argument("ANDROIDQF_PATH", type=click.Path(exists=True))
|
@click.argument("ANDROIDQF_PATH", type=click.Path(exists=True))
|
||||||
@click.pass_context
|
@click.pass_context
|
||||||
def check_androidqf(
|
def check_androidqf(
|
||||||
@@ -397,7 +403,9 @@ def check_androidqf(
|
|||||||
"time instead of UTC."
|
"time instead of UTC."
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@click.option("--verbose", "-v", is_flag=True, help=HELP_MSG_VERBOSE_COMMAND)
|
@click.option(
|
||||||
|
"--verbose", "-v", is_flag=True, hidden=True, help=HELP_MSG_VERBOSE_COMMAND
|
||||||
|
)
|
||||||
@click.argument("LOGS_PATH", type=click.Path(exists=True))
|
@click.argument("LOGS_PATH", type=click.Path(exists=True))
|
||||||
@click.pass_context
|
@click.pass_context
|
||||||
def check_intrusion_logs(
|
def check_intrusion_logs(
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ from .modules.backup import BACKUP_MODULES
|
|||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
MAX_BACKUP_PASSWORD_ATTEMPTS = 3
|
||||||
|
|
||||||
|
|
||||||
class InvalidAndroidBackup(Exception):
|
class InvalidAndroidBackup(Exception):
|
||||||
pass
|
pass
|
||||||
@@ -72,6 +74,15 @@ class CmdAndroidCheckBackup(Command):
|
|||||||
self.__tar: Optional[tarfile.TarFile] = None
|
self.__tar: Optional[tarfile.TarFile] = None
|
||||||
self.__files: List[str] = []
|
self.__files: List[str] = []
|
||||||
|
|
||||||
|
def _get_backup_password(self) -> str:
|
||||||
|
password = prompt_or_load_android_backup_password(log, self.module_options)
|
||||||
|
if not password:
|
||||||
|
if self.sub_command:
|
||||||
|
raise InvalidAndroidBackup("No backup password provided")
|
||||||
|
log.critical("No backup password provided.")
|
||||||
|
sys.exit(1)
|
||||||
|
return password
|
||||||
|
|
||||||
def from_ab(self, ab_file_bytes: bytes) -> None:
|
def from_ab(self, ab_file_bytes: bytes) -> None:
|
||||||
self.__type = "ab"
|
self.__type = "ab"
|
||||||
header = parse_ab_header(ab_file_bytes)
|
header = parse_ab_header(ab_file_bytes)
|
||||||
@@ -84,28 +95,40 @@ class CmdAndroidCheckBackup(Command):
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
password = None
|
password = None
|
||||||
|
# A password typed at the prompt is asked for again if it is wrong.
|
||||||
|
# One given on the command line or in the environment is not.
|
||||||
|
max_attempts = 1
|
||||||
if header["encryption"] != "none":
|
if header["encryption"] != "none":
|
||||||
password = prompt_or_load_android_backup_password(log, self.module_options)
|
if not self.module_options.get("backup_password", None):
|
||||||
if not password:
|
max_attempts = MAX_BACKUP_PASSWORD_ATTEMPTS
|
||||||
|
password = self._get_backup_password()
|
||||||
|
attempt = 1
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
tardata = parse_backup_file(ab_file_bytes, password=password)
|
||||||
|
break
|
||||||
|
except InvalidBackupPassword:
|
||||||
|
if attempt < max_attempts:
|
||||||
|
attempt += 1
|
||||||
|
log.error(
|
||||||
|
"Invalid backup password, try again (attempt %d of %d)",
|
||||||
|
attempt,
|
||||||
|
max_attempts,
|
||||||
|
)
|
||||||
|
password = self._get_backup_password()
|
||||||
|
continue
|
||||||
if self.sub_command:
|
if self.sub_command:
|
||||||
raise InvalidAndroidBackup("No backup password provided")
|
raise InvalidAndroidBackup("Invalid backup password")
|
||||||
log.critical("No backup password provided.")
|
log.critical("Invalid backup password")
|
||||||
|
sys.exit(1)
|
||||||
|
except AndroidBackupParsingError as exc:
|
||||||
|
if self.sub_command:
|
||||||
|
raise InvalidAndroidBackup(
|
||||||
|
f"Impossible to parse this backup file: {exc}"
|
||||||
|
) from exc
|
||||||
|
log.critical("Impossible to parse this backup file: %s", exc)
|
||||||
|
log.critical("Please use Android Backup Extractor (ABE) instead")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
try:
|
|
||||||
tardata = parse_backup_file(ab_file_bytes, password=password)
|
|
||||||
except InvalidBackupPassword:
|
|
||||||
if self.sub_command:
|
|
||||||
raise InvalidAndroidBackup("Invalid backup password")
|
|
||||||
log.critical("Invalid backup password")
|
|
||||||
sys.exit(1)
|
|
||||||
except AndroidBackupParsingError as exc:
|
|
||||||
if self.sub_command:
|
|
||||||
raise InvalidAndroidBackup(
|
|
||||||
f"Impossible to parse this backup file: {exc}"
|
|
||||||
) from exc
|
|
||||||
log.critical("Impossible to parse this backup file: %s", exc)
|
|
||||||
log.critical("Please use Android Backup Extractor (ABE) instead")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
dbytes = io.BytesIO(tardata)
|
dbytes = io.BytesIO(tardata)
|
||||||
try:
|
try:
|
||||||
|
|||||||
+9
-3
@@ -278,7 +278,9 @@ def extract_key(password, key_file, backup_path):
|
|||||||
help=HELP_MSG_LOAD_MODULE,
|
help=HELP_MSG_LOAD_MODULE,
|
||||||
)
|
)
|
||||||
@click.option("--hashes", "-H", is_flag=True, help=HELP_MSG_HASHES)
|
@click.option("--hashes", "-H", is_flag=True, help=HELP_MSG_HASHES)
|
||||||
@click.option("--verbose", "-v", is_flag=True, help=HELP_MSG_VERBOSE_COMMAND)
|
@click.option(
|
||||||
|
"--verbose", "-v", is_flag=True, hidden=True, help=HELP_MSG_VERBOSE_COMMAND
|
||||||
|
)
|
||||||
@click.argument("BACKUP_PATH", type=click.Path(exists=True))
|
@click.argument("BACKUP_PATH", type=click.Path(exists=True))
|
||||||
@click.pass_context
|
@click.pass_context
|
||||||
def check_backup(
|
def check_backup(
|
||||||
@@ -347,7 +349,9 @@ def check_backup(
|
|||||||
help=HELP_MSG_LOAD_MODULE,
|
help=HELP_MSG_LOAD_MODULE,
|
||||||
)
|
)
|
||||||
@click.option("--hashes", "-H", is_flag=True, help=HELP_MSG_HASHES)
|
@click.option("--hashes", "-H", is_flag=True, help=HELP_MSG_HASHES)
|
||||||
@click.option("--verbose", "-v", is_flag=True, help=HELP_MSG_VERBOSE_COMMAND)
|
@click.option(
|
||||||
|
"--verbose", "-v", is_flag=True, hidden=True, help=HELP_MSG_VERBOSE_COMMAND
|
||||||
|
)
|
||||||
@click.argument("DUMP_PATH", type=click.Path(exists=True))
|
@click.argument("DUMP_PATH", type=click.Path(exists=True))
|
||||||
@click.pass_context
|
@click.pass_context
|
||||||
def check_fs(
|
def check_fs(
|
||||||
@@ -416,7 +420,9 @@ def check_fs(
|
|||||||
help=HELP_MSG_LOAD_MODULE,
|
help=HELP_MSG_LOAD_MODULE,
|
||||||
)
|
)
|
||||||
@click.option("--hashes", "-H", is_flag=True, help=HELP_MSG_HASHES)
|
@click.option("--hashes", "-H", is_flag=True, help=HELP_MSG_HASHES)
|
||||||
@click.option("--verbose", "-v", is_flag=True, help=HELP_MSG_VERBOSE_COMMAND)
|
@click.option(
|
||||||
|
"--verbose", "-v", is_flag=True, hidden=True, help=HELP_MSG_VERBOSE_COMMAND
|
||||||
|
)
|
||||||
@click.argument("SYSDIAGNOSE_PATH", type=click.Path(exists=True))
|
@click.argument("SYSDIAGNOSE_PATH", type=click.Path(exists=True))
|
||||||
@click.pass_context
|
@click.pass_context
|
||||||
def check_sysdiagnose(
|
def check_sysdiagnose(
|
||||||
|
|||||||
@@ -1224,6 +1224,10 @@
|
|||||||
"version": "18.7.10",
|
"version": "18.7.10",
|
||||||
"build": "22H374"
|
"build": "22H374"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"version": "18.7.11",
|
||||||
|
"build": "22H384"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"version": "26",
|
"version": "26",
|
||||||
"build": "23A341"
|
"build": "23A341"
|
||||||
@@ -1283,5 +1287,13 @@
|
|||||||
{
|
{
|
||||||
"version": "27.0.1",
|
"version": "27.0.1",
|
||||||
"build": "24A446"
|
"build": "24A446"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": "27.2",
|
||||||
|
"build": "24B91020"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": "27.2",
|
||||||
|
"build": "24B119"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -59,6 +59,66 @@ class TestCheckAndroidBackupCommand:
|
|||||||
assert result.exit_code == 1
|
assert result.exit_code == 1
|
||||||
assert "Invalid backup password" in caplog.text
|
assert "Invalid backup password" in caplog.text
|
||||||
|
|
||||||
|
def test_check_encrypted_backup_prompt_retry(self, mocker):
|
||||||
|
"""A wrong password at the prompt is asked for again"""
|
||||||
|
prompt_mock = mocker.patch(
|
||||||
|
"mvt.android.modules.backup.helpers.prompt_password",
|
||||||
|
side_effect=["wrong_password", TEST_BACKUP_PASSWORD],
|
||||||
|
)
|
||||||
|
runner = CliRunner()
|
||||||
|
path = os.path.join(get_artifact_folder(), "androidqf_encrypted/backup.ab")
|
||||||
|
result = runner.invoke(check_backup, [path])
|
||||||
|
|
||||||
|
assert prompt_mock.call_count == 2
|
||||||
|
assert result.exit_code == 0
|
||||||
|
|
||||||
|
def test_check_encrypted_backup_prompt_gives_up(self, mocker, caplog):
|
||||||
|
"""The prompt gives up after three wrong passwords"""
|
||||||
|
prompt_mock = mocker.patch(
|
||||||
|
"mvt.android.modules.backup.helpers.prompt_password",
|
||||||
|
return_value="wrong_password",
|
||||||
|
)
|
||||||
|
runner = CliRunner()
|
||||||
|
path = os.path.join(get_artifact_folder(), "androidqf_encrypted/backup.ab")
|
||||||
|
|
||||||
|
with caplog.at_level(logging.CRITICAL):
|
||||||
|
result = runner.invoke(check_backup, [path])
|
||||||
|
|
||||||
|
assert prompt_mock.call_count == 3
|
||||||
|
assert result.exit_code == 1
|
||||||
|
assert "Invalid backup password" in caplog.text
|
||||||
|
|
||||||
|
def test_check_encrypted_backup_prompt_retry_empty(self, mocker, caplog):
|
||||||
|
"""An empty answer on a retry ends the check"""
|
||||||
|
prompt_mock = mocker.patch(
|
||||||
|
"mvt.android.modules.backup.helpers.prompt_password",
|
||||||
|
side_effect=["wrong_password", ""],
|
||||||
|
)
|
||||||
|
runner = CliRunner()
|
||||||
|
path = os.path.join(get_artifact_folder(), "androidqf_encrypted/backup.ab")
|
||||||
|
|
||||||
|
with caplog.at_level(logging.CRITICAL):
|
||||||
|
result = runner.invoke(check_backup, [path])
|
||||||
|
|
||||||
|
assert prompt_mock.call_count == 2
|
||||||
|
assert result.exit_code == 1
|
||||||
|
assert "No backup password provided" in caplog.text
|
||||||
|
|
||||||
|
def test_check_encrypted_backup_cli_invalid_no_prompt(self, mocker):
|
||||||
|
"""A password given on the command line is not asked for again"""
|
||||||
|
prompt_mock = mocker.patch(
|
||||||
|
"mvt.android.modules.backup.helpers.prompt_password",
|
||||||
|
return_value=TEST_BACKUP_PASSWORD,
|
||||||
|
)
|
||||||
|
runner = CliRunner()
|
||||||
|
path = os.path.join(get_artifact_folder(), "androidqf_encrypted/backup.ab")
|
||||||
|
result = runner.invoke(
|
||||||
|
check_backup, ["--backup-password", "invalid_password", path]
|
||||||
|
)
|
||||||
|
|
||||||
|
assert prompt_mock.call_count == 0
|
||||||
|
assert result.exit_code == 1
|
||||||
|
|
||||||
def test_check_encrypted_backup_env(self, mocker):
|
def test_check_encrypted_backup_env(self, mocker):
|
||||||
"""Provide password as environment variable"""
|
"""Provide password as environment variable"""
|
||||||
prompt_mock = mocker.patch(
|
prompt_mock = mocker.patch(
|
||||||
|
|||||||
@@ -100,8 +100,8 @@ class TestVerboseOnTheCheckCommands:
|
|||||||
assert result.exit_code == 0
|
assert result.exit_code == 0
|
||||||
assert _console_level() == logging.DEBUG
|
assert _console_level() == logging.DEBUG
|
||||||
|
|
||||||
def test_the_command_option_says_it_is_kept_for_compatibility(self):
|
def test_the_command_option_is_hidden_from_the_help(self):
|
||||||
result = CliRunner().invoke(ios_cli, [*OFFLINE, "check-backup", "--help"])
|
result = CliRunner().invoke(ios_cli, [*OFFLINE, "check-backup", "--help"])
|
||||||
|
|
||||||
assert result.exit_code == 0
|
assert result.exit_code == 0
|
||||||
assert "kept for compatibility" in result.output
|
assert "--verbose" not in result.output
|
||||||
Reference in new issue
Block a user