All patches and comments are welcome. Please squash your changes to logical
commits before using git-format-patch and git-send-email to
patches@git.madduck.net.
If you'd read over the Git project's submission guidelines and adhered to them,
I'd be especially grateful.
  14 from .backends import TaskWarrior
 
  15 from .task import Task, ReadOnlyDictView, TaskQuerySet
 
  16 from .lazy import LazyUUIDTask, LazyUUIDTaskSet
 
  17 from .serializing import DATE_FORMAT, local_zone
 
  19 # http://taskwarrior.org/docs/design/task.html , Section: The Attributes
 
  20 TASK_STANDARD_ATTRS = (
 
  43 total_seconds_2_6 = lambda x: x.microseconds / 1e6 + x.seconds + x.days * 24 * 3600
 
  46 class TasklibTest(unittest.TestCase):
 
  49         self.tmp = tempfile.mkdtemp(dir='.')
 
  50         self.tw = TaskWarrior(data_location=self.tmp, taskrc_location='/')
 
  53         shutil.rmtree(self.tmp)
 
  56 class TaskFilterTest(TasklibTest):
 
  58     def test_all_empty(self):
 
  59         self.assertEqual(len(self.tw.tasks.all()), 0)
 
  61     def test_all_non_empty(self):
 
  62         Task(self.tw, description="test task").save()
 
  63         self.assertEqual(len(self.tw.tasks.all()), 1)
 
  64         self.assertEqual(self.tw.tasks.all()[0]['description'], 'test task')
 
  65         self.assertEqual(self.tw.tasks.all()[0]['status'], 'pending')
 
  67     def test_pending_non_empty(self):
 
  68         Task(self.tw, description="test task").save()
 
  69         self.assertEqual(len(self.tw.tasks.pending()), 1)
 
  70         self.assertEqual(self.tw.tasks.pending()[0]['description'],
 
  72         self.assertEqual(self.tw.tasks.pending()[0]['status'], 'pending')
 
  74     def test_completed_empty(self):
 
  75         Task(self.tw, description="test task").save()
 
  76         self.assertEqual(len(self.tw.tasks.completed()), 0)
 
  78     def test_completed_non_empty(self):
 
  79         Task(self.tw, description="test task").save()
 
  80         self.assertEqual(len(self.tw.tasks.completed()), 0)
 
  81         self.tw.tasks.all()[0].done()
 
  82         self.assertEqual(len(self.tw.tasks.completed()), 1)
 
  84     def test_deleted_empty(self):
 
  85         Task(self.tw, description="test task").save()
 
  86         self.assertEqual(len(self.tw.tasks.deleted()), 0)
 
  88     def test_deleted_non_empty(self):
 
  89         Task(self.tw, description="test task").save()
 
  90         self.assertEqual(len(self.tw.tasks.deleted()), 0)
 
  91         self.tw.tasks.all()[0].delete()
 
  92         self.assertEqual(len(self.tw.tasks.deleted()), 1)
 
  94     def test_waiting_empty(self):
 
  95         Task(self.tw, description="test task").save()
 
  96         self.assertEqual(len(self.tw.tasks.waiting()), 0)
 
  98     def test_waiting_non_empty(self):
 
  99         Task(self.tw, description="test task").save()
 
 100         self.assertEqual(len(self.tw.tasks.waiting()), 0)
 
 102         t = self.tw.tasks.all()[0]
 
 103         t['wait'] = 'tomorrow'
 
 106         self.assertEqual(len(self.tw.tasks.waiting()), 1)
 
 108     def test_filtering_by_attribute(self):
 
 109         Task(self.tw, description="no priority task").save()
 
 110         Task(self.tw, priority="H", description="high priority task").save()
 
 111         self.assertEqual(len(self.tw.tasks.all()), 2)
 
 113         # Assert that the correct number of tasks is returned
 
 114         self.assertEqual(len(self.tw.tasks.filter(priority="H")), 1)
 
 116         # Assert that the correct tasks are returned
 
 117         high_priority_task = self.tw.tasks.get(priority="H")
 
 118         self.assertEqual(high_priority_task['description'], "high priority task")
 
 120     def test_filtering_by_empty_attribute(self):
 
 121         Task(self.tw, description="no priority task").save()
 
 122         Task(self.tw, priority="H", description="high priority task").save()
 
 123         self.assertEqual(len(self.tw.tasks.all()), 2)
 
 125         # Assert that the correct number of tasks is returned
 
 126         self.assertEqual(len(self.tw.tasks.filter(priority=None)), 1)
 
 128         # Assert that the correct tasks are returned
 
 129         no_priority_task = self.tw.tasks.get(priority=None)
 
 130         self.assertEqual(no_priority_task['description'], "no priority task")
 
 132     def test_filter_for_task_with_space_in_descripition(self):
 
 133         task = Task(self.tw, description="test task")
 
 136         filtered_task = self.tw.tasks.get(description="test task")
 
 137         self.assertEqual(filtered_task['description'], "test task")
 
 139     def test_filter_for_task_without_space_in_descripition(self):
 
 140         task = Task(self.tw, description="test")
 
 143         filtered_task = self.tw.tasks.get(description="test")
 
 144         self.assertEqual(filtered_task['description'], "test")
 
 146     def test_filter_for_task_with_space_in_project(self):
 
 147         task = Task(self.tw, description="test", project="random project")
 
 150         filtered_task = self.tw.tasks.get(project="random project")
 
 151         self.assertEqual(filtered_task['project'], "random project")
 
 153     def test_filter_for_task_without_space_in_project(self):
 
 154         task = Task(self.tw, description="test", project="random")
 
 157         filtered_task = self.tw.tasks.get(project="random")
 
 158         self.assertEqual(filtered_task['project'], "random")
 
 160     def test_filter_with_empty_uuid(self):
 
 161         self.assertRaises(ValueError, lambda: self.tw.tasks.get(uuid=''))
 
 163     def test_filter_dummy_by_status(self):
 
 164         t = Task(self.tw, description="test")
 
 167         tasks = self.tw.tasks.filter(status=t['status'])
 
 168         self.assertEqual(list(tasks), [t])
 
 170     def test_filter_dummy_by_uuid(self):
 
 171         t = Task(self.tw, description="test")
 
 174         tasks = self.tw.tasks.filter(uuid=t['uuid'])
 
 175         self.assertEqual(list(tasks), [t])
 
 177     def test_filter_dummy_by_entry(self):
 
 178         t = Task(self.tw, description="test")
 
 181         tasks = self.tw.tasks.filter(entry=t['entry'])
 
 182         self.assertEqual(list(tasks), [t])
 
 184     def test_filter_dummy_by_description(self):
 
 185         t = Task(self.tw, description="test")
 
 188         tasks = self.tw.tasks.filter(description=t['description'])
 
 189         self.assertEqual(list(tasks), [t])
 
 191     def test_filter_dummy_by_start(self):
 
 192         t = Task(self.tw, description="test")
 
 196         tasks = self.tw.tasks.filter(start=t['start'])
 
 197         self.assertEqual(list(tasks), [t])
 
 199     def test_filter_dummy_by_end(self):
 
 200         t = Task(self.tw, description="test")
 
 204         tasks = self.tw.tasks.filter(end=t['end'])
 
 205         self.assertEqual(list(tasks), [t])
 
 207     def test_filter_dummy_by_due(self):
 
 208         t = Task(self.tw, description="test", due=datetime.datetime.now())
 
 211         tasks = self.tw.tasks.filter(due=t['due'])
 
 212         self.assertEqual(list(tasks), [t])
 
 214     def test_filter_dummy_by_until(self):
 
 215         t = Task(self.tw, description="test")
 
 218         tasks = self.tw.tasks.filter(until=t['until'])
 
 219         self.assertEqual(list(tasks), [t])
 
 221     def test_filter_dummy_by_modified(self):
 
 222         # Older TW version does not support bumping modified
 
 224         if self.tw.version < six.text_type('2.2.0'):
 
 225             # Python2.6 does not support SkipTest. As a workaround
 
 226             # mark the test as passed by exiting.
 
 227             if getattr(unittest, 'SkipTest', None) is not None:
 
 228                 raise unittest.SkipTest()
 
 232         t = Task(self.tw, description="test")
 
 235         tasks = self.tw.tasks.filter(modified=t['modified'])
 
 236         self.assertEqual(list(tasks), [t])
 
 238     def test_filter_dummy_by_scheduled(self):
 
 239         t = Task(self.tw, description="test")
 
 242         tasks = self.tw.tasks.filter(scheduled=t['scheduled'])
 
 243         self.assertEqual(list(tasks), [t])
 
 245     def test_filter_dummy_by_tags(self):
 
 246         t = Task(self.tw, description="test", tags=["home"])
 
 249         tasks = self.tw.tasks.filter(tags=t['tags'])
 
 250         self.assertEqual(list(tasks), [t])
 
 252     def test_filter_dummy_by_projects(self):
 
 253         t = Task(self.tw, description="test", project="random")
 
 256         tasks = self.tw.tasks.filter(project=t['project'])
 
 257         self.assertEqual(list(tasks), [t])
 
 259     def test_filter_by_priority(self):
 
 260         t = Task(self.tw, description="test", priority="H")
 
 263         tasks = self.tw.tasks.filter(priority=t['priority'])
 
 264         self.assertEqual(list(tasks), [t])
 
 267 class TaskTest(TasklibTest):
 
 269     def test_create_unsaved_task(self):
 
 270         # Make sure a new task is not saved unless explicitly called for
 
 271         t = Task(self.tw, description="test task")
 
 272         self.assertEqual(len(self.tw.tasks.all()), 0)
 
 274     # TODO: once python 2.6 compatiblity is over, use context managers here
 
 275     #       and in all subsequent tests for assertRaises
 
 277     def test_delete_unsaved_task(self):
 
 278         t = Task(self.tw, description="test task")
 
 279         self.assertRaises(Task.NotSaved, t.delete)
 
 281     def test_complete_unsaved_task(self):
 
 282         t = Task(self.tw, description="test task")
 
 283         self.assertRaises(Task.NotSaved, t.done)
 
 285     def test_refresh_unsaved_task(self):
 
 286         t = Task(self.tw, description="test task")
 
 287         self.assertRaises(Task.NotSaved, t.refresh)
 
 289     def test_start_unsaved_task(self):
 
 290         t = Task(self.tw, description="test task")
 
 291         self.assertRaises(Task.NotSaved, t.start)
 
 293     def test_delete_deleted_task(self):
 
 294         t = Task(self.tw, description="test task")
 
 298         self.assertRaises(Task.DeletedTask, t.delete)
 
 300     def test_complete_completed_task(self):
 
 301         t = Task(self.tw, description="test task")
 
 305         self.assertRaises(Task.CompletedTask, t.done)
 
 307     def test_start_completed_task(self):
 
 308         t = Task(self.tw, description="test task")
 
 312         self.assertRaises(Task.CompletedTask, t.start)
 
 314     def test_add_completed_task(self):
 
 315         t = Task(self.tw, description="test", status="completed",
 
 316                  end=datetime.datetime.now())
 
 319     def test_add_multiple_completed_tasks(self):
 
 320         t1 = Task(self.tw, description="test1", status="completed",
 
 321                  end=datetime.datetime.now())
 
 322         t2 = Task(self.tw, description="test2", status="completed",
 
 323                  end=datetime.datetime.now())
 
 327     def test_complete_deleted_task(self):
 
 328         t = Task(self.tw, description="test task")
 
 332         self.assertRaises(Task.DeletedTask, t.done)
 
 334     def test_starting_task(self):
 
 335         t = Task(self.tw, description="test task")
 
 336         now = t.datetime_normalizer(datetime.datetime.now())
 
 340         self.assertTrue(now.replace(microsecond=0) <= t['start'])
 
 341         self.assertEqual(t['status'], 'pending')
 
 343     def test_completing_task(self):
 
 344         t = Task(self.tw, description="test task")
 
 345         now = t.datetime_normalizer(datetime.datetime.now())
 
 349         self.assertTrue(now.replace(microsecond=0) <= t['end'])
 
 350         self.assertEqual(t['status'], 'completed')
 
 352     def test_deleting_task(self):
 
 353         t = Task(self.tw, description="test task")
 
 354         now = t.datetime_normalizer(datetime.datetime.now())
 
 358         self.assertTrue(now.replace(microsecond=0) <= t['end'])
 
 359         self.assertEqual(t['status'], 'deleted')
 
 361     def test_started_task_active(self):
 
 362         t = Task(self.tw, description="test task")
 
 365         self.assertTrue(t.active)
 
 367     def test_unstarted_task_inactive(self):
 
 368         t = Task(self.tw, description="test task")
 
 369         self.assertFalse(t.active)
 
 371         self.assertFalse(t.active)
 
 373     def test_start_active_task(self):
 
 374         t = Task(self.tw, description="test task")
 
 377         self.assertRaises(Task.ActiveTask, t.start)
 
 379     def test_stop_completed_task(self):
 
 380         t = Task(self.tw, description="test task")
 
 385         self.assertRaises(Task.InactiveTask, t.stop)
 
 387         t = Task(self.tw, description="test task")
 
 391         self.assertRaises(Task.InactiveTask, t.stop)
 
 393     def test_stop_deleted_task(self):
 
 394         t = Task(self.tw, description="test task")
 
 400     def test_stop_inactive_task(self):
 
 401         t = Task(self.tw, description="test task")
 
 404         self.assertRaises(Task.InactiveTask, t.stop)
 
 406         t = Task(self.tw, description="test task")
 
 411         self.assertRaises(Task.InactiveTask, t.stop)
 
 413     def test_stopping_task(self):
 
 414         t = Task(self.tw, description="test task")
 
 415         now = t.datetime_normalizer(datetime.datetime.now())
 
 420         self.assertEqual(t['end'], None)
 
 421         self.assertEqual(t['status'], 'pending')
 
 422         self.assertFalse(t.active)
 
 424     def test_modify_simple_attribute_without_space(self):
 
 425         t = Task(self.tw, description="test")
 
 428         self.assertEquals(t['description'], "test")
 
 430         t['description'] = "test-modified"
 
 433         self.assertEquals(t['description'], "test-modified")
 
 435     def test_modify_simple_attribute_with_space(self):
 
 436         # Space can pose problems with parsing
 
 437         t = Task(self.tw, description="test task")
 
 440         self.assertEquals(t['description'], "test task")
 
 442         t['description'] = "test task modified"
 
 445         self.assertEquals(t['description'], "test task modified")
 
 447     def test_empty_dependency_set_of_unsaved_task(self):
 
 448         t = Task(self.tw, description="test task")
 
 449         self.assertEqual(t['depends'], set())
 
 451     def test_empty_dependency_set_of_saved_task(self):
 
 452         t = Task(self.tw, description="test task")
 
 454         self.assertEqual(t['depends'], set())
 
 456     def test_set_unsaved_task_as_dependency(self):
 
 457         # Adds only one dependency to task with no dependencies
 
 458         t = Task(self.tw, description="test task")
 
 459         dependency = Task(self.tw, description="needs to be done first")
 
 461         # We only save the parent task, dependency task is unsaved
 
 463         t['depends'] = set([dependency])
 
 465         self.assertRaises(Task.NotSaved, t.save)
 
 467     def test_set_simple_dependency_set(self):
 
 468         # Adds only one dependency to task with no dependencies
 
 469         t = Task(self.tw, description="test task")
 
 470         dependency = Task(self.tw, description="needs to be done first")
 
 475         t['depends'] = set([dependency])
 
 477         self.assertEqual(t['depends'], set([dependency]))
 
 479     def test_set_complex_dependency_set(self):
 
 480         # Adds two dependencies to task with no dependencies
 
 481         t = Task(self.tw, description="test task")
 
 482         dependency1 = Task(self.tw, description="needs to be done first")
 
 483         dependency2 = Task(self.tw, description="needs to be done second")
 
 489         t['depends'] = set([dependency1, dependency2])
 
 491         self.assertEqual(t['depends'], set([dependency1, dependency2]))
 
 493     def test_remove_from_dependency_set(self):
 
 494         # Removes dependency from task with two dependencies
 
 495         t = Task(self.tw, description="test task")
 
 496         dependency1 = Task(self.tw, description="needs to be done first")
 
 497         dependency2 = Task(self.tw, description="needs to be done second")
 
 502         t['depends'] = set([dependency1, dependency2])
 
 505         t['depends'].remove(dependency2)
 
 508         self.assertEqual(t['depends'], set([dependency1]))
 
 510     def test_add_to_dependency_set(self):
 
 511         # Adds dependency to task with one dependencies
 
 512         t = Task(self.tw, description="test task")
 
 513         dependency1 = Task(self.tw, description="needs to be done first")
 
 514         dependency2 = Task(self.tw, description="needs to be done second")
 
 519         t['depends'] = set([dependency1])
 
 522         t['depends'].add(dependency2)
 
 525         self.assertEqual(t['depends'], set([dependency1, dependency2]))
 
 527     def test_add_to_empty_dependency_set(self):
 
 528         # Adds dependency to task with one dependencies
 
 529         t = Task(self.tw, description="test task")
 
 530         dependency = Task(self.tw, description="needs to be done first")
 
 534         t['depends'].add(dependency)
 
 537         self.assertEqual(t['depends'], set([dependency]))
 
 539     def test_simple_dependency_set_save_repeatedly(self):
 
 540         # Adds only one dependency to task with no dependencies
 
 541         t = Task(self.tw, description="test task")
 
 542         dependency = Task(self.tw, description="needs to be done first")
 
 545         t['depends'] = set([dependency])
 
 548         # We taint the task, but keep depends intact
 
 549         t['description'] = "test task modified"
 
 552         self.assertEqual(t['depends'], set([dependency]))
 
 554         # We taint the task, but assign the same set to the depends
 
 555         t['depends'] = set([dependency])
 
 556         t['description'] = "test task modified again"
 
 559         self.assertEqual(t['depends'], set([dependency]))
 
 561     def test_compare_different_tasks(self):
 
 562         # Negative: compare two different tasks
 
 563         t1 = Task(self.tw, description="test task")
 
 564         t2 = Task(self.tw, description="test task")
 
 569         self.assertEqual(t1 == t2, False)
 
 571     def test_compare_same_task_object(self):
 
 572         # Compare Task object wit itself
 
 573         t = Task(self.tw, description="test task")
 
 576         self.assertEqual(t == t, True)
 
 578     def test_compare_same_task(self):
 
 579         # Compare the same task using two different objects
 
 580         t1 = Task(self.tw, description="test task")
 
 583         t2 = self.tw.tasks.get(uuid=t1['uuid'])
 
 584         self.assertEqual(t1 == t2, True)
 
 586     def test_compare_unsaved_tasks(self):
 
 587         # t1 and t2 are unsaved tasks, considered to be unequal
 
 588         # despite the content of data
 
 589         t1 = Task(self.tw, description="test task")
 
 590         t2 = Task(self.tw, description="test task")
 
 592         self.assertEqual(t1 == t2, False)
 
 594     def test_hash_unsaved_tasks(self):
 
 595         # Considered equal, it's the same object
 
 596         t1 = Task(self.tw, description="test task")
 
 598         self.assertEqual(hash(t1) == hash(t2), True)
 
 600     def test_hash_same_task(self):
 
 601         # Compare the hash of the task using two different objects
 
 602         t1 = Task(self.tw, description="test task")
 
 605         t2 = self.tw.tasks.get(uuid=t1['uuid'])
 
 606         self.assertEqual(t1.__hash__(), t2.__hash__())
 
 608     def test_adding_task_with_priority(self):
 
 609         t = Task(self.tw, description="test task", priority="M")
 
 612     def test_removing_priority_with_none(self):
 
 613         t = Task(self.tw, description="test task", priority="L")
 
 616         # Remove the priority mark
 
 620         # Assert that priority is not there after saving
 
 621         self.assertEqual(t['priority'], None)
 
 623     def test_adding_task_with_due_time(self):
 
 624         t = Task(self.tw, description="test task", due=datetime.datetime.now())
 
 627     def test_removing_due_time_with_none(self):
 
 628         t = Task(self.tw, description="test task", due=datetime.datetime.now())
 
 631         # Remove the due timestamp
 
 635         # Assert that due timestamp is no longer there
 
 636         self.assertEqual(t['due'], None)
 
 638     def test_modified_fields_new_task(self):
 
 641         # This should be empty with new task
 
 642         self.assertEqual(set(t._modified_fields), set())
 
 645         t['description'] = "test task"
 
 646         self.assertEqual(set(t._modified_fields), set(['description']))
 
 648         t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14)  # <3
 
 649         self.assertEqual(set(t._modified_fields), set(['description', 'due']))
 
 651         t['project'] = "test project"
 
 652         self.assertEqual(set(t._modified_fields),
 
 653                          set(['description', 'due', 'project']))
 
 655         # List of modified fields should clear out when saved
 
 657         self.assertEqual(set(t._modified_fields), set())
 
 659         # Reassigning the fields with the same values now should not produce
 
 661         t['description'] = "test task"
 
 662         t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14)  # <3
 
 663         t['project'] = "test project"
 
 664         self.assertEqual(set(t._modified_fields), set())
 
 666     def test_modified_fields_loaded_task(self):
 
 670         t['description'] = "test task"
 
 671         t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14)  # <3
 
 672         t['project'] = "test project"
 
 674         dependency = Task(self.tw, description="dependency")
 
 676         t['depends'] = set([dependency])
 
 678         # List of modified fields should clear out when saved
 
 680         self.assertEqual(set(t._modified_fields), set())
 
 682         # Get the task by using a filter by UUID
 
 683         t2 = self.tw.tasks.get(uuid=t['uuid'])
 
 685         # Reassigning the fields with the same values now should not produce
 
 687         t['description'] = "test task"
 
 688         t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14)  # <3
 
 689         t['project'] = "test project"
 
 690         t['depends'] = set([dependency])
 
 691         self.assertEqual(set(t._modified_fields), set())
 
 693     def test_modified_fields_not_affected_by_reading(self):
 
 696         for field in TASK_STANDARD_ATTRS:
 
 699         self.assertEqual(set(t._modified_fields), set())
 
 701     def test_setting_read_only_attrs_through_init(self):
 
 702         # Test that we are unable to set readonly attrs through __init__
 
 703         for readonly_key in Task.read_only_fields:
 
 704             kwargs = {'description': 'test task', readonly_key: 'value'}
 
 705             self.assertRaises(RuntimeError,
 
 706                               lambda: Task(self.tw, **kwargs))
 
 708     def test_setting_read_only_attrs_through_setitem(self):
 
 709         # Test that we are unable to set readonly attrs through __init__
 
 710         for readonly_key in Task.read_only_fields:
 
 711             t = Task(self.tw, description='test task')
 
 712             self.assertRaises(RuntimeError,
 
 713                               lambda: t.__setitem__(readonly_key, 'value'))
 
 715     def test_saving_unmodified_task(self):
 
 716         t = Task(self.tw, description="test task")
 
 720     def test_adding_tag_by_appending(self):
 
 721         t = Task(self.tw, description="test task", tags=['test1'])
 
 723         t['tags'].add('test2')
 
 725         self.assertEqual(t['tags'], set(['test1', 'test2']))
 
 727     def test_adding_tag_twice(self):
 
 728         t = Task(self.tw, description="test task", tags=['test1'])
 
 730         t['tags'].add('test2')
 
 731         t['tags'].add('test2')
 
 733         self.assertEqual(t['tags'], set(['test1', 'test2']))
 
 735     def test_adding_tag_by_appending_empty(self):
 
 736         t = Task(self.tw, description="test task")
 
 738         t['tags'].add('test')
 
 740         self.assertEqual(t['tags'], set(['test']))
 
 742     def test_serializers_returning_empty_string_for_none(self):
 
 743         # Test that any serializer returns '' when passed None
 
 745         serializers = [getattr(t, serializer_name) for serializer_name in
 
 746                        filter(lambda x: x.startswith('serialize_'), dir(t))]
 
 747         for serializer in serializers:
 
 748             self.assertEqual(serializer(None), '')
 
 750     def test_deserializer_returning_empty_value_for_empty_string(self):
 
 751         # Test that any deserializer returns empty value when passed ''
 
 753         deserializers = [getattr(t, deserializer_name) for deserializer_name in
 
 754                         filter(lambda x: x.startswith('deserialize_'), dir(t))]
 
 755         for deserializer in deserializers:
 
 756             self.assertTrue(deserializer('') in (None, [], set()))
 
 758     def test_normalizers_handling_none(self):
 
 759         # Test that any normalizer can handle None as a valid value
 
 762         for key in TASK_STANDARD_ATTRS:
 
 763             t._normalize(key, None)
 
 765     def test_recurrent_task_generation(self):
 
 766         today = datetime.date.today()
 
 767         t = Task(self.tw, description="brush teeth",
 
 768                  due=today, recur="daily")
 
 770         self.assertEqual(len(self.tw.tasks.pending()), 2)
 
 772     def test_spawned_task_parent(self):
 
 773         today = datetime.date.today()
 
 774         t = Task(self.tw, description="brush teeth",
 
 775                  due=today, recur="daily")
 
 778         spawned = self.tw.tasks.pending().get(due=today)
 
 779         assert spawned['parent'] == t
 
 781     def test_modify_number_of_tasks_at_once(self):
 
 782         for i in range(1, 100):
 
 783             Task(self.tw, description="test task %d" % i, tags=['test']).save()
 
 785         self.tw.execute_command(['+test', 'mod', 'unified', 'description'])
 
 787     def test_return_all_from_executed_command(self):
 
 788         Task(self.tw, description="test task", tags=['test']).save()
 
 789         out, err, rc = self.tw.execute_command(['count'], return_all=True)
 
 790         self.assertEqual(rc, 0)
 
 792     def test_return_all_from_failed_executed_command(self):
 
 793         Task(self.tw, description="test task", tags=['test']).save()
 
 794         out, err, rc = self.tw.execute_command(['countinvalid'],
 
 795             return_all=True, allow_failure=False)
 
 796         self.assertNotEqual(rc, 0)
 
 799 class TaskFromHookTest(TasklibTest):
 
 801     input_add_data = six.StringIO(
 
 802         '{"description":"Buy some milk",'
 
 803         '"entry":"20141118T050231Z",'
 
 804         '"status":"pending",'
 
 805         '"start":"20141119T152233Z",'
 
 806         '"uuid":"a360fc44-315c-4366-b70c-ea7e7520b749"}')
 
 808     input_add_data_recurring = six.StringIO(
 
 809         '{"description":"Mow the lawn",'
 
 810         '"entry":"20160210T224304Z",'
 
 811         '"parent":"62da6227-519c-42c2-915d-dccada926ad7",'
 
 813         '"status":"pending",'
 
 814         '"uuid":"81305335-0237-49ff-8e87-b3cdc2369cec"}')
 
 816     input_modify_data = six.StringIO(input_add_data.getvalue() + '\n' +
 
 817         '{"description":"Buy some milk finally",'
 
 818         '"entry":"20141118T050231Z",'
 
 819         '"status":"completed",'
 
 820         '"uuid":"a360fc44-315c-4366-b70c-ea7e7520b749"}')
 
 822     exported_raw_data = (
 
 824          '"due":"20150101T232323Z",'
 
 825          '"description":"test task"}')
 
 827     def test_setting_up_from_add_hook_input(self):
 
 828         t = Task.from_input(input_file=self.input_add_data, backend=self.tw)
 
 829         self.assertEqual(t['description'], "Buy some milk")
 
 830         self.assertEqual(t.pending, True)
 
 832     def test_setting_up_from_add_hook_input_recurring(self):
 
 833         t = Task.from_input(input_file=self.input_add_data_recurring,
 
 835         self.assertEqual(t['description'], "Mow the lawn")
 
 836         self.assertEqual(t.pending, True)
 
 838     def test_setting_up_from_modified_hook_input(self):
 
 839         t = Task.from_input(input_file=self.input_modify_data, modify=True,
 
 841         self.assertEqual(t['description'], "Buy some milk finally")
 
 842         self.assertEqual(t.pending, False)
 
 843         self.assertEqual(t.completed, True)
 
 845         self.assertEqual(t._original_data['status'], "pending")
 
 846         self.assertEqual(t._original_data['description'], "Buy some milk")
 
 847         self.assertEqual(set(t._modified_fields),
 
 848                          set(['status', 'description', 'start']))
 
 850     def test_export_data(self):
 
 851         t = Task(self.tw, description="test task",
 
 853             due=pytz.utc.localize(datetime.datetime(2015,1,1,23,23,23)))
 
 855         # Check that the output is a permutation of:
 
 856         # {"project":"Home","description":"test task","due":"20150101232323Z"}
 
 857         allowed_segments = self.exported_raw_data[1:-1].split(',')
 
 859             '{' + ','.join(segments) + '}'
 
 860             for segments in itertools.permutations(allowed_segments)
 
 863         self.assertTrue(any(t.export_data() == expected
 
 864                             for expected in allowed_output))
 
 866 class TimezoneAwareDatetimeTest(TasklibTest):
 
 869         super(TimezoneAwareDatetimeTest, self).setUp()
 
 870         self.zone = local_zone
 
 871         self.localdate_naive = datetime.datetime(2015,2,2)
 
 872         self.localtime_naive = datetime.datetime(2015,2,2,0,0,0)
 
 873         self.localtime_aware = self.zone.localize(self.localtime_naive)
 
 874         self.utctime_aware = self.localtime_aware.astimezone(pytz.utc)
 
 876     def test_timezone_naive_datetime_setitem(self):
 
 877         t = Task(self.tw, description="test task")
 
 878         t['due'] = self.localtime_naive
 
 879         self.assertEqual(t['due'], self.localtime_aware)
 
 881     def test_timezone_naive_datetime_using_init(self):
 
 882         t = Task(self.tw, description="test task", due=self.localtime_naive)
 
 883         self.assertEqual(t['due'], self.localtime_aware)
 
 885     def test_filter_by_naive_datetime(self):
 
 886         t = Task(self.tw, description="task1", due=self.localtime_naive)
 
 888         matching_tasks = self.tw.tasks.filter(due=self.localtime_naive)
 
 889         self.assertEqual(len(matching_tasks), 1)
 
 891     def test_serialize_naive_datetime(self):
 
 892         t = Task(self.tw, description="task1", due=self.localtime_naive)
 
 893         self.assertEqual(json.loads(t.export_data())['due'],
 
 894                          self.utctime_aware.strftime(DATE_FORMAT))
 
 896     def test_timezone_naive_date_setitem(self):
 
 897         t = Task(self.tw, description="test task")
 
 898         t['due'] = self.localdate_naive
 
 899         self.assertEqual(t['due'], self.localtime_aware)
 
 901     def test_timezone_naive_date_using_init(self):
 
 902         t = Task(self.tw, description="test task", due=self.localdate_naive)
 
 903         self.assertEqual(t['due'], self.localtime_aware)
 
 905     def test_filter_by_naive_date(self):
 
 906         t = Task(self.tw, description="task1", due=self.localdate_naive)
 
 908         matching_tasks = self.tw.tasks.filter(due=self.localdate_naive)
 
 909         self.assertEqual(len(matching_tasks), 1)
 
 911     def test_serialize_naive_date(self):
 
 912         t = Task(self.tw, description="task1", due=self.localdate_naive)
 
 913         self.assertEqual(json.loads(t.export_data())['due'],
 
 914                          self.utctime_aware.strftime(DATE_FORMAT))
 
 916     def test_timezone_aware_datetime_setitem(self):
 
 917         t = Task(self.tw, description="test task")
 
 918         t['due'] = self.localtime_aware
 
 919         self.assertEqual(t['due'], self.localtime_aware)
 
 921     def test_timezone_aware_datetime_using_init(self):
 
 922         t = Task(self.tw, description="test task", due=self.localtime_aware)
 
 923         self.assertEqual(t['due'], self.localtime_aware)
 
 925     def test_filter_by_aware_datetime(self):
 
 926         t = Task(self.tw, description="task1", due=self.localtime_aware)
 
 928         matching_tasks = self.tw.tasks.filter(due=self.localtime_aware)
 
 929         self.assertEqual(len(matching_tasks), 1)
 
 931     def test_serialize_aware_datetime(self):
 
 932         t = Task(self.tw, description="task1", due=self.localtime_aware)
 
 933         self.assertEqual(json.loads(t.export_data())['due'],
 
 934                          self.utctime_aware.strftime(DATE_FORMAT))
 
 936 class DatetimeStringTest(TasklibTest):
 
 938     def test_simple_now_conversion(self):
 
 939         if self.tw.version < six.text_type('2.4.0'):
 
 940             # Python2.6 does not support SkipTest. As a workaround
 
 941             # mark the test as passed by exiting.
 
 942             if getattr(unittest, 'SkipTest', None) is not None:
 
 943                 raise unittest.SkipTest()
 
 947         t = Task(self.tw, description="test task", due="now")
 
 948         now = local_zone.localize(datetime.datetime.now())
 
 950         # Assert that both times are not more than 5 seconds apart
 
 951         if sys.version_info < (2,7):
 
 952             self.assertTrue(total_seconds_2_6(now - t['due']) < 5)
 
 953             self.assertTrue(total_seconds_2_6(t['due'] - now) < 5)
 
 955             self.assertTrue((now - t['due']).total_seconds() < 5)
 
 956             self.assertTrue((t['due'] - now).total_seconds() < 5)
 
 958     def test_simple_eoy_conversion(self):
 
 959         if self.tw.version < six.text_type('2.4.0'):
 
 960             # Python2.6 does not support SkipTest. As a workaround
 
 961             # mark the test as passed by exiting.
 
 962             if getattr(unittest, 'SkipTest', None) is not None:
 
 963                 raise unittest.SkipTest()
 
 967         t = Task(self.tw, description="test task", due="eoy")
 
 968         now = local_zone.localize(datetime.datetime.now())
 
 969         eoy = local_zone.localize(datetime.datetime(
 
 977         self.assertEqual(eoy, t['due'])
 
 979     def test_complex_eoy_conversion(self):
 
 980         if self.tw.version < six.text_type('2.4.0'):
 
 981             # Python2.6 does not support SkipTest. As a workaround
 
 982             # mark the test as passed by exiting.
 
 983             if getattr(unittest, 'SkipTest', None) is not None:
 
 984                 raise unittest.SkipTest()
 
 988         t = Task(self.tw, description="test task", due="eoy - 4 months")
 
 989         now = local_zone.localize(datetime.datetime.now())
 
 990         due_date = local_zone.localize(datetime.datetime(
 
 997             )) - datetime.timedelta(0,4 * 30 * 86400)
 
 998         self.assertEqual(due_date, t['due'])
 
1000     def test_filtering_with_string_datetime(self):
 
1001         if self.tw.version < six.text_type('2.4.0'):
 
1002             # Python2.6 does not support SkipTest. As a workaround
 
1003             # mark the test as passed by exiting.
 
1004             if getattr(unittest, 'SkipTest', None) is not None:
 
1005                 raise unittest.SkipTest()
 
1009         t = Task(self.tw, description="test task",
 
1010                  due=datetime.datetime.now() - datetime.timedelta(0,2))
 
1012         self.assertEqual(len(self.tw.tasks.filter(due__before="now")), 1)
 
1014 class AnnotationTest(TasklibTest):
 
1017         super(AnnotationTest, self).setUp()
 
1018         Task(self.tw, description="test task").save()
 
1020     def test_adding_annotation(self):
 
1021         task = self.tw.tasks.get()
 
1022         task.add_annotation('test annotation')
 
1023         self.assertEqual(len(task['annotations']), 1)
 
1024         ann = task['annotations'][0]
 
1025         self.assertEqual(ann['description'], 'test annotation')
 
1027     def test_removing_annotation(self):
 
1028         task = self.tw.tasks.get()
 
1029         task.add_annotation('test annotation')
 
1030         ann = task['annotations'][0]
 
1032         self.assertEqual(len(task['annotations']), 0)
 
1034     def test_removing_annotation_by_description(self):
 
1035         task = self.tw.tasks.get()
 
1036         task.add_annotation('test annotation')
 
1037         task.remove_annotation('test annotation')
 
1038         self.assertEqual(len(task['annotations']), 0)
 
1040     def test_removing_annotation_by_obj(self):
 
1041         task = self.tw.tasks.get()
 
1042         task.add_annotation('test annotation')
 
1043         ann = task['annotations'][0]
 
1044         task.remove_annotation(ann)
 
1045         self.assertEqual(len(task['annotations']), 0)
 
1047     def test_annotation_after_modification(self):
 
1048          task = self.tw.tasks.get()
 
1049          task['project'] = 'test'
 
1050          task.add_annotation('I should really do this task')
 
1051          self.assertEqual(task['project'], 'test')
 
1053          self.assertEqual(task['project'], 'test')
 
1055     def test_serialize_annotations(self):
 
1056         # Test that serializing annotations is possible
 
1057         t = Task(self.tw, description="test")
 
1060         t.add_annotation("annotation1")
 
1061         t.add_annotation("annotation2")
 
1063         data = t._serialize('annotations', t._data['annotations'])
 
1065         self.assertEqual(len(data), 2)
 
1066         self.assertEqual(type(data[0]), dict)
 
1067         self.assertEqual(type(data[1]), dict)
 
1069         self.assertEqual(data[0]['description'], "annotation1")
 
1070         self.assertEqual(data[1]['description'], "annotation2")
 
1073 class UnicodeTest(TasklibTest):
 
1075     def test_unicode_task(self):
 
1076         Task(self.tw, description=six.u("†åßk")).save()
 
1079     def test_filter_by_unicode_task(self):
 
1080         Task(self.tw, description=six.u("†åßk")).save()
 
1081         tasks = self.tw.tasks.filter(description=six.u("†åßk"))
 
1082         self.assertEqual(len(tasks), 1)
 
1084     def test_non_unicode_task(self):
 
1085         Task(self.tw, description="test task").save()
 
1088 class ReadOnlyDictViewTest(unittest.TestCase):
 
1091         self.sample = dict(l=[1,2,3], d={'k':'v'})
 
1092         self.original_sample = copy.deepcopy(self.sample)
 
1093         self.view = ReadOnlyDictView(self.sample)
 
1095     def test_readonlydictview_getitem(self):
 
1097         self.assertEqual(l, self.sample['l'])
 
1099         # Assert that modification changed only copied value
 
1101         self.assertNotEqual(l, self.sample['l'])
 
1103         # Assert that viewed dict is not changed
 
1104         self.assertEqual(self.sample, self.original_sample)
 
1106     def test_readonlydictview_contains(self):
 
1107         self.assertEqual('l' in self.view, 'l' in self.sample)
 
1108         self.assertEqual('d' in self.view, 'd' in self.sample)
 
1109         self.assertEqual('k' in self.view, 'k' in self.sample)
 
1111         # Assert that viewed dict is not changed
 
1112         self.assertEqual(self.sample, self.original_sample)
 
1114     def test_readonlydictview_iter(self):
 
1115         self.assertEqual(list(k for k in self.view),
 
1116                          list(k for k in self.sample))
 
1118         # Assert the view is correct after modification
 
1119         self.sample['new'] = 'value'
 
1120         self.assertEqual(list(k for k in self.view),
 
1121                          list(k for k in self.sample))
 
1123     def test_readonlydictview_len(self):
 
1124         self.assertEqual(len(self.view), len(self.sample))
 
1126         # Assert the view is correct after modification
 
1127         self.sample['new'] = 'value'
 
1128         self.assertEqual(len(self.view), len(self.sample))
 
1130     def test_readonlydictview_get(self):
 
1131         l = self.view.get('l')
 
1132         self.assertEqual(l, self.sample.get('l'))
 
1134         # Assert that modification changed only copied value
 
1136         self.assertNotEqual(l, self.sample.get('l'))
 
1138         # Assert that viewed dict is not changed
 
1139         self.assertEqual(self.sample, self.original_sample)
 
1141     def test_readonlydict_items(self):
 
1142         view_items = self.view.items()
 
1143         sample_items = list(self.sample.items())
 
1144         self.assertEqual(view_items, sample_items)
 
1146         view_items.append('newkey')
 
1147         self.assertNotEqual(view_items, sample_items)
 
1148         self.assertEqual(self.sample, self.original_sample)
 
1150     def test_readonlydict_values(self):
 
1151         view_values = self.view.values()
 
1152         sample_values = list(self.sample.values())
 
1153         self.assertEqual(view_values, sample_values)
 
1155         view_list_item = list(filter(lambda x: type(x) is list,
 
1157         view_list_item.append(4)
 
1158         self.assertNotEqual(view_values, sample_values)
 
1159         self.assertEqual(self.sample, self.original_sample)
 
1162 class LazyUUIDTaskTest(TasklibTest):
 
1165         super(LazyUUIDTaskTest, self).setUp()
 
1167         self.stored = Task(self.tw, description="this is test task")
 
1170         self.lazy = LazyUUIDTask(self.tw, self.stored['uuid'])
 
1172     def test_uuid_non_conversion(self):
 
1173         assert self.stored['uuid'] == self.lazy['uuid']
 
1174         assert type(self.lazy) is LazyUUIDTask
 
1176     def test_lazy_explicit_conversion(self):
 
1177         assert type(self.lazy) is LazyUUIDTask
 
1179         assert type(self.lazy) is Task
 
1181     def test_conversion_key(self):
 
1182         assert self.stored['description'] == self.lazy['description']
 
1183         assert type(self.lazy) is Task
 
1185     def test_conversion_attribute(self):
 
1186         assert type(self.lazy) is LazyUUIDTask
 
1187         assert self.lazy.completed is False
 
1188         assert type(self.lazy) is Task
 
1190     def test_normal_to_lazy_equality(self):
 
1191         assert self.stored == self.lazy
 
1192         assert not self.stored != self.lazy
 
1193         assert type(self.lazy) is LazyUUIDTask
 
1195     def test_lazy_to_lazy_equality(self):
 
1196         lazy1 = LazyUUIDTask(self.tw, self.stored['uuid'])
 
1197         lazy2 = LazyUUIDTask(self.tw, self.stored['uuid'])
 
1199         assert lazy1 == lazy2
 
1200         assert not lazy1 != lazy2
 
1201         assert type(lazy1) is LazyUUIDTask
 
1202         assert type(lazy2) is LazyUUIDTask
 
1204     def test_normal_to_lazy_inequality(self):
 
1205         # Create a different UUID by changing the last letter
 
1206         wrong_uuid = self.stored['uuid']
 
1207         wrong_uuid = wrong_uuid[:-1] + ('a' if wrong_uuid[-1] != 'a' else 'b')
 
1209         wrong_lazy = LazyUUIDTask(self.tw, wrong_uuid)
 
1211         assert not self.stored == wrong_lazy
 
1212         assert self.stored != wrong_lazy
 
1213         assert type(wrong_lazy) is LazyUUIDTask
 
1215     def test_lazy_to_lazy_inequality(self):
 
1216         # Create a different UUID by changing the last letter
 
1217         wrong_uuid = self.stored['uuid']
 
1218         wrong_uuid = wrong_uuid[:-1] + ('a' if wrong_uuid[-1] != 'a' else 'b')
 
1220         lazy1 = LazyUUIDTask(self.tw, self.stored['uuid'])
 
1221         lazy2 = LazyUUIDTask(self.tw, wrong_uuid)
 
1223         assert not lazy1 == lazy2
 
1224         assert lazy1 != lazy2
 
1225         assert type(lazy1) is LazyUUIDTask
 
1226         assert type(lazy2) is LazyUUIDTask
 
1228     def test_lazy_in_queryset(self):
 
1229         tasks = self.tw.tasks.filter(uuid=self.stored['uuid'])
 
1231         assert self.lazy in tasks
 
1232         assert type(self.lazy) is LazyUUIDTask
 
1234     def test_lazy_saved(self):
 
1235         assert self.lazy.saved is True
 
1237     def test_lazy_modified(self):
 
1238         assert self.lazy.modified is False
 
1240     def test_lazy_modified_fields(self):
 
1241         assert self.lazy._modified_fields == set()
 
1244 class LazyUUIDTaskSetTest(TasklibTest):
 
1247         super(LazyUUIDTaskSetTest, self).setUp()
 
1249         self.task1 = Task(self.tw, description="task 1")
 
1250         self.task2 = Task(self.tw, description="task 2")
 
1251         self.task3 = Task(self.tw, description="task 3")
 
1263         self.lazy = LazyUUIDTaskSet(self.tw, self.uuids)
 
1265     def test_length(self):
 
1266         assert len(self.lazy) == 3
 
1267         assert type(self.lazy) is LazyUUIDTaskSet
 
1269     def test_contains(self):
 
1270         assert self.task1 in self.lazy
 
1271         assert self.task2 in self.lazy
 
1272         assert self.task3 in self.lazy
 
1273         assert type(self.lazy) is LazyUUIDTaskSet
 
1275     def test_eq_lazy(self):
 
1276         new_lazy = LazyUUIDTaskSet(self.tw, self.uuids)
 
1277         assert self.lazy == new_lazy
 
1278         assert not self.lazy != new_lazy
 
1279         assert type(self.lazy) is LazyUUIDTaskSet
 
1281     def test_eq_real(self):
 
1282         assert self.lazy == self.tw.tasks.all()
 
1283         assert self.tw.tasks.all() == self.lazy
 
1284         assert not self.lazy != self.tw.tasks.all()
 
1286         assert type(self.lazy) is LazyUUIDTaskSet
 
1288     def test_union(self):
 
1289         taskset = set([self.task1])
 
1290         lazyset = LazyUUIDTaskSet(
 
1292             (self.task2['uuid'], self.task3['uuid'])
 
1295         assert taskset | lazyset == self.lazy
 
1296         assert lazyset | taskset == self.lazy
 
1297         assert taskset.union(lazyset) == self.lazy
 
1298         assert lazyset.union(taskset) == self.lazy
 
1301         assert lazyset == self.lazy
 
1303     def test_difference(self):
 
1304         taskset = set([self.task1, self.task2])
 
1305         lazyset = LazyUUIDTaskSet(
 
1307             (self.task2['uuid'], self.task3['uuid'])
 
1310         assert taskset - lazyset == set([self.task1])
 
1311         assert lazyset - taskset == set([self.task3])
 
1312         assert taskset.difference(lazyset) == set([self.task1])
 
1313         assert lazyset.difference(taskset) == set([self.task3])
 
1316         assert lazyset == set([self.task3])
 
1318     def test_symmetric_difference(self):
 
1319         taskset = set([self.task1, self.task2])
 
1320         lazyset = LazyUUIDTaskSet(
 
1322             (self.task2['uuid'], self.task3['uuid'])
 
1325         assert taskset ^ lazyset == set([self.task1, self.task3])
 
1326         assert lazyset ^ taskset == set([self.task1, self.task3])
 
1327         assert taskset.symmetric_difference(lazyset) == set([self.task1, self.task3])
 
1328         assert lazyset.symmetric_difference(taskset) == set([self.task1, self.task3])
 
1331         assert lazyset == set([self.task1, self.task3])
 
1333     def test_intersection(self):
 
1334         taskset = set([self.task1, self.task2])
 
1335         lazyset = LazyUUIDTaskSet(
 
1337             (self.task2['uuid'], self.task3['uuid'])
 
1340         assert taskset & lazyset == set([self.task2])
 
1341         assert lazyset & taskset == set([self.task2])
 
1342         assert taskset.intersection(lazyset) == set([self.task2])
 
1343         assert lazyset.intersection(taskset) == set([self.task2])
 
1346         assert lazyset == set([self.task2])
 
1349 class TaskWarriorBackendTest(TasklibTest):
 
1351     def test_config(self):
 
1352         assert self.tw.config['nag'] == "You have more urgent tasks."
 
1353         assert self.tw.config['debug'] == "no"