ÿØÿà JFIF    ÿÛ „  ( %!1"%)+...383-7(-.+  -%%--------+------------/----------------------------ÿÀ  ¶" ÿÄ     ÿÄ @    !1AQaq‘¡"±ÁÑð2’BRráS‚ñ3b¢CT²Ò #cÿÄ    ÿÄ '   1!AQ2a"‘±#ÿÚ   ? ò(/p‘—–¢·,M|¯G»R£üešT. ôŸEoCFñ‡ 4ÔkåóxR|µÁ¨ÓDà!Ç´µ˜‡p ÍQ‹§!þž^õWsŒy  åÁ§$á«%ºq˜™JyyË0y«^Ϗ -3ÃÏËÈ£Æ\P’ä„s&•‹Üs üô'àWt¹pñCp$bÚF`·7.^)¾!)ÒÙ˜–ÅÏš^h¾ø³·ìe±ŒaeÖ—Í,e=B}Jéߦ$$½—šÙÜCC¯¶è8β–Åkèwx LÉngT±æ¹£Ócœ¿Í/©Éÿ ‰ÚCÒ­mq;Tt¶–}0"zÖPcd1š­¤tˆ„ÐÓâqú[™öUCŠ2޿د­É(¸ßúçšmW,Ò ºf‹Ån™ÄLêÁ8üÁkÁ€L¥ÍuJ1œôrÂSŒÓöt!7EUíïb)AËi¬â-ÙHk8òÁiXYwÅ)O^"ÇØriÛô{»wRUKÙÏÚlÓ¬€‚’שT ] ªb—ÊîKC^×M“xùäñz¬zJ× {4[¹M'IÆeH1LCWÕ]œûº WDæà°OÅ‚¹c²ÞuA–rL`HšpCez‡V’— Ž‚Ë„–ñ­'µR0ÕÖ=À:±G»C\nÆÉ5*¢ †3šhQ4ýÒi$Õ1"Ü]¢p-q3zcQ$ªö¨¥æf›‘»´ÝÚHá„e²JÇ–iÊ:·à¬Øc5Åk»Laª*wi [0Ówk ¦a¦0Õ²ÅÅ‚T¸’³q:Æ<¹ú Ò˜Æ}2VE•Ѐp¡Ô3Úº …h³^LJºçì¥Ánó|˜ÖˆÎˆ+« ¦dø‘Mº–d\ªDðÛ„Ì×LÝ——Ð9Ç|•FèçVm™¨€™3õS–9Äå¢<̉Ìm¢±`·º˜&Z²ž Ë®D«¶»6™‰©­HÈ Àá«h­i³04æ’hxœi3ï¨(ÓL·†‹¶ËpŒ ƒÜÒ? :õdsÀ –3æÓ·Üjâ¬9—]8n¼&]I F£¼…ì–—Œ0v°LǐÅfÛ2TÖ’aµ˜N@ÖBSÏ^à´4-±°b×nÈÈœÆD=KdŒø…ÄÈK,›ÖR§fjü$^ÔÎ!„6^ s:Üun\ôkCÞò÷<—Lúl Í›G¹ÀPá¼Nc¡šÒ…ÙWͳœ±vÊáЄ×9‹P‰cEØ‘™›µŸP<×g1 º©Ø¬wiÁ^l5× I/';Ë+´Z‡ÓÄFÕp[Èð’ HÄ ó 9¬Dl4“é±Ï”V=^Hð;žLädÉa‚°Ë,#/Ž~²BÔÛ QãñQtIeórVB,ÏÂf‹4#Μc‹É3ZIÖ Œ¢¥·üxa‡Ãçè„jŠ[5 Ä!U?,3žÎ×…ðdÓ÷háŠA‰ìÄ4÷ÃÜZÃE{‰®+W\ZÍE[‰»µk»MÝ­`¢©†˜ÃV‹-f¢¡†¢a«eŠ%«Y¨©q%jâd,Ôq`) ¤¦…šˆ ¦E>0Ùb²t–Ÿ  ÷jsςҒKÈcø(é›;k(’#ôŒQ ™C> õ³M9óºÆµ¤çâ;uÀ)5>RÔ¸fÓ~Ȧ—’-$fe«"FâUˆÊwµPÏu w+"åÉó¨6s™Û9(À¨ù©hE°½†n`m~“@©›p!Vkhµ Á1šbEqqÊ@ºèl›2 –Yã%‘‚hÛ{a¶‡ÅÞM²¬çL34®Çl]®‹ŽØŒm'RÜe35æöˆ­Á­ cP9šeÁköo´L³Ì=p8]”‡B¶,šº|ɏdzXˆØk*ÇÚ{#ñ‰pã(€¶‡9ý'šÜ³½¯kƒ†Â’ëSOƒ•Å®H6+a¢µˆ‹Y¨b˜b+XˆÖ!°u!¢"µˆˆn2ˆ Å0Ä`Å Ô®c(ÓÜGº•Ä»Œ âWî%q.áÐÄ÷î%umÍ£+ÜQ,Vn¦-GphU¸¢X­&,Gt ·K¢ÅÅ·¥RÄÊÉbKn N­R2g$VµÕó Qr#–ÓZUÏð0ÎtÙÀœz ëöo“Îk¯n„T°"SÞ+³/…€½"ižãO%Í%&Δâ‘ËØ´IˆÓ°Ë‰ÞM3.™yçÁzit›`4C`k¢¼á!álÄï sÂ[×!¤l¾æo"ƒIÔ'@ªu)3›ô®“@Z à¾] üœ'‡4¸PÞkÁ-qÄH‰ÓÍBd´ÿ —¹°ÄA&ÊUC„öz‚‚RæŠtf¬>˜ÑidšEçÑL÷áý¡V±h§­c¿¥¨ƒ@g¼…Ýö^ÞËc.ž„æ“I[ ª+·zÐÒÚ,½Ð»¶€C›7jk$๖€Ž¾ÅÙpp¶ÍþéΤ ½ØÎŒq ñpæžÅ¥#Øâ49ÃÂK^MYV“,¶Éz_òæ:…;Õ«¦I3âJ£¤û5$7Èxün\â]Êe¯(Ÿ&Ž‹¶2<6Ćf× æ¢ƱqWı[(ÙÂŽç‚T7¨@ÈÉÍß‚ïÙ"&3TYlGށ†)†#5ŠA©^T2ÆÁ†)©†©†¤yGXÁ†©"†)©¼ÅV WS†#©]R–r‹q5ÕbâWþBh¯u+ªÅÄ×ü„nÑ^êkªÁj‰jeœWˆÕÕ`µDµ2Ì+Ä µDµµ5Ô{¤Þ0RF’H÷AÛ<ÊÁ§ ƈæ0Ñ£ê4 öÚµ[¬/1²€â<-¬šö²r×0o×w ´xÐÒË„ g01¬„åEM蛇Ã\6µE›‘fÙO k÷CºnÓ1`iîÒ² áÂ!ÑLÄÿ ³¬\õ*:M=Ç»c‹2[‰ØsÕLò\ŒK L^™üÄN\¶#» Ç\……ßïIœÉ$¸ÎñÍ®ÔHÇ Ûí/´>Li”„†g^$ 7NÍÑÑ2M2ªNg=k½Ð}Ÿl6‡ —JWˆ”ÞéT·+µ¦üÂFè¢Vs6ýÆ4»2+2IÝ9ô9®ÇGÙÇ1+Ä8;*Õ§ž½D*îÑWÚçá3Ma¤·ˆ¥t}š²Ü…Q+Æüµ8HíS”üxGéÁˆ¦Ël„éÈ 5 µÄƒ6äÜ(kIš¯S†fÖ&¸®×v|Äyˆ*禌Éù@7-®Ä[]ÎÖ¿ê`º…=:…žJVnݳvðÖKE¼‚ »mà ÷f®:Ë ™–4rð¡4` ܹçž~‹C=œ…¾Ïj|VDtK.ƒ"9 Ï ­–éXá³6WÒïIL-Ї Ç home/smkb8269/public_html/lms.smk-almanshuriyah.sch.id/message/tests/api_test.php000064400001056710152266371250024013 0ustar00. namespace core_message; use core_message\tests\helper as testhelper; /** * Test message API. * * @package core_message * @category test * @copyright 2016 Mark Nelson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @covers \core_message\api */ final class api_test extends \advanced_testcase { /** * Test set up. * * This is executed before running any test in this file. */ public function setUp(): void { parent::setUp(); $this->resetAfterTest(); } public function test_mark_all_read_for_user_touser(): void { $this->resetAfterTest(); $sender = $this->getDataGenerator()->create_user(['firstname' => 'Test1', 'lastname' => 'User1']); $recipient = $this->getDataGenerator()->create_user(['firstname' => 'Test2', 'lastname' => 'User2']); testhelper::send_fake_message($sender, $recipient, 'Notification', 1); testhelper::send_fake_message($sender, $recipient, 'Notification', 1); testhelper::send_fake_message($sender, $recipient, 'Notification', 1); testhelper::send_fake_message($sender, $recipient); testhelper::send_fake_message($sender, $recipient); testhelper::send_fake_message($sender, $recipient); $this->assertEquals(1, api::count_unread_conversations($recipient)); api::mark_all_notifications_as_read($recipient->id); api::mark_all_messages_as_read($recipient->id); // We've marked all of recipients conversation messages as read. $this->assertEquals(0, api::count_unread_conversations($recipient)); } public function test_mark_all_read_for_user_touser_with_fromuser(): void { $this->resetAfterTest(); $sender1 = $this->getDataGenerator()->create_user(['firstname' => 'Test1', 'lastname' => 'User1']); $sender2 = $this->getDataGenerator()->create_user(['firstname' => 'Test3', 'lastname' => 'User3']); $recipient = $this->getDataGenerator()->create_user(['firstname' => 'Test2', 'lastname' => 'User2']); testhelper::send_fake_message($sender1, $recipient, 'Notification', 1); testhelper::send_fake_message($sender1, $recipient, 'Notification', 1); testhelper::send_fake_message($sender1, $recipient, 'Notification', 1); testhelper::send_fake_message($sender1, $recipient); testhelper::send_fake_message($sender1, $recipient); testhelper::send_fake_message($sender1, $recipient); testhelper::send_fake_message($sender2, $recipient, 'Notification', 1); testhelper::send_fake_message($sender2, $recipient, 'Notification', 1); testhelper::send_fake_message($sender2, $recipient, 'Notification', 1); testhelper::send_fake_message($sender2, $recipient); testhelper::send_fake_message($sender2, $recipient); testhelper::send_fake_message($sender2, $recipient); $this->assertEquals(2, api::count_unread_conversations($recipient)); api::mark_all_notifications_as_read($recipient->id, $sender1->id); $conversationid = api::get_conversation_between_users([$recipient->id, $sender1->id]); api::mark_all_messages_as_read($recipient->id, $conversationid); // We've marked only the conversation with sender1 messages as read. $this->assertEquals(1, api::count_unread_conversations($recipient)); } /** * Test count_blocked_users. */ public function test_count_blocked_users(): void { global $USER; $this->resetAfterTest(); // Set this user as the admin. $this->setAdminUser(); // Create user to add to the admin's block list. $user1 = $this->getDataGenerator()->create_user(); $user2 = $this->getDataGenerator()->create_user(); $this->assertEquals(0, api::count_blocked_users()); // Add 1 blocked user to admin's blocked user list. api::block_user($USER->id, $user1->id); $this->assertEquals(0, api::count_blocked_users($user1)); $this->assertEquals(1, api::count_blocked_users()); } /** * Tests searching for users when site-wide messaging is disabled. * * This test verifies that any contacts are returned, as well as any non-contacts whose profile we can view. * If checks this by placing some users in the same course, where default caps would permit a user to view another user's * profile. */ public function test_message_search_users_messagingallusers_disabled(): void { global $DB; $this->resetAfterTest(); // Create some users. $users = []; foreach (range(1, 8) as $i) { $user = new \stdClass(); $user->firstname = ($i == 4) ? 'User' : 'User search'; // Ensure the fourth user won't match the search term. $user->lastname = $i; $user = $this->getDataGenerator()->create_user($user); $users[$i] = $user; } // Enrol a few users in the same course, but leave them as non-contacts. $course1 = $this->getDataGenerator()->create_course(); $course2 = $this->getDataGenerator()->create_course(); $this->setAdminUser(); $this->getDataGenerator()->enrol_user($users[1]->id, $course1->id); $this->getDataGenerator()->enrol_user($users[6]->id, $course1->id); $this->getDataGenerator()->enrol_user($users[7]->id, $course1->id); // Add some other users as contacts. api::add_contact($users[1]->id, $users[2]->id); api::add_contact($users[3]->id, $users[1]->id); api::add_contact($users[1]->id, $users[4]->id); // Enrol a user as a teacher in the course, and make the teacher role a course contact role. $this->getDataGenerator()->enrol_user($users[8]->id, $course2->id, 'editingteacher'); $teacherrole = $DB->get_record('role', ['shortname' => 'editingteacher']); set_config('coursecontact', $teacherrole->id); // Create individual conversations between some users, one contact and one non-contact. $ic1 = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, [$users[1]->id, $users[2]->id] ); $ic2 = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, [$users[6]->id, $users[1]->id] ); // Create a group conversation between 4 users, including a contact and a non-contact. $gc1 = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_GROUP, [$users[1]->id, $users[2]->id, $users[4]->id, $users[7]->id], 'Project chat' ); // Set as the user performing the search. $this->setUser($users[1]); // Perform a search with $CFG->messagingallusers disabled. set_config('messagingallusers', 0); $result = api::message_search_users($users[1]->id, 'search'); // Confirm that we returns contacts and non-contacts. $this->assertArrayHasKey(0, $result); $this->assertArrayHasKey(1, $result); $contacts = $result[0]; $noncontacts = $result[1]; // Check that we retrieved the correct contacts. $this->assertCount(2, $contacts); $this->assertEquals($users[2]->id, $contacts[0]->id); $this->assertEquals($users[3]->id, $contacts[1]->id); // Verify the correct conversations were returned for the contacts. $this->assertCount(2, $contacts[0]->conversations); $this->assertEquals(api::MESSAGE_CONVERSATION_TYPE_GROUP, $contacts[0]->conversations[$gc1->id]->type); $this->assertEquals(api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, $contacts[0]->conversations[$ic1->id]->type); $this->assertCount(0, $contacts[1]->conversations); // Check that we retrieved the correct non-contacts. // When site wide messaging is disabled, we expect to see only those users who we share a course with and whose profiles // are visible in that course. This excludes users like course contacts. $this->assertCount(3, $noncontacts); // Self-conversation first. $this->assertEquals($users[1]->id, $noncontacts[0]->id); $this->assertEquals($users[6]->id, $noncontacts[1]->id); $this->assertEquals($users[7]->id, $noncontacts[2]->id); // Verify the correct conversations were returned for the non-contacts. $this->assertCount(1, $noncontacts[1]->conversations); $this->assertEquals( api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, $noncontacts[1]->conversations[$ic2->id]->type ); $this->assertCount(1, $noncontacts[2]->conversations); $this->assertEquals(api::MESSAGE_CONVERSATION_TYPE_GROUP, $noncontacts[2]->conversations[$gc1->id]->type); } /** * Tests searching for users when site-wide messaging is enabled. * * This test verifies that any contacts are returned, as well as any non-contacts, * provided the searching user can view their profile. */ public function test_message_search_users_messagingallusers_enabled(): void { global $DB; $this->resetAfterTest(); // Create some users. $users = []; foreach (range(1, 9) as $i) { $user = new \stdClass(); $user->firstname = ($i == 4) ? 'User' : 'User search'; // Ensure the fourth user won't match the search term. $user->lastname = $i; $user = $this->getDataGenerator()->create_user($user); $users[$i] = $user; } $course1 = $this->getDataGenerator()->create_course(); $coursecontext = \context_course::instance($course1->id); // Enrol a few users in the same course, but leave them as non-contacts. $this->setAdminUser(); $this->getDataGenerator()->enrol_user($users[1]->id, $course1->id, 'student'); $this->getDataGenerator()->enrol_user($users[6]->id, $course1->id, 'student'); $this->getDataGenerator()->enrol_user($users[7]->id, $course1->id, 'student'); // Add some other users as contacts. api::add_contact($users[1]->id, $users[2]->id); api::add_contact($users[3]->id, $users[1]->id); api::add_contact($users[1]->id, $users[4]->id); // Enrol a user as a teacher in the course, and make the teacher role a course contact role. $this->getDataGenerator()->enrol_user($users[9]->id, $course1->id, 'editingteacher'); $teacherrole = $DB->get_record('role', ['shortname' => 'editingteacher']); set_config('coursecontact', $teacherrole->id); // Get self-conversation. $selfconversation = api::get_self_conversation($users[1]->id); // Create individual conversations between some users, one contact and one non-contact. $ic1 = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, [$users[1]->id, $users[2]->id] ); $ic2 = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, [$users[6]->id, $users[1]->id] ); // Create a group conversation between 5 users, including a contact and a non-contact, and a user NOT in a shared course. $gc1 = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_GROUP, [$users[1]->id, $users[2]->id, $users[4]->id, $users[7]->id, $users[8]->id], 'Project chat' ); // Set as the user performing the search. $this->setUser($users[1]); // Perform a search with $CFG->messagingallusers enabled. set_config('messagingallusers', 1); $result = api::message_search_users($users[1]->id, 'search'); // Confirm that we returns contacts and non-contacts. $this->assertArrayHasKey(0, $result); $this->assertArrayHasKey(1, $result); $contacts = $result[0]; $noncontacts = $result[1]; // Check that we retrieved the correct contacts. $this->assertCount(2, $contacts); $this->assertEquals($users[2]->id, $contacts[0]->id); $this->assertEquals($users[3]->id, $contacts[1]->id); // Verify the correct conversations were returned for the contacts. $this->assertCount(2, $contacts[0]->conversations); $this->assertEquals(api::MESSAGE_CONVERSATION_TYPE_GROUP, $contacts[0]->conversations[$gc1->id]->type); $this->assertEquals(api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, $contacts[0]->conversations[$ic1->id]->type); $this->assertCount(0, $contacts[1]->conversations); // Check that we retrieved the correct non-contacts. // If site wide messaging is enabled, we expect to only be able to search for users whose profiles we can view. // In this case, as a student, that's the course contact for course2 and those noncontacts sharing a course with user1. // Consider first conversations is self-conversation. $this->assertCount(4, $noncontacts); $this->assertEquals($users[1]->id, $noncontacts[0]->id); $this->assertEquals($users[6]->id, $noncontacts[1]->id); $this->assertEquals($users[7]->id, $noncontacts[2]->id); $this->assertEquals($users[9]->id, $noncontacts[3]->id); $this->assertCount(1, $noncontacts[1]->conversations); $this->assertCount(1, $noncontacts[2]->conversations); $this->assertCount(0, $noncontacts[3]->conversations); // Verify the correct conversations were returned for the non-contacts. $this->assertEquals( api::MESSAGE_CONVERSATION_TYPE_SELF, $noncontacts[0]->conversations[$selfconversation->id]->type ); $this->assertCount(1, $noncontacts[1]->conversations); $this->assertEquals( api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, $noncontacts[1]->conversations[$ic2->id]->type ); $this->assertCount(1, $noncontacts[2]->conversations); $this->assertEquals(api::MESSAGE_CONVERSATION_TYPE_GROUP, $noncontacts[2]->conversations[$gc1->id]->type); $this->assertCount(0, $noncontacts[3]->conversations); } /** * Verify searching for users find themselves when they have self-conversations. */ public function test_message_search_users_self_conversations(): void { $this->resetAfterTest(); // Create some users. $user1 = new \stdClass(); $user1->firstname = 'User'; $user1->lastname = 'One'; $user1 = $this->getDataGenerator()->create_user($user1); $user2 = new \stdClass(); $user2->firstname = 'User'; $user2->lastname = 'Two'; $user2 = $this->getDataGenerator()->create_user($user2); // Get self-conversation for user1. $sc1 = api::get_self_conversation($user1->id); testhelper::send_fake_message_to_conversation($user1, $sc1->id, 'Hi myself!'); // Perform a search as user1. $this->setUser($user1); $result = api::message_search_users($user1->id, 'One'); // Check user1 is found as non-contacts. $this->assertCount(0, $result[0]); $this->assertCount(1, $result[1]); } /** * Verify searching for users works even if no matching users from either contacts, or non-contacts can be found. */ public function test_message_search_users_with_empty_result(): void { $this->resetAfterTest(); // Create some users, but make sure neither will match the search term. $user1 = new \stdClass(); $user1->firstname = 'User'; $user1->lastname = 'One'; $user1 = $this->getDataGenerator()->create_user($user1); $user2 = new \stdClass(); $user2->firstname = 'User'; $user2->lastname = 'Two'; $user2 = $this->getDataGenerator()->create_user($user2); // Perform a search as user1. $this->setUser($user1); $result = api::message_search_users($user1->id, 'search'); // Check results are empty. $this->assertCount(0, $result[0]); $this->assertCount(0, $result[1]); } /** * Test verifying that limits and offsets work for both the contacts and non-contacts return data. */ public function test_message_search_users_limit_offset(): void { $this->resetAfterTest(); // Create 20 users. $users = []; foreach (range(1, 20) as $i) { $user = new \stdClass(); $user->firstname = "User search"; $user->lastname = $i; $user = $this->getDataGenerator()->create_user($user); $users[$i] = $user; } // Enrol the first 9 users in the same course, but leave them as non-contacts. $this->setAdminUser(); $course1 = $this->getDataGenerator()->create_course(); foreach (range(1, 8) as $i) { $this->getDataGenerator()->enrol_user($users[$i]->id, $course1->id); } // Add 5 users, starting at the 11th user, as contacts for user1. foreach (range(11, 15) as $i) { api::add_contact($users[1]->id, $users[$i]->id); } // Set as the user performing the search. $this->setUser($users[1]); // Search using a limit of 3. // This tests the case where we have more results than the limit for both contacts and non-contacts. $result = api::message_search_users($users[1]->id, 'search', 0, 3); $contacts = $result[0]; $noncontacts = $result[1]; // Check that we retrieved the correct contacts. $this->assertCount(3, $contacts); $this->assertEquals($users[11]->id, $contacts[0]->id); $this->assertEquals($users[12]->id, $contacts[1]->id); $this->assertEquals($users[13]->id, $contacts[2]->id); // Check that we retrieved the correct non-contacts. // Consider first conversations is self-conversation. $this->assertCount(3, $noncontacts); $this->assertEquals($users[1]->id, $noncontacts[0]->id); $this->assertEquals($users[2]->id, $noncontacts[1]->id); $this->assertEquals($users[3]->id, $noncontacts[2]->id); // Now, offset to get the next batch of results. // We expect to see 2 contacts, and 3 non-contacts. $result = api::message_search_users($users[1]->id, 'search', 3, 3); $contacts = $result[0]; $noncontacts = $result[1]; $this->assertCount(2, $contacts); $this->assertEquals($users[14]->id, $contacts[0]->id); $this->assertEquals($users[15]->id, $contacts[1]->id); $this->assertCount(3, $noncontacts); $this->assertEquals($users[4]->id, $noncontacts[0]->id); $this->assertEquals($users[5]->id, $noncontacts[1]->id); $this->assertEquals($users[6]->id, $noncontacts[2]->id); // Now, offset to get the next batch of results. // We expect to see 0 contacts, and 2 non-contacts. $result = api::message_search_users($users[1]->id, 'search', 6, 3); $contacts = $result[0]; $noncontacts = $result[1]; $this->assertCount(0, $contacts); $this->assertCount(2, $noncontacts); $this->assertEquals($users[7]->id, $noncontacts[0]->id); $this->assertEquals($users[8]->id, $noncontacts[1]->id); } /** * Tests searching users as a user having the 'moodle/user:viewdetails' capability. */ public function test_message_search_users_with_cap(): void { $this->resetAfterTest(); global $DB; // Create some users. $users = []; foreach (range(1, 8) as $i) { $user = new \stdClass(); $user->firstname = ($i == 4) ? 'User' : 'User search'; // Ensure the fourth user won't match the search term. $user->lastname = $i; $user = $this->getDataGenerator()->create_user($user); $users[$i] = $user; } // Enrol a few users in the same course, but leave them as non-contacts. $course1 = $this->getDataGenerator()->create_course(); $this->setAdminUser(); $this->getDataGenerator()->enrol_user($users[1]->id, $course1->id); $this->getDataGenerator()->enrol_user($users[6]->id, $course1->id); $this->getDataGenerator()->enrol_user($users[7]->id, $course1->id); // Add some other users as contacts. api::add_contact($users[1]->id, $users[2]->id); api::add_contact($users[3]->id, $users[1]->id); api::add_contact($users[1]->id, $users[4]->id); // Set as the user performing the search. $this->setUser($users[1]); // Grant the authenticated user role the capability 'user:viewdetails' at site context. $authenticatedrole = $DB->get_record('role', ['shortname' => 'user'], '*', MUST_EXIST); assign_capability('moodle/user:viewdetails', CAP_ALLOW, $authenticatedrole->id, \context_system::instance()); // Perform a search with $CFG->messagingallusers disabled. set_config('messagingallusers', 0); $result = api::message_search_users($users[1]->id, 'search'); $contacts = $result[0]; $noncontacts = $result[1]; // Check that we retrieved the correct contacts. $this->assertCount(2, $contacts); $this->assertEquals($users[2]->id, $contacts[0]->id); $this->assertEquals($users[3]->id, $contacts[1]->id); // Check that we retrieved the correct non-contacts. // Site-wide messaging is disabled, so we expect to be able to search for any users whose profiles we can view. // Consider first conversations is self-conversation. $this->assertCount(3, $noncontacts); $this->assertEquals($users[1]->id, $noncontacts[0]->id); $this->assertEquals($users[6]->id, $noncontacts[1]->id); $this->assertEquals($users[7]->id, $noncontacts[2]->id); } /** * Tests searching users with messaging disabled. */ public function test_message_search_users_messaging_disabled(): void { $this->resetAfterTest(); // Create a user. $user = $this->getDataGenerator()->create_user(); // Disable messaging. set_config('messaging', 0); // Ensure an exception is thrown. $this->expectException('moodle_exception'); api::message_search_users($user->id, 'User'); } /** * Tests getting conversations between 2 users. */ public function test_get_conversations_between_users(): void { $this->resetAfterTest(); // Create some users. $user1 = new \stdClass(); $user1->firstname = 'User'; $user1->lastname = 'One'; $user1 = self::getDataGenerator()->create_user($user1); $user2 = new \stdClass(); $user2->firstname = 'User'; $user2->lastname = 'Two'; $user2 = self::getDataGenerator()->create_user($user2); $user3 = new \stdClass(); $user3->firstname = 'User search'; $user3->lastname = 'Three'; $user3 = self::getDataGenerator()->create_user($user3); $user4 = new \stdClass(); $user4->firstname = 'User'; $user4->lastname = 'Four'; $user4 = self::getDataGenerator()->create_user($user4); $user5 = new \stdClass(); $user5->firstname = 'User'; $user5->lastname = 'Five'; $user5 = self::getDataGenerator()->create_user($user5); $user6 = new \stdClass(); $user6->firstname = 'User search'; $user6->lastname = 'Six'; $user6 = self::getDataGenerator()->create_user($user6); // Add some users as contacts. api::add_contact($user1->id, $user2->id); api::add_contact($user6->id, $user1->id); // Create private conversations with some users. api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, [$user1->id, $user2->id] ); api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, [$user3->id, $user1->id] ); // Create a group conversation with users. api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_GROUP, [$user1->id, $user2->id, $user3->id, $user4->id], 'Project chat' ); // Check that we retrieved the correct conversations. $this->assertCount(2, api::get_conversations_between_users($user1->id, $user2->id)); $this->assertCount(2, api::get_conversations_between_users($user2->id, $user1->id)); $this->assertCount(2, api::get_conversations_between_users($user1->id, $user3->id)); $this->assertCount(2, api::get_conversations_between_users($user3->id, $user1->id)); $this->assertCount(1, api::get_conversations_between_users($user1->id, $user4->id)); $this->assertCount(1, api::get_conversations_between_users($user4->id, $user1->id)); $this->assertCount(0, api::get_conversations_between_users($user1->id, $user5->id)); $this->assertCount(0, api::get_conversations_between_users($user5->id, $user1->id)); $this->assertCount(0, api::get_conversations_between_users($user1->id, $user6->id)); $this->assertCount(0, api::get_conversations_between_users($user6->id, $user1->id)); } /** * Tests getting self-conversations. */ public function test_get_self_conversation(): void { $this->resetAfterTest(); // Create some users. $user1 = new \stdClass(); $user1->firstname = 'User'; $user1->lastname = 'One'; $user1 = self::getDataGenerator()->create_user($user1); $user2 = new \stdClass(); $user2->firstname = 'User'; $user2->lastname = 'Two'; $user2 = self::getDataGenerator()->create_user($user2); $user3 = new \stdClass(); $user3->firstname = 'User search'; $user3->lastname = 'Three'; $user3 = self::getDataGenerator()->create_user($user3); // Add some users as contacts. api::add_contact($user1->id, $user2->id); api::add_contact($user3->id, $user1->id); // Create private conversations with some users. api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, [$user1->id, $user2->id] ); api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, [$user3->id, $user1->id] ); // Create a group conversation with users. $gc = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_GROUP, [$user1->id, $user2->id, $user3->id], 'Project chat' ); // Get self-conversations. $rsc1 = api::get_self_conversation($user1->id); $rsc2 = api::get_self_conversation($user2->id); $rsc3 = api::get_self_conversation($user3->id); // Send message to self-conversation. testhelper::send_fake_message_to_conversation($user1, $rsc1->id, 'Message to myself!'); // Check that we retrieved the correct conversations. $this->assertEquals(api::MESSAGE_CONVERSATION_TYPE_SELF, $rsc1->type); $members = api::get_conversation_members($user1->id, $rsc1->id); $this->assertCount(1, $members); $member = reset($members); $this->assertEquals($user1->id, $member->id); $this->assertEquals(api::MESSAGE_CONVERSATION_TYPE_SELF, $rsc2->type); $members = api::get_conversation_members($user2->id, $rsc2->id); $this->assertCount(1, $members); $member = reset($members); $this->assertEquals($user2->id, $member->id); api::delete_all_conversation_data($rsc3->id); $selfconversation = api::get_self_conversation($user3->id); $members = api::get_conversation_members($user1->id, $selfconversation->id); $this->assertCount(1, $members); } /** * Tests searching messages. */ public function test_search_messages(): void { $this->resetAfterTest(); // Create some users. $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); $user3 = self::getDataGenerator()->create_user(); // The person doing the search. $this->setUser($user1); // Get self-conversation. $sc = api::get_self_conversation($user1->id); // Create group conversation. $gc = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_GROUP, [$user1->id, $user2->id, $user3->id] ); // Send some messages back and forth. $time = 1; testhelper::send_fake_message_to_conversation($user1, $sc->id, 'Test message to self!', $time); testhelper::send_fake_message_to_conversation($user1, $gc->id, 'My hero!', $time + 1); testhelper::send_fake_message($user3, $user1, 'Don\'t block me.', 0, $time + 2); testhelper::send_fake_message($user1, $user2, 'Yo!', 0, $time + 3); testhelper::send_fake_message($user2, $user1, 'Sup mang?', 0, $time + 4); testhelper::send_fake_message($user1, $user2, 'Writing PHPUnit tests!', 0, $time + 5); testhelper::send_fake_message($user2, $user1, 'Word.', 0, $time + 6); $convid = api::get_conversation_between_users([$user1->id, $user2->id]); $conv2id = api::get_conversation_between_users([$user1->id, $user3->id]); // Block user 3. api::block_user($user1->id, $user3->id); // Perform a search. $messages = api::search_messages($user1->id, 'o'); // Confirm the data is correct. $this->assertEquals(5, count($messages)); $message1 = $messages[0]; $message2 = $messages[1]; $message3 = $messages[2]; $message4 = $messages[3]; $message5 = $messages[4]; $this->assertEquals($user2->id, $message1->userid); $this->assertEquals($user2->id, $message1->useridfrom); $this->assertEquals(fullname($user2), $message1->fullname); $this->assertTrue($message1->ismessaging); $this->assertEquals('Word.', $message1->lastmessage); $this->assertNotEmpty($message1->messageid); $this->assertNull($message1->isonline); $this->assertFalse($message1->isread); $this->assertFalse($message1->isblocked); $this->assertNull($message1->unreadcount); $this->assertEquals($convid, $message1->conversationid); $this->assertEquals($user2->id, $message2->userid); $this->assertEquals($user1->id, $message2->useridfrom); $this->assertEquals(fullname($user2), $message2->fullname); $this->assertTrue($message2->ismessaging); $this->assertEquals('Yo!', $message2->lastmessage); $this->assertNotEmpty($message2->messageid); $this->assertNull($message2->isonline); $this->assertTrue($message2->isread); $this->assertFalse($message2->isblocked); $this->assertNull($message2->unreadcount); $this->assertEquals($convid, $message2->conversationid); $this->assertEquals($user3->id, $message3->userid); $this->assertEquals($user3->id, $message3->useridfrom); $this->assertEquals(fullname($user3), $message3->fullname); $this->assertTrue($message3->ismessaging); $this->assertEquals('Don\'t block me.', $message3->lastmessage); $this->assertNotEmpty($message3->messageid); $this->assertNull($message3->isonline); $this->assertFalse($message3->isread); $this->assertTrue($message3->isblocked); $this->assertNull($message3->unreadcount); $this->assertEquals($conv2id, $message3->conversationid); // This is a group conversation. For now, search_messages returns only one of the other users on the conversation. It can't // be guaranteed who will be returned in the first place, so we need to use the in_array to check all the possibilities. $this->assertTrue(in_array($message4->userid, [$user2->id, $user3->id])); $this->assertEquals($user1->id, $message4->useridfrom); $this->assertTrue($message4->ismessaging); $this->assertEquals('My hero!', $message4->lastmessage); $this->assertNotEmpty($message4->messageid); $this->assertNull($message4->isonline); $this->assertTrue($message4->isread); $this->assertNull($message4->unreadcount); $this->assertEquals($gc->id, $message4->conversationid); $this->assertEquals($user1->id, $message5->userid); $this->assertEquals($user1->id, $message5->useridfrom); $this->assertEquals(fullname($user1), $message5->fullname); $this->assertTrue($message5->ismessaging); $this->assertEquals('Test message to self!', $message5->lastmessage); $this->assertNotEmpty($message5->messageid); $this->assertFalse($message5->isonline); $this->assertTrue($message5->isread); $this->assertFalse($message5->isblocked); $this->assertNull($message5->unreadcount); $this->assertEquals($sc->id, $message5->conversationid); } /** * Test verifying that favourited conversations can be retrieved. */ public function test_get_favourite_conversations(): void { $this->resetAfterTest(); // Create some users. $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); $user3 = self::getDataGenerator()->create_user(); $user4 = self::getDataGenerator()->create_user(); // The person doing the search. $this->setUser($user1); // Only self-conversation created. $this->assertCount(1, api::get_conversations($user1->id)); // Create some conversations for user1. $time = 1; testhelper::send_fake_message($user1, $user2, 'Yo!', 0, $time + 1); testhelper::send_fake_message($user2, $user1, 'Sup mang?', 0, $time + 2); testhelper::send_fake_message($user1, $user2, 'Writing PHPUnit tests!', 0, $time + 3); $messageid1 = testhelper::send_fake_message($user2, $user1, 'Word.', 0, $time + 4); testhelper::send_fake_message($user1, $user3, 'Booyah', 0, $time + 5); testhelper::send_fake_message($user3, $user1, 'Whaaat?', 0, $time + 6); testhelper::send_fake_message($user1, $user3, 'Nothing.', 0, $time + 7); $messageid2 = testhelper::send_fake_message($user3, $user1, 'Cool.', 0, $time + 8); testhelper::send_fake_message($user1, $user4, 'Hey mate, you see the new messaging UI in Moodle?', 0, $time + 9); testhelper::send_fake_message($user4, $user1, 'Yah brah, it\'s pretty rad.', 0, $time + 10); $messageid3 = testhelper::send_fake_message($user1, $user4, 'Dope.', 0, $time + 11); // Favourite the first 2 conversations for user1. $convoids = []; $convoids[] = api::get_conversation_between_users([$user1->id, $user2->id]); $convoids[] = api::get_conversation_between_users([$user1->id, $user3->id]); $user1context = \context_user::instance($user1->id); $service = \core_favourites\service_factory::get_service_for_user_context($user1context); foreach ($convoids as $convoid) { $service->create_favourite('core_message', 'message_conversations', $convoid, $user1context); } // We should have 4 conversations. // Consider first conversations is self-conversation. $this->assertCount(4, api::get_conversations($user1->id)); // And 3 favourited conversations (self-conversation included). $conversations = api::get_conversations($user1->id, 0, 20, null, true); $this->assertCount(3, $conversations); $conversations = api::get_conversations( $user1->id, 0, 20, api::MESSAGE_CONVERSATION_TYPE_SELF, true ); $this->assertCount(1, $conversations); } /** * Tests retrieving favourite conversations with a limit and offset to ensure pagination works correctly. */ public function test_get_favourite_conversations_limit_offset(): void { $this->resetAfterTest(); // Create some users. $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); $user3 = self::getDataGenerator()->create_user(); $user4 = self::getDataGenerator()->create_user(); // The person doing the search. $this->setUser($user1); // Only self-conversation created. $this->assertCount(1, api::get_conversations($user1->id)); // Create some conversations for user1. $time = 1; testhelper::send_fake_message($user1, $user2, 'Yo!', 0, $time + 1); testhelper::send_fake_message($user2, $user1, 'Sup mang?', 0, $time + 2); testhelper::send_fake_message($user1, $user2, 'Writing PHPUnit tests!', 0, $time + 3); $messageid1 = testhelper::send_fake_message($user2, $user1, 'Word.', 0, $time + 4); testhelper::send_fake_message($user1, $user3, 'Booyah', 0, $time + 5); testhelper::send_fake_message($user3, $user1, 'Whaaat?', 0, $time + 6); testhelper::send_fake_message($user1, $user3, 'Nothing.', 0, $time + 7); $messageid2 = testhelper::send_fake_message($user3, $user1, 'Cool.', 0, $time + 8); testhelper::send_fake_message($user1, $user4, 'Hey mate, you see the new messaging UI in Moodle?', 0, $time + 9); testhelper::send_fake_message($user4, $user1, 'Yah brah, it\'s pretty rad.', 0, $time + 10); $messageid3 = testhelper::send_fake_message($user1, $user4, 'Dope.', 0, $time + 11); // Favourite the all conversations for user1. $convoids = []; $convoids[] = api::get_conversation_between_users([$user1->id, $user2->id]); $convoids[] = api::get_conversation_between_users([$user1->id, $user3->id]); $convoids[] = api::get_conversation_between_users([$user1->id, $user4->id]); $user1context = \context_user::instance($user1->id); $service = \core_favourites\service_factory::get_service_for_user_context($user1context); foreach ($convoids as $convoid) { $service->create_favourite('core_message', 'message_conversations', $convoid, $user1context); } // Consider first conversations is self-conversation. // Get all records, using offset 0 and large limit. $this->assertCount(4, api::get_conversations($user1->id, 0, 20, null, true)); // Now, get 10 conversations starting at the second record. We should see 2 conversations. $this->assertCount(3, api::get_conversations($user1->id, 1, 10, null, true)); // Now, try to get favourited conversations using an invalid offset. $this->assertCount(0, api::get_conversations($user1->id, 5, 10, null, true)); } /** * Tests retrieving favourite conversations when a conversation contains a deleted user. */ public function test_get_favourite_conversations_with_deleted_user(): void { $this->resetAfterTest(); // Create some users. $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); $user3 = self::getDataGenerator()->create_user(); // Send some messages back and forth, have some different conversations with different users. $time = 1; testhelper::send_fake_message($user1, $user2, 'Yo!', 0, $time + 1); testhelper::send_fake_message($user2, $user1, 'Sup mang?', 0, $time + 2); testhelper::send_fake_message($user1, $user2, 'Writing PHPUnit tests!', 0, $time + 3); testhelper::send_fake_message($user2, $user1, 'Word.', 0, $time + 4); testhelper::send_fake_message($user1, $user3, 'Booyah', 0, $time + 5); testhelper::send_fake_message($user3, $user1, 'Whaaat?', 0, $time + 6); testhelper::send_fake_message($user1, $user3, 'Nothing.', 0, $time + 7); testhelper::send_fake_message($user3, $user1, 'Cool.', 0, $time + 8); // Favourite the all conversations for user1. $convoids = []; $convoids[] = api::get_conversation_between_users([$user1->id, $user2->id]); $convoids[] = api::get_conversation_between_users([$user1->id, $user3->id]); $user1context = \context_user::instance($user1->id); $service = \core_favourites\service_factory::get_service_for_user_context($user1context); foreach ($convoids as $convoid) { $service->create_favourite('core_message', 'message_conversations', $convoid, $user1context); } // Delete the second user. delete_user($user2); // Retrieve the conversations. $conversations = api::get_conversations($user1->id, 0, 20, null, true); // We should have both conversations, despite the other user being soft-deleted. // Consider first conversations is self-conversation. $this->assertCount(3, $conversations); // Confirm the conversation is from the non-deleted user. $conversation = reset($conversations); $this->assertEquals($convoids[1], $conversation->id); } /** * Test confirming that conversations can be marked as favourites. */ public function test_set_favourite_conversation(): void { $this->resetAfterTest(); // Create some users. $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); $user3 = self::getDataGenerator()->create_user(); // Send some messages back and forth, have some different conversations with different users. $time = 1; testhelper::send_fake_message($user1, $user2, 'Yo!', 0, $time + 1); testhelper::send_fake_message($user2, $user1, 'Sup mang?', 0, $time + 2); testhelper::send_fake_message($user1, $user2, 'Writing PHPUnit tests!', 0, $time + 3); testhelper::send_fake_message($user2, $user1, 'Word.', 0, $time + 4); testhelper::send_fake_message($user1, $user3, 'Booyah', 0, $time + 5); testhelper::send_fake_message($user3, $user1, 'Whaaat?', 0, $time + 6); testhelper::send_fake_message($user1, $user3, 'Nothing.', 0, $time + 7); testhelper::send_fake_message($user3, $user1, 'Cool.', 0, $time + 8); // Favourite the first conversation as user 1. $conversationid1 = api::get_conversation_between_users([$user1->id, $user2->id]); $favourite = api::set_favourite_conversation($conversationid1, $user1->id); // Verify we have two favourite conversations a user 1. // Consider first conversations is self-conversation. $this->assertCount(2, api::get_conversations($user1->id, 0, 20, null, true)); // Verify we have only one favourite as user2, despite being a member in that conversation. // Consider first conversations is self-conversation. $this->assertCount(1, api::get_conversations($user2->id, 0, 20, null, true)); // Try to favourite the same conversation again should just return the existing favourite. $repeatresult = api::set_favourite_conversation($conversationid1, $user1->id); $this->assertEquals($favourite->id, $repeatresult->id); } /** * Test verifying that trying to mark a non-existent conversation as a favourite, results in an exception. */ public function test_set_favourite_conversation_nonexistent_conversation(): void { $this->resetAfterTest(); // Create some users. $user1 = self::getDataGenerator()->create_user(); // Try to favourite a non-existent conversation. $this->expectException(\moodle_exception::class); api::set_favourite_conversation(0, $user1->id); } /** * Test verifying that a conversation cannot be marked as favourite unless the user is a member of that conversation. */ public function test_set_favourite_conversation_non_member(): void { $this->resetAfterTest(); // Create some users. $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); $user3 = self::getDataGenerator()->create_user(); // Send some messages back and forth, have some different conversations with different users. $time = 1; testhelper::send_fake_message($user1, $user2, 'Yo!', 0, $time + 1); testhelper::send_fake_message($user2, $user1, 'Sup mang?', 0, $time + 2); testhelper::send_fake_message($user1, $user2, 'Writing PHPUnit tests!', 0, $time + 3); testhelper::send_fake_message($user2, $user1, 'Word.', 0, $time + 4); testhelper::send_fake_message($user1, $user3, 'Booyah', 0, $time + 5); testhelper::send_fake_message($user3, $user1, 'Whaaat?', 0, $time + 6); testhelper::send_fake_message($user1, $user3, 'Nothing.', 0, $time + 7); testhelper::send_fake_message($user3, $user1, 'Cool.', 0, $time + 8); // Try to favourite the first conversation as user 3, who is not a member. $conversationid1 = api::get_conversation_between_users([$user1->id, $user2->id]); $this->expectException(\moodle_exception::class); api::set_favourite_conversation($conversationid1, $user3->id); } /** * Test confirming that those conversations marked as favourites can be unfavourited. */ public function test_unset_favourite_conversation(): void { $this->resetAfterTest(); // Create some users. $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); $user3 = self::getDataGenerator()->create_user(); // Send some messages back and forth, have some different conversations with different users. $time = 1; testhelper::send_fake_message($user1, $user2, 'Yo!', 0, $time + 1); testhelper::send_fake_message($user2, $user1, 'Sup mang?', 0, $time + 2); testhelper::send_fake_message($user1, $user2, 'Writing PHPUnit tests!', 0, $time + 3); testhelper::send_fake_message($user2, $user1, 'Word.', 0, $time + 4); testhelper::send_fake_message($user1, $user3, 'Booyah', 0, $time + 5); testhelper::send_fake_message($user3, $user1, 'Whaaat?', 0, $time + 6); testhelper::send_fake_message($user1, $user3, 'Nothing.', 0, $time + 7); testhelper::send_fake_message($user3, $user1, 'Cool.', 0, $time + 8); // Favourite the first conversation as user 1 and the second as user 3. $conversationid1 = api::get_conversation_between_users([$user1->id, $user2->id]); $conversationid2 = api::get_conversation_between_users([$user1->id, $user3->id]); api::set_favourite_conversation($conversationid1, $user1->id); api::set_favourite_conversation($conversationid2, $user3->id); // Verify we have two favourite conversations for both user 1 and user 3, counting self conversations. $this->assertCount(2, api::get_conversations($user1->id, 0, 20, null, true)); $this->assertCount(2, api::get_conversations($user3->id, 0, 20, null, true)); // Now unfavourite the conversation as user 1. api::unset_favourite_conversation($conversationid1, $user1->id); // Verify we have two favourite conversations user 3 only, and one for user1, counting self conversations. $this->assertCount(2, api::get_conversations($user3->id, 0, 20, null, true)); $this->assertCount(1, api::get_conversations($user1->id, 0, 20, null, true)); // Try to favourite the same conversation again as user 1. $this->expectException(\moodle_exception::class); api::unset_favourite_conversation($conversationid1, $user1->id); } /** * Test verifying that a valid conversation cannot be unset as a favourite if it's not marked as a favourite. */ public function test_unset_favourite_conversation_not_favourite(): void { $this->resetAfterTest(); // Create some users. $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); // Send some messages back and forth, have some different conversations with different users. $time = 1; testhelper::send_fake_message($user1, $user2, 'Yo!', 0, $time + 1); testhelper::send_fake_message($user2, $user1, 'Sup mang?', 0, $time + 2); testhelper::send_fake_message($user1, $user2, 'Writing PHPUnit tests!', 0, $time + 3); testhelper::send_fake_message($user2, $user1, 'Word.', 0, $time + 4); // Now try to unfavourite the conversation as user 1. $conversationid1 = api::get_conversation_between_users([$user1->id, $user2->id]); $this->expectException(\moodle_exception::class); api::unset_favourite_conversation($conversationid1, $user1->id); } /** * Test verifying that a non-existent conversation cannot be unset as a favourite. */ public function test_unset_favourite_conversation_non_existent_conversation(): void { $this->resetAfterTest(); // Create some users. $user1 = self::getDataGenerator()->create_user(); // Now try to unfavourite the conversation as user 1. $this->expectException(\moodle_exception::class); api::unset_favourite_conversation(0, $user1->id); } /** * Helper to seed the database with initial state. */ protected function create_conversation_test_data() { // Create some users. $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); $user3 = self::getDataGenerator()->create_user(); $user4 = self::getDataGenerator()->create_user(); $time = 1; // Create some conversations. We want: // 1) At least one of each type (group, individual) of which user1 IS a member and DID send the most recent message. // 2) At least one of each type (group, individual) of which user1 IS a member and DID NOT send the most recent message. // 3) At least one of each type (group, individual) of which user1 IS NOT a member. // 4) At least two group conversation having 0 messages, of which user1 IS a member (To confirm conversationid ordering). // 5) At least one group conversation having 0 messages, of which user1 IS NOT a member. // Individual conversation, user1 is a member, last message from other user. $ic1 = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, [$user1->id, $user2->id] ); testhelper::send_fake_message_to_conversation($user1, $ic1->id, 'Message 1', $time); testhelper::send_fake_message_to_conversation($user2, $ic1->id, 'Message 2', $time + 1); // Individual conversation, user1 is a member, last message from user1. $ic2 = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, [$user1->id, $user3->id] ); testhelper::send_fake_message_to_conversation($user3, $ic2->id, 'Message 3', $time + 2); testhelper::send_fake_message_to_conversation($user1, $ic2->id, 'Message 4', $time + 3); // Individual conversation, user1 is not a member. $ic3 = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, [$user2->id, $user3->id] ); testhelper::send_fake_message_to_conversation($user2, $ic3->id, 'Message 5', $time + 4); testhelper::send_fake_message_to_conversation($user3, $ic3->id, 'Message 6', $time + 5); // Group conversation, user1 is not a member. $gc1 = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_GROUP, [$user2->id, $user3->id, $user4->id], 'Project discussions' ); testhelper::send_fake_message_to_conversation($user2, $gc1->id, 'Message 7', $time + 6); testhelper::send_fake_message_to_conversation($user4, $gc1->id, 'Message 8', $time + 7); // Group conversation, user1 is a member, last message from another user. $gc2 = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_GROUP, [$user1->id, $user3->id, $user4->id], 'Group chat' ); testhelper::send_fake_message_to_conversation($user1, $gc2->id, 'Message 9', $time + 8); testhelper::send_fake_message_to_conversation($user3, $gc2->id, 'Message 10', $time + 9); testhelper::send_fake_message_to_conversation($user4, $gc2->id, 'Message 11', $time + 10); // Group conversation, user1 is a member, last message from user1. $gc3 = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_GROUP, [$user1->id, $user2->id, $user3->id, $user4->id], 'Group chat again!' ); testhelper::send_fake_message_to_conversation($user4, $gc3->id, 'Message 12', $time + 11); testhelper::send_fake_message_to_conversation($user3, $gc3->id, 'Message 13', $time + 12); testhelper::send_fake_message_to_conversation($user1, $gc3->id, 'Message 14', $time + 13); // Empty group conversations (x2), user1 is a member. $gc4 = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_GROUP, [$user1->id, $user2->id, $user3->id], 'Empty group' ); $gc5 = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_GROUP, [$user1->id, $user2->id, $user4->id], 'Another empty group' ); // Empty group conversation, user1 is NOT a member. $gc6 = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_GROUP, [$user2->id, $user3->id, $user4->id], 'Empty group 3' ); return [$user1, $user2, $user3, $user4, $ic1, $ic2, $ic3, $gc1, $gc2, $gc3, $gc4, $gc5, $gc6]; } /** * Test verifying get_conversations when no limits, offsets, type filters or favourite restrictions are used. */ public function test_get_conversations_no_restrictions(): void { $this->resetAfterTest(); global $DB; $user1 = self::getDataGenerator()->create_user(); // Self-conversation should exists. $this->assertCount(1, api::get_conversations($user1->id)); // Get a bunch of conversations, some group, some individual and in different states. [$user1, $user2, $user3, $user4, $ic1, $ic2, $ic3, $gc1, $gc2, $gc3, $gc4, $gc5, $gc6] = $this->create_conversation_test_data(); // Get all conversations for user1. $conversations = api::get_conversations($user1->id); // Verify there are 2 individual conversation, 2 group conversations, 2 empty group conversations, // and a self-conversation. // The conversations with the most recent messages should be listed first, followed by the empty // conversations, with the most recently created first. $this->assertCount(7, $conversations); $typecounts = array_count_values(array_column($conversations, 'type')); $this->assertEquals(2, $typecounts[1]); $this->assertEquals(4, $typecounts[2]); $this->assertEquals(1, $typecounts[3]); // Those conversations having messages should be listed after self-conversation, ordered by most recent message time. $this->assertEquals($gc3->id, $conversations[0]->id); $this->assertEquals(api::MESSAGE_CONVERSATION_TYPE_GROUP, $conversations[0]->type); $this->assertFalse($conversations[1]->isfavourite); $this->assertCount(1, $conversations[0]->members); $this->assertEquals(4, $conversations[0]->membercount); $this->assertCount(1, $conversations[0]->messages); $message = $DB->get_record('messages', ['id' => $conversations[0]->messages[0]->id]); $expectedmessagetext = message_format_message_text($message); $this->assertEquals($expectedmessagetext, $conversations[0]->messages[0]->text); $this->assertEquals($user1->id, $conversations[0]->messages[0]->useridfrom); $this->assertEquals($gc2->id, $conversations[1]->id); $this->assertEquals(api::MESSAGE_CONVERSATION_TYPE_GROUP, $conversations[1]->type); $this->assertFalse($conversations[1]->isfavourite); $this->assertCount(1, $conversations[1]->members); $this->assertEquals(3, $conversations[1]->membercount); $this->assertCount(1, $conversations[1]->messages); $message = $DB->get_record('messages', ['id' => $conversations[1]->messages[0]->id]); $expectedmessagetext = message_format_message_text($message); $this->assertEquals($expectedmessagetext, $conversations[1]->messages[0]->text); $this->assertEquals($user4->id, $conversations[1]->messages[0]->useridfrom); $this->assertEquals($ic2->id, $conversations[2]->id); $this->assertEquals(api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, $conversations[2]->type); $this->assertFalse($conversations[2]->isfavourite); $this->assertCount(1, $conversations[2]->members); $this->assertEquals($user3->id, $conversations[2]->members[$user3->id]->id); $this->assertEquals(2, $conversations[2]->membercount); $this->assertCount(1, $conversations[2]->messages); $message = $DB->get_record('messages', ['id' => $conversations[2]->messages[0]->id]); $expectedmessagetext = message_format_message_text($message); $this->assertEquals($expectedmessagetext, $conversations[2]->messages[0]->text); $this->assertEquals($user1->id, $conversations[2]->messages[0]->useridfrom); $this->assertEquals($ic1->id, $conversations[3]->id); $this->assertEquals(api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, $conversations[3]->type); $this->assertFalse($conversations[3]->isfavourite); $this->assertCount(1, $conversations[3]->members); $this->assertEquals(2, $conversations[3]->membercount); $this->assertCount(1, $conversations[3]->messages); $message = $DB->get_record('messages', ['id' => $conversations[3]->messages[0]->id]); $expectedmessagetext = message_format_message_text($message); $this->assertEquals($expectedmessagetext, $conversations[3]->messages[0]->text); $this->assertEquals($user2->id, $conversations[3]->messages[0]->useridfrom); // Of the groups without messages, we expect to see the most recently created first. $this->assertEquals($gc5->id, $conversations[4]->id); $this->assertEquals(api::MESSAGE_CONVERSATION_TYPE_GROUP, $conversations[4]->type); $this->assertFalse($conversations[4]->isfavourite); $this->assertCount(0, $conversations[4]->members); // No members returned, because no recent messages exist. $this->assertEquals(3, $conversations[4]->membercount); $this->assertEmpty($conversations[4]->messages); $this->assertEquals($gc4->id, $conversations[5]->id); $this->assertEquals(api::MESSAGE_CONVERSATION_TYPE_GROUP, $conversations[5]->type); $this->assertFalse($conversations[5]->isfavourite); $this->assertCount(0, $conversations[5]->members); $this->assertEquals(3, $conversations[5]->membercount); $this->assertEmpty($conversations[5]->messages); // Verify format of the return structure. foreach ($conversations as $conv) { $this->assertObjectHasProperty('id', $conv); $this->assertObjectHasProperty('name', $conv); $this->assertObjectHasProperty('subname', $conv); $this->assertObjectHasProperty('imageurl', $conv); $this->assertObjectHasProperty('type', $conv); $this->assertObjectHasProperty('isfavourite', $conv); $this->assertObjectHasProperty('membercount', $conv); $this->assertObjectHasProperty('isread', $conv); $this->assertObjectHasProperty('unreadcount', $conv); $this->assertObjectHasProperty('members', $conv); foreach ($conv->members as $member) { $this->assertObjectHasProperty('id', $member); $this->assertObjectHasProperty('fullname', $member); $this->assertObjectHasProperty('profileimageurl', $member); $this->assertObjectHasProperty('profileimageurlsmall', $member); $this->assertObjectHasProperty('isonline', $member); $this->assertObjectHasProperty('showonlinestatus', $member); $this->assertObjectHasProperty('isblocked', $member); $this->assertObjectHasProperty('iscontact', $member); $this->assertObjectHasProperty('isdeleted', $member); $this->assertObjectHasProperty('canmessage', $member); $this->assertObjectHasProperty('requirescontact', $member); $this->assertObjectHasProperty('contactrequests', $member); } $this->assertObjectHasProperty('messages', $conv); foreach ($conv->messages as $message) { $this->assertObjectHasProperty('id', $message); $this->assertObjectHasProperty('useridfrom', $message); $this->assertObjectHasProperty('text', $message); $this->assertObjectHasProperty('timecreated', $message); } } } /** * Test verifying that html format messages are supported, and that message_format_message_text() is being called appropriately. */ public function test_get_conversations_message_format(): void { global $DB; $this->resetAfterTest(); // Create some users. $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); // Create conversation. $conversation = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, [$user1->id, $user2->id] ); // Send some messages back and forth. $time = 1; testhelper::send_fake_message_to_conversation($user2, $conversation->id, 'Sup mang?', $time + 1); $mid = testhelper::send_fake_message_to_conversation($user1, $conversation->id, 'A link', $time + 2); // Verify the format of the html message. $message = $DB->get_record('messages', ['id' => $mid]); $expectedmessagetext = message_format_message_text($message); $conversations = api::get_conversations($user1->id); $messages = $conversations[0]->messages; $this->assertEquals($expectedmessagetext, $messages[0]->text); } /** * Test verifying get_conversations identifies if a conversation is muted or not. */ public function test_get_conversations_some_muted(): void { $this->resetAfterTest(); // Create some users. $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); $user3 = self::getDataGenerator()->create_user(); $conversation1 = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, [$user1->id, $user2->id] ); testhelper::send_fake_message_to_conversation($user1, $conversation1->id, 'Message 1'); testhelper::send_fake_message_to_conversation($user2, $conversation1->id, 'Message 2'); api::mute_conversation($user1->id, $conversation1->id); $conversation2 = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, [$user1->id, $user3->id] ); testhelper::send_fake_message_to_conversation($user1, $conversation2->id, 'Message 1'); testhelper::send_fake_message_to_conversation($user2, $conversation2->id, 'Message 2'); $conversation3 = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_GROUP, [$user1->id, $user2->id] ); api::mute_conversation($user1->id, $conversation3->id); $conversation4 = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_GROUP, [$user1->id, $user3->id] ); $conversations = api::get_conversations($user1->id); usort($conversations, function ($first, $second) { return $first->id <=> $second->id; }); // Consider first conversations is self-conversation. $selfconversation = array_shift($conversations); $conv1 = array_shift($conversations); $conv2 = array_shift($conversations); $conv3 = array_shift($conversations); $conv4 = array_shift($conversations); $this->assertTrue($conv1->ismuted); $this->assertFalse($conv2->ismuted); $this->assertTrue($conv3->ismuted); $this->assertFalse($conv4->ismuted); } /** * Tests retrieving conversations with a limit and offset to ensure pagination works correctly. */ public function test_get_conversations_limit_offset(): void { $this->resetAfterTest(); // Get a bunch of conversations, some group, some individual and in different states. [$user1, $user2, $user3, $user4, $ic1, $ic2, $ic3, $gc1, $gc2, $gc3, $gc4, $gc5, $gc6] = $this->create_conversation_test_data(); // Get all conversations for user1, limited to 1 result. $conversations = api::get_conversations($user1->id, 0, 1); // Verify the first conversation. $this->assertCount(1, $conversations); $conversation = array_shift($conversations); $this->assertEquals($conversation->id, $gc3->id); // Verify the next conversation. $conversations = api::get_conversations($user1->id, 1, 1); $this->assertCount(1, $conversations); $this->assertEquals($gc2->id, $conversations[0]->id); // Verify the next conversation. $conversations = api::get_conversations($user1->id, 2, 1); $this->assertCount(1, $conversations); $this->assertEquals($ic2->id, $conversations[0]->id); // Skip one and get both empty conversations. $conversations = api::get_conversations($user1->id, 4, 2); $this->assertCount(2, $conversations); $this->assertEquals($gc5->id, $conversations[0]->id); $this->assertEmpty($conversations[0]->messages); $this->assertEquals($gc4->id, $conversations[1]->id); $this->assertEmpty($conversations[1]->messages); // Ask for an offset that doesn't exist and verify no conversations are returned. $conversations = api::get_conversations($user1->id, 10, 1); $this->assertCount(0, $conversations); } /** * Test verifying the type filtering behaviour of the */ public function test_get_conversations_type_filter(): void { $this->resetAfterTest(); // Get a bunch of conversations, some group, some individual and in different states. [$user1, $user2, $user3, $user4, $ic1, $ic2, $ic3, $gc1, $gc2, $gc3, $gc4, $gc5, $gc6] = $this->create_conversation_test_data(); // Verify we can ask for only individual conversations. $conversations = api::get_conversations( $user1->id, 0, 20, api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL ); $this->assertCount(2, $conversations); // Verify we can ask for only group conversations. $conversations = api::get_conversations( $user1->id, 0, 20, api::MESSAGE_CONVERSATION_TYPE_GROUP ); $this->assertCount(4, $conversations); // Verify an exception is thrown if an unrecognized type is specified. $this->expectException(\moodle_exception::class); $conversations = api::get_conversations($user1->id, 0, 20, 0); } /** * Tests retrieving conversations when a 'self' conversation exists. */ public function test_get_conversations_self_conversations(): void { global $DB; $this->resetAfterTest(); // Create a conversation between one user and themself. $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); $user3 = self::getDataGenerator()->create_user(); $user4 = self::getDataGenerator()->create_user(); // Create some individual conversations. $ic1 = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, [$user1->id, $user2->id] ); $ic2 = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, [$user1->id, $user3->id] ); testhelper::send_fake_message_to_conversation($user1, $ic1->id, 'Message from user1 to user2'); // Get some self-conversations. $sc1 = api::get_self_conversation($user1->id); $sc4 = api::get_self_conversation($user4->id); testhelper::send_fake_message_to_conversation($user1, $sc1->id, 'Test message to self 1!'); // Verify we are in a 'self' conversation state. $members = $DB->get_records('message_conversation_members', ['conversationid' => $sc1->id]); $this->assertCount(1, $members); $member = array_pop($members); $this->assertEquals($user1->id, $member->userid); // Verify the self-conversations are returned by the method. $conversations = api::get_conversations($user1->id, 0, 20, api::MESSAGE_CONVERSATION_TYPE_SELF); $this->assertCount(1, $conversations); $conversation = array_pop($conversations); $this->assertEquals($conversation->id, $sc1->id); $conversations = api::get_conversations($user4->id); // The self-conversation. $this->assertCount(1, $conversations); // Get only private conversations for user1 (empty conversations, like $ic2, are not returned). $conversations = api::get_conversations( $user1->id, 0, 20, api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL ); $this->assertCount(1, $conversations); // Merge self with private conversations for user1. $conversations = api::get_conversations( $user1->id, 0, 20, api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, null, true ); $this->assertCount(2, $conversations); // Get only private conversations for user2. $conversations = api::get_conversations( $user2->id, 0, 20, api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL ); $this->assertCount(1, $conversations); // Merge self with private conversations for user2. $conversations = api::get_conversations( $user2->id, 0, 20, api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, null, true ); $this->assertCount(2, $conversations); } /** * Tests retrieving conversations when a conversation contains a deleted user. */ public function test_get_conversations_with_deleted_user(): void { $this->resetAfterTest(); // Get a bunch of conversations, some group, some individual and in different states. [$user1, $user2, $user3, $user4, $ic1, $ic2, $ic3, $gc1, $gc2, $gc3, $gc4, $gc5, $gc6] = $this->create_conversation_test_data(); // Delete the second user and retrieve the conversations. // We should have 6 still, as conversations with soft-deleted users are still returned. // Group conversations are also present, albeit with less members. delete_user($user2); // This is to confirm an exception is not thrown when a user AND the user context is deleted. // We no longer delete the user context, but historically we did. \context_helper::delete_instance(CONTEXT_USER, $user2->id); $conversations = api::get_conversations($user1->id); // Consider there's a self-conversation (the last one). $this->assertCount(7, $conversations); $this->assertEquals($gc3->id, $conversations[0]->id); $this->assertcount(1, $conversations[0]->members); $this->assertEquals($gc2->id, $conversations[1]->id); $this->assertcount(1, $conversations[1]->members); $this->assertEquals($ic2->id, $conversations[2]->id); $this->assertEquals($ic1->id, $conversations[3]->id); $this->assertEquals($gc5->id, $conversations[4]->id); $this->assertEquals($gc4->id, $conversations[5]->id); // Delete a user from a group conversation where that user had sent the most recent message. // This user will still be present in the members array, as will the message in the messages array. delete_user($user4); $conversations = api::get_conversations($user1->id); // Consider there's a self-conversation (the last one). $this->assertCount(7, $conversations); $this->assertEquals($gc2->id, $conversations[1]->id); $this->assertcount(1, $conversations[1]->members); $this->assertEquals($user4->id, $conversations[1]->members[$user4->id]->id); $this->assertcount(1, $conversations[1]->messages); $this->assertEquals($user4->id, $conversations[1]->messages[0]->useridfrom); // Delete the third user and retrieve the conversations. // We should have 6 still, as conversations with soft-deleted users are still returned. // Group conversations are also present, albeit with less members. delete_user($user3); $conversations = api::get_conversations($user1->id); // Consider there's a self-conversation (the last one). $this->assertCount(7, $conversations); $this->assertEquals($gc3->id, $conversations[0]->id); $this->assertcount(1, $conversations[0]->members); $this->assertEquals($gc2->id, $conversations[1]->id); $this->assertcount(1, $conversations[1]->members); $this->assertEquals($ic2->id, $conversations[2]->id); $this->assertEquals($ic1->id, $conversations[3]->id); $this->assertEquals($gc5->id, $conversations[4]->id); $this->assertEquals($gc4->id, $conversations[5]->id); } /** * Test confirming the behaviour of get_conversations() when users delete all messages. */ public function test_get_conversations_deleted_messages(): void { $this->resetAfterTest(); // Get a bunch of conversations, some group, some individual and in different states. [$user1, $user2, $user3, $user4, $ic1, $ic2, $ic3, $gc1, $gc2, $gc3, $gc4, $gc5, $gc6] = $this->create_conversation_test_data(); $conversations = api::get_conversations($user1->id); // Consider first conversations is self-conversation. $this->assertCount(7, $conversations); // Delete all messages from a group conversation the user is in - it should be returned. $this->assertTrue(api::is_user_in_conversation($user1->id, $gc2->id)); $convmessages = api::get_conversation_messages($user1->id, $gc2->id); $messages = $convmessages['messages']; foreach ($messages as $message) { api::delete_message($user1->id, $message->id); } $conversations = api::get_conversations($user1->id); // Consider first conversations is self-conversation. $this->assertCount(7, $conversations); $this->assertContainsEquals($gc2->id, array_column($conversations, 'id')); // Delete all messages from an individual conversation the user is in - it should not be returned. $this->assertTrue(api::is_user_in_conversation($user1->id, $ic1->id)); $convmessages = api::get_conversation_messages($user1->id, $ic1->id); $messages = $convmessages['messages']; foreach ($messages as $message) { api::delete_message($user1->id, $message->id); } $conversations = api::get_conversations($user1->id); // Consider first conversations is self-conversation. $this->assertCount(6, $conversations); $this->assertNotContainsEquals($ic1->id, array_column($conversations, 'id')); } /** * Test verifying the behaviour of get_conversations() when fetching favourite conversations with only a single * favourite. */ public function test_get_conversations_favourite_conversations_single(): void { $this->resetAfterTest(); // Get a bunch of conversations, some group, some individual and in different states. [$user1, $user2, $user3, $user4, $ic1, $ic2, $ic3, $gc1, $gc2, $gc3, $gc4, $gc5, $gc6] = $this->create_conversation_test_data(); // Mark a single conversation as favourites. api::set_favourite_conversation($ic2->id, $user1->id); // Get the conversation, first with no restrictions, confirming the favourite status of the conversations. $conversations = api::get_conversations($user1->id); // Consider there is a self-conversation. $selfconversation = api::get_self_conversation($user1->id); $this->assertCount(7, $conversations); foreach ($conversations as $conv) { if (in_array($conv->id, [$ic2->id, $selfconversation->id])) { $this->assertTrue($conv->isfavourite); } else { $this->assertFalse($conv->isfavourite); } } // Now, get ONLY favourite conversations (including self-conversation). $conversations = api::get_conversations($user1->id, 0, 20, null, true); $this->assertCount(2, $conversations); foreach ($conversations as $conv) { if ($conv->type != api::MESSAGE_CONVERSATION_TYPE_SELF) { $this->assertTrue($conv->isfavourite); $this->assertEquals(api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, $conv->type); $this->assertEquals($ic2->id, $conv->id); } } // Now, try ONLY favourites of type 'group'. $conversations = api::get_conversations( $user1->id, 0, 20, api::MESSAGE_CONVERSATION_TYPE_GROUP, true ); $this->assertEmpty($conversations); // And NO favourite conversations. $conversations = api::get_conversations($user1->id, 0, 20, null, false); $this->assertCount(5, $conversations); foreach ($conversations as $conv) { $this->assertFalse($conv->isfavourite); $this->assertNotEquals($ic2, $conv->id); } } /** * Test verifying the behaviour of get_conversations() when fetching favourite conversations. */ public function test_get_conversations_favourite_conversations(): void { $this->resetAfterTest(); // Get a bunch of conversations, some group, some individual and in different states. [$user1, $user2, $user3, $user4, $ic1, $ic2, $ic3, $gc1, $gc2, $gc3, $gc4, $gc5, $gc6] = $this->create_conversation_test_data(); // Try to get ONLY favourite conversations, when only self-conversation exist. $this->assertCount(1, api::get_conversations($user1->id, 0, 20, null, true)); // Unstar self-conversation. $selfconversation = api::get_self_conversation($user1->id); api::unset_favourite_conversation($selfconversation->id, $user1->id); // Try to get ONLY favourite conversations, when no favourites exist. $this->assertEquals([], api::get_conversations($user1->id, 0, 20, null, true)); // Try to get NO favourite conversations, when no favourites exist. $this->assertCount(7, api::get_conversations($user1->id, 0, 20, null, false)); // Mark a few conversations as favourites. api::set_favourite_conversation($ic1->id, $user1->id); api::set_favourite_conversation($gc2->id, $user1->id); api::set_favourite_conversation($gc5->id, $user1->id); $favouriteids = [$ic1->id, $gc2->id, $gc5->id]; // Get the conversations, first with no restrictions, confirming the favourite status of the conversations. $conversations = api::get_conversations($user1->id); $this->assertCount(7, $conversations); foreach ($conversations as $conv) { if (in_array($conv->id, $favouriteids)) { $this->assertTrue($conv->isfavourite); } else { $this->assertFalse($conv->isfavourite); } } // Now, get ONLY favourite conversations. $conversations = api::get_conversations($user1->id, 0, 20, null, true); $this->assertCount(3, $conversations); foreach ($conversations as $conv) { $this->assertTrue($conv->isfavourite); $this->assertNotFalse(array_search($conv->id, $favouriteids)); } // Now, try ONLY favourites of type 'group'. $conversations = api::get_conversations( $user1->id, 0, 20, api::MESSAGE_CONVERSATION_TYPE_GROUP, true ); $this->assertCount(2, $conversations); foreach ($conversations as $conv) { $this->assertTrue($conv->isfavourite); $this->assertNotFalse(array_search($conv->id, [$gc2->id, $gc5->id])); } // And NO favourite conversations. $conversations = api::get_conversations($user1->id, 0, 20, null, false); $this->assertCount(4, $conversations); foreach ($conversations as $conv) { $this->assertFalse($conv->isfavourite); $this->assertFalse(array_search($conv->id, $favouriteids)); } } /** * Test verifying get_conversations when there are users in a group and/or individual conversation. The reason this * test is performed is because we do not need as much data for group conversations (saving DB calls), so we want * to confirm this happens. */ public function test_get_conversations_user_in_group_and_individual_chat(): void { $this->resetAfterTest(); $this->resetAfterTest(); $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); $user3 = self::getDataGenerator()->create_user(); $conversation = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, [ $user1->id, $user2->id, ], 'Individual conversation' ); testhelper::send_fake_message_to_conversation($user1, $conversation->id); $conversation = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_GROUP, [ $user1->id, $user2->id, ], 'Group conversation' ); testhelper::send_fake_message_to_conversation($user1, $conversation->id); api::create_contact_request($user1->id, $user2->id); api::create_contact_request($user1->id, $user3->id); $conversations = api::get_conversations($user2->id); $groupconversation = array_shift($conversations); $individualconversation = array_shift($conversations); $this->assertEquals('Group conversation', $groupconversation->name); $this->assertEquals('Individual conversation', $individualconversation->name); $this->assertCount(1, $groupconversation->members); $this->assertCount(1, $individualconversation->members); $groupmember = reset($groupconversation->members); $this->assertNull($groupmember->requirescontact); $this->assertNull($groupmember->canmessage); $this->assertEmpty($groupmember->contactrequests); $individualmember = reset($individualconversation->members); $this->assertNotNull($individualmember->requirescontact); $this->assertNotNull($individualmember->canmessage); $this->assertNotEmpty($individualmember->contactrequests); } /** * Test verifying that group linked conversations are returned and contain a subname matching the course name. */ public function test_get_conversations_group_linked(): void { global $CFG, $DB; $this->resetAfterTest(); // Create some users. $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); $user3 = self::getDataGenerator()->create_user(); $course1 = $this->getDataGenerator()->create_course(); // Create a group with a linked conversation and a valid image. $this->setAdminUser(); $this->getDataGenerator()->enrol_user($user1->id, $course1->id); $this->getDataGenerator()->enrol_user($user2->id, $course1->id); $this->getDataGenerator()->enrol_user($user3->id, $course1->id); $group1 = $this->getDataGenerator()->create_group([ 'courseid' => $course1->id, 'enablemessaging' => 1, 'picturepath' => $CFG->dirroot . '/lib/tests/fixtures/gd-logo.png', ]); // Add users to group1. $this->getDataGenerator()->create_group_member(['groupid' => $group1->id, 'userid' => $user1->id]); $this->getDataGenerator()->create_group_member(['groupid' => $group1->id, 'userid' => $user2->id]); // Verify the group with the image works as expected. $conversations = api::get_conversations($user1->id); $this->assertEquals(2, $conversations[0]->membercount); $this->assertEquals($course1->shortname, $conversations[0]->subname); $groupimageurl = get_group_picture_url($group1, $group1->courseid, true); $this->assertEquals($groupimageurl, $conversations[0]->imageurl); // Create a group with a linked conversation and without any image. $group2 = $this->getDataGenerator()->create_group([ 'courseid' => $course1->id, 'enablemessaging' => 1, ]); // Add users to group2. $this->getDataGenerator()->create_group_member(['groupid' => $group2->id, 'userid' => $user2->id]); $this->getDataGenerator()->create_group_member(['groupid' => $group2->id, 'userid' => $user3->id]); // Verify the group without any image works as expected too. $conversations = api::get_conversations($user3->id); // Consider first conversations is self-conversation. $this->assertEquals(2, $conversations[0]->membercount); $this->assertEquals($course1->shortname, $conversations[0]->subname); $this->assertEquals('https://www.example.com/moodle/theme/image.php/boost/core/1/g/g1', $conversations[0]->imageurl); // Now, disable the conversation linked to the group and verify it's no longer returned. $DB->set_field('message_conversations', 'enabled', 0, ['id' => $conversations[0]->id]); $conversations = api::get_conversations($user3->id); $this->assertCount(1, $conversations); } /** * The data provider for get_conversations_mixed. * * This provides sets of data to for testing. * * @return array */ public static function get_conversations_mixed_provider(): array { return [ 'Test that conversations with messages contacts is correctly ordered.' => [ 'usersdata' => [ 'user1', 'user2', 'user3', ], 'contacts' => [ ], 'messagesdata' => [ [ 'from' => 'user1', 'to' => 'user2', 'state' => 'unread', 'subject' => 'S1', ], [ 'from' => 'user2', 'to' => 'user1', 'state' => 'unread', 'subject' => 'S2', ], [ 'from' => 'user1', 'to' => 'user2', 'state' => 'unread', 'timecreated' => 0, 'subject' => 'S3', ], [ 'from' => 'user1', 'to' => 'user3', 'state' => 'read', 'timemodifier' => 1, 'subject' => 'S4', ], [ 'from' => 'user3', 'to' => 'user1', 'state' => 'read', 'timemodifier' => 1, 'subject' => 'S5', ], [ 'from' => 'user1', 'to' => 'user3', 'state' => 'read', 'timecreated' => 0, 'subject' => 'S6', ], ], 'expectations' => [ 'user1' => [ // User1 has conversed most recently with user3. The most recent message is M5. [ 'messageposition' => 0, 'with' => 'user3', 'subject' => '

S5

', 'unreadcount' => 0, ], // User1 has also conversed with user2. The most recent message is S2. [ 'messageposition' => 1, 'with' => 'user2', 'subject' => '

S2

', 'unreadcount' => 1, ], ], 'user2' => [ // User2 has only conversed with user1. Their most recent shared message was S2. [ 'messageposition' => 0, 'with' => 'user1', 'subject' => '

S2

', 'unreadcount' => 2, ], ], 'user3' => [ // User3 has only conversed with user1. Their most recent shared message was S5. [ 'messageposition' => 0, 'with' => 'user1', 'subject' => '

S5

', 'unreadcount' => 0, ], ], ], ], 'Test conversations with a single user, where some messages are read and some are not.' => [ 'usersdata' => [ 'user1', 'user2', ], 'contacts' => [ ], 'messagesdata' => [ [ 'from' => 'user1', 'to' => 'user2', 'state' => 'read', 'subject' => 'S1', ], [ 'from' => 'user2', 'to' => 'user1', 'state' => 'read', 'subject' => 'S2', ], [ 'from' => 'user1', 'to' => 'user2', 'state' => 'unread', 'timemodifier' => 1, 'subject' => 'S3', ], [ 'from' => 'user1', 'to' => 'user2', 'state' => 'unread', 'timemodifier' => 1, 'subject' => 'S4', ], ], 'expectations' => [ // The most recent message between user1 and user2 was S4. 'user1' => [ [ 'messageposition' => 0, 'with' => 'user2', 'subject' => '

S4

', 'unreadcount' => 0, ], ], 'user2' => [ // The most recent message between user1 and user2 was S4. [ 'messageposition' => 0, 'with' => 'user1', 'subject' => '

S4

', 'unreadcount' => 2, ], ], ], ], 'Test conversations with a single user, where some messages are read and some are not, and messages ' . 'are out of order' => [ // This can happen through a combination of factors including multi-master DB replication with messages // read somehow (e.g. API). 'usersdata' => [ 'user1', 'user2', ], 'contacts' => [ ], 'messagesdata' => [ [ 'from' => 'user1', 'to' => 'user2', 'state' => 'read', 'subject' => 'S1', 'timemodifier' => 1, ], [ 'from' => 'user2', 'to' => 'user1', 'state' => 'read', 'subject' => 'S2', 'timemodifier' => 2, ], [ 'from' => 'user1', 'to' => 'user2', 'state' => 'unread', 'subject' => 'S3', ], [ 'from' => 'user1', 'to' => 'user2', 'state' => 'unread', 'subject' => 'S4', ], ], 'expectations' => [ // The most recent message between user1 and user2 was S2, even though later IDs have not been read. 'user1' => [ [ 'messageposition' => 0, 'with' => 'user2', 'subject' => '

S2

', 'unreadcount' => 0, ], ], 'user2' => [ [ 'messageposition' => 0, 'with' => 'user1', 'subject' => '

S2

', 'unreadcount' => 2, ], ], ], ], 'Test unread message count is correct for both users' => [ 'usersdata' => [ 'user1', 'user2', ], 'contacts' => [ ], 'messagesdata' => [ [ 'from' => 'user1', 'to' => 'user2', 'state' => 'read', 'subject' => 'S1', 'timemodifier' => 1, ], [ 'from' => 'user2', 'to' => 'user1', 'state' => 'read', 'subject' => 'S2', 'timemodifier' => 2, ], [ 'from' => 'user1', 'to' => 'user2', 'state' => 'read', 'subject' => 'S3', 'timemodifier' => 3, ], [ 'from' => 'user1', 'to' => 'user2', 'state' => 'read', 'subject' => 'S4', 'timemodifier' => 4, ], [ 'from' => 'user1', 'to' => 'user2', 'state' => 'unread', 'subject' => 'S5', 'timemodifier' => 5, ], [ 'from' => 'user2', 'to' => 'user1', 'state' => 'unread', 'subject' => 'S6', 'timemodifier' => 6, ], [ 'from' => 'user1', 'to' => 'user2', 'state' => 'unread', 'subject' => 'S7', 'timemodifier' => 7, ], [ 'from' => 'user1', 'to' => 'user2', 'state' => 'unread', 'subject' => 'S8', 'timemodifier' => 8, ], ], 'expectations' => [ // The most recent message between user1 and user2 was S2, even though later IDs have not been read. 'user1' => [ [ 'messageposition' => 0, 'with' => 'user2', 'subject' => '

S8

', 'unreadcount' => 1, ], ], 'user2' => [ [ 'messageposition' => 0, 'with' => 'user1', 'subject' => '

S8

', 'unreadcount' => 3, ], ], ], ], ]; } /** * Test that creation can't create the same conversation twice for 1:1 conversations. */ public function test_create_conversation_duplicate_conversations(): void { global $DB; $this->resetAfterTest(); $user1 = $this::getDataGenerator()->create_user(); api::create_conversation(api::MESSAGE_CONVERSATION_TYPE_SELF, [$user1->id]); api::create_conversation(api::MESSAGE_CONVERSATION_TYPE_SELF, [$user1->id]); $convhash = helper::get_conversation_hash([$user1->id]); $countconversations = $DB->count_records('message_conversations', ['convhash' => $convhash]); $this->assertEquals(1, $countconversations); $this->assertNotEmpty($conversation = api::get_self_conversation($user1->id)); } /** * Test get_conversations with a mixture of messages. * * @dataProvider get_conversations_mixed_provider * @param array $usersdata The list of users to create for this test. * @param array $messagesdata The list of messages to create. * @param array $expectations The list of expected outcomes. */ public function test_get_conversations_mixed($usersdata, $contacts, $messagesdata, $expectations): void { global $DB; $this->resetAfterTest(); $this->redirectMessages(); // Create all of the users. $users = []; foreach ($usersdata as $username) { $users[$username] = $this->getDataGenerator()->create_user(['username' => $username]); } foreach ($contacts as $username => $contact) { foreach ($contact as $contactname => $blocked) { $record = new \stdClass(); $record->userid = $users[$username]->id; $record->contactid = $users[$contactname]->id; $record->blocked = $blocked; $record->id = $DB->insert_record('message_contacts', $record); } } $defaulttimecreated = time(); foreach ($messagesdata as $messagedata) { $from = $users[$messagedata['from']]; $to = $users[$messagedata['to']]; $subject = $messagedata['subject']; if (isset($messagedata['state']) && $messagedata['state'] == 'unread') { $messageid = testhelper::send_fake_message($from, $to, $subject); } else { // If there is no state, or the state is not 'unread', assume the message is read. $messageid = message_post_message($from, $to, $subject, FORMAT_PLAIN); } $updatemessage = new \stdClass(); $updatemessage->id = $messageid; if (isset($messagedata['timecreated'])) { $updatemessage->timecreated = $messagedata['timecreated']; } else if (isset($messagedata['timemodifier'])) { $updatemessage->timecreated = $defaulttimecreated + $messagedata['timemodifier']; } else { $updatemessage->timecreated = $defaulttimecreated; } $DB->update_record('messages', $updatemessage); } foreach ($expectations as $username => $data) { // Get the recent conversations for the specified user. $user = $users[$username]; $conversations = array_values(api::get_conversations($user->id)); foreach ($data as $expectation) { $otheruser = $users[$expectation['with']]; $conversation = $conversations[$expectation['messageposition']]; $this->assertEquals($otheruser->id, $conversation->members[$otheruser->id]->id); $this->assertEquals($expectation['subject'], $conversation->messages[0]->text); $this->assertEquals($expectation['unreadcount'], $conversation->unreadcount); } } } /** * Tests retrieving user contacts. */ public function test_get_user_contacts(): void { $this->resetAfterTest(); // Create some users. $user1 = self::getDataGenerator()->create_user(); // Set as the user. $this->setUser($user1); $user2 = new \stdClass(); $user2->firstname = 'User'; $user2->lastname = 'A'; $user2 = self::getDataGenerator()->create_user($user2); $user3 = new \stdClass(); $user3->firstname = 'User'; $user3->lastname = 'B'; $user3 = self::getDataGenerator()->create_user($user3); $user4 = new \stdClass(); $user4->firstname = 'User'; $user4->lastname = 'C'; $user4 = self::getDataGenerator()->create_user($user4); $user5 = new \stdClass(); $user5->firstname = 'User'; $user5->lastname = 'D'; $user5 = self::getDataGenerator()->create_user($user5); // Add some users as contacts. api::add_contact($user1->id, $user2->id); api::add_contact($user1->id, $user3->id); api::add_contact($user1->id, $user4->id); // Retrieve the contacts. $contacts = api::get_user_contacts($user1->id); // Confirm the data is correct. $this->assertEquals(3, count($contacts)); ksort($contacts); $contact1 = array_shift($contacts); $contact2 = array_shift($contacts); $contact3 = array_shift($contacts); $this->assertEquals($user2->id, $contact1->id); $this->assertEquals(fullname($user2), $contact1->fullname); $this->assertTrue($contact1->iscontact); $this->assertEquals($user3->id, $contact2->id); $this->assertEquals(fullname($user3), $contact2->fullname); $this->assertTrue($contact2->iscontact); $this->assertEquals($user4->id, $contact3->id); $this->assertEquals(fullname($user4), $contact3->fullname); $this->assertTrue($contact3->iscontact); } /** * Tests retrieving conversation messages. */ public function test_get_conversation_messages(): void { $this->resetAfterTest(); // Create some users. $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); // Create conversation. $conversation = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, [$user1->id, $user2->id] ); // The person doing the search. $this->setUser($user1); // Send some messages back and forth. $time = 1; testhelper::send_fake_message_to_conversation($user1, $conversation->id, 'Yo!', $time + 1); testhelper::send_fake_message_to_conversation($user2, $conversation->id, 'Sup mang?', $time + 2); testhelper::send_fake_message_to_conversation($user1, $conversation->id, 'Writing PHPUnit tests!', $time + 3); testhelper::send_fake_message_to_conversation($user1, $conversation->id, 'Word.', $time + 4); // Retrieve the messages. $convmessages = api::get_conversation_messages($user1->id, $conversation->id); // Confirm the conversation id is correct. $this->assertEquals($conversation->id, $convmessages['id']); // Confirm the message data is correct. $messages = $convmessages['messages']; $this->assertEquals(4, count($messages)); $message1 = $messages[0]; $message2 = $messages[1]; $message3 = $messages[2]; $message4 = $messages[3]; $this->assertEquals($user1->id, $message1->useridfrom); $this->assertStringContainsString('Yo!', $message1->text); $this->assertEquals($user2->id, $message2->useridfrom); $this->assertStringContainsString('Sup mang?', $message2->text); $this->assertEquals($user1->id, $message3->useridfrom); $this->assertStringContainsString('Writing PHPUnit tests!', $message3->text); $this->assertEquals($user1->id, $message4->useridfrom); $this->assertStringContainsString('Word.', $message4->text); // Confirm the members data is correct. $members = $convmessages['members']; $this->assertEquals(2, count($members)); } /** * Tests retrieving group conversation messages. */ public function test_get_group_conversation_messages(): void { $this->resetAfterTest(); // Create some users. $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); $user3 = self::getDataGenerator()->create_user(); $user4 = self::getDataGenerator()->create_user(); // Create group conversation. $conversation = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_GROUP, [$user1->id, $user2->id, $user3->id, $user4->id] ); // The person doing the search. $this->setUser($user1); // Send some messages back and forth. $time = 1; testhelper::send_fake_message_to_conversation($user1, $conversation->id, 'Yo!', $time + 1); testhelper::send_fake_message_to_conversation($user2, $conversation->id, 'Sup mang?', $time + 2); testhelper::send_fake_message_to_conversation($user3, $conversation->id, 'Writing PHPUnit tests!', $time + 3); testhelper::send_fake_message_to_conversation($user1, $conversation->id, 'Word.', $time + 4); testhelper::send_fake_message_to_conversation($user2, $conversation->id, 'Yeah!', $time + 5); // Retrieve the messages. $convmessages = api::get_conversation_messages($user1->id, $conversation->id); // Confirm the conversation id is correct. $this->assertEquals($conversation->id, $convmessages['id']); // Confirm the message data is correct. $messages = $convmessages['messages']; $this->assertEquals(5, count($messages)); $message1 = $messages[0]; $message2 = $messages[1]; $message3 = $messages[2]; $message4 = $messages[3]; $message5 = $messages[4]; $this->assertEquals($user1->id, $message1->useridfrom); $this->assertStringContainsString('Yo!', $message1->text); $this->assertEquals($user2->id, $message2->useridfrom); $this->assertStringContainsString('Sup mang?', $message2->text); $this->assertEquals($user3->id, $message3->useridfrom); $this->assertStringContainsString('Writing PHPUnit tests!', $message3->text); $this->assertEquals($user1->id, $message4->useridfrom); $this->assertStringContainsString('Word.', $message4->text); $this->assertEquals($user2->id, $message5->useridfrom); $this->assertStringContainsString('Yeah!', $message5->text); // Confirm the members data is correct. $members = $convmessages['members']; $this->assertEquals(3, count($members)); } /** * Test verifying the sorting param for get_conversation_messages is respected(). */ public function test_get_conversation_messages_sorting(): void { $this->resetAfterTest(); // Create some users. $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); $user3 = self::getDataGenerator()->create_user(); // Create conversations - 1 group and 1 individual. $conversation = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, [$user1->id, $user2->id] ); $conversation2 = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_GROUP, [$user1->id, $user2->id, $user3->id] ); // Send some messages back and forth. $time = 1; $m1id = testhelper::send_fake_message_to_conversation($user1, $conversation->id, 'Yo!', $time + 1); $m2id = testhelper::send_fake_message_to_conversation($user2, $conversation->id, 'Sup mang?', $time + 2); $m3id = testhelper::send_fake_message_to_conversation($user1, $conversation->id, 'Writing PHPUnit tests!', $time + 3); $m4id = testhelper::send_fake_message_to_conversation($user1, $conversation->id, 'Word.', $time + 4); $gm1id = testhelper::send_fake_message_to_conversation($user1, $conversation2->id, 'Yo!', $time + 1); $gm2id = testhelper::send_fake_message_to_conversation($user2, $conversation2->id, 'Sup mang?', $time + 2); $gm3id = testhelper::send_fake_message_to_conversation($user3, $conversation2->id, 'Writing PHPUnit tests!', $time + 3); $gm4id = testhelper::send_fake_message_to_conversation($user1, $conversation2->id, 'Word.', $time + 4); // The person doing the search. $this->setUser($user1); // Retrieve the messages using default sort ('timecreated ASC') and verify ordering. $convmessages = api::get_conversation_messages($user1->id, $conversation->id); $messages = $convmessages['messages']; $this->assertEquals($m1id, $messages[0]->id); $this->assertEquals($m2id, $messages[1]->id); $this->assertEquals($m3id, $messages[2]->id); $this->assertEquals($m4id, $messages[3]->id); // Retrieve the messages without specifying DESC sort ordering, and verify ordering. $convmessages = api::get_conversation_messages($user1->id, $conversation->id, 0, 0, 'timecreated DESC'); $messages = $convmessages['messages']; $this->assertEquals($m1id, $messages[3]->id); $this->assertEquals($m2id, $messages[2]->id); $this->assertEquals($m3id, $messages[1]->id); $this->assertEquals($m4id, $messages[0]->id); // Retrieve the messages using default sort ('timecreated ASC') and verify ordering. $convmessages = api::get_conversation_messages($user1->id, $conversation2->id); $messages = $convmessages['messages']; $this->assertEquals($gm1id, $messages[0]->id); $this->assertEquals($gm2id, $messages[1]->id); $this->assertEquals($gm3id, $messages[2]->id); $this->assertEquals($gm4id, $messages[3]->id); // Retrieve the messages without specifying DESC sort ordering, and verify ordering. $convmessages = api::get_conversation_messages($user1->id, $conversation2->id, 0, 0, 'timecreated DESC'); $messages = $convmessages['messages']; $this->assertEquals($gm1id, $messages[3]->id); $this->assertEquals($gm2id, $messages[2]->id); $this->assertEquals($gm3id, $messages[1]->id); $this->assertEquals($gm4id, $messages[0]->id); } /** * Test retrieving conversation messages by providing a minimum timecreated value. */ public function test_get_conversation_messages_time_from_only(): void { $this->resetAfterTest(); // Create some users. $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); $user3 = self::getDataGenerator()->create_user(); $user4 = self::getDataGenerator()->create_user(); // Create group conversation. $conversation = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_GROUP, [$user1->id, $user2->id, $user3->id, $user4->id] ); // The person doing the search. $this->setUser($user1); // Send some messages back and forth. $time = 1; testhelper::send_fake_message_to_conversation($user1, $conversation->id, 'Message 1', $time + 1); testhelper::send_fake_message_to_conversation($user2, $conversation->id, 'Message 2', $time + 2); testhelper::send_fake_message_to_conversation($user1, $conversation->id, 'Message 3', $time + 3); testhelper::send_fake_message_to_conversation($user3, $conversation->id, 'Message 4', $time + 4); // Retrieve the messages from $time, which should be all of them. $convmessages = api::get_conversation_messages($user1->id, $conversation->id, 0, 0, 'timecreated ASC', $time); // Confirm the conversation id is correct. $this->assertEquals($conversation->id, $convmessages['id']); // Confirm the message data is correct. $messages = $convmessages['messages']; $this->assertEquals(4, count($messages)); $message1 = $messages[0]; $message2 = $messages[1]; $message3 = $messages[2]; $message4 = $messages[3]; $this->assertStringContainsString('Message 1', $message1->text); $this->assertStringContainsString('Message 2', $message2->text); $this->assertStringContainsString('Message 3', $message3->text); $this->assertStringContainsString('Message 4', $message4->text); // Confirm the members data is correct. $members = $convmessages['members']; $this->assertEquals(3, count($members)); // Retrieve the messages from $time + 3, which should only be the 2 last messages. $convmessages = api::get_conversation_messages( $user1->id, $conversation->id, 0, 0, 'timecreated ASC', $time + 3 ); // Confirm the conversation id is correct. $this->assertEquals($conversation->id, $convmessages['id']); // Confirm the message data is correct. $messages = $convmessages['messages']; $this->assertEquals(2, count($messages)); $message1 = $messages[0]; $message2 = $messages[1]; $this->assertStringContainsString('Message 3', $message1->text); $this->assertStringContainsString('Message 4', $message2->text); // Confirm the members data is correct. $members = $convmessages['members']; $this->assertEquals(2, count($members)); } /** * Test retrieving conversation messages by providing a maximum timecreated value. */ public function test_get_conversation_messages_time_to_only(): void { $this->resetAfterTest(); // Create some users. $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); $user3 = self::getDataGenerator()->create_user(); $user4 = self::getDataGenerator()->create_user(); // Create group conversation. $conversation = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_GROUP, [$user1->id, $user2->id, $user3->id, $user4->id] ); // The person doing the search. $this->setUser($user1); // Send some messages back and forth. $time = 1; testhelper::send_fake_message_to_conversation($user1, $conversation->id, 'Message 1', $time + 1); testhelper::send_fake_message_to_conversation($user2, $conversation->id, 'Message 2', $time + 2); testhelper::send_fake_message_to_conversation($user1, $conversation->id, 'Message 3', $time + 3); testhelper::send_fake_message_to_conversation($user3, $conversation->id, 'Message 4', $time + 4); // Retrieve the messages up until $time + 4, which should be all of them. $convmessages = api::get_conversation_messages( $user1->id, $conversation->id, 0, 0, 'timecreated ASC', 0, $time + 4 ); // Confirm the conversation id is correct. $this->assertEquals($conversation->id, $convmessages['id']); // Confirm the message data is correct. $messages = $convmessages['messages']; $this->assertEquals(4, count($messages)); $message1 = $messages[0]; $message2 = $messages[1]; $message3 = $messages[2]; $message4 = $messages[3]; $this->assertStringContainsString('Message 1', $message1->text); $this->assertStringContainsString('Message 2', $message2->text); $this->assertStringContainsString('Message 3', $message3->text); $this->assertStringContainsString('Message 4', $message4->text); // Confirm the members data is correct. $members = $convmessages['members']; $this->assertEquals(3, count($members)); // Retrieve the messages up until $time + 2, which should be the first two. $convmessages = api::get_conversation_messages( $user1->id, $conversation->id, 0, 0, 'timecreated ASC', 0, $time + 2 ); // Confirm the conversation id is correct. $this->assertEquals($conversation->id, $convmessages['id']); // Confirm the message data is correct. $messages = $convmessages['messages']; $this->assertEquals(2, count($messages)); $message1 = $messages[0]; $message2 = $messages[1]; $this->assertStringContainsString('Message 1', $message1->text); $this->assertStringContainsString('Message 2', $message2->text); // Confirm the members data is correct. $members = $convmessages['members']; $this->assertEquals(2, count($members)); } /** * Test retrieving conversation messages by providing a minimum and maximum timecreated value. */ public function test_get_conversation_messages_time_from_and_to(): void { $this->resetAfterTest(); // Create some users. $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); $user3 = self::getDataGenerator()->create_user(); $user4 = self::getDataGenerator()->create_user(); // Create group conversation. $conversation = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_GROUP, [$user1->id, $user2->id, $user3->id, $user4->id] ); // The person doing the search. $this->setUser($user1); // Send some messages back and forth. $time = 1; testhelper::send_fake_message_to_conversation($user1, $conversation->id, 'Message 1', $time + 1); testhelper::send_fake_message_to_conversation($user2, $conversation->id, 'Message 2', $time + 2); testhelper::send_fake_message_to_conversation($user1, $conversation->id, 'Message 3', $time + 3); testhelper::send_fake_message_to_conversation($user3, $conversation->id, 'Message 4', $time + 4); // Retrieve the messages from $time + 2 up until $time + 3, which should be 2nd and 3rd message. $convmessages = api::get_conversation_messages( $user1->id, $conversation->id, 0, 0, 'timecreated ASC', $time + 2, $time + 3 ); // Confirm the conversation id is correct. $this->assertEquals($conversation->id, $convmessages['id']); // Confirm the message data is correct. $messages = $convmessages['messages']; $this->assertEquals(2, count($messages)); $message1 = $messages[0]; $message2 = $messages[1]; $this->assertStringContainsString('Message 2', $message1->text); $this->assertStringContainsString('Message 3', $message2->text); // Confirm the members data is correct. $members = $convmessages['members']; $this->assertEquals(2, count($members)); } /** * Test retrieving conversation messages by providing a limitfrom value. */ public function test_get_conversation_messages_limitfrom_only(): void { $this->resetAfterTest(); // Create some users. $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); $user3 = self::getDataGenerator()->create_user(); $user4 = self::getDataGenerator()->create_user(); // Create group conversation. $conversation = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_GROUP, [$user1->id, $user2->id, $user3->id, $user4->id] ); // The person doing the search. $this->setUser($user1); // Send some messages back and forth. $time = 1; testhelper::send_fake_message_to_conversation($user1, $conversation->id, 'Message 1', $time + 1); testhelper::send_fake_message_to_conversation($user2, $conversation->id, 'Message 2', $time + 2); testhelper::send_fake_message_to_conversation($user1, $conversation->id, 'Message 3', $time + 3); testhelper::send_fake_message_to_conversation($user3, $conversation->id, 'Message 4', $time + 4); // Retrieve the messages from $time, which should be all of them. $convmessages = api::get_conversation_messages($user1->id, $conversation->id, 2); // Confirm the conversation id is correct. $messages = $convmessages['messages']; $this->assertEquals($conversation->id, $convmessages['id']); // Confirm the message data is correct. $this->assertEquals(2, count($messages)); $message1 = $messages[0]; $message2 = $messages[1]; $this->assertStringContainsString('Message 3', $message1->text); $this->assertStringContainsString('Message 4', $message2->text); // Confirm the members data is correct. $members = $convmessages['members']; $this->assertEquals(2, count($members)); } /** * Test retrieving conversation messages by providing a limitnum value. */ public function test_get_conversation_messages_limitnum(): void { $this->resetAfterTest(); // Create some users. $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); $user3 = self::getDataGenerator()->create_user(); $user4 = self::getDataGenerator()->create_user(); // Create group conversation. $conversation = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_GROUP, [$user1->id, $user2->id, $user3->id, $user4->id] ); // The person doing the search. $this->setUser($user1); // Send some messages back and forth. $time = 1; testhelper::send_fake_message_to_conversation($user1, $conversation->id, 'Message 1', $time + 1); testhelper::send_fake_message_to_conversation($user2, $conversation->id, 'Message 2', $time + 2); testhelper::send_fake_message_to_conversation($user1, $conversation->id, 'Message 3', $time + 3); testhelper::send_fake_message_to_conversation($user3, $conversation->id, 'Message 4', $time + 4); // Retrieve the messages from $time, which should be all of them. $convmessages = api::get_conversation_messages($user1->id, $conversation->id, 2, 1); // Confirm the conversation id is correct. $messages = $convmessages['messages']; $this->assertEquals($conversation->id, $convmessages['id']); // Confirm the message data is correct. $messages = $convmessages['messages']; $this->assertEquals(1, count($messages)); $message1 = $messages[0]; $this->assertStringContainsString('Message 3', $message1->text); // Confirm the members data is correct. $members = $convmessages['members']; $this->assertEquals(1, count($members)); } /** * Tests retrieving most recent conversation message. */ public function test_get_most_recent_conversation_message(): void { $this->resetAfterTest(); // Create some users. $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); $user3 = self::getDataGenerator()->create_user(); // Create group conversation. $conversation = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_GROUP, [$user1->id, $user2->id, $user3->id] ); // The person getting the most recent conversation message. $this->setUser($user1); // Send some messages back and forth. $time = 1; testhelper::send_fake_message_to_conversation($user1, $conversation->id, 'Yo!', $time + 1); testhelper::send_fake_message_to_conversation($user2, $conversation->id, 'Sup mang?', $time + 2); testhelper::send_fake_message_to_conversation($user1, $conversation->id, 'Writing PHPUnit tests!', $time + 3); testhelper::send_fake_message_to_conversation($user2, $conversation->id, 'Word.', $time + 4); // Retrieve the most recent messages. $message = api::get_most_recent_conversation_message($conversation->id, $user1->id); // Check the results are correct. $this->assertEquals($user2->id, $message->useridfrom); $this->assertStringContainsString('Word.', $message->text); } /** * Tests checking if a user can mark all messages as read. */ public function test_can_mark_all_messages_as_read(): void { $this->resetAfterTest(); // Set as the admin. $this->setAdminUser(); // Create some users. $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); $user3 = self::getDataGenerator()->create_user(); // Send some messages back and forth. $time = 1; testhelper::send_fake_message($user1, $user2, 'Yo!', 0, $time + 1); testhelper::send_fake_message($user2, $user1, 'Sup mang?', 0, $time + 2); testhelper::send_fake_message($user1, $user2, 'Writing PHPUnit tests!', 0, $time + 3); testhelper::send_fake_message($user2, $user1, 'Word.', 0, $time + 4); $conversationid = api::get_conversation_between_users([$user1->id, $user2->id]); // The admin can do anything. $this->assertTrue(api::can_mark_all_messages_as_read($user1->id, $conversationid)); // Set as the user 1. $this->setUser($user1); // The user can mark the messages as he is in the conversation. $this->assertTrue(api::can_mark_all_messages_as_read($user1->id, $conversationid)); // User 1 can not mark the messages read for user 2. $this->assertFalse(api::can_mark_all_messages_as_read($user2->id, $conversationid)); // This user is not a part of the conversation. $this->assertFalse(api::can_mark_all_messages_as_read($user3->id, $conversationid)); } /** * Tests checking if a user can delete a conversation. */ public function test_can_delete_conversation(): void { $this->resetAfterTest(); // Set as the admin. $this->setAdminUser(); // Create some users. $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); // Send some messages back and forth. $time = 1; testhelper::send_fake_message($user1, $user2, 'Yo!', 0, $time + 1); testhelper::send_fake_message($user2, $user1, 'Sup mang?', 0, $time + 2); testhelper::send_fake_message($user1, $user2, 'Writing PHPUnit tests!', 0, $time + 3); testhelper::send_fake_message($user2, $user1, 'Word.', 0, $time + 4); $conversationid = api::get_conversation_between_users([$user1->id, $user2->id]); // The admin can do anything. $this->assertTrue(api::can_delete_conversation($user1->id, $conversationid)); // Set as the user 1. $this->setUser($user1); // They can delete their own messages. $this->assertTrue(api::can_delete_conversation($user1->id, $conversationid)); // They can't delete someone elses. $this->assertFalse(api::can_delete_conversation($user2->id, $conversationid)); } /** * Tests deleting a conversation by conversation id. */ public function test_delete_conversation_by_id(): void { global $DB; $this->resetAfterTest(); // Create some users. $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); // The person doing the search. $this->setUser($user1); // Get self-conversation. $sc1 = api::get_self_conversation($user1->id); $sc2 = api::get_self_conversation($user2->id); // Send some messages back and forth. $time = 1; $m1id = testhelper::send_fake_message($user1, $user2, 'Yo!', 0, $time + 1); $m2id = testhelper::send_fake_message($user2, $user1, 'Sup mang?', 0, $time + 2); $m3id = testhelper::send_fake_message($user1, $user2, 'Writing PHPUnit tests!', 0, $time + 3); $m4id = testhelper::send_fake_message($user2, $user1, 'Word.', 0, $time + 4); $m5id = testhelper::send_fake_message_to_conversation($user1, $sc1->id, 'Hi to myself!', $time + 5); $m6id = testhelper::send_fake_message_to_conversation($user2, $sc2->id, 'I am talking with myself', $time + 6); $conversationid = api::get_conversation_between_users([$user1->id, $user2->id]); // Delete the individual conversation between user1 and user2 (only for user1). api::delete_conversation_by_id($user1->id, $conversationid); $muas = $DB->get_records('message_user_actions', [], 'timecreated ASC'); $this->assertCount(4, $muas); // Sort by id. ksort($muas); $mua1 = array_shift($muas); $mua2 = array_shift($muas); $mua3 = array_shift($muas); $mua4 = array_shift($muas); $this->assertEquals($user1->id, $mua1->userid); $this->assertEquals($m1id, $mua1->messageid); $this->assertEquals(api::MESSAGE_ACTION_DELETED, $mua1->action); $this->assertEquals($user1->id, $mua2->userid); $this->assertEquals($m2id, $mua2->messageid); $this->assertEquals(api::MESSAGE_ACTION_DELETED, $mua2->action); $this->assertEquals($user1->id, $mua3->userid); $this->assertEquals($m3id, $mua3->messageid); $this->assertEquals(api::MESSAGE_ACTION_DELETED, $mua3->action); $this->assertEquals($user1->id, $mua4->userid); $this->assertEquals($m4id, $mua4->messageid); $this->assertEquals(api::MESSAGE_ACTION_DELETED, $mua4->action); // Delete the self-conversation as user 1. api::delete_conversation_by_id($user1->id, $sc1->id); $muas = $DB->get_records('message_user_actions', [], 'timecreated ASC'); $this->assertCount(5, $muas); // Sort by id. ksort($muas); $mua1 = array_shift($muas); $mua2 = array_shift($muas); $mua3 = array_shift($muas); $mua4 = array_shift($muas); $mua5 = array_shift($muas); // Check only messages in self-conversion for user1 are deleted (self-conversation for user2 shouldn't be removed). $this->assertEquals($user1->id, $mua5->userid); $this->assertEquals($m5id, $mua5->messageid); $this->assertEquals(api::MESSAGE_ACTION_DELETED, $mua5->action); } /** * Tests counting unread conversations. */ public function test_count_unread_conversations(): void { $this->resetAfterTest(true); // Create some users. $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); $user3 = self::getDataGenerator()->create_user(); $user4 = self::getDataGenerator()->create_user(); // The person wanting the conversation count. $this->setUser($user1); // Send some messages back and forth, have some different conversations with different users. testhelper::send_fake_message($user1, $user2, 'Yo!'); testhelper::send_fake_message($user2, $user1, 'Sup mang?'); testhelper::send_fake_message($user1, $user2, 'Writing PHPUnit tests!'); testhelper::send_fake_message($user2, $user1, 'Word.'); testhelper::send_fake_message($user1, $user3, 'Booyah'); testhelper::send_fake_message($user3, $user1, 'Whaaat?'); testhelper::send_fake_message($user1, $user3, 'Nothing.'); testhelper::send_fake_message($user3, $user1, 'Cool.'); testhelper::send_fake_message($user1, $user4, 'Hey mate, you see the new messaging UI in Moodle?'); testhelper::send_fake_message($user4, $user1, 'Yah brah, it\'s pretty rad.'); testhelper::send_fake_message($user1, $user4, 'Dope.'); // Check the amount for the current user. $this->assertEquals(3, api::count_unread_conversations()); // Check the amount for the second user. $this->assertEquals(1, api::count_unread_conversations($user2)); } /** * Tests counting unread conversations where one conversation is disabled. */ public function test_count_unread_conversations_disabled(): void { $this->resetAfterTest(true); // Create some users. $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); $user3 = self::getDataGenerator()->create_user(); $user4 = self::getDataGenerator()->create_user(); // The person wanting the conversation count. $this->setUser($user1); // Send some messages back and forth, have some different conversations with different users. testhelper::send_fake_message($user1, $user2, 'Yo!'); testhelper::send_fake_message($user2, $user1, 'Sup mang?'); testhelper::send_fake_message($user1, $user2, 'Writing PHPUnit tests!'); testhelper::send_fake_message($user2, $user1, 'Word.'); testhelper::send_fake_message($user1, $user3, 'Booyah'); testhelper::send_fake_message($user3, $user1, 'Whaaat?'); testhelper::send_fake_message($user1, $user3, 'Nothing.'); testhelper::send_fake_message($user3, $user1, 'Cool.'); testhelper::send_fake_message($user1, $user4, 'Hey mate, you see the new messaging UI in Moodle?'); testhelper::send_fake_message($user4, $user1, 'Yah brah, it\'s pretty rad.'); testhelper::send_fake_message($user1, $user4, 'Dope.'); // Let's disable the last conversation. $conversationid = api::get_conversation_between_users([$user1->id, $user4->id]); api::disable_conversation($conversationid); // Check that the disabled conversation was not included. $this->assertEquals(2, api::count_unread_conversations()); } /** * Tests deleting a conversation. */ public function test_get_all_message_preferences(): void { $this->resetAfterTest(); $user = self::getDataGenerator()->create_user(); $this->setUser($user); // Set a couple of preferences to test. set_user_preference('message_provider_mod_assign_assign_notification_enabled', 'popup', $user); set_user_preference('message_provider_mod_feedback_submission_enabled', 'email', $user); $processors = get_message_processors(); $providers = message_get_providers_for_user($user->id); $prefs = api::get_all_message_preferences($processors, $providers, $user); $this->assertEquals(1, $prefs->mod_assign_assign_notification_enabled['popup']); $this->assertEquals(1, $prefs->mod_feedback_submission_enabled['email']); } /** * Tests the user can send a message. */ public function test_can_send_message(): void { $this->resetAfterTest(); // Create some users. $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); // Set as the first user. $this->setUser($user1); // With the default privacy setting, users can't message them. $this->assertFalse(api::can_send_message($user2->id, $user1->id)); // Enrol users to the same course. $course = $this->getDataGenerator()->create_course(); $this->getDataGenerator()->enrol_user($user1->id, $course->id); $this->getDataGenerator()->enrol_user($user2->id, $course->id); // After enrolling users to the course, they should be able to message them with the default privacy setting. $this->assertTrue(api::can_send_message($user2->id, $user1->id)); } /** * Tests the user can't send a message without proper capability. */ public function test_can_send_message_without_sendmessage_cap(): void { global $DB; $this->resetAfterTest(); // Create some users. $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); // Set as the user 1. $this->setUser($user1); // Remove the capability to send a message. $roleids = $DB->get_records_menu('role', null, '', 'shortname, id'); unassign_capability( 'moodle/site:sendmessage', $roleids['user'], \context_system::instance() ); // Check that we can not post a message without the capability. $this->assertFalse(api::can_send_message($user2->id, $user1->id)); } /** * Tests the user can send a message when they are contact. */ public function test_can_send_message_when_contact(): void { $this->resetAfterTest(); // Create some users. $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); // Set as the first user. $this->setUser($user1); // Check that we can not send user2 a message. $this->assertFalse(api::can_send_message($user2->id, $user1->id)); // Add users as contacts. api::add_contact($user1->id, $user2->id); // Check that the return result is now true. $this->assertTrue(api::can_send_message($user2->id, $user1->id)); } /** * Tests the user can't send a message if they are not a contact and the user * has requested messages only from contacts. */ public function test_can_send_message_when_not_contact(): void { $this->resetAfterTest(); // Create some users. $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); // Set as the first user. $this->setUser($user1); // Set the second user's preference to not receive messages from non-contacts. set_user_preference('message_blocknoncontacts', api::MESSAGE_PRIVACY_ONLYCONTACTS, $user2->id); // Check that we can not send user 2 a message. $this->assertFalse(api::can_send_message($user2->id, $user1->id)); } /** * Tests the user can't send a message if they are blocked. */ public function test_can_send_message_when_blocked(): void { $this->resetAfterTest(); // Create some users. $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); // Set the user. $this->setUser($user1); // Block the second user. api::block_user($user1->id, $user2->id); // Check that the second user can no longer send the first user a message. $this->assertFalse(api::can_send_message($user1->id, $user2->id)); } /** * Tests the user can send a message when site-wide messaging setting is enabled, * even if they are not a contact and are not members of the same course. */ public function test_can_send_message_site_messaging_setting(): void { $this->resetAfterTest(); // Create some users. $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); // Set as the first user. $this->setUser($user1); // By default, user only can be messaged by contacts and members of any of his/her courses. $this->assertFalse(api::can_send_message($user2->id, $user1->id)); // Enable site-wide messagging privacy setting. The user will be able to receive messages from everybody. set_config('messagingallusers', true); // Set the second user's preference to receive messages from everybody. set_user_preference('message_blocknoncontacts', api::MESSAGE_PRIVACY_SITE, $user2->id); // Check that we can send user2 a message. $this->assertTrue(api::can_send_message($user2->id, $user1->id)); // Disable site-wide messagging privacy setting. The user will be able to receive messages from contacts // and members sharing a course with her. set_config('messagingallusers', false); // As site-wide messaging setting is disabled, the value for user2 will be changed to MESSAGE_PRIVACY_COURSEMEMBER. $this->assertFalse(api::can_send_message($user2->id, $user1->id)); // Enrol users to the same course. $course = $this->getDataGenerator()->create_course(); $this->getDataGenerator()->enrol_user($user1->id, $course->id); $this->getDataGenerator()->enrol_user($user2->id, $course->id); // Check that we can send user2 a message because they are sharing a course. $this->assertTrue(api::can_send_message($user2->id, $user1->id)); // Set the second user's preference to receive messages only from contacts. set_user_preference('message_blocknoncontacts', api::MESSAGE_PRIVACY_ONLYCONTACTS, $user2->id); // Check that now the user2 can't be contacted because user1 is not their contact. $this->assertFalse(api::can_send_message($user2->id, $user1->id)); // Make contacts user1 and user2. api::add_contact($user2->id, $user1->id); // Check that we can send user2 a message because they are contacts. $this->assertTrue(api::can_send_message($user2->id, $user1->id)); } /** * Tests the user with the messageanyuser capability can send a message. */ public function test_can_send_message_with_messageanyuser_cap(): void { global $DB; $this->resetAfterTest(); // Create some users. $teacher1 = self::getDataGenerator()->create_user(); $student1 = self::getDataGenerator()->create_user(); $student2 = self::getDataGenerator()->create_user(); // Create users not enrolled in any course. $user1 = self::getDataGenerator()->create_user(); // Create a course. $course1 = $this->getDataGenerator()->create_course(); // Enrol the users in the course. $this->getDataGenerator()->enrol_user($teacher1->id, $course1->id, 'editingteacher'); $this->getDataGenerator()->enrol_user($student1->id, $course1->id, 'student'); $this->getDataGenerator()->enrol_user($student2->id, $course1->id, 'student'); // Set some student preferences to not receive messages from non-contacts. set_user_preference('message_blocknoncontacts', api::MESSAGE_PRIVACY_ONLYCONTACTS, $student1->id); // Check that we can send student1 a message because teacher has the messageanyuser cap by default. $this->assertTrue(api::can_send_message($student1->id, $teacher1->id)); // Check that the teacher can't contact user1 because it's not his teacher. $this->assertFalse(api::can_send_message($user1->id, $teacher1->id)); // Remove the messageanyuser capability from the course1 for teachers. $coursecontext = \context_course::instance($course1->id); $teacherrole = $DB->get_record('role', ['shortname' => 'editingteacher']); assign_capability('moodle/site:messageanyuser', CAP_PROHIBIT, $teacherrole->id, $coursecontext->id); $coursecontext->mark_dirty(); // Check that we can't send user1 a message because they are not contacts. $this->assertFalse(api::can_send_message($student1->id, $teacher1->id)); // However, teacher can message student2 because they are sharing a course. $this->assertTrue(api::can_send_message($student2->id, $teacher1->id)); } /** * Tests the user when blocked will not be able to send messages if they are blocked. */ public function test_can_send_message_even_if_blocked(): void { $this->resetAfterTest(); $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); $this->assertFalse(api::can_send_message($user2->id, $user1->id, true)); } /** * Tests the user will be able to send a message even if they are blocked as the user * has the capability 'moodle/site:messageanyuser'. */ public function test_can_send_message_even_if_blocked_with_message_any_user_cap(): void { global $DB; $this->resetAfterTest(); $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); $authenticateduserrole = $DB->get_record('role', ['shortname' => 'user']); assign_capability('moodle/site:messageanyuser', CAP_ALLOW, $authenticateduserrole->id, \context_system::instance(), true); $this->assertTrue(api::can_send_message($user2->id, $user1->id, true)); } /** * Tests the user will be able to send a message even if they are blocked as the user * has the capability 'moodle/site:readallmessages'. */ public function test_can_send_message_even_if_blocked_with_read_all_message_cap(): void { global $DB; $this->resetAfterTest(); $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); $authenticateduserrole = $DB->get_record('role', ['shortname' => 'user']); assign_capability('moodle/site:readallmessages', CAP_ALLOW, $authenticateduserrole->id, \context_system::instance(), true); $this->assertTrue(api::can_send_message($user2->id, $user1->id, true)); } /** * Tests the user can not always send a message if they are blocked just because they share a course. */ public function test_can_send_message_even_if_blocked_shared_course(): void { $this->resetAfterTest(); // Create some users. $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); $course = self::getDataGenerator()->create_course(); $this->getDataGenerator()->enrol_user($user1->id, $course->id); $this->getDataGenerator()->enrol_user($user2->id, $course->id); $this->assertFalse(api::can_send_message($user2->id, $user1->id, true)); } /** * Tests the user can always send a message even if they are blocked because they share a course and * have the capability 'moodle/site:messageanyuser' at the course context. */ public function test_can_send_message_even_if_blocked_shared_course_with_message_any_user_cap(): void { global $DB; $this->resetAfterTest(); $editingteacherrole = $DB->get_record('role', ['shortname' => 'editingteacher']); $teacher = self::getDataGenerator()->create_user(); $student = self::getDataGenerator()->create_user(); $course = self::getDataGenerator()->create_course(); $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $editingteacherrole->id); $this->getDataGenerator()->enrol_user($student->id, $course->id); assign_capability( 'moodle/site:messageanyuser', CAP_ALLOW, $editingteacherrole->id, \context_course::instance($course->id), true ); // Check that the second user can no longer send the first user a message. $this->assertTrue(api::can_send_message($student->id, $teacher->id, true)); } /** * Verify the expected behaviour of the can_send_message_to_conversation() method for authenticated users with default settings. */ public function test_can_send_message_to_conversation_basic(): void { $this->resetAfterTest(); // Create some users. $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); $user3 = self::getDataGenerator()->create_user(); // Create an individual conversation between user1 and user2. $ic1 = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, [ $user1->id, $user2->id, ] ); // Create a group conversation between and users 1, 2 and 3. $gc1 = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_GROUP, [ $user1->id, $user2->id, $user3->id, ] ); // Get a self-conversation for user1. $sc1 = api::get_self_conversation($user1->id); // For group conversations, there are no user privacy checks, so only membership in the conversation is needed. $this->assertTrue(api::can_send_message_to_conversation($user1->id, $gc1->id)); // For self conversations, there are no user privacy checks, so only membership in the conversation is needed. $this->assertTrue(api::can_send_message_to_conversation($user1->id, $sc1->id)); // For individual conversations, the default privacy setting of 'only contacts and course members' applies. // Users are not in the same course, nor are they contacts, so messages cannot be sent. $this->assertFalse(api::can_send_message_to_conversation($user1->id, $ic1->id)); // Enrol the users into the same course. $course = $this->getDataGenerator()->create_course(); $this->getDataGenerator()->enrol_user($user1->id, $course->id); $this->getDataGenerator()->enrol_user($user2->id, $course->id); // After enrolling users to the course, they should be able to message them with the default privacy setting. $this->assertTrue(api::can_send_message_to_conversation($user1->id, $ic1->id)); } /** * Verify the behaviour of can_send_message_to_conversation() for authenticated users without the sendmessage capability. */ public function test_can_send_message_to_conversation_sendmessage_cap(): void { global $DB; $this->resetAfterTest(); $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); $user3 = self::getDataGenerator()->create_user(); // Enrol the users into the same course. $course = $this->getDataGenerator()->create_course(); $this->getDataGenerator()->enrol_user($user1->id, $course->id); $this->getDataGenerator()->enrol_user($user2->id, $course->id); $this->getDataGenerator()->enrol_user($user3->id, $course->id); // Create an individual conversation between user1 and user2. $ic1 = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, [ $user1->id, $user2->id, ] ); // Group conversation between and users 1, 2 and 3. $gc1 = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_GROUP, [ $user1->id, $user2->id, $user3->id, ] ); // Default settings - user1 can send a message to both conversations. $this->assertTrue(api::can_send_message_to_conversation($user1->id, $ic1->id)); $this->assertTrue(api::can_send_message_to_conversation($user1->id, $gc1->id)); // Remove the capability to send a message. $roleids = $DB->get_records_menu('role', null, '', 'shortname, id'); unassign_capability('moodle/site:sendmessage', $roleids['user'], \context_system::instance()); // Verify that a user cannot send a message to either an individual or a group conversation. $this->assertFalse(api::can_send_message_to_conversation($user1->id, $ic1->id)); $this->assertFalse(api::can_send_message_to_conversation($user1->id, $gc1->id)); } /** * Verify the behaviour of can_send_message_to_conversation() for authenticated users without the messageanyuser capability. */ public function test_can_send_message_to_conversation_messageanyuser_cap(): void { global $DB; $this->resetAfterTest(); $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); $user3 = self::getDataGenerator()->create_user(); // Enrol the users into the same course. $course = $this->getDataGenerator()->create_course(); $this->getDataGenerator()->enrol_user($user1->id, $course->id); $this->getDataGenerator()->enrol_user($user2->id, $course->id); $this->getDataGenerator()->enrol_user($user3->id, $course->id); // Create an individual conversation between user1 and user2. $ic1 = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, [ $user1->id, $user2->id, ] ); // Group conversation between and users 1, 2 and 3. $gc1 = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_GROUP, [ $user1->id, $user2->id, $user3->id, ] ); // Update the message preference for user2, so they can only be messaged by contacts. set_user_preference('message_blocknoncontacts', api::MESSAGE_PRIVACY_ONLYCONTACTS, $user2->id); // Verify that the user cannot be contacted in the individual conversation and that groups are unaffected. $this->assertFalse(api::can_send_message_to_conversation($user1->id, $ic1->id)); $this->assertTrue(api::can_send_message_to_conversation($user1->id, $gc1->id)); // Assign the 'messageanyuser' capability to user1 at system context. $systemcontext = \context_system::instance(); $authenticateduser = $DB->get_record('role', ['shortname' => 'user']); assign_capability('moodle/site:messageanyuser', CAP_ALLOW, $authenticateduser->id, $systemcontext->id); // Check that user1 can now message user2 due to the capability, and that group conversations is again unaffected. $this->assertTrue(api::can_send_message_to_conversation($user1->id, $ic1->id)); $this->assertTrue(api::can_send_message_to_conversation($user1->id, $gc1->id)); } /** * Test verifying that users cannot send messages to conversations they are not a part of. */ public function test_can_send_message_to_conversation_non_member(): void { $this->resetAfterTest(); // Create some users. $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); $user3 = self::getDataGenerator()->create_user(); $user4 = self::getDataGenerator()->create_user(); // Enrol the users into the same course. $course = $this->getDataGenerator()->create_course(); $this->getDataGenerator()->enrol_user($user1->id, $course->id); $this->getDataGenerator()->enrol_user($user2->id, $course->id); $this->getDataGenerator()->enrol_user($user3->id, $course->id); $this->getDataGenerator()->enrol_user($user4->id, $course->id); // Create an individual conversation between user1 and user2. $ic1 = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, [ $user1->id, $user2->id, ] ); // Create a group conversation between and users 1, 2 and 3. $gc1 = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_GROUP, [ $user1->id, $user2->id, $user3->id, ] ); // Get a self-conversation for user1. $sc1 = api::get_self_conversation($user1->id); // Verify, non members cannot send a message. $this->assertFalse(api::can_send_message_to_conversation($user4->id, $gc1->id)); $this->assertFalse(api::can_send_message_to_conversation($user4->id, $ic1->id)); $this->assertFalse(api::can_send_message_to_conversation($user4->id, $sc1->id)); } /** * Test verifying the behaviour of the can_send_message_to_conversation method when privacy is set to contacts only. */ public function test_can_send_message_to_conversation_privacy_contacts_only(): void { $this->resetAfterTest(); // Create some users. $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); $user3 = self::getDataGenerator()->create_user(); // Create an individual conversation between user1 and user2. $ic1 = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, [ $user1->id, $user2->id, ] ); // Create a group conversation between and users 1, 2 and 3. $gc1 = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_GROUP, [ $user1->id, $user2->id, $user3->id, ] ); // Set the message privacy preference to 'contacts only' for user 2. set_user_preference('message_blocknoncontacts', api::MESSAGE_PRIVACY_ONLYCONTACTS, $user2->id); // Verify that user1 cannot send a message to the individual conversation, but that the group conversation is unaffected. $this->assertFalse(api::can_send_message_to_conversation($user1->id, $ic1->id)); $this->assertTrue(api::can_send_message_to_conversation($user1->id, $gc1->id)); // Now, simulate a contact request (and approval) between user1 and user2. api::create_contact_request($user1->id, $user2->id); api::confirm_contact_request($user1->id, $user2->id); // Verify user1 can now message user2 again via their individual conversation. $this->assertTrue(api::can_send_message_to_conversation($user1->id, $ic1->id)); } /** * Test verifying the behaviour of the can_send_message_to_conversation method when privacy is set to contacts / course members. */ public function test_can_send_message_to_conversation_privacy_contacts_course(): void { $this->resetAfterTest(); // Create some users. $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); $user3 = self::getDataGenerator()->create_user(); // Set the message privacy preference to 'contacts + course members' for user 2. set_user_preference('message_blocknoncontacts', api::MESSAGE_PRIVACY_COURSEMEMBER, $user2->id); // Create an individual conversation between user1 and user2. $ic1 = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, [ $user1->id, $user2->id, ] ); // Create a group conversation between and users 1, 2 and 3. $gc1 = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_GROUP, [ $user1->id, $user2->id, $user3->id, ] ); // Verify that users in a group conversation can message one another (i.e. privacy controls ignored). $this->assertTrue(api::can_send_message_to_conversation($user1->id, $gc1->id)); // Verify that user1 can not message user2 unless they are either contacts, or share a course. $this->assertFalse(api::can_send_message_to_conversation($user1->id, $ic1->id)); // Enrol the users into the same course. $course = $this->getDataGenerator()->create_course(); $this->getDataGenerator()->enrol_user($user1->id, $course->id); $this->getDataGenerator()->enrol_user($user2->id, $course->id); $this->getDataGenerator()->enrol_user($user3->id, $course->id); // Verify that user1 can send a message to user2, based on the shared course, without being a contact. $this->assertFalse(api::is_contact($user1->id, $user2->id)); $this->assertTrue(api::can_send_message_to_conversation($user1->id, $ic1->id)); } /** * Test verifying the behaviour of the can_send_message_to_conversation method when privacy is set to any user. */ public function test_can_send_message_to_conversation_privacy_sitewide(): void { $this->resetAfterTest(); // Create some users. $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); $user3 = self::getDataGenerator()->create_user(); // Create an individual conversation between user1 and user2. $ic1 = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, [ $user1->id, $user2->id, ] ); // Create a group conversation between and users 1, 2 and 3. $gc1 = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_GROUP, [ $user1->id, $user2->id, $user3->id, ] ); // By default, the messaging privacy dictates that users can only be contacted by contacts, and members of their courses. // Verify also, that groups are not restricted in this way. $this->assertFalse(api::can_send_message_to_conversation($user1->id, $ic1->id)); $this->assertTrue(api::can_send_message_to_conversation($user1->id, $gc1->id)); // Enable site-wide messagging privacy setting. // This enables a privacy option for users, allowing them to choose to be contactable by anybody on the site. set_config('messagingallusers', true); // Set the second user's preference to receive messages from everybody. set_user_preference('message_blocknoncontacts', api::MESSAGE_PRIVACY_SITE, $user2->id); // Check that user1 can send user2 a message, and that the group conversation is unaffected. $this->assertTrue(api::can_send_message_to_conversation($user1->id, $ic1->id)); $this->assertTrue(api::can_send_message_to_conversation($user1->id, $gc1->id)); // Disable site-wide messagging privacy setting. The user will be able to receive messages from contacts // and members sharing a course with her. set_config('messagingallusers', false); // As site-wide messaging setting is disabled, the value for user2 will be changed to MESSAGE_PRIVACY_COURSEMEMBER. // Verify also that the group conversation is unaffected. $this->assertFalse(api::can_send_message_to_conversation($user1->id, $ic1->id)); $this->assertTrue(api::can_send_message_to_conversation($user1->id, $gc1->id)); } /** * Test verifying the behaviour of the can_send_message_to_conversation method when a user is blocked. */ public function test_can_send_message_to_conversation_when_blocked(): void { $this->resetAfterTest(); $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); $user3 = self::getDataGenerator()->create_user(); // Create an individual conversation between user1 and user2. $ic1 = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, [ $user1->id, $user2->id, ] ); // Create a group conversation between and users 1, 2 and 3. $gc1 = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_GROUP, [ $user1->id, $user2->id, $user3->id, ] ); // Enrol the users into the same course. $course = $this->getDataGenerator()->create_course(); $this->getDataGenerator()->enrol_user($user1->id, $course->id); $this->getDataGenerator()->enrol_user($user2->id, $course->id); $this->getDataGenerator()->enrol_user($user3->id, $course->id); // Block the second user. api::block_user($user1->id, $user2->id); // Check that user2 can not send user1 a message in their individual conversation. $this->assertFalse(api::can_send_message_to_conversation($user2->id, $ic1->id)); // Verify that group conversations are unaffected. $this->assertTrue(api::can_send_message_to_conversation($user1->id, $gc1->id)); $this->assertTrue(api::can_send_message_to_conversation($user2->id, $gc1->id)); } /** * Tests get_user_privacy_messaging_preference method. */ public function test_get_user_privacy_messaging_preference(): void { $this->resetAfterTest(); // Create some users. $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); $user3 = self::getDataGenerator()->create_user(); // Enable site-wide messagging privacy setting. The user will be able to receive messages from everybody. set_config('messagingallusers', true); // Set some user preferences. set_user_preference('message_blocknoncontacts', api::MESSAGE_PRIVACY_SITE, $user1->id); set_user_preference('message_blocknoncontacts', api::MESSAGE_PRIVACY_ONLYCONTACTS, $user2->id); // Check the returned value for each user. $this->assertEquals( api::MESSAGE_PRIVACY_SITE, api::get_user_privacy_messaging_preference($user1->id) ); $this->assertEquals( api::MESSAGE_PRIVACY_ONLYCONTACTS, api::get_user_privacy_messaging_preference($user2->id) ); $this->assertEquals( api::MESSAGE_PRIVACY_SITE, api::get_user_privacy_messaging_preference($user3->id) ); // Disable site-wide messagging privacy setting. The user will be able to receive messages from members of their course. set_config('messagingallusers', false); // Check the returned value for each user. $this->assertEquals( api::MESSAGE_PRIVACY_COURSEMEMBER, api::get_user_privacy_messaging_preference($user1->id) ); $this->assertEquals( api::MESSAGE_PRIVACY_ONLYCONTACTS, api::get_user_privacy_messaging_preference($user2->id) ); $this->assertEquals( api::MESSAGE_PRIVACY_COURSEMEMBER, api::get_user_privacy_messaging_preference($user3->id) ); } /* * Tes get_message_processor api. */ public function test_get_message_processor(): void { $this->resetAfterTest(); $processors = get_message_processors(true); if (empty($processors)) { $this->markTestSkipped("No message processors found"); } $name = key($processors); $processor = current($processors); $testprocessor = api::get_message_processor($name); $this->assertEquals($processor->name, $testprocessor->name); $this->assertEquals($processor->enabled, $testprocessor->enabled); $this->assertEquals($processor->available, $testprocessor->available); $this->assertEquals($processor->configured, $testprocessor->configured); // Disable processor and test. api::update_processor_status($testprocessor, 0); $testprocessor = api::get_message_processor($name, true); $this->assertEmpty($testprocessor); $testprocessor = api::get_message_processor($name); $this->assertEquals($processor->name, $testprocessor->name); $this->assertEquals(0, $testprocessor->enabled); // Enable again and test. api::update_processor_status($testprocessor, 1); $testprocessor = api::get_message_processor($name, true); $this->assertEquals($processor->name, $testprocessor->name); $this->assertEquals(1, $testprocessor->enabled); $testprocessor = api::get_message_processor($name); $this->assertEquals($processor->name, $testprocessor->name); $this->assertEquals(1, $testprocessor->enabled); } /** * Test method update_processor_status. */ public function test_update_processor_status(): void { $this->resetAfterTest(); $processors = get_message_processors(); if (empty($processors)) { $this->markTestSkipped("No message processors found"); } $name = key($processors); $testprocessor = current($processors); // Enable. api::update_processor_status($testprocessor, 1); $testprocessor = api::get_message_processor($name); $this->assertEquals(1, $testprocessor->enabled); // Disable. api::update_processor_status($testprocessor, 0); $testprocessor = api::get_message_processor($name); $this->assertEquals(0, $testprocessor->enabled); // Enable again. api::update_processor_status($testprocessor, 1); $testprocessor = api::get_message_processor($name); $this->assertEquals(1, $testprocessor->enabled); } /** * Test method is_user_enabled. */ public function is_user_enabled() { $processors = get_message_processors(); if (empty($processors)) { $this->markTestSkipped("No message processors found"); } $name = key($processors); $testprocessor = current($processors); // Enable. api::update_processor_status($testprocessor, 1); $status = api::is_processor_enabled($name); $this->assertEquals(1, $status); // Disable. api::update_processor_status($testprocessor, 0); $status = api::is_processor_enabled($name); $this->assertEquals(0, $status); // Enable again. api::update_processor_status($testprocessor, 1); $status = api::is_processor_enabled($name); $this->assertEquals(1, $status); } /** * Test returning blocked users. */ public function test_get_blocked_users(): void { global $USER; $this->resetAfterTest(); // Set this user as the admin. $this->setAdminUser(); // Create a user to add to the admin's contact list. $user1 = $this->getDataGenerator()->create_user(); $user2 = $this->getDataGenerator()->create_user(); // Add users to the admin's contact list. api::block_user($USER->id, $user2->id); $this->assertCount(1, api::get_blocked_users($USER->id)); // Block other user. api::block_user($USER->id, $user1->id); $this->assertCount(2, api::get_blocked_users($USER->id)); // Test deleting users. delete_user($user1); $this->assertCount(1, api::get_blocked_users($USER->id)); } /** * Test marking a message as read. */ public function test_mark_message_as_read(): void { global $DB; $this->resetAfterTest(); $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); testhelper::send_fake_message($user1, $user2); $m2id = testhelper::send_fake_message($user1, $user2); testhelper::send_fake_message($user2, $user1); $m4id = testhelper::send_fake_message($user2, $user1); $m2 = $DB->get_record('messages', ['id' => $m2id]); $m4 = $DB->get_record('messages', ['id' => $m4id]); api::mark_message_as_read($user2->id, $m2, 11); api::mark_message_as_read($user1->id, $m4, 12); // Confirm there are two user actions. $muas = $DB->get_records('message_user_actions', [], 'timecreated ASC'); $this->assertEquals(2, count($muas)); // Confirm they are correct. $mua1 = array_shift($muas); $mua2 = array_shift($muas); // Confirm first action. $this->assertEquals($user2->id, $mua1->userid); $this->assertEquals($m2id, $mua1->messageid); $this->assertEquals(api::MESSAGE_ACTION_READ, $mua1->action); $this->assertEquals(11, $mua1->timecreated); // Confirm second action. $this->assertEquals($user1->id, $mua2->userid); $this->assertEquals($m4id, $mua2->messageid); $this->assertEquals(api::MESSAGE_ACTION_READ, $mua2->action); $this->assertEquals(12, $mua2->timecreated); } /** * Test marking a notification as read. */ public function test_mark_notification_as_read(): void { global $DB; $this->resetAfterTest(); $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); testhelper::send_fake_message($user1, $user2, 'Notification 1', 1); $n2id = testhelper::send_fake_message($user1, $user2, 'Notification 2', 1); testhelper::send_fake_message($user2, $user1, 'Notification 3', 1); $n4id = testhelper::send_fake_message($user2, $user1, 'Notification 4', 1); $n2 = $DB->get_record('notifications', ['id' => $n2id]); $n4 = $DB->get_record('notifications', ['id' => $n4id]); api::mark_notification_as_read($n2, 11); api::mark_notification_as_read($n4, 12); // Retrieve the notifications. $n2 = $DB->get_record('notifications', ['id' => $n2id]); $n4 = $DB->get_record('notifications', ['id' => $n4id]); // Confirm they have been marked as read. $this->assertEquals(11, $n2->timeread); $this->assertEquals(12, $n4->timeread); } /** * Test a conversation is not returned if there is none. */ public function test_get_conversation_between_users_no_conversation(): void { $this->resetAfterTest(); $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); $this->assertFalse(api::get_conversation_between_users([$user1->id, $user2->id])); } /** * Test count_conversation_members for non existing conversation. */ public function test_count_conversation_members_no_existing_conversation(): void { $this->resetAfterTest(); $this->assertEquals( 0, api::count_conversation_members(0) ); } /** * Test count_conversation_members for existing conversation. */ public function test_count_conversation_members_existing_conversation(): void { $this->resetAfterTest(); $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); $conversation = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, [ $user1->id, $user2->id, ] ); $conversationid = $conversation->id; $this->assertEquals( 2, api::count_conversation_members($conversationid) ); } /** * Test add_members_to_conversation for an individual conversation. */ public function test_add_members_to_individual_conversation(): void { $this->resetAfterTest(); $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); $user3 = self::getDataGenerator()->create_user(); $conversation = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, [ $user1->id, $user2->id, ] ); $conversationid = $conversation->id; $this->expectException('moodle_exception'); api::add_members_to_conversation([$user3->id], $conversationid); } /** * Test add_members_to_conversation for existing conversation. */ public function test_add_members_to_existing_conversation(): void { $this->resetAfterTest(); $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); $user3 = self::getDataGenerator()->create_user(); $conversation = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_GROUP, [ $user1->id, $user2->id, ] ); $conversationid = $conversation->id; $this->assertNull(api::add_members_to_conversation([$user3->id], $conversationid)); $this->assertEquals( 3, api::count_conversation_members($conversationid) ); } /** * Test add_members_to_conversation for non existing conversation. */ public function test_add_members_to_no_existing_conversation(): void { $this->resetAfterTest(); $user1 = self::getDataGenerator()->create_user(); // Throw dml_missing_record_exception for non existing conversation. $this->expectException('dml_missing_record_exception'); api::add_members_to_conversation([$user1->id], 0); } /** * Test add_member_to_conversation for non existing user. */ public function test_add_members_to_no_existing_user(): void { $this->resetAfterTest(); $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); $conversation = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_GROUP, [ $user1->id, $user2->id, ] ); $conversationid = $conversation->id; // Don't throw an error for non existing user, but don't add it as a member. $this->assertNull(api::add_members_to_conversation([0], $conversationid)); $this->assertEquals( 2, api::count_conversation_members($conversationid) ); } /** * Test add_members_to_conversation for current conversation member. */ public function test_add_members_to_current_conversation_member(): void { $this->resetAfterTest(); $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); $conversation = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_GROUP, [ $user1->id, $user2->id, ] ); $conversationid = $conversation->id; // Don't add as a member a user that is already conversation member. $this->assertNull(api::add_members_to_conversation([$user1->id], $conversationid)); $this->assertEquals( 2, api::count_conversation_members($conversationid) ); } /** * Test add_members_to_conversation for multiple users. */ public function test_add_members_for_multiple_users(): void { $this->resetAfterTest(); $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); $user3 = self::getDataGenerator()->create_user(); $user4 = self::getDataGenerator()->create_user(); $conversation = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_GROUP, [ $user1->id, $user2->id, ] ); $conversationid = $conversation->id; $this->assertNull(api::add_members_to_conversation([$user3->id, $user4->id], $conversationid)); $this->assertEquals( 4, api::count_conversation_members($conversationid) ); } /** * Test add_members_to_conversation for multiple users, included non existing and current conversation members */ public function test_add_members_for_multiple_not_valid_users(): void { $this->resetAfterTest(); $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); $user3 = self::getDataGenerator()->create_user(); $conversation = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_GROUP, [ $user1->id, $user2->id, ] ); $conversationid = $conversation->id; // Don't throw errors, but don't add as members users don't exist or are already conversation members. $this->assertNull(api::add_members_to_conversation([$user3->id, $user1->id, 0], $conversationid)); $this->assertEquals( 3, api::count_conversation_members($conversationid) ); } /** * Test remove_members_from_conversation for individual conversation. */ public function test_remove_members_from_individual_conversation(): void { $this->resetAfterTest(); $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); $conversation = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, [ $user1->id, $user2->id, ] ); $conversationid = $conversation->id; $this->expectException('moodle_exception'); api::remove_members_from_conversation([$user1->id], $conversationid); } /** * Test remove_members_from_conversation for existing conversation. */ public function test_remove_members_from_existing_conversation(): void { $this->resetAfterTest(); $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); $conversation = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_GROUP, [ $user1->id, $user2->id, ] ); $conversationid = $conversation->id; $this->assertNull(api::remove_members_from_conversation([$user1->id], $conversationid)); $this->assertEquals( 1, api::count_conversation_members($conversationid) ); } /** * Test remove_members_from_conversation for non existing conversation. */ public function test_remove_members_from_no_existing_conversation(): void { $this->resetAfterTest(); $user1 = self::getDataGenerator()->create_user(); // Throw dml_missing_record_exception for non existing conversation. $this->expectException('dml_missing_record_exception'); api::remove_members_from_conversation([$user1->id], 0); } /** * Test remove_members_from_conversation for non existing user. */ public function test_remove_members_for_no_existing_user(): void { $this->resetAfterTest(); $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); $conversation = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_GROUP, [ $user1->id, $user2->id, ] ); $conversationid = $conversation->id; $this->assertNull(api::remove_members_from_conversation([0], $conversationid)); $this->assertEquals( 2, api::count_conversation_members($conversationid) ); } /** * Test remove_members_from_conversation for multiple users. */ public function test_remove_members_for_multiple_users(): void { $this->resetAfterTest(); $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); $user3 = self::getDataGenerator()->create_user(); $user4 = self::getDataGenerator()->create_user(); $conversation = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_GROUP, [ $user1->id, $user2->id, ] ); $conversationid = $conversation->id; $this->assertNull(api::add_members_to_conversation([$user3->id, $user4->id], $conversationid)); $this->assertNull(api::remove_members_from_conversation([$user3->id, $user4->id], $conversationid)); $this->assertEquals( 2, api::count_conversation_members($conversationid) ); } /** * Test remove_members_from_conversation for multiple non valid users. */ public function test_remove_members_for_multiple_no_valid_users(): void { $this->resetAfterTest(); $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); $user3 = self::getDataGenerator()->create_user(); $user4 = self::getDataGenerator()->create_user(); $conversation = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_GROUP, [ $user1->id, $user2->id, ] ); $conversationid = $conversation->id; $this->assertNull(api::add_members_to_conversation([$user3->id], $conversationid)); $this->assertNull( api::remove_members_from_conversation([$user2->id, $user3->id, $user4->id, 0], $conversationid) ); $this->assertEquals( 1, api::count_conversation_members($conversationid) ); } /** * Test count_conversation_members for empty conversation. */ public function test_count_conversation_members_empty_conversation(): void { $this->resetAfterTest(); $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); $conversation = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_GROUP, [ $user1->id, $user2->id, ] ); $conversationid = $conversation->id; $this->assertNull(api::remove_members_from_conversation([$user1->id, $user2->id], $conversationid)); $this->assertEquals( 0, api::count_conversation_members($conversationid) ); } /** * Test can create a contact request. */ public function test_can_create_contact_request(): void { global $CFG; $this->resetAfterTest(); $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); // Disable messaging. $CFG->messaging = 0; $this->assertFalse(api::can_create_contact($user1->id, $user2->id)); // Re-enable messaging. $CFG->messaging = 1; // Allow users to message anyone site-wide. $CFG->messagingallusers = 1; $this->assertTrue(api::can_create_contact($user1->id, $user2->id)); // Disallow users from messaging anyone site-wide. $CFG->messagingallusers = 0; $this->assertFalse(api::can_create_contact($user1->id, $user2->id)); // Put the users in the same course so a contact request should be possible. $course = self::getDataGenerator()->create_course(); $this->getDataGenerator()->enrol_user($user1->id, $course->id); $this->getDataGenerator()->enrol_user($user2->id, $course->id); $this->assertTrue(api::can_create_contact($user1->id, $user2->id)); } /** * Test creating a contact request. */ public function test_create_contact_request(): void { global $DB; $this->resetAfterTest(); $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); $sink = $this->redirectMessages(); $request = api::create_contact_request($user1->id, $user2->id); $messages = $sink->get_messages(); $sink->close(); // Test customdata. $customdata = json_decode($messages[0]->customdata); $this->assertObjectHasProperty('notificationiconurl', $customdata); $this->assertObjectHasProperty('actionbuttons', $customdata); $this->assertCount(2, (array) $customdata->actionbuttons); $this->assertEquals($user1->id, $request->userid); $this->assertEquals($user2->id, $request->requesteduserid); } /** * Test confirming a contact request. */ public function test_confirm_contact_request(): void { global $DB; $this->resetAfterTest(); $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); api::create_contact_request($user1->id, $user2->id); api::confirm_contact_request($user1->id, $user2->id); $this->assertEquals(0, $DB->count_records('message_contact_requests')); $contact = $DB->get_records('message_contacts'); $this->assertCount(1, $contact); $contact = reset($contact); $this->assertEquals($user1->id, $contact->userid); $this->assertEquals($user2->id, $contact->contactid); } /** * Test declining a contact request. */ public function test_decline_contact_request(): void { global $DB; $this->resetAfterTest(); $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); api::create_contact_request($user1->id, $user2->id); api::decline_contact_request($user1->id, $user2->id); $this->assertEquals(0, $DB->count_records('message_contact_requests')); $this->assertEquals(0, $DB->count_records('message_contacts')); } /** * Test retrieving contact requests. */ public function test_get_contact_requests(): void { global $PAGE; $this->resetAfterTest(); $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); $user3 = self::getDataGenerator()->create_user(); // Block one user, their request should not show up. api::block_user($user1->id, $user3->id); api::create_contact_request($user2->id, $user1->id); api::create_contact_request($user3->id, $user1->id); $requests = api::get_contact_requests($user1->id); $this->assertCount(1, $requests); $request = reset($requests); $userpicture = new \user_picture($user2); $profileimageurl = $userpicture->get_url($PAGE)->out(false); $this->assertEquals($user2->id, $request->id); $this->assertEquals(fullname($user2), $request->fullname); $this->assertObjectHasProperty('profileimageurl', $request); $this->assertObjectHasProperty('profileimageurlsmall', $request); $this->assertObjectHasProperty('isonline', $request); $this->assertObjectHasProperty('showonlinestatus', $request); $this->assertObjectHasProperty('isblocked', $request); $this->assertObjectHasProperty('iscontact', $request); } /** * Test the get_contact_requests() function when the user has blocked the sender of the request. */ public function test_get_contact_requests_blocked_sender(): void { $this->resetAfterTest(); $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); // User1 blocks User2. api::block_user($user1->id, $user2->id); // User2 tries to add User1 as a contact. api::create_contact_request($user2->id, $user1->id); // Verify we don't see the contact request from the blocked user User2 in the requests for User1. $requests = api::get_contact_requests($user1->id); $this->assertEmpty($requests); } /** * Test getting contact requests when there are none. */ public function test_get_contact_requests_no_requests(): void { $this->resetAfterTest(); $user1 = self::getDataGenerator()->create_user(); $requests = api::get_contact_requests($user1->id); $this->assertEmpty($requests); } /** * Test getting contact requests with limits. */ public function test_get_contact_requests_with_limits(): void { $this->resetAfterTest(); $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); $user3 = self::getDataGenerator()->create_user(); api::create_contact_request($user2->id, $user1->id); api::create_contact_request($user3->id, $user1->id); $requests = api::get_contact_requests($user1->id, 0, 1); $this->assertCount(1, $requests); } /** * Test adding contacts. */ public function test_add_contact(): void { global $DB; $this->resetAfterTest(); $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); api::add_contact($user1->id, $user2->id); $contact = $DB->get_records('message_contacts'); $this->assertCount(1, $contact); $contact = reset($contact); $this->assertEquals($user1->id, $contact->userid); $this->assertEquals($user2->id, $contact->contactid); } /** * Test removing contacts. */ public function test_remove_contact(): void { global $DB; $this->resetAfterTest(); $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); api::add_contact($user1->id, $user2->id); api::remove_contact($user1->id, $user2->id); $this->assertEquals(0, $DB->count_records('message_contacts')); } /** * Test blocking users. */ public function test_block_user(): void { global $DB; $this->resetAfterTest(); $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); api::block_user($user1->id, $user2->id); $blockedusers = $DB->get_records('message_users_blocked'); $this->assertCount(1, $blockedusers); $blockeduser = reset($blockedusers); $this->assertEquals($user1->id, $blockeduser->userid); $this->assertEquals($user2->id, $blockeduser->blockeduserid); } /** * Test unblocking users. */ public function test_unblock_user(): void { global $DB; $this->resetAfterTest(); $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); api::block_user($user1->id, $user2->id); api::unblock_user($user1->id, $user2->id); $this->assertEquals(0, $DB->count_records('message_users_blocked')); } /** * Test muting a conversation. */ public function test_mute_conversation(): void { global $DB; $this->resetAfterTest(); $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); $conversation = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, [ $user1->id, $user2->id, ] ); $conversationid = $conversation->id; api::mute_conversation($user1->id, $conversationid); $mutedconversation = $DB->get_records('message_conversation_actions'); $this->assertCount(1, $mutedconversation); $mutedconversation = reset($mutedconversation); $this->assertEquals($user1->id, $mutedconversation->userid); $this->assertEquals($conversationid, $mutedconversation->conversationid); $this->assertEquals(api::CONVERSATION_ACTION_MUTED, $mutedconversation->action); } /** * Test unmuting a conversation. */ public function test_unmute_conversation(): void { global $DB; $this->resetAfterTest(); $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); $conversation = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, [ $user1->id, $user2->id, ] ); $conversationid = $conversation->id; api::mute_conversation($user1->id, $conversationid); api::unmute_conversation($user1->id, $conversationid); $this->assertEquals(0, $DB->count_records('message_conversation_actions')); } /** * Test if a conversation is muted. */ public function test_is_conversation_muted(): void { $this->resetAfterTest(); $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); $conversation = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, [ $user1->id, $user2->id, ] ); $conversationid = $conversation->id; $this->assertFalse(api::is_conversation_muted($user1->id, $conversationid)); api::mute_conversation($user1->id, $conversationid); $this->assertTrue(api::is_conversation_muted($user1->id, $conversationid)); } /** * Test is contact check. */ public function test_is_contact(): void { $this->resetAfterTest(); $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); $user3 = self::getDataGenerator()->create_user(); api::add_contact($user1->id, $user2->id); $this->assertTrue(api::is_contact($user1->id, $user2->id)); $this->assertTrue(api::is_contact($user2->id, $user1->id)); $this->assertFalse(api::is_contact($user2->id, $user3->id)); } /** * Test get contact. */ public function test_get_contact(): void { $this->resetAfterTest(); $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); api::add_contact($user1->id, $user2->id); $contact = api::get_contact($user1->id, $user2->id); $this->assertEquals($user1->id, $contact->userid); $this->assertEquals($user2->id, $contact->contactid); } /** * Test is blocked checked. */ public function test_is_blocked(): void { $this->resetAfterTest(); $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); $this->assertFalse(api::is_blocked($user1->id, $user2->id)); $this->assertFalse(api::is_blocked($user2->id, $user1->id)); api::block_user($user1->id, $user2->id); $this->assertTrue(api::is_blocked($user1->id, $user2->id)); $this->assertFalse(api::is_blocked($user2->id, $user1->id)); } /** * Test the contact request exist check. */ public function test_does_contact_request_exist(): void { $this->resetAfterTest(); $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); $this->assertFalse(api::does_contact_request_exist($user1->id, $user2->id)); $this->assertFalse(api::does_contact_request_exist($user2->id, $user1->id)); api::create_contact_request($user1->id, $user2->id); $this->assertTrue(api::does_contact_request_exist($user1->id, $user2->id)); $this->assertTrue(api::does_contact_request_exist($user2->id, $user1->id)); } /** * Test the get_received_contact_requests_count() function. */ public function test_get_received_contact_requests_count(): void { $this->resetAfterTest(); $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); $user3 = self::getDataGenerator()->create_user(); $user4 = self::getDataGenerator()->create_user(); $this->assertEquals(0, api::get_received_contact_requests_count($user1->id)); api::create_contact_request($user2->id, $user1->id); $this->assertEquals(1, api::get_received_contact_requests_count($user1->id)); api::create_contact_request($user3->id, $user1->id); $this->assertEquals(2, api::get_received_contact_requests_count($user1->id)); api::create_contact_request($user1->id, $user4->id); // Function should ignore sent requests. $this->assertEquals(2, api::get_received_contact_requests_count($user1->id)); } /** * Test the get_received_contact_requests_count() function when the user has blocked the sender of the request. */ public function test_get_received_contact_requests_count_blocked_sender(): void { $this->resetAfterTest(); $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); // User1 blocks User2. api::block_user($user1->id, $user2->id); // User2 tries to add User1 as a contact. api::create_contact_request($user2->id, $user1->id); // Verify we don't see the contact request from the blocked user User2 in the count for User1. $this->assertEquals(0, api::get_received_contact_requests_count($user1->id)); } /** * Test the get_contact_requests_between_users() function. */ public function test_get_contact_requests_between_users(): void { $this->resetAfterTest(); $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); $user3 = self::getDataGenerator()->create_user(); $user4 = self::getDataGenerator()->create_user(); $this->assertEquals([], api::get_contact_requests_between_users($user1->id, $user2->id)); $request1 = api::create_contact_request($user2->id, $user1->id); $results = api::get_contact_requests_between_users($user1->id, $user2->id); $results = array_values($results); $this->assertCount(1, $results); $result = $results[0]; $this->assertEquals($request1->id, $result->id); $request2 = api::create_contact_request($user1->id, $user2->id); $results = api::get_contact_requests_between_users($user1->id, $user2->id); $results = array_values($results); $this->assertCount(2, $results); $actual = [(int) $results[0]->id, (int) $results[1]->id]; $expected = [(int) $request1->id, (int) $request2->id]; sort($actual); sort($expected); $this->assertEquals($expected, $actual); // Request from a different user. api::create_contact_request($user3->id, $user1->id); $results = api::get_contact_requests_between_users($user1->id, $user2->id); $results = array_values($results); $this->assertCount(2, $results); $actual = [(int) $results[0]->id, (int) $results[1]->id]; $expected = [(int) $request1->id, (int) $request2->id]; sort($actual); sort($expected); $this->assertEquals($expected, $actual); } /** * Test the user in conversation check. */ public function test_is_user_in_conversation(): void { $this->resetAfterTest(); $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); $conversation = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, [ $user1->id, $user2->id, ] ); $conversationid = $conversation->id; $this->assertTrue(api::is_user_in_conversation($user1->id, $conversationid)); } /** * Test the user in conversation check when they are not. */ public function test_is_user_in_conversation_when_not(): void { $this->resetAfterTest(); $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); $user3 = self::getDataGenerator()->create_user(); $conversation = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, [ $user1->id, $user2->id, ] ); $conversationid = $conversation->id; $this->assertFalse(api::is_user_in_conversation($user3->id, $conversationid)); } /** * Test can create a group conversation. */ public function test_can_create_group_conversation(): void { global $CFG; $this->resetAfterTest(); $student = self::getDataGenerator()->create_user(); $teacher = self::getDataGenerator()->create_user(); $course = self::getDataGenerator()->create_course(); $coursecontext = \context_course::instance($course->id); $this->getDataGenerator()->enrol_user($student->id, $course->id); $this->getDataGenerator()->enrol_user($teacher->id, $course->id, 'editingteacher'); // Disable messaging. $CFG->messaging = 0; $this->assertFalse(api::can_create_group_conversation($student->id, $coursecontext)); // Re-enable messaging. $CFG->messaging = 1; // Student shouldn't be able to. $this->assertFalse(api::can_create_group_conversation($student->id, $coursecontext)); // Teacher should. $this->assertTrue(api::can_create_group_conversation($teacher->id, $coursecontext)); } /** * Test creating an individual conversation. */ public function test_create_conversation_individual(): void { $this->resetAfterTest(); $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); $conversation = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, [ $user1->id, $user2->id, ], 'A conversation name' ); $this->assertEquals(api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, $conversation->type); $this->assertEquals('A conversation name', $conversation->name); $this->assertEquals(helper::get_conversation_hash([$user1->id, $user2->id]), $conversation->convhash); $this->assertCount(2, $conversation->members); $member1 = array_shift($conversation->members); $member2 = array_shift($conversation->members); $this->assertEquals($user1->id, $member1->userid); $this->assertEquals($conversation->id, $member1->conversationid); $this->assertEquals($user2->id, $member2->userid); $this->assertEquals($conversation->id, $member2->conversationid); } /** * Test creating a group conversation. */ public function test_create_conversation_group(): void { $this->resetAfterTest(); $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); $user3 = self::getDataGenerator()->create_user(); $conversation = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_GROUP, [ $user1->id, $user2->id, $user3->id, ], 'A conversation name' ); $this->assertEquals(api::MESSAGE_CONVERSATION_TYPE_GROUP, $conversation->type); $this->assertEquals('A conversation name', $conversation->name); $this->assertNull($conversation->convhash); $this->assertCount(3, $conversation->members); $member1 = array_shift($conversation->members); $member2 = array_shift($conversation->members); $member3 = array_shift($conversation->members); $this->assertEquals($user1->id, $member1->userid); $this->assertEquals($conversation->id, $member1->conversationid); $this->assertEquals($user2->id, $member2->userid); $this->assertEquals($conversation->id, $member2->conversationid); $this->assertEquals($user3->id, $member3->userid); $this->assertEquals($conversation->id, $member3->conversationid); } /** * Test creating an invalid conversation. */ public function test_create_conversation_invalid(): void { $this->expectException('moodle_exception'); api::create_conversation(3, [1, 2, 3]); } /** * Test creating an individual conversation with too many members. */ public function test_create_conversation_individual_too_many_members(): void { $this->expectException('moodle_exception'); api::create_conversation(api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, [1, 2, 3]); } /** * Test create message conversation with area. */ public function test_create_conversation_with_area(): void { $this->resetAfterTest(); $contextid = 111; $itemid = 222; $name = 'Name of conversation'; $conversation = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_GROUP, [], $name, api::MESSAGE_CONVERSATION_DISABLED, 'core_group', 'groups', $itemid, $contextid ); $this->assertEquals(api::MESSAGE_CONVERSATION_DISABLED, $conversation->enabled); $this->assertEquals('core_group', $conversation->component); $this->assertEquals('groups', $conversation->itemtype); $this->assertEquals($itemid, $conversation->itemid); $this->assertEquals($contextid, $conversation->contextid); } /** * Test get_conversation_by_area. */ public function test_get_conversation_by_area(): void { $this->resetAfterTest(); $contextid = 111; $itemid = 222; $name = 'Name of conversation'; $createconversation = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_GROUP, [], $name, api::MESSAGE_CONVERSATION_DISABLED, 'core_group', 'groups', $itemid, $contextid ); $conversation = api::get_conversation_by_area('core_group', 'groups', $itemid, $contextid); $this->assertEquals($createconversation->id, $conversation->id); $this->assertEquals(api::MESSAGE_CONVERSATION_DISABLED, $conversation->enabled); $this->assertEquals('core_group', $conversation->component); $this->assertEquals('groups', $conversation->itemtype); $this->assertEquals($itemid, $conversation->itemid); $this->assertEquals($contextid, $conversation->contextid); } /** * Test enable_conversation. */ public function test_enable_conversation(): void { global $DB; $this->resetAfterTest(); $name = 'Name of conversation'; $conversation = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_GROUP, [], $name, api::MESSAGE_CONVERSATION_DISABLED ); $this->assertEquals(api::MESSAGE_CONVERSATION_DISABLED, $conversation->enabled); api::enable_conversation($conversation->id); $conversationenabled = $DB->get_field('message_conversations', 'enabled', ['id' => $conversation->id]); $this->assertEquals(api::MESSAGE_CONVERSATION_ENABLED, $conversationenabled); } /** * Test disable_conversation. */ public function test_disable_conversation(): void { global $DB; $this->resetAfterTest(); $name = 'Name of conversation'; $conversation = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_GROUP, [], $name, api::MESSAGE_CONVERSATION_ENABLED ); $this->assertEquals(api::MESSAGE_CONVERSATION_ENABLED, $conversation->enabled); api::disable_conversation($conversation->id); $conversationenabled = $DB->get_field('message_conversations', 'enabled', ['id' => $conversation->id]); $this->assertEquals(api::MESSAGE_CONVERSATION_DISABLED, $conversationenabled); } /** * Test update_conversation_name. */ public function test_update_conversation_name(): void { global $DB; $this->resetAfterTest(); $conversation = api::create_conversation(api::MESSAGE_CONVERSATION_TYPE_GROUP, []); $newname = 'New name of conversation'; api::update_conversation_name($conversation->id, $newname); $this->assertEquals( $newname, $DB->get_field('message_conversations', 'name', ['id' => $conversation->id]) ); } /** * Test returning members in a conversation with no contact requests. */ public function test_get_conversation_members(): void { $this->resetAfterTest(); $lastaccess = new \stdClass(); $lastaccess->lastaccess = time(); $user1 = self::getDataGenerator()->create_user($lastaccess); $user2 = self::getDataGenerator()->create_user(); $user3 = self::getDataGenerator()->create_user(); // This user will not be in the conversation, but a contact request will exist for them. $user4 = self::getDataGenerator()->create_user(); // Add some contact requests. api::create_contact_request($user1->id, $user3->id); api::create_contact_request($user1->id, $user4->id); api::create_contact_request($user2->id, $user3->id); // User 1 and 2 are already contacts. api::add_contact($user1->id, $user2->id); // User 1 has blocked user 3. api::block_user($user1->id, $user3->id); $conversation = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_GROUP, [ $user1->id, $user2->id, $user3->id, ] ); $conversationid = $conversation->id; $members = api::get_conversation_members($user1->id, $conversationid, false); // Sort them by id. ksort($members); $this->assertCount(3, $members); $member1 = array_shift($members); $member2 = array_shift($members); $member3 = array_shift($members); // Confirm the standard fields are OK. $this->assertEquals($user1->id, $member1->id); $this->assertEquals(fullname($user1), $member1->fullname); $this->assertEquals(true, $member1->isonline); $this->assertEquals(true, $member1->showonlinestatus); $this->assertEquals(false, $member1->iscontact); $this->assertEquals(false, $member1->isblocked); $this->assertObjectHasProperty('contactrequests', $member1); $this->assertEmpty($member1->contactrequests); $this->assertEquals($user2->id, $member2->id); $this->assertEquals(fullname($user2), $member2->fullname); $this->assertEquals(false, $member2->isonline); $this->assertEquals(true, $member2->showonlinestatus); $this->assertEquals(true, $member2->iscontact); $this->assertEquals(false, $member2->isblocked); $this->assertObjectHasProperty('contactrequests', $member2); $this->assertEmpty($member2->contactrequests); $this->assertEquals($user3->id, $member3->id); $this->assertEquals(fullname($user3), $member3->fullname); $this->assertEquals(false, $member3->isonline); $this->assertEquals(true, $member3->showonlinestatus); $this->assertEquals(false, $member3->iscontact); $this->assertEquals(true, $member3->isblocked); $this->assertObjectHasProperty('contactrequests', $member3); $this->assertEmpty($member3->contactrequests); } /** * Test returning members in a conversation with contact requests. */ public function test_get_conversation_members_with_contact_requests(): void { $this->resetAfterTest(); $lastaccess = new \stdClass(); $lastaccess->lastaccess = time(); $user1 = self::getDataGenerator()->create_user($lastaccess); $user2 = self::getDataGenerator()->create_user(); $user3 = self::getDataGenerator()->create_user(); // This user will not be in the conversation, but a contact request will exist for them. $user4 = self::getDataGenerator()->create_user(); // Add some contact requests. api::create_contact_request($user1->id, $user2->id); api::create_contact_request($user1->id, $user3->id); api::create_contact_request($user1->id, $user4->id); api::create_contact_request($user2->id, $user3->id); // User 1 and 2 are already contacts. api::add_contact($user1->id, $user2->id); // User 1 has blocked user 3. api::block_user($user1->id, $user3->id); $conversation = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_GROUP, [ $user1->id, $user2->id, $user3->id, ] ); $conversationid = $conversation->id; $members = api::get_conversation_members($user1->id, $conversationid, true); // Sort them by id. ksort($members); $this->assertCount(3, $members); $member1 = array_shift($members); $member2 = array_shift($members); $member3 = array_shift($members); // Confirm the standard fields are OK. $this->assertEquals($user1->id, $member1->id); $this->assertEquals(fullname($user1), $member1->fullname); $this->assertEquals(true, $member1->isonline); $this->assertEquals(true, $member1->showonlinestatus); $this->assertEquals(false, $member1->iscontact); $this->assertEquals(false, $member1->isblocked); $this->assertCount(2, $member1->contactrequests); $this->assertEquals($user2->id, $member2->id); $this->assertEquals(fullname($user2), $member2->fullname); $this->assertEquals(false, $member2->isonline); $this->assertEquals(true, $member2->showonlinestatus); $this->assertEquals(true, $member2->iscontact); $this->assertEquals(false, $member2->isblocked); $this->assertCount(1, $member2->contactrequests); $this->assertEquals($user3->id, $member3->id); $this->assertEquals(fullname($user3), $member3->fullname); $this->assertEquals(false, $member3->isonline); $this->assertEquals(true, $member3->showonlinestatus); $this->assertEquals(false, $member3->iscontact); $this->assertEquals(true, $member3->isblocked); $this->assertCount(1, $member3->contactrequests); // Confirm the contact requests are OK. $request1 = array_shift($member1->contactrequests); $request2 = array_shift($member1->contactrequests); $this->assertEquals($user1->id, $request1->userid); $this->assertEquals($user2->id, $request1->requesteduserid); $this->assertEquals($user1->id, $request2->userid); $this->assertEquals($user3->id, $request2->requesteduserid); $request1 = array_shift($member2->contactrequests); $this->assertEquals($user1->id, $request1->userid); $this->assertEquals($user2->id, $request1->requesteduserid); $request1 = array_shift($member3->contactrequests); $this->assertEquals($user1->id, $request1->userid); $this->assertEquals($user3->id, $request1->requesteduserid); } /** * Test returning members of a self conversation. */ public function test_get_conversation_members_with_self_conversation(): void { $this->resetAfterTest(); $lastaccess = new \stdClass(); $lastaccess->lastaccess = time(); $user1 = self::getDataGenerator()->create_user($lastaccess); $selfconversation = api::get_self_conversation($user1->id); testhelper::send_fake_message_to_conversation($user1, $selfconversation->id, 'This is a self-message!'); // Get the members for the self-conversation. $members = api::get_conversation_members($user1->id, $selfconversation->id); $this->assertCount(1, $members); $member1 = array_shift($members); // Confirm the standard fields are OK. $this->assertEquals($user1->id, $member1->id); $this->assertEquals(fullname($user1), $member1->fullname); $this->assertEquals(true, $member1->isonline); $this->assertEquals(true, $member1->showonlinestatus); $this->assertEquals(false, $member1->iscontact); $this->assertEquals(false, $member1->isblocked); } /** * Test verifying that messages can be sent to existing individual conversations. */ public function test_send_message_to_conversation_individual_conversation(): void { $this->resetAfterTest(); // Get a bunch of conversations, some group, some individual and in different states. [$user1, $user2, $user3, $user4, $ic1, $ic2, $ic3, $gc1, $gc2, $gc3, $gc4, $gc5, $gc6] = $this->create_conversation_test_data(); // Enrol the users into the same course so the privacy checks will pass using default (contact+course members) setting. $course = $this->getDataGenerator()->create_course(); $this->getDataGenerator()->enrol_user($user1->id, $course->id); $this->getDataGenerator()->enrol_user($user2->id, $course->id); $this->getDataGenerator()->enrol_user($user3->id, $course->id); $this->getDataGenerator()->enrol_user($user4->id, $course->id); // Redirect messages. // This marks messages as read, but we can still observe and verify the number of conversation recipients, // based on the message_viewed events generated as part of marking the message as read for each user. $this->preventResetByRollback(); $sink = $this->redirectMessages(); // Send a message to an individual conversation. $sink = $this->redirectEvents(); $messagessink = $this->redirectMessages(); $message1 = api::send_message_to_conversation($user1->id, $ic1->id, 'this is a message', FORMAT_MOODLE); $events = $sink->get_events(); $messages = $messagessink->get_messages(); // Test customdata. $customdata = json_decode($messages[0]->customdata); $this->assertObjectHasProperty('notificationiconurl', $customdata); $this->assertObjectHasProperty('actionbuttons', $customdata); $this->assertCount(1, (array) $customdata->actionbuttons); $this->assertObjectHasProperty('placeholders', $customdata); $this->assertCount(1, (array) $customdata->placeholders); // Verify the message returned. $this->assertInstanceOf(\stdClass::class, $message1); $this->assertObjectHasProperty('id', $message1); $this->assertEquals($user1->id, $message1->useridfrom); $this->assertEquals('this is a message', $message1->text); $this->assertObjectHasProperty('timecreated', $message1); // Verify events. Note: the event is a message read event because of an if (PHPUNIT) conditional within message_send(), // however, we can still determine the number and ids of any recipients this way. $this->assertCount(1, $events); $userids = array_column($events, 'userid'); $this->assertNotContainsEquals($user1->id, $userids); $this->assertContainsEquals($user2->id, $userids); } /** * Test verifying that messages can be sent to existing group conversations. */ public function test_send_message_to_conversation_group_conversation(): void { $this->resetAfterTest(); // Get a bunch of conversations, some group, some individual and in different states. [$user1, $user2, $user3, $user4, $ic1, $ic2, $ic3, $gc1, $gc2, $gc3, $gc4, $gc5, $gc6] = $this->create_conversation_test_data(); // Enrol the users into the same course so the privacy checks will pass using default (contact+course members) setting. $course = $this->getDataGenerator()->create_course(); $this->getDataGenerator()->enrol_user($user1->id, $course->id); $this->getDataGenerator()->enrol_user($user2->id, $course->id); $this->getDataGenerator()->enrol_user($user3->id, $course->id); $this->getDataGenerator()->enrol_user($user4->id, $course->id); // Redirect messages. // This marks messages as read, but we can still observe and verify the number of conversation recipients, // based on the message_viewed events generated as part of marking the message as read for each user. $this->preventResetByRollback(); $sink = $this->redirectMessages(); // Send a message to a group conversation. $sink = $this->redirectEvents(); $messagessink = $this->redirectMessages(); $message1 = api::send_message_to_conversation($user1->id, $gc2->id, 'message to the group', FORMAT_MOODLE); $events = $sink->get_events(); $messages = $messagessink->get_messages(); // Verify the message returned. $this->assertInstanceOf(\stdClass::class, $message1); $this->assertObjectHasProperty('id', $message1); $this->assertEquals($user1->id, $message1->useridfrom); $this->assertEquals('message to the group', $message1->text); $this->assertObjectHasProperty('timecreated', $message1); // Test customdata. $customdata = json_decode($messages[0]->customdata); $this->assertObjectHasProperty('actionbuttons', $customdata); $this->assertCount(1, (array) $customdata->actionbuttons); $this->assertObjectHasProperty('placeholders', $customdata); $this->assertCount(1, (array) $customdata->placeholders); $this->assertObjectNotHasProperty('notificationiconurl', $customdata); // No group image means no image. // Verify events. Note: the event is a message read event because of an if (PHPUNIT) conditional within message_send(), // however, we can still determine the number and ids of any recipients this way. $this->assertCount(2, $events); $userids = array_column($events, 'userid'); $this->assertNotContainsEquals($user1->id, $userids); $this->assertContainsEquals($user3->id, $userids); $this->assertContainsEquals($user4->id, $userids); } /** * Test verifying that messages can be sent to existing linked group conversations. */ public function test_send_message_to_conversation_linked_group_conversation(): void { global $CFG, $PAGE; $this->resetAfterTest(); // Create some users. $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); $user3 = self::getDataGenerator()->create_user(); $course = $this->getDataGenerator()->create_course(); // Create a group with a linked conversation and a valid image. $this->setAdminUser(); $this->getDataGenerator()->enrol_user($user1->id, $course->id); $this->getDataGenerator()->enrol_user($user2->id, $course->id); $this->getDataGenerator()->enrol_user($user3->id, $course->id); $group = $this->getDataGenerator()->create_group([ 'courseid' => $course->id, 'enablemessaging' => 1, 'picturepath' => $CFG->dirroot . '/lib/tests/fixtures/gd-logo.png', ]); // Add users to group. $this->getDataGenerator()->create_group_member(['groupid' => $group->id, 'userid' => $user1->id]); $this->getDataGenerator()->create_group_member(['groupid' => $group->id, 'userid' => $user2->id]); // Verify the group with the image works as expected. $conversations = api::get_conversations($user1->id); $this->assertEquals(2, $conversations[0]->membercount); $this->assertEquals($course->shortname, $conversations[0]->subname); $groupimageurl = get_group_picture_url($group, $group->courseid, true); $this->assertEquals($groupimageurl, $conversations[0]->imageurl); // Redirect messages. // This marks messages as read, but we can still observe and verify the number of conversation recipients, // based on the message_viewed events generated as part of marking the message as read for each user. $this->preventResetByRollback(); $sink = $this->redirectMessages(); // Send a message to a group conversation. $messagessink = $this->redirectMessages(); $message1 = api::send_message_to_conversation( $user1->id, $conversations[0]->id, 'message to the group', FORMAT_MOODLE ); $messages = $messagessink->get_messages(); // Verify the message returned. $this->assertInstanceOf(\stdClass::class, $message1); $this->assertObjectHasProperty('id', $message1); $this->assertEquals($user1->id, $message1->useridfrom); $this->assertEquals('message to the group', $message1->text); $this->assertObjectHasProperty('timecreated', $message1); // Test customdata. $customdata = json_decode($messages[0]->customdata); $this->assertObjectHasProperty('notificationiconurl', $customdata); $this->assertObjectHasProperty('notificationsendericonurl', $customdata); $this->assertEquals($groupimageurl, $customdata->notificationiconurl); $this->assertEquals($group->name, $customdata->conversationname); $userpicture = new \user_picture($user1); $userpicture->size = 1; // Use f1 size. $this->assertEquals($userpicture->get_url($PAGE)->out(false), $customdata->notificationsendericonurl); } /** * Test verifying that messages cannot be sent to conversations that don't exist. */ public function test_send_message_to_conversation_non_existent_conversation(): void { $this->resetAfterTest(); // Get a bunch of conversations, some group, some individual and in different states. [$user1, $user2, $user3, $user4, $ic1, $ic2, $ic3, $gc1, $gc2, $gc3, $gc4, $gc5, $gc6] = $this->create_conversation_test_data(); $this->expectException(\moodle_exception::class); api::send_message_to_conversation($user1->id, 0, 'test', FORMAT_MOODLE); } /** * Test verifying that messages cannot be sent to conversations by users who are not members. */ public function test_send_message_to_conversation_non_member(): void { $this->resetAfterTest(); // Get a bunch of conversations, some group, some individual and in different states. [$user1, $user2, $user3, $user4, $ic1, $ic2, $ic3, $gc1, $gc2, $gc3, $gc4, $gc5, $gc6] = $this->create_conversation_test_data(); // Enrol the users into the same course so the privacy checks will pass using default (contact+course members) setting. $course = $this->getDataGenerator()->create_course(); $this->getDataGenerator()->enrol_user($user1->id, $course->id); $this->getDataGenerator()->enrol_user($user2->id, $course->id); $this->getDataGenerator()->enrol_user($user3->id, $course->id); $this->getDataGenerator()->enrol_user($user4->id, $course->id); $this->expectException(\moodle_exception::class); api::send_message_to_conversation($user3->id, $ic1->id, 'test', FORMAT_MOODLE); } /** * Test verifying that messages cannot be sent to conversations by users who are not members. */ public function test_send_message_to_conversation_blocked_user(): void { $this->resetAfterTest(); // Get a bunch of conversations, some group, some individual and in different states. [$user1, $user2, $user3, $user4, $ic1, $ic2, $ic3, $gc1, $gc2, $gc3, $gc4, $gc5, $gc6] = $this->create_conversation_test_data(); // Enrol the users into the same course so the privacy checks will pass using default (contact+course members) setting. $course = $this->getDataGenerator()->create_course(); $this->getDataGenerator()->enrol_user($user1->id, $course->id); $this->getDataGenerator()->enrol_user($user2->id, $course->id); $this->getDataGenerator()->enrol_user($user3->id, $course->id); $this->getDataGenerator()->enrol_user($user4->id, $course->id); // User 1 blocks user 2. api::block_user($user1->id, $user2->id); // Verify that a message can be sent to any group conversation in which user1 and user2 are members. $this->assertNotEmpty(api::send_message_to_conversation($user1->id, $gc2->id, 'Hey guys', FORMAT_PLAIN)); // User 2 cannot send a message to the conversation with user 1. $this->expectException(\moodle_exception::class); api::send_message_to_conversation($user2->id, $ic1->id, 'test', FORMAT_MOODLE); } /** * Test the get_conversation() function with a muted conversation. */ public function test_get_conversation_with_muted_conversation(): void { $this->resetAfterTest(); $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); $this->setUser($user1); $conversation = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, [$user1->id, $user2->id] ); $conversation = api::get_conversation($user1->id, $conversation->id); $this->assertFalse($conversation->ismuted); // Now, mute the conversation. api::mute_conversation($user1->id, $conversation->id); $conversation = api::get_conversation($user1->id, $conversation->id); $this->assertTrue($conversation->ismuted); } /** * Data provider for test_get_conversation_counts(). */ public static function get_conversation_counts_test_cases(): array { $typeindividual = api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL; $typegroup = api::MESSAGE_CONVERSATION_TYPE_GROUP; $typeself = api::MESSAGE_CONVERSATION_TYPE_SELF; [$user1, $user2, $user3, $user4, $user5, $user6, $user7, $user8] = [0, 1, 2, 3, 4, 5, 6, 7]; $conversations = [ [ 'type' => $typeindividual, 'users' => [$user1, $user2], 'messages' => [$user1, $user2, $user2], 'favourites' => [$user1], 'enabled' => null, // Individual conversations cannot be disabled. ], [ 'type' => $typeindividual, 'users' => [$user1, $user3], 'messages' => [$user1, $user3, $user1], 'favourites' => [], 'enabled' => null, // Individual conversations cannot be disabled. ], [ 'type' => $typegroup, 'users' => [$user1, $user2, $user3, $user4], 'messages' => [$user1, $user2, $user3, $user4], 'favourites' => [], 'enabled' => true, ], [ 'type' => $typegroup, 'users' => [$user2, $user3, $user4], 'messages' => [$user2, $user3, $user4], 'favourites' => [], 'enabled' => true, ], [ 'type' => $typegroup, 'users' => [$user6, $user7], 'messages' => [$user6, $user7, $user7], 'favourites' => [$user6], 'enabled' => false, ], [ 'type' => $typeself, 'users' => [$user8], 'messages' => [$user8], 'favourites' => [], 'enabled' => null, // Self-conversations cannot be disabled. ], ]; return [ 'No conversations' => [ 'conversationconfigs' => $conversations, 'deletemessagesuser' => null, 'deletemessages' => [], 'arguments' => [$user5], 'expectedcounts' => [ 'favourites' => 1, 'types' => [ api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 0, api::MESSAGE_CONVERSATION_TYPE_GROUP => 0, api::MESSAGE_CONVERSATION_TYPE_SELF => 0, ], ], 'expectedunreadcounts' => [ 'favourites' => 0, 'types' => [ api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 0, api::MESSAGE_CONVERSATION_TYPE_GROUP => 0, api::MESSAGE_CONVERSATION_TYPE_SELF => 0, ], ], 'deletedusers' => [], ], 'No individual conversations, 2 group conversations' => [ 'conversationconfigs' => $conversations, 'deletemessagesuser' => null, 'deletemessages' => [], 'arguments' => [$user4], 'expectedcounts' => [ 'favourites' => 1, 'types' => [ api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 0, api::MESSAGE_CONVERSATION_TYPE_GROUP => 2, api::MESSAGE_CONVERSATION_TYPE_SELF => 0, ], ], 'expectedunreadcounts' => [ 'favourites' => 0, 'types' => [ api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 0, api::MESSAGE_CONVERSATION_TYPE_GROUP => 2, api::MESSAGE_CONVERSATION_TYPE_SELF => 0, ], ], 'deletedusers' => [], ], '2 individual conversations (one favourited), 1 group conversation' => [ 'conversationconfigs' => $conversations, 'deletemessagesuser' => null, 'deletemessages' => [], 'arguments' => [$user1], 'expectedcounts' => [ 'favourites' => 2, 'types' => [ api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1, api::MESSAGE_CONVERSATION_TYPE_GROUP => 1, api::MESSAGE_CONVERSATION_TYPE_SELF => 0, ], ], 'expectedunreadcounts' => [ 'favourites' => 1, 'types' => [ api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1, api::MESSAGE_CONVERSATION_TYPE_GROUP => 1, api::MESSAGE_CONVERSATION_TYPE_SELF => 0, ], ], 'deletedusers' => [], ], '1 individual conversation, 2 group conversations' => [ 'conversationconfigs' => $conversations, 'deletemessagesuser' => null, 'deletemessages' => [], 'arguments' => [$user2], 'expectedcounts' => [ 'favourites' => 1, 'types' => [ api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1, api::MESSAGE_CONVERSATION_TYPE_GROUP => 2, api::MESSAGE_CONVERSATION_TYPE_SELF => 0, ], ], 'expectedunreadcounts' => [ 'favourites' => 0, 'types' => [ api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1, api::MESSAGE_CONVERSATION_TYPE_GROUP => 2, api::MESSAGE_CONVERSATION_TYPE_SELF => 0, ], ], 'deletedusers' => [], ], '2 group conversations only' => [ 'conversationconfigs' => $conversations, 'deletemessagesuser' => null, 'deletemessages' => [], 'arguments' => [$user4], 'expectedcounts' => [ 'favourites' => 1, 'types' => [ api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 0, api::MESSAGE_CONVERSATION_TYPE_GROUP => 2, api::MESSAGE_CONVERSATION_TYPE_SELF => 0, ], ], 'expectedunreadcounts' => [ 'favourites' => 0, 'types' => [ api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 0, api::MESSAGE_CONVERSATION_TYPE_GROUP => 2, api::MESSAGE_CONVERSATION_TYPE_SELF => 0, ], ], 'deletedusers' => [], ], 'All conversation types, delete a message from individual favourited, messages remaining' => [ 'conversationconfigs' => $conversations, 'deletemessagesuser' => $user1, 'deletemessages' => [0], 'arguments' => [$user1], 'expectedcounts' => [ 'favourites' => 2, 'types' => [ api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1, api::MESSAGE_CONVERSATION_TYPE_GROUP => 1, api::MESSAGE_CONVERSATION_TYPE_SELF => 0, ], ], 'expectedunreadcounts' => [ 'favourites' => 1, 'types' => [ api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1, api::MESSAGE_CONVERSATION_TYPE_GROUP => 1, api::MESSAGE_CONVERSATION_TYPE_SELF => 0, ], ], 'deletedusers' => [], ], 'All conversation types, delete a message from individual non-favourited, messages remaining' => [ 'conversationconfigs' => $conversations, 'deletemessagesuser' => $user1, 'deletemessages' => [3], 'arguments' => [$user1], 'expectedcounts' => [ 'favourites' => 2, 'types' => [ api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1, api::MESSAGE_CONVERSATION_TYPE_GROUP => 1, api::MESSAGE_CONVERSATION_TYPE_SELF => 0, ], ], 'expectedunreadcounts' => [ 'favourites' => 1, 'types' => [ api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1, api::MESSAGE_CONVERSATION_TYPE_GROUP => 1, api::MESSAGE_CONVERSATION_TYPE_SELF => 0, ], ], 'deletedusers' => [], ], 'All conversation types, delete all messages from individual favourited, no messages remaining' => [ 'conversationconfigs' => $conversations, 'deletemessagesuser' => $user1, 'deletemessages' => [0, 1, 2], 'arguments' => [$user1], 'expectedcounts' => [ 'favourites' => 1, 'types' => [ api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1, api::MESSAGE_CONVERSATION_TYPE_GROUP => 1, api::MESSAGE_CONVERSATION_TYPE_SELF => 0, ], ], 'expectedunreadcounts' => [ 'favourites' => 0, 'types' => [ api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1, api::MESSAGE_CONVERSATION_TYPE_GROUP => 1, api::MESSAGE_CONVERSATION_TYPE_SELF => 0, ], ], 'deletedusers' => [], ], 'All conversation types, delete all messages from individual non-favourited, no messages remaining' => [ 'conversationconfigs' => $conversations, 'deletemessagesuser' => $user1, 'deletemessages' => [3, 4, 5], 'arguments' => [$user1], 'expectedcounts' => [ 'favourites' => 2, 'types' => [ api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 0, api::MESSAGE_CONVERSATION_TYPE_GROUP => 1, api::MESSAGE_CONVERSATION_TYPE_SELF => 0, ], ], 'expectedunreadcounts' => [ 'favourites' => 1, 'types' => [ api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 0, api::MESSAGE_CONVERSATION_TYPE_GROUP => 1, api::MESSAGE_CONVERSATION_TYPE_SELF => 0, ], ], 'deletedusers' => [], ], 'All conversation types, delete all messages from individual favourited, no messages remaining, different user' => [ 'conversationconfigs' => $conversations, 'deletemessagesuser' => $user1, 'deletemessages' => [0, 1, 2], 'arguments' => [$user2], 'expectedcounts' => [ 'favourites' => 1, 'types' => [ api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1, api::MESSAGE_CONVERSATION_TYPE_GROUP => 2, api::MESSAGE_CONVERSATION_TYPE_SELF => 0, ], ], 'expectedunreadcounts' => [ 'favourites' => 0, 'types' => [ api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1, api::MESSAGE_CONVERSATION_TYPE_GROUP => 2, api::MESSAGE_CONVERSATION_TYPE_SELF => 0, ], ], 'deletedusers' => [], ], 'All conversation types, delete all messages from individual non-favourited, no messages remaining, different user' => [ 'conversationconfigs' => $conversations, 'deletemessagesuser' => $user1, 'deletemessages' => [3, 4, 5], 'arguments' => [$user3], 'expectedcounts' => [ 'favourites' => 1, 'types' => [ api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1, api::MESSAGE_CONVERSATION_TYPE_GROUP => 2, api::MESSAGE_CONVERSATION_TYPE_SELF => 0, ], ], 'expectedunreadcounts' => [ 'favourites' => 0, 'types' => [ api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1, api::MESSAGE_CONVERSATION_TYPE_GROUP => 2, api::MESSAGE_CONVERSATION_TYPE_SELF => 0, ], ], 'deletedusers' => [], ], 'All conversation types, delete some messages from group non-favourited, messages remaining,' => [ 'conversationconfigs' => $conversations, 'deletemessagesuser' => $user1, 'deletemessages' => [6, 7], 'arguments' => [$user1], 'expectedcounts' => [ 'favourites' => 2, 'types' => [ api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1, api::MESSAGE_CONVERSATION_TYPE_GROUP => 1, api::MESSAGE_CONVERSATION_TYPE_SELF => 0, ], ], 'expectedunreadcounts' => [ 'favourites' => 1, 'types' => [ api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1, api::MESSAGE_CONVERSATION_TYPE_GROUP => 1, api::MESSAGE_CONVERSATION_TYPE_SELF => 0, ], ], 'deletedusers' => [], ], 'All conversation types, delete all messages from group non-favourited, no messages remaining,' => [ 'conversationconfigs' => $conversations, 'deletemessagesuser' => $user1, 'deletemessages' => [6, 7, 8, 9], 'arguments' => [$user1], 'expectedcounts' => [ 'favourites' => 2, 'types' => [ api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1, api::MESSAGE_CONVERSATION_TYPE_GROUP => 1, api::MESSAGE_CONVERSATION_TYPE_SELF => 0, ], ], 'expectedunreadcounts' => [ 'favourites' => 1, 'types' => [ api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1, api::MESSAGE_CONVERSATION_TYPE_GROUP => 0, api::MESSAGE_CONVERSATION_TYPE_SELF => 0, ], ], 'deletedusers' => [], ], 'All conversation types, another user soft deleted' => [ 'conversationconfigs' => $conversations, 'deletemessagesuser' => null, 'deletemessages' => [], 'arguments' => [$user1], 'expectedcounts' => [ 'favourites' => 2, 'types' => [ api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1, api::MESSAGE_CONVERSATION_TYPE_GROUP => 1, api::MESSAGE_CONVERSATION_TYPE_SELF => 0, ], ], 'expectedunreadcounts' => [ 'favourites' => 1, 'types' => [ api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1, api::MESSAGE_CONVERSATION_TYPE_GROUP => 1, api::MESSAGE_CONVERSATION_TYPE_SELF => 0, ], ], 'deletedusers' => [$user2], ], 'All conversation types, all group users soft deleted' => [ 'conversationconfigs' => $conversations, 'deletemessagesuser' => null, 'deletemessages' => [], 'arguments' => [$user1], 'expectedcounts' => [ 'favourites' => 2, 'types' => [ api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1, api::MESSAGE_CONVERSATION_TYPE_GROUP => 1, api::MESSAGE_CONVERSATION_TYPE_SELF => 0, ], ], 'expectedunreadcounts' => [ 'favourites' => 1, 'types' => [ api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 1, api::MESSAGE_CONVERSATION_TYPE_GROUP => 1, api::MESSAGE_CONVERSATION_TYPE_SELF => 0, ], ], 'deletedusers' => [$user2, $user3, $user4], ], 'Group conversation which is disabled, favourited' => [ 'conversationconfigs' => $conversations, 'deletemessagesuser' => null, 'deletemessages' => [], 'arguments' => [$user6], 'expectedcounts' => [ 'favourites' => 1, 'types' => [ api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 0, api::MESSAGE_CONVERSATION_TYPE_GROUP => 0, api::MESSAGE_CONVERSATION_TYPE_SELF => 0, ], ], 'expectedunreadcounts' => [ 'favourites' => 0, 'types' => [ api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 0, api::MESSAGE_CONVERSATION_TYPE_GROUP => 0, api::MESSAGE_CONVERSATION_TYPE_SELF => 0, ], ], 'deletedusers' => [], ], 'Group conversation which is disabled, non-favourited' => [ 'conversationconfigs' => $conversations, 'deletemessagesuser' => null, 'deletemessages' => [], 'arguments' => [$user7], 'expectedcounts' => [ 'favourites' => 1, 'types' => [ api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 0, api::MESSAGE_CONVERSATION_TYPE_GROUP => 0, api::MESSAGE_CONVERSATION_TYPE_SELF => 0, ], ], 'expectedunreadcounts' => [ 'favourites' => 0, 'types' => [ api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 0, api::MESSAGE_CONVERSATION_TYPE_GROUP => 0, api::MESSAGE_CONVERSATION_TYPE_SELF => 0, ], ], 'deletedusers' => [], ], 'Conversation with self' => [ 'conversationconfigs' => $conversations, 'deletemessagesuser' => null, 'deletemessages' => [], 'arguments' => [$user8], 'expectedcounts' => [ 'favourites' => 0, 'types' => [ api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 0, api::MESSAGE_CONVERSATION_TYPE_GROUP => 0, api::MESSAGE_CONVERSATION_TYPE_SELF => 1, ], ], 'expectedunreadcounts' => [ 'favourites' => 0, 'types' => [ api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL => 0, api::MESSAGE_CONVERSATION_TYPE_GROUP => 0, api::MESSAGE_CONVERSATION_TYPE_SELF => 0, ], ], 'deletedusers' => [], ], ]; } /** * Test the get_conversation_counts() function. * * @dataProvider get_conversation_counts_test_cases * @param array $conversationconfigs Conversations to create * @param int $deletemessagesuser The user who is deleting the messages * @param array $deletemessages The list of messages to delete (by index) * @param array $arguments Arguments for the count conversations function * @param array $expectedcounts the expected conversation counts * @param array $expectedunreadcounts the expected unread conversation counts * @param array $deletedusers the array of users to soft delete. */ public function test_get_conversation_counts( $conversationconfigs, $deletemessagesuser, $deletemessages, $arguments, $expectedcounts, $expectedunreadcounts, $deletedusers ): void { $this->resetAfterTest(); $generator = $this->getDataGenerator(); $users = [ $generator->create_user(), $generator->create_user(), $generator->create_user(), $generator->create_user(), $generator->create_user(), $generator->create_user(), $generator->create_user(), $generator->create_user(), ]; $deleteuser = !is_null($deletemessagesuser) ? $users[$deletemessagesuser] : null; $arguments[0] = $users[$arguments[0]]->id; $systemcontext = \context_system::instance(); $conversations = []; $messageids = []; foreach ($conversationconfigs as $config) { $conversation = api::create_conversation( $config['type'], array_map(function ($userindex) use ($users) { return $users[$userindex]->id; }, $config['users']), null, ($config['enabled'] ?? true) ); foreach ($config['messages'] as $userfromindex) { $userfrom = $users[$userfromindex]; $messageids[] = testhelper::send_fake_message_to_conversation($userfrom, $conversation->id); } // Remove the self conversations created by the generator, // so we can choose to set that ourself and honour the original intention of the test. $userids = array_map(function ($userindex) use ($users) { return $users[$userindex]->id; }, $config['users']); foreach ($userids as $userid) { if ($conversation->type == api::MESSAGE_CONVERSATION_TYPE_SELF) { api::unset_favourite_conversation($conversation->id, $userid); } } foreach ($config['favourites'] as $userfromindex) { $userfrom = $users[$userfromindex]; $usercontext = \context_user::instance($userfrom->id); $ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext); $ufservice->create_favourite('core_message', 'message_conversations', $conversation->id, $systemcontext); } $conversations[] = $conversation; } foreach ($deletemessages as $messageindex) { api::delete_message($deleteuser->id, $messageids[$messageindex]); } foreach ($deletedusers as $deleteduser) { delete_user($users[$deleteduser]); } $counts = api::get_conversation_counts(...$arguments); $this->assertEquals($expectedcounts['favourites'], $counts['favourites']); $this->assertEquals( $expectedcounts['types'][api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL], $counts['types'][api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL] ); $this->assertEquals( $expectedcounts['types'][api::MESSAGE_CONVERSATION_TYPE_GROUP], $counts['types'][api::MESSAGE_CONVERSATION_TYPE_GROUP] ); $this->assertEquals( $expectedcounts['types'][api::MESSAGE_CONVERSATION_TYPE_SELF], $counts['types'][api::MESSAGE_CONVERSATION_TYPE_SELF] ); } /** * Test the count_contacts() function. */ public function test_count_contacts(): void { $this->resetAfterTest(); $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); $user3 = self::getDataGenerator()->create_user(); $this->assertEquals(0, api::count_contacts($user1->id)); api::create_contact_request($user1->id, $user2->id); // Still zero until the request is confirmed. $this->assertEquals(0, api::count_contacts($user1->id)); api::confirm_contact_request($user1->id, $user2->id); $this->assertEquals(1, api::count_contacts($user1->id)); api::create_contact_request($user3->id, $user1->id); // Still one until the request is confirmed. $this->assertEquals(1, api::count_contacts($user1->id)); api::confirm_contact_request($user3->id, $user1->id); $this->assertEquals(2, api::count_contacts($user1->id)); } /** * Test the get_unread_conversation_counts() function. * * @dataProvider get_conversation_counts_test_cases * @param array $conversationconfigs Conversations to create * @param int $deletemessagesuser The user who is deleting the messages * @param array $deletemessages The list of messages to delete (by index) * @param array $arguments Arguments for the count conversations function * @param array $expectedcounts the expected conversation counts * @param array $expectedunreadcounts the expected unread conversation counts * @param array $deletedusers the list of users to soft-delete. */ public function test_get_unread_conversation_counts( $conversationconfigs, $deletemessagesuser, $deletemessages, $arguments, $expectedcounts, $expectedunreadcounts, $deletedusers ): void { $this->resetAfterTest(); $generator = $this->getDataGenerator(); $users = [ $generator->create_user(), $generator->create_user(), $generator->create_user(), $generator->create_user(), $generator->create_user(), $generator->create_user(), $generator->create_user(), $generator->create_user(), ]; $deleteuser = !is_null($deletemessagesuser) ? $users[$deletemessagesuser] : null; $this->setUser($users[$arguments[0]]); $arguments[0] = $users[$arguments[0]]->id; $systemcontext = \context_system::instance(); $conversations = []; $messageids = []; foreach ($conversationconfigs as $config) { $conversation = api::create_conversation( $config['type'], array_map(function ($userindex) use ($users) { return $users[$userindex]->id; }, $config['users']), null, ($config['enabled'] ?? true) ); foreach ($config['messages'] as $userfromindex) { $userfrom = $users[$userfromindex]; $messageids[] = testhelper::send_fake_message_to_conversation($userfrom, $conversation->id); } foreach ($config['favourites'] as $userfromindex) { $userfrom = $users[$userfromindex]; $usercontext = \context_user::instance($userfrom->id); $ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext); $ufservice->create_favourite('core_message', 'message_conversations', $conversation->id, $systemcontext); } $conversations[] = $conversation; } foreach ($deletemessages as $messageindex) { api::delete_message($deleteuser->id, $messageids[$messageindex]); } foreach ($deletedusers as $deleteduser) { delete_user($users[$deleteduser]); } $counts = api::get_unread_conversation_counts(...$arguments); $this->assertEquals($expectedunreadcounts['favourites'], $counts['favourites']); $this->assertEquals( $expectedunreadcounts['types'][api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL], $counts['types'][api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL] ); $this->assertEquals( $expectedunreadcounts['types'][api::MESSAGE_CONVERSATION_TYPE_GROUP], $counts['types'][api::MESSAGE_CONVERSATION_TYPE_GROUP] ); $this->assertEquals( $expectedunreadcounts['types'][api::MESSAGE_CONVERSATION_TYPE_SELF], $counts['types'][api::MESSAGE_CONVERSATION_TYPE_SELF] ); } public function test_delete_all_conversation_data(): void { global $DB; $this->resetAfterTest(); $this->setAdminUser(); $course1 = $this->getDataGenerator()->create_course(); $coursecontext1 = \context_course::instance($course1->id); $user1 = $this->getDataGenerator()->create_user(); $user2 = $this->getDataGenerator()->create_user(); $this->getDataGenerator()->enrol_user($user1->id, $course1->id); $this->getDataGenerator()->enrol_user($user2->id, $course1->id); $group1 = $this->getDataGenerator()->create_group(['courseid' => $course1->id, 'enablemessaging' => 1]); $group2 = $this->getDataGenerator()->create_group(['courseid' => $course1->id, 'enablemessaging' => 1]); // Add users to both groups. $this->getDataGenerator()->create_group_member(['groupid' => $group1->id, 'userid' => $user1->id]); $this->getDataGenerator()->create_group_member(['groupid' => $group1->id, 'userid' => $user2->id]); $this->getDataGenerator()->create_group_member(['groupid' => $group2->id, 'userid' => $user1->id]); $this->getDataGenerator()->create_group_member(['groupid' => $group2->id, 'userid' => $user2->id]); $groupconversation1 = api::get_conversation_by_area( 'core_group', 'groups', $group1->id, $coursecontext1->id ); $groupconversation2 = api::get_conversation_by_area( 'core_group', 'groups', $group2->id, $coursecontext1->id ); // Send a few messages. $g1m1 = testhelper::send_fake_message_to_conversation($user1, $groupconversation1->id); $g1m2 = testhelper::send_fake_message_to_conversation($user2, $groupconversation1->id); $g1m3 = testhelper::send_fake_message_to_conversation($user1, $groupconversation1->id); $g1m4 = testhelper::send_fake_message_to_conversation($user2, $groupconversation1->id); $g2m1 = testhelper::send_fake_message_to_conversation($user1, $groupconversation2->id); $g2m2 = testhelper::send_fake_message_to_conversation($user2, $groupconversation2->id); $g2m3 = testhelper::send_fake_message_to_conversation($user1, $groupconversation2->id); $g2m4 = testhelper::send_fake_message_to_conversation($user2, $groupconversation2->id); // Favourite the conversation for several of the users. api::set_favourite_conversation($groupconversation1->id, $user1->id); api::set_favourite_conversation($groupconversation1->id, $user2->id); // Delete a few messages. api::delete_message($user1->id, $g1m1); api::delete_message($user1->id, $g1m2); api::delete_message($user1->id, $g2m1); api::delete_message($user1->id, $g2m2); // Mute the conversations. api::mute_conversation($user1->id, $groupconversation1->id); api::mute_conversation($user1->id, $groupconversation2->id); // Now, delete all the data for the group 1 conversation. api::delete_all_conversation_data($groupconversation1->id); // Confirm group conversation was deleted just for the group 1 conversation. $this->assertEquals(0, $DB->count_records('message_conversations', ['id' => $groupconversation1->id])); $this->assertEquals(1, $DB->count_records('message_conversations', ['id' => $groupconversation2->id])); // Confirm conversation members were deleted just for the group 1 conversation. $this->assertEquals(0, $DB->count_records('message_conversation_members', ['conversationid' => $groupconversation1->id])); $this->assertEquals(2, $DB->count_records('message_conversation_members', ['conversationid' => $groupconversation2->id])); // Confirm message conversation actions were deleted just for the group 1 conversation. $this->assertEquals(0, $DB->count_records('message_conversation_actions', ['conversationid' => $groupconversation1->id])); $this->assertEquals(1, $DB->count_records('message_conversation_actions', ['conversationid' => $groupconversation2->id])); // Confirm message user actions were deleted just for the group 1 conversation. $this->assertEquals(0, $DB->count_records('message_user_actions', ['messageid' => $g1m1])); $this->assertEquals(0, $DB->count_records('message_user_actions', ['messageid' => $g1m2])); $this->assertEquals(0, $DB->count_records('message_user_actions', ['messageid' => $g1m3])); $this->assertEquals(0, $DB->count_records('message_user_actions', ['messageid' => $g1m4])); $this->assertEquals(1, $DB->count_records('message_user_actions', ['messageid' => $g2m1])); $this->assertEquals(1, $DB->count_records('message_user_actions', ['messageid' => $g2m2])); $this->assertEquals(0, $DB->count_records('message_user_actions', ['messageid' => $g2m3])); $this->assertEquals(0, $DB->count_records('message_user_actions', ['messageid' => $g2m4])); // Confirm messages were deleted just for the group 1 conversation. $this->assertEquals(0, $DB->count_records('messages', ['id' => $g1m1])); $this->assertEquals(0, $DB->count_records('messages', ['id' => $g1m2])); $this->assertEquals(0, $DB->count_records('messages', ['id' => $g1m3])); $this->assertEquals(0, $DB->count_records('messages', ['id' => $g1m4])); $this->assertEquals(1, $DB->count_records('messages', ['id' => $g2m1])); $this->assertEquals(1, $DB->count_records('messages', ['id' => $g2m2])); $this->assertEquals(1, $DB->count_records('messages', ['id' => $g2m3])); $this->assertEquals(1, $DB->count_records('messages', ['id' => $g2m4])); // Confirm favourites were deleted for both users. $user1service = \core_favourites\service_factory::get_service_for_user_context(\context_user::instance($user1->id)); $this->assertFalse($user1service->favourite_exists( 'core_message', 'message_conversations', $groupconversation1->id, $coursecontext1 )); $user2service = \core_favourites\service_factory::get_service_for_user_context(\context_user::instance($user1->id)); $this->assertFalse($user2service->favourite_exists( 'core_message', 'message_conversations', $groupconversation1->id, $coursecontext1 )); } /** * Tests the user can delete message for all users as a teacher. */ public function test_can_delete_message_for_all_users_teacher(): void { global $DB; $this->resetAfterTest(true); // Create fake data to test it. [$teacher, $student1, $student2, $convgroup, $convindividual] = $this->create_delete_message_test_data(); // Allow Teacher can delete messages for all. $editingteacher = $DB->get_record('role', ['shortname' => 'editingteacher']); assign_capability('moodle/site:deleteanymessage', CAP_ALLOW, $editingteacher->id, \context_system::instance()); // Set as the first user. $this->setUser($teacher); // Send a message to private conversation and in a group conversation. $messageidind = testhelper::send_fake_message_to_conversation($teacher, $convindividual->id); $messageidgrp = testhelper::send_fake_message_to_conversation($teacher, $convgroup->id); // Teacher cannot delete message for everyone in a private conversation. $this->assertFalse(api::can_delete_message_for_all_users($teacher->id, $messageidind)); // Teacher can delete message for everyone in a group conversation. $this->assertTrue(api::can_delete_message_for_all_users($teacher->id, $messageidgrp)); } /** * Tests the user can delete message for all users as a student. */ public function test_can_delete_message_for_all_users_student(): void { $this->resetAfterTest(true); // Create fake data to test it. [$teacher, $student1, $student2, $convgroup, $convindividual] = $this->create_delete_message_test_data(); // Set as the first user. $this->setUser($student1); // Send a message to private conversation and in a group conversation. $messageidind = testhelper::send_fake_message_to_conversation($teacher, $convindividual->id); $messageidgrp = testhelper::send_fake_message_to_conversation($teacher, $convgroup->id); // Student1 cannot delete message for everyone in a private conversation. $this->assertFalse(api::can_delete_message_for_all_users($student1->id, $messageidind)); // Student1 cannot delete message for everyone in a group conversation. $this->assertFalse(api::can_delete_message_for_all_users($student1->id, $messageidgrp)); } /** * Tests tdelete message for all users in group conversation. */ public function test_delete_message_for_all_users_group_conversation(): void { global $DB; $this->resetAfterTest(true); // Create fake data to test it. [$teacher, $student1, $student2, $convgroup, $convindividual] = $this->create_delete_message_test_data(); // Send 3 messages to a group conversation. $mgid1 = testhelper::send_fake_message_to_conversation($teacher, $convgroup->id); $mgid2 = testhelper::send_fake_message_to_conversation($student1, $convgroup->id); $mgid3 = testhelper::send_fake_message_to_conversation($student2, $convgroup->id); // Delete message 1 for all users. api::delete_message_for_all_users($mgid1); // Get the messages to check if the message 1 was deleted for teacher. $convmessages1 = api::get_conversation_messages($teacher->id, $convgroup->id); // Only has to remains 2 messages. $this->assertCount(2, $convmessages1['messages']); // Check if no one of the two messages is message 1. foreach ($convmessages1['messages'] as $message) { $this->assertNotEquals($mgid1, $message->id); } // Get the messages to check if the message 1 was deleted for student1. $convmessages2 = api::get_conversation_messages($student1->id, $convgroup->id); // Only has to remains 2 messages. $this->assertCount(2, $convmessages2['messages']); // Check if no one of the two messages is message 1. foreach ($convmessages2['messages'] as $message) { $this->assertNotEquals($mgid1, $message->id); } // Get the messages to check if the message 1 was deleted for student2. $convmessages3 = api::get_conversation_messages($student2->id, $convgroup->id); // Only has to remains 2 messages. $this->assertCount(2, $convmessages3['messages']); // Check if no one of the two messages is message 1. foreach ($convmessages3['messages'] as $message) { $this->assertNotEquals($mgid1, $message->id); } } /** * Tests delete message for all users in private conversation. */ public function test_delete_message_for_all_users_individual_conversation(): void { global $DB; $this->resetAfterTest(true); // Create fake data to test it. [$teacher, $student1, $student2, $convgroup, $convindividual] = $this->create_delete_message_test_data(); // Send 2 messages in a individual conversation. $mid1 = testhelper::send_fake_message_to_conversation($teacher, $convindividual->id); $mid2 = testhelper::send_fake_message_to_conversation($student1, $convindividual->id); // Delete the first message for all users. api::delete_message_for_all_users($mid1); // Get the messages to check if the message 1 was deleted for teacher. $convmessages1 = api::get_conversation_messages($teacher->id, $convindividual->id); // Only has to remains 1 messages for teacher. $this->assertCount(1, $convmessages1['messages']); // Check the one messages remains not is the first message. $this->assertNotEquals($mid1, $convmessages1['messages'][0]->id); // Get the messages to check if the message 1 was deleted for student1. $convmessages2 = api::get_conversation_messages($student1->id, $convindividual->id); // Only has to remains 1 messages for student1. $this->assertCount(1, $convmessages2['messages']); // Check the one messages remains not is the first message. $this->assertNotEquals($mid1, $convmessages2['messages'][0]->id); } /** * Test retrieving conversation messages by providing a timefrom higher than last message timecreated. It should return no * messages but keep the return structure to not break when called from the ws. */ public function test_get_conversation_messages_timefrom_higher_than_last_timecreated(): void { $this->resetAfterTest(); // Create some users. $user1 = self::getDataGenerator()->create_user(); $user2 = self::getDataGenerator()->create_user(); $user3 = self::getDataGenerator()->create_user(); $user4 = self::getDataGenerator()->create_user(); // Create group conversation. $conversation = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_GROUP, [$user1->id, $user2->id, $user3->id, $user4->id] ); // The person doing the search. $this->setUser($user1); // Send some messages back and forth. $time = 1; testhelper::send_fake_message_to_conversation($user1, $conversation->id, 'Message 1', $time + 1); testhelper::send_fake_message_to_conversation($user2, $conversation->id, 'Message 2', $time + 2); testhelper::send_fake_message_to_conversation($user1, $conversation->id, 'Message 3', $time + 3); testhelper::send_fake_message_to_conversation($user3, $conversation->id, 'Message 4', $time + 4); // Retrieve the messages from $time + 5, which should return no messages. $convmessages = api::get_conversation_messages($user1->id, $conversation->id, 0, 0, '', $time + 5); // Confirm the conversation id is correct. $this->assertEquals($conversation->id, $convmessages['id']); // Confirm the message data is correct. $messages = $convmessages['messages']; $this->assertEquals(0, count($messages)); // Confirm that members key is present. $this->assertArrayHasKey('members', $convmessages); } /** * Helper to seed the database with initial state with data. */ protected function create_delete_message_test_data() { // Create some users. $teacher = self::getDataGenerator()->create_user(); $student1 = self::getDataGenerator()->create_user(); $student2 = self::getDataGenerator()->create_user(); // Create a course and enrol the users. $course = $this->getDataGenerator()->create_course(); $coursecontext = \context_course::instance($course->id); $this->getDataGenerator()->enrol_user($teacher->id, $course->id, 'editingteacher'); $this->getDataGenerator()->enrol_user($student1->id, $course->id, 'student'); $this->getDataGenerator()->enrol_user($student2->id, $course->id, 'student'); // Create a group and added the users into. $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]); groups_add_member($group1->id, $teacher->id); groups_add_member($group1->id, $student1->id); groups_add_member($group1->id, $student2->id); // Create a group conversation linked with the course. $convgroup = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_GROUP, [$teacher->id, $student1->id, $student2->id], 'Group test delete for everyone', api::MESSAGE_CONVERSATION_ENABLED, 'core_group', 'groups', $group1->id, \context_course::instance($course->id)->id ); // Create and individual conversation. $convindividual = api::create_conversation( api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, [$teacher->id, $student1->id] ); return [$teacher, $student1, $student2, $convgroup, $convindividual]; } /** * Comparison function for sorting contacts. * * @param \stdClass $a * @param \stdClass $b * @return bool */ protected static function sort_contacts($a, $b) { return $a->userid > $b->userid; } } home/smkb8269/public_html/lms.smk-almanshuriyah.sch.id/competency/tests/api_test.php000064400000710326152266424560024537 0ustar00. namespace core_competency; /** * API tests. * * @package core_competency * @copyright 2015 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class api_test extends \advanced_testcase { public function test_get_framework_related_contexts(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $cat1 = $dg->create_category(); $cat2 = $dg->create_category(array('parent' => $cat1->id)); $cat3 = $dg->create_category(array('parent' => $cat2->id)); $c1 = $dg->create_course(array('category' => $cat2->id)); // This context should not be returned. $cat1ctx = \context_coursecat::instance($cat1->id); $cat2ctx = \context_coursecat::instance($cat2->id); $cat3ctx = \context_coursecat::instance($cat3->id); $sysctx = \context_system::instance(); $expected = array($cat1ctx->id => $cat1ctx); $this->assertEquals($expected, api::get_related_contexts($cat1ctx, 'self')); $expected = array($cat1ctx->id => $cat1ctx, $cat2ctx->id => $cat2ctx, $cat3ctx->id => $cat3ctx); $this->assertEquals($expected, api::get_related_contexts($cat1ctx, 'children')); $expected = array($sysctx->id => $sysctx, $cat1ctx->id => $cat1ctx, $cat2ctx->id => $cat2ctx); $this->assertEquals($expected, api::get_related_contexts($cat2ctx, 'parents')); } public function test_get_framework_related_contexts_with_capabilities(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $user = $dg->create_user(); $cat1 = $dg->create_category(); $cat2 = $dg->create_category(array('parent' => $cat1->id)); $cat3 = $dg->create_category(array('parent' => $cat2->id)); $c1 = $dg->create_course(array('category' => $cat2->id)); // This context should not be returned. $cat1ctx = \context_coursecat::instance($cat1->id); $cat2ctx = \context_coursecat::instance($cat2->id); $cat3ctx = \context_coursecat::instance($cat3->id); $sysctx = \context_system::instance(); $roleallow = create_role('Allow', 'allow', 'Allow read'); assign_capability('moodle/competency:competencyview', CAP_ALLOW, $roleallow, $sysctx->id); role_assign($roleallow, $user->id, $sysctx->id); $roleprevent = create_role('Prevent', 'prevent', 'Prevent read'); assign_capability('moodle/competency:competencyview', CAP_PROHIBIT, $roleprevent, $sysctx->id); role_assign($roleprevent, $user->id, $cat2ctx->id); accesslib_clear_all_caches_for_unit_testing(); $this->setUser($user); $this->assertFalse(has_capability('moodle/competency:competencyview', $cat2ctx)); $requiredcap = array('moodle/competency:competencyview'); $expected = array(); $this->assertEquals($expected, api::get_related_contexts($cat2ctx, 'self', $requiredcap)); $expected = array($cat1ctx->id => $cat1ctx); $this->assertEquals($expected, api::get_related_contexts($cat1ctx, 'children', $requiredcap)); $expected = array($sysctx->id => $sysctx, $cat1ctx->id => $cat1ctx); $this->assertEquals($expected, api::get_related_contexts($cat2ctx, 'parents', $requiredcap)); } public function test_get_template_related_contexts(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $cat1 = $dg->create_category(); $cat2 = $dg->create_category(array('parent' => $cat1->id)); $cat3 = $dg->create_category(array('parent' => $cat2->id)); $c1 = $dg->create_course(array('category' => $cat2->id)); // This context should not be returned. $cat1ctx = \context_coursecat::instance($cat1->id); $cat2ctx = \context_coursecat::instance($cat2->id); $cat3ctx = \context_coursecat::instance($cat3->id); $sysctx = \context_system::instance(); $expected = array($cat1ctx->id => $cat1ctx); $this->assertEquals($expected, api::get_related_contexts($cat1ctx, 'self')); $expected = array($cat1ctx->id => $cat1ctx, $cat2ctx->id => $cat2ctx, $cat3ctx->id => $cat3ctx); $this->assertEquals($expected, api::get_related_contexts($cat1ctx, 'children')); $expected = array($sysctx->id => $sysctx, $cat1ctx->id => $cat1ctx, $cat2ctx->id => $cat2ctx); $this->assertEquals($expected, api::get_related_contexts($cat2ctx, 'parents')); } public function test_get_template_related_contexts_with_capabilities(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $user = $dg->create_user(); $cat1 = $dg->create_category(); $cat2 = $dg->create_category(array('parent' => $cat1->id)); $cat3 = $dg->create_category(array('parent' => $cat2->id)); $c1 = $dg->create_course(array('category' => $cat2->id)); // This context should not be returned. $cat1ctx = \context_coursecat::instance($cat1->id); $cat2ctx = \context_coursecat::instance($cat2->id); $cat3ctx = \context_coursecat::instance($cat3->id); $sysctx = \context_system::instance(); $roleallow = create_role('Allow', 'allow', 'Allow read'); assign_capability('moodle/competency:templateview', CAP_ALLOW, $roleallow, $sysctx->id); role_assign($roleallow, $user->id, $sysctx->id); $roleprevent = create_role('Prevent', 'prevent', 'Prevent read'); assign_capability('moodle/competency:templateview', CAP_PROHIBIT, $roleprevent, $sysctx->id); role_assign($roleprevent, $user->id, $cat2ctx->id); accesslib_clear_all_caches_for_unit_testing(); $this->setUser($user); $this->assertFalse(has_capability('moodle/competency:templateview', $cat2ctx)); $requiredcap = array('moodle/competency:templateview'); $expected = array(); $this->assertEquals($expected, api::get_related_contexts($cat2ctx, 'self', $requiredcap)); $expected = array($cat1ctx->id => $cat1ctx); $this->assertEquals($expected, api::get_related_contexts($cat1ctx, 'children', $requiredcap)); $expected = array($sysctx->id => $sysctx, $cat1ctx->id => $cat1ctx); $this->assertEquals($expected, api::get_related_contexts($cat2ctx, 'parents', $requiredcap)); } /** * Test updating a template. */ public function test_update_template(): void { $cat = $this->getDataGenerator()->create_category(); $this->resetAfterTest(true); $this->setAdminUser(); $syscontext = \context_system::instance(); $template = api::create_template((object) array('shortname' => 'testing', 'contextid' => $syscontext->id)); $this->assertEquals('testing', $template->get('shortname')); $this->assertEquals($syscontext->id, $template->get('contextid')); // Simple update. api::update_template((object) array('id' => $template->get('id'), 'shortname' => 'success')); $template = api::read_template($template->get('id')); $this->assertEquals('success', $template->get('shortname')); // Trying to change the context. $this->expectException(\coding_exception::class); api::update_template((object) ['id' => $template->get('id'), 'contextid' => \context_coursecat::instance($cat->id)->id]); } /** * Test listing framework with order param. */ public function test_list_frameworks(): void { $this->resetAfterTest(true); $this->setAdminUser(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); // Create a list of frameworks. $framework1 = $lpg->create_framework(array( 'shortname' => 'shortname_alpha', 'idnumber' => 'idnumber_cinnamon', 'description' => 'description', 'descriptionformat' => FORMAT_HTML, 'visible' => true, 'contextid' => \context_system::instance()->id )); $framework2 = $lpg->create_framework(array( 'shortname' => 'shortname_beetroot', 'idnumber' => 'idnumber_apple', 'description' => 'description', 'descriptionformat' => FORMAT_HTML, 'visible' => true, 'contextid' => \context_system::instance()->id )); $framework3 = $lpg->create_framework(array( 'shortname' => 'shortname_crisps', 'idnumber' => 'idnumber_beer', 'description' => 'description', 'descriptionformat' => FORMAT_HTML, 'visible' => false, 'contextid' => \context_system::instance()->id )); // Get frameworks list order by shortname desc. $result = api::list_frameworks('shortname', 'DESC', null, 3, \context_system::instance()); $f = (object) array_shift($result); $this->assertEquals($framework3->get('id'), $f->get('id')); $f = (object) array_shift($result); $this->assertEquals($framework2->get('id'), $f->get('id')); $f = (object) array_shift($result); $this->assertEquals($framework1->get('id'), $f->get('id')); // Get frameworks list order by idnumber asc. $result = api::list_frameworks('idnumber', 'ASC', null, 3, \context_system::instance()); $f = (object) array_shift($result); $this->assertEquals($framework2->get('id'), $f->get('id')); $f = (object) array_shift($result); $this->assertEquals($framework3->get('id'), $f->get('id')); $f = (object) array_shift($result); $this->assertEquals($framework1->get('id'), $f->get('id')); // Repeat excluding the non-visible ones. $result = api::list_frameworks('idnumber', 'ASC', null, 3, \context_system::instance(), 'self', true); $this->assertCount(2, $result); $f = (object) array_shift($result); $this->assertEquals($framework2->get('id'), $f->get('id')); $f = (object) array_shift($result); $this->assertEquals($framework1->get('id'), $f->get('id')); // Search by query string, trying match on shortname. $result = api::list_frameworks('idnumber', 'ASC', null, 3, \context_system::instance(), 'self', false, 'crisp'); $this->assertCount(1, $result); $f = (object) array_shift($result); $this->assertEquals($framework3->get('id'), $f->get('id')); // Search by query string, trying match on shortname, but hidden. $result = api::list_frameworks('idnumber', 'ASC', null, 3, \context_system::instance(), 'self', true, 'crisp'); $this->assertCount(0, $result); // Search by query string, trying match on ID number. $result = api::list_frameworks('idnumber', 'ASC', null, 3, \context_system::instance(), 'self', false, 'apple'); $this->assertCount(1, $result); $f = (object) array_shift($result); $this->assertEquals($framework2->get('id'), $f->get('id')); // Search by query string, trying match on both. $result = api::list_frameworks('idnumber', 'ASC', null, 3, \context_system::instance(), 'self', false, 'bee'); $this->assertCount(2, $result); $f = (object) array_shift($result); $this->assertEquals($framework2->get('id'), $f->get('id')); $f = (object) array_shift($result); $this->assertEquals($framework3->get('id'), $f->get('id')); } /** * Test duplicate a framework. */ public function test_duplicate_framework(): void { $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $this->resetAfterTest(true); $this->setAdminUser(); $syscontext = \context_system::instance(); $params = array( 'shortname' => 'shortname_a', 'idnumber' => 'idnumber_c', 'description' => 'description', 'descriptionformat' => FORMAT_HTML, 'visible' => true, 'contextid' => $syscontext->id ); $framework = $lpg->create_framework($params); $competency1 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $competency2 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $competency3 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $competency4 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $competency41 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'), 'parentid' => $competency4->get('id')) ); $competency42 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'), 'parentid' => $competency4->get('id')) ); $competencyidnumbers = array($competency1->get('idnumber'), $competency2->get('idnumber'), $competency3->get('idnumber'), $competency4->get('idnumber'), $competency41->get('idnumber'), $competency42->get('idnumber') ); $config = json_encode(array( 'base' => array('points' => 4), 'competencies' => array( array('id' => $competency41->get('id'), 'points' => 3, 'required' => 0), array('id' => $competency42->get('id'), 'points' => 2, 'required' => 1), ) )); $competency4->set('ruletype', 'core_competency\competency_rule_points'); $competency4->set('ruleoutcome', \core_competency\competency::OUTCOME_EVIDENCE); $competency4->set('ruleconfig', $config); $competency4->update(); api::add_related_competency($competency1->get('id'), $competency2->get('id')); api::add_related_competency($competency3->get('id'), $competency4->get('id')); $frameworkduplicated1 = api::duplicate_framework($framework->get('id')); $frameworkduplicated2 = api::duplicate_framework($framework->get('id')); $this->assertEquals($framework->get('idnumber').'_1', $frameworkduplicated1->get('idnumber')); $this->assertEquals($framework->get('idnumber').'_2', $frameworkduplicated2->get('idnumber')); $competenciesfr1 = api::list_competencies(array('competencyframeworkid' => $frameworkduplicated1->get('id'))); $competenciesfr2 = api::list_competencies(array('competencyframeworkid' => $frameworkduplicated2->get('id'))); $competencyidsfr1 = array(); $competencyidsfr2 = array(); foreach ($competenciesfr1 as $cmp) { $competencyidsfr1[] = $cmp->get('idnumber'); } foreach ($competenciesfr2 as $cmp) { $competencyidsfr2[] = $cmp->get('idnumber'); } $this->assertEmpty(array_diff($competencyidsfr1, $competencyidnumbers)); $this->assertEmpty(array_diff($competencyidsfr2, $competencyidnumbers)); $this->assertCount(6, $competenciesfr1); $this->assertCount(6, $competenciesfr2); // Test the related competencies. reset($competenciesfr1); $compduplicated1 = current($competenciesfr1); $relatedcompetencies = $compduplicated1->get_related_competencies(); $comprelated = current($relatedcompetencies); $this->assertEquals($comprelated->get('idnumber'), $competency2->get('idnumber')); // Check if config rule have been ported correctly. $competency4duplicated = competency::get_record(array( 'idnumber' => $competency4->get('idnumber'), 'competencyframeworkid' => $frameworkduplicated2->get('id') )); $configduplicated = json_decode($competency4duplicated->get('ruleconfig'), true); $configorigin = json_decode($config, true); // Check that the 2 config have the same base. $this->assertEquals($configorigin['base'], $configduplicated['base']); $this->assertEquals(count($configorigin['competencies']), count($configduplicated['competencies'])); $competencyidsrules = array(); foreach ($configduplicated['competencies'] as $key => $value) { // Check that the only difference between the 2 config is id competency. $this->assertEquals(1, count(array_diff($value, $configorigin['competencies'][$key]))); $competencyidsrules[] = $value['id']; } $this->assertTrue($competency4duplicated->is_parent_of($competencyidsrules)); // Test duplicate an empty framework. $emptyfrm = $lpg->create_framework(); $emptyfrmduplicated = api::duplicate_framework($emptyfrm->get('id')); $this->assertEquals($emptyfrm->get('idnumber').'_1', $emptyfrmduplicated->get('idnumber')); $nbcomp = api::count_competencies(array('competencyframeworkid' => $emptyfrmduplicated->get('id'))); $this->assertEquals(0, $nbcomp); } /** * Test update plan. */ public function test_update_plan(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $usermanageowndraft = $dg->create_user(); $usermanageown = $dg->create_user(); $usermanagedraft = $dg->create_user(); $usermanage = $dg->create_user(); $syscontext = \context_system::instance(); // Creating specific roles. $manageowndraftrole = $dg->create_role(array( 'name' => 'User manage own draft', 'shortname' => 'manage-own-draft' )); $manageownrole = $dg->create_role(array( 'name' => 'User manage own', 'shortname' => 'manage-own' )); $managedraftrole = $dg->create_role(array( 'name' => 'User manage draft', 'shortname' => 'manage-draft' )); $managerole = $dg->create_role(array( 'name' => 'User manage', 'shortname' => 'manage' )); assign_capability('moodle/competency:planmanageowndraft', CAP_ALLOW, $manageowndraftrole, $syscontext->id); assign_capability('moodle/competency:planviewowndraft', CAP_ALLOW, $manageowndraftrole, $syscontext->id); assign_capability('moodle/competency:planmanageown', CAP_ALLOW, $manageownrole, $syscontext->id); assign_capability('moodle/competency:planviewown', CAP_ALLOW, $manageownrole, $syscontext->id); assign_capability('moodle/competency:planmanagedraft', CAP_ALLOW, $managedraftrole, $syscontext->id); assign_capability('moodle/competency:planviewdraft', CAP_ALLOW, $managedraftrole, $syscontext->id); assign_capability('moodle/competency:planmanage', CAP_ALLOW, $managerole, $syscontext->id); assign_capability('moodle/competency:planview', CAP_ALLOW, $managerole, $syscontext->id); $dg->role_assign($manageowndraftrole, $usermanageowndraft->id, $syscontext->id); $dg->role_assign($manageownrole, $usermanageown->id, $syscontext->id); $dg->role_assign($managedraftrole, $usermanagedraft->id, $syscontext->id); $dg->role_assign($managerole, $usermanage->id, $syscontext->id); // Create first learning plan with user create draft. $this->setUser($usermanageowndraft); $plan = array ( 'name' => 'plan own draft', 'description' => 'plan own draft', 'userid' => $usermanageowndraft->id ); $plan = api::create_plan((object)$plan); $record = $plan->to_record(); $record->name = 'plan own draft modified'; // Check if user create draft can edit the plan name. $plan = api::update_plan($record); $this->assertInstanceOf('\core_competency\plan', $plan); // The status cannot be changed in this method. $record->status = \core_competency\plan::STATUS_ACTIVE; try { $plan = api::update_plan($record); $this->fail('Updating the status is not allowed.'); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression('/To change the status of a plan use the appropriate methods./', $e->getMessage()); } // Test when user with manage own plan capability try to edit other user plan. $record->status = \core_competency\plan::STATUS_DRAFT; $record->name = 'plan create draft modified 2'; $this->setUser($usermanageown); try { $plan = api::update_plan($record); $this->fail('User with manage own plan capability can only edit his own plan.'); } catch (\required_capability_exception $e) { $this->assertTrue(true); } // User with manage plan capability cannot edit the other user plans with status draft. $this->setUser($usermanage); $record->name = 'plan create draft modified 3'; try { $plan = api::update_plan($record); $this->fail('User with manage plan capability cannot edit the other user plans with status draft'); } catch (\required_capability_exception $e) { $this->assertTrue(true); } // User with manage draft capability can edit other user's learning plan if the status is draft. $this->setUser($usermanagedraft); $record->status = \core_competency\plan::STATUS_DRAFT; $record->name = 'plan manage draft modified 3'; $plan = api::update_plan($record); $this->assertInstanceOf('\core_competency\plan', $plan); // User with manage plan capability can create/edit learning plan if status is active/complete. $this->setUser($usermanage); $plan = array ( 'name' => 'plan create', 'description' => 'plan create', 'userid' => $usermanage->id, 'status' => \core_competency\plan::STATUS_ACTIVE ); $plan = api::create_plan((object)$plan); // Silently transition to complete status to avoid errors about transitioning to complete. $plan->set('status', \core_competency\plan::STATUS_COMPLETE); $plan->update(); $record = $plan->to_record(); $record->name = 'plan create own modified'; try { api::update_plan($record); $this->fail('Completed plan can not be edited'); } catch (\coding_exception $e) { $this->assertTrue(true); } } public function test_create_plan_from_template(): void { $this->resetAfterTest(true); $this->setAdminUser(); $u1 = $this->getDataGenerator()->create_user(); $tpl = $this->getDataGenerator()->get_plugin_generator('core_competency')->create_template(); // Creating a new plan. $plan = api::create_plan_from_template($tpl, $u1->id); $record = $plan->to_record(); $this->assertInstanceOf('\core_competency\plan', $plan); $this->assertTrue(\core_competency\plan::record_exists($plan->get('id'))); $this->assertEquals($tpl->get('id'), $plan->get('templateid')); $this->assertEquals($u1->id, $plan->get('userid')); $this->assertTrue($plan->is_based_on_template()); // Creating a plan that already exists. $plan = api::create_plan_from_template($tpl, $u1->id); $this->assertFalse($plan); // Check that api::create_plan cannot be used. unset($record->id); $this->expectException(\coding_exception::class); $plan = api::create_plan($record); } public function test_update_plan_based_on_template(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $u1 = $dg->create_user(); $u2 = $dg->create_user(); $this->setAdminUser(); $tpl1 = $lpg->create_template(); $tpl2 = $lpg->create_template(); $up1 = $lpg->create_plan(array('userid' => $u1->id, 'templateid' => $tpl1->get('id'))); $up2 = $lpg->create_plan(array('userid' => $u2->id, 'templateid' => null)); try { // Trying to remove the template dependency. $record = $up1->to_record(); $record->templateid = null; api::update_plan($record); $this->fail('A plan cannot be unlinked using api::update_plan()'); } catch (\coding_exception $e) { // All good. } try { // Trying to switch to another template. $record = $up1->to_record(); $record->templateid = $tpl2->get('id'); api::update_plan($record); $this->fail('A plan cannot be moved to another template.'); } catch (\coding_exception $e) { // All good. } try { // Trying to switch to using a template. $record = $up2->to_record(); $record->templateid = $tpl1->get('id'); api::update_plan($record); $this->fail('A plan cannot be update to use a template.'); } catch (\coding_exception $e) { // All good. } } public function test_unlink_plan_from_template(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $u1 = $dg->create_user(); $u2 = $dg->create_user(); $this->setAdminUser(); $f1 = $lpg->create_framework(); $f2 = $lpg->create_framework(); $c1a = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c2a = $lpg->create_competency(array('competencyframeworkid' => $f2->get('id'))); $c1b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $tpl1 = $lpg->create_template(); $tpl2 = $lpg->create_template(); $tplc1a = $lpg->create_template_competency(array('templateid' => $tpl1->get('id'), 'competencyid' => $c1a->get('id'), 'sortorder' => 9)); $tplc1b = $lpg->create_template_competency(array('templateid' => $tpl1->get('id'), 'competencyid' => $c1b->get('id'), 'sortorder' => 8)); $tplc2a = $lpg->create_template_competency(array('templateid' => $tpl2->get('id'), 'competencyid' => $c2a->get('id'))); $plan1 = $lpg->create_plan(array('userid' => $u1->id, 'templateid' => $tpl1->get('id'), 'status' => plan::STATUS_ACTIVE)); $plan2 = $lpg->create_plan(array('userid' => $u2->id, 'templateid' => $tpl2->get('id'))); $plan3 = $lpg->create_plan(array('userid' => $u1->id, 'templateid' => $tpl1->get('id'), 'status' => plan::STATUS_COMPLETE)); // Check that we have what we expect at this stage. $this->assertEquals(2, \core_competency\template_competency::count_records(array('templateid' => $tpl1->get('id')))); $this->assertEquals(1, \core_competency\template_competency::count_records(array('templateid' => $tpl2->get('id')))); $this->assertEquals(0, \core_competency\plan_competency::count_records(array('planid' => $plan1->get('id')))); $this->assertEquals(0, \core_competency\plan_competency::count_records(array('planid' => $plan2->get('id')))); $this->assertTrue($plan1->is_based_on_template()); $this->assertTrue($plan2->is_based_on_template()); // Let's do this! $tpl1comps = \core_competency\template_competency::list_competencies($tpl1->get('id'), true); $tpl2comps = \core_competency\template_competency::list_competencies($tpl2->get('id'), true); api::unlink_plan_from_template($plan1); $plan1->read(); $plan2->read(); $this->assertCount(2, $tpl1comps); $this->assertCount(1, $tpl2comps); $this->assertEquals(2, \core_competency\template_competency::count_records(array('templateid' => $tpl1->get('id')))); $this->assertEquals(1, \core_competency\template_competency::count_records(array('templateid' => $tpl2->get('id')))); $this->assertEquals(2, \core_competency\plan_competency::count_records(array('planid' => $plan1->get('id')))); $this->assertEquals(0, \core_competency\plan_competency::count_records(array('planid' => $plan2->get('id')))); $this->assertFalse($plan1->is_based_on_template()); $this->assertEquals($tpl1->get('id'), $plan1->get('origtemplateid')); $this->assertTrue($plan2->is_based_on_template()); $this->assertEquals(null, $plan2->get('origtemplateid')); // Check we can unlink draft plan. try { api::unlink_plan_from_template($plan2); } catch (\coding_exception $e) { $this->fail('Fail to unlink draft plan.'); } // Check we can not unlink completed plan. try { api::unlink_plan_from_template($plan3); $this->fail('We can not unlink completed plan.'); } catch (\coding_exception $e) { // All good. } // Even the order remains. $plan1comps = \core_competency\plan_competency::list_competencies($plan1->get('id')); $before = reset($tpl1comps); $after = reset($plan1comps); $this->assertEquals($before->get('id'), $after->get('id')); $this->assertEquals($before->get('sortorder'), $after->get('sortorder')); $before = next($tpl1comps); $after = next($plan1comps); $this->assertEquals($before->get('id'), $after->get('id')); $this->assertEquals($before->get('sortorder'), $after->get('sortorder')); } public function test_update_template_updates_plans(): void { $this->resetAfterTest(true); $this->setAdminUser(); $dg = $this->getDataGenerator(); $u1 = $dg->create_user(); $u2 = $dg->create_user(); $lpg = $dg->get_plugin_generator('core_competency'); $tpl1 = $lpg->create_template(); $tpl2 = $lpg->create_template(); // Create plans with data not matching templates. $time = time(); $plan1 = $lpg->create_plan(array('templateid' => $tpl1->get('id'), 'userid' => $u1->id, 'name' => 'Not good name', 'duedate' => $time + 3600, 'description' => 'Ahah', 'descriptionformat' => FORMAT_MARKDOWN)); $plan2 = $lpg->create_plan(array('templateid' => $tpl1->get('id'), 'userid' => $u2->id, 'name' => 'Not right name', 'duedate' => $time + 3601, 'description' => 'Ahah', 'descriptionformat' => FORMAT_PLAIN)); $plan3 = $lpg->create_plan(array('templateid' => $tpl2->get('id'), 'userid' => $u1->id, 'name' => 'Not sweet name', 'duedate' => $time + 3602, 'description' => 'Ahah', 'descriptionformat' => FORMAT_PLAIN)); // Prepare our expectations. $plan1->read(); $plan2->read(); $plan3->read(); $this->assertEquals($tpl1->get('id'), $plan1->get('templateid')); $this->assertEquals($tpl1->get('id'), $plan2->get('templateid')); $this->assertEquals($tpl2->get('id'), $plan3->get('templateid')); $this->assertNotEquals($tpl1->get('shortname'), $plan1->get('name')); $this->assertNotEquals($tpl1->get('shortname'), $plan2->get('name')); $this->assertNotEquals($tpl2->get('shortname'), $plan3->get('name')); $this->assertNotEquals($tpl1->get('description'), $plan1->get('description')); $this->assertNotEquals($tpl1->get('description'), $plan2->get('description')); $this->assertNotEquals($tpl2->get('description'), $plan3->get('description')); $this->assertNotEquals($tpl1->get('descriptionformat'), $plan1->get('descriptionformat')); $this->assertNotEquals($tpl1->get('descriptionformat'), $plan2->get('descriptionformat')); $this->assertNotEquals($tpl2->get('descriptionformat'), $plan3->get('descriptionformat')); $this->assertNotEquals($tpl1->get('duedate'), $plan1->get('duedate')); $this->assertNotEquals($tpl1->get('duedate'), $plan2->get('duedate')); $this->assertNotEquals($tpl2->get('duedate'), $plan3->get('duedate')); // Update the template without changing critical fields does not update the plans. $data = $tpl1->to_record(); $data->visible = 0; api::update_template($data); $this->assertNotEquals($tpl1->get('shortname'), $plan1->get('name')); $this->assertNotEquals($tpl1->get('shortname'), $plan2->get('name')); $this->assertNotEquals($tpl2->get('shortname'), $plan3->get('name')); $this->assertNotEquals($tpl1->get('description'), $plan1->get('description')); $this->assertNotEquals($tpl1->get('description'), $plan2->get('description')); $this->assertNotEquals($tpl2->get('description'), $plan3->get('description')); $this->assertNotEquals($tpl1->get('descriptionformat'), $plan1->get('descriptionformat')); $this->assertNotEquals($tpl1->get('descriptionformat'), $plan2->get('descriptionformat')); $this->assertNotEquals($tpl2->get('descriptionformat'), $plan3->get('descriptionformat')); $this->assertNotEquals($tpl1->get('duedate'), $plan1->get('duedate')); $this->assertNotEquals($tpl1->get('duedate'), $plan2->get('duedate')); $this->assertNotEquals($tpl2->get('duedate'), $plan3->get('duedate')); // Now really update the template. $data = $tpl1->to_record(); $data->shortname = 'Awesome!'; $data->description = 'This is too awesome!'; $data->descriptionformat = FORMAT_HTML; $data->duedate = $time + 200; api::update_template($data); $tpl1->read(); // Now confirm that the right plans were updated. $plan1->read(); $plan2->read(); $plan3->read(); $this->assertEquals($tpl1->get('id'), $plan1->get('templateid')); $this->assertEquals($tpl1->get('id'), $plan2->get('templateid')); $this->assertEquals($tpl2->get('id'), $plan3->get('templateid')); $this->assertEquals($tpl1->get('shortname'), $plan1->get('name')); $this->assertEquals($tpl1->get('shortname'), $plan2->get('name')); $this->assertNotEquals($tpl2->get('shortname'), $plan3->get('name')); $this->assertEquals($tpl1->get('description'), $plan1->get('description')); $this->assertEquals($tpl1->get('description'), $plan2->get('description')); $this->assertNotEquals($tpl2->get('description'), $plan3->get('description')); $this->assertEquals($tpl1->get('descriptionformat'), $plan1->get('descriptionformat')); $this->assertEquals($tpl1->get('descriptionformat'), $plan2->get('descriptionformat')); $this->assertNotEquals($tpl2->get('descriptionformat'), $plan3->get('descriptionformat')); $this->assertEquals($tpl1->get('duedate'), $plan1->get('duedate')); $this->assertEquals($tpl1->get('duedate'), $plan2->get('duedate')); $this->assertNotEquals($tpl2->get('duedate'), $plan3->get('duedate')); } /** * Test that the method to complete a plan. */ public function test_complete_plan(): void { global $DB; $this->resetAfterTest(true); $this->setAdminUser(); $dg = $this->getDataGenerator(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $user = $dg->create_user(); // Create a framework and assign competencies. $framework = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c3 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c4 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); // Create two plans and assign competencies. $plan = $lpg->create_plan(array('userid' => $user->id)); $otherplan = $lpg->create_plan(array('userid' => $user->id)); $lpg->create_plan_competency(array('planid' => $plan->get('id'), 'competencyid' => $c1->get('id'))); $lpg->create_plan_competency(array('planid' => $plan->get('id'), 'competencyid' => $c2->get('id'))); $lpg->create_plan_competency(array('planid' => $plan->get('id'), 'competencyid' => $c3->get('id'))); $lpg->create_plan_competency(array('planid' => $otherplan->get('id'), 'competencyid' => $c1->get('id'))); $uclist = array( $lpg->create_user_competency(array('userid' => $user->id, 'competencyid' => $c1->get('id'), 'proficiency' => true, 'grade' => 1 )), $lpg->create_user_competency(array('userid' => $user->id, 'competencyid' => $c2->get('id'), 'proficiency' => false, 'grade' => 2 )) ); $this->assertEquals(2, \core_competency\user_competency::count_records()); $this->assertEquals(0, \core_competency\user_competency_plan::count_records()); // Change status of the plan to complete. api::complete_plan($plan); // Check that user competencies are now in user_competency_plan objects and still in user_competency. $this->assertEquals(2, \core_competency\user_competency::count_records()); $this->assertEquals(3, \core_competency\user_competency_plan::count_records()); $usercompetenciesplan = \core_competency\user_competency_plan::get_records(); $this->assertEquals($uclist[0]->get('userid'), $usercompetenciesplan[0]->get('userid')); $this->assertEquals($uclist[0]->get('competencyid'), $usercompetenciesplan[0]->get('competencyid')); $this->assertEquals($uclist[0]->get('proficiency'), (bool) $usercompetenciesplan[0]->get('proficiency')); $this->assertEquals($uclist[0]->get('grade'), $usercompetenciesplan[0]->get('grade')); $this->assertEquals($plan->get('id'), $usercompetenciesplan[0]->get('planid')); $this->assertEquals($uclist[1]->get('userid'), $usercompetenciesplan[1]->get('userid')); $this->assertEquals($uclist[1]->get('competencyid'), $usercompetenciesplan[1]->get('competencyid')); $this->assertEquals($uclist[1]->get('proficiency'), (bool) $usercompetenciesplan[1]->get('proficiency')); $this->assertEquals($uclist[1]->get('grade'), $usercompetenciesplan[1]->get('grade')); $this->assertEquals($plan->get('id'), $usercompetenciesplan[1]->get('planid')); $this->assertEquals($user->id, $usercompetenciesplan[2]->get('userid')); $this->assertEquals($c3->get('id'), $usercompetenciesplan[2]->get('competencyid')); $this->assertNull($usercompetenciesplan[2]->get('proficiency')); $this->assertNull($usercompetenciesplan[2]->get('grade')); $this->assertEquals($plan->get('id'), $usercompetenciesplan[2]->get('planid')); // Check we can not add competency to completed plan. try { api::add_competency_to_plan($plan->get('id'), $c4->get('id')); $this->fail('We can not add competency to completed plan.'); } catch (\coding_exception $e) { // All good. } // Check we can not remove competency to completed plan. try { api::remove_competency_from_plan($plan->get('id'), $c3->get('id')); $this->fail('We can not remove competency to completed plan.'); } catch (\coding_exception $e) { // All good. } // Completing a plan that is completed throws an exception. $this->expectException(\coding_exception::class); api::complete_plan($plan); } /** * Set-up the workflow data (review, active, ...). * * @return array */ protected function setup_workflow_data() { $this->resetAfterTest(); $dg = $this->getDataGenerator(); $user = $dg->create_user(); $reviewer = $dg->create_user(); $otheruser = $dg->create_user(); $syscontext = \context_system::instance(); $userrole = $dg->create_role(); $reviewerrole = $dg->create_role(); $otheruserrole = $dg->create_role(); assign_capability('moodle/competency:planmanageowndraft', CAP_ALLOW, $userrole, $syscontext->id); assign_capability('moodle/competency:planmanage', CAP_ALLOW, $reviewerrole, $syscontext->id); assign_capability('moodle/competency:planviewdraft', CAP_ALLOW, $reviewerrole, $syscontext->id); $dg->role_assign($userrole, $user->id, $syscontext->id); $dg->role_assign($reviewerrole, $reviewer->id, $syscontext->id); accesslib_clear_all_caches_for_unit_testing(); $lpg = $dg->get_plugin_generator('core_competency'); $tpl = $lpg->create_template(); $plan = $lpg->create_plan(array('userid' => $user->id)); $tplplan = $lpg->create_plan(array('userid' => $user->id, 'templateid' => $tpl->get('id'))); return array( 'dg' => $dg, 'lpg' => $lpg, 'user' => $user, 'reviewer' => $reviewer, 'otheruser' => $otheruser, 'plan' => $plan, 'tplplan' => $tplplan, ); } /** * Testing requesting the review of a plan. */ public function test_plan_request_review(): void { $data = $this->setup_workflow_data(); $dg = $data['dg']; $lpg = $data['lpg']; $user = $data['user']; $reviewer = $data['reviewer']; $otheruser = $data['otheruser']; $plan = $data['plan']; $tplplan = $data['tplplan']; $this->assertEquals(plan::STATUS_DRAFT, $plan->get('status')); $this->assertEquals(plan::STATUS_DRAFT, $tplplan->get('status')); // Foreign user cannot do anything. $this->setUser($otheruser); try { api::plan_request_review($plan); $this->fail('The user can not read the plan.'); } catch (\required_capability_exception $e) { $this->assertEquals('nopermissions', $e->errorcode); } // Can not change a plan based on a template. $this->setUser($user); try { api::plan_request_review($tplplan); $this->fail('The plan is based on a template.'); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression('/Template plans cannot be reviewed./', $e->getMessage()); } // Can not send for review when not draft. $this->setUser($user); $plan->set('status', plan::STATUS_WAITING_FOR_REVIEW); try { api::plan_request_review($plan); $this->fail('The plan cannot be sent for review at this stage.'); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression('/The plan cannot be sent for review at this stage./', $e->getMessage()); } // Can not send for review when not draft. $this->setUser($user); $plan->set('status', plan::STATUS_ACTIVE); try { api::plan_request_review($plan); $this->fail('The plan cannot be sent for review at this stage.'); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression('/The plan cannot be sent for review at this stage./', $e->getMessage()); } // Can not send for review when not draft. $this->setUser($user); $plan->set('status', plan::STATUS_IN_REVIEW); try { api::plan_request_review($plan); $this->fail('The plan cannot be sent for review at this stage.'); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression('/The plan cannot be sent for review at this stage./', $e->getMessage()); } // Can not send for review when not draft. $this->setUser($user); $plan->set('status', plan::STATUS_COMPLETE); try { api::plan_request_review($plan); $this->fail('The plan cannot be sent for review at this stage.'); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression('/The plan cannot be sent for review at this stage./', $e->getMessage()); } // Sending for review as a reviewer. $this->setUser($reviewer); $plan->set('status', plan::STATUS_DRAFT); try { api::plan_request_review($plan); $this->fail('The user can not request a review.'); } catch (\required_capability_exception $e) { $this->assertEquals('nopermissions', $e->errorcode); } // Sending for review. $this->setUser($user); api::plan_request_review($plan); $plan->read(); $this->assertEquals(plan::STATUS_WAITING_FOR_REVIEW, $plan->get('status')); // Sending for review by ID. $plan->set('status', plan::STATUS_DRAFT); $plan->update(); api::plan_request_review($plan->get('id')); $plan->read(); $this->assertEquals(plan::STATUS_WAITING_FOR_REVIEW, $plan->get('status')); } /** * Testing cancelling the review request. */ public function test_plan_cancel_review_request(): void { $data = $this->setup_workflow_data(); $dg = $data['dg']; $lpg = $data['lpg']; $user = $data['user']; $reviewer = $data['reviewer']; $otheruser = $data['otheruser']; $plan = $data['plan']; $tplplan = $data['tplplan']; // Set waiting for review. $tplplan->set('status', plan::STATUS_WAITING_FOR_REVIEW); $tplplan->update(); $plan->set('status', plan::STATUS_WAITING_FOR_REVIEW); $plan->update(); // Foreign user cannot do anything. $this->setUser($otheruser); try { api::plan_cancel_review_request($plan); $this->fail('The user can not read the plan.'); } catch (\required_capability_exception $e) { $this->assertEquals('nopermissions', $e->errorcode); } // Can not change a plan based on a template. $this->setUser($user); try { api::plan_cancel_review_request($tplplan); $this->fail('The plan is based on a template.'); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression('/Template plans cannot be reviewed./', $e->getMessage()); } // Can not cancel review request when not waiting for review. $this->setUser($user); $plan->set('status', plan::STATUS_DRAFT); try { api::plan_cancel_review_request($plan); $this->fail('The plan cannot be sent for review at this stage.'); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression('/The plan review cannot be cancelled at this stage./', $e->getMessage()); } // Can not cancel review request when not waiting for review. $this->setUser($user); $plan->set('status', plan::STATUS_IN_REVIEW); try { api::plan_cancel_review_request($plan); $this->fail('The plan review cannot be cancelled at this stage.'); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression('/The plan review cannot be cancelled at this stage./', $e->getMessage()); } // Can not cancel review request when not waiting for review. $this->setUser($user); $plan->set('status', plan::STATUS_ACTIVE); try { api::plan_cancel_review_request($plan); $this->fail('The plan review cannot be cancelled at this stage.'); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression('/The plan review cannot be cancelled at this stage./', $e->getMessage()); } // Can not cancel review request when not waiting for review. $this->setUser($user); $plan->set('status', plan::STATUS_COMPLETE); try { api::plan_cancel_review_request($plan); $this->fail('The plan review cannot be cancelled at this stage.'); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression('/The plan review cannot be cancelled at this stage./', $e->getMessage()); } // Cancelling as a reviewer. $this->setUser($reviewer); $plan->set('status', plan::STATUS_WAITING_FOR_REVIEW); try { api::plan_cancel_review_request($plan); $this->fail('The user can not cancel a review request.'); } catch (\required_capability_exception $e) { $this->assertEquals('nopermissions', $e->errorcode); } // Cancelling review request. $this->setUser($user); api::plan_cancel_review_request($plan); $plan->read(); $this->assertEquals(plan::STATUS_DRAFT, $plan->get('status')); // Cancelling review request by ID. $plan->set('status', plan::STATUS_WAITING_FOR_REVIEW); $plan->update(); api::plan_cancel_review_request($plan->get('id')); $plan->read(); $this->assertEquals(plan::STATUS_DRAFT, $plan->get('status')); } /** * Testing starting the review. */ public function test_plan_start_review(): void { $data = $this->setup_workflow_data(); $dg = $data['dg']; $lpg = $data['lpg']; $user = $data['user']; $reviewer = $data['reviewer']; $otheruser = $data['otheruser']; $plan = $data['plan']; $tplplan = $data['tplplan']; // Set waiting for review. $tplplan->set('status', plan::STATUS_WAITING_FOR_REVIEW); $tplplan->update(); $plan->set('status', plan::STATUS_WAITING_FOR_REVIEW); $plan->update(); // Foreign user cannot do anything. $this->setUser($otheruser); try { api::plan_start_review($plan); $this->fail('The user can not read the plan.'); } catch (\required_capability_exception $e) { $this->assertEquals('nopermissions', $e->errorcode); } // Can not change a plan based on a template. $this->setUser($reviewer); try { api::plan_start_review($tplplan); $this->fail('The plan is based on a template.'); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression('/Template plans cannot be reviewed./', $e->getMessage()); } // Can not start a review when not waiting for review. $this->setUser($reviewer); $plan->set('status', plan::STATUS_DRAFT); try { api::plan_start_review($plan); $this->fail('The plan review cannot be started at this stage.'); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression('/The plan review cannot be started at this stage./', $e->getMessage()); } // Can not start a review when not waiting for review. $this->setUser($reviewer); $plan->set('status', plan::STATUS_IN_REVIEW); try { api::plan_start_review($plan); $this->fail('The plan review cannot be started at this stage.'); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression('/The plan review cannot be started at this stage./', $e->getMessage()); } // Can not start a review when not waiting for review. $this->setUser($reviewer); $plan->set('status', plan::STATUS_ACTIVE); try { api::plan_start_review($plan); $this->fail('The plan review cannot be started at this stage.'); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression('/The plan review cannot be started at this stage./', $e->getMessage()); } // Can not start a review when not waiting for review. $this->setUser($reviewer); $plan->set('status', plan::STATUS_COMPLETE); try { api::plan_start_review($plan); $this->fail('The plan review cannot be started at this stage.'); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression('/The plan review cannot be started at this stage./', $e->getMessage()); } // Starting as the owner. $this->setUser($user); $plan->set('status', plan::STATUS_WAITING_FOR_REVIEW); try { api::plan_start_review($plan); $this->fail('The user can not start a review.'); } catch (\required_capability_exception $e) { $this->assertEquals('nopermissions', $e->errorcode); } // Starting review. $this->setUser($reviewer); api::plan_start_review($plan); $plan->read(); $this->assertEquals(plan::STATUS_IN_REVIEW, $plan->get('status')); $this->assertEquals($reviewer->id, $plan->get('reviewerid')); // Starting review by ID. $plan->set('status', plan::STATUS_WAITING_FOR_REVIEW); $plan->set('reviewerid', null); $plan->update(); api::plan_start_review($plan->get('id')); $plan->read(); $this->assertEquals(plan::STATUS_IN_REVIEW, $plan->get('status')); $this->assertEquals($reviewer->id, $plan->get('reviewerid')); } /** * Testing stopping the review. */ public function test_plan_stop_review(): void { $data = $this->setup_workflow_data(); $dg = $data['dg']; $lpg = $data['lpg']; $user = $data['user']; $reviewer = $data['reviewer']; $otheruser = $data['otheruser']; $plan = $data['plan']; $tplplan = $data['tplplan']; // Set waiting for review. $tplplan->set('status', plan::STATUS_IN_REVIEW); $tplplan->update(); $plan->set('status', plan::STATUS_IN_REVIEW); $plan->update(); // Foreign user cannot do anything. $this->setUser($otheruser); try { api::plan_stop_review($plan); $this->fail('The user can not read the plan.'); } catch (\required_capability_exception $e) { $this->assertEquals('nopermissions', $e->errorcode); } // Can not change a plan based on a template. $this->setUser($reviewer); try { api::plan_stop_review($tplplan); $this->fail('The plan is based on a template.'); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression('/Template plans cannot be reviewed./', $e->getMessage()); } // Can not stop a review whe not in review. $this->setUser($reviewer); $plan->set('status', plan::STATUS_DRAFT); try { api::plan_stop_review($plan); $this->fail('The plan review cannot be stopped at this stage.'); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression('/The plan review cannot be stopped at this stage./', $e->getMessage()); } // Can not stop a review whe not in review. $this->setUser($reviewer); $plan->set('status', plan::STATUS_WAITING_FOR_REVIEW); try { api::plan_stop_review($plan); $this->fail('The plan review cannot be stopped at this stage.'); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression('/The plan review cannot be stopped at this stage./', $e->getMessage()); } // Can not stop a review whe not in review. $this->setUser($reviewer); $plan->set('status', plan::STATUS_ACTIVE); try { api::plan_stop_review($plan); $this->fail('The plan review cannot be stopped at this stage.'); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression('/The plan review cannot be stopped at this stage./', $e->getMessage()); } // Can not stop a review whe not in review. $this->setUser($reviewer); $plan->set('status', plan::STATUS_COMPLETE); try { api::plan_stop_review($plan); $this->fail('The plan review cannot be stopped at this stage.'); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression('/The plan review cannot be stopped at this stage./', $e->getMessage()); } // Stopping as the owner. $this->setUser($user); $plan->set('status', plan::STATUS_IN_REVIEW); try { api::plan_stop_review($plan); $this->fail('The user can not stop a review.'); } catch (\required_capability_exception $e) { $this->assertEquals('nopermissions', $e->errorcode); } // Stopping review. $this->setUser($reviewer); api::plan_stop_review($plan); $plan->read(); $this->assertEquals(plan::STATUS_DRAFT, $plan->get('status')); // Stopping review by ID. $plan->set('status', plan::STATUS_IN_REVIEW); $plan->update(); api::plan_stop_review($plan->get('id')); $plan->read(); $this->assertEquals(plan::STATUS_DRAFT, $plan->get('status')); } /** * Testing approving the plan. */ public function test_approve_plan(): void { $data = $this->setup_workflow_data(); $dg = $data['dg']; $lpg = $data['lpg']; $user = $data['user']; $reviewer = $data['reviewer']; $otheruser = $data['otheruser']; $plan = $data['plan']; $tplplan = $data['tplplan']; // Set waiting for review. $tplplan->set('status', plan::STATUS_IN_REVIEW); $tplplan->update(); $plan->set('status', plan::STATUS_IN_REVIEW); $plan->update(); // Foreign user cannot do anything. $this->setUser($otheruser); try { api::approve_plan($plan); $this->fail('The user can not read the plan.'); } catch (\required_capability_exception $e) { $this->assertEquals('nopermissions', $e->errorcode); } // Can not change a plan based on a template. $this->setUser($reviewer); try { api::approve_plan($tplplan); $this->fail('The plan is based on a template.'); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression('/Template plans are already approved./', $e->getMessage()); } // Can not approve a plan already approved. $this->setUser($reviewer); $plan->set('status', plan::STATUS_ACTIVE); try { api::approve_plan($plan); $this->fail('The plan cannot be approved at this stage.'); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression('/The plan cannot be approved at this stage./', $e->getMessage()); } // Can not approve a plan already approved. $this->setUser($reviewer); $plan->set('status', plan::STATUS_COMPLETE); try { api::approve_plan($plan); $this->fail('The plan cannot be approved at this stage.'); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression('/The plan cannot be approved at this stage./', $e->getMessage()); } // Approve as the owner. $this->setUser($user); $plan->set('status', plan::STATUS_IN_REVIEW); try { api::approve_plan($plan); $this->fail('The user can not approve the plan.'); } catch (\required_capability_exception $e) { $this->assertEquals('nopermissions', $e->errorcode); } // Approve plan from in review. $this->setUser($reviewer); api::approve_plan($plan); $plan->read(); $this->assertEquals(plan::STATUS_ACTIVE, $plan->get('status')); // Approve plan by ID. $plan->set('status', plan::STATUS_IN_REVIEW); $plan->update(); api::approve_plan($plan->get('id')); $plan->read(); $this->assertEquals(plan::STATUS_ACTIVE, $plan->get('status')); // Approve plan from draft. $plan->set('status', plan::STATUS_DRAFT); $plan->update(); api::approve_plan($plan); $plan->read(); $this->assertEquals(plan::STATUS_ACTIVE, $plan->get('status')); // Approve plan from waiting for review. $plan->set('status', plan::STATUS_WAITING_FOR_REVIEW); $plan->update(); api::approve_plan($plan); $plan->read(); $this->assertEquals(plan::STATUS_ACTIVE, $plan->get('status')); } /** * Testing stopping the review. */ public function test_unapprove_plan(): void { $data = $this->setup_workflow_data(); $dg = $data['dg']; $lpg = $data['lpg']; $user = $data['user']; $reviewer = $data['reviewer']; $otheruser = $data['otheruser']; $plan = $data['plan']; $tplplan = $data['tplplan']; // Set waiting for review. $tplplan->set('status', plan::STATUS_ACTIVE); $tplplan->update(); $plan->set('status', plan::STATUS_ACTIVE); $plan->update(); // Foreign user cannot do anything. $this->setUser($otheruser); try { api::unapprove_plan($plan); $this->fail('The user can not read the plan.'); } catch (\required_capability_exception $e) { $this->assertEquals('nopermissions', $e->errorcode); } // Can not change a plan based on a template. $this->setUser($reviewer); try { api::unapprove_plan($tplplan); $this->fail('The plan is based on a template.'); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression('/Template plans are always approved./', $e->getMessage()); } // Can not unapprove a non-draft plan. $this->setUser($reviewer); $plan->set('status', plan::STATUS_DRAFT); try { api::unapprove_plan($plan); $this->fail('The plan cannot be sent back to draft at this stage.'); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression('/The plan cannot be sent back to draft at this stage./', $e->getMessage()); } // Can not unapprove a non-draft plan. $this->setUser($reviewer); $plan->set('status', plan::STATUS_WAITING_FOR_REVIEW); try { api::unapprove_plan($plan); $this->fail('The plan cannot be sent back to draft at this stage.'); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression('/The plan cannot be sent back to draft at this stage./', $e->getMessage()); } // Can not unapprove a non-draft plan. $this->setUser($reviewer); $plan->set('status', plan::STATUS_IN_REVIEW); try { api::unapprove_plan($plan); $this->fail('The plan cannot be sent back to draft at this stage.'); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression('/The plan cannot be sent back to draft at this stage./', $e->getMessage()); } // Can not unapprove a non-draft plan. $this->setUser($reviewer); $plan->set('status', plan::STATUS_COMPLETE); try { api::unapprove_plan($plan); $this->fail('The plan cannot be sent back to draft at this stage.'); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression('/The plan cannot be sent back to draft at this stage./', $e->getMessage()); } // Unapprove as the owner. $this->setUser($user); $plan->set('status', plan::STATUS_ACTIVE); try { api::unapprove_plan($plan); $this->fail('The user can not unapprove the plan.'); } catch (\required_capability_exception $e) { $this->assertEquals('nopermissions', $e->errorcode); } // Unapprove plan. $this->setUser($reviewer); api::unapprove_plan($plan); $plan->read(); $this->assertEquals(plan::STATUS_DRAFT, $plan->get('status')); // Unapprove plan by ID. $plan->set('status', plan::STATUS_ACTIVE); $plan->update(); api::unapprove_plan($plan->get('id')); $plan->read(); $this->assertEquals(plan::STATUS_DRAFT, $plan->get('status')); } /** * Test update plan and the managing of archived user competencies. */ public function test_update_plan_manage_archived_competencies(): void { global $DB; $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $syscontext = \context_system::instance(); // Create users and roles for the test. $user = $dg->create_user(); $manageownrole = $dg->create_role(array( 'name' => 'User manage own', 'shortname' => 'manageown' )); assign_capability('moodle/competency:planmanageowndraft', CAP_ALLOW, $manageownrole, $syscontext->id); assign_capability('moodle/competency:planviewowndraft', CAP_ALLOW, $manageownrole, $syscontext->id); assign_capability('moodle/competency:planmanageown', CAP_ALLOW, $manageownrole, $syscontext->id); assign_capability('moodle/competency:planviewown', CAP_ALLOW, $manageownrole, $syscontext->id); $dg->role_assign($manageownrole, $user->id, $syscontext->id); $this->setUser($user); // Create a framework and assign competencies. $framework = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c3 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); // Create two plans and assign competencies. $plan = $lpg->create_plan(array('userid' => $user->id)); $otherplan = $lpg->create_plan(array('userid' => $user->id)); $lpg->create_plan_competency(array('planid' => $plan->get('id'), 'competencyid' => $c1->get('id'))); $lpg->create_plan_competency(array('planid' => $plan->get('id'), 'competencyid' => $c2->get('id'))); $lpg->create_plan_competency(array('planid' => $plan->get('id'), 'competencyid' => $c3->get('id'))); $lpg->create_plan_competency(array('planid' => $otherplan->get('id'), 'competencyid' => $c1->get('id'))); $uclist = array( $lpg->create_user_competency(array( 'userid' => $user->id, 'competencyid' => $c1->get('id'), 'proficiency' => true, 'grade' => 1 )), $lpg->create_user_competency(array( 'userid' => $user->id, 'competencyid' => $c2->get('id'), 'proficiency' => false, 'grade' => 2 )) ); // Change status of the plan to complete. $record = $plan->to_record(); $record->status = \core_competency\plan::STATUS_COMPLETE; try { $plan = api::update_plan($record); $this->fail('We cannot complete a plan using api::update_plan().'); } catch (\coding_exception $e) { // All good. } api::complete_plan($plan); // Check that user compretencies are now in user_competency_plan objects and still in user_competency. $this->assertEquals(2, \core_competency\user_competency::count_records()); $this->assertEquals(3, \core_competency\user_competency_plan::count_records()); $usercompetenciesplan = \core_competency\user_competency_plan::get_records(); $this->assertEquals($uclist[0]->get('userid'), $usercompetenciesplan[0]->get('userid')); $this->assertEquals($uclist[0]->get('competencyid'), $usercompetenciesplan[0]->get('competencyid')); $this->assertEquals($uclist[0]->get('proficiency'), (bool) $usercompetenciesplan[0]->get('proficiency')); $this->assertEquals($uclist[0]->get('grade'), $usercompetenciesplan[0]->get('grade')); $this->assertEquals($plan->get('id'), $usercompetenciesplan[0]->get('planid')); $this->assertEquals($uclist[1]->get('userid'), $usercompetenciesplan[1]->get('userid')); $this->assertEquals($uclist[1]->get('competencyid'), $usercompetenciesplan[1]->get('competencyid')); $this->assertEquals($uclist[1]->get('proficiency'), (bool) $usercompetenciesplan[1]->get('proficiency')); $this->assertEquals($uclist[1]->get('grade'), $usercompetenciesplan[1]->get('grade')); $this->assertEquals($plan->get('id'), $usercompetenciesplan[1]->get('planid')); $this->assertEquals($user->id, $usercompetenciesplan[2]->get('userid')); $this->assertEquals($c3->get('id'), $usercompetenciesplan[2]->get('competencyid')); $this->assertNull($usercompetenciesplan[2]->get('proficiency')); $this->assertNull($usercompetenciesplan[2]->get('grade')); $this->assertEquals($plan->get('id'), $usercompetenciesplan[2]->get('planid')); // Change status of the plan to active. $record = $plan->to_record(); $record->status = \core_competency\plan::STATUS_ACTIVE; try { api::update_plan($record); $this->fail('Completed plan can not be edited'); } catch (\coding_exception $e) { // All good. } api::reopen_plan($record->id); // Check that user_competency_plan objects are deleted if the plan status is changed to another status. $this->assertEquals(2, \core_competency\user_competency::count_records()); $this->assertEquals(0, \core_competency\user_competency_plan::count_records()); } /** * Test completing plan does not change the order of competencies. */ public function test_complete_plan_doesnot_change_order(): void { global $DB; $this->resetAfterTest(true); $this->setAdminUser(); $dg = $this->getDataGenerator(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $syscontext = \context_system::instance(); // Create users and roles for the test. $user = $dg->create_user(); // Create a framework and assign competencies. $framework = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c3 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); // Create two plans and assign competencies. $plan = $lpg->create_plan(array('userid' => $user->id)); $lpg->create_plan_competency(array('planid' => $plan->get('id'), 'competencyid' => $c1->get('id'))); $lpg->create_plan_competency(array('planid' => $plan->get('id'), 'competencyid' => $c2->get('id'))); $lpg->create_plan_competency(array('planid' => $plan->get('id'), 'competencyid' => $c3->get('id'))); // Changing competencies order in plan competency. api::reorder_plan_competency($plan->get('id'), $c1->get('id'), $c3->get('id')); $competencies = api::list_plan_competencies($plan); $this->assertEquals($c2->get('id'), $competencies[0]->competency->get('id')); $this->assertEquals($c3->get('id'), $competencies[1]->competency->get('id')); $this->assertEquals($c1->get('id'), $competencies[2]->competency->get('id')); // Completing plan. api::complete_plan($plan); $competencies = api::list_plan_competencies($plan); // Completing plan does not change order. $this->assertEquals($c2->get('id'), $competencies[0]->competency->get('id')); $this->assertEquals($c3->get('id'), $competencies[1]->competency->get('id')); $this->assertEquals($c1->get('id'), $competencies[2]->competency->get('id')); // Testing plan based on template. $template = $lpg->create_template(); $framework = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c3 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $lpg->create_template_competency(array( 'templateid' => $template->get('id'), 'competencyid' => $c1->get('id') )); $lpg->create_template_competency(array( 'templateid' => $template->get('id'), 'competencyid' => $c2->get('id') )); $lpg->create_template_competency(array( 'templateid' => $template->get('id'), 'competencyid' => $c3->get('id') )); // Reorder competencies in template. api::reorder_template_competency($template->get('id'), $c1->get('id'), $c3->get('id')); // Create plan from template. $plan = api::create_plan_from_template($template->get('id'), $user->id); $competencies = api::list_plan_competencies($plan); // Completing plan does not change order. $this->assertEquals($c2->get('id'), $competencies[0]->competency->get('id')); $this->assertEquals($c3->get('id'), $competencies[1]->competency->get('id')); $this->assertEquals($c1->get('id'), $competencies[2]->competency->get('id')); // Completing plan. api::complete_plan($plan); $competencies = api::list_plan_competencies($plan); // Completing plan does not change order. $this->assertEquals($c2->get('id'), $competencies[0]->competency->get('id')); $this->assertEquals($c3->get('id'), $competencies[1]->competency->get('id')); $this->assertEquals($c1->get('id'), $competencies[2]->competency->get('id')); } /** * Test remove plan and the managing of archived user competencies. */ public function test_delete_plan_manage_archived_competencies(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $syscontext = \context_system::instance(); // Create user and role for the test. $user = $dg->create_user(); $managerole = $dg->create_role(array( 'name' => 'User manage own', 'shortname' => 'manageown' )); assign_capability('moodle/competency:planmanageowndraft', CAP_ALLOW, $managerole, $syscontext->id); assign_capability('moodle/competency:planmanageown', CAP_ALLOW, $managerole, $syscontext->id); $dg->role_assign($managerole, $user->id, $syscontext->id); $this->setUser($user); // Create a framework and assign competencies. $framework = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c3 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); // Create completed plan with records in user_competency. $completedplan = $lpg->create_plan(array('userid' => $user->id, 'status' => \core_competency\plan::STATUS_COMPLETE)); $lpg->create_plan_competency(array('planid' => $completedplan->get('id'), 'competencyid' => $c1->get('id'))); $lpg->create_plan_competency(array('planid' => $completedplan->get('id'), 'competencyid' => $c2->get('id'))); $uc1 = $lpg->create_user_competency(array('userid' => $user->id, 'competencyid' => $c1->get('id'))); $uc2 = $lpg->create_user_competency(array('userid' => $user->id, 'competencyid' => $c2->get('id'))); $ucp1 = $lpg->create_user_competency_plan(array('userid' => $user->id, 'competencyid' => $c1->get('id'), 'planid' => $completedplan->get('id'))); $ucp2 = $lpg->create_user_competency_plan(array('userid' => $user->id, 'competencyid' => $c2->get('id'), 'planid' => $completedplan->get('id'))); api::delete_plan($completedplan->get('id')); // Check that achived user competencies are deleted. $this->assertEquals(0, \core_competency\plan::count_records()); $this->assertEquals(2, \core_competency\user_competency::count_records()); $this->assertEquals(0, \core_competency\user_competency_plan::count_records()); } /** * Test listing of plan competencies. */ public function test_list_plan_competencies_manage_archived_competencies(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $syscontext = \context_system::instance(); // Create user and role for the test. $user = $dg->create_user(); $viewrole = $dg->create_role(array( 'name' => 'User view', 'shortname' => 'view' )); assign_capability('moodle/competency:planviewdraft', CAP_ALLOW, $viewrole, $syscontext->id); assign_capability('moodle/competency:planview', CAP_ALLOW, $viewrole, $syscontext->id); $dg->role_assign($viewrole, $user->id, $syscontext->id); $this->setUser($user); // Create a framework and assign competencies. $framework = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c3 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); // Create draft plan with records in user_competency. $draftplan = $lpg->create_plan(array('userid' => $user->id)); $lpg->create_plan_competency(array('planid' => $draftplan->get('id'), 'competencyid' => $c1->get('id'))); $lpg->create_plan_competency(array('planid' => $draftplan->get('id'), 'competencyid' => $c2->get('id'))); $lpg->create_plan_competency(array('planid' => $draftplan->get('id'), 'competencyid' => $c3->get('id'))); $uc1 = $lpg->create_user_competency(array('userid' => $user->id, 'competencyid' => $c1->get('id'))); $uc2 = $lpg->create_user_competency(array('userid' => $user->id, 'competencyid' => $c2->get('id'))); // Check that user_competency objects are returned when plan status is not complete. $plancompetencies = api::list_plan_competencies($draftplan); $this->assertCount(3, $plancompetencies); $this->assertInstanceOf('\core_competency\user_competency', $plancompetencies[0]->usercompetency); $this->assertEquals($uc1->get('id'), $plancompetencies[0]->usercompetency->get('id')); $this->assertNull($plancompetencies[0]->usercompetencyplan); $this->assertInstanceOf('\core_competency\user_competency', $plancompetencies[1]->usercompetency); $this->assertEquals($uc2->get('id'), $plancompetencies[1]->usercompetency->get('id')); $this->assertNull($plancompetencies[1]->usercompetencyplan); $this->assertInstanceOf('\core_competency\user_competency', $plancompetencies[2]->usercompetency); $this->assertEquals(0, $plancompetencies[2]->usercompetency->get('id')); $this->assertNull($plancompetencies[2]->usercompetencyplan); // Create completed plan with records in user_competency_plan. $completedplan = $lpg->create_plan(array('userid' => $user->id, 'status' => \core_competency\plan::STATUS_COMPLETE)); $pc1 = $lpg->create_plan_competency(array('planid' => $completedplan->get('id'), 'competencyid' => $c1->get('id'))); $pc2 = $lpg->create_plan_competency(array('planid' => $completedplan->get('id'), 'competencyid' => $c2->get('id'))); $pc3 = $lpg->create_plan_competency(array('planid' => $completedplan->get('id'), 'competencyid' => $c3->get('id'))); $ucp1 = $lpg->create_user_competency_plan(array('userid' => $user->id, 'competencyid' => $c1->get('id'), 'planid' => $completedplan->get('id'))); $ucp2 = $lpg->create_user_competency_plan(array('userid' => $user->id, 'competencyid' => $c2->get('id'), 'planid' => $completedplan->get('id'))); $ucp3 = $lpg->create_user_competency_plan(array('userid' => $user->id, 'competencyid' => $c3->get('id'), 'planid' => $completedplan->get('id'))); // Check that user_competency_plan objects are returned when plan status is complete. $plancompetencies = api::list_plan_competencies($completedplan); $this->assertCount(3, $plancompetencies); $this->assertInstanceOf('\core_competency\user_competency_plan', $plancompetencies[0]->usercompetencyplan); $this->assertEquals($ucp1->get('id'), $plancompetencies[0]->usercompetencyplan->get('id')); $this->assertNull($plancompetencies[0]->usercompetency); $this->assertInstanceOf('\core_competency\user_competency_plan', $plancompetencies[1]->usercompetencyplan); $this->assertEquals($ucp2->get('id'), $plancompetencies[1]->usercompetencyplan->get('id')); $this->assertNull($plancompetencies[1]->usercompetency); $this->assertInstanceOf('\core_competency\user_competency_plan', $plancompetencies[2]->usercompetencyplan); $this->assertEquals($ucp3->get('id'), $plancompetencies[2]->usercompetencyplan->get('id')); $this->assertNull($plancompetencies[2]->usercompetency); } public function test_create_template_cohort(): void { $this->resetAfterTest(true); $this->setAdminUser(); $dg = $this->getDataGenerator(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $c1 = $dg->create_cohort(); $c2 = $dg->create_cohort(); $t1 = $lpg->create_template(); $t2 = $lpg->create_template(); $this->assertEquals(0, \core_competency\template_cohort::count_records()); // Create two relations with mixed parameters. $result = api::create_template_cohort($t1->get('id'), $c1->id); $result = api::create_template_cohort($t1, $c2); $this->assertEquals(2, \core_competency\template_cohort::count_records()); $this->assertInstanceOf('core_competency\template_cohort', $result); $this->assertEquals($c2->id, $result->get('cohortid')); $this->assertEquals($t1->get('id'), $result->get('templateid')); $this->assertEquals(2, \core_competency\template_cohort::count_records_select('templateid = :id', array('id' => $t1->get('id')))); $this->assertEquals(0, \core_competency\template_cohort::count_records_select('templateid = :id', array('id' => $t2->get('id')))); } public function test_create_template_cohort_permissions(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $cat = $dg->create_category(); $catcontext = \context_coursecat::instance($cat->id); $syscontext = \context_system::instance(); $user = $dg->create_user(); $role = $dg->create_role(); assign_capability('moodle/competency:templatemanage', CAP_ALLOW, $role, $syscontext->id, true); $dg->role_assign($role, $user->id, $syscontext->id); $cohortrole = $dg->create_role(); assign_capability('moodle/cohort:view', CAP_ALLOW, $cohortrole, $syscontext->id, true); accesslib_clear_all_caches_for_unit_testing(); $c1 = $dg->create_cohort(); $c2 = $dg->create_cohort(array('visible' => 0, 'contextid' => $catcontext->id)); $t1 = $lpg->create_template(); $this->assertEquals(0, \core_competency\template_cohort::count_records()); $this->setUser($user); $result = api::create_template_cohort($t1, $c1); $this->assertInstanceOf('core_competency\\template_cohort', $result); try { $result = api::create_template_cohort($t1, $c2); $this->fail('Permission required.'); } catch (\required_capability_exception $e) { // That's what should happen. } // Try again with the right permissions. $dg->role_assign($cohortrole, $user->id, $catcontext->id); accesslib_clear_all_caches_for_unit_testing(); $result = api::create_template_cohort($t1, $c2); $this->assertInstanceOf('core_competency\\template_cohort', $result); } public function test_reorder_template_competencies_permissions(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $cat = $dg->create_category(); $catcontext = \context_coursecat::instance($cat->id); $syscontext = \context_system::instance(); $user = $dg->create_user(); $role = $dg->create_role(); assign_capability('moodle/competency:templatemanage', CAP_ALLOW, $role, $syscontext->id, true); $dg->role_assign($role, $user->id, $syscontext->id); // Create a template. $template = $lpg->create_template(array('contextid' => $catcontext->id)); // Create a competency framework. $framework = $lpg->create_framework(array('contextid' => $catcontext->id)); // Create competencies. $competency1 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $competency2 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); // Add the competencies. $lpg->create_template_competency(array( 'templateid' => $template->get('id'), 'competencyid' => $competency1->get('id') )); $lpg->create_template_competency(array( 'templateid' => $template->get('id'), 'competencyid' => $competency2->get('id') )); $this->setUser($user); // Can reorder competencies with system context permissions in category context. $result = api::reorder_template_competency($template->get('id'), $competency2->get('id'), $competency1->get('id')); $this->assertTrue($result); unassign_capability('moodle/competency:templatemanage', $role, $syscontext->id); accesslib_clear_all_caches_for_unit_testing(); try { api::reorder_template_competency($template->get('id'), $competency2->get('id'), $competency1->get('id')); $this->fail('Exception expected due to not permissions to manage template competencies'); } catch (\required_capability_exception $e) { $this->assertEquals('nopermissions', $e->errorcode); } // Giving permissions in category context. assign_capability('moodle/competency:templatemanage', CAP_ALLOW, $role, $catcontext->id, true); $dg->role_assign($role, $user->id, $catcontext->id); // User with templatemanage capability in category context can reorder competencies in temple. $result = api::reorder_template_competency($template->get('id'), $competency1->get('id'), $competency2->get('id')); $this->assertTrue($result); // Removing templatemanage capability in category context. unassign_capability('moodle/competency:templatemanage', $role, $catcontext->id); accesslib_clear_all_caches_for_unit_testing(); try { api::reorder_template_competency($template->get('id'), $competency2->get('id'), $competency1->get('id')); $this->fail('Exception expected due to not permissions to manage template competencies'); } catch (\required_capability_exception $e) { $this->assertEquals('nopermissions', $e->errorcode); } } public function test_delete_template(): void { $this->resetAfterTest(true); $this->setAdminUser(); $dg = $this->getDataGenerator(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $c1 = $dg->create_cohort(); $c2 = $dg->create_cohort(); $template = $lpg->create_template(); $id = $template->get('id'); // Create 2 template cohorts. $tc1 = $lpg->create_template_cohort(array('templateid' => $template->get('id'), 'cohortid' => $c1->id)); $tc1 = $lpg->create_template_cohort(array('templateid' => $template->get('id'), 'cohortid' => $c2->id)); // Check pre-test. $this->assertTrue(\core_competency\template::record_exists($id)); $this->assertEquals(2, \core_competency\template_cohort::count_records(array('templateid' => $id))); $result = api::delete_template($template->get('id')); $this->assertTrue($result); // Check that the template deos not exist anymore. $this->assertFalse(\core_competency\template::record_exists($id)); // Test if associated cohorts are also deleted. $this->assertEquals(0, \core_competency\template_cohort::count_records(array('templateid' => $id))); } public function test_delete_template_cohort(): void { $this->resetAfterTest(true); $this->setAdminUser(); $dg = $this->getDataGenerator(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $c1 = $dg->create_cohort(); $c2 = $dg->create_cohort(); $t1 = $lpg->create_template(); $t2 = $lpg->create_template(); $tc1 = $lpg->create_template_cohort(array('templateid' => $t1->get('id'), 'cohortid' => $c1->id)); $tc1 = $lpg->create_template_cohort(array('templateid' => $t2->get('id'), 'cohortid' => $c2->id)); $this->assertEquals(2, \core_competency\template_cohort::count_records()); $this->assertEquals(1, \core_competency\template_cohort::count_records_select('templateid = :id', array('id' => $t1->get('id')))); $this->assertEquals(1, \core_competency\template_cohort::count_records_select('templateid = :id', array('id' => $t2->get('id')))); // Delete existing. $result = api::delete_template_cohort($t1->get('id'), $c1->id); $this->assertTrue($result); $this->assertEquals(1, \core_competency\template_cohort::count_records()); $this->assertEquals(0, \core_competency\template_cohort::count_records_select('templateid = :id', array('id' => $t1->get('id')))); $this->assertEquals(1, \core_competency\template_cohort::count_records_select('templateid = :id', array('id' => $t2->get('id')))); // Delete non-existant. $result = api::delete_template_cohort($t1->get('id'), $c1->id); $this->assertTrue($result); $this->assertEquals(1, \core_competency\template_cohort::count_records()); $this->assertEquals(0, \core_competency\template_cohort::count_records_select('templateid = :id', array('id' => $t1->get('id')))); $this->assertEquals(1, \core_competency\template_cohort::count_records_select('templateid = :id', array('id' => $t2->get('id')))); } public function test_add_evidence_log(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $u1 = $dg->create_user(); $u1ctx = \context_user::instance($u1->id); $f1 = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); // Creating a standard evidence with minimal information. $evidence = api::add_evidence($u1->id, $c1->get('id'), $u1ctx->id, \core_competency\evidence::ACTION_LOG, 'invaliddata', 'error'); $evidence->read(); $uc = \core_competency\user_competency::get_record(array('userid' => $u1->id, 'competencyid' => $c1->get('id'))); $this->assertEquals(\core_competency\user_competency::STATUS_IDLE, $uc->get('status')); $this->assertSame(null, $uc->get('grade')); $this->assertSame(null, $uc->get('proficiency')); $this->assertEquals($uc->get('id'), $evidence->get('usercompetencyid')); $this->assertEquals($u1ctx->id, $evidence->get('contextid')); $this->assertEquals(\core_competency\evidence::ACTION_LOG, $evidence->get('action')); $this->assertEquals('invaliddata', $evidence->get('descidentifier')); $this->assertEquals('error', $evidence->get('desccomponent')); $this->assertSame(null, $evidence->get('desca')); $this->assertSame(null, $evidence->get('url')); $this->assertSame(null, $evidence->get('grade')); $this->assertSame(null, $evidence->get('actionuserid')); // Creating a standard evidence with more information. $evidence = api::add_evidence($u1->id, $c1->get('id'), $u1ctx->id, \core_competency\evidence::ACTION_LOG, 'invaliddata', 'error', '$a', false, 'http://moodle.org', null, 2, 'The evidence of prior learning were reviewed.'); $evidence->read(); $uc = \core_competency\user_competency::get_record(array('userid' => $u1->id, 'competencyid' => $c1->get('id'))); $this->assertEquals(\core_competency\user_competency::STATUS_IDLE, $uc->get('status')); $this->assertSame(null, $uc->get('grade')); $this->assertSame(null, $uc->get('proficiency')); $this->assertEquals($uc->get('id'), $evidence->get('usercompetencyid')); $this->assertEquals($u1ctx->id, $evidence->get('contextid')); $this->assertEquals(\core_competency\evidence::ACTION_LOG, $evidence->get('action')); $this->assertEquals('invaliddata', $evidence->get('descidentifier')); $this->assertEquals('error', $evidence->get('desccomponent')); $this->assertEquals('$a', $evidence->get('desca')); $this->assertEquals('http://moodle.org', $evidence->get('url')); $this->assertSame(null, $evidence->get('grade')); $this->assertEquals(2, $evidence->get('actionuserid')); $this->assertSame('The evidence of prior learning were reviewed.', $evidence->get('note')); // Creating a standard evidence and send for review. $evidence = api::add_evidence($u1->id, $c2->get('id'), $u1ctx->id, \core_competency\evidence::ACTION_LOG, 'invaliddata', 'error', null, true); $evidence->read(); $uc = \core_competency\user_competency::get_record(array('userid' => $u1->id, 'competencyid' => $c2->get('id'))); $this->assertEquals(\core_competency\user_competency::STATUS_WAITING_FOR_REVIEW, $uc->get('status')); // Trying to pass a grade should fail. try { $evidence = api::add_evidence($u1->id, $c1->get('id'), $u1ctx->id, \core_competency\evidence::ACTION_LOG, 'invaliddata', 'error', null, false, null, 1); $this->fail('A grade can not be set'); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression('/grade MUST NOT be set/', $e->getMessage()); } } public function test_add_evidence_complete(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $u1 = $dg->create_user(); $u1ctx = \context_user::instance($u1->id); $scale = $dg->create_scale(array('scale' => 'A,B,C,D')); $scaleconfig = array(array('scaleid' => $scale->id)); $scaleconfig[] = array('name' => 'B', 'id' => 2, 'scaledefault' => 1, 'proficient' => 0); $scaleconfig[] = array('name' => 'C', 'id' => 3, 'scaledefault' => 0, 'proficient' => 1); $scaleconfig[] = array('name' => 'D', 'id' => 4, 'scaledefault' => 0, 'proficient' => 1); $c2scaleconfig = array(array('scaleid' => $scale->id)); $c2scaleconfig[] = array('name' => 'B', 'id' => 2, 'scaledefault' => 0, 'proficient' => 1); $c2scaleconfig[] = array('name' => 'C', 'id' => 3, 'scaledefault' => 0, 'proficient' => 0); $c2scaleconfig[] = array('name' => 'D', 'id' => 4, 'scaledefault' => 1, 'proficient' => 1); $f1 = $lpg->create_framework(array('scaleid' => $scale->id, 'scaleconfiguration' => $scaleconfig)); $c1 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'scaleid' => $scale->id, 'scaleconfiguration' => $c2scaleconfig)); $c3 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); // Creating an evidence with minimal information. $evidence = api::add_evidence($u1->id, $c1->get('id'), $u1ctx->id, \core_competency\evidence::ACTION_COMPLETE, 'invaliddata', 'error'); $evidence->read(); $uc = \core_competency\user_competency::get_record(array('userid' => $u1->id, 'competencyid' => $c1->get('id'))); $this->assertEquals(\core_competency\user_competency::STATUS_IDLE, $uc->get('status')); $this->assertEquals(2, $uc->get('grade')); // The grade has been set automatically to the framework default. $this->assertEquals(0, $uc->get('proficiency')); $this->assertEquals($uc->get('id'), $evidence->get('usercompetencyid')); $this->assertEquals($u1ctx->id, $evidence->get('contextid')); $this->assertEquals(\core_competency\evidence::ACTION_COMPLETE, $evidence->get('action')); $this->assertEquals('invaliddata', $evidence->get('descidentifier')); $this->assertEquals('error', $evidence->get('desccomponent')); $this->assertSame(null, $evidence->get('desca')); $this->assertSame(null, $evidence->get('url')); $this->assertEquals(2, $evidence->get('grade')); $this->assertSame(null, $evidence->get('actionuserid')); // Creating an evidence complete on competency with custom scale. $evidence = api::add_evidence($u1->id, $c2->get('id'), $u1ctx->id, \core_competency\evidence::ACTION_COMPLETE, 'invaliddata', 'error'); $evidence->read(); $uc = \core_competency\user_competency::get_record(array('userid' => $u1->id, 'competencyid' => $c2->get('id'))); $this->assertEquals(\core_competency\user_competency::STATUS_IDLE, $uc->get('status')); $this->assertEquals(4, $uc->get('grade')); // The grade has been set automatically to the competency default. $this->assertEquals(true, $uc->get('proficiency')); $this->assertEquals($uc->get('id'), $evidence->get('usercompetencyid')); $this->assertEquals($u1ctx->id, $evidence->get('contextid')); $this->assertEquals(\core_competency\evidence::ACTION_COMPLETE, $evidence->get('action')); $this->assertEquals('invaliddata', $evidence->get('descidentifier')); $this->assertEquals('error', $evidence->get('desccomponent')); $this->assertSame(null, $evidence->get('desca')); $this->assertSame(null, $evidence->get('url')); $this->assertEquals(4, $evidence->get('grade')); $this->assertSame(null, $evidence->get('actionuserid')); // Creating an evidence complete on a user competency with an existing grade. $uc = $lpg->create_user_competency(array('userid' => $u1->id, 'competencyid' => $c3->get('id'), 'grade' => 1, 'proficiency' => 0)); $this->assertEquals(1, $uc->get('grade')); $this->assertEquals(0, $uc->get('proficiency')); $evidence = api::add_evidence($u1->id, $c3->get('id'), $u1ctx->id, \core_competency\evidence::ACTION_COMPLETE, 'invaliddata', 'error'); $evidence->read(); $uc->read(); $this->assertEquals(\core_competency\user_competency::STATUS_IDLE, $uc->get('status')); $this->assertEquals(1, $uc->get('grade')); // The grade has not been changed. $this->assertEquals(0, $uc->get('proficiency')); $this->assertEquals($uc->get('id'), $evidence->get('usercompetencyid')); $this->assertEquals($u1ctx->id, $evidence->get('contextid')); $this->assertEquals(\core_competency\evidence::ACTION_COMPLETE, $evidence->get('action')); $this->assertEquals('invaliddata', $evidence->get('descidentifier')); $this->assertEquals('error', $evidence->get('desccomponent')); $this->assertSame(null, $evidence->get('desca')); $this->assertSame(null, $evidence->get('url')); $this->assertEquals(2, $evidence->get('grade')); // The complete grade has been set. $this->assertSame(null, $evidence->get('actionuserid')); // Creating a standard evidence and send for review. $evidence = api::add_evidence($u1->id, $c2->get('id'), $u1ctx->id, \core_competency\evidence::ACTION_COMPLETE, 'invaliddata', 'error', null, true); $evidence->read(); $uc = \core_competency\user_competency::get_record(array('userid' => $u1->id, 'competencyid' => $c2->get('id'))); $this->assertEquals(\core_competency\user_competency::STATUS_WAITING_FOR_REVIEW, $uc->get('status')); // Trying to pass a grade should throw an exception. try { api::add_evidence($u1->id, $c2->get('id'), $u1ctx->id, \core_competency\evidence::ACTION_COMPLETE, 'invaliddata', 'error', null, false, null, 1); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression('/grade MUST NOT be set/', $e->getMessage()); } } public function test_add_evidence_override(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $u1 = $dg->create_user(); $u1ctx = \context_user::instance($u1->id); $f1 = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); // Creating an evidence with minimal information. $evidence = api::add_evidence($u1->id, $c1->get('id'), $u1ctx->id, \core_competency\evidence::ACTION_OVERRIDE, 'invaliddata', 'error'); $evidence->read(); $uc = \core_competency\user_competency::get_record(array('userid' => $u1->id, 'competencyid' => $c1->get('id'))); $this->assertEquals(\core_competency\user_competency::STATUS_IDLE, $uc->get('status')); $this->assertSame(null, $uc->get('grade')); // We overrode with 'null'. $this->assertSame(null, $uc->get('proficiency')); $this->assertEquals($uc->get('id'), $evidence->get('usercompetencyid')); $this->assertEquals($u1ctx->id, $evidence->get('contextid')); $this->assertEquals(\core_competency\evidence::ACTION_OVERRIDE, $evidence->get('action')); $this->assertEquals('invaliddata', $evidence->get('descidentifier')); $this->assertEquals('error', $evidence->get('desccomponent')); $this->assertSame(null, $evidence->get('desca')); $this->assertSame(null, $evidence->get('url')); $this->assertSame(null, $evidence->get('grade')); // We overrode with 'null'. $this->assertSame(null, $evidence->get('actionuserid')); // Creating an evidence with a grade information. $evidence = api::add_evidence($u1->id, $c1->get('id'), $u1ctx->id, \core_competency\evidence::ACTION_OVERRIDE, 'invaliddata', 'error', null, false, null, 3); $evidence->read(); $uc = \core_competency\user_competency::get_record(array('userid' => $u1->id, 'competencyid' => $c1->get('id'))); $this->assertEquals(\core_competency\user_competency::STATUS_IDLE, $uc->get('status')); $this->assertEquals(3, $uc->get('grade')); $this->assertEquals(true, $uc->get('proficiency')); $this->assertEquals($uc->get('id'), $evidence->get('usercompetencyid')); $this->assertEquals($u1ctx->id, $evidence->get('contextid')); $this->assertEquals(\core_competency\evidence::ACTION_OVERRIDE, $evidence->get('action')); $this->assertEquals('invaliddata', $evidence->get('descidentifier')); $this->assertEquals('error', $evidence->get('desccomponent')); $this->assertSame(null, $evidence->get('desca')); $this->assertSame(null, $evidence->get('url')); $this->assertEquals(3, $evidence->get('grade')); $this->assertSame(null, $evidence->get('actionuserid')); // Creating an evidence with another grade information. $evidence = api::add_evidence($u1->id, $c1->get('id'), $u1ctx->id, \core_competency\evidence::ACTION_OVERRIDE, 'invaliddata', 'error', null, false, null, 1); $evidence->read(); $uc = \core_competency\user_competency::get_record(array('userid' => $u1->id, 'competencyid' => $c1->get('id'))); $this->assertEquals(\core_competency\user_competency::STATUS_IDLE, $uc->get('status')); $this->assertEquals(1, $uc->get('grade')); $this->assertEquals(0, $uc->get('proficiency')); $this->assertEquals($uc->get('id'), $evidence->get('usercompetencyid')); $this->assertEquals($u1ctx->id, $evidence->get('contextid')); $this->assertEquals(\core_competency\evidence::ACTION_OVERRIDE, $evidence->get('action')); $this->assertEquals('invaliddata', $evidence->get('descidentifier')); $this->assertEquals('error', $evidence->get('desccomponent')); $this->assertSame(null, $evidence->get('desca')); $this->assertSame(null, $evidence->get('url')); $this->assertEquals(1, $evidence->get('grade')); $this->assertSame(null, $evidence->get('actionuserid')); // Creating reverting the grade and send for review. $evidence = api::add_evidence($u1->id, $c1->get('id'), $u1ctx->id, \core_competency\evidence::ACTION_OVERRIDE, 'invaliddata', 'error', null, true); $evidence->read(); $uc = \core_competency\user_competency::get_record(array('userid' => $u1->id, 'competencyid' => $c1->get('id'))); $this->assertSame(null, $uc->get('grade')); $this->assertSame(null, $uc->get('proficiency')); $this->assertEquals(\core_competency\user_competency::STATUS_WAITING_FOR_REVIEW, $uc->get('status')); $this->assertSame(null, $evidence->get('grade')); } public function test_add_evidence_and_send_for_review(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $u1 = $dg->create_user(); $u1ctx = \context_user::instance($u1->id); $f1 = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); // Non-existing user competencies are created up for review. $evidence = api::add_evidence($u1->id, $c1->get('id'), $u1ctx->id, \core_competency\evidence::ACTION_LOG, 'invaliddata', 'error', null, true); $uc = \core_competency\user_competency::get_record(array('userid' => $u1->id, 'competencyid' => $c1->get('id'))); $this->assertEquals(\core_competency\user_competency::STATUS_WAITING_FOR_REVIEW, $uc->get('status')); // Existing user competencies sent for review don't change. $evidence = api::add_evidence($u1->id, $c1->get('id'), $u1ctx->id, \core_competency\evidence::ACTION_LOG, 'invaliddata', 'error', null, true); $uc->read(); $this->assertEquals(\core_competency\user_competency::STATUS_WAITING_FOR_REVIEW, $uc->get('status')); // A user competency with a status non-idle won't change. $uc->set('status', \core_competency\user_competency::STATUS_IN_REVIEW); $uc->update(); $evidence = api::add_evidence($u1->id, $c1->get('id'), $u1ctx->id, \core_competency\evidence::ACTION_LOG, 'invaliddata', 'error', null, true); $uc->read(); $this->assertEquals(\core_competency\user_competency::STATUS_IN_REVIEW, $uc->get('status')); } /** * Test add evidence for existing user_competency. */ public function test_add_evidence_existing_user_competency(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $syscontext = \context_system::instance(); // Create users. $user = $dg->create_user(); $this->setUser($user); // Create a framework and assign competencies. $framework = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c3 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $uc = $lpg->create_user_competency(array('userid' => $user->id, 'competencyid' => $c1->get('id'))); $this->assertSame(null, $uc->get('grade')); $this->assertSame(null, $uc->get('proficiency')); // Create an evidence and check it was created with the right usercomptencyid and information. $evidence = api::add_evidence($user->id, $c1->get('id'), $syscontext->id, \core_competency\evidence::ACTION_OVERRIDE, 'invalidevidencedesc', 'core_competency', array('a' => 'b'), false, 'http://moodle.org', 1, 2); $this->assertEquals(1, \core_competency\evidence::count_records()); $evidence->read(); $uc->read(); $this->assertEquals($uc->get('id'), $evidence->get('usercompetencyid')); $this->assertEquals('invalidevidencedesc', $evidence->get('descidentifier')); $this->assertEquals('core_competency', $evidence->get('desccomponent')); $this->assertEquals((object) array('a' => 'b'), $evidence->get('desca')); $this->assertEquals('http://moodle.org', $evidence->get('url')); $this->assertEquals(\core_competency\evidence::ACTION_OVERRIDE, $evidence->get('action')); $this->assertEquals(2, $evidence->get('actionuserid')); $this->assertEquals(1, $evidence->get('grade')); $this->assertEquals(1, $uc->get('grade')); $this->assertEquals(0, $uc->get('proficiency')); } /** * Test add evidence for non-existing user_competency. */ public function test_add_evidence_no_existing_user_competency(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $syscontext = \context_system::instance(); // Create users. $user = $dg->create_user(); $this->setUser($user); // Create a framework and assign competencies. $framework = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $this->assertEquals(0, \core_competency\user_competency::count_records()); // Create an evidence without a user competency record. $evidence = api::add_evidence($user->id, $c1->get('id'), $syscontext->id, \core_competency\evidence::ACTION_OVERRIDE, 'invalidevidencedesc', 'core_competency', 'Hello world!', false, 'http://moodle.org', 1, 2); $this->assertEquals(1, \core_competency\evidence::count_records()); $this->assertEquals(1, \core_competency\user_competency::count_records()); $uc = \core_competency\user_competency::get_record(array('userid' => $user->id, 'competencyid' => $c1->get('id'))); $evidence->read(); $this->assertEquals($uc->get('id'), $evidence->get('usercompetencyid')); $this->assertEquals('invalidevidencedesc', $evidence->get('descidentifier')); $this->assertEquals('core_competency', $evidence->get('desccomponent')); $this->assertEquals('Hello world!', $evidence->get('desca')); $this->assertEquals('http://moodle.org', $evidence->get('url')); $this->assertEquals(\core_competency\evidence::ACTION_OVERRIDE, $evidence->get('action')); $this->assertEquals(2, $evidence->get('actionuserid')); $this->assertEquals(1, $evidence->get('grade')); $this->assertEquals(1, $uc->get('grade')); $this->assertEquals(0, $uc->get('proficiency')); } public function test_add_evidence_applies_competency_rules(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $syscontext = \context_system::instance(); $ctxid = $syscontext->id; $u1 = $dg->create_user(); // Setting up the framework. $f1 = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c1a = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1->get('id'))); $c1b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c2a = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c2->get('id'))); $c3 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c3a = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c3->get('id'))); $c4 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c4a = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c4->get('id'))); $c5 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); // Setting up the rules. $c1->set('ruletype', 'core_competency\\competency_rule_all'); $c1->set('ruleoutcome', \core_competency\competency::OUTCOME_COMPLETE); $c1->update(); $c2->set('ruletype', 'core_competency\\competency_rule_all'); $c2->set('ruleoutcome', \core_competency\competency::OUTCOME_RECOMMEND); $c2->update(); $c3->set('ruletype', 'core_competency\\competency_rule_all'); $c3->set('ruleoutcome', \core_competency\competency::OUTCOME_EVIDENCE); $c3->update(); $c4->set('ruletype', 'core_competency\\competency_rule_all'); $c4->set('ruleoutcome', \core_competency\competency::OUTCOME_NONE); $c4->update(); // Confirm the current data. $this->assertEquals(0, user_competency::count_records()); $this->assertEquals(0, evidence::count_records()); // Let's do this! // First let's confirm that evidence not marking a completion have no impact. api::add_evidence($u1->id, $c1a, $ctxid, evidence::ACTION_LOG, 'commentincontext', 'core'); $uc1a = user_competency::get_record(array('userid' => $u1->id, 'competencyid' => $c1a->get('id'))); $this->assertSame(null, $uc1a->get('proficiency')); $this->assertFalse(user_competency::record_exists_select('userid = ? AND competencyid = ?', array($u1->id, $c1->get('id')))); // Now let's try complete a competency but the rule won't match (not all children are complete). // The parent (the thing with the rule) will be created but won't have any evidence attached, and not // not be marked as completed. api::add_evidence($u1->id, $c1a, $ctxid, evidence::ACTION_COMPLETE, 'commentincontext', 'core'); $uc1a = user_competency::get_record(array('userid' => $u1->id, 'competencyid' => $c1a->get('id'))); $this->assertEquals(true, $uc1a->get('proficiency')); $uc1 = user_competency::get_record(array('userid' => $u1->id, 'competencyid' => $c1->get('id'))); $this->assertSame(null, $uc1->get('proficiency')); $this->assertEquals(0, evidence::count_records(array('usercompetencyid' => $uc1->get('id')))); // Now we complete the other child. That will mark the parent as complete with an evidence. api::add_evidence($u1->id, $c1b, $ctxid, evidence::ACTION_COMPLETE, 'commentincontext', 'core'); $uc1b = user_competency::get_record(array('userid' => $u1->id, 'competencyid' => $c1b->get('id'))); $this->assertEquals(true, $uc1a->get('proficiency')); $uc1 = user_competency::get_record(array('userid' => $u1->id, 'competencyid' => $c1->get('id'))); $this->assertEquals(true, $uc1->get('proficiency')); $this->assertEquals(user_competency::STATUS_IDLE, $uc1->get('status')); $this->assertEquals(1, evidence::count_records(array('usercompetencyid' => $uc1->get('id')))); // Check rule recommending. api::add_evidence($u1->id, $c2a, $ctxid, evidence::ACTION_COMPLETE, 'commentincontext', 'core'); $uc2a = user_competency::get_record(array('userid' => $u1->id, 'competencyid' => $c2a->get('id'))); $this->assertEquals(true, $uc1a->get('proficiency')); $uc2 = user_competency::get_record(array('userid' => $u1->id, 'competencyid' => $c2->get('id'))); $this->assertSame(null, $uc2->get('proficiency')); $this->assertEquals(user_competency::STATUS_WAITING_FOR_REVIEW, $uc2->get('status')); $this->assertEquals(1, evidence::count_records(array('usercompetencyid' => $uc2->get('id')))); // Check rule evidence. api::add_evidence($u1->id, $c3a, $ctxid, evidence::ACTION_COMPLETE, 'commentincontext', 'core'); $uc3a = user_competency::get_record(array('userid' => $u1->id, 'competencyid' => $c3a->get('id'))); $this->assertEquals(true, $uc1a->get('proficiency')); $uc3 = user_competency::get_record(array('userid' => $u1->id, 'competencyid' => $c3->get('id'))); $this->assertSame(null, $uc3->get('proficiency')); $this->assertEquals(user_competency::STATUS_IDLE, $uc3->get('status')); $this->assertEquals(1, evidence::count_records(array('usercompetencyid' => $uc3->get('id')))); // Check rule nothing. api::add_evidence($u1->id, $c4a, $ctxid, evidence::ACTION_COMPLETE, 'commentincontext', 'core'); $uc4a = user_competency::get_record(array('userid' => $u1->id, 'competencyid' => $c4a->get('id'))); $this->assertEquals(true, $uc1a->get('proficiency')); $this->assertFalse(user_competency::record_exists_select('userid = ? AND competencyid = ?', array($u1->id, $c4->get('id')))); // Check marking on something that has no parent. This just checks that nothing breaks. api::add_evidence($u1->id, $c5, $ctxid, evidence::ACTION_COMPLETE, 'commentincontext', 'core'); } /** * Tests for the user_competency_course data when api::add_evidence() is invoked when * grading a user competency in the system context. */ public function test_add_evidence_for_user_competency_course_grade_outside_course(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $syscontext = \context_system::instance(); // Create a student. $student = $dg->create_user(); // Create a competency for the course. $lpg = $dg->get_plugin_generator('core_competency'); $framework = $lpg->create_framework(); $comp = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); // Add evidence. api::add_evidence($student->id, $comp, $syscontext, evidence::ACTION_OVERRIDE, 'commentincontext', 'core', null, false, null, 1); // Query for user_competency_course data. $filterparams = array( 'userid' => $student->id, 'competencyid' => $comp->get('id'), ); $usercompcourse = \core_competency\user_competency_course::get_record($filterparams); // There should be no user_competency_course object created when grading. $this->assertFalse($usercompcourse); } /** * Tests for the user_competency_course data when api::add_evidence() is invoked when * grading a user competency in a course. */ public function test_add_evidence_user_competency_course_grade_in_course(): void { global $USER; $this->resetAfterTest(true); $dg = $this->getDataGenerator(); // Create and assign a current user. $currentuser = $dg->create_user(); $this->setUser($currentuser); // Create a course. $course = $dg->create_course(); $record = array('courseid' => $course->id, 'pushratingstouserplans' => false); $settings = new course_competency_settings(0, (object) $record); $settings->create(); $coursecontext = \context_course::instance($course->id); // Create a student and enrol into the course. $student = $dg->create_user(); $studentarch = get_archetype_roles('student'); $studentrole = array_shift($studentarch); $dg->role_assign($studentrole->id, $student->id, $coursecontext->id); $dg->enrol_user($student->id, $course->id, $studentrole->id); // Create a competency for the course. $lpg = $dg->get_plugin_generator('core_competency'); $framework = $lpg->create_framework(); // Do not push ratings from course to user plans. $comp = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $lpg->create_course_competency(array('courseid' => $course->id, 'competencyid' => $comp->get('id'))); // Query for user_competency_course data. $filterparams = array( 'userid' => $student->id, 'competencyid' => $comp->get('id'), 'courseid' => $course->id ); // Add evidence that sets a grade to the course. $evidence = api::add_evidence($student->id, $comp, $coursecontext, evidence::ACTION_OVERRIDE, 'commentincontext', 'core', null, false, null, 3, $USER->id); // Get user competency course record. $usercompcourse = \core_competency\user_competency_course::get_record($filterparams); // There should be a user_competency_course object when adding a grade. $this->assertNotEmpty($usercompcourse); $grade = $evidence->get('grade'); $this->assertEquals($grade, $usercompcourse->get('grade')); $this->assertEquals(3, $usercompcourse->get('grade')); $proficiency = $comp->get_proficiency_of_grade($grade); $this->assertEquals($proficiency, $usercompcourse->get('proficiency')); // Confirm that the user competency's grade/proficiency has not been affected by the grade. $usercompetencyparams = [ 'userid' => $student->id, 'competencyid' => $comp->get('id'), ]; $usercompetency = \core_competency\user_competency::get_record($usercompetencyparams); $this->assertNotEmpty($usercompetency); $this->assertNotEquals($usercompcourse->get('grade'), $usercompetency->get('grade')); $this->assertNotEquals($usercompcourse->get('proficiency'), $usercompetency->get('proficiency')); } public function test_observe_course_completed(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); // Set-up users, framework, competencies and course competencies. $course = $dg->create_course(); $coursectx = \context_course::instance($course->id); $u1 = $dg->create_user(); $f1 = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c3 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c4 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $cc1 = $lpg->create_course_competency(array('competencyid' => $c1->get('id'), 'courseid' => $course->id, 'ruleoutcome' => \core_competency\course_competency::OUTCOME_NONE)); $cc2 = $lpg->create_course_competency(array('competencyid' => $c2->get('id'), 'courseid' => $course->id, 'ruleoutcome' => \core_competency\course_competency::OUTCOME_EVIDENCE)); $cc3 = $lpg->create_course_competency(array('competencyid' => $c3->get('id'), 'courseid' => $course->id, 'ruleoutcome' => \core_competency\course_competency::OUTCOME_RECOMMEND)); $cc4 = $lpg->create_course_competency(array('competencyid' => $c4->get('id'), 'courseid' => $course->id, 'ruleoutcome' => \core_competency\course_competency::OUTCOME_COMPLETE)); $event = \core\event\course_completed::create(array( 'objectid' => 1, 'relateduserid' => $u1->id, 'context' => $coursectx, 'courseid' => $course->id, 'other' => array('relateduserid' => $u1->id) )); $this->assertEquals(0, \core_competency\user_competency::count_records()); $this->assertEquals(0, \core_competency\evidence::count_records()); // Let's go! api::observe_course_completed($event); $this->assertEquals(3, \core_competency\user_competency::count_records()); $this->assertEquals(3, \core_competency\evidence::count_records()); // Outcome NONE did nothing. $this->assertFalse(\core_competency\user_competency::record_exists_select('userid = :uid AND competencyid = :cid', array( 'uid' => $u1->id, 'cid' => $c1->get('id') ))); // Outcome evidence. $uc2 = \core_competency\user_competency::get_record(array('userid' => $u1->id, 'competencyid' => $c2->get('id'))); $ev2 = \core_competency\evidence::get_record(array('usercompetencyid' => $uc2->get('id'))); $this->assertEquals(null, $uc2->get('grade')); $this->assertEquals(null, $uc2->get('proficiency')); $this->assertEquals(\core_competency\user_competency::STATUS_IDLE, $uc2->get('status')); $this->assertEquals('evidence_coursecompleted', $ev2->get('descidentifier')); $this->assertEquals('core_competency', $ev2->get('desccomponent')); $this->assertEquals($course->shortname, $ev2->get('desca')); $this->assertStringEndsWith('/report/completion/index.php?course=' . $course->id, $ev2->get('url')); $this->assertEquals(null, $ev2->get('grade')); $this->assertEquals($coursectx->id, $ev2->get('contextid')); $this->assertEquals(\core_competency\evidence::ACTION_LOG, $ev2->get('action')); $this->assertEquals(null, $ev2->get('actionuserid')); // Outcome recommend. $uc3 = \core_competency\user_competency::get_record(array('userid' => $u1->id, 'competencyid' => $c3->get('id'))); $ev3 = \core_competency\evidence::get_record(array('usercompetencyid' => $uc3->get('id'))); $this->assertEquals(null, $uc3->get('grade')); $this->assertEquals(null, $uc3->get('proficiency')); $this->assertEquals(\core_competency\user_competency::STATUS_WAITING_FOR_REVIEW, $uc3->get('status')); $this->assertEquals('evidence_coursecompleted', $ev3->get('descidentifier')); $this->assertEquals('core_competency', $ev3->get('desccomponent')); $this->assertEquals($course->shortname, $ev3->get('desca')); $this->assertStringEndsWith('/report/completion/index.php?course=' . $course->id, $ev3->get('url')); $this->assertEquals(null, $ev3->get('grade')); $this->assertEquals($coursectx->id, $ev3->get('contextid')); $this->assertEquals(\core_competency\evidence::ACTION_LOG, $ev3->get('action')); $this->assertEquals(null, $ev3->get('actionuserid')); // Outcome complete. $uc4 = \core_competency\user_competency::get_record(array('userid' => $u1->id, 'competencyid' => $c4->get('id'))); $ev4 = \core_competency\evidence::get_record(array('usercompetencyid' => $uc4->get('id'))); $this->assertEquals(3, $uc4->get('grade')); $this->assertEquals(1, $uc4->get('proficiency')); $this->assertEquals(\core_competency\user_competency::STATUS_IDLE, $uc4->get('status')); $this->assertEquals('evidence_coursecompleted', $ev4->get('descidentifier')); $this->assertEquals('core_competency', $ev4->get('desccomponent')); $this->assertEquals($course->shortname, $ev4->get('desca')); $this->assertStringEndsWith('/report/completion/index.php?course=' . $course->id, $ev4->get('url')); $this->assertEquals(3, $ev4->get('grade')); $this->assertEquals($coursectx->id, $ev4->get('contextid')); $this->assertEquals(\core_competency\evidence::ACTION_COMPLETE, $ev4->get('action')); $this->assertEquals(null, $ev4->get('actionuserid')); } public function test_list_evidence_in_course(): void { global $SITE; $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $u1 = $dg->create_user(); $course = $dg->create_course(); $coursecontext = \context_course::instance($course->id); $this->setAdminUser(); $f = $lpg->create_framework(); $c = $lpg->create_competency(array('competencyframeworkid' => $f->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $f->get('id'))); $cc = api::add_competency_to_course($course->id, $c->get('id')); $cc2 = api::add_competency_to_course($course->id, $c2->get('id')); $pagegenerator = $this->getDataGenerator()->get_plugin_generator('mod_page'); $page = $pagegenerator->create_instance(array('course' => $course->id)); $cm = get_coursemodule_from_instance('page', $page->id); $cmcontext = \context_module::instance($cm->id); // Add the competency to the course module. $ccm = api::add_competency_to_course_module($cm, $c->get('id')); // Now add the evidence to the course. $evidence1 = api::add_evidence($u1->id, $c->get('id'), $coursecontext->id, \core_competency\evidence::ACTION_LOG, 'invaliddata', 'error'); $result = api::list_evidence_in_course($u1->id, $course->id, $c->get('id')); $this->assertEquals($result[0]->get('id'), $evidence1->get('id')); // Now add the evidence to the course module. $evidence2 = api::add_evidence($u1->id, $c->get('id'), $cmcontext->id, \core_competency\evidence::ACTION_LOG, 'invaliddata', 'error'); $result = api::list_evidence_in_course($u1->id, $course->id, $c->get('id'), 'timecreated', 'ASC'); $this->assertEquals($evidence1->get('id'), $result[0]->get('id')); $this->assertEquals($evidence2->get('id'), $result[1]->get('id')); } public function test_list_course_modules_using_competency(): void { global $SITE; $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $u1 = $dg->create_user(); $u2 = $dg->create_user(); $course = $dg->create_course(); $course2 = $dg->create_course(); $this->setAdminUser(); $f = $lpg->create_framework(); $c = $lpg->create_competency(array('competencyframeworkid' => $f->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $f->get('id'))); $cc = api::add_competency_to_course($course->id, $c->get('id')); $cc2 = api::add_competency_to_course($course->id, $c2->get('id')); // First check we get an empty list when there are no links. $expected = array(); $result = api::list_course_modules_using_competency($c->get('id'), $course->id); $this->assertEquals($expected, $result); $pagegenerator = $this->getDataGenerator()->get_plugin_generator('mod_page'); $page = $pagegenerator->create_instance(array('course' => $course->id)); $cm = get_coursemodule_from_instance('page', $page->id); // Add a link and list again. $ccm = api::add_competency_to_course_module($cm, $c->get('id')); $expected = array($cm->id); $result = api::list_course_modules_using_competency($c->get('id'), $course->id); $this->assertEquals($expected, $result); // Check a different course. $expected = array(); $result = api::list_course_modules_using_competency($c->get('id'), $course2->id); $this->assertEquals($expected, $result); // Remove the link and check again. $result = api::remove_competency_from_course_module($cm, $c->get('id')); $expected = true; $this->assertEquals($expected, $result); $expected = array(); $result = api::list_course_modules_using_competency($c->get('id'), $course->id); $this->assertEquals($expected, $result); // Now add 2 links. api::add_competency_to_course_module($cm, $c->get('id')); api::add_competency_to_course_module($cm, $c2->get('id')); $result = api::list_course_module_competencies_in_course_module($cm->id); $this->assertEquals($result[0]->get('competencyid'), $c->get('id')); $this->assertEquals($result[1]->get('competencyid'), $c2->get('id')); // Now re-order. api::reorder_course_module_competency($cm, $c->get('id'), $c2->get('id')); $result = api::list_course_module_competencies_in_course_module($cm->id); $this->assertEquals($result[0]->get('competencyid'), $c2->get('id')); $this->assertEquals($result[1]->get('competencyid'), $c->get('id')); // And re-order again. api::reorder_course_module_competency($cm, $c->get('id'), $c2->get('id')); $result = api::list_course_module_competencies_in_course_module($cm->id); $this->assertEquals($result[0]->get('competencyid'), $c->get('id')); $this->assertEquals($result[1]->get('competencyid'), $c2->get('id')); // Now get the course competency and coursemodule competency together. $result = api::list_course_module_competencies($cm->id); // Now we should have an array and each element of the array should have a competency and // a coursemodulecompetency. foreach ($result as $instance) { $cmc = $instance['coursemodulecompetency']; $c = $instance['competency']; $this->assertEquals($cmc->get('competencyid'), $c->get('id')); } } /** * Test update ruleoutcome for course_competency. */ public function test_set_ruleoutcome_course_competency(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $u1 = $dg->create_user(); $u2 = $dg->create_user(); $course = $dg->create_course(); $this->setAdminUser(); $f = $lpg->create_framework(); $c = $lpg->create_competency(array('competencyframeworkid' => $f->get('id'))); $cc = api::add_competency_to_course($course->id, $c->get('id')); // Check record was created with default rule value Evidence. $this->assertEquals(1, \core_competency\course_competency::count_records()); $recordscc = api::list_course_competencies($course->id); $this->assertEquals(\core_competency\course_competency::OUTCOME_EVIDENCE, $recordscc[0]['coursecompetency']->get('ruleoutcome')); // Check ruleoutcome value is updated to None. $this->assertTrue(api::set_course_competency_ruleoutcome($recordscc[0]['coursecompetency']->get('id'), \core_competency\course_competency::OUTCOME_NONE)); $recordscc = api::list_course_competencies($course->id); $this->assertEquals(\core_competency\course_competency::OUTCOME_NONE, $recordscc[0]['coursecompetency']->get('ruleoutcome')); } /** * Test validation on grade on user_competency. */ public function test_validate_grade_in_user_competency(): void { global $DB; $this->resetAfterTest(true); $this->setAdminUser(); $dg = $this->getDataGenerator(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $user = $dg->create_user(); $s1 = $dg->create_scale(array("scale" => "value1, value2")); $s2 = $dg->create_scale(array("scale" => "value3, value4, value5, value6")); $scaleconfiguration1 = '[{"scaleid":"'.$s1->id.'"},{"name":"value1","id":1,"scaledefault":1,"proficient":0},' . '{"name":"value2","id":2,"scaledefault":0,"proficient":1}]'; $scaleconfiguration2 = '[{"scaleid":"'.$s2->id.'"},{"name":"value3","id":1,"scaledefault":1,"proficient":0},' . '{"name":"value4","id":2,"scaledefault":0,"proficient":1}]'; // Create a framework with scale configuration1. $frm = array( 'scaleid' => $s1->id, 'scaleconfiguration' => $scaleconfiguration1 ); $framework = $lpg->create_framework($frm); $c1 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); // Create competency with its own scale configuration. $c2 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'), 'scaleid' => $s2->id, 'scaleconfiguration' => $scaleconfiguration2 )); // Detecte invalid grade in competency using its framework competency scale. try { $usercompetency = new user_competency(0, (object) array('userid' => $user->id, 'competencyid' => $c1->get('id'), 'proficiency' => true, 'grade' => 3 )); $usercompetency->create(); $this->fail('Invalid grade not detected in framework scale'); } catch (\core\invalid_persistent_exception $e) { $this->assertTrue(true); } // Detecte invalid grade in competency using its own scale. try { $usercompetency = new user_competency(0, (object) array('userid' => $user->id, 'competencyid' => $c2->get('id'), 'proficiency' => true, 'grade' => 5 )); $usercompetency->create(); $this->fail('Invalid grade not detected in competency scale'); } catch (\core\invalid_persistent_exception $e) { $this->assertTrue(true); } // Accept valid grade in competency using its framework competency scale. try { $usercompetency = new user_competency(0, (object) array('userid' => $user->id, 'competencyid' => $c1->get('id'), 'proficiency' => true, 'grade' => 1 )); $usercompetency->create(); $this->assertTrue(true); } catch (\core\invalid_persistent_exception $e) { $this->fail('Valide grade rejected in framework scale'); } // Accept valid grade in competency using its framework competency scale. try { $usercompetency = new user_competency(0, (object) array('userid' => $user->id, 'competencyid' => $c2->get('id'), 'proficiency' => true, 'grade' => 4 )); $usercompetency->create(); $this->assertTrue(true); } catch (\core\invalid_persistent_exception $e) { $this->fail('Valide grade rejected in competency scale'); } } /** * Test when adding competency that belong to hidden framework to plan/template/course. */ public function test_hidden_framework(): void { $this->resetAfterTest(true); $this->setAdminUser(); $dg = $this->getDataGenerator(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $user = $dg->create_user(); // Create a course. $cat1 = $dg->create_category(); $course = $dg->create_course(array('category' => $cat1->id)); // Create a template. $template = $lpg->create_template(); // Create a plan. $plan = $lpg->create_plan(array('userid' => $user->id)); // Create a hidden framework. $frm = array( 'visible' => false ); $framework = $lpg->create_framework($frm); $competency = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); // Linking competency that belong to hidden framework to course. try { api::add_competency_to_course($course->id, $competency->get('id')); $this->fail('A competency belonging to hidden framework can not be linked to course'); } catch (\coding_exception $e) { $this->assertTrue(true); } // Adding competency that belong to hidden framework to template. try { api::add_competency_to_template($template->get('id'), $competency->get('id')); $this->fail('A competency belonging to hidden framework can not be added to template'); } catch (\coding_exception $e) { $this->assertTrue(true); } // Adding competency that belong to hidden framework to plan. try { api::add_competency_to_plan($plan->get('id'), $competency->get('id')); $this->fail('A competency belonging to hidden framework can not be added to plan'); } catch (\coding_exception $e) { $this->assertTrue(true); } } /** * Test when using hidden template in plan/cohort. */ public function test_hidden_template(): void { $this->resetAfterTest(true); $this->setAdminUser(); $dg = $this->getDataGenerator(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $user = $dg->create_user(); // Create a cohort. $cohort = $dg->create_cohort(); // Create a hidden template. $template = $lpg->create_template(array('visible' => false)); // Can not link hidden template to plan. try { api::create_plan_from_template($template->get('id'), $user->id); $this->fail('Can not link a hidden template to plan'); } catch (\coding_exception $e) { $this->assertTrue(true); } // Can associate hidden template to cohort. $templatecohort = api::create_template_cohort($template->get('id'), $cohort->id); $this->assertInstanceOf('\core_competency\template_cohort', $templatecohort); } /** * Test that completed plan created form a template does not change when template is modified. */ public function test_completed_plan_doesnot_change(): void { global $DB; $this->resetAfterTest(true); $this->setAdminUser(); $dg = $this->getDataGenerator(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $user = $dg->create_user(); // Create a framework and assign competencies. $framework = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c3 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c4 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); // Create template and assign competencies. $tp = $lpg->create_template(); $tpc1 = $lpg->create_template_competency(array('templateid' => $tp->get('id'), 'competencyid' => $c1->get('id'))); $tpc2 = $lpg->create_template_competency(array('templateid' => $tp->get('id'), 'competencyid' => $c2->get('id'))); $tpc3 = $lpg->create_template_competency(array('templateid' => $tp->get('id'), 'competencyid' => $c3->get('id'))); // Create a plan form template and change it status to complete. $plan = $lpg->create_plan(array('userid' => $user->id, 'templateid' => $tp->get('id'))); api::complete_plan($plan); // Check user competency plan created correctly. $this->assertEquals(3, \core_competency\user_competency_plan::count_records()); $ucp = \core_competency\user_competency_plan::get_records(); $this->assertEquals($ucp[0]->get('competencyid'), $c1->get('id')); $this->assertEquals($ucp[1]->get('competencyid'), $c2->get('id')); $this->assertEquals($ucp[2]->get('competencyid'), $c3->get('id')); // Add and remove a competency from the template. api::add_competency_to_template($tp->get('id'), $c4->get('id')); api::remove_competency_from_template($tp->get('id'), $c1->get('id')); // Check that user competency plan did not change. $competencies = $plan->get_competencies(); $this->assertEquals(3, count($competencies)); $ucp1 = array($c1->get('id'), $c2->get('id'), $c3->get('id')); $ucp2 = array(); foreach ($competencies as $id => $cmp) { $ucp2[] = $id; } $this->assertEquals(0, count(array_diff($ucp1, $ucp2))); } protected function setup_framework_for_reset_rules_tests() { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $this->setAdminUser(); $f1 = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c1a = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1->get('id'))); $c1a1 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1a->get('id'))); $c1a1a = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1a1->get('id'))); $c1b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c1b1 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1b->get('id'))); $c1b1a = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1b1->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c2a = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c1->set('ruleoutcome', competency::OUTCOME_EVIDENCE); $c1->set('ruletype', 'core_competency\\competency_rule_all'); $c1->update(); $c1a->set('ruleoutcome', competency::OUTCOME_EVIDENCE); $c1a->set('ruletype', 'core_competency\\competency_rule_all'); $c1a->update(); $c1a1->set('ruleoutcome', competency::OUTCOME_EVIDENCE); $c1a1->set('ruletype', 'core_competency\\competency_rule_all'); $c1a1->update(); $c1b->set('ruleoutcome', competency::OUTCOME_EVIDENCE); $c1b->set('ruletype', 'core_competency\\competency_rule_all'); $c1b->update(); $c2->set('ruleoutcome', competency::OUTCOME_EVIDENCE); $c2->set('ruletype', 'core_competency\\competency_rule_all'); $c2->update(); return array( 'f1' => $f1, 'c1' => $c1, 'c1a' => $c1a, 'c1a1' => $c1a1, 'c1a1a' => $c1a1a, 'c1b' => $c1b, 'c1b1' => $c1b1, 'c1b1a' => $c1b1a, 'c2' => $c2, 'c2a' => $c2a, ); } public function test_moving_competency_reset_rules_updown(): void { $data = $this->setup_framework_for_reset_rules_tests(); $f1 = $data['f1']; $c1 = $data['c1']; $c1a = $data['c1a']; $c1a1 = $data['c1a1']; $c1a1a = $data['c1a1a']; $c1b = $data['c1b']; $c1b1 = $data['c1b1']; $c1b1a = $data['c1b1a']; $c2 = $data['c2']; $c2a = $data['c2a']; // Moving up and down doesn't change anything. api::move_down_competency($c1a->get('id')); $c1->read(); $c1a->read(); $c1a1->read(); $this->assertEquals(competency::OUTCOME_EVIDENCE, $c1->get('ruleoutcome')); $this->assertEquals(competency::OUTCOME_EVIDENCE, $c1a->get('ruleoutcome')); $this->assertEquals(competency::OUTCOME_EVIDENCE, $c1a1->get('ruleoutcome')); api::move_up_competency($c1a->get('id')); $c1->read(); $c1a->read(); $c1a1->read(); $this->assertEquals(competency::OUTCOME_EVIDENCE, $c1->get('ruleoutcome')); $this->assertEquals(competency::OUTCOME_EVIDENCE, $c1a->get('ruleoutcome')); $this->assertEquals(competency::OUTCOME_EVIDENCE, $c1a1->get('ruleoutcome')); } public function test_moving_competency_reset_rules_parent(): void { $data = $this->setup_framework_for_reset_rules_tests(); $f1 = $data['f1']; $c1 = $data['c1']; $c1a = $data['c1a']; $c1a1 = $data['c1a1']; $c1a1a = $data['c1a1a']; $c1b = $data['c1b']; $c1b1 = $data['c1b1']; $c1b1a = $data['c1b1a']; $c2 = $data['c2']; $c2a = $data['c2a']; // Moving out of parent will reset the parent, and the destination. api::set_parent_competency($c1a->get('id'), $c1b->get('id')); $c1->read(); $c1a->read(); $c1a1->read(); $c1b->read(); $c2->read(); $this->assertEquals(competency::OUTCOME_NONE, $c1->get('ruleoutcome')); $this->assertEquals(competency::OUTCOME_EVIDENCE, $c1a->get('ruleoutcome')); $this->assertEquals(competency::OUTCOME_EVIDENCE, $c1a1->get('ruleoutcome')); $this->assertEquals(competency::OUTCOME_NONE, $c1b->get('ruleoutcome')); $this->assertEquals(competency::OUTCOME_EVIDENCE, $c2->get('ruleoutcome')); } public function test_moving_competency_reset_rules_totoplevel(): void { $data = $this->setup_framework_for_reset_rules_tests(); $f1 = $data['f1']; $c1 = $data['c1']; $c1a = $data['c1a']; $c1a1 = $data['c1a1']; $c1a1a = $data['c1a1a']; $c1b = $data['c1b']; $c1b1 = $data['c1b1']; $c1b1a = $data['c1b1a']; $c2 = $data['c2']; $c2a = $data['c2a']; // Moving to top level only affects the initial parent. api::set_parent_competency($c1a1->get('id'), 0); $c1->read(); $c1a->read(); $c1a1->read(); $c1b->read(); $c2->read(); $this->assertEquals(competency::OUTCOME_EVIDENCE, $c1->get('ruleoutcome')); $this->assertEquals(competency::OUTCOME_NONE, $c1a->get('ruleoutcome')); $this->assertEquals(competency::OUTCOME_EVIDENCE, $c1a1->get('ruleoutcome')); $this->assertEquals(competency::OUTCOME_EVIDENCE, $c1b->get('ruleoutcome')); $this->assertEquals(competency::OUTCOME_EVIDENCE, $c2->get('ruleoutcome')); } public function test_moving_competency_reset_rules_fromtoplevel(): void { $data = $this->setup_framework_for_reset_rules_tests(); $f1 = $data['f1']; $c1 = $data['c1']; $c1a = $data['c1a']; $c1a1 = $data['c1a1']; $c1a1a = $data['c1a1a']; $c1b = $data['c1b']; $c1b1 = $data['c1b1']; $c1b1a = $data['c1b1a']; $c2 = $data['c2']; $c2a = $data['c2a']; // Moving from top level only affects the destination parent. api::set_parent_competency($c2->get('id'), $c1a1->get('id')); $c1->read(); $c1a->read(); $c1a1->read(); $c1b->read(); $c2->read(); $this->assertEquals(competency::OUTCOME_EVIDENCE, $c1->get('ruleoutcome')); $this->assertEquals(competency::OUTCOME_EVIDENCE, $c1a->get('ruleoutcome')); $this->assertEquals(competency::OUTCOME_NONE, $c1a1->get('ruleoutcome')); $this->assertEquals(competency::OUTCOME_EVIDENCE, $c1b->get('ruleoutcome')); $this->assertEquals(competency::OUTCOME_EVIDENCE, $c2->get('ruleoutcome')); } public function test_moving_competency_reset_rules_child(): void { $data = $this->setup_framework_for_reset_rules_tests(); $f1 = $data['f1']; $c1 = $data['c1']; $c1a = $data['c1a']; $c1a1 = $data['c1a1']; $c1a1a = $data['c1a1a']; $c1b = $data['c1b']; $c1b1 = $data['c1b1']; $c1b1a = $data['c1b1a']; $c2 = $data['c2']; $c2a = $data['c2a']; // Moving to a child of self resets self, parent and destination. api::set_parent_competency($c1a->get('id'), $c1a1->get('id')); $c1->read(); $c1a->read(); $c1a1->read(); $c1b->read(); $c2->read(); $this->assertEquals(competency::OUTCOME_NONE, $c1->get('ruleoutcome')); $this->assertEquals(competency::OUTCOME_NONE, $c1a->get('ruleoutcome')); $this->assertEquals(competency::OUTCOME_NONE, $c1a1->get('ruleoutcome')); $this->assertEquals(competency::OUTCOME_EVIDENCE, $c1b->get('ruleoutcome')); $this->assertEquals(competency::OUTCOME_EVIDENCE, $c2->get('ruleoutcome')); } public function test_create_competency_reset_rules(): void { $data = $this->setup_framework_for_reset_rules_tests(); $f1 = $data['f1']; $c1 = $data['c1']; $c1a = $data['c1a']; $c1a1 = $data['c1a1']; $c1a1a = $data['c1a1a']; $c1b = $data['c1b']; $c1b1 = $data['c1b1']; $c1b1a = $data['c1b1a']; $c2 = $data['c2']; $c2a = $data['c2a']; // Adding a new competency resets the rule of its parent. api::create_competency((object) array('shortname' => 'A', 'parentid' => $c1->get('id'), 'idnumber' => 'A', 'competencyframeworkid' => $f1->get('id'))); $c1->read(); $c1a->read(); $c1a1->read(); $c1b->read(); $c2->read(); $this->assertEquals(competency::OUTCOME_NONE, $c1->get('ruleoutcome')); $this->assertEquals(competency::OUTCOME_EVIDENCE, $c1a->get('ruleoutcome')); $this->assertEquals(competency::OUTCOME_EVIDENCE, $c1a1->get('ruleoutcome')); $this->assertEquals(competency::OUTCOME_EVIDENCE, $c1b->get('ruleoutcome')); $this->assertEquals(competency::OUTCOME_EVIDENCE, $c2->get('ruleoutcome')); } public function test_delete_competency_reset_rules(): void { $data = $this->setup_framework_for_reset_rules_tests(); $f1 = $data['f1']; $c1 = $data['c1']; $c1a = $data['c1a']; $c1a1 = $data['c1a1']; $c1a1a = $data['c1a1a']; $c1b = $data['c1b']; $c1b1 = $data['c1b1']; $c1b1a = $data['c1b1a']; $c2 = $data['c2']; $c2a = $data['c2a']; // Deleting a competency resets the rule of its parent. api::delete_competency($c1a->get('id')); $c1->read(); $c1b->read(); $c2->read(); $this->assertEquals(competency::OUTCOME_NONE, $c1->get('ruleoutcome')); $this->assertEquals(competency::OUTCOME_EVIDENCE, $c1b->get('ruleoutcome')); $this->assertEquals(competency::OUTCOME_EVIDENCE, $c2->get('ruleoutcome')); } public function test_template_has_related_data(): void { $this->resetAfterTest(true); $this->setAdminUser(); $dg = $this->getDataGenerator(); $user = $dg->create_user(); $lpg = $dg->get_plugin_generator('core_competency'); $tpl1 = $lpg->create_template(); $tpl2 = $lpg->create_template(); // Create plans for first template. $time = time(); $plan1 = $lpg->create_plan(array('templateid' => $tpl1->get('id'), 'userid' => $user->id, 'name' => 'Not good name', 'duedate' => $time + 3600, 'description' => 'Ahah', 'descriptionformat' => FORMAT_PLAIN)); $this->assertTrue(api::template_has_related_data($tpl1->get('id'))); $this->assertFalse(api::template_has_related_data($tpl2->get('id'))); } public function test_delete_template_delete_plans(): void { $this->resetAfterTest(true); $this->setAdminUser(); $dg = $this->getDataGenerator(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $u1 = $dg->create_user(); $f = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $f->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $f->get('id'))); $tpl = $lpg->create_template(); $tplc1 = $lpg->create_template_competency(array('templateid' => $tpl->get('id'), 'competencyid' => $c1->get('id'), 'sortorder' => 1)); $tplc2 = $lpg->create_template_competency(array('templateid' => $tpl->get('id'), 'competencyid' => $c2->get('id'), 'sortorder' => 2)); $p1 = $lpg->create_plan(array('templateid' => $tpl->get('id'), 'userid' => $u1->id)); // Check pre-test. $this->assertTrue(\core_competency\template::record_exists($tpl->get('id'))); $this->assertEquals(2, \core_competency\template_competency::count_competencies($tpl->get('id'))); $this->assertEquals(1, count(\core_competency\plan::get_records(array('templateid' => $tpl->get('id'))))); $result = api::delete_template($tpl->get('id'), true); $this->assertTrue($result); // Check that the template does not exist anymore. $this->assertFalse(\core_competency\template::record_exists($tpl->get('id'))); // Check that associated competencies are also deleted. $this->assertEquals(0, \core_competency\template_competency::count_competencies($tpl->get('id'))); // Check that associated plan are also deleted. $this->assertEquals(0, count(\core_competency\plan::get_records(array('templateid' => $tpl->get('id'))))); } public function test_delete_template_unlink_plans(): void { $this->resetAfterTest(true); $this->setAdminUser(); $dg = $this->getDataGenerator(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $u1 = $dg->create_user(); $f = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $f->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $f->get('id'))); $tpl = $lpg->create_template(); $tplc1 = $lpg->create_template_competency(array('templateid' => $tpl->get('id'), 'competencyid' => $c1->get('id'), 'sortorder' => 1)); $tplc2 = $lpg->create_template_competency(array('templateid' => $tpl->get('id'), 'competencyid' => $c2->get('id'), 'sortorder' => 2)); $p1 = $lpg->create_plan(array('templateid' => $tpl->get('id'), 'userid' => $u1->id)); // Check pre-test. $this->assertTrue(\core_competency\template::record_exists($tpl->get('id'))); $this->assertEquals(2, \core_competency\template_competency::count_competencies($tpl->get('id'))); $this->assertEquals(1, count(\core_competency\plan::get_records(array('templateid' => $tpl->get('id'))))); $result = api::delete_template($tpl->get('id'), false); $this->assertTrue($result); // Check that the template does not exist anymore. $this->assertFalse(\core_competency\template::record_exists($tpl->get('id'))); // Check that associated competencies are also deleted. $this->assertEquals(0, \core_competency\template_competency::count_competencies($tpl->get('id'))); // Check that associated plan still exist but unlink from template. $plans = \core_competency\plan::get_records(array('id' => $p1->get('id'))); $this->assertEquals(1, count($plans)); $this->assertEquals($plans[0]->get('origtemplateid'), $tpl->get('id')); $this->assertNull($plans[0]->get('templateid')); } public function test_delete_competency(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $this->setAdminUser(); $u1 = $dg->create_user(); $f1 = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c1a = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1->get('id'))); $c1b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1a->get('id'))); $c11b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1b->get('id'))); $c12b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1b->get('id'))); // Set rules on parent competency. $c1->set('ruleoutcome', competency::OUTCOME_EVIDENCE); $c1->set('ruletype', 'core_competency\\competency_rule_all'); $c1->update(); // If we delete competeny, the related competencies relations and evidences should be deleted. // Create related competencies using one of c1a competency descendants. $rc = $lpg->create_related_competency(array( 'competencyid' => $c2->get('id'), 'relatedcompetencyid' => $c11b->get('id') )); $this->assertEquals($c11b->get('id'), $rc->get('relatedcompetencyid')); // Creating a standard evidence with minimal information. $uc2 = $lpg->create_user_competency(array('userid' => $u1->id, 'competencyid' => $c11b->get('id'))); $evidence = $lpg->create_evidence(array('usercompetencyid' => $uc2->get('id'))); $this->assertEquals($uc2->get('id'), $evidence->get('usercompetencyid')); $uc2->delete(); $this->assertTrue(api::delete_competency($c1a->get('id'))); $this->assertFalse(competency::record_exists($c1a->get('id'))); // Check that on delete, we reset the rule on parent competency. $c1->read(); $this->assertNull($c1->get('ruletype')); $this->assertNull($c1->get('ruletype')); $this->assertEquals(competency::OUTCOME_NONE, $c1->get('ruleoutcome')); // Check that descendants were also deleted. $this->assertFalse(competency::record_exists($c1b->get('id'))); $this->assertFalse(competency::record_exists($c11b->get('id'))); $this->assertFalse(competency::record_exists($c12b->get('id'))); // Check if evidence are also deleted. $this->assertEquals(0, \core_competency\user_evidence_competency::count_records(array('competencyid' => $c11b->get('id')))); // Check if related conpetency relation is deleted. $this->assertEquals(0, count(api::list_related_competencies($c2->get('id')))); // Delete a simple competency. $this->assertTrue(api::delete_competency($c2->get('id'))); $this->assertFalse(competency::record_exists($c2->get('id'))); } public function test_delete_competency_used_in_plan(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $this->setAdminUser(); $u1 = $dg->create_user(); $plan = $lpg->create_plan((object) array('userid' => $u1->id)); $f1 = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c1a = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1->get('id'))); $c1b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1a->get('id'))); $c11b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1b->get('id'))); $c12b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1b->get('id'))); // Add competency to plan. $pc = $lpg->create_plan_competency(array('planid' => $plan->get('id'), 'competencyid' => $c11b->get('id'))); // We can not delete a competency , if competency or competency children is associated to plan. $this->assertFalse(api::delete_competency($c1a->get('id'))); // We can delete the competency if we remove the competency from the plan. $pc->delete(); $this->assertTrue(api::delete_competency($c1a->get('id'))); $this->assertFalse(competency::record_exists($c1a->get('id'))); $this->assertFalse(competency::record_exists($c1b->get('id'))); $this->assertFalse(competency::record_exists($c11b->get('id'))); $this->assertFalse(competency::record_exists($c12b->get('id'))); } public function test_delete_competency_used_in_usercompetency(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $this->setAdminUser(); $u1 = $dg->create_user(); $f1 = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c1a = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1->get('id'))); $c1b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1a->get('id'))); $c11b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1b->get('id'))); $c12b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1b->get('id'))); // Create user competency. $uc1 = $lpg->create_user_competency(array('userid' => $u1->id, 'competencyid' => $c11b->get('id'))); // We can not delete a competency , if competency or competency children exist in user competency. $this->assertFalse(api::delete_competency($c1a->get('id'))); // We can delete the competency if we remove the competency from user competency. $uc1->delete(); $this->assertTrue(api::delete_competency($c1a->get('id'))); $this->assertFalse(competency::record_exists($c1a->get('id'))); $this->assertFalse(competency::record_exists($c1b->get('id'))); $this->assertFalse(competency::record_exists($c11b->get('id'))); $this->assertFalse(competency::record_exists($c12b->get('id'))); } public function test_delete_competency_used_in_usercompetencyplan(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $this->setAdminUser(); $u1 = $dg->create_user(); $plan = $lpg->create_plan((object) array('userid' => $u1->id)); $f1 = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c1a = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1->get('id'))); $c1b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1a->get('id'))); $c11b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1b->get('id'))); $c12b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1b->get('id'))); // Create user competency plan. $uc2 = $lpg->create_user_competency_plan(array( 'userid' => $u1->id, 'competencyid' => $c11b->get('id'), 'planid' => $plan->get('id') )); // We can not delete a competency , if competency or competency children exist in user competency plan. $this->assertFalse(api::delete_competency($c1a->get('id'))); // We can delete the competency if we remove the competency from user competency plan. $uc2->delete(); $this->assertTrue(api::delete_competency($c1a->get('id'))); $this->assertFalse(competency::record_exists($c1a->get('id'))); $this->assertFalse(competency::record_exists($c1b->get('id'))); $this->assertFalse(competency::record_exists($c11b->get('id'))); $this->assertFalse(competency::record_exists($c12b->get('id'))); } public function test_delete_competency_used_in_template(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $this->setAdminUser(); $template = $lpg->create_template(); $f1 = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c1a = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1->get('id'))); $c1b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1a->get('id'))); $c11b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1b->get('id'))); $c12b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1b->get('id'))); // Add competency to a template. $tc = $lpg->create_template_competency(array( 'templateid' => $template->get('id'), 'competencyid' => $c11b->get('id') )); // We can not delete a competency , if competency or competency children is linked to template. $this->assertFalse(api::delete_competency($c1a->get('id'))); // We can delete the competency if we remove the competency from template. $tc->delete(); $this->assertTrue(api::delete_competency($c1a->get('id'))); $this->assertFalse(competency::record_exists($c1a->get('id'))); $this->assertFalse(competency::record_exists($c1b->get('id'))); $this->assertFalse(competency::record_exists($c11b->get('id'))); $this->assertFalse(competency::record_exists($c12b->get('id'))); } public function test_delete_competency_used_in_course(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $this->setAdminUser(); $cat1 = $dg->create_category(); $course = $dg->create_course(array('category' => $cat1->id)); $f1 = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c1a = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1->get('id'))); $c1b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1a->get('id'))); $c11b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1b->get('id'))); $c12b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1b->get('id'))); // Add competency to course. $cc = $lpg->create_course_competency(array( 'courseid' => $course->id, 'competencyid' => $c11b->get('id') )); // We can not delete a competency if the competency or competencies children is linked to a course. $this->assertFalse(api::delete_competency($c1a->get('id'))); // We can delete the competency if we remove the competency from course. $cc->delete(); $this->assertTrue(api::delete_competency($c1a->get('id'))); $this->assertFalse(competency::record_exists($c1a->get('id'))); $this->assertFalse(competency::record_exists($c1b->get('id'))); $this->assertFalse(competency::record_exists($c11b->get('id'))); $this->assertFalse(competency::record_exists($c12b->get('id'))); } public function test_delete_framework(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $this->setAdminUser(); $u1 = $dg->create_user(); $f1 = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c2id = $c2->get('id'); $c1a = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1->get('id'))); $c1b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1a->get('id'))); $c11b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1b->get('id'))); $c12b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1b->get('id'))); // If we delete framework, the related competencies relations and evidences should be deleted. // Create related competencies using one of c1a competency descendants. $rc = $lpg->create_related_competency(array( 'competencyid' => $c2->get('id'), 'relatedcompetencyid' => $c11b->get('id') )); $this->assertEquals($c11b->get('id'), $rc->get('relatedcompetencyid')); // Creating a standard evidence with minimal information. $uc2 = $lpg->create_user_competency(array('userid' => $u1->id, 'competencyid' => $c11b->get('id'))); $evidence = $lpg->create_evidence(array('usercompetencyid' => $uc2->get('id'))); $this->assertEquals($uc2->get('id'), $evidence->get('usercompetencyid')); $uc2->delete(); $this->assertTrue(api::delete_framework($f1->get('id'))); $this->assertFalse(competency_framework::record_exists($f1->get('id'))); // Check that all competencies were also deleted. $this->assertFalse(competency::record_exists($c1->get('id'))); $this->assertFalse(competency::record_exists($c2->get('id'))); $this->assertFalse(competency::record_exists($c1a->get('id'))); $this->assertFalse(competency::record_exists($c1b->get('id'))); $this->assertFalse(competency::record_exists($c11b->get('id'))); $this->assertFalse(competency::record_exists($c12b->get('id'))); // Check if evidence are also deleted. $this->assertEquals(0, \core_competency\user_evidence_competency::count_records(array('competencyid' => $c11b->get('id')))); // Check if related conpetency relation is deleted. $this->assertEquals(0, count(\core_competency\related_competency::get_multiple_relations(array($c2id)))); // Delete a simple framework. $f2 = $lpg->create_framework(); $this->assertTrue(api::delete_framework($f2->get('id'))); $this->assertFalse(competency_framework::record_exists($f2->get('id'))); } public function test_delete_framework_competency_used_in_plan(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $this->setAdminUser(); $u1 = $dg->create_user(); $plan = $lpg->create_plan((object) array('userid' => $u1->id)); $f1 = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c2id = $c2->get('id'); $c1a = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1->get('id'))); $c1b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1a->get('id'))); $c11b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1b->get('id'))); $c12b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1b->get('id'))); // Create related competencies. $rc = $lpg->create_related_competency(array( 'competencyid' => $c2->get('id'), 'relatedcompetencyid' => $c11b->get('id') )); $this->assertEquals($c11b->get('id'), $rc->get('relatedcompetencyid')); // Creating a standard evidence with minimal information. $uc2 = $lpg->create_user_competency(array('userid' => $u1->id, 'competencyid' => $c11b->get('id'))); $usercompetencyid = $uc2->get('id'); $evidence = $lpg->create_evidence(array('usercompetencyid' => $usercompetencyid)); $this->assertEquals($uc2->get('id'), $evidence->get('usercompetencyid')); $uc2->delete(); // Add competency to plan. $pc = $lpg->create_plan_competency(array('planid' => $plan->get('id'), 'competencyid' => $c11b->get('id'))); // We can not delete a framework , if competency or competency children is associated to plan. $this->assertFalse(api::delete_framework($f1->get('id'))); // Check that none of associated data are deleted. $this->assertEquals($usercompetencyid, $evidence->read()->get('usercompetencyid')); $this->assertEquals($c2->get('id'), $rc->read()->get('competencyid')); // We can delete the competency if we remove the competency from the plan. $pc->delete(); $this->assertTrue(api::delete_framework($f1->get('id'))); $this->assertFalse(competency::record_exists($c1->get('id'))); $this->assertFalse(competency::record_exists($c2->get('id'))); $this->assertFalse(competency::record_exists($c1a->get('id'))); $this->assertFalse(competency::record_exists($c1b->get('id'))); $this->assertFalse(competency::record_exists($c11b->get('id'))); $this->assertFalse(competency::record_exists($c12b->get('id'))); // Check if evidence are also deleted. $this->assertEquals(0, \core_competency\user_evidence_competency::count_records(array('competencyid' => $c11b->get('id')))); // Check if related conpetency relation is deleted. $this->assertEquals(0, count(\core_competency\related_competency::get_multiple_relations(array($c2id)))); } public function test_delete_framework_competency_used_in_usercompetency(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $this->setAdminUser(); $u1 = $dg->create_user(); $f1 = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c2id = $c2->get('id'); $c1a = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1->get('id'))); $c1b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1a->get('id'))); $c11b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1b->get('id'))); $c12b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1b->get('id'))); // Create related competencies. $rc = $lpg->create_related_competency(array( 'competencyid' => $c2->get('id'), 'relatedcompetencyid' => $c11b->get('id') )); $this->assertEquals($c11b->get('id'), $rc->get('relatedcompetencyid')); // Creating a standard evidence with minimal information. $uc1 = $lpg->create_user_competency(array('userid' => $u1->id, 'competencyid' => $c11b->get('id'))); $usercompetencyid = $uc1->get('id'); $evidence = $lpg->create_evidence(array('usercompetencyid' => $usercompetencyid)); $this->assertEquals($uc1->get('id'), $evidence->get('usercompetencyid')); $uc1->delete(); // Create user competency. $uc2 = $lpg->create_user_competency(array('userid' => $u1->id, 'competencyid' => $c11b->get('id'))); // We can not delete a framework , if competency or competency children exist in user competency. $this->assertFalse(api::delete_framework($f1->get('id'))); // Check that none of associated data are deleted. $this->assertEquals($usercompetencyid, $evidence->read()->get('usercompetencyid')); $this->assertEquals($c2->get('id'), $rc->read()->get('competencyid')); // We can delete the framework if we remove the competency from user competency. $uc2->delete(); $this->assertTrue(api::delete_framework($f1->get('id'))); $this->assertFalse(competency::record_exists($c1->get('id'))); $this->assertFalse(competency::record_exists($c2->get('id'))); $this->assertFalse(competency::record_exists($c1a->get('id'))); $this->assertFalse(competency::record_exists($c1b->get('id'))); $this->assertFalse(competency::record_exists($c11b->get('id'))); $this->assertFalse(competency::record_exists($c12b->get('id'))); // Check if evidence are also deleted. $this->assertEquals(0, \core_competency\user_evidence_competency::count_records(array('competencyid' => $c11b->get('id')))); // Check if related conpetency relation is deleted. $this->assertEquals(0, count(\core_competency\related_competency::get_multiple_relations(array($c2id)))); } public function test_delete_framework_competency_used_in_usercompetencyplan(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $this->setAdminUser(); $u1 = $dg->create_user(); $plan = $lpg->create_plan((object) array('userid' => $u1->id)); $f1 = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c2id = $c2->get('id'); $c1a = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1->get('id'))); $c1b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1a->get('id'))); $c11b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1b->get('id'))); $c12b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1b->get('id'))); // Create related competencies. $rc = $lpg->create_related_competency(array( 'competencyid' => $c2->get('id'), 'relatedcompetencyid' => $c11b->get('id') )); $this->assertEquals($c11b->get('id'), $rc->get('relatedcompetencyid')); // Creating a standard evidence with minimal information. $uc1 = $lpg->create_user_competency(array('userid' => $u1->id, 'competencyid' => $c11b->get('id'))); $usercompetencyid = $uc1->get('id'); $evidence = $lpg->create_evidence(array('usercompetencyid' => $usercompetencyid)); $this->assertEquals($uc1->get('id'), $evidence->get('usercompetencyid')); $uc1->delete(); // Create user competency plan. $uc2 = $lpg->create_user_competency_plan(array( 'userid' => $u1->id, 'competencyid' => $c11b->get('id'), 'planid' => $plan->get('id') )); // We can not delete a framework , if competency or competency children exist in user competency plan. $this->assertFalse(api::delete_framework($f1->get('id'))); // Check that none of associated data are deleted. $this->assertEquals($usercompetencyid, $evidence->read()->get('usercompetencyid')); $this->assertEquals($c2->get('id'), $rc->read()->get('competencyid')); // We can delete the framework if we remove the competency from user competency plan. $uc2->delete(); $this->assertTrue(api::delete_framework($f1->get('id'))); $this->assertFalse(competency::record_exists($c1->get('id'))); $this->assertFalse(competency::record_exists($c2->get('id'))); $this->assertFalse(competency::record_exists($c1a->get('id'))); $this->assertFalse(competency::record_exists($c1b->get('id'))); $this->assertFalse(competency::record_exists($c11b->get('id'))); $this->assertFalse(competency::record_exists($c12b->get('id'))); // Check if evidence are also deleted. $this->assertEquals(0, \core_competency\user_evidence_competency::count_records(array('competencyid' => $c11b->get('id')))); // Check if related conpetency relation is deleted. $this->assertEquals(0, count(\core_competency\related_competency::get_multiple_relations(array($c2id)))); } public function test_delete_framework_competency_used_in_template(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $this->setAdminUser(); $u1 = $dg->create_user(); $template = $lpg->create_template(); $f1 = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c2id = $c2->get('id'); $c1a = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1->get('id'))); $c1b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1a->get('id'))); $c11b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1b->get('id'))); $c12b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1b->get('id'))); // Create related competencies. $rc = $lpg->create_related_competency(array( 'competencyid' => $c2->get('id'), 'relatedcompetencyid' => $c11b->get('id') )); $this->assertEquals($c11b->get('id'), $rc->get('relatedcompetencyid')); // Creating a standard evidence with minimal information. $uc1 = $lpg->create_user_competency(array('userid' => $u1->id, 'competencyid' => $c11b->get('id'))); $usercompetencyid = $uc1->get('id'); $evidence = $lpg->create_evidence(array('usercompetencyid' => $usercompetencyid)); $this->assertEquals($uc1->get('id'), $evidence->get('usercompetencyid')); $uc1->delete(); // Add competency to a template. $tc = $lpg->create_template_competency(array( 'templateid' => $template->get('id'), 'competencyid' => $c11b->get('id') )); // We can not delete a framework , if competency or competency children is linked to template. $this->assertFalse(api::delete_framework($f1->get('id'))); // Check that none of associated data are deleted. $this->assertEquals($usercompetencyid, $evidence->read()->get('usercompetencyid')); $this->assertEquals($c2->get('id'), $rc->read()->get('competencyid')); // We can delete the framework if we remove the competency from template. $tc->delete(); $this->assertTrue(api::delete_framework($f1->get('id'))); $this->assertFalse(competency::record_exists($c1->get('id'))); $this->assertFalse(competency::record_exists($c2->get('id'))); $this->assertFalse(competency::record_exists($c1a->get('id'))); $this->assertFalse(competency::record_exists($c1b->get('id'))); $this->assertFalse(competency::record_exists($c11b->get('id'))); $this->assertFalse(competency::record_exists($c12b->get('id'))); // Check if evidence are also deleted. $this->assertEquals(0, \core_competency\user_evidence_competency::count_records(array('competencyid' => $c11b->get('id')))); // Check if related conpetency relation is deleted. $this->assertEquals(0, count(\core_competency\related_competency::get_multiple_relations(array($c2id)))); } public function test_delete_framework_competency_used_in_course(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $this->setAdminUser(); $cat1 = $dg->create_category(); $u1 = $dg->create_user(); $course = $dg->create_course(array('category' => $cat1->id)); $f1 = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c2id = $c2->get('id'); $c1a = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1->get('id'))); $c1b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1a->get('id'))); $c11b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1b->get('id'))); $c12b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1b->get('id'))); // Create related competencies. $rc = $lpg->create_related_competency(array( 'competencyid' => $c2->get('id'), 'relatedcompetencyid' => $c11b->get('id') )); $this->assertEquals($c11b->get('id'), $rc->get('relatedcompetencyid')); // Creating a standard evidence with minimal information. $uc1 = $lpg->create_user_competency(array('userid' => $u1->id, 'competencyid' => $c11b->get('id'))); $usercompetencyid = $uc1->get('id'); $evidence = $lpg->create_evidence(array('usercompetencyid' => $usercompetencyid)); $this->assertEquals($uc1->get('id'), $evidence->get('usercompetencyid')); $uc1->delete(); // Add competency to course. $cc = $lpg->create_course_competency(array( 'courseid' => $course->id, 'competencyid' => $c11b->get('id') )); // We can not delete a framework if the competency or competencies children is linked to a course. $this->assertFalse(api::delete_framework($f1->get('id'))); // Check that none of associated data are deleted. $this->assertEquals($usercompetencyid, $evidence->read()->get('usercompetencyid')); $this->assertEquals($c2->get('id'), $rc->read()->get('competencyid')); // We can delete the framework if we remove the competency from course. $cc->delete(); $this->assertTrue(api::delete_framework($f1->get('id'))); $this->assertFalse(competency::record_exists($c1->get('id'))); $this->assertFalse(competency::record_exists($c2->get('id'))); $this->assertFalse(competency::record_exists($c1a->get('id'))); $this->assertFalse(competency::record_exists($c1b->get('id'))); $this->assertFalse(competency::record_exists($c11b->get('id'))); $this->assertFalse(competency::record_exists($c12b->get('id'))); // Check if evidence are also deleted. $this->assertEquals(0, \core_competency\user_evidence_competency::count_records(array('competencyid' => $c11b->get('id')))); // Check if related conpetency relation is deleted. $this->assertEquals(0, count(\core_competency\related_competency::get_multiple_relations(array($c2id)))); } public function test_grade_competency_in_course_permissions(): void { $this->resetAfterTest(); $dg = $this->getDataGenerator(); $c1 = $dg->create_course(); $c2 = $dg->create_course(); $sysctx = \context_system::instance(); $c1ctx = \context_course::instance($c1->id); $c2ctx = \context_course::instance($c2->id); $teacher1 = $dg->create_user(); $noneditingteacher = $dg->create_user(); $student1 = $dg->create_user(); $student2 = $dg->create_user(); $notstudent1 = $dg->create_user(); $lpg = $dg->get_plugin_generator('core_competency'); $framework = $lpg->create_framework(); $comp1 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $comp2 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $lpg->create_course_competency(array('courseid' => $c1->id, 'competencyid' => $comp1->get('id'))); $studentarch = get_archetype_roles('student'); $studentrole = array_shift($studentarch); $gradablerole = $dg->create_role(); assign_capability('moodle/competency:coursecompetencygradable', CAP_ALLOW, $gradablerole, $sysctx->id); $notgradablerole = $dg->create_role(); assign_capability('moodle/competency:coursecompetencygradable', CAP_PROHIBIT, $notgradablerole, $sysctx->id); $canviewucrole = $dg->create_role(); assign_capability('moodle/competency:usercompetencyview', CAP_ALLOW, $canviewucrole, $sysctx->id); $cannotviewcomp = $dg->create_role(); assign_capability('moodle/competency:competencyview', CAP_PROHIBIT, $cannotviewcomp, $sysctx->id); $canmanagecomp = $dg->create_role(); assign_capability('moodle/competency:competencymanage', CAP_ALLOW, $canmanagecomp, $sysctx->id); $cangraderole = $dg->create_role(); assign_capability('moodle/competency:competencygrade', CAP_ALLOW, $cangraderole, $sysctx->id); // Enrol s1 and s2 as students in course 1. $dg->enrol_user($student1->id, $c1->id, $studentrole->id); $dg->enrol_user($student2->id, $c1->id, $studentrole->id); // Mark the s2 as not being 'gradable'. $dg->role_assign($notgradablerole, $student2->id, $c1ctx->id); // Mark the 'non a student' as 'gradable' throughout the site. $dg->role_assign($gradablerole, $notstudent1->id, $sysctx->id); // From now we'll iterate over each permission. accesslib_clear_all_caches_for_unit_testing(); $this->setUser($teacher1); $this->assertExceptionWithGradeCompetencyInCourse('required_capability_exception', 'View a user competency', $c1->id, $student1->id, $comp1->get('id')); // Give permission to view competencies. $dg->role_assign($canviewucrole, $teacher1->id, $c1ctx->id); accesslib_clear_all_caches_for_unit_testing(); $this->assertExceptionWithGradeCompetencyInCourse('required_capability_exception', 'Set competency rating', $c1->id, $student1->id, $comp1->get('id')); // Give permission to rate. $dg->role_assign($cangraderole, $teacher1->id, $c1ctx->id); accesslib_clear_all_caches_for_unit_testing(); $this->assertSuccessWithGradeCompetencyInCourse($c1->id, $student1->id, $comp1->get('id')); // Remove permssion to read competencies, this leads to error. $dg->role_assign($cannotviewcomp, $teacher1->id, $sysctx->id); accesslib_clear_all_caches_for_unit_testing(); $this->assertExceptionWithGradeCompetencyInCourse('required_capability_exception', 'View competency frameworks', $c1->id, $student1->id, $comp1->get('id')); // Give permssion to manage course competencies, this leads to success. $dg->role_assign($canmanagecomp, $teacher1->id, $sysctx->id); accesslib_clear_all_caches_for_unit_testing(); $this->assertSuccessWithGradeCompetencyInCourse($c1->id, $student1->id, $comp1->get('id')); // Try to grade a user that is not gradable, lead to errors. $this->assertExceptionWithGradeCompetencyInCourse('coding_exception', 'The competency may not be rated at this time.', $c1->id, $student2->id, $comp1->get('id')); // Try to grade a competency not in the course. $this->assertExceptionWithGradeCompetencyInCourse('coding_exception', 'The competency does not belong to this course', $c1->id, $student1->id, $comp2->get('id')); // Try to grade a user that is not enrolled, even though they are 'gradable'. $this->assertExceptionWithGradeCompetencyInCourse('coding_exception', 'The competency may not be rated at this time.', $c1->id, $notstudent1->id, $comp1->get('id')); // Give permission for non-editing teacher to grade. $dg->role_assign($canviewucrole, $noneditingteacher->id, $c1ctx->id); $dg->role_assign($cangraderole, $noneditingteacher->id, $c1ctx->id); $this->setUser($noneditingteacher); accesslib_clear_all_caches_for_unit_testing(); $this->assertSuccessWithGradeCompetencyInCourse($c1->id, $student1->id, $comp1->get('id')); } /** * Assert that a competency was graded in a course. * * @param int $courseid The course ID. * @param int $userid The user ID. * @param int $compid The competency ID. * @param int $grade The grade. */ protected function assertSuccessWithGradeCompetencyInCourse($courseid, $userid, $compid, $grade = 1) { $beforecount = evidence::count_records(); api::grade_competency_in_course($courseid, $userid, $compid, $grade); $this->assertEquals($beforecount + 1, evidence::count_records()); $uc = user_competency::get_record(array('userid' => $userid, 'competencyid' => $compid)); $records = evidence::get_records(array(), 'id', 'DESC', 0, 1); $evidence = array_pop($records); $this->assertEquals($uc->get('id'), $evidence->get('usercompetencyid')); } /** * Assert that grading a competency in course throws an exception. * * @param string $exceptiontype The exception type. * @param string $exceptiontest The exceptiont text. * @param int $courseid The course ID. * @param int $userid The user ID. * @param int $compid The competency ID. * @param int $grade The grade. */ protected function assertExceptionWithGradeCompetencyInCourse($exceptiontype, $exceptiontext, $courseid, $userid, $compid, $grade = 1) { $raised = false; try { api::grade_competency_in_course($courseid, $userid, $compid, $grade); } catch (\moodle_exception $e) { $raised = true; $this->assertInstanceOf($exceptiontype, $e); $this->assertMatchesRegularExpression('@' . $exceptiontext . '@', $e->getMessage()); } if (!$raised) { $this->fail('Grading should not be allowed.'); } } /** * Test list of evidences for plan completed and not completed. */ public function test_list_evidence(): void { global $DB; $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $syscontext = \context_system::instance(); // Create users. $user = $dg->create_user(); $this->setUser($user); // Create a framework and assign competencies. $framework = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); // Create 2 user plans and add competency to each plan. $p1 = $lpg->create_plan(array('userid' => $user->id)); $p2 = $lpg->create_plan(array('userid' => $user->id)); $pc1 = $lpg->create_plan_competency(array('planid' => $p1->get('id'), 'competencyid' => $c1->get('id'))); $pc2 = $lpg->create_plan_competency(array('planid' => $p2->get('id'), 'competencyid' => $c1->get('id'))); // Create user competency and add an evidence. $uc = $lpg->create_user_competency(array('userid' => $user->id, 'competencyid' => $c1->get('id'))); $e1 = $lpg->create_evidence(array('usercompetencyid' => $uc->get('id'))); // Check both plans as one evidence. $this->assertEquals(1, count(api::list_evidence($user->id, $c1->get('id'), $p1->get('id')))); $this->assertEquals(1, count(api::list_evidence($user->id, $c1->get('id'), $p2->get('id')))); // Complete second plan. $p2->set('status', plan::STATUS_COMPLETE); $p2->update(); // Add another evidence for the same competency, but in the future (time + 1). $e2 = $lpg->create_evidence(array('usercompetencyid' => $uc->get('id'))); $evidencesql = "UPDATE {" . evidence::TABLE . "} SET timecreated = :currenttime WHERE id = :evidenceid"; $DB->execute($evidencesql, array('currenttime' => time() + 1, 'evidenceid' => $e2->get('id'))); // Check that the first plan, which is not completed, has all the evidence. $this->assertEquals(2, count(api::list_evidence($user->id, $c1->get('id'), $p1->get('id')))); // Check that the second plan, completed before the new evidence, only has the first piece of evidence. $listevidences = api::list_evidence($user->id, $c1->get('id'), $p2->get('id')); $this->assertEquals(1, count($listevidences)); $this->assertEquals($e1->get('id'), $listevidences[$e1->get('id')]->get('id')); } /** * Get a user competency in a course. */ public function test_get_user_competency_in_course(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $this->setAdminUser(); $user = $dg->create_user(); $c1 = $dg->create_course(); // Enrol the user so they can be rated in the course. $studentarch = get_archetype_roles('student'); $studentrole = array_shift($studentarch); $coursecontext = \context_course::instance($c1->id); $dg->role_assign($studentrole->id, $user->id, $coursecontext->id); $dg->enrol_user($user->id, $c1->id, $studentrole->id); $framework = $lpg->create_framework(); $comp1 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $comp2 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $lpg->create_course_competency(array('competencyid' => $comp1->get('id'), 'courseid' => $c1->id)); $lpg->create_course_competency(array('competencyid' => $comp2->get('id'), 'courseid' => $c1->id)); // Create a user competency for comp1. api::grade_competency_in_course($c1, $user->id, $comp1->get('id'), 3, 'Unit test'); // Test for competency already exist in user_competency. $uc = api::get_user_competency_in_course($c1->id, $user->id, $comp1->get('id')); $this->assertEquals($comp1->get('id'), $uc->get('competencyid')); $this->assertEquals($user->id, $uc->get('userid')); $this->assertEquals(3, $uc->get('grade')); $this->assertEquals(true, $uc->get('proficiency')); // Test for competency does not exist in user_competency. $uc2 = api::get_user_competency_in_course($c1->id, $user->id, $comp2->get('id')); $this->assertEquals($comp2->get('id'), $uc2->get('competencyid')); $this->assertEquals($user->id, $uc2->get('userid')); $this->assertEquals(null, $uc2->get('grade')); $this->assertEquals(null, $uc2->get('proficiency')); } /** * Test course statistics api functions. */ public function test_course_statistics(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $this->setAdminUser(); $u1 = $dg->create_user(); $u2 = $dg->create_user(); $u3 = $dg->create_user(); $u4 = $dg->create_user(); $c1 = $dg->create_course(); $framework = $lpg->create_framework(); // Enrol students in the course. $studentarch = get_archetype_roles('student'); $studentrole = array_shift($studentarch); $coursecontext = \context_course::instance($c1->id); $dg->role_assign($studentrole->id, $u1->id, $coursecontext->id); $dg->enrol_user($u1->id, $c1->id, $studentrole->id); $dg->role_assign($studentrole->id, $u2->id, $coursecontext->id); $dg->enrol_user($u2->id, $c1->id, $studentrole->id); $dg->role_assign($studentrole->id, $u3->id, $coursecontext->id); $dg->enrol_user($u3->id, $c1->id, $studentrole->id); $dg->role_assign($studentrole->id, $u4->id, $coursecontext->id); $dg->enrol_user($u4->id, $c1->id, $studentrole->id); // Create 6 competencies. $comp1 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $comp2 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $comp3 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $comp4 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $comp5 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $comp6 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); // Link 6 out of 6 to a course. $lpg->create_course_competency(array('competencyid' => $comp1->get('id'), 'courseid' => $c1->id)); $lpg->create_course_competency(array('competencyid' => $comp2->get('id'), 'courseid' => $c1->id)); $lpg->create_course_competency(array('competencyid' => $comp3->get('id'), 'courseid' => $c1->id)); $lpg->create_course_competency(array('competencyid' => $comp4->get('id'), 'courseid' => $c1->id)); $lpg->create_course_competency(array('competencyid' => $comp5->get('id'), 'courseid' => $c1->id)); $lpg->create_course_competency(array('competencyid' => $comp6->get('id'), 'courseid' => $c1->id)); // Rate some competencies. // User 1. api::grade_competency_in_course($c1, $u1->id, $comp1->get('id'), 4, 'Unit test'); api::grade_competency_in_course($c1, $u1->id, $comp2->get('id'), 4, 'Unit test'); api::grade_competency_in_course($c1, $u1->id, $comp3->get('id'), 4, 'Unit test'); api::grade_competency_in_course($c1, $u1->id, $comp4->get('id'), 4, 'Unit test'); // User 2. api::grade_competency_in_course($c1, $u2->id, $comp1->get('id'), 1, 'Unit test'); api::grade_competency_in_course($c1, $u2->id, $comp2->get('id'), 1, 'Unit test'); api::grade_competency_in_course($c1, $u2->id, $comp3->get('id'), 1, 'Unit test'); api::grade_competency_in_course($c1, $u2->id, $comp4->get('id'), 1, 'Unit test'); // User 3. api::grade_competency_in_course($c1, $u3->id, $comp1->get('id'), 3, 'Unit test'); api::grade_competency_in_course($c1, $u3->id, $comp2->get('id'), 3, 'Unit test'); // User 4. api::grade_competency_in_course($c1, $u4->id, $comp1->get('id'), 2, 'Unit test'); api::grade_competency_in_course($c1, $u4->id, $comp2->get('id'), 2, 'Unit test'); // OK we have enough data - lets call some API functions and check for expected results. $result = api::count_proficient_competencies_in_course_for_user($c1->id, $u1->id); $this->assertEquals(4, $result); $result = api::count_proficient_competencies_in_course_for_user($c1->id, $u2->id); $this->assertEquals(0, $result); $result = api::count_proficient_competencies_in_course_for_user($c1->id, $u3->id); $this->assertEquals(2, $result); $result = api::count_proficient_competencies_in_course_for_user($c1->id, $u4->id); $this->assertEquals(0, $result); $result = api::get_least_proficient_competencies_for_course($c1->id, 0, 2); // We should get 5 and 6 in repeatable order. $valid = false; if (($comp5->get('id') == $result[0]->get('id')) || ($comp6->get('id') == $result[0]->get('id'))) { $valid = true; } $this->assertTrue($valid); $valid = false; if (($comp5->get('id') == $result[1]->get('id')) || ($comp6->get('id') == $result[1]->get('id'))) { $valid = true; } $this->assertTrue($valid); $expected = $result[1]->get('id'); $result = api::get_least_proficient_competencies_for_course($c1->id, 1, 1); $this->assertEquals($result[0]->get('id'), $expected); } /** * Test template statistics api functions. */ public function test_template_statistics(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $this->setAdminUser(); $u1 = $dg->create_user(); $u2 = $dg->create_user(); $u3 = $dg->create_user(); $u4 = $dg->create_user(); $c1 = $dg->create_course(); $framework = $lpg->create_framework(); // Create 6 competencies. $comp1 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $comp2 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $comp3 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $comp4 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $comp5 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $comp6 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); // Link 5 out of 6 to a course. $lpg->create_course_competency(array('competencyid' => $comp1->get('id'), 'courseid' => $c1->id)); $lpg->create_course_competency(array('competencyid' => $comp2->get('id'), 'courseid' => $c1->id)); $lpg->create_course_competency(array('competencyid' => $comp3->get('id'), 'courseid' => $c1->id)); $lpg->create_course_competency(array('competencyid' => $comp4->get('id'), 'courseid' => $c1->id)); $lpg->create_course_competency(array('competencyid' => $comp5->get('id'), 'courseid' => $c1->id)); // Put all 6 in a template. $tpl = $this->getDataGenerator()->get_plugin_generator('core_competency')->create_template(); $tplc1 = $lpg->create_template_competency(array('templateid' => $tpl->get('id'), 'competencyid' => $comp1->get('id'))); $tplc2 = $lpg->create_template_competency(array('templateid' => $tpl->get('id'), 'competencyid' => $comp2->get('id'))); $tplc3 = $lpg->create_template_competency(array('templateid' => $tpl->get('id'), 'competencyid' => $comp3->get('id'))); $tplc4 = $lpg->create_template_competency(array('templateid' => $tpl->get('id'), 'competencyid' => $comp4->get('id'))); $tplc5 = $lpg->create_template_competency(array('templateid' => $tpl->get('id'), 'competencyid' => $comp5->get('id'))); $tplc6 = $lpg->create_template_competency(array('templateid' => $tpl->get('id'), 'competencyid' => $comp6->get('id'))); // Create some plans from the template. $p1 = $lpg->create_plan(array('templateid' => $tpl->get('id'), 'userid' => $u1->id)); $p2 = $lpg->create_plan(array('templateid' => $tpl->get('id'), 'userid' => $u2->id)); $p3 = $lpg->create_plan(array('templateid' => $tpl->get('id'), 'userid' => $u3->id)); $p4 = $lpg->create_plan(array('templateid' => $tpl->get('id'), 'userid' => $u4->id)); // Rate some competencies. // User 1. $lpg->create_user_competency(array('userid' => $u1->id, 'competencyid' => $comp1->get('id'), 'proficiency' => true, 'grade' => 1 )); $lpg->create_user_competency(array('userid' => $u1->id, 'competencyid' => $comp2->get('id'), 'proficiency' => true, 'grade' => 1 )); $lpg->create_user_competency(array('userid' => $u1->id, 'competencyid' => $comp3->get('id'), 'proficiency' => true, 'grade' => 1 )); // User 2. $lpg->create_user_competency(array('userid' => $u2->id, 'competencyid' => $comp1->get('id'), 'proficiency' => false, 'grade' => 1 )); $lpg->create_user_competency(array('userid' => $u2->id, 'competencyid' => $comp2->get('id'), 'proficiency' => false, 'grade' => 1 )); $lpg->create_user_competency(array('userid' => $u2->id, 'competencyid' => $comp3->get('id'), 'proficiency' => false, 'grade' => 1 )); // User 3. $lpg->create_user_competency(array('userid' => $u3->id, 'competencyid' => $comp2->get('id'), 'proficiency' => false, 'grade' => 1 )); $lpg->create_user_competency(array('userid' => $u3->id, 'competencyid' => $comp3->get('id'), 'proficiency' => true, 'grade' => 1 )); $lpg->create_user_competency(array('userid' => $u3->id, 'competencyid' => $comp4->get('id'), 'proficiency' => false, 'grade' => 1 )); $lpg->create_user_competency(array('userid' => $u3->id, 'competencyid' => $comp5->get('id'), 'proficiency' => true, 'grade' => 1 )); // User 4. $lpg->create_user_competency(array('userid' => $u4->id, 'competencyid' => $comp3->get('id'), 'proficiency' => true, 'grade' => 1 )); $lpg->create_user_competency(array('userid' => $u4->id, 'competencyid' => $comp5->get('id'), 'proficiency' => true, 'grade' => 1 )); // Complete 3 out of 4 plans. api::complete_plan($p1->get('id')); api::complete_plan($p2->get('id')); api::complete_plan($p3->get('id')); // OK we have enough data - lets call some API functions and check for expected results. $result = api::count_competencies_in_template_with_no_courses($tpl->get('id')); $this->assertEquals(1, $result); $result = api::count_plans_for_template($tpl->get('id')); $this->assertEquals(4, $result); $result = api::count_plans_for_template($tpl->get('id'), plan::STATUS_COMPLETE); $this->assertEquals(3, $result); // This counts the records of competencies in completed plans for all users with a plan from this template. $result = api::count_user_competency_plans_for_template($tpl->get('id')); // There should be 3 plans * 6 competencies. $this->assertEquals(18, $result); // This counts the records of proficient competencies in completed plans for all users with a plan from this template. $result = api::count_user_competency_plans_for_template($tpl->get('id'), true); // There should be 5. $this->assertEquals(5, $result); // This counts the records of not proficient competencies in completed plans for all users with a plan from this template. $result = api::count_user_competency_plans_for_template($tpl->get('id'), false); // There should be 13. $this->assertEquals(13, $result); // This lists the plans based on this template, optionally filtered by status. $result = api::list_plans_for_template($tpl->get('id')); $this->assertEquals(4, count($result)); foreach ($result as $one) { $this->assertInstanceOf('\core_competency\plan', $one); } // This lists the plans based on this template, optionally filtered by status. $result = api::list_plans_for_template($tpl->get('id'), plan::STATUS_COMPLETE); $this->assertEquals(3, count($result)); foreach ($result as $one) { $this->assertInstanceOf('\core_competency\plan', $one); $this->assertEquals(plan::STATUS_COMPLETE, $one->get('status')); } $result = api::get_least_proficient_competencies_for_template($tpl->get('id'), 0, 2); // Our times completed counts should look like this: // - comp1 - 1 // - comp2 - 1 // - comp3 - 2 // - comp4 - 0 // - comp5 - 1 // - comp6 - 0 // // And this is a fullstop to make CiBoT happy. $this->assertEquals(2, count($result)); $leastarray = array($comp4->get('id'), $comp6->get('id')); foreach ($result as $one) { $this->assertInstanceOf('\core_competency\competency', $one); $this->assertContainsEquals($one->get('id'), $leastarray); } } public function test_is_scale_used_anywhere(): void { $this->resetAfterTest(); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $scale1 = $dg->create_scale(); $scale2 = $dg->create_scale(); $scale3 = $dg->create_scale(); $scale4 = $dg->create_scale(); $this->assertFalse(api::is_scale_used_anywhere($scale1->id)); $this->assertFalse(api::is_scale_used_anywhere($scale2->id)); $this->assertFalse(api::is_scale_used_anywhere($scale3->id)); $this->assertFalse(api::is_scale_used_anywhere($scale4->id)); // Using scale 1 in a framework. $f1 = $lpg->create_framework([ 'scaleid' => $scale1->id, 'scaleconfiguration' => json_encode([ ['scaleid' => $scale1->id], ['id' => 1, 'scaledefault' => 1, 'proficient' => 1] ]) ]); $this->assertTrue(api::is_scale_used_anywhere($scale1->id)); $this->assertFalse(api::is_scale_used_anywhere($scale2->id)); $this->assertFalse(api::is_scale_used_anywhere($scale3->id)); $this->assertFalse(api::is_scale_used_anywhere($scale4->id)); // Using scale 2 in a competency. $f2 = $lpg->create_framework(); $c2 = $lpg->create_competency([ 'competencyframeworkid' => $f2->get('id'), 'scaleid' => $scale2->id, 'scaleconfiguration' => json_encode([ ['scaleid' => $scale2->id], ['id' => 1, 'scaledefault' => 1, 'proficient' => 1] ]) ]); $this->assertTrue(api::is_scale_used_anywhere($scale1->id)); $this->assertTrue(api::is_scale_used_anywhere($scale2->id)); $this->assertFalse(api::is_scale_used_anywhere($scale3->id)); $this->assertFalse(api::is_scale_used_anywhere($scale4->id)); // Using scale 3 in a framework, and scale 4 in a competency of that framework. $f3 = $lpg->create_framework([ 'scaleid' => $scale3->id, 'scaleconfiguration' => json_encode([ ['scaleid' => $scale3->id], ['id' => 1, 'scaledefault' => 1, 'proficient' => 1] ]) ]); $c3 = $lpg->create_competency([ 'competencyframeworkid' => $f3->get('id'), 'scaleid' => $scale4->id, 'scaleconfiguration' => json_encode([ ['scaleid' => $scale4->id], ['id' => 1, 'scaledefault' => 1, 'proficient' => 1] ]) ]); $this->assertTrue(api::is_scale_used_anywhere($scale1->id)); $this->assertTrue(api::is_scale_used_anywhere($scale2->id)); $this->assertTrue(api::is_scale_used_anywhere($scale3->id)); $this->assertTrue(api::is_scale_used_anywhere($scale4->id)); // Multiple occurrences of the same scale (3, and 4). $f4 = $lpg->create_framework([ 'scaleid' => $scale3->id, 'scaleconfiguration' => json_encode([ ['scaleid' => $scale3->id], ['id' => 1, 'scaledefault' => 1, 'proficient' => 1] ]) ]); $c4 = $lpg->create_competency([ 'competencyframeworkid' => $f3->get('id'), 'scaleid' => $scale4->id, 'scaleconfiguration' => json_encode([ ['scaleid' => $scale4->id], ['id' => 1, 'scaledefault' => 1, 'proficient' => 1] ]) ]); $this->assertTrue(api::is_scale_used_anywhere($scale1->id)); $this->assertTrue(api::is_scale_used_anywhere($scale2->id)); $this->assertTrue(api::is_scale_used_anywhere($scale3->id)); $this->assertTrue(api::is_scale_used_anywhere($scale4->id)); } public function test_delete_evidence(): void { $this->resetAfterTest(); $dg = $this->getDataGenerator(); $ccg = $dg->get_plugin_generator('core_competency'); $u1 = $dg->create_user(); $f1 = $ccg->create_framework(); $comp1 = $ccg->create_competency(['competencyframeworkid' => $f1->get('id')]); $uc1 = $ccg->create_user_competency(['userid' => $u1->id, 'competencyid' => $comp1->get('id')]); $ev1 = $ccg->create_evidence(['usercompetencyid' => $uc1->get('id')]); $ev2 = $ccg->create_evidence(['usercompetencyid' => $uc1->get('id')]); $this->setAdminUser($u1); $this->assertEquals(2, evidence::count_records()); api::delete_evidence($ev1); $this->assertEquals(1, evidence::count_records()); $this->assertFalse(evidence::record_exists($ev1->get('id'))); $this->assertTrue(evidence::record_exists($ev2->get('id'))); } public function test_delete_evidence_without_permissions(): void { $this->resetAfterTest(); $dg = $this->getDataGenerator(); $ccg = $dg->get_plugin_generator('core_competency'); $u1 = $dg->create_user(); $f1 = $ccg->create_framework(); $comp1 = $ccg->create_competency(['competencyframeworkid' => $f1->get('id')]); $uc1 = $ccg->create_user_competency(['userid' => $u1->id, 'competencyid' => $comp1->get('id')]); $ev1 = $ccg->create_evidence(['usercompetencyid' => $uc1->get('id')]); $this->setUser($u1); $this->expectException(\required_capability_exception::class); api::delete_evidence($ev1); } public function test_list_plans_to_review(): void { $dg = $this->getDataGenerator(); $this->resetAfterTest(); $ccg = $dg->get_plugin_generator('core_competency'); $sysctx = \context_system::instance(); $this->setAdminUser(); $reviewer = $dg->create_user(); $roleallow = $dg->create_role(); $roleprohibit = $dg->create_role(); assign_capability('moodle/competency:planreview', CAP_ALLOW, $roleallow, $sysctx->id); assign_capability('moodle/competency:planreview', CAP_PROHIBIT, $roleprohibit, $sysctx->id); role_assign($roleallow, $reviewer->id, $sysctx->id); accesslib_clear_all_caches_for_unit_testing(); $u1 = $dg->create_user(); $u2 = $dg->create_user(); $f1 = $ccg->create_framework(); $comp1 = $ccg->create_competency(['competencyframeworkid' => $f1->get('id')]); $p1a = $ccg->create_plan(['userid' => $u1->id, 'status' => plan::STATUS_WAITING_FOR_REVIEW]); $p1b = $ccg->create_plan(['userid' => $u1->id, 'status' => plan::STATUS_IN_REVIEW, 'reviewerid' => $reviewer->id]); $p1c = $ccg->create_plan(['userid' => $u1->id, 'status' => plan::STATUS_DRAFT]); $p2a = $ccg->create_plan(['userid' => $u2->id, 'status' => plan::STATUS_WAITING_FOR_REVIEW]); $p2b = $ccg->create_plan(['userid' => $u2->id, 'status' => plan::STATUS_IN_REVIEW]); $p2c = $ccg->create_plan(['userid' => $u2->id, 'status' => plan::STATUS_ACTIVE]); $p2d = $ccg->create_plan(['userid' => $u2->id, 'status' => plan::STATUS_ACTIVE]); api::complete_plan($p2d); // The reviewer can review all plans waiting for review, or in review where they are the reviewer. $this->setUser($reviewer); $result = api::list_plans_to_review(); $this->assertEquals(3, $result['count']); $this->assertEquals($p1a->get('id'), $result['plans'][0]->plan->get('id')); $this->assertEquals($p1b->get('id'), $result['plans'][1]->plan->get('id')); $this->assertEquals($p2a->get('id'), $result['plans'][2]->plan->get('id')); // The reviewer cannot view the plans when they do not have the permission in the user's context. role_assign($roleprohibit, $reviewer->id, \context_user::instance($u2->id)->id); accesslib_clear_all_caches_for_unit_testing(); $result = api::list_plans_to_review(); $this->assertEquals(2, $result['count']); $this->assertEquals($p1a->get('id'), $result['plans'][0]->plan->get('id')); $this->assertEquals($p1b->get('id'), $result['plans'][1]->plan->get('id')); } public function test_list_user_competencies_to_review(): void { global $CFG; require_once($CFG->dirroot . '/user/lib.php'); $dg = $this->getDataGenerator(); $this->resetAfterTest(); $ccg = $dg->get_plugin_generator('core_competency'); $sysctx = \context_system::instance(); $this->setAdminUser(); $reviewer = $dg->create_user(); $roleallow = $dg->create_role(); $roleprohibit = $dg->create_role(); assign_capability('moodle/competency:usercompetencyreview', CAP_ALLOW, $roleallow, $sysctx->id); assign_capability('moodle/competency:usercompetencyreview', CAP_PROHIBIT, $roleprohibit, $sysctx->id); role_assign($roleallow, $reviewer->id, $sysctx->id); accesslib_clear_all_caches_for_unit_testing(); $u1 = $dg->create_user(); $u2 = $dg->create_user(); $u3 = $dg->create_user(); $f1 = $ccg->create_framework(); $c1 = $ccg->create_competency(['competencyframeworkid' => $f1->get('id')]); $c2 = $ccg->create_competency(['competencyframeworkid' => $f1->get('id')]); $c3 = $ccg->create_competency(['competencyframeworkid' => $f1->get('id')]); $c4 = $ccg->create_competency(['competencyframeworkid' => $f1->get('id')]); $uc1a = $ccg->create_user_competency(['userid' => $u1->id, 'competencyid' => $c1->get('id'), 'status' => user_competency::STATUS_IDLE]); $uc1b = $ccg->create_user_competency(['userid' => $u1->id, 'competencyid' => $c2->get('id'), 'status' => user_competency::STATUS_WAITING_FOR_REVIEW]); $uc1c = $ccg->create_user_competency(['userid' => $u1->id, 'competencyid' => $c3->get('id'), 'status' => user_competency::STATUS_IN_REVIEW, 'reviewerid' => $reviewer->id]); $uc2a = $ccg->create_user_competency(['userid' => $u2->id, 'competencyid' => $c1->get('id'), 'status' => user_competency::STATUS_WAITING_FOR_REVIEW]); $uc2b = $ccg->create_user_competency(['userid' => $u2->id, 'competencyid' => $c2->get('id'), 'status' => user_competency::STATUS_IDLE]); $uc2c = $ccg->create_user_competency(['userid' => $u2->id, 'competencyid' => $c3->get('id'), 'status' => user_competency::STATUS_IN_REVIEW]); $uc3a = $ccg->create_user_competency(['userid' => $u3->id, 'competencyid' => $c4->get('id'), 'status' => user_competency::STATUS_WAITING_FOR_REVIEW]); // The reviewer can review all plans waiting for review, or in review where they are the reviewer. $this->setUser($reviewer); $result = api::list_user_competencies_to_review(); $this->assertEquals(4, $result['count']); $this->assertEquals($uc2a->get('id'), $result['competencies'][0]->usercompetency->get('id')); $this->assertEquals($uc1b->get('id'), $result['competencies'][1]->usercompetency->get('id')); $this->assertEquals($uc1c->get('id'), $result['competencies'][2]->usercompetency->get('id')); $this->assertEquals($uc3a->get('id'), $result['competencies'][3]->usercompetency->get('id')); // Now, let's delete user 3. // It should not be listed on user competencies to review any more. user_delete_user($u3); $result = api::list_user_competencies_to_review(); $this->assertEquals(3, $result['count']); // The reviewer cannot view the plans when they do not have the permission in the user's context. role_assign($roleprohibit, $reviewer->id, \context_user::instance($u2->id)->id); accesslib_clear_all_caches_for_unit_testing(); $result = api::list_user_competencies_to_review(); $this->assertEquals(2, $result['count']); $this->assertEquals($uc1b->get('id'), $result['competencies'][0]->usercompetency->get('id')); $this->assertEquals($uc1c->get('id'), $result['competencies'][1]->usercompetency->get('id')); } /** * Test we can get all of a users plans with a competency. */ public function test_list_plans_with_competency(): void { $this->resetAfterTest(true); $this->setAdminUser(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $u1 = $this->getDataGenerator()->create_user(); $tpl = $this->getDataGenerator()->get_plugin_generator('core_competency')->create_template(); // Create a framework and assign competencies. $framework = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); // Create two plans and assign the competency to each. $plan1 = $lpg->create_plan(array('userid' => $u1->id)); $plan2 = $lpg->create_plan(array('userid' => $u1->id)); $lpg->create_plan_competency(array('planid' => $plan1->get('id'), 'competencyid' => $c1->get('id'))); $lpg->create_plan_competency(array('planid' => $plan2->get('id'), 'competencyid' => $c1->get('id'))); // Create one more plan without the competency. $plan3 = $lpg->create_plan(array('userid' => $u1->id)); $plans = api::list_plans_with_competency($u1->id, $c1); $this->assertEquals(2, count($plans)); $this->assertEquals(reset($plans)->get('id'), $plan1->get('id')); $this->assertEquals(end($plans)->get('id'), $plan2->get('id')); } } home/smkb8269/public_html/lms.smk-almanshuriyah.sch.id/lib/xapi/tests/api_test.php000064400000014106152267621140024063 0ustar00. namespace core_xapi; use core_xapi\local\statement\item_activity; use advanced_testcase; /** * Contains test cases for testing xAPI API base methods. * * @package core_xapi * @since Moodle 4.2 * @covers \core_xapi\api * @copyright 2023 Sara Arjona (sara@moodle.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class api_test extends advanced_testcase { /** * Setup to ensure that fixtures are loaded. */ public static function setUpBeforeClass(): void { global $CFG; require_once($CFG->dirroot.'/lib/xapi/tests/helper.php'); parent::setUpBeforeClass(); } /** * Testing remove_states_from_component method. * * @return void */ public function test_remove_states_from_component(): void { global $DB; $this->resetAfterTest(); // Scenario. $this->setAdminUser(); // Add a few xAPI state records to database. test_helper::create_state(['activity' => item_activity::create_from_id('1')], true); test_helper::create_state(['activity' => item_activity::create_from_id('2')], true); test_helper::create_state(['activity' => item_activity::create_from_id('3')], true); test_helper::create_state(['activity' => item_activity::create_from_id('4')], true); test_helper::create_state(['activity' => item_activity::create_from_id('5'), 'component' => 'mod_h5pactivity'], true); test_helper::create_state(['activity' => item_activity::create_from_id('6'), 'component' => 'mod_h5pactivity'], true); test_helper::create_state(['activity' => item_activity::create_from_id('7'), 'component' => 'mod_h5pactivity'], true); test_helper::create_state(['activity' => item_activity::create_from_id('8'), 'component' => 'unexisting'], true); test_helper::create_state(['activity' => item_activity::create_from_id('9'), 'component' => 'unexisting'], true); // Check no state has been removed (because there are no entries for the another_component). api::remove_states_from_component('another_component'); $this->assertEquals(9, $DB->count_records('xapi_states')); // Check states for the fake_component have been removed. api::remove_states_from_component('fake_component'); $this->assertEquals(5, $DB->count_records('xapi_states')); $this->assertEquals(0, $DB->count_records('xapi_states', ['component' => 'fake_component'])); $this->assertEquals(3, $DB->count_records('xapi_states', ['component' => 'mod_h5pactivity'])); $this->assertEquals(2, $DB->count_records('xapi_states', ['component' => 'unexisting'])); // Check states for the mod_h5pactivity have been removed too. api::remove_states_from_component('mod_h5pactivity'); $this->assertEquals(2, $DB->count_records('xapi_states')); $this->assertEquals(0, $DB->count_records('xapi_states', ['component' => 'mod_h5pactivity'])); // Check states for the unexisting component have been removed (using the default state_store). api::remove_states_from_component('unexisting'); $this->assertEquals(0, $DB->count_records('xapi_states')); } /** * Testing execute_state_cleanup method. * * @return void */ public function test_execute_state_cleanup(): void { global $DB; $this->resetAfterTest(); // Scenario. $this->setAdminUser(); // Add a few xAPI state records to database. test_helper::create_state(['activity' => item_activity::create_from_id('1')], true); test_helper::create_state(['activity' => item_activity::create_from_id('2')], true); test_helper::create_state(['activity' => item_activity::create_from_id('3')], true); test_helper::create_state(['activity' => item_activity::create_from_id('4')], true); test_helper::create_state(['activity' => item_activity::create_from_id('5'), 'component' => 'mod_h5pactivity'], true); test_helper::create_state(['activity' => item_activity::create_from_id('6'), 'component' => 'mod_h5pactivity'], true); test_helper::create_state(['activity' => item_activity::create_from_id('7'), 'component' => 'mod_h5pactivity'], true); // Perform test. api::execute_state_cleanup(); // Check no state has been removed yet (because the entries are not old enough). $this->assertEquals(7, $DB->count_records('xapi_states')); // Make the existing state entries older. $timepast = time() - 2; $DB->set_field('xapi_states', 'timecreated', $timepast); $DB->set_field('xapi_states', 'timemodified', $timepast); // Create 1 more state, that shouldn't be removed after the cleanup. test_helper::create_state(['activity' => item_activity::create_from_id('8'), 'component' => 'mod_h5pactivity'], true); // Set the config to remove states older than 1 second. set_config('xapicleanupperiod', 1); // Check old states have been removed. api::execute_state_cleanup(); $this->assertEquals(5, $DB->count_records('xapi_states')); $this->assertEquals(4, $DB->count_records('xapi_states', ['component' => 'fake_component'])); $this->assertEquals(1, $DB->count_records('xapi_states', ['component' => 'mod_h5pactivity'])); $this->assertEquals(0, $DB->count_records('xapi_states', ['component' => 'my_component'])); } } home/smkb8269/public_html/lms.smk-almanshuriyah.sch.id/h5p/tests/api_test.php000064400000135005152271177450023057 0ustar00. /** * Testing the H5P API. * * @package core_h5p * @category test * @copyright 2020 Sara Arjona * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ declare(strict_types = 1); namespace core_h5p; use stdClass; defined('MOODLE_INTERNAL') || die(); /** * Test class covering the H5P API. * * @package core_h5p * @copyright 2020 Sara Arjona * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @coversDefaultClass \core_h5p\api */ final class api_test extends \advanced_testcase { /** * Test the behaviour of delete_library(). * * @dataProvider delete_library_provider * @param string $libraryname Machine name of the library to delete. * @param int $expectedh5p Total of H5P contents expected after deleting the library. * @param int $expectedlibraries Total of H5P libraries expected after deleting the library. * @param int $expectedcontents Total of H5P content_libraries expected after deleting the library. * @param int $expecteddependencies Total of H5P library dependencies expected after deleting the library. */ public function test_delete_library(string $libraryname, int $expectedh5p, int $expectedlibraries, int $expectedcontents, int $expecteddependencies): void { global $DB; $this->setRunTestInSeparateProcess(true); $this->resetAfterTest(); // Generate h5p related data. $generator = $this->getDataGenerator()->get_plugin_generator('core_h5p'); $generator->generate_h5p_data(); $generator->create_library_record('H5P.TestingLibrary', 'TestingLibrary', 1, 0); // Check the current content in H5P tables is the expected. $counth5p = $DB->count_records('h5p'); $counth5plibraries = $DB->count_records('h5p_libraries'); $counth5pcontents = $DB->count_records('h5p_contents_libraries'); $counth5pdependencies = $DB->count_records('h5p_library_dependencies'); $this->assertSame(1, $counth5p); $this->assertSame(7, $counth5plibraries); $this->assertSame(5, $counth5pcontents); $this->assertSame(7, $counth5pdependencies); // Delete this library. $factory = new factory(); $library = $DB->get_record('h5p_libraries', ['machinename' => $libraryname]); if ($library) { api::delete_library($factory, $library); } // Check the expected libraries and content have been removed. $counth5p = $DB->count_records('h5p'); $counth5plibraries = $DB->count_records('h5p_libraries'); $counth5pcontents = $DB->count_records('h5p_contents_libraries'); $counth5pdependencies = $DB->count_records('h5p_library_dependencies'); $this->assertSame($expectedh5p, $counth5p); $this->assertSame($expectedlibraries, $counth5plibraries); $this->assertSame($expectedcontents, $counth5pcontents); $this->assertSame($expecteddependencies, $counth5pdependencies); } /** * Data provider for test_delete_library(). * * @return array */ public static function delete_library_provider(): array { return [ 'Delete MainLibrary' => [ 'MainLibrary', 0, 6, 0, 4, ], 'Delete Library1' => [ 'Library1', 0, 5, 0, 1, ], 'Delete Library2' => [ 'Library2', 0, 4, 0, 1, ], 'Delete Library3' => [ 'Library3', 0, 4, 0, 0, ], 'Delete Library4' => [ 'Library4', 0, 4, 0, 1, ], 'Delete Library5' => [ 'Library5', 0, 3, 0, 0, ], 'Delete a library without dependencies' => [ 'H5P.TestingLibrary', 1, 6, 5, 7, ], 'Delete unexisting library' => [ 'LibraryX', 1, 7, 5, 7, ], ]; } /** * Test the behaviour of get_dependent_libraries(). * * @dataProvider get_dependent_libraries_provider * @param string $libraryname Machine name of the library to delete. * @param int $expectedvalue Total of H5P required libraries expected. */ public function test_get_dependent_libraries(string $libraryname, int $expectedvalue): void { global $DB; $this->resetAfterTest(); // Generate h5p related data. $generator = $this->getDataGenerator()->get_plugin_generator('core_h5p'); $generator->generate_h5p_data(); $generator->create_library_record('H5P.TestingLibrary', 'TestingLibrary', 1, 0); // Get required libraries. $library = $DB->get_record('h5p_libraries', ['machinename' => $libraryname], 'id'); if ($library) { $libraries = api::get_dependent_libraries((int)$library->id); } else { $libraries = []; } $this->assertCount($expectedvalue, $libraries); } /** * Data provider for test_get_dependent_libraries(). * * @return array */ public static function get_dependent_libraries_provider(): array { return [ 'Main library of a content' => [ 'MainLibrary', 0, ], 'Library1' => [ 'Library1', 1, ], 'Library2' => [ 'Library2', 2, ], 'Library without dependencies' => [ 'H5P.TestingLibrary', 0, ], 'Unexisting library' => [ 'LibraryX', 0, ], ]; } /** * Test the behaviour of get_library(). * * @dataProvider get_library_provider * @param string $libraryname Machine name of the library to delete. * @param bool $emptyexpected Wether the expected result is empty or not. */ public function test_get_library(string $libraryname, bool $emptyexpected): void { global $DB; $this->resetAfterTest(); // Generate h5p related data. $generator = $this->getDataGenerator()->get_plugin_generator('core_h5p'); $generator->generate_h5p_data(); $generator->create_library_record('H5P.TestingLibrary', 'TestingLibrary', 1, 0); // Get the library identifier. $library = $DB->get_record('h5p_libraries', ['machinename' => $libraryname], 'id'); if ($library) { $result = api::get_library((int)$library->id); } else { $result = null; } if ($emptyexpected) { $this->assertEmpty($result); } else { $this->assertEquals($library->id, $result->id); $this->assertEquals($libraryname, $result->machinename); } } /** * Data provider for test_get_library(). * * @return array */ public static function get_library_provider(): array { return [ 'Main library of a content' => [ 'MainLibrary', false, ], 'Library1' => [ 'Library1', false, ], 'Library without dependencies' => [ 'H5P.TestingLibrary', false, ], 'Unexisting library' => [ 'LibraryX', true, ], ]; } /** * Test the behaviour of get_content_from_pluginfile_url(). */ public function test_get_content_from_pluginfile_url(): void { $this->setRunTestInSeparateProcess(true); $this->resetAfterTest(); $factory = new factory(); // Create the H5P data. $filename = 'find-the-words.h5p'; $path = self::get_fixture_path(__NAMESPACE__, $filename); $fakefile = helper::create_fake_stored_file_from_path($path); $config = (object)[ 'frame' => 1, 'export' => 1, 'embed' => 0, 'copyright' => 0, ]; // Get URL for this H5P content file. $syscontext = \context_system::instance(); $url = \moodle_url::make_pluginfile_url( $syscontext->id, \core_h5p\file_storage::COMPONENT, 'unittest', $fakefile->get_itemid(), '/', $filename ); // Scenario 1: Get the H5P for this URL and check there isn't any existing H5P (because it hasn't been saved). list($newfile, $h5p) = api::get_content_from_pluginfile_url($url->out()); $this->assertEquals($fakefile->get_pathnamehash(), $newfile->get_pathnamehash()); $this->assertEquals($fakefile->get_contenthash(), $newfile->get_contenthash()); $this->assertFalse($h5p); // Scenario 2: Save the H5P and check now the H5P is exactly the same as the original one. $h5pid = helper::save_h5p($factory, $fakefile, $config); list($newfile, $h5p) = api::get_content_from_pluginfile_url($url->out()); $this->assertEquals($h5pid, $h5p->id); $this->assertEquals($fakefile->get_pathnamehash(), $h5p->pathnamehash); $this->assertEquals($fakefile->get_contenthash(), $h5p->contenthash); // Scenario 3: Get the H5P for an unexisting H5P file. $url = \moodle_url::make_pluginfile_url( $syscontext->id, \core_h5p\file_storage::COMPONENT, 'unittest', $fakefile->get_itemid(), '/', 'unexisting.h5p' ); list($newfile, $h5p) = api::get_content_from_pluginfile_url($url->out()); $this->assertFalse($newfile); $this->assertFalse($h5p); } /** * Test the behaviour of get_original_content_from_pluginfile_url(). * * @covers ::get_original_content_from_pluginfile_url */ public function test_get_original_content_from_pluginfile_url(): void { $this->setRunTestInSeparateProcess(true); $this->resetAfterTest(); $this->setAdminUser(); $factory = new factory(); $syscontext = \context_system::instance(); // Create the original file. $filename = 'greeting-card.h5p'; $path = self::get_fixture_path(__NAMESPACE__, $filename); $originalfile = helper::create_fake_stored_file_from_path($path); $originalfilerecord = [ 'contextid' => $originalfile->get_contextid(), 'component' => $originalfile->get_component(), 'filearea' => $originalfile->get_filearea(), 'itemid' => $originalfile->get_itemid(), 'filepath' => $originalfile->get_filepath(), 'filename' => $originalfile->get_filename(), ]; $config = (object)[ 'frame' => 1, 'export' => 1, 'embed' => 0, 'copyright' => 0, ]; $originalurl = \moodle_url::make_pluginfile_url( $originalfile->get_contextid(), $originalfile->get_component(), $originalfile->get_filearea(), $originalfile->get_itemid(), $originalfile->get_filepath(), $originalfile->get_filename() ); // Create a reference to the original file. $reffilerecord = [ 'contextid' => $syscontext->id, 'component' => 'core', 'filearea' => 'phpunit', 'itemid' => 0, 'filepath' => '/', 'filename' => $filename ]; $fs = get_file_storage(); $ref = $fs->pack_reference($originalfilerecord); $repos = \repository::get_instances(['type' => 'user']); $userrepository = reset($repos); $referencedfile = $fs->create_file_from_reference($reffilerecord, $userrepository->id, $ref); $this->assertEquals($referencedfile->get_contenthash(), $originalfile->get_contenthash()); $referencedurl = \moodle_url::make_pluginfile_url( $syscontext->id, 'core', 'phpunit', 0, '/', $filename ); // Scenario 1: Original file (without any reference). $originalh5pid = helper::save_h5p($factory, $originalfile, $config); list($source, $h5p, $file) = api::get_original_content_from_pluginfile_url($originalurl->out()); $this->assertEquals($originalfile->get_pathnamehash(), $source->get_pathnamehash()); $this->assertEquals($originalfile->get_contenthash(), $source->get_contenthash()); $this->assertEquals($originalh5pid, $h5p->id); $this->assertFalse($file); // Scenario 2: Referenced file (alias to originalfile). list($source, $h5p, $file) = api::get_original_content_from_pluginfile_url($referencedurl->out()); $this->assertEquals($originalfile->get_pathnamehash(), $source->get_pathnamehash()); $this->assertEquals($originalfile->get_contenthash(), $source->get_contenthash()); $this->assertEquals($originalfile->get_contenthash(), $source->get_contenthash()); $this->assertEquals($originalh5pid, $h5p->id); $this->assertEquals($referencedfile->get_pathnamehash(), $file->get_pathnamehash()); $this->assertEquals($referencedfile->get_contenthash(), $file->get_contenthash()); $this->assertEquals($referencedfile->get_contenthash(), $file->get_contenthash()); // Scenario 3: Unexisting file. $unexistingurl = \moodle_url::make_pluginfile_url( $syscontext->id, 'core', 'phpunit', 0, '/', 'unexisting.h5p' ); list($source, $h5p, $file) = api::get_original_content_from_pluginfile_url($unexistingurl->out()); $this->assertFalse($source); $this->assertFalse($h5p); $this->assertFalse($file); } /** * Test the behaviour of can_edit_content(). * * @covers ::can_edit_content * @dataProvider can_edit_content_provider * * @param string $currentuser User who will call the method. * @param string $fileauthor Author of the file to check. * @param string $filecomponent Component of the file to check. * @param bool $expected Expected result after calling the can_edit_content method. * @param string $filearea Area of the file to check. * * @return void */ public function test_can_edit_content(string $currentuser, string $fileauthor, string $filecomponent, bool $expected, $filearea = 'unittest'): void { global $USER, $DB; $this->setRunTestInSeparateProcess(true); $this->resetAfterTest(); // Create course. $course = $this->getDataGenerator()->create_course(); $context = \context_course::instance($course->id); // Create some users. $this->setAdminUser(); $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'); $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); $users = [ 'admin' => $USER, 'teacher' => $teacher, 'student' => $student, ]; // Set current user. if ($currentuser !== 'admin') { $this->setUser($users[$currentuser]); } $itemid = rand(); if ($filearea === 'post') { // Create a forum and add a discussion. $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]); $record = new stdClass(); $record->course = $course->id; $record->userid = $users[$fileauthor]->id; $record->forum = $forum->id; $discussion = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record); $post = $DB->get_record('forum_posts', ['discussion' => $discussion->id]); $itemid = $post->id; } // Create the file. $filename = 'greeting-card.h5p'; $path = self::get_fixture_path(__NAMESPACE__, $filename); if ($filecomponent === 'contentbank') { $generator = $this->getDataGenerator()->get_plugin_generator('core_contentbank'); $contents = $generator->generate_contentbank_data( 'contenttype_h5p', 1, (int)$users[$fileauthor]->id, $context, true, $path ); $content = array_shift($contents); $file = $content->get_file(); } else { $filerecord = [ 'contextid' => $context->id, 'component' => $filecomponent, 'filearea' => $filearea, 'itemid' => $itemid, 'filepath' => '/', 'filename' => basename($path), 'userid' => $users[$fileauthor]->id, ]; $fs = get_file_storage(); $file = $fs->create_file_from_pathname($filerecord, $path); } // Check if the currentuser can edit the file. $result = api::can_edit_content($file); $this->assertEquals($expected, $result); } /** * Data provider for test_can_edit_content(). * * @return array */ public static function can_edit_content_provider(): array { return [ // Component = user. 'user: Admin user is author' => [ 'currentuser' => 'admin', 'fileauthor' => 'admin', 'filecomponent' => 'user', 'expected' => true, ], 'user: Admin user, teacher is author' => [ 'currentuser' => 'admin', 'fileauthor' => 'teacher', 'filecomponent' => 'user', 'expected' => false, ], 'user: Teacher user, teacher is author' => [ 'currentuser' => 'teacher', 'fileauthor' => 'teacher', 'filecomponent' => 'user', 'expected' => true, ], 'user: Teacher user, admin is author' => [ 'currentuser' => 'teacher', 'fileauthor' => 'admin', 'filecomponent' => 'user', 'expected' => false, ], 'user: Student user, student is author' => [ 'currentuser' => 'student', 'fileauthor' => 'student', 'filecomponent' => 'user', 'expected' => true, ], 'user: Student user, teacher is author' => [ 'currentuser' => 'student', 'fileauthor' => 'teacher', 'filecomponent' => 'user', 'expected' => false, ], // Component = mod_h5pactivity. 'mod_h5pactivity: Admin user is author' => [ 'currentuser' => 'admin', 'fileauthor' => 'admin', 'filecomponent' => 'mod_h5pactivity', 'expected' => true, ], 'mod_h5pactivity: Admin user, teacher is author' => [ 'currentuser' => 'admin', 'fileauthor' => 'teacher', 'filecomponent' => 'mod_h5pactivity', 'expected' => true, ], 'mod_h5pactivity: Teacher user, teacher is author' => [ 'currentuser' => 'teacher', 'fileauthor' => 'teacher', 'filecomponent' => 'mod_h5pactivity', 'expected' => true, ], 'mod_h5pactivity: Teacher user, admin is author' => [ 'currentuser' => 'teacher', 'fileauthor' => 'admin', 'filecomponent' => 'mod_h5pactivity', 'expected' => true, ], 'mod_h5pactivity: Student user, student is author' => [ 'currentuser' => 'student', 'fileauthor' => 'student', 'filecomponent' => 'mod_h5pactivity', 'expected' => false, ], 'mod_h5pactivity: Student user, teacher is author' => [ 'currentuser' => 'student', 'fileauthor' => 'teacher', 'filecomponent' => 'mod_h5pactivity', 'expected' => false, ], // Component = mod_book. 'mod_book: Admin user is author' => [ 'currentuser' => 'admin', 'fileauthor' => 'admin', 'filecomponent' => 'mod_book', 'expected' => true, ], 'mod_book: Admin user, teacher is author' => [ 'currentuser' => 'admin', 'fileauthor' => 'teacher', 'filecomponent' => 'mod_book', 'expected' => true, ], // Component = mod_forum. 'mod_forum: Admin user is author' => [ 'currentuser' => 'admin', 'fileauthor' => 'admin', 'filecomponent' => 'mod_forum', 'expected' => true, ], 'mod_forum: Admin user, teacher is author' => [ 'currentuser' => 'admin', 'fileauthor' => 'teacher', 'filecomponent' => 'mod_forum', 'expected' => true, ], 'mod_forum: Teacher user, admin is author' => [ 'currentuser' => 'teacher', 'fileauthor' => 'admin', 'filecomponent' => 'mod_forum', 'expected' => true, ], 'mod_forum: Student user, teacher is author' => [ 'currentuser' => 'student', 'fileauthor' => 'teacher', 'filecomponent' => 'mod_forum', 'expected' => false, ], 'mod_forum/post: Admin user is author' => [ 'currentuser' => 'admin', 'fileauthor' => 'admin', 'filecomponent' => 'mod_forum', 'expected' => true, 'filearea' => 'post', ], 'mod_forum/post: Teacher user, admin is author' => [ 'currentuser' => 'teacher', 'fileauthor' => 'admin', 'filecomponent' => 'mod_forum', 'expected' => true, 'filearea' => 'post', ], 'mod_forum/post: Student user, teacher is author' => [ 'currentuser' => 'student', 'fileauthor' => 'teacher', 'filecomponent' => 'mod_forum', 'expected' => false, 'filearea' => 'post', ], // Component = block_html. 'block_html: Admin user is author' => [ 'currentuser' => 'admin', 'fileauthor' => 'admin', 'filecomponent' => 'block_html', 'expected' => true, ], 'block_html: Admin user, teacher is author' => [ 'currentuser' => 'admin', 'fileauthor' => 'teacher', 'filecomponent' => 'block_html', 'expected' => true, ], // Component = contentbank. 'contentbank: Admin user is author' => [ 'currentuser' => 'admin', 'fileauthor' => 'admin', 'filecomponent' => 'contentbank', 'expected' => true, ], 'contentbank: Admin user, teacher is author' => [ 'currentuser' => 'admin', 'fileauthor' => 'teacher', 'filecomponent' => 'contentbank', 'expected' => true, ], 'contentbank: Teacher user, teacher is author' => [ 'currentuser' => 'teacher', 'fileauthor' => 'teacher', 'filecomponent' => 'contentbank', 'expected' => true, ], 'contentbank: Teacher user, admin is author' => [ 'currentuser' => 'teacher', 'fileauthor' => 'admin', 'filecomponent' => 'contentbank', 'expected' => false, ], 'contentbank: Student user, student is author' => [ 'currentuser' => 'student', 'fileauthor' => 'student', 'filecomponent' => 'contentbank', 'expected' => false, ], 'contentbank: Student user, teacher is author' => [ 'currentuser' => 'student', 'fileauthor' => 'teacher', 'filecomponent' => 'contentbank', 'expected' => false, ], // Unexisting components. 'Unexisting component' => [ 'currentuser' => 'admin', 'fileauthor' => 'admin', 'filecomponent' => 'unexisting_component', 'expected' => false, ], 'Unexisting module activity' => [ 'currentuser' => 'admin', 'fileauthor' => 'admin', 'filecomponent' => 'mod_unexisting', 'expected' => false, ], 'Unexisting block' => [ 'currentuser' => 'admin', 'fileauthor' => 'admin', 'filecomponent' => 'block_unexisting', 'expected' => false, ], ]; } /** * Test the behaviour of create_content_from_pluginfile_url(). */ public function test_create_content_from_pluginfile_url(): void { global $DB; $this->setRunTestInSeparateProcess(true); $this->resetAfterTest(); $factory = new factory(); // Create the H5P data. $filename = 'find-the-words.h5p'; $path = self::get_fixture_path(__NAMESPACE__, $filename); $fakefile = helper::create_fake_stored_file_from_path($path); $config = (object)[ 'frame' => 1, 'export' => 1, 'embed' => 0, 'copyright' => 0, ]; // Get URL for this H5P content file. $syscontext = \context_system::instance(); $url = \moodle_url::make_pluginfile_url( $syscontext->id, \core_h5p\file_storage::COMPONENT, 'unittest', $fakefile->get_itemid(), '/', $filename ); // Scenario 1: Create the H5P from this URL and check the content is exactly the same as the fake file. $messages = new \stdClass(); list($newfile, $h5pid) = api::create_content_from_pluginfile_url($url->out(), $config, $factory, $messages); $this->assertNotFalse($h5pid); $h5p = $DB->get_record('h5p', ['id' => $h5pid]); $this->assertEquals($fakefile->get_pathnamehash(), $h5p->pathnamehash); $this->assertEquals($fakefile->get_contenthash(), $h5p->contenthash); $this->assertTrue(empty($messages->error)); $this->assertTrue(empty($messages->info)); // Scenario 2: Create the H5P for an unexisting H5P file. $url = \moodle_url::make_pluginfile_url( $syscontext->id, \core_h5p\file_storage::COMPONENT, 'unittest', $fakefile->get_itemid(), '/', 'unexisting.h5p' ); list($newfile, $h5p) = api::create_content_from_pluginfile_url($url->out(), $config, $factory, $messages); $this->assertFalse($newfile); $this->assertFalse($h5p); $this->assertTrue(empty($messages->error)); $this->assertTrue(empty($messages->info)); } /** * Test the behaviour of delete_content_from_pluginfile_url(). */ public function test_delete_content_from_pluginfile_url(): void { global $DB; $this->setRunTestInSeparateProcess(true); $this->resetAfterTest(); $factory = new factory(); // Create the H5P data. $filename = 'find-the-words.h5p'; $path = self::get_fixture_path(__NAMESPACE__, $filename); $fakefile = helper::create_fake_stored_file_from_path($path); $config = (object)[ 'frame' => 1, 'export' => 1, 'embed' => 0, 'copyright' => 0, ]; // Get URL for this H5P content file. $syscontext = \context_system::instance(); $url = \moodle_url::make_pluginfile_url( $syscontext->id, \core_h5p\file_storage::COMPONENT, 'unittest', $fakefile->get_itemid(), '/', $filename ); // Scenario 1: Try to remove the H5P content for an undeployed file. list($newfile, $h5p) = api::get_content_from_pluginfile_url($url->out()); $this->assertEquals(0, $DB->count_records('h5p')); api::delete_content_from_pluginfile_url($url->out(), $factory); $this->assertEquals(0, $DB->count_records('h5p')); // Scenario 2: Deploy an H5P from this URL, check it's created, remove it and check it has been removed as expected. $this->assertEquals(0, $DB->count_records('h5p')); $messages = new \stdClass(); list($newfile, $h5pid) = api::create_content_from_pluginfile_url($url->out(), $config, $factory, $messages); $this->assertEquals(1, $DB->count_records('h5p')); api::delete_content_from_pluginfile_url($url->out(), $factory); $this->assertEquals(0, $DB->count_records('h5p')); // Scenario 3: Try to remove the H5P for an unexisting H5P URL. $url = \moodle_url::make_pluginfile_url( $syscontext->id, \core_h5p\file_storage::COMPONENT, 'unittest', $fakefile->get_itemid(), '/', 'unexisting.h5p' ); $this->assertEquals(0, $DB->count_records('h5p')); api::delete_content_from_pluginfile_url($url->out(), $factory); $this->assertEquals(0, $DB->count_records('h5p')); } /** * Test the behaviour of get_export_info_from_context_id(). */ public function test_get_export_info_from_context_id(): void { global $DB; $this->setRunTestInSeparateProcess(true); $this->resetAfterTest(); $factory = new factory(); // Create the H5P data. $filename = 'find-the-words.h5p'; $syscontext = \context_system::instance(); // Test scenario 1: H5P exists and deployed. $generator = $this->getDataGenerator()->get_plugin_generator('core_h5p'); $fakeexportfile = $generator->create_export_file($filename, $syscontext->id, \core_h5p\file_storage::COMPONENT, \core_h5p\file_storage::EXPORT_FILEAREA); $exportfile = api::get_export_info_from_context_id($syscontext->id, $factory, \core_h5p\file_storage::COMPONENT, \core_h5p\file_storage::EXPORT_FILEAREA); $this->assertEquals($fakeexportfile['filename'], $exportfile['filename']); $this->assertEquals($fakeexportfile['filepath'], $exportfile['filepath']); $this->assertEquals($fakeexportfile['filesize'], $exportfile['filesize']); $this->assertEquals($fakeexportfile['timemodified'], $exportfile['timemodified']); $this->assertEquals($fakeexportfile['fileurl'], $exportfile['fileurl']); // Test scenario 2: H5P exist, deployed but the content has changed. // We need to change the contenthash to simulate the H5P file was changed. $h5pfile = $DB->get_record('h5p', []); $h5pfile->contenthash = sha1('testedit'); $DB->update_record('h5p', $h5pfile); $exportfile = api::get_export_info_from_context_id($syscontext->id, $factory, \core_h5p\file_storage::COMPONENT, \core_h5p\file_storage::EXPORT_FILEAREA); $this->assertNull($exportfile); // Tests scenario 3: H5P is not deployed. // We need to delete the H5P record to simulate the H5P was not deployed. $DB->delete_records('h5p', ['id' => $h5pfile->id]); $exportfile = api::get_export_info_from_context_id($syscontext->id, $factory, \core_h5p\file_storage::COMPONENT, \core_h5p\file_storage::EXPORT_FILEAREA); $this->assertNull($exportfile); } /** * Test the behaviour of set_library_enabled(). * * @covers ::set_library_enabled * @dataProvider set_library_enabled_provider * * @param string $libraryname Library name to enable/disable. * @param string $action Action to be done with the library. Supported values: enable, disable. * @param int $expected Expected value for the enabled library field. -1 will be passed if the library doesn't exist. */ public function test_set_library_enabled(string $libraryname, string $action, int $expected): void { global $DB; $this->resetAfterTest(); // Create libraries. $generator = $this->getDataGenerator()->get_plugin_generator('core_h5p'); $generator->generate_h5p_data(); // Check by default the library is enabled. $library = $DB->get_record('h5p_libraries', ['machinename' => $libraryname]); if ($expected >= 0) { $this->assertEquals(1, $library->enabled); $libraryid = (int) $library->id; } else { // Unexisting library. Set libraryid to some unexisting id. $libraryid = -1; $this->expectException('dml_missing_record_exception'); } \core_h5p\api::set_library_enabled($libraryid, ($action == 'enable')); // Check the value of the "enabled" field after calling enable/disable method. $libraries = $DB->get_records('h5p_libraries'); foreach ($libraries as $libraryid => $library) { if ($library->machinename == $libraryname) { $this->assertEquals($expected, $library->enabled); } else { // Check that only $libraryname has been enabled/disabled. $this->assertEquals(1, $library->enabled); } } } /** * Data provider for test_set_library_enabled(). * * @return array */ public static function set_library_enabled_provider(): array { return [ 'Disable existing library' => [ 'libraryname' => 'MainLibrary', 'action' => 'disable', 'expected' => 0, ], 'Enable existing library' => [ 'libraryname' => 'MainLibrary', 'action' => 'enable', 'expected' => 1, ], 'Disable existing library (not main)' => [ 'libraryname' => 'Library1', 'action' => 'disable', 'expected' => 0, ], 'Enable existing library (not main)' => [ 'libraryname' => 'Library1', 'action' => 'enable', 'expected' => 1, ], 'Disable existing library (not runnable)' => [ 'libraryname' => 'Library3', 'action' => 'disable', 'expected' => 1, // Not runnable libraries can't be disabled. ], 'Enable existing library (not runnable)' => [ 'libraryname' => 'Library3', 'action' => 'enable', 'expected' => 1, ], 'Enable unexisting library' => [ 'libraryname' => 'Unexisting library', 'action' => 'enable', 'expected' => -1, ], 'Disable unexisting library' => [ 'libraryname' => 'Unexisting library', 'action' => 'disable', 'expected' => -1, ], ]; } /** * Test the behaviour of is_library_enabled(). * * @covers ::is_library_enabled * @dataProvider is_library_enabled_provider * * @param string $libraryname Library name to check. * @param bool $expected Expected result after calling the method. * @param bool $exception Exception expected or not. * @param bool $useid Whether to use id for calling is_library_enabled method. * @param bool $uselibraryname Whether to use libraryname for calling is_library_enabled method. */ public function test_is_library_enabled(string $libraryname, bool $expected, bool $exception = false, bool $useid = false, bool $uselibraryname = true): void { global $DB; $this->resetAfterTest(); // Create the following libraries: // - H5P.Lib1: 1 version enabled, 1 version disabled. // - H5P.Lib2: 2 versions enabled. // - H5P.Lib3: 2 versions disabled. // - H5P.Lib4: 1 version disabled. // - H5P.Lib5: 1 version enabled. $generator = $this->getDataGenerator()->get_plugin_generator('core_h5p'); $libraries = [ 'H5P.Lib1.1' => $generator->create_library_record('H5P.Lib1', 'Lib1', 1, 1, 0, '', null, null, null, false), 'H5P.Lib1.2' => $generator->create_library_record('H5P.Lib1', 'Lib1', 1, 2), 'H5P.Lib2.1' => $generator->create_library_record('H5P.Lib2', 'Lib2', 2, 1), 'H5P.Lib2.2' => $generator->create_library_record('H5P.Lib2', 'Lib2', 2, 2), 'H5P.Lib3.1' => $generator->create_library_record('H5P.Lib3', 'Lib3', 3, 1, 0, '', null, null, null, false), 'H5P.Lib3.2' => $generator->create_library_record('H5P.Lib3', 'Lib3', 3, 2, 0, '', null, null, null, false), 'H5P.Lib4.1' => $generator->create_library_record('H5P.Lib4', 'Lib4', 4, 1, 0, '', null, null, null, false), 'H5P.Lib5.1' => $generator->create_library_record('H5P.Lib5', 'Lib5', 5, 1), ]; $countenabledlibraries = $DB->count_records('h5p_libraries', ['enabled' => 1]); $this->assertEquals(4, $countenabledlibraries); if ($useid) { $librarydata = ['id' => $libraries[$libraryname]->id]; } else if ($uselibraryname) { $librarydata = ['machinename' => $libraryname]; } else { $librarydata = ['invalid' => true]; } if ($exception) { $this->expectException(\moodle_exception::class); } $result = api::is_library_enabled((object) $librarydata); $this->assertEquals($expected, $result); } /** * Data provider for test_is_library_enabled(). * * @return array */ public static function is_library_enabled_provider(): array { return [ 'Library with 2 versions, one of them disabled' => [ 'libraryname' => 'H5P.Lib1', 'expected' => false, ], 'Library with 2 versions, all enabled' => [ 'libraryname' => 'H5P.Lib2', 'expected' => true, ], 'Library with 2 versions, all disabled' => [ 'libraryname' => 'H5P.Lib3', 'expected' => false, ], 'Library with only one version, disabled' => [ 'libraryname' => 'H5P.Lib4', 'expected' => false, ], 'Library with only one version, enabled' => [ 'libraryname' => 'H5P.Lib5', 'expected' => true, ], 'Library with 2 versions, one of them disabled (using id) - 1.1 (disabled)' => [ 'libraryname' => 'H5P.Lib1.1', 'expected' => false, 'exception' => false, 'useid' => true, ], 'Library with 2 versions, one of them disabled (using id) - 1.2 (enabled)' => [ 'libraryname' => 'H5P.Lib1.2', 'expected' => true, 'exception' => false, 'useid' => true, ], 'Library with 2 versions, all enabled (using id) - 2.1' => [ 'libraryname' => 'H5P.Lib2.1', 'expected' => true, 'exception' => false, 'useid' => true, ], 'Library with 2 versions, all enabled (using id) - 2.2' => [ 'libraryname' => 'H5P.Lib2.2', 'expected' => true, 'exception' => false, 'useid' => true, ], 'Library with 2 versions, all disabled (using id) - 3.1' => [ 'libraryname' => 'H5P.Lib3.1', 'expected' => false, 'exception' => false, 'useid' => true, ], 'Library with 2 versions, all disabled (using id) - 3.2' => [ 'libraryname' => 'H5P.Lib3.2', 'expected' => false, 'exception' => false, 'useid' => true, ], 'Library with only one version, disabled (using id)' => [ 'libraryname' => 'H5P.Lib4.1', 'expected' => false, 'exception' => false, 'useid' => true, ], 'Library with only one version, enabled (using id)' => [ 'libraryname' => 'H5P.Lib5.1', 'expected' => true, 'exception' => false, 'useid' => true, ], 'Unexisting library' => [ 'libraryname' => 'H5P.Unexisting', 'expected' => true, ], 'Missing required parameters' => [ 'libraryname' => 'H5P.Unexisting', 'expected' => false, 'exception' => true, 'useid' => false, 'uselibraryname' => false, ], ]; } /** * Test the behaviour of is_valid_package(). * @runInSeparateProcess * * @covers ::is_valid_package * @dataProvider is_valid_package_provider * * @param string $filename The H5P content to validate. * @param bool $expected Expected result after calling the method. * @param bool $isadmin Whether the user calling the method will be admin or not. * @param bool $onlyupdatelibs Whether new libraries can be installed or only the existing ones can be updated. * @param bool $skipcontent Should the content be skipped (so only the libraries will be saved)? */ public function test_is_valid_package(string $filename, bool $expected, bool $isadmin = false, bool $onlyupdatelibs = false, bool $skipcontent = false): void { global $USER; $this->resetAfterTest(); if ($isadmin) { $this->setAdminUser(); $user = $USER; } else { // Create a user. $user = $this->getDataGenerator()->create_user(); $this->setUser($user); } // Prepare the file. $path = __DIR__ . $filename; $file = helper::create_fake_stored_file_from_path($path, (int)$user->id); // Check if the H5P content is valid or not. $result = api::is_valid_package($file, $onlyupdatelibs, $skipcontent); $this->assertEquals($expected, $result); } /** * Data provider for test_is_valid_package(). * * @return array */ public static function is_valid_package_provider(): array { return [ 'Valid H5P file (as admin)' => [ 'filename' => '/fixtures/greeting-card.h5p', 'expected' => true, 'isadmin' => true, ], 'Valid H5P file (as user) without library update and checking content' => [ 'filename' => '/fixtures/greeting-card.h5p', 'expected' => false, // Libraries are missing and user hasn't the right permissions to upload them. 'isadmin' => false, 'onlyupdatelibs' => false, 'skipcontent' => false, ], 'Valid H5P file (as user) with library update and checking content' => [ 'filename' => '/fixtures/greeting-card.h5p', 'expected' => false, // Libraries are missing and user hasn't the right permissions to upload them. 'isadmin' => false, 'onlyupdatelibs' => true, 'skipcontent' => false, ], 'Valid H5P file (as user) without library update and skipping content' => [ 'filename' => '/fixtures/greeting-card.h5p', 'expected' => true, // Content check is skipped so the package will be considered valid. 'isadmin' => false, 'onlyupdatelibs' => false, 'skipcontent' => true, ], 'Valid H5P file (as user) with library update and skipping content' => [ 'filename' => '/fixtures/greeting-card.h5p', 'expected' => true, // Content check is skipped so the package will be considered valid. 'isadmin' => false, 'onlyupdatelibs' => true, 'skipcontent' => true, ], 'Invalid H5P file (as admin)' => [ 'filename' => '/fixtures/h5ptest.zip', 'expected' => false, 'isadmin' => true, ], 'Invalid H5P file (as user)' => [ 'filename' => '/fixtures/h5ptest.zip', 'expected' => false, 'isadmin' => false, ], 'Invalid H5P file (as user) skipping content' => [ 'filename' => '/fixtures/h5ptest.zip', 'expected' => true, // Content check is skipped so the package will be considered valid. 'isadmin' => false, 'onlyupdatelibs' => false, 'skipcontent' => true, ], ]; } }