Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 310 and 401] [Versions 39 and 401]

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * Tests for the tool_provider class.
  19   *
  20   * @package enrol_lti
  21   * @copyright 2016 Jun Pataleta <jun@moodle.com>
  22   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  namespace enrol_lti;
  26  
  27  use core\session\manager;
  28  use IMSGlobal\LTI\HTTPMessage;
  29  use IMSGlobal\LTI\ToolProvider\ResourceLink;
  30  use IMSGlobal\LTI\ToolProvider\ToolConsumer;
  31  use IMSGlobal\LTI\ToolProvider\ToolProvider;
  32  use IMSGlobal\LTI\ToolProvider\User;
  33  
  34  defined('MOODLE_INTERNAL') || die();
  35  
  36  /**
  37   * Tests for the tool_provider class.
  38   *
  39   * @package enrol_lti
  40   * @copyright 2016 Jun Pataleta <jun@moodle.com>
  41   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  42   */
  43  class tool_provider_test extends \advanced_testcase {
  44  
  45      /**
  46       * @var \stdClass $tool The LTI tool.
  47       */
  48      protected $tool;
  49  
  50      /**
  51       * Test set up.
  52       *
  53       * This is executed before running any tests in this file.
  54       */
  55      public function setUp(): void {
  56          global $SESSION;
  57          $this->resetAfterTest();
  58  
  59          manager::init_empty_session();
  60  
  61          // Set this user as the admin.
  62          $this->setAdminUser();
  63  
  64          $data = new \stdClass();
  65          $data->enrolstartdate = time();
  66          $data->secret = 'secret';
  67          $toolrecord = $this->getDataGenerator()->create_lti_tool($data);
  68          $this->tool = helper::get_lti_tool($toolrecord->id);
  69          $SESSION->notifications = [];
  70      }
  71  
  72      /**
  73       * Passing non-existent tool ID.
  74       */
  75      public function test_constructor_with_non_existent_tool() {
  76          $this->expectException('dml_exception');
  77          new tool_provider(-1);
  78      }
  79  
  80      /**
  81       * Constructor test.
  82       */
  83      public function test_constructor() {
  84          global $CFG, $SITE;
  85  
  86          $tool = $this->tool;
  87          $tp = new tool_provider($tool->id);
  88  
  89          $this->assertNull($tp->consumer);
  90          $this->assertNull($tp->returnUrl);
  91          $this->assertNull($tp->resourceLink);
  92          $this->assertNull($tp->context);
  93          $this->assertNotNull($tp->dataConnector);
  94          $this->assertEquals('', $tp->defaultEmail);
  95          $this->assertEquals(ToolProvider::ID_SCOPE_ID_ONLY, $tp->idScope);
  96          $this->assertFalse($tp->allowSharing);
  97          $this->assertEquals(ToolProvider::CONNECTION_ERROR_MESSAGE, $tp->message);
  98          $this->assertNull($tp->reason);
  99          $this->assertEmpty($tp->details);
 100          $this->assertEquals($CFG->wwwroot, $tp->baseUrl);
 101  
 102          $this->assertNotNull($tp->vendor);
 103          $this->assertEquals($SITE->shortname, $tp->vendor->id);
 104          $this->assertEquals($SITE->fullname, $tp->vendor->name);
 105          $this->assertEquals($SITE->summary, $tp->vendor->description);
 106  
 107          $token = helper::generate_proxy_token($tool->id);
 108          $name = helper::get_name($tool);
 109          $description = helper::get_description($tool);
 110  
 111          $this->assertNotNull($tp->product);
 112          $this->assertEquals($token, $tp->product->id);
 113          $this->assertEquals($name, $tp->product->name);
 114          $this->assertEquals($description, $tp->product->description);
 115  
 116          $this->assertNotNull($tp->requiredServices);
 117          $this->assertEmpty($tp->optionalServices);
 118          $this->assertNotNull($tp->resourceHandlers);
 119      }
 120  
 121      /**
 122       * Test for handle request.
 123       */
 124      public function test_handle_request_no_request_data() {
 125          $tool = $this->tool;
 126          $tp = new tool_provider($tool->id);
 127  
 128          // Tool provider object should have been created fine. OK flag should be fine for now.
 129          $this->assertTrue($tp->ok);
 130  
 131          // Call handleRequest but suppress output.
 132          ob_start();
 133          $tp->handleRequest();
 134          ob_end_clean();
 135  
 136          // There's basically no request data submitted so OK flag should turn out false.
 137          $this->assertFalse($tp->ok);
 138      }
 139  
 140      /**
 141       * Test for tool_provider::onError().
 142       */
 143      public function test_on_error() {
 144          $tool = $this->tool;
 145          $tp = new dummy_tool_provider($tool->id);
 146          $message = "THIS IS AN ERROR!";
 147          $tp->message = $message;
 148          $tp->onError();
 149          $errormessage = get_string('failedrequest', 'enrol_lti', ['reason' => $message]);
 150          $this->assertStringContainsString($errormessage, $tp->get_error_output());
 151      }
 152  
 153      /**
 154       * Test for tool_provider::onRegister() with no tool consumer set.
 155       */
 156      public function test_on_register_no_consumer() {
 157          $tool = $this->tool;
 158  
 159          $tp = new dummy_tool_provider($tool->id);
 160          $tp->onRegister();
 161  
 162          $this->assertFalse($tp->ok);
 163          $this->assertEquals(get_string('invalidtoolconsumer', 'enrol_lti'), $tp->message);
 164      }
 165  
 166      /**
 167       * Test for tool_provider::onRegister() without return URL.
 168       */
 169      public function test_on_register_no_return_url() {
 170          $tool = $this->tool;
 171  
 172          $dataconnector = new data_connector();
 173          $consumer = new ToolConsumer('testkey', $dataconnector);
 174          $consumer->ltiVersion = ToolProvider::LTI_VERSION2;
 175          $consumer->secret = $tool->secret;
 176          $consumer->name = 'TEST CONSUMER NAME';
 177          $consumer->consumerName = 'TEST CONSUMER INSTANCE NAME';
 178          $consumer->consumerGuid = 'TEST CONSUMER INSTANCE GUID';
 179          $consumer->consumerVersion = 'TEST CONSUMER INFO VERSION';
 180          $consumer->enabled = true;
 181          $consumer->protected = true;
 182          $consumer->save();
 183  
 184          $tp = new dummy_tool_provider($tool->id);
 185          $tp->consumer = $consumer;
 186  
 187          $tp->onRegister();
 188          $this->assertFalse($tp->ok);
 189          $this->assertEquals(get_string('returnurlnotset', 'enrol_lti'), $tp->message);
 190      }
 191  
 192      /**
 193       * Test for tool_provider::onRegister() when registration fails.
 194       */
 195      public function test_on_register_failed() {
 196          global $CFG;
 197          $tool = $this->tool;
 198  
 199          $dataconnector = new data_connector();
 200          $consumer = new dummy_tool_consumer('testkey', $dataconnector);
 201          $consumer->ltiVersion = ToolProvider::LTI_VERSION2;
 202          $consumer->secret = $tool->secret;
 203          $consumer->name = 'TEST CONSUMER NAME';
 204          $consumer->consumerName = 'TEST CONSUMER INSTANCE NAME';
 205          $consumer->consumerGuid = 'TEST CONSUMER INSTANCE GUID';
 206          $consumer->consumerVersion = 'TEST CONSUMER INFO VERSION';
 207          $consumer->enabled = true;
 208          $consumer->protected = true;
 209          $profilejson = file_get_contents(__DIR__ . '/fixtures/tool_consumer_profile.json');
 210          $consumer->profile = json_decode($profilejson);
 211          $consumer->save();
 212  
 213          $tp = new dummy_tool_provider($tool->id);
 214          $tp->consumer = $consumer;
 215          $tp->returnUrl = $CFG->wwwroot;
 216  
 217          $tp->onRegister();
 218  
 219          // The OK flag will be false.
 220          $this->assertFalse($tp->ok);
 221          // Check message.
 222          $this->assertEquals(get_string('couldnotestablishproxy', 'enrol_lti'), $tp->message);
 223      }
 224  
 225      /**
 226       * Test for tool_provider::onRegister() when registration succeeds.
 227       */
 228      public function test_on_register() {
 229          global $CFG, $DB;
 230          $tool = $this->tool;
 231  
 232          $dataconnector = new data_connector();
 233          $consumer = new dummy_tool_consumer('testkey', $dataconnector, false, true);
 234          $consumer->ltiVersion = ToolProvider::LTI_VERSION2;
 235          $consumer->secret = $tool->secret;
 236          $consumer->name = 'TEST CONSUMER NAME';
 237          $consumer->consumerName = 'TEST CONSUMER INSTANCE NAME';
 238          $consumer->consumerGuid = 'TEST CONSUMER INSTANCE GUID';
 239          $consumer->consumerVersion = 'TEST CONSUMER INFO VERSION';
 240          $consumer->enabled = true;
 241          $consumer->protected = true;
 242          $profilejson = file_get_contents(__DIR__ . '/fixtures/tool_consumer_profile.json');
 243          $consumer->profile = json_decode($profilejson);
 244          $consumer->save();
 245  
 246          $tp = new dummy_tool_provider($tool->id);
 247          $tp->consumer = $consumer;
 248          $tp->returnUrl = $CFG->wwwroot;
 249  
 250          // Capture output of onLaunch() method and save it as a string.
 251          ob_start();
 252          $tp->onRegister();
 253          $output = ob_get_clean();
 254  
 255          $successmessage = get_string('successfulregistration', 'enrol_lti');
 256  
 257          // Check output contents. Confirm that it has the success message and return URL.
 258          $this->assertStringContainsString($successmessage, $output);
 259          $this->assertStringContainsString($tp->returnUrl, $output);
 260  
 261          // The OK flag will be true on successful registration.
 262          $this->assertTrue($tp->ok);
 263  
 264          // Check tool provider message.
 265          $this->assertEquals($successmessage, $tp->message);
 266  
 267          // Check published tool and tool consumer mapping.
 268          $mappingparams = [
 269              'toolid' => $tool->id,
 270              'consumerid' => $tp->consumer->getRecordId()
 271          ];
 272          $this->assertTrue($DB->record_exists('enrol_lti_tool_consumer_map', $mappingparams));
 273      }
 274  
 275      /**
 276       * Test for tool_provider::onLaunch().
 277       */
 278      public function test_on_launch_no_frame_embedding() {
 279          $tp = $this->build_dummy_tp();
 280  
 281          // Capture output of onLaunch() method and save it as a string.
 282          ob_start();
 283          // Suppress session header errors.
 284          @$tp->onLaunch();
 285          $output = ob_get_clean();
 286  
 287          $this->assertStringContainsString(get_string('frameembeddingnotenabled', 'enrol_lti'), $output);
 288      }
 289  
 290      /**
 291       * Test for tool_provider::onLaunch().
 292       */
 293      public function test_on_launch_with_frame_embedding() {
 294          global $CFG;
 295          $CFG->allowframembedding = true;
 296  
 297          $tp = $this->build_dummy_tp();
 298  
 299          // If redirect was called here, we will encounter an 'unsupported redirect error'.
 300          // We just want to verify that redirect() was called if frame embedding is allowed.
 301          $this->expectException('moodle_exception');
 302  
 303          // Suppress session header errors.
 304          @$tp->onLaunch();
 305      }
 306  
 307      /**
 308       * Test for tool_provider::onLaunch() with invalid secret and no tool proxy (for LTI 1 launches).
 309       */
 310      public function test_on_launch_with_invalid_secret_and_no_proxy() {
 311          $tp = $this->build_dummy_tp('badsecret');
 312  
 313          // Suppress session header errors.
 314          @$tp->onLaunch();
 315          $this->assertFalse($tp->ok);
 316          $this->assertEquals(get_string('invalidrequest', 'enrol_lti'), $tp->message);
 317      }
 318  
 319      /**
 320       * Test for tool_provider::onLaunch() with invalid launch URL.
 321       */
 322      public function test_on_launch_proxy_with_invalid_launch_url() {
 323          $proxy = [
 324              'tool_profile' => [
 325                  'resource_handler' => [
 326                      [
 327                          'message' => [
 328                              [
 329                                  'message_type' => 'basic-lti-launch-request',
 330                                  'path' => '/enrol/lti/tool.php'
 331                              ]
 332                          ]
 333                      ]
 334                  ]
 335              ]
 336          ];
 337          $tp = $this->build_dummy_tp($this->tool->secret, $proxy);
 338          // Suppress session header errors.
 339          @$tp->onLaunch();
 340  
 341          $this->assertFalse($tp->ok);
 342          $this->assertEquals(get_string('invalidrequest', 'enrol_lti'), $tp->message);
 343      }
 344  
 345      /**
 346       * Test for tool_provider::onLaunch() with invalid launch URL.
 347       */
 348      public function test_on_launch_proxy_with_valid_launch_url() {
 349          $tool = $this->tool;
 350  
 351          $proxy = [
 352              'tool_profile' => [
 353                  'resource_handler' => [
 354                      [
 355                          'message' => [
 356                              [
 357                                  'message_type' => 'basic-lti-launch-request',
 358                                  'path' => '/enrol/lti/tool.php?id=' . $tool->id
 359                              ]
 360                          ]
 361                      ]
 362                  ]
 363              ]
 364          ];
 365          $tp = $this->build_dummy_tp($this->tool->secret, $proxy);
 366  
 367          // Capture output of onLaunch() method and save it as a string.
 368          ob_start();
 369          // Suppress session header errors.
 370          @$tp->onLaunch();
 371          $output = ob_get_clean();
 372  
 373          $this->assertTrue($tp->ok);
 374          $this->assertEquals(get_string('success'), $tp->message);
 375          $this->assertStringContainsString(get_string('frameembeddingnotenabled', 'enrol_lti'), $output);
 376      }
 377  
 378      /**
 379       * Test for tool_provider::onLaunch() for a request with message type other than basic-lti-launch-request.
 380       */
 381      public function test_on_launch_proxy_with_invalid_message_type() {
 382          $tool = $this->tool;
 383  
 384          $proxy = [
 385              'tool_profile' => [
 386                  'resource_handler' => [
 387                      [
 388                          'message' => [
 389                              [
 390                                  'message_type' => 'ContentItemSelectionRequest',
 391                                  'path' => '/enrol/lti/tool.php?id=' . $tool->id
 392                              ]
 393                          ]
 394                      ]
 395                  ]
 396              ]
 397          ];
 398          $tp = $this->build_dummy_tp($this->tool->secret, $proxy);
 399  
 400          // Suppress session header errors.
 401          @$tp->onLaunch();
 402  
 403          $this->assertFalse($tp->ok);
 404          $this->assertEquals(get_string('invalidrequest', 'enrol_lti'), $tp->message);
 405      }
 406  
 407      /**
 408       * Test for tool_provider::onLaunch() to verify that a user image can be set from the resource link's custom_user_image setting.
 409       */
 410      public function test_on_launch_with_user_image_from_resource_link() {
 411          global $DB;
 412  
 413          $userimageurl = $this->getExternalTestFileUrl('test.jpg');
 414          $resourcelinksettings = [
 415              'custom_user_image' => $userimageurl
 416          ];
 417          $tp = $this->build_dummy_tp($this->tool->secret, null, $resourcelinksettings);
 418  
 419          // Suppress output and session header errors.
 420          ob_start();
 421          @$tp->onLaunch();
 422          ob_end_clean();
 423  
 424          $this->assertEquals($userimageurl, $tp->resourceLink->getSetting('custom_user_image'));
 425  
 426          $username = helper::create_username($tp->consumer->getKey(), $tp->user->ltiUserId);
 427          $user = $DB->get_record('user', ['username' => $username]);
 428          // User was found.
 429          $this->assertNotFalse($user);
 430          // User picture was set.
 431          $this->assertNotEmpty($user->picture);
 432      }
 433  
 434      /**
 435       * Test for tool_provider::onLaunch() to verify that a LTI user has been enrolled.
 436       */
 437      public function test_on_launch_user_enrolment() {
 438          global $DB;
 439  
 440          $tp = $this->build_dummy_tp($this->tool->secret);
 441  
 442          // Suppress output and session header errors.
 443          ob_start();
 444          @$tp->onLaunch();
 445          ob_end_clean();
 446  
 447          $username = helper::create_username($tp->consumer->getKey(), $tp->user->ltiUserId);
 448          $user = $DB->get_record('user', ['username' => $username]);
 449          // User was found.
 450          $this->assertNotFalse($user);
 451          // User picture was not set.
 452          $this->assertEmpty($user->picture);
 453  
 454          // Check user enrolment.
 455          $enrolled = $DB->record_exists('user_enrolments', ['enrolid' => $this->tool->enrolid, 'userid' => $user->id]);
 456          $this->assertTrue($enrolled);
 457      }
 458  
 459      /**
 460       * Test for tool_provider::onLaunch() when the consumer object has not been set.
 461       */
 462      public function test_on_launch_no_consumer() {
 463          global $DB;
 464  
 465          $tool = $this->tool;
 466  
 467          $tp = new dummy_tool_provider($tool->id);
 468          $tp->onLaunch();
 469          $this->assertFalse($tp->ok);
 470          $this->assertEquals(get_string('invalidtoolconsumer', 'enrol_lti'), $tp->message);
 471  
 472          // Check published tool and tool consumer has not yet been mapped due to failure.
 473          $mappingparams = [
 474              'toolid' => $tool->id
 475          ];
 476          $this->assertFalse($DB->record_exists('enrol_lti_tool_consumer_map', $mappingparams));
 477      }
 478  
 479      /**
 480       * Test for tool_provider::onLaunch() when we have a non-existent consumer data.
 481       */
 482      public function test_on_launch_invalid_consumer() {
 483          $tool = $this->tool;
 484  
 485          $dataconnector = new data_connector();
 486          // Build consumer object but don't save it.
 487          $consumer = new dummy_tool_consumer('testkey', $dataconnector);
 488  
 489          $tp = new dummy_tool_provider($tool->id);
 490          $tp->consumer = $consumer;
 491          $tp->onLaunch();
 492          $this->assertFalse($tp->ok);
 493          $this->assertEquals(get_string('invalidtoolconsumer', 'enrol_lti'), $tp->message);
 494      }
 495  
 496      /**
 497       * Test for tool_provider::map_tool_to_consumer().
 498       */
 499      public function test_map_tool_to_consumer() {
 500          global $DB;
 501  
 502          $tp = $this->build_dummy_tp();
 503          $tp->map_tool_to_consumer();
 504  
 505          // Check published tool and tool consumer mapping.
 506          $mappingparams = [
 507              'toolid' => $this->tool->id,
 508              'consumerid' => $tp->consumer->getRecordId()
 509          ];
 510          $this->assertTrue($DB->record_exists('enrol_lti_tool_consumer_map', $mappingparams));
 511      }
 512  
 513      /**
 514       * Test for tool_provider::map_tool_to_consumer().
 515       */
 516      public function test_map_tool_to_consumer_no_consumer() {
 517          $tp = new dummy_tool_provider($this->tool->id);
 518          $this->expectException('moodle_exception');
 519          $tp->map_tool_to_consumer();
 520      }
 521  
 522      /**
 523       * Builds a dummy tool provider object.
 524       *
 525       * @param string $secret Consumer secret.
 526       * @param array|\stdClass $proxy Tool proxy data.
 527       * @param null $resourcelinksettings Key-value array for resource link settings.
 528       * @return dummy_tool_provider
 529       */
 530      protected function build_dummy_tp($secret = null, $proxy = null, $resourcelinksettings = null) {
 531          $tool = $this->tool;
 532  
 533          $dataconnector = new data_connector();
 534          $consumer = new ToolConsumer('testkey', $dataconnector);
 535  
 536          $ltiversion = ToolProvider::LTI_VERSION2;
 537          if ($secret === null && $proxy === null) {
 538              $consumer->secret = $tool->secret;
 539              $ltiversion = ToolProvider::LTI_VERSION1;
 540          } else {
 541              $consumer->secret = $secret;
 542          }
 543          $consumer->ltiVersion = $ltiversion;
 544  
 545          $consumer->name = 'TEST CONSUMER NAME';
 546          $consumer->consumerName = 'TEST CONSUMER INSTANCE NAME';
 547          $consumer->consumerGuid = 'TEST CONSUMER INSTANCE GUID';
 548          $consumer->consumerVersion = 'TEST CONSUMER INFO VERSION';
 549          $consumer->enabled = true;
 550          $consumer->protected = true;
 551          if ($proxy !== null) {
 552              $consumer->toolProxy = json_encode($proxy);
 553          }
 554          $consumer->save();
 555  
 556          $resourcelink = ResourceLink::fromConsumer($consumer, 'testresourcelinkid');
 557          if (!empty($resourcelinksettings)) {
 558              foreach ($resourcelinksettings as $setting => $value) {
 559                  $resourcelink->setSetting($setting, $value);
 560              }
 561          }
 562          $resourcelink->save();
 563  
 564          $ltiuser = User::fromResourceLink($resourcelink, '');
 565          $ltiuser->ltiResultSourcedId = 'testLtiResultSourcedId';
 566          $ltiuser->ltiUserId = 'testuserid';
 567          $ltiuser->email = 'user1@example.com';
 568          $ltiuser->save();
 569  
 570          $tp = new dummy_tool_provider($tool->id);
 571          $tp->user = $ltiuser;
 572          $tp->resourceLink = $resourcelink;
 573          $tp->consumer = $consumer;
 574  
 575          return $tp;
 576      }
 577  }
 578  
 579  /**
 580   * Class dummy_tool_provider.
 581   *
 582   * A class that extends tool_provider so that we can expose the protected methods that we have overridden.
 583   *
 584   * @copyright 2016 Jun Pataleta <jun@moodle.com>
 585   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 586   */
 587  class dummy_tool_provider extends tool_provider {
 588  
 589      /**
 590       * Exposes tool_provider::onError().
 591       */
 592      public function onError() {
 593          parent::onError();
 594      }
 595  
 596      /**
 597       * Exposes tool_provider::onLaunch().
 598       */
 599      public function onLaunch() {
 600          parent::onLaunch();
 601      }
 602  
 603      /**
 604       * Exposes tool_provider::onRegister().
 605       */
 606      public function onRegister() {
 607          parent::onRegister();
 608      }
 609  
 610      /**
 611       * Expose protected variable errorOutput.
 612       *
 613       * @return string
 614       */
 615      public function get_error_output() {
 616          return $this->errorOutput;
 617      }
 618  }
 619  
 620  /**
 621   * Class dummy_tool_consumer
 622   *
 623   * A class that extends ToolConsumer in order to override and simulate sending and receiving data to tool consumer endpoint.
 624   *
 625   * @copyright 2016 Jun Pataleta <jun@moodle.com>
 626   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 627   */
 628  class dummy_tool_consumer extends ToolConsumer {
 629  
 630      /**
 631       * @var bool Flag to indicate whether to send an OK response or a failed response.
 632       */
 633      protected $success = false;
 634  
 635      /**
 636       * dummy_tool_consumer constructor.
 637       *
 638       * @param null|string $key
 639       * @param mixed|null $dataconnector
 640       * @param bool $autoenable
 641       * @param bool $success
 642       */
 643      public function __construct($key = null, $dataconnector = null, $autoenable = false, $success = false) {
 644          parent::__construct($key, $dataconnector, $autoenable);
 645          $this->success = $success;
 646      }
 647  
 648      /**
 649       * Override ToolConsumer::doServiceRequest() to simulate sending/receiving data to and from the tool consumer.
 650       *
 651       * @param object $service
 652       * @param string $method
 653       * @param string $format
 654       * @param mixed $data
 655       * @return HTTPMessage
 656       */
 657      public function doServiceRequest($service, $method, $format, $data) {
 658          $response = (object)['tool_proxy_guid' => 1];
 659          $header = ToolConsumer::addSignature($service->endpoint, $this->getKey(), $this->secret, $data, $method, $format);
 660          $http = new HTTPMessage($service->endpoint, $method, $data, $header);
 661  
 662          if ($this->success) {
 663              $http->responseJson = $response;
 664              $http->ok = true;
 665              $http->status = 201;
 666          }
 667  
 668          return $http;
 669      }
 670  }