class CourseRunMarketingSitePublisherTests(MarketingSitePublisherTestMixin):
    """
    Tests covering course run-specific publishing logic.
    """
    def setUp(self):
        super().setUp()

        self.partner = PartnerFactory()
        self.publisher = CourseRunMarketingSitePublisher(self.partner)

        self.api_root = self.publisher.client.api_url
        self.username = self.publisher.client.username

        self.obj = CourseRunFactory()

    @mock.patch.object(CourseRunMarketingSitePublisher,
                       'node_id',
                       return_value='node_id')
    @mock.patch.object(CourseRunMarketingSitePublisher,
                       'serialize_obj',
                       return_value='data')
    @mock.patch.object(CourseRunMarketingSitePublisher,
                       'edit_node',
                       return_value=None)
    def test_publish_obj(self, mock_edit_node, *args):  # pylint: disable=unused-argument
        """
        Verify that the publisher attempts to publish when course run status changes.
        """
        # No previous object. No editing should occur.
        self.publisher.publish_obj(self.obj)
        assert not mock_edit_node.called

        # A previous object is provided, but the status hasn't changed.
        # No editing should occur.
        self.publisher.publish_obj(self.obj, previous_obj=self.obj)
        assert not mock_edit_node.called

        # A previous object is provided, and the status has changed.
        # Editing should occur.
        previous_obj = CourseRunFactory(status=CourseRunStatus.Unpublished)
        self.publisher.publish_obj(self.obj, previous_obj=previous_obj)
        mock_edit_node.assert_called_with('node_id', 'data')

    @responses.activate
    def test_serialize_obj(self):
        """
        Verify that the publisher serializes data required to publish course runs.
        """
        self.mock_api_client()

        actual = self.publisher.serialize_obj(self.obj)
        expected = {
            'field_course_id': self.obj.key,
            'author': {
                'id': self.user_id
            },
            'status': 1,
        }

        assert actual == expected

        self.obj.status = CourseRunStatus.Unpublished

        actual = self.publisher.serialize_obj(self.obj)
        expected['status'] = 0

        assert actual == expected
