0011590: improve concurrent update check performance
[tine20] / tests / tine20 / Tinebase / Timemachine / ModificationLogTest.php
1 <?php
2 /**
3  * Tine 2.0 - http://www.tine20.org
4  * 
5  * @package     Tinebase
6  * @subpackage  Record
7  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
8  * @copyright   Copyright (c) 2008-2012 Metaways Infosystems GmbH (http://www.metaways.de)
9  * @author      Cornelius Weiss <c.weiss@metaways.de>
10  */
11
12 /**
13  * Test helper
14  */
15 require_once dirname(dirname(dirname(__FILE__))) . DIRECTORY_SEPARATOR . 'TestHelper.php';
16
17 /**
18  * Test class for Tinebase_Group
19  */
20 class Tinebase_Timemachine_ModificationLogTest extends PHPUnit_Framework_TestCase
21 {
22     /**
23      * @var Tinebase_Timemachine_ModificationLog
24      */
25     protected $_modLogClass;
26     
27     /**
28      * @var Tinebase_Record_RecordSet
29      */
30     protected $_logEntries;
31     
32     /**
33      * @var Tinebase_Record_RecordSet
34      * Persistant Records we need to cleanup at tearDown()
35      */
36     protected $_persistantLogEntries;
37     
38     /**
39      * @var array holds recordId's we create log entries for
40      */
41     protected $_recordIds = array();
42     
43     /**
44      * Runs the test methods of this class.
45      *
46      * @access public
47      * @static
48      */
49     public static function main()
50     {
51         $suite  = new PHPUnit_Framework_TestSuite('Tinebase_Timemachine_ModificationLogTest');
52         PHPUnit_TextUI_TestRunner::run($suite);
53     }
54
55     /**
56      * Lets update a record tree times
57      *
58      * @access protected
59      */
60     protected function setUp()
61     {
62         Tinebase_TransactionManager::getInstance()->startTransaction(Tinebase_Core::getDb());
63         
64         $now = new Tinebase_DateTime();
65         $this->_modLogClass = Tinebase_Timemachine_ModificationLog::getInstance();
66         $this->_persistantLogEntries = new Tinebase_Record_RecordSet('Tinebase_Model_ModificationLog');
67         $this->_recordIds = array('5dea69be9c72ea3d263613277c3b02d529fbd8bc');
68         
69         $tinebaseApp = Tinebase_Application::getInstance()->getApplicationByName('Tinebase');
70         
71         $this->_logEntries = new Tinebase_Record_RecordSet('Tinebase_Model_ModificationLog', array(
72         array(
73             'application_id'       => $tinebaseApp,
74             'record_id'            => $this->_recordIds[0],
75             'record_type'          => 'TestType',
76             'record_backend'       => 'TestBackend',
77             'modification_time'    => $this->_cloner($now)->addDay(-2),
78             'modification_account' => 7,
79             'modified_attribute'   => 'FirstTestAttribute',
80             'old_value'            => 'Hamburg',
81             'new_value'            => 'Bremen'
82         ),
83         array(
84             'application_id'       => $tinebaseApp,
85             'record_id'            => $this->_recordIds[0],
86             'record_type'          => 'TestType',
87             'record_backend'       => 'TestBackend',
88             'modification_time'    => $this->_cloner($now)->addDay(-1),
89             'modification_account' => 7,
90             'modified_attribute'   => 'FirstTestAttribute',
91             'old_value'            => 'Bremen',
92             'new_value'            => 'Frankfurt'
93         ),
94         array(
95             'application_id'       => $tinebaseApp,
96             'record_id'            => $this->_recordIds[0],
97             'record_type'          => 'TestType',
98             'record_backend'       => 'TestBackend',
99             'modification_time'    => $this->_cloner($now),
100             'modification_account' => 7,
101             'modified_attribute'   => 'FirstTestAttribute',
102             'old_value'            => 'Frankfurt',
103             'new_value'            => 'Stuttgart'
104         ),
105         array(
106             'application_id'       => $tinebaseApp,
107             'record_id'            => $this->_recordIds[0],
108             'record_type'          => 'TestType',
109             'record_backend'       => 'TestBackend',
110             'modification_time'    => $this->_cloner($now)->addDay(-2),
111             'modification_account' => 7,
112             'modified_attribute'   => 'SecondTestAttribute',
113             'old_value'            => 'Deutschland',
114             'new_value'            => 'Östereich'
115         ),
116         array(
117             'application_id'       => $tinebaseApp,
118             'record_id'            => $this->_recordIds[0],
119             'record_type'          => 'TestType',
120             'record_backend'       => 'TestBackend',
121             'modification_time'    => $this->_cloner($now)->addDay(-1)->addSecond(1),
122             'modification_account' => 7,
123             'modified_attribute'   => 'SecondTestAttribute',
124             'old_value'            => 'Östereich',
125             'new_value'            => 'Schweitz'
126         ),
127         array(
128             'application_id'       => $tinebaseApp->getId(),
129             'record_id'            => $this->_recordIds[0],
130             'record_type'          => 'TestType',
131             'record_backend'       => 'TestBackend',
132             'modification_time'    => $this->_cloner($now),
133             'modification_account' => 7,
134             'modified_attribute'   => 'SecondTestAttribute',
135             'old_value'            => 'Schweitz',
136             'new_value'            => 'Italien'
137         )), true, false);
138         
139         
140         foreach ($this->_logEntries as $logEntry) {
141             $id = $this->_modLogClass->setModification($logEntry);
142             $this->_persistantLogEntries->addRecord($this->_modLogClass->getModification($id));
143         }
144     }
145
146     /**
147      * cleanup database
148      * @access protected
149      */
150     protected function tearDown()
151     {
152         Tinebase_TransactionManager::getInstance()->rollBack();
153     }
154
155     /**
156      * tests that the returned mod logs equal the initial ones we defined 
157      * in this test setup.
158      * If this works, also the setting of logs works!
159      *
160      */
161     public function testGetModification()
162     {
163         foreach ($this->_logEntries as $num => $logEntry) {
164             $rawLogEntry = $logEntry->toArray();
165             $rawPersistantLogEntry = $this->_persistantLogEntries[$num]->toArray();
166             
167             foreach ($rawLogEntry as $field => $value) {
168                 $persistantValue = $rawPersistantLogEntry[$field];
169                 if ($value != $persistantValue) {
170                     $this->fail("Failed asserting that contents of saved LogEntry #$num in field $field equals initial datas. \n" . 
171                                 "Expected '$value', got '$persistantValue'");
172                 }
173             }
174         }
175         $this->assertTrue(true);
176     }
177     
178     /**
179      * tests computation of a records differences described by a set of modification logs
180      */
181     public function testComputeDiff()
182     {
183         $diffs = $this->_modLogClass->computeDiff($this->_persistantLogEntries)->toArray();
184         $this->assertEquals(2, count($diffs)); // we changed two attributes
185         foreach ($diffs as $diff) {
186             switch ($diff['modified_attribute']) {
187                case 'FirstTestAttribute':
188                    $this->assertEquals('Hamburg', $diff['old_value']);
189                    $this->assertEquals('Stuttgart', $diff['new_value']);
190                    break;
191                case 'SecondTestAttribute':
192                    $this->assertEquals('Deutschland', $diff['old_value']);
193                    $this->assertEquals('Italien', $diff['new_value']);
194             }
195         }
196     }
197     
198     /**
199      * get modifications test
200      */
201     public function testGetModifications()
202     {
203         $testBase = array(
204             'record_id' => '5dea69be9c72ea3d263613277c3b02d529fbd8bc',
205             'type'      => 'TestType',
206             'backend'   => 'TestBackend'
207         );
208         $firstModificationTime = $this->_persistantLogEntries[0]->modification_time;
209         $lastModificationTime  = $this->_persistantLogEntries[count($this->_persistantLogEntries)-1]->modification_time;
210         
211         $toTest[] = $testBase + array(
212             'from_add'  => 'addDay,-3',
213             'until_add' => 'addDay,1',
214             'nums'      => 6
215         );
216         $toTest[] = $testBase + array(
217             'nums'  => 4
218         );
219         $toTest[] = $testBase + array(
220             'account' => Tinebase_Record_Abstract::generateUID(),
221             'nums'    => 0
222         );
223         
224         foreach ($toTest as $params) {
225             $from = clone $firstModificationTime;
226             $until = clone $lastModificationTime;
227             
228             if (isset($params['from_add'])) {
229                list($fn,$p) = explode(',', $params['from_add']);
230                $from->$fn($p);
231             }
232             if (isset($params['until_add'])) {
233                 list($fn,$p) = explode(',', $params['until_add']);
234                 $until->$fn($p);
235             }
236             
237             $account = isset($params['account']) ? $params['account'] : NULL;
238             $diffs = $this->_modLogClass->getModifications('Tinebase', $params['record_id'], $params['type'], $params['backend'], $from, $until, $account);
239             $count = 0;
240             foreach ($diffs as $diff) {
241                 if ($diff->record_id == $params['record_id']) {
242                    $count++;
243                 }
244             }
245             $this->assertEquals($params['nums'], $count);
246         }
247     }
248     
249     /**
250      * test modlog undo
251      * 
252      * @see 0006252: allow to undo history items (modlog)
253      * @see 0000554: modlog: records can't be updated in less than 1 second intervals
254      */
255     public function testUndo()
256     {
257         // create a record
258         $contact = Addressbook_Controller_Contact::getInstance()->create(new Addressbook_Model_Contact(array(
259             'n_family' => 'tester',
260             'tel_cell' => '+491234',
261         )));
262         // change something using the record controller
263         $contact->tel_cell = NULL;
264         $contact = Addressbook_Controller_Contact::getInstance()->update($contact);
265         
266         // fetch modlog and test seq
267         $modlog = $this->_modLogClass->getModifications('Addressbook', $contact->getId(), NULL, 'Sql',
268             Tinebase_DateTime::now()->subSecond(5), Tinebase_DateTime::now())->getFirstRecord();
269         $this->assertTrue($modlog !== NULL);
270         $this->assertEquals(2, $modlog->seq);
271         $this->assertEquals('+491234', $modlog->old_value);
272         
273         $filter = new Tinebase_Model_ModificationLogFilter(array(
274             array('field' => 'record_type',         'operator' => 'equals', 'value' => 'Addressbook_Model_Contact'),
275             array('field' => 'record_id',           'operator' => 'equals', 'value' => $contact->getId()),
276             array('field' => 'modification_time',   'operator' => 'within', 'value' => 'weekThis'),
277         ));
278         $result = $this->_modLogClass->undo($filter);
279         $this->assertEquals(1, $result['totalcount'], 'did not get 1 undone modlog: ' . print_r($result, TRUE));
280         $this->assertEquals('+491234', $result['undoneModlogs']->getFirstRecord()->old_value);
281         
282         // check record after undo
283         $contact = Addressbook_Controller_Contact::getInstance()->get($contact);
284         $this->assertEquals('+491234', $contact->tel_cell);
285     }
286     
287     /**
288      * purges mod log entries of given recordIds
289      *
290      * @param mixed [string|array|Tinebase_Record_RecordSet] $_recordIds
291      * 
292      * @todo should be removed when other tests do not need this anymore
293      */
294     public static function purgeLogs($_recordIds)
295     {
296         $table = new Tinebase_Db_Table(array('name' => SQL_TABLE_PREFIX . 'timemachine_modlog'));
297         
298         foreach ((array) $_recordIds as $recordId) {
299              $table->delete($table->getAdapter()->quoteInto('record_id = ?', $recordId));
300         }
301     }
302     
303     /**
304      * Workaround as the php clone operator does not return cloned 
305      * objects right hand sided
306      *
307      * @param object $_object
308      * @return object
309      */
310     protected function _cloner($_object)
311     {
312         return clone $_object;
313     }
314     
315     /**
316      * testDateTimeModlog
317      * 
318      * @see 0000996: add changes in relations/linked objects to modlog/history
319      */
320     public function testDateTimeModlog()
321     {
322         $task = Tasks_Controller_Task::getInstance()->create(new Tasks_Model_Task(array(
323             'summary' => 'test task',
324         )));
325         
326         $task->due = Tinebase_DateTime::now();
327         $updatedTask = Tasks_Controller_Task::getInstance()->update($task);
328         
329         $task->seq = 1;
330         $modlog = $this->_modLogClass->getModificationsBySeq(
331             Tinebase_Application::getInstance()->getApplicationByName('Tasks')->getId(),
332             $task, 2);
333         
334         $this->assertEquals(1, count($modlog));
335         $this->assertEquals((string) $task->due, (string) $modlog->getFirstRecord()->new_value, 'new value mismatch: ' . print_r($modlog->toArray(), TRUE));
336     }
337 }