The mock driver.
authorGunnar Wrobel <p@rdus.de>
Tue, 29 Sep 2009 15:23:36 +0000 (17:23 +0200)
committerGunnar Wrobel <p@rdus.de>
Tue, 29 Sep 2009 15:23:36 +0000 (17:23 +0200)
framework/History/lib/Horde/History/Factory.php
framework/History/lib/Horde/History/Mock.php [new file with mode: 0644]
framework/History/package.xml
framework/History/test/Horde/History/InterfaceTest.php

index c475912..cdfa8e6 100644 (file)
@@ -61,8 +61,9 @@ class Horde_History_Factory
         case 'Sql':
             return Horde_History_Factory::getHistorySql($injector, $config->params);
         case 'Mock':
-        default:
             return Horde_History_Factory::getHistoryMock($config->params);
+        default:
+            throw new Horde_Exception(sprintf("Driver %s not supported!", $config->driver));
         }
     }
 
@@ -109,6 +110,24 @@ class Horde_History_Factory
     }
 
     /**
+     * Create a concrete Horde_History_Mock instance.
+     *
+     * @param Horde_Injector $injector The environment for creating the instance.
+     * @param array          $params   The db connection parameters if the
+     *                                 environment does not already provide a 
+     *                                 connection.
+     *
+     * @return Horde_History_Mock The new Horde_History_Mock instance.
+     *
+     * @throws Horde_Exception If the injector provides no configuration or
+     *                         creating the database connection failed.
+     */
+    static protected function getHistoryMock(array $params)
+    {
+        return new Horde_History_Mock();
+    }
+
+    /**
      * Create a database connection.
      *
      * @param array $params The database connection parameters.
diff --git a/framework/History/lib/Horde/History/Mock.php b/framework/History/lib/Horde/History/Mock.php
new file mode 100644 (file)
index 0000000..dde9254
--- /dev/null
@@ -0,0 +1,248 @@
+<?php
+/**
+ * A mock history driver.
+ *
+ * PHP version 5
+ *
+ * @category Horde
+ * @package  History
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=History
+ */
+
+/**
+ * The Horde_History_Mock:: class provides a method of tracking changes in Horde
+ * objects, stored in memory.
+ *
+ * Copyright 2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @category Horde
+ * @package  History
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=History
+ */
+class Horde_History_Mock extends Horde_History
+{
+    /**
+     * Our storage location.
+     *
+     * @var array
+     */
+    private $_data = array();
+
+    /**
+     * The next id.
+     *
+     * @var int
+     */
+    private $_id = 1;
+
+    /**
+     * Logs an event to an item's history log. Any other details about the event
+     * are passed in $attributes.
+     *
+     * @param Horde_HistoryObject $history       The history item to add to.
+     * @param array               $attributes    The hash of name => value entries
+     *                                           that describe this event.
+     * @param boolean             $replaceAction If $attributes['action'] is
+     *                                           already present in the item's
+     *                                           history log, update that entry
+     *                                           instead of creating a new one.
+     *
+     * @return boolean True if the operation succeeded.
+     *
+     * @throws Horde_Exception
+     */
+    protected function _log(Horde_HistoryObject $history,
+                            array $attributes,
+                            $replaceAction = false)
+    {
+        $values = array(
+            'history_uid'    => $history->uid,
+            'history_ts'     => $attributes['ts'],
+            'history_who'    => $attributes['who'],
+            'history_desc'   => isset($attributes['desc']) ? $attributes['desc'] : null,
+            'history_action' => isset($attributes['action']) ? $attributes['action'] : null
+        );
+
+        unset($attributes['ts'], $attributes['who'], $attributes['desc'], $attributes['action']);
+
+        $values['history_extra'] = $attributes
+            ? serialize($attributes)
+            : null;
+
+        /* If we want to replace an entry with the same action, try and find
+         * one. Track whether or not we succeed in $done, so we know whether
+         * or not to add the entry later. */
+        $done = false;
+        if ($replaceAction && !empty($values['history_action'])) {
+            for ($i = 0, $count = count($history->data); $i < $count; ++$i) {
+                if (!empty($history->data[$i]['action']) &&
+                    $history->data[$i]['action'] == $values['history_action']) {
+                    $this->_data[$history->data[$i]['id']] = $values;
+                    $done = true;
+                    break;
+                }
+            }
+        }
+
+        /* If we're not replacing by action, or if we didn't find an entry to
+         * replace, insert a new row. */
+        if (!$done) {
+
+            $this->_data[$this->_id] = $values;
+            $this->_id++;
+        }
+
+        return true;
+    }
+
+    /**
+     * Returns a Horde_HistoryObject corresponding to the named history
+     * entry, with the data retrieved appropriately.
+     *
+     * @param string $guid The name of the history entry to retrieve.
+     *
+     * @return Horde_HistoryObject  A Horde_HistoryObject
+     */
+    public function getHistory($guid)
+    {
+        $result= array();
+        foreach ($this->_data as $id => $element) {
+            if ($element['history_uid'] == $guid) {
+                $element['history_id'] = $id;
+                $result[] = $element;
+            }
+        }
+        return new Horde_HistoryObject($guid, $result);
+    }
+
+    /**
+     * Finds history objects by timestamp, and optionally filter on other
+     * fields as well.
+     *
+     * @param string  $cmp     The comparison operator (<, >, <=, >=, or =) to
+     *                         check the timestamps with.
+     * @param integer $ts      The timestamp to compare against.
+     * @param array   $filters An array of additional (ANDed) criteria.
+     *                         Each array value should be an array with 3
+     *                         entries:
+     * <pre>
+     * 'field' - the history field being compared (i.e. 'action').
+     * 'op'    - the operator to compare this field with.
+     * 'value' - the value to check for (i.e. 'add').
+     * </pre>
+     * @param string  $parent  The parent history to start searching at. If
+     *                         non-empty, will be searched for with a LIKE
+     *                         '$parent:%' clause.
+     *
+     * @return array  An array of history object ids, or an empty array if
+     *                none matched the criteria.
+     *
+     * @throws Horde_Exception
+     */
+    public function getByTimestamp($cmp, $ts, $filters = array(),
+                                   $parent = null)
+    {
+        $result = array();
+
+        foreach ($this->_data as $id => $element) {
+
+            $ignore = false;
+
+            switch ($cmp) {
+            case '<=':
+            case '=<':
+                if ($element['history_ts'] > $ts) {
+                    $ignore = true;
+                };
+                break;
+            case '<':
+                if ($element['history_ts'] >= $ts) {
+                    $ignore = true;
+                };
+                break;
+            case '=':
+                if ($element['history_ts'] != $ts) {
+                    $ignore = true;
+                };
+                break;
+            case '>':
+                if ($element['history_ts'] <= $ts) {
+                    $ignore = true;
+                };
+                break;
+            case '>=':
+            case '=>':
+                if ($element['history_ts'] < $ts) {
+                    $ignore = true;
+                };
+                break;
+            default:
+                throw new Horde_Exception(sprintf("Comparison %s not implemented!", $cmp));
+            }
+
+            if ($ignore) {
+                continue;
+            }
+
+            /* Add additional filters, if there are any. */
+            if ($filters) {
+                foreach ($filters as $filter) {
+                    if ($filter['op'] != '=') {
+                        throw new Horde_Exception(sprintf("Comparison %s not implemented!", $filter['op']));
+                    }
+                    if ($element['history_' . $filter['field']] != $filter['value']) {
+                        $ignore = true;
+                    }
+                }
+            }
+
+            if ($ignore) {
+                continue;
+            }
+
+            if ($parent) {
+                if (substr($element['history_uid'], 0, strlen($parent) + 1) != $parent . ':') {
+                    continue;
+                }
+            }
+
+            $result[$element['history_uid']] = $id;
+        }
+        return $result;
+    }
+
+    /**
+     * Remove one or more history entries by name.
+     *
+     * @param array $names The history entries to remove.
+     *
+     * @return boolean True if the operation succeeded.
+     *
+     * @throws Horde_Exception
+     */
+    public function removeByNames($names)
+    {
+        if (!count($names)) {
+            return true;
+        }
+
+        $ids = array();
+        foreach ($this->_data as $id => $element) {
+            if (in_array($element['history_uid'], $names)) {
+                $ids[] = $id;
+            }
+        }
+
+        foreach ($ids as $id) {
+            unset($this->_data[$id]);
+        }
+        return true;
+    }
+}
index f31f2c5..0c104ee 100644 (file)
@@ -34,6 +34,7 @@ http://pear.php.net/dtd/package-2.0.xsd">
      <file name="HistoryObject.php" role="php" />
      <dir name="History">
       <file name="Factory.php" role="php" />
