Files
2026-07-13 13:32:23 +08:00

241 lines
8.2 KiB
Python

# Copyright (C) CVAT.ai Corporation
#
# SPDX-License-Identifier: MIT
import os
from datetime import timedelta
from io import BytesIO
from pathlib import Path
from unittest import mock
import packaging.version as pv
import pytest
from cvat_cli._internal.agent import (
_Event,
_NewReconnectionDelay,
_parse_event_stream,
_TaskCacheLimiter,
)
from cvat_sdk import Client
from cvat_sdk.api_client import models
from cvat_sdk.core.proxies.tasks import ResourceType
from sdk.util import https_reverse_proxy
from .util import TestCliBase, generate_images, run_cli
class TestCliMisc(TestCliBase):
def test_can_warn_on_mismatching_server_version(self, monkeypatch, caplog):
def mocked_version(_):
return pv.Version("0")
# We don't actually run a separate process in the tests here, so it works
monkeypatch.setattr(Client, "get_server_version", mocked_version)
self.run_cli("task", "ls")
assert "Server version '0' is not compatible with SDK version" in caplog.text
@pytest.mark.parametrize("verify", [True, False])
def test_can_control_ssl_verification_with_arg(self, verify: bool):
with https_reverse_proxy() as proxy_url:
if verify:
insecure_args = []
else:
insecure_args = ["--insecure"]
run_cli(
self,
f"--auth={self.user}:{self.password}",
f"--server-host={proxy_url}",
*insecure_args,
"task",
"ls",
expected_code=1 if verify else 0,
)
stdout = self.stdout.getvalue()
if not verify:
for line in stdout.splitlines():
int(line)
def test_can_control_organization_context(self):
org = "cli-test-org"
self.client.organizations.create(models.OrganizationWriteRequest(org))
files = generate_images(self.tmp_path, 1)
stdout = self.run_cli(
"task",
"create",
"personal_task",
ResourceType.LOCAL.name,
*map(os.fspath, files),
"--completion_verification_period=0.01",
organization="",
)
personal_task_id = int(stdout.split()[-1])
stdout = self.run_cli(
"task",
"create",
"org_task",
ResourceType.LOCAL.name,
*map(os.fspath, files),
"--completion_verification_period=0.01",
organization=org,
)
org_task_id = int(stdout.split()[-1])
personal_task_ids = list(map(int, self.run_cli("task", "ls", organization="").split()))
assert personal_task_id in personal_task_ids
assert org_task_id not in personal_task_ids
org_task_ids = list(map(int, self.run_cli("task", "ls", organization=org).split()))
assert personal_task_id not in org_task_ids
assert org_task_id in org_task_ids
all_task_ids = list(map(int, self.run_cli("task", "ls").split()))
assert personal_task_id in all_task_ids
assert org_task_id in all_task_ids
def test_can_use_access_token_env_variable(
self, monkeypatch: pytest.MonkeyPatch, access_tokens
):
token = next(t for t in access_tokens)["private_key"]
from cvat_sdk.api_client.rest import RESTClientObject
original_request = RESTClientObject.request
calls = 0
def patched_request(self, *args, **kwargs):
nonlocal calls
calls += 1
assert kwargs["headers"].get("Authorization") == f"Bearer {token}"
return original_request(self, *args, **kwargs)
monkeypatch.setenv("CVAT_ACCESS_TOKEN", token)
monkeypatch.setattr(RESTClientObject, "request", patched_request)
self.run_cli("task", "ls", authenticate=False)
assert calls
def test_can_use_current_user_env_variable(self, monkeypatch: pytest.MonkeyPatch):
# set all user env vars supported by getuser()
for env_var in ("LOGNAME", "USER", "LNAME", "USERNAME"):
monkeypatch.setenv(env_var, self.user)
from getpass import getuser as original_getuser
from cvat_sdk.core.auth import default_auth_factory
with (
mock.patch(
"cvat_sdk.core.auth.default_auth_factory", wraps=default_auth_factory
) as mock_auth_factory,
mock.patch("getpass.getuser", wraps=original_getuser) as mock_getuser,
mock.patch("getpass.getpass", return_value=self.password) as mock_getpass,
):
self.run_cli("task", "ls", authenticate=False)
mock_auth_factory.assert_called_once()
mock_getuser.assert_called()
mock_getpass.assert_called_once()
def test_can_use_pass_env_variable(self, monkeypatch: pytest.MonkeyPatch):
monkeypatch.setenv("PASS", self.password)
from cvat_sdk.core.auth import get_auth_factory
with (
mock.patch(
"cvat_sdk.core.auth.get_auth_factory", wraps=get_auth_factory
) as mock_auth_factory,
mock.patch("getpass.getpass") as mock_getpass,
):
self.run_cli(f"--auth={self.user}", "task", "ls", authenticate=False)
mock_auth_factory.assert_called_once()
mock_getpass.assert_not_called()
@pytest.mark.parametrize(
["lines", "messages"],
[
# empty
([], []),
([""], [_Event("", "")]),
# event only
(["event: test", ""], [_Event("test", "")]),
(["event: foo", "event: bar", ""], [_Event("bar", "")]),
# data only
(["data: test", ""], [_Event("", "test")]),
(["data: foo", "data: bar", ""], [_Event("", "foo\nbar")]),
# event and data
(["event: test", "data: foo", "data: bar", ""], [_Event("test", "foo\nbar")]),
(["data: foo", "event: test", "data: bar", ""], [_Event("test", "foo\nbar")]),
(["data: foo", "data: bar", "event: test", ""], [_Event("test", "foo\nbar")]),
# fields without values
(["event: test", "event", ""], [_Event("", "")]),
(["data: test", "data", ""], [_Event("", "test\n")]),
# incomplete event
(["event: test", "data: foo"], []),
# multiple events
(
["event: test1", "data: foo", "", "event: test2", "data: bar", ""],
[_Event("test1", "foo"), _Event("test2", "bar")],
),
# comments
([":"], []),
([":1", "event: test", ":2", "data: foo", ":3", ""], [_Event("test", "foo")]),
# retry
(["retry: 1234"], [_NewReconnectionDelay(timedelta(milliseconds=1234))]),
(["retry", "retry:", "retry: a"], []),
# no space
(["event:test", "data:foo", ""], [_Event("test", "foo")]),
# two spaces
(["event: test", "data: foo", ""], [_Event(" test", " foo")]),
# carriage return
(["event: test\r", "data: foo\r", "\r"], [_Event("test", "foo")]),
],
)
def test_parse_event_stream(lines, messages):
stream = BytesIO(b"".join(line.encode() + b"\n" for line in lines))
assert list(_parse_event_stream(stream)) == messages
def test_task_cache_limiter_keeps_last_10_tasks(
tmp_path: Path,
fxt_login: tuple[Client, str],
fxt_logger,
):
client = fxt_login[0]
client.logger = fxt_logger[0]
client.config.cache_dir = tmp_path / "cache"
limiter = _TaskCacheLimiter(client)
for task_id in range(1, 13):
limiter._cache_manager.task_dir(task_id).mkdir(parents=True)
with limiter.using_cache_for_task(task_id):
pass
if task_id <= 10:
for cached_task_id in range(1, task_id + 1):
assert limiter._cache_manager.task_dir(cached_task_id).exists()
elif task_id == 11:
assert not limiter._cache_manager.task_dir(1).exists()
for cached_task_id in range(2, 12):
assert limiter._cache_manager.task_dir(cached_task_id).exists()
elif task_id == 12:
assert not limiter._cache_manager.task_dir(1).exists()
assert not limiter._cache_manager.task_dir(2).exists()
for cached_task_id in range(3, 13):
assert limiter._cache_manager.task_dir(cached_task_id).exists()