Files
ss-tools/backend/tests/schemas/test_profile.py
busya ce0369ae5c test(backend): add 55+ test files to push coverage to 98%
Subagents delivered tests across all uncovered backend modules:

Schemas (100%): agent, auth, health, profile, settings, validation
Services (98-100%): auth, profile, health, llm, mapping, resource,
  security, git, superset_lookup, sql_table_extractor, rbac
API routes (new): auth, admin, health, environments, plugins,
  dashboards (helpers, projection, actions, listing),
  git (config, deps, env, helpers)
Clean Release (100%): DTO, facade, policy_engine, stages,
  repos, preparation, source_isolation, compliance
Git services: base, remote_providers
Agent module: app, run, middleware, langgraph_setup
Core: trace, cleanup, ws_log_handler, timezone, auth (config/oauth/security), matching
Reports: normalizer, report_service, type_profiles
Notifications: service, providers

Also:
- .gitignore: add .coverage, *.cover, coverage-* dirs
- src/schemas/auth.py: fix AD group DN regex (comma in CN=...)
- Remove co-located src/services/__tests__/ (caused pytest module collision)
2026-06-15 13:55:57 +03:00

343 lines
14 KiB
Python

# #region Test.Schemas.Profile [C:2] [TYPE Module] [SEMANTICS test,schema,profile]
# @BRIEF Tests for schemas/profile.py — ProfilePermissionState, ProfileSecuritySummary,
# ProfilePreference, ProfilePreferenceUpdateRequest, ProfilePreferenceResponse,
# SupersetAccountLookupRequest, SupersetAccountCandidate, SupersetAccountLookupResponse.
# @RELATION BINDS_TO -> [ProfileSchemas]
# @TEST_EDGE: missing_field -> optional fields default correctly
# @TEST_EDGE: invalid_type -> field constraints (ge/le) reject bad values
# @TEST_EDGE: serialization -> round-trip via model_dump/model_validate
from datetime import datetime, timezone
import pytest
from pydantic import ValidationError
from src.schemas.profile import (
ProfilePermissionState,
ProfilePreference,
ProfilePreferenceResponse,
ProfilePreferenceUpdateRequest,
ProfileSecuritySummary,
SupersetAccountCandidate,
SupersetAccountLookupRequest,
SupersetAccountLookupResponse,
)
class TestProfilePermissionState:
"""ProfilePermissionState — key and allowed flag."""
def test_minimal(self):
p = ProfilePermissionState(key="dashboard", allowed=True)
assert p.key == "dashboard"
assert p.allowed is True
def test_not_allowed(self):
p = ProfilePermissionState(key="dataset", allowed=False)
assert p.allowed is False
def test_serialize_roundtrip(self):
p = ProfilePermissionState(key="dashboard:write", allowed=False)
data = p.model_dump()
restored = ProfilePermissionState.model_validate(data)
assert restored.key == "dashboard:write"
assert restored.allowed is False
class TestProfileSecuritySummary:
"""ProfileSecuritySummary — security snapshot defaults."""
def test_defaults(self):
s = ProfileSecuritySummary()
assert s.read_only is True
assert s.auth_source is None
assert s.current_role is None
assert s.role_source is None
assert s.roles == []
assert s.permissions == []
def test_with_values(self):
perms = [ProfilePermissionState(key="dashboard", allowed=True)]
s = ProfileSecuritySummary(
read_only=False, auth_source="ADFS", current_role="Admin",
role_source="ADFS", roles=["Admin"], permissions=perms,
)
assert s.read_only is False
assert s.current_role == "Admin"
assert len(s.permissions) == 1
def test_serialize_roundtrip(self):
s = ProfileSecuritySummary(auth_source="LOCAL", roles=["Viewer"])
data = s.model_dump()
restored = ProfileSecuritySummary.model_validate(data)
assert restored.auth_source == "LOCAL"
assert "Viewer" in restored.roles
class TestProfilePreference:
"""ProfilePreference — full preference model with defaults."""
def test_minimal(self):
ts = datetime.now(timezone.utc)
p = ProfilePreference(user_id="u-1", created_at=ts, updated_at=ts)
assert p.user_id == "u-1"
assert p.superset_username is None
assert p.superset_username_normalized is None
assert p.show_only_my_dashboards is False
assert p.show_only_slug_dashboards is True
assert p.start_page == "dashboards"
assert p.auto_open_task_drawer is True
assert p.dashboards_table_density == "comfortable"
assert p.notify_on_fail is True
assert p.has_git_personal_access_token is False
def test_with_all_fields(self):
ts = datetime.now(timezone.utc)
p = ProfilePreference(
user_id="u-1",
superset_username="jdoe",
superset_username_normalized="jdoe",
show_only_my_dashboards=True,
show_only_slug_dashboards=False,
git_username="jdoe",
git_email="j@d.com",
has_git_personal_access_token=True,
git_personal_access_token_masked="jd***oe",
start_page="datasets",
auto_open_task_drawer=False,
dashboards_table_density="compact",
telegram_id="@jdoe",
email_address="j@d.com",
notify_on_fail=False,
created_at=ts,
updated_at=ts,
)
assert p.show_only_my_dashboards is True
assert p.git_username == "jdoe"
assert p.start_page == "datasets"
assert p.dashboards_table_density == "compact"
assert p.notify_on_fail is False
def test_serialize_roundtrip(self):
ts = datetime.now(timezone.utc)
p = ProfilePreference(user_id="u-1", created_at=ts, updated_at=ts)
data = p.model_dump()
restored = ProfilePreference.model_validate(data)
assert restored.user_id == "u-1"
assert restored.start_page == "dashboards"
def test_from_attributes(self):
class FakeORM:
user_id = "u-1"
superset_username = "admin"
superset_username_normalized = "admin"
show_only_my_dashboards = False
show_only_slug_dashboards = True
git_username = None
git_email = None
has_git_personal_access_token = False
git_personal_access_token_masked = None
start_page = "dashboards"
auto_open_task_drawer = True
dashboards_table_density = "comfortable"
telegram_id = None
email_address = None
notify_on_fail = True
created_at = datetime.now(timezone.utc)
updated_at = datetime.now(timezone.utc)
p = ProfilePreference.model_validate(FakeORM())
assert p.user_id == "u-1"
class TestProfilePreferenceUpdateRequest:
"""ProfilePreferenceUpdateRequest — all fields optional."""
def test_defaults(self):
req = ProfilePreferenceUpdateRequest()
assert req.superset_username is None
assert req.show_only_my_dashboards is None
assert req.show_only_slug_dashboards is None
assert req.git_username is None
assert req.git_email is None
assert req.git_personal_access_token is None
assert req.start_page is None
assert req.auto_open_task_drawer is None
assert req.dashboards_table_density is None
assert req.telegram_id is None
assert req.email_address is None
assert req.notify_on_fail is None
def test_partial_update(self):
req = ProfilePreferenceUpdateRequest(superset_username="jdoe", show_only_my_dashboards=True)
assert req.superset_username == "jdoe"
assert req.show_only_my_dashboards is True
def test_start_page_literals(self):
for page in ["dashboards", "datasets", "reports", "reports-logs"]:
req = ProfilePreferenceUpdateRequest(start_page=page)
assert req.start_page == page
def test_invalid_start_page_raises(self):
with pytest.raises(ValidationError):
ProfilePreferenceUpdateRequest(start_page="invalid")
def test_density_literals(self):
for density in ["compact", "comfortable", "free"]:
req = ProfilePreferenceUpdateRequest(dashboards_table_density=density)
assert req.dashboards_table_density == density
def test_invalid_density_raises(self):
with pytest.raises(ValidationError):
ProfilePreferenceUpdateRequest(dashboards_table_density="wide")
def test_serialize_roundtrip(self):
req = ProfilePreferenceUpdateRequest(superset_username="admin", email_address="a@b.com")
data = req.model_dump()
restored = ProfilePreferenceUpdateRequest.model_validate(data)
assert restored.superset_username == "admin"
assert restored.email_address == "a@b.com"
class TestProfilePreferenceResponse:
"""ProfilePreferenceResponse — response envelope with preference + security."""
def test_defaults(self):
ts = datetime.now(timezone.utc)
pref = ProfilePreference(user_id="u-1", created_at=ts, updated_at=ts)
resp = ProfilePreferenceResponse(preference=pref)
assert resp.status == "success"
assert resp.message is None
assert resp.validation_errors == []
assert resp.security.read_only is True
def test_with_errors(self):
ts = datetime.now(timezone.utc)
pref = ProfilePreference(user_id="u-1", created_at=ts, updated_at=ts)
resp = ProfilePreferenceResponse(
preference=pref, status="error", message="Failed",
validation_errors=["username required"],
)
assert resp.status == "error"
assert resp.validation_errors == ["username required"]
def test_serialize_roundtrip(self):
ts = datetime.now(timezone.utc)
pref = ProfilePreference(user_id="u-1", created_at=ts, updated_at=ts)
resp = ProfilePreferenceResponse(preference=pref)
data = resp.model_dump()
restored = ProfilePreferenceResponse.model_validate(data)
assert restored.status == "success"
assert restored.preference.user_id == "u-1"
class TestSupersetAccountLookupRequest:
"""SupersetAccountLookupRequest — pagination with constraints."""
def test_defaults(self):
req = SupersetAccountLookupRequest(environment_id="env-1")
assert req.environment_id == "env-1"
assert req.search is None
assert req.page_index == 0
assert req.page_size == 20
assert req.sort_column == "username"
assert req.sort_order == "desc"
def test_page_size_constraints(self):
req = SupersetAccountLookupRequest(environment_id="env-1", page_size=100)
assert req.page_size == 100
with pytest.raises(ValidationError):
SupersetAccountLookupRequest(environment_id="env-1", page_size=0)
with pytest.raises(ValidationError):
SupersetAccountLookupRequest(environment_id="env-1", page_size=101)
def test_page_index_ge_zero(self):
with pytest.raises(ValidationError):
SupersetAccountLookupRequest(environment_id="env-1", page_index=-1)
def test_custom_values(self):
req = SupersetAccountLookupRequest(
environment_id="env-1", search="admin", page_index=1, page_size=50,
sort_column="email", sort_order="asc",
)
assert req.search == "admin"
assert req.sort_column == "email"
assert req.sort_order == "asc"
def test_serialize_roundtrip(self):
req = SupersetAccountLookupRequest(environment_id="env-1", search="test")
data = req.model_dump()
restored = SupersetAccountLookupRequest.model_validate(data)
assert restored.search == "test"
assert restored.environment_id == "env-1"
class TestSupersetAccountCandidate:
"""SupersetAccountCandidate — projected Superset user."""
def test_minimal(self):
c = SupersetAccountCandidate(environment_id="env-1", username="admin")
assert c.environment_id == "env-1"
assert c.username == "admin"
assert c.display_name is None
assert c.email is None
assert c.is_active is None
def test_with_all_fields(self):
c = SupersetAccountCandidate(
environment_id="env-1", username="jdoe",
display_name="John Doe", email="j@d.com", is_active=True,
)
assert c.display_name == "John Doe"
assert c.is_active is True
def test_serialize_roundtrip(self):
c = SupersetAccountCandidate(environment_id="env-1", username="admin")
data = c.model_dump()
restored = SupersetAccountCandidate.model_validate(data)
assert restored.username == "admin"
class TestSupersetAccountLookupResponse:
"""SupersetAccountLookupResponse — paginated response envelope."""
def test_minimal(self):
resp = SupersetAccountLookupResponse(status="success", environment_id="env-1", page_index=0, page_size=20, total=0)
assert resp.items == []
assert resp.warning is None
def test_with_items(self):
items = [SupersetAccountCandidate(environment_id="env-1", username="admin")]
resp = SupersetAccountLookupResponse(
status="success", environment_id="env-1",
page_index=0, page_size=20, total=1, items=items,
)
assert len(resp.items) == 1
def test_degraded_status(self):
resp = SupersetAccountLookupResponse(
status="degraded", environment_id="env-1",
page_index=0, page_size=20, total=0,
warning="Cannot load accounts",
)
assert resp.status == "degraded"
assert resp.warning == "Cannot load accounts"
def test_constraints(self):
with pytest.raises(ValidationError):
SupersetAccountLookupResponse(status="success", environment_id="env-1", page_index=0, page_size=0, total=0)
with pytest.raises(ValidationError):
SupersetAccountLookupResponse(status="success", environment_id="env-1", page_index=0, page_size=101, total=0)
with pytest.raises(ValidationError):
SupersetAccountLookupResponse(status="success", environment_id="env-1", page_index=-1, page_size=20, total=0)
with pytest.raises(ValidationError):
SupersetAccountLookupResponse(status="success", environment_id="env-1", page_index=0, page_size=20, total=-1)
with pytest.raises(ValidationError):
SupersetAccountLookupResponse(status="invalid", environment_id="env-1", page_index=0, page_size=20, total=0)
def test_serialize_roundtrip(self):
resp = SupersetAccountLookupResponse(status="success", environment_id="env-1", page_index=0, page_size=20, total=5)
data = resp.model_dump()
restored = SupersetAccountLookupResponse.model_validate(data)
assert restored.total == 5
assert restored.status == "success"
# #endregion Test.Schemas.Profile