Closes #9. Auto-insert tracked homepage links in campaign compose, register them with url_shortening_service, and put short piha.li URLs in SMS, email hrefs, and postcard QR codes when SHORTENER_* is configured.
This commit is contained in:
+253
-2
@@ -1,5 +1,5 @@
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.test import Client, TestCase
|
||||
from django.test import Client, TestCase, override_settings
|
||||
from django.urls import reverse
|
||||
|
||||
from contacts.models import Channel, ConsentRecord, Contact, Suppression
|
||||
@@ -1359,6 +1359,7 @@ class CampaignRecipientTableTests(TestCase):
|
||||
self.assertNotContains(response, "Recent SMTP2GO events")
|
||||
|
||||
|
||||
@override_settings(SHORTENER_BASE_URL="", SHORTENER_API_TOKEN="")
|
||||
class Smtp2goSmsSendTests(TestCase):
|
||||
def setUp(self):
|
||||
self.contact = Contact.objects.create(
|
||||
@@ -1416,7 +1417,9 @@ class Smtp2goSmsSendTests(TestCase):
|
||||
payload = post.call_args.kwargs["json"]
|
||||
self.assertEqual(payload["api_key"], "api-test-key")
|
||||
self.assertEqual(payload["destination"], ["+13304022675"])
|
||||
self.assertEqual(payload["content"], "Hello Rufus")
|
||||
self.assertTrue(payload["content"].startswith("Hello Rufus"))
|
||||
self.assertIn("utm_source=monica", payload["content"])
|
||||
self.assertIn("utm_medium=sms", payload["content"])
|
||||
self.assertNotIn("to", payload)
|
||||
self.assertNotIn("text", payload)
|
||||
|
||||
@@ -1450,3 +1453,251 @@ class Smtp2goSmsSendTests(TestCase):
|
||||
|
||||
self.assertIn("Missing required field", str(ctx.exception))
|
||||
self.assertIn("INVALID_REQUEST", str(ctx.exception))
|
||||
|
||||
|
||||
@override_settings(SHORTENER_BASE_URL="", SHORTENER_API_TOKEN="")
|
||||
class CampaignUtmLinkTests(TestCase):
|
||||
def setUp(self):
|
||||
User = get_user_model()
|
||||
self.user = User.objects.create_user(
|
||||
username="utm-composer", password="test-pass-123"
|
||||
)
|
||||
self.client = Client()
|
||||
self.client.login(username="utm-composer", password="test-pass-123")
|
||||
self.contact = Contact.objects.create(
|
||||
email="pat@example.com",
|
||||
phone="5550100199",
|
||||
first_name="Pat",
|
||||
)
|
||||
set_channel_consent(
|
||||
self.contact, Channel.EMAIL, opted_in=True, reason="test"
|
||||
)
|
||||
set_channel_consent(
|
||||
self.contact, Channel.SMS, opted_in=True, reason="test"
|
||||
)
|
||||
|
||||
def test_slug_and_url_shape(self):
|
||||
from messaging.services import (
|
||||
build_campaign_utm_url,
|
||||
campaign_utm_slug,
|
||||
)
|
||||
|
||||
self.assertEqual(campaign_utm_slug("Spring seller tips"), "spring-seller-tips")
|
||||
with self.settings(PUBLIC_SITE_URL="https://mkdrealtor.com"):
|
||||
url = build_campaign_utm_url(
|
||||
name="Spring seller tips", medium="email"
|
||||
)
|
||||
self.assertEqual(
|
||||
url,
|
||||
"https://mkdrealtor.com/?utm_source=monica"
|
||||
"&utm_medium=email&utm_campaign=spring-seller-tips",
|
||||
)
|
||||
|
||||
def test_email_draft_inserts_html_link(self):
|
||||
with self.settings(PUBLIC_SITE_URL="https://mkdrealtor.com"):
|
||||
campaign = create_campaign_draft(
|
||||
name="Spring seller tips",
|
||||
audience=Campaign.Audience.EMAIL_OPT_IN,
|
||||
subject="Hello",
|
||||
body="<p>Hi there</p>",
|
||||
created_by=self.user,
|
||||
)
|
||||
self.assertIn('data-monica-utm="1"', campaign.body_override)
|
||||
self.assertIn("utm_medium=email", campaign.body_override)
|
||||
self.assertIn("utm_campaign=spring-seller-tips", campaign.body_override)
|
||||
self.assertIn("Hi there", campaign.body_override)
|
||||
|
||||
def test_sms_draft_inserts_plain_url(self):
|
||||
with self.settings(PUBLIC_SITE_URL="https://mkdrealtor.com"):
|
||||
campaign = create_campaign_draft(
|
||||
name="Open house",
|
||||
audience=Campaign.Audience.SMS_OPT_IN,
|
||||
body="See you Saturday",
|
||||
created_by=self.user,
|
||||
)
|
||||
self.assertIn("See you Saturday", campaign.body_override)
|
||||
self.assertIn(
|
||||
"https://mkdrealtor.com/?utm_source=monica&utm_medium=sms"
|
||||
"&utm_campaign=open-house",
|
||||
campaign.body_override,
|
||||
)
|
||||
self.assertNotIn("<a ", campaign.body_override)
|
||||
|
||||
def test_sms_replaces_existing_tracked_url(self):
|
||||
from messaging.services import ensure_campaign_utm_link
|
||||
|
||||
with self.settings(PUBLIC_SITE_URL="https://mkdrealtor.com"):
|
||||
old = (
|
||||
"Hi\n\nhttps://mkdrealtor.com/?utm_source=monica"
|
||||
"&utm_medium=sms&utm_campaign=old-name"
|
||||
)
|
||||
updated = ensure_campaign_utm_link(
|
||||
old, name="New name", medium="sms", html=False
|
||||
)
|
||||
self.assertIn("utm_campaign=new-name", updated)
|
||||
self.assertNotIn("old-name", updated)
|
||||
self.assertEqual(updated.count("https://"), 1)
|
||||
|
||||
def test_postcard_does_not_inject_into_body(self):
|
||||
campaign = create_campaign_draft(
|
||||
name="March mailer",
|
||||
audience=Campaign.Audience.POSTCARD_OPT_IN,
|
||||
body="Internal note only",
|
||||
created_by=self.user,
|
||||
)
|
||||
self.assertEqual(campaign.body_override, "Internal note only")
|
||||
self.assertNotIn("utm_source", campaign.body_override)
|
||||
|
||||
def test_composer_shows_tracked_link_panel(self):
|
||||
url = reverse("messaging:campaign_list")
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, "Tracked site link")
|
||||
self.assertContains(response, "campaign-utm.js")
|
||||
self.assertContains(response, "utm_campaign")
|
||||
self.assertContains(response, reverse("messaging:campaign_short_link"))
|
||||
|
||||
def test_detail_shows_tracked_url(self):
|
||||
with self.settings(PUBLIC_SITE_URL="https://mkdrealtor.com"):
|
||||
campaign = create_campaign_draft(
|
||||
name="Spring seller tips",
|
||||
audience=Campaign.Audience.EMAIL_OPT_IN,
|
||||
subject="Hello",
|
||||
body="Hi",
|
||||
created_by=self.user,
|
||||
)
|
||||
url = reverse(
|
||||
"messaging:campaign_detail", kwargs={"pk": campaign.pk}
|
||||
)
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, "utm_campaign=spring-seller-tips")
|
||||
self.assertContains(response, "utm_medium=email")
|
||||
|
||||
def test_sms_replaces_existing_short_url(self):
|
||||
from messaging.services import ensure_campaign_utm_in_text
|
||||
|
||||
updated = ensure_campaign_utm_in_text(
|
||||
"Hi\n\nhttps://piha.li/a3k9xm",
|
||||
"https://piha.li/zzzzzz",
|
||||
)
|
||||
self.assertEqual(updated, "Hi\n\nhttps://piha.li/zzzzzz")
|
||||
|
||||
def test_shortener_mints_into_sms_body(self):
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
mock = MagicMock()
|
||||
mock.status_code = 201
|
||||
mock.json.return_value = {
|
||||
"code": "a3k9xm",
|
||||
"short_url": "https://piha.li/a3k9xm",
|
||||
"target_url": (
|
||||
"https://mkdrealtor.com/?utm_source=monica"
|
||||
"&utm_medium=sms&utm_campaign=open-house"
|
||||
),
|
||||
}
|
||||
with self.settings(
|
||||
PUBLIC_SITE_URL="https://mkdrealtor.com",
|
||||
SHORTENER_BASE_URL="https://shortener.aimloperations.com",
|
||||
SHORTENER_API_TOKEN="monica:test-token",
|
||||
):
|
||||
with patch(
|
||||
"messaging.shortener.requests.post", return_value=mock
|
||||
) as post:
|
||||
campaign = create_campaign_draft(
|
||||
name="Open house",
|
||||
audience=Campaign.Audience.SMS_OPT_IN,
|
||||
body="See you Saturday",
|
||||
created_by=self.user,
|
||||
)
|
||||
self.assertIn("https://piha.li/a3k9xm", campaign.body_override)
|
||||
self.assertIn("See you Saturday", campaign.body_override)
|
||||
self.assertNotIn("utm_medium=sms", campaign.body_override)
|
||||
post.assert_called_once()
|
||||
headers = post.call_args.kwargs["headers"]
|
||||
self.assertEqual(headers["Authorization"], "Bearer monica:test-token")
|
||||
payload = post.call_args.kwargs["json"]
|
||||
self.assertIn("utm_medium=sms", payload["target_url"])
|
||||
self.assertIn("utm_campaign=open-house", payload["target_url"])
|
||||
self.assertEqual(payload["external_ref"], str(campaign.pk))
|
||||
self.assertEqual(campaign.messages.get().body_snapshot, campaign.body_override)
|
||||
|
||||
def test_shortener_failure_falls_back_to_long_url(self):
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
mock = MagicMock()
|
||||
mock.status_code = 401
|
||||
mock.text = '{"detail":"Unauthorized"}'
|
||||
with self.settings(
|
||||
PUBLIC_SITE_URL="https://mkdrealtor.com",
|
||||
SHORTENER_BASE_URL="https://shortener.aimloperations.com",
|
||||
SHORTENER_API_TOKEN="monica:bad",
|
||||
):
|
||||
with patch("messaging.shortener.requests.post", return_value=mock):
|
||||
campaign = create_campaign_draft(
|
||||
name="Open house",
|
||||
audience=Campaign.Audience.SMS_OPT_IN,
|
||||
body="See you Saturday",
|
||||
created_by=self.user,
|
||||
)
|
||||
self.assertIn(
|
||||
"https://mkdrealtor.com/?utm_source=monica&utm_medium=sms"
|
||||
"&utm_campaign=open-house",
|
||||
campaign.body_override,
|
||||
)
|
||||
|
||||
def test_short_link_endpoint_requires_login(self):
|
||||
client = Client()
|
||||
url = reverse("messaging:campaign_short_link")
|
||||
response = client.post(
|
||||
url,
|
||||
data='{"name":"Open house","medium":"sms"}',
|
||||
content_type="application/json",
|
||||
)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
def test_short_link_endpoint_unconfigured_returns_long_url(self):
|
||||
url = reverse("messaging:campaign_short_link")
|
||||
with self.settings(
|
||||
PUBLIC_SITE_URL="https://mkdrealtor.com",
|
||||
SHORTENER_BASE_URL="",
|
||||
SHORTENER_API_TOKEN="",
|
||||
):
|
||||
response = self.client.post(
|
||||
url,
|
||||
data='{"name":"Open house","medium":"sms"}',
|
||||
content_type="application/json",
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
data = response.json()
|
||||
self.assertFalse(data["shortened"])
|
||||
self.assertIn("utm_medium=sms", data["display_url"])
|
||||
self.assertIn("utm_campaign=open-house", data["display_url"])
|
||||
|
||||
def test_short_link_endpoint_mints(self):
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
mock = MagicMock()
|
||||
mock.status_code = 201
|
||||
mock.json.return_value = {
|
||||
"code": "a3k9xm",
|
||||
"short_url": "https://piha.li/a3k9xm",
|
||||
"target_url": "https://mkdrealtor.com/?utm_source=monica",
|
||||
}
|
||||
url = reverse("messaging:campaign_short_link")
|
||||
with self.settings(
|
||||
PUBLIC_SITE_URL="https://mkdrealtor.com",
|
||||
SHORTENER_BASE_URL="https://shortener.aimloperations.com",
|
||||
SHORTENER_API_TOKEN="monica:test-token",
|
||||
):
|
||||
with patch("messaging.shortener.requests.post", return_value=mock):
|
||||
response = self.client.post(
|
||||
url,
|
||||
data='{"name":"Open house","medium":"sms"}',
|
||||
content_type="application/json",
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
data = response.json()
|
||||
self.assertTrue(data["shortened"])
|
||||
self.assertEqual(data["display_url"], "https://piha.li/a3k9xm")
|
||||
self.assertIn("utm_medium=sms", data["target_url"])
|
||||
|
||||
Reference in New Issue
Block a user