# #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