+      <file name="Mock.php" role="php" />
       <file name="Sql.php" role="php" />
     </dir> <!-- /lib/Horde/History -->
     </dir> <!-- /lib/Horde -->
@@ -67,6 +68,7 @@ http://pear.php.net/dtd/package-2.0.xsd">
    <install name="lib/Horde/History.php" as="Horde/History.php" />
    <install name="lib/Horde/HistoryObject.php" as="Horde/HistoryObject.php" />
    <install name="lib/Horde/History/Factory.php" as="Horde/History/Factory.php" />
+   <install name="lib/Horde/History/Mock.php" as="Horde/History/Mock.php" />
    <install name="lib/Horde/History/Sql.php" as="Horde/History/Sql.php" />
    <install name="test/Horde/History/AllTests.php" as="Horde/History/AllTests.php" />
    <install name="test/Horde/History/InterfaceTest.php" as="Horde/History/InterfaceTest.php" />
index 178a6ac..829cd43 100644 (file)
@@ -34,10 +34,10 @@ require_once 'Horde/Autoloader.php';
 class Horde_History_InterfaceTest extends PHPUnit_Framework_TestCase
 {
     /** The basic mock environment */
-    const ENVIRONMENT_MOCK = 'mock';
+    const ENVIRONMENT_MOCK = 'Mock';
 
     /** The environment using a database */
-    const ENVIRONMENT_DB = 'db';
+    const ENVIRONMENT_DB = 'Sql';
 
     /**
      * Path to the temporary sqlite db used for testing the db environment.
@@ -66,7 +66,10 @@ class Horde_History_InterfaceTest extends PHPUnit_Framework_TestCase
              * The db environment provides our only test scenario before
              * refactoring.
              */
-            $this->_environments = array(self::ENVIRONMENT_DB);
+            $this->_environments = array(
+                self::ENVIRONMENT_MOCK,
+                self::ENVIRONMENT_DB,
+            );
         }
         return $this->_environments;
     }
