Add tests/test_auth.py
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
"""Unit-тесты IAM-авторизации и кэша токена."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime as dt
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
|
||||
from selectel_ml_tui.auth import (
|
||||
IamAuthError,
|
||||
IamClient,
|
||||
IamToken,
|
||||
IamTokenManager,
|
||||
)
|
||||
from selectel_ml_tui.config import Credentials, Settings
|
||||
|
||||
|
||||
class FakeIamClient:
|
||||
"""Заглушка IamClient для проверки логики кэширования."""
|
||||
|
||||
def __init__(self, token: IamToken) -> None:
|
||||
self._token = token
|
||||
self.calls = 0
|
||||
|
||||
async def get_token(self) -> IamToken:
|
||||
self.calls += 1
|
||||
return self._token
|
||||
|
||||
|
||||
def _far_future() -> dt.datetime:
|
||||
return dt.datetime.now(dt.timezone.utc) + dt.timedelta(hours=23)
|
||||
|
||||
|
||||
def _expired() -> dt.datetime:
|
||||
return dt.datetime.now(dt.timezone.utc) - dt.timedelta(minutes=1)
|
||||
|
||||
|
||||
async def test_iam_get_token_success() -> None:
|
||||
def handler(request: httpx.Request) -> httpx.Response:
|
||||
body = request.content.decode()
|
||||
assert '"name":"svc-user"' in body
|
||||
assert '"password":"secret"' in body
|
||||
assert '"id":"proj-1"' in body
|
||||
return httpx.Response(
|
||||
201,
|
||||
headers={"X-Subject-Token": "iam-token-abc"},
|
||||
json={
|
||||
"token": {
|
||||
"expires_at": "2026-08-20T10:00:00.000000Z",
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
client = IamClient(
|
||||
Settings(),
|
||||
Credentials(
|
||||
service_user="svc-user",
|
||||
password="secret",
|
||||
domain_id="239633",
|
||||
project_id="proj-1",
|
||||
),
|
||||
transport=httpx.MockTransport(handler),
|
||||
)
|
||||
token = await client.get_token()
|
||||
assert token.value == "iam-token-abc"
|
||||
assert token.expires_at == dt.datetime(2026, 8, 20, 10, 0, tzinfo=dt.timezone.utc)
|
||||
|
||||
|
||||
async def test_iam_get_token_missing_credentials() -> None:
|
||||
client = IamClient(Settings(), Credentials())
|
||||
with pytest.raises(IamAuthError, match="логин/пароль"):
|
||||
await client.get_token()
|
||||
|
||||
|
||||
async def test_iam_get_token_missing_subject_token() -> None:
|
||||
def handler(request: httpx.Request) -> httpx.Response:
|
||||
return httpx.Response(
|
||||
201, json={"token": {"expires_at": "2026-08-20T10:00:00Z"}}
|
||||
)
|
||||
|
||||
client = IamClient(
|
||||
Settings(),
|
||||
Credentials(
|
||||
service_user="svc", password="pwd", domain_id="239633", project_id="p"
|
||||
),
|
||||
transport=httpx.MockTransport(handler),
|
||||
)
|
||||
with pytest.raises(IamAuthError, match="X-Subject-Token"):
|
||||
await client.get_token()
|
||||
|
||||
|
||||
async def test_iam_token_manager_caches() -> None:
|
||||
fake = FakeIamClient(IamToken(value="t1", expires_at=_far_future()))
|
||||
manager = IamTokenManager(fake) # type: ignore[arg-type]
|
||||
assert await manager.get_token() == "t1"
|
||||
assert await manager.get_token() == "t1"
|
||||
assert fake.calls == 1
|
||||
assert manager.is_valid()
|
||||
|
||||
|
||||
async def test_iam_token_manager_refreshes_after_expiry() -> None:
|
||||
fake = FakeIamClient(IamToken(value="t-expired", expires_at=_expired()))
|
||||
manager = IamTokenManager(fake) # type: ignore[arg-type]
|
||||
assert await manager.get_token() == "t-expired"
|
||||
assert not manager.is_valid()
|
||||
assert await manager.get_token() == "t-expired"
|
||||
assert fake.calls == 2
|
||||
|
||||
|
||||
async def test_iam_token_manager_invalidate() -> None:
|
||||
fake = FakeIamClient(IamToken(value="t1", expires_at=_far_future()))
|
||||
manager = IamTokenManager(fake) # type: ignore[arg-type]
|
||||
await manager.get_token()
|
||||
assert manager.is_valid()
|
||||
manager.invalidate()
|
||||
assert not manager.is_valid()
|
||||
await manager.get_token()
|
||||
assert fake.calls == 2
|
||||
Reference in New Issue
Block a user