mirror of
https://github.com/TronoSfera/Law.git
synced 2026-05-18 10:03:45 +03:00
737 lines
29 KiB
Python
737 lines
29 KiB
Python
import os
|
|
import unittest
|
|
from datetime import datetime, timedelta, timezone
|
|
from uuid import UUID
|
|
from unittest.mock import patch
|
|
|
|
from botocore.exceptions import ClientError
|
|
from fastapi.testclient import TestClient
|
|
from sqlalchemy import create_engine, delete, text
|
|
from sqlalchemy.orm import sessionmaker
|
|
from sqlalchemy.pool import StaticPool
|
|
|
|
os.environ.setdefault("DATABASE_URL", "sqlite+pysqlite:///:memory:")
|
|
os.environ.setdefault("REDIS_URL", "redis://localhost:6379/0")
|
|
os.environ.setdefault("S3_ENDPOINT", "http://localhost:9000")
|
|
os.environ.setdefault("S3_ACCESS_KEY", "test")
|
|
os.environ.setdefault("S3_SECRET_KEY", "test")
|
|
os.environ.setdefault("S3_BUCKET", "test")
|
|
|
|
from app.core.config import settings
|
|
from app.core.security import create_jwt
|
|
from app.db.session import get_db
|
|
from app.chat_main import app as chat_app
|
|
from app.main import app as main_app
|
|
from app.models.attachment import Attachment
|
|
from app.models.message import Message
|
|
from app.models.notification import Notification
|
|
from app.models.request import Request
|
|
from app.services.chat_crypto import decrypt_message_body_for_request
|
|
from app.models.request_data_requirement import RequestDataRequirement
|
|
from app.models.status_history import StatusHistory
|
|
from app.services.chat_presence import clear_presence_for_tests, set_typing_presence
|
|
|
|
|
|
class _FakeBody:
|
|
def __init__(self, payload: bytes):
|
|
self.payload = payload
|
|
|
|
def iter_chunks(self, chunk_size=65536):
|
|
for i in range(0, len(self.payload), chunk_size):
|
|
yield self.payload[i : i + chunk_size]
|
|
|
|
|
|
class _FakeS3Storage:
|
|
def __init__(self):
|
|
self.objects = {}
|
|
|
|
def create_presigned_put_url(self, key: str, mime_type: str):
|
|
return f"http://s3.local/{key}?mime={mime_type}"
|
|
|
|
def head_object(self, key: str) -> dict:
|
|
row = self.objects.get(key)
|
|
if row is None:
|
|
raise ClientError({"Error": {"Code": "404", "Message": "Not Found"}}, "HeadObject")
|
|
return {"ContentLength": row["size"]}
|
|
|
|
def get_object(self, key: str) -> dict:
|
|
row = self.objects.get(key)
|
|
if row is None:
|
|
raise ClientError({"Error": {"Code": "404", "Message": "Not Found"}}, "GetObject")
|
|
return {"Body": _FakeBody(row["content"]), "ContentType": row["mime"], "ContentLength": row["size"]}
|
|
|
|
|
|
class PublicCabinetTests(unittest.TestCase):
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
cls.engine = create_engine(
|
|
"sqlite+pysqlite:///:memory:",
|
|
connect_args={"check_same_thread": False},
|
|
poolclass=StaticPool,
|
|
)
|
|
cls.SessionLocal = sessionmaker(bind=cls.engine, autocommit=False, autoflush=False)
|
|
Request.__table__.create(bind=cls.engine)
|
|
Notification.__table__.create(bind=cls.engine)
|
|
Message.__table__.create(bind=cls.engine)
|
|
Attachment.__table__.create(bind=cls.engine)
|
|
RequestDataRequirement.__table__.create(bind=cls.engine)
|
|
StatusHistory.__table__.create(bind=cls.engine)
|
|
|
|
@classmethod
|
|
def tearDownClass(cls):
|
|
RequestDataRequirement.__table__.drop(bind=cls.engine)
|
|
StatusHistory.__table__.drop(bind=cls.engine)
|
|
Attachment.__table__.drop(bind=cls.engine)
|
|
Message.__table__.drop(bind=cls.engine)
|
|
Notification.__table__.drop(bind=cls.engine)
|
|
Request.__table__.drop(bind=cls.engine)
|
|
cls.engine.dispose()
|
|
|
|
def setUp(self):
|
|
clear_presence_for_tests()
|
|
with self.SessionLocal() as db:
|
|
db.execute(delete(Notification))
|
|
db.execute(delete(StatusHistory))
|
|
db.execute(delete(Attachment))
|
|
db.execute(delete(RequestDataRequirement))
|
|
db.execute(delete(Message))
|
|
db.execute(delete(Request))
|
|
db.commit()
|
|
|
|
def override_get_db():
|
|
db = self.SessionLocal()
|
|
try:
|
|
yield db
|
|
finally:
|
|
db.close()
|
|
|
|
main_app.dependency_overrides[get_db] = override_get_db
|
|
chat_app.dependency_overrides[get_db] = override_get_db
|
|
self.client = TestClient(main_app)
|
|
self.chat_client = TestClient(chat_app)
|
|
|
|
def tearDown(self):
|
|
self.chat_client.close()
|
|
self.client.close()
|
|
chat_app.dependency_overrides.clear()
|
|
main_app.dependency_overrides.clear()
|
|
clear_presence_for_tests()
|
|
|
|
@staticmethod
|
|
def _public_cookies(track_number: str) -> dict[str, str]:
|
|
token = create_jwt({"sub": track_number, "purpose": "VIEW_REQUEST"}, settings.PUBLIC_JWT_SECRET, timedelta(days=1))
|
|
return {settings.PUBLIC_COOKIE_NAME: token}
|
|
|
|
def test_cabinet_lists_messages_attachments_history_and_timeline(self):
|
|
with self.SessionLocal() as db:
|
|
req = Request(
|
|
track_number="TRK-CAB-001",
|
|
client_name="Тест Клиент",
|
|
client_phone="+79991110000",
|
|
topic_code="consulting",
|
|
status_code="IN_PROGRESS",
|
|
description="Проверка кабинета",
|
|
extra_fields={},
|
|
)
|
|
db.add(req)
|
|
db.commit()
|
|
db.refresh(req)
|
|
|
|
db.add(
|
|
Message(
|
|
request_id=req.id,
|
|
author_type="LAWYER",
|
|
author_name="Юрист",
|
|
body="Принял в работу.",
|
|
)
|
|
)
|
|
db.add(
|
|
Attachment(
|
|
request_id=req.id,
|
|
file_name="doc.pdf",
|
|
mime_type="application/pdf",
|
|
size_bytes=1234,
|
|
s3_key="requests/key/doc.pdf",
|
|
)
|
|
)
|
|
db.add(
|
|
StatusHistory(
|
|
request_id=req.id,
|
|
from_status="NEW",
|
|
to_status="IN_PROGRESS",
|
|
comment="Юрист взял заявку",
|
|
)
|
|
)
|
|
db.commit()
|
|
|
|
cookies = self._public_cookies("TRK-CAB-001")
|
|
messages = self.chat_client.get("/api/public/chat/requests/TRK-CAB-001/messages", cookies=cookies)
|
|
self.assertEqual(messages.status_code, 200)
|
|
self.assertEqual(len(messages.json()), 1)
|
|
self.assertEqual(messages.json()[0]["author_type"], "LAWYER")
|
|
|
|
attachments = self.client.get("/api/public/requests/TRK-CAB-001/attachments", cookies=cookies)
|
|
self.assertEqual(attachments.status_code, 200)
|
|
self.assertEqual(len(attachments.json()), 1)
|
|
self.assertIn("/api/public/uploads/object/", attachments.json()[0]["download_url"])
|
|
|
|
history = self.client.get("/api/public/requests/TRK-CAB-001/history", cookies=cookies)
|
|
self.assertEqual(history.status_code, 200)
|
|
self.assertEqual(len(history.json()), 1)
|
|
self.assertEqual(history.json()[0]["to_status"], "IN_PROGRESS")
|
|
|
|
timeline = self.client.get("/api/public/requests/TRK-CAB-001/timeline", cookies=cookies)
|
|
self.assertEqual(timeline.status_code, 200)
|
|
events = timeline.json()
|
|
self.assertEqual(len(events), 3)
|
|
self.assertEqual({event["type"] for event in events}, {"status_change", "message", "attachment"})
|
|
|
|
def test_client_can_create_message_in_public_cabinet(self):
|
|
with self.SessionLocal() as db:
|
|
req = Request(
|
|
track_number="TRK-CAB-MSG",
|
|
client_name="Клиент Сообщение",
|
|
client_phone="+79992220000",
|
|
topic_code="consulting",
|
|
status_code="NEW",
|
|
description="Проверка отправки",
|
|
extra_fields={},
|
|
)
|
|
db.add(req)
|
|
db.commit()
|
|
request_id = req.id
|
|
|
|
cookies = self._public_cookies("TRK-CAB-MSG")
|
|
created = self.chat_client.post(
|
|
"/api/public/chat/requests/TRK-CAB-MSG/messages",
|
|
cookies=cookies,
|
|
json={"body": "Добрый день, есть вопрос по документам."},
|
|
)
|
|
self.assertEqual(created.status_code, 201)
|
|
self.assertEqual(created.json().get("message_kind"), "TEXT")
|
|
self.assertEqual(created.json().get("request_data_items"), [])
|
|
self.assertFalse(bool(created.json().get("request_data_all_filled")))
|
|
message_id = UUID(created.json()["id"])
|
|
|
|
with self.SessionLocal() as db:
|
|
row = db.get(Message, message_id)
|
|
self.assertIsNotNone(row)
|
|
self.assertEqual(row.request_id, request_id)
|
|
self.assertEqual(row.author_type, "CLIENT")
|
|
req = db.get(Request, request_id)
|
|
self.assertIsNotNone(req)
|
|
self.assertEqual(
|
|
decrypt_message_body_for_request(row.body, request_extra_fields=req.extra_fields),
|
|
"Добрый день, есть вопрос по документам.",
|
|
)
|
|
self.assertEqual(req.responsible, "Клиент")
|
|
self.assertTrue(req.lawyer_has_unread_updates)
|
|
self.assertEqual(req.lawyer_unread_event_type, "MESSAGE")
|
|
|
|
def test_legacy_public_request_messages_routes_are_not_exposed(self):
|
|
with self.SessionLocal() as db:
|
|
req = Request(
|
|
track_number="TRK-CAB-LEGACY",
|
|
client_name="Клиент Legacy Route",
|
|
client_phone="+79992220001",
|
|
topic_code="consulting",
|
|
status_code="NEW",
|
|
description="Проверка отсутствия legacy chat route",
|
|
extra_fields={},
|
|
)
|
|
db.add(req)
|
|
db.commit()
|
|
|
|
cookies = self._public_cookies("TRK-CAB-LEGACY")
|
|
listed = self.client.get("/api/public/requests/TRK-CAB-LEGACY/messages", cookies=cookies)
|
|
self.assertEqual(listed.status_code, 404)
|
|
|
|
created = self.client.post(
|
|
"/api/public/requests/TRK-CAB-LEGACY/messages",
|
|
cookies=cookies,
|
|
json={"body": "Legacy endpoint"},
|
|
)
|
|
self.assertEqual(created.status_code, 404)
|
|
|
|
def test_public_chat_service_endpoints_work_for_authorized_client(self):
|
|
with self.SessionLocal() as db:
|
|
req = Request(
|
|
track_number="TRK-CHAT-001",
|
|
client_name="Клиент Чат",
|
|
client_phone="+79997770000",
|
|
topic_code="consulting",
|
|
status_code="NEW",
|
|
description="Проверка chat service",
|
|
extra_fields={},
|
|
)
|
|
db.add(req)
|
|
db.commit()
|
|
|
|
cookies = self._public_cookies("TRK-CHAT-001")
|
|
created = self.chat_client.post(
|
|
"/api/public/chat/requests/TRK-CHAT-001/messages",
|
|
cookies=cookies,
|
|
json={"body": "Сообщение через выделенный сервис"},
|
|
)
|
|
self.assertEqual(created.status_code, 201)
|
|
self.assertEqual(created.json()["author_type"], "CLIENT")
|
|
|
|
listed = self.chat_client.get("/api/public/chat/requests/TRK-CHAT-001/messages", cookies=cookies)
|
|
self.assertEqual(listed.status_code, 200)
|
|
self.assertEqual(len(listed.json()), 1)
|
|
self.assertIn("выделенный сервис", listed.json()[0]["body"])
|
|
|
|
for index in range(4):
|
|
created_extra = self.chat_client.post(
|
|
"/api/public/chat/requests/TRK-CHAT-001/messages",
|
|
cookies=cookies,
|
|
json={"body": f"Сообщение {index}"},
|
|
)
|
|
self.assertEqual(created_extra.status_code, 201)
|
|
|
|
listed_window = self.chat_client.get(
|
|
"/api/public/chat/requests/TRK-CHAT-001/messages-window",
|
|
cookies=cookies,
|
|
params={"limit": 2, "include_body": "false"},
|
|
)
|
|
self.assertEqual(listed_window.status_code, 200)
|
|
window_payload = listed_window.json()
|
|
self.assertEqual(len(window_payload.get("rows") or []), 2)
|
|
self.assertTrue(bool(window_payload.get("has_more")))
|
|
self.assertTrue(all(item.get("body_loaded") is False for item in (window_payload.get("rows") or [])))
|
|
|
|
body_batch = self.chat_client.post(
|
|
"/api/public/chat/requests/TRK-CHAT-001/message-bodies",
|
|
cookies=cookies,
|
|
json={"ids": [item["id"] for item in (window_payload.get("rows") or [])]},
|
|
)
|
|
self.assertEqual(body_batch.status_code, 200)
|
|
self.assertEqual(len(body_batch.json().get("rows") or []), 2)
|
|
self.assertTrue(all(item.get("body_loaded") for item in (body_batch.json().get("rows") or [])))
|
|
|
|
denied = self.chat_client.get("/api/public/chat/requests/TRK-CHAT-001/messages", cookies=self._public_cookies("TRK-OTHER"))
|
|
self.assertEqual(denied.status_code, 404)
|
|
|
|
denied_batch = self.chat_client.post(
|
|
"/api/public/chat/requests/TRK-CHAT-001/message-bodies",
|
|
cookies=self._public_cookies("TRK-OTHER"),
|
|
json={"ids": [item["id"] for item in (window_payload.get("rows") or [])]},
|
|
)
|
|
self.assertEqual(denied_batch.status_code, 404)
|
|
|
|
def test_public_chat_marks_delivery_and_read_receipts_for_staff_messages(self):
|
|
with self.SessionLocal() as db:
|
|
req = Request(
|
|
track_number="TRK-CHAT-RECEIPTS-CLIENT",
|
|
client_name="Клиент Чат Receipt",
|
|
client_phone="+79997774411",
|
|
topic_code="consulting",
|
|
status_code="IN_PROGRESS",
|
|
description="Проверка delivered/read для клиента",
|
|
extra_fields={},
|
|
)
|
|
db.add(req)
|
|
db.flush()
|
|
msg = Message(
|
|
request_id=req.id,
|
|
author_type="LAWYER",
|
|
author_name="Юрист",
|
|
body="Проверка receipt",
|
|
)
|
|
db.add(msg)
|
|
db.commit()
|
|
message_id = msg.id
|
|
|
|
cookies = self._public_cookies("TRK-CHAT-RECEIPTS-CLIENT")
|
|
live = self.chat_client.get("/api/public/chat/requests/TRK-CHAT-RECEIPTS-CLIENT/live", cookies=cookies)
|
|
self.assertEqual(live.status_code, 200)
|
|
|
|
with self.SessionLocal() as db:
|
|
delivered_row = db.get(Message, message_id)
|
|
self.assertIsNotNone(delivered_row)
|
|
self.assertIsNotNone(delivered_row.delivered_to_client_at)
|
|
self.assertIsNone(delivered_row.read_by_client_at)
|
|
|
|
listed = self.chat_client.get("/api/public/chat/requests/TRK-CHAT-RECEIPTS-CLIENT/messages", cookies=cookies)
|
|
self.assertEqual(listed.status_code, 200)
|
|
self.assertEqual(len(listed.json()), 1)
|
|
self.assertTrue(bool(listed.json()[0].get("delivered_to_client_at")))
|
|
self.assertTrue(bool(listed.json()[0].get("read_by_client_at")))
|
|
|
|
with self.SessionLocal() as db:
|
|
read_row = db.get(Message, message_id)
|
|
self.assertIsNotNone(read_row)
|
|
self.assertIsNotNone(read_row.read_by_client_at)
|
|
|
|
def test_chat_message_is_encrypted_at_rest(self):
|
|
with self.SessionLocal() as db:
|
|
req = Request(
|
|
track_number="TRK-CHAT-ENC",
|
|
client_name="Клиент Шифрование",
|
|
client_phone="+79997779999",
|
|
topic_code="consulting",
|
|
status_code="NEW",
|
|
description="Проверка шифрования чата",
|
|
extra_fields={},
|
|
)
|
|
db.add(req)
|
|
db.commit()
|
|
|
|
payload_body = "Секретное сообщение клиента"
|
|
cookies = self._public_cookies("TRK-CHAT-ENC")
|
|
created = self.chat_client.post(
|
|
"/api/public/chat/requests/TRK-CHAT-ENC/messages",
|
|
cookies=cookies,
|
|
json={"body": payload_body},
|
|
)
|
|
self.assertEqual(created.status_code, 201)
|
|
|
|
with self.SessionLocal() as db:
|
|
raw_encrypted = db.execute(text("SELECT body FROM messages ORDER BY created_at DESC LIMIT 1")).scalar_one()
|
|
self.assertTrue(str(raw_encrypted).startswith("chatenc:v3:"))
|
|
self.assertNotEqual(str(raw_encrypted), payload_body)
|
|
request_row = db.query(Request).filter(Request.track_number == "TRK-CHAT-ENC").first()
|
|
self.assertIsNotNone(request_row)
|
|
self.assertTrue(bool((request_row.extra_fields or {}).get("chat_crypto")))
|
|
|
|
listed = self.chat_client.get("/api/public/chat/requests/TRK-CHAT-ENC/messages", cookies=cookies)
|
|
self.assertEqual(listed.status_code, 200)
|
|
self.assertEqual(listed.json()[0]["body"], payload_body)
|
|
|
|
def test_chat_supports_legacy_plaintext_rows(self):
|
|
with self.SessionLocal() as db:
|
|
req = Request(
|
|
track_number="TRK-CHAT-LEGACY",
|
|
client_name="Клиент Legacy",
|
|
client_phone="+79997778888",
|
|
topic_code="consulting",
|
|
status_code="NEW",
|
|
description="Проверка legacy формата",
|
|
extra_fields={},
|
|
)
|
|
db.add(req)
|
|
db.flush()
|
|
message = Message(
|
|
request_id=req.id,
|
|
author_type="LAWYER",
|
|
author_name="Юрист",
|
|
body="legacy placeholder",
|
|
)
|
|
db.add(message)
|
|
db.flush()
|
|
db.execute(
|
|
text("UPDATE messages SET body = :body WHERE rowid = (SELECT rowid FROM messages ORDER BY created_at DESC LIMIT 1)"),
|
|
{"body": "LEGACY_PLAINTEXT_MESSAGE"},
|
|
)
|
|
db.commit()
|
|
|
|
cookies = self._public_cookies("TRK-CHAT-LEGACY")
|
|
listed = self.chat_client.get("/api/public/chat/requests/TRK-CHAT-LEGACY/messages", cookies=cookies)
|
|
self.assertEqual(listed.status_code, 200)
|
|
self.assertEqual(len(listed.json()), 1)
|
|
self.assertEqual(listed.json()[0]["body"], "LEGACY_PLAINTEXT_MESSAGE")
|
|
|
|
def test_public_live_endpoint_and_typing_state(self):
|
|
with self.SessionLocal() as db:
|
|
req = Request(
|
|
track_number="TRK-LIVE-001",
|
|
client_name="Клиент Live",
|
|
client_phone="+79997771234",
|
|
topic_code="consulting",
|
|
status_code="NEW",
|
|
description="Проверка live",
|
|
extra_fields={},
|
|
)
|
|
db.add(req)
|
|
db.flush()
|
|
db.add(
|
|
Message(
|
|
request_id=req.id,
|
|
author_type="LAWYER",
|
|
author_name="Юрист",
|
|
body="Первое сообщение",
|
|
)
|
|
)
|
|
db.commit()
|
|
request_id = str(req.id)
|
|
|
|
cookies = self._public_cookies("TRK-LIVE-001")
|
|
live_initial = self.chat_client.get("/api/public/chat/requests/TRK-LIVE-001/live", cookies=cookies)
|
|
self.assertEqual(live_initial.status_code, 200)
|
|
live_body = live_initial.json()
|
|
self.assertTrue(bool(live_body.get("has_updates")))
|
|
self.assertTrue(bool(live_body.get("cursor")))
|
|
|
|
set_typing_presence(
|
|
request_key=request_id,
|
|
actor_key="LAWYER:test",
|
|
actor_label="Юрист Тест",
|
|
actor_role="LAWYER",
|
|
typing=True,
|
|
)
|
|
live_with_typing = self.chat_client.get("/api/public/chat/requests/TRK-LIVE-001/live", cookies=cookies)
|
|
self.assertEqual(live_with_typing.status_code, 200)
|
|
typing_rows = live_with_typing.json().get("typing") or []
|
|
self.assertTrue(any(str(item.get("actor_label")) == "Юрист Тест" for item in typing_rows))
|
|
|
|
current_cursor = str(live_with_typing.json().get("cursor") or "")
|
|
live_no_delta = self.chat_client.get(
|
|
"/api/public/chat/requests/TRK-LIVE-001/live",
|
|
params={"cursor": current_cursor},
|
|
cookies=cookies,
|
|
)
|
|
self.assertEqual(live_no_delta.status_code, 200)
|
|
self.assertFalse(bool(live_no_delta.json().get("has_updates")))
|
|
|
|
with self.SessionLocal() as db:
|
|
req = db.query(Request).filter(Request.track_number == "TRK-LIVE-001").first()
|
|
self.assertIsNotNone(req)
|
|
live_message = Message(
|
|
request_id=req.id,
|
|
author_type="LAWYER",
|
|
author_name="Юрист",
|
|
body="Новое сообщение live",
|
|
)
|
|
db.add(live_message)
|
|
db.flush()
|
|
db.add(
|
|
Attachment(
|
|
request_id=req.id,
|
|
message_id=live_message.id,
|
|
file_name="live-public.pdf",
|
|
mime_type="application/pdf",
|
|
size_bytes=512,
|
|
s3_key=f"requests/{req.id}/live-public.pdf",
|
|
)
|
|
)
|
|
db.commit()
|
|
|
|
live_delta = self.chat_client.get(
|
|
"/api/public/chat/requests/TRK-LIVE-001/live",
|
|
params={"cursor": current_cursor},
|
|
cookies=cookies,
|
|
)
|
|
self.assertEqual(live_delta.status_code, 200)
|
|
self.assertTrue(bool(live_delta.json().get("has_updates")))
|
|
self.assertEqual(len(live_delta.json().get("messages") or []), 1)
|
|
self.assertEqual(len(live_delta.json().get("attachments") or []), 1)
|
|
|
|
typing_on = self.chat_client.post(
|
|
"/api/public/chat/requests/TRK-LIVE-001/typing",
|
|
cookies=cookies,
|
|
json={"typing": True},
|
|
)
|
|
self.assertEqual(typing_on.status_code, 200)
|
|
self.assertTrue(bool(typing_on.json().get("typing")))
|
|
|
|
def test_public_live_detects_filled_request_data_updates(self):
|
|
with self.SessionLocal() as db:
|
|
now = datetime.now(timezone.utc)
|
|
req = Request(
|
|
track_number="TRK-LIVE-DATA-001",
|
|
client_name="Клиент Live Data",
|
|
client_phone="+79997771235",
|
|
topic_code="consulting",
|
|
status_code="IN_PROGRESS",
|
|
description="Проверка live по допданным",
|
|
extra_fields={},
|
|
)
|
|
db.add(req)
|
|
db.flush()
|
|
msg = Message(
|
|
request_id=req.id,
|
|
author_type="LAWYER",
|
|
author_name="Юрист",
|
|
body="Запрос",
|
|
created_at=now - timedelta(minutes=2),
|
|
updated_at=now - timedelta(minutes=2),
|
|
)
|
|
db.add(msg)
|
|
db.flush()
|
|
row = RequestDataRequirement(
|
|
request_id=req.id,
|
|
request_message_id=msg.id,
|
|
key="passport_series",
|
|
label="Серия паспорта",
|
|
field_type="text",
|
|
required=True,
|
|
sort_order=0,
|
|
)
|
|
db.add(row)
|
|
db.commit()
|
|
message_id = str(msg.id)
|
|
row_id = str(row.id)
|
|
|
|
cookies = self._public_cookies("TRK-LIVE-DATA-001")
|
|
live_initial = self.chat_client.get("/api/public/chat/requests/TRK-LIVE-DATA-001/live", cookies=cookies)
|
|
self.assertEqual(live_initial.status_code, 200)
|
|
cursor = str(live_initial.json().get("cursor") or "")
|
|
self.assertTrue(bool(cursor))
|
|
|
|
live_no_delta = self.chat_client.get(
|
|
"/api/public/chat/requests/TRK-LIVE-DATA-001/live",
|
|
params={"cursor": cursor},
|
|
cookies=cookies,
|
|
)
|
|
self.assertEqual(live_no_delta.status_code, 200)
|
|
self.assertFalse(bool(live_no_delta.json().get("has_updates")))
|
|
|
|
save_values = self.chat_client.post(
|
|
f"/api/public/chat/requests/TRK-LIVE-DATA-001/data-requests/{message_id}",
|
|
cookies=cookies,
|
|
json={"items": [{"id": row_id, "value_text": "1234"}]},
|
|
)
|
|
self.assertEqual(save_values.status_code, 200)
|
|
self.assertEqual(int(save_values.json().get("updated") or 0), 1)
|
|
|
|
live_after_fill = self.chat_client.get(
|
|
"/api/public/chat/requests/TRK-LIVE-DATA-001/live",
|
|
params={"cursor": cursor},
|
|
cookies=cookies,
|
|
)
|
|
self.assertEqual(live_after_fill.status_code, 200)
|
|
self.assertTrue(bool(live_after_fill.json().get("has_updates")))
|
|
|
|
def test_public_cabinet_respects_track_access(self):
|
|
with self.SessionLocal() as db:
|
|
req = Request(
|
|
track_number="TRK-REAL",
|
|
client_name="Клиент Ограничение",
|
|
client_phone="+79993330000",
|
|
topic_code="consulting",
|
|
status_code="NEW",
|
|
description="Проверка доступа",
|
|
extra_fields={},
|
|
)
|
|
db.add(req)
|
|
db.commit()
|
|
|
|
cookies = self._public_cookies("TRK-OTHER")
|
|
denied = self.chat_client.get("/api/public/chat/requests/TRK-REAL/messages", cookies=cookies)
|
|
self.assertEqual(denied.status_code, 404)
|
|
|
|
def test_public_attachment_download_requires_access(self):
|
|
fake_s3 = _FakeS3Storage()
|
|
with self.SessionLocal() as db:
|
|
req = Request(
|
|
track_number="TRK-FILE-1",
|
|
client_name="Клиент Файл",
|
|
client_phone="+79994440000",
|
|
topic_code="consulting",
|
|
status_code="NEW",
|
|
description="Файл",
|
|
extra_fields={},
|
|
)
|
|
db.add(req)
|
|
db.commit()
|
|
db.refresh(req)
|
|
|
|
att = Attachment(
|
|
request_id=req.id,
|
|
file_name="act.pdf",
|
|
mime_type="application/pdf",
|
|
size_bytes=4,
|
|
s3_key="requests/a/act.pdf",
|
|
)
|
|
db.add(att)
|
|
db.commit()
|
|
attachment_id = str(att.id)
|
|
|
|
fake_s3.objects["requests/a/act.pdf"] = {
|
|
"content": b"test",
|
|
"mime": "application/pdf",
|
|
"size": 4,
|
|
}
|
|
|
|
with patch("app.api.public.uploads.get_s3_storage", return_value=fake_s3):
|
|
allowed = self.client.get(
|
|
f"/api/public/uploads/object/{attachment_id}",
|
|
cookies=self._public_cookies("TRK-FILE-1"),
|
|
)
|
|
self.assertEqual(allowed.status_code, 200)
|
|
self.assertEqual(allowed.content, b"test")
|
|
|
|
denied = self.client.get(
|
|
f"/api/public/uploads/object/{attachment_id}",
|
|
cookies=self._public_cookies("TRK-OTHER"),
|
|
)
|
|
self.assertEqual(denied.status_code, 404)
|
|
|
|
def test_public_upload_complete_links_attachment_to_message_when_message_id_provided(self):
|
|
fake_s3 = _FakeS3Storage()
|
|
with self.SessionLocal() as db:
|
|
req = Request(
|
|
track_number="TRK-PUBLIC-UPL-1",
|
|
client_name="Клиент Файл Сообщение",
|
|
client_phone="+79995550000",
|
|
topic_code="consulting",
|
|
status_code="NEW",
|
|
description="Проверка привязки файла к сообщению",
|
|
extra_fields={},
|
|
)
|
|
db.add(req)
|
|
db.flush()
|
|
msg = Message(
|
|
request_id=req.id,
|
|
author_type="CLIENT",
|
|
author_name=req.client_name,
|
|
body="Сообщение клиента",
|
|
)
|
|
db.add(msg)
|
|
db.commit()
|
|
db.refresh(req)
|
|
db.refresh(msg)
|
|
request_id = str(req.id)
|
|
message_id = str(msg.id)
|
|
|
|
key = f"requests/{request_id}/chat/file.txt"
|
|
fake_s3.objects[key] = {
|
|
"content": b"hello",
|
|
"mime": "text/plain",
|
|
"size": 5,
|
|
}
|
|
|
|
with patch("app.api.public.uploads.get_s3_storage", return_value=fake_s3):
|
|
response = self.client.post(
|
|
"/api/public/uploads/complete",
|
|
cookies=self._public_cookies("TRK-PUBLIC-UPL-1"),
|
|
json={
|
|
"key": key,
|
|
"file_name": "file.txt",
|
|
"mime_type": "text/plain",
|
|
"size_bytes": 5,
|
|
"scope": "REQUEST_ATTACHMENT",
|
|
"request_id": request_id,
|
|
"message_id": message_id,
|
|
},
|
|
)
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
attachment_id = response.json().get("attachment_id")
|
|
self.assertIsNotNone(attachment_id)
|
|
|
|
with self.SessionLocal() as db:
|
|
row = db.get(Attachment, UUID(attachment_id))
|
|
self.assertIsNotNone(row)
|
|
self.assertEqual(str(row.message_id), message_id)
|
|
|
|
def test_public_status_route_endpoint_is_available_for_client(self):
|
|
with self.SessionLocal() as db:
|
|
req = Request(
|
|
track_number="TRK-ROUTE-001",
|
|
client_name="Клиент Маршрут",
|
|
client_phone="+79996660000",
|
|
topic_code="consulting",
|
|
status_code="IN_PROGRESS",
|
|
description="Проверка маршрута",
|
|
extra_fields={},
|
|
)
|
|
db.add(req)
|
|
db.commit()
|
|
|
|
response = self.client.get("/api/public/requests/TRK-ROUTE-001/status-route", cookies=self._public_cookies("TRK-ROUTE-001"))
|
|
self.assertEqual(response.status_code, 200)
|
|
payload = response.json()
|
|
self.assertEqual(payload.get("track_number"), "TRK-ROUTE-001")
|
|
self.assertEqual(payload.get("current_status"), "IN_PROGRESS")
|
|
self.assertTrue(isinstance(payload.get("nodes"), list))
|