feat: admin panel enrichment
This commit is contained in:
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,212 @@
|
||||
from django.contrib.auth.models import User
|
||||
from django.core import mail
|
||||
from django.test import TestCase, override_settings
|
||||
from django.urls import reverse
|
||||
|
||||
from apps.accounts.models import ClientProfile
|
||||
from apps.projects.models import ProjectBrief, ProjectInternalNote
|
||||
|
||||
|
||||
class ProjectPortalTest(TestCase):
|
||||
def setUp(self):
|
||||
self.user = User.objects.create_user(
|
||||
username="client",
|
||||
email="client@example.com",
|
||||
password="Str0ngPass!word",
|
||||
first_name="Client",
|
||||
last_name="User",
|
||||
)
|
||||
ClientProfile.objects.create(user=self.user, timezone="UTC")
|
||||
self.other = User.objects.create_user(
|
||||
username="other",
|
||||
email="other@example.com",
|
||||
password="Str0ngPass!word",
|
||||
)
|
||||
ClientProfile.objects.create(user=self.other, timezone="UTC")
|
||||
|
||||
def _brief_payload(self):
|
||||
return {
|
||||
"title": "Radiomics pipeline",
|
||||
"category": ProjectBrief.CATEGORY_MEDICAL_IMAGING,
|
||||
"description": "Need a standardized radiomics workflow.",
|
||||
"budget_range": ProjectBrief.BUDGET_5K_15K,
|
||||
"desired_deadline": "2026-12-31",
|
||||
"reference_links": "https://example.com/spec",
|
||||
}
|
||||
|
||||
def _create_brief(self, **overrides):
|
||||
defaults = dict(
|
||||
client=self.user,
|
||||
title="Brief",
|
||||
category=ProjectBrief.CATEGORY_WEB,
|
||||
description="d",
|
||||
budget_range=ProjectBrief.BUDGET_UNDER_5K,
|
||||
)
|
||||
defaults.update(overrides)
|
||||
return ProjectBrief.objects.create(**defaults)
|
||||
|
||||
def test_dashboard_requires_login(self):
|
||||
response = self.client.get(reverse("projects:dashboard"))
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
def test_create_brief_sends_admin_email(self):
|
||||
self.client.login(username="client", password="Str0ngPass!word")
|
||||
with override_settings(MANAGED_PROJECTS_NOTIFY_EMAILS="ops@tecvico.com"):
|
||||
response = self.client.post(reverse("projects:create"), data=self._brief_payload())
|
||||
self.assertEqual(response.status_code, 302)
|
||||
brief = ProjectBrief.objects.get(title="Radiomics pipeline")
|
||||
self.assertEqual(brief.client, self.user)
|
||||
self.assertEqual(brief.status, ProjectBrief.STATUS_SUBMITTED)
|
||||
self.assertEqual(len(mail.outbox), 1)
|
||||
self.assertIn("Radiomics pipeline", mail.outbox[0].subject)
|
||||
self.assertIn("ops@tecvico.com", mail.outbox[0].to)
|
||||
|
||||
def test_create_brief_with_title_only(self):
|
||||
self.client.login(username="client", password="Str0ngPass!word")
|
||||
response = self.client.post(
|
||||
reverse("projects:create"),
|
||||
data={"title": "Minimal brief"},
|
||||
)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
brief = ProjectBrief.objects.get(title="Minimal brief")
|
||||
self.assertEqual(brief.description, "")
|
||||
self.assertEqual(brief.category, "")
|
||||
self.assertEqual(brief.budget_range, "")
|
||||
|
||||
def test_create_brief_requires_title(self):
|
||||
self.client.login(username="client", password="Str0ngPass!word")
|
||||
response = self.client.post(reverse("projects:create"), data={"title": ""})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, "This field is required")
|
||||
self.assertEqual(ProjectBrief.objects.count(), 0)
|
||||
|
||||
def test_create_renders_submit_only_on_last_step(self):
|
||||
self.client.login(username="client", password="Str0ngPass!word")
|
||||
response = self.client.get(reverse("projects:create"))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, 'data-wizard-submit hidden')
|
||||
self.assertContains(response, 'data-wizard-next')
|
||||
self.assertNotContains(response, 'data-wizard-submit">{{ submit_label }}</button>')
|
||||
|
||||
def test_dashboard_lists_only_own_briefs(self):
|
||||
self._create_brief(title="Mine")
|
||||
self._create_brief(client=self.other, title="Not mine")
|
||||
self.client.login(username="client", password="Str0ngPass!word")
|
||||
response = self.client.get(reverse("projects:dashboard"))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
titles = [brief.title for brief in response.context["briefs"]]
|
||||
self.assertEqual(titles, ["Mine"])
|
||||
|
||||
def test_edit_allowed_only_when_submitted(self):
|
||||
brief = self._create_brief(
|
||||
title="Editable",
|
||||
category=ProjectBrief.CATEGORY_DATA,
|
||||
budget_range=ProjectBrief.BUDGET_NOT_SURE,
|
||||
status=ProjectBrief.STATUS_SUBMITTED,
|
||||
)
|
||||
self.client.login(username="client", password="Str0ngPass!word")
|
||||
response = self.client.get(reverse("projects:edit", kwargs={"pk": brief.pk}))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
brief.status = ProjectBrief.STATUS_UNDER_REVIEW
|
||||
brief.save()
|
||||
response = self.client.get(reverse("projects:edit", kwargs={"pk": brief.pk}))
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
def test_locked_project_blocks_edit(self):
|
||||
brief = self._create_brief(
|
||||
title="Locked",
|
||||
status=ProjectBrief.STATUS_SUBMITTED,
|
||||
locked=True,
|
||||
)
|
||||
self.client.login(username="client", password="Str0ngPass!word")
|
||||
response = self.client.get(reverse("projects:edit", kwargs={"pk": brief.pk}))
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
def test_locked_field_is_disabled_on_edit_form(self):
|
||||
brief = self._create_brief(
|
||||
title="Lock field",
|
||||
status=ProjectBrief.STATUS_SUBMITTED,
|
||||
lock_title=True,
|
||||
)
|
||||
self.client.login(username="client", password="Str0ngPass!word")
|
||||
response = self.client.get(reverse("projects:edit", kwargs={"pk": brief.pk}))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, 'name="title"')
|
||||
self.assertContains(response, "disabled")
|
||||
|
||||
def test_locked_field_value_is_preserved_on_submit(self):
|
||||
brief = self._create_brief(
|
||||
title="Original title",
|
||||
status=ProjectBrief.STATUS_SUBMITTED,
|
||||
lock_title=True,
|
||||
)
|
||||
self.client.login(username="client", password="Str0ngPass!word")
|
||||
self.client.post(
|
||||
reverse("projects:edit", kwargs={"pk": brief.pk}),
|
||||
data={
|
||||
"title": "Hacked title",
|
||||
"category": brief.category,
|
||||
"description": brief.description,
|
||||
"budget_range": brief.budget_range,
|
||||
"desired_deadline": "",
|
||||
"reference_links": "",
|
||||
},
|
||||
)
|
||||
brief.refresh_from_db()
|
||||
self.assertEqual(brief.title, "Original title")
|
||||
|
||||
def test_status_change_emails_client(self):
|
||||
brief = self._create_brief(
|
||||
title="Notify me",
|
||||
category=ProjectBrief.CATEGORY_RESEARCH,
|
||||
budget_range=ProjectBrief.BUDGET_15K_50K,
|
||||
status=ProjectBrief.STATUS_SUBMITTED,
|
||||
)
|
||||
mail.outbox.clear()
|
||||
brief.status = ProjectBrief.STATUS_UNDER_REVIEW
|
||||
brief.save()
|
||||
self.assertEqual(len(mail.outbox), 1)
|
||||
self.assertIn("client@example.com", mail.outbox[0].to)
|
||||
|
||||
def test_internal_notes_not_on_detail_page(self):
|
||||
brief = self._create_brief(
|
||||
title="Secret notes",
|
||||
category=ProjectBrief.CATEGORY_OTHER,
|
||||
)
|
||||
ProjectInternalNote.objects.create(brief=brief, note="Internal only", author=self.other)
|
||||
self.client.login(username="client", password="Str0ngPass!word")
|
||||
response = self.client.get(reverse("projects:detail", kwargs={"pk": brief.pk}))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertNotIn(b"Internal only", response.content)
|
||||
|
||||
def test_website_notes_visible_on_detail_page(self):
|
||||
brief = self._create_brief(
|
||||
title="Public notes",
|
||||
category=ProjectBrief.CATEGORY_WEB,
|
||||
website_notes="Your kickoff call is scheduled for next week.",
|
||||
)
|
||||
self.client.login(username="client", password="Str0ngPass!word")
|
||||
response = self.client.get(reverse("projects:detail", kwargs={"pk": brief.pk}))
|
||||
self.assertContains(response, "Notes from Tecvico")
|
||||
self.assertContains(response, "kickoff call is scheduled")
|
||||
|
||||
def test_create_renders_wizard_steps(self):
|
||||
self.client.login(username="client", password="Str0ngPass!word")
|
||||
response = self.client.get(reverse("projects:create"))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, "data-brief-wizard")
|
||||
self.assertContains(response, "Project basics")
|
||||
self.assertContains(response, "References & files")
|
||||
|
||||
def test_quote_visible_when_quoted(self):
|
||||
brief = self._create_brief(
|
||||
title="Quoted project",
|
||||
category=ProjectBrief.CATEGORY_WORKFLOW,
|
||||
budget_range=ProjectBrief.BUDGET_50K_PLUS,
|
||||
status=ProjectBrief.STATUS_QUOTED,
|
||||
quote_text="We can deliver in 8 weeks for $45,000.",
|
||||
)
|
||||
self.client.login(username="client", password="Str0ngPass!word")
|
||||
response = self.client.get(reverse("projects:detail", kwargs={"pk": brief.pk}))
|
||||
self.assertContains(response, "We can deliver in 8 weeks")
|
||||
Reference in New Issue
Block a user