# tests/schemas/syndarix/test_project_schemas.py """ Tests for Project schema validation. """ import uuid import pytest from pydantic import ValidationError from app.schemas.syndarix import ( AutonomyLevel, ProjectCreate, ProjectStatus, ProjectUpdate, ) class TestProjectCreateValidation: """Tests for ProjectCreate schema validation.""" def test_valid_project_create(self, valid_project_data): """Test creating project with valid data.""" project = ProjectCreate(**valid_project_data) assert project.name == "Test Project" assert project.slug == "test-project" assert project.description == "A test project" def test_project_create_defaults(self): """Test that defaults are applied correctly.""" project = ProjectCreate( name="Minimal Project", slug="minimal-project", ) assert project.autonomy_level == AutonomyLevel.MILESTONE assert project.status == ProjectStatus.ACTIVE assert project.settings == {} assert project.owner_id is None def test_project_create_with_owner(self, valid_project_data): """Test creating project with owner ID.""" owner_id = uuid.uuid4() project = ProjectCreate( **valid_project_data, owner_id=owner_id, ) assert project.owner_id == owner_id def test_project_create_name_empty_fails(self): """Test that empty name raises ValidationError.""" with pytest.raises(ValidationError) as exc_info: ProjectCreate( name="", slug="valid-slug", ) errors = exc_info.value.errors() assert any("name" in str(e) for e in errors) def test_project_create_name_whitespace_only_fails(self): """Test that whitespace-only name raises ValidationError.""" with pytest.raises(ValidationError) as exc_info: ProjectCreate( name=" ", slug="valid-slug", ) errors = exc_info.value.errors() assert any("name" in str(e) for e in errors) def test_project_create_name_stripped(self): """Test that name is stripped of leading/trailing whitespace.""" project = ProjectCreate( name=" Padded Name ", slug="padded-slug", ) assert project.name == "Padded Name" def test_project_create_slug_required(self): """Test that slug is required for create.""" with pytest.raises(ValidationError) as exc_info: ProjectCreate(name="No Slug Project") errors = exc_info.value.errors() assert any("slug" in str(e).lower() for e in errors) class TestProjectSlugValidation: """Tests for Project slug validation.""" def test_valid_slugs(self): """Test various valid slug formats.""" valid_slugs = [ "simple", "with-hyphens", "has123numbers", "mix3d-with-hyphen5", "a", # Single character ] for slug in valid_slugs: project = ProjectCreate( name="Test Project", slug=slug, ) assert project.slug == slug def test_invalid_slug_uppercase(self): """Test that uppercase letters in slug raise ValidationError.""" with pytest.raises(ValidationError) as exc_info: ProjectCreate( name="Test Project", slug="Invalid-Uppercase", ) errors = exc_info.value.errors() assert any("slug" in str(e).lower() for e in errors) def test_invalid_slug_special_chars(self): """Test that special characters in slug raise ValidationError.""" invalid_slugs = [ "has_underscore", "has.dot", "has@symbol", "has space", "has/slash", ] for slug in invalid_slugs: with pytest.raises(ValidationError): ProjectCreate( name="Test Project", slug=slug, ) def test_invalid_slug_starts_with_hyphen(self): """Test that slug starting with hyphen raises ValidationError.""" with pytest.raises(ValidationError) as exc_info: ProjectCreate( name="Test Project", slug="-invalid-start", ) errors = exc_info.value.errors() assert any("hyphen" in str(e).lower() for e in errors) def test_invalid_slug_ends_with_hyphen(self): """Test that slug ending with hyphen raises ValidationError.""" with pytest.raises(ValidationError) as exc_info: ProjectCreate( name="Test Project", slug="invalid-end-", ) errors = exc_info.value.errors() assert any("hyphen" in str(e).lower() for e in errors) def test_invalid_slug_consecutive_hyphens(self): """Test that consecutive hyphens in slug raise ValidationError.""" with pytest.raises(ValidationError) as exc_info: ProjectCreate( name="Test Project", slug="invalid--consecutive", ) errors = exc_info.value.errors() assert any("consecutive" in str(e).lower() for e in errors) class TestProjectUpdateValidation: """Tests for ProjectUpdate schema validation.""" def test_project_update_partial(self): """Test updating only some fields.""" update = ProjectUpdate( name="Updated Name", ) assert update.name == "Updated Name" assert update.slug is None assert update.description is None assert update.autonomy_level is None assert update.status is None def test_project_update_all_fields(self): """Test updating all fields.""" owner_id = uuid.uuid4() update = ProjectUpdate( name="Updated Name", slug="updated-slug", description="Updated description", autonomy_level=AutonomyLevel.AUTONOMOUS, status=ProjectStatus.PAUSED, settings={"key": "value"}, owner_id=owner_id, ) assert update.name == "Updated Name" assert update.slug == "updated-slug" assert update.autonomy_level == AutonomyLevel.AUTONOMOUS assert update.status == ProjectStatus.PAUSED def test_project_update_empty_name_fails(self): """Test that empty name in update raises ValidationError.""" with pytest.raises(ValidationError) as exc_info: ProjectUpdate(name="") errors = exc_info.value.errors() assert any("name" in str(e) for e in errors) def test_project_update_slug_validation(self): """Test that slug validation applies to updates too.""" with pytest.raises(ValidationError): ProjectUpdate(slug="Invalid-Slug") class TestProjectEnums: """Tests for Project enum validation.""" def test_valid_autonomy_levels(self): """Test all valid autonomy levels.""" for level in AutonomyLevel: # Replace underscores with hyphens for valid slug slug_suffix = level.value.replace("_", "-") project = ProjectCreate( name="Test Project", slug=f"project-{slug_suffix}", autonomy_level=level, ) assert project.autonomy_level == level def test_invalid_autonomy_level(self): """Test that invalid autonomy level raises ValidationError.""" with pytest.raises(ValidationError): ProjectCreate( name="Test Project", slug="invalid-autonomy", autonomy_level="invalid", # type: ignore ) def test_valid_project_statuses(self): """Test all valid project statuses.""" for status in ProjectStatus: project = ProjectCreate( name="Test Project", slug=f"project-status-{status.value}", status=status, ) assert project.status == status def test_invalid_project_status(self): """Test that invalid project status raises ValidationError.""" with pytest.raises(ValidationError): ProjectCreate( name="Test Project", slug="invalid-status", status="invalid", # type: ignore ) class TestProjectSettings: """Tests for Project settings validation.""" def test_settings_empty_dict(self): """Test that empty settings dict is valid.""" project = ProjectCreate( name="Test Project", slug="empty-settings", settings={}, ) assert project.settings == {} def test_settings_complex_structure(self): """Test that complex settings structure is valid.""" complex_settings = { "mcp_servers": ["gitea", "slack"], "webhooks": { "on_issue_created": "https://example.com", }, "flags": True, "count": 42, } project = ProjectCreate( name="Test Project", slug="complex-settings", settings=complex_settings, ) assert project.settings == complex_settings def test_settings_default_to_empty_dict(self): """Test that settings default to empty dict when not provided.""" project = ProjectCreate( name="Test Project", slug="default-settings", ) assert project.settings == {}