From 5c92405d1852ae37fc0804808cf17f245cd7497c Mon Sep 17 00:00:00 2001 From: "John Paul E. Balandan, CPA" Date: Wed, 18 Nov 2020 23:40:34 +0800 Subject: [PATCH 1/4] Split model testing --- composer.json | 3 +- .../Models/WithoutAutoincrementModel.php | 4 +- tests/system/Database/Live/ModelTest.php | 2712 ----------------- tests/system/Models/CountAllModelTest.php | 97 + tests/system/Models/DeleteModelTest.php | 202 ++ tests/system/Models/EventsModelTest.php | 161 + tests/system/Models/FindModelTest.php | 303 ++ tests/system/Models/GeneralModelTest.php | 142 + tests/system/Models/InsertModelTest.php | 221 ++ tests/system/Models/LiveModelTestCase.php | 54 + .../system/Models/MiscellaneousModelTest.php | 84 + tests/system/Models/PaginateModelTest.php | 61 + tests/system/Models/SaveModelTest.php | 310 ++ tests/system/Models/UpdateModelTest.php | 289 ++ tests/system/Models/ValidationModelTest.php | 322 ++ 15 files changed, 2251 insertions(+), 2714 deletions(-) delete mode 100644 tests/system/Database/Live/ModelTest.php create mode 100644 tests/system/Models/CountAllModelTest.php create mode 100644 tests/system/Models/DeleteModelTest.php create mode 100644 tests/system/Models/EventsModelTest.php create mode 100644 tests/system/Models/FindModelTest.php create mode 100644 tests/system/Models/GeneralModelTest.php create mode 100644 tests/system/Models/InsertModelTest.php create mode 100644 tests/system/Models/LiveModelTestCase.php create mode 100644 tests/system/Models/MiscellaneousModelTest.php create mode 100644 tests/system/Models/PaginateModelTest.php create mode 100644 tests/system/Models/SaveModelTest.php create mode 100644 tests/system/Models/UpdateModelTest.php create mode 100644 tests/system/Models/ValidationModelTest.php diff --git a/composer.json b/composer.json index cea7f9cccecf..412352408c03 100644 --- a/composer.json +++ b/composer.json @@ -36,7 +36,8 @@ }, "autoload-dev": { "psr-4": { - "Utils\\": "utils" + "CodeIgniter\\": "tests/system/", + "Utils\\": "utils/" } }, "scripts": { diff --git a/tests/_support/Models/WithoutAutoincrementModel.php b/tests/_support/Models/WithoutAutoincrementModel.php index af223ffd2798..76640a5d4ce9 100644 --- a/tests/_support/Models/WithoutAutoincrementModel.php +++ b/tests/_support/Models/WithoutAutoincrementModel.php @@ -1,4 +1,6 @@ -model = new Model($this->db); - } - - //-------------------------------------------------------------------- - - public function tearDown(): void - { - parent::tearDown(); - - Services::reset(); - Config::reset(); - } - - //-------------------------------------------------------------------- - - public function testFindReturnsRow() - { - $model = new JobModel($this->db); - - $job = $model->find(4); - - $this->assertEquals('Musician', $job->name); - } - - //-------------------------------------------------------------------- - - public function testFindReturnsMultipleRows() - { - $model = new JobModel($this->db); - - $job = $model->find([1, 4]); - - $this->assertEquals('Developer', $job[0]->name); - $this->assertEquals('Musician', $job[1]->name); - } - - //-------------------------------------------------------------------- - - public function testGetColumnWithStringColumnName() - { - $model = new JobModel($this->db); - - $job = $model->findColumn('name'); - - $this->assertEquals('Developer', $job[0]); - $this->assertEquals('Politician', $job[1]); - $this->assertEquals('Accountant', $job[2]); - $this->assertEquals('Musician', $job[3]); - } - - //-------------------------------------------------------------------- - - public function testGetColumnsWithMultipleColumnNames() - { - $model = new JobModel($this->db); - - $this->expectException(DataException::class); - $this->expectExceptionMessage('Only single column allowed in Column name.'); - - $job = $model->findColumn('name,description'); - } - - //-------------------------------------------------------------------- - - public function testFindActsAsGetWithNoParams() - { - $model = new JobModel($this->db); - - $jobs = $model->asArray() - ->find(); - - $this->assertCount(4, $jobs); - - $names = array_column($jobs, 'name'); - $this->assertTrue(in_array('Developer', $names)); - $this->assertTrue(in_array('Politician', $names)); - $this->assertTrue(in_array('Accountant', $names)); - $this->assertTrue(in_array('Musician', $names)); - } - - //-------------------------------------------------------------------- - - public function testFindRespectsReturnArray() - { - $model = new JobModel($this->db); - - $job = $model->asArray() - ->find(4); - - $this->assertIsArray($job); - } - - //-------------------------------------------------------------------- - - public function testFindRespectsReturnObject() - { - $model = new JobModel($this->db); - - $job = $model->asObject() - ->find(4); - - $this->assertIsObject($job); - } - - //-------------------------------------------------------------------- - - public function testFindRespectsSoftDeletes() - { - $this->db->table('user') - ->where('id', 4) - ->update(['deleted_at' => date('Y-m-d H:i:s')]); - - $model = new UserModel($this->db); - - $user = $model->asObject() - ->find(4); - - $this->assertEmpty($user); - - $user = $model->withDeleted() - ->find(4); - - // fix for PHP7.2 - $count = is_array($user) ? count($user) : 1; - $this->assertEquals(1, $count); - } - - //-------------------------------------------------------------------- - - public function testFindClearsBinds() - { - $model = new JobModel($this->db); - - $model->find(1); - $model->find(1); - - // Binds should be reset to 0 after each one - $binds = $model->builder() - ->getBinds(); - $this->assertCount(0, $binds); - - $query = $model->getLastQuery(); - $this->assertCount(1, $this->getPrivateProperty($query, 'binds')); - } - - //-------------------------------------------------------------------- - - public function testFindAllReturnsAllRecords() - { - $model = new UserModel($this->db); - - $users = $model->findAll(); - - $this->assertCount(4, $users); - } - - //-------------------------------------------------------------------- - - public function testFindAllRespectsLimits() - { - $model = new UserModel($this->db); - - $users = $model->findAll(2); - - $this->assertCount(2, $users); - $this->assertEquals('Derek Jones', $users[0]->name); - } - - //-------------------------------------------------------------------- - - public function testFindAllRespectsLimitsAndOffset() - { - $model = new UserModel($this->db); - - $users = $model->findAll(2, 2); - - $this->assertCount(2, $users); - $this->assertEquals('Richard A Causey', $users[0]->name); - } - - //-------------------------------------------------------------------- - - public function testFindAllRespectsSoftDeletes() - { - $this->db->table('user') - ->where('id', 4) - ->update(['deleted_at' => date('Y-m-d H:i:s')]); - - $model = new UserModel($this->db); - - $user = $model->findAll(); - - $this->assertCount(3, $user); - - $user = $model->withDeleted() - ->findAll(); - - $this->assertCount(4, $user); - } - - //-------------------------------------------------------------------- - - public function testFirst() - { - $model = new UserModel(); - - $user = $model->where('id >', 2) - ->first(); - - // fix for PHP7.2 - $count = is_array($user) ? count($user) : 1; - $this->assertEquals(1, $count); - $this->assertEquals(3, $user->id); - } - - //-------------------------------------------------------------------- - - public function provideGroupBy() - { - return [ - [ - true, - 3, - ], - [ - false, - 7, - ], - ]; - } - - /** - * @dataProvider provideGroupBy - */ - public function testFirstAggregate($groupBy, $total) - { - $model = new UserModel(); - - if ($groupBy) - { - $model->groupBy('id'); - } - - $user = $model->select('SUM(id) as total') - ->where('id >', 2) - ->first(); - - $this->assertEquals($total, $user->total); - } - - //-------------------------------------------------------------------- - - public function provideAggregateAndGroupBy() - { - return [ - [ - true, - true, - ], - [ - false, - false, - ], - [ - true, - false, - ], - [ - false, - true, - ], - ]; - } - - /** - * @dataProvider provideAggregateAndGroupBy - */ - public function testFirstRespectsSoftDeletes($aggregate, $groupBy) - { - $this->db->table('user') - ->where('id', 1) - ->update(['deleted_at' => date('Y-m-d H:i:s')]); - - $model = new UserModel(); - if ($aggregate) - { - $model->select('SUM(id) as id'); - } - - if ($groupBy) - { - $model->groupBy('id'); - } - - $user = $model->first(); - - if (! $aggregate || $groupBy) - { - // fix for PHP7.2 - $count = is_array($user) ? count($user) : 1; - $this->assertEquals(1, $count); - $this->assertEquals(2, $user->id); - } - else - { - $this->assertEquals(9, $user->id); - } - - $user = $model->withDeleted() - ->first(); - - $this->assertEquals(1, $user->id); - } - - //-------------------------------------------------------------------- - - public function testFirstWithNoPrimaryKey() - { - $model = new SecondaryModel(); - - $this->db->table('secondary') - ->insert([ - - 'key' => 'foo', - 'value' => 'bar', - ]); - - $this->db->table('secondary') - ->insert([ - - 'key' => 'bar', - 'value' => 'baz', - ]); - - $record = $model->first(); - - $this->assertInstanceOf('stdClass', $record); - $this->assertEquals('foo', $record->key); - } - - //-------------------------------------------------------------------- - - public function testSaveNewRecordObject() - { - $model = new JobModel(); - - $data = new \stdClass(); - $data->name = 'Magician'; - $data->description = 'Makes peoples things dissappear.'; - - $model->protect(false) - ->save($data); - - $this->seeInDatabase('job', ['name' => 'Magician']); - } - - //-------------------------------------------------------------------- - - public function testSaveNewRecordArray() - { - $model = new JobModel(); - - $data = [ - 'name' => 'Apprentice', - 'description' => 'That thing you do.', - ]; - - $model->protect(false) - ->save($data); - - $this->seeInDatabase('job', ['name' => 'Apprentice']); - } - - //-------------------------------------------------------------------- - - public function testSaveNewRecordArrayFail() - { - $this->setPrivateProperty($this->db, 'DBDebug', false); - - $model = new JobModel(); - - $data = [ - 'name123' => 'Apprentice', - 'description' => 'That thing you do.', - ]; - - $result = $model->protect(false) - ->save($data); - - $this->assertFalse($result); - - $this->dontSeeInDatabase('job', ['name' => 'Apprentice']); - } - - //-------------------------------------------------------------------- - - public function testSaveUpdateRecordArray() - { - $model = new JobModel(); - - $data = [ - 'name' => 'Apprentice', - 'description' => 'That thing you do.', - ]; - - $result = $model->protect(false) - ->save($data); - - $this->seeInDatabase('job', ['name' => 'Apprentice']); - $this->assertTrue($result); - } - - //-------------------------------------------------------------------- - - public function testSaveUpdateRecordArrayFail() - { - $this->setPrivateProperty($this->db, 'DBDebug', false); - - $model = new JobModel(); - - $data = [ - 'id' => 1, - 'name123' => 'Apprentice', - 'description' => 'That thing you do.', - ]; - - $result = $model->protect(false) - ->save($data); - - $this->assertFalse($result); - - $this->dontSeeInDatabase('job', ['name' => 'Apprentice']); - } - - //-------------------------------------------------------------------- - - public function testSaveUpdateRecordObject() - { - $model = new JobModel(); - - $data = new \stdClass(); - - // SQLSRV does not allow forcing an ID into an autoincrement field. - if ($this->db->DBDriver !== 'SQLSRV') - { - $data->id = 1; - } - - $data->name = 'Engineer'; - $data->description = 'A fancier term for Developer.'; - - $result = $model->protect(false) - ->save($data); - - $this->seeInDatabase('job', ['name' => 'Engineer']); - $this->assertTrue($result); - } - - //-------------------------------------------------------------------- - - public function testSaveProtected() - { - $model = new JobModel(); - - $data = new \stdClass(); - $data->id = 1; - $data->name = 'Engineer'; - $data->description = 'A fancier term for Developer.'; - $data->random_thing = 'Something wicked'; // If not protected, this would kill the script. - - $result = $model->protect(true) - ->save($data); - - $this->assertTrue($result); - } - - //-------------------------------------------------------------------- - - public function testDeleteBasics() - { - $model = new JobModel(); - - $this->seeInDatabase('job', ['name' => 'Developer']); - - $result = $model->delete(1); - $this->assertTrue($result->resultID !== false); - - $this->dontSeeInDatabase('job', ['name' => 'Developer']); - } - - //-------------------------------------------------------------------- - - public function testDeleteFail() - { - $this->setPrivateProperty($this->db, 'DBDebug', false); - - $model = new JobModel(); - - $this->seeInDatabase('job', ['name' => 'Developer']); - - $result = $model->where('name123', 'Developer')->delete(); - $this->assertFalse($result->resultID); - - $this->seeInDatabase('job', ['name' => 'Developer']); - } - - //-------------------------------------------------------------------- - - public function testDeleteStringPrimaryKey() - { - $model = new StringifyPkeyModel(); - - $this->seeInDatabase('stringifypkey', ['value' => 'test']); - - $model->delete('A01'); - - $this->dontSeeInDatabase('stringifypkey', ['value' => 'test']); - } - - //-------------------------------------------------------------------- - - public function testDeleteWithSoftDeletes() - { - $model = new UserModel(); - - $this->seeInDatabase('user', ['name' => 'Derek Jones', 'deleted_at IS NULL' => null]); - - $result = $model->delete(1); - $this->assertTrue($result); - - $this->seeInDatabase('user', ['name' => 'Derek Jones', 'deleted_at IS NOT NULL' => null]); - } - - //-------------------------------------------------------------------- - - public function testDeleteWithSoftDeleteFail() - { - $this->setPrivateProperty($this->db, 'DBDebug', false); - - $model = new UserModel(); - - $this->seeInDatabase('user', ['name' => 'Derek Jones', 'deleted_at IS NULL' => null]); - - $result = $model->where('name123', 'Derek Jones')->delete(); - $this->assertFalse($result); - - $this->seeInDatabase('user', ['name' => 'Derek Jones', 'deleted_at IS NULL' => null]); - } - - //-------------------------------------------------------------------- - - public function testDeleteWithSoftDeletesPurge() - { - $model = new UserModel(); - - $this->seeInDatabase('user', ['name' => 'Derek Jones', 'deleted_at IS NULL' => null]); - - $model->delete(1, true); - - $this->dontSeeInDatabase('user', ['name' => 'Derek Jones']); - } - - //-------------------------------------------------------------------- - - public function testDeleteMultiple() - { - $model = new JobModel(); - - $this->seeInDatabase('job', ['name' => 'Developer']); - $this->seeInDatabase('job', ['name' => 'Politician']); - - $model->delete([1, 2]); - - $this->dontSeeInDatabase('job', ['name' => 'Developer']); - $this->dontSeeInDatabase('job', ['name' => 'Politician']); - $this->seeInDatabase('job', ['name' => 'Accountant']); - } - - //-------------------------------------------------------------------- - - public function testDeleteNoParams() - { - $model = new JobModel(); - - $this->seeInDatabase('job', ['name' => 'Developer']); - - $model->where('id', 1) - ->delete(); - - $this->dontSeeInDatabase('job', ['name' => 'Developer']); - } - - //-------------------------------------------------------------------- - - public function testPurgeDeleted() - { - $model = new UserModel(); - - $this->db->table('user') - ->where('id', 1) - ->update(['deleted_at' => date('Y-m-d H:i:s')]); - - $model->purgeDeleted(); - - $users = $model->withDeleted() - ->findAll(); - - $this->assertCount(3, $users); - } - - //-------------------------------------------------------------------- - - public function testOnlyDeleted() - { - $model = new UserModel($this->db); - - $this->db->table('user') - ->where('id', 1) - ->update(['deleted_at' => date('Y-m-d H:i:s')]); - - $users = $model->onlyDeleted() - ->findAll(); - - $this->assertCount(1, $users); - } - /** - * If where condition is set, beyond the value was empty (0,'', NULL, etc.), - * Exception should not be thrown because condition was explicity set - * - * @dataProvider emptyPkValues - * @return void - */ - public function testDontThrowExceptionWhenSoftDeleteConditionIsSetWithEmptyValue($emptyValue) - { - $model = new UserModel(); - $this->seeInDatabase('user', ['name' => 'Derek Jones', 'deleted_at IS NULL' => null]); - $model->where('id', $emptyValue)->delete(); - $this->seeInDatabase('user', ['name' => 'Derek Jones', 'deleted_at IS NULL' => null]); - unset($model); - } //-------------------------------------------------------------------- - - /** - * @dataProvider emptyPkValues - * @return void - */ - public function testThrowExceptionWhenSoftDeleteParamIsEmptyValue($emptyValue) - { - $this->expectException('CodeIgniter\Database\Exceptions\DatabaseException'); - $this->expectExceptionMessage('Deletes are not allowed unless they contain a "where" or "like" clause.'); - - $model = new UserModel(); - $this->seeInDatabase('user', ['name' => 'Derek Jones', 'deleted_at IS NULL' => null]); - $model->delete($emptyValue); - } - - //-------------------------------------------------------------------- - - /** - * @dataProvider emptyPkValues - * @return void - */ - public function testDontDeleteRowsWhenSoftDeleteParamIsEmpty($emptyValue) - { - $this->expectException('CodeIgniter\Database\Exceptions\DatabaseException'); - $this->expectExceptionMessage('Deletes are not allowed unless they contain a "where" or "like" clause.'); - - $model = new UserModel(); - $this->seeInDatabase('user', ['name' => 'Derek Jones', 'deleted_at IS NULL' => null]); - $model->delete($emptyValue); - $this->seeInDatabase('user', ['name' => 'Derek Jones', 'deleted_at IS NULL' => null]); - unset($model); - } - - public function emptyPkValues() - { - return [ - [0], - [null], - ['0'], - ]; - } - //-------------------------------------------------------------------- - - public function testChunk() - { - $model = new UserModel(); - - $rowCount = 0; - - $model->chunk(2, function ($row) use (&$rowCount) { - $rowCount++; - }); - - $this->assertEquals(4, $rowCount); - } - - //-------------------------------------------------------------------- - - public function testValidationBasics() - { - $model = new ValidModel($this->db); - - $data = [ - 'name' => null, - 'description' => 'some great marketing stuff', - ]; - - $this->assertFalse($model->insert($data)); - - $errors = $model->errors(); - - $this->assertEquals('You forgot to name the baby.', $errors['name']); - } - - //-------------------------------------------------------------------- - - public function testValidationWithSetValidationRule() - { - $model = new ValidModel($this->db); - - $data = [ - 'name' => 'some name', - 'description' => 'some great marketing stuff', - ]; - - $model->setValidationRule('description', [ - 'rules' => 'required|min_length[50]', - 'errors' => [ - 'min_length' => 'Description is too short baby.', - ], - ]); - $this->assertFalse($model->insert($data)); - - $errors = $model->errors(); - - $this->assertEquals('Description is too short baby.', $errors['description']); - } - - //-------------------------------------------------------------------- - - public function testValidationWithSetValidationRules() - { - $model = new ValidModel($this->db); - - $data = [ - 'name' => '', - 'description' => 'some great marketing stuff', - ]; - - $model->setValidationRules([ - 'name' => [ - 'rules' => 'required', - 'errors' => [ - 'required' => 'Give me a name baby.', - ], - ], - 'description' => [ - 'rules' => 'required|min_length[50]', - 'errors' => [ - 'min_length' => 'Description is too short baby.', - ], - ], - ]); - $this->assertFalse($model->insert($data)); - - $errors = $model->errors(); - - $this->assertEquals('Give me a name baby.', $errors['name']); - $this->assertEquals('Description is too short baby.', $errors['description']); - } - - //-------------------------------------------------------------------- - - public function testValidationWithSetValidationMessage() - { - $model = new ValidModel($this->db); - - $data = [ - 'name' => null, - 'description' => 'some great marketing stuff', - ]; - - $model->setValidationMessage('name', [ - 'required' => 'Your baby name is missing.', - 'min_length' => 'Too short, man!', - ]); - $this->assertFalse($model->insert($data)); - - $errors = $model->errors(); - - $this->assertEquals('Your baby name is missing.', $errors['name']); - } - - //-------------------------------------------------------------------- - - public function testValidationWithSetValidationMessages() - { - $model = new ValidModel($this->db); - - $data = [ - 'name' => null, - 'description' => 'some great marketing stuff', - ]; - - $model->setValidationMessages([ - 'name' => [ - 'required' => 'Your baby name is missing.', - 'min_length' => 'Too short, man!', - ], - ]); - - $this->assertFalse($model->insert($data)); - - $errors = $model->errors(); - - $this->assertEquals('Your baby name is missing.', $errors['name']); - } - //-------------------------------------------------------------------- - - public function testValidationPlaceholdersSuccess() - { - $model = new ValidModel($this->db); - - $data = [ - 'name' => 'abc', - 'id' => 13, - 'token' => 13, - ]; - - $this->assertTrue($model->validate($data)); - } - - //-------------------------------------------------------------------- - - public function testValidationPlaceholdersFail() - { - $model = new ValidModel($this->db); - - $data = [ - 'name' => 'abc', - 'id' => 13, - 'token' => 12, - ]; - - $this->assertFalse($model->validate($data)); - } - - //-------------------------------------------------------------------- - - public function testSkipValidation() - { - $model = new ValidModel($this->db); - - $data = [ - 'name' => '2', - 'description' => 'some great marketing stuff', - ]; - - $this->assertIsNumeric($model->skipValidation(true)->insert($data)); - } - - //-------------------------------------------------------------------- - - public function testCleanValidationRemovesAllWhenNoDataProvided() - { - $model = new Model($this->db); - $cleaner = $this->getPrivateMethodInvoker($model, 'cleanValidationRules'); - - $rules = [ - 'name' => 'required', - 'foo' => 'bar', - ]; - - $rules = $cleaner($rules, null); - - $this->assertEmpty($rules); - } - - //-------------------------------------------------------------------- - - public function testCleanValidationRemovesOnlyForFieldsNotProvided() - { - $model = new Model($this->db); - $cleaner = $this->getPrivateMethodInvoker($model, 'cleanValidationRules'); - - $rules = [ - 'name' => 'required', - 'foo' => 'required', - ]; - - $data = [ - 'foo' => 'bar', - ]; - - $rules = $cleaner($rules, $data); - - $this->assertTrue(array_key_exists('foo', $rules)); - $this->assertFalse(array_key_exists('name', $rules)); - } - - //-------------------------------------------------------------------- - - public function testCleanValidationReturnsAllWhenAllExist() - { - $model = new Model($this->db); - $cleaner = $this->getPrivateMethodInvoker($model, 'cleanValidationRules'); - - $rules = [ - 'name' => 'required', - 'foo' => 'required', - ]; - - $data = [ - 'foo' => 'bar', - 'name' => null, - ]; - - $rules = $cleaner($rules, $data); - - $this->assertTrue(array_key_exists('foo', $rules)); - $this->assertTrue(array_key_exists('name', $rules)); - } - - //-------------------------------------------------------------------- - - public function testValidationPassesWithMissingFields() - { - $model = new ValidModel(); - - $data = [ - 'foo' => 'bar', - ]; - - $result = $model->validate($data); - - $this->assertTrue($result); - } - - //-------------------------------------------------------------------- - - public function testValidationWithGroupName() - { - $config = new \Config\Validation(); - $config->grouptest = [ - 'name' => [ - 'required', - 'min_length[3]', - ], - 'token' => 'in_list[{id}]', - ]; - - $data = [ - 'name' => 'abc', - 'id' => 13, - 'token' => 13, - ]; - - \CodeIgniter\Config\Config::injectMock('Validation', $config); - - $model = new ValidModel($this->db); - $this->setPrivateProperty($model, 'validationRules', 'grouptest'); - - $this->assertTrue($model->validate($data)); - } - - //-------------------------------------------------------------------- - - public function testCanCreateAndSaveEntityClasses() - { - $model = new EntityModel($this->db); - - $entity = $model->where('name', 'Developer') - ->first(); - - $this->assertInstanceOf(SimpleEntity::class, $entity); - $this->assertEquals('Developer', $entity->name); - $this->assertEquals('Awesome job, but sometimes makes you bored', $entity->description); - - $time = time(); - - $entity->name = 'Senior Developer'; - $entity->created_at = $time; - - $this->assertTrue($model->save($entity)); - - $result = $model->where('name', 'Senior Developer') - ->get() - ->getFirstRow(); - - $this->assertEquals(date('Y-m-d', $time), date('Y-m-d', $result->created_at)); - } - - //-------------------------------------------------------------------- - - /** - * @see https://github.com/codeigniter4/CodeIgniter4/issues/580 - */ - public function testPasswordsStoreCorrectly() - { - $model = new UserModel(); - - $pass = password_hash('secret123', PASSWORD_BCRYPT); - - $data = [ - 'name' => $pass, - 'email' => 'foo@example.com', - 'country' => 'US', - ]; - - $model->insert($data); - - $this->seeInDatabase('user', $data); - } - - //-------------------------------------------------------------------- - - public function testInsertEvent() - { - $model = new EventModel(); - - $data = [ - 'name' => 'Foo', - 'email' => 'foo@example.com', - 'country' => 'US', - 'deleted' => 0, - ]; - - $model->insert($data); - - $this->assertTrue($model->hasToken('beforeInsert')); - $this->assertTrue($model->hasToken('afterInsert')); - } - - //-------------------------------------------------------------------- - - public function testUpdateEvent() - { - $model = new EventModel(); - - $data = [ - 'name' => 'Foo', - 'email' => 'foo@example.com', - 'country' => 'US', - 'deleted' => 0, - ]; - - $id = $model->insert($data); - $model->update($id, $data); - - $this->assertTrue($model->hasToken('beforeUpdate')); - $this->assertTrue($model->hasToken('afterUpdate')); - } - - //-------------------------------------------------------------------- - - public function testDeleteEvent() - { - $model = new EventModel(); - - $model->delete(1); - - $this->assertTrue($model->hasToken('beforeDelete')); - $this->assertTrue($model->hasToken('afterDelete')); - } - - //-------------------------------------------------------------------- - - public function testFindEvent() - { - $model = new EventModel(); - - $model->find(1); - - $this->assertTrue($model->hasToken('beforeFind')); - $this->assertTrue($model->hasToken('afterFind')); - } - - public function testBeforeFindReturnsData() - { - $model = new EventModel(); - $model->beforeFindReturnData = true; - - $result = $model->find(1); - - $this->assertTrue($model->hasToken('beforeFind')); - $this->assertEquals($result, 'foobar'); - } - - public function testBeforeFindReturnDataPreventsAfterFind() - { - $model = new EventModel(); - $model->beforeFindReturnData = true; - - $model->find(1); - - $this->assertFalse($model->hasToken('afterFind')); - } - - public function testFindEventSingletons() - { - $model = new EventModel(); - - // afterFind - $model->first(); - $this->assertEquals(true, $model->eventData['singleton']); - - $model->find(1); - $this->assertEquals(true, $model->eventData['singleton']); - - $model->find(); - $this->assertEquals(false, $model->eventData['singleton']); - - $model->findAll(); - $this->assertEquals(false, $model->eventData['singleton']); - - // beforeFind - $model->beforeFindReturnData = true; - - $model->first(); - $this->assertEquals(true, $model->eventData['singleton']); - - $model->find(1); - $this->assertEquals(true, $model->eventData['singleton']); - - $model->find(); - $this->assertEquals(false, $model->eventData['singleton']); - - $model->findAll(); - $this->assertEquals(false, $model->eventData['singleton']); - } - - //-------------------------------------------------------------------- - - public function testAllowCallbacksFalsePreventsTriggers() - { - $model = new EventModel(); - - $model->allowCallbacks(false)->find(1); - - $this->assertFalse($model->hasToken('afterFind')); - } - - //-------------------------------------------------------------------- - - public function testAllowCallbacksTrueFiresTriggers() - { - $model = new EventModel(); - $this->setPrivateProperty($model, 'allowCallbacks', false); - - $model->allowCallbacks(true)->find(1); - - $this->assertTrue($model->hasToken('afterFind')); - } - - //-------------------------------------------------------------------- - - public function testAllowCallbacksResetsAfterTrigger() - { - $model = new EventModel(); - - $model->allowCallbacks(false)->find(1); - $model->delete(1); - - $this->assertFalse($model->hasToken('afterFind')); - $this->assertTrue($model->hasToken('afterDelete')); - } - - //-------------------------------------------------------------------- - - public function testAllowCallbacksUsesModelProperty() - { - $model = new EventModel(); - $this->setPrivateProperty($model, 'allowCallbacks', false); - $this->setPrivateProperty($model, 'tempAllowCallbacks', false); // Was already set by the constructor - - $model->find(1); - $model->delete(1); - - $this->assertFalse($model->hasToken('afterFind')); - $this->assertFalse($model->hasToken('afterDelete')); - } - - //-------------------------------------------------------------------- - - public function testSetWorksWithInsert() - { - $model = new EventModel(); - - $this->dontSeeInDatabase('user', [ - 'email' => 'foo@example.com', - ]); - - $model->set([ - 'email' => 'foo@example.com', - 'name' => 'Foo Bar', - 'country' => 'US', - ]) - ->insert(); - - $this->seeInDatabase('user', [ - 'email' => 'foo@example.com', - ]); - } - - //-------------------------------------------------------------------- - - public function testSetWorksWithUpdate() - { - $model = new EventModel(); - - $this->dontSeeInDatabase('user', [ - 'email' => 'foo@example.com', - ]); - - $userId = $model->insert([ - 'email' => 'foo@example.com', - 'name' => 'Foo Bar', - 'country' => 'US', - ]); - - $model->set([ - 'name' => 'Fred Flintstone', - ]) - ->update($userId); - - $this->seeInDatabase('user', [ - 'id' => $userId, - 'email' => 'foo@example.com', - 'name' => 'Fred Flintstone', - ]); - } - - //-------------------------------------------------------------------- - - public function testSetWorksWithUpdateNoId() - { - $model = new EventModel(); - - $this->dontSeeInDatabase('user', [ - 'email' => 'foo@example.com', - ]); - - $userId = $model->insert([ - 'email' => 'foo@example.com', - 'name' => 'Foo Bar', - 'country' => 'US', - ]); - - $model - ->where('id', $userId) - ->set([ - 'name' => 'Fred Flintstone', - ]) - ->update(); - - $this->seeInDatabase('user', [ - 'id' => $userId, - 'email' => 'foo@example.com', - 'name' => 'Fred Flintstone', - ]); - } - - //-------------------------------------------------------------------- - - public function testUpdateArray() - { - $model = new EventModel(); - - $data = [ - 'name' => 'Foo', - 'email' => 'foo@example.com', - 'country' => 'US', - 'deleted' => 0, - ]; - - $id = $model->insert($data); - $result = $model->update([1, 2], ['name' => 'Foo Bar']); - - $this->assertTrue($result); - - $this->seeInDatabase('user', ['id' => 1, 'name' => 'Foo Bar']); - $this->seeInDatabase('user', ['id' => 2, 'name' => 'Foo Bar']); - } - - //-------------------------------------------------------------------- - - public function testUpdateResultFail() - { - $this->setPrivateProperty($this->db, 'DBDebug', false); - - $model = new EventModel(); - - $data = [ - 'name' => 'Foo', - 'email' => 'foo@example.com', - 'country' => 'US', - 'deleted' => 0, - ]; - - $id = $model->insert($data); - - $this->setPrivateProperty($model, 'allowedFields', ['name123']); - $result = $model->update(1, ['name123' => 'Foo Bar 1']); - - $this->assertFalse($result); - - $this->dontSeeInDatabase('user', ['id' => 1, 'name' => 'Foo Bar 1']); - } - - //-------------------------------------------------------------------- - - public function testInsertBatchSuccess() - { - $job_data = [ - [ - 'name' => 'Comedian', - 'description' => 'Theres something in your teeth', - ], - [ - 'name' => 'Cab Driver', - 'description' => 'Iam yellow', - ], - ]; - - $model = new JobModel($this->db); - $model->insertBatch($job_data); - - $this->seeInDatabase('job', ['name' => 'Comedian']); - $this->seeInDatabase('job', ['name' => 'Cab Driver']); - } - - //-------------------------------------------------------------------- - - public function testInsertBatchValidationFail() - { - $job_data = [ - [ - 'name' => 'Comedian', - 'description' => null, - ], - ]; - - $model = new JobModel($this->db); - - $this->setPrivateProperty($model, 'validationRules', ['description' => 'required']); - - $this->assertFalse($model->insertBatch($job_data)); - - $error = $model->errors(); - $this->assertTrue(isset($error['description'])); - } - - //-------------------------------------------------------------------- - - public function testInsertBatchSetsIntTimestamps() - { - $job_data = [ - [ - 'name' => 'Philosopher', - ], - [ - 'name' => 'Laborer', - ], - ]; - - $model = new JobModel($this->db); - $this->setPrivateProperty($model, 'useTimestamps', true); - $this->assertEquals(2, $model->insertBatch($job_data)); - - $result = $model->where('name', 'Philosopher')->first(); - - $this->assertCloseEnough(time(), $result->created_at); - } - - public function testInsertBatchSetsDatetimeTimestamps() - { - $user_data = [ - [ - 'name' => 'Lou', - 'email' => 'lou@example.com', - 'country' => 'Ireland', - ], - [ - 'name' => 'Sue', - 'email' => 'sue@example.com', - 'country' => 'Ireland', - ], - ]; - - $model = new UserModel($this->db); - $this->setPrivateProperty($model, 'useTimestamps', true); - $this->assertEquals(2, $model->insertBatch($user_data)); - - $result = $model->where('name', 'Lou')->first(); - - $this->assertCloseEnough(time(), strtotime($result->created_at)); - } - - //-------------------------------------------------------------------- - - public function testUpdateBatchSuccess() - { - $data = [ - [ - 'name' => 'Derek Jones', - 'country' => 'Greece', - ], - [ - 'name' => 'Ahmadinejad', - 'country' => 'Greece', - ], - ]; - - $model = new EventModel($this->db); - - $model->updateBatch($data, 'name'); - - $this->seeInDatabase('user', [ - 'name' => 'Derek Jones', - 'country' => 'Greece', - ]); - $this->seeInDatabase('user', [ - 'name' => 'Ahmadinejad', - 'country' => 'Greece', - ]); - } - - //-------------------------------------------------------------------- - - public function testUpdateBatchValidationFail() - { - $data = [ - [ - 'name' => 'Derek Jones', - 'country' => null, - ], - ]; - - $model = new EventModel($this->db); - $this->setPrivateProperty($model, 'validationRules', ['country' => 'required']); - - $this->assertFalse($model->updateBatch($data, 'name')); - - $error = $model->errors(); - $this->assertTrue(isset($error['country'])); - } - - //-------------------------------------------------------------------- - - public function testUpdateBatchWithEntity() - { - $entity1 = new class extends Entity - { - protected $id; - protected $name; - protected $email; - protected $country; - protected $deleted; - protected $created_at; - protected $updated_at; - - protected $_options = [ - 'datamap' => [], - 'dates' => [ - 'created_at', - 'updated_at', - 'deleted_at', - ], - 'casts' => [], - ]; - }; - - $entity2 = new class extends Entity - { - protected $id; - protected $name; - protected $email; - protected $country; - protected $deleted; - protected $created_at; - protected $updated_at; - - protected $_options = [ - 'datamap' => [], - 'dates' => [ - 'created_at', - 'updated_at', - 'deleted_at', - ], - 'casts' => [], - ]; - }; - $testModel = new UserModel(); - - $entity1->id = 1; - $entity1->name = 'Jones Martin'; - $entity1->country = 'India'; - $entity1->deleted = 0; - - $entity2->id = 4; - $entity2->name = 'Jones Martin'; - $entity2->country = 'India'; - $entity2->deleted = 0; - - $this->assertEquals(2, $testModel->updateBatch([$entity1, $entity2], 'id')); - } - - //-------------------------------------------------------------------- - - public function testSelectAndEntitiesSaveOnlyChangedValues() - { - // Insert value in job table - $this->hasInDatabase('job', [ - 'name' => 'Rocket Scientist', - 'description' => 'Plays guitar for Queen', - 'created_at' => time(), - ]); - - $model = new EntityModel(); - - // get only id, name column - $job = $model->select('id, name') - ->where('name', 'Rocket Scientist') - ->first(); - - // Hence getting Null as description column not in select clause - $this->assertNull($job->description); - - // Equals with name to check, correct record fetched or not. - $this->assertEquals('Rocket Scientist', $job->name); - - $job->description = 'Some guitar description'; - - // saving the result set with description as empty - $model->save($job); - - // check for the record to same entry exists or not - $this->seeInDatabase('job', [ - 'id' => $job->id, - 'name' => 'Rocket Scientist', - ]); - - // select all columns from job table - $job = $model->select('id, name, description') - ->where('name', 'Rocket Scientist') - ->first(); - - // check whether the Null value successfully updated or not - $this->assertEquals('Some guitar description', $job->description); - } - - //-------------------------------------------------------------------- - - public function testUpdateNoPrimaryKey() - { - $model = new SecondaryModel(); - - $this->db->table('secondary') - ->insert([ - - 'key' => 'foo', - 'value' => 'bar', - ]); - - $this->dontSeeInDatabase('secondary', [ - 'key' => 'bar', - 'value' => 'baz', - ]); - - $model->where('key', 'foo') - ->update(null, ['key' => 'bar', 'value' => 'baz']); - - $this->seeInDatabase('secondary', [ - 'key' => 'bar', - 'value' => 'baz', - ]); - } - - //-------------------------------------------------------------------- - - /** - * @see https://github.com/codeigniter4/CodeIgniter4/issues/1617 - */ - public function testCountAllResultsRespectsSoftDeletes() - { - $model = new UserModel(); - - // testSeeder has 4 users.... - $this->assertEquals(4, $model->countAllResults()); - - $model->where('name', 'Derek Jones') - ->delete(); - - $this->assertEquals(3, $model->countAllResults()); - } - - //-------------------------------------------------------------------- - - /** - * @see https://github.com/codeigniter4/CodeIgniter4/issues/1584 - */ - public function testUpdateWithValidation() - { - $model = new ValidModel($this->db); - - $data = [ - 'description' => 'This is a first test!', - 'name' => 'valid', - 'id' => 42, - 'token' => 42, - ]; - - $id = $model->insert($data); - - $this->assertTrue((bool)$id); - - $data['description'] = 'This is a second test!'; - unset($data['name']); - - $result = $model->update($id, $data); - $this->assertTrue($result); - } - - //-------------------------------------------------------------------- - - /** - * @see https://github.com/codeigniter4/CodeIgniter4/issues/1717 - */ - public function testRequiredWithValidationEmptyString() - { - $model = new ValidModel($this->db); - - $data = [ - 'name' => '', - ]; - - $this->assertFalse($model->insert($data)); - } - - //-------------------------------------------------------------------- - - /** - * @see https://github.com/codeigniter4/CodeIgniter4/issues/1717 - */ - public function testRequiredWithValidationNull() - { - $model = new ValidModel($this->db); - - $data = [ - 'name' => null, - ]; - - $this->assertFalse($model->insert($data)); - } - - //-------------------------------------------------------------------- - - /** - * @see https://github.com/codeigniter4/CodeIgniter4/issues/1717 - */ - public function testRequiredWithValidationTrue() - { - $model = new ValidModel($this->db); - - $data = [ - 'name' => 'foobar', - 'description' => 'just because we have to', - ]; - - $this->assertTrue($model->insert($data) !== false); - } - - //-------------------------------------------------------------------- - - /** - * @see https://github.com/codeigniter4/CodeIgniter4/issues/1574 - */ - public function testValidationIncludingErrors() - { - $model = new ValidErrorsModel($this->db); - - $data = [ - 'description' => 'This is a first test!', - 'name' => 'valid', - 'id' => 42, - 'token' => 42, - ]; - - $id = $model->insert($data); - - $this->assertFalse((bool)$id); - - $errors = $model->errors(); - - $this->assertEquals('Minimum Length Error', $model->errors()['name']); - } - - //-------------------------------------------------------------------- - - public function testThrowsWithNoPrimaryKey() - { - $this->expectException('CodeIgniter\Exceptions\ModelException'); - $this->expectExceptionMessage('`Tests\Support\Models\UserModel` model class does not specify a Primary Key.'); - - $model = new UserModel(); - $this->setPrivateProperty($model, 'primaryKey', ''); - - $model->find(1); - } - - //-------------------------------------------------------------------- - - public function testThrowsWithNoDateFormat() - { - $this->expectException('CodeIgniter\Exceptions\ModelException'); - $this->expectExceptionMessage('`Tests\Support\Models\UserModel` model class does not have a valid dateFormat.'); - - $model = new UserModel(); - $this->setPrivateProperty($model, 'dateFormat', ''); - - $model->delete(1); - } - - //-------------------------------------------------------------------- - - public function testInsertID() - { - $model = new JobModel(); - - $data = [ - 'name' => 'Apprentice', - 'description' => 'That thing you do.', - ]; - - $model->protect(false) - ->save($data); - - $lastInsertId = $model->getInsertID(); - - $this->seeInDatabase('job', ['id' => $lastInsertId]); - } - - //-------------------------------------------------------------------- - - public function testInsertResult() - { - $model = new JobModel(); - - $data = [ - 'name' => 'Apprentice', - 'description' => 'That thing you do.', - ]; - - $result = $model->protect(false) - ->insert($data, false); - - $this->assertTrue($result->resultID !== false); - - $lastInsertId = $model->getInsertID(); - - $this->seeInDatabase('job', ['id' => $lastInsertId]); - } - - //-------------------------------------------------------------------- - - public function testInsertResultFail() - { - $this->setPrivateProperty($this->db, 'DBDebug', false); - - $model = new JobModel(); - - $data = [ - 'name123' => 'Apprentice', - 'description' => 'That thing you do.', - ]; - - $result = $model->protect(false) - ->insert($data, false); - - $this->assertFalse($result->resultID); - - $lastInsertId = $model->getInsertID(); - - $this->assertEquals(0, $lastInsertId); - - $this->dontSeeInDatabase('job', ['id' => $lastInsertId]); - } - - //-------------------------------------------------------------------- - - public function testSetTable() - { - $model = new SecondaryModel(); - - $model->setTable('job'); - - $data = [ - 'name' => 'Apprentice', - 'description' => 'That thing you do.', - ]; - - $model->protect(false) - ->save($data); - - $lastInsertId = $model->getInsertID(); - - $this->seeInDatabase('job', ['id' => $lastInsertId]); - } - - //-------------------------------------------------------------------- - - public function testPaginate() - { - $model = new ValidModel($this->db); - - $data = $model->paginate(); - - $this->assertEquals(4, count($data)); - } - - //-------------------------------------------------------------------- - - public function testPaginateChangeConfigPager() - { - $perPage = config('Pager')->perPage; - config('Pager')->perPage = 1; - - $model = new ValidModel($this->db); - - $data = $model->paginate(); - - $this->assertEquals(1, count($data)); - - config('Pager')->perPage = $perPage; - } - - //-------------------------------------------------------------------- - - public function testPaginatePassPerPageParameter() - { - $model = new ValidModel($this->db); - - $data = $model->paginate(2); - - $this->assertEquals(2, count($data)); - } - - //-------------------------------------------------------------------- - - public function testPaginateForQueryWithGroupBy() - { - $model = new ValidModel($this->db); - $model->groupBy('id'); - - $model->paginate(); - $this->assertEquals(4, $model->pager->getDetails()['total']); - } - - //-------------------------------------------------------------------- - - public function testPaginateWithDeleted() - { - $model = new UserModel($this->db); - $model->delete(1); - - $data = $model->withDeleted()->paginate(); - - $this->assertEquals(4, count($data)); - $this->assertEquals(4, $model->pager->getDetails()['total']); - } - - //-------------------------------------------------------------------- - - public function testPaginateWithoutDeleted() - { - $model = new UserModel($this->db); - $model->delete(1); - - $data = $model->withDeleted(false)->paginate(); - - $this->assertEquals(3, count($data)); - $this->assertEquals(3, $model->pager->getDetails()['total']); - } - - //-------------------------------------------------------------------- - - public function testValidationByObject() - { - $model = new ValidModel($this->db); - - $data = new class - { - public $name = ''; - public $id = ''; - public $token = ''; - }; - - $data->name = 'abc'; - $data->id = '13'; - $data->token = '13'; - - $this->assertTrue($model->validate($data)); - } - - //-------------------------------------------------------------------- - - public function testGetValidationRules() - { - $model = new JobModel($this->db); - - $this->setPrivateProperty($model, 'validationRules', ['description' => 'required']); - - $rules = $model->getValidationRules(); - - $this->assertEquals('required', $rules['description']); - } - - //-------------------------------------------------------------------- - - public function testGetValidationMessages() - { - $job_data = [ - [ - 'name' => 'Comedian', - 'description' => null, - ], - ]; - - $model = new JobModel($this->db); - - $this->setPrivateProperty($model, 'validationRules', ['description' => 'required']); - $this->setPrivateProperty($model, 'validationMessages', ['description' => 'Description field is required.']); - - $this->assertFalse($model->insertBatch($job_data)); - - $error = $model->getValidationMessages(); - $this->assertEquals('Description field is required.', $error['description']); - } - - //-------------------------------------------------------------------- - - public function testGetGetModelDetails() - { - $model = new JobModel($this->db); - - $this->assertEquals('job', $model->table); - $this->assertEquals('id', $model->primaryKey); - $this->assertEquals('object', $model->returnType); - $this->assertNull($model->DBGroup); - } - - //-------------------------------------------------------------------- - - public function testSaveObject() - { - $model = new ValidModel($this->db); - - $testModel = new JobModel(); - - $testModel->name = 'my name'; - $testModel->description = 'some description'; - - $this->setPrivateProperty($model, 'useTimestamps', true); - - $model->insert($testModel); - - $lastInsertId = $model->getInsertID(); - - $this->seeInDatabase('job', ['id' => $lastInsertId]); - } - - //-------------------------------------------------------------------- - - public function testEmptySaveData() - { - $model = new JobModel(); - - $data = []; - - $data = $model->protect(false) - ->save($data); - - $this->assertTrue($data); - } - - //-------------------------------------------------------------------- - - public function testUpdateObject() - { - $model = new ValidModel($this->db); - - $testModel = new JobModel(); - - $testModel->name = 'my name'; - $testModel->description = 'some description'; - - $this->setPrivateProperty($model, 'useTimestamps', true); - - $model->update(1, $testModel); - - $this->seeInDatabase('job', ['id' => 1]); - } - - //-------------------------------------------------------------------- - - public function testDeleteWithSoftDelete() - { - $model = new JobModel(); - - $this->setPrivateProperty($model, 'useTimestamps', true); - $this->setPrivateProperty($model, 'useSoftDeletes', true); - - $model->delete(1); - - $this->seeInDatabase('job', ['id' => 1, 'deleted_at IS NOT NULL' => null]); - } - - //-------------------------------------------------------------------- - - public function testPurgeDeletedWithSoftDeleteFalse() - { - $model = new JobModel(); - - $this->db->table('job') - ->where('id', 1) - ->update(['deleted_at' => time()]); - - $model->purgeDeleted(); - - $jobs = $model->findAll(); - - $this->assertCount(4, $jobs); - } - - //-------------------------------------------------------------------- - - public function testReplaceObject() - { - $model = new ValidModel($this->db); - - $data = [ - 'id' => 1, - 'name' => 'my name', - 'description' => 'some description', - ]; - - $model->replace($data); - - $this->seeInDatabase('job', ['id' => 1, 'name' => 'my name']); - } - - //-------------------------------------------------------------------- - - public function testGetValidationMessagesForReplace() - { - $job_data = [ - 'name' => 'Comedian', - 'description' => null, - ]; - - $model = new JobModel($this->db); - - $this->setPrivateProperty($model, 'validationRules', ['description' => 'required']); - - $this->assertFalse($model->replace($job_data)); - - $error = $model->errors(); - $this->assertTrue(isset($error['description'])); - } - - //-------------------------------------------------------------------- - - public function testInsertBatchNewEntityWithDateTime() - { - $entity = new class extends Entity{ - protected $id; - protected $name; - protected $email; - protected $country; - protected $deleted; - protected $created_at; - protected $updated_at; - - protected $_options = [ - 'datamap' => [], - 'dates' => [ - 'created_at', - 'updated_at', - 'deleted_at', - ], - 'casts' => [], - ]; - }; - $testModel = new UserModel(); - - $entity->name = 'Mark'; - $entity->email = 'mark@example.com'; - $entity->country = 'India'; - $entity->deleted = 0; - $entity->created_at = new Time('now'); - - $this->setPrivateProperty($testModel, 'useTimestamps', true); - - $this->assertEquals(2, $testModel->insertBatch([$entity, $entity])); - } - - //-------------------------------------------------------------------- - - public function testSaveNewEntityWithDateTime() - { - $entity = new class extends Entity{ - protected $id; - protected $name; - protected $email; - protected $country; - protected $deleted; - protected $created_at; - protected $updated_at; - - protected $_options = [ - 'datamap' => [], - 'dates' => [ - 'created_at', - 'updated_at', - 'deleted_at', - ], - 'casts' => [], - ]; - }; - $testModel = new UserModel(); - - $entity->name = 'Mark'; - $entity->email = 'mark@example.com'; - $entity->country = 'India'; - $entity->deleted = 0; - $entity->created_at = new Time('now'); - - $this->setPrivateProperty($testModel, 'useTimestamps', true); - - $this->assertTrue($testModel->save($entity)); - } - - //-------------------------------------------------------------------- - - public function testSaveNewEntityWithDate() - { - $entity = new class extends Entity - { - protected $id; - protected $name; - protected $created_at; - protected $updated_at; - protected $_options = [ - 'datamap' => [], - 'dates' => [ - 'created_at', - 'updated_at', - 'deleted_at', - ], - 'casts' => [], - ]; - }; - $testModel = new class extends Model - { - protected $table = 'empty'; - protected $allowedFields = [ - 'name', - ]; - protected $returnType = 'object'; - protected $useSoftDeletes = true; - protected $dateFormat = 'date'; - public $name = ''; - }; - - $entity->name = 'Mark'; - $entity->created_at = new Time('now'); - - $this->setPrivateProperty($testModel, 'useTimestamps', true); - - $this->assertTrue($testModel->save($entity)); - - $testModel->truncate(); - } - - //-------------------------------------------------------------------- - - public function testUndefinedEntityPropertyReturnsNull() - { - $entity = new class extends Entity {}; - - $this->assertNull($entity->undefinedProperty); - } - - //-------------------------------------------------------------------- - - public function testInsertArrayWithNoDataException() - { - $model = new UserModel(); - $data = []; - $this->expectException(DataException::class); - $this->expectExceptionMessage('There is no data to insert.'); - $model->insert($data); - } - - //-------------------------------------------------------------------- - - public function testUpdateArrayWithNoDataException() - { - $model = new EventModel(); - - $data = [ - 'name' => 'Foo', - 'email' => 'foo@example.com', - 'country' => 'US', - 'deleted' => 0, - ]; - - $id = $model->insert($data); - - $data = []; - - $this->expectException(DataException::class); - $this->expectExceptionMessage('There is no data to update.'); - - $model->update($id, $data); - } - - //-------------------------------------------------------------------- - - public function testInsertObjectWithNoDataException() - { - $model = new UserModel(); - $data = new \stdClass(); - $this->expectException(DataException::class); - $this->expectExceptionMessage('There is no data to insert.'); - $model->insert($data); - } - - //-------------------------------------------------------------------- - - public function testUpdateObjectWithNoDataException() - { - $model = new EventModel(); - - $data = (object) [ - 'name' => 'Foo', - 'email' => 'foo@example.com', - 'country' => 'US', - 'deleted' => 0, - ]; - - $id = $model->insert($data); - - $data = new \stdClass(); - - $this->expectException(DataException::class); - $this->expectExceptionMessage('There is no data to update.'); - - $model->update($id, $data); - } - - //-------------------------------------------------------------------- - - public function testInvalidAllowedFieldException() - { - $model = new JobModel(); - - $data = [ - 'name' => 'Apprentice', - 'description' => 'That thing you do.', - ]; - - $this->setPrivateProperty($model, 'allowedFields', []); - - $this->expectException(DataException::class); - $this->expectExceptionMessage('Allowed fields must be specified for model: Tests\Support\Models\JobModel'); - - $model->save($data); - } - - //-------------------------------------------------------------------- - - public function testInvalidEventException() - { - $model = new EventModel(); - - $data = [ - 'name' => 'Foo', - 'email' => 'foo@example.com', - 'country' => 'US', - 'deleted' => 0, - ]; - - $this->setPrivateProperty($model, 'beforeInsert', ['anotherBeforeInsertMethod']); - - $this->expectException(DataException::class); - $this->expectExceptionMessage('anotherBeforeInsertMethod is not a valid Model Event callback.'); - - $model->insert($data); - } - - //-------------------------------------------------------------------- - - /** - * @see https://github.com/codeigniter4/CodeIgniter4/issues/1881 - */ - public function testSoftDeleteWithTableJoinsFindAll() - { - $model = new UserModel(); - - $this->seeInDatabase('user', ['name' => 'Derek Jones', 'deleted_at' => null]); - - $results = $model->join('job', 'job.id = user.id') - ->findAll(); - - // Just making sure it didn't throw ambiguous delete error - $this->assertCount(4, $results); - } - - /** - * @see https://github.com/codeigniter4/CodeIgniter4/issues/1881 - */ - public function testSoftDeleteWithTableJoinsFind() - { - $model = new UserModel(); - - $this->seeInDatabase('user', ['name' => 'Derek Jones', 'deleted_at' => null]); - - $results = $model->join('job', 'job.id = user.id') - ->find(1); - - // Just making sure it didn't throw ambiguous deleted error - $this->assertEquals(1, $results->id); - } - - /** - * @see https://github.com/codeigniter4/CodeIgniter4/issues/1881 - */ - public function testSoftDeleteWithTableJoinsFirst() - { - $model = new UserModel(); - - $this->seeInDatabase('user', ['name' => 'Derek Jones', 'deleted_at' => null]); - - $results = $model->join('job', 'job.id = user.id') - ->first(1); - - // Just making sure it didn't throw ambiguous deleted error - $this->assertEquals(1, $results->id); - } - - //-------------------------------------------------------------------- - - public function testUseAutoIncrementSetToFalseInsertException() - { - $this->expectException(DataException::class); - $this->expectExceptionMessage('There is no primary key defined when trying to make insert'); - - $model = new WithoutAutoIncrementModel(); - - $insert = [ - 'value' => 'some different value', - ]; - - $model->insert($insert); - } - - public function testUseAutoIncrementSetToFalseInsert() - { - $model = new WithoutAutoIncrementModel(); - - $insert = [ - 'key' => 'some_random_key', - 'value' => 'some different value', - ]; - - $model->insert($insert); - - $this->assertEquals($insert['key'], $model->getInsertID()); - $this->seeInDatabase('without_auto_increment', $insert); - } - - public function testUseAutoIncrementSetToFalseUpdate() - { - $model = new WithoutAutoIncrementModel(); - - $key = 'key'; - $update = [ - 'value' => 'some different value', - ]; - - $model->update($key, $update); - - $this->seeInDatabase('without_auto_increment', ['key' => $key, 'value' => $update['value']]); - } - - public function testUseAutoIncrementSetToFalseSave() - { - $model = new WithoutAutoIncrementModel(); - - $insert = [ - 'key' => 'some_random_key', - 'value' => 'some value', - ]; - - $model->save($insert); - - $this->assertEquals($insert['key'], $model->getInsertID()); - $this->seeInDatabase('without_auto_increment', $insert); - - $update = [ - 'key' => 'some_random_key', - 'value' => 'some different value', - ]; - $model->save($update); - - $this->assertEquals($insert['key'], $model->getInsertID()); - $this->seeInDatabase('without_auto_increment', $update); - } - - //-------------------------------------------------------------------- - - public function testMagicIssetTrue() - { - $model = new UserModel(); - - $this->assertTrue(isset($model->table)); - } - - public function testMagicIssetFalse() - { - $model = new UserModel(); - - $this->assertFalse(isset($model->foobar)); - } - - public function testMagicIssetWithNewProperty() - { - $model = new UserModel(); - - $model->flavor = 'chocolate'; - - $this->assertTrue(isset($model->flavor)); - } - - public function testMagicIssetFromDb() - { - $model = new UserModel(); - - $this->assertTrue(isset($model->DBPrefix)); - } - - public function testMagicIssetFromBuilder() - { - $model = new UserModel(); - - $this->assertTrue(isset($model->QBNoEscape)); - } - - public function testMagicGet() - { - $model = new UserModel(); - - $this->assertEquals('user', $model->table); - } - - public function testMagicGetMissing() - { - $model = new UserModel(); - - $this->assertNull($model->foobar); - } - - public function testMagicGetFromDB() - { - $model = new UserModel(); - - $this->assertEquals('utf8', $model->charset); - } - - public function testMagicGetFromBuilder() - { - $model = new UserModel(); - - $this->assertIsArray($model->QBNoEscape); - } - - public function testUndefinedModelMethod() - { - $model = new UserModel($this->db); - $this->expectException(BadMethodCallException::class); - $this->expectExceptionMessage('Call to undefined method Tests\Support\Models\UserModel::undefinedMethodCall'); - $model->undefinedMethodCall(); - } - - /** - * @dataProvider provideAggregateAndGroupBy - */ - public function testFirstRecoverTempUseSoftDeletes($aggregate, $groupBy) - { - $model = new UserModel($this->db); - $model->delete(1); - if ($aggregate) - { - $model->select('sum(id) as id'); - } - - if ($groupBy) - { - $model->groupBy('id'); - } - - $user = $model->withDeleted()->first(); - $this->assertEquals(1, $user->id); - - $user2 = $model->first(); - $this->assertEquals(2, $user2->id); - } - - public function testcountAllResultsRecoverTempUseSoftDeletes() - { - $model = new UserModel($this->db); - $model->delete(1); - $this->assertEquals(4, $model->withDeleted()->countAllResults()); - $this->assertEquals(3, $model->countAllResults()); - } - - public function testcountAllResultsFalseWithDeletedTrue() - { - $builder = new BaseBuilder('user', $this->db); - $expectedSQL = $builder->testMode()->countAllResults(); - - $model = new UserModel($this->db); - $model->delete(1); - - $this->assertEquals(4, $model->withDeleted()->countAllResults(false)); - - $this->assertEquals($expectedSQL, (string)$this->db->getLastQuery()); - - $this->assertFalse($this->getPrivateProperty($model, 'tempUseSoftDeletes')); - - $this->assertEquals(4, $model->countAllResults()); - - $this->assertEquals($expectedSQL, (string)$this->db->getLastQuery()); - - $this->assertTrue($this->getPrivateProperty($model, 'tempUseSoftDeletes')); - } - - public function testcountAllResultsFalseWithDeletedFalse() - { - $builder = new BaseBuilder('user', $this->db); - $expectedSQL = $builder->testMode()->where('user.deleted_at', null)->countAllResults(); - - $model = new UserModel($this->db); - $model->delete(1); - - $this->assertEquals(3, $model->withDeleted(false)->countAllResults(false)); - - $this->assertEquals($expectedSQL, (string)$this->db->getLastQuery()); - - $this->assertFalse($this->getPrivateProperty($model, 'tempUseSoftDeletes')); - - $this->assertEquals(3, $model->countAllResults()); - - $this->assertEquals($expectedSQL, (string)$this->db->getLastQuery()); - - $this->assertTrue($this->getPrivateProperty($model, 'tempUseSoftDeletes')); - } - - public function testcountAllResultsFalseWithDeletedTrueUseSoftDeletesFalse() - { - $builder = new BaseBuilder('user', $this->db); - $expectedSQL = $builder->testMode()->countAllResults(); - - $model = new UserModel($this->db); - $model->delete(1); - - $this->setPrivateProperty($model, 'useSoftDeletes', false); - - $this->assertEquals(4, $model->withDeleted()->countAllResults(false)); - - $this->assertEquals($expectedSQL, (string)$this->db->getLastQuery()); - - $this->assertFalse($this->getPrivateProperty($model, 'tempUseSoftDeletes')); - - $this->assertEquals(4, $model->countAllResults()); - - $this->assertEquals($expectedSQL, (string)$this->db->getLastQuery()); - - $this->assertFalse($this->getPrivateProperty($model, 'tempUseSoftDeletes')); - } - - public function testcountAllResultsFalseWithDeletedFalseUseSoftDeletesFalse() - { - $builder = new BaseBuilder('user', $this->db); - $expectedSQL = $builder->testMode()->where('user.deleted_at', null)->countAllResults(); - - $model = new UserModel($this->db); - $model->delete(1); - - $this->setPrivateProperty($model, 'useSoftDeletes', false); - - $this->assertEquals(3, $model->withDeleted(false)->countAllResults(false)); - - $this->assertEquals($expectedSQL, (string)$this->db->getLastQuery()); - - $this->assertFalse($this->getPrivateProperty($model, 'tempUseSoftDeletes')); - - $this->assertEquals(3, $model->countAllResults()); - - $this->assertEquals($expectedSQL, (string)$this->db->getLastQuery()); - - $this->assertFalse($this->getPrivateProperty($model, 'tempUseSoftDeletes')); - } - - public function testSetAllowedFields() - { - $allowed1 = [ - 'id', - 'created_at', - ]; - $allowed2 = [ - 'id', - 'updated_at', - ]; - - $model = new class extends Model { - protected $allowedFields = [ - 'id', - 'created_at', - ]; - }; - - $this->assertSame($allowed1, $this->getPrivateProperty($model, 'allowedFields')); - - $model->setAllowedFields($allowed2); - $this->assertSame($allowed2, $this->getPrivateProperty($model, 'allowedFields')); - } - - //-------------------------------------------------------------------- - - public function testBuilderUsesModelTable() - { - $model = new UserModel($this->db); - $builder = $model->builder(); - - $this->assertEquals('user', $builder->getTable()); - } - - public function testBuilderRespectsTableParameter() - { - $model = new UserModel($this->db); - $builder1 = $model->builder('jobs'); - $builder2 = $model->builder(); - - $this->assertEquals('jobs', $builder1->getTable()); - $this->assertEquals('user', $builder2->getTable()); - } - - public function testBuilderWithParameterIgnoresShared() - { - $model = new UserModel($this->db); - - $builder1 = $model->builder(); - $builder2 = $model->builder('jobs'); - $builder3 = $model->builder(); - - $this->assertEquals('user', $builder1->getTable()); - $this->assertEquals('jobs', $builder2->getTable()); - $this->assertEquals('user', $builder3->getTable()); - } -} diff --git a/tests/system/Models/CountAllModelTest.php b/tests/system/Models/CountAllModelTest.php new file mode 100644 index 000000000000..d5dcaed360a4 --- /dev/null +++ b/tests/system/Models/CountAllModelTest.php @@ -0,0 +1,97 @@ +createModel(UserModel::class); + + // testSeeder has 4 users.... + $this->assertSame(4, $this->model->countAllResults()); + + $this->model->where('name', 'Derek Jones')->delete(); + $this->assertSame(3, $this->model->countAllResults()); + } + + public function testcountAllResultsRecoverTempUseSoftDeletes(): void + { + $this->createModel(UserModel::class); + $this->model->delete(1); + $this->assertSame(4, $this->model->withDeleted()->countAllResults()); + $this->assertSame(3, $this->model->countAllResults()); + } + + public function testcountAllResultsFalseWithDeletedTrue(): void + { + $builder = new BaseBuilder('user', $this->db); + $expectedSQL = $builder->testMode()->countAllResults(); + + $this->createModel(UserModel::class); + $this->model->delete(1); + + $this->assertSame(4, $this->model->withDeleted()->countAllResults(false)); + $this->assertSame($expectedSQL, (string) $this->db->getLastQuery()); + $this->assertFalse($this->getPrivateProperty($this->model, 'tempUseSoftDeletes')); + $this->assertSame(4, $this->model->countAllResults()); + $this->assertSame($expectedSQL, (string) $this->db->getLastQuery()); + $this->assertTrue($this->getPrivateProperty($this->model, 'tempUseSoftDeletes')); + } + + public function testcountAllResultsFalseWithDeletedFalse(): void + { + $builder = new BaseBuilder('user', $this->db); + $expectedSQL = $builder->testMode()->where('user.deleted_at', null)->countAllResults(); + + $this->createModel(UserModel::class); + $this->model->delete(1); + + $this->assertSame(3, $this->model->withDeleted(false)->countAllResults(false)); + $this->assertSame($expectedSQL, (string) $this->db->getLastQuery()); + $this->assertFalse($this->getPrivateProperty($this->model, 'tempUseSoftDeletes')); + $this->assertSame(3, $this->model->countAllResults()); + $this->assertSame($expectedSQL, (string) $this->db->getLastQuery()); + $this->assertTrue($this->getPrivateProperty($this->model, 'tempUseSoftDeletes')); + } + + public function testcountAllResultsFalseWithDeletedTrueUseSoftDeletesFalse(): void + { + $builder = new BaseBuilder('user', $this->db); + $expectedSQL = $builder->testMode()->countAllResults(); + + $this->createModel(UserModel::class); + $this->model->delete(1); + $this->setPrivateProperty($this->model, 'useSoftDeletes', false); + + $this->assertSame(4, $this->model->withDeleted()->countAllResults(false)); + $this->assertSame($expectedSQL, (string) $this->db->getLastQuery()); + $this->assertFalse($this->getPrivateProperty($this->model, 'tempUseSoftDeletes')); + $this->assertSame(4, $this->model->countAllResults()); + $this->assertSame($expectedSQL, (string) $this->db->getLastQuery()); + $this->assertFalse($this->getPrivateProperty($this->model, 'tempUseSoftDeletes')); + } + + public function testcountAllResultsFalseWithDeletedFalseUseSoftDeletesFalse(): void + { + $builder = new BaseBuilder('user', $this->db); + $expectedSQL = $builder->testMode()->where('user.deleted_at', null)->countAllResults(); + + $this->createModel(UserModel::class); + $this->model->delete(1); + $this->setPrivateProperty($this->model, 'useSoftDeletes', false); + + $this->assertSame(3, $this->model->withDeleted(false)->countAllResults(false)); + $this->assertSame($expectedSQL, (string) $this->db->getLastQuery()); + $this->assertFalse($this->getPrivateProperty($this->model, 'tempUseSoftDeletes')); + $this->assertSame(3, $this->model->countAllResults()); + $this->assertSame($expectedSQL, (string) $this->db->getLastQuery()); + $this->assertFalse($this->getPrivateProperty($this->model, 'tempUseSoftDeletes')); + } +} diff --git a/tests/system/Models/DeleteModelTest.php b/tests/system/Models/DeleteModelTest.php new file mode 100644 index 000000000000..38da0970e755 --- /dev/null +++ b/tests/system/Models/DeleteModelTest.php @@ -0,0 +1,202 @@ +createModel(JobModel::class); + $this->seeInDatabase('job', ['name' => 'Developer']); + + $result = $this->model->delete(1); + $this->assertTrue($result->resultID !== false); + $this->dontSeeInDatabase('job', ['name' => 'Developer']); + } + + public function testDeleteFail(): void + { + $this->setPrivateProperty($this->db, 'DBDebug', false); + $this->createModel(JobModel::class); + $this->seeInDatabase('job', ['name' => 'Developer']); + + $result = $this->model->where('name123', 'Developer')->delete(); + $this->assertFalse($result->resultID); + $this->seeInDatabase('job', ['name' => 'Developer']); + } + + public function testDeleteStringPrimaryKey(): void + { + $this->createModel(StringifyPkeyModel::class); + $this->seeInDatabase('stringifypkey', ['value' => 'test']); + + $this->model->delete('A01'); + $this->dontSeeInDatabase('stringifypkey', ['value' => 'test']); + } + + public function testDeleteWithSoftDeletes(): void + { + $this->createModel(UserModel::class); + $this->seeInDatabase('user', ['name' => 'Derek Jones', 'deleted_at IS NULL' => null]); + + $result = $this->model->delete(1); + $this->assertTrue($result); + $this->seeInDatabase('user', ['name' => 'Derek Jones', 'deleted_at IS NOT NULL' => null]); + } + + public function testDeleteWithSoftDeleteFail(): void + { + $this->setPrivateProperty($this->db, 'DBDebug', false); + $this->createModel(UserModel::class); + $this->seeInDatabase('user', ['name' => 'Derek Jones', 'deleted_at IS NULL' => null]); + + $result = $this->model->where('name123', 'Derek Jones')->delete(); + $this->assertFalse($result); + $this->seeInDatabase('user', ['name' => 'Derek Jones', 'deleted_at IS NULL' => null]); + } + + public function testDeleteWithSoftDeletesPurge(): void + { + $this->createModel(UserModel::class); + $this->seeInDatabase('user', ['name' => 'Derek Jones', 'deleted_at IS NULL' => null]); + + $this->model->delete(1, true); + $this->dontSeeInDatabase('user', ['name' => 'Derek Jones']); + } + + public function testDeleteMultiple(): void + { + $this->createModel(JobModel::class); + $this->seeInDatabase('job', ['name' => 'Developer']); + $this->seeInDatabase('job', ['name' => 'Politician']); + + $this->model->delete([1, 2]); + $this->dontSeeInDatabase('job', ['name' => 'Developer']); + $this->dontSeeInDatabase('job', ['name' => 'Politician']); + $this->seeInDatabase('job', ['name' => 'Accountant']); + } + + public function testDeleteNoParams(): void + { + $this->createModel(JobModel::class); + $this->seeInDatabase('job', ['name' => 'Developer']); + + $this->model->where('id', 1)->delete(); + $this->dontSeeInDatabase('job', ['name' => 'Developer']); + } + + public function testPurgeDeleted(): void + { + $this->createModel(UserModel::class); + + $this->db->table('user') + ->where('id', 1) + ->update(['deleted_at' => date('Y-m-d H:i:s')]); + + $this->model->purgeDeleted(); + + $users = $this->model->withDeleted()->findAll(); + $this->assertCount(3, $users); + } + + public function testOnlyDeleted(): void + { + $this->createModel(UserModel::class); + + $this->db->table('user') + ->where('id', 1) + ->update(['deleted_at' => date('Y-m-d H:i:s')]); + + $users = $this->model->onlyDeleted()->findAll(); + $this->assertCount(1, $users); + } + + /** + * If where condition is set, beyond the value was empty (0,'', NULL, etc.), + * Exception should not be thrown because condition was explicity set + * + * @dataProvider emptyPkValues + */ + public function testDontThrowExceptionWhenSoftDeleteConditionIsSetWithEmptyValue($emptyValue): void + { + $this->createModel(UserModel::class); + $this->seeInDatabase('user', ['name' => 'Derek Jones', 'deleted_at IS NULL' => null]); + + $this->model->where('id', $emptyValue)->delete(); + $this->seeInDatabase('user', ['name' => 'Derek Jones', 'deleted_at IS NULL' => null]); + } + + /** + * @dataProvider emptyPkValues + */ + public function testThrowExceptionWhenSoftDeleteParamIsEmptyValue($emptyValue): void + { + $this->expectException(DatabaseException::class); + $this->expectExceptionMessage('Deletes are not allowed unless they contain a "where" or "like" clause.'); + + $this->seeInDatabase('user', ['name' => 'Derek Jones', 'deleted_at IS NULL' => null]); + $this->createModel(UserModel::class)->delete($emptyValue); + } + + /** + * @dataProvider emptyPkValues + */ + public function testDontDeleteRowsWhenSoftDeleteParamIsEmpty($emptyValue): void + { + $this->expectException(DatabaseException::class); + $this->expectExceptionMessage('Deletes are not allowed unless they contain a "where" or "like" clause.'); + + $this->seeInDatabase('user', ['name' => 'Derek Jones', 'deleted_at IS NULL' => null]); + + $this->createModel(UserModel::class)->delete($emptyValue); + $this->seeInDatabase('user', ['name' => 'Derek Jones', 'deleted_at IS NULL' => null]); + } + + public static function emptyPkValues(): iterable + { + return [ + [0], + [null], + ['0'], + ]; + } + + public function testThrowsWithNoDateFormat(): void + { + $this->expectException(ModelException::class); + $this->expectExceptionMessage('`Tests\Support\Models\UserModel` model class does not have a valid dateFormat.'); + + $this->createModel(UserModel::class); + $this->setPrivateProperty($this->model, 'dateFormat', ''); + $this->model->delete(1); + } + + public function testDeleteWithSoftDelete(): void + { + $this->createModel(JobModel::class); + $this->setPrivateProperty($this->model, 'useTimestamps', true); + $this->setPrivateProperty($this->model, 'useSoftDeletes', true); + + $this->model->delete(1); + $this->seeInDatabase('job', ['id' => 1, 'deleted_at IS NOT NULL' => null]); + } + + public function testPurgeDeletedWithSoftDeleteFalse(): void + { + $this->db->table('job') + ->where('id', 1) + ->update(['deleted_at' => time()]); + + $this->createModel(JobModel::class); + $this->model->purgeDeleted(); + + $jobs = $this->model->findAll(); + $this->assertCount(4, $jobs); + } +} diff --git a/tests/system/Models/EventsModelTest.php b/tests/system/Models/EventsModelTest.php new file mode 100644 index 000000000000..43ddecd8862c --- /dev/null +++ b/tests/system/Models/EventsModelTest.php @@ -0,0 +1,161 @@ +createModel(EventModel::class); + } + + public function testInsertEvent(): void + { + $data = [ + 'name' => 'Foo', + 'email' => 'foo@example.com', + 'country' => 'US', + 'deleted' => 0, + ]; + + $this->model->insert($data); + $this->assertTrue($this->model->hasToken('beforeInsert')); + $this->assertTrue($this->model->hasToken('afterInsert')); + } + + public function testUpdateEvent(): void + { + $data = [ + 'name' => 'Foo', + 'email' => 'foo@example.com', + 'country' => 'US', + 'deleted' => 0, + ]; + + $id = $this->model->insert($data); + + $this->model->update($id, $data); + $this->assertTrue($this->model->hasToken('beforeUpdate')); + $this->assertTrue($this->model->hasToken('afterUpdate')); + } + + public function testDeleteEvent(): void + { + $this->model->delete(1); + $this->assertTrue($this->model->hasToken('beforeDelete')); + $this->assertTrue($this->model->hasToken('afterDelete')); + } + + public function testFindEvent(): void + { + $this->model->find(1); + $this->assertTrue($this->model->hasToken('beforeFind')); + $this->assertTrue($this->model->hasToken('afterFind')); + } + + public function testBeforeFindReturnsData(): void + { + $this->model->beforeFindReturnData = true; + + $result = $this->model->find(1); + $this->assertTrue($this->model->hasToken('beforeFind')); + $this->assertSame($result, 'foobar'); + } + + public function testBeforeFindReturnDataPreventsAfterFind(): void + { + $this->model->beforeFindReturnData = true; + $this->model->find(1); + $this->assertFalse($this->model->hasToken('afterFind')); + } + + public function testFindEventSingletons(): void + { + // afterFind + $this->model->first(); + $this->assertTrue($this->model->eventData['singleton']); + + $this->model->find(1); + $this->assertTrue($this->model->eventData['singleton']); + + $this->model->find(); + $this->assertFalse($this->model->eventData['singleton']); + + $this->model->findAll(); + $this->assertFalse($this->model->eventData['singleton']); + + // beforeFind + $this->model->beforeFindReturnData = true; + + $this->model->first(); + $this->assertTrue($this->model->eventData['singleton']); + + $this->model->find(1); + $this->assertTrue($this->model->eventData['singleton']); + + $this->model->find(); + $this->assertFalse($this->model->eventData['singleton']); + + $this->model->findAll(); + $this->assertFalse($this->model->eventData['singleton']); + } + + public function testAllowCallbacksFalsePreventsTriggers(): void + { + $this->model->allowCallbacks(false)->find(1); + $this->assertFalse($this->model->hasToken('afterFind')); + } + + public function testAllowCallbacksTrueFiresTriggers(): void + { + $this->setPrivateProperty($this->model, 'allowCallbacks', false); + $this->model->allowCallbacks(true)->find(1); + $this->assertTrue($this->model->hasToken('afterFind')); + } + + public function testAllowCallbacksResetsAfterTrigger(): void + { + $this->model->allowCallbacks(false)->find(1); + $this->model->delete(1); + + $this->assertFalse($this->model->hasToken('afterFind')); + $this->assertTrue($this->model->hasToken('afterDelete')); + } + + public function testAllowCallbacksUsesModelProperty(): void + { + $this->setPrivateProperty($this->model, 'allowCallbacks', false); + $this->setPrivateProperty($this->model, 'tempAllowCallbacks', false); // Was already set by the constructor + + $this->model->find(1); + $this->model->delete(1); + + $this->assertFalse($this->model->hasToken('afterFind')); + $this->assertFalse($this->model->hasToken('afterDelete')); + } + + public function testInvalidEventException(): void + { + $data = [ + 'name' => 'Foo', + 'email' => 'foo@example.com', + 'country' => 'US', + 'deleted' => 0, + ]; + + $this->setPrivateProperty($this->model, 'beforeInsert', ['anotherBeforeInsertMethod']); + + $this->expectException(DataException::class); + $this->expectExceptionMessage('anotherBeforeInsertMethod is not a valid Model Event callback.'); + $this->model->insert($data); + } +} diff --git a/tests/system/Models/FindModelTest.php b/tests/system/Models/FindModelTest.php new file mode 100644 index 000000000000..eac062db1571 --- /dev/null +++ b/tests/system/Models/FindModelTest.php @@ -0,0 +1,303 @@ +createModel(JobModel::class); + $this->assertSame('Musician', $this->model->find(4)->name); + } + + public function testFindReturnsMultipleRows(): void + { + $this->createModel(JobModel::class); + $jobs = $this->model->find([1, 4]); + + $this->assertSame('Developer', $jobs[0]->name); + $this->assertSame('Musician', $jobs[1]->name); + } + + public function testGetColumnWithStringColumnName(): void + { + $this->createModel(JobModel::class); + $job = $this->model->findColumn('name'); + + $this->assertSame('Developer', $job[0]); + $this->assertSame('Politician', $job[1]); + $this->assertSame('Accountant', $job[2]); + $this->assertSame('Musician', $job[3]); + } + + public function testGetColumnsWithMultipleColumnNames(): void + { + $this->expectException(DataException::class); + $this->expectExceptionMessage('Only single column allowed in Column name.'); + $this->createModel(JobModel::class)->findColumn('name,description'); + } + + public function testFindActsAsGetWithNoParams(): void + { + $this->createModel(JobModel::class); + + $jobs = $this->model->asArray()->find(); + $this->assertCount(4, $jobs); + + $names = array_column($jobs, 'name'); + $this->assertContains('Developer', $names); + $this->assertContains('Politician', $names); + $this->assertContains('Accountant', $names); + $this->assertContains('Musician', $names); + } + + public function testFindRespectsReturnArray(): void + { + $job = $this->createModel(JobModel::class)->asArray()->find(4); + $this->assertIsArray($job); + } + + public function testFindRespectsReturnObject(): void + { + $job = $this->createModel(JobModel::class)->asObject()->find(4); + $this->assertIsObject($job); + } + + public function testFindRespectsSoftDeletes(): void + { + $this->db->table('user') + ->where('id', 4) + ->update(['deleted_at' => date('Y-m-d H:i:s')]); + + $this->createModel(UserModel::class); + + $user = $this->model->find(4); + $this->assertEmpty($user); + + $user = $this->model->withDeleted()->find(4); + $count = is_object($user) ? 1 : 0; + $this->assertSame(1, $count); + } + + public function testFindClearsBinds(): void + { + $this->createModel(JobModel::class); + $this->model->find(1); + $this->model->find(1); + + // Binds should be reset to 0 after each one + $binds = $this->model->builder()->getBinds(); + $this->assertCount(0, $binds); + + $query = $this->model->getLastQuery(); + $this->assertCount(1, $this->getPrivateProperty($query, 'binds')); + } + + public function testFindAllReturnsAllRecords(): void + { + $users = $this->createModel(UserModel::class)->findAll(); + $this->assertCount(4, $users); + } + + public function testFindAllRespectsLimits(): void + { + $users = $this->createModel(UserModel::class)->findAll(2); + $this->assertCount(2, $users); + $this->assertSame('Derek Jones', $users[0]->name); + } + + public function testFindAllRespectsLimitsAndOffset(): void + { + $users = $this->createModel(UserModel::class)->findAll(2, 2); + $this->assertCount(2, $users); + $this->assertSame('Richard A Causey', $users[0]->name); + } + + public function testFindAllRespectsSoftDeletes(): void + { + $this->db->table('user') + ->where('id', 4) + ->update(['deleted_at' => date('Y-m-d H:i:s')]); + + $this->createModel(UserModel::class); + + $user = $this->model->findAll(); + $this->assertCount(3, $user); + + $user = $this->model->withDeleted()->findAll(); + $this->assertCount(4, $user); + } + + public function testFirst(): void + { + $user = $this->createModel(UserModel::class)->where('id >', 2)->first(); + $count = is_object($user) ? 1 : 0; + $this->assertSame(1, $count); + $this->assertSame(3, (int) $user->id); + } + + /** + * @dataProvider provideGroupBy + */ + public function testFirstAggregate($groupBy, $total): void + { + $this->createModel(UserModel::class); + + if ($groupBy) + { + $this->model->groupBy('id'); + } + + $user = $this->model->select('SUM(id) as total')->where('id >', 2)->first(); + $this->assertSame($total, (int) $user->total); + } + + public static function provideGroupBy(): iterable + { + return [ + [true, 3], + [false, 7], + ]; + } + + /** + * @dataProvider provideAggregateAndGroupBy + */ + public function testFirstRespectsSoftDeletes($aggregate, $groupBy): void + { + $this->db->table('user') + ->where('id', 1) + ->update(['deleted_at' => date('Y-m-d H:i:s')]); + + $this->createModel(UserModel::class); + + if ($aggregate) + { + $this->model->select('SUM(id) as id'); + } + + if ($groupBy) + { + $this->model->groupBy('id'); + } + + $user = $this->model->first(); + + if (! $aggregate || $groupBy) + { + $count = is_object($user) ? 1 : 0; + $this->assertSame(1, $count); + $this->assertSame(2, (int) $user->id); + } + else + { + $this->assertSame(9, (int) $user->id); + } + + $user = $this->model->withDeleted()->first(); + $this->assertSame(1, (int) $user->id); + } + + /** + * @dataProvider provideAggregateAndGroupBy + */ + public function testFirstRecoverTempUseSoftDeletes($aggregate, $groupBy): void + { + $this->createModel(UserModel::class); + $this->model->delete(1); + + if ($aggregate) + { + $this->model->select('sum(id) as id'); + } + + if ($groupBy) + { + $this->model->groupBy('id'); + } + + $user = $this->model->withDeleted()->first(); + $this->assertSame(1, (int) $user->id); + + $user2 = $this->model->first(); + $this->assertSame(2, (int) $user2->id); + } + + public static function provideAggregateAndGroupBy(): iterable + { + return [ + [true, true], + [false, false], + [true, false], + [false, true], + ]; + } + + public function testFirstWithNoPrimaryKey(): void + { + $this->createModel(SecondaryModel::class); + + $this->db->table('secondary')->insert([ + 'key' => 'foo', + 'value' => 'bar', + ]); + + $this->db->table('secondary')->insert([ + 'key' => 'bar', + 'value' => 'baz', + ]); + + $record = $this->model->first(); + $this->assertInstanceOf('stdClass', $record); + $this->assertSame('foo', $record->key); + } + + public function testThrowsWithNoPrimaryKey(): void + { + $this->expectException(ModelException::class); + $this->expectExceptionMessage('`Tests\Support\Models\UserModel` model class does not specify a Primary Key.'); + + $this->createModel(UserModel::class); + $this->setPrivateProperty($this->model, 'primaryKey', ''); + $this->model->find(1); + } + + /** + * @see https://github.com/codeigniter4/CodeIgniter4/issues/1881 + */ + public function testSoftDeleteWithTableJoinsFindAll(): void + { + $this->seeInDatabase('user', ['name' => 'Derek Jones', 'deleted_at' => null]); + + $results = $this->createModel(UserModel::class)->join('job', 'job.id = user.id')->findAll(); + $this->assertCount(4, $results); + } + + /** + * @see https://github.com/codeigniter4/CodeIgniter4/issues/1881 + */ + public function testSoftDeleteWithTableJoinsFind(): void + { + $this->seeInDatabase('user', ['name' => 'Derek Jones', 'deleted_at' => null]); + + $results = $this->createModel(UserModel::class)->join('job', 'job.id = user.id')->find(1); + $this->assertSame(1, (int) $results->id); + } + + /** + * @see https://github.com/codeigniter4/CodeIgniter4/issues/1881 + */ + public function testSoftDeleteWithTableJoinsFirst(): void + { + $this->seeInDatabase('user', ['name' => 'Derek Jones', 'deleted_at' => null]); + + $results = $this->createModel(UserModel::class)->join('job', 'job.id = user.id')->first(1); + $this->assertSame(1, (int) $results->id); + } +} diff --git a/tests/system/Models/GeneralModelTest.php b/tests/system/Models/GeneralModelTest.php new file mode 100644 index 000000000000..e69c0158c90a --- /dev/null +++ b/tests/system/Models/GeneralModelTest.php @@ -0,0 +1,142 @@ +resetServices(); + } + + /** + * Create an instance of Model for use in testing. + * + * @param string $modelName + * @param BaseConnection|null $db + * + * @return Model + */ + private function createModel(string $modelName, BaseConnection $db = null): Model + { + $this->model = new $modelName($db); + + return $this->model; + } + + public function testGetModelDetails(): void + { + $this->createModel(JobModel::class); + + $this->assertSame('job', $this->model->table); + $this->assertSame('id', $this->model->primaryKey); + $this->assertSame('object', $this->model->returnType); + $this->assertNull($this->model->DBGroup); + } + + public function testMagicGetters(): void + { + $this->createModel(UserModel::class); + + $this->assertTrue(isset($this->model->table)); + $this->assertSame('user', $this->model->table); + $this->assertFalse(isset($this->model->foobar)); + $this->assertNull($this->model->foobar); + + $this->model->flavor = 'chocolate'; + $this->assertTrue(isset($this->model->flavor)); + $this->assertSame('chocolate', $this->model->flavor); + + // from DB + $this->assertTrue(isset($this->model->DBPrefix)); + $this->assertSame('utf8', $this->model->charset); + + // from Builder + $this->assertTrue(isset($this->model->QBNoEscape)); + $this->assertIsArray($this->model->QBNoEscape); + } + + public function testUndefinedModelMethod(): void + { + $this->expectException(BadMethodCallException::class); + $this->expectExceptionMessage('Call to undefined method Tests\Support\Models\UserModel::undefinedMethodCall'); + $this->createModel(UserModel::class)->undefinedMethodCall(); + } + + public function testSetAllowedFields(): void + { + $allowed1 = [ + 'id', + 'created_at', + ]; + $allowed2 = [ + 'id', + 'updated_at', + ]; + + $model = new class extends Model + { + protected $allowedFields = [ + 'id', + 'created_at', + ]; + }; + + $this->assertSame($allowed1, $this->getPrivateProperty($model, 'allowedFields')); + + $model->setAllowedFields($allowed2); + $this->assertSame($allowed2, $this->getPrivateProperty($model, 'allowedFields')); + } + + public function testBuilderUsesModelTable(): void + { + $builder = $this->createModel(UserModel::class)->builder(); + $this->assertSame('user', $builder->getTable()); + } + + public function testBuilderRespectsTableParameter(): void + { + $this->createModel(UserModel::class); + $builder1 = $this->model->builder('jobs'); + $builder2 = $this->model->builder(); + + $this->assertSame('jobs', $builder1->getTable()); + $this->assertSame('user', $builder2->getTable()); + } + + public function testBuilderWithParameterIgnoresShared(): void + { + $this->createModel(UserModel::class); + $builder1 = $this->model->builder(); + $builder2 = $this->model->builder('jobs'); + $builder3 = $this->model->builder(); + + $this->assertSame('user', $builder1->getTable()); + $this->assertSame('jobs', $builder2->getTable()); + $this->assertSame('user', $builder3->getTable()); + } +} diff --git a/tests/system/Models/InsertModelTest.php b/tests/system/Models/InsertModelTest.php new file mode 100644 index 000000000000..5787057b3066 --- /dev/null +++ b/tests/system/Models/InsertModelTest.php @@ -0,0 +1,221 @@ +dontSeeInDatabase('user', [ + 'email' => 'foo@example.com', + ]); + + $this->createModel(UserModel::class)->set([ + 'email' => 'foo@example.com', + 'name' => 'Foo Bar', + 'country' => 'US', + ])->insert(); + + $this->seeInDatabase('user', [ + 'email' => 'foo@example.com', + ]); + } + + public function testInsertBatchSuccess(): void + { + $jobData = [ + [ + 'name' => 'Comedian', + 'description' => 'There\'s something in your teeth', + ], + [ + 'name' => 'Cab Driver', + 'description' => 'I am yellow', + ], + ]; + + $this->createModel(JobModel::class)->insertBatch($jobData); + $this->seeInDatabase('job', ['name' => 'Comedian']); + $this->seeInDatabase('job', ['name' => 'Cab Driver']); + } + + public function testInsertBatchValidationFail(): void + { + $jobData = [ + [ + 'name' => 'Comedian', + 'description' => null, + ], + ]; + + $this->createModel(JobModel::class); + + $this->setPrivateProperty($this->model, 'validationRules', ['description' => 'required']); + $this->assertFalse($this->model->insertBatch($jobData)); + + $error = $this->model->errors(); + $this->assertTrue(isset($error['description'])); + } + + public function testInsertBatchSetsIntTimestamps(): void + { + $jobData = [ + [ + 'name' => 'Philosopher', + ], + [ + 'name' => 'Laborer', + ], + ]; + + $this->createModel(JobModel::class); + + $this->setPrivateProperty($this->model, 'useTimestamps', true); + $this->assertSame(2, $this->model->insertBatch($jobData)); + + $result = $this->model->where('name', 'Philosopher')->first(); + $this->assertCloseEnough(time(), $result->created_at); + } + + public function testInsertBatchSetsDatetimeTimestamps(): void + { + $userData = [ + [ + 'name' => 'Lou', + 'email' => 'lou@example.com', + 'country' => 'Ireland', + ], + [ + 'name' => 'Sue', + 'email' => 'sue@example.com', + 'country' => 'Ireland', + ], + ]; + + $this->createModel(UserModel::class); + + $this->setPrivateProperty($this->model, 'useTimestamps', true); + $this->assertSame(2, $this->model->insertBatch($userData)); + + $result = $this->model->where('name', 'Lou')->first(); + $this->assertCloseEnough(time(), strtotime($result->created_at)); + } + + public function testInsertResult(): void + { + $data = [ + 'name' => 'Apprentice', + 'description' => 'That thing you do.', + ]; + + $this->createModel(JobModel::class); + + $result = $this->model->protect(false)->insert($data, false); + $this->assertTrue($result->resultID !== false); + + $lastInsertId = $this->model->getInsertID(); + $this->seeInDatabase('job', ['id' => $lastInsertId]); + } + + public function testInsertResultFail(): void + { + $this->setPrivateProperty($this->db, 'DBDebug', false); + + $data = [ + 'name123' => 'Apprentice', + 'description' => 'That thing you do.', + ]; + + $this->createModel(JobModel::class); + $result = $this->model->protect(false)->insert($data, false); + $this->assertFalse($result->resultID); + + $lastInsertId = $this->model->getInsertID(); + $this->assertSame(0, $lastInsertId); + $this->dontSeeInDatabase('job', ['id' => $lastInsertId]); + } + + public function testInsertBatchNewEntityWithDateTime(): void + { + $entity = new class extends Entity + { + protected $id; + protected $name; + protected $email; + protected $country; + protected $deleted; + protected $created_at; + protected $updated_at; + + protected $_options = [ + 'datamap' => [], + 'dates' => [ + 'created_at', + 'updated_at', + 'deleted_at', + ], + 'casts' => [], + ]; + }; + + $this->createModel(UserModel::class); + + $entity->name = 'Mark'; + $entity->email = 'mark@example.com'; + $entity->country = 'India'; + $entity->deleted = 0; + $entity->created_at = new Time('now'); + + $this->setPrivateProperty($this->model, 'useTimestamps', true); + $this->assertSame(2, $this->model->insertBatch([$entity, $entity])); + } + + public function testInsertArrayWithDataException(): void + { + $this->expectException(DataException::class); + $this->expectExceptionMessage('There is no data to insert.'); + $this->createModel(UserModel::class)->insert([]); + } + + public function testInsertObjectWithNoDataException(): void + { + $data = new stdClass(); + $this->expectException(DataException::class); + $this->expectExceptionMessage('There is no data to insert.'); + $this->createModel(UserModel::class)->insert($data); + } + + public function testUseAutoIncrementSetToFalseInsertException(): void + { + $this->expectException(DataException::class); + $this->expectExceptionMessage('There is no primary key defined when trying to make insert'); + + $insert = [ + 'value' => 'some different value', + ]; + + $this->createModel(WithoutAutoIncrementModel::class)->insert($insert); + } + + public function testUseAutoIncrementSetToFalseInsert(): void + { + $insert = [ + 'key' => 'some_random_key', + 'value' => 'some different value', + ]; + + $this->createModel(WithoutAutoIncrementModel::class); + $this->model->insert($insert); + + $this->assertSame($insert['key'], $this->model->getInsertID()); + $this->seeInDatabase('without_auto_increment', $insert); + } +} diff --git a/tests/system/Models/LiveModelTestCase.php b/tests/system/Models/LiveModelTestCase.php new file mode 100644 index 000000000000..395fefe4f975 --- /dev/null +++ b/tests/system/Models/LiveModelTestCase.php @@ -0,0 +1,54 @@ +resetServices(); + $this->resetFactories(); + } + + /** + * Create an instance of Model for use in testing. + * + * @param string $modelName + * @param BaseConnection|null $db + * + * @return Model + */ + protected function createModel(string $modelName, BaseConnection $db = null): Model + { + $this->db = $db ?? $this->db; + $this->model = new $modelName($this->db); + + return $this->model; + } +} diff --git a/tests/system/Models/MiscellaneousModelTest.php b/tests/system/Models/MiscellaneousModelTest.php new file mode 100644 index 000000000000..54583030656a --- /dev/null +++ b/tests/system/Models/MiscellaneousModelTest.php @@ -0,0 +1,84 @@ +createModel(UserModel::class)->chunk(2, static function ($row) use (&$rowCount) { + $rowCount++; + }); + + $this->assertSame(4, $rowCount); + } + + public function testCanCreateAndSaveEntityClasses(): void + { + $entity = $this->createModel(EntityModel::class)->where('name', 'Developer')->first(); + + $this->assertInstanceOf(SimpleEntity::class, $entity); + $this->assertSame('Developer', $entity->name); + $this->assertSame('Awesome job, but sometimes makes you bored', $entity->description); + + $time = time(); + + $entity->name = 'Senior Developer'; + $entity->created_at = $time; + + $this->assertTrue($this->model->save($entity)); + + $result = $this->model->where('name', 'Senior Developer')->get()->getFirstRow(); + $this->assertSame(date('Y-m-d', $time), date('Y-m-d', $result->created_at)); + } + + /** + * @see https://github.com/codeigniter4/CodeIgniter4/issues/580 + */ + public function testPasswordsStoreCorrectly(): void + { + $data = [ + 'name' => password_hash('secret123', PASSWORD_BCRYPT), + 'email' => 'foo@example.com', + 'country' => 'US', + ]; + + $this->createModel(UserModel::class)->insert($data); + $this->seeInDatabase('user', $data); + } + + public function testReplaceObject(): void + { + $data = [ + 'id' => 1, + 'name' => 'my name', + 'description' => 'some description', + ]; + + $this->createModel(ValidModel::class)->replace($data); + $this->seeInDatabase('job', ['id' => 1, 'name' => 'my name']); + } + + public function testGetValidationMessagesForReplace(): void + { + $jobData = [ + 'name' => 'Comedian', + 'description' => null, + ]; + + $this->createModel(JobModel::class); + $this->setPrivateProperty($this->model, 'validationRules', ['description' => 'required']); + $this->assertFalse($this->model->replace($jobData)); + + $error = $this->model->errors(); + $this->assertTrue(isset($error['description'])); + } +} diff --git a/tests/system/Models/PaginateModelTest.php b/tests/system/Models/PaginateModelTest.php new file mode 100644 index 000000000000..28b5dac23052 --- /dev/null +++ b/tests/system/Models/PaginateModelTest.php @@ -0,0 +1,61 @@ +createModel(ValidModel::class)->paginate(); + $this->assertCount(4, $data); + } + + public function testPaginateChangeConfigPager(): void + { + $perPage = config('Pager')->perPage; + + config('Pager')->perPage = 1; + + $data = $this->createModel(ValidModel::class)->paginate(); + $this->assertCount(1, $data); + + config('Pager')->perPage = $perPage; + } + + public function testPaginatePassPerPageParameter(): void + { + $data = $this->createModel(ValidModel::class)->paginate(2); + $this->assertCount(2, $data); + } + + public function testPaginateForQueryWithGroupBy(): void + { + $this->createModel(ValidModel::class); + $this->model->groupBy('id'); + $this->model->paginate(); + $this->assertSame(4, $this->model->pager->getDetails()['total']); + } + + public function testPaginateWithDeleted(): void + { + $this->createModel(UserModel::class); + $this->model->delete(1); + + $data = $this->model->withDeleted()->paginate(); + $this->assertCount(4, $data); + $this->assertSame(4, $this->model->pager->getDetails()['total']); + } + + public function testPaginateWithoutDeleted(): void + { + $this->createModel(UserModel::class); + $this->model->delete(1); + + $data = $this->model->withDeleted(false)->paginate(); + $this->assertCount(3, $data); + $this->assertSame(3, $this->model->pager->getDetails()['total']); + } +} diff --git a/tests/system/Models/SaveModelTest.php b/tests/system/Models/SaveModelTest.php new file mode 100644 index 000000000000..92d2acdee0de --- /dev/null +++ b/tests/system/Models/SaveModelTest.php @@ -0,0 +1,310 @@ +createModel(JobModel::class); + + $data = new stdClass(); + $data->name = 'Magician'; + $data->description = 'Makes peoples things dissappear.'; + + $this->model->protect(false)->save($data); + $this->seeInDatabase('job', ['name' => 'Magician']); + } + + public function testSaveNewRecordArray(): void + { + $this->createModel(JobModel::class); + + $data = [ + 'name' => 'Apprentice', + 'description' => 'That thing you do.', + ]; + + $this->model->protect(false)->save($data); + $this->seeInDatabase('job', ['name' => 'Apprentice']); + } + + public function testSaveNewRecordArrayFail(): void + { + $this->setPrivateProperty($this->db, 'DBDebug', false); + $this->createModel(JobModel::class); + + $data = [ + 'name123' => 'Apprentice', + 'description' => 'That thing you do.', + ]; + + $result = $this->model->protect(false)->save($data); + $this->assertFalse($result); + $this->dontSeeInDatabase('job', ['name' => 'Apprentice']); + } + + public function testSaveUpdateRecordArray(): void + { + $this->createModel(JobModel::class); + + $data = [ + 'name' => 'Apprentice', + 'description' => 'That thing you do.', + ]; + + $result = $this->model->protect(false)->save($data); + $this->seeInDatabase('job', ['name' => 'Apprentice']); + $this->assertTrue($result); + } + + public function testSaveUpdateRecordArrayFail(): void + { + $this->setPrivateProperty($this->db, 'DBDebug', false); + $this->createModel(JobModel::class); + + $data = [ + 'id' => 1, + 'name123' => 'Apprentice', + 'description' => 'That thing you do.', + ]; + + $result = $this->model->protect(false)->save($data); + $this->assertFalse($result); + $this->dontSeeInDatabase('job', ['name' => 'Apprentice']); + } + + public function testSaveUpdateRecordObject(): void + { + $this->createModel(JobModel::class); + $data = new stdClass(); + + // Sqlsrv does not allow forcing an ID into an autoincrement field. + if ($this->db->DBDriver !== 'Sqlsrv') + { + $data->id = 1; + } + + $data->name = 'Engineer'; + $data->description = 'A fancier term for Developer.'; + + $result = $this->model->protect(false)->save($data); + $this->seeInDatabase('job', ['name' => 'Engineer']); + $this->assertTrue($result); + } + + public function testSaveProtected(): void + { + $this->createModel(JobModel::class); + + $data = new stdClass(); + $data->id = 1; + $data->name = 'Engineer'; + $data->description = 'A fancier term for Developer.'; + $data->random_thing = 'Something wicked'; // If not protected, this would kill the script. + + $result = $this->model->protect(true)->save($data); + $this->assertTrue($result); + } + + public function testSelectAndEntitiesSaveOnlyChangedValues(): void + { + $this->hasInDatabase('job', [ + 'name' => 'Rocket Scientist', + 'description' => 'Plays guitar for Queen', + 'created_at' => time(), + ]); + + $this->createModel(EntityModel::class); + + $job = $this->model->select('id, name')->where('name', 'Rocket Scientist')->first(); + $this->assertNull($job->description); + $this->assertSame('Rocket Scientist', $job->name); + + $job->description = 'Some guitar description'; + $this->model->save($job); + $this->seeInDatabase('job', [ + 'id' => $job->id, + 'name' => 'Rocket Scientist', + ]); + + $job = $this->model->select('id, name, description')->where('name', 'Rocket Scientist')->first(); + $this->assertSame('Some guitar description', $job->description); + } + + public function testInsertID(): void + { + $data = [ + 'name' => 'Apprentice', + 'description' => 'That thing you do.', + ]; + + $this->createModel(JobModel::class); + $this->model->protect(false)->save($data); + + $lastInsertId = $this->model->getInsertID(); + $this->seeInDatabase('job', ['id' => $lastInsertId]); + } + + public function testSetTable(): void + { + $this->createModel(SecondaryModel::class); + + $this->model->setTable('job'); + + $data = [ + 'name' => 'Apprentice', + 'description' => 'That thing you do.', + ]; + + $this->model->protect(false)->save($data); + $lastInsertId = $this->model->getInsertID(); + $this->seeInDatabase('job', ['id' => $lastInsertId]); + } + + public function testSaveObject(): void + { + $this->createModel(ValidModel::class); + + $testModel = new JobModel(); + + $testModel->name = 'my name'; + $testModel->description = 'some description'; + + $this->setPrivateProperty($this->model, 'useTimestamps', true); + + $this->model->insert($testModel); + $lastInsertId = $this->model->getInsertID(); + $this->seeInDatabase('job', ['id' => $lastInsertId]); + } + + public function testEmptySaveData(): void + { + $this->assertTrue($this->createModel(JobModel::class)->protect(false)->save([])); + } + + public function testSaveNewEntityWithDateTime(): void + { + $entity = new class extends Entity + { + protected $id; + protected $name; + protected $email; + protected $country; + protected $deleted; + protected $created_at; + protected $updated_at; + + protected $_options = [ + 'datamap' => [], + 'dates' => [ + 'created_at', + 'updated_at', + 'deleted_at', + ], + 'casts' => [], + ]; + }; + + $this->createModel(UserModel::class); + + $entity->name = 'Mark'; + $entity->email = 'mark@example.com'; + $entity->country = 'India'; + $entity->deleted = 0; + $entity->created_at = new Time('now'); + + $this->setPrivateProperty($this->model, 'useTimestamps', true); + $this->assertTrue($this->model->save($entity)); + } + + public function testSaveNewEntityWithDate(): void + { + $entity = new class extends Entity + { + protected $id; + protected $name; + protected $created_at; + protected $updated_at; + protected $_options = [ + 'datamap' => [], + 'dates' => [ + 'created_at', + 'updated_at', + 'deleted_at', + ], + 'casts' => [], + ]; + }; + + $testModel = new class extends Model + { + protected $table = 'empty'; + protected $allowedFields = [ + 'name', + ]; + protected $returnType = 'object'; + protected $useSoftDeletes = true; + protected $dateFormat = 'date'; + public $name = ''; + }; + + $entity->name = 'Mark'; + $entity->created_at = new Time('now'); + + $this->setPrivateProperty($testModel, 'useTimestamps', true); + $this->assertTrue($testModel->save($entity)); + $testModel->truncate(); + } + + public function testInvalidAllowedFieldException(): void + { + $this->createModel(JobModel::class); + $this->model->setAllowedFields([]); + + $data = [ + 'name' => 'Apprentice', + 'description' => 'That thing you do.', + ]; + + $this->expectException(DataException::class); + $this->expectExceptionMessage('Allowed fields must be specified for model: Tests\Support\Models\JobModel'); + + $this->model->save($data); + } + + public function testUseAutoIncrementSetToFalseSave(): void + { + $insert = [ + 'key' => 'some_random_key', + 'value' => 'some value', + ]; + + $this->createModel(WithoutAutoIncrementModel::class); + $this->model->save($insert); + + $this->assertSame($insert['key'], $this->model->getInsertID()); + $this->seeInDatabase('without_auto_increment', $insert); + + $update = [ + 'key' => 'some_random_key', + 'value' => 'some different value', + ]; + + $this->model->save($update); + $this->assertSame($insert['key'], $this->model->getInsertID()); + $this->seeInDatabase('without_auto_increment', $update); + } +} diff --git a/tests/system/Models/UpdateModelTest.php b/tests/system/Models/UpdateModelTest.php new file mode 100644 index 000000000000..903ac3eff4a4 --- /dev/null +++ b/tests/system/Models/UpdateModelTest.php @@ -0,0 +1,289 @@ +dontSeeInDatabase('user', [ + 'email' => 'foo@example.com', + ]); + + $this->createModel(UserModel::class); + + $userId = $this->model->insert([ + 'email' => 'foo@example.com', + 'name' => 'Foo Bar', + 'country' => 'US', + ]); + + $this->model->set([ + 'name' => 'Fred Flintstone', + ])->update($userId); + + $this->seeInDatabase('user', [ + 'id' => $userId, + 'email' => 'foo@example.com', + 'name' => 'Fred Flintstone', + ]); + } + + public function testSetWorksWithUpdateNoId(): void + { + $this->dontSeeInDatabase('user', [ + 'email' => 'foo@example.com', + ]); + + $this->createModel(UserModel::class); + + $userId = $this->model->insert([ + 'email' => 'foo@example.com', + 'name' => 'Foo Bar', + 'country' => 'US', + ]); + + $this->model->where('id', $userId)->set([ + 'name' => 'Fred Flintstone', + ])->update(); + + $this->seeInDatabase('user', [ + 'id' => $userId, + 'email' => 'foo@example.com', + 'name' => 'Fred Flintstone', + ]); + } + + public function testUpdateArray(): void + { + $data = [ + 'name' => 'Foo', + 'email' => 'foo@example.com', + 'country' => 'US', + 'deleted' => 0, + ]; + + $this->createModel(UserModel::class); + $this->model->insert($data); + + $result = $this->model->update([1, 2], ['name' => 'Foo Bar']); + $this->assertTrue($result); + $this->seeInDatabase('user', ['id' => 1, 'name' => 'Foo Bar']); + $this->seeInDatabase('user', ['id' => 2, 'name' => 'Foo Bar']); + } + + public function testUpdateResultFail(): void + { + $this->setPrivateProperty($this->db, 'DBDebug', false); + + $data = [ + 'name' => 'Foo', + 'email' => 'foo@example.com', + 'country' => 'US', + 'deleted' => 0, + ]; + + $this->createModel(UserModel::class); + $this->model->insert($data); + + $this->setPrivateProperty($this->model, 'allowedFields', ['name123']); + $result = $this->model->update(1, ['name123' => 'Foo Bar 1']); + $this->assertFalse($result); + $this->dontSeeInDatabase('user', ['id' => 1, 'name' => 'Foo Bar 1']); + } + + public function testUpdateBatchSuccess(): void + { + $data = [ + [ + 'name' => 'Derek Jones', + 'country' => 'Greece', + ], + [ + 'name' => 'Ahmadinejad', + 'country' => 'Greece', + ], + ]; + + $this->createModel(UserModel::class)->updateBatch($data, 'name'); + + $this->seeInDatabase('user', [ + 'name' => 'Derek Jones', + 'country' => 'Greece', + ]); + $this->seeInDatabase('user', [ + 'name' => 'Ahmadinejad', + 'country' => 'Greece', + ]); + } + + public function testUpdateBatchValidationFail(): void + { + $data = [ + [ + 'name' => 'Derek Jones', + 'country' => null, + ], + ]; + + $this->createModel(UserModel::class); + $this->setPrivateProperty($this->model, 'validationRules', ['country' => 'required']); + $this->assertFalse($this->model->updateBatch($data, 'name')); + + $error = $this->model->errors(); + $this->assertTrue(isset($error['country'])); + } + + public function testUpdateBatchWithEntity(): void + { + $entity1 = new class extends Entity + { + protected $id; + protected $name; + protected $email; + protected $country; + protected $deleted; + protected $created_at; + protected $updated_at; + + protected $_options = [ + 'datamap' => [], + 'dates' => [ + 'created_at', + 'updated_at', + 'deleted_at', + ], + 'casts' => [], + ]; + }; + + $entity2 = new class extends Entity + { + protected $id; + protected $name; + protected $email; + protected $country; + protected $deleted; + protected $created_at; + protected $updated_at; + + protected $_options = [ + 'datamap' => [], + 'dates' => [ + 'created_at', + 'updated_at', + 'deleted_at', + ], + 'casts' => [], + ]; + }; + + $entity1->id = 1; + $entity1->name = 'Jones Martin'; + $entity1->country = 'India'; + $entity1->deleted = 0; + + $entity2->id = 4; + $entity2->name = 'Jones Martin'; + $entity2->country = 'India'; + $entity2->deleted = 0; + + $this->assertSame(2, $this->createModel(UserModel::class)->updateBatch([$entity1, $entity2], 'id')); + } + + public function testUpdateNoPrimaryKey(): void + { + $this->db->table('secondary')->insert([ + 'key' => 'foo', + 'value' => 'bar', + ]); + + $this->dontSeeInDatabase('secondary', [ + 'key' => 'bar', + 'value' => 'baz', + ]); + + $this->createModel(SecondaryModel::class) + ->where('key', 'foo') + ->update(null, ['key' => 'bar', 'value' => 'baz']); + + $this->seeInDatabase('secondary', [ + 'key' => 'bar', + 'value' => 'baz', + ]); + } + + public function testUpdateObject(): void + { + $this->createModel(ValidModel::class); + + $testModel = new JobModel(); + + $testModel->name = 'my name'; + $testModel->description = 'some description'; + + $this->setPrivateProperty($this->model, 'useTimestamps', true); + $this->model->update(1, $testModel); + $this->seeInDatabase('job', ['id' => 1]); + } + + public function testUpdateArrayWithDataException(): void + { + $this->createModel(EventModel::class); + + $data = [ + 'name' => 'Foo', + 'email' => 'foo@example.com', + 'country' => 'US', + 'deleted' => 0, + ]; + + $id = $this->model->insert($data); + + $this->expectException(DataException::class); + $this->expectExceptionMessage('There is no data to update.'); + $this->model->update($id, []); + } + + public function testUpdateObjectWithDataException(): void + { + $this->createModel(EventModel::class); + + $data = (object) [ + 'name' => 'Foo', + 'email' => 'foo@example.com', + 'country' => 'US', + 'deleted' => 0, + ]; + + $id = $this->model->insert($data); + + $data = new stdClass(); + + $this->expectException(DataException::class); + $this->expectExceptionMessage('There is no data to update.'); + $this->model->update($id, $data); + } + + public function testUseAutoIncrementSetToFalseUpdate(): void + { + $key = 'key'; + + $update = [ + 'value' => 'some different value', + ]; + + $this->createModel(WithoutAutoIncrementModel::class)->update($key, $update); + $this->seeInDatabase('without_auto_increment', ['key' => $key, 'value' => $update['value']]); + } +} diff --git a/tests/system/Models/ValidationModelTest.php b/tests/system/Models/ValidationModelTest.php new file mode 100644 index 000000000000..a561707ccd45 --- /dev/null +++ b/tests/system/Models/ValidationModelTest.php @@ -0,0 +1,322 @@ +createModel(ValidModel::class); + } + + public function testValidationBasics(): void + { + $data = [ + 'name' => null, + 'description' => 'some great marketing stuff', + ]; + + $this->assertFalse($this->model->insert($data)); + + $errors = $this->model->errors(); + $this->assertSame('You forgot to name the baby.', $errors['name']); + } + + public function testValidationWithSetValidationRule(): void + { + $data = [ + 'name' => 'some name', + 'description' => 'some great marketing stuff', + ]; + + $this->model->setValidationRule('description', [ + 'rules' => 'required|min_length[50]', + 'errors' => [ + 'min_length' => 'Description is too short baby.', + ], + ]); + $this->assertFalse($this->model->insert($data)); + + $errors = $this->model->errors(); + $this->assertSame('Description is too short baby.', $errors['description']); + } + + public function testValidationWithSetValidationRules(): void + { + $data = [ + 'name' => '', + 'description' => 'some great marketing stuff', + ]; + + $this->model->setValidationRules([ + 'name' => [ + 'rules' => 'required', + 'errors' => [ + 'required' => 'Give me a name baby.', + ], + ], + 'description' => [ + 'rules' => 'required|min_length[50]', + 'errors' => [ + 'min_length' => 'Description is too short baby.', + ], + ], + ]); + $this->assertFalse($this->model->insert($data)); + + $errors = $this->model->errors(); + $this->assertSame('Give me a name baby.', $errors['name']); + $this->assertSame('Description is too short baby.', $errors['description']); + } + + public function testValidationWithSetValidationMessage(): void + { + $data = [ + 'name' => null, + 'description' => 'some great marketing stuff', + ]; + + $this->model->setValidationMessage('name', [ + 'required' => 'Your baby name is missing.', + 'min_length' => 'Too short, man!', + ]); + $this->assertFalse($this->model->insert($data)); + + $errors = $this->model->errors(); + $this->assertSame('Your baby name is missing.', $errors['name']); + } + + public function testValidationPlaceholdersSuccess(): void + { + $data = [ + 'name' => 'abc', + 'id' => 13, + 'token' => 13, + ]; + + $this->assertTrue($this->model->validate($data)); + } + + public function testValidationPlaceholdersFail(): void + { + $data = [ + 'name' => 'abc', + 'id' => 13, + 'token' => 12, + ]; + + $this->assertFalse($this->model->validate($data)); + } + + public function testSkipValidation(): void + { + $data = [ + 'name' => '2', + 'description' => 'some great marketing stuff', + ]; + + $this->assertIsNumeric($this->model->skipValidation(true)->insert($data)); + } + + public function testCleanValidationRemovesAllWhenNoDataProvided(): void + { + $cleaner = $this->getPrivateMethodInvoker($this->model, 'cleanValidationRules'); + + $rules = [ + 'name' => 'required', + 'foo' => 'bar', + ]; + + $rules = call_user_func($cleaner, $rules, null); + $this->assertEmpty($rules); + } + + public function testCleanValidationRemovesOnlyForFieldsNotProvided(): void + { + $cleaner = $this->getPrivateMethodInvoker($this->model, 'cleanValidationRules'); + + $rules = [ + 'name' => 'required', + 'foo' => 'required', + ]; + + $data = [ + 'foo' => 'bar', + ]; + + $rules = call_user_func($cleaner, $rules, $data); + $this->assertArrayHasKey('foo', $rules); + $this->assertArrayNotHasKey('name', $rules); + } + + public function testCleanValidationReturnsAllWhenAllExist(): void + { + $cleaner = $this->getPrivateMethodInvoker($this->model, 'cleanValidationRules'); + + $rules = [ + 'name' => 'required', + 'foo' => 'required', + ]; + + $data = [ + 'foo' => 'bar', + 'name' => null, + ]; + + $rules = call_user_func($cleaner, $rules, $data); + $this->assertArrayHasKey('foo', $rules); + $this->assertArrayHasKey('name', $rules); + } + + public function testValidationPassesWithMissingFields(): void + { + $data = [ + 'foo' => 'bar', + ]; + + $result = $this->model->validate($data); + $this->assertTrue($result); + } + + public function testValidationWithGroupName(): void + { + $config = new Validation(); + + $config->grouptest = [ + 'name' => [ + 'required', + 'min_length[3]', + ], + 'token' => 'in_list[{id}]', + ]; + + $data = [ + 'name' => 'abc', + 'id' => 13, + 'token' => 13, + ]; + + Factories::injectMock('config', 'Validation', $config); + + $this->createModel(ValidModel::class); + $this->setPrivateProperty($this->model, 'validationRules', 'grouptest'); + $this->assertTrue($this->model->validate($data)); + } + + /** + * @see https://github.com/codeigniter4/CodeIgniter4/issues/1584 + */ + public function testUpdateWithValidation(): void + { + $data = [ + 'description' => 'This is a first test!', + 'name' => 'valid', + 'id' => 42, + 'token' => 42, + ]; + + $id = $this->model->insert($data); + $this->assertTrue((bool) $id); + + $data['description'] = 'This is a second test!'; + unset($data['name']); + + $result = $this->model->update($id, $data); + $this->assertTrue($result); + } + + /** + * @see https://github.com/codeigniter4/CodeIgniter4/issues/1717 + */ + public function testRequiredWithValidationEmptyString(): void + { + $this->assertFalse($this->model->insert(['name' => ''])); + } + + /** + * @see https://github.com/codeigniter4/CodeIgniter4/issues/1717 + */ + public function testRequiredWithValidationNull(): void + { + $this->assertFalse($this->model->insert(['name' => null])); + } + + /** + * @see https://github.com/codeigniter4/CodeIgniter4/issues/1717 + */ + public function testRequiredWithValidationTrue(): void + { + $data = [ + 'name' => 'foobar', + 'description' => 'just because we have to', + ]; + + $this->assertTrue($this->model->insert($data) !== false); + } + + /** + * @see https://github.com/codeigniter4/CodeIgniter4/issues/1574 + */ + public function testValidationIncludingErrors(): void + { + $data = [ + 'description' => 'This is a first test!', + 'name' => 'valid', + 'id' => 42, + 'token' => 42, + ]; + + $this->createModel(ValidErrorsModel::class); + + $id = $this->model->insert($data); + $this->assertFalse((bool)$id); + $this->assertSame('Minimum Length Error', $this->model->errors()['name']); + } + + public function testValidationByObject(): void + { + $data = new stdClass(); + + $data->name = 'abc'; + $data->id = '13'; + $data->token = '13'; + + $this->assertTrue($this->model->validate($data)); + } + + public function testGetValidationRules(): void + { + $this->createModel(JobModel::class); + $this->setPrivateProperty($this->model, 'validationRules', ['description' => 'required']); + + $rules = $this->model->getValidationRules(); + $this->assertSame('required', $rules['description']); + } + + public function testGetValidationMessages(): void + { + $jobData = [ + [ + 'name' => 'Comedian', + 'description' => null, + ], + ]; + + $this->createModel(JobModel::class); + $this->setPrivateProperty($this->model, 'validationRules', ['description' => 'required']); + $this->setPrivateProperty($this->model, 'validationMessages', ['description' => 'Description field is required.']); + + $this->assertFalse($this->model->insertBatch($jobData)); + + $error = $this->model->getValidationMessages(); + $this->assertSame('Description field is required.', $error['description']); + } +} From a803fc4e15fc02fff46c40ad7f2420c604d18db4 Mon Sep 17 00:00:00 2001 From: "John Paul E. Balandan, CPA" Date: Wed, 18 Nov 2020 23:41:15 +0800 Subject: [PATCH 2/4] Fix incompatible text and varchar comparison --- system/Database/MigrationRunner.php | 5 +++-- tests/system/Database/Migrations/MigrationRunnerTest.php | 8 ++------ 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/system/Database/MigrationRunner.php b/system/Database/MigrationRunner.php index bb980f9fbb3c..d368d3d470fc 100644 --- a/system/Database/MigrationRunner.php +++ b/system/Database/MigrationRunner.php @@ -957,8 +957,9 @@ public function ensureTable() 'null' => false, ], 'class' => [ - 'type' => 'TEXT', - 'null' => false, + 'type' => 'VARCHAR', + 'constraint' => 255, + 'null' => false, ], 'group' => [ 'type' => 'VARCHAR', diff --git a/tests/system/Database/Migrations/MigrationRunnerTest.php b/tests/system/Database/Migrations/MigrationRunnerTest.php index 7c841eed6efe..ead50c61d57f 100644 --- a/tests/system/Database/Migrations/MigrationRunnerTest.php +++ b/tests/system/Database/Migrations/MigrationRunnerTest.php @@ -68,9 +68,7 @@ public function testGetCliMessages() public function testGetHistory() { $runner = new MigrationRunner($this->config); - - $tableMaker = $this->getPrivateMethodInvoker($runner, 'ensureTable'); - $tableMaker(); + $runner->ensureTable(); $history = [ 'id' => 4, @@ -103,9 +101,7 @@ public function testGetHistory() public function testGetHistoryReturnsEmptyArrayWithNoResults() { $runner = new MigrationRunner($this->config); - - $tableMaker = $this->getPrivateMethodInvoker($runner, 'ensureTable'); - $tableMaker(); + $runner->ensureTable(); $this->assertEquals([], $runner->getHistory()); } From 5623eee58c4d738df79ecfced2578228359b3ada Mon Sep 17 00:00:00 2001 From: "John Paul E. Balandan, CPA" Date: Wed, 18 Nov 2020 23:52:18 +0800 Subject: [PATCH 3/4] Rename test model to conform with PSR4 --- ...ithoutAutoincrementModel.php => WithoutAutoIncrementModel.php} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/_support/Models/{WithoutAutoincrementModel.php => WithoutAutoIncrementModel.php} (100%) diff --git a/tests/_support/Models/WithoutAutoincrementModel.php b/tests/_support/Models/WithoutAutoIncrementModel.php similarity index 100% rename from tests/_support/Models/WithoutAutoincrementModel.php rename to tests/_support/Models/WithoutAutoIncrementModel.php From 726e97480b5271839412db1126ec4788c292dacc Mon Sep 17 00:00:00 2001 From: "John Paul E. Balandan, CPA" Date: Tue, 5 Jan 2021 23:20:23 +0800 Subject: [PATCH 4/4] Rename to SQLSRV --- tests/system/Models/SaveModelTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/system/Models/SaveModelTest.php b/tests/system/Models/SaveModelTest.php index 92d2acdee0de..c94485043510 100644 --- a/tests/system/Models/SaveModelTest.php +++ b/tests/system/Models/SaveModelTest.php @@ -92,7 +92,7 @@ public function testSaveUpdateRecordObject(): void $data = new stdClass(); // Sqlsrv does not allow forcing an ID into an autoincrement field. - if ($this->db->DBDriver !== 'Sqlsrv') + if ($this->db->DBDriver !== 'SQLSRV') { $data->id = 1; }