intent/tests/test_features.py

149 lines
5.8 KiB
Python
Raw Normal View History

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"<h2" in response.content and b"Draft" in response.content
feature.refresh_from_db()
assert feature.current_revision == 1
def test_edit_via_form(client, owner, feature):
client.force_login(owner)
client.post(
furl("edit", feature),
{"title": "Sign in", "description": "Line uno\n", "status": "active", "note": "Spanish"},
)
feature.refresh_from_db()
assert feature.current_revision == 2
def test_feature_pages_render(client, owner, project, feature):
services.update_feature(feature, owner, note="Edit", description="Line uno\n", title="Login")
client.force_login(owner)
for path in [
furl("list", project=project),
reverse("projects:home", kwargs={"org_slug": "catbriar", "project_slug": "intent"}),
furl("detail", feature),
furl("history", feature),
furl("revision", feature, revision=1),
furl("compare", feature),
]:
assert client.get(path).status_code == 200, path
compare = client.get(furl("compare", feature) + "?old=1&new=2").content.decode()
assert "Line one" in compare and "Line uno" in compare and "Removed: " in compare
assert client.get(furl("revision", feature, revision=2)).status_code == 302
def test_feature_key_lookup(client, owner, feature):
client.force_login(owner)
base = furl("list", project=feature.project)
assert client.get(f"{base}int-1/").status_code == 200
assert client.get(f"{base}NOPE-1/").status_code == 404
assert client.get(f"{base}INT-99/").status_code == 404
def test_viewer_cannot_open_edit_page(client, make_user, project, feature):
project.visibility = "public"
project.save()
assert client.get(furl("detail", feature)).status_code == 200
client.force_login(make_user())
assert client.get(furl("edit", feature)).status_code == 403