# coding: utf-8
#
# Copyright 2018 The Oppia Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS-IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Tests for rights domain objects."""

from __future__ import annotations

import logging

from core import utils
from core.constants import constants
from core.domain import rights_domain
from core.domain import rights_manager
from core.domain import user_services
from core.tests import test_utils

from typing import Sequence


class ActivityRightsTests(test_utils.GenericTestBase):

    def setUp(self) -> None:
        super(ActivityRightsTests, self).setUp()
        self.signup(self.OWNER_EMAIL, self.OWNER_USERNAME)
        self.owner_id = self.get_user_id_from_email(self.OWNER_EMAIL) # type: ignore[no-untyped-call]
        self.owner = user_services.get_user_actions_info(self.owner_id) # type: ignore[no-untyped-call]
        self.signup(self.VIEWER_EMAIL, self.VIEWER_USERNAME)
        self.viewer_id = self.get_user_id_from_email(self.VIEWER_EMAIL) # type: ignore[no-untyped-call]
        self.viewer = user_services.get_user_actions_info(self.viewer_id) # type: ignore[no-untyped-call]

        self.exp_id = 'exp_id'
        self.save_new_valid_exploration(self.exp_id, self.owner_id) # type: ignore[no-untyped-call]
        rights_manager.publish_exploration(self.owner, self.exp_id) # type: ignore[no-untyped-call]
        self.activity_rights = rights_manager.get_exploration_rights( # type: ignore[no-untyped-call]
            self.exp_id)

    def test_validate_community_owned_explorations(self) -> None:
        self.activity_rights.community_owned = True
        with self.assertRaisesRegex( # type: ignore[no-untyped-call]
            Exception,
            'Community-owned explorations should have no owners, '
            'editors, voice artists or viewers specified.'):
            self.activity_rights.validate()

        self.activity_rights.owner_ids = []
        self.activity_rights.status = rights_domain.ACTIVITY_STATUS_PRIVATE
        with self.assertRaisesRegex( # type: ignore[no-untyped-call]
            Exception, 'Community-owned explorations cannot be private'):
            self.activity_rights.validate()

    def test_validate_private_explorations(self) -> None:
        self.activity_rights.viewer_ids = [self.viewer_id]
        with self.assertRaisesRegex( # type: ignore[no-untyped-call]
            Exception, 'Public explorations should have no viewers specified.'):
            self.activity_rights.validate()

    def test_validate_owner_cannot_be_editor(self) -> None:
        self.activity_rights.editor_ids = [self.owner_id]
        with self.assertRaisesRegex( # type: ignore[no-untyped-call]
            Exception, 'A user cannot be both an owner and an editor.'):
            self.activity_rights.validate()

    def test_validate_owner_cannot_be_voice_artist(self) -> None:
        self.activity_rights.voice_artist_ids = [self.owner_id]
        with self.assertRaisesRegex( # type: ignore[no-untyped-call]
            Exception, 'A user cannot be both an owner and a voice artist.'):
            self.activity_rights.validate()

    def test_validate_owner_cannot_be_viewer(self) -> None:
        self.activity_rights.viewer_ids = [self.owner_id]
        self.activity_rights.status = rights_domain.ACTIVITY_STATUS_PRIVATE
        with self.assertRaisesRegex( # type: ignore[no-untyped-call]
            Exception, 'A user cannot be both an owner and a viewer.'):
            self.activity_rights.validate()

    def test_validate_editor_cannot_be_voice_artist(self) -> None:
        self.activity_rights.voice_artist_ids = [self.viewer_id]
        self.activity_rights.editor_ids = [self.viewer_id]
        self.activity_rights.status = rights_domain.ACTIVITY_STATUS_PRIVATE
        with self.assertRaisesRegex( # type: ignore[no-untyped-call]
            Exception, 'A user cannot be both an editor and a voice artist.'):
            self.activity_rights.validate()

    def test_validate_editor_cannot_be_viewer(self) -> None:
        self.activity_rights.viewer_ids = [self.viewer_id]
        self.activity_rights.editor_ids = [self.viewer_id]
        self.activity_rights.status = rights_domain.ACTIVITY_STATUS_PRIVATE
        with self.assertRaisesRegex( # type: ignore[no-untyped-call]
            Exception, 'A user cannot be both an editor and a viewer.'):
            self.activity_rights.validate()

    def test_validate_voice_artist_cannot_be_viewer(self) -> None:
        self.activity_rights.viewer_ids = [self.viewer_id]
        self.activity_rights.voice_artist_ids = [self.viewer_id]
        self.activity_rights.status = rights_domain.ACTIVITY_STATUS_PRIVATE
        with self.assertRaisesRegex( # type: ignore[no-untyped-call]
            Exception, 'A user cannot be both a voice artist and a viewer.'):
            self.activity_rights.validate()

    def test_update_activity_first_published_msec(self) -> None:
        rights_manager.update_activity_first_published_msec( # type: ignore[no-untyped-call]
            constants.ACTIVITY_TYPE_EXPLORATION, self.exp_id, 0.0)

        activity_rights = rights_manager.get_exploration_rights(self.exp_id) # type: ignore[no-untyped-call]
        self.assertEqual(activity_rights.first_published_msec, 0.0)

    def test_cannot_update_activity_first_published_msec_for_invalid_activity(
        self
    ) -> None:
        with self.assertRaisesRegex( # type: ignore[no-untyped-call]
            Exception, 'Cannot get activity rights for unknown activity type'):
            rights_manager.update_activity_first_published_msec( # type: ignore[no-untyped-call]
                'invalid_activity', 'activity_id', 0.0)

    def test_check_cannot_access_activity_with_no_activity_rights(self) -> None:
        self.assertFalse(rights_manager.check_can_access_activity( # type: ignore[no-untyped-call]
            self.owner, None))

    def test_check_cannot_edit_activity_with_no_activity_rights(self) -> None:
        self.assertFalse(rights_manager.check_can_edit_activity( # type: ignore[no-untyped-call]
            self.owner, None))

    def test_check_cannot_voiceover_activity_with_no_activity_rights(
        self
    ) -> None:
        self.assertFalse(rights_manager.check_can_voiceover_activity( # type: ignore[no-untyped-call]
            self.owner, None))

    def test_cannot_save_activity_with_no_activity_rights(self) -> None:
        self.assertFalse(rights_manager.check_can_save_activity( # type: ignore[no-untyped-call]
            self.owner, None))

    def test_check_cannot_delete_activity_with_no_activity_rights(self) -> None:
        self.assertFalse(rights_manager.check_can_delete_activity( # type: ignore[no-untyped-call]
            self.owner, None))

    def test_check_cannot_modify_activity_roles_with_no_activity_rights(
        self
    ) -> None:
        self.assertFalse(rights_manager.check_can_modify_core_activity_roles( # type: ignore[no-untyped-call]
            self.owner, None))

    def test_check_cannot_release_ownership_with_no_activity_rights(
        self
    ) -> None:
        self.assertFalse(rights_manager.check_can_release_ownership( # type: ignore[no-untyped-call]
            self.owner, None))

    def test_check_cannnot_publish_activity_with_no_activity_rights(
        self
    ) -> None:
        self.assertFalse(rights_manager.check_can_publish_activity( # type: ignore[no-untyped-call]
            self.owner, None))

    def test_check_cannot_publish_activity_with_cloned_from(self) -> None:
        self.activity_rights.cloned_from = True
        self.assertFalse(rights_manager.check_can_publish_activity( # type: ignore[no-untyped-call]
            self.owner, self.activity_rights))

    def test_check_cannot_unpublish_activity_with_no_activity_rights(
        self
    ) -> None:
        self.assertFalse(rights_manager.check_can_unpublish_activity( # type: ignore[no-untyped-call]
            self.owner, None))

    def test_cannot_release_ownership_of_exploration_with_insufficient_rights(
        self
    ) -> None:
        observed_log_messages = []

        def _mock_logging_function(msg: str, *args: Sequence[str]) -> None:
            """Mocks logging.error()."""
            observed_log_messages.append(msg % args)

        logging_swap = self.swap(logging, 'error', _mock_logging_function)

        assert_raises_regexp_context_manager = self.assertRaisesRegex( # type: ignore[no-untyped-call]
            Exception, 'The ownership of this exploration cannot be released.')

        with logging_swap, assert_raises_regexp_context_manager:
            rights_manager.release_ownership_of_exploration( # type: ignore[no-untyped-call]
                self.viewer, self.exp_id)

        self.assertEqual(len(observed_log_messages), 1)
        self.assertEqual(
            observed_log_messages[0],
            'User %s tried to release ownership of exploration %s but was '
            'refused permission.' % (self.viewer_id, self.exp_id))

    def test_activity_should_have_atlest_one_owner(self) -> None:
        self.activity_rights.community_owned = False
        self.activity_rights.owner_ids = []

        with self.assertRaisesRegex( # type: ignore[no-untyped-call]
            utils.ValidationError,
            'Activity should have atleast one owner.'):
            self.activity_rights.validate()

    def test_to_dict(self) -> None:
        sample_activity_rights_dict: rights_domain.ActivityRightsDict = {
            'cloned_from': None,
            'status': rights_domain.ACTIVITY_STATUS_PUBLIC,
            'community_owned': False,
            'owner_names': ['owner'],
            'editor_names': [],
            'voice_artist_names': [],
            'viewer_names': [],
            'viewable_if_private': False,
        }
        self.assertEqual(
            self.activity_rights.to_dict(), sample_activity_rights_dict
        )

        self.activity_rights.community_owned = True
        sample_activity_rights_dict['community_owned'] = True
        sample_activity_rights_dict['owner_names'] = []
        self.assertEqual(
            self.activity_rights.to_dict(), sample_activity_rights_dict
        )

    def test_is_editor(self) -> None:
        self.activity_rights.editor_ids = ['123456']
        self.assertTrue(self.activity_rights.is_editor('123456'))
        self.assertFalse(self.activity_rights.is_editor('123457'))

    def test_is_voice_artist(self) -> None:
        self.activity_rights.voice_artist_ids = ['123456']
        self.assertTrue(self.activity_rights.is_voice_artist('123456'))
        self.assertFalse(self.activity_rights.is_voice_artist('123457'))

    def test_is_viewer(self) -> None:
        self.activity_rights.viewer_ids = ['123456']
        self.assertTrue(self.activity_rights.is_viewer('123456'))
        self.assertFalse(self.activity_rights.is_viewer('123457'))

    def test_is_solely_owned_by_user(self) -> None:
        self.activity_rights.owner_ids = ['123456']
        self.assertTrue(self.activity_rights.is_solely_owned_by_user('123456'))

        self.activity_rights.owner_ids = ['123456', '1234567']
        self.assertFalse(self.activity_rights.is_solely_owned_by_user('123456'))

    def test_assign_role_replaces_old_role(self) -> None:
        self.activity_rights.owner_ids = ['123456']
        self.activity_rights.editor_ids = []
        self.activity_rights.viewer_ids = []
        self.activity_rights.voice_artist_ids = []

        self.activity_rights.assign_new_role(
            '123456', rights_domain.ROLE_VOICE_ARTIST)
        self.assertTrue('123456' not in self.activity_rights.owner_ids)
        self.assertTrue('123456' in self.activity_rights.voice_artist_ids)

    def test_assign_new_role(self) -> None:
        self.activity_rights.owner_ids = []
        self.activity_rights.editor_ids = []
        self.activity_rights.viewer_ids = []

        self.activity_rights.assign_new_role('123456', rights_domain.ROLE_OWNER)
        self.assertTrue('123456' in self.activity_rights.owner_ids)

    def test_cannot_assign_same_role(self) -> None:
        self.activity_rights.owner_ids = ['123456']
        self.activity_rights.editor_ids = []
        self.activity_rights.viewer_ids = []

        with self.assertRaisesRegex( # type: ignore[no-untyped-call]
            Exception, 'This user already owns this exploration.'):
            self.activity_rights.assign_new_role(
                '123456', rights_domain.ROLE_OWNER)

        self.activity_rights.assign_new_role(
            '123456', rights_domain.ROLE_EDITOR)
        with self.assertRaisesRegex( # type: ignore[no-untyped-call]
            Exception, 'This user already can edit this exploration.'):
            self.activity_rights.assign_new_role(
                '123456', rights_domain.ROLE_EDITOR)

        self.activity_rights.assign_new_role(
            '123456', rights_domain.ROLE_VOICE_ARTIST)
        with self.assertRaisesRegex( # type: ignore[no-untyped-call]
            Exception, 'This user already can voiceover this exploration.'):
            self.activity_rights.assign_new_role(
                '123456', rights_domain.ROLE_VOICE_ARTIST)

        self.activity_rights.status = rights_domain.ACTIVITY_STATUS_PRIVATE
        self.activity_rights.assign_new_role(
                '123456', rights_domain.ROLE_VIEWER)
        with self.assertRaisesRegex( # type: ignore[no-untyped-call]
            Exception, 'This user already can view this exploration.'):
            self.activity_rights.assign_new_role(
                '123456', rights_domain.ROLE_VIEWER)

    def test_cannot_assign_viewer_to_public_exp(self) -> None:
        self.activity_rights.owner_ids = []
        self.activity_rights.editor_ids = []
        self.activity_rights.viewer_ids = []
        self.activity_rights.status = rights_domain.ACTIVITY_STATUS_PUBLIC

        with self.assertRaisesRegex( # type: ignore[no-untyped-call]
            Exception, 'Public explorations can be viewed by anyone.'):
            self.activity_rights.assign_new_role(
                '123456', rights_domain.ROLE_VIEWER)


