Hi {contact.first_name or "there"},
' @@ -322,7 +322,7 @@ def send_csat_survey(case_id, org_id): ) msg = EmailMessage( - subject=f"How did we do? — {case.name}", + subject=f"How did we do?, {case.name}", body=html, to=[contact.email], ) @@ -342,7 +342,7 @@ def send_csat_survey(case_id, org_id): # --------------------------------------------------------------------------- -# Tier 3 time-tracking — auto-stop forgotten timers. +# Tier 3 time-tracking: auto-stop forgotten timers. # A running timer this old gets killed by the Celery beat. Hand-tuned: 12h # covers an overnight forgotten timer without clobbering a mid-day session diff --git a/backend/cases/tests/test_analytics_service.py b/backend/cases/tests/test_analytics_service.py index 6f513409..63f5dcdb 100644 --- a/backend/cases/tests/test_analytics_service.py +++ b/backend/cases/tests/test_analytics_service.py @@ -18,7 +18,7 @@ from django.utils import timezone from cases.models import Case # A single reference instant for the whole fixture. Anchoring every timestamp -# to one `now` keeps the FRT deltas exact — deriving each from its own +# to one `now` keeps the FRT deltas exact, deriving each from its own # timezone.now() call drifts by microseconds, which would nudge a case that # meets its SLA exactly (1h against a 1h target) just over the line. _BASE = timezone.now() @@ -83,7 +83,7 @@ def service_cases(org_a, admin_profile): # -------------------------------------------------------------------------- -# Authorization — the admin-only gate (both directions) +# Authorization: the admin-only gate (both directions) class TestServiceAuthorization: @@ -92,7 +92,7 @@ class TestServiceAuthorization: assert resp.status_code in (401, 403) def test_non_admin_forbidden(self, user_client, service_cases): - """A USER-role member gets 403 — never a personal slice.""" + """A USER-role member gets 403, never a personal slice.""" resp = user_client.get("/api/cases/analytics/service/") assert resp.status_code == 403 @@ -226,7 +226,7 @@ class TestServiceByAgent: # -------------------------------------------------------------------------- -# Empty org — the shape holds so the SSR page never divides by an absent array +# Empty org: the shape holds so the SSR page never divides by an absent array class TestServiceEmpty: diff --git a/backend/cases/tests/test_approvals.py b/backend/cases/tests/test_approvals.py index b03c54b3..6036fc3d 100644 --- a/backend/cases/tests/test_approvals.py +++ b/backend/cases/tests/test_approvals.py @@ -216,7 +216,7 @@ class TestStateTransitions: def test_approve_records_activity( self, admin_client, admin_profile, user_profile, regular_user, org_a ): - # Requested by the USER, approved by the admin — a requester and an + # Requested by the USER, approved by the admin. A requester and an # approver who are different people, which the rule now requires. case, rule, approval = self._setup(org_a, user_profile, regular_user) res = admin_client.post( @@ -528,7 +528,7 @@ class TestRuleCRUD: assert rule.match_team_id is None def test_put_accepts_same_org_approver(self, admin_client, org_a, user_profile): - """The allowed side — a same-org approver PUTs fine, proving the two + """The allowed side, a same-org approver PUTs fine, proving the two rejections above are org-scoping and not a blanket block on the field.""" rule = _make_rule(org_a, match_priority="Urgent") res = admin_client.put( @@ -560,7 +560,7 @@ class TestRuleCRUD: # --------------------------------------------------------------------------- -# Rule-list analytics — pending_count per rule + org totals, read from the +# Rule-list analytics: pending_count per rule + org totals, read from the # Approval log (state="pending"). # --------------------------------------------------------------------------- diff --git a/backend/cases/tests/test_case_access_and_sla.py b/backend/cases/tests/test_case_access_and_sla.py index 9ada4e05..380648a0 100644 --- a/backend/cases/tests/test_case_access_and_sla.py +++ b/backend/cases/tests/test_case_access_and_sla.py @@ -12,25 +12,25 @@ answered the wrong thing. What was broken, in the order the classes appear: 2. The watcher allowance was half-built. `CaseListView` and `watcher_views` granted it with a citation; the detail view dropped it. A watcher's queue showed the ticket and opening it answered 403. -3. The verbs disagreed by accident — read, write and delete each had a +3. The verbs disagreed by accident: read, write and delete each had a different idea of who was allowed, spread over five copies. 4. A missing case was a **500** on PUT / PATCH / DELETE (only GET checked for `None`), and a malformed id was a 500 on all four plus the comment and attachment endpoints, because a UUID column raises rather than returning nothing. 5. `CaseAttachmentView.delete` looked its row up by pk with **no org - filter** — one endpoint that deleted any attachment in the database — + filter**, one endpoint that deleted any attachment in the database, and its permission branch compared a `Profile` to a `User` FK, so the person who uploaded a file could not delete it. 6. `account` accepted **another org's account** on create, stored the link, and echoed that account's name, email, phone and website back through the nested serializer. 7. The close gate did not exist in practice. `Case.clean()` requires a - `closed_on` and, where an active rule matches, a recorded approval — but + `closed_on` and, where an active rule matches, a recorded approval, but DRF never calls `Model.clean()`. With a matching rule armed, `PATCH {"status": "Closed"}` returned 200 and recorded zero approvals. 8. `resolved_at` was written by exactly one code path in the repo - (`close_with_children`), so an ordinarily-closed case stayed NULL — and + (`close_with_children`), so an ordinarily-closed case stayed NULL, and the property reads NULL as *not resolved*, leaving it permanently "resolution breached" and invisible to MTTR. 9. `first_response_at` was written by **no** code path at all, while four @@ -64,7 +64,7 @@ def _created_by(instance, user): """Set `created_by` after the fact. `BaseModel.save()` takes `created_by` from the crum thread-local and sets - it to **None** when there is no request in flight — so passing it to + it to **None** when there is no request in flight, so passing it to `objects.create()` inside a test silently stores nothing. `.update()` writes the column directly and skips that. """ @@ -92,7 +92,7 @@ def assigned_case(case_a, user_profile): @pytest.fixture def watched_case(case_a, user_profile, org_a): - """A case the non-admin only watches — not creator, not assignee.""" + """A case the non-admin only watches, not creator, not assignee.""" CaseWatcher.objects.create( case=case_a, profile=user_profile, org=org_a, subscribed_via="manual" ) @@ -113,7 +113,7 @@ class TestWhoMayOpenACase: assert response.json()["cases_obj"]["id"] == str(case_a.id) def test_assignee_opens_a_case_they_did_not_raise(self, user_client, assigned_case): - """Also a 500 — every non-admin hit the same line.""" + """Also a 500. Every non-admin hit the same line.""" response = user_client.get(_detail(assigned_case.id)) assert response.status_code == status.HTTP_200_OK @@ -132,7 +132,7 @@ class TestWhoMayOpenACase: assert response.status_code == status.HTTP_403_FORBIDDEN def test_another_org_gets_404_not_403(self, org_b_client, case_a): - """404, not 403 — confirming a record exists is itself a disclosure.""" + """404, not 403. Confirming a record exists is itself a disclosure.""" response = org_b_client.get(_detail(case_a.id)) assert response.status_code == status.HTTP_404_NOT_FOUND @@ -368,7 +368,7 @@ class TestClosingACase: self, admin_client, case_a, org_a ): """The rule matches on priority, so it is evaluated against the - incoming values — not the stored ones a caller is about to replace.""" + incoming values, not the stored ones a caller is about to replace.""" case_a.priority = "High" case_a.save() ApprovalRule.objects.create( @@ -494,7 +494,7 @@ class TestFirstResponseIsRecorded: @pytest.mark.django_db class TestListOrderAndMentions: def test_newest_first(self, admin_client, case_a, case_b_same_org): - """Ordering was `-id` — a random UUID — under a header that said + """Ordering was `-id`, a random UUID, under a header that said the newest were on top.""" Case.objects.filter(pk=case_a.id).update( created_at=timezone.now() - timezone.timedelta(days=3) diff --git a/backend/cases/tests/test_case_merge.py b/backend/cases/tests/test_case_merge.py index d5355dc7..ab127e9d 100644 --- a/backend/cases/tests/test_case_merge.py +++ b/backend/cases/tests/test_case_merge.py @@ -50,7 +50,7 @@ def _make_case( """Create a Case with `created_by` set deterministically. `BaseModel.save()` reads `crum.get_current_user()` to populate - `created_by` — outside a request that's None, which would break the + `created_by`: outside a request that's None, which would break the permission tests. Set the current user explicitly for the duration of the create. """ @@ -206,8 +206,8 @@ class TestCaseMergePermissions: def test_creator_of_only_one_forbidden( self, user_client, regular_user, admin_user, org_a ): - a = _make_case(org_a, regular_user, name="A — user-created") - b = _make_case(org_a, admin_user, name="B — admin-created") + a = _make_case(org_a, regular_user, name="A. User-created") + b = _make_case(org_a, admin_user, name="B, admin-created") response = user_client.post(f"/api/cases/{a.id}/merge/{b.id}/") assert response.status_code == 403 diff --git a/backend/cases/tests/test_cases_api.py b/backend/cases/tests/test_cases_api.py index 36d874d9..6e5a0f6a 100644 --- a/backend/cases/tests/test_cases_api.py +++ b/backend/cases/tests/test_cases_api.py @@ -72,8 +72,8 @@ class TestCaseListView: def test_create_case_unauthenticated(self, unauthenticated_client): """Asserted `pytest.raises(PermissionDenied)`, which can never hold - through an APIClient — DRF renders an APIException as a response - rather than letting it escape — so this had always failed. It now + through an APIClient, DRF renders an APIException as a response + rather than letting it escape, so this had always failed. It now checks the response the client actually receives.""" response = unauthenticated_client.post( CASES_LIST_URL, @@ -1390,7 +1390,7 @@ class TestCaseModelMethods: def test_save_applies_priority_specific_sla_defaults(self, admin_user, org_a): """New case picks up the priority-specific SLA defaults via save(). - See cases/workflow.py — Urgent gets 1h first-response / 4h resolution. + See cases/workflow.py: Urgent gets 1h first-response / 4h resolution. Previously this logic was guarded by `not self.pk`, which never fired because BaseModel populates pk via uuid4 default; we now use _state.adding so the auto-set actually applies. diff --git a/backend/cases/tests/test_csat.py b/backend/cases/tests/test_csat.py index b0e0a9e2..e89401de 100644 --- a/backend/cases/tests/test_csat.py +++ b/backend/cases/tests/test_csat.py @@ -211,7 +211,7 @@ class TestPublicCsatPost: def test_edit_after_24h_locked(self, client, closed_case): survey, token = self._seed(closed_case) - # First submit — rewind responded_at past the edit window. + # First submit: rewind responded_at past the edit window. client.post( f"/api/public/csat/{token}/", data={"rating": 3}, @@ -259,7 +259,7 @@ class TestCsatAggregate: rating=rating, responded_at=timezone.now(), ) - # One un-responded survey — must not count. + # One un-responded survey, must not count. case = Case.objects.create( org=org_a, name="Cx", status="Closed", priority="Normal", closed_on=timezone.now().date(), diff --git a/backend/cases/tests/test_escalation.py b/backend/cases/tests/test_escalation.py index 9fdce185..ffd04b51 100644 --- a/backend/cases/tests/test_escalation.py +++ b/backend/cases/tests/test_escalation.py @@ -171,7 +171,7 @@ class TestEscalationPolicyAPI: def test_cross_org_isolation(self, admin_client, org_a, org_b): target_b = _make_target_profile(org_b, "b@x.com") _make_policy(org_b, target_b, priority="Urgent") - # admin_client has org_a — should see no policies + # admin_client has org_a, should see no policies assert admin_client.get(POLICIES_URL).json()["policies"] == [] @@ -237,7 +237,7 @@ class TestScanForBreachedCases: _create_breached_case(admin_user, org_a) scan_for_breached_cases() - # Second pass within cooldown — no new activity + # Second pass within cooldown, no new activity assert scan_for_breached_cases() == 0 assert Activity.objects.filter(action="ESCALATED").count() == 1 @@ -284,7 +284,7 @@ class TestScanForBreachedCases: ) _create_breached_case(admin_user, org_a) scan_for_breached_cases() - # Look at any of the .delay calls — recipients should include both ids + # Look at any of the .delay calls. Recipients should include both ids called_recipients = [] for call in mock_email.delay.call_args_list: called_recipients.extend(call.args[0]) @@ -320,7 +320,7 @@ class TestScanForBreachedCases: # --------------------------------------------------------------------------- -# breaches_last_30d — the analytics the escalation page reads (Urgent SLAs: +# breaches_last_30d. The analytics the escalation page reads (Urgent SLAs: # 1h first response, 4h resolution). # --------------------------------------------------------------------------- @@ -419,7 +419,7 @@ class TestEscalationBreachCounts: def test_dead_policy_still_reports_breaches(self, admin_client, admin_user, org_a): """A disabled policy fires no ESCALATED activity, but its breaches must - still surface — that is the whole reason the page exists.""" + still surface. That is the whole reason the page exists.""" target = _make_target_profile(org_a) _make_policy(org_a, target, priority="Urgent", is_active=False) _case_with_sla(admin_user, org_a, created_ago=timedelta(hours=5)) diff --git a/backend/cases/tests/test_import.py b/backend/cases/tests/test_import.py index 9da12a5f..871bf043 100644 --- a/backend/cases/tests/test_import.py +++ b/backend/cases/tests/test_import.py @@ -134,7 +134,7 @@ class TestImportPreview: def test_cross_org_account_rejected( self, admin_client, admin_profile, org_b, user_b ): - # Account belongs to org_b but admin is in org_a — must not resolve. + # Account belongs to org_b but admin is in org_a, must not resolve. Account.objects.create(name="Secret Co", org=org_b, created_by=user_b) csv_file = _csv( ["name", "status", "priority", "account_name"], @@ -199,7 +199,7 @@ class TestImportCommit: assert case.tags.filter(name="auth").exists() def test_rolls_back_on_any_error(self, admin_client, org_a, admin_profile): - # Mix one good row with one invalid row — neither should be created. + # Mix one good row with one invalid row. Neither should be created. csv_file = _csv( ["name", "status", "priority"], [ diff --git a/backend/cases/tests/test_inbound_email.py b/backend/cases/tests/test_inbound_email.py index ff35869e..e369d50f 100644 --- a/backend/cases/tests/test_inbound_email.py +++ b/backend/cases/tests/test_inbound_email.py @@ -258,7 +258,7 @@ class TestThreading: org=org_a, created_by=admin_user, ) - # Subject identical but no `[Case #...]` marker — must not match. + # Subject identical but no `[Case #...]` marker, must not match. parsed = parse_raw_email(_raw_email(message_id="