class CourseRunMarketingSitePublisherTests(MarketingSitePublisherTestMixin):
    """
    Tests covering course run-specific publishing logic.
    """
    def setUp(self):
        super().setUp()

        self.partner = PartnerFactory()
        self.publisher = CourseRunMarketingSitePublisher(self.partner)

        self.api_root = self.publisher.client.api_url
        self.username = self.publisher.client.username

        self.obj = CourseRunFactory()

    @mock.patch.object(CourseRunMarketingSitePublisher,
                       'node_id',
                       return_value=None)
    @mock.patch.object(CourseRunMarketingSitePublisher, 'create_node')
    def test_publish_obj_create_disabled(self, mock_create_node, mock_node_id):
        self.publisher.publish_obj(self.obj)
        mock_node_id.assert_called_with(self.obj)
        assert not mock_create_node.called

    @mock.patch.object(CourseRunMarketingSitePublisher,
                       'serialize_obj',
                       return_value={'data': 'test'})
    @mock.patch.object(CourseRunMarketingSitePublisher,
                       'node_id',
                       return_value=None)
    @mock.patch.object(CourseRunMarketingSitePublisher,
                       'create_node',
                       return_value='node_id')
    @mock.patch.object(CourseRunMarketingSitePublisher, 'update_node_alias')
    @override_switch('auto_course_about_page_creation', True)
    def test_publish_obj_create_successful(self, mock_update_node_alias,
                                           mock_create_node, *args):  # pylint: disable=unused-argument
        self.publisher.publish_obj(self.obj)
        mock_create_node.assert_called_with({
            'data':
            'test',
            'field_course_uuid':
            str(self.obj.uuid)
        })
        mock_update_node_alias.assert_called_with(self.obj, 'node_id', None)

    @mock.patch.object(CourseRunMarketingSitePublisher,
                       'node_id',
                       return_value=None)
    @mock.patch.object(CourseRunMarketingSitePublisher,
                       'serialize_obj',
                       return_value={'data': 'test'})
    @mock.patch.object(CourseRunMarketingSitePublisher,
                       'create_node',
                       return_value='node1')
    @mock.patch.object(CourseRunMarketingSitePublisher, 'update_node_alias')
    @override_switch('auto_course_about_page_creation', True)
    def test_publish_obj_create_if_exists_on_discovery(self,
                                                       mock_update_node_alias,
                                                       mock_create_node,
                                                       mock_serialize_obj,
                                                       mock_node_id, *args):  # pylint: disable=unused-argument
        self.publisher.publish_obj(self.obj)
        mock_node_id.assert_called_with(self.obj)
        mock_serialize_obj.assert_called_with(self.obj)
        mock_create_node.assert_called_with({
            'data':
            'test',
            'field_course_uuid':
            str(self.obj.uuid)
        })
        mock_update_node_alias.assert_called_with(self.obj, 'node1', None)

    @mock.patch.object(CourseRunMarketingSitePublisher,
                       'node_id',
                       return_value='node_id')
    @mock.patch.object(CourseRunMarketingSitePublisher,
                       'serialize_obj',
                       return_value='data')
    @mock.patch.object(CourseRunMarketingSitePublisher,
                       'edit_node',
                       return_value=None)
    @mock.patch.object(CourseRunMarketingSitePublisher, 'update_node_alias')
    def test_publish_obj_edit(self, mock_node_alias, mock_edit_node, *args):  # pylint: disable=unused-argument
        """
        Verify that the publisher attempts to publish when course run status changes.
        """

        # A previous object is provided, but the status hasn't changed.
        # No editing should occur.
        self.publisher.publish_obj(self.obj, previous_obj=self.obj)
        assert not mock_edit_node.called

        # A previous object is provided, and the status has changed.
        # Editing should occur.
        previous_obj = CourseRunFactory(status=CourseRunStatus.Unpublished)
        self.publisher.publish_obj(self.obj, previous_obj=previous_obj)
        mock_edit_node.assert_called_with('node_id', 'data')
        mock_node_alias.assert_called_with(self.obj, 'node_id', None)

    @responses.activate
    def test_serialize_obj(self):
        """
        Verify that the publisher serializes data required to publish course runs.
        """
        self.mock_api_client()

        actual = self.publisher.serialize_obj(self.obj)
        expected = {
            'field_course_id': self.obj.key,
            'title': self.obj.title,
            'author': {
                'id': self.user_id
            },
            'status': 1,
            'type': 'course',
        }

        assert actual == expected

        self.obj.status = CourseRunStatus.Unpublished

        actual = self.publisher.serialize_obj(self.obj)
        expected['status'] = 0

        assert actual == expected

    @responses.activate
    def test_update_node_alias(self):
        """
        Verify that the publisher attempts to create a new alias associated with the new course_run,
        and that appropriate exceptions are raised for non-200 status codes.
        """
        # No previous object is provided. Create a new node and make sure
        # title alias created, by default, based on the title is deleted
        # and a new alias based on marketing slug is created.
        self.mock_api_client()
        self.mock_get_alias_form()
        self.mock_get_delete_form(self.obj.slug)
        self.mock_delete_alias()
        self.mock_get_delete_form(self.obj.slug)
        self.mock_add_alias()

        self.publisher.update_node_alias(self.obj, self.node_id, None)

        assert responses.calls[-1].request.url == '{}/add'.format(
            self.publisher.alias_api_base)

        responses.reset()

        # Same scenario, but this time a non-200 status code is returned during
        # alias creation. An exception should be raised.
        self.mock_api_client()
        self.mock_get_alias_form()
        self.mock_get_delete_form(self.obj.slug)
        self.mock_delete_alias()
        self.mock_get_delete_form(self.obj.slug)
        self.mock_add_alias(status=500)

        with pytest.raises(AliasCreateError):
            self.publisher.update_node_alias(self.obj, self.node_id, None)

        responses.reset()

        # A previous object is provided, but the marketing slug hasn't changed.
        # Neither alias creation nor alias deletion should occur.
        self.mock_api_client()
        self.mock_get_delete_form(self.obj.slug)

        self.publisher.update_node_alias(self.obj, self.node_id, self.obj)

        responses.reset()

        # In this case, similate the fact that alias form retrival returned error
        # FormRetrievalError should be raised
        self.mock_api_client()
        self.mock_get_delete_form(self.obj.slug)
        self.mock_get_alias_form(status=500)
        with pytest.raises(FormRetrievalError):
            self.publisher.update_node_alias(self.obj, self.node_id, None)

    def test_alias(self):
        """
        Verify that aliases are constructed correctly.
        """
        actual = self.publisher.alias(self.obj)
        expected = 'course/{slug}'.format(slug=self.obj.slug)

        assert actual == expected