class ExplorationRightsChangeTests(test_utils.GenericTestBase):

    def test_exploration_rights_change_object_with_missing_cmd(self) -> None:
        with self.assertRaisesRegex( # type: ignore[no-untyped-call]
            utils.ValidationError, 'Missing cmd key in change dict'):
            rights_domain.ExplorationRightsChange({'invalid': 'data'}) # type: ignore[no-untyped-call]

    def test_exploration_rights_change_object_with_invalid_cmd(self) -> None:
        with self.assertRaisesRegex( # type: ignore[no-untyped-call]
            utils.ValidationError, 'Command invalid is not allowed'):
            rights_domain.ExplorationRightsChange({'cmd': 'invalid'}) # type: ignore[no-untyped-call]

    def test_exploration_rights_change_object_with_missing_attribute_in_cmd(
        self
    ) -> None:
        with self.assertRaisesRegex( # type: ignore[no-untyped-call]
            utils.ValidationError, (
                'The following required attributes are missing: '
                'new_role, old_role')):
            rights_domain.ExplorationRightsChange({ # type: ignore[no-untyped-call]
                'cmd': 'change_role',
                'assignee_id': 'assignee_id',
            })

    def test_exploration_rights_change_object_with_extra_attribute_in_cmd(
        self
    ) -> None:
        with self.assertRaisesRegex( # type: ignore[no-untyped-call]
            utils.ValidationError, (
                'The following extra attributes are present: invalid')):
            rights_domain.ExplorationRightsChange({ # type: ignore[no-untyped-call]
                'cmd': 'change_private_viewability',
                'old_viewable_if_private': 'old_viewable_if_private',
                'new_viewable_if_private': 'new_viewable_if_private',
                'invalid': 'invalid'
            })

    def test_exploration_rights_change_object_with_invalid_role(self) -> None:
        with self.assertRaisesRegex( # type: ignore[no-untyped-call]
            utils.ValidationError, (
                'Value for new_role in cmd change_role: '
                'invalid is not allowed')):
            rights_domain.ExplorationRightsChange({ # type: ignore[no-untyped-call]
                'cmd': 'change_role',
                'assignee_id': 'assignee_id',
                'old_role': rights_domain.ROLE_OWNER,
                'new_role': 'invalid',
            })

    def test_exploration_rights_change_object_with_invalid_status(self) -> None:
        with self.assertRaisesRegex( # type: ignore[no-untyped-call]
            utils.ValidationError, (
                'Value for new_status in cmd change_exploration_status: '
                'invalid is not allowed')):
            rights_domain.ExplorationRightsChange({ # type: ignore[no-untyped-call]
                'cmd': 'change_exploration_status',
                'old_status': rights_domain.ACTIVITY_STATUS_PRIVATE,
                'new_status': 'invalid'
            })

    def test_exploration_rights_change_object_with_create_new(self) -> None:
        exploration_rights_change_object = (
            rights_domain.ExplorationRightsChange({'cmd': 'create_new'})) # type: ignore[no-untyped-call]

        self.assertEqual(exploration_rights_change_object.cmd, 'create_new')

    def test_exploration_rights_change_object_with_change_role(self) -> None:
        exploration_rights_change_object = (
            rights_domain.ExplorationRightsChange({ # type: ignore[no-untyped-call]
                'cmd': 'change_role',
                'assignee_id': 'assignee_id',
                'old_role': rights_domain.ROLE_OWNER,
                'new_role': rights_domain.ROLE_VIEWER
            })
        )

        self.assertEqual(exploration_rights_change_object.cmd, 'change_role')
        self.assertEqual(
            exploration_rights_change_object.assignee_id, 'assignee_id')
        self.assertEqual(
            exploration_rights_change_object.old_role,
            rights_domain.ROLE_OWNER
        )
        self.assertEqual(
            exploration_rights_change_object.new_role,
            rights_domain.ROLE_VIEWER
        )

    def test_exploration_rights_change_object_with_release_ownership(
        self
    ) -> None:
        exploration_rights_change_object = (
            rights_domain.ExplorationRightsChange( # type: ignore[no-untyped-call]
                {'cmd': 'release_ownership'}
            )
        )

        self.assertEqual(
            exploration_rights_change_object.cmd, 'release_ownership')

    def test_exploration_rights_change_object_with_change_private_viewability(
        self
    ) -> None:
        exploration_rights_change_object = (
            rights_domain.ExplorationRightsChange({ # type: ignore[no-untyped-call]
                'cmd': 'change_private_viewability',
                'old_viewable_if_private': 'old_viewable_if_private',
                'new_viewable_if_private': 'new_viewable_if_private'
            })
        )

        self.assertEqual(
            exploration_rights_change_object.cmd, 'change_private_viewability')
        self.assertEqual(
            exploration_rights_change_object.old_viewable_if_private,
            'old_viewable_if_private')
        self.assertEqual(
            exploration_rights_change_object.new_viewable_if_private,
            'new_viewable_if_private')

    def test_exploration_rights_change_object_with_update_first_published_msec(
        self
    ) -> None:
        exploration_rights_change_object = (
            rights_domain.ExplorationRightsChange({ # type: ignore[no-untyped-call]
                'cmd': 'update_first_published_msec',
                'old_first_published_msec': 'old_first_published_msec',
                'new_first_published_msec': 'new_first_published_msec'
            })
        )

        self.assertEqual(
            exploration_rights_change_object.cmd, 'update_first_published_msec')
        self.assertEqual(
            exploration_rights_change_object.old_first_published_msec,
            'old_first_published_msec')
        self.assertEqual(
            exploration_rights_change_object.new_first_published_msec,
            'new_first_published_msec')

    def test_exploration_rights_change_object_with_change_exploration_status(
        self
    ) -> None:
        exploration_rights_change_object = (
            rights_domain.ExplorationRightsChange({ # type: ignore[no-untyped-call]
                'cmd': 'change_exploration_status',
                'old_status': rights_domain.ACTIVITY_STATUS_PRIVATE,
                'new_status': rights_domain.ACTIVITY_STATUS_PUBLIC
            })
        )

        self.assertEqual(
            exploration_rights_change_object.cmd, 'change_exploration_status')
        self.assertEqual(
            exploration_rights_change_object.old_status,
            rights_domain.ACTIVITY_STATUS_PRIVATE)
        self.assertEqual(
            exploration_rights_change_object.new_status,
            rights_domain.ACTIVITY_STATUS_PUBLIC)

    def test_to_dict(self) -> None:
        exploration_rights_change_dict = {
            'cmd': 'change_private_viewability',
            'old_viewable_if_private': 'old_viewable_if_private',
            'new_viewable_if_private': 'new_viewable_if_private'
        }
        exploration_rights_change_object = (
            rights_domain.ExplorationRightsChange( # type: ignore[no-untyped-call]
                exploration_rights_change_dict))
        self.assertEqual(
            exploration_rights_change_object.to_dict(), # type: ignore[no-untyped-call]
            exploration_rights_change_dict)


