diff --git a/zerver/fixtures/freshdesk/freshdesk_priority_changed.json b/zerver/fixtures/freshdesk/freshdesk_priority_changed.json
new file mode 100644
index 0000000000..b2c7f01b73
--- /dev/null
+++ b/zerver/fixtures/freshdesk/freshdesk_priority_changed.json
@@ -0,0 +1,14 @@
+{"freshdesk_webhook":
+ {
+ "triggered_event":"{priority:{from:3,to:1}}",
+ "ticket_id":"11",
+ "ticket_url":"http://test1234zzz.freshdesk.com/helpdesk/tickets/11",
+ "ticket_type":"Incident",
+ "ticket_subject":"Test ticket subject",
+ "ticket_description":"
Test ticket description.
",
+ "ticket_status":"Resolved",
+ "ticket_priority":"Low",
+ "requester_name":"Requester Bob",
+ "requester_email":"requester-bob@example.com",
+ }
+}
\ No newline at end of file
diff --git a/zerver/fixtures/freshdesk/freshdesk_private_note.json b/zerver/fixtures/freshdesk/freshdesk_private_note.json
new file mode 100644
index 0000000000..94acc4213f
--- /dev/null
+++ b/zerver/fixtures/freshdesk/freshdesk_private_note.json
@@ -0,0 +1,14 @@
+{"freshdesk_webhook":
+ {
+ "triggered_event":"{note_type:private}",
+ "ticket_id":"11",
+ "ticket_url":"http://test1234zzz.freshdesk.com/helpdesk/tickets/11",
+ "ticket_type":"Incident",
+ "ticket_subject":"Test ticket subject",
+ "ticket_description":"Test ticket description.
",
+ "ticket_status":"Open",
+ "ticket_priority":"Medium",
+ "requester_name":"Requester Bob",
+ "requester_email":"requester-bob@example.com",
+ }
+}
\ No newline at end of file
diff --git a/zerver/fixtures/freshdesk/freshdesk_public_note.json b/zerver/fixtures/freshdesk/freshdesk_public_note.json
new file mode 100644
index 0000000000..b379486190
--- /dev/null
+++ b/zerver/fixtures/freshdesk/freshdesk_public_note.json
@@ -0,0 +1,14 @@
+{"freshdesk_webhook":
+ {
+ "triggered_event":"{note_type:public}",
+ "ticket_id":"11",
+ "ticket_url":"http://test1234zzz.freshdesk.com/helpdesk/tickets/11",
+ "ticket_type":"Incident",
+ "ticket_subject":"Test ticket subject",
+ "ticket_description":"Test ticket description.
",
+ "ticket_status":"Open",
+ "ticket_priority":"Medium",
+ "requester_name":"Requester Bob",
+ "requester_email":"requester-bob@example.com",
+ }
+}
\ No newline at end of file
diff --git a/zerver/fixtures/freshdesk/freshdesk_status_changed.json b/zerver/fixtures/freshdesk/freshdesk_status_changed.json
new file mode 100644
index 0000000000..98c9349abc
--- /dev/null
+++ b/zerver/fixtures/freshdesk/freshdesk_status_changed.json
@@ -0,0 +1,14 @@
+{"freshdesk_webhook":
+ {
+ "triggered_event":"{status:{from:4,to:6}}",
+ "ticket_id":"11",
+ "ticket_url":"http://test1234zzz.freshdesk.com/helpdesk/tickets/11",
+ "ticket_type":"Incident",
+ "ticket_subject":"Test ticket subject",
+ "ticket_description":"Test ticket description.
",
+ "ticket_status":"Waiting on Customer",
+ "ticket_priority":"Low",
+ "requester_name":"Requester Bob",
+ "requester_email":"requester-bob@example.com",
+ }
+}
\ No newline at end of file
diff --git a/zerver/fixtures/freshdesk/freshdesk_ticket_created.json b/zerver/fixtures/freshdesk/freshdesk_ticket_created.json
new file mode 100644
index 0000000000..0879977edc
--- /dev/null
+++ b/zerver/fixtures/freshdesk/freshdesk_ticket_created.json
@@ -0,0 +1,14 @@
+{"freshdesk_webhook":
+ {
+ "triggered_event":"{ticket_action:created}",
+ "ticket_id":"11",
+ "ticket_url":"http://test1234zzz.freshdesk.com/helpdesk/tickets/11",
+ "ticket_type":"Incident",
+ "ticket_subject":"Test ticket subject",
+ "ticket_description":"Test ticket description.
",
+ "ticket_status":"Pending",
+ "ticket_priority":"High",
+ "requester_name":"Requester Bob",
+ "requester_email":"requester-bob@example.com",
+ }
+}
diff --git a/zerver/tests.py b/zerver/tests.py
index b3fdae2eaa..609ec74b5b 100644
--- a/zerver/tests.py
+++ b/zerver/tests.py
@@ -4044,6 +4044,73 @@ class StashHookTests(AuthedTestCase):
* `f259e90`: Updating poms ...""")
+class FreshdeskHookTests(AuthedTestCase):
+ def generate_webhook_response(self, fixture):
+ """
+ Helper function to handle the webhook boilerplate.
+ """
+ email = "hamlet@zulip.com"
+ return self.send_json_payload(
+ email, "/api/v1/external/freshdesk?stream=freshdesk",
+ self.fixture_data("freshdesk", fixture, file_type="json"),
+ stream_name="freshdesk",
+ content_type="application/x-www-form-urlencoded",
+ **self.api_auth(email))
+
+ def test_ticket_creation(self):
+ """
+ Messages are generated on ticket creation through Freshdesk's
+ "Dispatch'r" service.
+ """
+ msg = self.generate_webhook_response("ticket_created")
+ self.assertEqual(msg.subject, u"#11: Test ticket subject")
+ self.assertEqual(msg.content, """Requester Bob created [ticket #11](http://test1234zzz.freshdesk.com/helpdesk/tickets/11):
+
+~~~ quote
+Test ticket description.
+~~~
+
+Type: **Incident**
+Priority: **High**
+Status: **Pending**""")
+
+ def test_status_change(self):
+ """
+ Messages are generated when a ticket's status changes through
+ Freshdesk's "Observer" service.
+ """
+ msg = self.generate_webhook_response("status_changed")
+ self.assertEqual(msg.subject, u"#11: Test ticket subject")
+ self.assertEqual(msg.content, """Requester Bob updated [ticket #11](http://test1234zzz.freshdesk.com/helpdesk/tickets/11):
+
+Status: **Resolved** => **Waiting on Customer**""")
+
+ def test_priority_change(self):
+ """
+ Messages are generated when a ticket's priority changes through
+ Freshdesk's "Observer" service.
+ """
+ msg = self.generate_webhook_response("priority_changed")
+ self.assertEqual(msg.subject, u"#11: Test ticket subject")
+ self.assertEqual(msg.content, """Requester Bob updated [ticket #11](http://test1234zzz.freshdesk.com/helpdesk/tickets/11):
+
+Priority: **High** => **Low**""")
+
+ def note_change(self, fixture, note_type):
+ """
+ Messages are generated when a note gets added to a ticket through
+ Freshdesk's "Observer" service.
+ """
+ msg = self.generate_webhook_response(fixture)
+ self.assertEqual(msg.subject, u"#11: Test ticket subject")
+ self.assertEqual(msg.content, """Requester Bob added a %s note to [ticket #11](http://test1234zzz.freshdesk.com/helpdesk/tickets/11).""" % (note_type,))
+
+ def test_private_note_change(self):
+ self.note_change("private_note", "private")
+
+ def test_public_note_change(self):
+ self.note_change("public_note", "public")
+
class RateLimitTests(AuthedTestCase):
def setUp(self):