import pytest from django.core.exceptions import PermissionDenied, ValidationError from django.urls import reverse from intent.features.models import Feature, FeatureStatus from intent.features.services import create_feature, update_feature from intent.orgs.models import OrgMembership from intent.orgs.services import remove_member as remove_org_member from intent.projects import services from intent.projects.models import Project, ProjectMembership def url(name, project, **kwargs): return reverse( f"projects:{name}", kwargs={"org_slug": project.org.slug, "project_slug": project.slug, **kwargs}, ) def test_create_requires_grant(collaborator, org): with pytest.raises(PermissionDenied): services.create_project( org, collaborator, slug="x", name="X", feature_prefix="XX", visibility="private", open_contribution=False, ) def test_granted_collaborator_creates_and_owns_project(client, collaborator, org): OrgMembership.objects.filter(user=collaborator).update(can_create_projects=True) client.force_login(collaborator) response = client.post( reverse("project_create", kwargs={"org_slug": org.slug}), {"name": "Docs", "slug": "docs", "feature_prefix": "doc", "visibility": "public"}, ) project = Project.objects.get(slug="docs") assert response.status_code == 302 assert project.feature_prefix == "DOC" assert project.memberships.get(user=collaborator).role == "owner" def test_duplicate_prefix_is_a_form_error(client, owner, org, project): client.force_login(owner) response = client.post( reverse("project_create", kwargs={"org_slug": org.slug}), {"name": "Other", "slug": "other", "feature_prefix": "INT", "visibility": "private"}, ) assert response.status_code == 200 assert b"already exists" in response.content def test_reserved_slug_rejected(owner, org): with pytest.raises(ValidationError): services.create_project( org, owner, slug="members", name="M", feature_prefix="MM", visibility="private", open_contribution=False, ) def test_private_project_hidden_from_outsiders(client, make_user, project): missing = f"/{project.org.slug}/no-such-project/" # Signed-out visitors can't tell a private project from a missing one: both ask to sign in. for path in [url("home", project), missing]: response = client.get(path) assert response.status_code == 302 and "/accounts/login/" in response["Location"], path client.force_login(make_user()) assert client.get(url("home", project)).status_code == 404 assert client.get(missing).status_code == 404 def test_public_project_visible_to_anonymous(client, project): project.visibility = "public" project.save() assert client.get(url("home", project)).status_code == 200 def test_settings_require_admin(client, editor, owner, project): client.force_login(editor) assert client.get(url("settings", project)).status_code == 403 client.force_login(owner) client.post(url("settings", project), {"name": "Renamed", "visibility": "public"}) project.refresh_from_db() assert project.name == "Renamed" and project.visibility == "public" def test_delete_needs_typed_slug_and_cascades_features(client, owner, project): parent = create_feature(project, owner, title="Parent") child = create_feature(project, owner, title="Child", parent=parent) update_feature( parent, owner, note="Replaced", status=FeatureStatus.SUPERSEDED, superseded_by=child ) client.force_login(owner) client.post(url("delete", project), {"confirm": "wrong"}) assert Project.objects.filter(pk=project.pk).exists() client.post(url("delete", project), {"confirm": "intent"}) assert not Project.objects.filter(pk=project.pk).exists() assert not Feature.objects.exists() def test_project_members_must_be_on_the_org_team(owner, make_user, project): with pytest.raises(ValidationError): services.add_member(project, owner, user=make_user()) def test_leaving_org_drops_project_roles(editor, org, project): remove_org_member(org.memberships.get(user=editor), editor) assert not ProjectMembership.objects.filter(user=editor).exists() def test_admin_adds_member_via_page(client, owner, collaborator, project): client.force_login(owner) client.post(url("members", project), {"user": collaborator.pk, "role": "collaborator"}) assert project.memberships.filter(user=collaborator).exists() def test_org_page_lists_visible_projects(client, owner, collaborator, org, project): org_page = reverse("orgs:detail", kwargs={"org_slug": org.slug}) link = f'href="{url("home", project)}"' client.force_login(owner) assert link in client.get(org_page).content.decode() client.force_login(collaborator) assert link not in client.get(org_page).content.decode() def test_slugs_are_lowercased(client, owner, org): client.force_login(owner) client.post( reverse("project_create", kwargs={"org_slug": org.slug}), {"name": "Docs", "slug": "Docs", "feature_prefix": "doc", "visibility": "public"}, ) assert Project.objects.filter(slug="docs").exists()