Browse Source

membership tests

Mokhtar Naamani 6 years ago
parent
commit
27b784111c
3 changed files with 217 additions and 59 deletions
  1. 38 5
      src/membership/mock.rs
  2. 42 51
      src/membership/registry.rs
  3. 137 3
      src/membership/tests.rs

+ 38 - 5
src/membership/mock.rs

@@ -69,12 +69,45 @@ impl GovernanceCurrency for Test {
     type Currency = balances::Module<Self>;
 }
 
-// This function basically just builds a genesis storage key/value store according to
-// our desired mockup.
-pub fn initial_test_ext() -> runtime_io::TestExternalities<Blake2Hasher> {
-    let mut t = system::GenesisConfig::<Test>::default().build_storage().unwrap().0;
+impl registry::Trait for Test {
+    type Event = ();
+    type MemberId = u32;
+    type PaidTermId = u32;
+    type SubscriptionId = u32;
+}
+
+pub struct ExtBuilder {
+	first_member_id: u32,
+	default_paid_membership_fee: u32,
+}
+impl Default for ExtBuilder {
+	fn default() -> Self {
+		Self {
+			first_member_id: 1,
+			default_paid_membership_fee: 100,
+		}
+	}
+}
+
+impl ExtBuilder {
+	pub fn first_member_id(mut self, first_member_id: u32) -> Self {
+		self.first_member_id = first_member_id;
+		self
+	}
+	pub fn default_paid_membership_fee(mut self, default_paid_membership_fee: u32) -> Self {
+		self.default_paid_membership_fee = default_paid_membership_fee;
+		self
+	}
+    pub fn build(self) -> runtime_io::TestExternalities<Blake2Hasher> {
+        let mut t = system::GenesisConfig::<Test>::default().build_storage().unwrap().0;
+
+        t.extend(registry::GenesisConfig::<Test>{
+            first_member_id: self.first_member_id,
+            default_paid_membership_fee: self.default_paid_membership_fee,
+        }.build_storage().unwrap().0);
 
-    runtime_io::TestExternalities::new(t)
+        t.into()
+    }
 }
 
 pub type System = system::Module<Test>;

+ 42 - 51
src/membership/registry.rs

@@ -43,24 +43,24 @@ const DEFAULT_MAX_ABOUT_TEXT_LENGTH: u32 = 2048;
 #[derive(Encode, Decode)]
 /// Stored information about a registered user
 pub struct Profile<T: Trait> {
-    id: T::MemberId,
-    handle: Vec<u8>,
-    avatar_uri: Vec<u8>,
-    about: Vec<u8>,
-    registered_at_block: T::BlockNumber,
-    registered_at_time: T::Moment,
-    entry: EntryMethod<T>,
-    suspended: bool,
-    subscription: Option<T::SubscriptionId>,
-    sub_accounts: Vec<T::AccountId>
+    pub id: T::MemberId,
+    pub handle: Vec<u8>,
+    pub avatar_uri: Vec<u8>,
+    pub about: Vec<u8>,
+    pub registered_at_block: T::BlockNumber,
+    pub registered_at_time: T::Moment,
+    pub entry: EntryMethod<T>,
+    pub suspended: bool,
+    pub subscription: Option<T::SubscriptionId>,
+    pub sub_accounts: Vec<T::AccountId>
 }
 
 #[derive(Clone, Debug, Encode, Decode, PartialEq)]
 /// Structure used to batch user configurable profile information when registering or updating info
 pub struct UserInfo {
-    handle: Option<Vec<u8>>,
-    avatar_uri: Option<Vec<u8>>,
-    about: Option<Vec<u8>>,
+    pub handle: Option<Vec<u8>>,
+    pub avatar_uri: Option<Vec<u8>>,
+    pub about: Option<Vec<u8>>,
 }
 
 struct CheckedUserInfo {
@@ -77,14 +77,14 @@ pub enum EntryMethod<T: Trait> {
 }
 
 //#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
-#[derive(Encode, Decode)]
+#[derive(Encode, Decode, Eq, PartialEq)]
 pub struct PaidMembershipTerms<T: Trait> {
     /// Unique identifier - the term id
-    id: T::PaidTermId,
+    pub id: T::PaidTermId,
     /// Quantity of native tokens which must be provably burned
-    fee: BalanceOf<T>,
+    pub fee: BalanceOf<T>,
     /// String of capped length describing human readable conditions which are being agreed upon
-    text: Vec<u8>
+    pub text: Vec<u8>
 }
 
 impl<T: Trait> Default for PaidMembershipTerms<T> {
@@ -101,59 +101,59 @@ decl_storage! {
     trait Store for Module<T: Trait> as Membership {
         /// Records at what runtime spec version the store was initialized. This allows the runtime
         /// to know when to run initialize code if it was installed as an update.
-        StoreSpecVersion get(store_spec_version) build(|_| Some(VERSION.spec_version)) : Option<u32>;
+        pub StoreSpecVersion get(store_spec_version) build(|_| Some(VERSION.spec_version)) : Option<u32>;
 
         /// MemberId's start at this value
-        FirstMemberId get(first_member_id) config(first_member_id): T::MemberId = T::MemberId::sa(DEFAULT_FIRST_MEMBER_ID);
+        pub FirstMemberId get(first_member_id) config(first_member_id): T::MemberId = T::MemberId::sa(DEFAULT_FIRST_MEMBER_ID);
 
         /// MemberId to assign to next member that is added to the registry
-        NextMemberId get(next_member_id) build(|config: &GenesisConfig<T>| config.first_member_id): T::MemberId = T::MemberId::sa(DEFAULT_FIRST_MEMBER_ID);
+        pub NextMemberId get(next_member_id) build(|config: &GenesisConfig<T>| config.first_member_id): T::MemberId = T::MemberId::sa(DEFAULT_FIRST_MEMBER_ID);
 
         /// Mapping of member ids to their corresponding primary accountid
-        AccountIdByMemberId get(account_id_by_member_id) : map T::MemberId => T::AccountId;
+        pub AccountIdByMemberId get(account_id_by_member_id) : map T::MemberId => T::AccountId;
 
         /// Mapping of members' accountid ids to their member id.
         /// A member can have one primary account and multiple sub accounts associated with their profile
-        MemberIdByAccountId get(member_id_by_account_id) : map T::AccountId => Option<T::MemberId>;
+        pub MemberIdByAccountId get(member_id_by_account_id) : map T::AccountId => Option<T::MemberId>;
 
         /// Mapping of member's id to their membership profile
         // Value is Option<Profile> because it is not meaningful to have a Default value for Profile
-        MemberProfile get(member_profile) : map T::MemberId => Option<Profile<T>>;
+        pub MemberProfile get(member_profile) : map T::MemberId => Option<Profile<T>>;
 
         /// Registered unique handles and their mapping to their owner
-        Handles get(handles) : map Vec<u8> => Option<T::MemberId>;
+        pub Handles get(handles) : map Vec<u8> => Option<T::MemberId>;
 
         /// Next paid membership terms id
-        NextPaidMembershipTermsId get(next_paid_membership_terms_id) : T::PaidTermId = T::PaidTermId::sa(FIRST_PAID_TERMS_ID);
+        pub NextPaidMembershipTermsId get(next_paid_membership_terms_id) : T::PaidTermId = T::PaidTermId::sa(FIRST_PAID_TERMS_ID);
 
         /// Paid membership terms record
         // Remember to add _genesis_phantom_data: std::marker::PhantomData{} to membership
         // genesis config if not providing config() or extra_genesis
-        PaidMembershipTermsById get(paid_membership_terms_by_id) build(|config: &GenesisConfig<T>| {
+        pub PaidMembershipTermsById get(paid_membership_terms_by_id) build(|config: &GenesisConfig<T>| {
             // This method only gets called when initializing storage, and is
             // compiled as native code. (Will be called when building `raw` chainspec)
             // So it can't be relied upon to initialize storage for runtimes updates.
             // Initialization for updated runtime is done in run_migration()
             let mut terms: PaidMembershipTerms<T> = Default::default();
-            terms.fee = BalanceOf::<T>::sa(config.default_paid_membership_fee);
+            terms.fee = config.default_paid_membership_fee;
             vec![(terms.id, terms)]
         }) : map T::PaidTermId => Option<PaidMembershipTerms<T>>;
 
         /// Active Paid membership terms
-        ActivePaidMembershipTerms get(active_paid_membership_terms) : Vec<T::PaidTermId> = vec![T::PaidTermId::sa(DEFAULT_PAID_TERM_ID)];
+        pub ActivePaidMembershipTerms get(active_paid_membership_terms) : Vec<T::PaidTermId> = vec![T::PaidTermId::sa(DEFAULT_PAID_TERM_ID)];
 
         /// Is the platform is accepting new members or not
-        NewMembershipsAllowed get(new_memberships_allowed) : bool = true;
+        pub NewMembershipsAllowed get(new_memberships_allowed) : bool = true;
 
         // User Input Validation parameters - do these really need to be state variables
         // I don't see a need to adjust these in future?
-        MinHandleLength get(min_handle_length) : u32 = DEFAULT_MIN_HANDLE_LENGTH;
-        MaxHandleLength get(max_handle_length) : u32 = DEFAULT_MAX_HANDLE_LENGTH;
-        MaxAvatarUriLength get(max_avatar_uri_length) : u32 = DEFAULT_MAX_AVATAR_URI_LENGTH;
-        MaxAboutTextLength get(max_about_text_length) : u32 = DEFAULT_MAX_ABOUT_TEXT_LENGTH;
+        pub MinHandleLength get(min_handle_length) : u32 = DEFAULT_MIN_HANDLE_LENGTH;
+        pub MaxHandleLength get(max_handle_length) : u32 = DEFAULT_MAX_HANDLE_LENGTH;
+        pub MaxAvatarUriLength get(max_avatar_uri_length) : u32 = DEFAULT_MAX_AVATAR_URI_LENGTH;
+        pub MaxAboutTextLength get(max_about_text_length) : u32 = DEFAULT_MAX_ABOUT_TEXT_LENGTH;
     }
     add_extra_genesis {
-        config(default_paid_membership_fee): u64;
+        config(default_paid_membership_fee): BalanceOf<T>;
     }
 }
 
@@ -201,7 +201,7 @@ decl_module! {
         }
 
         /// Non-members can buy membership
-        fn buy_membership(origin, paid_terms_id: T::PaidTermId, user_info: UserInfo) {
+        pub fn buy_membership(origin, paid_terms_id: T::PaidTermId, user_info: UserInfo) {
             let who = ensure_signed(origin)?;
 
             // make sure we are accepting new memberships
@@ -227,26 +227,26 @@ decl_module! {
             Self::deposit_event(RawEvent::MemberRegistered(member_id, who.clone()));
         }
 
-        fn change_member_about_text(origin, text: Vec<u8>) {
+        pub fn change_member_about_text(origin, text: Vec<u8>) {
             let who = ensure_signed(origin)?;
             let member_id = Self::ensure_is_member_primary_account(who.clone())?;
             Self::_change_member_about_text(member_id, &text)?;
         }
 
-        fn change_member_avatar(origin, uri: Vec<u8>) {
+        pub fn change_member_avatar(origin, uri: Vec<u8>) {
             let who = ensure_signed(origin)?;
             let member_id = Self::ensure_is_member_primary_account(who.clone())?;
             Self::_change_member_avatar(member_id, &uri)?;
         }
 
         /// Change member's handle.
-        fn change_member_handle(origin, handle: Vec<u8>) {
+        pub fn change_member_handle(origin, handle: Vec<u8>) {
             let who = ensure_signed(origin)?;
             let member_id = Self::ensure_is_member_primary_account(who.clone())?;
             Self::_change_member_handle(member_id, handle)?;
         }
 
-        fn update_profile(origin, user_info: UserInfo) {
+        pub fn update_profile(origin, user_info: UserInfo) {
             let who = ensure_signed(origin)?;
             let member_id = Self::ensure_is_member_primary_account(who.clone())?;
 
@@ -261,15 +261,6 @@ decl_module! {
             }
         }
 
-        /// Buy the default membership (if it is active) and only provide handle
-        fn buy_default_membership_testing(origin, handle: Vec<u8>) {
-            Self::buy_membership(origin, T::PaidTermId::sa(DEFAULT_PAID_TERM_ID), UserInfo {
-                handle: Some(handle.clone()),
-                avatar_uri: None,
-                about: None
-            })?;
-        }
-
         // Notes:
         // Ability to change primary account and add/remove sub accounts should be restricted
         // to accounts that are not actively being used in other roles in the platform?
@@ -278,7 +269,7 @@ decl_module! {
         // and a way to limit damage from a compromised sub account.
         // Maybe should also not be allowed if member is suspended?
         // Main usecase for changing primary account is for identity recoverability
-        fn change_member_primary_account(origin, new_primary_account: T::AccountId) {
+        pub fn change_member_primary_account(origin, new_primary_account: T::AccountId) {
             let who = ensure_signed(origin)?;
 
             // only primary account can assign new primary account
@@ -296,7 +287,7 @@ decl_module! {
             Self::deposit_event(RawEvent::MemberChangedPrimaryAccount(member_id, new_primary_account));
         }
 
-        fn add_member_sub_account(origin, sub_account: T::AccountId) {
+        pub fn add_member_sub_account(origin, sub_account: T::AccountId) {
             let who = ensure_signed(origin)?;
             // only primary account can manage sub accounts
             let member_id = Self::ensure_is_member_primary_account(who.clone())?;
@@ -309,7 +300,7 @@ decl_module! {
             Self::deposit_event(RawEvent::MemberAddedSubAccount(member_id, sub_account));
         }
 
-        fn remove_member_sub_account(origin, sub_account: T::AccountId) {
+        pub fn remove_member_sub_account(origin, sub_account: T::AccountId) {
             let who = ensure_signed(origin)?;
             // only primary account can manage sub accounts
             let member_id = Self::ensure_is_member_primary_account(who.clone())?;

+ 137 - 3
src/membership/tests.rs

@@ -7,9 +7,143 @@ use parity_codec::Encode;
 use runtime_io::with_externalities;
 use srml_support::*;
 
+fn assert_ok_unwrap<T>(value: Option<T>, err: &'static str) -> T {
+    match value {
+        None => { assert!(false, err); value.unwrap() },
+        Some(v) => v
+    }
+}
+
+fn get_alice_info() -> registry::UserInfo {
+    registry::UserInfo {
+        handle: Some(String::from("alice").as_bytes().to_vec()),
+        avatar_uri: Some(String::from("http://avatar-url.com/alice").as_bytes().to_vec()),
+        about: Some(String::from("my name is alice").as_bytes().to_vec()),
+    }
+}
+
+const ALICE_ACCOUNT_ID: u64 = 1;
+const DEFAULT_TERMS_ID: u32 = 0;
+
+fn buy_default_membership_as_alice() -> dispatch::Result {
+    Membership::buy_membership(Origin::signed(ALICE_ACCOUNT_ID), DEFAULT_TERMS_ID, get_alice_info())
+}
+
+fn set_alice_free_balance(balance: u32) {
+    Balances::set_free_balance(&ALICE_ACCOUNT_ID, balance);
+}
+
+#[test]
+fn initial_state() {
+    const DEFAULT_FEE: u32 = 500;
+    const DEFAULT_FIRST_ID: u32 = 1000;
+
+    with_externalities(&mut ExtBuilder::default()
+        .default_paid_membership_fee(DEFAULT_FEE)
+        .first_member_id(DEFAULT_FIRST_ID).build(), ||
+    {
+        assert_eq!(Membership::first_member_id(), DEFAULT_FIRST_ID);
+        assert_eq!(Membership::next_member_id(), DEFAULT_FIRST_ID);
+
+        let default_terms = assert_ok_unwrap(Membership::paid_membership_terms_by_id(DEFAULT_TERMS_ID), "default terms not initialized");
+
+        assert_eq!(default_terms.id, DEFAULT_TERMS_ID);
+        assert_eq!(default_terms.fee, DEFAULT_FEE);
+    });
+}
+
 #[test]
-fn test_setup() {
-    with_externalities(&mut initial_test_ext(), || {
-        assert!(false);
+fn buy_membership() {
+    const DEFAULT_FEE: u32 = 500;
+    const DEFAULT_FIRST_ID: u32 = 1000;
+    const SURPLUS_BALANCE: u32 = 500;
+
+    with_externalities(&mut ExtBuilder::default()
+        .default_paid_membership_fee(DEFAULT_FEE)
+        .first_member_id(DEFAULT_FIRST_ID).build(), ||
+    {
+        let initial_balance = DEFAULT_FEE + SURPLUS_BALANCE;
+        set_alice_free_balance(initial_balance);
+
+        assert_ok!(buy_default_membership_as_alice());
+
+        let member_id = assert_ok_unwrap(Membership::member_id_by_account_id(&ALICE_ACCOUNT_ID), "member id not assigned");
+
+        let profile = assert_ok_unwrap(Membership::member_profile(&member_id), "member profile created");
+
+        assert_eq!(Some(profile.handle), get_alice_info().handle);
+
+        assert_eq!(Balances::free_balance(&ALICE_ACCOUNT_ID), SURPLUS_BALANCE);
+
+    });
+}
+
+#[test]
+fn buy_membership_fails_without_enough_balance() {
+    const DEFAULT_FEE: u32 = 500;
+
+    with_externalities(&mut ExtBuilder::default()
+        .default_paid_membership_fee(DEFAULT_FEE).build(), ||
+    {
+        let initial_balance = DEFAULT_FEE - 1;
+        set_alice_free_balance(initial_balance);
+
+        assert!(buy_default_membership_as_alice().is_err());
+    });
+}
+
+#[test]
+fn new_memberships_allowed_flag() {
+    const DEFAULT_FEE: u32 = 500;
+
+    with_externalities(&mut ExtBuilder::default()
+        .default_paid_membership_fee(DEFAULT_FEE).build(), ||
+    {
+        let initial_balance = DEFAULT_FEE + 1;
+        set_alice_free_balance(initial_balance);
+
+        <registry::NewMembershipsAllowed<Test>>::put(false);
+
+        assert!(buy_default_membership_as_alice().is_err());
+    });
+}
+
+#[test]
+fn account_cannot_create_multiple_memberships() {
+    const DEFAULT_FEE: u32 = 500;
+    const SURPLUS_BALANCE: u32 = 500;
+
+    with_externalities(&mut ExtBuilder::default()
+        .default_paid_membership_fee(DEFAULT_FEE).build(), ||
+    {
+        let initial_balance = DEFAULT_FEE + SURPLUS_BALANCE;
+        set_alice_free_balance(initial_balance);
+
+        // First time it works
+        assert_ok!(buy_default_membership_as_alice());
+
+        // second attempt should fail
+        assert!(buy_default_membership_as_alice().is_err());
+
+    });
+}
+
+#[test]
+fn unique_handles() {
+    const DEFAULT_FEE: u32 = 500;
+    const SURPLUS_BALANCE: u32 = 500;
+
+    with_externalities(&mut ExtBuilder::default()
+        .default_paid_membership_fee(DEFAULT_FEE).build(), ||
+    {
+        let initial_balance = DEFAULT_FEE + SURPLUS_BALANCE;
+        set_alice_free_balance(initial_balance);
+
+        // alice's handle already taken
+        <registry::Handles<Test>>::insert(get_alice_info().handle.unwrap(), 1);
+
+        // should not be allowed to buy membership with that handle
+        assert!(buy_default_membership_as_alice().is_err());
+
     });
 }