Files
pezkuwi-subxt/substrate/frame/bags-list/src/tests.rs
T
Kian Paimani adb9f6d71a make BagsList::put_in_front_of be permissionless (#14714)
* make BagsList::put_in_fron_of be permissionless

* Update frame/bags-list/src/lib.rs

Co-authored-by: Liam Aharon <liam.aharon@hotmail.com>

* improve docs as well

* update lock

* Update frame/bags-list/Cargo.toml

Co-authored-by: Sam Johnson <sam@durosoft.com>

* fix

* Update frame/bags-list/src/lib.rs

Co-authored-by: Michael Assaf <94772640+snowmead@users.noreply.github.com>

---------

Co-authored-by: Liam Aharon <liam.aharon@hotmail.com>
Co-authored-by: Sam Johnson <sam@durosoft.com>
Co-authored-by: Michael Assaf <94772640+snowmead@users.noreply.github.com>
2023-08-12 19:25:09 +00:00

739 lines
22 KiB
Rust

// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// 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.
use frame_support::{assert_noop, assert_ok, assert_storage_noop, traits::IntegrityTest};
use super::*;
use frame_election_provider_support::{SortedListProvider, VoteWeight};
use list::Bag;
use mock::{test_utils::*, *};
#[docify::export]
#[test]
fn examples_work() {
ExtBuilder::default()
.skip_genesis_ids()
// initially set the score of 11 for 22 to push it next to 12
.add_ids(vec![(25, 25), (21, 21), (12, 12), (22, 11), (5, 5), (7, 7), (3, 3)])
.build_and_execute(|| {
// initial bags
assert_eq!(
List::<Runtime>::get_bags(),
vec![
// bag 0 -> 10
(10, vec![5, 7, 3]),
// bag 10 -> 20
(20, vec![12, 22]),
// bag 20 -> 30
(30, vec![25, 21])
]
);
// set score of 22 to 22
StakingMock::set_score_of(&22, 22);
// now we rebag 22 to the first bag
assert_ok!(BagsList::rebag(RuntimeOrigin::signed(42), 22));
assert_eq!(
List::<Runtime>::get_bags(),
vec![
// bag 0 -> 10
(10, vec![5, 7, 3]),
// bag 10 -> 20
(20, vec![12]),
// bag 20 -> 30
(30, vec![25, 21, 22])
]
);
// now we put 7 at the front of bag 0
assert_ok!(BagsList::put_in_front_of(RuntimeOrigin::signed(7), 5));
assert_eq!(
List::<Runtime>::get_bags(),
vec![
// bag 0 -> 10
(10, vec![7, 5, 3]),
// bag 10 -> 20
(20, vec![12]),
// bag 20 -> 30
(30, vec![25, 21, 22])
]
);
})
}
mod pallet {
use super::*;
#[test]
fn rebag_works() {
ExtBuilder::default().add_ids(vec![(42, 20)]).build_and_execute(|| {
// given
assert_eq!(
List::<Runtime>::get_bags(),
vec![(10, vec![1]), (20, vec![42]), (1_000, vec![2, 3, 4])]
);
// when increasing score to the level of non-existent bag
assert_eq!(List::<Runtime>::get_score(&42).unwrap(), 20);
StakingMock::set_score_of(&42, 2_000);
assert_ok!(BagsList::rebag(RuntimeOrigin::signed(0), 42));
assert_eq!(List::<Runtime>::get_score(&42).unwrap(), 2_000);
// then a new bag is created and the id moves into it
assert_eq!(
List::<Runtime>::get_bags(),
vec![(10, vec![1]), (1_000, vec![2, 3, 4]), (2_000, vec![42])]
);
// when decreasing score within the range of the current bag
StakingMock::set_score_of(&42, 1_001);
assert_ok!(BagsList::rebag(RuntimeOrigin::signed(0), 42));
// then the id does not move
assert_eq!(
List::<Runtime>::get_bags(),
vec![(10, vec![1]), (1_000, vec![2, 3, 4]), (2_000, vec![42])]
);
// but the score is updated
assert_eq!(List::<Runtime>::get_score(&42).unwrap(), 1_001);
// when reducing score to the level of a non-existent bag
StakingMock::set_score_of(&42, 30);
assert_ok!(BagsList::rebag(RuntimeOrigin::signed(0), 42));
// then a new bag is created and the id moves into it
assert_eq!(
List::<Runtime>::get_bags(),
vec![(10, vec![1]), (30, vec![42]), (1_000, vec![2, 3, 4])]
);
assert_eq!(List::<Runtime>::get_score(&42).unwrap(), 30);
// when increasing score to the level of a pre-existing bag
StakingMock::set_score_of(&42, 500);
assert_ok!(BagsList::rebag(RuntimeOrigin::signed(0), 42));
// then the id moves into that bag
assert_eq!(
List::<Runtime>::get_bags(),
vec![(10, vec![1]), (1_000, vec![2, 3, 4, 42])]
);
assert_eq!(List::<Runtime>::get_score(&42).unwrap(), 500);
});
}
// Rebagging the tail of a bag results in the old bag having a new tail and an overall correct
// state.
#[test]
fn rebag_tail_works() {
ExtBuilder::default().build_and_execute(|| {
// given
assert_eq!(List::<Runtime>::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]);
// when
StakingMock::set_score_of(&4, 10);
assert_ok!(BagsList::rebag(RuntimeOrigin::signed(0), 4));
// then
assert_eq!(List::<Runtime>::get_bags(), vec![(10, vec![1, 4]), (1_000, vec![2, 3])]);
assert_eq!(Bag::<Runtime>::get(1_000).unwrap(), Bag::new(Some(2), Some(3), 1_000));
// when
StakingMock::set_score_of(&3, 10);
assert_ok!(BagsList::rebag(RuntimeOrigin::signed(0), 3));
// then
assert_eq!(List::<Runtime>::get_bags(), vec![(10, vec![1, 4, 3]), (1_000, vec![2])]);
assert_eq!(Bag::<Runtime>::get(10).unwrap(), Bag::new(Some(1), Some(3), 10));
assert_eq!(Bag::<Runtime>::get(1_000).unwrap(), Bag::new(Some(2), Some(2), 1_000));
assert_eq!(get_list_as_ids(), vec![2u32, 1, 4, 3]);
// when
StakingMock::set_score_of(&2, 10);
assert_ok!(BagsList::rebag(RuntimeOrigin::signed(0), 2));
// then
assert_eq!(List::<Runtime>::get_bags(), vec![(10, vec![1, 4, 3, 2])]);
assert_eq!(Bag::<Runtime>::get(1_000), None);
});
}
// Rebagging the head of a bag results in the old bag having a new head and an overall correct
// state.
#[test]
fn rebag_head_works() {
ExtBuilder::default().build_and_execute(|| {
// when
StakingMock::set_score_of(&2, 10);
assert_ok!(BagsList::rebag(RuntimeOrigin::signed(0), 2));
// then
assert_eq!(List::<Runtime>::get_bags(), vec![(10, vec![1, 2]), (1_000, vec![3, 4])]);
assert_eq!(Bag::<Runtime>::get(1_000).unwrap(), Bag::new(Some(3), Some(4), 1_000));
// when
StakingMock::set_score_of(&3, 10);
assert_ok!(BagsList::rebag(RuntimeOrigin::signed(0), 3));
// then
assert_eq!(List::<Runtime>::get_bags(), vec![(10, vec![1, 2, 3]), (1_000, vec![4])]);
assert_eq!(Bag::<Runtime>::get(1_000).unwrap(), Bag::new(Some(4), Some(4), 1_000));
// when
StakingMock::set_score_of(&4, 10);
assert_ok!(BagsList::rebag(RuntimeOrigin::signed(0), 4));
// then
assert_eq!(List::<Runtime>::get_bags(), vec![(10, vec![1, 2, 3, 4])]);
assert_eq!(Bag::<Runtime>::get(1_000), None);
});
}
#[test]
fn wrong_rebag_errs() {
ExtBuilder::default().build_and_execute(|| {
let node_3 = list::Node::<Runtime>::get(&3).unwrap();
// when account 3 is _not_ misplaced with score 500
NextVoteWeight::set(500);
assert!(!node_3.is_misplaced(500));
// then calling rebag on account 3 with score 500 is a noop
assert_storage_noop!(assert_eq!(BagsList::rebag(RuntimeOrigin::signed(0), 3), Ok(())));
// when account 42 is not in the list
assert!(!BagsList::contains(&42));
// then rebag-ing account 42 is an error
assert_storage_noop!(assert!(matches!(
BagsList::rebag(RuntimeOrigin::signed(0), 42),
Err(_)
)));
});
}
#[test]
#[should_panic = "thresholds must strictly increase, and have no duplicates"]
fn duplicate_in_bags_threshold_panics() {
const DUPE_THRESH: &[VoteWeight; 4] = &[10, 20, 30, 30];
BagThresholds::set(DUPE_THRESH);
BagsList::integrity_test();
}
#[test]
#[should_panic = "thresholds must strictly increase, and have no duplicates"]
fn decreasing_in_bags_threshold_panics() {
const DECREASING_THRESH: &[VoteWeight; 4] = &[10, 30, 20, 40];
BagThresholds::set(DECREASING_THRESH);
BagsList::integrity_test();
}
#[test]
fn empty_threshold_works() {
BagThresholds::set(Default::default()); // which is the same as passing `()` to `Get<_>`.
ExtBuilder::default().build_and_execute(|| {
// everyone in the same bag.
assert_eq!(List::<Runtime>::get_bags(), vec![(VoteWeight::MAX, vec![1, 2, 3, 4])]);
// any insertion goes there as well.
assert_ok!(List::<Runtime>::insert(5, 999));
assert_ok!(List::<Runtime>::insert(6, 0));
assert_eq!(
List::<Runtime>::get_bags(),
vec![(VoteWeight::MAX, vec![1, 2, 3, 4, 5, 6])]
);
// any rebag is noop.
assert_storage_noop!(assert_eq!(BagsList::rebag(RuntimeOrigin::signed(0), 1), Ok(())));
})
}
#[test]
fn put_in_front_of_other_can_be_permissionless() {
ExtBuilder::default()
.skip_genesis_ids()
.add_ids(vec![(10, 15), (11, 16), (12, 19)])
.build_and_execute(|| {
// given
assert_eq!(List::<Runtime>::get_bags(), vec![(20, vec![10, 11, 12])]);
// 11 now has more weight than 10 and can be moved before it.
StakingMock::set_score_of(&11u32, 17);
// when
assert_ok!(BagsList::put_in_front_of_other(RuntimeOrigin::signed(42), 11u32, 10));
// then
assert_eq!(List::<Runtime>::get_bags(), vec![(20, vec![11, 10, 12])]);
});
}
#[test]
fn put_in_front_of_two_node_bag_heavier_is_tail() {
ExtBuilder::default()
.skip_genesis_ids()
.add_ids(vec![(10, 15), (11, 16)])
.build_and_execute(|| {
// given
assert_eq!(List::<Runtime>::get_bags(), vec![(20, vec![10, 11])]);
// when
assert_ok!(BagsList::put_in_front_of(RuntimeOrigin::signed(11), 10));
// then
assert_eq!(List::<Runtime>::get_bags(), vec![(20, vec![11, 10])]);
});
}
#[test]
fn put_in_front_of_two_node_bag_heavier_is_head() {
ExtBuilder::default()
.skip_genesis_ids()
.add_ids(vec![(11, 16), (10, 15)])
.build_and_execute(|| {
// given
assert_eq!(List::<Runtime>::get_bags(), vec![(20, vec![11, 10])]);
// when
assert_ok!(BagsList::put_in_front_of(RuntimeOrigin::signed(11), 10));
// then
assert_eq!(List::<Runtime>::get_bags(), vec![(20, vec![11, 10])]);
});
}
#[test]
fn put_in_front_of_non_terminal_nodes_heavier_behind() {
ExtBuilder::default().add_ids(vec![(5, 1_000)]).build_and_execute(|| {
// given
assert_eq!(List::<Runtime>::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4, 5])]);
StakingMock::set_score_of(&3, 999);
// when
assert_ok!(BagsList::put_in_front_of(RuntimeOrigin::signed(4), 3));
// then
assert_eq!(List::<Runtime>::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 4, 3, 5])]);
});
}
#[test]
fn put_in_front_of_non_terminal_nodes_heavier_in_front() {
ExtBuilder::default()
.add_ids(vec![(5, 1_000), (6, 1_000)])
.build_and_execute(|| {
// given
assert_eq!(
List::<Runtime>::get_bags(),
vec![(10, vec![1]), (1_000, vec![2, 3, 4, 5, 6])]
);
StakingMock::set_score_of(&5, 999);
// when
assert_ok!(BagsList::put_in_front_of(RuntimeOrigin::signed(3), 5));
// then
assert_eq!(
List::<Runtime>::get_bags(),
vec![(10, vec![1]), (1_000, vec![2, 4, 3, 5, 6])]
);
});
}
#[test]
fn put_in_front_of_lighter_is_head_heavier_is_non_terminal() {
ExtBuilder::default().build_and_execute(|| {
// given
assert_eq!(List::<Runtime>::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]);
StakingMock::set_score_of(&2, 999);
// when
assert_ok!(BagsList::put_in_front_of(RuntimeOrigin::signed(3), 2));
// then
assert_eq!(List::<Runtime>::get_bags(), vec![(10, vec![1]), (1_000, vec![3, 2, 4])]);
});
}
#[test]
fn put_in_front_of_heavier_is_tail_lighter_is_non_terminal() {
ExtBuilder::default().build_and_execute(|| {
// given
assert_eq!(List::<Runtime>::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]);
StakingMock::set_score_of(&3, 999);
// when
assert_ok!(BagsList::put_in_front_of(RuntimeOrigin::signed(4), 3));
// then
assert_eq!(List::<Runtime>::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 4, 3])]);
});
}
#[test]
fn put_in_front_of_heavier_is_tail_lighter_is_head() {
ExtBuilder::default().add_ids(vec![(5, 1_000)]).build_and_execute(|| {
// given
assert_eq!(List::<Runtime>::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4, 5])]);
StakingMock::set_score_of(&2, 999);
// when
assert_ok!(BagsList::put_in_front_of(RuntimeOrigin::signed(5), 2));
// then
assert_eq!(List::<Runtime>::get_bags(), vec![(10, vec![1]), (1_000, vec![5, 2, 3, 4])]);
});
}
#[test]
fn put_in_front_of_heavier_is_head_lighter_is_not_terminal() {
ExtBuilder::default().add_ids(vec![(5, 1_000)]).build_and_execute(|| {
// given
assert_eq!(List::<Runtime>::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4, 5])]);
StakingMock::set_score_of(&4, 999);
// when
BagsList::put_in_front_of(RuntimeOrigin::signed(2), 4).unwrap();
// then
assert_eq!(List::<Runtime>::get_bags(), vec![(10, vec![1]), (1_000, vec![3, 2, 4, 5])]);
});
}
#[test]
fn put_in_front_of_lighter_is_tail_heavier_is_not_terminal() {
ExtBuilder::default().add_ids(vec![(5, 900)]).build_and_execute(|| {
// given
assert_eq!(List::<Runtime>::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4, 5])]);
// when
BagsList::put_in_front_of(RuntimeOrigin::signed(3), 5).unwrap();
// then
assert_eq!(List::<Runtime>::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 4, 3, 5])]);
});
}
#[test]
fn put_in_front_of_lighter_is_tail_heavier_is_head() {
ExtBuilder::default().build_and_execute(|| {
// given
assert_eq!(List::<Runtime>::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]);
StakingMock::set_score_of(&4, 999);
// when
assert_ok!(BagsList::put_in_front_of(RuntimeOrigin::signed(2), 4));
// then
assert_eq!(List::<Runtime>::get_bags(), vec![(10, vec![1]), (1_000, vec![3, 2, 4])]);
});
}
#[test]
fn put_in_front_of_errors_if_heavier_is_less_than_lighter() {
ExtBuilder::default().build_and_execute(|| {
// given
assert_eq!(List::<Runtime>::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]);
StakingMock::set_score_of(&3, 999);
// then
assert_noop!(
BagsList::put_in_front_of(RuntimeOrigin::signed(3), 2),
crate::pallet::Error::<Runtime>::List(ListError::NotHeavier)
);
});
}
#[test]
fn put_in_front_of_errors_if_heavier_is_equal_weight_to_lighter() {
ExtBuilder::default().build_and_execute(|| {
// given
assert_eq!(List::<Runtime>::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]);
// then
assert_noop!(
BagsList::put_in_front_of(RuntimeOrigin::signed(3), 4),
crate::pallet::Error::<Runtime>::List(ListError::NotHeavier)
);
});
}
#[test]
fn put_in_front_of_errors_if_nodes_not_found() {
// `heavier` not found
ExtBuilder::default().build_and_execute(|| {
// given
assert_eq!(List::<Runtime>::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]);
assert!(!ListNodes::<Runtime>::contains_key(5));
// then
assert_noop!(
BagsList::put_in_front_of(RuntimeOrigin::signed(5), 4),
crate::pallet::Error::<Runtime>::List(ListError::NodeNotFound)
);
});
// `lighter` not found
ExtBuilder::default().build_and_execute(|| {
// given
assert_eq!(List::<Runtime>::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]);
assert!(!ListNodes::<Runtime>::contains_key(5));
// then
assert_noop!(
BagsList::put_in_front_of(RuntimeOrigin::signed(4), 5),
crate::pallet::Error::<Runtime>::List(ListError::NodeNotFound)
);
});
}
#[test]
fn put_in_front_of_errors_if_nodes_not_in_same_bag() {
ExtBuilder::default().build_and_execute(|| {
// given
assert_eq!(List::<Runtime>::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]);
// then
assert_noop!(
BagsList::put_in_front_of(RuntimeOrigin::signed(4), 1),
crate::pallet::Error::<Runtime>::List(ListError::NotInSameBag)
);
});
}
}
mod sorted_list_provider {
use super::*;
#[test]
fn iter_works() {
ExtBuilder::default().build_and_execute(|| {
let expected = vec![2, 3, 4, 1];
for (i, id) in BagsList::iter().enumerate() {
assert_eq!(id, expected[i])
}
});
}
#[test]
fn iter_from_works() {
ExtBuilder::default().add_ids(vec![(5, 5), (6, 15)]).build_and_execute(|| {
// given
assert_eq!(
List::<Runtime>::get_bags(),
vec![(10, vec![1, 5]), (20, vec![6]), (1000, vec![2, 3, 4])]
);
assert_eq!(BagsList::iter_from(&2).unwrap().collect::<Vec<_>>(), vec![3, 4, 6, 1, 5]);
assert_eq!(BagsList::iter_from(&3).unwrap().collect::<Vec<_>>(), vec![4, 6, 1, 5]);
assert_eq!(BagsList::iter_from(&4).unwrap().collect::<Vec<_>>(), vec![6, 1, 5]);
assert_eq!(BagsList::iter_from(&6).unwrap().collect::<Vec<_>>(), vec![1, 5]);
assert_eq!(BagsList::iter_from(&1).unwrap().collect::<Vec<_>>(), vec![5]);
assert!(BagsList::iter_from(&5).unwrap().collect::<Vec<_>>().is_empty());
assert!(BagsList::iter_from(&7).is_err());
assert_storage_noop!(assert!(BagsList::iter_from(&8).is_err()));
});
}
#[test]
fn count_works() {
ExtBuilder::default().build_and_execute(|| {
// given
assert_eq!(BagsList::count(), 4);
// when inserting
assert_ok!(BagsList::on_insert(201, 0));
// then the count goes up
assert_eq!(BagsList::count(), 5);
// when removing
BagsList::on_remove(&201).unwrap();
// then the count goes down
assert_eq!(BagsList::count(), 4);
// when updating
assert_noop!(BagsList::on_update(&201, VoteWeight::MAX), ListError::NodeNotFound);
// then the count stays the same
assert_eq!(BagsList::count(), 4);
});
}
#[test]
fn on_insert_works() {
ExtBuilder::default().build_and_execute(|| {
// when
assert_ok!(BagsList::on_insert(6, 1_000));
// then the bags
assert_eq!(List::<Runtime>::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4, 6])]);
// and list correctly include the new id,
assert_eq!(BagsList::iter().collect::<Vec<_>>(), vec![2, 3, 4, 6, 1]);
// and the count is incremented.
assert_eq!(BagsList::count(), 5);
// when
assert_ok!(BagsList::on_insert(7, 1_001));
// then the bags
assert_eq!(
List::<Runtime>::get_bags(),
vec![(10, vec![1]), (1_000, vec![2, 3, 4, 6]), (2_000, vec![7])]
);
// and list correctly include the new id,
assert_eq!(BagsList::iter().collect::<Vec<_>>(), vec![7, 2, 3, 4, 6, 1]);
// and the count is incremented.
assert_eq!(BagsList::count(), 6);
})
}
#[test]
fn on_insert_errors_with_duplicate_id() {
ExtBuilder::default().build_and_execute(|| {
// given
assert!(get_list_as_ids().contains(&3));
// then
assert_storage_noop!(assert_eq!(
BagsList::on_insert(3, 20).unwrap_err(),
ListError::Duplicate
));
});
}
#[test]
fn on_update_works() {
ExtBuilder::default().add_ids(vec![(42, 20)]).build_and_execute(|| {
// given
assert_eq!(
List::<Runtime>::get_bags(),
vec![(10, vec![1]), (20, vec![42]), (1_000, vec![2, 3, 4])]
);
assert_eq!(BagsList::count(), 5);
// when increasing score to the level of non-existent bag
BagsList::on_update(&42, 2_000).unwrap();
// then the bag is created with the id in it,
assert_eq!(
List::<Runtime>::get_bags(),
vec![(10, vec![1]), (1_000, vec![2, 3, 4]), (2000, vec![42])]
);
// and the id position is updated in the list.
assert_eq!(BagsList::iter().collect::<Vec<_>>(), vec![42, 2, 3, 4, 1]);
// when decreasing score within the range of the current bag
BagsList::on_update(&42, 1_001).unwrap();
// then the id does not change bags,
assert_eq!(
List::<Runtime>::get_bags(),
vec![(10, vec![1]), (1_000, vec![2, 3, 4]), (2000, vec![42])]
);
// or change position in the list.
assert_eq!(BagsList::iter().collect::<Vec<_>>(), vec![42, 2, 3, 4, 1]);
// when increasing score to the level of a non-existent bag with the max threshold
BagsList::on_update(&42, VoteWeight::MAX).unwrap();
// the the new bag is created with the id in it,
assert_eq!(
List::<Runtime>::get_bags(),
vec![(10, vec![1]), (1_000, vec![2, 3, 4]), (VoteWeight::MAX, vec![42])]
);
// and the id position is updated in the list.
assert_eq!(BagsList::iter().collect::<Vec<_>>(), vec![42, 2, 3, 4, 1]);
// when decreasing the score to a pre-existing bag
BagsList::on_update(&42, 1_000).unwrap();
// then id is moved to the correct bag (as the last member),
assert_eq!(
List::<Runtime>::get_bags(),
vec![(10, vec![1]), (1_000, vec![2, 3, 4, 42])]
);
// and the id position is updated in the list.
assert_eq!(BagsList::iter().collect::<Vec<_>>(), vec![2, 3, 4, 42, 1]);
// since we have only called on_update, the `count` has not changed.
assert_eq!(BagsList::count(), 5);
});
}
#[test]
fn on_remove_works() {
let ensure_left = |id, counter| {
assert!(!ListNodes::<Runtime>::contains_key(id));
assert_eq!(BagsList::count(), counter);
assert_eq!(ListNodes::<Runtime>::count(), counter);
assert_eq!(ListNodes::<Runtime>::iter().count() as u32, counter);
};
ExtBuilder::default().build_and_execute(|| {
// it is a noop removing a non-existent id
assert!(!ListNodes::<Runtime>::contains_key(42));
assert_noop!(BagsList::on_remove(&42), ListError::NodeNotFound);
// when removing a node from a bag with multiple nodes
BagsList::on_remove(&2).unwrap();
// then
assert_eq!(get_list_as_ids(), vec![3, 4, 1]);
assert_eq!(List::<Runtime>::get_bags(), vec![(10, vec![1]), (1_000, vec![3, 4])]);
ensure_left(2, 3);
// when removing a node from a bag with only one node
BagsList::on_remove(&1).unwrap();
// then
assert_eq!(get_list_as_ids(), vec![3, 4]);
assert_eq!(List::<Runtime>::get_bags(), vec![(1_000, vec![3, 4])]);
ensure_left(1, 2);
// when removing all remaining ids
BagsList::on_remove(&4).unwrap();
assert_eq!(get_list_as_ids(), vec![3]);
ensure_left(4, 1);
BagsList::on_remove(&3).unwrap();
// then the storage is completely cleaned up
assert_eq!(get_list_as_ids(), Vec::<AccountId>::new());
ensure_left(3, 0);
});
}
#[test]
fn contains_works() {
ExtBuilder::default().build_and_execute(|| {
assert!(GENESIS_IDS.iter().all(|(id, _)| BagsList::contains(id)));
let non_existent_ids = vec![&42, &666, &13];
assert!(non_existent_ids.iter().all(|id| !BagsList::contains(id)));
})
}
}