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.
14 from .backends import TaskWarrior
15 from .task import Task, ReadOnlyDictView, TaskQuerySet
16 from .lazy import LazyUUIDTask, LazyUUIDTaskSet
17 from .serializing import DATE_FORMAT, local_zone
19 # http://taskwarrior.org/docs/design/task.html , Section: The Attributes
20 TASK_STANDARD_ATTRS = (
43 total_seconds_2_6 = lambda x: x.microseconds / 1e6 + x.seconds + x.days * 24 * 3600
46 class TasklibTest(unittest.TestCase):
49 self.tmp = tempfile.mkdtemp(dir='.')
50 self.tw = TaskWarrior(data_location=self.tmp, taskrc_location='/')
53 shutil.rmtree(self.tmp)
56 class TaskFilterTest(TasklibTest):
58 def test_all_empty(self):
59 self.assertEqual(len(self.tw.tasks.all()), 0)
61 def test_all_non_empty(self):
62 Task(self.tw, description="test task").save()
63 self.assertEqual(len(self.tw.tasks.all()), 1)
64 self.assertEqual(self.tw.tasks.all()[0]['description'], 'test task')
65 self.assertEqual(self.tw.tasks.all()[0]['status'], 'pending')
67 def test_pending_non_empty(self):
68 Task(self.tw, description="test task").save()
69 self.assertEqual(len(self.tw.tasks.pending()), 1)
70 self.assertEqual(self.tw.tasks.pending()[0]['description'],
72 self.assertEqual(self.tw.tasks.pending()[0]['status'], 'pending')
74 def test_completed_empty(self):
75 Task(self.tw, description="test task").save()
76 self.assertEqual(len(self.tw.tasks.completed()), 0)
78 def test_completed_non_empty(self):
79 Task(self.tw, description="test task").save()
80 self.assertEqual(len(self.tw.tasks.completed()), 0)
81 self.tw.tasks.all()[0].done()
82 self.assertEqual(len(self.tw.tasks.completed()), 1)
84 def test_deleted_empty(self):
85 Task(self.tw, description="test task").save()
86 self.assertEqual(len(self.tw.tasks.deleted()), 0)
88 def test_deleted_non_empty(self):
89 Task(self.tw, description="test task").save()
90 self.assertEqual(len(self.tw.tasks.deleted()), 0)
91 self.tw.tasks.all()[0].delete()
92 self.assertEqual(len(self.tw.tasks.deleted()), 1)
94 def test_waiting_empty(self):
95 Task(self.tw, description="test task").save()
96 self.assertEqual(len(self.tw.tasks.waiting()), 0)
98 def test_waiting_non_empty(self):
99 Task(self.tw, description="test task").save()
100 self.assertEqual(len(self.tw.tasks.waiting()), 0)
102 t = self.tw.tasks.all()[0]
103 t['wait'] = datetime.datetime.now() + datetime.timedelta(days=1)
106 self.assertEqual(len(self.tw.tasks.waiting()), 1)
108 def test_recurring_empty(self):
109 Task(self.tw, description="test task").save()
110 self.assertEqual(len(self.tw.tasks.recurring()), 0)
112 def test_recurring_non_empty(self):
113 Task(self.tw, description="test task", recur="daily",
114 due=datetime.datetime.now()).save()
115 self.assertEqual(len(self.tw.tasks.recurring()), 1)
117 def test_filtering_by_attribute(self):
118 Task(self.tw, description="no priority task").save()
119 Task(self.tw, priority="H", description="high priority task").save()
120 self.assertEqual(len(self.tw.tasks.all()), 2)
122 # Assert that the correct number of tasks is returned
123 self.assertEqual(len(self.tw.tasks.filter(priority="H")), 1)
125 # Assert that the correct tasks are returned
126 high_priority_task = self.tw.tasks.get(priority="H")
127 self.assertEqual(high_priority_task['description'], "high priority task")
129 def test_filtering_by_empty_attribute(self):
130 Task(self.tw, description="no priority task").save()
131 Task(self.tw, priority="H", description="high priority task").save()
132 self.assertEqual(len(self.tw.tasks.all()), 2)
134 # Assert that the correct number of tasks is returned
135 self.assertEqual(len(self.tw.tasks.filter(priority=None)), 1)
137 # Assert that the correct tasks are returned
138 no_priority_task = self.tw.tasks.get(priority=None)
139 self.assertEqual(no_priority_task['description'], "no priority task")
141 def test_filter_for_task_with_space_in_descripition(self):
142 task = Task(self.tw, description="test task")
145 filtered_task = self.tw.tasks.get(description="test task")
146 self.assertEqual(filtered_task['description'], "test task")
148 def test_filter_for_task_without_space_in_descripition(self):
149 task = Task(self.tw, description="test")
152 filtered_task = self.tw.tasks.get(description="test")
153 self.assertEqual(filtered_task['description'], "test")
155 def test_filter_for_task_with_space_in_project(self):
156 task = Task(self.tw, description="test", project="random project")
159 filtered_task = self.tw.tasks.get(project="random project")
160 self.assertEqual(filtered_task['project'], "random project")
162 def test_filter_for_task_without_space_in_project(self):
163 task = Task(self.tw, description="test", project="random")
166 filtered_task = self.tw.tasks.get(project="random")
167 self.assertEqual(filtered_task['project'], "random")
169 def test_filter_with_empty_uuid(self):
170 self.assertRaises(ValueError, lambda: self.tw.tasks.get(uuid=''))
172 def test_filter_dummy_by_status(self):
173 t = Task(self.tw, description="test")
176 tasks = self.tw.tasks.filter(status=t['status'])
177 self.assertEqual(list(tasks), [t])
179 def test_filter_dummy_by_uuid(self):
180 t = Task(self.tw, description="test")
183 tasks = self.tw.tasks.filter(uuid=t['uuid'])
184 self.assertEqual(list(tasks), [t])
186 def test_filter_dummy_by_entry(self):
187 t = Task(self.tw, description="test")
190 tasks = self.tw.tasks.filter(entry=t['entry'])
191 self.assertEqual(list(tasks), [t])
193 def test_filter_dummy_by_description(self):
194 t = Task(self.tw, description="test")
197 tasks = self.tw.tasks.filter(description=t['description'])
198 self.assertEqual(list(tasks), [t])
200 def test_filter_dummy_by_start(self):
201 t = Task(self.tw, description="test")
205 tasks = self.tw.tasks.filter(start=t['start'])
206 self.assertEqual(list(tasks), [t])
208 def test_filter_dummy_by_end(self):
209 t = Task(self.tw, description="test")
213 tasks = self.tw.tasks.filter(end=t['end'])
214 self.assertEqual(list(tasks), [t])
216 def test_filter_dummy_by_due(self):
217 t = Task(self.tw, description="test", due=datetime.datetime.now())
220 tasks = self.tw.tasks.filter(due=t['due'])
221 self.assertEqual(list(tasks), [t])
223 def test_filter_dummy_by_until(self):
224 t = Task(self.tw, description="test")
227 tasks = self.tw.tasks.filter(until=t['until'])
228 self.assertEqual(list(tasks), [t])
230 def test_filter_dummy_by_modified(self):
231 # Older TW version does not support bumping modified
233 if self.tw.version < six.text_type('2.2.0'):
234 # Python2.6 does not support SkipTest. As a workaround
235 # mark the test as passed by exiting.
236 if getattr(unittest, 'SkipTest', None) is not None:
237 raise unittest.SkipTest()
241 t = Task(self.tw, description="test")
244 tasks = self.tw.tasks.filter(modified=t['modified'])
245 self.assertEqual(list(tasks), [t])
247 def test_filter_dummy_by_scheduled(self):
248 t = Task(self.tw, description="test")
251 tasks = self.tw.tasks.filter(scheduled=t['scheduled'])
252 self.assertEqual(list(tasks), [t])
254 def test_filter_dummy_by_tags(self):
255 t = Task(self.tw, description="test", tags=["home"])
258 tasks = self.tw.tasks.filter(tags=t['tags'])
259 self.assertEqual(list(tasks), [t])
261 def test_filter_dummy_by_projects(self):
262 t = Task(self.tw, description="test", project="random")
265 tasks = self.tw.tasks.filter(project=t['project'])
266 self.assertEqual(list(tasks), [t])
268 def test_filter_by_priority(self):
269 t = Task(self.tw, description="test", priority="H")
272 tasks = self.tw.tasks.filter(priority=t['priority'])
273 self.assertEqual(list(tasks), [t])
276 class TaskTest(TasklibTest):
278 def test_create_unsaved_task(self):
279 # Make sure a new task is not saved unless explicitly called for
280 t = Task(self.tw, description="test task")
281 self.assertEqual(len(self.tw.tasks.all()), 0)
283 # TODO: once python 2.6 compatiblity is over, use context managers here
284 # and in all subsequent tests for assertRaises
286 def test_delete_unsaved_task(self):
287 t = Task(self.tw, description="test task")
288 self.assertRaises(Task.NotSaved, t.delete)
290 def test_complete_unsaved_task(self):
291 t = Task(self.tw, description="test task")
292 self.assertRaises(Task.NotSaved, t.done)
294 def test_refresh_unsaved_task(self):
295 t = Task(self.tw, description="test task")
296 self.assertRaises(Task.NotSaved, t.refresh)
298 def test_start_unsaved_task(self):
299 t = Task(self.tw, description="test task")
300 self.assertRaises(Task.NotSaved, t.start)
302 def test_delete_deleted_task(self):
303 t = Task(self.tw, description="test task")
307 self.assertRaises(Task.DeletedTask, t.delete)
309 def test_complete_completed_task(self):
310 t = Task(self.tw, description="test task")
314 self.assertRaises(Task.CompletedTask, t.done)
316 def test_start_completed_task(self):
317 t = Task(self.tw, description="test task")
321 self.assertRaises(Task.CompletedTask, t.start)
323 def test_add_completed_task(self):
324 t = Task(self.tw, description="test", status="completed",
325 end=datetime.datetime.now())
328 def test_add_multiple_completed_tasks(self):
329 t1 = Task(self.tw, description="test1", status="completed",
330 end=datetime.datetime.now())
331 t2 = Task(self.tw, description="test2", status="completed",
332 end=datetime.datetime.now())
336 def test_complete_deleted_task(self):
337 t = Task(self.tw, description="test task")
341 self.assertRaises(Task.DeletedTask, t.done)
343 def test_starting_task(self):
344 t = Task(self.tw, description="test task")
345 now = t.datetime_normalizer(datetime.datetime.now())
349 self.assertTrue(now.replace(microsecond=0) <= t['start'])
350 self.assertEqual(t['status'], 'pending')
352 def test_completing_task(self):
353 t = Task(self.tw, description="test task")
354 now = t.datetime_normalizer(datetime.datetime.now())
358 self.assertTrue(now.replace(microsecond=0) <= t['end'])
359 self.assertEqual(t['status'], 'completed')
361 def test_deleting_task(self):
362 t = Task(self.tw, description="test task")
363 now = t.datetime_normalizer(datetime.datetime.now())
367 self.assertTrue(now.replace(microsecond=0) <= t['end'])
368 self.assertEqual(t['status'], 'deleted')
370 def test_started_task_active(self):
371 t = Task(self.tw, description="test task")
374 self.assertTrue(t.active)
376 def test_unstarted_task_inactive(self):
377 t = Task(self.tw, description="test task")
378 self.assertFalse(t.active)
380 self.assertFalse(t.active)
382 def test_start_active_task(self):
383 t = Task(self.tw, description="test task")
386 self.assertRaises(Task.ActiveTask, t.start)
388 def test_stop_completed_task(self):
389 t = Task(self.tw, description="test task")
394 self.assertRaises(Task.InactiveTask, t.stop)
396 t = Task(self.tw, description="test task")
400 self.assertRaises(Task.InactiveTask, t.stop)
402 def test_stop_deleted_task(self):
403 t = Task(self.tw, description="test task")
409 def test_stop_inactive_task(self):
410 t = Task(self.tw, description="test task")
413 self.assertRaises(Task.InactiveTask, t.stop)
415 t = Task(self.tw, description="test task")
420 self.assertRaises(Task.InactiveTask, t.stop)
422 def test_stopping_task(self):
423 t = Task(self.tw, description="test task")
424 now = t.datetime_normalizer(datetime.datetime.now())
429 self.assertEqual(t['end'], None)
430 self.assertEqual(t['status'], 'pending')
431 self.assertFalse(t.active)
433 def test_modify_simple_attribute_without_space(self):
434 t = Task(self.tw, description="test")
437 self.assertEquals(t['description'], "test")
439 t['description'] = "test-modified"
442 self.assertEquals(t['description'], "test-modified")
444 def test_modify_simple_attribute_with_space(self):
445 # Space can pose problems with parsing
446 t = Task(self.tw, description="test task")
449 self.assertEquals(t['description'], "test task")
451 t['description'] = "test task modified"
454 self.assertEquals(t['description'], "test task modified")
456 def test_empty_dependency_set_of_unsaved_task(self):
457 t = Task(self.tw, description="test task")
458 self.assertEqual(t['depends'], set())
460 def test_empty_dependency_set_of_saved_task(self):
461 t = Task(self.tw, description="test task")
463 self.assertEqual(t['depends'], set())
465 def test_set_unsaved_task_as_dependency(self):
466 # Adds only one dependency to task with no dependencies
467 t = Task(self.tw, description="test task")
468 dependency = Task(self.tw, description="needs to be done first")
470 # We only save the parent task, dependency task is unsaved
472 t['depends'] = set([dependency])
474 self.assertRaises(Task.NotSaved, t.save)
476 def test_set_simple_dependency_set(self):
477 # Adds only one dependency to task with no dependencies
478 t = Task(self.tw, description="test task")
479 dependency = Task(self.tw, description="needs to be done first")
484 t['depends'] = set([dependency])
486 self.assertEqual(t['depends'], set([dependency]))
488 def test_set_complex_dependency_set(self):
489 # Adds two dependencies to task with no dependencies
490 t = Task(self.tw, description="test task")
491 dependency1 = Task(self.tw, description="needs to be done first")
492 dependency2 = Task(self.tw, description="needs to be done second")
498 t['depends'] = set([dependency1, dependency2])
500 self.assertEqual(t['depends'], set([dependency1, dependency2]))
502 def test_remove_from_dependency_set(self):
503 # Removes dependency from task with two dependencies
504 t = Task(self.tw, description="test task")
505 dependency1 = Task(self.tw, description="needs to be done first")
506 dependency2 = Task(self.tw, description="needs to be done second")
511 t['depends'] = set([dependency1, dependency2])
514 t['depends'].remove(dependency2)
517 self.assertEqual(t['depends'], set([dependency1]))
519 def test_add_to_dependency_set(self):
520 # Adds dependency to task with one dependencies
521 t = Task(self.tw, description="test task")
522 dependency1 = Task(self.tw, description="needs to be done first")
523 dependency2 = Task(self.tw, description="needs to be done second")
528 t['depends'] = set([dependency1])
531 t['depends'].add(dependency2)
534 self.assertEqual(t['depends'], set([dependency1, dependency2]))
536 def test_add_to_empty_dependency_set(self):
537 # Adds dependency to task with one dependencies
538 t = Task(self.tw, description="test task")
539 dependency = Task(self.tw, description="needs to be done first")
543 t['depends'].add(dependency)
546 self.assertEqual(t['depends'], set([dependency]))
548 def test_simple_dependency_set_save_repeatedly(self):
549 # Adds only one dependency to task with no dependencies
550 t = Task(self.tw, description="test task")
551 dependency = Task(self.tw, description="needs to be done first")
554 t['depends'] = set([dependency])
557 # We taint the task, but keep depends intact
558 t['description'] = "test task modified"
561 self.assertEqual(t['depends'], set([dependency]))
563 # We taint the task, but assign the same set to the depends
564 t['depends'] = set([dependency])
565 t['description'] = "test task modified again"
568 self.assertEqual(t['depends'], set([dependency]))
570 def test_compare_different_tasks(self):
571 # Negative: compare two different tasks
572 t1 = Task(self.tw, description="test task")
573 t2 = Task(self.tw, description="test task")
578 self.assertEqual(t1 == t2, False)
580 def test_compare_same_task_object(self):
581 # Compare Task object wit itself
582 t = Task(self.tw, description="test task")
585 self.assertEqual(t == t, True)
587 def test_compare_same_task(self):
588 # Compare the same task using two different objects
589 t1 = Task(self.tw, description="test task")
592 t2 = self.tw.tasks.get(uuid=t1['uuid'])
593 self.assertEqual(t1 == t2, True)
595 def test_compare_unsaved_tasks(self):
596 # t1 and t2 are unsaved tasks, considered to be unequal
597 # despite the content of data
598 t1 = Task(self.tw, description="test task")
599 t2 = Task(self.tw, description="test task")
601 self.assertEqual(t1 == t2, False)
603 def test_hash_unsaved_tasks(self):
604 # Considered equal, it's the same object
605 t1 = Task(self.tw, description="test task")
607 self.assertEqual(hash(t1) == hash(t2), True)
609 def test_hash_same_task(self):
610 # Compare the hash of the task using two different objects
611 t1 = Task(self.tw, description="test task")
614 t2 = self.tw.tasks.get(uuid=t1['uuid'])
615 self.assertEqual(t1.__hash__(), t2.__hash__())
617 def test_hash_unequal_unsaved_tasks(self):
618 # Compare the hash of the task using two different objects
619 t1 = Task(self.tw, description="test task 1")
620 t2 = Task(self.tw, description="test task 2")
622 self.assertNotEqual(t1.__hash__(), t2.__hash__())
624 def test_hash_unequal_saved_tasks(self):
625 # Compare the hash of the task using two different objects
626 t1 = Task(self.tw, description="test task 1")
627 t2 = Task(self.tw, description="test task 2")
632 self.assertNotEqual(t1.__hash__(), t2.__hash__())
634 def test_adding_task_with_priority(self):
635 t = Task(self.tw, description="test task", priority="M")
638 def test_removing_priority_with_none(self):
639 t = Task(self.tw, description="test task", priority="L")
642 # Remove the priority mark
646 # Assert that priority is not there after saving
647 self.assertEqual(t['priority'], None)
649 def test_adding_task_with_due_time(self):
650 t = Task(self.tw, description="test task", due=datetime.datetime.now())
653 def test_removing_due_time_with_none(self):
654 t = Task(self.tw, description="test task", due=datetime.datetime.now())
657 # Remove the due timestamp
661 # Assert that due timestamp is no longer there
662 self.assertEqual(t['due'], None)
664 def test_modified_fields_new_task(self):
667 # This should be empty with new task
668 self.assertEqual(set(t._modified_fields), set())
671 t['description'] = "test task"
672 self.assertEqual(set(t._modified_fields), set(['description']))
674 t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14) # <3
675 self.assertEqual(set(t._modified_fields), set(['description', 'due']))
677 t['project'] = "test project"
678 self.assertEqual(set(t._modified_fields),
679 set(['description', 'due', 'project']))
681 # List of modified fields should clear out when saved
683 self.assertEqual(set(t._modified_fields), set())
685 # Reassigning the fields with the same values now should not produce
687 t['description'] = "test task"
688 t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14) # <3
689 t['project'] = "test project"
690 self.assertEqual(set(t._modified_fields), set())
692 def test_modified_fields_loaded_task(self):
696 t['description'] = "test task"
697 t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14) # <3
698 t['project'] = "test project"
700 dependency = Task(self.tw, description="dependency")
702 t['depends'] = set([dependency])
704 # List of modified fields should clear out when saved
706 self.assertEqual(set(t._modified_fields), set())
708 # Get the task by using a filter by UUID
709 t2 = self.tw.tasks.get(uuid=t['uuid'])
711 # Reassigning the fields with the same values now should not produce
713 t['description'] = "test task"
714 t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14) # <3
715 t['project'] = "test project"
716 t['depends'] = set([dependency])
717 self.assertEqual(set(t._modified_fields), set())
719 def test_modified_fields_not_affected_by_reading(self):
722 for field in TASK_STANDARD_ATTRS:
725 self.assertEqual(set(t._modified_fields), set())
727 def test_setting_read_only_attrs_through_init(self):
728 # Test that we are unable to set readonly attrs through __init__
729 for readonly_key in Task.read_only_fields:
730 kwargs = {'description': 'test task', readonly_key: 'value'}
731 self.assertRaises(RuntimeError,
732 lambda: Task(self.tw, **kwargs))
734 def test_setting_read_only_attrs_through_setitem(self):
735 # Test that we are unable to set readonly attrs through __init__
736 for readonly_key in Task.read_only_fields:
737 t = Task(self.tw, description='test task')
738 self.assertRaises(RuntimeError,
739 lambda: t.__setitem__(readonly_key, 'value'))
741 def test_saving_unmodified_task(self):
742 t = Task(self.tw, description="test task")
746 def test_adding_tag_by_appending(self):
747 t = Task(self.tw, description="test task", tags=['test1'])
749 t['tags'].add('test2')
751 self.assertEqual(t['tags'], set(['test1', 'test2']))
753 def test_adding_tag_twice(self):
754 t = Task(self.tw, description="test task", tags=['test1'])
756 t['tags'].add('test2')
757 t['tags'].add('test2')
759 self.assertEqual(t['tags'], set(['test1', 'test2']))
761 def test_adding_tag_by_appending_empty(self):
762 t = Task(self.tw, description="test task")
764 t['tags'].add('test')
766 self.assertEqual(t['tags'], set(['test']))
768 def test_serializers_returning_empty_string_for_none(self):
769 # Test that any serializer returns '' when passed None
771 serializers = [getattr(t, serializer_name) for serializer_name in
772 filter(lambda x: x.startswith('serialize_'), dir(t))]
773 for serializer in serializers:
774 self.assertEqual(serializer(None), '')
776 def test_deserializer_returning_empty_value_for_empty_string(self):
777 # Test that any deserializer returns empty value when passed ''
779 deserializers = [getattr(t, deserializer_name) for deserializer_name in
780 filter(lambda x: x.startswith('deserialize_'), dir(t))]
781 for deserializer in deserializers:
782 self.assertTrue(deserializer('') in (None, [], set()))
784 def test_normalizers_handling_none(self):
785 # Test that any normalizer can handle None as a valid value
788 for key in TASK_STANDARD_ATTRS:
789 t._normalize(key, None)
791 def test_recurrent_task_generation(self):
792 today = datetime.date.today()
793 t = Task(self.tw, description="brush teeth",
794 due=today, recur="daily")
796 self.assertEqual(len(self.tw.tasks.pending()), 2)
798 def test_spawned_task_parent(self):
799 today = datetime.date.today()
800 t = Task(self.tw, description="brush teeth",
801 due=today, recur="daily")
804 spawned = self.tw.tasks.pending().get(due=today)
805 assert spawned['parent'] == t
807 def test_modify_number_of_tasks_at_once(self):
808 for i in range(1, 100):
809 Task(self.tw, description="test task %d" % i, tags=['test']).save()
811 self.tw.execute_command(['+test', 'mod', 'unified', 'description'])
813 def test_return_all_from_executed_command(self):
814 Task(self.tw, description="test task", tags=['test']).save()
815 out, err, rc = self.tw.execute_command(['count'], return_all=True)
816 self.assertEqual(rc, 0)
818 def test_return_all_from_failed_executed_command(self):
819 Task(self.tw, description="test task", tags=['test']).save()
820 out, err, rc = self.tw.execute_command(['countinvalid'],
821 return_all=True, allow_failure=False)
822 self.assertNotEqual(rc, 0)
825 class TaskFromHookTest(TasklibTest):
827 input_add_data = six.StringIO(
828 '{"description":"Buy some milk",'
829 '"entry":"20141118T050231Z",'
830 '"status":"pending",'
831 '"start":"20141119T152233Z",'
832 '"uuid":"a360fc44-315c-4366-b70c-ea7e7520b749"}')
834 input_add_data_recurring = six.StringIO(
835 '{"description":"Mow the lawn",'
836 '"entry":"20160210T224304Z",'
837 '"parent":"62da6227-519c-42c2-915d-dccada926ad7",'
839 '"status":"pending",'
840 '"uuid":"81305335-0237-49ff-8e87-b3cdc2369cec"}')
842 input_modify_data = six.StringIO(input_add_data.getvalue() + '\n' +
843 '{"description":"Buy some milk finally",'
844 '"entry":"20141118T050231Z",'
845 '"status":"completed",'
846 '"uuid":"a360fc44-315c-4366-b70c-ea7e7520b749"}')
848 exported_raw_data = (
850 '"due":"20150101T232323Z",'
851 '"description":"test task"}')
853 def test_setting_up_from_add_hook_input(self):
854 t = Task.from_input(input_file=self.input_add_data, backend=self.tw)
855 self.assertEqual(t['description'], "Buy some milk")
856 self.assertEqual(t.pending, True)
858 def test_setting_up_from_add_hook_input_recurring(self):
859 t = Task.from_input(input_file=self.input_add_data_recurring,
861 self.assertEqual(t['description'], "Mow the lawn")
862 self.assertEqual(t.pending, True)
864 def test_setting_up_from_modified_hook_input(self):
865 t = Task.from_input(input_file=self.input_modify_data, modify=True,
867 self.assertEqual(t['description'], "Buy some milk finally")
868 self.assertEqual(t.pending, False)
869 self.assertEqual(t.completed, True)
871 self.assertEqual(t._original_data['status'], "pending")
872 self.assertEqual(t._original_data['description'], "Buy some milk")
873 self.assertEqual(set(t._modified_fields),
874 set(['status', 'description', 'start']))
876 def test_export_data(self):
877 t = Task(self.tw, description="test task",
879 due=pytz.utc.localize(datetime.datetime(2015,1,1,23,23,23)))
881 # Check that the output is a permutation of:
882 # {"project":"Home","description":"test task","due":"20150101232323Z"}
883 allowed_segments = self.exported_raw_data[1:-1].split(',')
885 '{' + ','.join(segments) + '}'
886 for segments in itertools.permutations(allowed_segments)
889 self.assertTrue(any(t.export_data() == expected
890 for expected in allowed_output))
892 class TimezoneAwareDatetimeTest(TasklibTest):
895 super(TimezoneAwareDatetimeTest, self).setUp()
896 self.zone = local_zone
897 self.localdate_naive = datetime.datetime(2015,2,2)
898 self.localtime_naive = datetime.datetime(2015,2,2,0,0,0)
899 self.localtime_aware = self.zone.localize(self.localtime_naive)
900 self.utctime_aware = self.localtime_aware.astimezone(pytz.utc)
902 def test_timezone_naive_datetime_setitem(self):
903 t = Task(self.tw, description="test task")
904 t['due'] = self.localtime_naive
905 self.assertEqual(t['due'], self.localtime_aware)
907 def test_timezone_naive_datetime_using_init(self):
908 t = Task(self.tw, description="test task", due=self.localtime_naive)
909 self.assertEqual(t['due'], self.localtime_aware)
911 def test_filter_by_naive_datetime(self):
912 t = Task(self.tw, description="task1", due=self.localtime_naive)
914 matching_tasks = self.tw.tasks.filter(due=self.localtime_naive)
915 self.assertEqual(len(matching_tasks), 1)
917 def test_serialize_naive_datetime(self):
918 t = Task(self.tw, description="task1", due=self.localtime_naive)
919 self.assertEqual(json.loads(t.export_data())['due'],
920 self.utctime_aware.strftime(DATE_FORMAT))
922 def test_timezone_naive_date_setitem(self):
923 t = Task(self.tw, description="test task")
924 t['due'] = self.localdate_naive
925 self.assertEqual(t['due'], self.localtime_aware)
927 def test_timezone_naive_date_using_init(self):
928 t = Task(self.tw, description="test task", due=self.localdate_naive)
929 self.assertEqual(t['due'], self.localtime_aware)
931 def test_filter_by_naive_date(self):
932 t = Task(self.tw, description="task1", due=self.localdate_naive)
934 matching_tasks = self.tw.tasks.filter(due=self.localdate_naive)
935 self.assertEqual(len(matching_tasks), 1)
937 def test_serialize_naive_date(self):
938 t = Task(self.tw, description="task1", due=self.localdate_naive)
939 self.assertEqual(json.loads(t.export_data())['due'],
940 self.utctime_aware.strftime(DATE_FORMAT))
942 def test_timezone_aware_datetime_setitem(self):
943 t = Task(self.tw, description="test task")
944 t['due'] = self.localtime_aware
945 self.assertEqual(t['due'], self.localtime_aware)
947 def test_timezone_aware_datetime_using_init(self):
948 t = Task(self.tw, description="test task", due=self.localtime_aware)
949 self.assertEqual(t['due'], self.localtime_aware)
951 def test_filter_by_aware_datetime(self):
952 t = Task(self.tw, description="task1", due=self.localtime_aware)
954 matching_tasks = self.tw.tasks.filter(due=self.localtime_aware)
955 self.assertEqual(len(matching_tasks), 1)
957 def test_serialize_aware_datetime(self):
958 t = Task(self.tw, description="task1", due=self.localtime_aware)
959 self.assertEqual(json.loads(t.export_data())['due'],
960 self.utctime_aware.strftime(DATE_FORMAT))
962 class DatetimeStringTest(TasklibTest):
964 def test_simple_now_conversion(self):
965 if self.tw.version < six.text_type('2.4.0'):
966 # Python2.6 does not support SkipTest. As a workaround
967 # mark the test as passed by exiting.
968 if getattr(unittest, 'SkipTest', None) is not None:
969 raise unittest.SkipTest()
973 t = Task(self.tw, description="test task", due="now")
974 now = local_zone.localize(datetime.datetime.now())
976 # Assert that both times are not more than 5 seconds apart
977 if sys.version_info < (2,7):
978 self.assertTrue(total_seconds_2_6(now - t['due']) < 5)
979 self.assertTrue(total_seconds_2_6(t['due'] - now) < 5)
981 self.assertTrue((now - t['due']).total_seconds() < 5)
982 self.assertTrue((t['due'] - now).total_seconds() < 5)
984 def test_simple_eoy_conversion(self):
985 if self.tw.version < six.text_type('2.4.0'):
986 # Python2.6 does not support SkipTest. As a workaround
987 # mark the test as passed by exiting.
988 if getattr(unittest, 'SkipTest', None) is not None:
989 raise unittest.SkipTest()
993 t = Task(self.tw, description="test task", due="eoy")
994 now = local_zone.localize(datetime.datetime.now())
995 eoy = local_zone.localize(datetime.datetime(
1003 self.assertEqual(eoy, t['due'])
1005 def test_complex_eoy_conversion(self):
1006 if self.tw.version < six.text_type('2.4.0'):
1007 # Python2.6 does not support SkipTest. As a workaround
1008 # mark the test as passed by exiting.
1009 if getattr(unittest, 'SkipTest', None) is not None:
1010 raise unittest.SkipTest()
1014 t = Task(self.tw, description="test task", due="eoy - 4 months")
1015 now = local_zone.localize(datetime.datetime.now())
1016 due_date = local_zone.localize(datetime.datetime(
1023 )) - datetime.timedelta(0,4 * 30 * 86400)
1024 self.assertEqual(due_date, t['due'])
1026 def test_filtering_with_string_datetime(self):
1027 if self.tw.version < six.text_type('2.4.0'):
1028 # Python2.6 does not support SkipTest. As a workaround
1029 # mark the test as passed by exiting.
1030 if getattr(unittest, 'SkipTest', None) is not None:
1031 raise unittest.SkipTest()
1035 t = Task(self.tw, description="test task",
1036 due=datetime.datetime.now() - datetime.timedelta(0,2))
1038 self.assertEqual(len(self.tw.tasks.filter(due__before="now")), 1)
1040 class AnnotationTest(TasklibTest):
1043 super(AnnotationTest, self).setUp()
1044 Task(self.tw, description="test task").save()
1046 def test_adding_annotation(self):
1047 task = self.tw.tasks.get()
1048 task.add_annotation('test annotation')
1049 self.assertEqual(len(task['annotations']), 1)
1050 ann = task['annotations'][0]
1051 self.assertEqual(ann['description'], 'test annotation')
1053 def test_removing_annotation(self):
1054 task = self.tw.tasks.get()
1055 task.add_annotation('test annotation')
1056 ann = task['annotations'][0]
1058 self.assertEqual(len(task['annotations']), 0)
1060 def test_removing_annotation_by_description(self):
1061 task = self.tw.tasks.get()
1062 task.add_annotation('test annotation')
1063 task.remove_annotation('test annotation')
1064 self.assertEqual(len(task['annotations']), 0)
1066 def test_removing_annotation_by_obj(self):
1067 task = self.tw.tasks.get()
1068 task.add_annotation('test annotation')
1069 ann = task['annotations'][0]
1070 task.remove_annotation(ann)
1071 self.assertEqual(len(task['annotations']), 0)
1073 def test_annotation_after_modification(self):
1074 task = self.tw.tasks.get()
1075 task['project'] = 'test'
1076 task.add_annotation('I should really do this task')
1077 self.assertEqual(task['project'], 'test')
1079 self.assertEqual(task['project'], 'test')
1081 def test_serialize_annotations(self):
1082 # Test that serializing annotations is possible
1083 t = Task(self.tw, description="test")
1086 t.add_annotation("annotation1")
1087 t.add_annotation("annotation2")
1089 data = t._serialize('annotations', t._data['annotations'])
1091 self.assertEqual(len(data), 2)
1092 self.assertEqual(type(data[0]), dict)
1093 self.assertEqual(type(data[1]), dict)
1095 self.assertEqual(data[0]['description'], "annotation1")
1096 self.assertEqual(data[1]['description'], "annotation2")
1099 class UnicodeTest(TasklibTest):
1101 def test_unicode_task(self):
1102 Task(self.tw, description=six.u("†åßk")).save()
1105 def test_filter_by_unicode_task(self):
1106 Task(self.tw, description=six.u("†åßk")).save()
1107 tasks = self.tw.tasks.filter(description=six.u("†åßk"))
1108 self.assertEqual(len(tasks), 1)
1110 def test_non_unicode_task(self):
1111 Task(self.tw, description="test task").save()
1114 class ReadOnlyDictViewTest(unittest.TestCase):
1117 self.sample = dict(l=[1,2,3], d={'k':'v'})
1118 self.original_sample = copy.deepcopy(self.sample)
1119 self.view = ReadOnlyDictView(self.sample)
1121 def test_readonlydictview_getitem(self):
1123 self.assertEqual(l, self.sample['l'])
1125 # Assert that modification changed only copied value
1127 self.assertNotEqual(l, self.sample['l'])
1129 # Assert that viewed dict is not changed
1130 self.assertEqual(self.sample, self.original_sample)
1132 def test_readonlydictview_contains(self):
1133 self.assertEqual('l' in self.view, 'l' in self.sample)
1134 self.assertEqual('d' in self.view, 'd' in self.sample)
1135 self.assertEqual('k' in self.view, 'k' in self.sample)
1137 # Assert that viewed dict is not changed
1138 self.assertEqual(self.sample, self.original_sample)
1140 def test_readonlydictview_iter(self):
1141 self.assertEqual(list(k for k in self.view),
1142 list(k for k in self.sample))
1144 # Assert the view is correct after modification
1145 self.sample['new'] = 'value'
1146 self.assertEqual(list(k for k in self.view),
1147 list(k for k in self.sample))
1149 def test_readonlydictview_len(self):
1150 self.assertEqual(len(self.view), len(self.sample))
1152 # Assert the view is correct after modification
1153 self.sample['new'] = 'value'
1154 self.assertEqual(len(self.view), len(self.sample))
1156 def test_readonlydictview_get(self):
1157 l = self.view.get('l')
1158 self.assertEqual(l, self.sample.get('l'))
1160 # Assert that modification changed only copied value
1162 self.assertNotEqual(l, self.sample.get('l'))
1164 # Assert that viewed dict is not changed
1165 self.assertEqual(self.sample, self.original_sample)
1167 def test_readonlydict_items(self):
1168 view_items = self.view.items()
1169 sample_items = list(self.sample.items())
1170 self.assertEqual(view_items, sample_items)
1172 view_items.append('newkey')
1173 self.assertNotEqual(view_items, sample_items)
1174 self.assertEqual(self.sample, self.original_sample)
1176 def test_readonlydict_values(self):
1177 view_values = self.view.values()
1178 sample_values = list(self.sample.values())
1179 self.assertEqual(view_values, sample_values)
1181 view_list_item = list(filter(lambda x: type(x) is list,
1183 view_list_item.append(4)
1184 self.assertNotEqual(view_values, sample_values)
1185 self.assertEqual(self.sample, self.original_sample)
1188 class LazyUUIDTaskTest(TasklibTest):
1191 super(LazyUUIDTaskTest, self).setUp()
1193 self.stored = Task(self.tw, description="this is test task")
1196 self.lazy = LazyUUIDTask(self.tw, self.stored['uuid'])
1198 def test_uuid_non_conversion(self):
1199 assert self.stored['uuid'] == self.lazy['uuid']
1200 assert type(self.lazy) is LazyUUIDTask
1202 def test_lazy_explicit_conversion(self):
1203 assert type(self.lazy) is LazyUUIDTask
1205 assert type(self.lazy) is Task
1207 def test_conversion_key(self):
1208 assert self.stored['description'] == self.lazy['description']
1209 assert type(self.lazy) is Task
1211 def test_conversion_attribute(self):
1212 assert type(self.lazy) is LazyUUIDTask
1213 assert self.lazy.completed is False
1214 assert type(self.lazy) is Task
1216 def test_normal_to_lazy_equality(self):
1217 assert self.stored == self.lazy
1218 assert not self.stored != self.lazy
1219 assert type(self.lazy) is LazyUUIDTask
1221 def test_lazy_to_lazy_equality(self):
1222 lazy1 = LazyUUIDTask(self.tw, self.stored['uuid'])
1223 lazy2 = LazyUUIDTask(self.tw, self.stored['uuid'])
1225 assert lazy1 == lazy2
1226 assert not lazy1 != lazy2
1227 assert type(lazy1) is LazyUUIDTask
1228 assert type(lazy2) is LazyUUIDTask
1230 def test_normal_to_lazy_inequality(self):
1231 # Create a different UUID by changing the last letter
1232 wrong_uuid = self.stored['uuid']
1233 wrong_uuid = wrong_uuid[:-1] + ('a' if wrong_uuid[-1] != 'a' else 'b')
1235 wrong_lazy = LazyUUIDTask(self.tw, wrong_uuid)
1237 assert not self.stored == wrong_lazy
1238 assert self.stored != wrong_lazy
1239 assert type(wrong_lazy) is LazyUUIDTask
1241 def test_lazy_to_lazy_inequality(self):
1242 # Create a different UUID by changing the last letter
1243 wrong_uuid = self.stored['uuid']
1244 wrong_uuid = wrong_uuid[:-1] + ('a' if wrong_uuid[-1] != 'a' else 'b')
1246 lazy1 = LazyUUIDTask(self.tw, self.stored['uuid'])
1247 lazy2 = LazyUUIDTask(self.tw, wrong_uuid)
1249 assert not lazy1 == lazy2
1250 assert lazy1 != lazy2
1251 assert type(lazy1) is LazyUUIDTask
1252 assert type(lazy2) is LazyUUIDTask
1254 def test_lazy_in_queryset(self):
1255 tasks = self.tw.tasks.filter(uuid=self.stored['uuid'])
1257 assert self.lazy in tasks
1258 assert type(self.lazy) is LazyUUIDTask
1260 def test_lazy_saved(self):
1261 assert self.lazy.saved is True
1263 def test_lazy_modified(self):
1264 assert self.lazy.modified is False
1266 def test_lazy_modified_fields(self):
1267 assert self.lazy._modified_fields == set()
1270 class LazyUUIDTaskSetTest(TasklibTest):
1273 super(LazyUUIDTaskSetTest, self).setUp()
1275 self.task1 = Task(self.tw, description="task 1")
1276 self.task2 = Task(self.tw, description="task 2")
1277 self.task3 = Task(self.tw, description="task 3")
1289 self.lazy = LazyUUIDTaskSet(self.tw, self.uuids)
1291 def test_length(self):
1292 assert len(self.lazy) == 3
1293 assert type(self.lazy) is LazyUUIDTaskSet
1295 def test_contains(self):
1296 assert self.task1 in self.lazy
1297 assert self.task2 in self.lazy
1298 assert self.task3 in self.lazy
1299 assert type(self.lazy) is LazyUUIDTaskSet
1301 def test_eq_lazy(self):
1302 new_lazy = LazyUUIDTaskSet(self.tw, self.uuids)
1303 assert self.lazy == new_lazy
1304 assert not self.lazy != new_lazy
1305 assert type(self.lazy) is LazyUUIDTaskSet
1307 def test_eq_real(self):
1308 assert self.lazy == self.tw.tasks.all()
1309 assert self.tw.tasks.all() == self.lazy
1310 assert not self.lazy != self.tw.tasks.all()
1312 assert type(self.lazy) is LazyUUIDTaskSet
1314 def test_union(self):
1315 taskset = set([self.task1])
1316 lazyset = LazyUUIDTaskSet(
1318 (self.task2['uuid'], self.task3['uuid'])
1321 assert taskset | lazyset == self.lazy
1322 assert lazyset | taskset == self.lazy
1323 assert taskset.union(lazyset) == self.lazy
1324 assert lazyset.union(taskset) == self.lazy
1327 assert lazyset == self.lazy
1329 def test_difference(self):
1330 taskset = set([self.task1, self.task2])
1331 lazyset = LazyUUIDTaskSet(
1333 (self.task2['uuid'], self.task3['uuid'])
1336 assert taskset - lazyset == set([self.task1])
1337 assert lazyset - taskset == set([self.task3])
1338 assert taskset.difference(lazyset) == set([self.task1])
1339 assert lazyset.difference(taskset) == set([self.task3])
1342 assert lazyset == set([self.task3])
1344 def test_symmetric_difference(self):
1345 taskset = set([self.task1, self.task2])
1346 lazyset = LazyUUIDTaskSet(
1348 (self.task2['uuid'], self.task3['uuid'])
1351 assert taskset ^ lazyset == set([self.task1, self.task3])
1352 assert lazyset ^ taskset == set([self.task1, self.task3])
1353 assert taskset.symmetric_difference(lazyset) == set([self.task1, self.task3])
1354 assert lazyset.symmetric_difference(taskset) == set([self.task1, self.task3])
1357 assert lazyset == set([self.task1, self.task3])
1359 def test_intersection(self):
1360 taskset = set([self.task1, self.task2])
1361 lazyset = LazyUUIDTaskSet(
1363 (self.task2['uuid'], self.task3['uuid'])
1366 assert taskset & lazyset == set([self.task2])
1367 assert lazyset & taskset == set([self.task2])
1368 assert taskset.intersection(lazyset) == set([self.task2])
1369 assert lazyset.intersection(taskset) == set([self.task2])
1372 assert lazyset == set([self.task2])
1375 class TaskWarriorBackendTest(TasklibTest):
1377 def test_config(self):
1378 assert self.tw.config['nag'] == "You have more urgent tasks."
1379 assert self.tw.config['default.command'] == "next"
1380 assert self.tw.config['dependency.indicator'] == "D"