from pathlib import Path import pytest from pydantic import ValidationError from app.copilot.actions import ACTION_DEFINITIONS from app.social.schemas.posts import SocialPostCreate, SocialPostValidation def test_canonical_copy_and_project_provenance_are_strict() -> None: payload = SocialPostCreate.model_validate({ "project_id": "123e4567-e89b-12d3-a456-426614174000", "caption": "Release update", "hashtags": ["#release", "release", "media"], "targets": [{ "social_account_id": "x-account", "caption": {"text": "X override"}, "x": {"text": "X override"}, }], }) assert payload.caption == "Release update" assert payload.hashtags == ["release", "media"] with pytest.raises(ValidationError): SocialPostCreate.model_validate({ "targets": [{ "social_account_id": "x-account", "caption": {}, "x": {"text": "valid"}, "unknown_provider_payload": {}, }], }) def test_structured_validation_is_per_target() -> None: result = SocialPostValidation.model_validate({ "post_id": "post-1", "valid": False, "targets": [{ "target_id": "target-1", "provider": "youtube", "account_id": "account-1", "valid": False, "errors": [{"code": "SOCIAL_MEDIA_INVALID", "message": "Invalid media."}], "warnings": [], }], }) assert not result.valid assert result.targets[0].errors[0].code == "SOCIAL_MEDIA_INVALID" def test_copilot_external_publishing_actions_require_confirmation() -> None: definitions = { item.type: item for item in ACTION_DEFINITIONS if item.type.startswith("publishing.") } assert set(definitions) == { "publishing.validate", "publishing.create_post", "publishing.schedule", "publishing.publish", "publishing.cancel", } for name in ("publishing.schedule", "publishing.publish", "publishing.cancel"): assert definitions[name].external_side_effect assert definitions[name].requires_confirmation def test_additive_migration_extends_existing_social_tables() -> None: sql = Path("app/social/migrations/0008_unified_publishing.sql").read_text() lowered = sql.lower() assert "alter table social_posts" in lowered assert "foreign key (project_id) references projects(id)" in lowered assert "force row level security" in lowered assert "create table social_posts" not in lowered assert "create table social_jobs" not in lowered