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_adding_task_with_priority(self):
618 t = Task(self.tw, description="test task", priority="M")
621 def test_removing_priority_with_none(self):
622 t = Task(self.tw, description="test task", priority="L")
625 # Remove the priority mark
629 # Assert that priority is not there after saving
630 self.assertEqual(t['priority'], None)
632 def test_adding_task_with_due_time(self):
633 t = Task(self.tw, description="test task", due=datetime.datetime.now())
636 def test_removing_due_time_with_none(self):
637 t = Task(self.tw, description="test task", due=datetime.datetime.now())
640 # Remove the due timestamp
644 # Assert that due timestamp is no longer there
645 self.assertEqual(t['due'], None)
647 def test_modified_fields_new_task(self):
650 # This should be empty with new task
651 self.assertEqual(set(t._modified_fields), set())
654 t['description'] = "test task"
655 self.assertEqual(set(t._modified_fields), set(['description']))
657 t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14) # <3
658 self.assertEqual(set(t._modified_fields), set(['description', 'due']))
660 t['project'] = "test project"
661 self.assertEqual(set(t._modified_fields),
662 set(['description', 'due', 'project']))
664 # List of modified fields should clear out when saved
666 self.assertEqual(set(t._modified_fields), set())
668 # Reassigning the fields with the same values now should not produce
670 t['description'] = "test task"
671 t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14) # <3
672 t['project'] = "test project"
673 self.assertEqual(set(t._modified_fields), set())
675 def test_modified_fields_loaded_task(self):
679 t['description'] = "test task"
680 t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14) # <3
681 t['project'] = "test project"
683 dependency = Task(self.tw, description="dependency")
685 t['depends'] = set([dependency])
687 # List of modified fields should clear out when saved
689 self.assertEqual(set(t._modified_fields), set())
691 # Get the task by using a filter by UUID
692 t2 = self.tw.tasks.get(uuid=t['uuid'])
694 # Reassigning the fields with the same values now should not produce
696 t['description'] = "test task"
697 t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14) # <3
698 t['project'] = "test project"
699 t['depends'] = set([dependency])
700 self.assertEqual(set(t._modified_fields), set())
702 def test_modified_fields_not_affected_by_reading(self):
705 for field in TASK_STANDARD_ATTRS:
708 self.assertEqual(set(t._modified_fields), set())
710 def test_setting_read_only_attrs_through_init(self):
711 # Test that we are unable to set readonly attrs through __init__
712 for readonly_key in Task.read_only_fields:
713 kwargs = {'description': 'test task', readonly_key: 'value'}
714 self.assertRaises(RuntimeError,
715 lambda: Task(self.tw, **kwargs))
717 def test_setting_read_only_attrs_through_setitem(self):
718 # Test that we are unable to set readonly attrs through __init__
719 for readonly_key in Task.read_only_fields:
720 t = Task(self.tw, description='test task')
721 self.assertRaises(RuntimeError,
722 lambda: t.__setitem__(readonly_key, 'value'))
724 def test_saving_unmodified_task(self):
725 t = Task(self.tw, description="test task")
729 def test_adding_tag_by_appending(self):
730 t = Task(self.tw, description="test task", tags=['test1'])
732 t['tags'].add('test2')
734 self.assertEqual(t['tags'], set(['test1', 'test2']))
736 def test_adding_tag_twice(self):
737 t = Task(self.tw, description="test task", tags=['test1'])
739 t['tags'].add('test2')
740 t['tags'].add('test2')
742 self.assertEqual(t['tags'], set(['test1', 'test2']))
744 def test_adding_tag_by_appending_empty(self):
745 t = Task(self.tw, description="test task")
747 t['tags'].add('test')
749 self.assertEqual(t['tags'], set(['test']))
751 def test_serializers_returning_empty_string_for_none(self):
752 # Test that any serializer returns '' when passed None
754 serializers = [getattr(t, serializer_name) for serializer_name in
755 filter(lambda x: x.startswith('serialize_'), dir(t))]
756 for serializer in serializers:
757 self.assertEqual(serializer(None), '')
759 def test_deserializer_returning_empty_value_for_empty_string(self):
760 # Test that any deserializer returns empty value when passed ''
762 deserializers = [getattr(t, deserializer_name) for deserializer_name in
763 filter(lambda x: x.startswith('deserialize_'), dir(t))]
764 for deserializer in deserializers:
765 self.assertTrue(deserializer('') in (None, [], set()))
767 def test_normalizers_handling_none(self):
768 # Test that any normalizer can handle None as a valid value
771 for key in TASK_STANDARD_ATTRS:
772 t._normalize(key, None)
774 def test_recurrent_task_generation(self):
775 today = datetime.date.today()
776 t = Task(self.tw, description="brush teeth",
777 due=today, recur="daily")
779 self.assertEqual(len(self.tw.tasks.pending()), 2)
781 def test_spawned_task_parent(self):
782 today = datetime.date.today()
783 t = Task(self.tw, description="brush teeth",
784 due=today, recur="daily")
787 spawned = self.tw.tasks.pending().get(due=today)
788 assert spawned['parent'] == t
790 def test_modify_number_of_tasks_at_once(self):
791 for i in range(1, 100):
792 Task(self.tw, description="test task %d" % i, tags=['test']).save()
794 self.tw.execute_command(['+test', 'mod', 'unified', 'description'])
796 def test_return_all_from_executed_command(self):
797 Task(self.tw, description="test task", tags=['test']).save()
798 out, err, rc = self.tw.execute_command(['count'], return_all=True)
799 self.assertEqual(rc, 0)
801 def test_return_all_from_failed_executed_command(self):
802 Task(self.tw, description="test task", tags=['test']).save()
803 out, err, rc = self.tw.execute_command(['countinvalid'],
804 return_all=True, allow_failure=False)
805 self.assertNotEqual(rc, 0)
808 class TaskFromHookTest(TasklibTest):
810 input_add_data = six.StringIO(
811 '{"description":"Buy some milk",'
812 '"entry":"20141118T050231Z",'
813 '"status":"pending",'
814 '"start":"20141119T152233Z",'
815 '"uuid":"a360fc44-315c-4366-b70c-ea7e7520b749"}')
817 input_add_data_recurring = six.StringIO(
818 '{"description":"Mow the lawn",'
819 '"entry":"20160210T224304Z",'
820 '"parent":"62da6227-519c-42c2-915d-dccada926ad7",'
822 '"status":"pending",'
823 '"uuid":"81305335-0237-49ff-8e87-b3cdc2369cec"}')
825 input_modify_data = six.StringIO(input_add_data.getvalue() + '\n' +
826 '{"description":"Buy some milk finally",'
827 '"entry":"20141118T050231Z",'
828 '"status":"completed",'
829 '"uuid":"a360fc44-315c-4366-b70c-ea7e7520b749"}')
831 exported_raw_data = (
833 '"due":"20150101T232323Z",'
834 '"description":"test task"}')
836 def test_setting_up_from_add_hook_input(self):
837 t = Task.from_input(input_file=self.input_add_data, backend=self.tw)
838 self.assertEqual(t['description'], "Buy some milk")
839 self.assertEqual(t.pending, True)
841 def test_setting_up_from_add_hook_input_recurring(self):
842 t = Task.from_input(input_file=self.input_add_data_recurring,
844 self.assertEqual(t['description'], "Mow the lawn")
845 self.assertEqual(t.pending, True)
847 def test_setting_up_from_modified_hook_input(self):
848 t = Task.from_input(input_file=self.input_modify_data, modify=True,
850 self.assertEqual(t['description'], "Buy some milk finally")
851 self.assertEqual(t.pending, False)
852 self.assertEqual(t.completed, True)
854 self.assertEqual(t._original_data['status'], "pending")
855 self.assertEqual(t._original_data['description'], "Buy some milk")
856 self.assertEqual(set(t._modified_fields),
857 set(['status', 'description', 'start']))
859 def test_export_data(self):
860 t = Task(self.tw, description="test task",
862 due=pytz.utc.localize(datetime.datetime(2015,1,1,23,23,23)))
864 # Check that the output is a permutation of:
865 # {"project":"Home","description":"test task","due":"20150101232323Z"}
866 allowed_segments = self.exported_raw_data[1:-1].split(',')
868 '{' + ','.join(segments) + '}'
869 for segments in itertools.permutations(allowed_segments)
872 self.assertTrue(any(t.export_data() == expected
873 for expected in allowed_output))
875 class TimezoneAwareDatetimeTest(TasklibTest):
878 super(TimezoneAwareDatetimeTest, self).setUp()
879 self.zone = local_zone
880 self.localdate_naive = datetime.datetime(2015,2,2)
881 self.localtime_naive = datetime.datetime(2015,2,2,0,0,0)
882 self.localtime_aware = self.zone.localize(self.localtime_naive)
883 self.utctime_aware = self.localtime_aware.astimezone(pytz.utc)
885 def test_timezone_naive_datetime_setitem(self):
886 t = Task(self.tw, description="test task")
887 t['due'] = self.localtime_naive
888 self.assertEqual(t['due'], self.localtime_aware)
890 def test_timezone_naive_datetime_using_init(self):
891 t = Task(self.tw, description="test task", due=self.localtime_naive)
892 self.assertEqual(t['due'], self.localtime_aware)
894 def test_filter_by_naive_datetime(self):
895 t = Task(self.tw, description="task1", due=self.localtime_naive)
897 matching_tasks = self.tw.tasks.filter(due=self.localtime_naive)
898 self.assertEqual(len(matching_tasks), 1)
900 def test_serialize_naive_datetime(self):
901 t = Task(self.tw, description="task1", due=self.localtime_naive)
902 self.assertEqual(json.loads(t.export_data())['due'],
903 self.utctime_aware.strftime(DATE_FORMAT))
905 def test_timezone_naive_date_setitem(self):
906 t = Task(self.tw, description="test task")
907 t['due'] = self.localdate_naive
908 self.assertEqual(t['due'], self.localtime_aware)
910 def test_timezone_naive_date_using_init(self):
911 t = Task(self.tw, description="test task", due=self.localdate_naive)
912 self.assertEqual(t['due'], self.localtime_aware)
914 def test_filter_by_naive_date(self):
915 t = Task(self.tw, description="task1", due=self.localdate_naive)
917 matching_tasks = self.tw.tasks.filter(due=self.localdate_naive)
918 self.assertEqual(len(matching_tasks), 1)
920 def test_serialize_naive_date(self):
921 t = Task(self.tw, description="task1", due=self.localdate_naive)
922 self.assertEqual(json.loads(t.export_data())['due'],
923 self.utctime_aware.strftime(DATE_FORMAT))
925 def test_timezone_aware_datetime_setitem(self):
926 t = Task(self.tw, description="test task")
927 t['due'] = self.localtime_aware
928 self.assertEqual(t['due'], self.localtime_aware)
930 def test_timezone_aware_datetime_using_init(self):
931 t = Task(self.tw, description="test task", due=self.localtime_aware)
932 self.assertEqual(t['due'], self.localtime_aware)
934 def test_filter_by_aware_datetime(self):
935 t = Task(self.tw, description="task1", due=self.localtime_aware)
937 matching_tasks = self.tw.tasks.filter(due=self.localtime_aware)
938 self.assertEqual(len(matching_tasks), 1)
940 def test_serialize_aware_datetime(self):
941 t = Task(self.tw, description="task1", due=self.localtime_aware)
942 self.assertEqual(json.loads(t.export_data())['due'],
943 self.utctime_aware.strftime(DATE_FORMAT))
945 class DatetimeStringTest(TasklibTest):
947 def test_simple_now_conversion(self):
948 if self.tw.version < six.text_type('2.4.0'):
949 # Python2.6 does not support SkipTest. As a workaround
950 # mark the test as passed by exiting.
951 if getattr(unittest, 'SkipTest', None) is not None:
952 raise unittest.SkipTest()
956 t = Task(self.tw, description="test task", due="now")
957 now = local_zone.localize(datetime.datetime.now())
959 # Assert that both times are not more than 5 seconds apart
960 if sys.version_info < (2,7):
961 self.assertTrue(total_seconds_2_6(now - t['due']) < 5)
962 self.assertTrue(total_seconds_2_6(t['due'] - now) < 5)
964 self.assertTrue((now - t['due']).total_seconds() < 5)
965 self.assertTrue((t['due'] - now).total_seconds() < 5)
967 def test_simple_eoy_conversion(self):
968 if self.tw.version < six.text_type('2.4.0'):
969 # Python2.6 does not support SkipTest. As a workaround
970 # mark the test as passed by exiting.
971 if getattr(unittest, 'SkipTest', None) is not None:
972 raise unittest.SkipTest()
976 t = Task(self.tw, description="test task", due="eoy")
977 now = local_zone.localize(datetime.datetime.now())
978 eoy = local_zone.localize(datetime.datetime(
986 self.assertEqual(eoy, t['due'])
988 def test_complex_eoy_conversion(self):
989 if self.tw.version < six.text_type('2.4.0'):
990 # Python2.6 does not support SkipTest. As a workaround
991 # mark the test as passed by exiting.
992 if getattr(unittest, 'SkipTest', None) is not None:
993 raise unittest.SkipTest()
997 t = Task(self.tw, description="test task", due="eoy - 4 months")
998 now = local_zone.localize(datetime.datetime.now())
999 due_date = local_zone.localize(datetime.datetime(
1006 )) - datetime.timedelta(0,4 * 30 * 86400)
1007 self.assertEqual(due_date, t['due'])
1009 def test_filtering_with_string_datetime(self):
1010 if self.tw.version < six.text_type('2.4.0'):
1011 # Python2.6 does not support SkipTest. As a workaround
1012 # mark the test as passed by exiting.
1013 if getattr(unittest, 'SkipTest', None) is not None:
1014 raise unittest.SkipTest()
1018 t = Task(self.tw, description="test task",
1019 due=datetime.datetime.now() - datetime.timedelta(0,2))
1021 self.assertEqual(len(self.tw.tasks.filter(due__before="now")), 1)
1023 class AnnotationTest(TasklibTest):
1026 super(AnnotationTest, self).setUp()
1027 Task(self.tw, description="test task").save()
1029 def test_adding_annotation(self):
1030 task = self.tw.tasks.get()
1031 task.add_annotation('test annotation')
1032 self.assertEqual(len(task['annotations']), 1)
1033 ann = task['annotations'][0]
1034 self.assertEqual(ann['description'], 'test annotation')
1036 def test_removing_annotation(self):
1037 task = self.tw.tasks.get()
1038 task.add_annotation('test annotation')
1039 ann = task['annotations'][0]
1041 self.assertEqual(len(task['annotations']), 0)
1043 def test_removing_annotation_by_description(self):
1044 task = self.tw.tasks.get()
1045 task.add_annotation('test annotation')
1046 task.remove_annotation('test annotation')
1047 self.assertEqual(len(task['annotations']), 0)
1049 def test_removing_annotation_by_obj(self):
1050 task = self.tw.tasks.get()
1051 task.add_annotation('test annotation')
1052 ann = task['annotations'][0]
1053 task.remove_annotation(ann)
1054 self.assertEqual(len(task['annotations']), 0)
1056 def test_annotation_after_modification(self):
1057 task = self.tw.tasks.get()
1058 task['project'] = 'test'
1059 task.add_annotation('I should really do this task')
1060 self.assertEqual(task['project'], 'test')
1062 self.assertEqual(task['project'], 'test')
1064 def test_serialize_annotations(self):
1065 # Test that serializing annotations is possible
1066 t = Task(self.tw, description="test")
1069 t.add_annotation("annotation1")
1070 t.add_annotation("annotation2")
1072 data = t._serialize('annotations', t._data['annotations'])
1074 self.assertEqual(len(data), 2)
1075 self.assertEqual(type(data[0]), dict)
1076 self.assertEqual(type(data[1]), dict)
1078 self.assertEqual(data[0]['description'], "annotation1")
1079 self.assertEqual(data[1]['description'], "annotation2")
1082 class UnicodeTest(TasklibTest):
1084 def test_unicode_task(self):
1085 Task(self.tw, description=six.u("†åßk")).save()
1088 def test_filter_by_unicode_task(self):
1089 Task(self.tw, description=six.u("†åßk")).save()
1090 tasks = self.tw.tasks.filter(description=six.u("†åßk"))
1091 self.assertEqual(len(tasks), 1)
1093 def test_non_unicode_task(self):
1094 Task(self.tw, description="test task").save()
1097 class ReadOnlyDictViewTest(unittest.TestCase):
1100 self.sample = dict(l=[1,2,3], d={'k':'v'})
1101 self.original_sample = copy.deepcopy(self.sample)
1102 self.view = ReadOnlyDictView(self.sample)
1104 def test_readonlydictview_getitem(self):
1106 self.assertEqual(l, self.sample['l'])
1108 # Assert that modification changed only copied value
1110 self.assertNotEqual(l, self.sample['l'])
1112 # Assert that viewed dict is not changed
1113 self.assertEqual(self.sample, self.original_sample)
1115 def test_readonlydictview_contains(self):
1116 self.assertEqual('l' in self.view, 'l' in self.sample)
1117 self.assertEqual('d' in self.view, 'd' in self.sample)
1118 self.assertEqual('k' in self.view, 'k' in self.sample)
1120 # Assert that viewed dict is not changed
1121 self.assertEqual(self.sample, self.original_sample)
1123 def test_readonlydictview_iter(self):
1124 self.assertEqual(list(k for k in self.view),
1125 list(k for k in self.sample))
1127 # Assert the view is correct after modification
1128 self.sample['new'] = 'value'
1129 self.assertEqual(list(k for k in self.view),
1130 list(k for k in self.sample))
1132 def test_readonlydictview_len(self):
1133 self.assertEqual(len(self.view), len(self.sample))
1135 # Assert the view is correct after modification
1136 self.sample['new'] = 'value'
1137 self.assertEqual(len(self.view), len(self.sample))
1139 def test_readonlydictview_get(self):
1140 l = self.view.get('l')
1141 self.assertEqual(l, self.sample.get('l'))
1143 # Assert that modification changed only copied value
1145 self.assertNotEqual(l, self.sample.get('l'))
1147 # Assert that viewed dict is not changed
1148 self.assertEqual(self.sample, self.original_sample)
1150 def test_readonlydict_items(self):
1151 view_items = self.view.items()
1152 sample_items = list(self.sample.items())
1153 self.assertEqual(view_items, sample_items)
1155 view_items.append('newkey')
1156 self.assertNotEqual(view_items, sample_items)
1157 self.assertEqual(self.sample, self.original_sample)
1159 def test_readonlydict_values(self):
1160 view_values = self.view.values()
1161 sample_values = list(self.sample.values())
1162 self.assertEqual(view_values, sample_values)
1164 view_list_item = list(filter(lambda x: type(x) is list,
1166 view_list_item.append(4)
1167 self.assertNotEqual(view_values, sample_values)
1168 self.assertEqual(self.sample, self.original_sample)
1171 class LazyUUIDTaskTest(TasklibTest):
1174 super(LazyUUIDTaskTest, self).setUp()
1176 self.stored = Task(self.tw, description="this is test task")
1179 self.lazy = LazyUUIDTask(self.tw, self.stored['uuid'])
1181 def test_uuid_non_conversion(self):
1182 assert self.stored['uuid'] == self.lazy['uuid']
1183 assert type(self.lazy) is LazyUUIDTask
1185 def test_lazy_explicit_conversion(self):
1186 assert type(self.lazy) is LazyUUIDTask
1188 assert type(self.lazy) is Task
1190 def test_conversion_key(self):
1191 assert self.stored['description'] == self.lazy['description']
1192 assert type(self.lazy) is Task
1194 def test_conversion_attribute(self):
1195 assert type(self.lazy) is LazyUUIDTask
1196 assert self.lazy.completed is False
1197 assert type(self.lazy) is Task
1199 def test_normal_to_lazy_equality(self):
1200 assert self.stored == self.lazy
1201 assert not self.stored != self.lazy
1202 assert type(self.lazy) is LazyUUIDTask
1204 def test_lazy_to_lazy_equality(self):
1205 lazy1 = LazyUUIDTask(self.tw, self.stored['uuid'])
1206 lazy2 = LazyUUIDTask(self.tw, self.stored['uuid'])
1208 assert lazy1 == lazy2
1209 assert not lazy1 != lazy2
1210 assert type(lazy1) is LazyUUIDTask
1211 assert type(lazy2) is LazyUUIDTask
1213 def test_normal_to_lazy_inequality(self):
1214 # Create a different UUID by changing the last letter
1215 wrong_uuid = self.stored['uuid']
1216 wrong_uuid = wrong_uuid[:-1] + ('a' if wrong_uuid[-1] != 'a' else 'b')
1218 wrong_lazy = LazyUUIDTask(self.tw, wrong_uuid)
1220 assert not self.stored == wrong_lazy
1221 assert self.stored != wrong_lazy
1222 assert type(wrong_lazy) is LazyUUIDTask
1224 def test_lazy_to_lazy_inequality(self):
1225 # Create a different UUID by changing the last letter
1226 wrong_uuid = self.stored['uuid']
1227 wrong_uuid = wrong_uuid[:-1] + ('a' if wrong_uuid[-1] != 'a' else 'b')
1229 lazy1 = LazyUUIDTask(self.tw, self.stored['uuid'])
1230 lazy2 = LazyUUIDTask(self.tw, wrong_uuid)
1232 assert not lazy1 == lazy2
1233 assert lazy1 != lazy2
1234 assert type(lazy1) is LazyUUIDTask
1235 assert type(lazy2) is LazyUUIDTask
1237 def test_lazy_in_queryset(self):
1238 tasks = self.tw.tasks.filter(uuid=self.stored['uuid'])
1240 assert self.lazy in tasks
1241 assert type(self.lazy) is LazyUUIDTask
1243 def test_lazy_saved(self):
1244 assert self.lazy.saved is True
1246 def test_lazy_modified(self):
1247 assert self.lazy.modified is False
1249 def test_lazy_modified_fields(self):
1250 assert self.lazy._modified_fields == set()
1253 class LazyUUIDTaskSetTest(TasklibTest):
1256 super(LazyUUIDTaskSetTest, self).setUp()
1258 self.task1 = Task(self.tw, description="task 1")
1259 self.task2 = Task(self.tw, description="task 2")
1260 self.task3 = Task(self.tw, description="task 3")
1272 self.lazy = LazyUUIDTaskSet(self.tw, self.uuids)
1274 def test_length(self):
1275 assert len(self.lazy) == 3
1276 assert type(self.lazy) is LazyUUIDTaskSet
1278 def test_contains(self):
1279 assert self.task1 in self.lazy
1280 assert self.task2 in self.lazy
1281 assert self.task3 in self.lazy
1282 assert type(self.lazy) is LazyUUIDTaskSet
1284 def test_eq_lazy(self):
1285 new_lazy = LazyUUIDTaskSet(self.tw, self.uuids)
1286 assert self.lazy == new_lazy
1287 assert not self.lazy != new_lazy
1288 assert type(self.lazy) is LazyUUIDTaskSet
1290 def test_eq_real(self):
1291 assert self.lazy == self.tw.tasks.all()
1292 assert self.tw.tasks.all() == self.lazy
1293 assert not self.lazy != self.tw.tasks.all()
1295 assert type(self.lazy) is LazyUUIDTaskSet
1297 def test_union(self):
1298 taskset = set([self.task1])
1299 lazyset = LazyUUIDTaskSet(
1301 (self.task2['uuid'], self.task3['uuid'])
1304 assert taskset | lazyset == self.lazy
1305 assert lazyset | taskset == self.lazy
1306 assert taskset.union(lazyset) == self.lazy
1307 assert lazyset.union(taskset) == self.lazy
1310 assert lazyset == self.lazy
1312 def test_difference(self):
1313 taskset = set([self.task1, self.task2])
1314 lazyset = LazyUUIDTaskSet(
1316 (self.task2['uuid'], self.task3['uuid'])
1319 assert taskset - lazyset == set([self.task1])
1320 assert lazyset - taskset == set([self.task3])
1321 assert taskset.difference(lazyset) == set([self.task1])
1322 assert lazyset.difference(taskset) == set([self.task3])
1325 assert lazyset == set([self.task3])
1327 def test_symmetric_difference(self):
1328 taskset = set([self.task1, self.task2])
1329 lazyset = LazyUUIDTaskSet(
1331 (self.task2['uuid'], self.task3['uuid'])
1334 assert taskset ^ lazyset == set([self.task1, self.task3])
1335 assert lazyset ^ taskset == set([self.task1, self.task3])
1336 assert taskset.symmetric_difference(lazyset) == set([self.task1, self.task3])
1337 assert lazyset.symmetric_difference(taskset) == set([self.task1, self.task3])
1340 assert lazyset == set([self.task1, self.task3])
1342 def test_intersection(self):
1343 taskset = set([self.task1, self.task2])
1344 lazyset = LazyUUIDTaskSet(
1346 (self.task2['uuid'], self.task3['uuid'])
1349 assert taskset & lazyset == set([self.task2])
1350 assert lazyset & taskset == set([self.task2])
1351 assert taskset.intersection(lazyset) == set([self.task2])
1352 assert lazyset.intersection(taskset) == set([self.task2])
1355 assert lazyset == set([self.task2])
1358 class TaskWarriorBackendTest(TasklibTest):
1360 def test_config(self):
1361 assert self.tw.config['nag'] == "You have more urgent tasks."
1362 assert self.tw.config['debug'] == "no"