from unittest.mock import patch from django.contrib.auth import get_user_model from django.test import Client, TestCase from django.urls import reverse from social.models import ( Platform, SocialAccount, SocialPost, is_generic_account_label, ) class AccountLabelTests(TestCase): def test_generic_placeholders(self): self.assertTrue(is_generic_account_label("")) self.assertTrue(is_generic_account_label("LinkedIn member")) self.assertTrue(is_generic_account_label("Instagram account")) self.assertTrue(is_generic_account_label("Page 123456")) self.assertTrue(is_generic_account_label("urn:li:person:abc")) self.assertFalse(is_generic_account_label("Monica Dhillon")) self.assertFalse(is_generic_account_label("@mkdrealtor")) def test_resolve_keeps_custom_name(self): existing = SocialAccount( platform=Platform.LINKEDIN, label="Monica (personal)", external_id="urn:li:person:abc", ) self.assertEqual( SocialAccount.resolve_label(existing, "LinkedIn member"), "Monica (personal)", ) def test_resolve_replaces_generic(self): existing = SocialAccount( platform=Platform.LINKEDIN, label="LinkedIn member", external_id="urn:li:person:abc", ) self.assertEqual( SocialAccount.resolve_label(existing, "Monica Dhillon"), "Monica Dhillon", ) class SocialComposerTests(TestCase): def setUp(self): User = get_user_model() self.user = User.objects.create_user( username="composer", password="test-pass-123" ) self.client = Client() self.client.login(username="composer", password="test-pass-123") self.account = SocialAccount.objects.create( platform=Platform.LINKEDIN, label="LinkedIn member", external_id="urn:li:person:abc123", is_active=True, owner=self.user, ) def test_composer_shows_send_now_and_full_name(self): response = self.client.get(reverse("social:composer")) self.assertEqual(response.status_code, 200) self.assertContains(response, "Send now") self.assertContains(response, "Schedule") self.assertContains(response, "LinkedIn member") self.assertContains(response, "urn:li:person:abc123") self.assertNotContains(response, "Save / publish") def test_send_now_queues_without_schedule(self): mock_task = patch("social.views.publish_social_post").start() self.addCleanup(patch.stopall) response = self.client.post( reverse("social:composer"), { "body": "Open house Saturday", "account_ids": [str(self.account.pk)], "action": "send_now", }, ) post = SocialPost.objects.get() self.assertEqual(response.status_code, 302) self.assertEqual( response.url, reverse("social:post_detail", kwargs={"pk": post.pk}) ) self.assertEqual(post.status, SocialPost.Status.QUEUED) self.assertIsNotNone(post.scheduled_for) self.assertEqual(post.targets.count(), 1) mock_task.enqueue.assert_called_once_with(post_id=str(post.pk)) def test_schedule_requires_datetime(self): response = self.client.post( reverse("social:composer"), { "body": "Open house Saturday", "account_ids": [str(self.account.pk)], "action": "schedule", }, ) self.assertEqual(response.status_code, 200) self.assertContains(response, "Pick a schedule date/time, or choose Send now.") self.assertFalse(SocialPost.objects.exists()) def test_schedule_with_datetime(self): response = self.client.post( reverse("social:composer"), { "body": "Open house Saturday", "account_ids": [str(self.account.pk)], "action": "schedule", "scheduled_for": "2026-08-10T09:30", }, ) post = SocialPost.objects.get() self.assertEqual(response.status_code, 302) self.assertEqual(post.status, SocialPost.Status.SCHEDULED) self.assertIsNotNone(post.scheduled_for) def test_save_draft(self): response = self.client.post( reverse("social:composer"), { "body": "Draft caption", "action": "save", }, ) post = SocialPost.objects.get() self.assertEqual(response.status_code, 302) self.assertEqual(post.status, SocialPost.Status.DRAFT) class SocialAccountRenameTests(TestCase): def setUp(self): User = get_user_model() self.user = User.objects.create_user( username="renamer", password="test-pass-123" ) self.client = Client() self.client.login(username="renamer", password="test-pass-123") self.account = SocialAccount.objects.create( platform=Platform.FACEBOOK, label="Page 999", external_id="999", is_active=True, owner=self.user, ) def test_accounts_page_shows_rename_field(self): response = self.client.get(reverse("social:account_list")) self.assertEqual(response.status_code, 200) self.assertContains(response, 'name="label"') self.assertContains(response, "Page 999") self.assertContains(response, "Rename") def test_rename_updates_label(self): response = self.client.post( reverse("social:account_list"), { "action": "rename", "account_id": str(self.account.pk), "label": "Monica Dhillon Realty", }, ) self.assertEqual(response.status_code, 302) self.account.refresh_from_db() self.assertEqual(self.account.label, "Monica Dhillon Realty") def test_rename_rejects_blank(self): response = self.client.post( reverse("social:account_list"), { "action": "rename", "account_id": str(self.account.pk), "label": " ", }, ) self.assertEqual(response.status_code, 302) self.account.refresh_from_db() self.assertEqual(self.account.label, "Page 999") from datetime import timedelta from io import StringIO from unittest.mock import patch as _patch from django.core.management import call_command from django.utils import timezone class DispatchDueSocialTests(TestCase): def test_enqueues_past_scheduled_social_post(self): post = SocialPost.objects.create( body="Open house", status=SocialPost.Status.SCHEDULED, scheduled_for=timezone.now() - timedelta(minutes=5), ) mock_task = _patch("social.tasks.publish_social_post").start() self.addCleanup(_patch.stopall) out = StringIO() call_command("dispatch_due", stdout=out) post.refresh_from_db() self.assertEqual(post.status, SocialPost.Status.QUEUED) mock_task.enqueue.assert_called_once_with(post_id=str(post.pk)) self.assertIn("Enqueued 1 due item(s).", out.getvalue())