149 lines
5.8 KiB
Python
149 lines
5.8 KiB
Python
|
|
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
|