From 97df533ba47fae292c710483db30f67c124ebddb Mon Sep 17 00:00:00 2001 From: Bob Olde Hampsink Date: Fri, 29 Jan 2016 10:20:30 +0100 Subject: [PATCH 01/15] Updated for Craft 2.5 --- README.md | 2 +- controllers/AuditLogController.php | 9 +++++++-- elementtypes/AuditLogElementType.php | 23 ++++++++++++----------- services/AuditLog_CategoryService.php | 8 +++++++- services/AuditLog_EntryService.php | 8 +++++++- services/AuditLog_UserService.php | 8 +++++++- 6 files changed, 41 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 474832e..036eec1 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Features: - Has hooks that you can use to extend this plugin - registerAuditLogSources - getAuditLogTableAttributeHtml - - modifyAuditLogTableAttributes + - defineAvailableTableAttributes - modifyAuditLogSortableAttributes - Has events that you can listen to - auditLog.onElementChanged diff --git a/controllers/AuditLogController.php b/controllers/AuditLogController.php index 1494bbe..06a8a35 100644 --- a/controllers/AuditLogController.php +++ b/controllers/AuditLogController.php @@ -51,8 +51,13 @@ public function actionDownload() // Set status attribute $attributes['status'] = Craft::t('Status'); - // Get table attributes - $attributes += $elementType->defineTableAttributes(); + // Get nice attributes + $availableAttributes = $elementType->defineAvailableTableAttributes(); + + // Make 'em fit + foreach ($availableAttributes as $key => $result) { + $attributes[$key] = $result['label']; + } // Ditch the changes button unset($attributes['changes']); diff --git a/elementtypes/AuditLogElementType.php b/elementtypes/AuditLogElementType.php index 038f73a..3639154 100644 --- a/elementtypes/AuditLogElementType.php +++ b/elementtypes/AuditLogElementType.php @@ -50,27 +50,28 @@ public function getStatuses() } /** - * Define table column names. - * - * @param string $source + * Define available table column names. * * @return array */ - public function defineTableAttributes($source = null) + public function defineAvailableTableAttributes() { // Define default attributes $attributes = array( - 'type' => Craft::t('Type'), - 'user' => Craft::t('User'), - 'origin' => Craft::t('Origin'), - 'dateUpdated' => Craft::t('Modified'), + 'type' => array('label' => Craft::t('Type')), + 'user' => array('label' => Craft::t('User')), + 'origin' => array('label' => Craft::t('Origin')), + 'dateUpdated' => array('label' => Craft::t('Modified')), ); // Allow plugins to modify the attributes - craft()->plugins->call('modifyAuditLogTableAttributes', array(&$attributes, $source)); + $pluginAttributes = craft()->plugins->call('defineAdditionalAuditLogTableAttributes', array(), true); + foreach ($pluginAttributes as $thisPluginAttributes) { + $attributes = array_merge($attributes, $thisPluginAttributes); + } // Set changes at last - $attributes['changes'] = Craft::t('Changes'); + $attributes['changes'] = array('label' => Craft::t('Changes')); // Return the attributes return $attributes; @@ -334,7 +335,7 @@ public function defineSortableAttributes() $attributes['dateUpdated'] = Craft::t('Modified'); // Get table attributes - $attributes = array_merge($attributes, $this->defineTableAttributes()); + $attributes = array_merge($attributes, parent::defineSortableAttributes()); // Unset unsortable attributes unset($attributes['user'], $attributes['changes']); diff --git a/services/AuditLog_CategoryService.php b/services/AuditLog_CategoryService.php index 32f8169..be95081 100644 --- a/services/AuditLog_CategoryService.php +++ b/services/AuditLog_CategoryService.php @@ -168,7 +168,13 @@ public function fields(CategoryModel $category, $empty = false) $elementType = craft()->elements->getElementType(ElementType::Category); // Get nice attributes - $attributes = $elementType->defineTableAttributes(); + $availableAttributes = $elementType->defineAvailableTableAttributes(); + + // Make 'em fit + $attributes = array(); + foreach ($availableAttributes as $key => $result) { + $attributes[$key] = $result['label']; + } // Get static "fields" foreach ($category->getAttributes() as $handle => $value) { diff --git a/services/AuditLog_EntryService.php b/services/AuditLog_EntryService.php index 20a8a42..f4a6bf9 100644 --- a/services/AuditLog_EntryService.php +++ b/services/AuditLog_EntryService.php @@ -168,7 +168,13 @@ public function fields(EntryModel $entry, $empty = false) $elementType = craft()->elements->getElementType(ElementType::Entry); // Get nice attributes - $attributes = $elementType->defineTableAttributes(); + $availableAttributes = $elementType->defineAvailableTableAttributes(); + + // Make 'em fit + $attributes = array(); + foreach ($availableAttributes as $key => $result) { + $attributes[$key] = $result['label']; + } // Get static "fields" foreach ($entry->getAttributes() as $handle => $value) { diff --git a/services/AuditLog_UserService.php b/services/AuditLog_UserService.php index 4b67dd2..e02ee97 100644 --- a/services/AuditLog_UserService.php +++ b/services/AuditLog_UserService.php @@ -179,7 +179,13 @@ public function fields(UserModel $user, $empty = false) $elementType = craft()->elements->getElementType(ElementType::User); // Get nice attributes - $attributes = $elementType->defineTableAttributes(); + $availableAttributes = $elementType->defineAvailableTableAttributes(); + + // Make 'em fit + $attributes = array(); + foreach ($availableAttributes as $key => $result) { + $attributes[$key] = $result['label']; + } // Get static "fields" foreach ($user->getAttributes() as $handle => $value) { From 530a8ef88bffba6c0da6d724d066516b7f06c827 Mon Sep 17 00:00:00 2001 From: Bob Olde Hampsink Date: Fri, 29 Jan 2016 10:32:39 +0100 Subject: [PATCH 02/15] Updated js for Craft 2.5 --- templates/index.twig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/index.twig b/templates/index.twig index 1f8cb77..e13a148 100644 --- a/templates/index.twig +++ b/templates/index.twig @@ -74,7 +74,7 @@ $('#auditlog-download-csv').click(function(e) { var form = $(this).after('
{{ getCsrfInput() }}
').next('form'); // Add controller data - var params = decodeURIComponent($.param(Craft.elementIndex.getControllerData())).split('&'); + var params = decodeURIComponent($.param(Craft.elementIndex.getViewParams())).split('&'); $.each(params, function(key, value) { $(form).append(''); }); From 8496c6e4e3c6318dba3b9f9286f1c08369969e47 Mon Sep 17 00:00:00 2001 From: Bob Olde Hampsink Date: Fri, 29 Jan 2016 10:33:36 +0100 Subject: [PATCH 03/15] Bumped version # --- AuditLogPlugin.php | 2 +- README.md | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/AuditLogPlugin.php b/AuditLogPlugin.php index fc2c9a5..e93a067 100644 --- a/AuditLogPlugin.php +++ b/AuditLogPlugin.php @@ -32,7 +32,7 @@ public function getName() */ public function getVersion() { - return '0.6.2'; + return '0.7.0'; } /** diff --git a/README.md b/README.md index 036eec1..65fa003 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,9 @@ The plugin's folder should be named "auditlog" Changelog ================= +###0.7.0### + - Added Craft 2.5 compatibility + ###0.6.2### - Fixed a bug where the date range didn't fully work - Fixed criteria attributes not fully working From 1b4f89a0bfa18884f066d2e2f4607855809b1e53 Mon Sep 17 00:00:00 2001 From: Bob Olde Hampsink Date: Fri, 29 Jan 2016 12:12:10 +0100 Subject: [PATCH 04/15] Set default table attributes --- elementtypes/AuditLogElementType.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/elementtypes/AuditLogElementType.php b/elementtypes/AuditLogElementType.php index 3639154..08e7bb6 100644 --- a/elementtypes/AuditLogElementType.php +++ b/elementtypes/AuditLogElementType.php @@ -77,6 +77,18 @@ public function defineAvailableTableAttributes() return $attributes; } + /** + * Returns the default table attributes. + * + * @param string $source + * + * @return array + */ + public function getDefaultTableAttributes($source = null) + { + return array('type', 'user', 'origin', 'dateUpdated', 'changes'); + } + /** * Return table attribute html. * From 346dd9b9f0266d7adc4054ca924682b696b983a9 Mon Sep 17 00:00:00 2001 From: Bob Olde Hampsink Date: Tue, 23 Feb 2016 15:19:11 +0100 Subject: [PATCH 05/15] Added unit tests --- .gitignore | 1 + .scrutinizer.yml | 7 +++ .travis.yml | 43 ++++++++++++++ README.md | 9 ++- phpunit.xml.dist | 25 ++++++++ services/AuditLogService.php | 15 ++--- tests/AuditLogServiceTest.php | 109 ++++++++++++++++++++++++++++++++++ tests/AuditLogTest.php | 48 --------------- 8 files changed, 201 insertions(+), 56 deletions(-) create mode 100644 .gitignore create mode 100644 .scrutinizer.yml create mode 100644 .travis.yml create mode 100644 phpunit.xml.dist create mode 100644 tests/AuditLogServiceTest.php delete mode 100644 tests/AuditLogTest.php diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..567609b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/.scrutinizer.yml b/.scrutinizer.yml new file mode 100644 index 0000000..a456355 --- /dev/null +++ b/.scrutinizer.yml @@ -0,0 +1,7 @@ +checks: + php: + code_rating: true + duplication: true + +tools: + external_code_coverage: true diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..de0b3f3 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,43 @@ +# see http://about.travis-ci.org/docs/user/languages/php/ for more hints +language: php + +# list any PHP version you want to test against +php: + # using major version aliases + + # aliased to a recent 5.4.x version + - 5.4 + # aliased to a recent 5.5.x version + - 5.5 + # aliased to a recent 5.6.x version + - 5.6 + # aliased to a recent hhvm version + - hhvm + +# optionally set up exclutions and allowed failures in the matrix +matrix: + allow_failures: + - php: hhvm + +# execute any number of scripts before the test run, custom env's are available as variables +before_script: + - curl -sS https://codeload.github.com/pixelandtonic/Craft-Release/zip/master > craft.zip + - unzip craft.zip + - rm craft.zip + - mv Craft-Release-master craft + - mkdir craft/config + - echo " 'test');" > craft/config/db.php + - mkdir craft/storage + - mkdir -p craft/plugins/auditlog + - for item in *; do if [[ ! "$item" == "craft" ]]; then mv $item craft/plugins/auditlog; fi; done + - cd craft/app + - composer require mockery/mockery + - cd ../.. + +# execute tests +script: phpunit --bootstrap craft/app/tests/bootstrap.php --configuration craft/plugins/auditlog/phpunit.xml.dist --coverage-clover coverage.clover craft/plugins/auditlog/tests + +# upload coverage to scrutinizer +after_script: + - wget https://scrutinizer-ci.com/ocular.phar + - php ocular.phar code-coverage:upload --format=php-clover coverage.clover diff --git a/README.md b/README.md index 65fa003..2f7603b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -Audit Log plugin for Craft CMS +Audit Log plugin for Craft CMS [![Build Status](https://scrutinizer-ci.com/g/boboldehampsink/auditlog/badges/build.png?b=develop)](https://scrutinizer-ci.com/g/boboldehampsink/auditlog/build-status/develop) [![Code Coverage](https://scrutinizer-ci.com/g/boboldehampsink/auditlog/badges/coverage.png?b=develop)](https://scrutinizer-ci.com/g/boboldehampsink/auditlog/?branch=develop) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/boboldehampsink/auditlog/badges/quality-score.png?b=develop)](https://scrutinizer-ci.com/g/boboldehampsink/auditlog/?branch=develop) ================= Plugin that allows you to log adding/updating/deleting of categories/entries/users. @@ -23,6 +23,13 @@ Roadmap: Important: The plugin's folder should be named "auditlog" +Development +================= +Run this from your Craft installation to test your changes to this plugin before submitting a Pull Request +```bash +phpunit --bootstrap craft/app/tests/bootstrap.php --configuration craft/plugins/auditlog/phpunit.xml.dist --coverage-text craft/plugins/auditlog/tests +``` + Changelog ================= ###0.7.0### diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..19f5544 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,25 @@ + + + + + tests + + + + + ./services + + + + + + + diff --git a/services/AuditLogService.php b/services/AuditLogService.php index efef05f..f363e8f 100644 --- a/services/AuditLogService.php +++ b/services/AuditLogService.php @@ -21,6 +21,8 @@ class AuditLogService extends BaseApplicationComponent * @param object $criteria * * @return array + * + * @codeCoverageIgnore */ public function log($criteria) { @@ -40,7 +42,6 @@ public function log($criteria) */ public function view($id) { - // Get log from record $log = craft()->elements->getCriteria('AuditLog', array('id' => $id))->first(); @@ -52,10 +53,10 @@ public function view($id) // Set parsed values $diff[$handle] = array( - 'label' => $item['label'], + 'label' => $item['label'], 'changed' => ($item['value'] != $log['before'][$handle]['value']), - 'after' => $item['value'], - 'before' => $log['before'][$handle]['value'], + 'after' => $item['value'], + 'before' => $log['before'][$handle]['value'], ); } @@ -150,7 +151,7 @@ public function elementHasChanged($elementType, $id, $before, $after) // Flatten arrays $flatBefore = ArrayHelper::flattenArray($before); - $flatAfter = ArrayHelper::flattenArray($after); + $flatAfter = ArrayHelper::flattenArray($after); // Calculate the diffence $flatDiff = array_diff_assoc($flatAfter, $flatBefore); @@ -171,8 +172,8 @@ public function elementHasChanged($elementType, $id, $before, $after) // Fire an "onElementChanged" event $event = new Event($this, array( 'elementType' => $elementType, - 'id' => $id, - 'diff' => $diff, + 'id' => $id, + 'diff' => $diff, )); $this->onElementChanged($event); } diff --git a/tests/AuditLogServiceTest.php b/tests/AuditLogServiceTest.php new file mode 100644 index 0000000..2395533 --- /dev/null +++ b/tests/AuditLogServiceTest.php @@ -0,0 +1,109 @@ + + * @copyright Copyright (c) 2015, Bob Olde Hampsink + * @license MIT + * + * @link http://github.com/boboldehampsink + */ +class AuditLogServiceTest extends BaseTest +{ + /** + * {@inheritdoc} + */ + public static function setUpBeforeClass() + { + // Set up parent + parent::setUpBeforeClass(); + + // Require dependencies + require_once __DIR__.'/../services/AuditLogService.php'; + require_once __DIR__.'/../models/AuditLogModel.php'; + } + + /** + * Test view. + * + * @covers ::view + */ + final public function testView() + { + $this->setMockElementsService(); + + $service = new AuditLogService(); + $result = $service->view(1); + + $this->assertCount(1, $result); + } + + /** + * Mock ElementsService. + */ + private function setMockElementsService() + { + $mock = $this->getMockBuilder('Craft\ElementsService') + ->disableOriginalConstructor() + ->setMethods(array('getCriteria')) + ->getMock(); + + $criteria = $this->getMockElementCriteriaModel(); + + $mock->expects($this->any())->method('getCriteria')->willReturn($criteria); + + $this->setComponent(craft(), 'elements', $mock); + } + + /** + * Mock ElementCriteriaModel. + * + * @return ElementCriteriaModel + */ + private function getMockElementCriteriaModel() + { + $mock = $this->getMockBuilder('Craft\ElementCriteriaModel') + ->disableOriginalConstructor() + ->setMethods(array('__set', 'first', 'find')) + ->getMock(); + + $log = $this->getMockAuditLogModel(); + + $mock->expects($this->any())->method('__set')->willReturn(true); + $mock->expects($this->any())->method('first')->willReturn($log); + $mock->expects($this->any())->method('find')->willReturn(array($log)); + + return $mock; + } + + /** + * Mock EntryModel. + * + * @return EntryModel + */ + private function getMockAuditLogModel() + { + $mock = $this->getMockBuilder('Craft\AuditLogModel') + ->disableOriginalConstructor() + ->setMethods(array('__get')) + ->getMock(); + + $mock->expects($this->any())->method('__get')->willReturn($this->returnCallback(function ($attribute) { + switch ($attribute) { + case 'before': + case 'after': + return array(); + + default: + return 'test'; + } + })); + + return $mock; + } +} diff --git a/tests/AuditLogTest.php b/tests/AuditLogTest.php deleted file mode 100644 index e415690..0000000 --- a/tests/AuditLogTest.php +++ /dev/null @@ -1,48 +0,0 @@ - - * @copyright Copyright (c) 2015, author - * @license http://buildwithcraft.com/license Craft License Agreement - * - * @link http://github.com/boboldehampsink - */ -class AuditLogTest extends BaseTest -{ - /** - * Load the plugin component. - */ - public function setUp() - { - - // Load plugins - $pluginsService = craft()->getComponent('plugins'); - $pluginsService->loadPlugins(); - } - - /** - * Test if viewing and parsing works. - */ - public function testActionDownload() - { - - // Get first log item - $log = craft()->auditLog->view(1); - - // Only test if already set - if ($log) { - - // $log is a model, want to break that down - $result = craft()->auditLog->parseFieldData('title', $log); - - // Result is always a string - $this->assertInternalType('string', $result); - } - } -} From da512928fca5c22f0f509e4c3388b7c6cd68a884 Mon Sep 17 00:00:00 2001 From: Bob Olde Hampsink Date: Tue, 23 Feb 2016 15:22:52 +0100 Subject: [PATCH 06/15] Fixed some scrutinizer issues --- .scrutinizer.yml | 2 +- controllers/AuditLogController.php | 2 +- elementtypes/AuditLogElementType.php | 57 +++++++++++++--------------- tests/AuditLogServiceTest.php | 3 ++ 4 files changed, 31 insertions(+), 33 deletions(-) diff --git a/.scrutinizer.yml b/.scrutinizer.yml index a456355..2f24d5a 100644 --- a/.scrutinizer.yml +++ b/.scrutinizer.yml @@ -1,7 +1,7 @@ checks: php: code_rating: true - duplication: true + duplication: false tools: external_code_coverage: true diff --git a/controllers/AuditLogController.php b/controllers/AuditLogController.php index 06a8a35..490cda1 100644 --- a/controllers/AuditLogController.php +++ b/controllers/AuditLogController.php @@ -49,7 +49,7 @@ public function actionDownload() $log = craft()->auditLog->log($criteria); // Set status attribute - $attributes['status'] = Craft::t('Status'); + $attributes = array('status' => Craft::t('Status')); // Get nice attributes $availableAttributes = $elementType->defineAvailableTableAttributes(); diff --git a/elementtypes/AuditLogElementType.php b/elementtypes/AuditLogElementType.php index 08e7bb6..18010c2 100644 --- a/elementtypes/AuditLogElementType.php +++ b/elementtypes/AuditLogElementType.php @@ -43,9 +43,9 @@ public function hasStatuses() public function getStatuses() { return array( - AuditLogModel::CREATED => Craft::t('Created'), + AuditLogModel::CREATED => Craft::t('Created'), AuditLogModel::MODIFIED => Craft::t('Modified'), - AuditLogModel::DELETED => Craft::t('Deleted'), + AuditLogModel::DELETED => Craft::t('Deleted'), ); } @@ -58,9 +58,9 @@ public function defineAvailableTableAttributes() { // Define default attributes $attributes = array( - 'type' => array('label' => Craft::t('Type')), - 'user' => array('label' => Craft::t('User')), - 'origin' => array('label' => Craft::t('Origin')), + 'type' => array('label' => Craft::t('Type')), + 'user' => array('label' => Craft::t('User')), + 'origin' => array('label' => Craft::t('Origin')), 'dateUpdated' => array('label' => Craft::t('Modified')), ); @@ -114,29 +114,24 @@ public function getTableAttributeHtml(BaseElementModel $element, $attribute) case 'dateCreated': case 'dateUpdated': return craft()->dateFormatter->formatDateTime($element->$attribute); - break; // Return clickable user link case 'user': $user = $element->getUser(); return $user ? ''.$user.'' : Craft::t('Guest'); - break; // Return clickable event origin case 'origin': return ''.$element->origin.''; - break; // Return view changes button case 'changes': return ''.Craft::t('View').''; - break; // Default behavior default: return $element->$attribute; - break; } } @@ -148,16 +143,16 @@ public function getTableAttributeHtml(BaseElementModel $element, $attribute) public function defineCriteriaAttributes() { return array( - 'type' => AttributeType::String, - 'userId' => AttributeType::Number, - 'origin' => AttributeType::String, - 'modified' => AttributeType::DateTime, - 'before' => AttributeType::String, - 'after' => AttributeType::String, - 'status' => AttributeType::String, - 'from' => AttributeType::DateTime, - 'to' => AttributeType::DateTime, - 'order' => array(AttributeType::String, 'default' => 'auditlog.id desc'), + 'type' => AttributeType::String, + 'userId' => AttributeType::Number, + 'origin' => AttributeType::String, + 'modified' => AttributeType::DateTime, + 'before' => AttributeType::String, + 'after' => AttributeType::String, + 'status' => AttributeType::String, + 'from' => AttributeType::DateTime, + 'to' => AttributeType::DateTime, + 'order' => array(AttributeType::String, 'default' => 'auditlog.id desc'), ); } @@ -286,7 +281,7 @@ public function getSources($context = null) // Set default sources $sources = array( '*' => array( - 'label' => Craft::t('All logs'), + 'label' => Craft::t('All logs'), ), array('heading' => Craft::t('Elements')), ); @@ -294,9 +289,9 @@ public function getSources($context = null) // Show sources for entries when enabled if (in_array(ElementType::Entry, $settings->enabled)) { $sources['entries'] = array( - 'label' => Craft::t('Entries'), - 'criteria' => array( - 'type' => ElementType::Entry, + 'label' => Craft::t('Entries'), + 'criteria' => array( + 'type' => ElementType::Entry, ), ); } @@ -304,9 +299,9 @@ public function getSources($context = null) // Show sources for categories when enabled if (in_array(ElementType::Category, $settings->enabled)) { $sources['categories'] = array( - 'label' => Craft::t('Categories'), - 'criteria' => array( - 'type' => ElementType::Category, + 'label' => Craft::t('Categories'), + 'criteria' => array( + 'type' => ElementType::Category, ), ); } @@ -314,9 +309,9 @@ public function getSources($context = null) // Show sources for users when enabled if (in_array(ElementType::User, $settings->enabled)) { $sources['users'] = array( - 'label' => Craft::t('Users'), - 'criteria' => array( - 'type' => ElementType::User, + 'label' => Craft::t('Users'), + 'criteria' => array( + 'type' => ElementType::User, ), ); } @@ -344,7 +339,7 @@ public function getSources($context = null) public function defineSortableAttributes() { // Set modified first - $attributes['dateUpdated'] = Craft::t('Modified'); + $attributes = array('dateUpdated' => Craft::t('Modified')); // Get table attributes $attributes = array_merge($attributes, parent::defineSortableAttributes()); diff --git a/tests/AuditLogServiceTest.php b/tests/AuditLogServiceTest.php index 2395533..41f8eea 100644 --- a/tests/AuditLogServiceTest.php +++ b/tests/AuditLogServiceTest.php @@ -12,6 +12,9 @@ * @license MIT * * @link http://github.com/boboldehampsink + * + * @coversDefaultClass Craft\AuditLogService + * @covers :: */ class AuditLogServiceTest extends BaseTest { From 1444a014f495b9fe03520ce6a7592b9bab7aded9 Mon Sep 17 00:00:00 2001 From: Bob Olde Hampsink Date: Tue, 23 Feb 2016 15:28:41 +0100 Subject: [PATCH 07/15] Fixed failing test --- tests/AuditLogServiceTest.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/AuditLogServiceTest.php b/tests/AuditLogServiceTest.php index 41f8eea..a8cc313 100644 --- a/tests/AuditLogServiceTest.php +++ b/tests/AuditLogServiceTest.php @@ -43,7 +43,7 @@ final public function testView() $service = new AuditLogService(); $result = $service->view(1); - $this->assertCount(1, $result); + $this->assertInstanceOf('Craft\AuditLogModel', $result); } /** @@ -93,7 +93,7 @@ private function getMockAuditLogModel() { $mock = $this->getMockBuilder('Craft\AuditLogModel') ->disableOriginalConstructor() - ->setMethods(array('__get')) + ->setMethods(array('__get', 'setAttribute')) ->getMock(); $mock->expects($this->any())->method('__get')->willReturn($this->returnCallback(function ($attribute) { @@ -106,6 +106,7 @@ private function getMockAuditLogModel() return 'test'; } })); + $mock->expects($this->any())->method('setAttribute')->willReturn(true); return $mock; } From e0670d3cf0b24dce2737286a75abd27de28ed4d4 Mon Sep 17 00:00:00 2001 From: Bob Olde Hampsink Date: Tue, 23 Feb 2016 16:24:30 +0100 Subject: [PATCH 08/15] Added unit tests for audit log service --- services/AuditLogService.php | 8 +- tests/AuditLogServiceTest.php | 153 ++++++++++++++++++++++++++++++++-- 2 files changed, 154 insertions(+), 7 deletions(-) diff --git a/services/AuditLogService.php b/services/AuditLogService.php index f363e8f..278dc66 100644 --- a/services/AuditLogService.php +++ b/services/AuditLogService.php @@ -77,7 +77,6 @@ public function view($id) */ public function parseFieldData($handle, $data) { - // Do we have any data at all if (!is_null($data)) { @@ -145,10 +144,11 @@ public function parseFieldData($handle, $data) * @param int $id * @param array $before * @param array $after + * + * @return array */ public function elementHasChanged($elementType, $id, $before, $after) { - // Flatten arrays $flatBefore = ArrayHelper::flattenArray($before); $flatAfter = ArrayHelper::flattenArray($after); @@ -177,6 +177,8 @@ public function elementHasChanged($elementType, $id, $before, $after) )); $this->onElementChanged($event); } + + return $diff; } /** @@ -193,6 +195,8 @@ public function onElementChanged(Event $event) * Fires an "onFieldChanged" event. * * @param Event $event + * + * @codeCoverageIgnore */ public function onFieldChanged(Event $event) { diff --git a/tests/AuditLogServiceTest.php b/tests/AuditLogServiceTest.php index a8cc313..b82f7c7 100644 --- a/tests/AuditLogServiceTest.php +++ b/tests/AuditLogServiceTest.php @@ -46,6 +46,102 @@ final public function testView() $this->assertInstanceOf('Craft\AuditLogModel', $result); } + /** + * Test parseFieldData. + * + * @param string $handle + * @param mixed $data + * @param string $expected + * + * @covers ::parseFieldData + * @dataProvider provideFieldData + */ + final public function testParseFieldData($handle, $data, $expected) + { + $this->setMockFieldsService($handle); + + $service = new AuditLogService(); + $result = $service->parseFieldData($handle, $data); + + $this->assertSame($result, $expected); + } + + /** + * Test elementHasChanged. + * + * @covers ::elementHasChanged + * @covers ::onElementChanged + */ + final public function testElementHasChanged() + { + $before = array( + 'test' => array( + 'label' => 'test1', + 'value' => 'test1', + ), + ); + + $after = array( + 'test' => array( + 'label' => 'test2', + 'value' => 'test2', + ), + ); + + $service = new AuditLogService(); + $result = $service->elementHasChanged(ElementType::Entry, 1, $before, $after); + + $this->assertInternalType('array', $result); + } + + /** + * Provide field data. + * + * @return array + */ + final public function provideFieldData() + { + require_once __DIR__.'/../models/AuditLogModel.php'; + + return array( + 'Parse ElementCriteriaModel' => array( + 'element', + $this->getMockElementCriteriaModel(), + 'test, test', + ), + 'Parse Lightswitch with "no" option' => array( + 'lightswitch', + '0', + Craft::t('No'), + ), + 'Parse Lightswitch with "yes" option' => array( + 'lightswitch', + '1', + Craft::t('Yes'), + ), + 'Parse empty data' => array( + 'empty', + null, + '', + ), + 'Parse data array' => array( + 'array', + array('test', 'test'), + 'test, test', + ), + 'Parse data object' => array( + 'object', + call_user_func(function () { + $class = new \stdClass(); + $class->test = 'test'; + + return $class; + }), + 'test', + ), + ); + } + /** * Mock ElementsService. */ @@ -72,14 +168,15 @@ private function getMockElementCriteriaModel() { $mock = $this->getMockBuilder('Craft\ElementCriteriaModel') ->disableOriginalConstructor() - ->setMethods(array('__set', 'first', 'find')) + ->setMethods(array('__set', '__get', 'first', 'find')) ->getMock(); $log = $this->getMockAuditLogModel(); $mock->expects($this->any())->method('__set')->willReturn(true); + $mock->expects($this->any())->method('__get')->willReturn(true); $mock->expects($this->any())->method('first')->willReturn($log); - $mock->expects($this->any())->method('find')->willReturn(array($log)); + $mock->expects($this->any())->method('find')->willReturn(array($log, $log)); return $mock; } @@ -93,14 +190,20 @@ private function getMockAuditLogModel() { $mock = $this->getMockBuilder('Craft\AuditLogModel') ->disableOriginalConstructor() - ->setMethods(array('__get', 'setAttribute')) + ->setMethods(array('__toString', '__get', 'setAttribute')) ->getMock(); - $mock->expects($this->any())->method('__get')->willReturn($this->returnCallback(function ($attribute) { + $mock->expects($this->any())->method('__toString')->willReturn('test'); + $mock->expects($this->any())->method('__get')->will($this->returnCallback(function ($attribute) { switch ($attribute) { case 'before': case 'after': - return array(); + return array( + 'test' => array( + 'label' => 'test', + 'value' => 'test', + ), + ); default: return 'test'; @@ -110,4 +213,44 @@ private function getMockAuditLogModel() return $mock; } + + /** + * Mock FieldsService. + * + * @param string $handle + */ + private function setMockFieldsService($handle) + { + $mock = $this->getMockBuilder('Craft\FieldsService') + ->disableOriginalConstructor() + ->setMethods(array('getFieldByHandle')) + ->getMock(); + + $field = $this->getMockFieldModel($handle); + + $mock->expects($this->any())->method('getFieldByHandle')->willReturn($field); + + $this->setComponent(craft(), 'fields', $mock); + } + + /** + * Mock FieldModel. + * + * @param string $handle + * + * @return FieldModel + */ + private function getMockFieldModel($handle) + { + $mock = $this->getMockBuilder('Craft\FieldsService') + ->disableOriginalConstructor() + ->setMethods(array('__get')) + ->getMock(); + + $mock->expects($this->any())->method('__get')->will($this->returnCallback(function ($attribute) use ($handle) { + return $handle == 'element' ? AuditLogModel::FieldTypeEntries : AuditLogModel::FieldTypeLightswitch; + })); + + return $mock; + } } From e5caae0f3cb67a24ed2346e309610a2219a45b8c Mon Sep 17 00:00:00 2001 From: Bob Olde Hampsink Date: Wed, 24 Feb 2016 10:03:33 +0100 Subject: [PATCH 09/15] Added unit tests for auditlog user service --- services/AuditLog_UserService.php | 158 +++++++------ tests/AuditLog_UserServiceTest.php | 342 +++++++++++++++++++++++++++++ 2 files changed, 430 insertions(+), 70 deletions(-) create mode 100644 tests/AuditLog_UserServiceTest.php diff --git a/services/AuditLog_UserService.php b/services/AuditLog_UserService.php index e02ee97..d03e2a8 100644 --- a/services/AuditLog_UserService.php +++ b/services/AuditLog_UserService.php @@ -27,111 +27,130 @@ class AuditLog_UserService extends BaseApplicationComponent * * @var array */ - public $after = array(); + public $after = array(); /** - * Initialize the category saving/deleting events. + * Initialize the user saving/deleting events. + * + * @codeCoverageIgnore */ public function log() { - // Get values before saving - craft()->on('users.onBeforeSaveUser', function (Event $event) { - - // Get user id to save - $id = $event->params['user']->id; - - if (!$event->params['isNewUser']) { + craft()->on('users.onBeforeSaveUser', array($this, 'onBeforeSaveUser')); - // Get old user from db - $user = UserModel::populateModel(UserRecord::model()->findById($id)); - - // Get fields - craft()->auditLog_user->before = craft()->auditLog_user->fields($user); - } else { + // Get values after saving + craft()->on('users.onSaveUser', array($this, 'onSaveUser')); - // Get fields - craft()->auditLog_user->before = craft()->auditLog_user->fields($event->params['user'], true); - } + // Get values before deleting + craft()->on('users.onBeforeDeleteUser', array($this, 'onBeforeDeleteUser')); + } - }); + /** + * Handle the onBeforeSaveUser event. + * + * @param Event $event + */ + public function onBeforeSaveUser(Event $event) + { + // Get user id to save + $id = $event->params['user']->id; - // Get values after saving - craft()->on('users.onSaveUser', function (Event $event) { + if (!$event->params['isNewUser']) { - // Get saved user - $user = $event->params['user']; + // Get old user from db + $user = UserModel::populateModel(UserRecord::model()->findById($id)); // Get fields - craft()->auditLog_user->after = craft()->auditLog_user->fields($user); + $this->before = $this->fields($user); + } else { - // New row - $log = new AuditLogRecord(); + // Get fields + $this->before = $this->fields($event->params['user'], true); + } + } - // Set user id - $log->userId = craft()->userSession->getUser() ? craft()->userSession->getUser()->id : $user->id; + /** + * Handle the onSaveUser event. + * + * @param Event $event + */ + public function onSaveUser(Event $event) + { + // Get saved user + $user = $event->params['user']; - // Set element type - $log->type = ElementType::User; + // Get fields + $this->after = $this->fields($user); - // Set origin - $log->origin = craft()->request->isCpRequest() ? craft()->config->get('cpTrigger').'/'.craft()->request->path : craft()->request->path; + // New row + $log = new AuditLogRecord(); - // Set before - $log->before = craft()->auditLog_user->before; + // Set user id + $log->userId = craft()->userSession->getUser() ? craft()->userSession->getUser()->id : $user->id; - // Set after - $log->after = craft()->auditLog_user->after; + // Set element type + $log->type = ElementType::User; - // Set status - $log->status = ($event->params['isNewUser'] ? AuditLogModel::CREATED : AuditLogModel::MODIFIED); + // Set origin + $log->origin = craft()->request->isCpRequest() ? craft()->config->get('cpTrigger').'/'.craft()->request->path : craft()->request->path; - // Save row - $log->save(false); + // Set before + $log->before = $this->before; - // Callback - craft()->auditLog->elementHasChanged(ElementType::User, $user->id, craft()->auditLog_user->before, craft()->auditLog_user->after); + // Set after + $log->after = $this->after; - }); + // Set status + $log->status = ($event->params['isNewUser'] ? AuditLogModel::CREATED : AuditLogModel::MODIFIED); - // Get values before deleting - craft()->on('users.onBeforeDeleteUser', function (Event $event) { + // Save row + $log->save(false); - // Get deleted user - $user = $event->params['user']; + // Callback + craft()->auditLog->elementHasChanged(ElementType::User, $user->id, $this->before, $this->after); + } - // Get fields - craft()->auditLog_user->before = craft()->auditLog_user->fields($user); - craft()->auditLog_user->after = craft()->auditLog_user->fields($user, true); + /** + * Handle the onBeforeDeleteUser event. + * + * @param Event $event + */ + public function onBeforeDeleteUser(Event $event) + { + // Get deleted user + $user = $event->params['user']; - // New row - $log = new AuditLogRecord(); + // Get fields + $this->before = $this->fields($user); + $this->after = $this->fields($user, true); - // Set user id - $log->userId = craft()->userSession->getUser()->id; + // New row + $log = new AuditLogRecord(); - // Set element type - $log->type = ElementType::User; + // Set user id + $log->userId = craft()->userSession->getUser()->id; - // Set origin - $log->origin = craft()->request->isCpRequest() ? craft()->config->get('cpTrigger').'/'.craft()->request->path : craft()->request->path; + // Set element type + $log->type = ElementType::User; - // Set before - $log->before = craft()->auditLog_user->before; + // Set origin + $log->origin = craft()->request->isCpRequest() ? craft()->config->get('cpTrigger').'/'.craft()->request->path : craft()->request->path; - // Set after - $log->after = craft()->auditLog_user->after; + // Set before + $log->before = $this->before; - // Set status - $log->status = AuditLogModel::DELETED; + // Set after + $log->after = $this->after; - // Save row - $log->save(false); + // Set status + $log->status = AuditLogModel::DELETED; - // Callback - craft()->auditLog->elementHasChanged(ElementType::User, $user->id, craft()->auditLog_user->before, craft()->auditLog_user->after); + // Save row + $log->save(false); - }); + // Callback + craft()->auditLog->elementHasChanged(ElementType::User, $user->id, $this->before, $this->after); } /** @@ -144,7 +163,6 @@ public function log() */ public function fields(UserModel $user, $empty = false) { - // Check if we are saving new groups $groupIds = craft()->request->getPost('groups', false); diff --git a/tests/AuditLog_UserServiceTest.php b/tests/AuditLog_UserServiceTest.php new file mode 100644 index 0000000..9e7b69c --- /dev/null +++ b/tests/AuditLog_UserServiceTest.php @@ -0,0 +1,342 @@ + + * @copyright Copyright (c) 2015, Bob Olde Hampsink + * @license MIT + * + * @link http://github.com/boboldehampsink + * + * @coversDefaultClass Craft\AuditLog_UserService + * @covers :: + */ +class AuditLog_UserServiceTest extends BaseTest +{ + /** + * {@inheritdoc} + */ + public static function setUpBeforeClass() + { + // Set up parent + parent::setUpBeforeClass(); + + // Require dependencies + require_once __DIR__.'/../services/AuditLogService.php'; + require_once __DIR__.'/../services/AuditLog_UserService.php'; + require_once __DIR__.'/../records/AuditLogRecord.php'; + } + + /** + * {@inheritdoc} + */ + public function teardown() + { + parent::teardown(); + AuditLogRecord::$db = craft()->db; + } + + /** + * Test onBeforeSaveUser. + * + * @param UserModel $user + * @param bool $isNewUser + * + * @covers ::onBeforeSaveUser + * @covers ::fields + * @dataProvider provideSaveUserEvents + */ + final public function testOnBeforeSaveUser(UserModel $user, $isNewUser) + { + $this->setMockAuditLogService(); + + $service = new AuditLog_UserService(); + $event = new Event($service, array( + 'user' => $user, + 'isNewUser' => $isNewUser, + )); + $service->onBeforeSaveUser($event); + + $this->assertArrayHasKey('username', $service->before); + } + + /** + * Test onSaveUser. + * + * @param UserModel $user + * @param bool $isNewUser + * + * @covers ::onSaveUser + * @covers ::fields + * @dataProvider provideSaveUserEvents + */ + final public function testOnSaveUser(UserModel $user, $isNewUser) + { + AuditLogRecord::$db = $this->setMockDbConnection(); + + $this->setMockAuditLogService(); + $this->setMockUserGroupsService(); + $this->setMockUserSessionService(); + + $service = new AuditLog_UserService(); + $event = new Event($service, array( + 'user' => $user, + 'isNewUser' => $isNewUser, + )); + $service->onSaveUser($event); + + $this->assertArrayHasKey('username', $service->after); + } + + /** + * Test onBeforeDeleteUser. + * + * @param UserModel $user + * + * @covers ::onBeforeDeleteUser + * @covers ::fields + * @dataProvider provideSaveUserEvents + */ + final public function testOnBeforeDeleteUser(UserModel $user) + { + AuditLogRecord::$db = $this->setMockDbConnection(); + + $this->setMockAuditLogService(); + $this->setMockUserGroupsService(); + $this->setMockUserSessionService(); + + $service = new AuditLog_UserService(); + $event = new Event($service, array( + 'user' => $user, + )); + $service->onBeforeDeleteUser($event); + + $this->assertArrayHasKey('username', $service->after); + } + + /** + * Provide saveUser events. + * + * @return array + */ + final public function provideSaveUserEvents() + { + $user = $this->getMockUserModel(); + + return array( + 'With new user' => array($user, true), + 'Without new user' => array($user, false), + 'With posted groups' => call_user_func(function () use ($user) { + $this->setMockRequestService(); + + return array($user, false); + }), + ); + } + + /** + * Mock RequestService. + */ + private function setMockRequestService() + { + $mock = $this->getMockBuilder('Craft\HttpRequestService') + ->disableOriginalConstructor() + ->setMethods(array('getPost')) + ->getMock(); + + $mock->expects($this->any())->method('getPost')->willReturn(array(1, 2)); + + $this->setComponent(craft(), 'request', $mock); + } + + /** + * Mock UserModel. + * + * @return UserModel + */ + private function getMockUserModel() + { + $mock = $this->getMockBuilder('Craft\UserModel') + ->disableOriginalConstructor() + ->setMethods(array('__get')) + ->getMock(); + + $mock->expects($this->any())->method('__get')->willReturn('test'); + + return $mock; + } + + /** + * Mock AuditLogService. + */ + private function setMockAuditLogService() + { + $mock = $this->getMockBuilder('Craft\AuditLogService') + ->disableOriginalConstructor() + ->setMethods(array('elementHasChanged', 'parseFieldData')) + ->getMock(); + + $mock->expects($this->any())->method('elementHasChanged')->willReturn(true); + $mock->expects($this->any())->method('parseFieldData')->willReturn('test'); + + $this->setComponent(craft(), 'auditLog', $mock); + } + + /** + * Mock DbConnection. + * + * @return DbConnection + */ + private function setMockDbConnection() + { + $mock = $this->getMockBuilder('Craft\DbConnection') + ->disableOriginalConstructor() + ->setMethods(array('createCommand', 'getSchema')) + ->getMock(); + $mock->autoConnect = false; // Do not auto connect + + $command = $this->getMockDbCommand($mock); + $schema = $this->getMockDbSchema($mock); + + $mock->expects($this->any())->method('createCommand')->willReturn($command); + $mock->expects($this->any())->method('getSchema')->willReturn($schema); + + $this->setComponent(craft(), 'db', $mock); + + return $mock; + } + + /** + * Mock DbCommand. + * + * @param DbConnection $connection + * + * @return DbCommand + */ + private function getMockDbCommand(DbConnection $connection) + { + $mock = $this->getMockBuilder('Craft\DbCommand') + ->setConstructorArgs(array($connection)) + ->setMethods(array('execute', 'prepare')) + ->getMock(); + + $mock->expects($this->any())->method('execute')->willReturn(true); + $mock->expects($this->any())->method('prepare')->willReturn(true); + + return $mock; + } + + /** + * Mock MysqlSchema. + * + * @param DbConncetion $connection + * + * @return MysqlSchema + */ + private function getMockDbSchema(DbConnection $connection) + { + $mock = $this->getMockBuilder('Craft\MysqlSchema') + ->disableOriginalConstructor() + ->setMethods(array('getTable', 'getCommandBuilder')) + ->getMock(); + + $table = new \CMysqlTableSchema(); + $table->columns = array( + 'id' => new \CMysqlColumnSchema(), + 'userId' => new \CMysqlColumnSchema(), + 'type' => new \CMysqlColumnSchema(), + 'origin' => new \CMysqlColumnSchema(), + 'before' => new \CMysqlColumnSchema(), + 'after' => new \CMysqlColumnSchema(), + 'status' => new \CMysqlColumnSchema(), + 'type' => new \CMysqlColumnSchema(), + 'dateCreated' => new \CMysqlColumnSchema(), + 'dateUpdated' => new \CMysqlColumnSchema(), + 'uid' => new \CMysqlColumnSchema(), + ); + $builder = $this->getMockCommandBuilder($connection); + + $mock->expects($this->any())->method('getTable')->willReturn($table); + $mock->expects($this->any())->method('getCommandBuilder')->willReturn($builder); + + return $mock; + } + + /** + * Mock CdbCommandBuilder. + * + * @param DbConnection $connection + * + * @return \CdbCommandBuilder + */ + private function getMockCommandBuilder(DbConnection $connection) + { + $mock = $this->getMockBuilder('\CdbCommandBuilder') + ->disableOriginalConstructor() + ->setMethods(array('createInsertCommand')) + ->getMock(); + + $command = $this->getMockDbCommand($connection); + + $mock->expects($this->any())->method('createInsertCommand')->willReturn($command); + + return $mock; + } + + /** + * Mock UserGroupsService. + */ + private function setMockUserGroupsService() + { + $mock = $this->getMockBuilder('Craft\UserGroupsService') + ->disableOriginalConstructor() + ->setMethods(array('getGroupsByUserId', 'getGroupById')) + ->getMock(); + + $group = $this->getMockUserGroupModel(); + + $mock->expects($this->any())->method('getGroupsByUserId')->willReturn(array($group)); + $mock->expects($this->any())->method('getGroupById')->willReturn($group); + + $this->setComponent(craft(), 'userGroups', $mock); + } + + /** + * Mock UserSessionService. + */ + private function setMockUserSessionService() + { + $mock = $this->getMockBuilder('Craft\UserSessionService') + ->disableOriginalConstructor() + ->setMethods(array('getUser')) + ->getMock(); + + $user = $this->getMockUserModel(); + + $mock->expects($this->any())->method('getUser')->willReturn($user); + + $this->setComponent(craft(), 'userSession', $mock); + } + + /** + * Mock UserGroupModel. + * + * @return UserGroupModel + */ + private function getMockUserGroupModel() + { + $mock = $this->getMockBuilder('Craft\UserGroupModel') + ->disableOriginalConstructor() + ->setMethods(array('__toString')) + ->getMock(); + + $mock->expects($this->any())->method('__toString')->willReturn('test'); + + return $mock; + } +} From 355c47c4eef203d84da0f89912a4c65407ce4558 Mon Sep 17 00:00:00 2001 From: Bob Olde Hampsink Date: Wed, 24 Feb 2016 11:26:35 +0100 Subject: [PATCH 10/15] Fixed unit tests to run independently --- tests/AuditLog_UserServiceTest.php | 177 ++++++++++++++++++++--------- 1 file changed, 123 insertions(+), 54 deletions(-) diff --git a/tests/AuditLog_UserServiceTest.php b/tests/AuditLog_UserServiceTest.php index 9e7b69c..8b62178 100644 --- a/tests/AuditLog_UserServiceTest.php +++ b/tests/AuditLog_UserServiceTest.php @@ -32,45 +32,13 @@ public static function setUpBeforeClass() require_once __DIR__.'/../records/AuditLogRecord.php'; } - /** - * {@inheritdoc} - */ - public function teardown() - { - parent::teardown(); - AuditLogRecord::$db = craft()->db; - } - - /** - * Test onBeforeSaveUser. - * - * @param UserModel $user - * @param bool $isNewUser - * - * @covers ::onBeforeSaveUser - * @covers ::fields - * @dataProvider provideSaveUserEvents - */ - final public function testOnBeforeSaveUser(UserModel $user, $isNewUser) - { - $this->setMockAuditLogService(); - - $service = new AuditLog_UserService(); - $event = new Event($service, array( - 'user' => $user, - 'isNewUser' => $isNewUser, - )); - $service->onBeforeSaveUser($event); - - $this->assertArrayHasKey('username', $service->before); - } - /** * Test onSaveUser. * * @param UserModel $user * @param bool $isNewUser * + * @covers ::onBeforeSaveUser * @covers ::onSaveUser * @covers ::fields * @dataProvider provideSaveUserEvents @@ -82,15 +50,18 @@ final public function testOnSaveUser(UserModel $user, $isNewUser) $this->setMockAuditLogService(); $this->setMockUserGroupsService(); $this->setMockUserSessionService(); + $this->setMockFieldsService(); + $this->setMockLocalizationService(); $service = new AuditLog_UserService(); $event = new Event($service, array( 'user' => $user, 'isNewUser' => $isNewUser, )); + $service->onBeforeSaveUser($event); $service->onSaveUser($event); - $this->assertArrayHasKey('username', $service->after); + $this->assertArrayHasKey('id', $service->after); } /** @@ -109,6 +80,7 @@ final public function testOnBeforeDeleteUser(UserModel $user) $this->setMockAuditLogService(); $this->setMockUserGroupsService(); $this->setMockUserSessionService(); + $this->setMockFieldsService(); $service = new AuditLog_UserService(); $event = new Event($service, array( @@ -116,7 +88,7 @@ final public function testOnBeforeDeleteUser(UserModel $user) )); $service->onBeforeDeleteUser($event); - $this->assertArrayHasKey('username', $service->after); + $this->assertArrayHasKey('id', $service->after); } /** @@ -126,15 +98,13 @@ final public function testOnBeforeDeleteUser(UserModel $user) */ final public function provideSaveUserEvents() { - $user = $this->getMockUserModel(); - return array( - 'With new user' => array($user, true), - 'Without new user' => array($user, false), - 'With posted groups' => call_user_func(function () use ($user) { + 'With new user' => array($this->getMockUserModel(), true), + 'Without new user' => array($this->getMockUserModel(), false), + 'With posted groups' => call_user_func(function () { $this->setMockRequestService(); - return array($user, false); + return array($this->getMockUserModel(), false); }), ); } @@ -163,10 +133,13 @@ private function getMockUserModel() { $mock = $this->getMockBuilder('Craft\UserModel') ->disableOriginalConstructor() - ->setMethods(array('__get')) + ->setMethods(array('__get', 'getAttributes')) ->getMock(); $mock->expects($this->any())->method('__get')->willReturn('test'); + $mock->expects($this->any())->method('getAttributes')->willReturn(array( + array('id' => 'test'), + )); return $mock; } @@ -206,8 +179,6 @@ private function setMockDbConnection() $mock->expects($this->any())->method('createCommand')->willReturn($command); $mock->expects($this->any())->method('getSchema')->willReturn($schema); - $this->setComponent(craft(), 'db', $mock); - return $mock; } @@ -222,11 +193,13 @@ private function getMockDbCommand(DbConnection $connection) { $mock = $this->getMockBuilder('Craft\DbCommand') ->setConstructorArgs(array($connection)) - ->setMethods(array('execute', 'prepare')) + ->setMethods(array('execute', 'prepare', 'queryRow', 'queryAll')) ->getMock(); $mock->expects($this->any())->method('execute')->willReturn(true); $mock->expects($this->any())->method('prepare')->willReturn(true); + $mock->expects($this->any())->method('queryRow')->willReturn(array('username' => 'test')); + $mock->expects($this->any())->method('queryAll')->willReturn(array(array('username' => 'test'))); return $mock; } @@ -259,7 +232,7 @@ private function getMockDbSchema(DbConnection $connection) 'dateUpdated' => new \CMysqlColumnSchema(), 'uid' => new \CMysqlColumnSchema(), ); - $builder = $this->getMockCommandBuilder($connection); + $builder = $this->getMockCommandBuilder($connection, $mock); $mock->expects($this->any())->method('getTable')->willReturn($table); $mock->expects($this->any())->method('getCommandBuilder')->willReturn($builder); @@ -271,19 +244,25 @@ private function getMockDbSchema(DbConnection $connection) * Mock CdbCommandBuilder. * * @param DbConnection $connection + * @param MysqlSchema $schema * * @return \CdbCommandBuilder */ - private function getMockCommandBuilder(DbConnection $connection) + private function getMockCommandBuilder(DbConnection $connection, MysqlSchema $schema) { $mock = $this->getMockBuilder('\CdbCommandBuilder') ->disableOriginalConstructor() - ->setMethods(array('createInsertCommand')) + ->setMethods(array('createInsertCommand', 'createPkCommand', 'createPkCriteria', 'createFindCommand', 'applyLimit', 'getSchema', 'getDbConnection', 'bindValues')) ->getMock(); $command = $this->getMockDbCommand($connection); $mock->expects($this->any())->method('createInsertCommand')->willReturn($command); + $mock->expects($this->any())->method('createPkCommand')->willReturn($command); + $mock->expects($this->any())->method('createPkCriteria')->willReturn($command); + $mock->expects($this->any())->method('createFindCommand')->willReturn($command); + $mock->expects($this->any())->method('getSchema')->willReturn($schema); + $mock->expects($this->any())->method('getDbConnection')->willReturn($connection); return $mock; } @@ -306,6 +285,23 @@ private function setMockUserGroupsService() $this->setComponent(craft(), 'userGroups', $mock); } + /** + * Mock UserGroupModel. + * + * @return UserGroupModel + */ + private function getMockUserGroupModel() + { + $mock = $this->getMockBuilder('Craft\UserGroupModel') + ->disableOriginalConstructor() + ->setMethods(array('__toString')) + ->getMock(); + + $mock->expects($this->any())->method('__toString')->willReturn('test'); + + return $mock; + } + /** * Mock UserSessionService. */ @@ -324,19 +320,92 @@ private function setMockUserSessionService() } /** - * Mock UserGroupModel. + * Mock FieldsService. + */ + private function setMockFieldsService() + { + $mock = $this->getMockBuilder('Craft\FieldsService') + ->disableOriginalConstructor() + ->setMethods(array('getLayoutByType', 'getFieldByHandle', 'getAllFields')) + ->getMock(); + + $layout = $this->getMockFieldLayoutModel(); + $field = $this->getMockFieldModel(); + + $mock->expects($this->any())->method('getLayoutByType')->willReturn($layout); + $mock->expects($this->any())->method('getFieldByHandle')->willReturn($field); + $mock->expects($this->any())->method('getAllFields')->willReturn(array($field)); + + $this->setComponent(craft(), 'fields', $mock); + } + + /** + * Mock FieldLayoutModel. * - * @return UserGroupModel + * @return FieldLayoutModel */ - private function getMockUserGroupModel() + private function getMockFieldLayoutModel() { - $mock = $this->getMockBuilder('Craft\UserGroupModel') + $mock = $this->getMockBuilder('Craft\FieldLayoutModel') ->disableOriginalConstructor() - ->setMethods(array('__toString')) + ->setMethods(array('getFields')) ->getMock(); - $mock->expects($this->any())->method('__toString')->willReturn('test'); + $fields = array($this->getMockFieldLayoutFieldModel()); + + $mock->expects($this->any())->method('getFields')->willReturn($fields); return $mock; } + + /** + * Mock FieldLayoutFieldModel. + * + * @return FieldLayoutFieldModel + */ + private function getMockFieldLayoutFieldModel() + { + $mock = $this->getMockBuilder('Craft\FieldLayoutFieldModel') + ->disableOriginalConstructor() + ->setMethods(array('getField')) + ->getMock(); + + $field = $this->getMockFieldModel(); + + $mock->expects($this->any())->method('getField')->willReturn($field); + + return $mock; + } + + /** + * Mock FieldModel. + * + * @return FieldModel + */ + private function getMockFieldModel() + { + $mock = $this->getMockBuilder('Craft\FieldModel') + ->disableOriginalConstructor() + ->setMethods(array('__get')) + ->getMock(); + + $mock->expects($this->any())->method('__get')->willReturn('test'); + + return $mock; + } + + /** + * Mock LocalizationService. + */ + private function setMockLocalizationService() + { + $mock = $this->getMockBuilder('Craft\LocalizationService') + ->disableOriginalConstructor() + ->setMethods(array('getPrimarySiteLocaleId')) + ->getMock(); + + $mock->expects($this->any())->method('getPrimarySiteLocaleId')->willReturn('nl'); + + $this->setComponent(craft(), 'i18n', $mock); + } } From 18e76c66c8a7c3c02bec858268992135d06f6025 Mon Sep 17 00:00:00 2001 From: Bob Olde Hampsink Date: Wed, 24 Feb 2016 11:50:12 +0100 Subject: [PATCH 11/15] Added unit tests for auditlog entry service --- services/AuditLog_EntryService.php | 195 ++++++++------ tests/AuditLog_EntryServiceTest.php | 398 ++++++++++++++++++++++++++++ 2 files changed, 506 insertions(+), 87 deletions(-) create mode 100644 tests/AuditLog_EntryServiceTest.php diff --git a/services/AuditLog_EntryService.php b/services/AuditLog_EntryService.php index f4a6bf9..51509ad 100644 --- a/services/AuditLog_EntryService.php +++ b/services/AuditLog_EntryService.php @@ -27,114 +27,133 @@ class AuditLog_EntryService extends BaseApplicationComponent * * @var array */ - public $after = array(); + public $after = array(); /** * Initialize the category saving/deleting events. + * + * @codeCoverageIgnore */ public function log() { - // Get values before saving - craft()->on('entries.onBeforeSaveEntry', function (Event $event) { - - // Get entry id to save - $id = $event->params['entry']->id; - - if (!$event->params['isNewEntry']) { + craft()->on('entries.onBeforeSaveEntry', array($this, 'onBeforeSaveEntry')); - // Get old entry from db - $entry = EntryModel::populateModel(EntryRecord::model()->findById($id)); - - // Get fields - craft()->auditLog_entry->before = craft()->auditLog_entry->fields($entry); - } else { + // Get values after saving + craft()->on('entries.onSaveEntry', array($this, 'onSaveEntry')); - // Get fields - craft()->auditLog_entry->before = craft()->auditLog_entry->fields($event->params['entry'], true); - } + // Get values before deleting + craft()->on('entries.onBeforeDeleteEntry', array($this, 'onBeforeDeleteEntry')); + } - }); + /** + * Handle the onBeforeSaveEntry event. + * + * @param Event $event + */ + public function onBeforeSaveEntry(Event $event) + { + // Get entry id to save + $id = $event->params['entry']->id; - // Get values after saving - craft()->on('entries.onSaveEntry', function (Event $event) { + if (!$event->params['isNewEntry']) { - // Get saved entry - $entry = $event->params['entry']; + // Get old entry from db + $entry = EntryModel::populateModel(EntryRecord::model()->findById($id)); // Get fields - craft()->auditLog_entry->after = craft()->auditLog_entry->fields($entry); + $this->before = $this->fields($entry); + } else { - // New row - $log = new AuditLogRecord(); + // Get fields + $this->before = $this->fields($event->params['entry'], true); + } + } - // Get user - $user = craft()->userSession->getUser(); + /** + * Handle the onSaveEntry event. + * + * @param Event $event + */ + public function onSaveEntry(Event $event) + { + // Get saved entry + $entry = $event->params['entry']; - // Set user id - $log->userId = $user ? $user->id : null; + // Get fields + $this->after = $this->fields($entry); - // Set element type - $log->type = ElementType::Entry; + // New row + $log = new AuditLogRecord(); - // Set origin - $log->origin = craft()->request->isCpRequest() ? craft()->config->get('cpTrigger').'/'.craft()->request->path : craft()->request->path; + // Get user + $user = craft()->userSession->getUser(); - // Set before - $log->before = craft()->auditLog_entry->before; + // Set user id + $log->userId = $user ? $user->id : null; - // Set after - $log->after = craft()->auditLog_entry->after; + // Set element type + $log->type = ElementType::Entry; - // Set status - $log->status = ($event->params['isNewEntry'] ? AuditLogModel::CREATED : AuditLogModel::MODIFIED); + // Set origin + $log->origin = craft()->request->isCpRequest() ? craft()->config->get('cpTrigger').'/'.craft()->request->path : craft()->request->path; - // Save row - $log->save(false); + // Set before + $log->before = $this->before; - // Callback - craft()->auditLog->elementHasChanged(ElementType::Entry, $entry->id, craft()->auditLog_entry->before, craft()->auditLog_entry->after); + // Set after + $log->after = $this->after; - }); + // Set status + $log->status = ($event->params['isNewEntry'] ? AuditLogModel::CREATED : AuditLogModel::MODIFIED); - // Get values before deleting - craft()->on('entries.onBeforeDeleteEntry', function (Event $event) { + // Save row + $log->save(false); - // Get deleted entry - $entry = $event->params['entry']; + // Callback + craft()->auditLog->elementHasChanged(ElementType::Entry, $entry->id, $this->before, $this->after); + } - // Get fields - craft()->auditLog_entry->before = craft()->auditLog_entry->fields($entry); - craft()->auditLog_entry->after = craft()->auditLog_entry->fields($entry, true); + /** + * Handle the onBeforeDeleteEntry event. + * + * @param Event $event + */ + public function onBeforeDeleteEntry(Event $event) + { + // Get deleted entry + $entry = $event->params['entry']; - // New row - $log = new AuditLogRecord(); + // Get fields + $this->before = $this->fields($entry); + $this->after = $this->fields($entry, true); - // Set user id - $log->userId = craft()->userSession->getUser()->id; + // New row + $log = new AuditLogRecord(); - // Set element type - $log->type = ElementType::Entry; + // Set user id + $log->userId = craft()->userSession->getUser()->id; - // Set origin - $log->origin = craft()->request->isCpRequest() ? craft()->config->get('cpTrigger').'/'.craft()->request->path : craft()->request->path; + // Set element type + $log->type = ElementType::Entry; - // Set before - $log->before = craft()->auditLog_entry->before; + // Set origin + $log->origin = craft()->request->isCpRequest() ? craft()->config->get('cpTrigger').'/'.craft()->request->path : craft()->request->path; - // Set after - $log->after = craft()->auditLog_entry->after; + // Set before + $log->before = $this->before; - // Set status - $log->status = AuditLogModel::DELETED; + // Set after + $log->after = $this->after; - // Save row - $log->save(false); + // Set status + $log->status = AuditLogModel::DELETED; - // Callback - craft()->auditLog->elementHasChanged(ElementType::Entry, $entry->id, craft()->auditLog_entry->before, craft()->auditLog_entry->after); + // Save row + $log->save(false); - }); + // Callback + craft()->auditLog->elementHasChanged(ElementType::Entry, $entry->id, $this->before, $this->after); } /** @@ -156,11 +175,11 @@ public function fields(EntryModel $entry, $empty = false) ), 'title' => array( 'label' => Craft::t('Title'), - 'value' => $entry->title, + 'value' => (string) $entry->getTitle(), ), 'section' => array( 'label' => Craft::t('Section'), - 'value' => $entry->section->name, + 'value' => (string) $entry->getSection(), ), ); @@ -190,21 +209,23 @@ public function fields(EntryModel $entry, $empty = false) // Get fieldlayout $entrytype = $entry->getType(); - $tabs = craft()->fields->getLayoutById($entrytype->fieldLayoutId)->getTabs(); - foreach ($tabs as $tab) { - foreach ($tab->getFields() as $field) { - - // Get field values - $field = $field->getField(); - $handle = $field->handle; - $label = $field->name; - $value = $empty ? '' : craft()->auditLog->parseFieldData($handle, $entry->$handle); - - // Set on fields - $fields[$handle] = array( - 'label' => $label, - 'value' => $value, - ); + if ($entrytype) { + $tabs = craft()->fields->getLayoutById($entrytype->fieldLayoutId)->getTabs(); + foreach ($tabs as $tab) { + foreach ($tab->getFields() as $field) { + + // Get field values + $field = $field->getField(); + $handle = $field->handle; + $label = $field->name; + $value = $empty ? '' : craft()->auditLog->parseFieldData($handle, $entry->$handle); + + // Set on fields + $fields[$handle] = array( + 'label' => $label, + 'value' => $value, + ); + } } } diff --git a/tests/AuditLog_EntryServiceTest.php b/tests/AuditLog_EntryServiceTest.php new file mode 100644 index 0000000..7eaf3d1 --- /dev/null +++ b/tests/AuditLog_EntryServiceTest.php @@ -0,0 +1,398 @@ + + * @copyright Copyright (c) 2015, Bob Olde Hampsink + * @license MIT + * + * @link http://github.com/boboldehampsink + * + * @coversDefaultClass Craft\AuditLog_EntryService + * @covers :: + */ +class AuditLog_EntryServiceTest extends BaseTest +{ + /** + * {@inheritdoc} + */ + public static function setUpBeforeClass() + { + // Set up parent + parent::setUpBeforeClass(); + + // Require dependencies + require_once __DIR__.'/../services/AuditLogService.php'; + require_once __DIR__.'/../services/AuditLog_EntryService.php'; + require_once __DIR__.'/../records/AuditLogRecord.php'; + } + + /** + * Test onSaveEntry. + * + * @param EntryModel $entry + * @param bool $isNewEntry + * + * @covers ::onBeforeSaveEntry + * @covers ::onSaveEntry + * @covers ::fields + * @dataProvider provideSaveEntryEvents + */ + final public function testOnSaveEntry(EntryModel $entry, $isNewEntry) + { + AuditLogRecord::$db = $this->setMockDbConnection(); + + $this->setMockAuditLogService(); + $this->setMockUserSessionService(); + $this->setMockFieldsService(); + $this->setMockLocalizationService(); + + $service = new AuditLog_EntryService(); + $event = new Event($service, array( + 'entry' => $entry, + 'isNewEntry' => $isNewEntry, + )); + $service->onBeforeSaveEntry($event); + $service->onSaveEntry($event); + + $this->assertArrayHasKey('id', $service->after); + } + + /** + * Test onBeforeDeleteEntry. + * + * @param EntryModel $entry + * + * @covers ::onBeforeDeleteEntry + * @covers ::fields + * @dataProvider provideSaveEntryEvents + */ + final public function testOnBeforeDeleteEntry(EntryModel $entry) + { + AuditLogRecord::$db = $this->setMockDbConnection(); + + $this->setMockAuditLogService(); + $this->setMockUserSessionService(); + $this->setMockFieldsService(); + + $service = new AuditLog_EntryService(); + $event = new Event($service, array( + 'entry' => $entry, + )); + $service->onBeforeDeleteEntry($event); + + $this->assertArrayHasKey('id', $service->after); + } + + /** + * Provide saveEntry events. + * + * @return array + */ + final public function provideSaveEntryEvents() + { + return array( + 'With new entry' => array($this->getMockEntryModel(), true), + 'Without new entry' => array($this->getMockEntryModel(), false), + ); + } + + /** + * Mock EntryModel. + * + * @return EntryModel + */ + private function getMockEntryModel() + { + $mock = $this->getMockBuilder('Craft\EntryModel') + ->disableOriginalConstructor() + ->setMethods(array('__get', 'getAttributes', 'getTitle', 'getSection', 'getType')) + ->getMock(); + + $mock->expects($this->any())->method('__get')->willReturn('test'); + $mock->expects($this->any())->method('getAttributes')->willReturn(array( + array('id' => 'test'), + )); + $mock->expects($this->any())->method('getTitle')->willReturn('test'); + + // Doesn't really matter if we mock sections and types - returning self is enough + $mock->expects($this->any())->method('getSection')->will($this->returnSelf()); + $mock->expects($this->any())->method('getType')->will($this->returnSelf()); + + return $mock; + } + + /** + * Mock AuditLogService. + */ + private function setMockAuditLogService() + { + $mock = $this->getMockBuilder('Craft\AuditLogService') + ->disableOriginalConstructor() + ->setMethods(array('elementHasChanged', 'parseFieldData')) + ->getMock(); + + $mock->expects($this->any())->method('elementHasChanged')->willReturn(true); + $mock->expects($this->any())->method('parseFieldData')->willReturn('test'); + + $this->setComponent(craft(), 'auditLog', $mock); + } + + /** + * Mock DbConnection. + * + * @return DbConnection + */ + private function setMockDbConnection() + { + $mock = $this->getMockBuilder('Craft\DbConnection') + ->disableOriginalConstructor() + ->setMethods(array('createCommand', 'getSchema')) + ->getMock(); + $mock->autoConnect = false; // Do not auto connect + + $command = $this->getMockDbCommand($mock); + $schema = $this->getMockDbSchema($mock); + + $mock->expects($this->any())->method('createCommand')->willReturn($command); + $mock->expects($this->any())->method('getSchema')->willReturn($schema); + + return $mock; + } + + /** + * Mock DbCommand. + * + * @param DbConnection $connection + * + * @return DbCommand + */ + private function getMockDbCommand(DbConnection $connection) + { + $mock = $this->getMockBuilder('Craft\DbCommand') + ->setConstructorArgs(array($connection)) + ->setMethods(array('execute', 'prepare', 'queryRow', 'queryAll')) + ->getMock(); + + $mock->expects($this->any())->method('execute')->willReturn(true); + $mock->expects($this->any())->method('prepare')->willReturn(true); + $mock->expects($this->any())->method('queryRow')->willReturn(array('authorId' => 1)); + $mock->expects($this->any())->method('queryAll')->willReturn(array(array('authorId' => 1))); + + return $mock; + } + + /** + * Mock MysqlSchema. + * + * @param DbConncetion $connection + * + * @return MysqlSchema + */ + private function getMockDbSchema(DbConnection $connection) + { + $mock = $this->getMockBuilder('Craft\MysqlSchema') + ->disableOriginalConstructor() + ->setMethods(array('getTable', 'getCommandBuilder')) + ->getMock(); + + $table = new \CMysqlTableSchema(); + $table->columns = array( + 'id' => new \CMysqlColumnSchema(), + 'userId' => new \CMysqlColumnSchema(), + 'type' => new \CMysqlColumnSchema(), + 'origin' => new \CMysqlColumnSchema(), + 'before' => new \CMysqlColumnSchema(), + 'after' => new \CMysqlColumnSchema(), + 'status' => new \CMysqlColumnSchema(), + 'type' => new \CMysqlColumnSchema(), + 'dateCreated' => new \CMysqlColumnSchema(), + 'dateUpdated' => new \CMysqlColumnSchema(), + 'uid' => new \CMysqlColumnSchema(), + ); + $builder = $this->getMockCommandBuilder($connection, $mock); + + $mock->expects($this->any())->method('getTable')->willReturn($table); + $mock->expects($this->any())->method('getCommandBuilder')->willReturn($builder); + + return $mock; + } + + /** + * Mock CdbCommandBuilder. + * + * @param DbConnection $connection + * @param MysqlSchema $schema + * + * @return \CdbCommandBuilder + */ + private function getMockCommandBuilder(DbConnection $connection, MysqlSchema $schema) + { + $mock = $this->getMockBuilder('\CdbCommandBuilder') + ->disableOriginalConstructor() + ->setMethods(array('createInsertCommand', 'createPkCommand', 'createPkCriteria', 'createFindCommand', 'applyLimit', 'getSchema', 'getDbConnection', 'bindValues')) + ->getMock(); + + $command = $this->getMockDbCommand($connection); + + $mock->expects($this->any())->method('createInsertCommand')->willReturn($command); + $mock->expects($this->any())->method('createPkCommand')->willReturn($command); + $mock->expects($this->any())->method('createPkCriteria')->willReturn($command); + $mock->expects($this->any())->method('createFindCommand')->willReturn($command); + $mock->expects($this->any())->method('getSchema')->willReturn($schema); + $mock->expects($this->any())->method('getDbConnection')->willReturn($connection); + + return $mock; + } + + /** + * Mock UserSessionService. + */ + private function setMockUserSessionService() + { + $mock = $this->getMockBuilder('Craft\UserSessionService') + ->disableOriginalConstructor() + ->setMethods(array('getUser')) + ->getMock(); + + $user = $this->getMockUserModel(); + + $mock->expects($this->any())->method('getUser')->willReturn($user); + + $this->setComponent(craft(), 'userSession', $mock); + } + + /** + * Mock UserModel. + * + * @return UserModel + */ + private function getMockUserModel() + { + $mock = $this->getMockBuilder('Craft\UserModel') + ->disableOriginalConstructor() + ->setMethods(array('__get')) + ->getMock(); + + $mock->expects($this->any())->method('__get')->willReturn('test'); + + return $mock; + } + + /** + * Mock FieldsService. + */ + private function setMockFieldsService() + { + $mock = $this->getMockBuilder('Craft\FieldsService') + ->disableOriginalConstructor() + ->setMethods(array('getLayoutByType', 'getFieldByHandle', 'getAllFields', 'getLayoutById')) + ->getMock(); + + $layout = $this->getMockFieldLayoutModel(); + $field = $this->getMockFieldModel(); + + $mock->expects($this->any())->method('getLayoutByType')->willReturn($layout); + $mock->expects($this->any())->method('getFieldByHandle')->willReturn($field); + $mock->expects($this->any())->method('getAllFields')->willReturn(array($field)); + $mock->expects($this->any())->method('getLayoutById')->willReturn($layout); + + $this->setComponent(craft(), 'fields', $mock); + } + + /** + * Mock FieldLayoutModel. + * + * @return FieldLayoutModel + */ + private function getMockFieldLayoutModel() + { + $mock = $this->getMockBuilder('Craft\FieldLayoutModel') + ->disableOriginalConstructor() + ->setMethods(array('getFields', 'getTabs')) + ->getMock(); + + $fields = array($this->getMockFieldLayoutFieldModel()); + $tabs = array($this->getMockFieldLayoutTabModel()); + + $mock->expects($this->any())->method('getFields')->willReturn($fields); + $mock->expects($this->any())->method('getTabs')->willReturn($tabs); + + return $mock; + } + + /** + * Mock FieldLayoutFieldModel. + * + * @return FieldLayoutFieldModel + */ + private function getMockFieldLayoutFieldModel() + { + $mock = $this->getMockBuilder('Craft\FieldLayoutFieldModel') + ->disableOriginalConstructor() + ->setMethods(array('getField')) + ->getMock(); + + $field = $this->getMockFieldModel(); + + $mock->expects($this->any())->method('getField')->willReturn($field); + + return $mock; + } + + /** + * Mock FieldLayoutFieldModel. + * + * @return FieldLayoutFieldModel + */ + private function getMockFieldLayoutTabModel() + { + $mock = $this->getMockBuilder('Craft\FieldLayoutTabModel') + ->disableOriginalConstructor() + ->setMethods(array('getFields')) + ->getMock(); + + $fields = array($this->getMockFieldLayoutFieldModel()); + + $mock->expects($this->any())->method('getFields')->willReturn($fields); + + return $mock; + } + + /** + * Mock FieldModel. + * + * @return FieldModel + */ + private function getMockFieldModel() + { + $mock = $this->getMockBuilder('Craft\FieldModel') + ->disableOriginalConstructor() + ->setMethods(array('__get')) + ->getMock(); + + $mock->expects($this->any())->method('__get')->willReturn('test'); + + return $mock; + } + + /** + * Mock LocalizationService. + */ + private function setMockLocalizationService() + { + $mock = $this->getMockBuilder('Craft\LocalizationService') + ->disableOriginalConstructor() + ->setMethods(array('getPrimarySiteLocaleId')) + ->getMock(); + + $mock->expects($this->any())->method('getPrimarySiteLocaleId')->willReturn('nl'); + + $this->setComponent(craft(), 'i18n', $mock); + } +} From 1630dd54fa58a675adf22d1008556bc7aed6f3b3 Mon Sep 17 00:00:00 2001 From: Bob Olde Hampsink Date: Wed, 24 Feb 2016 12:11:43 +0100 Subject: [PATCH 12/15] Added unit tests for audit log category service --- services/AuditLog_CategoryService.php | 163 ++++++----- tests/AuditLog_CategoryServiceTest.php | 390 +++++++++++++++++++++++++ 2 files changed, 481 insertions(+), 72 deletions(-) create mode 100644 tests/AuditLog_CategoryServiceTest.php diff --git a/services/AuditLog_CategoryService.php b/services/AuditLog_CategoryService.php index be95081..4caf7a8 100644 --- a/services/AuditLog_CategoryService.php +++ b/services/AuditLog_CategoryService.php @@ -27,114 +27,133 @@ class AuditLog_CategoryService extends BaseApplicationComponent * * @var array */ - public $after = array(); + public $after = array(); /** * Initialize the category saving/deleting events. + * + * @codeCoverageIgnore */ public function log() { - // Get values before saving - craft()->on('categories.onBeforeSaveCategory', function (Event $event) { - - // Get category id to save - $id = $event->params['category']->id; - - if (!$event->params['isNewCategory']) { + craft()->on('categories.onBeforeSaveCategory', array($this, 'onBeforeSaveCategory')); - // Get old category from db - $category = CategoryModel::populateModel(CategoryRecord::model()->findById($id)); - - // Get fields - craft()->auditLog_category->before = craft()->auditLog_category->fields($category); - } else { + // Get values after saving + craft()->on('categories.onSaveCategory', array($this, 'onSaveCategory')); - // Get fields - craft()->auditLog_category->before = craft()->auditLog_category->fields($event->params['category'], true); - } + // Get values before deleting + craft()->on('categories.onBeforeDeleteCategory', array($this, 'onBeforeDeleteCategory')); + } - }); + /** + * Handle the onBeforeSaveCategory event. + * + * @param Event $event + */ + public function onBeforeSaveCategory(Event $event) + { + // Get category id to save + $id = $event->params['category']->id; - // Get values after saving - craft()->on('categories.onSaveCategory', function (Event $event) { + if (!$event->params['isNewCategory']) { - // Get saved category - $category = $event->params['category']; + // Get old category from db + $category = CategoryModel::populateModel(CategoryRecord::model()->findById($id)); // Get fields - craft()->auditLog_category->after = craft()->auditLog_category->fields($category); + $this->before = $this->fields($category); + } else { - // New row - $log = new AuditLogRecord(); + // Get fields + $this->before = $this->fields($event->params['category'], true); + } + } - // Get user - $user = craft()->userSession->getUser(); + /** + * Handle the onSaveCategory event. + * + * @param Event $event + */ + public function onSaveCategory(Event $event) + { + // Get saved category + $category = $event->params['category']; - // Set user id - $log->userId = $user ? $user->id : null; + // Get fields + $this->after = $this->fields($category); - // Set element type - $log->type = ElementType::Category; + // New row + $log = new AuditLogRecord(); - // Set origin - $log->origin = craft()->request->isCpRequest() ? craft()->config->get('cpTrigger').'/'.craft()->request->path : craft()->request->path; + // Get user + $user = craft()->userSession->getUser(); - // Set before - $log->before = craft()->auditLog_category->before; + // Set user id + $log->userId = $user ? $user->id : null; - // Set after - $log->after = craft()->auditLog_category->after; + // Set element type + $log->type = ElementType::Category; - // Set status - $log->status = ($event->params['isNewCategory'] ? AuditLogModel::CREATED : AuditLogModel::MODIFIED); + // Set origin + $log->origin = craft()->request->isCpRequest() ? craft()->config->get('cpTrigger').'/'.craft()->request->path : craft()->request->path; - // Save row - $log->save(false); + // Set before + $log->before = $this->before; - // Callback - craft()->auditLog->elementHasChanged(ElementType::Category, $category->id, craft()->auditLog_category->before, craft()->auditLog_category->after); + // Set after + $log->after = $this->after; - }); + // Set status + $log->status = ($event->params['isNewCategory'] ? AuditLogModel::CREATED : AuditLogModel::MODIFIED); - // Get values before deleting - craft()->on('categories.onBeforeDeleteCategory', function (Event $event) { + // Save row + $log->save(false); - // Get deleted category - $category = $event->params['category']; + // Callback + craft()->auditLog->elementHasChanged(ElementType::Category, $category->id, $this->before, $this->after); + } - // Get fields - craft()->auditLog_category->before = craft()->auditLog_category->fields($category); - craft()->auditLog_category->after = craft()->auditLog_category->fields($category, true); + /** + * Handle the onBeforeDeleteCategory event. + * + * @param Event $event + */ + public function onBeforeDeleteCategory(Event $event) + { + // Get deleted category + $category = $event->params['category']; - // New row - $log = new AuditLogRecord(); + // Get fields + $this->before = $this->fields($category); + $this->after = $this->fields($category, true); - // Set user id - $log->userId = craft()->userSession->getUser()->id; + // New row + $log = new AuditLogRecord(); - // Set element type - $log->type = ElementType::Category; + // Set user id + $log->userId = craft()->userSession->getUser()->id; - // Set origin - $log->origin = craft()->request->isCpRequest() ? craft()->config->get('cpTrigger').'/'.craft()->request->path : craft()->request->path; + // Set element type + $log->type = ElementType::Category; - // Set before - $log->before = craft()->auditLog_category->before; + // Set origin + $log->origin = craft()->request->isCpRequest() ? craft()->config->get('cpTrigger').'/'.craft()->request->path : craft()->request->path; - // Set after - $log->after = craft()->auditLog_category->after; + // Set before + $log->before = $this->before; - // Set status - $log->status = AuditLogModel::DELETED; + // Set after + $log->after = $this->after; - // Save row - $log->save(false); + // Set status + $log->status = AuditLogModel::DELETED; - // Callback - craft()->auditLog->elementHasChanged(ElementType::Category, $category->id, craft()->auditLog_category->before, craft()->auditLog_category->after); + // Save row + $log->save(false); - }); + // Callback + craft()->auditLog->elementHasChanged(ElementType::Category, $category->id, $this->before, $this->after); } /** @@ -156,11 +175,11 @@ public function fields(CategoryModel $category, $empty = false) ), 'title' => array( 'label' => Craft::t('Title'), - 'value' => $category->title, + 'value' => (string) $category->getTitle(), ), 'group' => array( 'label' => Craft::t('Group'), - 'value' => $category->group->name, + 'value' => (string) $category->getGroup(), ), ); diff --git a/tests/AuditLog_CategoryServiceTest.php b/tests/AuditLog_CategoryServiceTest.php new file mode 100644 index 0000000..f93c120 --- /dev/null +++ b/tests/AuditLog_CategoryServiceTest.php @@ -0,0 +1,390 @@ + + * @copyright Copyright (c) 2015, Bob Olde Hampsink + * @license MIT + * + * @link http://github.com/boboldehampsink + * + * @coversDefaultClass Craft\AuditLog_CategoryService + * @covers :: + */ +class AuditLog_CategoryServiceTest extends BaseTest +{ + /** + * {@inheritdoc} + */ + public static function setUpBeforeClass() + { + // Set up parent + parent::setUpBeforeClass(); + + // Require dependencies + require_once __DIR__.'/../services/AuditLogService.php'; + require_once __DIR__.'/../services/AuditLog_CategoryService.php'; + require_once __DIR__.'/../records/AuditLogRecord.php'; + } + + /** + * Test onSaveCategory. + * + * @param CategoryModel $category + * @param bool $isNewCategory + * + * @covers ::onBeforeSaveCategory + * @covers ::onSaveCategory + * @covers ::fields + * @dataProvider provideSaveCategoryEvents + */ + final public function testOnSaveCategory(CategoryModel $category, $isNewCategory) + { + AuditLogRecord::$db = $this->setMockDbConnection(); + + $this->setMockAuditLogService(); + $this->setMockUserSessionService(); + $this->setMockFieldsService(); + $this->setMockLocalizationService(); + + $service = new AuditLog_CategoryService(); + $event = new Event($service, array( + 'category' => $category, + 'isNewCategory' => $isNewCategory, + )); + $service->onBeforeSaveCategory($event); + $service->onSaveCategory($event); + + $this->assertArrayHasKey('id', $service->after); + } + + /** + * Test onBeforeDeleteCategory. + * + * @param CategoryModel $category + * + * @covers ::onBeforeDeleteCategory + * @covers ::fields + * @dataProvider provideSaveCategoryEvents + */ + final public function testOnBeforeDeleteCategory(CategoryModel $category) + { + AuditLogRecord::$db = $this->setMockDbConnection(); + + $this->setMockAuditLogService(); + $this->setMockUserSessionService(); + $this->setMockFieldsService(); + + $service = new AuditLog_CategoryService(); + $event = new Event($service, array( + 'category' => $category, + )); + $service->onBeforeDeleteCategory($event); + + $this->assertArrayHasKey('id', $service->after); + } + + /** + * Provide saveCategory events. + * + * @return array + */ + final public function provideSaveCategoryEvents() + { + return array( + 'With new category' => array($this->getMockCategoryModel(), true), + 'Without new category' => array($this->getMockCategoryModel(), false), + ); + } + + /** + * Mock CategoryModel. + * + * @return CategoryModel + */ + private function getMockCategoryModel() + { + $mock = $this->getMockBuilder('Craft\CategoryModel') + ->disableOriginalConstructor() + ->setMethods(array('__get', 'getAttributes', 'getTitle', 'getGroup')) + ->getMock(); + + $mock->expects($this->any())->method('__get')->willReturn('test'); + $mock->expects($this->any())->method('getAttributes')->willReturn(array( + array('id' => 'test'), + )); + $mock->expects($this->any())->method('getTitle')->willReturn('test'); + $mock->expects($this->any())->method('getGroup')->will($this->returnSelf()); + + return $mock; + } + + /** + * Mock AuditLogService. + */ + private function setMockAuditLogService() + { + $mock = $this->getMockBuilder('Craft\AuditLogService') + ->disableOriginalConstructor() + ->setMethods(array('elementHasChanged', 'parseFieldData')) + ->getMock(); + + $mock->expects($this->any())->method('elementHasChanged')->willReturn(true); + $mock->expects($this->any())->method('parseFieldData')->willReturn('test'); + + $this->setComponent(craft(), 'auditLog', $mock); + } + + /** + * Mock DbConnection. + * + * @return DbConnection + */ + private function setMockDbConnection() + { + $mock = $this->getMockBuilder('Craft\DbConnection') + ->disableOriginalConstructor() + ->setMethods(array('createCommand', 'getSchema')) + ->getMock(); + $mock->autoConnect = false; // Do not auto connect + + $command = $this->getMockDbCommand($mock); + $schema = $this->getMockDbSchema($mock); + + $mock->expects($this->any())->method('createCommand')->willReturn($command); + $mock->expects($this->any())->method('getSchema')->willReturn($schema); + + return $mock; + } + + /** + * Mock DbCommand. + * + * @param DbConnection $connection + * + * @return DbCommand + */ + private function getMockDbCommand(DbConnection $connection) + { + $mock = $this->getMockBuilder('Craft\DbCommand') + ->setConstructorArgs(array($connection)) + ->setMethods(array('execute', 'prepare', 'queryRow', 'queryAll')) + ->getMock(); + + $mock->expects($this->any())->method('execute')->willReturn(true); + $mock->expects($this->any())->method('prepare')->willReturn(true); + $mock->expects($this->any())->method('queryRow')->willReturn(array('username' => 'test')); + $mock->expects($this->any())->method('queryAll')->willReturn(array(array('username' => 'test'))); + + return $mock; + } + + /** + * Mock MysqlSchema. + * + * @param DbConncetion $connection + * + * @return MysqlSchema + */ + private function getMockDbSchema(DbConnection $connection) + { + $mock = $this->getMockBuilder('Craft\MysqlSchema') + ->disableOriginalConstructor() + ->setMethods(array('getTable', 'getCommandBuilder')) + ->getMock(); + + $table = new \CMysqlTableSchema(); + $table->columns = array( + 'id' => new \CMysqlColumnSchema(), + 'userId' => new \CMysqlColumnSchema(), + 'type' => new \CMysqlColumnSchema(), + 'origin' => new \CMysqlColumnSchema(), + 'before' => new \CMysqlColumnSchema(), + 'after' => new \CMysqlColumnSchema(), + 'status' => new \CMysqlColumnSchema(), + 'type' => new \CMysqlColumnSchema(), + 'dateCreated' => new \CMysqlColumnSchema(), + 'dateUpdated' => new \CMysqlColumnSchema(), + 'uid' => new \CMysqlColumnSchema(), + ); + $builder = $this->getMockCommandBuilder($connection, $mock); + + $mock->expects($this->any())->method('getTable')->willReturn($table); + $mock->expects($this->any())->method('getCommandBuilder')->willReturn($builder); + + return $mock; + } + + /** + * Mock CdbCommandBuilder. + * + * @param DbConnection $connection + * @param MysqlSchema $schema + * + * @return \CdbCommandBuilder + */ + private function getMockCommandBuilder(DbConnection $connection, MysqlSchema $schema) + { + $mock = $this->getMockBuilder('\CdbCommandBuilder') + ->disableOriginalConstructor() + ->setMethods(array('createInsertCommand', 'createPkCommand', 'createPkCriteria', 'createFindCommand', 'applyLimit', 'getSchema', 'getDbConnection', 'bindValues')) + ->getMock(); + + $command = $this->getMockDbCommand($connection); + + $mock->expects($this->any())->method('createInsertCommand')->willReturn($command); + $mock->expects($this->any())->method('createPkCommand')->willReturn($command); + $mock->expects($this->any())->method('createPkCriteria')->willReturn($command); + $mock->expects($this->any())->method('createFindCommand')->willReturn($command); + $mock->expects($this->any())->method('getSchema')->willReturn($schema); + $mock->expects($this->any())->method('getDbConnection')->willReturn($connection); + + return $mock; + } + + /** + * Mock UserGroupModel. + * + * @return UserGroupModel + */ + private function getMockUserGroupModel() + { + $mock = $this->getMockBuilder('Craft\UserGroupModel') + ->disableOriginalConstructor() + ->setMethods(array('__toString')) + ->getMock(); + + $mock->expects($this->any())->method('__toString')->willReturn('test'); + + return $mock; + } + + /** + * Mock UserSessionService. + */ + private function setMockUserSessionService() + { + $mock = $this->getMockBuilder('Craft\UserSessionService') + ->disableOriginalConstructor() + ->setMethods(array('getUser')) + ->getMock(); + + $user = $this->getMockUserModel(); + + $mock->expects($this->any())->method('getUser')->willReturn($user); + + $this->setComponent(craft(), 'userSession', $mock); + } + + /** + * Mock UserModel. + * + * @return UserModel + */ + private function getMockUserModel() + { + $mock = $this->getMockBuilder('Craft\UserModel') + ->disableOriginalConstructor() + ->setMethods(array('__get')) + ->getMock(); + + $mock->expects($this->any())->method('__get')->willReturn('test'); + + return $mock; + } + + /** + * Mock FieldsService. + */ + private function setMockFieldsService() + { + $mock = $this->getMockBuilder('Craft\FieldsService') + ->disableOriginalConstructor() + ->setMethods(array('getLayoutByType', 'getFieldByHandle', 'getAllFields')) + ->getMock(); + + $layout = $this->getMockFieldLayoutModel(); + $field = $this->getMockFieldModel(); + + $mock->expects($this->any())->method('getLayoutByType')->willReturn($layout); + $mock->expects($this->any())->method('getFieldByHandle')->willReturn($field); + $mock->expects($this->any())->method('getAllFields')->willReturn(array($field)); + + $this->setComponent(craft(), 'fields', $mock); + } + + /** + * Mock FieldLayoutModel. + * + * @return FieldLayoutModel + */ + private function getMockFieldLayoutModel() + { + $mock = $this->getMockBuilder('Craft\FieldLayoutModel') + ->disableOriginalConstructor() + ->setMethods(array('getFields')) + ->getMock(); + + $fields = array($this->getMockFieldLayoutFieldModel()); + + $mock->expects($this->any())->method('getFields')->willReturn($fields); + + return $mock; + } + + /** + * Mock FieldLayoutFieldModel. + * + * @return FieldLayoutFieldModel + */ + private function getMockFieldLayoutFieldModel() + { + $mock = $this->getMockBuilder('Craft\FieldLayoutFieldModel') + ->disableOriginalConstructor() + ->setMethods(array('getField')) + ->getMock(); + + $field = $this->getMockFieldModel(); + + $mock->expects($this->any())->method('getField')->willReturn($field); + + return $mock; + } + + /** + * Mock FieldModel. + * + * @return FieldModel + */ + private function getMockFieldModel() + { + $mock = $this->getMockBuilder('Craft\FieldModel') + ->disableOriginalConstructor() + ->setMethods(array('__get')) + ->getMock(); + + $mock->expects($this->any())->method('__get')->willReturn('test'); + + return $mock; + } + + /** + * Mock LocalizationService. + */ + private function setMockLocalizationService() + { + $mock = $this->getMockBuilder('Craft\LocalizationService') + ->disableOriginalConstructor() + ->setMethods(array('getPrimarySiteLocaleId')) + ->getMock(); + + $mock->expects($this->any())->method('getPrimarySiteLocaleId')->willReturn('nl'); + + $this->setComponent(craft(), 'i18n', $mock); + } +} From e24163da75eac1b121b03e4b9bc9a4035745b530 Mon Sep 17 00:00:00 2001 From: Bob Olde Hampsink Date: Wed, 24 Feb 2016 12:38:01 +0100 Subject: [PATCH 13/15] Improved code quality --- elementtypes/AuditLogElementType.php | 50 +++++++++++++++++++--------- 1 file changed, 34 insertions(+), 16 deletions(-) diff --git a/elementtypes/AuditLogElementType.php b/elementtypes/AuditLogElementType.php index 18010c2..2fcec1c 100644 --- a/elementtypes/AuditLogElementType.php +++ b/elementtypes/AuditLogElementType.php @@ -198,11 +198,6 @@ public function modifyElementsQuery(DbCommand $query, ElementCriteriaModel $crit $query->andWhere(DbHelper::parseParam('auditlog.origin', $criteria->origin, $query->params)); } - // Check for date modified - if (!empty($criteria->modified)) { - $query->andWhere(DbHelper::parseDateParam('auditlog.dateUpdated', $criteria->modified, $query->params)); - } - // Check before if (!empty($criteria->before)) { $query->andWhere(DbHelper::parseParam('auditlog.before', $criteria->before, $query->params)); @@ -213,6 +208,31 @@ public function modifyElementsQuery(DbCommand $query, ElementCriteriaModel $crit $query->andWhere(DbHelper::parseParam('auditlog.after', $criteria->after, $query->params)); } + // Check for status + if (!empty($criteria->status)) { + $query->andWhere(DbHelper::parseParam('auditlog.status', $criteria->status, $query->params)); + } + + // Dates + $this->applyDateCriteria($criteria, $query); + + // Search + $this->applySearchCriteria($criteria, $query); + } + + /** + * Apply date criteria. + * + * @param ElementCriteriaModel $search + * @param DbCommand $query + */ + private function applyDateCriteria(ElementCriteriaModel $criteria, DbCommand $query) + { + // Check for date modified + if (!empty($criteria->modified)) { + $query->andWhere(DbHelper::parseDateParam('auditlog.dateUpdated', $criteria->modified, $query->params)); + } + // Check for date from if (!empty($criteria->from)) { $query->andWhere(DbHelper::parseDateParam('auditlog.dateUpdated', '>= '.DateTimeHelper::formatTimeForDb($criteria->from), $query->params)); @@ -223,18 +243,16 @@ public function modifyElementsQuery(DbCommand $query, ElementCriteriaModel $crit $criteria->to->add(new DateInterval('PT23H59M59S')); $query->andWhere(DbHelper::parseDateParam('auditlog.dateUpdated', '<= '.DateTimeHelper::formatTimeForDb($criteria->to), $query->params)); } + } - // Check for type - if (!empty($criteria->type)) { - $query->andWhere(DbHelper::parseParam('auditlog.type', $criteria->type, $query->params)); - } - - // Check for status - if (!empty($criteria->status)) { - $query->andWhere(DbHelper::parseParam('auditlog.status', $criteria->status, $query->params)); - } - - // Search + /** + * Apply search criteria. + * + * @param ElementCriteriaModel $search + * @param DbCommand $query + */ + private function applySearchCriteria(ElementCriteriaModel $criteria, DbCommand $query) + { if (!empty($criteria->search)) { // Always perform a LIKE search From 3cd4c13ee92acde4cdc0b39ac2c466130653c35c Mon Sep 17 00:00:00 2001 From: Bob Olde Hampsink Date: Wed, 24 Feb 2016 12:40:00 +0100 Subject: [PATCH 14/15] Updated notes --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 2f7603b..7668690 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,8 @@ Changelog ================= ###0.7.0### - Added Craft 2.5 compatibility + - Refactored plugin for better readability, quality and testability + - All service code is now fully covered by unit tests ###0.6.2### - Fixed a bug where the date range didn't fully work From de3ce3e2ed46a672f79bd7cf49f9616b11d28980 Mon Sep 17 00:00:00 2001 From: Bob Olde Hampsink Date: Wed, 24 Feb 2016 12:45:03 +0100 Subject: [PATCH 15/15] Fixed some scrutinizer issues --- elementtypes/AuditLogElementType.php | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/elementtypes/AuditLogElementType.php b/elementtypes/AuditLogElementType.php index 2fcec1c..239a3b5 100644 --- a/elementtypes/AuditLogElementType.php +++ b/elementtypes/AuditLogElementType.php @@ -82,7 +82,7 @@ public function defineAvailableTableAttributes() * * @param string $source * - * @return array + * @return string[] */ public function getDefaultTableAttributes($source = null) { @@ -157,12 +157,10 @@ public function defineCriteriaAttributes() } /** - * Cancel the elements query. + * Modify the elements query. * * @param DbCommand $query * @param ElementCriteriaModel $criteria - * - * @return bool */ public function modifyElementsQuery(DbCommand $query, ElementCriteriaModel $criteria) { @@ -223,7 +221,7 @@ public function modifyElementsQuery(DbCommand $query, ElementCriteriaModel $crit /** * Apply date criteria. * - * @param ElementCriteriaModel $search + * @param ElementCriteriaModel $criteria * @param DbCommand $query */ private function applyDateCriteria(ElementCriteriaModel $criteria, DbCommand $query) @@ -248,7 +246,7 @@ private function applyDateCriteria(ElementCriteriaModel $criteria, DbCommand $qu /** * Apply search criteria. * - * @param ElementCriteriaModel $search + * @param ElementCriteriaModel $criteria * @param DbCommand $query */ private function applySearchCriteria(ElementCriteriaModel $criteria, DbCommand $query)