瀏覽代碼

Merge pull request #164 from mnaamani/fix/163

prevent multiple active applications by a member to an opening
Bedeho Mender 5 年之前
父節點
當前提交
10a07fa6d0
共有 5 個文件被更改,包括 106 次插入5 次删除
  1. 1 1
      Cargo.lock
  2. 1 1
      Cargo.toml
  3. 30 1
      src/content_working_group/lib.rs
  4. 73 1
      src/content_working_group/tests.rs
  5. 1 1
      src/lib.rs

+ 1 - 1
Cargo.lock

@@ -968,7 +968,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "joystream-node-runtime"
-version = "6.0.0"
+version = "6.1.0"
 dependencies = [
  "parity-scale-codec 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "safe-mix 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",

+ 1 - 1
Cargo.toml

@@ -4,7 +4,7 @@ edition = '2018'
 name = 'joystream-node-runtime'
 # Follow convention: https://github.com/Joystream/substrate-runtime-joystream/issues/1
 # {Authoring}.{Spec}.{Impl} of the RuntimeVersion
-version = '6.0.0'
+version = '6.1.0'
 
 [features]
 default = ['std']

+ 30 - 1
src/content_working_group/lib.rs

@@ -225,8 +225,10 @@ pub static MSG_APPLY_ON_CURATOR_OPENING_UNSIGNED_ORIGIN: &str = "Unsigned origin
 pub static MSG_APPLY_ON_CURATOR_OPENING_MEMBER_ID_INVALID: &str = "Member id is invalid";
 pub static MSG_APPLY_ON_CURATOR_OPENING_SIGNER_NOT_CONTROLLER_ACCOUNT: &str =
     "Signer does not match controller account";
-static MSG_ORIGIN_IS_NIETHER_MEMBER_CONTROLLER_OR_ROOT: &str =
+pub static MSG_ORIGIN_IS_NIETHER_MEMBER_CONTROLLER_OR_ROOT: &str =
     "Origin must be controller or root account of member";
+pub static MSG_MEMBER_HAS_ACTIVE_APPLICATION_ON_OPENING: &str =
+    "Member already has an active application on the opening";
 
 /// The exit stage of a lead involvement in the working group.
 #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
@@ -1726,6 +1728,12 @@ decl_module! {
                 hiring::Module::<T>::ensure_can_add_application(curator_opening.opening_id, opt_role_stake_balance, opt_application_stake_balance)
             )?;
 
+            // Ensure member does not have an active application to this opening
+            Self::ensure_member_has_no_active_application_on_opening(
+                curator_opening.curator_applications,
+                member_id
+            )?;
+
             //
             // == MUTATION SAFE ==
             //
@@ -2055,6 +2063,27 @@ impl<T: Trait> versioned_store_permissions::CredentialChecker<T> for Module<T> {
 }
 
 impl<T: Trait> Module<T> {
+    fn ensure_member_has_no_active_application_on_opening(
+        curator_applications: CuratorApplicationIdSet<T>,
+        member_id: T::MemberId,
+    ) -> Result<(), &'static str> {
+        for curator_application_id in curator_applications {
+            let curator_application = CuratorApplicationById::<T>::get(curator_application_id);
+            // Look for application by the member for the opening
+            if curator_application.member_id != member_id {
+                continue;
+            }
+            // Get application details
+            let application = <hiring::ApplicationById<T>>::get(curator_application.application_id);
+            // Return error if application is in active stage
+            if application.stage == hiring::ApplicationStage::Active {
+                return Err(MSG_MEMBER_HAS_ACTIVE_APPLICATION_ON_OPENING);
+            }
+        }
+        // Member does not have any active applications to the opening
+        Ok(())
+    }
+
     fn ensure_can_register_role_on_member(
         member_id: &T::MemberId,
         role: role_types::Role,

+ 73 - 1
src/content_working_group/tests.rs

@@ -8,7 +8,7 @@ use hiring;
 use rstd::collections::btree_map::BTreeMap;
 use rstd::collections::btree_set::BTreeSet;
 use runtime_primitives::traits::One;
-use srml_support::{StorageLinkedMap, StorageValue};
+use srml_support::{assert_err, assert_ok, StorageLinkedMap, StorageValue};
 
 /// DIRTY IMPORT BECAUSE
 /// InputValidationLengthConstraint has not been factored out yet!!!
@@ -747,6 +747,78 @@ fn apply_on_curator_opening_success() {
         });
 }
 
+#[test]
+fn multiple_applications_by_same_member_to_opening_fails() {
+    TestExternalitiesBuilder::<Test>::default()
+        .build()
+        .execute_with(|| {
+            /*
+             * Setup
+             */
+
+            let normal_opening_constructed = setup_normal_accepting_opening();
+
+            // Add curator membership
+
+            let curator_applicant_root_and_controller_account = 72618;
+
+            let curator_applicant_member_id = add_member(
+                curator_applicant_root_and_controller_account,
+                to_vec("IwillTrytoapplyhere"),
+            );
+
+            let curator_applicant_role_account = 8881111;
+
+            let role_stake_balance = get_baseline_opening_policy()
+                .role_staking_policy
+                .unwrap()
+                .amount;
+            let application_stake_balance = get_baseline_opening_policy()
+                .application_staking_policy
+                .unwrap()
+                .amount;
+            let total_balance = role_stake_balance + application_stake_balance;
+
+            let source_account = curator_applicant_root_and_controller_account;
+
+            // Credit staking source account with enough funds for two applications,
+            // because we don't want our second application to fail for lack of funds
+            let _ = balances::Module::<Test>::deposit_creating(&source_account, total_balance * 2);
+
+            let human_readable_text = generate_valid_length_buffer(&ChannelHandleConstraint::get());
+
+            /*
+             * Test
+             */
+
+            // First application should work
+            assert_ok!(ContentWorkingGroup::apply_on_curator_opening(
+                Origin::signed(curator_applicant_root_and_controller_account),
+                curator_applicant_member_id,
+                normal_opening_constructed.curator_opening_id,
+                curator_applicant_role_account,
+                Some(role_stake_balance),
+                Some(application_stake_balance),
+                human_readable_text.clone()
+            ));
+
+            // Second application should fail since
+            // first application is still active
+            assert_err!(
+                ContentWorkingGroup::apply_on_curator_opening(
+                    Origin::signed(curator_applicant_root_and_controller_account),
+                    curator_applicant_member_id,
+                    normal_opening_constructed.curator_opening_id,
+                    curator_applicant_role_account,
+                    Some(role_stake_balance),
+                    Some(application_stake_balance),
+                    human_readable_text
+                ),
+                MSG_MEMBER_HAS_ACTIVE_APPLICATION_ON_OPENING
+            );
+        });
+}
+
 struct UpdateCuratorRoleAccountFixture {
     pub origin: Origin,
     pub member_id: <Test as members::Trait>::MemberId,

+ 1 - 1
src/lib.rs

@@ -118,7 +118,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
     spec_name: create_runtime_str!("joystream-node"),
     impl_name: create_runtime_str!("joystream-node"),
     authoring_version: 6,
-    spec_version: 0,
+    spec_version: 1,
     impl_version: 0,
     apis: RUNTIME_API_VERSIONS,
 };