mirror of
https://github.com/dartdavros/chatballs.git
synced 2026-10-05 01:14:58 +03:00
🧹 test: фикстуры без имён вендора и его продуктов (Acme, example.com)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
a891947016
commit
ed4393d22a
18 files changed
+84
-84
No files matched your search
@@ -25,7 +25,7 @@ class KnowledgeMetadataImportTests(TestCase):
|
||||
)
|
||||
self.app = create_category(
|
||||
context=self.context,
|
||||
name="FoxRay",
|
||||
name="Acme",
|
||||
parent=self.products,
|
||||
)
|
||||
self.client = TenantAPIClient()
|
||||
@@ -53,16 +53,16 @@ class KnowledgeMetadataImportTests(TestCase):
|
||||
response = self._import(
|
||||
[
|
||||
{
|
||||
"title": "FoxRay support",
|
||||
"title": "Acme support",
|
||||
"description": "Support rules",
|
||||
"categoryPath": ["Products", "FoxRay"],
|
||||
"categoryPath": ["Products", "Acme"],
|
||||
"content": "Procedure",
|
||||
}
|
||||
]
|
||||
)
|
||||
|
||||
self.assertEqual(response.json()["created"], 1)
|
||||
knowledge = Knowledge.objects.get(title="FoxRay support")
|
||||
knowledge = Knowledge.objects.get(title="Acme support")
|
||||
self.assertEqual(knowledge.category_id, self.app.id)
|
||||
|
||||
def test_unknown_category_path_fails_per_document(self) -> None:
|
||||
@@ -93,7 +93,7 @@ class KnowledgeMetadataImportTests(TestCase):
|
||||
[
|
||||
{
|
||||
"title": "Preserved",
|
||||
"categoryPath": ["Products", "FoxRay"],
|
||||
"categoryPath": ["Products", "Acme"],
|
||||
"content": "Version one",
|
||||
}
|
||||
]
|
||||
@@ -127,7 +127,7 @@ class KnowledgeMetadataImportTests(TestCase):
|
||||
[
|
||||
{
|
||||
"title": "Metadata",
|
||||
"categoryPath": ["Products", "FoxRay"],
|
||||
"categoryPath": ["Products", "Acme"],
|
||||
"content": "Stable content",
|
||||
}
|
||||
]
|
||||
|
||||
@@ -52,7 +52,7 @@ class PolicyInvariantMigrationTests(TestCase):
|
||||
|
||||
self.product = Product.objects.create(
|
||||
|
||||
organization=self.organization, code="app", name="FoxRay"
|
||||
organization=self.organization, code="app", name="Acme"
|
||||
|
||||
)
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ EMAIL_CONFIG = {
|
||||
|
||||
|
||||
|
||||
def _raw(*, from_="Иван Петров <Ivan@example.com>", subject="Вопрос по FoxRay", text="Здравствуйте!", message_id="<m1@example.com>", html=None, attach=False) -> bytes:
|
||||
def _raw(*, from_="Иван Петров <Ivan@example.com>", subject="Вопрос по Acme", text="Здравствуйте!", message_id="<m1@example.com>", html=None, attach=False) -> bytes:
|
||||
|
||||
message = MimeMessage()
|
||||
|
||||
@@ -140,7 +140,7 @@ class EmailNormalizeTests(TestCase):
|
||||
|
||||
self.assertEqual(inbound.text, "Здравствуйте!")
|
||||
|
||||
self.assertEqual(inbound.thread_meta, {"subject": "Вопрос по FoxRay", "last_message_id": "<m1@example.com>"})
|
||||
self.assertEqual(inbound.thread_meta, {"subject": "Вопрос по Acme", "last_message_id": "<m1@example.com>"})
|
||||
|
||||
self.assertEqual(message_id, "<m1@example.com>")
|
||||
|
||||
@@ -162,7 +162,7 @@ class EmailNormalizeTests(TestCase):
|
||||
|
||||
inbound, _ = email_transport._normalize(
|
||||
|
||||
_parsed(_raw(html="<p>Добрый день!</p><p>Сколько стоит «FoxRay»?</p>")),
|
||||
_parsed(_raw(html="<p>Добрый день!</p><p>Сколько стоит «Acme»?</p>")),
|
||||
|
||||
own_address="support@example.com",
|
||||
|
||||
@@ -174,7 +174,7 @@ class EmailNormalizeTests(TestCase):
|
||||
|
||||
self.assertIn("Добрый день!", inbound.text)
|
||||
|
||||
self.assertIn("«FoxRay»", inbound.text)
|
||||
self.assertIn("«Acme»", inbound.text)
|
||||
|
||||
self.assertNotIn("<p>", inbound.text)
|
||||
|
||||
@@ -370,7 +370,7 @@ class EmailSendTests(TestCase):
|
||||
|
||||
self.organization = Organization.objects.get(slug="demo")
|
||||
|
||||
self.channel = Channel.objects.create(organization=self.organization, code="edevs", name="Edevs — главный сайт")
|
||||
self.channel = Channel.objects.create(organization=self.organization, code="acme", name="Acme — главный сайт")
|
||||
|
||||
self.integration = _integration(organization=self.organization, channel=self.channel)
|
||||
|
||||
@@ -390,7 +390,7 @@ class EmailSendTests(TestCase):
|
||||
|
||||
external_chat_id="ivan@example.com",
|
||||
|
||||
transport_meta={"subject": "Вопрос по FoxRay", "last_message_id": "<m1@example.com>"},
|
||||
transport_meta={"subject": "Вопрос по Acme", "last_message_id": "<m1@example.com>"},
|
||||
|
||||
)
|
||||
|
||||
@@ -412,7 +412,7 @@ class EmailSendTests(TestCase):
|
||||
|
||||
self.assertEqual(outgoing["To"], "ivan@example.com")
|
||||
|
||||
self.assertEqual(outgoing["Subject"], "Re: Вопрос по FoxRay")
|
||||
self.assertEqual(outgoing["Subject"], "Re: Вопрос по Acme")
|
||||
|
||||
self.assertEqual(outgoing["In-Reply-To"], "<m1@example.com>")
|
||||
|
||||
@@ -464,7 +464,7 @@ class EmailIngestThreadMetaTests(TestCase):
|
||||
|
||||
self.organization = Organization.objects.get(slug="demo")
|
||||
|
||||
self.channel = Channel.objects.create(organization=self.organization, code="edevs", name="Edevs — главный сайт")
|
||||
self.channel = Channel.objects.create(organization=self.organization, code="acme", name="Acme — главный сайт")
|
||||
|
||||
self.integration = _integration(organization=self.organization, channel=self.channel)
|
||||
|
||||
@@ -510,13 +510,13 @@ class EmailIngestThreadMetaTests(TestCase):
|
||||
|
||||
def test_subject_pinned_to_first_message_id_follows_last(self) -> None:
|
||||
|
||||
self._ingest(external_id="<m1@example.com>", subject="Вопрос по FoxRay", message_id="<m1@example.com>")
|
||||
self._ingest(external_id="<m1@example.com>", subject="Вопрос по Acme", message_id="<m1@example.com>")
|
||||
|
||||
self._ingest(external_id="<m2@example.com>", subject="Re: Вопрос по FoxRay", message_id="<m2@example.com>")
|
||||
self._ingest(external_id="<m2@example.com>", subject="Re: Вопрос по Acme", message_id="<m2@example.com>")
|
||||
|
||||
conversation = Conversation.objects.get(channel=self.channel)
|
||||
|
||||
self.assertEqual(conversation.transport_meta["subject"], "Вопрос по FoxRay")
|
||||
self.assertEqual(conversation.transport_meta["subject"], "Вопрос по Acme")
|
||||
|
||||
self.assertEqual(conversation.transport_meta["last_message_id"], "<m2@example.com>")
|
||||
|
||||
|
||||
@@ -52,10 +52,10 @@ class IngestLimitHandlingTests(TestCase):
|
||||
def setUp(self) -> None:
|
||||
bootstrap_owner(email="owner@example.com", password="temporary-password")
|
||||
self.organization = Organization.objects.get(slug="demo")
|
||||
self.channel = Channel.objects.create(organization=self.organization, code="app-sales", name="FoxRay — продажи")
|
||||
self.channel = Channel.objects.create(organization=self.organization, code="app-sales", name="Acme — продажи")
|
||||
AIAgent.objects.create(
|
||||
channel=self.channel,
|
||||
name="FoxRay Agent",
|
||||
name="Acme Agent",
|
||||
model="openai/gpt-4o-mini",
|
||||
status=AIAgentStatus.ACTIVE,
|
||||
)
|
||||
@@ -179,10 +179,10 @@ class ContactShareIngestTests(TestCase):
|
||||
def setUp(self) -> None:
|
||||
bootstrap_owner(email="owner@example.com", password="temporary-password")
|
||||
self.organization = Organization.objects.get(slug="demo")
|
||||
self.channel = Channel.objects.create(organization=self.organization, code="app-sales", name="FoxRay — продажи")
|
||||
self.channel = Channel.objects.create(organization=self.organization, code="app-sales", name="Acme — продажи")
|
||||
AIAgent.objects.create(
|
||||
channel=self.channel,
|
||||
name="FoxRay Agent",
|
||||
name="Acme Agent",
|
||||
model="openai/gpt-4o-mini",
|
||||
status=AIAgentStatus.ACTIVE,
|
||||
)
|
||||
@@ -233,7 +233,7 @@ class RequestContactApiTests(TestCase):
|
||||
def setUp(self) -> None:
|
||||
bootstrap_owner(email="owner@example.com", password="temporary-password")
|
||||
self.organization = Organization.objects.get(slug="demo")
|
||||
self.channel = Channel.objects.create(organization=self.organization, code="app-sales", name="FoxRay — продажи")
|
||||
self.channel = Channel.objects.create(organization=self.organization, code="app-sales", name="Acme — продажи")
|
||||
self.integration = _messenger_connection(self.channel)
|
||||
self.contact = Contact.objects.create(organization=self.organization, name="Иван")
|
||||
ConnectionIdentity.objects.create(
|
||||
@@ -281,7 +281,7 @@ class ConversationReadTests(TestCase):
|
||||
def setUp(self) -> None:
|
||||
bootstrap_owner(email="owner@example.com", password="temporary-password")
|
||||
self.organization = Organization.objects.get(slug="demo")
|
||||
self.channel = Channel.objects.create(organization=self.organization, code="app-sales", name="FoxRay — продажи")
|
||||
self.channel = Channel.objects.create(organization=self.organization, code="app-sales", name="Acme — продажи")
|
||||
self.integration = _messenger_connection(self.channel)
|
||||
contact = Contact.objects.create(organization=self.organization, name="Иван")
|
||||
self.conversation = Conversation.objects.create(
|
||||
@@ -348,7 +348,7 @@ class WebchatContactTests(TestCase):
|
||||
def setUp(self) -> None:
|
||||
bootstrap_owner(email="owner@example.com", password="temporary-password")
|
||||
self.organization = Organization.objects.get(slug="demo")
|
||||
self.channel = Channel.objects.create(organization=self.organization, code="edevs-web", name="Веб-чат")
|
||||
self.channel = Channel.objects.create(organization=self.organization, code="demo-web", name="Веб-чат")
|
||||
AIAgent.objects.create(
|
||||
channel=self.channel,
|
||||
name="Web Agent",
|
||||
|
||||
@@ -74,7 +74,7 @@ class WebIntegrationCheckTests(TestCase):
|
||||
|
||||
|
||||
|
||||
self.channel = Channel.objects.create(organization=self.organization, code="edevs", name="Edevs — главный сайт")
|
||||
self.channel = Channel.objects.create(organization=self.organization, code="acme", name="Acme — главный сайт")
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ from chatballs.notifications.services import notify
|
||||
from chatballs.notifications.selectors import visible_for
|
||||
|
||||
|
||||
def _notifier(organization, provider=IntegrationProvider.TELEGRAM, username="edevs_notify_bot"):
|
||||
def _notifier(organization, provider=IntegrationProvider.TELEGRAM, username="chatballs_notify_bot"):
|
||||
return Integration.objects.create(
|
||||
organization=organization,
|
||||
kind=IntegrationKind.MESSENGER,
|
||||
@@ -39,9 +39,9 @@ class NotifierTestBase(TestCase):
|
||||
class BindingTests(NotifierTestBase):
|
||||
def test_deep_link_for_telegram_and_max(self) -> None:
|
||||
code = issue_binding_code(context=self.context, integration=self.integration)
|
||||
self.assertEqual(deep_link(self.integration, code.code), f"https://t.me/edevs_notify_bot?start={code.code}")
|
||||
max_bot = _notifier(self.organization, provider=IntegrationProvider.MAX, username="edevs_max_bot")
|
||||
self.assertEqual(deep_link(max_bot, "abc"), "https://max.ru/edevs_max_bot?start=abc")
|
||||
self.assertEqual(deep_link(self.integration, code.code), f"https://t.me/chatballs_notify_bot?start={code.code}")
|
||||
max_bot = _notifier(self.organization, provider=IntegrationProvider.MAX, username="chatballs_max_bot")
|
||||
self.assertEqual(deep_link(max_bot, "abc"), "https://max.ru/chatballs_max_bot?start=abc")
|
||||
|
||||
def test_start_code_creates_binding_and_confirms(self) -> None:
|
||||
code = issue_binding_code(context=self.context, integration=self.integration)
|
||||
@@ -63,7 +63,7 @@ class BindingTests(NotifierTestBase):
|
||||
def test_binding_is_exclusive_across_messengers(self) -> None:
|
||||
# Уведомления идут в один мессенджер: привязка MAX заменяет привязку TG.
|
||||
MessengerBinding.objects.create(user=self.owner, integration=self.integration, external_chat_id="111")
|
||||
max_bot = _notifier(self.organization, provider=IntegrationProvider.MAX, username="edevs_max_bot")
|
||||
max_bot = _notifier(self.organization, provider=IntegrationProvider.MAX, username="chatballs_max_bot")
|
||||
code = issue_binding_code(context=self.context, integration=max_bot)
|
||||
inbound = InboundMessage(external_id="3", user_id="9", chat_id="9", text=f"/start {code.code}", display_name="Андрей")
|
||||
with mock.patch("chatballs.notifications.binding.transports.send_reply", return_value=True):
|
||||
@@ -93,7 +93,7 @@ class DeliveryTests(NotifierTestBase):
|
||||
context=self.context,
|
||||
type=NotificationType.DIALOG_WAITING,
|
||||
audience=NotificationAudience.OPERATORS,
|
||||
title="Новый диалог · Edevs — сайт",
|
||||
title="Новый диалог · Acme — сайт",
|
||||
body="Гость · TELEGRAM: Привет",
|
||||
)
|
||||
self._dispatch_last_event()
|
||||
@@ -156,7 +156,7 @@ class PollerSelectionTests(NotifierTestBase):
|
||||
from chatballs.channels.models import Channel
|
||||
from chatballs.conversations import poller
|
||||
|
||||
channel = Channel.objects.create(organization=self.organization, code="app-sales", name="FoxRay — продажи")
|
||||
channel = Channel.objects.create(organization=self.organization, code="app-sales", name="Acme — продажи")
|
||||
client_bot = Integration.objects.create(
|
||||
organization=self.organization, kind=IntegrationKind.MESSENGER,
|
||||
provider=IntegrationProvider.TELEGRAM, name="client-bot", secret="token", channel=channel,
|
||||
@@ -227,7 +227,7 @@ class BindingApiTests(NotifierTestBase):
|
||||
issued = self.client.post(f"/api/v1/notifications/messenger-bindings/{self.integration.id}/")
|
||||
self.assertEqual(issued.status_code, 201)
|
||||
payload = issued.json()
|
||||
self.assertTrue(payload["deepLink"].startswith("https://t.me/edevs_notify_bot?start="))
|
||||
self.assertTrue(payload["deepLink"].startswith("https://t.me/chatballs_notify_bot?start="))
|
||||
|
||||
MessengerBinding.objects.create(user=self.owner, integration=self.integration, external_chat_id="777")
|
||||
self.assertTrue(self.client.get("/api/v1/notifications/messenger-bindings/").json()["items"][0]["bound"])
|
||||
|
||||
@@ -22,7 +22,7 @@ class ProductApiTests(TestCase):
|
||||
{
|
||||
"code": "academy",
|
||||
"name": "Academy",
|
||||
"siteUrl": "https://academy.edevs.tech",
|
||||
"siteUrl": "https://academy.example.com",
|
||||
}
|
||||
),
|
||||
content_type="application/json",
|
||||
|
||||
@@ -47,8 +47,8 @@ def make_support_token(
|
||||
return f"{header_b64}.{payload_b64}.{signature_b64}"
|
||||
|
||||
|
||||
# Пример payload FoxRay (SPEC-HUB-0011 §4.1 / §5.2).
|
||||
FOXRAY_DATA = {
|
||||
# Пример payload Acme (SPEC-HUB-0011 §4.1 / §5.2).
|
||||
ACME_DATA = {
|
||||
"doctor": {"id": "u_456", "name": "Иван Петров", "email": "doctor@example.com"},
|
||||
"clinic": {"id": "c_123", "name": "Клиника Альфа"},
|
||||
"subscription": {"tariff": "pro", "status": "active"},
|
||||
|
||||
@@ -12,7 +12,7 @@ from chatballs.identity.bootstrap import bootstrap_owner
|
||||
from chatballs.identity.models import Organization
|
||||
from chatballs.products.models import Product
|
||||
from chatballs.support.models import ContractStatus, ProductSupportContract
|
||||
from chatballs.support.test_helpers import FOXRAY_DATA, make_support_token
|
||||
from chatballs.support.test_helpers import ACME_DATA, make_support_token
|
||||
from chatballs.webchat.testing import create_web_widget
|
||||
|
||||
SECRET = "test-support-secret-very-long-32bytes!!"
|
||||
@@ -57,7 +57,7 @@ def _setup_support_channel(
|
||||
channel = Channel.objects.create(
|
||||
organization=organization,
|
||||
code="app-support",
|
||||
name="FoxRay — поддержка",
|
||||
name="Acme — поддержка",
|
||||
product=product,
|
||||
requires_authenticated_product_identity=True,
|
||||
allow_anonymous_sessions=False,
|
||||
@@ -77,11 +77,11 @@ class SupportWidgetMessagesTests(TestCase):
|
||||
self.channel, self.contract = _setup_support_channel(
|
||||
self.organization, self.product
|
||||
)
|
||||
self.widget = create_web_widget(self.channel, name="FoxRay support widget")
|
||||
self.widget = create_web_widget(self.channel, name="Acme support widget")
|
||||
self.client = APIClient()
|
||||
|
||||
def _start_session(self) -> dict:
|
||||
token = make_support_token(secret=SECRET, data=FOXRAY_DATA)
|
||||
token = make_support_token(secret=SECRET, data=ACME_DATA)
|
||||
response = self.client.post(
|
||||
"/api/v1/support/sessions/",
|
||||
data=json.dumps({"widgetKey": self.widget.public_key, "token": token}),
|
||||
|
||||
@@ -15,7 +15,7 @@ from chatballs.support.models import (
|
||||
ProductSupportContract,
|
||||
SupportIdentitySnapshot,
|
||||
)
|
||||
from chatballs.support.test_helpers import FOXRAY_DATA, make_support_token
|
||||
from chatballs.support.test_helpers import ACME_DATA, make_support_token
|
||||
from chatballs.webchat.models import WebChatWidgetMode
|
||||
from chatballs.webchat.testing import create_web_widget
|
||||
|
||||
@@ -87,7 +87,7 @@ class SupportSessionTests(TestCase):
|
||||
self.channel = Channel.objects.create(
|
||||
organization=self.organization,
|
||||
code="app-support",
|
||||
name="FoxRay — поддержка",
|
||||
name="Acme — поддержка",
|
||||
product=self.product,
|
||||
requires_authenticated_product_identity=True,
|
||||
allow_anonymous_sessions=False,
|
||||
@@ -96,7 +96,7 @@ class SupportSessionTests(TestCase):
|
||||
allow_checkout_actions=False,
|
||||
)
|
||||
self.contract.allowed_channels.add(self.channel)
|
||||
self.widget = create_web_widget(self.channel, name="FoxRay support widget")
|
||||
self.widget = create_web_widget(self.channel, name="Acme support widget")
|
||||
self.client = APIClient()
|
||||
|
||||
def _start(self, token: str, widget_key: str | None = None):
|
||||
@@ -107,7 +107,7 @@ class SupportSessionTests(TestCase):
|
||||
)
|
||||
|
||||
def test_happy_path_creates_snapshot_and_conversation(self) -> None:
|
||||
token = make_support_token(secret=SECRET, data=FOXRAY_DATA)
|
||||
token = make_support_token(secret=SECRET, data=ACME_DATA)
|
||||
response = self._start(token)
|
||||
self.assertEqual(response.status_code, 201, response.content)
|
||||
body = response.json()
|
||||
@@ -133,14 +133,14 @@ class SupportSessionTests(TestCase):
|
||||
)
|
||||
|
||||
def test_invalid_signature_denied(self) -> None:
|
||||
token = make_support_token(secret="wrong-secret", data=FOXRAY_DATA)
|
||||
token = make_support_token(secret="wrong-secret", data=ACME_DATA)
|
||||
response = self._start(token)
|
||||
self.assertEqual(response.status_code, 422)
|
||||
self.assertEqual(response.json()["error"], "support_unavailable")
|
||||
self._assert_denied_audit("TOKEN_SIGNATURE_INVALID")
|
||||
|
||||
def test_expired_token_denied(self) -> None:
|
||||
token = make_support_token(secret=SECRET, data=FOXRAY_DATA, exp_delta=-100)
|
||||
token = make_support_token(secret=SECRET, data=ACME_DATA, exp_delta=-100)
|
||||
response = self._start(token)
|
||||
self.assertEqual(response.status_code, 422)
|
||||
self._assert_denied_audit("TOKEN_EXPIRED")
|
||||
@@ -164,7 +164,7 @@ class SupportSessionTests(TestCase):
|
||||
def test_disabled_contract_denied(self) -> None:
|
||||
self.contract.status = ContractStatus.DISABLED
|
||||
self.contract.save(update_fields=["status"])
|
||||
token = make_support_token(secret=SECRET, data=FOXRAY_DATA)
|
||||
token = make_support_token(secret=SECRET, data=ACME_DATA)
|
||||
response = self._start(token)
|
||||
self.assertEqual(response.status_code, 422)
|
||||
self._assert_denied_audit("CONTRACT_DISABLED")
|
||||
@@ -174,21 +174,21 @@ class SupportSessionTests(TestCase):
|
||||
# принимать support-токен.
|
||||
sales_channel = Channel.objects.create(
|
||||
organization=self.organization, code="app-sales-x",
|
||||
name="FoxRay sales", product=self.product,
|
||||
name="Acme sales", product=self.product,
|
||||
)
|
||||
invalid_widget = create_web_widget(
|
||||
sales_channel,
|
||||
name="Invalid support widget",
|
||||
mode=WebChatWidgetMode.AUTHENTICATED_PRODUCT,
|
||||
)
|
||||
token = make_support_token(secret=SECRET, data=FOXRAY_DATA)
|
||||
token = make_support_token(secret=SECRET, data=ACME_DATA)
|
||||
response = self._start(token, widget_key=invalid_widget.public_key)
|
||||
self.assertEqual(response.status_code, 422)
|
||||
self._assert_denied_audit("CHANNEL_NOT_SUPPORT")
|
||||
|
||||
def test_channel_product_mismatch_denied(self) -> None:
|
||||
# iss=site, но канал привязан к app.
|
||||
token = make_support_token(secret=SECRET, iss="site", data=FOXRAY_DATA)
|
||||
token = make_support_token(secret=SECRET, iss="site", data=ACME_DATA)
|
||||
response = self._start(token)
|
||||
self.assertEqual(response.status_code, 422)
|
||||
self._assert_denied_audit("CHANNEL_PRODUCT_MISMATCH")
|
||||
@@ -203,18 +203,18 @@ class SupportSessionTests(TestCase):
|
||||
self.assertEqual(response.json()["error"], "support_unavailable")
|
||||
|
||||
def test_continue_session_reuses_open_conversation(self) -> None:
|
||||
token1 = make_support_token(secret=SECRET, data=FOXRAY_DATA, jti="jti-1")
|
||||
token1 = make_support_token(secret=SECRET, data=ACME_DATA, jti="jti-1")
|
||||
first = self._start(token1)
|
||||
self.assertEqual(first.status_code, 201)
|
||||
conv_id = first.json()["conversation"]["id"]
|
||||
# Повторный старт тем же subject → тот же открытый диалог.
|
||||
token2 = make_support_token(secret=SECRET, data=FOXRAY_DATA, jti="jti-2")
|
||||
token2 = make_support_token(secret=SECRET, data=ACME_DATA, jti="jti-2")
|
||||
second = self._start(token2)
|
||||
self.assertEqual(second.status_code, 201)
|
||||
self.assertEqual(second.json()["conversation"]["id"], conv_id)
|
||||
|
||||
def test_raw_token_not_in_audit(self) -> None:
|
||||
token = make_support_token(secret="wrong-secret", data=FOXRAY_DATA, jti="secret-jti-xyz")
|
||||
token = make_support_token(secret="wrong-secret", data=ACME_DATA, jti="secret-jti-xyz")
|
||||
self._start(token)
|
||||
denied = AuditEvent.objects.filter(action="support.session_denied").first()
|
||||
self.assertIsNotNone(denied)
|
||||
|
||||
@@ -21,13 +21,13 @@ class SupportPortalManagementTests(SupportPortalTestCase):
|
||||
widget_channel = Channel.objects.create(
|
||||
organization=self.organization,
|
||||
code="app-portal-chat",
|
||||
name="FoxRay — чат портала",
|
||||
name="Acme — чат портала",
|
||||
product=self.product,
|
||||
requires_authenticated_product_identity=False,
|
||||
allow_anonymous_sessions=True,
|
||||
allow_self_reported_contact=True,
|
||||
)
|
||||
widget = create_web_widget(widget_channel, name="FoxRay portal widget")
|
||||
widget = create_web_widget(widget_channel, name="Acme portal widget")
|
||||
|
||||
options = self.client.get(
|
||||
f"/api/v1/support/portals/{portal_id}/support-channels/"
|
||||
|
||||
@@ -18,19 +18,19 @@ class PublicSupportPortalTests(TestCase):
|
||||
self.client.force_authenticate(result.owner)
|
||||
portal = self.client.post(
|
||||
"/api/v1/support/portals/",
|
||||
{"slug": "edevs-help", "name": "Edevs Help"},
|
||||
{"slug": "demo-help", "name": "Acme Help"},
|
||||
format="json",
|
||||
).json()["portal"]
|
||||
self.portal_id = portal["id"]
|
||||
self.portal_host = portal["hostedDomain"]
|
||||
widget_channel = Channel.objects.create(
|
||||
organization=result.organization,
|
||||
code="edevs-help-chat",
|
||||
name="Edevs Help — чат",
|
||||
code="demo-help-chat",
|
||||
name="Acme Help — чат",
|
||||
requires_authenticated_product_identity=False,
|
||||
allow_anonymous_sessions=True,
|
||||
)
|
||||
self.widget = create_web_widget(widget_channel, name="Edevs Help widget")
|
||||
self.widget = create_web_widget(widget_channel, name="Acme Help widget")
|
||||
self.client.patch(
|
||||
f"/api/v1/support/portals/{self.portal_id}/",
|
||||
{"widgetId": self.widget.id},
|
||||
@@ -85,7 +85,7 @@ class PublicSupportPortalTests(TestCase):
|
||||
|
||||
portal = self.client.get("/api/v1/help/", HTTP_HOST=self.portal_host)
|
||||
self.assertEqual(portal.status_code, 200, portal.content)
|
||||
self.assertEqual(portal.json()["portal"]["name"], "Edevs Help")
|
||||
self.assertEqual(portal.json()["portal"]["name"], "Acme Help")
|
||||
self.assertEqual(
|
||||
portal.json()["portal"]["webWidgetKey"],
|
||||
self.widget.public_key,
|
||||
@@ -146,7 +146,7 @@ class PublicSupportPortalTests(TestCase):
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, 200, response.content)
|
||||
self.assertEqual(response.json()["portal"]["name"], "Edevs Help")
|
||||
self.assertEqual(response.json()["portal"]["name"], "Acme Help")
|
||||
|
||||
@override_settings(ROOT_URLCONF="chatballs_backend.urls_platform")
|
||||
def test_gateway_authorizes_only_published_portal_domains(self) -> None:
|
||||
|
||||
@@ -48,7 +48,7 @@ describe("knowledge API", () => {
|
||||
await fetchKnowledgeList({
|
||||
category: 7,
|
||||
isEnabled: false,
|
||||
search: "тариф FoxRay",
|
||||
search: "тариф Acme",
|
||||
});
|
||||
|
||||
const url = new URL(fetchMock.mock.calls[0][0], "https://app.example");
|
||||
@@ -58,7 +58,7 @@ describe("knowledge API", () => {
|
||||
expect(Object.fromEntries(url.searchParams)).toEqual({
|
||||
category: "7",
|
||||
isEnabled: "false",
|
||||
search: "тариф FoxRay",
|
||||
search: "тариф Acme",
|
||||
});
|
||||
});
|
||||
|
||||
@@ -66,8 +66,8 @@ describe("knowledge API", () => {
|
||||
const fetchMock = mockSuccess({ items: [] });
|
||||
|
||||
await fetchKnowledgeCategories();
|
||||
await createKnowledgeCategory({ name: "FoxRay", parentId: 3, sortOrder: 20 });
|
||||
await updateKnowledgeCategory(7, { name: "FoxRay Pro", parentId: null, sortOrder: 30 });
|
||||
await createKnowledgeCategory({ name: "Acme", parentId: 3, sortOrder: 20 });
|
||||
await updateKnowledgeCategory(7, { name: "Acme Pro", parentId: null, sortOrder: 30 });
|
||||
|
||||
expect(fetchMock.mock.calls.map(([path]) => path)).toEqual([
|
||||
`/api/v1/organizations/${organizationPublicId}/ai/knowledge/categories/`,
|
||||
@@ -75,12 +75,12 @@ describe("knowledge API", () => {
|
||||
`/api/v1/organizations/${organizationPublicId}/ai/knowledge/categories/7/`,
|
||||
]);
|
||||
expect(JSON.parse(fetchMock.mock.calls[1][1].body)).toEqual({
|
||||
name: "FoxRay",
|
||||
name: "Acme",
|
||||
parentId: 3,
|
||||
sortOrder: 20,
|
||||
});
|
||||
expect(JSON.parse(fetchMock.mock.calls[2][1].body)).toEqual({
|
||||
name: "FoxRay Pro",
|
||||
name: "Acme Pro",
|
||||
parentId: null,
|
||||
sortOrder: 30,
|
||||
});
|
||||
|
||||
@@ -15,7 +15,7 @@ const categories: KnowledgeCategory[] = [
|
||||
{ id: 1, name: "Без категории", parentId: null, sortOrder: 0, isSystem: true, knowledgeCount: 4 },
|
||||
{ id: 3, name: "Продажи", parentId: null, sortOrder: 20, isSystem: false, knowledgeCount: 9 },
|
||||
{ id: 2, name: "Продукты", parentId: null, sortOrder: 10, isSystem: false, knowledgeCount: 14 },
|
||||
{ id: 5, name: "FoxRay", parentId: 2, sortOrder: 10, isSystem: false, knowledgeCount: 8 },
|
||||
{ id: 5, name: "Acme", parentId: 2, sortOrder: 10, isSystem: false, knowledgeCount: 8 },
|
||||
];
|
||||
|
||||
describe("knowledge category tree", () => {
|
||||
|
||||
@@ -18,10 +18,10 @@ describe("splitQuotedEmail", () => {
|
||||
});
|
||||
|
||||
it("separates an inline localized reply header", () => {
|
||||
const text = "Держись\nСр, 22 июля 2026 г. в 21:10, Foxray <foxray@example.com>: > Старое письмо";
|
||||
const text = "Держись\nСр, 22 июля 2026 г. в 21:10, Acme <acme@example.com>: > Старое письмо";
|
||||
expect(splitQuotedEmail(text)).toEqual({
|
||||
latest: "Держись",
|
||||
quoted: "Ср, 22 июля 2026 г. в 21:10, Foxray <foxray@example.com>: > Старое письмо",
|
||||
quoted: "Ср, 22 июля 2026 г. в 21:10, Acme <acme@example.com>: > Старое письмо",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,20 +5,20 @@ import { supportHref } from "./SupportLauncher";
|
||||
describe("supportHref", () => {
|
||||
it("passes the selected support channel to the product", () => {
|
||||
expect(supportHref({
|
||||
code: "foxray",
|
||||
name: "Foxray",
|
||||
siteUrl: "https://foxray.example/account?from=help",
|
||||
code: "acme",
|
||||
name: "Acme",
|
||||
siteUrl: "https://acme.example/account?from=help",
|
||||
supportAvailable: true,
|
||||
supportWidgetKey: "wgt_foxray_support",
|
||||
supportChannelCode: "foxray-support",
|
||||
})).toBe("https://foxray.example/account?from=help&chatballsSupportWidget=wgt_foxray_support");
|
||||
supportWidgetKey: "wgt_acme_support",
|
||||
supportChannelCode: "acme-support",
|
||||
})).toBe("https://acme.example/account?from=help&chatballsSupportWidget=wgt_acme_support");
|
||||
});
|
||||
|
||||
it("does not build an unauthenticated fallback without a channel", () => {
|
||||
expect(supportHref({
|
||||
code: "foxray",
|
||||
name: "Foxray",
|
||||
siteUrl: "https://foxray.example",
|
||||
code: "acme",
|
||||
name: "Acme",
|
||||
siteUrl: "https://acme.example",
|
||||
supportAvailable: false,
|
||||
supportChannelCode: null,
|
||||
supportWidgetKey: null,
|
||||
|
||||
@@ -22,8 +22,8 @@ describe("webWidgetSnippet", () => {
|
||||
vi.stubGlobal("window", { location: { origin: "https://acme.test" } });
|
||||
const { webWidgetSnippet } = await import("./model");
|
||||
|
||||
expect(webWidgetSnippet("wgt_foxray")).toBe(
|
||||
`<script src="https://acme.test/chat-widget.js" data-widget-key="wgt_foxray" async></script>`,
|
||||
expect(webWidgetSnippet("wgt_acme")).toBe(
|
||||
`<script src="https://acme.test/chat-widget.js" data-widget-key="wgt_acme" async></script>`,
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -14,8 +14,8 @@ const OWNER = {
|
||||
fullName: "Владелец",
|
||||
role: "OWNER",
|
||||
positionTitle: "Владелец",
|
||||
organization: "edevs",
|
||||
organizationName: "Edevs",
|
||||
organization: "acme",
|
||||
organizationName: "Acme",
|
||||
department: null,
|
||||
mustChangePassword: false,
|
||||
totpRequired: false,
|
||||
|
||||
Reference in new issue
Block a user