estelam ticket, varizha , others

This commit is contained in:
javad committed 2026-10-13 15:48:24 +03:30
1 parent 35a1ec1794
commit 6b546aad76
71 files changed
+2865 -191

No files matched your search

+103
View File
@@ -0,0 +1,103 @@
from concurrent.futures import ThreadPoolExecutor
from threading import Barrier
import pytest
from django.db import connection, connections
from django.db.migrations.executor import MigrationExecutor
from apps.accounts.models import User
from apps.messaging.models import Message
from apps.notifications.dispatch_opening import ensure_opening_template
from apps.notifications.models import NotificationJob
from apps.tickets.dispatch_responses import OPENING_EVENT, open_dispatch_responses, render_opening_sms
from apps.tickets.models import DispatchResponseControl, GroupDispatch
from apps.tickets.services import reply_ticket
from tests.test_dispatch_response_opening import opening_case
pytestmark = pytest.mark.django_db(transaction=True)
def _postgres_only():
if connection.vendor != "postgresql":
pytest.skip("Real row-lock concurrency requires PostgreSQL")
def test_two_simultaneous_openings_commit_one_event_and_one_outbox_job(opening_case):
_postgres_only()
c = opening_case
ensure_opening_template()
text = render_opening_sms(c["dispatch"])
barrier = Barrier(2)
def open_once(user_id):
connections.close_all()
try:
user = User.objects.get(pk=user_id)
dispatch = GroupDispatch.objects.get(pk=c["dispatch"].pk)
barrier.wait(timeout=10)
return open_dispatch_responses(dispatch, user, text)[1]
finally:
connections.close_all()
with ThreadPoolExecutor(max_workers=2) as executor:
futures = [executor.submit(open_once, c[actor].pk) for actor in ("staff", "admin")]
assert sorted(future.result(timeout=20) for future in futures) == [False, True]
assert NotificationJob.objects.filter(event_type=OPENING_EVENT).count() == 1
assert DispatchResponseControl.objects.get(dispatch=c["dispatch"]).opened_at
def test_reply_racing_opening_matches_the_atomic_recipient_snapshot(opening_case):
_postgres_only()
c = opening_case
ensure_opening_template()
text = render_opening_sms(c["dispatch"])
barrier = Barrier(2)
def run(action):
connections.close_all()
try:
barrier.wait(timeout=10)
if action == "reply":
return reply_ticket(ticket_id=c["tickets"][1].pk, user=User.objects.get(pk=c["customers"][1].pk), body="پاسخ هم‌زمان")
return open_dispatch_responses(c["dispatch"], User.objects.get(pk=c["staff"].pk), text)
finally:
connections.close_all()
with ThreadPoolExecutor(max_workers=2) as executor:
futures = [executor.submit(run, action) for action in ("reply", "open")]
for future in futures:
future.result(timeout=20)
control = DispatchResponseControl.objects.get(dispatch=c["dispatch"])
expected = set(Message.objects.filter(ticket__group_dispatch=c["dispatch"], author_id__in=[u.pk for u in c["customers"]], created_at__lte=control.opened_at).values_list("author_id", flat=True))
assert set(control.respondents_snapshot) == {str(value) for value in User.objects.filter(pk__in=expected).values_list("public_id", flat=True)}
assert set(NotificationJob.objects.filter(event_type=OPENING_EVENT).values_list("customer_id", flat=True)) == expected
def test_additive_migrations_preserve_existing_rows_and_legacy_response_visibility(opening_case):
c = opening_case
# Turn this fixture into a pre-feature dispatch before moving the test schema back.
DispatchResponseControl.objects.filter(dispatch=c["dispatch"]).delete()
before_dispatch = list(GroupDispatch.objects.values())
before_messages = list(Message.objects.values())
before_users = list(User.objects.values())
old_targets = [("tickets", "0017_ticketaccessgrant_first_viewed_at_and_more"), ("notifications", "0020_receipt_rejection_template")]
executor = MigrationExecutor(connection)
current_targets = executor.loader.graph.leaf_nodes()
try:
executor.migrate(old_targets)
MigrationExecutor(connection).migrate(current_targets)
assert list(GroupDispatch.objects.values()) == before_dispatch
assert list(Message.objects.values()) == before_messages
assert list(User.objects.values()) == before_users
assert not DispatchResponseControl.objects.filter(dispatch=c["dispatch"]).exists()
from apps.tickets.staff_conversation import visible_messages
assert visible_messages(c["tickets"][0], c["staff"]).filter(pk=c["message"].pk).exists()
finally:
MigrationExecutor(connection).migrate(current_targets)
def test_migration_refuses_to_drop_response_confidentiality_after_use(opening_case):
from django.apps import apps
from django.db.migrations.exceptions import IrreversibleError
from types import SimpleNamespace
import importlib
guard = importlib.import_module("apps.tickets.migrations.0018_dispatch_response_opening").preserve_used_response_controls
with pytest.raises(IrreversibleError):
guard(apps, SimpleNamespace(connection=connection))
assert DispatchResponseControl.objects.filter(dispatch=opening_case["dispatch"]).exists()