Browse Source

Merge branch 'content_censorship_rework' into cli_videos

iorveth 4 years ago
parent
commit
68f2e51d17

+ 0 - 3
runtime-modules/content/src/errors.rs

@@ -10,9 +10,6 @@ decl_error! {
         // Curator Management Errors
         // -------------------------
 
-        /// Curator group can`t be removed
-        CuratorGroupRemovalForbidden,
-
         /// Curator under provided curator id is not a member of curaror group under given id
         CuratorIsNotAMemberOfGivenCuratorGroup,
 

+ 38 - 77
runtime-modules/content/src/lib.rs

@@ -638,7 +638,7 @@ decl_module! {
             actor: ContentActor<T::CuratorGroupId, T::CuratorId, T::MemberId>,
             params: ChannelCreationParameters<ContentParameters<T>, T::AccountId>,
         ) {
-            ensure_actor_authorized_to_create_channels_and_videos_assets::<T>(
+            ensure_actor_authorized_to_create_channel::<T>(
                 origin,
                 &actor,
             )?;
@@ -691,7 +691,7 @@ decl_module! {
             // check that channel exists
             let channel = Self::ensure_channel_exists(&channel_id)?;
 
-            ensure_actor_authorized_update_channel_and_videos::<T>(
+            ensure_actor_authorized_to_update_channel::<T>(
                 origin,
                 &actor,
                 &channel.owner,
@@ -752,7 +752,7 @@ decl_module! {
             // check that channel exists
             let channel = Self::ensure_channel_exists(&channel_id)?;
 
-            ensure_actor_authorized_update_channel_and_videos::<T>(
+            ensure_actor_authorized_to_update_channel::<T>(
                 origin,
                 &actor,
                 &channel.owner,
@@ -770,15 +770,20 @@ decl_module! {
         }
 
         #[weight = 10_000_000] // TODO: adjust weight
-        pub fn censor_channel(
+        pub fn update_channel_censorship_status(
             origin,
             actor: ContentActor<T::CuratorGroupId, T::CuratorId, T::MemberId>,
             channel_id: T::ChannelId,
+            is_censored: bool,
             rationale: Vec<u8>,
         ) {
             // check that channel exists
             let channel = Self::ensure_channel_exists(&channel_id)?;
 
+            if channel.is_censored == is_censored {
+                return Ok(())
+            }
+
             ensure_actor_authorized_to_censor::<T>(
                 origin,
                 &actor,
@@ -791,44 +796,14 @@ decl_module! {
 
             let mut channel = channel;
 
-            channel.is_censored = true;
+            channel.is_censored = is_censored;
 
             // TODO: unset the reward account ? so no revenue can be earned for censored channels?
 
             // Update the channel
             ChannelById::<T>::insert(channel_id, channel);
 
-            Self::deposit_event(RawEvent::ChannelCensored(actor, channel_id, rationale));
-        }
-
-        #[weight = 10_000_000] // TODO: adjust weight
-        pub fn uncensor_channel(
-            origin,
-            actor: ContentActor<T::CuratorGroupId, T::CuratorId, T::MemberId>,
-            channel_id: T::ChannelId,
-            rationale: Vec<u8>,
-        ) {
-            // check that channel exists
-            let channel = Self::ensure_channel_exists(&channel_id)?;
-
-            ensure_actor_authorized_to_censor::<T>(
-                origin,
-                &actor,
-                &channel.owner,
-            )?;
-
-            //
-            // == MUTATION SAFE ==
-            //
-
-            let mut channel = channel;
-
-            channel.is_censored = false;
-
-            // Update the channel
-            ChannelById::<T>::insert(channel_id, channel);
-
-            Self::deposit_event(RawEvent::ChannelUncensored(actor, channel_id, rationale));
+            Self::deposit_event(RawEvent::ChannelCensorshipStatusUpdated(actor, channel_id, is_censored, rationale));
         }
 
         #[weight = 10_000_000] // TODO: adjust weight
@@ -926,9 +901,13 @@ decl_module! {
             channel_id: T::ChannelId,
             params: VideoCreationParameters<ContentParameters<T>>,
         ) {
-            ensure_actor_authorized_to_create_channels_and_videos_assets::<T>(
+            // check that channel exists
+            let channel = Self::ensure_channel_exists(&channel_id)?;
+
+            ensure_actor_authorized_to_update_channel::<T>(
                 origin,
                 &actor,
+                &channel.owner,
             )?;
 
             // Pick out the assets to be uploaded to storage system
@@ -982,10 +961,9 @@ decl_module! {
             // check that video exists, retrieve corresponding channel id.
             let channel_id = Self::ensure_video_exists(&video_id)?.in_channel;
 
-            ensure_actor_authorized_update_channel_and_videos::<T>(
+            ensure_actor_authorized_to_update_channel::<T>(
                 origin,
                 &actor,
-                // The channel owner will be..
                 &Self::channel_by_id(channel_id).owner,
             )?;
 
@@ -1035,7 +1013,7 @@ decl_module! {
 
             let channel_id = video.in_channel;
 
-            ensure_actor_authorized_update_channel_and_videos::<T>(
+            ensure_actor_authorized_to_update_channel::<T>(
                 origin,
                 &actor,
                 // The channel owner will be..
@@ -1218,45 +1196,19 @@ decl_module! {
         }
 
         #[weight = 10_000_000] // TODO: adjust weight
-        pub fn censor_video(
+        pub fn update_video_censorship_status(
             origin,
             actor: ContentActor<T::CuratorGroupId, T::CuratorId, T::MemberId>,
             video_id: T::VideoId,
+            is_censored: bool,
             rationale: Vec<u8>,
         ) {
             // check that video exists
             let video = Self::ensure_video_exists(&video_id)?;
 
-            ensure_actor_authorized_to_censor::<T>(
-                origin,
-                &actor,
-                // The channel owner will be..
-                &Self::channel_by_id(video.in_channel).owner,
-            )?;
-
-            //
-            // == MUTATION SAFE ==
-            //
-
-            let mut video = video;
-
-            video.is_censored = true;
-
-            // Update the video
-            VideoById::<T>::insert(video_id, video);
-
-            Self::deposit_event(RawEvent::VideoCensored(actor, video_id, rationale));
-        }
-
-        #[weight = 10_000_000] // TODO: adjust weight
-        pub fn uncensor_video(
-            origin,
-            actor: ContentActor<T::CuratorGroupId, T::CuratorId, T::MemberId>,
-            video_id: T::VideoId,
-            rationale: Vec<u8>
-        ) {
-            // check that video exists
-            let video = Self::ensure_video_exists(&video_id)?;
+            if video.is_censored == is_censored {
+                return Ok(())
+            }
 
             ensure_actor_authorized_to_censor::<T>(
                 origin,
@@ -1271,12 +1223,12 @@ decl_module! {
 
             let mut video = video;
 
-            video.is_censored = false;
+            video.is_censored = is_censored;
 
             // Update the video
             VideoById::<T>::insert(video_id, video);
 
-            Self::deposit_event(RawEvent::VideoUncensored(actor, video_id, rationale));
+            Self::deposit_event(RawEvent::VideoCensorshipStatusUpdated(actor, video_id, is_censored, rationale));
         }
 
         #[weight = 10_000_000] // TODO: adjust weight
@@ -1450,6 +1402,7 @@ decl_event!(
         ContentParameters = ContentParameters<T>,
         AccountId = <T as system::Trait>::AccountId,
         ContentId = ContentId<T>,
+        IsCensored = bool,
     {
         // Curators
         CuratorGroupCreated(CuratorGroupId),
@@ -1472,8 +1425,12 @@ decl_event!(
         ),
         ChannelAssetsRemoved(ContentActor, ChannelId, Vec<ContentId>),
 
-        ChannelCensored(ContentActor, ChannelId, Vec<u8> /* rationale */),
-        ChannelUncensored(ContentActor, ChannelId, Vec<u8> /* rationale */),
+        ChannelCensorshipStatusUpdated(
+            ContentActor,
+            ChannelId,
+            IsCensored,
+            Vec<u8>, /* rationale */
+        ),
 
         // Channel Ownership Transfers
         ChannelOwnershipTransferRequested(
@@ -1519,8 +1476,12 @@ decl_event!(
         ),
         VideoDeleted(ContentActor, VideoId),
 
-        VideoCensored(ContentActor, VideoId, Vec<u8> /* rationale */),
-        VideoUncensored(ContentActor, VideoId, Vec<u8> /* rationale */),
+        VideoCensorshipStatusUpdated(
+            ContentActor,
+            VideoId,
+            IsCensored,
+            Vec<u8>, /* rationale */
+        ),
 
         // Featured Videos
         FeaturedVideosSet(ContentActor, Vec<VideoId>),

+ 17 - 17
runtime-modules/content/src/permissions/mod.rs

@@ -12,7 +12,7 @@ use frame_support::{ensure, Parameter};
 pub use serde::{Deserialize, Serialize};
 use sp_arithmetic::traits::BaseArithmetic;
 use sp_runtime::traits::{MaybeSerializeDeserialize, Member};
-use system::ensure_root;
+// use system::ensure_root;
 
 /// Model of authentication manager.
 pub trait ContentActorAuthenticator: system::Trait + MembershipTypes {
@@ -99,7 +99,7 @@ pub fn ensure_is_lead<T: Trait>(origin: T::Origin) -> DispatchResult {
     ensure_lead_auth_success::<T>(&account_id)
 }
 
-pub fn ensure_actor_authorized_to_create_channels_and_videos_assets<T: Trait>(
+pub fn ensure_actor_authorized_to_create_channel<T: Trait>(
     origin: T::Origin,
     actor: &ContentActor<T::CuratorGroupId, T::CuratorId, T::MemberId>,
 ) -> DispatchResult {
@@ -128,8 +128,8 @@ pub fn ensure_actor_authorized_to_create_channels_and_videos_assets<T: Trait>(
     }
 }
 
-// Enure actor can update or delete channels and videos
-pub fn ensure_actor_authorized_update_channel_and_videos<T: Trait>(
+// Enure actor can update channels and videos in the channel
+pub fn ensure_actor_authorized_to_update_channel<T: Trait>(
     origin: T::Origin,
     actor: &ContentActor<T::CuratorGroupId, T::CuratorId, T::MemberId>,
     owner: &ChannelOwner<T::MemberId, T::CuratorGroupId, T::DAOId>,
@@ -263,19 +263,19 @@ pub fn ensure_actor_authorized_to_manage_categories<T: Trait>(
     }
 }
 
-pub fn ensure_actor_authorized_to_delete_stale_assets<T: Trait>(
-    origin: T::Origin,
-    actor: &ContentActor<T::CuratorGroupId, T::CuratorId, T::MemberId>,
-) -> DispatchResult {
-    // Only Lead and (sudo) can delete assets no longer associated with a channel or person.
-    if let ContentActor::Lead = actor {
-        let sender = ensure_signed(origin)?;
-        ensure_lead_auth_success::<T>(&sender)
-    } else {
-        ensure_root(origin)?;
-        Ok(())
-    }
-}
+// pub fn ensure_actor_authorized_to_delete_stale_assets<T: Trait>(
+//     origin: T::Origin,
+//     actor: &ContentActor<T::CuratorGroupId, T::CuratorId, T::MemberId>,
+// ) -> DispatchResult {
+//     // Only Lead and (sudo) can delete assets no longer associated with a channel or person.
+//     if let ContentActor::Lead = actor {
+//         let sender = ensure_signed(origin)?;
+//         ensure_lead_auth_success::<T>(&sender)
+//     } else {
+//         ensure_root(origin)?;
+//         Ok(())
+//     }
+// }
 
 /// Enum, representing all possible `Actor`s
 #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]

+ 234 - 26
runtime-modules/content/src/tests/channels.rs

@@ -1,33 +1,16 @@
 #![cfg(test)]
 
+use super::curators;
 use super::mock::*;
 use crate::*;
 use frame_support::{assert_err, assert_ok};
 
-fn add_curator_to_new_group(curator_id: CuratorId) -> CuratorGroupId {
-    let curator_group_id = Content::next_curator_group_id();
-    // create new group and add curator id to it
-    assert_ok!(Content::create_curator_group(Origin::signed(LEAD_ORIGIN)));
-    assert_ok!(Content::add_curator_to_group(
-        Origin::signed(LEAD_ORIGIN),
-        curator_group_id,
-        curator_id
-    ));
-    // make group active
-    assert_ok!(Content::set_curator_group_status(
-        Origin::signed(LEAD_ORIGIN),
-        curator_group_id,
-        true
-    ));
-    curator_group_id
-}
-
 #[test]
 fn lead_cannot_create_channel() {
     with_default_mock_builder(|| {
         assert_err!(
             Content::create_channel(
-                Origin::signed(FIRST_MEMBER_ORIGIN),
+                Origin::signed(LEAD_ORIGIN),
                 ContentActor::Lead,
                 ChannelCreationParameters {
                     assets: vec![],
@@ -41,8 +24,11 @@ fn lead_cannot_create_channel() {
 }
 
 #[test]
-fn curators_can_create_channel() {
+fn curator_owned_channels() {
     with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
         // Curator group doesn't exist yet
         assert_err!(
             Content::create_channel(
@@ -57,7 +43,7 @@ fn curators_can_create_channel() {
             Error::<Test>::CuratorGroupIsNotActive
         );
 
-        let group_id = add_curator_to_new_group(FIRST_CURATOR_ID);
+        let group_id = curators::add_curator_to_new_group(FIRST_CURATOR_ID);
         assert_eq!(FIRST_CURATOR_GROUP_ID, group_id);
 
         // Curator from wrong group
@@ -88,6 +74,8 @@ fn curators_can_create_channel() {
             Error::<Test>::CuratorAuthFailed
         );
 
+        let channel_id = Content::next_channel_id();
+
         // Curator in correct active group, with correct origin
         assert_ok!(Content::create_channel(
             Origin::signed(FIRST_CURATOR_ORIGIN),
@@ -98,11 +86,60 @@ fn curators_can_create_channel() {
                 reward_account: None,
             }
         ));
+
+        assert_eq!(
+            System::events().last().unwrap().event,
+            MetaEvent::content(RawEvent::ChannelCreated(
+                ContentActor::Curator(FIRST_CURATOR_GROUP_ID, FIRST_CURATOR_ID),
+                channel_id,
+                ChannelRecord {
+                    owner: ChannelOwner::CuratorGroup(FIRST_CURATOR_GROUP_ID),
+                    videos: vec![],
+                    playlists: vec![],
+                    series: vec![],
+                    is_censored: false,
+                    reward_account: None,
+                },
+                ChannelCreationParameters {
+                    assets: vec![],
+                    meta: vec![],
+                    reward_account: None,
+                }
+            ))
+        );
+
+        // Curator can update channel
+        assert_ok!(Content::update_channel(
+            Origin::signed(FIRST_CURATOR_ORIGIN),
+            ContentActor::Curator(FIRST_CURATOR_GROUP_ID, FIRST_CURATOR_ID),
+            channel_id,
+            ChannelUpdateParameters {
+                assets: None,
+                new_meta: None,
+                reward_account: None,
+            }
+        ));
+
+        // Lead can update curator owned channels
+        assert_ok!(Content::update_channel(
+            Origin::signed(LEAD_ORIGIN),
+            ContentActor::Lead,
+            channel_id,
+            ChannelUpdateParameters {
+                assets: None,
+                new_meta: None,
+                reward_account: None,
+            }
+        ));
     })
 }
+
 #[test]
-fn members_can_manage_channels() {
+fn member_owned_channels() {
     with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
         // Not a member
         assert_err!(
             Content::create_channel(
@@ -130,7 +167,26 @@ fn members_can_manage_channels() {
             }
         ));
 
-        // TODO: assert emitted events...
+        assert_eq!(
+            System::events().last().unwrap().event,
+            MetaEvent::content(RawEvent::ChannelCreated(
+                ContentActor::Member(FIRST_MEMBER_ID),
+                channel_id_1,
+                ChannelRecord {
+                    owner: ChannelOwner::Member(FIRST_MEMBER_ID),
+                    videos: vec![],
+                    playlists: vec![],
+                    series: vec![],
+                    is_censored: false,
+                    reward_account: None,
+                },
+                ChannelCreationParameters {
+                    assets: vec![],
+                    meta: vec![],
+                    reward_account: None,
+                }
+            ))
+        );
 
         let channel_id_2 = Content::next_channel_id();
 
@@ -145,7 +201,26 @@ fn members_can_manage_channels() {
             }
         ));
 
-        // TODO: assert emitted events...
+        assert_eq!(
+            System::events().last().unwrap().event,
+            MetaEvent::content(RawEvent::ChannelCreated(
+                ContentActor::Member(SECOND_MEMBER_ID),
+                channel_id_2,
+                ChannelRecord {
+                    owner: ChannelOwner::Member(SECOND_MEMBER_ID),
+                    videos: vec![],
+                    playlists: vec![],
+                    series: vec![],
+                    is_censored: false,
+                    reward_account: None,
+                },
+                ChannelCreationParameters {
+                    assets: vec![],
+                    meta: vec![],
+                    reward_account: None,
+                }
+            ))
+        );
 
         // Update channel
         assert_ok!(Content::update_channel(
@@ -159,7 +234,26 @@ fn members_can_manage_channels() {
             }
         ));
 
-        // TODO: assert emitted events...
+        assert_eq!(
+            System::events().last().unwrap().event,
+            MetaEvent::content(RawEvent::ChannelUpdated(
+                ContentActor::Member(FIRST_MEMBER_ID),
+                channel_id_1,
+                ChannelRecord {
+                    owner: ChannelOwner::Member(FIRST_MEMBER_ID),
+                    videos: vec![],
+                    playlists: vec![],
+                    series: vec![],
+                    is_censored: false,
+                    reward_account: None,
+                },
+                ChannelUpdateParameters {
+                    assets: None,
+                    new_meta: None,
+                    reward_account: None,
+                }
+            ))
+        );
 
         // Member cannot update a channel they do not own
         assert_err!(
@@ -175,7 +269,121 @@ fn members_can_manage_channels() {
             ),
             Error::<Test>::ActorNotAuthorized
         );
+    })
+}
+
+#[test]
+fn channel_censoring() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let channel_id = Content::next_channel_id();
+        assert_ok!(Content::create_channel(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            ChannelCreationParameters {
+                assets: vec![],
+                meta: vec![],
+                reward_account: None,
+            }
+        ));
+
+        let group_id = curators::add_curator_to_new_group(FIRST_CURATOR_ID);
+
+        // Curator can censor channels
+        let is_censored = true;
+        assert_ok!(Content::update_channel_censorship_status(
+            Origin::signed(FIRST_CURATOR_ORIGIN),
+            ContentActor::Curator(group_id, FIRST_CURATOR_ID),
+            channel_id,
+            is_censored,
+            vec![]
+        ));
+
+        assert_eq!(
+            System::events().last().unwrap().event,
+            MetaEvent::content(RawEvent::ChannelCensorshipStatusUpdated(
+                ContentActor::Curator(group_id, FIRST_CURATOR_ID),
+                channel_id,
+                is_censored,
+                vec![]
+            ))
+        );
+
+        let channel = Content::channel_by_id(channel_id);
+
+        assert!(channel.is_censored);
+
+        // Curator can un-censor channels
+        let is_censored = false;
+        assert_ok!(Content::update_channel_censorship_status(
+            Origin::signed(FIRST_CURATOR_ORIGIN),
+            ContentActor::Curator(group_id, FIRST_CURATOR_ID),
+            channel_id,
+            is_censored,
+            vec![]
+        ));
 
-        // TODO: assert emitted events...
+        assert_eq!(
+            System::events().last().unwrap().event,
+            MetaEvent::content(RawEvent::ChannelCensorshipStatusUpdated(
+                ContentActor::Curator(group_id, FIRST_CURATOR_ID),
+                channel_id,
+                is_censored,
+                vec![]
+            ))
+        );
+
+        let channel = Content::channel_by_id(channel_id);
+
+        assert!(!channel.is_censored);
+
+        // Member cannot censor channels
+        let is_censored = true;
+        assert_err!(
+            Content::update_channel_censorship_status(
+                Origin::signed(FIRST_MEMBER_ORIGIN),
+                ContentActor::Member(FIRST_MEMBER_ID),
+                channel_id,
+                is_censored,
+                vec![]
+            ),
+            Error::<Test>::ActorNotAuthorized
+        );
+
+        let curator_channel_id = Content::next_channel_id();
+
+        // create curator channel
+        assert_ok!(Content::create_channel(
+            Origin::signed(FIRST_CURATOR_ORIGIN),
+            ContentActor::Curator(group_id, FIRST_CURATOR_ID),
+            ChannelCreationParameters {
+                assets: vec![],
+                meta: vec![],
+                reward_account: None,
+            }
+        ));
+
+        // Curator cannot censor curator group channels
+        assert_err!(
+            Content::update_channel_censorship_status(
+                Origin::signed(FIRST_CURATOR_ORIGIN),
+                ContentActor::Curator(group_id, FIRST_CURATOR_ID),
+                curator_channel_id,
+                is_censored,
+                vec![]
+            ),
+            Error::<Test>::CannotCensoreCuratorGroupOwnedChannels
+        );
+
+        // Lead can still censor curator group channels
+        assert_ok!(Content::update_channel_censorship_status(
+            Origin::signed(LEAD_ORIGIN),
+            ContentActor::Lead,
+            curator_channel_id,
+            is_censored,
+            vec![]
+        ));
     })
 }

+ 131 - 0
runtime-modules/content/src/tests/curators.rs

@@ -0,0 +1,131 @@
+#![cfg(test)]
+
+use super::mock::*;
+use crate::*;
+use frame_support::{assert_err, assert_ok};
+
+pub fn add_curator_to_new_group(curator_id: CuratorId) -> CuratorGroupId {
+    let curator_group_id = Content::next_curator_group_id();
+    // create new group and add curator id to it
+    assert_ok!(Content::create_curator_group(Origin::signed(LEAD_ORIGIN)));
+    assert_ok!(Content::add_curator_to_group(
+        Origin::signed(LEAD_ORIGIN),
+        curator_group_id,
+        curator_id
+    ));
+    // make group active
+    assert_ok!(Content::set_curator_group_status(
+        Origin::signed(LEAD_ORIGIN),
+        curator_group_id,
+        true
+    ));
+    curator_group_id
+}
+
+#[test]
+fn curator_group_management() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let curator_group_id = Content::next_curator_group_id();
+        assert_ok!(Content::create_curator_group(Origin::signed(LEAD_ORIGIN)));
+
+        assert_eq!(
+            System::events().last().unwrap().event,
+            MetaEvent::content(RawEvent::CuratorGroupCreated(curator_group_id))
+        );
+
+        let group = Content::curator_group_by_id(curator_group_id);
+
+        // By default group is empty and not active
+        assert_eq!(group.is_active(), false);
+        assert_eq!(group.get_curators().len(), 0);
+
+        // Activate group
+        assert_ok!(Content::set_curator_group_status(
+            Origin::signed(LEAD_ORIGIN),
+            curator_group_id,
+            true
+        ));
+
+        assert_eq!(
+            System::events().last().unwrap().event,
+            MetaEvent::content(RawEvent::CuratorGroupStatusSet(curator_group_id, true))
+        );
+
+        let group = Content::curator_group_by_id(curator_group_id);
+        assert_eq!(group.is_active(), true);
+
+        // Cannot add non curators into group
+        assert_err!(
+            Content::add_curator_to_group(
+                Origin::signed(LEAD_ORIGIN),
+                curator_group_id,
+                MEMBERS_COUNT + 1 // not a curator
+            ),
+            Error::<Test>::CuratorIdInvalid
+        );
+
+        // Add curator to group
+        assert_ok!(Content::add_curator_to_group(
+            Origin::signed(LEAD_ORIGIN),
+            curator_group_id,
+            FIRST_CURATOR_ID
+        ));
+
+        assert_eq!(
+            System::events().last().unwrap().event,
+            MetaEvent::content(RawEvent::CuratorAdded(curator_group_id, FIRST_CURATOR_ID))
+        );
+
+        // Ensure curator is in group
+        let group = Content::curator_group_by_id(curator_group_id);
+        assert!(group.has_curator(&FIRST_CURATOR_ID));
+
+        // Cannot add same curator again
+        assert_err!(
+            Content::add_curator_to_group(
+                Origin::signed(LEAD_ORIGIN),
+                curator_group_id,
+                FIRST_CURATOR_ID
+            ),
+            Error::<Test>::CuratorIsAlreadyAMemberOfGivenCuratorGroup
+        );
+
+        // Cannot remove curator if not in group
+        assert_err!(
+            Content::remove_curator_from_group(
+                Origin::signed(LEAD_ORIGIN),
+                curator_group_id,
+                MEMBERS_COUNT + 1 // not a curator
+            ),
+            Error::<Test>::CuratorIsNotAMemberOfGivenCuratorGroup
+        );
+
+        // Remove curator from group
+        assert_ok!(Content::remove_curator_from_group(
+            Origin::signed(LEAD_ORIGIN),
+            curator_group_id,
+            FIRST_CURATOR_ID
+        ));
+
+        assert_eq!(
+            System::events().last().unwrap().event,
+            MetaEvent::content(RawEvent::CuratorRemoved(curator_group_id, FIRST_CURATOR_ID))
+        );
+
+        let group = Content::curator_group_by_id(curator_group_id);
+        assert!(!group.has_curator(&FIRST_CURATOR_ID));
+
+        // Already removed cannot remove again
+        assert_err!(
+            Content::remove_curator_from_group(
+                Origin::signed(LEAD_ORIGIN),
+                curator_group_id,
+                FIRST_CURATOR_ID
+            ),
+            Error::<Test>::CuratorIsNotAMemberOfGivenCuratorGroup
+        );
+    })
+}

+ 12 - 3
runtime-modules/content/src/tests/mock.rs

@@ -2,8 +2,7 @@
 
 use crate::*;
 
-// use frame_support::storage::StorageMap;
-// use frame_support::traits::{OnFinalize, OnInitialize};
+use frame_support::traits::{OnFinalize, OnInitialize};
 use frame_support::{impl_outer_event, impl_outer_origin, parameter_types};
 use sp_core::H256;
 use sp_runtime::{
@@ -20,7 +19,7 @@ use common::storage::StorageSystem;
 pub type CuratorId = <Test as ContentActorAuthenticator>::CuratorId;
 pub type CuratorGroupId = <Test as ContentActorAuthenticator>::CuratorGroupId;
 pub type MemberId = <Test as MembershipTypes>::MemberId;
-// pub type ChannelId = <Test as StorageOwnership>::ChannelId;
+pub type ChannelId = <Test as StorageOwnership>::ChannelId;
 // pub type DAOId = <Test as StorageOwnership>::DAOId;
 
 /// Origins
@@ -294,3 +293,13 @@ impl ExtBuilder {
 pub fn with_default_mock_builder<R, F: FnOnce() -> R>(f: F) -> R {
     ExtBuilder::default().build().execute_with(|| f())
 }
+
+// Recommendation from Parity on testing on_finalize
+// https://substrate.dev/docs/en/next/development/module/tests
+pub fn run_to_block(n: u64) {
+    while System::block_number() < n {
+        <System as OnFinalize<u64>>::on_finalize(System::block_number());
+        System::set_block_number(System::block_number() + 1);
+        <System as OnInitialize<u64>>::on_initialize(System::block_number());
+    }
+}

+ 2 - 0
runtime-modules/content/src/tests/mod.rs

@@ -1,4 +1,6 @@
 #![cfg(test)]
 
 mod channels;
+mod curators;
 mod mock;
+mod videos;

+ 254 - 0
runtime-modules/content/src/tests/videos.rs

@@ -0,0 +1,254 @@
+#![cfg(test)]
+
+use super::curators;
+use super::mock::*;
+use crate::*;
+use frame_support::{assert_err, assert_ok};
+
+fn create_member_channel() -> ChannelId {
+    let channel_id = Content::next_channel_id();
+
+    // Member can create the channel
+    assert_ok!(Content::create_channel(
+        Origin::signed(FIRST_MEMBER_ORIGIN),
+        ContentActor::Member(FIRST_MEMBER_ID),
+        ChannelCreationParameters {
+            assets: vec![],
+            meta: vec![],
+            reward_account: None,
+        }
+    ));
+
+    channel_id
+}
+
+#[test]
+fn member_can_create_videos() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+        let channel_id = create_member_channel();
+
+        let video_id = Content::next_video_id();
+        assert_ok!(Content::create_video(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            channel_id,
+            VideoCreationParameters {
+                assets: vec![NewAsset::Urls(vec![b"https://somewhere.com/".to_vec()])],
+                meta: b"metablob".to_vec(),
+            }
+        ));
+
+        assert_eq!(
+            System::events().last().unwrap().event,
+            MetaEvent::content(RawEvent::VideoCreated(
+                ContentActor::Member(FIRST_MEMBER_ID),
+                channel_id,
+                video_id,
+                VideoCreationParameters {
+                    assets: vec![NewAsset::Urls(vec![b"https://somewhere.com/".to_vec()])],
+                    meta: b"metablob".to_vec(),
+                }
+            ))
+        );
+
+        // Video is created in correct channel
+        let video = Content::video_by_id(video_id);
+        assert_eq!(channel_id, video.in_channel);
+
+        // Can update own video
+        assert_ok!(Content::update_video(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            VideoUpdateParameters {
+                assets: Some(vec![NewAsset::Urls(vec![
+                    b"https://somewhere-else.com/".to_vec()
+                ])]),
+                new_meta: Some(b"newmetablob".to_vec()),
+            }
+        ));
+
+        assert_eq!(
+            System::events().last().unwrap().event,
+            MetaEvent::content(RawEvent::VideoUpdated(
+                ContentActor::Member(FIRST_MEMBER_ID),
+                video_id,
+                VideoUpdateParameters {
+                    assets: Some(vec![NewAsset::Urls(vec![
+                        b"https://somewhere-else.com/".to_vec()
+                    ])]),
+                    new_meta: Some(b"newmetablob".to_vec()),
+                }
+            ))
+        );
+
+        // Member cannot create video in a channel they do not own
+        assert_err!(
+            Content::create_video(
+                Origin::signed(SECOND_MEMBER_ORIGIN),
+                ContentActor::Member(SECOND_MEMBER_ID),
+                channel_id,
+                VideoCreationParameters {
+                    assets: vec![],
+                    meta: vec![],
+                }
+            ),
+            Error::<Test>::ActorNotAuthorized
+        );
+
+        // Member cannot update video in a channel they do not own
+        assert_err!(
+            Content::update_video(
+                Origin::signed(SECOND_MEMBER_ORIGIN),
+                ContentActor::Member(SECOND_MEMBER_ID),
+                video_id,
+                VideoUpdateParameters {
+                    assets: None,
+                    new_meta: None,
+                }
+            ),
+            Error::<Test>::ActorNotAuthorized
+        );
+
+        // Member cannot delete video in a channel they do not own
+        assert_err!(
+            Content::delete_video(
+                Origin::signed(SECOND_MEMBER_ORIGIN),
+                ContentActor::Member(SECOND_MEMBER_ID),
+                video_id
+            ),
+            Error::<Test>::ActorNotAuthorized
+        );
+
+        // Owner can delete their video
+        assert_ok!(Content::delete_video(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id
+        ));
+
+        assert_eq!(
+            System::events().last().unwrap().event,
+            MetaEvent::content(RawEvent::VideoDeleted(
+                ContentActor::Member(FIRST_MEMBER_ID),
+                video_id
+            ))
+        );
+    })
+}
+
+#[test]
+fn curators_can_censor_videos() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+        let channel_id = create_member_channel();
+
+        let video_id = Content::next_video_id();
+        assert_ok!(Content::create_video(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            channel_id,
+            VideoCreationParameters {
+                assets: vec![NewAsset::Urls(vec![b"https://somewhere.com/".to_vec()])],
+                meta: b"metablob".to_vec(),
+            }
+        ));
+
+        let group_id = curators::add_curator_to_new_group(FIRST_CURATOR_ID);
+
+        // Curator can censor videos
+        let is_censored = true;
+        assert_ok!(Content::update_video_censorship_status(
+            Origin::signed(FIRST_CURATOR_ORIGIN),
+            ContentActor::Curator(group_id, FIRST_CURATOR_ID),
+            video_id,
+            is_censored,
+            vec![]
+        ));
+
+        assert_eq!(
+            System::events().last().unwrap().event,
+            MetaEvent::content(RawEvent::VideoCensorshipStatusUpdated(
+                ContentActor::Curator(group_id, FIRST_CURATOR_ID),
+                video_id,
+                is_censored,
+                vec![]
+            ))
+        );
+
+        let video = Content::video_by_id(video_id);
+
+        assert!(video.is_censored);
+
+        // Curator can un-censor videos
+        let is_censored = false;
+        assert_ok!(Content::update_video_censorship_status(
+            Origin::signed(FIRST_CURATOR_ORIGIN),
+            ContentActor::Curator(group_id, FIRST_CURATOR_ID),
+            video_id,
+            is_censored,
+            vec![]
+        ));
+
+        assert_eq!(
+            System::events().last().unwrap().event,
+            MetaEvent::content(RawEvent::VideoCensorshipStatusUpdated(
+                ContentActor::Curator(group_id, FIRST_CURATOR_ID),
+                video_id,
+                is_censored,
+                vec![]
+            ))
+        );
+
+        let video = Content::video_by_id(video_id);
+
+        assert!(!video.is_censored);
+
+        // Members cannot censor videos
+        assert_err!(
+            Content::update_video_censorship_status(
+                Origin::signed(FIRST_MEMBER_ORIGIN),
+                ContentActor::Member(FIRST_MEMBER_ORIGIN),
+                channel_id,
+                true,
+                vec![]
+            ),
+            Error::<Test>::ActorNotAuthorized
+        );
+    })
+}
+
+#[test]
+fn featured_videos() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        // Lead can update curator owned channels
+        assert_ok!(Content::set_featured_videos(
+            Origin::signed(LEAD_ORIGIN),
+            ContentActor::Lead,
+            vec![1, 2, 3]
+        ));
+
+        assert_eq!(
+            System::events().last().unwrap().event,
+            MetaEvent::content(RawEvent::FeaturedVideosSet(
+                ContentActor::Lead,
+                vec![1, 2, 3]
+            ))
+        );
+
+        assert_err!(
+            Content::set_featured_videos(
+                Origin::signed(FIRST_MEMBER_ORIGIN),
+                ContentActor::Member(FIRST_MEMBER_ID),
+                vec![1, 2, 3]
+            ),
+            Error::<Test>::ActorNotAuthorized
+        );
+    })
+}

+ 9 - 4
runtime-modules/storage/src/data_directory.rs

@@ -39,10 +39,15 @@ use crate::data_object_type_registry;
 use crate::data_object_type_registry::IsActiveDataObjectType;
 use crate::*;
 
-pub const DEFAULT_VOUCHER_SIZE_LIMIT_UPPER_BOUND: u64 = 100000000;
-pub const DEFAULT_VOUCHER_OBJECTS_LIMIT_UPPER_BOUND: u64 = 200;
-pub const DEFAULT_GLOBAL_VOUCHER: Voucher = Voucher::new(200000000, 2000);
-pub const DEFAULT_VOUCHER: Voucher = Voucher::new(5000000, 100);
+/// The default maximum storage size (bytes) that lead can set on the voucher of an owner
+pub const DEFAULT_VOUCHER_SIZE_LIMIT_UPPER_BOUND: u64 = 54_000_000_000;
+/// The default maximum number of objects that lead can set on the voucher of an owner
+pub const DEFAULT_VOUCHER_OBJECTS_LIMIT_UPPER_BOUND: u64 = 10_000;
+/// The default system global storage limits
+pub const DEFAULT_GLOBAL_VOUCHER: Voucher = Voucher::new(1_100_000_000_000, 1_000_000);
+/// The default initial owner voucher
+pub const DEFAULT_VOUCHER: Voucher = Voucher::new(5_400_000_000, 1_000);
+/// The default starting upload blocked status
 pub const DEFAULT_UPLOADING_BLOCKED_STATUS: bool = false;
 
 /// The _Data directory_ main _Trait_.