import pytest from django.core.exceptions import PermissionDenied, ValidationError from django.db import IntegrityError from django.urls import reverse from intent.features import services from intent.features.models import Feature, FeatureStatus def furl(name, feature=None, project=None, **kwargs): project = project or feature.project kwargs = {"org_slug": project.org.slug, "project_slug": project.slug, **kwargs} if feature: kwargs["feature_key"] = feature.key return reverse(f"features:{name}", kwargs=kwargs) @pytest.fixture def feature(project, owner): return services.create_feature(project, owner, title="Sign in", description="Line one\n") def test_numbers_are_sequential_and_first_revision_recorded(project, owner, feature): second = services.create_feature(project, owner, title="Second") assert (feature.key, second.key) == ("INT-1", "INT-2") revision = feature.revisions.get() assert (revision.revision, revision.note) == (1, "Created.") def test_update_records_revision_with_note(owner, feature): revision = services.update_feature(feature, owner, note="Clarify", description="Line uno\n") assert revision.revision == feature.current_revision == 2 assert revision.note == "Clarify" assert feature.revisions.get(revision=1).description == "Line one\n" def test_update_needs_a_note_and_a_change(owner, feature): with pytest.raises(ValidationError): services.update_feature(feature, owner, note=" ", title="New") with pytest.raises(ValidationError): services.update_feature(feature, owner, note="No-op", title="Sign in") def test_cannot_create_a_cycle(project, owner, feature): child = services.create_feature(project, owner, title="Child", parent=feature) grandchild = services.create_feature(project, owner, title="Grandchild", parent=child) with pytest.raises(ValidationError): services.update_feature(feature, owner, note="Loop", parent=grandchild) with pytest.raises(ValidationError): services.update_feature(feature, owner, note="Self", parent=feature) def test_superseded_needs_replacement(project, owner, feature): with pytest.raises(ValidationError): services.update_feature(feature, owner, note="Gone", status=FeatureStatus.SUPERSEDED) replacement = services.create_feature(project, owner, title="Sign in v2") services.update_feature( feature, owner, note="Replaced", status=FeatureStatus.SUPERSEDED, superseded_by=replacement ) services.update_feature(feature, owner, note="Back", status=FeatureStatus.ACTIVE) feature.refresh_from_db() assert feature.superseded_by is None def test_database_enforces_superseded_by(feature): with pytest.raises(IntegrityError): Feature.objects.filter(pk=feature.pk).update(status=FeatureStatus.SUPERSEDED) def test_collaborator_without_project_role_cannot_edit(collaborator, project, feature): with pytest.raises(PermissionDenied): services.create_feature(project, collaborator, title="Nope") with pytest.raises(PermissionDenied): services.update_feature(feature, collaborator, note="x", title="Nope") def test_create_via_form_with_parent_prefill(client, editor, project, feature): client.force_login(editor) response = client.get(furl("create", project=project) + "?parent=int-1") assert b"selected>INT-1 Sign in" in response.content response = client.post( furl("create", project=project), {"title": "Child", "description": "", "parent": feature.pk, "note": ""}, ) child = Feature.objects.get(title="Child") assert response["Location"] == furl("detail", child) assert child.parent == feature def test_preview_does_not_save(client, owner, feature): client.force_login(owner) response = client.post( furl("edit", feature), { "title": "Sign in", "description": "# Draft", "status": "active", "note": "x", "preview": "1", }, ) assert b"