cancel_opening.rs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431
  1. use crate::mock::*;
  2. use crate::test::*;
  3. use crate::test::public_api::*;
  4. use sp_std::collections::btree_map::BTreeMap;
  5. /*
  6. Not covered:
  7. - Application content check
  8. */
  9. pub struct CancelOpeningFixture {
  10. pub opening_id: OpeningId,
  11. pub application_stake_unstaking_period: Option<BlockNumber>,
  12. pub role_stake_unstaking_period: Option<BlockNumber>,
  13. }
  14. impl CancelOpeningFixture {
  15. pub(crate) fn default_for_opening(opening_id: OpeningId) -> Self {
  16. CancelOpeningFixture {
  17. opening_id,
  18. application_stake_unstaking_period: None,
  19. role_stake_unstaking_period: None,
  20. }
  21. }
  22. fn call_and_assert(&self, expected_result: Result<OpeningCancelled, CancelOpeningError>) {
  23. let old_opening = <OpeningById<Test>>::get(self.opening_id);
  24. let old_applications = self.extract_applications();
  25. let cancel_opening_result = self.cancel_opening();
  26. assert_eq!(cancel_opening_result, expected_result);
  27. self.assert_opening_content(old_opening, cancel_opening_result.clone());
  28. if !cancel_opening_result.is_ok() {
  29. self.assert_same_applications(old_applications);
  30. }
  31. }
  32. fn assert_same_applications(
  33. &self,
  34. old_applications: BTreeMap<ApplicationId, Application<OpeningId, BlockNumber, StakeId>>,
  35. ) {
  36. for (app_id, application) in old_applications {
  37. let test_application = <ApplicationById<Test>>::get(app_id);
  38. assert_eq!(application, test_application)
  39. }
  40. }
  41. fn extract_applications(
  42. &self,
  43. ) -> BTreeMap<ApplicationId, Application<OpeningId, BlockNumber, StakeId>> {
  44. let opening = <OpeningById<Test>>::get(self.opening_id);
  45. if let OpeningStage::Active {
  46. applications_added, ..
  47. } = opening.stage
  48. {
  49. applications_added
  50. .iter()
  51. .map(|app_id| (*app_id, <ApplicationById<Test>>::get(app_id)))
  52. .collect::<BTreeMap<ApplicationId, Application<OpeningId, BlockNumber, StakeId>>>()
  53. } else {
  54. BTreeMap::new()
  55. }
  56. }
  57. pub(crate) fn cancel_opening(&self) -> Result<OpeningCancelled, CancelOpeningError> {
  58. Hiring::cancel_opening(
  59. self.opening_id,
  60. self.application_stake_unstaking_period,
  61. self.role_stake_unstaking_period,
  62. )
  63. }
  64. fn assert_opening_content(
  65. &self,
  66. old_opening: Opening<Balance, BlockNumber, ApplicationId>,
  67. cancel_opening_result: Result<OpeningCancelled, CancelOpeningError>,
  68. ) {
  69. let new_opening = <OpeningById<Test>>::get(self.opening_id);
  70. let mut expected_opening = old_opening.clone();
  71. if cancel_opening_result.is_ok() {
  72. if let hiring::OpeningStage::Active {
  73. stage,
  74. applications_added,
  75. ..
  76. } = old_opening.stage
  77. {
  78. // compose expected stage
  79. let expected_active_stage = match stage {
  80. ActiveOpeningStage::AcceptingApplications {
  81. started_accepting_applicants_at_block,
  82. } => ActiveOpeningStage::Deactivated {
  83. cause: OpeningDeactivationCause::CancelledAcceptingApplications,
  84. deactivated_at_block: FIRST_BLOCK_HEIGHT,
  85. started_accepting_applicants_at_block,
  86. started_review_period_at_block: None,
  87. },
  88. ActiveOpeningStage::ReviewPeriod {
  89. started_accepting_applicants_at_block,
  90. started_review_period_at_block,
  91. } => ActiveOpeningStage::Deactivated {
  92. cause: OpeningDeactivationCause::CancelledInReviewPeriod,
  93. deactivated_at_block: FIRST_BLOCK_HEIGHT,
  94. started_accepting_applicants_at_block,
  95. started_review_period_at_block: Some(started_review_period_at_block),
  96. },
  97. ActiveOpeningStage::Deactivated { .. } => {
  98. panic!("OpeningNotInCancellableStage")
  99. }
  100. };
  101. // calculate application counters
  102. let mut deactivated_app_count = 0;
  103. let mut unstaking_app_count = 0;
  104. for app_id in applications_added.clone() {
  105. let application = <ApplicationById<Test>>::get(app_id);
  106. match application.stage {
  107. ApplicationStage::Active => panic!("Cannot be in active stage"),
  108. ApplicationStage::Inactive { .. } => {
  109. deactivated_app_count += 1;
  110. }
  111. ApplicationStage::Unstaking { .. } => {
  112. unstaking_app_count += 1;
  113. }
  114. }
  115. }
  116. expected_opening.stage = hiring::OpeningStage::Active {
  117. stage: expected_active_stage,
  118. applications_added,
  119. active_application_count: 0,
  120. unstaking_application_count: unstaking_app_count,
  121. deactivated_application_count: deactivated_app_count,
  122. };
  123. } else {
  124. panic!("old opening stage MUST be active")
  125. }
  126. };
  127. assert_eq!(expected_opening, new_opening);
  128. }
  129. }
  130. #[test]
  131. fn cancel_opening_fails_due_to_opening_not_existing() {
  132. build_test_externalities().execute_with(|| {
  133. let cancel_opening_fixture = CancelOpeningFixture::default_for_opening(0);
  134. cancel_opening_fixture.call_and_assert(Err(CancelOpeningError::OpeningDoesNotExist));
  135. });
  136. }
  137. #[test]
  138. fn cancel_opening_succeeds_with_single_crowded_out_application() {
  139. build_test_externalities().execute_with(|| {
  140. let mut opening_fixture = AddOpeningFixture::default();
  141. opening_fixture.application_rationing_policy = Some(hiring::ApplicationRationingPolicy {
  142. max_active_applicants: 1,
  143. });
  144. opening_fixture.application_staking_policy = Some(StakingPolicy {
  145. amount: 100,
  146. amount_mode: StakingAmountLimitMode::AtLeast,
  147. crowded_out_unstaking_period_length: None,
  148. review_period_expired_unstaking_period_length: None,
  149. });
  150. opening_fixture.role_staking_policy = Some(StakingPolicy {
  151. amount: 100,
  152. amount_mode: StakingAmountLimitMode::AtLeast,
  153. crowded_out_unstaking_period_length: None,
  154. review_period_expired_unstaking_period_length: None,
  155. });
  156. let add_opening_result = opening_fixture.add_opening();
  157. let opening_id = add_opening_result.unwrap();
  158. let mut application_fixture = AddApplicationFixture::default_for_opening(opening_id);
  159. application_fixture.opt_application_stake_imbalance =
  160. Some(stake::NegativeImbalance::<Test>::new(100));
  161. application_fixture.opt_role_stake_imbalance =
  162. Some(stake::NegativeImbalance::<Test>::new(100));
  163. assert!(application_fixture.add_application().is_ok());
  164. application_fixture.opt_application_stake_imbalance =
  165. Some(stake::NegativeImbalance::<Test>::new(101));
  166. application_fixture.opt_role_stake_imbalance =
  167. Some(stake::NegativeImbalance::<Test>::new(101));
  168. assert!(application_fixture.add_application().is_ok());
  169. let cancel_opening_fixture = CancelOpeningFixture::default_for_opening(opening_id);
  170. cancel_opening_fixture.call_and_assert(Ok(OpeningCancelled {
  171. number_of_unstaking_applications: 1,
  172. number_of_deactivated_applications: 0,
  173. }));
  174. });
  175. }
  176. #[test]
  177. fn cancel_opening_succeeds_with_single_unstaking_application() {
  178. build_test_externalities().execute_with(|| {
  179. let mut opening_fixture = AddOpeningFixture::default();
  180. opening_fixture.application_staking_policy = Some(StakingPolicy {
  181. amount: 100,
  182. amount_mode: StakingAmountLimitMode::AtLeast,
  183. crowded_out_unstaking_period_length: None,
  184. review_period_expired_unstaking_period_length: None,
  185. });
  186. let add_opening_result = opening_fixture.add_opening();
  187. let opening_id = add_opening_result.unwrap();
  188. let mut application_fixture = AddApplicationFixture::default_for_opening(opening_id);
  189. application_fixture.opt_application_stake_imbalance =
  190. Some(stake::NegativeImbalance::<Test>::new(100));
  191. let app_application_result = application_fixture.add_application();
  192. assert!(app_application_result.is_ok());
  193. let cancel_opening_fixture = CancelOpeningFixture::default_for_opening(opening_id);
  194. cancel_opening_fixture.call_and_assert(Ok(OpeningCancelled {
  195. number_of_unstaking_applications: 1,
  196. number_of_deactivated_applications: 0,
  197. }));
  198. });
  199. }
  200. #[test]
  201. fn cancel_opening_succeeds_with_single_deactivated_application() {
  202. build_test_externalities().execute_with(|| {
  203. let opening_fixture = AddOpeningFixture::default();
  204. let add_opening_result = opening_fixture.add_opening();
  205. let opening_id = add_opening_result.unwrap();
  206. let application_fixture = AddApplicationFixture::default_for_opening(opening_id);
  207. let app_application_result = application_fixture.add_application();
  208. assert!(app_application_result.is_ok());
  209. let cancel_opening_fixture = CancelOpeningFixture::default_for_opening(opening_id);
  210. cancel_opening_fixture.call_and_assert(Ok(OpeningCancelled {
  211. number_of_unstaking_applications: 0,
  212. number_of_deactivated_applications: 1,
  213. }));
  214. });
  215. }
  216. #[test]
  217. fn cancel_opening_fails_due_to_opening_is_not_active() {
  218. build_test_externalities().execute_with(|| {
  219. let mut opening_fixture = AddOpeningFixture::default();
  220. opening_fixture.activate_at = ActivateOpeningAt::ExactBlock(5);
  221. let add_opening_result = opening_fixture.add_opening();
  222. let opening_id = add_opening_result.unwrap();
  223. let cancel_opening_fixture = CancelOpeningFixture::default_for_opening(opening_id);
  224. cancel_opening_fixture
  225. .call_and_assert(Err(CancelOpeningError::OpeningNotInCancellableStage));
  226. });
  227. }
  228. #[test]
  229. fn cancel_opening_fails_due_to_opening_not_in_cancellable_stage() {
  230. build_test_externalities().execute_with(|| {
  231. let opening_fixture = AddOpeningFixture::default();
  232. let add_opening_result = opening_fixture.add_opening();
  233. let opening_id = add_opening_result.unwrap();
  234. let application_fixture = AddApplicationFixture::default_for_opening(opening_id);
  235. let app_application_result = application_fixture.add_application();
  236. assert!(app_application_result.is_ok());
  237. let cancel_opening_fixture = CancelOpeningFixture::default_for_opening(opening_id);
  238. assert!(cancel_opening_fixture.cancel_opening().is_ok());
  239. cancel_opening_fixture
  240. .call_and_assert(Err(CancelOpeningError::OpeningNotInCancellableStage));
  241. });
  242. }
  243. #[test]
  244. fn cancel_opening_fails_due_to_redundant_application_unstaking_period() {
  245. build_test_externalities().execute_with(|| {
  246. let opening_fixture = AddOpeningFixture::default();
  247. let add_opening_result = opening_fixture.add_opening();
  248. let opening_id = add_opening_result.unwrap();
  249. let mut cancel_opening_fixture = CancelOpeningFixture::default_for_opening(opening_id);
  250. cancel_opening_fixture.application_stake_unstaking_period = Some(0);
  251. cancel_opening_fixture.call_and_assert(Err(CancelOpeningError::UnstakingPeriodTooShort(
  252. StakePurpose::Application,
  253. )));
  254. });
  255. }
  256. #[test]
  257. fn cancel_opening_fails_due_to_redundant_role_unstaking_period() {
  258. build_test_externalities().execute_with(|| {
  259. let opening_fixture = AddOpeningFixture::default();
  260. let add_opening_result = opening_fixture.add_opening();
  261. let opening_id = add_opening_result.unwrap();
  262. let mut cancel_opening_fixture = CancelOpeningFixture::default_for_opening(opening_id);
  263. cancel_opening_fixture.role_stake_unstaking_period = Some(50);
  264. cancel_opening_fixture.call_and_assert(Err(
  265. CancelOpeningError::RedundantUnstakingPeriodProvided(StakePurpose::Role),
  266. ));
  267. });
  268. }
  269. #[test]
  270. fn cancel_opening_fails_due_to_too_short_application_unstaking_period() {
  271. build_test_externalities().execute_with(|| {
  272. let mut opening_fixture = AddOpeningFixture::default();
  273. opening_fixture.application_staking_policy = Some(StakingPolicy {
  274. amount: 100,
  275. amount_mode: StakingAmountLimitMode::AtLeast,
  276. crowded_out_unstaking_period_length: None,
  277. review_period_expired_unstaking_period_length: None,
  278. });
  279. let add_opening_result = opening_fixture.add_opening();
  280. let opening_id = add_opening_result.unwrap();
  281. let mut cancel_opening_fixture = CancelOpeningFixture::default_for_opening(opening_id);
  282. cancel_opening_fixture.application_stake_unstaking_period = Some(0);
  283. cancel_opening_fixture.call_and_assert(Err(CancelOpeningError::UnstakingPeriodTooShort(
  284. StakePurpose::Application,
  285. )));
  286. });
  287. }
  288. #[test]
  289. fn cancel_opening_fails_due_to_too_short_role_unstaking_period() {
  290. build_test_externalities().execute_with(|| {
  291. let mut opening_fixture = AddOpeningFixture::default();
  292. opening_fixture.role_staking_policy = Some(StakingPolicy {
  293. amount: 100,
  294. amount_mode: StakingAmountLimitMode::AtLeast,
  295. crowded_out_unstaking_period_length: None,
  296. review_period_expired_unstaking_period_length: None,
  297. });
  298. let add_opening_result = opening_fixture.add_opening();
  299. let opening_id = add_opening_result.unwrap();
  300. let mut cancel_opening_fixture = CancelOpeningFixture::default_for_opening(opening_id);
  301. cancel_opening_fixture.role_stake_unstaking_period = Some(0);
  302. cancel_opening_fixture.call_and_assert(Err(CancelOpeningError::UnstakingPeriodTooShort(
  303. StakePurpose::Role,
  304. )));
  305. });
  306. }
  307. #[test]
  308. fn cancel_opening_succeeds_with_single_unstaking_application_with_application_stake_checks() {
  309. handle_mock(|| {
  310. build_test_externalities().execute_with(|| {
  311. let mock = default_mock_for_creating_stake();
  312. set_stake_handler_impl(mock.clone());
  313. let mut opening_fixture = AddOpeningFixture::default();
  314. opening_fixture.application_staking_policy = Some(StakingPolicy {
  315. amount: 100,
  316. amount_mode: StakingAmountLimitMode::AtLeast,
  317. crowded_out_unstaking_period_length: None,
  318. review_period_expired_unstaking_period_length: None,
  319. });
  320. let add_opening_result = opening_fixture.add_opening();
  321. let opening_id = add_opening_result.unwrap();
  322. let mut application_fixture = AddApplicationFixture::default_for_opening(opening_id);
  323. application_fixture.opt_application_stake_imbalance =
  324. Some(stake::NegativeImbalance::<Test>::new(100));
  325. let app_application_result = application_fixture.add_application();
  326. let application_id = app_application_result.unwrap().application_id_added;
  327. let mock2 = default_mock_for_unstaking();
  328. set_stake_handler_impl(mock2.clone());
  329. let cancel_opening_fixture = CancelOpeningFixture::default_for_opening(opening_id);
  330. cancel_opening_fixture.call_and_assert(Ok(OpeningCancelled {
  331. number_of_unstaking_applications: 1,
  332. number_of_deactivated_applications: 0,
  333. }));
  334. TestApplicationDeactivatedHandler::assert_deactivated_application(
  335. application_id,
  336. ApplicationDeactivationCause::OpeningCancelled,
  337. );
  338. })
  339. });
  340. }
  341. #[test]
  342. fn cancel_opening_succeeds_with_single_unstaking_application_with_role_stake_checks() {
  343. handle_mock(|| {
  344. build_test_externalities().execute_with(|| {
  345. let mock = default_mock_for_creating_stake();
  346. set_stake_handler_impl(mock.clone());
  347. let mut opening_fixture = AddOpeningFixture::default();
  348. opening_fixture.role_staking_policy = Some(StakingPolicy {
  349. amount: 100,
  350. amount_mode: StakingAmountLimitMode::AtLeast,
  351. crowded_out_unstaking_period_length: None,
  352. review_period_expired_unstaking_period_length: None,
  353. });
  354. let add_opening_result = opening_fixture.add_opening();
  355. let opening_id = add_opening_result.unwrap();
  356. let mut application_fixture = AddApplicationFixture::default_for_opening(opening_id);
  357. application_fixture.opt_role_stake_imbalance =
  358. Some(stake::NegativeImbalance::<Test>::new(100));
  359. let app_application_result = application_fixture.add_application();
  360. assert!(app_application_result.is_ok());
  361. let mock2 = default_mock_for_unstaking();
  362. set_stake_handler_impl(mock2.clone());
  363. let cancel_opening_fixture = CancelOpeningFixture::default_for_opening(opening_id);
  364. cancel_opening_fixture.call_and_assert(Ok(OpeningCancelled {
  365. number_of_unstaking_applications: 1,
  366. number_of_deactivated_applications: 0,
  367. }));
  368. })
  369. });
  370. }