@@ -135,6 +138,12 @@ EOL;
             $config->params = $conf['sql'];
             $injector->setInstance('Horde_History_Config', $config);
             break;
+        case self::ENVIRONMENT_MOCK:
+            $injector = new Horde_Injector(new Horde_Injector_TopLevel());
+            $injector->bindImplementation(
+                'Horde_History',
+                'Horde_History_Mock'
+            );
         }
         return $injector;
     }
@@ -156,12 +165,11 @@ EOL;
 
     public function testMethodSingletonHasResultHordehistoryWhichIsAlwaysTheSame()
     {
-        //TODO: More complex
         foreach ($this->getEnvironments() as $environment) {
             $history = $this->getHistory($environment);
-            $history1 = Horde_History::singleton();
+            $history1 = Horde_History::singleton($environment);
             $this->assertType('Horde_History', $history1);
-            $history2 = Horde_History::singleton();
+            $history2 = Horde_History::singleton($environment);
             $this->assertType('Horde_History', $history2);
             $this->assertSame($history1, $history2);
         }
@@ -169,7 +177,7 @@ EOL;
 
     public function testMethodLogHasPostConditionThatTimestampAndActorAreAlwaysRecorded()
     {
-        //TODO: More complex
+        //@todo: More complex
         foreach ($this->getEnvironments() as $environment) {
             $history = $this->getHistory($environment);
             $history->log('test', array('action' => 'test'));
@@ -181,7 +189,7 @@ EOL;
 
     public function testMethodLogHasPostConditionThatTheGivenEventHasBeenRecorded()
     {
-        //TODO: More complex
+        //@todo: More complex
         foreach ($this->getEnvironments() as $environment) {
             $history = $this->getHistory($environment);
             $history->log('test', array('who' => 'me', 'ts' => 1000, 'action' => 'test'));
@@ -301,13 +309,14 @@ EOL;
 
     public function testMethodGetbytimestampHasResultArrayContainingTheMatchingEventIds()
     {
-        //TODO: More complex
+        //@todo: More complex
         foreach ($this->getEnvironments() as $environment) {
             $history = $this->getHistory($environment);
             $history->log('test', array('who' => 'me', 'ts' => 1000, 'action' => 'test'));
+            $history->log('test', array('who' => 'me', 'ts' => 1000, 'action' => 'test'));
             $history->log('test', array('who' => 'you', 'ts' => 2000, 'action' => 'yours', 'extra' => array('a' => 'a')));
             $result = $history->getByTimestamp('<', 1001);
-            $this->assertEquals(array('test' => 1), $result);
+            $this->assertEquals(array('test' => 2), $result);
         }
     }
 
@@ -485,21 +494,21 @@ class Dummy_Db extends DB_common
 {
     public function query($sql, $values = null)
     {
-        return PEAR::raiseError('Error');
+        return new PEAR_Error('Error');
     }
 
     public function nextId($table)
     {
-        return PEAR::raiseError('Error');
+        return new PEAR_Error('Error');
     }
 
     public function getAll($sql)
     {
-        return PEAR::raiseError('Error');
+        return new PEAR_Error('Error');
     }
 
     public function getAssoc($sql)
     {
-        return PEAR::raiseError('Error');
+        return new PEAR_Error('Error');
     }
 }
\ No newline at end of file