mirror of
https://github.com/moodle/moodle.git
synced 2025-01-19 14:27:22 +01:00
315 lines
12 KiB
PHP
315 lines
12 KiB
PHP
<?php
|
||
// This file is part of Moodle - http://moodle.org/
|
||
//
|
||
// Moodle is free software: you can redistribute it and/or modify
|
||
// it under the terms of the GNU General Public License as published by
|
||
// the Free Software Foundation, either version 3 of the License, or
|
||
// (at your option) any later version.
|
||
//
|
||
// Moodle is distributed in the hope that it will be useful,
|
||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
// GNU General Public License for more details.
|
||
//
|
||
// You should have received a copy of the GNU General Public License
|
||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||
|
||
namespace core\moodlenet;
|
||
|
||
use context_course;
|
||
use core\http_client;
|
||
use core\oauth2\issuer;
|
||
use GuzzleHttp\Exception\ClientException;
|
||
use GuzzleHttp\Handler\MockHandler;
|
||
use GuzzleHttp\HandlerStack;
|
||
use GuzzleHttp\Psr7\Response;
|
||
use PHPUnit\Framework\MockObject\MockObject;
|
||
use Psr\Http\Message\ResponseInterface;
|
||
use ReflectionMethod;
|
||
use stdClass;
|
||
use testing_data_generator;
|
||
|
||
/**
|
||
* Unit tests for {@see activity_sender}.
|
||
*
|
||
* @coversDefaultClass \core\moodlenet\activity_sender
|
||
* @package core
|
||
* @copyright 2023 Huong Nguyen <huongnv13@gmail.com>
|
||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||
*/
|
||
class activity_sender_test extends \advanced_testcase {
|
||
|
||
/** @var testing_data_generator Data generator. */
|
||
private testing_data_generator $generator;
|
||
/** @var stdClass Course object. */
|
||
private stdClass $course;
|
||
/** @var stdClass Activity object, */
|
||
private stdClass $moduleinstance;
|
||
/** @var context_course Course context instance. */
|
||
private context_course $coursecontext;
|
||
/** @var issuer $issuer Dummy issuer. */
|
||
private issuer $issuer;
|
||
/** @var MockObject $mockoauthclient Mock OAuth client. */
|
||
private MockObject $mockoauthclient;
|
||
|
||
public static function setUpBeforeClass(): void {
|
||
parent::setUpBeforeClass();
|
||
|
||
require_once(__DIR__ . '/helpers.php');
|
||
}
|
||
|
||
/**
|
||
* Set up function for tests.
|
||
*/
|
||
protected function setUp(): void {
|
||
parent::setUp();
|
||
|
||
$this->resetAfterTest();
|
||
// Get data generator.
|
||
$this->generator = $this->getDataGenerator();
|
||
// Create course.
|
||
$this->course = $this->generator->create_course();
|
||
$this->moduleinstance = $this->generator->create_module('assign', ['course' => $this->course->id]);
|
||
$this->coursecontext = context_course::instance($this->course->id);
|
||
// Create mock issuer.
|
||
$this->issuer = helpers::get_mock_issuer(1);
|
||
// Create mock builder for OAuth2 client.
|
||
$mockbuilder = $this->getMockBuilder('core\oauth2\client');
|
||
$mockbuilder->onlyMethods(['get_issuer', 'is_logged_in', 'get_accesstoken']);
|
||
$mockbuilder->setConstructorArgs([$this->issuer, '', '']);
|
||
// Get the OAuth2 client mock.
|
||
$this->mockoauthclient = $mockbuilder->getMock();
|
||
}
|
||
|
||
/**
|
||
* Test prepare_share_contents method.
|
||
*
|
||
* @covers ::prepare_share_contents
|
||
*/
|
||
public function test_prepare_share_contents(): void {
|
||
global $USER;
|
||
$this->setAdminUser();
|
||
|
||
$httpclient = new http_client();
|
||
$moodlenetclient = new moodlenet_client($httpclient, $this->mockoauthclient);
|
||
|
||
// Set get_file method accessibility.
|
||
$method = new ReflectionMethod(activity_sender::class, 'prepare_share_contents');
|
||
$method->setAccessible(true);
|
||
|
||
// Test with invalid share format.
|
||
$this->expectException(\moodle_exception::class);
|
||
$this->expectExceptionMessage(get_string('moodlenet:invalidshareformat', 'error'));
|
||
$package = $method->invoke(new activity_sender(
|
||
$this->moduleinstance->cmid,
|
||
$USER->id,
|
||
$moodlenetclient,
|
||
$this->mockoauthclient,
|
||
random_int(5, 30)
|
||
));
|
||
|
||
// Test with valid share format and invalid course module.
|
||
$package = $method->invoke(new activity_sender(
|
||
random_int(5, 30),
|
||
$USER->id,
|
||
$moodlenetclient,
|
||
$this->mockoauthclient,
|
||
resource_sender::SHARE_FORMAT_BACKUP
|
||
));
|
||
$this->assertEmpty($package);
|
||
|
||
// Test with valid share format and valid course module.
|
||
$package = $method->invoke(new activity_sender(
|
||
$this->moduleinstance->cmid,
|
||
$USER->id,
|
||
$moodlenetclient,
|
||
$this->mockoauthclient,
|
||
resource_sender::SHARE_FORMAT_BACKUP
|
||
));
|
||
$this->assertNotEmpty($package);
|
||
|
||
// Confirm the expected stored_file object is returned.
|
||
$this->assertInstanceOf(\stored_file::class, $package);
|
||
}
|
||
|
||
/**
|
||
* Test get_resource_description method.
|
||
*
|
||
* @covers ::get_resource_description
|
||
*/
|
||
public function test_get_resource_description(): void {
|
||
global $USER;
|
||
$this->setAdminUser();
|
||
|
||
$activity = $this->generator->create_module('assign', [
|
||
'course' => $this->course->id,
|
||
'intro' => '<p>This is an example Moodle activity description.</p>
|
||
<p> </p>
|
||
<p>This is a formatted intro</p>
|
||
<p> </p>
|
||
<p>This thing has many lines.</p>
|
||
<p> </p>
|
||
<p>The last word of this sentence is in <strong>bold</strong></p>'
|
||
]);
|
||
|
||
$httpclient = new http_client();
|
||
$moodlenetclient = new moodlenet_client($httpclient, $this->mockoauthclient);
|
||
|
||
// Set get_resource_description method accessibility.
|
||
$method = new ReflectionMethod(activity_sender::class, 'get_resource_description');
|
||
$method->setAccessible(true);
|
||
|
||
// Test the processed description.
|
||
$processeddescription = $method->invoke(new activity_sender(
|
||
$activity->cmid,
|
||
$USER->id,
|
||
$moodlenetclient,
|
||
$this->mockoauthclient,
|
||
resource_sender::SHARE_FORMAT_BACKUP
|
||
), $this->coursecontext);
|
||
|
||
$this->assertEquals('This is an example Moodle activity description.
|
||
|
||
This is a formatted intro
|
||
|
||
This thing has many lines.
|
||
|
||
The last word of this sentence is in bold', $processeddescription);
|
||
}
|
||
|
||
/**
|
||
* Test share_resource() method.
|
||
*
|
||
* @dataProvider share_resource_provider
|
||
* @covers ::share_resource
|
||
* @covers ::log_event
|
||
* @covers \core\moodlenet\moodlenet_client::create_resource_from_stored_file
|
||
* @covers \core\moodlenet\moodlenet_client::prepare_file_share_request_data
|
||
* @param ResponseInterface $httpresponse
|
||
* @param array $expected
|
||
*/
|
||
public function test_share_resource(ResponseInterface $httpresponse, array $expected): void {
|
||
global $CFG, $USER;
|
||
$this->setAdminUser();
|
||
|
||
// Enable the experimental flag.
|
||
$CFG->enablesharingtomoodlenet = true;
|
||
|
||
// Set OAuth 2 service in the outbound setting to the dummy issuer.
|
||
set_config('oauthservice', $this->issuer->get('id'), 'moodlenet');
|
||
|
||
// Generate access token for the mock.
|
||
$accesstoken = new stdClass();
|
||
$accesstoken->token = random_string(64);
|
||
|
||
// Get the OAuth2 client mock and set the return value for necessary methods.
|
||
$this->mockoauthclient->method('get_issuer')->will($this->returnValue($this->issuer));
|
||
$this->mockoauthclient->method('is_logged_in')->will($this->returnValue(true));
|
||
$this->mockoauthclient->method('get_accesstoken')->will($this->returnValue($accesstoken));
|
||
|
||
// Create Guzzle mock.
|
||
$mockguzzlehandler = new MockHandler([$httpresponse]);
|
||
$handlerstack = HandlerStack::create($mockguzzlehandler);
|
||
$httpclient = new http_client(['handler' => $handlerstack]);
|
||
|
||
// Create events sink.
|
||
$sink = $this->redirectEvents();
|
||
|
||
// Create activity sender.
|
||
$moodlenetclient = new moodlenet_client($httpclient, $this->mockoauthclient);
|
||
$activitysender = new activity_sender(
|
||
$this->moduleinstance->cmid,
|
||
$USER->id,
|
||
$moodlenetclient,
|
||
$this->mockoauthclient,
|
||
resource_sender::SHARE_FORMAT_BACKUP
|
||
);
|
||
|
||
if (isset($expected['exception'])) {
|
||
$this->expectException(ClientException::class);
|
||
$this->expectExceptionMessage($expected['exception']);
|
||
}
|
||
// Call the API.
|
||
$result = $activitysender->share_resource();
|
||
|
||
// Verify the result.
|
||
$this->assertEquals($expected['response_code'], $result['responsecode']);
|
||
$this->assertEquals($expected['resource_url'], $result['drafturl']);
|
||
|
||
// Verify the events.
|
||
$events = $sink->get_events();
|
||
$event = reset($events);
|
||
$this->assertInstanceOf('\core\event\moodlenet_resource_exported', $event);
|
||
$this->assertEquals($USER->id, $event->userid);
|
||
|
||
if ($result['responsecode'] == 201) {
|
||
$description = "The user with id '{$USER->id}' successfully shared activities to MoodleNet with the " .
|
||
"following course module ids, from context with id '{$this->coursecontext->id}': '{$this->moduleinstance->cmid}'.";
|
||
} else {
|
||
$description = "The user with id '{$USER->id}' failed to share activities to MoodleNet with the " .
|
||
"following course module ids, from context with id '{$this->coursecontext->id}': '{$this->moduleinstance->cmid}'.";
|
||
}
|
||
$this->assertEquals($description, $event->get_description());
|
||
}
|
||
|
||
/**
|
||
* Provider for test share_resource().
|
||
*
|
||
* @return array Test data.
|
||
*/
|
||
public function share_resource_provider(): array {
|
||
return [
|
||
'Success' => [
|
||
'http_response' => new Response(
|
||
201,
|
||
['Content-Type' => 'application/json'],
|
||
json_encode([
|
||
'homepage' => 'https://moodlenet.example.com/drafts/view/activity_backup_1.mbz',
|
||
]),
|
||
),
|
||
'expected' => [
|
||
'response_code' => 201,
|
||
'resource_url' => 'https://moodlenet.example.com/drafts/view/activity_backup_1.mbz',
|
||
],
|
||
],
|
||
'Fail with 200 status code' => [
|
||
'http_response' => new Response(
|
||
200,
|
||
['Content-Type' => 'application/json'],
|
||
json_encode([
|
||
'homepage' => 'https://moodlenet.example.com/drafts/view/activity_backup_2.mbz',
|
||
]),
|
||
),
|
||
'expected' => [
|
||
'response_code' => 200,
|
||
'resource_url' => 'https://moodlenet.example.com/drafts/view/activity_backup_2.mbz',
|
||
],
|
||
],
|
||
'Fail with 401 status code' => [
|
||
'http_response' => new Response(
|
||
401,
|
||
),
|
||
'expected' => [
|
||
'response_code' => 401,
|
||
'resource_url' => '',
|
||
'exception' => 'Client error: ' .
|
||
'`POST https://moodlenet.example.com/.pkg/@moodlenet/ed-resource/basic/v1/create` ' .
|
||
'resulted in a `401 Unauthorized` response',
|
||
],
|
||
],
|
||
'Fail with 404 status code' => [
|
||
'http_response' => new Response(
|
||
404,
|
||
),
|
||
'expected' => [
|
||
'response_code' => 404,
|
||
'resource_url' => '',
|
||
'exception' => 'Client error: '.
|
||
'`POST https://moodlenet.example.com/.pkg/@moodlenet/ed-resource/basic/v1/create` ' .
|
||
'resulted in a `404 Not Found` response',
|
||
],
|
||
],
|
||
];
|
||
}
|
||
}
|