mod.rs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427
  1. mod mock;
  2. use frame_support::dispatch::{DispatchError, DispatchResult};
  3. use frame_system::RawOrigin;
  4. use frame_system::{EventRecord, Phase};
  5. use crate::*;
  6. use mock::*;
  7. struct EventFixture;
  8. impl EventFixture {
  9. fn assert_events(expected_raw_events: Vec<RawEvent<u64, u64, u64>>) {
  10. let expected_events = expected_raw_events
  11. .iter()
  12. .map(|ev| EventRecord {
  13. phase: Phase::Initialization,
  14. event: TestEvent::discussion(ev.clone()),
  15. topics: vec![],
  16. })
  17. .collect::<Vec<EventRecord<_, _>>>();
  18. assert_eq!(System::events(), expected_events);
  19. }
  20. }
  21. struct TestPostEntry {
  22. pub post_id: u64,
  23. pub text: Vec<u8>,
  24. pub edition_number: u32,
  25. }
  26. struct TestThreadEntry {
  27. pub thread_id: u64,
  28. pub title: Vec<u8>,
  29. }
  30. fn assert_thread_content(thread_entry: TestThreadEntry, post_entries: Vec<TestPostEntry>) {
  31. assert!(<ThreadById<Test>>::contains_key(thread_entry.thread_id));
  32. let actual_thread = <ThreadById<Test>>::get(thread_entry.thread_id);
  33. let expected_thread = DiscussionThread {
  34. title: thread_entry.title,
  35. created_at: 0,
  36. author_id: 1,
  37. };
  38. assert_eq!(actual_thread, expected_thread);
  39. for post_entry in post_entries {
  40. let actual_post =
  41. <PostThreadIdByPostId<Test>>::get(thread_entry.thread_id, post_entry.post_id);
  42. let expected_post = DiscussionPost {
  43. text: post_entry.text,
  44. created_at: 0,
  45. updated_at: 0,
  46. author_id: 1,
  47. thread_id: thread_entry.thread_id,
  48. edition_number: post_entry.edition_number,
  49. };
  50. assert_eq!(actual_post, expected_post);
  51. }
  52. }
  53. struct DiscussionFixture {
  54. pub title: Vec<u8>,
  55. pub origin: RawOrigin<u64>,
  56. pub author_id: u64,
  57. }
  58. impl Default for DiscussionFixture {
  59. fn default() -> Self {
  60. DiscussionFixture {
  61. title: b"title".to_vec(),
  62. origin: RawOrigin::Signed(1),
  63. author_id: 1,
  64. }
  65. }
  66. }
  67. impl DiscussionFixture {
  68. fn with_title(self, title: Vec<u8>) -> Self {
  69. DiscussionFixture { title, ..self }
  70. }
  71. fn create_discussion_and_assert(&self, result: Result<u64, DispatchError>) -> Option<u64> {
  72. let create_discussion_result =
  73. Discussions::create_thread(self.author_id, self.title.clone());
  74. assert_eq!(create_discussion_result, result);
  75. create_discussion_result.ok()
  76. }
  77. }
  78. struct PostFixture {
  79. pub text: Vec<u8>,
  80. pub origin: RawOrigin<u64>,
  81. pub thread_id: u64,
  82. pub post_id: Option<u64>,
  83. pub author_id: u64,
  84. }
  85. impl PostFixture {
  86. fn default_for_thread(thread_id: u64) -> Self {
  87. PostFixture {
  88. text: b"text".to_vec(),
  89. author_id: 1,
  90. thread_id,
  91. origin: RawOrigin::Signed(1),
  92. post_id: None,
  93. }
  94. }
  95. fn with_text(self, text: Vec<u8>) -> Self {
  96. PostFixture { text, ..self }
  97. }
  98. fn with_origin(self, origin: RawOrigin<u64>) -> Self {
  99. PostFixture { origin, ..self }
  100. }
  101. fn with_author(self, author_id: u64) -> Self {
  102. PostFixture { author_id, ..self }
  103. }
  104. fn change_thread_id(self, thread_id: u64) -> Self {
  105. PostFixture { thread_id, ..self }
  106. }
  107. fn change_post_id(self, post_id: u64) -> Self {
  108. PostFixture {
  109. post_id: Some(post_id),
  110. ..self
  111. }
  112. }
  113. fn add_post_and_assert(&mut self, result: DispatchResult) -> Option<u64> {
  114. let add_post_result = Discussions::add_post(
  115. self.origin.clone().into(),
  116. self.author_id,
  117. self.thread_id,
  118. self.text.clone(),
  119. );
  120. assert_eq!(add_post_result, result);
  121. if result.is_ok() {
  122. self.post_id = Some(<PostCount>::get());
  123. }
  124. self.post_id
  125. }
  126. fn update_post_with_text_and_assert(&mut self, new_text: Vec<u8>, result: DispatchResult) {
  127. let add_post_result = Discussions::update_post(
  128. self.origin.clone().into(),
  129. self.author_id,
  130. self.thread_id,
  131. self.post_id.unwrap(),
  132. new_text,
  133. );
  134. assert_eq!(add_post_result, result);
  135. }
  136. fn update_post_and_assert(&mut self, result: DispatchResult) {
  137. self.update_post_with_text_and_assert(self.text.clone(), result);
  138. }
  139. }
  140. #[test]
  141. fn create_discussion_call_succeeds() {
  142. initial_test_ext().execute_with(|| {
  143. let discussion_fixture = DiscussionFixture::default();
  144. discussion_fixture.create_discussion_and_assert(Ok(1));
  145. });
  146. }
  147. #[test]
  148. fn create_post_call_succeeds() {
  149. initial_test_ext().execute_with(|| {
  150. let discussion_fixture = DiscussionFixture::default();
  151. let thread_id = discussion_fixture
  152. .create_discussion_and_assert(Ok(1))
  153. .unwrap();
  154. let mut post_fixture = PostFixture::default_for_thread(thread_id);
  155. post_fixture.add_post_and_assert(Ok(()));
  156. });
  157. }
  158. #[test]
  159. fn update_post_call_succeeds() {
  160. initial_test_ext().execute_with(|| {
  161. /*
  162. Events are not emitted on block 0.
  163. So any dispatchable calls made during genesis block formation will have no events emitted.
  164. https://substrate.dev/recipes/2-appetizers/4-events.html
  165. */
  166. run_to_block(1);
  167. let discussion_fixture = DiscussionFixture::default();
  168. let thread_id = discussion_fixture
  169. .create_discussion_and_assert(Ok(1))
  170. .unwrap();
  171. let mut post_fixture = PostFixture::default_for_thread(thread_id);
  172. post_fixture.add_post_and_assert(Ok(()));
  173. post_fixture.update_post_and_assert(Ok(()));
  174. EventFixture::assert_events(vec![
  175. RawEvent::ThreadCreated(1, 1),
  176. RawEvent::PostCreated(1, 1),
  177. RawEvent::PostUpdated(1, 1),
  178. ]);
  179. });
  180. }
  181. #[test]
  182. fn update_post_call_fails_because_of_post_edition_limit() {
  183. initial_test_ext().execute_with(|| {
  184. let discussion_fixture = DiscussionFixture::default();
  185. let thread_id = discussion_fixture
  186. .create_discussion_and_assert(Ok(1))
  187. .unwrap();
  188. let mut post_fixture = PostFixture::default_for_thread(thread_id);
  189. post_fixture.add_post_and_assert(Ok(()));
  190. for _ in 1..6 {
  191. post_fixture.update_post_and_assert(Ok(()));
  192. }
  193. post_fixture.update_post_and_assert(Err(Error::<Test>::PostEditionNumberExceeded.into()));
  194. });
  195. }
  196. #[test]
  197. fn update_post_call_fails_because_of_the_wrong_author() {
  198. initial_test_ext().execute_with(|| {
  199. let discussion_fixture = DiscussionFixture::default();
  200. let thread_id = discussion_fixture
  201. .create_discussion_and_assert(Ok(1))
  202. .unwrap();
  203. let mut post_fixture = PostFixture::default_for_thread(thread_id);
  204. post_fixture.add_post_and_assert(Ok(()));
  205. post_fixture = post_fixture.with_author(2);
  206. post_fixture.update_post_and_assert(Err(DispatchError::Other("Invalid author")));
  207. post_fixture = post_fixture.with_origin(RawOrigin::None).with_author(2);
  208. post_fixture.update_post_and_assert(Err(Error::<Test>::NotAuthor.into()));
  209. });
  210. }
  211. #[test]
  212. fn thread_content_check_succeeded() {
  213. initial_test_ext().execute_with(|| {
  214. let discussion_fixture = DiscussionFixture::default();
  215. let thread_id = discussion_fixture
  216. .create_discussion_and_assert(Ok(1))
  217. .unwrap();
  218. let mut post_fixture1 = PostFixture::default_for_thread(thread_id);
  219. let post_id1 = post_fixture1.add_post_and_assert(Ok(())).unwrap();
  220. let mut post_fixture2 = PostFixture::default_for_thread(thread_id);
  221. let post_id2 = post_fixture2.add_post_and_assert(Ok(())).unwrap();
  222. post_fixture1.update_post_with_text_and_assert(b"new_text".to_vec(), Ok(()));
  223. assert_thread_content(
  224. TestThreadEntry {
  225. thread_id,
  226. title: b"title".to_vec(),
  227. },
  228. vec![
  229. TestPostEntry {
  230. post_id: post_id1,
  231. text: b"new_text".to_vec(),
  232. edition_number: 1,
  233. },
  234. TestPostEntry {
  235. post_id: post_id2,
  236. text: b"text".to_vec(),
  237. edition_number: 0,
  238. },
  239. ],
  240. );
  241. });
  242. }
  243. #[test]
  244. fn create_discussion_call_with_bad_title_failed() {
  245. initial_test_ext().execute_with(|| {
  246. let mut discussion_fixture = DiscussionFixture::default().with_title(Vec::new());
  247. discussion_fixture
  248. .create_discussion_and_assert(Err(Error::<Test>::EmptyTitleProvided.into()));
  249. discussion_fixture = DiscussionFixture::default().with_title([0; 201].to_vec());
  250. discussion_fixture.create_discussion_and_assert(Err(Error::<Test>::TitleIsTooLong.into()));
  251. });
  252. }
  253. #[test]
  254. fn add_post_call_with_invalid_thread_failed() {
  255. initial_test_ext().execute_with(|| {
  256. let discussion_fixture = DiscussionFixture::default();
  257. discussion_fixture
  258. .create_discussion_and_assert(Ok(1))
  259. .unwrap();
  260. let mut post_fixture = PostFixture::default_for_thread(2);
  261. post_fixture.add_post_and_assert(Err(Error::<Test>::ThreadDoesntExist.into()));
  262. });
  263. }
  264. #[test]
  265. fn update_post_call_with_invalid_post_failed() {
  266. initial_test_ext().execute_with(|| {
  267. let discussion_fixture = DiscussionFixture::default();
  268. let thread_id = discussion_fixture
  269. .create_discussion_and_assert(Ok(1))
  270. .unwrap();
  271. let mut post_fixture1 = PostFixture::default_for_thread(thread_id);
  272. post_fixture1.add_post_and_assert(Ok(())).unwrap();
  273. let mut post_fixture2 = post_fixture1.change_post_id(2);
  274. post_fixture2.update_post_and_assert(Err(Error::<Test>::PostDoesntExist.into()));
  275. });
  276. }
  277. #[test]
  278. fn update_post_call_with_invalid_thread_failed() {
  279. initial_test_ext().execute_with(|| {
  280. let discussion_fixture = DiscussionFixture::default();
  281. let thread_id = discussion_fixture
  282. .create_discussion_and_assert(Ok(1))
  283. .unwrap();
  284. let mut post_fixture1 = PostFixture::default_for_thread(thread_id);
  285. post_fixture1.add_post_and_assert(Ok(())).unwrap();
  286. let mut post_fixture2 = post_fixture1.change_thread_id(2);
  287. post_fixture2.update_post_and_assert(Err(Error::<Test>::ThreadDoesntExist.into()));
  288. });
  289. }
  290. #[test]
  291. fn add_post_call_with_invalid_text_failed() {
  292. initial_test_ext().execute_with(|| {
  293. let discussion_fixture = DiscussionFixture::default();
  294. let thread_id = discussion_fixture
  295. .create_discussion_and_assert(Ok(1))
  296. .unwrap();
  297. let mut post_fixture1 = PostFixture::default_for_thread(thread_id).with_text(Vec::new());
  298. post_fixture1.add_post_and_assert(Err(Error::<Test>::EmptyPostProvided.into()));
  299. let mut post_fixture2 =
  300. PostFixture::default_for_thread(thread_id).with_text([0; 2001].to_vec());
  301. post_fixture2.add_post_and_assert(Err(Error::<Test>::PostIsTooLong.into()));
  302. });
  303. }
  304. #[test]
  305. fn update_post_call_with_invalid_text_failed() {
  306. initial_test_ext().execute_with(|| {
  307. let discussion_fixture = DiscussionFixture::default();
  308. let thread_id = discussion_fixture
  309. .create_discussion_and_assert(Ok(1))
  310. .unwrap();
  311. let mut post_fixture1 = PostFixture::default_for_thread(thread_id);
  312. post_fixture1.add_post_and_assert(Ok(()));
  313. let mut post_fixture2 = post_fixture1.with_text(Vec::new());
  314. post_fixture2.update_post_and_assert(Err(Error::<Test>::EmptyPostProvided.into()));
  315. let mut post_fixture3 = post_fixture2.with_text([0; 2001].to_vec());
  316. post_fixture3.update_post_and_assert(Err(Error::<Test>::PostIsTooLong.into()));
  317. });
  318. }
  319. #[test]
  320. fn add_discussion_thread_fails_because_of_max_thread_by_same_author_in_a_row_limit_exceeded() {
  321. initial_test_ext().execute_with(|| {
  322. let discussion_fixture = DiscussionFixture::default();
  323. for idx in 1..=3 {
  324. discussion_fixture
  325. .create_discussion_and_assert(Ok(idx))
  326. .unwrap();
  327. }
  328. discussion_fixture
  329. .create_discussion_and_assert(Err(Error::<Test>::MaxThreadInARowLimitExceeded.into()));
  330. });
  331. }
  332. #[test]
  333. fn discussion_thread_and_post_counters_are_valid() {
  334. initial_test_ext().execute_with(|| {
  335. let discussion_fixture = DiscussionFixture::default();
  336. let thread_id = discussion_fixture
  337. .create_discussion_and_assert(Ok(1))
  338. .unwrap();
  339. let mut post_fixture1 = PostFixture::default_for_thread(thread_id);
  340. let _ = post_fixture1.add_post_and_assert(Ok(())).unwrap();
  341. assert_eq!(Discussions::thread_count(), 1);
  342. assert_eq!(Discussions::post_count(), 1);
  343. });
  344. }