class CollectionRightsChangeTests(test_utils.GenericTestBase):

    def test_collection_rights_change_object_with_missing_cmd(self) -> None:
        with self.assertRaisesRegex( # type: ignore[no-untyped-call]
            utils.ValidationError, 'Missing cmd key in change dict'):
            rights_domain.CollectionRightsChange({'invalid': 'data'}) # type: ignore[no-untyped-call]

    def test_collection_rights_change_object_with_invalid_cmd(self) -> None:
        with self.assertRaisesRegex( # type: ignore[no-untyped-call]
            utils.ValidationError, 'Command invalid is not allowed'):
            rights_domain.CollectionRightsChange({'cmd': 'invalid'}) # type: ignore[no-untyped-call]

    def test_collection_rights_change_object_with_missing_attribute_in_cmd(
        self
    ) -> None:
        with self.assertRaisesRegex( # type: ignore[no-untyped-call]
            utils.ValidationError, (
                'The following required attributes are missing: '
                'new_role, old_role')):
            rights_domain.CollectionRightsChange({ # type: ignore[no-untyped-call]
                'cmd': 'change_role',
                'assignee_id': 'assignee_id',
            })

    def test_collection_rights_change_object_with_extra_attribute_in_cmd(
        self
    ) -> None:
        with self.assertRaisesRegex( # type: ignore[no-untyped-call]
            utils.ValidationError, (
                'The following extra attributes are present: invalid')):
            rights_domain.CollectionRightsChange({ # type: ignore[no-untyped-call]
                'cmd': 'change_private_viewability',
                'old_viewable_if_private': 'old_viewable_if_private',
                'new_viewable_if_private': 'new_viewable_if_private',
                'invalid': 'invalid'
            })

    def test_collection_rights_change_object_with_invalid_role(self) -> None:
        with self.assertRaisesRegex( # type: ignore[no-untyped-call]
            utils.ValidationError, (
                'Value for new_role in cmd change_role: '
                'invalid is not allowed')):
            rights_domain.CollectionRightsChange({ # type: ignore[no-untyped-call]
                'cmd': 'change_role',
                'assignee_id': 'assignee_id',
                'old_role': rights_domain.ROLE_OWNER,
                'new_role': 'invalid',
            })

    def test_collection_rights_change_object_with_invalid_status(self) -> None:
        with self.assertRaisesRegex( # type: ignore[no-untyped-call]
            utils.ValidationError, (
                'Value for new_status in cmd change_collection_status: '
                'invalid is not allowed')):
            rights_domain.CollectionRightsChange({ # type: ignore[no-untyped-call]
                'cmd': 'change_collection_status',
                'old_status': rights_domain.ACTIVITY_STATUS_PRIVATE,
                'new_status': 'invalid'
            })

    def test_collection_rights_change_object_with_create_new(self) -> None:
        collection_rights_change_object = rights_domain.CollectionRightsChange({ # type: ignore[no-untyped-call]
            'cmd': 'create_new'
        })

        self.assertEqual(collection_rights_change_object.cmd, 'create_new')

    def test_collection_rights_change_object_with_change_role(self) -> None:
        collection_rights_change_object = rights_domain.CollectionRightsChange({ # type: ignore[no-untyped-call]
            'cmd': 'change_role',
            'assignee_id': 'assignee_id',
            'old_role': rights_domain.ROLE_OWNER,
            'new_role': rights_domain.ROLE_VIEWER
        })

        self.assertEqual(collection_rights_change_object.cmd, 'change_role')
        self.assertEqual(
            collection_rights_change_object.assignee_id, 'assignee_id')
        self.assertEqual(
            collection_rights_change_object.old_role, rights_domain.ROLE_OWNER)
        self.assertEqual(
            collection_rights_change_object.new_role, rights_domain.ROLE_VIEWER)

    def test_collection_rights_change_object_with_release_ownership(
        self
    ) -> None:
        collection_rights_change_object = rights_domain.CollectionRightsChange({ # type: ignore[no-untyped-call]
            'cmd': 'release_ownership'
        })

        self.assertEqual(
            collection_rights_change_object.cmd, 'release_ownership')

    def test_collection_rights_change_object_with_change_private_viewability(
        self
    ) -> None:
        collection_rights_change_object = rights_domain.CollectionRightsChange({ # type: ignore[no-untyped-call]
            'cmd': 'change_private_viewability',
            'old_viewable_if_private': 'old_viewable_if_private',
            'new_viewable_if_private': 'new_viewable_if_private'
        })

        self.assertEqual(
            collection_rights_change_object.cmd, 'change_private_viewability')
        self.assertEqual(
            collection_rights_change_object.old_viewable_if_private,
            'old_viewable_if_private')
        self.assertEqual(
            collection_rights_change_object.new_viewable_if_private,
            'new_viewable_if_private')

    def test_collection_rights_change_object_with_update_first_published_msec(
        self
    ) -> None:
        collection_rights_change_object = rights_domain.CollectionRightsChange({ # type: ignore[no-untyped-call]
            'cmd': 'update_first_published_msec',
            'old_first_published_msec': 'old_first_published_msec',
            'new_first_published_msec': 'new_first_published_msec'
        })

        self.assertEqual(
            collection_rights_change_object.cmd, 'update_first_published_msec')
        self.assertEqual(
            collection_rights_change_object.old_first_published_msec,
            'old_first_published_msec')
        self.assertEqual(
            collection_rights_change_object.new_first_published_msec,
            'new_first_published_msec')

    def test_collection_rights_change_object_with_change_collection_status(
        self
    ) -> None:
        collection_rights_change_object = (
            rights_domain.CollectionRightsChange({ # type: ignore[no-untyped-call]
                'cmd': 'change_collection_status',
                'old_status': rights_domain.ACTIVITY_STATUS_PRIVATE,
                'new_status': rights_domain.ACTIVITY_STATUS_PUBLIC
            })
        )

        self.assertEqual(
            collection_rights_change_object.cmd, 'change_collection_status')
        self.assertEqual(
            collection_rights_change_object.old_status,
            rights_domain.ACTIVITY_STATUS_PRIVATE)
        self.assertEqual(
            collection_rights_change_object.new_status,
            rights_domain.ACTIVITY_STATUS_PUBLIC)

    def test_to_dict(self) -> None:
        collection_rights_change_dict = {
            'cmd': 'change_private_viewability',
            'old_viewable_if_private': 'old_viewable_if_private',
            'new_viewable_if_private': 'new_viewable_if_private'
        }
        collection_rights_change_object = rights_domain.CollectionRightsChange( # type: ignore[no-untyped-call]
            collection_rights_change_dict)
        self.assertEqual(
            collection_rights_change_object.to_dict(), # type: ignore[no-untyped-call]
            collection_rights_change_dict)
