1import os
2import unittest
3from unittest import mock
4
5from django.conf import settings
6from django.core import mail
7from django.test import RequestFactory, TestCase
8
9from contact_form.forms import AkismetContactForm, ContactForm
10
11
12class ContactFormTests(TestCase):
13    """
14    Tests the base ContactForm.
15
16    """
17
18    valid_data = {"name": "Test", "email": "test@example.com", "body": "Test message"}
19
20    def request(self):
21        return RequestFactory().request()
22
23    def test_request_required(self):
24        """
25        Can't instantiate without an HttpRequest.
26
27        """
28        self.assertRaises(TypeError, ContactForm)
29
30    def test_valid_data_required(self):
31        """
32        Can't try to build the message dict unless data is valid.
33
34        """
35        data = {"name": "Test", "body": "Test message"}
36        form = ContactForm(request=self.request(), data=data)
37        self.assertRaises(ValueError, form.get_message_dict)
38        self.assertRaises(ValueError, form.get_context)
39
40    def test_send(self):
41        """
42        Valid form can and does in fact send email.
43
44        """
45        form = ContactForm(request=self.request(), data=self.valid_data)
46        self.assertTrue(form.is_valid())
47
48        form.save()
49        self.assertEqual(1, len(mail.outbox))
50
51        message = mail.outbox[0]
52        self.assertTrue(self.valid_data["body"] in message.body)
53        self.assertEqual(settings.DEFAULT_FROM_EMAIL, message.from_email)
54        self.assertEqual(form.recipient_list, message.recipients())
55
56    def test_no_sites(self):
57        """
58        Sites integration works with or without installed
59        contrib.sites.
60
61        """
62        with self.modify_settings(INSTALLED_APPS={"remove": ["django.contrib.sites"]}):
63            form = ContactForm(request=self.request(), data=self.valid_data)
64            self.assertTrue(form.is_valid())
65
66            form.save()
67            self.assertEqual(1, len(mail.outbox))
68
69    def test_recipient_list(self):
70        """
71        Passing recipient_list when instantiating ContactForm properly
72        overrides the list of recipients.
73
74        """
75        recipient_list = ["recipient_list@example.com"]
76        form = ContactForm(
77            request=self.request(), data=self.valid_data, recipient_list=recipient_list
78        )
79        self.assertTrue(form.is_valid())
80
81        form.save()
82        self.assertEqual(1, len(mail.outbox))
83
84        message = mail.outbox[0]
85        self.assertEqual(recipient_list, message.recipients())
86
87    def test_callable_template_name(self):
88        """
89        When a template_name() method is defined, it is used and
90        preferred over a 'template_name' attribute.
91
92        """
93
94        class CallableTemplateName(ContactForm):
95            def template_name(self):
96                return "contact_form/test_callable_template_name.html"
97
98        form = CallableTemplateName(request=self.request(), data=self.valid_data)
99        self.assertTrue(form.is_valid())
100
101        form.save()
102        self.assertEqual(1, len(mail.outbox))
103
104        message = mail.outbox[0]
105        self.assertTrue("Callable template_name used." in message.body)
106
107    def test_callable_message_parts(self):
108        """
109        Message parts implemented as methods are called and preferred
110        over attributes.
111
112        """
113        overridden_data = {
114            "from_email": "override@example.com",
115            "message": "Overridden message.",
116            "recipient_list": ["override_recpt@example.com"],
117            "subject": "Overridden subject",
118        }
119
120        class CallableMessageParts(ContactForm):
121            def from_email(self):
122                return overridden_data["from_email"]
123
124            def message(self):
125                return overridden_data["message"]
126
127            def recipient_list(self):
128                return overridden_data["recipient_list"]
129
130            def subject(self):
131                return overridden_data["subject"]
132
133        form = CallableMessageParts(request=self.request(), data=self.valid_data)
134        self.assertTrue(form.is_valid())
135
136        self.assertEqual(overridden_data, form.get_message_dict())
137
138
139@unittest.skipUnless(
140    getattr(settings, "AKISMET_API_KEY", os.getenv("PYTHON_AKISMET_API_KEY"))
141    is not None,
142    "AkismetContactForm requires Akismet configuration",
143)
144class AkismetContactFormTests(TestCase):
145    """
146    Tests the Akismet contact form.
147
148    """
149
150    def request(self):
151        return RequestFactory().request()
152
153    def test_akismet_form_spam(self):
154        """
155        The Akismet contact form correctly rejects spam.
156
157        """
158        data = {
159            "name": "viagra-test-123",
160            "email": "email@example.com",
161            "body": "This is spam.",
162        }
163        with mock.patch("akismet.Akismet", autospec=True) as akismet_mock:
164            instance = akismet_mock.return_value
165            instance.verify_key.return_value = True
166            instance.comment_check.return_value = True
167            form = AkismetContactForm(request=self.request(), data=data)
168            self.assertFalse(form.is_valid())
169            self.assertTrue(str(form.SPAM_MESSAGE) in form.errors["body"])
170
171    def test_akismet_form_ham(self):
172        """
173        The Akismet contact form correctly accepts non-spam.
174
175        """
176        data = {"name": "Test", "email": "email@example.com", "body": "Test message."}
177        with mock.patch("akismet.Akismet", autospec=True) as akismet_mock:
178            instance = akismet_mock.return_value
179            instance.verify_key.return_value = True
180            instance.comment_check.return_value = False
181            form = AkismetContactForm(request=self.request(), data=data)
182            self.assertTrue(form.is_valid())
183
184    def test_akismet_form_no_body(self):
185        """
186        The Akismet contact form correctly skips validation when no email
187        body is provided.
188
189        """
190        data = {"name": "Test", "email": "email@example.com"}
191        with mock.patch("akismet.Akismet", autospec=True) as akismet_mock:
192            form = AkismetContactForm(request=self.request(), data=data)
193            self.assertFalse(form.is_valid())
194            akismet_mock.assert_not_called()
195            self.assertFalse(form.is_valid())
196