]> git.madduck.net Git - etc/taskwarrior.git/blob - tasklib/tests.py

madduck's git repository

Every one of the projects in this repository is available at the canonical URL git://git.madduck.net/madduck/pub/<projectpath> — see each project's metadata for the exact URL.

All patches and comments are welcome. Please squash your changes to logical commits before using git-format-patch and git-send-email to patches@git.madduck.net. If you'd read over the Git project's submission guidelines and adhered to them, I'd be especially grateful.

SSH access, as well as push access can be individually arranged.

If you use my repositories frequently, consider adding the following snippet to ~/.gitconfig and using the third clone URL listed for each project:

[url "git://git.madduck.net/madduck/"]
  insteadOf = madduck:

Bump version for 0.10.0 release
[etc/taskwarrior.git] / tasklib / tests.py
1 # coding=utf-8
2
3 import copy
4 import datetime
5 import itertools
6 import json
7 import pytz
8 import six
9 import shutil
10 import sys
11 import tempfile
12 import unittest
13
14 from .task import TaskWarrior, Task, ReadOnlyDictView, local_zone, DATE_FORMAT
15
16 # http://taskwarrior.org/docs/design/task.html , Section: The Attributes
17 TASK_STANDARD_ATTRS = (
18     'status',
19     'uuid',
20     'entry',
21     'description',
22     'start',
23     'end',
24     'due',
25     'until',
26     'wait',
27     'modified',
28     'scheduled',
29     'recur',
30     'mask',
31     'imask',
32     'parent',
33     'project',
34     'priority',
35     'depends',
36     'tags',
37     'annotations',
38 )
39
40 total_seconds_2_6 = lambda x: x.microseconds / 1e6 + x.seconds + x.days * 24 * 3600
41
42
43 class TasklibTest(unittest.TestCase):
44
45     def setUp(self):
46         self.tmp = tempfile.mkdtemp(dir='.')
47         self.tw = TaskWarrior(data_location=self.tmp, taskrc_location='/')
48
49     def tearDown(self):
50         shutil.rmtree(self.tmp)
51
52
53 class TaskFilterTest(TasklibTest):
54
55     def test_all_empty(self):
56         self.assertEqual(len(self.tw.tasks.all()), 0)
57
58     def test_all_non_empty(self):
59         Task(self.tw, description="test task").save()
60         self.assertEqual(len(self.tw.tasks.all()), 1)
61         self.assertEqual(self.tw.tasks.all()[0]['description'], 'test task')
62         self.assertEqual(self.tw.tasks.all()[0]['status'], 'pending')
63
64     def test_pending_non_empty(self):
65         Task(self.tw, description="test task").save()
66         self.assertEqual(len(self.tw.tasks.pending()), 1)
67         self.assertEqual(self.tw.tasks.pending()[0]['description'],
68                          'test task')
69         self.assertEqual(self.tw.tasks.pending()[0]['status'], 'pending')
70
71     def test_completed_empty(self):
72         Task(self.tw, description="test task").save()
73         self.assertEqual(len(self.tw.tasks.completed()), 0)
74
75     def test_completed_non_empty(self):
76         Task(self.tw, description="test task").save()
77         self.assertEqual(len(self.tw.tasks.completed()), 0)
78         self.tw.tasks.all()[0].done()
79         self.assertEqual(len(self.tw.tasks.completed()), 1)
80
81     def test_filtering_by_attribute(self):
82         Task(self.tw, description="no priority task").save()
83         Task(self.tw, priority="H", description="high priority task").save()
84         self.assertEqual(len(self.tw.tasks.all()), 2)
85
86         # Assert that the correct number of tasks is returned
87         self.assertEqual(len(self.tw.tasks.filter(priority="H")), 1)
88
89         # Assert that the correct tasks are returned
90         high_priority_task = self.tw.tasks.get(priority="H")
91         self.assertEqual(high_priority_task['description'], "high priority task")
92
93     def test_filtering_by_empty_attribute(self):
94         Task(self.tw, description="no priority task").save()
95         Task(self.tw, priority="H", description="high priority task").save()
96         self.assertEqual(len(self.tw.tasks.all()), 2)
97
98         # Assert that the correct number of tasks is returned
99         self.assertEqual(len(self.tw.tasks.filter(priority=None)), 1)
100
101         # Assert that the correct tasks are returned
102         no_priority_task = self.tw.tasks.get(priority=None)
103         self.assertEqual(no_priority_task['description'], "no priority task")
104
105     def test_filter_for_task_with_space_in_descripition(self):
106         task = Task(self.tw, description="test task")
107         task.save()
108
109         filtered_task = self.tw.tasks.get(description="test task")
110         self.assertEqual(filtered_task['description'], "test task")
111
112     def test_filter_for_task_without_space_in_descripition(self):
113         task = Task(self.tw, description="test")
114         task.save()
115
116         filtered_task = self.tw.tasks.get(description="test")
117         self.assertEqual(filtered_task['description'], "test")
118
119     def test_filter_for_task_with_space_in_project(self):
120         task = Task(self.tw, description="test", project="random project")
121         task.save()
122
123         filtered_task = self.tw.tasks.get(project="random project")
124         self.assertEqual(filtered_task['project'], "random project")
125
126     def test_filter_for_task_without_space_in_project(self):
127         task = Task(self.tw, description="test", project="random")
128         task.save()
129
130         filtered_task = self.tw.tasks.get(project="random")
131         self.assertEqual(filtered_task['project'], "random")
132
133     def test_filter_with_empty_uuid(self):
134         self.assertRaises(ValueError, lambda: self.tw.tasks.get(uuid=''))
135
136     def test_filter_dummy_by_status(self):
137         t = Task(self.tw, description="test")
138         t.save()
139
140         tasks = self.tw.tasks.filter(status=t['status'])
141         self.assertEqual(list(tasks), [t])
142
143     def test_filter_dummy_by_uuid(self):
144         t = Task(self.tw, description="test")
145         t.save()
146
147         tasks = self.tw.tasks.filter(uuid=t['uuid'])
148         self.assertEqual(list(tasks), [t])
149
150     def test_filter_dummy_by_entry(self):
151         t = Task(self.tw, description="test")
152         t.save()
153
154         tasks = self.tw.tasks.filter(entry=t['entry'])
155         self.assertEqual(list(tasks), [t])
156
157     def test_filter_dummy_by_description(self):
158         t = Task(self.tw, description="test")
159         t.save()
160
161         tasks = self.tw.tasks.filter(description=t['description'])
162         self.assertEqual(list(tasks), [t])
163
164     def test_filter_dummy_by_start(self):
165         t = Task(self.tw, description="test")
166         t.save()
167         t.start()
168
169         tasks = self.tw.tasks.filter(start=t['start'])
170         self.assertEqual(list(tasks), [t])
171
172     def test_filter_dummy_by_end(self):
173         t = Task(self.tw, description="test")
174         t.save()
175         t.done()
176
177         tasks = self.tw.tasks.filter(end=t['end'])
178         self.assertEqual(list(tasks), [t])
179
180     def test_filter_dummy_by_due(self):
181         t = Task(self.tw, description="test", due=datetime.datetime.now())
182         t.save()
183
184         tasks = self.tw.tasks.filter(due=t['due'])
185         self.assertEqual(list(tasks), [t])
186
187     def test_filter_dummy_by_until(self):
188         t = Task(self.tw, description="test")
189         t.save()
190
191         tasks = self.tw.tasks.filter(until=t['until'])
192         self.assertEqual(list(tasks), [t])
193
194     def test_filter_dummy_by_modified(self):
195         # Older TW version does not support bumping modified
196         # on save
197         if self.tw.version < six.text_type('2.2.0'):
198             # Python2.6 does not support SkipTest. As a workaround
199             # mark the test as passed by exiting.
200             if getattr(unittest, 'SkipTest', None) is not None:
201                 raise unittest.SkipTest()
202             else:
203                 return
204
205         t = Task(self.tw, description="test")
206         t.save()
207
208         tasks = self.tw.tasks.filter(modified=t['modified'])
209         self.assertEqual(list(tasks), [t])
210
211     def test_filter_dummy_by_scheduled(self):
212         t = Task(self.tw, description="test")
213         t.save()
214
215         tasks = self.tw.tasks.filter(scheduled=t['scheduled'])
216         self.assertEqual(list(tasks), [t])
217
218     def test_filter_dummy_by_tags(self):
219         t = Task(self.tw, description="test", tags=["home"])
220         t.save()
221
222         tasks = self.tw.tasks.filter(tags=t['tags'])
223         self.assertEqual(list(tasks), [t])
224
225     def test_filter_dummy_by_projects(self):
226         t = Task(self.tw, description="test", project="random")
227         t.save()
228
229         tasks = self.tw.tasks.filter(project=t['project'])
230         self.assertEqual(list(tasks), [t])
231
232     def test_filter_by_priority(self):
233         t = Task(self.tw, description="test", priority="H")
234         t.save()
235
236         tasks = self.tw.tasks.filter(priority=t['priority'])
237         self.assertEqual(list(tasks), [t])
238
239
240 class TaskTest(TasklibTest):
241
242     def test_create_unsaved_task(self):
243         # Make sure a new task is not saved unless explicitly called for
244         t = Task(self.tw, description="test task")
245         self.assertEqual(len(self.tw.tasks.all()), 0)
246
247     # TODO: once python 2.6 compatiblity is over, use context managers here
248     #       and in all subsequent tests for assertRaises
249
250     def test_delete_unsaved_task(self):
251         t = Task(self.tw, description="test task")
252         self.assertRaises(Task.NotSaved, t.delete)
253
254     def test_complete_unsaved_task(self):
255         t = Task(self.tw, description="test task")
256         self.assertRaises(Task.NotSaved, t.done)
257
258     def test_refresh_unsaved_task(self):
259         t = Task(self.tw, description="test task")
260         self.assertRaises(Task.NotSaved, t.refresh)
261
262     def test_start_unsaved_task(self):
263         t = Task(self.tw, description="test task")
264         self.assertRaises(Task.NotSaved, t.start)
265
266     def test_delete_deleted_task(self):
267         t = Task(self.tw, description="test task")
268         t.save()
269         t.delete()
270
271         self.assertRaises(Task.DeletedTask, t.delete)
272
273     def test_complete_completed_task(self):
274         t = Task(self.tw, description="test task")
275         t.save()
276         t.done()
277
278         self.assertRaises(Task.CompletedTask, t.done)
279
280     def test_start_completed_task(self):
281         t = Task(self.tw, description="test task")
282         t.save()
283         t.done()
284
285         self.assertRaises(Task.CompletedTask, t.start)
286
287     def test_complete_deleted_task(self):
288         t = Task(self.tw, description="test task")
289         t.save()
290         t.delete()
291
292         self.assertRaises(Task.DeletedTask, t.done)
293
294     def test_starting_task(self):
295         t = Task(self.tw, description="test task")
296         now = t.datetime_normalizer(datetime.datetime.now())
297         t.save()
298         t.start()
299
300         self.assertTrue(now.replace(microsecond=0) <= t['start'])
301         self.assertEqual(t['status'], 'pending')
302
303     def test_completing_task(self):
304         t = Task(self.tw, description="test task")
305         now = t.datetime_normalizer(datetime.datetime.now())
306         t.save()
307         t.done()
308
309         self.assertTrue(now.replace(microsecond=0) <= t['end'])
310         self.assertEqual(t['status'], 'completed')
311
312     def test_deleting_task(self):
313         t = Task(self.tw, description="test task")
314         now = t.datetime_normalizer(datetime.datetime.now())
315         t.save()
316         t.delete()
317
318         self.assertTrue(now.replace(microsecond=0) <= t['end'])
319         self.assertEqual(t['status'], 'deleted')
320
321     def test_started_task_active(self):
322         t = Task(self.tw, description="test task")
323         t.save()
324         t.start()
325         self.assertTrue(t.active)
326
327     def test_unstarted_task_inactive(self):
328         t = Task(self.tw, description="test task")
329         self.assertFalse(t.active)
330         t.save()
331         self.assertFalse(t.active)
332
333     def test_start_active_task(self):
334         t = Task(self.tw, description="test task")
335         t.save()
336         t.start()
337         self.assertRaises(Task.ActiveTask, t.start)
338
339     def test_stop_completed_task(self):
340         t = Task(self.tw, description="test task")
341         t.save()
342         t.start()
343         t.done()
344
345         self.assertRaises(Task.InactiveTask, t.stop)
346
347         t = Task(self.tw, description="test task")
348         t.save()
349         t.done()
350
351         self.assertRaises(Task.InactiveTask, t.stop)
352
353     def test_stop_deleted_task(self):
354         t = Task(self.tw, description="test task")
355         t.save()
356         t.start()
357         t.delete()
358         t.stop()
359
360     def test_stop_inactive_task(self):
361         t = Task(self.tw, description="test task")
362         t.save()
363
364         self.assertRaises(Task.InactiveTask, t.stop)
365
366         t = Task(self.tw, description="test task")
367         t.save()
368         t.start()
369         t.stop()
370
371         self.assertRaises(Task.InactiveTask, t.stop)
372
373     def test_stopping_task(self):
374         t = Task(self.tw, description="test task")
375         now = t.datetime_normalizer(datetime.datetime.now())
376         t.save()
377         t.start()
378         t.stop()
379
380         self.assertEqual(t['end'], None)
381         self.assertEqual(t['status'], 'pending')
382         self.assertFalse(t.active)
383
384     def test_modify_simple_attribute_without_space(self):
385         t = Task(self.tw, description="test")
386         t.save()
387
388         self.assertEquals(t['description'], "test")
389
390         t['description'] = "test-modified"
391         t.save()
392
393         self.assertEquals(t['description'], "test-modified")
394
395     def test_modify_simple_attribute_with_space(self):
396         # Space can pose problems with parsing
397         t = Task(self.tw, description="test task")
398         t.save()
399
400         self.assertEquals(t['description'], "test task")
401
402         t['description'] = "test task modified"
403         t.save()
404
405         self.assertEquals(t['description'], "test task modified")
406
407     def test_empty_dependency_set_of_unsaved_task(self):
408         t = Task(self.tw, description="test task")
409         self.assertEqual(t['depends'], set())
410
411     def test_empty_dependency_set_of_saved_task(self):
412         t = Task(self.tw, description="test task")
413         t.save()
414         self.assertEqual(t['depends'], set())
415
416     def test_set_unsaved_task_as_dependency(self):
417         # Adds only one dependency to task with no dependencies
418         t = Task(self.tw, description="test task")
419         dependency = Task(self.tw, description="needs to be done first")
420
421         # We only save the parent task, dependency task is unsaved
422         t.save()
423         t['depends'] = set([dependency])
424
425         self.assertRaises(Task.NotSaved, t.save)
426
427     def test_set_simple_dependency_set(self):
428         # Adds only one dependency to task with no dependencies
429         t = Task(self.tw, description="test task")
430         dependency = Task(self.tw, description="needs to be done first")
431
432         t.save()
433         dependency.save()
434
435         t['depends'] = set([dependency])
436
437         self.assertEqual(t['depends'], set([dependency]))
438
439     def test_set_complex_dependency_set(self):
440         # Adds two dependencies to task with no dependencies
441         t = Task(self.tw, description="test task")
442         dependency1 = Task(self.tw, description="needs to be done first")
443         dependency2 = Task(self.tw, description="needs to be done second")
444
445         t.save()
446         dependency1.save()
447         dependency2.save()
448
449         t['depends'] = set([dependency1, dependency2])
450
451         self.assertEqual(t['depends'], set([dependency1, dependency2]))
452
453     def test_remove_from_dependency_set(self):
454         # Removes dependency from task with two dependencies
455         t = Task(self.tw, description="test task")
456         dependency1 = Task(self.tw, description="needs to be done first")
457         dependency2 = Task(self.tw, description="needs to be done second")
458
459         dependency1.save()
460         dependency2.save()
461
462         t['depends'] = set([dependency1, dependency2])
463         t.save()
464
465         t['depends'].remove(dependency2)
466         t.save()
467
468         self.assertEqual(t['depends'], set([dependency1]))
469
470     def test_add_to_dependency_set(self):
471         # Adds dependency to task with one dependencies
472         t = Task(self.tw, description="test task")
473         dependency1 = Task(self.tw, description="needs to be done first")
474         dependency2 = Task(self.tw, description="needs to be done second")
475
476         dependency1.save()
477         dependency2.save()
478
479         t['depends'] = set([dependency1])
480         t.save()
481
482         t['depends'].add(dependency2)
483         t.save()
484
485         self.assertEqual(t['depends'], set([dependency1, dependency2]))
486
487     def test_add_to_empty_dependency_set(self):
488         # Adds dependency to task with one dependencies
489         t = Task(self.tw, description="test task")
490         dependency = Task(self.tw, description="needs to be done first")
491
492         dependency.save()
493
494         t['depends'].add(dependency)
495         t.save()
496
497         self.assertEqual(t['depends'], set([dependency]))
498
499     def test_simple_dependency_set_save_repeatedly(self):
500         # Adds only one dependency to task with no dependencies
501         t = Task(self.tw, description="test task")
502         dependency = Task(self.tw, description="needs to be done first")
503         dependency.save()
504
505         t['depends'] = set([dependency])
506         t.save()
507
508         # We taint the task, but keep depends intact
509         t['description'] = "test task modified"
510         t.save()
511
512         self.assertEqual(t['depends'], set([dependency]))
513
514         # We taint the task, but assign the same set to the depends
515         t['depends'] = set([dependency])
516         t['description'] = "test task modified again"
517         t.save()
518
519         self.assertEqual(t['depends'], set([dependency]))
520
521     def test_compare_different_tasks(self):
522         # Negative: compare two different tasks
523         t1 = Task(self.tw, description="test task")
524         t2 = Task(self.tw, description="test task")
525
526         t1.save()
527         t2.save()
528
529         self.assertEqual(t1 == t2, False)
530
531     def test_compare_same_task_object(self):
532         # Compare Task object wit itself
533         t = Task(self.tw, description="test task")
534         t.save()
535
536         self.assertEqual(t == t, True)
537
538     def test_compare_same_task(self):
539         # Compare the same task using two different objects
540         t1 = Task(self.tw, description="test task")
541         t1.save()
542
543         t2 = self.tw.tasks.get(uuid=t1['uuid'])
544         self.assertEqual(t1 == t2, True)
545
546     def test_compare_unsaved_tasks(self):
547         # t1 and t2 are unsaved tasks, considered to be unequal
548         # despite the content of data
549         t1 = Task(self.tw, description="test task")
550         t2 = Task(self.tw, description="test task")
551
552         self.assertEqual(t1 == t2, False)
553
554     def test_hash_unsaved_tasks(self):
555         # Considered equal, it's the same object
556         t1 = Task(self.tw, description="test task")
557         t2 = t1
558         self.assertEqual(hash(t1) == hash(t2), True)
559
560     def test_hash_same_task(self):
561         # Compare the hash of the task using two different objects
562         t1 = Task(self.tw, description="test task")
563         t1.save()
564
565         t2 = self.tw.tasks.get(uuid=t1['uuid'])
566         self.assertEqual(t1.__hash__(), t2.__hash__())
567
568     def test_adding_task_with_priority(self):
569         t = Task(self.tw, description="test task", priority="M")
570         t.save()
571
572     def test_removing_priority_with_none(self):
573         t = Task(self.tw, description="test task", priority="L")
574         t.save()
575
576         # Remove the priority mark
577         t['priority'] = None
578         t.save()
579
580         # Assert that priority is not there after saving
581         self.assertEqual(t['priority'], None)
582
583     def test_adding_task_with_due_time(self):
584         t = Task(self.tw, description="test task", due=datetime.datetime.now())
585         t.save()
586
587     def test_removing_due_time_with_none(self):
588         t = Task(self.tw, description="test task", due=datetime.datetime.now())
589         t.save()
590
591         # Remove the due timestamp
592         t['due'] = None
593         t.save()
594
595         # Assert that due timestamp is no longer there
596         self.assertEqual(t['due'], None)
597
598     def test_modified_fields_new_task(self):
599         t = Task(self.tw)
600
601         # This should be empty with new task
602         self.assertEqual(set(t._modified_fields), set())
603
604         # Modify the task
605         t['description'] = "test task"
606         self.assertEqual(set(t._modified_fields), set(['description']))
607
608         t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14)  # <3
609         self.assertEqual(set(t._modified_fields), set(['description', 'due']))
610
611         t['project'] = "test project"
612         self.assertEqual(set(t._modified_fields),
613                          set(['description', 'due', 'project']))
614
615         # List of modified fields should clear out when saved
616         t.save()
617         self.assertEqual(set(t._modified_fields), set())
618
619         # Reassigning the fields with the same values now should not produce
620         # modified fields
621         t['description'] = "test task"
622         t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14)  # <3
623         t['project'] = "test project"
624         self.assertEqual(set(t._modified_fields), set())
625
626     def test_modified_fields_loaded_task(self):
627         t = Task(self.tw)
628
629         # Modify the task
630         t['description'] = "test task"
631         t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14)  # <3
632         t['project'] = "test project"
633
634         dependency = Task(self.tw, description="dependency")
635         dependency.save()
636         t['depends'] = set([dependency])
637
638         # List of modified fields should clear out when saved
639         t.save()
640         self.assertEqual(set(t._modified_fields), set())
641
642         # Get the task by using a filter by UUID
643         t2 = self.tw.tasks.get(uuid=t['uuid'])
644
645         # Reassigning the fields with the same values now should not produce
646         # modified fields
647         t['description'] = "test task"
648         t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14)  # <3
649         t['project'] = "test project"
650         t['depends'] = set([dependency])
651         self.assertEqual(set(t._modified_fields), set())
652
653     def test_modified_fields_not_affected_by_reading(self):
654         t = Task(self.tw)
655
656         for field in TASK_STANDARD_ATTRS:
657             value = t[field]
658
659         self.assertEqual(set(t._modified_fields), set())
660
661     def test_setting_read_only_attrs_through_init(self):
662         # Test that we are unable to set readonly attrs through __init__
663         for readonly_key in Task.read_only_fields:
664             kwargs = {'description': 'test task', readonly_key: 'value'}
665             self.assertRaises(RuntimeError,
666                               lambda: Task(self.tw, **kwargs))
667
668     def test_setting_read_only_attrs_through_setitem(self):
669         # Test that we are unable to set readonly attrs through __init__
670         for readonly_key in Task.read_only_fields:
671             t = Task(self.tw, description='test task')
672             self.assertRaises(RuntimeError,
673                               lambda: t.__setitem__(readonly_key, 'value'))
674
675     def test_saving_unmodified_task(self):
676         t = Task(self.tw, description="test task")
677         t.save()
678         t.save()
679
680     def test_adding_tag_by_appending(self):
681         t = Task(self.tw, description="test task", tags=['test1'])
682         t.save()
683         t['tags'].append('test2')
684         t.save()
685         self.assertEqual(t['tags'], ['test1', 'test2'])
686
687     def test_adding_tag_by_appending_empty(self):
688         t = Task(self.tw, description="test task")
689         t.save()
690         t['tags'].append('test')
691         t.save()
692         self.assertEqual(t['tags'], ['test'])
693
694     def test_serializers_returning_empty_string_for_none(self):
695         # Test that any serializer returns '' when passed None
696         t = Task(self.tw)
697         serializers = [getattr(t, serializer_name) for serializer_name in
698                        filter(lambda x: x.startswith('serialize_'), dir(t))]
699         for serializer in serializers:
700             self.assertEqual(serializer(None), '')
701
702     def test_deserializer_returning_empty_value_for_empty_string(self):
703         # Test that any deserializer returns empty value when passed ''
704         t = Task(self.tw)
705         deserializers = [getattr(t, deserializer_name) for deserializer_name in
706                         filter(lambda x: x.startswith('deserialize_'), dir(t))]
707         for deserializer in deserializers:
708             self.assertTrue(deserializer('') in (None, [], set()))
709
710     def test_normalizers_handling_none(self):
711         # Test that any normalizer can handle None as a valid value
712         t = Task(self.tw)
713
714         for key in TASK_STANDARD_ATTRS:
715             t._normalize(key, None)
716
717     def test_recurrent_task_generation(self):
718         today = datetime.date.today()
719         t = Task(self.tw, description="brush teeth",
720                  due=today, recur="daily")
721         t.save()
722         self.assertEqual(len(self.tw.tasks.pending()), 2)
723
724     def test_modify_number_of_tasks_at_once(self):
725         for i in range(1, 100):
726             Task(self.tw, description="test task %d" % i, tags=['test']).save()
727
728         self.tw.execute_command(['+test', 'mod', 'unified', 'description'])
729
730     def test_return_all_from_executed_command(self):
731         Task(self.tw, description="test task", tags=['test']).save()
732         out, err, rc = self.tw.execute_command(['count'], return_all=True)
733         self.assertEqual(rc, 0)
734
735     def test_return_all_from_failed_executed_command(self):
736         Task(self.tw, description="test task", tags=['test']).save()
737         out, err, rc = self.tw.execute_command(['countinvalid'],
738             return_all=True, allow_failure=False)
739         self.assertNotEqual(rc, 0)
740
741
742 class TaskFromHookTest(TasklibTest):
743
744     input_add_data = six.StringIO(
745         '{"description":"Buy some milk",'
746         '"entry":"20141118T050231Z",'
747         '"status":"pending",'
748         '"start":"20141119T152233Z",'
749         '"uuid":"a360fc44-315c-4366-b70c-ea7e7520b749"}')
750
751     input_modify_data = six.StringIO(input_add_data.getvalue() + '\n' +
752         '{"description":"Buy some milk finally",'
753         '"entry":"20141118T050231Z",'
754         '"status":"completed",'
755         '"uuid":"a360fc44-315c-4366-b70c-ea7e7520b749"}')
756
757     exported_raw_data = (
758         '{"project":"Home",'
759          '"due":"20150101T232323Z",'
760          '"description":"test task"}')
761
762     def test_setting_up_from_add_hook_input(self):
763         t = Task.from_input(input_file=self.input_add_data, warrior=self.tw)
764         self.assertEqual(t['description'], "Buy some milk")
765         self.assertEqual(t.pending, True)
766
767     def test_setting_up_from_modified_hook_input(self):
768         t = Task.from_input(input_file=self.input_modify_data, modify=True,
769                             warrior=self.tw)
770         self.assertEqual(t['description'], "Buy some milk finally")
771         self.assertEqual(t.pending, False)
772         self.assertEqual(t.completed, True)
773
774         self.assertEqual(t._original_data['status'], "pending")
775         self.assertEqual(t._original_data['description'], "Buy some milk")
776         self.assertEqual(set(t._modified_fields),
777                          set(['status', 'description', 'start']))
778
779     def test_export_data(self):
780         t = Task(self.tw, description="test task",
781             project="Home",
782             due=pytz.utc.localize(datetime.datetime(2015,1,1,23,23,23)))
783
784         # Check that the output is a permutation of:
785         # {"project":"Home","description":"test task","due":"20150101232323Z"}
786         allowed_segments = self.exported_raw_data[1:-1].split(',')
787         allowed_output = [
788             '{' + ','.join(segments) + '}'
789             for segments in itertools.permutations(allowed_segments)
790         ]
791
792         self.assertTrue(any(t.export_data() == expected
793                             for expected in allowed_output))
794
795 class TimezoneAwareDatetimeTest(TasklibTest):
796
797     def setUp(self):
798         super(TimezoneAwareDatetimeTest, self).setUp()
799         self.zone = local_zone
800         self.localdate_naive = datetime.datetime(2015,2,2)
801         self.localtime_naive = datetime.datetime(2015,2,2,0,0,0)
802         self.localtime_aware = self.zone.localize(self.localtime_naive)
803         self.utctime_aware = self.localtime_aware.astimezone(pytz.utc)
804
805     def test_timezone_naive_datetime_setitem(self):
806         t = Task(self.tw, description="test task")
807         t['due'] = self.localtime_naive
808         self.assertEqual(t['due'], self.localtime_aware)
809
810     def test_timezone_naive_datetime_using_init(self):
811         t = Task(self.tw, description="test task", due=self.localtime_naive)
812         self.assertEqual(t['due'], self.localtime_aware)
813
814     def test_filter_by_naive_datetime(self):
815         t = Task(self.tw, description="task1", due=self.localtime_naive)
816         t.save()
817         matching_tasks = self.tw.tasks.filter(due=self.localtime_naive)
818         self.assertEqual(len(matching_tasks), 1)
819
820     def test_serialize_naive_datetime(self):
821         t = Task(self.tw, description="task1", due=self.localtime_naive)
822         self.assertEqual(json.loads(t.export_data())['due'],
823                          self.utctime_aware.strftime(DATE_FORMAT))
824
825     def test_timezone_naive_date_setitem(self):
826         t = Task(self.tw, description="test task")
827         t['due'] = self.localdate_naive
828         self.assertEqual(t['due'], self.localtime_aware)
829
830     def test_timezone_naive_date_using_init(self):
831         t = Task(self.tw, description="test task", due=self.localdate_naive)
832         self.assertEqual(t['due'], self.localtime_aware)
833
834     def test_filter_by_naive_date(self):
835         t = Task(self.tw, description="task1", due=self.localdate_naive)
836         t.save()
837         matching_tasks = self.tw.tasks.filter(due=self.localdate_naive)
838         self.assertEqual(len(matching_tasks), 1)
839
840     def test_serialize_naive_date(self):
841         t = Task(self.tw, description="task1", due=self.localdate_naive)
842         self.assertEqual(json.loads(t.export_data())['due'],
843                          self.utctime_aware.strftime(DATE_FORMAT))
844
845     def test_timezone_aware_datetime_setitem(self):
846         t = Task(self.tw, description="test task")
847         t['due'] = self.localtime_aware
848         self.assertEqual(t['due'], self.localtime_aware)
849
850     def test_timezone_aware_datetime_using_init(self):
851         t = Task(self.tw, description="test task", due=self.localtime_aware)
852         self.assertEqual(t['due'], self.localtime_aware)
853
854     def test_filter_by_aware_datetime(self):
855         t = Task(self.tw, description="task1", due=self.localtime_aware)
856         t.save()
857         matching_tasks = self.tw.tasks.filter(due=self.localtime_aware)
858         self.assertEqual(len(matching_tasks), 1)
859
860     def test_serialize_aware_datetime(self):
861         t = Task(self.tw, description="task1", due=self.localtime_aware)
862         self.assertEqual(json.loads(t.export_data())['due'],
863                          self.utctime_aware.strftime(DATE_FORMAT))
864
865 class DatetimeStringTest(TasklibTest):
866
867     def test_simple_now_conversion(self):
868         if self.tw.version < six.text_type('2.4.0'):
869             # Python2.6 does not support SkipTest. As a workaround
870             # mark the test as passed by exiting.
871             if getattr(unittest, 'SkipTest', None) is not None:
872                 raise unittest.SkipTest()
873             else:
874                 return
875
876         t = Task(self.tw, description="test task", due="now")
877         now = local_zone.localize(datetime.datetime.now())
878
879         # Assert that both times are not more than 5 seconds apart
880         if sys.version_info < (2,7):
881             self.assertTrue(total_seconds_2_6(now - t['due']) < 5)
882             self.assertTrue(total_seconds_2_6(t['due'] - now) < 5)
883         else:
884             self.assertTrue((now - t['due']).total_seconds() < 5)
885             self.assertTrue((t['due'] - now).total_seconds() < 5)
886
887     def test_simple_eoy_conversion(self):
888         if self.tw.version < six.text_type('2.4.0'):
889             # Python2.6 does not support SkipTest. As a workaround
890             # mark the test as passed by exiting.
891             if getattr(unittest, 'SkipTest', None) is not None:
892                 raise unittest.SkipTest()
893             else:
894                 return
895
896         t = Task(self.tw, description="test task", due="eoy")
897         now = local_zone.localize(datetime.datetime.now())
898         eoy = local_zone.localize(datetime.datetime(
899             year=now.year,
900             month=12,
901             day=31,
902             hour=23,
903             minute=59,
904             second=59
905             ))
906         self.assertEqual(eoy, t['due'])
907
908     def test_complex_eoy_conversion(self):
909         if self.tw.version < six.text_type('2.4.0'):
910             # Python2.6 does not support SkipTest. As a workaround
911             # mark the test as passed by exiting.
912             if getattr(unittest, 'SkipTest', None) is not None:
913                 raise unittest.SkipTest()
914             else:
915                 return
916
917         t = Task(self.tw, description="test task", due="eoy - 4 months")
918         now = local_zone.localize(datetime.datetime.now())
919         due_date = local_zone.localize(datetime.datetime(
920             year=now.year,
921             month=12,
922             day=31,
923             hour=23,
924             minute=59,
925             second=59
926             )) - datetime.timedelta(0,4 * 30 * 86400)
927         self.assertEqual(due_date, t['due'])
928
929     def test_filtering_with_string_datetime(self):
930         if self.tw.version < six.text_type('2.4.0'):
931             # Python2.6 does not support SkipTest. As a workaround
932             # mark the test as passed by exiting.
933             if getattr(unittest, 'SkipTest', None) is not None:
934                 raise unittest.SkipTest()
935             else:
936                 return
937
938         t = Task(self.tw, description="test task",
939                  due=datetime.datetime.now() - datetime.timedelta(0,2))
940         t.save()
941         self.assertEqual(len(self.tw.tasks.filter(due__before="now")), 1)
942
943 class AnnotationTest(TasklibTest):
944
945     def setUp(self):
946         super(AnnotationTest, self).setUp()
947         Task(self.tw, description="test task").save()
948
949     def test_adding_annotation(self):
950         task = self.tw.tasks.get()
951         task.add_annotation('test annotation')
952         self.assertEqual(len(task['annotations']), 1)
953         ann = task['annotations'][0]
954         self.assertEqual(ann['description'], 'test annotation')
955
956     def test_removing_annotation(self):
957         task = self.tw.tasks.get()
958         task.add_annotation('test annotation')
959         ann = task['annotations'][0]
960         ann.remove()
961         self.assertEqual(len(task['annotations']), 0)
962
963     def test_removing_annotation_by_description(self):
964         task = self.tw.tasks.get()
965         task.add_annotation('test annotation')
966         task.remove_annotation('test annotation')
967         self.assertEqual(len(task['annotations']), 0)
968
969     def test_removing_annotation_by_obj(self):
970         task = self.tw.tasks.get()
971         task.add_annotation('test annotation')
972         ann = task['annotations'][0]
973         task.remove_annotation(ann)
974         self.assertEqual(len(task['annotations']), 0)
975
976     def test_annotation_after_modification(self):
977          task = self.tw.tasks.get()
978          task['project'] = 'test'
979          task.add_annotation('I should really do this task')
980          self.assertEqual(task['project'], 'test')
981          task.save()
982          self.assertEqual(task['project'], 'test')
983
984     def test_serialize_annotations(self):
985         # Test that serializing annotations is possible
986         t = Task(self.tw, description="test")
987         t.save()
988
989         t.add_annotation("annotation1")
990         t.add_annotation("annotation2")
991
992         data = t._serialize('annotations', t._data['annotations'])
993
994         self.assertEqual(len(data), 2)
995         self.assertEqual(type(data[0]), dict)
996         self.assertEqual(type(data[1]), dict)
997
998         self.assertEqual(data[0]['description'], "annotation1")
999         self.assertEqual(data[1]['description'], "annotation2")
1000
1001
1002 class UnicodeTest(TasklibTest):
1003
1004     def test_unicode_task(self):
1005         Task(self.tw, description=six.u("†åßk")).save()
1006         self.tw.tasks.get()
1007
1008     def test_filter_by_unicode_task(self):
1009         Task(self.tw, description=six.u("†åßk")).save()
1010         tasks = self.tw.tasks.filter(description=six.u("†åßk"))
1011         self.assertEqual(len(tasks), 1)
1012
1013     def test_non_unicode_task(self):
1014         Task(self.tw, description="test task").save()
1015         self.tw.tasks.get()
1016
1017 class ReadOnlyDictViewTest(unittest.TestCase):
1018
1019     def setUp(self):
1020         self.sample = dict(l=[1,2,3], d={'k':'v'})
1021         self.original_sample = copy.deepcopy(self.sample)
1022         self.view = ReadOnlyDictView(self.sample)
1023
1024     def test_readonlydictview_getitem(self):
1025         l = self.view['l']
1026         self.assertEqual(l, self.sample['l'])
1027
1028         # Assert that modification changed only copied value
1029         l.append(4)
1030         self.assertNotEqual(l, self.sample['l'])
1031
1032         # Assert that viewed dict is not changed
1033         self.assertEqual(self.sample, self.original_sample)
1034
1035     def test_readonlydictview_contains(self):
1036         self.assertEqual('l' in self.view, 'l' in self.sample)
1037         self.assertEqual('d' in self.view, 'd' in self.sample)
1038         self.assertEqual('k' in self.view, 'k' in self.sample)
1039
1040         # Assert that viewed dict is not changed
1041         self.assertEqual(self.sample, self.original_sample)
1042
1043     def test_readonlydictview_iter(self):
1044         self.assertEqual(list(k for k in self.view),
1045                          list(k for k in self.sample))
1046
1047         # Assert the view is correct after modification
1048         self.sample['new'] = 'value'
1049         self.assertEqual(list(k for k in self.view),
1050                          list(k for k in self.sample))
1051
1052     def test_readonlydictview_len(self):
1053         self.assertEqual(len(self.view), len(self.sample))
1054
1055         # Assert the view is correct after modification
1056         self.sample['new'] = 'value'
1057         self.assertEqual(len(self.view), len(self.sample))
1058
1059     def test_readonlydictview_get(self):
1060         l = self.view.get('l')
1061         self.assertEqual(l, self.sample.get('l'))
1062
1063         # Assert that modification changed only copied value
1064         l.append(4)
1065         self.assertNotEqual(l, self.sample.get('l'))
1066
1067         # Assert that viewed dict is not changed
1068         self.assertEqual(self.sample, self.original_sample)
1069
1070     def test_readonlydict_items(self):
1071         view_items = self.view.items()
1072         sample_items = list(self.sample.items())
1073         self.assertEqual(view_items, sample_items)
1074
1075         view_items.append('newkey')
1076         self.assertNotEqual(view_items, sample_items)
1077         self.assertEqual(self.sample, self.original_sample)
1078
1079     def test_readonlydict_values(self):
1080         view_values = self.view.values()
1081         sample_values = list(self.sample.values())
1082         self.assertEqual(view_values, sample_values)
1083
1084         view_list_item = list(filter(lambda x: type(x) is list,
1085                                      view_values))[0]
1086         view_list_item.append(4)
1087         self.assertNotEqual(view_values, sample_values)
1088         self.assertEqual(self.sample, self.original_sample)