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'].append('test2')
701 self.assertEqual(t['tags'], ['test1', 'test2'])
703 def test_adding_tag_by_appending_empty(self):
704 t = Task(self.tw, description="test task")
706 t['tags'].append('test')
708 self.assertEqual(t['tags'], ['test'])
710 def test_serializers_returning_empty_string_for_none(self):
711 # Test that any serializer returns '' when passed None
713 serializers = [getattr(t, serializer_name) for serializer_name in
714 filter(lambda x: x.startswith('serialize_'), dir(t))]
715 for serializer in serializers:
716 self.assertEqual(serializer(None), '')
718 def test_deserializer_returning_empty_value_for_empty_string(self):
719 # Test that any deserializer returns empty value when passed ''
721 deserializers = [getattr(t, deserializer_name) for deserializer_name in
722 filter(lambda x: x.startswith('deserialize_'), dir(t))]
723 for deserializer in deserializers:
724 self.assertTrue(deserializer('') in (None, [], set()))
726 def test_normalizers_handling_none(self):
727 # Test that any normalizer can handle None as a valid value
730 for key in TASK_STANDARD_ATTRS:
731 t._normalize(key, None)
733 def test_recurrent_task_generation(self):
734 today = datetime.date.today()
735 t = Task(self.tw, description="brush teeth",
736 due=today, recur="daily")
738 self.assertEqual(len(self.tw.tasks.pending()), 2)
740 def test_modify_number_of_tasks_at_once(self):
741 for i in range(1, 100):
742 Task(self.tw, description="test task %d" % i, tags=['test']).save()
744 self.tw.execute_command(['+test', 'mod', 'unified', 'description'])
746 def test_return_all_from_executed_command(self):
747 Task(self.tw, description="test task", tags=['test']).save()
748 out, err, rc = self.tw.execute_command(['count'], return_all=True)
749 self.assertEqual(rc, 0)
751 def test_return_all_from_failed_executed_command(self):
752 Task(self.tw, description="test task", tags=['test']).save()
753 out, err, rc = self.tw.execute_command(['countinvalid'],
754 return_all=True, allow_failure=False)
755 self.assertNotEqual(rc, 0)
758 class TaskFromHookTest(TasklibTest):
760 input_add_data = six.StringIO(
761 '{"description":"Buy some milk",'
762 '"entry":"20141118T050231Z",'
763 '"status":"pending",'
764 '"start":"20141119T152233Z",'
765 '"uuid":"a360fc44-315c-4366-b70c-ea7e7520b749"}')
767 input_modify_data = six.StringIO(input_add_data.getvalue() + '\n' +
768 '{"description":"Buy some milk finally",'
769 '"entry":"20141118T050231Z",'
770 '"status":"completed",'
771 '"uuid":"a360fc44-315c-4366-b70c-ea7e7520b749"}')
773 exported_raw_data = (
775 '"due":"20150101T232323Z",'
776 '"description":"test task"}')
778 def test_setting_up_from_add_hook_input(self):
779 t = Task.from_input(input_file=self.input_add_data, backend=self.tw)
780 self.assertEqual(t['description'], "Buy some milk")
781 self.assertEqual(t.pending, True)
783 def test_setting_up_from_modified_hook_input(self):
784 t = Task.from_input(input_file=self.input_modify_data, modify=True,
786 self.assertEqual(t['description'], "Buy some milk finally")
787 self.assertEqual(t.pending, False)
788 self.assertEqual(t.completed, True)
790 self.assertEqual(t._original_data['status'], "pending")
791 self.assertEqual(t._original_data['description'], "Buy some milk")
792 self.assertEqual(set(t._modified_fields),
793 set(['status', 'description', 'start']))
795 def test_export_data(self):
796 t = Task(self.tw, description="test task",
798 due=pytz.utc.localize(datetime.datetime(2015,1,1,23,23,23)))
800 # Check that the output is a permutation of:
801 # {"project":"Home","description":"test task","due":"20150101232323Z"}
802 allowed_segments = self.exported_raw_data[1:-1].split(',')
804 '{' + ','.join(segments) + '}'
805 for segments in itertools.permutations(allowed_segments)
808 self.assertTrue(any(t.export_data() == expected
809 for expected in allowed_output))
811 class TimezoneAwareDatetimeTest(TasklibTest):
814 super(TimezoneAwareDatetimeTest, self).setUp()
815 self.zone = local_zone
816 self.localdate_naive = datetime.datetime(2015,2,2)
817 self.localtime_naive = datetime.datetime(2015,2,2,0,0,0)
818 self.localtime_aware = self.zone.localize(self.localtime_naive)
819 self.utctime_aware = self.localtime_aware.astimezone(pytz.utc)
821 def test_timezone_naive_datetime_setitem(self):
822 t = Task(self.tw, description="test task")
823 t['due'] = self.localtime_naive
824 self.assertEqual(t['due'], self.localtime_aware)
826 def test_timezone_naive_datetime_using_init(self):
827 t = Task(self.tw, description="test task", due=self.localtime_naive)
828 self.assertEqual(t['due'], self.localtime_aware)
830 def test_filter_by_naive_datetime(self):
831 t = Task(self.tw, description="task1", due=self.localtime_naive)
833 matching_tasks = self.tw.tasks.filter(due=self.localtime_naive)
834 self.assertEqual(len(matching_tasks), 1)
836 def test_serialize_naive_datetime(self):
837 t = Task(self.tw, description="task1", due=self.localtime_naive)
838 self.assertEqual(json.loads(t.export_data())['due'],
839 self.utctime_aware.strftime(DATE_FORMAT))
841 def test_timezone_naive_date_setitem(self):
842 t = Task(self.tw, description="test task")
843 t['due'] = self.localdate_naive
844 self.assertEqual(t['due'], self.localtime_aware)
846 def test_timezone_naive_date_using_init(self):
847 t = Task(self.tw, description="test task", due=self.localdate_naive)
848 self.assertEqual(t['due'], self.localtime_aware)
850 def test_filter_by_naive_date(self):
851 t = Task(self.tw, description="task1", due=self.localdate_naive)
853 matching_tasks = self.tw.tasks.filter(due=self.localdate_naive)
854 self.assertEqual(len(matching_tasks), 1)
856 def test_serialize_naive_date(self):
857 t = Task(self.tw, description="task1", due=self.localdate_naive)
858 self.assertEqual(json.loads(t.export_data())['due'],
859 self.utctime_aware.strftime(DATE_FORMAT))
861 def test_timezone_aware_datetime_setitem(self):
862 t = Task(self.tw, description="test task")
863 t['due'] = self.localtime_aware
864 self.assertEqual(t['due'], self.localtime_aware)
866 def test_timezone_aware_datetime_using_init(self):
867 t = Task(self.tw, description="test task", due=self.localtime_aware)
868 self.assertEqual(t['due'], self.localtime_aware)
870 def test_filter_by_aware_datetime(self):
871 t = Task(self.tw, description="task1", due=self.localtime_aware)
873 matching_tasks = self.tw.tasks.filter(due=self.localtime_aware)
874 self.assertEqual(len(matching_tasks), 1)
876 def test_serialize_aware_datetime(self):
877 t = Task(self.tw, description="task1", due=self.localtime_aware)
878 self.assertEqual(json.loads(t.export_data())['due'],
879 self.utctime_aware.strftime(DATE_FORMAT))
881 class DatetimeStringTest(TasklibTest):
883 def test_simple_now_conversion(self):
884 if self.tw.version < six.text_type('2.4.0'):
885 # Python2.6 does not support SkipTest. As a workaround
886 # mark the test as passed by exiting.
887 if getattr(unittest, 'SkipTest', None) is not None:
888 raise unittest.SkipTest()
892 t = Task(self.tw, description="test task", due="now")
893 now = local_zone.localize(datetime.datetime.now())
895 # Assert that both times are not more than 5 seconds apart
896 if sys.version_info < (2,7):
897 self.assertTrue(total_seconds_2_6(now - t['due']) < 5)
898 self.assertTrue(total_seconds_2_6(t['due'] - now) < 5)
900 self.assertTrue((now - t['due']).total_seconds() < 5)
901 self.assertTrue((t['due'] - now).total_seconds() < 5)
903 def test_simple_eoy_conversion(self):
904 if self.tw.version < six.text_type('2.4.0'):
905 # Python2.6 does not support SkipTest. As a workaround
906 # mark the test as passed by exiting.
907 if getattr(unittest, 'SkipTest', None) is not None:
908 raise unittest.SkipTest()
912 t = Task(self.tw, description="test task", due="eoy")
913 now = local_zone.localize(datetime.datetime.now())
914 eoy = local_zone.localize(datetime.datetime(
922 self.assertEqual(eoy, t['due'])
924 def test_complex_eoy_conversion(self):
925 if self.tw.version < six.text_type('2.4.0'):
926 # Python2.6 does not support SkipTest. As a workaround
927 # mark the test as passed by exiting.
928 if getattr(unittest, 'SkipTest', None) is not None:
929 raise unittest.SkipTest()
933 t = Task(self.tw, description="test task", due="eoy - 4 months")
934 now = local_zone.localize(datetime.datetime.now())
935 due_date = local_zone.localize(datetime.datetime(
942 )) - datetime.timedelta(0,4 * 30 * 86400)
943 self.assertEqual(due_date, t['due'])
945 def test_filtering_with_string_datetime(self):
946 if self.tw.version < six.text_type('2.4.0'):
947 # Python2.6 does not support SkipTest. As a workaround
948 # mark the test as passed by exiting.
949 if getattr(unittest, 'SkipTest', None) is not None:
950 raise unittest.SkipTest()
954 t = Task(self.tw, description="test task",
955 due=datetime.datetime.now() - datetime.timedelta(0,2))
957 self.assertEqual(len(self.tw.tasks.filter(due__before="now")), 1)
959 class AnnotationTest(TasklibTest):
962 super(AnnotationTest, self).setUp()
963 Task(self.tw, description="test task").save()
965 def test_adding_annotation(self):
966 task = self.tw.tasks.get()
967 task.add_annotation('test annotation')
968 self.assertEqual(len(task['annotations']), 1)
969 ann = task['annotations'][0]
970 self.assertEqual(ann['description'], 'test annotation')
972 def test_removing_annotation(self):
973 task = self.tw.tasks.get()
974 task.add_annotation('test annotation')
975 ann = task['annotations'][0]
977 self.assertEqual(len(task['annotations']), 0)
979 def test_removing_annotation_by_description(self):
980 task = self.tw.tasks.get()
981 task.add_annotation('test annotation')
982 task.remove_annotation('test annotation')
983 self.assertEqual(len(task['annotations']), 0)
985 def test_removing_annotation_by_obj(self):
986 task = self.tw.tasks.get()
987 task.add_annotation('test annotation')
988 ann = task['annotations'][0]
989 task.remove_annotation(ann)
990 self.assertEqual(len(task['annotations']), 0)
992 def test_annotation_after_modification(self):
993 task = self.tw.tasks.get()
994 task['project'] = 'test'
995 task.add_annotation('I should really do this task')
996 self.assertEqual(task['project'], 'test')
998 self.assertEqual(task['project'], 'test')
1000 def test_serialize_annotations(self):
1001 # Test that serializing annotations is possible
1002 t = Task(self.tw, description="test")
1005 t.add_annotation("annotation1")
1006 t.add_annotation("annotation2")
1008 data = t._serialize('annotations', t._data['annotations'])
1010 self.assertEqual(len(data), 2)
1011 self.assertEqual(type(data[0]), dict)
1012 self.assertEqual(type(data[1]), dict)
1014 self.assertEqual(data[0]['description'], "annotation1")
1015 self.assertEqual(data[1]['description'], "annotation2")
1018 class UnicodeTest(TasklibTest):
1020 def test_unicode_task(self):
1021 Task(self.tw, description=six.u("†åßk")).save()
1024 def test_filter_by_unicode_task(self):
1025 Task(self.tw, description=six.u("†åßk")).save()
1026 tasks = self.tw.tasks.filter(description=six.u("†åßk"))
1027 self.assertEqual(len(tasks), 1)
1029 def test_non_unicode_task(self):
1030 Task(self.tw, description="test task").save()
1033 class ReadOnlyDictViewTest(unittest.TestCase):
1036 self.sample = dict(l=[1,2,3], d={'k':'v'})
1037 self.original_sample = copy.deepcopy(self.sample)
1038 self.view = ReadOnlyDictView(self.sample)
1040 def test_readonlydictview_getitem(self):
1042 self.assertEqual(l, self.sample['l'])
1044 # Assert that modification changed only copied value
1046 self.assertNotEqual(l, self.sample['l'])
1048 # Assert that viewed dict is not changed
1049 self.assertEqual(self.sample, self.original_sample)
1051 def test_readonlydictview_contains(self):
1052 self.assertEqual('l' in self.view, 'l' in self.sample)
1053 self.assertEqual('d' in self.view, 'd' in self.sample)
1054 self.assertEqual('k' in self.view, 'k' in self.sample)
1056 # Assert that viewed dict is not changed
1057 self.assertEqual(self.sample, self.original_sample)
1059 def test_readonlydictview_iter(self):
1060 self.assertEqual(list(k for k in self.view),
1061 list(k for k in self.sample))
1063 # Assert the view is correct after modification
1064 self.sample['new'] = 'value'
1065 self.assertEqual(list(k for k in self.view),
1066 list(k for k in self.sample))
1068 def test_readonlydictview_len(self):
1069 self.assertEqual(len(self.view), len(self.sample))
1071 # Assert the view is correct after modification
1072 self.sample['new'] = 'value'
1073 self.assertEqual(len(self.view), len(self.sample))
1075 def test_readonlydictview_get(self):
1076 l = self.view.get('l')
1077 self.assertEqual(l, self.sample.get('l'))
1079 # Assert that modification changed only copied value
1081 self.assertNotEqual(l, self.sample.get('l'))
1083 # Assert that viewed dict is not changed
1084 self.assertEqual(self.sample, self.original_sample)
1086 def test_readonlydict_items(self):
1087 view_items = self.view.items()
1088 sample_items = list(self.sample.items())
1089 self.assertEqual(view_items, sample_items)
1091 view_items.append('newkey')
1092 self.assertNotEqual(view_items, sample_items)
1093 self.assertEqual(self.sample, self.original_sample)
1095 def test_readonlydict_values(self):
1096 view_values = self.view.values()
1097 sample_values = list(self.sample.values())
1098 self.assertEqual(view_values, sample_values)
1100 view_list_item = list(filter(lambda x: type(x) is list,
1102 view_list_item.append(4)
1103 self.assertNotEqual(view_values, sample_values)
1104 self.assertEqual(self.sample, self.original_sample)
1107 class LazyUUIDTaskTest(TasklibTest):
1110 super(LazyUUIDTaskTest, self).setUp()
1112 self.stored = Task(self.tw, description="this is test task")
1115 self.lazy = LazyUUIDTask(self.tw, self.stored['uuid'])
1117 def test_uuid_non_conversion(self):
1118 assert self.stored['uuid'] == self.lazy['uuid']
1119 assert type(self.lazy) is LazyUUIDTask
1121 def test_lazy_explicit_conversion(self):
1122 assert type(self.lazy) is LazyUUIDTask
1124 assert type(self.lazy) is Task
1126 def test_conversion_key(self):
1127 assert self.stored['description'] == self.lazy['description']
1128 assert type(self.lazy) is Task
1130 def test_conversion_attribute(self):
1131 assert type(self.lazy) is LazyUUIDTask
1132 assert self.lazy.completed is False
1133 assert type(self.lazy) is Task
1135 def test_normal_to_lazy_equality(self):
1136 assert self.stored == self.lazy
1137 assert type(self.lazy) is LazyUUIDTask
1139 def test_lazy_to_lazy_equality(self):
1140 lazy1 = LazyUUIDTask(self.tw, self.stored['uuid'])
1141 lazy2 = LazyUUIDTask(self.tw, self.stored['uuid'])
1143 assert lazy1 == lazy2
1144 assert type(lazy1) is LazyUUIDTask
1145 assert type(lazy2) is LazyUUIDTask
1147 def test_lazy_in_queryset(self):
1148 tasks = self.tw.tasks.filter(uuid=self.stored['uuid'])
1150 assert self.lazy in tasks
1151 assert type(self.lazy) is LazyUUIDTask
1153 def test_lazy_saved(self):
1154 assert self.lazy.saved is True
1156 def test_lazy_modified(self):
1157 assert self.lazy.modified is False
1159 def test_lazy_modified_fields(self):
1160 assert self.lazy._modified_fields == set()
1163 class LazyUUIDTaskSetTest(TasklibTest):
1166 super(LazyUUIDTaskSetTest, self).setUp()
1168 self.task1 = Task(self.tw, description="task 1")
1169 self.task2 = Task(self.tw, description="task 2")
1170 self.task3 = Task(self.tw, description="task 3")
1182 self.lazy = LazyUUIDTaskSet(self.tw, self.uuids)
1184 def test_length(self):
1185 assert len(self.lazy) == 3
1186 assert type(self.lazy) is LazyUUIDTaskSet
1188 def test_contains(self):
1189 assert self.task1 in self.lazy
1190 assert self.task2 in self.lazy
1191 assert self.task3 in self.lazy
1192 assert type(self.lazy) is LazyUUIDTaskSet
1194 def test_eq_lazy(self):
1195 new_lazy = LazyUUIDTaskSet(self.tw, self.uuids)
1196 assert self.lazy == new_lazy
1197 assert not self.lazy != new_lazy
1198 assert type(self.lazy) is LazyUUIDTaskSet
1200 def test_eq_real(self):
1201 assert self.lazy == self.tw.tasks.all()
1202 assert self.tw.tasks.all() == self.lazy
1203 assert not self.lazy != self.tw.tasks.all()
1205 assert type(self.lazy) is LazyUUIDTaskSet
1207 def test_union(self):
1208 taskset = set([self.task1])
1209 lazyset = LazyUUIDTaskSet(
1211 (self.task2['uuid'], self.task3['uuid'])
1214 assert taskset | lazyset == self.lazy
1215 assert lazyset | taskset == self.lazy
1216 assert taskset.union(lazyset) == self.lazy
1217 assert lazyset.union(taskset) == self.lazy
1220 assert lazyset == self.lazy
1222 def test_difference(self):
1223 taskset = set([self.task1, self.task2])
1224 lazyset = LazyUUIDTaskSet(
1226 (self.task2['uuid'], self.task3['uuid'])
1229 assert taskset - lazyset == set([self.task1])
1230 assert lazyset - taskset == set([self.task3])
1231 assert taskset.difference(lazyset) == set([self.task1])
1232 assert lazyset.difference(taskset) == set([self.task3])
1235 assert lazyset == set([self.task3])
1237 def test_symmetric_difference(self):
1238 taskset = set([self.task1, self.task2])
1239 lazyset = LazyUUIDTaskSet(
1241 (self.task2['uuid'], self.task3['uuid'])
1244 assert taskset ^ lazyset == set([self.task1, self.task3])
1245 assert lazyset ^ taskset == set([self.task1, self.task3])
1246 assert taskset.symmetric_difference(lazyset) == set([self.task1, self.task3])
1247 assert lazyset.symmetric_difference(taskset) == set([self.task1, self.task3])
1250 assert lazyset == set([self.task1, self.task3])
1252 def test_intersection(self):
1253 taskset = set([self.task1, self.task2])
1254 lazyset = LazyUUIDTaskSet(
1256 (self.task2['uuid'], self.task3['uuid'])
1259 assert taskset & lazyset == set([self.task2])
1260 assert lazyset & taskset == set([self.task2])
1261 assert taskset.intersection(lazyset) == set([self.task2])
1262 assert lazyset.intersection(taskset) == set([self.task2])
1265 assert lazyset == set([self.task2])
1268 class TaskWarriorBackendTest(TasklibTest):
1270 def test_config(self):
1271 assert self.tw.config['nag'] == "You have more urgent tasks."
1272 assert self.tw.config['sugar'] == "yes"