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_add_data_recurring = six.StringIO(
785 '{"description":"Mow the lawn",'
786 '"entry":"20160210T224304Z",'
787 '"parent":"62da6227-519c-42c2-915d-dccada926ad7",'
789 '"status":"pending",'
790 '"uuid":"81305335-0237-49ff-8e87-b3cdc2369cec"}')
792 input_modify_data = six.StringIO(input_add_data.getvalue() + '\n' +
793 '{"description":"Buy some milk finally",'
794 '"entry":"20141118T050231Z",'
795 '"status":"completed",'
796 '"uuid":"a360fc44-315c-4366-b70c-ea7e7520b749"}')
798 exported_raw_data = (
800 '"due":"20150101T232323Z",'
801 '"description":"test task"}')
803 def test_setting_up_from_add_hook_input(self):
804 t = Task.from_input(input_file=self.input_add_data, backend=self.tw)
805 self.assertEqual(t['description'], "Buy some milk")
806 self.assertEqual(t.pending, True)
808 def test_setting_up_from_add_hook_input_recurring(self):
809 t = Task.from_input(input_file=self.input_add_data_recurring,
811 self.assertEqual(t['description'], "Mow the lawn")
812 self.assertEqual(t.pending, True)
814 def test_setting_up_from_modified_hook_input(self):
815 t = Task.from_input(input_file=self.input_modify_data, modify=True,
817 self.assertEqual(t['description'], "Buy some milk finally")
818 self.assertEqual(t.pending, False)
819 self.assertEqual(t.completed, True)
821 self.assertEqual(t._original_data['status'], "pending")
822 self.assertEqual(t._original_data['description'], "Buy some milk")
823 self.assertEqual(set(t._modified_fields),
824 set(['status', 'description', 'start']))
826 def test_export_data(self):
827 t = Task(self.tw, description="test task",
829 due=pytz.utc.localize(datetime.datetime(2015,1,1,23,23,23)))
831 # Check that the output is a permutation of:
832 # {"project":"Home","description":"test task","due":"20150101232323Z"}
833 allowed_segments = self.exported_raw_data[1:-1].split(',')
835 '{' + ','.join(segments) + '}'
836 for segments in itertools.permutations(allowed_segments)
839 self.assertTrue(any(t.export_data() == expected
840 for expected in allowed_output))
842 class TimezoneAwareDatetimeTest(TasklibTest):
845 super(TimezoneAwareDatetimeTest, self).setUp()
846 self.zone = local_zone
847 self.localdate_naive = datetime.datetime(2015,2,2)
848 self.localtime_naive = datetime.datetime(2015,2,2,0,0,0)
849 self.localtime_aware = self.zone.localize(self.localtime_naive)
850 self.utctime_aware = self.localtime_aware.astimezone(pytz.utc)
852 def test_timezone_naive_datetime_setitem(self):
853 t = Task(self.tw, description="test task")
854 t['due'] = self.localtime_naive
855 self.assertEqual(t['due'], self.localtime_aware)
857 def test_timezone_naive_datetime_using_init(self):
858 t = Task(self.tw, description="test task", due=self.localtime_naive)
859 self.assertEqual(t['due'], self.localtime_aware)
861 def test_filter_by_naive_datetime(self):
862 t = Task(self.tw, description="task1", due=self.localtime_naive)
864 matching_tasks = self.tw.tasks.filter(due=self.localtime_naive)
865 self.assertEqual(len(matching_tasks), 1)
867 def test_serialize_naive_datetime(self):
868 t = Task(self.tw, description="task1", due=self.localtime_naive)
869 self.assertEqual(json.loads(t.export_data())['due'],
870 self.utctime_aware.strftime(DATE_FORMAT))
872 def test_timezone_naive_date_setitem(self):
873 t = Task(self.tw, description="test task")
874 t['due'] = self.localdate_naive
875 self.assertEqual(t['due'], self.localtime_aware)
877 def test_timezone_naive_date_using_init(self):
878 t = Task(self.tw, description="test task", due=self.localdate_naive)
879 self.assertEqual(t['due'], self.localtime_aware)
881 def test_filter_by_naive_date(self):
882 t = Task(self.tw, description="task1", due=self.localdate_naive)
884 matching_tasks = self.tw.tasks.filter(due=self.localdate_naive)
885 self.assertEqual(len(matching_tasks), 1)
887 def test_serialize_naive_date(self):
888 t = Task(self.tw, description="task1", due=self.localdate_naive)
889 self.assertEqual(json.loads(t.export_data())['due'],
890 self.utctime_aware.strftime(DATE_FORMAT))
892 def test_timezone_aware_datetime_setitem(self):
893 t = Task(self.tw, description="test task")
894 t['due'] = self.localtime_aware
895 self.assertEqual(t['due'], self.localtime_aware)
897 def test_timezone_aware_datetime_using_init(self):
898 t = Task(self.tw, description="test task", due=self.localtime_aware)
899 self.assertEqual(t['due'], self.localtime_aware)
901 def test_filter_by_aware_datetime(self):
902 t = Task(self.tw, description="task1", due=self.localtime_aware)
904 matching_tasks = self.tw.tasks.filter(due=self.localtime_aware)
905 self.assertEqual(len(matching_tasks), 1)
907 def test_serialize_aware_datetime(self):
908 t = Task(self.tw, description="task1", due=self.localtime_aware)
909 self.assertEqual(json.loads(t.export_data())['due'],
910 self.utctime_aware.strftime(DATE_FORMAT))
912 class DatetimeStringTest(TasklibTest):
914 def test_simple_now_conversion(self):
915 if self.tw.version < six.text_type('2.4.0'):
916 # Python2.6 does not support SkipTest. As a workaround
917 # mark the test as passed by exiting.
918 if getattr(unittest, 'SkipTest', None) is not None:
919 raise unittest.SkipTest()
923 t = Task(self.tw, description="test task", due="now")
924 now = local_zone.localize(datetime.datetime.now())
926 # Assert that both times are not more than 5 seconds apart
927 if sys.version_info < (2,7):
928 self.assertTrue(total_seconds_2_6(now - t['due']) < 5)
929 self.assertTrue(total_seconds_2_6(t['due'] - now) < 5)
931 self.assertTrue((now - t['due']).total_seconds() < 5)
932 self.assertTrue((t['due'] - now).total_seconds() < 5)
934 def test_simple_eoy_conversion(self):
935 if self.tw.version < six.text_type('2.4.0'):
936 # Python2.6 does not support SkipTest. As a workaround
937 # mark the test as passed by exiting.
938 if getattr(unittest, 'SkipTest', None) is not None:
939 raise unittest.SkipTest()
943 t = Task(self.tw, description="test task", due="eoy")
944 now = local_zone.localize(datetime.datetime.now())
945 eoy = local_zone.localize(datetime.datetime(
953 self.assertEqual(eoy, t['due'])
955 def test_complex_eoy_conversion(self):
956 if self.tw.version < six.text_type('2.4.0'):
957 # Python2.6 does not support SkipTest. As a workaround
958 # mark the test as passed by exiting.
959 if getattr(unittest, 'SkipTest', None) is not None:
960 raise unittest.SkipTest()
964 t = Task(self.tw, description="test task", due="eoy - 4 months")
965 now = local_zone.localize(datetime.datetime.now())
966 due_date = local_zone.localize(datetime.datetime(
973 )) - datetime.timedelta(0,4 * 30 * 86400)
974 self.assertEqual(due_date, t['due'])
976 def test_filtering_with_string_datetime(self):
977 if self.tw.version < six.text_type('2.4.0'):
978 # Python2.6 does not support SkipTest. As a workaround
979 # mark the test as passed by exiting.
980 if getattr(unittest, 'SkipTest', None) is not None:
981 raise unittest.SkipTest()
985 t = Task(self.tw, description="test task",
986 due=datetime.datetime.now() - datetime.timedelta(0,2))
988 self.assertEqual(len(self.tw.tasks.filter(due__before="now")), 1)
990 class AnnotationTest(TasklibTest):
993 super(AnnotationTest, self).setUp()
994 Task(self.tw, description="test task").save()
996 def test_adding_annotation(self):
997 task = self.tw.tasks.get()
998 task.add_annotation('test annotation')
999 self.assertEqual(len(task['annotations']), 1)
1000 ann = task['annotations'][0]
1001 self.assertEqual(ann['description'], 'test annotation')
1003 def test_removing_annotation(self):
1004 task = self.tw.tasks.get()
1005 task.add_annotation('test annotation')
1006 ann = task['annotations'][0]
1008 self.assertEqual(len(task['annotations']), 0)
1010 def test_removing_annotation_by_description(self):
1011 task = self.tw.tasks.get()
1012 task.add_annotation('test annotation')
1013 task.remove_annotation('test annotation')
1014 self.assertEqual(len(task['annotations']), 0)
1016 def test_removing_annotation_by_obj(self):
1017 task = self.tw.tasks.get()
1018 task.add_annotation('test annotation')
1019 ann = task['annotations'][0]
1020 task.remove_annotation(ann)
1021 self.assertEqual(len(task['annotations']), 0)
1023 def test_annotation_after_modification(self):
1024 task = self.tw.tasks.get()
1025 task['project'] = 'test'
1026 task.add_annotation('I should really do this task')
1027 self.assertEqual(task['project'], 'test')
1029 self.assertEqual(task['project'], 'test')
1031 def test_serialize_annotations(self):
1032 # Test that serializing annotations is possible
1033 t = Task(self.tw, description="test")
1036 t.add_annotation("annotation1")
1037 t.add_annotation("annotation2")
1039 data = t._serialize('annotations', t._data['annotations'])
1041 self.assertEqual(len(data), 2)
1042 self.assertEqual(type(data[0]), dict)
1043 self.assertEqual(type(data[1]), dict)
1045 self.assertEqual(data[0]['description'], "annotation1")
1046 self.assertEqual(data[1]['description'], "annotation2")
1049 class UnicodeTest(TasklibTest):
1051 def test_unicode_task(self):
1052 Task(self.tw, description=six.u("†åßk")).save()
1055 def test_filter_by_unicode_task(self):
1056 Task(self.tw, description=six.u("†åßk")).save()
1057 tasks = self.tw.tasks.filter(description=six.u("†åßk"))
1058 self.assertEqual(len(tasks), 1)
1060 def test_non_unicode_task(self):
1061 Task(self.tw, description="test task").save()
1064 class ReadOnlyDictViewTest(unittest.TestCase):
1067 self.sample = dict(l=[1,2,3], d={'k':'v'})
1068 self.original_sample = copy.deepcopy(self.sample)
1069 self.view = ReadOnlyDictView(self.sample)
1071 def test_readonlydictview_getitem(self):
1073 self.assertEqual(l, self.sample['l'])
1075 # Assert that modification changed only copied value
1077 self.assertNotEqual(l, self.sample['l'])
1079 # Assert that viewed dict is not changed
1080 self.assertEqual(self.sample, self.original_sample)
1082 def test_readonlydictview_contains(self):
1083 self.assertEqual('l' in self.view, 'l' in self.sample)
1084 self.assertEqual('d' in self.view, 'd' in self.sample)
1085 self.assertEqual('k' in self.view, 'k' in self.sample)
1087 # Assert that viewed dict is not changed
1088 self.assertEqual(self.sample, self.original_sample)
1090 def test_readonlydictview_iter(self):
1091 self.assertEqual(list(k for k in self.view),
1092 list(k for k in self.sample))
1094 # Assert the view is correct after modification
1095 self.sample['new'] = 'value'
1096 self.assertEqual(list(k for k in self.view),
1097 list(k for k in self.sample))
1099 def test_readonlydictview_len(self):
1100 self.assertEqual(len(self.view), len(self.sample))
1102 # Assert the view is correct after modification
1103 self.sample['new'] = 'value'
1104 self.assertEqual(len(self.view), len(self.sample))
1106 def test_readonlydictview_get(self):
1107 l = self.view.get('l')
1108 self.assertEqual(l, self.sample.get('l'))
1110 # Assert that modification changed only copied value
1112 self.assertNotEqual(l, self.sample.get('l'))
1114 # Assert that viewed dict is not changed
1115 self.assertEqual(self.sample, self.original_sample)
1117 def test_readonlydict_items(self):
1118 view_items = self.view.items()
1119 sample_items = list(self.sample.items())
1120 self.assertEqual(view_items, sample_items)
1122 view_items.append('newkey')
1123 self.assertNotEqual(view_items, sample_items)
1124 self.assertEqual(self.sample, self.original_sample)
1126 def test_readonlydict_values(self):
1127 view_values = self.view.values()
1128 sample_values = list(self.sample.values())
1129 self.assertEqual(view_values, sample_values)
1131 view_list_item = list(filter(lambda x: type(x) is list,
1133 view_list_item.append(4)
1134 self.assertNotEqual(view_values, sample_values)
1135 self.assertEqual(self.sample, self.original_sample)
1138 class LazyUUIDTaskTest(TasklibTest):
1141 super(LazyUUIDTaskTest, self).setUp()
1143 self.stored = Task(self.tw, description="this is test task")
1146 self.lazy = LazyUUIDTask(self.tw, self.stored['uuid'])
1148 def test_uuid_non_conversion(self):
1149 assert self.stored['uuid'] == self.lazy['uuid']
1150 assert type(self.lazy) is LazyUUIDTask
1152 def test_lazy_explicit_conversion(self):
1153 assert type(self.lazy) is LazyUUIDTask
1155 assert type(self.lazy) is Task
1157 def test_conversion_key(self):
1158 assert self.stored['description'] == self.lazy['description']
1159 assert type(self.lazy) is Task
1161 def test_conversion_attribute(self):
1162 assert type(self.lazy) is LazyUUIDTask
1163 assert self.lazy.completed is False
1164 assert type(self.lazy) is Task
1166 def test_normal_to_lazy_equality(self):
1167 assert self.stored == self.lazy
1168 assert not self.stored != self.lazy
1169 assert type(self.lazy) is LazyUUIDTask
1171 def test_lazy_to_lazy_equality(self):
1172 lazy1 = LazyUUIDTask(self.tw, self.stored['uuid'])
1173 lazy2 = LazyUUIDTask(self.tw, self.stored['uuid'])
1175 assert lazy1 == lazy2
1176 assert not lazy1 != lazy2
1177 assert type(lazy1) is LazyUUIDTask
1178 assert type(lazy2) is LazyUUIDTask
1180 def test_normal_to_lazy_inequality(self):
1181 # Create a different UUID by changing the last letter
1182 wrong_uuid = self.stored['uuid']
1183 wrong_uuid = wrong_uuid[:-1] + ('a' if wrong_uuid[-1] != 'a' else 'b')
1185 wrong_lazy = LazyUUIDTask(self.tw, wrong_uuid)
1187 assert not self.stored == wrong_lazy
1188 assert self.stored != wrong_lazy
1189 assert type(wrong_lazy) is LazyUUIDTask
1191 def test_lazy_to_lazy_inequality(self):
1192 # Create a different UUID by changing the last letter
1193 wrong_uuid = self.stored['uuid']
1194 wrong_uuid = wrong_uuid[:-1] + ('a' if wrong_uuid[-1] != 'a' else 'b')
1196 lazy1 = LazyUUIDTask(self.tw, self.stored['uuid'])
1197 lazy2 = LazyUUIDTask(self.tw, wrong_uuid)
1199 assert not lazy1 == lazy2
1200 assert lazy1 != lazy2
1201 assert type(lazy1) is LazyUUIDTask
1202 assert type(lazy2) is LazyUUIDTask
1204 def test_lazy_in_queryset(self):
1205 tasks = self.tw.tasks.filter(uuid=self.stored['uuid'])
1207 assert self.lazy in tasks
1208 assert type(self.lazy) is LazyUUIDTask
1210 def test_lazy_saved(self):
1211 assert self.lazy.saved is True
1213 def test_lazy_modified(self):
1214 assert self.lazy.modified is False
1216 def test_lazy_modified_fields(self):
1217 assert self.lazy._modified_fields == set()
1220 class LazyUUIDTaskSetTest(TasklibTest):
1223 super(LazyUUIDTaskSetTest, self).setUp()
1225 self.task1 = Task(self.tw, description="task 1")
1226 self.task2 = Task(self.tw, description="task 2")
1227 self.task3 = Task(self.tw, description="task 3")
1239 self.lazy = LazyUUIDTaskSet(self.tw, self.uuids)
1241 def test_length(self):
1242 assert len(self.lazy) == 3
1243 assert type(self.lazy) is LazyUUIDTaskSet
1245 def test_contains(self):
1246 assert self.task1 in self.lazy
1247 assert self.task2 in self.lazy
1248 assert self.task3 in self.lazy
1249 assert type(self.lazy) is LazyUUIDTaskSet
1251 def test_eq_lazy(self):
1252 new_lazy = LazyUUIDTaskSet(self.tw, self.uuids)
1253 assert self.lazy == new_lazy
1254 assert not self.lazy != new_lazy
1255 assert type(self.lazy) is LazyUUIDTaskSet
1257 def test_eq_real(self):
1258 assert self.lazy == self.tw.tasks.all()
1259 assert self.tw.tasks.all() == self.lazy
1260 assert not self.lazy != self.tw.tasks.all()
1262 assert type(self.lazy) is LazyUUIDTaskSet
1264 def test_union(self):
1265 taskset = set([self.task1])
1266 lazyset = LazyUUIDTaskSet(
1268 (self.task2['uuid'], self.task3['uuid'])
1271 assert taskset | lazyset == self.lazy
1272 assert lazyset | taskset == self.lazy
1273 assert taskset.union(lazyset) == self.lazy
1274 assert lazyset.union(taskset) == self.lazy
1277 assert lazyset == self.lazy
1279 def test_difference(self):
1280 taskset = set([self.task1, self.task2])
1281 lazyset = LazyUUIDTaskSet(
1283 (self.task2['uuid'], self.task3['uuid'])
1286 assert taskset - lazyset == set([self.task1])
1287 assert lazyset - taskset == set([self.task3])
1288 assert taskset.difference(lazyset) == set([self.task1])
1289 assert lazyset.difference(taskset) == set([self.task3])
1292 assert lazyset == set([self.task3])
1294 def test_symmetric_difference(self):
1295 taskset = set([self.task1, self.task2])
1296 lazyset = LazyUUIDTaskSet(
1298 (self.task2['uuid'], self.task3['uuid'])
1301 assert taskset ^ lazyset == set([self.task1, self.task3])
1302 assert lazyset ^ taskset == set([self.task1, self.task3])
1303 assert taskset.symmetric_difference(lazyset) == set([self.task1, self.task3])
1304 assert lazyset.symmetric_difference(taskset) == set([self.task1, self.task3])
1307 assert lazyset == set([self.task1, self.task3])
1309 def test_intersection(self):
1310 taskset = set([self.task1, self.task2])
1311 lazyset = LazyUUIDTaskSet(
1313 (self.task2['uuid'], self.task3['uuid'])
1316 assert taskset & lazyset == set([self.task2])
1317 assert lazyset & taskset == set([self.task2])
1318 assert taskset.intersection(lazyset) == set([self.task2])
1319 assert lazyset.intersection(taskset) == set([self.task2])
1322 assert lazyset == set([self.task2])
1325 class TaskWarriorBackendTest(TasklibTest):
1327 def test_config(self):
1328 assert self.tw.config['nag'] == "You have more urgent tasks."
1329 assert self.tw.config['debug'] == "no"