Files
ss-tools/backend/tests/schemas/test_settings.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

261 lines
8.9 KiB
Python

# #region Test.Schemas.Settings [C:2] [TYPE Module] [SEMANTICS test,schema,settings]
# @BRIEF Tests for schemas/settings.py — NotificationChannel, ValidationPolicyBase,
# ValidationPolicyCreate, ValidationPolicyUpdate, ValidationPolicyResponse,
# TranslationScheduleItem.
# @RELATION BINDS_TO -> [SettingsSchemas]
# @TEST_EDGE: missing_field -> optional fields default correctly
# @TEST_EDGE: invalid_type -> validation rejects bad types
# @TEST_EDGE: serialization -> round-trip via model_dump/model_validate
from datetime import datetime, time, timezone
import pytest
from pydantic import ValidationError
from src.schemas.settings import (
NotificationChannel,
TranslationScheduleItem,
ValidationPolicyBase,
ValidationPolicyCreate,
ValidationPolicyResponse,
ValidationPolicyUpdate,
)
class TestNotificationChannel:
"""NotificationChannel — type and target."""
def test_minimal(self):
nc = NotificationChannel(type="SLACK", target="#alerts")
assert nc.type == "SLACK"
assert nc.target == "#alerts"
def test_missing_field_raises(self):
with pytest.raises(ValidationError):
NotificationChannel(type="SLACK")
with pytest.raises(ValidationError):
NotificationChannel(target="#alerts")
def test_serialize_roundtrip(self):
nc = NotificationChannel(type="TELEGRAM", target="chat-1")
data = nc.model_dump()
restored = NotificationChannel.model_validate(data)
assert restored.type == "TELEGRAM"
assert restored.target == "chat-1"
class TestValidationPolicyBase:
"""ValidationPolicyBase — required fields and defaults."""
def make_policy(self, **overrides):
defaults = dict(
name="Test Policy",
environment_id="env-1",
dashboard_ids=["1", "2"],
schedule_days=[0, 2, 4],
window_start=time(9, 0),
window_end=time(18, 0),
)
defaults.update(overrides)
return ValidationPolicyBase(**defaults)
def test_minimal(self):
p = self.make_policy()
assert p.name == "Test Policy"
assert p.is_active is True
assert p.notify_owners is True
assert p.alert_condition == "FAIL_ONLY"
assert p.custom_channels is None
def test_with_channels(self):
channels = [NotificationChannel(type="SLACK", target="#alerts")]
p = self.make_policy(custom_channels=channels)
assert len(p.custom_channels) == 1
assert p.custom_channels[0].type == "SLACK"
def test_inactive(self):
p = self.make_policy(is_active=False)
assert p.is_active is False
def test_alert_conditions(self):
for condition in ["FAIL_ONLY", "WARN_AND_FAIL", "ALWAYS"]:
p = self.make_policy(alert_condition=condition)
assert p.alert_condition == condition
def test_serialize_roundtrip(self):
p = self.make_policy()
data = p.model_dump()
restored = ValidationPolicyBase.model_validate(data)
assert restored.name == "Test Policy"
assert restored.is_active is True
class TestValidationPolicyCreate:
"""ValidationPolicyCreate — inherits ValidationPolicyBase."""
def test_create(self):
p = ValidationPolicyCreate(
name="New Policy",
environment_id="env-1",
dashboard_ids=["1"],
schedule_days=[1],
window_start=time(10, 0),
window_end=time(20, 0),
)
assert p.name == "New Policy"
def test_serialize_roundtrip(self):
p = ValidationPolicyCreate(
name="P", environment_id="e1", dashboard_ids=["1"],
schedule_days=[1], window_start=time(9, 0), window_end=time(17, 0),
)
data = p.model_dump()
restored = ValidationPolicyCreate.model_validate(data)
assert restored.name == "P"
class TestValidationPolicyUpdate:
"""ValidationPolicyUpdate — all fields optional for partial update."""
def test_empty(self):
p = ValidationPolicyUpdate()
assert p.name is None
assert p.environment_id is None
assert p.is_active is None
assert p.dashboard_ids is None
assert p.schedule_days is None
assert p.window_start is None
assert p.window_end is None
assert p.notify_owners is None
assert p.custom_channels is None
assert p.alert_condition is None
def test_partial(self):
p = ValidationPolicyUpdate(name="Updated")
assert p.name == "Updated"
def test_serialize_roundtrip(self):
p = ValidationPolicyUpdate(name="Updated", is_active=False)
data = p.model_dump()
restored = ValidationPolicyUpdate.model_validate(data)
assert restored.name == "Updated"
assert restored.is_active is False
class TestValidationPolicyResponse:
"""ValidationPolicyResponse — inherits ValidationPolicyBase with id + timestamps."""
def test_minimal(self):
ts = datetime.now(timezone.utc)
p = ValidationPolicyResponse(
id="pol-1",
name="Policy",
environment_id="env-1",
dashboard_ids=["1"],
schedule_days=[1],
window_start=time(9, 0),
window_end=time(17, 0),
created_at=ts,
updated_at=ts,
)
assert p.id == "pol-1"
assert p.created_at == ts
assert p.updated_at == ts
def test_serialize_roundtrip(self):
ts = datetime.now(timezone.utc)
p = ValidationPolicyResponse(
id="pol-1", name="P", environment_id="e1", dashboard_ids=["1"],
schedule_days=[1], window_start=time(9, 0), window_end=time(17, 0),
created_at=ts, updated_at=ts,
)
data = p.model_dump()
restored = ValidationPolicyResponse.model_validate(data)
assert restored.id == "pol-1"
def test_defaults_from_base(self):
ts = datetime.now(timezone.utc)
p = ValidationPolicyResponse(
id="pol-1", name="P", environment_id="e1", dashboard_ids=["1"],
schedule_days=[1], window_start=time(9, 0), window_end=time(17, 0),
created_at=ts, updated_at=ts,
)
assert p.is_active is True
assert p.notify_owners is True
assert p.alert_condition == "FAIL_ONLY"
def test_from_attributes(self):
ts = datetime.now(timezone.utc)
class FakeORM:
id = "pol-1"
name = "P"
environment_id = "e1"
is_active = True
dashboard_ids = ["1"]
schedule_days = [1]
window_start = time(9, 0)
window_end = time(17, 0)
notify_owners = False
custom_channels = None
alert_condition = "WARN_AND_FAIL"
created_at = ts
updated_at = ts
p = ValidationPolicyResponse.model_validate(FakeORM())
assert p.notify_owners is False
class TestTranslationScheduleItem:
"""TranslationScheduleItem — schedule with cron expression."""
def test_minimal(self):
s = TranslationScheduleItem(
schedule_id="s-1", job_id="j-1",
cron_expression="0 */6 * * *", timezone="UTC", is_active=True,
)
assert s.job_name is None
assert s.execution_mode == "full"
assert s.last_run_at is None
assert s.next_run_at is None
assert s.created_by is None
assert s.created_at is None
def test_with_all_fields(self):
ts = datetime.now(timezone.utc)
s = TranslationScheduleItem(
schedule_id="s-1", job_id="j-1", job_name="Nightly Sync",
cron_expression="0 0 * * *", timezone="America/New_York",
is_active=False, execution_mode="incremental",
last_run_at=ts, next_run_at=ts, created_by="admin", created_at=ts,
)
assert s.job_name == "Nightly Sync"
assert s.execution_mode == "incremental"
assert s.is_active is False
def test_serialize_roundtrip(self):
s = TranslationScheduleItem(
schedule_id="s-1", job_id="j-1",
cron_expression="0 0 * * *", timezone="UTC", is_active=True,
)
data = s.model_dump()
restored = TranslationScheduleItem.model_validate(data)
assert restored.schedule_id == "s-1"
assert restored.is_active is True
def test_from_attributes(self):
ts = datetime.now(timezone.utc)
class FakeORM:
schedule_id = "s-1"
job_id = "j-1"
job_name = "Test"
cron_expression = "*/5 * * * *"
timezone = "UTC"
is_active = True
execution_mode = "full"
last_run_at = ts
next_run_at = ts
created_by = "admin"
created_at = ts
s = TranslationScheduleItem.model_validate(FakeORM())
assert s.cron_expression == "*/5 * * * *"
assert s.execution_mode == "full"
# #endregion Test.Schemas.Settings