137 lines
5.1 KiB
Python
137 lines
5.1 KiB
Python
|
|
"""The example flow from docs/concept.md, steps 1–7, driven through the views like a browser."""
|
|||
|
|
|
|||
|
|
from django.urls import reverse
|
|||
|
|
|
|||
|
|
from intent.comments.models import CommentThread
|
|||
|
|
from intent.features.models import Feature
|
|||
|
|
from intent.issues.models import Issue, IssueStatus
|
|||
|
|
|
|||
|
|
P = {"org_slug": "catbriar", "project_slug": "intent"}
|
|||
|
|
|
|||
|
|
|
|||
|
|
def issue_url(name, number, **kwargs):
|
|||
|
|
return reverse(f"issues:{name}", kwargs={**P, "number": number, **kwargs})
|
|||
|
|
|
|||
|
|
|
|||
|
|
def feature_url(name, key, **kwargs):
|
|||
|
|
return reverse(f"features:{name}", kwargs={**P, "feature_key": key, **kwargs})
|
|||
|
|
|
|||
|
|
|
|||
|
|
def reply_url(thread):
|
|||
|
|
return reverse("comments:reply", kwargs={**P, "thread_id": thread.pk})
|
|||
|
|
|
|||
|
|
|
|||
|
|
def test_example_flow(client, open_project, outsider, editor):
|
|||
|
|
# 1. A general user creates a new issue in a project.
|
|||
|
|
client.force_login(outsider)
|
|||
|
|
client.post(
|
|||
|
|
reverse("issues:create", kwargs=P),
|
|||
|
|
{"title": "Remember me on shared computers", "body": "Sessions last too long."},
|
|||
|
|
)
|
|||
|
|
issue = Issue.objects.get()
|
|||
|
|
|
|||
|
|
# 2. Users, including team members, discuss in comments on the issue.
|
|||
|
|
issue_thread = issue.threads.get()
|
|||
|
|
client.post(reply_url(issue_thread), {"body": "Public library computers especially."})
|
|||
|
|
client.force_login(editor)
|
|||
|
|
client.post(reply_url(issue_thread), {"body": "Agreed, let's define this."})
|
|||
|
|
|
|||
|
|
# 3. A team member accepts the issue and creates a feature from it, linked to the issue.
|
|||
|
|
create = reverse("features:create", kwargs=P) + f"?issue={issue.number}"
|
|||
|
|
client.post(
|
|||
|
|
create,
|
|||
|
|
{
|
|||
|
|
"title": "Session length",
|
|||
|
|
"description": "Sessions end after 12 hours.\n\nShared computers end at close.\n",
|
|||
|
|
"note": "",
|
|||
|
|
"accept_issue": "on",
|
|||
|
|
},
|
|||
|
|
)
|
|||
|
|
issue.refresh_from_db()
|
|||
|
|
feature = Feature.objects.get()
|
|||
|
|
assert issue.status == IssueStatus.ACCEPTED
|
|||
|
|
assert list(issue.features.all()) == [feature]
|
|||
|
|
assert feature.revisions.get().issues.get() == issue
|
|||
|
|
|
|||
|
|
# 4. The team member updates the feature's status manually.
|
|||
|
|
client.post(
|
|||
|
|
feature_url("edit", feature.key),
|
|||
|
|
{
|
|||
|
|
"title": feature.title,
|
|||
|
|
"description": feature.description,
|
|||
|
|
"status": "withdrawn",
|
|||
|
|
"note": "Paused until the login rework.",
|
|||
|
|
},
|
|||
|
|
)
|
|||
|
|
feature.refresh_from_db()
|
|||
|
|
assert (feature.status, feature.current_revision) == ("withdrawn", 2)
|
|||
|
|
client.post(
|
|||
|
|
feature_url("edit", feature.key),
|
|||
|
|
{
|
|||
|
|
"title": feature.title,
|
|||
|
|
"description": feature.description,
|
|||
|
|
"status": "active",
|
|||
|
|
"note": "Back on.",
|
|||
|
|
},
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 5. Later, a user starts a comment thread on lines of the feature.
|
|||
|
|
client.force_login(outsider)
|
|||
|
|
client.post(
|
|||
|
|
feature_url("thread_create", feature.key),
|
|||
|
|
{"body": "What counts as close?", "revision": 3, "line_start": 3, "line_end": 3},
|
|||
|
|
)
|
|||
|
|
thread = CommentThread.objects.get(feature=feature)
|
|||
|
|
|
|||
|
|
# 6. After some discussion, the user promotes the thread to an issue.
|
|||
|
|
client.force_login(editor)
|
|||
|
|
client.post(reply_url(thread), {"body": "Browser close, I think."})
|
|||
|
|
client.force_login(outsider)
|
|||
|
|
promote = reverse("comments:promote", kwargs={**P, "thread_id": thread.pk})
|
|||
|
|
client.post(promote, {"title": "Define 'close'", "body": "From the thread."})
|
|||
|
|
follow_up = Issue.objects.get(number=2)
|
|||
|
|
thread.refresh_from_db()
|
|||
|
|
assert thread.promoted_to == follow_up and thread.is_resolved
|
|||
|
|
assert list(follow_up.features.all()) == [feature]
|
|||
|
|
|
|||
|
|
# 7. More discussion, then a team member accepts the issue and updates the feature.
|
|||
|
|
client.post(reply_url(follow_up.threads.get()), {"body": "Tab close too?"})
|
|||
|
|
client.force_login(editor)
|
|||
|
|
client.post(
|
|||
|
|
feature_url("edit", feature.key) + f"?issue={follow_up.number}",
|
|||
|
|
{
|
|||
|
|
"title": feature.title,
|
|||
|
|
"description": (
|
|||
|
|
"Sessions end after 12 hours.\n\nShared computers end when the browser closes.\n"
|
|||
|
|
),
|
|||
|
|
"status": "active",
|
|||
|
|
"note": "Define close as browser close.",
|
|||
|
|
"accept_issue": "on",
|
|||
|
|
},
|
|||
|
|
)
|
|||
|
|
follow_up.refresh_from_db()
|
|||
|
|
feature.refresh_from_db()
|
|||
|
|
assert follow_up.status == IssueStatus.ACCEPTED
|
|||
|
|
assert feature.current_revision == 4
|
|||
|
|
assert feature.revisions.get(revision=4).issues.get() == follow_up
|
|||
|
|
|
|||
|
|
# The line thread is now outdated, and the feature page shows both issues.
|
|||
|
|
page = client.get(feature_url("detail", feature.key)).content.decode()
|
|||
|
|
assert "Comments on earlier revisions" in page
|
|||
|
|
assert "#1 Remember me on shared computers" in page and "#2 Define" in page
|
|||
|
|
|
|||
|
|
# 7, alternative: a third issue is rejected but stays linked, so the proposal is visible.
|
|||
|
|
client.force_login(outsider)
|
|||
|
|
client.post(
|
|||
|
|
reverse("issues:create", kwargs=P),
|
|||
|
|
{"title": "Never expire sessions", "features": [feature.pk]},
|
|||
|
|
)
|
|||
|
|
client.force_login(editor)
|
|||
|
|
client.post(issue_url("status", 3), {"status": "rejected"})
|
|||
|
|
rejected = Issue.objects.get(number=3)
|
|||
|
|
assert rejected.status == IssueStatus.REJECTED
|
|||
|
|
assert (
|
|||
|
|
"#3 Never expire sessions"
|
|||
|
|
in client.get(feature_url("detail", feature.key)).content.decode()
|
|||
|
|
)
|