Skip to content

Commit 0001c6d

Browse files
committed
type fix
1 parent d729c94 commit 0001c6d

File tree

4 files changed

+82
-169
lines changed

4 files changed

+82
-169
lines changed

posthog/models/person/person.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -572,6 +572,9 @@ def get_distinct_ids_for_subquery(person: Person | None, team: Team) -> list[str
572572
last_ids_limit = MAX_LIMIT_DISTINCT_IDS - first_ids_limit
573573

574574
if person is not None:
575+
# When a Person comes from personhog (via proto_person_to_model), distinct IDs
576+
# are already populated on _distinct_ids — use them directly to avoid hitting
577+
# the Django ORM below, which would defeat the purpose of the personhog path.
575578
if hasattr(person, "_distinct_ids") and person._distinct_ids is not None:
576579
ids = person._distinct_ids
577580
if len(ids) <= MAX_LIMIT_DISTINCT_IDS:

posthog/personhog_client/test_helpers.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ class PersonhogTestMixin:
4141
"""
4242

4343
personhog: bool = False
44+
_personhog_cm: Any = None
4445
_fake_client: FakePersonHogClient | None = None
4546

4647
def setUp(self) -> None:

products/conversations/backend/api/tests/test_tickets.py

Lines changed: 78 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@
66
from django.db import transaction
77
from django.utils import timezone
88

9-
from parameterized import parameterized
9+
from parameterized import parameterized, parameterized_class
1010
from rest_framework import status
1111

1212
from posthog.models import ActivityLog, Comment, Organization, User
1313
from posthog.models.person import Person
14+
from posthog.personhog_client.test_helpers import PersonhogTestMixin
1415

1516
from products.conversations.backend.models import Ticket, TicketAssignment
1617
from products.conversations.backend.models.constants import Channel, ChannelDetail, Priority, Status
@@ -99,61 +100,6 @@ def test_retrieve_ticket_marks_as_read(self, mock_on_commit):
99100
self.ticket.refresh_from_db()
100101
self.assertEqual(self.ticket.unread_team_count, 0)
101102

102-
def test_retrieve_ticket_includes_person_data(self, mock_on_commit):
103-
"""Test that retrieve includes person data when person exists."""
104-
person = Person.objects.create(
105-
team=self.team,
106-
distinct_ids=["user-123", "user@example.com", "another-id"],
107-
properties={"email": "test@example.com", "name": "Test User"},
108-
)
109-
110-
response = self.client.get(f"/api/projects/{self.team.id}/conversations/tickets/{self.ticket.id}/")
111-
self.assertEqual(response.status_code, status.HTTP_200_OK)
112-
self.assertIn("person", response.json())
113-
self.assertIsNotNone(response.json()["person"])
114-
self.assertEqual(response.json()["person"]["id"], str(person.uuid))
115-
self.assertEqual(response.json()["person"]["properties"]["email"], "test@example.com")
116-
# Verify all distinct_ids are returned, not just the ticket's
117-
self.assertCountEqual(
118-
response.json()["person"]["distinct_ids"],
119-
["user-123", "user@example.com", "another-id"],
120-
)
121-
122-
def test_retrieve_ticket_person_null_when_no_person(self, mock_on_commit):
123-
"""Test that person is null when no person exists for distinct_id."""
124-
response = self.client.get(f"/api/projects/{self.team.id}/conversations/tickets/{self.ticket.id}/")
125-
self.assertEqual(response.status_code, status.HTTP_200_OK)
126-
self.assertIn("person", response.json())
127-
self.assertIsNone(response.json()["person"])
128-
129-
def test_list_tickets_includes_person_data(self, mock_on_commit):
130-
"""Test that list includes person data for tickets with persons."""
131-
Person.objects.create(
132-
team=self.team,
133-
distinct_ids=["user-123", "user@example.com"],
134-
properties={"email": "test@example.com"},
135-
)
136-
137-
response = self.client.get(f"/api/projects/{self.team.id}/conversations/tickets/")
138-
self.assertEqual(response.status_code, status.HTTP_200_OK)
139-
self.assertEqual(response.json()["count"], 1)
140-
self.assertIn("person", response.json()["results"][0])
141-
self.assertIsNotNone(response.json()["results"][0]["person"])
142-
self.assertEqual(response.json()["results"][0]["person"]["properties"]["email"], "test@example.com")
143-
# Verify all distinct_ids are returned, not just the ticket's
144-
self.assertCountEqual(
145-
response.json()["results"][0]["person"]["distinct_ids"],
146-
["user-123", "user@example.com"],
147-
)
148-
149-
def test_list_tickets_person_null_when_no_person(self, mock_on_commit):
150-
"""Test that person is null in list when no person exists."""
151-
response = self.client.get(f"/api/projects/{self.team.id}/conversations/tickets/")
152-
self.assertEqual(response.status_code, status.HTTP_200_OK)
153-
self.assertEqual(response.json()["count"], 1)
154-
self.assertIn("person", response.json()["results"][0])
155-
self.assertIsNone(response.json()["results"][0]["person"])
156-
157103
def test_retrieve_ticket_includes_anonymous_traits(self, mock_on_commit):
158104
"""Test that retrieve includes anonymous_traits."""
159105
self.ticket.anonymous_traits = {"name": "John Doe", "email": "john@example.com"}
@@ -177,21 +123,6 @@ def test_list_tickets_includes_anonymous_traits(self, mock_on_commit):
177123
self.assertEqual(response.json()["results"][0]["anonymous_traits"]["name"], "Jane Doe")
178124
self.assertEqual(response.json()["results"][0]["anonymous_traits"]["company"], "ACME")
179125

180-
def test_person_data_scoped_to_team(self, mock_on_commit):
181-
"""Test that person lookup is scoped to team (no cross-team leakage)."""
182-
# Create person in different team
183-
other_team = self.organization.teams.create(name="Other Team")
184-
Person.objects.create(
185-
team=other_team,
186-
distinct_ids=["user-123"], # Same distinct_id as ticket
187-
properties={"email": "other@example.com"},
188-
)
189-
190-
response = self.client.get(f"/api/projects/{self.team.id}/conversations/tickets/{self.ticket.id}/")
191-
self.assertEqual(response.status_code, status.HTTP_200_OK)
192-
# Should be null because person is in different team
193-
self.assertIsNone(response.json()["person"])
194-
195126
def test_update_sla_due_at(self, mock_on_commit):
196127
sla_time = timezone.now() + timedelta(hours=5)
197128
response = self.client.patch(
@@ -1100,3 +1031,79 @@ def test_auto_increments_ticket_number(self):
11001031

11011032
self.assertEqual(ticket1.ticket_number, 1)
11021033
self.assertEqual(ticket2.ticket_number, 2)
1034+
1035+
1036+
@parameterized_class(("personhog",), [(False,), (True,)])
1037+
@patch.object(transaction, "on_commit", side_effect=immediate_on_commit)
1038+
class TestTicketPersonData(PersonhogTestMixin, APIBaseTest):
1039+
"""Tests that ticket person enrichment produces identical results
1040+
via the ORM and personhog paths."""
1041+
1042+
def setUp(self):
1043+
super().setUp()
1044+
self.ticket = Ticket.objects.create_with_number(
1045+
team=self.team,
1046+
channel_source=Channel.WIDGET,
1047+
widget_session_id="test-session-123",
1048+
distinct_id="user-123",
1049+
status=Status.NEW,
1050+
)
1051+
1052+
def test_retrieve_ticket_includes_person_data(self, mock_on_commit):
1053+
person = self._seed_person(
1054+
team=self.team,
1055+
distinct_ids=["user-123", "user@example.com", "another-id"],
1056+
properties={"email": "test@example.com", "name": "Test User"},
1057+
)
1058+
1059+
response = self.client.get(f"/api/projects/{self.team.id}/conversations/tickets/{self.ticket.id}/")
1060+
1061+
assert response.status_code == status.HTTP_200_OK
1062+
person_data = response.json()["person"]
1063+
assert person_data is not None
1064+
assert person_data["id"] == str(person.uuid)
1065+
assert person_data["properties"]["email"] == "test@example.com"
1066+
assert set(person_data["distinct_ids"]) == {"user-123", "user@example.com", "another-id"}
1067+
1068+
def test_retrieve_ticket_person_null_when_no_person(self, mock_on_commit):
1069+
response = self.client.get(f"/api/projects/{self.team.id}/conversations/tickets/{self.ticket.id}/")
1070+
1071+
assert response.status_code == status.HTTP_200_OK
1072+
assert response.json()["person"] is None
1073+
1074+
def test_list_tickets_includes_person_data(self, mock_on_commit):
1075+
self._seed_person(
1076+
team=self.team,
1077+
distinct_ids=["user-123", "user@example.com"],
1078+
properties={"email": "test@example.com"},
1079+
)
1080+
1081+
response = self.client.get(f"/api/projects/{self.team.id}/conversations/tickets/")
1082+
1083+
assert response.status_code == status.HTTP_200_OK
1084+
assert response.json()["count"] == 1
1085+
person_data = response.json()["results"][0]["person"]
1086+
assert person_data is not None
1087+
assert person_data["properties"]["email"] == "test@example.com"
1088+
assert set(person_data["distinct_ids"]) == {"user-123", "user@example.com"}
1089+
self._assert_personhog_called("get_persons_by_distinct_ids_in_team")
1090+
1091+
def test_list_tickets_person_null_when_no_person(self, mock_on_commit):
1092+
response = self.client.get(f"/api/projects/{self.team.id}/conversations/tickets/")
1093+
1094+
assert response.status_code == status.HTTP_200_OK
1095+
assert response.json()["count"] == 1
1096+
assert response.json()["results"][0]["person"] is None
1097+
1098+
def test_person_data_scoped_to_team(self, mock_on_commit):
1099+
other_team = self.organization.teams.create(name="Other Team")
1100+
self._seed_person(
1101+
team=other_team,
1102+
distinct_ids=["user-123"],
1103+
properties={"email": "other@example.com"},
1104+
)
1105+
1106+
response = self.client.get(f"/api/projects/{self.team.id}/conversations/tickets/{self.ticket.id}/")
1107+
1108+
assert response.status_code == status.HTTP_200_OK
1109+
assert response.json()["person"] is None

products/conversations/backend/api/tests/test_tickets_personhog.py

Lines changed: 0 additions & 98 deletions
This file was deleted.

0 commit comments

Comments
 (0)