Commit cfe5ad4b by Simon Chen

ECOM-5402 Delete the drupal node when program is hard deleted

parent faef420e
...@@ -4,3 +4,8 @@ from django.apps import AppConfig ...@@ -4,3 +4,8 @@ from django.apps import AppConfig
class CourseMetadataConfig(AppConfig): class CourseMetadataConfig(AppConfig):
name = 'course_discovery.apps.course_metadata' name = 'course_discovery.apps.course_metadata'
verbose_name = 'Course Metadata' verbose_name = 'Course Metadata'
def ready(self):
super(CourseMetadataConfig, self).ready()
# noinspection PyUnresolvedReferences
import course_discovery.apps.course_metadata.signals # pylint: disable=unused-variable
...@@ -89,6 +89,27 @@ class MarketingSitePublisher(object): ...@@ -89,6 +89,27 @@ class MarketingSitePublisher(object):
'title': program_before.title, 'title': program_before.title,
} }
def _get_api_client(self, program):
if not program.partner.has_marketing_site:
return
if not (program.partner.marketing_site_api_username and program.partner.marketing_site_api_password):
msg = 'Marketing Site API credentials are not properly configured for Partner [{partner}]!'.format(
partner=program.partner.short_code)
raise ProgramPublisherException(msg)
if self.data_before and \
all(self.data_before[key] == getattr(program, key) for key in ['title', 'status', 'type']):
# We don't need to publish to marketing site because
# nothing we care about has changed. This would save at least 4 network calls
return
return MarketingSiteAPIClient(
program.partner.marketing_site_api_username,
program.partner.marketing_site_api_password,
program.partner.marketing_site_url_root
)
def _get_node_data(self, program, user_id): def _get_node_data(self, program, user_id):
return { return {
'type': str(program.type).lower(), 'type': str(program.type).lower(),
...@@ -128,34 +149,25 @@ class MarketingSitePublisher(object): ...@@ -128,34 +149,25 @@ class MarketingSitePublisher(object):
else: else:
raise ProgramPublisherException("Marketing site page creation failed!") raise ProgramPublisherException("Marketing site page creation failed!")
def publish_program(self, program): def _delete_node(self, api_client, nid):
if not program.partner.has_marketing_site: node_url = '{root}/node.json/{nid}'.format(root=api_client.api_url, nid=nid)
return api_client.api_session.delete(node_url)
if not (program.partner.marketing_site_api_username and program.partner.marketing_site_api_password):
msg = 'Marketing Site API credentials are not properly configured for Partner [{partner}]!'.format(
partner=program.partner.short_code)
raise ProgramPublisherException(msg)
if self.data_before and \
self.data_before.get('title') == program.title and \
self.data_before.get('status') == program.status and \
self.data_before.get('type') == program.type:
# We don't need to publish to marketing site because
# nothing we care about has changed. This would save at least 4 network calls
return
api_client = MarketingSiteAPIClient(
program.partner.marketing_site_api_username,
program.partner.marketing_site_api_password,
program.partner.marketing_site_url_root
)
node_data = self._get_node_data(program, api_client.user_id) def publish_program(self, program):
nid = self._get_node_id(api_client, program.uuid) api_client = self._get_api_client(program)
if nid: if api_client:
# We would like to edit the existing node node_data = self._get_node_data(program, api_client.user_id)
self._edit_node(api_client, nid, node_data) nid = self._get_node_id(api_client, program.uuid)
else: if nid:
# We should create a new node # We would like to edit the existing node
self._create_node(api_client, node_data) self._edit_node(api_client, nid, node_data)
else:
# We should create a new node
self._create_node(api_client, node_data)
def delete_program(self, program):
api_client = self._get_api_client(program)
if api_client:
nid = self._get_node_id(api_client, program.uuid)
if nid:
self._delete_node(api_client, nid)
from django.db.models.signals import pre_delete
from django.dispatch import receiver
import waffle
from course_discovery.apps.course_metadata.models import Program
from course_discovery.apps.course_metadata.publishers import MarketingSitePublisher
@receiver(pre_delete, sender=Program)
def delete_program(sender, instance, **kwargs): # pylint: disable=unused-argument
if waffle.switch_is_active('publish_program_to_marketing_site') and \
instance.partner.has_marketing_site:
publisher = MarketingSitePublisher()
publisher.delete_program(instance)
import json import json
from django.test import TestCase
from factory.fuzzy import FuzzyText, FuzzyInteger from factory.fuzzy import FuzzyText, FuzzyInteger
import responses import responses
from course_discovery.apps.core.tests.utils import FuzzyUrlRoot from course_discovery.apps.core.tests.utils import FuzzyUrlRoot
class MarketingSiteAPIClientTestMixin(object): class MarketingSiteAPIClientTestMixin(TestCase):
""" """
The mixing to help mock the responses for marketing site API Client The mixing to help mock the responses for marketing site API Client
""" """
...@@ -70,6 +71,9 @@ class MarketingSiteAPIClientTestMixin(object): ...@@ -70,6 +71,9 @@ class MarketingSiteAPIClientTestMixin(object):
match_querystring=True match_querystring=True
) )
def assert_responses_call_count(self, count):
self.assertEqual(len(responses.calls), count)
class MarketingSitePublisherTestMixin(MarketingSiteAPIClientTestMixin): class MarketingSitePublisherTestMixin(MarketingSiteAPIClientTestMixin):
""" """
...@@ -121,3 +125,12 @@ class MarketingSitePublisherTestMixin(MarketingSiteAPIClientTestMixin): ...@@ -121,3 +125,12 @@ class MarketingSitePublisherTestMixin(MarketingSiteAPIClientTestMixin):
content_type='application/json', content_type='application/json',
status=status status=status
) )
def mock_node_delete(self, status):
responses.add(
responses.DELETE,
'{root}/node.json/{nid}'.format(root=self.api_root, nid=self.nid),
body='',
content_type='text/html',
status=status
)
...@@ -268,7 +268,7 @@ class AbstractValueModelTests(TestCase): ...@@ -268,7 +268,7 @@ class AbstractValueModelTests(TestCase):
@ddt.ddt @ddt.ddt
class ProgramTests(MarketingSitePublisherTestMixin, TestCase): class ProgramTests(MarketingSitePublisherTestMixin):
"""Tests of the Program model.""" """Tests of the Program model."""
def setUp(self): def setUp(self):
...@@ -395,7 +395,12 @@ class ProgramTests(MarketingSitePublisherTestMixin, TestCase): ...@@ -395,7 +395,12 @@ class ProgramTests(MarketingSitePublisherTestMixin, TestCase):
def test_save_without_publish(self): def test_save_without_publish(self):
self.program.title = FuzzyText().fuzz() self.program.title = FuzzyText().fuzz()
self.program.save() self.program.save()
self.assertEqual(len(responses.calls), 0) self.assert_responses_call_count(0)
@responses.activate
def test_delete_without_publish(self):
self.program.delete()
self.assert_responses_call_count(0)
@responses.activate @responses.activate
def test_save_and_publish_success(self): def test_save_and_publish_success(self):
...@@ -409,8 +414,7 @@ class ProgramTests(MarketingSitePublisherTestMixin, TestCase): ...@@ -409,8 +414,7 @@ class ProgramTests(MarketingSitePublisherTestMixin, TestCase):
toggle_switch('publish_program_to_marketing_site', True) toggle_switch('publish_program_to_marketing_site', True)
self.program.title = FuzzyText().fuzz() self.program.title = FuzzyText().fuzz()
self.program.save() self.program.save()
self.assertEqual(len(responses.calls), 6) self.assert_responses_call_count(6)
toggle_switch('publish_program_to_marketing_site', False)
@responses.activate @responses.activate
def test_save_and_no_marketing_site(self): def test_save_and_no_marketing_site(self):
...@@ -419,8 +423,28 @@ class ProgramTests(MarketingSitePublisherTestMixin, TestCase): ...@@ -419,8 +423,28 @@ class ProgramTests(MarketingSitePublisherTestMixin, TestCase):
toggle_switch('publish_program_to_marketing_site', True) toggle_switch('publish_program_to_marketing_site', True)
self.program.title = FuzzyText().fuzz() self.program.title = FuzzyText().fuzz()
self.program.save() self.program.save()
self.assertEqual(len(responses.calls), 0) self.assert_responses_call_count(0)
toggle_switch('publish_program_to_marketing_site', False)
@responses.activate
def test_delete_and_publish_success(self):
self.program.partner.marketing_site_url_root = self.api_root
self.program.partner.marketing_site_api_username = self.username
self.program.partner.marketing_site_api_password = self.password
self.program.save()
self.mock_api_client(200)
self.mock_node_retrieval(self.program.uuid)
self.mock_node_delete(204)
toggle_switch('publish_program_to_marketing_site', True)
self.program.delete()
self.assert_responses_call_count(5)
@responses.activate
def test_delete_and_no_marketing_site(self):
self.program.partner.marketing_site_url_root = None
self.program.save()
toggle_switch('publish_program_to_marketing_site', True)
self.program.delete()
self.assert_responses_call_count(0)
def test_course_update_caught_exception(self): def test_course_update_caught_exception(self):
""" Test that the index update process failing will not cause the program save to error """ """ Test that the index update process failing will not cause the program save to error """
......
from django.test import TestCase
import responses import responses
from course_discovery.apps.course_metadata.publishers import ( from course_discovery.apps.course_metadata.publishers import (
...@@ -14,7 +13,7 @@ from course_discovery.apps.course_metadata.tests.mixins import ( ...@@ -14,7 +13,7 @@ from course_discovery.apps.course_metadata.tests.mixins import (
from course_discovery.apps.course_metadata.models import Program from course_discovery.apps.course_metadata.models import Program
class MarketingSiteAPIClientTests(MarketingSiteAPIClientTestMixin, TestCase): class MarketingSiteAPIClientTests(MarketingSiteAPIClientTestMixin):
""" """
Unit test cases for MarketinSiteAPIClient Unit test cases for MarketinSiteAPIClient
""" """
...@@ -30,7 +29,7 @@ class MarketingSiteAPIClientTests(MarketingSiteAPIClientTestMixin, TestCase): ...@@ -30,7 +29,7 @@ class MarketingSiteAPIClientTests(MarketingSiteAPIClientTestMixin, TestCase):
def test_init_session(self): def test_init_session(self):
self.mock_login_response(200) self.mock_login_response(200)
session = self.api_client.init_session session = self.api_client.init_session
self.assertEqual(len(responses.calls), 2) self.assert_responses_call_count(2)
self.assertIsNotNone(session) self.assertIsNotNone(session)
@responses.activate @responses.activate
...@@ -44,7 +43,7 @@ class MarketingSiteAPIClientTests(MarketingSiteAPIClientTestMixin, TestCase): ...@@ -44,7 +43,7 @@ class MarketingSiteAPIClientTests(MarketingSiteAPIClientTestMixin, TestCase):
self.mock_login_response(200) self.mock_login_response(200)
self.mock_csrf_token_response(200) self.mock_csrf_token_response(200)
csrf_token = self.api_client.csrf_token csrf_token = self.api_client.csrf_token
self.assertEqual(len(responses.calls), 3) self.assert_responses_call_count(3)
self.assertEqual(self.csrf_token, csrf_token) self.assertEqual(self.csrf_token, csrf_token)
@responses.activate @responses.activate
...@@ -59,7 +58,7 @@ class MarketingSiteAPIClientTests(MarketingSiteAPIClientTestMixin, TestCase): ...@@ -59,7 +58,7 @@ class MarketingSiteAPIClientTests(MarketingSiteAPIClientTestMixin, TestCase):
self.mock_login_response(200) self.mock_login_response(200)
self.mock_user_id_response(200) self.mock_user_id_response(200)
user_id = self.api_client.user_id user_id = self.api_client.user_id
self.assertEqual(len(responses.calls), 3) self.assert_responses_call_count(3)
self.assertEqual(self.user_id, user_id) self.assertEqual(self.user_id, user_id)
@responses.activate @responses.activate
...@@ -74,7 +73,7 @@ class MarketingSiteAPIClientTests(MarketingSiteAPIClientTestMixin, TestCase): ...@@ -74,7 +73,7 @@ class MarketingSiteAPIClientTests(MarketingSiteAPIClientTestMixin, TestCase):
self.mock_login_response(200) self.mock_login_response(200)
self.mock_csrf_token_response(200) self.mock_csrf_token_response(200)
api_session = self.api_client.api_session api_session = self.api_client.api_session
self.assertEqual(len(responses.calls), 3) self.assert_responses_call_count(3)
self.assertIsNotNone(api_session) self.assertIsNotNone(api_session)
self.assertEqual(api_session.headers.get('Content-Type'), 'application/json') self.assertEqual(api_session.headers.get('Content-Type'), 'application/json')
self.assertEqual(api_session.headers.get('X-CSRF-Token'), self.csrf_token) self.assertEqual(api_session.headers.get('X-CSRF-Token'), self.csrf_token)
...@@ -87,7 +86,7 @@ class MarketingSiteAPIClientTests(MarketingSiteAPIClientTestMixin, TestCase): ...@@ -87,7 +86,7 @@ class MarketingSiteAPIClientTests(MarketingSiteAPIClientTestMixin, TestCase):
self.api_client.api_session # pylint: disable=pointless-statement self.api_client.api_session # pylint: disable=pointless-statement
class MarketingSitePublisherTests(MarketingSitePublisherTestMixin, TestCase): class MarketingSitePublisherTests(MarketingSitePublisherTestMixin):
""" """
Unit test cases for the MarketingSitePublisher Unit test cases for the MarketingSitePublisher
""" """
...@@ -125,7 +124,7 @@ class MarketingSitePublisherTests(MarketingSitePublisherTestMixin, TestCase): ...@@ -125,7 +124,7 @@ class MarketingSitePublisherTests(MarketingSitePublisherTestMixin, TestCase):
self.mock_node_retrieval(self.program.uuid) self.mock_node_retrieval(self.program.uuid)
publisher = MarketingSitePublisher() publisher = MarketingSitePublisher()
node_id = publisher._get_node_id(self.api_client, self.program.uuid) # pylint: disable=protected-access node_id = publisher._get_node_id(self.api_client, self.program.uuid) # pylint: disable=protected-access
self.assertEqual(len(responses.calls), 4) self.assert_responses_call_count(4)
self.assertEqual(node_id, self.nid) self.assertEqual(node_id, self.nid)
@responses.activate @responses.activate
...@@ -143,7 +142,7 @@ class MarketingSitePublisherTests(MarketingSitePublisherTestMixin, TestCase): ...@@ -143,7 +142,7 @@ class MarketingSitePublisherTests(MarketingSitePublisherTestMixin, TestCase):
publisher = MarketingSitePublisher() publisher = MarketingSitePublisher()
publish_data = publisher._get_node_data(self.program, self.user_id) # pylint: disable=protected-access publish_data = publisher._get_node_data(self.program, self.user_id) # pylint: disable=protected-access
publisher._edit_node(self.api_client, self.nid, publish_data) # pylint: disable=protected-access publisher._edit_node(self.api_client, self.nid, publish_data) # pylint: disable=protected-access
self.assertEqual(len(responses.calls), 4) self.assert_responses_call_count(4)
@responses.activate @responses.activate
def test_edit_node_failed(self): def test_edit_node_failed(self):
...@@ -189,7 +188,7 @@ class MarketingSitePublisherTests(MarketingSitePublisherTestMixin, TestCase): ...@@ -189,7 +188,7 @@ class MarketingSitePublisherTests(MarketingSitePublisherTestMixin, TestCase):
self.mock_node_create(expected, 201) self.mock_node_create(expected, 201)
publisher = MarketingSitePublisher() publisher = MarketingSitePublisher()
publisher.publish_program(self.program) publisher.publish_program(self.program)
self.assertEqual(len(responses.calls), 6) self.assert_responses_call_count(6)
@responses.activate @responses.activate
def test_publish_program_edit(self): def test_publish_program_edit(self):
...@@ -198,7 +197,7 @@ class MarketingSitePublisherTests(MarketingSitePublisherTestMixin, TestCase): ...@@ -198,7 +197,7 @@ class MarketingSitePublisherTests(MarketingSitePublisherTestMixin, TestCase):
self.mock_node_edit(200) self.mock_node_edit(200)
publisher = MarketingSitePublisher() publisher = MarketingSitePublisher()
publisher.publish_program(self.program) publisher.publish_program(self.program)
self.assertEqual(len(responses.calls), 6) self.assert_responses_call_count(6)
@responses.activate @responses.activate
def test_publish_modified_program(self): def test_publish_modified_program(self):
...@@ -208,11 +207,38 @@ class MarketingSitePublisherTests(MarketingSitePublisherTestMixin, TestCase): ...@@ -208,11 +207,38 @@ class MarketingSitePublisherTests(MarketingSitePublisherTestMixin, TestCase):
program_before = ProgramFactory() program_before = ProgramFactory()
publisher = MarketingSitePublisher(program_before) publisher = MarketingSitePublisher(program_before)
publisher.publish_program(self.program) publisher.publish_program(self.program)
self.assertEqual(len(responses.calls), 6) self.assert_responses_call_count(6)
@responses.activate @responses.activate
def test_publish_unmodified_program(self): def test_publish_unmodified_program(self):
self.mock_api_client(200) self.mock_api_client(200)
publisher = MarketingSitePublisher(self.program) publisher = MarketingSitePublisher(self.program)
publisher.publish_program(self.program) publisher.publish_program(self.program)
self.assertEqual(len(responses.calls), 0) self.assert_responses_call_count(0)
@responses.activate
def test_publish_program_no_credential(self):
self.program.partner.marketing_site_api_password = None
self.program.partner.marketing_site_api_username = None
self.program.save() # pylint: disable=no-member
publisher = MarketingSitePublisher()
with self.assertRaises(ProgramPublisherException):
publisher.publish_program(self.program)
self.assert_responses_call_count(0)
@responses.activate
def test_publish_delete_program(self):
self.mock_api_client(200)
self.mock_node_retrieval(self.program.uuid)
self.mock_node_delete(204)
publisher = MarketingSitePublisher()
publisher.delete_program(self.program)
self.assert_responses_call_count(5)
@responses.activate
def test_publish_delete_non_existent_program(self):
self.mock_api_client(200)
self.mock_node_retrieval(self.program.uuid, exists=False)
publisher = MarketingSitePublisher()
publisher.delete_program(self.program)
self.assert_responses_call_count(4)
# pylint: disable=no-member
from unittest.mock import patch
from django.test import TestCase
from course_discovery.apps.course_metadata.tests import factories, toggle_switch
MARKETING_SITE_PUBLISHERS_MODULE = 'course_discovery.apps.course_metadata.publishers.MarketingSitePublisher'
@patch(MARKETING_SITE_PUBLISHERS_MODULE + '.delete_program')
class SignalsTest(TestCase):
def setUp(self):
super(SignalsTest, self).setUp()
self.program = factories.ProgramFactory()
def test_delete_program_signal_no_publish(self, delete_program_mock):
toggle_switch('publish_program_to_marketing_site', False)
self.program.delete()
self.assertFalse(delete_program_mock.called)
def test_delete_program_signal_with_publish(self, delete_program_mock):
toggle_switch('publish_program_to_marketing_site', True)
self.program.delete()
delete_program_mock.assert_called_once_with(self.program)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment