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_filtering_by_attribute(self):
85 Task(self.tw, description="no priority task").save()
86 Task(self.tw, priority="H", description="high priority task").save()
87 self.assertEqual(len(self.tw.tasks.all()), 2)
89 # Assert that the correct number of tasks is returned
90 self.assertEqual(len(self.tw.tasks.filter(priority="H")), 1)
92 # Assert that the correct tasks are returned
93 high_priority_task = self.tw.tasks.get(priority="H")
94 self.assertEqual(high_priority_task['description'], "high priority task")
96 def test_filtering_by_empty_attribute(self):
97 Task(self.tw, description="no priority task").save()
98 Task(self.tw, priority="H", description="high priority task").save()
99 self.assertEqual(len(self.tw.tasks.all()), 2)
101 # Assert that the correct number of tasks is returned
102 self.assertEqual(len(self.tw.tasks.filter(priority=None)), 1)
104 # Assert that the correct tasks are returned
105 no_priority_task = self.tw.tasks.get(priority=None)
106 self.assertEqual(no_priority_task['description'], "no priority task")
108 def test_filter_for_task_with_space_in_descripition(self):
109 task = Task(self.tw, description="test task")
112 filtered_task = self.tw.tasks.get(description="test task")
113 self.assertEqual(filtered_task['description'], "test task")
115 def test_filter_for_task_without_space_in_descripition(self):
116 task = Task(self.tw, description="test")
119 filtered_task = self.tw.tasks.get(description="test")
120 self.assertEqual(filtered_task['description'], "test")
122 def test_filter_for_task_with_space_in_project(self):
123 task = Task(self.tw, description="test", project="random project")
126 filtered_task = self.tw.tasks.get(project="random project")
127 self.assertEqual(filtered_task['project'], "random project")
129 def test_filter_for_task_without_space_in_project(self):
130 task = Task(self.tw, description="test", project="random")
133 filtered_task = self.tw.tasks.get(project="random")
134 self.assertEqual(filtered_task['project'], "random")
136 def test_filter_with_empty_uuid(self):
137 self.assertRaises(ValueError, lambda: self.tw.tasks.get(uuid=''))
139 def test_filter_dummy_by_status(self):
140 t = Task(self.tw, description="test")
143 tasks = self.tw.tasks.filter(status=t['status'])
144 self.assertEqual(list(tasks), [t])
146 def test_filter_dummy_by_uuid(self):
147 t = Task(self.tw, description="test")
150 tasks = self.tw.tasks.filter(uuid=t['uuid'])
151 self.assertEqual(list(tasks), [t])
153 def test_filter_dummy_by_entry(self):
154 t = Task(self.tw, description="test")
157 tasks = self.tw.tasks.filter(entry=t['entry'])
158 self.assertEqual(list(tasks), [t])
160 def test_filter_dummy_by_description(self):
161 t = Task(self.tw, description="test")
164 tasks = self.tw.tasks.filter(description=t['description'])
165 self.assertEqual(list(tasks), [t])
167 def test_filter_dummy_by_start(self):
168 t = Task(self.tw, description="test")
172 tasks = self.tw.tasks.filter(start=t['start'])
173 self.assertEqual(list(tasks), [t])
175 def test_filter_dummy_by_end(self):
176 t = Task(self.tw, description="test")
180 tasks = self.tw.tasks.filter(end=t['end'])
181 self.assertEqual(list(tasks), [t])
183 def test_filter_dummy_by_due(self):
184 t = Task(self.tw, description="test", due=datetime.datetime.now())
187 tasks = self.tw.tasks.filter(due=t['due'])
188 self.assertEqual(list(tasks), [t])
190 def test_filter_dummy_by_until(self):
191 t = Task(self.tw, description="test")
194 tasks = self.tw.tasks.filter(until=t['until'])
195 self.assertEqual(list(tasks), [t])
197 def test_filter_dummy_by_modified(self):
198 # Older TW version does not support bumping modified
200 if self.tw.version < six.text_type('2.2.0'):
201 # Python2.6 does not support SkipTest. As a workaround
202 # mark the test as passed by exiting.
203 if getattr(unittest, 'SkipTest', None) is not None:
204 raise unittest.SkipTest()
208 t = Task(self.tw, description="test")
211 tasks = self.tw.tasks.filter(modified=t['modified'])
212 self.assertEqual(list(tasks), [t])
214 def test_filter_dummy_by_scheduled(self):
215 t = Task(self.tw, description="test")
218 tasks = self.tw.tasks.filter(scheduled=t['scheduled'])
219 self.assertEqual(list(tasks), [t])
221 def test_filter_dummy_by_tags(self):
222 t = Task(self.tw, description="test", tags=["home"])
225 tasks = self.tw.tasks.filter(tags=t['tags'])
226 self.assertEqual(list(tasks), [t])
228 def test_filter_dummy_by_projects(self):
229 t = Task(self.tw, description="test", project="random")
232 tasks = self.tw.tasks.filter(project=t['project'])
233 self.assertEqual(list(tasks), [t])
235 def test_filter_by_priority(self):
236 t = Task(self.tw, description="test", priority="H")
239 tasks = self.tw.tasks.filter(priority=t['priority'])
240 self.assertEqual(list(tasks), [t])
243 class TaskTest(TasklibTest):
245 def test_create_unsaved_task(self):
246 # Make sure a new task is not saved unless explicitly called for
247 t = Task(self.tw, description="test task")
248 self.assertEqual(len(self.tw.tasks.all()), 0)
250 # TODO: once python 2.6 compatiblity is over, use context managers here
251 # and in all subsequent tests for assertRaises
253 def test_delete_unsaved_task(self):
254 t = Task(self.tw, description="test task")
255 self.assertRaises(Task.NotSaved, t.delete)
257 def test_complete_unsaved_task(self):
258 t = Task(self.tw, description="test task")
259 self.assertRaises(Task.NotSaved, t.done)
261 def test_refresh_unsaved_task(self):
262 t = Task(self.tw, description="test task")
263 self.assertRaises(Task.NotSaved, t.refresh)
265 def test_start_unsaved_task(self):
266 t = Task(self.tw, description="test task")
267 self.assertRaises(Task.NotSaved, t.start)
269 def test_delete_deleted_task(self):
270 t = Task(self.tw, description="test task")
274 self.assertRaises(Task.DeletedTask, t.delete)
276 def test_complete_completed_task(self):
277 t = Task(self.tw, description="test task")
281 self.assertRaises(Task.CompletedTask, t.done)
283 def test_start_completed_task(self):
284 t = Task(self.tw, description="test task")
288 self.assertRaises(Task.CompletedTask, t.start)
290 def test_add_completed_task(self):
291 t = Task(self.tw, description="test", status="completed",
292 end=datetime.datetime.now())
295 def test_add_multiple_completed_tasks(self):
296 t1 = Task(self.tw, description="test1", status="completed",
297 end=datetime.datetime.now())
298 t2 = Task(self.tw, description="test2", status="completed",
299 end=datetime.datetime.now())
303 def test_complete_deleted_task(self):
304 t = Task(self.tw, description="test task")
308 self.assertRaises(Task.DeletedTask, t.done)
310 def test_starting_task(self):
311 t = Task(self.tw, description="test task")
312 now = t.datetime_normalizer(datetime.datetime.now())
316 self.assertTrue(now.replace(microsecond=0) <= t['start'])
317 self.assertEqual(t['status'], 'pending')
319 def test_completing_task(self):
320 t = Task(self.tw, description="test task")
321 now = t.datetime_normalizer(datetime.datetime.now())
325 self.assertTrue(now.replace(microsecond=0) <= t['end'])
326 self.assertEqual(t['status'], 'completed')
328 def test_deleting_task(self):
329 t = Task(self.tw, description="test task")
330 now = t.datetime_normalizer(datetime.datetime.now())
334 self.assertTrue(now.replace(microsecond=0) <= t['end'])
335 self.assertEqual(t['status'], 'deleted')
337 def test_started_task_active(self):
338 t = Task(self.tw, description="test task")
341 self.assertTrue(t.active)
343 def test_unstarted_task_inactive(self):
344 t = Task(self.tw, description="test task")
345 self.assertFalse(t.active)
347 self.assertFalse(t.active)
349 def test_start_active_task(self):
350 t = Task(self.tw, description="test task")
353 self.assertRaises(Task.ActiveTask, t.start)
355 def test_stop_completed_task(self):
356 t = Task(self.tw, description="test task")
361 self.assertRaises(Task.InactiveTask, t.stop)
363 t = Task(self.tw, description="test task")
367 self.assertRaises(Task.InactiveTask, t.stop)
369 def test_stop_deleted_task(self):
370 t = Task(self.tw, description="test task")
376 def test_stop_inactive_task(self):
377 t = Task(self.tw, description="test task")
380 self.assertRaises(Task.InactiveTask, t.stop)
382 t = Task(self.tw, description="test task")
387 self.assertRaises(Task.InactiveTask, t.stop)
389 def test_stopping_task(self):
390 t = Task(self.tw, description="test task")
391 now = t.datetime_normalizer(datetime.datetime.now())
396 self.assertEqual(t['end'], None)
397 self.assertEqual(t['status'], 'pending')
398 self.assertFalse(t.active)
400 def test_modify_simple_attribute_without_space(self):
401 t = Task(self.tw, description="test")
404 self.assertEquals(t['description'], "test")
406 t['description'] = "test-modified"
409 self.assertEquals(t['description'], "test-modified")
411 def test_modify_simple_attribute_with_space(self):
412 # Space can pose problems with parsing
413 t = Task(self.tw, description="test task")
416 self.assertEquals(t['description'], "test task")
418 t['description'] = "test task modified"
421 self.assertEquals(t['description'], "test task modified")
423 def test_empty_dependency_set_of_unsaved_task(self):
424 t = Task(self.tw, description="test task")
425 self.assertEqual(t['depends'], set())
427 def test_empty_dependency_set_of_saved_task(self):
428 t = Task(self.tw, description="test task")
430 self.assertEqual(t['depends'], set())
432 def test_set_unsaved_task_as_dependency(self):
433 # Adds only one dependency to task with no dependencies
434 t = Task(self.tw, description="test task")
435 dependency = Task(self.tw, description="needs to be done first")
437 # We only save the parent task, dependency task is unsaved
439 t['depends'] = set([dependency])
441 self.assertRaises(Task.NotSaved, t.save)
443 def test_set_simple_dependency_set(self):
444 # Adds only one dependency to task with no dependencies
445 t = Task(self.tw, description="test task")
446 dependency = Task(self.tw, description="needs to be done first")
451 t['depends'] = set([dependency])
453 self.assertEqual(t['depends'], set([dependency]))
455 def test_set_complex_dependency_set(self):
456 # Adds two dependencies to task with no dependencies
457 t = Task(self.tw, description="test task")
458 dependency1 = Task(self.tw, description="needs to be done first")
459 dependency2 = Task(self.tw, description="needs to be done second")
465 t['depends'] = set([dependency1, dependency2])
467 self.assertEqual(t['depends'], set([dependency1, dependency2]))
469 def test_remove_from_dependency_set(self):
470 # Removes dependency from task with two dependencies
471 t = Task(self.tw, description="test task")
472 dependency1 = Task(self.tw, description="needs to be done first")
473 dependency2 = Task(self.tw, description="needs to be done second")
478 t['depends'] = set([dependency1, dependency2])
481 t['depends'].remove(dependency2)
484 self.assertEqual(t['depends'], set([dependency1]))
486 def test_add_to_dependency_set(self):
487 # Adds dependency to task with one dependencies
488 t = Task(self.tw, description="test task")
489 dependency1 = Task(self.tw, description="needs to be done first")
490 dependency2 = Task(self.tw, description="needs to be done second")
495 t['depends'] = set([dependency1])
498 t['depends'].add(dependency2)
501 self.assertEqual(t['depends'], set([dependency1, dependency2]))
503 def test_add_to_empty_dependency_set(self):
504 # Adds dependency to task with one dependencies
505 t = Task(self.tw, description="test task")
506 dependency = Task(self.tw, description="needs to be done first")
510 t['depends'].add(dependency)
513 self.assertEqual(t['depends'], set([dependency]))
515 def test_simple_dependency_set_save_repeatedly(self):
516 # Adds only one dependency to task with no dependencies
517 t = Task(self.tw, description="test task")
518 dependency = Task(self.tw, description="needs to be done first")
521 t['depends'] = set([dependency])
524 # We taint the task, but keep depends intact
525 t['description'] = "test task modified"
528 self.assertEqual(t['depends'], set([dependency]))
530 # We taint the task, but assign the same set to the depends
531 t['depends'] = set([dependency])
532 t['description'] = "test task modified again"
535 self.assertEqual(t['depends'], set([dependency]))
537 def test_compare_different_tasks(self):
538 # Negative: compare two different tasks
539 t1 = Task(self.tw, description="test task")
540 t2 = Task(self.tw, description="test task")
545 self.assertEqual(t1 == t2, False)
547 def test_compare_same_task_object(self):
548 # Compare Task object wit itself
549 t = Task(self.tw, description="test task")
552 self.assertEqual(t == t, True)
554 def test_compare_same_task(self):
555 # Compare the same task using two different objects
556 t1 = Task(self.tw, description="test task")
559 t2 = self.tw.tasks.get(uuid=t1['uuid'])
560 self.assertEqual(t1 == t2, True)
562 def test_compare_unsaved_tasks(self):
563 # t1 and t2 are unsaved tasks, considered to be unequal
564 # despite the content of data
565 t1 = Task(self.tw, description="test task")
566 t2 = Task(self.tw, description="test task")
568 self.assertEqual(t1 == t2, False)
570 def test_hash_unsaved_tasks(self):
571 # Considered equal, it's the same object
572 t1 = Task(self.tw, description="test task")
574 self.assertEqual(hash(t1) == hash(t2), True)
576 def test_hash_same_task(self):
577 # Compare the hash of the task using two different objects
578 t1 = Task(self.tw, description="test task")
581 t2 = self.tw.tasks.get(uuid=t1['uuid'])
582 self.assertEqual(t1.__hash__(), t2.__hash__())
584 def test_adding_task_with_priority(self):
585 t = Task(self.tw, description="test task", priority="M")
588 def test_removing_priority_with_none(self):
589 t = Task(self.tw, description="test task", priority="L")
592 # Remove the priority mark
596 # Assert that priority is not there after saving
597 self.assertEqual(t['priority'], None)
599 def test_adding_task_with_due_time(self):
600 t = Task(self.tw, description="test task", due=datetime.datetime.now())
603 def test_removing_due_time_with_none(self):
604 t = Task(self.tw, description="test task", due=datetime.datetime.now())
607 # Remove the due timestamp
611 # Assert that due timestamp is no longer there
612 self.assertEqual(t['due'], None)
614 def test_modified_fields_new_task(self):
617 # This should be empty with new task
618 self.assertEqual(set(t._modified_fields), set())
621 t['description'] = "test task"
622 self.assertEqual(set(t._modified_fields), set(['description']))
624 t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14) # <3
625 self.assertEqual(set(t._modified_fields), set(['description', 'due']))
627 t['project'] = "test project"
628 self.assertEqual(set(t._modified_fields),
629 set(['description', 'due', 'project']))
631 # List of modified fields should clear out when saved
633 self.assertEqual(set(t._modified_fields), set())
635 # Reassigning the fields with the same values now should not produce
637 t['description'] = "test task"
638 t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14) # <3
639 t['project'] = "test project"
640 self.assertEqual(set(t._modified_fields), set())
642 def test_modified_fields_loaded_task(self):
646 t['description'] = "test task"
647 t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14) # <3
648 t['project'] = "test project"
650 dependency = Task(self.tw, description="dependency")
652 t['depends'] = set([dependency])
654 # List of modified fields should clear out when saved
656 self.assertEqual(set(t._modified_fields), set())
658 # Get the task by using a filter by UUID
659 t2 = self.tw.tasks.get(uuid=t['uuid'])
661 # Reassigning the fields with the same values now should not produce
663 t['description'] = "test task"
664 t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14) # <3
665 t['project'] = "test project"
666 t['depends'] = set([dependency])
667 self.assertEqual(set(t._modified_fields), set())
669 def test_modified_fields_not_affected_by_reading(self):
672 for field in TASK_STANDARD_ATTRS:
675 self.assertEqual(set(t._modified_fields), set())
677 def test_setting_read_only_attrs_through_init(self):
678 # Test that we are unable to set readonly attrs through __init__
679 for readonly_key in Task.read_only_fields:
680 kwargs = {'description': 'test task', readonly_key: 'value'}
681 self.assertRaises(RuntimeError,
682 lambda: Task(self.tw, **kwargs))
684 def test_setting_read_only_attrs_through_setitem(self):
685 # Test that we are unable to set readonly attrs through __init__
686 for readonly_key in Task.read_only_fields:
687 t = Task(self.tw, description='test task')
688 self.assertRaises(RuntimeError,
689 lambda: t.__setitem__(readonly_key, 'value'))
691 def test_saving_unmodified_task(self):
692 t = Task(self.tw, description="test task")
696 def test_adding_tag_by_appending(self):
697 t = Task(self.tw, description="test task", tags=['test1'])
699 t['tags'].add('test2')
701 self.assertEqual(t['tags'], set(['test1', 'test2']))
703 def test_adding_tag_twice(self):
704 t = Task(self.tw, description="test task", tags=['test1'])
706 t['tags'].add('test2')
707 t['tags'].add('test2')
709 self.assertEqual(t['tags'], set(['test1', 'test2']))
711 def test_adding_tag_by_appending_empty(self):
712 t = Task(self.tw, description="test task")
714 t['tags'].add('test')
716 self.assertEqual(t['tags'], set(['test']))
718 def test_serializers_returning_empty_string_for_none(self):
719 # Test that any serializer returns '' when passed None
721 serializers = [getattr(t, serializer_name) for serializer_name in
722 filter(lambda x: x.startswith('serialize_'), dir(t))]
723 for serializer in serializers:
724 self.assertEqual(serializer(None), '')
726 def test_deserializer_returning_empty_value_for_empty_string(self):
727 # Test that any deserializer returns empty value when passed ''
729 deserializers = [getattr(t, deserializer_name) for deserializer_name in
730 filter(lambda x: x.startswith('deserialize_'), dir(t))]
731 for deserializer in deserializers:
732 self.assertTrue(deserializer('') in (None, [], set()))
734 def test_normalizers_handling_none(self):
735 # Test that any normalizer can handle None as a valid value
738 for key in TASK_STANDARD_ATTRS:
739 t._normalize(key, None)
741 def test_recurrent_task_generation(self):
742 today = datetime.date.today()
743 t = Task(self.tw, description="brush teeth",
744 due=today, recur="daily")
746 self.assertEqual(len(self.tw.tasks.pending()), 2)
748 def test_spawned_task_parent(self):
749 today = datetime.date.today()
750 t = Task(self.tw, description="brush teeth",
751 due=today, recur="daily")
754 spawned = self.tw.tasks.pending().get(due=today)
755 assert spawned['parent'] == t
757 def test_modify_number_of_tasks_at_once(self):
758 for i in range(1, 100):
759 Task(self.tw, description="test task %d" % i, tags=['test']).save()
761 self.tw.execute_command(['+test', 'mod', 'unified', 'description'])
763 def test_return_all_from_executed_command(self):
764 Task(self.tw, description="test task", tags=['test']).save()
765 out, err, rc = self.tw.execute_command(['count'], return_all=True)
766 self.assertEqual(rc, 0)
768 def test_return_all_from_failed_executed_command(self):
769 Task(self.tw, description="test task", tags=['test']).save()
770 out, err, rc = self.tw.execute_command(['countinvalid'],
771 return_all=True, allow_failure=False)
772 self.assertNotEqual(rc, 0)
775 class TaskFromHookTest(TasklibTest):
777 input_add_data = six.StringIO(
778 '{"description":"Buy some milk",'
779 '"entry":"20141118T050231Z",'
780 '"status":"pending",'
781 '"start":"20141119T152233Z",'
782 '"uuid":"a360fc44-315c-4366-b70c-ea7e7520b749"}')
784 input_modify_data = six.StringIO(input_add_data.getvalue() + '\n' +
785 '{"description":"Buy some milk finally",'
786 '"entry":"20141118T050231Z",'
787 '"status":"completed",'
788 '"uuid":"a360fc44-315c-4366-b70c-ea7e7520b749"}')
790 exported_raw_data = (
792 '"due":"20150101T232323Z",'
793 '"description":"test task"}')
795 def test_setting_up_from_add_hook_input(self):
796 t = Task.from_input(input_file=self.input_add_data, backend=self.tw)
797 self.assertEqual(t['description'], "Buy some milk")
798 self.assertEqual(t.pending, True)
800 def test_setting_up_from_modified_hook_input(self):
801 t = Task.from_input(input_file=self.input_modify_data, modify=True,
803 self.assertEqual(t['description'], "Buy some milk finally")
804 self.assertEqual(t.pending, False)
805 self.assertEqual(t.completed, True)
807 self.assertEqual(t._original_data['status'], "pending")
808 self.assertEqual(t._original_data['description'], "Buy some milk")
809 self.assertEqual(set(t._modified_fields),
810 set(['status', 'description', 'start']))
812 def test_export_data(self):
813 t = Task(self.tw, description="test task",
815 due=pytz.utc.localize(datetime.datetime(2015,1,1,23,23,23)))
817 # Check that the output is a permutation of:
818 # {"project":"Home","description":"test task","due":"20150101232323Z"}
819 allowed_segments = self.exported_raw_data[1:-1].split(',')
821 '{' + ','.join(segments) + '}'
822 for segments in itertools.permutations(allowed_segments)
825 self.assertTrue(any(t.export_data() == expected
826 for expected in allowed_output))
828 class TimezoneAwareDatetimeTest(TasklibTest):
831 super(TimezoneAwareDatetimeTest, self).setUp()
832 self.zone = local_zone
833 self.localdate_naive = datetime.datetime(2015,2,2)
834 self.localtime_naive = datetime.datetime(2015,2,2,0,0,0)
835 self.localtime_aware = self.zone.localize(self.localtime_naive)
836 self.utctime_aware = self.localtime_aware.astimezone(pytz.utc)
838 def test_timezone_naive_datetime_setitem(self):
839 t = Task(self.tw, description="test task")
840 t['due'] = self.localtime_naive
841 self.assertEqual(t['due'], self.localtime_aware)
843 def test_timezone_naive_datetime_using_init(self):
844 t = Task(self.tw, description="test task", due=self.localtime_naive)
845 self.assertEqual(t['due'], self.localtime_aware)
847 def test_filter_by_naive_datetime(self):
848 t = Task(self.tw, description="task1", due=self.localtime_naive)
850 matching_tasks = self.tw.tasks.filter(due=self.localtime_naive)
851 self.assertEqual(len(matching_tasks), 1)
853 def test_serialize_naive_datetime(self):
854 t = Task(self.tw, description="task1", due=self.localtime_naive)
855 self.assertEqual(json.loads(t.export_data())['due'],
856 self.utctime_aware.strftime(DATE_FORMAT))
858 def test_timezone_naive_date_setitem(self):
859 t = Task(self.tw, description="test task")
860 t['due'] = self.localdate_naive
861 self.assertEqual(t['due'], self.localtime_aware)
863 def test_timezone_naive_date_using_init(self):
864 t = Task(self.tw, description="test task", due=self.localdate_naive)
865 self.assertEqual(t['due'], self.localtime_aware)
867 def test_filter_by_naive_date(self):
868 t = Task(self.tw, description="task1", due=self.localdate_naive)
870 matching_tasks = self.tw.tasks.filter(due=self.localdate_naive)
871 self.assertEqual(len(matching_tasks), 1)
873 def test_serialize_naive_date(self):
874 t = Task(self.tw, description="task1", due=self.localdate_naive)
875 self.assertEqual(json.loads(t.export_data())['due'],
876 self.utctime_aware.strftime(DATE_FORMAT))
878 def test_timezone_aware_datetime_setitem(self):
879 t = Task(self.tw, description="test task")
880 t['due'] = self.localtime_aware
881 self.assertEqual(t['due'], self.localtime_aware)
883 def test_timezone_aware_datetime_using_init(self):
884 t = Task(self.tw, description="test task", due=self.localtime_aware)
885 self.assertEqual(t['due'], self.localtime_aware)
887 def test_filter_by_aware_datetime(self):
888 t = Task(self.tw, description="task1", due=self.localtime_aware)
890 matching_tasks = self.tw.tasks.filter(due=self.localtime_aware)
891 self.assertEqual(len(matching_tasks), 1)
893 def test_serialize_aware_datetime(self):
894 t = Task(self.tw, description="task1", due=self.localtime_aware)
895 self.assertEqual(json.loads(t.export_data())['due'],
896 self.utctime_aware.strftime(DATE_FORMAT))
898 class DatetimeStringTest(TasklibTest):
900 def test_simple_now_conversion(self):
901 if self.tw.version < six.text_type('2.4.0'):
902 # Python2.6 does not support SkipTest. As a workaround
903 # mark the test as passed by exiting.
904 if getattr(unittest, 'SkipTest', None) is not None:
905 raise unittest.SkipTest()
909 t = Task(self.tw, description="test task", due="now")
910 now = local_zone.localize(datetime.datetime.now())
912 # Assert that both times are not more than 5 seconds apart
913 if sys.version_info < (2,7):
914 self.assertTrue(total_seconds_2_6(now - t['due']) < 5)
915 self.assertTrue(total_seconds_2_6(t['due'] - now) < 5)
917 self.assertTrue((now - t['due']).total_seconds() < 5)
918 self.assertTrue((t['due'] - now).total_seconds() < 5)
920 def test_simple_eoy_conversion(self):
921 if self.tw.version < six.text_type('2.4.0'):
922 # Python2.6 does not support SkipTest. As a workaround
923 # mark the test as passed by exiting.
924 if getattr(unittest, 'SkipTest', None) is not None:
925 raise unittest.SkipTest()
929 t = Task(self.tw, description="test task", due="eoy")
930 now = local_zone.localize(datetime.datetime.now())
931 eoy = local_zone.localize(datetime.datetime(
939 self.assertEqual(eoy, t['due'])
941 def test_complex_eoy_conversion(self):
942 if self.tw.version < six.text_type('2.4.0'):
943 # Python2.6 does not support SkipTest. As a workaround
944 # mark the test as passed by exiting.
945 if getattr(unittest, 'SkipTest', None) is not None:
946 raise unittest.SkipTest()
950 t = Task(self.tw, description="test task", due="eoy - 4 months")
951 now = local_zone.localize(datetime.datetime.now())
952 due_date = local_zone.localize(datetime.datetime(
959 )) - datetime.timedelta(0,4 * 30 * 86400)
960 self.assertEqual(due_date, t['due'])
962 def test_filtering_with_string_datetime(self):
963 if self.tw.version < six.text_type('2.4.0'):
964 # Python2.6 does not support SkipTest. As a workaround
965 # mark the test as passed by exiting.
966 if getattr(unittest, 'SkipTest', None) is not None:
967 raise unittest.SkipTest()
971 t = Task(self.tw, description="test task",
972 due=datetime.datetime.now() - datetime.timedelta(0,2))
974 self.assertEqual(len(self.tw.tasks.filter(due__before="now")), 1)
976 class AnnotationTest(TasklibTest):
979 super(AnnotationTest, self).setUp()
980 Task(self.tw, description="test task").save()
982 def test_adding_annotation(self):
983 task = self.tw.tasks.get()
984 task.add_annotation('test annotation')
985 self.assertEqual(len(task['annotations']), 1)
986 ann = task['annotations'][0]
987 self.assertEqual(ann['description'], 'test annotation')
989 def test_removing_annotation(self):
990 task = self.tw.tasks.get()
991 task.add_annotation('test annotation')
992 ann = task['annotations'][0]
994 self.assertEqual(len(task['annotations']), 0)
996 def test_removing_annotation_by_description(self):
997 task = self.tw.tasks.get()
998 task.add_annotation('test annotation')
999 task.remove_annotation('test annotation')
1000 self.assertEqual(len(task['annotations']), 0)
1002 def test_removing_annotation_by_obj(self):
1003 task = self.tw.tasks.get()
1004 task.add_annotation('test annotation')
1005 ann = task['annotations'][0]
1006 task.remove_annotation(ann)
1007 self.assertEqual(len(task['annotations']), 0)
1009 def test_annotation_after_modification(self):
1010 task = self.tw.tasks.get()
1011 task['project'] = 'test'
1012 task.add_annotation('I should really do this task')
1013 self.assertEqual(task['project'], 'test')
1015 self.assertEqual(task['project'], 'test')
1017 def test_serialize_annotations(self):
1018 # Test that serializing annotations is possible
1019 t = Task(self.tw, description="test")
1022 t.add_annotation("annotation1")
1023 t.add_annotation("annotation2")
1025 data = t._serialize('annotations', t._data['annotations'])
1027 self.assertEqual(len(data), 2)
1028 self.assertEqual(type(data[0]), dict)
1029 self.assertEqual(type(data[1]), dict)
1031 self.assertEqual(data[0]['description'], "annotation1")
1032 self.assertEqual(data[1]['description'], "annotation2")
1035 class UnicodeTest(TasklibTest):
1037 def test_unicode_task(self):
1038 Task(self.tw, description=six.u("†åßk")).save()
1041 def test_filter_by_unicode_task(self):
1042 Task(self.tw, description=six.u("†åßk")).save()
1043 tasks = self.tw.tasks.filter(description=six.u("†åßk"))
1044 self.assertEqual(len(tasks), 1)
1046 def test_non_unicode_task(self):
1047 Task(self.tw, description="test task").save()
1050 class ReadOnlyDictViewTest(unittest.TestCase):
1053 self.sample = dict(l=[1,2,3], d={'k':'v'})
1054 self.original_sample = copy.deepcopy(self.sample)
1055 self.view = ReadOnlyDictView(self.sample)
1057 def test_readonlydictview_getitem(self):
1059 self.assertEqual(l, self.sample['l'])
1061 # Assert that modification changed only copied value
1063 self.assertNotEqual(l, self.sample['l'])
1065 # Assert that viewed dict is not changed
1066 self.assertEqual(self.sample, self.original_sample)
1068 def test_readonlydictview_contains(self):
1069 self.assertEqual('l' in self.view, 'l' in self.sample)
1070 self.assertEqual('d' in self.view, 'd' in self.sample)
1071 self.assertEqual('k' in self.view, 'k' in self.sample)
1073 # Assert that viewed dict is not changed
1074 self.assertEqual(self.sample, self.original_sample)
1076 def test_readonlydictview_iter(self):
1077 self.assertEqual(list(k for k in self.view),
1078 list(k for k in self.sample))
1080 # Assert the view is correct after modification
1081 self.sample['new'] = 'value'
1082 self.assertEqual(list(k for k in self.view),
1083 list(k for k in self.sample))
1085 def test_readonlydictview_len(self):
1086 self.assertEqual(len(self.view), len(self.sample))
1088 # Assert the view is correct after modification
1089 self.sample['new'] = 'value'
1090 self.assertEqual(len(self.view), len(self.sample))
1092 def test_readonlydictview_get(self):
1093 l = self.view.get('l')
1094 self.assertEqual(l, self.sample.get('l'))
1096 # Assert that modification changed only copied value
1098 self.assertNotEqual(l, self.sample.get('l'))
1100 # Assert that viewed dict is not changed
1101 self.assertEqual(self.sample, self.original_sample)
1103 def test_readonlydict_items(self):
1104 view_items = self.view.items()
1105 sample_items = list(self.sample.items())
1106 self.assertEqual(view_items, sample_items)
1108 view_items.append('newkey')
1109 self.assertNotEqual(view_items, sample_items)
1110 self.assertEqual(self.sample, self.original_sample)
1112 def test_readonlydict_values(self):
1113 view_values = self.view.values()
1114 sample_values = list(self.sample.values())
1115 self.assertEqual(view_values, sample_values)
1117 view_list_item = list(filter(lambda x: type(x) is list,
1119 view_list_item.append(4)
1120 self.assertNotEqual(view_values, sample_values)
1121 self.assertEqual(self.sample, self.original_sample)
1124 class LazyUUIDTaskTest(TasklibTest):
1127 super(LazyUUIDTaskTest, self).setUp()
1129 self.stored = Task(self.tw, description="this is test task")
1132 self.lazy = LazyUUIDTask(self.tw, self.stored['uuid'])
1134 def test_uuid_non_conversion(self):
1135 assert self.stored['uuid'] == self.lazy['uuid']
1136 assert type(self.lazy) is LazyUUIDTask
1138 def test_lazy_explicit_conversion(self):
1139 assert type(self.lazy) is LazyUUIDTask
1141 assert type(self.lazy) is Task
1143 def test_conversion_key(self):
1144 assert self.stored['description'] == self.lazy['description']
1145 assert type(self.lazy) is Task
1147 def test_conversion_attribute(self):
1148 assert type(self.lazy) is LazyUUIDTask
1149 assert self.lazy.completed is False
1150 assert type(self.lazy) is Task
1152 def test_normal_to_lazy_equality(self):
1153 assert self.stored == self.lazy
1154 assert type(self.lazy) is LazyUUIDTask
1156 def test_lazy_to_lazy_equality(self):
1157 lazy1 = LazyUUIDTask(self.tw, self.stored['uuid'])
1158 lazy2 = LazyUUIDTask(self.tw, self.stored['uuid'])
1160 assert lazy1 == lazy2
1161 assert type(lazy1) is LazyUUIDTask
1162 assert type(lazy2) is LazyUUIDTask
1164 def test_lazy_in_queryset(self):
1165 tasks = self.tw.tasks.filter(uuid=self.stored['uuid'])
1167 assert self.lazy in tasks
1168 assert type(self.lazy) is LazyUUIDTask
1170 def test_lazy_saved(self):
1171 assert self.lazy.saved is True
1173 def test_lazy_modified(self):
1174 assert self.lazy.modified is False
1176 def test_lazy_modified_fields(self):
1177 assert self.lazy._modified_fields == set()
1180 class LazyUUIDTaskSetTest(TasklibTest):
1183 super(LazyUUIDTaskSetTest, self).setUp()
1185 self.task1 = Task(self.tw, description="task 1")
1186 self.task2 = Task(self.tw, description="task 2")
1187 self.task3 = Task(self.tw, description="task 3")
1199 self.lazy = LazyUUIDTaskSet(self.tw, self.uuids)
1201 def test_length(self):
1202 assert len(self.lazy) == 3
1203 assert type(self.lazy) is LazyUUIDTaskSet
1205 def test_contains(self):
1206 assert self.task1 in self.lazy
1207 assert self.task2 in self.lazy
1208 assert self.task3 in self.lazy
1209 assert type(self.lazy) is LazyUUIDTaskSet
1211 def test_eq_lazy(self):
1212 new_lazy = LazyUUIDTaskSet(self.tw, self.uuids)
1213 assert self.lazy == new_lazy
1214 assert not self.lazy != new_lazy
1215 assert type(self.lazy) is LazyUUIDTaskSet
1217 def test_eq_real(self):
1218 assert self.lazy == self.tw.tasks.all()
1219 assert self.tw.tasks.all() == self.lazy
1220 assert not self.lazy != self.tw.tasks.all()
1222 assert type(self.lazy) is LazyUUIDTaskSet
1224 def test_union(self):
1225 taskset = set([self.task1])
1226 lazyset = LazyUUIDTaskSet(
1228 (self.task2['uuid'], self.task3['uuid'])
1231 assert taskset | lazyset == self.lazy
1232 assert lazyset | taskset == self.lazy
1233 assert taskset.union(lazyset) == self.lazy
1234 assert lazyset.union(taskset) == self.lazy
1237 assert lazyset == self.lazy
1239 def test_difference(self):
1240 taskset = set([self.task1, self.task2])
1241 lazyset = LazyUUIDTaskSet(
1243 (self.task2['uuid'], self.task3['uuid'])
1246 assert taskset - lazyset == set([self.task1])
1247 assert lazyset - taskset == set([self.task3])
1248 assert taskset.difference(lazyset) == set([self.task1])
1249 assert lazyset.difference(taskset) == set([self.task3])
1252 assert lazyset == set([self.task3])
1254 def test_symmetric_difference(self):
1255 taskset = set([self.task1, self.task2])
1256 lazyset = LazyUUIDTaskSet(
1258 (self.task2['uuid'], self.task3['uuid'])
1261 assert taskset ^ lazyset == set([self.task1, self.task3])
1262 assert lazyset ^ taskset == set([self.task1, self.task3])
1263 assert taskset.symmetric_difference(lazyset) == set([self.task1, self.task3])
1264 assert lazyset.symmetric_difference(taskset) == set([self.task1, self.task3])
1267 assert lazyset == set([self.task1, self.task3])
1269 def test_intersection(self):
1270 taskset = set([self.task1, self.task2])
1271 lazyset = LazyUUIDTaskSet(
1273 (self.task2['uuid'], self.task3['uuid'])
1276 assert taskset & lazyset == set([self.task2])
1277 assert lazyset & taskset == set([self.task2])
1278 assert taskset.intersection(lazyset) == set([self.task2])
1279 assert lazyset.intersection(taskset) == set([self.task2])
1282 assert lazyset == set([self.task2])
1285 class TaskWarriorBackendTest(TasklibTest):
1287 def test_config(self):
1288 assert self.tw.config['nag'] == "You have more urgent tasks."
1289 assert self.tw.config['debug'] == "no"