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'] = datetime.datetime.now() + datetime.timedelta(days=1)
 
 106         self.assertEqual(len(self.tw.tasks.waiting()), 1)
 
 108     def test_recurring_empty(self):
 
 109         Task(self.tw, description="test task").save()
 
 110         self.assertEqual(len(self.tw.tasks.recurring()), 0)
 
 112     def test_recurring_non_empty(self):
 
 113         Task(self.tw, description="test task", recur="daily",
 
 114              due=datetime.datetime.now()).save()
 
 115         self.assertEqual(len(self.tw.tasks.recurring()), 1)
 
 117     def test_filtering_by_attribute(self):
 
 118         Task(self.tw, description="no priority task").save()
 
 119         Task(self.tw, priority="H", description="high priority task").save()
 
 120         self.assertEqual(len(self.tw.tasks.all()), 2)
 
 122         # Assert that the correct number of tasks is returned
 
 123         self.assertEqual(len(self.tw.tasks.filter(priority="H")), 1)
 
 125         # Assert that the correct tasks are returned
 
 126         high_priority_task = self.tw.tasks.get(priority="H")
 
 127         self.assertEqual(high_priority_task['description'], "high priority task")
 
 129     def test_filtering_by_empty_attribute(self):
 
 130         Task(self.tw, description="no priority task").save()
 
 131         Task(self.tw, priority="H", description="high priority task").save()
 
 132         self.assertEqual(len(self.tw.tasks.all()), 2)
 
 134         # Assert that the correct number of tasks is returned
 
 135         self.assertEqual(len(self.tw.tasks.filter(priority=None)), 1)
 
 137         # Assert that the correct tasks are returned
 
 138         no_priority_task = self.tw.tasks.get(priority=None)
 
 139         self.assertEqual(no_priority_task['description'], "no priority task")
 
 141     def test_filter_for_task_with_space_in_descripition(self):
 
 142         task = Task(self.tw, description="test task")
 
 145         filtered_task = self.tw.tasks.get(description="test task")
 
 146         self.assertEqual(filtered_task['description'], "test task")
 
 148     def test_filter_for_task_without_space_in_descripition(self):
 
 149         task = Task(self.tw, description="test")
 
 152         filtered_task = self.tw.tasks.get(description="test")
 
 153         self.assertEqual(filtered_task['description'], "test")
 
 155     def test_filter_for_task_with_space_in_project(self):
 
 156         task = Task(self.tw, description="test", project="random project")
 
 159         filtered_task = self.tw.tasks.get(project="random project")
 
 160         self.assertEqual(filtered_task['project'], "random project")
 
 162     def test_filter_for_task_without_space_in_project(self):
 
 163         task = Task(self.tw, description="test", project="random")
 
 166         filtered_task = self.tw.tasks.get(project="random")
 
 167         self.assertEqual(filtered_task['project'], "random")
 
 169     def test_filter_with_empty_uuid(self):
 
 170         self.assertRaises(ValueError, lambda: self.tw.tasks.get(uuid=''))
 
 172     def test_filter_dummy_by_status(self):
 
 173         t = Task(self.tw, description="test")
 
 176         tasks = self.tw.tasks.filter(status=t['status'])
 
 177         self.assertEqual(list(tasks), [t])
 
 179     def test_filter_dummy_by_uuid(self):
 
 180         t = Task(self.tw, description="test")
 
 183         tasks = self.tw.tasks.filter(uuid=t['uuid'])
 
 184         self.assertEqual(list(tasks), [t])
 
 186     def test_filter_dummy_by_entry(self):
 
 187         t = Task(self.tw, description="test")
 
 190         tasks = self.tw.tasks.filter(entry=t['entry'])
 
 191         self.assertEqual(list(tasks), [t])
 
 193     def test_filter_dummy_by_description(self):
 
 194         t = Task(self.tw, description="test")
 
 197         tasks = self.tw.tasks.filter(description=t['description'])
 
 198         self.assertEqual(list(tasks), [t])
 
 200     def test_filter_dummy_by_start(self):
 
 201         t = Task(self.tw, description="test")
 
 205         tasks = self.tw.tasks.filter(start=t['start'])
 
 206         self.assertEqual(list(tasks), [t])
 
 208     def test_filter_dummy_by_end(self):
 
 209         t = Task(self.tw, description="test")
 
 213         tasks = self.tw.tasks.filter(end=t['end'])
 
 214         self.assertEqual(list(tasks), [t])
 
 216     def test_filter_dummy_by_due(self):
 
 217         t = Task(self.tw, description="test", due=datetime.datetime.now())
 
 220         tasks = self.tw.tasks.filter(due=t['due'])
 
 221         self.assertEqual(list(tasks), [t])
 
 223     def test_filter_dummy_by_until(self):
 
 224         t = Task(self.tw, description="test")
 
 227         tasks = self.tw.tasks.filter(until=t['until'])
 
 228         self.assertEqual(list(tasks), [t])
 
 230     def test_filter_dummy_by_modified(self):
 
 231         # Older TW version does not support bumping modified
 
 233         if self.tw.version < six.text_type('2.2.0'):
 
 234             # Python2.6 does not support SkipTest. As a workaround
 
 235             # mark the test as passed by exiting.
 
 236             if getattr(unittest, 'SkipTest', None) is not None:
 
 237                 raise unittest.SkipTest()
 
 241         t = Task(self.tw, description="test")
 
 244         tasks = self.tw.tasks.filter(modified=t['modified'])
 
 245         self.assertEqual(list(tasks), [t])
 
 247     def test_filter_dummy_by_scheduled(self):
 
 248         t = Task(self.tw, description="test")
 
 251         tasks = self.tw.tasks.filter(scheduled=t['scheduled'])
 
 252         self.assertEqual(list(tasks), [t])
 
 254     def test_filter_dummy_by_tags(self):
 
 255         t = Task(self.tw, description="test", tags=["home"])
 
 258         tasks = self.tw.tasks.filter(tags=t['tags'])
 
 259         self.assertEqual(list(tasks), [t])
 
 261     def test_filter_dummy_by_projects(self):
 
 262         t = Task(self.tw, description="test", project="random")
 
 265         tasks = self.tw.tasks.filter(project=t['project'])
 
 266         self.assertEqual(list(tasks), [t])
 
 268     def test_filter_by_priority(self):
 
 269         t = Task(self.tw, description="test", priority="H")
 
 272         tasks = self.tw.tasks.filter(priority=t['priority'])
 
 273         self.assertEqual(list(tasks), [t])
 
 276 class TaskTest(TasklibTest):
 
 278     def test_create_unsaved_task(self):
 
 279         # Make sure a new task is not saved unless explicitly called for
 
 280         t = Task(self.tw, description="test task")
 
 281         self.assertEqual(len(self.tw.tasks.all()), 0)
 
 283     # TODO: once python 2.6 compatiblity is over, use context managers here
 
 284     #       and in all subsequent tests for assertRaises
 
 286     def test_delete_unsaved_task(self):
 
 287         t = Task(self.tw, description="test task")
 
 288         self.assertRaises(Task.NotSaved, t.delete)
 
 290     def test_complete_unsaved_task(self):
 
 291         t = Task(self.tw, description="test task")
 
 292         self.assertRaises(Task.NotSaved, t.done)
 
 294     def test_refresh_unsaved_task(self):
 
 295         t = Task(self.tw, description="test task")
 
 296         self.assertRaises(Task.NotSaved, t.refresh)
 
 298     def test_start_unsaved_task(self):
 
 299         t = Task(self.tw, description="test task")
 
 300         self.assertRaises(Task.NotSaved, t.start)
 
 302     def test_delete_deleted_task(self):
 
 303         t = Task(self.tw, description="test task")
 
 307         self.assertRaises(Task.DeletedTask, t.delete)
 
 309     def test_complete_completed_task(self):
 
 310         t = Task(self.tw, description="test task")
 
 314         self.assertRaises(Task.CompletedTask, t.done)
 
 316     def test_start_completed_task(self):
 
 317         t = Task(self.tw, description="test task")
 
 321         self.assertRaises(Task.CompletedTask, t.start)
 
 323     def test_add_completed_task(self):
 
 324         t = Task(self.tw, description="test", status="completed",
 
 325                  end=datetime.datetime.now())
 
 328     def test_add_multiple_completed_tasks(self):
 
 329         t1 = Task(self.tw, description="test1", status="completed",
 
 330                  end=datetime.datetime.now())
 
 331         t2 = Task(self.tw, description="test2", status="completed",
 
 332                  end=datetime.datetime.now())
 
 336     def test_complete_deleted_task(self):
 
 337         t = Task(self.tw, description="test task")
 
 341         self.assertRaises(Task.DeletedTask, t.done)
 
 343     def test_starting_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['start'])
 
 350         self.assertEqual(t['status'], 'pending')
 
 352     def test_completing_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'], 'completed')
 
 361     def test_deleting_task(self):
 
 362         t = Task(self.tw, description="test task")
 
 363         now = t.datetime_normalizer(datetime.datetime.now())
 
 367         self.assertTrue(now.replace(microsecond=0) <= t['end'])
 
 368         self.assertEqual(t['status'], 'deleted')
 
 370     def test_started_task_active(self):
 
 371         t = Task(self.tw, description="test task")
 
 374         self.assertTrue(t.active)
 
 376     def test_unstarted_task_inactive(self):
 
 377         t = Task(self.tw, description="test task")
 
 378         self.assertFalse(t.active)
 
 380         self.assertFalse(t.active)
 
 382     def test_start_active_task(self):
 
 383         t = Task(self.tw, description="test task")
 
 386         self.assertRaises(Task.ActiveTask, t.start)
 
 388     def test_stop_completed_task(self):
 
 389         t = Task(self.tw, description="test task")
 
 394         self.assertRaises(Task.InactiveTask, t.stop)
 
 396         t = Task(self.tw, description="test task")
 
 400         self.assertRaises(Task.InactiveTask, t.stop)
 
 402     def test_stop_deleted_task(self):
 
 403         t = Task(self.tw, description="test task")
 
 409     def test_stop_inactive_task(self):
 
 410         t = Task(self.tw, description="test task")
 
 413         self.assertRaises(Task.InactiveTask, t.stop)
 
 415         t = Task(self.tw, description="test task")
 
 420         self.assertRaises(Task.InactiveTask, t.stop)
 
 422     def test_stopping_task(self):
 
 423         t = Task(self.tw, description="test task")
 
 424         now = t.datetime_normalizer(datetime.datetime.now())
 
 429         self.assertEqual(t['end'], None)
 
 430         self.assertEqual(t['status'], 'pending')
 
 431         self.assertFalse(t.active)
 
 433     def test_modify_simple_attribute_without_space(self):
 
 434         t = Task(self.tw, description="test")
 
 437         self.assertEquals(t['description'], "test")
 
 439         t['description'] = "test-modified"
 
 442         self.assertEquals(t['description'], "test-modified")
 
 444     def test_modify_simple_attribute_with_space(self):
 
 445         # Space can pose problems with parsing
 
 446         t = Task(self.tw, description="test task")
 
 449         self.assertEquals(t['description'], "test task")
 
 451         t['description'] = "test task modified"
 
 454         self.assertEquals(t['description'], "test task modified")
 
 456     def test_empty_dependency_set_of_unsaved_task(self):
 
 457         t = Task(self.tw, description="test task")
 
 458         self.assertEqual(t['depends'], set())
 
 460     def test_empty_dependency_set_of_saved_task(self):
 
 461         t = Task(self.tw, description="test task")
 
 463         self.assertEqual(t['depends'], set())
 
 465     def test_set_unsaved_task_as_dependency(self):
 
 466         # Adds only one dependency to task with no dependencies
 
 467         t = Task(self.tw, description="test task")
 
 468         dependency = Task(self.tw, description="needs to be done first")
 
 470         # We only save the parent task, dependency task is unsaved
 
 472         t['depends'] = set([dependency])
 
 474         self.assertRaises(Task.NotSaved, t.save)
 
 476     def test_set_simple_dependency_set(self):
 
 477         # Adds only one dependency to task with no dependencies
 
 478         t = Task(self.tw, description="test task")
 
 479         dependency = Task(self.tw, description="needs to be done first")
 
 484         t['depends'] = set([dependency])
 
 486         self.assertEqual(t['depends'], set([dependency]))
 
 488     def test_set_complex_dependency_set(self):
 
 489         # Adds two dependencies to task with no dependencies
 
 490         t = Task(self.tw, description="test task")
 
 491         dependency1 = Task(self.tw, description="needs to be done first")
 
 492         dependency2 = Task(self.tw, description="needs to be done second")
 
 498         t['depends'] = set([dependency1, dependency2])
 
 500         self.assertEqual(t['depends'], set([dependency1, dependency2]))
 
 502     def test_remove_from_dependency_set(self):
 
 503         # Removes dependency from task with two dependencies
 
 504         t = Task(self.tw, description="test task")
 
 505         dependency1 = Task(self.tw, description="needs to be done first")
 
 506         dependency2 = Task(self.tw, description="needs to be done second")
 
 511         t['depends'] = set([dependency1, dependency2])
 
 514         t['depends'].remove(dependency2)
 
 517         self.assertEqual(t['depends'], set([dependency1]))
 
 519     def test_add_to_dependency_set(self):
 
 520         # Adds dependency to task with one dependencies
 
 521         t = Task(self.tw, description="test task")
 
 522         dependency1 = Task(self.tw, description="needs to be done first")
 
 523         dependency2 = Task(self.tw, description="needs to be done second")
 
 528         t['depends'] = set([dependency1])
 
 531         t['depends'].add(dependency2)
 
 534         self.assertEqual(t['depends'], set([dependency1, dependency2]))
 
 536     def test_add_to_empty_dependency_set(self):
 
 537         # Adds dependency to task with one dependencies
 
 538         t = Task(self.tw, description="test task")
 
 539         dependency = Task(self.tw, description="needs to be done first")
 
 543         t['depends'].add(dependency)
 
 546         self.assertEqual(t['depends'], set([dependency]))
 
 548     def test_simple_dependency_set_save_repeatedly(self):
 
 549         # Adds only one dependency to task with no dependencies
 
 550         t = Task(self.tw, description="test task")
 
 551         dependency = Task(self.tw, description="needs to be done first")
 
 554         t['depends'] = set([dependency])
 
 557         # We taint the task, but keep depends intact
 
 558         t['description'] = "test task modified"
 
 561         self.assertEqual(t['depends'], set([dependency]))
 
 563         # We taint the task, but assign the same set to the depends
 
 564         t['depends'] = set([dependency])
 
 565         t['description'] = "test task modified again"
 
 568         self.assertEqual(t['depends'], set([dependency]))
 
 570     def test_compare_different_tasks(self):
 
 571         # Negative: compare two different tasks
 
 572         t1 = Task(self.tw, description="test task")
 
 573         t2 = Task(self.tw, description="test task")
 
 578         self.assertEqual(t1 == t2, False)
 
 580     def test_compare_same_task_object(self):
 
 581         # Compare Task object wit itself
 
 582         t = Task(self.tw, description="test task")
 
 585         self.assertEqual(t == t, True)
 
 587     def test_compare_same_task(self):
 
 588         # Compare the same task using two different objects
 
 589         t1 = Task(self.tw, description="test task")
 
 592         t2 = self.tw.tasks.get(uuid=t1['uuid'])
 
 593         self.assertEqual(t1 == t2, True)
 
 595     def test_compare_unsaved_tasks(self):
 
 596         # t1 and t2 are unsaved tasks, considered to be unequal
 
 597         # despite the content of data
 
 598         t1 = Task(self.tw, description="test task")
 
 599         t2 = Task(self.tw, description="test task")
 
 601         self.assertEqual(t1 == t2, False)
 
 603     def test_hash_unsaved_tasks(self):
 
 604         # Considered equal, it's the same object
 
 605         t1 = Task(self.tw, description="test task")
 
 607         self.assertEqual(hash(t1) == hash(t2), True)
 
 609     def test_hash_same_task(self):
 
 610         # Compare the hash of the task using two different objects
 
 611         t1 = Task(self.tw, description="test task")
 
 614         t2 = self.tw.tasks.get(uuid=t1['uuid'])
 
 615         self.assertEqual(t1.__hash__(), t2.__hash__())
 
 617     def test_adding_task_with_priority(self):
 
 618         t = Task(self.tw, description="test task", priority="M")
 
 621     def test_removing_priority_with_none(self):
 
 622         t = Task(self.tw, description="test task", priority="L")
 
 625         # Remove the priority mark
 
 629         # Assert that priority is not there after saving
 
 630         self.assertEqual(t['priority'], None)
 
 632     def test_adding_task_with_due_time(self):
 
 633         t = Task(self.tw, description="test task", due=datetime.datetime.now())
 
 636     def test_removing_due_time_with_none(self):
 
 637         t = Task(self.tw, description="test task", due=datetime.datetime.now())
 
 640         # Remove the due timestamp
 
 644         # Assert that due timestamp is no longer there
 
 645         self.assertEqual(t['due'], None)
 
 647     def test_modified_fields_new_task(self):
 
 650         # This should be empty with new task
 
 651         self.assertEqual(set(t._modified_fields), set())
 
 654         t['description'] = "test task"
 
 655         self.assertEqual(set(t._modified_fields), set(['description']))
 
 657         t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14)  # <3
 
 658         self.assertEqual(set(t._modified_fields), set(['description', 'due']))
 
 660         t['project'] = "test project"
 
 661         self.assertEqual(set(t._modified_fields),
 
 662                          set(['description', 'due', 'project']))
 
 664         # List of modified fields should clear out when saved
 
 666         self.assertEqual(set(t._modified_fields), set())
 
 668         # Reassigning the fields with the same values now should not produce
 
 670         t['description'] = "test task"
 
 671         t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14)  # <3
 
 672         t['project'] = "test project"
 
 673         self.assertEqual(set(t._modified_fields), set())
 
 675     def test_modified_fields_loaded_task(self):
 
 679         t['description'] = "test task"
 
 680         t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14)  # <3
 
 681         t['project'] = "test project"
 
 683         dependency = Task(self.tw, description="dependency")
 
 685         t['depends'] = set([dependency])
 
 687         # List of modified fields should clear out when saved
 
 689         self.assertEqual(set(t._modified_fields), set())
 
 691         # Get the task by using a filter by UUID
 
 692         t2 = self.tw.tasks.get(uuid=t['uuid'])
 
 694         # Reassigning the fields with the same values now should not produce
 
 696         t['description'] = "test task"
 
 697         t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14)  # <3
 
 698         t['project'] = "test project"
 
 699         t['depends'] = set([dependency])
 
 700         self.assertEqual(set(t._modified_fields), set())
 
 702     def test_modified_fields_not_affected_by_reading(self):
 
 705         for field in TASK_STANDARD_ATTRS:
 
 708         self.assertEqual(set(t._modified_fields), set())
 
 710     def test_setting_read_only_attrs_through_init(self):
 
 711         # Test that we are unable to set readonly attrs through __init__
 
 712         for readonly_key in Task.read_only_fields:
 
 713             kwargs = {'description': 'test task', readonly_key: 'value'}
 
 714             self.assertRaises(RuntimeError,
 
 715                               lambda: Task(self.tw, **kwargs))
 
 717     def test_setting_read_only_attrs_through_setitem(self):
 
 718         # Test that we are unable to set readonly attrs through __init__
 
 719         for readonly_key in Task.read_only_fields:
 
 720             t = Task(self.tw, description='test task')
 
 721             self.assertRaises(RuntimeError,
 
 722                               lambda: t.__setitem__(readonly_key, 'value'))
 
 724     def test_saving_unmodified_task(self):
 
 725         t = Task(self.tw, description="test task")
 
 729     def test_adding_tag_by_appending(self):
 
 730         t = Task(self.tw, description="test task", tags=['test1'])
 
 732         t['tags'].add('test2')
 
 734         self.assertEqual(t['tags'], set(['test1', 'test2']))
 
 736     def test_adding_tag_twice(self):
 
 737         t = Task(self.tw, description="test task", tags=['test1'])
 
 739         t['tags'].add('test2')
 
 740         t['tags'].add('test2')
 
 742         self.assertEqual(t['tags'], set(['test1', 'test2']))
 
 744     def test_adding_tag_by_appending_empty(self):
 
 745         t = Task(self.tw, description="test task")
 
 747         t['tags'].add('test')
 
 749         self.assertEqual(t['tags'], set(['test']))
 
 751     def test_serializers_returning_empty_string_for_none(self):
 
 752         # Test that any serializer returns '' when passed None
 
 754         serializers = [getattr(t, serializer_name) for serializer_name in
 
 755                        filter(lambda x: x.startswith('serialize_'), dir(t))]
 
 756         for serializer in serializers:
 
 757             self.assertEqual(serializer(None), '')
 
 759     def test_deserializer_returning_empty_value_for_empty_string(self):
 
 760         # Test that any deserializer returns empty value when passed ''
 
 762         deserializers = [getattr(t, deserializer_name) for deserializer_name in
 
 763                         filter(lambda x: x.startswith('deserialize_'), dir(t))]
 
 764         for deserializer in deserializers:
 
 765             self.assertTrue(deserializer('') in (None, [], set()))
 
 767     def test_normalizers_handling_none(self):
 
 768         # Test that any normalizer can handle None as a valid value
 
 771         for key in TASK_STANDARD_ATTRS:
 
 772             t._normalize(key, None)
 
 774     def test_recurrent_task_generation(self):
 
 775         today = datetime.date.today()
 
 776         t = Task(self.tw, description="brush teeth",
 
 777                  due=today, recur="daily")
 
 779         self.assertEqual(len(self.tw.tasks.pending()), 2)
 
 781     def test_spawned_task_parent(self):
 
 782         today = datetime.date.today()
 
 783         t = Task(self.tw, description="brush teeth",
 
 784                  due=today, recur="daily")
 
 787         spawned = self.tw.tasks.pending().get(due=today)
 
 788         assert spawned['parent'] == t
 
 790     def test_modify_number_of_tasks_at_once(self):
 
 791         for i in range(1, 100):
 
 792             Task(self.tw, description="test task %d" % i, tags=['test']).save()
 
 794         self.tw.execute_command(['+test', 'mod', 'unified', 'description'])
 
 796     def test_return_all_from_executed_command(self):
 
 797         Task(self.tw, description="test task", tags=['test']).save()
 
 798         out, err, rc = self.tw.execute_command(['count'], return_all=True)
 
 799         self.assertEqual(rc, 0)
 
 801     def test_return_all_from_failed_executed_command(self):
 
 802         Task(self.tw, description="test task", tags=['test']).save()
 
 803         out, err, rc = self.tw.execute_command(['countinvalid'],
 
 804             return_all=True, allow_failure=False)
 
 805         self.assertNotEqual(rc, 0)
 
 808 class TaskFromHookTest(TasklibTest):
 
 810     input_add_data = six.StringIO(
 
 811         '{"description":"Buy some milk",'
 
 812         '"entry":"20141118T050231Z",'
 
 813         '"status":"pending",'
 
 814         '"start":"20141119T152233Z",'
 
 815         '"uuid":"a360fc44-315c-4366-b70c-ea7e7520b749"}')
 
 817     input_add_data_recurring = six.StringIO(
 
 818         '{"description":"Mow the lawn",'
 
 819         '"entry":"20160210T224304Z",'
 
 820         '"parent":"62da6227-519c-42c2-915d-dccada926ad7",'
 
 822         '"status":"pending",'
 
 823         '"uuid":"81305335-0237-49ff-8e87-b3cdc2369cec"}')
 
 825     input_modify_data = six.StringIO(input_add_data.getvalue() + '\n' +
 
 826         '{"description":"Buy some milk finally",'
 
 827         '"entry":"20141118T050231Z",'
 
 828         '"status":"completed",'
 
 829         '"uuid":"a360fc44-315c-4366-b70c-ea7e7520b749"}')
 
 831     exported_raw_data = (
 
 833          '"due":"20150101T232323Z",'
 
 834          '"description":"test task"}')
 
 836     def test_setting_up_from_add_hook_input(self):
 
 837         t = Task.from_input(input_file=self.input_add_data, backend=self.tw)
 
 838         self.assertEqual(t['description'], "Buy some milk")
 
 839         self.assertEqual(t.pending, True)
 
 841     def test_setting_up_from_add_hook_input_recurring(self):
 
 842         t = Task.from_input(input_file=self.input_add_data_recurring,
 
 844         self.assertEqual(t['description'], "Mow the lawn")
 
 845         self.assertEqual(t.pending, True)
 
 847     def test_setting_up_from_modified_hook_input(self):
 
 848         t = Task.from_input(input_file=self.input_modify_data, modify=True,
 
 850         self.assertEqual(t['description'], "Buy some milk finally")
 
 851         self.assertEqual(t.pending, False)
 
 852         self.assertEqual(t.completed, True)
 
 854         self.assertEqual(t._original_data['status'], "pending")
 
 855         self.assertEqual(t._original_data['description'], "Buy some milk")
 
 856         self.assertEqual(set(t._modified_fields),
 
 857                          set(['status', 'description', 'start']))
 
 859     def test_export_data(self):
 
 860         t = Task(self.tw, description="test task",
 
 862             due=pytz.utc.localize(datetime.datetime(2015,1,1,23,23,23)))
 
 864         # Check that the output is a permutation of:
 
 865         # {"project":"Home","description":"test task","due":"20150101232323Z"}
 
 866         allowed_segments = self.exported_raw_data[1:-1].split(',')
 
 868             '{' + ','.join(segments) + '}'
 
 869             for segments in itertools.permutations(allowed_segments)
 
 872         self.assertTrue(any(t.export_data() == expected
 
 873                             for expected in allowed_output))
 
 875 class TimezoneAwareDatetimeTest(TasklibTest):
 
 878         super(TimezoneAwareDatetimeTest, self).setUp()
 
 879         self.zone = local_zone
 
 880         self.localdate_naive = datetime.datetime(2015,2,2)
 
 881         self.localtime_naive = datetime.datetime(2015,2,2,0,0,0)
 
 882         self.localtime_aware = self.zone.localize(self.localtime_naive)
 
 883         self.utctime_aware = self.localtime_aware.astimezone(pytz.utc)
 
 885     def test_timezone_naive_datetime_setitem(self):
 
 886         t = Task(self.tw, description="test task")
 
 887         t['due'] = self.localtime_naive
 
 888         self.assertEqual(t['due'], self.localtime_aware)
 
 890     def test_timezone_naive_datetime_using_init(self):
 
 891         t = Task(self.tw, description="test task", due=self.localtime_naive)
 
 892         self.assertEqual(t['due'], self.localtime_aware)
 
 894     def test_filter_by_naive_datetime(self):
 
 895         t = Task(self.tw, description="task1", due=self.localtime_naive)
 
 897         matching_tasks = self.tw.tasks.filter(due=self.localtime_naive)
 
 898         self.assertEqual(len(matching_tasks), 1)
 
 900     def test_serialize_naive_datetime(self):
 
 901         t = Task(self.tw, description="task1", due=self.localtime_naive)
 
 902         self.assertEqual(json.loads(t.export_data())['due'],
 
 903                          self.utctime_aware.strftime(DATE_FORMAT))
 
 905     def test_timezone_naive_date_setitem(self):
 
 906         t = Task(self.tw, description="test task")
 
 907         t['due'] = self.localdate_naive
 
 908         self.assertEqual(t['due'], self.localtime_aware)
 
 910     def test_timezone_naive_date_using_init(self):
 
 911         t = Task(self.tw, description="test task", due=self.localdate_naive)
 
 912         self.assertEqual(t['due'], self.localtime_aware)
 
 914     def test_filter_by_naive_date(self):
 
 915         t = Task(self.tw, description="task1", due=self.localdate_naive)
 
 917         matching_tasks = self.tw.tasks.filter(due=self.localdate_naive)
 
 918         self.assertEqual(len(matching_tasks), 1)
 
 920     def test_serialize_naive_date(self):
 
 921         t = Task(self.tw, description="task1", due=self.localdate_naive)
 
 922         self.assertEqual(json.loads(t.export_data())['due'],
 
 923                          self.utctime_aware.strftime(DATE_FORMAT))
 
 925     def test_timezone_aware_datetime_setitem(self):
 
 926         t = Task(self.tw, description="test task")
 
 927         t['due'] = self.localtime_aware
 
 928         self.assertEqual(t['due'], self.localtime_aware)
 
 930     def test_timezone_aware_datetime_using_init(self):
 
 931         t = Task(self.tw, description="test task", due=self.localtime_aware)
 
 932         self.assertEqual(t['due'], self.localtime_aware)
 
 934     def test_filter_by_aware_datetime(self):
 
 935         t = Task(self.tw, description="task1", due=self.localtime_aware)
 
 937         matching_tasks = self.tw.tasks.filter(due=self.localtime_aware)
 
 938         self.assertEqual(len(matching_tasks), 1)
 
 940     def test_serialize_aware_datetime(self):
 
 941         t = Task(self.tw, description="task1", due=self.localtime_aware)
 
 942         self.assertEqual(json.loads(t.export_data())['due'],
 
 943                          self.utctime_aware.strftime(DATE_FORMAT))
 
 945 class DatetimeStringTest(TasklibTest):
 
 947     def test_simple_now_conversion(self):
 
 948         if self.tw.version < six.text_type('2.4.0'):
 
 949             # Python2.6 does not support SkipTest. As a workaround
 
 950             # mark the test as passed by exiting.
 
 951             if getattr(unittest, 'SkipTest', None) is not None:
 
 952                 raise unittest.SkipTest()
 
 956         t = Task(self.tw, description="test task", due="now")
 
 957         now = local_zone.localize(datetime.datetime.now())
 
 959         # Assert that both times are not more than 5 seconds apart
 
 960         if sys.version_info < (2,7):
 
 961             self.assertTrue(total_seconds_2_6(now - t['due']) < 5)
 
 962             self.assertTrue(total_seconds_2_6(t['due'] - now) < 5)
 
 964             self.assertTrue((now - t['due']).total_seconds() < 5)
 
 965             self.assertTrue((t['due'] - now).total_seconds() < 5)
 
 967     def test_simple_eoy_conversion(self):
 
 968         if self.tw.version < six.text_type('2.4.0'):
 
 969             # Python2.6 does not support SkipTest. As a workaround
 
 970             # mark the test as passed by exiting.
 
 971             if getattr(unittest, 'SkipTest', None) is not None:
 
 972                 raise unittest.SkipTest()
 
 976         t = Task(self.tw, description="test task", due="eoy")
 
 977         now = local_zone.localize(datetime.datetime.now())
 
 978         eoy = local_zone.localize(datetime.datetime(
 
 986         self.assertEqual(eoy, t['due'])
 
 988     def test_complex_eoy_conversion(self):
 
 989         if self.tw.version < six.text_type('2.4.0'):
 
 990             # Python2.6 does not support SkipTest. As a workaround
 
 991             # mark the test as passed by exiting.
 
 992             if getattr(unittest, 'SkipTest', None) is not None:
 
 993                 raise unittest.SkipTest()
 
 997         t = Task(self.tw, description="test task", due="eoy - 4 months")
 
 998         now = local_zone.localize(datetime.datetime.now())
 
 999         due_date = local_zone.localize(datetime.datetime(
 
1006             )) - datetime.timedelta(0,4 * 30 * 86400)
 
1007         self.assertEqual(due_date, t['due'])
 
1009     def test_filtering_with_string_datetime(self):
 
1010         if self.tw.version < six.text_type('2.4.0'):
 
1011             # Python2.6 does not support SkipTest. As a workaround
 
1012             # mark the test as passed by exiting.
 
1013             if getattr(unittest, 'SkipTest', None) is not None:
 
1014                 raise unittest.SkipTest()
 
1018         t = Task(self.tw, description="test task",
 
1019                  due=datetime.datetime.now() - datetime.timedelta(0,2))
 
1021         self.assertEqual(len(self.tw.tasks.filter(due__before="now")), 1)
 
1023 class AnnotationTest(TasklibTest):
 
1026         super(AnnotationTest, self).setUp()
 
1027         Task(self.tw, description="test task").save()
 
1029     def test_adding_annotation(self):
 
1030         task = self.tw.tasks.get()
 
1031         task.add_annotation('test annotation')
 
1032         self.assertEqual(len(task['annotations']), 1)
 
1033         ann = task['annotations'][0]
 
1034         self.assertEqual(ann['description'], 'test annotation')
 
1036     def test_removing_annotation(self):
 
1037         task = self.tw.tasks.get()
 
1038         task.add_annotation('test annotation')
 
1039         ann = task['annotations'][0]
 
1041         self.assertEqual(len(task['annotations']), 0)
 
1043     def test_removing_annotation_by_description(self):
 
1044         task = self.tw.tasks.get()
 
1045         task.add_annotation('test annotation')
 
1046         task.remove_annotation('test annotation')
 
1047         self.assertEqual(len(task['annotations']), 0)
 
1049     def test_removing_annotation_by_obj(self):
 
1050         task = self.tw.tasks.get()
 
1051         task.add_annotation('test annotation')
 
1052         ann = task['annotations'][0]
 
1053         task.remove_annotation(ann)
 
1054         self.assertEqual(len(task['annotations']), 0)
 
1056     def test_annotation_after_modification(self):
 
1057          task = self.tw.tasks.get()
 
1058          task['project'] = 'test'
 
1059          task.add_annotation('I should really do this task')
 
1060          self.assertEqual(task['project'], 'test')
 
1062          self.assertEqual(task['project'], 'test')
 
1064     def test_serialize_annotations(self):
 
1065         # Test that serializing annotations is possible
 
1066         t = Task(self.tw, description="test")
 
1069         t.add_annotation("annotation1")
 
1070         t.add_annotation("annotation2")
 
1072         data = t._serialize('annotations', t._data['annotations'])
 
1074         self.assertEqual(len(data), 2)
 
1075         self.assertEqual(type(data[0]), dict)
 
1076         self.assertEqual(type(data[1]), dict)
 
1078         self.assertEqual(data[0]['description'], "annotation1")
 
1079         self.assertEqual(data[1]['description'], "annotation2")
 
1082 class UnicodeTest(TasklibTest):
 
1084     def test_unicode_task(self):
 
1085         Task(self.tw, description=six.u("†åßk")).save()
 
1088     def test_filter_by_unicode_task(self):
 
1089         Task(self.tw, description=six.u("†åßk")).save()
 
1090         tasks = self.tw.tasks.filter(description=six.u("†åßk"))
 
1091         self.assertEqual(len(tasks), 1)
 
1093     def test_non_unicode_task(self):
 
1094         Task(self.tw, description="test task").save()
 
1097 class ReadOnlyDictViewTest(unittest.TestCase):
 
1100         self.sample = dict(l=[1,2,3], d={'k':'v'})
 
1101         self.original_sample = copy.deepcopy(self.sample)
 
1102         self.view = ReadOnlyDictView(self.sample)
 
1104     def test_readonlydictview_getitem(self):
 
1106         self.assertEqual(l, self.sample['l'])
 
1108         # Assert that modification changed only copied value
 
1110         self.assertNotEqual(l, self.sample['l'])
 
1112         # Assert that viewed dict is not changed
 
1113         self.assertEqual(self.sample, self.original_sample)
 
1115     def test_readonlydictview_contains(self):
 
1116         self.assertEqual('l' in self.view, 'l' in self.sample)
 
1117         self.assertEqual('d' in self.view, 'd' in self.sample)
 
1118         self.assertEqual('k' in self.view, 'k' in self.sample)
 
1120         # Assert that viewed dict is not changed
 
1121         self.assertEqual(self.sample, self.original_sample)
 
1123     def test_readonlydictview_iter(self):
 
1124         self.assertEqual(list(k for k in self.view),
 
1125                          list(k for k in self.sample))
 
1127         # Assert the view is correct after modification
 
1128         self.sample['new'] = 'value'
 
1129         self.assertEqual(list(k for k in self.view),
 
1130                          list(k for k in self.sample))
 
1132     def test_readonlydictview_len(self):
 
1133         self.assertEqual(len(self.view), len(self.sample))
 
1135         # Assert the view is correct after modification
 
1136         self.sample['new'] = 'value'
 
1137         self.assertEqual(len(self.view), len(self.sample))
 
1139     def test_readonlydictview_get(self):
 
1140         l = self.view.get('l')
 
1141         self.assertEqual(l, self.sample.get('l'))
 
1143         # Assert that modification changed only copied value
 
1145         self.assertNotEqual(l, self.sample.get('l'))
 
1147         # Assert that viewed dict is not changed
 
1148         self.assertEqual(self.sample, self.original_sample)
 
1150     def test_readonlydict_items(self):
 
1151         view_items = self.view.items()
 
1152         sample_items = list(self.sample.items())
 
1153         self.assertEqual(view_items, sample_items)
 
1155         view_items.append('newkey')
 
1156         self.assertNotEqual(view_items, sample_items)
 
1157         self.assertEqual(self.sample, self.original_sample)
 
1159     def test_readonlydict_values(self):
 
1160         view_values = self.view.values()
 
1161         sample_values = list(self.sample.values())
 
1162         self.assertEqual(view_values, sample_values)
 
1164         view_list_item = list(filter(lambda x: type(x) is list,
 
1166         view_list_item.append(4)
 
1167         self.assertNotEqual(view_values, sample_values)
 
1168         self.assertEqual(self.sample, self.original_sample)
 
1171 class LazyUUIDTaskTest(TasklibTest):
 
1174         super(LazyUUIDTaskTest, self).setUp()
 
1176         self.stored = Task(self.tw, description="this is test task")
 
1179         self.lazy = LazyUUIDTask(self.tw, self.stored['uuid'])
 
1181     def test_uuid_non_conversion(self):
 
1182         assert self.stored['uuid'] == self.lazy['uuid']
 
1183         assert type(self.lazy) is LazyUUIDTask
 
1185     def test_lazy_explicit_conversion(self):
 
1186         assert type(self.lazy) is LazyUUIDTask
 
1188         assert type(self.lazy) is Task
 
1190     def test_conversion_key(self):
 
1191         assert self.stored['description'] == self.lazy['description']
 
1192         assert type(self.lazy) is Task
 
1194     def test_conversion_attribute(self):
 
1195         assert type(self.lazy) is LazyUUIDTask
 
1196         assert self.lazy.completed is False
 
1197         assert type(self.lazy) is Task
 
1199     def test_normal_to_lazy_equality(self):
 
1200         assert self.stored == self.lazy
 
1201         assert not self.stored != self.lazy
 
1202         assert type(self.lazy) is LazyUUIDTask
 
1204     def test_lazy_to_lazy_equality(self):
 
1205         lazy1 = LazyUUIDTask(self.tw, self.stored['uuid'])
 
1206         lazy2 = LazyUUIDTask(self.tw, self.stored['uuid'])
 
1208         assert lazy1 == lazy2
 
1209         assert not lazy1 != lazy2
 
1210         assert type(lazy1) is LazyUUIDTask
 
1211         assert type(lazy2) is LazyUUIDTask
 
1213     def test_normal_to_lazy_inequality(self):
 
1214         # Create a different UUID by changing the last letter
 
1215         wrong_uuid = self.stored['uuid']
 
1216         wrong_uuid = wrong_uuid[:-1] + ('a' if wrong_uuid[-1] != 'a' else 'b')
 
1218         wrong_lazy = LazyUUIDTask(self.tw, wrong_uuid)
 
1220         assert not self.stored == wrong_lazy
 
1221         assert self.stored != wrong_lazy
 
1222         assert type(wrong_lazy) is LazyUUIDTask
 
1224     def test_lazy_to_lazy_inequality(self):
 
1225         # Create a different UUID by changing the last letter
 
1226         wrong_uuid = self.stored['uuid']
 
1227         wrong_uuid = wrong_uuid[:-1] + ('a' if wrong_uuid[-1] != 'a' else 'b')
 
1229         lazy1 = LazyUUIDTask(self.tw, self.stored['uuid'])
 
1230         lazy2 = LazyUUIDTask(self.tw, wrong_uuid)
 
1232         assert not lazy1 == lazy2
 
1233         assert lazy1 != lazy2
 
1234         assert type(lazy1) is LazyUUIDTask
 
1235         assert type(lazy2) is LazyUUIDTask
 
1237     def test_lazy_in_queryset(self):
 
1238         tasks = self.tw.tasks.filter(uuid=self.stored['uuid'])
 
1240         assert self.lazy in tasks
 
1241         assert type(self.lazy) is LazyUUIDTask
 
1243     def test_lazy_saved(self):
 
1244         assert self.lazy.saved is True
 
1246     def test_lazy_modified(self):
 
1247         assert self.lazy.modified is False
 
1249     def test_lazy_modified_fields(self):
 
1250         assert self.lazy._modified_fields == set()
 
1253 class LazyUUIDTaskSetTest(TasklibTest):
 
1256         super(LazyUUIDTaskSetTest, self).setUp()
 
1258         self.task1 = Task(self.tw, description="task 1")
 
1259         self.task2 = Task(self.tw, description="task 2")
 
1260         self.task3 = Task(self.tw, description="task 3")
 
1272         self.lazy = LazyUUIDTaskSet(self.tw, self.uuids)
 
1274     def test_length(self):
 
1275         assert len(self.lazy) == 3
 
1276         assert type(self.lazy) is LazyUUIDTaskSet
 
1278     def test_contains(self):
 
1279         assert self.task1 in self.lazy
 
1280         assert self.task2 in self.lazy
 
1281         assert self.task3 in self.lazy
 
1282         assert type(self.lazy) is LazyUUIDTaskSet
 
1284     def test_eq_lazy(self):
 
1285         new_lazy = LazyUUIDTaskSet(self.tw, self.uuids)
 
1286         assert self.lazy == new_lazy
 
1287         assert not self.lazy != new_lazy
 
1288         assert type(self.lazy) is LazyUUIDTaskSet
 
1290     def test_eq_real(self):
 
1291         assert self.lazy == self.tw.tasks.all()
 
1292         assert self.tw.tasks.all() == self.lazy
 
1293         assert not self.lazy != self.tw.tasks.all()
 
1295         assert type(self.lazy) is LazyUUIDTaskSet
 
1297     def test_union(self):
 
1298         taskset = set([self.task1])
 
1299         lazyset = LazyUUIDTaskSet(
 
1301             (self.task2['uuid'], self.task3['uuid'])
 
1304         assert taskset | lazyset == self.lazy
 
1305         assert lazyset | taskset == self.lazy
 
1306         assert taskset.union(lazyset) == self.lazy
 
1307         assert lazyset.union(taskset) == self.lazy
 
1310         assert lazyset == self.lazy
 
1312     def test_difference(self):
 
1313         taskset = set([self.task1, self.task2])
 
1314         lazyset = LazyUUIDTaskSet(
 
1316             (self.task2['uuid'], self.task3['uuid'])
 
1319         assert taskset - lazyset == set([self.task1])
 
1320         assert lazyset - taskset == set([self.task3])
 
1321         assert taskset.difference(lazyset) == set([self.task1])
 
1322         assert lazyset.difference(taskset) == set([self.task3])
 
1325         assert lazyset == set([self.task3])
 
1327     def test_symmetric_difference(self):
 
1328         taskset = set([self.task1, self.task2])
 
1329         lazyset = LazyUUIDTaskSet(
 
1331             (self.task2['uuid'], self.task3['uuid'])
 
1334         assert taskset ^ lazyset == set([self.task1, self.task3])
 
1335         assert lazyset ^ taskset == set([self.task1, self.task3])
 
1336         assert taskset.symmetric_difference(lazyset) == set([self.task1, self.task3])
 
1337         assert lazyset.symmetric_difference(taskset) == set([self.task1, self.task3])
 
1340         assert lazyset == set([self.task1, self.task3])
 
1342     def test_intersection(self):
 
1343         taskset = set([self.task1, self.task2])
 
1344         lazyset = LazyUUIDTaskSet(
 
1346             (self.task2['uuid'], self.task3['uuid'])
 
1349         assert taskset & lazyset == set([self.task2])
 
1350         assert lazyset & taskset == set([self.task2])
 
1351         assert taskset.intersection(lazyset) == set([self.task2])
 
1352         assert lazyset.intersection(taskset) == set([self.task2])
 
1355         assert lazyset == set([self.task2])
 
1358 class TaskWarriorBackendTest(TasklibTest):
 
1360     def test_config(self):
 
1361         assert self.tw.config['nag'] == "You have more urgent tasks."
 
1362         assert self.tw.config['debug'] == "no"