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
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 = (
44 def total_seconds_2_6(x):
45 return x.microseconds / 1e6 + x.seconds + x.days * 24 * 3600
48 class TasklibTest(unittest.TestCase):
51 self.tmp = tempfile.mkdtemp(dir='.')
52 self.tw = TaskWarrior(data_location=self.tmp, taskrc_location='/')
55 shutil.rmtree(self.tmp)
58 class TaskFilterTest(TasklibTest):
60 def test_all_empty(self):
61 self.assertEqual(len(self.tw.tasks.all()), 0)
63 def test_all_non_empty(self):
64 Task(self.tw, description="test task").save()
65 self.assertEqual(len(self.tw.tasks.all()), 1)
66 self.assertEqual(self.tw.tasks.all()[0]['description'], 'test task')
67 self.assertEqual(self.tw.tasks.all()[0]['status'], 'pending')
69 def test_pending_non_empty(self):
70 Task(self.tw, description="test task").save()
71 self.assertEqual(len(self.tw.tasks.pending()), 1)
72 self.assertEqual(self.tw.tasks.pending()[0]['description'],
74 self.assertEqual(self.tw.tasks.pending()[0]['status'], 'pending')
76 def test_completed_empty(self):
77 Task(self.tw, description="test task").save()
78 self.assertEqual(len(self.tw.tasks.completed()), 0)
80 def test_completed_non_empty(self):
81 Task(self.tw, description="test task").save()
82 self.assertEqual(len(self.tw.tasks.completed()), 0)
83 self.tw.tasks.all()[0].done()
84 self.assertEqual(len(self.tw.tasks.completed()), 1)
86 def test_deleted_empty(self):
87 Task(self.tw, description="test task").save()
88 self.assertEqual(len(self.tw.tasks.deleted()), 0)
90 def test_deleted_non_empty(self):
91 Task(self.tw, description="test task").save()
92 self.assertEqual(len(self.tw.tasks.deleted()), 0)
93 self.tw.tasks.all()[0].delete()
94 self.assertEqual(len(self.tw.tasks.deleted()), 1)
96 def test_waiting_empty(self):
97 Task(self.tw, description="test task").save()
98 self.assertEqual(len(self.tw.tasks.waiting()), 0)
100 def test_waiting_non_empty(self):
101 Task(self.tw, description="test task").save()
102 self.assertEqual(len(self.tw.tasks.waiting()), 0)
104 t = self.tw.tasks.all()[0]
105 t['wait'] = datetime.datetime.now() + datetime.timedelta(days=1)
108 self.assertEqual(len(self.tw.tasks.waiting()), 1)
110 def test_recurring_empty(self):
111 Task(self.tw, description="test task").save()
112 self.assertEqual(len(self.tw.tasks.recurring()), 0)
114 def test_recurring_non_empty(self):
115 Task(self.tw, description="test task", recur="daily",
116 due=datetime.datetime.now()).save()
117 self.assertEqual(len(self.tw.tasks.recurring()), 1)
119 def test_filtering_by_attribute(self):
120 Task(self.tw, description="no priority task").save()
121 Task(self.tw, priority="H", description="high priority task").save()
122 self.assertEqual(len(self.tw.tasks.all()), 2)
124 # Assert that the correct number of tasks is returned
125 self.assertEqual(len(self.tw.tasks.filter(priority="H")), 1)
127 # Assert that the correct tasks are returned
128 high_priority_task = self.tw.tasks.get(priority="H")
129 self.assertEqual(high_priority_task['description'],
130 "high priority task")
132 def test_filtering_by_empty_attribute(self):
133 Task(self.tw, description="no priority task").save()
134 Task(self.tw, priority="H", description="high priority task").save()
135 self.assertEqual(len(self.tw.tasks.all()), 2)
137 # Assert that the correct number of tasks is returned
138 self.assertEqual(len(self.tw.tasks.filter(priority=None)), 1)
140 # Assert that the correct tasks are returned
141 no_priority_task = self.tw.tasks.get(priority=None)
142 self.assertEqual(no_priority_task['description'], "no priority task")
144 def test_filter_for_task_with_space_in_descripition(self):
145 task = Task(self.tw, description="test task")
148 filtered_task = self.tw.tasks.get(description="test task")
149 self.assertEqual(filtered_task['description'], "test task")
151 def test_filter_for_task_without_space_in_descripition(self):
152 task = Task(self.tw, description="test")
155 filtered_task = self.tw.tasks.get(description="test")
156 self.assertEqual(filtered_task['description'], "test")
158 def test_filter_for_task_with_space_in_project(self):
159 task = Task(self.tw, description="test", project="random project")
162 filtered_task = self.tw.tasks.get(project="random project")
163 self.assertEqual(filtered_task['project'], "random project")
165 def test_filter_for_task_without_space_in_project(self):
166 task = Task(self.tw, description="test", project="random")
169 filtered_task = self.tw.tasks.get(project="random")
170 self.assertEqual(filtered_task['project'], "random")
172 def test_filter_with_empty_uuid(self):
173 self.assertRaises(ValueError, lambda: self.tw.tasks.get(uuid=''))
175 def test_filter_dummy_by_status(self):
176 t = Task(self.tw, description="test")
179 tasks = self.tw.tasks.filter(status=t['status'])
180 self.assertEqual(list(tasks), [t])
182 def test_filter_dummy_by_uuid(self):
183 t = Task(self.tw, description="test")
186 tasks = self.tw.tasks.filter(uuid=t['uuid'])
187 self.assertEqual(list(tasks), [t])
189 def test_filter_dummy_by_entry(self):
190 t = Task(self.tw, description="test")
193 tasks = self.tw.tasks.filter(entry=t['entry'])
194 self.assertEqual(list(tasks), [t])
196 def test_filter_dummy_by_description(self):
197 t = Task(self.tw, description="test")
200 tasks = self.tw.tasks.filter(description=t['description'])
201 self.assertEqual(list(tasks), [t])
203 def test_filter_dummy_by_start(self):
204 t = Task(self.tw, description="test")
208 tasks = self.tw.tasks.filter(start=t['start'])
209 self.assertEqual(list(tasks), [t])
211 def test_filter_dummy_by_end(self):
212 t = Task(self.tw, description="test")
216 tasks = self.tw.tasks.filter(end=t['end'])
217 self.assertEqual(list(tasks), [t])
219 def test_filter_dummy_by_due(self):
220 t = Task(self.tw, description="test", due=datetime.datetime.now())
223 tasks = self.tw.tasks.filter(due=t['due'])
224 self.assertEqual(list(tasks), [t])
226 def test_filter_dummy_by_until(self):
227 t = Task(self.tw, description="test")
230 tasks = self.tw.tasks.filter(until=t['until'])
231 self.assertEqual(list(tasks), [t])
233 def test_filter_dummy_by_modified(self):
234 # Older TW version does not support bumping modified
236 if self.tw.version < six.text_type('2.2.0'):
237 # Python2.6 does not support SkipTest. As a workaround
238 # mark the test as passed by exiting.
239 if getattr(unittest, 'SkipTest', None) is not None:
240 raise unittest.SkipTest()
244 t = Task(self.tw, description="test")
247 tasks = self.tw.tasks.filter(modified=t['modified'])
248 self.assertEqual(list(tasks), [t])
250 def test_filter_dummy_by_scheduled(self):
251 t = Task(self.tw, description="test")
254 tasks = self.tw.tasks.filter(scheduled=t['scheduled'])
255 self.assertEqual(list(tasks), [t])
257 def test_filter_dummy_by_tags(self):
258 t = Task(self.tw, description="test", tags=["home"])
261 tasks = self.tw.tasks.filter(tags=t['tags'])
262 self.assertEqual(list(tasks), [t])
264 def test_filter_dummy_by_projects(self):
265 t = Task(self.tw, description="test", project="random")
268 tasks = self.tw.tasks.filter(project=t['project'])
269 self.assertEqual(list(tasks), [t])
271 def test_filter_by_priority(self):
272 t = Task(self.tw, description="test", priority="H")
275 tasks = self.tw.tasks.filter(priority=t['priority'])
276 self.assertEqual(list(tasks), [t])
279 class TaskTest(TasklibTest):
281 def test_create_unsaved_task(self):
282 # Make sure a new task is not saved unless explicitly called for
283 Task(self.tw, description="test task")
284 self.assertEqual(len(self.tw.tasks.all()), 0)
286 # TODO: once python 2.6 compatiblity is over, use context managers here
287 # and in all subsequent tests for assertRaises
289 def test_delete_unsaved_task(self):
290 t = Task(self.tw, description="test task")
291 self.assertRaises(Task.NotSaved, t.delete)
293 def test_complete_unsaved_task(self):
294 t = Task(self.tw, description="test task")
295 self.assertRaises(Task.NotSaved, t.done)
297 def test_refresh_unsaved_task(self):
298 t = Task(self.tw, description="test task")
299 self.assertRaises(Task.NotSaved, t.refresh)
301 def test_start_unsaved_task(self):
302 t = Task(self.tw, description="test task")
303 self.assertRaises(Task.NotSaved, t.start)
305 def test_delete_deleted_task(self):
306 t = Task(self.tw, description="test task")
310 self.assertRaises(Task.DeletedTask, t.delete)
312 def test_complete_completed_task(self):
313 t = Task(self.tw, description="test task")
317 self.assertRaises(Task.CompletedTask, t.done)
319 def test_start_completed_task(self):
320 t = Task(self.tw, description="test task")
324 self.assertRaises(Task.CompletedTask, t.start)
326 def test_add_completed_task(self):
327 t = Task(self.tw, description="test", status="completed",
328 end=datetime.datetime.now())
331 def test_add_multiple_completed_tasks(self):
332 t1 = Task(self.tw, description="test1", status="completed",
333 end=datetime.datetime.now())
334 t2 = Task(self.tw, description="test2", status="completed",
335 end=datetime.datetime.now())
339 def test_complete_deleted_task(self):
340 t = Task(self.tw, description="test task")
344 self.assertRaises(Task.DeletedTask, t.done)
346 def test_starting_task(self):
347 t = Task(self.tw, description="test task")
348 now = t.datetime_normalizer(datetime.datetime.now())
352 self.assertTrue(now.replace(microsecond=0) <= t['start'])
353 self.assertEqual(t['status'], 'pending')
355 def test_completing_task(self):
356 t = Task(self.tw, description="test task")
357 now = t.datetime_normalizer(datetime.datetime.now())
361 self.assertTrue(now.replace(microsecond=0) <= t['end'])
362 self.assertEqual(t['status'], 'completed')
364 def test_deleting_task(self):
365 t = Task(self.tw, description="test task")
366 now = t.datetime_normalizer(datetime.datetime.now())
370 self.assertTrue(now.replace(microsecond=0) <= t['end'])
371 self.assertEqual(t['status'], 'deleted')
373 def test_started_task_active(self):
374 t = Task(self.tw, description="test task")
377 self.assertTrue(t.active)
379 def test_unstarted_task_inactive(self):
380 t = Task(self.tw, description="test task")
381 self.assertFalse(t.active)
383 self.assertFalse(t.active)
385 def test_start_active_task(self):
386 t = Task(self.tw, description="test task")
389 self.assertRaises(Task.ActiveTask, t.start)
391 def test_stop_completed_task(self):
392 t = Task(self.tw, description="test task")
397 self.assertRaises(Task.InactiveTask, t.stop)
399 t = Task(self.tw, description="test task")
403 self.assertRaises(Task.InactiveTask, t.stop)
405 def test_stop_deleted_task(self):
406 t = Task(self.tw, description="test task")
412 def test_stop_inactive_task(self):
413 t = Task(self.tw, description="test task")
416 self.assertRaises(Task.InactiveTask, t.stop)
418 t = Task(self.tw, description="test task")
423 self.assertRaises(Task.InactiveTask, t.stop)
425 def test_stopping_task(self):
426 t = Task(self.tw, description="test task")
427 t.datetime_normalizer(datetime.datetime.now())
432 self.assertEqual(t['end'], None)
433 self.assertEqual(t['status'], 'pending')
434 self.assertFalse(t.active)
436 def test_modify_simple_attribute_without_space(self):
437 t = Task(self.tw, description="test")
440 self.assertEquals(t['description'], "test")
442 t['description'] = "test-modified"
445 self.assertEquals(t['description'], "test-modified")
447 def test_modify_simple_attribute_with_space(self):
448 # Space can pose problems with parsing
449 t = Task(self.tw, description="test task")
452 self.assertEquals(t['description'], "test task")
454 t['description'] = "test task modified"
457 self.assertEquals(t['description'], "test task modified")
459 def test_empty_dependency_set_of_unsaved_task(self):
460 t = Task(self.tw, description="test task")
461 self.assertEqual(t['depends'], set())
463 def test_empty_dependency_set_of_saved_task(self):
464 t = Task(self.tw, description="test task")
466 self.assertEqual(t['depends'], set())
468 def test_set_unsaved_task_as_dependency(self):
469 # Adds only one dependency to task with no dependencies
470 t = Task(self.tw, description="test task")
471 dependency = Task(self.tw, description="needs to be done first")
473 # We only save the parent task, dependency task is unsaved
475 t['depends'] = set([dependency])
477 self.assertRaises(Task.NotSaved, t.save)
479 def test_set_simple_dependency_set(self):
480 # Adds only one dependency to task with no dependencies
481 t = Task(self.tw, description="test task")
482 dependency = Task(self.tw, description="needs to be done first")
487 t['depends'] = set([dependency])
489 self.assertEqual(t['depends'], set([dependency]))
491 def test_set_complex_dependency_set(self):
492 # Adds two dependencies to task with no dependencies
493 t = Task(self.tw, description="test task")
494 dependency1 = Task(self.tw, description="needs to be done first")
495 dependency2 = Task(self.tw, description="needs to be done second")
501 t['depends'] = set([dependency1, dependency2])
503 self.assertEqual(t['depends'], set([dependency1, dependency2]))
505 def test_remove_from_dependency_set(self):
506 # Removes dependency from task with two dependencies
507 t = Task(self.tw, description="test task")
508 dependency1 = Task(self.tw, description="needs to be done first")
509 dependency2 = Task(self.tw, description="needs to be done second")
514 t['depends'] = set([dependency1, dependency2])
517 t['depends'].remove(dependency2)
520 self.assertEqual(t['depends'], set([dependency1]))
522 def test_add_to_dependency_set(self):
523 # Adds dependency to task with one dependencies
524 t = Task(self.tw, description="test task")
525 dependency1 = Task(self.tw, description="needs to be done first")
526 dependency2 = Task(self.tw, description="needs to be done second")
531 t['depends'] = set([dependency1])
534 t['depends'].add(dependency2)
537 self.assertEqual(t['depends'], set([dependency1, dependency2]))
539 def test_add_to_empty_dependency_set(self):
540 # Adds dependency to task with one dependencies
541 t = Task(self.tw, description="test task")
542 dependency = Task(self.tw, description="needs to be done first")
546 t['depends'].add(dependency)
549 self.assertEqual(t['depends'], set([dependency]))
551 def test_simple_dependency_set_save_repeatedly(self):
552 # Adds only one dependency to task with no dependencies
553 t = Task(self.tw, description="test task")
554 dependency = Task(self.tw, description="needs to be done first")
557 t['depends'] = set([dependency])
560 # We taint the task, but keep depends intact
561 t['description'] = "test task modified"
564 self.assertEqual(t['depends'], set([dependency]))
566 # We taint the task, but assign the same set to the depends
567 t['depends'] = set([dependency])
568 t['description'] = "test task modified again"
571 self.assertEqual(t['depends'], set([dependency]))
573 def test_compare_different_tasks(self):
574 # Negative: compare two different tasks
575 t1 = Task(self.tw, description="test task")
576 t2 = Task(self.tw, description="test task")
581 self.assertEqual(t1 == t2, False)
583 def test_compare_same_task_object(self):
584 # Compare Task object wit itself
585 t = Task(self.tw, description="test task")
588 self.assertEqual(t == t, True)
590 def test_compare_same_task(self):
591 # Compare the same task using two different objects
592 t1 = Task(self.tw, description="test task")
595 t2 = self.tw.tasks.get(uuid=t1['uuid'])
596 self.assertEqual(t1 == t2, True)
598 def test_compare_unsaved_tasks(self):
599 # t1 and t2 are unsaved tasks, considered to be unequal
600 # despite the content of data
601 t1 = Task(self.tw, description="test task")
602 t2 = Task(self.tw, description="test task")
604 self.assertEqual(t1 == t2, False)
606 def test_hash_unsaved_tasks(self):
607 # Considered equal, it's the same object
608 t1 = Task(self.tw, description="test task")
610 self.assertEqual(hash(t1) == hash(t2), True)
612 def test_hash_same_task(self):
613 # Compare the hash of the task using two different objects
614 t1 = Task(self.tw, description="test task")
617 t2 = self.tw.tasks.get(uuid=t1['uuid'])
618 self.assertEqual(t1.__hash__(), t2.__hash__())
620 def test_hash_unequal_unsaved_tasks(self):
621 # Compare the hash of the task using two different objects
622 t1 = Task(self.tw, description="test task 1")
623 t2 = Task(self.tw, description="test task 2")
625 self.assertNotEqual(t1.__hash__(), t2.__hash__())
627 def test_hash_unequal_saved_tasks(self):
628 # Compare the hash of the task using two different objects
629 t1 = Task(self.tw, description="test task 1")
630 t2 = Task(self.tw, description="test task 2")
635 self.assertNotEqual(t1.__hash__(), t2.__hash__())
637 def test_adding_task_with_priority(self):
638 t = Task(self.tw, description="test task", priority="M")
641 def test_removing_priority_with_none(self):
642 t = Task(self.tw, description="test task", priority="L")
645 # Remove the priority mark
649 # Assert that priority is not there after saving
650 self.assertEqual(t['priority'], None)
652 def test_adding_task_with_due_time(self):
653 t = Task(self.tw, description="test task", due=datetime.datetime.now())
656 def test_removing_due_time_with_none(self):
657 t = Task(self.tw, description="test task", due=datetime.datetime.now())
660 # Remove the due timestamp
664 # Assert that due timestamp is no longer there
665 self.assertEqual(t['due'], None)
667 def test_modified_fields_new_task(self):
670 # This should be empty with new task
671 self.assertEqual(set(t._modified_fields), set())
674 t['description'] = "test task"
675 self.assertEqual(set(t._modified_fields), set(['description']))
677 t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14) # <3
678 self.assertEqual(set(t._modified_fields), set(['description', 'due']))
680 t['project'] = "test project"
681 self.assertEqual(set(t._modified_fields),
682 set(['description', 'due', 'project']))
684 # List of modified fields should clear out when saved
686 self.assertEqual(set(t._modified_fields), set())
688 # Reassigning the fields with the same values now should not produce
690 t['description'] = "test task"
691 t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14) # <3
692 t['project'] = "test project"
693 self.assertEqual(set(t._modified_fields), set())
695 def test_modified_fields_loaded_task(self):
699 t['description'] = "test task"
700 t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14) # <3
701 t['project'] = "test project"
703 dependency = Task(self.tw, description="dependency")
705 t['depends'] = set([dependency])
707 # List of modified fields should clear out when saved
709 self.assertEqual(set(t._modified_fields), set())
711 # Get the task by using a filter by UUID
712 self.tw.tasks.get(uuid=t['uuid'])
714 # Reassigning the fields with the same values now should not produce
716 t['description'] = "test task"
717 t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14) # <3
718 t['project'] = "test project"
719 t['depends'] = set([dependency])
720 self.assertEqual(set(t._modified_fields), set())
722 def test_modified_fields_not_affected_by_reading(self):
725 for field in TASK_STANDARD_ATTRS:
728 self.assertEqual(set(t._modified_fields), set())
730 def test_setting_read_only_attrs_through_init(self):
731 # Test that we are unable to set readonly attrs through __init__
732 for readonly_key in Task.read_only_fields:
733 kwargs = {'description': 'test task', readonly_key: 'value'}
734 self.assertRaises(RuntimeError,
735 lambda: Task(self.tw, **kwargs))
737 def test_setting_read_only_attrs_through_setitem(self):
738 # Test that we are unable to set readonly attrs through __init__
739 for readonly_key in Task.read_only_fields:
740 t = Task(self.tw, description='test task')
741 self.assertRaises(RuntimeError,
742 lambda: t.__setitem__(readonly_key, 'value'))
744 def test_saving_unmodified_task(self):
745 t = Task(self.tw, description="test task")
749 def test_adding_tag_by_appending(self):
750 t = Task(self.tw, description="test task", tags=['test1'])
752 t['tags'].add('test2')
754 self.assertEqual(t['tags'], set(['test1', 'test2']))
756 def test_adding_tag_twice(self):
757 t = Task(self.tw, description="test task", tags=['test1'])
759 t['tags'].add('test2')
760 t['tags'].add('test2')
762 self.assertEqual(t['tags'], set(['test1', 'test2']))
764 def test_adding_tag_by_appending_empty(self):
765 t = Task(self.tw, description="test task")
767 t['tags'].add('test')
769 self.assertEqual(t['tags'], set(['test']))
771 def test_serializers_returning_empty_string_for_none(self):
772 # Test that any serializer returns '' when passed None
774 serializers = [getattr(t, serializer_name) for serializer_name in
775 filter(lambda x: x.startswith('serialize_'), dir(t))]
776 for serializer in serializers:
777 self.assertEqual(serializer(None), '')
779 def test_deserializer_returning_empty_value_for_empty_string(self):
780 # Test that any deserializer returns empty value when passed ''
782 deserializers = [getattr(t, deserializer_name) for deserializer_name in
783 filter(lambda x: x.startswith('deserialize_'), dir(t))]
784 for deserializer in deserializers:
785 self.assertTrue(deserializer('') in (None, [], set()))
787 def test_normalizers_handling_none(self):
788 # Test that any normalizer can handle None as a valid value
791 for key in TASK_STANDARD_ATTRS:
792 t._normalize(key, None)
794 def test_recurrent_task_generation(self):
795 today = datetime.date.today()
796 t = Task(self.tw, description="brush teeth",
797 due=today, recur="daily")
799 self.assertEqual(len(self.tw.tasks.pending()), 2)
801 def test_spawned_task_parent(self):
802 today = datetime.date.today()
803 t = Task(self.tw, description="brush teeth",
804 due=today, recur="daily")
807 spawned = self.tw.tasks.pending().get(due=today)
808 assert spawned['parent'] == t
810 def test_modify_number_of_tasks_at_once(self):
811 for i in range(1, 100):
812 Task(self.tw, description="test task %d" % i, tags=['test']).save()
814 self.tw.execute_command(['+test', 'mod', 'unified', 'description'])
816 def test_return_all_from_executed_command(self):
817 Task(self.tw, description="test task", tags=['test']).save()
818 out, err, rc = self.tw.execute_command(['count'], return_all=True)
819 self.assertEqual(rc, 0)
821 def test_return_all_from_failed_executed_command(self):
822 Task(self.tw, description="test task", tags=['test']).save()
823 out, err, rc = self.tw.execute_command(['countinvalid'],
826 self.assertNotEqual(rc, 0)
829 class TaskFromHookTest(TasklibTest):
831 input_add_data = six.StringIO(
832 '{"description":"Buy some milk",'
833 '"entry":"20141118T050231Z",'
834 '"status":"pending",'
835 '"start":"20141119T152233Z",'
836 '"uuid":"a360fc44-315c-4366-b70c-ea7e7520b749"}')
838 input_add_data_recurring = six.StringIO(
839 '{"description":"Mow the lawn",'
840 '"entry":"20160210T224304Z",'
841 '"parent":"62da6227-519c-42c2-915d-dccada926ad7",'
843 '"status":"pending",'
844 '"uuid":"81305335-0237-49ff-8e87-b3cdc2369cec"}')
846 input_modify_data = six.StringIO(
847 input_add_data.getvalue() + '\n' +
848 '{"description":"Buy some milk finally",'
849 '"entry":"20141118T050231Z",'
850 '"status":"completed",'
851 '"uuid":"a360fc44-315c-4366-b70c-ea7e7520b749"}')
853 exported_raw_data = (
855 '"due":"20150101T232323Z",'
856 '"description":"test task"}')
858 def test_setting_up_from_add_hook_input(self):
859 t = Task.from_input(input_file=self.input_add_data, backend=self.tw)
860 self.assertEqual(t['description'], "Buy some milk")
861 self.assertEqual(t.pending, True)
863 def test_setting_up_from_add_hook_input_recurring(self):
864 t = Task.from_input(input_file=self.input_add_data_recurring,
866 self.assertEqual(t['description'], "Mow the lawn")
867 self.assertEqual(t.pending, True)
869 def test_setting_up_from_modified_hook_input(self):
870 t = Task.from_input(input_file=self.input_modify_data, modify=True,
872 self.assertEqual(t['description'], "Buy some milk finally")
873 self.assertEqual(t.pending, False)
874 self.assertEqual(t.completed, True)
876 self.assertEqual(t._original_data['status'], "pending")
877 self.assertEqual(t._original_data['description'], "Buy some milk")
878 self.assertEqual(set(t._modified_fields),
879 set(['status', 'description', 'start']))
881 def test_export_data(self):
882 t = Task(self.tw, description="test task",
884 due=pytz.utc.localize(datetime.datetime(2015, 1, 1,
887 # Check that the output is a permutation of:
888 # {"project":"Home","description":"test task","due":"20150101232323Z"}
889 allowed_segments = self.exported_raw_data[1:-1].split(',')
891 '{' + ','.join(segments) + '}'
892 for segments in itertools.permutations(allowed_segments)
895 self.assertTrue(any(t.export_data() == expected
896 for expected in allowed_output))
899 class TimezoneAwareDatetimeTest(TasklibTest):
902 super(TimezoneAwareDatetimeTest, self).setUp()
903 self.zone = local_zone
904 self.localdate_naive = datetime.datetime(2015, 2, 2)
905 self.localtime_naive = datetime.datetime(2015, 2, 2, 0, 0, 0)
906 self.localtime_aware = self.zone.localize(self.localtime_naive)
907 self.utctime_aware = self.localtime_aware.astimezone(pytz.utc)
909 def test_timezone_naive_datetime_setitem(self):
910 t = Task(self.tw, description="test task")
911 t['due'] = self.localtime_naive
912 self.assertEqual(t['due'], self.localtime_aware)
914 def test_timezone_naive_datetime_using_init(self):
915 t = Task(self.tw, description="test task", due=self.localtime_naive)
916 self.assertEqual(t['due'], self.localtime_aware)
918 def test_filter_by_naive_datetime(self):
919 t = Task(self.tw, description="task1", due=self.localtime_naive)
921 matching_tasks = self.tw.tasks.filter(due=self.localtime_naive)
922 self.assertEqual(len(matching_tasks), 1)
924 def test_serialize_naive_datetime(self):
925 t = Task(self.tw, description="task1", due=self.localtime_naive)
926 self.assertEqual(json.loads(t.export_data())['due'],
927 self.utctime_aware.strftime(DATE_FORMAT))
929 def test_timezone_naive_date_setitem(self):
930 t = Task(self.tw, description="test task")
931 t['due'] = self.localdate_naive
932 self.assertEqual(t['due'], self.localtime_aware)
934 def test_timezone_naive_date_using_init(self):
935 t = Task(self.tw, description="test task", due=self.localdate_naive)
936 self.assertEqual(t['due'], self.localtime_aware)
938 def test_filter_by_naive_date(self):
939 t = Task(self.tw, description="task1", due=self.localdate_naive)
941 matching_tasks = self.tw.tasks.filter(due=self.localdate_naive)
942 self.assertEqual(len(matching_tasks), 1)
944 def test_serialize_naive_date(self):
945 t = Task(self.tw, description="task1", due=self.localdate_naive)
946 self.assertEqual(json.loads(t.export_data())['due'],
947 self.utctime_aware.strftime(DATE_FORMAT))
949 def test_timezone_aware_datetime_setitem(self):
950 t = Task(self.tw, description="test task")
951 t['due'] = self.localtime_aware
952 self.assertEqual(t['due'], self.localtime_aware)
954 def test_timezone_aware_datetime_using_init(self):
955 t = Task(self.tw, description="test task", due=self.localtime_aware)
956 self.assertEqual(t['due'], self.localtime_aware)
958 def test_filter_by_aware_datetime(self):
959 t = Task(self.tw, description="task1", due=self.localtime_aware)
961 matching_tasks = self.tw.tasks.filter(due=self.localtime_aware)
962 self.assertEqual(len(matching_tasks), 1)
964 def test_serialize_aware_datetime(self):
965 t = Task(self.tw, description="task1", due=self.localtime_aware)
966 self.assertEqual(json.loads(t.export_data())['due'],
967 self.utctime_aware.strftime(DATE_FORMAT))
970 class DatetimeStringTest(TasklibTest):
972 def test_simple_now_conversion(self):
973 if self.tw.version < six.text_type('2.4.0'):
974 # Python2.6 does not support SkipTest. As a workaround
975 # mark the test as passed by exiting.
976 if getattr(unittest, 'SkipTest', None) is not None:
977 raise unittest.SkipTest()
981 t = Task(self.tw, description="test task", due="now")
982 now = local_zone.localize(datetime.datetime.now())
984 # Assert that both times are not more than 5 seconds apart
985 if sys.version_info < (2, 7):
986 self.assertTrue(total_seconds_2_6(now - t['due']) < 5)
987 self.assertTrue(total_seconds_2_6(t['due'] - now) < 5)
989 self.assertTrue((now - t['due']).total_seconds() < 5)
990 self.assertTrue((t['due'] - now).total_seconds() < 5)
992 def test_simple_eoy_conversion(self):
993 if self.tw.version < six.text_type('2.4.0'):
994 # Python2.6 does not support SkipTest. As a workaround
995 # mark the test as passed by exiting.
996 if getattr(unittest, 'SkipTest', None) is not None:
997 raise unittest.SkipTest()
1001 t = Task(self.tw, description="test task", due="eoy")
1002 now = local_zone.localize(datetime.datetime.now())
1003 eoy = local_zone.localize(datetime.datetime(
1011 self.assertEqual(eoy, t['due'])
1013 def test_complex_eoy_conversion(self):
1014 if self.tw.version < six.text_type('2.4.0'):
1015 # Python2.6 does not support SkipTest. As a workaround
1016 # mark the test as passed by exiting.
1017 if getattr(unittest, 'SkipTest', None) is not None:
1018 raise unittest.SkipTest()
1022 t = Task(self.tw, description="test task", due="eoy - 4 months")
1023 now = local_zone.localize(datetime.datetime.now())
1024 due_date = local_zone.localize(datetime.datetime(
1031 )) - datetime.timedelta(0, 4 * 30 * 86400)
1032 self.assertEqual(due_date, t['due'])
1034 def test_filtering_with_string_datetime(self):
1035 if self.tw.version < six.text_type('2.4.0'):
1036 # Python2.6 does not support SkipTest. As a workaround
1037 # mark the test as passed by exiting.
1038 if getattr(unittest, 'SkipTest', None) is not None:
1039 raise unittest.SkipTest()
1043 t = Task(self.tw, description="test task",
1044 due=datetime.datetime.now() - datetime.timedelta(0, 2))
1046 self.assertEqual(len(self.tw.tasks.filter(due__before="now")), 1)
1049 class AnnotationTest(TasklibTest):
1052 super(AnnotationTest, self).setUp()
1053 Task(self.tw, description="test task").save()
1055 def test_adding_annotation(self):
1056 task = self.tw.tasks.get()
1057 task.add_annotation('test annotation')
1058 self.assertEqual(len(task['annotations']), 1)
1059 ann = task['annotations'][0]
1060 self.assertEqual(ann['description'], 'test annotation')
1062 def test_removing_annotation(self):
1063 task = self.tw.tasks.get()
1064 task.add_annotation('test annotation')
1065 ann = task['annotations'][0]
1067 self.assertEqual(len(task['annotations']), 0)
1069 def test_removing_annotation_by_description(self):
1070 task = self.tw.tasks.get()
1071 task.add_annotation('test annotation')
1072 task.remove_annotation('test annotation')
1073 self.assertEqual(len(task['annotations']), 0)
1075 def test_removing_annotation_by_obj(self):
1076 task = self.tw.tasks.get()
1077 task.add_annotation('test annotation')
1078 ann = task['annotations'][0]
1079 task.remove_annotation(ann)
1080 self.assertEqual(len(task['annotations']), 0)
1082 def test_annotation_after_modification(self):
1083 task = self.tw.tasks.get()
1084 task['project'] = 'test'
1085 task.add_annotation('I should really do this task')
1086 self.assertEqual(task['project'], 'test')
1088 self.assertEqual(task['project'], 'test')
1090 def test_serialize_annotations(self):
1091 # Test that serializing annotations is possible
1092 t = Task(self.tw, description="test")
1095 t.add_annotation("annotation1")
1096 t.add_annotation("annotation2")
1098 data = t._serialize('annotations', t._data['annotations'])
1100 self.assertEqual(len(data), 2)
1101 self.assertEqual(type(data[0]), dict)
1102 self.assertEqual(type(data[1]), dict)
1104 self.assertEqual(data[0]['description'], "annotation1")
1105 self.assertEqual(data[1]['description'], "annotation2")
1108 class UnicodeTest(TasklibTest):
1110 def test_unicode_task(self):
1111 Task(self.tw, description=six.u("†åßk")).save()
1114 def test_filter_by_unicode_task(self):
1115 Task(self.tw, description=six.u("†åßk")).save()
1116 tasks = self.tw.tasks.filter(description=six.u("†åßk"))
1117 self.assertEqual(len(tasks), 1)
1119 def test_non_unicode_task(self):
1120 Task(self.tw, description="test task").save()
1124 class ReadOnlyDictViewTest(unittest.TestCase):
1127 self.sample = dict(sample_list=[1, 2, 3], sample_dict={'key': 'value'})
1128 self.original_sample = copy.deepcopy(self.sample)
1129 self.view = ReadOnlyDictView(self.sample)
1131 def test_readonlydictview_getitem(self):
1132 sample_list = self.view['sample_list']
1133 self.assertEqual(sample_list, self.sample['sample_list'])
1135 # Assert that modification changed only copied value
1136 sample_list.append(4)
1137 self.assertNotEqual(sample_list, self.sample['sample_list'])
1139 # Assert that viewed dict is not changed
1140 self.assertEqual(self.sample, self.original_sample)
1142 def test_readonlydictview_contains(self):
1143 self.assertEqual('sample_list' in self.view,
1144 'sample_list' in self.sample)
1145 self.assertEqual('sample_dict' in self.view,
1146 'sample_dict' in self.sample)
1147 self.assertEqual('key' in self.view, 'key' in self.sample)
1149 # Assert that viewed dict is not changed
1150 self.assertEqual(self.sample, self.original_sample)
1152 def test_readonlydictview_iter(self):
1153 self.assertEqual(list(key for key in self.view),
1154 list(key for key in self.sample))
1156 # Assert the view is correct after modification
1157 self.sample['new'] = 'value'
1158 self.assertEqual(list(key for key in self.view),
1159 list(key for key in self.sample))
1161 def test_readonlydictview_len(self):
1162 self.assertEqual(len(self.view), len(self.sample))
1164 # Assert the view is correct after modification
1165 self.sample['new'] = 'value'
1166 self.assertEqual(len(self.view), len(self.sample))
1168 def test_readonlydictview_get(self):
1169 sample_list = self.view.get('sample_list')
1170 self.assertEqual(sample_list, self.sample.get('sample_list'))
1172 # Assert that modification changed only copied value
1173 sample_list.append(4)
1174 self.assertNotEqual(sample_list, self.sample.get('sample_list'))
1176 # Assert that viewed dict is not changed
1177 self.assertEqual(self.sample, self.original_sample)
1179 def test_readonlydict_items(self):
1180 view_items = self.view.items()
1181 sample_items = list(self.sample.items())
1182 self.assertEqual(view_items, sample_items)
1184 view_items.append('newkey')
1185 self.assertNotEqual(view_items, sample_items)
1186 self.assertEqual(self.sample, self.original_sample)
1188 def test_readonlydict_values(self):
1189 view_values = self.view.values()
1190 sample_values = list(self.sample.values())
1191 self.assertEqual(view_values, sample_values)
1193 view_list_item = list(filter(lambda x: type(x) is list,
1195 view_list_item.append(4)
1196 self.assertNotEqual(view_values, sample_values)
1197 self.assertEqual(self.sample, self.original_sample)
1200 class LazyUUIDTaskTest(TasklibTest):
1203 super(LazyUUIDTaskTest, self).setUp()
1205 self.stored = Task(self.tw, description="this is test task")
1208 self.lazy = LazyUUIDTask(self.tw, self.stored['uuid'])
1210 def test_uuid_non_conversion(self):
1211 assert self.stored['uuid'] == self.lazy['uuid']
1212 assert type(self.lazy) is LazyUUIDTask
1214 def test_lazy_explicit_conversion(self):
1215 assert type(self.lazy) is LazyUUIDTask
1217 assert type(self.lazy) is Task
1219 def test_conversion_key(self):
1220 assert self.stored['description'] == self.lazy['description']
1221 assert type(self.lazy) is Task
1223 def test_conversion_attribute(self):
1224 assert type(self.lazy) is LazyUUIDTask
1225 assert self.lazy.completed is False
1226 assert type(self.lazy) is Task
1228 def test_normal_to_lazy_equality(self):
1229 assert self.stored == self.lazy
1230 assert not self.stored != self.lazy
1231 assert type(self.lazy) is LazyUUIDTask
1233 def test_lazy_to_lazy_equality(self):
1234 lazy1 = LazyUUIDTask(self.tw, self.stored['uuid'])
1235 lazy2 = LazyUUIDTask(self.tw, self.stored['uuid'])
1237 assert lazy1 == lazy2
1238 assert not lazy1 != lazy2
1239 assert type(lazy1) is LazyUUIDTask
1240 assert type(lazy2) is LazyUUIDTask
1242 def test_normal_to_lazy_inequality(self):
1243 # Create a different UUID by changing the last letter
1244 wrong_uuid = self.stored['uuid']
1245 wrong_uuid = wrong_uuid[:-1] + ('a' if wrong_uuid[-1] != 'a' else 'b')
1247 wrong_lazy = LazyUUIDTask(self.tw, wrong_uuid)
1249 assert not self.stored == wrong_lazy
1250 assert self.stored != wrong_lazy
1251 assert type(wrong_lazy) is LazyUUIDTask
1253 def test_lazy_to_lazy_inequality(self):
1254 # Create a different UUID by changing the last letter
1255 wrong_uuid = self.stored['uuid']
1256 wrong_uuid = wrong_uuid[:-1] + ('a' if wrong_uuid[-1] != 'a' else 'b')
1258 lazy1 = LazyUUIDTask(self.tw, self.stored['uuid'])
1259 lazy2 = LazyUUIDTask(self.tw, wrong_uuid)
1261 assert not lazy1 == lazy2
1262 assert lazy1 != lazy2
1263 assert type(lazy1) is LazyUUIDTask
1264 assert type(lazy2) is LazyUUIDTask
1266 def test_lazy_in_queryset(self):
1267 tasks = self.tw.tasks.filter(uuid=self.stored['uuid'])
1269 assert self.lazy in tasks
1270 assert type(self.lazy) is LazyUUIDTask
1272 def test_lazy_saved(self):
1273 assert self.lazy.saved is True
1275 def test_lazy_modified(self):
1276 assert self.lazy.modified is False
1278 def test_lazy_modified_fields(self):
1279 assert self.lazy._modified_fields == set()
1282 class LazyUUIDTaskSetTest(TasklibTest):
1285 super(LazyUUIDTaskSetTest, self).setUp()
1287 self.task1 = Task(self.tw, description="task 1")
1288 self.task2 = Task(self.tw, description="task 2")
1289 self.task3 = Task(self.tw, description="task 3")
1301 self.lazy = LazyUUIDTaskSet(self.tw, self.uuids)
1303 def test_length(self):
1304 assert len(self.lazy) == 3
1305 assert type(self.lazy) is LazyUUIDTaskSet
1307 def test_contains(self):
1308 assert self.task1 in self.lazy
1309 assert self.task2 in self.lazy
1310 assert self.task3 in self.lazy
1311 assert type(self.lazy) is LazyUUIDTaskSet
1313 def test_eq_lazy(self):
1314 new_lazy = LazyUUIDTaskSet(self.tw, self.uuids)
1315 assert self.lazy == new_lazy
1316 assert not self.lazy != new_lazy
1317 assert type(self.lazy) is LazyUUIDTaskSet
1319 def test_eq_real(self):
1320 assert self.lazy == self.tw.tasks.all()
1321 assert self.tw.tasks.all() == self.lazy
1322 assert not self.lazy != self.tw.tasks.all()
1324 assert type(self.lazy) is LazyUUIDTaskSet
1326 def test_union(self):
1327 taskset = set([self.task1])
1328 lazyset = LazyUUIDTaskSet(
1330 (self.task2['uuid'], self.task3['uuid'])
1333 assert taskset | lazyset == self.lazy
1334 assert lazyset | taskset == self.lazy
1335 assert taskset.union(lazyset) == self.lazy
1336 assert lazyset.union(taskset) == self.lazy
1339 assert lazyset == self.lazy
1341 def test_difference(self):
1342 taskset = set([self.task1, self.task2])
1343 lazyset = LazyUUIDTaskSet(
1345 (self.task2['uuid'], self.task3['uuid'])
1348 assert taskset - lazyset == set([self.task1])
1349 assert lazyset - taskset == set([self.task3])
1350 assert taskset.difference(lazyset) == set([self.task1])
1351 assert lazyset.difference(taskset) == set([self.task3])
1354 assert lazyset == set([self.task3])
1356 def test_symmetric_difference(self):
1357 taskset = set([self.task1, self.task2])
1358 lazyset = LazyUUIDTaskSet(
1360 (self.task2['uuid'], self.task3['uuid'])
1363 assert taskset ^ lazyset == set([self.task1, self.task3])
1364 assert lazyset ^ taskset == set([self.task1, self.task3])
1365 assert taskset.symmetric_difference(lazyset) == set([self.task1,
1367 assert lazyset.symmetric_difference(taskset) == set([self.task1,
1371 assert lazyset == set([self.task1, self.task3])
1373 def test_intersection(self):
1374 taskset = set([self.task1, self.task2])
1375 lazyset = LazyUUIDTaskSet(
1377 (self.task2['uuid'], self.task3['uuid'])
1380 assert taskset & lazyset == set([self.task2])
1381 assert lazyset & taskset == set([self.task2])
1382 assert taskset.intersection(lazyset) == set([self.task2])
1383 assert lazyset.intersection(taskset) == set([self.task2])
1386 assert lazyset == set([self.task2])
1389 class TaskWarriorBackendTest(TasklibTest):
1391 def test_config(self):
1392 assert self.tw.config['nag'] == "You have more urgent tasks."
1393 assert self.tw.config['default.command'] == "next"
1394 assert self.tw.config['dependency.indicator'] == "D"