Search moodle.org's
Developer Documentation

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.
  • Differences Between: [Versions 310 and 311] [Versions 311 and 400] [Versions 37 and 311] [Versions 38 and 311] [Versions 39 and 311]

       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   * Persistent class tests.
      19   *
      20   * @package    core
      21   * @copyright  2015 Frédéric Massart - FMCorz.net
      22   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
      23   */
      24  
      25  defined('MOODLE_INTERNAL') || die();
      26  global $CFG;
      27  
      28  /**
      29   * Persistent testcase.
      30   *
      31   * @package    core
      32   * @copyright  2015 Frédéric Massart - FMCorz.net
      33   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
      34   */
      35  class core_persistent_testcase extends advanced_testcase {
      36  
      37      public function setUp(): void {
      38          $this->make_persistent_table();
      39          $this->resetAfterTest();
      40      }
      41  
      42      /**
      43       * Make the table for the persistent.
      44       */
      45      protected function make_persistent_table() {
      46          global $DB;
      47          $dbman = $DB->get_manager();
      48  
      49          $table = new xmldb_table(core_testable_persistent::TABLE);
      50          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
      51          $table->add_field('shortname', XMLDB_TYPE_CHAR, '100', null, null, null, null);
      52          $table->add_field('idnumber', XMLDB_TYPE_CHAR, '100', null, null, null, null);
      53          $table->add_field('description', XMLDB_TYPE_TEXT, null, null, null, null, null);
      54          $table->add_field('descriptionformat', XMLDB_TYPE_INTEGER, '4', null, XMLDB_NOTNULL, null, '0');
      55          $table->add_field('parentid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
      56          $table->add_field('path', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null);
      57          $table->add_field('sortorder', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
      58          $table->add_field('scaleid', XMLDB_TYPE_INTEGER, '10', null, null, null, null);
      59          $table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
      60          $table->add_field('timemodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
      61          $table->add_field('usermodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
      62  
      63          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
      64  
      65          if ($dbman->table_exists($table)) {
      66              $dbman->drop_table($table);
      67          }
      68  
      69          $dbman->create_table($table);
      70      }
      71  
      72      public function test_properties_definition() {
      73          $expected = array(
      74              'shortname' => array(
      75                  'type' => PARAM_TEXT,
      76                  'default' => '',
      77                  'null' => NULL_NOT_ALLOWED
      78              ),
      79              'idnumber' => array(
      80                  'type' => PARAM_TEXT,
      81                  'null' => NULL_NOT_ALLOWED
      82              ),
      83              'description' => array(
      84                  'type' => PARAM_TEXT,
      85                  'default' => '',
      86                  'null' => NULL_NOT_ALLOWED
      87              ),
      88              'descriptionformat' => array(
      89                  'choices' => array(FORMAT_HTML, FORMAT_MOODLE, FORMAT_PLAIN, FORMAT_MARKDOWN),
      90                  'type' => PARAM_INT,
      91                  'default' => FORMAT_HTML,
      92                  'null' => NULL_NOT_ALLOWED
      93              ),
      94              'parentid' => array(
      95                  'type' => PARAM_INT,
      96                  'default' => 0,
      97                  'null' => NULL_NOT_ALLOWED
      98              ),
      99              'path' => array(
     100                  'type' => PARAM_RAW,
     101                  'default' => '',
     102                  'null' => NULL_NOT_ALLOWED
     103              ),
     104              'sortorder' => array(
     105                  'type' => PARAM_INT,
     106                  'message' => new lang_string('invalidrequest', 'error'),
     107                  'null' => NULL_NOT_ALLOWED
     108              ),
     109              'scaleid' => array(
     110                  'default' => null,
     111                  'type' => PARAM_INT,
     112                  'null' => NULL_ALLOWED
     113              ),
     114              'id' => array(
     115                  'default' => 0,
     116                  'type' => PARAM_INT,
     117                  'null' => NULL_NOT_ALLOWED
     118              ),
     119              'timecreated' => array(
     120                  'default' => 0,
     121                  'type' => PARAM_INT,
     122                  'null' => NULL_NOT_ALLOWED
     123              ),
     124              'timemodified' => array(
     125                  'default' => 0,
     126                  'type' => PARAM_INT,
     127                  'null' => NULL_NOT_ALLOWED
     128              ),
     129              'usermodified' => array(
     130                  'default' => 0,
     131                  'type' => PARAM_INT,
     132                  'null' => NULL_NOT_ALLOWED
     133              ),
     134          );
     135          $this->assertEquals($expected, core_testable_persistent::properties_definition());
     136      }
     137  
     138      public function test_to_record() {
     139          $p = new core_testable_persistent();
     140          $expected = (object) array(
     141              'shortname' => '',
     142              'idnumber' => null,
     143              'description' => '',
     144              'descriptionformat' => FORMAT_HTML,
     145              'parentid' => 0,
     146              'path' => '',
     147              'sortorder' => null,
     148              'id' => 0,
     149              'timecreated' => 0,
     150              'timemodified' => 0,
     151              'usermodified' => 0,
     152              'scaleid' => null,
     153          );
     154          $this->assertEquals($expected, $p->to_record());
     155      }
     156  
     157      public function test_from_record() {
     158          $p = new core_testable_persistent();
     159          $data = (object) array(
     160              'shortname' => 'ddd',
     161              'idnumber' => 'abc',
     162              'description' => 'xyz',
     163              'descriptionformat' => FORMAT_PLAIN,
     164              'parentid' => 999,
     165              'path' => '/a/b/c',
     166              'sortorder' => 12,
     167              'id' => 1,
     168              'timecreated' => 2,
     169              'timemodified' => 3,
     170              'usermodified' => 4,
     171              'scaleid' => null,
     172          );
     173          $p->from_record($data);
     174          $this->assertEquals($data, $p->to_record());
     175      }
     176  
     177      public function test_from_record_invalid_param() {
     178          $p = new core_testable_persistent();
     179          $data = (object) array(
     180              'shortname' => 'ddd',
     181              'idnumber' => 'abc',
     182              'description' => 'xyz',
     183              'descriptionformat' => FORMAT_PLAIN,
     184              'parentid' => 999,
     185              'path' => '/a/b/c',
     186              'sortorder' => 12,
     187              'id' => 1,
     188              'timecreated' => 2,
     189              'timemodified' => 3,
     190              'usermodified' => 4,
     191              'scaleid' => null,
     192              'invalidparam' => 'abc'
     193          );
     194  
     195          $p->from_record($data);
     196  
     197          // Previous call should succeed, assert we get back all data except invalid param.
     198          unset($data->invalidparam);
     199          $this->assertEquals($data, $p->to_record());
     200      }
     201  
     202      public function test_validate() {
     203          $data = (object) array(
     204              'idnumber' => 'abc',
     205              'sortorder' => 0
     206          );
     207          $p = new core_testable_persistent(0, $data);
     208          $this->assertFalse(isset($p->beforevalidate));
     209          $this->assertTrue($p->validate());
     210          $this->assertTrue(isset($p->beforevalidate));
     211          $this->assertTrue($p->is_valid());
     212          $this->assertEquals(array(), $p->get_errors());
     213          $p->set('descriptionformat', -100);
     214  
     215          $expected = array(
     216              'descriptionformat' => new lang_string('invaliddata', 'error'),
     217          );
     218          $this->assertEquals($expected, $p->validate());
     219          $this->assertFalse($p->is_valid());
     220          $this->assertEquals($expected, $p->get_errors());
     221      }
     222  
     223      public function test_validation_required() {
     224          $data = (object) array(
     225              'idnumber' => 'abc'
     226          );
     227          $p = new core_testable_persistent(0, $data);
     228          $expected = array(
     229              'sortorder' => new lang_string('requiredelement', 'form'),
     230          );
     231          $this->assertFalse($p->is_valid());
     232          $this->assertEquals($expected, $p->get_errors());
     233      }
     234  
     235      public function test_validation_custom() {
     236          $data = (object) array(
     237              'idnumber' => 'abc',
     238              'sortorder' => 10,
     239          );
     240          $p = new core_testable_persistent(0, $data);
     241          $expected = array(
     242              'sortorder' => new lang_string('invalidkey', 'error'),
     243          );
     244          $this->assertFalse($p->is_valid());
     245          $this->assertEquals($expected, $p->get_errors());
     246      }
     247  
     248      public function test_validation_custom_message() {
     249          $data = (object) array(
     250              'idnumber' => 'abc',
     251              'sortorder' => 'abc',
     252          );
     253          $p = new core_testable_persistent(0, $data);
     254          $expected = array(
     255              'sortorder' => new lang_string('invalidrequest', 'error'),
     256          );
     257          $this->assertFalse($p->is_valid());
     258          $this->assertEquals($expected, $p->get_errors());
     259      }
     260  
     261      public function test_validation_choices() {
     262          $data = (object) array(
     263              'idnumber' => 'abc',
     264              'sortorder' => 0,
     265              'descriptionformat' => -100
     266          );
     267          $p = new core_testable_persistent(0, $data);
     268          $expected = array(
     269              'descriptionformat' => new lang_string('invaliddata', 'error'),
     270          );
     271          $this->assertFalse($p->is_valid());
     272          $this->assertEquals($expected, $p->get_errors());
     273      }
     274  
     275      public function test_validation_type() {
     276          $data = (object) array(
     277              'idnumber' => 'abc',
     278              'sortorder' => 'NaN'
     279          );
     280          $p = new core_testable_persistent(0, $data);
     281          $this->assertFalse($p->is_valid());
     282          $this->assertArrayHasKey('sortorder', $p->get_errors());
     283      }
     284  
     285      public function test_validation_null() {
     286          $data = (object) array(
     287              'idnumber' => null,
     288              'sortorder' => 0,
     289              'scaleid' => 'bad!'
     290          );
     291          $p = new core_testable_persistent(0, $data);
     292          $this->assertFalse($p->is_valid());
     293          $this->assertArrayHasKey('idnumber', $p->get_errors());
     294          $this->assertArrayHasKey('scaleid', $p->get_errors());
     295          $p->set('idnumber', 'abc');
     296          $this->assertFalse($p->is_valid());
     297          $this->assertArrayNotHasKey('idnumber', $p->get_errors());
     298          $this->assertArrayHasKey('scaleid', $p->get_errors());
     299          $p->set('scaleid', null);
     300          $this->assertTrue($p->is_valid());
     301          $this->assertArrayNotHasKey('scaleid', $p->get_errors());
     302      }
     303  
     304      public function test_create() {
     305          global $DB;
     306          $p = new core_testable_persistent(0, (object) array('sortorder' => 123, 'idnumber' => 'abc'));
     307          $this->assertFalse(isset($p->beforecreate));
     308          $this->assertFalse(isset($p->aftercreate));
     309          $p->create();
     310          $record = $DB->get_record(core_testable_persistent::TABLE, array('id' => $p->get('id')), '*', MUST_EXIST);
     311          $expected = $p->to_record();
     312          $this->assertTrue(isset($p->beforecreate));
     313          $this->assertTrue(isset($p->aftercreate));
     314          $this->assertEquals($expected->sortorder, $record->sortorder);
     315          $this->assertEquals($expected->idnumber, $record->idnumber);
     316          $this->assertEquals($expected->id, $record->id);
     317          $this->assertTrue($p->is_valid()); // Should always be valid after a create.
     318      }
     319  
     320      public function test_update() {
     321          global $DB;
     322          $p = new core_testable_persistent(0, (object) array('sortorder' => 123, 'idnumber' => 'abc'));
     323          $p->create();
     324          $id = $p->get('id');
     325          $p->set('sortorder', 456);
     326          $p->from_record((object) array('idnumber' => 'def'));
     327          $this->assertFalse(isset($p->beforeupdate));
     328          $this->assertFalse(isset($p->afterupdate));
     329          $p->update();
     330  
     331          $expected = $p->to_record();
     332          $record = $DB->get_record(core_testable_persistent::TABLE, array('id' => $p->get('id')), '*', MUST_EXIST);
     333          $this->assertTrue(isset($p->beforeupdate));
     334          $this->assertTrue(isset($p->afterupdate));
     335          $this->assertEquals($id, $record->id);
     336          $this->assertEquals(456, $record->sortorder);
     337          $this->assertEquals('def', $record->idnumber);
     338          $this->assertTrue($p->is_valid()); // Should always be valid after an update.
     339      }
     340  
     341      public function test_save() {
     342          global $DB;
     343          $p = new core_testable_persistent(0, (object) array('sortorder' => 123, 'idnumber' => 'abc'));
     344          $this->assertFalse(isset($p->beforecreate));
     345          $this->assertFalse(isset($p->aftercreate));
     346          $this->assertFalse(isset($p->beforeupdate));
     347          $this->assertFalse(isset($p->beforeupdate));
     348          $p->save();
     349          $record = $DB->get_record(core_testable_persistent::TABLE, array('id' => $p->get('id')), '*', MUST_EXIST);
     350          $expected = $p->to_record();
     351          $this->assertTrue(isset($p->beforecreate));
     352          $this->assertTrue(isset($p->aftercreate));
     353          $this->assertFalse(isset($p->beforeupdate));
     354          $this->assertFalse(isset($p->beforeupdate));
     355          $this->assertEquals($expected->sortorder, $record->sortorder);
     356          $this->assertEquals($expected->idnumber, $record->idnumber);
     357          $this->assertEquals($expected->id, $record->id);
     358          $this->assertTrue($p->is_valid()); // Should always be valid after a save/create.
     359  
     360          $p->set('idnumber', 'abcd');
     361          $p->save();
     362          $record = $DB->get_record(core_testable_persistent::TABLE, array('id' => $p->get('id')), '*', MUST_EXIST);
     363          $expected = $p->to_record();
     364          $this->assertTrue(isset($p->beforeupdate));
     365          $this->assertTrue(isset($p->beforeupdate));
     366          $this->assertEquals($expected->sortorder, $record->sortorder);
     367          $this->assertEquals($expected->idnumber, $record->idnumber);
     368          $this->assertEquals($expected->id, $record->id);
     369          $this->assertTrue($p->is_valid()); // Should always be valid after a save/update.
     370      }
     371  
     372      public function test_read() {
     373          $p = new core_testable_persistent(0, (object) array('sortorder' => 123, 'idnumber' => 'abc'));
     374          $p->create();
     375          unset($p->beforevalidate);
     376          unset($p->beforecreate);
     377          unset($p->aftercreate);
     378  
     379          $p2 = new core_testable_persistent($p->get('id'));
     380          $this->assertEquals($p, $p2);
     381  
     382          $p3 = new core_testable_persistent();
     383          $p3->set('id', $p->get('id'));
     384          $p3->read();
     385          $this->assertEquals($p, $p3);
     386      }
     387  
     388      public function test_delete() {
     389          global $DB;
     390  
     391          $p = new core_testable_persistent(0, (object) array('sortorder' => 123, 'idnumber' => 'abc'));
     392          $p->create();
     393          $this->assertNotEquals(0, $p->get('id'));
     394          $this->assertTrue($DB->record_exists_select(core_testable_persistent::TABLE, 'id = ?', array($p->get('id'))));
     395          $this->assertFalse(isset($p->beforedelete));
     396          $this->assertFalse(isset($p->afterdelete));
     397  
     398          $p->delete();
     399          $this->assertFalse($DB->record_exists_select(core_testable_persistent::TABLE, 'id = ?', array($p->get('id'))));
     400          $this->assertEquals(0, $p->get('id'));
     401          $this->assertEquals(true, $p->beforedelete);
     402          $this->assertEquals(true, $p->afterdelete);
     403      }
     404  
     405      public function test_has_property() {
     406          $this->assertFalse(core_testable_persistent::has_property('unknown'));
     407          $this->assertTrue(core_testable_persistent::has_property('idnumber'));
     408      }
     409  
     410      public function test_custom_setter_getter() {
     411          global $DB;
     412  
     413          $path = array(1, 2, 3);
     414          $json = json_encode($path);
     415  
     416          $p = new core_testable_persistent(0, (object) array('sortorder' => 0, 'idnumber' => 'abc'));
     417          $p->set('path', $path);
     418          $this->assertEquals($path, $p->get('path'));
     419          $this->assertEquals($json, $p->to_record()->path);
     420  
     421          $p->create();
     422          $record = $DB->get_record(core_testable_persistent::TABLE, array('id' => $p->get('id')), 'id, path', MUST_EXIST);
     423          $this->assertEquals($json, $record->path);
     424      }
     425  
     426      public function test_record_exists() {
     427          global $DB;
     428          $this->assertFalse($DB->record_exists(core_testable_persistent::TABLE, array('idnumber' => 'abc')));
     429          $p = new core_testable_persistent(0, (object) array('sortorder' => 123, 'idnumber' => 'abc'));
     430          $p->create();
     431          $id = $p->get('id');
     432          $this->assertTrue(core_testable_persistent::record_exists($id));
     433          $this->assertTrue($DB->record_exists(core_testable_persistent::TABLE, array('idnumber' => 'abc')));
     434          $p->delete();
     435          $this->assertFalse(core_testable_persistent::record_exists($id));
     436      }
     437  
     438      public function test_get_sql_fields() {
     439          $expected = '' .
     440              'c.id AS prefix_id, ' .
     441              'c.shortname AS prefix_shortname, ' .
     442              'c.idnumber AS prefix_idnumber, ' .
     443              'c.description AS prefix_description, ' .
     444              'c.descriptionformat AS prefix_descriptionformat, ' .
     445              'c.parentid AS prefix_parentid, ' .
     446              'c.path AS prefix_path, ' .
     447              'c.sortorder AS prefix_sortorder, ' .
     448              'c.scaleid AS prefix_scaleid, ' .
     449              'c.timecreated AS prefix_timecreated, ' .
     450              'c.timemodified AS prefix_timemodified, ' .
     451              'c.usermodified AS prefix_usermodified';
     452          $this->assertEquals($expected, core_testable_persistent::get_sql_fields('c', 'prefix_'));
     453      }
     454  
     455      public function test_get_sql_fields_too_long() {
     456          $this->expectException(coding_exception::class);
     457          $this->expectExceptionMessageMatches('/The alias .+ exceeds 30 characters/');
     458          core_testable_persistent::get_sql_fields('c');
     459      }
     460  }
     461  
     462  /**
     463   * Example persistent class.
     464   *
     465   * @package    core
     466   * @copyright  2015 Frédéric Massart - FMCorz.net
     467   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
     468   */
     469  class core_testable_persistent extends \core\persistent {
     470  
     471      const TABLE = 'phpunit_persistent';
     472  
     473      protected static function define_properties() {
     474          return array(
     475              'shortname' => array(
     476                  'type' => PARAM_TEXT,
     477                  'default' => ''
     478              ),
     479              'idnumber' => array(
     480                  'type' => PARAM_TEXT,
     481              ),
     482              'description' => array(
     483                  'type' => PARAM_TEXT,
     484                  'default' => ''
     485              ),
     486              'descriptionformat' => array(
     487                  'choices' => array(FORMAT_HTML, FORMAT_MOODLE, FORMAT_PLAIN, FORMAT_MARKDOWN),
     488                  'type' => PARAM_INT,
     489                  'default' => FORMAT_HTML
     490              ),
     491              'parentid' => array(
     492                  'type' => PARAM_INT,
     493                  'default' => 0
     494              ),
     495              'path' => array(
     496                  'type' => PARAM_RAW,
     497                  'default' => ''
     498              ),
     499              'sortorder' => array(
     500                  'type' => PARAM_INT,
     501                  'message' => new lang_string('invalidrequest', 'error')
     502              ),
     503              'scaleid' => array(
     504                  'type' => PARAM_INT,
     505                  'default' => null,
     506                  'null' => NULL_ALLOWED
     507              )
     508          );
     509      }
     510  
     511      protected function before_validate() {
     512          $this->beforevalidate = true;
     513      }
     514  
     515      protected function before_create() {
     516          $this->beforecreate = true;
     517      }
     518  
     519      protected function before_update() {
     520          $this->beforeupdate = true;
     521      }
     522  
     523      protected function before_delete() {
     524          $this->beforedelete = true;
     525      }
     526  
     527      protected function after_create() {
     528          $this->aftercreate = true;
     529      }
     530  
     531      protected function after_update($result) {
     532          $this->afterupdate = true;
     533      }
     534  
     535      protected function after_delete($result) {
     536          $this->afterdelete = true;
     537      }
     538  
     539      protected function get_path() {
     540          $value = $this->raw_get('path');
     541          if (!empty($value)) {
     542              $value = json_decode($value);
     543          }
     544          return $value;
     545      }
     546  
     547      protected function set_path($value) {
     548          if (!empty($value)) {
     549              $value = json_encode($value);
     550          }
     551          $this->raw_set('path', $value);
     552      }
     553  
     554      protected function validate_sortorder($value) {
     555          if ($value == 10) {
     556              return new lang_string('invalidkey', 'error');
     557          }
     558          return true;
     559      }
     560  
     561  }