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

273 lines
8.6 KiB
Python

# Copyright (C) CVAT.ai Corporation
#
# SPDX-License-Identifier: MIT
import argparse
import logging
import pytest
from cvat_sdk.core.auth import (
CVAT_ACCESS_TOKEN_ENV_VAR,
DEFAULT_SERVER,
AuthStore,
AuthStoreError,
ClientAuthParameters,
ProfileEntry,
configure_client_auth_arguments,
default_auth_factory,
get_auth_factory,
make_client_from_cli,
make_client_from_profile,
)
from cvat_sdk.core.client import AccessTokenCredentials, Config, PasswordCredentials
AUTH_URL = "https://auth.test.invalid"
PROFILE_URL = "https://profile.test.invalid"
EXPLICIT_URL = "https://explicit.test.invalid"
STUB_URL = "https://stub.test.invalid"
class _FakeClient:
def __init__(self, url, **kwargs):
self.url = url
self.logged_in_with = None
self.organization_slug = None
self.config = kwargs.get("config")
self.logger = kwargs.get("logger")
self.check_server_version = kwargs.get("check_server_version")
self.api_client = type(
"ApiClient",
(),
{"configuration": type("Config", (), {"host": STUB_URL})()},
)()
def login(self, credentials):
self.logged_in_with = credentials
def _ns(**kw):
ns = argparse.Namespace(
profile=None,
auth=None,
server_host=None,
server_port=None,
insecure=False,
organization=None,
)
for k, v in kw.items():
setattr(ns, k, v)
return ns
def test_get_auth_factory_parses_user_pass():
cred = get_auth_factory("alice:secret")(AUTH_URL)
assert isinstance(cred, PasswordCredentials)
assert cred.user == "alice"
assert cred.password == "secret"
def test_default_auth_factory_reads_env_token(monkeypatch):
monkeypatch.setenv(CVAT_ACCESS_TOKEN_ENV_VAR, "pat-123")
cred = default_auth_factory()(AUTH_URL)
assert isinstance(cred, AccessTokenCredentials)
assert cred.token == "pat-123"
def test_configure_client_auth_arguments_defines_shared_parser_surface():
parser = argparse.ArgumentParser()
configure_client_auth_arguments(parser)
args = parser.parse_args(
[
"--profile",
"p",
"--insecure",
"--organization",
"org",
]
)
params = ClientAuthParameters.from_namespace(args)
assert params.profile == "p"
assert params.insecure is True
assert params.organization == "org"
def test_configure_client_auth_arguments_parses_auth_and_host():
parser = argparse.ArgumentParser()
configure_client_auth_arguments(parser)
args = parser.parse_args(
[
"--server-host",
EXPLICIT_URL,
"--server-port",
"8443",
"--auth",
"alice:secret",
]
)
params = ClientAuthParameters.from_namespace(args)
cred = params.auth(AUTH_URL)
assert params.server_host == EXPLICIT_URL
assert params.server_port == 8443
assert isinstance(cred, PasswordCredentials)
assert cred.user == "alice"
assert cred.password == "secret"
def test_make_client_from_profile_uses_profile_entry(monkeypatch):
from cvat_sdk.core import auth as auth_mod
logger = logging.getLogger("test-auth")
config = Config(status_check_period=0.1, verify_ssl=False)
entry = ProfileEntry(
server=PROFILE_URL, token="pat-9", created_date="2026-01-01T00:00:00+00:00"
)
monkeypatch.setattr(auth_mod, "Client", _FakeClient)
client = make_client_from_profile(
entry, logger=logger, config=config, check_server_version=True
)
assert client.url == PROFILE_URL
assert client.logged_in_with.token == "pat-9"
assert client.logger is logger
assert client.config is config
assert client.check_server_version is True
def test_make_client_from_cli_unknown_profile_raises(tmp_path):
store = AuthStore(path=tmp_path / "auth.json")
with pytest.raises(AuthStoreError, match="profile"):
make_client_from_cli(_ns(profile="ghost"), store=store)
def test_make_client_from_cli_uses_existing_named_profile(tmp_path, monkeypatch):
from cvat_sdk.core import auth as auth_mod
store = AuthStore(path=tmp_path / "auth.json")
store.put_profile(
"p",
ProfileEntry(server=PROFILE_URL, token="pat", created_date="2026-01-01T00:00:00+00:00"),
)
monkeypatch.setattr(auth_mod, "Client", _FakeClient)
client = make_client_from_cli(_ns(profile="p"), store=store)
assert client.url == PROFILE_URL
assert client.logged_in_with.token == "pat"
def test_make_client_from_cli_profile_conflicts_with_explicit_host_or_auth(tmp_path):
store = AuthStore(path=tmp_path / "auth.json")
with pytest.raises(AuthStoreError, match="mutually exclusive"):
make_client_from_cli(_ns(profile="p", server_host=EXPLICIT_URL), store=store)
with pytest.raises(AuthStoreError, match="mutually exclusive"):
make_client_from_cli(_ns(profile="p", auth=get_auth_factory("u:p")), store=store)
def test_make_client_from_cli_zero_flag_uses_default_profile(tmp_path, monkeypatch):
from cvat_sdk.core import auth as auth_mod
store = AuthStore(path=tmp_path / "auth.json")
store.put_profile(
"p",
ProfileEntry(server=PROFILE_URL, token="dpat", created_date="2026-01-01T00:00:00+00:00"),
set_default=True,
)
monkeypatch.setattr(auth_mod, "Client", _FakeClient)
monkeypatch.delenv(CVAT_ACCESS_TOKEN_ENV_VAR, raising=False)
client = make_client_from_cli(_ns(), store=store)
assert client.url == PROFILE_URL
assert client.logged_in_with.token == "dpat"
def test_make_client_from_cli_applies_client_options_to_named_profile(tmp_path, monkeypatch):
from cvat_sdk.core import auth as auth_mod
logger = logging.getLogger("test-auth")
config = Config(status_check_period=0.1)
store = AuthStore(path=tmp_path / "auth.json")
store.put_profile(
"p",
ProfileEntry(server=PROFILE_URL, token="pat", created_date="2026-01-01T00:00:00+00:00"),
)
monkeypatch.setattr(auth_mod, "Client", _FakeClient)
client = make_client_from_cli(
_ns(profile="p", insecure=True),
logger=logger,
config=config,
check_server_version=True,
store=store,
)
assert client.url == PROFILE_URL
assert client.config.status_check_period == 0.1
assert client.config.verify_ssl is False
assert client.logger is logger
assert client.check_server_version is True
def test_make_client_from_cli_explicit_host_does_not_borrow_profile_token(tmp_path, monkeypatch):
from cvat_sdk.core import auth as auth_mod
store = AuthStore(path=tmp_path / "auth.json")
store.put_profile(
"p",
ProfileEntry(server=PROFILE_URL, token="dpat", created_date="2026-01-01T00:00:00+00:00"),
set_default=True,
)
monkeypatch.setattr(auth_mod, "Client", _FakeClient)
monkeypatch.setenv(CVAT_ACCESS_TOKEN_ENV_VAR, "env-tok")
client = make_client_from_cli(_ns(server_host=EXPLICIT_URL), store=store)
assert client.url == EXPLICIT_URL
assert client.logged_in_with.token == "env-tok"
def test_make_client_from_cli_accepts_typed_parameters_and_config(tmp_path, monkeypatch):
from cvat_sdk.core import auth as auth_mod
config = Config(status_check_period=0.1)
store = AuthStore(path=tmp_path / "auth.json")
monkeypatch.setattr(auth_mod, "Client", _FakeClient)
monkeypatch.setenv(CVAT_ACCESS_TOKEN_ENV_VAR, "env-tok")
client = make_client_from_cli(
ClientAuthParameters(server_host=EXPLICIT_URL, insecure=True),
config=config,
store=store,
)
assert client.url == EXPLICIT_URL
assert client.config.status_check_period == 0.1
assert client.config.verify_ssl is False
assert client.logged_in_with.token == "env-tok"
def test_make_client_from_cli_env_token_zero_flag(tmp_path, monkeypatch):
from cvat_sdk.core import auth as auth_mod
store = AuthStore(path=tmp_path / "auth.json")
monkeypatch.setattr(auth_mod, "Client", _FakeClient)
monkeypatch.setenv(CVAT_ACCESS_TOKEN_ENV_VAR, "env-tok")
client = make_client_from_cli(_ns(), store=store)
assert client.url == DEFAULT_SERVER
assert client.logged_in_with.token == "env-tok"
def test_public_exports():
import cvat_sdk
import cvat_sdk.core
for name in (
"AuthStore",
"ClientAuthParameters",
"ProfileEntry",
"configure_client_auth_arguments",
"get_auth_store_path",
"make_client_from_cli",
"make_client_from_profile",
):
assert hasattr(cvat_sdk, name), f"cvat_sdk.{name} missing"
assert hasattr(cvat_sdk.core, name), f"cvat_sdk.core.{name} missing"