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"