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
 
  16 from .serializing import DATE_FORMAT, local_zone
 
  18 # http://taskwarrior.org/docs/design/task.html , Section: The Attributes
 
  19 TASK_STANDARD_ATTRS = (
 
  42 total_seconds_2_6 = lambda x: x.microseconds / 1e6 + x.seconds + x.days * 24 * 3600
 
  45 class TasklibTest(unittest.TestCase):
 
  48         self.tmp = tempfile.mkdtemp(dir='.')
 
  49         self.tw = TaskWarrior(data_location=self.tmp, taskrc_location='/')
 
  52         shutil.rmtree(self.tmp)
 
  55 class TaskFilterTest(TasklibTest):
 
  57     def test_all_empty(self):
 
  58         self.assertEqual(len(self.tw.tasks.all()), 0)
 
  60     def test_all_non_empty(self):
 
  61         Task(self.tw, description="test task").save()
 
  62         self.assertEqual(len(self.tw.tasks.all()), 1)
 
  63         self.assertEqual(self.tw.tasks.all()[0]['description'], 'test task')
 
  64         self.assertEqual(self.tw.tasks.all()[0]['status'], 'pending')
 
  66     def test_pending_non_empty(self):
 
  67         Task(self.tw, description="test task").save()
 
  68         self.assertEqual(len(self.tw.tasks.pending()), 1)
 
  69         self.assertEqual(self.tw.tasks.pending()[0]['description'],
 
  71         self.assertEqual(self.tw.tasks.pending()[0]['status'], 'pending')
 
  73     def test_completed_empty(self):
 
  74         Task(self.tw, description="test task").save()
 
  75         self.assertEqual(len(self.tw.tasks.completed()), 0)
 
  77     def test_completed_non_empty(self):
 
  78         Task(self.tw, description="test task").save()
 
  79         self.assertEqual(len(self.tw.tasks.completed()), 0)
 
  80         self.tw.tasks.all()[0].done()
 
  81         self.assertEqual(len(self.tw.tasks.completed()), 1)
 
  83     def test_filtering_by_attribute(self):
 
  84         Task(self.tw, description="no priority task").save()
 
  85         Task(self.tw, priority="H", description="high priority task").save()
 
  86         self.assertEqual(len(self.tw.tasks.all()), 2)
 
  88         # Assert that the correct number of tasks is returned
 
  89         self.assertEqual(len(self.tw.tasks.filter(priority="H")), 1)
 
  91         # Assert that the correct tasks are returned
 
  92         high_priority_task = self.tw.tasks.get(priority="H")
 
  93         self.assertEqual(high_priority_task['description'], "high priority task")
 
  95     def test_filtering_by_empty_attribute(self):
 
  96         Task(self.tw, description="no priority task").save()
 
  97         Task(self.tw, priority="H", description="high priority task").save()
 
  98         self.assertEqual(len(self.tw.tasks.all()), 2)
 
 100         # Assert that the correct number of tasks is returned
 
 101         self.assertEqual(len(self.tw.tasks.filter(priority=None)), 1)
 
 103         # Assert that the correct tasks are returned
 
 104         no_priority_task = self.tw.tasks.get(priority=None)
 
 105         self.assertEqual(no_priority_task['description'], "no priority task")
 
 107     def test_filter_for_task_with_space_in_descripition(self):
 
 108         task = Task(self.tw, description="test task")
 
 111         filtered_task = self.tw.tasks.get(description="test task")
 
 112         self.assertEqual(filtered_task['description'], "test task")
 
 114     def test_filter_for_task_without_space_in_descripition(self):
 
 115         task = Task(self.tw, description="test")
 
 118         filtered_task = self.tw.tasks.get(description="test")
 
 119         self.assertEqual(filtered_task['description'], "test")
 
 121     def test_filter_for_task_with_space_in_project(self):
 
 122         task = Task(self.tw, description="test", project="random project")
 
 125         filtered_task = self.tw.tasks.get(project="random project")
 
 126         self.assertEqual(filtered_task['project'], "random project")
 
 128     def test_filter_for_task_without_space_in_project(self):
 
 129         task = Task(self.tw, description="test", project="random")
 
 132         filtered_task = self.tw.tasks.get(project="random")
 
 133         self.assertEqual(filtered_task['project'], "random")
 
 135     def test_filter_with_empty_uuid(self):
 
 136         self.assertRaises(ValueError, lambda: self.tw.tasks.get(uuid=''))
 
 138     def test_filter_dummy_by_status(self):
 
 139         t = Task(self.tw, description="test")
 
 142         tasks = self.tw.tasks.filter(status=t['status'])
 
 143         self.assertEqual(list(tasks), [t])
 
 145     def test_filter_dummy_by_uuid(self):
 
 146         t = Task(self.tw, description="test")
 
 149         tasks = self.tw.tasks.filter(uuid=t['uuid'])
 
 150         self.assertEqual(list(tasks), [t])
 
 152     def test_filter_dummy_by_entry(self):
 
 153         t = Task(self.tw, description="test")
 
 156         tasks = self.tw.tasks.filter(entry=t['entry'])
 
 157         self.assertEqual(list(tasks), [t])
 
 159     def test_filter_dummy_by_description(self):
 
 160         t = Task(self.tw, description="test")
 
 163         tasks = self.tw.tasks.filter(description=t['description'])
 
 164         self.assertEqual(list(tasks), [t])
 
 166     def test_filter_dummy_by_start(self):
 
 167         t = Task(self.tw, description="test")
 
 171         tasks = self.tw.tasks.filter(start=t['start'])
 
 172         self.assertEqual(list(tasks), [t])
 
 174     def test_filter_dummy_by_end(self):
 
 175         t = Task(self.tw, description="test")
 
 179         tasks = self.tw.tasks.filter(end=t['end'])
 
 180         self.assertEqual(list(tasks), [t])
 
 182     def test_filter_dummy_by_due(self):
 
 183         t = Task(self.tw, description="test", due=datetime.datetime.now())
 
 186         tasks = self.tw.tasks.filter(due=t['due'])
 
 187         self.assertEqual(list(tasks), [t])
 
 189     def test_filter_dummy_by_until(self):
 
 190         t = Task(self.tw, description="test")
 
 193         tasks = self.tw.tasks.filter(until=t['until'])
 
 194         self.assertEqual(list(tasks), [t])
 
 196     def test_filter_dummy_by_modified(self):
 
 197         # Older TW version does not support bumping modified
 
 199         if self.tw.version < six.text_type('2.2.0'):
 
 200             # Python2.6 does not support SkipTest. As a workaround
 
 201             # mark the test as passed by exiting.
 
 202             if getattr(unittest, 'SkipTest', None) is not None:
 
 203                 raise unittest.SkipTest()
 
 207         t = Task(self.tw, description="test")
 
 210         tasks = self.tw.tasks.filter(modified=t['modified'])
 
 211         self.assertEqual(list(tasks), [t])
 
 213     def test_filter_dummy_by_scheduled(self):
 
 214         t = Task(self.tw, description="test")
 
 217         tasks = self.tw.tasks.filter(scheduled=t['scheduled'])
 
 218         self.assertEqual(list(tasks), [t])
 
 220     def test_filter_dummy_by_tags(self):
 
 221         t = Task(self.tw, description="test", tags=["home"])
 
 224         tasks = self.tw.tasks.filter(tags=t['tags'])
 
 225         self.assertEqual(list(tasks), [t])
 
 227     def test_filter_dummy_by_projects(self):
 
 228         t = Task(self.tw, description="test", project="random")
 
 231         tasks = self.tw.tasks.filter(project=t['project'])
 
 232         self.assertEqual(list(tasks), [t])
 
 234     def test_filter_by_priority(self):
 
 235         t = Task(self.tw, description="test", priority="H")
 
 238         tasks = self.tw.tasks.filter(priority=t['priority'])
 
 239         self.assertEqual(list(tasks), [t])
 
 242 class TaskTest(TasklibTest):
 
 244     def test_create_unsaved_task(self):
 
 245         # Make sure a new task is not saved unless explicitly called for
 
 246         t = Task(self.tw, description="test task")
 
 247         self.assertEqual(len(self.tw.tasks.all()), 0)
 
 249     # TODO: once python 2.6 compatiblity is over, use context managers here
 
 250     #       and in all subsequent tests for assertRaises
 
 252     def test_delete_unsaved_task(self):
 
 253         t = Task(self.tw, description="test task")
 
 254         self.assertRaises(Task.NotSaved, t.delete)
 
 256     def test_complete_unsaved_task(self):
 
 257         t = Task(self.tw, description="test task")
 
 258         self.assertRaises(Task.NotSaved, t.done)
 
 260     def test_refresh_unsaved_task(self):
 
 261         t = Task(self.tw, description="test task")
 
 262         self.assertRaises(Task.NotSaved, t.refresh)
 
 264     def test_start_unsaved_task(self):
 
 265         t = Task(self.tw, description="test task")
 
 266         self.assertRaises(Task.NotSaved, t.start)
 
 268     def test_delete_deleted_task(self):
 
 269         t = Task(self.tw, description="test task")
 
 273         self.assertRaises(Task.DeletedTask, t.delete)
 
 275     def test_complete_completed_task(self):
 
 276         t = Task(self.tw, description="test task")
 
 280         self.assertRaises(Task.CompletedTask, t.done)
 
 282     def test_start_completed_task(self):
 
 283         t = Task(self.tw, description="test task")
 
 287         self.assertRaises(Task.CompletedTask, t.start)
 
 289     def test_add_completed_task(self):
 
 290         t = Task(self.tw, description="test", status="completed",
 
 291                  end=datetime.datetime.now())
 
 294     def test_add_multiple_completed_tasks(self):
 
 295         t1 = Task(self.tw, description="test1", status="completed",
 
 296                  end=datetime.datetime.now())
 
 297         t2 = Task(self.tw, description="test2", status="completed",
 
 298                  end=datetime.datetime.now())
 
 302     def test_complete_deleted_task(self):
 
 303         t = Task(self.tw, description="test task")
 
 307         self.assertRaises(Task.DeletedTask, t.done)
 
 309     def test_starting_task(self):
 
 310         t = Task(self.tw, description="test task")
 
 311         now = t.datetime_normalizer(datetime.datetime.now())
 
 315         self.assertTrue(now.replace(microsecond=0) <= t['start'])
 
 316         self.assertEqual(t['status'], 'pending')
 
 318     def test_completing_task(self):
 
 319         t = Task(self.tw, description="test task")
 
 320         now = t.datetime_normalizer(datetime.datetime.now())
 
 324         self.assertTrue(now.replace(microsecond=0) <= t['end'])
 
 325         self.assertEqual(t['status'], 'completed')
 
 327     def test_deleting_task(self):
 
 328         t = Task(self.tw, description="test task")
 
 329         now = t.datetime_normalizer(datetime.datetime.now())
 
 333         self.assertTrue(now.replace(microsecond=0) <= t['end'])
 
 334         self.assertEqual(t['status'], 'deleted')
 
 336     def test_started_task_active(self):
 
 337         t = Task(self.tw, description="test task")
 
 340         self.assertTrue(t.active)
 
 342     def test_unstarted_task_inactive(self):
 
 343         t = Task(self.tw, description="test task")
 
 344         self.assertFalse(t.active)
 
 346         self.assertFalse(t.active)
 
 348     def test_start_active_task(self):
 
 349         t = Task(self.tw, description="test task")
 
 352         self.assertRaises(Task.ActiveTask, t.start)
 
 354     def test_stop_completed_task(self):
 
 355         t = Task(self.tw, description="test task")
 
 360         self.assertRaises(Task.InactiveTask, t.stop)
 
 362         t = Task(self.tw, description="test task")
 
 366         self.assertRaises(Task.InactiveTask, t.stop)
 
 368     def test_stop_deleted_task(self):
 
 369         t = Task(self.tw, description="test task")
 
 375     def test_stop_inactive_task(self):
 
 376         t = Task(self.tw, description="test task")
 
 379         self.assertRaises(Task.InactiveTask, t.stop)
 
 381         t = Task(self.tw, description="test task")
 
 386         self.assertRaises(Task.InactiveTask, t.stop)
 
 388     def test_stopping_task(self):
 
 389         t = Task(self.tw, description="test task")
 
 390         now = t.datetime_normalizer(datetime.datetime.now())
 
 395         self.assertEqual(t['end'], None)
 
 396         self.assertEqual(t['status'], 'pending')
 
 397         self.assertFalse(t.active)
 
 399     def test_modify_simple_attribute_without_space(self):
 
 400         t = Task(self.tw, description="test")
 
 403         self.assertEquals(t['description'], "test")
 
 405         t['description'] = "test-modified"
 
 408         self.assertEquals(t['description'], "test-modified")
 
 410     def test_modify_simple_attribute_with_space(self):
 
 411         # Space can pose problems with parsing
 
 412         t = Task(self.tw, description="test task")
 
 415         self.assertEquals(t['description'], "test task")
 
 417         t['description'] = "test task modified"
 
 420         self.assertEquals(t['description'], "test task modified")
 
 422     def test_empty_dependency_set_of_unsaved_task(self):
 
 423         t = Task(self.tw, description="test task")
 
 424         self.assertEqual(t['depends'], set())
 
 426     def test_empty_dependency_set_of_saved_task(self):
 
 427         t = Task(self.tw, description="test task")
 
 429         self.assertEqual(t['depends'], set())
 
 431     def test_set_unsaved_task_as_dependency(self):
 
 432         # Adds only one dependency to task with no dependencies
 
 433         t = Task(self.tw, description="test task")
 
 434         dependency = Task(self.tw, description="needs to be done first")
 
 436         # We only save the parent task, dependency task is unsaved
 
 438         t['depends'] = set([dependency])
 
 440         self.assertRaises(Task.NotSaved, t.save)
 
 442     def test_set_simple_dependency_set(self):
 
 443         # Adds only one dependency to task with no dependencies
 
 444         t = Task(self.tw, description="test task")
 
 445         dependency = Task(self.tw, description="needs to be done first")
 
 450         t['depends'] = set([dependency])
 
 452         self.assertEqual(t['depends'], set([dependency]))
 
 454     def test_set_complex_dependency_set(self):
 
 455         # Adds two dependencies to task with no dependencies
 
 456         t = Task(self.tw, description="test task")
 
 457         dependency1 = Task(self.tw, description="needs to be done first")
 
 458         dependency2 = Task(self.tw, description="needs to be done second")
 
 464         t['depends'] = set([dependency1, dependency2])
 
 466         self.assertEqual(t['depends'], set([dependency1, dependency2]))
 
 468     def test_remove_from_dependency_set(self):
 
 469         # Removes dependency from task with two dependencies
 
 470         t = Task(self.tw, description="test task")
 
 471         dependency1 = Task(self.tw, description="needs to be done first")
 
 472         dependency2 = Task(self.tw, description="needs to be done second")
 
 477         t['depends'] = set([dependency1, dependency2])
 
 480         t['depends'].remove(dependency2)
 
 483         self.assertEqual(t['depends'], set([dependency1]))
 
 485     def test_add_to_dependency_set(self):
 
 486         # Adds dependency to task with one dependencies
 
 487         t = Task(self.tw, description="test task")
 
 488         dependency1 = Task(self.tw, description="needs to be done first")
 
 489         dependency2 = Task(self.tw, description="needs to be done second")
 
 494         t['depends'] = set([dependency1])
 
 497         t['depends'].add(dependency2)
 
 500         self.assertEqual(t['depends'], set([dependency1, dependency2]))
 
 502     def test_add_to_empty_dependency_set(self):
 
 503         # Adds dependency to task with one dependencies
 
 504         t = Task(self.tw, description="test task")
 
 505         dependency = Task(self.tw, description="needs to be done first")
 
 509         t['depends'].add(dependency)
 
 512         self.assertEqual(t['depends'], set([dependency]))
 
 514     def test_simple_dependency_set_save_repeatedly(self):
 
 515         # Adds only one dependency to task with no dependencies
 
 516         t = Task(self.tw, description="test task")
 
 517         dependency = Task(self.tw, description="needs to be done first")
 
 520         t['depends'] = set([dependency])
 
 523         # We taint the task, but keep depends intact
 
 524         t['description'] = "test task modified"
 
 527         self.assertEqual(t['depends'], set([dependency]))
 
 529         # We taint the task, but assign the same set to the depends
 
 530         t['depends'] = set([dependency])
 
 531         t['description'] = "test task modified again"
 
 534         self.assertEqual(t['depends'], set([dependency]))
 
 536     def test_compare_different_tasks(self):
 
 537         # Negative: compare two different tasks
 
 538         t1 = Task(self.tw, description="test task")
 
 539         t2 = Task(self.tw, description="test task")
 
 544         self.assertEqual(t1 == t2, False)
 
 546     def test_compare_same_task_object(self):
 
 547         # Compare Task object wit itself
 
 548         t = Task(self.tw, description="test task")
 
 551         self.assertEqual(t == t, True)
 
 553     def test_compare_same_task(self):
 
 554         # Compare the same task using two different objects
 
 555         t1 = Task(self.tw, description="test task")
 
 558         t2 = self.tw.tasks.get(uuid=t1['uuid'])
 
 559         self.assertEqual(t1 == t2, True)
 
 561     def test_compare_unsaved_tasks(self):
 
 562         # t1 and t2 are unsaved tasks, considered to be unequal
 
 563         # despite the content of data
 
 564         t1 = Task(self.tw, description="test task")
 
 565         t2 = Task(self.tw, description="test task")
 
 567         self.assertEqual(t1 == t2, False)
 
 569     def test_hash_unsaved_tasks(self):
 
 570         # Considered equal, it's the same object
 
 571         t1 = Task(self.tw, description="test task")
 
 573         self.assertEqual(hash(t1) == hash(t2), True)
 
 575     def test_hash_same_task(self):
 
 576         # Compare the hash of the task using two different objects
 
 577         t1 = Task(self.tw, description="test task")
 
 580         t2 = self.tw.tasks.get(uuid=t1['uuid'])
 
 581         self.assertEqual(t1.__hash__(), t2.__hash__())
 
 583     def test_adding_task_with_priority(self):
 
 584         t = Task(self.tw, description="test task", priority="M")
 
 587     def test_removing_priority_with_none(self):
 
 588         t = Task(self.tw, description="test task", priority="L")
 
 591         # Remove the priority mark
 
 595         # Assert that priority is not there after saving
 
 596         self.assertEqual(t['priority'], None)
 
 598     def test_adding_task_with_due_time(self):
 
 599         t = Task(self.tw, description="test task", due=datetime.datetime.now())
 
 602     def test_removing_due_time_with_none(self):
 
 603         t = Task(self.tw, description="test task", due=datetime.datetime.now())
 
 606         # Remove the due timestamp
 
 610         # Assert that due timestamp is no longer there
 
 611         self.assertEqual(t['due'], None)
 
 613     def test_modified_fields_new_task(self):
 
 616         # This should be empty with new task
 
 617         self.assertEqual(set(t._modified_fields), set())
 
 620         t['description'] = "test task"
 
 621         self.assertEqual(set(t._modified_fields), set(['description']))
 
 623         t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14)  # <3
 
 624         self.assertEqual(set(t._modified_fields), set(['description', 'due']))
 
 626         t['project'] = "test project"
 
 627         self.assertEqual(set(t._modified_fields),
 
 628                          set(['description', 'due', 'project']))
 
 630         # List of modified fields should clear out when saved
 
 632         self.assertEqual(set(t._modified_fields), set())
 
 634         # Reassigning the fields with the same values now should not produce
 
 636         t['description'] = "test task"
 
 637         t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14)  # <3
 
 638         t['project'] = "test project"
 
 639         self.assertEqual(set(t._modified_fields), set())
 
 641     def test_modified_fields_loaded_task(self):
 
 645         t['description'] = "test task"
 
 646         t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14)  # <3
 
 647         t['project'] = "test project"
 
 649         dependency = Task(self.tw, description="dependency")
 
 651         t['depends'] = set([dependency])
 
 653         # List of modified fields should clear out when saved
 
 655         self.assertEqual(set(t._modified_fields), set())
 
 657         # Get the task by using a filter by UUID
 
 658         t2 = self.tw.tasks.get(uuid=t['uuid'])
 
 660         # Reassigning the fields with the same values now should not produce
 
 662         t['description'] = "test task"
 
 663         t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14)  # <3
 
 664         t['project'] = "test project"
 
 665         t['depends'] = set([dependency])
 
 666         self.assertEqual(set(t._modified_fields), set())
 
 668     def test_modified_fields_not_affected_by_reading(self):
 
 671         for field in TASK_STANDARD_ATTRS:
 
 674         self.assertEqual(set(t._modified_fields), set())
 
 676     def test_setting_read_only_attrs_through_init(self):
 
 677         # Test that we are unable to set readonly attrs through __init__
 
 678         for readonly_key in Task.read_only_fields:
 
 679             kwargs = {'description': 'test task', readonly_key: 'value'}
 
 680             self.assertRaises(RuntimeError,
 
 681                               lambda: Task(self.tw, **kwargs))
 
 683     def test_setting_read_only_attrs_through_setitem(self):
 
 684         # Test that we are unable to set readonly attrs through __init__
 
 685         for readonly_key in Task.read_only_fields:
 
 686             t = Task(self.tw, description='test task')
 
 687             self.assertRaises(RuntimeError,
 
 688                               lambda: t.__setitem__(readonly_key, 'value'))
 
 690     def test_saving_unmodified_task(self):
 
 691         t = Task(self.tw, description="test task")
 
 695     def test_adding_tag_by_appending(self):
 
 696         t = Task(self.tw, description="test task", tags=['test1'])
 
 698         t['tags'].append('test2')
 
 700         self.assertEqual(t['tags'], ['test1', 'test2'])
 
 702     def test_adding_tag_by_appending_empty(self):
 
 703         t = Task(self.tw, description="test task")
 
 705         t['tags'].append('test')
 
 707         self.assertEqual(t['tags'], ['test'])
 
 709     def test_serializers_returning_empty_string_for_none(self):
 
 710         # Test that any serializer returns '' when passed None
 
 712         serializers = [getattr(t, serializer_name) for serializer_name in
 
 713                        filter(lambda x: x.startswith('serialize_'), dir(t))]
 
 714         for serializer in serializers:
 
 715             self.assertEqual(serializer(None), '')
 
 717     def test_deserializer_returning_empty_value_for_empty_string(self):
 
 718         # Test that any deserializer returns empty value when passed ''
 
 720         deserializers = [getattr(t, deserializer_name) for deserializer_name in
 
 721                         filter(lambda x: x.startswith('deserialize_'), dir(t))]
 
 722         for deserializer in deserializers:
 
 723             self.assertTrue(deserializer('') in (None, [], set()))
 
 725     def test_normalizers_handling_none(self):
 
 726         # Test that any normalizer can handle None as a valid value
 
 729         for key in TASK_STANDARD_ATTRS:
 
 730             t._normalize(key, None)
 
 732     def test_recurrent_task_generation(self):
 
 733         today = datetime.date.today()
 
 734         t = Task(self.tw, description="brush teeth",
 
 735                  due=today, recur="daily")
 
 737         self.assertEqual(len(self.tw.tasks.pending()), 2)
 
 739     def test_modify_number_of_tasks_at_once(self):
 
 740         for i in range(1, 100):
 
 741             Task(self.tw, description="test task %d" % i, tags=['test']).save()
 
 743         self.tw.execute_command(['+test', 'mod', 'unified', 'description'])
 
 745     def test_return_all_from_executed_command(self):
 
 746         Task(self.tw, description="test task", tags=['test']).save()
 
 747         out, err, rc = self.tw.execute_command(['count'], return_all=True)
 
 748         self.assertEqual(rc, 0)
 
 750     def test_return_all_from_failed_executed_command(self):
 
 751         Task(self.tw, description="test task", tags=['test']).save()
 
 752         out, err, rc = self.tw.execute_command(['countinvalid'],
 
 753             return_all=True, allow_failure=False)
 
 754         self.assertNotEqual(rc, 0)
 
 757 class TaskFromHookTest(TasklibTest):
 
 759     input_add_data = six.StringIO(
 
 760         '{"description":"Buy some milk",'
 
 761         '"entry":"20141118T050231Z",'
 
 762         '"status":"pending",'
 
 763         '"start":"20141119T152233Z",'
 
 764         '"uuid":"a360fc44-315c-4366-b70c-ea7e7520b749"}')
 
 766     input_modify_data = six.StringIO(input_add_data.getvalue() + '\n' +
 
 767         '{"description":"Buy some milk finally",'
 
 768         '"entry":"20141118T050231Z",'
 
 769         '"status":"completed",'
 
 770         '"uuid":"a360fc44-315c-4366-b70c-ea7e7520b749"}')
 
 772     exported_raw_data = (
 
 774          '"due":"20150101T232323Z",'
 
 775          '"description":"test task"}')
 
 777     def test_setting_up_from_add_hook_input(self):
 
 778         t = Task.from_input(input_file=self.input_add_data, backend=self.tw)
 
 779         self.assertEqual(t['description'], "Buy some milk")
 
 780         self.assertEqual(t.pending, True)
 
 782     def test_setting_up_from_modified_hook_input(self):
 
 783         t = Task.from_input(input_file=self.input_modify_data, modify=True,
 
 785         self.assertEqual(t['description'], "Buy some milk finally")
 
 786         self.assertEqual(t.pending, False)
 
 787         self.assertEqual(t.completed, True)
 
 789         self.assertEqual(t._original_data['status'], "pending")
 
 790         self.assertEqual(t._original_data['description'], "Buy some milk")
 
 791         self.assertEqual(set(t._modified_fields),
 
 792                          set(['status', 'description', 'start']))
 
 794     def test_export_data(self):
 
 795         t = Task(self.tw, description="test task",
 
 797             due=pytz.utc.localize(datetime.datetime(2015,1,1,23,23,23)))
 
 799         # Check that the output is a permutation of:
 
 800         # {"project":"Home","description":"test task","due":"20150101232323Z"}
 
 801         allowed_segments = self.exported_raw_data[1:-1].split(',')
 
 803             '{' + ','.join(segments) + '}'
 
 804             for segments in itertools.permutations(allowed_segments)
 
 807         self.assertTrue(any(t.export_data() == expected
 
 808                             for expected in allowed_output))
 
 810 class TimezoneAwareDatetimeTest(TasklibTest):
 
 813         super(TimezoneAwareDatetimeTest, self).setUp()
 
 814         self.zone = local_zone
 
 815         self.localdate_naive = datetime.datetime(2015,2,2)
 
 816         self.localtime_naive = datetime.datetime(2015,2,2,0,0,0)
 
 817         self.localtime_aware = self.zone.localize(self.localtime_naive)
 
 818         self.utctime_aware = self.localtime_aware.astimezone(pytz.utc)
 
 820     def test_timezone_naive_datetime_setitem(self):
 
 821         t = Task(self.tw, description="test task")
 
 822         t['due'] = self.localtime_naive
 
 823         self.assertEqual(t['due'], self.localtime_aware)
 
 825     def test_timezone_naive_datetime_using_init(self):
 
 826         t = Task(self.tw, description="test task", due=self.localtime_naive)
 
 827         self.assertEqual(t['due'], self.localtime_aware)
 
 829     def test_filter_by_naive_datetime(self):
 
 830         t = Task(self.tw, description="task1", due=self.localtime_naive)
 
 832         matching_tasks = self.tw.tasks.filter(due=self.localtime_naive)
 
 833         self.assertEqual(len(matching_tasks), 1)
 
 835     def test_serialize_naive_datetime(self):
 
 836         t = Task(self.tw, description="task1", due=self.localtime_naive)
 
 837         self.assertEqual(json.loads(t.export_data())['due'],
 
 838                          self.utctime_aware.strftime(DATE_FORMAT))
 
 840     def test_timezone_naive_date_setitem(self):
 
 841         t = Task(self.tw, description="test task")
 
 842         t['due'] = self.localdate_naive
 
 843         self.assertEqual(t['due'], self.localtime_aware)
 
 845     def test_timezone_naive_date_using_init(self):
 
 846         t = Task(self.tw, description="test task", due=self.localdate_naive)
 
 847         self.assertEqual(t['due'], self.localtime_aware)
 
 849     def test_filter_by_naive_date(self):
 
 850         t = Task(self.tw, description="task1", due=self.localdate_naive)
 
 852         matching_tasks = self.tw.tasks.filter(due=self.localdate_naive)
 
 853         self.assertEqual(len(matching_tasks), 1)
 
 855     def test_serialize_naive_date(self):
 
 856         t = Task(self.tw, description="task1", due=self.localdate_naive)
 
 857         self.assertEqual(json.loads(t.export_data())['due'],
 
 858                          self.utctime_aware.strftime(DATE_FORMAT))
 
 860     def test_timezone_aware_datetime_setitem(self):
 
 861         t = Task(self.tw, description="test task")
 
 862         t['due'] = self.localtime_aware
 
 863         self.assertEqual(t['due'], self.localtime_aware)
 
 865     def test_timezone_aware_datetime_using_init(self):
 
 866         t = Task(self.tw, description="test task", due=self.localtime_aware)
 
 867         self.assertEqual(t['due'], self.localtime_aware)
 
 869     def test_filter_by_aware_datetime(self):
 
 870         t = Task(self.tw, description="task1", due=self.localtime_aware)
 
 872         matching_tasks = self.tw.tasks.filter(due=self.localtime_aware)
 
 873         self.assertEqual(len(matching_tasks), 1)
 
 875     def test_serialize_aware_datetime(self):
 
 876         t = Task(self.tw, description="task1", due=self.localtime_aware)
 
 877         self.assertEqual(json.loads(t.export_data())['due'],
 
 878                          self.utctime_aware.strftime(DATE_FORMAT))
 
 880 class DatetimeStringTest(TasklibTest):
 
 882     def test_simple_now_conversion(self):
 
 883         if self.tw.version < six.text_type('2.4.0'):
 
 884             # Python2.6 does not support SkipTest. As a workaround
 
 885             # mark the test as passed by exiting.
 
 886             if getattr(unittest, 'SkipTest', None) is not None:
 
 887                 raise unittest.SkipTest()
 
 891         t = Task(self.tw, description="test task", due="now")
 
 892         now = local_zone.localize(datetime.datetime.now())
 
 894         # Assert that both times are not more than 5 seconds apart
 
 895         if sys.version_info < (2,7):
 
 896             self.assertTrue(total_seconds_2_6(now - t['due']) < 5)
 
 897             self.assertTrue(total_seconds_2_6(t['due'] - now) < 5)
 
 899             self.assertTrue((now - t['due']).total_seconds() < 5)
 
 900             self.assertTrue((t['due'] - now).total_seconds() < 5)
 
 902     def test_simple_eoy_conversion(self):
 
 903         if self.tw.version < six.text_type('2.4.0'):
 
 904             # Python2.6 does not support SkipTest. As a workaround
 
 905             # mark the test as passed by exiting.
 
 906             if getattr(unittest, 'SkipTest', None) is not None:
 
 907                 raise unittest.SkipTest()
 
 911         t = Task(self.tw, description="test task", due="eoy")
 
 912         now = local_zone.localize(datetime.datetime.now())
 
 913         eoy = local_zone.localize(datetime.datetime(
 
 921         self.assertEqual(eoy, t['due'])
 
 923     def test_complex_eoy_conversion(self):
 
 924         if self.tw.version < six.text_type('2.4.0'):
 
 925             # Python2.6 does not support SkipTest. As a workaround
 
 926             # mark the test as passed by exiting.
 
 927             if getattr(unittest, 'SkipTest', None) is not None:
 
 928                 raise unittest.SkipTest()
 
 932         t = Task(self.tw, description="test task", due="eoy - 4 months")
 
 933         now = local_zone.localize(datetime.datetime.now())
 
 934         due_date = local_zone.localize(datetime.datetime(
 
 941             )) - datetime.timedelta(0,4 * 30 * 86400)
 
 942         self.assertEqual(due_date, t['due'])
 
 944     def test_filtering_with_string_datetime(self):
 
 945         if self.tw.version < six.text_type('2.4.0'):
 
 946             # Python2.6 does not support SkipTest. As a workaround
 
 947             # mark the test as passed by exiting.
 
 948             if getattr(unittest, 'SkipTest', None) is not None:
 
 949                 raise unittest.SkipTest()
 
 953         t = Task(self.tw, description="test task",
 
 954                  due=datetime.datetime.now() - datetime.timedelta(0,2))
 
 956         self.assertEqual(len(self.tw.tasks.filter(due__before="now")), 1)
 
 958 class AnnotationTest(TasklibTest):
 
 961         super(AnnotationTest, self).setUp()
 
 962         Task(self.tw, description="test task").save()
 
 964     def test_adding_annotation(self):
 
 965         task = self.tw.tasks.get()
 
 966         task.add_annotation('test annotation')
 
 967         self.assertEqual(len(task['annotations']), 1)
 
 968         ann = task['annotations'][0]
 
 969         self.assertEqual(ann['description'], 'test annotation')
 
 971     def test_removing_annotation(self):
 
 972         task = self.tw.tasks.get()
 
 973         task.add_annotation('test annotation')
 
 974         ann = task['annotations'][0]
 
 976         self.assertEqual(len(task['annotations']), 0)
 
 978     def test_removing_annotation_by_description(self):
 
 979         task = self.tw.tasks.get()
 
 980         task.add_annotation('test annotation')
 
 981         task.remove_annotation('test annotation')
 
 982         self.assertEqual(len(task['annotations']), 0)
 
 984     def test_removing_annotation_by_obj(self):
 
 985         task = self.tw.tasks.get()
 
 986         task.add_annotation('test annotation')
 
 987         ann = task['annotations'][0]
 
 988         task.remove_annotation(ann)
 
 989         self.assertEqual(len(task['annotations']), 0)
 
 991     def test_annotation_after_modification(self):
 
 992          task = self.tw.tasks.get()
 
 993          task['project'] = 'test'
 
 994          task.add_annotation('I should really do this task')
 
 995          self.assertEqual(task['project'], 'test')
 
 997          self.assertEqual(task['project'], 'test')
 
 999     def test_serialize_annotations(self):
 
1000         # Test that serializing annotations is possible
 
1001         t = Task(self.tw, description="test")
 
1004         t.add_annotation("annotation1")
 
1005         t.add_annotation("annotation2")
 
1007         data = t._serialize('annotations', t._data['annotations'])
 
1009         self.assertEqual(len(data), 2)
 
1010         self.assertEqual(type(data[0]), dict)
 
1011         self.assertEqual(type(data[1]), dict)
 
1013         self.assertEqual(data[0]['description'], "annotation1")
 
1014         self.assertEqual(data[1]['description'], "annotation2")
 
1017 class UnicodeTest(TasklibTest):
 
1019     def test_unicode_task(self):
 
1020         Task(self.tw, description=six.u("†åßk")).save()
 
1023     def test_filter_by_unicode_task(self):
 
1024         Task(self.tw, description=six.u("†åßk")).save()
 
1025         tasks = self.tw.tasks.filter(description=six.u("†åßk"))
 
1026         self.assertEqual(len(tasks), 1)
 
1028     def test_non_unicode_task(self):
 
1029         Task(self.tw, description="test task").save()
 
1032 class ReadOnlyDictViewTest(unittest.TestCase):
 
1035         self.sample = dict(l=[1,2,3], d={'k':'v'})
 
1036         self.original_sample = copy.deepcopy(self.sample)
 
1037         self.view = ReadOnlyDictView(self.sample)
 
1039     def test_readonlydictview_getitem(self):
 
1041         self.assertEqual(l, self.sample['l'])
 
1043         # Assert that modification changed only copied value
 
1045         self.assertNotEqual(l, self.sample['l'])
 
1047         # Assert that viewed dict is not changed
 
1048         self.assertEqual(self.sample, self.original_sample)
 
1050     def test_readonlydictview_contains(self):
 
1051         self.assertEqual('l' in self.view, 'l' in self.sample)
 
1052         self.assertEqual('d' in self.view, 'd' in self.sample)
 
1053         self.assertEqual('k' in self.view, 'k' in self.sample)
 
1055         # Assert that viewed dict is not changed
 
1056         self.assertEqual(self.sample, self.original_sample)
 
1058     def test_readonlydictview_iter(self):
 
1059         self.assertEqual(list(k for k in self.view),
 
1060                          list(k for k in self.sample))
 
1062         # Assert the view is correct after modification
 
1063         self.sample['new'] = 'value'
 
1064         self.assertEqual(list(k for k in self.view),
 
1065                          list(k for k in self.sample))
 
1067     def test_readonlydictview_len(self):
 
1068         self.assertEqual(len(self.view), len(self.sample))
 
1070         # Assert the view is correct after modification
 
1071         self.sample['new'] = 'value'
 
1072         self.assertEqual(len(self.view), len(self.sample))
 
1074     def test_readonlydictview_get(self):
 
1075         l = self.view.get('l')
 
1076         self.assertEqual(l, self.sample.get('l'))
 
1078         # Assert that modification changed only copied value
 
1080         self.assertNotEqual(l, self.sample.get('l'))
 
1082         # Assert that viewed dict is not changed
 
1083         self.assertEqual(self.sample, self.original_sample)
 
1085     def test_readonlydict_items(self):
 
1086         view_items = self.view.items()
 
1087         sample_items = list(self.sample.items())
 
1088         self.assertEqual(view_items, sample_items)
 
1090         view_items.append('newkey')
 
1091         self.assertNotEqual(view_items, sample_items)
 
1092         self.assertEqual(self.sample, self.original_sample)
 
1094     def test_readonlydict_values(self):
 
1095         view_values = self.view.values()
 
1096         sample_values = list(self.sample.values())
 
1097         self.assertEqual(view_values, sample_values)
 
1099         view_list_item = list(filter(lambda x: type(x) is list,
 
1101         view_list_item.append(4)
 
1102         self.assertNotEqual(view_values, sample_values)
 
1103         self.assertEqual(self.sample, self.original_sample)